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