diff options
| author | 2016-04-07 00:45:51 +0900 | |
|---|---|---|
| committer | 2016-04-15 23:27:21 +0000 | |
| commit | 0e7d22535d4b3d3b9e6f520c5837b2d52629529c (patch) | |
| tree | 82990f7d7f930e91afd5f93a87cb08aafda4f315 | |
| parent | dd3d44faf9d74a793458a515fddf153432601dd6 (diff) | |
ExifInterface: keep the original metadata data instead of strings
This CL makes ExifInterface store the tag values as the original forms
and the format validiation is added that compares the given value and
the data format specificed in EXIF specification in order to keep the
valid tag values only.
Bug: 27583378, Bug: 27614052, Bug: 28075709
Change-Id: If60bbddefe74c4b87b4ce64b5fc79e467e36a5b9
| -rw-r--r-- | api/current.txt | 4 | ||||
| -rw-r--r-- | api/system-current.txt | 4 | ||||
| -rw-r--r-- | api/test-current.txt | 4 | ||||
| -rw-r--r-- | media/java/android/media/ExifInterface.java | 1539 | ||||
| -rw-r--r-- | media/jni/android_media_ExifInterface.cpp | 10 | ||||
| -rw-r--r-- | media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java | 12 |
6 files changed, 980 insertions, 593 deletions
diff --git a/api/current.txt b/api/current.txt index f924b5b8c54a..07b664d1370e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -20260,9 +20260,9 @@ package android.media { field public static final java.lang.String TAG_SUBJECT_DISTANCE_RANGE = "SubjectDistanceRange"; field public static final java.lang.String TAG_SUBJECT_LOCATION = "SubjectLocation"; field public static final java.lang.String TAG_SUBSEC_TIME = "SubSecTime"; - field public static final java.lang.String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; + field public static final deprecated java.lang.String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; field public static final java.lang.String TAG_SUBSEC_TIME_DIGITIZED = "SubSecTimeDigitized"; - field public static final java.lang.String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; + field public static final deprecated java.lang.String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; field public static final java.lang.String TAG_SUBSEC_TIME_ORIGINAL = "SubSecTimeOriginal"; field public static final java.lang.String TAG_THUMBNAIL_IMAGE_LENGTH = "ThumbnailImageLength"; field public static final java.lang.String TAG_THUMBNAIL_IMAGE_WIDTH = "ThumbnailImageWidth"; diff --git a/api/system-current.txt b/api/system-current.txt index 915ceb20a6a5..cc98426a19e9 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -21741,9 +21741,9 @@ package android.media { field public static final java.lang.String TAG_SUBJECT_DISTANCE_RANGE = "SubjectDistanceRange"; field public static final java.lang.String TAG_SUBJECT_LOCATION = "SubjectLocation"; field public static final java.lang.String TAG_SUBSEC_TIME = "SubSecTime"; - field public static final java.lang.String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; + field public static final deprecated java.lang.String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; field public static final java.lang.String TAG_SUBSEC_TIME_DIGITIZED = "SubSecTimeDigitized"; - field public static final java.lang.String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; + field public static final deprecated java.lang.String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; field public static final java.lang.String TAG_SUBSEC_TIME_ORIGINAL = "SubSecTimeOriginal"; field public static final java.lang.String TAG_THUMBNAIL_IMAGE_LENGTH = "ThumbnailImageLength"; field public static final java.lang.String TAG_THUMBNAIL_IMAGE_WIDTH = "ThumbnailImageWidth"; diff --git a/api/test-current.txt b/api/test-current.txt index 0c86fd1a32e5..89c09cb36e2b 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -20328,9 +20328,9 @@ package android.media { field public static final java.lang.String TAG_SUBJECT_DISTANCE_RANGE = "SubjectDistanceRange"; field public static final java.lang.String TAG_SUBJECT_LOCATION = "SubjectLocation"; field public static final java.lang.String TAG_SUBSEC_TIME = "SubSecTime"; - field public static final java.lang.String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; + field public static final deprecated java.lang.String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; field public static final java.lang.String TAG_SUBSEC_TIME_DIGITIZED = "SubSecTimeDigitized"; - field public static final java.lang.String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; + field public static final deprecated java.lang.String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; field public static final java.lang.String TAG_SUBSEC_TIME_ORIGINAL = "SubSecTimeOriginal"; field public static final java.lang.String TAG_THUMBNAIL_IMAGE_LENGTH = "ThumbnailImageLength"; field public static final java.lang.String TAG_THUMBNAIL_IMAGE_WIDTH = "ThumbnailImageWidth"; diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 72f57425e8fe..4848630baacf 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -24,20 +24,22 @@ import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import android.util.Log; +import android.util.Pair; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.Charset; import java.text.ParsePosition; @@ -45,9 +47,11 @@ import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TimeZone; +import java.util.regex.Matcher; import java.util.regex.Pattern; import libcore.io.IoUtils; @@ -188,7 +192,7 @@ public class ExifInterface { public static final String TAG_FOCAL_PLANE_X_RESOLUTION = "FocalPlaneXResolution"; /** Type is rational. */ public static final String TAG_FOCAL_PLANE_Y_RESOLUTION = "FocalPlaneYResolution"; - /** Type is rational. */ + /** Type is int. */ public static final String TAG_GAIN_CONTROL = "GainControl"; /** Type is int. */ public static final String TAG_ISO_SPEED_RATINGS = "ISOSpeedRatings"; @@ -233,15 +237,23 @@ public class ExifInterface { public static final String TAG_SPATIAL_FREQUENCY_RESPONSE = "SpatialFrequencyResponse"; /** Type is String. */ public static final String TAG_SPECTRAL_SENSITIVITY = "SpectralSensitivity"; - /** Type is int. */ + /** Type is String. */ public static final String TAG_SUBSEC_TIME = "SubSecTime"; - /** Type is int. */ - public static final String TAG_SUBSEC_TIME_DIGITIZED = "SubSecTimeDigitized"; - /** Type is int. */ + /** + * Type is String. + * + * @deprecated use {@link #TAG_SUBSEC_TIME_DIGITIZED} instead + */ public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized"; - /** Type is int. */ + /** Type is String. */ + public static final String TAG_SUBSEC_TIME_DIGITIZED = "SubSecTimeDigitized"; + /** + * Type is String. + * + * @deprecated use {@link #TAG_SUBSEC_TIME_ORIGINAL} instead + */ public static final String TAG_SUBSEC_TIME_ORIG = "SubSecTimeOriginal"; - /** Type is int. */ + /** Type is String. */ public static final String TAG_SUBSEC_TIME_ORIGINAL = "SubSecTimeOriginal"; /** Type is int. */ public static final String TAG_SUBJECT_AREA = "SubjectArea"; @@ -293,11 +305,11 @@ public class ExifInterface { public static final String TAG_GPS_IMG_DIRECTION = "GPSImgDirection"; /** Type is String. */ public static final String TAG_GPS_IMG_DIRECTION_REF = "GPSImgDirectionRef"; - /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */ + /** Type is rational. Format is "num1/denom1,num2/denom2,num3/denom3". */ public static final String TAG_GPS_LATITUDE = "GPSLatitude"; /** Type is String. */ public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef"; - /** String. Format is "num1/denom1,num2/denom2,num3/denom3". */ + /** Type is rational. Format is "num1/denom1,num2/denom2,num3/denom3". */ public static final String TAG_GPS_LONGITUDE = "GPSLongitude"; /** Type is String. */ public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef"; @@ -315,7 +327,7 @@ public class ExifInterface { public static final String TAG_GPS_SPEED_REF = "GPSSpeedRef"; /** Type is String. */ public static final String TAG_GPS_STATUS = "GPSStatus"; - /** Type is String. */ + /** Type is String. Format is "hh:mm:ss". */ public static final String TAG_GPS_TIMESTAMP = "GPSTimeStamp"; /** Type is rational. */ public static final String TAG_GPS_TRACK = "GPSTrack"; @@ -336,10 +348,10 @@ public class ExifInterface { private static final String TAG_INTEROPERABILITY_IFD_POINTER = "InteroperabilityIFDPointer"; // Private tags used for thumbnail information. - private static final String TAG_HAS_THUMBNAIL = "hasThumbnail"; - private static final String TAG_THUMBNAIL_OFFSET = "thumbnailOffset"; - private static final String TAG_THUMBNAIL_LENGTH = "thumbnailLength"; - private static final String TAG_THUMBNAIL_DATA = "thumbnailData"; + private static final String TAG_HAS_THUMBNAIL = "HasThumbnail"; + private static final String TAG_THUMBNAIL_OFFSET = "ThumbnailOffset"; + private static final String TAG_THUMBNAIL_LENGTH = "ThumbnailLength"; + private static final String TAG_THUMBNAIL_DATA = "ThumbnailData"; // Constants used for the Orientation Exif tag. public static final int ORIENTATION_UNDEFINED = 0; @@ -385,6 +397,11 @@ public class ExifInterface { private static final int IFD_FORMAT_SRATIONAL = 10; private static final int IFD_FORMAT_SINGLE = 11; private static final int IFD_FORMAT_DOUBLE = 12; + // Names for the data formats for debugging purpose. + private static final String[] IFD_FORMAT_NAMES = new String[] { + "", "BYTE", "STRING", "USHORT", "ULONG", "URATIONAL", "SBYTE", "UNDEFINED", "SSHORT", + "SLONG", "SRATIONAL", "SINGLE", "DOUBLE" + }; // Sizes of the components of each IFD value format private static final int[] IFD_FORMAT_BYTES_PER_FORMAT = new int[] { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 @@ -393,184 +410,579 @@ public class ExifInterface { 0x41, 0x53, 0x43, 0x49, 0x49, 0x0, 0x0, 0x0 }; + // A class for indicating EXIF rational type. + private static class Rational { + public final long numerator; + public final long denominator; + + private Rational(long numerator, long denominator) { + // Handle erroneous case + if (denominator == 0) { + this.numerator = 0; + this.denominator = 1; + return; + } + this.numerator = numerator; + this.denominator = denominator; + } + + @Override + public String toString() { + return numerator + "/" + denominator; + } + + public double calculate() { + return (double) numerator / denominator; + } + } + + // A class for indicating EXIF attribute. + private static class ExifAttribute { + public final int format; + public final int numberOfComponents; + public final byte[] bytes; + + private ExifAttribute(int format, int numberOfComponents, byte[] bytes) { + this.format = format; + this.numberOfComponents = numberOfComponents; + this.bytes = bytes; + } + + public static ExifAttribute createUShort(int[] values, ByteOrder byteOrder) { + final ByteBuffer buffer = ByteBuffer.wrap( + new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_USHORT] * values.length]); + buffer.order(byteOrder); + for (int value : values) { + buffer.putShort((short) value); + } + return new ExifAttribute(IFD_FORMAT_USHORT, values.length, buffer.array()); + } + + public static ExifAttribute createUShort(int value, ByteOrder byteOrder) { + return createUShort(new int[] {value}, byteOrder); + } + + public static ExifAttribute createULong(long[] values, ByteOrder byteOrder) { + final ByteBuffer buffer = ByteBuffer.wrap( + new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_ULONG] * values.length]); + buffer.order(byteOrder); + for (long value : values) { + buffer.putInt((int) value); + } + return new ExifAttribute(IFD_FORMAT_ULONG, values.length, buffer.array()); + } + + public static ExifAttribute createULong(long value, ByteOrder byteOrder) { + return createULong(new long[] {value}, byteOrder); + } + + public static ExifAttribute createSLong(int[] values, ByteOrder byteOrder) { + final ByteBuffer buffer = ByteBuffer.wrap( + new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_SLONG] * values.length]); + buffer.order(byteOrder); + for (int value : values) { + buffer.putInt(value); + } + return new ExifAttribute(IFD_FORMAT_SLONG, values.length, buffer.array()); + } + + public static ExifAttribute createSLong(int value, ByteOrder byteOrder) { + return createSLong(new int[] {value}, byteOrder); + } + + public static ExifAttribute createByte(String value) { + // Exception for GPSAltitudeRef tag + if (value.length() == 1 && value.charAt(0) >= '0' && value.charAt(0) <= '1') { + final byte[] bytes = new byte[] { (byte) (value.charAt(0) - '0') }; + return new ExifAttribute(IFD_FORMAT_BYTE, bytes.length, bytes); + } + final byte[] ascii = value.getBytes(ASCII); + return new ExifAttribute(IFD_FORMAT_BYTE, ascii.length, ascii); + } + + public static ExifAttribute createString(String value) { + final byte[] ascii = (value + '\0').getBytes(ASCII); + return new ExifAttribute(IFD_FORMAT_STRING, ascii.length, ascii); + } + + public static ExifAttribute createURational(Rational[] values, ByteOrder byteOrder) { + final ByteBuffer buffer = ByteBuffer.wrap( + new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_URATIONAL] * values.length]); + buffer.order(byteOrder); + for (Rational value : values) { + buffer.putInt((int) value.numerator); + buffer.putInt((int) value.denominator); + } + return new ExifAttribute(IFD_FORMAT_URATIONAL, values.length, buffer.array()); + } + + public static ExifAttribute createURational(Rational value, ByteOrder byteOrder) { + return createURational(new Rational[] {value}, byteOrder); + } + + public static ExifAttribute createSRational(Rational[] values, ByteOrder byteOrder) { + final ByteBuffer buffer = ByteBuffer.wrap( + new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_SRATIONAL] * values.length]); + buffer.order(byteOrder); + for (Rational value : values) { + buffer.putInt((int) value.numerator); + buffer.putInt((int) value.denominator); + } + return new ExifAttribute(IFD_FORMAT_SRATIONAL, values.length, buffer.array()); + } + + public static ExifAttribute createSRational(Rational value, ByteOrder byteOrder) { + return createSRational(new Rational[] {value}, byteOrder); + } + + public static ExifAttribute createDouble(double[] values, ByteOrder byteOrder) { + final ByteBuffer buffer = ByteBuffer.wrap( + new byte[IFD_FORMAT_BYTES_PER_FORMAT[IFD_FORMAT_DOUBLE] * values.length]); + buffer.order(byteOrder); + for (double value : values) { + buffer.putDouble(value); + } + return new ExifAttribute(IFD_FORMAT_DOUBLE, values.length, buffer.array()); + } + + public static ExifAttribute createDouble(double value, ByteOrder byteOrder) { + return createDouble(new double[] {value}, byteOrder); + } + + @Override + public String toString() { + return "(" + IFD_FORMAT_NAMES[format] + ", data length:" + bytes.length + ")"; + } + + private Object getValue(ByteOrder byteOrder) { + try { + ByteOrderAwarenessDataInputStream inputStream = + new ByteOrderAwarenessDataInputStream(bytes); + inputStream.setByteOrder(byteOrder); + switch (format) { + case IFD_FORMAT_BYTE: + case IFD_FORMAT_SBYTE: { + // Exception for GPSAltitudeRef tag + if (bytes.length == 1 && bytes[0] >= 0 && bytes[0] <= 1) { + return new String(new char[] { (char) (bytes[0] + '0') }); + } + return new String(bytes, ASCII); + } + case IFD_FORMAT_UNDEFINED: + case IFD_FORMAT_STRING: { + int index = 0; + if (numberOfComponents >= EXIF_ASCII_PREFIX.length) { + boolean same = true; + for (int i = 0; i < EXIF_ASCII_PREFIX.length; ++i) { + if (bytes[i] != EXIF_ASCII_PREFIX[i]) { + same = false; + break; + } + } + if (same) { + index = EXIF_ASCII_PREFIX.length; + } + } + + StringBuilder stringBuilder = new StringBuilder(); + while (index < numberOfComponents) { + int ch = bytes[index]; + if (ch == 0) { + break; + } + if (ch >= 32) { + stringBuilder.append((char) ch); + } else { + stringBuilder.append('?'); + } + ++index; + } + return stringBuilder.toString(); + } + case IFD_FORMAT_USHORT: { + final int[] values = new int[numberOfComponents]; + for (int i = 0; i < numberOfComponents; ++i) { + values[i] = inputStream.readUnsignedShort(); + } + return values; + } + case IFD_FORMAT_ULONG: { + final long[] values = new long[numberOfComponents]; + for (int i = 0; i < numberOfComponents; ++i) { + values[i] = inputStream.readUnsignedInt(); + } + return values; + } + case IFD_FORMAT_URATIONAL: { + final Rational[] values = new Rational[numberOfComponents]; + for (int i = 0; i < numberOfComponents; ++i) { + final long numerator = inputStream.readUnsignedInt(); + final long denominator = inputStream.readUnsignedInt(); + values[i] = new Rational(numerator, denominator); + } + return values; + } + case IFD_FORMAT_SSHORT: { + final int[] values = new int[numberOfComponents]; + for (int i = 0; i < numberOfComponents; ++i) { + values[i] = inputStream.readShort(); + } + return values; + } + case IFD_FORMAT_SLONG: { + final int[] values = new int[numberOfComponents]; + for (int i = 0; i < numberOfComponents; ++i) { + values[i] = inputStream.readInt(); + } + return values; + } + case IFD_FORMAT_SRATIONAL: { + final Rational[] values = new Rational[numberOfComponents]; + for (int i = 0; i < numberOfComponents; ++i) { + final long numerator = inputStream.readInt(); + final long denominator = inputStream.readInt(); + values[i] = new Rational(numerator, denominator); + } + return values; + } + case IFD_FORMAT_SINGLE: { + final double[] values = new double[numberOfComponents]; + for (int i = 0; i < numberOfComponents; ++i) { + values[i] = inputStream.readFloat(); + } + return values; + } + case IFD_FORMAT_DOUBLE: { + final double[] values = new double[numberOfComponents]; + for (int i = 0; i < numberOfComponents; ++i) { + values[i] = inputStream.readDouble(); + } + return values; + } + default: + return null; + } + } catch (IOException e) { + Log.w(TAG, "IOException occurred during reading a value", e); + return null; + } + } + + public double getDoubleValue(ByteOrder byteOrder) { + Object value = getValue(byteOrder); + if (value == null) { + throw new NumberFormatException("NULL can't be converted to a double value"); + } + if (value instanceof String) { + return Double.parseDouble((String) value); + } + if (value instanceof long[]) { + long[] array = (long[]) value; + if (array.length == 1) { + return (double) array[0]; + } + throw new NumberFormatException("There are more than one component"); + } + if (value instanceof int[]) { + int[] array = (int[]) value; + if (array.length == 1) { + return (double) array[0]; + } + throw new NumberFormatException("There are more than one component"); + } + if (value instanceof double[]) { + double[] array = (double[]) value; + if (array.length == 1) { + return array[0]; + } + throw new NumberFormatException("There are more than one component"); + } + if (value instanceof Rational[]) { + Rational[] array = (Rational[]) value; + if (array.length == 1) { + return array[0].calculate(); + } + throw new NumberFormatException("There are more than one component"); + } + throw new NumberFormatException("Couldn't find a double value"); + } + + public int getIntValue(ByteOrder byteOrder) { + Object value = getValue(byteOrder); + if (value == null) { + throw new NumberFormatException("NULL can't be converted to a integer value"); + } + if (value instanceof String) { + return Integer.parseInt((String) value); + } + if (value instanceof long[]) { + long[] array = (long[]) value; + if (array.length == 1) { + return (int) array[0]; + } + throw new NumberFormatException("There are more than one component"); + } + if (value instanceof int[]) { + int[] array = (int[]) value; + if (array.length == 1) { + return array[0]; + } + throw new NumberFormatException("There are more than one component"); + } + throw new NumberFormatException("Couldn't find a integer value"); + } + + public String getStringValue(ByteOrder byteOrder) { + Object value = getValue(byteOrder); + if (value == null) { + return null; + } + if (value instanceof String) { + return (String) value; + } + + final StringBuilder stringBuilder = new StringBuilder(); + if (value instanceof long[]) { + long[] array = (long[]) value; + for (int i = 0; i < array.length; ++i) { + stringBuilder.append(array[i]); + if (i + 1 != array.length) { + stringBuilder.append(","); + } + } + return stringBuilder.toString(); + } + if (value instanceof int[]) { + int[] array = (int[]) value; + for (int i = 0; i < array.length; ++i) { + stringBuilder.append(array[i]); + if (i + 1 != array.length) { + stringBuilder.append(","); + } + } + return stringBuilder.toString(); + } + if (value instanceof double[]) { + double[] array = (double[]) value; + for (int i = 0; i < array.length; ++i) { + stringBuilder.append(array[i]); + if (i + 1 != array.length) { + stringBuilder.append(","); + } + } + return stringBuilder.toString(); + } + if (value instanceof Rational[]) { + Rational[] array = (Rational[]) value; + for (int i = 0; i < array.length; ++i) { + stringBuilder.append(array[i].numerator); + stringBuilder.append('/'); + stringBuilder.append(array[i].denominator); + if (i + 1 != array.length) { + stringBuilder.append(","); + } + } + return stringBuilder.toString(); + } + return null; + } + + public int size() { + return IFD_FORMAT_BYTES_PER_FORMAT[format] * numberOfComponents; + } + } + // A class for indicating EXIF tag. private static class ExifTag { public final int number; public final String name; + public final int primaryFormat; + public final int secondaryFormat; - private ExifTag(String name, int number) { + private ExifTag(String name, int number, int format) { this.name = name; this.number = number; + this.primaryFormat = format; + this.secondaryFormat = -1; + } + + private ExifTag(String name, int number, int primaryFormat, int secondaryFormat) { + this.name = name; + this.number = number; + this.primaryFormat = primaryFormat; + this.secondaryFormat = secondaryFormat; } } // Primary image IFD TIFF tags (See JEITA CP-3451 Table 14. page 54). private static final ExifTag[] IFD_TIFF_TAGS = new ExifTag[] { - new ExifTag(TAG_IMAGE_WIDTH, 256), - new ExifTag(TAG_IMAGE_LENGTH, 257), - new ExifTag(TAG_BITS_PER_SAMPLE, 258), - new ExifTag(TAG_COMPRESSION, 259), - new ExifTag(TAG_PHOTOMETRIC_INTERPRETATION, 262), - new ExifTag(TAG_IMAGE_DESCRIPTION, 270), - new ExifTag(TAG_MAKE, 271), - new ExifTag(TAG_MODEL, 272), - new ExifTag(TAG_STRIP_OFFSETS, 273), - new ExifTag(TAG_ORIENTATION, 274), - new ExifTag(TAG_SAMPLES_PER_PIXEL, 277), - new ExifTag(TAG_ROWS_PER_STRIP, 278), - new ExifTag(TAG_STRIP_BYTE_COUNTS, 279), - new ExifTag(TAG_X_RESOLUTION, 282), - new ExifTag(TAG_Y_RESOLUTION, 283), - new ExifTag(TAG_PLANAR_CONFIGURATION, 284), - new ExifTag(TAG_RESOLUTION_UNIT, 296), - new ExifTag(TAG_TRANSFER_FUNCTION, 301), - new ExifTag(TAG_SOFTWARE, 305), - new ExifTag(TAG_DATETIME, 306), - new ExifTag(TAG_ARTIST, 315), - new ExifTag(TAG_WHITE_POINT, 318), - new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319), - new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513), - new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514), - new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529), - new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530), - new ExifTag(TAG_Y_CB_CR_POSITIONING, 531), - new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532), - new ExifTag(TAG_COPYRIGHT, 33432), - new ExifTag(TAG_EXIF_IFD_POINTER, 34665), - new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853), + new ExifTag(TAG_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_BITS_PER_SAMPLE, 258, IFD_FORMAT_USHORT), + new ExifTag(TAG_COMPRESSION, 259, IFD_FORMAT_USHORT), + new ExifTag(TAG_PHOTOMETRIC_INTERPRETATION, 262, IFD_FORMAT_USHORT), + new ExifTag(TAG_IMAGE_DESCRIPTION, 270, IFD_FORMAT_STRING), + new ExifTag(TAG_MAKE, 271, IFD_FORMAT_STRING), + new ExifTag(TAG_MODEL, 272, IFD_FORMAT_STRING), + new ExifTag(TAG_STRIP_OFFSETS, 273, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_ORIENTATION, 274, IFD_FORMAT_USHORT), + new ExifTag(TAG_SAMPLES_PER_PIXEL, 277, IFD_FORMAT_USHORT), + new ExifTag(TAG_ROWS_PER_STRIP, 278, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_STRIP_BYTE_COUNTS, 279, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_X_RESOLUTION, 282, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_Y_RESOLUTION, 283, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_PLANAR_CONFIGURATION, 284, IFD_FORMAT_USHORT), + new ExifTag(TAG_RESOLUTION_UNIT, 296, IFD_FORMAT_USHORT), + new ExifTag(TAG_TRANSFER_FUNCTION, 301, IFD_FORMAT_USHORT), + new ExifTag(TAG_SOFTWARE, 305, IFD_FORMAT_STRING), + new ExifTag(TAG_DATETIME, 306, IFD_FORMAT_STRING), + new ExifTag(TAG_ARTIST, 315, IFD_FORMAT_STRING), + new ExifTag(TAG_WHITE_POINT, 318, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG), + new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG), + new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530, IFD_FORMAT_USHORT), + new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT), + new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING), + new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG), + new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG), }; + // Primary image IFD Exif Private tags (See JEITA CP-3451 Table 15. page 55). private static final ExifTag[] IFD_EXIF_TAGS = new ExifTag[] { - new ExifTag(TAG_EXPOSURE_TIME, 33434), - new ExifTag(TAG_F_NUMBER, 33437), - new ExifTag(TAG_EXPOSURE_PROGRAM, 34850), - new ExifTag(TAG_SPECTRAL_SENSITIVITY, 34852), - new ExifTag(TAG_ISO_SPEED_RATINGS, 34855), - new ExifTag(TAG_OECF, 34856), - new ExifTag(TAG_EXIF_VERSION, 36864), - new ExifTag(TAG_DATETIME_ORIGINAL, 36867), - new ExifTag(TAG_DATETIME_DIGITIZED, 36868), - new ExifTag(TAG_COMPONENTS_CONFIGURATION, 37121), - new ExifTag(TAG_COMPRESSED_BITS_PER_PIXEL, 37122), - new ExifTag(TAG_SHUTTER_SPEED_VALUE, 37377), - new ExifTag(TAG_APERTURE_VALUE, 37378), - new ExifTag(TAG_BRIGHTNESS_VALUE, 37379), - new ExifTag(TAG_EXPOSURE_BIAS_VALUE, 37380), - new ExifTag(TAG_MAX_APERTURE_VALUE, 37381), - new ExifTag(TAG_SUBJECT_DISTANCE, 37382), - new ExifTag(TAG_METERING_MODE, 37383), - new ExifTag(TAG_LIGHT_SOURCE, 37384), - new ExifTag(TAG_FLASH, 37385), - new ExifTag(TAG_FOCAL_LENGTH, 37386), - new ExifTag(TAG_SUBJECT_AREA, 37396), - new ExifTag(TAG_MAKER_NOTE, 37500), - new ExifTag(TAG_USER_COMMENT, 37510), - new ExifTag(TAG_SUBSEC_TIME, 37520), - new ExifTag(TAG_SUBSEC_TIME_ORIG, 37521), - new ExifTag(TAG_SUBSEC_TIME_DIG, 37522), - new ExifTag(TAG_FLASHPIX_VERSION, 40960), - new ExifTag(TAG_COLOR_SPACE, 40961), - new ExifTag(TAG_PIXEL_X_DIMENSION, 40962), - new ExifTag(TAG_PIXEL_Y_DIMENSION, 40963), - new ExifTag(TAG_RELATED_SOUND_FILE, 40964), - new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965), - new ExifTag(TAG_FLASH_ENERGY, 41483), - new ExifTag(TAG_SPATIAL_FREQUENCY_RESPONSE, 41484), - new ExifTag(TAG_FOCAL_PLANE_X_RESOLUTION, 41486), - new ExifTag(TAG_FOCAL_PLANE_Y_RESOLUTION, 41487), - new ExifTag(TAG_FOCAL_PLANE_RESOLUTION_UNIT, 41488), - new ExifTag(TAG_SUBJECT_LOCATION, 41492), - new ExifTag(TAG_EXPOSURE_INDEX, 41493), - new ExifTag(TAG_SENSING_METHOD, 41495), - new ExifTag(TAG_FILE_SOURCE, 41728), - new ExifTag(TAG_SCENE_TYPE, 41729), - new ExifTag(TAG_CFA_PATTERN, 41730), - new ExifTag(TAG_CUSTOM_RENDERED, 41985), - new ExifTag(TAG_EXPOSURE_MODE, 41986), - new ExifTag(TAG_WHITE_BALANCE, 41987), - new ExifTag(TAG_DIGITAL_ZOOM_RATIO, 41988), - new ExifTag(TAG_FOCAL_LENGTH_IN_35MM_FILM, 41989), - new ExifTag(TAG_SCENE_CAPTURE_TYPE, 41990), - new ExifTag(TAG_GAIN_CONTROL, 41991), - new ExifTag(TAG_CONTRAST, 41992), - new ExifTag(TAG_SATURATION, 41993), - new ExifTag(TAG_SHARPNESS, 41994), - new ExifTag(TAG_DEVICE_SETTING_DESCRIPTION, 41995), - new ExifTag(TAG_SUBJECT_DISTANCE_RANGE, 41996), - new ExifTag(TAG_IMAGE_UNIQUE_ID, 42016), + new ExifTag(TAG_EXPOSURE_TIME, 33434, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_F_NUMBER, 33437, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_EXPOSURE_PROGRAM, 34850, IFD_FORMAT_USHORT), + new ExifTag(TAG_SPECTRAL_SENSITIVITY, 34852, IFD_FORMAT_STRING), + new ExifTag(TAG_ISO_SPEED_RATINGS, 34855, IFD_FORMAT_USHORT), + new ExifTag(TAG_OECF, 34856, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_EXIF_VERSION, 36864, IFD_FORMAT_STRING), + new ExifTag(TAG_DATETIME_ORIGINAL, 36867, IFD_FORMAT_STRING), + new ExifTag(TAG_DATETIME_DIGITIZED, 36868, IFD_FORMAT_STRING), + new ExifTag(TAG_COMPONENTS_CONFIGURATION, 37121, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_COMPRESSED_BITS_PER_PIXEL, 37122, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_SHUTTER_SPEED_VALUE, 37377, IFD_FORMAT_SRATIONAL), + new ExifTag(TAG_APERTURE_VALUE, 37378, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_BRIGHTNESS_VALUE, 37379, IFD_FORMAT_SRATIONAL), + new ExifTag(TAG_EXPOSURE_BIAS_VALUE, 37380, IFD_FORMAT_SRATIONAL), + new ExifTag(TAG_MAX_APERTURE_VALUE, 37381, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_SUBJECT_DISTANCE, 37382, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_METERING_MODE, 37383, IFD_FORMAT_USHORT), + new ExifTag(TAG_LIGHT_SOURCE, 37384, IFD_FORMAT_USHORT), + new ExifTag(TAG_FLASH, 37385, IFD_FORMAT_USHORT), + new ExifTag(TAG_FOCAL_LENGTH, 37386, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_SUBJECT_AREA, 37396, IFD_FORMAT_USHORT), + new ExifTag(TAG_MAKER_NOTE, 37500, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_USER_COMMENT, 37510, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_SUBSEC_TIME, 37520, IFD_FORMAT_STRING), + new ExifTag(TAG_SUBSEC_TIME_ORIG, 37521, IFD_FORMAT_STRING), + new ExifTag(TAG_SUBSEC_TIME_DIG, 37522, IFD_FORMAT_STRING), + new ExifTag(TAG_FLASHPIX_VERSION, 40960, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_COLOR_SPACE, 40961, IFD_FORMAT_USHORT), + new ExifTag(TAG_PIXEL_X_DIMENSION, 40962, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_PIXEL_Y_DIMENSION, 40963, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_RELATED_SOUND_FILE, 40964, IFD_FORMAT_STRING), + new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG), + new ExifTag(TAG_FLASH_ENERGY, 41483, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_SPATIAL_FREQUENCY_RESPONSE, 41484, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_FOCAL_PLANE_X_RESOLUTION, 41486, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_FOCAL_PLANE_Y_RESOLUTION, 41487, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_FOCAL_PLANE_RESOLUTION_UNIT, 41488, IFD_FORMAT_USHORT), + new ExifTag(TAG_SUBJECT_LOCATION, 41492, IFD_FORMAT_USHORT), + new ExifTag(TAG_EXPOSURE_INDEX, 41493, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_SENSING_METHOD, 41495, IFD_FORMAT_USHORT), + new ExifTag(TAG_FILE_SOURCE, 41728, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_SCENE_TYPE, 41729, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_CFA_PATTERN, 41730, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_CUSTOM_RENDERED, 41985, IFD_FORMAT_USHORT), + new ExifTag(TAG_EXPOSURE_MODE, 41986, IFD_FORMAT_USHORT), + new ExifTag(TAG_WHITE_BALANCE, 41987, IFD_FORMAT_USHORT), + new ExifTag(TAG_DIGITAL_ZOOM_RATIO, 41988, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_FOCAL_LENGTH_IN_35MM_FILM, 41989, IFD_FORMAT_USHORT), + new ExifTag(TAG_SCENE_CAPTURE_TYPE, 41990, IFD_FORMAT_USHORT), + new ExifTag(TAG_GAIN_CONTROL, 41991, IFD_FORMAT_USHORT), + new ExifTag(TAG_CONTRAST, 41992, IFD_FORMAT_USHORT), + new ExifTag(TAG_SATURATION, 41993, IFD_FORMAT_USHORT), + new ExifTag(TAG_SHARPNESS, 41994, IFD_FORMAT_USHORT), + new ExifTag(TAG_DEVICE_SETTING_DESCRIPTION, 41995, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_SUBJECT_DISTANCE_RANGE, 41996, IFD_FORMAT_USHORT), + new ExifTag(TAG_IMAGE_UNIQUE_ID, 42016, IFD_FORMAT_STRING), }; + // Primary image IFD GPS Info tags (See JEITA CP-3451 Table 16. page 56). private static final ExifTag[] IFD_GPS_TAGS = new ExifTag[] { - new ExifTag(TAG_GPS_VERSION_ID, 0), - new ExifTag(TAG_GPS_LATITUDE_REF, 1), - new ExifTag(TAG_GPS_LATITUDE, 2), - new ExifTag(TAG_GPS_LONGITUDE_REF, 3), - new ExifTag(TAG_GPS_LONGITUDE, 4), - new ExifTag(TAG_GPS_ALTITUDE_REF, 5), - new ExifTag(TAG_GPS_ALTITUDE, 6), - new ExifTag(TAG_GPS_TIMESTAMP, 7), - new ExifTag(TAG_GPS_SATELLITES, 8), - new ExifTag(TAG_GPS_STATUS, 9), - new ExifTag(TAG_GPS_MEASURE_MODE, 10), - new ExifTag(TAG_GPS_DOP, 11), - new ExifTag(TAG_GPS_SPEED_REF, 12), - new ExifTag(TAG_GPS_SPEED, 13), - new ExifTag(TAG_GPS_TRACK_REF, 14), - new ExifTag(TAG_GPS_TRACK, 15), - new ExifTag(TAG_GPS_IMG_DIRECTION_REF, 16), - new ExifTag(TAG_GPS_IMG_DIRECTION, 17), - new ExifTag(TAG_GPS_MAP_DATUM, 18), - new ExifTag(TAG_GPS_DEST_LATITUDE_REF, 19), - new ExifTag(TAG_GPS_DEST_LATITUDE, 20), - new ExifTag(TAG_GPS_DEST_LONGITUDE_REF, 21), - new ExifTag(TAG_GPS_DEST_LONGITUDE, 22), - new ExifTag(TAG_GPS_DEST_BEARING_REF, 23), - new ExifTag(TAG_GPS_DEST_BEARING, 24), - new ExifTag(TAG_GPS_DEST_DISTANCE_REF, 25), - new ExifTag(TAG_GPS_DEST_DISTANCE, 26), - new ExifTag(TAG_GPS_PROCESSING_METHOD, 27), - new ExifTag(TAG_GPS_AREA_INFORMATION, 28), - new ExifTag(TAG_GPS_DATESTAMP, 29), - new ExifTag(TAG_GPS_DIFFERENTIAL, 30), + new ExifTag(TAG_GPS_VERSION_ID, 0, IFD_FORMAT_BYTE), + new ExifTag(TAG_GPS_LATITUDE_REF, 1, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_LATITUDE, 2, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_LONGITUDE_REF, 3, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_LONGITUDE, 4, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_ALTITUDE_REF, 5, IFD_FORMAT_BYTE), + new ExifTag(TAG_GPS_ALTITUDE, 6, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_TIMESTAMP, 7, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_SATELLITES, 8, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_STATUS, 9, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_MEASURE_MODE, 10, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_DOP, 11, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_SPEED_REF, 12, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_SPEED, 13, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_TRACK_REF, 14, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_TRACK, 15, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_IMG_DIRECTION_REF, 16, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_IMG_DIRECTION, 17, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_MAP_DATUM, 18, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_DEST_LATITUDE_REF, 19, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_DEST_LATITUDE, 20, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_DEST_LONGITUDE_REF, 21, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_DEST_LONGITUDE, 22, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_DEST_BEARING_REF, 23, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_DEST_BEARING, 24, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_DEST_DISTANCE_REF, 25, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_DEST_DISTANCE, 26, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_GPS_PROCESSING_METHOD, 27, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_GPS_AREA_INFORMATION, 28, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_GPS_DATESTAMP, 29, IFD_FORMAT_STRING), + new ExifTag(TAG_GPS_DIFFERENTIAL, 30, IFD_FORMAT_USHORT), }; // Primary image IFD Interoperability tag (See JEITA CP-3451 Table 17. page 56). private static final ExifTag[] IFD_INTEROPERABILITY_TAGS = new ExifTag[] { - new ExifTag(TAG_INTEROPERABILITY_INDEX, 1), + new ExifTag(TAG_INTEROPERABILITY_INDEX, 1, IFD_FORMAT_STRING), }; // IFD Thumbnail tags (See JEITA CP-3451 Table 18. page 57). private static final ExifTag[] IFD_THUMBNAIL_TAGS = new ExifTag[] { - new ExifTag(TAG_THUMBNAIL_IMAGE_WIDTH, 256), - new ExifTag(TAG_THUMBNAIL_IMAGE_LENGTH, 257), - new ExifTag(TAG_BITS_PER_SAMPLE, 258), - new ExifTag(TAG_COMPRESSION, 259), - new ExifTag(TAG_PHOTOMETRIC_INTERPRETATION, 262), - new ExifTag(TAG_IMAGE_DESCRIPTION, 270), - new ExifTag(TAG_MAKE, 271), - new ExifTag(TAG_MODEL, 272), - new ExifTag(TAG_STRIP_OFFSETS, 273), - new ExifTag(TAG_ORIENTATION, 274), - new ExifTag(TAG_SAMPLES_PER_PIXEL, 277), - new ExifTag(TAG_ROWS_PER_STRIP, 278), - new ExifTag(TAG_STRIP_BYTE_COUNTS, 279), - new ExifTag(TAG_X_RESOLUTION, 282), - new ExifTag(TAG_Y_RESOLUTION, 283), - new ExifTag(TAG_PLANAR_CONFIGURATION, 284), - new ExifTag(TAG_RESOLUTION_UNIT, 296), - new ExifTag(TAG_TRANSFER_FUNCTION, 301), - new ExifTag(TAG_SOFTWARE, 305), - new ExifTag(TAG_DATETIME, 306), - new ExifTag(TAG_ARTIST, 315), - new ExifTag(TAG_WHITE_POINT, 318), - new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319), - new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513), - new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514), - new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529), - new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530), - new ExifTag(TAG_Y_CB_CR_POSITIONING, 531), - new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532), - new ExifTag(TAG_COPYRIGHT, 33432), - new ExifTag(TAG_EXIF_IFD_POINTER, 34665), - new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853), + new ExifTag(TAG_THUMBNAIL_IMAGE_WIDTH, 256, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_THUMBNAIL_IMAGE_LENGTH, 257, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_BITS_PER_SAMPLE, 258, IFD_FORMAT_USHORT), + new ExifTag(TAG_COMPRESSION, 259, IFD_FORMAT_USHORT), + new ExifTag(TAG_PHOTOMETRIC_INTERPRETATION, 262, IFD_FORMAT_USHORT), + new ExifTag(TAG_IMAGE_DESCRIPTION, 270, IFD_FORMAT_STRING), + new ExifTag(TAG_MAKE, 271, IFD_FORMAT_STRING), + new ExifTag(TAG_MODEL, 272, IFD_FORMAT_STRING), + new ExifTag(TAG_STRIP_OFFSETS, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_ORIENTATION, 274, IFD_FORMAT_USHORT), + new ExifTag(TAG_SAMPLES_PER_PIXEL, 277, IFD_FORMAT_USHORT), + new ExifTag(TAG_ROWS_PER_STRIP, 278, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_STRIP_BYTE_COUNTS, 279, IFD_FORMAT_USHORT, IFD_FORMAT_ULONG), + new ExifTag(TAG_X_RESOLUTION, 282, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_Y_RESOLUTION, 283, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_PLANAR_CONFIGURATION, 284, IFD_FORMAT_USHORT), + new ExifTag(TAG_RESOLUTION_UNIT, 296, IFD_FORMAT_USHORT), + new ExifTag(TAG_TRANSFER_FUNCTION, 301, IFD_FORMAT_USHORT), + new ExifTag(TAG_SOFTWARE, 305, IFD_FORMAT_STRING), + new ExifTag(TAG_DATETIME, 306, IFD_FORMAT_STRING), + new ExifTag(TAG_ARTIST, 315, IFD_FORMAT_STRING), + new ExifTag(TAG_WHITE_POINT, 318, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_PRIMARY_CHROMATICITIES, 319, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG), + new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG), + new ExifTag(TAG_Y_CB_CR_COEFFICIENTS, 529, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530, IFD_FORMAT_USHORT), + new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT), + new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL), + new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING), + new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG), + new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG), }; // See JEITA CP-3451 Figure 5. page 9. @@ -589,9 +1001,9 @@ public class ExifInterface { }; // List of tags for pointing to the other image file directory offset. private static final ExifTag[] IFD_POINTER_TAGS = new ExifTag[] { - new ExifTag(TAG_EXIF_IFD_POINTER, 34665), - new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853), - new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965), + new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG), + new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG), + new ExifTag(TAG_INTEROPERABILITY_IFD_POINTER, 40965, IFD_FORMAT_ULONG), }; // List of indices of the indicated tag groups according to the IFD_POINTER_TAGS private static final int[] IFD_POINTER_TAG_HINTS = new int[] { @@ -599,23 +1011,26 @@ public class ExifInterface { }; // Tags for indicating the thumbnail offset and length private static final ExifTag JPEG_INTERCHANGE_FORMAT_TAG = - new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513); + new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT, 513, IFD_FORMAT_ULONG); private static final ExifTag JPEG_INTERCHANGE_FORMAT_LENGTH_TAG = - new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514); + new ExifTag(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, 514, IFD_FORMAT_ULONG); // Mappings from tag number to tag name and each item represents one IFD tag group. private static final HashMap[] sExifTagMapsForReading = new HashMap[EXIF_TAGS.length]; // Mappings from tag name to tag number and each item represents one IFD tag group. private static final HashMap[] sExifTagMapsForWriting = new HashMap[EXIF_TAGS.length]; + private static final HashSet<String> sTagSetForCompatibility = new HashSet<>(Arrays.asList( + TAG_F_NUMBER, TAG_DIGITAL_ZOOM_RATIO, TAG_EXPOSURE_TIME, TAG_SUBJECT_DISTANCE, + TAG_GPS_TIMESTAMP)); // See JPEG File Interchange Format Version 1.02. // The following values are defined for handling JPEG streams. In this implementation, we are // not only getting information from EXIF but also from some JPEG special segments such as // MARKER_COM for user comment and MARKER_SOFx for image width and height. + private static final Charset ASCII = Charset.forName("US-ASCII"); // Identifier for EXIF APP1 segment in JPEG - private static final byte[] IDENTIFIER_EXIF_APP1 = - "Exif\0\0".getBytes(Charset.forName("US-ASCII")); + private static final byte[] IDENTIFIER_EXIF_APP1 = "Exif\0\0".getBytes(ASCII); // JPEG segment markers, that each marker consumes two bytes beginning with 0xff and ending with // the indicator. There is no SOF4, SOF8, SOF16 markers in JPEG and SOFx markers indicates start // of frame(baseline DCT) and the image size info exists in its beginning part. @@ -650,8 +1065,8 @@ public class ExifInterface { sExifTagMapsForReading[hint] = new HashMap(); sExifTagMapsForWriting[hint] = new HashMap(); for (ExifTag tag : EXIF_TAGS[hint]) { - sExifTagMapsForReading[hint].put(tag.number, tag.name); - sExifTagMapsForWriting[hint].put(tag.name, tag.number); + sExifTagMapsForReading[hint].put(tag.number, tag); + sExifTagMapsForWriting[hint].put(tag.name, tag); } } } @@ -662,6 +1077,7 @@ public class ExifInterface { private final boolean mIsInputStream; private boolean mIsRaw; private final HashMap[] mAttributes = new HashMap[EXIF_TAGS.length]; + private ByteOrder mExifByteOrder = ByteOrder.BIG_ENDIAN; private boolean mHasThumbnail; // The following values used for indicating a thumbnail position. private int mThumbnailOffset; @@ -670,6 +1086,9 @@ public class ExifInterface { // Pattern to check non zero timestamp private static final Pattern sNonZeroTimePattern = Pattern.compile(".*[1-9].*"); + // Pattern to check gps timestamp + private static final Pattern sGpsTimestampPattern = + Pattern.compile("^([0-9][0-9]):([0-9][0-9]):([0-9][0-9])$"); /** * Reads Exif tags from the specified image file. @@ -755,18 +1174,54 @@ public class ExifInterface { } /** - * Returns the value of the specified tag or {@code null} if there - * is no such tag in the image file. + * Returns the EXIF attribute of the specified tag or {@code null} if there is no such tag in + * the image file. * * @param tag the name of the tag. */ - public String getAttribute(String tag) { + private ExifAttribute getExifAttribute(String tag) { // Retrieves all tag groups. The value from primary image tag group has a higher priority // than the value from the thumbnail tag group if there are more than one candidates. for (int i = 0; i < EXIF_TAGS.length; ++i) { Object value = mAttributes[i].get(tag); if (value != null) { - return (String) value; + return (ExifAttribute) value; + } + } + return null; + } + + /** + * Returns the value of the specified tag or {@code null} if there + * is no such tag in the image file. + * + * @param tag the name of the tag. + */ + public String getAttribute(String tag) { + ExifAttribute attribute = getExifAttribute(tag); + if (attribute != null) { + if (!sTagSetForCompatibility.contains(tag)) { + return attribute.getStringValue(mExifByteOrder); + } + if (tag.equals(TAG_GPS_TIMESTAMP)) { + // Convert the rational values to the custom formats for backwards compatibility. + if (attribute.format != IFD_FORMAT_URATIONAL + && attribute.format != IFD_FORMAT_SRATIONAL) { + return null; + } + Rational[] array = (Rational[]) attribute.getValue(mExifByteOrder); + if (array.length != 3) { + return null; + } + return String.format("%02d:%02d:%02d", + (int) ((float) array[0].numerator / array[0].denominator), + (int) ((float) array[1].numerator / array[1].denominator), + (int) ((float) array[2].numerator / array[2].denominator)); + } + try { + return Double.toString(attribute.getDoubleValue(mExifByteOrder)); + } catch (NumberFormatException e) { + return null; } } return null; @@ -781,10 +1236,13 @@ public class ExifInterface { * @param defaultValue the value to return if the tag is not available. */ public int getAttributeInt(String tag, int defaultValue) { - String value = getAttribute(tag); - if (value == null) return defaultValue; + ExifAttribute exifAttribute = getExifAttribute(tag); + if (exifAttribute == null) { + return defaultValue; + } + try { - return Integer.valueOf(value); + return exifAttribute.getIntValue(mExifByteOrder); } catch (NumberFormatException e) { return defaultValue; } @@ -799,15 +1257,13 @@ public class ExifInterface { * @param defaultValue the value to return if the tag is not available. */ public double getAttributeDouble(String tag, double defaultValue) { - String value = getAttribute(tag); - if (value == null) return defaultValue; + ExifAttribute exifAttribute = getExifAttribute(tag); + if (exifAttribute == null) { + return defaultValue; + } + try { - int index = value.indexOf("/"); - if (index == -1) return Double.parseDouble(value); - double denom = Double.parseDouble(value.substring(index + 1)); - if (denom == 0) return defaultValue; - double num = Double.parseDouble(value.substring(0, index)); - return num / denom; + return exifAttribute.getDoubleValue(mExifByteOrder); } catch (NumberFormatException e) { return defaultValue; } @@ -820,12 +1276,136 @@ public class ExifInterface { * @param value the value of the tag. */ public void setAttribute(String tag, String value) { + // Convert the given value to rational values for backwards compatibility. + if (value != null && sTagSetForCompatibility.contains(tag)) { + if (tag.equals(TAG_GPS_TIMESTAMP)) { + Matcher m = sGpsTimestampPattern.matcher(value); + if (!m.find()) { + Log.w(TAG, "Invalid value for " + tag + " : " + value); + return; + } + value = Integer.parseInt(m.group(1)) + "/1," + Integer.parseInt(m.group(2)) + "/1," + + Integer.parseInt(m.group(3)) + "/1"; + } else { + try { + double doubleValue = Double.parseDouble(value); + value = (long) (doubleValue * 10000L) + "/10000"; + } catch (NumberFormatException e) { + Log.w(TAG, "Invalid value for " + tag + " : " + value); + return; + } + } + } + for (int i = 0 ; i < EXIF_TAGS.length; ++i) { if (i == IFD_THUMBNAIL_HINT && !mHasThumbnail) { continue; } - if (sExifTagMapsForWriting[i].containsKey(tag)) { - mAttributes[i].put(tag, value); + final Object obj = sExifTagMapsForWriting[i].get(tag); + if (obj != null) { + if (value == null) { + mAttributes[i].remove(tag); + continue; + } + final ExifTag exifTag = (ExifTag) obj; + Pair<Integer, Integer> guess = guessDataFormat(value); + int dataFormat; + if (exifTag.primaryFormat == guess.first || exifTag.primaryFormat == guess.second) { + dataFormat = exifTag.primaryFormat; + } else if (exifTag.secondaryFormat != -1 && (exifTag.secondaryFormat == guess.first + || exifTag.secondaryFormat == guess.second)) { + dataFormat = exifTag.secondaryFormat; + } else if (exifTag.primaryFormat == IFD_FORMAT_BYTE + || exifTag.primaryFormat == IFD_FORMAT_UNDEFINED + || exifTag.primaryFormat == IFD_FORMAT_STRING) { + dataFormat = exifTag.primaryFormat; + } else { + Log.w(TAG, "Given tag (" + tag + ") value didn't match with one of expected " + + "formats: " + IFD_FORMAT_NAMES[exifTag.primaryFormat] + + (exifTag.secondaryFormat == -1 ? "" : ", " + + IFD_FORMAT_NAMES[exifTag.secondaryFormat]) + " (guess: " + + IFD_FORMAT_NAMES[guess.first] + (guess.second == -1 ? "" : ", " + + IFD_FORMAT_NAMES[guess.second]) + ")"); + continue; + } + switch (dataFormat) { + case IFD_FORMAT_BYTE: { + mAttributes[i].put(tag, ExifAttribute.createByte(value)); + break; + } + case IFD_FORMAT_UNDEFINED: + case IFD_FORMAT_STRING: { + mAttributes[i].put(tag, ExifAttribute.createString(value)); + break; + } + case IFD_FORMAT_USHORT: { + final String[] values = value.split(","); + final int[] intArray = new int[values.length]; + for (int j = 0; j < values.length; ++j) { + intArray[j] = Integer.parseInt(values[j]); + } + mAttributes[i].put(tag, + ExifAttribute.createUShort(intArray, mExifByteOrder)); + break; + } + case IFD_FORMAT_SLONG: { + final String[] values = value.split(","); + final int[] intArray = new int[values.length]; + for (int j = 0; j < values.length; ++j) { + intArray[j] = Integer.parseInt(values[j]); + } + mAttributes[i].put(tag, + ExifAttribute.createSLong(intArray, mExifByteOrder)); + break; + } + case IFD_FORMAT_ULONG: { + final String[] values = value.split(","); + final long[] longArray = new long[values.length]; + for (int j = 0; j < values.length; ++j) { + longArray[j] = Long.parseLong(values[j]); + } + mAttributes[i].put(tag, + ExifAttribute.createULong(longArray, mExifByteOrder)); + break; + } + case IFD_FORMAT_URATIONAL: { + final String[] values = value.split(","); + final Rational[] rationalArray = new Rational[values.length]; + for (int j = 0; j < values.length; ++j) { + final String[] numbers = values[j].split("/"); + rationalArray[j] = new Rational(Long.parseLong(numbers[0]), + Long.parseLong(numbers[1])); + } + mAttributes[i].put(tag, + ExifAttribute.createURational(rationalArray, mExifByteOrder)); + break; + } + case IFD_FORMAT_SRATIONAL: { + final String[] values = value.split(","); + final Rational[] rationalArray = new Rational[values.length]; + for (int j = 0; j < values.length; ++j) { + final String[] numbers = values[j].split("/"); + rationalArray[j] = new Rational(Long.parseLong(numbers[0]), + Long.parseLong(numbers[1])); + } + mAttributes[i].put(tag, + ExifAttribute.createSRational(rationalArray, mExifByteOrder)); + break; + } + case IFD_FORMAT_DOUBLE: { + final String[] values = value.split(","); + final double[] doubleArray = new double[values.length]; + for (int j = 0; j < values.length; ++j) { + doubleArray[j] = Double.parseDouble(values[j]); + } + mAttributes[i].put(tag, + ExifAttribute.createDouble(doubleArray, mExifByteOrder)); + break; + } + default: + Log.w(TAG, "Data format isn't one of expected formats: " + dataFormat); + continue; + } } } } @@ -834,10 +1414,10 @@ public class ExifInterface { * Update the values of the tags in the tag groups if any value for the tag already was stored. * * @param tag the name of the tag. - * @param value the value of the tag. + * @param value the value of the tag in a form of {@link ExifAttribute}. * @return Returns {@code true} if updating is placed. */ - private boolean updateAttribute(String tag, String value) { + private boolean updateAttribute(String tag, ExifAttribute value) { boolean updated = false; for (int i = 0 ; i < EXIF_TAGS.length; ++i) { if (mAttributes[i].containsKey(tag)) { @@ -899,6 +1479,8 @@ public class ExifInterface { + "(ExifInterface supports JPEG and some RAW image formats only) " + "or a corrupted JPEG file to ExifInterface.", e); } finally { + addDefaultValuesForCompatibility(); + if (DEBUG) { printAttributes(); } @@ -957,7 +1539,9 @@ public class ExifInterface { for (int i = 0; i < mAttributes.length; ++i) { Log.d(TAG, "The size of tag group[" + i + "]: " + mAttributes[i].size()); for (Map.Entry entry : (Set<Map.Entry>) mAttributes[i].entrySet()) { - Log.d(TAG, "tagName: " + entry.getKey() + ", tagValue: " + entry.getValue()); + final ExifAttribute tagValue = (ExifAttribute) entry.getValue(); + Log.d(TAG, "tagName: " + entry.getKey() + ", tagType: " + tagValue.toString() + + ", tagValue: '" + tagValue.getStringValue(mExifByteOrder) + "'"); } } } @@ -1317,8 +1901,10 @@ public class ExifInterface { throw new IOException("Invalid exif"); } length = 0; - mAttributes[IFD_EXIF_HINT].put(TAG_USER_COMMENT, - new String(bytes, Charset.forName("US-ASCII"))); + if (getAttribute(TAG_USER_COMMENT) == null) { + mAttributes[IFD_EXIF_HINT].put(TAG_USER_COMMENT, ExifAttribute.createString( + new String(bytes, ASCII))); + } break; } @@ -1338,10 +1924,10 @@ public class ExifInterface { if (dataInputStream.skipBytes(1) != 1) { throw new IOException("Invalid SOFx"); } - mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH, - String.valueOf(dataInputStream.readUnsignedShort())); - mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH, - String.valueOf(dataInputStream.readUnsignedShort())); + mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH, ExifAttribute.createULong( + dataInputStream.readUnsignedShort(), mExifByteOrder)); + mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH, ExifAttribute.createULong( + dataInputStream.readUnsignedShort(), mExifByteOrder)); length -= 5; break; } @@ -1369,7 +1955,8 @@ public class ExifInterface { + ", outputStream: " + outputStream + ")"); } DataInputStream dataInputStream = new DataInputStream(inputStream); - ExifDataOutputStream dataOutputStream = new ExifDataOutputStream(outputStream); + ByteOrderAwarenessDataOutputStream dataOutputStream = + new ByteOrderAwarenessDataOutputStream(outputStream, ByteOrder.BIG_ENDIAN); if (dataInputStream.readByte() != MARKER) { throw new IOException("Invalid marker"); } @@ -1470,18 +2057,21 @@ public class ExifInterface { if (DEBUG) { Log.d(TAG, "readExifSegment: Byte Align II"); } - dataInputStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + mExifByteOrder = ByteOrder.LITTLE_ENDIAN; break; case BYTE_ALIGN_MM: if (DEBUG) { Log.d(TAG, "readExifSegment: Byte Align MM"); } - dataInputStream.setByteOrder(ByteOrder.BIG_ENDIAN); + mExifByteOrder = ByteOrder.BIG_ENDIAN; break; default: throw new IOException("Invalid byte order: " + Integer.toHexString(byteOrder)); } + // Set byte order. + dataInputStream.setByteOrder(mExifByteOrder); + int startCode = dataInputStream.readUnsignedShort(); if (startCode != 0x2a) { throw new IOException("Invalid exif start: " + Integer.toHexString(startCode)); @@ -1513,8 +2103,7 @@ public class ExifInterface { .parseInt(jpegInterchangeFormatLengthString); // The following code limits the size of thumbnail size not to overflow EXIF data area. jpegInterchangeFormatLength = Math.min(jpegInterchangeFormat - + jpegInterchangeFormatLength, exifOffsetFromBeginning + exifBytes.length) - - jpegInterchangeFormat; + + jpegInterchangeFormatLength, exifBytes.length) - jpegInterchangeFormat; if (jpegInterchangeFormat > 0 && jpegInterchangeFormatLength > 0) { mHasThumbnail = true; mThumbnailOffset = exifOffsetFromBeginning + jpegInterchangeFormat; @@ -1542,150 +2131,32 @@ public class ExifInterface { // Ignored the corrupted image. } } + } - // For compatibility, keep data formats as follows. - convertToInt(TAG_IMAGE_WIDTH); - convertToInt(TAG_IMAGE_LENGTH); - convertToInt(TAG_ORIENTATION); - convertToInt(TAG_FLASH); - convertToRational(TAG_FOCAL_LENGTH); - convertToDouble(TAG_DIGITAL_ZOOM_RATIO); - convertToDouble(TAG_EXPOSURE_TIME); - convertToDouble(TAG_F_NUMBER); - convertToDouble(TAG_SUBJECT_DISTANCE); - convertToInt(TAG_ISO_SPEED_RATINGS); - convertToDouble(TAG_EXPOSURE_BIAS_VALUE); - convertToInt(TAG_WHITE_BALANCE); - convertToInt(TAG_LIGHT_SOURCE); - convertToInt(TAG_METERING_MODE); - convertToInt(TAG_EXPOSURE_PROGRAM); - convertToInt(TAG_EXPOSURE_MODE); - convertToRational(TAG_GPS_ALTITUDE); - convertToInt(TAG_GPS_ALTITUDE_REF); - convertToRational(TAG_GPS_LONGITUDE); - convertToRational(TAG_GPS_LATITUDE); - convertToTimestamp(TAG_GPS_TIMESTAMP); - + private void addDefaultValuesForCompatibility() { // The value of DATETIME tag has the same value of DATETIME_ORIGINAL tag. String valueOfDateTimeOriginal = getAttribute(TAG_DATETIME_ORIGINAL); if (valueOfDateTimeOriginal != null) { - mAttributes[IFD_TIFF_HINT].put(TAG_DATETIME, valueOfDateTimeOriginal); + mAttributes[IFD_TIFF_HINT].put(TAG_DATETIME, + ExifAttribute.createString(valueOfDateTimeOriginal)); } // Add the default value. if (getAttribute(TAG_IMAGE_WIDTH) == null) { - mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH, "0"); + mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_WIDTH, + ExifAttribute.createULong(0, mExifByteOrder)); } if (getAttribute(TAG_IMAGE_LENGTH) == null) { - mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH, "0"); + mAttributes[IFD_TIFF_HINT].put(TAG_IMAGE_LENGTH, + ExifAttribute.createULong(0, mExifByteOrder)); } if (getAttribute(TAG_ORIENTATION) == null) { - mAttributes[IFD_TIFF_HINT].put(TAG_ORIENTATION, "0"); + mAttributes[IFD_TIFF_HINT].put(TAG_ORIENTATION, + ExifAttribute.createULong(0, mExifByteOrder)); } if (getAttribute(TAG_LIGHT_SOURCE) == null) { - mAttributes[IFD_EXIF_HINT].put(TAG_LIGHT_SOURCE, "0"); - } - } - - // Converts the tag value to timestamp; Otherwise deletes the given tag. - private void convertToTimestamp(String tagName) { - String entryValue = getAttribute(tagName); - if (entryValue == null) return; - int dataFormat = getDataFormatOfExifEntryValue(entryValue); - String[] components = entryValue.split(","); - if (dataFormat == IFD_FORMAT_SRATIONAL && components.length == 3) { - StringBuilder stringBuilder = new StringBuilder(); - for (String component : components) { - if (stringBuilder.length() > 0) { - stringBuilder.append(":"); - } - String[] rationalNumber = component.split("/"); - int numerator = Integer.parseInt(rationalNumber[0]); - int denominator = Integer.parseInt(rationalNumber[1]); - if (denominator == 0) { - numerator = 0; - denominator = 1; - } - int value = numerator / denominator; - stringBuilder.append(String.format("%02d", value)); - } - updateAttribute(tagName, stringBuilder.toString()); - } else if (dataFormat != IFD_FORMAT_STRING) { - removeAttribute(tagName); - } - } - - // Checks the tag value of a given tag formatted in double type; Otherwise try to convert it to - // double type or delete it. - private void convertToDouble(String tagName) { - String entryValue = getAttribute(tagName); - if (entryValue == null) return; - int dataFormat = getDataFormatOfExifEntryValue(entryValue); - switch (dataFormat) { - case IFD_FORMAT_SRATIONAL: { - StringBuilder stringBuilder = new StringBuilder(); - String[] components = entryValue.split(","); - for (String component : components) { - if (stringBuilder.length() > 0) { - stringBuilder.append(","); - } - String[] rationalNumber = component.split("/"); - int numerator = Integer.parseInt(rationalNumber[0]); - int denominator = Integer.parseInt(rationalNumber[1]); - if (denominator == 0) { - numerator = 0; - denominator = 1; - } - stringBuilder.append((double) numerator / denominator); - } - updateAttribute(tagName, stringBuilder.toString()); - break; - } - case IFD_FORMAT_DOUBLE: - // Keep it as is. - break; - default: - removeAttribute(tagName); - break; - } - } - - // Checks the tag value of a given tag formatted in int type; Otherwise deletes the tag value. - private void convertToRational(String tagName) { - String entryValue = getAttribute(tagName); - if (entryValue == null) return; - int dataFormat = getDataFormatOfExifEntryValue(entryValue); - switch (dataFormat) { - case IFD_FORMAT_SLONG: - case IFD_FORMAT_DOUBLE: { - StringBuilder stringBuilder = new StringBuilder(); - String[] components = entryValue.split(","); - for (String component : components) { - if (stringBuilder.length() > 0) { - stringBuilder.append(","); - } - double doubleValue = Double.parseDouble(component); - stringBuilder.append((int) (doubleValue * 10000.0)).append("/").append(10000); - } - updateAttribute(tagName, stringBuilder.toString()); - break; - } - case IFD_FORMAT_SRATIONAL: - // Keep it as is. - break; - default: - removeAttribute(tagName); - break; - } - } - - // Checks the tag value of a given tag formatted in int type; Otherwise deletes the tag value. - private void convertToInt(String tagName) { - String entryValue = getAttribute(tagName); - if (entryValue == null) return; - int dataFormat = getDataFormatOfExifEntryValue(entryValue); - if (dataFormat != IFD_FORMAT_SLONG) { - removeAttribute(tagName); + mAttributes[IFD_EXIF_HINT].put(TAG_LIGHT_SOURCE, + ExifAttribute.createULong(0, mExifByteOrder)); } } @@ -1714,18 +2185,18 @@ public class ExifInterface { long nextEntryOffset = dataInputStream.peek() + 4; // next four bytes is for data // offset or value. // Look up a corresponding tag from tag number - String tagName = (String) sExifTagMapsForReading[hint].get(tagNumber); + final ExifTag tag = (ExifTag) sExifTagMapsForReading[hint].get(tagNumber); if (DEBUG) { Log.d(TAG, String.format("hint: %d, tagNumber: %d, tagName: %s, dataFormat: %d, " + - "numberOfComponents: %d", hint, tagNumber, tagName, dataFormat, - numberOfComponents)); + "numberOfComponents: %d", hint, tagNumber, tag != null ? tag.name : null, + dataFormat, numberOfComponents)); } - if (tagName == null || dataFormat <= 0 || + if (tag == null || dataFormat <= 0 || dataFormat >= IFD_FORMAT_BYTES_PER_FORMAT.length) { // Skip if the parsed tag number is not defined or invalid data format. - if (tagName == null) { + if (tag == null) { Log.w(TAG, "Skip the tag entry since tag number is not defined: " + tagNumber); } else { Log.w(TAG, "Skip the tag entry since data format is invalid: " + dataFormat); @@ -1757,6 +2228,7 @@ public class ExifInterface { if (DEBUG) { Log.d(TAG, "innerIfdHint: " + innerIfdHint + " byteCount: " + byteCount); } + if (innerIfdHint >= 0) { long offset = -1L; // Get offset from data field @@ -1783,7 +2255,7 @@ public class ExifInterface { } } if (DEBUG) { - Log.d(TAG, String.format("Offset: %d, tagName: %s", offset, tagName)); + Log.d(TAG, String.format("Offset: %d, tagName: %s", offset, tag.name)); } if (offset > 0L && offset < dataInputStream.mLength) { dataInputStream.seek(offset); @@ -1796,25 +2268,10 @@ public class ExifInterface { continue; } - if (numberOfComponents == 1 || dataFormat == IFD_FORMAT_STRING - || dataFormat == IFD_FORMAT_UNDEFINED) { - String entryValue = readExifEntryValue( - dataInputStream, dataFormat, numberOfComponents); - if (entryValue != null) { - mAttributes[hint].put(tagName, entryValue); - } - } else { - StringBuilder entryValueBuilder = new StringBuilder(); - for (int c = 0; c < numberOfComponents; ++c) { - if (entryValueBuilder.length() > 0) { - entryValueBuilder.append(","); - } - entryValueBuilder.append(readExifEntryValue( - dataInputStream, dataFormat, numberOfComponents)); - } - mAttributes[hint].put(tagName, entryValueBuilder.toString()); - } - + byte[] bytes = new byte[numberOfComponents * IFD_FORMAT_BYTES_PER_FORMAT[dataFormat]]; + dataInputStream.readFully(bytes); + mAttributes[hint].put( + tag.name, new ExifAttribute(dataFormat, numberOfComponents, bytes)); if (dataInputStream.peek() != nextEntryOffset) { dataInputStream.seek(nextEntryOffset); } @@ -1834,82 +2291,6 @@ public class ExifInterface { } } - // Reads a value from where the entry value are stored. - private String readExifEntryValue(ByteOrderAwarenessDataInputStream dataInputStream, - int dataFormat, int numberOfComponents) throws IOException { - // See TIFF 6.0 spec Types. page 15. - switch (dataFormat) { - case IFD_FORMAT_BYTE: { - return String.valueOf(dataInputStream.readByte()); - } - case IFD_FORMAT_SBYTE: { - return String.valueOf(dataInputStream.readByte() & 0xff); - } - case IFD_FORMAT_USHORT: { - return String.valueOf(dataInputStream.readUnsignedShort()); - } - case IFD_FORMAT_SSHORT: { - return String.valueOf(dataInputStream.readUnsignedInt()); - } - case IFD_FORMAT_ULONG: { - return String.valueOf(dataInputStream.readInt()); - } - case IFD_FORMAT_SLONG: { - return String.valueOf(dataInputStream.readInt()); - } - case IFD_FORMAT_URATIONAL: - case IFD_FORMAT_SRATIONAL: { - int numerator = dataInputStream.readInt(); - int denominator = dataInputStream.readInt(); - return numerator + "/" + denominator; - } - case IFD_FORMAT_SINGLE: { - return String.valueOf(dataInputStream.readFloat()); - } - case IFD_FORMAT_DOUBLE: { - return String.valueOf(dataInputStream.readDouble()); - } - case IFD_FORMAT_UNDEFINED: // Usually UNDEFINED format is ASCII. - case IFD_FORMAT_STRING: { - byte[] bytes = new byte[numberOfComponents]; - dataInputStream.readFully(bytes); - int index = 0; - if (numberOfComponents >= EXIF_ASCII_PREFIX.length) { - boolean same = true; - for (int i = 0; i < EXIF_ASCII_PREFIX.length; ++i) { - if (bytes[i] != EXIF_ASCII_PREFIX[i]) { - same = false; - break; - } - } - if (same) { - index = EXIF_ASCII_PREFIX.length; - } - } - - StringBuilder stringBuilder = new StringBuilder(); - while (index < numberOfComponents) { - int ch = bytes[index]; - if (ch == 0) { - break; - } - if (ch >= 32) { - stringBuilder.append((char) ch); - } - else { - stringBuilder.append('?'); - } - ++index; - } - return stringBuilder.toString(); - } - default: { - // Nothing to do - return null; - } - } - } - // Gets the corresponding IFD group index of the given tag number for writing Exif Tags. private static int getIfdHintFromTagNumber(int tagNumber) { for (int i = 0; i < IFD_POINTER_TAG_HINTS.length; ++i) { @@ -1921,8 +2302,8 @@ public class ExifInterface { } // Writes an Exif segment into the given output stream. - private int writeExifSegment(ExifDataOutputStream dataOutputStream, int exifOffsetFromBeginning) - throws IOException { + private int writeExifSegment(ByteOrderAwarenessDataOutputStream dataOutputStream, + int exifOffsetFromBeginning) throws IOException { // The following variables are for calculating each IFD tag group size in bytes. int[] ifdOffsets = new int[EXIF_TAGS.length]; int[] ifdDataSizes = new int[EXIF_TAGS.length]; @@ -1938,7 +2319,7 @@ public class ExifInterface { // Remove null value tags. for (int hint = 0; hint < EXIF_TAGS.length; ++hint) { for (Object obj : mAttributes[hint].entrySet().toArray()) { - Map.Entry entry = (Map.Entry) obj; + final Map.Entry entry = (Map.Entry) obj; if (entry.getValue() == null) { mAttributes[hint].remove(entry.getKey()); } @@ -1948,28 +2329,31 @@ public class ExifInterface { // Add IFD pointer tags. The next offset of primary image TIFF IFD will have thumbnail IFD // offset when there is one or more tags in the thumbnail IFD. if (!mAttributes[IFD_INTEROPERABILITY_HINT].isEmpty()) { - mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].name, "0"); + mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].name, + ExifAttribute.createULong(0, mExifByteOrder)); } if (!mAttributes[IFD_EXIF_HINT].isEmpty()) { - mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].name, "0"); + mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].name, + ExifAttribute.createULong(0, mExifByteOrder)); } if (!mAttributes[IFD_GPS_HINT].isEmpty()) { - mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name, "0"); + mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name, + ExifAttribute.createULong(0, mExifByteOrder)); } if (mHasThumbnail) { - mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name, "0"); + mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name, + ExifAttribute.createULong(0, mExifByteOrder)); mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name, - String.valueOf(mThumbnailLength)); + ExifAttribute.createULong(mThumbnailLength, mExifByteOrder)); } // Calculate IFD group data area sizes. IFD group data area is assigned to save the entry // value which has a bigger size than 4 bytes. - for (int i = 0; i < 5; ++i) { + for (int i = 0; i < EXIF_TAGS.length; ++i) { int sum = 0; for (Map.Entry entry : (Set<Map.Entry>) mAttributes[i].entrySet()) { - String entryValue = (String) ((Map.Entry) entry).getValue(); - int dataFormat = getDataFormatOfExifEntryValue(entryValue); - int size = getSizeOfExifEntryValue(dataFormat, entryValue); + final ExifAttribute exifAttribute = (ExifAttribute) ((Map.Entry) entry).getValue(); + final int size = exifAttribute.size(); if (size > 4) { sum += size; } @@ -1988,9 +2372,7 @@ public class ExifInterface { if (mHasThumbnail) { int thumbnailOffset = position; mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_TAG.name, - String.valueOf(thumbnailOffset)); - mAttributes[IFD_TIFF_HINT].put(JPEG_INTERCHANGE_FORMAT_LENGTH_TAG.name, - String.valueOf(mThumbnailLength)); + ExifAttribute.createULong(thumbnailOffset, mExifByteOrder)); mThumbnailOffset = exifOffsetFromBeginning + thumbnailOffset; position += mThumbnailLength; } @@ -1999,7 +2381,7 @@ public class ExifInterface { int totalSize = position + 8; // eight bytes is for header part. if (DEBUG) { Log.d(TAG, "totalSize length: " + totalSize); - for (int i = 0; i < 5; ++i) { + for (int i = 0; i < EXIF_TAGS.length; ++i) { Log.d(TAG, String.format("index: %d, offsets: %d, tag count: %d, data sizes: %d", i, ifdOffsets[i], mAttributes[i].size(), ifdDataSizes[i])); } @@ -2008,21 +2390,23 @@ public class ExifInterface { // Update IFD pointer tags with the calculated offsets. if (!mAttributes[IFD_EXIF_HINT].isEmpty()) { mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[0].name, - String.valueOf(ifdOffsets[IFD_EXIF_HINT])); + ExifAttribute.createULong(ifdOffsets[IFD_EXIF_HINT], mExifByteOrder)); } if (!mAttributes[IFD_GPS_HINT].isEmpty()) { mAttributes[IFD_TIFF_HINT].put(IFD_POINTER_TAGS[1].name, - String.valueOf(ifdOffsets[IFD_GPS_HINT])); + ExifAttribute.createULong(ifdOffsets[IFD_GPS_HINT], mExifByteOrder)); } if (!mAttributes[IFD_INTEROPERABILITY_HINT].isEmpty()) { - mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].name, - String.valueOf(ifdOffsets[IFD_INTEROPERABILITY_HINT])); + mAttributes[IFD_EXIF_HINT].put(IFD_POINTER_TAGS[2].name, ExifAttribute.createULong( + ifdOffsets[IFD_INTEROPERABILITY_HINT], mExifByteOrder)); } // Write TIFF Headers. See JEITA CP-3451C Table 1. page 10. dataOutputStream.writeUnsignedShort(totalSize); dataOutputStream.write(IDENTIFIER_EXIF_APP1); - dataOutputStream.writeShort(BYTE_ALIGN_MM); + dataOutputStream.writeShort(mExifByteOrder == ByteOrder.BIG_ENDIAN + ? BYTE_ALIGN_MM : BYTE_ALIGN_II); + dataOutputStream.setByteOrder(mExifByteOrder); dataOutputStream.writeUnsignedShort(0x2a); dataOutputStream.writeUnsignedInt(8); @@ -2037,26 +2421,23 @@ public class ExifInterface { int dataOffset = ifdOffsets[hint] + 2 + mAttributes[hint].size() * 12 + 4; for (Map.Entry entry : (Set<Map.Entry>) mAttributes[hint].entrySet()) { // Convert tag name to tag number. - int tagNumber = (int) sExifTagMapsForWriting[hint].get(entry.getKey()); - String entryValue = (String) entry.getValue(); - - int dataFormat = getDataFormatOfExifEntryValue(entryValue); - int numberOfComponents = getNumberOfComponentsInExifEntryValue(dataFormat, - entryValue); - int byteCount = getSizeOfExifEntryValue(dataFormat, entryValue); + final ExifTag tag = (ExifTag) sExifTagMapsForWriting[hint].get(entry.getKey()); + final int tagNumber = tag.number; + final ExifAttribute attribute = (ExifAttribute) entry.getValue(); + final int size = attribute.size(); dataOutputStream.writeUnsignedShort(tagNumber); - dataOutputStream.writeUnsignedShort(dataFormat); - dataOutputStream.writeInt(numberOfComponents); - if (byteCount > 4) { + dataOutputStream.writeUnsignedShort(attribute.format); + dataOutputStream.writeInt(attribute.numberOfComponents); + if (size > 4) { dataOutputStream.writeUnsignedInt(dataOffset); - dataOffset += byteCount; + dataOffset += size; } else { - int bytesWritten = writeExifEntryValue(dataOutputStream, entryValue); + dataOutputStream.write(attribute.bytes); // Fill zero up to 4 bytes - if (bytesWritten < 4) { - for (int i = bytesWritten; i < 4; ++i) { - dataOutputStream.write(0); + if (size < 4) { + for (int i = size; i < 4; ++i) { + dataOutputStream.writeByte(0); } } } @@ -2073,12 +2454,10 @@ public class ExifInterface { // Write values of data field exceeding 4 bytes after the next offset. for (Map.Entry entry : (Set<Map.Entry>) mAttributes[hint].entrySet()) { - String entryValue = (String) entry.getValue(); + ExifAttribute attribute = (ExifAttribute) entry.getValue(); - int dataFormat = getDataFormatOfExifEntryValue(entryValue); - int byteCount = getSizeOfExifEntryValue(dataFormat, entryValue); - if (byteCount > 4) { - writeExifEntryValue(dataOutputStream, entryValue); + if (attribute.bytes.length > 4) { + dataOutputStream.write(attribute.bytes, 0, attribute.bytes.length); } } } @@ -2089,59 +2468,49 @@ public class ExifInterface { dataOutputStream.write(getThumbnail()); } - return totalSize; - } + // Reset the byte order to big endian in order to write remaining parts of the JPEG file. + dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN); - // Writes EXIF entry value and its entry value type will be automatically determined. - private static int writeExifEntryValue(ExifDataOutputStream dataOutputStream, String entryValue) - throws IOException { - int bytesWritten = 0; - int dataFormat = getDataFormatOfExifEntryValue(entryValue); - - if (dataFormat == IFD_FORMAT_STRING) { - byte[] asciiArray = (entryValue + '\0').getBytes(Charset.forName("US-ASCII")); - dataOutputStream.write(asciiArray); - return asciiArray.length; - } - - // Values can be composed of several components. Each component is separated by char ','. - String[] components = entryValue.split(","); - for (String component : components) { - switch (dataFormat) { - case IFD_FORMAT_SLONG: - dataOutputStream.writeInt(Integer.parseInt(component)); - bytesWritten += 4; - break; - case IFD_FORMAT_DOUBLE: - dataOutputStream.writeDouble(Double.parseDouble(component)); - bytesWritten += 8; - break; - case IFD_FORMAT_SRATIONAL: - String[] rationalNumber = component.split("/"); - dataOutputStream.writeInt(Integer.parseInt(rationalNumber[0])); - dataOutputStream.writeInt(Integer.parseInt(rationalNumber[1])); - bytesWritten += 8; - break; - default: - throw new IllegalArgumentException(); - } - } - return bytesWritten; + return totalSize; } - // Determines the data format of EXIF entry value. - private static int getDataFormatOfExifEntryValue(String entryValue) { + /** + * Determines the data format of EXIF entry value. + * + * @param entryValue The value to be determined. + * @return Returns two data formats gussed as a pair in integer. If there is no two candidate + data formats for the given entry value, returns {@code -1} in the second of the pair. + */ + private static Pair<Integer, Integer> guessDataFormat(String entryValue) { // See TIFF 6.0 spec Types. page 15. // Take the first component if there are more than one component. if (entryValue.contains(",")) { String[] entryValues = entryValue.split(","); - int dataFormat = getDataFormatOfExifEntryValue(entryValues[0]); - if (dataFormat == IFD_FORMAT_STRING) { - return IFD_FORMAT_STRING; + Pair<Integer, Integer> dataFormat = guessDataFormat(entryValues[0]); + if (dataFormat.first == IFD_FORMAT_STRING) { + return dataFormat; } for (int i = 1; i < entryValues.length; ++i) { - if (getDataFormatOfExifEntryValue(entryValues[i]) != dataFormat) { - return IFD_FORMAT_STRING; + final Pair<Integer, Integer> guessDataFormat = guessDataFormat(entryValues[i]); + int first = -1, second = -1; + if (guessDataFormat.first == dataFormat.first + || guessDataFormat.second == dataFormat.first) { + first = dataFormat.first; + } + if (dataFormat.second != -1 && (guessDataFormat.first == dataFormat.second + || guessDataFormat.second == dataFormat.second)) { + second = dataFormat.second; + } + if (first == -1 && second == -1) { + return new Pair<>(IFD_FORMAT_STRING, -1); + } + if (first == -1) { + dataFormat = new Pair<>(second, -1); + continue; + } + if (second == -1) { + dataFormat = new Pair<>(first, -1); + continue; } } return dataFormat; @@ -2151,68 +2520,40 @@ public class ExifInterface { String[] rationalNumber = entryValue.split("/"); if (rationalNumber.length == 2) { try { - Integer.parseInt(rationalNumber[0]); - Integer.parseInt(rationalNumber[1]); - return IFD_FORMAT_SRATIONAL; + long numerator = Long.parseLong(rationalNumber[0]); + long denominator = Long.parseLong(rationalNumber[1]); + if (numerator < 0L || denominator < 0L) { + return new Pair<>(IFD_FORMAT_SRATIONAL, - 1); + } + if (numerator > Integer.MAX_VALUE || denominator > Integer.MAX_VALUE) { + return new Pair<>(IFD_FORMAT_URATIONAL, -1); + } + return new Pair<>(IFD_FORMAT_SRATIONAL, IFD_FORMAT_URATIONAL); } catch (NumberFormatException e) { // Ignored } } - return IFD_FORMAT_STRING; + return new Pair<>(IFD_FORMAT_STRING, -1); } try { - Integer.parseInt(entryValue); - return IFD_FORMAT_SLONG; + Long longValue = Long.parseLong(entryValue); + if (longValue >= 0 && longValue <= 65535) { + return new Pair<>(IFD_FORMAT_USHORT, IFD_FORMAT_ULONG); + } + if (longValue < 0) { + return new Pair<>(IFD_FORMAT_SLONG, -1); + } + return new Pair<>(IFD_FORMAT_ULONG, -1); } catch (NumberFormatException e) { // Ignored } try { Double.parseDouble(entryValue); - return IFD_FORMAT_DOUBLE; + return new Pair<>(IFD_FORMAT_DOUBLE, -1); } catch (NumberFormatException e) { // Ignored } - return IFD_FORMAT_STRING; - } - - // Determines the size of EXIF entry value. - private static int getSizeOfExifEntryValue(int dataFormat, String entryValue) { - // See TIFF 6.0 spec Types page 15. - if (dataFormat == IFD_FORMAT_STRING) { - return (entryValue + '\0').getBytes(Charset.forName("US-ASCII")).length; - } - int bytesEstimated = 0; - String[] components = entryValue.split(","); - for (String component : components) { - switch (dataFormat) { - case IFD_FORMAT_SLONG: - bytesEstimated += 4; - break; - case IFD_FORMAT_DOUBLE: - bytesEstimated += 8; - break; - case IFD_FORMAT_SRATIONAL: - bytesEstimated += 8; - break; - default: - throw new IllegalArgumentException(); - } - } - return bytesEstimated; - } - - // Determines the number of components of EXIF entry value. - private static int getNumberOfComponentsInExifEntryValue(int dataFormat, String entryValue) { - if (dataFormat == IFD_FORMAT_STRING) { - return (entryValue + '\0').getBytes(Charset.forName("US-ASCII")).length; - } - int count = 1; - for (int i = 0; i < entryValue.length(); ++i) { - if (entryValue.charAt(i) == ',') { - ++count; - } - } - return count; + return new Pair<>(IFD_FORMAT_STRING, -1); } // An input stream to parse EXIF data area, which can be written in either little or big endian @@ -2373,10 +2714,56 @@ public class ExifInterface { } } - // An output stream to write EXIF data area, that will be written in big endian byte order. - private static class ExifDataOutputStream extends DataOutputStream { - public ExifDataOutputStream(OutputStream out) { + // An output stream to write EXIF data area, which can be written in either little or big endian + // order. + private static class ByteOrderAwarenessDataOutputStream extends FilterOutputStream { + private final OutputStream mOutputStream; + private ByteOrder mByteOrder; + + public ByteOrderAwarenessDataOutputStream(OutputStream out, ByteOrder byteOrder) { super(out); + mOutputStream = out; + mByteOrder = byteOrder; + } + + public void setByteOrder(ByteOrder byteOrder) { + mByteOrder = byteOrder; + } + + public void write(byte[] bytes) throws IOException { + mOutputStream.write(bytes); + } + + public void write(byte[] bytes, int offset, int length) throws IOException { + mOutputStream.write(bytes, offset, length); + } + + public void writeByte(int val) throws IOException { + mOutputStream.write(val); + } + + public void writeShort(short val) throws IOException { + if (mByteOrder == ByteOrder.LITTLE_ENDIAN) { + mOutputStream.write((val >>> 0) & 0xFF); + mOutputStream.write((val >>> 8) & 0xFF); + } else if (mByteOrder == ByteOrder.BIG_ENDIAN) { + mOutputStream.write((val >>> 8) & 0xFF); + mOutputStream.write((val >>> 0) & 0xFF); + } + } + + public void writeInt(int val) throws IOException { + if (mByteOrder == ByteOrder.LITTLE_ENDIAN) { + mOutputStream.write((val >>> 0) & 0xFF); + mOutputStream.write((val >>> 8) & 0xFF); + mOutputStream.write((val >>> 16) & 0xFF); + mOutputStream.write((val >>> 24) & 0xFF); + } else if (mByteOrder == ByteOrder.BIG_ENDIAN) { + mOutputStream.write((val >>> 24) & 0xFF); + mOutputStream.write((val >>> 16) & 0xFF); + mOutputStream.write((val >>> 8) & 0xFF); + mOutputStream.write((val >>> 0) & 0xFF); + } } public void writeUnsignedShort(int val) throws IOException { diff --git a/media/jni/android_media_ExifInterface.cpp b/media/jni/android_media_ExifInterface.cpp index ae23720fd132..731deae14603 100644 --- a/media/jni/android_media_ExifInterface.cpp +++ b/media/jni/android_media_ExifInterface.cpp @@ -138,11 +138,11 @@ static jobject getRawAttributes(JNIEnv* env, SkStream* stream, bool returnThumbn if (image_data.thumbnail.length > 0 && image_data.thumbnail.format == ::piex::Image::kJpegCompressed) { - map.add(String8("hasThumbnail"), String8("true")); - map.add(String8("thumbnailOffset"), String8::format("%d", image_data.thumbnail.offset)); - map.add(String8("thumbnailLength"), String8::format("%d", image_data.thumbnail.length)); + map.add(String8("HasThumbnail"), String8("true")); + map.add(String8("ThumbnailOffset"), String8::format("%d", image_data.thumbnail.offset)); + map.add(String8("ThumbnailLength"), String8::format("%d", image_data.thumbnail.length)); } else { - map.add(String8("hasThumbnail"), String8("false")); + map.add(String8("HasThumbnail"), String8("false")); } map.add( @@ -307,7 +307,7 @@ static jobject getRawAttributes(JNIEnv* env, SkStream* stream, bool returnThumbn (uint8_t*)thumbnailData.get()); env->SetByteArrayRegion( jthumbnailByteArray, 0, image_data.thumbnail.length, thumbnailData.get()); - jstring jkey = env->NewStringUTF(String8("thumbnailData")); + jstring jkey = env->NewStringUTF(String8("ThumbnailData")); env->CallObjectMethod(hashMap, gFields.hashMap.put, jkey, jthumbnailByteArray); env->DeleteLocalRef(jkey); env->DeleteLocalRef(jthumbnailByteArray); diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java index 312d9aaa8750..db326ba6e2c2 100644 --- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/ExifInterfaceTest.java @@ -97,7 +97,7 @@ public class ExifInterfaceTest extends AndroidTestCase { // Values. public final String make; public final String model; - public final float aperture; + public final float fNumber; public final String datetime; public final float exposureTime; public final float flash; @@ -140,7 +140,7 @@ public class ExifInterfaceTest extends AndroidTestCase { // Reads values. make = getString(typedArray, 7); model = getString(typedArray, 8); - aperture = typedArray.getFloat(9, 0f); + fNumber = typedArray.getFloat(9, 0f); datetime = getString(typedArray, 10); exposureTime = typedArray.getFloat(11, 0f); flash = typedArray.getFloat(12, 0f); @@ -243,7 +243,7 @@ public class ExifInterfaceTest extends AndroidTestCase { assertEquals(expectedValue, intValue); } - private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) { + private void assertDoubleTag(ExifInterface exifInterface, String tag, float expectedValue) { double doubleValue = exifInterface.getAttributeDouble(tag, 0.0); assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE); } @@ -288,10 +288,10 @@ public class ExifInterfaceTest extends AndroidTestCase { // Checks values. assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make); assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model); - assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture); + assertDoubleTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.fNumber); assertStringTag(exifInterface, ExifInterface.TAG_DATETIME, expectedValue.datetime); - assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime); - assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash); + assertDoubleTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime); + assertDoubleTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash); assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength); assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude); assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF, |