Skip to content

Commit

Permalink
Fix for TIFFs with multiple image elements (#89)
Browse files Browse the repository at this point in the history
  • Loading branch information
StefanOltmann authored Apr 7, 2024
1 parent f25e83f commit 9959eee
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 141 deletions.
13 changes: 3 additions & 10 deletions examples/kim-kotlin-jvm-sample/src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import com.ashampoo.kim.Kim
import com.ashampoo.kim.common.ByteOrder
import com.ashampoo.kim.common.writeBytes
import com.ashampoo.kim.format.jpeg.JpegRewriter
import com.ashampoo.kim.format.tiff.TiffContents
Expand Down Expand Up @@ -156,9 +155,7 @@ fun setGeoTiffToTiff() {

OutputStreamByteWriter(outputFile.outputStream()).use { outputStreamByteWriter ->

val tiffWriter = TiffWriterLossy(
ByteOrder.LITTLE_ENDIAN
)
val tiffWriter = TiffWriterLossy(outputSet.byteOrder)

tiffWriter.write(
byteWriter = outputStreamByteWriter,
Expand Down Expand Up @@ -202,9 +199,7 @@ fun setGeoTiffToTiffUsingKotlinx() {

val byteArrayByteWriter = ByteArrayByteWriter()

val tiffWriter = TiffWriterLossy(
ByteOrder.LITTLE_ENDIAN
)
val tiffWriter = TiffWriterLossy(outputSet.byteOrder)

tiffWriter.write(
byteWriter = byteArrayByteWriter,
Expand Down Expand Up @@ -261,9 +256,7 @@ fun setGeoTiffToTiffUsingKotlinxAndTiffReader() {

val byteArrayByteWriter = ByteArrayByteWriter()

val tiffWriter = TiffWriterLossy(
ByteOrder.LITTLE_ENDIAN
)
val tiffWriter = TiffWriterLossy(outputSet.byteOrder)

tiffWriter.write(
byteWriter = byteArrayByteWriter,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ data class TiffContents(

fun getExifThumbnailBytes(): ByteArray? =
directories.asSequence()
.mapNotNull { it.thumbnailImageDataElement?.bytes }
.mapNotNull { it.thumbnailBytes }
.firstOrNull()

fun createOutputSet(): TiffOutputSet {
Expand Down
35 changes: 23 additions & 12 deletions src/commonMain/kotlin/com/ashampoo/kim/format/tiff/TiffDirectory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ class TiffDirectory(
TiffConstants.TIFF_ENTRY_LENGTH + TiffConstants.TIFF_DIRECTORY_FOOTER_LENGTH
) {

var thumbnailImageDataElement: JpegImageDataElement? = null
var tiffImageDataElement: TiffImageDataElement? = null
var thumbnailBytes: ByteArray? = null
var tiffImageBytes: ByteArray? = null

fun getDirectoryEntries(): List<TiffField> = entries

Expand Down Expand Up @@ -124,7 +124,7 @@ class TiffDirectory(
return field.valueBytes.toInts(field.byteOrder)
}

fun getJpegImageDataElement(): ImageDataElement {
fun getJpegImageDataElement(): ImageDataElement? {

val jpegInterchangeFormat = findField(TiffTag.TIFF_TAG_JPEG_INTERCHANGE_FORMAT)
val jpegInterchangeFormatLength = findField(TiffTag.TIFF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)
Expand All @@ -137,23 +137,35 @@ class TiffDirectory(
return ImageDataElement(offset, byteCount)
}

throw ImageReadException("Couldn't find image data.")
return null
}

fun getStripImageDataElement(): ImageDataElement {
/**
* Returns a list as tiff image bytes can be splitted upon the whole file.
* ImageIO creates small splits while GIMP creates a single big chunk.
*/
fun getStripImageDataElements(): List<ImageDataElement>? {

val offsetField = findField(TiffTag.TIFF_TAG_STRIP_OFFSETS)
val lengthField = findField(TiffTag.TIFF_TAG_STRIP_BYTE_COUNTS)

if (offsetField != null && lengthField != null) {

val offset = offsetField.toInt()
val length = lengthField.toInt()
val offsets = offsetField.toIntArray()
val lengths = lengthField.toIntArray()

if (offsets.size != lengths.size)
throw ImageReadException("Offsets & Lengths mismatch: ${offsets.size} != ${lengths.size}")

val imageDataElements = mutableListOf<ImageDataElement>()

return ImageDataElement(offset, length)
for (index in offsets.indices)
imageDataElements.add(ImageDataElement(offsets[index], lengths[index]))

return imageDataElements
}

throw ImageReadException("Couldn't find image data.")
return null
}

fun createOutputDirectory(byteOrder: ByteOrder): TiffOutputDirectory {
Expand Down Expand Up @@ -229,9 +241,8 @@ class TiffDirectory(
)
}

outputDirectory.setThumbnailImageDataElement(thumbnailImageDataElement)

outputDirectory.setTiffImageDataElement(tiffImageDataElement)
outputDirectory.setThumbnailBytes(thumbnailBytes)
outputDirectory.setTiffImageBytes(tiffImageBytes)

return outputDirectory

Expand Down

This file was deleted.

61 changes: 36 additions & 25 deletions src/commonMain/kotlin/com/ashampoo/kim/format/tiff/TiffReader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import com.ashampoo.kim.format.tiff.taginfo.TagInfoLongs
import com.ashampoo.kim.input.ByteArrayByteReader
import com.ashampoo.kim.input.ByteReader
import com.ashampoo.kim.input.RandomAccessByteReader
import com.ashampoo.kim.output.ByteArrayByteWriter

object TiffReader {

Expand Down Expand Up @@ -183,10 +184,10 @@ object TiffReader {
)

if (directory.hasJpegImageData())
directory.thumbnailImageDataElement = getJpegImageDataElement(byteReader, directory)
directory.thumbnailBytes = readThumbnailBytes(byteReader, directory)

if (directory.hasStripImageData())
directory.tiffImageDataElement = getStripImageDataElement(byteReader, directory)
directory.tiffImageBytes = readTiffImageBytes(byteReader, directory)

addDirectory(directory)

Expand Down Expand Up @@ -359,12 +360,12 @@ object TiffReader {
*
* Discarding corrupt thumbnails is not a big issue, so no exceptions will be thrown here.
*/
private fun getJpegImageDataElement(
private fun readThumbnailBytes(
byteReader: RandomAccessByteReader,
directory: TiffDirectory
): JpegImageDataElement? {
): ByteArray? {

val element = directory.getJpegImageDataElement()
val element = directory.getJpegImageDataElement() ?: return null

val offset = element.offset
var length = element.length
Expand Down Expand Up @@ -401,37 +402,47 @@ object TiffReader {
* there are some random bytes present.
*/

return JpegImageDataElement(offset, length, bytes)
return bytes
}

private fun getStripImageDataElement(
private fun readTiffImageBytes(
byteReader: RandomAccessByteReader,
directory: TiffDirectory
): TiffImageDataElement? {
): ByteArray? {

val element = directory.getStripImageDataElement()
val elements = directory.getStripImageDataElements() ?: return null

val offset = element.offset
var length = element.length
val byteArrayByteWriter = ByteArrayByteWriter()

/*
* If the length is not correct (going beyond the file size) we need to adjust it.
*/
if (offset + length > byteReader.contentLength)
length = (byteReader.contentLength - offset).toInt()
for (element in elements) {

/*
* If the new length is 0 or negative, ignore this element.
*/
if (length <= 0)
return null
val offset = element.offset
var length = element.length

val bytes = byteReader.readBytes(offset.toInt(), length)
/*
* If the length is not correct (going beyond the file size) we need to adjust it.
*/
if (offset + length > byteReader.contentLength)
length = (byteReader.contentLength - offset).toInt()

if (bytes.size != length)
return null
/*
* If the new length is 0 or negative, ignore this element.
*/
if (length <= 0)
continue

val bytes = byteReader.readBytes(offset.toInt(), length)

/*
* Break if something is wrong.
*/
if (bytes.size != length)
return null

byteArrayByteWriter.write(bytes)
}

return TiffImageDataElement(offset, length, bytes)
return byteArrayByteWriter.toByteArray()
}

/**
Expand Down
Loading

0 comments on commit 9959eee

Please sign in to comment.