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>ihdr</w>
<w>infos</w>
<w>iptc</w>
<w>jfif</w>
<w>kapt</w>
<w>kddi</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
import android.util.Log
import it.sephiroth.android.library.exif2.utils.notEmpty
import java.io.UnsupportedEncodingException
import java.nio.ByteOrder
@ -33,94 +34,74 @@ import java.util.Arrays
* @see IfdData
*/
@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 imageWidth = - 1
var jpegProcess : Short = 0
var mUncompressedDataPosition = 0
/**
* Gets the strip count.
*/
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 stripCount : Int
get() = mStripBytes.size
/**
* Decodes the user comment tag into string as specified in the EXIF
* standard. Returns null if decoding failed.
*/
// 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[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))
?: return null
if(tag.componentCount < 8) {
if(tag.componentCount < 8)
return null
}
val buf = ByteArray(tag.componentCount)
tag.getBytes(buf)
val code = ByteArray(8)
System.arraycopy(buf, 0, code, 0, 8)
return try {
when {
code.contentEquals(USER_COMMENT_ASCII) -> String(
buf,
8,
buf.size - 8,
Charsets.US_ASCII
)
code.contentEquals(USER_COMMENT_JIS) -> String(
buf,
8,
buf.size - 8,
Charset.forName("EUC-JP")
)
code.contentEquals(USER_COMMENT_UNICODE) -> String(
buf,
8,
buf.size - 8,
Charsets.UTF_16
)
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
}
}
/**
* Returns a list of all [ExifTag]s in the ExifData or null if there
* are none.
*/
val allTags : List<ExifTag>?
get() {
val ret = ArrayList<ExifTag>()
mIfdDatas.forEach { it?.allTags?.forEach { tag -> ret.add(tag) } }
return if(ret.isEmpty()) null else ret
}
// 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 stripList : List<ByteArray>?
get() = mStripBytes.filterNotNull().notEmpty()
/**
* 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.
* Otherwise returns null.
*/
fun getTag(tag : Short, ifd : Int) : ExifTag? {
val ifdData = mIfdDatas[ifd]
return ifdData?.getTag(tag)
}
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? {
if(tag != null) {
val ifd = tag.ifd
return addTag(tag, ifd)
fun addTag(tag : ExifTag?) : ExifTag? =
when(tag) {
null -> null
else -> addTag(tag, tag.ifd)
}
return null
}
/**
* 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? {
if(tag != null && ExifTag.isValidIfd(ifdId)) {
val ifdData = getOrCreateIfdData(ifdId)
return ifdData.setTag(tag)
private fun addTag(tag : ExifTag?, ifdId : Int) : ExifTag? =
when {
tag == null -> null
! ExifTag.isValidIfd(ifdId) -> null
else -> getOrCreateIfdData(ifdId).setTag(tag)
}
return null
}
/**
* Returns the [IfdData] object corresponding to a given IFD or
* generates one if none exist.
*/
private fun getOrCreateIfdData(ifdId : Int) : IfdData {
var ifdData : IfdData? = mIfdDatas[ifdId]
var ifdData = mIfdDatas[ifdId]
if(ifdData == null) {
ifdData = IfdData(ifdId)
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.
*/
protected fun removeThumbnailData() {
fun removeThumbnailData() {
clearThumbnailAndStrips()
mIfdDatas[IfdId.TYPE_IFD_1] = null
mIfdDatas[IfdData.TYPE_IFD_1] = null
}
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
* are none.
*/
fun getAllTagsForIfd(ifd : Int) : List<ExifTag>? {
val d = mIfdDatas[ifd] ?: return null
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
}
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.
@ -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 ifd2 = getIfdData(i)
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
* exists or null.
*/
fun getIfdData(ifdId : Int) : IfdData? {
return if(ExifTag.isValidIfd(ifdId)) {
mIfdDatas[ifdId]
} else null
fun getIfdData(ifdId : Int) = when {
! ExifTag.isValidIfd(ifdId) -> null
else -> mIfdDatas[ifdId]
}
fun setImageSize(imageWidth : Int, imageLength : Int) {
@ -307,7 +273,7 @@ internal open class ExifData( val byteOrder : ByteOrder ) {
override fun hashCode() : Int {
var result = byteOrder.hashCode()
result = 31 * result + (sections?.hashCode() ?: 0)
result = 31 * result + (sections.hashCode())
result = 31 * result + mIfdDatas.contentHashCode()
result = 31 * result + (compressedThumbnail?.contentHashCode() ?: 0)
result = 31 * result + mStripBytes.hashCode()
@ -330,5 +296,8 @@ internal open class ExifData( val byteOrder : ByteOrder ) {
private val USER_COMMENT_UNICODE =
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
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.ArrayList
import java.util.*
@Suppress("unused")
internal class ExifOutputStream(private val mInterface : ExifInterface) {
/**
* Gets the Exif header to be written into the JPEF file.
*/
/**
* Sets the ExifData to be written into the JPEG file. Should be called
* before writing image data.
*/
var exifData : ExifData? = null
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(
@ -49,13 +47,9 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
@Throws(IOException::class)
fun writeExifData(out : OutputStream) {
if(exifData == null) {
return
}
Log.v(TAG, "Writing exif data...")
val nullTags = stripNullValueTags(exifData !!)
val nullTags = stripNullValueTags(exifData)
createRequiredIfdAndTag()
val exifSize = calculateAllOffset()
// Log.i(TAG, "exifSize: " + (exifSize + 8));
@ -64,7 +58,8 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
}
val outputStream = BufferedOutputStream(out, STREAMBUFFER_SIZE)
val dataOutputStream = OrderedDataOutputStream(outputStream)
val dataOutputStream =
OrderedDataOutputStream(outputStream)
dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN)
@ -73,12 +68,12 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
dataOutputStream.writeShort((exifSize + 8).toShort())
dataOutputStream.writeInt(EXIF_HEADER)
dataOutputStream.writeShort(0x0000.toShort())
if(exifData !!.byteOrder == ByteOrder.BIG_ENDIAN) {
if(exifData.byteOrder == ByteOrder.BIG_ENDIAN) {
dataOutputStream.writeShort(TIFF_BIG_ENDIAN)
} else {
dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN)
}
dataOutputStream.setByteOrder(exifData !!.byteOrder)
dataOutputStream.setByteOrder(exifData.byteOrder)
dataOutputStream.writeShort(TIFF_HEADER)
dataOutputStream.writeInt(8)
writeAllTags(dataOutputStream)
@ -86,57 +81,55 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
writeThumbnail(dataOutputStream)
for(t in nullTags) {
exifData !!.addTag(t)
exifData.addTag(t)
}
dataOutputStream.flush()
}
private fun stripNullValueTags(data : ExifData) : ArrayList<ExifTag> {
val nullTags = ArrayList<ExifTag>()
for(t in data.allTags !!) {
if(t.getValue() == null && ! ExifInterface.isOffsetTag(t.tagId)) {
data.removeTag(t.tagId, t.ifd)
nullTags.add(t)
// 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)
}
}
}
}
return nullTags
}
@Throws(IOException::class)
private fun writeThumbnail(dataOutputStream : OrderedDataOutputStream) {
if(exifData !!.hasCompressedThumbnail()) {
val compressedThumbnail = exifData.compressedThumbnail
if(compressedThumbnail != null) {
Log.d(TAG, "writing thumbnail..")
dataOutputStream.write(exifData !!.compressedThumbnail !!)
} else if(exifData !!.hasUncompressedStrip()) {
Log.d(TAG, "writing uncompressed strip..")
for(i in 0 until exifData !!.stripCount) {
dataOutputStream.write(exifData !!.getStrip(i) !!)
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(IfdId.TYPE_IFD_0) !!, dataOutputStream)
writeIfd(exifData !!.getIfdData(IfdId.TYPE_IFD_EXIF) !!, dataOutputStream)
val interoperabilityIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY)
if(interoperabilityIfd != null) {
writeIfd(interoperabilityIfd, dataOutputStream)
}
val gpsIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_GPS)
if(gpsIfd != null) {
writeIfd(gpsIfd, dataOutputStream)
}
val ifd1 = exifData !!.getIfdData(IfdId.TYPE_IFD_1)
if(ifd1 != null) {
writeIfd(exifData !!.getIfdData(IfdId.TYPE_IFD_1) !!, dataOutputStream)
}
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) {
val tags = ifd.allTags
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)
@ -166,8 +159,8 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
private fun calculateOffsetOfIfd(ifd : IfdData, offsetArg : Int) : Int {
var offset = offsetArg
offset += 2 + ifd.tagCount * TAG_SIZE + 4
val tags = ifd.allTags
for(tag in tags) {
for(tag in ifd.allTagsCollection) {
if(tag.dataSize > 4) {
tag.offset = offset
offset += tag.dataSize
@ -178,49 +171,53 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
@Throws(IOException::class)
private fun createRequiredIfdAndTag() {
// 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) {
ifd0 = IfdData(IfdId.TYPE_IFD_0)
exifData !!.addIfdData(ifd0)
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(IfdId.TYPE_IFD_EXIF)
var exifIfd = exifData.getIfdData(IfdData.TYPE_IFD_EXIF)
if(exifIfd == null) {
exifIfd = IfdData(IfdId.TYPE_IFD_EXIF)
exifData !!.addIfdData(exifIfd)
exifIfd = IfdData(IfdData.TYPE_IFD_EXIF)
exifData.addIfdData(exifIfd)
}
// GPS IFD
val gpsIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_GPS)
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)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_GPS_IFD}")
ifd0.setTag(gpsOffsetTag)
}
// Interoperability IFD
val interIfd = exifData !!.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY)
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)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_INTEROPERABILITY_IFD}")
exifIfd.setTag(interOffsetTag)
}
var ifd1 = exifData !!.getIfdData(IfdId.TYPE_IFD_1)
var ifd1 = exifData.getIfdData(IfdData.TYPE_IFD_1)
// thumbnail
val compressedThumbnail = exifData.compressedThumbnail
val stripList = exifData.stripList
when {
exifData !!.hasCompressedThumbnail() -> {
compressedThumbnail != null -> {
if(ifd1 == null) {
ifd1 = IfdData(IfdId.TYPE_IFD_1)
exifData !!.addIfdData(ifd1)
ifd1 = IfdData(IfdData.TYPE_IFD_1)
exifData.addIfdData(ifd1)
}
val offsetTag =
@ -232,34 +229,35 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
mInterface.buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH}")
lengthTag.setValue(exifData !!.compressedThumbnail !!.size)
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))
}
exifData !!.hasUncompressedStrip() -> {
stripList != null -> {
if(ifd1 == null) {
ifd1 = IfdData(IfdId.TYPE_IFD_1)
exifData !!.addIfdData(ifd1)
ifd1 = IfdData(IfdData.TYPE_IFD_1)
exifData.addIfdData(ifd1)
}
val stripCount = exifData !!.stripCount
val offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_STRIP_OFFSETS}")
val lengthTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS)
?: throw IOException("No definition for crucial exif tag: ${ExifInterface.TAG_STRIP_BYTE_COUNTS}")
val lengths = LongArray(stripCount)
for(i in 0 until exifData !!.stripCount) {
lengths[i] = exifData !!.getStrip(i) !!.size.toLong()
}
lengthTag.setValue(lengths)
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))
@ -272,50 +270,55 @@ internal class ExifOutputStream(private val mInterface : ExifInterface) {
private fun calculateAllOffset() : Int {
var offset = TIFF_HEADER_SIZE.toInt()
val exifData = this.exifData !!
val ifd0 = exifData.getIfdData(IfdId.TYPE_IFD_0)
offset = calculateOffsetOfIfd(ifd0 !!, offset)
ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD))
?.setValue(offset)
val exifIfd = exifData.getIfdData(IfdId.TYPE_IFD_EXIF)
offset = calculateOffsetOfIfd(exifIfd !!, offset)
val interIfd = exifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY)
if(interIfd != null) {
exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
?.setValue(offset)
offset = calculateOffsetOfIfd(interIfd, offset)
val ifd0 = exifData.getIfdData(IfdData.TYPE_IFD_0)?.also {
offset = calculateOffsetOfIfd(it, offset)
}
val gpsIfd = exifData.getIfdData(IfdId.TYPE_IFD_GPS)
if(gpsIfd != null) {
ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD))
val exifIfd = exifData.getIfdData(IfdData.TYPE_IFD_EXIF)?.also { it ->
ifd0?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD))
?.setValue(offset)
offset = calculateOffsetOfIfd(gpsIfd, offset)
offset = calculateOffsetOfIfd(it, offset)
}
val ifd1 = exifData.getIfdData(IfdId.TYPE_IFD_1)
if(ifd1 != null) {
ifd0.offsetToNextIfd = offset
offset = calculateOffsetOfIfd(ifd1, offset)
exifData.getIfdData(IfdData.TYPE_IFD_INTEROPERABILITY)?.also { it ->
exifIfd?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
?.setValue(offset)
offset = calculateOffsetOfIfd(it, offset)
}
// thumbnail
if(exifData .hasCompressedThumbnail()) {
ifd1 !!.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT))
exifData.getIfdData(IfdData.TYPE_IFD_GPS)?.also {
ifd0?.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD))
?.setValue(offset)
offset += exifData .compressedThumbnail !!.size
} else if(exifData .hasUncompressedStrip()) {
val stripCount = exifData .stripCount
val offsets = LongArray(stripCount)
for(i in 0 until exifData .stripCount) {
offsets[i] = offset.toLong()
offset += exifData .getStrip(i) !!.size
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)
}
ifd1 !!.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS))
?.setValue(offsets)
}
return offset
}

