diff --git a/README.md b/README.md index 787ac5c2..80b5317b 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ of Ashampoo Photos, which, in turn, is driven by user community feedback. ## Installation ``` -implementation("com.ashampoo:kim:0.7.4") +implementation("com.ashampoo:kim:0.7.5") ``` ## Sample usages diff --git a/build.gradle.kts b/build.gradle.kts index efd56c74..f33f0db1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,9 +22,9 @@ repositories { val productName = "Ashampoo Kim" -val ktorVersion: String = "2.3.6" -val xmpCoreVersion: String = "0.2.2" -val dateTimeVersion: String = "0.4.1" +val ktorVersion: String = "2.3.7" +val xmpCoreVersion: String = "0.2.3" +val dateTimeVersion: String = "0.5.0" val testRessourcesVersion: String = "0.4.0" val ioCoreVersion: String = "0.3.0" @@ -84,25 +84,16 @@ sonar { ) ) - /* Include Android Lint */ - property("sonar.android.lint.report", "${project.buildDir}/reports/lint-results.xml") - - /* Include Detekt issues */ - val detektPath = "${project.buildDir}/reports/detekt/detekt.xml" - println("Detekt report: $detektPath") - property("sonar.kotlin.detekt.reportPaths", detektPath) - - /* Include Kover code coverage */ - val koverPath = "${project.buildDir}/reports/kover/xml/report.xml" - println("Kover report: $koverPath") - property("sonar.coverage.jacoco.xmlReportPaths", koverPath) + property("sonar.android.lint.report", "build/reports/lint-results.xml") + property("sonar.kotlin.detekt.reportPaths", "build/reports/detekt/detekt.xml") + property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/kover/xml/report.xml") } } detekt { - source = files("src", "build.gradle.kts") + source.from("src", "build.gradle.kts") allRules = true - config = files("detekt.yml") + config.setFrom("$projectDir/detekt.yml") parallel = true ignoreFailures = true autoCorrect = true diff --git a/detekt.yml b/detekt.yml index bda8fd9a..7e220fcd 100644 --- a/detekt.yml +++ b/detekt.yml @@ -619,6 +619,7 @@ style: - '0' - '1' - '2' + - '0xFF' ignoreHashCodeFunction: true ignorePropertyDeclaration: false ignoreLocalVariableDeclaration: false diff --git a/src/androidMain/kotlin/com/ashampoo/kim/input/AndroidInputStreamByteReader.kt b/src/androidMain/kotlin/com/ashampoo/kim/input/AndroidInputStreamByteReader.kt index 224c413e..470c0ec8 100644 --- a/src/androidMain/kotlin/com/ashampoo/kim/input/AndroidInputStreamByteReader.kt +++ b/src/androidMain/kotlin/com/ashampoo/kim/input/AndroidInputStreamByteReader.kt @@ -15,6 +15,7 @@ */ package com.ashampoo.kim.input +import com.ashampoo.kim.common.slice import java.io.InputStream open class AndroidInputStreamByteReader( @@ -46,7 +47,7 @@ open class AndroidInputStreamByteReader( return if (bytes == count) buffer else - buffer.sliceArray(0..count) + buffer.slice(startIndex = 0, count = count) } override fun close() = diff --git a/src/commonMain/kotlin/com/ashampoo/kim/common/ByteArrayExtensions.kt b/src/commonMain/kotlin/com/ashampoo/kim/common/ByteArrayExtensions.kt index d42d0443..2e664ec1 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/common/ByteArrayExtensions.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/common/ByteArrayExtensions.kt @@ -17,6 +17,8 @@ package com.ashampoo.kim.common +import io.ktor.utils.io.charsets.Charsets + private const val FF = 0xFF const val HEX_RADIX = 16 @@ -39,8 +41,11 @@ fun ByteArray.toSingleNumberHexes(): String = joinToString(", ") { "0x" + it.toHex() } @Suppress("MagicNumber") -fun ByteArray.toAsciiString(): String = - this.decodeToString() +fun ByteArray.decodeToIso8859String(): String = + io.ktor.utils.io.core.String( + bytes = this, + charset = Charsets.ISO_8859_1 + ) fun ByteArray.indexOfNullTerminator(): Int = indexOfNullTerminator(0) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/common/PhotoValueFormatter.kt b/src/commonMain/kotlin/com/ashampoo/kim/common/PhotoValueFormatter.kt index 25861d88..4bbbf144 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/common/PhotoValueFormatter.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/common/PhotoValueFormatter.kt @@ -219,7 +219,9 @@ object PhotoValueFormatter { */ lensName.replaceFirst(cameraName, "").trim() - } else lensName + } else { + lensName + } } fun createCameraAndLensName( @@ -237,7 +239,9 @@ object PhotoValueFormatter { "$cameraName | $modLensName" - } else cameraName ?: lensName + } else { + cameraName ?: lensName + } } fun formatIso(iso: Int): String = "ISO $iso" diff --git a/src/commonMain/kotlin/com/ashampoo/kim/common/RationalNumber.kt b/src/commonMain/kotlin/com/ashampoo/kim/common/RationalNumber.kt index 890ceee8..909127cf 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/common/RationalNumber.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/common/RationalNumber.kt @@ -119,14 +119,14 @@ class RationalNumber { */ if (isUnsignedType && negatedNumerator < 0) { - if (commonDivisor != 0L) { - - val reducedNumerator = numerator / commonDivisor - val reducedDivisor = divisor / commonDivisor - return RationalNumber(-reducedNumerator, reducedDivisor, false) - - } else + if (commonDivisor == 0L) throw NumberFormatException("Unsigned numerator is too large to negate: $numerator") + + return RationalNumber( + numerator = -(numerator / commonDivisor), + divisor = divisor / commonDivisor, + unsignedType = false + ) } return RationalNumber(negatedNumerator, divisor, false) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/iptc/IptcParser.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/iptc/IptcParser.kt index c3ce1489..197f8980 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/iptc/IptcParser.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/iptc/IptcParser.kt @@ -18,6 +18,7 @@ package com.ashampoo.kim.format.jpeg.iptc import com.ashampoo.kim.common.ByteOrder import com.ashampoo.kim.common.ImageReadException +import com.ashampoo.kim.common.decodeToIso8859String import com.ashampoo.kim.common.slice import com.ashampoo.kim.common.startsWith import com.ashampoo.kim.common.toInt @@ -26,9 +27,6 @@ import com.ashampoo.kim.common.toUInt8 import com.ashampoo.kim.format.jpeg.JpegConstants import com.ashampoo.kim.format.jpeg.iptc.IptcTypes.Companion.getIptcType import com.ashampoo.kim.input.ByteArrayByteReader -import io.ktor.utils.io.charsets.Charset -import io.ktor.utils.io.charsets.Charsets -import io.ktor.utils.io.core.String object IptcParser { @@ -43,8 +41,6 @@ object IptcParser { @Suppress("MagicNumber") private val PHOTOSHOP_IGNORED_BLOCK_TYPE = listOf(1084, 1085, 1086, 1087) - val DEFAULT_CHARSET = Charsets.ISO_8859_1 - const val CODED_CHARACTER_SET_IPTC_CODE = 90 /* "ESC % G" as bytes */ @@ -97,7 +93,7 @@ object IptcParser { private fun parseIPTCBlock(bytes: ByteArray): List { - var charset = DEFAULT_CHARSET + var isUtf8 = false val records = mutableListOf() @@ -131,7 +127,7 @@ object IptcParser { if (recordNumber == IptcConstants.IPTC_ENVELOPE_RECORD_NUMBER && recordType == CODED_CHARACTER_SET_IPTC_CODE ) { - charset = findCharset(recordData) + isUtf8 = isUtf8(recordData) continue } @@ -144,7 +140,10 @@ object IptcParser { records.add( IptcRecord( iptcType = getIptcType(recordType), - value = String(recordData, charset = charset) + value = if (isUtf8) + recordData.decodeToString() + else + recordData.decodeToIso8859String() ) ) } @@ -260,7 +259,7 @@ object IptcParser { return blocks } - private fun findCharset(codedCharset: ByteArray): Charset { + private fun isUtf8(codedCharset: ByteArray): Boolean { /* * check if encoding is a escape sequence @@ -273,8 +272,6 @@ object IptcParser { if (element != ' '.code.toByte()) codedCharsetNormalized[index++] = element - val utf8 = UTF8_CHARACTER_ESCAPE_SEQUENCE.contentEquals(codedCharsetNormalized) - - return if (utf8) Charsets.UTF_8 else DEFAULT_CHARSET + return UTF8_CHARACTER_ESCAPE_SEQUENCE.contentEquals(codedCharsetNormalized) } } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/iptc/IptcWriter.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/iptc/IptcWriter.kt index b9ab85d5..96c6e728 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/iptc/IptcWriter.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/iptc/IptcWriter.kt @@ -22,7 +22,6 @@ import com.ashampoo.kim.format.jpeg.iptc.IptcParser.APP13_BYTE_ORDER import com.ashampoo.kim.output.BigEndianBinaryByteWriter import com.ashampoo.kim.output.BinaryByteWriter import com.ashampoo.kim.output.ByteArrayByteWriter -import io.ktor.utils.io.core.toByteArray object IptcWriter { @@ -120,7 +119,7 @@ object IptcWriter { binaryWriter.write(iptcType.type) - val recordData = value.toByteArray() + val recordData = value.encodeToByteArray() binaryWriter.write2Bytes(recordData.size) binaryWriter.write(recordData) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/xmp/JpegXmpParser.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/xmp/JpegXmpParser.kt index 875bb3b2..b60fd541 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/xmp/JpegXmpParser.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jpeg/xmp/JpegXmpParser.kt @@ -17,10 +17,9 @@ package com.ashampoo.kim.format.jpeg.xmp import com.ashampoo.kim.common.ImageReadException +import com.ashampoo.kim.common.slice import com.ashampoo.kim.common.startsWith import com.ashampoo.kim.format.jpeg.JpegConstants -import io.ktor.utils.io.charsets.Charsets -import io.ktor.utils.io.core.String object JpegXmpParser { @@ -35,6 +34,9 @@ object JpegXmpParser { val index = JpegConstants.XMP_IDENTIFIER.size /* The data is UTF-8 encoded XML */ - return String(segmentData, index, segmentData.size - index, Charsets.UTF_8) + return segmentData.slice( + startIndex = index, + count = segmentData.size - index + ).decodeToString() } } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/png/ChunkType.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/png/ChunkType.kt index 7b145313..735ab889 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/png/ChunkType.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/png/ChunkType.kt @@ -16,8 +16,6 @@ */ package com.ashampoo.kim.format.png -import io.ktor.utils.io.core.toByteArray - /** * Type of a PNG chunk. * @@ -46,29 +44,30 @@ data class ChunkType internal constructor( companion object { /** Image header */ - val IHDR = of("IHDR".toByteArray()) + val IHDR = of("IHDR".encodeToByteArray()) /** Image data */ - val IDAT = of("IDAT".toByteArray()) + val IDAT = of("IDAT".encodeToByteArray()) /** Image end */ - val IEND = of("IEND".toByteArray()) + val IEND = of("IEND".encodeToByteArray()) /** Time */ - val TIME = of("tIME".toByteArray()) + val TIME = of("tIME".encodeToByteArray()) /** Text */ - val TEXT = of("tEXt".toByteArray()) + val TEXT = of("tEXt".encodeToByteArray()) /** Compressed text */ - val ZTXT = of("zTXt".toByteArray()) + val ZTXT = of("zTXt".encodeToByteArray()) /** UTF-8 text, for example XMP */ - val ITXT = of("iTXt".toByteArray()) + val ITXT = of("iTXt".encodeToByteArray()) /** EXIF (since 2017) */ - val EXIF = of("eXIf".toByteArray()) + val EXIF = of("eXIf".encodeToByteArray()) + @Suppress("MagicNumber") fun of(typeBytes: ByteArray): ChunkType { require(typeBytes.size == PngConstants.TPYE_LENGTH) { "ChunkType must be always 4 bytes!" } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/png/PngWriter.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/png/PngWriter.kt index a2cb55a6..d9eb4324 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/png/PngWriter.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/png/PngWriter.kt @@ -25,7 +25,6 @@ import com.ashampoo.kim.format.png.chunks.PngTextChunk import com.ashampoo.kim.input.ByteReader import com.ashampoo.kim.output.ByteArrayByteWriter import com.ashampoo.kim.output.ByteWriter -import io.ktor.utils.io.core.toByteArray object PngWriter { @@ -166,11 +165,11 @@ object PngWriter { writer.write(0) // No language tag /* XMP keyword - null-terminated */ - writer.write(PngConstants.XMP_KEYWORD.toByteArray()) + writer.write(PngConstants.XMP_KEYWORD.encodeToByteArray()) writer.write(0) /* XMP bytes */ - writer.write(xmpXml.toByteArray()) + writer.write(xmpXml.encodeToByteArray()) writeChunk(byteWriter, ChunkType.ITXT, writer.toByteArray()) } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkItxt.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkItxt.kt index f5eeafd5..6ea0b8bb 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkItxt.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkItxt.kt @@ -17,12 +17,12 @@ package com.ashampoo.kim.format.png.chunks import com.ashampoo.kim.common.ImageReadException +import com.ashampoo.kim.common.decodeToIso8859String import com.ashampoo.kim.common.decompress import com.ashampoo.kim.common.indexOfNullTerminator +import com.ashampoo.kim.common.slice import com.ashampoo.kim.format.png.ChunkType import com.ashampoo.kim.format.png.PngConstants -import io.ktor.utils.io.charsets.Charsets -import io.ktor.utils.io.core.String class PngChunkItxt( length: Int, @@ -48,7 +48,10 @@ class PngChunkItxt( if (terminatorIndex < 0) throw ImageReadException("PNG iTXt chunk keyword is not terminated.") - keyword = String(bytes, 0, terminatorIndex, Charsets.ISO_8859_1) + keyword = bytes.slice( + startIndex = 0, + count = terminatorIndex + ).decodeToIso8859String() var index = terminatorIndex + 1 @@ -69,7 +72,10 @@ class PngChunkItxt( if (terminatorIndex < 0) throw ImageReadException("PNG iTXt chunk language tag is not terminated.") - languageTag = String(bytes, index, terminatorIndex - index, Charsets.ISO_8859_1) + languageTag = bytes.copyOfRange( + fromIndex = index, + toIndex = terminatorIndex + ).decodeToIso8859String() index = terminatorIndex + 1 @@ -78,14 +84,22 @@ class PngChunkItxt( if (terminatorIndex < 0) throw ImageReadException("PNG iTXt chunk translated keyword is not terminated.") - translatedKeyword = String(bytes, index, terminatorIndex - index, Charsets.UTF_8) + translatedKeyword = bytes.copyOfRange( + fromIndex = index, + toIndex = terminatorIndex + ).decodeToString() index = terminatorIndex + 1 + val subBytes = bytes.copyOfRange( + fromIndex = index, + toIndex = bytes.size + ) + text = if (compressed) - decompress(bytes.copyOfRange(index, bytes.size)) + decompress(subBytes) else - String(bytes, index, bytes.size - index, Charsets.UTF_8) + subBytes.decodeToString() } /** diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkText.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkText.kt index 399f7d58..d4b4de9a 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkText.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkText.kt @@ -17,10 +17,9 @@ package com.ashampoo.kim.format.png.chunks import com.ashampoo.kim.common.ImageReadException +import com.ashampoo.kim.common.decodeToIso8859String import com.ashampoo.kim.common.indexOfNullTerminator import com.ashampoo.kim.format.png.ChunkType -import io.ktor.utils.io.charsets.Charsets -import io.ktor.utils.io.core.String class PngChunkText( length: Int, @@ -42,11 +41,17 @@ class PngChunkText( if (index < 0) throw ImageReadException("PNG tEXt chunk keyword is not terminated.") - keyword = String(bytes, 0, index, Charsets.ISO_8859_1) + keyword = bytes.copyOfRange( + fromIndex = 0, + toIndex = index + ).decodeToIso8859String() val textLength = bytes.size - (index + 1) - text = String(bytes, index + 1, textLength, Charsets.ISO_8859_1) + text = bytes.copyOfRange( + fromIndex = index + 1, + toIndex = textLength + ).decodeToIso8859String() } override fun getKeyword(): String = diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkZtxt.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkZtxt.kt index e49e5adb..abac2167 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkZtxt.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/png/chunks/PngChunkZtxt.kt @@ -17,12 +17,11 @@ package com.ashampoo.kim.format.png.chunks import com.ashampoo.kim.common.ImageReadException +import com.ashampoo.kim.common.decodeToIso8859String import com.ashampoo.kim.common.decompress import com.ashampoo.kim.common.indexOfNullTerminator import com.ashampoo.kim.format.png.ChunkType import com.ashampoo.kim.format.png.PngConstants -import io.ktor.utils.io.charsets.Charsets -import io.ktor.utils.io.core.String class PngChunkZtxt( length: Int, @@ -44,7 +43,11 @@ class PngChunkZtxt( if (index < 0) throw ImageReadException("PNG zTXt chunk keyword is not terminated.") - keyword = String(bytes, 0, index, Charsets.ISO_8859_1) + keyword = bytes.copyOfRange( + fromIndex = 0, + toIndex = index + ).decodeToIso8859String() + index++ val compressionMethod = bytes[index++].toInt() diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/raf/RafMetadataExtractor.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/raf/RafMetadataExtractor.kt index 40e1c9fe..4afcd831 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/raf/RafMetadataExtractor.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/raf/RafMetadataExtractor.kt @@ -56,7 +56,7 @@ object RafMetadataExtractor : MetadataExtractor { return@tryWithImageReadException JpegMetadataExtractor.extractMetadataBytes(newReader) } - @Suppress("ComplexCondition", "LoopWithTooManyJumpStatements") + @Suppress("ComplexCondition", "LoopWithTooManyJumpStatements", "MagicNumber") internal fun skipToJpegMagicBytes(byteReader: ByteReader) { @Suppress("kotlin:S1481") // false positive @@ -73,7 +73,8 @@ object RafMetadataExtractor : MetadataExtractor { bytes[bytes.lastIndex - 2] == JpegConstants.SOI[0] && bytes[bytes.lastIndex - 1] == JpegConstants.SOI[1] && bytes[bytes.lastIndex - 0] == 0xFF.toByte() - ) break + ) + break } } } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/raf/RafPreviewExtractor.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/raf/RafPreviewExtractor.kt index 5c0c6d95..e88e51ab 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/raf/RafPreviewExtractor.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/raf/RafPreviewExtractor.kt @@ -63,7 +63,8 @@ object RafPreviewExtractor { if (bytes.size >= 2 && bytes[bytes.lastIndex - 1] == JpegMetadataExtractor.SEGMENT_IDENTIFIER && bytes[bytes.lastIndex - 0] == JpegMetadataExtractor.MARKER_END_OF_IMAGE - ) break + ) + break } return@tryWithImageReadException bytes.toByteArray() diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/TiffImageParser.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/TiffImageParser.kt index ee83b2a2..338bcd70 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/TiffImageParser.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/TiffImageParser.kt @@ -25,8 +25,6 @@ import com.ashampoo.kim.input.ByteReader import com.ashampoo.kim.input.DefaultRandomAccessByteReader import com.ashampoo.kim.model.ImageFormat import com.ashampoo.kim.model.ImageSize -import io.ktor.utils.io.charsets.Charsets -import io.ktor.utils.io.core.String object TiffImageParser : ImageParser { @@ -91,6 +89,6 @@ object TiffImageParser : ImageParser { if (bytes.isEmpty()) return null - return String(bytes, charset = Charsets.UTF_8) + return bytes.decodeToString() } } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/TiffReader.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/TiffReader.kt index 922dd02a..a7d81094 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/TiffReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/TiffReader.kt @@ -282,8 +282,10 @@ object TiffReader { byteReader.readBytes(valueOrOffset.toInt(), valueLength.toInt()) - } else + } else { + valueOrOffsetBytes + } fields.add( TiffField(tag, dirType, fieldType, count, valueOrOffset, valueBytes, byteOrder, entryIndex) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/fieldtypes/FieldTypeAscii.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/fieldtypes/FieldTypeAscii.kt index 3abf1d19..e327ac25 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/fieldtypes/FieldTypeAscii.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/fieldtypes/FieldTypeAscii.kt @@ -19,10 +19,8 @@ package com.ashampoo.kim.format.tiff.fieldtypes import com.ashampoo.kim.common.ByteOrder import com.ashampoo.kim.common.ImageWriteException import com.ashampoo.kim.common.indexOfNullTerminator +import com.ashampoo.kim.common.slice import com.ashampoo.kim.format.tiff.TiffField -import io.ktor.utils.io.charsets.Charsets -import io.ktor.utils.io.core.String -import io.ktor.utils.io.core.toByteArray class FieldTypeAscii(type: Int, name: String) : FieldType(type, name, 1) { @@ -54,12 +52,10 @@ class FieldTypeAscii(type: Int, name: String) : FieldType(type, name, 1) { * specifies that the TIFF ASCII fields are actually UTF-8. * Exiftool however allows you to configure the charset used. */ - val string = String( - bytes = bytes, - offset = 0, - length = length, - charset = Charsets.UTF_8 - ) + val string = bytes.slice( + startIndex = 0, + count = length + ).decodeToString() return string } @@ -73,7 +69,7 @@ class FieldTypeAscii(type: Int, name: String) : FieldType(type, name, 1) { } if (data is String) { - val bytes = data.toByteArray() + val bytes = data.encodeToByteArray() val result = bytes.copyOf(bytes.size + 1) result[result.lastIndex] = 0 return result diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfo.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfo.kt index 095efcf4..8ec7d558 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfo.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfo.kt @@ -17,6 +17,7 @@ package com.ashampoo.kim.format.tiff.taginfos import com.ashampoo.kim.common.ByteOrder +import com.ashampoo.kim.common.HEX_RADIX import com.ashampoo.kim.format.tiff.TiffField import com.ashampoo.kim.format.tiff.constants.ExifTag.EXIF_DIRECTORY_UNKNOWN import com.ashampoo.kim.format.tiff.constants.TiffDirectoryType @@ -33,7 +34,7 @@ open class TagInfo( /** Return a proper Tag ID like 0x0100 */ val tagFormatted: String = - "0x" + tag.toString(16).padStart(4, '0') + "0x" + tag.toString(HEX_RADIX).padStart(4, '0') val description: String = "$tagFormatted $name" diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfoAscii.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfoAscii.kt index cb248cce..a15ec460 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfoAscii.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfoAscii.kt @@ -19,8 +19,6 @@ package com.ashampoo.kim.format.tiff.taginfos import com.ashampoo.kim.common.ByteOrder import com.ashampoo.kim.format.tiff.constants.TiffDirectoryType import com.ashampoo.kim.format.tiff.fieldtypes.FieldType -import io.ktor.utils.io.charsets.Charsets -import io.ktor.utils.io.core.String class TagInfoAscii( name: String, @@ -45,12 +43,10 @@ class TagInfoAscii( if (bytes[index].toInt() == 0) { - val string = String( - bytes = bytes, - offset = nextStringPos, - length = index - nextStringPos, - charset = Charsets.UTF_8 - ) + val string = bytes.copyOfRange( + fromIndex = nextStringPos, + toIndex = index + ).decodeToString() /* Ignore blank strings and rewrite files without them. */ if (string.isNotBlank()) @@ -66,12 +62,10 @@ class TagInfoAscii( */ if (nextStringPos < bytes.size) { - val string = String( - bytes = bytes, - offset = nextStringPos, - length = bytes.size - nextStringPos, - charset = Charsets.UTF_8 - ) + val string = bytes.copyOfRange( + fromIndex = nextStringPos, + toIndex = bytes.size + ).decodeToString() /* Ignore blank strings and rewrite files without them. */ if (string.isNotBlank()) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfoGpsText.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfoGpsText.kt index 69a2a3b1..f9764a6e 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfoGpsText.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/taginfos/TagInfoGpsText.kt @@ -19,6 +19,7 @@ package com.ashampoo.kim.format.tiff.taginfos import com.ashampoo.kim.common.ByteOrder import com.ashampoo.kim.common.ImageReadException import com.ashampoo.kim.common.ImageWriteException +import com.ashampoo.kim.common.decodeToIso8859String import com.ashampoo.kim.common.isEquals import com.ashampoo.kim.format.tiff.TiffField import com.ashampoo.kim.format.tiff.constants.TiffDirectoryType @@ -80,14 +81,15 @@ class TagInfoGpsText( /* TODO Handle */ } else if (fieldType === FieldType.BYTE) { /* TODO Handle */ - } else + } else { throw ImageReadException("GPS text field not encoded as bytes.") + } val bytes = entry.byteArrayValue /* Try ASCII with NO prefix. */ if (bytes.size < 8) - return String(bytes, charset = Charsets.ISO_8859_1) + return bytes.decodeToIso8859String() for (encoding in TEXT_ENCODINGS) { @@ -119,7 +121,7 @@ class TagInfoGpsText( } } - return String(bytes, charset = Charsets.ISO_8859_1) + return bytes.decodeToIso8859String() } companion object { diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/write/TiffWriterLossless.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/write/TiffWriterLossless.kt index 05832697..c9e9ce8e 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/write/TiffWriterLossless.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/tiff/write/TiffWriterLossless.kt @@ -392,11 +392,5 @@ class TiffWriterLossless( companion object { const val OFFSET_TOLERANCE = 3 - - private val elementLengthComparator = - compareBy { element: TiffElement -> element.length } - - private val itemLengthComparator = - compareBy { item: TiffOutputItem -> item.getItemLength() } } } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt index 8f3cfee5..40e6431b 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/xmp/XmpWriter.kt @@ -91,7 +91,7 @@ object XmpWriter { } /** - * @param writePackageWrapper Should be "true" for embedded XMP + * Note: Parameter 'writePackageWrapper' should be "true" for embedded XMP */ @Throws(XMPException::class) @Suppress("LoopWithTooManyJumpStatements") @@ -108,7 +108,7 @@ object XmpWriter { } /** - * @param writePackageWrapper Should be "true" for embedded XMP + * Note: Parameter 'writePackageWrapper' should be "true" for embedded XMP */ @Throws(XMPException::class) @Suppress("LoopWithTooManyJumpStatements") diff --git a/src/commonMain/kotlin/com/ashampoo/kim/input/ByteReader.kt b/src/commonMain/kotlin/com/ashampoo/kim/input/ByteReader.kt index fe2699b9..09230eb6 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/input/ByteReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/input/ByteReader.kt @@ -158,11 +158,14 @@ interface ByteReader : Closeable { position++ - if (position == needle.size) + if (position == needle.size) { return true + } + + } else { - } else position = 0 + } } return false diff --git a/src/commonMain/kotlin/com/ashampoo/kim/output/ByteWriter.kt b/src/commonMain/kotlin/com/ashampoo/kim/output/ByteWriter.kt index 33728e53..90e4a996 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/output/ByteWriter.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/output/ByteWriter.kt @@ -25,6 +25,7 @@ interface ByteWriter : Closeable { fun flush() + @Suppress("MagicNumber") fun writeInt(value: Int) { write(0xFF and (value shr 24)) write(0xFF and (value shr 16)) diff --git a/src/commonTest/kotlin/com/ashampoo/kim/common/ByteArrayExtensionsTest.kt b/src/commonTest/kotlin/com/ashampoo/kim/common/ByteArrayExtensionsTest.kt index 42d6f4eb..631309a5 100644 --- a/src/commonTest/kotlin/com/ashampoo/kim/common/ByteArrayExtensionsTest.kt +++ b/src/commonTest/kotlin/com/ashampoo/kim/common/ByteArrayExtensionsTest.kt @@ -115,20 +115,20 @@ class ByteArrayExtensionsTest { } @Test - fun testToAsciiString() { + fun testDecodeToIso8859String() { assertEquals( "RIFF", byteArrayOf( 0x52, 0x49, 0x46, 0x46 - ).toAsciiString() + ).decodeToIso8859String() ) assertEquals( "WEBP", byteArrayOf( 0x57, 0x45, 0x42, 0x50 - ).toAsciiString() + ).decodeToIso8859String() ) assertEquals( @@ -136,7 +136,30 @@ class ByteArrayExtensionsTest { byteArrayOf( 0x46, 0x55, 0x4A, 0x49, 0x46, 0x49, 0x4C, 0x4D, 0x43, 0x43, 0x44, 0x2D, 0x52, 0x41, 0x57 - ).toAsciiString() + ).decodeToIso8859String() + ) + + /* ISO 8859-1 bytes */ + assertEquals( + "Äußerst öffentliches Ü!", + byteArrayOf( + 0xC4.toByte(), 0x75, 0xDF.toByte(), 0x65, 0x72, + 0x73, 0x74, 0x20, 0xF6.toByte(), 0x66, 0x66, + 0x65, 0x6E, 0x74, 0x6C, 0x69, 0x63, 0x68, 0x65, + 0x73, 0x20, 0xDC.toByte(), 0x21 + ).decodeToIso8859String() + ) + + /* Just for comparison the UTF-8 bytes. */ + assertEquals( + "Äußerst öffentliches Ü!", + byteArrayOf( + 0xC3.toByte(), 0x84.toByte(), 0x75, 0xC3.toByte(), + 0x9F.toByte(), 0x65, 0x72, 0x73, 0x74, 0x20, + 0xC3.toByte(), 0xB6.toByte(), 0x66, 0x66, 0x65, + 0x6E, 0x74, 0x6C, 0x69, 0x63, 0x68, 0x65, 0x73, + 0x20, 0xC3.toByte(), 0x9C.toByte(), 0x21 + ).decodeToString() ) } } diff --git a/src/commonTest/kotlin/com/ashampoo/kim/format/jpeg/JpegRewriterTest.kt b/src/commonTest/kotlin/com/ashampoo/kim/format/jpeg/JpegRewriterTest.kt index 6fedf82d..9f41260e 100644 --- a/src/commonTest/kotlin/com/ashampoo/kim/format/jpeg/JpegRewriterTest.kt +++ b/src/commonTest/kotlin/com/ashampoo/kim/format/jpeg/JpegRewriterTest.kt @@ -61,6 +61,9 @@ class JpegRewriterTest { """.trimIndent() + private val photosWithoutEmbeddedXmp = + setOf(2, 20, 23, 30, 48) + @BeforeTest fun setUp() { Kim.underUnitTesting = true @@ -209,9 +212,8 @@ class JpegRewriterTest { val expectedTagInfos = expectedDirectory.getFields().map { it.tagInfo.tag }.sorted() val actualTagInfos = actualDirectory.getFields().map { it.tagInfo.tag }.sorted() + /* For some reason these offsets disappear, even if they are written. */ val missingTagInfos = expectedTagInfos - actualTagInfos.toSet() - - /* For some reason this offsets disappear, even if they are written. */ - // FIXME Find out, why ExifTag.EXIF_TAG_EXIF_OFFSET.tag - ExifTag.EXIF_TAG_GPSINFO.tag - ExifTag.EXIF_TAG_INTEROP_OFFSET.tag - @@ -333,9 +335,6 @@ class JpegRewriterTest { } } - private val photosWithoutEmbeddedXmp = - setOf(2, 20, 23, 30, 48) - /** * Regression test based on a fixed small set of test files. */ diff --git a/src/posixMain/kotlin/com/ashampoo/kim/common/ZLib.kt b/src/posixMain/kotlin/com/ashampoo/kim/common/ZLib.kt index 88015496..05b3abbd 100644 --- a/src/posixMain/kotlin/com/ashampoo/kim/common/ZLib.kt +++ b/src/posixMain/kotlin/com/ashampoo/kim/common/ZLib.kt @@ -38,6 +38,8 @@ import platform.zlib.inflateInit import platform.zlib.uByteVar import platform.zlib.z_stream +private const val OUTPUT_BUFFER_LENGTH = 4096 + @OptIn(UnsafeNumber::class, ExperimentalForeignApi::class) actual fun compress(input: String): ByteArray { @@ -93,8 +95,7 @@ actual fun decompress(byteArray: ByteArray): String { /* Specify the decompression mode */ inflateInit(stream.ptr) - val outputBufferLength = 4096 - val outputBuffer = ByteArray(outputBufferLength) + val outputBuffer = ByteArray(OUTPUT_BUFFER_LENGTH) var decompressedData = "" try { @@ -102,7 +103,7 @@ actual fun decompress(byteArray: ByteArray): String { /* Set the output buffer and its length */ stream.next_out = outputBuffer.refTo(0).getPointer(this) as CPointer - stream.avail_out = outputBufferLength.toUInt() + stream.avail_out = OUTPUT_BUFFER_LENGTH.toUInt() /* Decompress the data */ val result = inflate(stream.ptr, Z_NO_FLUSH) @@ -111,14 +112,14 @@ actual fun decompress(byteArray: ByteArray): String { Z_STREAM_END -> { /* The end of the compressed data was reached */ - val bytesWritten = outputBufferLength - stream.avail_out.toInt() + val bytesWritten = OUTPUT_BUFFER_LENGTH - stream.avail_out.toInt() decompressedData += outputBuffer.decodeToString(0, bytesWritten) break } Z_OK -> { /* More decompressed data is available */ - val bytesWritten = outputBufferLength - stream.avail_out.toInt() + val bytesWritten = OUTPUT_BUFFER_LENGTH - stream.avail_out.toInt() decompressedData += outputBuffer.decodeToString(0, bytesWritten) }