This commit is contained in:
tateisu 2019-10-08 03:46:19 +09:00
parent c4b0c04126
commit 0c3a222fc7
14 changed files with 639 additions and 864 deletions

View File

@ -51,6 +51,8 @@
<w>idempotency</w> <w>idempotency</w>
<w>ihdr</w> <w>ihdr</w>
<w>infos</w> <w>infos</w>
<w>iptc</w>
<w>jfif</w>
<w>kapt</w> <w>kapt</w>
<w>kddi</w> <w>kddi</w>
<w>kenglxn</w> <w>kenglxn</w>

View File

@ -1,38 +0,0 @@
///*
// * Copyright (C) 2012 The Android Open Source Project
// *
// * Licensed under the Apache License, Version 2.0 (the "License");
// * you may not use this file except in compliance with the License.
// * You may obtain a copy of the License at
// *
// * http://www.apache.org/licenses/LICENSE-2.0
// *
// * Unless required by applicable law or agreed to in writing, software
// * distributed under the License is distributed on an "AS IS" BASIS,
// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// * See the License for the specific language governing permissions and
// * limitations under the License.
// */
//
package it.sephiroth.android.library.exif2
//
//import java.io.InputStream
//import java.nio.ByteBuffer
//import kotlin.math.min
//
//internal class ByteBufferInputStream(private val mBuf : ByteBuffer) : InputStream() {
//
// override fun read() : Int = when {
// ! mBuf.hasRemaining() -> - 1
// else -> mBuf.get().toInt() and 0xFF
// }
//
// override fun read(bytes : ByteArray, off : Int, len : Int) : Int {
// if(! mBuf.hasRemaining()) return - 1
// val willRead = min(len, mBuf.remaining())
// mBuf.get(bytes, off, willRead)
// return willRead
// }
//}

View File

