diff --git a/apng_android/build.gradle b/apng_android/build.gradle index 8e8f149a..c2029027 100644 --- a/apng_android/build.gradle +++ b/apng_android/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion target_sdk_version + compileSdkVersion compile_sdk_version compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/app/build.gradle b/app/build.gradle index 7971f168..7c6b43a0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,7 +6,7 @@ apply plugin: 'kotlin-kapt' android { - compileSdkVersion target_sdk_version + compileSdkVersion compile_sdk_version // exoPlayer 2.9.0 以降は Java 8 compiler support を要求する // https://github.com/google/ExoPlayer/blob/release-v2/RELEASENOTES.md diff --git a/build.gradle b/build.gradle index 42653af5..2938f089 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ buildscript { ext.min_sdk_version = 21 ext.target_sdk_version = 30 + ext.compile_sdk_version = 30 ext.appcompat_version='1.2.0' ext.kotlin_version = '1.4.30' diff --git a/colorpicker/build.gradle b/colorpicker/build.gradle index 9273f601..8eb37549 100644 --- a/colorpicker/build.gradle +++ b/colorpicker/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'com.android.library' android { - compileSdkVersion target_sdk_version + compileSdkVersion compile_sdk_version compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/emoji/build.gradle b/emoji/build.gradle index 02782dd6..303924c0 100644 --- a/emoji/build.gradle +++ b/emoji/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion target_sdk_version + compileSdkVersion compile_sdk_version compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/exif/.gitignore b/exif/.gitignore deleted file mode 100644 index 3543521e..00000000 --- a/exif/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/exif/build.gradle b/exif/build.gradle deleted file mode 100644 index 58293e3d..00000000 --- a/exif/build.gradle +++ /dev/null @@ -1,52 +0,0 @@ -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' -apply plugin: 'de.mobilej.unmock' - -android { - compileSdkVersion target_sdk_version - - defaultConfig { - targetSdkVersion target_sdk_version - minSdkVersion min_sdk_version - - versionCode 1 - versionName "1.0" - - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - -} - -unMock { - keepStartingWith "android.util." - keepStartingWith "com.android.internal.util." - - keep "android.graphics.Bitmap" - keep "android.graphics.BitmapFactory" -} - -dependencies { - - testImplementation "junit:junit:$junit_version" - androidTestImplementation 'androidx.test:runner:1.3.0-rc03' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0-rc03' - - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'commons-io:commons-io:2.6' - implementation 'androidx.annotation:annotation:1.1.0' - implementation "androidx.core:core-ktx:1.3.1" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - unmock 'org.robolectric:android-all:5.0.0_r2-robolectric-0' -} - -repositories { - mavenCentral() -} diff --git a/exif/proguard-rules.pro b/exif/proguard-rules.pro deleted file mode 100644 index 90e79ae6..00000000 --- a/exif/proguard-rules.pro +++ /dev/null @@ -1,26 +0,0 @@ -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in C:\android\sdk/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the proguardFiles -# directive in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile --dontobfuscate diff --git a/exif/src/main/AndroidManifest.xml b/exif/src/main/AndroidManifest.xml deleted file mode 100644 index fbb83707..00000000 --- a/exif/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifData.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifData.kt deleted file mode 100644 index 32c5a1f3..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifData.kt +++ /dev/null @@ -1,298 +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.graphics.Bitmap -import android.graphics.BitmapFactory -import android.util.Log -import it.sephiroth.android.library.exif2.utils.notEmpty - -import java.io.UnsupportedEncodingException -import java.nio.ByteOrder -import java.nio.charset.Charset -import java.util.ArrayList -import java.util.Arrays - -/** - * This class stores the EXIF header in IFDs according to the JPEG - * specification. It is the result produced by [ExifReader]. - * - * @see ExifReader - * - * @see IfdData - */ -@Suppress("unused") -internal class ExifData( - val byteOrder : ByteOrder = ExifInterface.DEFAULT_BYTE_ORDER, - val sections : List = ArrayList(), - val mUncompressedDataPosition : Int = 0, - val qualityGuess : Int = 0, - val jpegProcess : Short = 0 -) { - - private var imageLength = - 1 - private var imageWidth = - 1 - - private val mIfdDatas = arrayOfNulls(IfdData.TYPE_IFD_COUNT) - - // the compressed thumbnail. - // null if there is no compressed thumbnail. - var compressedThumbnail : ByteArray? = null - - private val mStripBytes = ArrayList() - - val stripList : List? - get() = mStripBytes.filterNotNull().notEmpty() - - // Decodes the user comment tag into string as specified in the EXIF standard. - // Returns null if decoding failed. - val userComment : String? - get() { - - val ifdData = mIfdDatas[IfdData.TYPE_IFD_0] - ?: return null - - val tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)) - ?: return null - - if(tag.componentCount < 8) - return null - - return try { - - val buf = ByteArray(tag.componentCount) - tag.getBytes(buf) - - val code = ByteArray(8) - System.arraycopy(buf, 0, code, 0, 8) - - val charset = when { - code.contentEquals(USER_COMMENT_ASCII) -> Charsets.US_ASCII - code.contentEquals(USER_COMMENT_JIS) -> eucJp - code.contentEquals(USER_COMMENT_UNICODE) -> Charsets.UTF_16 - else -> null - } - if(charset == null) null else String(buf, 8, buf.size - 8, charset) - } catch(e : UnsupportedEncodingException) { - Log.w(TAG, "Failed to decode the user comment") - null - } - } - - // list of all [ExifTag]s in the ExifData - // or null if there are none. - val allTags : List - get() = ArrayList() - .apply { mIfdDatas.forEach { if(it != null) addAll(it.allTagsCollection) } } - - val imageSize : IntArray - get() = intArrayOf(imageWidth, imageLength) - - val thumbnailBytes : ByteArray? - get() = when { - compressedThumbnail != null -> compressedThumbnail - stripList != null -> null // TODO: implement this - else -> null - } - - val thumbnailBitmap : Bitmap? - get() { - val compressedThumbnail = this.compressedThumbnail - if(compressedThumbnail != null) { - return BitmapFactory - .decodeByteArray(compressedThumbnail, 0, compressedThumbnail.size) - } - val stripList = this.stripList - if(stripList != null) { - // TODO: decoding uncompressed thumbnail is not implemented. - return null - } - - return null - } - - /** - * Adds an uncompressed strip. - */ - fun setStripBytes(index : Int, strip : ByteArray) { - if(index in mStripBytes.indices) { - mStripBytes[index] = strip - } else { - for(i in mStripBytes.size until index) { - mStripBytes.add(null) - } - mStripBytes.add(strip) - } - } - - /** - * Returns the [IfdData] object corresponding to a given IFD or - * generates one if none exist. - */ - private fun prepareIfdData(ifdId : Int) : IfdData { - var ifdData = mIfdDatas[ifdId] - if(ifdData == null) { - ifdData = IfdData(ifdId) - mIfdDatas[ifdId] = ifdData - } - return ifdData - } - - /** - * Adds IFD data. If IFD data of the same type already exists, it will be - * replaced by the new data. - */ - fun addIfdData(data : IfdData) { - mIfdDatas[data.id] = data - } - - /** - * Returns the tag with a given TID in the given IFD if the tag exists. - * Otherwise returns null. - */ - fun getTag(tag : Short, ifd : Int) : ExifTag? = - mIfdDatas[ifd]?.getTag(tag) - - /** - * Adds the given ExifTag to its default IFD and returns an existing ExifTag - * with the same TID or null if none exist. - */ - fun addTag(tag : ExifTag?) : ExifTag? = - when(tag) { - null -> null - else -> addTag(tag, tag.ifd) - } - - /** - * Adds the given ExifTag to the given IFD and returns an existing ExifTag - * with the same TID or null if none exist. - */ - private fun addTag(tag : ExifTag?, ifdId : Int) : ExifTag? = - when { - tag == null -> null - ! ExifTag.isValidIfd(ifdId) -> null - else -> prepareIfdData(ifdId).setTag(tag) - } - - /** - * Removes the tag with a given TID and IFD. - */ - fun removeTag(tagId : Short, ifdId : Int) { - mIfdDatas[ifdId]?.removeTag(tagId) - } - - /** - * Removes the thumbnail and its related tags. IFD1 will be removed. - */ - fun removeThumbnailData() { - clearThumbnailAndStrips() - mIfdDatas[IfdData.TYPE_IFD_1] = null - } - - fun clearThumbnailAndStrips() { - compressedThumbnail = null - mStripBytes.clear() - } - - /** - * Returns a list of all [ExifTag]s in a given IFD or null if there - * are none. - */ - fun getAllTagsForIfd(ifd : Int) : List? = - mIfdDatas[ifd]?.allTagsCollection?.notEmpty()?.toList() - - // Returns a list of all [ExifTag]s with a given TID - // or null if there are none. - fun getAllTagsForTagId(tag : Short) : List? = - ArrayList() - .apply { mIfdDatas.forEach { it?.getTag(tag)?.let { t -> add(t) } } } - .notEmpty() - - 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 IfdData.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) = when { - ! ExifTag.isValidIfd(ifdId) -> null - else -> mIfdDatas[ifdId] - } - - 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()) - 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) - - private val eucJp = Charset.forName("EUC-JP") - - } -} 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 deleted file mode 100644 index 74321cb9..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.kt +++ /dev/null @@ -1,2614 +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.content.Context -import android.graphics.Bitmap -import android.os.Build -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 -import kotlin.math.ln - -/** - * 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") -class ExifInterface { - - private val mGPSTimeStampCalendar : Calendar by lazy { - Calendar.getInstance(TimeZone.getTimeZone("UTC")) - } - - val tagInfo : SparseIntArray by lazy { - SparseIntArray().initTagInfo() - } - - private var mData = ExifData() - - /** - * 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 - - /** - * Returns the JPEG quality used to generate the image - * or 0 if not found - * - * @return qualityGuess - */ - val qualityGuess : Int - get() = mData.qualityGuess - - /** - * 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() = mData.thumbnailBytes - - /** - * Returns the thumbnail from IFD1 as a bitmap, or null if none exists. - * - * @return the thumbnail as a bitmap. - */ - val thumbnailBitmap : Bitmap? - get() = mData.thumbnailBitmap - - /** - * 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.compressedThumbnail != null - - /** - * 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) - - return when { - - latitude == null - || longitude == null - || latitudeRef == null - || longitudeRef == null - || latitude.size < 3 - || longitude.size < 3 -> null - - else -> doubleArrayOf( - convertLatOrLongToDouble(latitude, latitudeRef), - convertLatOrLongToDouble(longitude, longitudeRef) - ) - } - } - - /** - * 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 when { - 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 when { - null == longitude || null == longitudeRef -> null - else -> convertRationalLatLonToString(longitude, longitudeRef) - } - } - - val altitude : Double? // may null - get() = when(val gpsAltitude = getTagRationalValue(TAG_GPS_ALTITUDE)) { - null -> null - - else -> { - val seaLevel = when(getTagByteValue(TAG_GPS_ALTITUDE_REF)) { - 1.toByte() -> - 1 - else -> 1 - } - gpsAltitude.toDouble() * seaLevel - } - } - - /** - * Return the aperture size, if present, 0 if missing - */ - val apertureSize : Double - get() { - var v = getTagRationalValue(TAG_F_NUMBER)?.toDouble() - if(v != null && v > 0.0) return v - - v = getTagRationalValue(TAG_APERTURE_VALUE)?.toDouble() - if(v != null && v > 0.0) return exp(v * ln(2.0) * 0.5) - - return 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) - if(null != rat) return ExifUtil.processLensSpecifications(rat) - - return null - } - - /** - * 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 - } - - /** - * Clears this ExifInterface object's existing exif tags. - */ - private fun clearExif() : ExifInterface { - mData = ExifData() - return this - } - - /** - * 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) : ExifInterface { - mData = ExifReader(this).read(inStream, options) - return this - } - - /** - * 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) : ExifInterface = - BufferedInputStream(FileInputStream(inFileName)).use { readExif(it, options) } - - /** - * 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) = - ByteArrayInputStream(jpeg).use { readExif(it, options) } - - /** - * 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) : ExifInterface { - clearExif() - setTags(tags) - return this - } - - /** - * 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? = - mData.addTag(tag) - - /** - * 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) : ExifInterface { - for(t in tags) setTag(t) - return this - } - - @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 - FileInputStream(srcFilename).use { input -> - FileOutputStream(dstFilename).use { output -> - 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() - } - } - } - - @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() - } - - // input is used *ONLY* to read the image uncompressed data - // exif tags are not used here - @Throws(IOException::class) - fun writeExif(input : Bitmap, dstFilename : String, quality : Int) { - Log.i(TAG, "writeExif: $dstFilename") - ByteArrayOutputStream().use { out -> - input.compress(Bitmap.CompressFormat.JPEG, quality, out) - ByteArrayInputStream(out.toByteArray()).use { inStream -> - writeExif(inStream, dstFilename) - } - } - } - - /** - * 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) - } - - /** - * 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() - } - - private fun getTagStringValue( - tagId : Int, - ifdId : Int = getDefinedTagDefaultIfd(tagId) - ) : String? = - getTag(tagId, ifdId)?.valueAsString - - // array - - private fun getTagLongValues( - tagId : Int, - ifdId : Int = getDefinedTagDefaultIfd(tagId) - ) : LongArray? = - getTag(tagId, ifdId)?.valueAsLongs - - private fun getTagIntValues( - tagId : Int, - ifdId : Int = getDefinedTagDefaultIfd(tagId) - ) : IntArray? = - getTag(tagId, ifdId)?.valueAsInts - - private fun getTagByteValues( - tagId : Int, - ifdId : Int = getDefinedTagDefaultIfd(tagId) - ) : ByteArray? = - getTag(tagId, ifdId)?.valueAsBytes - - private fun getTagRationalValues( - tagId : Int, - ifdId : Int = getDefinedTagDefaultIfd(tagId) - ) : Array? = - getTag(tagId, ifdId)?.valueAsRationals - - // single value - - fun getTagLongValue(tagId : Int, ifdId : Int = getDefinedTagDefaultIfd(tagId)) : Long? = - getTagLongValues(tagId, ifdId)?.firstOrNull() - - fun getTagIntValue(tagId : Int, ifdId : Int = getDefinedTagDefaultIfd(tagId)) : Int? = - getTagIntValues(tagId, ifdId)?.firstOrNull() - - private fun getTagByteValue(tagId : Int, ifdId : Int = getDefinedTagDefaultIfd(tagId)) : Byte? = - getTagByteValues(tagId, ifdId)?.firstOrNull() - - private fun getTagRationalValue( - tagId : Int, - ifdId : Int = getDefinedTagDefaultIfd(tagId) - ) : Rational? = - getTagRationalValues(tagId, ifdId)?.firstOrNull() - - /** - * 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 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, - tagValue : Any, - ifdId : Int = getDefinedTagDefaultIfd(tagId) - ) : Boolean = - getTag(tagId, ifdId)?.setValueAny(tagValue) ?: false - - /** - * 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 = getDefinedTagDefaultIfd(tagId) - ) : ExifInterface { - mData.removeTag(getTrueTagKey(tagId), ifdId) - return this - } - - /** - * 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 = - tagInfo.get(defineTag(defaultIfd, tagId)) - - private fun getTagDefinitionsForTagId(tagId : Short) : IntArray? { - val ifds = IfdData.list - val defs = IntArray(ifds.size) - var counter = 0 - for(i in ifds) { - val def = defineTag(i, tagId) - if(tagInfo.get(def) != DEFINITION_NULL) { - defs[counter ++] = def - } - } - return if(counter == 0) null else defs.copyOfRange(0, counter) - - } - - fun getTagDefinitionForTag(tag : ExifTag) : Int = - getTagDefinitionForTag(tag.tagId, tag.dataType, tag.componentCount, tag.ifd) - - private fun getTagDefinitionForTag( - tagId : Short, - type : Short, - count : Int, - ifd : Int - ) : Int { - getTagDefinitionsForTagId(tagId)?.forEach { i -> - val info = tagInfo.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) - ) { - return i - } - } - return TAG_NULL - } - - /** - * 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 - // } - - /** - * 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 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, tagValue : Any, ifdId : Int = getTrueIfd(tagId)) : ExifTag? { - val info = tagInfo.get(tagId) - if(info == 0 || ! isIfdAllowed(info, ifdId)) return null - - val definedCount = getComponentCountFromInfo(info) - - val t = ExifTag( - tagId = getTrueTagKey(tagId), - dataType = getTypeFromInfo(info), - componentCount = definedCount, - ifd = ifdId, - mHasDefinedDefaultComponentCount = definedCount != ExifTag.SIZE_UNDEFINED - ) - - return when { - t.setValueAny(tagValue) -> t - else -> null - } - } - - /** - * 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 - } - - /** - * 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 - private val TAG_IMAGE_WIDTH = defineTag(IfdData.TYPE_IFD_0, 0x0100.toShort()) - private val TAG_IMAGE_LENGTH = - defineTag(IfdData.TYPE_IFD_0, 0x0101.toShort()) // Image height - private val TAG_BITS_PER_SAMPLE = defineTag(IfdData.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 - */ - private val TAG_COMPRESSION = defineTag(IfdData.TYPE_IFD_0, 0x0103.toShort()) - private val TAG_PHOTOMETRIC_INTERPRETATION = defineTag(IfdData.TYPE_IFD_0, 0x0106.toShort()) - private val TAG_IMAGE_DESCRIPTION = defineTag(IfdData.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. - */ - private val TAG_MAKE = defineTag(IfdData.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. - */ - private val TAG_MODEL = defineTag(IfdData.TYPE_IFD_0, 0x0110.toShort()) - val TAG_STRIP_OFFSETS = defineTag(IfdData.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(IfdData.TYPE_IFD_0, 0x0112.toShort()) - private val TAG_SAMPLES_PER_PIXEL = defineTag(IfdData.TYPE_IFD_0, 0x0115.toShort()) - private val TAG_ROWS_PER_STRIP = defineTag(IfdData.TYPE_IFD_0, 0x0116.toShort()) - val TAG_STRIP_BYTE_COUNTS = defineTag(IfdData.TYPE_IFD_0, 0x0117.toShort()) - - private val TAG_INTEROP_VERSION = - defineTag(IfdData.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. - */ - private val TAG_X_RESOLUTION = defineTag(IfdData.TYPE_IFD_0, 0x011A.toShort()) - - /** - * @see .TAG_X_RESOLUTION - */ - private val TAG_Y_RESOLUTION = defineTag(IfdData.TYPE_IFD_0, 0x011B.toShort()) - private val TAG_PLANAR_CONFIGURATION = defineTag(IfdData.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 - * - */ - private val TAG_RESOLUTION_UNIT = defineTag(IfdData.TYPE_IFD_0, 0x0128.toShort()) - private val TAG_TRANSFER_FUNCTION = defineTag(IfdData.TYPE_IFD_0, 0x012D.toShort()) - - /** - * Value is ascii string

- * Shows firmware(internal software of digicam) version number. - */ - private val TAG_SOFTWARE = defineTag(IfdData.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(IfdData.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. - */ - private val TAG_ARTIST = defineTag(IfdData.TYPE_IFD_0, 0x013B.toShort()) - private val TAG_WHITE_POINT = defineTag(IfdData.TYPE_IFD_0, 0x013E.toShort()) - private val TAG_PRIMARY_CHROMATICITIES = defineTag(IfdData.TYPE_IFD_0, 0x013F.toShort()) - private val TAG_Y_CB_CR_COEFFICIENTS = defineTag(IfdData.TYPE_IFD_0, 0x0211.toShort()) - private val TAG_Y_CB_CR_SUB_SAMPLING = defineTag(IfdData.TYPE_IFD_0, 0x0212.toShort()) - private val TAG_Y_CB_CR_POSITIONING = defineTag(IfdData.TYPE_IFD_0, 0x0213.toShort()) - private val TAG_REFERENCE_BLACK_WHITE = defineTag(IfdData.TYPE_IFD_0, 0x0214.toShort()) - - /** - * Values is ascii string

- * Shows copyright information - */ - private val TAG_COPYRIGHT = defineTag(IfdData.TYPE_IFD_0, 0x8298.toShort()) - val TAG_EXIF_IFD = defineTag(IfdData.TYPE_IFD_0, 0x8769.toShort()) - val TAG_GPS_IFD = defineTag(IfdData.TYPE_IFD_0, 0x8825.toShort()) - // IFD 1 - val TAG_JPEG_INTERCHANGE_FORMAT = defineTag(IfdData.TYPE_IFD_1, 0x0201.toShort()) - val TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = defineTag(IfdData.TYPE_IFD_1, 0x0202.toShort()) - // IFD Exif Tags - - /** - * Value is unsigned double

- * Exposure time (reciprocal of shutter speed). Unit is second - */ - private val TAG_EXPOSURE_TIME = defineTag(IfdData.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(IfdData.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. - * - */ - private val TAG_EXPOSURE_PROGRAM = defineTag(IfdData.TYPE_IFD_EXIF, 0x8822.toShort()) - private val TAG_SPECTRAL_SENSITIVITY = defineTag(IfdData.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 - */ - private val TAG_ISO_SPEED_RATINGS = defineTag(IfdData.TYPE_IFD_EXIF, 0x8827.toShort()) - private val TAG_OECF = defineTag(IfdData.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 - */ - private val TAG_EXIF_VERSION = defineTag(IfdData.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(IfdData.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(IfdData.TYPE_IFD_EXIF, 0x9004.toShort()) - private val TAG_COMPONENTS_CONFIGURATION = - defineTag(IfdData.TYPE_IFD_EXIF, 0x9101.toShort()) - private val TAG_COMPRESSED_BITS_PER_PIXEL = - defineTag(IfdData.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. - */ - private val TAG_SHUTTER_SPEED_VALUE = defineTag(IfdData.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(IfdData.TYPE_IFD_EXIF, 0x9202.toShort()) - - /** - * Value is signed double

- * Brightness of taken subject, unit is EV. - */ - private val TAG_BRIGHTNESS_VALUE = defineTag(IfdData.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 - */ - private val TAG_EXPOSURE_BIAS_VALUE = defineTag(IfdData.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 )
-		
* - */ - private val TAG_MAX_APERTURE_VALUE = defineTag(IfdData.TYPE_IFD_EXIF, 0x9205.toShort()) - - /** - * Value if signed double.

- * Distance to focus point, unit is meter. If value < 0 then focus point is infinite - */ - private val TAG_SUBJECT_DISTANCE = defineTag(IfdData.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 - * - */ - private val TAG_METERING_MODE = defineTag(IfdData.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 - * - */ - private val TAG_LIGHT_SOURCE = defineTag(IfdData.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) - */ - private val TAG_FLASH = defineTag(IfdData.TYPE_IFD_EXIF, 0x9209.toShort()) - - /** - * Value is unsigned double

- * Focal length of lens used to take image. Unit is millimeter. - */ - private val TAG_FOCAL_LENGTH = defineTag(IfdData.TYPE_IFD_EXIF, 0x920A.toShort()) - private val TAG_SUBJECT_AREA = defineTag(IfdData.TYPE_IFD_EXIF, 0x9214.toShort()) - private val TAG_MAKER_NOTE = defineTag(IfdData.TYPE_IFD_EXIF, 0x927C.toShort()) - val TAG_USER_COMMENT = defineTag(IfdData.TYPE_IFD_EXIF, 0x9286.toShort()) - private val TAG_SUB_SEC_TIME = defineTag(IfdData.TYPE_IFD_EXIF, 0x9290.toShort()) - private val TAG_SUB_SEC_TIME_ORIGINAL = defineTag(IfdData.TYPE_IFD_EXIF, 0x9291.toShort()) - private val TAG_SUB_SEC_TIME_DIGITIZED = defineTag(IfdData.TYPE_IFD_EXIF, 0x9292.toShort()) - private val TAG_FLASHPIX_VERSION = defineTag(IfdData.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 - * - */ - private val TAG_COLOR_SPACE = defineTag(IfdData.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. - */ - private val TAG_PIXEL_X_DIMENSION = defineTag(IfdData.TYPE_IFD_EXIF, 0xA002.toShort()) - - /** - * @see .TAG_PIXEL_X_DIMENSION - */ - private val TAG_PIXEL_Y_DIMENSION = defineTag(IfdData.TYPE_IFD_EXIF, 0xA003.toShort()) - private val TAG_RELATED_SOUND_FILE = defineTag(IfdData.TYPE_IFD_EXIF, 0xA004.toShort()) - val TAG_INTEROPERABILITY_IFD = defineTag(IfdData.TYPE_IFD_EXIF, 0xA005.toShort()) - private val TAG_FLASH_ENERGY = defineTag(IfdData.TYPE_IFD_EXIF, 0xA20B.toShort()) - private val TAG_SPATIAL_FREQUENCY_RESPONSE = - defineTag(IfdData.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 - */ - private val TAG_FOCAL_PLANE_X_RESOLUTION = - defineTag(IfdData.TYPE_IFD_EXIF, 0xA20E.toShort()) - - /** - * @see .TAG_FOCAL_PLANE_X_RESOLUTION - */ - private val TAG_FOCAL_PLANE_Y_RESOLUTION = - defineTag(IfdData.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 )
-		
* - */ - private val TAG_FOCAL_PLANE_RESOLUTION_UNIT = - defineTag(IfdData.TYPE_IFD_EXIF, 0xA210.toShort()) - private val TAG_SUBJECT_LOCATION = defineTag(IfdData.TYPE_IFD_EXIF, 0xA214.toShort()) - private val TAG_EXPOSURE_INDEX = defineTag(IfdData.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 - * - */ - private val TAG_SENSING_METHOD = defineTag(IfdData.TYPE_IFD_EXIF, 0xA217.toShort()) - private val TAG_FILE_SOURCE = defineTag(IfdData.TYPE_IFD_EXIF, 0xA300.toShort()) - private val TAG_SCENE_TYPE = defineTag(IfdData.TYPE_IFD_EXIF, 0xA301.toShort()) - private val TAG_CFA_PATTERN = defineTag(IfdData.TYPE_IFD_EXIF, 0xA302.toShort()) - private val TAG_CUSTOM_RENDERED = defineTag(IfdData.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 - * - */ - private val TAG_EXPOSURE_MODE = defineTag(IfdData.TYPE_IFD_EXIF, 0xA402.toShort()) - private val TAG_WHITE_BALANCE = defineTag(IfdData.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 - */ - private val TAG_DIGITAL_ZOOM_RATIO = defineTag(IfdData.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 );
-		
* - */ - private val TAG_FOCAL_LENGTH_IN_35_MM_FILE = - defineTag(IfdData.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 - * - */ - private val TAG_SCENE_CAPTURE_TYPE = defineTag(IfdData.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 - * - */ - private val TAG_GAIN_CONTROL = defineTag(IfdData.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 - * - */ - private val TAG_CONTRAST = defineTag(IfdData.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 - * - */ - private val TAG_SATURATION = defineTag(IfdData.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 - * - */ - private val TAG_SHARPNESS = defineTag(IfdData.TYPE_IFD_EXIF, 0xA40A.toShort()) - private val TAG_DEVICE_SETTING_DESCRIPTION = - defineTag(IfdData.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 - * - */ - private val TAG_SUBJECT_DISTANCE_RANGE = defineTag(IfdData.TYPE_IFD_EXIF, 0xA40C.toShort()) - - /** - * [ExifTag.TYPE_ASCII] - */ - private val TAG_IMAGE_UNIQUE_ID = defineTag(IfdData.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(IfdData.TYPE_IFD_EXIF, 0xA432.toShort()) - - /** - * Lens maker - * [ExifTag.TYPE_ASCII] - * - * @since EXIF 2.3 - */ - private val TAG_LENS_MAKE = defineTag(IfdData.TYPE_IFD_EXIF, 0xA433.toShort()) - /** - * Lens model name and number - * [ExifTag.TYPE_ASCII] - * - * @since EXIF 2.3 - */ - val TAG_LENS_MODEL = defineTag(IfdData.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 - */ - private val TAG_SENSITIVITY_TYPE = defineTag(IfdData.TYPE_IFD_EXIF, 0x8830.toShort()) - - // IFD GPS tags - private val TAG_GPS_VERSION_ID = defineTag(IfdData.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(IfdData.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(IfdData.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(IfdData.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(IfdData.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(IfdData.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(IfdData.TYPE_IFD_GPS, 6.toShort()) - val TAG_GPS_TIME_STAMP = defineTag(IfdData.TYPE_IFD_GPS, 7.toShort()) - private val TAG_GPS_SATTELLITES = defineTag(IfdData.TYPE_IFD_GPS, 8.toShort()) - private val TAG_GPS_STATUS = defineTag(IfdData.TYPE_IFD_GPS, 9.toShort()) - private val TAG_GPS_MEASURE_MODE = defineTag(IfdData.TYPE_IFD_GPS, 10.toShort()) - private val TAG_GPS_DOP = defineTag(IfdData.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. - */ - private val TAG_GPS_SPEED_REF = defineTag(IfdData.TYPE_IFD_GPS, 12.toShort()) - - /** - * Value is string.

- * Indicates the speed of GPS receiver movement - */ - private val TAG_GPS_SPEED = defineTag(IfdData.TYPE_IFD_GPS, 13.toShort()) - private val TAG_GPS_TRACK_REF = defineTag(IfdData.TYPE_IFD_GPS, 14.toShort()) - private val TAG_GPS_TRACK = defineTag(IfdData.TYPE_IFD_GPS, 15.toShort()) - private val TAG_GPS_IMG_DIRECTION_REF = defineTag(IfdData.TYPE_IFD_GPS, 16.toShort()) - private val TAG_GPS_IMG_DIRECTION = defineTag(IfdData.TYPE_IFD_GPS, 17.toShort()) - private val TAG_GPS_MAP_DATUM = defineTag(IfdData.TYPE_IFD_GPS, 18.toShort()) - private val TAG_GPS_DEST_LATITUDE_REF = defineTag(IfdData.TYPE_IFD_GPS, 19.toShort()) - private val TAG_GPS_DEST_LATITUDE = defineTag(IfdData.TYPE_IFD_GPS, 20.toShort()) - val TAG_GPS_DEST_LONGITUDE_REF = defineTag(IfdData.TYPE_IFD_GPS, 21.toShort()) - val TAG_GPS_DEST_LONGITUDE = defineTag(IfdData.TYPE_IFD_GPS, 22.toShort()) - private val TAG_GPS_DEST_BEARING_REF = defineTag(IfdData.TYPE_IFD_GPS, 23.toShort()) - private val TAG_GPS_DEST_BEARING = defineTag(IfdData.TYPE_IFD_GPS, 24.toShort()) - private val TAG_GPS_DEST_DISTANCE_REF = defineTag(IfdData.TYPE_IFD_GPS, 25.toShort()) - private val TAG_GPS_DEST_DISTANCE = defineTag(IfdData.TYPE_IFD_GPS, 26.toShort()) - private val TAG_GPS_PROCESSING_METHOD = defineTag(IfdData.TYPE_IFD_GPS, 27.toShort()) - private val TAG_GPS_AREA_INFORMATION = defineTag(IfdData.TYPE_IFD_GPS, 28.toShort()) - val TAG_GPS_DATE_STAMP = defineTag(IfdData.TYPE_IFD_GPS, 29.toShort()) - private val TAG_GPS_DIFFERENTIAL = defineTag(IfdData.TYPE_IFD_GPS, 30.toShort()) - // IFD Interoperability tags - private val TAG_INTEROPERABILITY_INDEX = - defineTag(IfdData.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) - .apply { timeZone = TimeZone.getTimeZone("UTC") } - - 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().apply { - add(getTrueTagKey(TAG_GPS_IFD)) - add(getTrueTagKey(TAG_EXIF_IFD)) - add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT)) - add(getTrueTagKey(TAG_INTEROPERABILITY_IFD)) - add(getTrueTagKey(TAG_STRIP_OFFSETS)) - } - - /** - * Tags with definitions that cannot be overridden (banned defines). - */ - var sBannedDefines = HashSet(sOffsetTags).apply { - add(getTrueTagKey(TAG_NULL)) - add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) - 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 = - 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 { - 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 when { - reference.startsWith("S") || reference.startsWith("W") -> - result - else -> result - } - } - - fun getAllowedIfdsFromInfo(info : Int) : IntArray? { - val ifdFlags = getAllowedIfdFlagsFromInfo(info) - val ifds = IfdData.list - val l = ArrayList() - for(i in 0 until IfdData.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 - } - - @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.firstOrNull()?.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, exifData) - eo.writeExifData(output) - - // 6.2 write all the sections except for the SOS ( start of scan ) - sections.forEach { - // Log.v( TAG, "writing section.. " + String.format( "0x%2X", current.type ) ); - output.write(0xFF) - output.write(it.type) - output.write(it.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 = tag.ushr(16) - - /** - * Returns the TID for a tag constant. - */ - fun getTrueTagKey(tag : Int) : Short = tag.toShort() - - private fun getFlagsFromAllowedIfds(allowedIfds : IntArray) : Int { - if(allowedIfds.isEmpty()) return 0 - var flags = 0 - val ifds = IfdData.list - for(i in 0 until IfdData.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 = info and 0x0ffff - - private fun getTypeFromInfo(info : Int) : Short = (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 = 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.ENGLISH) - ) - } 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? { - - 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 ifdFlags = getAllowedIfdFlagsFromInfo(info) - IfdData.list.forEachIndexed { itemIndex, itemId -> - if(ifd == itemId && ifdFlags shr itemIndex and 1 == 1) return true - } - return false - } - - private fun getAllowedIfdFlagsFromInfo(info : Int) : Int = 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 - - private fun SparseIntArray.initTagInfo() : SparseIntArray { - - var f : Int - - /* - * 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 - f = getFlagsFromAllowedIfds(intArrayOf(IfdData.TYPE_IFD_0, IfdData.TYPE_IFD_1)) shl 24 - - put(TAG_MAKE, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_IMAGE_WIDTH, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1) - put(TAG_IMAGE_LENGTH, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1) - put(TAG_BITS_PER_SAMPLE, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 3) - put(TAG_COMPRESSION, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_PHOTOMETRIC_INTERPRETATION, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_ORIENTATION, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_SAMPLES_PER_PIXEL, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_PLANAR_CONFIGURATION, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_Y_CB_CR_SUB_SAMPLING, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 2) - put(TAG_Y_CB_CR_POSITIONING, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_X_RESOLUTION, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_Y_RESOLUTION, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_RESOLUTION_UNIT, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_STRIP_OFFSETS, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16)) - put(TAG_ROWS_PER_STRIP, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1) - put(TAG_STRIP_BYTE_COUNTS, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16)) - put(TAG_TRANSFER_FUNCTION, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 3 * 256) - put(TAG_WHITE_POINT, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 2) - put(TAG_PRIMARY_CHROMATICITIES, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 6) - put(TAG_Y_CB_CR_COEFFICIENTS, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 3) - put(TAG_REFERENCE_BLACK_WHITE, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 6) - put(TAG_DATE_TIME, f or (ExifTag.TYPE_ASCII shl 16) or 20) - put(TAG_IMAGE_DESCRIPTION, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_MODEL, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_SOFTWARE, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_ARTIST, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_COPYRIGHT, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_EXIF_IFD, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1) - put(TAG_GPS_IFD, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1) - - // IFD1 tags - f = getFlagsFromAllowedIfds(intArrayOf(IfdData.TYPE_IFD_1)) shl 24 - put(TAG_JPEG_INTERCHANGE_FORMAT, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1) - put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1) - - // Exif tags - f = getFlagsFromAllowedIfds(intArrayOf(IfdData.TYPE_IFD_EXIF)) shl 24 - put(TAG_EXIF_VERSION, f or (ExifTag.TYPE_UNDEFINED shl 16) or 4) - put(TAG_FLASHPIX_VERSION, f or (ExifTag.TYPE_UNDEFINED shl 16) or 4) - put(TAG_COLOR_SPACE, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_COMPONENTS_CONFIGURATION, f or (ExifTag.TYPE_UNDEFINED shl 16) or 4) - put(TAG_COMPRESSED_BITS_PER_PIXEL, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_PIXEL_X_DIMENSION, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1) - put(TAG_PIXEL_Y_DIMENSION, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1) - put(TAG_MAKER_NOTE, f or (ExifTag.TYPE_UNDEFINED shl 16)) - put(TAG_USER_COMMENT, f or (ExifTag.TYPE_UNDEFINED shl 16)) - put(TAG_RELATED_SOUND_FILE, f or (ExifTag.TYPE_ASCII shl 16) or 13) - put(TAG_DATE_TIME_ORIGINAL, f or (ExifTag.TYPE_ASCII shl 16) or 20) - put(TAG_DATE_TIME_DIGITIZED, f or (ExifTag.TYPE_ASCII shl 16) or 20) - put(TAG_SUB_SEC_TIME, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_SUB_SEC_TIME_ORIGINAL, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_SUB_SEC_TIME_DIGITIZED, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_IMAGE_UNIQUE_ID, f or (ExifTag.TYPE_ASCII shl 16) or 33) - put(TAG_LENS_SPECS, f or (ExifTag.TYPE_RATIONAL shl 16) or 4) - put(TAG_LENS_MAKE, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_LENS_MODEL, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_SENSITIVITY_TYPE, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_EXPOSURE_TIME, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_F_NUMBER, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_EXPOSURE_PROGRAM, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_SPECTRAL_SENSITIVITY, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_ISO_SPEED_RATINGS, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16)) - put(TAG_OECF, f or (ExifTag.TYPE_UNDEFINED shl 16)) - put(TAG_SHUTTER_SPEED_VALUE, f or (ExifTag.TYPE_RATIONAL shl 16) or 1) - put(TAG_APERTURE_VALUE, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_BRIGHTNESS_VALUE, f or (ExifTag.TYPE_RATIONAL shl 16) or 1) - put(TAG_EXPOSURE_BIAS_VALUE, f or (ExifTag.TYPE_RATIONAL shl 16) or 1) - put(TAG_MAX_APERTURE_VALUE, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_SUBJECT_DISTANCE, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_METERING_MODE, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_LIGHT_SOURCE, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_FLASH, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_FOCAL_LENGTH, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_SUBJECT_AREA, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16)) - put(TAG_FLASH_ENERGY, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_SPATIAL_FREQUENCY_RESPONSE, f or (ExifTag.TYPE_UNDEFINED shl 16)) - put(TAG_FOCAL_PLANE_X_RESOLUTION, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_FOCAL_PLANE_Y_RESOLUTION, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_FOCAL_PLANE_RESOLUTION_UNIT, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_SUBJECT_LOCATION, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 2) - put(TAG_EXPOSURE_INDEX, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_SENSING_METHOD, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_FILE_SOURCE, f or (ExifTag.TYPE_UNDEFINED shl 16) or 1) - put(TAG_SCENE_TYPE, f or (ExifTag.TYPE_UNDEFINED shl 16) or 1) - put(TAG_CFA_PATTERN, f or (ExifTag.TYPE_UNDEFINED shl 16)) - put(TAG_CUSTOM_RENDERED, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_EXPOSURE_MODE, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_WHITE_BALANCE, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_DIGITAL_ZOOM_RATIO, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_FOCAL_LENGTH_IN_35_MM_FILE, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_SCENE_CAPTURE_TYPE, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_GAIN_CONTROL, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_CONTRAST, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_SATURATION, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_SHARPNESS, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_DEVICE_SETTING_DESCRIPTION, f or (ExifTag.TYPE_UNDEFINED shl 16)) - put(TAG_SUBJECT_DISTANCE_RANGE, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 1) - put(TAG_INTEROPERABILITY_IFD, f or (ExifTag.TYPE_UNSIGNED_LONG shl 16) or 1) - - // GPS tag - f = getFlagsFromAllowedIfds(intArrayOf(IfdData.TYPE_IFD_GPS)) shl 24 - put(TAG_GPS_VERSION_ID, f or (ExifTag.TYPE_UNSIGNED_BYTE shl 16) or 4) - put(TAG_GPS_LATITUDE_REF, f or (ExifTag.TYPE_ASCII shl 16) or 2) - put(TAG_GPS_LONGITUDE_REF, f or (ExifTag.TYPE_ASCII shl 16) or 2) - put(TAG_GPS_LATITUDE, f or (ExifTag.TYPE_RATIONAL shl 16) or 3) - put(TAG_GPS_LONGITUDE, f or (ExifTag.TYPE_RATIONAL shl 16) or 3) - put(TAG_GPS_ALTITUDE_REF, f or (ExifTag.TYPE_UNSIGNED_BYTE shl 16) or 1) - put(TAG_GPS_ALTITUDE, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_GPS_TIME_STAMP, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 3) - put(TAG_GPS_SATTELLITES, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_GPS_STATUS, f or (ExifTag.TYPE_ASCII shl 16) or 2) - put(TAG_GPS_MEASURE_MODE, f or (ExifTag.TYPE_ASCII shl 16) or 2) - put(TAG_GPS_DOP, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_GPS_SPEED_REF, f or (ExifTag.TYPE_ASCII shl 16) or 2) - put(TAG_GPS_SPEED, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_GPS_TRACK_REF, f or (ExifTag.TYPE_ASCII shl 16) or 2) - put(TAG_GPS_TRACK, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_GPS_IMG_DIRECTION_REF, f or (ExifTag.TYPE_ASCII shl 16) or 2) - put(TAG_GPS_IMG_DIRECTION, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_GPS_MAP_DATUM, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_GPS_DEST_LATITUDE_REF, f or (ExifTag.TYPE_ASCII shl 16) or 2) - put(TAG_GPS_DEST_LATITUDE, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_GPS_DEST_BEARING_REF, f or (ExifTag.TYPE_ASCII shl 16) or 2) - put(TAG_GPS_DEST_BEARING, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_GPS_DEST_DISTANCE_REF, f or (ExifTag.TYPE_ASCII shl 16) or 2) - put(TAG_GPS_DEST_DISTANCE, f or (ExifTag.TYPE_UNSIGNED_RATIONAL shl 16) or 1) - put(TAG_GPS_PROCESSING_METHOD, f or (ExifTag.TYPE_UNDEFINED shl 16)) - put(TAG_GPS_AREA_INFORMATION, f or (ExifTag.TYPE_UNDEFINED shl 16)) - put(TAG_GPS_DATE_STAMP, f or (ExifTag.TYPE_ASCII shl 16) or 11) - put(TAG_GPS_DIFFERENTIAL, f or (ExifTag.TYPE_UNSIGNED_SHORT shl 16) or 11) - - // Interoperability tag - f = getFlagsFromAllowedIfds(intArrayOf(IfdData.TYPE_IFD_INTEROPERABILITY)) shl 24 - put(TAG_INTEROPERABILITY_INDEX, f or (ExifTag.TYPE_ASCII shl 16)) - put(TAG_INTEROP_VERSION, f or (ExifTag.TYPE_UNDEFINED shl 16) or 4) - - return this - } - - } -} diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInvalidFormatException.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInvalidFormatException.kt deleted file mode 100644 index b5c08886..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifInvalidFormatException.kt +++ /dev/null @@ -1,19 +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 ExifInvalidFormatException(meg : String) : Exception(meg) \ No newline at end of file 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 deleted file mode 100644 index b20299c1..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifOutputStream.kt +++ /dev/null @@ -1,378 +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 it.sephiroth.android.library.exif2.utils.OrderedDataOutputStream -import java.io.BufferedOutputStream -import java.io.IOException -import java.io.OutputStream -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.util.* - -@Suppress("unused") -internal class ExifOutputStream( - - private val mInterface : ExifInterface, - - // the Exif header to be written into the JPEG file. - private val exifData : ExifData -) { - - 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) { - 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() - } - - // strip tags that has null value - // return list of removed tags - private fun stripNullValueTags(data : ExifData) = - ArrayList() - .apply { - for(t in data.allTags) { - if(t.getValue() == null && ! ExifInterface.isOffsetTag(t.tagId)) { - data.removeTag(t.tagId, t.ifd) - add(t) - } - } - } - - @Throws(IOException::class) - private fun writeThumbnail(dataOutputStream : OrderedDataOutputStream) { - val compressedThumbnail = exifData.compressedThumbnail - if(compressedThumbnail != null) { - Log.d(TAG, "writing thumbnail..") - dataOutputStream.write(compressedThumbnail) - } else { - val stripList = exifData.stripList - if(stripList != null) { - Log.d(TAG, "writing uncompressed strip..") - stripList.forEach { - dataOutputStream.write(it) - } - } - } - } - - @Throws(IOException::class) - private fun writeAllTags(dataOutputStream : OrderedDataOutputStream) { - writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_0), dataOutputStream) - writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_EXIF), dataOutputStream) - writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_INTEROPERABILITY), dataOutputStream) - writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_GPS), dataOutputStream) - writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_1), dataOutputStream) - } - - @Throws(IOException::class) - private fun writeIfd(ifd : IfdData?, dataOutputStream : OrderedDataOutputStream) { - ifd ?: return - val tags = ifd.allTagsCollection - 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 - - for(tag in ifd.allTagsCollection) { - 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(IfdData.TYPE_IFD_0) - if(ifd0 == null) { - ifd0 = IfdData(IfdData.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(IfdData.TYPE_IFD_EXIF) - if(exifIfd == null) { - exifIfd = IfdData(IfdData.TYPE_IFD_EXIF) - exifData.addIfdData(exifIfd) - } - - // GPS IFD - val gpsIfd = exifData.getIfdData(IfdData.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(IfdData.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(IfdData.TYPE_IFD_1) - - // thumbnail - val compressedThumbnail = exifData.compressedThumbnail - val stripList = exifData.stripList - when { - - compressedThumbnail != null -> { - if(ifd1 == null) { - ifd1 = IfdData(IfdData.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(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)) - } - - stripList != null -> { - if(ifd1 == null) { - ifd1 = IfdData(IfdData.TYPE_IFD_1) - exifData.addIfdData(ifd1) - } - 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 bytesList = LongArray(stripList.size) - stripList.forEachIndexed { index, bytes -> bytesList[index] = bytes.size.toLong() } - lengthTag.setValue(bytesList) - 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 ifd0 = exifData.getIfdData(IfdData.TYPE_IFD_0)?.also { - offset = calculateOffsetOfIfd(it, offset) - } - - val exifIfd = exifData.getIfdData(IfdData.TYPE_IFD_EXIF)?.also { it -> - ifd0?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)) - ?.setValue(offset) - offset = calculateOffsetOfIfd(it, offset) - } - - exifData.getIfdData(IfdData.TYPE_IFD_INTEROPERABILITY)?.also { it -> - exifIfd?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD)) - ?.setValue(offset) - offset = calculateOffsetOfIfd(it, offset) - } - - exifData.getIfdData(IfdData.TYPE_IFD_GPS)?.also { - ifd0?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)) - ?.setValue(offset) - offset = calculateOffsetOfIfd(it, offset) - } - - val ifd1 = exifData.getIfdData(IfdData.TYPE_IFD_1)?.also { - ifd0?.offsetToNextIfd = offset - offset = calculateOffsetOfIfd(it, offset) - } - - val compressedThumbnail = exifData.compressedThumbnail - if(compressedThumbnail != null) { - ifd1 - ?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) - ?.setValue(offset) - offset += compressedThumbnail.size - } else { - // uncompressed thumbnail - val stripList = exifData.stripList - if(stripList != null) { - val offsets = LongArray(stripList.size) - stripList.forEachIndexed { index, bytes -> - offsets[index] = offset.toLong() - offset += bytes.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.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifParser.kt deleted file mode 100644 index 6f4d99c1..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifParser.kt +++ /dev/null @@ -1,1165 +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 it.sephiroth.android.library.exif2.utils.CountedDataInputStream -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 -) { - - // number of tags in the current IFD area. - private var tagCountInCurrentIfd = 0 - - /** - * the ID of current IFD. - * - * @see IfdData.TYPE_IFD_0 - * @see IfdData.TYPE_IFD_1 - * @see IfdData.TYPE_IFD_GPS - * @see IfdData.TYPE_IFD_INTEROPERABILITY - * @see IfdData.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 - - 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 - - private val mCorrespondingEvent = TreeMap() - - private val mSections = ArrayList
(0) - - private var mIfdStartOffset = 0 - - private val mTiffStream : CountedDataInputStream = seekTiffData(inputStream) - - init { - - // Log.d( TAG, "sections size: " + mSections.size() ); - - val tiffStream = mTiffStream - - parseTiffHeader(tiffStream) - - val offset = tiffStream.readUnsignedInt() - if(offset > Integer.MAX_VALUE) { - throw ExifInvalidFormatException("Invalid offset $offset") - } - mIfd0Position = offset.toInt() - currentIfd = IfdData.TYPE_IFD_0 - - if(isIfdRequested(IfdData.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) { - registerIfd(IfdData.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 == 137 && b == 80) error("maybe PNG image") - - if(a != 0xFF || b != JpegHeader.TAG_SOI) error("invalid jpeg header") - - 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") - } - - // 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") - } - - 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) - } - - val section = Section(type = marker, size = itemlen, data = data) - - var ignore = false - - when(marker) { - JpegHeader.TAG_M_SOS -> { - // stop before hitting compressed data - mSections.add(section) - uncompressedDataPosition = dataStream.readByteCount - return tiffStream ?: error("stop before hitting compressed data") - } - - JpegHeader.TAG_M_DQT -> - // Use for jpeg quality guessing - process_M_DQT(data) - - JpegHeader.TAG_M_DHT -> { - } - - // in case it's a tables-only JPEG stream - JpegHeader.TAG_M_EOI -> { - error("\"No image in jpeg!\"") - } - - 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) { - IfdData.TYPE_IFD_0 -> return mOptions and ExifInterface.Options.OPTION_IFD_0 != 0 - IfdData.TYPE_IFD_1 -> return mOptions and ExifInterface.Options.OPTION_IFD_1 != 0 - IfdData.TYPE_IFD_EXIF -> return mOptions and ExifInterface.Options.OPTION_IFD_EXIF != 0 - IfdData.TYPE_IFD_GPS -> return mOptions and ExifInterface.Options.OPTION_IFD_GPS != 0 - IfdData.TYPE_IFD_INTEROPERABILITY -> return mOptions and ExifInterface.Options.OPTION_IFD_INTEROPERABILITY != 0 - } - return false - } - - private fun needToParseOffsetsInCurrentIfd() : Boolean { - return when(currentIfd) { - - IfdData.TYPE_IFD_0 -> - isIfdRequested(IfdData.TYPE_IFD_EXIF) || - isIfdRequested(IfdData.TYPE_IFD_GPS) || - isIfdRequested(IfdData.TYPE_IFD_INTEROPERABILITY) || - isIfdRequested(IfdData.TYPE_IFD_1) - - IfdData.TYPE_IFD_1 -> isThumbnailRequested - - IfdData.TYPE_IFD_EXIF -> - // The offset to interoperability IFD is located in Exif IFD - isIfdRequested(IfdData.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 = mTiffStream.read(buffer) - - // - // /** - // * 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 { - - 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 == IfdData.TYPE_IFD_0) { - val ifdOffset = readUnsignedLong() - if(isIfdRequested(IfdData.TYPE_IFD_1) || isThumbnailRequested) { - if(ifdOffset != 0L) { - registerIfd(IfdData.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() { - - 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 == IfdData.TYPE_IFD_0 && (isIfdRequested(IfdData.TYPE_IFD_1) || isThumbnailRequested)) { - if(ifdOffset > 0) { - registerIfd(IfdData.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) { - 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? { - - 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(IfdData.TYPE_IFD_EXIF) || isIfdRequested(IfdData.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdData.TYPE_IFD_EXIF, tag.getValueAt(0)) - } - } else if(tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) { - if(isIfdRequested(IfdData.TYPE_IFD_GPS)) { - registerIfd(IfdData.TYPE_IFD_GPS, tag.getValueAt(0)) - } - } else if(tid == TAG_INTEROPERABILITY_IFD && checkAllowed( - ifd, - ExifInterface.TAG_INTEROPERABILITY_IFD - )) { - if(isIfdRequested(IfdData.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdData.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 : Short) : Boolean { - return mInterface.tagInfo.get( - ExifInterface.defineTag( - ifdId, - tagId - ) - ) != 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) { - - // 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.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 { - 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 = - mTiffStream.readShort().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 = - mTiffStream.readInt().toLong() - - /** - * 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(var size : Int, var type : Int, var data : ByteArray) - - 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 = - ExifParser(inputStream, options, iRef) - } -} \ No newline at end of file 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 deleted file mode 100644 index 078dcfe0..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifReader.kt +++ /dev/null @@ -1,114 +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 - * [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( - byteOrder = parser.byteOrder, - sections = parser.sections, - mUncompressedDataPosition = parser.uncompressedDataPosition, - qualityGuess = parser.qualityGuess, - jpegProcess = parser.jpegProcess - ) - - val w = parser.imageWidth - val h = parser.imageLength - - if(w > 0 && h > 0) { - exifData.setImageSize(w, h) - } - - 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 -> { - val tag = parser.tag - when { - tag == null -> - Log.w(TAG, "parser.tag is null") - - ! tag.hasValue -> - parser.registerForTagValue(tag) - - ! parser.isDefinedTag(tag.ifd, tag.tagId) -> - Log.w(TAG, "skip tag because not registered in the tag table:$tag") - - else -> - exifData.getIfdData(tag.ifd)?.setTag(tag) - } - - } - - ExifParser.EVENT_VALUE_OF_REGISTERED_TAG -> { - val 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.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/ExifTag.kt deleted file mode 100644 index d7b15231..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifTag.kt +++ /dev/null @@ -1,842 +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.* - -/** - * 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 IfdData.TYPE_IFD_0 - * @see IfdData.TYPE_IFD_1 - * @see IfdData.TYPE_IFD_EXIF - * @see IfdData.TYPE_IFD_GPS - * @see IfdData.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 = componentCount - 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) - - /** - * Returns true if this ExifTag contains value; otherwise, this tag will - * contain an offset value that is determined when the tag is written. - */ - val hasValue :Boolean - get() = mValue != null - - /** - * 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 - - /** - * 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 - } - - /** - * 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) = 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) = 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) =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) = 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 = - null != value.find { it !in 0 .. UNSIGNED_SHORT_MAX } - - private fun checkOverflowForUnsignedLong(value : LongArray) : Boolean = - null != value.find { it !in 0 .. UNSIGNED_LONG_MAX } - - private fun checkOverflowForUnsignedLong(value : IntArray) : Boolean = - null != value.find { it < 0 } - - private fun checkOverflowForUnsignedRational(value : Array) : Boolean = - null != value.find { it.numerator !in 0 .. UNSIGNED_LONG_MAX || it.denominator !in 0 .. UNSIGNED_LONG_MAX } - - private fun checkOverflowForRational(value : Array) : Boolean = - null != value.find { it.numerator !in LONG_MIN .. LONG_MAX || it.denominator !in LONG_MIN .. LONG_MAX } - - 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) { - IfdData.TYPE_IFD_0, - IfdData.TYPE_IFD_1, - IfdData.TYPE_IFD_EXIF, - IfdData.TYPE_IFD_INTEROPERABILITY, - IfdData.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 -> "" - } - } -} 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 deleted file mode 100644 index a4fa589a..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/ExifUtil.kt +++ /dev/null @@ -1,32 +0,0 @@ -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.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/IfdData.kt deleted file mode 100644 index 4e62636a..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/IfdData.kt +++ /dev/null @@ -1,107 +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 - -// This class stores all the tags in an IFD. -// an IfdData with given IFD ID. -internal class IfdData( - val id : Int // the ID of this IFD. -) { - - companion object { - /** - * The constants of the IFD ID defined in EXIF spec. - */ - 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 - - val list = intArrayOf( - TYPE_IFD_0, - TYPE_IFD_1, - TYPE_IFD_EXIF, - TYPE_IFD_INTEROPERABILITY, - 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 - - // Collection the contains all [ExifTag] in this IFD. - val allTagsCollection : Collection - get() = mExifTags.values - - // 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 is IfdData) { - if(other === this) return true - if(other.id == id && other.tagCount == tagCount) { - for(tag in other.allTagsCollection) { - if(ExifInterface.isOffsetTag(tag.tagId)) continue - if(tag != mExifTags[tag.tagId]) 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.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/JpegHeader.kt deleted file mode 100644 index 60f3a5ea..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/JpegHeader.kt +++ /dev/null @@ -1,123 +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 - -@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/Rational.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/Rational.kt deleted file mode 100644 index bdede84f..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/Rational.kt +++ /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 - -/** - * The rational data type of EXIF tag. Contains a pair of longs representing the - * numerator and denominator of a Rational number. - */ -class Rational( - - //the numerator of the rational. - val numerator : Long = 0, - - //the denominator of the rational - val denominator : Long = 1 -) { - - // copy from a Rational. - @Suppress("unused") - constructor(r : Rational) : this( - numerator = r.numerator, - denominator = r.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() - - override fun toString() : String = "$numerator/$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() -} diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/utils/CountedDataInputStream.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/utils/CountedDataInputStream.kt deleted file mode 100644 index b57801c7..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/utils/CountedDataInputStream.kt +++ /dev/null @@ -1,149 +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.utils - -import java.io.EOFException -import java.io.FilterInputStream -import java.io.IOException -import java.io.InputStream -import java.nio.ByteBuffer -import java.nio.ByteOrder -import java.nio.charset.Charset -import java.nio.charset.StandardCharsets - -@Suppress("unused") -internal class CountedDataInputStream constructor(`in` : InputStream) : - FilterInputStream(`in`) { - - // allocate a byte buffer for a long value; - private val mByteArray = ByteArray(8) - private val mByteBuffer = ByteBuffer.wrap(mByteArray) - var readByteCount = 0 - private set - var end = 0 - - var byteOrder : ByteOrder - get() = mByteBuffer.order() - set(order) { - mByteBuffer.order(order) - } - - @Throws(IOException::class) - override fun read(b : ByteArray) : Int { - val r = `in`.read(b) - readByteCount += if(r >= 0) r else 0 - return r - } - - @Throws(IOException::class) - override fun read() : Int { - val r = `in`.read() - readByteCount += if(r >= 0) 1 else 0 - return r - } - - @Throws(IOException::class) - override fun read(b : ByteArray, off : Int, len : Int) : Int { - val r = `in`.read(b, off, len) - readByteCount += if(r >= 0) r else 0 - return r - } - - @Throws(IOException::class) - override fun skip(length : Long) : Long { - val skip = `in`.skip(length) - readByteCount += skip.toInt() - return skip - } - - @Throws(IOException::class) - fun skipTo(target : Long) { - val cur = readByteCount.toLong() - val diff = target - cur - if(diff < 0) throw IndexOutOfBoundsException("skipTo: negative move") - skipOrThrow(diff) - } - - @Throws(IOException::class) - fun skipOrThrow(length : Long) { - if(skip(length) != length) throw EOFException() - } - - @Throws(IOException::class) - fun readUnsignedShort() : Int = readShort().toInt() and 0xffff - - @Throws(IOException::class) - fun readShort() : Short { - readOrThrow(mByteArray, 0, 2) - mByteBuffer.rewind() - return mByteBuffer.short - } - - @Throws(IOException::class) - fun readByte() : Byte { - readOrThrow(mByteArray, 0, 1) - mByteBuffer.rewind() - return mByteBuffer.get() - } - - @Throws(IOException::class) - fun readUnsignedByte() : Int { - readOrThrow(mByteArray, 0, 1) - mByteBuffer.rewind() - return mByteBuffer.get().toInt() and 0xff - } - - @Throws(IOException::class) - @JvmOverloads - fun readOrThrow(b : ByteArray, off : Int = 0, len : Int = b.size) { - val r = read(b, off, len) - if(r != len) throw EOFException() - } - - @Throws(IOException::class) - fun readUnsignedInt() : Long { - return readInt().toLong() and 0xffffffffL - } - - @Throws(IOException::class) - fun readInt() : Int { - readOrThrow(mByteArray, 0, 4) - mByteBuffer.rewind() - return mByteBuffer.int - } - - @Throws(IOException::class) - fun readLong() : Long { - readOrThrow(mByteArray, 0, 8) - mByteBuffer.rewind() - return mByteBuffer.long - } - - @Throws(IOException::class) - fun readString(n : Int) : String { - val buf = ByteArray(n) - readOrThrow(buf) - return String(buf, StandardCharsets.UTF_8) - } - - @Throws(IOException::class) - fun readString(n : Int, charset : Charset) : String { - val buf = ByteArray(n) - readOrThrow(buf) - return String(buf, charset) - } -} \ No newline at end of file diff --git a/exif/src/main/java/it/sephiroth/android/library/exif2/utils/OrderedDataOutputStream.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/utils/OrderedDataOutputStream.kt deleted file mode 100644 index 104502c4..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/utils/OrderedDataOutputStream.kt +++ /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.utils - -import it.sephiroth.android.library.exif2.Rational -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/utils/Utils.kt b/exif/src/main/java/it/sephiroth/android/library/exif2/utils/Utils.kt deleted file mode 100644 index 26416c9f..00000000 --- a/exif/src/main/java/it/sephiroth/android/library/exif2/utils/Utils.kt +++ /dev/null @@ -1,7 +0,0 @@ -package it.sephiroth.android.library.exif2.utils - -internal fun Collection?.notEmpty() : Collection? = - if(this?.isNotEmpty() == true) this else null - -internal fun List?.notEmpty() : List? = - if(this?.isNotEmpty() == true) this else null diff --git a/exif/src/test/java/it/sephiroth/android/library/exif2/Test1.kt b/exif/src/test/java/it/sephiroth/android/library/exif2/Test1.kt deleted file mode 100644 index efd31b11..00000000 --- a/exif/src/test/java/it/sephiroth/android/library/exif2/Test1.kt +++ /dev/null @@ -1,115 +0,0 @@ -package it.sephiroth.android.library.exif2 - -import android.util.Log -import android.util.SparseIntArray -import org.junit.Assert.* -import org.junit.Test -import java.io.File -import java.io.FileInputStream - -class Test1 { - - @Test - fun testLog() { - Log.v("TEST", "test") - assertTrue("using android.util.Log", true) - } - - @Test - fun testSparseIntArray() { - val a = SparseIntArray() - a.put(1, 2) - assertTrue("get existing value", a[1] == 2) - assertTrue("fallback to default value ", a.get(0, - 1) == - 1) - } - - // get File object from files in src/test/resources/ - private fun getFile(fileName : String) : File { - return when(val resource = this.javaClass.classLoader !!.getResource(fileName)) { - null -> error("missing file $fileName") - else -> File(resource.path) - } - } - - private fun getOrientation(fileName : String) : Pair = - try { - val o = FileInputStream(getFile(fileName)).use { inStream -> - ExifInterface() - .readExif( - inStream, - ExifInterface.Options.OPTION_IFD_0 - or ExifInterface.Options.OPTION_IFD_1 - or ExifInterface.Options.OPTION_IFD_EXIF - ) - .getTagIntValue(ExifInterface.TAG_ORIENTATION) - } - Pair(o, null) - } catch(ex : Throwable) { - Pair(null, ex) - } - - private fun getThumbnailBytes(fileName : String) : Pair = - try { - val o = FileInputStream(getFile(fileName)).use { inStream -> - ExifInterface() - .readExif( - inStream, - ExifInterface.Options.OPTION_IFD_0 - or ExifInterface.Options.OPTION_IFD_1 - or ExifInterface.Options.OPTION_IFD_EXIF - ) - .thumbnailBytes - } - Pair(o, null) - } catch(ex : Throwable) { - Pair(null, ex) - } - - @Test - fun testNotJpeg() { - fun testNotJpegSub(fileName : String) { - val (o, ex) = getOrientation(fileName) - assertTrue("testNotJpegSub", o == null && ex != null) - } - testNotJpegSub("test.gif") - testNotJpegSub("test.png") - testNotJpegSub("test.webp") - } - - @Test - fun testJpeg() { - var fileName : String - var rvO : Pair - var rvT : Pair - - // this file has orientation 6. - fileName = "test3.jpg" - rvO = getOrientation(fileName) - assertEquals(fileName, 6, rvO.first) - rvT = getThumbnailBytes(fileName) - assertNull(fileName, rvT.first) - - // this file has orientation 1 - fileName = "test1.jpg" - rvO = getOrientation(fileName) - assertEquals(fileName, 1, rvO.first) - rvT = getThumbnailBytes(fileName) - assertNull(fileName, rvT.first) - - // this file has no orientation, it raises exception. - fileName = "test2.jpg" - rvO = getOrientation(fileName) - assertNotNull( - fileName, - rvO.second - ) // - - rvT = getThumbnailBytes(fileName) - assertNotNull( - fileName, - rvT.second - ) // - - } - -} \ No newline at end of file diff --git a/exif/src/test/resources/test.gif b/exif/src/test/resources/test.gif deleted file mode 100644 index 6e42eb2b..00000000 Binary files a/exif/src/test/resources/test.gif and /dev/null differ diff --git a/exif/src/test/resources/test.png b/exif/src/test/resources/test.png deleted file mode 100644 index 141e0a16..00000000 Binary files a/exif/src/test/resources/test.png and /dev/null differ diff --git a/exif/src/test/resources/test.webp b/exif/src/test/resources/test.webp deleted file mode 100644 index 4d9a81af..00000000 Binary files a/exif/src/test/resources/test.webp and /dev/null differ diff --git a/exif/src/test/resources/test1.jpg b/exif/src/test/resources/test1.jpg deleted file mode 100644 index 935bc426..00000000 Binary files a/exif/src/test/resources/test1.jpg and /dev/null differ diff --git a/exif/src/test/resources/test2.jpg b/exif/src/test/resources/test2.jpg deleted file mode 100644 index 335f16d5..00000000 Binary files a/exif/src/test/resources/test2.jpg and /dev/null differ diff --git a/exif/src/test/resources/test3.jpg b/exif/src/test/resources/test3.jpg deleted file mode 100644 index 61a5591f..00000000 Binary files a/exif/src/test/resources/test3.jpg and /dev/null differ diff --git a/exif/src/test/resources/test3.jpg_original b/exif/src/test/resources/test3.jpg_original deleted file mode 100644 index 335f16d5..00000000 Binary files a/exif/src/test/resources/test3.jpg_original and /dev/null differ diff --git a/sample_apng/build.gradle b/sample_apng/build.gradle index ef7ed5b7..7d6e9ab4 100644 --- a/sample_apng/build.gradle +++ b/sample_apng/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion target_sdk_version + compileSdkVersion compile_sdk_version compileOptions { sourceCompatibility JavaVersion.VERSION_1_8