()
+ for(d in mIfdDatas) {
+ if(d != null) {
+ val t = d.getTag(tag)
+ if(t != null) {
+ ret.add(t)
+ }
+ }
+ }
+ return if(ret.isEmpty()) null else ret
+ }
+
+ override fun equals(other : Any?) : Boolean {
+ if(this === other) return true
+ if(other is ExifData) {
+ if(other.byteOrder != byteOrder
+ || other.mStripBytes.size != mStripBytes.size
+ || ! Arrays.equals(other.compressedThumbnail, compressedThumbnail)
+ ) {
+ return false
+ }
+ for(i in mStripBytes.indices) {
+ val a = mStripBytes[i]
+ val b = other.mStripBytes[i]
+
+ if(a != null && b != null) {
+ if(! a.contentEquals(b)) return false // 内容が異なる
+ } else if((a == null) xor (b == null)) {
+ return false // 片方だけnull
+ }
+ }
+
+ for(i in 0 until IfdId.TYPE_IFD_COUNT) {
+ val ifd1 = other.getIfdData(i)
+ val ifd2 = getIfdData(i)
+ if(ifd1 != ifd2) return false
+ }
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Returns the [IfdData] object corresponding to a given IFD if it
+ * exists or null.
+ */
+ fun getIfdData(ifdId : Int) : IfdData? {
+ return if(ExifTag.isValidIfd(ifdId)) {
+ mIfdDatas[ifdId]
+ } else null
+ }
+
+ fun setImageSize(imageWidth : Int, imageLength : Int) {
+ this.imageWidth = imageWidth
+ this.imageLength = imageLength
+ }
+
+ override fun hashCode() : Int {
+ var result = byteOrder.hashCode()
+ result = 31 * result + (sections?.hashCode() ?: 0)
+ result = 31 * result + mIfdDatas.contentHashCode()
+ result = 31 * result + (compressedThumbnail?.contentHashCode() ?: 0)
+ result = 31 * result + mStripBytes.hashCode()
+ result = 31 * result + qualityGuess
+ result = 31 * result + imageLength
+ result = 31 * result + imageWidth
+ result = 31 * result + jpegProcess
+ result = 31 * result + mUncompressedDataPosition
+ return result
+ }
+
+ companion object {
+ private const val TAG = "ExifData"
+ private val USER_COMMENT_ASCII = byteArrayOf(0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00)
+ private val USER_COMMENT_JIS = byteArrayOf(0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00)
+ private val USER_COMMENT_UNICODE =
+ byteArrayOf(0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00)
+ }
+}
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
deleted file mode 100644
index 89f555fe..00000000
--- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.java
+++ /dev/null
@@ -1,2853 +0,0 @@
-/*
- * 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/ExifInterface.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.kt
new file mode 100644
index 00000000..a430e39e
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.kt
@@ -0,0 +1,3150 @@
+/*
+ * 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.util.Log
+import android.util.SparseIntArray
+import org.apache.commons.io.IOUtils
+import java.io.*
+import java.nio.ByteOrder
+import java.text.SimpleDateFormat
+import java.util.*
+import kotlin.math.abs
+import kotlin.math.exp
+
+/**
+ * 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
+ */
+@Suppress("unused", "unused")
+class ExifInterface {
+
+ private var mData = ExifData(DEFAULT_BYTE_ORDER)
+
+ private val mGPSTimeStampCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
+ private var mTagInfo : SparseIntArray? = null
+
+ /**
+ * Get the exif tags in this ExifInterface object or null if none exist.
+ *
+ * @return a List of [ExifTag]s.
+ */
+ val allTags : List?
+ get() = mData.allTags
+
+ val tagInfo : SparseIntArray
+ get() {
+ var v = mTagInfo
+ if(v == null) {
+ v = SparseIntArray()
+ mTagInfo = v
+ initTagInfo()
+ }
+ return v
+ }
+
+ /**
+ * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
+ *
+ * @return the thumbnail as a bitmap.
+ */
+ // TODO: implement uncompressed
+ val thumbnailBitmap : Bitmap?
+ get() {
+ if(mData.hasCompressedThumbnail()) {
+ val thumb = mData.compressedThumbnail
+ return BitmapFactory.decodeByteArray(thumb, 0, thumb !!.size)
+ } else if(mData.hasUncompressedStrip()) {
+ }
+ 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.
+ */
+ val thumbnailBytes : ByteArray?
+ get() = when {
+ mData.hasCompressedThumbnail() -> mData.compressedThumbnail
+ mData.hasUncompressedStrip() -> null // TODO: implement this
+ else -> null
+ }
+
+ /**
+ * Returns the thumbnail if it is jpeg compressed, or null if none exists.
+ *
+ * @return the thumbnail as a byte array.
+ */
+ val thumbnail : ByteArray?
+ get() = mData.compressedThumbnail
+
+ /**
+ * Returns the JPEG quality used to generate the image
+ * or 0 if not found
+ *
+ * @return qualityGuess
+ */
+ val qualityGuess : Int
+ get() = mData.qualityGuess
+
+ /**
+ * 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
+ *
+ */
+ val jpegProcess : Short
+ get() = mData.jpegProcess
+
+ /**
+ * Returns the Image size as decoded from the SOF marker
+ */
+ val imageSize : IntArray
+ get() = mData.imageSize
+
+ /**
+ * Check if thumbnail is compressed.
+ *
+ * @return true if the thumbnail is compressed.
+ */
+ val isThumbnailCompressed : Boolean
+ get() = mData.hasCompressedThumbnail()
+
+ /**
+ * Decodes the user comment tag into string as specified in the EXIF
+ * standard. Returns null if decoding failed.
+ */
+ val userComment : String?
+ get() = mData.userComment
+
+ /**
+ * 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
+ */
+ val latLongAsDoubles : DoubleArray?
+ get() {
+ val latitude = getTagRationalValues(TAG_GPS_LATITUDE)
+ val latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF)
+ val longitude = getTagRationalValues(TAG_GPS_LONGITUDE)
+ val longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF)
+ if(latitude == null || longitude == null || latitudeRef == null || longitudeRef == null || latitude.size < 3 || longitude.size < 3) {
+ return null
+ }
+ val latLon = DoubleArray(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
+ */
+ val latitude : String?
+ get() {
+ val latitude = getTagRationalValues(TAG_GPS_LATITUDE)
+ val latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF)
+
+ return if(null == latitude || null == latitudeRef) null else convertRationalLatLonToString(
+ latitude,
+ latitudeRef
+ )
+ }
+
+ /**
+ * Returns a formatted String with the longitude representation:
+ * 77° 37' 51.6" W
+ */
+ val longitude : String?
+ get() {
+ val longitude = getTagRationalValues(TAG_GPS_LONGITUDE)
+ val longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF)
+
+ return if(null == longitude || null == longitudeRef) null else convertRationalLatLonToString(
+ longitude,
+ longitudeRef
+ )
+ }
+
+ /**
+ * Return the aperture size, if present, 0 if missing
+ */
+ val apertureSize : Double
+ get() {
+ var rational = getTagRationalValue(TAG_F_NUMBER)
+ if(null != rational && rational.toDouble() > 0) {
+ return rational.toDouble()
+ }
+
+ rational = getTagRationalValue(TAG_APERTURE_VALUE)
+ return if(null != rational && rational.toDouble() > 0) {
+ exp(rational.toDouble() * Math.log(2.0) * 0.5)
+ } else 0.0
+ }
+
+ /**
+ * Returns the lens model as string if any of the tags [.TAG_LENS_MODEL]
+ * or [.TAG_LENS_SPECS] are found
+ *
+ * @return the string representation of the lens spec
+ */
+ val lensModelDescription : String?
+ get() {
+ val lensModel = getTagStringValue(TAG_LENS_MODEL)
+ if(null != lensModel) return lensModel
+
+ val rat = getTagRationalValues(TAG_LENS_SPECS)
+ return if(null != rat) ExifUtil.processLensSpecifications(rat) else null
+
+ }
+
+ init {
+ mGPSDateStampFormat.timeZone = TimeZone.getTimeZone("UTC")
+ }
+
+ /**
+ * Given the value from [.TAG_FOCAL_PLANE_RESOLUTION_UNIT] or [.TAG_RESOLUTION_UNIT]
+ * this method will return the corresponding value in millimeters
+ *
+ * @param resolution [.TAG_FOCAL_PLANE_RESOLUTION_UNIT] or [.TAG_RESOLUTION_UNIT]
+ * @return resolution in millimeters
+ */
+ fun getResolutionUnit(resolution : Int) : Double =
+ when(resolution.toShort()) {
+ ResolutionUnit.INCHES -> 25.4
+ ResolutionUnit.CENTIMETERS -> 10.0
+ ResolutionUnit.MILLIMETERS -> 1.0
+ ResolutionUnit.MICROMETERS -> .001
+ else -> 25.4
+ }
+
+ /**
+ * 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 [it.sephiroth.android.library.exif2.ExifInterface.Options]
+ * @throws java.io.IOException for I/O error
+ * @see .readExif
+ */
+ @Throws(IOException::class)
+ fun readExif(inFileName : String?, options : Int) {
+ requireNotNull(inFileName) { NULL_ARGUMENT_STRING }
+ var `is` : InputStream? = null
+ try {
+ `is` = BufferedInputStream(FileInputStream(inFileName))
+ readExif(`is`, options)
+ } catch(e : IOException) {
+ 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 [it.sephiroth.android.library.exif2.ExifInterface.Options]
+ * @throws java.io.IOException for I/O error
+ */
+ @Throws(IOException::class)
+ fun readExif(inStream : InputStream?, options : Int) {
+ requireNotNull(inStream) { NULL_ARGUMENT_STRING }
+ val d : ExifData
+ try {
+ d = ExifReader(this).read(inStream, options)
+ } catch(e : ExifInvalidFormatException) {
+ throw IOException("Invalid exif format : $e")
+ }
+
+ mData = d
+ }
+
+ /**
+ * Sets the exif tags, clearing this ExifInterface object's existing exif
+ * tags.
+ *
+ * @param tags a collection of exif tags to set.
+ */
+ fun setExif(tags : Collection) {
+ clearExif()
+ setTags(tags)
+ }
+
+ /**
+ * Clears this ExifInterface object's existing exif tags.
+ */
+ private fun clearExif() {
+ mData = 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
+ */
+ private fun setTags(tags : Collection?) {
+ if(null == tags) return
+ for(t in 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 [.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.
+ */
+ private fun setTag(tag : ExifTag) : ExifTag? {
+ return mData.addTag(tag)
+ }
+
+ @Throws(IOException::class)
+ fun writeExif(dstFilename : String) {
+ Log.i(TAG, "writeExif: $dstFilename")
+
+ // create a backup file
+ val dst_file = File(dstFilename)
+ val bak_file = 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.absolutePath, bak_file.absolutePath)
+
+ // Now switch bak into dst
+ // Log.d( TAG, "rename the bak into dst" );
+
+ bak_file.renameTo(dst_file)
+ } finally {
+ // deleting backup file
+
+ bak_file.delete()
+ }
+ }
+
+ @Throws(IOException::class)
+ fun writeExif(srcFilename : String, dstFilename : String) {
+ Log.i(TAG, "writeExif: $dstFilename")
+
+ // src and dst cannot be the same
+ if(srcFilename == 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
+ val input = FileInputStream(srcFilename)
+ val output = FileOutputStream(dstFilename)
+
+ val position = writeExif_internal(input, output, mData)
+
+ // 7. write the rest of the image..
+ val in_channel = input.channel
+ val out_channel = output.channel
+ in_channel.transferTo(position.toLong(), in_channel.size() - position, out_channel)
+ output.flush()
+
+ closeQuietly(input)
+ closeQuietly(output)
+ }
+
+ @Throws(IOException::class)
+ fun writeExif(input : InputStream, dstFilename : String) {
+ Log.i(TAG, "writeExif: $dstFilename")
+
+ // inpur is used *ONLY* to read the image uncompressed data
+ // exif tags are not used here
+
+ val output = FileOutputStream(dstFilename)
+ writeExif_internal(input, output, mData)
+
+ // 7. write the rest of the image..
+ IOUtils.copy(input, output)
+
+ output.flush()
+ output.close()
+ }
+
+ @Throws(IOException::class)
+ fun writeExif(input : Bitmap, dstFilename : String, quality : Int) {
+ Log.i(TAG, "writeExif: $dstFilename")
+
+ // inpur is used *ONLY* to read the image uncompressed data
+ // exif tags are not used here
+
+ val out = ByteArrayOutputStream()
+ input.compress(Bitmap.CompressFormat.JPEG, quality, out)
+
+ val `in` = ByteArrayInputStream(out.toByteArray())
+ out.close()
+
+ writeExif(`in`, dstFilename)
+ }
+
+ /**
+ * 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 [it.sephiroth.android.library.exif2.ExifInterface.Options]
+ * @throws java.io.IOException for I/O error
+ * @see .readExif
+ */
+ @Throws(IOException::class)
+ fun readExif(jpeg : ByteArray, options : Int) {
+ readExif(ByteArrayInputStream(jpeg), options)
+ }
+
+ /**
+ * Returns a list of ExifTags that share a TID (which can be obtained by
+ * calling [.getTrueTagKey] on a defined tag constant) or null if none
+ * exist.
+ *
+ * @param tagId a TID as defined in the exif standard (or with
+ * [.defineTag]).
+ * @return a List of [ExifTag]s.
+ */
+ fun getTagsForTagId(tagId : Short) : List? {
+ return mData.getAllTagsForTagId(tagId)
+ }
+
+ /**
+ * Returns a list of ExifTags that share an IFD (which can be obtained by
+ * calling [.getTrueIfd] on a defined tag constant) or null if none
+ * exist.
+ *
+ * @param ifdId an IFD as defined in the exif standard (or with
+ * [.defineTag]).
+ * @return a List of [ExifTag]s.
+ */
+ fun getTagsForIfdId(ifdId : Int) : List? {
+ 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. [.TAG_IMAGE_WIDTH].
+ * @return an [ExifTag] or null if none exists.
+ */
+ fun getTag(tagId : Int) : ExifTag? {
+ val ifdId = getDefinedTagDefaultIfd(tagId)
+ return getTag(tagId, ifdId)
+ }
+
+ /**
+ * Gets the default IFD for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. [.TAG_IMAGE_WIDTH].
+ * @return the default IFD for a tag definition or [.IFD_NULL] if no
+ * definition exists.
+ */
+ private fun getDefinedTagDefaultIfd(tagId : Int) : Int {
+ val info = tagInfo.get(tagId)
+ return if(info == DEFINITION_NULL) {
+ IFD_NULL
+ } else getTrueIfd(tagId)
+ }
+
+ /**
+ * Gets an ExifTag for an IFD other than the tag's default.
+ *
+ * @see .getTag
+ */
+ private fun getTag(tagId : Int, ifdId : Int) : ExifTag? {
+ return if(! ExifTag.isValidIfd(ifdId)) {
+ null
+ } else mData.getTag(getTrueTagKey(tagId), ifdId)
+ }
+
+ private fun 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
+ val ifdAllowedIfds = intArrayOf(IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1)
+ val ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) shl 24
+ mTagInfo !!.put(TAG_MAKE, ifdFlags or (ExifTag.TYPE_ASCII shl 16))
+ mTagInfo !!.put(
+ TAG_IMAGE_WIDTH,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_IMAGE_LENGTH,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_BITS_PER_SAMPLE,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 3
+ )
+ mTagInfo !!.put(
+ TAG_COMPRESSION,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_PHOTOMETRIC_INTERPRETATION,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_ORIENTATION,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_SAMPLES_PER_PIXEL,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_PLANAR_CONFIGURATION,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_Y_CB_CR_SUB_SAMPLING,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_Y_CB_CR_POSITIONING,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_X_RESOLUTION,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_Y_RESOLUTION,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_RESOLUTION_UNIT,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_STRIP_OFFSETS,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_LONG shl 16)
+ )
+ mTagInfo !!.put(
+ TAG_ROWS_PER_STRIP,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_STRIP_BYTE_COUNTS,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_LONG shl 16)
+ )
+ mTagInfo !!.put(
+ TAG_TRANSFER_FUNCTION,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 3 * 256
+ )
+ mTagInfo !!.put(
+ TAG_WHITE_POINT,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_PRIMARY_CHROMATICITIES,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 6
+ )
+ mTagInfo !!.put(
+ TAG_Y_CB_CR_COEFFICIENTS,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 3
+ )
+ mTagInfo !!.put(
+ TAG_REFERENCE_BLACK_WHITE,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 6
+ )
+ mTagInfo !!.put(TAG_DATE_TIME, ifdFlags or (ExifTag.TYPE_ASCII shl 16) or 20)
+ mTagInfo !!.put(
+ TAG_IMAGE_DESCRIPTION,
+ ifdFlags or (ExifTag.TYPE_ASCII shl 16)
+ )
+ mTagInfo !!.put(TAG_MODEL, ifdFlags or (ExifTag.TYPE_ASCII shl 16))
+ mTagInfo !!.put(TAG_SOFTWARE, ifdFlags or (ExifTag.TYPE_ASCII shl 16))
+ mTagInfo !!.put(TAG_ARTIST, ifdFlags or (ExifTag.TYPE_ASCII shl 16))
+ mTagInfo !!.put(TAG_COPYRIGHT, ifdFlags or (ExifTag.TYPE_ASCII shl 16))
+ mTagInfo !!.put(
+ TAG_EXIF_IFD,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_GPS_IFD,
+ ifdFlags or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1
+ )
+ // IFD1 tags
+ val ifd1AllowedIfds = intArrayOf(IfdId.TYPE_IFD_1)
+ val ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) shl 24
+ mTagInfo !!.put(
+ TAG_JPEG_INTERCHANGE_FORMAT,
+ ifdFlags1 or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+ ifdFlags1 or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1
+ )
+ // Exif tags
+ val exifAllowedIfds = intArrayOf(IfdId.TYPE_IFD_EXIF)
+ val exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) shl 24
+ mTagInfo !!.put(
+ TAG_EXIF_VERSION,
+ exifFlags or (ExifTag.TYPE_UNDEFINED shl 16) or 4
+ )
+ mTagInfo !!.put(
+ TAG_FLASHPIX_VERSION,
+ exifFlags or (ExifTag.TYPE_UNDEFINED shl 16) or 4
+ )
+ mTagInfo !!.put(
+ TAG_COLOR_SPACE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_COMPONENTS_CONFIGURATION,
+ exifFlags or (ExifTag.TYPE_UNDEFINED shl 16) or 4
+ )
+ mTagInfo !!.put(
+ TAG_COMPRESSED_BITS_PER_PIXEL,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_PIXEL_X_DIMENSION,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_PIXEL_Y_DIMENSION,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1
+ )
+ mTagInfo !!.put(TAG_MAKER_NOTE, exifFlags or (ExifTag.TYPE_UNDEFINED shl 16))
+ mTagInfo !!.put(
+ TAG_USER_COMMENT,
+ exifFlags or (ExifTag.TYPE_UNDEFINED shl 16)
+ )
+ mTagInfo !!.put(
+ TAG_RELATED_SOUND_FILE,
+ exifFlags or (ExifTag.TYPE_ASCII shl 16) or 13
+ )
+ mTagInfo !!.put(
+ TAG_DATE_TIME_ORIGINAL,
+ exifFlags or (ExifTag.TYPE_ASCII shl 16) or 20
+ )
+ mTagInfo !!.put(
+ TAG_DATE_TIME_DIGITIZED,
+ exifFlags or (ExifTag.TYPE_ASCII shl 16) or 20
+ )
+ mTagInfo !!.put(TAG_SUB_SEC_TIME, exifFlags or (ExifTag.TYPE_ASCII shl 16))
+ mTagInfo !!.put(
+ TAG_SUB_SEC_TIME_ORIGINAL,
+ exifFlags or (ExifTag.TYPE_ASCII shl 16)
+ )
+ mTagInfo !!.put(
+ TAG_SUB_SEC_TIME_DIGITIZED,
+ exifFlags or (ExifTag.TYPE_ASCII shl 16)
+ )
+ mTagInfo !!.put(
+ TAG_IMAGE_UNIQUE_ID,
+ exifFlags or (ExifTag.TYPE_ASCII shl 16) or 33
+ )
+ mTagInfo !!.put(
+ TAG_LENS_SPECS,
+ exifFlags or (ExifTag.TYPE_RATIONAL shl 16) or 4
+ )
+ mTagInfo !!.put(TAG_LENS_MAKE, exifFlags or (ExifTag.TYPE_ASCII shl 16))
+ mTagInfo !!.put(TAG_LENS_MODEL, exifFlags or (ExifTag.TYPE_ASCII shl 16))
+ mTagInfo !!.put(
+ TAG_SENSITIVITY_TYPE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_EXPOSURE_TIME,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_F_NUMBER,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_EXPOSURE_PROGRAM,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_SPECTRAL_SENSITIVITY,
+ exifFlags or (ExifTag.TYPE_ASCII shl 16)
+ )
+ mTagInfo !!.put(
+ TAG_ISO_SPEED_RATINGS,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16)
+ )
+ mTagInfo !!.put(TAG_OECF, exifFlags or (ExifTag.TYPE_UNDEFINED shl 16))
+ mTagInfo !!.put(
+ TAG_SHUTTER_SPEED_VALUE,
+ exifFlags or (ExifTag.TYPE_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_APERTURE_VALUE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_BRIGHTNESS_VALUE,
+ exifFlags or (ExifTag.TYPE_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_EXPOSURE_BIAS_VALUE,
+ exifFlags or (ExifTag.TYPE_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_MAX_APERTURE_VALUE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_SUBJECT_DISTANCE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_METERING_MODE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_LIGHT_SOURCE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_FLASH,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_FOCAL_LENGTH,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_SUBJECT_AREA,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16)
+ )
+ mTagInfo !!.put(
+ TAG_FLASH_ENERGY,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_SPATIAL_FREQUENCY_RESPONSE,
+ exifFlags or (ExifTag.TYPE_UNDEFINED shl 16)
+ )
+ mTagInfo !!.put(
+ TAG_FOCAL_PLANE_X_RESOLUTION,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_FOCAL_PLANE_Y_RESOLUTION,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_FOCAL_PLANE_RESOLUTION_UNIT,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_SUBJECT_LOCATION,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_EXPOSURE_INDEX,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_SENSING_METHOD,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_FILE_SOURCE,
+ exifFlags or (ExifTag.TYPE_UNDEFINED shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_SCENE_TYPE,
+ exifFlags or (ExifTag.TYPE_UNDEFINED shl 16) or 1
+ )
+ mTagInfo !!.put(TAG_CFA_PATTERN, exifFlags or (ExifTag.TYPE_UNDEFINED shl 16))
+ mTagInfo !!.put(
+ TAG_CUSTOM_RENDERED,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_EXPOSURE_MODE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_WHITE_BALANCE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_DIGITAL_ZOOM_RATIO,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_FOCAL_LENGTH_IN_35_MM_FILE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_SCENE_CAPTURE_TYPE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_GAIN_CONTROL,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_CONTRAST,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_SATURATION,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_SHARPNESS,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_DEVICE_SETTING_DESCRIPTION,
+ exifFlags or (ExifTag.TYPE_UNDEFINED shl 16)
+ )
+ mTagInfo !!.put(
+ TAG_SUBJECT_DISTANCE_RANGE,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_INTEROPERABILITY_IFD,
+ exifFlags or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1
+ )
+ // GPS tag
+ val gpsAllowedIfds = intArrayOf(IfdId.TYPE_IFD_GPS)
+ val gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) shl 24
+ mTagInfo !!.put(
+ TAG_GPS_VERSION_ID,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_BYTE shl 16) or 4
+ )
+ mTagInfo !!.put(
+ TAG_GPS_LATITUDE_REF,
+ gpsFlags or (ExifTag.TYPE_ASCII shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_GPS_LONGITUDE_REF,
+ gpsFlags or (ExifTag.TYPE_ASCII shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_GPS_LATITUDE,
+ gpsFlags or (ExifTag.TYPE_RATIONAL shl 16) or 3
+ )
+ mTagInfo !!.put(
+ TAG_GPS_LONGITUDE,
+ gpsFlags or (ExifTag.TYPE_RATIONAL shl 16) or 3
+ )
+ mTagInfo !!.put(
+ TAG_GPS_ALTITUDE_REF,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_BYTE shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_GPS_ALTITUDE,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_GPS_TIME_STAMP,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 3
+ )
+ mTagInfo !!.put(TAG_GPS_SATTELLITES, gpsFlags or (ExifTag.TYPE_ASCII shl 16))
+ mTagInfo !!.put(TAG_GPS_STATUS, gpsFlags or (ExifTag.TYPE_ASCII shl 16) or 2)
+ mTagInfo !!.put(
+ TAG_GPS_MEASURE_MODE,
+ gpsFlags or (ExifTag.TYPE_ASCII shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_GPS_DOP,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_GPS_SPEED_REF,
+ gpsFlags or (ExifTag.TYPE_ASCII shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_GPS_SPEED,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_GPS_TRACK_REF,
+ gpsFlags or (ExifTag.TYPE_ASCII shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_GPS_TRACK,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_GPS_IMG_DIRECTION_REF,
+ gpsFlags or (ExifTag.TYPE_ASCII shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_GPS_IMG_DIRECTION,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(TAG_GPS_MAP_DATUM, gpsFlags or (ExifTag.TYPE_ASCII shl 16))
+ mTagInfo !!.put(
+ TAG_GPS_DEST_LATITUDE_REF,
+ gpsFlags or (ExifTag.TYPE_ASCII shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_GPS_DEST_LATITUDE,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_GPS_DEST_BEARING_REF,
+ gpsFlags or (ExifTag.TYPE_ASCII shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_GPS_DEST_BEARING,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_GPS_DEST_DISTANCE_REF,
+ gpsFlags or (ExifTag.TYPE_ASCII shl 16) or 2
+ )
+ mTagInfo !!.put(
+ TAG_GPS_DEST_DISTANCE,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1
+ )
+ mTagInfo !!.put(
+ TAG_GPS_PROCESSING_METHOD,
+ gpsFlags or (ExifTag.TYPE_UNDEFINED shl 16)
+ )
+ mTagInfo !!.put(
+ TAG_GPS_AREA_INFORMATION,
+ gpsFlags or (ExifTag.TYPE_UNDEFINED shl 16)
+ )
+ mTagInfo !!.put(
+ TAG_GPS_DATE_STAMP,
+ gpsFlags or (ExifTag.TYPE_ASCII shl 16) or 11
+ )
+ mTagInfo !!.put(
+ TAG_GPS_DIFFERENTIAL,
+ gpsFlags or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 11
+ )
+ // Interoperability tag
+ val interopAllowedIfds = intArrayOf(IfdId.TYPE_IFD_INTEROPERABILITY)
+ val interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) shl 24
+ mTagInfo !!.put(TAG_INTEROPERABILITY_INDEX, interopFlags or (ExifTag.TYPE_ASCII shl 16))
+ mTagInfo !!.put(TAG_INTEROP_VERSION, interopFlags or (ExifTag.TYPE_UNDEFINED shl 16) or 4)
+ }
+
+ /**
+ * 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. [.TAG_IMAGE_WIDTH].
+ * @return the value of the ExifTag or null if none exists.
+ */
+ fun getTagValue(tagId : Int) : Any? {
+ val ifdId = getDefinedTagDefaultIfd(tagId)
+ return getTagValue(tagId, ifdId)
+ }
+
+ /**
+ * Gets a tag value for an IFD other than the tag's default.
+ *
+ * @see .getTagValue
+ */
+ private fun getTagValue(tagId : Int, ifdId : Int) : Any? {
+ val t = getTag(tagId, ifdId)
+ return t?.getValue()
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagStringValue(tagId : Int, ifdId : Int) : String? {
+ val t = getTag(tagId, ifdId) ?: return null
+ return t.valueAsString
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagStringValue(tagId : Int) : String? {
+ val ifdId = getDefinedTagDefaultIfd(tagId)
+ return getTagStringValue(tagId, ifdId)
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ fun getTagLongValue(tagId : Int) : Long? {
+ val ifdId = getDefinedTagDefaultIfd(tagId)
+ return getTagLongValue(tagId, ifdId)
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagLongValue(tagId : Int, ifdId : Int) : Long? {
+ val l = getTagLongValues(tagId, ifdId)
+ return if(l == null || l.isEmpty()) {
+ null
+ } else l[0]
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagLongValues(tagId : Int, ifdId : Int) : LongArray? {
+ val t = getTag(tagId, ifdId) ?: return null
+ return t.valueAsLongs
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ fun getTagIntValue(tagId : Int) : Int? {
+ val ifdId = getDefinedTagDefaultIfd(tagId)
+ return getTagIntValue(tagId, ifdId)
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagIntValue(tagId : Int, ifdId : Int) : Int? {
+ val l = getTagIntValues(tagId, ifdId)
+ return if(l == null || l.isEmpty()) {
+ null
+ } else l[0]
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagIntValues(tagId : Int, ifdId : Int) : IntArray? {
+ val t = getTag(tagId, ifdId) ?: return null
+ return t.valueAsInts
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagByteValue(tagId : Int) : Byte? {
+ val ifdId = getDefinedTagDefaultIfd(tagId)
+ return getTagByteValue(tagId, ifdId)
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagByteValue(tagId : Int, ifdId : Int) : Byte? {
+ val l = getTagByteValues(tagId, ifdId)
+ return if(l == null || l.isEmpty()) {
+ null
+ } else l[0]
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagByteValues(tagId : Int, ifdId : Int) : ByteArray? {
+ val t = getTag(tagId, ifdId) ?: return null
+ return t.valueAsBytes
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagRationalValue(tagId : Int) : Rational? {
+ val ifdId = getDefinedTagDefaultIfd(tagId)
+ return getTagRationalValue(tagId, ifdId)
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagRationalValue(tagId : Int, ifdId : Int) : Rational? {
+ val l = getTagRationalValues(tagId, ifdId)
+ return if(l == null || l.isEmpty()) {
+ null
+ } else 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
+ */
+ private fun getTagRationalValues(tagId : Int, ifdId : Int) : Array? {
+ val t = getTag(tagId, ifdId) ?: return null
+ return t.valueAsRationals
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ fun getTagLongValues(tagId : Int) : LongArray? {
+ val ifdId = getDefinedTagDefaultIfd(tagId)
+ return getTagLongValues(tagId, ifdId)
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ fun getTagIntValues(tagId : Int) : IntArray? {
+ val ifdId = getDefinedTagDefaultIfd(tagId)
+ return getTagIntValues(tagId, ifdId)
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ fun getTagByteValues(tagId : Int) : ByteArray? {
+ val ifdId = getDefinedTagDefaultIfd(tagId)
+ return getTagByteValues(tagId, ifdId)
+ }
+
+ /**
+ * @see .getTagValue
+ */
+ private fun getTagRationalValues(tagId : Int) : Array? {
+ val 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. [.TAG_IMAGE_WIDTH].
+ * @return true if the tag has a defined number of elements.
+ */
+ fun isTagCountDefined(tagId : Int) : Boolean {
+ val info = tagInfo.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
+ }
+
+ /**
+ * Gets the defined number of elements for a tag.
+ *
+ * @param tagId a defined tag constant, e.g. [.TAG_IMAGE_WIDTH].
+ * @return the number of elements or [ExifTag.SIZE_UNDEFINED] if the
+ * tag or the number of elements is not defined.
+ */
+ fun getDefinedTagCount(tagId : Int) : Int =
+ when(val info = tagInfo.get(tagId)) {
+ 0 -> ExifTag.SIZE_UNDEFINED
+ else -> getComponentCountFromInfo(info)
+ }
+
+ /**
+ * Gets the number of elements for an ExifTag in a given IFD.
+ *
+ * @param tagId a defined tag constant, e.g. [.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.
+ */
+ fun getActualTagCount(tagId : Int, ifdId : Int) : Int {
+ val t = getTag(tagId, ifdId) ?: return 0
+ return t.componentCount
+ }
+
+ // Gets the defined type for a tag.
+ // tagId : a defined tag constant, e.g. [.TAG_IMAGE_WIDTH].
+ fun getDefinedTagType(tagId : Int) : Short {
+ val info = tagInfo.get(tagId)
+ return if(info == 0) - 1 else getTypeFromInfo(info)
+ }
+
+ fun buildUninitializedTag(tagId : Int) : ExifTag? {
+ val info = tagInfo.get(tagId)
+ if(info == 0) {
+ return null
+ }
+ val type = getTypeFromInfo(info)
+ val definedCount = getComponentCountFromInfo(info)
+ val hasDefinedCount = definedCount != ExifTag.SIZE_UNDEFINED
+ val ifdId = getTrueIfd(tagId)
+ return 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. [.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.
+ */
+ fun setTagValue(tagId : Int, `val` : Any) : Boolean {
+ val 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. [.TAG_IMAGE_WIDTH].
+ * @param ifdId the IFD that the ExifTag is in.
+ * @param tagValue 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
+ */
+ private fun setTagValue(tagId : Int, ifdId : Int, tagValue : Any) : Boolean {
+ return getTag(tagId, ifdId)?.setValueAny(tagValue) ?: false
+ }
+
+ /**
+ * Removes the ExifTag for a tag constant from that tag's default IFD.
+ *
+ * @param tagId a tag constant, e.g. [.TAG_IMAGE_WIDTH].
+ */
+ fun deleteTag(tagId : Int) {
+ val ifdId = getDefinedTagDefaultIfd(tagId)
+ deleteTag(tagId, ifdId)
+ }
+
+ /**
+ * Removes the ExifTag for a tag constant from the given IFD.
+ *
+ * @param tagId a tag constant, e.g. [.TAG_IMAGE_WIDTH].
+ * @param ifdId the IFD of the ExifTag to remove.
+ */
+ private fun deleteTag(tagId : Int, ifdId : Int) {
+ 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
+ * @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. [.TAG_IMAGE_WIDTH]) or
+ * [.TAG_NULL] if the definition could not be made.
+ */
+ fun setTagDefinition(
+ tagId : Short,
+ defaultIfd : Int,
+ tagType : Short,
+ defaultComponentCount : Short,
+ allowedIfds : IntArray
+ ) : Int {
+ if(sBannedDefines.contains(tagId)) {
+ return TAG_NULL
+ }
+ if(ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) {
+ val tagDef = defineTag(defaultIfd, tagId)
+ if(tagDef == TAG_NULL) {
+ return TAG_NULL
+ }
+ val otherDefs = getTagDefinitionsForTagId(tagId)
+ val infos = tagInfo
+ // Make sure defaultIfd is in allowedIfds
+ var defaultCheck = false
+ for(i in allowedIfds) {
+ if(defaultIfd == i) {
+ defaultCheck = true
+ }
+ if(! ExifTag.isValidIfd(i)) {
+ return TAG_NULL
+ }
+ }
+ if(! defaultCheck) {
+ return TAG_NULL
+ }
+
+ val ifdFlags = getFlagsFromAllowedIfds(allowedIfds)
+ // Make sure no identical tags can exist in allowedIfds
+ if(otherDefs != null) {
+ for(def in otherDefs) {
+ val tagInfo = infos.get(def)
+ val allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo)
+ if(ifdFlags and allowedFlags != 0) {
+ return TAG_NULL
+ }
+ }
+ }
+ tagInfo.put(
+ tagDef,
+ ifdFlags shl 24 or (tagType shl 16) or defaultComponentCount.toInt()
+ )
+ return tagDef
+ }
+ return TAG_NULL
+ }
+
+ fun getTagDefinition(tagId : Short, defaultIfd : Int) : Int {
+ return tagInfo.get(defineTag(defaultIfd, tagId))
+ }
+
+ private fun getTagDefinitionsForTagId(tagId : Short) : IntArray? {
+ val ifds = IfdData.ifds
+ val defs = IntArray(ifds.size)
+ var counter = 0
+ val infos = tagInfo
+ for(i in ifds) {
+ val def = defineTag(i, tagId)
+ if(infos.get(def) != DEFINITION_NULL) {
+ defs[counter ++] = def
+ }
+ }
+ return if(counter == 0) null else defs.copyOfRange(0, counter)
+
+ }
+
+ fun getTagDefinitionForTag(tag : ExifTag) : Int {
+ val type = tag.dataType
+ val count = tag.componentCount
+ val ifd = tag.ifd
+ return getTagDefinitionForTag(tag.tagId, type, count, ifd)
+ }
+
+ private fun getTagDefinitionForTag(
+ tagId : Short,
+ type : Short,
+ count : Int,
+ ifd : Int
+ ) : Int {
+ val defs = getTagDefinitionsForTagId(tagId) ?: return TAG_NULL
+ val infos = tagInfo
+ var ret = TAG_NULL
+ for(i in defs) {
+ val info = infos.get(i)
+ val def_type = getTypeFromInfo(info)
+ val def_count = getComponentCountFromInfo(info)
+ val def_ifds = getAllowedIfdsFromInfo(info)
+ var valid_ifd = false
+ if(def_ifds != null) {
+ for(j in 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. [.TAG_IMAGE_WIDTH].
+ */
+ fun removeTagDefinition(tagId : Int) {
+ tagInfo.delete(tagId)
+ }
+
+ /**
+ * Resets tag definitions to the default ones.
+ */
+ fun resetTagDefinitions() {
+ mTagInfo = null
+ }
+
+ /**
+ * Check if thumbnail exists.
+ *
+ * @return true if a compressed thumbnail exists.
+ */
+ fun hasThumbnail() : Boolean {
+ // 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.
+ */
+ fun setCompressedThumbnail(thumb : Bitmap) : Boolean {
+ val thumbnail = ByteArrayOutputStream()
+ return if(! thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) {
+ false
+ } else 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.
+ */
+ private fun setCompressedThumbnail(thumb : ByteArray) : Boolean {
+ mData.clearThumbnailAndStrips()
+ mData.compressedThumbnail = thumb
+ return true
+ }
+
+ /**
+ * Clears the compressed thumbnail if it exists.
+ */
+ fun removeCompressedThumbnail() {
+ mData.compressedThumbnail = null
+ }
+
+ /**
+ * 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.
+ */
+ fun getAltitude(defaultValue : Double) : Double {
+
+ val ref = getTagByteValue(TAG_GPS_ALTITUDE_REF)
+ val gpsAltitude = getTagRationalValue(TAG_GPS_ALTITUDE)
+
+ var seaLevel = 1
+ if(null != ref) {
+ seaLevel = if(ref == 1.toByte()) - 1 else 1
+ }
+
+ return if(gpsAltitude != null) {
+ gpsAltitude.toDouble() * seaLevel
+ } else defaultValue
+
+ }
+
+ /**
+ * Creates, formats, and sets the DateTimeStamp tag for one of:
+ * [.TAG_DATE_TIME], [.TAG_DATE_TIME_DIGITIZED],
+ * [.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.
+ */
+ fun addDateTimeStampTag(tagId : Int, timestamp : Long, timezone : TimeZone) : Boolean {
+ if(tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED || tagId == TAG_DATE_TIME_ORIGINAL) {
+ mDateTimeStampFormat.timeZone = timezone
+ val t = buildTag(tagId, mDateTimeStampFormat.format(timestamp)) ?: 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. [.TAG_IMAGE_WIDTH].
+ * @param val the tag's value.
+ * @return an ExifTag object.
+ */
+ fun buildTag(tagId : Int, `val` : Any) : ExifTag? {
+ val 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
+ * [ExifTag.setValue] for this tag's datatype would fail.
+ *
+ * @param tagId a tag constant, e.g. [.TAG_IMAGE_WIDTH].
+ * @param ifdId the IFD that the tag should be in.
+ * @param tagValue the value of the tag to set.
+ * @return an ExifTag object or null if one could not be constructed.
+ * @see .buildTag
+ */
+ fun buildTag(tagId : Int, ifdId : Int, tagValue : Any?) : ExifTag? {
+ val info = tagInfo.get(tagId)
+ if(info == 0 || tagValue == null) {
+ return null
+ }
+ val type = getTypeFromInfo(info)
+ val definedCount = getComponentCountFromInfo(info)
+ val hasDefinedCount = definedCount != ExifTag.SIZE_UNDEFINED
+ if(! isIfdAllowed(info, ifdId)) {
+ return null
+ }
+ val t = ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount)
+ return if(! t.setValueAny(tagValue)) {
+ null
+ } else t
+ }
+
+ /**
+ * 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.
+ */
+ fun addGpsTags(latitude : Double, longitude : Double) : Boolean {
+ val latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude))
+ val longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude))
+ val latRefTag = buildTag(
+ TAG_GPS_LATITUDE_REF,
+ if(latitude >= 0) GpsLatitudeRef.NORTH else GpsLatitudeRef.SOUTH
+ )
+ val longRefTag = buildTag(
+ TAG_GPS_LONGITUDE_REF,
+ if(longitude >= 0) GpsLongitudeRef.EAST else GpsLongitudeRef.WEST
+ )
+ if(latTag == null || longTag == null || latRefTag == null || longRefTag == null) {
+ return false
+ }
+ setTag(latTag)
+ setTag(longTag)
+ setTag(latRefTag)
+ setTag(longRefTag)
+ return true
+ }
+
+ /**
+ * Creates and sets the GPS timestamp tag.
+ *
+ * @param timestamp a GPS timestamp.
+ * @return true if success, false if could not be created or set.
+ */
+ fun addGpsDateTimeStampTag(timestamp : Long) : Boolean {
+ var t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp)) ?: return false
+ setTag(t)
+ mGPSTimeStampCalendar.timeInMillis = timestamp
+ t = buildTag(
+ TAG_GPS_TIME_STAMP,
+ arrayOf(
+ Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY).toLong(), 1),
+ Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE).toLong(), 1),
+ Rational(mGPSTimeStampCalendar.get(Calendar.SECOND).toLong(), 1)
+ )
+ ) ?: return false
+ setTag(t)
+ return true
+ }
+
+ /**
+ * Constants for [.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.
+ *
+ */
+ object Orientation {
+
+ const val TOP_LEFT : Short = 1
+ const val TOP_RIGHT : Short = 2
+ const val BOTTOM_RIGHT : Short = 3
+ const val BOTTOM_LEFT : Short = 4
+ const val LEFT_TOP : Short = 5
+ const val RIGHT_TOP : Short = 6
+ const val RIGHT_BOTTOM : Short = 7
+ const val LEFT_BOTTOM : Short = 8
+ }
+
+ /**
+ * Constants for [.TAG_Y_CB_CR_POSITIONING]
+ */
+ object YCbCrPositioning {
+
+ const val CENTERED : Short = 1
+ const val CO_SITED : Short = 2
+ }
+
+ /**
+ * Constants for [.TAG_COMPRESSION]
+ */
+ object Compression {
+
+ const val UNCOMPRESSION : Short = 1
+ const val JPEG : Short = 6
+ }
+
+ // TODO: uncompressed thumbnail setters
+
+ /**
+ * Constants for [.TAG_RESOLUTION_UNIT]
+ */
+ object ResolutionUnit {
+
+ const val INCHES : Short = 2
+ const val CENTIMETERS : Short = 3
+ const val MILLIMETERS : Short = 4
+ const val MICROMETERS : Short = 5
+ }
+
+ /**
+ * Constants for [.TAG_PHOTOMETRIC_INTERPRETATION]
+ */
+ object PhotometricInterpretation {
+
+ const val RGB : Short = 2
+ const val YCBCR : Short = 6
+ }
+
+ /**
+ * Constants for [.TAG_PLANAR_CONFIGURATION]
+ */
+ object PlanarConfiguration {
+
+ const val CHUNKY : Short = 1
+ const val PLANAR : Short = 2
+ }
+
+ // Convenience methods:
+
+ /**
+ * Constants for [.TAG_EXPOSURE_PROGRAM]
+ */
+ object ExposureProgram {
+
+ const val NOT_DEFINED : Short = 0
+ const val MANUAL : Short = 1
+ const val NORMAL_PROGRAM : Short = 2
+ const val APERTURE_PRIORITY : Short = 3
+ const val SHUTTER_PRIORITY : Short = 4
+ const val CREATIVE_PROGRAM : Short = 5
+ const val ACTION_PROGRAM : Short = 6
+ const val PROTRAIT_MODE : Short = 7
+ const val LANDSCAPE_MODE : Short = 8
+ }
+
+ /**
+ * Constants for [.TAG_METERING_MODE]
+ */
+ object MeteringMode {
+
+ const val UNKNOWN : Short = 0
+ const val AVERAGE : Short = 1
+ const val CENTER_WEIGHTED_AVERAGE : Short = 2
+ const val SPOT : Short = 3
+ const val MULTISPOT : Short = 4
+ const val PATTERN : Short = 5
+ const val PARTAIL : Short = 6
+ const val OTHER : Short = 255
+ }
+
+ /**
+ * Constants for [.TAG_FLASH] As the definition in Jeita EXIF 2.2
+ */
+ object Flash {
+
+ /**
+ * first bit
+ */
+ enum class FlashFired {
+
+ NO, YES
+ }
+
+ /**
+ * Values for bits 1 and 2 indicating the status of returned light
+ */
+ enum class StrobeLightDetection {
+
+ NO_DETECTION, RESERVED, LIGHT_NOT_DETECTED, LIGHT_DETECTED
+ }
+
+ /**
+ * Values for bits 3 and 4 indicating the camera's flash mode
+ */
+ enum class CompulsoryMode {
+
+ UNKNOWN,
+ FIRING,
+ SUPPRESSION,
+ AUTO
+ }
+
+ /**
+ * Values for bit 5 indicating the presence of a flash function.
+ */
+ enum class FlashFunction {
+
+ FUNCTION_PRESENT,
+ FUNCTION_NOR_PRESENT
+ }
+
+ /**
+ * Values for bit 6 indicating the camera's red-eye mode.
+ */
+ enum class RedEyeMode {
+
+ NONE,
+ SUPPORTED
+ }
+ }
+
+ /**
+ * Constants for [.TAG_COLOR_SPACE]
+ */
+ object ColorSpace {
+
+ const val SRGB : Short = 1
+ const val UNCALIBRATED = 0xFFFF.toShort()
+ }
+
+ /**
+ * Constants for [.TAG_EXPOSURE_MODE]
+ */
+ object ExposureMode {
+
+ const val AUTO_EXPOSURE : Short = 0
+ const val MANUAL_EXPOSURE : Short = 1
+ const val AUTO_BRACKET : Short = 2
+ }
+
+ /**
+ * Constants for [.TAG_WHITE_BALANCE]
+ */
+ object WhiteBalance {
+
+ const val AUTO : Short = 0
+ const val MANUAL : Short = 1
+ }
+
+ /**
+ * Constants for [.TAG_SCENE_CAPTURE_TYPE]
+ */
+ object SceneCapture {
+
+ const val STANDARD : Short = 0
+ const val LANDSCAPE : Short = 1
+ const val PROTRAIT : Short = 2
+ const val NIGHT_SCENE : Short = 3
+ }
+
+ /**
+ * Constants for [.TAG_COMPONENTS_CONFIGURATION]
+ */
+ object ComponentsConfiguration {
+
+ const val NOT_EXIST : Short = 0
+ const val Y : Short = 1
+ const val CB : Short = 2
+ const val CR : Short = 3
+ const val R : Short = 4
+ const val G : Short = 5
+ const val B : Short = 6
+ }
+
+ /**
+ * Constants for [.TAG_LIGHT_SOURCE]
+ */
+ object LightSource {
+
+ const val UNKNOWN : Short = 0
+ const val DAYLIGHT : Short = 1
+ const val FLUORESCENT : Short = 2
+ const val TUNGSTEN : Short = 3
+ const val FLASH : Short = 4
+ const val FINE_WEATHER : Short = 9
+ const val CLOUDY_WEATHER : Short = 10
+ const val SHADE : Short = 11
+ const val DAYLIGHT_FLUORESCENT : Short = 12
+ const val DAY_WHITE_FLUORESCENT : Short = 13
+ const val COOL_WHITE_FLUORESCENT : Short = 14
+ const val WHITE_FLUORESCENT : Short = 15
+ const val STANDARD_LIGHT_A : Short = 17
+ const val STANDARD_LIGHT_B : Short = 18
+ const val STANDARD_LIGHT_C : Short = 19
+ const val D55 : Short = 20
+ const val D65 : Short = 21
+ const val D75 : Short = 22
+ const val D50 : Short = 23
+ const val ISO_STUDIO_TUNGSTEN : Short = 24
+ const val OTHER : Short = 255
+ }
+
+ /**
+ * Constants for [.TAG_SENSING_METHOD]
+ */
+ object SensingMethod {
+
+ const val NOT_DEFINED : Short = 1
+ const val ONE_CHIP_COLOR : Short = 2
+ const val TWO_CHIP_COLOR : Short = 3
+ const val THREE_CHIP_COLOR : Short = 4
+ const val COLOR_SEQUENTIAL_AREA : Short = 5
+ const val TRILINEAR : Short = 7
+ const val COLOR_SEQUENTIAL_LINEAR : Short = 8
+ }
+
+ /**
+ * Constants for [.TAG_FILE_SOURCE]
+ */
+ object FileSource {
+
+ const val DSC : Short = 3
+ }
+
+ /**
+ * Constants for [.TAG_SCENE_TYPE]
+ */
+ object SceneType {
+
+ const val DIRECT_PHOTOGRAPHED : Short = 1
+ }
+
+ /**
+ * Constants for [.TAG_GAIN_CONTROL]
+ */
+ object GainControl {
+
+ const val NONE : Short = 0
+ const val LOW_UP : Short = 1
+ const val HIGH_UP : Short = 2
+ const val LOW_DOWN : Short = 3
+ const val HIGH_DOWN : Short = 4
+ }
+
+ /**
+ * Constants for [.TAG_CONTRAST]
+ */
+ object Contrast {
+
+ const val NORMAL : Short = 0
+ const val SOFT : Short = 1
+ const val HARD : Short = 2
+ }
+
+ /**
+ * Constants for [.TAG_SATURATION]
+ */
+ object Saturation {
+
+ const val NORMAL : Short = 0
+ const val LOW : Short = 1
+ const val HIGH : Short = 2
+ }
+
+ /**
+ * Constants for [.TAG_SHARPNESS]
+ */
+ object Sharpness {
+
+ const val NORMAL : Short = 0
+ const val SOFT : Short = 1
+ const val HARD : Short = 2
+ }
+
+ /**
+ * Constants for [.TAG_SUBJECT_DISTANCE]
+ */
+ object SubjectDistance {
+
+ const val UNKNOWN : Short = 0
+ const val MACRO : Short = 1
+ const val CLOSE_VIEW : Short = 2
+ const val DISTANT_VIEW : Short = 3
+ }
+
+ /**
+ * Constants for [.TAG_GPS_LATITUDE_REF],
+ * [.TAG_GPS_DEST_LATITUDE_REF]
+ */
+ object GpsLatitudeRef {
+
+ const val NORTH = "N"
+ const val SOUTH = "S"
+ }
+
+ /**
+ * Constants for [.TAG_GPS_LONGITUDE_REF],
+ * [.TAG_GPS_DEST_LONGITUDE_REF]
+ */
+ object GpsLongitudeRef {
+
+ const val EAST = "E"
+ const val WEST = "W"
+ }
+
+ /**
+ * Constants for [.TAG_GPS_ALTITUDE_REF]
+ */
+ object GpsAltitudeRef {
+
+ const val SEA_LEVEL : Short = 0
+ const val SEA_LEVEL_NEGATIVE : Short = 1
+ }
+
+ /**
+ * Constants for [.TAG_GPS_STATUS]
+ */
+ object GpsStatus {
+
+ const val IN_PROGRESS = "A"
+ const val INTEROPERABILITY = "V"
+ }
+
+ /**
+ * Constants for [.TAG_GPS_MEASURE_MODE]
+ */
+ object GpsMeasureMode {
+
+ const val MODE_2_DIMENSIONAL = "2"
+ const val MODE_3_DIMENSIONAL = "3"
+ }
+
+ /**
+ * Constants for [.TAG_GPS_SPEED_REF],
+ * [.TAG_GPS_DEST_DISTANCE_REF]
+ */
+ object GpsSpeedRef {
+
+ const val KILOMETERS = "K"
+ const val MILES = "M"
+ const val KNOTS = "N"
+ }
+
+ /**
+ * Constants for [.TAG_GPS_TRACK_REF],
+ * [.TAG_GPS_IMG_DIRECTION_REF], [.TAG_GPS_DEST_BEARING_REF]
+ */
+ object GpsTrackRef {
+
+ const val TRUE_DIRECTION = "T"
+ const val MAGNETIC_DIRECTION = "M"
+ }
+
+ /**
+ * Constants for [.TAG_GPS_DIFFERENTIAL]
+ */
+ object GpsDifferential {
+
+ const val WITHOUT_DIFFERENTIAL_CORRECTION : Short = 0
+ const val DIFFERENTIAL_CORRECTION_APPLIED : Short = 1
+ }
+
+ /**
+ * Constants for the jpeg process algorithm used.
+ *
+ * @see .getJpegProcess
+ */
+ object JpegProcess {
+
+ const val BASELINE = 0xFFC0.toShort()
+ const val EXTENDED_SEQUENTIAL = 0xFFC1.toShort()
+ const val PROGRESSIVE = 0xFFC2.toShort()
+ const val LOSSLESS = 0xFFC3.toShort()
+ const val DIFFERENTIAL_SEQUENTIAL = 0xFFC5.toShort()
+ const val DIFFERENTIAL_PROGRESSIVE = 0xFFC6.toShort()
+ const val DIFFERENTIAL_LOSSLESS = 0xFFC7.toShort()
+ const val EXTENDED_SEQ_ARITHMETIC_CODING = 0xFFC9.toShort()
+ const val PROGRESSIVE_AIRTHMETIC_CODING = 0xFFCA.toShort()
+ const val LOSSLESS_AITHMETIC_CODING = 0xFFCB.toShort()
+ const val DIFFERENTIAL_SEQ_ARITHMETIC_CODING = 0xFFCD.toShort()
+ const val DIFFERENTIAL_PROGRESSIVE_ARITHMETIC_CODING = 0xFFCE.toShort()
+ const val DIFFERENTIAL_LOSSLESS_ARITHMETIC_CODING = 0xFFCF.toShort()
+ }
+
+ /**
+ * Constants for the [.TAG_SENSITIVITY_TYPE] tag
+ */
+ object SensitivityType {
+
+
+ const val UNKNOWN : Short = 0
+
+ /**
+ * Standard output sensitivity
+ */
+ const val SOS : Short = 1
+
+ /**
+ * Recommended exposure index
+ */
+ const val REI : Short = 2
+
+ /**
+ * ISO Speed
+ */
+ const val ISO : Short = 3
+
+ /**
+ * Standard output sensitivity and Recommended output index
+ */
+ const val SOS_REI : Short = 4
+
+ /**
+ * Standard output sensitivity and ISO speed
+ */
+ const val SOS_ISO : Short = 5
+
+ /**
+ * Recommended output index and ISO Speed
+ */
+ const val REI_ISO : Short = 6
+
+ /**
+ * Standard output sensitivity and Recommended output index and ISO Speed
+ */
+ const val SOS_REI_ISO : Short = 7
+ }
+
+ /**
+ * Options for calling [.readExif], [.readExif],
+ * [.readExif]
+ */
+ object Options {
+
+ /**
+ * Option bit to request to parse IFD0.
+ */
+ const val OPTION_IFD_0 = 1
+ /**
+ * Option bit to request to parse IFD1.
+ */
+ const val OPTION_IFD_1 = 1 shl 1
+ /**
+ * Option bit to request to parse Exif-IFD.
+ */
+ const val OPTION_IFD_EXIF = 1 shl 2
+ /**
+ * Option bit to request to parse GPS-IFD.
+ */
+ const val OPTION_IFD_GPS = 1 shl 3
+ /**
+ * Option bit to request to parse Interoperability-IFD.
+ */
+ const val OPTION_IFD_INTEROPERABILITY = 1 shl 4
+ /**
+ * Option bit to request to parse thumbnail.
+ */
+ const val OPTION_THUMBNAIL = 1 shl 5
+ /**
+ * Option bit to request all the options
+ */
+ const val OPTION_ALL =
+ OPTION_IFD_0 xor OPTION_IFD_1 xor OPTION_IFD_EXIF xor OPTION_IFD_GPS xor OPTION_IFD_INTEROPERABILITY xor OPTION_THUMBNAIL
+
+ }
+
+ @Suppress("unused")
+ companion object {
+
+ private const val TAG = "ExifInterface"
+
+ const val TAG_NULL = - 1
+ const val IFD_NULL = - 1
+ const val DEFINITION_NULL = 0
+
+ /**
+ * Tag constants for Jeita EXIF 2.2
+ */
+
+ // IFD 0
+ val TAG_IMAGE_WIDTH = defineTag(IfdId.TYPE_IFD_0, 0x0100.toShort())
+ val TAG_IMAGE_LENGTH = defineTag(IfdId.TYPE_IFD_0, 0x0101.toShort()) // Image height
+ val TAG_BITS_PER_SAMPLE = defineTag(IfdId.TYPE_IFD_0, 0x0102.toShort())
+
+ /**
+ * 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
+ */
+ val TAG_COMPRESSION = defineTag(IfdId.TYPE_IFD_0, 0x0103.toShort())
+ val TAG_PHOTOMETRIC_INTERPRETATION = defineTag(IfdId.TYPE_IFD_0, 0x0106.toShort())
+ val TAG_IMAGE_DESCRIPTION = defineTag(IfdId.TYPE_IFD_0, 0x010E.toShort())
+
+ /**
+ * 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.
+ */
+ val TAG_MAKE = defineTag(IfdId.TYPE_IFD_0, 0x010F.toShort())
+
+ /**
+ * 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.
+ */
+ val TAG_MODEL = defineTag(IfdId.TYPE_IFD_0, 0x0110.toShort())
+ val TAG_STRIP_OFFSETS = defineTag(IfdId.TYPE_IFD_0, 0x0111.toShort())
+
+ /**
+ * 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
+ *
+ */
+ val TAG_ORIENTATION = defineTag(IfdId.TYPE_IFD_0, 0x0112.toShort())
+ val TAG_SAMPLES_PER_PIXEL = defineTag(IfdId.TYPE_IFD_0, 0x0115.toShort())
+ val TAG_ROWS_PER_STRIP = defineTag(IfdId.TYPE_IFD_0, 0x0116.toShort())
+ val TAG_STRIP_BYTE_COUNTS = defineTag(IfdId.TYPE_IFD_0, 0x0117.toShort())
+
+ val TAG_INTEROP_VERSION = defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, 0x0002.toShort())
+
+ /**
+ * 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.
+ */
+ val TAG_X_RESOLUTION = defineTag(IfdId.TYPE_IFD_0, 0x011A.toShort())
+
+ /**
+ * @see .TAG_X_RESOLUTION
+ */
+ val TAG_Y_RESOLUTION = defineTag(IfdId.TYPE_IFD_0, 0x011B.toShort())
+ val TAG_PLANAR_CONFIGURATION = defineTag(IfdId.TYPE_IFD_0, 0x011C.toShort())
+
+ /**
+ * Value is unsigned int.
+ * Unit of XResolution(0x011a)/YResolution(0x011b)
+ *
+ * * '1' means no-unit ( use inch )
+ * * '2' inch
+ * * '3' centimeter
+ * * '4' millimeter
+ * * '5' micrometer
+ *
+ */
+ val TAG_RESOLUTION_UNIT = defineTag(IfdId.TYPE_IFD_0, 0x0128.toShort())
+ val TAG_TRANSFER_FUNCTION = defineTag(IfdId.TYPE_IFD_0, 0x012D.toShort())
+
+ /**
+ * Value is ascii string
+ * Shows firmware(internal software of digicam) version number.
+ */
+ val TAG_SOFTWARE = defineTag(IfdId.TYPE_IFD_0, 0x0131.toShort())
+
+ /**
+ * 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)
+ */
+ val TAG_DATE_TIME = defineTag(IfdId.TYPE_IFD_0, 0x0132.toShort())
+
+ /**
+ * 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.
+ */
+ val TAG_ARTIST = defineTag(IfdId.TYPE_IFD_0, 0x013B.toShort())
+ val TAG_WHITE_POINT = defineTag(IfdId.TYPE_IFD_0, 0x013E.toShort())
+ val TAG_PRIMARY_CHROMATICITIES = defineTag(IfdId.TYPE_IFD_0, 0x013F.toShort())
+ val TAG_Y_CB_CR_COEFFICIENTS = defineTag(IfdId.TYPE_IFD_0, 0x0211.toShort())
+ val TAG_Y_CB_CR_SUB_SAMPLING = defineTag(IfdId.TYPE_IFD_0, 0x0212.toShort())
+ val TAG_Y_CB_CR_POSITIONING = defineTag(IfdId.TYPE_IFD_0, 0x0213.toShort())
+ val TAG_REFERENCE_BLACK_WHITE = defineTag(IfdId.TYPE_IFD_0, 0x0214.toShort())
+
+ /**
+ * Values is ascii string
+ * Shows copyright information
+ */
+ val TAG_COPYRIGHT = defineTag(IfdId.TYPE_IFD_0, 0x8298.toShort())
+ val TAG_EXIF_IFD = defineTag(IfdId.TYPE_IFD_0, 0x8769.toShort())
+ val TAG_GPS_IFD = defineTag(IfdId.TYPE_IFD_0, 0x8825.toShort())
+ // IFD 1
+ val TAG_JPEG_INTERCHANGE_FORMAT = defineTag(IfdId.TYPE_IFD_1, 0x0201.toShort())
+ val TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = defineTag(IfdId.TYPE_IFD_1, 0x0202.toShort())
+ // IFD Exif Tags
+
+ /**
+ * Value is unsigned double
+ * Exposure time (reciprocal of shutter speed). Unit is second
+ */
+ val TAG_EXPOSURE_TIME = defineTag(IfdId.TYPE_IFD_EXIF, 0x829A.toShort())
+
+ /**
+ * Value is unsigned double
+ * The actual F-number(F-stop) of lens when the image was taken
+ *
+ * @see .TAG_APERTURE_VALUE
+ */
+ val TAG_F_NUMBER = defineTag(IfdId.TYPE_IFD_EXIF, 0x829D.toShort())
+
+ /**
+ * 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.
+ *
+ */
+ val TAG_EXPOSURE_PROGRAM = defineTag(IfdId.TYPE_IFD_EXIF, 0x8822.toShort())
+ val TAG_SPECTRAL_SENSITIVITY = defineTag(IfdId.TYPE_IFD_EXIF, 0x8824.toShort())
+
+ /**
+ * 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
+ */
+ val TAG_ISO_SPEED_RATINGS = defineTag(IfdId.TYPE_IFD_EXIF, 0x8827.toShort())
+ val TAG_OECF = defineTag(IfdId.TYPE_IFD_EXIF, 0x8828.toShort())
+
+ /**
+ * 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
+ */
+ val TAG_EXIF_VERSION = defineTag(IfdId.TYPE_IFD_EXIF, 0x9000.toShort())
+
+ /**
+ * Value is ascii string (20)
+ * Date/Time of original image taken. This value should not be modified by user program.
+ */
+ val TAG_DATE_TIME_ORIGINAL = defineTag(IfdId.TYPE_IFD_EXIF, 0x9003.toShort())
+
+ /**
+ * Value is ascii string (20)
+ * Date/Time of image digitized. Usually, it contains the same value of DateTimeOriginal(0x9003).
+ */
+ val TAG_DATE_TIME_DIGITIZED = defineTag(IfdId.TYPE_IFD_EXIF, 0x9004.toShort())
+ val TAG_COMPONENTS_CONFIGURATION = defineTag(IfdId.TYPE_IFD_EXIF, 0x9101.toShort())
+ val TAG_COMPRESSED_BITS_PER_PIXEL = defineTag(IfdId.TYPE_IFD_EXIF, 0x9102.toShort())
+
+ /**
+ * 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.
+ */
+ val TAG_SHUTTER_SPEED_VALUE = defineTag(IfdId.TYPE_IFD_EXIF, 0x9201.toShort())
+
+ /**
+ * 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
+ */
+ val TAG_APERTURE_VALUE = defineTag(IfdId.TYPE_IFD_EXIF, 0x9202.toShort())
+
+ /**
+ * Value is signed double
+ * Brightness of taken subject, unit is EV.
+ */
+ val TAG_BRIGHTNESS_VALUE = defineTag(IfdId.TYPE_IFD_EXIF, 0x9203.toShort())
+
+ /**
+ * 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
+ */
+ val TAG_EXPOSURE_BIAS_VALUE = defineTag(IfdId.TYPE_IFD_EXIF, 0x9204.toShort())
+
+ /**
+ * 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 )
+
*
+ */
+ val TAG_MAX_APERTURE_VALUE = defineTag(IfdId.TYPE_IFD_EXIF, 0x9205.toShort())
+
+ /**
+ * Value if signed double.
+ * Distance to focus point, unit is meter. If value < 0 then focus point is infinite
+ */
+ val TAG_SUBJECT_DISTANCE = defineTag(IfdId.TYPE_IFD_EXIF, 0x9206.toShort())
+
+ /**
+ * 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
+ *
+ */
+ val TAG_METERING_MODE = defineTag(IfdId.TYPE_IFD_EXIF, 0x9207.toShort())
+
+ /**
+ * 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
+ *
+ */
+ val TAG_LIGHT_SOURCE = defineTag(IfdId.TYPE_IFD_EXIF, 0x9208.toShort())
+
+ /**
+ * Value is unsigned integer
+ * The 8 bits can be extracted and evaluated in this way:
+ *
+ * 1. Bit 0 indicates the flash firing status
+ * 1. bits 1 and 2 indicate the flash return status
+ * 1. bits 3 and 4 indicate the flash mode
+ * 1. bit 5 indicates whether the flash function is present
+ * 1. and bit 6 indicates "red eye" mode
+ * 1. 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](http://www.exif.org/Exif2-2.PDF)
+ */
+ val TAG_FLASH = defineTag(IfdId.TYPE_IFD_EXIF, 0x9209.toShort())
+
+ /**
+ * Value is unsigned double
+ * Focal length of lens used to take image. Unit is millimeter.
+ */
+ val TAG_FOCAL_LENGTH = defineTag(IfdId.TYPE_IFD_EXIF, 0x920A.toShort())
+ val TAG_SUBJECT_AREA = defineTag(IfdId.TYPE_IFD_EXIF, 0x9214.toShort())
+ val TAG_MAKER_NOTE = defineTag(IfdId.TYPE_IFD_EXIF, 0x927C.toShort())
+ val TAG_USER_COMMENT = defineTag(IfdId.TYPE_IFD_EXIF, 0x9286.toShort())
+ val TAG_SUB_SEC_TIME = defineTag(IfdId.TYPE_IFD_EXIF, 0x9290.toShort())
+ val TAG_SUB_SEC_TIME_ORIGINAL = defineTag(IfdId.TYPE_IFD_EXIF, 0x9291.toShort())
+ val TAG_SUB_SEC_TIME_DIGITIZED = defineTag(IfdId.TYPE_IFD_EXIF, 0x9292.toShort())
+ val TAG_FLASHPIX_VERSION = defineTag(IfdId.TYPE_IFD_EXIF, 0xA000.toShort())
+
+ /**
+ * 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
+ *
+ */
+ val TAG_COLOR_SPACE = defineTag(IfdId.TYPE_IFD_EXIF, 0xA001.toShort())
+
+ /**
+ * 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.
+ */
+ val TAG_PIXEL_X_DIMENSION = defineTag(IfdId.TYPE_IFD_EXIF, 0xA002.toShort())
+
+ /**
+ * @see .TAG_PIXEL_X_DIMENSION
+ */
+ val TAG_PIXEL_Y_DIMENSION = defineTag(IfdId.TYPE_IFD_EXIF, 0xA003.toShort())
+ val TAG_RELATED_SOUND_FILE = defineTag(IfdId.TYPE_IFD_EXIF, 0xA004.toShort())
+ val TAG_INTEROPERABILITY_IFD = defineTag(IfdId.TYPE_IFD_EXIF, 0xA005.toShort())
+ val TAG_FLASH_ENERGY = defineTag(IfdId.TYPE_IFD_EXIF, 0xA20B.toShort())
+ val TAG_SPATIAL_FREQUENCY_RESPONSE = defineTag(IfdId.TYPE_IFD_EXIF, 0xA20C.toShort())
+
+ /**
+ * 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
+ */
+ val TAG_FOCAL_PLANE_X_RESOLUTION = defineTag(IfdId.TYPE_IFD_EXIF, 0xA20E.toShort())
+
+ /**
+ * @see .TAG_FOCAL_PLANE_X_RESOLUTION
+ */
+ val TAG_FOCAL_PLANE_Y_RESOLUTION = defineTag(IfdId.TYPE_IFD_EXIF, 0xA20F.toShort())
+
+ /**
+ * 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 )
+
*
+ */
+ val TAG_FOCAL_PLANE_RESOLUTION_UNIT = defineTag(IfdId.TYPE_IFD_EXIF, 0xA210.toShort())
+ val TAG_SUBJECT_LOCATION = defineTag(IfdId.TYPE_IFD_EXIF, 0xA214.toShort())
+ val TAG_EXPOSURE_INDEX = defineTag(IfdId.TYPE_IFD_EXIF, 0xA215.toShort())
+
+ /**
+ * 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
+ *
+ */
+ val TAG_SENSING_METHOD = defineTag(IfdId.TYPE_IFD_EXIF, 0xA217.toShort())
+ val TAG_FILE_SOURCE = defineTag(IfdId.TYPE_IFD_EXIF, 0xA300.toShort())
+ val TAG_SCENE_TYPE = defineTag(IfdId.TYPE_IFD_EXIF, 0xA301.toShort())
+ val TAG_CFA_PATTERN = defineTag(IfdId.TYPE_IFD_EXIF, 0xA302.toShort())
+ val TAG_CUSTOM_RENDERED = defineTag(IfdId.TYPE_IFD_EXIF, 0xA401.toShort())
+
+ /**
+ * 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
+ *
+ */
+ val TAG_EXPOSURE_MODE = defineTag(IfdId.TYPE_IFD_EXIF, 0xA402.toShort())
+ val TAG_WHITE_BALANCE = defineTag(IfdId.TYPE_IFD_EXIF, 0xA403.toShort())
+
+ /**
+ * 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
+ */
+ val TAG_DIGITAL_ZOOM_RATIO = defineTag(IfdId.TYPE_IFD_EXIF, 0xA404.toShort())
+
+ /**
+ * 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 );
+
*
+ */
+ val TAG_FOCAL_LENGTH_IN_35_MM_FILE = defineTag(IfdId.TYPE_IFD_EXIF, 0xA405.toShort())
+
+ /**
+ * 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
+ *
+ */
+ val TAG_SCENE_CAPTURE_TYPE = defineTag(IfdId.TYPE_IFD_EXIF, 0xA406.toShort())
+
+ /**
+ * 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
+ *
+ */
+ val TAG_GAIN_CONTROL = defineTag(IfdId.TYPE_IFD_EXIF, 0xA407.toShort())
+
+ /**
+ * 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
+ *
+ */
+ val TAG_CONTRAST = defineTag(IfdId.TYPE_IFD_EXIF, 0xA408.toShort())
+
+ /**
+ * 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
+ *
+ */
+ val TAG_SATURATION = defineTag(IfdId.TYPE_IFD_EXIF, 0xA409.toShort())
+
+ /**
+ * 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
+ *
+ */
+ val TAG_SHARPNESS = defineTag(IfdId.TYPE_IFD_EXIF, 0xA40A.toShort())
+ val TAG_DEVICE_SETTING_DESCRIPTION = defineTag(IfdId.TYPE_IFD_EXIF, 0xA40B.toShort())
+
+ /**
+ * Value is int.
+ * This tag indicates the distance to the subject.
+ *
+ * * 0 = unknown
+ * * 1 = Macro
+ * * 2 = Close view
+ * * 3 = Distant view
+ * * Other = reserved
+ *
+ */
+ val TAG_SUBJECT_DISTANCE_RANGE = defineTag(IfdId.TYPE_IFD_EXIF, 0xA40C.toShort())
+
+ /**
+ * [ExifTag.TYPE_ASCII]
+ */
+ val TAG_IMAGE_UNIQUE_ID = defineTag(IfdId.TYPE_IFD_EXIF, 0xA420.toShort())
+
+ /**
+ * Lens Specifications. The value it's a 4 rational containing:
+ *
+ * 1. Minimum focal length (in mm)
+ * 1. Maximum focal length (in mm)
+ * 1. Minimum F Number in the minimum focal length
+ * 1. Maximum F Number in the maximum focal length
+ *
+ *
+ *
+ * [ExifTag.TYPE_RATIONAL]
+ *
+ * @see it.sephiroth.android.library.exif2.ExifUtil.processLensSpecifications
+ * @since EXIF 2.3
+ */
+ val TAG_LENS_SPECS = defineTag(IfdId.TYPE_IFD_EXIF, 0xA432.toShort())
+
+ /**
+ * Lens maker
+ * [ExifTag.TYPE_ASCII]
+ *
+ * @since EXIF 2.3
+ */
+ val TAG_LENS_MAKE = defineTag(IfdId.TYPE_IFD_EXIF, 0xA433.toShort())
+ /**
+ * Lens model name and number
+ * [ExifTag.TYPE_ASCII]
+ *
+ * @since EXIF 2.3
+ */
+ val TAG_LENS_MODEL = defineTag(IfdId.TYPE_IFD_EXIF, 0xA434.toShort())
+
+ /**
+ * 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
+ *
+ *
+ *
+ * [ExifTag.TYPE_UNSIGNED_SHORT]
+ *
+ * @see it.sephiroth.android.library.exif2.ExifInterface.SensitivityType
+ *
+ * @since EXIF 2.3
+ */
+ val TAG_SENSITIVITY_TYPE = defineTag(IfdId.TYPE_IFD_EXIF, 0x8830.toShort())
+
+ // IFD GPS tags
+ val TAG_GPS_VERSION_ID = defineTag(IfdId.TYPE_IFD_GPS, 0.toShort())
+
+ /**
+ * 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.
+ */
+ val TAG_GPS_LATITUDE_REF = defineTag(IfdId.TYPE_IFD_GPS, 1.toShort())
+
+ /**
+ * 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.
+ */
+ val TAG_GPS_LATITUDE = defineTag(IfdId.TYPE_IFD_GPS, 2.toShort())
+
+ /**
+ * Value is string(1)
+ * Indicates whether the longitude is east or west longitude. ASCII 'E' indicates east longitude, and 'W' is west longitude.
+ */
+ val TAG_GPS_LONGITUDE_REF = defineTag(IfdId.TYPE_IFD_GPS, 3.toShort())
+
+ /**
+ * 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.
+ */
+ val TAG_GPS_LONGITUDE = defineTag(IfdId.TYPE_IFD_GPS, 4.toShort())
+
+ /**
+ * 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
+ */
+ val TAG_GPS_ALTITUDE_REF = defineTag(IfdId.TYPE_IFD_GPS, 5.toShort())
+
+ /**
+ * Value is string.
+ * Indicates the altitude based on the reference in GPSAltitudeRef. Altitude is expressed as one RATIONAL value. The reference unit is meters.
+ */
+ val TAG_GPS_ALTITUDE = defineTag(IfdId.TYPE_IFD_GPS, 6.toShort())
+ val TAG_GPS_TIME_STAMP = defineTag(IfdId.TYPE_IFD_GPS, 7.toShort())
+ val TAG_GPS_SATTELLITES = defineTag(IfdId.TYPE_IFD_GPS, 8.toShort())
+ val TAG_GPS_STATUS = defineTag(IfdId.TYPE_IFD_GPS, 9.toShort())
+ val TAG_GPS_MEASURE_MODE = defineTag(IfdId.TYPE_IFD_GPS, 10.toShort())
+ val TAG_GPS_DOP = defineTag(IfdId.TYPE_IFD_GPS, 11.toShort())
+
+ /**
+ * 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.
+ */
+ val TAG_GPS_SPEED_REF = defineTag(IfdId.TYPE_IFD_GPS, 12.toShort())
+
+ /**
+ * Value is string.
+ * Indicates the speed of GPS receiver movement
+ */
+ val TAG_GPS_SPEED = defineTag(IfdId.TYPE_IFD_GPS, 13.toShort())
+ val TAG_GPS_TRACK_REF = defineTag(IfdId.TYPE_IFD_GPS, 14.toShort())
+ val TAG_GPS_TRACK = defineTag(IfdId.TYPE_IFD_GPS, 15.toShort())
+ val TAG_GPS_IMG_DIRECTION_REF = defineTag(IfdId.TYPE_IFD_GPS, 16.toShort())
+ val TAG_GPS_IMG_DIRECTION = defineTag(IfdId.TYPE_IFD_GPS, 17.toShort())
+ val TAG_GPS_MAP_DATUM = defineTag(IfdId.TYPE_IFD_GPS, 18.toShort())
+ val TAG_GPS_DEST_LATITUDE_REF = defineTag(IfdId.TYPE_IFD_GPS, 19.toShort())
+ val TAG_GPS_DEST_LATITUDE = defineTag(IfdId.TYPE_IFD_GPS, 20.toShort())
+ val TAG_GPS_DEST_LONGITUDE_REF = defineTag(IfdId.TYPE_IFD_GPS, 21.toShort())
+ val TAG_GPS_DEST_LONGITUDE = defineTag(IfdId.TYPE_IFD_GPS, 22.toShort())
+ val TAG_GPS_DEST_BEARING_REF = defineTag(IfdId.TYPE_IFD_GPS, 23.toShort())
+ val TAG_GPS_DEST_BEARING = defineTag(IfdId.TYPE_IFD_GPS, 24.toShort())
+ val TAG_GPS_DEST_DISTANCE_REF = defineTag(IfdId.TYPE_IFD_GPS, 25.toShort())
+ val TAG_GPS_DEST_DISTANCE = defineTag(IfdId.TYPE_IFD_GPS, 26.toShort())
+ val TAG_GPS_PROCESSING_METHOD = defineTag(IfdId.TYPE_IFD_GPS, 27.toShort())
+ val TAG_GPS_AREA_INFORMATION = defineTag(IfdId.TYPE_IFD_GPS, 28.toShort())
+ val TAG_GPS_DATE_STAMP = defineTag(IfdId.TYPE_IFD_GPS, 29.toShort())
+ val TAG_GPS_DIFFERENTIAL = defineTag(IfdId.TYPE_IFD_GPS, 30.toShort())
+ // IFD Interoperability tags
+ val TAG_INTEROPERABILITY_INDEX = defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, 1.toShort())
+
+ val DEFAULT_BYTE_ORDER :ByteOrder = ByteOrder.BIG_ENDIAN
+ private const val NULL_ARGUMENT_STRING = "Argument is null"
+
+ private const val GPS_DATE_FORMAT_STR = "yyyy:MM:dd"
+ private val mGPSDateStampFormat = SimpleDateFormat(GPS_DATE_FORMAT_STR, Locale.ENGLISH)
+ private const val DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss"
+ private val mDateTimeStampFormat = SimpleDateFormat(DATETIME_FORMAT_STR, Locale.ENGLISH)
+
+ /**
+ * Tags that contain offset markers. These are included in the banned
+ * defines.
+ */
+ private val sOffsetTags = HashSet()
+
+ init {
+ 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).
+ */
+ var sBannedDefines = HashSet(sOffsetTags)
+
+ init {
+ sBannedDefines.add(getTrueTagKey(TAG_NULL))
+ sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH))
+ sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS))
+ }
+
+ /**
+ * Returns true if tag TID is one of the following: [.TAG_EXIF_IFD],
+ * [.TAG_GPS_IFD], [.TAG_JPEG_INTERCHANGE_FORMAT],
+ * [.TAG_STRIP_OFFSETS], [.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
+ * [.getTrueTagKey]).
+ * @return true if the TID is that of an offset tag.
+ */
+ fun isOffsetTag(tag : Short) : Boolean {
+ return sOffsetTags.contains(tag)
+ }
+
+ /**
+ * Returns the Orientation ExifTag value for a given number of degrees.
+ *
+ * @param degreesArg the amount an image is rotated in degrees.
+ */
+ fun getOrientationValueForRotation(degreesArg : Int) : Short {
+ var degrees = degreesArg
+ degrees %= 360
+ if(degrees < 0) {
+ degrees += 360
+ }
+ return when {
+ degrees < 90 -> Orientation.TOP_LEFT // 0 degrees
+ degrees < 180 -> Orientation.RIGHT_TOP // 90 degrees cw
+ degrees < 270 -> Orientation.BOTTOM_LEFT // 180 degrees
+ else -> Orientation.RIGHT_BOTTOM // 270 degrees cw
+ }
+ }
+
+ /**
+ * Returns the rotation degrees corresponding to an ExifTag Orientation
+ * value.
+ *
+ * @param orientation the ExifTag Orientation value.
+ */
+ fun getRotationForOrientationValue(orientation : Short) : Int =
+ when(orientation) {
+ Orientation.TOP_LEFT -> 0
+ Orientation.RIGHT_TOP -> 90
+ Orientation.BOTTOM_LEFT -> 180
+ Orientation.RIGHT_BOTTOM -> 270
+ else -> 0
+ }
+
+ /**
+ * 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
+ */
+ fun convertLatOrLongToDouble(coordinate : Array, reference : String) : Double {
+ try {
+ val degrees = coordinate[0].toDouble()
+ val minutes = coordinate[1].toDouble()
+ val seconds = coordinate[2].toDouble()
+ val result = degrees + minutes / 60.0 + seconds / 3600.0
+ return if(reference.startsWith("S") || reference.startsWith("W")) {
+ - result
+ } else result
+ } catch(e : ArrayIndexOutOfBoundsException) {
+ throw IllegalArgumentException()
+ }
+
+ }
+
+ fun getAllowedIfdsFromInfo(info : Int) : IntArray? {
+ val ifdFlags = getAllowedIfdFlagsFromInfo(info)
+ val ifds = IfdData.ifds
+ val l = ArrayList()
+ for(i in 0 until IfdId.TYPE_IFD_COUNT) {
+ val flag = ifdFlags shr i and 1
+ if(flag == 1) {
+ l.add(ifds[i])
+ }
+ }
+ if(l.size <= 0) {
+ return null
+ }
+ val ret = IntArray(l.size)
+ var j = 0
+ for(i in l) {
+ ret[j ++] = i
+ }
+ return ret
+ }
+
+ fun closeSilently(c : Closeable?) {
+ if(c != null) {
+ try {
+ c.close()
+ } catch(e : Throwable) {
+ // ignored
+ }
+
+ }
+ }
+
+ fun closeQuietly(input : InputStream) {
+ @Suppress("DEPRECATION")
+ IOUtils.closeQuietly(input)
+ }
+
+ fun closeQuietly(output : OutputStream) {
+ @Suppress("DEPRECATION")
+ IOUtils.closeQuietly(output)
+ }
+
+ @Throws(IOException::class)
+ private fun writeExif_internal(
+ input : InputStream,
+ output : OutputStream,
+ exifData : ExifData
+ ) : Int {
+ // Log.i( TAG, "writeExif_internal" );
+
+ // 1. read the output file first
+ val src_exif = ExifInterface()
+ src_exif.readExif(input, 0)
+
+ // 4. Create the destination outputstream
+ // 5. write headers
+ output.write(0xFF)
+ output.write(JpegHeader.TAG_SOI)
+
+ val sections = src_exif.mData.sections !!
+
+ // 6. write all the sections from the srcFilename
+ if(sections[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
+ val eo = ExifOutputStream(src_exif)
+ eo.exifData = exifData
+ eo.writeExifData(output)
+
+ // 6.2 write all the sections except for the SOS ( start of scan )
+ for(a in 0 until sections.size - 1) {
+ val current = sections[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
+ val current = sections[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
+ }
+
+ /**
+ * Returns the default IFD for a tag constant.
+ */
+ fun getTrueIfd(tag : Int) : Int {
+ return tag.ushr(16)
+ }
+
+ /**
+ * Returns the TID for a tag constant.
+ */
+ fun getTrueTagKey(tag : Int) : Short {
+ // Truncate
+ return tag.toShort()
+ }
+
+ private fun getFlagsFromAllowedIfds(allowedIfds : IntArray?) : Int {
+ if(allowedIfds == null || allowedIfds.isEmpty()) {
+ return 0
+ }
+ var flags = 0
+ val ifds = IfdData.ifds
+ for(i in 0 until IfdId.TYPE_IFD_COUNT) {
+ for(j in allowedIfds) {
+ if(ifds[i] == j) {
+ flags = flags or (1 shl i)
+ break
+ }
+ }
+ }
+ return flags
+ }
+
+ private fun getComponentCountFromInfo(info : Int) : Int {
+ return info and 0x0ffff
+ }
+
+ private fun getTypeFromInfo(info : Int) : Short {
+ return (info shr 16 and 0x0ff).toShort()
+ }
+
+ /**
+ * Returns the constant representing a tag with a given TID and default IFD.
+ */
+ fun defineTag(ifdId : Int, tagId : Short) : Int {
+ return tagId or (ifdId shl 16)
+ }
+
+ private fun convertRationalLatLonToString(
+ coord : Array,
+ refArg : String
+ ) : String? {
+ return try {
+ var ref = refArg
+
+ val degrees = coord[0].toDouble()
+ val minutes = coord[1].toDouble()
+ val seconds = coord[2].toDouble()
+ ref = ref.substring(0, 1)
+
+ String.format(
+ Locale.ENGLISH,
+ "%1$.0f° %2$.0f' %3$.0f\" %4\$s",
+ degrees,
+ minutes,
+ seconds,
+ ref.toUpperCase(Locale.getDefault())
+ )
+ } catch(ex : Throwable) {
+ ex.printStackTrace()
+ null
+ }
+ }
+
+ /**
+ * Given an exif date time, like [.TAG_DATE_TIME] or [.TAG_DATE_TIME_DIGITIZED]
+ * returns a java Date object
+ *
+ * @param dateTimeString one of the value of [.TAG_DATE_TIME] or [.TAG_DATE_TIME_DIGITIZED]
+ * @param timeZone the target timezone
+ * @return the parsed date
+ */
+ fun getDateTime(dateTimeString : String?, timeZone : TimeZone) : Date? {
+ dateTimeString ?: return null
+
+ return try {
+ val formatter = SimpleDateFormat(DATETIME_FORMAT_STR, Locale.ENGLISH)
+ formatter.timeZone = timeZone
+ formatter.parse(dateTimeString)
+ } catch(e : Throwable) {
+ e.printStackTrace()
+ null
+ }
+ }
+
+ fun isIfdAllowed(info : Int, ifd : Int) : Boolean {
+ val ifds = IfdData.ifds
+ val ifdFlags = getAllowedIfdFlagsFromInfo(info)
+ for(i in ifds.indices) {
+ if(ifd == ifds[i] && ifdFlags shr i and 1 == 1) {
+ return true
+ }
+ }
+ return false
+ }
+
+ private fun getAllowedIfdFlagsFromInfo(info : Int) : Int {
+ return info.ushr(24)
+ }
+
+ private fun toExifLatLong(valueArg : Double) : Array {
+ // convert to the format dd/1 mm/1 ssss/100
+ var value = abs(valueArg)
+ val degrees = value
+ value = (value - degrees) * 60
+ val minutes = value
+ value = (value - minutes) * 6000
+ val seconds = value
+ return arrayOf(
+ Rational(degrees.toLong(), 1),
+ Rational(minutes.toLong(), 1),
+ Rational(seconds.toLong(), 100)
+ )
+ }
+
+ fun toBitArray(value : Short) : ByteArray {
+ val result = ByteArray(16)
+ for(i in 0 .. 15) {
+ result[15 - i] = (value shr i and 1).toByte()
+ }
+ return result
+ }
+
+ infix fun Short.shl(bits : Int) : Int = (this.toInt() and 0xffff) shl bits
+ infix fun Short.shr(bits : Int) : Int = (this.toInt() and 0xffff) shr bits
+ infix fun Short.or(bits : Int) : Int = (this.toInt() and 0xffff) or bits
+ }
+}
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.kt
similarity index 78%
rename from exif/src/main/java/it/sephiroth/android/library/exif2/ExifInvalidFormatException.java
rename to exif/src/main/java/it/sephiroth/android/library/exif2/ExifInvalidFormatException.kt
index 51405a9c..b5c08886 100644
--- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInvalidFormatException.java
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInvalidFormatException.kt
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-package it.sephiroth.android.library.exif2;
+package it.sephiroth.android.library.exif2
-public class ExifInvalidFormatException extends Exception {
- public ExifInvalidFormatException( String meg ) {
- super( meg );
- }
-}
\ No newline at end of file
+class ExifInvalidFormatException(meg : String) : Exception(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
deleted file mode 100644
index 0e40a798..00000000
--- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifOutputStream.java
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package 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/ExifOutputStream.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifOutputStream.kt
new file mode 100644
index 00000000..2f2e0ab5
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifOutputStream.kt
@@ -0,0 +1,375 @@
+/*
+ * 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
+
+@Suppress("unused")
+internal class ExifOutputStream(private val mInterface : ExifInterface) {
+ /**
+ * Gets the Exif header to be written into the JPEF file.
+ */
+ /**
+ * Sets the ExifData to be written into the JPEG file. Should be called
+ * before writing image data.
+ */
+ var exifData : ExifData? = null
+
+ private val mBuffer = ByteBuffer.allocate(4)
+
+ private fun requestByteToBuffer(
+ requestByteCount : Int, buffer : ByteArray, offset : Int, length : Int
+ ) : Int {
+ val byteNeeded = requestByteCount - mBuffer.position()
+ val byteToRead = if(length > byteNeeded) byteNeeded else length
+ mBuffer.put(buffer, offset, byteToRead)
+ return byteToRead
+ }
+
+ @Throws(IOException::class)
+ fun writeExifData(out : OutputStream) {
+ if(exifData == null) {
+ return
+ }
+
+ Log.v(TAG, "Writing exif data...")
+
+ val nullTags = stripNullValueTags(exifData !!)
+ createRequiredIfdAndTag()
+ val exifSize = calculateAllOffset()
+ // Log.i(TAG, "exifSize: " + (exifSize + 8));
+ if(exifSize + 8 > MAX_EXIF_SIZE) {
+ throw IOException("Exif header is too large (>64Kb)")
+ }
+
+ val outputStream = BufferedOutputStream(out, STREAMBUFFER_SIZE)
+ val dataOutputStream = OrderedDataOutputStream(outputStream)
+
+ dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN)
+
+ dataOutputStream.write(0xFF)
+ dataOutputStream.write(JpegHeader.TAG_M_EXIF)
+ dataOutputStream.writeShort((exifSize + 8).toShort())
+ dataOutputStream.writeInt(EXIF_HEADER)
+ dataOutputStream.writeShort(0x0000.toShort())
+ if(exifData !!.byteOrder == ByteOrder.BIG_ENDIAN) {
+ dataOutputStream.writeShort(TIFF_BIG_ENDIAN)
+ } else {
+ dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN)
+ }
+ dataOutputStream.setByteOrder(exifData !!.byteOrder)
+ dataOutputStream.writeShort(TIFF_HEADER)
+ dataOutputStream.writeInt(8)
+ writeAllTags(dataOutputStream)
+
+ writeThumbnail(dataOutputStream)
+
+ for(t in nullTags) {
+ exifData !!.addTag(t)
+ }
+
+ dataOutputStream.flush()
+ }
+
+ private fun stripNullValueTags(data : ExifData) : ArrayList {
+ val nullTags = ArrayList()
+ for(t in data.allTags !!) {
+ if(t.getValue() == null && ! ExifInterface.isOffsetTag(t.tagId)) {
+ data.removeTag(t.tagId, t.ifd)
+ nullTags.add(t)
+ }
+ }
+ return nullTags
+ }
+
+ @Throws(IOException::class)
+ private fun writeThumbnail(dataOutputStream : OrderedDataOutputStream) {
+ if(exifData !!.hasCompressedThumbnail()) {
+ Log.d(TAG, "writing thumbnail..")
+ dataOutputStream.write(exifData !!.compressedThumbnail !!)
+ } else if(exifData !!.hasUncompressedStrip()) {
+ Log.d(TAG, "writing uncompressed strip..")
+ for(i in 0 until exifData !!.stripCount) {
+ dataOutputStream.write(exifData !!.getStrip(i) !!)
+ }
+ }
+ }
+
+ @Throws(IOException::class)
+ private fun writeAllTags(dataOutputStream : OrderedDataOutputStream) {
+ writeIfd(exifData !!.getIfdData(IfdId.TYPE_IFD_0) !!, dataOutputStream)
+ writeIfd(exifData !!.getIfdData(IfdId.TYPE_IFD_EXIF) !!, dataOutputStream)
+ val interoperabilityIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY)
+ if(interoperabilityIfd != null) {
+ writeIfd(interoperabilityIfd, dataOutputStream)
+ }
+ val gpsIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_GPS)
+ if(gpsIfd != null) {
+ writeIfd(gpsIfd, dataOutputStream)
+ }
+ val ifd1 = exifData !!.getIfdData(IfdId.TYPE_IFD_1)
+ if(ifd1 != null) {
+ writeIfd(exifData !!.getIfdData(IfdId.TYPE_IFD_1) !!, dataOutputStream)
+ }
+ }
+
+ @Throws(IOException::class)
+ private fun writeIfd(ifd : IfdData, dataOutputStream : OrderedDataOutputStream) {
+ val tags = ifd.allTags
+ dataOutputStream.writeShort(tags.size.toShort())
+ for(tag in tags) {
+ dataOutputStream.writeShort(tag.tagId)
+ dataOutputStream.writeShort(tag.dataType)
+ dataOutputStream.writeInt(tag.componentCount)
+ // Log.v( TAG, "\n" + tag.toString() );
+ if(tag.dataSize > 4) {
+ dataOutputStream.writeInt(tag.offset)
+ } else {
+ writeTagValue(tag, dataOutputStream)
+ var i = 0
+ val n = 4 - tag.dataSize
+ while(i < n) {
+ dataOutputStream.write(0)
+ i ++
+ }
+ }
+ }
+ dataOutputStream.writeInt(ifd.offsetToNextIfd)
+ for(tag in tags) {
+ if(tag.dataSize > 4) {
+ writeTagValue(tag, dataOutputStream)
+ }
+ }
+ }
+
+ private fun calculateOffsetOfIfd(ifd : IfdData, offsetArg : Int) : Int {
+ var offset = offsetArg
+ offset += 2 + ifd.tagCount * TAG_SIZE + 4
+ val tags = ifd.allTags
+ for(tag in tags) {
+ if(tag.dataSize > 4) {
+ tag.offset = offset
+ offset += tag.dataSize
+ }
+ }
+ return offset
+ }
+
+ @Throws(IOException::class)
+ private fun createRequiredIfdAndTag() {
+ // IFD0 is required for all file
+ var ifd0 = exifData !!.getIfdData(IfdId.TYPE_IFD_0)
+ if(ifd0 == null) {
+ ifd0 = IfdData(IfdId.TYPE_IFD_0)
+ exifData !!.addIfdData(ifd0)
+ }
+ val exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD)
+ ?: throw IOException("No definition for crucial exif tag: " + ExifInterface.TAG_EXIF_IFD)
+ ifd0.setTag(exifOffsetTag)
+
+ // Exif IFD is required for all files.
+ var exifIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_EXIF)
+ if(exifIfd == null) {
+ exifIfd = IfdData(IfdId.TYPE_IFD_EXIF)
+ exifData !!.addIfdData(exifIfd)
+ }
+
+ // GPS IFD
+ val gpsIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_GPS)
+ if(gpsIfd != null) {
+ val gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD)
+ ?: throw IOException("No definition for crucial exif tag: " + ExifInterface.TAG_GPS_IFD)
+ ifd0.setTag(gpsOffsetTag)
+ }
+
+ // Interoperability IFD
+ val interIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY)
+ if(interIfd != null) {
+ val interOffsetTag =
+ mInterface.buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD)
+ ?: throw IOException("No definition for crucial exif tag: " + ExifInterface.TAG_INTEROPERABILITY_IFD)
+ exifIfd.setTag(interOffsetTag)
+ }
+
+ var ifd1 = exifData !!.getIfdData(IfdId.TYPE_IFD_1)
+
+ // thumbnail
+ when {
+ exifData !!.hasCompressedThumbnail() -> {
+
+ if(ifd1 == null) {
+ ifd1 = IfdData(IfdId.TYPE_IFD_1)
+ exifData !!.addIfdData(ifd1)
+ }
+
+ val offsetTag =
+ mInterface.buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)
+ ?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT}")
+
+ ifd1.setTag(offsetTag)
+ val lengthTag =
+ mInterface.buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)
+ ?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH}")
+
+ lengthTag.setValue(exifData !!.compressedThumbnail !!.size)
+ 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))
+ }
+ exifData !!.hasUncompressedStrip() -> {
+ if(ifd1 == null) {
+ ifd1 = IfdData(IfdId.TYPE_IFD_1)
+ exifData !!.addIfdData(ifd1)
+ }
+ val stripCount = exifData !!.stripCount
+ val offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS)
+ ?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_STRIP_OFFSETS}")
+ val lengthTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS)
+ ?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_STRIP_BYTE_COUNTS}")
+ val lengths = LongArray(stripCount)
+ for(i in 0 until exifData !!.stripCount) {
+ lengths[i] = exifData !!.getStrip(i) !!.size.toLong()
+ }
+ 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))
+ }
+ 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 fun calculateAllOffset() : Int {
+ var offset = TIFF_HEADER_SIZE.toInt()
+ val exifData = this.exifData !!
+ val ifd0 = exifData.getIfdData(IfdId.TYPE_IFD_0)
+ offset = calculateOffsetOfIfd(ifd0 !!, offset)
+ ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD))
+ ?.setValue(offset)
+
+ val exifIfd = exifData.getIfdData(IfdId.TYPE_IFD_EXIF)
+ offset = calculateOffsetOfIfd(exifIfd !!, offset)
+
+ val interIfd = exifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY)
+ if(interIfd != null) {
+ exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
+ ?.setValue(offset)
+ offset = calculateOffsetOfIfd(interIfd, offset)
+ }
+
+ val gpsIfd = exifData.getIfdData(IfdId.TYPE_IFD_GPS)
+ if(gpsIfd != null) {
+ ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD))
+ ?.setValue(offset)
+ offset = calculateOffsetOfIfd(gpsIfd, offset)
+ }
+
+ val ifd1 = exifData.getIfdData(IfdId.TYPE_IFD_1)
+ if(ifd1 != null) {
+ ifd0.offsetToNextIfd = offset
+ offset = calculateOffsetOfIfd(ifd1, offset)
+ }
+
+ // thumbnail
+ if(exifData .hasCompressedThumbnail()) {
+ ifd1 !!.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
+ ?.setValue(offset)
+ offset += exifData .compressedThumbnail !!.size
+ } else if(exifData .hasUncompressedStrip()) {
+ val stripCount = exifData .stripCount
+ val offsets = LongArray(stripCount)
+ for(i in 0 until exifData .stripCount) {
+ offsets[i] = offset.toLong()
+ offset += exifData .getStrip(i) !!.size
+ }
+ ifd1 !!.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
+ ?.setValue(offsets)
+ }
+ return offset
+ }
+
+ companion object {
+ private const val TAG = "ExifOutputStream"
+ private const val STREAMBUFFER_SIZE = 0x00010000 // 64Kb
+
+ private const val STATE_SOI = 0
+ private const val EXIF_HEADER = 0x45786966
+ private const val TIFF_HEADER : Short = 0x002A
+ private const val TIFF_BIG_ENDIAN : Short = 0x4d4d
+ private const val TIFF_LITTLE_ENDIAN : Short = 0x4949
+ private const val TAG_SIZE : Short = 12
+ private const val TIFF_HEADER_SIZE : Short = 8
+ private const val MAX_EXIF_SIZE = 65535
+
+ @Throws(IOException::class)
+ fun writeTagValue(tag : ExifTag, dataOutputStream : OrderedDataOutputStream) {
+ when(tag.dataType) {
+ ExifTag.TYPE_ASCII -> {
+ val buf = tag.stringByte !!
+ if(buf.size == tag.componentCount) {
+ buf[buf.size - 1] = 0
+ dataOutputStream.write(buf)
+ } else {
+ dataOutputStream.write(buf)
+ dataOutputStream.write(0)
+ }
+ }
+
+ ExifTag.TYPE_LONG, ExifTag.TYPE_UNSIGNED_LONG -> run {
+ for(i in 0 until tag.componentCount) {
+ dataOutputStream.writeInt(tag.getValueAt(i).toInt())
+ }
+ }
+
+ ExifTag.TYPE_RATIONAL, ExifTag.TYPE_UNSIGNED_RATIONAL -> run {
+ for(i in 0 until tag.componentCount) {
+ dataOutputStream.writeRational(tag.getRational(i) !!)
+ }
+ }
+
+ ExifTag.TYPE_UNDEFINED, ExifTag.TYPE_UNSIGNED_BYTE -> {
+ val buf = ByteArray(tag.componentCount)
+ tag.getBytes(buf)
+ dataOutputStream.write(buf)
+ }
+
+ ExifTag.TYPE_UNSIGNED_SHORT -> {
+ for(i in 0 until tag.componentCount) {
+ dataOutputStream.writeShort(tag.getValueAt(i).toShort())
+ }
+ }
+ }
+ }
+ }
+}
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
deleted file mode 100644
index d6df859d..00000000
--- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifParser.java
+++ /dev/null
@@ -1,1099 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package 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/ExifParser.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifParser.kt
new file mode 100644
index 00000000..fc50e596
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifParser.kt
@@ -0,0 +1,1191 @@
+/*
+ * 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.*
+import kotlin.math.min
+
+internal open class ExifParser @Throws(IOException::class, ExifInvalidFormatException::class)
+private constructor(
+ inputStream : InputStream?,
+ private val mOptions : Int,
+ private val mInterface : ExifInterface
+) {
+
+ private val mCorrespondingEvent = TreeMap()
+ private val mTiffStream : CountedDataInputStream?
+ private var mIfdStartOffset = 0
+
+ /**
+ * Gets number of tags in the current IFD area.
+ */
+ private var tagCountInCurrentIfd = 0
+
+ /**
+ * 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
+ */
+ var currentIfd : Int = 0
+ private set
+ /**
+ * If [.next] return [.EVENT_NEW_TAG] or
+ * [.EVENT_VALUE_OF_REGISTERED_TAG], call this function to get the
+ * corresponding tag.
+ *
+ *
+ * For [.EVENT_NEW_TAG], the tag may not contain the value if the size
+ * of the value is greater than 4 bytes. One should call
+ * [ExifTag.hasValue] to check if the tag contains value. If there
+ * is no value,call [.registerForTagValue] to have the parser
+ * emit [.EVENT_VALUE_OF_REGISTERED_TAG] when it reaches the area
+ * pointed by the offset.
+ *
+ *
+ * When [.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
+ * @see .read
+ * @see .read
+ * @see .readLong
+ * @see .readRational
+ * @see .readString
+ * @see .readString
+ */
+ var tag : ExifTag? = null
+ private set
+
+ private var mImageEvent : ImageEvent? = null
+ private var mStripSizeTag : ExifTag? = null
+ private var mJpegSizeTag : ExifTag? = null
+ private var mNeedToParseOffsetsInCurrentIfd : Boolean = false
+ private var mDataAboveIfd0 : ByteArray? = null
+ private var mIfd0Position : Int = 0
+
+ var qualityGuess : Int = 0
+ private set
+ var imageWidth : Int = 0
+ private set
+ var imageLength : Int = 0
+ private set
+ var jpegProcess : Short = 0
+ private set
+ private val mSections : MutableList
+ var uncompressedDataPosition = 0
+ private set
+
+ private val mByteArray = ByteArray(8)
+ private val mByteBuffer = ByteBuffer.wrap(mByteArray)
+
+ private val isThumbnailRequested : Boolean
+ get() = mOptions and ExifInterface.Options.OPTION_THUMBNAIL != 0
+
+ /**
+ * When receiving [.EVENT_UNCOMPRESSED_STRIP], call this function to
+ * get the index of this strip.
+ */
+ val stripIndex : Int
+ get() = mImageEvent?.stripIndex ?: 0
+
+ /**
+ * When receiving [.EVENT_UNCOMPRESSED_STRIP], call this function to
+ * get the strip size.
+ */
+ val stripSize : Int
+ get() = mStripSizeTag?.getValueAt(0)?.toInt() ?: 0
+
+ /**
+ * When receiving [.EVENT_COMPRESSED_IMAGE], call this function to get
+ * the image data size.
+ */
+ val compressedImageSize : Int
+ get() = mJpegSizeTag?.getValueAt(0)?.toInt() ?: 0
+
+ /**
+ * Gets the byte order of the current InputStream.
+ */
+ val byteOrder : ByteOrder?
+ get() = mTiffStream?.byteOrder
+
+ val sections : List
+ get() = mSections
+
+ init {
+ if(inputStream == null) {
+ throw IOException("Null argument inputStream to ExifParser")
+ }
+
+ Log.v(TAG, "Reading exif...")
+ mSections = ArrayList(0)
+ mTiffStream = seekTiffData(inputStream)
+
+ // Log.d( TAG, "sections size: " + mSections.size() );
+
+ val tiffStream = mTiffStream
+ if(tiffStream != null) {
+ parseTiffHeader(tiffStream)
+
+ val offset = tiffStream.readUnsignedInt()
+ if(offset > Integer.MAX_VALUE) {
+ throw ExifInvalidFormatException("Invalid offset $offset")
+ }
+ mIfd0Position = offset.toInt()
+ currentIfd = IfdId.TYPE_IFD_0
+
+ if(isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) {
+ registerIfd(IfdId.TYPE_IFD_0, offset)
+ if(offset != DEFAULT_IFD0_OFFSET.toLong()) {
+ val ba = ByteArray(offset.toInt() - DEFAULT_IFD0_OFFSET)
+ mDataAboveIfd0 = ba
+ read(ba)
+ }
+ }
+ }
+ }
+
+ private fun readInt(b : ByteArray, @Suppress("SameParameterValue") offset : Int) : Int {
+ mByteBuffer.rewind()
+ mByteBuffer.put(b, offset, 4)
+ mByteBuffer.rewind()
+ return mByteBuffer.int
+ }
+
+ private fun readShort(b : ByteArray, @Suppress("SameParameterValue") offset : Int) : Short {
+ mByteBuffer.rewind()
+ mByteBuffer.put(b, offset, 2)
+ mByteBuffer.rewind()
+ return mByteBuffer.short
+ }
+
+ @Throws(IOException::class, ExifInvalidFormatException::class)
+ private fun seekTiffData(inputStream : InputStream) : CountedDataInputStream? {
+ val dataStream = CountedDataInputStream(inputStream)
+ var tiffStream : CountedDataInputStream? = null
+
+ var a = dataStream.readUnsignedByte()
+ val b = dataStream.readUnsignedByte()
+
+ if(a != 0xFF || b != JpegHeader.TAG_SOI) {
+ Log.e(TAG, "invalid jpeg header")
+ return null
+ }
+
+ while(true) {
+ val itemlen : Int
+ var marker : Int
+
+ val got : Int
+ val data : ByteArray
+
+ var prev = 0
+ a = 0
+ while(true) {
+ marker = dataStream.readUnsignedByte()
+ if(marker != 0xff && prev == 0xff) break
+ prev = marker
+ a ++
+ }
+
+ if(a > 10) {
+ Log.w(TAG, "Extraneous " + (a - 1) + " padding bytes before section " + marker)
+ }
+
+ val section = Section()
+ section.type = marker
+
+ // Read the length of the section.
+ val lh = dataStream.readByte().toInt()
+ val ll = dataStream.readByte().toInt()
+ itemlen = lh and 0xff shl 8 or (ll and 0xff)
+
+ if(itemlen < 2) {
+ throw ExifInvalidFormatException("Invalid marker")
+ }
+
+ section.size = itemlen
+
+ data = ByteArray(itemlen)
+ data[0] = lh.toByte()
+ data[1] = ll.toByte()
+
+ // 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 ExifInvalidFormatException("Premature end of file? Expecting " + (itemlen - 2) + ", received " + got)
+ }
+
+ section.data = data
+
+ var ignore = false
+
+ when(marker) {
+ JpegHeader.TAG_M_SOS -> {
+ // stop before hitting compressed data
+ mSections.add(section)
+ uncompressedDataPosition = dataStream.readByteCount
+ return tiffStream
+ }
+
+ JpegHeader.TAG_M_DQT ->
+ // Use for jpeg quality guessing
+ process_M_DQT(data)
+
+ JpegHeader.TAG_M_DHT -> {
+ }
+
+ JpegHeader.TAG_M_EOI -> {
+ // in case it's a tables-only JPEG stream
+ Log.w(TAG, "No image in jpeg!")
+ return null
+ }
+
+ JpegHeader.TAG_M_COM ->
+ // Comment section
+ ignore = true
+
+ JpegHeader.TAG_M_JFIF -> if(itemlen < 16) {
+ ignore = true
+ }
+
+ JpegHeader.TAG_M_IPTC -> {
+ }
+
+ JpegHeader.TAG_M_SOF0, JpegHeader.TAG_M_SOF1, JpegHeader.TAG_M_SOF2, JpegHeader.TAG_M_SOF3, JpegHeader.TAG_M_SOF5, JpegHeader.TAG_M_SOF6, JpegHeader.TAG_M_SOF7, JpegHeader.TAG_M_SOF9, JpegHeader.TAG_M_SOF10, JpegHeader.TAG_M_SOF11, JpegHeader.TAG_M_SOF13, JpegHeader.TAG_M_SOF14, JpegHeader.TAG_M_SOF15 -> process_M_SOFn(
+ data,
+ marker
+ )
+
+ JpegHeader.TAG_M_EXIF -> if(itemlen >= 8) {
+ val header = readInt(data, 2)
+ val headerTail = readShort(data, 6)
+ // header = Exif, headerTail=\0\0
+ if(header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
+ tiffStream =
+ CountedDataInputStream(ByteArrayInputStream(data, 8, itemlen - 8))
+ tiffStream.end = itemlen - 6
+ ignore = false
+ } else {
+ Log.v(TAG, "Image cotains XMP section")
+ }
+ }
+
+ else -> Log.w(
+ TAG,
+ "Unknown marker: " + String.format("0x%2X", marker) + ", length: " + itemlen
+ )
+ }
+
+ 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 [java.io.InputStream.read] because
+ * on remote input streams reading large amount of data can fail
+ *
+ * @param dataStream
+ * @param data
+ * @param offsetArg
+ * @param length
+ * @return
+ * @throws IOException
+ */
+ @Throws(IOException::class)
+ private fun readBytes(
+ dataStream : InputStream,
+ data : ByteArray,
+ @Suppress("SameParameterValue") offsetArg : Int,
+ length : Int
+ ) : Int {
+ var offset = offsetArg
+ var count = 0
+ var n : Int
+ var max_length = min(1024, length)
+ while(true) {
+ n = dataStream.read(data, offset, max_length)
+ if(n <= 0) break
+ count += n
+ offset += n
+ max_length = min(max_length, length - count)
+ }
+ return count
+ }
+
+ private fun process_M_SOFn(data : ByteArray, marker : Int) {
+ if(data.size > 7) {
+ //int data_precision = data[2] & 0xff;
+ //int num_components = data[7] & 0xff;
+ imageLength = Get16m(data, 3)
+ imageWidth = Get16m(data, 5)
+ }
+ jpegProcess = marker.toShort()
+ }
+
+ private fun process_M_DQT(data : ByteArray) {
+ var a = 2
+ var c : Int
+ var tableindex : Int
+ var coefindex : Int
+ var cumsf = 0.0
+ var reftable : IntArray? = null
+ var allones = 1
+
+ while(a < data.size) {
+ c = data[a ++].toInt()
+ tableindex = c and 0x0f
+
+ if(tableindex < 2) {
+ reftable = deftabs[tableindex]
+ }
+
+ // Read in the table, compute statistics relative to reference table
+ coefindex = 0
+ while(coefindex < 64) {
+ val `val` : Int
+ if(c shr 4 != 0) {
+ var temp : Int
+ temp = data[a ++].toInt()
+ temp *= 256
+ `val` = data[a ++].toInt() + temp
+ } else {
+ `val` = data[a ++].toInt()
+ }
+ if(reftable != null) {
+ val x : Double
+ // scaling factor in percent
+ x = 100.0 * `val`.toDouble() / reftable[coefindex].toDouble()
+ cumsf += x
+ // separate check for all-ones table (Q 100)
+ if(`val` != 1) allones = 0
+ }
+ coefindex ++
+ }
+ // Print summary stats
+ if(reftable != null) { // terse output includes quality
+ val qual : Double
+ cumsf /= 64.0 // mean scale factor
+
+ qual = when {
+ allones != 0 -> 100.0 // special case for all-ones table
+ cumsf <= 100.0 -> (200.0 - cumsf) / 2.0
+ else -> 5000.0 / cumsf
+ }
+
+ if(tableindex == 0) {
+ qualityGuess = (qual + 0.5).toInt()
+ // Log.v( TAG, "quality guess: " + mQualityGuess );
+ }
+ }
+ }
+ }
+
+ @Throws(IOException::class, ExifInvalidFormatException::class)
+ private fun parseTiffHeader(stream : CountedDataInputStream) {
+
+ stream.byteOrder = when(stream.readShort()) {
+ LITTLE_ENDIAN_TAG -> ByteOrder.LITTLE_ENDIAN
+ BIG_ENDIAN_TAG -> ByteOrder.BIG_ENDIAN
+ else -> throw ExifInvalidFormatException("Invalid TIFF header")
+ }
+
+ if(stream.readShort() != TIFF_HEADER_TAIL) {
+ throw ExifInvalidFormatException("Invalid TIFF header")
+ }
+ }
+
+ private fun isIfdRequested(ifdType : Int) : Boolean {
+ when(ifdType) {
+ IfdId.TYPE_IFD_0 -> return mOptions and ExifInterface.Options.OPTION_IFD_0 != 0
+ IfdId.TYPE_IFD_1 -> return mOptions and ExifInterface.Options.OPTION_IFD_1 != 0
+ IfdId.TYPE_IFD_EXIF -> return mOptions and ExifInterface.Options.OPTION_IFD_EXIF != 0
+ IfdId.TYPE_IFD_GPS -> return mOptions and ExifInterface.Options.OPTION_IFD_GPS != 0
+ IfdId.TYPE_IFD_INTEROPERABILITY -> return mOptions and ExifInterface.Options.OPTION_IFD_INTEROPERABILITY != 0
+ }
+ return false
+ }
+
+ private fun needToParseOffsetsInCurrentIfd() : Boolean {
+ return when(currentIfd) {
+
+ IfdId.TYPE_IFD_0 ->
+ isIfdRequested(IfdId.TYPE_IFD_EXIF) ||
+ isIfdRequested(IfdId.TYPE_IFD_GPS) ||
+ isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY) ||
+ isIfdRequested(IfdId.TYPE_IFD_1)
+
+ IfdId.TYPE_IFD_1 -> isThumbnailRequested
+
+ IfdId.TYPE_IFD_EXIF ->
+ // The offset to interoperability IFD is located in Exif IFD
+ isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)
+
+ else -> false
+ }
+ }
+
+ private fun registerIfd(ifdType : Int, offset : Long) {
+ // Cast unsigned int to int since the offset is always smaller
+ // than the size of M_EXIF (65536)
+ mCorrespondingEvent[offset.toInt()] = IfdEvent(ifdType, isIfdRequested(ifdType))
+ }
+
+ /**
+ * Equivalent to read(buffer, 0, buffer.length).
+ */
+ @Throws(IOException::class)
+ fun read(buffer : ByteArray) : Int {
+ return mTiffStream?.read(buffer) ?: 0
+ }
+ //
+ // /**
+ // * 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
+ */
+ @Throws(IOException::class, ExifInvalidFormatException::class)
+ operator fun next() : Int {
+ mTiffStream ?: return EVENT_END
+
+ val offset = mTiffStream.readByteCount
+ val endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * tagCountInCurrentIfd
+ if(offset < endOfTags) {
+ val tag = readTag()
+ this.tag = tag
+ if(tag == null) {
+ return next()
+ } else if(mNeedToParseOffsetsInCurrentIfd) {
+ checkOffsetOrImageTag(tag)
+ }
+ return EVENT_NEW_TAG
+ } else if(offset == endOfTags) {
+ // There is a link to ifd1 at the end of ifd0
+ if(currentIfd == IfdId.TYPE_IFD_0) {
+ val ifdOffset = readUnsignedLong()
+ if(isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested) {
+ if(ifdOffset != 0L) {
+ registerIfd(IfdId.TYPE_IFD_1, ifdOffset)
+ }
+ }
+ } else {
+ var offsetSize = 4
+ // Some camera models use invalid length of the offset
+ if(mCorrespondingEvent.size > 0) {
+ val firstEntry = mCorrespondingEvent.firstEntry() !!
+ offsetSize = firstEntry.key - mTiffStream.readByteCount
+ }
+ if(offsetSize < 4) {
+ Log.w(TAG, "Invalid size of link to next IFD: $offsetSize")
+ } else {
+ val ifdOffset = readUnsignedLong()
+ if(ifdOffset != 0L) {
+ Log.w(TAG, "Invalid link to next IFD: $ifdOffset")
+ }
+ }
+ }
+ }
+ while(mCorrespondingEvent.size != 0) {
+ val entry = mCorrespondingEvent.pollFirstEntry() !!
+ val event = entry.value
+ try {
+ // Log.v(TAG, "skipTo: " + entry.getKey());
+ skipTo(entry.key)
+ } catch(e : IOException) {
+ Log.w(
+ TAG,
+ "Failed to skip to data at: ${entry.key} for ${event.javaClass.name}, the file may be broken."
+ )
+ continue
+ }
+
+ if(event is IfdEvent) {
+ currentIfd = event.ifd
+ tagCountInCurrentIfd = mTiffStream.readUnsignedShort()
+ mIfdStartOffset = entry.key
+
+ if(tagCountInCurrentIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mTiffStream.end) {
+ Log.w(TAG, "Invalid size of IFD $currentIfd")
+ return EVENT_END
+ }
+
+ mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd()
+ if(event.isRequested) {
+ return EVENT_START_OF_IFD
+ } else {
+ skipRemainingTagsInCurrentIfd()
+ }
+ } else if(event is ImageEvent) {
+ mImageEvent = event
+ return event.type
+ } else {
+ val tagEvent = event as ExifTagEvent
+ val tag = tagEvent.tag
+ this.tag = tag
+ if(tag.dataType != ExifTag.TYPE_UNDEFINED) {
+ readFullTagValue(tag)
+ checkOffsetOrImageTag(tag)
+ }
+ 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
+ */
+ @Throws(IOException::class, ExifInvalidFormatException::class)
+ protected fun skipRemainingTagsInCurrentIfd() {
+ if(mTiffStream == null) return
+
+ val endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * tagCountInCurrentIfd
+ var offset = mTiffStream.readByteCount
+ if(offset > endOfTags) {
+ return
+ }
+ if(mNeedToParseOffsetsInCurrentIfd) {
+ while(offset < endOfTags) {
+ val tag = readTag()
+ this.tag = tag
+ offset += TAG_SIZE
+ if(tag == null) {
+ continue
+ }
+ checkOffsetOrImageTag(tag)
+ }
+ } else {
+ skipTo(endOfTags)
+ }
+ val ifdOffset = readUnsignedLong()
+ // For ifd0, there is a link to ifd1 in the end of all tags
+ if(currentIfd == IfdId.TYPE_IFD_0 && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested)) {
+ if(ifdOffset > 0) {
+ registerIfd(IfdId.TYPE_IFD_1, ifdOffset)
+ }
+ }
+ }
+
+ @Throws(IOException::class)
+ private fun skipTo(offset : Int) {
+ mTiffStream?.skipTo(offset.toLong())
+ // Log.v(TAG, "available: " + mTiffStream.available() );
+ while(! mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) {
+ mCorrespondingEvent.pollFirstEntry()
+ }
+ }
+
+ /**
+ * When getting [.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 [.EVENT_VALUE_OF_REGISTERED_TAG] when it reaches the area
+ * where the value is located.
+ *
+ * @see .EVENT_VALUE_OF_REGISTERED_TAG
+ */
+ fun registerForTagValue(tag : ExifTag) {
+ mTiffStream ?: return
+ if(tag.offset >= mTiffStream.readByteCount) {
+ mCorrespondingEvent[tag.offset] = ExifTagEvent(tag, true)
+ }
+ }
+
+ private fun registerCompressedImage(offset : Long) {
+ mCorrespondingEvent[offset.toInt()] = ImageEvent(EVENT_COMPRESSED_IMAGE)
+ }
+
+ private fun registerUncompressedStrip(stripIndex : Int, offset : Long) {
+ mCorrespondingEvent[offset.toInt()] = ImageEvent(EVENT_UNCOMPRESSED_STRIP, stripIndex)
+ }
+
+ @Throws(IOException::class, ExifInvalidFormatException::class)
+ private fun readTag() : ExifTag? {
+ mTiffStream ?: return null
+
+ val tagId = mTiffStream.readShort()
+ val dataFormat = mTiffStream.readShort()
+ val numOfComp = mTiffStream.readUnsignedInt()
+ if(numOfComp > Integer.MAX_VALUE) {
+ throw 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
+ val tag = ExifTag(
+ tagId,
+ dataFormat,
+ numOfComp.toInt(),
+ currentIfd,
+ numOfComp.toInt() != ExifTag.SIZE_UNDEFINED
+ )
+ val dataSize = tag.dataSize
+ if(dataSize > 4) {
+ val offset = mTiffStream.readUnsignedInt()
+ if(offset > Integer.MAX_VALUE) {
+ throw 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) {
+ val buf = ByteArray(numOfComp.toInt())
+ System.arraycopy(
+ mDataAboveIfd0 !!,
+ offset.toInt() - DEFAULT_IFD0_OFFSET,
+ buf,
+ 0,
+ numOfComp.toInt()
+ )
+ tag.setValue(buf)
+ } else {
+ tag.offset = offset.toInt()
+ }
+ } else {
+ val defCount = tag.hasDefinedCount
+ // Set defined count to 0 so we can add \0 to non-terminated strings
+ tag.hasDefinedCount = false
+ // Read value
+ readFullTagValue(tag)
+ tag.hasDefinedCount = defCount
+ mTiffStream.skip((4 - dataSize).toLong())
+ // Set the offset to the position of value.
+ tag.offset = mTiffStream.readByteCount - 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 fun checkOffsetOrImageTag(tag : ExifTag) {
+ // Some invalid formattd image contains tag with 0 size.
+ if(tag.componentCount == 0) {
+ return
+ }
+ val tid = tag.tagId
+ val ifd = tag.ifd
+ 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(i in 0 until tag.componentCount) {
+ if(tag.dataType == ExifTag.TYPE_UNSIGNED_SHORT) {
+ registerUncompressedStrip(i, tag.getValueAt(i))
+ } else {
+ registerUncompressedStrip(i, tag.getValueAt(i))
+ }
+ }
+ } else {
+ mCorrespondingEvent[tag.offset] = ExifTagEvent(tag, false)
+ }
+ }
+ } else if(tid == TAG_STRIP_BYTE_COUNTS && checkAllowed(
+ ifd,
+ ExifInterface.TAG_STRIP_BYTE_COUNTS
+ ) && isThumbnailRequested && tag.hasValue()) {
+ mStripSizeTag = tag
+ }
+ }
+
+ fun isDefinedTag(ifdId : Int, tagId : Int) : Boolean {
+ return mInterface.tagInfo.get(
+ ExifInterface.defineTag(
+ ifdId,
+ tagId.toShort()
+ )
+ ) != ExifInterface.DEFINITION_NULL
+ }
+
+ private fun checkAllowed(ifd : Int, tagId : Int) : Boolean {
+ val info = mInterface.tagInfo.get(tagId)
+ return if(info == ExifInterface.DEFINITION_NULL) {
+ false
+ } else ExifInterface.isIfdAllowed(info, ifd)
+ }
+
+ @Throws(IOException::class)
+ fun readFullTagValue(tag : ExifTag) {
+ mTiffStream ?: return
+
+ // Some invalid images contains tags with wrong size, check it here
+ val type = tag.dataType
+ val componentCount = tag.componentCount
+
+ // sanity check
+ if(componentCount >= 0x66000000) throw IOException("size out of bounds")
+
+ if(type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
+ type == ExifTag.TYPE_UNSIGNED_BYTE) {
+ var size = tag.componentCount
+ if(mCorrespondingEvent.size > 0) {
+ val firstEntry = mCorrespondingEvent.firstEntry() !!
+ if(firstEntry.key < mTiffStream.readByteCount + size) {
+ val event = firstEntry.value
+ if(event is ImageEvent) {
+ // Tag value overlaps thumbnail, ignore thumbnail.
+ Log.w(TAG, "Thumbnail overlaps value for tag: \n$tag")
+ val entry = mCorrespondingEvent.pollFirstEntry() !!
+ Log.w(TAG, "Invalid thumbnail offset: " + entry.key)
+ } else {
+ // Tag value overlaps another tag, shorten count
+ if(event is IfdEvent) {
+ Log.w(TAG, "Ifd ${event.ifd} overlaps value for tag: \n$tag")
+ } else if(event is ExifTagEvent) {
+ Log.w(
+ TAG,
+ "Tag value for tag: \n${event.tag} overlaps value for tag: \n$tag"
+ )
+ }
+ size = firstEntry.key - mTiffStream.readByteCount
+ Log.w(TAG, "Invalid size of tag: \n$tag setting count to: $size")
+ tag.forceSetComponentCount(size)
+ }
+ }
+ }
+ }
+ when(tag.dataType) {
+ ExifTag.TYPE_UNSIGNED_BYTE, ExifTag.TYPE_UNDEFINED ->
+ tag.setValue(ByteArray(componentCount).also { read(it) })
+
+ ExifTag.TYPE_ASCII ->
+ tag.setValue(readString(componentCount))
+
+ ExifTag.TYPE_UNSIGNED_SHORT ->
+ tag.setValue(IntArray(componentCount) { readUnsignedShort() })
+
+ ExifTag.TYPE_LONG ->
+ tag.setValue(IntArray(componentCount) { readLong().toInt() })
+ ExifTag.TYPE_UNSIGNED_LONG ->
+ tag.setValue(LongArray(componentCount) { readUnsignedLong() })
+
+ ExifTag.TYPE_RATIONAL ->
+ tag.setValue(Array(componentCount) { readRational() })
+ ExifTag.TYPE_UNSIGNED_RATIONAL ->
+ tag.setValue(Array(componentCount) { readUnsignedRational() })
+
+ }
+
+ // Log.v( TAG, "\n" + tag.toString() );
+ }
+
+ /**
+ * Reads bytes from the InputStream.
+ */
+ @Throws(IOException::class)
+ protected fun read(buffer : ByteArray, offset : Int, length : Int) : Int {
+ mTiffStream ?: return 0
+ 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 [ExifTag.TYPE_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 [ExifTag.TYPE_ASCII].
+ */
+ @Throws(IOException::class)
+ @JvmOverloads
+ protected fun readString(n : Int, charset : Charset = US_ASCII) : String =
+ when {
+ mTiffStream == null || n <= 0 -> ""
+ else -> mTiffStream.readString(n, charset)
+ }
+
+ /**
+ * Reads value of type [ExifTag.TYPE_UNSIGNED_SHORT] from the
+ * InputStream.
+ */
+ @Throws(IOException::class)
+ protected fun readUnsignedShort() : Int {
+ val iv = mTiffStream?.readShort() ?: 0
+ return iv.toInt() and 0xffff
+ }
+
+ /**
+ * Reads value of type [ExifTag.TYPE_UNSIGNED_LONG] from the
+ * InputStream.
+ */
+ @Throws(IOException::class)
+ protected fun readUnsignedLong() : Long {
+ return readLong() and 0xffffffffL
+ }
+
+ /**
+ * Reads value of type [ExifTag.TYPE_UNSIGNED_RATIONAL] from the
+ * InputStream.
+ */
+ @Throws(IOException::class)
+ protected fun readUnsignedRational() : Rational {
+ val nomi = readUnsignedLong()
+ val denomi = readUnsignedLong()
+ return Rational(nomi, denomi)
+ }
+
+ /**
+ * Reads value of type [ExifTag.TYPE_LONG] from the InputStream.
+ */
+ @Throws(IOException::class)
+ protected fun readLong() : Long {
+ return mTiffStream?.readInt()?.toLong() ?: 0L
+ }
+
+ /**
+ * Reads value of type [ExifTag.TYPE_RATIONAL] from the InputStream.
+ */
+ @Throws(IOException::class)
+ protected fun readRational() : Rational {
+ val nomi = readLong()
+ val denomi = readLong()
+ return Rational(nomi, denomi)
+ }
+
+ private class ImageEvent {
+ internal var stripIndex : Int = 0
+ internal var type : Int = 0
+
+ internal constructor(type : Int) {
+ this.stripIndex = 0
+ this.type = type
+ }
+
+ internal constructor(type : Int, stripIndex : Int) {
+ this.type = type
+ this.stripIndex = stripIndex
+ }
+ }
+
+ private class IfdEvent internal constructor(
+ internal var ifd : Int,
+ internal var isRequested : Boolean
+ )
+
+ private class ExifTagEvent internal constructor(
+ internal var tag : ExifTag,
+ internal var isRequested : Boolean
+ )
+
+ class Section {
+ internal var size : Int = 0
+ internal var type : Int = 0
+ internal var data : ByteArray? = null
+ }
+
+ companion object {
+ private const val TAG = "ExifParser"
+
+ /**
+ * When the parser reaches a new IFD area. Call [.getCurrentIfd] to
+ * know which IFD we are in.
+ */
+ const val EVENT_START_OF_IFD = 0
+ /**
+ * When the parser reaches a new tag. Call [.getTag]to get the
+ * corresponding tag.
+ */
+ const val EVENT_NEW_TAG = 1
+ /**
+ * When the parser reaches the value area of tag that is registered by
+ * [.registerForTagValue] previously. Call [.getTag]
+ * to get the corresponding tag.
+ */
+ const val EVENT_VALUE_OF_REGISTERED_TAG = 2
+ /**
+ * When the parser reaches the compressed image area.
+ */
+ const val EVENT_COMPRESSED_IMAGE = 3
+ /**
+ * When the parser reaches the uncompressed image strip. Call
+ * [.getStripIndex] to get the index of the strip.
+ *
+ * @see .getStripIndex
+ */
+ const val EVENT_UNCOMPRESSED_STRIP = 4
+ /**
+ * When there is nothing more to parse.
+ */
+ const val EVENT_END = 5
+
+ protected const val EXIF_HEADER = 0x45786966 // EXIF header "Exif"
+ protected const val EXIF_HEADER_TAIL = 0x0000.toShort() // EXIF header in M_EXIF
+ // TIFF header
+ protected const val LITTLE_ENDIAN_TAG = 0x4949.toShort() // "II"
+ protected const val BIG_ENDIAN_TAG = 0x4d4d.toShort() // "MM"
+ protected const val TIFF_HEADER_TAIL : Short = 0x002A
+ protected const val TAG_SIZE = 12
+ protected const val OFFSET_SIZE = 2
+ protected const val DEFAULT_IFD0_OFFSET = 8
+ private val US_ASCII = Charset.forName("US-ASCII")
+ private val TAG_EXIF_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)
+ private val TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)
+ private val TAG_INTEROPERABILITY_IFD =
+ ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD)
+ private val TAG_JPEG_INTERCHANGE_FORMAT =
+ ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)
+ private val TAG_JPEG_INTERCHANGE_FORMAT_LENGTH =
+ ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)
+ private val TAG_STRIP_OFFSETS = ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)
+ private val TAG_STRIP_BYTE_COUNTS =
+ ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)
+
+ private val std_luminance_quant_tbl : IntArray
+ private val std_chrominance_quant_tbl : IntArray
+ val deftabs : Array
+
+ init {
+ std_luminance_quant_tbl = intArrayOf(
+ 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 = intArrayOf(
+ 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 = arrayOf(std_luminance_quant_tbl, std_chrominance_quant_tbl)
+ }
+
+ fun Get16m(data : ByteArray, position : Int) : Int {
+ val b1 = data[position].toInt() and 0xFF shl 8
+ val b2 = data[position + 1].toInt() and 0xFF
+ return b1 or b2
+ }
+
+ /**
+ * Parses the the given InputStream with the given options
+ *
+ * @throws java.io.IOException
+ * @throws ExifInvalidFormatException
+ */
+ @Throws(IOException::class, ExifInvalidFormatException::class)
+ fun parse(inputStream : InputStream, options : Int, iRef : ExifInterface) : ExifParser {
+ return ExifParser(inputStream, options, iRef)
+ }
+ }
+}
\ No newline at end of file
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
deleted file mode 100644
index 72729cd3..00000000
--- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifReader.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package 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/ExifReader.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifReader.kt
new file mode 100644
index 00000000..6c45148a
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifReader.kt
@@ -0,0 +1,112 @@
+/*
+ * 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
+ * [ExifData].
+ */
+internal class ExifReader(private val mInterface : ExifInterface) {
+
+ /**
+ * Parses the inputStream and and returns the EXIF data in an
+ * [ExifData].
+ *
+ * @throws ExifInvalidFormatException
+ * @throws java.io.IOException
+ */
+ @Throws(ExifInvalidFormatException::class, IOException::class)
+ fun read(inputStream : InputStream, options : Int) : ExifData {
+ val parser = ExifParser.parse(inputStream, options, mInterface)
+ val exifData = ExifData(parser.byteOrder !!)
+ exifData.sections = parser.sections
+ exifData.mUncompressedDataPosition = parser.uncompressedDataPosition
+
+ exifData.qualityGuess = parser.qualityGuess
+ exifData.jpegProcess = parser.jpegProcess
+
+ val w = parser.imageWidth
+ val h = parser.imageLength
+
+ if(w > 0 && h > 0) {
+ exifData.setImageSize(w, h)
+ }
+
+ var tag : ExifTag?
+
+ var event = parser.next()
+ while(event != ExifParser.EVENT_END) {
+ when(event) {
+ ExifParser.EVENT_START_OF_IFD -> exifData.addIfdData(IfdData(parser.currentIfd))
+
+ ExifParser.EVENT_NEW_TAG -> {
+ tag = parser.tag
+
+
+
+ if(! tag !!.hasValue()) {
+ parser.registerForTagValue(tag)
+ } else {
+ // Log.v(TAG, "parsing id " + tag.getTagId() + " = " + tag);
+ if(parser.isDefinedTag(tag.ifd, tag.tagId.toInt())) {
+ exifData.getIfdData(tag.ifd) !!.setTag(tag)
+ } else {
+ Log.w(TAG, "skip tag because not registered in the tag table:$tag")
+ }
+ }
+ }
+
+ ExifParser.EVENT_VALUE_OF_REGISTERED_TAG -> {
+ tag = parser.tag
+ if(tag !!.dataType == ExifTag.TYPE_UNDEFINED) {
+ parser.readFullTagValue(tag)
+ }
+ exifData.getIfdData(tag.ifd) !!.setTag(tag)
+ }
+
+ ExifParser.EVENT_COMPRESSED_IMAGE -> {
+ val buf = ByteArray(parser.compressedImageSize)
+ if(buf.size == parser.read(buf)) {
+ exifData.compressedThumbnail = buf
+ } else {
+ Log.w(TAG, "Failed to read the compressed thumbnail")
+ }
+ }
+
+ ExifParser.EVENT_UNCOMPRESSED_STRIP -> {
+ val buf = ByteArray(parser.stripSize)
+ if(buf.size == parser.read(buf)) {
+ exifData.setStripBytes(parser.stripIndex, buf)
+ } else {
+ Log.w(TAG, "Failed to read the strip bytes")
+ }
+ }
+ }
+ event = parser.next()
+ }
+ return exifData
+ }
+
+ companion object {
+ private const val TAG = "ExifReader"
+ }
+}
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
deleted file mode 100644
index 2921c330..00000000
--- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifTag.java
+++ /dev/null
@@ -1,1016 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package 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/ExifTag.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifTag.kt
new file mode 100644
index 00000000..aea52276
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifTag.kt
@@ -0,0 +1,893 @@
+/*
+ * 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.*
+
+/**
+ * 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 [ExifInterface.buildTag].
+ *
+ * @see ExifInterface
+ */
+// Use builtTag in ExifInterface instead of constructor.
+@Suppress("unused")
+open class ExifTag internal constructor(
+
+ // Exif TagId. the TID of this tag.
+ val tagId : Short,
+
+ // Exif Tag Type. 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
+ */
+ val dataType : Short,
+
+ componentCount : Int,
+
+ // The ifd that this tag should be put in. 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
+ */
+ var ifd : Int,
+
+ // If tag has defined count
+ private var mHasDefinedDefaultComponentCount : Boolean
+) {
+ // Actual data count in tag (should be number of elements in value array)
+ /**
+ * Gets the component count of this tag.
+ */
+
+ // TODO: fix integer overflows with this
+ var componentCount : Int = 0
+ private set
+
+ // The value (array of elements of type Tag Type)
+ private var mValue : Any? = null
+
+ // Value offset in exif header. 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.
+ var offset : Int = 0
+
+ // the total data size in bytes of the value of this tag.
+ val dataSize : Int
+ get() = componentCount * getElementSize(dataType)
+
+
+ /**
+ * Gets the value as a byte array. This method should be used for tags of
+ * type [.TYPE_UNDEFINED] or [.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.
+ */
+ val valueAsBytes : ByteArray?
+ get() = mValue as? ByteArray
+
+ /**
+ * Gets the value as an array of longs. This method should be used for tags
+ * of type [.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.
+ */
+ val valueAsLongs : LongArray?
+ get() = mValue as? LongArray
+
+ /**
+ * Gets the value as an array of Rationals. This method should be used for
+ * tags of type [.TYPE_RATIONAL] or [.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.
+ */
+ @Suppress("UNCHECKED_CAST")
+ val valueAsRationals : Array?
+ get() = mValue as? Array
+
+ /**
+ * Gets the value as an array of ints. This method should be used for tags
+ * of type [.TYPE_UNSIGNED_SHORT], [.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.
+ */
+ // Truncates
+ val valueAsInts : IntArray?
+ get() = when(val v = mValue){
+ is LongArray-> IntArray(v.size){ v[it].toInt()}
+ else ->null
+ }
+
+ /**
+ * Gets the value as a String. This method should be used for tags of type
+ * [.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.
+ */
+ val valueAsString : String?
+ get() = when(val v = mValue) {
+ is String -> v
+ is ByteArray -> String(v, US_ASCII)
+ else -> null
+ }
+
+
+ /**
+ * Gets the [.TYPE_ASCII] data.
+ *
+ * @throws IllegalArgumentException If the type is NOT
+ * [.TYPE_ASCII].
+ */
+ protected val string : String
+ get() = valueAsString !!
+
+ /*
+ * Get the converted ascii byte. Used by ExifOutputStream.
+ */
+ val stringByte : ByteArray?
+ get() = mValue as? ByteArray
+
+ init {
+ this.componentCount = componentCount
+ mValue = null
+ }
+
+ /**
+ * Sets the component count of this tag. Call this function before
+ * setValue() if the length of value does not match the component count.
+ */
+ fun forceSetComponentCount(count : Int) {
+ componentCount = count
+ }
+
+ /**
+ * Returns true if this ExifTag contains value; otherwise, this tag will
+ * contain an offset value that is determined when the tag is written.
+ */
+ fun hasValue() : Boolean {
+ return mValue != null
+ }
+
+ /**
+ * Sets integer values into this tag. This method should be used for tags of
+ * type [.TYPE_UNSIGNED_SHORT]. This method will fail if:
+ *
+ * * The component type of this tag is not [.TYPE_UNSIGNED_SHORT],
+ * [.TYPE_UNSIGNED_LONG], or [.TYPE_LONG].
+ * * The value overflows.
+ * * The value.length does NOT match the component count in the definition
+ * for this tag.
+ *
+ */
+ fun setValue(value : IntArray) : Boolean {
+ if(checkBadComponentCount(value.size)) {
+ return false
+ }
+ if(dataType != TYPE_UNSIGNED_SHORT && dataType != TYPE_LONG &&
+ dataType != TYPE_UNSIGNED_LONG) {
+ return false
+ }
+ if(dataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
+ return false
+ } else if(dataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
+ return false
+ }
+
+ val data = LongArray(value.size)
+ for(i in value.indices) {
+ data[i] = value[i].toLong()
+ }
+ mValue = data
+ componentCount = value.size
+ return true
+ }
+
+ /**
+ * Sets integer value into this tag. This method should be used for tags of
+ * type [.TYPE_UNSIGNED_SHORT], or [.TYPE_LONG]. This method
+ * will fail if:
+ *
+ * * The component type of this tag is not [.TYPE_UNSIGNED_SHORT],
+ * [.TYPE_UNSIGNED_LONG], or [.TYPE_LONG].
+ * * The value overflows.
+ * * The component count in the definition of this tag is not 1.
+ *
+ */
+ fun setValue(value : Int) : Boolean {
+ return setValue(intArrayOf(value))
+ }
+
+ /**
+ * Sets long values into this tag. This method should be used for tags of
+ * type [.TYPE_UNSIGNED_LONG]. This method will fail if:
+ *
+ * * The component type of this tag is not [.TYPE_UNSIGNED_LONG].
+ * * The value overflows.
+ * * The value.length does NOT match the component count in the definition
+ * for this tag.
+ *
+ */
+ fun setValue(value : LongArray) : Boolean {
+ if(checkBadComponentCount(value.size) || dataType != TYPE_UNSIGNED_LONG) {
+ return false
+ }
+ if(checkOverflowForUnsignedLong(value)) {
+ return false
+ }
+ mValue = value
+ componentCount = value.size
+ return true
+ }
+
+ /**
+ * Sets long values into this tag. This method should be used for tags of
+ * type [.TYPE_UNSIGNED_LONG]. This method will fail if:
+ *
+ * * The component type of this tag is not [.TYPE_UNSIGNED_LONG].
+ * * The value overflows.
+ * * The component count in the definition for this tag is not 1.
+ *
+ */
+ fun setValue(value : Long) : Boolean {
+ return setValue(longArrayOf(value))
+ }
+
+ /**
+ * Sets Rational values into this tag. This method should be used for tags
+ * of type [.TYPE_UNSIGNED_RATIONAL], or [.TYPE_RATIONAL]. This
+ * method will fail if:
+ *
+ * * The component type of this tag is not [.TYPE_UNSIGNED_RATIONAL]
+ * or [.TYPE_RATIONAL].
+ * * The value overflows.
+ * * The value.length does NOT match the component count in the definition
+ * for this tag.
+ *
+ *
+ * @see Rational
+ */
+ fun setValue(value : Array) : Boolean {
+ if(checkBadComponentCount(value.size)) {
+ return false
+ }
+ if(dataType != TYPE_UNSIGNED_RATIONAL && dataType != TYPE_RATIONAL) {
+ return false
+ }
+ if(dataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
+ return false
+ } else if(dataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
+ return false
+ }
+
+ mValue = value
+ componentCount = value.size
+ return true
+ }
+
+ /**
+ * Sets a Rational value into this tag. This method should be used for tags
+ * of type [.TYPE_UNSIGNED_RATIONAL], or [.TYPE_RATIONAL]. This
+ * method will fail if:
+ *
+ * * The component type of this tag is not [.TYPE_UNSIGNED_RATIONAL]
+ * or [.TYPE_RATIONAL].
+ * * The value overflows.
+ * * The component count in the definition for this tag is not 1.
+ *
+ *
+ * @see Rational
+ */
+ fun setValue(value : Rational) : Boolean =
+ setValue(arrayOf(value))
+
+ /**
+ * Sets byte values into this tag. This method should be used for tags of
+ * type [.TYPE_UNSIGNED_BYTE] or [.TYPE_UNDEFINED]. This method
+ * will fail if:
+ *
+ * * The component type of this tag is not [.TYPE_UNSIGNED_BYTE] or
+ * [.TYPE_UNDEFINED] .
+ * * The length does NOT match the component count in the definition for
+ * this tag.
+ *
+ */
+ @JvmOverloads
+ fun setValue(value : ByteArray, offset : Int = 0, length : Int = value.size) : Boolean {
+ if(checkBadComponentCount(length)) {
+ return false
+ }
+ if(dataType != TYPE_UNSIGNED_BYTE && dataType != TYPE_UNDEFINED) {
+ return false
+ }
+ componentCount = length
+ mValue = ByteArray(length).also{
+ System.arraycopy(value, offset, it, 0, length)
+ }
+ return true
+ }
+
+ /**
+ * Sets byte value into this tag. This method should be used for tags of
+ * type [.TYPE_UNSIGNED_BYTE] or [.TYPE_UNDEFINED]. This method
+ * will fail if:
+ *
+ * * The component type of this tag is not [.TYPE_UNSIGNED_BYTE] or
+ * [.TYPE_UNDEFINED] .
+ * * The component count in the definition for this tag is not 1.
+ *
+ */
+ fun setValue(value : Byte) : Boolean =
+ setValue(byteArrayOf(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.
+ *
+ */
+ inline fun setValueAny(obj : T) : Boolean {
+ when(obj) {
+ null -> return false
+
+ is String -> return setValue(obj)
+ is ByteArray -> return setValue(obj)
+ is IntArray -> return setValue(obj)
+ is LongArray -> return setValue(obj)
+ is Rational -> return setValue(obj)
+ is Byte -> return setValue(obj.toByte())
+ is Short -> return setValue(obj.toInt() and 0x0ffff)
+ is Int -> return setValue(obj.toInt())
+ is Long -> return setValue(obj.toLong())
+
+ else ->{
+
+ @Suppress("UNCHECKED_CAST")
+ val ra = obj as? Array
+ if(ra != null) return setValue( ra )
+
+ // Nulls in this array are treated as zeroes.
+ @Suppress("UNCHECKED_CAST")
+ val sa = obj as? Array
+ if( sa != null) return setValue(IntArray(sa.size){ (sa[it]?.toInt() ?: 0) and 0xffff})
+
+ // Nulls in this array are treated as zeroes.
+ @Suppress("UNCHECKED_CAST")
+ val ia = obj as? Array
+ if( ia != null) return setValue(IntArray(ia.size){ ia[it] ?: 0 })
+
+ // Nulls in this array are treated as zeroes.
+ @Suppress("UNCHECKED_CAST")
+ val la = obj as? Array
+ if( la != null) return setValue(LongArray(la.size){ la[it] ?: 0L })
+
+ // Nulls in this array are treated as zeroes.
+ @Suppress("UNCHECKED_CAST")
+ val ba = obj as? Array
+ if( ba != null) return setValue(ByteArray(ba.size){ ba[it] ?: 0 })
+
+ 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 [.setValue]. This
+ * method will fail if the data type is not [.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
+ */
+ fun setValueTime(time : Long) : Boolean {
+ // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
+ synchronized(TIME_FORMAT) {
+ return setValue(TIME_FORMAT.format(Date(time)))
+ }
+ }
+
+ /**
+ * Sets a string value into this tag. This method should be used for tags of
+ * type [.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 [.TYPE_ASCII] or [.TYPE_UNDEFINED].
+ * * The length of the string is not equal to (component count -1) or
+ * (component count) in the definition for this tag.
+ *
+ */
+ fun setValue(value : String) : Boolean {
+ if(dataType != TYPE_ASCII && dataType != TYPE_UNDEFINED) {
+ return false
+ }
+
+ val buf = value.toByteArray(US_ASCII)
+
+ val finalBuf = when {
+ buf.isNotEmpty() -> when {
+ buf[buf.size - 1].toInt() == 0 || dataType == TYPE_UNDEFINED -> buf
+ else -> buf.copyOf(buf.size + 1)
+ }
+ dataType == TYPE_ASCII && componentCount == 1 -> byteArrayOf(0)
+ else -> buf
+ }
+ val count = finalBuf.size
+ if(checkBadComponentCount(count)) {
+ return false
+ }
+ componentCount = count
+ mValue = finalBuf
+ return true
+ }
+
+ private fun checkBadComponentCount(count : Int) : Boolean {
+ return mHasDefinedDefaultComponentCount && componentCount != count
+ }
+
+ /**
+ * Gets the value as a String. This method should be used for tags of type
+ * [.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.
+ */
+ fun getValueAsString(defaultValue : String) : String {
+ return valueAsString ?: defaultValue
+ }
+
+ /**
+ * 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
+ * [.TYPE_UNDEFINED] or [.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.
+ */
+ fun getValueAsByte(defaultValue : Byte) : Byte {
+ val array = valueAsBytes
+ return if(array?.isNotEmpty()==true) array[0] else defaultValue
+ }
+
+ /**
+ * 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
+ * [.TYPE_RATIONAL] or [.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.
+ */
+ fun getValueAsRational(defaultValue : Long) : Rational =
+ getValueAsRational( Rational(defaultValue, 1))
+
+ /**
+ * 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
+ * [.TYPE_RATIONAL] or [.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.
+ */
+ private fun getValueAsRational(defaultValue : Rational) : Rational {
+ val array = valueAsRationals
+ return if(array?.isNotEmpty()==true) array[0] else defaultValue
+ }
+
+ /**
+ * 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
+ * [.TYPE_UNSIGNED_SHORT], [.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.
+ */
+ fun getValueAsInt(defaultValue : Int) : Int {
+ val array = valueAsInts
+ return if(array?.isNotEmpty()==true) array[0] else defaultValue
+ }
+
+ /**
+ * 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 [.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.
+ */
+ fun getValueAsLong(defaultValue : Long) : Long {
+ val array = valueAsLongs
+ return if(array?.isNotEmpty()==true) array[0] else defaultValue
+ }
+
+ /**
+ * Gets the tag's value or null if none exists.
+ */
+ fun getValue() : Any? {
+ 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.
+ */
+ fun forceGetValueAsLong(defaultValue : Long) : Long {
+ when(val v = mValue) {
+ is LongArray -> if(v.isNotEmpty()) return v[0]
+ is ByteArray -> if(v.isNotEmpty()) return v[0].toLong()
+
+ else -> {
+ val r = valueAsRationals
+ if(r?.isNotEmpty() == true && r[0].denominator != 0L) {
+ return r[0].toDouble().toLong()
+ }
+ }
+ }
+ return defaultValue
+ }
+
+ /**
+ * Gets the value for type [.TYPE_ASCII], [.TYPE_LONG],
+ * [.TYPE_UNDEFINED], [.TYPE_UNSIGNED_BYTE],
+ * [.TYPE_UNSIGNED_LONG], or [.TYPE_UNSIGNED_SHORT]. For
+ * [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL], call
+ * [.getRational] instead.
+ *
+ * @throws IllegalArgumentException if the data type is
+ * [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
+ */
+ fun getValueAt(index : Int) : Long {
+ return when(val v = mValue) {
+ is LongArray -> v[index]
+ is ByteArray -> v[index].toLong()
+ else -> error(
+ "Cannot get integer value from ${convertTypeToString(dataType)}"
+ )
+ }
+ }
+
+ /**
+ * Gets the [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL] data.
+ *
+ * @throws IllegalArgumentException If the type is NOT
+ * [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
+ */
+ fun getRational(index : Int) : Rational? {
+ require(! (dataType != TYPE_RATIONAL && dataType != TYPE_UNSIGNED_RATIONAL)) {
+ "Cannot get RATIONAL value from " + convertTypeToString(dataType)
+ }
+ return valueAsRationals?.get(index)
+ }
+
+ /**
+ * Gets the [.TYPE_UNDEFINED] or [.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
+ * [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE].
+ */
+ @JvmOverloads
+ fun getBytes(buf : ByteArray, offset : Int = 0, length : Int = buf.size) {
+ require(! (dataType != TYPE_UNDEFINED && dataType != TYPE_UNSIGNED_BYTE)) {
+ "Cannot get BYTE value from " + convertTypeToString(
+ dataType
+ )
+ }
+ System.arraycopy(
+ mValue !!,
+ 0,
+ buf,
+ offset,
+ if(length > componentCount) componentCount else length
+ )
+ }
+
+ var hasDefinedCount : Boolean
+ get() = mHasDefinedDefaultComponentCount
+ set(value){
+ mHasDefinedDefaultComponentCount = value
+ }
+
+ private fun checkOverflowForUnsignedShort(value : IntArray) : Boolean {
+ for(v in value) {
+ if(v > UNSIGNED_SHORT_MAX || v < 0) {
+ return true
+ }
+ }
+ return false
+ }
+
+ private fun checkOverflowForUnsignedLong(value : LongArray) : Boolean {
+ for(v in value) {
+ if(v < 0 || v > UNSIGNED_LONG_MAX) {
+ return true
+ }
+ }
+ return false
+ }
+
+ private fun checkOverflowForUnsignedLong(value : IntArray) : Boolean {
+ for(v in value) {
+ if(v < 0) {
+ return true
+ }
+ }
+ return false
+ }
+
+ private fun checkOverflowForUnsignedRational(value : Array) : Boolean {
+ for(v in value) {
+ if(v.numerator < 0 || v.denominator < 0 || v.numerator > UNSIGNED_LONG_MAX || v.denominator > UNSIGNED_LONG_MAX) {
+ return true
+ }
+ }
+ return false
+ }
+
+ private fun checkOverflowForRational(value : Array) : Boolean {
+ for(v in value) {
+ if(v.numerator < LONG_MIN || v.denominator < LONG_MIN || v.numerator > LONG_MAX || v.denominator > LONG_MAX) {
+ return true
+ }
+ }
+ return false
+ }
+
+ override fun hashCode() : Int {
+ var result = tagId.toInt()
+ result = 31 * result + dataType.toInt()
+ result = 31 * result + ifd
+ result = 31 * result + componentCount
+ result = 31 * result + offset
+ result = 31 * result + mHasDefinedDefaultComponentCount.hashCode()
+ result = 31 * result + (mValue?.hashCode() ?: 0)
+ return result
+ }
+
+ override fun equals(other : Any?) : Boolean {
+ if(other !is ExifTag) return false
+
+ if(other.tagId != this.tagId
+ || other.componentCount != this.componentCount
+ || other.dataType != this.dataType
+ ) {
+ return false
+ }
+
+ val va = this.mValue
+ val vb = other.mValue
+
+ return when {
+
+ va == null -> vb == null
+
+ vb == null -> false
+
+ va is LongArray -> when(vb) {
+ is LongArray -> Arrays.equals(va, vb)
+ else -> false
+ }
+
+ va is ByteArray -> when(vb) {
+ is ByteArray -> Arrays.equals(va, vb)
+ else -> false
+ }
+
+ va is Array<*> && va.isArrayOf() -> when {
+ vb is Array<*> && vb.isArrayOf() -> Arrays.equals(va, vb)
+ else -> false
+ }
+
+ else -> va == vb
+ }
+ }
+
+ override fun toString() : String {
+ val strTagId = String.format("%04X", tagId)
+ return "tag id: $strTagId\nifd id: $ifd\ntype: ${convertTypeToString(dataType)}\ncount: $componentCount\noffset: $offset\nvalue: ${forceGetValueAsString()}\n"
+ }
+
+ /**
+ * Gets a string representation of the value.
+ */
+ private fun forceGetValueAsString() : String {
+ when(val v = mValue) {
+
+ null -> return ""
+
+ is ByteArray -> return when(dataType) {
+ TYPE_ASCII -> String(v, US_ASCII)
+ else -> Arrays.toString(v)
+ }
+
+ is LongArray -> return when {
+ v.size == 1 -> v[0].toString()
+ else -> Arrays.toString(v)
+ }
+
+ is Array<*> -> return when {
+ v.size == 1 -> v[0]?.toString() ?: ""
+ else -> Arrays.toString(v)
+ }
+
+ else -> return v.toString()
+ }
+ }
+
+
+
+ companion object {
+ /**
+ * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
+ */
+ const val TYPE_UNSIGNED_BYTE : Short = 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.
+ */
+ const val TYPE_ASCII : Short = 2
+ /**
+ * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer
+ */
+ const val TYPE_UNSIGNED_SHORT : Short = 3
+ /**
+ * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer
+ */
+ const val TYPE_UNSIGNED_LONG : Short = 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.
+ */
+ const val TYPE_UNSIGNED_RATIONAL : Short = 5
+ /**
+ * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
+ * value depending on the field definition.
+ */
+ const val TYPE_UNDEFINED : Short = 7
+ /**
+ * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer
+ * (2's complement notation).
+ */
+ const val TYPE_LONG : Short = 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.
+ */
+ const val TYPE_RATIONAL : Short = 10
+ internal const val SIZE_UNDEFINED = 0
+ private val TYPE_TO_SIZE_MAP = IntArray(11)
+ private const val UNSIGNED_SHORT_MAX = 65535
+ private const val UNSIGNED_LONG_MAX = 4294967295L
+ private const val LONG_MAX = Integer.MAX_VALUE.toLong()
+ private const val LONG_MIN = Integer.MIN_VALUE.toLong()
+
+ init {
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE.toInt()] = 1
+ TYPE_TO_SIZE_MAP[TYPE_ASCII.toInt()] = 1
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT.toInt()] = 2
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG.toInt()] = 4
+ TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL.toInt()] = 8
+ TYPE_TO_SIZE_MAP[TYPE_UNDEFINED.toInt()] = 1
+ TYPE_TO_SIZE_MAP[TYPE_LONG.toInt()] = 4
+ TYPE_TO_SIZE_MAP[TYPE_RATIONAL.toInt()] = 8
+ }
+
+ private val TIME_FORMAT = SimpleDateFormat("yyyy:MM:dd kk:mm:ss", Locale.ENGLISH)
+ private val US_ASCII = Charset.forName("US-ASCII")
+
+ /**
+ * Returns true if the given IFD is a valid IFD.
+ */
+ fun isValidIfd(ifdId : Int) : Boolean =
+ when(ifdId) {
+ IfdId.TYPE_IFD_0,
+ IfdId.TYPE_IFD_1,
+ IfdId.TYPE_IFD_EXIF,
+ IfdId.TYPE_IFD_INTEROPERABILITY,
+ IfdId.TYPE_IFD_GPS -> true
+ else -> false
+ }
+
+ /**
+ * Returns true if a given type is a valid tag type.
+ */
+ fun isValidType(type : Short) : Boolean =
+ when(type) {
+ TYPE_UNSIGNED_BYTE,
+ TYPE_ASCII,
+ TYPE_UNSIGNED_SHORT,
+ TYPE_UNSIGNED_LONG,
+ TYPE_UNSIGNED_RATIONAL,
+ TYPE_UNDEFINED,
+ TYPE_LONG,
+ TYPE_RATIONAL -> true
+ else -> false
+ }
+
+ /**
+ * 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
+ */
+ fun getElementSize(type : Short) : Int = TYPE_TO_SIZE_MAP[type.toInt()]
+
+ private fun convertTypeToString(type : Short) : String =
+ when(type) {
+ TYPE_UNSIGNED_BYTE -> "UNSIGNED_BYTE"
+ TYPE_ASCII -> "ASCII"
+ TYPE_UNSIGNED_SHORT -> "UNSIGNED_SHORT"
+ TYPE_UNSIGNED_LONG -> "UNSIGNED_LONG"
+ TYPE_UNSIGNED_RATIONAL -> "UNSIGNED_RATIONAL"
+ TYPE_UNDEFINED -> "UNDEFINED"
+ TYPE_LONG -> "LONG"
+ TYPE_RATIONAL -> "RATIONAL"
+ else -> ""
+ }
+ }
+
+}
+/**
+ * Equivalent to setValue(value, 0, value.length).
+ */
+/**
+ * Equivalent to getBytes(buffer, 0, buffer.length).
+ */
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
deleted file mode 100644
index aaae7b3c..00000000
--- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifUtil.java
+++ /dev/null
@@ -1,33 +0,0 @@
-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/ExifUtil.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifUtil.kt
new file mode 100644
index 00000000..a4fa589a
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifUtil.kt
@@ -0,0 +1,32 @@
+package it.sephiroth.android.library.exif2
+
+import java.text.DecimalFormat
+
+/**
+ * Created by alessandro on 20/04/14.
+ */
+object ExifUtil {
+
+ private val formatter = DecimalFormat.getInstance()
+
+ fun processLensSpecifications(values : Array) : String {
+ val min_focal = values[0]
+ val max_focal = values[1]
+ val min_f = values[2]
+ val max_f = values[3]
+
+ formatter.maximumFractionDigits = 1
+
+ val sb = 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
deleted file mode 100644
index 876c67da..00000000
--- a/exif/src/main/java/it/sephiroth/android/library/exif2/IfdData.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package 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/IfdData.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/IfdData.kt
new file mode 100644
index 00000000..fc78070a
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/IfdData.kt
@@ -0,0 +1,119 @@
+/*
+ * 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
+
+
+/**
+ * The constants of the IFD ID defined in EXIF spec.
+ */
+object IfdId {
+ const val TYPE_IFD_0 = 0
+ const val TYPE_IFD_1 = 1
+ const val TYPE_IFD_EXIF = 2
+ const val TYPE_IFD_INTEROPERABILITY = 3
+ const val TYPE_IFD_GPS = 4
+ /* This is used in ExifData to allocate enough IfdData */
+ const val TYPE_IFD_COUNT = 5
+}
+
+
+// This class stores all the tags in an IFD.
+// an IfdData with given IFD ID.
+internal class IfdData(
+ // the ID of this IFD.
+ val id : Int
+) {
+
+ companion object {
+ val ifds = intArrayOf(
+ IfdId.TYPE_IFD_0,
+ IfdId.TYPE_IFD_1,
+ IfdId.TYPE_IFD_EXIF,
+ IfdId.TYPE_IFD_INTEROPERABILITY,
+ IfdId.TYPE_IFD_GPS
+ )
+ }
+
+ private val mExifTags = HashMap()
+
+ // the offset of next IFD.
+ var offsetToNextIfd = 0
+
+ // the tags count in the IFD.
+ val tagCount : Int
+ get() = mExifTags.size
+
+ // a array the contains all [ExifTag] in this IFD.
+ val allTags : Array
+ get() = mExifTags.values.toTypedArray()
+
+ // checkCollision
+ fun contains(tagId : Short) : Boolean {
+ return mExifTags[tagId] != null
+ }
+
+ // the [ExifTag] with given tag id.
+ // null if there is no such tag.
+ fun getTag(tagId : Short) : ExifTag? {
+ return mExifTags[tagId]
+ }
+
+ // Adds or replaces a [ExifTag].
+ fun setTag(tag : ExifTag) : ExifTag? {
+ tag.ifd = id
+ return mExifTags.put(tag.tagId, tag)
+ }
+
+ // Removes the tag of the given ID
+ fun removeTag(tagId : Short) {
+ mExifTags.remove(tagId)
+ }
+
+ /**
+ * Returns true if all tags in this two IFDs are equal. Note that tags of
+ * IFDs offset or thumbnail offset will be ignored.
+ */
+ override fun equals(other : Any?) : Boolean {
+ if(other === null) return false
+ if(other === this) return true
+ if(other is IfdData) {
+ if(other.id == id && other.tagCount == tagCount) {
+ val tags = other.allTags
+ for(tag in tags) {
+ if(ExifInterface.isOffsetTag(tag.tagId)) {
+ continue
+ }
+ val tag2 = mExifTags[tag.tagId]
+ if(tag != tag2) {
+ return false
+ }
+ }
+ return true
+ }
+ }
+ return false
+ }
+
+ override fun hashCode() : Int {
+ var result = id
+ result = 31 * result + mExifTags.hashCode()
+ result = 31 * result + offsetToNextIfd
+ return result
+ }
+}
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
deleted file mode 100644
index ea5833e5..00000000
--- a/exif/src/main/java/it/sephiroth/android/library/exif2/JpegHeader.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package 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/JpegHeader.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/JpegHeader.kt
new file mode 100644
index 00000000..60f3a5ea
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/JpegHeader.kt
@@ -0,0 +1,123 @@
+/*
+ * 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
+
+@Suppress("unused", "MemberVisibilityCanBePrivate")
+object JpegHeader {
+ /** Start Of Image */
+ const val TAG_SOI = 0xD8
+
+ /** JFIF (JPEG File Interchange Format) */
+ const val TAG_M_JFIF = 0xE0
+
+ /** EXIF table */
+ const val TAG_M_EXIF = 0xE1
+
+ /** Product Information Comment */
+ const val TAG_M_COM = 0xFE
+
+ /** Quantization Table */
+ const val TAG_M_DQT = 0xDB
+
+ /** Start of frame */
+ const val TAG_M_SOF0 = 0xC0
+ const val TAG_M_SOF1 = 0xC1
+ const val TAG_M_SOF2 = 0xC2
+ const val TAG_M_SOF3 = 0xC3
+ const val TAG_M_DHT = 0xC4
+ const val TAG_M_SOF5 = 0xC5
+ const val TAG_M_SOF6 = 0xC6
+ const val TAG_M_SOF7 = 0xC7
+ const val TAG_M_SOF9 = 0xC9
+ const val TAG_M_SOF10 = 0xCA
+ const val TAG_M_SOF11 = 0xCB
+ const val TAG_M_SOF13 = 0xCD
+ const val TAG_M_SOF14 = 0xCE
+ const val TAG_M_SOF15 = 0xCF
+
+ /** Start Of Scan */
+ const val TAG_M_SOS = 0xDA
+
+ /** End of Image */
+ const val TAG_M_EOI = 0xD9
+
+ const val TAG_M_IPTC = 0xED
+
+ /** default JFIF Header bytes */
+ val JFIF_HEADER = byteArrayOf(
+ 0xff.toByte(),
+ TAG_M_JFIF.toByte(),
+ 0x00,
+ 0x10,
+ 'J'.toByte(),
+ 'F'.toByte(),
+ 'I'.toByte(),
+ 'F'.toByte(),
+ 0x00,
+ 0x01,
+ 0x01,
+ 0x01,
+ 0x01,
+ 0x2C,
+ 0x01,
+ 0x2C,
+ 0x00,
+ 0x00
+ )
+
+ const val SOI = 0xFFD8.toShort()
+ const val M_EXIF = 0xFFE1.toShort()
+ const val M_JFIF = 0xFFE0.toShort()
+ const val M_EOI = 0xFFD9.toShort()
+
+ /**
+ * SOF (start of frame). All value between M_SOF0 and SOF15 is SOF marker except for M_DHT, JPG,
+ * and DAC marker.
+ */
+ const val M_SOF0 = 0xFFC0.toShort()
+ const val M_SOF1 = 0xFFC1.toShort()
+ const val M_SOF2 = 0xFFC2.toShort()
+ const val M_SOF3 = 0xFFC3.toShort()
+ const val M_SOF5 = 0xFFC5.toShort()
+ const val M_SOF6 = 0xFFC6.toShort()
+ const val M_SOF7 = 0xFFC7.toShort()
+ const val M_SOF9 = 0xFFC9.toShort()
+ const val M_SOF10 = 0xFFCA.toShort()
+ const val M_SOF11 = 0xFFCB.toShort()
+ const val M_SOF13 = 0xFFCD.toShort()
+ const val M_SOF14 = 0xFFCE.toShort()
+ const val M_SOF15 = 0xFFCF.toShort()
+ const val M_DHT = 0xFFC4.toShort()
+ const val JPG = 0xFFC8.toShort()
+ const val DAC = 0xFFCC.toShort()
+
+ /** Define quantization table */
+ const val M_DQT = 0xFFDB.toShort()
+
+ /** IPTC marker */
+ const val M_IPTC = 0xFFED.toShort()
+
+ /** Start of scan (begins compressed data */
+ const val M_SOS = 0xFFDA.toShort()
+
+ /** Comment section * */
+ const val M_COM = 0xFFFE.toShort() // Comment section
+
+ fun isSofMarker(marker : Short) : Boolean {
+ 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
deleted file mode 100644
index 3a461952..00000000
--- a/exif/src/main/java/it/sephiroth/android/library/exif2/OrderedDataOutputStream.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package 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/OrderedDataOutputStream.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/OrderedDataOutputStream.kt
new file mode 100644
index 00000000..817c9246
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/OrderedDataOutputStream.kt
@@ -0,0 +1,55 @@
+/*
+ * 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
+
+internal class OrderedDataOutputStream(out : OutputStream) : FilterOutputStream(out) {
+ private val mByteBuffer = ByteBuffer.allocate(4)
+
+ fun setByteOrder(order : ByteOrder) : OrderedDataOutputStream {
+ mByteBuffer.order(order)
+ return this
+ }
+
+ @Throws(IOException::class)
+ fun writeShort(value : Short) : OrderedDataOutputStream {
+ mByteBuffer.rewind()
+ mByteBuffer.putShort(value)
+ out.write(mByteBuffer.array(), 0, 2)
+ return this
+ }
+
+ @Throws(IOException::class)
+ fun writeRational(rational : Rational) : OrderedDataOutputStream {
+ writeInt(rational.numerator.toInt())
+ writeInt(rational.denominator.toInt())
+ return this
+ }
+
+ @Throws(IOException::class)
+ fun writeInt(value : Int) : OrderedDataOutputStream {
+ 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
deleted file mode 100644
index fb159280..00000000
--- a/exif/src/main/java/it/sephiroth/android/library/exif2/Rational.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package 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/exif/src/main/java/it/sephiroth/android/library/exif2/Rational.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/Rational.kt
new file mode 100644
index 00000000..f32039b0
--- /dev/null
+++ b/exif/src/main/java/it/sephiroth/android/library/exif2/Rational.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+class Rational(
+ val numerator : Long=0,//the numerator of the rational.
+ val denominator : Long =1//the denominator of the rational
+
+) {
+ // copy from a Rational.
+ constructor(r : Rational) :this(
+ numerator = r.numerator,
+ denominator = r.denominator
+ )
+
+ // Gets the rational value as type double.
+ // Will cause a divide-by-zero error if the denominator is 0.
+ fun toDouble() : Double = numerator.toDouble() / denominator.toDouble()
+
+ override fun toString() : String = "$numerator/$denominator"
+
+ override fun equals(other : Any?) : Boolean {
+ return when {
+ other === null -> false
+ other === this -> true
+ other is Rational -> numerator == other.numerator && denominator == other.denominator
+ else -> false
+ }
+ }
+
+
+ override fun hashCode() : Int =
+ 31 * numerator.hashCode() + denominator.hashCode()
+}