summaryrefslogtreecommitdiffstats
path: root/gallerycommon/src/com/android/gallery3d
diff options
context:
space:
mode:
Diffstat (limited to 'gallerycommon/src/com/android/gallery3d')
-rw-r--r--gallerycommon/src/com/android/gallery3d/common/ApiHelper.java9
-rw-r--r--gallerycommon/src/com/android/gallery3d/common/ExifTags.java27
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifData.java95
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java21
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifParser.java183
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifReader.java22
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifTag.java168
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/Rational.java9
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/Util.java34
9 files changed, 328 insertions, 240 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
index 837777e51..56adcb1e9 100644
--- a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
+++ b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java
@@ -170,6 +170,15 @@ public class ApiHelper {
public static final boolean HAS_POST_ON_ANIMATION =
Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+ public static final boolean HAS_ANNOUNCE_FOR_ACCESSIBILITY =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+
+ public static final boolean HAS_OBJECT_ANIMATION =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
+ public static final boolean HAS_GLES20_REQUIRED =
+ Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB;
+
public static int getIntFieldIfExists(Class<?> klass, String fieldName,
Class<?> obj, int defaultVal) {
try {
diff --git a/gallerycommon/src/com/android/gallery3d/common/ExifTags.java b/gallerycommon/src/com/android/gallery3d/common/ExifTags.java
deleted file mode 100644
index 9b11fe416..000000000
--- a/gallerycommon/src/com/android/gallery3d/common/ExifTags.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.common;
-
-/**
- * The class holds the EXIF tag names that are not available in
- * {@link android.media.ExifInterface} prior to API level 11.
- */
-public interface ExifTags {
- static final String TAG_ISO = "ISOSpeedRatings";
- static final String TAG_EXPOSURE_TIME = "ExposureTime";
- static final String TAG_APERTURE = "FNumber";
-}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
index 39eb57455..6e5c227d5 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
@@ -17,8 +17,12 @@
package com.android.gallery3d.exif;
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;
/**
* This class stores the EXIF header in IFDs according to the JPEG specification.
@@ -27,6 +31,17 @@ import java.util.Arrays;
* @see IfdData
*/
public class 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 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"));
+
private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
private byte[] mThumbnail;
private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
@@ -34,6 +49,7 @@ public class ExifData {
public ExifData(ByteOrder order) {
mByteOrder = order;
+ mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
IfdData getIfdData(int ifdId) {
@@ -127,7 +143,9 @@ public class ExifData {
}
for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
- if (!Util.equals(data.getIfdData(i), getIfdData(i))) return false;
+ IfdData ifd1 = data.getIfdData(i);
+ IfdData ifd2 = getIfdData(i);
+ if ((ifd1 != ifd2) && (ifd1 != null && !ifd1.equals(ifd2))) return false;
}
return true;
}
@@ -135,8 +153,9 @@ public class ExifData {
}
/**
- * Adds {@link ExifTag#TAG_GPS_LATITUDE}, {@link ExifTag#TAG_GPS_LONGITUDE},
- * {@link ExifTag#TAG_GPS_LATITUDE_REF} and {@link ExifTag#TAG_GPS_LONGITUDE_REF} with the
+ * 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.
*/
public void addGpsTags(double latitude, double longitude) {
@@ -167,6 +186,40 @@ public class ExifData {
gpsIfd.setTag(longRefTag);
}
+ /**
+ * 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.
+ *
+ */
+ 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));
+ }
+ }
+
+ /**
+ * 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.
+ */
+ 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);
@@ -216,7 +269,8 @@ public class ExifData {
}
/**
- * Adds a tag with the given tag ID. The original tag will be replaced by the new tag. For tags
+ * Adds a tag with the given tag ID. If the tag of the given ID already exists,
+ * the original tag will be returned. Otherwise, a new ExifTag will be created. For tags
* related to interoperability or thumbnail, call {@link #addInteroperabilityTag(short)} or
* {@link #addThumbnailTag(short)} respectively.
* @exception IllegalArgumentException if the tag ID is invalid.
@@ -224,32 +278,43 @@ public class ExifData {
public ExifTag addTag(short tagId) {
int ifdId = ExifTag.getIfdIdFromTagId(tagId);
IfdData ifdData = getOrCreateIfdData(ifdId);
- ExifTag tag = ExifTag.buildTag(tagId);
- ifdData.setTag(tag);
+ ExifTag tag = ifdData.getTag(tagId);
+ if (tag == null) {
+ tag = ExifTag.buildTag(tagId);
+ ifdData.setTag(tag);
+ }
return tag;
}
/**
- * Adds a thumbnail-related tag with the given tag ID. The original tag will be replaced
- * by the new tag.
+ * Adds a thumbnail-related tag with the given tag ID. If the tag of the given ID
+ * already exists, the original tag will be returned. Otherwise, a new ExifTag will
+ * be created.
* @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);
+ ExifTag tag = ifdData.getTag(tagId);
+ if (tag == null) {
+ tag = ExifTag.buildThumbnailTag(tagId);
+ ifdData.setTag(tag);
+ }
return tag;
}
/**
- * Adds an interoperability-related tag with the given tag ID. The original tag will be
- * replaced by the new tag.
+ * Adds an interoperability-related tag with the given tag ID. If the tag of the given ID
+ * already exists, the original tag will be returned. Otherwise, a new ExifTag will
+ * be created.
* @exception IllegalArgumentException if the tag ID is invalid.
*/
public ExifTag addInteroperabilityTag(short tagId) {
IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
- ExifTag tag = ExifTag.buildInteroperabilityTag(tagId);
- ifdData.setTag(tag);
+ ExifTag tag = ifdData.getTag(tagId);
+ if (tag == null) {
+ tag = ExifTag.buildInteroperabilityTag(tagId);
+ ifdData.setTag(tag);
+ }
return tag;
}
@@ -258,4 +323,4 @@ public class ExifData {
mStripBytes.clear();
mIfdDatas[IfdId.TYPE_IFD_1] = null;
}
-} \ No newline at end of file
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
index b8db8e34c..51a30ffa2 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
@@ -215,15 +215,19 @@ public class ExifOutputStream extends FilterOutputStream {
throws IOException {
switch (tag.getDataType()) {
case ExifTag.TYPE_ASCII:
- dataOutputStream.write(tag.getString().getBytes());
- int remain = tag.getComponentCount() - tag.getString().length();
- for (int i = 0; i < remain; i++) {
+ 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(tag.getLong(i));
+ dataOutputStream.writeInt((int) tag.getValueAt(i));
}
break;
case ExifTag.TYPE_RATIONAL:
@@ -234,18 +238,13 @@ public class ExifOutputStream extends FilterOutputStream {
break;
case ExifTag.TYPE_UNDEFINED:
case ExifTag.TYPE_UNSIGNED_BYTE:
- byte[] buf = new byte[tag.getComponentCount()];
+ buf = new byte[tag.getComponentCount()];
tag.getBytes(buf);
dataOutputStream.write(buf);
break;
- case ExifTag.TYPE_UNSIGNED_LONG:
- for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- dataOutputStream.writeInt((int) tag.getUnsignedLong(i));
- }
- break;
case ExifTag.TYPE_UNSIGNED_SHORT:
for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
- dataOutputStream.writeShort((short) tag.getUnsignedShort(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 f1e52c5b3..2cff12a3d 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
@@ -16,8 +16,9 @@
package com.android.gallery3d.exif;
+import android.util.Log;
+
import java.io.DataInputStream;
-import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
@@ -64,6 +65,7 @@ import java.util.TreeMap;
* </pre>
*/
public class ExifParser {
+ private static final String TAG = "ExifParser";
/**
* When the parser reaches a new IFD area. Call
* {@link #getCurrentIfd()} to know which IFD we are in.
@@ -133,6 +135,10 @@ public class ExifParser {
private static final int TAG_SIZE = 12;
private static final int OFFSET_SIZE = 2;
+ private static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+ private static final int DEFAULT_IFD0_OFFSET = 8;
+
private final CountedDataInputStream mTiffStream;
private final int mOptions;
private int mIfdStartOffset = 0;
@@ -145,6 +151,9 @@ public class ExifParser {
private ExifTag mJpegSizeTag;
private boolean mNeedToParseOffsetsInCurrentIfd;
private boolean mContainExifData = false;
+ private int mApp1End;
+ private byte[] mDataAboveIfd0;
+ private int mIfd0Position;
private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
@@ -174,10 +183,17 @@ public class ExifParser {
mTiffStream = new CountedDataInputStream(inputStream);
mOptions = options;
if (!mContainExifData) return;
- if (mTiffStream.getReadByteCount() == 0) {
- parseTiffHeader();
- long offset = mTiffStream.readUnsignedInt();
- registerIfd(IfdId.TYPE_IFD_0, offset);
+
+ parseTiffHeader();
+ long offset = mTiffStream.readUnsignedInt();
+ if (offset > Integer.MAX_VALUE) {
+ throw new ExifInvalidFormatException("Invalid offset " + offset);
+ }
+ mIfd0Position = (int) offset;
+ registerIfd(IfdId.TYPE_IFD_0, offset);
+ if (offset != DEFAULT_IFD0_OFFSET) {
+ mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET];
+ read(mDataAboveIfd0);
}
}
@@ -225,33 +241,59 @@ public class ExifParser {
int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
if (offset < endOfTags) {
mTag = readTag();
+ if (mTag == null) {
+ return next();
+ }
if (mNeedToParseOffsetsInCurrentIfd) {
checkOffsetOrImageTag(mTag);
}
return EVENT_NEW_TAG;
} else if (offset == endOfTags) {
- long ifdOffset = readUnsignedLong();
// There is a link to ifd1 at the end of ifd0
if (mIfdType == IfdId.TYPE_IFD_0) {
+ long ifdOffset = readUnsignedLong();
if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) {
if (ifdOffset != 0) {
registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
}
}
} else {
- if (ifdOffset != 0) {
- throw new ExifInvalidFormatException("Invalid link to next IFD");
+ int offsetSize = 4;
+ // Some camera models use invalid length of the offset
+ if (mCorrespondingEvent.size() > 0) {
+ offsetSize = mCorrespondingEvent.firstEntry().getKey() -
+ mTiffStream.getReadByteCount();
+ }
+ if (offsetSize < 4) {
+ Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize);
+ } else {
+ long ifdOffset = readUnsignedLong();
+ if (ifdOffset != 0) {
+ Log.w(TAG, "Invalid link to next IFD: " + ifdOffset);
+ }
}
}
}
while(mCorrespondingEvent.size() != 0) {
Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
Object event = entry.getValue();
- skipTo(entry.getKey());
+ try {
+ skipTo(entry.getKey());
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to skip to data at: " + entry.getKey() +
+ " for " + event.getClass().getName() + ", the file may be broken.");
+ continue;
+ }
if (event instanceof IfdEvent) {
mIfdType = ((IfdEvent) event).ifd;
mNumOfTagInIfd = mTiffStream.readUnsignedShort();
mIfdStartOffset = entry.getKey();
+
+ if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) {
+ Log.w(TAG, "Invalid size of IFD " + mIfdType);
+ return EVENT_END;
+ }
+
mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
if (((IfdEvent) event).isRequested) {
return EVENT_START_OF_IFD;
@@ -290,8 +332,9 @@ public class ExifParser {
if (mNeedToParseOffsetsInCurrentIfd) {
while (offset < endOfTags) {
mTag = readTag();
- checkOffsetOrImageTag(mTag);
offset += TAG_SIZE;
+ if (mTag == null) continue;
+ checkOffsetOrImageTag(mTag);
}
} else {
skipTo(endOfTags);
@@ -342,7 +385,6 @@ public class ExifParser {
* @see #read(byte[], int, int)
* @see #readLong()
* @see #readRational()
- * @see #readShort()
* @see #readString(int)
* @see #readString(int, Charset)
*/
@@ -393,13 +435,7 @@ public class ExifParser {
*/
public int getStripSize() {
if (mStripSizeTag == null) return 0;
- if (mStripSizeTag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
- return mStripSizeTag.getUnsignedShort(mImageEvent.stripIndex);
- } else {
- // Cast unsigned int to int since the strip size is always smaller
- // than the size of APP1 (65536)
- return (int) mStripSizeTag.getUnsignedLong(mImageEvent.stripIndex);
- }
+ return (int) mStripSizeTag.getValueAt(0);
}
/**
@@ -408,9 +444,7 @@ public class ExifParser {
*/
public int getCompressedImageSize() {
if (mJpegSizeTag == null) return 0;
- // Cast unsigned int to int since the thumbnail is always smaller
- // than the size of APP1 (65536)
- return (int) mJpegSizeTag.getUnsignedLong(0);
+ return (int) mJpegSizeTag.getValueAt(0);
}
private void skipTo(int offset) throws IOException {
@@ -425,7 +459,6 @@ public class ExifParser {
* 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) {
@@ -455,6 +488,12 @@ public class ExifParser {
throw new ExifInvalidFormatException(
"Number of component is larger then Integer.MAX_VALUE");
}
+ // Some invalid image file contains invalid data type. Ignore those tags
+ if (!ExifTag.isValidType(dataFormat)) {
+ Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat));
+ mTiffStream.skip(4);
+ return null;
+ }
ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType);
int dataSize = tag.getDataSize();
if (dataSize > 4) {
@@ -463,7 +502,16 @@ public class ExifParser {
throw new ExifInvalidFormatException(
"offset is larger then Integer.MAX_VALUE");
}
- tag.setOffset((int) offset);
+ // Some invalid images put some undefined data before IFD0.
+ // Read the data here.
+ if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) {
+ byte[] buf = new byte[(int) numOfComp];
+ System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET,
+ buf, 0, (int) numOfComp);
+ tag.setValue(buf);
+ } else {
+ tag.setOffset((int) offset);
+ }
} else {
readFullTagValue(tag);
mTiffStream.skip(4 - dataSize);
@@ -476,26 +524,30 @@ public class ExifParser {
* 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.getUnsignedLong(0));
+ 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.getUnsignedLong(0));
+ 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.getUnsignedLong(0));
+ registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0));
}
break;
case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT:
if (isThumbnailRequested()) {
- registerCompressedImage(tag.getUnsignedLong(0));
+ registerCompressedImage(tag.getValueAt(0));
}
break;
case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
@@ -508,9 +560,9 @@ public class ExifParser {
if (tag.hasValue()) {
for (int i = 0; i < tag.getComponentCount(); i++) {
if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
- registerUncompressedStrip(i, tag.getUnsignedShort(i));
+ registerUncompressedStrip(i, tag.getValueAt(i));
} else {
- registerUncompressedStrip(i, tag.getUnsignedLong(i));
+ registerUncompressedStrip(i, tag.getValueAt(i));
}
}
} else {
@@ -528,7 +580,22 @@ public class ExifParser {
}
}
- private void readFullTagValue(ExifTag tag) throws IOException {
+ 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);
+ }
+ }
+ }
switch(tag.getDataType()) {
case ExifTag.TYPE_UNSIGNED_BYTE:
case ExifTag.TYPE_UNDEFINED:
@@ -610,29 +677,36 @@ public class ExifParser {
ExifInvalidFormatException {
DataInputStream dataStream = new DataInputStream(inputStream);
- // SOI and APP1
if (dataStream.readShort() != JpegHeader.SOI) {
throw new ExifInvalidFormatException("Invalid JPEG format");
}
short marker = dataStream.readShort();
- while(marker != JpegHeader.APP1 && marker != JpegHeader.EOI
+ while(marker != JpegHeader.EOI
&& !JpegHeader.isSofMarker(marker)) {
int length = dataStream.readUnsignedShort();
- if ((length - 2) != dataStream.skip(length - 2)) {
- throw new EOFException();
+ // Some invalid formatted image contains multiple APP1,
+ // try to find the one with Exif data.
+ if (marker == JpegHeader.APP1) {
+ int header = 0;
+ short headerTail = 0;
+ if (length >= 8) {
+ header = dataStream.readInt();
+ headerTail = dataStream.readShort();
+ length -= 6;
+ if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
+ mApp1End = length;
+ return true;
+ }
+ }
+ }
+ if (length < 2 || (length - 2) != dataStream.skip(length - 2)) {
+ Log.w(TAG, "Invalid JPEG format.");
+ return false;
}
marker = dataStream.readShort();
}
-
- if (marker != JpegHeader.APP1) return false; // No APP1 segment
-
- // APP1 length, it's not used for us
- dataStream.readShort();
-
- // Exif header
- return (dataStream.readInt() == EXIF_HEADER
- && dataStream.readShort() == EXIF_HEADER_TAIL);
+ return false;
}
/**
@@ -650,27 +724,26 @@ public class ExifParser {
}
/**
- * Reads a String from the InputStream with UTF8 charset.
+ * 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 {
- if (n > 0) {
- byte[] buf = new byte[n];
- mTiffStream.readOrThrow(buf);
- return new String(buf, 0, n - 1, "UTF8");
- } else {
- return "";
- }
+ 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}.
*/
public String readString(int n, Charset charset) throws IOException {
- byte[] buf = new byte[n];
- mTiffStream.readOrThrow(buf);
- return new String(buf, 0, n - 1, charset);
+ if (n > 0) {
+ byte[] buf = new byte[n];
+ return mTiffStream.readString(n, charset);
+ } else {
+ return "";
+ }
}
/**
@@ -749,4 +822,4 @@ public class ExifParser {
public ByteOrder getByteOrder() {
return mTiffStream.getByteOrder();
}
-} \ No newline at end of file
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java
index d8083b2dd..5bce9c496 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java
@@ -16,6 +16,8 @@
package com.android.gallery3d.exif;
+import android.util.Log;
+
import java.io.IOException;
import java.io.InputStream;
@@ -23,6 +25,7 @@ import java.io.InputStream;
* This class reads the EXIF header of a JPEG file and stores it in {@link ExifData}.
*/
public class ExifReader {
+ private static final String TAG = "ExifReader";
/**
* Parses the inputStream and and returns the EXIF data in an {@link ExifData}.
* @throws ExifInvalidFormatException
@@ -50,25 +53,28 @@ public class ExifReader {
case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
tag = parser.getTag();
if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
- byte[] buf = new byte[tag.getComponentCount()];
- parser.read(buf);
- tag.setValue(buf);
+ parser.readFullTagValue(tag);
}
exifData.getIfdData(tag.getIfd()).setTag(tag);
break;
case ExifParser.EVENT_COMPRESSED_IMAGE:
byte buf[] = new byte[parser.getCompressedImageSize()];
- parser.read(buf);
- exifData.setCompressedThumbnail(buf);
+ if (buf.length == parser.read(buf)) {
+ exifData.setCompressedThumbnail(buf);
+ } else {
+ Log.w(TAG, "Failed to read the compressed thumbnail");
+ }
break;
case ExifParser.EVENT_UNCOMPRESSED_STRIP:
buf = new byte[parser.getStripSize()];
- parser.read(buf);
- exifData.setStripBytes(parser.getStripIndex(), buf);
+ if (buf.length == parser.read(buf)) {
+ exifData.setStripBytes(parser.getStripIndex(), buf);
+ Log.w(TAG, "Failed to read the strip bytes");
+ }
break;
}
event = parser.next();
}
return exifData;
}
-} \ No newline at end of file
+}
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
index 49cb6edbc..cda67c2e2 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
@@ -18,6 +18,7 @@ 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;
@@ -156,7 +157,7 @@ public class ExifTag {
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_DATA_STAMP = 29;
+ public static final short TAG_GPS_DATE_STAMP = 29;
public static final short TAG_GPS_DIFFERENTIAL = 30;
// Interoperability tag
@@ -827,12 +828,13 @@ public class ExifTag {
(IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
sTagInfo.put(TAG_GPS_AREA_INFORMATION,
(IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED);
- sTagInfo.put(TAG_GPS_DATA_STAMP,
+ 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);
}
+ private static Charset US_ASCII = Charset.forName("US-ASCII");
private final short mTagId;
private final short mDataType;
private final int mIfd;
@@ -911,6 +913,13 @@ public class ExifTag {
IfdId.TYPE_IFD_INTEROPERABILITY);
}
+ 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) {
mTagId = tagId;
mDataType = type;
@@ -970,6 +979,15 @@ public class ExifTag {
}
/**
+ * 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;
+ }
+
+ /**
* 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.
*
@@ -1171,18 +1189,37 @@ public class ExifTag {
}
/**
- * Sets string values into this tag.
+ * 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.
+ *
* @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII}
- * or value.length() + 1 does NOT fit the definition of the component count in the
- * EXIF standard.
+ * or the length of the string is not equal to (component count -1) and (component count)
*/
public void setValue(String value) {
- checkComponentCountOrThrow(value.length() + 1);
if (mDataType != TYPE_ASCII) {
throwTypeNotMatchedException("String");
}
- mComponentCount = value.length() + 1;
- mValue = value;
+
+ byte[] buf = new byte[value.length()];
+ for (int i = 0, n = value.length(); i < n; i++) {
+ buf[i] = (byte) value.charAt(i);
+ }
+
+ 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;
+ }
+ mValue = buf;
}
/**
@@ -1249,7 +1286,8 @@ public class ExifTag {
setValue(value, 0, value.length);
}
- private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
+ private static final SimpleDateFormat TIME_FORMAT =
+ new SimpleDateFormat("yyyy:MM:dd kk:mm:ss");
/**
* Sets a timestamp to this tag. The method converts the timestamp with the format of
@@ -1265,41 +1303,23 @@ public class ExifTag {
setValue(TIME_FORMAT.format(new Date(time)));
}
}
-
- /**
- * Gets the {@link #TYPE_UNSIGNED_SHORT} data.
- * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_SHORT}.
- */
- public int getUnsignedShort(int index) {
- if (mDataType != TYPE_UNSIGNED_SHORT) {
- throw new IllegalArgumentException("Cannot get UNSIGNED_SHORT value from "
- + convertTypeToString(mDataType));
- }
- return (int) (((long[]) mValue) [index]);
- }
-
- /**
- * Gets the {@link #TYPE_LONG} data.
- * @exception IllegalArgumentException If the type is NOT {@link #TYPE_LONG}.
- */
- public int getLong(int index) {
- if (mDataType != TYPE_LONG) {
- throw new IllegalArgumentException("Cannot get LONG value from "
- + convertTypeToString(mDataType));
- }
- return (int) (((long[]) mValue) [index]);
- }
-
/**
- * Gets the {@link #TYPE_UNSIGNED_LONG} data.
- * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_LONG}.
+ * 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.
+ *
+ * @exception IllegalArgumentException if the data type is {@link #TYPE_RATIONAL} or
+ * {@link #TYPE_UNSIGNED_RATIONAL}.
*/
- public long getUnsignedLong(int index) {
- if (mDataType != TYPE_UNSIGNED_LONG) {
- throw new IllegalArgumentException("Cannot get UNSIGNED LONG value from "
- + convertTypeToString(mDataType));
+ public long getValueAt(int index) {
+ if (mValue instanceof long[]) {
+ return ((long[]) mValue) [index];
+ } else if (mValue instanceof byte[]) {
+ return ((byte[]) mValue) [index];
}
- return ((long[]) mValue) [index];
+ throw new IllegalArgumentException("Cannot get integer value from "
+ + convertTypeToString(mDataType));
}
/**
@@ -1311,7 +1331,14 @@ public class ExifTag {
throw new IllegalArgumentException("Cannot get ASCII value from "
+ convertTypeToString(mDataType));
}
- return (String) mValue;
+ return new String((byte[]) mValue, US_ASCII);
+ }
+
+ /*
+ * Get the converted ascii byte. Used by ExifOutputStream.
+ */
+ byte[] getStringByte() {
+ return (byte[]) mValue;
}
/**
@@ -1355,54 +1382,6 @@ public class ExifTag {
}
/**
- * Returns a string representation of the value of this tag.
- */
- public String valueToString() {
- StringBuilder sbuilder = new StringBuilder();
- switch (getDataType()) {
- case ExifTag.TYPE_UNDEFINED:
- case ExifTag.TYPE_UNSIGNED_BYTE:
- byte buf[] = new byte[getComponentCount()];
- getBytes(buf);
- for(int i = 0, n = getComponentCount(); i < n; i++) {
- if(i != 0) sbuilder.append(" ");
- sbuilder.append(String.format("%02x", buf[i]));
- }
- break;
- case ExifTag.TYPE_ASCII:
- sbuilder.append(getString());
- break;
- case ExifTag.TYPE_UNSIGNED_LONG:
- for(int i = 0, n = getComponentCount(); i < n; i++) {
- if(i != 0) sbuilder.append(" ");
- sbuilder.append(getUnsignedLong(i));
- }
- break;
- case ExifTag.TYPE_RATIONAL:
- case ExifTag.TYPE_UNSIGNED_RATIONAL:
- for(int i = 0, n = getComponentCount(); i < n; i++) {
- Rational r = getRational(i);
- if(i != 0) sbuilder.append(" ");
- sbuilder.append(r.getNominator()).append("/").append(r.getDenominator());
- }
- break;
- case ExifTag.TYPE_UNSIGNED_SHORT:
- for(int i = 0, n = getComponentCount(); i < n; i++) {
- if(i != 0) sbuilder.append(" ");
- sbuilder.append(getUnsignedShort(i));
- }
- break;
- case ExifTag.TYPE_LONG:
- for(int i = 0, n = getComponentCount(); i < n; i++) {
- if(i != 0) sbuilder.append(" ");
- sbuilder.append(getLong(i));
- }
- break;
- }
- return sbuilder.toString();
- }
-
- /**
* 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}
@@ -1415,6 +1394,15 @@ public class ExifTag {
|| tagId == TAG_INTEROPERABILITY_IFD;
}
+ /**
+ * Returns true if the ID is one of the following: {@link #TAG_EXIF_IFD},
+ * {@link #TAG_GPS_IFD}, {@link #TAG_INTEROPERABILITY_IFD}
+ */
+ static boolean isSubIfdOffsetTag(short tagId) {
+ return tagId == TAG_EXIF_IFD
+ || tagId == TAG_GPS_IFD
+ || tagId == TAG_INTEROPERABILITY_IFD;
+ }
@Override
public boolean equals(Object obj) {
if (obj instanceof ExifTag) {
diff --git a/gallerycommon/src/com/android/gallery3d/exif/Rational.java b/gallerycommon/src/com/android/gallery3d/exif/Rational.java
index 7d9026261..202c5d46d 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/Rational.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/Rational.java
@@ -42,4 +42,13 @@ public class Rational {
}
return false;
}
+
+ @Override
+ public String toString() {
+ return mNominator + "/" + mDenominator;
+ }
+
+ public double toDouble() {
+ return mNominator / (double) mDenominator;
+ }
} \ No newline at end of file
diff --git a/gallerycommon/src/com/android/gallery3d/exif/Util.java b/gallerycommon/src/com/android/gallery3d/exif/Util.java
deleted file mode 100644
index 594d6fc7f..000000000
--- a/gallerycommon/src/com/android/gallery3d/exif/Util.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.gallery3d.exif;
-
-import java.io.Closeable;
-
-class Util {
- public static boolean equals(Object a, Object b) {
- return (a == b) || (a == null ? false : a.equals(b));
- }
-
- public static void closeSilently(Closeable c) {
- if (c == null) return;
- try {
- c.close();
- } catch (Throwable t) {
- // do nothing
- }
- }
-}