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+
117package com.ashampoo.kim.format.gif
218
319import 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