PageRenderTime 58ms CodeModel.GetById 13ms app.highlight 39ms RepoModel.GetById 1ms app.codeStats 1ms

/packages/WallpaperCropper/src/com/android/gallery3d/exif/ExifOutputStream.java

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