getSections() {
+ return mSections;
+ }
+}
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.java b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.java
new file mode 100644
index 00000000..89f555fe
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.java
@@ -0,0 +1,2853 @@
+/*
+ * Copyright (C) 2013 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 it.sephiroth.android.library.exif2;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * This class provides methods and constants for reading and writing jpeg file
+ * metadata. It contains a collection of ExifTags, and a collection of
+ * definitions for creating valid ExifTags. The collection of ExifTags can be
+ * updated by: reading new ones from a file, deleting or adding existing ones,
+ * or building new ExifTags from a tag definition. These ExifTags can be written
+ * to a valid jpeg image as exif metadata.
+ *
+ * Each ExifTag has a tag ID (TID) and is stored in a specific image file
+ * directory (IFD) as specified by the exif standard. A tag definition can be
+ * looked up with a constant that is a combination of TID and IFD. This
+ * definition has information about the type, number of components, and valid
+ * IFDs for a tag.
+ *
+ * @see ExifTag
+ */
+public class ExifInterface {
+ private static final String TAG = "ExifInterface";
+
+ public static final int TAG_NULL = - 1;
+ public static final int IFD_NULL = - 1;
+ public static final int DEFINITION_NULL = 0;
+
+ /**
+ * Tag constants for Jeita EXIF 2.2
+ */
+
+ // IFD 0
+ public static final int TAG_IMAGE_WIDTH = defineTag( IfdId.TYPE_IFD_0, (short) 0x0100 );
+ public static final int TAG_IMAGE_LENGTH = defineTag( IfdId.TYPE_IFD_0, (short) 0x0101 ); // Image height
+ public static final int TAG_BITS_PER_SAMPLE = defineTag( IfdId.TYPE_IFD_0, (short) 0x0102 );
+
+ /**
+ * Value is unsigned int.
+ * (Read only tag) The compression scheme used for the image data. When a primary image is JPEG compressed, this designation is
+ * not necessary and is omitted. When thumbnails use JPEG compression, this tag value is set to 6.
+ *
+ * - 1 = uncompressed
+ * - 6 = JPEG compression (thumbnails only)
+ * - Other = reserved
+ */
+ public static final int TAG_COMPRESSION = defineTag( IfdId.TYPE_IFD_0, (short) 0x0103 );
+ public static final int TAG_PHOTOMETRIC_INTERPRETATION = defineTag( IfdId.TYPE_IFD_0, (short) 0x0106 );
+ public static final int TAG_IMAGE_DESCRIPTION = defineTag( IfdId.TYPE_IFD_0, (short) 0x010E );
+
+ /**
+ * Value is ascii string
+ * The manufacturer of the recording equipment. This is the manufacturer of the DSC, scanner, video digitizer or other equipment
+ * that generated the image. When the field is left blank, it is treated as unknown.
+ */
+ public static final int TAG_MAKE = defineTag( IfdId.TYPE_IFD_0, (short) 0x010F );
+
+ /**
+ * Value is ascii string
+ * The model name or model number of the equipment. This is the model name of number of the DSC, scanner, video digitizer or
+ * other equipment that generated the image. When the field is left blank, it is treated as unknown.
+ */
+ public static final int TAG_MODEL = defineTag( IfdId.TYPE_IFD_0, (short) 0x0110 );
+ public static final int TAG_STRIP_OFFSETS = defineTag( IfdId.TYPE_IFD_0, (short) 0x0111 );
+
+ /**
+ * Value is int
+ * The orientation of the camera relative to the scene, when the image was captured. The start point of stored data is:
+ *
+ * - '0' undefined
+ * - '1' normal
+ * - '2' flip horizontal
+ * - '3' rotate 180
+ * - '4' flip vertical
+ * - '5' transpose, flipped about top-left <--> bottom-right axis
+ * - '6' rotate 90 cw
+ * - '7' transverse, flipped about top-right <--> bottom-left axis
+ * - '8' rotate 270
+ * - '9' undefined
+ *
+ */
+ public static final int TAG_ORIENTATION = defineTag( IfdId.TYPE_IFD_0, (short) 0x0112 );
+ public static final int TAG_SAMPLES_PER_PIXEL = defineTag( IfdId.TYPE_IFD_0, (short) 0x0115 );
+ public static final int TAG_ROWS_PER_STRIP = defineTag( IfdId.TYPE_IFD_0, (short) 0x0116 );
+ public static final int TAG_STRIP_BYTE_COUNTS = defineTag( IfdId.TYPE_IFD_0, (short) 0x0117 );
+
+ public static final int TAG_INTEROP_VERSION = defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short)0x0002);
+
+ /**
+ * Value is unsigned double.
+ * Display/Print resolution of image. Large number of digicam uses 1/72inch, but it has no mean because personal computer doesn't
+ * use this value to display/print out.
+ */
+ public static final int TAG_X_RESOLUTION = defineTag( IfdId.TYPE_IFD_0, (short) 0x011A );
+
+ /**
+ * @see #TAG_X_RESOLUTION
+ */
+ public static final int TAG_Y_RESOLUTION = defineTag( IfdId.TYPE_IFD_0, (short) 0x011B );
+ public static final int TAG_PLANAR_CONFIGURATION = defineTag( IfdId.TYPE_IFD_0, (short) 0x011C );
+
+ /**
+ * Value is unsigned int.
+ * Unit of XResolution(0x011a)/YResolution(0x011b)
+ *
+ * - '1' means no-unit ( use inch )
+ * - '2' inch
+ * - '3' centimeter
+ * - '4' millimeter
+ * - '5' micrometer
+ *
+ */
+ public static final int TAG_RESOLUTION_UNIT = defineTag( IfdId.TYPE_IFD_0, (short) 0x0128 );
+ public static final int TAG_TRANSFER_FUNCTION = defineTag( IfdId.TYPE_IFD_0, (short) 0x012D );
+
+ /**
+ * Value is ascii string
+ * Shows firmware(internal software of digicam) version number.
+ */
+ public static final int TAG_SOFTWARE = defineTag( IfdId.TYPE_IFD_0, (short) 0x0131 );
+
+ /**
+ * Value is ascii string (20)
+ * Date/Time of image was last modified. Data format is "YYYY:MM:DD HH:MM:SS"+0x00, total 20bytes. In usual, it has the same
+ * value of DateTimeOriginal(0x9003)
+ */
+ public static final int TAG_DATE_TIME = defineTag( IfdId.TYPE_IFD_0, (short) 0x0132 );
+
+ /**
+ * Vallue is ascii String
+ * This tag records the name of the camera owner, photographer or image creator. The detailed format is not specified, but it is
+ * recommended that the information be written as in the example below for ease of Interoperability. When the field is left
+ * blank, it is treated as unknown.
+ */
+ public static final int TAG_ARTIST = defineTag( IfdId.TYPE_IFD_0, (short) 0x013B );
+ public static final int TAG_WHITE_POINT = defineTag( IfdId.TYPE_IFD_0, (short) 0x013E );
+ public static final int TAG_PRIMARY_CHROMATICITIES = defineTag( IfdId.TYPE_IFD_0, (short) 0x013F );
+ public static final int TAG_Y_CB_CR_COEFFICIENTS = defineTag( IfdId.TYPE_IFD_0, (short) 0x0211 );
+ public static final int TAG_Y_CB_CR_SUB_SAMPLING = defineTag( IfdId.TYPE_IFD_0, (short) 0x0212 );
+ public static final int TAG_Y_CB_CR_POSITIONING = defineTag( IfdId.TYPE_IFD_0, (short) 0x0213 );
+ public static final int TAG_REFERENCE_BLACK_WHITE = defineTag( IfdId.TYPE_IFD_0, (short) 0x0214 );
+
+ /**
+ * Values is ascii string
+ * Shows copyright information
+ */
+ public static final int TAG_COPYRIGHT = defineTag( IfdId.TYPE_IFD_0, (short) 0x8298 );
+ public static final int TAG_EXIF_IFD = defineTag( IfdId.TYPE_IFD_0, (short) 0x8769 );
+ public static final int TAG_GPS_IFD = defineTag( IfdId.TYPE_IFD_0, (short) 0x8825 );
+ // IFD 1
+ public static final int TAG_JPEG_INTERCHANGE_FORMAT = defineTag( IfdId.TYPE_IFD_1, (short) 0x0201 );
+ public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = defineTag( IfdId.TYPE_IFD_1, (short) 0x0202 );
+ // IFD Exif Tags
+
+ /**
+ * Value is unsigned double
+ * Exposure time (reciprocal of shutter speed). Unit is second
+ */
+ public static final int TAG_EXPOSURE_TIME = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x829A );
+
+ /**
+ * Value is unsigned double
+ * The actual F-number(F-stop) of lens when the image was taken
+ *
+ * @see #TAG_APERTURE_VALUE
+ */
+ public static final int TAG_F_NUMBER = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x829D );
+
+ /**
+ * Value is unsigned int.
+ * Exposure program that the camera used when image was taken.
+ *
+ * - '1' means manual control
+ * - '2' program normal
+ * - '3' aperture priority
+ * - '4' shutter priority
+ * - '5' program creative (slow program)
+ * - '6' program action(high-speed program)
+ * - '7' portrait mode
+ * - '8' landscape mode.
+ *
+ */
+ public static final int TAG_EXPOSURE_PROGRAM = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x8822 );
+ public static final int TAG_SPECTRAL_SENSITIVITY = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x8824 );
+
+ /**
+ * Value is unsigned int.
+ * CCD sensitivity equivalent to Ag-Hr film speedrate.
+ * Indicates the ISO Speed and ISO Latitude of the camera or input device as specified in ISO 12232
+ */
+ public static final int TAG_ISO_SPEED_RATINGS = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x8827 );
+ public static final int TAG_OECF = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x8828 );
+
+ /**
+ * ASCII string (4).
+ * The version of this standard supported. Nonexistence of this field is taken to mean nonconformance to the standard (see
+ * section 4.2). Conformance to this standard is indicated by recording "0220" as 4-byte ASCII
+ */
+ public static final int TAG_EXIF_VERSION = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9000 );
+
+ /**
+ * Value is ascii string (20)
+ * Date/Time of original image taken. This value should not be modified by user program.
+ */
+ public static final int TAG_DATE_TIME_ORIGINAL = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9003 );
+
+ /**
+ * Value is ascii string (20)
+ * Date/Time of image digitized. Usually, it contains the same value of DateTimeOriginal(0x9003).
+ */
+ public static final int TAG_DATE_TIME_DIGITIZED = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9004 );
+ public static final int TAG_COMPONENTS_CONFIGURATION = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9101 );
+ public static final int TAG_COMPRESSED_BITS_PER_PIXEL = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9102 );
+
+ /**
+ * Value is signed double.
+ * Shutter speed. To convert this value to ordinary 'Shutter Speed'; calculate this value's power of 2, then reciprocal. For
+ * example, if value is '4', shutter speed is 1/(2^4)=1/16 second.
+ */
+ public static final int TAG_SHUTTER_SPEED_VALUE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9201 );
+
+
+ /**
+ * Value is unsigned double
+ * The actual aperture value of lens when the image was taken.
+ * To convert this value to ordinary F-number(F-stop), calculate this value's power of root 2 (=1.4142).
+ * For example, if value is '5', F-number is 1.4142^5 = F5.6
+ *
+ *
+ * FNumber = Math.exp( ApertureValue * Math.log( 2 ) * 0.5 );
+ *
+ *
+ * @see #TAG_F_NUMBER
+ */
+ public static final int TAG_APERTURE_VALUE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9202 );
+
+ /**
+ * Value is signed double
+ * Brightness of taken subject, unit is EV.
+ */
+ public static final int TAG_BRIGHTNESS_VALUE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9203 );
+
+ /**
+ * Value is signed double.
+ * The exposure bias. The unit is the APEX value. Ordinarily it is given in the range of -99.99 to 99.99
+ */
+ public static final int TAG_EXPOSURE_BIAS_VALUE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9204 );
+
+ /**
+ * Value is unsigned double.
+ * Maximum aperture value of lens.
+ * You can convert to F-number by calculating power of root 2 (same process of ApertureValue(0x9202).
+ *
+ *
+ * FNumber = Math.exp( MaxApertureValue * Math.log( 2 ) * 0.5 )
+ *
+ */
+ public static final int TAG_MAX_APERTURE_VALUE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9205 );
+
+ /**
+ * Value if signed double.
+ * Distance to focus point, unit is meter. If value < 0 then focus point is infinite
+ */
+ public static final int TAG_SUBJECT_DISTANCE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9206 );
+
+ /**
+ * Value is unsigned int.
+ * Exposure metering method:
+ *
+ * - 0 = unknown
+ * - 1 = Average
+ * - 2 = CenterWeightedAverage
+ * - 3 = Spot
+ * - 4 = MultiSpot
+ * - 5 = Pattern
+ * - 6 = Partial
+ * - Other = reserved
+ * - 255 = other
+ *
+ */
+ public static final int TAG_METERING_MODE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9207 );
+
+ /**
+ * Value is unsigned int.
+ * Light source, actually this means white balance setting.
+ *
+ * - 0 = means auto
+ * - 1 = Daylight
+ * - 2 = Fluorescent
+ * - 3 = Tungsten (incandescent light)
+ * - 4 = Flash
+ * - 9 = Fine weather
+ * - 10 = Cloudy weather
+ * - 11 = Shade
+ * - 12 = Daylight fluorescent (D 5700 - 7100K)
+ * - 13 = Day white fluorescent (N 4600 - 5400K)
+ * - 14 = Cool white fluorescent (W 3900 - 4500K)
+ * - 15 = White fluorescent (WW 3200 - 3700K)
+ * - 17 = Standard light A
+ * - 18 = Standard light B
+ * - 19 = Standard light C
+ * - 20 = D55
+ * - 21 = D65
+ * - 22 = D75
+ * - 23 = D50
+ * - 24 = ISO studio tungsten
+ * - 255 = other light source
+ * - Other = reserved
+ *
+ */
+ public static final int TAG_LIGHT_SOURCE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9208 );
+
+ /**
+ * Value is unsigned integer
+ * The 8 bits can be extracted and evaluated in this way:
+ *
+ * - Bit 0 indicates the flash firing status
+ * - bits 1 and 2 indicate the flash return status
+ * - bits 3 and 4 indicate the flash mode
+ * - bit 5 indicates whether the flash function is present
+ * - and bit 6 indicates "red eye" mode
+ * - bit 7 unused
+ *
+ *
+ * Resulting Flash tag values are:
+ *
+ * - 0000.H = Flash did not fire
+ * - 0001.H = Flash fired
+ * - 0005.H = Strobe return light not detected
+ * - 0007.H = Strobe return light detected
+ * - 0009.H = Flash fired, compulsory flash mode
+ * - 000D.H = Flash fired, compulsory flash mode, return light not detected
+ * - 000F.H = Flash fired, compulsory flash mode, return light detected
+ * - 0010.H = Flash did not fire, compulsory flash mode
+ * - 0018.H = Flash did not fire, auto mode
+ * - 0019.H = Flash fired, auto mode
+ * - 001D.H = Flash fired, auto mode, return light not detected
+ * - 001F.H = Flash fired, auto mode, return light detected
+ * - 0020.H = No flash function
+ * - 0041.H = Flash fired, red-eye reduction mode
+ * - 0045.H = Flash fired, red-eye reduction mode, return light not detected
+ * - 0047.H = Flash fired, red-eye reduction mode, return light detected
+ * - 0049.H = Flash fired, compulsory flash mode, red-eye reduction mode
+ * - 004D.H = Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected
+ * - 004F.H = Flash fired, compulsory flash mode, red-eye reduction mode, return light detected
+ * - 0059.H = Flash fired, auto mode, red-eye reduction mode
+ * - 005D.H = Flash fired, auto mode, return light not detected, red-eye reduction mode
+ * - 005F.H = Flash fired, auto mode, return light detected, red-eye reduction mode
+ * - Other = reserved
+ *
+ *
+ * @see http://www.exif.org/Exif2-2.PDF
+ */
+ public static final int TAG_FLASH = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9209 );
+
+ /**
+ * Value is unsigned double
+ * Focal length of lens used to take image. Unit is millimeter.
+ */
+ public static final int TAG_FOCAL_LENGTH = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x920A );
+ public static final int TAG_SUBJECT_AREA = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9214 );
+ public static final int TAG_MAKER_NOTE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x927C );
+ public static final int TAG_USER_COMMENT = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9286 );
+ public static final int TAG_SUB_SEC_TIME = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9290 );
+ public static final int TAG_SUB_SEC_TIME_ORIGINAL = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9291 );
+ public static final int TAG_SUB_SEC_TIME_DIGITIZED = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x9292 );
+ public static final int TAG_FLASHPIX_VERSION = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA000 );
+
+ /**
+ * Value is int.
+ * Normally sRGB (=1) is used to define the color space based on the PC monitor conditions and environment. If a color space
+ * other than sRGB is used, Uncalibrated (=FFFF.H) is set. Image data recorded as Uncalibrated can be treated as sRGB when it is
+ * converted to Flashpix. On sRGB see Annex E.
+ *
+ * - '1' = sRGB
+ * - 'FFFF' = Uncalibrated
+ * - 'other' = Reserved
+ *
+ */
+ public static final int TAG_COLOR_SPACE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA001 );
+
+ /**
+ * Value is unsigned int.
+ * Specific to compressed data; the valid width of the meaningful image. When a compressed file is recorded, the valid width of
+ * the meaningful image shall be recorded in this tag, whether or not there is padding data or a restart marker. This tag should
+ * not exist in an uncompressed file.
+ */
+ public static final int TAG_PIXEL_X_DIMENSION = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA002 );
+
+ /**
+ * @see #TAG_PIXEL_X_DIMENSION
+ */
+ public static final int TAG_PIXEL_Y_DIMENSION = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA003 );
+ public static final int TAG_RELATED_SOUND_FILE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA004 );
+ public static final int TAG_INTEROPERABILITY_IFD = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA005 );
+ public static final int TAG_FLASH_ENERGY = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA20B );
+ public static final int TAG_SPATIAL_FREQUENCY_RESPONSE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA20C );
+
+ /**
+ * Value is unsigned double.
+ * Indicates the number of pixels in the image width (X) direction per FocalPlaneResolutionUnit on the camera focal plane. CCD's
+ * pixel density
+ *
+ * @see #TAG_FOCAL_PLANE_RESOLUTION_UNIT
+ */
+ public static final int TAG_FOCAL_PLANE_X_RESOLUTION = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA20E );
+
+ /**
+ * @see #TAG_FOCAL_PLANE_X_RESOLUTION
+ */
+ public static final int TAG_FOCAL_PLANE_Y_RESOLUTION = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA20F );
+
+ /**
+ * Value is unsigned int.
+ * Unit of FocalPlaneXResoluton/FocalPlaneYResolution.
+ *
+ * - '1' means no-unit
+ * - '2' inch
+ * - '3' centimeter
+ * - '4' millimeter
+ * - '5' micrometer
+ *
+ *
+ * This tag can be used to calculate the CCD Width:
+ *
+ *
+ * CCDWidth = ( PixelXDimension * FocalPlaneResolutionUnit / FocalPlaneXResolution )
+ *
+ */
+ public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA210 );
+ public static final int TAG_SUBJECT_LOCATION = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA214 );
+ public static final int TAG_EXPOSURE_INDEX = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA215 );
+
+ /**
+ * Value is unsigned int.
+ * Indicates the image sensor type on the camera or input device. The values are as follows:
+ *
+ * - 1 = Not defined
+ * - 2 = One-chip color area sensor
+ * - 3 = Two-chip color area sensor JEITA CP-3451 - 41
+ * - 4 = Three-chip color area sensor
+ * - 5 = Color sequential area sensor
+ * - 7 = Trilinear sensor
+ * - 8 = Color sequential linear sensor
+ * - Other = reserved
+ *
+ */
+ public static final int TAG_SENSING_METHOD = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA217 );
+ public static final int TAG_FILE_SOURCE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA300 );
+ public static final int TAG_SCENE_TYPE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA301 );
+ public static final int TAG_CFA_PATTERN = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA302 );
+ public static final int TAG_CUSTOM_RENDERED = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA401 );
+
+ /**
+ * Value is int.
+ * This tag indicates the exposure mode set when the image was shot. In auto-bracketing mode, the camera shoots a series of
+ * frames of the same scene at different exposure settings.
+ *
+ * - 0 = Auto exposure
+ * - 1 = Manual exposure
+ * - 2 = Auto bracket
+ * - Other = reserved
+ *
+ */
+ public static final int TAG_EXPOSURE_MODE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA402 );
+ public static final int TAG_WHITE_BALANCE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA403 );
+
+ /**
+ * Value is double.
+ * This tag indicates the digital zoom ratio when the image was shot. If the numerator of the recorded value is 0, this indicates
+ * that digital zoom was not used
+ */
+ public static final int TAG_DIGITAL_ZOOM_RATIO = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA404 );
+
+ /**
+ * Value is unsigned int.
+ * This tag indicates the equivalent focal length assuming a 35mm film camera, in mm.
+ * Exif 2.2 tag, usually not present, it can be calculated by:
+ *
+ *
+ * CCDWidth = ( PixelXDimension * FocalplaneUnits / FocalplaneXRes );
+ * FocalLengthIn35mmFilm = ( FocalLength / CCDWidth * 36 + 0.5 );
+ *
+ */
+ public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA405 );
+
+ /**
+ * Value is int.
+ * This tag indicates the type of scene that was shot. It can also be used to record the mode in which the image was shot. Note
+ * that this differs from the scene type (SceneType) tag.
+ *
+ * - 0 = Standard
+ * - 1 = Landscape
+ * - 2 = Portrait
+ * - 3 = Night scene
+ * - Other = reserved
+ *
+ */
+ public static final int TAG_SCENE_CAPTURE_TYPE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA406 );
+
+ /**
+ * Value is int.
+ * This tag indicates the degree of overall image gain adjustment.
+ *
+ * - 0 = None
+ * - 1 = Low gain up
+ * - 2 = High gain up
+ * - 3 = Low gain down
+ * - 4 = High gain down
+ * - Other = reserved
+ *
+ */
+ public static final int TAG_GAIN_CONTROL = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA407 );
+
+ /**
+ * Value is int.
+ * This tag indicates the direction of contrast processing applied by the camera when the image was shot.
+ *
+ * - 0 = Normal
+ * - 1 = Soft
+ * - 2 = Hard
+ * - Other = reserved
+ *
+ */
+ public static final int TAG_CONTRAST = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA408 );
+
+ /**
+ * Value is int.
+ * This tag indicates the direction of saturation processing applied by the camera when the image was shot.
+ *
+ * - 0 = Normal
+ * - 1 = Low saturation
+ * - 2 = High saturation
+ * - Other = reserved
+ *
+ */
+ public static final int TAG_SATURATION = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA409 );
+
+ /**
+ * Value is int.
+ * This tag indicates the direction of sharpness processing applied by the camera when the image was shot
+ *
+ * - 0 = Normal
+ * - 1 = Soft
+ * - 2 = Hard
+ * - Other = reserved
+ *
+ */
+ public static final int TAG_SHARPNESS = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA40A );
+ public static final int TAG_DEVICE_SETTING_DESCRIPTION = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA40B );
+
+ /**
+ * Value is int.
+ * This tag indicates the distance to the subject.
+ *
+ * - 0 = unknown
+ * - 1 = Macro
+ * - 2 = Close view
+ * - 3 = Distant view
+ * - Other = reserved
+ *
+ */
+ public static final int TAG_SUBJECT_DISTANCE_RANGE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA40C );
+
+ /**
+ * {@link ExifTag#TYPE_ASCII}
+ */
+ public static final int TAG_IMAGE_UNIQUE_ID = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA420 );
+
+ /**
+ * Lens Specifications. The value it's a 4 rational containing:
+ *
+ * - Minimum focal length (in mm)
+ * - Maximum focal length (in mm)
+ * - Minimum F Number in the minimum focal length
+ * - Maximum F Number in the maximum focal length
+ *
+ *
+ * {@link ExifTag#TYPE_RATIONAL}
+ * @since EXIF 2.3
+ * @see it.sephiroth.android.library.exif2.ExifUtil#processLensSpecifications(Rational[])
+ */
+ public static final int TAG_LENS_SPECS = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA432 );
+
+ /**
+ * Lens maker
+ * {@link ExifTag#TYPE_ASCII}
+ * @since EXIF 2.3
+ */
+ public static final int TAG_LENS_MAKE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA433 );
+ /**
+ * Lens model name and number
+ * {@link ExifTag#TYPE_ASCII}
+ * @since EXIF 2.3
+ */
+ public static final int TAG_LENS_MODEL = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0xA434 );
+
+ /**
+ * The SensitivityType tag indicates which one of the parameters of ISO12232 is the
+ * PhotographicSensitivity tag. Although it is an optional tag, it should be recorded
+ * when a PhotographicSensitivity tag is recorded.
+ * Value = 4, 5, 6, or 7 may be used in case that the values of plural
+ * parameters are the same.
+ * Values:
+ *
+ * - 0: Unknown
+ * - 1: Standardoutputsensitivity(SOS)
+ * - 2: Recommended exposure index (REI)
+ * - 3: ISOspeed
+ * - 4: Standard output sensitivity (SOS) and recommended exposure index (REI)
+ * - 5: Standardoutputsensitivity(SOS)andISOspeed
+ * - 6: Recommendedexposureindex(REI)andISOspeed
+ * - 7: Standard output sensitivity (SOS) and recommended exposure index (REI) and ISO speed
+ * - Other: Reserved
+ *
+ *
+ * {@link ExifTag#TYPE_UNSIGNED_SHORT}
+ * @see it.sephiroth.android.library.exif2.ExifInterface.SensitivityType
+ * @since EXIF 2.3
+ */
+ public static final int TAG_SENSITIVITY_TYPE = defineTag( IfdId.TYPE_IFD_EXIF, (short) 0x8830 );
+
+
+ // IFD GPS tags
+ public static final int TAG_GPS_VERSION_ID = defineTag( IfdId.TYPE_IFD_GPS, (short) 0 );
+
+ /**
+ * Value is string(1)
+ * Indicates whether the latitude is north or south latitude. The ASCII value 'N' indicates north latitude, and 'S' is south latitude.
+ */
+ public static final int TAG_GPS_LATITUDE_REF = defineTag( IfdId.TYPE_IFD_GPS, (short) 1 );
+
+ /**
+ * Value is string.
+ * Indicates the latitude. The latitude is expressed as three RATIONAL values giving the degrees, minutes, and
+ * seconds, respectively. If latitude is expressed as degrees, minutes and seconds, a typical format would be
+ * dd/1,mm/1,ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two
+ * decimal places, the format would be dd/1,mmmm/100,0/1.
+ */
+ public static final int TAG_GPS_LATITUDE = defineTag( IfdId.TYPE_IFD_GPS, (short) 2 );
+
+ /**
+ * Value is string(1)
+ * Indicates whether the longitude is east or west longitude. ASCII 'E' indicates east longitude, and 'W' is west longitude.
+ */
+ public static final int TAG_GPS_LONGITUDE_REF = defineTag( IfdId.TYPE_IFD_GPS, (short) 3 );
+
+ /**
+ * Value is string.
+ * Indicates the longitude. The longitude is expressed as three RATIONAL values giving the degrees, minutes, and
+ * seconds, respectively. If longitude is expressed as degrees, minutes and seconds, a typical format would be
+ * ddd/1,mm/1,ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two
+ * decimal places, the format would be ddd/1,mmmm/100,0/1.
+ */
+ public static final int TAG_GPS_LONGITUDE = defineTag( IfdId.TYPE_IFD_GPS, (short) 4 );
+
+ /**
+ * Value is byte
+ * Indicates the altitude used as the reference altitude. If the reference is sea level and the altitude is above sea level,
+ * 0 is given. If the altitude is below sea level, a value of 1 is given and the altitude is indicated as an absolute value in
+ * the GPSAltitude tag. The reference unit is meters. Note that this tag is BYTE type, unlike other reference tags
+ */
+ public static final int TAG_GPS_ALTITUDE_REF = defineTag( IfdId.TYPE_IFD_GPS, (short) 5 );
+
+ /**
+ * Value is string.
+ * Indicates the altitude based on the reference in GPSAltitudeRef. Altitude is expressed as one RATIONAL value. The reference unit is meters.
+ */
+ public static final int TAG_GPS_ALTITUDE = defineTag( IfdId.TYPE_IFD_GPS, (short) 6 );
+ public static final int TAG_GPS_TIME_STAMP = defineTag( IfdId.TYPE_IFD_GPS, (short) 7 );
+ public static final int TAG_GPS_SATTELLITES = defineTag( IfdId.TYPE_IFD_GPS, (short) 8 );
+ public static final int TAG_GPS_STATUS = defineTag( IfdId.TYPE_IFD_GPS, (short) 9 );
+ public static final int TAG_GPS_MEASURE_MODE = defineTag( IfdId.TYPE_IFD_GPS, (short) 10 );
+ public static final int TAG_GPS_DOP = defineTag( IfdId.TYPE_IFD_GPS, (short) 11 );
+
+ /**
+ * Value is string(1).
+ * Indicates the unit used to express the GPS receiver speed of movement. 'K' 'M' and 'N' represents kilometers per hour, miles per hour, and knots.
+ */
+ public static final int TAG_GPS_SPEED_REF = defineTag( IfdId.TYPE_IFD_GPS, (short) 12 );
+
+ /**
+ * Value is string.
+ * Indicates the speed of GPS receiver movement
+ */
+ public static final int TAG_GPS_SPEED = defineTag( IfdId.TYPE_IFD_GPS, (short) 13 );
+ public static final int TAG_GPS_TRACK_REF = defineTag( IfdId.TYPE_IFD_GPS, (short) 14 );
+ public static final int TAG_GPS_TRACK = defineTag( IfdId.TYPE_IFD_GPS, (short) 15 );
+ public static final int TAG_GPS_IMG_DIRECTION_REF = defineTag( IfdId.TYPE_IFD_GPS, (short) 16 );
+ public static final int TAG_GPS_IMG_DIRECTION = defineTag( IfdId.TYPE_IFD_GPS, (short) 17 );
+ public static final int TAG_GPS_MAP_DATUM = defineTag( IfdId.TYPE_IFD_GPS, (short) 18 );
+ public static final int TAG_GPS_DEST_LATITUDE_REF = defineTag( IfdId.TYPE_IFD_GPS, (short) 19 );
+ public static final int TAG_GPS_DEST_LATITUDE = defineTag( IfdId.TYPE_IFD_GPS, (short) 20 );
+ public static final int TAG_GPS_DEST_LONGITUDE_REF = defineTag( IfdId.TYPE_IFD_GPS, (short) 21 );
+ public static final int TAG_GPS_DEST_LONGITUDE = defineTag( IfdId.TYPE_IFD_GPS, (short) 22 );
+ public static final int TAG_GPS_DEST_BEARING_REF = defineTag( IfdId.TYPE_IFD_GPS, (short) 23 );
+ public static final int TAG_GPS_DEST_BEARING = defineTag( IfdId.TYPE_IFD_GPS, (short) 24 );
+ public static final int TAG_GPS_DEST_DISTANCE_REF = defineTag( IfdId.TYPE_IFD_GPS, (short) 25 );
+ public static final int TAG_GPS_DEST_DISTANCE = defineTag( IfdId.TYPE_IFD_GPS, (short) 26 );
+ public static final int TAG_GPS_PROCESSING_METHOD = defineTag( IfdId.TYPE_IFD_GPS, (short) 27 );
+ public static final int TAG_GPS_AREA_INFORMATION = defineTag( IfdId.TYPE_IFD_GPS, (short) 28 );
+ public static final int TAG_GPS_DATE_STAMP = defineTag( IfdId.TYPE_IFD_GPS, (short) 29 );
+ public static final int TAG_GPS_DIFFERENTIAL = defineTag( IfdId.TYPE_IFD_GPS, (short) 30 );
+ // IFD Interoperability tags
+ public static final int TAG_INTEROPERABILITY_INDEX = defineTag( IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1 );
+
+
+
+
+ public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
+ private ExifData mData = new ExifData( DEFAULT_BYTE_ORDER );
+ private static final String NULL_ARGUMENT_STRING = "Argument is null";
+
+ private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
+ private static final DateFormat mGPSDateStampFormat = new SimpleDateFormat( GPS_DATE_FORMAT_STR );
+ private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
+ private static final DateFormat mDateTimeStampFormat = new SimpleDateFormat( DATETIME_FORMAT_STR );
+
+ /**
+ * Tags that contain offset markers. These are included in the banned
+ * defines.
+ */
+ private static HashSet sOffsetTags = new HashSet();
+
+ static {
+ sOffsetTags.add( getTrueTagKey( TAG_GPS_IFD ) );
+ sOffsetTags.add( getTrueTagKey( TAG_EXIF_IFD ) );
+ sOffsetTags.add( getTrueTagKey( TAG_JPEG_INTERCHANGE_FORMAT ) );
+ sOffsetTags.add( getTrueTagKey( TAG_INTEROPERABILITY_IFD ) );
+ sOffsetTags.add( getTrueTagKey( TAG_STRIP_OFFSETS ) );
+ }
+
+ /**
+ * Tags with definitions that cannot be overridden (banned defines).
+ */
+ protected static HashSet sBannedDefines = new HashSet( sOffsetTags );
+
+ static {
+ sBannedDefines.add( getTrueTagKey( TAG_NULL ) );
+ sBannedDefines.add( getTrueTagKey( TAG_JPEG_INTERCHANGE_FORMAT_LENGTH ) );
+ sBannedDefines.add( getTrueTagKey( TAG_STRIP_BYTE_COUNTS ) );
+ }
+
+ private final Calendar mGPSTimeStampCalendar = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) );
+ private SparseIntArray mTagInfo = null;
+
+ public ExifInterface() {
+ mGPSDateStampFormat.setTimeZone( TimeZone.getTimeZone( "UTC" ) );
+ }
+
+ /**
+ * Returns true if tag TID is one of the following: {@link #TAG_EXIF_IFD},
+ * {@link #TAG_GPS_IFD}, {@link #TAG_JPEG_INTERCHANGE_FORMAT},
+ * {@link #TAG_STRIP_OFFSETS}, {@link #TAG_INTEROPERABILITY_IFD}
+ *
+ * Note: defining tags with these TID's is disallowed.
+ *
+ * @param tag a tag's TID (can be obtained from a defined tag constant with
+ * {@link #getTrueTagKey}).
+ * @return true if the TID is that of an offset tag.
+ */
+ protected static boolean isOffsetTag( short tag ) {
+ return sOffsetTags.contains( tag );
+ }
+
+ /**
+ * Returns the Orientation ExifTag value for a given number of degrees.
+ *
+ * @param degrees the amount an image is rotated in degrees.
+ */
+ public static short getOrientationValueForRotation( int degrees ) {
+ degrees %= 360;
+ if( degrees < 0 ) {
+ degrees += 360;
+ }
+ if( degrees < 90 ) {
+ return Orientation.TOP_LEFT; // 0 degrees
+ }
+ else if( degrees < 180 ) {
+ return Orientation.RIGHT_TOP; // 90 degrees cw
+ }
+ else if( degrees < 270 ) {
+ return Orientation.BOTTOM_LEFT; // 180 degrees
+ }
+ else {
+ return Orientation.RIGHT_BOTTOM; // 270 degrees cw
+ }
+ }
+
+ /**
+ * Returns the rotation degrees corresponding to an ExifTag Orientation
+ * value.
+ *
+ * @param orientation the ExifTag Orientation value.
+ */
+ @SuppressWarnings( "unused" )
+ public static int getRotationForOrientationValue( short orientation ) {
+ switch( orientation ) {
+ case Orientation.TOP_LEFT:
+ return 0;
+ case Orientation.RIGHT_TOP:
+ return 90;
+ case Orientation.BOTTOM_LEFT:
+ return 180;
+ case Orientation.RIGHT_BOTTOM:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Given the value from {@link #TAG_FOCAL_PLANE_RESOLUTION_UNIT} or {@link #TAG_RESOLUTION_UNIT}
+ * this method will return the corresponding value in millimeters
+ *
+ * @param resolution {@link #TAG_FOCAL_PLANE_RESOLUTION_UNIT} or {@link #TAG_RESOLUTION_UNIT}
+ * @return resolution in millimeters
+ */
+ @SuppressWarnings( "unused" )
+ public double getResolutionUnit( int resolution ) {
+ switch( resolution ) {
+ case 1:
+ case ResolutionUnit.INCHES:
+ return 25.4;
+
+ case ResolutionUnit.CENTIMETERS:
+ return 10;
+
+ case ResolutionUnit.MILLIMETERS:
+ return 1;
+
+ case ResolutionUnit.MICROMETERS:
+ return .001;
+
+ default:
+ return 25.4;
+ }
+ }
+
+ /**
+ * Gets the double representation of the GPS latitude or longitude
+ * coordinate.
+ *
+ * @param coordinate an array of 3 Rationals representing the degrees,
+ * minutes, and seconds of the GPS location as defined in the
+ * exif specification.
+ * @param reference a GPS reference reperesented by a String containing "N",
+ * "S", "E", or "W".
+ * @return the GPS coordinate represented as degrees + minutes/60 +
+ * seconds/3600
+ */
+ public static double convertLatOrLongToDouble( Rational[] coordinate, String reference ) {
+ try {
+ double degrees = coordinate[0].toDouble();
+ double minutes = coordinate[1].toDouble();
+ double seconds = coordinate[2].toDouble();
+ double result = degrees + minutes / 60.0 + seconds / 3600.0;
+ if( ( reference.startsWith( "S" ) || reference.startsWith( "W" ) ) ) {
+ return - result;
+ }
+ return result;
+ } catch( ArrayIndexOutOfBoundsException e ) {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ protected static int[] getAllowedIfdsFromInfo( int info ) {
+ int ifdFlags = getAllowedIfdFlagsFromInfo( info );
+ int[] ifds = IfdData.getIfds();
+ ArrayList l = new ArrayList();
+ for( int i = 0; i < IfdId.TYPE_IFD_COUNT; i++ ) {
+ int flag = ( ifdFlags >> i ) & 1;
+ if( flag == 1 ) {
+ l.add( ifds[i] );
+ }
+ }
+ if( l.size() <= 0 ) {
+ return null;
+ }
+ int[] ret = new int[l.size()];
+ int j = 0;
+ for( int i : l ) {
+ ret[j++] = i;
+ }
+ return ret;
+ }
+
+ /**
+ * Reads the exif tags from a file, clearing this ExifInterface object's
+ * existing exif tags.
+ *
+ * @param inFileName a string representing the filepath to jpeg file.
+ * @param options bit flag which defines which type of tags to process, see {@link it.sephiroth.android.library.exif2.ExifInterface.Options}
+ * @see #readExif(java.io.InputStream, int)
+ * @throws java.io.IOException
+ */
+ @SuppressWarnings( "unused" )
+ public void readExif( String inFileName, int options ) throws IOException {
+ if( inFileName == null ) {
+ throw new IllegalArgumentException( NULL_ARGUMENT_STRING );
+ }
+ InputStream is = null;
+ try {
+ is = new BufferedInputStream( new FileInputStream( inFileName ) );
+ readExif( is, options );
+ } catch( IOException e ) {
+ closeSilently( is );
+ throw e;
+ }
+ is.close();
+ }
+
+ /**
+ * Reads the exif tags from an InputStream, clearing this ExifInterface
+ * object's existing exif tags.
+ *
+ * ExifInterface exif = new ExifInterface();
+ * exif.readExif( stream, Options.OPTION_IFD_0 | Options.OPTION_IFD_1 | Options.OPTION_IFD_EXIF );
+ * ...
+ * // to request all the options use the OPTION_ALL bit mask
+ * exif.readExif( stream, Options.OPTION_ALL );
+ *
+ *
+ * @param inStream an InputStream containing a jpeg compressed image.
+ * @param options bit flag which defines which type of tags to process, see {@link it.sephiroth.android.library.exif2.ExifInterface.Options}
+ * @throws java.io.IOException
+ */
+ @SuppressWarnings( "unused" )
+ public void readExif( InputStream inStream, int options ) throws IOException {
+ if( inStream == null ) {
+ throw new IllegalArgumentException( NULL_ARGUMENT_STRING );
+ }
+ ExifData d;
+ try {
+ d = new ExifReader( this ).read( inStream, options );
+ } catch( ExifInvalidFormatException e ) {
+ throw new IOException( "Invalid exif format : " + e );
+ }
+ mData = d;
+ }
+
+ protected static void closeSilently( Closeable c ) {
+ if( c != null ) {
+ try {
+ c.close();
+ } catch( Throwable e ) {
+ // ignored
+ }
+ }
+ }
+
+ /**
+ * Sets the exif tags, clearing this ExifInterface object's existing exif
+ * tags.
+ *
+ * @param tags a collection of exif tags to set.
+ */
+ public void setExif( Collection tags ) {
+ clearExif();
+ setTags( tags );
+ }
+
+ /**
+ * Clears this ExifInterface object's existing exif tags.
+ */
+ public void clearExif() {
+ mData = new ExifData( DEFAULT_BYTE_ORDER );
+ }
+
+ /**
+ * Puts a collection of ExifTags into this ExifInterface objects's tags. Any
+ * previous ExifTags with the same TID and IFDs will be removed.
+ *
+ * @param tags a Collection of ExifTags.
+ * @see #setTag
+ */
+ public void setTags( Collection tags ) {
+ if( null == tags ) return;
+ for( ExifTag t : tags ) {
+ setTag( t );
+ }
+ }
+
+ /**
+ * Puts an ExifTag into this ExifInterface object's tags, removing a
+ * previous ExifTag with the same TID and IFD. The IFD it is put into will
+ * be the one the tag was created with in {@link #buildTag}.
+ *
+ * @param tag an ExifTag to put into this ExifInterface's tags.
+ * @return the previous ExifTag with the same TID and IFD or null if none
+ * exists.
+ */
+ public ExifTag setTag( ExifTag tag ) {
+ return mData.addTag( tag );
+ }
+
+ @SuppressWarnings( "unused" )
+ public void writeExif( final String dstFilename ) throws IOException {
+ Log.i( TAG, "writeExif: " + dstFilename );
+
+ // create a backup file
+ File dst_file = new File( dstFilename );
+ File bak_file = new File( dstFilename + ".t" );
+
+ // try to delete old copy of backup
+ // Log.d( TAG, "delete old backup file" );
+ bak_file.delete();
+
+ // rename dst file into backup file
+ // Log.d( TAG, "rename dst into bak" )
+ // if( ! dst_file.renameTo( bak_file ) ) return;
+
+ try {
+ // Log.d( TAG, "try to write into dst" );
+ // writeExif( bak_file.getAbsolutePath(), dst_file.getAbsolutePath() );
+
+ // Trying to write into bak_file using dst_file as source
+ writeExif( dst_file.getAbsolutePath(), bak_file.getAbsolutePath() );
+
+ // Now switch bak into dst
+ // Log.d( TAG, "rename the bak into dst" );
+ bak_file.renameTo( dst_file );
+ } catch( IOException e ) {
+ throw e;
+ } finally {
+ // deleting backup file
+ bak_file.delete();
+ }
+ }
+
+ @SuppressWarnings( "unused" )
+ public void writeExif( final String srcFilename, final String dstFilename ) throws IOException {
+ Log.i( TAG, "writeExif: " + dstFilename );
+
+ // src and dst cannot be the same
+ if( srcFilename.equals( dstFilename ) ) return;
+
+ // srcFilename is used *ONLY* to read the image uncompressed data
+ // exif tags are not used here
+
+ // 3. rename dst file into backup file
+ FileInputStream input = new FileInputStream( srcFilename );
+ FileOutputStream output = new FileOutputStream( dstFilename );
+
+ int position = writeExif_internal( input, output, mData );
+
+ // 7. write the rest of the image..
+ FileChannel in_channel = input.getChannel();
+ FileChannel out_channel = output.getChannel();
+ in_channel.transferTo( position, in_channel.size() - position, out_channel );
+ output.flush();
+
+ IOUtils.closeQuietly( input );
+ IOUtils.closeQuietly( output );
+ }
+
+
+ public void writeExif( final InputStream input, final String dstFilename ) throws IOException {
+ Log.i( TAG, "writeExif: " + dstFilename );
+
+ // inpur is used *ONLY* to read the image uncompressed data
+ // exif tags are not used here
+
+ FileOutputStream output = new FileOutputStream( dstFilename );
+ writeExif_internal( input, output, mData );
+
+ // 7. write the rest of the image..
+ IOUtils.copy( input, output );
+
+ output.flush();
+ output.close();
+ }
+
+ @SuppressWarnings( "unused" )
+ public void writeExif( final Bitmap input, final String dstFilename, int quality ) throws IOException {
+ Log.i( TAG, "writeExif: " + dstFilename );
+
+ // inpur is used *ONLY* to read the image uncompressed data
+ // exif tags are not used here
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ input.compress( Bitmap.CompressFormat.JPEG, quality, out );
+
+ ByteArrayInputStream in = new ByteArrayInputStream( out.toByteArray() );
+ out.close();
+
+ writeExif( in, dstFilename );
+ }
+
+ private static int writeExif_internal( final InputStream input, final OutputStream output, ExifData exifData ) throws IOException {
+ // Log.i( TAG, "writeExif_internal" );
+
+ // 1. read the output file first
+ ExifInterface src_exif = new ExifInterface();
+ src_exif.readExif( input, 0 );
+
+ // 4. Create the destination outputstream
+ // 5. write headers
+ output.write( 0xFF );
+ output.write( JpegHeader.TAG_SOI );
+
+ final List sections = src_exif.mData.getSections();
+
+ // 6. write all the sections from the srcFilename
+ if( sections.get( 0 ).type != JpegHeader.TAG_M_JFIF ) {
+ Log.w( TAG, "first section is not a JFIF or EXIF tag" );
+ output.write( JpegHeader.JFIF_HEADER );
+ }
+
+ // 6.1 write the *new* EXIF tag
+ ExifOutputStream eo = new ExifOutputStream( src_exif );
+ eo.setExifData( exifData );
+ eo.writeExifData( output );
+
+ // 6.2 write all the sections except for the SOS ( start of scan )
+ for( int a = 0; a < sections.size() - 1; a++ ) {
+ ExifParser.Section current = sections.get( a );
+ // Log.v( TAG, "writing section.. " + String.format( "0x%2X", current.type ) );
+ output.write( 0xFF );
+ output.write( current.type );
+ output.write( current.data );
+ }
+
+ // 6.3 write the last SOS marker
+ ExifParser.Section current = sections.get( sections.size() - 1 );
+ // Log.v( TAG, "writing last section.. " + String.format( "0x%2X", current.type ) );
+ output.write( 0xFF );
+ output.write( current.type );
+ output.write( current.data );
+
+ // return the position where the input stream should be copied
+ return src_exif.mData.mUncompressedDataPosition;
+ }
+
+
+ /**
+ * Get the exif tags in this ExifInterface object or null if none exist.
+ *
+ * @return a List of {@link ExifTag}s.
+ */
+ public List getAllTags() {
+ return mData.getAllTags();
+ }
+
+ /**
+ * Reads the exif tags from a byte array, clearing this ExifInterface
+ * object's existing exif tags.
+ *
+ * @param jpeg a byte array containing a jpeg compressed image.
+ * @param options bit flag which defines which type of tags to process, see {@link it.sephiroth.android.library.exif2.ExifInterface.Options}
+ * @throws java.io.IOException
+ * @see #readExif(java.io.InputStream, int)
+ */
+ @SuppressWarnings( "unused" )
+ public void readExif( byte[] jpeg, int options ) throws IOException {
+ readExif( new ByteArrayInputStream( jpeg ), options );
+ }
+
+ /**
+ * Returns a list of ExifTags that share a TID (which can be obtained by
+ * calling {@link #getTrueTagKey} on a defined tag constant) or null if none
+ * exist.
+ *
+ * @param tagId a TID as defined in the exif standard (or with
+ * {@link #defineTag}).
+ * @return a List of {@link ExifTag}s.
+ */
+ @SuppressWarnings( "unused" )
+ public List getTagsForTagId( short tagId ) {
+ return mData.getAllTagsForTagId( tagId );
+ }
+
+ /**
+ * Returns a list of ExifTags that share an IFD (which can be obtained by
+ * calling {@link #getTrueIfd(int)} on a defined tag constant) or null if none
+ * exist.
+ *
+ * @param ifdId an IFD as defined in the exif standard (or with
+ * {@link #defineTag}).
+ * @return a List of {@link ExifTag}s.
+ */
+ @SuppressWarnings( "unused" )
+ public List getTagsForIfdId( int ifdId ) {
+ return mData.getAllTagsForIfd( ifdId );
+ }
+
+ /**
+ * Returns the ExifTag in that tag's default IFD for a defined tag constant
+ * or null if none exists.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return an {@link ExifTag} or null if none exists.
+ */
+ public ExifTag getTag( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return getTag( tagId, ifdId );
+ }
+
+ /**
+ * Gets the default IFD for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the default IFD for a tag definition or {@link #IFD_NULL} if no
+ * definition exists.
+ */
+ public int getDefinedTagDefaultIfd( int tagId ) {
+ int info = getTagInfo().get( tagId );
+ if( info == DEFINITION_NULL ) {
+ return IFD_NULL;
+ }
+ return getTrueIfd( tagId );
+ }
+
+ /**
+ * Gets an ExifTag for an IFD other than the tag's default.
+ *
+ * @see #getTag
+ */
+ public ExifTag getTag( int tagId, int ifdId ) {
+ if( ! ExifTag.isValidIfd( ifdId ) ) {
+ return null;
+ }
+ return mData.getTag( getTrueTagKey( tagId ), ifdId );
+ }
+
+ protected SparseIntArray getTagInfo() {
+ if( mTagInfo == null ) {
+ mTagInfo = new SparseIntArray();
+ initTagInfo();
+ }
+ return mTagInfo;
+ }
+
+ /**
+ * Returns the default IFD for a tag constant.
+ */
+ public static int getTrueIfd( int tag ) {
+ return tag >>> 16;
+ }
+
+ /**
+ * Returns the TID for a tag constant.
+ */
+ public static short getTrueTagKey( int tag ) {
+ // Truncate
+ return (short) tag;
+ }
+
+ private void initTagInfo() {
+ /**
+ * We put tag information in a 4-bytes integer. The first byte a bitmask
+ * representing the allowed IFDs of the tag, the second byte is the data
+ * type, and the last two byte are a short value indicating the default
+ * component count of this tag.
+ */
+ // IFD0 tags
+ int[] ifdAllowedIfds = { IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1 };
+ int ifdFlags = getFlagsFromAllowedIfds( ifdAllowedIfds ) << 24;
+ mTagInfo.put( ExifInterface.TAG_MAKE, ifdFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_IMAGE_WIDTH, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_IMAGE_LENGTH, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_BITS_PER_SAMPLE, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 );
+ mTagInfo.put( ExifInterface.TAG_COMPRESSION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_SAMPLES_PER_PIXEL, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_PLANAR_CONFIGURATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_Y_CB_CR_POSITIONING, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_X_RESOLUTION, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_Y_RESOLUTION, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_RESOLUTION_UNIT, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_STRIP_OFFSETS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 );
+ mTagInfo.put( ExifInterface.TAG_ROWS_PER_STRIP, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_STRIP_BYTE_COUNTS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 );
+ mTagInfo.put( ExifInterface.TAG_TRANSFER_FUNCTION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256 );
+ mTagInfo.put( ExifInterface.TAG_WHITE_POINT, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_PRIMARY_CHROMATICITIES, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6 );
+ mTagInfo.put( ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3 );
+ mTagInfo.put( ExifInterface.TAG_REFERENCE_BLACK_WHITE, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6 );
+ mTagInfo.put( ExifInterface.TAG_DATE_TIME, ifdFlags | ExifTag.TYPE_ASCII << 16 | 20 );
+ mTagInfo.put( ExifInterface.TAG_IMAGE_DESCRIPTION, ifdFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_MODEL, ifdFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_SOFTWARE, ifdFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_ARTIST, ifdFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_COPYRIGHT, ifdFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_EXIF_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_GPS_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1 );
+ // IFD1 tags
+ int[] ifd1AllowedIfds = { IfdId.TYPE_IFD_1 };
+ int ifdFlags1 = getFlagsFromAllowedIfds( ifd1AllowedIfds ) << 24;
+ mTagInfo.put( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1 );
+ // Exif tags
+ int[] exifAllowedIfds = { IfdId.TYPE_IFD_EXIF };
+ int exifFlags = getFlagsFromAllowedIfds( exifAllowedIfds ) << 24;
+ mTagInfo.put( ExifInterface.TAG_EXIF_VERSION, exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4 );
+ mTagInfo.put( ExifInterface.TAG_FLASHPIX_VERSION, exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4 );
+ mTagInfo.put( ExifInterface.TAG_COLOR_SPACE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_COMPONENTS_CONFIGURATION, exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4 );
+ mTagInfo.put( ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_PIXEL_X_DIMENSION, exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_PIXEL_Y_DIMENSION, exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_MAKER_NOTE, exifFlags | ExifTag.TYPE_UNDEFINED << 16 );
+ mTagInfo.put( ExifInterface.TAG_USER_COMMENT, exifFlags | ExifTag.TYPE_UNDEFINED << 16 );
+ mTagInfo.put( ExifInterface.TAG_RELATED_SOUND_FILE, exifFlags | ExifTag.TYPE_ASCII << 16 | 13 );
+ mTagInfo.put( ExifInterface.TAG_DATE_TIME_ORIGINAL, exifFlags | ExifTag.TYPE_ASCII << 16 | 20 );
+ mTagInfo.put( ExifInterface.TAG_DATE_TIME_DIGITIZED, exifFlags | ExifTag.TYPE_ASCII << 16 | 20 );
+ mTagInfo.put( ExifInterface.TAG_SUB_SEC_TIME, exifFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL, exifFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED, exifFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_IMAGE_UNIQUE_ID, exifFlags | ExifTag.TYPE_ASCII << 16 | 33 );
+ mTagInfo.put( ExifInterface.TAG_LENS_SPECS, exifFlags | ExifTag.TYPE_RATIONAL << 16 | 4 );
+ mTagInfo.put( ExifInterface.TAG_LENS_MAKE, exifFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_LENS_MODEL, exifFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_SENSITIVITY_TYPE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_EXPOSURE_TIME, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_F_NUMBER, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_EXPOSURE_PROGRAM, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_SPECTRAL_SENSITIVITY, exifFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_ISO_SPEED_RATINGS, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 );
+ mTagInfo.put( ExifInterface.TAG_OECF, exifFlags | ExifTag.TYPE_UNDEFINED << 16 );
+ mTagInfo.put( ExifInterface.TAG_SHUTTER_SPEED_VALUE, exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_APERTURE_VALUE, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_BRIGHTNESS_VALUE, exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_EXPOSURE_BIAS_VALUE, exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_MAX_APERTURE_VALUE, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_SUBJECT_DISTANCE, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_METERING_MODE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_LIGHT_SOURCE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_FLASH, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_FOCAL_LENGTH, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_SUBJECT_AREA, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 );
+ mTagInfo.put( ExifInterface.TAG_FLASH_ENERGY, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, exifFlags | ExifTag.TYPE_UNDEFINED << 16 );
+ mTagInfo.put( ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_SUBJECT_LOCATION, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_EXPOSURE_INDEX, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_SENSING_METHOD, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_FILE_SOURCE, exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_SCENE_TYPE, exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_CFA_PATTERN, exifFlags | ExifTag.TYPE_UNDEFINED << 16 );
+ mTagInfo.put( ExifInterface.TAG_CUSTOM_RENDERED, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_EXPOSURE_MODE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_WHITE_BALANCE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_DIGITAL_ZOOM_RATIO, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_SCENE_CAPTURE_TYPE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_GAIN_CONTROL, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_CONTRAST, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_SATURATION, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_SHARPNESS, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, exifFlags | ExifTag.TYPE_UNDEFINED << 16 );
+ mTagInfo.put( ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1 );
+ // GPS tag
+ int[] gpsAllowedIfds = { IfdId.TYPE_IFD_GPS };
+ int gpsFlags = getFlagsFromAllowedIfds( gpsAllowedIfds ) << 24;
+ mTagInfo.put( ExifInterface.TAG_GPS_VERSION_ID, gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4 );
+ mTagInfo.put( ExifInterface.TAG_GPS_LATITUDE_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_GPS_LONGITUDE_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_GPS_LATITUDE, gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3 );
+ mTagInfo.put( ExifInterface.TAG_GPS_LONGITUDE, gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3 );
+ mTagInfo.put( ExifInterface.TAG_GPS_ALTITUDE_REF, gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_GPS_ALTITUDE, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_GPS_TIME_STAMP, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3 );
+ mTagInfo.put( ExifInterface.TAG_GPS_SATTELLITES, gpsFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_GPS_STATUS, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_GPS_MEASURE_MODE, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_GPS_DOP, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_GPS_SPEED_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_GPS_SPEED, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_GPS_TRACK_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_GPS_TRACK, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_GPS_IMG_DIRECTION_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_GPS_IMG_DIRECTION, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_GPS_MAP_DATUM, gpsFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( ExifInterface.TAG_GPS_DEST_LATITUDE_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_GPS_DEST_LATITUDE, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_GPS_DEST_BEARING_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_GPS_DEST_BEARING, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_GPS_DEST_DISTANCE_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2 );
+ mTagInfo.put( ExifInterface.TAG_GPS_DEST_DISTANCE, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1 );
+ mTagInfo.put( ExifInterface.TAG_GPS_PROCESSING_METHOD, gpsFlags | ExifTag.TYPE_UNDEFINED << 16 );
+ mTagInfo.put( ExifInterface.TAG_GPS_AREA_INFORMATION, gpsFlags | ExifTag.TYPE_UNDEFINED << 16 );
+ mTagInfo.put( ExifInterface.TAG_GPS_DATE_STAMP, gpsFlags | ExifTag.TYPE_ASCII << 16 | 11 );
+ mTagInfo.put( ExifInterface.TAG_GPS_DIFFERENTIAL, gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11 );
+ // Interoperability tag
+ int[] interopAllowedIfds = { IfdId.TYPE_IFD_INTEROPERABILITY };
+ int interopFlags = getFlagsFromAllowedIfds( interopAllowedIfds ) << 24;
+ mTagInfo.put( TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16 );
+ mTagInfo.put( TAG_INTEROP_VERSION, interopFlags | ExifTag.TYPE_UNDEFINED << 16 | 4 );
+ }
+
+ protected static int getFlagsFromAllowedIfds( int[] allowedIfds ) {
+ if( allowedIfds == null || allowedIfds.length == 0 ) {
+ return 0;
+ }
+ int flags = 0;
+ int[] ifds = IfdData.getIfds();
+ for( int i = 0; i < IfdId.TYPE_IFD_COUNT; i++ ) {
+ for( int j : allowedIfds ) {
+ if( ifds[i] == j ) {
+ flags |= 1 << i;
+ break;
+ }
+ }
+ }
+ return flags;
+ }
+
+ /**
+ * Returns the value of the ExifTag in that tag's default IFD for a defined
+ * tag constant or null if none exists or the value could not be cast into
+ * the return type.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the value of the ExifTag or null if none exists.
+ */
+ @SuppressWarnings( "unused" )
+ public Object getTagValue( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return getTagValue( tagId, ifdId );
+ }
+
+ /**
+ * Gets a tag value for an IFD other than the tag's default.
+ *
+ * @see #getTagValue
+ */
+ public Object getTagValue( int tagId, int ifdId ) {
+ ExifTag t = getTag( tagId, ifdId );
+ return ( t == null ) ? null : t.getValue();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public String getTagStringValue( int tagId, int ifdId ) {
+ ExifTag t = getTag( tagId, ifdId );
+ if( t == null ) {
+ return null;
+ }
+ return t.getValueAsString();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public String getTagStringValue( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return getTagStringValue( tagId, ifdId );
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ @SuppressWarnings( "unused" )
+ public Long getTagLongValue( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return getTagLongValue( tagId, ifdId );
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Long getTagLongValue( int tagId, int ifdId ) {
+ long[] l = getTagLongValues( tagId, ifdId );
+ if( l == null || l.length <= 0 ) {
+ return null;
+ }
+ return new Long( l[0] );
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public long[] getTagLongValues( int tagId, int ifdId ) {
+ ExifTag t = getTag( tagId, ifdId );
+ if( t == null ) {
+ return null;
+ }
+ return t.getValueAsLongs();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Integer getTagIntValue( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return getTagIntValue( tagId, ifdId );
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Integer getTagIntValue( int tagId, int ifdId ) {
+ int[] l = getTagIntValues( tagId, ifdId );
+ if( l == null || l.length <= 0 ) {
+ return null;
+ }
+ return new Integer( l[0] );
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public int[] getTagIntValues( int tagId, int ifdId ) {
+ ExifTag t = getTag( tagId, ifdId );
+ if( t == null ) {
+ return null;
+ }
+ return t.getValueAsInts();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Byte getTagByteValue( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return getTagByteValue( tagId, ifdId );
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Byte getTagByteValue( int tagId, int ifdId ) {
+ byte[] l = getTagByteValues( tagId, ifdId );
+ if( l == null || l.length <= 0 ) {
+ return null;
+ }
+ return new Byte( l[0] );
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public byte[] getTagByteValues( int tagId, int ifdId ) {
+ ExifTag t = getTag( tagId, ifdId );
+ if( t == null ) {
+ return null;
+ }
+ return t.getValueAsBytes();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational getTagRationalValue( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return getTagRationalValue( tagId, ifdId );
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational getTagRationalValue( int tagId, int ifdId ) {
+ Rational[] l = getTagRationalValues( tagId, ifdId );
+ if( l == null || l.length == 0 ) {
+ return null;
+ }
+ return new Rational( l[0] );
+ }
+
+ /*
+ * Getter methods that are similar to getTagValue. Null is returned if the
+ * tag value cannot be cast into the return type.
+ */
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational[] getTagRationalValues( int tagId, int ifdId ) {
+ ExifTag t = getTag( tagId, ifdId );
+ if( t == null ) {
+ return null;
+ }
+ return t.getValueAsRationals();
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ @SuppressWarnings( "unused" )
+ public long[] getTagLongValues( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return getTagLongValues( tagId, ifdId );
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ @SuppressWarnings( "unused" )
+ public int[] getTagIntValues( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return getTagIntValues( tagId, ifdId );
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ @SuppressWarnings( "unused" )
+ public byte[] getTagByteValues( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return getTagByteValues( tagId, ifdId );
+ }
+
+ /**
+ * @see #getTagValue
+ */
+ public Rational[] getTagRationalValues( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return getTagRationalValues( tagId, ifdId );
+ }
+
+ /**
+ * Checks whether a tag has a defined number of elements.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return true if the tag has a defined number of elements.
+ */
+ @SuppressWarnings( "unused" )
+ public boolean isTagCountDefined( int tagId ) {
+ int info = getTagInfo().get( tagId );
+ // No value in info can be zero, as all tags have a non-zero type
+ return info != 0 && getComponentCountFromInfo( info ) != ExifTag.SIZE_UNDEFINED;
+ }
+
+ protected static int getComponentCountFromInfo( int info ) {
+ return info & 0x0ffff;
+ }
+
+ /**
+ * Gets the defined number of elements for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the
+ * tag or the number of elements is not defined.
+ */
+ @SuppressWarnings( "unused" )
+ public int getDefinedTagCount( int tagId ) {
+ int info = getTagInfo().get( tagId );
+ if( info == 0 ) {
+ return ExifTag.SIZE_UNDEFINED;
+ }
+ return getComponentCountFromInfo( info );
+ }
+
+ /**
+ * Gets the number of elements for an ExifTag in a given IFD.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD containing the ExifTag to check.
+ * @return the number of elements in the ExifTag, if the tag's size is
+ * undefined this will return the actual number of elements that is
+ * in the ExifTag's value.
+ */
+ @SuppressWarnings( "unused" )
+ public int getActualTagCount( int tagId, int ifdId ) {
+ ExifTag t = getTag( tagId, ifdId );
+ if( t == null ) {
+ return 0;
+ }
+ return t.getComponentCount();
+ }
+
+ /**
+ * Gets the defined type for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @return the type.
+ * @see ExifTag#getDataType()
+ */
+ @SuppressWarnings( "unused" )
+ public short getDefinedTagType( int tagId ) {
+ int info = getTagInfo().get( tagId );
+ if( info == 0 ) {
+ return - 1;
+ }
+ return getTypeFromInfo( info );
+ }
+
+ protected static short getTypeFromInfo( int info ) {
+ return (short) ( ( info >> 16 ) & 0x0ff );
+ }
+
+ protected ExifTag buildUninitializedTag( int tagId ) {
+ int info = getTagInfo().get( tagId );
+ if( info == 0 ) {
+ return null;
+ }
+ short type = getTypeFromInfo( info );
+ int definedCount = getComponentCountFromInfo( info );
+ boolean hasDefinedCount = ( definedCount != ExifTag.SIZE_UNDEFINED );
+ int ifdId = getTrueIfd( tagId );
+ return new ExifTag( getTrueTagKey( tagId ), type, definedCount, ifdId, hasDefinedCount );
+ }
+
+ /**
+ * Sets the value of an ExifTag if it exists it's default IFD. The value
+ * must be the correct type and length for that ExifTag.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param val the value to set.
+ * @return true if success, false if the ExifTag doesn't exist or the value
+ * is the wrong type/length.
+ */
+ @SuppressWarnings( "unused" )
+ public boolean setTagValue( int tagId, Object val ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ return setTagValue( tagId, ifdId, val );
+ }
+
+ /**
+ * Sets the value of an ExifTag if it exists in the given IFD. The value
+ * must be the correct type and length for that ExifTag.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD that the ExifTag is in.
+ * @param val the value to set.
+ * @return true if success, false if the ExifTag doesn't exist or the value
+ * is the wrong type/length.
+ * @see #setTagValue
+ */
+ public boolean setTagValue( int tagId, int ifdId, Object val ) {
+ ExifTag t = getTag( tagId, ifdId );
+ return t != null && t.setValue( val );
+ }
+
+ /**
+ * Removes the ExifTag for a tag constant from that tag's default IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ */
+ public void deleteTag( int tagId ) {
+ int ifdId = getDefinedTagDefaultIfd( tagId );
+ deleteTag( tagId, ifdId );
+ }
+
+ /**
+ * Removes the ExifTag for a tag constant from the given IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD of the ExifTag to remove.
+ */
+ public void deleteTag( int tagId, int ifdId ) {
+ mData.removeTag( getTrueTagKey( tagId ), ifdId );
+ }
+
+ /**
+ * Creates a new tag definition in this ExifInterface object for a given TID
+ * and default IFD. Creating a definition with the same TID and default IFD
+ * as a previous definition will override it.
+ *
+ * @param tagId the TID for the tag.
+ * @param defaultIfd the default IFD for the tag.
+ * @param tagType the type of the tag (see {@link ExifTag#getDataType()}).
+ * @param defaultComponentCount the number of elements of this tag's type in
+ * the tags value.
+ * @param allowedIfds the IFD's this tag is allowed to be put in.
+ * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or
+ * {@link #TAG_NULL} if the definition could not be made.
+ */
+ @SuppressWarnings( "unused" )
+ public int setTagDefinition(
+ short tagId, int defaultIfd, short tagType, short defaultComponentCount, int[] allowedIfds ) {
+ if( sBannedDefines.contains( tagId ) ) {
+ return TAG_NULL;
+ }
+ if( ExifTag.isValidType( tagType ) && ExifTag.isValidIfd( defaultIfd ) ) {
+ int tagDef = defineTag( defaultIfd, tagId );
+ if( tagDef == TAG_NULL ) {
+ return TAG_NULL;
+ }
+ int[] otherDefs = getTagDefinitionsForTagId( tagId );
+ SparseIntArray infos = getTagInfo();
+ // Make sure defaultIfd is in allowedIfds
+ boolean defaultCheck = false;
+ for( int i : allowedIfds ) {
+ if( defaultIfd == i ) {
+ defaultCheck = true;
+ }
+ if( ! ExifTag.isValidIfd( i ) ) {
+ return TAG_NULL;
+ }
+ }
+ if( ! defaultCheck ) {
+ return TAG_NULL;
+ }
+
+ int ifdFlags = getFlagsFromAllowedIfds( allowedIfds );
+ // Make sure no identical tags can exist in allowedIfds
+ if( otherDefs != null ) {
+ for( int def : otherDefs ) {
+ int tagInfo = infos.get( def );
+ int allowedFlags = getAllowedIfdFlagsFromInfo( tagInfo );
+ if( ( ifdFlags & allowedFlags ) != 0 ) {
+ return TAG_NULL;
+ }
+ }
+ }
+ getTagInfo().put( tagDef, ifdFlags << 24 | ( tagType << 16 ) | defaultComponentCount );
+ return tagDef;
+ }
+ return TAG_NULL;
+ }
+
+ @SuppressWarnings( "unused" )
+ protected int getTagDefinition( short tagId, int defaultIfd ) {
+ return getTagInfo().get( defineTag( defaultIfd, tagId ) );
+ }
+
+ /**
+ * Returns the constant representing a tag with a given TID and default IFD.
+ */
+ public static int defineTag( int ifdId, short tagId ) {
+ return ( tagId & 0x0000ffff ) | ( ifdId << 16 );
+ }
+
+ protected int[] getTagDefinitionsForTagId( short tagId ) {
+ int[] ifds = IfdData.getIfds();
+ int[] defs = new int[ifds.length];
+ int counter = 0;
+ SparseIntArray infos = getTagInfo();
+ for( int i : ifds ) {
+ int def = defineTag( i, tagId );
+ if( infos.get( def ) != DEFINITION_NULL ) {
+ defs[counter++] = def;
+ }
+ }
+ if( counter == 0 ) {
+ return null;
+ }
+
+ return Arrays.copyOfRange( defs, 0, counter );
+ }
+
+ @SuppressWarnings( "unused" )
+ protected int getTagDefinitionForTag( ExifTag tag ) {
+ short type = tag.getDataType();
+ int count = tag.getComponentCount();
+ int ifd = tag.getIfd();
+ return getTagDefinitionForTag( tag.getTagId(), type, count, ifd );
+ }
+
+ protected int getTagDefinitionForTag( short tagId, short type, int count, int ifd ) {
+ int[] defs = getTagDefinitionsForTagId( tagId );
+ if( defs == null ) {
+ return TAG_NULL;
+ }
+ SparseIntArray infos = getTagInfo();
+ int ret = TAG_NULL;
+ for( int i : defs ) {
+ int info = infos.get( i );
+ short def_type = getTypeFromInfo( info );
+ int def_count = getComponentCountFromInfo( info );
+ int[] def_ifds = getAllowedIfdsFromInfo( info );
+ boolean valid_ifd = false;
+ for( int j : def_ifds ) {
+ if( j == ifd ) {
+ valid_ifd = true;
+ break;
+ }
+ }
+ if( valid_ifd && type == def_type && ( count == def_count || def_count == ExifTag.SIZE_UNDEFINED ) ) {
+ ret = i;
+ break;
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Removes a tag definition for given defined tag constant.
+ *
+ * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ */
+ @SuppressWarnings( "unused" )
+ public void removeTagDefinition( int tagId ) {
+ getTagInfo().delete( tagId );
+ }
+
+ /**
+ * Resets tag definitions to the default ones.
+ */
+ @SuppressWarnings( "unused" )
+ public void resetTagDefinitions() {
+ mTagInfo = null;
+ }
+
+ /**
+ * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
+ *
+ * @return the thumbnail as a bitmap.
+ */
+ public Bitmap getThumbnailBitmap() {
+ if( mData.hasCompressedThumbnail() ) {
+ byte[] thumb = mData.getCompressedThumbnail();
+ return BitmapFactory.decodeByteArray( thumb, 0, thumb.length );
+ }
+ else if( mData.hasUncompressedStrip() ) {
+ // TODO: implement uncompressed
+ }
+ return null;
+ }
+
+ /**
+ * Returns the thumbnail from IFD1 as a byte array, or null if none exists.
+ * The bytes may either be an uncompressed strip as specified in the exif
+ * standard or a jpeg compressed image.
+ *
+ * @return the thumbnail as a byte array.
+ */
+ @SuppressWarnings( "unused" )
+ public byte[] getThumbnailBytes() {
+ if( mData.hasCompressedThumbnail() ) {
+ return mData.getCompressedThumbnail();
+ }
+ else if( mData.hasUncompressedStrip() ) {
+ // TODO: implement this
+ }
+ return null;
+ }
+
+ /**
+ * Returns the thumbnail if it is jpeg compressed, or null if none exists.
+ *
+ * @return the thumbnail as a byte array.
+ */
+ public byte[] getThumbnail() {
+ return mData.getCompressedThumbnail();
+ }
+
+ /**
+ * Returns the JPEG quality used to generate the image
+ * or 0 if not found
+ *
+ * @return
+ */
+ public int getQualityGuess() {
+ return mData.getQualityGuess();
+ }
+
+ /**
+ * this gives information about the process used to create the JPEG file.
+ * Possible values are:
+ *
+ * - '0' Unknown
+ * - '192' Baseline
+ * - '193' Extended sequential
+ * - '194' Progressive
+ * - '195' Lossless
+ * - '197' Differential sequential
+ * - '198' Differential progressive
+ * - '199' Differential lossless
+ * - '201' Extended sequential, arithmetic coding
+ * - '202' Progressive, arithmetic coding
+ * - '203' Lossless, arithmetic coding
+ * - '205' Differential sequential, arithmetic coding
+ * - '206' Differential progressive, arithmetic codng
+ * - '207' Differential lossless, arithmetic coding
+ *
+ */
+ public short getJpegProcess() {
+ return mData.getJpegProcess();
+ }
+
+ /**
+ * Returns the Image size as decoded from the SOF marker
+ */
+ public int[] getImageSize() {
+ return mData.getImageSize();
+ }
+
+ /**
+ * Check if thumbnail is compressed.
+ *
+ * @return true if the thumbnail is compressed.
+ */
+ @SuppressWarnings( "unused" )
+ public boolean isThumbnailCompressed() {
+ return mData.hasCompressedThumbnail();
+ }
+
+ /**
+ * Check if thumbnail exists.
+ *
+ * @return true if a compressed thumbnail exists.
+ */
+ public boolean hasThumbnail() {
+ // TODO: add back in uncompressed strip
+ return mData.hasCompressedThumbnail();
+ }
+
+ /**
+ * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior
+ * thumbnail.
+ *
+ * @param thumb a bitmap to compress to a jpeg thumbnail.
+ * @return true if the thumbnail was set.
+ */
+ @SuppressWarnings( "unused" )
+ public boolean setCompressedThumbnail( Bitmap thumb ) {
+ ByteArrayOutputStream thumbnail = new ByteArrayOutputStream();
+ if( ! thumb.compress( Bitmap.CompressFormat.JPEG, 90, thumbnail ) ) {
+ return false;
+ }
+ return setCompressedThumbnail( thumbnail.toByteArray() );
+ }
+
+ /**
+ * Sets the thumbnail to be a jpeg compressed image. Clears any prior
+ * thumbnail.
+ *
+ * @param thumb a byte array containing a jpeg compressed image.
+ * @return true if the thumbnail was set.
+ */
+ public boolean setCompressedThumbnail( byte[] thumb ) {
+ mData.clearThumbnailAndStrips();
+ mData.setCompressedThumbnail( thumb );
+ return true;
+ }
+
+ /**
+ * Clears the compressed thumbnail if it exists.
+ */
+ @SuppressWarnings( "unused" )
+ public void removeCompressedThumbnail() {
+ mData.setCompressedThumbnail( null );
+ }
+
+ /**
+ * Decodes the user comment tag into string as specified in the EXIF
+ * standard. Returns null if decoding failed.
+ */
+ @SuppressWarnings( "unused" )
+ public String getUserComment() {
+ return mData.getUserComment();
+ }
+
+ /**
+ * Return the altitude in meters. If the exif tag does not exist, return
+ * defaultValue.
+ *
+ * @param defaultValue the value to return if the tag is not available.
+ */
+ @SuppressWarnings( "unused" )
+ public double getAltitude( double defaultValue ) {
+
+ Byte ref = getTagByteValue( TAG_GPS_ALTITUDE_REF );
+ Rational gpsAltitude = getTagRationalValue( TAG_GPS_ALTITUDE );
+
+ int seaLevel = 1;
+ if( null != ref ) {
+ seaLevel = ref.intValue() == 1 ? - 1 : 1;
+ }
+
+ if( gpsAltitude != null ) {
+ return gpsAltitude.toDouble() * seaLevel;
+ }
+
+ return defaultValue;
+ }
+
+ /**
+ * Gets the GPS latitude and longitude as a pair of doubles from this
+ * ExifInterface object's tags, or null if the necessary tags do not exist.
+ *
+ * @return an array of 2 doubles containing the latitude, and longitude
+ * respectively.
+ * @see #convertLatOrLongToDouble
+ */
+ public double[] getLatLongAsDoubles() {
+ Rational[] latitude = getTagRationalValues( TAG_GPS_LATITUDE );
+ String latitudeRef = getTagStringValue( TAG_GPS_LATITUDE_REF );
+ Rational[] longitude = getTagRationalValues( TAG_GPS_LONGITUDE );
+ String longitudeRef = getTagStringValue( TAG_GPS_LONGITUDE_REF );
+ if( latitude == null || longitude == null || latitudeRef == null || longitudeRef == null || latitude.length < 3 || longitude.length < 3 ) {
+ return null;
+ }
+ double[] latLon = new double[2];
+ latLon[0] = convertLatOrLongToDouble( latitude, latitudeRef );
+ latLon[1] = convertLatOrLongToDouble( longitude, longitudeRef );
+ return latLon;
+ }
+
+ /**
+ * Returns a formatted String with the latitude representation:
+ * 39° 8' 16.8" N
+ */
+ public String getLatitude() {
+ Rational[] latitude = getTagRationalValues( TAG_GPS_LATITUDE );
+ String latitudeRef = getTagStringValue( TAG_GPS_LATITUDE_REF );
+
+ if( null == latitude || null == latitudeRef ) return null;
+ return convertRationalLatLonToString( latitude, latitudeRef );
+ }
+
+ /**
+ * Returns a formatted String with the longitude representation:
+ * 77° 37' 51.6" W
+ */
+ public String getLongitude() {
+ Rational[] longitude = getTagRationalValues( TAG_GPS_LONGITUDE );
+ String longitudeRef = getTagStringValue( TAG_GPS_LONGITUDE_REF );
+
+ if( null == longitude || null == longitudeRef ) return null;
+ return convertRationalLatLonToString( longitude, longitudeRef );
+ }
+
+ private static String convertRationalLatLonToString( Rational[] coord, String ref ) {
+ try {
+
+ double degrees = coord[0].toDouble();
+ double minutes = coord[1].toDouble();
+ double seconds = coord[2].toDouble();
+ ref = ref.substring( 0, 1 );
+
+ return String.format( "%1$.0f° %2$.0f' %3$.0f\" %4$s", degrees, minutes, seconds, ref.toUpperCase( Locale.getDefault() ) );
+ } catch( NumberFormatException e ) {
+ e.printStackTrace();
+ } catch( ArrayIndexOutOfBoundsException e ) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Given an exif date time, like {@link #TAG_DATE_TIME} or {@link #TAG_DATE_TIME_DIGITIZED}
+ * returns a java Date object
+ *
+ * @param dateTimeString one of the value of {@link #TAG_DATE_TIME} or {@link #TAG_DATE_TIME_DIGITIZED}
+ * @param timeZone the target timezone
+ * @return the parsed date
+ */
+ public static Date getDateTime( String dateTimeString, TimeZone timeZone ) {
+ if( dateTimeString == null ) return null;
+
+ DateFormat formatter = new SimpleDateFormat( DATETIME_FORMAT_STR );
+ formatter.setTimeZone( timeZone );
+
+ try {
+ return formatter.parse( dateTimeString );
+ } catch( IllegalArgumentException e ) {
+ e.printStackTrace();
+ } catch( ParseException e ) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Creates, formats, and sets the DateTimeStamp tag for one of:
+ * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED},
+ * {@link #TAG_DATE_TIME_ORIGINAL}.
+ *
+ * @param tagId one of the DateTimeStamp tags.
+ * @param timestamp a timestamp to format.
+ * @param timezone a TimeZone object.
+ * @return true if success, false if the tag could not be set.
+ */
+ public boolean addDateTimeStampTag( int tagId, long timestamp, TimeZone timezone ) {
+ if( tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED || tagId == TAG_DATE_TIME_ORIGINAL ) {
+ mDateTimeStampFormat.setTimeZone( timezone );
+ ExifTag t = buildTag( tagId, mDateTimeStampFormat.format( timestamp ) );
+ if( t == null ) {
+ return false;
+ }
+ setTag( t );
+ }
+ else {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Creates a tag for a defined tag constant in the tag's default IFD.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param val the tag's value.
+ * @return an ExifTag object.
+ */
+ public ExifTag buildTag( int tagId, Object val ) {
+ int ifdId = getTrueIfd( tagId );
+ return buildTag( tagId, ifdId, val );
+ }
+
+ /**
+ * Creates a tag for a defined tag constant in a given IFD if that IFD is
+ * allowed for the tag. This method will fail anytime the appropriate
+ * {@link ExifTag#setValue} for this tag's datatype would fail.
+ *
+ * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
+ * @param ifdId the IFD that the tag should be in.
+ * @param val the value of the tag to set.
+ * @return an ExifTag object or null if one could not be constructed.
+ * @see #buildTag
+ */
+ public ExifTag buildTag( int tagId, int ifdId, Object val ) {
+ int info = getTagInfo().get( tagId );
+ if( info == 0 || val == null ) {
+ return null;
+ }
+ short type = getTypeFromInfo( info );
+ int definedCount = getComponentCountFromInfo( info );
+ boolean hasDefinedCount = ( definedCount != ExifTag.SIZE_UNDEFINED );
+ if( ! ExifInterface.isIfdAllowed( info, ifdId ) ) {
+ return null;
+ }
+ ExifTag t = new ExifTag( getTrueTagKey( tagId ), type, definedCount, ifdId, hasDefinedCount );
+ if( ! t.setValue( val ) ) {
+ return null;
+ }
+ return t;
+ }
+
+ protected static boolean isIfdAllowed( int info, int ifd ) {
+ int[] ifds = IfdData.getIfds();
+ int ifdFlags = getAllowedIfdFlagsFromInfo( info );
+ for( int i = 0; i < ifds.length; i++ ) {
+ if( ifd == ifds[i] && ( ( ifdFlags >> i ) & 1 ) == 1 ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected static int getAllowedIfdFlagsFromInfo( int info ) {
+ return info >>> 24;
+ }
+
+ /**
+ * Creates and sets all to the GPS tags for a give latitude and longitude.
+ *
+ * @param latitude a GPS latitude coordinate.
+ * @param longitude a GPS longitude coordinate.
+ * @return true if success, false if they could not be created or set.
+ */
+ @SuppressWarnings( "unused" )
+ public boolean addGpsTags( double latitude, double longitude ) {
+ ExifTag latTag = buildTag( TAG_GPS_LATITUDE, toExifLatLong( latitude ) );
+ ExifTag longTag = buildTag( TAG_GPS_LONGITUDE, toExifLatLong( longitude ) );
+ ExifTag latRefTag = buildTag( TAG_GPS_LATITUDE_REF, latitude >= 0 ? GpsLatitudeRef.NORTH : GpsLatitudeRef.SOUTH );
+ ExifTag longRefTag = buildTag( TAG_GPS_LONGITUDE_REF, longitude >= 0 ? GpsLongitudeRef.EAST : GpsLongitudeRef.WEST );
+ if( latTag == null || longTag == null || latRefTag == null || longRefTag == null ) {
+ return false;
+ }
+ setTag( latTag );
+ setTag( longTag );
+ setTag( latRefTag );
+ setTag( longRefTag );
+ return true;
+ }
+
+ private static Rational[] toExifLatLong( double value ) {
+ // convert to the format dd/1 mm/1 ssss/100
+ value = Math.abs( value );
+ int degrees = (int) value;
+ value = ( value - degrees ) * 60;
+ int minutes = (int) value;
+ value = ( value - minutes ) * 6000;
+ int seconds = (int) value;
+ return new Rational[]{ new Rational( degrees, 1 ), new Rational( minutes, 1 ), new Rational( seconds, 100 ) };
+ }
+
+ /**
+ * Creates and sets the GPS timestamp tag.
+ *
+ * @param timestamp a GPS timestamp.
+ * @return true if success, false if could not be created or set.
+ */
+ @SuppressWarnings( "unused" )
+ public boolean addGpsDateTimeStampTag( long timestamp ) {
+ ExifTag t = buildTag( TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format( timestamp ) );
+ if( t == null ) {
+ return false;
+ }
+ setTag( t );
+ mGPSTimeStampCalendar.setTimeInMillis( timestamp );
+ t = buildTag( TAG_GPS_TIME_STAMP,
+ new Rational[]{ new Rational( mGPSTimeStampCalendar.get( Calendar.HOUR_OF_DAY ), 1 ), new Rational( mGPSTimeStampCalendar.get( Calendar.MINUTE ), 1 ),
+ new Rational( mGPSTimeStampCalendar.get( Calendar.SECOND ), 1 ) }
+ );
+ if( t == null ) {
+ return false;
+ }
+ setTag( t );
+ return true;
+ }
+
+ /**
+ * Return the aperture size, if present, 0 if missing
+ */
+ public double getApertureSize() {
+ Rational rational = getTagRationalValue( TAG_F_NUMBER );
+ if( null != rational && rational.toDouble() > 0 ) {
+ return rational.toDouble();
+ }
+
+ rational = getTagRationalValue( TAG_APERTURE_VALUE );
+ if( null != rational && rational.toDouble() > 0 ) {
+ return Math.exp( rational.toDouble() * Math.log( 2 ) * 0.5 );
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the lens model as string if any of the tags {@link #TAG_LENS_MODEL}
+ * or {@link #TAG_LENS_SPECS} are found
+ *
+ * @return the string representation of the lens spec
+ */
+ public String getLensModelDescription() {
+ String lensModel = getTagStringValue( TAG_LENS_MODEL );
+ if( null != lensModel ) return lensModel;
+
+ Rational[] rat = getTagRationalValues( TAG_LENS_SPECS );
+ if( null != rat ) return ExifUtil.processLensSpecifications( rat );
+
+ return null;
+ }
+
+ /**
+ * Constants for {@link #TAG_ORIENTATION}. They can be interpreted as
+ * follows:
+ *
+ * - TOP_LEFT is the normal orientation.
+ * - TOP_RIGHT is a left-right mirror.
+ * - BOTTOM_LEFT is a 180 degree rotation.
+ * - BOTTOM_RIGHT is a top-bottom mirror.
+ * - LEFT_TOP is mirrored about the top-left<->bottom-right axis.
+ * - RIGHT_TOP is a 90 degree clockwise rotation.
+ * - LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.
+ * - RIGHT_BOTTOM is a 270 degree clockwise rotation.
+ *
+ */
+ @SuppressWarnings( "unused" )
+ public static interface Orientation {
+ public static final short TOP_LEFT = 1;
+ public static final short TOP_RIGHT = 2;
+ public static final short BOTTOM_RIGHT = 3;
+ public static final short BOTTOM_LEFT = 4;
+ public static final short LEFT_TOP = 5;
+ public static final short RIGHT_TOP = 6;
+ public static final short RIGHT_BOTTOM = 7;
+ public static final short LEFT_BOTTOM = 8;
+ }
+
+ /**
+ * Constants for {@link #TAG_Y_CB_CR_POSITIONING}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface YCbCrPositioning {
+ public static final short CENTERED = 1;
+ public static final short CO_SITED = 2;
+ }
+
+ /**
+ * Constants for {@link #TAG_COMPRESSION}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface Compression {
+ public static final short UNCOMPRESSION = 1;
+ public static final short JPEG = 6;
+ }
+
+ // TODO: uncompressed thumbnail setters
+
+ /**
+ * Constants for {@link #TAG_RESOLUTION_UNIT}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface ResolutionUnit {
+ public static final short INCHES = 2;
+ public static final short CENTIMETERS = 3;
+ public static final short MILLIMETERS = 4;
+ public static final short MICROMETERS = 5;
+ }
+
+ /**
+ * Constants for {@link #TAG_PHOTOMETRIC_INTERPRETATION}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface PhotometricInterpretation {
+ public static final short RGB = 2;
+ public static final short YCBCR = 6;
+ }
+
+ /**
+ * Constants for {@link #TAG_PLANAR_CONFIGURATION}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface PlanarConfiguration {
+ public static final short CHUNKY = 1;
+ public static final short PLANAR = 2;
+ }
+
+ // Convenience methods:
+
+ /**
+ * Constants for {@link #TAG_EXPOSURE_PROGRAM}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface ExposureProgram {
+ public static final short NOT_DEFINED = 0;
+ public static final short MANUAL = 1;
+ public static final short NORMAL_PROGRAM = 2;
+ public static final short APERTURE_PRIORITY = 3;
+ public static final short SHUTTER_PRIORITY = 4;
+ public static final short CREATIVE_PROGRAM = 5;
+ public static final short ACTION_PROGRAM = 6;
+ public static final short PROTRAIT_MODE = 7;
+ public static final short LANDSCAPE_MODE = 8;
+ }
+
+ /**
+ * Constants for {@link #TAG_METERING_MODE}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface MeteringMode {
+ public static final short UNKNOWN = 0;
+ public static final short AVERAGE = 1;
+ public static final short CENTER_WEIGHTED_AVERAGE = 2;
+ public static final short SPOT = 3;
+ public static final short MULTISPOT = 4;
+ public static final short PATTERN = 5;
+ public static final short PARTAIL = 6;
+ public static final short OTHER = 255;
+ }
+
+ @SuppressWarnings( "unused" )
+ public static byte[] toBitArray( short value ) {
+ byte[] result = new byte[16];
+ for( int i = 0; i < 16; i++ ) {
+ result[15 - i] = (byte) ( ( value >> i ) & 1 );
+ }
+ return result;
+ }
+
+ /**
+ * Constants for {@link #TAG_FLASH} As the definition in Jeita EXIF 2.2
+ */
+ @SuppressWarnings( "unused" )
+ public static interface Flash {
+
+ /** first bit */
+ public static enum FlashFired {
+ NO, YES
+ }
+
+ /** Values for bits 1 and 2 indicating the status of returned light */
+ public static enum StrobeLightDetection {
+ NO_DETECTION, RESERVED, LIGHT_NOT_DETECTED, LIGHT_DETECTED
+ }
+
+ /** Values for bits 3 and 4 indicating the camera's flash mode */
+ public static enum CompulsoryMode {
+ UNKNOWN,
+ FIRING,
+ SUPPRESSION,
+ AUTO
+ }
+
+ /** Values for bit 5 indicating the presence of a flash function. */
+ public static enum FlashFunction {
+ FUNCTION_PRESENT,
+ FUNCTION_NOR_PRESENT
+ }
+
+ /** Values for bit 6 indicating the camera's red-eye mode. */
+ public static enum RedEyeMode {
+ NONE,
+ SUPPORTED
+ }
+ }
+
+ /**
+ * Constants for {@link #TAG_COLOR_SPACE}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface ColorSpace {
+ public static final short SRGB = 1;
+ public static final short UNCALIBRATED = (short) 0xFFFF;
+ }
+
+ /**
+ * Constants for {@link #TAG_EXPOSURE_MODE}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface ExposureMode {
+ public static final short AUTO_EXPOSURE = 0;
+ public static final short MANUAL_EXPOSURE = 1;
+ public static final short AUTO_BRACKET = 2;
+ }
+
+ /**
+ * Constants for {@link #TAG_WHITE_BALANCE}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface WhiteBalance {
+ public static final short AUTO = 0;
+ public static final short MANUAL = 1;
+ }
+
+ /**
+ * Constants for {@link #TAG_SCENE_CAPTURE_TYPE}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface SceneCapture {
+ public static final short STANDARD = 0;
+ public static final short LANDSCAPE = 1;
+ public static final short PROTRAIT = 2;
+ public static final short NIGHT_SCENE = 3;
+ }
+
+ /**
+ * Constants for {@link #TAG_COMPONENTS_CONFIGURATION}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface ComponentsConfiguration {
+ public static final short NOT_EXIST = 0;
+ public static final short Y = 1;
+ public static final short CB = 2;
+ public static final short CR = 3;
+ public static final short R = 4;
+ public static final short G = 5;
+ public static final short B = 6;
+ }
+
+ /**
+ * Constants for {@link #TAG_LIGHT_SOURCE}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface LightSource {
+ public static final short UNKNOWN = 0;
+ public static final short DAYLIGHT = 1;
+ public static final short FLUORESCENT = 2;
+ public static final short TUNGSTEN = 3;
+ public static final short FLASH = 4;
+ public static final short FINE_WEATHER = 9;
+ public static final short CLOUDY_WEATHER = 10;
+ public static final short SHADE = 11;
+ public static final short DAYLIGHT_FLUORESCENT = 12;
+ public static final short DAY_WHITE_FLUORESCENT = 13;
+ public static final short COOL_WHITE_FLUORESCENT = 14;
+ public static final short WHITE_FLUORESCENT = 15;
+ public static final short STANDARD_LIGHT_A = 17;
+ public static final short STANDARD_LIGHT_B = 18;
+ public static final short STANDARD_LIGHT_C = 19;
+ public static final short D55 = 20;
+ public static final short D65 = 21;
+ public static final short D75 = 22;
+ public static final short D50 = 23;
+ public static final short ISO_STUDIO_TUNGSTEN = 24;
+ public static final short OTHER = 255;
+ }
+
+ /**
+ * Constants for {@link #TAG_SENSING_METHOD}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface SensingMethod {
+ public static final short NOT_DEFINED = 1;
+ public static final short ONE_CHIP_COLOR = 2;
+ public static final short TWO_CHIP_COLOR = 3;
+ public static final short THREE_CHIP_COLOR = 4;
+ public static final short COLOR_SEQUENTIAL_AREA = 5;
+ public static final short TRILINEAR = 7;
+ public static final short COLOR_SEQUENTIAL_LINEAR = 8;
+ }
+
+ /**
+ * Constants for {@link #TAG_FILE_SOURCE}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface FileSource {
+ public static final short DSC = 3;
+ }
+
+ /**
+ * Constants for {@link #TAG_SCENE_TYPE}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface SceneType {
+ public static final short DIRECT_PHOTOGRAPHED = 1;
+ }
+
+ /**
+ * Constants for {@link #TAG_GAIN_CONTROL}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface GainControl {
+ public static final short NONE = 0;
+ public static final short LOW_UP = 1;
+ public static final short HIGH_UP = 2;
+ public static final short LOW_DOWN = 3;
+ public static final short HIGH_DOWN = 4;
+ }
+
+ /**
+ * Constants for {@link #TAG_CONTRAST}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface Contrast {
+ public static final short NORMAL = 0;
+ public static final short SOFT = 1;
+ public static final short HARD = 2;
+ }
+
+ /**
+ * Constants for {@link #TAG_SATURATION}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface Saturation {
+ public static final short NORMAL = 0;
+ public static final short LOW = 1;
+ public static final short HIGH = 2;
+ }
+
+ /**
+ * Constants for {@link #TAG_SHARPNESS}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface Sharpness {
+ public static final short NORMAL = 0;
+ public static final short SOFT = 1;
+ public static final short HARD = 2;
+ }
+
+ /**
+ * Constants for {@link #TAG_SUBJECT_DISTANCE}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface SubjectDistance {
+ public static final short UNKNOWN = 0;
+ public static final short MACRO = 1;
+ public static final short CLOSE_VIEW = 2;
+ public static final short DISTANT_VIEW = 3;
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_LATITUDE_REF},
+ * {@link #TAG_GPS_DEST_LATITUDE_REF}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface GpsLatitudeRef {
+ public static final String NORTH = "N";
+ public static final String SOUTH = "S";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_LONGITUDE_REF},
+ * {@link #TAG_GPS_DEST_LONGITUDE_REF}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface GpsLongitudeRef {
+ public static final String EAST = "E";
+ public static final String WEST = "W";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_ALTITUDE_REF}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface GpsAltitudeRef {
+ public static final short SEA_LEVEL = 0;
+ public static final short SEA_LEVEL_NEGATIVE = 1;
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_STATUS}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface GpsStatus {
+ public static final String IN_PROGRESS = "A";
+ public static final String INTEROPERABILITY = "V";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_MEASURE_MODE}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface GpsMeasureMode {
+ public static final String MODE_2_DIMENSIONAL = "2";
+ public static final String MODE_3_DIMENSIONAL = "3";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_SPEED_REF},
+ * {@link #TAG_GPS_DEST_DISTANCE_REF}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface GpsSpeedRef {
+ public static final String KILOMETERS = "K";
+ public static final String MILES = "M";
+ public static final String KNOTS = "N";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_TRACK_REF},
+ * {@link #TAG_GPS_IMG_DIRECTION_REF}, {@link #TAG_GPS_DEST_BEARING_REF}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface GpsTrackRef {
+ public static final String TRUE_DIRECTION = "T";
+ public static final String MAGNETIC_DIRECTION = "M";
+ }
+
+ /**
+ * Constants for {@link #TAG_GPS_DIFFERENTIAL}
+ */
+ @SuppressWarnings( "unused" )
+ public static interface GpsDifferential {
+ public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
+ public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
+ }
+
+ /**
+ * Constants for the jpeg process algorithm used.
+ *
+ * @see #getJpegProcess()
+ */
+ @SuppressWarnings( "unused" )
+ public static interface JpegProcess {
+ public static final short BASELINE = (short) 0xFFC0;
+ public static final short EXTENDED_SEQUENTIAL = (short) 0xFFC1;
+ public static final short PROGRESSIVE = (short) 0xFFC2;
+ public static final short LOSSLESS = (short) 0xFFC3;
+ public static final short DIFFERENTIAL_SEQUENTIAL = (short) 0xFFC5;
+ public static final short DIFFERENTIAL_PROGRESSIVE = (short) 0xFFC6;
+ public static final short DIFFERENTIAL_LOSSLESS = (short) 0xFFC7;
+ public static final short EXTENDED_SEQ_ARITHMETIC_CODING = (short) 0xFFC9;
+ public static final short PROGRESSIVE_AIRTHMETIC_CODING = (short) 0xFFCA;
+ public static final short LOSSLESS_AITHMETIC_CODING = (short) 0xFFCB;
+ public static final short DIFFERENTIAL_SEQ_ARITHMETIC_CODING = (short) 0xFFCD;
+ public static final short DIFFERENTIAL_PROGRESSIVE_ARITHMETIC_CODING = (short) 0xFFCE;
+ public static final short DIFFERENTIAL_LOSSLESS_ARITHMETIC_CODING = (short) 0xFFCF;
+ }
+
+ /**
+ * Constants for the {@link #TAG_SENSITIVITY_TYPE} tag
+ */
+ @SuppressWarnings( "unused" )
+ public static interface SensitivityType {
+
+ public static final short UNKNOWN = 0;
+
+ /** Standard output sensitivity */
+ public static final short SOS = 1;
+
+ /** Recommended exposure index */
+ public static final short REI = 2;
+
+ /** ISO Speed */
+ public static final short ISO = 3;
+
+ /** Standard output sensitivity and Recommended output index */
+ public static final short SOS_REI = 4;
+
+ /** Standard output sensitivity and ISO speed */
+ public static final short SOS_ISO = 5;
+
+ /** Recommended output index and ISO Speed */
+ public static final short REI_ISO = 6;
+
+ /** Standard output sensitivity and Recommended output index and ISO Speed */
+ public static final short SOS_REI_ISO = 7;
+ }
+
+ /**
+ * Options for calling {@link #readExif(java.io.InputStream, int)}, {@link #readExif(byte[], int)},
+ * {@link #readExif(String, int)}
+ */
+ public static interface Options {
+ /**
+ * Option bit to request to parse IFD0.
+ */
+ int OPTION_IFD_0 = 1;
+ /**
+ * Option bit to request to parse IFD1.
+ */
+ int OPTION_IFD_1 = 1 << 1;
+ /**
+ * Option bit to request to parse Exif-IFD.
+ */
+ int OPTION_IFD_EXIF = 1 << 2;
+ /**
+ * Option bit to request to parse GPS-IFD.
+ */
+ int OPTION_IFD_GPS = 1 << 3;
+ /**
+ * Option bit to request to parse Interoperability-IFD.
+ */
+ int OPTION_IFD_INTEROPERABILITY = 1 << 4;
+ /**
+ * Option bit to request to parse thumbnail.
+ */
+ int OPTION_THUMBNAIL = 1 << 5;
+ /**
+ * Option bit to request all the options
+ */
+ int OPTION_ALL = OPTION_IFD_0 ^ OPTION_IFD_1 ^ OPTION_IFD_EXIF ^ OPTION_IFD_GPS ^ OPTION_IFD_INTEROPERABILITY ^ OPTION_THUMBNAIL;
+ }
+}
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInvalidFormatException.java b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInvalidFormatException.java
new file mode 100644
index 00000000..51405a9c
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInvalidFormatException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.sephiroth.android.library.exif2;
+
+public class ExifInvalidFormatException extends Exception {
+ public ExifInvalidFormatException( String meg ) {
+ super( meg );
+ }
+}
\ No newline at end of file
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifOutputStream.java b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifOutputStream.java
new file mode 100644
index 00000000..0e40a798
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifOutputStream.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.sephiroth.android.library.exif2;
+
+import android.util.Log;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+class ExifOutputStream {
+ private static final String TAG = "ExifOutputStream";
+ private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
+
+ private static final int STATE_SOI = 0;
+ private static final int EXIF_HEADER = 0x45786966;
+ private static final short TIFF_HEADER = 0x002A;
+ private static final short TIFF_BIG_ENDIAN = 0x4d4d;
+ private static final short TIFF_LITTLE_ENDIAN = 0x4949;
+ private static final short TAG_SIZE = 12;
+ private static final short TIFF_HEADER_SIZE = 8;
+ private static final int MAX_EXIF_SIZE = 65535;
+ private final ExifInterface mInterface;
+ private ExifData mExifData;
+ private ByteBuffer mBuffer = ByteBuffer.allocate( 4 );
+
+ protected ExifOutputStream( ExifInterface iRef ) {
+ mInterface = iRef;
+ }
+
+ /**
+ * Gets the Exif header to be written into the JPEF file.
+ */
+ protected ExifData getExifData() {
+ return mExifData;
+ }
+
+ /**
+ * Sets the ExifData to be written into the JPEG file. Should be called
+ * before writing image data.
+ */
+ protected void setExifData( ExifData exifData ) {
+ mExifData = exifData;
+ }
+
+ private int requestByteToBuffer(
+ int requestByteCount, byte[] buffer, int offset, int length ) {
+ int byteNeeded = requestByteCount - mBuffer.position();
+ int byteToRead = length > byteNeeded ? byteNeeded : length;
+ mBuffer.put( buffer, offset, byteToRead );
+ return byteToRead;
+ }
+
+ public void writeExifData( OutputStream out ) throws IOException {
+ if( mExifData == null ) {
+ return;
+ }
+
+ Log.v( TAG, "Writing exif data..." );
+
+ ArrayList nullTags = stripNullValueTags( mExifData );
+ createRequiredIfdAndTag();
+ int exifSize = calculateAllOffset();
+ // Log.i(TAG, "exifSize: " + (exifSize + 8));
+ if( exifSize + 8 > MAX_EXIF_SIZE ) {
+ throw new IOException( "Exif header is too large (>64Kb)" );
+ }
+
+ BufferedOutputStream outputStream = new BufferedOutputStream( out, STREAMBUFFER_SIZE );
+ OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream( outputStream );
+
+ dataOutputStream.setByteOrder( ByteOrder.BIG_ENDIAN );
+
+ dataOutputStream.write( 0xFF );
+ dataOutputStream.write( JpegHeader.TAG_M_EXIF );
+ dataOutputStream.writeShort( (short) ( exifSize + 8 ) );
+ dataOutputStream.writeInt( EXIF_HEADER );
+ dataOutputStream.writeShort( (short) 0x0000 );
+ if( mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN ) {
+ dataOutputStream.writeShort( TIFF_BIG_ENDIAN );
+ }
+ else {
+ dataOutputStream.writeShort( TIFF_LITTLE_ENDIAN );
+ }
+ dataOutputStream.setByteOrder( mExifData.getByteOrder() );
+ dataOutputStream.writeShort( TIFF_HEADER );
+ dataOutputStream.writeInt( 8 );
+ writeAllTags( dataOutputStream );
+
+ writeThumbnail( dataOutputStream );
+
+ for( ExifTag t : nullTags ) {
+ mExifData.addTag( t );
+ }
+
+ dataOutputStream.flush();
+ }
+
+ private ArrayList stripNullValueTags( ExifData data ) {
+ ArrayList nullTags = new ArrayList();
+ for( ExifTag t : data.getAllTags() ) {
+ if( t.getValue() == null && ! ExifInterface.isOffsetTag( t.getTagId() ) ) {
+ data.removeTag( t.getTagId(), t.getIfd() );
+ nullTags.add( t );
+ }
+ }
+ return nullTags;
+ }
+
+ private void writeThumbnail( OrderedDataOutputStream dataOutputStream ) throws IOException {
+ if( mExifData.hasCompressedThumbnail() ) {
+ Log.d( TAG, "writing thumbnail.." );
+ dataOutputStream.write( mExifData.getCompressedThumbnail() );
+ }
+ else if( mExifData.hasUncompressedStrip() ) {
+ Log.d( TAG, "writing uncompressed strip.." );
+ for( int i = 0; i < mExifData.getStripCount(); i++ ) {
+ dataOutputStream.write( mExifData.getStrip( i ) );
+ }
+ }
+ }
+
+ private void writeAllTags( OrderedDataOutputStream dataOutputStream ) throws IOException {
+ writeIfd( mExifData.getIfdData( IfdId.TYPE_IFD_0 ), dataOutputStream );
+ writeIfd( mExifData.getIfdData( IfdId.TYPE_IFD_EXIF ), dataOutputStream );
+ IfdData interoperabilityIfd = mExifData.getIfdData( IfdId.TYPE_IFD_INTEROPERABILITY );
+ if( interoperabilityIfd != null ) {
+ writeIfd( interoperabilityIfd, dataOutputStream );
+ }
+ IfdData gpsIfd = mExifData.getIfdData( IfdId.TYPE_IFD_GPS );
+ if( gpsIfd != null ) {
+ writeIfd( gpsIfd, dataOutputStream );
+ }
+ IfdData ifd1 = mExifData.getIfdData( IfdId.TYPE_IFD_1 );
+ if( ifd1 != null ) {
+ writeIfd( mExifData.getIfdData( IfdId.TYPE_IFD_1 ), dataOutputStream );
+ }
+ }
+
+ private void writeIfd( IfdData ifd, OrderedDataOutputStream dataOutputStream ) throws IOException {
+ ExifTag[] tags = ifd.getAllTags();
+ dataOutputStream.writeShort( (short) tags.length );
+ for( ExifTag tag : tags ) {
+ dataOutputStream.writeShort( tag.getTagId() );
+ dataOutputStream.writeShort( tag.getDataType() );
+ dataOutputStream.writeInt( tag.getComponentCount() );
+ // Log.v( TAG, "\n" + tag.toString() );
+ if( tag.getDataSize() > 4 ) {
+ dataOutputStream.writeInt( tag.getOffset() );
+ }
+ else {
+ ExifOutputStream.writeTagValue( tag, dataOutputStream );
+ for( int i = 0, n = 4 - tag.getDataSize(); i < n; i++ ) {
+ dataOutputStream.write( 0 );
+ }
+ }
+ }
+ dataOutputStream.writeInt( ifd.getOffsetToNextIfd() );
+ for( ExifTag tag : tags ) {
+ if( tag.getDataSize() > 4 ) {
+ ExifOutputStream.writeTagValue( tag, dataOutputStream );
+ }
+ }
+ }
+
+ private int calculateOffsetOfIfd( IfdData ifd, int offset ) {
+ offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
+ ExifTag[] tags = ifd.getAllTags();
+ for( ExifTag tag : tags ) {
+ if( tag.getDataSize() > 4 ) {
+ tag.setOffset( offset );
+ offset += tag.getDataSize();
+ }
+ }
+ return offset;
+ }
+
+ private void createRequiredIfdAndTag() throws IOException {
+ // IFD0 is required for all file
+ IfdData ifd0 = mExifData.getIfdData( IfdId.TYPE_IFD_0 );
+ if( ifd0 == null ) {
+ ifd0 = new IfdData( IfdId.TYPE_IFD_0 );
+ mExifData.addIfdData( ifd0 );
+ }
+ ExifTag exifOffsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_EXIF_IFD );
+ if( exifOffsetTag == null ) {
+ throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_EXIF_IFD );
+ }
+ ifd0.setTag( exifOffsetTag );
+
+ // Exif IFD is required for all files.
+ IfdData exifIfd = mExifData.getIfdData( IfdId.TYPE_IFD_EXIF );
+ if( exifIfd == null ) {
+ exifIfd = new IfdData( IfdId.TYPE_IFD_EXIF );
+ mExifData.addIfdData( exifIfd );
+ }
+
+ // GPS IFD
+ IfdData gpsIfd = mExifData.getIfdData( IfdId.TYPE_IFD_GPS );
+ if( gpsIfd != null ) {
+ ExifTag gpsOffsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_GPS_IFD );
+ if( gpsOffsetTag == null ) {
+ throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_GPS_IFD );
+ }
+ ifd0.setTag( gpsOffsetTag );
+ }
+
+ // Interoperability IFD
+ IfdData interIfd = mExifData.getIfdData( IfdId.TYPE_IFD_INTEROPERABILITY );
+ if( interIfd != null ) {
+ ExifTag interOffsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_INTEROPERABILITY_IFD );
+ if( interOffsetTag == null ) {
+ throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_INTEROPERABILITY_IFD );
+ }
+ exifIfd.setTag( interOffsetTag );
+ }
+
+ IfdData ifd1 = mExifData.getIfdData( IfdId.TYPE_IFD_1 );
+
+ // thumbnail
+ if( mExifData.hasCompressedThumbnail() ) {
+
+ if( ifd1 == null ) {
+ ifd1 = new IfdData( IfdId.TYPE_IFD_1 );
+ mExifData.addIfdData( ifd1 );
+ }
+
+ ExifTag offsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT );
+ if( offsetTag == null ) {
+ throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT );
+ }
+
+ ifd1.setTag( offsetTag );
+ ExifTag lengthTag = mInterface.buildUninitializedTag( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH );
+ if( lengthTag == null ) {
+ throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH );
+ }
+
+ lengthTag.setValue( mExifData.getCompressedThumbnail().length );
+ ifd1.setTag( lengthTag );
+
+ // Get rid of tags for uncompressed if they exist.
+ ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_OFFSETS ) );
+ ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_BYTE_COUNTS ) );
+ }
+ else if( mExifData.hasUncompressedStrip() ) {
+ if( ifd1 == null ) {
+ ifd1 = new IfdData( IfdId.TYPE_IFD_1 );
+ mExifData.addIfdData( ifd1 );
+ }
+ int stripCount = mExifData.getStripCount();
+ ExifTag offsetTag = mInterface.buildUninitializedTag( ExifInterface.TAG_STRIP_OFFSETS );
+ if( offsetTag == null ) {
+ throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_STRIP_OFFSETS );
+ }
+ ExifTag lengthTag = mInterface.buildUninitializedTag( ExifInterface.TAG_STRIP_BYTE_COUNTS );
+ if( lengthTag == null ) {
+ throw new IOException( "No definition for crucial exif tag: " + ExifInterface.TAG_STRIP_BYTE_COUNTS );
+ }
+ long[] lengths = new long[stripCount];
+ for( int i = 0; i < mExifData.getStripCount(); i++ ) {
+ lengths[i] = mExifData.getStrip( i ).length;
+ }
+ lengthTag.setValue( lengths );
+ ifd1.setTag( offsetTag );
+ ifd1.setTag( lengthTag );
+ // Get rid of tags for compressed if they exist.
+ ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT ) );
+ ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH ) );
+ }
+ else if( ifd1 != null ) {
+ // Get rid of offset and length tags if there is no thumbnail.
+ ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_OFFSETS ) );
+ ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_BYTE_COUNTS ) );
+ ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT ) );
+ ifd1.removeTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH ) );
+ }
+ }
+
+ private int calculateAllOffset() {
+ int offset = TIFF_HEADER_SIZE;
+ IfdData ifd0 = mExifData.getIfdData( IfdId.TYPE_IFD_0 );
+ offset = calculateOffsetOfIfd( ifd0, offset );
+ ifd0.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_EXIF_IFD ) ).setValue( offset );
+
+ IfdData exifIfd = mExifData.getIfdData( IfdId.TYPE_IFD_EXIF );
+ offset = calculateOffsetOfIfd( exifIfd, offset );
+
+ IfdData interIfd = mExifData.getIfdData( IfdId.TYPE_IFD_INTEROPERABILITY );
+ if( interIfd != null ) {
+ exifIfd.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_INTEROPERABILITY_IFD ) ).setValue( offset );
+ offset = calculateOffsetOfIfd( interIfd, offset );
+ }
+
+ IfdData gpsIfd = mExifData.getIfdData( IfdId.TYPE_IFD_GPS );
+ if( gpsIfd != null ) {
+ ifd0.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_GPS_IFD ) ).setValue( offset );
+ offset = calculateOffsetOfIfd( gpsIfd, offset );
+ }
+
+ IfdData ifd1 = mExifData.getIfdData( IfdId.TYPE_IFD_1 );
+ if( ifd1 != null ) {
+ ifd0.setOffsetToNextIfd( offset );
+ offset = calculateOffsetOfIfd( ifd1, offset );
+ }
+
+ // thumbnail
+ if( mExifData.hasCompressedThumbnail() ) {
+ ifd1.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT ) ).setValue( offset );
+ offset += mExifData.getCompressedThumbnail().length;
+ }
+ else if( mExifData.hasUncompressedStrip() ) {
+ int stripCount = mExifData.getStripCount();
+ long[] offsets = new long[stripCount];
+ for( int i = 0; i < mExifData.getStripCount(); i++ ) {
+ offsets[i] = offset;
+ offset += mExifData.getStrip( i ).length;
+ }
+ ifd1.getTag( ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_OFFSETS ) ).setValue( offsets );
+ }
+ return offset;
+ }
+
+ static void writeTagValue( ExifTag tag, OrderedDataOutputStream dataOutputStream ) throws IOException {
+ switch( tag.getDataType() ) {
+ case ExifTag.TYPE_ASCII:
+ byte buf[] = tag.getStringByte();
+ if( buf.length == tag.getComponentCount() ) {
+ buf[buf.length - 1] = 0;
+ dataOutputStream.write( buf );
+ }
+ else {
+ dataOutputStream.write( buf );
+ dataOutputStream.write( 0 );
+ }
+ break;
+ case ExifTag.TYPE_LONG:
+ case ExifTag.TYPE_UNSIGNED_LONG:
+ for( int i = 0, n = tag.getComponentCount(); i < n; i++ ) {
+ dataOutputStream.writeInt( (int) tag.getValueAt( i ) );
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ for( int i = 0, n = tag.getComponentCount(); i < n; i++ ) {
+ dataOutputStream.writeRational( tag.getRational( i ) );
+ }
+ break;
+ case ExifTag.TYPE_UNDEFINED:
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ buf = new byte[tag.getComponentCount()];
+ tag.getBytes( buf );
+ dataOutputStream.write( buf );
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ for( int i = 0, n = tag.getComponentCount(); i < n; i++ ) {
+ dataOutputStream.writeShort( (short) tag.getValueAt( i ) );
+ }
+ break;
+ }
+ }
+}
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifParser.java b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifParser.java
new file mode 100644
index 00000000..d6df859d
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifParser.java
@@ -0,0 +1,1099 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.sephiroth.android.library.exif2;
+
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
+class ExifParser {
+ private static final String TAG = "ExifParser";
+
+ /**
+ * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to
+ * know which IFD we are in.
+ */
+ public static final int EVENT_START_OF_IFD = 0;
+ /**
+ * When the parser reaches a new tag. Call {@link #getTag()}to get the
+ * corresponding tag.
+ */
+ public static final int EVENT_NEW_TAG = 1;
+ /**
+ * When the parser reaches the value area of tag that is registered by
+ * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()}
+ * to get the corresponding tag.
+ */
+ public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
+ /**
+ * When the parser reaches the compressed image area.
+ */
+ public static final int EVENT_COMPRESSED_IMAGE = 3;
+ /**
+ * When the parser reaches the uncompressed image strip. Call
+ * {@link #getStripIndex()} to get the index of the strip.
+ *
+ * @see #getStripIndex()
+ */
+ public static final int EVENT_UNCOMPRESSED_STRIP = 4;
+ /**
+ * When there is nothing more to parse.
+ */
+ public static final int EVENT_END = 5;
+
+ protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
+ protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in M_EXIF
+ // TIFF header
+ protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
+ protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
+ protected static final short TIFF_HEADER_TAIL = 0x002A;
+ protected static final int TAG_SIZE = 12;
+ protected static final int OFFSET_SIZE = 2;
+ protected static final int DEFAULT_IFD0_OFFSET = 8;
+ private static final Charset US_ASCII = Charset.forName( "US-ASCII" );
+ private static final short TAG_EXIF_IFD = ExifInterface.getTrueTagKey( ExifInterface.TAG_EXIF_IFD );
+ private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey( ExifInterface.TAG_GPS_IFD );
+ private static final short TAG_INTEROPERABILITY_IFD = ExifInterface.getTrueTagKey( ExifInterface.TAG_INTEROPERABILITY_IFD );
+ private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT );
+ private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface.getTrueTagKey( ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH );
+ private static final short TAG_STRIP_OFFSETS = ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_OFFSETS );
+ private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface.getTrueTagKey( ExifInterface.TAG_STRIP_BYTE_COUNTS );
+ private final int mOptions;
+ private final ExifInterface mInterface;
+ private final TreeMap mCorrespondingEvent = new TreeMap();
+ private final CountedDataInputStream mTiffStream;
+ private int mIfdStartOffset = 0;
+ private int mNumOfTagInIfd = 0;
+ private int mIfdType;
+ private ExifTag mTag;
+ private ImageEvent mImageEvent;
+ private ExifTag mStripSizeTag;
+ private ExifTag mJpegSizeTag;
+ private boolean mNeedToParseOffsetsInCurrentIfd;
+ private byte[] mDataAboveIfd0;
+ private int mIfd0Position;
+ private int mQualityGuess;
+ private int mImageWidth;
+ private int mImageLength;
+ private short mProcess = 0;
+ private List mSections;
+ private int mUncompressedDataPosition = 0;
+
+ static final int std_luminance_quant_tbl[];
+ static final int std_chrominance_quant_tbl[];
+ static final int deftabs[][];
+
+ static {
+ std_luminance_quant_tbl = new int[]{ 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 57, 51, 56, 55, 64,
+ 72, 92, 78, 64, 68, 87, 69, 55, 56, 80, 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112, 100, 120, 92, 101, 103, 99 };
+
+ std_chrominance_quant_tbl = new int[]
+
+ { 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99 };
+
+ deftabs = new int[][]{ std_luminance_quant_tbl, std_chrominance_quant_tbl };
+ }
+
+ private ExifParser( InputStream inputStream, int options, ExifInterface iRef ) throws IOException, ExifInvalidFormatException {
+ if( inputStream == null ) {
+ throw new IOException( "Null argument inputStream to ExifParser" );
+ }
+
+ Log.v( TAG, "Reading exif..." );
+ mSections = new ArrayList(0);
+
+ mInterface = iRef;
+ mTiffStream = seekTiffData( inputStream );
+ mOptions = options;
+
+ // Log.d( TAG, "sections size: " + mSections.size() );
+
+ if( mTiffStream == null ) {
+ return;
+ }
+
+ parseTiffHeader( mTiffStream );
+
+ long offset = mTiffStream.readUnsignedInt();
+ if( offset > Integer.MAX_VALUE ) {
+ throw new ExifInvalidFormatException( "Invalid offset " + offset );
+ }
+ mIfd0Position = (int) offset;
+ mIfdType = IfdId.TYPE_IFD_0;
+
+ if( isIfdRequested( IfdId.TYPE_IFD_0 ) || needToParseOffsetsInCurrentIfd() ) {
+ registerIfd( IfdId.TYPE_IFD_0, offset );
+ if( offset != DEFAULT_IFD0_OFFSET ) {
+ mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET];
+ read( mDataAboveIfd0 );
+ }
+ }
+ }
+
+ private final byte mByteArray[] = new byte[8];
+ private final ByteBuffer mByteBuffer = ByteBuffer.wrap( mByteArray );
+
+ private int readInt( byte b[], int offset ) {
+ mByteBuffer.rewind();
+ mByteBuffer.put( b, offset, 4 );
+ mByteBuffer.rewind();
+ return mByteBuffer.getInt();
+ }
+
+ private short readShort( byte b[], int offset ) {
+ mByteBuffer.rewind();
+ mByteBuffer.put( b, offset, 2 );
+ mByteBuffer.rewind();
+ return mByteBuffer.getShort();
+ }
+
+ private CountedDataInputStream seekTiffData( InputStream inputStream ) throws IOException, ExifInvalidFormatException {
+ CountedDataInputStream dataStream = new CountedDataInputStream( inputStream );
+ CountedDataInputStream tiffStream = null;
+
+ int a = dataStream.readUnsignedByte();
+ int b = dataStream.readUnsignedByte();
+
+ if( a != 0xFF || b != JpegHeader.TAG_SOI ) {
+ Log.e( TAG, "invalid jpeg header" );
+ return null;
+ }
+
+ while( true ) {
+ int itemlen;
+ int prev;
+ int marker;
+ byte ll,lh;
+ int got;
+ byte data[];
+
+ prev = 0;
+ for( a = 0; ; a++ ) {
+ marker = dataStream.readUnsignedByte();
+ if( marker != 0xff && prev == 0xff ) break;
+ prev = marker;
+ }
+
+ if (a > 10){
+ Log.w( TAG, "Extraneous " + ( a - 1 ) + " padding bytes before section " + marker );
+ }
+
+ Section section = new Section();
+ section.type = marker;
+
+ // Read the length of the section.
+ lh = dataStream.readByte();
+ ll = dataStream.readByte();
+ itemlen = ( ( lh & 0xff ) << 8 ) | ( ll & 0xff );
+
+ if( itemlen < 2 ) {
+ throw new ExifInvalidFormatException( "Invalid marker" );
+ }
+
+ section.size = itemlen;
+
+ data = new byte[itemlen];
+ data[0] = lh;
+ data[1] = ll;
+
+ // Log.i( TAG, "marker: " + String.format( "0x%2X", marker ) + ": " + itemlen + ", position: " + dataStream.getReadByteCount() + ", available: " + dataStream.available() );
+ // got = dataStream.read( data, 2, itemlen-2 );
+
+ got = readBytes( dataStream, data, 2, itemlen - 2 );
+
+ if( got != itemlen - 2 ) {
+ throw new ExifInvalidFormatException( "Premature end of file? Expecting " + (itemlen-2) + ", received " + got );
+ }
+
+ section.data = data;
+
+
+ boolean ignore = false;
+
+ switch( marker ) {
+ case JpegHeader.TAG_M_SOS:
+ // stop before hitting compressed data
+ mSections.add( section );
+ mUncompressedDataPosition = dataStream.getReadByteCount();
+ return tiffStream;
+
+ case JpegHeader.TAG_M_DQT:
+ // Use for jpeg quality guessing
+ process_M_DQT( data, itemlen );
+ break;
+
+ case JpegHeader.TAG_M_DHT:
+ break;
+
+ case JpegHeader.TAG_M_EOI:
+ // in case it's a tables-only JPEG stream
+ Log.w( TAG, "No image in jpeg!" );
+ return null;
+
+ case JpegHeader.TAG_M_COM:
+ // Comment section
+ ignore = true;
+ break;
+
+ case JpegHeader.TAG_M_JFIF:
+ if( itemlen < 16 ) {
+ ignore = true;
+ }
+ break;
+
+ case JpegHeader.TAG_M_IPTC:
+ break;
+
+ case JpegHeader.TAG_M_SOF0:
+ case JpegHeader.TAG_M_SOF1:
+ case JpegHeader.TAG_M_SOF2:
+ case JpegHeader.TAG_M_SOF3:
+ case JpegHeader.TAG_M_SOF5:
+ case JpegHeader.TAG_M_SOF6:
+ case JpegHeader.TAG_M_SOF7:
+ case JpegHeader.TAG_M_SOF9:
+ case JpegHeader.TAG_M_SOF10:
+ case JpegHeader.TAG_M_SOF11:
+ case JpegHeader.TAG_M_SOF13:
+ case JpegHeader.TAG_M_SOF14:
+ case JpegHeader.TAG_M_SOF15:
+ process_M_SOFn( data, marker );
+ break;
+
+ case JpegHeader.TAG_M_EXIF:
+ if( itemlen >= 8 ) {
+ int header = readInt( data, 2 );
+ short headerTail = readShort( data, 6 );
+ // header = Exif, headerTail=\0\0
+ if( header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL ) {
+ tiffStream = new CountedDataInputStream( new ByteArrayInputStream( data, 8, itemlen - 8 ) );
+ tiffStream.setEnd( itemlen - 6 );
+ ignore = false;
+ } else {
+ Log.v( TAG, "Image cotains XMP section" );
+ }
+ }
+ break;
+
+ default:
+ Log.w( TAG, "Unknown marker: " + String.format( "0x%2X", marker ) + ", length: " + itemlen );
+ break;
+ }
+
+ if( !ignore ) {
+ // Log.d( TAG, "adding section with size: " + section.size );
+ mSections.add( section );
+ }
+ else {
+ Log.v( TAG, "ignoring marker: " + String.format( "0x%2X", marker ) + ", length: " + itemlen );
+ }
+ }
+ }
+
+ /**
+ * Using this instead of the default {@link java.io.InputStream#read(byte[], int, int)} because
+ * on remote input streams reading large amount of data can fail
+ *
+ * @param dataStream
+ * @param data
+ * @param offset
+ * @param length
+ * @return
+ * @throws IOException
+ */
+ private int readBytes( final InputStream dataStream, final byte[] data, int offset, final int length ) throws IOException {
+ int count = 0;
+ int n;
+ int max_length = Math.min( 1024, length );
+
+ while( 0 < (n = dataStream.read(data, offset, max_length))) {
+ count += n;
+ offset += n;
+ max_length = Math.min( max_length, length-count );
+ }
+ return count;
+ }
+
+ static int Get16m( byte[] data, int position ) {
+ int b1 = ( data[position] & 0xFF ) << 8;
+ int b2 = data[position + 1] & 0xFF;
+ return b1 | b2;
+ }
+
+ private void process_M_SOFn( final byte[] data, final int marker ) {
+ if( data.length > 7 ) {
+ //int data_precision = data[2] & 0xff;
+ //int num_components = data[7] & 0xff;
+ mImageLength = Get16m( data, 3 );
+ mImageWidth = Get16m( data, 5 );
+ }
+ mProcess = (short) marker;
+ }
+
+ private void process_M_DQT( final byte[] data, int length ) {
+ int a = 2;
+ int c;
+ int tableindex, coefindex;
+ double cumsf = 0.0;
+ int[] reftable = null;
+ int allones = 1;
+
+ while( a < data.length ) {
+ c = data[a++];
+ tableindex = c & 0x0f;
+
+ if( tableindex < 2 ) {
+ reftable = deftabs[tableindex];
+ }
+
+ // Read in the table, compute statistics relative to reference table
+ for( coefindex = 0; coefindex < 64; coefindex++ ) {
+ int val;
+ if( ( c >> 4 ) != 0 ) {
+ int temp;
+ temp = (int) ( data[a++] );
+ temp *= 256;
+ val = (int) data[a++] + temp;
+ }
+ else {
+ val = (int) data[a++];
+ }
+ if( reftable != null ) {
+ double x;
+ // scaling factor in percent
+ x = 100.0 * (double) val / (double) reftable[coefindex];
+ cumsf += x;
+ // separate check for all-ones table (Q 100)
+ if( val != 1 ) allones = 0;
+ }
+ }
+ // Print summary stats
+ if( reftable != null ) { // terse output includes quality
+ double qual;
+ cumsf /= 64.0; // mean scale factor
+ if( allones != 0 ) { // special case for all-ones table
+ qual = 100.0;
+ }
+ else if( cumsf <= 100.0 ) {
+ qual = ( 200.0 - cumsf ) / 2.0;
+ }
+ else {
+ qual = 5000.0 / cumsf;
+ }
+
+ if( tableindex == 0 ) {
+ mQualityGuess = (int) ( qual + 0.5 );
+ // Log.v( TAG, "quality guess: " + mQualityGuess );
+ }
+ }
+ }
+ }
+
+ private void parseTiffHeader( final CountedDataInputStream stream ) throws IOException, ExifInvalidFormatException {
+ short byteOrder = stream.readShort();
+ if( LITTLE_ENDIAN_TAG == byteOrder ) {
+ stream.setByteOrder( ByteOrder.LITTLE_ENDIAN );
+ }
+ else if( BIG_ENDIAN_TAG == byteOrder ) {
+ stream.setByteOrder( ByteOrder.BIG_ENDIAN );
+ }
+ else {
+ throw new ExifInvalidFormatException( "Invalid TIFF header" );
+ }
+
+ if( stream.readShort() != TIFF_HEADER_TAIL ) {
+ throw new ExifInvalidFormatException( "Invalid TIFF header" );
+ }
+ }
+
+ private boolean isIfdRequested( int ifdType ) {
+ switch( ifdType ) {
+ case IfdId.TYPE_IFD_0:
+ return ( mOptions & ExifInterface.Options.OPTION_IFD_0 ) != 0;
+ case IfdId.TYPE_IFD_1:
+ return ( mOptions & ExifInterface.Options.OPTION_IFD_1 ) != 0;
+ case IfdId.TYPE_IFD_EXIF:
+ return ( mOptions & ExifInterface.Options.OPTION_IFD_EXIF ) != 0;
+ case IfdId.TYPE_IFD_GPS:
+ return ( mOptions & ExifInterface.Options.OPTION_IFD_GPS ) != 0;
+ case IfdId.TYPE_IFD_INTEROPERABILITY:
+ return ( mOptions & ExifInterface.Options.OPTION_IFD_INTEROPERABILITY ) != 0;
+ }
+ return false;
+ }
+
+ private boolean needToParseOffsetsInCurrentIfd() {
+ switch( mIfdType ) {
+ case IfdId.TYPE_IFD_0:
+ return isIfdRequested( IfdId.TYPE_IFD_EXIF ) || isIfdRequested( IfdId.TYPE_IFD_GPS ) || isIfdRequested( IfdId.TYPE_IFD_INTEROPERABILITY ) ||
+ isIfdRequested( IfdId.TYPE_IFD_1 );
+ case IfdId.TYPE_IFD_1:
+ return isThumbnailRequested();
+ case IfdId.TYPE_IFD_EXIF:
+ // The offset to interoperability IFD is located in Exif IFD
+ return isIfdRequested( IfdId.TYPE_IFD_INTEROPERABILITY );
+ default:
+ return false;
+ }
+ }
+
+ private void registerIfd( int ifdType, long offset ) {
+ // Cast unsigned int to int since the offset is always smaller
+ // than the size of M_EXIF (65536)
+ mCorrespondingEvent.put( (int) offset, new IfdEvent( ifdType, isIfdRequested( ifdType ) ) );
+ }
+
+ /**
+ * Equivalent to read(buffer, 0, buffer.length).
+ */
+ protected int read( byte[] buffer ) throws IOException {
+ return mTiffStream.read( buffer );
+ }
+
+ private boolean isThumbnailRequested() {
+ return ( mOptions & ExifInterface.Options.OPTION_THUMBNAIL ) != 0;
+ }
+
+ /**
+ * Parses the the given InputStream with the given options
+ *
+ * @throws java.io.IOException
+ * @throws ExifInvalidFormatException
+ */
+ protected static ExifParser parse( InputStream inputStream, int options, ExifInterface iRef ) throws IOException, ExifInvalidFormatException {
+ return new ExifParser( inputStream, options, iRef );
+ }
+//
+// /**
+// * Parses the the given InputStream with default options; that is, every IFD
+// * and thumbnaill will be parsed.
+// *
+// * @throws java.io.IOException
+// * @throws ExifInvalidFormatException
+// */
+// protected static ExifParser parse( InputStream inputStream, boolean requestThumbnail, ExifInterface iRef ) throws IOException, ExifInvalidFormatException {
+// return new ExifParser( inputStream, OPTION_IFD_0 | OPTION_IFD_1 | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY | ( requestThumbnail ? OPTION_THUMBNAIL : 0 ), iRef );
+// }
+
+ /**
+ * Moves the parser forward and returns the next parsing event
+ *
+ * @throws java.io.IOException
+ * @throws ExifInvalidFormatException
+ * @see #EVENT_START_OF_IFD
+ * @see #EVENT_NEW_TAG
+ * @see #EVENT_VALUE_OF_REGISTERED_TAG
+ * @see #EVENT_COMPRESSED_IMAGE
+ * @see #EVENT_UNCOMPRESSED_STRIP
+ * @see #EVENT_END
+ */
+ protected int next() throws IOException, ExifInvalidFormatException {
+ if( null == mTiffStream ) {
+ return EVENT_END;
+ }
+
+ int offset = mTiffStream.getReadByteCount();
+ int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+ if( offset < endOfTags ) {
+ mTag = readTag();
+ if( mTag == null ) {
+ return next();
+ }
+ if( mNeedToParseOffsetsInCurrentIfd ) {
+ checkOffsetOrImageTag( mTag );
+ }
+ return EVENT_NEW_TAG;
+ }
+ else if( offset == endOfTags ) {
+ // There is a link to ifd1 at the end of ifd0
+ if( mIfdType == IfdId.TYPE_IFD_0 ) {
+ long ifdOffset = readUnsignedLong();
+ if( isIfdRequested( IfdId.TYPE_IFD_1 ) || isThumbnailRequested() ) {
+ if( ifdOffset != 0 ) {
+ registerIfd( IfdId.TYPE_IFD_1, ifdOffset );
+ }
+ }
+ }
+ else {
+ int offsetSize = 4;
+ // Some camera models use invalid length of the offset
+ if( mCorrespondingEvent.size() > 0 ) {
+ offsetSize = mCorrespondingEvent.firstEntry().getKey() - mTiffStream.getReadByteCount();
+ }
+ if( offsetSize < 4 ) {
+ Log.w( TAG, "Invalid size of link to next IFD: " + offsetSize );
+ }
+ else {
+ long ifdOffset = readUnsignedLong();
+ if( ifdOffset != 0 ) {
+ Log.w( TAG, "Invalid link to next IFD: " + ifdOffset );
+ }
+ }
+ }
+ }
+ while( mCorrespondingEvent.size() != 0 ) {
+ Entry entry = mCorrespondingEvent.pollFirstEntry();
+ Object event = entry.getValue();
+ try {
+ // Log.v(TAG, "skipTo: " + entry.getKey());
+ skipTo( entry.getKey() );
+ } catch( IOException e ) {
+ Log.w( TAG, "Failed to skip to data at: " + entry.getKey() +
+ " for " + event.getClass().getName() + ", the file may be broken." );
+ continue;
+ }
+ if( event instanceof IfdEvent ) {
+ mIfdType = ( (IfdEvent) event ).ifd;
+ mNumOfTagInIfd = mTiffStream.readUnsignedShort();
+ mIfdStartOffset = entry.getKey();
+
+ if( mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mTiffStream.getEnd() ) {
+ Log.w( TAG, "Invalid size of IFD " + mIfdType );
+ return EVENT_END;
+ }
+
+ mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
+ if( ( (IfdEvent) event ).isRequested ) {
+ return EVENT_START_OF_IFD;
+ }
+ else {
+ skipRemainingTagsInCurrentIfd();
+ }
+ }
+ else if( event instanceof ImageEvent ) {
+ mImageEvent = (ImageEvent) event;
+ return mImageEvent.type;
+ }
+ else {
+ ExifTagEvent tagEvent = (ExifTagEvent) event;
+ mTag = tagEvent.tag;
+ if( mTag.getDataType() != ExifTag.TYPE_UNDEFINED ) {
+ readFullTagValue( mTag );
+ checkOffsetOrImageTag( mTag );
+ }
+ if( tagEvent.isRequested ) {
+ return EVENT_VALUE_OF_REGISTERED_TAG;
+ }
+ }
+ }
+ return EVENT_END;
+ }
+
+ /**
+ * Skips the tags area of current IFD, if the parser is not in the tag area,
+ * nothing will happen.
+ *
+ * @throws java.io.IOException
+ * @throws ExifInvalidFormatException
+ */
+ protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
+ int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
+ int offset = mTiffStream.getReadByteCount();
+ if( offset > endOfTags ) {
+ return;
+ }
+ if( mNeedToParseOffsetsInCurrentIfd ) {
+ while( offset < endOfTags ) {
+ mTag = readTag();
+ offset += TAG_SIZE;
+ if( mTag == null ) {
+ continue;
+ }
+ checkOffsetOrImageTag( mTag );
+ }
+ }
+ else {
+ skipTo( endOfTags );
+ }
+ long ifdOffset = readUnsignedLong();
+ // For ifd0, there is a link to ifd1 in the end of all tags
+ if( mIfdType == IfdId.TYPE_IFD_0 && ( isIfdRequested( IfdId.TYPE_IFD_1 ) || isThumbnailRequested() ) ) {
+ if( ifdOffset > 0 ) {
+ registerIfd( IfdId.TYPE_IFD_1, ifdOffset );
+ }
+ }
+ }
+
+ /**
+ * If {@link #next()} return {@link #EVENT_NEW_TAG} or
+ * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the
+ * corresponding tag.
+ *
+ * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size
+ * of the value is greater than 4 bytes. One should call
+ * {@link ExifTag#hasValue()} to check if the tag contains value. If there
+ * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser
+ * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+ * pointed by the offset.
+ *
+ * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the
+ * tag will have already been read except for tags of undefined type. For
+ * tags of undefined type, call one of the read methods to get the value.
+ *
+ * @see #registerForTagValue(ExifTag)
+ * @see #read(byte[])
+ * @see #read(byte[], int, int)
+ * @see #readLong()
+ * @see #readRational()
+ * @see #readString(int)
+ * @see #readString(int, java.nio.charset.Charset)
+ */
+ protected ExifTag getTag() {
+ return mTag;
+ }
+
+ /**
+ * Gets number of tags in the current IFD area.
+ */
+ public int getTagCountInCurrentIfd() {
+ return mNumOfTagInIfd;
+ }
+
+ /**
+ * Gets the ID of current IFD.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ * @see IfdId#TYPE_IFD_EXIF
+ */
+ protected int getCurrentIfd() {
+ return mIfdType;
+ }
+
+ /**
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+ * get the index of this strip.
+ */
+ protected int getStripIndex() {
+ return mImageEvent.stripIndex;
+ }
+
+ /**
+ * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
+ * get the strip size.
+ */
+ protected int getStripSize() {
+ if( mStripSizeTag == null ) return 0;
+ return (int) mStripSizeTag.getValueAt( 0 );
+ }
+
+ /**
+ * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get
+ * the image data size.
+ */
+ protected int getCompressedImageSize() {
+ if( mJpegSizeTag == null ) {
+ return 0;
+ }
+ return (int) mJpegSizeTag.getValueAt( 0 );
+ }
+
+ private void skipTo( int offset ) throws IOException {
+ mTiffStream.skipTo( offset );
+ // Log.v(TAG, "available: " + mTiffStream.available() );
+ while( ! mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset ) {
+ mCorrespondingEvent.pollFirstEntry();
+ }
+ }
+
+ /**
+ * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may
+ * not contain the value if the size of the value is greater than 4 bytes.
+ * When the value is not available here, call this method so that the parser
+ * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
+ * where the value is located.
+ *
+ * @see #EVENT_VALUE_OF_REGISTERED_TAG
+ */
+ protected void registerForTagValue( ExifTag tag ) {
+ if( tag.getOffset() >= mTiffStream.getReadByteCount() ) {
+ mCorrespondingEvent.put( tag.getOffset(), new ExifTagEvent( tag, true ) );
+ }
+ }
+
+ private void registerCompressedImage( long offset ) {
+ mCorrespondingEvent.put( (int) offset, new ImageEvent( EVENT_COMPRESSED_IMAGE ) );
+ }
+
+ private void registerUncompressedStrip( int stripIndex, long offset ) {
+ mCorrespondingEvent.put( (int) offset, new ImageEvent( EVENT_UNCOMPRESSED_STRIP, stripIndex ) );
+ }
+
+ private ExifTag readTag() throws IOException, ExifInvalidFormatException {
+ short tagId = mTiffStream.readShort();
+ short dataFormat = mTiffStream.readShort();
+ long numOfComp = mTiffStream.readUnsignedInt();
+ if( numOfComp > Integer.MAX_VALUE ) {
+ throw new ExifInvalidFormatException( "Number of component is larger then Integer.MAX_VALUE" );
+ }
+ // Some invalid image file contains invalid data type. Ignore those tags
+ if( ! ExifTag.isValidType( dataFormat ) ) {
+ Log.w( TAG, String.format( "Tag %04x: Invalid data type %d", tagId, dataFormat ) );
+ mTiffStream.skip( 4 );
+ return null;
+ }
+ // TODO: handle numOfComp overflow
+ ExifTag tag = new ExifTag( tagId, dataFormat, (int) numOfComp, mIfdType, ( (int) numOfComp ) != ExifTag.SIZE_UNDEFINED );
+ int dataSize = tag.getDataSize();
+ if( dataSize > 4 ) {
+ long offset = mTiffStream.readUnsignedInt();
+ if( offset > Integer.MAX_VALUE ) {
+ throw new ExifInvalidFormatException( "offset is larger then Integer.MAX_VALUE" );
+ }
+ // Some invalid images put some undefined data before IFD0.
+ // Read the data here.
+ if( ( offset < mIfd0Position ) && ( dataFormat == ExifTag.TYPE_UNDEFINED ) ) {
+ byte[] buf = new byte[(int) numOfComp];
+ System.arraycopy( mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET, buf, 0, (int) numOfComp );
+ tag.setValue( buf );
+ }
+ else {
+ tag.setOffset( (int) offset );
+ }
+ }
+ else {
+ boolean defCount = tag.hasDefinedCount();
+ // Set defined count to 0 so we can add \0 to non-terminated strings
+ tag.setHasDefinedCount( false );
+ // Read value
+ readFullTagValue( tag );
+ tag.setHasDefinedCount( defCount );
+ mTiffStream.skip( 4 - dataSize );
+ // Set the offset to the position of value.
+ tag.setOffset( mTiffStream.getReadByteCount() - 4 );
+ }
+ return tag;
+ }
+
+ /**
+ * Check the tag, if the tag is one of the offset tag that points to the IFD
+ * or image the caller is interested in, register the IFD or image.
+ */
+ private void checkOffsetOrImageTag( ExifTag tag ) {
+ // Some invalid formattd image contains tag with 0 size.
+ if( tag.getComponentCount() == 0 ) {
+ return;
+ }
+ short tid = tag.getTagId();
+ int ifd = tag.getIfd();
+ if( tid == TAG_EXIF_IFD && checkAllowed( ifd, ExifInterface.TAG_EXIF_IFD ) ) {
+ if( isIfdRequested( IfdId.TYPE_IFD_EXIF ) || isIfdRequested( IfdId.TYPE_IFD_INTEROPERABILITY ) ) {
+ registerIfd( IfdId.TYPE_IFD_EXIF, tag.getValueAt( 0 ) );
+ }
+ }
+ else if( tid == TAG_GPS_IFD && checkAllowed( ifd, ExifInterface.TAG_GPS_IFD ) ) {
+ if( isIfdRequested( IfdId.TYPE_IFD_GPS ) ) {
+ registerIfd( IfdId.TYPE_IFD_GPS, tag.getValueAt( 0 ) );
+ }
+ }
+ else if( tid == TAG_INTEROPERABILITY_IFD && checkAllowed( ifd, ExifInterface.TAG_INTEROPERABILITY_IFD ) ) {
+ if( isIfdRequested( IfdId.TYPE_IFD_INTEROPERABILITY ) ) {
+ registerIfd( IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt( 0 ) );
+ }
+ }
+ else if( tid == TAG_JPEG_INTERCHANGE_FORMAT && checkAllowed( ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT ) ) {
+ if( isThumbnailRequested() ) {
+ registerCompressedImage( tag.getValueAt( 0 ) );
+ }
+ }
+ else if( tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH && checkAllowed( ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH ) ) {
+ if( isThumbnailRequested() ) {
+ mJpegSizeTag = tag;
+ }
+ }
+ else if( tid == TAG_STRIP_OFFSETS && checkAllowed( ifd, ExifInterface.TAG_STRIP_OFFSETS ) ) {
+ if( isThumbnailRequested() ) {
+ if( tag.hasValue() ) {
+ for( int i = 0; i < tag.getComponentCount(); i++ ) {
+ if( tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT ) {
+ registerUncompressedStrip( i, tag.getValueAt( i ) );
+ }
+ else {
+ registerUncompressedStrip( i, tag.getValueAt( i ) );
+ }
+ }
+ }
+ else {
+ mCorrespondingEvent.put( tag.getOffset(), new ExifTagEvent( tag, false ) );
+ }
+ }
+ }
+ else if( tid == TAG_STRIP_BYTE_COUNTS && checkAllowed( ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS ) && isThumbnailRequested() && tag.hasValue() ) {
+ mStripSizeTag = tag;
+ }
+ }
+
+ public boolean isDefinedTag(int ifdId, int tagId ) {
+ return mInterface.getTagInfo().get(ExifInterface.defineTag(ifdId, (short)tagId)) != ExifInterface.DEFINITION_NULL;
+ }
+
+ public boolean checkAllowed( int ifd, int tagId ) {
+ int info = mInterface.getTagInfo().get( tagId );
+ if( info == ExifInterface.DEFINITION_NULL ) {
+ return false;
+ }
+ return ExifInterface.isIfdAllowed( info, ifd );
+ }
+
+ protected void readFullTagValue( final ExifTag tag ) throws IOException {
+ // Some invalid images contains tags with wrong size, check it here
+ short type = tag.getDataType();
+ final int componentCount = tag.getComponentCount();
+
+ // sanity check
+ if (componentCount >= 0x66000000) throw new IOException("size out of bounds");
+
+ if( type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
+ type == ExifTag.TYPE_UNSIGNED_BYTE ) {
+ int size = tag.getComponentCount();
+ if( mCorrespondingEvent.size() > 0 ) {
+ if( mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount() + size ) {
+ Object event = mCorrespondingEvent.firstEntry().getValue();
+ if( event instanceof ImageEvent ) {
+ // Tag value overlaps thumbnail, ignore thumbnail.
+ Log.w( TAG, "Thumbnail overlaps value for tag: \n" + tag.toString() );
+ Entry entry = mCorrespondingEvent.pollFirstEntry();
+ Log.w( TAG, "Invalid thumbnail offset: " + entry.getKey() );
+ }
+ else {
+ // Tag value overlaps another tag, shorten count
+ if( event instanceof IfdEvent ) {
+ Log.w( TAG, "Ifd " + ( (IfdEvent) event ).ifd + " overlaps value for tag: \n" + tag.toString() );
+ }
+ else if( event instanceof ExifTagEvent ) {
+ Log.w( TAG, "Tag value for tag: \n" + ( (ExifTagEvent) event ).tag.toString() + " overlaps value for tag: \n" + tag.toString() );
+ }
+ size = mCorrespondingEvent.firstEntry().getKey() - mTiffStream.getReadByteCount();
+ Log.w( TAG, "Invalid size of tag: \n" + tag.toString() + " setting count to: " + size );
+ tag.forceSetComponentCount( size );
+ }
+ }
+ }
+ }
+ switch( tag.getDataType() ) {
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ case ExifTag.TYPE_UNDEFINED: {
+ byte buf[] = new byte[componentCount];
+ read( buf );
+ tag.setValue( buf );
+ }
+ break;
+ case ExifTag.TYPE_ASCII:
+ tag.setValue( readString(componentCount) );
+ break;
+ case ExifTag.TYPE_UNSIGNED_LONG: {
+ long value[] = new long[componentCount];
+ for( int i = 0, n = value.length; i < n; i++ ) {
+ value[i] = readUnsignedLong();
+ }
+ tag.setValue( value );
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_RATIONAL: {
+ Rational value[] = new Rational[componentCount];
+ for( int i = 0, n = value.length; i < n; i++ ) {
+ value[i] = readUnsignedRational();
+ }
+ tag.setValue( value );
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT: {
+ int value[] = new int[componentCount];
+ for( int i = 0, n = value.length; i < n; i++ ) {
+ value[i] = readUnsignedShort();
+ }
+ tag.setValue( value );
+ }
+ break;
+ case ExifTag.TYPE_LONG: {
+ int value[] = new int[componentCount];
+ for( int i = 0, n = value.length; i < n; i++ ) {
+ value[i] = readLong();
+ }
+ tag.setValue( value );
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL: {
+ Rational value[] = new Rational[componentCount];
+ for( int i = 0, n = value.length; i < n; i++ ) {
+ value[i] = readRational();
+ }
+ tag.setValue( value );
+ }
+ break;
+ }
+
+ // Log.v( TAG, "\n" + tag.toString() );
+ }
+
+ /**
+ * Reads bytes from the InputStream.
+ */
+ protected int read( byte[] buffer, int offset, int length ) throws IOException {
+ return mTiffStream.read( buffer, offset, length );
+ }
+
+ /**
+ * Reads a String from the InputStream with US-ASCII charset. The parser
+ * will read n bytes and convert it to ascii string. This is used for
+ * reading values of type {@link ExifTag#TYPE_ASCII}.
+ */
+ protected String readString( int n ) throws IOException {
+ return readString( n, US_ASCII );
+ }
+
+ /**
+ * Reads a String from the InputStream with the given charset. The parser
+ * will read n bytes and convert it to string. This is used for reading
+ * values of type {@link ExifTag#TYPE_ASCII}.
+ */
+ protected String readString( int n, Charset charset ) throws IOException {
+ if( n > 0 ) {
+ return mTiffStream.readString( n, charset );
+ }
+ else {
+ return "";
+ }
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the
+ * InputStream.
+ */
+ protected int readUnsignedShort() throws IOException {
+ return mTiffStream.readShort() & 0xffff;
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the
+ * InputStream.
+ */
+ protected long readUnsignedLong() throws IOException {
+ return readLong() & 0xffffffffL;
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the
+ * InputStream.
+ */
+ protected Rational readUnsignedRational() throws IOException {
+ long nomi = readUnsignedLong();
+ long denomi = readUnsignedLong();
+ return new Rational( nomi, denomi );
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream.
+ */
+ protected int readLong() throws IOException {
+ return mTiffStream.readInt();
+ }
+
+ /**
+ * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream.
+ */
+ protected Rational readRational() throws IOException {
+ int nomi = readLong();
+ int denomi = readLong();
+ return new Rational( nomi, denomi );
+ }
+
+ /**
+ * Gets the byte order of the current InputStream.
+ */
+ protected ByteOrder getByteOrder() {
+ if( null != mTiffStream ) return mTiffStream.getByteOrder();
+ return null;
+ }
+
+ public int getQualityGuess() {
+ return mQualityGuess;
+ }
+
+ public int getImageWidth() {
+ return mImageWidth;
+ }
+
+ public short getJpegProcess() {
+ return mProcess;
+ }
+
+ public int getImageLength() {
+ return mImageLength;
+ }
+
+ public List getSections() {
+ return mSections;
+ }
+
+ public int getUncompressedDataPosition() {
+ return mUncompressedDataPosition;
+ }
+
+ private static class ImageEvent {
+ int stripIndex;
+ int type;
+
+ ImageEvent( int type ) {
+ this.stripIndex = 0;
+ this.type = type;
+ }
+
+ ImageEvent( int type, int stripIndex ) {
+ this.type = type;
+ this.stripIndex = stripIndex;
+ }
+ }
+
+ private static class IfdEvent {
+ int ifd;
+ boolean isRequested;
+
+ IfdEvent( int ifd, boolean isInterestedIfd ) {
+ this.ifd = ifd;
+ this.isRequested = isInterestedIfd;
+ }
+ }
+
+ private static class ExifTagEvent {
+ ExifTag tag;
+ boolean isRequested;
+
+ ExifTagEvent( ExifTag tag, boolean isRequireByUser ) {
+ this.tag = tag;
+ this.isRequested = isRequireByUser;
+ }
+ }
+
+ public static class Section {
+ int size;
+ int type;
+ byte[] data;
+ }
+}
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifReader.java b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifReader.java
new file mode 100644
index 00000000..72729cd3
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifReader.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.sephiroth.android.library.exif2;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This class reads the EXIF header of a JPEG file and stores it in
+ * {@link ExifData}.
+ */
+class ExifReader {
+ private static final String TAG = "ExifReader";
+
+ private final ExifInterface mInterface;
+
+ ExifReader( ExifInterface iRef ) {
+ mInterface = iRef;
+ }
+
+ /**
+ * Parses the inputStream and and returns the EXIF data in an
+ * {@link ExifData}.
+ *
+ * @throws ExifInvalidFormatException
+ * @throws java.io.IOException
+ */
+ protected ExifData read( InputStream inputStream, int options ) throws ExifInvalidFormatException, IOException {
+ ExifParser parser = ExifParser.parse( inputStream, options, mInterface );
+ ExifData exifData = new ExifData( parser.getByteOrder() );
+ exifData.setSections( parser.getSections() );
+ exifData.mUncompressedDataPosition = parser.getUncompressedDataPosition();
+
+ exifData.setQualityGuess( parser.getQualityGuess() );
+ exifData.setJpegProcess( parser.getJpegProcess() );
+
+ final int w = parser.getImageWidth();
+ final int h = parser.getImageLength();
+
+ if( w > 0 && h > 0 ) {
+ exifData.setImageSize( w, h );
+ }
+
+ ExifTag tag;
+
+ int event = parser.next();
+ while( event != ExifParser.EVENT_END ) {
+ switch( event ) {
+ case ExifParser.EVENT_START_OF_IFD:
+ exifData.addIfdData( new IfdData( parser.getCurrentIfd() ) );
+ break;
+ case ExifParser.EVENT_NEW_TAG:
+ tag = parser.getTag();
+
+
+
+ if( ! tag.hasValue() ) {
+ parser.registerForTagValue( tag );
+ }
+ else {
+ // Log.v(TAG, "parsing id " + tag.getTagId() + " = " + tag);
+ if (parser.isDefinedTag(tag.getIfd(), tag.getTagId())) {
+ exifData.getIfdData(tag.getIfd()).setTag(tag);
+ }
+ else {
+ Log.w(TAG, "skip tag because not registered in the tag table:" + tag);
+ }
+ }
+ break;
+ case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
+ tag = parser.getTag();
+ if( tag.getDataType() == ExifTag.TYPE_UNDEFINED ) {
+ parser.readFullTagValue( tag );
+ }
+ exifData.getIfdData( tag.getIfd() ).setTag( tag );
+ break;
+ case ExifParser.EVENT_COMPRESSED_IMAGE:
+ byte buf[] = new byte[parser.getCompressedImageSize()];
+ if( buf.length == parser.read( buf ) ) {
+ exifData.setCompressedThumbnail( buf );
+ }
+ else {
+ Log.w( TAG, "Failed to read the compressed thumbnail" );
+ }
+ break;
+ case ExifParser.EVENT_UNCOMPRESSED_STRIP:
+ buf = new byte[parser.getStripSize()];
+ if( buf.length == parser.read( buf ) ) {
+ exifData.setStripBytes( parser.getStripIndex(), buf );
+ }
+ else {
+ Log.w( TAG, "Failed to read the strip bytes" );
+ }
+ break;
+ }
+ event = parser.next();
+ }
+ return exifData;
+ }
+}
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifTag.java b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifTag.java
new file mode 100644
index 00000000..2921c330
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifTag.java
@@ -0,0 +1,1016 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.sephiroth.android.library.exif2;
+
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+/**
+ * This class stores information of an EXIF tag. For more information about
+ * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
+ * instantiated using {@link ExifInterface#buildTag}.
+ *
+ * @see ExifInterface
+ */
+public class ExifTag {
+ /**
+ * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
+ */
+ public static final short TYPE_UNSIGNED_BYTE = 1;
+ /**
+ * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit
+ * ASCII code. The final byte is terminated with NULL.
+ */
+ public static final short TYPE_ASCII = 2;
+ /**
+ * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer
+ */
+ public static final short TYPE_UNSIGNED_SHORT = 3;
+ /**
+ * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer
+ */
+ public static final short TYPE_UNSIGNED_LONG = 4;
+ /**
+ * The RATIONAL type of EXIF standard. It consists of two LONGs. The first
+ * one is the numerator and the second one expresses the denominator.
+ */
+ public static final short TYPE_UNSIGNED_RATIONAL = 5;
+ /**
+ * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
+ * value depending on the field definition.
+ */
+ public static final short TYPE_UNDEFINED = 7;
+ /**
+ * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer
+ * (2's complement notation).
+ */
+ public static final short TYPE_LONG = 9;
+ /**
+ * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first
+ * one is the numerator and the second one is the denominator.
+ */
+ public static final short TYPE_RATIONAL = 10;
+ static final int SIZE_UNDEFINED = 0;
+ private static final int TYPE_TO_SIZE_MAP[] = new int[11];
+ private static final int UNSIGNED_SHORT_MAX = 65535;
+ private static final long UNSIGNED_LONG_MAX = 4294967295L;
+ private static final long LONG_MAX = Integer.MAX_VALUE;
+ private static final long LONG_MIN = Integer.MIN_VALUE;
+
+ static {
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT] = 2;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG] = 4;
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL] = 8;
+ TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_LONG] = 4;
+ TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
+ }
+ private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat( "yyyy:MM:dd kk:mm:ss" );
+ private static Charset US_ASCII = Charset.forName( "US-ASCII" );
+ // Exif TagId
+ private final short mTagId;
+ // Exif Tag Type
+ private final short mDataType;
+ // If tag has defined count
+ private boolean mHasDefinedDefaultComponentCount;
+ // Actual data count in tag (should be number of elements in value array)
+ private int mComponentCountActual;
+ // The ifd that this tag should be put in
+ private int mIfd;
+ // The value (array of elements of type Tag Type)
+ private Object mValue;
+ // Value offset in exif header.
+ private int mOffset;
+
+ // Use builtTag in ExifInterface instead of constructor.
+ ExifTag(
+ short tagId, short type, int componentCount, int ifd, boolean hasDefinedComponentCount ) {
+ mTagId = tagId;
+ mDataType = type;
+ mComponentCountActual = componentCount;
+ mHasDefinedDefaultComponentCount = hasDefinedComponentCount;
+ mIfd = ifd;
+ mValue = null;
+ }
+
+ /**
+ * Returns true if the given IFD is a valid IFD.
+ */
+ public static boolean isValidIfd( int ifdId ) {
+ return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1 || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY || ifdId == IfdId.TYPE_IFD_GPS;
+ }
+
+ /**
+ * Returns true if a given type is a valid tag type.
+ */
+ public static boolean isValidType( short type ) {
+ return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII ||
+ type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG ||
+ type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED ||
+ type == TYPE_LONG || type == TYPE_RATIONAL;
+ }
+
+ /**
+ * Returns the ID of the IFD this tag belongs to.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ public int getIfd() {
+ return mIfd;
+ }
+
+ protected void setIfd( int ifdId ) {
+ mIfd = ifdId;
+ }
+
+ /**
+ * Gets the TID of this tag.
+ */
+ public short getTagId() {
+ return mTagId;
+ }
+
+ /**
+ * Gets the total data size in bytes of the value of this tag.
+ */
+ public int getDataSize() {
+ return getComponentCount() * getElementSize( getDataType() );
+ }
+
+ /**
+ * Gets the component count of this tag.
+ */
+
+ // TODO: fix integer overflows with this
+ public int getComponentCount() {
+ return mComponentCountActual;
+ }
+
+ /**
+ * Gets the element size of the given data type in bytes.
+ *
+ * @see #TYPE_ASCII
+ * @see #TYPE_LONG
+ * @see #TYPE_RATIONAL
+ * @see #TYPE_UNDEFINED
+ * @see #TYPE_UNSIGNED_BYTE
+ * @see #TYPE_UNSIGNED_LONG
+ * @see #TYPE_UNSIGNED_RATIONAL
+ * @see #TYPE_UNSIGNED_SHORT
+ */
+ public static int getElementSize( short type ) {
+ return TYPE_TO_SIZE_MAP[type];
+ }
+
+ /**
+ * Gets the data type of this tag
+ *
+ * @see #TYPE_ASCII
+ * @see #TYPE_LONG
+ * @see #TYPE_RATIONAL
+ * @see #TYPE_UNDEFINED
+ * @see #TYPE_UNSIGNED_BYTE
+ * @see #TYPE_UNSIGNED_LONG
+ * @see #TYPE_UNSIGNED_RATIONAL
+ * @see #TYPE_UNSIGNED_SHORT
+ */
+ public short getDataType() {
+ return mDataType;
+ }
+
+ /**
+ * Sets the component count of this tag. Call this function before
+ * setValue() if the length of value does not match the component count.
+ */
+ protected void forceSetComponentCount( int count ) {
+ mComponentCountActual = count;
+ }
+
+ /**
+ * Returns true if this ExifTag contains value; otherwise, this tag will
+ * contain an offset value that is determined when the tag is written.
+ */
+ public boolean hasValue() {
+ return mValue != null;
+ }
+
+ /**
+ * Sets integer values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if:
+ *
+ * - The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
+ * - The value overflows.
+ * - The value.length does NOT match the component count in the definition
+ * for this tag.
+ *
+ */
+ public boolean setValue( int[] value ) {
+ if( checkBadComponentCount( value.length ) ) {
+ return false;
+ }
+ if( mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG &&
+ mDataType != TYPE_UNSIGNED_LONG ) {
+ return false;
+ }
+ if( mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort( value ) ) {
+ return false;
+ }
+ else if( mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong( value ) ) {
+ return false;
+ }
+
+ long[] data = new long[value.length];
+ for( int i = 0; i < value.length; i++ ) {
+ data[i] = value[i];
+ }
+ mValue = data;
+ mComponentCountActual = value.length;
+ return true;
+ }
+
+ /**
+ * Sets integer value into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method
+ * will fail if:
+ *
+ * - The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
+ * - The value overflows.
+ * - The component count in the definition of this tag is not 1.
+ *
+ */
+ public boolean setValue( int value ) {
+ return setValue( new int[]{ value } );
+ }
+
+ /**
+ * Sets long values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+ *
+ * - The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
+ * - The value overflows.
+ * - The value.length does NOT match the component count in the definition
+ * for this tag.
+ *
+ */
+ public boolean setValue( long[] value ) {
+ if( checkBadComponentCount( value.length ) || mDataType != TYPE_UNSIGNED_LONG ) {
+ return false;
+ }
+ if( checkOverflowForUnsignedLong( value ) ) {
+ return false;
+ }
+ mValue = value;
+ mComponentCountActual = value.length;
+ return true;
+ }
+
+ /**
+ * Sets long values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if:
+ *
+ * - The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
+ * - The value overflows.
+ * - The component count in the definition for this tag is not 1.
+ *
+ */
+ public boolean setValue( long value ) {
+ return setValue( new long[]{ value } );
+ }
+
+ /**
+ * Sets Rational values into this tag. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+ * method will fail if:
+ *
+ * - The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+ * or {@link #TYPE_RATIONAL}.
+ * - The value overflows.
+ * - The value.length does NOT match the component count in the definition
+ * for this tag.
+ *
+ *
+ * @see Rational
+ */
+ public boolean setValue( Rational[] value ) {
+ if( checkBadComponentCount( value.length ) ) {
+ return false;
+ }
+ if( mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL ) {
+ return false;
+ }
+ if( mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational( value ) ) {
+ return false;
+ }
+ else if( mDataType == TYPE_RATIONAL && checkOverflowForRational( value ) ) {
+ return false;
+ }
+
+ mValue = value;
+ mComponentCountActual = value.length;
+ return true;
+ }
+
+ /**
+ * Sets a Rational value into this tag. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This
+ * method will fail if:
+ *
+ * - The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL}
+ * or {@link #TYPE_RATIONAL}.
+ * - The value overflows.
+ * - The component count in the definition for this tag is not 1.
+ *
+ *
+ * @see Rational
+ */
+ public boolean setValue( Rational value ) {
+ return setValue( new Rational[]{ value } );
+ }
+
+ /**
+ * Sets byte values into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+ * will fail if:
+ *
+ * - The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+ * {@link #TYPE_UNDEFINED} .
+ * - The length does NOT match the component count in the definition for
+ * this tag.
+ *
+ */
+ public boolean setValue( byte[] value, int offset, int length ) {
+ if( checkBadComponentCount( length ) ) {
+ return false;
+ }
+ if( mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED ) {
+ return false;
+ }
+ mValue = new byte[length];
+ System.arraycopy( value, offset, mValue, 0, length );
+ mComponentCountActual = length;
+ return true;
+ }
+
+ /**
+ * Equivalent to setValue(value, 0, value.length).
+ */
+ public boolean setValue( byte[] value ) {
+ return setValue( value, 0, value.length );
+ }
+
+ /**
+ * Sets byte value into this tag. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method
+ * will fail if:
+ *
+ * - The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or
+ * {@link #TYPE_UNDEFINED} .
+ * - The component count in the definition for this tag is not 1.
+ *
+ */
+ public boolean setValue( byte value ) {
+ return setValue( new byte[]{ value } );
+ }
+
+ /**
+ * Sets the value for this tag using an appropriate setValue method for the
+ * given object. This method will fail if:
+ *
+ * - The corresponding setValue method for the class of the object passed
+ * in would fail.
+ * - There is no obvious way to cast the object passed in into an EXIF tag
+ * type.
+ *
+ */
+ public boolean setValue( Object obj ) {
+ if( obj == null ) {
+ return false;
+ }
+ else if( obj instanceof Short ) {
+ return setValue( ( (Short) obj ).shortValue() & 0x0ffff );
+ }
+ else if( obj instanceof String ) {
+ return setValue( (String) obj );
+ }
+ else if( obj instanceof int[] ) {
+ return setValue( (int[]) obj );
+ }
+ else if( obj instanceof long[] ) {
+ return setValue( (long[]) obj );
+ }
+ else if( obj instanceof Rational ) {
+ return setValue( (Rational) obj );
+ }
+ else if( obj instanceof Rational[] ) {
+ return setValue( (Rational[]) obj );
+ }
+ else if( obj instanceof byte[] ) {
+ return setValue( (byte[]) obj );
+ }
+ else if( obj instanceof Integer ) {
+ return setValue( ( (Integer) obj ).intValue() );
+ }
+ else if( obj instanceof Long ) {
+ return setValue( ( (Long) obj ).longValue() );
+ }
+ else if( obj instanceof Byte ) {
+ return setValue( ( (Byte) obj ).byteValue() );
+ }
+ else if( obj instanceof Short[] ) {
+ // Nulls in this array are treated as zeroes.
+ Short[] arr = (Short[]) obj;
+ int[] fin = new int[arr.length];
+ for( int i = 0; i < arr.length; i++ ) {
+ fin[i] = ( arr[i] == null ) ? 0 : arr[i].shortValue() & 0x0ffff;
+ }
+ return setValue( fin );
+ }
+ else if( obj instanceof Integer[] ) {
+ // Nulls in this array are treated as zeroes.
+ Integer[] arr = (Integer[]) obj;
+ int[] fin = new int[arr.length];
+ for( int i = 0; i < arr.length; i++ ) {
+ fin[i] = ( arr[i] == null ) ? 0 : arr[i].intValue();
+ }
+ return setValue( fin );
+ }
+ else if( obj instanceof Long[] ) {
+ // Nulls in this array are treated as zeroes.
+ Long[] arr = (Long[]) obj;
+ long[] fin = new long[arr.length];
+ for( int i = 0; i < arr.length; i++ ) {
+ fin[i] = ( arr[i] == null ) ? 0 : arr[i].longValue();
+ }
+ return setValue( fin );
+ }
+ else if( obj instanceof Byte[] ) {
+ // Nulls in this array are treated as zeroes.
+ Byte[] arr = (Byte[]) obj;
+ byte[] fin = new byte[arr.length];
+ for( int i = 0; i < arr.length; i++ ) {
+ fin[i] = ( arr[i] == null ) ? 0 : arr[i].byteValue();
+ }
+ return setValue( fin );
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * Sets a timestamp to this tag. The method converts the timestamp with the
+ * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This
+ * method will fail if the data type is not {@link #TYPE_ASCII} or the
+ * component count of this tag is not 20 or undefined.
+ *
+ * @param time the number of milliseconds since Jan. 1, 1970 GMT
+ * @return true on success
+ */
+ public boolean setTimeValue( long time ) {
+ // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
+ synchronized( TIME_FORMAT ) {
+ return setValue( TIME_FORMAT.format( new Date( time ) ) );
+ }
+ }
+
+ /**
+ * Sets a string value into this tag. This method should be used for tags of
+ * type {@link #TYPE_ASCII}. The string is converted to an ASCII string.
+ * Characters that cannot be converted are replaced with '?'. The length of
+ * the string must be equal to either (component count -1) or (component
+ * count). The final byte will be set to the string null terminator '\0',
+ * overwriting the last character in the string if the value.length is equal
+ * to the component count. This method will fail if:
+ *
+ * - The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.
+ * - The length of the string is not equal to (component count -1) or
+ * (component count) in the definition for this tag.
+ *
+ */
+ public boolean setValue( String value ) {
+ if( mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED ) {
+ return false;
+ }
+
+ byte[] buf = value.getBytes( US_ASCII );
+ byte[] finalBuf = buf;
+ if( buf.length > 0 ) {
+ finalBuf = ( buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED ) ? buf : Arrays.copyOf( buf, buf.length + 1 );
+ }
+ else if( mDataType == TYPE_ASCII && mComponentCountActual == 1 ) {
+ finalBuf = new byte[]{ 0 };
+ }
+ int count = finalBuf.length;
+ if( checkBadComponentCount( count ) ) {
+ return false;
+ }
+ mComponentCountActual = count;
+ mValue = finalBuf;
+ return true;
+ }
+
+ private boolean checkBadComponentCount( int count ) {
+ if( mHasDefinedDefaultComponentCount && ( mComponentCountActual != count ) ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Gets the value as a String. This method should be used for tags of type
+ * {@link #TYPE_ASCII}.
+ *
+ * @param defaultValue the String to return if the tag's value does not
+ * exist or cannot be converted to a String.
+ * @return the tag's value as a String, or the defaultValue.
+ */
+ public String getValueAsString( String defaultValue ) {
+ String s = getValueAsString();
+ if( s == null ) {
+ return defaultValue;
+ }
+ return s;
+ }
+
+ /**
+ * Gets the value as a String. This method should be used for tags of type
+ * {@link #TYPE_ASCII}.
+ *
+ * @return the value as a String, or null if the tag's value does not exist
+ * or cannot be converted to a String.
+ */
+ public String getValueAsString() {
+ if( mValue == null ) {
+ return null;
+ }
+ else if( mValue instanceof String ) {
+ return (String) mValue;
+ }
+ else if( mValue instanceof byte[] ) {
+ return new String( (byte[]) mValue, US_ASCII );
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value as a byte. If there are more than 1 bytes in this value,
+ * gets the first byte. This method should be used for tags of type
+ * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+ *
+ * @param defaultValue the byte to return if tag's value does not exist or
+ * cannot be converted to a byte.
+ * @return the tag's value as a byte, or the defaultValue.
+ */
+ public byte getValueAsByte( byte defaultValue ) {
+ byte[] b = getValueAsBytes();
+ if( b == null || b.length < 1 ) {
+ return defaultValue;
+ }
+ return b[0];
+ }
+
+ /**
+ * Gets the value as a byte array. This method should be used for tags of
+ * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+ *
+ * @return the value as a byte array, or null if the tag's value does not
+ * exist or cannot be converted to a byte array.
+ */
+ public byte[] getValueAsBytes() {
+ if( mValue instanceof byte[] ) {
+ return (byte[]) mValue;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value as a Rational. If there are more than 1 Rationals in this
+ * value, gets the first one. This method should be used for tags of type
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @param defaultValue the numerator of the Rational to return if tag's
+ * value does not exist or cannot be converted to a Rational (the
+ * denominator will be 1).
+ * @return the tag's value as a Rational, or the defaultValue.
+ */
+ public Rational getValueAsRational( long defaultValue ) {
+ Rational defaultVal = new Rational( defaultValue, 1 );
+ return getValueAsRational( defaultVal );
+ }
+
+ /**
+ * Gets the value as a Rational. If there are more than 1 Rationals in this
+ * value, gets the first one. This method should be used for tags of type
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @param defaultValue the Rational to return if tag's value does not exist
+ * or cannot be converted to a Rational.
+ * @return the tag's value as a Rational, or the defaultValue.
+ */
+ public Rational getValueAsRational( Rational defaultValue ) {
+ Rational[] r = getValueAsRationals();
+ if( r == null || r.length < 1 ) {
+ return defaultValue;
+ }
+ return r[0];
+ }
+
+ /**
+ * Gets the value as an array of Rationals. This method should be used for
+ * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ *
+ * @return the value as as an array of Rationals, or null if the tag's value
+ * does not exist or cannot be converted to an array of Rationals.
+ */
+ public Rational[] getValueAsRationals() {
+ if( mValue instanceof Rational[] ) {
+ return (Rational[]) mValue;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value as an int. If there are more than 1 ints in this value,
+ * gets the first one. This method should be used for tags of type
+ * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @param defaultValue the int to return if tag's value does not exist or
+ * cannot be converted to an int.
+ * @return the tag's value as a int, or the defaultValue.
+ */
+ public int getValueAsInt( int defaultValue ) {
+ int[] i = getValueAsInts();
+ if( i == null || i.length < 1 ) {
+ return defaultValue;
+ }
+ return i[0];
+ }
+
+ /**
+ * Gets the value as an array of ints. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @return the value as as an array of ints, or null if the tag's value does
+ * not exist or cannot be converted to an array of ints.
+ */
+ public int[] getValueAsInts() {
+ if( mValue == null ) {
+ return null;
+ }
+ else if( mValue instanceof long[] ) {
+ long[] val = (long[]) mValue;
+ int[] arr = new int[val.length];
+ for( int i = 0; i < val.length; i++ ) {
+ arr[i] = (int) val[i]; // Truncates
+ }
+ return arr;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the value or null if none exists. If there are more than 1 longs in
+ * this value, gets the first one. This method should be used for tags of
+ * type {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @param defaultValue the long to return if tag's value does not exist or
+ * cannot be converted to a long.
+ * @return the tag's value as a long, or the defaultValue.
+ */
+ public long getValueAsLong( long defaultValue ) {
+ long[] l = getValueAsLongs();
+ if( l == null || l.length < 1 ) {
+ return defaultValue;
+ }
+ return l[0];
+ }
+
+ /**
+ * Gets the value as an array of longs. This method should be used for tags
+ * of type {@link #TYPE_UNSIGNED_LONG}.
+ *
+ * @return the value as as an array of longs, or null if the tag's value
+ * does not exist or cannot be converted to an array of longs.
+ */
+ public long[] getValueAsLongs() {
+ if( mValue instanceof long[] ) {
+ return (long[]) mValue;
+ }
+ return null;
+ }
+
+ /**
+ * Gets the tag's value or null if none exists.
+ */
+ public Object getValue() {
+ return mValue;
+ }
+
+ /**
+ * Gets a long representation of the value.
+ *
+ * @param defaultValue value to return if there is no value or value is a
+ * rational with a denominator of 0.
+ * @return the tag's value as a long, or defaultValue if no representation
+ * exists.
+ */
+ public long forceGetValueAsLong( long defaultValue ) {
+ long[] l = getValueAsLongs();
+ if( l != null && l.length >= 1 ) {
+ return l[0];
+ }
+ byte[] b = getValueAsBytes();
+ if( b != null && b.length >= 1 ) {
+ return b[0];
+ }
+ Rational[] r = getValueAsRationals();
+ if( r != null && r.length >= 1 && r[0].getDenominator() != 0 ) {
+ return (long) r[0].toDouble();
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG},
+ * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE},
+ * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call
+ * {@link #getRational(int)} instead.
+ *
+ * @throws IllegalArgumentException if the data type is
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ */
+ protected long getValueAt( int index ) {
+ if( mValue instanceof long[] ) {
+ return ( (long[]) mValue )[index];
+ }
+ else if( mValue instanceof byte[] ) {
+ return ( (byte[]) mValue )[index];
+ }
+ throw new IllegalArgumentException( "Cannot get integer value from " + convertTypeToString( mDataType ) );
+ }
+
+ private static String convertTypeToString( short type ) {
+ switch( type ) {
+ case TYPE_UNSIGNED_BYTE:
+ return "UNSIGNED_BYTE";
+ case TYPE_ASCII:
+ return "ASCII";
+ case TYPE_UNSIGNED_SHORT:
+ return "UNSIGNED_SHORT";
+ case TYPE_UNSIGNED_LONG:
+ return "UNSIGNED_LONG";
+ case TYPE_UNSIGNED_RATIONAL:
+ return "UNSIGNED_RATIONAL";
+ case TYPE_UNDEFINED:
+ return "UNDEFINED";
+ case TYPE_LONG:
+ return "LONG";
+ case TYPE_RATIONAL:
+ return "RATIONAL";
+ default:
+ return "";
+ }
+ }
+
+ /**
+ * Gets the {@link #TYPE_ASCII} data.
+ *
+ * @throws IllegalArgumentException If the type is NOT
+ * {@link #TYPE_ASCII}.
+ */
+ protected String getString() {
+ if( mDataType != TYPE_ASCII ) {
+ throw new IllegalArgumentException( "Cannot get ASCII value from " + convertTypeToString( mDataType ) );
+ }
+ return new String( (byte[]) mValue, US_ASCII );
+ }
+
+ /*
+ * Get the converted ascii byte. Used by ExifOutputStream.
+ */
+ protected byte[] getStringByte() {
+ return (byte[]) mValue;
+ }
+
+ /**
+ * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data.
+ *
+ * @throws IllegalArgumentException If the type is NOT
+ * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}.
+ */
+ protected Rational getRational( int index ) {
+ if( ( mDataType != TYPE_RATIONAL ) && ( mDataType != TYPE_UNSIGNED_RATIONAL ) ) {
+ throw new IllegalArgumentException( "Cannot get RATIONAL value from " + convertTypeToString( mDataType ) );
+ }
+ return ( (Rational[]) mValue )[index];
+ }
+
+ /**
+ * Equivalent to getBytes(buffer, 0, buffer.length).
+ */
+ protected void getBytes( byte[] buf ) {
+ getBytes( buf, 0, buf.length );
+ }
+
+ /**
+ * Gets the {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE} data.
+ *
+ * @param buf the byte array in which to store the bytes read.
+ * @param offset the initial position in buffer to store the bytes.
+ * @param length the maximum number of bytes to store in buffer. If length >
+ * component count, only the valid bytes will be stored.
+ * @throws IllegalArgumentException If the type is NOT
+ * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}.
+ */
+ protected void getBytes( byte[] buf, int offset, int length ) {
+ if( ( mDataType != TYPE_UNDEFINED ) && ( mDataType != TYPE_UNSIGNED_BYTE ) ) {
+ throw new IllegalArgumentException( "Cannot get BYTE value from " + convertTypeToString( mDataType ) );
+ }
+ System.arraycopy( mValue, 0, buf, offset, ( length > mComponentCountActual ) ? mComponentCountActual : length );
+ }
+
+ /**
+ * Gets the offset of this tag. This is only valid if this data size > 4 and
+ * contains an offset to the location of the actual value.
+ */
+ protected int getOffset() {
+ return mOffset;
+ }
+
+ /**
+ * Sets the offset of this tag.
+ */
+ protected void setOffset( int offset ) {
+ mOffset = offset;
+ }
+
+ protected void setHasDefinedCount( boolean d ) {
+ mHasDefinedDefaultComponentCount = d;
+ }
+
+ protected boolean hasDefinedCount() {
+ return mHasDefinedDefaultComponentCount;
+ }
+
+ private boolean checkOverflowForUnsignedShort( int[] value ) {
+ for( int v : value ) {
+ if( v > UNSIGNED_SHORT_MAX || v < 0 ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForUnsignedLong( long[] value ) {
+ for( long v : value ) {
+ if( v < 0 || v > UNSIGNED_LONG_MAX ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForUnsignedLong( int[] value ) {
+ for( int v : value ) {
+ if( v < 0 ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForUnsignedRational( Rational[] value ) {
+ for( Rational v : value ) {
+ if( v.getNumerator() < 0 || v.getDenominator() < 0 || v.getNumerator() > UNSIGNED_LONG_MAX || v.getDenominator() > UNSIGNED_LONG_MAX ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean checkOverflowForRational( Rational[] value ) {
+ for( Rational v : value ) {
+ if( v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN || v.getNumerator() > LONG_MAX || v.getDenominator() > LONG_MAX ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean equals( Object obj ) {
+ if( obj == null ) {
+ return false;
+ }
+ if( obj instanceof ExifTag ) {
+ ExifTag tag = (ExifTag) obj;
+ if( tag.mTagId != this.mTagId || tag.mComponentCountActual != this.mComponentCountActual || tag.mDataType != this.mDataType ) {
+ return false;
+ }
+ if( mValue != null ) {
+ if( tag.mValue == null ) {
+ return false;
+ }
+ else if( mValue instanceof long[] ) {
+ if( ! ( tag.mValue instanceof long[] ) ) {
+ return false;
+ }
+ return Arrays.equals( (long[]) mValue, (long[]) tag.mValue );
+ }
+ else if( mValue instanceof Rational[] ) {
+ if( ! ( tag.mValue instanceof Rational[] ) ) {
+ return false;
+ }
+ return Arrays.equals( (Rational[]) mValue, (Rational[]) tag.mValue );
+ }
+ else if( mValue instanceof byte[] ) {
+ if( ! ( tag.mValue instanceof byte[] ) ) {
+ return false;
+ }
+ return Arrays.equals( (byte[]) mValue, (byte[]) tag.mValue );
+ }
+ else {
+ return mValue.equals( tag.mValue );
+ }
+ }
+ else {
+ return tag.mValue == null;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format( "tag id: %04X\n", mTagId ) + "ifd id: " + mIfd + "\ntype: " + convertTypeToString( mDataType ) + "\ncount: " + mComponentCountActual + "\noffset: " +
+ mOffset + "\nvalue: " + forceGetValueAsString() + "\n";
+ }
+
+ /**
+ * Gets a string representation of the value.
+ */
+ public String forceGetValueAsString() {
+ if( mValue == null ) {
+ return "";
+ }
+ else if( mValue instanceof byte[] ) {
+ if( mDataType == TYPE_ASCII ) {
+ return new String( (byte[]) mValue, US_ASCII );
+ }
+ else {
+ return Arrays.toString( (byte[]) mValue );
+ }
+ }
+ else if( mValue instanceof long[] ) {
+ if( ( (long[]) mValue ).length == 1 ) {
+ return String.valueOf( ( (long[]) mValue )[0] );
+ }
+ else {
+ return Arrays.toString( (long[]) mValue );
+ }
+ }
+ else if( mValue instanceof Object[] ) {
+ if( ( (Object[]) mValue ).length == 1 ) {
+ Object val = ( (Object[]) mValue )[0];
+ if( val == null ) {
+ return "";
+ }
+ else {
+ return val.toString();
+ }
+ }
+ else {
+ return Arrays.toString( (Object[]) mValue );
+ }
+ }
+ else {
+ return mValue.toString();
+ }
+ }
+
+}
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifUtil.java b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifUtil.java
new file mode 100644
index 00000000..aaae7b3c
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifUtil.java
@@ -0,0 +1,33 @@
+package it.sephiroth.android.library.exif2;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+/**
+ * Created by alessandro on 20/04/14.
+ */
+public class ExifUtil {
+
+ static final NumberFormat formatter = DecimalFormat.getInstance();
+
+ public static String processLensSpecifications( Rational[] values ) {
+ Rational min_focal = values[0];
+ Rational max_focal = values[1];
+ Rational min_f = values[2];
+ Rational max_f = values[3];
+
+ formatter.setMaximumFractionDigits(1);
+
+ StringBuilder sb = new StringBuilder();
+ sb.append( formatter.format( min_focal.toDouble() ) );
+ sb.append( "-" );
+ sb.append( formatter.format( max_focal.toDouble() ) );
+ sb.append( "mm f/" );
+ sb.append( formatter.format( min_f.toDouble() ) );
+ sb.append( "-" );
+ sb.append( formatter.format( max_f.toDouble() ) );
+
+ return sb.toString();
+ }
+
+}
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/IfdData.java b/exif/src/main/java/it/sephiroth/android/library/exif2/IfdData.java
new file mode 100644
index 00000000..876c67da
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/IfdData.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.sephiroth.android.library.exif2;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class stores all the tags in an IFD.
+ *
+ * @see ExifData
+ * @see ExifTag
+ */
+class IfdData {
+
+ private static final int[] sIfds = { IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF, IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS };
+ private final int mIfdId;
+ private final Map mExifTags = new HashMap();
+ private int mOffsetToNextIfd = 0;
+
+ /**
+ * Creates an IfdData with given IFD ID.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ IfdData( int ifdId ) {
+ mIfdId = ifdId;
+ }
+
+ static protected int[] getIfds() {
+ return sIfds;
+ }
+
+ /**
+ * Gets the {@link ExifTag} with given tag id. Return null if there is no
+ * such tag.
+ */
+ protected ExifTag getTag( short tagId ) {
+ return mExifTags.get( tagId );
+ }
+
+ /**
+ * Adds or replaces a {@link ExifTag}.
+ */
+ protected ExifTag setTag( ExifTag tag ) {
+ tag.setIfd( mIfdId );
+ return mExifTags.put( tag.getTagId(), tag );
+ }
+
+ protected boolean checkCollision( short tagId ) {
+ return mExifTags.get( tagId ) != null;
+ }
+
+ /**
+ * Removes the tag of the given ID
+ */
+ protected void removeTag( short tagId ) {
+ mExifTags.remove( tagId );
+ }
+
+ /**
+ * Gets the offset of next IFD.
+ */
+ protected int getOffsetToNextIfd() {
+ return mOffsetToNextIfd;
+ }
+
+ /**
+ * Sets the offset of next IFD.
+ */
+ protected void setOffsetToNextIfd( int offset ) {
+ mOffsetToNextIfd = offset;
+ }
+
+ /**
+ * Returns true if all tags in this two IFDs are equal. Note that tags of
+ * IFDs offset or thumbnail offset will be ignored.
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if( this == obj ) {
+ return true;
+ }
+ if( obj == null ) {
+ return false;
+ }
+ if( obj instanceof IfdData ) {
+ IfdData data = (IfdData) obj;
+ if( data.getId() == mIfdId && data.getTagCount() == getTagCount() ) {
+ ExifTag[] tags = data.getAllTags();
+ for( ExifTag tag : tags ) {
+ if( ExifInterface.isOffsetTag( tag.getTagId() ) ) {
+ continue;
+ }
+ ExifTag tag2 = mExifTags.get( tag.getTagId() );
+ if( ! tag.equals( tag2 ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the tags count in the IFD.
+ */
+ protected int getTagCount() {
+ return mExifTags.size();
+ }
+
+ /**
+ * Gets the ID of this IFD.
+ *
+ * @see IfdId#TYPE_IFD_0
+ * @see IfdId#TYPE_IFD_1
+ * @see IfdId#TYPE_IFD_EXIF
+ * @see IfdId#TYPE_IFD_GPS
+ * @see IfdId#TYPE_IFD_INTEROPERABILITY
+ */
+ protected int getId() {
+ return mIfdId;
+ }
+
+ /**
+ * Get a array the contains all {@link ExifTag} in this IFD.
+ */
+ protected ExifTag[] getAllTags() {
+ return mExifTags.values().toArray( new ExifTag[mExifTags.size()] );
+ }
+}
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/IfdId.java b/exif/src/main/java/it/sephiroth/android/library/exif2/IfdId.java
new file mode 100644
index 00000000..33e9323c
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/IfdId.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.sephiroth.android.library.exif2;
+
+/**
+ * The constants of the IFD ID defined in EXIF spec.
+ */
+public interface IfdId {
+ public static final int TYPE_IFD_0 = 0;
+ public static final int TYPE_IFD_1 = 1;
+ public static final int TYPE_IFD_EXIF = 2;
+ public static final int TYPE_IFD_INTEROPERABILITY = 3;
+ public static final int TYPE_IFD_GPS = 4;
+ /* This is used in ExifData to allocate enough IfdData */
+ static final int TYPE_IFD_COUNT = 5;
+
+}
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/JpegHeader.java b/exif/src/main/java/it/sephiroth/android/library/exif2/JpegHeader.java
new file mode 100644
index 00000000..ea5833e5
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/JpegHeader.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.sephiroth.android.library.exif2;
+
+class JpegHeader {
+ /** Start Of Image **/
+ public static final int TAG_SOI = 0xD8;
+
+ /** JFIF (JPEG File Interchange Format) */
+ public static final int TAG_M_JFIF = 0xE0;
+
+ /** EXIF table */
+ public static final int TAG_M_EXIF = 0xE1;
+
+ /** Product Information Comment */
+ public static final int TAG_M_COM = 0xFE;
+
+ /** Quantization Table */
+ public static final int TAG_M_DQT = 0xDB;
+
+ /** Start of frame */
+ public static final int TAG_M_SOF0 = 0xC0;
+ public static final int TAG_M_SOF1 = 0xC1;
+ public static final int TAG_M_SOF2 = 0xC2;
+ public static final int TAG_M_SOF3 = 0xC3;
+ public static final int TAG_M_DHT = 0xC4;
+ public static final int TAG_M_SOF5 = 0xC5;
+ public static final int TAG_M_SOF6 = 0xC6;
+ public static final int TAG_M_SOF7 = 0xC7;
+ public static final int TAG_M_SOF9 = 0xC9;
+ public static final int TAG_M_SOF10 = 0xCA;
+ public static final int TAG_M_SOF11 = 0xCB;
+ public static final int TAG_M_SOF13 = 0xCD;
+ public static final int TAG_M_SOF14 = 0xCE;
+ public static final int TAG_M_SOF15 = 0xCF;
+
+ /** Start Of Scan **/
+ public static final int TAG_M_SOS = 0xDA;
+
+ /** End of Image */
+ public static final int TAG_M_EOI = 0xD9;
+
+ public static final int TAG_M_IPTC = 0xED;
+
+ /** default JFIF Header bytes */
+ public static final byte JFIF_HEADER[] = {
+ (byte) 0xff, (byte) JpegHeader.TAG_M_JFIF,
+ 0x00, 0x10, 'J', 'F', 'I', 'F',
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x2C, 0x01,
+ 0x2C, 0x00, 0x00
+ };
+
+
+ public static final short SOI = (short) 0xFFD8;
+ public static final short M_EXIF = (short) 0xFFE1;
+ public static final short M_JFIF = (short) 0xFFE0;
+ public static final short M_EOI = (short) 0xFFD9;
+
+ /**
+ * SOF (start of frame). All value between M_SOF0 and SOF15 is SOF marker except for M_DHT, JPG,
+ * and DAC marker.
+ */
+ public static final short M_SOF0 = (short) 0xFFC0;
+ public static final short M_SOF1 = (short) 0xFFC1;
+ public static final short M_SOF2 = (short) 0xFFC2;
+ public static final short M_SOF3 = (short) 0xFFC3;
+ public static final short M_SOF5 = (short) 0xFFC5;
+ public static final short M_SOF6 = (short) 0xFFC6;
+ public static final short M_SOF7 = (short) 0xFFC7;
+ public static final short M_SOF9 = (short) 0xFFC9;
+ public static final short M_SOF10 = (short) 0xFFCA;
+ public static final short M_SOF11 = (short) 0xFFCB;
+ public static final short M_SOF13 = (short) 0xFFCD;
+ public static final short M_SOF14 = (short) 0xFFCE;
+ public static final short M_SOF15 = (short) 0xFFCF;
+ public static final short M_DHT = (short) 0xFFC4;
+ public static final short JPG = (short) 0xFFC8;
+ public static final short DAC = (short) 0xFFCC;
+
+ /** Define quantization table */
+ public static final short M_DQT = (short) 0xFFDB;
+
+ /** IPTC marker */
+ public static final short M_IPTC = (short) 0xFFED;
+
+ /** Start of scan (begins compressed data */
+ public static final short M_SOS = (short) 0xFFDA;
+
+ /** Comment section * */
+ public static final short M_COM = (short) 0xFFFE; // Comment section
+
+ public static final boolean isSofMarker( short marker ) {
+ return marker >= M_SOF0 && marker <= M_SOF15 && marker != M_DHT && marker != JPG && marker != DAC;
+ }
+}
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/OrderedDataOutputStream.java b/exif/src/main/java/it/sephiroth/android/library/exif2/OrderedDataOutputStream.java
new file mode 100644
index 00000000..3a461952
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/OrderedDataOutputStream.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.sephiroth.android.library.exif2;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+class OrderedDataOutputStream extends FilterOutputStream {
+ private final ByteBuffer mByteBuffer = ByteBuffer.allocate( 4 );
+
+ public OrderedDataOutputStream( OutputStream out ) {
+ super( out );
+ }
+
+ public OrderedDataOutputStream setByteOrder( ByteOrder order ) {
+ mByteBuffer.order( order );
+ return this;
+ }
+
+ public OrderedDataOutputStream writeShort( short value ) throws IOException {
+ mByteBuffer.rewind();
+ mByteBuffer.putShort( value );
+ out.write( mByteBuffer.array(), 0, 2 );
+ return this;
+ }
+
+ public OrderedDataOutputStream writeRational( Rational rational ) throws IOException {
+ writeInt( (int) rational.getNumerator() );
+ writeInt( (int) rational.getDenominator() );
+ return this;
+ }
+
+ public OrderedDataOutputStream writeInt( int value ) throws IOException {
+ mByteBuffer.rewind();
+ mByteBuffer.putInt( value );
+ out.write( mByteBuffer.array() );
+ return this;
+ }
+}
diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/Rational.java b/exif/src/main/java/it/sephiroth/android/library/exif2/Rational.java
new file mode 100644
index 00000000..fb159280
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/Rational.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package it.sephiroth.android.library.exif2;
+
+/**
+ * The rational data type of EXIF tag. Contains a pair of longs representing the
+ * numerator and denominator of a Rational number.
+ */
+public class Rational {
+
+ private final long mNumerator;
+ private final long mDenominator;
+
+ /**
+ * Create a Rational with a given numerator and denominator.
+ *
+ * @param nominator
+ * @param denominator
+ */
+ public Rational( long nominator, long denominator ) {
+ mNumerator = nominator;
+ mDenominator = denominator;
+ }
+
+ /**
+ * Create a copy of a Rational.
+ */
+ public Rational( Rational r ) {
+ mNumerator = r.mNumerator;
+ mDenominator = r.mDenominator;
+ }
+
+ /**
+ * Gets the numerator of the rational.
+ */
+ public long getNumerator() {
+ return mNumerator;
+ }
+
+ /**
+ * Gets the denominator of the rational
+ */
+ public long getDenominator() {
+ return mDenominator;
+ }
+
+ /**
+ * Gets the rational value as type double. Will cause a divide-by-zero error
+ * if the denominator is 0.
+ */
+ public double toDouble() {
+ return mNumerator / (double) mDenominator;
+ }
+
+ @Override
+ public boolean equals( Object obj ) {
+ if( obj == null ) {
+ return false;
+ }
+ if( this == obj ) {
+ return true;
+ }
+ if( obj instanceof Rational ) {
+ Rational data = (Rational) obj;
+ return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return mNumerator + "/" + mDenominator;
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index d3db1092..7435716d 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':app'
+include ':app', ':exif'