diff options
-rw-r--r-- | src/com/android/gallery3d/app/CropImage.java | 13 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/ExifData.java | 92 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/ExifOutputStream.java | 22 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/ExifParser.java | 83 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/ExifTag.java | 772 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/IfdData.java | 2 | ||||
-rw-r--r-- | tests/src/com/android/gallery3d/exif/ExifParserTest.java | 2 | ||||
-rw-r--r-- | tests/src/com/android/gallery3d/exif/ExifReaderTest.java | 6 |
8 files changed, 817 insertions, 175 deletions
diff --git a/src/com/android/gallery3d/app/CropImage.java b/src/com/android/gallery3d/app/CropImage.java index c77f57c05..78eeac700 100644 --- a/src/com/android/gallery3d/app/CropImage.java +++ b/src/com/android/gallery3d/app/CropImage.java @@ -59,7 +59,6 @@ import com.android.gallery3d.exif.ExifData; import com.android.gallery3d.exif.ExifOutputStream; import com.android.gallery3d.exif.ExifReader; import com.android.gallery3d.exif.ExifTag; -import com.android.gallery3d.exif.IfdId; import com.android.gallery3d.picasasource.PicasaSource; import com.android.gallery3d.ui.BitmapTileProvider; import com.android.gallery3d.ui.CropView; @@ -399,16 +398,8 @@ public class CropImage extends AbstractGalleryActivity { } private void changeExifImageSizeTags(ExifData data, int width, int height) { - // FIXME: would the image size be too large for TYPE_UNSIGHED_SHORT? - ExifTag tag = new ExifTag(ExifTag.TAG_IMAGE_WIDTH, - ExifTag.TYPE_UNSIGNED_SHORT, 1, IfdId.TYPE_IFD_0); - tag.setValue(new int[] {width}); - data.getIfdData(IfdId.TYPE_IFD_0).setTag(tag); - - tag = new ExifTag(ExifTag.TAG_IMAGE_LENGTH, - ExifTag.TYPE_UNSIGNED_SHORT, 1, IfdId.TYPE_IFD_0); - tag.setValue(new int[] {height}); - data.getIfdData(IfdId.TYPE_IFD_0).setTag(tag); + data.addTag(ExifTag.TAG_IMAGE_WIDTH).setValue(width); + data.addTag(ExifTag.TAG_IMAGE_LENGTH).setValue(height); } private Uri saveToMediaProvider(JobContext jc, Bitmap cropped) { diff --git a/src/com/android/gallery3d/exif/ExifData.java b/src/com/android/gallery3d/exif/ExifData.java index 0c16cc9aa..59b5f97ea 100644 --- a/src/com/android/gallery3d/exif/ExifData.java +++ b/src/com/android/gallery3d/exif/ExifData.java @@ -36,16 +36,7 @@ public class ExifData { mByteOrder = order; } - /** - * 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) { + IfdData getIfdData(int ifdId) { return mIfdDatas[ifdId]; } @@ -53,7 +44,7 @@ public class ExifData { * 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) { + void addIfdData(IfdData data) { mIfdDatas[data.getId()] = data; } @@ -143,6 +134,11 @@ public class ExifData { return false; } + /** + * Adds {@link ExifTag#TAG_GPS_LATITUDE}, {@link ExifTag#TAG_GPS_LONGITUDE}, + * {@link ExifTag#TAG_GPS_LATITUDE_REF} and {@link ExifTag#TAG_GPS_LONGITUDE_REF} with the + * given latitude and longitude. + */ public void addGpsTags(double latitude, double longitude) { IfdData gpsIfd = getIfdData(IfdId.TYPE_IFD_GPS); if (gpsIfd == null) { @@ -182,4 +178,78 @@ public class ExifData { return new Rational[] { new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)}; } + + private IfdData getOrCreateIfdData(int ifdId) { + IfdData ifdData = mIfdDatas[ifdId]; + if (ifdData == null) { + ifdData = new IfdData(ifdId); + mIfdDatas[ifdId] = ifdData; + } + return ifdData; + } + + /** + * Gets the tag with the given tag ID. Returns null if the tag does not exist. For tags + * related to interoperability or thumbnail, call {@link #getInteroperabilityTag(short)} and + * {@link #getThumbnailTag(short)} respectively. + */ + public ExifTag getTag(short tagId) { + int ifdId = ExifTag.getIfdIdFromTagId(tagId); + IfdData ifdData = mIfdDatas[ifdId]; + return (ifdData == null) ? null : ifdData.getTag(tagId); + } + + /** + * Gets the thumbnail-related tag with the given tag ID. + */ + public ExifTag getThumbnailTag(short tagId) { + IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_1]; + return (ifdData == null) ? null : ifdData.getTag(tagId); + } + + /** + * Gets the interoperability-related tag with the given tag ID. + */ + public ExifTag getInteroperabilityTag(short tagId) { + IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_INTEROPERABILITY]; + return (ifdData == null) ? null : ifdData.getTag(tagId); + } + + /** + * Adds a tag with the given tag ID. The original tag will be replaced by the new tag. For tags + * related to interoperability or thumbnail, call {@link #addInteroperabilityTag(short)} or + * {@link #addThumbnailTag(short)} respectively. + * @exception IllegalArgumentException if the tag ID is invalid. + */ + public ExifTag addTag(short tagId) { + int ifdId = ExifTag.getIfdIdFromTagId(tagId); + IfdData ifdData = getOrCreateIfdData(ifdId); + ExifTag tag = ExifTag.buildTag(tagId); + ifdData.setTag(tag); + return tag; + } + + /** + * Adds a thumbnail-related tag with the given tag ID. The original tag will be replaced + * by the new tag. + * @exception IllegalArgumentException if the tag ID is invalid. + */ + public ExifTag addThumbnailTag(short tagId) { + IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_1); + ExifTag tag = ExifTag.buildThumbnailTag(tagId); + ifdData.setTag(tag); + return tag; + } + + /** + * Adds an interoperability-related tag with the given tag ID. The original tag will be + * replaced by the new tag. + * @exception IllegalArgumentException if the tag ID is invalid. + */ + public ExifTag addInteroperabilityTag(short tagId) { + IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); + ExifTag tag = ExifTag.buildInteroperabilityTag(tagId); + ifdData.setTag(tag); + return tag; + } }
\ No newline at end of file diff --git a/src/com/android/gallery3d/exif/ExifOutputStream.java b/src/com/android/gallery3d/exif/ExifOutputStream.java index 9466faa58..b8db8e34c 100644 --- a/src/com/android/gallery3d/exif/ExifOutputStream.java +++ b/src/com/android/gallery3d/exif/ExifOutputStream.java @@ -221,9 +221,9 @@ public class ExifOutputStream extends FilterOutputStream { dataOutputStream.write(0); } break; - case ExifTag.TYPE_INT: + case ExifTag.TYPE_LONG: for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeInt(tag.getInt(i)); + dataOutputStream.writeInt(tag.getLong(i)); } break; case ExifTag.TYPE_RATIONAL: @@ -238,9 +238,9 @@ public class ExifOutputStream extends FilterOutputStream { tag.getBytes(buf); dataOutputStream.write(buf); break; - case ExifTag.TYPE_UNSIGNED_INT: + case ExifTag.TYPE_UNSIGNED_LONG: for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeInt((int) tag.getUnsignedInt(i)); + dataOutputStream.writeInt((int) tag.getUnsignedLong(i)); } break; case ExifTag.TYPE_UNSIGNED_SHORT: @@ -271,7 +271,7 @@ public class ExifOutputStream extends FilterOutputStream { mExifData.addIfdData(ifd0); } ExifTag exifOffsetTag = new ExifTag(ExifTag.TAG_EXIF_IFD, - ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_0); + ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0); ifd0.setTag(exifOffsetTag); // Exif IFD is required for all file. @@ -285,7 +285,7 @@ public class ExifOutputStream extends FilterOutputStream { IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS); if (gpsIfd != null) { ExifTag gpsOffsetTag = new ExifTag(ExifTag.TAG_GPS_IFD, - ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_0); + ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0); ifd0.setTag(gpsOffsetTag); } @@ -293,7 +293,7 @@ public class ExifOutputStream extends FilterOutputStream { IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); if (interIfd != null) { ExifTag interOffsetTag = new ExifTag(ExifTag.TAG_INTEROPERABILITY_IFD, - ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_EXIF); + ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_EXIF); exifIfd.setTag(interOffsetTag); } @@ -306,10 +306,10 @@ public class ExifOutputStream extends FilterOutputStream { mExifData.addIfdData(ifd1); } ExifTag offsetTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT, - ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_1); + ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1); ifd1.setTag(offsetTag); ExifTag lengthTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, - ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_1); + ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1); lengthTag.setValue(mExifData.getCompressedThumbnail().length); ifd1.setTag(lengthTag); } else if (mExifData.hasUncompressedStrip()){ @@ -319,9 +319,9 @@ public class ExifOutputStream extends FilterOutputStream { } int stripCount = mExifData.getStripCount(); ExifTag offsetTag = new ExifTag(ExifTag.TAG_STRIP_OFFSETS, - ExifTag.TYPE_UNSIGNED_INT, stripCount, IfdId.TYPE_IFD_1); + ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1); ExifTag lengthTag = new ExifTag(ExifTag.TAG_STRIP_BYTE_COUNTS, - ExifTag.TYPE_UNSIGNED_INT, stripCount, IfdId.TYPE_IFD_1); + ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1); long[] lengths = new long[stripCount]; for (int i = 0; i < mExifData.getStripCount(); i++) { lengths[i] = mExifData.getStrip(i).length; diff --git a/src/com/android/gallery3d/exif/ExifParser.java b/src/com/android/gallery3d/exif/ExifParser.java index dfa818409..f1e52c5b3 100644 --- a/src/com/android/gallery3d/exif/ExifParser.java +++ b/src/com/android/gallery3d/exif/ExifParser.java @@ -230,7 +230,7 @@ public class ExifParser { } return EVENT_NEW_TAG; } else if (offset == endOfTags) { - long ifdOffset = readUnsignedInt(); + long ifdOffset = readUnsignedLong(); // There is a link to ifd1 at the end of ifd0 if (mIfdType == IfdId.TYPE_IFD_0) { if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) { @@ -296,7 +296,7 @@ public class ExifParser { } else { skipTo(endOfTags); } - long ifdOffset = readUnsignedInt(); + long ifdOffset = readUnsignedLong(); // For ifd0, there is a link to ifd1 in the end of all tags if (mIfdType == IfdId.TYPE_IFD_0 && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) { @@ -340,7 +340,7 @@ public class ExifParser { * @see #registerForTagValue(ExifTag) * @see #read(byte[]) * @see #read(byte[], int, int) - * @see #readInt() + * @see #readLong() * @see #readRational() * @see #readShort() * @see #readString(int) @@ -396,7 +396,9 @@ public class ExifParser { if (mStripSizeTag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { return mStripSizeTag.getUnsignedShort(mImageEvent.stripIndex); } else { - return (int) mStripSizeTag.getUnsignedInt(mImageEvent.stripIndex); + // Cast unsigned int to int since the strip size is always smaller + // than the size of APP1 (65536) + return (int) mStripSizeTag.getUnsignedLong(mImageEvent.stripIndex); } } @@ -406,7 +408,9 @@ public class ExifParser { */ public int getCompressedImageSize() { if (mJpegSizeTag == null) return 0; - return (int) mJpegSizeTag.getUnsignedInt(0); + // Cast unsigned int to int since the thumbnail is always smaller + // than the size of APP1 (65536) + return (int) mJpegSizeTag.getUnsignedLong(0); } private void skipTo(int offset) throws IOException { @@ -429,6 +433,8 @@ public class ExifParser { } private void registerIfd(int ifdType, long offset) { + // Cast unsigned int to int since the offset is always smaller + // than the size of APP1 (65536) mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType))); } @@ -474,22 +480,22 @@ public class ExifParser { case ExifTag.TAG_EXIF_IFD: if (isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_EXIF, tag.getUnsignedInt(0)); + registerIfd(IfdId.TYPE_IFD_EXIF, tag.getUnsignedLong(0)); } break; case ExifTag.TAG_GPS_IFD: if (isIfdRequested(IfdId.TYPE_IFD_GPS)) { - registerIfd(IfdId.TYPE_IFD_GPS, tag.getUnsignedInt(0)); + registerIfd(IfdId.TYPE_IFD_GPS, tag.getUnsignedLong(0)); } break; case ExifTag.TAG_INTEROPERABILITY_IFD: if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getUnsignedInt(0)); + registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getUnsignedLong(0)); } break; case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT: if (isThumbnailRequested()) { - registerCompressedImage(tag.getUnsignedInt(0)); + registerCompressedImage(tag.getUnsignedLong(0)); } break; case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: @@ -504,7 +510,7 @@ public class ExifParser { if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { registerUncompressedStrip(i, tag.getUnsignedShort(i)); } else { - registerUncompressedStrip(i, tag.getUnsignedInt(i)); + registerUncompressedStrip(i, tag.getUnsignedLong(i)); } } } else { @@ -535,11 +541,11 @@ public class ExifParser { case ExifTag.TYPE_ASCII: tag.setValue(readString(tag.getComponentCount())); break; - case ExifTag.TYPE_UNSIGNED_INT: + case ExifTag.TYPE_UNSIGNED_LONG: { long value[] = new long[tag.getComponentCount()]; for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedInt(); + value[i] = readUnsignedLong(); } tag.setValue(value); } @@ -562,11 +568,11 @@ public class ExifParser { tag.setValue(value); } break; - case ExifTag.TYPE_INT: + case ExifTag.TYPE_LONG: { int value[] = new int[tag.getComponentCount()]; for (int i = 0, n = value.length; i < n; i++) { - value[i] = readInt(); + value[i] = readLong(); } tag.setValue(value); } @@ -629,14 +635,24 @@ public class ExifParser { && dataStream.readShort() == EXIF_HEADER_TAIL); } + /** + * Reads bytes from the InputStream. + */ public int read(byte[] buffer, int offset, int length) throws IOException { return mTiffStream.read(buffer, offset, length); } + /** + * Equivalent to read(buffer, 0, buffer.length). + */ public int read(byte[] buffer) throws IOException { return mTiffStream.read(buffer); } + /** + * Reads a String from the InputStream with UTF8 charset. + * This is used for reading values of type {@link ExifTag#TYPE_ASCII}. + */ public String readString(int n) throws IOException { if (n > 0) { byte[] buf = new byte[n]; @@ -647,37 +663,52 @@ public class ExifParser { } } + /** + * Reads a String from the InputStream with the given charset. + * This is used for reading values of type {@link ExifTag#TYPE_ASCII}. + */ 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); } + /** + * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the InputStream. + */ public int readUnsignedShort() throws IOException { - return readShort() & 0xffff; + return mTiffStream.readShort() & 0xffff; } - public long readUnsignedInt() throws IOException { - return readInt() & 0xffffffffL; + /** + * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the InputStream. + */ + public long readUnsignedLong() throws IOException { + return readLong() & 0xffffffffL; } + /** + * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the InputStream. + */ public Rational readUnsignedRational() throws IOException { - long nomi = readUnsignedInt(); - long denomi = readUnsignedInt(); + long nomi = readUnsignedLong(); + long denomi = readUnsignedLong(); return new Rational(nomi, denomi); } - public int readInt() throws IOException { + /** + * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream. + */ + public int readLong() throws IOException { return mTiffStream.readInt(); } - public short readShort() throws IOException { - return mTiffStream.readShort(); - } - + /** + * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream. + */ public Rational readRational() throws IOException { - int nomi = readInt(); - int denomi = readInt(); + int nomi = readLong(); + int denomi = readLong(); return new Rational(nomi, denomi); } diff --git a/src/com/android/gallery3d/exif/ExifTag.java b/src/com/android/gallery3d/exif/ExifTag.java index 72a2f98ab..1aa84e565 100644 --- a/src/com/android/gallery3d/exif/ExifTag.java +++ b/src/com/android/gallery3d/exif/ExifTag.java @@ -16,7 +16,8 @@ package com.android.gallery3d.exif; -import java.lang.reflect.Array; +import android.util.SparseArray; + import java.util.Arrays; /** @@ -182,7 +183,7 @@ public class ExifTag { } /** - * Contants for {@link #TAG_COMPRESSION} + * Constants for {@link #TAG_COMPRESSION} */ public static interface Compression { public static final short UNCOMPRESSION = 1; @@ -254,19 +255,19 @@ public class ExifTag { // LSB public static final short DID_NOT_FIRED = 0; public static final short FIRED = 1; - // 1~2 bits + // 1st~2nd bits public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1; public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1; public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1; - // 3~4 bits + // 3rd~4th bits public static final short MODE_UNKNOWN = 0 << 3; public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3; public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3; public static final short MODE_AUTO_MODE = 3 << 3; - // 5 bit + // 5th bit public static final short FUNCTION_PRESENT = 0 << 5; public static final short FUNCTION_NO_FUNCTION = 1 << 5; - // 6 bit + // 6th bit public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6; public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6; } @@ -487,13 +488,42 @@ public class ExifTag { public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1; } + /** + * The BYTE type in the EXIF standard. An 8-bit unsigned integer. + */ public static final short TYPE_UNSIGNED_BYTE = 1; + /** + * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit ASCII code. + * The final byte is terminated with NULL. + */ public static final short TYPE_ASCII = 2; + /** + * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer + */ public static final short TYPE_UNSIGNED_SHORT = 3; - public static final short TYPE_UNSIGNED_INT = 4; + /** + * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer + */ + public static final short TYPE_UNSIGNED_LONG = 4; + /** + * The RATIONAL type of EXIF standard. It consists of two LONGs. The first one is the numerator + * and the second one expresses the denominator. + */ public static final short TYPE_UNSIGNED_RATIONAL = 5; + /** + * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any value + * depending on the field definition. + */ public static final short TYPE_UNDEFINED = 7; - public static final short TYPE_INT = 9; + /** + * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer + * (2's complement notation). + */ + public static final short TYPE_LONG = 9; + /** + * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first one is the + * numerator and the second one is the denominator. + */ public static final short TYPE_RATIONAL = 10; private static final int TYPE_TO_SIZE_MAP[] = new int[11]; @@ -501,10 +531,10 @@ public class ExifTag { TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1; TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1; TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2; - TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_INT] = 4; + TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 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_LONG] = 4; TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8; } @@ -512,11 +542,11 @@ public class ExifTag { * Gets the element size of the given data type. * * @see #TYPE_ASCII - * @see #TYPE_INT + * @see #TYPE_LONG * @see #TYPE_RATIONAL * @see #TYPE_UNDEFINED * @see #TYPE_UNSIGNED_BYTE - * @see #TYPE_UNSIGNED_INT + * @see #TYPE_UNSIGNED_LONG * @see #TYPE_UNSIGNED_RATIONAL * @see #TYPE_UNSIGNED_SHORT */ @@ -524,17 +554,366 @@ public class ExifTag { return TYPE_TO_SIZE_MAP[type]; } + private static volatile SparseArray<Integer> sTagInfo = null; + private static volatile SparseArray<Integer> sInteroperTagInfo = null; + private static final int SIZE_UNDEFINED = 0; + + private static SparseArray<Integer> getTagInfo() { + if (sTagInfo == null) { + synchronized(ExifTag.class) { + if (sTagInfo == null) { + sTagInfo = new SparseArray<Integer>(); + initTagInfo(); + } + } + } + return sTagInfo; + } + + private static SparseArray<Integer> getInteroperTagInfo() { + if (sInteroperTagInfo == null) { + synchronized(ExifTag.class) { + if (sInteroperTagInfo == null) { + sInteroperTagInfo = new SparseArray<Integer>(); + sInteroperTagInfo.put(TAG_INTEROPERABILITY_INDEX, + (IfdId.TYPE_IFD_INTEROPERABILITY << 24) + | TYPE_ASCII << 16 | SIZE_UNDEFINED); + } + } + } + return sInteroperTagInfo; + } + + private static void initTagInfo() { + /** + * We put tag information in a 4-bytes integer. The first byte is the + * IFD of the tag, and the second byte is the default data type. The + * last two byte are a short value indicating the component count of this + * tag. + */ + sTagInfo.put(TAG_MAKE, + (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_IMAGE_WIDTH, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); + sTagInfo.put(TAG_IMAGE_LENGTH, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); + sTagInfo.put(TAG_BITS_PER_SAMPLE, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 3); + sTagInfo.put(TAG_COMPRESSION, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_PHOTOMETRIC_INTERPRETATION, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_ORIENTATION, (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_SAMPLES_PER_PIXEL, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_PLANAR_CONFIGURATION, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_Y_CB_CR_SUB_SAMPLING, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 2); + sTagInfo.put(TAG_Y_CB_CR_POSITIONING, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_X_RESOLUTION, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_Y_RESOLUTION, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_RESOLUTION_UNIT, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_STRIP_OFFSETS, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_ROWS_PER_STRIP, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); + sTagInfo.put(TAG_STRIP_BYTE_COUNTS, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_JPEG_INTERCHANGE_FORMAT, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); + sTagInfo.put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); + sTagInfo.put(TAG_TRANSFER_FUNCTION, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 3 * 256); + sTagInfo.put(TAG_WHITE_POINT, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 2); + sTagInfo.put(TAG_PRIMARY_CHROMATICITIES, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 6); + sTagInfo.put(TAG_Y_CB_CR_COEFFICIENTS, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 3); + sTagInfo.put(TAG_REFERENCE_BLACK_WHITE, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 6); + sTagInfo.put(TAG_DATE_TIME, + (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | 20); + sTagInfo.put(TAG_IMAGE_DESCRIPTION, + (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_MAKE, + (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_MODEL, + (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_SOFTWARE, + (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_ARTIST, + (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_COPYRIGHT, + (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_EXIF_IFD, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); + sTagInfo.put(TAG_GPS_IFD, + (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); + + // EXIF TAG + sTagInfo.put(TAG_EXIF_VERSION, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4); + sTagInfo.put(TAG_FLASHPIX_VERSION, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4); + sTagInfo.put(TAG_COLOR_SPACE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_COMPONENTS_CONFIGURATION, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4); + sTagInfo.put(TAG_COMPRESSED_BITS_PER_PIXEL, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_PIXEL_X_DIMENSION, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_LONG << 16 | 1); + sTagInfo.put(TAG_PIXEL_Y_DIMENSION, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_LONG << 16 | 1); + sTagInfo.put(TAG_MAKER_NOTE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_USER_COMMENT, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_RELATED_SOUND_FILE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 13); + sTagInfo.put(TAG_DATE_TIME_ORIGINAL, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 20); + sTagInfo.put(TAG_DATE_TIME_DIGITIZED, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 20); + sTagInfo.put(TAG_SUB_SEC_TIME, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_SUB_SEC_TIME_ORIGINAL, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_SUB_SEC_TIME_DIGITIZED, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_IMAGE_UNIQUE_ID, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 33); + sTagInfo.put(TAG_EXPOSURE_TIME, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_F_NUMBER, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_EXPOSURE_PROGRAM, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_SPECTRAL_SENSITIVITY, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_ISO_SPEED_RATINGS, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_OECF, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_SHUTTER_SPEED_VALUE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1); + sTagInfo.put(TAG_APERTURE_VALUE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_BRIGHTNESS_VALUE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1); + sTagInfo.put(TAG_EXPOSURE_BIAS_VALUE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1); + sTagInfo.put(TAG_MAX_APERTURE_VALUE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_SUBJECT_DISTANCE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_METERING_MODE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_LIGHT_SOURCE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_FLASH, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_FOCAL_LENGTH, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_SUBJECT_AREA, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_FLASH_ENERGY, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_SPATIAL_FREQUENCY_RESPONSE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_FOCAL_PLANE_X_RESOLUTION, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_FOCAL_PLANE_Y_RESOLUTION, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_FOCAL_PLANE_RESOLUTION_UNIT, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_SUBJECT_LOCATION, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 2); + sTagInfo.put(TAG_EXPOSURE_INDEX, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_SENSING_METHOD, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_FILE_SOURCE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 1); + sTagInfo.put(TAG_SCENE_TYPE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 1); + sTagInfo.put(TAG_CFA_PATTERN, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_CUSTOM_RENDERED, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_EXPOSURE_MODE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_WHITE_BALANCE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_DIGITAL_ZOOM_RATIO, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_FOCAL_LENGTH_IN_35_MM_FILE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_SCENE_CAPTURE_TYPE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_GAIN_CONTROL, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_CONTRAST, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_SATURATION, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_SHARPNESS, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + sTagInfo.put(TAG_DEVICE_SETTING_DESCRIPTION, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_SUBJECT_DISTANCE_RANGE, + (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); + // GPS tag + sTagInfo.put(TAG_GPS_VERSION_ID, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_BYTE << 16 | 4); + sTagInfo.put(TAG_GPS_LATITUDE_REF, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); + sTagInfo.put(TAG_GPS_LONGITUDE_REF, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); + sTagInfo.put(TAG_GPS_LATITUDE, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_RATIONAL << 16 | 3); + sTagInfo.put(TAG_GPS_LONGITUDE, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_RATIONAL << 16 | 3); + sTagInfo.put(TAG_GPS_ALTITUDE_REF, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_BYTE << 16 | 1); + sTagInfo.put(TAG_GPS_ALTITUDE, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_GPS_TIME_STAMP, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 3); + sTagInfo.put(TAG_GPS_SATTELLITES, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_GPS_STATUS, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); + sTagInfo.put(TAG_GPS_MEASURE_MODE, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); + sTagInfo.put(TAG_GPS_DOP, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_GPS_SPEED_REF, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); + sTagInfo.put(TAG_GPS_SPEED, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_GPS_TRACK_REF, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); + sTagInfo.put(TAG_GPS_TRACK, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_GPS_IMG_DIRECTION_REF, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); + sTagInfo.put(TAG_GPS_IMG_DIRECTION, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_GPS_MAP_DATUM, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_GPS_DEST_LATITUDE_REF, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); + sTagInfo.put(TAG_GPS_DEST_LATITUDE, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_GPS_DEST_BEARING_REF, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); + sTagInfo.put(TAG_GPS_DEST_BEARING, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_GPS_DEST_DISTANCE_REF, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); + sTagInfo.put(TAG_GPS_DEST_DISTANCE, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); + sTagInfo.put(TAG_GPS_PROCESSING_METHOD, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_GPS_AREA_INFORMATION, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); + sTagInfo.put(TAG_GPS_DATA_STAMP, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 11); + sTagInfo.put(TAG_GPS_DIFFERENTIAL, + (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_SHORT << 16 | 11); + } + private final short mTagId; private final short mDataType; private final int mIfd; + private final boolean mComponentCountDefined; private int mComponentCount; private Object mValue; private int mOffset; - public ExifTag(short tagId, short type, int componentCount, int ifd) { + static private short getTypeFromInfo(int info) { + return (short) ((info >> 16) & 0xff); + } + + static private int getComponentCountFromInfo(int info) { + return info & 0xffff; + } + + static private int getIfdIdFromInfo(int info) { + return (info >> 24) & 0xff; + } + + static private boolean getComponentCountDefined(short tagId, int ifd) { + Integer info = (ifd == IfdId.TYPE_IFD_INTEROPERABILITY) ? + getInteroperTagInfo().get(tagId) : getTagInfo().get(tagId); + if (info == null) return false; + return getComponentCountFromInfo(info) != SIZE_UNDEFINED; + } + + static int getIfdIdFromTagId(short tagId) { + Integer info = getTagInfo().get(tagId); + if (info == null) { + throw new IllegalArgumentException("Unknown Tag ID: " + tagId); + } + return getIfdIdFromInfo(info); + } + + /** + * Create a tag with given ID. For tags related to interoperability and thumbnail, call + * {@link #buildInteroperabilityTag(short)} and {@link #buildThumbnailTag(short)} respectively. + * @exception IllegalArgumentException If the ID is invalid. + */ + static public ExifTag buildTag(short tagId) { + Integer info = getTagInfo().get(tagId); + if (info == null) { + throw new IllegalArgumentException("Unknown Tag ID: " + tagId); + } + return new ExifTag(tagId, getTypeFromInfo(info), + getComponentCountFromInfo(info), + getIfdIdFromInfo(info)); + } + + /** + * Create a tag related to thumbnail with given ID. + * @exception IllegalArgumentException If the ID is invalid. + */ + static public ExifTag buildThumbnailTag(short tagId) { + Integer info = getTagInfo().get(tagId); + if (info == null || getIfdIdFromInfo(info) != IfdId.TYPE_IFD_0) { + throw new IllegalArgumentException("Unknown Thumnail Tag ID: " + tagId); + } + return new ExifTag(tagId, getTypeFromInfo(info), + getComponentCountFromInfo(info), + IfdId.TYPE_IFD_1); + } + + /** + * Create a tag related to interoperability with given ID. + * @exception IllegalArgumentException If the ID is invalid. + */ + static public ExifTag buildInteroperabilityTag(short tagId) { + Integer info = getInteroperTagInfo().get(tagId); + if (info == null || getIfdIdFromInfo(info) != IfdId.TYPE_IFD_INTEROPERABILITY) { + throw new RuntimeException("Unknown Interoperability Tag ID: " + tagId); + } + return new ExifTag(tagId, getTypeFromInfo(info), + getComponentCountFromInfo(info), + IfdId.TYPE_IFD_INTEROPERABILITY); + } + + ExifTag(short tagId, short type, int componentCount, int ifd) { mTagId = tagId; mDataType = type; mComponentCount = componentCount; + mComponentCountDefined = getComponentCountDefined(tagId, ifd); mIfd = ifd; } @@ -562,11 +941,11 @@ public class ExifTag { * Gets the data type of this tag * * @see #TYPE_ASCII - * @see #TYPE_INT + * @see #TYPE_LONG * @see #TYPE_RATIONAL * @see #TYPE_UNDEFINED * @see #TYPE_UNSIGNED_BYTE - * @see #TYPE_UNSIGNED_INT + * @see #TYPE_UNSIGNED_LONG * @see #TYPE_UNSIGNED_RATIONAL * @see #TYPE_UNSIGNED_SHORT */ @@ -613,33 +992,126 @@ public class ExifTag { mOffset = offset; } - /** - * Sets integer values into this tag. This is useful when we want to modify the tags - * and write it back to the JPEG file. The component count will be set to the length. - */ - public void setValue(int[] value) { - long[] data = new long[value.length]; - for (int i = 0; i < value.length; i++) { - data[i] = value[i]; + private void checkComponentCountOrThrow(int count) + throws IllegalArgumentException { + if (mComponentCountDefined && (mComponentCount != count)) { + throw new IllegalArgumentException("Tag " + mTagId + ": Required " + + mComponentCount + " components but was given " + count + + " component(s)"); } - mValue = data; - mComponentCount = value.length; } - /** - * Sets integer value into this tag. This is useful when we want to modify the tags - * and write it back to the JPEG file. The component count will be set to 1. - */ - public void setValue(int value) { - mValue = new long[] {value}; - mComponentCount = 1; + private void throwTypeNotMatchedException(String className) + throws IllegalArgumentException { + throw new IllegalArgumentException("Tag " + mTagId + ": expect type " + + convertTypeToString(mDataType) + " but got " + className); + } + + private static String convertTypeToString(short type) { + switch (type) { + case TYPE_UNSIGNED_BYTE: + return "UNSIGNED_BYTE"; + case TYPE_ASCII: + return "ASCII"; + case TYPE_UNSIGNED_SHORT: + return "UNSIGNED_SHORT"; + case TYPE_UNSIGNED_LONG: + return "UNSIGNED_LONG"; + case TYPE_UNSIGNED_RATIONAL: + return "UNSIGNED_RATIONAL"; + case TYPE_UNDEFINED: + return "UNDEFINED"; + case TYPE_LONG: + return "LONG"; + case TYPE_RATIONAL: + return "RATIONAL"; + default: + return ""; + } + } + + private static final int UNSIGNED_SHORT_MAX = 65535; + private static final long UNSIGNED_LONG_MAX = 4294967295L; + private static final long LONG_MAX = Integer.MAX_VALUE; + private static final long LONG_MIN = Integer.MIN_VALUE; + + private void checkOverflowForUnsignedShort(int[] value) { + for (int v : value) { + if (v > UNSIGNED_SHORT_MAX || v < 0) { + throw new IllegalArgumentException( + "Tag " + mTagId+ ": Value" + v + + " is illegal for type UNSIGNED_SHORT"); + } + } + } + + private void checkOverflowForUnsignedLong(long[] value) { + for (long v: value) { + if (v < 0 || v > UNSIGNED_LONG_MAX) { + throw new IllegalArgumentException( + "Tag " + mTagId+ ": Value" + v + + " is illegal for type UNSIGNED_LONG"); + } + } + } + + private void checkOverflowForUnsignedLong(int[] value) { + for (int v: value) { + if (v < 0) { + throw new IllegalArgumentException( + "Tag " + mTagId+ ": Value" + v + + " is illegal for type UNSIGNED_LONG"); + } + } + } + + private void checkOverflowForUnsignedRational(Rational[] value) { + for (Rational v: value) { + if (v.getNominator() < 0 || v.getDenominator() < 0 + || v.getNominator() > UNSIGNED_LONG_MAX + || v.getDenominator() > UNSIGNED_LONG_MAX) { + throw new IllegalArgumentException( + "Tag " + mTagId+ ": Value" + v + + " is illegal for type UNSIGNED_RATIONAL"); + } + } + } + + private void checkOverflowForRational(Rational[] value) { + for (Rational v: value) { + if (v.getNominator() < LONG_MIN || v.getDenominator() < LONG_MIN + || v.getNominator() > LONG_MAX + || v.getDenominator() > LONG_MAX) { + throw new IllegalArgumentException( + "Tag " + mTagId+ ": Value" + v + + " is illegal for type RATIONAL"); + } + } } /** - * Sets short values into this tag. This is useful when we want to modify the tags - * and write it back to the JPEG file. The component count will be set to the length. + * Sets integer values into this tag. + * @exception IllegalArgumentException for the following situation: + * <ul> + * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT}, + * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li> + * <li>The value overflows. </li> + * <li>The value.length does NOT match the definition of component count in + * EXIF standard.</li> + * </ul> */ - public void setValue(short[] value) { + public void setValue(int[] value) { + checkComponentCountOrThrow(value.length); + if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG && + mDataType != TYPE_UNSIGNED_LONG) { + throwTypeNotMatchedException("int"); + } + if (mDataType == TYPE_UNSIGNED_SHORT) { + checkOverflowForUnsignedShort(value); + } else if (mDataType == TYPE_UNSIGNED_LONG) { + checkOverflowForUnsignedLong(value); + } + long[] data = new long[value.length]; for (int i = 0; i < value.length; i++) { data[i] = value[i]; @@ -649,141 +1121,218 @@ public class ExifTag { } /** - * Sets short value into this tag. This is useful when we want to modify the tags - * and write it back to the JPEG file. The component count will be set to 1. + * Sets integer values into this tag. + * @exception IllegalArgumentException For the following situation: + * <ul> + * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT}, + * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.</li> + * <li>The value overflows.</li> + * <li>The component count in the definition of EXIF standard is not 1.</li> + * </ul> */ - public void setValue(short value) { - mValue = new long[] {value}; - mComponentCount = 1; + public void setValue(int value) { + checkComponentCountOrThrow(1); + setValue(new int[] {value}); } /** - * Sets long values into this tag. This is useful when we want to modify the tags - * and write it back to the JPEG file. The component count will be set to the length. + * Sets long values into this tag. + * @exception IllegalArgumentException For the following situation: + * <ul> + * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li> + * <li>The value overflows. </li> + * <li>The value.length does NOT match the definition of component count in + * EXIF standard.</li> + * </ul> */ public void setValue(long[] value) { - long[] data = new long[value.length]; - System.arraycopy(value, 0, data, 0, value.length); - mValue = data; + checkComponentCountOrThrow(value.length); + if (mDataType != TYPE_UNSIGNED_LONG) { + throwTypeNotMatchedException("long"); + } + checkOverflowForUnsignedLong(value); + mValue = value; mComponentCount = value.length; } /** - * Sets long value into this tag. This is useful when we want to modify the tags - * and write it back to the JPEG file. The component count will be set to 1. + * Sets long values into this tag. + * @exception IllegalArgumentException For the following situation: + * <ul> + * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.</li> + * <li>The value overflows. </li> + * <li>The component count in the definition of EXIF standard is not 1.</li> + * </ul> */ public void setValue(long value) { - mValue = new long[] {value}; - mComponentCount = 1; + setValue(new long[] {value}); } /** - * Sets String value into this tag. This is useful when we want to modify the tags - * and write it back to the JPEG file. The component count will be set to - * value.length() + 1. + * Sets string values into this tag. + * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII} + * or value.length() + 1 does NOT fit the definition of the component count in the + * EXIF standard. */ public void setValue(String value) { + checkComponentCountOrThrow(value.length() + 1); + if (mDataType != TYPE_ASCII) { + throwTypeNotMatchedException("String"); + } mComponentCount = value.length() + 1; mValue = value; } /** - * Sets Rational values into this tag. This is useful when we want to modify the tags - * and write it back to the JPEG file. The component count will be set to the length. + * Sets Rational values into this tag. + * @exception IllegalArgumentException For the following situation: + * <ul> + * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} or + * {@link #TYPE_RATIONAL} .</li> + * <li>The value overflows. </li> + * <li>The value.length does NOT match the definition of component count in + * EXIF standard.</li> + * </ul> */ public void setValue(Rational[] value) { - mValue = new Rational[value.length]; - System.arraycopy(value, 0, mValue, 0, value.length); + if (mDataType == TYPE_UNSIGNED_RATIONAL) { + checkOverflowForUnsignedRational(value); + } else if (mDataType == TYPE_RATIONAL) { + checkOverflowForRational(value); + } else { + throwTypeNotMatchedException("Rational"); + } + checkComponentCountOrThrow(value.length); + mValue = value; mComponentCount = value.length; } /** - * Sets Rational value into this tag. This is useful when we want to modify the tags - * and write it back to the JPEG file. The component count will be set to 1. - */ + * Sets Rational values into this tag. + * @exception IllegalArgumentException For the following situation: + * <ul> + * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} or + * {@link #TYPE_RATIONAL} .</li> + * <li>The value overflows. </li> + * <li>The component count in the definition of EXIF standard is not 1.</li> + * </ul> + * */ public void setValue(Rational value) { - mValue = new Rational[] {value}; - mComponentCount = 1; + setValue(new Rational[] {value}); } /** - * Sets byte values into this tag. This is useful when we want to modify the tags - * and write it back to the JPEG file. The component count will be set to the length. - */ + * Sets byte values into this tag. + * @exception IllegalArgumentException For the following situation: + * <ul> + * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or + * {@link #TYPE_UNDEFINED} .</li> + * <li>The length does NOT match the definition of component count in EXIF standard.</li> + * </ul> + * */ public void setValue(byte[] value, int offset, int length) { - long[] data = new long[length]; - for (int i = 0; i < length; i++) { - data[i] = value[i + offset]; + checkComponentCountOrThrow(length); + if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) { + throwTypeNotMatchedException("byte"); } - mValue = data; + mValue = new byte[length]; + System.arraycopy(value, offset, mValue, 0, length); mComponentCount = length; } /** - * Sets the byte array as the value of this tag. - * This is equivalent to setValue(value, 0, value.length). + * Equivalent to setValue(value, 0, value.length). */ public void setValue(byte[] value) { setValue(value, 0, value.length); } - public short getShort(int index) { - if (mValue instanceof long[]) { - return (short) (((long[]) mValue) [index]); - } else { - throw new RuntimeException("There is no numerical value in this tag"); - } - } - + /** + * Gets the {@link #TYPE_UNSIGNED_SHORT} data. + * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_SHORT}. + */ public int getUnsignedShort(int index) { - if (mValue instanceof long[]) { - return (int) (((long[]) mValue) [index]); - } else { - throw new RuntimeException("There is no numerical value in this tag"); + if (mDataType != TYPE_UNSIGNED_SHORT) { + throw new IllegalArgumentException("Cannot get UNSIGNED_SHORT value from " + + convertTypeToString(mDataType)); } + return (int) (((long[]) mValue) [index]); } - public int getInt(int index) { - if (mValue instanceof long[]) { - return (int) (((long[]) mValue) [index]); - } else { - throw new RuntimeException("There is no numerical value in this tag"); + /** + * Gets the {@link #TYPE_LONG} data. + * @exception IllegalArgumentException If the type is NOT {@link #TYPE_LONG}. + */ + public int getLong(int index) { + if (mDataType != TYPE_LONG) { + throw new IllegalArgumentException("Cannot get LONG value from " + + convertTypeToString(mDataType)); } + return (int) (((long[]) mValue) [index]); } - public long getUnsignedInt(int index) { - if (mValue instanceof long[]) { - return ((long[]) mValue) [index]; - } else { - throw new RuntimeException("There is no numerical value in this tag"); + /** + * Gets the {@link #TYPE_UNSIGNED_LONG} data. + * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_LONG}. + */ + public long getUnsignedLong(int index) { + if (mDataType != TYPE_UNSIGNED_LONG) { + throw new IllegalArgumentException("Cannot get UNSIGNED LONG value from " + + convertTypeToString(mDataType)); } + return ((long[]) mValue) [index]; } + /** + * Gets the {@link #TYPE_ASCII} data. + * @exception IllegalArgumentException If the type is NOT {@link #TYPE_ASCII}. + */ public String getString() { + if (mDataType != TYPE_ASCII) { + throw new IllegalArgumentException("Cannot get ASCII value from " + + convertTypeToString(mDataType)); + } return (String) mValue; } + /** + * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data. + * @exception IllegalArgumentException If the type is NOT {@link #TYPE_RATIONAL} or + * {@link #TYPE_UNSIGNED_RATIONAL}. + */ public Rational getRational(int index) { - Object value = Array.get(mValue, index); - if (value instanceof Rational) { - return (Rational) value; - } else { - throw new RuntimeException("There is no Rational value in this tag"); + if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) { + throw new IllegalArgumentException("Cannot get RATIONAL value from " + + convertTypeToString(mDataType)); } + return ((Rational[]) mValue) [index]; } + /** + * Equivalent to getBytes(buffer, 0, buffer.length). + */ public void getBytes(byte[] buf) { getBytes(buf, 0, buf.length); } + /** + * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data. + * + * @param buf the byte array in which to store the bytes read. + * @param offset the initial position in buffer to store the bytes. + * @param length the maximum number of bytes to store in buffer. If length > component count, + * only the valid bytes will be stored. + * + * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNDEFINED} or + * {@link #TYPE_UNSIGNED_BYTE}. + */ public void getBytes(byte[] buf, int offset, int length) { - if (!(mValue instanceof long[])) { - throw new RuntimeException("There is no byte value in this tag"); - } - long[] data = (long[]) mValue; - for (int i = 0; i < length; i++) { - buf[offset + i] = (byte) data[i]; + if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) { + throw new IllegalArgumentException("Cannot get BYTE value from " + + convertTypeToString(mDataType)); } + System.arraycopy(mValue, 0, buf, offset, + (length > mComponentCount) ? mComponentCount : length); } /** @@ -802,13 +1351,12 @@ public class ExifTag { } break; case ExifTag.TYPE_ASCII: - // trim the string for comparison between xml - sbuilder.append(getString().trim()); + sbuilder.append(getString()); break; - case ExifTag.TYPE_UNSIGNED_INT: + case ExifTag.TYPE_UNSIGNED_LONG: for(int i = 0, n = getComponentCount(); i < n; i++) { if(i != 0) sbuilder.append(" "); - sbuilder.append(getUnsignedInt(i)); + sbuilder.append(getUnsignedLong(i)); } break; case ExifTag.TYPE_RATIONAL: @@ -825,10 +1373,10 @@ public class ExifTag { sbuilder.append(getUnsignedShort(i)); } break; - case ExifTag.TYPE_INT: + case ExifTag.TYPE_LONG: for(int i = 0, n = getComponentCount(); i < n; i++) { if(i != 0) sbuilder.append(" "); - sbuilder.append(getInt(i)); + sbuilder.append(getLong(i)); } break; } @@ -840,7 +1388,7 @@ public class ExifTag { * {@link #TAG_GPS_IFD}, {@link #TAG_JPEG_INTERCHANGE_FORMAT}, * {@link #TAG_STRIP_OFFSETS}, {@link #TAG_INTEROPERABILITY_IFD} */ - public static boolean isOffsetTag(short tagId) { + static boolean isOffsetTag(short tagId) { return tagId == TAG_EXIF_IFD || tagId == TAG_GPS_IFD || tagId == TAG_JPEG_INTERCHANGE_FORMAT @@ -852,7 +1400,6 @@ public class ExifTag { public boolean equals(Object obj) { if (obj instanceof ExifTag) { ExifTag tag = (ExifTag) obj; - boolean isArray = mValue != null && mValue.getClass().isArray(); if (mValue != null) { if (mValue instanceof long[]) { if (!(tag.mValue instanceof long[])) return false; @@ -860,6 +1407,9 @@ public class ExifTag { } else if (mValue instanceof Rational[]) { if (!(tag.mValue instanceof Rational[])) return false; return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue); + } else if (mValue instanceof byte[]) { + if (!(tag.mValue instanceof byte[])) return false; + return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue); } else { return mValue.equals(tag.mValue); } diff --git a/src/com/android/gallery3d/exif/IfdData.java b/src/com/android/gallery3d/exif/IfdData.java index a2b367fc5..78f9173cc 100644 --- a/src/com/android/gallery3d/exif/IfdData.java +++ b/src/com/android/gallery3d/exif/IfdData.java @@ -25,7 +25,7 @@ import java.util.Map; * @see ExifData * @see ExifTag */ -public class IfdData { +class IfdData { private final int mIfdId; private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>(); diff --git a/tests/src/com/android/gallery3d/exif/ExifParserTest.java b/tests/src/com/android/gallery3d/exif/ExifParserTest.java index 1af106f3b..549fb0681 100644 --- a/tests/src/com/android/gallery3d/exif/ExifParserTest.java +++ b/tests/src/com/android/gallery3d/exif/ExifParserTest.java @@ -103,7 +103,7 @@ public class ExifParserTest extends InstrumentationTestCase { } String truthString = truth.get(tag.getTagId()); - String dataString = tag.valueToString(); + String dataString = tag.valueToString().trim(); if (truthString == null) { fail(String.format("Unknown Tag %02x", tag.getTagId())); } diff --git a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java index 236c0aea2..74b8bd3d8 100644 --- a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java +++ b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java @@ -105,7 +105,7 @@ public class ExifReaderTest extends InstrumentationTestCase { assertEquals(byteCountTag.getUnsignedShort(i), exifData.getStrip(i).length); } else { assertEquals( - byteCountTag.getUnsignedInt(i), exifData.getStrip(i).length); + byteCountTag.getUnsignedLong(i), exifData.getStrip(i).length); } } } @@ -116,7 +116,7 @@ public class ExifReaderTest extends InstrumentationTestCase { if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { return tag.getUnsignedShort(0); } else { - return (int) tag.getUnsignedInt(0); + return (int) tag.getUnsignedLong(0); } } @@ -127,7 +127,7 @@ public class ExifReaderTest extends InstrumentationTestCase { } ExifTag[] tags = ifd.getAllTags(); for (ExifTag tag : tags) { - assertEquals(ifdValue.get(tag.getTagId()), tag.valueToString()); + assertEquals(ifdValue.get(tag.getTagId()), tag.valueToString().trim()); } assertEquals(ifdValue.size(), tags.length); } |