1e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka/*
2e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * Copyright (C) 2012 The Android Open Source Project
3e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *
4e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * Licensed under the Apache License, Version 2.0 (the "License");
5e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * you may not use this file except in compliance with the License.
6e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * You may obtain a copy of the License at
7e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *
8e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *      http://www.apache.org/licenses/LICENSE-2.0
9e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *
10e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * Unless required by applicable law or agreed to in writing, software
11e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * distributed under the License is distributed on an "AS IS" BASIS,
12e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * See the License for the specific language governing permissions and
14e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * limitations under the License.
15e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka */
16e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
17e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkapackage com.android.gallery3d.exif;
18e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
19e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport android.util.Log;
20e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
21e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.io.BufferedOutputStream;
22e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.io.FilterOutputStream;
23e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.io.IOException;
24e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.io.OutputStream;
25e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.nio.ByteBuffer;
26e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.nio.ByteOrder;
27e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaimport java.util.ArrayList;
28e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
29e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka/**
30e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * This class provides a way to replace the Exif header of a JPEG image.
31e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * <p>
32e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * Below is an example of writing EXIF data into a file
33e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *
34e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * <pre>
35e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
36e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *     OutputStream os = null;
37e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *     try {
38e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *         os = new FileOutputStream(path);
39e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *         ExifOutputStream eos = new ExifOutputStream(os);
40e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *         // Set the exif header
41e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *         eos.setExifData(exif);
42e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *         // Write the original jpeg out, the header will be add into the file.
43e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *         eos.write(jpeg);
44e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *     } catch (FileNotFoundException e) {
45e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *         e.printStackTrace();
46e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *     } catch (IOException e) {
47e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *         e.printStackTrace();
48e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *     } finally {
49e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *         if (os != null) {
50e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *             try {
51e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *                 os.close();
52e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *             } catch (IOException e) {
53e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *                 e.printStackTrace();
54e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *             }
55e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *         }
56e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka *     }
57e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * }
58e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka * </pre>
59e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka */
60e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurkaclass ExifOutputStream extends FilterOutputStream {
61e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final String TAG = "ExifOutputStream";
62e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final boolean DEBUG = false;
63e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
64e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
65e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final int STATE_SOI = 0;
66e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final int STATE_FRAME_HEADER = 1;
67e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final int STATE_JPEG_DATA = 2;
68e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
69e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final int EXIF_HEADER = 0x45786966;
70e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final short TIFF_HEADER = 0x002A;
71e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final short TIFF_BIG_ENDIAN = 0x4d4d;
72e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final short TIFF_LITTLE_ENDIAN = 0x4949;
73e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final short TAG_SIZE = 12;
74e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final short TIFF_HEADER_SIZE = 8;
75e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private static final int MAX_EXIF_SIZE = 65535;
76e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
77e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private ExifData mExifData;
78e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private int mState = STATE_SOI;
79e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private int mByteToSkip;
80e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private int mByteToCopy;
81e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private byte[] mSingleByteArray = new byte[1];
82e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private ByteBuffer mBuffer = ByteBuffer.allocate(4);
83e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private final ExifInterface mInterface;
84e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
85e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
86e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
87e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mInterface = iRef;
88e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
89e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
90e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    /**
91e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * Sets the ExifData to be written into the JPEG file. Should be called
92e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * before writing image data.
93e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     */
94e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected void setExifData(ExifData exifData) {
95e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mExifData = exifData;
96e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
97e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
98e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    /**
99e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * Gets the Exif header to be written into the JPEF file.
100e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     */
101e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    protected ExifData getExifData() {
102e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return mExifData;
103e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
104e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
105e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private int requestByteToBuffer(int requestByteCount, byte[] buffer
106e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            , int offset, int length) {
107e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        int byteNeeded = requestByteCount - mBuffer.position();
108e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        int byteToRead = length > byteNeeded ? byteNeeded : length;
109e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mBuffer.put(buffer, offset, byteToRead);
110e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return byteToRead;
111e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
112e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
113e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    /**
114e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * Writes the image out. The input data should be a valid JPEG format. After
115e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * writing, it's Exif header will be replaced by the given header.
116e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     */
117e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    @Override
118e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public void write(byte[] buffer, int offset, int length) throws IOException {
119e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
120e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                && length > 0) {
121e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (mByteToSkip > 0) {
122e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
123e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                length -= byteToProcess;
124e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                mByteToSkip -= byteToProcess;
125e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                offset += byteToProcess;
126e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
127e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (mByteToCopy > 0) {
128e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
129e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                out.write(buffer, offset, byteToProcess);
130e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                length -= byteToProcess;
131e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                mByteToCopy -= byteToProcess;
132e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                offset += byteToProcess;
133e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
134e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (length == 0) {
135e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                return;
136e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
137e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            switch (mState) {
138e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                case STATE_SOI:
139e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    int byteRead = requestByteToBuffer(2, buffer, offset, length);
140e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    offset += byteRead;
141e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    length -= byteRead;
142e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    if (mBuffer.position() < 2) {
143e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        return;
144e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
145e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    mBuffer.rewind();
146e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    if (mBuffer.getShort() != JpegHeader.SOI) {
147e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        throw new IOException("Not a valid jpeg image, cannot write exif");
148e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
149e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    out.write(mBuffer.array(), 0, 2);
150e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    mState = STATE_FRAME_HEADER;
151e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    mBuffer.rewind();
152e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    writeExifData();
153e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    break;
154e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                case STATE_FRAME_HEADER:
155e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    // We ignore the APP1 segment and copy all other segments
156e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    // until SOF tag.
157e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    byteRead = requestByteToBuffer(4, buffer, offset, length);
158e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    offset += byteRead;
159e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    length -= byteRead;
160e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    // Check if this image data doesn't contain SOF.
161e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    if (mBuffer.position() == 2) {
162e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        short tag = mBuffer.getShort();
163e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        if (tag == JpegHeader.EOI) {
164e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                            out.write(mBuffer.array(), 0, 2);
165e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                            mBuffer.rewind();
166e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        }
167e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
168e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    if (mBuffer.position() < 4) {
169e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        return;
170e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
171e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    mBuffer.rewind();
172e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    short marker = mBuffer.getShort();
173e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    if (marker == JpegHeader.APP1) {
174e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
175e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        mState = STATE_JPEG_DATA;
176e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    } else if (!JpegHeader.isSofMarker(marker)) {
177e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        out.write(mBuffer.array(), 0, 4);
178e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
179e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    } else {
180e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        out.write(mBuffer.array(), 0, 4);
181e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        mState = STATE_JPEG_DATA;
182e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    }
183e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    mBuffer.rewind();
184e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
185e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
186e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (length > 0) {
187e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            out.write(buffer, offset, length);
188e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
189e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
190e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
191e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    /**
192e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * Writes the one bytes out. The input data should be a valid JPEG format.
193e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * After writing, it's Exif header will be replaced by the given header.
194e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     */
195e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    @Override
196e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public void write(int oneByte) throws IOException {
197e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        mSingleByteArray[0] = (byte) (0xff & oneByte);
198e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        write(mSingleByteArray);
199e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
200e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
201e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    /**
202e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     * Equivalent to calling write(buffer, 0, buffer.length).
203e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka     */
204e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    @Override
205e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    public void write(byte[] buffer) throws IOException {
206e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        write(buffer, 0, buffer.length);
207e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
208e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
209e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private void writeExifData() throws IOException {
210e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (mExifData == null) {
211e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            return;
212e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
213e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (DEBUG) {
214e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            Log.v(TAG, "Writing exif data...");
215e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
216e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData);
217e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        createRequiredIfdAndTag();
218e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        int exifSize = calculateAllOffset();
219e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (exifSize + 8 > MAX_EXIF_SIZE) {
220e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            throw new IOException("Exif header is too large (>64Kb)");
221e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
222e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
223e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
224e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        dataOutputStream.writeShort(JpegHeader.APP1);
225e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        dataOutputStream.writeShort((short) (exifSize + 8));
226e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        dataOutputStream.writeInt(EXIF_HEADER);
227e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        dataOutputStream.writeShort((short) 0x0000);
228e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
229e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
230e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        } else {
231e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
232e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
233e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        dataOutputStream.setByteOrder(mExifData.getByteOrder());
234e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        dataOutputStream.writeShort(TIFF_HEADER);
235e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        dataOutputStream.writeInt(8);
236e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        writeAllTags(dataOutputStream);
237e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        writeThumbnail(dataOutputStream);
238e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        for (ExifTag t : nullTags) {
239e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mExifData.addTag(t);
240e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
241e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
242e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
243e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private ArrayList<ExifTag> stripNullValueTags(ExifData data) {
244e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
245e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        for(ExifTag t : data.getAllTags()) {
246e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) {
247e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                data.removeTag(t.getTagId(), t.getIfd());
248e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                nullTags.add(t);
249e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
250e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
251e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return nullTags;
252e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
253e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
254e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
255e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (mExifData.hasCompressedThumbnail()) {
256e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            dataOutputStream.write(mExifData.getCompressedThumbnail());
257e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        } else if (mExifData.hasUncompressedStrip()) {
258e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            for (int i = 0; i < mExifData.getStripCount(); i++) {
259e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                dataOutputStream.write(mExifData.getStrip(i));
260e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
261e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
262e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
263e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
264e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
265e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
266e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
267e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
268e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (interoperabilityIfd != null) {
269e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            writeIfd(interoperabilityIfd, dataOutputStream);
270e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
271e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
272e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (gpsIfd != null) {
273e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            writeIfd(gpsIfd, dataOutputStream);
274e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
275e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
276e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (ifd1 != null) {
277e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
278e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
279e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
280e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
281e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
282e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            throws IOException {
283e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        ExifTag[] tags = ifd.getAllTags();
284e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        dataOutputStream.writeShort((short) tags.length);
285e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        for (ExifTag tag : tags) {
286e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            dataOutputStream.writeShort(tag.getTagId());
287e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            dataOutputStream.writeShort(tag.getDataType());
288e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            dataOutputStream.writeInt(tag.getComponentCount());
289e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (DEBUG) {
290e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                Log.v(TAG, "\n" + tag.toString());
291e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
292e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (tag.getDataSize() > 4) {
293e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                dataOutputStream.writeInt(tag.getOffset());
294e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            } else {
295e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                ExifOutputStream.writeTagValue(tag, dataOutputStream);
296e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
297e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    dataOutputStream.write(0);
298e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
299e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
300e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
301e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
302e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        for (ExifTag tag : tags) {
303e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (tag.getDataSize() > 4) {
304e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                ExifOutputStream.writeTagValue(tag, dataOutputStream);
305e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
306e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
307e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
308e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
309e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private int calculateOffsetOfIfd(IfdData ifd, int offset) {
310e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
311e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        ExifTag[] tags = ifd.getAllTags();
312e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        for (ExifTag tag : tags) {
313e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (tag.getDataSize() > 4) {
314e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                tag.setOffset(offset);
315e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                offset += tag.getDataSize();
316e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
317e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
318e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return offset;
319e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
320e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
321e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private void createRequiredIfdAndTag() throws IOException {
322e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // IFD0 is required for all file
323e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
324e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (ifd0 == null) {
325e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd0 = new IfdData(IfdId.TYPE_IFD_0);
326e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mExifData.addIfdData(ifd0);
327e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
328e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
329e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (exifOffsetTag == null) {
330e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            throw new IOException("No definition for crucial exif tag: "
331e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    + ExifInterface.TAG_EXIF_IFD);
332e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
333e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        ifd0.setTag(exifOffsetTag);
334e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
335e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // Exif IFD is required for all files.
336e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
337e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (exifIfd == null) {
338e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
339e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            mExifData.addIfdData(exifIfd);
340e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
341e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
342e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // GPS IFD
343e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
344e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (gpsIfd != null) {
345e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
346e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (gpsOffsetTag == null) {
347e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                throw new IOException("No definition for crucial exif tag: "
348e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        + ExifInterface.TAG_GPS_IFD);
349e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
350e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd0.setTag(gpsOffsetTag);
351e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
352e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
353e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // Interoperability IFD
354e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
355e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (interIfd != null) {
356e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ExifTag interOffsetTag = mInterface
357e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
358e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (interOffsetTag == null) {
359e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                throw new IOException("No definition for crucial exif tag: "
360e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        + ExifInterface.TAG_INTEROPERABILITY_IFD);
361e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
362e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            exifIfd.setTag(interOffsetTag);
363e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
364e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
365e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
366e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
367e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // thumbnail
368e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (mExifData.hasCompressedThumbnail()) {
369e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
370e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (ifd1 == null) {
371e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
372e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                mExifData.addIfdData(ifd1);
373e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
374e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
375e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ExifTag offsetTag = mInterface
376e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
377e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (offsetTag == null) {
378e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                throw new IOException("No definition for crucial exif tag: "
379e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
380e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
381e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
382e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.setTag(offsetTag);
383e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ExifTag lengthTag = mInterface
384e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
385e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (lengthTag == null) {
386e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                throw new IOException("No definition for crucial exif tag: "
387e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
388e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
389e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
390e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            lengthTag.setValue(mExifData.getCompressedThumbnail().length);
391e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.setTag(lengthTag);
392e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
393e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            // Get rid of tags for uncompressed if they exist.
394e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
395e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
396e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        } else if (mExifData.hasUncompressedStrip()) {
397e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (ifd1 == null) {
398e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
399e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                mExifData.addIfdData(ifd1);
400e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
401e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            int stripCount = mExifData.getStripCount();
402e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
403e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (offsetTag == null) {
404e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                throw new IOException("No definition for crucial exif tag: "
405e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        + ExifInterface.TAG_STRIP_OFFSETS);
406e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
407e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ExifTag lengthTag = mInterface
408e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
409e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            if (lengthTag == null) {
410e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                throw new IOException("No definition for crucial exif tag: "
411e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                        + ExifInterface.TAG_STRIP_BYTE_COUNTS);
412e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
413e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            long[] lengths = new long[stripCount];
414e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            for (int i = 0; i < mExifData.getStripCount(); i++) {
415e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                lengths[i] = mExifData.getStrip(i).length;
416e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
417e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            lengthTag.setValue(lengths);
418e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.setTag(offsetTag);
419e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.setTag(lengthTag);
420e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            // Get rid of tags for compressed if they exist.
421e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
422e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.removeTag(ExifInterface
423e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
424e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        } else if (ifd1 != null) {
425e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            // Get rid of offset and length tags if there is no thumbnail.
426e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
427e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
428e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
429e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.removeTag(ExifInterface
430e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
431e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
432e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
433e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
434e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    private int calculateAllOffset() {
435e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        int offset = TIFF_HEADER_SIZE;
436e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
437e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        offset = calculateOffsetOfIfd(ifd0, offset);
438e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
439e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
440e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
441e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        offset = calculateOffsetOfIfd(exifIfd, offset);
442e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
443e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
444e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (interIfd != null) {
445e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
446e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    .setValue(offset);
447e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            offset = calculateOffsetOfIfd(interIfd, offset);
448e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
449e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
450e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
451e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (gpsIfd != null) {
452e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
453e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            offset = calculateOffsetOfIfd(gpsIfd, offset);
454e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
455e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
456e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
457e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (ifd1 != null) {
458e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd0.setOffsetToNextIfd(offset);
459e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            offset = calculateOffsetOfIfd(ifd1, offset);
460e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
461e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
462e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        // thumbnail
463e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        if (mExifData.hasCompressedThumbnail()) {
464e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
465e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    .setValue(offset);
466e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            offset += mExifData.getCompressedThumbnail().length;
467e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        } else if (mExifData.hasUncompressedStrip()) {
468e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            int stripCount = mExifData.getStripCount();
469e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            long[] offsets = new long[stripCount];
470e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            for (int i = 0; i < mExifData.getStripCount(); i++) {
471e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                offsets[i] = offset;
472e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                offset += mExifData.getStrip(i).length;
473e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            }
474e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
475e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    offsets);
476e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
477e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        return offset;
478e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
479e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka
480e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
481e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            throws IOException {
482e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        switch (tag.getDataType()) {
483e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            case ExifTag.TYPE_ASCII:
484e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                byte buf[] = tag.getStringByte();
485e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                if (buf.length == tag.getComponentCount()) {
486e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    buf[buf.length - 1] = 0;
487e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    dataOutputStream.write(buf);
488e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                } else {
489e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    dataOutputStream.write(buf);
490e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    dataOutputStream.write(0);
491e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
492e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                break;
493e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            case ExifTag.TYPE_LONG:
494e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            case ExifTag.TYPE_UNSIGNED_LONG:
495e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
496e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    dataOutputStream.writeInt((int) tag.getValueAt(i));
497e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
498e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                break;
499e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            case ExifTag.TYPE_RATIONAL:
500e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            case ExifTag.TYPE_UNSIGNED_RATIONAL:
501e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
502e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    dataOutputStream.writeRational(tag.getRational(i));
503e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
504e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                break;
505e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            case ExifTag.TYPE_UNDEFINED:
506e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            case ExifTag.TYPE_UNSIGNED_BYTE:
507e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                buf = new byte[tag.getComponentCount()];
508e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                tag.getBytes(buf);
509e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                dataOutputStream.write(buf);
510e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                break;
511e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka            case ExifTag.TYPE_UNSIGNED_SHORT:
512e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
513e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                    dataOutputStream.writeShort((short) tag.getValueAt(i));
514e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                }
515e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka                break;
516e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka        }
517e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka    }
518e8d1bf7a439450b9979701909164a6baffbe8baeMichael Jurka}
519