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