Skip to content

Commit 625d0e6

Browse files
Updated Kotlin version and some cleanups for the new code (#121)
1 parent 63fbfbe commit 625d0e6

27 files changed

+592
-116
lines changed

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Kim - Kotlin Image Metadata
22

3-
[![Kotlin](https://img.shields.io/badge/kotlin-2.1.10-blue.svg?logo=kotlin)](httpw://kotlinlang.org)
3+
[![Kotlin](https://img.shields.io/badge/kotlin-2.1.20-blue.svg?logo=kotlin)](httpw://kotlinlang.org)
44
![JVM](https://img.shields.io/badge/-JVM-gray.svg?style=flat)
55
![Android](https://img.shields.io/badge/-Android-gray.svg?style=flat)
66
![iOS](https://img.shields.io/badge/-iOS-gray.svg?style=flat)
@@ -39,7 +39,7 @@ of Ashampoo Photo Organizer, which, in turn, is driven by user community feedbac
3939
## Installation
4040

4141
```
42-
implementation("com.ashampoo:kim:0.23")
42+
implementation("com.ashampoo:kim:0.24")
4343
```
4444

4545
For the targets `wasmJs` & `js` you also need to specify this:

‎build.gradle.kts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
44
import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework
55

66
plugins {
7-
kotlin("multiplatform") version "2.1.10"
7+
kotlin("multiplatform") version "2.1.20"
88
id("com.android.library") version "8.5.0"
99
id("maven-publish")
1010
id("signing")
@@ -25,7 +25,7 @@ repositories {
2525

2626
val productName: String = "Ashampoo Kim"
2727

28-
val ktorVersion: String = "3.1.1"
28+
val ktorVersion: String = "3.1.2"
2929
val xmpCoreVersion: String = "1.5.1"
3030
val dateTimeVersion: String = "0.6.2"
3131
val kotlinxIoVersion: String = "0.7.0"

‎src/commonMain/kotlin/com/ashampoo/kim/format/gif/GifChunkType.kt‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright 2025 Ramon Bouckaert
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package com.ashampoo.kim.format.gif
218

319
public enum class GifChunkType {

‎src/commonMain/kotlin/com/ashampoo/kim/format/gif/GifConstants.kt‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright 2025 Ramon Bouckaert
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package com.ashampoo.kim.format.gif
218

319
import com.ashampoo.kim.common.ByteOrder

‎src/commonMain/kotlin/com/ashampoo/kim/format/gif/GifImageParser.kt‎

Lines changed: 92 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
/*
2+
* Copyright 2025 Ramon Bouckaert
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
117
package com.ashampoo.kim.format.gif
218

319
import com.ashampoo.kim.common.ImageReadException
@@ -31,11 +47,11 @@ public object GifImageParser : ImageParser {
3147
@Throws(ImageReadException::class)
3248
override fun parseMetadata(byteReader: ByteReader): ImageMetadata =
3349
tryWithImageReadException {
50+
3451
val chunks = readChunks(byteReader, metadataChunkTypes)
3552

36-
if (chunks.isEmpty()) {
53+
if (chunks.isEmpty())
3754
throw ImageReadException("Did not find any chunks in file.")
38-
}
3955

4056
return@tryWithImageReadException parseMetadataFromChunks(chunks)
4157
}
@@ -52,7 +68,7 @@ public object GifImageParser : ImageParser {
5268

5369
checkNotNull(headerChunk) {
5470
"Did not find mandatory header chunk. " +
55-
"Found chunk types: ${chunks.map { it.type }}"
71+
"Found chunk types: ${chunks.map { it.type }}"
5672
}
5773

5874
val version = headerChunk.version
@@ -61,13 +77,16 @@ public object GifImageParser : ImageParser {
6177

6278
checkNotNull(firstImageDescriptorChunk) {
6379
"Did not find mandatory image descriptor chunk. " +
64-
"Found chunk types: ${chunks.map { it.type }}"
80+
"Found chunk types: ${chunks.map { it.type }}"
6581
}
6682

6783
val imageSize = firstImageDescriptorChunk.imageSize
6884

69-
// Only GIF89A supports XMP metadata
70-
val xmp = if (version == GifVersion.GIF89A) getXmpXml(chunks) else null
85+
/* Only GIF89A supports XMP metadata */
86+
val xmp = if (version == GifVersion.GIF89A)
87+
getXmpXml(chunks)
88+
else
89+
null
7190

7291
return@tryWithImageReadException ImageMetadata(
7392
imageFormat = ImageFormat.GIF,
@@ -89,36 +108,47 @@ public object GifImageParser : ImageParser {
89108
byteReader: ByteReader,
90109
chunkTypeFilter: List<GifChunkType>?
91110
): List<GifChunk> {
111+
92112
val chunks = mutableListOf<GifChunk>()
93113

94-
// Read header chunk
114+
/* Read header chunk */
95115
val headerBytes = byteReader.readBytes(6)
96-
if (chunkTypeFilter?.contains(GifChunkType.HEADER) != false) chunks.add(GifChunkHeader(headerBytes))
97116

98-
// Read logical screen descriptor chunk
117+
if (chunkTypeFilter?.contains(GifChunkType.HEADER) != false)
118+
chunks.add(GifChunkHeader(headerBytes))
119+
120+
/* Read logical screen descriptor chunk */
99121
val logicalScreenDescriptorBytes = byteReader.readBytes(7)
100122
val logicalScreenDescriptorChunk = GifChunkLogicalScreenDescriptor(logicalScreenDescriptorBytes)
123+
101124
if (chunkTypeFilter?.contains(GifChunkType.LOGICAL_SCREEN_DESCRIPTOR) != false)
102125
chunks.add(logicalScreenDescriptorChunk)
103126

104-
// Read global color table chunk if present
127+
/* Read global color table chunk if present */
105128
if (logicalScreenDescriptorChunk.globalColorTableFlag) {
129+
106130
val globalColorTableSize = 3 * (1 shl (logicalScreenDescriptorChunk.globalColorTableSize + 1))
131+
107132
val globalColorTableBytes = byteReader.readBytes(globalColorTableSize)
108-
if (chunkTypeFilter?.contains(GifChunkType.GLOBAL_COLOR_TABLE) != false) {
133+
134+
if (chunkTypeFilter?.contains(GifChunkType.GLOBAL_COLOR_TABLE) != false)
109135
chunks.add(GifChunk(GifChunkType.GLOBAL_COLOR_TABLE, globalColorTableBytes))
110-
}
111136
}
112137

113-
// Read remaining chunks
138+
/* Read remaining chunks */
114139
while (true) {
140+
115141
when (byteReader.readByte("introducer")) {
142+
116143
GifConstants.IMAGE_SEPARATOR -> chunks.addAll(readImageChunks(byteReader, chunkTypeFilter))
144+
117145
GifConstants.EXTENSION_INTRODUCER -> readExtensionChunk(byteReader, chunkTypeFilter)?.also(chunks::add)
146+
118147
GifConstants.GIF_TERMINATOR -> {
119-
if (chunkTypeFilter?.contains(GifChunkType.TERMINATOR) != false) {
148+
149+
if (chunkTypeFilter?.contains(GifChunkType.TERMINATOR) != false)
120150
chunks.add(GifChunkTerminator(byteArrayOf(GifConstants.GIF_TERMINATOR)))
121-
}
151+
122152
break
123153
}
124154
}
@@ -127,91 +157,107 @@ public object GifImageParser : ImageParser {
127157
return chunks
128158
}
129159

130-
private fun readImageChunks(byteReader: ByteReader, chunkTypeFilter: List<GifChunkType>?): List<GifChunk> {
160+
private fun readImageChunks(
161+
byteReader: ByteReader,
162+
chunkTypeFilter: List<GifChunkType>?
163+
): List<GifChunk> {
164+
131165
val chunks = mutableListOf<GifChunk>()
132166

133-
// Read image descriptor
167+
/* Read image descriptor */
134168
val imageDescriptorBytes = byteReader.readBytes("image descriptor", 9)
169+
135170
val imageDescriptorChunk = GifChunkImageDescriptor(
136171
byteArrayOf(GifConstants.IMAGE_SEPARATOR) + imageDescriptorBytes
137172
)
173+
138174
if (chunkTypeFilter?.contains(GifChunkType.IMAGE_DESCRIPTOR) != false)
139175
chunks.add(imageDescriptorChunk)
140176

141-
// Read local color table if present
177+
/* Read local color table if present */
142178
if (imageDescriptorChunk.localColorTableFlag) {
179+
143180
val localColorTableSize = 3 * (1 shl (imageDescriptorChunk.localColorTableSize + 1))
144181
val localColorTableBytes = byteReader.readBytes("local color table", localColorTableSize)
145-
if (chunkTypeFilter?.contains(GifChunkType.LOCAL_COLOR_TABLE) != false) {
182+
183+
if (chunkTypeFilter?.contains(GifChunkType.LOCAL_COLOR_TABLE) != false)
146184
chunks.add(GifChunk(GifChunkType.LOCAL_COLOR_TABLE, localColorTableBytes))
147-
}
148185
}
149186

150-
// Read image data
187+
/* Read image data */
151188
val lzwMinimumCodeSize = byteReader.readByte("LZW minimum code size")
152189
val subChunks = byteReader.parseGifSubChunksUntilEmpty("image data")
153-
if (chunkTypeFilter?.contains(GifChunkType.IMAGE_DATA) != false) {
190+
191+
if (chunkTypeFilter?.contains(GifChunkType.IMAGE_DATA) != false)
154192
chunks.add(GifChunkImageData(lzwMinimumCodeSize, subChunks))
155-
}
156193

157194
return chunks
158195
}
159196

160-
private fun readExtensionChunk(byteReader: ByteReader, chunkTypeFilter: List<GifChunkType>?): GifChunk? =
197+
private fun readExtensionChunk(
198+
byteReader: ByteReader,
199+
chunkTypeFilter: List<GifChunkType>?
200+
): GifChunk? =
161201
when (byteReader.readByte("extension label")) {
202+
162203
GifConstants.GRAPHICS_CONTROL_EXTENSION_LABEL -> {
204+
163205
val graphicsControlExtensionBytes = byteReader.readBytes("graphics control extension", 6)
206+
164207
val graphicsControlExtensionChunk = GifChunk(
165208
GifChunkType.GRAPHICS_CONTROL_EXTENSION,
166209
byteArrayOf(
167210
GifConstants.EXTENSION_INTRODUCER,
168211
GifConstants.GRAPHICS_CONTROL_EXTENSION_LABEL
169212
) + graphicsControlExtensionBytes
170213
)
171-
if (chunkTypeFilter?.contains(GifChunkType.GRAPHICS_CONTROL_EXTENSION) != false) {
214+
215+
if (chunkTypeFilter?.contains(GifChunkType.GRAPHICS_CONTROL_EXTENSION) != false)
172216
graphicsControlExtensionChunk
173-
} else {
217+
else
174218
null
175-
}
176219
}
177220

178221
GifConstants.APPLICATION_EXTENSION_LABEL -> {
222+
179223
val subChunks = byteReader.parseGifSubChunksUntilEmpty("application extension")
180-
if (chunkTypeFilter?.contains(GifChunkType.APPLICATION_EXTENSION) != false) {
224+
225+
if (chunkTypeFilter?.contains(GifChunkType.APPLICATION_EXTENSION) != false)
181226
GifChunkApplicationExtension(
182227
byteArrayOf(
183228
GifConstants.EXTENSION_INTRODUCER,
184229
GifConstants.APPLICATION_EXTENSION_LABEL
185230
),
186231
subChunks
187232
)
188-
} else {
233+
else
189234
null
190-
}
191235
}
192236

193237
GifConstants.COMMENT_EXTENSION_LABEL -> {
238+
194239
val subChunks = byteReader.parseGifSubChunksUntilEmpty("comment extension")
195-
if (chunkTypeFilter?.contains(GifChunkType.COMMENT_EXTENSION) != false) {
240+
241+
if (chunkTypeFilter?.contains(GifChunkType.COMMENT_EXTENSION) != false)
196242
GifChunkCommentExtension(
197243
byteArrayOf(GifConstants.EXTENSION_INTRODUCER, GifConstants.COMMENT_EXTENSION_LABEL),
198244
subChunks
199245
)
200-
} else {
246+
else
201247
null
202-
}
203248
}
204249

205250
GifConstants.PLAIN_TEXT_EXTENSION_LABEL -> {
251+
206252
val subChunks = byteReader.parseGifSubChunksUntilEmpty("plain text extension")
207-
if (chunkTypeFilter?.contains(GifChunkType.PLAIN_TEXT_EXTENSION) != false) {
253+
254+
if (chunkTypeFilter?.contains(GifChunkType.PLAIN_TEXT_EXTENSION) != false)
208255
GifChunkPlainTextExtension(
209256
byteArrayOf(GifConstants.EXTENSION_INTRODUCER, GifConstants.PLAIN_TEXT_EXTENSION_LABEL),
210257
subChunks
211258
)
212-
} else {
259+
else
213260
null
214-
}
215261
}
216262

217263
else -> null
@@ -220,13 +266,22 @@ public object GifImageParser : ImageParser {
220266
internal fun ByteReader.parseGifSubChunksUntilEmpty(
221267
fieldName: String
222268
): List<ByteArray> {
269+
223270
val subChunks = mutableListOf<ByteArray>()
271+
224272
while (true) {
273+
225274
val subChunkSize = this.readByteAsInt()
226-
if (subChunkSize == 0) break // End of sub chunks
275+
276+
/* Break at the end of sub chunks */
277+
if (subChunkSize == 0)
278+
break
279+
227280
val subChunkBytes = this.readBytes("$fieldName sub chunk", subChunkSize)
281+
228282
subChunks.add(byteArrayOf(subChunkSize.toByte()) + subChunkBytes)
229283
}
284+
230285
return subChunks
231286
}
232287
}

0 commit comments

Comments
 (0)