From 104c45677660586026a7e74ef8c47d396403d50e Mon Sep 17 00:00:00 2001 From: Michael Jurka Date: Mon, 8 Jul 2013 18:03:46 -0700 Subject: Initial implementation of new wallpaper picker. Change-Id: Ib4c5ac4989b4959fa62465d9cde3cac662e24949 --- src/com/android/gallery3d/exif/ExifParser.java | 916 +++++++++++++++++++++++++ 1 file changed, 916 insertions(+) create mode 100644 src/com/android/gallery3d/exif/ExifParser.java (limited to 'src/com/android/gallery3d/exif/ExifParser.java') diff --git a/src/com/android/gallery3d/exif/ExifParser.java b/src/com/android/gallery3d/exif/ExifParser.java new file mode 100644 index 000000000..5467d423d --- /dev/null +++ b/src/com/android/gallery3d/exif/ExifParser.java @@ -0,0 +1,916 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.exif; + +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.util.Map.Entry; +import java.util.TreeMap; + +/** + * This class provides a low-level EXIF parsing API. Given a JPEG format + * InputStream, the caller can request which IFD's to read via + * {@link #parse(InputStream, int)} with given options. + *

+ * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the + * parser. + * + *

+ * void parse() {
+ *     ExifParser parser = ExifParser.parse(mImageInputStream,
+ *             ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF);
+ *     int event = parser.next();
+ *     while (event != ExifParser.EVENT_END) {
+ *         switch (event) {
+ *             case ExifParser.EVENT_START_OF_IFD:
+ *                 break;
+ *             case ExifParser.EVENT_NEW_TAG:
+ *                 ExifTag tag = parser.getTag();
+ *                 if (!tag.hasValue()) {
+ *                     parser.registerForTagValue(tag);
+ *                 } else {
+ *                     processTag(tag);
+ *                 }
+ *                 break;
+ *             case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ *                 tag = parser.getTag();
+ *                 if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) {
+ *                     processTag(tag);
+ *                 }
+ *                 break;
+ *         }
+ *         event = parser.next();
+ *     }
+ * }
+ *
+ * void processTag(ExifTag tag) {
+ *     // process the tag as you like.
+ * }
+ * 
+ */ +class ExifParser { + private static final boolean LOGV = false; + private static final String TAG = "ExifParser"; + /** + * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to + * know which IFD we are in. + */ + public static final int EVENT_START_OF_IFD = 0; + /** + * When the parser reaches a new tag. Call {@link #getTag()}to get the + * corresponding tag. + */ + public static final int EVENT_NEW_TAG = 1; + /** + * When the parser reaches the value area of tag that is registered by + * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()} + * to get the corresponding tag. + */ + public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2; + + /** + * When the parser reaches the compressed image area. + */ + public static final int EVENT_COMPRESSED_IMAGE = 3; + /** + * When the parser reaches the uncompressed image strip. Call + * {@link #getStripIndex()} to get the index of the strip. + * + * @see #getStripIndex() + * @see #getStripCount() + */ + public static final int EVENT_UNCOMPRESSED_STRIP = 4; + /** + * When there is nothing more to parse. + */ + public static final int EVENT_END = 5; + + /** + * Option bit to request to parse IFD0. + */ + public static final int OPTION_IFD_0 = 1 << 0; + /** + * Option bit to request to parse IFD1. + */ + public static final int OPTION_IFD_1 = 1 << 1; + /** + * Option bit to request to parse Exif-IFD. + */ + public static final int OPTION_IFD_EXIF = 1 << 2; + /** + * Option bit to request to parse GPS-IFD. + */ + public static final int OPTION_IFD_GPS = 1 << 3; + /** + * Option bit to request to parse Interoperability-IFD. + */ + public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4; + /** + * Option bit to request to parse thumbnail. + */ + public static final int OPTION_THUMBNAIL = 1 << 5; + + protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif" + protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1 + + // TIFF header + protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II" + protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM" + protected static final short TIFF_HEADER_TAIL = 0x002A; + + protected static final int TAG_SIZE = 12; + protected static final int OFFSET_SIZE = 2; + + private static final Charset US_ASCII = Charset.forName("US-ASCII"); + + protected static final int DEFAULT_IFD0_OFFSET = 8; + + private final CountedDataInputStream mTiffStream; + private final int mOptions; + private int mIfdStartOffset = 0; + private int mNumOfTagInIfd = 0; + private int mIfdType; + private ExifTag mTag; + private ImageEvent mImageEvent; + private int mStripCount; + private ExifTag mStripSizeTag; + private ExifTag mJpegSizeTag; + private boolean mNeedToParseOffsetsInCurrentIfd; + private boolean mContainExifData = false; + private int mApp1End; + private int mOffsetToApp1EndFromSOF = 0; + private byte[] mDataAboveIfd0; + private int mIfd0Position; + private int mTiffStartPosition; + private final ExifInterface mInterface; + + private static final short TAG_EXIF_IFD = ExifInterface + .getTrueTagKey(ExifInterface.TAG_EXIF_IFD); + private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD); + private static final short TAG_INTEROPERABILITY_IFD = ExifInterface + .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD); + private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface + .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT); + private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface + .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + private static final short TAG_STRIP_OFFSETS = ExifInterface + .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS); + private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface + .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS); + + private final TreeMap mCorrespondingEvent = new TreeMap(); + + private boolean isIfdRequested(int ifdType) { + switch (ifdType) { + case IfdId.TYPE_IFD_0: + return (mOptions & OPTION_IFD_0) != 0; + case IfdId.TYPE_IFD_1: + return (mOptions & OPTION_IFD_1) != 0; + case IfdId.TYPE_IFD_EXIF: + return (mOptions & OPTION_IFD_EXIF) != 0; + case IfdId.TYPE_IFD_GPS: + return (mOptions & OPTION_IFD_GPS) != 0; + case IfdId.TYPE_IFD_INTEROPERABILITY: + return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0; + } + return false; + } + + private boolean isThumbnailRequested() { + return (mOptions & OPTION_THUMBNAIL) != 0; + } + + private ExifParser(InputStream inputStream, int options, ExifInterface iRef) + throws IOException, ExifInvalidFormatException { + if (inputStream == null) { + throw new IOException("Null argument inputStream to ExifParser"); + } + if (LOGV) { + Log.v(TAG, "Reading exif..."); + } + mInterface = iRef; + mContainExifData = seekTiffData(inputStream); + mTiffStream = new CountedDataInputStream(inputStream); + mOptions = options; + if (!mContainExifData) { + return; + } + + parseTiffHeader(); + long offset = mTiffStream.readUnsignedInt(); + if (offset > Integer.MAX_VALUE) { + throw new ExifInvalidFormatException("Invalid offset " + offset); + } + mIfd0Position = (int) offset; + mIfdType = IfdId.TYPE_IFD_0; + if (isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) { + registerIfd(IfdId.TYPE_IFD_0, offset); + if (offset != DEFAULT_IFD0_OFFSET) { + mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET]; + read(mDataAboveIfd0); + } + } + } + + /** + * Parses the the given InputStream with the given options + * + * @exception IOException + * @exception ExifInvalidFormatException + */ + protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef) + throws IOException, ExifInvalidFormatException { + return new ExifParser(inputStream, options, iRef); + } + + /** + * Parses the the given InputStream with default options; that is, every IFD + * and thumbnaill will be parsed. + * + * @exception IOException + * @exception ExifInvalidFormatException + * @see #parse(InputStream, int) + */ + protected static ExifParser parse(InputStream inputStream, ExifInterface iRef) + throws IOException, ExifInvalidFormatException { + return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1 + | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY + | OPTION_THUMBNAIL, iRef); + } + + /** + * Moves the parser forward and returns the next parsing event + * + * @exception IOException + * @exception ExifInvalidFormatException + * @see #EVENT_START_OF_IFD + * @see #EVENT_NEW_TAG + * @see #EVENT_VALUE_OF_REGISTERED_TAG + * @see #EVENT_COMPRESSED_IMAGE + * @see #EVENT_UNCOMPRESSED_STRIP + * @see #EVENT_END + */ + protected int next() throws IOException, ExifInvalidFormatException { + if (!mContainExifData) { + return EVENT_END; + } + int offset = mTiffStream.getReadByteCount(); + int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd; + if (offset < endOfTags) { + mTag = readTag(); + if (mTag == null) { + return next(); + } + if (mNeedToParseOffsetsInCurrentIfd) { + checkOffsetOrImageTag(mTag); + } + return EVENT_NEW_TAG; + } else if (offset == endOfTags) { + // There is a link to ifd1 at the end of ifd0 + if (mIfdType == IfdId.TYPE_IFD_0) { + long ifdOffset = readUnsignedLong(); + if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) { + if (ifdOffset != 0) { + registerIfd(IfdId.TYPE_IFD_1, ifdOffset); + } + } + } else { + int offsetSize = 4; + // Some camera models use invalid length of the offset + if (mCorrespondingEvent.size() > 0) { + offsetSize = mCorrespondingEvent.firstEntry().getKey() - + mTiffStream.getReadByteCount(); + } + if (offsetSize < 4) { + Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize); + } else { + long ifdOffset = readUnsignedLong(); + if (ifdOffset != 0) { + Log.w(TAG, "Invalid link to next IFD: " + ifdOffset); + } + } + } + } + while (mCorrespondingEvent.size() != 0) { + Entry entry = mCorrespondingEvent.pollFirstEntry(); + Object event = entry.getValue(); + try { + skipTo(entry.getKey()); + } catch (IOException e) { + Log.w(TAG, "Failed to skip to data at: " + entry.getKey() + + " for " + event.getClass().getName() + ", the file may be broken."); + continue; + } + if (event instanceof IfdEvent) { + mIfdType = ((IfdEvent) event).ifd; + mNumOfTagInIfd = mTiffStream.readUnsignedShort(); + mIfdStartOffset = entry.getKey(); + + if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) { + Log.w(TAG, "Invalid size of IFD " + mIfdType); + return EVENT_END; + } + + mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd(); + if (((IfdEvent) event).isRequested) { + return EVENT_START_OF_IFD; + } else { + skipRemainingTagsInCurrentIfd(); + } + } else if (event instanceof ImageEvent) { + mImageEvent = (ImageEvent) event; + return mImageEvent.type; + } else { + ExifTagEvent tagEvent = (ExifTagEvent) event; + mTag = tagEvent.tag; + if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) { + readFullTagValue(mTag); + checkOffsetOrImageTag(mTag); + } + if (tagEvent.isRequested) { + return EVENT_VALUE_OF_REGISTERED_TAG; + } + } + } + return EVENT_END; + } + + /** + * Skips the tags area of current IFD, if the parser is not in the tag area, + * nothing will happen. + * + * @throws IOException + * @throws ExifInvalidFormatException + */ + protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException { + int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd; + int offset = mTiffStream.getReadByteCount(); + if (offset > endOfTags) { + return; + } + if (mNeedToParseOffsetsInCurrentIfd) { + while (offset < endOfTags) { + mTag = readTag(); + offset += TAG_SIZE; + if (mTag == null) { + continue; + } + checkOffsetOrImageTag(mTag); + } + } else { + skipTo(endOfTags); + } + long ifdOffset = readUnsignedLong(); + // For ifd0, there is a link to ifd1 in the end of all tags + if (mIfdType == IfdId.TYPE_IFD_0 + && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) { + if (ifdOffset > 0) { + registerIfd(IfdId.TYPE_IFD_1, ifdOffset); + } + } + } + + private boolean needToParseOffsetsInCurrentIfd() { + switch (mIfdType) { + case IfdId.TYPE_IFD_0: + return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS) + || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY) + || isIfdRequested(IfdId.TYPE_IFD_1); + case IfdId.TYPE_IFD_1: + return isThumbnailRequested(); + case IfdId.TYPE_IFD_EXIF: + // The offset to interoperability IFD is located in Exif IFD + return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY); + default: + return false; + } + } + + /** + * If {@link #next()} return {@link #EVENT_NEW_TAG} or + * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the + * corresponding tag. + *

+ * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size + * of the value is greater than 4 bytes. One should call + * {@link ExifTag#hasValue()} to check if the tag contains value. If there + * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser + * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area + * pointed by the offset. + *

+ * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the + * tag will have already been read except for tags of undefined type. For + * tags of undefined type, call one of the read methods to get the value. + * + * @see #registerForTagValue(ExifTag) + * @see #read(byte[]) + * @see #read(byte[], int, int) + * @see #readLong() + * @see #readRational() + * @see #readString(int) + * @see #readString(int, Charset) + */ + protected ExifTag getTag() { + return mTag; + } + + /** + * Gets number of tags in the current IFD area. + */ + protected int getTagCountInCurrentIfd() { + return mNumOfTagInIfd; + } + + /** + * Gets the ID of current IFD. + * + * @see IfdId#TYPE_IFD_0 + * @see IfdId#TYPE_IFD_1 + * @see IfdId#TYPE_IFD_GPS + * @see IfdId#TYPE_IFD_INTEROPERABILITY + * @see IfdId#TYPE_IFD_EXIF + */ + protected int getCurrentIfd() { + return mIfdType; + } + + /** + * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to + * get the index of this strip. + * + * @see #getStripCount() + */ + protected int getStripIndex() { + return mImageEvent.stripIndex; + } + + /** + * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to + * get the number of strip data. + * + * @see #getStripIndex() + */ + protected int getStripCount() { + return mStripCount; + } + + /** + * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to + * get the strip size. + */ + protected int getStripSize() { + if (mStripSizeTag == null) + return 0; + return (int) mStripSizeTag.getValueAt(0); + } + + /** + * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get + * the image data size. + */ + protected int getCompressedImageSize() { + if (mJpegSizeTag == null) { + return 0; + } + return (int) mJpegSizeTag.getValueAt(0); + } + + private void skipTo(int offset) throws IOException { + mTiffStream.skipTo(offset); + while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) { + mCorrespondingEvent.pollFirstEntry(); + } + } + + /** + * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may + * not contain the value if the size of the value is greater than 4 bytes. + * When the value is not available here, call this method so that the parser + * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area + * where the value is located. + * + * @see #EVENT_VALUE_OF_REGISTERED_TAG + */ + protected void registerForTagValue(ExifTag tag) { + if (tag.getOffset() >= mTiffStream.getReadByteCount()) { + mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true)); + } + } + + private void registerIfd(int ifdType, long offset) { + // Cast unsigned int to int since the offset is always smaller + // than the size of APP1 (65536) + mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType))); + } + + private void registerCompressedImage(long offset) { + mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE)); + } + + private void registerUncompressedStrip(int stripIndex, long offset) { + mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP + , stripIndex)); + } + + private ExifTag readTag() throws IOException, ExifInvalidFormatException { + short tagId = mTiffStream.readShort(); + short dataFormat = mTiffStream.readShort(); + long numOfComp = mTiffStream.readUnsignedInt(); + if (numOfComp > Integer.MAX_VALUE) { + throw new ExifInvalidFormatException( + "Number of component is larger then Integer.MAX_VALUE"); + } + // Some invalid image file contains invalid data type. Ignore those tags + if (!ExifTag.isValidType(dataFormat)) { + Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat)); + mTiffStream.skip(4); + return null; + } + // TODO: handle numOfComp overflow + ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType, + ((int) numOfComp) != ExifTag.SIZE_UNDEFINED); + int dataSize = tag.getDataSize(); + if (dataSize > 4) { + long offset = mTiffStream.readUnsignedInt(); + if (offset > Integer.MAX_VALUE) { + throw new ExifInvalidFormatException( + "offset is larger then Integer.MAX_VALUE"); + } + // Some invalid images put some undefined data before IFD0. + // Read the data here. + if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) { + byte[] buf = new byte[(int) numOfComp]; + System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET, + buf, 0, (int) numOfComp); + tag.setValue(buf); + } else { + tag.setOffset((int) offset); + } + } else { + boolean defCount = tag.hasDefinedCount(); + // Set defined count to 0 so we can add \0 to non-terminated strings + tag.setHasDefinedCount(false); + // Read value + readFullTagValue(tag); + tag.setHasDefinedCount(defCount); + mTiffStream.skip(4 - dataSize); + // Set the offset to the position of value. + tag.setOffset(mTiffStream.getReadByteCount() - 4); + } + return tag; + } + + /** + * Check the tag, if the tag is one of the offset tag that points to the IFD + * or image the caller is interested in, register the IFD or image. + */ + private void checkOffsetOrImageTag(ExifTag tag) { + // Some invalid formattd image contains tag with 0 size. + if (tag.getComponentCount() == 0) { + return; + } + short tid = tag.getTagId(); + int ifd = tag.getIfd(); + if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) { + if (isIfdRequested(IfdId.TYPE_IFD_EXIF) + || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { + registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0)); + } + } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) { + if (isIfdRequested(IfdId.TYPE_IFD_GPS)) { + registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0)); + } + } else if (tid == TAG_INTEROPERABILITY_IFD + && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) { + if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { + registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0)); + } + } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT + && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) { + if (isThumbnailRequested()) { + registerCompressedImage(tag.getValueAt(0)); + } + } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH + && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) { + if (isThumbnailRequested()) { + mJpegSizeTag = tag; + } + } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) { + if (isThumbnailRequested()) { + if (tag.hasValue()) { + for (int i = 0; i < tag.getComponentCount(); i++) { + if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { + registerUncompressedStrip(i, tag.getValueAt(i)); + } else { + registerUncompressedStrip(i, tag.getValueAt(i)); + } + } + } else { + mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false)); + } + } + } else if (tid == TAG_STRIP_BYTE_COUNTS + && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS) + &&isThumbnailRequested() && tag.hasValue()) { + mStripSizeTag = tag; + } + } + + private boolean checkAllowed(int ifd, int tagId) { + int info = mInterface.getTagInfo().get(tagId); + if (info == ExifInterface.DEFINITION_NULL) { + return false; + } + return ExifInterface.isIfdAllowed(info, ifd); + } + + protected void readFullTagValue(ExifTag tag) throws IOException { + // Some invalid images contains tags with wrong size, check it here + short type = tag.getDataType(); + if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED || + type == ExifTag.TYPE_UNSIGNED_BYTE) { + int size = tag.getComponentCount(); + if (mCorrespondingEvent.size() > 0) { + if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount() + + size) { + Object event = mCorrespondingEvent.firstEntry().getValue(); + if (event instanceof ImageEvent) { + // Tag value overlaps thumbnail, ignore thumbnail. + Log.w(TAG, "Thumbnail overlaps value for tag: \n" + tag.toString()); + Entry entry = mCorrespondingEvent.pollFirstEntry(); + Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey()); + } else { + // Tag value overlaps another tag, shorten count + if (event instanceof IfdEvent) { + Log.w(TAG, "Ifd " + ((IfdEvent) event).ifd + + " overlaps value for tag: \n" + tag.toString()); + } else if (event instanceof ExifTagEvent) { + Log.w(TAG, "Tag value for tag: \n" + + ((ExifTagEvent) event).tag.toString() + + " overlaps value for tag: \n" + tag.toString()); + } + size = mCorrespondingEvent.firstEntry().getKey() + - mTiffStream.getReadByteCount(); + Log.w(TAG, "Invalid size of tag: \n" + tag.toString() + + " setting count to: " + size); + tag.forceSetComponentCount(size); + } + } + } + } + switch (tag.getDataType()) { + case ExifTag.TYPE_UNSIGNED_BYTE: + case ExifTag.TYPE_UNDEFINED: { + byte buf[] = new byte[tag.getComponentCount()]; + read(buf); + tag.setValue(buf); + } + break; + case ExifTag.TYPE_ASCII: + tag.setValue(readString(tag.getComponentCount())); + break; + case ExifTag.TYPE_UNSIGNED_LONG: { + long value[] = new long[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readUnsignedLong(); + } + tag.setValue(value); + } + break; + case ExifTag.TYPE_UNSIGNED_RATIONAL: { + Rational value[] = new Rational[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readUnsignedRational(); + } + tag.setValue(value); + } + break; + case ExifTag.TYPE_UNSIGNED_SHORT: { + int value[] = new int[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readUnsignedShort(); + } + tag.setValue(value); + } + break; + case ExifTag.TYPE_LONG: { + int value[] = new int[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readLong(); + } + tag.setValue(value); + } + break; + case ExifTag.TYPE_RATIONAL: { + Rational value[] = new Rational[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readRational(); + } + tag.setValue(value); + } + break; + } + if (LOGV) { + Log.v(TAG, "\n" + tag.toString()); + } + } + + private void parseTiffHeader() throws IOException, + ExifInvalidFormatException { + short byteOrder = mTiffStream.readShort(); + if (LITTLE_ENDIAN_TAG == byteOrder) { + mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + } else if (BIG_ENDIAN_TAG == byteOrder) { + mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN); + } else { + throw new ExifInvalidFormatException("Invalid TIFF header"); + } + + if (mTiffStream.readShort() != TIFF_HEADER_TAIL) { + throw new ExifInvalidFormatException("Invalid TIFF header"); + } + } + + private boolean seekTiffData(InputStream inputStream) throws IOException, + ExifInvalidFormatException { + CountedDataInputStream dataStream = new CountedDataInputStream(inputStream); + if (dataStream.readShort() != JpegHeader.SOI) { + throw new ExifInvalidFormatException("Invalid JPEG format"); + } + + short marker = dataStream.readShort(); + while (marker != JpegHeader.EOI + && !JpegHeader.isSofMarker(marker)) { + int length = dataStream.readUnsignedShort(); + // Some invalid formatted image contains multiple APP1, + // try to find the one with Exif data. + if (marker == JpegHeader.APP1) { + int header = 0; + short headerTail = 0; + if (length >= 8) { + header = dataStream.readInt(); + headerTail = dataStream.readShort(); + length -= 6; + if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) { + mTiffStartPosition = dataStream.getReadByteCount(); + mApp1End = length; + mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End; + return true; + } + } + } + if (length < 2 || (length - 2) != dataStream.skip(length - 2)) { + Log.w(TAG, "Invalid JPEG format."); + return false; + } + marker = dataStream.readShort(); + } + return false; + } + + protected int getOffsetToExifEndFromSOF() { + return mOffsetToApp1EndFromSOF; + } + + protected int getTiffStartPosition() { + return mTiffStartPosition; + } + + /** + * Reads bytes from the InputStream. + */ + protected int read(byte[] buffer, int offset, int length) throws IOException { + return mTiffStream.read(buffer, offset, length); + } + + /** + * Equivalent to read(buffer, 0, buffer.length). + */ + protected int read(byte[] buffer) throws IOException { + return mTiffStream.read(buffer); + } + + /** + * Reads a String from the InputStream with US-ASCII charset. The parser + * will read n bytes and convert it to ascii string. This is used for + * reading values of type {@link ExifTag#TYPE_ASCII}. + */ + protected String readString(int n) throws IOException { + return readString(n, US_ASCII); + } + + /** + * Reads a String from the InputStream with the given charset. The parser + * will read n bytes and convert it to string. This is used for reading + * values of type {@link ExifTag#TYPE_ASCII}. + */ + protected String readString(int n, Charset charset) throws IOException { + if (n > 0) { + return mTiffStream.readString(n, charset); + } else { + return ""; + } + } + + /** + * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the + * InputStream. + */ + protected int readUnsignedShort() throws IOException { + return mTiffStream.readShort() & 0xffff; + } + + /** + * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the + * InputStream. + */ + protected long readUnsignedLong() throws IOException { + return readLong() & 0xffffffffL; + } + + /** + * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the + * InputStream. + */ + protected Rational readUnsignedRational() throws IOException { + long nomi = readUnsignedLong(); + long denomi = readUnsignedLong(); + return new Rational(nomi, denomi); + } + + /** + * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream. + */ + protected int readLong() throws IOException { + return mTiffStream.readInt(); + } + + /** + * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream. + */ + protected Rational readRational() throws IOException { + int nomi = readLong(); + int denomi = readLong(); + return new Rational(nomi, denomi); + } + + private static class ImageEvent { + int stripIndex; + int type; + + ImageEvent(int type) { + this.stripIndex = 0; + this.type = type; + } + + ImageEvent(int type, int stripIndex) { + this.type = type; + this.stripIndex = stripIndex; + } + } + + private static class IfdEvent { + int ifd; + boolean isRequested; + + IfdEvent(int ifd, boolean isInterestedIfd) { + this.ifd = ifd; + this.isRequested = isInterestedIfd; + } + } + + private static class ExifTagEvent { + ExifTag tag; + boolean isRequested; + + ExifTagEvent(ExifTag tag, boolean isRequireByUser) { + this.tag = tag; + this.isRequested = isRequireByUser; + } + } + + /** + * Gets the byte order of the current InputStream. + */ + protected ByteOrder getByteOrder() { + return mTiffStream.getByteOrder(); + } +} -- cgit v1.2.3