summaryrefslogtreecommitdiffstats
path: root/gallerycommon
diff options
context:
space:
mode:
authorRuben Brunk <rubenbrunk@google.com>2013-03-11 19:00:12 -0700
committerRuben Brunk <rubenbrunk@google.com>2013-03-13 18:01:42 -0700
commit29fd4aa661f7e626a1d11558f09e8f7c011efcc2 (patch)
treec493ac90f0f3783e2efeea17083164a742cc621f /gallerycommon
parentacca4ee0fefe6442b853510b6b360f6cb7ad1bc5 (diff)
downloadandroid_packages_apps_Snap-29fd4aa661f7e626a1d11558f09e8f7c011efcc2.zip
android_packages_apps_Snap-29fd4aa661f7e626a1d11558f09e8f7c011efcc2.tar.gz
android_packages_apps_Snap-29fd4aa661f7e626a1d11558f09e8f7c011efcc2.tar.bz2
Exif parser modifications.
Bug: 8018327 Change-Id: I66a2ec309f9807ac255bbf29d8f5f26de60e89b8
Diffstat (limited to 'gallerycommon')
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifData.java399
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java2403
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java70
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java256
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifParser.java436
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifReader.java24
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifTag.java1798
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/IfdData.java59
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/IfdId.java2
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java16
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/Rational.java58
11 files changed, 3838 insertions, 1683 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
index adaceb5..8422382 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
@@ -19,91 +19,67 @@ package com.android.gallery3d.exif;
import android.util.Log;
import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Array;
import java.nio.ByteOrder;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Calendar;
-import java.util.TimeZone;
+import java.util.List;
/**
- * 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
+ * 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 {
+class ExifData {
private static final String TAG = "ExifData";
- private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
- private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
-
private static final byte[] USER_COMMENT_ASCII = {
- 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00};
+ 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
+ };
private static final byte[] USER_COMMENT_JIS = {
- 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00};
+ 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
private static final byte[] USER_COMMENT_UNICODE = {
- 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00};
- private static final byte[] USER_COMMENT_UNDEFINED = {
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-
- private final DateFormat mDateTimeStampFormat =
- new SimpleDateFormat(DATETIME_FORMAT_STR);
- private final DateFormat mGPSDateStampFormat =
- new SimpleDateFormat(GPS_DATE_FORMAT_STR);
- private final Calendar mGPSTimeStampCalendar = Calendar.getInstance(
- TimeZone.getTimeZone("UTC"));
+ 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
+ };
private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
private byte[] mThumbnail;
private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
private final ByteOrder mByteOrder;
- public ExifData(ByteOrder order) {
+ ExifData(ByteOrder order) {
mByteOrder = order;
- mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- }
-
- 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.
- */
- void addIfdData(IfdData data) {
- mIfdDatas[data.getId()] = data;
- }
-
- /**
- * Gets the compressed thumbnail. Returns null if there is no compressed thumbnail.
+ * Gets the compressed thumbnail. Returns null if there is no compressed
+ * thumbnail.
*
* @see #hasCompressedThumbnail()
*/
- public byte[] getCompressedThumbnail() {
+ protected byte[] getCompressedThumbnail() {
return mThumbnail;
}
/**
* Sets the compressed thumbnail.
*/
- public void setCompressedThumbnail(byte[] thumbnail) {
+ protected void setCompressedThumbnail(byte[] thumbnail) {
mThumbnail = thumbnail;
}
/**
* Returns true it this header contains a compressed thumbnail.
*/
- public boolean hasCompressedThumbnail() {
+ protected boolean hasCompressedThumbnail() {
return mThumbnail != null;
}
/**
* Adds an uncompressed strip.
*/
- public void setStripBytes(int index, byte[] strip) {
+ protected void setStripBytes(int index, byte[] strip) {
if (index < mStripBytes.size()) {
mStripBytes.set(index, strip);
} else {
@@ -117,135 +93,57 @@ public class ExifData {
/**
* Gets the strip count.
*/
- public int getStripCount() {
+ protected int getStripCount() {
return mStripBytes.size();
}
/**
* Gets the strip at the specified index.
+ *
* @exceptions #IndexOutOfBoundException
*/
- public byte[] getStrip(int index) {
+ protected byte[] getStrip(int index) {
return mStripBytes.get(index);
}
/**
- * Gets the byte order.
+ * Returns true if this header contains uncompressed strip.
*/
- public ByteOrder getByteOrder() {
- return mByteOrder;
+ protected boolean hasUncompressedStrip() {
+ return mStripBytes.size() != 0;
}
/**
- * Returns true if this header contains uncompressed strip of thumbnail.
+ * Gets the byte order.
*/
- public boolean hasUncompressedStrip() {
- return mStripBytes.size() != 0;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof ExifData) {
- ExifData data = (ExifData) obj;
- if (data.mByteOrder != mByteOrder
- || !Arrays.equals(data.mThumbnail, mThumbnail)
- || data.mStripBytes.size() != mStripBytes.size()) return false;
-
- for (int i = 0; i < mStripBytes.size(); i++) {
- if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) return false;
- }
-
- for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
- IfdData ifd1 = data.getIfdData(i);
- IfdData ifd2 = getIfdData(i);
- if ((ifd1 != ifd2) && (ifd1 != null && !ifd1.equals(ifd2))) return false;
- }
- return true;
- }
- return false;
+ protected ByteOrder getByteOrder() {
+ return mByteOrder;
}
/**
- * A convenient method to adds tags {@link ExifTag#TAG_GPS_LATITUDE},
- * {@link ExifTag#TAG_GPS_LONGITUDE}, {@link ExifTag#TAG_GPS_LATITUDE_REF} and
- * {@link ExifTag#TAG_GPS_LONGITUDE_REF} at once with the
- * given latitude and longitude.
+ * Returns the {@link IfdData} object corresponding to a given IFD if it
+ * exists or null.
*/
- public void addGpsTags(double latitude, double longitude) {
- IfdData gpsIfd = getIfdData(IfdId.TYPE_IFD_GPS);
- if (gpsIfd == null) {
- gpsIfd = new IfdData(IfdId.TYPE_IFD_GPS);
- addIfdData(gpsIfd);
+ protected IfdData getIfdData(int ifdId) {
+ if (ExifTag.isValidIfd(ifdId)) {
+ return mIfdDatas[ifdId];
}
- ExifTag latTag = new ExifTag(ExifTag.TAG_GPS_LATITUDE, ExifTag.TYPE_RATIONAL,
- 3, IfdId.TYPE_IFD_GPS);
- ExifTag longTag = new ExifTag(ExifTag.TAG_GPS_LONGITUDE, ExifTag.TYPE_RATIONAL,
- 3, IfdId.TYPE_IFD_GPS);
- ExifTag latRefTag = new ExifTag(ExifTag.TAG_GPS_LATITUDE_REF,
- ExifTag.TYPE_ASCII, 2, IfdId.TYPE_IFD_GPS);
- ExifTag longRefTag = new ExifTag(ExifTag.TAG_GPS_LONGITUDE_REF,
- ExifTag.TYPE_ASCII, 2, IfdId.TYPE_IFD_GPS);
- latTag.setValue(toExifLatLong(latitude));
- longTag.setValue(toExifLatLong(longitude));
- latRefTag.setValue(latitude >= 0
- ? ExifTag.GpsLatitudeRef.NORTH
- : ExifTag.GpsLatitudeRef.SOUTH);
- longRefTag.setValue(longitude >= 0
- ? ExifTag.GpsLongitudeRef.EAST
- : ExifTag.GpsLongitudeRef.WEST);
- gpsIfd.setTag(latTag);
- gpsIfd.setTag(longTag);
- gpsIfd.setTag(latRefTag);
- gpsIfd.setTag(longRefTag);
+ return null;
}
/**
- * A convenient method to add date or time related tags (
- * {@link ExifTag#TAG_DATE_TIME_DIGITIZED}, {@link ExifTag#TAG_DATE_TIME_ORIGINAL},
- * and {@link ExifTag#TAG_DATE_TIME}) with the given time stamp value.
- *
+ * Adds IFD data. If IFD data of the same type already exists, it will be
+ * replaced by the new data.
*/
- public void addDateTimeStampTag(short tagId, long timestamp, TimeZone timezone) {
- if (tagId == ExifTag.TAG_DATE_TIME ||
- tagId == ExifTag.TAG_DATE_TIME_DIGITIZED ||
- tagId == ExifTag.TAG_DATE_TIME_ORIGINAL) {
- mDateTimeStampFormat.setTimeZone(timezone);
- addTag(tagId).setValue(mDateTimeStampFormat.format(timestamp));
- } else {
- throw new IllegalArgumentException(
- String.format("Tag %04x is not a supported date or time stamp tag", tagId));
- }
+ protected void addIfdData(IfdData data) {
+ mIfdDatas[data.getId()] = data;
}
/**
- * A convenient method to add both {@link ExifTag#TAG_GPS_DATE_STAMP}
- * and {@link ExifTag#TAG_GPS_TIME_STAMP}).
- * Note that UTC timezone will be used as specified in the EXIF standard.
+ * Returns the {@link IfdData} object corresponding to a given IFD or
+ * generates one if none exist.
*/
- public void addGpsDateTimeStampTag(long timestamp) {
- addTag(ExifTag.TAG_GPS_DATE_STAMP).setValue(mGPSDateStampFormat.format(timestamp));
-
- mGPSTimeStampCalendar.setTimeInMillis(timestamp);
- addTag(ExifTag.TAG_GPS_TIME_STAMP).
- setValue(new Rational[] {
- new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1),
- new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1),
- new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)});
- }
-
- private static Rational[] toExifLatLong(double value) {
- // convert to the format dd/1 mm/1 ssss/100
- value = Math.abs(value);
- int degrees = (int) value;
- value = (value - degrees) * 60;
- int minutes = (int) value;
- value = (value - minutes) * 6000;
- int seconds = (int) value;
- return new Rational[] {
- new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)};
- }
-
- private IfdData getOrCreateIfdData(int ifdId) {
+ protected IfdData getOrCreateIfdData(int ifdId) {
IfdData ifdData = mIfdDatas[ifdId];
if (ifdData == null) {
ifdData = new IfdData(ifdId);
@@ -255,98 +153,78 @@ public class ExifData {
}
/**
- * 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.
+ * Returns the tag with a given TID in the given IFD if the tag exists.
+ * Otherwise returns null.
*/
- public ExifTag getInteroperabilityTag(short tagId) {
- IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_INTEROPERABILITY];
- return (ifdData == null) ? null : ifdData.getTag(tagId);
+ protected ExifTag getTag(short tag, int ifd) {
+ IfdData ifdData = mIfdDatas[ifd];
+ return (ifdData == null) ? null : ifdData.getTag(tag);
}
/**
- * Adds a tag with the given tag ID. If the tag of the given ID already exists,
- * the original tag will be replaced. For tags
- * related to interoperability or thumbnail, call {@link #addInteroperabilityTag(short)} or
- * {@link #addThumbnailTag(short)} respectively.
- * @exception IllegalArgumentException if the tag ID is invalid.
+ * Adds the given ExifTag to its default IFD and returns an existing ExifTag
+ * with the same TID or null if none exist.
*/
- public ExifTag addTag(short tagId) {
- int ifdId = ExifTag.getIfdIdFromTagId(tagId);
- IfdData ifdData = getOrCreateIfdData(ifdId);
- ExifTag tag = ExifTag.buildTag(tagId);
- ifdData.setTag(tag);
- return tag;
+ protected ExifTag addTag(ExifTag tag) {
+ if (tag != null) {
+ int ifd = tag.getIfd();
+ return addTag(tag, ifd);
+ }
+ return null;
}
/**
- * Adds the given ExifTag to its corresponding IFD.
+ * Adds the given ExifTag to the given IFD and returns an existing ExifTag
+ * with the same TID or null if none exist.
*/
- public void addTag(ExifTag tag) {
- IfdData ifdData = getOrCreateIfdData(tag.getIfd());
- ifdData.setTag(tag);
+ protected ExifTag addTag(ExifTag tag, int ifdId) {
+ if (tag != null && ExifTag.isValidIfd(ifdId)) {
+ IfdData ifdData = getOrCreateIfdData(ifdId);
+ return ifdData.setTag(tag);
+ }
+ return null;
}
- /**
- * Adds a thumbnail-related tag with the given tag ID. If the tag of the given ID
- * already exists, the original tag will be replaced.
- * @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;
+ protected void clearThumbnailAndStrips() {
+ mThumbnail = null;
+ mStripBytes.clear();
}
/**
- * Adds an interoperability-related tag with the given tag ID. If the tag of the given ID
- * already exists, the original tag will be replaced.
- * @exception IllegalArgumentException if the tag ID is invalid.
+ * Removes the thumbnail and its related tags. IFD1 will be removed.
*/
- public ExifTag addInteroperabilityTag(short tagId) {
- IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
- ExifTag tag = ExifTag.buildInteroperabilityTag(tagId);
- ifdData.setTag(tag);
- return tag;
+ protected void removeThumbnailData() {
+ clearThumbnailAndStrips();
+ mIfdDatas[IfdId.TYPE_IFD_1] = null;
}
/**
- * Removes the thumbnail and its related tags. IFD1 will be removed.
+ * Removes the tag with a given TID and IFD.
*/
- public void removeThumbnailData() {
- mThumbnail = null;
- mStripBytes.clear();
- mIfdDatas[IfdId.TYPE_IFD_1] = null;
+ protected void removeTag(short tagId, int ifdId) {
+ IfdData ifdData = mIfdDatas[ifdId];
+ if (ifdData == null) {
+ return;
+ }
+ ifdData.removeTag(tagId);
}
/**
- * Decodes the user comment tag into string as specified in the EXIF standard.
- * Returns null if decoding failed.
+ * Decodes the user comment tag into string as specified in the EXIF
+ * standard. Returns null if decoding failed.
*/
- public String decodeUserComment() {
+ protected String getUserComment() {
IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
- if (ifdData == null) return null;
- ExifTag tag = ifdData.getTag(ExifTag.TAG_USER_COMMENT);
- if (tag == null) return null;
- if (tag.getComponentCount() < 8) return null;
+ if (ifdData == null) {
+ return null;
+ }
+ ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
+ if (tag == null) {
+ return null;
+ }
+ if (tag.getComponentCount() < 8) {
+ return null;
+ }
byte[] buf = new byte[tag.getComponentCount()];
tag.getBytes(buf);
@@ -368,6 +246,103 @@ public class ExifData {
Log.w(TAG, "Failed to decode the user comment");
return null;
}
+ }
+ /**
+ * Returns a list of all {@link ExifTag}s in the ExifData or null if there
+ * are none.
+ */
+ protected List<ExifTag> getAllTags() {
+ ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+ for (IfdData d : mIfdDatas) {
+ if (d != null) {
+ ExifTag[] tags = d.getAllTags();
+ if (tags != null) {
+ for (ExifTag t : tags) {
+ ret.add(t);
+ }
+ }
+ }
+ }
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
}
+
+ /**
+ * Returns a list of all {@link ExifTag}s in a given IFD or null if there
+ * are none.
+ */
+ protected List<ExifTag> getAllTagsForIfd(int ifd) {
+ IfdData d = mIfdDatas[ifd];
+ if (d == null) {
+ return null;
+ }
+ ExifTag[] tags = d.getAllTags();
+ if (tags == null) {
+ return null;
+ }
+ ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
+ for (ExifTag t : tags) {
+ ret.add(t);
+ }
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ /**
+ * Returns a list of all {@link ExifTag}s with a given TID or null if there
+ * are none.
+ */
+ protected List<ExifTag> getAllTagsForTagId(short tag) {
+ ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
+ for (IfdData d : mIfdDatas) {
+ if (d != null) {
+ ExifTag t = d.getTag(tag);
+ if (t != null) {
+ ret.add(t);
+ }
+ }
+ }
+ if (ret.size() == 0) {
+ return null;
+ }
+ return ret;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof ExifData) {
+ ExifData data = (ExifData) obj;
+ if (data.mByteOrder != mByteOrder ||
+ data.mStripBytes.size() != mStripBytes.size() ||
+ !Arrays.equals(data.mThumbnail, mThumbnail)) {
+ return false;
+ }
+ for (int i = 0; i < mStripBytes.size(); i++) {
+ if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
+ return false;
+ }
+ }
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ IfdData ifd1 = data.getIfdData(i);
+ IfdData ifd2 = getIfdData(i);
+ if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java b/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java
new file mode 100644
index 0000000..2fef9ed
--- /dev/null
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java
@@ -0,0 +1,2403 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.exif;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.SparseIntArray;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel.MapMode;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.TimeZone;
+
+/**
+ * This class provides methods and constants for reading and writing jpeg file
+ * metadata. It contains a collection of ExifTags, and a collection of
+ * definitions for creating valid ExifTags. The collection of ExifTags can be
+ * updated by: reading new ones from a file, deleting or adding existing ones,
+ * or building new ExifTags from a tag definition. These ExifTags can be written
+ * to a valid jpeg image as exif metadata.
+ * <p>
+ * Each ExifTag has a tag ID (TID) and is stored in a specific image file
+ * directory (IFD) as specified by the exif standard. A tag definition can be
+ * looked up with a constant that is a combination of TID and IFD. This
+ * definition has information about the type, number of components, and valid
+ * IFDs for a tag.
+ *
+ * @see ExifTag
+ */
+public class ExifInterface {
+ public static final int TAG_NULL = -1;
+ public static final int IFD_NULL = -1;
+ public static final int DEFINITION_NULL = 0;
+
+ /**
+ * Tag constants for Jeita EXIF 2.2
+ */
+
+ // IFD 0
+ public static final int TAG_IMAGE_WIDTH =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0100);
+ public static final int TAG_IMAGE_LENGTH =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height
+ public static final int TAG_BITS_PER_SAMPLE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0102);
+ public static final int TAG_COMPRESSION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0103);
+ public static final int TAG_PHOTOMETRIC_INTERPRETATION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0106);
+ public static final int TAG_IMAGE_DESCRIPTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x010E);
+ public static final int TAG_MAKE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x010F);
+ public static final int TAG_MODEL =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0110);
+ public static final int TAG_STRIP_OFFSETS =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0111);
+ public static final int TAG_ORIENTATION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0112);
+ public static final int TAG_SAMPLES_PER_PIXEL =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0115);
+ public static final int TAG_ROWS_PER_STRIP =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0116);
+ public static final int TAG_STRIP_BYTE_COUNTS =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0117);
+ public static final int TAG_X_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x011A);
+ public static final int TAG_Y_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x011B);
+ public static final int TAG_PLANAR_CONFIGURATION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x011C);
+ public static final int TAG_RESOLUTION_UNIT =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0128);
+ public static final int TAG_TRANSFER_FUNCTION =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x012D);
+ public static final int TAG_SOFTWARE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0131);
+ public static final int TAG_DATE_TIME =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0132);
+ public static final int TAG_ARTIST =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x013B);
+ public static final int TAG_WHITE_POINT =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x013E);
+ public static final int TAG_PRIMARY_CHROMATICITIES =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x013F);
+ public static final int TAG_Y_CB_CR_COEFFICIENTS =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0211);
+ public static final int TAG_Y_CB_CR_SUB_SAMPLING =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0212);
+ public static final int TAG_Y_CB_CR_POSITIONING =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0213);
+ public static final int TAG_REFERENCE_BLACK_WHITE =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x0214);
+ public static final int TAG_COPYRIGHT =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x8298);
+ public static final int TAG_EXIF_IFD =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x8769);
+ public static final int TAG_GPS_IFD =
+ defineTag(IfdId.TYPE_IFD_0, (short) 0x8825);
+ // IFD 1
+ public static final int TAG_JPEG_INTERCHANGE_FORMAT =
+ defineTag(IfdId.TYPE_IFD_1, (short) 0x0201);
+ public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH =
+ defineTag(IfdId.TYPE_IFD_1, (short) 0x0202);
+ // IFD Exif Tags
+ public static final int TAG_EXPOSURE_TIME =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A);
+ public static final int TAG_F_NUMBER =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D);
+ public static final int TAG_EXPOSURE_PROGRAM =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822);
+ public static final int TAG_SPECTRAL_SENSITIVITY =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824);
+ public static final int TAG_ISO_SPEED_RATINGS =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827);
+ public static final int TAG_OECF =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828);
+ public static final int TAG_EXIF_VERSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000);
+ public static final int TAG_DATE_TIME_ORIGINAL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003);
+ public static final int TAG_DATE_TIME_DIGITIZED =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004);
+ public static final int TAG_COMPONENTS_CONFIGURATION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101);
+ public static final int TAG_COMPRESSED_BITS_PER_PIXEL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102);
+ public static final int TAG_SHUTTER_SPEED_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201);
+ public static final int TAG_APERTURE_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202);
+ public static final int TAG_BRIGHTNESS_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203);
+ public static final int TAG_EXPOSURE_BIAS_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204);
+ public static final int TAG_MAX_APERTURE_VALUE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205);
+ public static final int TAG_SUBJECT_DISTANCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206);
+ public static final int TAG_METERING_MODE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207);
+ public static final int TAG_LIGHT_SOURCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208);
+ public static final int TAG_FLASH =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209);
+ public static final int TAG_FOCAL_LENGTH =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A);
+ public static final int TAG_SUBJECT_AREA =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214);
+ public static final int TAG_MAKER_NOTE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C);
+ public static final int TAG_USER_COMMENT =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286);
+ public static final int TAG_SUB_SEC_TIME =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290);
+ public static final int TAG_SUB_SEC_TIME_ORIGINAL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291);
+ public static final int TAG_SUB_SEC_TIME_DIGITIZED =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292);
+ public static final int TAG_FLASHPIX_VERSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000);
+ public static final int TAG_COLOR_SPACE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001);
+ public static final int TAG_PIXEL_X_DIMENSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002);
+ public static final int TAG_PIXEL_Y_DIMENSION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003);
+ public static final int TAG_RELATED_SOUND_FILE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004);
+ public static final int TAG_INTEROPERABILITY_IFD =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005);
+ public static final int TAG_FLASH_ENERGY =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B);
+ public static final int TAG_SPATIAL_FREQUENCY_RESPONSE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C);
+ public static final int TAG_FOCAL_PLANE_X_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E);
+ public static final int TAG_FOCAL_PLANE_Y_RESOLUTION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F);
+ public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210);
+ public static final int TAG_SUBJECT_LOCATION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214);
+ public static final int TAG_EXPOSURE_INDEX =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215);
+ public static final int TAG_SENSING_METHOD =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217);
+ public static final int TAG_FILE_SOURCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300);
+ public static final int TAG_SCENE_TYPE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301);
+ public static final int TAG_CFA_PATTERN =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302);
+ public static final int TAG_CUSTOM_RENDERED =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401);
+ public static final int TAG_EXPOSURE_MODE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402);
+ public static final int TAG_WHITE_BALANCE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403);
+ public static final int TAG_DIGITAL_ZOOM_RATIO =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404);
+ public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405);
+ public static final int TAG_SCENE_CAPTURE_TYPE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406);
+ public static final int TAG_GAIN_CONTROL =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407);
+ public static final int TAG_CONTRAST =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408);
+ public static final int TAG_SATURATION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409);
+ public static final int TAG_SHARPNESS =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A);
+ public static final int TAG_DEVICE_SETTING_DESCRIPTION =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B);
+ public static final int TAG_SUBJECT_DISTANCE_RANGE =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C);
+ public static final int TAG_IMAGE_UNIQUE_ID =
+ defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420);
+ // IFD GPS tags
+ public static final int TAG_GPS_VERSION_ID =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 0);
+ public static final int TAG_GPS_LATITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 1);
+ public static final int TAG_GPS_LATITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 2);
+ public static final int TAG_GPS_LONGITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 3);
+ public static final int TAG_GPS_LONGITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 4);
+ public static final int TAG_GPS_ALTITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 5);
+ public static final int TAG_GPS_ALTITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 6);
+ public static final int TAG_GPS_TIME_STAMP =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 7);
+ public static final int TAG_GPS_SATTELLITES =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 8);
+ public static final int TAG_GPS_STATUS =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 9);
+ public static final int TAG_GPS_MEASURE_MODE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 10);
+ public static final int TAG_GPS_DOP =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 11);
+ public static final int TAG_GPS_SPEED_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 12);
+ public static final int TAG_GPS_SPEED =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 13);
+ public static final int TAG_GPS_TRACK_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 14);
+ public static final int TAG_GPS_TRACK =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 15);
+ public static final int TAG_GPS_IMG_DIRECTION_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 16);
+ public static final int TAG_GPS_IMG_DIRECTION =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 17);
+ public static final int TAG_GPS_MAP_DATUM =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 18);
+ public static final int TAG_GPS_DEST_LATITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 19);
+ public static final int TAG_GPS_DEST_LATITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 20);
+ public static final int TAG_GPS_DEST_LONGITUDE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 21);
+ public static final int TAG_GPS_DEST_LONGITUDE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 22);
+ public static final int TAG_GPS_DEST_BEARING_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 23);
+ public static final int TAG_GPS_DEST_BEARING =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 24);
+ public static final int TAG_GPS_DEST_DISTANCE_REF =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 25);
+ public static final int TAG_GPS_DEST_DISTANCE =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 26);
+ public static final int TAG_GPS_PROCESSING_METHOD =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 27);
+ public static final int TAG_GPS_AREA_INFORMATION =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 28);
+ public static final int TAG_GPS_DATE_STAMP =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 29);
+ public static final int TAG_GPS_DIFFERENTIAL =
+ defineTag(IfdId.TYPE_IFD_GPS, (short) 30);
+ // IFD Interoperability tags
+ public static final int TAG_INTEROPERABILITY_INDEX =
+ defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1);
+
+ /**
+ * Tags that contain offset markers. These are included in the banned
+ * defines.
+ */
+ private static HashSet<Short> sOffsetTags = new HashSet<Short>();
+ static {
+ sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
+ sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
+ sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS));
+ }
+
+ /**
+ * Tags with definitions that cannot be overridden (banned defines).
+ */
+ protected static HashSet<Short> sBannedDefines = new HashSet<Short>(sOffsetTags);
+ static {
+ sBannedDefines.add(getTrueTagKey(TAG_NULL));
+ sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+ sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS));
+ }
+
+ /**
+ * Returns the constant representing a tag with a given TID and default IFD.
+ */
+ public static int defineTag(int ifdId, short tagId) {
+ return (tagId & 0x0000ffff) | (ifdId << 16);
+ }
+
+ /**
+ * Returns the TID for a tag constant.
+ */
+ public static short getTrueTagKey(int tag) {
+ // Truncate
+ return (short) tag;
+ }
+
+ /**
+ * Returns the default IFD for a tag constant.
+ */
+ public static int getTrueIfd(int tag) {
+ return tag >>> 16;
+ }
+
+ /**
+ * Constants for {@link TAG_ORIENTATION}. They can be interpreted as
+ * follows:
+ * <ul>
+ * <li>TOP_LEFT is the normal orientation.</li>
+ * <li>TOP_RIGHT is a left-right mirror.</li>
+ * <li>BOTTOM_LEFT is a 180 degree rotation.</li>
+ * <li>BOTTOM_RIGHT is a top-bottom mirror.</li>
+ * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.</li>
+ * <li>RIGHT_TOP is a 90 degree clockwise rotation.</li>
+ * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.</li>
+ * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.</li>
+ * </ul>
+ */
+ public static interface Orientation {
+ public static final short TOP_LEFT = 1;
+ public static final short TOP_RIGHT = 2;
+ public static final short BOTTOM_LEFT = 3;
+ public static final short BOTTOM_RIGHT = 4;
+ public static final short LEFT_TOP = 5;
+ public static final short RIGHT_TOP = 6;
+ public static final short LEFT_BOTTOM = 7;
+ public static final short RIGHT_BOTTOM = 8;
+ }
+
+ /**
+ * Constants for {@link TAG_Y_CB_CR_POSITIONING}
+ */
+ public static interface YCbCrPositioning {
+ public static final short CENTERED = 1;
+ public static final short CO_SITED = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_COMPRESSION}
+ */
+ public static interface Compression {
+ public static final short UNCOMPRESSION = 1;
+ public static final short JPEG = 6;
+ }
+
+ /**
+ * Constants for {@link TAG_RESOLUTION_UNIT}
+ */
+ public static interface ResolutionUnit {
+ public static final short INCHES = 2;
+ public static final short CENTIMETERS = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_PHOTOMETRIC_INTERPRETATION}
+ */
+ public static interface PhotometricInterpretation {
+ public static final short RGB = 2;
+ public static final short YCBCR = 6;
+ }
+
+ /**
+ * Constants for {@link TAG_PLANAR_CONFIGURATION}
+ */
+ public static interface PlanarConfiguration {
+ public static final short CHUNKY = 1;
+ public static final short PLANAR = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_EXPOSURE_PROGRAM}
+ */
+ public static interface ExposureProgram {
+ public static final short NOT_DEFINED = 0;
+ public static final short MANUAL = 1;
+ public static final short NORMAL_PROGRAM = 2;
+ public static final short APERTURE_PRIORITY = 3;
+ public static final short SHUTTER_PRIORITY = 4;
+ public static final short CREATIVE_PROGRAM = 5;
+ public static final short ACTION_PROGRAM = 6;
+ public static final short PROTRAIT_MODE = 7;
+ public static final short LANDSCAPE_MODE = 8;
+ }
+
+ /**
+ * Constants for {@link TAG_METERING_MODE}
+ */
+ public static interface MeteringMode {
+ public static final short UNKNOWN = 0;
+ public static final short AVERAGE = 1;
+ public static final short CENTER_WEIGHTED_AVERAGE = 2;
+ public static final short SPOT = 3;
+ public static final short MULTISPOT = 4;
+ public static final short PATTERN = 5;
+ public static final short PARTAIL = 6;
+ public static final short OTHER = 255;
+ }
+
+ /**
+ * Constants for {@link TAG_FLASH} As the definition in Jeita EXIF 2.2
+ * standard, we can treat this constant as bitwise flag.
+ * <p>
+ * e.g.
+ * <p>
+ * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED |
+ * MODE_AUTO_MODE
+ */
+ public static interface Flash {
+ // LSB
+ public static final short DID_NOT_FIRED = 0;
+ public static final short FIRED = 1;
+ // 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;
+ // 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;
+ // 5th bit
+ public static final short FUNCTION_PRESENT = 0 << 5;
+ public static final short FUNCTION_NO_FUNCTION = 1 << 5;
+ // 6th bit
+ public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
+ public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
+ }
+
+ /**
+ * Constants for {@link TAG_COLOR_SPACE}
+ */
+ public static interface ColorSpace {
+ public static final short SRGB = 1;
+ public static final short UNCALIBRATED = (short) 0xFFFF;
+ }
+
+ /**
+ * Constants for {@link TAG_EXPOSURE_MODE}
+ */
+ public static interface ExposureMode {
+ public static final short AUTO_EXPOSURE = 0;
+ public static final short MANUAL_EXPOSURE = 1;
+ public static final short AUTO_BRACKET = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_WHITE_BALANCE}
+ */
+ public static interface WhiteBalance {
+ public static final short AUTO = 0;
+ public static final short MANUAL = 1;
+ }
+
+ /**
+ * Constants for {@link TAG_SCENE_CAPTURE_TYPE}
+ */
+ public static interface SceneCapture {
+ public static final short STANDARD = 0;
+ public static final short LANDSCAPE = 1;
+ public static final short PROTRAIT = 2;
+ public static final short NIGHT_SCENE = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_COMPONENTS_CONFIGURATION}
+ */
+ public static interface ComponentsConfiguration {
+ public static final short NOT_EXIST = 0;
+ public static final short Y = 1;
+ public static final short CB = 2;
+ public static final short CR = 3;
+ public static final short R = 4;
+ public static final short G = 5;
+ public static final short B = 6;
+ }
+
+ /**
+ * Constants for {@link TAG_LIGHT_SOURCE}
+ */
+ public static interface LightSource {
+ public static final short UNKNOWN = 0;
+ public static final short DAYLIGHT = 1;
+ public static final short FLUORESCENT = 2;
+ public static final short TUNGSTEN = 3;
+ public static final short FLASH = 4;
+ public static final short FINE_WEATHER = 9;
+ public static final short CLOUDY_WEATHER = 10;
+ public static final short SHADE = 11;
+ public static final short DAYLIGHT_FLUORESCENT = 12;
+ public static final short DAY_WHITE_FLUORESCENT = 13;
+ public static final short COOL_WHITE_FLUORESCENT = 14;
+ public static final short WHITE_FLUORESCENT = 15;
+ public static final short STANDARD_LIGHT_A = 17;
+ public static final short STANDARD_LIGHT_B = 18;
+ public static final short STANDARD_LIGHT_C = 19;
+ public static final short D55 = 20;
+ public static final short D65 = 21;
+ public static final short D75 = 22;
+ public static final short D50 = 23;
+ public static final short ISO_STUDIO_TUNGSTEN = 24;
+ public static final short OTHER = 255;
+ }
+
+ /**
+ * Constants for {@link TAG_SENSING_METHOD}
+ */
+ public static interface SensingMethod {
+ public static final short NOT_DEFINED = 1;
+ public static final short ONE_CHIP_COLOR = 2;
+ public static final short TWO_CHIP_COLOR = 3;
+ public static final short THREE_CHIP_COLOR = 4;
+ public static final short COLOR_SEQUENTIAL_AREA = 5;
+ public static final short TRILINEAR = 7;
+ public static final short COLOR_SEQUENTIAL_LINEAR = 8;
+ }
+
+ /**
+ * Constants for {@link TAG_FILE_SOURCE}
+ */
+ public static interface FileSource {
+ public static final short DSC = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_SCENE_TYPE}
+ */
+ public static interface SceneType {
+ public static final short DIRECT_PHOTOGRAPHED = 1;
+ }
+
+ /**
+ * Constants for {@link TAG_GAIN_CONTROL}
+ */
+ public static interface GainControl {
+ public static final short NONE = 0;
+ public static final short LOW_UP = 1;
+ public static final short HIGH_UP = 2;
+ public static final short LOW_DOWN = 3;
+ public static final short HIGH_DOWN = 4;
+ }
+
+ /**
+ * Constants for {@link TAG_CONTRAST}
+ */
+ public static interface Contrast {
+ public static final short NORMAL = 0;
+ public static final short SOFT = 1;
+ public static final short HARD = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_SATURATION}
+ */
+ public static interface Saturation {
+ public static final short NORMAL = 0;
+ public static final short LOW = 1;
+ public static final short HIGH = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_SHARPNESS}
+ */
+ public static interface Sharpness {
+ public static final short NORMAL = 0;
+ public static final short SOFT = 1;
+ public static final short HARD = 2;
+ }
+
+ /**
+ * Constants for {@link TAG_SUBJECT_DISTANCE}
+ */
+ public static interface SubjectDistance {
+ public static final short UNKNOWN = 0;
+ public static final short MACRO = 1;
+ public static final short CLOSE_VIEW = 2;
+ public static final short DISTANT_VIEW = 3;
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_LATITUDE_REF},
+ * {@link TAG_GPS_DEST_LATITUDE_REF}
+ */
+ public static interface GpsLatitudeRef {
+ public static final String NORTH = "N";
+ public static final String SOUTH = "S";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_LONGITUDE_REF},
+ * {@link TAG_GPS_DEST_LONGITUDE_REF}
+ */
+ public static interface GpsLongitudeRef {
+ public static final String EAST = "E";
+ public static final String WEST = "W";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_ALTITUDE_REF}
+ */
+ public static interface GpsAltitudeRef {
+ public static final short SEA_LEVEL = 0;
+ public static final short SEA_LEVEL_NEGATIVE = 1;
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_STATUS}
+ */
+ public static interface GpsStatus {
+ public static final String IN_PROGRESS = "A";
+ public static final String INTEROPERABILITY = "V";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_MEASURE_MODE}
+ */
+ public static interface GpsMeasureMode {
+ public static final String MODE_2_DIMENSIONAL = "2";
+ public static final String MODE_3_DIMENSIONAL = "3";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_SPEED_REF},
+ * {@link TAG_GPS_DEST_DISTANCE_REF}
+ */
+ public static interface GpsSpeedRef {
+ public static final String KILOMETERS = "K";
+ public static final String MILES = "M";
+ public static final String KNOTS = "N";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_TRACK_REF},
+ * {@link TAG_GPS_IMG_DIRECTION_REF}, {@link TAG_GPS_DEST_BEARING_REF}
+ */
+ public static interface GpsTrackRef {
+ public static final String TRUE_DIRECTION = "T";
+ public static final String MAGNETIC_DIRECTION = "M";
+ }
+
+ /**
+ * Constants for {@link TAG_GPS_DIFFERENTIAL}
+ */
+ public static interface GpsDifferential {
+ public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
+ public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
+ }
+
+ private static final String NULL_ARGUMENT_STRING = "Argument is null";
+ private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER);
+ public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
+
+ public ExifInterface() {
+ mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ /**
+ * Reads the exif tags from a byte array, clearing this ExifInterface
+ * object's existing exif tags.
+ *
+ * @param jpeg a byte array containing a jpeg compressed image.
+ * @throws IOException
+ */
+ public void readExif(byte[] jpeg) throws IOException {
+ readExif(new ByteArrayInputStream(jpeg));
+ }
+
+ /**
+ * Reads the exif tags from an InputStream, clearing this ExifInterface
+ * object's existing exif tags.
+ *
+ * @param inStream an InputStream containing a jpeg compressed image.
+ * @throws IOException
+ */
+ public void readExif(InputStream inStream) throws IOException {
+ if (inStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ ExifData d = null;
+ try {
+ d = new ExifReader(this).read(inStream);
+ } catch (ExifInvalidFormatException e) {
+ throw new IOException("Invalid exif format : " + e);
+ }
+ mData = d;
+ }
+
+ /**
+ * Reads the exif tags from a file, clearing this ExifInterface object's
+ * existing exif tags.
+ *
+ * @param inFileName a string representing the filepath to jpeg file.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void readExif(String inFileName) throws FileNotFoundException, IOException {
+ if (inFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ InputStream is = null;
+ try {
+ is = (InputStream) new FileInputStream(inFileName);
+ readExif(is);
+ } catch (IOException e) {
+ closeSilently(is);
+ throw e;
+ }
+ is.close();
+ }
+
+ /**
+ * Sets the exif tags, clearing this ExifInterface object's existing exif
+ * tags.
+ *
+ * @param tags a collection of exif tags to set.
+ */
+ public void setExif(Collection<ExifTag> tags) {
+ clearExif();
+ setTags(tags);
+ }
+
+ /**
+ * Clears this ExifInterface object's existing exif tags.
+ */
+ public void clearExif() {
+ mData = new ExifData(DEFAULT_BYTE_ORDER);
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg image,
+ * removing prior exif tags.
+ *
+ * @param jpeg a byte array containing a jpeg compressed image.
+ * @param exifOutStream an OutputStream to which the jpeg image with added
+ * exif tags will be written.
+ * @throws IOException
+ */
+ public void writeExif(byte[] jpeg, OutputStream exifOutStream) throws IOException {
+ if (jpeg == null || exifOutStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = getExifWriterStream(exifOutStream);
+ s.write(jpeg, 0, jpeg.length);
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg compressed
+ * bitmap, removing prior exif tags.
+ *
+ * @param bmap a bitmap to compress and write exif into.
+ * @param exifOutStream the OutputStream to which the jpeg image with added
+ * exif tags will be written.
+ * @throws IOException
+ */
+ public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException {
+ if (bmap == null || exifOutStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = getExifWriterStream(exifOutStream);
+ bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg stream,
+ * removing prior exif tags.
+ *
+ * @param jpegStream an InputStream containing a jpeg compressed image.
+ * @param exifOutStream an OutputStream to which the jpeg image with added
+ * exif tags will be written.
+ * @throws IOException
+ */
+ public void writeExif(InputStream jpegStream, OutputStream exifOutStream) throws IOException {
+ if (jpegStream == null || exifOutStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = getExifWriterStream(exifOutStream);
+ doExifStreamIO(jpegStream, s);
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg image,
+ * removing prior exif tags.
+ *
+ * @param jpeg a byte array containing a jpeg compressed image.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void writeExif(byte[] jpeg, String exifOutFileName) throws FileNotFoundException,
+ IOException {
+ if (jpeg == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = null;
+ try {
+ s = getExifWriterStream(exifOutFileName);
+ s.write(jpeg, 0, jpeg.length);
+ s.flush();
+ } catch (IOException e) {
+ closeSilently(s);
+ throw e;
+ }
+ s.close();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg compressed
+ * bitmap, removing prior exif tags.
+ *
+ * @param bmap a bitmap to compress and write exif into.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void writeExif(Bitmap bmap, String exifOutFileName) throws FileNotFoundException,
+ IOException {
+ if (bmap == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = null;
+ try {
+ s = getExifWriterStream(exifOutFileName);
+ bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+ s.flush();
+ } catch (IOException e) {
+ closeSilently(s);
+ throw e;
+ }
+ s.close();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg stream,
+ * removing prior exif tags.
+ *
+ * @param jpegStream an InputStream containing a jpeg compressed image.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void writeExif(InputStream jpegStream, String exifOutFileName)
+ throws FileNotFoundException, IOException {
+ if (jpegStream == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream s = null;
+ try {
+ s = getExifWriterStream(exifOutFileName);
+ doExifStreamIO(jpegStream, s);
+ s.flush();
+ } catch (IOException e) {
+ closeSilently(s);
+ throw e;
+ }
+ s.close();
+ }
+
+ /**
+ * Writes the tags from this ExifInterface object into a jpeg file, removing
+ * prior exif tags.
+ *
+ * @param jpegFileName a String containing the filepath for a jpeg file.
+ * @param exifOutFileName a String containing the filepath to which the jpeg
+ * image with added exif tags will be written.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public void writeExif(String jpegFileName, String exifOutFileName)
+ throws FileNotFoundException, IOException {
+ if (jpegFileName == null || exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ InputStream is = null;
+ try {
+ is = new FileInputStream(jpegFileName);
+ writeExif(is, exifOutFileName);
+ } catch (IOException e) {
+ closeSilently(is);
+ throw e;
+ }
+ is.close();
+ }
+
+ /**
+ * Wraps an OutputStream object with an ExifOutputStream. Exif tags in this
+ * ExifInterface object will be added to a jpeg image written to this
+ * stream, removing prior exif tags. Other methods of this ExifInterface
+ * object should not be called until the returned OutputStream has been
+ * closed.
+ *
+ * @param outStream an OutputStream to wrap.
+ * @return an OutputStream that wraps the outStream parameter, and adds exif
+ * metadata. A jpeg image should be written to this stream.
+ */
+ public OutputStream getExifWriterStream(OutputStream outStream) {
+ if (outStream == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ ExifOutputStream eos = new ExifOutputStream(outStream, this);
+ eos.setExifData(mData);
+ return eos;
+ }
+
+ /**
+ * Returns an OutputStream object that writes to a file. Exif tags in this
+ * ExifInterface object will be added to a jpeg image written to this
+ * stream, removing prior exif tags. Other methods of this ExifInterface
+ * object should not be called until the returned OutputStream has been
+ * closed.
+ *
+ * @param exifOutFileName an String containing a filepath for a jpeg file.
+ * @return an OutputStream that writes to the exifOutFileName file, and adds
+ * exif metadata. A jpeg image should be written to this stream.
+ * @throws FileNotFoundException
+ */
+ public OutputStream getExifWriterStream(String exifOutFileName) throws FileNotFoundException {
+ if (exifOutFileName == null) {
+ throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
+ }
+ OutputStream out = null;
+ try {
+ out = (OutputStream) new FileOutputStream(exifOutFileName);
+ } catch (FileNotFoundException e) {
+ closeSilently(out);
+ throw e;
+ }
+ return getExifWriterStream(out);
+ }
+
+ /**
+ * Attempts to do an in-place rewrite the exif metadata in a file for the
+ * given tags. If tags do not exist or do not have the same size as the
+ * existing exif tags, this method will fail.
+ *
+ * @param filename a String containing a filepath for a jpeg file with exif
+ * tags to rewrite.
+ * @param tags tags that will be written into the jpeg file over existing
+ * tags if possible.
+ * @return true if success, false if could not overwrite. If false, no
+ * changes are made to the file.
+ * @throws FileNotFoundException
+ * @throws IOException
+ */
+ public boolean rewriteExif(String filename, Collection<ExifTag> tags)
+ throws FileNotFoundException, IOException {
+ RandomAccessFile file = null;
+ InputStream is = null;
+ boolean ret;
+ try {
+ File temp = new File(filename);
+ is = new FileInputStream(temp);
+
+ // Parse beginning of APP1 in exif to find size of exif header.
+ ExifParser parser = null;
+ try {
+ parser = ExifParser.parse(is, this);
+ } catch (ExifInvalidFormatException e) {
+ throw new IOException("Invalid exif format : ", e);
+ }
+ long exifSize = parser.getOffsetToExifEndFromSOF();
+
+ // Free up resources
+ is.close();
+ is = null;
+
+ // Open file for memory mapping.
+ file = new RandomAccessFile(temp, "rw");
+ long fileLength = file.length();
+ if (fileLength < exifSize) {
+ throw new IOException("Filesize changed during operation");
+ }
+
+ // Map only exif header into memory.
+ ByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, exifSize);
+
+ // Attempt to overwrite tag values without changing lengths (avoids
+ // file copy).
+ ret = rewriteExif(buf, tags);
+ } catch (IOException e) {
+ closeSilently(file);
+ throw e;
+ } finally {
+ closeSilently(is);
+ }
+ file.close();
+ return ret;
+ }
+
+ /**
+ * Attempts to do an in-place rewrite the exif metadata in a ByteBuffer for
+ * the given tags. If tags do not exist or do not have the same size as the
+ * existing exif tags, this method will fail.
+ *
+ * @param buf a ByteBuffer containing a jpeg file with existing exif tags to
+ * rewrite.
+ * @param tags tags that will be written into the jpeg ByteBuffer over
+ * existing tags if possible.
+ * @return true if success, false if could not overwrite. If false, no
+ * changes are made to the ByteBuffer.
+ * @throws IOException
+ */
+ public boolean rewriteExif(ByteBuffer buf, Collection<ExifTag> tags) throws IOException {
+ ExifModifier mod = null;
+ try {
+ mod = new ExifModifier(buf, this);
+ for (ExifTag t : tags) {
+ mod.modifyTag(t);
+ }
+ return mod.commit();
+ } catch (ExifInvalidFormatException e) {
+ throw new IOException("Invalid exif format : " + e);
+ }
+ }
+
+ /**
+ * Attempts to do an in-place rewrite of the exif metadata. If this fails,
+ * fall back to overwriting file. This preserves tags that are not being
+ * rewritten.
+ *
+ * @param filename a String containing a filepath for a jpeg file.
+ * @param tags tags that will be written into the jpeg file over existing
+ * tags if possible.
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @see #rewriteExif
+ */
+ public void forceRewriteExif(String filename, Collection<ExifTag> tags)
+ throws FileNotFoundException,
+ IOException {
+ // Attempt in-place write
+ if (!rewriteExif(filename, tags)) {
+ // Fall back to doing a copy
+ ExifData tempData = mData;
+ mData = new ExifData(DEFAULT_BYTE_ORDER);
+ FileInputStream is = null;
+ ByteArrayOutputStream bytes = null;
+ try {
+ is = new FileInputStream(filename);
+ bytes = new ByteArrayOutputStream();
+ doExifStreamIO(is, bytes);
+ byte[] imageBytes = bytes.toByteArray();
+ readExif(imageBytes);
+ setTags(tags);
+ writeExif(imageBytes, filename);
+ } catch (IOException e) {
+ closeSilently(is);
+ throw e;
+ } finally {
+ is.close();
+ // Prevent clobbering of mData
+ mData = tempData;
+ }
+ }
+ }
+
+ /**
+ * Attempts to do an in-place rewrite of the exif metadata using the tags in
+ * this ExifInterface object. If this fails, fall back to overwriting file.
+ * This preserves tags that are not being rewritten.
+ *
+ * @param filename a String containing a filepath for a jpeg file.
+ * @throws FileNotFoundException
+ * @throws IOException
+ * @see #rewriteExif
+ */
+ public void forceRewriteExif(String filename) throws FileNotFoundException, IOException {
+ forceRewriteExif(filename, getAllTags());
+ }
+
+ /**
+ * Get the exif tags in this ExifInterface object or null if none exist.
+ *
+ * @return a List of {@link ExifTag}s.
+ */
+ public List<ExifTag> getAllTags() {
+ return mData.getAllTags();
+ }
+
+ /**
+ * Returns a list of ExifTags that share a TID (which can be obtained by
+ * calling {@link #getTrueTagKey} on a defined tag constant) or null if none
+ * exist.
+ *
+ * @param tagId a TID as defined in the exif standard (or with
+ * {@link #defineTag}).
+ * @return a List of {@link ExifTag}s.
+ */
+ public List<ExifTag> getTagsForTagId(short tagId) {
+ return mData.getAllTagsForTagId(tagId);
+ }
+
+ /**
+ * Returns a list of ExifTags that share an IFD (which can be obtained by
+ * calling {@link #getTrueIFD} on a defined tag constant) or null if none
+ * exist.
+ *
+ * @param ifdId an IFD as defined in the exif standard (or with
+ * {@link #defineTag}).
+ * @return a List of {@link ExifTag}s.
+ */
+ public List<ExifTag> getTagsForIfdId(int ifdId) {
+ return mData.getAllTagsForIfd(ifdId);
+ }
+
+ /**
+ * Gets an ExifTag for an IFD other than the tag's default.
+ *
+ * @see #getTag
+ */
+ public ExifTag getTag(int tagId, int ifdId) {
+ if (!ExifTag.isValidIfd(ifdId)) {
+ return null;
+ }
+ return mData.getTag(getTrueTagKey(tagId), ifdId);
+ }
+
+ /**
+ * Returns the ExifTag in that tag's default IFD for a defined tag constant
+ * or null if none exists.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return an {@link ExifTag} or null if none exists.
+ */
+ public ExifTag getTag(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTag(tagId, ifdId);
+ }
+
+ /**
+ * Gets a tag value for an IFD other than the tag's default.
+ *
+ * @see #getTagValue
+ */
+ public Object getTagValue(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ return (t == null) ? null : t.getValue();
+ }
+
+ /**
+ * Returns the value of the ExifTag in that tag's default IFD for a defined
+ * tag constant or null if none exists or the value could not be cast into
+ * the return type.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the value of the ExifTag or null if none exists.
+ */
+ public Object getTagValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagValue(tagId, ifdId);
+ }
+
+ /*
+ * Getter methods that are similar to getTagValue. Null is returned if the
+ * tag value cannot be cast into the return type.
+ */
+
+ /**
+ * @see #getTagValue
+ */
+ public String getTagStringValue(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsString();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public String getTagStringValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagStringValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Long getTagLongValue(int tagId, int ifdId) {
+ long[] l = getTagLongValues(tagId, ifdId);
+ if (l == null || l.length <= 0) {
+ return null;
+ }
+ return new Long(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Long getTagLongValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagLongValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Integer getTagIntValue(int tagId, int ifdId) {
+ int[] l = getTagIntValues(tagId, ifdId);
+ if (l == null || l.length <= 0) {
+ return null;
+ }
+ return new Integer(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Integer getTagIntValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagIntValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Byte getTagByteValue(int tagId, int ifdId) {
+ byte[] l = getTagByteValues(tagId, ifdId);
+ if (l == null || l.length <= 0) {
+ return null;
+ }
+ return new Byte(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Byte getTagByteValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagByteValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational getTagRationalValue(int tagId, int ifdId) {
+ Rational[] l = getTagRationalValues(tagId, ifdId);
+ if (l == null || l.length == 0) {
+ return null;
+ }
+ return new Rational(l[0]);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational getTagRationalValue(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagRationalValue(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public long[] getTagLongValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsLongs();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public long[] getTagLongValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagLongValues(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public int[] getTagIntValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsInts();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public int[] getTagIntValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagIntValues(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public byte[] getTagByteValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsBytes();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public byte[] getTagByteValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagByteValues(tagId, ifdId);
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational[] getTagRationalValues(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return null;
+ }
+ return t.getValueAsRationals();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational[] getTagRationalValues(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return getTagRationalValues(tagId, ifdId);
+ }
+
+ /**
+ * Checks whether a tag has a defined number of elements.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return true if the tag has a defined number of elements.
+ */
+ public boolean isTagCountDefined(int tagId) {
+ int info = getTagInfo().get(tagId);
+ // No value in info can be zero, as all tags have a non-zero type
+ if (info == 0) {
+ return false;
+ }
+ return getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED;
+ }
+
+ /**
+ * Gets the defined number of elements for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the
+ * tag or the number of elements is not defined.
+ */
+ public int getDefinedTagCount(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0) {
+ return ExifTag.SIZE_UNDEFINED;
+ }
+ return getComponentCountFromInfo(info);
+ }
+
+ /**
+ * Gets the number of elements for an ExifTag in a given IFD.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD containing the ExifTag to check.
+ * @return the number of elements in the ExifTag, if the tag's size is
+ * undefined this will return the actual number of elements that is
+ * in the ExifTag's value.
+ */
+ public int getActualTagCount(int tagId, int ifdId) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return 0;
+ }
+ return t.getComponentCount();
+ }
+
+ /**
+ * Gets the default IFD for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the default IFD for a tag definition or {@link #IFD_NULL} if no
+ * definition exists.
+ */
+ public int getDefinedTagDefaultIfd(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == DEFINITION_NULL) {
+ return IFD_NULL;
+ }
+ return getTrueIfd(tagId);
+ }
+
+ /**
+ * Gets the defined type for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the type.
+ * @see ExifTag#getDataType()
+ */
+ public short getDefinedTagType(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0) {
+ return -1;
+ }
+ return getTypeFromInfo(info);
+ }
+
+ /**
+ * Returns true if tag TID is one of the following: {@link TAG_EXIF_IFD},
+ * {@link TAG_GPS_IFD}, {@link TAG_JPEG_INTERCHANGE_FORMAT},
+ * {@link TAG_STRIP_OFFSETS}, {@link TAG_INTEROPERABILITY_IFD}
+ * <p>
+ * Note: defining tags with these TID's is disallowed.
+ *
+ * @param tag a tag's TID (can be obtained from a defined tag constant with
+ * {@link #getTrueTagKey}).
+ * @return true if the TID is that of an offset tag.
+ */
+ protected static boolean isOffsetTag(short tag) {
+ return sOffsetTags.contains(tag);
+ }
+
+ /**
+ * Creates a tag for a defined tag constant in a given IFD if that IFD is
+ * allowed for the tag. This method will fail anytime the appropriate
+ * {@link ExifTag#setValue} for this tag's datatype would fail.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD that the tag should be in.
+ * @param val the value of the tag to set.
+ * @return an ExifTag object or null if one could not be constructed.
+ * @see #buildTag
+ */
+ public ExifTag buildTag(int tagId, int ifdId, Object val) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0 || val == null) {
+ return null;
+ }
+ short type = getTypeFromInfo(info);
+ int definedCount = getComponentCountFromInfo(info);
+ boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+ if (!ExifInterface.isIfdAllowed(info, ifdId)) {
+ return null;
+ }
+ ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+ if (!t.setValue(val)) {
+ return null;
+ }
+ return t;
+ }
+
+ /**
+ * Creates a tag for a defined tag constant in the tag's default IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param val the tag's value.
+ * @return an ExifTag object.
+ */
+ public ExifTag buildTag(int tagId, Object val) {
+ int ifdId = getTrueIfd(tagId);
+ return buildTag(tagId, ifdId, val);
+ }
+
+ protected ExifTag buildUninitializedTag(int tagId) {
+ int info = getTagInfo().get(tagId);
+ if (info == 0) {
+ return null;
+ }
+ short type = getTypeFromInfo(info);
+ int definedCount = getComponentCountFromInfo(info);
+ boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
+ int ifdId = getTrueIfd(tagId);
+ ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
+ return t;
+ }
+
+ /**
+ * Sets the value of an ExifTag if it exists in the given IFD. The value
+ * must be the correct type and length for that ExifTag.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD that the ExifTag is in.
+ * @param val the value to set.
+ * @return true if success, false if the ExifTag doesn't exist or the value
+ * is the wrong type/length.
+ * @see #setTagValue
+ */
+ public boolean setTagValue(int tagId, int ifdId, Object val) {
+ ExifTag t = getTag(tagId, ifdId);
+ if (t == null) {
+ return false;
+ }
+ return t.setValue(val);
+ }
+
+ /**
+ * Sets the value of an ExifTag if it exists it's default IFD. The value
+ * must be the correct type and length for that ExifTag.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param val the value to set.
+ * @return true if success, false if the ExifTag doesn't exist or the value
+ * is the wrong type/length.
+ */
+ public boolean setTagValue(int tagId, Object val) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ return setTagValue(tagId, ifdId, val);
+ }
+
+ /**
+ * Puts an ExifTag into this ExifInterface object's tags, removing a
+ * previous ExifTag with the same TID and IFD. The IFD it is put into will
+ * be the one the tag was created with in {@link #buildTag}.
+ *
+ * @param tag an ExifTag to put into this ExifInterface's tags.
+ * @return the previous ExifTag with the same TID and IFD or null if none
+ * exists.
+ */
+ public ExifTag setTag(ExifTag tag) {
+ return mData.addTag(tag);
+ }
+
+ /**
+ * Puts a collection of ExifTags into this ExifInterface objects's tags. Any
+ * previous ExifTags with the same TID and IFDs will be removed.
+ *
+ * @param tags a Collection of ExifTags.
+ * @see #setTag
+ */
+ public void setTags(Collection<ExifTag> tags) {
+ for (ExifTag t : tags) {
+ setTag(t);
+ }
+ }
+
+ /**
+ * Removes the ExifTag for a tag constant from the given IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD of the ExifTag to remove.
+ */
+ public void deleteTag(int tagId, int ifdId) {
+ mData.removeTag(getTrueTagKey(tagId), ifdId);
+ }
+
+ /**
+ * Removes the ExifTag for a tag constant from that tag's default IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ */
+ public void deleteTag(int tagId) {
+ int ifdId = getDefinedTagDefaultIfd(tagId);
+ deleteTag(tagId, ifdId);
+ }
+
+ /**
+ * Creates a new tag definition in this ExifInterface object for a given TID
+ * and default IFD. Creating a definition with the same TID and default IFD
+ * as a previous definition will override it.
+ *
+ * @param tagId the TID for the tag.
+ * @param defaultIfd the default IFD for the tag.
+ * @param tagType the type of the tag (see {@link ExifTag#getDataType()}).
+ * @param defaultComponentCount the number of elements of this tag's type in
+ * the tags value.
+ * @param allowedIfds the IFD's this tag is allowed to be put in.
+ * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or
+ * {@link #TAG_NULL} if the definition could not be made.
+ */
+ public int setTagDefinition(short tagId, int defaultIfd, short tagType,
+ short defaultComponentCount, int[] allowedIfds) {
+ if (sBannedDefines.contains(tagId)) {
+ return TAG_NULL;
+ }
+ if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) {
+ int tagDef = defineTag(defaultIfd, tagId);
+ if (tagDef == TAG_NULL) {
+ return TAG_NULL;
+ }
+ int[] otherDefs = getTagDefinitionsForTagId(tagId);
+ SparseIntArray infos = getTagInfo();
+ // Make sure defaultIfd is in allowedIfds
+ boolean defaultCheck = false;
+ for (int i : allowedIfds) {
+ if (defaultIfd == i) {
+ defaultCheck = true;
+ }
+ if (!ExifTag.isValidIfd(i)) {
+ return TAG_NULL;
+ }
+ }
+ if (!defaultCheck) {
+ return TAG_NULL;
+ }
+
+ int ifdFlags = getFlagsFromAllowedIfds(allowedIfds);
+ // Make sure no identical tags can exist in allowedIfds
+ if (otherDefs != null) {
+ for (int def : otherDefs) {
+ int tagInfo = infos.get(def);
+ int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo);
+ if ((ifdFlags & allowedFlags) != 0) {
+ return TAG_NULL;
+ }
+ }
+ }
+ getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount);
+ return tagDef;
+ }
+ return TAG_NULL;
+ }
+
+ protected int getTagDefinition(short tagId, int defaultIfd) {
+ return getTagInfo().get(defineTag(defaultIfd, tagId));
+ }
+
+ protected int[] getTagDefinitionsForTagId(short tagId) {
+ int[] ifds = IfdData.getIfds();
+ int[] defs = new int[ifds.length];
+ int counter = 0;
+ SparseIntArray infos = getTagInfo();
+ for (int i : ifds) {
+ int def = defineTag(i, tagId);
+ if (infos.get(def) != DEFINITION_NULL) {
+ defs[counter++] = def;
+ }
+ }
+ if (counter == 0) {
+ return null;
+ }
+
+ return Arrays.copyOfRange(defs, 0, counter);
+ }
+
+ protected int getTagDefinitionForTag(ExifTag tag) {
+ short type = tag.getDataType();
+ int count = tag.getComponentCount();
+ int ifd = tag.getIfd();
+ return getTagDefinitionForTag(tag.getTagId(), type, count, ifd);
+ }
+
+ protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) {
+ int[] defs = getTagDefinitionsForTagId(tagId);
+ if (defs == null) {
+ return TAG_NULL;
+ }
+ SparseIntArray infos = getTagInfo();
+ int ret = TAG_NULL;
+ for (int i : defs) {
+ int info = infos.get(i);
+ short def_type = getTypeFromInfo(info);
+ int def_count = getComponentCountFromInfo(info);
+ int[] def_ifds = getAllowedIfdsFromInfo(info);
+ boolean valid_ifd = false;
+ for (int j : def_ifds) {
+ if (j == ifd) {
+ valid_ifd = true;
+ break;
+ }
+ }
+ if (valid_ifd && type == def_type
+ && (count == def_count || def_count == ExifTag.SIZE_UNDEFINED)) {
+ ret = i;
+ break;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Removes a tag definition for given defined tag constant.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ */
+ public void removeTagDefinition(int tagId) {
+ getTagInfo().delete(tagId);
+ }
+
+ /**
+ * Resets tag definitions to the default ones.
+ */
+ public void resetTagDefinitions() {
+ mTagInfo = null;
+ }
+
+ /**
+ * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
+ *
+ * @return the thumbnail as a bitmap.
+ */
+ public Bitmap getThumbnailBitmap() {
+ if (mData.hasCompressedThumbnail()) {
+ byte[] thumb = mData.getCompressedThumbnail();
+ return BitmapFactory.decodeByteArray(thumb, 0, thumb.length);
+ } else if (mData.hasUncompressedStrip()) {
+ // TODO: implement uncompressed
+ }
+ return null;
+ }
+
+ /**
+ * Returns the thumbnail from IFD1 as a byte array, or null if none exists.
+ * The bytes may either be an uncompressed strip as specified in the exif
+ * standard or a jpeg compressed image.
+ *
+ * @return the thumbnail as a byte array.
+ */
+ public byte[] getThumbnailBytes() {
+ if (mData.hasCompressedThumbnail()) {
+ return mData.getCompressedThumbnail();
+ } else if (mData.hasUncompressedStrip()) {
+ // TODO: implement this
+ }
+ return null;
+ }
+
+ /**
+ * Returns the thumbnail if it is jpeg compressed, or null if none exists.
+ *
+ * @return the thumbnail as a byte array.
+ */
+ public byte[] getThumbnail() {
+ return mData.getCompressedThumbnail();
+ }
+
+ /**
+ * Check if thumbnail is compressed.
+ *
+ * @return true if the thumbnail is compressed.
+ */
+ public boolean isThumbnailCompressed() {
+ return mData.hasCompressedThumbnail();
+ }
+
+ /**
+ * Check if thumbnail exists.
+ *
+ * @return true if a compressed thumbnail exists.
+ */
+ public boolean hasThumbnail() {
+ // TODO: add back in uncompressed strip
+ return mData.hasCompressedThumbnail();
+ }
+
+ // TODO: uncompressed thumbnail setters
+
+ /**
+ * Sets the thumbnail to be a jpeg compressed image. Clears any prior
+ * thumbnail.
+ *
+ * @param thumb a byte array containing a jpeg compressed image.
+ * @return true if the thumbnail was set.
+ */
+ public boolean setCompressedThumbnail(byte[] thumb) {
+ mData.clearThumbnailAndStrips();
+ mData.setCompressedThumbnail(thumb);
+ return true;
+ }
+
+ /**
+ * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior
+ * thumbnail.
+ *
+ * @param thumb a bitmap to compress to a jpeg thumbnail.
+ * @return true if the thumbnail was set.
+ */
+ public boolean setCompressedThumbnail(Bitmap thumb) {
+ ByteArrayOutputStream thumbnail = new ByteArrayOutputStream();
+ if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) {
+ return false;
+ }
+ return setCompressedThumbnail(thumbnail.toByteArray());
+ }
+
+ /**
+ * Clears the compressed thumbnail if it exists.
+ */
+ public void removeCompressedThumbnail() {
+ mData.setCompressedThumbnail(null);
+ }
+
+ // Convenience methods:
+
+ /**
+ * Decodes the user comment tag into string as specified in the EXIF
+ * standard. Returns null if decoding failed.
+ */
+ public String getUserComment() {
+ return mData.getUserComment();
+ }
+
+ /**
+ * Returns the Orientation ExifTag value for a given number of degrees.
+ *
+ * @param degrees the amount an image is rotated in degrees.
+ */
+ public static short getOrientationValueForRotation(int degrees) {
+ degrees %= 360;
+ if (degrees < 0) {
+ degrees += 360;
+ }
+ if (degrees < 90) {
+ return Orientation.TOP_LEFT; // 0 degrees
+ } else if (degrees < 180) {
+ return Orientation.RIGHT_TOP; // 90 degrees cw
+ } else if (degrees < 270) {
+ return Orientation.BOTTOM_LEFT; // 180 degrees
+ } else {
+ return Orientation.RIGHT_BOTTOM; // 270 degrees cw
+ }
+ }
+
+ /**
+ * Returns the rotation degrees corresponding to an ExifTag Orientation
+ * value.
+ *
+ * @param orientation the ExifTag Orientation value.
+ */
+ public static int getRotationForOrientationValue(short orientation) {
+ switch (orientation) {
+ case Orientation.TOP_LEFT:
+ return 0;
+ case Orientation.RIGHT_TOP:
+ return 90;
+ case Orientation.BOTTOM_LEFT:
+ return 180;
+ case Orientation.RIGHT_BOTTOM:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Gets the double representation of the GPS latitude or longitude
+ * coordinate.
+ *
+ * @param coordinate an array of 3 Rationals representing the degrees,
+ * minutes, and seconds of the GPS location as defined in the
+ * exif specification.
+ * @param reference a GPS reference reperesented by a String containing "N",
+ * "S", "E", or "W".
+ * @return the GPS coordinate represented as degrees + minutes/60 +
+ * seconds/3600
+ */
+ public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) {
+ try {
+ double degrees = coordinate[0].toDouble();
+ double minutes = coordinate[1].toDouble();
+ double seconds = coordinate[2].toDouble();
+ double result = degrees + minutes / 60.0 + seconds / 3600.0;
+ if ((reference.equals("S") || reference.equals("W"))) {
+ return -result;
+ }
+ return result;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * Gets the GPS latitude and longitude as a pair of doubles from this
+ * ExifInterface object's tags, or null if the necessary tags do not exist.
+ *
+ * @return an array of 2 doubles containing the latitude, and longitude
+ * respectively.
+ * @see #convertLatOrLongToDouble
+ */
+ public double[] getLatLongAsDoubles() {
+ Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE);
+ String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF);
+ Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE);
+ String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF);
+ if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null
+ || latitude.length < 3 || longitude.length < 3) {
+ return null;
+ }
+ double[] latLon = new double[2];
+ latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef);
+ latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef);
+ return latLon;
+ }
+
+ private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
+ private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
+ private final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR);
+ private final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
+ private final Calendar mGPSTimeStampCalendar = Calendar
+ .getInstance(TimeZone.getTimeZone("UTC"));
+
+ /**
+ * Creates, formats, and sets the DateTimeStamp tag for one of:
+ * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED},
+ * {@link #TAG_DATE_TIME_ORIGINAL}.
+ *
+ * @param tagId one of the DateTimeStamp tags.
+ * @param timestamp a timestamp to format.
+ * @param timezone a TimeZone object.
+ * @return true if success, false if the tag could not be set.
+ */
+ public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) {
+ if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED
+ || tagId == TAG_DATE_TIME_ORIGINAL) {
+ mDateTimeStampFormat.setTimeZone(timezone);
+ ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp));
+ if (t == null) {
+ return false;
+ }
+ setTag(t);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Creates and sets all to the GPS tags for a give latitude and longitude.
+ *
+ * @param latitude a GPS latitude coordinate.
+ * @param longitude a GPS longitude coordinate.
+ * @return true if success, false if they could not be created or set.
+ */
+ public boolean addGpsTags(double latitude, double longitude) {
+ ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude));
+ ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude));
+ ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF,
+ latitude >= 0 ? ExifInterface.GpsLatitudeRef.NORTH
+ : ExifInterface.GpsLatitudeRef.SOUTH);
+ ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF,
+ longitude >= 0 ? ExifInterface.GpsLongitudeRef.EAST
+ : ExifInterface.GpsLongitudeRef.WEST);
+ if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) {
+ return false;
+ }
+ setTag(latTag);
+ setTag(longTag);
+ setTag(latRefTag);
+ setTag(longRefTag);
+ return true;
+ }
+
+ /**
+ * Creates and sets the GPS timestamp tag.
+ *
+ * @param timestamp a GPS timestamp.
+ * @return true if success, false if could not be created or set.
+ */
+ public boolean addGpsDateTimeStampTag(long timestamp) {
+ ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp));
+ if (t == null) {
+ return false;
+ }
+ setTag(t);
+ mGPSTimeStampCalendar.setTimeInMillis(timestamp);
+ t = buildTag(TAG_GPS_TIME_STAMP, new Rational[] {
+ new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1),
+ new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1),
+ new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)
+ });
+ if (t == null) {
+ return false;
+ }
+ setTag(t);
+ return true;
+ }
+
+ private static Rational[] toExifLatLong(double value) {
+ // convert to the format dd/1 mm/1 ssss/100
+ value = Math.abs(value);
+ int degrees = (int) value;
+ value = (value - degrees) * 60;
+ int minutes = (int) value;
+ value = (value - minutes) * 6000;
+ int seconds = (int) value;
+ return new Rational[] {
+ new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)
+ };
+ }
+
+ private void doExifStreamIO(InputStream is, OutputStream os) throws IOException {
+ byte[] buf = new byte[1024];
+ int ret = is.read(buf, 0, 1024);
+ while (ret != -1) {
+ os.write(buf, 0, ret);
+ ret = is.read(buf, 0, 1024);
+ }
+ }
+
+ protected static void closeSilently(Closeable c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (Throwable e) {
+ // ignored
+ }
+ }
+ }
+
+ private SparseIntArray mTagInfo = null;
+
+ protected SparseIntArray getTagInfo() {
+ if (mTagInfo == null) {
+ mTagInfo = new SparseIntArray();
+ initTagInfo();
+ }
+ return mTagInfo;
+ }
+
+ private void initTagInfo() {
+ /**
+ * We put tag information in a 4-bytes integer. The first byte a bitmask
+ * representing the allowed IFDs of the tag, the second byte is the data
+ * type, and the last two byte are a short value indicating the default
+ * component count of this tag.
+ */
+ // IFD0 tags
+ int[] ifdAllowedIfds = {
+ IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1
+ };
+ int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_MAKE,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_COMPRESSION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16
+ | 1);
+ mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_X_RESOLUTION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
+ mTagInfo.put(ExifInterface.TAG_WHITE_POINT,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+ mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
+ mTagInfo.put(ExifInterface.TAG_DATE_TIME,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | 20);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_MAKE,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_MODEL,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SOFTWARE,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_ARTIST,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_COPYRIGHT,
+ ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_EXIF_IFD,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_IFD,
+ ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ // IFD1 tags
+ int[] ifd1AllowedIfds = {
+ IfdId.TYPE_IFD_1
+ };
+ int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
+ ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+ ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ // Exif tags
+ int[] exifAllowedIfds = {
+ IfdId.TYPE_IFD_EXIF
+ };
+ int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_EXIF_VERSION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_COLOR_SPACE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_MAKER_NOTE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_USER_COMMENT,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 13);
+ mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+ mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
+ mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | 33);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_F_NUMBER,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY,
+ exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_OECF,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE,
+ exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE,
+ exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
+ exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_METERING_MODE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FLASH,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SENSING_METHOD,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FILE_SOURCE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SCENE_TYPE,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_CFA_PATTERN,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL,
+ exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_CONTRAST,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SATURATION,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_SHARPNESS,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
+ exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
+ exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags
+ | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
+ // GPS tag
+ int[] gpsAllowedIfds = {
+ IfdId.TYPE_IFD_GPS
+ };
+ int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24;
+ mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4);
+ mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE,
+ gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE,
+ gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
+ mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_STATUS,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DOP,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_SPEED,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_TRACK,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
+ mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
+ mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD,
+ gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION,
+ gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED);
+ mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP,
+ gpsFlags | ExifTag.TYPE_ASCII << 16 | 11);
+ mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL,
+ gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11);
+ // Interoperability tag
+ int[] interopAllowedIfds = {
+ IfdId.TYPE_IFD_INTEROPERABILITY
+ };
+ int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24;
+ mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16
+ | ExifTag.SIZE_UNDEFINED);
+ }
+
+ protected static int getAllowedIfdFlagsFromInfo(int info) {
+ return info >>> 24;
+ }
+
+ protected static int[] getAllowedIfdsFromInfo(int info) {
+ int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+ int[] ifds = IfdData.getIfds();
+ ArrayList<Integer> l = new ArrayList<Integer>();
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ int flag = (ifdFlags >> i) & 1;
+ if (flag == 1) {
+ l.add(ifds[i]);
+ }
+ }
+ if (l.size() <= 0) {
+ return null;
+ }
+ int[] ret = new int[l.size()];
+ int j = 0;
+ for (int i : l) {
+ ret[j++] = i;
+ }
+ return ret;
+ }
+
+ protected static boolean isIfdAllowed(int info, int ifd) {
+ int[] ifds = IfdData.getIfds();
+ int ifdFlags = getAllowedIfdFlagsFromInfo(info);
+ for (int i = 0; i < ifds.length; i++) {
+ if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected static int getFlagsFromAllowedIfds(int[] allowedIfds) {
+ if (allowedIfds == null || allowedIfds.length == 0) {
+ return 0;
+ }
+ int flags = 0;
+ int[] ifds = IfdData.getIfds();
+ for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
+ for (int j : allowedIfds) {
+ if (ifds[i] == j) {
+ flags |= 1 << i;
+ break;
+ }
+ }
+ }
+ return flags;
+ }
+
+ protected static short getTypeFromInfo(int info) {
+ return (short) ((info >> 16) & 0x0ff);
+ }
+
+ protected static int getComponentCountFromInfo(int info) {
+ return info & 0x0ffff;
+ }
+
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java b/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java
index da31a29..f00362b 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java
@@ -16,6 +16,8 @@
package com.android.gallery3d.exif;
+import android.util.Log;
+
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
@@ -24,10 +26,13 @@ import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
-public class ExifModifier {
+class ExifModifier {
+ public static final String TAG = "ExifModifier";
+ public static final boolean DEBUG = false;
private final ByteBuffer mByteBuffer;
private final ExifData mTagToModified;
private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
+ private final ExifInterface mInterface;
private int mOffsetBase;
private static class TagOffset {
@@ -38,33 +43,31 @@ public class ExifModifier {
mTag = tag;
mOffset = offset;
}
-
- public ExifTag getTag() {
- return mTag;
- }
}
- public ExifModifier(ByteBuffer byteBuffer) throws IOException, ExifInvalidFormatException {
+ protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException,
+ ExifInvalidFormatException {
mByteBuffer = byteBuffer;
mOffsetBase = byteBuffer.position();
+ mInterface = iRef;
InputStream is = null;
try {
is = new ByteBufferInputStream(byteBuffer);
// Do not require any IFD;
- ExifParser parser = ExifParser.parse(is, 0);
+ ExifParser parser = ExifParser.parse(is, mInterface);
mTagToModified = new ExifData(parser.getByteOrder());
mOffsetBase += parser.getTiffStartPosition();
mByteBuffer.position(0);
} finally {
- closeSilently(is);
+ ExifInterface.closeSilently(is);
}
}
- public ByteOrder getByteOrder() {
+ protected ByteOrder getByteOrder() {
return mTagToModified.getByteOrder();
}
- public boolean commit() throws IOException, ExifInvalidFormatException {
+ protected boolean commit() throws IOException, ExifInvalidFormatException {
InputStream is = null;
try {
is = new ByteBufferInputStream(mByteBuffer);
@@ -77,22 +80,32 @@ public class ExifModifier {
mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS)
};
- if (ifdDatas[IfdId.TYPE_IFD_0] != null) flag |= ExifParser.OPTION_IFD_0;
- if (ifdDatas[IfdId.TYPE_IFD_1] != null) flag |= ExifParser.OPTION_IFD_1;
- if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) flag |= ExifParser.OPTION_IFD_EXIF;
- if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) flag |= ExifParser.OPTION_IFD_GPS;
+ if (ifdDatas[IfdId.TYPE_IFD_0] != null) {
+ flag |= ExifParser.OPTION_IFD_0;
+ }
+ if (ifdDatas[IfdId.TYPE_IFD_1] != null) {
+ flag |= ExifParser.OPTION_IFD_1;
+ }
+ if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) {
+ flag |= ExifParser.OPTION_IFD_EXIF;
+ }
+ if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) {
+ flag |= ExifParser.OPTION_IFD_GPS;
+ }
if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) {
flag |= ExifParser.OPTION_IFD_INTEROPERABILITY;
}
- ExifParser parser = ExifParser.parse(is, flag);
+ ExifParser parser = ExifParser.parse(is, flag, mInterface);
int event = parser.next();
IfdData currIfd = null;
while (event != ExifParser.EVENT_END) {
switch (event) {
case ExifParser.EVENT_START_OF_IFD:
currIfd = ifdDatas[parser.getCurrentIfd()];
- if (currIfd == null) parser.skipRemainingTagsInCurrentIfd();
+ if (currIfd == null) {
+ parser.skipRemainingTagsInCurrentIfd();
+ }
break;
case ExifParser.EVENT_NEW_TAG:
ExifTag oldTag = parser.getTag();
@@ -113,24 +126,30 @@ public class ExifModifier {
}
event = parser.next();
}
- for (IfdData ifd: ifdDatas) {
- if (ifd != null && ifd.getTagCount() > 0) return false;
+ for (IfdData ifd : ifdDatas) {
+ if (ifd != null && ifd.getTagCount() > 0) {
+ return false;
+ }
}
modify();
} finally {
- closeSilently(is);
+ ExifInterface.closeSilently(is);
}
return true;
}
private void modify() {
mByteBuffer.order(getByteOrder());
- for (TagOffset tagOffset: mTagOffsets) {
+ for (TagOffset tagOffset : mTagOffsets) {
writeTagValue(tagOffset.mTag, tagOffset.mOffset);
}
}
private void writeTagValue(ExifTag tag, int offset) {
+ if (DEBUG) {
+ Log.v(TAG, "modifying tag to: \n" + tag.toString());
+ Log.v(TAG, "at offset: " + offset);
+ }
mByteBuffer.position(offset + mOffsetBase);
switch (tag.getDataType()) {
case ExifTag.TYPE_ASCII:
@@ -153,7 +172,7 @@ public class ExifModifier {
case ExifTag.TYPE_UNSIGNED_RATIONAL:
for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
Rational v = tag.getRational(i);
- mByteBuffer.putInt((int) v.getNominator());
+ mByteBuffer.putInt((int) v.getNumerator());
mByteBuffer.putInt((int) v.getDenominator());
}
break;
@@ -174,13 +193,4 @@ public class ExifModifier {
public void modifyTag(ExifTag tag) {
mTagToModified.addTag(tag);
}
-
- private static void closeSilently(Closeable c) {
- if (c == null) return;
- try {
- c.close();
- } catch (Throwable t) {
- // do nothing
- }
- }
}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
index f34321b..6ea6554 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
@@ -16,6 +16,8 @@
package com.android.gallery3d.exif;
+import android.util.Log;
+
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -26,16 +28,15 @@ import java.nio.ByteOrder;
* This class provides a way to replace the Exif header of a JPEG image.
* <p>
* Below is an example of writing EXIF data into a file
+ *
* <pre>
* public static void writeExif(byte[] jpeg, ExifData exif, String path) {
* OutputStream os = null;
* try {
* os = new FileOutputStream(path);
* ExifOutputStream eos = new ExifOutputStream(os);
- *
* // Set the exif header
* eos.setExifData(exif);
- *
* // Write the original jpeg out, the header will be add into the file.
* eos.write(jpeg);
* } catch (FileNotFoundException e) {
@@ -54,8 +55,9 @@ import java.nio.ByteOrder;
* }
* </pre>
*/
-public class ExifOutputStream extends FilterOutputStream {
+class ExifOutputStream extends FilterOutputStream {
private static final String TAG = "ExifOutputStream";
+ private static final boolean DEBUG = false;
private static final int STATE_SOI = 0;
private static final int STATE_FRAME_HEADER = 1;
@@ -67,29 +69,32 @@ public class ExifOutputStream extends FilterOutputStream {
private static final short TIFF_LITTLE_ENDIAN = 0x4949;
private static final short TAG_SIZE = 12;
private static final short TIFF_HEADER_SIZE = 8;
+ private static final int MAX_EXIF_SIZE = 65535;
private ExifData mExifData;
private int mState = STATE_SOI;
private int mByteToSkip;
private int mByteToCopy;
private ByteBuffer mBuffer = ByteBuffer.allocate(4);
+ private final ExifInterface mInterface;
- public ExifOutputStream(OutputStream ou) {
+ protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
super(ou);
+ mInterface = iRef;
}
/**
- * Sets the ExifData to be written into the JPEG file. Should be called before writing image
- * data.
+ * Sets the ExifData to be written into the JPEG file. Should be called
+ * before writing image data.
*/
- public void setExifData(ExifData exifData) {
+ protected void setExifData(ExifData exifData) {
mExifData = exifData;
}
/**
* Gets the Exif header to be written into the JPEF file.
*/
- public ExifData getExifData() {
+ protected ExifData getExifData() {
return mExifData;
}
@@ -102,12 +107,12 @@ public class ExifOutputStream extends FilterOutputStream {
}
/**
- * Writes the image out. The input data should be a valid JPEG format. After writing, it's
- * Exif header will be replaced by the given header.
+ * Writes the image out. The input data should be a valid JPEG format. After
+ * writing, it's Exif header will be replaced by the given header.
*/
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
- while((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
+ while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
&& length > 0) {
if (mByteToSkip > 0) {
int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
@@ -122,22 +127,29 @@ public class ExifOutputStream extends FilterOutputStream {
mByteToCopy -= byteToProcess;
offset += byteToProcess;
}
- if (length == 0) return;
+ if (length == 0) {
+ return;
+ }
switch (mState) {
case STATE_SOI:
int byteRead = requestByteToBuffer(2, buffer, offset, length);
offset += byteRead;
length -= byteRead;
- if (mBuffer.position() < 2) return;
+ if (mBuffer.position() < 2) {
+ return;
+ }
mBuffer.rewind();
- assert(mBuffer.getShort() == JpegHeader.SOI);
- out.write(mBuffer.array(), 0 ,2);
+ if (mBuffer.getShort() != JpegHeader.SOI) {
+ throw new IOException("Not a valid jpeg image, cannot write exif");
+ }
+ out.write(mBuffer.array(), 0, 2);
mState = STATE_FRAME_HEADER;
mBuffer.rewind();
writeExifData();
break;
case STATE_FRAME_HEADER:
- // We ignore the APP1 segment and copy all other segments until SOF tag.
+ // We ignore the APP1 segment and copy all other segments
+ // until SOF tag.
byteRead = requestByteToBuffer(4, buffer, offset, length);
offset += byteRead;
length -= byteRead;
@@ -149,15 +161,17 @@ public class ExifOutputStream extends FilterOutputStream {
mBuffer.rewind();
}
}
- if (mBuffer.position() < 4) return;
+ if (mBuffer.position() < 4) {
+ return;
+ }
mBuffer.rewind();
short marker = mBuffer.getShort();
if (marker == JpegHeader.APP1) {
- mByteToSkip = (mBuffer.getShort() & 0xff) - 2;
+ mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
mState = STATE_JPEG_DATA;
} else if (!JpegHeader.isSofMarker(marker)) {
out.write(mBuffer.array(), 0, 4);
- mByteToCopy = (mBuffer.getShort() & 0xff) - 2;
+ mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
} else {
out.write(mBuffer.array(), 0, 4);
mState = STATE_JPEG_DATA;
@@ -171,12 +185,14 @@ public class ExifOutputStream extends FilterOutputStream {
}
/**
- * Writes the one bytes out. The input data should be a valid JPEG format. After writing, it's
- * Exif header will be replaced by the given header.
+ * Writes the one bytes out. The input data should be a valid JPEG format.
+ * After writing, it's Exif header will be replaced by the given header.
*/
@Override
public void write(int oneByte) throws IOException {
- byte[] buf = new byte[] {(byte) (0xff & oneByte)};
+ byte[] buf = new byte[] {
+ (byte) (0xff & oneByte)
+ };
write(buf);
}
@@ -189,9 +205,17 @@ public class ExifOutputStream extends FilterOutputStream {
}
private void writeExifData() throws IOException {
- if (mExifData == null) return;
+ if (mExifData == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "Writing exif data...");
+ }
createRequiredIfdAndTag();
int exifSize = calculateAllOffset();
+ if (exifSize + 8 > MAX_EXIF_SIZE) {
+ throw new IOException("Exif header is too large (>64Kb)");
+ }
OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
dataOutputStream.writeShort(JpegHeader.APP1);
@@ -241,70 +265,34 @@ public class ExifOutputStream extends FilterOutputStream {
throws IOException {
ExifTag[] tags = ifd.getAllTags();
dataOutputStream.writeShort((short) tags.length);
- for (ExifTag tag: tags) {
+ for (ExifTag tag : tags) {
dataOutputStream.writeShort(tag.getTagId());
dataOutputStream.writeShort(tag.getDataType());
dataOutputStream.writeInt(tag.getComponentCount());
+ if (DEBUG) {
+ Log.v(TAG, "\n" + tag.toString());
+ }
if (tag.getDataSize() > 4) {
dataOutputStream.writeInt(tag.getOffset());
} else {
- writeTagValue(tag, dataOutputStream);
+ ExifOutputStream.writeTagValue(tag, dataOutputStream);
for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
dataOutputStream.write(0);
}
}
}
dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
- for (ExifTag tag: tags) {
+ for (ExifTag tag : tags) {
if (tag.getDataSize() > 4) {
- writeTagValue(tag, dataOutputStream);
+ ExifOutputStream.writeTagValue(tag, dataOutputStream);
}
}
}
- private void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
- throws IOException {
- switch (tag.getDataType()) {
- case ExifTag.TYPE_ASCII:
- byte buf[] = tag.getStringByte();
- if (buf.length == tag.getComponentCount()) {
- buf[buf.length - 1] = 0;
- dataOutputStream.write(buf);
- } else {
- dataOutputStream.write(buf);
- dataOutputStream.write(0);
- }
- break;
- case ExifTag.TYPE_LONG:
- case ExifTag.TYPE_UNSIGNED_LONG:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- dataOutputStream.writeInt((int) tag.getValueAt(i));
- }
- break;
- case ExifTag.TYPE_RATIONAL:
- case ExifTag.TYPE_UNSIGNED_RATIONAL:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- dataOutputStream.writeRational(tag.getRational(i));
- }
- break;
- case ExifTag.TYPE_UNDEFINED:
- case ExifTag.TYPE_UNSIGNED_BYTE:
- buf = new byte[tag.getComponentCount()];
- tag.getBytes(buf);
- dataOutputStream.write(buf);
- break;
- case ExifTag.TYPE_UNSIGNED_SHORT:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- dataOutputStream.writeShort((short) tag.getValueAt(i));
- }
- break;
- }
- }
-
private int calculateOffsetOfIfd(IfdData ifd, int offset) {
offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
ExifTag[] tags = ifd.getAllTags();
- for(ExifTag tag: tags) {
+ for (ExifTag tag : tags) {
if (tag.getDataSize() > 4) {
tag.setOffset(offset);
offset += tag.getDataSize();
@@ -313,18 +301,21 @@ public class ExifOutputStream extends FilterOutputStream {
return offset;
}
- private void createRequiredIfdAndTag() {
+ private void createRequiredIfdAndTag() throws IOException {
// IFD0 is required for all file
IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
if (ifd0 == null) {
ifd0 = new IfdData(IfdId.TYPE_IFD_0);
mExifData.addIfdData(ifd0);
}
- ExifTag exifOffsetTag = new ExifTag(ExifTag.TAG_EXIF_IFD,
- ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0);
+ ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
+ if (exifOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_EXIF_IFD);
+ }
ifd0.setTag(exifOffsetTag);
- // Exif IFD is required for all file.
+ // Exif IFD is required for all files.
IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
if (exifIfd == null) {
exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
@@ -334,16 +325,23 @@ public class ExifOutputStream extends FilterOutputStream {
// GPS IFD
IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
if (gpsIfd != null) {
- ExifTag gpsOffsetTag = new ExifTag(ExifTag.TAG_GPS_IFD,
- ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0);
+ ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
+ if (gpsOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_GPS_IFD);
+ }
ifd0.setTag(gpsOffsetTag);
}
// Interoperability IFD
IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
if (interIfd != null) {
- ExifTag interOffsetTag = new ExifTag(ExifTag.TAG_INTEROPERABILITY_IFD,
- ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_EXIF);
+ ExifTag interOffsetTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
+ if (interOffsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_INTEROPERABILITY_IFD);
+ }
exifIfd.setTag(interOffsetTag);
}
@@ -351,27 +349,50 @@ public class ExifOutputStream extends FilterOutputStream {
// thumbnail
if (mExifData.hasCompressedThumbnail()) {
+
if (ifd1 == null) {
ifd1 = new IfdData(IfdId.TYPE_IFD_1);
mExifData.addIfdData(ifd1);
}
- ExifTag offsetTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT,
- ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1);
+
+ ExifTag offsetTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+ if (offsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+ }
+
ifd1.setTag(offsetTag);
- ExifTag lengthTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
- ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1);
+ ExifTag lengthTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ if (lengthTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ }
+
lengthTag.setValue(mExifData.getCompressedThumbnail().length);
ifd1.setTag(lengthTag);
- } else if (mExifData.hasUncompressedStrip()){
+
+ // Get rid of tags for uncompressed if they exist.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+ } else if (mExifData.hasUncompressedStrip()) {
if (ifd1 == null) {
ifd1 = new IfdData(IfdId.TYPE_IFD_1);
mExifData.addIfdData(ifd1);
}
int stripCount = mExifData.getStripCount();
- ExifTag offsetTag = new ExifTag(ExifTag.TAG_STRIP_OFFSETS,
- ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1);
- ExifTag lengthTag = new ExifTag(ExifTag.TAG_STRIP_BYTE_COUNTS,
- ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1);
+ ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
+ if (offsetTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_STRIP_OFFSETS);
+ }
+ ExifTag lengthTag = mInterface
+ .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
+ if (lengthTag == null) {
+ throw new IOException("No definition for crucial exif tag: "
+ + ExifInterface.TAG_STRIP_BYTE_COUNTS);
+ }
long[] lengths = new long[stripCount];
for (int i = 0; i < mExifData.getStripCount(); i++) {
lengths[i] = mExifData.getStrip(i).length;
@@ -379,6 +400,17 @@ public class ExifOutputStream extends FilterOutputStream {
lengthTag.setValue(lengths);
ifd1.setTag(offsetTag);
ifd1.setTag(lengthTag);
+ // Get rid of tags for compressed if they exist.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+ ifd1.removeTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
+ } else {
+ // Get rid of offset and length tags if there is no thumbnail.
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
+ ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
+ ifd1.removeTag(ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
}
}
@@ -386,20 +418,21 @@ public class ExifOutputStream extends FilterOutputStream {
int offset = TIFF_HEADER_SIZE;
IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
offset = calculateOffsetOfIfd(ifd0, offset);
- ifd0.getTag(ExifTag.TAG_EXIF_IFD).setValue(offset);
+ ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset);
IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
offset = calculateOffsetOfIfd(exifIfd, offset);
IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
if (interIfd != null) {
- exifIfd.getTag(ExifTag.TAG_INTEROPERABILITY_IFD).setValue(offset);
+ exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
+ .setValue(offset);
offset = calculateOffsetOfIfd(interIfd, offset);
}
IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
if (gpsIfd != null) {
- ifd0.getTag(ExifTag.TAG_GPS_IFD).setValue(offset);
+ ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset);
offset = calculateOffsetOfIfd(gpsIfd, offset);
}
@@ -411,17 +444,58 @@ public class ExifOutputStream extends FilterOutputStream {
// thumbnail
if (mExifData.hasCompressedThumbnail()) {
- ifd1.getTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT).setValue(offset);
+ ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
+ .setValue(offset);
offset += mExifData.getCompressedThumbnail().length;
- } else if (mExifData.hasUncompressedStrip()){
+ } else if (mExifData.hasUncompressedStrip()) {
int stripCount = mExifData.getStripCount();
long[] offsets = new long[stripCount];
for (int i = 0; i < mExifData.getStripCount(); i++) {
offsets[i] = offset;
offset += mExifData.getStrip(i).length;
}
- ifd1.getTag(ExifTag.TAG_STRIP_OFFSETS).setValue(offsets);
+ ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
+ offsets);
}
return offset;
}
-} \ No newline at end of file
+
+ static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ switch (tag.getDataType()) {
+ case ExifTag.TYPE_ASCII:
+ byte buf[] = tag.getStringByte();
+ if (buf.length == tag.getComponentCount()) {
+ buf[buf.length - 1] = 0;
+ dataOutputStream.write(buf);
+ } else {
+ dataOutputStream.write(buf);
+ dataOutputStream.write(0);
+ }
+ break;
+ case ExifTag.TYPE_LONG:
+ case ExifTag.TYPE_UNSIGNED_LONG:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeInt((int) tag.getValueAt(i));
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeRational(tag.getRational(i));
+ }
+ break;
+ case ExifTag.TYPE_UNDEFINED:
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ dataOutputStream.write(buf);
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
+ dataOutputStream.writeShort((short) tag.getValueAt(i));
+ }
+ break;
+ }
+ }
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
index 1e4803f..b6d7e61 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
@@ -26,10 +26,13 @@ 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.
+ * 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.
+ * 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,
@@ -63,11 +66,12 @@ import java.util.TreeMap;
* }
* </pre>
*/
-public class ExifParser {
+class ExifParser {
+ private static final boolean LOGV = false;
private static final String TAG = "ExifParser";
/**
- * When the parser reaches a new IFD area. Call
- * {@link #getCurrentIfd()} to know which IFD we are in.
+ * 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;
/**
@@ -77,8 +81,8 @@ public class ExifParser {
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.
+ * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()}
+ * to get the corresponding tag.
*/
public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
@@ -87,8 +91,9 @@ public class ExifParser {
*/
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.
+ * When the parser reaches the uncompressed image strip. Call
+ * {@link #getStripIndex()} to get the index of the strip.
+ *
* @see #getStripIndex()
* @see #getStripCount()
*/
@@ -123,20 +128,20 @@ public class ExifParser {
*/
public static final int OPTION_THUMBNAIL = 1 << 5;
- private static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
- private static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
+ protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
+ protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
// TIFF header
- private static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
- private static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
- private static final short TIFF_HEADER_TAIL = 0x002A;
+ protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
+ protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
+ protected static final short TIFF_HEADER_TAIL = 0x002A;
- private static final int TAG_SIZE = 12;
- private static final int OFFSET_SIZE = 2;
+ protected static final int TAG_SIZE = 12;
+ protected static final int OFFSET_SIZE = 2;
private static final Charset US_ASCII = Charset.forName("US-ASCII");
- private static final int DEFAULT_IFD0_OFFSET = 8;
+ protected static final int DEFAULT_IFD0_OFFSET = 8;
private final CountedDataInputStream mTiffStream;
private final int mOptions;
@@ -151,9 +156,25 @@ public class ExifParser {
private boolean mNeedToParseOffsetsInCurrentIfd;
private boolean mContainExifData = false;
private int mApp1End;
+ private int mOffsetToApp1EndFromSOF = 0;
private byte[] mDataAboveIfd0;
private int mIfd0Position;
private int mTiffStartPosition;
+ private final ExifInterface mInterface;
+
+ private static final short TAG_EXIF_IFD = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_EXIF_IFD);
+ private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD);
+ private static final short TAG_INTEROPERABILITY_IFD = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD);
+ private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
+ private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ private static final short TAG_STRIP_OFFSETS = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS);
+ private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface
+ .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS);
private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
@@ -177,15 +198,21 @@ public class ExifParser {
return (mOptions & OPTION_THUMBNAIL) != 0;
}
- private ExifParser(InputStream inputStream, int options)
+ private ExifParser(InputStream inputStream, int options, ExifInterface iRef)
throws IOException, ExifInvalidFormatException {
if (inputStream == null) {
throw new IOException("Null argument inputStream to ExifParser");
}
+ if (LOGV) {
+ Log.v(TAG, "Reading exif...");
+ }
+ mInterface = iRef;
mContainExifData = seekTiffData(inputStream);
mTiffStream = new CountedDataInputStream(inputStream);
mOptions = options;
- if (!mContainExifData) return;
+ if (!mContainExifData) {
+ return;
+ }
parseTiffHeader();
long offset = mTiffStream.readUnsignedInt();
@@ -205,26 +232,28 @@ public class ExifParser {
/**
* Parses the the given InputStream with the given options
+ *
* @exception IOException
* @exception ExifInvalidFormatException
*/
- public static ExifParser parse(InputStream inputStream, int options)
+ protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef)
throws IOException, ExifInvalidFormatException {
- return new ExifParser(inputStream, options);
+ return new ExifParser(inputStream, options, iRef);
}
/**
- * Parses the the given InputStream with default options; that is, every IFD and thumbnaill
- * will be parsed.
+ * 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)
+ protected static ExifParser parse(InputStream inputStream, ExifInterface iRef)
throws IOException, ExifInvalidFormatException {
return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1
| OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY
- | OPTION_THUMBNAIL);
+ | OPTION_THUMBNAIL, iRef);
}
/**
@@ -239,7 +268,7 @@ public class ExifParser {
* @see #EVENT_UNCOMPRESSED_STRIP
* @see #EVENT_END
*/
- public int next() throws IOException, ExifInvalidFormatException {
+ protected int next() throws IOException, ExifInvalidFormatException {
if (!mContainExifData) {
return EVENT_END;
}
@@ -280,7 +309,7 @@ public class ExifParser {
}
}
}
- while(mCorrespondingEvent.size() != 0) {
+ while (mCorrespondingEvent.size() != 0) {
Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
Object event = entry.getValue();
try {
@@ -325,21 +354,25 @@ public class ExifParser {
}
/**
- * Skips the tags area of current IFD, if the parser is not in the tag area, nothing will
- * happen.
+ * 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 {
+ protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
int offset = mTiffStream.getReadByteCount();
- if (offset > endOfTags) return;
+ if (offset > endOfTags) {
+ return;
+ }
if (mNeedToParseOffsetsInCurrentIfd) {
while (offset < endOfTags) {
mTag = readTag();
offset += TAG_SIZE;
- if (mTag == null) continue;
+ if (mTag == null) {
+ continue;
+ }
checkOffsetOrImageTag(mTag);
}
} else {
@@ -372,20 +405,20 @@ public class ExifParser {
}
/**
- * If {@link #next()} return {@link #EVENT_NEW_TAG} or {@link #EVENT_VALUE_OF_REGISTERED_TAG},
- * call this function to get the corresponding tag.
+ * 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.
- *
+ * 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.
+ * 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[])
@@ -395,14 +428,14 @@ public class ExifParser {
* @see #readString(int)
* @see #readString(int, Charset)
*/
- public ExifTag getTag() {
+ protected ExifTag getTag() {
return mTag;
}
/**
* Gets number of tags in the current IFD area.
*/
- public int getTagCountInCurrentIfd() {
+ protected int getTagCountInCurrentIfd() {
return mNumOfTagInIfd;
}
@@ -415,42 +448,48 @@ public class ExifParser {
* @see IfdId#TYPE_IFD_INTEROPERABILITY
* @see IfdId#TYPE_IFD_EXIF
*/
- public int getCurrentIfd() {
+ protected int getCurrentIfd() {
return mIfdType;
}
/**
- * When receiving {@link #EVENT_UNCOMPRESSED_STRIP},
- * call this function to get the index of this strip.
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+ * get the index of this strip.
+ *
* @see #getStripCount()
*/
- public int getStripIndex() {
+ protected int getStripIndex() {
return mImageEvent.stripIndex;
}
/**
- * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the number
- * of strip data.
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+ * get the number of strip data.
+ *
* @see #getStripIndex()
*/
- public int getStripCount() {
+ protected int getStripCount() {
return mStripCount;
}
/**
- * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the strip size.
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+ * get the strip size.
*/
- public int getStripSize() {
- if (mStripSizeTag == null) return 0;
+ protected int getStripSize() {
+ if (mStripSizeTag == null)
+ return 0;
return (int) mStripSizeTag.getValueAt(0);
}
/**
- * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get the image data
- * size.
+ * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get
+ * the image data size.
*/
- public int getCompressedImageSize() {
- if (mJpegSizeTag == null) return 0;
+ protected int getCompressedImageSize() {
+ if (mJpegSizeTag == null) {
+ return 0;
+ }
return (int) mJpegSizeTag.getValueAt(0);
}
@@ -462,13 +501,15 @@ public class ExifParser {
}
/**
- * 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.
+ * 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) {
+ protected void registerForTagValue(ExifTag tag) {
mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
}
@@ -501,7 +542,9 @@ public class ExifParser {
mTiffStream.skip(4);
return null;
}
- ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType);
+ // TODO: handle numOfComp overflow
+ ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType,
+ ((int) numOfComp) != ExifTag.SIZE_UNDEFINED);
int dataSize = tag.getDataSize();
if (dataSize > 4) {
long offset = mTiffStream.readUnsignedInt();
@@ -529,146 +572,154 @@ public class ExifParser {
}
/**
- * 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.
+ * Check the tag, if the tag is one of the offset tag that points to the IFD
+ * or image the caller is interested in, register the IFD or image.
*/
private void checkOffsetOrImageTag(ExifTag tag) {
// Some invalid formattd image contains tag with 0 size.
if (tag.getComponentCount() == 0) {
return;
}
- switch (tag.getTagId()) {
- case ExifTag.TAG_EXIF_IFD:
- if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
- || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
- registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0));
- }
- break;
- case ExifTag.TAG_GPS_IFD:
- if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
- registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0));
- }
- break;
- case ExifTag.TAG_INTEROPERABILITY_IFD:
- if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
- registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0));
- }
- break;
- case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT:
- if (isThumbnailRequested()) {
- registerCompressedImage(tag.getValueAt(0));
- }
- break;
- case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
- if (isThumbnailRequested()) {
- mJpegSizeTag = tag;
- }
- break;
- case ExifTag.TAG_STRIP_OFFSETS:
- if (isThumbnailRequested()) {
- if (tag.hasValue()) {
- for (int i = 0; i < tag.getComponentCount(); i++) {
- if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
- registerUncompressedStrip(i, tag.getValueAt(i));
- } else {
- registerUncompressedStrip(i, tag.getValueAt(i));
- }
+ short tid = tag.getTagId();
+ int ifd = tag.getIfd();
+ if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) {
+ if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
+ || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+ registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0));
+ }
+ } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) {
+ if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
+ registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0));
+ }
+ } else if (tid == TAG_INTEROPERABILITY_IFD
+ && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) {
+ if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
+ registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0));
+ }
+ } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT
+ && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) {
+ if (isThumbnailRequested()) {
+ registerCompressedImage(tag.getValueAt(0));
+ }
+ } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH
+ && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) {
+ if (isThumbnailRequested()) {
+ mJpegSizeTag = tag;
+ }
+ } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) {
+ if (isThumbnailRequested()) {
+ if (tag.hasValue()) {
+ for (int i = 0; i < tag.getComponentCount(); i++) {
+ if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
+ registerUncompressedStrip(i, tag.getValueAt(i));
+ } else {
+ registerUncompressedStrip(i, tag.getValueAt(i));
}
- } else {
- mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
- }
- }
- break;
- case ExifTag.TAG_STRIP_BYTE_COUNTS:
- if (isThumbnailRequested()) {
- if (tag.hasValue()) {
- mStripSizeTag = tag;
}
+ } else {
+ mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
}
- break;
+ }
+ } else if (tid == TAG_STRIP_BYTE_COUNTS
+ && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS)
+ &&isThumbnailRequested() && tag.hasValue()) {
+ mStripSizeTag = tag;
+ }
+ }
+
+ private boolean checkAllowed(int ifd, int tagId) {
+ int info = mInterface.getTagInfo().get(tagId);
+ if (info == ExifInterface.DEFINITION_NULL) {
+ return false;
}
+ return ExifInterface.isIfdAllowed(info, ifd);
}
- void readFullTagValue(ExifTag tag) throws IOException {
+ protected void readFullTagValue(ExifTag tag) throws IOException {
// Some invalid images contains tags with wrong size, check it here
short type = tag.getDataType();
if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
type == ExifTag.TYPE_UNSIGNED_BYTE) {
int size = tag.getComponentCount();
if (mCorrespondingEvent.size() > 0) {
- if (mCorrespondingEvent.firstEntry().getKey() <
- mTiffStream.getReadByteCount() + size) {
- Log.w(TAG, "Invalid size of tag.");
- size = mCorrespondingEvent.firstEntry().getKey()
- - mTiffStream.getReadByteCount();
- tag.setComponentCount(size);
+ if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount()
+ + size) {
+ if (mCorrespondingEvent.firstEntry().getValue() instanceof ImageEvent) {
+ // Invalid thumbnail offset: tag metadata overlaps with
+ // strip.
+ Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
+ // Ignore thumbnail.
+ Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey());
+ } else {
+ size = mCorrespondingEvent.firstEntry().getKey()
+ - mTiffStream.getReadByteCount();
+ Log.w(TAG, "Invalid size of tag: \n" + tag.toString()
+ + " setting count to: " + size);
+ tag.forceSetComponentCount(size);
+ }
}
}
}
- switch(tag.getDataType()) {
+ switch (tag.getDataType()) {
case ExifTag.TYPE_UNSIGNED_BYTE:
- case ExifTag.TYPE_UNDEFINED:
- {
- byte buf[] = new byte[tag.getComponentCount()];
- read(buf);
- tag.setValue(buf);
- }
+ case ExifTag.TYPE_UNDEFINED: {
+ byte buf[] = new byte[tag.getComponentCount()];
+ read(buf);
+ tag.setValue(buf);
+ }
break;
case ExifTag.TYPE_ASCII:
tag.setValue(readString(tag.getComponentCount()));
break;
- case ExifTag.TYPE_UNSIGNED_LONG:
- {
- long value[] = new long[tag.getComponentCount()];
- for (int i = 0, n = value.length; i < n; i++) {
- value[i] = readUnsignedLong();
- }
- tag.setValue(value);
+ case ExifTag.TYPE_UNSIGNED_LONG: {
+ long value[] = new long[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readUnsignedLong();
}
+ tag.setValue(value);
+ }
break;
- case ExifTag.TYPE_UNSIGNED_RATIONAL:
- {
- Rational value[] = new Rational[tag.getComponentCount()];
- for (int i = 0, n = value.length; i < n; i++) {
- value[i] = readUnsignedRational();
- }
- tag.setValue(value);
+ 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);
+ case ExifTag.TYPE_UNSIGNED_SHORT: {
+ int value[] = new int[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readUnsignedShort();
}
+ tag.setValue(value);
+ }
break;
- case ExifTag.TYPE_LONG:
- {
- int value[] = new int[tag.getComponentCount()];
- for (int i = 0, n = value.length; i < n; i++) {
- value[i] = readLong();
- }
- tag.setValue(value);
+ case ExifTag.TYPE_LONG: {
+ int value[] = new int[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readLong();
}
+ tag.setValue(value);
+ }
break;
- case ExifTag.TYPE_RATIONAL:
- {
- Rational value[] = new Rational[tag.getComponentCount()];
- for (int i = 0, n = value.length; i < n; i++) {
- value[i] = readRational();
- }
- tag.setValue(value);
+ case ExifTag.TYPE_RATIONAL: {
+ Rational value[] = new Rational[tag.getComponentCount()];
+ for (int i = 0, n = value.length; i < n; i++) {
+ value[i] = readRational();
}
+ tag.setValue(value);
+ }
break;
}
+ if (LOGV) {
+ Log.v(TAG, "\n" + tag.toString());
+ }
}
private void parseTiffHeader() throws IOException,
ExifInvalidFormatException {
short byteOrder = mTiffStream.readShort();
- ByteOrder order;
if (LITTLE_ENDIAN_TAG == byteOrder) {
mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
} else if (BIG_ENDIAN_TAG == byteOrder) {
@@ -690,7 +741,7 @@ public class ExifParser {
}
short marker = dataStream.readShort();
- while(marker != JpegHeader.EOI
+ while (marker != JpegHeader.EOI
&& !JpegHeader.isSofMarker(marker)) {
int length = dataStream.readUnsignedShort();
// Some invalid formatted image contains multiple APP1,
@@ -705,6 +756,7 @@ public class ExifParser {
if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
mTiffStartPosition = dataStream.getReadByteCount();
mApp1End = length;
+ mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End;
return true;
}
}
@@ -718,41 +770,44 @@ public class ExifParser {
return false;
}
- int getTiffStartPosition() {
+ protected int getOffsetToExifEndFromSOF() {
+ return mOffsetToApp1EndFromSOF;
+ }
+
+ protected int getTiffStartPosition() {
return mTiffStartPosition;
}
/**
* Reads bytes from the InputStream.
*/
- public int read(byte[] buffer, int offset, int length) throws IOException {
+ protected 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 {
+ protected int read(byte[] buffer) throws IOException {
return mTiffStream.read(buffer);
}
/**
- * Reads a String from the InputStream with US-ASCII charset.
- * The parser will read n bytes and convert it to ascii string.
- * This is used for reading values of type {@link ExifTag#TYPE_ASCII}.
+ * Reads a String from the InputStream with US-ASCII charset. The parser
+ * will read n bytes and convert it to ascii string. This is used for
+ * reading values of type {@link ExifTag#TYPE_ASCII}.
*/
- public String readString(int n) throws IOException {
+ protected String readString(int n) throws IOException {
return readString(n, US_ASCII);
}
/**
- * Reads a String from the InputStream with the given charset.
- * The parser will read n bytes and convert it to string.
- * This is used for reading values of type {@link ExifTag#TYPE_ASCII}.
+ * Reads a String from the InputStream with the given charset. The parser
+ * will read n bytes and convert it to string. This is used for reading
+ * values of type {@link ExifTag#TYPE_ASCII}.
*/
- public String readString(int n, Charset charset) throws IOException {
+ protected String readString(int n, Charset charset) throws IOException {
if (n > 0) {
- byte[] buf = new byte[n];
return mTiffStream.readString(n, charset);
} else {
return "";
@@ -760,23 +815,26 @@ public class ExifParser {
}
/**
- * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the InputStream.
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the
+ * InputStream.
*/
- public int readUnsignedShort() throws IOException {
+ protected int readUnsignedShort() throws IOException {
return mTiffStream.readShort() & 0xffff;
}
/**
- * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the InputStream.
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the
+ * InputStream.
*/
- public long readUnsignedLong() throws IOException {
+ protected long readUnsignedLong() throws IOException {
return readLong() & 0xffffffffL;
}
/**
- * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the InputStream.
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the
+ * InputStream.
*/
- public Rational readUnsignedRational() throws IOException {
+ protected Rational readUnsignedRational() throws IOException {
long nomi = readUnsignedLong();
long denomi = readUnsignedLong();
return new Rational(nomi, denomi);
@@ -785,14 +843,14 @@ public class ExifParser {
/**
* Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream.
*/
- public int readLong() throws IOException {
+ protected int readLong() throws IOException {
return mTiffStream.readInt();
}
/**
* Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream.
*/
- public Rational readRational() throws IOException {
+ protected Rational readRational() throws IOException {
int nomi = readLong();
int denomi = readLong();
return new Rational(nomi, denomi);
@@ -801,10 +859,12 @@ public class ExifParser {
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;
@@ -814,6 +874,7 @@ public class ExifParser {
private static class IfdEvent {
int ifd;
boolean isRequested;
+
IfdEvent(int ifd, boolean isInterestedIfd) {
this.ifd = ifd;
this.isRequested = isInterestedIfd;
@@ -823,6 +884,7 @@ public class ExifParser {
private static class ExifTagEvent {
ExifTag tag;
boolean isRequested;
+
ExifTagEvent(ExifTag tag, boolean isRequireByUser) {
this.tag = tag;
this.isRequested = isRequireByUser;
@@ -832,7 +894,7 @@ public class ExifParser {
/**
* Gets the byte order of the current InputStream.
*/
- public ByteOrder getByteOrder() {
+ protected ByteOrder getByteOrder() {
return mTiffStream.getByteOrder();
}
}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java
index 5bce9c4..68e972f 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java
@@ -22,19 +22,30 @@ import java.io.IOException;
import java.io.InputStream;
/**
- * This class reads the EXIF header of a JPEG file and stores it in {@link ExifData}.
+ * This class reads the EXIF header of a JPEG file and stores it in
+ * {@link ExifData}.
*/
-public class ExifReader {
+class ExifReader {
private static final String TAG = "ExifReader";
+
+ private final ExifInterface mInterface;
+
+ ExifReader(ExifInterface iRef) {
+ mInterface = iRef;
+ }
+
/**
- * Parses the inputStream and and returns the EXIF data in an {@link ExifData}.
+ * Parses the inputStream and and returns the EXIF data in an
+ * {@link ExifData}.
+ *
* @throws ExifInvalidFormatException
* @throws IOException
*/
- public ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
+ protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
IOException {
- ExifParser parser = ExifParser.parse(inputStream);
+ ExifParser parser = ExifParser.parse(inputStream, mInterface);
ExifData exifData = new ExifData(parser.getByteOrder());
+ ExifTag tag = null;
int event = parser.next();
while (event != ExifParser.EVENT_END) {
@@ -43,7 +54,7 @@ public class ExifReader {
exifData.addIfdData(new IfdData(parser.getCurrentIfd()));
break;
case ExifParser.EVENT_NEW_TAG:
- ExifTag tag = parser.getTag();
+ tag = parser.getTag();
if (!tag.hasValue()) {
parser.registerForTagValue(tag);
} else {
@@ -69,6 +80,7 @@ public class ExifReader {
buf = new byte[parser.getStripSize()];
if (buf.length == parser.read(buf)) {
exifData.setStripBytes(parser.getStripIndex(), buf);
+ } else {
Log.w(TAG, "Failed to read the strip bytes");
}
break;
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
index 753b18c..0b31c4d 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
@@ -16,488 +16,26 @@
package com.android.gallery3d.exif;
-import android.util.SparseArray;
-
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
- * This class stores information of an EXIF tag.
- * @see ExifParser
- * @see ExifReader
- * @see IfdData
- * @see ExifData
+ * This class stores information of an EXIF tag. For more information about
+ * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
+ * instantiated using {@link ExifInterface#buildTag}.
+ *
+ * @see ExifInterface
*/
public class ExifTag {
- // Tiff Tags
- public static final short TAG_IMAGE_WIDTH = 0x100;
- /*
- * The height of the image.
- */
- public static final short TAG_IMAGE_LENGTH = 0x101;
- public static final short TAG_BITS_PER_SAMPLE = 0x102;
- public static final short TAG_COMPRESSION = 0x103;
- public static final short TAG_PHOTOMETRIC_INTERPRETATION = 0x106;
- public static final short TAG_IMAGE_DESCRIPTION = 0x10E;
- public static final short TAG_MAKE = 0x10F;
- public static final short TAG_MODEL = 0x110;
- public static final short TAG_STRIP_OFFSETS = 0x111;
- public static final short TAG_ORIENTATION = 0x112;
- public static final short TAG_SAMPLES_PER_PIXEL = 0x115;
- public static final short TAG_ROWS_PER_STRIP = 0x116;
- public static final short TAG_STRIP_BYTE_COUNTS = 0x117;
- public static final short TAG_X_RESOLUTION = 0x11A;
- public static final short TAG_Y_RESOLUTION = 0x11B;
- public static final short TAG_PLANAR_CONFIGURATION = 0x11C;
- public static final short TAG_RESOLUTION_UNIT = 0x128;
- public static final short TAG_TRANSFER_FUNCTION = 0x12D;
- public static final short TAG_SOFTWARE = 0x131;
- public static final short TAG_DATE_TIME = 0x132;
- public static final short TAG_ARTIST = 0x13B;
- public static final short TAG_WHITE_POINT = 0x13E;
- public static final short TAG_PRIMARY_CHROMATICITIES = 0x13F;
- public static final short TAG_JPEG_INTERCHANGE_FORMAT = 0x201;
- public static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 0x202;
- public static final short TAG_Y_CB_CR_COEFFICIENTS = 0x211;
- public static final short TAG_Y_CB_CR_SUB_SAMPLING = 0x212;
- public static final short TAG_Y_CB_CR_POSITIONING = 0x213;
- public static final short TAG_REFERENCE_BLACK_WHITE = 0x214;
- public static final short TAG_COPYRIGHT = (short) 0x8298;
- public static final short TAG_EXIF_IFD = (short) 0x8769;
- public static final short TAG_GPS_IFD = (short) 0x8825;
-
- // Exif Tags
- public static final short TAG_EXPOSURE_TIME = (short) 0x829A;
- public static final short TAG_F_NUMBER = (short) 0x829D;
- public static final short TAG_EXPOSURE_PROGRAM = (short) 0x8822;
- public static final short TAG_SPECTRAL_SENSITIVITY = (short) 0x8824;
- public static final short TAG_ISO_SPEED_RATINGS = (short) 0x8827;
- public static final short TAG_OECF = (short) 0x8828;
- public static final short TAG_EXIF_VERSION = (short) 0x9000;
- public static final short TAG_DATE_TIME_ORIGINAL = (short) 0x9003;
- public static final short TAG_DATE_TIME_DIGITIZED = (short) 0x9004;
- public static final short TAG_COMPONENTS_CONFIGURATION = (short) 0x9101;
- public static final short TAG_COMPRESSED_BITS_PER_PIXEL = (short) 0x9102;
- public static final short TAG_SHUTTER_SPEED_VALUE = (short) 0x9201;
- public static final short TAG_APERTURE_VALUE = (short) 0x9202;
- public static final short TAG_BRIGHTNESS_VALUE = (short) 0x9203;
- public static final short TAG_EXPOSURE_BIAS_VALUE = (short) 0x9204;
- public static final short TAG_MAX_APERTURE_VALUE = (short) 0x9205;
- public static final short TAG_SUBJECT_DISTANCE = (short) 0x9206;
- public static final short TAG_METERING_MODE = (short) 0x9207;
- public static final short TAG_LIGHT_SOURCE = (short) 0x9208;
- public static final short TAG_FLASH = (short) 0x9209;
- public static final short TAG_FOCAL_LENGTH = (short) 0x920A;
- public static final short TAG_SUBJECT_AREA = (short) 0x9214;
- public static final short TAG_MAKER_NOTE = (short) 0x927C;
- public static final short TAG_USER_COMMENT = (short) 0x9286;
- public static final short TAG_SUB_SEC_TIME = (short) 0x9290;
- public static final short TAG_SUB_SEC_TIME_ORIGINAL = (short) 0x9291;
- public static final short TAG_SUB_SEC_TIME_DIGITIZED = (short) 0x9292;
- public static final short TAG_FLASHPIX_VERSION = (short) 0xA000;
- public static final short TAG_COLOR_SPACE = (short) 0xA001;
- public static final short TAG_PIXEL_X_DIMENSION = (short) 0xA002;
- public static final short TAG_PIXEL_Y_DIMENSION = (short) 0xA003;
- public static final short TAG_RELATED_SOUND_FILE = (short) 0xA004;
- public static final short TAG_INTEROPERABILITY_IFD = (short) 0xA005;
- public static final short TAG_FLASH_ENERGY = (short) 0xA20B;
- public static final short TAG_SPATIAL_FREQUENCY_RESPONSE = (short) 0xA20C;
- public static final short TAG_FOCAL_PLANE_X_RESOLUTION = (short) 0xA20E;
- public static final short TAG_FOCAL_PLANE_Y_RESOLUTION = (short) 0xA20F;
- public static final short TAG_FOCAL_PLANE_RESOLUTION_UNIT = (short) 0xA210;
- public static final short TAG_SUBJECT_LOCATION = (short) 0xA214;
- public static final short TAG_EXPOSURE_INDEX = (short) 0xA215;
- public static final short TAG_SENSING_METHOD = (short) 0xA217;
- public static final short TAG_FILE_SOURCE = (short) 0xA300;
- public static final short TAG_SCENE_TYPE = (short) 0xA301;
- public static final short TAG_CFA_PATTERN = (short) 0xA302;
- public static final short TAG_CUSTOM_RENDERED = (short) 0xA401;
- public static final short TAG_EXPOSURE_MODE = (short) 0xA402;
- public static final short TAG_WHITE_BALANCE = (short) 0xA403;
- public static final short TAG_DIGITAL_ZOOM_RATIO = (short) 0xA404;
- public static final short TAG_FOCAL_LENGTH_IN_35_MM_FILE = (short) 0xA405;
- public static final short TAG_SCENE_CAPTURE_TYPE = (short) 0xA406;
- public static final short TAG_GAIN_CONTROL = (short) 0xA407;
- public static final short TAG_CONTRAST = (short) 0xA408;
- public static final short TAG_SATURATION = (short) 0xA409;
- public static final short TAG_SHARPNESS = (short) 0xA40A;
- public static final short TAG_DEVICE_SETTING_DESCRIPTION = (short) 0xA40B;
- public static final short TAG_SUBJECT_DISTANCE_RANGE = (short) 0xA40C;
- public static final short TAG_IMAGE_UNIQUE_ID = (short) 0xA420;
-
- // GPS tags
- public static final short TAG_GPS_VERSION_ID = 0;
- public static final short TAG_GPS_LATITUDE_REF = 1;
- public static final short TAG_GPS_LATITUDE = 2;
- public static final short TAG_GPS_LONGITUDE_REF = 3;
- public static final short TAG_GPS_LONGITUDE = 4;
- public static final short TAG_GPS_ALTITUDE_REF = 5;
- public static final short TAG_GPS_ALTITUDE = 6;
- public static final short TAG_GPS_TIME_STAMP = 7;
- public static final short TAG_GPS_SATTELLITES = 8;
- public static final short TAG_GPS_STATUS = 9;
- public static final short TAG_GPS_MEASURE_MODE = 10;
- public static final short TAG_GPS_DOP = 11;
- public static final short TAG_GPS_SPEED_REF = 12;
- public static final short TAG_GPS_SPEED = 13;
- public static final short TAG_GPS_TRACK_REF = 14;
- public static final short TAG_GPS_TRACK = 15;
- public static final short TAG_GPS_IMG_DIRECTION_REF = 16;
- public static final short TAG_GPS_IMG_DIRECTION = 17;
- public static final short TAG_GPS_MAP_DATUM = 18;
- public static final short TAG_GPS_DEST_LATITUDE_REF = 19;
- public static final short TAG_GPS_DEST_LATITUDE = 20;
- public static final short TAG_GPS_DEST_LONGITUDE_REF = 21;
- public static final short TAG_GPS_DEST_LONGITUDE = 22;
- public static final short TAG_GPS_DEST_BEARING_REF = 23;
- public static final short TAG_GPS_DEST_BEARING = 24;
- public static final short TAG_GPS_DEST_DISTANCE_REF = 25;
- public static final short TAG_GPS_DEST_DISTANCE = 26;
- public static final short TAG_GPS_PROCESSING_METHOD = 27;
- public static final short TAG_GPS_AREA_INFORMATION = 28;
- public static final short TAG_GPS_DATE_STAMP = 29;
- public static final short TAG_GPS_DIFFERENTIAL = 30;
-
- // Interoperability tag
- public static final short TAG_INTEROPERABILITY_INDEX = 1;
-
- /**
- * Constants for {@link #TAG_ORIENTATION}
- */
- public static interface Orientation {
- public static final short TOP_LEFT = 1;
- public static final short TOP_RIGHT = 2;
- public static final short BOTTOM_LEFT = 3;
- public static final short BOTTOM_RIGHT = 4;
- public static final short LEFT_TOP = 5;
- public static final short RIGHT_TOP = 6;
- public static final short LEFT_BOTTOM = 7;
- public static final short RIGHT_BOTTOM = 8;
- }
-
- /**
- * Constants for {@link #TAG_Y_CB_CR_POSITIONING}
- */
- public static interface YCbCrPositioning {
- public static final short CENTERED = 1;
- public static final short CO_SITED = 2;
- }
-
- /**
- * Constants for {@link #TAG_COMPRESSION}
- */
- public static interface Compression {
- public static final short UNCOMPRESSION = 1;
- public static final short JPEG = 6;
- }
-
- /**
- * Constants for {@link #TAG_RESOLUTION_UNIT}
- */
- public static interface ResolutionUnit {
- public static final short INCHES = 2;
- public static final short CENTIMETERS = 3;
- }
-
- /**
- * Constants for {@link #TAG_PHOTOMETRIC_INTERPRETATION}
- */
- public static interface PhotometricInterpretation {
- public static final short RGB = 2;
- public static final short YCBCR = 6;
- }
-
- /**
- * Constants for {@link #TAG_PLANAR_CONFIGURATION}
- */
- public static interface PlanarConfiguration {
- public static final short CHUNKY = 1;
- public static final short PLANAR = 2;
- }
-
- /**
- * Constants for {@link #TAG_EXPOSURE_PROGRAM}
- */
- public static interface ExposureProgram {
- public static final short NOT_DEFINED = 0;
- public static final short MANUAL = 1;
- public static final short NORMAL_PROGRAM = 2;
- public static final short APERTURE_PRIORITY = 3;
- public static final short SHUTTER_PRIORITY = 4;
- public static final short CREATIVE_PROGRAM = 5;
- public static final short ACTION_PROGRAM = 6;
- public static final short PROTRAIT_MODE = 7;
- public static final short LANDSCAPE_MODE = 8;
- }
-
- /**
- * Constants for {@link #TAG_METERING_MODE}
- */
- public static interface MeteringMode {
- public static final short UNKNOWN = 0;
- public static final short AVERAGE = 1;
- public static final short CENTER_WEIGHTED_AVERAGE = 2;
- public static final short SPOT = 3;
- public static final short MULTISPOT = 4;
- public static final short PATTERN = 5;
- public static final short PARTAIL = 6;
- public static final short OTHER = 255;
- }
-
- /**
- * Constants for {@link #TAG_FLASH} As the definition in Jeita EXIF 2.2 standard, we can
- * treat this constant as bitwise flag.
- * <p>
- * e.g.
- * <p>
- * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED | MODE_AUTO_MODE
- */
- public static interface Flash {
- // LSB
- public static final short DID_NOT_FIRED = 0;
- public static final short FIRED = 1;
- // 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;
- // 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;
- // 5th bit
- public static final short FUNCTION_PRESENT = 0 << 5;
- public static final short FUNCTION_NO_FUNCTION = 1 << 5;
- // 6th bit
- public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6;
- public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6;
- }
-
- /**
- * Constants for {@link #TAG_COLOR_SPACE}
- */
- public static interface ColorSpace {
- public static final short SRGB = 1;
- public static final short UNCALIBRATED = (short) 0xFFFF;
- }
-
- /**
- * Constants for {@link #TAG_EXPOSURE_MODE}
- */
- public static interface ExposureMode {
- public static final short AUTO_EXPOSURE = 0;
- public static final short MANUAL_EXPOSURE = 1;
- public static final short AUTO_BRACKET = 2;
- }
-
- /**
- * Constants for {@link #TAG_WHITE_BALANCE}
- */
- public static interface WhiteBalance {
- public static final short AUTO = 0;
- public static final short MANUAL = 1;
- }
-
- /**
- * Constants for {@link #TAG_SCENE_CAPTURE_TYPE}
- */
- public static interface SceneCapture {
- public static final short STANDARD = 0;
- public static final short LANDSCAPE = 1;
- public static final short PROTRAIT = 2;
- public static final short NIGHT_SCENE = 3;
- }
-
- /**
- * Constants for {@link #TAG_COMPONENTS_CONFIGURATION}
- */
- public static interface ComponentsConfiguration {
- public static final short NOT_EXIST = 0;
- public static final short Y = 1;
- public static final short CB = 2;
- public static final short CR = 3;
- public static final short R = 4;
- public static final short G = 5;
- public static final short B = 6;
- }
-
- /**
- * Constants for {@link #TAG_LIGHT_SOURCE}
- */
- public static interface LightSource {
- public static final short UNKNOWN = 0;
- public static final short DAYLIGHT = 1;
- public static final short FLUORESCENT = 2;
- public static final short TUNGSTEN = 3;
- public static final short FLASH = 4;
- public static final short FINE_WEATHER = 9;
- public static final short CLOUDY_WEATHER = 10;
- public static final short SHADE = 11;
- public static final short DAYLIGHT_FLUORESCENT = 12;
- public static final short DAY_WHITE_FLUORESCENT = 13;
- public static final short COOL_WHITE_FLUORESCENT = 14;
- public static final short WHITE_FLUORESCENT = 15;
- public static final short STANDARD_LIGHT_A = 17;
- public static final short STANDARD_LIGHT_B = 18;
- public static final short STANDARD_LIGHT_C = 19;
- public static final short D55 = 20;
- public static final short D65 = 21;
- public static final short D75 = 22;
- public static final short D50 = 23;
- public static final short ISO_STUDIO_TUNGSTEN = 24;
- public static final short OTHER = 255;
- }
-
- /**
- * Constants for {@link #TAG_SENSING_METHOD}
- */
- public static interface SensingMethod {
- public static final short NOT_DEFINED = 1;
- public static final short ONE_CHIP_COLOR = 2;
- public static final short TWO_CHIP_COLOR = 3;
- public static final short THREE_CHIP_COLOR = 4;
- public static final short COLOR_SEQUENTIAL_AREA = 5;
- public static final short TRILINEAR = 7;
- public static final short COLOR_SEQUENTIAL_LINEAR = 8;
- }
-
- /**
- * Constants for {@link #TAG_FILE_SOURCE}
- */
- public static interface FileSource {
- public static final short DSC = 3;
- }
-
- /**
- * Constants for {@link #TAG_SCENE_TYPE}
- */
- public static interface SceneType {
- public static final short DIRECT_PHOTOGRAPHED = 1;
- }
-
- /**
- * Constants for {@link #TAG_GAIN_CONTROL}
- */
- public static interface GainControl {
- public static final short NONE = 0;
- public static final short LOW_UP = 1;
- public static final short HIGH_UP = 2;
- public static final short LOW_DOWN = 3;
- public static final short HIGH_DOWN = 4;
- }
-
- /**
- * Constants for {@link #TAG_CONTRAST}
- */
- public static interface Contrast {
- public static final short NORMAL = 0;
- public static final short SOFT = 1;
- public static final short HARD = 2;
- }
-
- /**
- * Constants for {@link #TAG_SATURATION}
- */
- public static interface Saturation {
- public static final short NORMAL = 0;
- public static final short LOW = 1;
- public static final short HIGH = 2;
- }
-
- /**
- * Constants for {@link #TAG_SHARPNESS}
- */
- public static interface Sharpness {
- public static final short NORMAL = 0;
- public static final short SOFT = 1;
- public static final short HARD = 2;
- }
-
- /**
- * Constants for {@link #TAG_SUBJECT_DISTANCE}
- */
- public static interface SubjectDistance {
- public static final short UNKNOWN = 0;
- public static final short MACRO = 1;
- public static final short CLOSE_VIEW = 2;
- public static final short DISTANT_VIEW = 3;
- }
-
- /**
- * Constants for {@link #TAG_GPS_LATITUDE_REF}, {@link #TAG_GPS_DEST_LATITUDE_REF}
- */
- public static interface GpsLatitudeRef {
- public static final String NORTH = "N";
- public static final String SOUTH = "S";
- }
-
- /**
- * Constants for {@link #TAG_GPS_LONGITUDE_REF}, {@link #TAG_GPS_DEST_LONGITUDE_REF}
- */
- public static interface GpsLongitudeRef {
- public static final String EAST = "E";
- public static final String WEST = "W";
- }
-
- /**
- * Constants for {@link #TAG_GPS_ALTITUDE_REF}
- */
- public static interface GpsAltitudeRef {
- public static final short SEA_LEVEL = 0;
- public static final short SEA_LEVEL_NEGATIVE = 1;
- }
-
- /**
- * Constants for {@link #TAG_GPS_STATUS}
- */
- public static interface GpsStatus {
- public static final String IN_PROGRESS = "A";
- public static final String INTEROPERABILITY = "V";
- }
-
- /**
- * Constants for {@link #TAG_GPS_MEASURE_MODE}
- */
- public static interface GpsMeasureMode {
- public static final String MODE_2_DIMENSIONAL = "2";
- public static final String MODE_3_DIMENSIONAL = "3";
- }
-
- /**
- * Constants for {@link #TAG_GPS_SPEED_REF}, {@link #TAG_GPS_DEST_DISTANCE_REF}
- */
- public static interface GpsSpeedRef {
- public static final String KILOMETERS = "K";
- public static final String MILES = "M";
- public static final String KNOTS = "N";
- }
-
- /**
- * Constants for {@link #TAG_GPS_TRACK_REF}, {@link #TAG_GPS_IMG_DIRECTION_REF},
- * {@link #TAG_GPS_DEST_BEARING_REF}
- */
- public static interface GpsTrackRef {
- public static final String TRUE_DIRECTION = "T";
- public static final String MAGNETIC_DIRECTION = "M";
- }
-
- /**
- * Constants for {@link #TAG_GPS_DIFFERENTIAL}
- */
- public static interface GpsDifferential {
- public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
- 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.
+ * 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;
/**
@@ -509,13 +47,13 @@ public class ExifTag {
*/
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.
+ * 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.
+ * 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;
/**
@@ -524,12 +62,18 @@ public class ExifTag {
*/
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.
+ * 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 Charset US_ASCII = Charset.forName("US-ASCII");
private static final int TYPE_TO_SIZE_MAP[] = new int[11];
+ 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;
+
static {
TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
@@ -541,391 +85,69 @@ public class ExifTag {
TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
}
- /**
- * Gets the element size of the given data type.
- *
- * @see #TYPE_ASCII
- * @see #TYPE_LONG
- * @see #TYPE_RATIONAL
- * @see #TYPE_UNDEFINED
- * @see #TYPE_UNSIGNED_BYTE
- * @see #TYPE_UNSIGNED_LONG
- * @see #TYPE_UNSIGNED_RATIONAL
- * @see #TYPE_UNSIGNED_SHORT
- */
- public static int getElementSize(short type) {
- 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_DATE_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);
- }
+ static final int SIZE_UNDEFINED = 0;
- private static Charset US_ASCII = Charset.forName("US-ASCII");
+ // Exif TagId
private final short mTagId;
+ // Exif Tag Type
private final short mDataType;
- private final int mIfd;
- private final boolean mComponentCountDefined;
- private int mComponentCount;
+ // If tag has defined count
+ private boolean mHasDefinedDefaultComponentCount;
+ // Actual data count in tag (should be number of elements in value array)
+ private int mComponentCountActual;
+ // The ifd that this tag should be put in
+ private int mIfd;
+ // The value (array of elements of type Tag Type)
private Object mValue;
+ // Value offset in exif header.
private int mOffset;
- 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));
- }
+ private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
/**
- * Create a tag related to thumbnail with given ID.
- * @exception IllegalArgumentException If the ID is invalid.
+ * Returns true if the given IFD is a valid IFD.
*/
- 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);
+ public static boolean isValidIfd(int ifdId) {
+ return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1
+ || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY
+ || ifdId == IfdId.TYPE_IFD_GPS;
}
/**
- * Create a tag related to interoperability with given ID.
- * @exception IllegalArgumentException If the ID is invalid.
+ * Returns true if a given type is a valid tag type.
*/
- 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);
- }
-
- static boolean isValidType(short type) {
+ public static boolean isValidType(short type) {
return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII ||
type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG ||
type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED ||
type == TYPE_LONG || type == TYPE_RATIONAL;
}
- ExifTag(short tagId, short type, int componentCount, int ifd) {
+ // Use builtTag in ExifInterface instead of constructor.
+ ExifTag(short tagId, short type, int componentCount, int ifd,
+ boolean hasDefinedComponentCount) {
mTagId = tagId;
mDataType = type;
- mComponentCount = componentCount;
- mComponentCountDefined = getComponentCountDefined(tagId, ifd);
+ mComponentCountActual = componentCount;
+ mHasDefinedDefaultComponentCount = hasDefinedComponentCount;
mIfd = ifd;
+ mValue = null;
+ }
+
+ /**
+ * Gets the element size of the given data type in bytes.
+ *
+ * @see #TYPE_ASCII
+ * @see #TYPE_LONG
+ * @see #TYPE_RATIONAL
+ * @see #TYPE_UNDEFINED
+ * @see #TYPE_UNSIGNED_BYTE
+ * @see #TYPE_UNSIGNED_LONG
+ * @see #TYPE_UNSIGNED_RATIONAL
+ * @see #TYPE_UNSIGNED_SHORT
+ */
+ public static int getElementSize(short type) {
+ return TYPE_TO_SIZE_MAP[type];
}
/**
@@ -941,8 +163,12 @@ public class ExifTag {
return mIfd;
}
+ protected void setIfd(int ifdId) {
+ mIfd = ifdId;
+ }
+
/**
- * Gets the ID of this tag.
+ * Gets the TID of this tag.
*/
public short getTagId() {
return mTagId;
@@ -974,349 +200,589 @@ public class ExifTag {
/**
* Gets the component count of this tag.
*/
+
+ // TODO: fix integer overflows with this
public int getComponentCount() {
- return mComponentCount;
+ return mComponentCountActual;
}
/**
- * Sets the component count of this tag.
- * Call this function before setValue() if the length of value does not
- * match the component count.
+ * Sets the component count of this tag. Call this function before
+ * setValue() if the length of value does not match the component count.
*/
- public void setComponentCount(int count) {
- mComponentCount = count;
+ protected void forceSetComponentCount(int count) {
+ mComponentCountActual = count;
}
/**
- * 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()
+ * Returns true if this ExifTag contains value; otherwise, this tag will
+ * contain an offset value that is determined when the tag is written.
*/
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.
+ * Sets integer values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if:
+ * <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 component count in the definition
+ * for this tag.</li>
+ * </ul>
*/
- public int getOffset() {
- return mOffset;
+ public boolean setValue(int[] value) {
+ if (checkBadComponentCount(value.length)) {
+ return false;
+ }
+ if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
+ mDataType != TYPE_UNSIGNED_LONG) {
+ return false;
+ }
+ if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
+ return false;
+ } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
+ return false;
+ }
+
+ long[] data = new long[value.length];
+ System.arraycopy(value, 0, data, 0, value.length);
+ mValue = data;
+ mComponentCountActual = value.length;
+ return true;
}
/**
- * Sets the offset of this tag.
+ * Sets integer value into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method
+ * will fail if:
+ * <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 this tag is not 1.</li>
+ * </ul>
*/
- void setOffset(int offset) {
- mOffset = offset;
+ public boolean setValue(int value) {
+ return setValue(new int[] {
+ value
+ });
}
- 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)");
+ /**
+ * Sets long values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+ * <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 component count in the definition
+ * for this tag.</li>
+ * </ul>
+ */
+ public boolean setValue(long[] value) {
+ if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) {
+ return false;
+ }
+ if (checkOverflowForUnsignedLong(value)) {
+ return false;
}
+ mValue = value;
+ mComponentCountActual = value.length;
+ return true;
}
- private void throwTypeNotMatchedException(String className)
- throws IllegalArgumentException {
- throw new IllegalArgumentException("Tag " + mTagId + ": expect type " +
- convertTypeToString(mDataType) + " but got " + className);
+ /**
+ * Sets long values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+ * <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 for this tag is not 1.</li>
+ * </ul>
+ */
+ public boolean setValue(long value) {
+ return setValue(new long[] {
+ value
+ });
}
- 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 "";
+ /**
+ * Sets a string value into this tag. This method should be used for tags of
+ * type {@link #TYPE_ASCII}. The string is converted to an ASCII string.
+ * Characters that cannot be converted are replaced with '?'. The length of
+ * the string must be equal to either (component count -1) or (component
+ * count). The final byte will be set to the string null terminator '\0',
+ * overwriting the last character in the string if the value.length is equal
+ * to the component count. This method will fail if:
+ * <ul>
+ * <li>The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.</li>
+ * <li>The length of the string is not equal to (component count -1) or
+ * (component count) in the definition for this tag.</li>
+ * </ul>
+ */
+ public boolean setValue(String value) {
+ if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) {
+ return false;
}
- }
- 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");
- }
+ byte[] buf = value.getBytes(US_ASCII);
+ byte[] finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays
+ .copyOf(buf, buf.length + 1);
+ int count = finalBuf.length;
+ if (checkBadComponentCount(count)) {
+ return false;
}
+ mComponentCountActual = count;
+ mValue = finalBuf;
+ return true;
}
- 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");
- }
+ /**
+ * Sets Rational values into this tag. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+ * method will fail if:
+ * <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 component count in the definition
+ * for this tag.</li>
+ * </ul>
+ *
+ * @see Rational
+ */
+ public boolean setValue(Rational[] value) {
+ if (checkBadComponentCount(value.length)) {
+ return false;
}
- }
-
- 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");
- }
+ if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) {
+ return false;
}
- }
-
- 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");
- }
+ if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
+ return false;
+ } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
+ return false;
}
+
+ mValue = value;
+ mComponentCountActual = value.length;
+ return true;
}
- 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 a Rational value into this tag. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+ * method will fail if:
+ * <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 for this tag is not 1.</li>
+ * </ul>
+ *
+ * @see Rational
+ */
+ public boolean setValue(Rational value) {
+ return setValue(new Rational[] {
+ value
+ });
}
/**
- * Sets integer values into this tag.
- * @exception IllegalArgumentException for the following situation:
+ * Sets byte values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+ * will fail if:
* <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>
+ * <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 component count in the definition for
+ * this tag.</li>
* </ul>
*/
- public void setValue(int[] value) {
- checkComponentCountOrThrow(value.length);
- if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
- mDataType != TYPE_UNSIGNED_LONG) {
- throwTypeNotMatchedException("int");
+ public boolean setValue(byte[] value, int offset, int length) {
+ if (checkBadComponentCount(length)) {
+ return false;
}
- if (mDataType == TYPE_UNSIGNED_SHORT) {
- checkOverflowForUnsignedShort(value);
- } else if (mDataType == TYPE_UNSIGNED_LONG) {
- checkOverflowForUnsignedLong(value);
+ if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
+ return false;
}
+ mValue = new byte[length];
+ System.arraycopy(value, offset, mValue, 0, length);
+ mComponentCountActual = length;
+ return true;
+ }
- long[] data = new long[value.length];
- for (int i = 0; i < value.length; i++) {
- data[i] = value[i];
- }
- mValue = data;
- mComponentCount = value.length;
+ /**
+ * Equivalent to setValue(value, 0, value.length).
+ */
+ public boolean setValue(byte[] value) {
+ return setValue(value, 0, value.length);
}
/**
- * Sets integer values into this tag.
- * @exception IllegalArgumentException For the following situation:
+ * Sets byte value into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+ * will fail if:
* <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>
+ * <li>The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+ * {@link #TYPE_UNDEFINED} .</li>
+ * <li>The component count in the definition for this tag is not 1.</li>
* </ul>
*/
- public void setValue(int value) {
- checkComponentCountOrThrow(1);
- setValue(new int[] {value});
+ public boolean setValue(byte value) {
+ return setValue(new byte[] {
+ value
+ });
}
/**
- * Sets long values into this tag.
- * @exception IllegalArgumentException For the following situation:
+ * Sets the value for this tag using an appropriate setValue method for the
+ * given object. This method will fail if:
* <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>
+ * <li>The corresponding setValue method for the class of the object passed
+ * in would fail.</li>
+ * <li>There is no obvious way to cast the object passed in into an EXIF tag
+ * type.</li>
* </ul>
*/
- public void setValue(long[] value) {
- checkComponentCountOrThrow(value.length);
- if (mDataType != TYPE_UNSIGNED_LONG) {
- throwTypeNotMatchedException("long");
+ public boolean setValue(Object obj) {
+ if (obj == null) {
+ return false;
+ } else if (obj instanceof Short) {
+ return setValue(((Short) obj).shortValue() & 0x0ffff);
+ } else if (obj instanceof String) {
+ return setValue((String) obj);
+ } else if (obj instanceof int[]) {
+ return setValue((int[]) obj);
+ } else if (obj instanceof long[]) {
+ return setValue((long[]) obj);
+ } else if (obj instanceof Rational) {
+ return setValue((Rational) obj);
+ } else if (obj instanceof Rational[]) {
+ return setValue((Rational[]) obj);
+ } else if (obj instanceof byte[]) {
+ return setValue((byte[]) obj);
+ } else if (obj instanceof Integer) {
+ return setValue(((Integer) obj).intValue());
+ } else if (obj instanceof Long) {
+ return setValue(((Long) obj).longValue());
+ } else if (obj instanceof Byte) {
+ return setValue(((Byte) obj).byteValue());
+ } else if (obj instanceof Short[]) {
+ // Nulls in this array are treated as zeroes.
+ Short[] arr = (Short[]) obj;
+ int[] fin = new int[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff;
+ }
+ return setValue(fin);
+ } else if (obj instanceof Integer[]) {
+ // Nulls in this array are treated as zeroes.
+ Integer[] arr = (Integer[]) obj;
+ int[] fin = new int[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].intValue();
+ }
+ return setValue(fin);
+ } else if (obj instanceof Long[]) {
+ // Nulls in this array are treated as zeroes.
+ Long[] arr = (Long[]) obj;
+ long[] fin = new long[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].longValue();
+ }
+ return setValue(fin);
+ } else if (obj instanceof Byte[]) {
+ // Nulls in this array are treated as zeroes.
+ Byte[] arr = (Byte[]) obj;
+ byte[] fin = new byte[arr.length];
+ for (int i = 0; i < arr.length; i++) {
+ fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue();
+ }
+ return setValue(fin);
+ } else {
+ return false;
}
- checkOverflowForUnsignedLong(value);
- mValue = value;
- mComponentCount = value.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 component count in the definition of EXIF standard is not 1.</li>
- * </ul>
+ * Sets a timestamp to this tag. The method converts the timestamp with the
+ * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This
+ * method will fail if the data type is not {@link #TYPE_ASCII} or the
+ * component count of this tag is not 20 or undefined.
+ *
+ * @param time the number of milliseconds since Jan. 1, 1970 GMT
+ * @return true on success
*/
- public void setValue(long value) {
- setValue(new long[] {value});
+ public boolean setTimeValue(long time) {
+ // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
+ synchronized (TIME_FORMAT) {
+ return setValue(TIME_FORMAT.format(new Date(time)));
+ }
}
/**
- * Sets a string value into this tag. The value is treated as an ASCII string where we only
- * preserve the lower byte of each character. The length of the string should be equal
- * to either (component count -1) or (component count). A "0" byte will be appeneded while
- * written to the EXIF file. If the length equals (component count), the final byte will be
- * replaced by a "0" byte.
+ * Gets the value as a String. This method should be used for tags of type
+ * {@link #TYPE_ASCII}.
*
- * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII}
- * or the length of the string is not equal to (component count -1) and (component count)
- */
- public void setValue(String value) {
- if (mDataType != TYPE_ASCII) {
- throwTypeNotMatchedException("String");
+ * @return the value as a String, or null if the tag's value does not exist
+ * or cannot be converted to a String.
+ */
+ public String getValueAsString() {
+ if (mValue == null) {
+ return null;
+ } else if (mValue instanceof String) {
+ return (String) mValue;
+ } else if (mValue instanceof byte[]) {
+ return new String((byte[]) mValue, US_ASCII);
}
+ return null;
+ }
- byte[] buf = new byte[value.length()];
- for (int i = 0, n = value.length(); i < n; i++) {
- buf[i] = (byte) value.charAt(i);
+ /**
+ * Gets the value as a String. This method should be used for tags of type
+ * {@link #TYPE_ASCII}.
+ *
+ * @param defaultValue the String to return if the tag's value does not
+ * exist or cannot be converted to a String.
+ * @return the tag's value as a String, or the defaultValue.
+ */
+ public String getValueAsString(String defaultValue) {
+ String s = getValueAsString();
+ if (s == null) {
+ return defaultValue;
}
+ return s;
+ }
- int count = buf.length;
- if (mComponentCountDefined) {
- if (mComponentCount != count && mComponentCount != count + 1) {
- throw new IllegalArgumentException("Tag " + mTagId + ": Required "
- + mComponentCount + " or " + (mComponentCount + 1)
- + " components but was given " + count
- + " component(s)");
- }
- } else {
- mComponentCount = buf[count - 1] == 0 ? count : count + 1;
+ /**
+ * Gets the value as a byte array. This method should be used for tags of
+ * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+ *
+ * @return the value as a byte array, or null if the tag's value does not
+ * exist or cannot be converted to a byte array.
+ */
+ public byte[] getValueAsBytes() {
+ if (mValue instanceof byte[]) {
+ return (byte[]) mValue;
}
- mValue = buf;
+ return null;
}
/**
- * 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>
+ * Gets the value as a byte. If there are more than 1 bytes in this value,
+ * gets the first byte. This method should be used for tags of type
+ * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+ *
+ * @param defaultValue the byte to return if tag's value does not exist or
+ * cannot be converted to a byte.
+ * @return the tag's value as a byte, or the defaultValue.
+ */
+ public byte getValueAsByte(byte defaultValue) {
+ byte[] b = getValueAsBytes();
+ if (b == null || b.length < 1) {
+ return defaultValue;
+ }
+ return b[0];
+ }
+
+ /**
+ * Gets the value as an array of Rationals. This method should be used for
+ * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @return the value as as an array of Rationals, or null if the tag's value
+ * does not exist or cannot be converted to an array of Rationals.
*/
- public void setValue(Rational[] value) {
- if (mDataType == TYPE_UNSIGNED_RATIONAL) {
- checkOverflowForUnsignedRational(value);
- } else if (mDataType == TYPE_RATIONAL) {
- checkOverflowForRational(value);
- } else {
- throwTypeNotMatchedException("Rational");
+ public Rational[] getValueAsRationals() {
+ if (mValue instanceof Rational[]) {
+ return (Rational[]) mValue;
}
- checkComponentCountOrThrow(value.length);
- mValue = value;
- mComponentCount = value.length;
+ return null;
}
/**
- * 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) {
- setValue(new Rational[] {value});
+ * Gets the value as a Rational. If there are more than 1 Rationals in this
+ * value, gets the first one. This method should be used for tags of type
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @param defaultValue the Rational to return if tag's value does not exist
+ * or cannot be converted to a Rational.
+ * @return the tag's value as a Rational, or the defaultValue.
+ */
+ public Rational getValueAsRational(Rational defaultValue) {
+ Rational[] r = getValueAsRationals();
+ if (r == null || r.length < 1) {
+ return defaultValue;
+ }
+ return r[0];
}
/**
- * 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) {
- checkComponentCountOrThrow(length);
- if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) {
- throwTypeNotMatchedException("byte");
+ * Gets the value as a Rational. If there are more than 1 Rationals in this
+ * value, gets the first one. This method should be used for tags of type
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @param defaultValue the numerator of the Rational to return if tag's
+ * value does not exist or cannot be converted to a Rational (the
+ * denominator will be 1).
+ * @return the tag's value as a Rational, or the defaultValue.
+ */
+ public Rational getValueAsRational(long defaultValue) {
+ Rational defaultVal = new Rational(defaultValue, 1);
+ return getValueAsRational(defaultVal);
+ }
+
+ /**
+ * Gets the value as an array of ints. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @return the value as as an array of ints, or null if the tag's value does
+ * not exist or cannot be converted to an array of ints.
+ */
+ public int[] getValueAsInts() {
+ if (mValue == null) {
+ return null;
+ } else if (mValue instanceof long[]) {
+ long[] val = (long[]) mValue;
+ int[] arr = new int[val.length];
+ for (int i = 0; i < val.length; i++) {
+ arr[i] = (int) val[i]; // Truncates
+ }
+ return arr;
}
- mValue = new byte[length];
- System.arraycopy(value, offset, mValue, 0, length);
- mComponentCount = length;
+ return null;
}
/**
- * Equivalent to setValue(value, 0, value.length).
+ * Gets the value as an int. If there are more than 1 ints in this value,
+ * gets the first one. This method should be used for tags of type
+ * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @param defaultValue the int to return if tag's value does not exist or
+ * cannot be converted to an int.
+ * @return the tag's value as a int, or the defaultValue.
+ */
+ public int getValueAsInt(int defaultValue) {
+ int[] i = getValueAsInts();
+ if (i == null || i.length < 1) {
+ return defaultValue;
+ }
+ return i[0];
+ }
+
+ /**
+ * Gets the value as an array of longs. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @return the value as as an array of longs, or null if the tag's value
+ * does not exist or cannot be converted to an array of longs.
*/
- public void setValue(byte[] value) {
- setValue(value, 0, value.length);
+ public long[] getValueAsLongs() {
+ if (mValue instanceof long[]) {
+ return (long[]) mValue;
+ }
+ return null;
}
- private static final SimpleDateFormat TIME_FORMAT =
- new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
+ /**
+ * Gets the value or null if none exists. If there are more than 1 longs in
+ * this value, gets the first one. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @param defaultValue the long to return if tag's value does not exist or
+ * cannot be converted to a long.
+ * @return the tag's value as a long, or the defaultValue.
+ */
+ public long getValueAsLong(long defaultValue) {
+ long[] l = getValueAsLongs();
+ if (l == null || l.length < 1) {
+ return defaultValue;
+ }
+ return l[0];
+ }
/**
- * Sets a timestamp to this tag. The method converts the timestamp with the format of
- * "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}.
+ * Gets the tag's value or null if none exists.
+ */
+ public Object getValue() {
+ return mValue;
+ }
+
+ /**
+ * Gets a long representation of the value.
*
- * @param time the number of milliseconds since Jan. 1, 1970 GMT
- * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII}
- * or the component count of this tag is not 20 or undefined
+ * @param defaultValue value to return if there is no value or value is a
+ * rational with a denominator of 0.
+ * @return the tag's value as a long, or defaultValue if no representation
+ * exists.
+ */
+ public long forceGetValueAsLong(long defaultValue) {
+ long[] l = getValueAsLongs();
+ if (l != null && l.length >= 1) {
+ return l[0];
+ }
+ byte[] b = getValueAsBytes();
+ if (b != null && b.length >= 1) {
+ return b[0];
+ }
+ Rational[] r = getValueAsRationals();
+ if (r != null && r.length >= 1 && r[0].getDenominator() != 0) {
+ return (long) r[0].toDouble();
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Gets a string representation of the value.
*/
- public void setTimeValue(long time) {
- // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
- synchronized (TIME_FORMAT) {
- setValue(TIME_FORMAT.format(new Date(time)));
+ public String forceGetValueAsString() {
+ if (mValue == null) {
+ return "";
+ } else if (mValue instanceof byte[]) {
+ if (mDataType == TYPE_ASCII) {
+ return new String((byte[]) mValue, US_ASCII);
+ } else {
+ return Arrays.toString((byte[]) mValue);
+ }
+ } else if (mValue instanceof long[]) {
+ if (((long[]) mValue).length == 1) {
+ return String.valueOf(((long[]) mValue)[0]);
+ } else {
+ return Arrays.toString((long[]) mValue);
+ }
+ } else if (mValue instanceof Object[]) {
+ if (((Object[]) mValue).length == 1) {
+ Object val = ((Object[]) mValue)[0];
+ if (val == null) {
+ return "";
+ } else {
+ return val.toString();
+ }
+ } else {
+ return Arrays.toString((Object[]) mValue);
+ }
+ } else {
+ return mValue.toString();
}
}
+
/**
* Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG},
- * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE}, {@link #TYPE_UNSIGNED_LONG}, or
- * {@link #TYPE_UNSIGNED_SHORT}. For {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL},
- * call {@link #getRational(int)} instead.
+ * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call
+ * {@link #getRational(int)} instead.
*
- * @exception IllegalArgumentException if the data type is {@link #TYPE_RATIONAL} or
- * {@link #TYPE_UNSIGNED_RATIONAL}.
+ * @exception IllegalArgumentException if the data type is
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
*/
- public long getValueAt(int index) {
+ protected long getValueAt(int index) {
if (mValue instanceof long[]) {
- return ((long[]) mValue) [index];
+ return ((long[]) mValue)[index];
} else if (mValue instanceof byte[]) {
- return ((byte[]) mValue) [index];
+ return ((byte[]) mValue)[index];
}
throw new IllegalArgumentException("Cannot get integer value from "
+ convertTypeToString(mDataType));
@@ -1324,9 +790,11 @@ public class ExifTag {
/**
* Gets the {@link #TYPE_ASCII} data.
- * @exception IllegalArgumentException If the type is NOT {@link #TYPE_ASCII}.
+ *
+ * @exception IllegalArgumentException If the type is NOT
+ * {@link #TYPE_ASCII}.
*/
- public String getString() {
+ protected String getString() {
if (mDataType != TYPE_ASCII) {
throw new IllegalArgumentException("Cannot get ASCII value from "
+ convertTypeToString(mDataType));
@@ -1337,27 +805,28 @@ public class ExifTag {
/*
* Get the converted ascii byte. Used by ExifOutputStream.
*/
- byte[] getStringByte() {
+ protected byte[] getStringByte() {
return (byte[]) 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}.
+ *
+ * @exception IllegalArgumentException If the type is NOT
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
*/
- public Rational getRational(int index) {
+ protected Rational getRational(int index) {
if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) {
throw new IllegalArgumentException("Cannot get RATIONAL value from "
+ convertTypeToString(mDataType));
}
- return ((Rational[]) mValue) [index];
+ return ((Rational[]) mValue)[index];
}
/**
* Equivalent to getBytes(buffer, 0, buffer.length).
*/
- public void getBytes(byte[] buf) {
+ protected void getBytes(byte[] buf) {
getBytes(buf, 0, buf.length);
}
@@ -1366,56 +835,147 @@ public class ExifTag {
*
* @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}.
+ * @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) {
+ protected void getBytes(byte[] buf, int offset, int length) {
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);
+ (length > mComponentCountActual) ? mComponentCountActual : length);
}
/**
- * Returns true if the ID is one of the following: {@link #TAG_EXIF_IFD},
- * {@link #TAG_GPS_IFD}, {@link #TAG_JPEG_INTERCHANGE_FORMAT},
- * {@link #TAG_STRIP_OFFSETS}, {@link #TAG_INTEROPERABILITY_IFD}
+ * 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.
*/
- static boolean isOffsetTag(short tagId) {
- return tagId == TAG_EXIF_IFD
- || tagId == TAG_GPS_IFD
- || tagId == TAG_JPEG_INTERCHANGE_FORMAT
- || tagId == TAG_STRIP_OFFSETS
- || tagId == TAG_INTEROPERABILITY_IFD;
+ protected int getOffset() {
+ return mOffset;
}
/**
- * Returns true if the ID is one of the following: {@link #TAG_EXIF_IFD},
- * {@link #TAG_GPS_IFD}, {@link #TAG_INTEROPERABILITY_IFD}
+ * Sets the offset of this tag.
*/
- static boolean isSubIfdOffsetTag(short tagId) {
- return tagId == TAG_EXIF_IFD
- || tagId == TAG_GPS_IFD
- || tagId == TAG_INTEROPERABILITY_IFD;
+ protected void setOffset(int offset) {
+ mOffset = offset;
+ }
+
+ protected void setHasDefinedCount(boolean d) {
+ mHasDefinedDefaultComponentCount = d;
+ }
+
+ private boolean checkBadComponentCount(int count) {
+ if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) {
+ return true;
+ }
+ return false;
+ }
+
+ 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 boolean checkOverflowForUnsignedShort(int[] value) {
+ for (int v : value) {
+ if (v > UNSIGNED_SHORT_MAX || v < 0) {
+ return true;
+ }
+ }
+ return false;
}
+
+ private boolean checkOverflowForUnsignedLong(long[] value) {
+ for (long v : value) {
+ if (v < 0 || v > UNSIGNED_LONG_MAX) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForUnsignedLong(int[] value) {
+ for (int v : value) {
+ if (v < 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForUnsignedRational(Rational[] value) {
+ for (Rational v : value) {
+ if (v.getNumerator() < 0 || v.getDenominator() < 0
+ || v.getNumerator() > UNSIGNED_LONG_MAX
+ || v.getDenominator() > UNSIGNED_LONG_MAX) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForRational(Rational[] value) {
+ for (Rational v : value) {
+ if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN
+ || v.getNumerator() > LONG_MAX
+ || v.getDenominator() > LONG_MAX) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
if (obj instanceof ExifTag) {
ExifTag tag = (ExifTag) obj;
+ if (tag.mTagId != this.mTagId
+ || tag.mComponentCountActual != this.mComponentCountActual
+ || tag.mDataType != this.mDataType) {
+ return false;
+ }
if (mValue != null) {
- if (mValue instanceof long[]) {
- if (!(tag.mValue instanceof long[])) return false;
+ if (tag.mValue == null) {
+ return false;
+ } else if (mValue instanceof long[]) {
+ if (!(tag.mValue instanceof long[])) {
+ return false;
+ }
return Arrays.equals((long[]) mValue, (long[]) tag.mValue);
} else if (mValue instanceof Rational[]) {
- if (!(tag.mValue instanceof Rational[])) return false;
+ 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;
+ if (!(tag.mValue instanceof byte[])) {
+ return false;
+ }
return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue);
} else {
return mValue.equals(tag.mValue);
@@ -1426,4 +986,12 @@ public class ExifTag {
}
return false;
}
+
+ @Override
+ public String toString() {
+ return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: "
+ + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual
+ + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n";
+ }
+
}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/IfdData.java b/gallerycommon/src/com/android/gallery3d/exif/IfdData.java
index 6336049..093944a 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/IfdData.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/IfdData.java
@@ -30,7 +30,10 @@ class IfdData {
private final int mIfdId;
private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
private int mOffsetToNextIfd = 0;
-
+ private static final int[] sIfds = {
+ IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF,
+ IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS
+ };
/**
* Creates an IfdData with given IFD ID.
*
@@ -40,14 +43,18 @@ class IfdData {
* @see IfdId#TYPE_IFD_GPS
* @see IfdId#TYPE_IFD_INTEROPERABILITY
*/
- public IfdData(int ifdId) {
+ IfdData(int ifdId) {
mIfdId = ifdId;
}
+ static protected int[] getIfds() {
+ return sIfds;
+ }
+
/**
* Get a array the contains all {@link ExifTag} in this IFD.
*/
- public ExifTag[] getAllTags() {
+ protected ExifTag[] getAllTags() {
return mExifTags.values().toArray(new ExifTag[mExifTags.size()]);
}
@@ -60,70 +67,86 @@ class IfdData {
* @see IfdId#TYPE_IFD_GPS
* @see IfdId#TYPE_IFD_INTEROPERABILITY
*/
- public int getId() {
+ protected int getId() {
return mIfdId;
}
/**
- * Gets the {@link ExifTag} with given tag id. Return null if there is no such tag.
+ * Gets the {@link ExifTag} with given tag id. Return null if there is no
+ * such tag.
*/
- public ExifTag getTag(short tagId) {
+ protected ExifTag getTag(short tagId) {
return mExifTags.get(tagId);
}
/**
* Adds or replaces a {@link ExifTag}.
*/
- public void setTag(ExifTag tag) {
- mExifTags.put(tag.getTagId(), tag);
+ protected ExifTag setTag(ExifTag tag) {
+ tag.setIfd(mIfdId);
+ return mExifTags.put(tag.getTagId(), tag);
+ }
+
+ protected boolean checkCollision(short tagId) {
+ return mExifTags.get(tagId) != null;
}
/**
* Removes the tag of the given ID
*/
- public void removeTag(short tagId) {
+ protected void removeTag(short tagId) {
mExifTags.remove(tagId);
}
/**
* Gets the tags count in the IFD.
*/
- public int getTagCount() {
+ protected int getTagCount() {
return mExifTags.size();
}
/**
* Sets the offset of next IFD.
*/
- void setOffsetToNextIfd(int offset) {
+ protected void setOffsetToNextIfd(int offset) {
mOffsetToNextIfd = offset;
}
/**
* Gets the offset of next IFD.
*/
- int getOffsetToNextIfd() {
+ protected int getOffsetToNextIfd() {
return mOffsetToNextIfd;
}
/**
- * Returns true if all tags in this two IFDs are equal. Note that tags of IFDs offset or
- * thumbnail offset will be ignored.
+ * Returns true if all tags in this two IFDs are equal. Note that tags of
+ * IFDs offset or thumbnail offset will be ignored.
*/
@Override
public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
if (obj instanceof IfdData) {
IfdData data = (IfdData) obj;
if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) {
ExifTag[] tags = data.getAllTags();
- for (ExifTag tag: tags) {
- if (ExifTag.isOffsetTag(tag.getTagId())) continue;
+ for (ExifTag tag : tags) {
+ if (ExifInterface.isOffsetTag(tag.getTagId())) {
+ continue;
+ }
ExifTag tag2 = mExifTags.get(tag.getTagId());
- if (!tag.equals(tag2)) return false;
+ if (!tag.equals(tag2)) {
+ return false;
+ }
}
return true;
}
}
return false;
}
-} \ No newline at end of file
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/IfdId.java b/gallerycommon/src/com/android/gallery3d/exif/IfdId.java
index c05fb02..7842edb 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/IfdId.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/IfdId.java
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.gallery3d.exif;
/**
@@ -26,4 +27,5 @@ public interface IfdId {
public static final int TYPE_IFD_GPS = 4;
/* This is used in ExifData to allocate enough IfdData */
static final int TYPE_IFD_COUNT = 5;
+
}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
index 4f785a8..428e6b9 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
@@ -29,24 +29,28 @@ class OrderedDataOutputStream extends FilterOutputStream {
super(out);
}
- public void setByteOrder(ByteOrder order) {
+ public OrderedDataOutputStream setByteOrder(ByteOrder order) {
mByteBuffer.order(order);
+ return this;
}
- public void writeShort(short value) throws IOException {
+ public OrderedDataOutputStream writeShort(short value) throws IOException {
mByteBuffer.rewind();
mByteBuffer.putShort(value);
out.write(mByteBuffer.array(), 0, 2);
- }
+ return this;
+ }
- public void writeInt(int value) throws IOException {
+ public OrderedDataOutputStream writeInt(int value) throws IOException {
mByteBuffer.rewind();
mByteBuffer.putInt(value);
out.write(mByteBuffer.array());
+ return this;
}
- public void writeRational(Rational rational) throws IOException {
- writeInt((int) rational.getNominator());
+ public OrderedDataOutputStream writeRational(Rational rational) throws IOException {
+ writeInt((int) rational.getNumerator());
writeInt((int) rational.getDenominator());
+ return this;
}
}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/Rational.java b/gallerycommon/src/com/android/gallery3d/exif/Rational.java
index 3f8c18a..591d63f 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/Rational.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/Rational.java
@@ -17,50 +17,72 @@
package com.android.gallery3d.exif;
/**
- * The rational data type of EXIF tag.
+ * The rational data type of EXIF tag. Contains a pair of longs representing the
+ * numerator and denominator of a Rational number.
*/
public class Rational {
- private final long mNominator;
+ private final long mNumerator;
private final long mDenominator;
+ /**
+ * Create a Rational with a given numerator and denominator.
+ *
+ * @param nominator
+ * @param denominator
+ */
public Rational(long nominator, long denominator) {
- mNominator = nominator;
+ mNumerator = nominator;
mDenominator = denominator;
}
- /*
- * Gets the nominator of the rational.
+ /**
+ * Create a copy of a Rational.
+ */
+ public Rational(Rational r) {
+ mNumerator = r.mNumerator;
+ mDenominator = r.mDenominator;
+ }
+
+ /**
+ * Gets the numerator of the rational.
*/
- public long getNominator() {
- return mNominator;
+ public long getNumerator() {
+ return mNumerator;
}
- /*
+ /**
* Gets the denominator of the rational
*/
public long getDenominator() {
return mDenominator;
}
+ /**
+ * Gets the rational value as type double. Will cause a divide-by-zero error
+ * if the denominator is 0.
+ */
+ public double toDouble() {
+ return mNumerator / (double) mDenominator;
+ }
+
@Override
public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
if (obj instanceof Rational) {
Rational data = (Rational) obj;
- return mNominator == data.mNominator && mDenominator == data.mDenominator;
+ return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
}
return false;
}
@Override
public String toString() {
- return mNominator + "/" + mDenominator;
- }
-
- /*
- * Gets the rational value as type double.
- */
- public double toDouble() {
- return mNominator / (double) mDenominator;
+ return mNumerator + "/" + mDenominator;
}
-} \ No newline at end of file
+}