/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.media; import android.annotation.NonNull; import android.content.res.AssetManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; 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.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; 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; import libcore.io.Streams; /** * This is a class for reading and writing Exif tags in a JPEG file or a RAW image file. *
* Supported formats are: JPEG, DNG, CR2, NEF, NRW, ARW, RW2, ORF and RAF. *
* Attribute mutation is supported for JPEG image files.
*/
public class ExifInterface {
private static final String TAG = "ExifInterface";
private static final boolean DEBUG = false;
// The Exif tag names
/** Type is String. */
public static final String TAG_ARTIST = "Artist";
/** Type is int. */
public static final String TAG_BITS_PER_SAMPLE = "BitsPerSample";
/** Type is int. */
public static final String TAG_COMPRESSION = "Compression";
/** Type is String. */
public static final String TAG_COPYRIGHT = "Copyright";
/** Type is String. */
public static final String TAG_DATETIME = "DateTime";
/** Type is String. */
public static final String TAG_IMAGE_DESCRIPTION = "ImageDescription";
/** Type is int. */
public static final String TAG_IMAGE_LENGTH = "ImageLength";
/** Type is int. */
public static final String TAG_IMAGE_WIDTH = "ImageWidth";
/** Type is int. */
public static final String TAG_JPEG_INTERCHANGE_FORMAT = "JPEGInterchangeFormat";
/** Type is int. */
public static final String TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = "JPEGInterchangeFormatLength";
/** Type is String. */
public static final String TAG_MAKE = "Make";
/** Type is String. */
public static final String TAG_MODEL = "Model";
/** Type is int. */
public static final String TAG_ORIENTATION = "Orientation";
/** Type is int. */
public static final String TAG_PHOTOMETRIC_INTERPRETATION = "PhotometricInterpretation";
/** Type is int. */
public static final String TAG_PLANAR_CONFIGURATION = "PlanarConfiguration";
/** Type is rational. */
public static final String TAG_PRIMARY_CHROMATICITIES = "PrimaryChromaticities";
/** Type is rational. */
public static final String TAG_REFERENCE_BLACK_WHITE = "ReferenceBlackWhite";
/** Type is int. */
public static final String TAG_RESOLUTION_UNIT = "ResolutionUnit";
/** Type is int. */
public static final String TAG_ROWS_PER_STRIP = "RowsPerStrip";
/** Type is int. */
public static final String TAG_SAMPLES_PER_PIXEL = "SamplesPerPixel";
/** Type is String. */
public static final String TAG_SOFTWARE = "Software";
/** Type is int. */
public static final String TAG_STRIP_BYTE_COUNTS = "StripByteCounts";
/** Type is int. */
public static final String TAG_STRIP_OFFSETS = "StripOffsets";
/** Type is int. */
public static final String TAG_TRANSFER_FUNCTION = "TransferFunction";
/** Type is rational. */
public static final String TAG_WHITE_POINT = "WhitePoint";
/** Type is rational. */
public static final String TAG_X_RESOLUTION = "XResolution";
/** Type is rational. */
public static final String TAG_Y_CB_CR_COEFFICIENTS = "YCbCrCoefficients";
/** Type is int. */
public static final String TAG_Y_CB_CR_POSITIONING = "YCbCrPositioning";
/** Type is int. */
public static final String TAG_Y_CB_CR_SUB_SAMPLING = "YCbCrSubSampling";
/** Type is rational. */
public static final String TAG_Y_RESOLUTION = "YResolution";
/** Type is rational. */
public static final String TAG_APERTURE_VALUE = "ApertureValue";
/** Type is rational. */
public static final String TAG_BRIGHTNESS_VALUE = "BrightnessValue";
/** Type is String. */
public static final String TAG_CFA_PATTERN = "CFAPattern";
/** Type is int. */
public static final String TAG_COLOR_SPACE = "ColorSpace";
/** Type is String. */
public static final String TAG_COMPONENTS_CONFIGURATION = "ComponentsConfiguration";
/** Type is rational. */
public static final String TAG_COMPRESSED_BITS_PER_PIXEL = "CompressedBitsPerPixel";
/** Type is int. */
public static final String TAG_CONTRAST = "Contrast";
/** Type is int. */
public static final String TAG_CUSTOM_RENDERED = "CustomRendered";
/** Type is String. */
public static final String TAG_DATETIME_DIGITIZED = "DateTimeDigitized";
/** Type is String. */
public static final String TAG_DATETIME_ORIGINAL = "DateTimeOriginal";
/** Type is String. */
public static final String TAG_DEVICE_SETTING_DESCRIPTION = "DeviceSettingDescription";
/** Type is double. */
public static final String TAG_DIGITAL_ZOOM_RATIO = "DigitalZoomRatio";
/** Type is String. */
public static final String TAG_EXIF_VERSION = "ExifVersion";
/** Type is double. */
public static final String TAG_EXPOSURE_BIAS_VALUE = "ExposureBiasValue";
/** Type is rational. */
public static final String TAG_EXPOSURE_INDEX = "ExposureIndex";
/** Type is int. */
public static final String TAG_EXPOSURE_MODE = "ExposureMode";
/** Type is int. */
public static final String TAG_EXPOSURE_PROGRAM = "ExposureProgram";
/** Type is double. */
public static final String TAG_EXPOSURE_TIME = "ExposureTime";
/** Type is double. */
public static final String TAG_F_NUMBER = "FNumber";
/**
* Type is double.
*
* @deprecated use {@link #TAG_F_NUMBER} instead
*/
@Deprecated
public static final String TAG_APERTURE = "FNumber";
/** Type is String. */
public static final String TAG_FILE_SOURCE = "FileSource";
/** Type is int. */
public static final String TAG_FLASH = "Flash";
/** Type is rational. */
public static final String TAG_FLASH_ENERGY = "FlashEnergy";
/** Type is String. */
public static final String TAG_FLASHPIX_VERSION = "FlashpixVersion";
/** Type is rational. */
public static final String TAG_FOCAL_LENGTH = "FocalLength";
/** Type is int. */
public static final String TAG_FOCAL_LENGTH_IN_35MM_FILM = "FocalLengthIn35mmFilm";
/** Type is int. */
public static final String TAG_FOCAL_PLANE_RESOLUTION_UNIT = "FocalPlaneResolutionUnit";
/** Type is rational. */
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 int. */
public static final String TAG_GAIN_CONTROL = "GainControl";
/** Type is int. */
public static final String TAG_ISO_SPEED_RATINGS = "ISOSpeedRatings";
/**
* Type is int.
*
* @deprecated use {@link #TAG_ISO_SPEED_RATINGS} instead
*/
@Deprecated
public static final String TAG_ISO = "ISOSpeedRatings";
/** Type is String. */
public static final String TAG_IMAGE_UNIQUE_ID = "ImageUniqueID";
/** Type is int. */
public static final String TAG_LIGHT_SOURCE = "LightSource";
/** Type is String. */
public static final String TAG_MAKER_NOTE = "MakerNote";
/** Type is rational. */
public static final String TAG_MAX_APERTURE_VALUE = "MaxApertureValue";
/** Type is int. */
public static final String TAG_METERING_MODE = "MeteringMode";
/** Type is String. */
public static final String TAG_OECF = "OECF";
/** Type is int. */
public static final String TAG_PIXEL_X_DIMENSION = "PixelXDimension";
/** Type is int. */
public static final String TAG_PIXEL_Y_DIMENSION = "PixelYDimension";
/** Type is String. */
public static final String TAG_RELATED_SOUND_FILE = "RelatedSoundFile";
/** Type is int. */
public static final String TAG_SATURATION = "Saturation";
/** Type is int. */
public static final String TAG_SCENE_CAPTURE_TYPE = "SceneCaptureType";
/** Type is String. */
public static final String TAG_SCENE_TYPE = "SceneType";
/** Type is int. */
public static final String TAG_SENSING_METHOD = "SensingMethod";
/** Type is int. */
public static final String TAG_SHARPNESS = "Sharpness";
/** Type is rational. */
public static final String TAG_SHUTTER_SPEED_VALUE = "ShutterSpeedValue";
/** Type is String. */
public static final String TAG_SPATIAL_FREQUENCY_RESPONSE = "SpatialFrequencyResponse";
/** Type is String. */
public static final String TAG_SPECTRAL_SENSITIVITY = "SpectralSensitivity";
/** Type is String. */
public static final String TAG_SUBSEC_TIME = "SubSecTime";
/**
* Type is String.
*
* @deprecated use {@link #TAG_SUBSEC_TIME_DIGITIZED} instead
*/
public static final String TAG_SUBSEC_TIME_DIG = "SubSecTimeDigitized";
/** 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 String. */
public static final String TAG_SUBSEC_TIME_ORIGINAL = "SubSecTimeOriginal";
/** Type is int. */
public static final String TAG_SUBJECT_AREA = "SubjectArea";
/** Type is double. */
public static final String TAG_SUBJECT_DISTANCE = "SubjectDistance";
/** Type is int. */
public static final String TAG_SUBJECT_DISTANCE_RANGE = "SubjectDistanceRange";
/** Type is int. */
public static final String TAG_SUBJECT_LOCATION = "SubjectLocation";
/** Type is String. */
public static final String TAG_USER_COMMENT = "UserComment";
/** Type is int. */
public static final String TAG_WHITE_BALANCE = "WhiteBalance";
/**
* The altitude (in meters) based on the reference in TAG_GPS_ALTITUDE_REF.
* Type is rational.
*/
public static final String TAG_GPS_ALTITUDE = "GPSAltitude";
/**
* 0 if the altitude is above sea level. 1 if the altitude is below sea
* level. Type is int.
*/
public static final String TAG_GPS_ALTITUDE_REF = "GPSAltitudeRef";
/** Type is String. */
public static final String TAG_GPS_AREA_INFORMATION = "GPSAreaInformation";
/** Type is rational. */
public static final String TAG_GPS_DOP = "GPSDOP";
/** Type is String. */
public static final String TAG_GPS_DATESTAMP = "GPSDateStamp";
/** Type is rational. */
public static final String TAG_GPS_DEST_BEARING = "GPSDestBearing";
/** Type is String. */
public static final String TAG_GPS_DEST_BEARING_REF = "GPSDestBearingRef";
/** Type is rational. */
public static final String TAG_GPS_DEST_DISTANCE = "GPSDestDistance";
/** Type is String. */
public static final String TAG_GPS_DEST_DISTANCE_REF = "GPSDestDistanceRef";
/** Type is rational. */
public static final String TAG_GPS_DEST_LATITUDE = "GPSDestLatitude";
/** Type is String. */
public static final String TAG_GPS_DEST_LATITUDE_REF = "GPSDestLatitudeRef";
/** Type is rational. */
public static final String TAG_GPS_DEST_LONGITUDE = "GPSDestLongitude";
/** Type is String. */
public static final String TAG_GPS_DEST_LONGITUDE_REF = "GPSDestLongitudeRef";
/** Type is int. */
public static final String TAG_GPS_DIFFERENTIAL = "GPSDifferential";
/** Type is rational. */
public static final String TAG_GPS_IMG_DIRECTION = "GPSImgDirection";
/** Type is String. */
public static final String TAG_GPS_IMG_DIRECTION_REF = "GPSImgDirectionRef";
/** 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";
/** 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";
/** Type is String. */
public static final String TAG_GPS_MAP_DATUM = "GPSMapDatum";
/** Type is String. */
public static final String TAG_GPS_MEASURE_MODE = "GPSMeasureMode";
/** Type is String. Name of GPS processing method used for location finding. */
public static final String TAG_GPS_PROCESSING_METHOD = "GPSProcessingMethod";
/** Type is String. */
public static final String TAG_GPS_SATELLITES = "GPSSatellites";
/** Type is rational. */
public static final String TAG_GPS_SPEED = "GPSSpeed";
/** Type is String. */
public static final String TAG_GPS_SPEED_REF = "GPSSpeedRef";
/** Type is String. */
public static final String TAG_GPS_STATUS = "GPSStatus";
/** 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";
/** Type is String. */
public static final String TAG_GPS_TRACK_REF = "GPSTrackRef";
/** Type is String. */
public static final String TAG_GPS_VERSION_ID = "GPSVersionID";
/** Type is String. */
public static final String TAG_INTEROPERABILITY_INDEX = "InteroperabilityIndex";
/** Type is int. */
public static final String TAG_THUMBNAIL_IMAGE_LENGTH = "ThumbnailImageLength";
/** Type is int. */
public static final String TAG_THUMBNAIL_IMAGE_WIDTH = "ThumbnailImageWidth";
// Private tags used for pointing the other IFD offset. The types of the following tags are int.
private static final String TAG_EXIF_IFD_POINTER = "ExifIFDPointer";
private static final String TAG_GPS_INFO_IFD_POINTER = "GPSInfoIFDPointer";
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";
// Constants used for the Orientation Exif tag.
public static final int ORIENTATION_UNDEFINED = 0;
public static final int ORIENTATION_NORMAL = 1;
public static final int ORIENTATION_FLIP_HORIZONTAL = 2; // left right reversed mirror
public static final int ORIENTATION_ROTATE_180 = 3;
public static final int ORIENTATION_FLIP_VERTICAL = 4; // upside down mirror
// flipped about top-left <--> bottom-right axis
public static final int ORIENTATION_TRANSPOSE = 5;
public static final int ORIENTATION_ROTATE_90 = 6; // rotate 90 cw to right it
// flipped about top-right <--> bottom-left axis
public static final int ORIENTATION_TRANSVERSE = 7;
public static final int ORIENTATION_ROTATE_270 = 8; // rotate 270 to right it
// Constants used for white balance
public static final int WHITEBALANCE_AUTO = 0;
public static final int WHITEBALANCE_MANUAL = 1;
private static final byte[] JPEG_SIGNATURE = new byte[] {(byte) 0xff, (byte) 0xd8, (byte) 0xff};
private static final int JPEG_SIGNATURE_SIZE = 3;
private static SimpleDateFormat sFormatter;
// See Exchangeable image file format for digital still cameras: Exif version 2.2.
// The following values are for parsing EXIF data area. There are tag groups in EXIF data area.
// They are called "Image File Directory". They have multiple data formats to cover various
// image metadata from GPS longitude to camera model name.
// Types of Exif byte alignments (see JEITA CP-3451 page 10)
private static final short BYTE_ALIGN_II = 0x4949; // II: Intel order
private static final short BYTE_ALIGN_MM = 0x4d4d; // MM: Motorola order
// Formats for the value in IFD entry (See TIFF 6.0 spec Types page 15).
private static final int IFD_FORMAT_BYTE = 1;
private static final int IFD_FORMAT_STRING = 2;
private static final int IFD_FORMAT_USHORT = 3;
private static final int IFD_FORMAT_ULONG = 4;
private static final int IFD_FORMAT_URATIONAL = 5;
private static final int IFD_FORMAT_SBYTE = 6;
private static final int IFD_FORMAT_UNDEFINED = 7;
private static final int IFD_FORMAT_SSHORT = 8;
private static final int IFD_FORMAT_SLONG = 9;
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
};
private static final byte[] EXIF_ASCII_PREFIX = new byte[] {
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, 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, 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, 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, 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, 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, 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.
// The following values are used for indicating pointers to the other Image File Directorys.
// Indices of Exif Ifd tag groups
private static final int IFD_TIFF_HINT = 0;
private static final int IFD_EXIF_HINT = 1;
private static final int IFD_GPS_HINT = 2;
private static final int IFD_INTEROPERABILITY_HINT = 3;
private static final int IFD_THUMBNAIL_HINT = 4;
// List of Exif tag groups
private static final ExifTag[][] EXIF_TAGS = new ExifTag[][] {
IFD_TIFF_TAGS, IFD_EXIF_TAGS, IFD_GPS_TAGS, IFD_INTEROPERABILITY_TAGS,
IFD_THUMBNAIL_TAGS
};
// 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, 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[] {
IFD_EXIF_HINT, IFD_GPS_HINT, IFD_INTEROPERABILITY_HINT
};
// Tags for indicating the thumbnail offset and length
private static final ExifTag JPEG_INTERCHANGE_FORMAT_TAG =
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, 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