remove unused module exif. define compile_sdk_version build script variable.

This commit is contained in:
tateisu 2021-02-25 10:24:08 +09:00
parent 22cec0cb97
commit 9f000ea212
32 changed files with 6 additions and 6162 deletions

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -1,7 +1,7 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion target_sdk_version
compileSdkVersion compile_sdk_version
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8

View File

@ -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

1
exif/.gitignore vendored
View File

@ -1 +0,0 @@
/build

View File

@ -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()
}

View File

@ -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

View File

@ -1,3 +0,0 @@
<manifest package="it.sephiroth.android.library.exif2">
<application/>
</manifest>

View File

@ -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<ExifParser.Section> = 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>(IfdData.TYPE_IFD_COUNT)
// the compressed thumbnail.
// null if there is no compressed thumbnail.
var compressedThumbnail : ByteArray? = null
private val mStripBytes = ArrayList<ByteArray?>()
val stripList : List<ByteArray>?
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<ExifTag>
get() = ArrayList<ExifTag>()
.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<ExifTag>? =
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<ExifTag>? =
ArrayList<ExifTag>()
.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")
}
}

View File

@ -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)

View File

@ -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<ExifTag>()
.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())
}
}
}
}
}
}

View File

@ -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"
}
}

View File

@ -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<Rational>?
get() = mValue as? Array<Rational>
/**
* 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<Rational>) : 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 <reified T : Any> 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<Rational>
if(ra != null) return setValue(ra)
// Nulls in this array are treated as zeroes.
@Suppress("UNCHECKED_CAST")
val sa = obj as? Array<Short?>
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<Int?>
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<Long?>
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<Byte?>
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<Rational>) : Boolean =
null != value.find { it.numerator !in 0 .. UNSIGNED_LONG_MAX || it.denominator !in 0 .. UNSIGNED_LONG_MAX }
private fun checkOverflowForRational(value : Array<Rational>) : 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<Rational>() -> when {
vb is Array<*> && vb.isArrayOf<Rational>() -> 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 -> ""
}
}
}

View File

@ -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<Rational>) : 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()
}
}

View File

@ -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<Short, ExifTag>()
// 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<ExifTag>
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
}
}

View File

@ -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
}
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -1,7 +0,0 @@
package it.sephiroth.android.library.exif2.utils
internal fun <E> Collection<E>?.notEmpty() : Collection<E>? =
if(this?.isNotEmpty() == true) this else null
internal fun <E> List<E>?.notEmpty() : List<E>? =
if(this?.isNotEmpty() == true) this else null

View File

@ -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<Int?, Throwable?> =
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<ByteArray?, Throwable?> =
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<Int?, Throwable?>
var rvT : Pair<ByteArray?, Throwable?>
// 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
) // <java.lang.IllegalStateException: stop before hitting compressed data>
rvT = getThumbnailBytes(fileName)
assertNotNull(
fileName,
rvT.second
) // <java.lang.IllegalStateException: stop before hitting compressed data>
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -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