14f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou/*
24f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou * Copyright (C) 2012 The Android Open Source Project
34f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou *
44f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou * Licensed under the Apache License, Version 2.0 (the "License");
54f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou * you may not use this file except in compliance with the License.
64f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou * You may obtain a copy of the License at
74f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou *
84f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou *      http://www.apache.org/licenses/LICENSE-2.0
94f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou *
104f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou * Unless required by applicable law or agreed to in writing, software
114f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou * distributed under the License is distributed on an "AS IS" BASIS,
124f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou * See the License for the specific language governing permissions and
144f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou * limitations under the License.
154f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou */
164f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
174f529e7cada294befb66a1fc9b72b1aa164597dfEarl Oupackage com.android.gallery3d.exif;
184f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
196e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunkimport android.util.Log;
206e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk
214bee0f203dc96652aca02516f6f2d7d2493fab52Ruben Brunkimport java.io.BufferedOutputStream;
224f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ouimport java.io.FilterOutputStream;
234f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ouimport java.io.IOException;
244f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ouimport java.io.OutputStream;
254f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ouimport java.nio.ByteBuffer;
264f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ouimport java.nio.ByteOrder;
27c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunkimport java.util.ArrayList;
284f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
293223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou/**
303223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou * This class provides a way to replace the Exif header of a JPEG image.
313223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou * <p>
323223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou * Below is an example of writing EXIF data into a file
336e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk *
343223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou * <pre>
353223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
363223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *     OutputStream os = null;
373223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *     try {
383223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *         os = new FileOutputStream(path);
393223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *         ExifOutputStream eos = new ExifOutputStream(os);
403223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *         // Set the exif header
413223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *         eos.setExifData(exif);
423223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *         // Write the original jpeg out, the header will be add into the file.
433223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *         eos.write(jpeg);
443223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *     } catch (FileNotFoundException e) {
453223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *         e.printStackTrace();
463223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *     } catch (IOException e) {
473223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *         e.printStackTrace();
483223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *     } finally {
493223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *         if (os != null) {
503223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *             try {
513223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *                 os.close();
523223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *             } catch (IOException e) {
533223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *                 e.printStackTrace();
543223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *             }
553223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *         }
563223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou *     }
573223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou * }
583223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou * </pre>
593223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou */
606e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunkclass ExifOutputStream extends FilterOutputStream {
614f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private static final String TAG = "ExifOutputStream";
626e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk    private static final boolean DEBUG = false;
634bee0f203dc96652aca02516f6f2d7d2493fab52Ruben Brunk    private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
644f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
654f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private static final int STATE_SOI = 0;
66b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou    private static final int STATE_FRAME_HEADER = 1;
674f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private static final int STATE_JPEG_DATA = 2;
684f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
694f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private static final int EXIF_HEADER = 0x45786966;
704f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private static final short TIFF_HEADER = 0x002A;
714f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private static final short TIFF_BIG_ENDIAN = 0x4d4d;
724f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private static final short TIFF_LITTLE_ENDIAN = 0x4949;
734f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private static final short TAG_SIZE = 12;
744f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private static final short TIFF_HEADER_SIZE = 8;
756e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk    private static final int MAX_EXIF_SIZE = 65535;
764f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
774f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private ExifData mExifData;
78e48c0f96ee3f9654c3538664dd59485927fb44d6Earl Ou    private int mState = STATE_SOI;
794f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private int mByteToSkip;
804f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private int mByteToCopy;
8114954a62d0d103a5b120225ccf1d7da38b8694e3Ruben Brunk    private byte[] mSingleByteArray = new byte[1];
824f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private ByteBuffer mBuffer = ByteBuffer.allocate(4);
836e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk    private final ExifInterface mInterface;
844f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
856e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk    protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
864bee0f203dc96652aca02516f6f2d7d2493fab52Ruben Brunk        super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
876e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        mInterface = iRef;
884f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
894f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
903223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou    /**
916e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk     * Sets the ExifData to be written into the JPEG file. Should be called
926e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk     * before writing image data.
933223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou     */
946e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk    protected void setExifData(ExifData exifData) {
954f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        mExifData = exifData;
964f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
974f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
983223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou    /**
993223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou     * Gets the Exif header to be written into the JPEF file.
1003223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou     */
1016e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk    protected ExifData getExifData() {
1024f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        return mExifData;
1034f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
1044f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
1054f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private int requestByteToBuffer(int requestByteCount, byte[] buffer
1064f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            , int offset, int length) {
1074f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        int byteNeeded = requestByteCount - mBuffer.position();
1084f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        int byteToRead = length > byteNeeded ? byteNeeded : length;
1094f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        mBuffer.put(buffer, offset, byteToRead);
1104f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        return byteToRead;
1114f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
1124f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
1133223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou    /**
1146e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk     * Writes the image out. The input data should be a valid JPEG format. After
1156e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk     * writing, it's Exif header will be replaced by the given header.
1163223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou     */
1174f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    @Override
1184f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    public void write(byte[] buffer, int offset, int length) throws IOException {
1196e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
1204f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                && length > 0) {
1214f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            if (mByteToSkip > 0) {
1224f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
1234f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                length -= byteToProcess;
1244f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                mByteToSkip -= byteToProcess;
1254f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                offset += byteToProcess;
1264f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            }
1274f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            if (mByteToCopy > 0) {
1284f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
1294f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                out.write(buffer, offset, byteToProcess);
1304f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                length -= byteToProcess;
1314f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                mByteToCopy -= byteToProcess;
1324f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                offset += byteToProcess;
1334f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            }
1346e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            if (length == 0) {
1356e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                return;
1366e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            }
1374f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            switch (mState) {
1384f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                case STATE_SOI:
1394f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    int byteRead = requestByteToBuffer(2, buffer, offset, length);
1404f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    offset += byteRead;
1414f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    length -= byteRead;
1426e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    if (mBuffer.position() < 2) {
1436e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                        return;
1446e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    }
1454f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    mBuffer.rewind();
1466e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    if (mBuffer.getShort() != JpegHeader.SOI) {
1476e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                        throw new IOException("Not a valid jpeg image, cannot write exif");
1486e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    }
1496e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    out.write(mBuffer.array(), 0, 2);
150b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                    mState = STATE_FRAME_HEADER;
1514f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    mBuffer.rewind();
152b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                    writeExifData();
1534f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    break;
154b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                case STATE_FRAME_HEADER:
1556e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    // We ignore the APP1 segment and copy all other segments
1566e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    // until SOF tag.
1574f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    byteRead = requestByteToBuffer(4, buffer, offset, length);
1584f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    offset += byteRead;
1594f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    length -= byteRead;
160b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                    // Check if this image data doesn't contain SOF.
161b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                    if (mBuffer.position() == 2) {
162b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                        short tag = mBuffer.getShort();
1634cec566b34655e76b90f8ca089f151e6f42ede0cEarl Ou                        if (tag == JpegHeader.EOI) {
164b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                            out.write(mBuffer.array(), 0, 2);
165b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                            mBuffer.rewind();
166b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                        }
167b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                    }
1686e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    if (mBuffer.position() < 4) {
1696e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                        return;
1706e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    }
1714f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    mBuffer.rewind();
172b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                    short marker = mBuffer.getShort();
1734cec566b34655e76b90f8ca089f151e6f42ede0cEarl Ou                    if (marker == JpegHeader.APP1) {
1746e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                        mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
1754f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                        mState = STATE_JPEG_DATA;
1764cec566b34655e76b90f8ca089f151e6f42ede0cEarl Ou                    } else if (!JpegHeader.isSofMarker(marker)) {
177b6cb8121aa89d06d1fc397d443d22224b634da88Earl Ou                        out.write(mBuffer.array(), 0, 4);
1786e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                        mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
1794f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    } else {
1804f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                        out.write(mBuffer.array(), 0, 4);
1814f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                        mState = STATE_JPEG_DATA;
1824f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    }
1834f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    mBuffer.rewind();
1844f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            }
1854f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
1864f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (length > 0) {
1874f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            out.write(buffer, offset, length);
1884f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
1894f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
1904f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
1913223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou    /**
1926e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk     * Writes the one bytes out. The input data should be a valid JPEG format.
1936e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk     * After writing, it's Exif header will be replaced by the given header.
1943223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou     */
1954f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    @Override
1964f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    public void write(int oneByte) throws IOException {
19714954a62d0d103a5b120225ccf1d7da38b8694e3Ruben Brunk        mSingleByteArray[0] = (byte) (0xff & oneByte);
19814954a62d0d103a5b120225ccf1d7da38b8694e3Ruben Brunk        write(mSingleByteArray);
1994f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
2004f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
2013223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou    /**
2023223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou     * Equivalent to calling write(buffer, 0, buffer.length).
2033223b4c4505837f2e2347e813fca96bbbe3191aaEarl Ou     */
2044f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    @Override
2054f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    public void write(byte[] buffer) throws IOException {
2064f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        write(buffer, 0, buffer.length);
2074f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
2084f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
2094f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private void writeExifData() throws IOException {
2106e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        if (mExifData == null) {
2116e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            return;
2126e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        }
2136e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        if (DEBUG) {
2146e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            Log.v(TAG, "Writing exif data...");
2156e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        }
216c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk        ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData);
2174f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        createRequiredIfdAndTag();
2184f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        int exifSize = calculateAllOffset();
2196e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        if (exifSize + 8 > MAX_EXIF_SIZE) {
2206e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            throw new IOException("Exif header is too large (>64Kb)");
2216e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        }
2224f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
223e48c0f96ee3f9654c3538664dd59485927fb44d6Earl Ou        dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
2244cec566b34655e76b90f8ca089f151e6f42ede0cEarl Ou        dataOutputStream.writeShort(JpegHeader.APP1);
2254f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        dataOutputStream.writeShort((short) (exifSize + 8));
2264f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        dataOutputStream.writeInt(EXIF_HEADER);
2274f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        dataOutputStream.writeShort((short) 0x0000);
2284f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
2294f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
2304f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        } else {
2314f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
2324f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
2334f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        dataOutputStream.setByteOrder(mExifData.getByteOrder());
2344f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        dataOutputStream.writeShort(TIFF_HEADER);
2354f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        dataOutputStream.writeInt(8);
236e48c0f96ee3f9654c3538664dd59485927fb44d6Earl Ou        writeAllTags(dataOutputStream);
2374f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        writeThumbnail(dataOutputStream);
238c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk        for (ExifTag t : nullTags) {
239c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk            mExifData.addTag(t);
240c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk        }
241c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk    }
242c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk
243c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk    private ArrayList<ExifTag> stripNullValueTags(ExifData data) {
244c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk        ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
245c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk        for(ExifTag t : data.getAllTags()) {
246c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk            if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) {
247c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk                data.removeTag(t.getTagId(), t.getIfd());
248c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk                nullTags.add(t);
249c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk            }
250c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk        }
251c59be38287ed3d58f6945d63713b03ac20510c22Ruben Brunk        return nullTags;
2524f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
2534f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
2544f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
2554f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (mExifData.hasCompressedThumbnail()) {
2564f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            dataOutputStream.write(mExifData.getCompressedThumbnail());
2574f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        } else if (mExifData.hasUncompressedStrip()) {
2584f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            for (int i = 0; i < mExifData.getStripCount(); i++) {
2594f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                dataOutputStream.write(mExifData.getStrip(i));
2604f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            }
2614f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
2624f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
2634f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
264e48c0f96ee3f9654c3538664dd59485927fb44d6Earl Ou    private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
2654f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
2664f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
2674f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
2684f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (interoperabilityIfd != null) {
2694f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            writeIfd(interoperabilityIfd, dataOutputStream);
2704f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
2714f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
2724f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (gpsIfd != null) {
2734f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            writeIfd(gpsIfd, dataOutputStream);
2744f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
2754f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
2764f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (ifd1 != null) {
2774f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
2784f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
2794f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
2804f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
2814f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
2824f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            throws IOException {
2839bc2a5c83de59a3131bfeb31dad45df2b47715abEarl Ou        ExifTag[] tags = ifd.getAllTags();
2844f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        dataOutputStream.writeShort((short) tags.length);
2856e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        for (ExifTag tag : tags) {
2864f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            dataOutputStream.writeShort(tag.getTagId());
2874f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            dataOutputStream.writeShort(tag.getDataType());
2884f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            dataOutputStream.writeInt(tag.getComponentCount());
2896e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            if (DEBUG) {
2906e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                Log.v(TAG, "\n" + tag.toString());
2916e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            }
2924f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            if (tag.getDataSize() > 4) {
2934f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                dataOutputStream.writeInt(tag.getOffset());
2944f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            } else {
2956e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                ExifOutputStream.writeTagValue(tag, dataOutputStream);
296e48c0f96ee3f9654c3538664dd59485927fb44d6Earl Ou                for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
2974f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                    dataOutputStream.write(0);
2984f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                }
2994f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            }
3004f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
3014f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
3026e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        for (ExifTag tag : tags) {
3034f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            if (tag.getDataSize() > 4) {
3046e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                ExifOutputStream.writeTagValue(tag, dataOutputStream);
3054f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            }
3064f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
3074f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
3084f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
3094f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private int calculateOffsetOfIfd(IfdData ifd, int offset) {
3104f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
3119bc2a5c83de59a3131bfeb31dad45df2b47715abEarl Ou        ExifTag[] tags = ifd.getAllTags();
3126e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        for (ExifTag tag : tags) {
3134f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            if (tag.getDataSize() > 4) {
3144f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                tag.setOffset(offset);
3154f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                offset += tag.getDataSize();
3164f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            }
3174f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
3184f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        return offset;
3194f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
3204f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
3216e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk    private void createRequiredIfdAndTag() throws IOException {
3224f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        // IFD0 is required for all file
3234f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
3244f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (ifd0 == null) {
3254f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            ifd0 = new IfdData(IfdId.TYPE_IFD_0);
3264f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            mExifData.addIfdData(ifd0);
3274f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
3286e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
3296e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        if (exifOffsetTag == null) {
3306e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            throw new IOException("No definition for crucial exif tag: "
3316e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    + ExifInterface.TAG_EXIF_IFD);
3326e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        }
3334f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        ifd0.setTag(exifOffsetTag);
3344f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
3356e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        // Exif IFD is required for all files.
3364f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
3374f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (exifIfd == null) {
3384f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
3394f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            mExifData.addIfdData(exifIfd);
3404f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
3414f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
3424f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        // GPS IFD
3434f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
3444f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (gpsIfd != null) {
3456e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
3466e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            if (gpsOffsetTag == null) {
3476e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                throw new IOException("No definition for crucial exif tag: "
3486e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                        + ExifInterface.TAG_GPS_IFD);
3496e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            }
3504f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            ifd0.setTag(gpsOffsetTag);
3514f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
3524f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
3534f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        // Interoperability IFD
3544f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
3554f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (interIfd != null) {
3566e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ExifTag interOffsetTag = mInterface
3576e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
3586e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            if (interOffsetTag == null) {
3596e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                throw new IOException("No definition for crucial exif tag: "
3606e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                        + ExifInterface.TAG_INTEROPERABILITY_IFD);
3616e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            }
3624f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            exifIfd.setTag(interOffsetTag);
3634f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
3644f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
3654f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
3664f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
3674f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        // thumbnail
3684f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (mExifData.hasCompressedThumbnail()) {
3696e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk
3704f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            if (ifd1 == null) {
3714f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
3724f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                mExifData.addIfdData(ifd1);
3734f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            }
3746e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk
3756e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ExifTag offsetTag = mInterface
3766e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
3776e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            if (offsetTag == null) {
3786e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                throw new IOException("No definition for crucial exif tag: "
3796e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
3806e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            }
3816e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk
3824f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            ifd1.setTag(offsetTag);
3836e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ExifTag lengthTag = mInterface
3846e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
3856e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            if (lengthTag == null) {
3866e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                throw new IOException("No definition for crucial exif tag: "
3876e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                        + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
3886e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            }
3896e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk
3904f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            lengthTag.setValue(mExifData.getCompressedThumbnail().length);
3914f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            ifd1.setTag(lengthTag);
3926e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk
3936e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            // Get rid of tags for uncompressed if they exist.
3946e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
3956e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
3966e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        } else if (mExifData.hasUncompressedStrip()) {
3974f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            if (ifd1 == null) {
3984f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                ifd1 = new IfdData(IfdId.TYPE_IFD_1);
3994f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                mExifData.addIfdData(ifd1);
4004f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            }
4014f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            int stripCount = mExifData.getStripCount();
4026e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
4036e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            if (offsetTag == null) {
4046e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                throw new IOException("No definition for crucial exif tag: "
4056e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                        + ExifInterface.TAG_STRIP_OFFSETS);
4066e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            }
4076e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ExifTag lengthTag = mInterface
4086e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
4096e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            if (lengthTag == null) {
4106e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                throw new IOException("No definition for crucial exif tag: "
4116e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                        + ExifInterface.TAG_STRIP_BYTE_COUNTS);
4126e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            }
4134f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            long[] lengths = new long[stripCount];
4144f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            for (int i = 0; i < mExifData.getStripCount(); i++) {
4154f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                lengths[i] = mExifData.getStrip(i).length;
4164f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            }
4174f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            lengthTag.setValue(lengths);
4184f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            ifd1.setTag(offsetTag);
4194f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            ifd1.setTag(lengthTag);
4206e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            // Get rid of tags for compressed if they exist.
4216e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
4226e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ifd1.removeTag(ExifInterface
4236e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
424978f4aae1d2923fd125f85a6d5c568a26c571522nicolasroard        } else if (ifd1 != null) {
4256e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            // Get rid of offset and length tags if there is no thumbnail.
4266e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
4276e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
4286e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
4296e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ifd1.removeTag(ExifInterface
4306e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
4314f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
4324f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
4334f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
4344f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    private int calculateAllOffset() {
4354f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        int offset = TIFF_HEADER_SIZE;
4364f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
4374f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        offset = calculateOffsetOfIfd(ifd0, offset);
4386e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
4394f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
4404f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
4414f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        offset = calculateOffsetOfIfd(exifIfd, offset);
4424f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
4434f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
4444f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (interIfd != null) {
4456e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
4466e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    .setValue(offset);
4474f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            offset = calculateOffsetOfIfd(interIfd, offset);
4484f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
4494f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
4504f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
4514f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (gpsIfd != null) {
4526e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
4534f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            offset = calculateOffsetOfIfd(gpsIfd, offset);
4544f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
4554f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
4564f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
4574f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (ifd1 != null) {
4584f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            ifd0.setOffsetToNextIfd(offset);
4594f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            offset = calculateOffsetOfIfd(ifd1, offset);
4604f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
4614f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou
4624f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        // thumbnail
4634f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        if (mExifData.hasCompressedThumbnail()) {
4646e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
4656e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    .setValue(offset);
4664f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            offset += mExifData.getCompressedThumbnail().length;
4676e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        } else if (mExifData.hasUncompressedStrip()) {
4684f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            int stripCount = mExifData.getStripCount();
4694f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            long[] offsets = new long[stripCount];
4704f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            for (int i = 0; i < mExifData.getStripCount(); i++) {
4714f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                offsets[i] = offset;
4724f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou                offset += mExifData.getStrip(i).length;
4734f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou            }
4746e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
4756e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    offsets);
4764f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        }
4774f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou        return offset;
4784f529e7cada294befb66a1fc9b72b1aa164597dfEarl Ou    }
4796e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk
4806e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk    static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
4816e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            throws IOException {
4826e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        switch (tag.getDataType()) {
4836e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            case ExifTag.TYPE_ASCII:
4846e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                byte buf[] = tag.getStringByte();
4856e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                if (buf.length == tag.getComponentCount()) {
4866e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    buf[buf.length - 1] = 0;
4876e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    dataOutputStream.write(buf);
4886e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                } else {
4896e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    dataOutputStream.write(buf);
4906e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    dataOutputStream.write(0);
4916e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                }
4926e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                break;
4936e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            case ExifTag.TYPE_LONG:
4946e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            case ExifTag.TYPE_UNSIGNED_LONG:
4956e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
4966e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    dataOutputStream.writeInt((int) tag.getValueAt(i));
4976e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                }
4986e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                break;
4996e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            case ExifTag.TYPE_RATIONAL:
5006e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            case ExifTag.TYPE_UNSIGNED_RATIONAL:
5016e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
5026e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    dataOutputStream.writeRational(tag.getRational(i));
5036e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                }
5046e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                break;
5056e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            case ExifTag.TYPE_UNDEFINED:
5066e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            case ExifTag.TYPE_UNSIGNED_BYTE:
5076e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                buf = new byte[tag.getComponentCount()];
5086e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                tag.getBytes(buf);
5096e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                dataOutputStream.write(buf);
5106e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                break;
5116e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk            case ExifTag.TYPE_UNSIGNED_SHORT:
5126e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
5136e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                    dataOutputStream.writeShort((short) tag.getValueAt(i));
5146e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                }
5156e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk                break;
5166e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk        }
5176e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk    }
5186e6a524390d8ddebce5de0dcc8ae258e652ec80aRuben Brunk}
519