This commit is contained in:
tateisu 2019-10-08 06:19:45 +09:00
parent f3aa4e9fdf
commit c1f83e1706
6 changed files with 240 additions and 297 deletions

View File

@ -57,6 +57,7 @@
<w>kddi</w> <w>kddi</w>
<w>kenglxn</w> <w>kenglxn</w>
<w>kotlinx</w> <w>kotlinx</w>
<w>lparams</w>
<w>magick</w> <w>magick</w>
<w>mailto</w> <w>mailto</w>
<w>mimumedon</w> <w>mimumedon</w>

View File

@ -13,14 +13,14 @@ private val log = LogCategory("BitmapUtils")
val InputStream.imageOrientation : Int? val InputStream.imageOrientation : Int?
get() = try { get() = try {
ExifInterface().apply { ExifInterface()
readExif( .readExif(
this@imageOrientation, this@imageOrientation,
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)
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.w(ex, "imageOrientation: exif parse failed.") log.w(ex, "imageOrientation: exif parse failed.")
null null

View File

@ -16,6 +16,8 @@
package it.sephiroth.android.library.exif2 package it.sephiroth.android.library.exif2
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Log import android.util.Log
import it.sephiroth.android.library.exif2.utils.notEmpty import it.sephiroth.android.library.exif2.utils.notEmpty
@ -102,12 +104,28 @@ internal class ExifData(
val stripList : List<ByteArray>? val stripList : List<ByteArray>?
get() = mStripBytes.filterNotNull().notEmpty() get() = mStripBytes.filterNotNull().notEmpty()
/** val thumbnailBytes : ByteArray?
* Returns true it this header contains a compressed thumbnail. get() = when {
*/ compressedThumbnail != null -> compressedThumbnail
fun hasCompressedThumbnail() : Boolean { hasUncompressedStrip() -> null // TODO: implement this
return compressedThumbnail != null else -> null
} }
val thumbnailBitmap : Bitmap?
get() {
val compressedThumbnail = this.compressedThumbnail
if(compressedThumbnail != null) {
return BitmapFactory
.decodeByteArray(compressedThumbnail, 0, compressedThumbnail.size)
}
val stripList = this.stripList
if(stripList != null) {
// TODO: decoding uncompressed thumbnail is not implemented.
return null
}
return null
}
/** /**
* Adds an uncompressed strip. * Adds an uncompressed strip.

View File

@ -17,7 +17,6 @@
package it.sephiroth.android.library.exif2 package it.sephiroth.android.library.exif2
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Log import android.util.Log
import android.util.SparseIntArray import android.util.SparseIntArray
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
@ -46,12 +45,18 @@ import kotlin.math.ln
* *
* @see ExifTag * @see ExifTag
*/ */
@Suppress("unused", "unused") @Suppress("unused")
class ExifInterface { class ExifInterface {
private var mData = ExifData() private val mGPSTimeStampCalendar : Calendar by lazy {
Calendar.getInstance(TimeZone.getTimeZone("UTC"))
}
private val mGPSTimeStampCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")) val tagInfo : SparseIntArray by lazy {
SparseIntArray().initTagInfo()
}
private var mData = ExifData()
/** /**
* Get the exif tags in this ExifInterface object or null if none exist. * Get the exif tags in this ExifInterface object or null if none exist.
@ -61,28 +66,14 @@ class ExifInterface {
val allTags : List<ExifTag> val allTags : List<ExifTag>
get() = mData.allTags get() = mData.allTags
val tagInfo : SparseIntArray by lazy { SparseIntArray().also { it.initTagInfo() } }
/** /**
* Returns the thumbnail from IFD1 as a bitmap, or null if none exists. * Returns the JPEG quality used to generate the image
* or 0 if not found
* *
* @return the thumbnail as a bitmap. * @return qualityGuess
*/ */
val thumbnailBitmap : Bitmap? val qualityGuess : Int
get() { get() = mData.qualityGuess
val compressedThumbnail = mData.compressedThumbnail
if(compressedThumbnail != null) {
return BitmapFactory
.decodeByteArray(compressedThumbnail, 0, compressedThumbnail.size)
}
val stripList = mData.stripList
if(stripList != null) {
// TODO: decoding uncompressed thumbnail is not implemented.
return null
}
return null
}
/** /**
* Returns the thumbnail from IFD1 as a byte array, or null if none exists. * Returns the thumbnail from IFD1 as a byte array, or null if none exists.
@ -92,28 +83,15 @@ class ExifInterface {
* @return the thumbnail as a byte array. * @return the thumbnail as a byte array.
*/ */
val thumbnailBytes : ByteArray? val thumbnailBytes : ByteArray?
get() = when { get() = mData.thumbnailBytes
mData.hasCompressedThumbnail() -> mData.compressedThumbnail
mData.hasUncompressedStrip() -> null // TODO: implement this
else -> null
}
/** /**
* Returns the thumbnail if it is jpeg compressed, or null if none exists. * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
* *
* @return the thumbnail as a byte array. * @return the thumbnail as a bitmap.
*/ */
val thumbnail : ByteArray? val thumbnailBitmap : Bitmap?
get() = mData.compressedThumbnail get() = mData.thumbnailBitmap
/**
* Returns the JPEG quality used to generate the image
* or 0 if not found
*
* @return qualityGuess
*/
val qualityGuess : Int
get() = mData.qualityGuess
/** /**
* this gives information about the process used to create the JPEG file. * this gives information about the process used to create the JPEG file.
@ -150,7 +128,7 @@ class ExifInterface {
* @return true if the thumbnail is compressed. * @return true if the thumbnail is compressed.
*/ */
val isThumbnailCompressed : Boolean val isThumbnailCompressed : Boolean
get() = mData.hasCompressedThumbnail() get() = mData.compressedThumbnail != null
/** /**
* Decodes the user comment tag into string as specified in the EXIF * Decodes the user comment tag into string as specified in the EXIF
@ -173,13 +151,21 @@ class ExifInterface {
val latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF) val latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF)
val longitude = getTagRationalValues(TAG_GPS_LONGITUDE) val longitude = getTagRationalValues(TAG_GPS_LONGITUDE)
val longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF) val longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF)
if(latitude == null || longitude == null || latitudeRef == null || longitudeRef == null || latitude.size < 3 || longitude.size < 3) {
return null return when {
latitude == null
|| longitude == null
|| latitudeRef == null
|| longitudeRef == null
|| latitude.size < 3
|| longitude.size < 3 -> null
else -> doubleArrayOf(
convertLatOrLongToDouble(latitude, latitudeRef),
convertLatOrLongToDouble(longitude, longitudeRef)
)
} }
val latLon = DoubleArray(2)
latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef)
latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef)
return latLon
} }
/** /**
@ -191,10 +177,10 @@ class ExifInterface {
val latitude = getTagRationalValues(TAG_GPS_LATITUDE) val latitude = getTagRationalValues(TAG_GPS_LATITUDE)
val latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF) val latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF)
return if(null == latitude || null == latitudeRef) return when {
null null == latitude || null == latitudeRef -> null
else else -> convertRationalLatLonToString(latitude, latitudeRef)
convertRationalLatLonToString(latitude, latitudeRef) }
} }
/** /**
@ -206,10 +192,23 @@ class ExifInterface {
val longitude = getTagRationalValues(TAG_GPS_LONGITUDE) val longitude = getTagRationalValues(TAG_GPS_LONGITUDE)
val longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF) val longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF)
return if(null == longitude || null == longitudeRef) return when {
null null == longitude || null == longitudeRef -> null
else else -> convertRationalLatLonToString(longitude, longitudeRef)
convertRationalLatLonToString(longitude, longitudeRef) }
}
val altitude : Double? // may null
get() = when(val gpsAltitude = getTagRationalValue(TAG_GPS_ALTITUDE)) {
null -> null
else -> {
val seaLevel = when(getTagByteValue(TAG_GPS_ALTITUDE_REF)) {
1.toByte() -> - 1
else -> 1
}
gpsAltitude.toDouble() * seaLevel
}
} }
/** /**
@ -243,10 +242,6 @@ class ExifInterface {
return null return null
} }
init {
mGPSDateStampFormat.timeZone = TimeZone.getTimeZone("UTC")
}
/** /**
* Given the value from [.TAG_FOCAL_PLANE_RESOLUTION_UNIT] or [.TAG_RESOLUTION_UNIT] * Given the value from [.TAG_FOCAL_PLANE_RESOLUTION_UNIT] or [.TAG_RESOLUTION_UNIT]
* this method will return the corresponding value in millimeters * this method will return the corresponding value in millimeters
@ -264,17 +259,11 @@ class ExifInterface {
} }
/** /**
* Reads the exif tags from a file, clearing this ExifInterface object's * Clears this ExifInterface object's existing exif tags.
* existing exif tags.
*
* @param inFileName a string representing the filepath to jpeg file.
* @param options bit flag which defines which type of tags to process, see [it.sephiroth.android.library.exif2.ExifInterface.Options]
* @throws java.io.IOException for I/O error
* @see .readExif
*/ */
@Throws(IOException::class) private fun clearExif() : ExifInterface {
fun readExif(inFileName : String, options : Int) { mData = ExifData()
BufferedInputStream(FileInputStream(inFileName)).use { readExif(it, options) } return this
} }
/** /**
@ -293,39 +282,47 @@ class ExifInterface {
* @throws java.io.IOException for I/O error * @throws java.io.IOException for I/O error
*/ */
@Throws(IOException::class) @Throws(IOException::class)
fun readExif(inStream : InputStream, options : Int) { fun readExif(inStream : InputStream, options : Int) : ExifInterface {
mData = ExifReader(this).read(inStream, options) mData = ExifReader(this).read(inStream, options)
return this
} }
/**
* Reads the exif tags from a file, clearing this ExifInterface object's
* existing exif tags.
*
* @param inFileName a string representing the filepath to jpeg file.
* @param options bit flag which defines which type of tags to process, see [it.sephiroth.android.library.exif2.ExifInterface.Options]
* @throws java.io.IOException for I/O error
* @see .readExif
*/
@Throws(IOException::class)
fun readExif(inFileName : String, options : Int) : ExifInterface =
BufferedInputStream(FileInputStream(inFileName)).use { readExif(it, options) }
/**
* Reads the exif tags from a byte array, clearing this ExifInterface
* object's existing exif tags.
*
* @param jpeg a byte array containing a jpeg compressed image.
* @param options bit flag which defines which type of tags to process, see [it.sephiroth.android.library.exif2.ExifInterface.Options]
* @throws java.io.IOException for I/O error
* @see .readExif
*/
@Throws(IOException::class)
fun readExif(jpeg : ByteArray, options : Int) =
ByteArrayInputStream(jpeg).use { readExif(it, options) }
/** /**
* Sets the exif tags, clearing this ExifInterface object's existing exif * Sets the exif tags, clearing this ExifInterface object's existing exif
* tags. * tags.
* *
* @param tags a collection of exif tags to set. * @param tags a collection of exif tags to set.
*/ */
fun setExif(tags : Collection<ExifTag>) { fun setExif(tags : Collection<ExifTag>) : ExifInterface {
clearExif() clearExif()
setTags(tags) setTags(tags)
} return this
/**
* Clears this ExifInterface object's existing exif tags.
*/
private fun clearExif() {
mData = ExifData()
}
/**
* Puts a collection of ExifTags into this ExifInterface objects's tags. Any
* previous ExifTags with the same TID and IFDs will be removed.
*
* @param tags a Collection of ExifTags.
* @see .setTag
*/
private fun setTags(tags : Collection<ExifTag>) {
for(t in tags) {
setTag(t)
}
} }
/** /**
@ -337,8 +334,19 @@ class ExifInterface {
* @return the previous ExifTag with the same TID and IFD or null if none * @return the previous ExifTag with the same TID and IFD or null if none
* exists. * exists.
*/ */
private fun setTag(tag : ExifTag) : ExifTag? { private fun setTag(tag : ExifTag) : ExifTag? =
return mData.addTag(tag) mData.addTag(tag)
/**
* Puts a collection of ExifTags into this ExifInterface objects's tags. Any
* previous ExifTags with the same TID and IFDs will be removed.
*
* @param tags a Collection of ExifTags.
* @see .setTag
*/
private fun setTags(tags : Collection<ExifTag>) : ExifInterface {
for(t in tags) setTag(t)
return this
} }
@Throws(IOException::class) @Throws(IOException::class)
@ -417,34 +425,17 @@ class ExifInterface {
output.close() output.close()
} }
// input is used *ONLY* to read the image uncompressed data
// exif tags are not used here
@Throws(IOException::class) @Throws(IOException::class)
fun writeExif(input : Bitmap, dstFilename : String, quality : Int) { fun writeExif(input : Bitmap, dstFilename : String, quality : Int) {
Log.i(TAG, "writeExif: $dstFilename") Log.i(TAG, "writeExif: $dstFilename")
ByteArrayOutputStream().use { out ->
// input is used *ONLY* to read the image uncompressed data input.compress(Bitmap.CompressFormat.JPEG, quality, out)
// exif tags are not used here ByteArrayInputStream(out.toByteArray()).use { inStream ->
writeExif(inStream, dstFilename)
val out = ByteArrayOutputStream() }
input.compress(Bitmap.CompressFormat.JPEG, quality, out) }
val `in` = ByteArrayInputStream(out.toByteArray())
out.close()
writeExif(`in`, dstFilename)
}
/**
* Reads the exif tags from a byte array, clearing this ExifInterface
* object's existing exif tags.
*
* @param jpeg a byte array containing a jpeg compressed image.
* @param options bit flag which defines which type of tags to process, see [it.sephiroth.android.library.exif2.ExifInterface.Options]
* @throws java.io.IOException for I/O error
* @see .readExif
*/
@Throws(IOException::class)
fun readExif(jpeg : ByteArray, options : Int) {
readExif(ByteArrayInputStream(jpeg), options)
} }
/** /**
@ -640,20 +631,6 @@ class ExifInterface {
return ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount) return ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount)
} }
/**
* Sets the value of an ExifTag if it exists it's default IFD. The value
* must be the correct type and length for that ExifTag.
*
* @param tagId a tag constant, e.g. [.TAG_IMAGE_WIDTH].
* @param val the value to set.
* @return true if success, false if the ExifTag doesn't exist or the value
* is the wrong type/length.
*/
fun setTagValue(tagId : Int, `val` : Any) : Boolean {
val ifdId = getDefinedTagDefaultIfd(tagId)
return setTagValue(tagId, ifdId, `val`)
}
/** /**
* Sets the value of an ExifTag if it exists in the given IFD. The value * Sets the value of an ExifTag if it exists in the given IFD. The value
* must be the correct type and length for that ExifTag. * must be the correct type and length for that ExifTag.
@ -665,19 +642,12 @@ class ExifInterface {
* is the wrong type/length. * is the wrong type/length.
* @see .setTagValue * @see .setTagValue
*/ */
private fun setTagValue(tagId : Int, ifdId : Int, tagValue : Any) : Boolean { private fun setTagValue(
return getTag(tagId, ifdId)?.setValueAny(tagValue) ?: false tagId : Int,
} tagValue : Any,
ifdId : Int = getDefinedTagDefaultIfd(tagId)
/** ) : Boolean =
* Removes the ExifTag for a tag constant from that tag's default IFD. getTag(tagId, ifdId)?.setValueAny(tagValue) ?: false
*
* @param tagId a tag constant, e.g. [.TAG_IMAGE_WIDTH].
*/
fun deleteTag(tagId : Int) {
val ifdId = getDefinedTagDefaultIfd(tagId)
deleteTag(tagId, ifdId)
}
/** /**
* Removes the ExifTag for a tag constant from the given IFD. * Removes the ExifTag for a tag constant from the given IFD.
@ -685,8 +655,12 @@ class ExifInterface {
* @param tagId a tag constant, e.g. [.TAG_IMAGE_WIDTH]. * @param tagId a tag constant, e.g. [.TAG_IMAGE_WIDTH].
* @param ifdId the IFD of the ExifTag to remove. * @param ifdId the IFD of the ExifTag to remove.
*/ */
private fun deleteTag(tagId : Int, ifdId : Int) { private fun deleteTag(
tagId : Int,
ifdId : Int = getDefinedTagDefaultIfd(tagId)
) : ExifInterface {
mData.removeTag(getTrueTagKey(tagId), ifdId) mData.removeTag(getTrueTagKey(tagId), ifdId)
return this
} }
/** /**
@ -754,18 +728,16 @@ class ExifInterface {
return TAG_NULL return TAG_NULL
} }
fun getTagDefinition(tagId : Short, defaultIfd : Int) : Int { fun getTagDefinition(tagId : Short, defaultIfd : Int) : Int =
return tagInfo.get(defineTag(defaultIfd, tagId)) tagInfo.get(defineTag(defaultIfd, tagId))
}
private fun getTagDefinitionsForTagId(tagId : Short) : IntArray? { private fun getTagDefinitionsForTagId(tagId : Short) : IntArray? {
val ifds = IfdData.list val ifds = IfdData.list
val defs = IntArray(ifds.size) val defs = IntArray(ifds.size)
var counter = 0 var counter = 0
val infos = tagInfo
for(i in ifds) { for(i in ifds) {
val def = defineTag(i, tagId) val def = defineTag(i, tagId)
if(infos.get(def) != DEFINITION_NULL) { if(tagInfo.get(def) != DEFINITION_NULL) {
defs[counter ++] = def defs[counter ++] = def
} }
} }
@ -773,12 +745,8 @@ class ExifInterface {
} }
fun getTagDefinitionForTag(tag : ExifTag) : Int { fun getTagDefinitionForTag(tag : ExifTag) : Int =
val type = tag.dataType getTagDefinitionForTag(tag.tagId, tag.dataType, tag.componentCount, tag.ifd)
val count = tag.componentCount
val ifd = tag.ifd
return getTagDefinitionForTag(tag.tagId, type, count, ifd)
}
private fun getTagDefinitionForTag( private fun getTagDefinitionForTag(
tagId : Short, tagId : Short,
@ -786,11 +754,8 @@ class ExifInterface {
count : Int, count : Int,
ifd : Int ifd : Int
) : Int { ) : Int {
val defs = getTagDefinitionsForTagId(tagId) ?: return TAG_NULL getTagDefinitionsForTagId(tagId)?.forEach { i ->
val infos = tagInfo val info = tagInfo.get(i)
var ret = TAG_NULL
for(i in defs) {
val info = infos.get(i)
val def_type = getTypeFromInfo(info) val def_type = getTypeFromInfo(info)
val def_count = getComponentCountFromInfo(info) val def_count = getComponentCountFromInfo(info)
val def_ifds = getAllowedIfdsFromInfo(info) val def_ifds = getAllowedIfdsFromInfo(info)
@ -803,12 +768,14 @@ class ExifInterface {
} }
} }
} }
if(valid_ifd && type == def_type && (count == def_count || def_count == ExifTag.SIZE_UNDEFINED)) { if(valid_ifd
ret = i && type == def_type
break && (count == def_count || def_count == ExifTag.SIZE_UNDEFINED)
) {
return i
} }
} }
return ret return TAG_NULL
} }
/** /**
@ -827,16 +794,6 @@ class ExifInterface {
// mTagInfo = null // mTagInfo = null
// } // }
/**
* Check if thumbnail exists.
*
* @return true if a compressed thumbnail exists.
*/
fun hasThumbnail() : Boolean {
// TODO: add back in uncompressed strip
return mData.hasCompressedThumbnail()
}
/** /**
* Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior
* thumbnail. * thumbnail.
@ -914,18 +871,6 @@ class ExifInterface {
return true return true
} }
/**
* Creates a tag for a defined tag constant in the tag's default IFD.
*
* @param tagId a tag constant, e.g. [.TAG_IMAGE_WIDTH].
* @param val the tag's value.
* @return an ExifTag object.
*/
fun buildTag(tagId : Int, `val` : Any) : ExifTag? {
val ifdId = getTrueIfd(tagId)
return buildTag(tagId, ifdId, `val`)
}
/** /**
* Creates a tag for a defined tag constant in a given IFD if that IFD is * Creates a tag for a defined tag constant in a given IFD if that IFD is
* allowed for the tag. This method will fail anytime the appropriate * allowed for the tag. This method will fail anytime the appropriate
@ -937,21 +882,24 @@ class ExifInterface {
* @return an ExifTag object or null if one could not be constructed. * @return an ExifTag object or null if one could not be constructed.
* @see .buildTag * @see .buildTag
*/ */
fun buildTag(tagId : Int, ifdId : Int, tagValue : Any?) : ExifTag? { fun buildTag(tagId : Int, tagValue : Any, ifdId : Int = getTrueIfd(tagId)) : ExifTag? {
val info = tagInfo.get(tagId) val info = tagInfo.get(tagId)
if(info == 0 || tagValue == null) { if(info == 0 || ! isIfdAllowed(info, ifdId)) return null
return null
}
val type = getTypeFromInfo(info)
val definedCount = getComponentCountFromInfo(info) val definedCount = getComponentCountFromInfo(info)
val hasDefinedCount = definedCount != ExifTag.SIZE_UNDEFINED
if(! isIfdAllowed(info, ifdId)) { val t = ExifTag(
return null tagId = getTrueTagKey(tagId),
dataType = getTypeFromInfo(info),
componentCount = definedCount,
ifd = ifdId,
mHasDefinedDefaultComponentCount = definedCount != ExifTag.SIZE_UNDEFINED
)
return when {
t.setValueAny(tagValue) -> t
else -> null
} }
val t = ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount)
return if(! t.setValueAny(tagValue)) {
null
} else t
} }
/** /**
@ -2220,6 +2168,8 @@ class ExifInterface {
private const val GPS_DATE_FORMAT_STR = "yyyy:MM:dd" private const val GPS_DATE_FORMAT_STR = "yyyy:MM:dd"
private val mGPSDateStampFormat = SimpleDateFormat(GPS_DATE_FORMAT_STR, Locale.ENGLISH) private val mGPSDateStampFormat = SimpleDateFormat(GPS_DATE_FORMAT_STR, Locale.ENGLISH)
.apply { timeZone = TimeZone.getTimeZone("UTC") }
private const val DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss" private const val DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss"
private val mDateTimeStampFormat = SimpleDateFormat(DATETIME_FORMAT_STR, Locale.ENGLISH) private val mDateTimeStampFormat = SimpleDateFormat(DATETIME_FORMAT_STR, Locale.ENGLISH)
@ -2227,25 +2177,21 @@ class ExifInterface {
* Tags that contain offset markers. These are included in the banned * Tags that contain offset markers. These are included in the banned
* defines. * defines.
*/ */
private val sOffsetTags = HashSet<Short>() private val sOffsetTags = HashSet<Short>().apply {
add(getTrueTagKey(TAG_GPS_IFD))
init { add(getTrueTagKey(TAG_EXIF_IFD))
sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD)) add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT))
sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD)) add(getTrueTagKey(TAG_INTEROPERABILITY_IFD))
sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT)) add(getTrueTagKey(TAG_STRIP_OFFSETS))
sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD))
sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS))
} }
/** /**
* Tags with definitions that cannot be overridden (banned defines). * Tags with definitions that cannot be overridden (banned defines).
*/ */
var sBannedDefines = HashSet(sOffsetTags) var sBannedDefines = HashSet(sOffsetTags).apply {
add(getTrueTagKey(TAG_NULL))
init { add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH))
sBannedDefines.add(getTrueTagKey(TAG_NULL)) add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS))
sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH))
sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS))
} }
/** /**
@ -2260,9 +2206,8 @@ class ExifInterface {
* [.getTrueTagKey]). * [.getTrueTagKey]).
* @return true if the TID is that of an offset tag. * @return true if the TID is that of an offset tag.
*/ */
fun isOffsetTag(tag : Short) : Boolean { fun isOffsetTag(tag : Short) : Boolean =
return sOffsetTags.contains(tag) sOffsetTags.contains(tag)
}
/** /**
* Returns the Orientation ExifTag value for a given number of degrees. * Returns the Orientation ExifTag value for a given number of degrees.
@ -2311,18 +2256,14 @@ class ExifInterface {
* seconds/3600 * seconds/3600
*/ */
fun convertLatOrLongToDouble(coordinate : Array<Rational>, reference : String) : Double { fun convertLatOrLongToDouble(coordinate : Array<Rational>, reference : String) : Double {
try { val degrees = coordinate[0].toDouble()
val degrees = coordinate[0].toDouble() val minutes = coordinate[1].toDouble()
val minutes = coordinate[1].toDouble() val seconds = coordinate[2].toDouble()
val seconds = coordinate[2].toDouble() val result = degrees + minutes / 60.0 + seconds / 3600.0
val result = degrees + minutes / 60.0 + seconds / 3600.0 return when {
return if(reference.startsWith("S") || reference.startsWith("W")) { reference.startsWith("S") || reference.startsWith("W") -> - result
- result else -> result
} else result
} catch(e : ArrayIndexOutOfBoundsException) {
throw IllegalArgumentException()
} }
} }
fun getAllowedIfdsFromInfo(info : Int) : IntArray? { fun getAllowedIfdsFromInfo(info : Int) : IntArray? {
@ -2397,22 +2338,15 @@ class ExifInterface {
/** /**
* Returns the default IFD for a tag constant. * Returns the default IFD for a tag constant.
*/ */
fun getTrueIfd(tag : Int) : Int { fun getTrueIfd(tag : Int) : Int = tag.ushr(16)
return tag.ushr(16)
}
/** /**
* Returns the TID for a tag constant. * Returns the TID for a tag constant.
*/ */
fun getTrueTagKey(tag : Int) : Short { fun getTrueTagKey(tag : Int) : Short = tag.toShort()
// Truncate
return tag.toShort()
}
private fun getFlagsFromAllowedIfds(allowedIfds : IntArray?) : Int { private fun getFlagsFromAllowedIfds(allowedIfds : IntArray) : Int {
if(allowedIfds == null || allowedIfds.isEmpty()) { if(allowedIfds.isEmpty()) return 0
return 0
}
var flags = 0 var flags = 0
val ifds = IfdData.list val ifds = IfdData.list
for(i in 0 until IfdData.TYPE_IFD_COUNT) { for(i in 0 until IfdData.TYPE_IFD_COUNT) {
@ -2426,20 +2360,14 @@ class ExifInterface {
return flags return flags
} }
private fun getComponentCountFromInfo(info : Int) : Int { private fun getComponentCountFromInfo(info : Int) : Int = info and 0x0ffff
return info and 0x0ffff
}
private fun getTypeFromInfo(info : Int) : Short { private fun getTypeFromInfo(info : Int) : Short = (info shr 16 and 0x0ff).toShort()
return (info shr 16 and 0x0ff).toShort()
}
/** /**
* Returns the constant representing a tag with a given TID and default IFD. * Returns the constant representing a tag with a given TID and default IFD.
*/ */
fun defineTag(ifdId : Int, tagId : Short) : Int { fun defineTag(ifdId : Int, tagId : Short) : Int = tagId or (ifdId shl 16)
return tagId or (ifdId shl 16)
}
private fun convertRationalLatLonToString( private fun convertRationalLatLonToString(
coord : Array<Rational>, coord : Array<Rational>,
@ -2475,8 +2403,7 @@ class ExifInterface {
* @param timeZone the target timezone * @param timeZone the target timezone
* @return the parsed date * @return the parsed date
*/ */
fun getDateTime(dateTimeString : String?, timeZone : TimeZone) : Date? { fun getDateTime(dateTimeString : String, timeZone : TimeZone) : Date? {
dateTimeString ?: return null
return try { return try {
val formatter = SimpleDateFormat(DATETIME_FORMAT_STR, Locale.ENGLISH) val formatter = SimpleDateFormat(DATETIME_FORMAT_STR, Locale.ENGLISH)
@ -2489,19 +2416,14 @@ class ExifInterface {
} }
fun isIfdAllowed(info : Int, ifd : Int) : Boolean { fun isIfdAllowed(info : Int, ifd : Int) : Boolean {
val ifds = IfdData.list
val ifdFlags = getAllowedIfdFlagsFromInfo(info) val ifdFlags = getAllowedIfdFlagsFromInfo(info)
for(i in ifds.indices) { IfdData.list.forEachIndexed { itemIndex, itemId ->
if(ifd == ifds[i] && ifdFlags shr i and 1 == 1) { if(ifd == itemId && ifdFlags shr itemIndex and 1 == 1) return true
return true
}
} }
return false return false
} }
private fun getAllowedIfdFlagsFromInfo(info : Int) : Int { private fun getAllowedIfdFlagsFromInfo(info : Int) : Int = info.ushr(24)
return info.ushr(24)
}
private fun toExifLatLong(valueArg : Double) : Array<Rational> { private fun toExifLatLong(valueArg : Double) : Array<Rational> {
// convert to the format dd/1 mm/1 ssss/100 // convert to the format dd/1 mm/1 ssss/100
@ -2530,7 +2452,7 @@ class ExifInterface {
infix fun Short.shr(bits : Int) : Int = (this.toInt() and 0xffff) shr bits infix fun Short.shr(bits : Int) : Int = (this.toInt() and 0xffff) shr bits
infix fun Short.or(bits : Int) : Int = (this.toInt() and 0xffff) or bits infix fun Short.or(bits : Int) : Int = (this.toInt() and 0xffff) or bits
private fun SparseIntArray.initTagInfo() { private fun SparseIntArray.initTagInfo() : SparseIntArray {
var f : Int var f : Int
@ -2680,6 +2602,8 @@ class ExifInterface {
f = getFlagsFromAllowedIfds(intArrayOf(IfdData.TYPE_IFD_INTEROPERABILITY)) shl 24 f = getFlagsFromAllowedIfds(intArrayOf(IfdData.TYPE_IFD_INTEROPERABILITY)) shl 24
put(TAG_INTEROPERABILITY_INDEX, f or (ExifTag.TYPE_ASCII shl 16)) put(TAG_INTEROPERABILITY_INDEX, f or (ExifTag.TYPE_ASCII shl 16))
put(TAG_INTEROP_VERSION, f or (ExifTag.TYPE_UNDEFINED shl 16) or 4) put(TAG_INTEROP_VERSION, f or (ExifTag.TYPE_UNDEFINED shl 16) or 4)
return this
} }
} }

View File

@ -347,9 +347,9 @@ open class ExifTag internal constructor(
* type. * type.
* *
*/ */
inline fun <reified T : Any?> setValueAny(obj : T) : Boolean { inline fun <reified T : Any> setValueAny(obj : T) : Boolean {
when(obj) { when(obj) {
null -> return false // null -> return false
is String -> return setValue(obj) is String -> return setValue(obj)
is ByteArray -> return setValue(obj) is ByteArray -> return setValue(obj)

View File

@ -35,14 +35,12 @@ class Test1 {
try { try {
val o = FileInputStream(getFile(fileName)).use { inStream -> val o = FileInputStream(getFile(fileName)).use { inStream ->
ExifInterface() ExifInterface()
.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) Pair(o, null)
@ -54,14 +52,12 @@ class Test1 {
try { try {
val o = FileInputStream(getFile(fileName)).use { inStream -> val o = FileInputStream(getFile(fileName)).use { inStream ->
ExifInterface() ExifInterface()
.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 )
)
}
.thumbnailBytes .thumbnailBytes
} }
Pair(o, null) Pair(o, null)
@ -69,14 +65,12 @@ class Test1 {
Pair(null, ex) 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}")
}
@Test @Test
fun testNotJpeg() { fun testNotJpeg() {
fun testNotJpegSub(fileName : String) {
val (o, ex) = getOrientation(fileName)
assertTrue("testNotJpegSub", o == null && ex != null)
}
testNotJpegSub("test.gif") testNotJpegSub("test.gif")
testNotJpegSub("test.png") testNotJpegSub("test.png")
testNotJpegSub("test.webp") testNotJpegSub("test.webp")
@ -93,22 +87,28 @@ class Test1 {
rvO = getOrientation(fileName) rvO = getOrientation(fileName)
assertEquals(fileName, 6, rvO.first) assertEquals(fileName, 6, rvO.first)
rvT = getThumbnailBytes(fileName) rvT = getThumbnailBytes(fileName)
assertNull(fileName,rvT.first) assertNull(fileName, rvT.first)
// this file has orientation 1 // this file has orientation 1
fileName = "test1.jpg" fileName = "test1.jpg"
rvO = getOrientation(fileName) rvO = getOrientation(fileName)
assertEquals(fileName, 1, rvO.first) assertEquals(fileName, 1, rvO.first)
rvT = getThumbnailBytes(fileName) rvT = getThumbnailBytes(fileName)
assertNull(fileName,rvT.first) assertNull(fileName, rvT.first)
// this file has no orientation, it raises exception. // this file has no orientation, it raises exception.
fileName = "test2.jpg" fileName = "test2.jpg"
rvO = getOrientation(fileName) rvO = getOrientation(fileName)
assertNotNull(fileName, rvO.second) // <java.lang.IllegalStateException: stop before hitting compressed data> assertNotNull(
fileName,
rvO.second
) // <java.lang.IllegalStateException: stop before hitting compressed data>
rvT = getThumbnailBytes(fileName) rvT = getThumbnailBytes(fileName)
assertNotNull(fileName,rvT.second) // <java.lang.IllegalStateException: stop before hitting compressed data> assertNotNull(
fileName,
rvT.second
) // <java.lang.IllegalStateException: stop before hitting compressed data>
} }