root/trunk/lodepng/lodepng/Decode.d

Revision 256, 28.8 kB (checked in by Lutger, 9 months ago)

bugfixes, added png2bin.d example

Line 
1 // written in the D programming language
2
3 /***************************************************************************************************
4 License:
5 Copyright (c) 2005-2007 Lode Vandevenne
6 All rights reserved.
7
8 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
9
10   - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.<br>
11   - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.<br>
12   - Neither the name of Lode Vandevenne nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission.<br>
13
14 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
16 Authors: Lode Vandevenne (original version in C++), Lutger Blijdestijn (D version) : lutger dot blijdestijn at gmail dot com.
17
18 About:
19 The decoder is small but sufficient for most purposes. It is compliant to the png specification and
20 has been tested with the png suite. To decode images, only <i>decode</i> is needed. The <i>decode32</i> function is for convenience,
21 it can decode and convert to the common 32-bit RGBA format in one go.
22 The rest of the api exposes the low-level functionality of lodepng, which is made available in order to use this library
23 for png-editing purposes.
24 <br>
25 This module publicly imports lodepng.Common, where you'll find the data types used by both the encoder
26 and decoder, as well as some utility and image format conversion routines.
27
28 Date: Januari 16, 2008
29
30 Examples:
31 Here is an example how you could use LodePNG with opengl, see the api documentation for details.
32 ---
33 uint loadPNG(char[] filename)
34 {
35     uint textureID;
36
37     glEnable(GL_TEXTURE_2D);
38     glGenTextures(1, &textureID);
39     glBindTexture(GL_TEXTURE_2D, textureID);
40     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
41     glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
42
43     PngInfo info;
44     ubyte[] image = decode32(cast(ubyte[])std.file.read(filename), info);
45
46     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, info.image.width, info.image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
47                         image.ptr);
48     return textureID;
49 }
50 ---
51
52 Features:
53 The following features are supported by the decoder:
54 <ul>
55     <li> conformant decoding of PNGs (all color types, bit depth, interlace mode, CRC checking, etc.)</li>
56     <li> support for translucent PNG's, including translucent palettes and color key</li>
57     <li> textual key-value meta-data</li>
58     <li> the following chunks are interpreted by the decoder
59         <ul>
60             <li>IHDR (image information)</li>
61             <li>PLTE (color palette)</li>
62             <li>IDAT (pixel data)</li>
63             <li>IEND (the final chunk)</li>
64             <li>tRNS (transparency for palettized images)</li>
65             <li>bKGD (suggested background color)</li>
66             <li>tEXt (uncompressed latin-1 key-value strings)</li>
67             <li>zTXt (compressed latin-1 key-value strings)</li>
68             <li>iTXt (utf8 key-value strings)</li>
69         </ul>
70    </li>
71 </UL>
72
73 Limitations:
74 The following features are not supported.
75 <ul>
76     <li> Streaming / progressive display. All data must be available and is processed in one call.</li>
77     <li> The following optional chunk types are not interpreted by the decoder
78         <ul>
79             <li>cHRM (device independent color info)
80             <li>gAMA (device independent color info)
81             <li>iCCP (device independent color info)
82             <li>sBIT (original number of significant bits)
83             <li>sRGB (device independent color info)
84             <li>pHYs (physical pixel dimensions)
85             <li>sPLT (suggested reduced palette)
86             <li>tIME (last image modification time)
87         </ul>
88     </li>
89 </ul>
90
91
92 References:
93 $(LINK2 http://members.gamedev.net/lode/projects/LodePNG/, Original lodepng) <br>
94 $(LINK2 http://www.w3.org/TR/PNG/, PNG Specification) <br>
95 $(LINK2 http://www.libpng.org/pub/png/pngsuite.html, PNG Suite: set of test images) <br>
96 $(LINK2 http://optipng.sourceforge.net/, OptiPNG: tool to experimentally optimize png images)
97 */
98
99 module lodepng.Decode;
100 import lodepng.ZlibCodec;
101 import lodepng.util;
102 import std.intrinsic;
103
104
105 version (Tango)
106 {
107     import czlib = tango.io.compress.c.zlib;
108 }
109 else
110 {
111     import czlib = etc.c.zlib;
112 }
113
114 public import lodepng.Common;
115
116
117 /** Decode source png file
118
119     If a buffer is provided, it may be used to store the result. See bufferSize for details.
120
121     Throws: PngException
122
123     Returns: Decoded image pixels. The color format of the resulting image is the
124     same as the source image, see lodepng.Common.convert and decode32 if a specific color format
125     is desired.
126
127     Params:
128       source = a png file
129       info = information about the image will be stored in here
130       buffer = optionally provide an array to use as a buffer while decoding
131       dg = optionally provide a delegate that will be called for (and only for) unknown chunks
132 */
133 ubyte[] decode(in ubyte[] source, ref PngInfo info, ubyte[] buffer = null, int delegate(inout Chunk) dg = null)
134 {
135     info.image = source.readHeader();
136     scope decompressor = new PngDecoder(info.image);
137
138     foreach(chunk; StreamChunkIter(source[HEADER_SIZE - 1 .. $]))
139     {
140         if  (chunk.type == IDAT)
141             decompressor(chunk.data);
142         else if (!parseChunk(chunk, info))
143             if (dg !is null)
144                 if (dg(chunk) != 0)
145                     return null;
146
147     }
148     assert(decompressor.ended);
149
150     return decompressor.reconstructImage()[0..info.image.width * bytesPerPixel(info.image) * info.image.height];
151 }
152
153 /***************************************************************************************************
154     Decode source png file to 32-bit RGBA format
155
156     Throws: PngException
157
158     Returns: decoded image pixels in 32-bit RGBA format
159
160     Params:
161           source = a png file
162           info = information about the image will be stored in here
163           buffer = optionally provide an array to use as a buffer while decoding
164 ***************************************************************************************************/
165 ubyte[] decode32(/+const+/ in ubyte[] source, ref PngInfo info, ubyte[] buffer = null)
166 {
167     buffer = decode(source, info, buffer);
168     buffer = convert(buffer, info, ColorType.RGBA);
169     info.image.colorType = ColorType.RGBA;
170     info.image.bpp = 32;
171     info.image.bitDepth = 8;
172     info.palette.length = 0;
173     return buffer[0..info.image.width * bytesPerPixel(info.image) * info.image.height];
174 }
175
176
177 /***************************************************************************************************
178     Parse png image header from memory.
179
180     throws: PngException
181
182     returns: header information
183
184     Params: source=must contain the first 33 bytes of a png file
185 ***************************************************************************************************/
186 PngImage readHeader(/+const+/ in ubyte[] source)
187 in
188 {
189     assert(source.length >= HEADER_SIZE, "array is too small to contain png header");
190 }
191 body // see spec: http://www.w3.org/TR/PNG/#11IHDR
192 {
193     mixin(pngEnforce(`source.length >= HEADER_SIZE`, "png header data is too small"));
194     mixin(pngEnforce(`source[0..8] == [cast(ubyte)137, 80, 78, 71, 13, 10, 26, 10]`, "invalid png header "));
195     mixin(pngEnforce((toUint(source[12..16]) == IHDR).stringof, "invalid png header"));
196     mixin(pngEnforce(`checkCRC(source[29 .. 33], source[12 .. 29])`, "invalid CRC"));
197
198     PngImage result;
199
200
201     with (result)
202     {
203         width = toUint(source[16..20]);
204         height = toUint(source[20..24]);
205         bitDepth = source[24];
206         colorType = cast(ColorType)source[25];
207         mixin(pngEnforce( `source[26] == 0`, "unsupported compression method in png header" ));
208         mixin(pngEnforce( `source[27] == 0`, "unsupported filter method in png header" ));
209         mixin(pngEnforce( `checkColorValidity(colorType, bitDepth)`, "invalid header: wrong color format" ));
210         interlaced = source[28];
211         mixin(pngEnforce( `interlaced < 2`, "invalid  interlace method in png header" ));
212         bpp = numChannels(colorType) * bitDepth;
213     }
214
215     return result;
216 }
217
218 /***************************************************************************************************
219     Iterates through chunks in source, parses only the header
220
221     Throws: PngException
222     Params: source = a png file
223         image = the parsed header
224         dg = will be called for each chunk, return anything other than 0 to stop iterating
225
226 ***************************************************************************************************/
227 void iterateChunks(/+const+/ in ubyte[] source, out PngImage image, int delegate(inout Chunk) dg)
228 {
229     image = source.readHeader();
230
231     foreach(chunk; StreamChunkIter(source[HEADER_SIZE - 1 .. $]))
232         if (dg(chunk) != 0)
233             return;
234 }
235
236 /***************************************************************************************************
237     decode IDAT data
238
239 ***************************************************************************************************/
240 scope class PngDecoder
241 {
242     /** constructor */
243     this(PngImage image)
244     {
245         this(image, buf);
246     }
247
248     /** ditto */
249     this(PngImage image, ref ubyte[] buffer)
250     {
251         img = image;
252         buf = buffer;
253         if (img.interlaced == 1)
254             buf.length = ((img.width * img.bpp + 7) / 8) * img.height + (img.height * 2); // guess
255         else
256             buf.length = ((img.width * img.bpp + 7) / 8) * img.height + img.height;
257         if (img.interlaced == 1)
258             ilaceBuffer.length = buf.length - img.height;
259         decoder = DecodeStream.create(buf);
260     }
261
262     /***************************************************************************************************
263         inflate, call multiple times if there are more than 1 IDAT chunks to be decompressed
264
265         Throws: ZlibException
266         params: data of an IDAT chunk
267     ***************************************************************************************************/
268     void opCall(ref ubyte[] data)
269     {
270         decoder(data);
271         return true;
272     }
273
274     /***************************************************************************************************
275         Whether inflation has completed
276
277     ***************************************************************************************************/
278     bool ended()
279     {
280         return decoder.hasEnded;
281     }
282
283     /***************************************************************************************************
284         Apply reconstruction filters and deinterlace if required
285
286         note that ended() must return true before this function can be called if no data is provided
287
288         params:
289             filtered=optionally provide uncompressed filtered pixels yourself
290     **************************************************************************************************/
291     ubyte[] reconstructImage(ubyte[] filtered = null)
292     in
293     {
294         assert(filtered !is null || ended);
295        
296     }
297     body
298     {
299         buf = filtered is null ? decoder() : filtered;
300         return (img.interlaced == 0) ? reconstruct(buf, img) : deinterlace(buf, ilaceBuffer, img);
301     }
302
303     private
304     {
305         DecodeStream decoder;
306         PngImage img;
307
308         ubyte[] buf;
309         ubyte[] ilaceBuffer;
310     }
311 }
312
313
314 /***************************************************************************************************
315     parse any known chunk except IDAT
316
317     params:
318             chunk=chunk to be parsed
319             info=parsed information will be written to info
320     returns: true if a chunk is parsed, false otherwise
321
322 ***************************************************************************************************/
323 bool parseChunk(/+const+/ ref Chunk chunk, ref PngInfo info)
324 {
325     switch(chunk.type)
326     {
327         case PLTE: parsePLTE(chunk, info);
328             break;
329         case tRNS: parsetRNS(chunk, info);
330             break;
331         case bKGD: parsebKGD(chunk, info);
332             break;
333         case zTXt: parsezTXt(chunk, info);
334             break;
335         case tEXt: parsetTXt(chunk, info);
336             break;
337         case iTXt: parseiTXt(chunk, info);
338             break;
339         default:
340             return false;
341     }
342     return true;
343 }
344
345 /** parse palette chunk */
346 void parsePLTE(/+const+/ ref Chunk chunk, ref PngInfo info)
347 {
348     mixin(pngEnforce(`chunk.data.length <= 256 * 3`, "palette size is too large"));
349     info.palette.length = chunk.data.length / 3;
350     foreach(index, ref ubyte[4] color; info.palette)
351     {
352         color[0..3] = chunk.data[index * 3 .. index * 3 + 3];
353         color[3] = 255;
354     }
355 }
356
357 /** parse transparency chunk */
358 void parsetRNS(/+const+/ ref Chunk chunk, ref PngInfo info)
359 {
360     if (chunk.data.length == 0)
361         return;
362     info.colorKey = true;
363     if (info.image.colorType == ColorType.Palette) // index-values
364     {
365         mixin(pngEnforce(`chunk.data.length <= info.palette.length`, "palette size is too large"));
366         foreach(index, alpha; chunk.data)
367             info.palette[index][3] = alpha;
368     }
369     else if (info.image.colorType == ColorType.RGB)
370     {
371         info.keyR = 256U * chunk.data[0] + chunk.data[1];
372         info.keyG = 256U * chunk.data[2] + chunk.data[3];
373         info.keyB = 256U * chunk.data[4] + chunk.data[5];
374     }
375     else if (info.image.colorType == ColorType.Greyscale)
376     {
377         info.keyR = 256U * chunk.data[0] + chunk.data[1];
378     }
379     else
380     {
381         assert(false);
382     }
383 }
384
385 /** parse background color chunk */
386 void parsebKGD(/+const+/ ref Chunk chunk, ref PngInfo info)
387 {
388     if (info.image.colorType == ColorType.Palette || info.image.bitDepth == 16)
389         info.backgroundColor = chunk.data.dup;
390     else
391     {
392         info.backgroundColor.length = chunk.data.length / 2;
393             foreach(index, ref value; info.backgroundColor)
394                 value = chunk.data[index * 2];
395     }
396 }
397
398 /** parse latin1 text chunk */
399 void parsetTXt(/+const+/ ref Chunk chunk, ref PngInfo info)
400 {
401     if (info.parseText)
402     {
403         auto sep = strFind(cast(char[])chunk.data, 0);
404         if (sep > 0)
405             info.latin1Text[chunk.data[0..sep]] = chunk.data[sep + 1 .. $];
406     }
407 }
408
409 /** parse latin1 compressed text chunk */
410 void parsezTXt(/+const+/ ref Chunk chunk, ref PngInfo info)
411 {
412     if (info.parseText)
413     {
414         auto sep = strFind(cast(char[])chunk.data, 0);
415         if (sep > 0)
416         {
417             if (chunk.data[sep + 1] == 0)
418                 info.latin1Text[chunk.data[0..sep]] = chunk.data[sep + 2 .. $];
419             else
420             {
421                 ubyte[] value;
422                 auto decoder = DecodeStream.create(value);
423                 decoder(chunk.data[sep + 2 .. $]);
424                 info.latin1Text[chunk.data[0..sep]] = value;
425             }
426         }
427     }
428 }
429
430 /** parse unicode text chunk */
431 void parseiTXt(/+const+/ ref Chunk chunk, ref PngInfo info)
432 {
433     if (info.parseText)
434     {
435
436         auto sep = strFind(cast(char[])chunk.data, 0);
437         char[] keyword = cast(char[])chunk.data[0..sep];
438         bool compressed = chunk.data[sep + 1] == 0 ? false : true;
439         sep += strFind(cast(char[])chunk.data[sep + 3 .. $], 0) + 3;
440         sep += strFind(cast(char[])chunk.data[sep + 1 .. $], 0) + 1;
441         if (!compressed)
442             info.unicodeText[keyword] = cast(char[])chunk.data[sep + 1..$];
443         else
444         {
445             ubyte[] value;
446             auto decoder = DecodeStream.create(value);
447             decoder(chunk.data[sep + 1..$]);
448             info.unicodeText[keyword] = cast(char[])value;
449         }
450     }
451 }
452
453
454
455
456
457
458 /***************************************************************************************************
459     Predict size of buffer needed for decoding
460
461         Estimate of the amount of heap memory needed to decode an image. Interlaced images, images
462         with a color format of less than 8 bits per pixel and the parsing of certain information
463         such as text will allocate more heap memory.
464 ***************************************************************************************************/
465 uint bufferSize( /+const+/ ref PngImage image)
466 {
467     return ((image.width * image.bpp + 7) / 8) * image.height + image.height;
468 }
469
470 ////////////////////////////////////////////////////////////////////////////////////////////////////
471 //                                                                                                //
472 //                          PRIVATE SECTION                                                       //
473 //                                                                                                //
474 ////////////////////////////////////////////////////////////////////////////////////////////////////
475
476 private:
477
478 const uint HEADER_SIZE = 34;
479
480 struct StreamChunkIter
481 {
482     int opApply(int delegate(ref Chunk chunk) visitor)
483     {
484         int result = 0;
485         uint pos = 0;
486         Chunk chunk;
487         while(pos + 12 <= stream.length)
488         {
489             chunk = Chunk.fromStream(stream[pos..$]);
490             if (chunk.type == IEND)
491                 break;
492             result = visitor(chunk);
493             if (result)
494                 return result;
495             pos += chunk.length;
496         }
497         return result;
498     }
499     ubyte[] stream;
500 }
501
502 //filter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, the filter works
503 //byte per byte (bytewidth = 1)precon is the previous filtered scanline, recon the result, scanline
504 //the current one
505 void unFilterFirstScanline(ubyte[] result, ubyte[] scanline, uint bytewidth, uint filterType)
506 in
507 {
508     assert(filterType >= 0 && filterType <= 4);
509 }
510 body
511 {
512     mixin(pngEnforce(`filterType >= 0 && filterType <= 4`, "wrong filter byte: image corrupt?"));
513     switch(filterType)
514     {
515         case 0:
516             for(uint i = 0; i < scanline.length; i++)
517                 result[i] = scanline[i];
518             break;
519         case 1:
520             for(uint i = 0; i < bytewidth; i++)
521                 result[i] = scanline[i];
522             for(uint i = bytewidth; i < scanline.length; i++)
523                 result[i] = scanline[i] + result[i - bytewidth];
524         break;
525         case 2:
526             for(size_t i = 0; i < scanline.length; i++)
527                 result[i] = scanline[i];
528             break;
529         case 3:
530             for(size_t i = 0; i < bytewidth; i++)
531                 result[i] = scanline[i];
532             for(size_t i = bytewidth; i < scanline.length; i++)
533                 result[i] = (scanline[i] + result[i - bytewidth]) / 2;
534             break;
535         case 4:
536             for(size_t i = 0; i < bytewidth; i++)
537                 result[i] = scanline[i];
538             for(size_t i = bytewidth; i < scanline.length; i++)
539                 result[i] = cast(ubyte)(scanline[i] + paethPredictor(result[i - bytewidth], 0, 0));
540             break;
541         default:
542             mixin(pngEnforce("false", "wrong type of filter"));
543             break;
544
545     }
546 }
547
548 //filter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, the filter works
549 //byte per byte (bytewidth = 1) precon is the previous filtered scanline, recon the result,
550 //scanline the current one
551 void unFilterScanline(ubyte[] result, ubyte[] scanline, ubyte[] previous, uint bytewidth,
552                      uint filterType)
553 in
554 {
555     assert(filterType >= 0 && filterType <= 4);
556 }
557 body
558 {
559     switch(filterType)
560     {
561         case 4:
562             for(size_t i = 0; i < bytewidth; i++)
563                 result[i] = cast(ubyte)(scanline[i] + previous[i] );
564             for(size_t i = bytewidth; i < scanline.length; i++)
565                 result[i] = cast(ubyte)(scanline[i] +
566                             paethPredictor(result[i - bytewidth],
567                                            previous[i],
568                                            previous[i - bytewidth]));
569             break;
570         case 3:
571             for(size_t i = 0; i < bytewidth; i++)
572                 result[i] = scanline[i] + previous[i] / 2;
573             for(size_t i = bytewidth; i < scanline.length; i++)
574                 result[i] = scanline[i] + ((result[i - bytewidth] + previous[i]) / 2);
575             break;
576         case 2:
577             for(size_t i = 0; i < scanline.length; i++)
578                 result[i] = scanline[i] + previous[i];
579             break;
580         case 1:
581             for(size_t i = 0; i < bytewidth; i++)
582                 result[i] = scanline[i];
583             for(size_t i = bytewidth; i < scanline.length; i++)
584                 result[i] = scanline[i] + result[i - bytewidth];
585         break;
586         case 0:
587             for(uint i = 0; i < scanline.length; i++) result[i] = scanline[i];
588             break;
589         default:
590             mixin(pngEnforce("false", "wrong type of filter"));
591             break;
592     }
593 }
594
595 ubyte[] deinterlace(in ubyte[] scanlines, ref ubyte[] result, ref PngImage image)
596 {