Mercurial > projects > dynamin
comparison dynamin/lodepng/encode.d @ 0:aa4efef0f0b1
Initial commit of code.
author | Jordan Miner <jminer7@gmail.com> |
---|---|
date | Mon, 15 Jun 2009 22:10:48 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:aa4efef0f0b1 |
---|---|
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 Stewart Gordon (bug fixes) | |
19 | |
20 Date: August 7, 2007 | |
21 | |
22 About: | |
23 The lodepng encoder can encode images of any of the supported png color formats to 24-bit RGB or | |
24 32-bit RGBA png images. Conversion, if needed, is done automatically. It is compatible with the | |
25 Phobos and Tango libraries. For Tango, lodepng expects the zlib binding from phobos in etc.c.zlib.d<br> | |
26 This module publicly imports lodepng.Common, where you'll find the data types used by both the encoder | |
27 and decoder, as well as some utility and image format conversion routines.<br> | |
28 | |
29 Features: | |
30 The following features are supported by the encoder: | |
31 <ul> | |
32 <li> conformant encoding of 24-bit RGB and 32-bit RGBA PNG images </li> | |
33 <li> automatic conversion of other color formats </li> | |
34 <li> setting the compression and filter methods </li> | |
35 <li> textual key-value metadata: normal and compressed latin1, unicode (utf-8) </li> | |
36 <li> transparency / colorkey </li> | |
37 <li> the following chunks are written by the encoder | |
38 <ul> | |
39 <li>IHDR (image information)</li> | |
40 <li>IDAT (pixel data)</li> | |
41 <li>IEND (the final chunk)</li> | |
42 <li>tRNS (colorkey)</li> | |
43 <li>bKGD (suggested background color) </li> | |
44 <li>tEXt (uncompressed latin-1 key-value strings)</li> | |
45 <li>zTXt (compressed latin-1 key-value strings)</li> | |
46 <li>iTXt (utf8 key-value strings)</li> | |
47 </ul> | |
48 </li> | |
49 </UL> | |
50 | |
51 <b>Limitations:</b><br> | |
52 The following features are not supported. | |
53 <ul> | |
54 <li> Ouput in any color formats other than 24-bit RGB or 32-bit RGBA</li> | |
55 <li> Interlacing </li> | |
56 <li> The following chunk types are not written by the encoder | |
57 <ul> | |
58 <li>PLTE (color palette)</li> | |
59 <li>cHRM (device independent color info) </li> | |
60 <li>gAMA (device independent color info) </li> | |
61 <li>iCCP (device independent color info) </li> | |
62 <li>sBIT (original number of significant bits) </li> | |
63 <li>sRGB (device independent color info) </li> | |
64 <li>pHYs (physical pixel dimensions) </li> | |
65 <li>sPLT (suggested reduced palette) </li> | |
66 <li>tIME (last image modification time) </li> | |
67 </ul> | |
68 </li> | |
69 </ul> | |
70 | |
71 References: | |
72 $(LINK2 http://members.gamedev.net/lode/projects/LodePNG/, Original lodepng) <br> | |
73 $(LINK2 http://www.w3.org/TR/PNG/, PNG Specification) <br> | |
74 $(LINK2 http://www.libpng.org/pub/png/pngsuite.html, PNG Suite: set of test images) <br> | |
75 $(LINK2 http://optipng.sourceforge.net/, OptiPNG: tool to experimentally optimize png images) | |
76 ***************************************************************************************************/ | |
77 module dynamin.lodepng.encode; | |
78 | |
79 version (Tango) | |
80 { | |
81 import tango.math.Math; | |
82 } | |
83 else | |
84 import std.math; | |
85 import dynamin.lodepng.zlib_codec; | |
86 public import dynamin.lodepng.common; | |
87 | |
88 /*************************************************************************************************** | |
89 Returns a png image of the raw pixels provided by source and described by original | |
90 | |
91 This function will attempt to convert to 24-bit RGB if it is a lossless operation, otherwise | |
92 the resulting png image will be in the 32-bit RGBA format. The array returned can be written to disk | |
93 as a png file. | |
94 | |
95 throws: PngException | |
96 ***************************************************************************************************/ | |
97 ubyte[] encode(in ubyte[] source, in PngInfo original) | |
98 { | |
99 ubyte[] buf; | |
100 return _encode(source, original, EncodeOptions(), buf); | |
101 } | |
102 | |
103 /************************************************************************************************** | |
104 Returns a png image of the raw pixels provided by source and described by original. | |
105 | |
106 See also: EncodeOptions | |
107 | |
108 throws: PngException | |
109 ***************************************************************************************************/ | |
110 ubyte[] encode(in ubyte[] source, in PngInfo original, in EncodeOptions options) | |
111 { | |
112 ubyte[] buf; | |
113 return _encode(source, original, options, buf); | |
114 } | |
115 | |
116 /*************************************************************************************************** | |
117 Combinations of filter and compression trade-offs | |
118 | |
119 The png compression scheme works by applying lossless filters on the image data and then | |
120 compressing with the deflate (zlib) algorithm. The filters condition the data for better | |
121 compression. This enumeration can be used with EncodeOptions, see FilterStrategy and | |
122 CompressionStrategy for more control over how the image is encoded. | |
123 | |
124 ***************************************************************************************************/ | |
125 enum EncodingStrategy | |
126 { | |
127 /// source data is stored as is | |
128 Store, | |
129 | |
130 /** this gives zero compression but can be useful for storage in a zlib compressed file, in | |
131 which case the filtering conditions the data for better compression **/ | |
132 FilterOnly, | |
133 | |
134 /// aim for reasonable compression but with emphasis on speed | |
135 Fast, | |
136 | |
137 /// aim for good compression / speed tradeoff | |
138 Normal, | |
139 | |
140 /// aim for best compression | |
141 Best | |
142 } | |
143 | |
144 /*************************************************************************************************** | |
145 Filter method | |
146 | |
147 The png specification defines five types of filters. In addition each scanline can have a | |
148 different filtering method applied (Dynamic). The latter method gives the best compression, | |
149 when a fixed method is preffered usually paeth works best. | |
150 | |
151 ***************************************************************************************************/ | |
152 enum FilterStrategy : ubyte | |
153 { | |
154 None = 0, /// | |
155 Up = 1, /// | |
156 Sub = 2, /// | |
157 Average = 3, /// | |
158 Paeth = 4, /// | |
159 Dynamic, /// | |
160 } | |
161 | |
162 /*************************************************************************************************** | |
163 Zlib compression method | |
164 | |
165 Which compression scheme works best depends on the type of image. Tools such as optipng can | |
166 figure this out experimentally. | |
167 | |
168 ***************************************************************************************************/ | |
169 enum CompressionStrategy : ubyte | |
170 { | |
171 | |
172 Default = 0, /// | |
173 Filtered = 1, /// | |
174 RLE = 3, /// | |
175 None = ubyte.max /// | |
176 } | |
177 | |
178 /// Controls how a png image is encoded and what auxiliary data is to be written | |
179 struct EncodeOptions | |
180 { | |
181 /// constructors | |
182 static EncodeOptions opCall() | |
183 { | |
184 EncodeOptions result; | |
185 return result; | |
186 } | |
187 | |
188 /// ditto | |
189 static EncodeOptions opCall(ColorType colorType, | |
190 EncodingStrategy strategy = EncodingStrategy.Normal, | |
191 bool autoRemoveAlpha = true) | |
192 { | |
193 EncodeOptions result; | |
194 result.setStrategy(strategy); | |
195 | |
196 with (result) | |
197 { | |
198 autoRemoveAlpha = autoRemoveAlpha; | |
199 targetColorType = colorType; | |
200 } | |
201 return result; | |
202 } | |
203 | |
204 /// ditto | |
205 static EncodeOptions opCall(EncodingStrategy strategy, | |
206 bool autoRemoveAlpha = true) | |
207 { | |
208 EncodeOptions result; | |
209 result.setStrategy(strategy); | |
210 return result; | |
211 } | |
212 | |
213 /// ditto | |
214 static EncodeOptions opCall(CompressionStrategy compression, FilterStrategy filter = FilterStrategy.Dynamic) | |
215 { | |
216 EncodeOptions result; | |
217 result.compressionLevel = 9; | |
218 result.compressionStrategy = compression; | |
219 result.filterStrategy = filter; | |
220 return result; | |
221 } | |
222 | |
223 invariant | |
224 { | |
225 assert(compressionLevel >=0 && compressionLevel <= 9, "invalid zlib compression level"); | |
226 assert(targetColorType == ColorType.Any || | |
227 targetColorType == ColorType.RGB || | |
228 targetColorType == ColorType.RGBA, "colortype is not supported"); | |
229 } | |
230 | |
231 /*********************************************************************************************** | |
232 The colortype of the target image | |
233 | |
234 lodepng can only encode in RGB(A) format. If the format is set ColorType.Any, RGB or | |
235 RGBA is chosen depending on whether the source image has an alpha channel. | |
236 ***********************************************************************************************/ | |
237 ColorType targetColorType = ColorType.Any; | |
238 | |
239 /*********************************************************************************************** | |
240 Remove alpha channel | |
241 | |
242 If set to true and the source image has an alpha channel, this will be removed if (and | |
243 only if) the image is fully opaque or a colorkey can be written. This is considered a | |
244 lossless operation. | |
245 ***********************************************************************************************/ | |
246 bool autoRemoveAlpha = true; | |
247 | |
248 PngText text; /// key-value strings to be written, see also: lodepng.Common.PngText | |
249 bool compressText = false; /// if zlib compression is to be used on text | |
250 | |
251 ubyte[] backgroundColor; /// suggested background RGB-triplet, must be either 3 or 6 bytes | |
252 | |
253 bool colorKey = false; /// colorkey, set to true if it is to be encoded | |
254 ubyte keyR; /// red/greyscale component of color key | |
255 ubyte keyG; /// green component of color key | |
256 ubyte keyB; /// blue component of color key | |
257 | |
258 ubyte compressionLevel = 6; /// zlib compression level, affects memory use. Must be in range 0-9 | |
259 FilterStrategy filterStrategy = FilterStrategy.Dynamic; /// see FilterStrategy | |
260 CompressionStrategy compressionStrategy = CompressionStrategy.RLE; /// see CompressionStrategy | |
261 | |
262 /// This will set compressionLevel, compressionStrategy and filterStrategy | |
263 void setStrategy(EncodingStrategy strategy) | |
264 { | |
265 switch (strategy) | |
266 { | |
267 case EncodingStrategy.Store: | |
268 compressionLevel = 0; | |
269 compressionStrategy = CompressionStrategy.None; | |
270 filterStrategy = FilterStrategy.None; | |
271 break; | |
272 case EncodingStrategy.FilterOnly: | |
273 compressionLevel = 0; | |
274 compressionStrategy = CompressionStrategy.None; | |
275 filterStrategy = FilterStrategy.Dynamic; | |
276 break; | |
277 case EncodingStrategy.Fast: | |
278 compressionLevel = 6; | |
279 compressionStrategy = CompressionStrategy.RLE; | |
280 filterStrategy = FilterStrategy.Paeth; | |
281 break; | |
282 case EncodingStrategy.Normal: | |
283 compressionLevel = 6; | |
284 compressionStrategy = CompressionStrategy.RLE; | |
285 filterStrategy = FilterStrategy.Dynamic; | |
286 break; | |
287 case EncodingStrategy.Best: | |
288 compressionLevel = 9; | |
289 compressionStrategy = CompressionStrategy.Filtered; | |
290 filterStrategy = FilterStrategy.Dynamic; | |
291 break; | |
292 /+ TODO: decide to implement or leave it out | |
293 case EncodingStrategy.ByteCrunch: | |
294 compressionLevel = 9; | |
295 compressionStrategy = CompressionStrategy.Filtered; | |
296 filterStrategy = FilterStrategy.Dynamic; | |
297 break; | |
298 +/ | |
299 default: | |
300 assert(false, "I don't know about this strategy"); | |
301 break; | |
302 } | |
303 } | |
304 } | |
305 | |
306 private | |
307 { | |
308 ubyte[] _encode( in ubyte[] source, in PngInfo original, in EncodeOptions options, ref ubyte[] buffer) | |
309 { | |
310 // TODO: be more sparing with memory here, can at least avoid one array copy | |
311 | |
312 Chunk[] ChunkList; | |
313 | |
314 // find out what colortype of target should be and whether colorkey (tRNS) should be made | |
315 ColorType destColor = (options.targetColorType == ColorType.RGB || | |
316 options.targetColorType == ColorType.RGBA) ? options.targetColorType : | |
317 original.image.colorType; | |
318 if (!(destColor == ColorType.RGB || destColor == ColorType.RGBA)) | |
319 destColor = (hasAlphaChannel(original.image.colorType)) ? ColorType.RGBA : ColorType.RGB; | |
320 if (options.autoRemoveAlpha && destColor == ColorType.RGBA && !options.colorKey) | |
321 { | |
322 ubyte[] colorKey; | |
323 if (opaqueOrColorkey(source, original, colorKey)) | |
324 { | |
325 if (colorKey.length) | |
326 ChunkList ~= Chunk(tRNS, colorKey); | |
327 destColor = ColorType.RGB; | |
328 } | |
329 } | |
330 | |
331 // properties of image to be written | |
332 auto image = PngImage(original.image.width, original.image.height, 8, destColor); | |
333 | |
334 // convert original if necessary | |
335 ubyte[] pixels; | |
336 if (original.image.colorType != destColor || original.image.bitDepth != 8) | |
337 pixels = convert(source, original, destColor); | |
338 else | |
339 pixels = source; | |
340 | |
341 ChunkList ~= Chunk(IHDR, headerData(image)); | |
342 ChunkList ~= Chunk(IDAT, | |
343 Encoder.create(options.compressionStrategy, options.compressionLevel)(filter(pixels, image, options.filterStrategy))); | |
344 if(options.colorKey) | |
345 ChunkList ~= Chunk(tRNS, [0, cast(ubyte)options.keyR, 0, cast(ubyte)options.keyG, 0, cast(ubyte)options.keyB]); | |
346 if(options.backgroundColor.length == 3) | |
347 ChunkList ~= Chunk( bKGD, [ | |
348 0, options.backgroundColor[0], | |
349 0, options.backgroundColor[1], | |
350 0, options.backgroundColor[2] ]); | |
351 else if(options.backgroundColor.length == 6) | |
352 ChunkList ~= Chunk( bKGD, options.backgroundColor); | |
353 if (options.text !is null && (options.text.latin1Text.length > 0 || options.text.unicodeText.length > 0)) | |
354 ChunkList ~= chunkifyText(options); | |
355 ChunkList.sort; | |
356 ChunkList ~= Chunk(IEND, []); | |
357 | |
358 // pre-allocate space needed | |
359 uint pngLength = 8; | |
360 foreach(chunk; ChunkList) | |
361 pngLength += chunk.length; | |
362 buffer.length = pngLength; | |
363 buffer.length = 0; | |
364 | |
365 // create and write all data | |
366 writeSignature(buffer); | |
367 foreach(chunk; ChunkList) | |
368 writeChunk(buffer, chunk); | |
369 | |
370 return buffer; | |
371 } | |
372 | |
373 Chunk[] chunkifyText(EncodeOptions options) | |
374 { | |
375 Chunk[] result; | |
376 if (options.compressText) | |
377 { | |
378 auto enc = Encoder.create(); | |
379 foreach(ubyte[] keyword, ubyte[] value; options.text) | |
380 result ~= Chunk(zTXt, keyword ~ cast(ubyte[])[0, 0] ~ enc(value)); | |
381 foreach(char[] keyword, char[] value; options.text) | |
382 result ~= Chunk(iTXt, cast(ubyte[])keyword ~ cast(ubyte[])[0, 1, 0, 0, 0] ~ enc(cast(ubyte[])value)); | |
383 } | |
384 else | |
385 { | |
386 foreach(ubyte[] keyword, ubyte[] value; options.text) | |
387 result ~= Chunk(tEXt, keyword ~ cast(ubyte[])[0] ~ value); | |
388 foreach(char[] keyword, char[] value; options.text) | |
389 result ~= Chunk(iTXt, cast(ubyte[])keyword ~ cast(ubyte[])[0, 0, 0, 0, 0] ~ cast(ubyte[])value); | |
390 } | |
391 return result; | |
392 } | |
393 | |
394 ubyte[] filter(in ubyte[] source, ref PngImage image, FilterStrategy filterMethod = FilterStrategy.Dynamic) | |
395 { | |
396 /* adaptive filtering */ | |
397 | |
398 ubyte[] buffer = new ubyte[image.width * (image.bpp / 8) * image.height]; | |
399 uint bytewidth = (image.bpp + 7) / 8; | |
400 | |
401 uint scanlength = image.width * bytewidth; | |
402 buffer.length = image.height * (scanlength + 1) + image.height; | |
403 ubyte[] line, previous; | |
404 buffer.length = 0; | |
405 line = source[0..scanlength]; | |
406 ubyte bestFilter = 0; | |
407 | |
408 uint absSum(ubyte[] array) | |
409 { | |
410 uint result = 0; | |
411 foreach(value; array) | |
412 result += abs(cast(int)(cast(byte)value)); | |
413 return result; | |
414 } | |
415 | |
416 uint smallest = absSum(filterMap(line, &None, bytewidth)); | |
417 | |
418 void setSmallest(uint sum, ubyte filterType) | |
419 { | |
420 if (sum < smallest) | |
421 { | |
422 smallest = sum; | |
423 bestFilter = filterType; | |
424 } | |
425 } | |
426 | |
427 if (filterMethod == FilterStrategy.Dynamic) | |
428 { | |
429 for (ubyte f = 1; f < 5; f++) | |
430 setSmallest(absSum(dynFilterMap(line, f, bytewidth)), f); | |
431 buffer ~= bestFilter; | |
432 buffer ~= dynFilterMap(line, bestFilter, bytewidth); | |
433 | |
434 for (int y = 1; y < image.height; y++) | |
435 { | |
436 line = source[scanlength * y..scanlength * y + scanlength]; | |
437 previous = source[scanlength * (y - 1)..scanlength * y]; | |
438 | |
439 bestFilter = 0; | |
440 smallest = absSum(filterMap(line, &None, bytewidth)); | |
441 for (ubyte f = 1; f < 5; f++) | |
442 setSmallest(absSum(dynFilterMap(previous, line, f, bytewidth)), f); | |
443 | |
444 buffer ~= bestFilter; | |
445 buffer ~= dynFilterMap(previous, line, bestFilter, bytewidth); | |
446 } | |
447 } | |
448 else | |
449 { | |
450 buffer ~= cast(ubyte)filterMethod; | |
451 buffer ~= dynFilterMap(line, cast(ubyte)filterMethod, bytewidth); | |
452 | |
453 for (int y = 1; y < image.height; y++) | |
454 { | |
455 line = source[scanlength * y..scanlength * y + scanlength]; | |
456 previous = source[scanlength * (y - 1)..scanlength * y]; | |
457 | |
458 buffer ~= cast(ubyte)filterMethod; | |
459 buffer ~= dynFilterMap(previous, line, cast(ubyte)filterMethod, bytewidth); | |
460 } | |
461 } | |
462 return buffer; | |
463 } | |
464 | |
465 bool opaqueOrColorkey(in ubyte[] image, in PngInfo info, out ubyte[] colorKey) | |
466 { | |
467 if (info.image.colorType == ColorType.Greyscale || info.image.colorType == ColorType.RGB) | |
468 return false; | |
469 else if (info.image.colorType == ColorType.RGBA ) | |
470 { | |
471 uint numpixels = info.image.width * info.image.height; | |
472 | |
473 ubyte[3] ckey; | |
474 bool hasCkey = false; | |
475 | |
476 for(size_t i = 0; i < numpixels; i++) | |
477 { | |
478 if(image[i * 4 + 3] != 255) | |
479 { | |
480 if(image[i * 4 + 3] == 0) | |
481 { | |
482 if (hasCkey) | |
483 { | |
484 if (ckey[0] != image[i * 4] || ckey[1] != image[i * 4 + 1] || | |
485 ckey[2] != image[i * 4 + 2] ) | |
486 return false; | |
487 } | |
488 else | |
489 { | |
490 hasCkey = true; | |
491 ckey[0..2] = image[i * 4 .. i * 4 + 2]; | |
492 } | |
493 } | |
494 else | |
495 return false; | |
496 } | |
497 } | |
498 if (hasCkey) | |
499 { | |
500 colorKey = new ubyte[6]; | |
501 colorKey[1] = ckey[0]; | |
502 colorKey[3] = ckey[1]; | |
503 colorKey[5] = ckey[2]; | |
504 } | |
505 return true; | |
506 } | |
507 else if(info.image.colorType == ColorType.GreyscaleAlpha) | |
508 { | |
509 size_t numpixels = image.length / 2; | |
510 | |
511 ubyte[1] ckey; | |
512 bool hasCkey = false; | |
513 | |
514 for(size_t i = 0; i < numpixels; i++) | |
515 { | |
516 if(image[i * 2 + 1] != 255) | |
517 { | |
518 if (hasCkey) | |
519 { | |
520 if (ckey[0] != image[i * 2]) | |
521 return false; | |
522 } | |
523 else | |
524 { | |
525 hasCkey = true; | |
526 ckey[0] = image[i * 2]; | |
527 } | |
528 } | |
529 } | |
530 if (hasCkey) | |
531 { | |
532 colorKey = new ubyte[2]; | |
533 colorKey[1] = ckey[0]; | |
534 } | |
535 return true; | |
536 } | |
537 else if (info.image.colorType == ColorType.Palette) | |
538 { | |
539 // TODO: implement this (compression optimization) | |
540 return false; | |
541 } | |
542 } | |
543 | |
544 ubyte[] headerData(PngImage image) | |
545 { | |
546 ubyte[] header = new ubyte[13]; | |
547 header.length = 0; | |
548 | |
549 header.concatUint(image.width); | |
550 header.concatUint(image.height); | |
551 header ~= image.bitDepth; | |
552 header ~= image.colorType; | |
553 header ~= 0; //compression method | |
554 header ~= 0; //filter method | |
555 header ~= 0; //interlace method | |
556 | |
557 return header; | |
558 } | |
559 | |
560 void concatUint(ref ubyte[] bytestream, uint num) | |
561 { | |
562 bytestream.length = bytestream.length + 4; | |
563 bytestream[$-4] = num >> 24; | |
564 bytestream[$-3] = num >> 16; | |
565 bytestream[$-2] = num >> 8; | |
566 bytestream[$-1] = num; | |
567 } | |
568 | |
569 void writeChunk(ref ubyte[] bytestream, ref Chunk chunk) | |
570 { | |
571 bytestream.concatUint(chunk.data.length); | |
572 | |
573 bytestream.length = bytestream.length + 4; | |
574 bytestream[$-4] = (chunk.type & 0xff000000) >> 24 ; | |
575 bytestream[$-3] = (chunk.type & 0x00ff0000) >> 16; | |
576 bytestream[$-2] = (chunk.type & 0x0000ff00) >> 8; | |
577 bytestream[$-1] = chunk.type & 0x000000ff; | |
578 | |
579 bytestream.length = bytestream.length + chunk.data.length; | |
580 bytestream[$ - chunk.data.length..$] = chunk.data; | |
581 | |
582 uint CRC = createCRC(bytestream[$ - chunk.data.length - 4.. $]); | |
583 bytestream.concatUint(CRC); | |
584 } | |
585 | |
586 void writeSignature(ref ubyte[] byteStream) | |
587 { | |
588 byteStream ~= [137, 80, 78, 71, 13, 10, 26, 10]; | |
589 } | |
590 | |
591 /++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
592 From the png spec: pixels for filtering are defined as follows such, where x is the current | |
593 being filtered and c, b correspond to the previous scanline: | |
594 c b | |
595 a x | |
596 | |
597 filters: construction Reconstruction | |
598 0 None Filt(x) = Orig(x) Recon(x) = Filt(x) | |
599 1 Sub Filt(x) = Orig(x) - Orig(a) Recon(x) = Filt(x) + Recon(a) | |
600 2 Up Filt(x) = Orig(x) - Orig(b) Recon(x) = Filt(x) + Recon(b) | |
601 3 Average Filt(x) = Orig(x) - floor((Orig(a) + Orig(b)) / 2) Recon(x) = Filt(x) + floor((Recon(a) + Recon(b)) / 2) | |
602 4 Paeth Filt(x) = Orig(x) - PaethPredictor(Orig(a), Orig(b), Orig(c)) Recon(x) = Filt(x) + PaethPredictor(Recon(a), Recon(b), Recon(c) | |
603 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++/ | |
604 ubyte None (ubyte c, ubyte b, ubyte a, ubyte x) { return x; } | |
605 ubyte Sub (ubyte c, ubyte b, ubyte a, ubyte x) { return x - a; } | |
606 ubyte Up (ubyte c, ubyte b, ubyte a, ubyte x) { return x - b; } | |
607 ubyte Average (ubyte c, ubyte b, ubyte a, ubyte x) { return x - (a + b) / 2; } | |
608 ubyte Paeth (ubyte c, ubyte b, ubyte a, ubyte x) { return x - paethPredictor(a,b,c); } | |
609 | |
610 /* map for filtering. seq1 contains the previous scanline and seq2 the current | |
611 * pixels are passed in the order they appear in the serialized image (left to right, top to bottom) | |
612 */ | |
613 ubyte[] filterMap(T)(ubyte[] seq1, ubyte[] seq2, T op, uint bytewidth) | |
614 { | |
615 ubyte[] result; | |
616 result.length = seq1.length < seq2.length ? seq1.length : seq2.length; | |
617 | |
618 auto bw = bytewidth; | |
619 while (bw--) | |
620 result[bw] = op(0, seq1[bw], 0, seq2[bw]); | |
621 for (int i = bytewidth; i < result.length; i++) | |
622 result[i] = op(seq1[i - bytewidth], seq1[i], seq2[i - bytewidth], seq2[i]); | |
623 return result; | |
624 } | |
625 | |
626 /* see filterMap above, this is a special case for the top scanline, where pixels from the | |
627 * previous scanline are set to zero | |
628 */ | |
629 ubyte[] filterMap(T)(ubyte[] seq, T op, uint bytewidth) | |
630 { | |
631 ubyte[] result; | |
632 result.length = seq.length; | |
633 | |
634 auto bw = bytewidth; | |
635 while (bw--) | |
636 result[bw] = op(0, 0, 0, seq[bw]); | |
637 for (int i = bytewidth; i < result.length; i++) | |
638 result[i] = op(0, 0, seq[i - bytewidth], seq[i]); | |
639 return result; | |
640 } | |
641 | |
642 ubyte[] dynFilterMap(ubyte[] seq1, ubyte[] seq2, ubyte fOp, uint bytewidth) | |
643 { | |
644 switch(fOp) | |
645 { | |
646 case 0: return filterMap(seq1,seq2, &None, bytewidth); | |
647 case 1: return filterMap(seq1,seq2, &Sub, bytewidth); | |
648 case 2: return filterMap(seq1,seq2, &Up, bytewidth); | |
649 case 3: return filterMap(seq1,seq2, &Average, bytewidth); | |
650 case 4: return filterMap(seq1,seq2, &Paeth, bytewidth); | |
651 default: | |
652 mixin(pngEnforce(`false`, "wrong png filter")); | |
653 break; | |
654 } | |
655 } | |
656 | |
657 ubyte[] dynFilterMap(ubyte[] seq, ubyte fOp, uint bytewidth) | |
658 { | |
659 switch(fOp) | |
660 { | |
661 case 0: return filterMap(seq, &None, bytewidth); | |
662 case 1: return filterMap(seq, &Sub, bytewidth); | |
663 case 2: return filterMap(seq, &Up, bytewidth); | |
664 case 3: return filterMap(seq, &Average, bytewidth); | |
665 case 4: return filterMap(seq, &Paeth, bytewidth); | |
666 default: | |
667 mixin(pngEnforce(`false`, "wrong png filter")); | |
668 break; | |
669 } | |
670 } | |
671 } | |
672 |