View File

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

View File

@ -37,12 +37,13 @@ internal class ExifReader(private val mInterface : ExifInterface) {
@Throws(ExifInvalidFormatException::class, IOException::class)
fun read(inputStream : InputStream, options : Int) : ExifData {
val parser = ExifParser.parse(inputStream, options, mInterface)
val exifData = ExifData(parser.byteOrder )
exifData.sections = parser.sections
exifData.mUncompressedDataPosition = parser.uncompressedDataPosition
exifData.qualityGuess = parser.qualityGuess
exifData.jpegProcess = parser.jpegProcess
val 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
@ -51,33 +52,34 @@ internal class ExifReader(private val mInterface : ExifInterface) {
exifData.setImageSize(w, h)
}
var tag : ExifTag?
var event = parser.next()
while(event != ExifParser.EVENT_END) {
when(event) {
ExifParser.EVENT_START_OF_IFD -> exifData.addIfdData(IfdData(parser.currentIfd))
ExifParser.EVENT_START_OF_IFD ->
exifData.addIfdData(IfdData(parser.currentIfd))
ExifParser.EVENT_NEW_TAG -> {
tag = parser.tag
if(! tag !!.hasValue()) {
parser.registerForTagValue(tag)
} else {
// Log.v(TAG, "parsing id " + tag.getTagId() + " = " + tag);
if(parser.isDefinedTag(tag.ifd, tag.tagId.toInt())) {
exifData.getIfdData(tag.ifd) !!.setTag(tag)
} else {
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 -> {
tag = parser.tag
if(tag !!.dataType == ExifTag.TYPE_UNDEFINED) {
val tag = parser.tag !!
if(tag.dataType == ExifTag.TYPE_UNDEFINED) {
parser.readFullTagValue(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.
/*
* @see IfdId.TYPE_IFD_0
* @see IfdId.TYPE_IFD_1
* @see IfdId.TYPE_IFD_EXIF
* @see IfdId.TYPE_IFD_GPS
* @see IfdId.TYPE_IFD_INTEROPERABILITY
* @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,
@ -69,7 +69,7 @@ open class ExifTag internal constructor(
*/
// TODO: fix integer overflows with this
var componentCount : Int = 0
var componentCount : Int = componentCount
private set
// The value (array of elements of type Tag Type)
@ -83,7 +83,6 @@ open class ExifTag internal constructor(
val dataSize : Int
get() = componentCount * getElementSize(dataType)
/**
* Gets the value as a byte array. This method should be used for tags of
* type [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE].
@ -124,9 +123,9 @@ open class ExifTag internal constructor(
*/
// Truncates
val valueAsInts : IntArray?
get() = when(val v = mValue){
is LongArray-> IntArray(v.size){ v[it].toInt()}
else ->null
get() = when(val v = mValue) {
is LongArray -> IntArray(v.size) { v[it].toInt() }
else -> null
}
/**
@ -143,7 +142,6 @@ open class ExifTag internal constructor(
else -> null
}
/**
* Gets the [.TYPE_ASCII] data.
*
@ -159,11 +157,6 @@ open class ExifTag internal constructor(
val stringByte : ByteArray?
get() = mValue as? ByteArray
init {
this.componentCount = componentCount
mValue = null
}
/**
* Sets the component count of this tag. Call this function before
* setValue() if the length of value does not match the component count.
@ -176,9 +169,8 @@ open class ExifTag internal constructor(
* Returns true if this ExifTag contains value; otherwise, this tag will
* contain an offset value that is determined when the tag is written.
*/
fun hasValue() : Boolean {
return mValue != null
}
val hasValue :Boolean
get() = mValue != null
/**
* 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.
*
*/
fun setValue(value : Int) : Boolean {
return setValue(intArrayOf(value))
}
fun setValue(value : Int) = setValue(intArrayOf(value))
/**
* 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.
*
*/
fun setValue(value : Long) : Boolean {
return setValue(longArrayOf(value))
}
fun setValue(value : Long) = setValue(longArrayOf(value))
/**
* Sets Rational values into this tag. This method should be used for tags
@ -309,8 +297,7 @@ open class ExifTag internal constructor(
*
* @see Rational
*/
fun setValue(value : Rational) : Boolean =
setValue(arrayOf(value))
fun setValue(value : Rational) =setValue(arrayOf(value))
/**
* 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
}
componentCount = length
mValue = ByteArray(length).also{
mValue = ByteArray(length).also {
System.arraycopy(value, offset, it, 0, length)
}
return true
@ -348,8 +335,7 @@ open class ExifTag internal constructor(
* * The component count in the definition for this tag is not 1.
*
*/
fun setValue(value : Byte) : Boolean =
setValue(byteArrayOf(value))
fun setValue(value : Byte) = setValue(byteArrayOf(value))
/**
* 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 Long -> return setValue(obj.toLong())
else ->{
else -> {
@Suppress("UNCHECKED_CAST")
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.
@Suppress("UNCHECKED_CAST")
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.
@Suppress("UNCHECKED_CAST")
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.
@Suppress("UNCHECKED_CAST")
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.
@Suppress("UNCHECKED_CAST")
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
}
@ -487,7 +475,7 @@ open class ExifTag internal constructor(
*/
fun getValueAsByte(defaultValue : Byte) : Byte {
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.
*/
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
@ -514,7 +502,7 @@ open class ExifTag internal constructor(
*/
private fun getValueAsRational(defaultValue : Rational) : Rational {
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 {
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 {
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
get() = mHasDefinedDefaultComponentCount
set(value){
set(value) {
mHasDefinedDefaultComponentCount = value
}
private fun checkOverflowForUnsignedShort(value : IntArray) : Boolean {
for(v in value) {
if(v > UNSIGNED_SHORT_MAX || v < 0) {
return true
}
}
return false
}
private fun checkOverflowForUnsignedShort(value : IntArray) : Boolean =
null != value.find { it !in 0 .. UNSIGNED_SHORT_MAX }
private fun checkOverflowForUnsignedLong(value : LongArray) : Boolean {
for(v in value) {
if(v < 0 || v > UNSIGNED_LONG_MAX) {
return true
}
}
return false
}
private fun checkOverflowForUnsignedLong(value : LongArray) : Boolean =
null != value.find { it !in 0 .. UNSIGNED_LONG_MAX }
private fun checkOverflowForUnsignedLong(value : IntArray) : Boolean {
for(v in value) {
if(v < 0) {
return true
}
}
return false
}
private fun checkOverflowForUnsignedLong(value : IntArray) : Boolean =
null != value.find { it < 0 }
private fun checkOverflowForUnsignedRational(value : Array<Rational>) : Boolean {
for(v in value) {
if(v.numerator < 0 || v.denominator < 0 || v.numerator > UNSIGNED_LONG_MAX || v.denominator > UNSIGNED_LONG_MAX) {
return true
}
}
return false
}
private fun 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 {
for(v in value) {
if(v.numerator < LONG_MIN || v.denominator < LONG_MIN || v.numerator > LONG_MAX || v.denominator > LONG_MAX) {
return true
}
}
return false
}
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()
@ -766,8 +724,6 @@ open class ExifTag internal constructor(
}
}
companion object {
/**
* 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 =
when(ifdId) {
IfdId.TYPE_IFD_0,
IfdId.TYPE_IFD_1,
IfdId.TYPE_IFD_EXIF,
IfdId.TYPE_IFD_INTEROPERABILITY,
IfdId.TYPE_IFD_GPS -> true
IfdData.TYPE_IFD_0,
IfdData.TYPE_IFD_1,
IfdData.TYPE_IFD_EXIF,
IfdData.TYPE_IFD_INTEROPERABILITY,
IfdData.TYPE_IFD_GPS -> true
else -> false
}
@ -883,11 +839,4 @@ open class ExifTag internal constructor(
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
/**
* 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.
// an IfdData with given IFD ID.
internal class IfdData(
// the ID of this IFD.
val id : Int
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.
@ -56,9 +54,9 @@ internal class IfdData(
val tagCount : Int
get() = mExifTags.size
// a array the contains all [ExifTag] in this IFD.
val allTags : Array<ExifTag>
get() = mExifTags.values.toTypedArray()
// Collection the contains all [ExifTag] in this IFD.
val allTagsCollection : Collection<ExifTag>
get() = mExifTags.values
// checkCollision
fun contains(tagId : Short) : Boolean {
@ -87,19 +85,12 @@ internal class IfdData(
* IFDs offset or thumbnail offset will be ignored.
*/
override fun equals(other : Any?) : Boolean {
if(other === null) return false
if(other === this) return true
if(other is IfdData) {
if(other === this) return true
if(other.id == id && other.tagCount == tagCount) {
val tags = other.allTags
for(tag in tags) {
if(ExifInterface.isOffsetTag(tag.tagId)) {
continue
}
val tag2 = mExifTags[tag.tagId]
if(tag != tag2) {
return false
}
for(tag in other.allTagsCollection) {
if(ExifInterface.isOffsetTag(tag.tagId)) continue
if(tag != mExifTags[tag.tagId]) return false
}
return true
}

View File

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

View File

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

View File

@ -14,8 +14,9 @@
* 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.IOException
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.SparseIntArray
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.*
import org.junit.Test
import java.io.File
import java.io.FileInputStream
@ -12,7 +11,7 @@ class Test1 {
@Test
fun testLog() {
Log.v("TEST","test")
Log.v("TEST", "test")
assertTrue("using android.util.Log", true)
}
@ -32,29 +31,48 @@ class Test1 {
}
}
private fun getOrientation(fileName : String) : Pair<Int?,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
)
}
.getTagIntValue(ExifInterface.TAG_ORIENTATION)
private fun getOrientation(fileName : String) : Pair<Int?, 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
)
}
.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) {
val(o,ex) = getOrientation(fileName)
assertTrue("testNotJpegSub",o ==null && ex!=null)
if( ex!= null) println("exception raised: ${ex::class.java} ${ex.message}")
val (o, ex) = getOrientation(fileName)
assertTrue("testNotJpegSub", o == null && ex != null)
if(ex != null) println("exception raised: ${ex::class.java} ${ex.message}")
}
@Test
@ -66,23 +84,32 @@ class Test1 {
@Test
fun testJpeg() {
var fileName :String
var rv : Pair<Int?,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)
var fileName : String
var rvO : Pair<Int?, Throwable?>
var rvT : Pair<ByteArray?, Throwable?>
// this file has orientation 6.
fileName = "test3.jpg"
rv = getOrientation(fileName)
assertEquals(fileName,rv.first , 6)
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>
}
}