diff options
-rw-r--r-- | src/com/android/gallery3d/exif/ExifData.java | 37 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/ExifParser.java | 653 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/ExifReader.java | 146 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/ExifTag.java | 244 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/IfdData.java | 121 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/IfdId.java | 26 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/IfdParser.java | 170 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/Rational.java | 2 | ||||
-rw-r--r-- | tests/src/com/android/gallery3d/exif/ExifParserTest.java | 419 | ||||
-rw-r--r-- | tests/src/com/android/gallery3d/exif/ExifReaderTest.java | 61 | ||||
-rw-r--r-- | tests/src/com/android/gallery3d/exif/ExifXmlReader.java | 2 |
11 files changed, 1119 insertions, 762 deletions
diff --git a/src/com/android/gallery3d/exif/ExifData.java b/src/com/android/gallery3d/exif/ExifData.java index 2c316f701..712698ef4 100644 --- a/src/com/android/gallery3d/exif/ExifData.java +++ b/src/com/android/gallery3d/exif/ExifData.java @@ -15,22 +15,33 @@ */ package com.android.gallery3d.exif; - - +/** + * This class stores the EXIF header in IFDs according to the JPEG specification. + * It is the result produced by {@link ExifReader}. + * @see ExifReader + * @see IfdData + */ public class ExifData { - public static final int TYPE_IFD_0 = 0; - public static final int TYPE_IFD_EXIF = 1; - public static final int TYPE_IFD_1 = 2; - public static final int TYPE_IFD_GPS = 3; - public static final int TYPE_IFD_INTEROPERABILITY = 4; - - private final IfdData[] mIfdDatas = new IfdData[5]; + private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT]; - public IfdData getIfdData(int ifdType) { - return mIfdDatas[ifdType]; + /** + * Gets the IFD data of the specified IFD. + * + * @see IfdId#TYPE_IFD_0 + * @see IfdId#TYPE_IFD_1 + * @see IfdId#TYPE_IFD_EXIF + * @see IfdId#TYPE_IFD_GPS + * @see IfdId#TYPE_IFD_INTEROPERABILITY + */ + public IfdData getIfdData(int ifdId) { + return mIfdDatas[ifdId]; } + /** + * Adds IFD data. If IFD data of the same type already exists, + * it will be replaced by the new data. + */ public void addIfdData(IfdData data) { - mIfdDatas[data.getIfdType()] = data; + mIfdDatas[data.getId()] = data; } -} +}
\ No newline at end of file diff --git a/src/com/android/gallery3d/exif/ExifParser.java b/src/com/android/gallery3d/exif/ExifParser.java index f536a55f4..4249a7241 100644 --- a/src/com/android/gallery3d/exif/ExifParser.java +++ b/src/com/android/gallery3d/exif/ExifParser.java @@ -21,10 +21,106 @@ import java.io.EOFException; 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. + * <p> + * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the parser. + * <pre> + * 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. + * } + * </pre> + */ public class 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; - private static final String TAG = "ExifParser"; + /** + * 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; private static final short SOI = (short) 0xFFD8; // SOI marker of JPEG private static final short APP1 = (short) 0xFFE1; // APP1 marker of JPEG @@ -38,41 +134,471 @@ public class ExifParser { private static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM" private static final short TIFF_HEADER_TAIL = 0x002A; - public IfdParser parse(InputStream inputStream) throws ExifInvalidFormatException, IOException{ - if (!seekTiffData(inputStream)) { - return null; + private static final int TAG_SIZE = 12; + private static final int OFFSET_SIZE = 2; + + private final TiffInputStream 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 final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>(); + + 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) + throws IOException, ExifInvalidFormatException { + seekTiffData(inputStream); + mTiffStream = new TiffInputStream(inputStream); + mOptions = options; + if (mTiffStream.getReadByteCount() == 0) { + parseTiffHeader(); + long offset = mTiffStream.readUnsignedInt(); + registerIfd(IfdId.TYPE_IFD_0, offset); + } + } + + /** + * Parses the the given InputStream with the given options + * @exception IOException + * @exception ExifInvalidFormatException + */ + public static ExifParser parse(InputStream inputStream, int options) + throws IOException, ExifInvalidFormatException { + return new ExifParser(inputStream, options); + } + + /** + * 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) + */ + public static ExifParser parse(InputStream inputStream) + throws IOException, ExifInvalidFormatException { + return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1 + | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY + | OPTION_THUMBNAIL); + } + + /** + * 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 + */ + public int next() throws IOException, ExifInvalidFormatException { + int offset = mTiffStream.getReadByteCount(); + int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd; + if (offset < endOfTags) { + mTag = readTag(); + if (mNeedToParseOffsetsInCurrentIfd) { + checkOffsetOrImageTag(mTag); + } + return EVENT_NEW_TAG; + } else if (offset == endOfTags) { + long ifdOffset = readUnsignedInt(); + // There is a link to ifd1 at the end of ifd0 + if (mIfdType == IfdId.TYPE_IFD_0) { + if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) { + if (ifdOffset != 0) { + registerIfd(IfdId.TYPE_IFD_1, ifdOffset); + } + } + } else { + if (ifdOffset != 0) { + throw new ExifInvalidFormatException("Invalid link to next IFD"); + } + } + } + while(mCorrespondingEvent.size() != 0) { + Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry(); + Object event = entry.getValue(); + skipTo(entry.getKey()); + if (event instanceof IfdEvent) { + mIfdType = ((IfdEvent) event).ifd; + mNumOfTagInIfd = mTiffStream.readUnsignedShort(); + mIfdStartOffset = entry.getKey(); + 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 + */ + public 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(); + checkOffsetOrImageTag(mTag); + offset += TAG_SIZE; + } + } else { + skipTo(endOfTags); + } + long ifdOffset = readUnsignedInt(); + // 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); + 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. + * <p> + * + * 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. + * + * <p> + * 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 #readInt() + * @see #readRational() + * @see #readShort() + * @see #readString(int) + * @see #readString(int, Charset) + */ + public ExifTag getTag() { + return mTag; + } + + /** + * Gets number of tags in the current IFD area. + */ + public 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 + */ + public int getCurrentIfd() { + return mIfdType; + } + + /** + * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, + * call this function to get the index of this strip. + * @see #getStripCount() + */ + public int getStripIndex() { + return mImageEvent.stripIndex; + } + + /** + * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the number + * of strip data. + * @see #getStripIndex() + */ + public int getStripCount() { + return mStripCount; + } + + /** + * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the strip size. + */ + public int getStripSize() { + if (mStripSizeTag == null) return 0; + if (mStripSizeTag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { + return mStripSizeTag.getUnsignedShort(mImageEvent.stripIndex); + } else { + return (int) mStripSizeTag.getUnsignedInt(mImageEvent.stripIndex); } - TiffInputStream tiffStream = new TiffInputStream(inputStream); - parseTiffHeader(tiffStream); - long offset = tiffStream.readUnsignedInt(); - if (offset > Integer.MAX_VALUE) { - throw new ExifInvalidFormatException("Offset value is larger than Integer.MAX_VALUE"); + } + + /** + * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get the image data + * size. + */ + public int getCompressedImageSize() { + if (mJpegSizeTag == null) return 0; + return (int) mJpegSizeTag.getUnsignedInt(); + } + + private void skipTo(int offset) throws IOException { + mTiffStream.skipTo(offset); + while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) { + mCorrespondingEvent.pollFirstEntry(); } - return new IfdParser(tiffStream, (int)offset); } - private void parseTiffHeader(TiffInputStream tiffStream) throws IOException, + /** + * 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 + */ + public void registerForTagValue(ExifTag tag) { + mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true)); + } + + private void registerIfd(int ifdType, long offset) { + 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"); + } + ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType); + if (tag.getDataSize() > 4) { + long offset = mTiffStream.readUnsignedInt(); + if (offset > Integer.MAX_VALUE) { + throw new ExifInvalidFormatException( + "offset is larger then Integer.MAX_VALUE"); + } + tag.setOffset((int) offset); + } else { + readFullTagValue(tag); + mTiffStream.skip(4 - tag.getDataSize()); + } + 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) { + switch (tag.getTagId()) { + case ExifTag.TIFF_TAG.TAG_EXIF_IFD: + if (isIfdRequested(IfdId.TYPE_IFD_EXIF) + || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { + registerIfd(IfdId.TYPE_IFD_EXIF, tag.getUnsignedInt()); + } + break; + case ExifTag.TIFF_TAG.TAG_GPS_IFD: + if (isIfdRequested(IfdId.TYPE_IFD_GPS)) { + registerIfd(IfdId.TYPE_IFD_GPS, tag.getUnsignedInt()); + } + break; + case ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD: + if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { + registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getUnsignedInt()); + } + break; + case ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT: + if (isThumbnailRequested()) { + registerCompressedImage(tag.getUnsignedInt()); + } + break; + case ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: + if (isThumbnailRequested()) { + mJpegSizeTag = tag; + } + break; + case ExifTag.TIFF_TAG.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.getUnsignedShort(i)); + } else { + registerUncompressedStrip(i, tag.getUnsignedInt(i)); + } + } + } else { + mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false)); + } + } + break; + case ExifTag.TIFF_TAG.TAG_STRIP_BYTE_COUNTS: + if (isThumbnailRequested()) { + if (tag.hasValue()) { + mStripSizeTag = tag; + } + } + break; + } + } + + private void readFullTagValue(ExifTag tag) throws IOException { + 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_INT: + { + long value[] = new long[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readUnsignedInt(); + } + 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_INT: + { + int value[] = new int[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readInt(); + } + 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; + } + } + + private void parseTiffHeader() throws IOException, ExifInvalidFormatException { - short byteOrder = tiffStream.readShort(); + short byteOrder = mTiffStream.readShort(); ByteOrder order; if (LITTLE_ENDIAN_TAG == byteOrder) { - tiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); } else if (BIG_ENDIAN_TAG == byteOrder) { - tiffStream.setByteOrder(ByteOrder.BIG_ENDIAN); + mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN); } else { throw new ExifInvalidFormatException("Invalid TIFF header"); } - if (tiffStream.readShort() != TIFF_HEADER_TAIL) { + if (mTiffStream.readShort() != TIFF_HEADER_TAIL) { throw new ExifInvalidFormatException("Invalid TIFF header"); } } - /** - * Try to seek the tiff data. If there is no tiff data, return false, else return true and - * the inputstream will be at the start of tiff data - */ - private boolean seekTiffData(InputStream inputStream) throws IOException, + private void seekTiffData(InputStream inputStream) throws IOException, ExifInvalidFormatException { DataInputStream dataStream = new DataInputStream(inputStream); @@ -91,7 +617,7 @@ public class ExifParser { } if (tag != APP1) { - return false; + throw new ExifInvalidFormatException("No APP1 segment"); } // APP1 length, it's not used for us @@ -101,9 +627,90 @@ public class ExifParser { if (dataStream.readInt() != EXIF_HEADER || dataStream.readShort() != EXIF_HEADER_TAIL) { // There is no EXIF data; - return false; + throw new ExifInvalidFormatException("No Exif header in APP1"); + } + } + + public int read(byte[] buffer, int offset, int length) throws IOException { + return mTiffStream.read(buffer, offset, length); + } + + public int read(byte[] buffer) throws IOException { + return mTiffStream.read(buffer); + } + + public String readString(int n) throws IOException { + if (n > 0) { + byte[] buf = new byte[n]; + mTiffStream.readOrThrow(buf); + return new String(buf, 0, n - 1, "UTF8"); + } else { + return ""; + } + } + + public String readString(int n, Charset charset) throws IOException { + byte[] buf = new byte[n]; + mTiffStream.readOrThrow(buf); + return new String(buf, 0, n - 1, charset); + } + + public int readUnsignedShort() throws IOException { + return readShort() & 0xffff; + } + + public long readUnsignedInt() throws IOException { + return readInt() & 0xffffffffL; + } + + public Rational readUnsignedRational() throws IOException { + long nomi = readUnsignedInt(); + long denomi = readUnsignedInt(); + return new Rational(nomi, denomi); + } + + public int readInt() throws IOException { + return mTiffStream.readInt(); + } + + public short readShort() throws IOException { + return mTiffStream.readShort(); + } + + public Rational readRational() throws IOException { + int nomi = readInt(); + int denomi = readInt(); + 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; + } + } - return true; + 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; + } } }
\ No newline at end of file diff --git a/src/com/android/gallery3d/exif/ExifReader.java b/src/com/android/gallery3d/exif/ExifReader.java index f406cb739..125b6c65e 100644 --- a/src/com/android/gallery3d/exif/ExifReader.java +++ b/src/com/android/gallery3d/exif/ExifReader.java @@ -19,132 +19,46 @@ package com.android.gallery3d.exif; import java.io.IOException; import java.io.InputStream; +/** + * This class reads the EXIF header of a JPEG file and stores it in {@link ExifData}. + */ public class ExifReader { - + /** + * Parse the inputStream and return all Exif data. + * @throws ExifInvalidFormatException + * @throws IOException + */ public ExifData getExifData(InputStream inputStream) throws ExifInvalidFormatException, IOException { - ExifParser parser = new ExifParser(); - IfdParser ifdParser = parser.parse(inputStream); + ExifParser parser = ExifParser.parse(inputStream); ExifData exifData = new ExifData(); - IfdData ifdData = new IfdData(ExifData.TYPE_IFD_0); - parseIfd(ifdParser, ifdData, exifData); - exifData.addIfdData(ifdData); - return exifData; - } - public void parseIfd(IfdParser ifdParser, IfdData ifdData, ExifData exifData) - throws IOException, ExifInvalidFormatException { - int type = ifdParser.next(); - while (type != IfdParser.TYPE_END) { - switch (type) { - case IfdParser.TYPE_NEW_TAG: - ExifTag tag = ifdParser.readTag(); - if (tag.getDataSize() > 4) { - long offset = ifdParser.readUnsignedInt(); - ifdParser.waitValueOfTag(tag, offset); - } else if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD - || tag.getTagId() == ExifTag.TIFF_TAG.TAG_GPS_IFD - || tag.getTagId() == ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD) { - long offset = ifdParser.readUnsignedInt(); - ifdParser.waitValueOfTag(tag, offset); - ifdData.addTag(tag, offset); + int event = parser.next(); + while (event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_START_OF_IFD: + exifData.addIfdData(new IfdData(parser.getCurrentIfd())); + break; + case ExifParser.EVENT_NEW_TAG: + ExifTag tag = parser.getTag(); + if (!tag.hasValue()) { + parser.registerForTagValue(tag); } else { - readAndSaveTag(tag, ifdParser, ifdData); + exifData.getIfdData(tag.getIfd()).setTag(tag); } break; - case IfdParser.TYPE_NEXT_IFD: - IfdData ifd1 = new IfdData(ExifData.TYPE_IFD_1); - parseIfd(ifdParser.parseIfdBlock(), ifd1, exifData); - exifData.addIfdData(ifd1); - break; - case IfdParser.TYPE_VALUE_OF_PREV_TAG: - tag = ifdParser.getCorrespodingExifTag(); - if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) { - IfdData ifd = new IfdData(ExifData.TYPE_IFD_EXIF); - parseIfd(ifdParser.parseIfdBlock(), ifd, exifData); - exifData.addIfdData(ifd); - } else if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_GPS_IFD) { - IfdData ifd = new IfdData(ExifData.TYPE_IFD_GPS); - parseIfd(ifdParser.parseIfdBlock(), ifd, exifData); - exifData.addIfdData(ifd); - } else if(tag.getTagId() == ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD) { - IfdData ifd = new IfdData(ExifData.TYPE_IFD_INTEROPERABILITY); - parseIfd(ifdParser.parseIfdBlock(), ifd, exifData); - exifData.addIfdData(ifd); - } else { - readAndSaveTag(tag, ifdParser, ifdData); + case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: + tag = parser.getTag(); + if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { + byte[] buf = new byte[tag.getComponentCount()]; + parser.read(buf); + tag.setValue(buf); } + exifData.getIfdData(tag.getIfd()).setTag(tag); break; } - type = ifdParser.next(); - } - } - - public void readAndSaveTag(ExifTag tag, IfdParser parser, IfdData ifdData) - throws IOException { - switch(tag.getDataType()) { - case ExifTag.TYPE_BYTE: - { - byte buf[] = new byte[tag.getComponentCount()]; - parser.read(buf); - ifdData.addTag(tag, buf); - break; - } - case ExifTag.TYPE_ASCII: - ifdData.addTag(tag, parser.readString(tag.getComponentCount())); - break; - case ExifTag.TYPE_INT: - { - long value[] = new long[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = parser.readUnsignedInt(); - } - ifdData.addTag(tag, value); - break; - } - case ExifTag.TYPE_RATIONAL: - { - Rational value[] = new Rational[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = parser.readUnsignedRational(); - } - ifdData.addTag(tag, value); - break; - } - case ExifTag.TYPE_SHORT: - { - int value[] = new int[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = parser.readUnsignedShort(); - } - ifdData.addTag(tag, value); - break; - } - case ExifTag.TYPE_SINT: - { - int value[] = new int[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = parser.readInt(); - } - ifdData.addTag(tag, value); - break; - } - case ExifTag.TYPE_SRATIONAL: - { - Rational value[] = new Rational[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = parser.readRational(); - } - ifdData.addTag(tag, value); - break; - } - case ExifTag.TYPE_UNDEFINED: - { - byte buf[] = new byte[tag.getComponentCount()]; - parser.read(buf); - ifdData.addTag(tag, buf); - break; - } + event = parser.next(); } + return exifData; } -} +}
\ No newline at end of file diff --git a/src/com/android/gallery3d/exif/ExifTag.java b/src/com/android/gallery3d/exif/ExifTag.java index c11171c79..7b5a2eb6b 100644 --- a/src/com/android/gallery3d/exif/ExifTag.java +++ b/src/com/android/gallery3d/exif/ExifTag.java @@ -16,6 +16,15 @@ package com.android.gallery3d.exif; +import java.lang.reflect.Array; + +/** + * This class stores information of an EXIF tag. + * @see ExifParser + * @see ExifReader + * @see IfdData + * @see ExifData + */ public class ExifTag { public static interface TIFF_TAG { public static final short TAG_IMAGE_WIDTH = 0x100; @@ -315,54 +324,259 @@ public class ExifTag { public static final short INTEROPERABILITY_INDEX = 1; } - public static final short TYPE_BYTE = 1; + public static final short TYPE_UNSIGNED_BYTE = 1; public static final short TYPE_ASCII = 2; - public static final short TYPE_SHORT = 3; - public static final short TYPE_INT = 4; - public static final short TYPE_RATIONAL = 5; + public static final short TYPE_UNSIGNED_SHORT = 3; + public static final short TYPE_UNSIGNED_INT = 4; + public static final short TYPE_UNSIGNED_RATIONAL = 5; public static final short TYPE_UNDEFINED = 7; - public static final short TYPE_SINT = 9; - public static final short TYPE_SRATIONAL = 10; + public static final short TYPE_INT = 9; + public static final short TYPE_RATIONAL = 10; private static final int TYPE_TO_SIZE_MAP[] = new int[11]; static { - TYPE_TO_SIZE_MAP[TYPE_BYTE] = 1; + TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1; TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1; - TYPE_TO_SIZE_MAP[TYPE_SHORT] = 2; + TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2; + TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_INT] = 4; + TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8; + TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1; TYPE_TO_SIZE_MAP[TYPE_INT] = 4; TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8; - TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1; - TYPE_TO_SIZE_MAP[TYPE_SINT] = 4; - TYPE_TO_SIZE_MAP[TYPE_SRATIONAL] = 8; } + /** + * Gets the element size of the given data type. + * + * @see #TYPE_ASCII + * @see #TYPE_INT + * @see #TYPE_RATIONAL + * @see #TYPE_UNDEFINED + * @see #TYPE_UNSIGNED_BYTE + * @see #TYPE_UNSIGNED_INT + * @see #TYPE_UNSIGNED_RATIONAL + * @see #TYPE_UNSIGNED_SHORT + */ public static int getElementSize(short type) { return TYPE_TO_SIZE_MAP[type]; } private final short mTagId; private final short mDataType; - private final int mDataCount; + private final int mComponentCount; + private final int mIfd; + private Object mValue; + private int mOffset; - ExifTag(short tagId, short type, int dataCount) { + ExifTag(short tagId, short type, int componentCount, int ifd) { mTagId = tagId; mDataType = type; - mDataCount = dataCount; + mComponentCount = componentCount; + mIfd = ifd; } + /** + * Returns the ID of the IFD this tag belongs to. + * + * @see IfdId#TYPE_IFD_0 + * @see IfdId#TYPE_IFD_1 + * @see IfdId#TYPE_IFD_EXIF + * @see IfdId#TYPE_IFD_GPS + * @see IfdId#TYPE_IFD_INTEROPERABILITY + */ + public int getIfd() { + return mIfd; + } + + /** + * Gets the ID of this tag. + */ public short getTagId() { return mTagId; } + /** + * Gets the data type of this tag + * + * @see #TYPE_ASCII + * @see #TYPE_INT + * @see #TYPE_RATIONAL + * @see #TYPE_UNDEFINED + * @see #TYPE_UNSIGNED_BYTE + * @see #TYPE_UNSIGNED_INT + * @see #TYPE_UNSIGNED_RATIONAL + * @see #TYPE_UNSIGNED_SHORT + */ public short getDataType() { return mDataType; } + /** + * Gets the total data size in bytes of the value of this tag. + */ public int getDataSize() { return getComponentCount() * getElementSize(getDataType()); } + /** + * Gets the component count of this tag. + */ public int getComponentCount() { - return mDataCount; + return mComponentCount; + } + + /** + * Returns true if this ExifTag contains value; otherwise, this tag will contain an offset value + * that links to the area where the actual value is located. + * + * @see #getOffset() + */ + public boolean hasValue() { + return mValue != null; + } + + /** + * Gets the offset of this tag. This is only valid if this data size > 4 and contains an offset + * to the location of the actual value. + */ + public int getOffset() { + return mOffset; + } + + /** + * Sets the offset of this tag. + */ + void setOffset(int offset) { + mOffset = offset; + } + + /** + * Sets the value of this tag. This is useful when we want to modify the tags and write it back + * to the JPEG file + */ + public void setValue(Object object) { + if (object.getClass().isArray()) { + assert(mComponentCount == Array.getLength(object)); + mValue = object; + } else if (object instanceof String) { + assert(mComponentCount == ((String) object).length() + 1); + mValue = object; + } else { + // Wrap object with an array because user may try to get object by get method with + // index 0 when size = 1 + // e.g. getShort(0) + assert(mComponentCount == 1); + Object array = Array.newInstance(object.getClass(), 1); + Array.set(array, 0, object); + mValue = array; + } + } + + public short getShort(int index) { + return (Short) Array.get(mValue, index); + } + + public short getShort() { + return (Short) Array.get(mValue, 0); + } + + public int getUnsignedShort(int index) { + return (Integer) Array.get(mValue, index); + } + + public int getUnsignedShort() { + return (Integer) Array.get(mValue, 0); + } + + public int getInt(int index) { + return (Integer) Array.get(mValue, index); + } + + public int getInt() { + return (Integer) Array.get(mValue, 0); + } + + public long getUnsignedInt(int index) { + return (Long) Array.get(mValue, index); + } + + public long getUnsignedInt() { + return (Long) Array.get(mValue, 0); + } + + public String getString() { + return (String) mValue; + } + + public Rational getRational(int index) { + return ((Rational[]) mValue)[index]; + } + + public Rational getRational() { + return ((Rational[]) mValue)[0]; + } + + public int getBytes(byte[] buf) { + return getBytes(buf, 0, buf.length); + } + + public int getBytes(byte[] buf, int offset, int length) { + byte[] data = (byte[]) mValue; + if (data.length < length + offset) { + System.arraycopy(data, offset, buf, 0, data.length - offset); + return data.length - offset; + } else { + System.arraycopy(data, offset, buf, 0, length); + return length; + } + } + + /** + * Returns a string representation of the value of this tag. + */ + public String valueToString() { + StringBuilder sbuilder = new StringBuilder(); + switch(getDataType()) { + case ExifTag.TYPE_UNDEFINED: + case ExifTag.TYPE_UNSIGNED_BYTE: + byte buf[] = new byte[getComponentCount()]; + getBytes(buf); + for(int i = 0; i < getComponentCount(); i++) { + if(i != 0) sbuilder.append(" "); + sbuilder.append(String.format("%02x", buf[i])); + } + break; + case ExifTag.TYPE_ASCII: + // trim the string for comparison between xml + sbuilder.append(getString().trim()); + break; + case ExifTag.TYPE_UNSIGNED_INT: + for(int i = 0; i < getComponentCount(); i++) { + if(i != 0) sbuilder.append(" "); + sbuilder.append(getUnsignedInt(i)); + } + break; + case ExifTag.TYPE_RATIONAL: + case ExifTag.TYPE_UNSIGNED_RATIONAL: + for(int i = 0; i < getComponentCount(); i++) { + Rational r = getRational(i); + if(i != 0) sbuilder.append(" "); + sbuilder.append(r.getNominator()).append("/").append(r.getDenominator()); + } + break; + case ExifTag.TYPE_UNSIGNED_SHORT: + for(int i = 0; i < getComponentCount(); i++) { + if(i != 0) sbuilder.append(" "); + sbuilder.append(getUnsignedShort(i)); + } + break; + case ExifTag.TYPE_INT: + for(int i = 0; i < getComponentCount(); i++) { + if(i != 0) sbuilder.append(" "); + sbuilder.append(getInt(i)); + } + break; + } + return sbuilder.toString(); } }
\ No newline at end of file diff --git a/src/com/android/gallery3d/exif/IfdData.java b/src/com/android/gallery3d/exif/IfdData.java index b0d063023..4747c3cce 100644 --- a/src/com/android/gallery3d/exif/IfdData.java +++ b/src/com/android/gallery3d/exif/IfdData.java @@ -16,99 +16,64 @@ package com.android.gallery3d.exif; -import java.lang.reflect.Array; import java.util.HashMap; import java.util.Map; +/** + * This class stores all the tags in an IFD. + * + * @see ExifData + * @see ExifTag + */ public class IfdData { - private final int mIfdType; + private final int mIfdId; private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>(); - private final Map<Short, Object> mValues = new HashMap<Short, Object>(); - - public IfdData(int ifdType) { - mIfdType = ifdType; - } + /** + * Creates an IfdData with given IFD ID. + * + * @see IfdId#TYPE_IFD_0 + * @see IfdId#TYPE_IFD_1 + * @see IfdId#TYPE_IFD_EXIF + * @see IfdId#TYPE_IFD_GPS + * @see IfdId#TYPE_IFD_INTEROPERABILITY + */ + public IfdData(int ifdId) { + mIfdId = ifdId; + } + + /** + * Get a array the contains all {@link ExifTag} in this IFD. + */ public ExifTag[] getAllTags(ExifTag[] outTag) { return mExifTags.values().toArray(outTag); } - public int getIfdType() { - return mIfdType; + /** + * Gets the ID of this IFD. + * + * @see IfdId#TYPE_IFD_0 + * @see IfdId#TYPE_IFD_1 + * @see IfdId#TYPE_IFD_EXIF + * @see IfdId#TYPE_IFD_GPS + * @see IfdId#TYPE_IFD_INTEROPERABILITY + */ + public int getId() { + return mIfdId; } + /** + * Gets the {@link ExifTag} with given tag id. Return null if there is no such tag. + */ public ExifTag getTag(short tagId) { return mExifTags.get(tagId); } - public short getShort(short tagId, int index) { - return (Short) Array.get(mValues.get(tagId), index); - } - - public short getShort(short tagId) { - return (Short) Array.get(mValues.get(tagId), 0); - } - - public int getUnsignedShort(short tagId, int index) { - return (Integer) Array.get(mValues.get(tagId), index); - } - - public int getUnsignedShort(short tagId) { - return (Integer) Array.get(mValues.get(tagId), 0); - } - - public int getInt(short tagId, int index) { - return (Integer) Array.get(mValues.get(tagId), index); - } - - public int getInt(short tagId) { - return (Integer) Array.get(mValues.get(tagId), 0); - } - - public long getUnsignedInt(short tagId, int index) { - return (Long) Array.get(mValues.get(tagId), index); - } - - public long getUnsignedInt(short tagId) { - return (Long) Array.get(mValues.get(tagId), 0); - } - - public String getString(short tagId) { - return (String) mValues.get(tagId); - } - - public Rational getRational(short tagId, int index) { - return ((Rational[]) mValues.get(tagId))[index]; - } - - public Rational getRational(short tagId) { - return ((Rational[]) mValues.get(tagId))[0]; - } - - public int getBytes(short tagId, byte[] buf) { - return getBytes(tagId, buf, 0, buf.length); - } - - public int getBytes(short tagId, byte[] buf, int offset, int length) { - Object data = mValues.get(tagId); - if (Array.getLength(data) < length + offset) { - System.arraycopy(data, offset, buf, 0, Array.getLength(data) - offset); - return Array.getLength(data) - offset; - } else { - System.arraycopy(data, offset, buf, 0, length); - return length; - } - } - - public void addTag(ExifTag tag, Object object) { - mExifTags.put(tag.getTagId(), tag); - if (object.getClass().isArray() || object.getClass() == String.class) { - mValues.put(tag.getTagId(), object); - } else { - Object array = Array.newInstance(object.getClass(), 1); - Array.set(array, 0, object); - mValues.put(tag.getTagId(), array); - } + /** + * Adds or replaces a {@link ExifTag}. + */ + public void setTag(ExifTag tag) { + mExifTags.put(tag.getTagId(), tag); } -} +}
\ No newline at end of file diff --git a/src/com/android/gallery3d/exif/IfdId.java b/src/com/android/gallery3d/exif/IfdId.java new file mode 100644 index 000000000..1b9634369 --- /dev/null +++ b/src/com/android/gallery3d/exif/IfdId.java @@ -0,0 +1,26 @@ +/* + * 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; + +public interface IfdId { + public static final int TYPE_IFD_0 = 0; + public static final int TYPE_IFD_1 = 1; + public static final int TYPE_IFD_EXIF = 2; + public static final int TYPE_IFD_INTEROPERABILITY = 3; + public static final int TYPE_IFD_GPS = 4; + /* This is use in ExifData to allocate enough IfdData */ + static final int TYPE_IFD_COUNT = 5; +} diff --git a/src/com/android/gallery3d/exif/IfdParser.java b/src/com/android/gallery3d/exif/IfdParser.java deleted file mode 100644 index 0d1059cb0..000000000 --- a/src/com/android/gallery3d/exif/IfdParser.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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 java.io.IOException; -import java.nio.charset.Charset; -import java.util.Map.Entry; -import java.util.TreeMap; - -public class IfdParser { - - private static final int TAG_SIZE = 12; - - private final TiffInputStream mTiffStream; - private final int mEndOfTagOffset; - private final int mNumOfTag; - private int mNextOffset; - private int mOffsetToNextIfd = 0; - - private TreeMap<Integer, ExifTag> mCorrespondingTag = new TreeMap<Integer, ExifTag>(); - - private ExifTag mCurrTag; - private int mCurrTagOffset; - - public static final int TYPE_NEW_TAG = 0; - public static final int TYPE_VALUE_OF_PREV_TAG = 1; - public static final int TYPE_NEXT_IFD = 2; - public static final int TYPE_END = 3; - - IfdParser(TiffInputStream tiffStream, int offset) throws IOException { - mTiffStream = tiffStream; - mTiffStream.skipTo(offset); - mNumOfTag = mTiffStream.readUnsignedShort(); - mEndOfTagOffset = offset + mNumOfTag * TAG_SIZE + 2; - mNextOffset = offset + 2; - } - - public int next() throws IOException { - int offset = mTiffStream.getReadByteCount(); - if (offset < mEndOfTagOffset) { - offset = mNextOffset; - skipTo(mNextOffset); - - if(mNextOffset < mEndOfTagOffset) { - mNextOffset += TAG_SIZE; - return TYPE_NEW_TAG; - } - } - - if (offset == mEndOfTagOffset) { - mOffsetToNextIfd = mTiffStream.readInt(); - } - - if (!mCorrespondingTag.isEmpty()) { - Entry<Integer, ExifTag> entry = mCorrespondingTag.pollFirstEntry(); - mCurrTag = entry.getValue(); - mCurrTagOffset = entry.getKey(); - skipTo(entry.getKey()); - return TYPE_VALUE_OF_PREV_TAG; - } else { - if (offset <= mOffsetToNextIfd) { - skipTo(mOffsetToNextIfd); - // Reset mOffsetToNextIfd to 0 so next call to next() will point to the end - mOffsetToNextIfd = 0; - return TYPE_NEXT_IFD; - } else { - return TYPE_END; - } - } - } - - public 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"); - } - return new ExifTag(tagId, dataFormat, (int) numOfComp); - } - - public ExifTag getCorrespodingExifTag() { - return mCurrTagOffset != mTiffStream.getReadByteCount() ? null : mCurrTag; - } - - public void waitValueOfTag(ExifTag tag, long offset) { - if (offset > Integer.MAX_VALUE || offset < 0) { - throw new IllegalArgumentException(offset + " must be in 0 ~ " + Integer.MAX_VALUE); - } - mCorrespondingTag.put((int) offset, tag); - } - - public void skipTo(int offset) throws IOException { - mTiffStream.skipTo(offset); - while (!mCorrespondingTag.isEmpty() && mCorrespondingTag.firstKey() < offset) { - mCorrespondingTag.pollFirstEntry(); - } - } - - public IfdParser parseIfdBlock() throws IOException { - return new IfdParser(mTiffStream, mTiffStream.getReadByteCount()); - } - - public int read(byte[] buffer, int offset, int length) throws IOException { - return mTiffStream.read(buffer, offset, length); - } - - public int read(byte[] buffer) throws IOException { - return mTiffStream.read(buffer); - } - - public String readString(int n) throws IOException { - if (n > 0) { - byte[] buf = new byte[n]; - mTiffStream.readOrThrow(buf); - return new String(buf, 0, n - 1, "UTF8"); - } else { - return ""; - } - } - - public String readString(int n, Charset charset) throws IOException { - byte[] buf = new byte[n]; - mTiffStream.readOrThrow(buf); - return new String(buf, 0, n - 1, charset); - } - - public int readUnsignedShort() throws IOException { - return readShort() & 0xffff; - } - - public long readUnsignedInt() throws IOException { - return readInt() & 0xffffffffL; - } - - public Rational readUnsignedRational() throws IOException { - long nomi = readUnsignedInt(); - long denomi = readUnsignedInt(); - return new Rational(nomi, denomi); - } - - public int readInt() throws IOException { - return mTiffStream.readInt(); - } - - public short readShort() throws IOException { - return mTiffStream.readShort(); - } - - public Rational readRational() throws IOException { - int nomi = readInt(); - int denomi = readInt(); - return new Rational(nomi, denomi); - } -}
\ No newline at end of file diff --git a/src/com/android/gallery3d/exif/Rational.java b/src/com/android/gallery3d/exif/Rational.java index cef6c9112..4c3c77ffb 100644 --- a/src/com/android/gallery3d/exif/Rational.java +++ b/src/com/android/gallery3d/exif/Rational.java @@ -33,4 +33,4 @@ public class Rational { public long getDenominator() { return mDenominator; } -} +}
\ No newline at end of file diff --git a/tests/src/com/android/gallery3d/exif/ExifParserTest.java b/tests/src/com/android/gallery3d/exif/ExifParserTest.java index 86ef12fa7..241aaf575 100644 --- a/tests/src/com/android/gallery3d/exif/ExifParserTest.java +++ b/tests/src/com/android/gallery3d/exif/ExifParserTest.java @@ -20,7 +20,6 @@ import android.content.res.XmlResourceParser; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.test.InstrumentationTestCase; -import android.util.Log; import java.io.IOException; import java.io.InputStream; @@ -58,338 +57,176 @@ public class ExifParserTest extends InstrumentationTestCase { } public void testParse() throws IOException, ExifInvalidFormatException { - ExifParser parser = new ExifParser(); - parseIfd0(parser.parse(mImageInputStream)); - } - - private void parseIfd0(IfdParser ifdParser) throws IOException, - ExifInvalidFormatException { - int type = ifdParser.next(); - int tagNumber=0; - boolean isEnterNextIfd = false; - boolean isEnterExifIfd = false; - while (type != IfdParser.TYPE_END) { - switch (type) { - case IfdParser.TYPE_NEW_TAG: - ExifTag tag = ifdParser.readTag(); - if (tag.getDataSize() > 4 || tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) { - long offset = ifdParser.readUnsignedInt(); - assertTrue(offset <= Integer.MAX_VALUE); - ifdParser.waitValueOfTag(tag, offset); + ExifParser parser = ExifParser.parse(mImageInputStream); + 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 { - checkTag(tag, ifdParser, mIfd0Value); + checkTag(tag); } - tagNumber++; break; - case IfdParser.TYPE_NEXT_IFD: - parseIfd1(ifdParser.parseIfdBlock()); - isEnterNextIfd = true; - break; - case IfdParser.TYPE_VALUE_OF_PREV_TAG: - tag = ifdParser.getCorrespodingExifTag(); - if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) { - parseExifIfd(ifdParser.parseIfdBlock()); - isEnterExifIfd = true; - } else { - checkTag(ifdParser.getCorrespodingExifTag(), ifdParser, mIfd0Value); + case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: + tag = parser.getTag(); + if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { + byte[] buf = new byte[tag.getComponentCount()]; + parser.read(buf); + tag.setValue(buf); } + checkTag(tag); break; } - type = ifdParser.next(); + event = parser.next(); } - assertEquals(mIfd0Value.size(), tagNumber); - assertTrue(isEnterNextIfd); - assertTrue(isEnterExifIfd); } - private void parseIfd1(IfdParser ifdParser) throws IOException, - ExifInvalidFormatException { - int type = ifdParser.next(); - int tagNumber = 0; - while (type != IfdParser.TYPE_END) { - switch (type) { - case IfdParser.TYPE_NEW_TAG: - ExifTag tag = ifdParser.readTag(); - if (tag.getDataSize() > 4) { - long offset = ifdParser.readUnsignedInt(); - assertTrue(offset <= Integer.MAX_VALUE); - ifdParser.waitValueOfTag(tag, offset); - } else { - checkTag(tag, ifdParser, mIfd1Value); - } - tagNumber++; - break; - case IfdParser.TYPE_NEXT_IFD: - fail("Find a ifd after ifd1"); - break; - case IfdParser.TYPE_VALUE_OF_PREV_TAG: - checkTag(ifdParser.getCorrespodingExifTag(), ifdParser, mIfd1Value); - break; - } - type = ifdParser.next(); + private void checkTag(ExifTag tag) { + HashMap<Short, String> truth = null; + switch (tag.getIfd()) { + case IfdId.TYPE_IFD_0: + truth = mIfd0Value; + break; + case IfdId.TYPE_IFD_1: + truth = mIfd1Value; + break; + case IfdId.TYPE_IFD_EXIF: + truth = mExifIfdValue; + break; + case IfdId.TYPE_IFD_INTEROPERABILITY: + truth = mInteroperabilityIfdValue; + break; } - assertEquals(mIfd1Value.size(), tagNumber); - } - private void parseExifIfd(IfdParser ifdParser) throws IOException, - ExifInvalidFormatException { - int type = ifdParser.next(); - int tagNumber = 0; - boolean isHasInterIfd = false; - boolean isEnterInterIfd = false; - while (type != IfdParser.TYPE_END) { - switch (type) { - case IfdParser.TYPE_NEW_TAG: - ExifTag tag = ifdParser.readTag(); - if (tag.getDataSize() > 4) { - long offset = ifdParser.readUnsignedInt(); - assertTrue(offset <= Integer.MAX_VALUE); - ifdParser.waitValueOfTag(tag, offset); - } else if (tag.getTagId() == ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD) { - long offset = ifdParser.readUnsignedInt(); - assertTrue(offset <= Integer.MAX_VALUE); - ifdParser.waitValueOfTag(tag, offset); - isHasInterIfd = true; - } else { - checkTag(tag, ifdParser, mExifIfdValue); - } - tagNumber++; - break; - case IfdParser.TYPE_NEXT_IFD: - fail("Find a ifd after exif ifd"); - break; - case IfdParser.TYPE_VALUE_OF_PREV_TAG: - tag = ifdParser.getCorrespodingExifTag(); - if (tag.getTagId() == ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD) { - parseInteroperabilityIfd(ifdParser.parseIfdBlock()); - isEnterInterIfd = true; - } else { - checkTag(ifdParser.getCorrespodingExifTag(), ifdParser, mExifIfdValue); - } - break; - } - type = ifdParser.next(); - } - assertEquals(mExifIfdValue.size(), tagNumber); - if (isHasInterIfd) { - assertTrue(isEnterInterIfd); + String truthString = truth.get(tag.getTagId()); + String dataString = tag.valueToString(); + if (truthString == null) { + fail(String.format("Unknown Tag %02x", tag.getTagId())); } + assertEquals(String.format("Tag %02x", tag.getTagId()), truthString, dataString); } - private void parseInteroperabilityIfd(IfdParser ifdParser) throws IOException, - ExifInvalidFormatException { - int type = ifdParser.next(); - int tagNumber = 0; - while (type != IfdParser.TYPE_END) { - switch (type) { - case IfdParser.TYPE_NEW_TAG: - ExifTag tag = ifdParser.readTag(); - if (tag.getDataSize() > 4) { - long offset = ifdParser.readUnsignedInt(); - assertTrue(offset <= Integer.MAX_VALUE); - ifdParser.waitValueOfTag(tag, offset); + + private void parseOneIfd(int ifd, int options, HashMap<Short, String> expectedResult) + throws IOException, ExifInvalidFormatException { + int numOfTag = 0; + ExifParser parser = ExifParser.parse(mImageInputStream, options); + int event = parser.next(); + while(event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_START_OF_IFD: + assertEquals(ifd, parser.getCurrentIfd()); + break; + case ExifParser.EVENT_NEW_TAG: + numOfTag++; + ExifTag tag = parser.getTag(); + if (tag.hasValue()) { + checkTag(tag); } else { - checkTag(tag, ifdParser, mInteroperabilityIfdValue); + parser.registerForTagValue(tag); } - tagNumber++; break; - case IfdParser.TYPE_NEXT_IFD: - fail("Find a ifd after exif ifd"); + case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: + tag = parser.getTag(); + if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { + byte[] buf = new byte[tag.getComponentCount()]; + parser.read(buf); + tag.setValue(buf); + } + checkTag(tag); break; - case IfdParser.TYPE_VALUE_OF_PREV_TAG: - checkTag(ifdParser.getCorrespodingExifTag(), ifdParser - , mInteroperabilityIfdValue); + case ExifParser.EVENT_COMPRESSED_IMAGE: + case ExifParser.EVENT_UNCOMPRESSED_STRIP: + fail("Invalid Event type: " + event); break; } - type = ifdParser.next(); + event = parser.next(); } - assertEquals(mInteroperabilityIfdValue.size(), tagNumber); + assertEquals(expectedResult.size(), numOfTag); } - private void checkTag(ExifTag tag, IfdParser ifdParser, HashMap<Short, String> truth) - throws IOException { - String truthString = truth.get(tag.getTagId()); - if (truthString == null) { - fail(String.format("Unknown Tag %02x", tag.getTagId())); - } - String dataString = readValueToString(tag, ifdParser); - if (!truthString.equals(dataString)) { - fail(String.format("Tag %02x: expect %s but %s", - tag.getTagId(), truthString, dataString)); - } + public void testOnlyExifIfd() throws IOException, ExifInvalidFormatException { + parseOneIfd(IfdId.TYPE_IFD_EXIF, ExifParser.OPTION_IFD_EXIF, mExifIfdValue); } - private String readValueToString(ExifTag tag, IfdParser parser) throws IOException { - StringBuilder sbuilder = new StringBuilder(); - switch(tag.getDataType()) { - case ExifTag.TYPE_BYTE: - byte buf[] = new byte[tag.getComponentCount()]; - parser.read(buf); - for(int i = 0; i < tag.getComponentCount(); i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(String.format("%02x", buf[i])); - } - break; - case ExifTag.TYPE_ASCII: - // trim the string for comparison between xml - sbuilder.append(parser.readString(tag.getComponentCount()).trim()); - break; - case ExifTag.TYPE_INT: - for(int i = 0; i < tag.getComponentCount(); i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(parser.readUnsignedInt()); - } - break; - case ExifTag.TYPE_RATIONAL: - for(int i = 0; i < tag.getComponentCount(); i++) { - Rational r = parser.readUnsignedRational(); - if(i != 0) sbuilder.append(" "); - sbuilder.append(r.getNominator()).append("/").append(r.getDenominator()); - } - break; - case ExifTag.TYPE_SHORT: - for(int i = 0; i < tag.getComponentCount(); i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(parser.readUnsignedShort()); - } - break; - case ExifTag.TYPE_SINT: - for(int i = 0; i < tag.getComponentCount(); i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(parser.readInt()); - } - break; - case ExifTag.TYPE_SRATIONAL: - for(int i = 0; i < tag.getComponentCount(); i++) { - Rational r = parser.readRational(); - if(i != 0) sbuilder.append(" "); - sbuilder.append(r.getNominator()).append("/").append(r.getDenominator()); - } - break; - case ExifTag.TYPE_UNDEFINED: - byte buffer[] = new byte[tag.getComponentCount()]; - parser.read(buffer); - for(int i = 0; i < tag.getComponentCount(); i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(String.format("%02x", buffer[i])); - } - break; - } - return sbuilder.toString(); + public void testOnlyIfd0() throws IOException, ExifInvalidFormatException { + parseOneIfd(IfdId.TYPE_IFD_0, ExifParser.OPTION_IFD_0, mIfd0Value); } - public void testSkipToNextIfd() throws ExifInvalidFormatException, IOException { - ExifParser exifParser = new ExifParser(); - IfdParser ifdParser = exifParser.parse(mImageInputStream); - int type = ifdParser.next(); - boolean isEnterNextIfd = false; - while (type != IfdParser.TYPE_END) { - switch (type) { - case IfdParser.TYPE_NEW_TAG: - // Do nothing, we don't care - break; - case IfdParser.TYPE_NEXT_IFD: - parseIfd1(ifdParser.parseIfdBlock()); - isEnterNextIfd = true; - break; - case IfdParser.TYPE_VALUE_OF_PREV_TAG: - // We won't get this since to skip everything - fail("Get value of previous tag but we've skip everything"); - break; - } - type = ifdParser.next(); - } - assertTrue(isEnterNextIfd); + public void testOnlyIfd1() throws IOException, ExifInvalidFormatException { + parseOneIfd(IfdId.TYPE_IFD_1, ExifParser.OPTION_IFD_1, mIfd1Value); + } + + public void testOnlyInteroperabilityIfd() throws IOException, ExifInvalidFormatException { + parseOneIfd(IfdId.TYPE_IFD_INTEROPERABILITY, ExifParser.OPTION_IFD_INTEROPERABILITY + , mInteroperabilityIfdValue); } - public void testOnlySaveSomeValue() throws ExifInvalidFormatException, IOException { - ExifParser exifParser = new ExifParser(); - IfdParser ifdParser = exifParser.parse(mImageInputStream); - boolean isEnterNextIfd = false; - boolean isEnterExifIfd = false; - int type = ifdParser.next(); - while (type != IfdParser.TYPE_END) { - switch (type) { - case IfdParser.TYPE_NEW_TAG: - ExifTag tag = ifdParser.readTag(); - // only interested in these two tags - if (tag.getDataSize() > 4 || tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) { - if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_MODEL - || tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) { - long offset = ifdParser.readUnsignedInt(); - assertTrue(offset <= Integer.MAX_VALUE); - ifdParser.waitValueOfTag(tag, offset); + public void testOnlyReadSomeTag() throws IOException, ExifInvalidFormatException { + ExifParser parser = ExifParser.parse(mImageInputStream, ExifParser.OPTION_IFD_0); + int event = parser.next(); + boolean isTagFound = false; + while (event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_START_OF_IFD: + assertEquals(IfdId.TYPE_IFD_0, parser.getCurrentIfd()); + break; + case ExifParser.EVENT_NEW_TAG: + ExifTag tag = parser.getTag(); + if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_MODEL) { + if (tag.hasValue()) { + isTagFound = true; + checkTag(tag); + } else { + parser.registerForTagValue(tag); } + parser.skipRemainingTagsInCurrentIfd(); } break; - case IfdParser.TYPE_NEXT_IFD: - parseIfd1(ifdParser.parseIfdBlock()); - isEnterNextIfd = true; - break; - case IfdParser.TYPE_VALUE_OF_PREV_TAG: - tag = ifdParser.getCorrespodingExifTag(); - if(tag.getTagId() == ExifTag.TIFF_TAG.TAG_EXIF_IFD) { - parseExifIfd(ifdParser.parseIfdBlock()); - isEnterExifIfd = true; - } else { - checkTag(ifdParser.getCorrespodingExifTag(), ifdParser, mIfd0Value); - } + case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: + tag = parser.getTag(); + assertEquals(ExifTag.TIFF_TAG.TAG_MODEL, tag.getTagId()); + checkTag(tag); + isTagFound = true; break; } - type = ifdParser.next(); + event = parser.next(); } - assertTrue(isEnterNextIfd); - assertTrue(isEnterExifIfd); + assertTrue(isTagFound); } public void testReadThumbnail() throws ExifInvalidFormatException, IOException { - ExifParser exifParser = new ExifParser(); - IfdParser ifdParser = exifParser.parse(mImageInputStream); - int type = ifdParser.next(); - while (type != IfdParser.TYPE_END && type != IfdParser.TYPE_NEXT_IFD) { - type = ifdParser.next(); - } - if (type == IfdParser.TYPE_END) { - Log.i(TAG, "No Thumbnail"); - return; - } - IfdParser ifd1Parser = ifdParser.parseIfdBlock(); - int thumbOffset = 0; - int thumbSize = 0; - boolean isFinishRead = false; - while (!isFinishRead) { - switch (ifd1Parser.next()) { - case IfdParser.TYPE_NEW_TAG: - ExifTag tag = ifd1Parser.readTag(); - if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT) { - long unsignedInt = ifdParser.readUnsignedInt(); - assertTrue(unsignedInt <= Integer.MAX_VALUE); - thumbOffset = (int) unsignedInt; - } else if (tag.getTagId() == - ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) { - long unsignedInt = ifdParser.readUnsignedInt(); - assertTrue(unsignedInt <= Integer.MAX_VALUE); - thumbSize = (int) unsignedInt; - } else if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_COMPRESSION) { - if (ifdParser.readUnsignedShort() == - ExifTag.TIFF_TAG.COMPRESSION_UNCOMPRESSION) { - // This test doesn't apply to uncompression thumbnail. - return; + ExifParser parser = ExifParser.parse(mImageInputStream, + ExifParser.OPTION_IFD_1 | ExifParser.OPTION_THUMBNAIL); + + int event = parser.next(); + Bitmap bmp = null; + boolean mIsContainCompressedImage = false; + while (event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_NEW_TAG: + ExifTag tag = parser.getTag(); + if (tag.getTagId() == ExifTag.TIFF_TAG.TAG_COMPRESSION) { + if (tag.getUnsignedShort() == ExifTag.TIFF_TAG.COMPRESSION_JPEG) { + mIsContainCompressedImage = true; } } - isFinishRead = thumbOffset != 0 && thumbSize != 0; break; - case IfdParser.TYPE_END: - fail("No thumbnail information found"); + case ExifParser.EVENT_COMPRESSED_IMAGE: + int imageSize = parser.getCompressedImageSize(); + byte buf[] = new byte[imageSize]; + parser.read(buf); + bmp = BitmapFactory.decodeByteArray(buf, 0, imageSize); break; } + event = parser.next(); + } + if (mIsContainCompressedImage) { + assertNotNull(bmp); } - - byte buf[] = new byte[thumbSize]; - ifd1Parser.skipTo(thumbOffset); - ifd1Parser.read(buf); - Bitmap bmp = BitmapFactory.decodeByteArray(buf, 0, thumbSize); - // Check correctly decoded - assertTrue(bmp != null); } @Override @@ -399,4 +236,4 @@ public class ExifParserTest extends InstrumentationTestCase { mIfd1Value.clear(); mExifIfdValue.clear(); } -} +}
\ No newline at end of file diff --git a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java index 9964997f2..c0c2f9f81 100644 --- a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java +++ b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java @@ -57,72 +57,25 @@ public class ExifReaderTest extends InstrumentationTestCase { public void testRead() throws ExifInvalidFormatException, IOException { ExifReader reader = new ExifReader(); ExifData exifData = reader.getExifData(mImageInputStream); - checkIfd(exifData, ExifData.TYPE_IFD_0, mIfd0Value); - checkIfd(exifData, ExifData.TYPE_IFD_1, mIfd1Value); - checkIfd(exifData, ExifData.TYPE_IFD_EXIF, mExifIfdValue); - checkIfd(exifData, ExifData.TYPE_IFD_INTEROPERABILITY, mInteroperabilityIfdValue); + checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_0), mIfd0Value); + checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_1), mIfd1Value); + checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_EXIF), mExifIfdValue); + checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY), + mInteroperabilityIfdValue); } - private void checkIfd(ExifData exifData, int ifdType, HashMap<Short, String> ifdValue) - throws IOException { - IfdData ifd = exifData.getIfdData(ifdType); + private void checkIfd(IfdData ifd, HashMap<Short, String> ifdValue) { if (ifd == null) { assertEquals(0 ,ifdValue.size()); return; } ExifTag[] tags = ifd.getAllTags(new ExifTag[0]); for (ExifTag tag : tags) { - assertEquals(ifdValue.get(tag.getTagId()), readValueToString(tag, ifd)); + assertEquals(ifdValue.get(tag.getTagId()), tag.valueToString()); } assertEquals(ifdValue.size(), tags.length); } - private String readValueToString(ExifTag tag, IfdData ifdData) throws IOException { - StringBuilder sbuilder = new StringBuilder(); - switch(tag.getDataType()) { - case ExifTag.TYPE_UNDEFINED: - case ExifTag.TYPE_BYTE: - byte buf[] = new byte[tag.getComponentCount()]; - ifdData.getBytes(tag.getTagId(), buf); - for(int i = 0; i < tag.getComponentCount(); i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(String.format("%02x", buf[i])); - } - break; - case ExifTag.TYPE_ASCII: - // trim the string for comparison between xml - sbuilder.append(ifdData.getString(tag.getTagId()).trim()); - break; - case ExifTag.TYPE_INT: - for(int i = 0; i < tag.getComponentCount(); i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(ifdData.getUnsignedInt(tag.getTagId(), i)); - } - break; - case ExifTag.TYPE_SRATIONAL: - case ExifTag.TYPE_RATIONAL: - for(int i = 0; i < tag.getComponentCount(); i++) { - Rational r = ifdData.getRational(tag.getTagId(), i); - if(i != 0) sbuilder.append(" "); - sbuilder.append(r.getNominator()).append("/").append(r.getDenominator()); - } - break; - case ExifTag.TYPE_SHORT: - for(int i = 0; i < tag.getComponentCount(); i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(ifdData.getUnsignedShort(tag.getTagId(), i)); - } - break; - case ExifTag.TYPE_SINT: - for(int i = 0; i < tag.getComponentCount(); i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(ifdData.getInt(tag.getTagId(), i)); - } - break; - } - return sbuilder.toString(); - } - @Override public void tearDown() throws Exception { mImageInputStream.close(); diff --git a/tests/src/com/android/gallery3d/exif/ExifXmlReader.java b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java index ea748cc18..72dd31373 100644 --- a/tests/src/com/android/gallery3d/exif/ExifXmlReader.java +++ b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java @@ -94,4 +94,4 @@ public class ExifXmlReader { data.put(id, value); parser.require(XmlPullParser.END_TAG, null, XML_TAG); } -} +}
\ No newline at end of file |