diff options
Diffstat (limited to 'gallerycommon/src/com/android/gallery3d')
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) { |