From 9f000ea2126eddc2cb804c010159f11431924215 Mon Sep 17 00:00:00 2001 From: tateisu Date: Thu, 25 Feb 2021 10:24:08 +0900 Subject: [PATCH] remove unused module exif. define compile_sdk_version build script variable. --- apng_android/build.gradle | 2 +- app/build.gradle | 2 +- build.gradle | 1 + colorpicker/build.gradle | 2 +- emoji/build.gradle | 2 +- exif/.gitignore | 1 - exif/build.gradle | 52 - exif/proguard-rules.pro | 26 - exif/src/main/AndroidManifest.xml | 3 - .../android/library/exif2/ExifData.kt | 298 -- .../android/library/exif2/ExifInterface.kt | 2614 ----------------- .../exif2/ExifInvalidFormatException.kt | 19 - .../android/library/exif2/ExifOutputStream.kt | 378 --- .../android/library/exif2/ExifParser.kt | 1165 -------- .../android/library/exif2/ExifReader.kt | 114 - .../android/library/exif2/ExifTag.kt | 842 ------ .../android/library/exif2/ExifUtil.kt | 32 - .../android/library/exif2/IfdData.kt | 107 - .../android/library/exif2/JpegHeader.kt | 123 - .../android/library/exif2/Rational.kt | 56 - .../exif2/utils/CountedDataInputStream.kt | 149 - .../exif2/utils/OrderedDataOutputStream.kt | 56 - .../android/library/exif2/utils/Utils.kt | 7 - .../sephiroth/android/library/exif2/Test1.kt | 115 - exif/src/test/resources/test.gif | Bin 14964 -> 0 bytes exif/src/test/resources/test.png | Bin 3534 -> 0 bytes exif/src/test/resources/test.webp | Bin 8782 -> 0 bytes exif/src/test/resources/test1.jpg | Bin 13936 -> 0 bytes exif/src/test/resources/test2.jpg | Bin 3386 -> 0 bytes exif/src/test/resources/test3.jpg | Bin 3486 -> 0 bytes exif/src/test/resources/test3.jpg_original | Bin 3386 -> 0 bytes sample_apng/build.gradle | 2 +- 32 files changed, 6 insertions(+), 6162 deletions(-) delete mode 100644 exif/.gitignore delete mode 100644 exif/build.gradle delete mode 100644 exif/proguard-rules.pro delete mode 100644 exif/src/main/AndroidManifest.xml delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/ExifData.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/ExifInterface.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/ExifInvalidFormatException.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/ExifOutputStream.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/ExifParser.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/ExifReader.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/ExifTag.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/ExifUtil.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/IfdData.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/JpegHeader.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/Rational.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/utils/CountedDataInputStream.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/utils/OrderedDataOutputStream.kt delete mode 100644 exif/src/main/java/it/sephiroth/android/library/exif2/utils/Utils.kt delete mode 100644 exif/src/test/java/it/sephiroth/android/library/exif2/Test1.kt delete mode 100644 exif/src/test/resources/test.gif delete mode 100644 exif/src/test/resources/test.png delete mode 100644 exif/src/test/resources/test.webp delete mode 100644 exif/src/test/resources/test1.jpg delete mode 100644 exif/src/test/resources/test2.jpg delete mode 100644 exif/src/test/resources/test3.jpg delete mode 100644 exif/src/test/resources/test3.jpg_original 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 6e42eb2bf6dd3448ca12de5292b25c65273430e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14964 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 W3Js2u_2bNeRT*S_x>L%LR8iO diff --git a/exif/src/test/resources/test.png b/exif/src/test/resources/test.png deleted file mode 100644 index 141e0a1651bc1e5835e1d15b1b59e7fdf56dada7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3534 zcmV;<4KebGP)EX>4Tx07!|QmUmQB*%pV-y*Is3k`RiN&}(Q?0!R(L zNRcioF$oY#z>okUHbhi#L{X8Z2r?+(fTKf^u_B6v0a3B*1Q|rsac~qHmPur-8Q;8l z@6DUvANPK1pS{oBXYYO1x&V;;g9XA&SP6g(p;#2*=f#MPi)Ua50Sxc}18e}`aI>>Q z7WhU2nF4&+jBJ?`_!qsp4j}paD$_rV!2tiCl(|_VF#u4QjOX(B*<2YH$v8b%oF%tU z$(Xh@P0lb%&LUZYGFFpw@+@0?_L*f5IrB1vJQ>S#&f;b8cV}o=_hCs$|GJ-ARc>v%@$zSl&FIdda6Uz_9 z&dgda5+tXH875p)hK-XGi{a1DP3Mcn%rFi&jU(bQ*qIqw9N}^RX3zXt6nSkKvLZX! zI5{{lZ7prSDAa#l{F{>Zc9vd*f9@GXANa%eSALld0I;TIwb}ZIZD|z%UF!i*yZwjF zU@riQvc7c=eQ_STd|pz-;w)z?tK8gNO97v2DKF^n`kxMeLtlK)Qoh~qM8wF>;&Ay4=AVc79|!(*9u^V&B)*6*lto0#rc5AAmbF{R6Nm+wLWV&2 zpPKj&!~Ue%xt59A_z}>SSOTRX8bE#?04OREAPIY9E70$K3&uwS`OS;bnV6mX&w~Da zSGY|6$QC4jj$=neGPn{^&g`1}S^_j607XCp>OdRl0~5dmw!jg%01w~;0zoK<1aV+7 z;DQv80Yo4d6o9p$7?gsoU?->sb)XS6gEnv&bb({wG&lz?fy-b7+yPQB4xWH1@CwX8 z5QK%u5EW8~bRa{>9I}O2kQ?L!1w#=~9FzzpLqbRb6+r8tQm7oNhU%ea=v(M0bQ-z< z4MVq}QD_qS6?z9FFbSr?TCfpp1+!pJI0%k}7s1K!GB_VDg15kxa07f0?u1Xnm*5dt z3O|9T5r7a8I--j(5f;KmLXmhR2@xTykP@TC$XgT!MMW`COq2`C z9~Fh-qL!gnp*EwcQ3p_+s6NzH)F^5S^$|@*Yog83&gcMiEIJvTi!Mf2pqtPg=(Fe% z^f>wz27{qvj4_TFe@q-E6|(}f8M7PHjyZ)H#*AU6u~@7+)*S1K4aIV>Vr((C3VRTH z5_<(Zj(vk8;&gDfIA2^mPKYbSRp451CvaDA6Sx_?65bH+j1R^0@XPUK_(psWeh5E~ zpCKp{j0vuUNJ1)MEuoUoMmS5jOL##f67`5q#Bid3xQ19sJVZQC93{RbQAlPaHYtH5 zA#EY;C!HeQBE2A!$wp)kay(f~-a>9BpCR8TzfqtnSSkc4@Dx@n)F^Z+Tv2$Yh*vaJ z^i*7|n6Fr&ctmkX@u?DC$w-N<#8FzMRHJlM>4ws@GF90|IaE1Ad9!kh@&)Bb6fDJv z;zQw4iYWUiXDDM-gsM+vQ@PZ2)JE!A>NpKUGo}U5QfZ~MZ)k(GDHV!}ol3Myo=T0% zaTO^Yp&QWy=;`z_`eFKY`a4xERZmsE>L%4T)hnv6)#j*qsPWZG)Y{cX)ZVEx)P2;` z)VHa3so&E;X_#q*YvgL|(KxH|bPjEf%N*{Uk~xRx+}4CO%`_u4S7`3j9MGKB($@0R z%F?RRI-~Veo38DlovOV<`-JwS4pqlZN1(Gq=cLYKh6=-zkLZ@rEqJ6vJJH{f4iNjE!Q9 zHW+moJu+4^4lvF)ZZ*DZLN;+XS!U8;a?KQD$}&we-EDf=3^ubjOEIf48#0H@9n1yh zyUm9!&=yV>LW>5A8%z?@lbOS8WsX|XErTr!ExRnASs7TxTWz!IxB6&pZ=G)4Xnn_q zViRanXwzf!tF4(W*S5y?+FbHn-?^*jcF%ooXKu&0+hcdro@yUrzrnuO{)2;~gUF%H zVbamSG10Ns@dk^=3S(_%op(Yzc{#0iI_C7&*}+-teAxLH7p6;^ON+~+dB*ej^BU)k zx$3!cTZVb0Xx4mvscU^amdxQG}4}A}wN0Y~dr>SSE=RwbBUe;bBuMV%*Y-jdL z_9<_~+t0hid(emC6XjFwbKh6bH`%w{0a^jvfaZXyK*zw9 zfqg-wpantIK@Wn>fV8I z2F~=-fTgudr?_nHF76Ya2X6;&lJCkd=T9WLCY2{WN_I`&o;;c2o>GzWRKONg3!bO? zr`DyuP76)jpY|y|CcQlamywupR7eq~3Hvg&GxIWsv&^%Kv!u(Mm+f3OB?=NXWkcDE zvb)7J+0WE~#6+@QGMeL-QhTd=lZ zbfxFY`c=@XrK@^Z>#r_aJ-)_o&4IOqwP|aAD6}ptFMPQ!W?fH_R?(WGvGsoITZV0)e z^+=6ZO?$0o?WWq-yLr2>?D5#sR;N{0TK8_RVDHU(zxvJwqlSuon0-0>9yUfd_J7U# zy17ZCskG_Ce&K%UfrtZr&5q5@Et)N5t#GTPb@E`s!OP!xf79K@Y^!glx0fCQha`s{ zf1CL2^}|7jdylY=w0&pzU2O-oqofn+T;4g=mC_~cj_V#i8hEs~$EBy^d&}?lAJaWn zb6n+k*$Kjlq7$D^=AWECm38Xr>EzR6y-RxUoQXYituMT9@NCf8^XGieo$2@NKY8Bu z{ILtp7mi+JUF^E#aH(^^exTzA`yV<69R@px9EZ9uJ6-M>o;Q5riu;w*SG}*EyB2Wm z(#ZUg;pqt>?FMZqM9Va~FNLGD$lbNT*KP&%S`^@Co zcfWZ2GB6c8HU3=m{L`|I+Sd?{wJo{Z|>UW?q-PQGavbE$eOnyO?(qGr8}v z?<+r;e(3oa^zrVej8C6_1NVgU`*8t=>i_@%AY({UO#lFTCIA3{ga82g0001h=l}q9 zFaQARU;qF*m;eA5aGbhPJOBU#97#k$R5;58!GBL1WdOkO?>+aU*WUGaODS!k8^XrW z2_Y^~TqM!B7vvi-@fGL`*!HK1{x_$|7L6`5`{AY&afHcGM-j?Oq3yMI*FMjkpN}^9 z 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 diff --git a/exif/src/test/resources/test2.jpg b/exif/src/test/resources/test2.jpg deleted file mode 100644 index 335f16d54261ae7e9e7602af1c9e26cebe480882..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3386 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 diff --git a/exif/src/test/resources/test3.jpg b/exif/src/test/resources/test3.jpg deleted file mode 100644 index 61a5591fe47f004204bf8e37bd0162e1af43de63..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3486 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 diff --git a/exif/src/test/resources/test3.jpg_original b/exif/src/test/resources/test3.jpg_original deleted file mode 100644 index 335f16d54261ae7e9e7602af1c9e26cebe480882..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3386 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 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