@ -17,6 +17,7 @@
package it.sephiroth.android.library.exif2 package it.sephiroth.android.library.exif2
import android.util.Log import android.util.Log
import it.sephiroth.android.library.exif2.utils.notEmpty
import java.io.UnsupportedEncodingException import java.io.UnsupportedEncodingException
import java.nio.ByteOrder import java.nio.ByteOrder
@ -33,94 +34,74 @@ import java.util.Arrays
* @see IfdData * @see IfdData
*/ */
@Suppress("unused") @Suppress("unused")
internal open class ExifData( val byteOrder : ByteOrder ) { 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
) {
var sections : List<ExifParser.Section>? = null
private val mIfdDatas = arrayOfNulls<IfdData>(IfdId.TYPE_IFD_COUNT)
/**
* Gets the compressed thumbnail. Returns null if there is no compressed
* thumbnail.
*
* @see .hasCompressedThumbnail
*/
/**
* Sets the compressed thumbnail.
*/
var compressedThumbnail : ByteArray? = null
private val mStripBytes = ArrayList<ByteArray?>()
var qualityGuess = 0
private var imageLength = - 1 private var imageLength = - 1
private var imageWidth = - 1 private var imageWidth = - 1
var jpegProcess : Short = 0
var mUncompressedDataPosition = 0
/** private val mIfdDatas = arrayOfNulls<IfdData>(IfdData.TYPE_IFD_COUNT)
* Gets the strip count.
*/ // the compressed thumbnail.
// null if there is no compressed thumbnail.
var compressedThumbnail : ByteArray? = null
private val mStripBytes = ArrayList<ByteArray?>()
val stripCount : Int val stripCount : Int
get() = mStripBytes.size get() = mStripBytes.size
/** // Decodes the user comment tag into string as specified in the EXIF standard.
* Decodes the user comment tag into string as specified in the EXIF // Returns null if decoding failed.
* standard. Returns null if decoding failed.
*/
val userComment : String? val userComment : String?
get() { get() {
val ifdData = mIfdDatas[IfdId.TYPE_IFD_0] ?: return null
val ifdData = mIfdDatas[IfdData.TYPE_IFD_0]
?: return null
val tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)) val tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT))
?: return null ?: return null
if(tag.componentCount < 8) {
if(tag.componentCount < 8)
return null return null
}
val buf = ByteArray(tag.componentCount)
tag.getBytes(buf)
val code = ByteArray(8)
System.arraycopy(buf, 0, code, 0, 8)
return try { return try {
when {
code.contentEquals(USER_COMMENT_ASCII) -> String( val buf = ByteArray(tag.componentCount)
buf, tag.getBytes(buf)
8,
buf.size - 8, val code = ByteArray(8)
Charsets.US_ASCII System.arraycopy(buf, 0, code, 0, 8)
)
code.contentEquals(USER_COMMENT_JIS) -> String( val charset = when {
buf, code.contentEquals(USER_COMMENT_ASCII) -> Charsets.US_ASCII
8, code.contentEquals(USER_COMMENT_JIS) -> eucJp
buf.size - 8, code.contentEquals(USER_COMMENT_UNICODE) -> Charsets.UTF_16
Charset.forName("EUC-JP")
)
code.contentEquals(USER_COMMENT_UNICODE) -> String(
buf,
8,
buf.size - 8,
Charsets.UTF_16
)
else -> null else -> null
} }
if(charset == null) null else String(buf, 8, buf.size - 8, charset)
} catch(e : UnsupportedEncodingException) { } catch(e : UnsupportedEncodingException) {
Log.w(TAG, "Failed to decode the user comment") Log.w(TAG, "Failed to decode the user comment")
null null
} }
} }
/** // list of all [ExifTag]s in the ExifData
* Returns a list of all [ExifTag]s in the ExifData or null if there // or null if there are none.
* are none. val allTags : List<ExifTag>
*/ get() = ArrayList<ExifTag>()
val allTags : List<ExifTag>? .apply { mIfdDatas.forEach { if(it != null) addAll(it.allTagsCollection) } }
get() {
val ret = ArrayList<ExifTag>()
mIfdDatas.forEach { it?.allTags?.forEach { tag -> ret.add(tag) } }
return if(ret.isEmpty()) null else ret
}
val imageSize : IntArray val imageSize : IntArray
get() = intArrayOf(imageWidth, imageLength) get() = intArrayOf(imageWidth, imageLength)
val stripList : List<ByteArray>?
get() = mStripBytes.filterNotNull().notEmpty()
/** /**
* Returns true it this header contains a compressed thumbnail. * Returns true it this header contains a compressed thumbnail.
*/ */
@ -166,41 +147,36 @@ internal open class ExifData( val byteOrder : ByteOrder ) {
* Returns the tag with a given TID in the given IFD if the tag exists. * Returns the tag with a given TID in the given IFD if the tag exists.
* Otherwise returns null. * Otherwise returns null.
*/ */
fun getTag(tag : Short, ifd : Int) : ExifTag? { fun getTag(tag : Short, ifd : Int) : ExifTag? =
val ifdData = mIfdDatas[ifd] mIfdDatas[ifd]?.getTag(tag)
return ifdData?.getTag(tag)
}
/** /**
* Adds the given ExifTag to its default IFD and returns an existing ExifTag * Adds the given ExifTag to its default IFD and returns an existing ExifTag
* with the same TID or null if none exist. * with the same TID or null if none exist.
*/ */
fun addTag(tag : ExifTag?) : ExifTag? { fun addTag(tag : ExifTag?) : ExifTag? =
if(tag != null) { when(tag) {
val ifd = tag.ifd null -> null
return addTag(tag, ifd) else -> addTag(tag, tag.ifd)
} }
return null
}
/** /**
* Adds the given ExifTag to the given IFD and returns an existing ExifTag * Adds the given ExifTag to the given IFD and returns an existing ExifTag
* with the same TID or null if none exist. * with the same TID or null if none exist.
*/ */
private fun addTag(tag : ExifTag?, ifdId : Int) : ExifTag? { private fun addTag(tag : ExifTag?, ifdId : Int) : ExifTag? =
if(tag != null && ExifTag.isValidIfd(ifdId)) { when {
val ifdData = getOrCreateIfdData(ifdId) tag == null -> null
return ifdData.setTag(tag) ! ExifTag.isValidIfd(ifdId) -> null
else -> getOrCreateIfdData(ifdId).setTag(tag)
} }
return null
}
/** /**
* Returns the [IfdData] object corresponding to a given IFD or * Returns the [IfdData] object corresponding to a given IFD or
* generates one if none exist. * generates one if none exist.
*/ */
private fun getOrCreateIfdData(ifdId : Int) : IfdData { private fun getOrCreateIfdData(ifdId : Int) : IfdData {
var ifdData : IfdData? = mIfdDatas[ifdId] var ifdData = mIfdDatas[ifdId]
if(ifdData == null) { if(ifdData == null) {
ifdData = IfdData(ifdId) ifdData = IfdData(ifdId)
mIfdDatas[ifdId] = ifdData mIfdDatas[ifdId] = ifdData
@ -211,9 +187,9 @@ internal open class ExifData( val byteOrder : ByteOrder ) {
/** /**
* Removes the thumbnail and its related tags. IFD1 will be removed. * Removes the thumbnail and its related tags. IFD1 will be removed.
*/ */
protected fun removeThumbnailData() { fun removeThumbnailData() {
clearThumbnailAndStrips() clearThumbnailAndStrips()
mIfdDatas[IfdId.TYPE_IFD_1] = null mIfdDatas[IfdData.TYPE_IFD_1] = null
} }
fun clearThumbnailAndStrips() { fun clearThumbnailAndStrips() {
@ -233,17 +209,8 @@ internal open class ExifData( val byteOrder : ByteOrder ) {
* Returns a list of all [ExifTag]s in a given IFD or null if there * Returns a list of all [ExifTag]s in a given IFD or null if there
* are none. * are none.
*/ */
fun getAllTagsForIfd(ifd : Int) : List<ExifTag>? { fun getAllTagsForIfd(ifd : Int) : List<ExifTag>? =
val d = mIfdDatas[ifd] ?: return null mIfdDatas[ifd]?.allTagsCollection?.notEmpty()?.toList()
val tags = d.allTags
val ret = ArrayList<ExifTag>(tags.size)
for(t in tags) {
ret.add(t)
}
return if(ret.size == 0) {
null
} else ret
}
// Returns a list of all [ExifTag]s with a given TID // Returns a list of all [ExifTag]s with a given TID
// or null if there are none. // or null if there are none.
@ -280,7 +247,7 @@ internal open class ExifData( val byteOrder : ByteOrder ) {
} }
} }
for(i in 0 until IfdId.TYPE_IFD_COUNT) { for(i in 0 until IfdData.TYPE_IFD_COUNT) {
val ifd1 = other.getIfdData(i) val ifd1 = other.getIfdData(i)
val ifd2 = getIfdData(i) val ifd2 = getIfdData(i)
if(ifd1 != ifd2) return false if(ifd1 != ifd2) return false
@ -294,10 +261,9 @@ internal open class ExifData( val byteOrder : ByteOrder ) {
* Returns the [IfdData] object corresponding to a given IFD if it * Returns the [IfdData] object corresponding to a given IFD if it
* exists or null. * exists or null.
*/ */
fun getIfdData(ifdId : Int) : IfdData? { fun getIfdData(ifdId : Int) = when {
return if(ExifTag.isValidIfd(ifdId)) { ! ExifTag.isValidIfd(ifdId) -> null
mIfdDatas[ifdId] else -> mIfdDatas[ifdId]
} else null
} }
fun setImageSize(imageWidth : Int, imageLength : Int) { fun setImageSize(imageWidth : Int, imageLength : Int) {
@ -307,7 +273,7 @@ internal open class ExifData( val byteOrder : ByteOrder ) {
override fun hashCode() : Int { override fun hashCode() : Int {
var result = byteOrder.hashCode() var result = byteOrder.hashCode()
result = 31 * result + (sections?.hashCode() ?: 0) result = 31 * result + (sections.hashCode())
result = 31 * result + mIfdDatas.contentHashCode() result = 31 * result + mIfdDatas.contentHashCode()
result = 31 * result + (compressedThumbnail?.contentHashCode() ?: 0) result = 31 * result + (compressedThumbnail?.contentHashCode() ?: 0)
result = 31 * result + mStripBytes.hashCode() result = 31 * result + mStripBytes.hashCode()
@ -330,5 +296,8 @@ internal open class ExifData( val byteOrder : ByteOrder ) {
private val USER_COMMENT_UNICODE = private val USER_COMMENT_UNICODE =
byteArrayOf(0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00) byteArrayOf(0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00)
private val eucJp = Charset.forName("EUC-JP")
} }
} }

View File

@ -17,25 +17,23 @@
package it.sephiroth.android.library.exif2 package it.sephiroth.android.library.exif2
import android.util.Log import android.util.Log
import it.sephiroth.android.library.exif2.utils.OrderedDataOutputStream
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.util.ArrayList import java.util.*
@Suppress("unused") @Suppress("unused")
internal class ExifOutputStream(private val mInterface : ExifInterface) { internal class ExifOutputStream(
/**
* Gets the Exif header to be written into the JPEF file. private val mInterface : ExifInterface,
*/
/** // the Exif header to be written into the JPEG file.
* Sets the ExifData to be written into the JPEG file. Should be called private val exifData : ExifData
* before writing image data. ) {
*/
var exifData : ExifData? = null
private val mBuffer = ByteBuffer.allocate(4) private val mBuffer = ByteBuffer.allocate(4)
private fun requestByteToBuffer( private fun requestByteToBuffer(
@ -49,13 +47,9 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
@Throws(IOException::class) @Throws(IOException::class)
fun writeExifData(out : OutputStream) { fun writeExifData(out : OutputStream) {
if(exifData == null) {
return
}
Log.v(TAG, "Writing exif data...") Log.v(TAG, "Writing exif data...")
val nullTags = stripNullValueTags(exifData !!) val nullTags = stripNullValueTags(exifData)
createRequiredIfdAndTag() createRequiredIfdAndTag()
val exifSize = calculateAllOffset() val exifSize = calculateAllOffset()
// Log.i(TAG, "exifSize: " + (exifSize + 8)); // Log.i(TAG, "exifSize: " + (exifSize + 8));
@ -64,7 +58,8 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
} }
val outputStream = BufferedOutputStream(out, STREAMBUFFER_SIZE) val outputStream = BufferedOutputStream(out, STREAMBUFFER_SIZE)
val dataOutputStream = OrderedDataOutputStream(outputStream) val dataOutputStream =
OrderedDataOutputStream(outputStream)
dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN) dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN)
@ -73,12 +68,12 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
dataOutputStream.writeShort((exifSize + 8).toShort()) dataOutputStream.writeShort((exifSize + 8).toShort())
dataOutputStream.writeInt(EXIF_HEADER) dataOutputStream.writeInt(EXIF_HEADER)
dataOutputStream.writeShort(0x0000.toShort()) dataOutputStream.writeShort(0x0000.toShort())
if(exifData !!.byteOrder == ByteOrder.BIG_ENDIAN) { if(exifData.byteOrder == ByteOrder.BIG_ENDIAN) {
dataOutputStream.writeShort(TIFF_BIG_ENDIAN) dataOutputStream.writeShort(TIFF_BIG_ENDIAN)
} else { } else {
dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN) dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN)
} }
dataOutputStream.setByteOrder(exifData !!.byteOrder) dataOutputStream.setByteOrder(exifData.byteOrder)
dataOutputStream.writeShort(TIFF_HEADER) dataOutputStream.writeShort(TIFF_HEADER)
dataOutputStream.writeInt(8) dataOutputStream.writeInt(8)
writeAllTags(dataOutputStream) writeAllTags(dataOutputStream)
@ -86,57 +81,55 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
writeThumbnail(dataOutputStream) writeThumbnail(dataOutputStream)
for(t in nullTags) { for(t in nullTags) {
exifData !!.addTag(t) exifData.addTag(t)
} }
dataOutputStream.flush() dataOutputStream.flush()
} }
private fun stripNullValueTags(data : ExifData) : ArrayList<ExifTag> { // strip tags that has null value
val nullTags = ArrayList<ExifTag>() // return list of removed tags
for(t in data.allTags !!) { private fun stripNullValueTags(data : ExifData) =
if(t.getValue() == null && ! ExifInterface.isOffsetTag(t.tagId)) { ArrayList<ExifTag>()
data.removeTag(t.tagId, t.ifd) .apply {
nullTags.add(t) for(t in data.allTags) {
if(t.getValue() == null && ! ExifInterface.isOffsetTag(t.tagId)) {
data.removeTag(t.tagId, t.ifd)
add(t)
}
}
} }
}
return nullTags
}
@Throws(IOException::class) @Throws(IOException::class)
private fun writeThumbnail(dataOutputStream : OrderedDataOutputStream) { private fun writeThumbnail(dataOutputStream : OrderedDataOutputStream) {
if(exifData !!.hasCompressedThumbnail()) { val compressedThumbnail = exifData.compressedThumbnail
if(compressedThumbnail != null) {
Log.d(TAG, "writing thumbnail..") Log.d(TAG, "writing thumbnail..")
dataOutputStream.write(exifData !!.compressedThumbnail !!) dataOutputStream.write(compressedThumbnail)
} else if(exifData !!.hasUncompressedStrip()) { } else {
Log.d(TAG, "writing uncompressed strip..") val stripList = exifData.stripList
for(i in 0 until exifData !!.stripCount) { if(stripList != null) {
dataOutputStream.write(exifData !!.getStrip(i) !!) Log.d(TAG, "writing uncompressed strip..")
stripList.forEach {
dataOutputStream.write(it)
}
} }
} }
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun writeAllTags(dataOutputStream : OrderedDataOutputStream) { private fun writeAllTags(dataOutputStream : OrderedDataOutputStream) {
writeIfd(exifData !!.getIfdData(IfdId.TYPE_IFD_0) !!, dataOutputStream) writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_0), dataOutputStream)
writeIfd(exifData !!.getIfdData(IfdId.TYPE_IFD_EXIF) !!, dataOutputStream) writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_EXIF), dataOutputStream)
val interoperabilityIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY) writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_INTEROPERABILITY), dataOutputStream)
if(interoperabilityIfd != null) { writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_GPS), dataOutputStream)
writeIfd(interoperabilityIfd, dataOutputStream) writeIfd(exifData.getIfdData(IfdData.TYPE_IFD_1), dataOutputStream)
}
val gpsIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_GPS)
if(gpsIfd != null) {
writeIfd(gpsIfd, dataOutputStream)
}
val ifd1 = exifData !!.getIfdData(IfdId.TYPE_IFD_1)
if(ifd1 != null) {
writeIfd(exifData !!.getIfdData(IfdId.TYPE_IFD_1) !!, dataOutputStream)
}
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun writeIfd(ifd : IfdData, dataOutputStream : OrderedDataOutputStream) { private fun writeIfd(ifd : IfdData?, dataOutputStream : OrderedDataOutputStream) {
val tags = ifd.allTags ifd ?: return
val tags = ifd.allTagsCollection
dataOutputStream.writeShort(tags.size.toShort()) dataOutputStream.writeShort(tags.size.toShort())
for(tag in tags) { for(tag in tags) {
dataOutputStream.writeShort(tag.tagId) dataOutputStream.writeShort(tag.tagId)
@ -166,8 +159,8 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
private fun calculateOffsetOfIfd(ifd : IfdData, offsetArg : Int) : Int { private fun calculateOffsetOfIfd(ifd : IfdData, offsetArg : Int) : Int {
var offset = offsetArg var offset = offsetArg
offset += 2 + ifd.tagCount * TAG_SIZE + 4 offset += 2 + ifd.tagCount * TAG_SIZE + 4
val tags = ifd.allTags
for(tag in tags) { for(tag in ifd.allTagsCollection) {
if(tag.dataSize > 4) { if(tag.dataSize > 4) {
tag.offset = offset tag.offset = offset
offset += tag.dataSize offset += tag.dataSize
@ -178,49 +171,53 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
@Throws(IOException::class) @Throws(IOException::class)
private fun createRequiredIfdAndTag() { private fun createRequiredIfdAndTag() {
// IFD0 is required for all file // IFD0 is required for all file
var ifd0 = exifData !!.getIfdData(IfdId.TYPE_IFD_0) var ifd0 = exifData.getIfdData(IfdData.TYPE_IFD_0)
if(ifd0 == null) { if(ifd0 == null) {
ifd0 = IfdData(IfdId.TYPE_IFD_0) ifd0 = IfdData(IfdData.TYPE_IFD_0)
exifData !!.addIfdData(ifd0) exifData.addIfdData(ifd0)
} }
val exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD) val exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD)
?: throw IOException("No definition for crucial exif tag: " + ExifInterface.TAG_EXIF_IFD) ?: throw IOException("No definition for crucial exif tag: " + ExifInterface.TAG_EXIF_IFD)
ifd0.setTag(exifOffsetTag) ifd0.setTag(exifOffsetTag)
// Exif IFD is required for all files. // Exif IFD is required for all files.
var exifIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_EXIF) var exifIfd = exifData.getIfdData(IfdData.TYPE_IFD_EXIF)
if(exifIfd == null) { if(exifIfd == null) {
exifIfd = IfdData(IfdId.TYPE_IFD_EXIF) exifIfd = IfdData(IfdData.TYPE_IFD_EXIF)
exifData !!.addIfdData(exifIfd) exifData.addIfdData(exifIfd)
} }
// GPS IFD // GPS IFD
val gpsIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_GPS) val gpsIfd = exifData.getIfdData(IfdData.TYPE_IFD_GPS)
if(gpsIfd != null) { if(gpsIfd != null) {
val gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD) val gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD)
?: throw IOException("No definition for crucial exif tag: " + ExifInterface.TAG_GPS_IFD) ?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_GPS_IFD}")
ifd0.setTag(gpsOffsetTag) ifd0.setTag(gpsOffsetTag)
} }
// Interoperability IFD // Interoperability IFD
val interIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY) val interIfd = exifData.getIfdData(IfdData.TYPE_IFD_INTEROPERABILITY)
if(interIfd != null) { if(interIfd != null) {
val interOffsetTag = val interOffsetTag =
mInterface.buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD) mInterface.buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD)
?: throw IOException("No definition for crucial exif tag: " + ExifInterface.TAG_INTEROPERABILITY_IFD) ?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_INTEROPERABILITY_IFD}")
exifIfd.setTag(interOffsetTag) exifIfd.setTag(interOffsetTag)
} }
var ifd1 = exifData !!.getIfdData(IfdId.TYPE_IFD_1) var ifd1 = exifData.getIfdData(IfdData.TYPE_IFD_1)
// thumbnail // thumbnail
val compressedThumbnail = exifData.compressedThumbnail
val stripList = exifData.stripList
when { when {
exifData !!.hasCompressedThumbnail() -> {
compressedThumbnail != null -> {
if(ifd1 == null) { if(ifd1 == null) {
ifd1 = IfdData(IfdId.TYPE_IFD_1) ifd1 = IfdData(IfdData.TYPE_IFD_1)
exifData !!.addIfdData(ifd1) exifData.addIfdData(ifd1)
} }
val offsetTag = val offsetTag =
@ -232,34 +229,35 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
mInterface.buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH) mInterface.buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH}") ?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH}")
lengthTag.setValue(exifData !!.compressedThumbnail !!.size) lengthTag.setValue(compressedThumbnail.size)
ifd1.setTag(lengthTag) ifd1.setTag(lengthTag)
// Get rid of tags for uncompressed if they exist. // Get rid of tags for uncompressed if they exist.
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)) ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)) ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS))
} }
exifData !!.hasUncompressedStrip() -> {
stripList != null -> {
if(ifd1 == null) { if(ifd1 == null) {
ifd1 = IfdData(IfdId.TYPE_IFD_1) ifd1 = IfdData(IfdData.TYPE_IFD_1)
exifData !!.addIfdData(ifd1) exifData.addIfdData(ifd1)
} }
val stripCount = exifData !!.stripCount
val offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS) val offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS)
?: throw IOException("No definition for crucial exif tag: ${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) val lengthTag =
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_STRIP_BYTE_COUNTS}") mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS)
val lengths = LongArray(stripCount) ?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_STRIP_BYTE_COUNTS}")
for(i in 0 until exifData !!.stripCount) {
lengths[i] = exifData !!.getStrip(i) !!.size.toLong() val bytesList = LongArray(stripList.size)
} stripList.forEachIndexed { index, bytes -> bytesList[index] = bytes.size.toLong() }
lengthTag.setValue(lengths) lengthTag.setValue(bytesList)
ifd1.setTag(offsetTag) ifd1.setTag(offsetTag)
ifd1.setTag(lengthTag) ifd1.setTag(lengthTag)
// Get rid of tags for compressed if they exist. // 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))
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH))
} }
ifd1 != null -> { ifd1 != null -> {
// Get rid of offset and length tags if there is no thumbnail. // 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_OFFSETS))
@ -272,50 +270,55 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
private fun calculateAllOffset() : Int { private fun calculateAllOffset() : Int {
var offset = TIFF_HEADER_SIZE.toInt() var offset = TIFF_HEADER_SIZE.toInt()
val exifData = this.exifData !!
val ifd0 = exifData.getIfdData(IfdId.TYPE_IFD_0)
offset = calculateOffsetOfIfd(ifd0 !!, offset)
ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD))
?.setValue(offset)
val exifIfd = exifData.getIfdData(IfdId.TYPE_IFD_EXIF) val ifd0 = exifData.getIfdData(IfdData.TYPE_IFD_0)?.also {
offset = calculateOffsetOfIfd(exifIfd !!, offset) offset = calculateOffsetOfIfd(it, offset)
val interIfd = exifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY)
if(interIfd != null) {
exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
?.setValue(offset)
offset = calculateOffsetOfIfd(interIfd, offset)
} }
val gpsIfd = exifData.getIfdData(IfdId.TYPE_IFD_GPS) val exifIfd = exifData.getIfdData(IfdData.TYPE_IFD_EXIF)?.also { it ->
if(gpsIfd != null) { ifd0?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD))
ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD))
?.setValue(offset) ?.setValue(offset)
offset = calculateOffsetOfIfd(gpsIfd, offset) offset = calculateOffsetOfIfd(it, offset)
} }
val ifd1 = exifData.getIfdData(IfdId.TYPE_IFD_1) exifData.getIfdData(IfdData.TYPE_IFD_INTEROPERABILITY)?.also { it ->
if(ifd1 != null) { exifIfd?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
ifd0.offsetToNextIfd = offset ?.setValue(offset)
offset = calculateOffsetOfIfd(ifd1, offset) offset = calculateOffsetOfIfd(it, offset)
} }
// thumbnail exifData.getIfdData(IfdData.TYPE_IFD_GPS)?.also {
if(exifData .hasCompressedThumbnail()) { ifd0?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD))
ifd1 !!.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
?.setValue(offset) ?.setValue(offset)
offset += exifData .compressedThumbnail !!.size offset = calculateOffsetOfIfd(it, offset)
} else if(exifData .hasUncompressedStrip()) { }
val stripCount = exifData .stripCount
val offsets = LongArray(stripCount) val ifd1 = exifData.getIfdData(IfdData.TYPE_IFD_1)?.also {
for(i in 0 until exifData .stripCount) { ifd0?.offsetToNextIfd = offset
offsets[i] = offset.toLong() offset = calculateOffsetOfIfd(it, offset)
offset += exifData .getStrip(i) !!.size }
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)
} }
ifd1 !!.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
?.setValue(offsets)
} }
return offset return offset
} }

