1 // Written in the D programming language.
2 
3 /**
4 * Copyright: Copyright 2012 -
5 * License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
6 * Authors: Callum Anderson, Stewart Gordon
7 * Date: July 24, 2012
8 */
9 module imaged.png;
10 
11 import
12     std.file,
13     std.stdio,
14     std.math,
15     std.algorithm,
16     std.conv,
17     std.zlib,
18     undead.stream;
19 
20 import
21     imaged.image;
22 
23 
24 /**
25 * Png decoder.
26 */
27 class PngDecoder : Decoder
28 {
29     enum Chunk
30     {
31         NONE,
32         IHDR, // header
33         IDAT, // image
34         PLTE, // palette
35         IEND // end of image
36     }
37 
38 
39     // Empty constructor, usefule for parsing a stream manually
40     this(in bool logging = false)
41     {
42         m_logging = logging;
43         zliber = new UnCompress();
44     }
45 
46 
47     // Constructor taking a filename
48     this(in string filename, in bool logging = false)
49     {
50         this(logging);
51         parseFile(filename);
52 
53     } // c'tor
54 
55 
56     // Parse one byte
57     override void parseByte(ubyte bite)
58     {
59         segment.buffer ~= bite;
60 
61         if (!m_haveHeader && (segment.buffer.length == 8))
62         {
63             if (segment.buffer[0..8] == [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])
64             {
65                 // File has correct header
66                 segment.buffer.destroy;
67                 m_pendingChunk = true;
68                 m_haveHeader = true;
69             }
70             else
71             {
72                 // Not a valid png
73                 m_errorState.code = 1;
74                 m_errorState.message = "Header does not match PNG type!";
75                 return;
76             }
77         }
78 
79         if (m_pendingChunk && (segment.buffer.length == 8))
80         {
81             m_pendingChunk = false;
82 
83             segment.chunkLength = fourBytesToInt(segment.buffer[0..4]);
84             char[] type = cast(char[])segment.buffer[4..8];
85 
86             if (type == "IHDR")
87             {
88                 segment.chunkType = Chunk.IHDR;
89             }
90             else if (type == "IDAT")
91             {
92                 segment.chunkType = Chunk.IDAT;
93             }
94             else if (type == "PLTE")
95             {
96                 segment.chunkType = Chunk.PLTE;
97             }
98             else if (type == "IEND")
99             {
100                 segment.chunkType = Chunk.IEND;
101             }
102         }
103 
104         if (segment.chunkType != Chunk.IDAT)
105         {
106             if (m_haveHeader && !m_pendingChunk && (segment.buffer.length == segment.chunkLength + 8 + 4))
107             {
108                 processChunk();
109                 m_previousChunk = segment.chunkType;
110                 m_pendingChunk = true;
111                 segment = PNGSegment();
112             }
113         }
114         else
115         {
116             if (segment.buffer.length > 8 && segment.buffer.length <= segment.chunkLength + 8)
117             {
118                 uncompressStream([bite]);
119             }
120             else if (segment.buffer.length == segment.chunkLength + 8 + 4)
121             {
122                 processChunk();
123                 m_previousChunk = segment.chunkType;
124                 m_pendingChunk = true;
125                 segment = PNGSegment();
126             }
127         }
128 
129         m_totalBytesParsed ++;
130     } // parseByte
131 
132 
133 private:
134 
135     bool m_haveHeader = false;
136     Chunk m_previousChunk = Chunk.NONE;
137     bool m_pendingChunk = false;
138     uint m_totalBytesParsed;
139     ubyte m_interlacePass = 0;
140 
141     int[7] m_pixPerLine;
142     int[7] m_scanLines;
143 
144     struct InterLace
145     {
146         int imageRow;
147         int[7] start_row =      [ 0, 0, 4, 0, 2, 0, 1 ];
148         int[7] start_col =      [ 0, 4, 0, 2, 0, 1, 0 ];
149         int[7] row_increment =  [ 8, 8, 8, 4, 4, 2, 2 ];
150         int[7] col_increment =  [ 8, 8, 4, 4, 2, 2, 1 ];
151         int[7] block_height =   [ 8, 8, 4, 4, 2, 2, 1 ];
152         int[7] block_width =    [ 8, 4, 4, 2, 2, 1, 1 ];
153     }
154     InterLace m_ilace;
155 
156 
157     struct PNGSegment
158     {
159         Chunk chunkType = Chunk.NONE;
160         int chunkLength;
161         ubyte[] buffer;
162     }
163     PNGSegment segment;
164 
165     ubyte[] scanLine1, scanLine2;
166     int m_currentScanLine;
167     UnCompress zliber;
168     uint checkSum;
169 
170     int m_width, m_height;
171     int m_bitDepth,
172         m_colorType,
173         m_compression,
174         m_filter,
175         m_interlace,
176         m_bytesPerScanline,
177         m_nChannels,
178         m_stride,
179         m_pixelScale;
180 
181     ubyte[] m_palette;
182 
183     /**
184     * Color types are:
185     * Color    Allowed    Interpretation
186     * Type    Bit Depths
187     * 0       1,2,4,8,16  Each pixel is a grayscale sample.
188     * 2       8,16        Each pixel is an R,G,B triple.
189     * 3       1,2,4,8     Each pixel is a palette index; a PLTE chunk must appear.
190     * 4       8,16        Each pixel is a grayscale sample followed by an alpha sample.
191     * 6       8,16        Each pixel is an R,G,B triple, followed by an alpha sample.
192     */
193 
194     // COnvert 4 bytes to an integer
195     int fourBytesToInt(ubyte[] bytes)
196     {
197         return (bytes[0] << 24 | bytes[1] << 16 | bytes[2] <<  8 | bytes[3]);
198     }
199 
200 
201     void processChunk()
202     {
203         /**
204         * Remeber - first 8 bytes in the segment.buffer are length (4byte) and type (4byte)
205         * So chunk data begins at index 8 of the buffer. We keep this stuff for calculating
206         * the checksum (it actually only uses the chunk data and the type field).
207         */
208 
209         debug
210         {
211             if (m_logging) writeln("PNG ProcessChunk: Processing " ~ to!string(segment.chunkType));
212         }
213 
214         // Compare checksums, but let chunk types determine how to handle failed checks
215         bool csum_passed = true;
216         uint csum_calc = crc32(0, segment.buffer[4..$-4]);
217         uint csum_read = fourBytesToInt(segment.buffer[$-4..$]);
218         if (csum_calc != csum_read)
219         {
220             csum_passed = false;
221             debug
222             {
223                 if (m_logging) writeln("PNG: Error - checksum failed!");
224             }
225         }
226 
227         switch(segment.chunkType)
228         {
229         case Chunk.IHDR: // IHDR chunk contains height, width info
230         {
231             if (!csum_passed)
232             {
233                 errorState.code = 1;
234                 errorState.message = "PNG: Checksum failed in IHDR!";
235                 return;
236             }
237 
238             m_width = fourBytesToInt(segment.buffer[8..12]);
239             m_height = fourBytesToInt(segment.buffer[12..16]);
240             m_bitDepth = segment.buffer[16];
241             m_colorType = segment.buffer[17];
242             m_compression = segment.buffer[18];
243             m_filter = segment.buffer[19];
244             m_interlace = segment.buffer[20];
245             m_pixelScale = 1;
246 
247             // This function sets some state, like m_nChannels
248             allocateImage();
249 
250             // Calculate pixels per line and scanlines per pass for interlacing
251             if (m_interlace == 1)
252             {
253                 setInterlace();
254             }
255 
256             auto bitStride = m_nChannels*m_bitDepth;
257             auto imageBitsPerLine = m_width*bitStride;
258 
259             m_bytesPerScanline = 1 + imageBitsPerLine/8;
260             m_stride = bitStride/8;
261 
262             if (imageBitsPerLine % 8 > 0)
263             {
264                 m_bytesPerScanline ++;
265                 m_stride = 1;
266             }
267 
268             if (m_stride == 0)
269             {
270                 m_stride = 1;
271             }
272 
273             // Initialize this scanLine, since it will be empty initially
274             scanLine1 = new ubyte[](m_bytesPerScanline);
275 
276             debug
277             {
278                 if (m_logging)
279                 {
280                     writefln("PNG\n Width: %s\nHeight: %s\nBitDepth: %s\nColorType: %s\n" ~ 
281                              "Compression: %s\nFilter: %s\nInterlacing: %s\nStride: %s",
282                              m_width, m_height, m_bitDepth, m_colorType,
283                              m_compression, m_filter, m_interlace, m_stride);
284                 }
285             }
286             break;
287         }
288         case Chunk.IDAT: // Actual image data
289         {
290             if (!csum_passed)
291             {
292                 errorState.code = 1;
293                 errorState.message = "PNG: Checksum failed in IDAT!";
294                 return;
295             }
296             break;
297         }
298         case Chunk.PLTE: // Palette chunk
299         {
300             if (!csum_passed)
301             {
302                 errorState.code = 1;
303                 errorState.message = "PNG: Checksum failed in IPLTE!";
304                 return;
305             }
306 
307             if (m_colorType == 3)
308                 m_palette = segment.buffer[8..$-4].dup;
309 
310             break;
311         }
312         case Chunk.IEND: // Image end
313         {
314             // Flush out the rest of the stream
315             uncompressStream(scanLine2, true);
316             break;
317         }
318 
319         default:
320         {
321             debug
322             {
323                 if (m_logging) writeln("PNG ProcessChunk: Un-handled chunk " ~ to!string(segment.chunkType));
324             }
325             break;
326         }
327         }
328     } // processChunk
329 
330 
331     // Allocate the image
332     void allocateImage()
333     {
334         switch (m_colorType)
335         {
336         case 0:   // greyscale
337         {
338             m_nChannels = 1;
339             switch(m_bitDepth)
340             {
341             case 1:
342                 m_image = new Img!(Px.L8)(m_width, m_height);
343                 m_pixelScale = 255;
344                 break;
345             case 2:
346                 m_image = new Img!(Px.L8)(m_width, m_height);
347                 m_pixelScale = 64;
348                 break;
349             case 4:
350                 m_image = new Img!(Px.L8)(m_width, m_height);
351                 m_pixelScale = 16;
352                 break;
353             case 8:
354                 m_image = new Img!(Px.L8)(m_width, m_height);
355                 break;
356             case 16:
357                 m_image = new Img!(Px.L16)(m_width, m_height);
358                 break;
359             default:
360                 m_errorState.code = 1;
361                 m_errorState.message = "PNG: Greyscale image with incorrect bit depth detected";
362             }
363             break;
364         }
365         case 2:   // rgb
366         {
367             m_nChannels = 3;
368             switch(m_bitDepth)
369             {
370             case 8:
371                 m_image = new Img!(Px.R8G8B8)(m_width, m_height);
372                 break;
373             case 16:
374                 m_image = new Img!(Px.R16G16B16)(m_width, m_height);
375                 break;
376             default:
377                 m_errorState.code = 1;
378                 m_errorState.message = "PNG: RGB image with incorrect bit depth detected";
379             }
380             break;
381         }
382         case 3:   // palette
383         {
384             m_nChannels = 1;
385             m_image = new Img!(Px.R8G8B8)(m_width, m_height);
386             break;
387         }
388         case 4:   // greyscale + alpha
389         {
390             m_nChannels = 2;
391             switch(m_bitDepth)
392             {
393             case 8:
394                 m_image = new Img!(Px.L8A8)(m_width, m_height);
395                 break;
396             case 16:
397                 m_image = new Img!(Px.L16A16)(m_width, m_height);
398                 break;
399             default:
400                 m_errorState.code = 1;
401                 m_errorState.message = "PNG: Greysca;+alpha with incorrect bit depth detected";
402             }
403             break;
404         }
405         case 6:   // rgba
406         {
407             m_nChannels = 4;
408             switch(m_bitDepth)
409             {
410             case 8:
411                 m_image = new Img!(Px.R8G8B8A8)(m_width, m_height);
412                 break;
413             case 16:
414                 m_image = new Img!(Px.R16G16B16A16)(m_width, m_height);
415                 break;
416             default:
417                 m_errorState.code = 1;
418                 m_errorState.message = "PNG: RGBA image with incorrect bit depth detected";
419             }
420             break;
421         }
422         default: // error
423             m_errorState.code = 1;
424             m_errorState.message = "PNG: Incorrect color type detected";
425         }
426     } // allocateImage
427 
428 
429     // Set some state dealing with interlacing
430     void setInterlace()
431     {
432         foreach(i; 0..m_width)
433         {
434             if (i % 8 == 0) m_pixPerLine[0] ++;
435             if (i % 8 == 4) m_pixPerLine[1] ++;
436             if (i % 8 == 0 ||
437                 i % 8 == 4) m_pixPerLine[2] ++;
438             if (i % 8 == 2 ||
439                 i % 8 == 6) m_pixPerLine[3] ++;
440             if (i % 8 == 0 ||
441                 i % 8 == 2 ||
442                 i % 8 == 4 ||
443                 i % 8 == 6 ) m_pixPerLine[4] ++;
444             if (i % 8 == 1 ||
445                 i % 8 == 3 ||
446                 i % 8 == 5 ||
447                 i % 8 == 7 ) m_pixPerLine[5] ++;
448         }
449         m_pixPerLine[6] = m_width;
450 
451         foreach(i; 0..m_height)
452         {
453             if (i % 8 == 0) m_scanLines[0] ++;
454             if (i % 8 == 0) m_scanLines[1] ++;
455             if (i % 8 == 4) m_scanLines[2] ++;
456             if (i % 8 == 0 ||
457                 i % 8 == 4) m_scanLines[3] ++;
458             if (i % 8 == 2 ||
459                 i % 8 == 6 ) m_scanLines[4] ++;
460             if (i % 8 == 0 ||
461                 i % 8 == 2 ||
462                 i % 8 == 4 ||
463                 i % 8 == 6 ) m_scanLines[5] ++;
464             if (i % 8 == 1 ||
465                 i % 8 == 3 ||
466                 i % 8 == 5 ||
467                 i % 8 == 7 ) m_scanLines[6] ++;
468         }
469     } // setInterlace
470 
471 
472     // Uncompress the stream, apply filters, and store image
473     void uncompressStream(ubyte[] stream, bool finalize = false)
474     {
475         if (m_currentScanLine >= m_height || m_interlacePass >= 7)
476             return;
477 
478         ubyte[] data;
479         if (!finalize)
480         {
481             data = cast(ubyte[])(zliber.uncompress(cast(void[])stream));
482         }
483         else
484         {   // finalize means flush out any remaining data
485             data = cast(ubyte[])(zliber.flush());
486         }
487 
488         // Number of bytes in a scanline depends on the interlace pass
489         int bytesPerLine, nscanLines;
490         passInfo(bytesPerLine, nscanLines);
491 
492         int taken = 0, takeLen = 0; // bytes taken, bytes to take
493         while (taken < data.length)
494         {
495             // Put data into the lower scanline first
496             takeLen = cast(int) (bytesPerLine - scanLine2.length);
497             if (takeLen > 0 && taken + takeLen <= data.length)
498             {
499                 scanLine2 ~= data[taken..taken+takeLen];
500                 taken += takeLen;
501             }
502             else if (takeLen > 0)
503             {
504                 scanLine2 ~= data[taken..$];
505                 taken += data.length - taken;
506             }
507 
508             if (scanLine2.length == bytesPerLine)
509             {
510                 // Have a full scanline, so filter it...
511                 filter();
512 
513                 auto scanLineStride = m_stride;
514 
515                 // Unpack the bits if needed
516                 ubyte[] sLine;
517                 if (m_bitDepth < 8)
518                 {
519                     sLine = unpackBits(scanLine2[1..$]);
520                 }
521                 else
522                 {
523                     sLine = scanLine2[1..$];
524                 }
525 
526                 // For palette colortypes, convert scanline to RGB
527                 if (m_colorType == 3)
528                 {
529                     sLine = convertPaletteToRGB(sLine);
530                     scanLineStride = 3;
531                 }
532 
533                 // Scale the bits up to 0-255
534                 if (m_colorType != 3 && m_bitDepth < 8)
535                 {
536                     sLine[] *= cast(ubyte)m_pixelScale;
537                 }
538 
539                 // Put it into the Image
540                 if (m_interlace == 0)
541                 {
542                     // Non-interlaced
543                     m_image.setRow(m_currentScanLine, sLine);
544                 }
545                 else
546                 {
547                     // Image is interlaced, so fill not just given pixels, but blocks of pixels
548                     with(m_ilace)
549                     {
550                         int pass = m_interlacePass;
551                         int col = start_col[pass]; // Image column
552                         int i = 0; // scanline offset
553 
554                         while (col < m_width)
555                         {
556                             // Max x,y indices to fill up to for a given pass
557                             auto maxY = min(block_height[pass], m_height - imageRow);
558                             auto maxX = min(block_width[pass], m_width - col);
559 
560                             foreach(py; 0..maxY)
561                             {
562                                 foreach(px; 0..maxX)
563                                 {
564                                     m_image.setPixel(col + px, imageRow + py, sLine[i..i+scanLineStride]);
565                                 }
566                             }
567 
568                             col = col + col_increment[pass];
569                             i += scanLineStride;
570 
571                         } // while col < m_width
572                     } // with(m_ilace)
573                 } // if m_interlace
574 
575 
576                 // Increment scanline counter
577                 m_currentScanLine ++;
578 
579                 // Swap the scanlines
580                 auto tmp = scanLine1;
581                 scanLine1 = scanLine2;
582                 scanLine2 = scanLine1;
583                 scanLine2.destroy;
584 
585                 if (m_interlace == 1)
586                 {
587                     m_ilace.imageRow += m_ilace.row_increment[m_interlacePass];
588                 }
589 
590                 if (m_interlace == 1 && m_currentScanLine == nscanLines)
591                 {
592                     m_currentScanLine = 0;
593                     m_interlacePass ++;
594 
595                     if (m_interlacePass == 7 || m_currentScanLine >= m_height)
596                     {
597                         break;
598                     }
599                     else
600                     {
601                         scanLine1 = new ubyte[](m_bytesPerScanline);
602                         m_ilace.imageRow = m_ilace.start_row[m_interlacePass];
603 
604                         // Recalc pass info
605                         passInfo(bytesPerLine, nscanLines);
606                     }
607                 }
608             }
609 
610         } // while (taken < data.length)
611     } // uncompressStream
612 
613 
614     // Calculate some per-pass info
615     void passInfo(out int bytesPerLine, out int nscanLines)
616     {
617         bytesPerLine = 0; // number of bytes in a scanline (dependent on interlace pass)
618         nscanLines = 0; // number of scanlines (also depends on interlace pass)
619 
620         if (m_interlace == 1)
621         {
622             if (m_bitDepth < 8)
623             {
624                 auto bitsPerLine = m_pixPerLine[m_interlacePass]*m_bitDepth;
625                 bytesPerLine = bitsPerLine/8;
626 
627                 if (bitsPerLine % 8 > 0)
628                 {
629                     bytesPerLine ++;
630                 }
631                 bytesPerLine ++; // need to acount for the filter-type byte
632             }
633             else
634             {
635                 bytesPerLine = m_pixPerLine[m_interlacePass]*m_stride + 1;
636             }
637 
638             nscanLines = m_scanLines[m_interlacePass];
639         }
640         else
641         {
642             bytesPerLine = m_bytesPerScanline;
643             nscanLines = m_height;
644         }
645     } // passInfo
646 
647 
648     // Unpack a scanline's worth of bits into a byte array
649     ubyte[] unpackBits(ubyte[] data)
650     {
651         int bytesPerLine = 0;
652         if (m_interlace == 0)
653         {
654             bytesPerLine = m_width;
655         }
656         else
657         {
658             bytesPerLine = m_pixPerLine[m_interlacePass];
659         }
660 
661         ubyte[] unpacked;
662         unpacked.length = bytesPerLine;
663 
664         auto data_index = 0, byte_index = 0;
665         while(byte_index < bytesPerLine)
666         {
667             for(int j = 0; j < 8; j += m_bitDepth)
668             {
669                 auto mask = ((1<<m_bitDepth)-1) << (8 - j - m_bitDepth);
670                 auto val = ((data[data_index] & mask) >> (8 - j - m_bitDepth));
671                 unpacked[byte_index] = cast(ubyte) val;
672                 byte_index ++;
673                 if (byte_index >= bytesPerLine)
674                     break;
675             }
676             data_index ++;
677         }
678 
679         return unpacked;
680     } // unpackBits
681 
682 
683     // Convert a scanline of palette values to a scanline of 8-bit RGB's
684     ubyte[] convertPaletteToRGB(ubyte[] data)
685     {
686         ubyte[] rgb;
687         rgb.length = data.length * 3;
688         foreach(i; 0..data.length)
689         {
690             rgb[i*3] = m_palette[data[i]*3];
691             rgb[i*3 + 1] = m_palette[data[i]*3 + 1];
692             rgb[i*3 + 2] = m_palette[data[i]*3 + 2];
693         }
694         return rgb;
695     } // convertPaletteToRGB
696 
697 
698     // Apply filters to a scanline
699     void filter()
700     {
701         int filterType = scanLine2[0];
702 
703         switch(filterType)
704         {
705         case 0:   // no filtering, excellent
706         {
707             break;
708         }
709         case 1:   // difference filter, using previous pixel on same scanline
710         {
711             filter1();
712             break;
713         }
714         case 2:   // difference filter, using pixel on scanline above, same column
715         {
716             filter2();
717             break;
718         }
719         case 3:   // average filter, average of pixel above and pixel to left
720         {
721             filter3();
722             break;
723         }
724         case 4:   // Paeth filter
725         {
726             filter4();
727             break;
728         }
729         default:
730         {
731             if (m_logging)
732             {
733                 writeln("PNG: Unhandled filter (" ~ to!string(filterType) ~ ") on scan line "
734                         ~ to!string(m_currentScanLine) ~ ", Pass: " ~ to!string(m_interlacePass));
735             }
736             break;
737         }
738         } // switch filterType
739     }
740 
741 
742     // Apply filter 1 to scanline (difference filter)
743     void filter1()
744     {
745         // Can't use vector op because results need to accumulate
746         for(int i=m_stride+1; i<scanLine2.length; i+=m_stride)
747         {
748             scanLine2[i..i+m_stride] += ( scanLine2[i-m_stride..i] );
749         }
750     }
751 
752 
753     // Apply filter 2 to scanline (difference filter, using scanline above)
754     void filter2()
755     {
756         scanLine2[1..$] += scanLine1[1..$];
757     }
758 
759 
760     // Apply filter 3 to scanline (average filter)
761     void filter3()
762     {
763         scanLine2[1..1+m_stride] += cast(ubyte[])(scanLine1[1..1+m_stride] / 2);
764 
765         for(int i=m_stride+1; i<scanLine2.length; i+=m_stride)
766         {
767             scanLine2[i..i+m_stride] += cast(ubyte[])(( scanLine2[i-m_stride..i] +
768                                         scanLine1[i..i+m_stride] ) / 2);
769         }
770     }
771 
772 
773     // Apply filter 4 to scanline (Paeth filter)
774     void filter4()
775     {
776         int paeth(ubyte a, ubyte b, ubyte c)
777         {
778             int p = (a + b - c);
779             int pa = abs(p - a);
780             int pb = abs(p - b);
781             int pc = abs(p - c);
782 
783             int pred = 0;
784             if ((pa <= pb) && (pa <= pc))
785             {
786                 pred = a;
787             }
788             else if (pb <= pc)
789             {
790                 pred = b;
791             }
792             else
793             {
794                 pred = c;
795             }
796             return pred;
797         }
798 
799         foreach(i; 1..m_stride+1)
800         {
801             scanLine2[i] += paeth(0, scanLine1[i], 0);
802         }
803 
804         for(int i=m_stride+1; i<scanLine2.length; i+=m_stride)
805         {
806             foreach(j; 0..m_stride)
807             {
808                 scanLine2[i+j] += paeth(scanLine2[i+j-m_stride], scanLine1[i+j], scanLine1[i+j-m_stride]);
809             }
810         }
811 
812     } // filter4
813 } // PngDecoder
814 
815 
816 /**
817 * PNG encoder for writing out Image classes to files as PNG.
818 * TODO: currently won't work with 16-bit Images.
819 */
820 class PngEncoder : Encoder
821 {
822     /**
823     * Params:
824     * img = the image containing the data to write as a png
825     * filename = filename of the output
826     * Returns: true if writing succeeded, else false.
827     */
828     override bool write(in Image img, string filename)
829     {
830         // The required PNG header
831         ubyte[] outData = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
832 
833         // Add in image header info
834         appendChunk(createHeaderChunk(img), outData);
835 
836         // Filter and compress the data
837         auto rowLengthBytes = img.width*img.stride;
838 
839         PNGChunk idat = PNGChunk("IDAT");
840         ubyte[] imageData;
841         ubyte[] scanLine1, scanLine2;
842         scanLine1.length = rowLengthBytes; // initialize this scanline, since it will be empty to start
843 
844         foreach(row; 0..img.height)
845         {
846             scanLine2 = img.pixels[row*rowLengthBytes..(row+1)*rowLengthBytes].dup;
847 
848             // Apply adaptive filter
849             ubyte filterType; // this will hold the actual filter that was used
850             ubyte[] filtered = filter(img, scanLine1, scanLine2, filterType);
851             imageData ~= filterType ~ filtered;
852 
853             // Swap the scanlines
854             auto tmp = scanLine1;
855             scanLine1 = scanLine2;
856             scanLine2 = scanLine1;
857             scanLine2.destroy;
858         }
859 
860         idat.data = cast(ubyte[]) compress(cast(void[])imageData);
861         appendChunk(idat, outData);
862 
863         // End the image with an IEND
864         appendChunk(PNGChunk("IEND"), outData);
865 
866         // Write the PNG
867         std.file.write(filename, outData);
868 
869         return true;
870     }
871 
872 private:
873 
874     struct PNGChunk
875     {
876         string type;
877         ubyte[] data;
878     }
879 
880     // Array of function pointers containing filter algorithms
881     static ubyte[] function(in Image, in ubyte[], in ubyte[], out uint)[5] m_filters =
882                                 [&PngEncoder.filter0, &PngEncoder.filter1,
883                                  &PngEncoder.filter2, &PngEncoder.filter3,
884                                  &PngEncoder.filter4];
885 
886     /**
887     * Determines which filter type to apply to a given scanline by simply trying all of them,
888     * and calculating the sum of the absolute value in each filtered line. The filter which
889     * gives the minimum absolute sum will be used.
890     * Params:
891     * scanLine1 = the scanline $(I above) the scanline to be filtered
892     * scanLine2 = the scanline to be filtered
893     * filterType = a variable to hold the actual filter type that was used
894     * Returns: the filtered scanline
895     */
896     static ubyte[] filter(in Image img, in ubyte[] scanLine1, ubyte[] scanLine2, out ubyte filterType)
897     {
898         uint sum = 0, minSum = uint.max;
899         ubyte[] filtered = scanLine2.dup;
900 
901         foreach(index, filter; m_filters)
902         {
903             auto s = filter(img, scanLine1, scanLine2, sum);
904             if (sum < minSum)
905             {
906                 filtered = s;
907                 minSum = sum;
908                 filterType = cast(ubyte)index;
909             }
910         }
911         return filtered;
912     }
913 
914     // Filter 0 means no filtering
915     static ubyte[] filter0(in Image img, in ubyte[] scanLine1, in ubyte[] scanLine2, out uint absSum)
916     {
917         ubyte[] filtered = scanLine2.dup;
918         absSum = reduce!("a + abs(b)")(0, filtered);
919         return filtered;
920     }
921 
922     // Filter 1 is a difference between the current pixel and the previous pixl on same scanline
923     static ubyte[] filter1(in Image img, in ubyte[] scanLine1, in ubyte[] scanLine2, out uint absSum)
924     {
925         ubyte[] filtered = scanLine2.dup;
926         uint s = img.stride;
927         absSum = reduce!("a + abs(b)")(0, filtered[0..s]);
928         for(int i = s; i < scanLine2.length; i++)
929         {
930             filtered[i] = cast(ubyte) (scanLine2[i] - scanLine2[i-s]);
931             absSum += abs(filtered[i]);
932         }
933         return filtered;
934     }
935 
936     // FIlter 2 is a difference between current pixel and same pixel on scanline above
937     static ubyte[] filter2(in Image img, in ubyte[] scanLine1, in ubyte[] scanLine2, out uint absSum)
938     {
939         ubyte[] filtered = scanLine2.dup;
940         filtered[] = scanLine2[] - scanLine1[];
941         absSum = reduce!("a + abs(b)")(0, filtered);
942         return filtered;
943     }
944 
945     // Filter 3 is an average of previous pixel on same scanline ,and same pixel on line above
946     static ubyte[] filter3(in Image img, in ubyte[] scanLine1, in ubyte[] scanLine2, out uint absSum)
947     {
948         ubyte[] filtered = scanLine2.dup;
949 
950         for(int i = 0; i < img.stride; i++)
951         {
952             filtered[i] = cast(ubyte) (scanLine2[i] - (scanLine1[i]/2) );
953             absSum += abs(filtered[i]);
954         }
955 
956         for(int i = img.stride; i < scanLine2.length; i++)
957         {
958             filtered[i] = cast(ubyte) (scanLine2[i] - (scanLine1[i]+scanLine2[i-img.stride])/2);
959             absSum += abs(filtered[i]);
960         }
961         return filtered;
962     }
963 
964     // Paeth filter
965     static ubyte[] filter4(in Image img, in ubyte[] scanLine1, in ubyte[] scanLine2, out uint absSum)
966     {
967         int paeth(ubyte a, ubyte b, ubyte c)
968         {
969             int p = (a + b - c);
970             int pa = abs(p - a);
971             int pb = abs(p - b);
972             int pc = abs(p - c);
973 
974             int pred = 0;
975             if ((pa <= pb) && (pa <= pc))
976             {
977                 pred = a;
978             }
979             else if (pb <= pc)
980             {
981                 pred = b;
982             }
983             else
984             {
985                 pred = c;
986             }
987             return pred;
988         }
989 
990         ubyte[] filtered = scanLine2.dup;
991 
992         for(int i = 0; i < img.stride; i++)
993         {
994             filtered[i] = cast(ubyte) (scanLine2[i] - paeth(0, scanLine1[i], 0) );
995             absSum += abs(filtered[i]);
996         }
997 
998         for(int i = img.stride; i < scanLine2.length; i++)
999         {
1000             filtered[i] = cast(ubyte) (scanLine2[i] - paeth(scanLine2[i-img.stride],
1001                                                             scanLine1[i],
1002                                                             scanLine1[i-img.stride]) );
1003             absSum += abs(filtered[i]);
1004         }
1005         return filtered;
1006     }
1007 
1008     // Create the header chunk
1009     PNGChunk createHeaderChunk(in Image img)
1010     {
1011         PNGChunk ihdr;
1012         ihdr.type = "IHDR";
1013         ihdr.data.length = 13;
1014         ihdr.data[0..4] = bigEndianBytes(img.width);
1015         ihdr.data[4..8] = bigEndianBytes(img.height);
1016         ihdr.data[8] = cast(ubyte)img.bitDepth;
1017         ihdr.data[9] = getColorType(img);
1018         ihdr.data[10..12] = [0,0];
1019         ihdr.data[12] = 0; // non-interlaced
1020         return ihdr;
1021     }
1022 
1023     // Append a chunk to the output data, fixing up endianness and calculating checksum
1024     void appendChunk(in PNGChunk chunk, ref ubyte[] data)
1025     {
1026         assert(chunk.type.length == 4);
1027 
1028         ubyte[] typeAndData = cast(ubyte[])chunk.type ~ chunk.data;
1029         uint checksum = crc32(0, typeAndData);
1030 
1031         data ~= bigEndianBytes(cast(uint)chunk.data.length) ~
1032                 typeAndData ~
1033                 bigEndianBytes(checksum);
1034     }
1035 
1036     // Figure out the PNG colortype of an image
1037     ubyte getColorType(in Image img)
1038     {
1039         ubyte colorType;
1040         if (img.pixelFormat == Px.L8 ||
1041             img.pixelFormat == Px.L16 )
1042         {
1043             colorType = 0;
1044         }
1045         else if (img.pixelFormat == Px.L8A8 ||
1046                  img.pixelFormat == Px.L16A16 )
1047         {
1048             colorType = 4;
1049         }
1050         else if (img.pixelFormat == Px.R8G8B8 ||
1051                  img.pixelFormat == Px.R16G16B16 )
1052         {
1053             colorType = 2;
1054         }
1055         else if (img.pixelFormat == Px.R8G8B8A8 ||
1056                  img.pixelFormat == Px.R16G16B16A16 )
1057         {
1058             colorType = 6;
1059         }
1060         else
1061         {
1062             assert(0);
1063         }
1064         return colorType;
1065     }
1066 
1067     // Convert a uint to corrct endianness for writing (must be in big endian/network order for writing)
1068     version (LittleEndian)
1069     {
1070         uint bigEndian(uint value)
1071         {
1072             return (value << 24) | ((value & 0x0000FF00) << 8)
1073                    | ((value & 0x00FF0000) >> 8) | (value >> 24);
1074         }
1075     }
1076     else
1077     {
1078         uint bigEndian(uint value) { return value; }
1079     }
1080 
1081     ubyte[] bigEndianBytes(uint value)
1082     {
1083         uint[] be = [bigEndian(value)];
1084         return cast(ubyte[])be;
1085     }
1086 
1087 } // PngEncoder
1088 
1089