1 /*
2 Copyright (c) 2011-2021 Timur Gafarov, Martin Cejp, Vadim Lopatin
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 /**
30  * Decode and encode PNG/APNG images
31  *
32  * Copyright: Timur Gafarov, Martin Cejp, Vadim Lopatin 2011-2021.
33  * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).
34  * Authors: Timur Gafarov, Martin Cejp, Vadim Lopatin
35  */
36 module dlib.image.io.png;
37 
38 import std.stdio;
39 import std.math;
40 import std..string;
41 import std.range;
42 import dlib.core.memory;
43 import dlib.core.stream;
44 import dlib.core.compound;
45 import dlib.filesystem.local;
46 import dlib.math.utils;
47 import dlib.math.interpolation;
48 import dlib.coding.zlib;
49 import dlib.image.color;
50 import dlib.image.image;
51 import dlib.image.animation;
52 import dlib.image.io;
53 
54 // uncomment this to see debug messages:
55 //version = PNGDebug;
56 
57 static const ubyte[8] PNGSignature = [137, 80, 78, 71, 13, 10, 26, 10];
58 
59 // Standard chunks
60 static const ubyte[4] IHDR = ['I', 'H', 'D', 'R']; // Image header
61 static const ubyte[4] IEND = ['I', 'E', 'N', 'D']; // Image end
62 static const ubyte[4] IDAT = ['I', 'D', 'A', 'T']; // Image data
63 static const ubyte[4] PLTE = ['P', 'L', 'T', 'E']; // Palette
64 static const ubyte[4] tRNS = ['t', 'R', 'N', 'S']; // Transparency
65 static const ubyte[4] bKGD = ['b', 'K', 'G', 'D']; // Background color
66 static const ubyte[4] tEXt = ['t', 'E', 'X', 't']; // Textual data (uncompressed)
67 static const ubyte[4] zTXt = ['z', 'T', 'X', 't']; // Textual data (zlib compressed)
68 static const ubyte[4] iTXt = ['i', 'T', 'X', 't']; // International text
69 
70 // Extension chunks
71 static const ubyte[4] oFFs = ['o', 'F', 'F', 's']; // Image offset
72 static const ubyte[4] pCAL = ['p', 'C', 'A', 'L']; // Calibration of pixel values
73 static const ubyte[4] sCAL = ['s', 'C', 'A', 'L']; // Physical scale of image subject
74 static const ubyte[4] gIFg = ['g', 'I', 'F', 'g']; // GIF graphic control
75 static const ubyte[4] gIFx = ['g', 'I', 'F', 'x']; // GIF application
76 static const ubyte[4] gIFt = ['g', 'I', 'F', 't']; // GIF plain text (deprecated)
77 static const ubyte[4] fRAc = ['f', 'R', 'A', 'c']; // Fractal image parameters
78 static const ubyte[4] sTER = ['s', 'T', 'E', 'R']; // Indicator of stereo image
79 static const ubyte[4] dSIG = ['d', 'S', 'I', 'G']; // Digital signature
80 
81 // ImageMagick chunks
82 static const ubyte[4] vpAg = ['v', 'p', 'A', 'g']; // VirtualPage Tags
83 
84 // APNG chunks
85 static const ubyte[4] acTL = ['a', 'c', 'T', 'L']; // Animation control
86 static const ubyte[4] fcTL = ['f', 'c', 'T', 'L']; // Frame control
87 static const ubyte[4] fdAT = ['f', 'd', 'A', 'T']; // Frame data
88 
89 enum ColorType: ubyte
90 {
91     Greyscale = 0,      // allowed bit depths: 1, 2, 4, 8 and 16
92     RGB = 2,            // allowed bit depths: 8 and 16
93     Palette = 3,        // allowed bit depths: 1, 2, 4 and 8
94     GreyscaleAlpha = 4, // allowed bit depths: 8 and 16
95     RGBA = 6,           // allowed bit depths: 8 and 16
96     Any = 7             // one of the above
97 }
98 
99 enum FilterMethod: ubyte
100 {
101     None = 0,
102     Sub = 1,
103     Up = 2,
104     Average = 3,
105     Paeth = 4
106 }
107 
108 struct PNGChunk
109 {
110     uint length;
111     ubyte[4] type;
112     ubyte[] data;
113     uint crc;
114 
115     void free()
116     {
117         if (data.ptr)
118             Delete(data);
119     }
120 }
121 
122 struct PNGHeader
123 {
124     union
125     {
126         struct
127         {
128             uint width;
129             uint height;
130             ubyte bitDepth;
131             ubyte colorType;
132             ubyte compressionMethod;
133             ubyte filterMethod;
134             ubyte interlaceMethod;
135         };
136         ubyte[13] bytes;
137     }
138 }
139 
140 struct AnimationControlChunk
141 {
142     union
143     {
144         struct
145         {
146             uint numFrames;
147             uint numPlays;
148         };
149         ubyte[8] bytes;
150     }
151 
152     void readFromBuffer(ubyte[] data)
153     {
154         *(&numFrames) = *(cast(uint*)data.ptr);
155         numFrames = bigEndian(numFrames);
156         *(&numPlays) = *(cast(uint*)(data.ptr+4));
157         numPlays = bigEndian(numPlays);
158     }
159 }
160 
161 struct OffsetChunk
162 {
163     int posX;
164     int posY;
165     ubyte unitSpecifier;
166 
167     void readFromBuffer(ubyte[] data)
168     {
169         *(&posX) = *(cast(int*)data.ptr);
170         posX = bigEndian(posX);
171         *(&posY) = *(cast(int*)(data.ptr+4));
172         posY = bigEndian(posY);
173         *(&unitSpecifier) = *(data.ptr+8);
174     }
175 }
176 
177 enum DisposeOp: ubyte
178 {
179     None = 0,
180     Background = 1,
181     Previous = 2
182 }
183 
184 enum BlendOp: ubyte
185 {
186     Source = 0,
187     Over = 1
188 }
189 
190 struct FrameControlChunk
191 {
192     union
193     {
194         struct
195         {
196             uint sequenceNumber;
197             uint width;
198             uint height;
199             uint x;
200             uint y;
201             ushort delayNumerator;
202             ushort delayDenominator;
203             ubyte disposeOp;
204             ubyte blendOp;
205         };
206         ubyte[26] bytes;
207     }
208 
209     void readFromBuffer(ubyte[] data)
210     {
211         *(&sequenceNumber) = *(cast(uint*)data.ptr);
212         sequenceNumber = bigEndian(sequenceNumber);
213         *(&width) = *(cast(uint*)(data.ptr+4));
214         width = bigEndian(width);
215         *(&height) = *(cast(uint*)(data.ptr+8));
216         height = bigEndian(height);
217         *(&x) = *(cast(uint*)(data.ptr+12));
218         x = bigEndian(x);
219         *(&y) = *(cast(uint*)(data.ptr+16));
220         y = bigEndian(y);
221         *(&delayNumerator) = *(cast(ushort*)(data.ptr+20));
222         delayNumerator = bigEndian(delayNumerator);
223         *(&delayDenominator) = *(cast(ushort*)(data.ptr+22));
224         delayDenominator = bigEndian(delayDenominator);
225         disposeOp = data[24];
226         blendOp = data[25];
227     }
228 }
229 
230 struct PNGImage
231 {
232     // Common PNG data
233     PNGHeader hdr;
234     uint numChannels;
235     uint bitDepth;
236     uint bytesPerChannel;
237     bool isAnimated = false;
238 
239     // Data for indexed PNG
240     ubyte[] palette;
241     ubyte[] transparency;
242     uint paletteSize = 0;
243 
244     // APNG data
245     uint numFrames = 1;
246     uint numLoops = 0;
247     bool decodingFirstFrame;
248     FrameControlChunk frame;
249 
250     // Offset
251     OffsetChunk offset;
252 
253     ZlibDecoder decoder;
254 
255     ubyte[] frameBuffer;
256     uint frameSize;
257 
258     ubyte[] filteredBuffer;
259     uint filteredBufferSize;
260 
261     void initDecoder()
262     {
263         ubyte[] buffer;
264 
265         if (decoder.buffer.length)
266         {
267             //Delete(decoder.buffer);
268             buffer = decoder.buffer;
269         }
270         else
271         {
272             uint bufferLength = hdr.width * hdr.height * numChannels * bytesPerChannel + hdr.height;
273             buffer = New!(ubyte[])(bufferLength);
274         }
275 
276         decoder = ZlibDecoder(buffer);
277     }
278 
279     void initFrameBuffer()
280     {
281         if (frameBuffer.length)
282             Delete(frameBuffer);
283 
284         if (filteredBuffer.length)
285             Delete(filteredBuffer);
286 
287         frameBuffer = New!(ubyte[])(hdr.width * hdr.height * numChannels * bytesPerChannel);
288         filteredBuffer = New!(ubyte[])(hdr.width * hdr.height * numChannels * bytesPerChannel);
289     }
290 
291     void free()
292     {
293         if (decoder.buffer.length)
294             Delete(decoder.buffer);
295 
296         if (frameBuffer.length)
297             Delete(frameBuffer);
298 
299         if (filteredBuffer.length)
300             Delete(filteredBuffer);
301 
302         if (palette.length)
303             Delete(palette);
304 
305         if (transparency.length)
306             Delete(transparency);
307     }
308 }
309 
310 class PNGLoadException: ImageLoadException
311 {
312     this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null)
313     {
314         super(msg, file, line, next);
315     }
316 }
317 
318 /**
319  * Load PNG from file using local FileSystem.
320  * Causes GC allocation
321  */
322 SuperImage loadPNG(string filename)
323 {
324     InputStream input = openForInput(filename);
325     auto img = loadPNG(input);
326     input.close();
327     return img;
328 }
329 
330 /**
331  * Load animated PNG (APNG) from file using local FileSystem.
332  * Causes GC allocation
333  */
334 SuperAnimatedImage loadAPNG(string filename)
335 {
336     InputStream input = openForInput(filename);
337     auto img = loadAPNG(input);
338     input.close();
339     return img;
340 }
341 
342 /**
343  * Save PNG to file using local FileSystem.
344  * Causes GC allocation
345  */
346 void savePNG(SuperImage img, string filename)
347 {
348     OutputStream output = openForOutput(filename);
349     Compound!(bool, string) res = savePNG(img, output);
350     output.close();
351 
352     if (!res[0])
353         throw new PNGLoadException(res[1]);
354 }
355 
356 /**
357  * Save APNG to file using local FileSystem.
358  * Causes GC allocation
359  */
360 void saveAPNG(SuperAnimatedImage img, string filename)
361 {
362     OutputStream output = openForOutput(filename);
363     Compound!(bool, string) res = saveAPNG(img, output);
364     output.close();
365 
366     if (!res[0])
367         throw new PNGLoadException(res[1]);
368 }
369 
370 /**
371  * Load PNG from stream using default image factory.
372  * Causes GC allocation
373  */
374 SuperImage loadPNG(InputStream istrm)
375 {
376     Compound!(SuperImage, string) res =
377         loadPNG(istrm, defaultImageFactory);
378     if (res[0] is null)
379         throw new PNGLoadException(res[1]);
380     else
381         return res[0];
382 }
383 
384 ///
385 unittest
386 {
387     import std.base64;
388 
389     InputStream png() {
390         string minimal =
391             "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADklEQVR42mL4z8AAEGAAAwEBAGb9nyQAAAAASUVORK5CYII=";
392 
393         ubyte[] bytes = Base64.decode(minimal);
394         return new ArrayStream(bytes, bytes.length);
395     }
396 
397     SuperImage img = loadPNG(png());
398 
399     assert(img.width == 1);
400     assert(img.height == 1);
401     assert(img.channels == 3);
402     assert(img.pixelSize == 3);
403     assert(img.data == [0xff, 0x00, 0x00]);
404 
405     createDir("tests", false);
406     savePNG(img, "tests/minimal.png");
407     loadPNG("tests/minimal.png");
408 }
409 
410 /**
411  * Load animated PNG (APNG) from stream using default animated image factory.
412  * Causes GC allocation
413  */
414 SuperAnimatedImage loadAPNG(InputStream istrm)
415 {
416     Compound!(SuperImage, string) res =
417         loadPNG(istrm, animatedImageFactory);
418     if (res[0] is null)
419         throw new PNGLoadException(res[1]);
420     else
421         return cast(SuperAnimatedImage)res[0];
422 }
423 
424 /**
425  * Load PNG from stream using specified image factory.
426  * GC-free
427  */
428 Compound!(SuperImage, string) loadPNG(
429     InputStream istrm,
430     SuperImageFactory imgFac)
431 {
432     PNGImage png;
433 
434     SuperImage img = null;
435     SuperAnimatedImage animImg = null;
436     SuperImage tmpImg = null;
437 
438     void finalize()
439     {
440         png.free();
441 
442         if (tmpImg)
443         {
444             tmpImg.free();
445             tmpImg = null;
446         }
447 
448         // don't close the stream, just release our reference
449         istrm = null;
450     }
451 
452     Compound!(SuperImage, string) error(string errorMsg)
453     {
454         finalize();
455         if (img)
456         {
457             img.free();
458             img = null;
459         }
460         return compound(img, errorMsg);
461     }
462 
463     ubyte[8] signatureBuffer;
464 
465     if (!istrm.fillArray(signatureBuffer))
466     {
467         return error("loadPNG error: signature check failed");
468     }
469 
470     version(PNGDebug)
471     {
472         writeln("----------------");
473         writeln("PNG Signature: ", signatureBuffer);
474         writeln("----------------");
475     }
476 
477     bool endChunk = false;
478     while (!endChunk && istrm.readable)
479     {
480         PNGChunk chunk;
481         auto res = readChunk(&png, istrm, &chunk);
482 
483         if (!res[0])
484         {
485             chunk.free();
486             return error(res[1]);
487         }
488         else
489         {
490             if (chunk.type == IEND)
491             {
492                 endChunk = true;
493                 chunk.free();
494             }
495             else if (chunk.type == IHDR)
496             {
497                 res = readIHDR(&png, &chunk);
498                 chunk.free();
499                 if (!res[0])
500                 {
501                     return error(res[1]);
502                 }
503 
504                 png.decodingFirstFrame = true;
505                 png.frame.width = png.hdr.width;
506                 png.frame.height = png.hdr.height;
507                 png.frame.x = 0;
508                 png.frame.y = 0;
509 
510                 png.initFrameBuffer();
511                 png.initDecoder();
512             }
513             else if (chunk.type == IDAT)
514             {
515                 png.decoder.decode(chunk.data);
516                 chunk.free();
517             }
518             else if (chunk.type == PLTE)
519             {
520                 png.palette = chunk.data;
521             }
522             else if (chunk.type == tRNS)
523             {
524                 png.transparency = chunk.data;
525 
526                 if (png.hdr.colorType == ColorType.Palette)
527                 {
528                     if (png.transparency.length > 0)
529                         png.numChannels = 4;
530                     else
531                         png.numChannels = 3;
532                 }
533 
534                 version(PNGDebug)
535                 {
536                     writefln("transparency.length = %s", png.transparency.length);
537                 }
538 
539                 png.initFrameBuffer();
540                 png.initDecoder();
541             }
542             else if (chunk.type == oFFs)
543             {
544                 png.offset.readFromBuffer(chunk.data);
545                 chunk.free();
546 
547                 version(PNGDebug)
548                 {
549                     writefln("posX = %s", png.offset.posX);
550                     writefln("posY = %s", png.offset.posY);
551                     writefln("unitSpecifier = %s", png.offset.unitSpecifier);
552                 }
553             }
554             else if (chunk.type == acTL)
555             {
556                 AnimationControlChunk animControl;
557                 animControl.readFromBuffer(chunk.data);
558                 png.numFrames = animControl.numFrames;
559                 png.numLoops = animControl.numPlays;
560                 png.isAnimated = true;
561 
562                 version(PNGDebug)
563                 {
564                     writefln("numFrames = %s", png.numFrames);
565                     writefln("numLoops = %s", png.numLoops);
566                 }
567 
568                 chunk.free();
569             }
570             else if (chunk.type == fcTL)
571             {
572                 png.frame.readFromBuffer(chunk.data);
573 
574                 version(PNGDebug)
575                 {
576                     writefln("sequenceNumber = %s", png.frame.sequenceNumber);
577                     writefln("frameWidth = %s", png.frame.width);
578                     writefln("frameHeight = %s", png.frame.height);
579                     writefln("frameX = %s", png.frame.x);
580                     writefln("frameY = %s", png.frame.y);
581                     writefln("delayNumerator = %s", png.frame.delayNumerator);
582                     writefln("delayDenominator = %s", png.frame.delayDenominator);
583                     writefln("disposeOp = %s", cast(DisposeOp)png.frame.disposeOp);
584                     writefln("blendOp = %s", cast(BlendOp)png.frame.blendOp);
585                 }
586 
587                 png.initDecoder();
588 
589                 chunk.free();
590             }
591             else if (chunk.type == fdAT)
592             {
593                 uint dataSequenceNumber;
594                 *(&dataSequenceNumber) = *(cast(uint*)chunk.data.ptr);
595                 dataSequenceNumber = bigEndian(dataSequenceNumber);
596                 version(PNGDebug)
597                 {
598                     writefln("sequenceNumber = %s", dataSequenceNumber);
599                 }
600 
601                 png.decoder.decode(chunk.data[4..$]);
602                 chunk.free();
603             }
604             else
605             {
606                 chunk.free();
607             }
608 
609             version(PNGDebug)
610             {
611                 writeln("----------------");
612             }
613         }
614 
615         if (png.decoder.hasEnded)
616         {
617             if (img is null)
618             {
619                 tmpImg = imgFac.createImage(png.hdr.width, png.hdr.height, png.numChannels, png.bitDepth);
620                 img = imgFac.createImage(png.hdr.width, png.hdr.height, png.numChannels, png.bitDepth, png.numFrames);
621 
622                 if (png.isAnimated)
623                     animImg = cast(SuperAnimatedImage)img;
624             }
625 
626             res = fillFrame(&png);
627             if (res[0])
628             {
629                 if (png.decodingFirstFrame)
630                 {
631                     png.decodingFirstFrame = false;
632 
633                     if (tmpImg.data.length != png.frameBuffer.length)
634                     {
635                         return error("loadPNG error: uncompressed data length mismatch");
636                     }
637 
638                     tmpImg.data[] = png.frameBuffer[0..png.frameSize];
639                     img.data[] = tmpImg.data[];
640 
641                     if (animImg)
642                     {
643                         disposeFrame(&png, animImg, tmpImg, true);
644                     }
645                 }
646                 else
647                 {
648                     blitFrame(&png, png.frameBuffer, tmpImg);
649 
650                     img.data[] = tmpImg.data[];
651 
652                     uint f = animImg.currentFrame;
653                     animImg.currentFrame = f - 1;
654                     disposeFrame(&png, animImg, tmpImg, false);
655                     animImg.currentFrame = f;
656                 }
657 
658                 if (animImg)
659                 {
660                     if (animImg.currentFrame == animImg.numFrames-1)
661                     {
662                         // Last frame, stop here
663                         animImg.currentFrame = 0;
664                         break;
665                     }
666                 }
667             }
668             else
669             {
670                 return error(res[1]);
671             }
672 
673             if (animImg)
674             {
675                 animImg.advanceFrame();
676             }
677             else
678             {
679                 // Stop decoding if we don't need animation
680                 break;
681             }
682         }
683     }
684 
685     finalize();
686     return compound(img, "");
687 }
688 
689 /**
690  * Load animated PNG (APNG) from stream using specified image factory.
691  * GC-free
692  */
693 Compound!(SuperAnimatedImage, string) loadAPNG(
694     InputStream istrm,
695     SuperImageFactory imgFac)
696 {
697     SuperAnimatedImage img = null;
698     auto res = loadPNG(istrm, imgFac);
699     if (res[0])
700         img = cast(SuperAnimatedImage)res[0];
701     return compound(img, res[1]);
702 }
703 
704 /**
705  * Save APNG to stream.
706  * GC-free
707  */
708 Compound!(bool, string) saveAPNG(SuperAnimatedImage img, OutputStream output)
709 in
710 {
711     assert (img.data.length);
712 }
713 do
714 {
715     ubyte[] raw;
716     ubyte[] buffer;
717 
718     void finalize()
719     {
720         if (buffer.length)
721             Delete(buffer);
722         if (raw.length)
723             Delete(raw);
724     }
725 
726     Compound!(bool, string) error(string errorMsg)
727     {
728         finalize();
729         return compound(false, errorMsg);
730     }
731 
732     if (img.bitDepth != 8)
733         return error("savePNG error: only 8-bit images are supported by encoder");
734 
735     bool writeChunk(ubyte[4] chunkType, ubyte[] chunkData)
736     {
737         PNGChunk hdrChunk;
738         hdrChunk.length = cast(uint)chunkData.length;
739         hdrChunk.type = chunkType;
740         hdrChunk.data = chunkData;
741         hdrChunk.crc = crc32(chain(chunkType[0..$], hdrChunk.data));
742 
743         if (!output.writeBE!uint(hdrChunk.length)
744             || !output.writeArray(hdrChunk.type))
745             return false;
746 
747         if (chunkData.length)
748             if (!output.writeArray(hdrChunk.data))
749                 return false;
750 
751         if (!output.writeBE!uint(hdrChunk.crc))
752             return false;
753 
754         return true;
755     }
756 
757     bool writeHeader()
758     {
759         PNGHeader hdr;
760         hdr.width = networkByteOrder(img.width);
761         hdr.height = networkByteOrder(img.height);
762         hdr.bitDepth = 8;
763         if (img.channels == 4)
764             hdr.colorType = ColorType.RGBA;
765         else if (img.channels == 3)
766             hdr.colorType = ColorType.RGB;
767         else if (img.channels == 2)
768             hdr.colorType = ColorType.GreyscaleAlpha;
769         else if (img.channels == 1)
770             hdr.colorType = ColorType.Greyscale;
771         hdr.compressionMethod = 0;
772         hdr.filterMethod = 0;
773         hdr.interlaceMethod = 0;
774 
775         return writeChunk(IHDR, hdr.bytes);
776     }
777 
778     uint seqNumber = 0;
779 
780     bool writeAnimationControlChunk()
781     {
782         AnimationControlChunk actl;
783         actl.numFrames = networkByteOrder(img.numFrames);
784         actl.numPlays = networkByteOrder(0);
785         return writeChunk(acTL, actl.bytes);
786     }
787 
788     bool writeFrameControlChunk()
789     {
790         FrameControlChunk fctl;
791         fctl.sequenceNumber = networkByteOrder(seqNumber);
792         seqNumber++;
793         fctl.width = networkByteOrder(img.width);
794         fctl.height = networkByteOrder(img.height);
795         fctl.x = networkByteOrder(0);
796         fctl.y = networkByteOrder(0);
797         // TODO: add timeStep to SuperAnimatedImage
798         fctl.delayNumerator = networkByteOrder(75);
799         fctl.delayDenominator = networkByteOrder(1000);
800         fctl.disposeOp = DisposeOp.Background;
801         fctl.blendOp = BlendOp.Source;
802         return writeChunk(fcTL, fctl.bytes);
803     }
804 
805     bool writeFrameDataChunk(ubyte[] data)
806     {
807         uint len = cast(uint)data.length + 4;
808         ubyte[4] type = fdAT;
809         uint seq = seqNumber;
810         uint seqBE = networkByteOrder(seqNumber);
811         seqNumber++;
812         ubyte[4] seqNumberBytes;
813         seqNumberBytes = (cast(ubyte*)&seqBE)[0..4][];
814         uint crc = crc32(chain(type[0..$], seqNumberBytes[0..$], data));
815 
816         if (!output.writeBE!uint(len)
817             || !output.writeArray(type))
818             return false;
819 
820         if (!output.writeBE!uint(seq))
821             return false;
822 
823         if (data.length)
824             if (!output.writeArray(data))
825                 return false;
826 
827         if (!output.writeBE!uint(crc))
828             return false;
829 
830         return true;
831     }
832 
833     output.writeArray(PNGSignature);
834     if (!writeHeader())
835         return error("savePNG error: write failed (disk full?)");
836 
837     if (!writeAnimationControlChunk())
838         return error("savePNG error: write failed (disk full?)");
839 
840     //TODO: filtering
841     raw = New!(ubyte[])(img.width * img.height * img.channels + img.height);
842     buffer = New!(ubyte[])(64 * 1024);
843 
844     bool encode(uint frame)
845     {
846         if (!writeFrameControlChunk())
847             return false;
848 
849         foreach(y; 0..img.height)
850         {
851             auto rowStart = y * (img.width * img.channels + 1);
852             raw[rowStart] = 0; // No filter
853 
854             foreach(x; 0..img.width)
855             {
856                 auto dataIndex = (y * img.width + x) * img.channels;
857                 auto rawIndex = rowStart + 1 + x * img.channels;
858 
859                 foreach(ch; 0..img.channels)
860                     raw[rawIndex + ch] = img.data[dataIndex + ch];
861             }
862         }
863 
864         ZlibBufferedEncoder zlibEncoder = ZlibBufferedEncoder(buffer, raw);
865         while (!zlibEncoder.ended)
866         {
867             auto len = zlibEncoder.encode();
868             if (len > 0)
869             {
870                 bool res;
871                 if (frame == 0)
872                     res = writeChunk(IDAT, zlibEncoder.buffer[0..len]);
873                 else
874                     res = writeFrameDataChunk(zlibEncoder.buffer[0..len]);
875 
876                 if (!res)
877                     return false;
878             }
879         }
880 
881         return true;
882     }
883 
884     uint startFrame = img.currentFrame;
885     foreach(f; 0..img.numFrames)
886     {
887         img.currentFrame = f;
888         if (!encode(f))
889             return error("savePNG error: write failed (disk full?)");
890     }
891     img.currentFrame = startFrame;
892 
893     writeChunk(IEND, []);
894 
895     finalize();
896     return compound(true, "");
897 }
898 
899 /**
900  * Save PNG to stream.
901  * GC-free
902  */
903 Compound!(bool, string) savePNG(SuperImage img, OutputStream output)
904 in
905 {
906     assert (img.data.length);
907 }
908 do
909 {
910     Compound!(bool, string) error(string errorMsg)
911     {
912         return compound(false, errorMsg);
913     }
914 
915     if (img.bitDepth != 8)
916         return error("savePNG error: only 8-bit images are supported by encoder");
917 
918     bool writeChunk(ubyte[4] chunkType, ubyte[] chunkData)
919     {
920         PNGChunk hdrChunk;
921         hdrChunk.length = cast(uint)chunkData.length;
922         hdrChunk.type = chunkType;
923         hdrChunk.data = chunkData;
924         hdrChunk.crc = crc32(chain(chunkType[0..$], hdrChunk.data));
925 
926         if (!output.writeBE!uint(hdrChunk.length)
927             || !output.writeArray(hdrChunk.type))
928             return false;
929 
930         if (chunkData.length)
931             if (!output.writeArray(hdrChunk.data))
932                 return false;
933 
934         if (!output.writeBE!uint(hdrChunk.crc))
935             return false;
936 
937         return true;
938     }
939 
940     bool writeHeader()
941     {
942         PNGHeader hdr;
943         hdr.width = networkByteOrder(img.width);
944         hdr.height = networkByteOrder(img.height);
945         hdr.bitDepth = 8;
946         if (img.channels == 4)
947             hdr.colorType = ColorType.RGBA;
948         else if (img.channels == 3)
949             hdr.colorType = ColorType.RGB;
950         else if (img.channels == 2)
951             hdr.colorType = ColorType.GreyscaleAlpha;
952         else if (img.channels == 1)
953             hdr.colorType = ColorType.Greyscale;
954         hdr.compressionMethod = 0;
955         hdr.filterMethod = 0;
956         hdr.interlaceMethod = 0;
957 
958         return writeChunk(IHDR, hdr.bytes);
959     }
960 
961     output.writeArray(PNGSignature);
962     if (!writeHeader())
963         return error("savePNG error: write failed (disk full?)");
964 
965     //TODO: filtering
966     ubyte[] raw = New!(ubyte[])(img.width * img.height * img.channels + img.height);
967     foreach(y; 0..img.height)
968     {
969         auto rowStart = y * (img.width * img.channels + 1);
970         raw[rowStart] = 0; // No filter
971 
972         foreach(x; 0..img.width)
973         {
974             auto dataIndex = (y * img.width + x) * img.channels;
975             auto rawIndex = rowStart + 1 + x * img.channels;
976 
977             foreach(ch; 0..img.channels)
978                 raw[rawIndex + ch] = img.data[dataIndex + ch];
979         }
980     }
981 
982     ubyte[] buffer = New!(ubyte[])(64 * 1024);
983     ZlibBufferedEncoder zlibEncoder = ZlibBufferedEncoder(buffer, raw);
984     while (!zlibEncoder.ended)
985     {
986         auto len = zlibEncoder.encode();
987         if (len > 0)
988             writeChunk(IDAT, zlibEncoder.buffer[0..len]);
989     }
990 
991     writeChunk(IEND, []);
992 
993     Delete(buffer);
994     Delete(raw);
995 
996     return compound(true, "");
997 }
998 
999 Compound!(bool, string) err(string msg)
1000 {
1001     return compound(false, msg);
1002 }
1003 
1004 Compound!(bool, string) suc()
1005 {
1006     return compound(true, "");
1007 }
1008 
1009 Compound!(bool, string) readChunk(
1010     PNGImage* png,
1011     InputStream istrm,
1012     PNGChunk* chunk)
1013 {
1014     if (!istrm.readBE!uint(&chunk.length) ||
1015         !istrm.fillArray(chunk.type))
1016     {
1017         return err("loadPNG error: failed to read chunk, invalid PNG stream");
1018     }
1019 
1020     version(PNGDebug) writefln("Chunk length = %s", chunk.length);
1021     version(PNGDebug) writefln("Chunk type = %s", cast(char[])chunk.type);
1022 
1023     if (chunk.length > 0)
1024     {
1025         chunk.data = New!(ubyte[])(chunk.length);
1026         if (!istrm.fillArray(chunk.data))
1027         {
1028             return err("loadPNG error: failed to read chunk data, invalid PNG stream");
1029         }
1030     }
1031 
1032     version(PNGDebug) writefln("Chunk data.length = %s", chunk.data.length);
1033 
1034     if (!istrm.readBE!uint(&chunk.crc))
1035     {
1036         return err("loadPNG error: failed to read chunk CRC, invalid PNG stream");
1037     }
1038 
1039     uint calculatedCRC = crc32(chain(chunk.type[0..$], chunk.data));
1040 
1041     version(PNGDebug)
1042     {
1043         writefln("Chunk CRC = %X", chunk.crc);
1044         writefln("Calculated CRC = %X", calculatedCRC);
1045     }
1046 
1047     if (chunk.crc != calculatedCRC)
1048     {
1049         return err("loadPNG error: chunk CRC check failed");
1050     }
1051 
1052     return suc();
1053 }
1054 
1055 Compound!(bool, string) readIHDR(
1056     PNGImage* png,
1057     PNGChunk* chunk)
1058 {
1059     PNGHeader* hdr = &png.hdr;
1060 
1061     if (chunk.data.length < hdr.bytes.length)
1062         return err("loadPNG error: illegal header chunk");
1063 
1064     hdr.bytes[] = chunk.data[0..hdr.bytes.length];
1065     hdr.width = bigEndian(hdr.width);
1066     hdr.height = bigEndian(hdr.height);
1067 
1068     version(PNGDebug)
1069     {
1070         writefln("width = %s", hdr.width);
1071         writefln("height = %s", hdr.height);
1072         writefln("bitDepth = %s", hdr.bitDepth);
1073         writefln("colorType = %s", hdr.colorType);
1074         writefln("compressionMethod = %s", hdr.compressionMethod);
1075         writefln("filterMethod = %s", hdr.filterMethod);
1076         writefln("interlaceMethod = %s", hdr.interlaceMethod);
1077     }
1078 
1079     bool supportedIndexed =
1080         (hdr.colorType == ColorType.Palette) &&
1081         (hdr.bitDepth == 1 ||
1082          hdr.bitDepth == 2 ||
1083          hdr.bitDepth == 4 ||
1084          hdr.bitDepth == 8);
1085 
1086     if (hdr.bitDepth != 8 && hdr.bitDepth != 16 && !supportedIndexed)
1087         return err("loadPNG error: unsupported bit depth");
1088 
1089     if (hdr.compressionMethod != 0)
1090         return err("loadPNG error: unsupported compression method");
1091 
1092     if (hdr.filterMethod != 0)
1093         return err("loadPNG error: unsupported filter method");
1094 
1095     if (hdr.interlaceMethod != 0)
1096         return err("loadPNG error: interlacing is not supported");
1097 
1098     if (hdr.colorType == ColorType.Greyscale)
1099         png.numChannels = 1;
1100     else if (hdr.colorType == ColorType.GreyscaleAlpha)
1101         png.numChannels = 2;
1102     else if (hdr.colorType == ColorType.RGB)
1103         png.numChannels = 3;
1104     else if (hdr.colorType == ColorType.RGBA)
1105         png.numChannels = 4;
1106     else if (hdr.colorType == ColorType.Palette)
1107     {
1108         if (png.transparency.length > 0)
1109             png.numChannels = 4;
1110         else
1111             png.numChannels = 3;
1112     }
1113     else
1114         return err("loadPNG error: unsupported color type");
1115 
1116     if (hdr.colorType == ColorType.Palette)
1117         png.bitDepth = 8;
1118     else
1119         png.bitDepth = hdr.bitDepth;
1120 
1121     png.bytesPerChannel = png.bitDepth / 8;
1122 
1123     version(PNGDebug)
1124     {
1125         writefln("bytesPerChannel = %s", png.bytesPerChannel);
1126     }
1127 
1128     return suc();
1129 }
1130 
1131 Compound!(bool, string) fillFrame(PNGImage* png)
1132 {
1133     ubyte[] decodedBuffer = png.decoder.buffer;
1134     version(PNGDebug) writefln("decodedBuffer.length = %s", decodedBuffer.length);
1135 
1136     bool indexed = (png.hdr.colorType == ColorType.Palette);
1137 
1138     uint calculatedSize;
1139     if (indexed)
1140         calculatedSize = (png.frame.width * png.frame.height * png.hdr.bitDepth) / 8 + png.frame.height;
1141     else
1142         calculatedSize = png.frame.width * png.frame.height * (png.hdr.bitDepth / 8) * png.numChannels + png.frame.height;
1143 
1144     png.frameSize = png.frame.width * png.frame.height * png.numChannels * png.bytesPerChannel;
1145     png.filteredBufferSize = calculatedSize - png.frame.height;
1146 
1147     version(PNGDebug)
1148     {
1149         writefln("calculatedSize = %s", calculatedSize);
1150         writefln("frameSize = %s", png.frameSize);
1151         writefln("filteredBufferSize = %s", png.filteredBufferSize);
1152 
1153         writefln("png.frameBuffer.length = %s", png.frameBuffer.length);
1154     }
1155 
1156     ubyte[] pdata = png.frameBuffer[0..png.frameSize];
1157     ubyte[] filteredBuffer = png.filteredBuffer[0..png.filteredBufferSize];
1158 
1159     if (decodedBuffer.length != calculatedSize)
1160     {
1161         return err("loadPNG error: image size and data mismatch");
1162     }
1163 
1164     // apply filtering to the image data
1165     auto res = filter(png, indexed, decodedBuffer, filteredBuffer);
1166     if (!res[0])
1167     {
1168         return err(res[1]);
1169     }
1170 
1171     // if a palette is used, substitute target colors
1172     if (indexed)
1173     {
1174         if (png.palette.length == 0)
1175             return err("loadPNG error: palette chunk not found");
1176 
1177         if (png.hdr.bitDepth == 8)
1178         {
1179             for (int i = 0; i < filteredBuffer.length; ++i)
1180             {
1181                 ubyte b = filteredBuffer[i];
1182                 pdata[i * png.numChannels + 0] = png.palette[b * 3 + 0];
1183                 pdata[i * png.numChannels + 1] = png.palette[b * 3 + 1];
1184                 pdata[i * png.numChannels + 2] = png.palette[b * 3 + 2];
1185                 if (png.transparency.length > 0)
1186                     pdata[i * png.numChannels + 3] =
1187                         b < png.transparency.length ? png.transparency[b] : 255;
1188             }
1189         }
1190         else // bit depths 1, 2, 4
1191         {
1192             int srcindex = 0;
1193             int srcshift = 8 - png.hdr.bitDepth;
1194             ubyte mask = cast(ubyte)((1 << png.hdr.bitDepth) - 1);
1195             int sz = png.frame.width * png.frame.height;
1196             for (int dstindex = 0; dstindex < sz; dstindex++)
1197             {
1198                 auto b = ((filteredBuffer[srcindex] >> srcshift) & mask);
1199                 //assert(b * 3 + 2 < palette.length);
1200                 pdata[dstindex * png.numChannels + 0] = png.palette[b * 3 + 0];
1201                 pdata[dstindex * png.numChannels + 1] = png.palette[b * 3 + 1];
1202                 pdata[dstindex * png.numChannels + 2] = png.palette[b * 3 + 2];
1203 
1204                 if (png.transparency.length > 0)
1205                     pdata[dstindex * png.numChannels + 3] =
1206                         b < png.transparency.length ? png.transparency[b] : 255;
1207 
1208                 if (srcshift <= 0)
1209                 {
1210                     srcshift = 8 - png.hdr.bitDepth;
1211                     srcindex++;
1212                 }
1213                 else
1214                 {
1215                     srcshift -= png.hdr.bitDepth;
1216                 }
1217             }
1218         }
1219     }
1220     else
1221     {
1222         pdata[] = filteredBuffer[];
1223     }
1224 
1225     return suc();
1226 }
1227 
1228 void blitFrame(
1229     PNGImage* png,
1230     ubyte[] frameBuffer,
1231     SuperImage img)
1232 {
1233     for(uint y = 0; y < png.frame.height; y++)
1234     {
1235         for(uint x = 0; x < png.frame.width; x++)
1236         {
1237             Color4f c1 = img[png.frame.x + x, png.frame.y + y];
1238             Color4f c2 = getColor(png, frameBuffer, x, y);
1239 
1240             if (png.frame.blendOp == BlendOp.Source)
1241                 img[png.frame.x + x, png.frame.y + y] = c2;
1242             else
1243                 img[png.frame.x + x, png.frame.y + y] = alphaOver(c1, c2);
1244         }
1245     }
1246 }
1247 
1248 void disposeFrame(
1249     PNGImage* png,
1250     SuperImage prevImg,
1251     SuperImage img,
1252     bool firstFrame)
1253 {
1254     if (png.frame.disposeOp != DisposeOp.None)
1255     for(uint y = 0; y < png.hdr.height; y++)
1256     {
1257         for(uint x = 0; x < png.hdr.width; x++)
1258         {
1259             if (png.frame.disposeOp == DisposeOp.Previous && !firstFrame)
1260                 img[x, y] = prevImg[x, y];
1261             else
1262                 img[x, y] = Color4f(0, 0, 0, 0);
1263         }
1264     }
1265 }
1266 
1267 Color4f getColor(
1268     PNGImage* png,
1269     ubyte[] pixData,
1270     uint x,
1271     uint y)
1272 {
1273     uint bitDepth = png.bitDepth;
1274     uint channels = png.numChannels;
1275     uint pixelSize = png.bytesPerChannel * channels;
1276     uint index = (y * png.frame.width + x) * pixelSize;
1277     uint maxv = (2 ^^ bitDepth) - 1;
1278 
1279     Color4 res = Color4(0, 0, 0, 0);
1280 
1281     if (channels == 1 && bitDepth == 8)
1282     {
1283         auto v = pixData[index];
1284         res = Color4(v, v, v);
1285     }
1286     else if (channels == 2 && bitDepth == 8)
1287     {
1288         auto v = pixData[index];
1289         res = Color4(v, v, v, pixData[index+1]);
1290     }
1291     else if (channels == 3 && bitDepth == 8)
1292     {
1293         res = Color4(pixData[index], pixData[index+1], pixData[index+2], cast(ubyte)maxv);
1294     }
1295     else if (channels == 4 && bitDepth == 8)
1296     {
1297         res = Color4(pixData[index], pixData[index+1], pixData[index+2], pixData[index+3]);
1298     }
1299     else if (channels == 1 && bitDepth == 16)
1300     {
1301         ushort v = pixData[index] << 8 | pixData[index+1];
1302         res = Color4(v, v, v);
1303     }
1304     else if (channels == 2 && bitDepth == 16)
1305     {
1306         ushort v = pixData[index]   << 8 | pixData[index+1];
1307         ushort a = pixData[index+2] << 8 | pixData[index+3];
1308         res = Color4(v, v, v, a);
1309     }
1310     else if (channels == 3 && bitDepth == 16)
1311     {
1312         ushort r = pixData[index]   << 8 | pixData[index+1];
1313         ushort g = pixData[index+2] << 8 | pixData[index+3];
1314         ushort b = pixData[index+4] << 8 | pixData[index+5];
1315         ushort a = cast(ushort)maxv;
1316         res = Color4(r, g, b, a);
1317     }
1318     else if (channels == 4 && bitDepth == 16)
1319     {
1320         ushort r = pixData[index]   << 8 | pixData[index+1];
1321         ushort g = pixData[index+2] << 8 | pixData[index+3];
1322         ushort b = pixData[index+4] << 8 | pixData[index+5];
1323         ushort a = pixData[index+6] << 8 | pixData[index+7];
1324         res = Color4(r, g, b, a);
1325     }
1326     else
1327         assert(0);
1328 
1329     return Color4f(res, bitDepth);
1330 }
1331 
1332 /*
1333  * Performs the paeth PNG filter from pixels values:
1334  *   a = back
1335  *   b = up
1336  *   c = up and back
1337  */
1338 pure ubyte paeth(ubyte a, ubyte b, ubyte c)
1339 {
1340     int p = a + b - c;
1341     int pa = std.math.abs(p - a);
1342     int pb = std.math.abs(p - b);
1343     int pc = std.math.abs(p - c);
1344     if (pa <= pb && pa <= pc) return a;
1345     else if (pb <= pc) return b;
1346     else return c;
1347 }
1348 
1349 Compound!(bool, string) filter(
1350       PNGImage* png,
1351       bool indexed,
1352       ubyte[] ibuffer,
1353       ubyte[] obuffer)
1354 {
1355     uint width = png.frame.width;
1356     uint height = png.frame.height;
1357     uint channels = png.numChannels;
1358 
1359     uint bytesPerPixel = png.hdr.bitDepth / 8; // 1 for 8bit, 2 for 16bit
1360 
1361     uint scanlineSize;
1362     if (indexed)
1363         scanlineSize = (width * png.hdr.bitDepth) / 8 + 1;
1364     else
1365         scanlineSize = width * bytesPerPixel * channels + 1;
1366 
1367     ubyte pback, pup, pupback, cbyte;
1368 
1369     for (int i = 0; i < height; ++i)
1370     {
1371         pback = 0;
1372 
1373         // get the first byte of a scanline
1374         ubyte scanFilter = ibuffer[i * scanlineSize];
1375 
1376         if (indexed)
1377         {
1378             width = scanlineSize - 1;
1379             for (int j = 0; j < width; ++j)
1380             {
1381                 if (i == 0) pup = 0;
1382                 else pup = obuffer[(i-1) * width + j];
1383                 if (j == 0) pback = 0;
1384                 else pback = obuffer[i * width + j-1];
1385                 if (i == 0 || j == 0) pupback = 0;
1386                 else pupback = obuffer[(i-1) * width + j-1];
1387 
1388                 cbyte = ibuffer[i * scanlineSize + j+1];
1389 
1390                 // filter, then set the current byte in data
1391                 switch (scanFilter)
1392                 {
1393                     case FilterMethod.None:
1394                         obuffer[i * width + j] = cbyte;
1395                         break;
1396                     case FilterMethod.Sub:
1397                         obuffer[i * width + j] = cast(ubyte)(cbyte + pback);
1398                         break;
1399                     case FilterMethod.Up:
1400                         obuffer[i * width + j] = cast(ubyte)(cbyte + pup);
1401                         break;
1402                     case FilterMethod.Average:
1403                         obuffer[i * width + j] = cast(ubyte)(cbyte + (pback + pup) / 2);
1404                         break;
1405                     case FilterMethod.Paeth:
1406                         obuffer[i * width + j] = cast(ubyte)(cbyte + paeth(pback, pup, pupback));
1407                         break;
1408                     default:
1409                         return err(format("loadPNG error: unknown scanline filter (%s)", scanFilter));
1410                 }
1411             }
1412         }
1413         else
1414         {
1415             for (int j = 0; j < width; ++j)
1416             {
1417                 for (int k = 0; k < bytesPerPixel * channels; ++k)
1418                 {
1419                     if (i == 0) pup = 0;
1420                     else pup = obuffer[((i-1) * width + j) * bytesPerPixel * channels + k];
1421                     if (j == 0) pback = 0;
1422                     else pback = obuffer[(i * width + j-1) * bytesPerPixel * channels + k];
1423                     if (i == 0 || j == 0) pupback = 0;
1424                     else pupback = obuffer[((i-1) * width + j-1) * bytesPerPixel * channels + k];
1425 
1426                     // get the current byte from ibuffer
1427                     cbyte = ibuffer[i * (width * bytesPerPixel * channels + 1) + j * bytesPerPixel * channels + k + 1];
1428 
1429                     // filter, then set the current byte in data
1430                     switch (scanFilter)
1431                     {
1432                         case FilterMethod.None:
1433                             obuffer[(i * width + j) * bytesPerPixel * channels + k] = cbyte;
1434                             break;
1435                         case FilterMethod.Sub:
1436                             obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + pback);
1437                             break;
1438                         case FilterMethod.Up:
1439                             obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + pup);
1440                             break;
1441                         case FilterMethod.Average:
1442                             obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + (pback + pup) / 2);
1443                             break;
1444                         case FilterMethod.Paeth:
1445                             obuffer[(i * width + j) * bytesPerPixel * channels + k] = cast(ubyte)(cbyte + paeth(pback, pup, pupback));
1446                             break;
1447                         default:
1448                             return err(format("loadPNG error: unknown scanline filter (%s)", scanFilter));
1449                     }
1450                 }
1451             }
1452         }
1453     }
1454 
1455     return suc();
1456 }
1457 
1458 uint crc32(R)(R range, uint inCrc = 0) if (isInputRange!R)
1459 {
1460     uint[256] generateTable()
1461     {
1462         uint[256] table;
1463         uint crc;
1464         for (int i = 0; i < 256; i++)
1465         {
1466             crc = i;
1467             for (int j = 0; j < 8; j++)
1468                 crc = crc & 1 ? (crc >> 1) ^ 0xEDB88320UL : crc >> 1;
1469             table[i] = crc;
1470         }
1471         return table;
1472     }
1473 
1474     static const uint[256] table = generateTable();
1475 
1476     uint crc;
1477 
1478     crc = inCrc ^ 0xFFFFFFFF;
1479     foreach(v; range)
1480         crc = (crc >> 8) ^ table[(crc ^ v) & 0xFF];
1481 
1482     return (crc ^ 0xFFFFFFFF);
1483 }