View File

@ -17,6 +17,7 @@
package it.sephiroth.android.library.exif2 package it.sephiroth.android.library.exif2
import android.util.Log import android.util.Log
import it.sephiroth.android.library.exif2.utils.CountedDataInputStream
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -40,15 +41,15 @@ private constructor(
/** /**
* the ID of current IFD. * the ID of current IFD.
* *
* @see IfdId.TYPE_IFD_0 * @see IfdData.TYPE_IFD_0
* @see IfdId.TYPE_IFD_1 * @see IfdData.TYPE_IFD_1
* @see IfdId.TYPE_IFD_GPS * @see IfdData.TYPE_IFD_GPS
* @see IfdId.TYPE_IFD_INTEROPERABILITY * @see IfdData.TYPE_IFD_INTEROPERABILITY
* @see IfdId.TYPE_IFD_EXIF * @see IfdData.TYPE_IFD_EXIF
*/ */
var currentIfd : Int = 0 var currentIfd : Int = 0
private set private set
/** /**
* If [.next] return [.EVENT_NEW_TAG] or * If [.next] return [.EVENT_NEW_TAG] or
* [.EVENT_VALUE_OF_REGISTERED_TAG], call this function to get the * [.EVENT_VALUE_OF_REGISTERED_TAG], call this function to get the
@ -154,10 +155,10 @@ private constructor(
throw ExifInvalidFormatException("Invalid offset $offset") throw ExifInvalidFormatException("Invalid offset $offset")
} }
mIfd0Position = offset.toInt() mIfd0Position = offset.toInt()
currentIfd = IfdId.TYPE_IFD_0 currentIfd = IfdData.TYPE_IFD_0
if(isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) { if(isIfdRequested(IfdData.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) {
registerIfd(IfdId.TYPE_IFD_0, offset) registerIfd(IfdData.TYPE_IFD_0, offset)
if(offset != DEFAULT_IFD0_OFFSET.toLong()) { if(offset != DEFAULT_IFD0_OFFSET.toLong()) {
val ba = ByteArray(offset.toInt() - DEFAULT_IFD0_OFFSET) val ba = ByteArray(offset.toInt() - DEFAULT_IFD0_OFFSET)
mDataAboveIfd0 = ba mDataAboveIfd0 = ba
@ -182,15 +183,16 @@ private constructor(
@Throws(IOException::class, ExifInvalidFormatException::class) @Throws(IOException::class, ExifInvalidFormatException::class)
private fun seekTiffData(inputStream : InputStream) : CountedDataInputStream { private fun seekTiffData(inputStream : InputStream) : CountedDataInputStream {
val dataStream = CountedDataInputStream(inputStream) val dataStream =
CountedDataInputStream(inputStream)
var tiffStream : CountedDataInputStream? = null var tiffStream : CountedDataInputStream? = null
var a = dataStream.readUnsignedByte() var a = dataStream.readUnsignedByte()
val b = dataStream.readUnsignedByte() val b = dataStream.readUnsignedByte()
if(a == 137 && b == 80) error("maybe PNG image") if(a == 137 && b == 80) error("maybe PNG image")
if(a != 0xFF || b != JpegHeader.TAG_SOI) error("invalid jpeg header") if(a != 0xFF || b != JpegHeader.TAG_SOI) error("invalid jpeg header")
while(true) { while(true) {
@ -213,9 +215,6 @@ private constructor(
Log.w(TAG, "Extraneous ${a - 1} padding bytes before section $marker") Log.w(TAG, "Extraneous ${a - 1} padding bytes before section $marker")
} }
val section = Section()
section.type = marker
// Read the length of the section. // Read the length of the section.
val lh = dataStream.readByte().toInt() val lh = dataStream.readByte().toInt()
val ll = dataStream.readByte().toInt() val ll = dataStream.readByte().toInt()
@ -225,8 +224,6 @@ private constructor(
throw ExifInvalidFormatException("Invalid marker") throw ExifInvalidFormatException("Invalid marker")
} }
section.size = itemlen
data = ByteArray(itemlen) data = ByteArray(itemlen)
data[0] = lh.toByte() data[0] = lh.toByte()
data[1] = ll.toByte() data[1] = ll.toByte()
@ -240,7 +237,7 @@ private constructor(
throw ExifInvalidFormatException("Premature end of file? Expecting " + (itemlen - 2) + ", received " + got) throw ExifInvalidFormatException("Premature end of file? Expecting " + (itemlen - 2) + ", received " + got)
} }
section.data = data val section = Section(type = marker, size = itemlen, data = data)
var ignore = false var ignore = false
@ -286,7 +283,9 @@ private constructor(
// header = Exif, headerTail=\0\0 // header = Exif, headerTail=\0\0
if(header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) { if(header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
tiffStream = tiffStream =
CountedDataInputStream(ByteArrayInputStream(data, 8, itemlen - 8)) CountedDataInputStream(
ByteArrayInputStream(data, 8, itemlen - 8)
)
tiffStream.end = itemlen - 6 tiffStream.end = itemlen - 6
ignore = false ignore = false
} else { } else {
@ -428,11 +427,11 @@ private constructor(
private fun isIfdRequested(ifdType : Int) : Boolean { private fun isIfdRequested(ifdType : Int) : Boolean {
when(ifdType) { when(ifdType) {
IfdId.TYPE_IFD_0 -> return mOptions and ExifInterface.Options.OPTION_IFD_0 != 0 IfdData.TYPE_IFD_0 -> return mOptions and ExifInterface.Options.OPTION_IFD_0 != 0
IfdId.TYPE_IFD_1 -> return mOptions and ExifInterface.Options.OPTION_IFD_1 != 0 IfdData.TYPE_IFD_1 -> return mOptions and ExifInterface.Options.OPTION_IFD_1 != 0
IfdId.TYPE_IFD_EXIF -> return mOptions and ExifInterface.Options.OPTION_IFD_EXIF != 0 IfdData.TYPE_IFD_EXIF -> return mOptions and ExifInterface.Options.OPTION_IFD_EXIF != 0
IfdId.TYPE_IFD_GPS -> return mOptions and ExifInterface.Options.OPTION_IFD_GPS != 0 IfdData.TYPE_IFD_GPS -> return mOptions and ExifInterface.Options.OPTION_IFD_GPS != 0
IfdId.TYPE_IFD_INTEROPERABILITY -> return mOptions and ExifInterface.Options.OPTION_IFD_INTEROPERABILITY != 0 IfdData.TYPE_IFD_INTEROPERABILITY -> return mOptions and ExifInterface.Options.OPTION_IFD_INTEROPERABILITY != 0
} }
return false return false
} }
@ -440,17 +439,17 @@ private constructor(
private fun needToParseOffsetsInCurrentIfd() : Boolean { private fun needToParseOffsetsInCurrentIfd() : Boolean {
return when(currentIfd) { return when(currentIfd) {
IfdId.TYPE_IFD_0 -> IfdData.TYPE_IFD_0 ->
isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdData.TYPE_IFD_EXIF) ||
isIfdRequested(IfdId.TYPE_IFD_GPS) || isIfdRequested(IfdData.TYPE_IFD_GPS) ||
isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY) || isIfdRequested(IfdData.TYPE_IFD_INTEROPERABILITY) ||
isIfdRequested(IfdId.TYPE_IFD_1) isIfdRequested(IfdData.TYPE_IFD_1)
IfdId.TYPE_IFD_1 -> isThumbnailRequested IfdData.TYPE_IFD_1 -> isThumbnailRequested
IfdId.TYPE_IFD_EXIF -> IfdData.TYPE_IFD_EXIF ->
// The offset to interoperability IFD is located in Exif IFD // The offset to interoperability IFD is located in Exif IFD
isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY) isIfdRequested(IfdData.TYPE_IFD_INTEROPERABILITY)
else -> false else -> false
} }
@ -508,11 +507,11 @@ private constructor(
return EVENT_NEW_TAG return EVENT_NEW_TAG
} else if(offset == endOfTags) { } else if(offset == endOfTags) {
// There is a link to ifd1 at the end of ifd0 // There is a link to ifd1 at the end of ifd0
if(currentIfd == IfdId.TYPE_IFD_0) { if(currentIfd == IfdData.TYPE_IFD_0) {
val ifdOffset = readUnsignedLong() val ifdOffset = readUnsignedLong()
if(isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested) { if(isIfdRequested(IfdData.TYPE_IFD_1) || isThumbnailRequested) {
if(ifdOffset != 0L) { if(ifdOffset != 0L) {
registerIfd(IfdId.TYPE_IFD_1, ifdOffset) registerIfd(IfdData.TYPE_IFD_1, ifdOffset)
} }
} }
} else { } else {
@ -610,9 +609,9 @@ private constructor(
} }
val ifdOffset = readUnsignedLong() val ifdOffset = readUnsignedLong()
// For ifd0, there is a link to ifd1 in the end of all tags // For ifd0, there is a link to ifd1 in the end of all tags
if(currentIfd == IfdId.TYPE_IFD_0 && (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested)) { if(currentIfd == IfdData.TYPE_IFD_0 && (isIfdRequested(IfdData.TYPE_IFD_1) || isThumbnailRequested)) {
if(ifdOffset > 0) { if(ifdOffset > 0) {
registerIfd(IfdId.TYPE_IFD_1, ifdOffset) registerIfd(IfdData.TYPE_IFD_1, ifdOffset)
} }
} }
} }
@ -720,19 +719,19 @@ private constructor(
val tid = tag.tagId val tid = tag.tagId
val ifd = tag.ifd val ifd = tag.ifd
if(tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) { if(tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) {
if(isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { if(isIfdRequested(IfdData.TYPE_IFD_EXIF) || isIfdRequested(IfdData.TYPE_IFD_INTEROPERABILITY)) {
registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0)) registerIfd(IfdData.TYPE_IFD_EXIF, tag.getValueAt(0))
} }
} else if(tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) { } else if(tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) {
if(isIfdRequested(IfdId.TYPE_IFD_GPS)) { if(isIfdRequested(IfdData.TYPE_IFD_GPS)) {
registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0)) registerIfd(IfdData.TYPE_IFD_GPS, tag.getValueAt(0))
} }
} else if(tid == TAG_INTEROPERABILITY_IFD && checkAllowed( } else if(tid == TAG_INTEROPERABILITY_IFD && checkAllowed(
ifd, ifd,
ExifInterface.TAG_INTEROPERABILITY_IFD ExifInterface.TAG_INTEROPERABILITY_IFD
)) { )) {
if(isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { if(isIfdRequested(IfdData.TYPE_IFD_INTEROPERABILITY)) {
registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0)) registerIfd(IfdData.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0))
} }
} else if(tid == TAG_JPEG_INTERCHANGE_FORMAT && checkAllowed( } else if(tid == TAG_JPEG_INTERCHANGE_FORMAT && checkAllowed(
ifd, ifd,
@ -750,7 +749,7 @@ private constructor(
} }
} else if(tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) { } else if(tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) {
if(isThumbnailRequested) { if(isThumbnailRequested) {
if(tag.hasValue()) { if(tag.hasValue) {
for(i in 0 until tag.componentCount) { for(i in 0 until tag.componentCount) {
if(tag.dataType == ExifTag.TYPE_UNSIGNED_SHORT) { if(tag.dataType == ExifTag.TYPE_UNSIGNED_SHORT) {
registerUncompressedStrip(i, tag.getValueAt(i)) registerUncompressedStrip(i, tag.getValueAt(i))
@ -765,16 +764,16 @@ private constructor(
} else if(tid == TAG_STRIP_BYTE_COUNTS && checkAllowed( } else if(tid == TAG_STRIP_BYTE_COUNTS && checkAllowed(
ifd, ifd,
ExifInterface.TAG_STRIP_BYTE_COUNTS ExifInterface.TAG_STRIP_BYTE_COUNTS
) && isThumbnailRequested && tag.hasValue()) { ) && isThumbnailRequested && tag.hasValue) {
mStripSizeTag = tag mStripSizeTag = tag
} }
} }
fun isDefinedTag(ifdId : Int, tagId : Int) : Boolean { fun isDefinedTag(ifdId : Int, tagId : Short) : Boolean {
return mInterface.tagInfo.get( return mInterface.tagInfo.get(
ExifInterface.defineTag( ExifInterface.defineTag(
ifdId, ifdId,
tagId.toShort() tagId
) )
) != ExifInterface.DEFINITION_NULL ) != ExifInterface.DEFINITION_NULL
} }
@ -946,12 +945,8 @@ private constructor(
internal var isRequested : Boolean internal var isRequested : Boolean
) )
class Section { class Section(var size : Int, var type : Int, var data : ByteArray)
internal var size : Int = 0
internal var type : Int = 0
internal var data : ByteArray? = null
}
companion object { companion object {
private const val TAG = "ExifParser" private const val TAG = "ExifParser"

View File

@ -37,12 +37,13 @@ internal class ExifReader(private val mInterface : ExifInterface) {
@Throws(ExifInvalidFormatException::class, IOException::class) @Throws(ExifInvalidFormatException::class, IOException::class)
fun read(inputStream : InputStream, options : Int) : ExifData { fun read(inputStream : InputStream, options : Int) : ExifData {
val parser = ExifParser.parse(inputStream, options, mInterface) val parser = ExifParser.parse(inputStream, options, mInterface)
val exifData = ExifData(parser.byteOrder ) val exifData = ExifData(
exifData.sections = parser.sections byteOrder = parser.byteOrder,
exifData.mUncompressedDataPosition = parser.uncompressedDataPosition sections = parser.sections,
mUncompressedDataPosition = parser.uncompressedDataPosition,
exifData.qualityGuess = parser.qualityGuess qualityGuess = parser.qualityGuess,
exifData.jpegProcess = parser.jpegProcess jpegProcess = parser.jpegProcess
)
val w = parser.imageWidth val w = parser.imageWidth
val h = parser.imageLength val h = parser.imageLength
@ -51,33 +52,34 @@ internal class ExifReader(private val mInterface : ExifInterface) {
exifData.setImageSize(w, h) exifData.setImageSize(w, h)
} }
var tag : ExifTag?
var event = parser.next() var event = parser.next()
while(event != ExifParser.EVENT_END) { while(event != ExifParser.EVENT_END) {
when(event) { when(event) {
ExifParser.EVENT_START_OF_IFD -> exifData.addIfdData(IfdData(parser.currentIfd))
ExifParser.EVENT_START_OF_IFD ->
exifData.addIfdData(IfdData(parser.currentIfd))
ExifParser.EVENT_NEW_TAG -> { ExifParser.EVENT_NEW_TAG -> {
tag = parser.tag val tag = parser.tag
when {
tag == null ->
Log.w(TAG, "parser.tag is null")
if(! tag !!.hasValue()) {
parser.registerForTagValue(tag) ! tag.hasValue ->
} else { parser.registerForTagValue(tag)
// Log.v(TAG, "parsing id " + tag.getTagId() + " = " + tag);
if(parser.isDefinedTag(tag.ifd, tag.tagId.toInt())) { ! parser.isDefinedTag(tag.ifd, tag.tagId) ->
exifData.getIfdData(tag.ifd) !!.setTag(tag)
} else {
Log.w(TAG, "skip tag because not registered in the tag table:$tag") 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 -> { ExifParser.EVENT_VALUE_OF_REGISTERED_TAG -> {
tag = parser.tag val tag = parser.tag !!
if(tag !!.dataType == ExifTag.TYPE_UNDEFINED) { if(tag.dataType == ExifTag.TYPE_UNDEFINED) {
parser.readFullTagValue(tag) parser.readFullTagValue(tag)
} }
exifData.getIfdData(tag.ifd) !!.setTag(tag) exifData.getIfdData(tag.ifd) !!.setTag(tag)

View File

@ -52,11 +52,11 @@ open class ExifTag internal constructor(
// The ifd that this tag should be put in. the ID of the IFD this tag belongs to. // The ifd that this tag should be put in. the ID of the IFD this tag belongs to.
/* /*
* @see IfdId.TYPE_IFD_0 * @see IfdData.TYPE_IFD_0
* @see IfdId.TYPE_IFD_1 * @see IfdData.TYPE_IFD_1
* @see IfdId.TYPE_IFD_EXIF * @see IfdData.TYPE_IFD_EXIF
* @see IfdId.TYPE_IFD_GPS * @see IfdData.TYPE_IFD_GPS
* @see IfdId.TYPE_IFD_INTEROPERABILITY * @see IfdData.TYPE_IFD_INTEROPERABILITY
*/ */
var ifd : Int, var ifd : Int,
@ -69,7 +69,7 @@ open class ExifTag internal constructor(
*/ */
// TODO: fix integer overflows with this // TODO: fix integer overflows with this
var componentCount : Int = 0 var componentCount : Int = componentCount
private set private set
// The value (array of elements of type Tag Type) // The value (array of elements of type Tag Type)
@ -83,7 +83,6 @@ open class ExifTag internal constructor(
val dataSize : Int val dataSize : Int
get() = componentCount * getElementSize(dataType) get() = componentCount * getElementSize(dataType)
/** /**
* Gets the value as a byte array. This method should be used for tags of * Gets the value as a byte array. This method should be used for tags of
* type [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE]. * type [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE].
@ -124,9 +123,9 @@ open class ExifTag internal constructor(
*/ */
// Truncates // Truncates
val valueAsInts : IntArray? val valueAsInts : IntArray?
get() = when(val v = mValue){ get() = when(val v = mValue) {
is LongArray-> IntArray(v.size){ v[it].toInt()} is LongArray -> IntArray(v.size) { v[it].toInt() }
else ->null else -> null
} }
/** /**
@ -143,7 +142,6 @@ open class ExifTag internal constructor(
else -> null else -> null
} }
/** /**
* Gets the [.TYPE_ASCII] data. * Gets the [.TYPE_ASCII] data.
* *
@ -159,11 +157,6 @@ open class ExifTag internal constructor(
val stringByte : ByteArray? val stringByte : ByteArray?
get() = mValue as? ByteArray get() = mValue as? ByteArray
init {
this.componentCount = componentCount
mValue = null
}
/** /**
* Sets the component count of this tag. Call this function before * Sets the component count of this tag. Call this function before
* setValue() if the length of value does not match the component count. * setValue() if the length of value does not match the component count.
@ -176,9 +169,8 @@ open class ExifTag internal constructor(
* Returns true if this ExifTag contains value; otherwise, this tag will * Returns true if this ExifTag contains value; otherwise, this tag will
* contain an offset value that is determined when the tag is written. * contain an offset value that is determined when the tag is written.
*/ */
fun hasValue() : Boolean { val hasValue :Boolean
return mValue != null get() = mValue != null
}
/** /**
* Sets integer values into this tag. This method should be used for tags of * Sets integer values into this tag. This method should be used for tags of
@ -225,9 +217,7 @@ open class ExifTag internal constructor(
* * The component count in the definition of this tag is not 1. * * The component count in the definition of this tag is not 1.
* *
*/ */
fun setValue(value : Int) : Boolean { fun setValue(value : Int) = setValue(intArrayOf(value))
return setValue(intArrayOf(value))
}
/** /**
* Sets long values into this tag. This method should be used for tags of * Sets long values into this tag. This method should be used for tags of
@ -260,9 +250,7 @@ open class ExifTag internal constructor(
* * The component count in the definition for this tag is not 1. * * The component count in the definition for this tag is not 1.
* *
*/ */
fun setValue(value : Long) : Boolean { fun setValue(value : Long) = setValue(longArrayOf(value))
return setValue(longArrayOf(value))
}
/** /**
* Sets Rational values into this tag. This method should be used for tags * Sets Rational values into this tag. This method should be used for tags
@ -309,8 +297,7 @@ open class ExifTag internal constructor(
* *
* @see Rational * @see Rational
*/ */
fun setValue(value : Rational) : Boolean = fun setValue(value : Rational) =setValue(arrayOf(value))
setValue(arrayOf(value))
/** /**
* Sets byte values into this tag. This method should be used for tags of * Sets byte values into this tag. This method should be used for tags of
@ -332,7 +319,7 @@ open class ExifTag internal constructor(
return false return false
} }
componentCount = length componentCount = length
mValue = ByteArray(length).also{ mValue = ByteArray(length).also {
System.arraycopy(value, offset, it, 0, length) System.arraycopy(value, offset, it, 0, length)
} }
return true return true
@ -348,8 +335,7 @@ open class ExifTag internal constructor(
* * The component count in the definition for this tag is not 1. * * The component count in the definition for this tag is not 1.
* *
*/ */
fun setValue(value : Byte) : Boolean = fun setValue(value : Byte) = setValue(byteArrayOf(value))
setValue(byteArrayOf(value))
/** /**
* Sets the value for this tag using an appropriate setValue method for the * Sets the value for this tag using an appropriate setValue method for the
@ -375,31 +361,33 @@ open class ExifTag internal constructor(
is Int -> return setValue(obj.toInt()) is Int -> return setValue(obj.toInt())
is Long -> return setValue(obj.toLong()) is Long -> return setValue(obj.toLong())
else ->{ else -> {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val ra = obj as? Array<Rational> val ra = obj as? Array<Rational>
if(ra != null) return setValue( ra ) if(ra != null) return setValue(ra)
// Nulls in this array are treated as zeroes. // Nulls in this array are treated as zeroes.
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val sa = obj as? Array<Short?> val sa = obj as? Array<Short?>
if( sa != null) return setValue(IntArray(sa.size){ (sa[it]?.toInt() ?: 0) and 0xffff}) if(sa != null) return setValue(IntArray(sa.size) {
(sa[it]?.toInt() ?: 0) and 0xffff
})
// Nulls in this array are treated as zeroes. // Nulls in this array are treated as zeroes.
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val ia = obj as? Array<Int?> val ia = obj as? Array<Int?>
if( ia != null) return setValue(IntArray(ia.size){ ia[it] ?: 0 }) if(ia != null) return setValue(IntArray(ia.size) { ia[it] ?: 0 })
// Nulls in this array are treated as zeroes. // Nulls in this array are treated as zeroes.
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val la = obj as? Array<Long?> val la = obj as? Array<Long?>
if( la != null) return setValue(LongArray(la.size){ la[it] ?: 0L }) if(la != null) return setValue(LongArray(la.size) { la[it] ?: 0L })
// Nulls in this array are treated as zeroes. // Nulls in this array are treated as zeroes.
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val ba = obj as? Array<Byte?> val ba = obj as? Array<Byte?>
if( ba != null) return setValue(ByteArray(ba.size){ ba[it] ?: 0 }) if(ba != null) return setValue(ByteArray(ba.size) { ba[it] ?: 0 })
return false return false
} }
@ -487,7 +475,7 @@ open class ExifTag internal constructor(
*/ */
fun getValueAsByte(defaultValue : Byte) : Byte { fun getValueAsByte(defaultValue : Byte) : Byte {
val array = valueAsBytes val array = valueAsBytes
return if(array?.isNotEmpty()==true) array[0] else defaultValue return if(array?.isNotEmpty() == true) array[0] else defaultValue
} }
/** /**
@ -501,7 +489,7 @@ open class ExifTag internal constructor(
* @return the tag's value as a Rational, or the defaultValue. * @return the tag's value as a Rational, or the defaultValue.
*/ */
fun getValueAsRational(defaultValue : Long) : Rational = fun getValueAsRational(defaultValue : Long) : Rational =
getValueAsRational( Rational(defaultValue, 1)) getValueAsRational(Rational(defaultValue, 1))
/** /**
* Gets the value as a Rational. If there are more than 1 Rationals in this * Gets the value as a Rational. If there are more than 1 Rationals in this
@ -514,7 +502,7 @@ open class ExifTag internal constructor(
*/ */
private fun getValueAsRational(defaultValue : Rational) : Rational { private fun getValueAsRational(defaultValue : Rational) : Rational {
val array = valueAsRationals val array = valueAsRationals
return if(array?.isNotEmpty()==true) array[0] else defaultValue return if(array?.isNotEmpty() == true) array[0] else defaultValue
} }
/** /**
@ -528,7 +516,7 @@ open class ExifTag internal constructor(
*/ */
fun getValueAsInt(defaultValue : Int) : Int { fun getValueAsInt(defaultValue : Int) : Int {
val array = valueAsInts val array = valueAsInts
return if(array?.isNotEmpty()==true) array[0] else defaultValue return if(array?.isNotEmpty() == true) array[0] else defaultValue
} }
/** /**
@ -542,7 +530,7 @@ open class ExifTag internal constructor(
*/ */
fun getValueAsLong(defaultValue : Long) : Long { fun getValueAsLong(defaultValue : Long) : Long {
val array = valueAsLongs val array = valueAsLongs
return if(array?.isNotEmpty()==true) array[0] else defaultValue return if(array?.isNotEmpty() == true) array[0] else defaultValue
} }
/** /**
@ -636,54 +624,24 @@ open class ExifTag internal constructor(
var hasDefinedCount : Boolean var hasDefinedCount : Boolean
get() = mHasDefinedDefaultComponentCount get() = mHasDefinedDefaultComponentCount
set(value){ set(value) {
mHasDefinedDefaultComponentCount = value mHasDefinedDefaultComponentCount = value
} }
private fun checkOverflowForUnsignedShort(value : IntArray) : Boolean { private fun checkOverflowForUnsignedShort(value : IntArray) : Boolean =
for(v in value) { null != value.find { it !in 0 .. UNSIGNED_SHORT_MAX }
if(v > UNSIGNED_SHORT_MAX || v < 0) {
return true
}
}
return false
}
private fun checkOverflowForUnsignedLong(value : LongArray) : Boolean { private fun checkOverflowForUnsignedLong(value : LongArray) : Boolean =
for(v in value) { null != value.find { it !in 0 .. UNSIGNED_LONG_MAX }
if(v < 0 || v > UNSIGNED_LONG_MAX) {
return true
}
}
return false
}
private fun checkOverflowForUnsignedLong(value : IntArray) : Boolean { private fun checkOverflowForUnsignedLong(value : IntArray) : Boolean =
for(v in value) { null != value.find { it < 0 }
if(v < 0) {
return true
}
}
return false
}
private fun checkOverflowForUnsignedRational(value : Array<Rational>) : Boolean { private fun checkOverflowForUnsignedRational(value : Array<Rational>) : Boolean =
for(v in value) { null != value.find { it.numerator !in 0 .. UNSIGNED_LONG_MAX || it.denominator !in 0 .. UNSIGNED_LONG_MAX }
if(v.numerator < 0 || v.denominator < 0 || v.numerator > UNSIGNED_LONG_MAX || v.denominator > UNSIGNED_LONG_MAX) {
return true
}
}
return false
}
private fun checkOverflowForRational(value : Array<Rational>) : Boolean { private fun checkOverflowForRational(value : Array<Rational>) : Boolean =
for(v in value) { null != value.find { it.numerator !in LONG_MIN .. LONG_MAX || it.denominator !in LONG_MIN .. LONG_MAX }
if(v.numerator < LONG_MIN || v.denominator < LONG_MIN || v.numerator > LONG_MAX || v.denominator > LONG_MAX) {
return true
}
}
return false
}
override fun hashCode() : Int { override fun hashCode() : Int {
var result = tagId.toInt() var result = tagId.toInt()
@ -766,8 +724,6 @@ open class ExifTag internal constructor(
} }
} }
companion object { companion object {
/** /**
* The BYTE type in the EXIF standard. An 8-bit unsigned integer. * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
@ -832,11 +788,11 @@ open class ExifTag internal constructor(
*/ */
fun isValidIfd(ifdId : Int) : Boolean = fun isValidIfd(ifdId : Int) : Boolean =
when(ifdId) { when(ifdId) {
IfdId.TYPE_IFD_0, IfdData.TYPE_IFD_0,
IfdId.TYPE_IFD_1, IfdData.TYPE_IFD_1,
IfdId.TYPE_IFD_EXIF, IfdData.TYPE_IFD_EXIF,
IfdId.TYPE_IFD_INTEROPERABILITY, IfdData.TYPE_IFD_INTEROPERABILITY,
IfdId.TYPE_IFD_GPS -> true IfdData.TYPE_IFD_GPS -> true
else -> false else -> false
} }
@ -883,11 +839,4 @@ open class ExifTag internal constructor(
else -> "" else -> ""
} }
} }
} }
/**
* Equivalent to setValue(value, 0, value.length).
*/
/**
* Equivalent to getBytes(buffer, 0, buffer.length).
*/

