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/exif/ExifData.java36
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java11
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifParser.java90
-rw-r--r--gallerycommon/src/com/android/gallery3d/exif/ExifTag.java115
4 files changed, 194 insertions, 58 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
index 39eb57455..7f7971384 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java
@@ -216,7 +216,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 +225,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 +270,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..46cd65594 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
@@ -215,9 +215,12 @@ 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;
@@ -234,7 +237,7 @@ 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;
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java
index f1e52c5b3..b25cd2068 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,8 @@ 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 final CountedDataInputStream mTiffStream;
private final int mOptions;
private int mIfdStartOffset = 0;
@@ -225,22 +229,36 @@ 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);
+ }
}
}
}
@@ -290,8 +308,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);
@@ -455,6 +474,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) {
@@ -610,29 +635,35 @@ 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) {
+ 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 +681,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 +779,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/ExifTag.java b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java
index 49cb6edbc..157a8db3b 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;
@@ -833,6 +834,7 @@ public class ExifTag {
(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;
}
/**
@@ -1311,7 +1348,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;
}
/**
@@ -1354,23 +1398,61 @@ public class ExifTag {
(length > mComponentCount) ? mComponentCount : length);
}
+ private String undefinedTypeValueToString() {
+ StringBuilder sbuilder = new StringBuilder();
+ byte buf[] = (byte[]) mValue;
+ switch (mTagId) {
+ case TAG_COMPONENTS_CONFIGURATION:
+ for(int i = 0, n = getComponentCount(); i < n; i++) {
+ if(i != 0) sbuilder.append(" ");
+ sbuilder.append(buf[i]);
+ }
+ break;
+ default:
+ if (buf.length == 1) {
+ sbuilder.append(buf[0]);
+ } else {
+ for (int i = 0, n = buf.length; i < n; i++) {
+ byte code = buf[i];
+ if (code == 0) continue;
+ if (code > 31 && code < 127) {
+ sbuilder.append((char) code);
+ } else {
+ sbuilder.append('.');
+ }
+ }
+ }
+ }
+ return sbuilder.toString();
+ }
+
/**
* Returns a string representation of the value of this tag.
*/
- public String valueToString() {
+ String valueToString() {
StringBuilder sbuilder = new StringBuilder();
switch (getDataType()) {
case ExifTag.TYPE_UNDEFINED:
+ sbuilder.append(undefinedTypeValueToString());
+ break;
case ExifTag.TYPE_UNSIGNED_BYTE:
- byte buf[] = new byte[getComponentCount()];
- getBytes(buf);
+ byte buf[] = (byte[]) mValue;
for(int i = 0, n = getComponentCount(); i < n; i++) {
if(i != 0) sbuilder.append(" ");
- sbuilder.append(String.format("%02x", buf[i]));
+ sbuilder.append(buf[i]);
}
break;
case ExifTag.TYPE_ASCII:
- sbuilder.append(getString());
+ buf = (byte[]) mValue;
+ for (int i = 0, n = buf.length; i < n; i++) {
+ byte code = buf[i];
+ if (code == 0) break;
+ if (code > 31 && code < 127) {
+ sbuilder.append((char) code);
+ } else {
+ sbuilder.append('.');
+ }
+ }
break;
case ExifTag.TYPE_UNSIGNED_LONG:
for(int i = 0, n = getComponentCount(); i < n; i++) {
@@ -1415,6 +1497,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) {