0
|
1 // Written in the D programming language
|
|
2 // www.digitalmars.com/d/
|
|
3
|
|
4 /***************************************************************************************************
|
|
5 License:
|
|
6 Copyright (c) 2005-2007 Lode Vandevenne
|
|
7 All rights reserved.
|
|
8
|
|
9 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
10
|
|
11 - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.<br>
|
|
12 - 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>
|
|
13 - 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>
|
|
14
|
|
15 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.
|
|
16
|
|
17 Authors: Lode Vandevenne (original version in C++), Lutger Blijdestijn (D version) : lutger dot blijdestijn at gmail dot com.
|
|
18
|
|
19 About:
|
|
20 The decoder is small but sufficient for most purposes. It is compliant to the png specification and
|
|
21 has been tested with the png suite. The api is procedural and simple, meant to be easily integrated. It is compatible with the
|
|
22 Phobos and Tango libraries. For Tango, lodepng expects the zlib binding from phobos in etc.c.zlib.d<br>
|
|
23 This module publicly imports lodepng.Common, where you'll find the data types used by both the encoder
|
|
24 and decoder, as well as some utility and image format conversion routines.
|
|
25
|
|
26 Date: August 7, 2007
|
|
27
|
|
28 Features:
|
|
29 The following features are supported by the decoder:
|
|
30 <ul>
|
|
31 <li> conformant decoding of PNGs (all color types, bit depth, interlace mode, CRC checking, etc.)</li>
|
|
32 <li> support for translucent PNG's, including translucent palettes and color key</li>
|
|
33 <li> textual key-value meta-data</li>
|
|
34 <li> the following chunks are interpreted by the decoder
|
|
35 <ul>
|
|
36 <li>IHDR (image information)</li>
|
|
37 <li>PLTE (color palette)</li>
|
|
38 <li>IDAT (pixel data)</li>
|
|
39 <li>IEND (the final chunk)</li>
|
|
40 <li>tRNS (transparency for palettized images)</li>
|
|
41 <li>bKGD (suggested background color)</li>
|
|
42 <li>tEXt (uncompressed latin-1 key-value strings)</li>
|
|
43 <li>zTXt (compressed latin-1 key-value strings)</li>
|
|
44 <li>iTXt (utf8 key-value strings)</li>
|
|
45 </ul>
|
|
46 </li>
|
|
47 </UL>
|
|
48
|
|
49 Limitations:
|
|
50 The following features are not supported.
|
|
51 <ul>
|
|
52 <li> editing a PNG image (unless you use the decoder, then edit it, then use the
|
|
53 encoder, but ignored chunks will then be gone from the original image)</li>
|
|
54 <li> Streaming / progressive display. All data must be available and is processed in one call.</li>
|
|
55 <li> The following optional chunk types are not interpreted by the decoder
|
|
56 <ul>
|
|
57 <li>cHRM (device independent color info)
|
|
58 <li>gAMA (device independent color info)
|
|
59 <li>iCCP (device independent color info)
|
|
60 <li>sBIT (original number of significant bits)
|
|
61 <li>sRGB (device independent color info)
|
|
62 <li>pHYs (physical pixel dimensions)
|
|
63 <li>sPLT (suggested reduced palette)
|
|
64 <li>tIME (last image modification time)
|
|
65 </ul>
|
|
66 </li>
|
|
67 </ul>
|
|
68
|
|
69 Examples:
|
|
70 Here is an example how you could use LodePNG with opengl, see the api documentation for details.
|
|
71 ---
|
|
72 uint loadPNG(char[] filename)
|
|
73 {
|
|
74 uint textureID;
|
|
75
|
|
76 glEnable(GL_TEXTURE_2D);
|
|
77 glGenTextures(1, &textureID);
|
|
78 glBindTexture(GL_TEXTURE_2D, textureID);
|
|
79 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
|
|
80 glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
|
81
|
|
82 PngInfo info;
|
|
83 ubyte[] image = decode32(cast(ubyte[])std.file.read(filename), info);
|
|
84
|
|
85 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, info.image.width, info.image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
|
86 image.ptr);
|
|
87 return textureID;
|
|
88 }
|
|
89 ---
|
|
90
|
|
91 References:
|
|
92 $(LINK2 http://members.gamedev.net/lode/projects/LodePNG/, Original lodepng) <br>
|
|
93 $(LINK2 http://www.w3.org/TR/PNG/, PNG Specification) <br>
|
|
94 $(LINK2 http://www.libpng.org/pub/png/pngsuite.html, PNG Suite: set of test images) <br>
|
|
95 $(LINK2 http://optipng.sourceforge.net/, OptiPNG: tool to experimentally optimize png images)
|
|
96 */
|
|
97
|
|
98 module dynamin.lodepng.decode;
|
|
99 import dynamin.lodepng.zlib_codec;
|
|
100 import dynamin.lodepng.util;
|
|
101 import std.intrinsic;
|
|
102
|
|
103 public import dynamin.lodepng.common;
|
|
104
|
|
105 /***************************************************************************************************
|
|
106 Parse png image header from memory. The first 33 bytes of the png file need to be available
|
|
107
|
|
108 throws: PngException
|
|
109
|
|
110 ***************************************************************************************************/
|
|
111 PngImage readHeader(in ubyte[] source)
|
|
112 {
|
|
113 ubyte interlace;
|
|
114 return readHeader(source, interlace);
|
|
115 }
|
|
116
|
|
117 /// ditto
|
|
118 PngImage readHeader(in ubyte[] source, out ubyte interlace)
|
|
119 in
|
|
120 {
|
|
121 assert(source.length >= HEADER_SIZE, "array is too small to contain png header");
|
|
122 }
|
|
123 body // see spec: http://www.w3.org/TR/PNG/#11IHDR
|
|
124 {
|
|
125 mixin(pngEnforce(`source.length >= HEADER_SIZE`, "png header data is too small"));
|
|
126 mixin(pngEnforce(`source[0..8] == [cast(ubyte)137, 80, 78, 71, 13, 10, 26, 10]`, "invalid png header "));
|
|
127 mixin(pngEnforce((toUint(source[12..16]) == IHDR).stringof, "invalid png header"));
|
|
128 mixin(pngEnforce(`checkCRC(source[29 .. 33], source[12 .. 29])`, "invalid CRC"));
|
|
129
|
|
130 PngImage result;
|
|
131 ubyte interlaceMethod;
|
|
132
|
|
133 with (result)
|
|
134 {
|
|
135 width = toUint(source[16..20]);
|
|
136 height = toUint(source[20..24]);
|
|
137 bitDepth = source[24];
|
|
138 colorType = cast(ColorType)source[25];
|
|
139 mixin(pngEnforce( `source[26] == 0`, "unsupported compression method in png header" ));
|
|
140 mixin(pngEnforce( `source[27] == 0`, "unsupported filter method in png header" ));
|
|
141 mixin(pngEnforce( `checkColorValidity(colorType, bitDepth)`, "invalid header: wrong color format" ));
|
|
142 interlaceMethod = source[28];
|
|
143 mixin(pngEnforce( `interlaceMethod < 2`, "invalid interlace method in png header" ));
|
|
144 bpp = numChannels(colorType) * bitDepth;
|
|
145 }
|
|
146 interlace = interlaceMethod;
|
|
147 return result;
|
|
148 }
|
|
149
|
|
150 /***************************************************************************************************
|
|
151 Decode source png file
|
|
152
|
|
153 If a buffer is provided, it may be used to store the result. See bufferSize for details.
|
|
154
|
|
155 Throws: PngException
|
|
156 Returns: Decoded image pixels. The color format of the resulting image is the
|
|
157 same as the source image, see lodepng.Common.convert and decode32 if a specific color format
|
|
158 is desired.
|
|
159
|
|
160 ***************************************************************************************************/
|
|
161 ubyte[] decode(in ubyte[] source, ref PngInfo info, ubyte[] buffer = null)
|
|
162 {
|
|
163 info.image = readHeader(source, info.interlace);
|
|
164
|
|
165 // holds interlaced filtered scanlines
|
|
166 ubyte[] ilaceBuffer;
|
|
167
|
|
168 // to allocate memory as needed
|
|
169 if (info.interlace == 1)
|
|
170 buffer.length = ((info.image.width * info.image.bpp + 7) / 8) * info.image.height + (info.image.height * 2); // guess
|
|
171 else
|
|
172 buffer.length = ((info.image.width * info.image.bpp + 7) / 8) * info.image.height + info.image.height;
|
|
173
|
|
174
|
|
175 if (info.interlace == 1)
|
|
176 ilaceBuffer.length = buffer.length - info.image.height;
|
|
177
|
|
178 auto inflator = DecodeStream.create(buffer);
|
|
179
|
|
180 foreach(chunk; StreamChunkIter(source[HEADER_SIZE - 1 .. $]))
|
|
181 {
|
|
182 switch(chunk.type)
|
|
183 {
|
|
184 case IDAT:
|
|
185 inflator(chunk.data);
|
|
186 break;
|
|
187 case PLTE:
|
|
188 mixin(pngEnforce(`chunk.data.length <= 256 * 3`, "palette size is too large"));
|
|
189 info.palette.length = chunk.data.length / 3;
|
|
190 foreach(index, ref ubyte[4] color; info.palette)
|
|
191 {
|
|
192 color[0..3] = chunk.data[index * 3 .. index * 3 + 3];
|
|
193 color[3] = 255;
|
|
194 }
|
|
195
|
|
196 break;
|
|
197 case tRNS:
|
|
198 if (chunk.data.length == 0)
|
|
199 break;
|
|
200 info.colorKey = true;
|
|
201 if (info.image.colorType == ColorType.Palette) // index-values
|
|
202 {
|
|
203 mixin(pngEnforce(`chunk.data.length <= info.palette.length`, "palette size is too large"));
|
|
204 foreach(index, alpha; chunk.data)
|
|
205 info.palette[index][3] = alpha;
|
|
206 }
|
|
207 else if (info.image.colorType == ColorType.RGB)
|
|
208 {
|
|
209 info.keyR = 256U * chunk.data[0] + chunk.data[1];
|
|
210 info.keyG = 256U * chunk.data[2] + chunk.data[3];
|
|
211 info.keyB = 256U * chunk.data[4] + chunk.data[5];
|
|
212 }
|
|
213 else if (info.image.colorType == ColorType.Greyscale)
|
|
214 {
|
|
215 info.keyR = 256U * chunk.data[0] + chunk.data[1];
|
|
216 }
|
|
217 else
|
|
218 assert(false);
|
|
219 break;
|
|
220 case bKGD:
|
|
221 if (info.image.colorType == ColorType.Palette || info.image.bitDepth == 16)
|
|
222 info.backgroundColor = chunk.data.dup;
|
|
223 else
|
|
224 {
|
|
225 info.backgroundColor.length = chunk.data.length / 2;
|
|
226 foreach(index, ref value; info.backgroundColor)
|
|
227 value = chunk.data[index * 2];
|
|
228 }
|
|
229 break;
|
|
230 case zTXt:
|
|
231 if (info.parseText)
|
|
232 {
|
|
233 if (info.textual is null)
|
|
234 info.textual = new PngText;
|
|
235 auto sep = strFind(cast(char[])chunk.data, 0);
|
|
236 if (sep > 0)
|
|
237 {
|
|
238 if (chunk.data[sep + 1] == 0)
|
|
239 info.textual[chunk.data[0..sep]] = chunk.data[sep + 2 .. $];
|
|
240 else
|
|
241 {
|
|
242 ubyte[] value;
|
|
243 auto decoder = DecodeStream.create(value);
|
|
244 decoder(chunk.data[sep + 2 .. $]);
|
|
245 info.textual[chunk.data[0..sep]] = value;
|
|
246 }
|
|
247 }
|
|
248 }
|
|
249 break;
|
|
250 case tEXt:
|
|
251 if (info.parseText)
|
|
252 {
|
|
253 if (info.textual is null)
|
|
254 info.textual = new PngText;
|
|
255 auto sep = strFind(cast(char[])chunk.data, 0);
|
|
256 if (sep > 0)
|
|
257 {
|
|
258 info.textual[chunk.data[0..sep]] = chunk.data[sep + 1 .. $];
|
|
259 }
|
|
260 }
|
|
261 break;
|
|
262 case iTXt:
|
|
263 if (info.parseText)
|
|
264 {
|
|
265 if (info.textual is null)
|
|
266 info.textual = new PngText;
|
|
267 auto sep = strFind(cast(char[])chunk.data, 0);
|
|
268 char[] keyword = cast(char[])chunk.data[0..sep];
|
|
269 bool compressed = chunk.data[sep + 1] == 0 ? false : true;
|
|
270 sep += strFind(cast(char[])chunk.data[sep + 3 .. $], 0) + 3;
|
|
271 sep += strFind(cast(char[])chunk.data[sep + 1 .. $], 0) + 1;
|
|
272 if (!compressed)
|
|
273 info.textual[keyword] = cast(char[])chunk.data[sep + 1..$];
|
|
274 else
|
|
275 {
|
|
276 ubyte[] value;
|
|
277 auto decoder = DecodeStream.create(value);
|
|
278 decoder(chunk.data[sep + 1..$]);
|
|
279 info.textual[keyword] = cast(char[])value;
|
|
280 }
|
|
281 }
|
|
282 break;
|
|
283 default:
|
|
284 mixin(pngEnforce(`(bt(&chunk.type, 6) < 0)`, "unrecognized critical chunk"));
|
|
285 break;
|
|
286 }
|
|
287 }
|
|
288 assert(inflator.hasEnded);
|
|
289 buffer = inflator();
|
|
290
|
|
291 return (info.interlace == 0) ? reconstruct(buffer, info.image)
|
|
292 : deinterlace(buffer, ilaceBuffer, info.image);
|
|
293 }
|
|
294
|
|
295 /***************************************************************************************************
|
|
296 Decode source png file to RGBA format
|
|
297
|
|
298 Throws: PngException
|
|
299 Returns: decoded image pixels in 32-bit RGBA format
|
|
300 ***************************************************************************************************/
|
|
301 ubyte[] decode32(in ubyte[] source, ref PngInfo info, ubyte[] buffer = null)
|
|
302 {
|
|
303 buffer = decode(source, info, buffer);
|
|
304 buffer = convert(buffer, info, ColorType.RGBA);
|
|
305 info.image.colorType = ColorType.RGBA;
|
|
306 info.image.bpp = 32;
|
|
307 info.image.bitDepth = 8;
|
|
308 info.palette.length = 0;
|
|
309 return buffer;
|
|
310 }
|
|
311
|
|
312
|
|
313
|
|
314
|
|
315 /***************************************************************************************************
|
|
316 Predict size of buffer needed for decoding
|
|
317
|
|
318 Estimate of the amount of heap memory needed to decode an image. Interlaced images, images
|
|
319 with a color format of less than 8 bits per pixel and the parsing of certain information
|
|
320 such as text will allocate more heap memory.
|
|
321 ***************************************************************************************************/
|
|
322 uint bufferSize( /+const+/ ref PngImage image)
|
|
323 {
|
|
324 return ((image.width * image.bpp + 7) / 8) * image.height + image.height;
|
|
325 }
|
|
326
|
|
327 ////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
328 // //
|
|
329 // PRIVATE SECTION //
|
|
330 // //
|
|
331 ////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
332
|
|
333 private:
|
|
334
|
|
335 const uint HEADER_SIZE = 34;
|
|
336
|
|
337 struct StreamChunkIter
|
|
338 {
|
|
339 int opApply(int delegate(ref Chunk chunk) visitor)
|
|
340 {
|
|
341 int result = 0;
|
|
342 uint pos = 0;
|
|
343 Chunk chunk;
|
|
344 while(pos + 12 <= stream.length)
|
|
345 {
|
|
346 chunk = Chunk.fromStream(stream[pos..$]);
|
|
347 if (chunk.type == IEND)
|
|
348 break;
|
|
349 result = visitor(chunk);
|
|
350 if (result)
|
|
351 return result;
|
|
352 pos += chunk.length;
|
|
353 }
|
|
354 return result;
|
|
355 }
|
|
356 ubyte[] stream;
|
|
357 }
|
|
358
|
|
359 //filter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, the filter works
|
|
360 //byte per byte (bytewidth = 1)precon is the previous filtered scanline, recon the result, scanline
|
|
361 //the current one
|
|
362 void unFilterFirstScanline(ubyte[] result, ubyte[] scanline, uint bytewidth, uint filterType)
|
|
363 in
|
|
364 {
|
|
365 assert(filterType >= 0 && filterType <= 4);
|
|
366 }
|
|
367 body
|
|
368 {
|
|
369 mixin(pngEnforce(`filterType >= 0 && filterType <= 4`, "wrong filter byte: image corrupt?"));
|
|
370 switch(filterType)
|
|
371 {
|
|
372 case 0:
|
|
373 for(uint i = 0; i < scanline.length; i++)
|
|
374 result[i] = scanline[i];
|
|
375 break;
|
|
376 case 1:
|
|
377 for(uint i = 0; i < bytewidth; i++)
|
|
378 result[i] = scanline[i];
|
|
379 for(uint i = bytewidth; i < scanline.length; i++)
|
|
380 result[i] = scanline[i] + result[i - bytewidth];
|
|
381 break;
|
|
382 case 2:
|
|
383 for(size_t i = 0; i < scanline.length; i++)
|
|
384 result[i] = scanline[i];
|
|
385 break;
|
|
386 case 3:
|
|
387 for(size_t i = 0; i < bytewidth; i++)
|
|
388 result[i] = scanline[i];
|
|
389 for(size_t i = bytewidth; i < scanline.length; i++)
|
|
390 result[i] = (scanline[i] + result[i - bytewidth]) / 2;
|
|
391 break;
|
|
392 case 4:
|
|
393 for(size_t i = 0; i < bytewidth; i++)
|
|
394 result[i] = scanline[i];
|
|
395 for(size_t i = bytewidth; i < scanline.length; i++)
|
|
396 result[i] = cast(ubyte)(scanline[i] + paethPredictor(result[i - bytewidth], 0, 0));
|
|
397 break;
|
|
398 default:
|
|
399 mixin(pngEnforce("false", "wrong type of filter"));
|
|
400 break;
|
|
401
|
|
402 }
|
|
403 }
|
|
404
|
|
405 //filter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, the filter works
|
|
406 //byte per byte (bytewidth = 1) precon is the previous filtered scanline, recon the result,
|
|
407 //scanline the current one
|
|
408 void unFilterScanline(ubyte[] result, ubyte[] scanline, ubyte[] previous, uint bytewidth,
|
|
409 uint filterType)
|
|
410 in
|
|
411 {
|
|
412 assert(filterType >= 0 && filterType <= 4);
|
|
413 }
|
|
414 body
|
|
415 {
|
|
416 switch(filterType)
|
|
417 {
|
|
418 case 4:
|
|
419 for(size_t i = 0; i < bytewidth; i++)
|
|
420 result[i] = cast(ubyte)(scanline[i] + previous[i] );
|
|
421 for(size_t i = bytewidth; i < scanline.length; i++)
|
|
422 result[i] = cast(ubyte)(scanline[i] +
|
|
423 paethPredictor(result[i - bytewidth],
|
|
424 previous[i],
|
|
425 previous[i - bytewidth]));
|
|
426 break;
|
|
427 case 3:
|
|
428 for(size_t i = 0; i < bytewidth; i++)
|
|
429 result[i] = scanline[i] + previous[i] / 2;
|
|
430 for(size_t i = bytewidth; i < scanline.length; i++)
|
|
431 result[i] = scanline[i] + ((result[i - bytewidth] + previous[i]) / 2);
|
|
432 break;
|
|
433 case 2:
|
|
434 for(size_t i = 0; i < scanline.length; i++)
|
|
435 result[i] = scanline[i] + previous[i];
|
|
436 break;
|
|
437 case 1:
|
|
438 for(size_t i = 0; i < bytewidth; i++)
|
|
439 result[i] = scanline[i];
|
|
440 for(size_t i = bytewidth; i < scanline.length; i++)
|
|
441 result[i] = scanline[i] + result[i - bytewidth];
|
|
442 break;
|
|
443 case 0:
|
|
444 for(uint i = 0; i < scanline.length; i++) result[i] = scanline[i];
|
|
445 break;
|
|
446 default:
|
|
447 mixin(pngEnforce("false", "wrong type of filter"));
|
|
448 break;
|
|
449 }
|
|
450 }
|
|
451
|
|
452 ubyte[] deinterlace(in ubyte[] scanlines, ref ubyte[] result, ref PngImage image)
|
|
453 {
|
|
454 const x = 0;
|
|
455 const y = 1;
|
|
456 uint bytewidth = (image.bpp + 7) / 8; // bytewidth is used for filtering
|
|
457 ubyte[] source;
|
|
458 ubyte[] scanlineo = new ubyte[image.width * bytewidth]; //"old" scanline
|
|
459 ubyte[] scanlinen = new ubyte[image.width * bytewidth]; //"new" scanline
|
|
460
|
|
461 uint[7][2] passDim;
|
|
462 passDim[x][0] = (image.width + 7) / 8; passDim[y][0] = (image.height + 7) / 8;
|
|
463 passDim[x][1] = (image.width + 3) / 8; passDim[y][1] = (image.height + 7) / 8;
|
|
464 passDim[x][2] = (image.width + 3) / 4; passDim[y][2] = (image.height + 3) / 8;
|
|
465 passDim[x][3] = (image.width + 1) / 4; passDim[y][3] = (image.height + 3) / 4;
|
|
466 passDim[x][4] = (image.width + 1) / 2; passDim[y][4] = (image.height + 1) / 4;
|
|
467 passDim[x][5] = (image.width + 0) / 2; passDim[y][5] = (image.height + 1) / 2;
|
|
468 passDim[x][6] = (image.width + 0) / 1; passDim[y][6] = (image.height + 0) / 2;
|
|
469
|
|
470 // locate start in source of each pass, note:
|
|
471 // (HACK) pass can be empty, sets both x and y axis to zero to recognize this in processPass
|
|
472
|
|
473 size_t[7] passstart;
|
|
474 passstart[0] = 0;
|
|
475 for(int i = 0; i < 6; i++)
|
|
476 {
|
|
477 if (passDim[y][i] * passDim[x][i] == 0)
|
|
478 passDim[y][i] = passDim[x][i] = 0;
|
|
479
|
|
480 passstart[i + 1] = passstart[i] + (passDim[y][i] * (1 + (passDim[x][i] * image.bpp + 7) / 8));
|
|
481 }
|
|
482
|
|
483 void adam7Pass( size_t passleft, size_t passtop, size_t spacex, size_t spacey,
|
|
484 size_t passw, size_t passh)
|
|
485 {
|
|
486 size_t linelength = 1 + ((image.bpp * passw + 7) / 8);
|
|
487 size_t linestart = 0;
|
|
488 uint filterType = source[0];
|
|
489
|
|
490 void placePixels(uint s)
|
|
491 {
|
|
492 if(image.bpp >= 8)
|
|
493 for(size_t i = 0; i < passw; i++)
|
|
494 for(size_t b = 0; b < bytewidth; b++) //b = current byte of this pixel
|
|
495 result[bytewidth * image.width * (passtop + spacey * s) + bytewidth *
|
|
496 (passleft + spacex * i) + b] = scanlinen[bytewidth * i + b];
|
|
497 else
|
|
498 {
|
|
499 for(size_t i = 0; i < passw; i++)
|
|
500 {
|
|
501 size_t outbitp = image.bpp * image.width * (passtop + spacey * s) +
|
|
502 image.bpp * (passleft + spacex * i);
|
|
503 for(size_t b = 0; b < image.bpp; b++) //b = current bit of this pixel
|
|
504 {
|
|
505 size_t obp = outbitp + b;
|
|
506 //where bitpos 0 refers to the LSB, bitpot 7 to the MSB of a byte
|
|
507 size_t obitpos = 7 - (obp & 0x7);
|
|
508 size_t bp = i * image.bpp + b;
|
|
509 //where bitpos 0 refers to the LSB, bitpot 7 to the MSB of a byte
|
|
510 size_t bitpos = 7 - (bp & 0x7);
|
|
511 uint _bit = (scanlinen[bp / 8] >> bitpos) & 1;
|
|
512 result[obp / 8] = cast(ubyte)((result[obp / 8] & ~(1 << obitpos)) |
|
|
513 (_bit << obitpos));
|
|
514 }
|
|
515 }
|
|
516 }
|
|
517 }
|
|
518
|
|
519 void swapScanlines()
|
|
520 {
|
|
521 ubyte[] temp = scanlinen;
|
|
522 scanlinen = scanlineo;
|
|
523 scanlineo = temp;
|
|
524 }
|
|
525
|
|
526 unFilterFirstScanline(scanlinen, source[linestart + 1 .. linestart + linelength],
|
|
527 bytewidth, filterType);
|
|
528 placePixels(0);
|
|
529 swapScanlines();
|
|
530
|
|
531 for(uint s = 1; s < passh; s++)
|
|
532 {
|
|
533 linestart = s * linelength;
|
|
534 filterType = source[linestart];
|
|
535
|
|
536 unFilterScanline(scanlinen, source[linestart + 1 .. linestart + linelength],
|
|
537 scanlineo, bytewidth, filterType);
|
|
538 placePixels(s);
|
|
539 swapScanlines();
|
|
540 }
|
|
541 }
|
|
542
|
|
543 void processPass(size_t pass, size_t left, size_t top, size_t sx, size_t sy)
|
|
544 {
|
|
545 // check if pass is empty
|
|
546 if (pass > 0 && (passDim[x][pass] * passDim[y][pass] == 0))
|
|
547 return;
|
|
548 source = scanlines[passstart[pass]..$];
|
|
549 adam7Pass( left, top, sx, sy, passDim[x][pass], passDim[y][pass]);
|
|
550 }
|
|
551
|
|
552
|
|
553 processPass(0, 0, 0, 8, 8);
|
|
554 processPass(1, 4, 0, 8, 8);
|
|
555 processPass(2, 0, 4, 4, 8);
|
|
556 processPass(3, 2, 0, 4, 4);
|
|
557 processPass(4, 0, 2, 2, 4);
|
|
558 processPass(5, 1, 0, 2, 2);
|
|
559 processPass(6, 0, 1, 1, 2);
|
|
560
|
|
561 return result;
|
|
562 }
|
|
563
|
|
564 ubyte[] reconstruct(ref ubyte[] buffer, /+const+/ ref PngImage image)
|
|
565 {
|
|
566 //filter and interlace
|
|
567 uint bytewidth = (image.bpp + 7) / 8; // bytewidth is used for filtering
|
|
568 uint outlength = image.height * image.width * bytewidth;
|
|
569
|
|
570
|
|
571 size_t linestart = 0; //start of current scanline
|
|
572
|
|
573 // bits are tightly packed, but scanlines are always padded to 1 byte bounderies:
|
|
574 uint scanlength = ((image.width * image.bpp) + 7) / 8;
|
|
575
|
|
576 mixin(pngEnforce( `buffer.length >= (scanlength + 1) * image.height && buffer.length > 0`, "invalid size of source data"));
|
|
577
|
|
578 uint filterType = buffer[linestart];
|
|
579
|
|
580
|
|
581 if(image.bpp >= 8) //byte per byte
|
|
582 {
|
|
583 filterType = buffer[linestart];
|
|
584 unFilterFirstScanline(buffer, buffer[1..1 + scanlength], bytewidth, filterType);
|
|
585
|
|
586 //go to start of next scanline
|
|
587 linestart += 1 + scanlength;
|
|
588
|
|
589 for(size_t s = 1; s < image.height; ++s)
|
|
590 {
|
|
591 filterType = buffer[linestart];
|
|
592 unFilterScanline(buffer[linestart - s..$],
|
|
593 buffer[linestart + 1 .. linestart + 1 + scanlength],
|
|
594 buffer[(s - 1) * image.width * bytewidth..$],
|
|
595 bytewidth, filterType);
|
|
596
|
|
597
|
|
598 //go to start of next scanline
|
|
599 linestart += 1 + scanlength;
|
|
600 }
|
|
601 }
|
|
602 else //less than 8 bits per pixel, so fill it up bit per bit
|
|
603 {
|
|
604 size_t obp = 0; //out bit pointer
|
|
605
|
|
606 ubyte[] templine = new ubyte[scanlength];
|
|
607 filterType = buffer[linestart];
|
|
608 unFilterFirstScanline(templine, buffer[1 .. 1 + scanlength], bytewidth, filterType);
|
|
609
|
|
610 linestart += 1 + scanlength;
|
|
611
|
|
612 //bp is here bit pointer in templine
|
|
613 for(size_t bp = 0; bp < image.width * image.bpp; bp++)
|
|
614 {
|
|
615 size_t obitpos = 7 - (obp & 0x7);
|
|
616 size_t bitpos = 7 - (bp & 0x7);
|
|
617 uint _bit = (templine[bp / 8] >> bitpos) & 1;
|
|
618 buffer[obp / 8] = cast(ubyte)((buffer[obp / 8] & ~(1 << obitpos)) |
|
|
619 (_bit << obitpos)); //set current bit
|
|
620 obp++;
|
|
621 }
|
|
622
|
|
623 for(size_t s = 1; s < image.height; ++s)
|
|
624 {
|
|
625 filterType = buffer[linestart];
|
|
626 unFilterScanline( templine,
|
|
627 buffer[linestart + 1 .. linestart + 1 + scanlength],
|
|
628 buffer[(s - 1) * scanlength..$],
|
|
629 bytewidth,
|
|
630 filterType);
|
|
631
|
|
632 //bp is here bit pointer in templine
|
|
633 for(size_t bp = 0; bp < image.width * image.bpp; bp++)
|
|
634 {
|
|
635 size_t obitpos = 7 - (obp & 0x7);
|
|
636 size_t bitpos = 7 - (bp & 0x7);
|
|
637 uint _bit = (templine[bp / 8] >> bitpos) & 1;
|
|
638 buffer[obp / 8] = cast(ubyte)((buffer[obp / 8] & ~(1 << obitpos)) |
|
|
639 (_bit << obitpos)); //set current bit
|
|
640 obp++;
|
|
641 }
|
|
642
|
|
643 //go to start of next scanline
|
|
644 linestart += 1 + ((image.width * image.bpp + 7) / 8);
|
|
645 }
|
|
646 }
|
|
647 return buffer;
|
|
648 }
|
|
649
|