1 /*
2 Copyright (c) 2011-2015 Timur Gafarov, Martin Cejp
3
4 Boost Software License - Version 1.0 - August 17th, 2003
5
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28
29 // dimage is actually stripped out part of dlib - just to support reading PNG and JPEG
30 module dimage.png; //dlib.image.io.png
31
32 private
33 {
34 import std.stdio;
35 import std.math;
36 import std.string;
37 import std.range;
38
39 import dimage.memory;
40 import dimage.stream;
41 import dimage.compound;
42 //import dlib.filesystem.local;
43 //import dlib.math.utils;
44 import dimage.zlib;
45 import dimage.image;
46 //import dlib.image.io.io;
47
48 //import dlib.core.memory;
49 //import dlib.core.stream;
50 //import dlib.core.compound;
51 //import dlib.filesystem.local;
52 //import dlib.math.utils;
53 //import dlib.coding.zlib;
54 //import dlib.image.image;
55 //import dlib.image.io.io;
56 }
57
58 // uncomment this to see debug messages:
59 //version = PNGDebug;
60
61 static const ubyte[8] PNGSignature = [137, 80, 78, 71, 13, 10, 26, 10];
62 static const ubyte[4] IHDR = ['I', 'H', 'D', 'R'];
63 static const ubyte[4] IEND = ['I', 'E', 'N', 'D'];
64 static const ubyte[4] IDAT = ['I', 'D', 'A', 'T'];
65 static const ubyte[4] PLTE = ['P', 'L', 'T', 'E'];
66 static const ubyte[4] tRNS = ['t', 'R', 'N', 'S'];
67 static const ubyte[4] bKGD = ['b', 'K', 'G', 'D'];
68 static const ubyte[4] tEXt = ['t', 'E', 'X', 't'];
69 static const ubyte[4] iTXt = ['i', 'T', 'X', 't'];
70 static const ubyte[4] zTXt = ['z', 'T', 'X', 't'];
71
72 enum ColorType: ubyte
73 {
74 Greyscale = 0, // allowed bit depths: 1, 2, 4, 8 and 16
75 RGB = 2, // allowed bit depths: 8 and 16
76 Palette = 3, // allowed bit depths: 1, 2, 4 and 8
77 GreyscaleAlpha = 4, // allowed bit depths: 8 and 16
78 RGBA = 6, // allowed bit depths: 8 and 16
79 Any = 7 // one of the above
80 }
81
82 enum FilterMethod: ubyte
83 {
84 None = 0,
85 Sub = 1,
86 Up = 2,
87 Average = 3,
88 Paeth = 4
89 }
90
91 struct PNGChunk
92 {
93 uint length;
94 ubyte[4] type;
95 ubyte[] data;
96 uint crc;
97
98 void free()
99 {
100 if (data.ptr)
101 Delete(data);
102 }
103 }
104
105 struct PNGHeader
106 {
107 union
108 {
109 struct
110 {
111 uint width;
112 uint height;
113 ubyte bitDepth;
114 ubyte colorType;
115 ubyte compressionMethod;
116 ubyte filterMethod;
117 ubyte interlaceMethod;
118 };
119 ubyte[13] bytes;
120 }
121 }
122 class PNGLoadException: ImageLoadException
123 {
124 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
125 {
126 super(msg, file, line, next);
127 }
128 }
129
130 /*
131 * Load PNG from file using local FileSystem.
132 * Causes GC allocation
133 */
134 //SuperImage loadPNG(string filename)
135 //{
136 // InputStream input = openForInput(filename);
137 // auto img = loadPNG(input);
138 // input.close();
139 // return img;
140 //}
141
142 /*
143 * Save PNG to file using local FileSystem.
144 * Causes GC allocation
145 */
146 //void savePNG(SuperImage img, string filename)
147 //{
148 // OutputStream output = openForOutput(filename);
149 // Compound!(bool, string) res =
150 // savePNG(img, output);
151 // output.close();
152 //
153 // if (!res[0])
154 // throw new PNGLoadException(res[1]);
155 //}
156
157 /*
158 * Load PNG from stream using default image factory.
159 * Causes GC allocation
160 */
161 SuperImage loadPNG(InputStream istrm)
162 {
163 Compound!(SuperImage, string) res =
164 loadPNG(istrm, defaultImageFactory);
165 if (res[0] is null)
166 throw new PNGLoadException(res[1]);
167 else
168 return res[0];
169 }
170
171 /*
172 * Load PNG from stream using specified image factory.
173 * GC-free
174 */
175 Compound!(SuperImage, string) loadPNG(
176 InputStream istrm,
177 SuperImageFactory imgFac)
178 {
179 SuperImage img = null;
180
181 Compound!(SuperImage, string) error(string errorMsg)
182 {
183 if (img)
184 {
185 img.free();
186 img = null;
187 }
188 return compound(img, errorMsg);
189 }
190
191 bool readChunk(PNGChunk* chunk)
192 {
193 if (!istrm.readBE!uint(&chunk.length)
194 || !istrm.fillArray(chunk.type))
195 {
196 return false;
197 }
198
199 version(PNGDebug) writefln("Chunk length = %s", chunk.length);
200 version(PNGDebug) writefln("Chunk type = %s", cast(char[])chunk.type);
201
202 if (chunk.length > 0)
203 {
204 chunk.data = New!(ubyte[])(chunk.length);
205
206 if (!istrm.fillArray(chunk.data))
207 {
208 return false;
209 }
210 }
211
212 version(PNGDebug) writefln("Chunk data.length = %s", chunk.data.length);
213
214 if (!istrm.readBE!uint(&chunk.crc))
215 {
216 return false;
217 }
218
219 // TODO: reimplement CRC check with ranges instead of concatenation
220 uint calculatedCRC = crc32(chain(chunk.type[0..$], chunk.data));
221
222 version(PNGDebug)
223 {
224 writefln("Chunk CRC = %X", chunk.crc);
225 writefln("Calculated CRC = %X", calculatedCRC);
226 writeln("-------------------");
227 }
228
229 if (chunk.crc != calculatedCRC)
230 {
231 return false;
232 }
233
234 return true;
235 }
236
237 bool readHeader(PNGHeader* hdr, PNGChunk* chunk)
238 {
239 hdr.bytes[] = chunk.data[];
240 hdr.width = bigEndian(hdr.width);
241 hdr.height = bigEndian(hdr.height);
242
243 version(PNGDebug)
244 {
245 writefln("width = %s", hdr.width);
246 writefln("height = %s", hdr.height);
247 writefln("bitDepth = %s", hdr.bitDepth);
248 writefln("colorType = %s", hdr.colorType);
249 writefln("compressionMethod = %s", hdr.compressionMethod);
250 writefln("filterMethod = %s", hdr.filterMethod);
251 writefln("interlaceMethod = %s", hdr.interlaceMethod);
252 writeln("----------------");
253 }
254
255 return true;
256 }
257
258 ubyte[8] signatureBuffer;
259
260 if (!istrm.fillArray(signatureBuffer))
261 {
262 return error("loadPNG error: signature check failed");
263 }
264
265 version(PNGDebug)
266 {
267 writeln("----------------");
268 writeln("PNG Signature: ", signatureBuffer);
269 writeln("----------------");
270 }
271
272 PNGHeader hdr;
273
274 ZlibDecoder zlibDecoder;
275
276 ubyte[] palette;
277 ubyte[] transparency;
278 uint paletteSize = 0;
279
280 bool endChunk = false;
281 while (!endChunk && istrm.readable)
282 {
283 PNGChunk chunk;
284 bool res = readChunk(&chunk);
285 if (!res)
286 {
287 chunk.free();
288 return error("loadPNG error: failed to read chunk");
289 }
290 else
291 {
292 if (chunk.type == IEND)
293 {
294 endChunk = true;
295 chunk.free();
296 }
297 else if (chunk.type == IHDR)
298 {
299 if (chunk.data.length < hdr.bytes.length)
300 return error("loadPNG error: illegal header chunk");
301
302 readHeader(&hdr, &chunk);
303 chunk.free();
304
305 bool supportedIndexed =
306 (hdr.colorType == ColorType.Palette) &&
307 (hdr.bitDepth == 1 ||
308 hdr.bitDepth == 2 ||
309 hdr.bitDepth == 4 ||
310 hdr.bitDepth == 8);
311
312 if (hdr.bitDepth != 8 && hdr.bitDepth != 16 && !supportedIndexed)
313 return error("loadPNG error: unsupported bit depth");
314
315 if (hdr.compressionMethod != 0)
316 return error("loadPNG error: unsupported compression method");
317
318 if (hdr.filterMethod != 0)
319 return error("loadPNG error: unsupported filter method");
320
321 if (hdr.interlaceMethod != 0)
322 return error("loadPNG error: interlacing is not supported");
323
324 uint bufferLength = ((hdr.width * hdr.bitDepth + 7) / 8) * hdr.height + hdr.height;
325 ubyte[] buffer = New!(ubyte[])(bufferLength);
326
327 zlibDecoder = ZlibDecoder(buffer);
328
329 version(PNGDebug)
330 {
331 writefln("buffer.length = %s", bufferLength);
332 writeln("----------------");
333 }
334 }
335 else if (chunk.type == IDAT)
336 {
337 zlibDecoder.decode(chunk.data);
338 chunk.free();
339 }
340 else if (chunk.type == PLTE)
341 {
342 palette = chunk.data;
343 }
344 else if (chunk.type == tRNS)
345 {
346 transparency = chunk.data;
347 version(PNGDebug)
348 {
349 writeln("----------------");
350 writefln("transparency.length = %s", transparency.length);
351 writeln("----------------");
352 }
353 }
354 else
355 {
356 chunk.free();
357 }
358 }
359 }
360
361 // finalize decoder
362 version(PNGDebug) writefln("zlibDecoder.hasEnded = %s", zlibDecoder.hasEnded);
363 if (!zlibDecoder.hasEnded)
364 return error("loadPNG error: unexpected end of zlib stream");
365
366 ubyte[] buffer = zlibDecoder.buffer;
367 version(PNGDebug) writefln("buffer.length = %s", buffer.length);
368
369 bool transparencyPalette;
370
371 // create image
372 if (hdr.colorType == ColorType.Greyscale)
373 {
374 if (hdr.bitDepth == 8)
375 img = imgFac.createImage(hdr.width, hdr.height, 1, 8);
376 else if (hdr.bitDepth == 16)
377 img = imgFac.createImage(hdr.width, hdr.height, 1, 16);
378 }
379 else if (hdr.colorType == ColorType.GreyscaleAlpha)
380 {
381 if (hdr.bitDepth == 8)
382 img = imgFac.createImage(hdr.width, hdr.height, 2, 8);
383 else if (hdr.bitDepth == 16)
384 img = imgFac.createImage(hdr.width, hdr.height, 2, 16);
385 }
386 else if (hdr.colorType == ColorType.RGB)
387 {
388 if (hdr.bitDepth == 8)
389 img = imgFac.createImage(hdr.width, hdr.height, 3, 8);
390 else if (hdr.bitDepth == 16)
391 img = imgFac.createImage(hdr.width, hdr.height, 3, 16);
392 }
393 else if (hdr.colorType == ColorType.RGBA)
394 {
395 if (hdr.bitDepth == 8)
396 img = imgFac.createImage(hdr.width, hdr.height, 4, 8);
397 else if (hdr.bitDepth == 16)
398 img = imgFac.createImage(hdr.width, hdr.height, 4, 16);
399 }
400 else if (hdr.colorType == ColorType.Palette)
401 {
402 if (transparency.length > 0) {
403 img = imgFac.createImage(hdr.width, hdr.height, 4, 8);
404 transparencyPalette = true;
405 } else
406 img = imgFac.createImage(hdr.width, hdr.height, 3, 8);
407 }
408 else
409 return error("loadPNG error: unsupported color type");
410
411 version(PNGDebug)
412 {
413 writefln("img.width = %s", img.width);
414 writefln("img.height = %s", img.height);
415 writefln("img.bitDepth = %s", img.bitDepth);
416 writefln("img.channels = %s", img.channels);
417 writeln("----------------");
418 }
419
420 bool indexed = (hdr.colorType == ColorType.Palette);
421
422 // don't close the stream, just release our reference
423 istrm = null;
424
425 // apply filtering to the image data
426 ubyte[] buffer2;
427 string errorMsg;
428 if (!filter(&hdr, img.channels, indexed, buffer, buffer2, errorMsg))
429 {
430 return error(errorMsg);
431 }
432 Delete(buffer);
433 buffer = buffer2;
434
435 // if a palette is used, substitute target colors
436 if (indexed)
437 {
438 if (palette.length == 0)
439 return error("loadPNG error: palette chunk not found");
440
441 ubyte[] pdata = New!(ubyte[])(img.width * img.height * img.channels);
442 if (hdr.bitDepth == 8)
443 {
444 for (int i = 0; i < buffer.length; ++i)
445 {
446 ubyte b = buffer[i];
447 pdata[i * img.channels + 0] = palette[b * 3 + 0];
448 pdata[i * img.channels + 1] = palette[b * 3 + 1];
449 pdata[i * img.channels + 2] = palette[b * 3 + 2];
450 if (transparency.length > 0)
451 pdata[i * img.channels + 3] =
452 b < transparency.length ? transparency[b] : 0;
453 }
454 }
455 else // bit depths 1, 2, 4
456 {
457 int srcindex = 0;
458 int srcshift = 8 - hdr.bitDepth;
459 ubyte mask = cast(ubyte)((1 << hdr.bitDepth) - 1);
460 int sz = img.width * img.height;
461 for (int dstindex = 0; dstindex < sz; dstindex++)
462 {
463 auto b = ((buffer[srcindex] >> srcshift) & mask);
464 //assert(b * 3 + 2 < palette.length);
465 pdata[dstindex * img.channels + 0] = palette[b * 3 + 0];
466 pdata[dstindex * img.channels + 1] = palette[b * 3 + 1];
467 pdata[dstindex * img.channels + 2] = palette[b * 3 + 2];
468
469 if (transparency.length > 0)
470 pdata[dstindex * img.channels + 3] =
471 b < transparency.length ? transparency[b] : 0;
472
473 if (srcshift <= 0)
474 {
475 srcshift = 8 - hdr.bitDepth;
476 srcindex++;
477 }
478 else
479 {
480 srcshift -= hdr.bitDepth;
481 }
482 }
483 }
484
485 Delete(buffer);
486 buffer = pdata;
487
488 Delete(palette);
489
490 if (transparency.length > 0)
491 Delete(transparency);
492 }
493
494 //if (img.data.length != buffer.length)
495 // return error("loadPNG error: uncompressed data length mismatch");
496 //
497 //foreach(i, v; buffer)
498 // img.data[i] = v;
499
500 int bufindex = 0;
501 if (hdr.colorType == ColorType.Greyscale)
502 {
503 if (hdr.bitDepth == 8) {
504 //img = imgFac.createImage(hdr.width, hdr.height, 1, 8);
505 for (int i = 0; i < img.length; i++) {
506 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex])<<8) | ((cast(uint)buffer[bufindex])<<0) | 0xFF000000;
507 bufindex += 1;
508 }
509 } else if (hdr.bitDepth == 16) {
510 //img = imgFac.createImage(hdr.width, hdr.height, 1, 16);
511 assert(false);
512 }
513 }
514 else if (hdr.colorType == ColorType.GreyscaleAlpha)
515 {
516 if (hdr.bitDepth == 8) {
517 //img = imgFac.createImage(hdr.width, hdr.height, 2, 8);
518 for (int i = 0; i < img.length; i++) {
519 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex])<<8) | ((cast(uint)buffer[bufindex])<<0) | ((cast(uint)buffer[bufindex + 1])<<24);
520 bufindex += 2;
521 }
522 } else if (hdr.bitDepth == 16) {
523 //img = imgFac.createImage(hdr.width, hdr.height, 2, 16);
524 assert(false);
525 }
526 }
527 else if (hdr.colorType == ColorType.RGB)
528 {
529 if (hdr.bitDepth == 8) {
530 //img = imgFac.createImage(hdr.width, hdr.height, 3, 8);
531 for (int i = 0; i < img.length; i++) {
532 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex + 1])<<8) | ((cast(uint)buffer[bufindex + 2])<<0) | 0xFF000000;
533 bufindex += 3;
534 }
535 } else if (hdr.bitDepth == 16) {
536 //img = imgFac.createImage(hdr.width, hdr.height, 3, 16);
537 assert(false);
538 }
539 }
540 else if (hdr.colorType == ColorType.RGBA)
541 {
542 if (hdr.bitDepth == 8) {
543 //img = imgFac.createImage(hdr.width, hdr.height, 4, 8);
544 for (int i = 0; i < img.length; i++) {
545 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex + 1])<<8) | ((cast(uint)buffer[bufindex + 2])<<0) | ((cast(uint)buffer[bufindex + 3])<<24);
546 bufindex += 4;
547 }
548 } else if (hdr.bitDepth == 16) {
549 //img = imgFac.createImage(hdr.width, hdr.height, 4, 16);
550 assert(false);
551 }
552 }
553 else if (hdr.colorType == ColorType.Palette)
554 {
555 if (transparencyPalette) {
556 for (int i = 0; i < img.length; i++) {
557 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex + 1])<<8) | ((cast(uint)buffer[bufindex + 2])<<0) | ((cast(uint)buffer[bufindex + 3])<<24);
558 bufindex += 4;
559 }
560 //img = imgFac.createImage(hdr.width, hdr.height, 4, 8);
561 } else {
562 //img = imgFac.createImage(hdr.width, hdr.height, 3, 8);
563 for (int i = 0; i < img.length; i++) {
564 img.data[i] = ((cast(uint)buffer[bufindex])<<16) | ((cast(uint)buffer[bufindex + 1])<<8) | ((cast(uint)buffer[bufindex + 2])<<0) | 0xFF000000;
565 bufindex += 3;
566 }
567 }
568 }
569
570 Delete(buffer);
571
572 return compound(img, "");
573 }
574
575 version (ENABLE_SAVE_PNG) {
576 /*
577 * Save PNG to stream.
578 * GC-free
579 */
580 Compound!(bool, string) savePNG(SuperImage img, OutputStream output)
581 in
582 {
583 assert (img.data.length);
584 }
585 body
586 {
587 Compound!(bool, string) error(string errorMsg)
588 {
589 return compound(false, errorMsg);
590 }
591
592 if (img.bitDepth != 8)
593 return error("savePNG error: only 8-bit images are supported by encoder");
594
595 bool writeChunk(ubyte[4] chunkType, ubyte[] chunkData)
596 {
597 PNGChunk hdrChunk;
598 hdrChunk.length = cast(uint)chunkData.length;
599 hdrChunk.type = chunkType;
600 hdrChunk.data = chunkData;
601 hdrChunk.crc = crc32(chain(chunkType[0..$], hdrChunk.data));
602
603 if (!output.writeBE!uint(hdrChunk.length)
604 || !output.writeArray(hdrChunk.type))
605 return false;
606
607 if (chunkData.length)
608 if (!output.writeArray(hdrChunk.data))
609 return false;
610
611 if (!output.writeBE!uint(hdrChunk.crc))
612 return false;
613
614 return true;
615 }
616
617 bool writeHeader()
618 {
619 PNGHeader hdr;
620 hdr.width = networkByteOrder(img.width);
621 hdr.height = networkByteOrder(img.height);
622 hdr.bitDepth = 8;
623 if (img.channels == 4)
624 hdr.colorType = ColorType.RGBA;
625 else if (img.channels == 3)
626 hdr.colorType = ColorType.RGB;
627 else if (img.channels == 2)
628 hdr.colorType = ColorType.GreyscaleAlpha;
629 else if (img.channels == 1)
630 hdr.colorType = ColorType.Greyscale;
631 hdr.compressionMethod = 0;
632 hdr.filterMethod = 0;
633 hdr.interlaceMethod = 0;
634
635 return writeChunk(IHDR, hdr.bytes);
636 }
637
638 output.writeArray(PNGSignature);
639 if (!writeHeader())
640 return error("savePNG error: write failed (disk full?)");
641
642 //TODO: filtering
643 ubyte[] raw = New!(ubyte[])(img.width * img.height * img.channels + img.height);
644 foreach(y; 0..img.height)
645 {
646 auto rowStart = y * (img.width * img.channels + 1);
647 raw[rowStart] = 0; // No filter
648
649 foreach(x; 0..img.width)
650 {
651 auto dataIndex = (y * img.width + x) * img.channels;
652 auto rawIndex = rowStart + 1 + x * img.channels;
653
654 foreach(ch; 0..img.channels)
655 raw[rawIndex + ch] = img.data[dataIndex + ch];
656 }
657 }
658
659 ubyte[] buffer = New!(ubyte[])(64 * 1024);
660 ZlibBufferedEncoder zlibEncoder = ZlibBufferedEncoder(buffer, raw);
661 while (!zlibEncoder.ended)
662 {
663 auto len = zlibEncoder.encode();
664 if (len > 0)
665 writeChunk(IDAT, zlibEncoder.buffer[0..len]);
666 }
667
668 writeChunk(IEND, []);
669
670 Delete(buffer);
671 Delete(raw);
672
673 return compound(true, "");
674 }
675 }
676
677 /*
678 * performs the paeth PNG filter from pixels values:
679 * a = back
680 * b = up
681 * c = up and back
682 */
683 pure ubyte paeth(ubyte a, ubyte b, ubyte c)
684 {
685 int p = a + b - c;
686 int pa = abs(p - a);
687 int pb = abs(p - b);
688 int pc = abs(p - c);
689 if (pa <= pb && pa <= pc) return a;
690 else if (pb <= pc) return b;
691 else return c;
692 }
693
694 bool filter(PNGHeader* hdr,
695 uint channels,
696 bool indexed,
697 ubyte[] ibuffer,
698 out ubyte[] obuffer,
699 out string errorMsg)
700 {
701 uint dataSize = cast(uint)ibuffer.length;
702 uint scanlineSize;
703
704 uint calculatedSize;
705 if (indexed)
706 {
707 calculatedSize = hdr.width * hdr.height * hdr.bitDepth / 8 + hdr.height;
708 scanlineSize = hdr.width * hdr.bitDepth / 8 + 1;
709 }
710 else
711 {
712 calculatedSize = hdr.width * hdr.height * channels + hdr.height;
713 scanlineSize = hdr.width * channels + 1;
714 }
715
716 version(PNGDebug)
717 {
718 writefln("[filter] dataSize = %s", dataSize);
719 writefln("[filter] calculatedSize = %s", calculatedSize);
720 }
721
722 if (dataSize != calculatedSize)
723 {
724 errorMsg = "loadPNG error: image size and data mismatch";
725 return false;
726 }
727
728 obuffer = New!(ubyte[])(calculatedSize - hdr.height);
729
730 ubyte pback, pup, pupback, cbyte;
731
732 for (int i = 0; i < hdr.height; ++i)
733 {
734 pback = 0;
735
736 // get the first byte of a scanline
737 ubyte scanFilter = ibuffer[i * scanlineSize];
738
739 if (indexed)
740 {
741 // TODO: support filtering for indexed images
742 if (scanFilter != FilterMethod.None)
743 {
744 errorMsg = "loadPNG error: filtering is not supported for indexed images";
745 return false;
746 }
747
748 for (int j = 1; j < scanlineSize; ++j)
749 {
750 ubyte b = ibuffer[(i * scanlineSize) + j];
751 obuffer[(i * (scanlineSize-1) + j - 1)] = b;
752 }
753 continue;
754 }
755
756 for (int j = 0; j < hdr.width; ++j)
757 {
758 for (int k = 0; k < channels; ++k)
759 {
760 if (i == 0) pup = 0;
761 else pup = obuffer[((i-1) * hdr.width + j) * channels + k]; // (hdr.height-(i-1)-1)
762 if (j == 0) pback = 0;
763 else pback = obuffer[(i * hdr.width + j-1) * channels + k];
764 if (i == 0 || j == 0) pupback = 0;
765 else pupback = obuffer[((i-1) * hdr.width + j - 1) * channels + k];
766
767 // get the current byte from ibuffer
768 cbyte = ibuffer[i * (hdr.width * channels + 1) + j * channels + k + 1];
769
770 // filter, then set the current byte in data
771 switch (scanFilter)
772 {
773 case FilterMethod.None:
774 obuffer[(i * hdr.width + j) * channels + k] = cbyte;
775 break;
776 case FilterMethod.Sub:
777 obuffer[(i * hdr.width + j) * channels + k] = cast(ubyte)(cbyte + pback);
778 break;
779 case FilterMethod.Up:
780 obuffer[(i * hdr.width + j) * channels + k] = cast(ubyte)(cbyte + pup);
781 break;
782 case FilterMethod.Average:
783 obuffer[(i * hdr.width + j) * channels + k] = cast(ubyte)(cbyte + (pback + pup) / 2);
784 break;
785 case FilterMethod.Paeth:
786 obuffer[(i * hdr.width + j) * channels + k] = cast(ubyte)(cbyte + paeth(pback, pup, pupback));
787 break;
788 default:
789 errorMsg = format("loadPNG error: unknown scanline filter (%s)", scanFilter);
790 return false;
791 }
792 }
793 }
794 }
795
796 return true;
797 }
798
799 uint crc32(R)(R range, uint inCrc = 0) if (isInputRange!R)
800 {
801 uint[256] generateTable()
802 {
803 uint[256] table;
804 uint crc;
805 for (int i = 0; i < 256; i++)
806 {
807 crc = i;
808 for (int j = 0; j < 8; j++)
809 crc = crc & 1 ? (crc >> 1) ^ 0xEDB88320UL : crc >> 1;
810 table[i] = crc;
811 }
812 return table;
813 }
814
815 static const uint[256] table = generateTable();
816
817 uint crc;
818
819 crc = inCrc ^ 0xFFFFFFFF;
820 foreach(v; range)
821 crc = (crc >> 8) ^ table[(crc ^ v) & 0xFF];
822
823 return (crc ^ 0xFFFFFFFF);
824 }
825
826 static if (false) {
827 unittest
828 {
829 import std.base64;
830
831 InputStream png() {
832 string minimal =
833 "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADklEQVR42mL4z8AAEGAAAwEBAGb9nyQAAAAASUVORK5CYII=";
834
835 ubyte[] bytes = Base64.decode(minimal);
836 return new ArrayStream(bytes, bytes.length);
837 }
838
839 SuperImage img = loadPNG(png());
840
841 assert(img.width == 1);
842 assert(img.height == 1);
843 assert(img.channels == 3);
844 assert(img.pixelSize == 3);
845 assert(img.data == [0xff, 0x00, 0x00]);
846
847 createDir("tests", false);
848 savePNG(img, "tests/minimal.png");
849 loadPNG("tests/minimal.png");
850 }
851 }