| | 123 | |
|---|
| | 124 | /** Decode source png file |
|---|
| | 125 | |
|---|
| | 126 | If a buffer is provided, it may be used to store the result. See bufferSize for details. |
|---|
| | 127 | |
|---|
| | 128 | Throws: PngException |
|---|
| | 129 | |
|---|
| | 130 | Returns: Decoded image pixels. The color format of the resulting image is the |
|---|
| | 131 | same as the source image, see lodepng.Common.convert and decode32 if a specific color format |
|---|
| | 132 | is desired. |
|---|
| | 133 | |
|---|
| | 134 | Params: |
|---|
| | 135 | source = a png file |
|---|
| | 136 | info = information about the image will be stored in here |
|---|
| | 137 | buffer = optionally provide an array to use as a buffer while decoding |
|---|
| | 138 | dg = optionally provide a delegate that will be called for (and only for) unknown chunks |
|---|
| | 139 | */ |
|---|
| | 140 | ubyte[] decode(in ubyte[] source, ref PngInfo info, ubyte[] buffer = null, int delegate(inout Chunk) dg = null) |
|---|
| | 141 | { |
|---|
| | 142 | info.image = source.readHeader(); |
|---|
| | 143 | scope decompressor = new PngDecoder(info.image); |
|---|
| | 144 | |
|---|
| | 145 | foreach(chunk; StreamChunkIter(source[HEADER_SIZE - 1 .. $])) |
|---|
| | 146 | { |
|---|
| | 147 | if (chunk.type == IDAT) |
|---|
| | 148 | decompressor(chunk.data); |
|---|
| | 149 | else if (!parseChunk(chunk, info)) |
|---|
| | 150 | if (dg !is null) |
|---|
| | 151 | if (dg(chunk) != 0) |
|---|
| | 152 | return null; |
|---|
| | 153 | |
|---|
| | 154 | } |
|---|
| | 155 | assert(decompressor.ended); |
|---|
| | 156 | |
|---|
| | 157 | return decompressor.reconstructImage(); |
|---|
| | 158 | } |
|---|
| | 159 | |
|---|
| 110 | | PngImage readHeader(in ubyte[] source) |
|---|
| 111 | | { |
|---|
| 112 | | ubyte interlace; |
|---|
| 113 | | return readHeader(source, interlace); |
|---|
| 114 | | } |
|---|
| 115 | | |
|---|
| 116 | | /// ditto |
|---|
| 117 | | PngImage readHeader(in ubyte[] source, out ubyte interlace) |
|---|
| | 172 | ubyte[] decode32(/+const+/ in ubyte[] source, ref PngInfo info, ubyte[] buffer = null) |
|---|
| | 173 | { |
|---|
| | 174 | buffer = decode(source, info, buffer); |
|---|
| | 175 | buffer = convert(buffer, info, ColorType.RGBA); |
|---|
| | 176 | info.image.colorType = ColorType.RGBA; |
|---|
| | 177 | info.image.bpp = 32; |
|---|
| | 178 | info.image.bitDepth = 8; |
|---|
| | 179 | info.palette.length = 0; |
|---|
| | 180 | return buffer; |
|---|
| | 181 | } |
|---|
| | 182 | |
|---|
| | 183 | |
|---|
| | 184 | /*************************************************************************************************** |
|---|
| | 185 | Parse png image header from memory. |
|---|
| | 186 | |
|---|
| | 187 | throws: PngException |
|---|
| | 188 | |
|---|
| | 189 | returns: header information |
|---|
| | 190 | |
|---|
| | 191 | Params: source=must contain the first 33 bytes of a png file |
|---|
| | 192 | ***************************************************************************************************/ |
|---|
| | 193 | PngImage readHeader(/+const+/ in ubyte[] source) |
|---|
| 124 | | mixin(pngEnforce(`source.length >= HEADER_SIZE`, "png header data is too small")); |
|---|
| 125 | | mixin(pngEnforce(`source[0..8] == [cast(ubyte)137, 80, 78, 71, 13, 10, 26, 10]`, "invalid png header ")); |
|---|
| 126 | | mixin(pngEnforce((toUint(source[12..16]) == IHDR).stringof, "invalid png header")); |
|---|
| 127 | | mixin(pngEnforce(`checkCRC(source[29 .. 33], source[12 .. 29])`, "invalid CRC")); |
|---|
| 128 | | |
|---|
| 129 | | PngImage result; |
|---|
| 130 | | ubyte interlaceMethod; |
|---|
| 131 | | |
|---|
| 132 | | with (result) |
|---|
| 133 | | { |
|---|
| 134 | | width = toUint(source[16..20]); |
|---|
| 135 | | height = toUint(source[20..24]); |
|---|
| 136 | | bitDepth = source[24]; |
|---|
| 137 | | colorType = cast(ColorType)source[25]; |
|---|
| 138 | | mixin(pngEnforce( `source[26] == 0`, "unsupported compression method in png header" )); |
|---|
| 139 | | mixin(pngEnforce( `source[27] == 0`, "unsupported filter method in png header" )); |
|---|
| 140 | | mixin(pngEnforce( `checkColorValidity(colorType, bitDepth)`, "invalid header: wrong color format" )); |
|---|
| 141 | | interlaceMethod = source[28]; |
|---|
| 142 | | mixin(pngEnforce( `interlaceMethod < 2`, "invalid interlace method in png header" )); |
|---|
| 143 | | bpp = numChannels(colorType) * bitDepth; |
|---|
| 144 | | } |
|---|
| 145 | | interlace = interlaceMethod; |
|---|
| 146 | | return result; |
|---|
| | 200 | mixin(pngEnforce(`source.length >= HEADER_SIZE`, "png header data is too small")); |
|---|
| | 201 | mixin(pngEnforce(`source[0..8] == [cast(ubyte)137, 80, 78, 71, 13, 10, 26, 10]`, "invalid png header ")); |
|---|
| | 202 | mixin(pngEnforce((toUint(source[12..16]) == IHDR).stringof, "invalid png header")); |
|---|
| | 203 | mixin(pngEnforce(`checkCRC(source[29 .. 33], source[12 .. 29])`, "invalid CRC")); |
|---|
| | 204 | |
|---|
| | 205 | PngImage result; |
|---|
| | 206 | |
|---|
| | 207 | |
|---|
| | 208 | with (result) |
|---|
| | 209 | { |
|---|
| | 210 | width = toUint(source[16..20]); |
|---|
| | 211 | height = toUint(source[20..24]); |
|---|
| | 212 | bitDepth = source[24]; |
|---|
| | 213 | colorType = cast(ColorType)source[25]; |
|---|
| | 214 | mixin(pngEnforce( `source[26] == 0`, "unsupported compression method in png header" )); |
|---|
| | 215 | mixin(pngEnforce( `source[27] == 0`, "unsupported filter method in png header" )); |
|---|
| | 216 | mixin(pngEnforce( `checkColorValidity(colorType, bitDepth)`, "invalid header: wrong color format" )); |
|---|
| | 217 | interlaced = source[28]; |
|---|
| | 218 | mixin(pngEnforce( `interlaced < 2`, "invalid interlace method in png header" )); |
|---|
| | 219 | bpp = numChannels(colorType) * bitDepth; |
|---|
| | 220 | } |
|---|
| | 221 | |
|---|
| | 222 | return result; |
|---|
| 160 | | |
|---|
| 161 | | ubyte[] decode(in ubyte[] source, ref PngInfo info, ubyte[] buffer = null, int delegate(inout Chunk) dg = null) |
|---|
| 162 | | { |
|---|
| 163 | | info.image = readHeader(source, info.interlace); |
|---|
| 164 | | |
|---|
| 165 | | // holds interlaced filtered scanlines |
|---|
| 166 | | ubyte[] ilaceBuffer; |
|---|
| 167 | | |
|---|
| 168 | | // to allocate memory as needed |
|---|
| 169 | | if (info.interlace == 1) |
|---|
| 170 | | buffer.length = ((info.image.width * info.image.bpp + 7) / 8) * info.image.height + (info.image.height * 2); // guess |
|---|
| | 234 | void iterateChunks(/+const+/ in ubyte[] source, out PngImage image, int delegate(inout Chunk) dg) |
|---|
| | 235 | { |
|---|
| | 236 | image = source.readHeader(); |
|---|
| | 237 | |
|---|
| | 238 | foreach(chunk; StreamChunkIter(source[HEADER_SIZE - 1 .. $])) |
|---|
| | 239 | if (dg(chunk) != 0) |
|---|
| | 240 | return; |
|---|
| | 241 | } |
|---|
| | 242 | |
|---|
| | 243 | /*************************************************************************************************** |
|---|
| | 244 | decode IDAT data |
|---|
| | 245 | |
|---|
| | 246 | ***************************************************************************************************/ |
|---|
| | 247 | scope class PngDecoder |
|---|
| | 248 | { |
|---|
| | 249 | /** constructor */ |
|---|
| | 250 | this(PngImage image) |
|---|
| | 251 | { |
|---|
| | 252 | this(image, buf); |
|---|
| | 253 | } |
|---|
| | 254 | |
|---|
| | 255 | /** ditto */ |
|---|
| | 256 | this(PngImage image, ref ubyte[] buffer) |
|---|
| | 257 | { |
|---|
| | 258 | img = image; |
|---|
| | 259 | buf = buffer; |
|---|
| | 260 | if (img.interlaced == 1) |
|---|
| | 261 | buf.length = ((img.width * img.bpp + 7) / 8) * img.height + (img.height * 2); // guess |
|---|
| | 262 | else |
|---|
| | 263 | buf.length = ((img.width * img.bpp + 7) / 8) * img.height + img.height; |
|---|
| | 264 | if (img.interlaced == 1) |
|---|
| | 265 | ilaceBuffer.length = buf.length - img.height; |
|---|
| | 266 | decoder = DecodeStream.create(buf); |
|---|
| | 267 | } |
|---|
| | 268 | |
|---|
| | 269 | /*************************************************************************************************** |
|---|
| | 270 | inflate, call multiple times if there are more than 1 IDAT chunks to be decompressed |
|---|
| | 271 | |
|---|
| | 272 | Throws: ZlibException |
|---|
| | 273 | params: data of an IDAT chunk |
|---|
| | 274 | ***************************************************************************************************/ |
|---|
| | 275 | void opCall(ref ubyte[] data) |
|---|
| | 276 | { |
|---|
| | 277 | decoder(data); |
|---|
| | 278 | return true; |
|---|
| | 279 | } |
|---|
| | 280 | |
|---|
| | 281 | /*************************************************************************************************** |
|---|
| | 282 | Whether inflation has completed |
|---|
| | 283 | |
|---|
| | 284 | ***************************************************************************************************/ |
|---|
| | 285 | bool ended() |
|---|
| | 286 | { |
|---|
| | 287 | return decoder.hasEnded; |
|---|
| | 288 | } |
|---|
| | 289 | |
|---|
| | 290 | /*************************************************************************************************** |
|---|
| | 291 | Apply reconstruction filters and deinterlace if required |
|---|
| | 292 | |
|---|
| | 293 | note that ended() must return true before this function can be called if no data is provided |
|---|
| | 294 | |
|---|
| | 295 | params: |
|---|
| | 296 | filtered=optionally provide uncompressed filtered pixels yourself |
|---|
| | 297 | **************************************************************************************************/ |
|---|
| | 298 | ubyte[] reconstructImage(ubyte[] filtered = null) |
|---|
| | 299 | |
|---|
| | 300 | { |
|---|
| | 301 | buf = filtered is null ? filtered : decoder(); |
|---|
| | 302 | return (img.interlaced == 0) ? reconstruct(buf, img) : deinterlace(buf, ilaceBuffer, img); |
|---|
| | 303 | |
|---|
| | 304 | } |
|---|
| | 305 | |
|---|
| | 306 | private |
|---|
| | 307 | { |
|---|
| | 308 | DecodeStream decoder; |
|---|
| | 309 | PngImage img; |
|---|
| | 310 | |
|---|
| | 311 | ubyte[] buf; |
|---|
| | 312 | ubyte[] ilaceBuffer; |
|---|
| | 313 | } |
|---|
| | 314 | } |
|---|
| | 315 | |
|---|
| | 316 | |
|---|
| | 317 | /*************************************************************************************************** |
|---|
| | 318 | parse any known chunk except IDAT |
|---|
| | 319 | |
|---|
| | 320 | params: |
|---|
| | 321 | chunk=chunk to be parsed |
|---|
| | 322 | info=parsed information will be written to info |
|---|
| | 323 | returns: true if a chunk is parsed, false otherwise |
|---|
| | 324 | |
|---|
| | 325 | ***************************************************************************************************/ |
|---|
| | 326 | bool parseChunk(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| | 327 | { |
|---|
| | 328 | switch(chunk.type) |
|---|
| | 329 | { |
|---|
| | 330 | case PLTE: parsePLTE(chunk, info); |
|---|
| | 331 | break; |
|---|
| | 332 | case tRNS: parsetRNS(chunk, info); |
|---|
| | 333 | break; |
|---|
| | 334 | case bKGD: parsebKGD(chunk, info); |
|---|
| | 335 | break; |
|---|
| | 336 | case zTXt: parsezTXt(chunk, info); |
|---|
| | 337 | break; |
|---|
| | 338 | case tEXt: parsetTXt(chunk, info); |
|---|
| | 339 | break; |
|---|
| | 340 | case iTXt: parseiTXt(chunk, info); |
|---|
| | 341 | break; |
|---|
| | 342 | default: |
|---|
| | 343 | return false; |
|---|
| | 344 | } |
|---|
| | 345 | return true; |
|---|
| | 346 | } |
|---|
| | 347 | |
|---|
| | 348 | /** parse palette chunk */ |
|---|
| | 349 | void parsePLTE(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| | 350 | { |
|---|
| | 351 | mixin(pngEnforce(`chunk.data.length <= 256 * 3`, "palette size is too large")); |
|---|
| | 352 | info.palette.length = chunk.data.length / 3; |
|---|
| | 353 | foreach(index, ref ubyte[4] color; info.palette) |
|---|
| | 354 | { |
|---|
| | 355 | color[0..3] = chunk.data[index * 3 .. index * 3 + 3]; |
|---|
| | 356 | color[3] = 255; |
|---|
| | 357 | } |
|---|
| | 358 | } |
|---|
| | 359 | |
|---|
| | 360 | /** parse transparency chunk */ |
|---|
| | 361 | void parsetRNS(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| | 362 | { |
|---|
| | 363 | if (chunk.data.length == 0) |
|---|
| | 364 | return; |
|---|
| | 365 | info.colorKey = true; |
|---|
| | 366 | if (info.image.colorType == ColorType.Palette) // index-values |
|---|
| | 367 | { |
|---|
| | 368 | mixin(pngEnforce(`chunk.data.length <= info.palette.length`, "palette size is too large")); |
|---|
| | 369 | foreach(index, alpha; chunk.data) |
|---|
| | 370 | info.palette[index][3] = alpha; |
|---|
| | 371 | } |
|---|
| | 372 | else if (info.image.colorType == ColorType.RGB) |
|---|
| | 373 | { |
|---|
| | 374 | info.keyR = 256U * chunk.data[0] + chunk.data[1]; |
|---|
| | 375 | info.keyG = 256U * chunk.data[2] + chunk.data[3]; |
|---|
| | 376 | info.keyB = 256U * chunk.data[4] + chunk.data[5]; |
|---|
| | 377 | } |
|---|
| | 378 | else if (info.image.colorType == ColorType.Greyscale) |
|---|
| | 379 | { |
|---|
| | 380 | info.keyR = 256U * chunk.data[0] + chunk.data[1]; |
|---|
| | 381 | } |
|---|
| 172 | | buffer.length = ((info.image.width * info.image.bpp + 7) / 8) * info.image.height + info.image.height; |
|---|
| 173 | | |
|---|
| 174 | | |
|---|
| 175 | | if (info.interlace == 1) |
|---|
| 176 | | ilaceBuffer.length = buffer.length - info.image.height; |
|---|
| 177 | | |
|---|
| 178 | | auto inflator = DecodeStream.create(buffer); |
|---|
| 179 | | |
|---|
| 180 | | foreach(chunk; StreamChunkIter(source[HEADER_SIZE - 1 .. $])) |
|---|
| 181 | | { |
|---|
| 182 | | switch(chunk.type) |
|---|
| | 383 | { |
|---|
| | 384 | assert(false); |
|---|
| | 385 | } |
|---|
| | 386 | } |
|---|
| | 387 | |
|---|
| | 388 | /** parse background color chunk */ |
|---|
| | 389 | void parsebKGD(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| | 390 | { |
|---|
| | 391 | if (info.image.colorType == ColorType.Palette || info.image.bitDepth == 16) |
|---|
| | 392 | info.backgroundColor = chunk.data.dup; |
|---|
| | 393 | else |
|---|
| | 394 | { |
|---|
| | 395 | info.backgroundColor.length = chunk.data.length / 2; |
|---|
| | 396 | foreach(index, ref value; info.backgroundColor) |
|---|
| | 397 | value = chunk.data[index * 2]; |
|---|
| | 398 | } |
|---|
| | 399 | } |
|---|
| | 400 | |
|---|
| | 401 | /** parse latin1 text chunk */ |
|---|
| | 402 | void parsetTXt(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| | 403 | { |
|---|
| | 404 | if (info.parseText) |
|---|
| | 405 | { |
|---|
| | 406 | auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| | 407 | if (sep > 0) |
|---|
| | 408 | info.latin1Text[chunk.data[0..sep]] = chunk.data[sep + 1 .. $]; |
|---|
| | 409 | } |
|---|
| | 410 | } |
|---|
| | 411 | |
|---|
| | 412 | /** parse latin1 compressed text chunk */ |
|---|
| | 413 | void parsezTXt(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| | 414 | { |
|---|
| | 415 | if (info.parseText) |
|---|
| | 416 | { |
|---|
| | 417 | auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| | 418 | if (sep > 0) |
|---|
| | 419 | { |
|---|
| | 420 | if (chunk.data[sep + 1] == 0) |
|---|
| | 421 | info.latin1Text[chunk.data[0..sep]] = chunk.data[sep + 2 .. $]; |
|---|
| | 422 | else |
|---|
| | 423 | { |
|---|
| | 424 | ubyte[] value; |
|---|
| | 425 | auto decoder = DecodeStream.create(value); |
|---|
| | 426 | decoder(chunk.data[sep + 2 .. $]); |
|---|
| | 427 | info.latin1Text[chunk.data[0..sep]] = value; |
|---|
| | 428 | } |
|---|
| | 429 | } |
|---|
| | 430 | } |
|---|
| | 431 | } |
|---|
| | 432 | |
|---|
| | 433 | /** parse unicode text chunk */ |
|---|
| | 434 | void parseiTXt(/+const+/ ref Chunk chunk, ref PngInfo info) |
|---|
| | 435 | { |
|---|
| | 436 | if (info.parseText) |
|---|
| | 437 | { |
|---|
| | 438 | |
|---|
| | 439 | auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| | 440 | char[] keyword = cast(char[])chunk.data[0..sep]; |
|---|
| | 441 | bool compressed = chunk.data[sep + 1] == 0 ? false : true; |
|---|
| | 442 | sep += strFind(cast(char[])chunk.data[sep + 3 .. $], 0) + 3; |
|---|
| | 443 | sep += strFind(cast(char[])chunk.data[sep + 1 .. $], 0) + 1; |
|---|
| | 444 | if (!compressed) |
|---|
| | 445 | info.unicodeText[keyword] = cast(char[])chunk.data[sep + 1..$]; |
|---|
| | 446 | else |
|---|
| 184 | | case IDAT: |
|---|
| 185 | | inflator(chunk.data); |
|---|
| 186 | | break; |
|---|
| 187 | | case PLTE: |
|---|
| 188 | | mixin(pngEnforce(`chunk.data.length <= 256 * 3`, "palette size is too large")); |
|---|
| 189 | | info.palette.length = chunk.data.length / 3; |
|---|
| 190 | | foreach(index, ref ubyte[4] color; info.palette) |
|---|
| 191 | | { |
|---|
| 192 | | color[0..3] = chunk.data[index * 3 .. index * 3 + 3]; |
|---|
| 193 | | color[3] = 255; |
|---|
| 194 | | } |
|---|
| 195 | | |
|---|
| 196 | | break; |
|---|
| 197 | | case tRNS: |
|---|
| 198 | | if (chunk.data.length == 0) |
|---|
| 199 | | break; |
|---|
| 200 | | info.colorKey = true; |
|---|
| 201 | | if (info.image.colorType == ColorType.Palette) // index-values |
|---|
| 202 | | { |
|---|
| 203 | | mixin(pngEnforce(`chunk.data.length <= info.palette.length`, "palette size is too large")); |
|---|
| 204 | | foreach(index, alpha; chunk.data) |
|---|
| 205 | | info.palette[index][3] = alpha; |
|---|
| 206 | | } |
|---|
| 207 | | else if (info.image.colorType == ColorType.RGB) |
|---|
| 208 | | { |
|---|
| 209 | | info.keyR = 256U * chunk.data[0] + chunk.data[1]; |
|---|
| 210 | | info.keyG = 256U * chunk.data[2] + chunk.data[3]; |
|---|
| 211 | | info.keyB = 256U * chunk.data[4] + chunk.data[5]; |
|---|
| 212 | | } |
|---|
| 213 | | else if (info.image.colorType == ColorType.Greyscale) |
|---|
| 214 | | { |
|---|
| 215 | | info.keyR = 256U * chunk.data[0] + chunk.data[1]; |
|---|
| 216 | | } |
|---|
| 217 | | else |
|---|
| 218 | | assert(false); |
|---|
| 219 | | break; |
|---|
| 220 | | case bKGD: |
|---|
| 221 | | if (info.image.colorType == ColorType.Palette || info.image.bitDepth == 16) |
|---|
| 222 | | info.backgroundColor = chunk.data.dup; |
|---|
| 223 | | else |
|---|
| 224 | | { |
|---|
| 225 | | info.backgroundColor.length = chunk.data.length / 2; |
|---|
| 226 | | foreach(index, ref value; info.backgroundColor) |
|---|
| 227 | | value = chunk.data[index * 2]; |
|---|
| 228 | | } |
|---|
| 229 | | break; |
|---|
| 230 | | case zTXt: |
|---|
| 231 | | if (info.parseText) |
|---|
| 232 | | { |
|---|
| 233 | | if (info.textual is null) |
|---|
| 234 | | info.textual = new PngText; |
|---|
| 235 | | auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| 236 | | if (sep > 0) |
|---|
| 237 | | { |
|---|
| 238 | | if (chunk.data[sep + 1] == 0) |
|---|
| 239 | | info.textual[chunk.data[0..sep]] = chunk.data[sep + 2 .. $]; |
|---|
| 240 | | else |
|---|
| 241 | | { |
|---|
| 242 | | ubyte[] value; |
|---|
| 243 | | auto decoder = DecodeStream.create(value); |
|---|
| 244 | | decoder(chunk.data[sep + 2 .. $]); |
|---|
| 245 | | info.textual[chunk.data[0..sep]] = value; |
|---|
| 246 | | } |
|---|
| 247 | | } |
|---|
| 248 | | } |
|---|
| 249 | | break; |
|---|
| 250 | | case tEXt: |
|---|
| 251 | | if (info.parseText) |
|---|
| 252 | | { |
|---|
| 253 | | if (info.textual is null) |
|---|
| 254 | | info.textual = new PngText; |
|---|
| 255 | | auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| 256 | | if (sep > 0) |
|---|
| 257 | | { |
|---|
| 258 | | info.textual[chunk.data[0..sep]] = chunk.data[sep + 1 .. $]; |
|---|
| 259 | | } |
|---|
| 260 | | } |
|---|
| 261 | | break; |
|---|
| 262 | | case iTXt: |
|---|
| 263 | | if (info.parseText) |
|---|
| 264 | | { |
|---|
| 265 | | if (info.textual is null) |
|---|
| 266 | | info.textual = new PngText; |
|---|
| 267 | | auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| 268 | | char[] keyword = cast(char[])chunk.data[0..sep]; |
|---|
| 269 | | bool compressed = chunk.data[sep + 1] == 0 ? false : true; |
|---|
| 270 | | sep += strFind(cast(char[])chunk.data[sep + 3 .. $], 0) + 3; |
|---|
| 271 | | sep += strFind(cast(char[])chunk.data[sep + 1 .. $], 0) + 1; |
|---|
| 272 | | if (!compressed) |
|---|
| 273 | | info.textual[keyword] = cast(char[])chunk.data[sep + 1..$]; |
|---|
| 274 | | else |
|---|
| 275 | | { |
|---|
| 276 | | ubyte[] value; |
|---|
| 277 | | auto decoder = DecodeStream.create(value); |
|---|
| 278 | | decoder(chunk.data[sep + 1..$]); |
|---|
| 279 | | info.textual[keyword] = cast(char[])value; |
|---|
| 280 | | } |
|---|
| 281 | | } |
|---|
| 282 | | break; |
|---|
| 283 | | default: |
|---|
| 284 | | if (dg !is null) |
|---|
| 285 | | if (!dg(chunk)) |
|---|
| 286 | | return null; |
|---|
| 287 | | //mixin(pngEnforce(`(bt(&chunk.type, 6) < 0)`, "unrecognized critical chunk")); |
|---|
| 288 | | break; |
|---|
| | 448 | ubyte[] value; |
|---|
| | 449 | auto decoder = DecodeStream.create(value); |
|---|
| | 450 | decoder(chunk.data[sep + 1..$]); |
|---|
| | 451 | info.unicodeText[keyword] = cast(char[])value; |
|---|
| 291 | | assert(inflator.hasEnded); |
|---|
| 292 | | buffer = inflator(); |
|---|
| 293 | | |
|---|
| 294 | | return (info.interlace == 0) ? reconstruct(buffer, info.image) |
|---|
| 295 | | : deinterlace(buffer, ilaceBuffer, info.image); |
|---|
| 296 | | |
|---|
| 297 | | } |
|---|
| 298 | | /+ |
|---|
| 299 | | ubyte[] decode(in ubyte[] source, ref PngInfo info, ubyte[] buffer = null) |
|---|
| 300 | | { |
|---|
| 301 | | info.image = readHeader(source, info.interlace); |
|---|
| 302 | | |
|---|
| 303 | | // holds interlaced filtered scanlines |
|---|
| 304 | | ubyte[] ilaceBuffer; |
|---|
| 305 | | |
|---|
| 306 | | // to allocate memory as needed |
|---|
| 307 | | if (info.interlace == 1) |
|---|
| 308 | | buffer.length = ((info.image.width * info.image.bpp + 7) / 8) * info.image.height + (info.image.height * 2); // guess |
|---|
| 309 | | else |
|---|
| 310 | | buffer.length = ((info.image.width * info.image.bpp + 7) / 8) * info.image.height + info.image.height; |
|---|
| 311 | | |
|---|
| 312 | | |
|---|
| 313 | | if (info.interlace == 1) |
|---|
| 314 | | ilaceBuffer.length = buffer.length - info.image.height; |
|---|
| 315 | | |
|---|
| 316 | | auto inflator = DecodeStream.create(buffer); |
|---|
| 317 | | |
|---|
| 318 | | foreach(chunk; StreamChunkIter(source[HEADER_SIZE - 1 .. $])) |
|---|
| 319 | | { |
|---|
| 320 | | switch(chunk.type) |
|---|
| 321 | | { |
|---|
| 322 | | case IDAT: |
|---|
| 323 | | inflator(chunk.data); |
|---|
| 324 | | break; |
|---|
| 325 | | case PLTE: |
|---|
| 326 | | mixin(pngEnforce(`chunk.data.length <= 256 * 3`, "palette size is too large")); |
|---|
| 327 | | info.palette.length = chunk.data.length / 3; |
|---|
| 328 | | foreach(index, ref ubyte[4] color; info.palette) |
|---|
| 329 | | { |
|---|
| 330 | | color[0..3] = chunk.data[index * 3 .. index * 3 + 3]; |
|---|
| 331 | | color[3] = 255; |
|---|
| 332 | | } |
|---|
| 333 | | |
|---|
| 334 | | break; |
|---|
| 335 | | case tRNS: |
|---|
| 336 | | if (chunk.data.length == 0) |
|---|
| 337 | | break; |
|---|
| 338 | | info.colorKey = true; |
|---|
| 339 | | if (info.image.colorType == ColorType.Palette) // index-values |
|---|
| 340 | | { |
|---|
| 341 | | mixin(pngEnforce(`chunk.data.length <= info.palette.length`, "palette size is too large")); |
|---|
| 342 | | foreach(index, alpha; chunk.data) |
|---|
| 343 | | info.palette[index][3] = alpha; |
|---|
| 344 | | } |
|---|
| 345 | | else if (info.image.colorType == ColorType.RGB) |
|---|
| 346 | | { |
|---|
| 347 | | info.keyR = 256U * chunk.data[0] + chunk.data[1]; |
|---|
| 348 | | info.keyG = 256U * chunk.data[2] + chunk.data[3]; |
|---|
| 349 | | info.keyB = 256U * chunk.data[4] + chunk.data[5]; |
|---|
| 350 | | } |
|---|
| 351 | | else if (info.image.colorType == ColorType.Greyscale) |
|---|
| 352 | | { |
|---|
| 353 | | info.keyR = 256U * chunk.data[0] + chunk.data[1]; |
|---|
| 354 | | } |
|---|
| 355 | | else |
|---|
| 356 | | assert(false); |
|---|
| 357 | | break; |
|---|
| 358 | | case bKGD: |
|---|
| 359 | | if (info.image.colorType == ColorType.Palette || info.image.bitDepth == 16) |
|---|
| 360 | | info.backgroundColor = chunk.data.dup; |
|---|
| 361 | | else |
|---|
| 362 | | { |
|---|
| 363 | | info.backgroundColor.length = chunk.data.length / 2; |
|---|
| 364 | | foreach(index, ref value; info.backgroundColor) |
|---|
| 365 | | value = chunk.data[index * 2]; |
|---|
| 366 | | } |
|---|
| 367 | | break; |
|---|
| 368 | | case zTXt: |
|---|
| 369 | | if (info.parseText) |
|---|
| 370 | | { |
|---|
| 371 | | if (info.textual is null) |
|---|
| 372 | | info.textual = new PngText; |
|---|
| 373 | | auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| 374 | | if (sep > 0) |
|---|
| 375 | | { |
|---|
| 376 | | if (chunk.data[sep + 1] == 0) |
|---|
| 377 | | info.textual[chunk.data[0..sep]] = chunk.data[sep + 2 .. $]; |
|---|
| 378 | | else |
|---|
| 379 | | { |
|---|
| 380 | | ubyte[] value; |
|---|
| 381 | | auto decoder = DecodeStream.create(value); |
|---|
| 382 | | decoder(chunk.data[sep + 2 .. $]); |
|---|
| 383 | | info.textual[chunk.data[0..sep]] = value; |
|---|
| 384 | | } |
|---|
| 385 | | } |
|---|
| 386 | | } |
|---|
| 387 | | break; |
|---|
| 388 | | case tEXt: |
|---|
| 389 | | if (info.parseText) |
|---|
| 390 | | { |
|---|
| 391 | | if (info.textual is null) |
|---|
| 392 | | info.textual = new PngText; |
|---|
| 393 | | auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| 394 | | if (sep > 0) |
|---|
| 395 | | { |
|---|
| 396 | | info.textual[chunk.data[0..sep]] = chunk.data[sep + 1 .. $]; |
|---|
| 397 | | } |
|---|
| 398 | | } |
|---|
| 399 | | break; |
|---|
| 400 | | case iTXt: |
|---|
| 401 | | if (info.parseText) |
|---|
| 402 | | { |
|---|
| 403 | | if (info.textual is null) |
|---|
| 404 | | info.textual = new PngText; |
|---|
| 405 | | auto sep = strFind(cast(char[])chunk.data, 0); |
|---|
| 406 | | char[] keyword = cast(char[])chunk.data[0..sep]; |
|---|
| 407 | | bool compressed = chunk.data[sep + 1] == 0 ? false : true; |
|---|
| 408 | | sep += strFind(cast(char[])chunk.data[sep + 3 .. $], 0) + 3; |
|---|
| 409 | | sep += strFind(cast(char[])chunk.data[sep + 1 .. $], 0) + 1; |
|---|
| 410 | | if (!compressed) |
|---|
| 411 | | info.textual[keyword] = cast(char[])chunk.data[sep + 1..$]; |
|---|
| 412 | | else |
|---|
| 413 | | { |
|---|
| 414 | | ubyte[] value; |
|---|
| 415 | | auto decoder = DecodeStream.create(value); |
|---|
| 416 | | decoder(chunk.data[sep + 1..$]); |
|---|
| 417 | | info.textual[keyword] = cast(char[])value; |
|---|
| 418 | | } |
|---|
| 419 | | } |
|---|
| 420 | | break; |
|---|
| 421 | | default: |
|---|
| 422 | | mixin(pngEnforce(`(bt(&chunk.type, 6) < 0)`, "unrecognized critical chunk")); |
|---|
| 423 | | break; |
|---|
| 424 | | } |
|---|
| 425 | | } |
|---|
| 426 | | assert(inflator.hasEnded); |
|---|
| 427 | | buffer = inflator(); |
|---|
| 428 | | |
|---|
| 429 | | return (info.interlace == 0) ? reconstruct(buffer, info.image) |
|---|
| 430 | | : deinterlace(buffer, ilaceBuffer, info.image); |
|---|
| 431 | | } |
|---|
| 432 | | +/ |
|---|
| 433 | | /*************************************************************************************************** |
|---|
| 434 | | Decode source png file to RGBA format |
|---|
| 435 | | |
|---|
| 436 | | Throws: PngException |
|---|
| 437 | | Returns: decoded image pixels in 32-bit RGBA format |
|---|
| 438 | | ***************************************************************************************************/ |
|---|
| 439 | | ubyte[] decode32(in ubyte[] source, ref PngInfo info, ubyte[] buffer = null) |
|---|
| 440 | | { |
|---|
| 441 | | buffer = decode(source, info, buffer); |
|---|
| 442 | | buffer = convert(buffer, info, ColorType.RGBA); |
|---|
| 443 | | info.image.colorType = ColorType.RGBA; |
|---|
| 444 | | info.image.bpp = 32; |
|---|
| 445 | | info.image.bitDepth = 8; |
|---|
| 446 | | info.palette.length = 0; |
|---|
| 447 | | return buffer; |
|---|
| 448 | | } |
|---|
| | 454 | } |
|---|
| | 455 | |
|---|
| | 456 | |
|---|