diff --git a/api/current.txt b/api/current.txt index d499880cb8478..957dac0e348ca 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 670760c3f3e8d..f2b13e5c19d29 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 f2cc6e8a276f9..6896941ce8f97 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 72f57425e8fe7..4848630baacfb 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 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. @@ -754,6 +1173,24 @@ public class ExifInterface { loadAttributes(inputStream); } + /** + * 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. + */ + 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 (ExifAttribute) value; + } + } + return null; + } + /** * Returns the value of the specified tag or {@code null} if there * is no such tag in the image file. @@ -761,12 +1198,30 @@ public class ExifInterface { * @param tag the name of the tag. */ public String getAttribute(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; + 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 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) 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) 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) 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) 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()); } + // Reset the byte order to big endian in order to write remaining parts of the JPEG file. + dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN); + return totalSize; } - // 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; - } - - // 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 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 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 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 ae23720fd132b..731deae146030 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 312d9aaa87501..db326ba6e2c20 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,