summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEarl Ou <shunhsingou@google.com>2012-08-14 17:50:08 +0800
committerEarl Ou <shunhsingou@google.com>2012-08-24 16:35:19 +0800
commitccd9f271fa2bfa56196aef78d0a87d3b00be2015 (patch)
tree408d139c310851e4a4c65a84e01c0172aa8d3f74 /src
parenta987b12c396ef9b0482c4ca827131db125bf8ef8 (diff)
downloadandroid_packages_apps_Snap-ccd9f271fa2bfa56196aef78d0a87d3b00be2015.tar.gz
android_packages_apps_Snap-ccd9f271fa2bfa56196aef78d0a87d3b00be2015.tar.bz2
android_packages_apps_Snap-ccd9f271fa2bfa56196aef78d0a87d3b00be2015.zip
Re-design ExifParser for some strange Exif header
The previous design failed to parser file with strange IFD position. Therefore we need a new design. Change-Id: I9f008a9259df6ef7d9a5022e126d6f25069e3d00
Diffstat (limited to 'src')
-rw-r--r--src/com/android/gallery3d/exif/ExifData.java37
-rw-r--r--src/com/android/gallery3d/exif/ExifParser.java653
-rw-r--r--src/com/android/gallery3d/exif/ExifReader.java146
-rw-r--r--src/com/android/gallery3d/exif/ExifTag.java244
-rw-r--r--src/com/android/gallery3d/exif/IfdData.java121
-rw-r--r--src/com/android/gallery3d/exif/IfdId.java26
-rw-r--r--src/com/android/gallery3d/exif/IfdParser.java170
-rw-r--r--src/com/android/gallery3d/exif/Rational.java2
8 files changed, 983 insertions, 416 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