View File

@ -18,35 +18,33 @@ package it.sephiroth.android.library.exif2
import java.util.HashMap import java.util.HashMap
/**
* The constants of the IFD ID defined in EXIF spec.
*/
object IfdId {
const val TYPE_IFD_0 = 0
const val TYPE_IFD_1 = 1
const val TYPE_IFD_EXIF = 2
const val TYPE_IFD_INTEROPERABILITY = 3
const val TYPE_IFD_GPS = 4
/* This is used in ExifData to allocate enough IfdData */
const val TYPE_IFD_COUNT = 5
val list = intArrayOf(
TYPE_IFD_0,
TYPE_IFD_1,
TYPE_IFD_EXIF,
TYPE_IFD_INTEROPERABILITY,
TYPE_IFD_GPS
)
}
// This class stores all the tags in an IFD. // This class stores all the tags in an IFD.
// an IfdData with given IFD ID. // an IfdData with given IFD ID.
internal class IfdData( internal class IfdData(
// the ID of this IFD. val id : Int // the ID of this IFD.
val id : Int
) { ) {
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>() private val mExifTags = HashMap<Short, ExifTag>()
// the offset of next IFD. // the offset of next IFD.
@ -56,9 +54,9 @@ internal class IfdData(
val tagCount : Int val tagCount : Int
get() = mExifTags.size get() = mExifTags.size
// a array the contains all [ExifTag] in this IFD. // Collection the contains all [ExifTag] in this IFD.
val allTags : Array<ExifTag> val allTagsCollection : Collection<ExifTag>
get() = mExifTags.values.toTypedArray() get() = mExifTags.values
// checkCollision // checkCollision
fun contains(tagId : Short) : Boolean { fun contains(tagId : Short) : Boolean {
@ -87,19 +85,12 @@ internal class IfdData(
* IFDs offset or thumbnail offset will be ignored. * IFDs offset or thumbnail offset will be ignored.
*/ */
override fun equals(other : Any?) : Boolean { override fun equals(other : Any?) : Boolean {
if(other === null) return false
if(other === this) return true
if(other is IfdData) { if(other is IfdData) {
if(other === this) return true
if(other.id == id && other.tagCount == tagCount) { if(other.id == id && other.tagCount == tagCount) {
val tags = other.allTags for(tag in other.allTagsCollection) {
for(tag in tags) { if(ExifInterface.isOffsetTag(tag.tagId)) continue
if(ExifInterface.isOffsetTag(tag.tagId)) { if(tag != mExifTags[tag.tagId]) return false
continue
}
val tag2 = mExifTags[tag.tagId]
if(tag != tag2) {
return false
}
} }
return true return true
} }

View File

@ -30,6 +30,7 @@ class Rational(
) { ) {
// copy from a Rational. // copy from a Rational.
@Suppress("unused")
constructor(r : Rational) : this( constructor(r : Rational) : this(
numerator = r.numerator, numerator = r.numerator,
denominator = r.denominator denominator = r.denominator

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package it.sephiroth.android.library.exif2 package it.sephiroth.android.library.exif2.utils
import java.io.EOFException import java.io.EOFException
import java.io.FilterInputStream import java.io.FilterInputStream

View File

@ -14,8 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package it.sephiroth.android.library.exif2 package it.sephiroth.android.library.exif2.utils
import it.sephiroth.android.library.exif2.Rational
import java.io.FilterOutputStream import java.io.FilterOutputStream
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream

View File

@ -0,0 +1,7 @@
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

@ -2,8 +2,7 @@ package it.sephiroth.android.library.exif2
import android.util.Log import android.util.Log
import android.util.SparseIntArray import android.util.SparseIntArray
import org.junit.Assert.assertEquals import org.junit.Assert.*
import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
@ -12,7 +11,7 @@ class Test1 {
@Test @Test
fun testLog() { fun testLog() {
Log.v("TEST","test") Log.v("TEST", "test")
assertTrue("using android.util.Log", true) assertTrue("using android.util.Log", true)
} }
@ -32,29 +31,48 @@ class Test1 {
} }
} }
private fun getOrientation(fileName : String) : Pair<Int?,Throwable?> = private fun getOrientation(fileName : String) : Pair<Int?, Throwable?> =
try{ try {
val o = FileInputStream(getFile(fileName)).use { inStream -> val o = FileInputStream(getFile(fileName)).use { inStream ->
ExifInterface() ExifInterface()
.apply { .apply {
readExif( readExif(
inStream, inStream,
ExifInterface.Options.OPTION_IFD_0 ExifInterface.Options.OPTION_IFD_0
or ExifInterface.Options.OPTION_IFD_1 or ExifInterface.Options.OPTION_IFD_1
or ExifInterface.Options.OPTION_IFD_EXIF or ExifInterface.Options.OPTION_IFD_EXIF
) )
} }
.getTagIntValue(ExifInterface.TAG_ORIENTATION) .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()
.apply {
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)
} }
Pair(o,null)
}catch(ex:Throwable){
Pair(null,ex)
}
private fun testNotJpegSub(fileName : String) { private fun testNotJpegSub(fileName : String) {
val(o,ex) = getOrientation(fileName) val (o, ex) = getOrientation(fileName)
assertTrue("testNotJpegSub",o ==null && ex!=null) assertTrue("testNotJpegSub", o == null && ex != null)
if( ex!= null) println("exception raised: ${ex::class.java} ${ex.message}") if(ex != null) println("exception raised: ${ex::class.java} ${ex.message}")
} }
@Test @Test
@ -66,23 +84,32 @@ class Test1 {
@Test @Test
fun testJpeg() { fun testJpeg() {
var fileName :String var fileName : String
var rv : Pair<Int?,Throwable?> var rvO : Pair<Int?, Throwable?>
var rvT : Pair<ByteArray?, Throwable?>
// this file has orientation 1
fileName = "test1.jpg"
rv = getOrientation(fileName)
assertEquals(fileName,1,rv.first)
// this file has no orientation, it raises exception.
fileName = "test2.jpg"
rv = getOrientation(fileName)
assertTrue(fileName,rv.second != null)
// this file has orientation 6. // this file has orientation 6.
fileName = "test3.jpg" fileName = "test3.jpg"
rv = getOrientation(fileName) rvO = getOrientation(fileName)
assertEquals(fileName,rv.first , 6) 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>
} }
} }