@@ -5,40 +5,47 @@ import java.nio.ByteBuffer
55import java.nio.ByteOrder
66
77/* *
8- * IFD0 string tags that ExifInterface may miss when an image editor
8+ * IFD0 tags that ExifInterface may miss when an image editor
99 * (e.g. ON1 Photo RAW) places them inside the ExifIFD instead of IFD0.
1010 *
1111 * Maps EXIF tag number → ExifInterface tag name.
1212 */
1313private val FALLBACK_TAGS =
1414 mapOf (
15+ 0x010E to " ImageDescription" ,
1516 0x010F to " Make" ,
1617 0x0110 to " Model" ,
18+ 0x0112 to " Orientation" ,
19+ 0x011A to " XResolution" ,
20+ 0x011B to " YResolution" ,
21+ 0x0128 to " ResolutionUnit" ,
22+ 0x0131 to " Software" ,
1723 0x013B to " Artist" ,
1824 0x8298 to " Copyright" ,
19- 0x010E to " ImageDescription" ,
20- 0x0131 to " Software" ,
2125 )
2226
27+ private const val IFD_FORMAT_SHORT = 3
28+ private const val IFD_FORMAT_LONG = 4
29+ private const val IFD_FORMAT_RATIONAL = 5
2330private const val IFD_FORMAT_STRING = 2
2431private const val IFD_FORMAT_UNDEFINED = 7
2532private const val EXIF_IFD_POINTER_TAG = 0x8769
2633
2734/* *
28- * Scans raw JPEG bytes for IFD0 string tags that may have been placed in
35+ * Scans raw JPEG bytes for IFD0 tags that may have been placed in
2936 * the ExifIFD. Returns a map of tag name → value for any tags found.
3037 */
3138fun readFallbackTags (
3239 inputStream : InputStream ,
3340 missingTags : Set <String >,
34- ): Map <String , String > {
41+ ): Map <String , Any > {
3542 if (missingTags.isEmpty()) return emptyMap()
3643
3744 val neededTagNumbers = FALLBACK_TAGS .filterValues { it in missingTags }.keys
3845 if (neededTagNumbers.isEmpty()) return emptyMap()
3946
4047 val bytes = readExifSegment(inputStream) ? : return emptyMap()
41- val result = mutableMapOf<String , String >()
48+ val result = mutableMapOf<String , Any >()
4249
4350 val app1 = findApp1Exif(bytes) ? : return emptyMap()
4451 val tiffOffset = app1.tiffOffset
@@ -102,7 +109,10 @@ private fun readExifSegment(inputStream: InputStream): ByteArray? {
102109 return out .toByteArray()
103110}
104111
105- private data class App1Info (val tiffOffset : Int , val byteOrder : ByteOrder )
112+ private data class App1Info (
113+ val tiffOffset : Int ,
114+ val byteOrder : ByteOrder ,
115+ )
106116
107117private fun findApp1Exif (bytes : ByteArray ): App1Info ? {
108118 if (bytes.size < 4 || bytes[0 ] != 0xFF .toByte() || bytes[1 ] != 0xD8 .toByte()) return null
@@ -166,7 +176,7 @@ private fun scanIfd(
166176 tiffOffset : Int ,
167177 ifdOffset : Int ,
168178 neededTagNumbers : Set <Int >,
169- result : MutableMap <String , String >,
179+ result : MutableMap <String , Any >,
170180) {
171181 val absOffset = tiffOffset + ifdOffset
172182 if (absOffset + 2 > buf.limit()) return
@@ -181,29 +191,55 @@ private fun scanIfd(
181191
182192 val format = buf.getShort(entryOffset + 2 ).toInt() and 0xFFFF
183193 val componentCount = buf.getInt(entryOffset + 4 )
194+ if (componentCount <= 0 ) continue
195+
196+ val tagName = FALLBACK_TAGS [tagNumber] ? : continue
197+
198+ when (format) {
199+ IFD_FORMAT_SHORT -> {
200+ if (componentCount != 1 ) continue
201+ val value = buf.getShort(entryOffset + 8 ).toInt() and 0xFFFF
202+ if (value == 0 ) continue
203+ result[tagName] = value
204+ }
184205
185- if (format != IFD_FORMAT_STRING && format != IFD_FORMAT_UNDEFINED ) continue
186- if (componentCount <= 0 || componentCount > 1024 ) continue
206+ IFD_FORMAT_LONG -> {
207+ if (componentCount != 1 ) continue
208+ val value = buf.getInt(entryOffset + 8 ).toLong() and 0xFFFFFFFFL
209+ if (value == 0L ) continue
210+ result[tagName] = value.toInt()
211+ }
187212
188- val dataOffset =
189- if (componentCount <= 4 ) {
190- entryOffset + 8
191- } else {
192- tiffOffset + buf.getInt(entryOffset + 8 )
213+ IFD_FORMAT_RATIONAL -> {
214+ if (componentCount != 1 ) continue
215+ val dataOffset = tiffOffset + buf.getInt(entryOffset + 8 )
216+ if (dataOffset < 0 || dataOffset + 8 > buf.limit()) continue
217+ val numerator = buf.getInt(dataOffset).toLong() and 0xFFFFFFFFL
218+ val denominator = buf.getInt(dataOffset + 4 ).toLong() and 0xFFFFFFFFL
219+ if (denominator == 0L ) continue
220+ result[tagName] = numerator.toDouble() / denominator.toDouble()
193221 }
194222
195- if (dataOffset < 0 || dataOffset + componentCount > buf.limit()) continue
223+ IFD_FORMAT_STRING , IFD_FORMAT_UNDEFINED -> {
224+ if (componentCount > 1024 ) continue
225+ val dataOffset =
226+ if (componentCount <= 4 ) {
227+ entryOffset + 8
228+ } else {
229+ tiffOffset + buf.getInt(entryOffset + 8 )
230+ }
231+ if (dataOffset < 0 || dataOffset + componentCount > buf.limit()) continue
196232
197- val strBytes = ByteArray (componentCount)
198- buf.position(dataOffset)
199- buf.get(strBytes)
233+ val strBytes = ByteArray (componentCount)
234+ buf.position(dataOffset)
235+ buf.get(strBytes)
200236
201- // Trim trailing null bytes
202- var len = strBytes.size
203- while (len > 0 && strBytes[len - 1 ] == 0 .toByte()) len--
204- if (len == 0 ) continue
237+ var len = strBytes.size
238+ while (len > 0 && strBytes[len - 1 ] == 0 .toByte()) len--
239+ if (len == 0 ) continue
205240
206- val tagName = FALLBACK_TAGS [tagNumber] ? : continue
207- result[tagName] = String (strBytes, 0 , len, Charsets .UTF_8 ).trim()
241+ result[tagName] = String (strBytes, 0 , len, Charsets .UTF_8 ).trim()
242+ }
243+ }
208244 }
209245}
0 commit comments