1 /++
2 	PNG file read and write. Leverages [arsd.color|color.d]'s [MemoryImage] interfaces for interop.
3 
4 	The main high-level functions you want are [readPng], [readPngFromBytes], [writePng], and maybe [writeImageToPngFile] or [writePngLazy] for some circumstances.
5 
6 	The other functions are low-level implementations and helpers for dissecting the png file format.
7 
8 	History:
9 		Originally written in 2009. This is why some of it is still written in a C-like style!
10 
11 	See_Also:
12 	$(LIST
13 		* [arsd.image] has generic load interfaces that can handle multiple file formats, including png.
14 		* [arsd.apng] handles the animated png extensions.
15 	)
16 +/
17 module arsd.png;
18 
19 import core.memory;
20 
21 /++
22 	Easily reads a png file into a [MemoryImage]
23 
24 	Returns:
25 		Please note this function doesn't return null right now, but you should still check for null anyway as that might change.
26 
27 		The returned [MemoryImage] is either a [IndexedImage] or a [TrueColorImage], depending on the file's color mode. You can cast it to one or the other, or just call [MemoryImage.getAsTrueColorImage] which will cast and return or convert as needed automatically.
28 
29 		Greyscale pngs and bit depths other than 8 are converted for the ease of the MemoryImage interface. If you need more detail, try [PNG] and [getDatastream] etc.
30 +/
31 MemoryImage readPng(string filename) {
32 	import std.file;
33 	return imageFromPng(readPng(cast(ubyte[]) read(filename)));
34 }
35 
36 /++
37 	Easily reads a png from a data array into a MemoryImage.
38 
39 	History:
40 		Added December 29, 2021 (dub v10.5)
41 +/
42 MemoryImage readPngFromBytes(const(ubyte)[] bytes) {
43 	return imageFromPng(readPng(bytes));
44 }
45 
46 /++
47 	Saves a MemoryImage to a png file. See also: [writeImageToPngFile] which uses memory a little more efficiently
48 
49 	See_Also:
50 		[writePngToArray]
51 +/
52 void writePng(string filename, MemoryImage mi) {
53 	// FIXME: it would be nice to write the file lazily so we don't have so many intermediate buffers here
54 	import std.file;
55 	std.file.write(filename, writePngToArray(mi));
56 }
57 
58 /++
59 	Creates an in-memory png file from the given memory image, returning it.
60 
61 	History:
62 		Added April 21, 2023 (dub v11.0)
63 	See_Also:
64 		[writePng]
65 +/
66 ubyte[] writePngToArray(MemoryImage mi) {
67 	PNG* png;
68 	if(auto p = cast(IndexedImage) mi)
69 		png = pngFromImage(p);
70 	else if(auto p = cast(TrueColorImage) mi)
71 		png = pngFromImage(p);
72 	else assert(0);
73 	return writePng(png);
74 }
75 
76 /++
77 	Represents the different types of png files, with numbers matching what the spec gives for filevalues.
78 +/
79 enum PngType {
80 	greyscale = 0, /// The data must be `depth` bits per pixel
81 	truecolor = 2, /// The data will be RGB triples, so `depth * 3` bits per pixel. Depth must be 8 or 16.
82 	indexed = 3, /// The data must be `depth` bits per pixel, with a palette attached. Use [writePng] with [IndexedImage] for this mode. Depth must be <= 8.
83 	greyscale_with_alpha = 4, /// The data must be (grey, alpha) byte pairs for each pixel. Thus `depth * 2` bits per pixel. Depth must be 8 or 16.
84 	truecolor_with_alpha = 6 /// The data must be RGBA quads for each pixel. Thus, `depth * 4` bits per pixel. Depth must be 8 or 16.
85 }
86 
87 /++
88 	Saves an image from an existing array of pixel data. Note that depth other than 8 may not be implemented yet. Also note depth of 16 must be stored big endian
89 +/
90 void writePng(string filename, const ubyte[] data, int width, int height, PngType type, ubyte depth = 8) {
91 	PngHeader h;
92 	h.width = width;
93 	h.height = height;
94 	h.type = cast(ubyte) type;
95 	h.depth = depth;
96 
97 	auto png = blankPNG(h);
98 	addImageDatastreamToPng(data, png);
99 
100 	import std.file;
101 	std.file.write(filename, writePng(png));
102 }
103 
104 
105 /*
106 //Here's a simple test program that shows how to write a quick image viewer with simpledisplay:
107 
108 import arsd.png;
109 import arsd.simpledisplay;
110 
111 import std.file;
112 void main(string[] args) {
113 	// older api, the individual functions give you more control if you need it
114 	//auto img = imageFromPng(readPng(cast(ubyte[]) read(args[1])));
115 
116 	// newer api, simpler but less control
117 	auto img = readPng(args[1]);
118 
119 	// displayImage is from simpledisplay and just pops up a window to show the image
120 	// simpledisplay's Images are a little different than MemoryImages that this loads,
121 	// but conversion is easy
122 	displayImage(Image.fromMemoryImage(img));
123 }
124 */
125 
126 // By Adam D. Ruppe, 2009-2010, released into the public domain
127 //import std.file;
128 
129 //import std.zlib;
130 
131 public import arsd.color;
132 
133 /**
134 	The return value should be casted to indexed or truecolor depending on what the file is. You can
135 	also use getAsTrueColorImage to forcibly convert it if needed.
136 
137 	To get an image from a png file, do something like this:
138 
139 	auto i = cast(TrueColorImage) imageFromPng(readPng(cast(ubyte)[]) std.file.read("file.png")));
140 */
141 MemoryImage imageFromPng(PNG* png) {
142 	PngHeader h = getHeader(png);
143 
144 	/** Types from the PNG spec:
145 		0 - greyscale
146 		2 - truecolor
147 		3 - indexed color
148 		4 - grey with alpha
149 		6 - true with alpha
150 
151 		1, 5, and 7 are invalid.
152 
153 		There's a kind of bitmask going on here:
154 			If type&1, it has a palette.
155 			If type&2, it is in color.
156 			If type&4, it has an alpha channel in the datastream.
157 	*/
158 
159 	MemoryImage i;
160 	ubyte[] idata;
161 	// FIXME: some duplication with the lazy reader below in the module
162 
163 	switch(h.type) {
164 		case 0: // greyscale
165 		case 4: // greyscale with alpha
166 			// this might be a different class eventually...
167 			auto a = new TrueColorImage(h.width, h.height);
168 			idata = a.imageData.bytes;
169 			i = a;
170 		break;
171 		case 2: // truecolor
172 		case 6: // truecolor with alpha
173 			auto a = new TrueColorImage(h.width, h.height);
174 			idata = a.imageData.bytes;
175 			i = a;
176 		break;
177 		case 3: // indexed
178 			auto a = new IndexedImage(h.width, h.height);
179 			a.palette = fetchPalette(png);
180 			a.hasAlpha = true; // FIXME: don't be so conservative here
181 			idata = a.data;
182 			i = a;
183 		break;
184 		default:
185 			assert(0, "invalid png");
186 	}
187 
188 	size_t idataIdx = 0;
189 
190 	auto file = LazyPngFile!(Chunk[])(png.chunks);
191 	immutable(ubyte)[] previousLine;
192 	auto bpp = bytesPerPixel(h);
193 	foreach(line; file.rawDatastreamByChunk()) {
194 		auto filter = line[0];
195 		auto data = unfilter(filter, line[1 .. $], previousLine, bpp);
196 		previousLine = data;
197 
198 		convertPngData(h.type, h.depth, data, h.width, idata, idataIdx);
199 	}
200 	assert(idataIdx == idata.length, "not all filled, wtf");
201 
202 	assert(i !is null);
203 
204 	return i;
205 }
206 
207 /+
208 	This is used by the load MemoryImage functions to convert the png'd datastream into the format MemoryImage's implementations expect.
209 
210 	idata needs to be already sized for the image! width * height if indexed, width*height*4 if not.
211 +/
212 void convertPngData(ubyte type, ubyte depth, const(ubyte)[] data, int width, ubyte[] idata, ref size_t idataIdx) {
213 	ubyte consumeOne() {
214 		ubyte ret = data[0];
215 		data = data[1 .. $];
216 		return ret;
217 	}
218 	import std.conv;
219 
220 	loop: for(int pixel = 0; pixel < width; pixel++)
221 		switch(type) {
222 			case 0: // greyscale
223 			case 4: // greyscale with alpha
224 			case 3: // indexed
225 
226 				void acceptPixel(ubyte p) {
227 					if(type == 3) {
228 						idata[idataIdx++] = p;
229 					} else {
230 						if(depth == 1) {
231 							p = p ? 0xff : 0;
232 						} else if (depth == 2) {
233 							p |= p << 2;
234 							p |= p << 4;
235 						}
236 						else if (depth == 4) {
237 							p |= p << 4;
238 						}
239 						idata[idataIdx++] = p;
240 						idata[idataIdx++] = p;
241 						idata[idataIdx++] = p;
242 
243 						if(type == 0)
244 							idata[idataIdx++] = 255;
245 						else if(type == 4)
246 							idata[idataIdx++] = consumeOne();
247 					}
248 				}
249 
250 				auto b = consumeOne();
251 				switch(depth) {
252 					case 1:
253 						acceptPixel((b >> 7) & 0x01);
254 						pixel++; if(pixel == width) break loop;
255 						acceptPixel((b >> 6) & 0x01);
256 						pixel++; if(pixel == width) break loop;
257 						acceptPixel((b >> 5) & 0x01);
258 						pixel++; if(pixel == width) break loop;
259 						acceptPixel((b >> 4) & 0x01);
260 						pixel++; if(pixel == width) break loop;
261 						acceptPixel((b >> 3) & 0x01);
262 						pixel++; if(pixel == width) break loop;
263 						acceptPixel((b >> 2) & 0x01);
264 						pixel++; if(pixel == width) break loop;
265 						acceptPixel((b >> 1) & 0x01);
266 						pixel++; if(pixel == width) break loop;
267 						acceptPixel(b & 0x01);
268 					break;
269 					case 2:
270 						acceptPixel((b >> 6) & 0x03);
271 						pixel++; if(pixel == width) break loop;
272 						acceptPixel((b >> 4) & 0x03);
273 						pixel++; if(pixel == width) break loop;
274 						acceptPixel((b >> 2) & 0x03);
275 						pixel++; if(pixel == width) break loop;
276 						acceptPixel(b & 0x03);
277 					break;
278 					case 4:
279 						acceptPixel((b >> 4) & 0x0f);
280 						pixel++; if(pixel == width) break loop;
281 						acceptPixel(b & 0x0f);
282 					break;
283 					case 8:
284 						acceptPixel(b);
285 					break;
286 					case 16:
287 						assert(type != 3); // 16 bit indexed isn't supported per png spec
288 						acceptPixel(b);
289 						consumeOne(); // discarding the least significant byte as we can't store it anyway
290 					break;
291 					default:
292 						assert(0, "bit depth not implemented");
293 				}
294 			break;
295 			case 2: // truecolor
296 			case 6: // true with alpha
297 				if(depth == 8) {
298 					idata[idataIdx++] = consumeOne();
299 					idata[idataIdx++] = consumeOne();
300 					idata[idataIdx++] = consumeOne();
301 					idata[idataIdx++] = (type == 6) ? consumeOne() : 255;
302 				} else if(depth == 16) {
303 					idata[idataIdx++] = consumeOne();
304 					consumeOne();
305 					idata[idataIdx++] = consumeOne();
306 					consumeOne();
307 					idata[idataIdx++] = consumeOne();
308 					consumeOne();
309 					idata[idataIdx++] = (type == 6) ? consumeOne() : 255;
310 					if(type == 6)
311 						consumeOne();
312 
313 				} else assert(0, "unsupported truecolor bit depth " ~ to!string(depth));
314 			break;
315 			default: assert(0);
316 		}
317 	assert(data.length == 0, "not all consumed, wtf " ~ to!string(data));
318 }
319 
320 /*
321 struct PngHeader {
322 	uint width;
323 	uint height;
324 	ubyte depth = 8;
325 	ubyte type = 6; // 0 - greyscale, 2 - truecolor, 3 - indexed color, 4 - grey with alpha, 6 - true with alpha
326 	ubyte compressionMethod = 0; // should be zero
327 	ubyte filterMethod = 0; // should be zero
328 	ubyte interlaceMethod = 0; // bool
329 }
330 */
331 
332 
333 /++
334 	Creates the [PNG] data structure out of an [IndexedImage]. This structure will have the minimum number of colors
335 	needed to represent the image faithfully in the file and will be ready for writing to a file.
336 
337 	This is called by [writePng].
338 +/
339 PNG* pngFromImage(IndexedImage i) {
340 	PngHeader h;
341 	h.width = i.width;
342 	h.height = i.height;
343 	h.type = 3;
344 	if(i.numColors() <= 2)
345 		h.depth = 1;
346 	else if(i.numColors() <= 4)
347 		h.depth = 2;
348 	else if(i.numColors() <= 16)
349 		h.depth = 4;
350 	else if(i.numColors() <= 256)
351 		h.depth = 8;
352 	else throw new Exception("can't save this as an indexed png");
353 
354 	auto png = blankPNG(h);
355 
356 	// do palette and alpha
357 	// FIXME: if there is only one transparent color, set it as the special chunk for that
358 
359 	// FIXME: we'd get a smaller file size if the transparent pixels were arranged first
360 	Chunk palette;
361 	palette.type = ['P', 'L', 'T', 'E'];
362 	palette.size = cast(int) i.palette.length * 3;
363 	palette.payload.length = palette.size;
364 
365 	Chunk alpha;
366 	if(i.hasAlpha) {
367 		alpha.type = ['t', 'R', 'N', 'S'];
368 		alpha.size = cast(uint) i.palette.length;
369 		alpha.payload.length = alpha.size;
370 	}
371 
372 	for(int a = 0; a < i.palette.length; a++) {
373 		palette.payload[a*3+0] = i.palette[a].r;
374 		palette.payload[a*3+1] = i.palette[a].g;
375 		palette.payload[a*3+2] = i.palette[a].b;
376 		if(i.hasAlpha)
377 			alpha.payload[a] = i.palette[a].a;
378 	}
379 
380 	palette.checksum = crc("PLTE", palette.payload);
381 	png.chunks ~= palette;
382 	if(i.hasAlpha) {
383 		alpha.checksum = crc("tRNS", alpha.payload);
384 		png.chunks ~= alpha;
385 	}
386 
387 	// do the datastream
388 	if(h.depth == 8) {
389 		addImageDatastreamToPng(i.data, png);
390 	} else {
391 		// gotta convert it
392 
393 		auto bitsPerLine = i.width * h.depth;
394 		if(bitsPerLine % 8 != 0)
395 			bitsPerLine = bitsPerLine / 8 + 1;
396 		else
397 			bitsPerLine = bitsPerLine / 8;
398 
399 		ubyte[] datastream = new ubyte[bitsPerLine * i.height];
400 		int shift = 0;
401 
402 		switch(h.depth) {
403 			default: assert(0);
404 			case 1: shift = 7; break;
405 			case 2: shift = 6; break;
406 			case 4: shift = 4; break;
407 			case 8: shift = 0; break;
408 		}
409 		size_t dsp = 0;
410 		size_t dpos = 0;
411 		bool justAdvanced;
412 		for(int y = 0; y < i.height; y++) {
413 		for(int x = 0; x < i.width; x++) {
414 			datastream[dsp] |= i.data[dpos++] << shift;
415 
416 			switch(h.depth) {
417 				default: assert(0);
418 				case 1: shift-= 1; break;
419 				case 2: shift-= 2; break;
420 				case 4: shift-= 4; break;
421 				case 8: shift-= 8; break;
422 			}
423 
424 			justAdvanced = shift < 0;
425 			if(shift < 0) {
426 				dsp++;
427 				switch(h.depth) {
428 					default: assert(0);
429 					case 1: shift = 7; break;
430 					case 2: shift = 6; break;
431 					case 4: shift = 4; break;
432 					case 8: shift = 0; break;
433 				}
434 			}
435 		}
436 			if(!justAdvanced)
437 				dsp++;
438 			switch(h.depth) {
439 				default: assert(0);
440 				case 1: shift = 7; break;
441 				case 2: shift = 6; break;
442 				case 4: shift = 4; break;
443 				case 8: shift = 0; break;
444 			}
445 
446 		}
447 
448 		addImageDatastreamToPng(datastream, png);
449 	}
450 
451 	return png;
452 }
453 
454 /++
455 	Creates the [PNG] data structure out of a [TrueColorImage]. This implementation currently always make
456 	the file a true color with alpha png type.
457 
458 	This is called by [writePng].
459 +/
460 
461 PNG* pngFromImage(TrueColorImage i) {
462 	PngHeader h;
463 	h.width = i.width;
464 	h.height = i.height;
465 	// FIXME: optimize it if it is greyscale or doesn't use alpha alpha
466 
467 	auto png = blankPNG(h);
468 	addImageDatastreamToPng(i.imageData.bytes, png);
469 
470 	return png;
471 }
472 
473 /*
474 void main(string[] args) {
475 	auto a = readPng(cast(ubyte[]) read(args[1]));
476 	auto f = getDatastream(a);
477 
478 	foreach(i; f) {
479 		writef("%d ", i);
480 	}
481 
482 	writefln("\n\n%d", f.length);
483 }
484 */
485 
486 /++
487 	Represents the PNG file's data. This struct is intended to be passed around by pointer.
488 +/
489 struct PNG {
490 	/++
491 		The length of the file.
492 	+/
493 	uint length;
494 	/++
495 		The PNG file magic number header. Please note the image data header is a IHDR chunk, not this (see [getHeader] for that). This just a static identifier
496 
497 		History:
498 			Prior to October 10, 2022, this was called `header`.
499 	+/
500 	ubyte[8] magic;// = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; // this is the only valid value but idk if it is worth changing here since the ctor sets it regardless.
501 	/// ditto
502 	deprecated("use `magic` instead") alias header = magic;
503 
504 	/++
505 		The array of chunks that make up the file contents. See [getChunkNullable], [getChunk], [insertChunk], and [replaceChunk] for functions to access and manipulate this array.
506 	+/
507 	Chunk[] chunks;
508 
509 	/++
510 		Gets the chunk with the given name, or throws if it cannot be found.
511 
512 		Returns:
513 			A non-null pointer to the chunk in the [chunks] array.
514 		Throws:
515 			an exception if the chunk can not be found. The type of this exception is subject to change at this time.
516 		See_Also:
517 			[getChunkNullable], which returns a null pointer instead of throwing.
518 	+/
519 	pure @trusted /* see note on getChunkNullable */
520 	Chunk* getChunk(string what) {
521 		foreach(ref c; chunks) {
522 			if(c.stype == what)
523 				return &c;
524 		}
525 		throw new Exception("no such chunk " ~ what);
526 	}
527 
528 	/++
529 		Gets the chunk with the given name, return `null` if it is not found.
530 
531 		See_Also:
532 			[getChunk], which throws if the chunk cannot be found.
533 	+/
534 	nothrow @nogc pure @trusted /* trusted because &c i know is referring to the dynamic array, not actually a local. That has lifetime at least as much of the parent PNG object. */
535 	Chunk* getChunkNullable(string what) {
536 		foreach(ref c; chunks) {
537 			if(c.stype == what)
538 				return &c;
539 		}
540 		return null;
541 	}
542 
543 	/++
544 		Insert chunk before IDAT. PNG specs allows to drop all chunks after IDAT,
545 		so we have to insert our custom chunks right before it.
546 		Use `Chunk.create()` to create new chunk, and then `insertChunk()` to add it.
547 		Return `true` if we did replacement.
548 	+/
549 	nothrow pure @trusted /* the chunks.ptr here fails safe, but it does that for performance and again I control that data so can be reasonably assured */
550 	bool insertChunk (Chunk* chk, bool replaceExisting=false) {
551 		if (chk is null) return false; // just in case
552 		// use reversed loop, as "IDAT" is usually present, and it is usually the last,
553 		// so we will somewhat amortize painter's algorithm here.
554 		foreach_reverse (immutable idx, ref cc; chunks) {
555 			if (replaceExisting && cc.type == chk.type) {
556 				// replace existing chunk, the easiest case
557 				chunks[idx] = *chk;
558 				return true;
559 			}
560 			if (cc.stype == "IDAT") {
561 				// ok, insert it; and don't use phobos
562 				chunks.length += 1;
563 				foreach_reverse (immutable c; idx+1..chunks.length) chunks.ptr[c] = chunks.ptr[c-1];
564 				chunks.ptr[idx] = *chk;
565 				return false;
566 			}
567 		}
568 		chunks ~= *chk;
569 		return false;
570 	}
571 
572 	/++
573 		Convenient wrapper for `insertChunk()`.
574 	+/
575 	nothrow pure @safe
576 	bool replaceChunk (Chunk* chk) { return insertChunk(chk, true); }
577 }
578 
579 /++
580 	this is just like writePng(filename, pngFromImage(image)), but it manages
581 	its own memory and writes straight to the file instead of using intermediate buffers that might not get gc'd right
582 +/
583 void writeImageToPngFile(in char[] filename, TrueColorImage image) {
584 	PNG* png;
585 	ubyte[] com;
586 {
587 	import std.zlib;
588 	PngHeader h;
589 	h.width = image.width;
590 	h.height = image.height;
591 	png = blankPNG(h);
592 
593 	size_t bytesPerLine = cast(size_t)h.width * 4;
594 	if(h.type == 3)
595 		bytesPerLine = cast(size_t)h.width * 8 / h.depth;
596 	Chunk dat;
597 	dat.type = ['I', 'D', 'A', 'T'];
598 	size_t pos = 0;
599 
600 	auto compressor = new Compress();
601 
602 	import core.stdc.stdlib;
603 	auto lineBuffer = (cast(ubyte*)malloc(1 + bytesPerLine))[0 .. 1+bytesPerLine];
604 	scope(exit) free(lineBuffer.ptr);
605 
606 	while(pos+bytesPerLine <= image.imageData.bytes.length) {
607 		lineBuffer[0] = 0;
608 		lineBuffer[1..1+bytesPerLine] = image.imageData.bytes[pos.. pos+bytesPerLine];
609 		com ~= cast(ubyte[]) compressor.compress(lineBuffer);
610 		pos += bytesPerLine;
611 	}
612 
613 	com ~= cast(ubyte[]) compressor.flush();
614 
615 	assert(com.length <= uint.max);
616 	dat.size = cast(uint) com.length;
617 	dat.payload = com;
618 	dat.checksum = crc("IDAT", dat.payload);
619 
620 	png.chunks ~= dat;
621 
622 	Chunk c;
623 
624 	c.size = 0;
625 	c.type = ['I', 'E', 'N', 'D'];
626 	c.checksum = crc("IEND", c.payload);
627 
628 	png.chunks ~= c;
629 }
630 	assert(png !is null);
631 
632 	import core.stdc.stdio;
633 	import std.string;
634 	FILE* fp = fopen(toStringz(filename), "wb");
635 	if(fp is null)
636 		throw new Exception("Couldn't open png file for writing.");
637 	scope(exit) fclose(fp);
638 
639 	fwrite(png.magic.ptr, 1, 8, fp);
640 	foreach(c; png.chunks) {
641 		fputc((c.size & 0xff000000) >> 24, fp);
642 		fputc((c.size & 0x00ff0000) >> 16, fp);
643 		fputc((c.size & 0x0000ff00) >> 8, fp);
644 		fputc((c.size & 0x000000ff) >> 0, fp);
645 
646 		fwrite(c.type.ptr, 1, 4, fp);
647 		fwrite(c.payload.ptr, 1, c.size, fp);
648 
649 		fputc((c.checksum & 0xff000000) >> 24, fp);
650 		fputc((c.checksum & 0x00ff0000) >> 16, fp);
651 		fputc((c.checksum & 0x0000ff00) >> 8, fp);
652 		fputc((c.checksum & 0x000000ff) >> 0, fp);
653 	}
654 
655 	{ import core.memory : GC; GC.free(com.ptr); } // there is a reference to this in the PNG struct, but it is going out of scope here too, so who cares
656 	// just wanna make sure this crap doesn't stick around
657 }
658 
659 /++
660 	Turns a [PNG] structure into an array of bytes, ready to be written to a file.
661 +/
662 ubyte[] writePng(PNG* p) {
663 	ubyte[] a;
664 	if(p.length)
665 		a.length = p.length;
666 	else {
667 		a.length = 8;
668 		foreach(c; p.chunks)
669 			a.length += c.size + 12;
670 	}
671 	size_t pos;
672 
673 	a[0..8] = p.magic[0..8];
674 	pos = 8;
675 	foreach(c; p.chunks) {
676 		a[pos++] = (c.size & 0xff000000) >> 24;
677 		a[pos++] = (c.size & 0x00ff0000) >> 16;
678 		a[pos++] = (c.size & 0x0000ff00) >> 8;
679 		a[pos++] = (c.size & 0x000000ff) >> 0;
680 
681 		a[pos..pos+4] = c.type[0..4];
682 		pos += 4;
683 		a[pos..pos+c.size] = c.payload[0..c.size];
684 		pos += c.size;
685 
686 		a[pos++] = (c.checksum & 0xff000000) >> 24;
687 		a[pos++] = (c.checksum & 0x00ff0000) >> 16;
688 		a[pos++] = (c.checksum & 0x0000ff00) >> 8;
689 		a[pos++] = (c.checksum & 0x000000ff) >> 0;
690 	}
691 
692 	return a;
693 }
694 
695 /++
696 	Opens a file and pulls the [PngHeader] out, leaving the rest of the data alone.
697 
698 	This might be useful when you're only interested in getting a file's image size or
699 	other basic metainfo without loading the whole thing.
700 +/
701 PngHeader getHeaderFromFile(string filename) {
702 	import std.stdio;
703 	auto file = File(filename, "rb");
704 	ubyte[12] initialBuffer; // file header + size of first chunk (should be IHDR)
705 	auto data = file.rawRead(initialBuffer[]);
706 	if(data.length != 12)
707 		throw new Exception("couldn't get png file header off " ~ filename);
708 
709 	if(data[0..8] != [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
710 		throw new Exception("file " ~ filename ~ " is not a png");
711 
712 	size_t pos = 8;
713 	size_t size;
714 	size |= data[pos++] << 24;
715 	size |= data[pos++] << 16;
716 	size |= data[pos++] << 8;
717 	size |= data[pos++] << 0;
718 
719 	size += 4; // chunk type
720 	size += 4; // checksum
721 
722 	ubyte[] more;
723 	more.length = size;
724 
725 	auto chunk = file.rawRead(more);
726 	if(chunk.length != size)
727 		throw new Exception("couldn't get png image header off " ~ filename);
728 
729 
730 	more = data ~ chunk;
731 
732 	auto png = readPng(more);
733 	return getHeader(png);
734 }
735 
736 /++
737 	Given an in-memory array of bytes from a PNG file, returns the parsed out [PNG] object.
738 
739 	You might want the other [readPng] overload instead, which returns an even more processed [MemoryImage] object.
740 +/
741 PNG* readPng(in ubyte[] data) {
742 	auto p = new PNG;
743 
744 	p.length = cast(int) data.length;
745 	if(p.length < 8)
746 		throw new Exception("not a png, too short");
747 	p.magic[0..8] = data[0..8];
748 
749 	if(p.magic != [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
750 		throw new Exception("not a png, header wrong");
751 
752 	size_t pos = 8;
753 
754 	while(pos < data.length && data.length - pos >= 12) {
755 		Chunk n;
756 		n.size |= data[pos++] << 24;
757 		n.size |= data[pos++] << 16;
758 		n.size |= data[pos++] << 8;
759 		n.size |= data[pos++] << 0;
760 		n.type[0..4] = data[pos..pos+4];
761 		pos += 4;
762 		n.payload.length = n.size;
763 		if(pos + n.size > data.length)
764 			throw new Exception(format("malformed png, chunk '%s' %d @ %d longer than data %d", n.type, n.size, pos, data.length));
765 		if(pos + n.size < pos)
766 			throw new Exception("uint overflow: chunk too large");
767 		n.payload[0..n.size] = data[pos..pos+n.size];
768 		pos += n.size;
769 
770 		n.checksum |= data[pos++] << 24;
771 		n.checksum |= data[pos++] << 16;
772 		n.checksum |= data[pos++] << 8;
773 		n.checksum |= data[pos++] << 0;
774 
775 		p.chunks ~= n;
776 
777 		if(n.type == "IEND")
778 			break;
779 	}
780 
781 	return p;
782 }
783 
784 /++
785 	Creates a new [PNG] object from the given header parameters, ready to receive data.
786 +/
787 PNG* blankPNG(PngHeader h) {
788 	auto p = new PNG;
789 	p.magic = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
790 
791 	Chunk c;
792 
793 	c.size = 13;
794 	c.type = ['I', 'H', 'D', 'R'];
795 
796 	c.payload.length = 13;
797 	size_t pos = 0;
798 
799 	c.payload[pos++] = h.width >> 24;
800 	c.payload[pos++] = (h.width >> 16) & 0xff;
801 	c.payload[pos++] = (h.width >> 8) & 0xff;
802 	c.payload[pos++] = h.width & 0xff;
803 
804 	c.payload[pos++] = h.height >> 24;
805 	c.payload[pos++] = (h.height >> 16) & 0xff;
806 	c.payload[pos++] = (h.height >> 8) & 0xff;
807 	c.payload[pos++] = h.height & 0xff;
808 
809 	c.payload[pos++] = h.depth;
810 	c.payload[pos++] = h.type;
811 	c.payload[pos++] = h.compressionMethod;
812 	c.payload[pos++] = h.filterMethod;
813 	c.payload[pos++] = h.interlaceMethod;
814 
815 
816 	c.checksum = crc("IHDR", c.payload);
817 
818 	p.chunks ~= c;
819 
820 	return p;
821 }
822 
823 /+
824 	Implementation helper for creating png files.
825 
826 	Its API is subject to change; it would be private except it might be useful to you.
827 +/
828 // should NOT have any idata already.
829 // FIXME: doesn't handle palettes
830 void addImageDatastreamToPng(const(ubyte)[] data, PNG* png, bool addIend = true) {
831 	// we need to go through the lines and add the filter byte
832 	// then compress it into an IDAT chunk
833 	// then add the IEND chunk
834 	import std.zlib;
835 
836 	PngHeader h = getHeader(png);
837 
838 	if(h.depth == 0)
839 		throw new Exception("depth of zero makes no sense");
840 	if(h.width == 0)
841 		throw new Exception("width zero?!!?!?!");
842 
843 	int multiplier;
844 	size_t bytesPerLine;
845 	switch(h.type) {
846 		case 0:
847 			multiplier = 1;
848 		break;
849 		case 2:
850 			multiplier = 3;
851 		break;
852 		case 3:
853 			multiplier = 1;
854 		break;
855 		case 4:
856 			multiplier = 2;
857 		break;
858 		case 6:
859 			multiplier = 4;
860 		break;
861 		default: assert(0);
862 	}
863 
864 	bytesPerLine = h.width * multiplier * h.depth / 8;
865 	if((h.width * multiplier * h.depth) % 8 != 0)
866 		bytesPerLine += 1;
867 
868 	assert(bytesPerLine >= 1);
869 	Chunk dat;
870 	dat.type = ['I', 'D', 'A', 'T'];
871 	size_t pos = 0;
872 
873 	const(ubyte)[] output;
874 	while(pos+bytesPerLine <= data.length) {
875 		output ~= 0;
876 		output ~= data[pos..pos+bytesPerLine];
877 		pos += bytesPerLine;
878 	}
879 
880 	auto com = cast(ubyte[]) compress(output);
881 	dat.size = cast(int) com.length;
882 	dat.payload = com;
883 	dat.checksum = crc("IDAT", dat.payload);
884 
885 	png.chunks ~= dat;
886 
887 	if(addIend) {
888 		Chunk c;
889 
890 		c.size = 0;
891 		c.type = ['I', 'E', 'N', 'D'];
892 		c.checksum = crc("IEND", c.payload);
893 
894 		png.chunks ~= c;
895 	}
896 
897 }
898 
899 deprecated alias PngHeader PNGHeader;
900 
901 // bKGD - palette entry for background or the RGB (16 bits each) for that. or 16 bits of grey
902 
903 /+
904 	Uncompresses the raw datastream out of the file chunks, but does not continue processing it, so the scanlines are still filtered, etc.
905 +/
906 ubyte[] getDatastream(PNG* p) {
907 	import std.zlib;
908 	ubyte[] compressed;
909 
910 	foreach(c; p.chunks) {
911 		if(c.stype != "IDAT")
912 			continue;
913 		compressed ~= c.payload;
914 	}
915 
916 	return cast(ubyte[]) uncompress(compressed);
917 }
918 
919 /+
920 	Gets a raw datastream out of a 8 bpp png. See also [getANDMask]
921 +/
922 // FIXME: Assuming 8 bits per pixel
923 ubyte[] getUnfilteredDatastream(PNG* p) {
924 	PngHeader h = getHeader(p);
925 	assert(h.filterMethod == 0);
926 
927 	assert(h.type == 3); // FIXME
928 	assert(h.depth == 8); // FIXME
929 
930 	ubyte[] data = getDatastream(p);
931 	ubyte[] ufdata = new ubyte[data.length - h.height];
932 
933 	int bytesPerLine = cast(int) ufdata.length / h.height;
934 
935 	int pos = 0, pos2 = 0;
936 	for(int a = 0; a < h.height; a++) {
937 		assert(data[pos2] == 0);
938 		ufdata[pos..pos+bytesPerLine] = data[pos2+1..pos2+bytesPerLine+1];
939 		pos+= bytesPerLine;
940 		pos2+= bytesPerLine + 1;
941 	}
942 
943 	return ufdata;
944 }
945 
946 /+
947 	Gets the unfiltered raw datastream for conversion to Windows ico files. See also [getANDMask] and [fetchPaletteWin32].
948 +/
949 ubyte[] getFlippedUnfilteredDatastream(PNG* p) {
950 	PngHeader h = getHeader(p);
951 	assert(h.filterMethod == 0);
952 
953 	assert(h.type == 3); // FIXME
954 	assert(h.depth == 8 || h.depth == 4); // FIXME
955 
956 	ubyte[] data = getDatastream(p);
957 	ubyte[] ufdata = new ubyte[data.length - h.height];
958 
959 	int bytesPerLine = cast(int) ufdata.length / h.height;
960 
961 
962 	int pos = cast(int) ufdata.length - bytesPerLine, pos2 = 0;
963 	for(int a = 0; a < h.height; a++) {
964 		assert(data[pos2] == 0);
965 		ufdata[pos..pos+bytesPerLine] = data[pos2+1..pos2+bytesPerLine+1];
966 		pos-= bytesPerLine;
967 		pos2+= bytesPerLine + 1;
968 	}
969 
970 	return ufdata;
971 }
972 
973 ubyte getHighNybble(ubyte a) {
974 	return cast(ubyte)(a >> 4); // FIXME
975 }
976 
977 ubyte getLowNybble(ubyte a) {
978 	return a & 0x0f;
979 }
980 
981 /++
982 	Takes the transparency info and returns an AND mask suitable for use in a Windows ico
983 +/
984 ubyte[] getANDMask(PNG* p) {
985 	PngHeader h = getHeader(p);
986 	assert(h.filterMethod == 0);
987 
988 	assert(h.type == 3); // FIXME
989 	assert(h.depth == 8 || h.depth == 4); // FIXME
990 
991 	assert(h.width % 8 == 0); // might actually be %2
992 
993 	ubyte[] data = getDatastream(p);
994 	ubyte[] ufdata = new ubyte[h.height*((((h.width+7)/8)+3)&~3)]; // gotta pad to DWORDs...
995 
996 	Color[] colors = fetchPalette(p);
997 
998 	int pos = 0, pos2 = (h.width/((h.depth == 8) ? 1 : 2)+1)*(h.height-1);
999 	bool bits = false;
1000 	for(int a = 0; a < h.height; a++) {
1001 		assert(data[pos2++] == 0);
1002 		for(int b = 0; b < h.width; b++) {
1003 			if(h.depth == 4) {
1004 				ufdata[pos/8] |= ((colors[bits? getLowNybble(data[pos2]) : getHighNybble(data[pos2])].a <= 30) << (7-(pos%8)));
1005 			} else
1006 				ufdata[pos/8] |= ((colors[data[pos2]].a == 0) << (7-(pos%8)));
1007 			pos++;
1008 			if(h.depth == 4) {
1009 				if(bits) {
1010 					pos2++;
1011 				}
1012 				bits = !bits;
1013 			} else
1014 				pos2++;
1015 		}
1016 
1017 		int pad = 0;
1018 		for(; pad < ((pos/8) % 4); pad++) {
1019 			ufdata[pos/8] = 0;
1020 			pos+=8;
1021 		}
1022 		if(h.depth == 4)
1023 			pos2 -= h.width + 2;
1024 		else
1025 			pos2-= 2*(h.width) +2;
1026 	}
1027 
1028 	return ufdata;
1029 }
1030 
1031 // Done with assumption
1032 
1033 /++
1034 	Gets the parsed [PngHeader] data out of the [PNG] object.
1035 +/
1036 @nogc @safe pure
1037 PngHeader getHeader(PNG* p) {
1038 	PngHeader h;
1039 	ubyte[] data = p.getChunkNullable("IHDR").payload;
1040 
1041 	int pos = 0;
1042 
1043 	h.width |= data[pos++] << 24;
1044 	h.width |= data[pos++] << 16;
1045 	h.width |= data[pos++] << 8;
1046 	h.width |= data[pos++] << 0;
1047 
1048 	h.height |= data[pos++] << 24;
1049 	h.height |= data[pos++] << 16;
1050 	h.height |= data[pos++] << 8;
1051 	h.height |= data[pos++] << 0;
1052 
1053 	h.depth = data[pos++];
1054 	h.type = data[pos++];
1055 	h.compressionMethod = data[pos++];
1056 	h.filterMethod = data[pos++];
1057 	h.interlaceMethod = data[pos++];
1058 
1059 	return h;
1060 }
1061 
1062 /*
1063 struct Color {
1064 	ubyte r;
1065 	ubyte g;
1066 	ubyte b;
1067 	ubyte a;
1068 }
1069 */
1070 
1071 /+
1072 class Image {
1073 	Color[][] trueColorData;
1074 	ubyte[] indexData;
1075 
1076 	Color[] palette;
1077 
1078 	uint width;
1079 	uint height;
1080 
1081 	this(uint w, uint h) {}
1082 }
1083 
1084 Image fromPNG(PNG* p) {
1085 
1086 }
1087 
1088 PNG* toPNG(Image i) {
1089 
1090 }
1091 +/		struct RGBQUAD {
1092 			ubyte rgbBlue;
1093 			ubyte rgbGreen;
1094 			ubyte rgbRed;
1095 			ubyte rgbReserved;
1096 		}
1097 
1098 /+
1099 	Gets the palette out of the format Windows expects for bmp and ico files.
1100 
1101 	See also getANDMask
1102 +/
1103 RGBQUAD[] fetchPaletteWin32(PNG* p) {
1104 	RGBQUAD[] colors;
1105 
1106 	auto palette = p.getChunk("PLTE");
1107 
1108 	colors.length = (palette.size) / 3;
1109 
1110 	for(int i = 0; i < colors.length; i++) {
1111 		colors[i].rgbRed = palette.payload[i*3+0];
1112 		colors[i].rgbGreen = palette.payload[i*3+1];
1113 		colors[i].rgbBlue = palette.payload[i*3+2];
1114 		colors[i].rgbReserved = 0;
1115 	}
1116 
1117 	return colors;
1118 
1119 }
1120 
1121 /++
1122 	Extracts the palette chunk from a PNG object as an array of RGBA quads.
1123 
1124 	See_Also:
1125 		[replacePalette]
1126 +/
1127 Color[] fetchPalette(PNG* p) {
1128 	Color[] colors;
1129 
1130 	auto header = getHeader(p);
1131 	if(header.type == 0) { // greyscale
1132 		colors.length = 256;
1133 		foreach(i; 0..256)
1134 			colors[i] = Color(cast(ubyte) i, cast(ubyte) i, cast(ubyte) i);
1135 		return colors;
1136 	}
1137 
1138 	// assuming this is indexed
1139 	assert(header.type == 3);
1140 
1141 	auto palette = p.getChunk("PLTE");
1142 
1143 	Chunk* alpha = p.getChunkNullable("tRNS");
1144 
1145 	colors.length = palette.size / 3;
1146 
1147 	for(int i = 0; i < colors.length; i++) {
1148 		colors[i].r = palette.payload[i*3+0];
1149 		colors[i].g = palette.payload[i*3+1];
1150 		colors[i].b = palette.payload[i*3+2];
1151 		if(alpha !is null && i < alpha.size)
1152 			colors[i].a = alpha.payload[i];
1153 		else
1154 			colors[i].a = 255;
1155 
1156 		//writefln("%2d: %3d %3d %3d %3d", i, colors[i].r, colors[i].g, colors[i].b, colors[i].a);
1157 	}
1158 
1159 	return colors;
1160 }
1161 
1162 /++
1163 	Replaces the palette data in a [PNG] object.
1164 
1165 	See_Also:
1166 		[fetchPalette]
1167 +/
1168 void replacePalette(PNG* p, Color[] colors) {
1169 	auto palette = p.getChunk("PLTE");
1170 	auto alpha = p.getChunkNullable("tRNS");
1171 
1172 	//import std.string;
1173 	//assert(0, format("%s %s", colors.length, alpha.size));
1174 	//assert(colors.length == alpha.size);
1175 	if(alpha) {
1176 		alpha.size = cast(int) colors.length;
1177 		alpha.payload.length = colors.length; // we make sure there's room for our simple method below
1178 	}
1179 	p.length = 0; // so write will recalculate
1180 
1181 	for(int i = 0; i < colors.length; i++) {
1182 		palette.payload[i*3+0] = colors[i].r;
1183 		palette.payload[i*3+1] = colors[i].g;
1184 		palette.payload[i*3+2] = colors[i].b;
1185 		if(alpha)
1186 			alpha.payload[i] = colors[i].a;
1187 	}
1188 
1189 	palette.checksum = crc("PLTE", palette.payload);
1190 	if(alpha)
1191 		alpha.checksum = crc("tRNS", alpha.payload);
1192 }
1193 
1194 @safe nothrow pure @nogc
1195 uint update_crc(in uint crc, in ubyte[] buf){
1196 	static const uint[256] crc_table = [0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685, 2657392035, 249268274, 2044508324, 3772115230, 2547177864, 162941995, 2125561021, 3887607047, 2428444049, 498536548, 1789927666, 4089016648, 2227061214, 450548861, 1843258603, 4107580753, 2211677639, 325883990, 1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755, 2366115317, 997073096, 1281953886, 3579855332, 2724688242, 1006888145, 1258607687, 3524101629, 2768942443, 901097722, 1119000684, 3686517206, 2898065728, 853044451, 1172266101, 3705015759, 2882616665, 651767980, 1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705, 3099436303, 671266974, 1594198024, 3322730930, 2970347812, 795835527, 1483230225, 3244367275, 3060149565, 1994146192, 31158534, 2563907772, 4023717930, 1907459465, 112637215, 2680153253, 3904427059, 2013776290, 251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719, 3865271297, 1802195444, 476864866, 2238001368, 4066508878, 1812370925, 453092731, 2181625025, 4111451223, 1706088902, 314042704, 2344532202, 4240017532, 1658658271, 366619977, 2362670323, 4224994405, 1303535960, 984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733, 3554079995, 1131014506, 879679996, 2909243462, 3663771856, 1141124467, 855842277, 2852801631, 3708648649, 1342533948, 654459306, 3188396048, 3373015174, 1466479909, 544179635, 3110523913, 3462522015, 1591671054, 702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443, 3233442989, 3988292384, 2596254646, 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523, 3814918930, 2489596804, 225274430, 2053790376, 3826175755, 2466906013, 167816743, 2097651377, 4027552580, 2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225, 1852507879, 4275313526, 2312317920, 282753626, 1742555852, 4189708143, 2394877945, 397917763, 1622183637, 3604390888, 2714866558, 953729732, 1340076626, 3518719985, 2797360999, 1068828381, 1219638859, 3624741850, 2936675148, 906185462, 1090812512, 3747672003, 2825379669, 829329135, 1181335161, 3412177804, 3160834842, 628085408, 1382605366, 3423369109, 3138078467, 570562233, 1426400815, 3317316542, 2998733608, 733239954, 1555261956, 3268935591, 3050360625, 752459403, 1541320221, 2607071920, 3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877, 83908371, 2512341634, 3803740692, 2075208622, 213261112, 2463272603, 3855990285, 2094854071, 198958881, 2262029012, 4057260610, 1759359992, 534414190, 2176718541, 4139329115, 1873836001, 414664567, 2282248934, 4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795, 376229701, 2685067896, 3608007406, 1308918612, 956543938, 2808555105, 3495958263, 1231636301, 1047427035, 2932959818, 3654703836, 1088359270, 936918000, 2847714899, 3736837829, 1202900863, 817233897, 3183342108, 3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449, 601450431, 3009837614, 3294710456, 1567103746, 711928724, 3020668471, 3272380065, 1510334235, 755167117];
1197 
1198 	uint c = crc;
1199 
1200 	foreach(b; buf)
1201 		c = crc_table[(c ^ b) & 0xff] ^ (c >> 8);
1202 
1203 	return c;
1204 }
1205 
1206 /+
1207 	Figures out the crc for a chunk. Used internally.
1208 
1209 	lol is just the chunk name
1210 +/
1211 uint crc(in string lol, in ubyte[] buf){
1212 	uint c = update_crc(0xffffffffL, cast(ubyte[]) lol);
1213 	return update_crc(c, buf) ^ 0xffffffffL;
1214 }
1215 
1216 
1217 /* former module arsd.lazypng follows */
1218 
1219 // this is like png.d but all range based so more complicated...
1220 // and I don't remember how to actually use it.
1221 
1222 // some day I'll prolly merge it with png.d but for now just throwing it up there
1223 
1224 //module arsd.lazypng;
1225 
1226 //import arsd.color;
1227 
1228 //import std.stdio;
1229 
1230 import std.range;
1231 import std.traits;
1232 import std.exception;
1233 import std.string;
1234 //import std.conv;
1235 
1236 /*
1237 struct Color {
1238 	ubyte r;
1239 	ubyte g;
1240 	ubyte b;
1241 	ubyte a;
1242 
1243 	string toString() {
1244 		return format("#%2x%2x%2x %2x", r, g, b, a);
1245 	}
1246 }
1247 */
1248 
1249 //import arsd.simpledisplay;
1250 
1251 struct RgbaScanline {
1252 	Color[] pixels;
1253 }
1254 
1255 
1256 // lazy range to convert some png scanlines into greyscale. part of an experiment i didn't do much with but still use sometimes.
1257 auto convertToGreyscale(ImageLines)(ImageLines lines)
1258 	if(isInputRange!ImageLines && is(ElementType!ImageLines == RgbaScanline))
1259 {
1260 	struct GreyscaleLines {
1261 		ImageLines lines;
1262 		bool isEmpty;
1263 		this(ImageLines lines) {
1264 			this.lines = lines;
1265 			if(!empty())
1266 				popFront(); // prime
1267 		}
1268 
1269 		int length() {
1270 			return lines.length;
1271 		}
1272 
1273 		bool empty() {
1274 			return isEmpty;
1275 		}
1276 
1277 		RgbaScanline current;
1278 		RgbaScanline front() {
1279 			return current;
1280 		}
1281 
1282 		void popFront() {
1283 			if(lines.empty()) {
1284 				isEmpty = true;
1285 				return;
1286 			}
1287 			auto old = lines.front();
1288 			current.pixels.length = old.pixels.length;
1289 			foreach(i, c; old.pixels) {
1290 				ubyte v = cast(ubyte) (
1291 					cast(int) c.r * 0.30 +
1292 					cast(int) c.g * 0.59 +
1293 					cast(int) c.b * 0.11);
1294 				current.pixels[i] = Color(v, v, v, c.a);
1295 			}
1296 			lines.popFront;
1297 		}
1298 	}
1299 
1300 	return GreyscaleLines(lines);
1301 }
1302 
1303 
1304 
1305 
1306 /// Lazily breaks the buffered input range into
1307 /// png chunks, as defined in the PNG spec
1308 ///
1309 /// Note: bufferedInputRange is defined in this file too.
1310 LazyPngChunks!(Range) readPngChunks(Range)(Range r)
1311 	if(isBufferedInputRange!(Range) && is(ElementType!(Range) == ubyte[]))
1312 {
1313 	// First, we need to check the header
1314 	// Then we'll lazily pull the chunks
1315 
1316 	while(r.front.length < 8) {
1317 		enforce(!r.empty(), "This isn't big enough to be a PNG file");
1318 		r.appendToFront();
1319 	}
1320 
1321 	enforce(r.front[0..8] == PNG_MAGIC_NUMBER,
1322 		"The file's magic number doesn't look like PNG");
1323 
1324 	r.consumeFromFront(8);
1325 
1326 	return LazyPngChunks!Range(r);
1327 }
1328 
1329 /// Same as above, but takes a regular input range instead of a buffered one.
1330 /// Provided for easier compatibility with standard input ranges
1331 /// (for example, std.stdio.File.byChunk)
1332 auto readPngChunks(Range)(Range r)
1333 	if(!isBufferedInputRange!(Range) && isInputRange!(Range))
1334 {
1335 	return readPngChunks(BufferedInputRange!Range(r));
1336 }
1337 
1338 /// Given an input range of bytes, return a lazy PNG file
1339 auto pngFromBytes(Range)(Range r)
1340 	if(isInputRange!(Range) && is(ElementType!Range == ubyte[]))
1341 {
1342 	auto chunks = readPngChunks(r);
1343 	auto file = LazyPngFile!(typeof(chunks))(chunks);
1344 
1345 	return file;
1346 }
1347 
1348 /// See: [readPngChunks]
1349 struct LazyPngChunks(T)
1350 	if(isBufferedInputRange!(T) && is(ElementType!T == ubyte[]))
1351 {
1352 	T bytes;
1353 	Chunk current;
1354 
1355 	this(T range) {
1356 		bytes = range;
1357 		popFront(); // priming it
1358 	}
1359 
1360 	Chunk front() {
1361 		return current;
1362 	}
1363 
1364 	bool empty() {
1365 		return (bytes.front.length == 0 && bytes.empty);
1366 	}
1367 
1368 	void popFront() {
1369 		enforce(!empty());
1370 
1371 		while(bytes.front().length < 4) {
1372 			enforce(!bytes.empty,
1373 				format("Malformed PNG file - chunk size too short (%s < 4)",
1374 					bytes.front().length));
1375 			bytes.appendToFront();
1376 		}
1377 
1378 		Chunk n;
1379 		n.size |= bytes.front()[0] << 24;
1380 		n.size |= bytes.front()[1] << 16;
1381 		n.size |= bytes.front()[2] << 8;
1382 		n.size |= bytes.front()[3] << 0;
1383 
1384 		bytes.consumeFromFront(4);
1385 
1386 		while(bytes.front().length < n.size + 8) {
1387 			enforce(!bytes.empty,
1388 				format("Malformed PNG file - chunk too short (%s < %s)",
1389 					bytes.front.length, n.size));
1390 			bytes.appendToFront();
1391 		}
1392 		n.type[0 .. 4] = bytes.front()[0 .. 4];
1393 		bytes.consumeFromFront(4);
1394 
1395 		n.payload.length = n.size;
1396 		n.payload[0 .. n.size] = bytes.front()[0 .. n.size];
1397 		bytes.consumeFromFront(n.size);
1398 
1399 		n.checksum |= bytes.front()[0] << 24;
1400 		n.checksum |= bytes.front()[1] << 16;
1401 		n.checksum |= bytes.front()[2] << 8;
1402 		n.checksum |= bytes.front()[3] << 0;
1403 
1404 		bytes.consumeFromFront(4);
1405 
1406 		enforce(n.checksum == crcPng(n.stype, n.payload), "Chunk checksum didn't match");
1407 
1408 		current = n;
1409 	}
1410 }
1411 
1412 /// Lazily reads out basic info from a png (header, palette, image data)
1413 /// It will only allocate memory to read a palette, and only copies on
1414 /// the header and the palette. It ignores everything else.
1415 ///
1416 /// FIXME: it doesn't handle interlaced files.
1417 struct LazyPngFile(LazyPngChunksProvider)
1418 	if(isInputRange!(LazyPngChunksProvider) &&
1419 		is(ElementType!(LazyPngChunksProvider) == Chunk))
1420 {
1421 	LazyPngChunksProvider chunks;
1422 
1423 	this(LazyPngChunksProvider chunks) {
1424 		enforce(!chunks.empty(), "There are no chunks in this png");
1425 
1426 		header = PngHeader.fromChunk(chunks.front());
1427 		chunks.popFront();
1428 
1429 		// And now, find the datastream so we're primed for lazy
1430 		// reading, saving the palette and transparency info, if
1431 		// present
1432 
1433 		chunkLoop:
1434 		while(!chunks.empty()) {
1435 			auto chunk = chunks.front();
1436 			switch(chunks.front.stype) {
1437 				case "PLTE":
1438 					// if it is in color, palettes are
1439 					// always stored as 8 bit per channel
1440 					// RGB triplets Alpha is stored elsewhere.
1441 
1442 					// FIXME: doesn't do greyscale palettes!
1443 
1444 					enforce(chunk.size % 3 == 0);
1445 					palette.length = chunk.size / 3;
1446 
1447 					auto offset = 0;
1448 					foreach(i; 0 .. palette.length) {
1449 						palette[i] = Color(
1450 							chunk.payload[offset+0],
1451 							chunk.payload[offset+1],
1452 							chunk.payload[offset+2],
1453 							255);
1454 						offset += 3;
1455 					}
1456 				break;
1457 				case "tRNS":
1458 					// 8 bit channel in same order as
1459 					// palette
1460 
1461 					if(chunk.size > palette.length)
1462 						palette.length = chunk.size;
1463 
1464 					foreach(i, a; chunk.payload)
1465 						palette[i].a = a;
1466 				break;
1467 				case "IDAT":
1468 					// leave the datastream for later
1469 					break chunkLoop;
1470 				default:
1471 					// ignore chunks we don't care about
1472 			}
1473 			chunks.popFront();
1474 		}
1475 
1476 		this.chunks = chunks;
1477 		enforce(!chunks.empty() && chunks.front().stype == "IDAT",
1478 			"Malformed PNG file - no image data is present");
1479 	}
1480 
1481 	/// Lazily reads and decompresses the image datastream, returning chunkSize bytes of
1482 	/// it per front. It does *not* change anything, so the filter byte is still there.
1483 	///
1484 	/// If chunkSize == 0, it automatically calculates chunk size to give you data by line.
1485 	auto rawDatastreamByChunk(int chunkSize = 0) {
1486 		assert(chunks.front().stype == "IDAT");
1487 
1488 		if(chunkSize == 0)
1489 			chunkSize = bytesPerLine();
1490 
1491 		struct DatastreamByChunk(T) {
1492 			private import etc.c.zlib;
1493 			z_stream* zs; // we have to malloc this too, as dmd can move the struct, and zlib 1.2.10 is intolerant to that
1494 			int chunkSize;
1495 			int bufpos;
1496 			int plpos; // bytes eaten in current chunk payload
1497 			T chunks;
1498 			bool eoz;
1499 
1500 			this(int cs, T chunks) {
1501 				import core.stdc.stdlib : malloc;
1502 				import core.stdc.string : memset;
1503 				this.chunkSize = cs;
1504 				this.chunks = chunks;
1505 				assert(chunkSize > 0);
1506 				buffer = (cast(ubyte*)malloc(chunkSize))[0..chunkSize];
1507 				pkbuf = (cast(ubyte*)malloc(32768))[0..32768]; // arbitrary number
1508 				zs = cast(z_stream*)malloc(z_stream.sizeof);
1509 				memset(zs, 0, z_stream.sizeof);
1510 				zs.avail_in = 0;
1511 				zs.avail_out = 0;
1512 				auto res = inflateInit2(zs, 15);
1513 				assert(res == Z_OK);
1514 				popFront(); // priming
1515 			}
1516 
1517 			~this () {
1518 				version(arsdpng_debug) { import core.stdc.stdio : printf; printf("destroying lazy PNG reader...\n"); }
1519 				import core.stdc.stdlib : free;
1520 				if (zs !is null) { inflateEnd(zs); free(zs); }
1521 				if (pkbuf.ptr !is null) free(pkbuf.ptr);
1522 				if (buffer.ptr !is null) free(buffer.ptr);
1523 			}
1524 
1525 			@disable this (this); // no copies!
1526 
1527 			ubyte[] front () { return (bufpos > 0 ? buffer[0..bufpos] : null); }
1528 
1529 			ubyte[] buffer;
1530 			ubyte[] pkbuf; // we will keep some packed data here in case payload moves, lol
1531 
1532 			void popFront () {
1533 				bufpos = 0;
1534 				while (plpos != plpos.max && bufpos < chunkSize) {
1535 					// do we have some bytes in zstream?
1536 					if (zs.avail_in > 0) {
1537 						// just unpack
1538 						zs.next_out = cast(typeof(zs.next_out))(buffer.ptr+bufpos);
1539 						int rd = chunkSize-bufpos;
1540 						zs.avail_out = rd;
1541 						auto err = inflate(zs, Z_SYNC_FLUSH);
1542 						if (err != Z_STREAM_END && err != Z_OK) throw new Exception("PNG unpack error");
1543 						if (err == Z_STREAM_END) {
1544 							if(zs.avail_in != 0) {
1545 								// this thing is malformed..
1546 								// libpng would warn here "libpng warning: IDAT: Extra compressed data"
1547 								// i used to just throw with the assertion on the next line
1548 								// but now just gonna discard the extra data to be a bit more permissive
1549 								zs.avail_in = 0;
1550 							}
1551 							assert(zs.avail_in == 0);
1552 							eoz = true;
1553 						}
1554 						bufpos += rd-zs.avail_out;
1555 						continue;
1556 					}
1557 					// no more zstream bytes; do we have something in current chunk?
1558 					if (plpos == plpos.max || plpos >= chunks.front.payload.length) {
1559 						// current chunk is complete, do we have more chunks?
1560 						if (chunks.front.stype != "IDAT") break; // this chunk is not IDAT, that means that... alas
1561 						chunks.popFront(); // remove current IDAT
1562 						plpos = 0;
1563 						if (chunks.empty || chunks.front.stype != "IDAT") plpos = plpos.max; // special value
1564 						continue;
1565 					}
1566 					if (plpos < chunks.front.payload.length) {
1567 						// current chunk is not complete, get some more bytes from it
1568 						int rd = cast(int)(chunks.front.payload.length-plpos <= pkbuf.length ? chunks.front.payload.length-plpos : pkbuf.length);
1569 						assert(rd > 0);
1570 						pkbuf[0..rd] = chunks.front.payload[plpos..plpos+rd];
1571 						plpos += rd;
1572 						if (eoz) {
1573 							// we did hit end-of-stream, reinit zlib (well, well, i know that we can reset it... meh)
1574 							inflateEnd(zs);
1575 							zs.avail_in = 0;
1576 							zs.avail_out = 0;
1577 							auto res = inflateInit2(zs, 15);
1578 							assert(res == Z_OK);
1579 							eoz = false;
1580 						}
1581 						// setup read pointer
1582 						zs.next_in = cast(typeof(zs.next_in))pkbuf.ptr;
1583 						zs.avail_in = cast(uint)rd;
1584 						continue;
1585 					}
1586 					assert(0, "wtf?! we should not be here!");
1587 				}
1588 			}
1589 
1590 			bool empty () { return (bufpos == 0); }
1591 		}
1592 
1593 		return DatastreamByChunk!(typeof(chunks))(chunkSize, chunks);
1594 	}
1595 
1596 	// FIXME: no longer compiles
1597 	version(none)
1598 	auto byRgbaScanline() {
1599 		static struct ByRgbaScanline {
1600 			ReturnType!(rawDatastreamByChunk) datastream;
1601 			RgbaScanline current;
1602 			PngHeader header;
1603 			int bpp;
1604 			Color[] palette;
1605 
1606 			bool isEmpty = false;
1607 
1608 			bool empty() {
1609 				return isEmpty;
1610 			}
1611 
1612 			@property int length() {
1613 				return header.height;
1614 			}
1615 
1616 			// This is needed for the filter algorithms
1617 			immutable(ubyte)[] previousLine;
1618 
1619 			// FIXME: I think my range logic got screwed somewhere
1620 			// in the stack... this is messed up.
1621 			void popFront() {
1622 				assert(!empty());
1623 				if(datastream.empty()) {
1624 					isEmpty = true;
1625 					return;
1626 				}
1627 				current.pixels.length = header.width;
1628 
1629 				// ensure it is primed
1630 				if(datastream.front.length == 0)
1631 					datastream.popFront;
1632 
1633 				auto rawData = datastream.front();
1634 				auto filter = rawData[0];
1635 				auto data = unfilter(filter, rawData[1 .. $], previousLine, bpp);
1636 
1637 				if(data.length == 0) {
1638 					isEmpty = true;
1639 					return;
1640 				}
1641 
1642 				assert(data.length);
1643 
1644 				previousLine = data;
1645 
1646 				// FIXME: if it's rgba, this could probably be faster
1647 				assert(header.depth == 8,
1648 					"Sorry, depths other than 8 aren't implemented yet.");
1649 
1650 				auto offset = 0;
1651 				foreach(i; 0 .. header.width) {
1652 					switch(header.type) {
1653 						case 0: // greyscale
1654 						case 4: // grey with alpha
1655 							auto value = data[offset++];
1656 							current.pixels[i] = Color(
1657 								value,
1658 								value,
1659 								value,
1660 								(header.type == 4)
1661 									? data[offset++] : 255
1662 							);
1663 						break;
1664 						case 3: // indexed
1665 							current.pixels[i] = palette[data[offset++]];
1666 						break;
1667 						case 2: // truecolor
1668 						case 6: // true with alpha
1669 							current.pixels[i] = Color(
1670 								data[offset++],
1671 								data[offset++],
1672 								data[offset++],
1673 								(header.type == 6)
1674 									? data[offset++] : 255
1675 							);
1676 						break;
1677 						default:
1678 							throw new Exception("invalid png file");
1679 					}
1680 				}
1681 
1682 				assert(offset == data.length);
1683 				if(!datastream.empty())
1684 					datastream.popFront();
1685 			}
1686 
1687 			RgbaScanline front() {
1688 				return current;
1689 			}
1690 		}
1691 
1692 		assert(chunks.front.stype == "IDAT");
1693 
1694 		ByRgbaScanline range;
1695 		range.header = header;
1696 		range.bpp = bytesPerPixel;
1697 		range.palette = palette;
1698 		range.datastream = rawDatastreamByChunk(bytesPerLine());
1699 		range.popFront();
1700 
1701 		return range;
1702 	}
1703 
1704 	int bytesPerPixel() {
1705 		return .bytesPerPixel(header);
1706 	}
1707 
1708 	int bytesPerLine() {
1709 		return .bytesPerLineOfPng(header.depth, header.type, header.width);
1710 	}
1711 
1712 	PngHeader header;
1713 	Color[] palette;
1714 }
1715 
1716 // FIXME: doesn't handle interlacing... I think
1717 // note it returns the length including the filter byte!!
1718 @nogc @safe pure nothrow
1719 int bytesPerLineOfPng(ubyte depth, ubyte type, uint width) {
1720 	immutable bitsPerChannel = depth;
1721 
1722 	int bitsPerPixel = bitsPerChannel;
1723 	if(type & 2 && !(type & 1)) // in color, but no palette
1724 		bitsPerPixel *= 3;
1725 	if(type & 4) // has alpha channel
1726 		bitsPerPixel += bitsPerChannel;
1727 
1728 	immutable int sizeInBits = width * bitsPerPixel;
1729 
1730 	// need to round up to the nearest byte
1731 	int sizeInBytes = (sizeInBits + 7) / 8;
1732 
1733 	return sizeInBytes + 1; // the +1 is for the filter byte that precedes all lines
1734 }
1735 
1736 /**************************************************
1737  * Buffered input range - generic, non-image code
1738 ***************************************************/
1739 
1740 /// Is the given range a buffered input range? That is, an input range
1741 /// that also provides consumeFromFront(int) and appendToFront()
1742 ///
1743 /// THIS IS BAD CODE. I wrote it before understanding how ranges are supposed to work.
1744 template isBufferedInputRange(R) {
1745 	enum bool isBufferedInputRange =
1746 		isInputRange!(R) && is(typeof(
1747 	{
1748 		R r;
1749 		r.consumeFromFront(0);
1750 		r.appendToFront();
1751 	}()));
1752 }
1753 
1754 /// Allows appending to front on a regular input range, if that range is
1755 /// an array. It appends to the array rather than creating an array of
1756 /// arrays; it's meant to make the illusion of one continuous front rather
1757 /// than simply adding capability to walk backward to an existing input range.
1758 ///
1759 /// I think something like this should be standard; I find File.byChunk
1760 /// to be almost useless without this capability.
1761 
1762 // FIXME: what if Range is actually an array itself? We should just use
1763 // slices right into it... I guess maybe r.front() would be the whole
1764 // thing in that case though, so we would indeed be slicing in right now.
1765 // Gotta check it though.
1766 struct BufferedInputRange(Range)
1767 	if(isInputRange!(Range) && isArray!(ElementType!(Range)))
1768 {
1769 	private Range underlyingRange;
1770 	private ElementType!(Range) buffer;
1771 
1772 	/// Creates a buffer for the given range. You probably shouldn't
1773 	/// keep using the underlying range directly.
1774 	///
1775 	/// It assumes the underlying range has already been primed.
1776 	this(Range r) {
1777 		underlyingRange = r;
1778 		// Is this really correct? Want to make sure r.front
1779 		// is valid but it doesn't necessarily need to have
1780 		// more elements...
1781 		enforce(!r.empty());
1782 
1783 		buffer = r.front();
1784 		usingUnderlyingBuffer = true;
1785 	}
1786 
1787 	/// Forwards to the underlying range's empty function
1788 	bool empty() {
1789 		return underlyingRange.empty();
1790 	}
1791 
1792 	/// Returns the current buffer
1793 	ElementType!(Range) front() {
1794 		return buffer;
1795 	}
1796 
1797 	// actually, not terribly useful IMO. appendToFront calls it
1798 	// implicitly when necessary
1799 
1800 	/// Discard the current buffer and get the next item off the
1801 	/// underlying range. Be sure to call at least once to prime
1802 	/// the range (after checking if it is empty, of course)
1803 	void popFront() {
1804 		enforce(!empty());
1805 		underlyingRange.popFront();
1806 		buffer = underlyingRange.front();
1807 		usingUnderlyingBuffer = true;
1808 	}
1809 
1810 	bool usingUnderlyingBuffer = false;
1811 
1812 	/// Remove the first count items from the buffer
1813 	void consumeFromFront(int count) {
1814 		buffer = buffer[count .. $];
1815 	}
1816 
1817 	/// Append the next item available on the underlying range to
1818 	/// our buffer.
1819 	void appendToFront() {
1820 		if(buffer.length == 0) {
1821 			// may let us reuse the underlying range's buffer,
1822 			// hopefully avoiding an extra allocation
1823 			popFront();
1824 		} else {
1825 			enforce(!underlyingRange.empty());
1826 
1827 			// need to make sure underlyingRange.popFront doesn't overwrite any
1828 			// of our buffer...
1829 			if(usingUnderlyingBuffer) {
1830 				buffer = buffer.dup;
1831 				usingUnderlyingBuffer = false;
1832 			}
1833 
1834 			underlyingRange.popFront();
1835 
1836 			buffer ~= underlyingRange.front();
1837 		}
1838 	}
1839 }
1840 
1841 /**************************************************
1842  * Lower level implementations of image formats.
1843  * and associated helper functions.
1844  *
1845  * Related to the module, but not particularly
1846  * interesting, so it's at the bottom.
1847 ***************************************************/
1848 
1849 
1850 /* PNG file format implementation */
1851 
1852 //import std.zlib;
1853 import std.math;
1854 
1855 /// All PNG files are supposed to open with these bytes according to the spec
1856 static immutable(ubyte[]) PNG_MAGIC_NUMBER = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
1857 
1858 /// A PNG file consists of the magic number then a stream of chunks. This
1859 /// struct represents those chunks.
1860 struct Chunk {
1861 	uint size;
1862 	ubyte[4] type;
1863 	ubyte[] payload;
1864 	uint checksum;
1865 
1866 	/// returns the type as a string for easier comparison with literals
1867 	@nogc @safe nothrow pure
1868 	const(char)[] stype() return const {
1869 		return cast(const(char)[]) type;
1870 	}
1871 
1872 	@trusted nothrow pure /* trusted because of the cast of name to ubyte. It is copied into a new buffer anyway though so obviously harmless. */
1873 	static Chunk* create(string type, ubyte[] payload)
1874 		in {
1875 			assert(type.length == 4);
1876 		}
1877 	do {
1878 		Chunk* c = new Chunk;
1879 		c.size = cast(int) payload.length;
1880 		c.type[] = (cast(ubyte[]) type)[];
1881 		c.payload = payload;
1882 
1883 		c.checksum = crcPng(type, payload);
1884 
1885 		return c;
1886 	}
1887 
1888 	/// Puts it into the format for outputting to a file
1889 	@safe nothrow pure
1890 	ubyte[] toArray() {
1891 		ubyte[] a;
1892 		a.length = size + 12;
1893 
1894 		int pos = 0;
1895 
1896 		a[pos++] = (size & 0xff000000) >> 24;
1897 		a[pos++] = (size & 0x00ff0000) >> 16;
1898 		a[pos++] = (size & 0x0000ff00) >> 8;
1899 		a[pos++] = (size & 0x000000ff) >> 0;
1900 
1901 		a[pos .. pos + 4] = type[0 .. 4];
1902 		pos += 4;
1903 
1904 		a[pos .. pos + size] = payload[0 .. size];
1905 
1906 		pos += size;
1907 
1908 		assert(checksum);
1909 
1910 		a[pos++] = (checksum & 0xff000000) >> 24;
1911 		a[pos++] = (checksum & 0x00ff0000) >> 16;
1912 		a[pos++] = (checksum & 0x0000ff00) >> 8;
1913 		a[pos++] = (checksum & 0x000000ff) >> 0;
1914 
1915 		return a;
1916 	}
1917 }
1918 
1919 /// The first chunk in a PNG file is a header that contains this info
1920 struct PngHeader {
1921 	/// Width of the image, in pixels.
1922 	uint width;
1923 
1924 	/// Height of the image, in pixels.
1925 	uint height;
1926 
1927 	/**
1928 		This is bits per channel - per color for truecolor or grey
1929 		and per pixel for palette.
1930 
1931 		Indexed ones can have depth of 1,2,4, or 8,
1932 
1933 		Greyscale can be 1,2,4,8,16
1934 
1935 		Everything else must be 8 or 16.
1936 	*/
1937 	ubyte depth = 8;
1938 
1939 	/** Types from the PNG spec:
1940 		0 - greyscale
1941 		2 - truecolor
1942 		3 - indexed color
1943 		4 - grey with alpha
1944 		6 - true with alpha
1945 
1946 		1, 5, and 7 are invalid.
1947 
1948 		There's a kind of bitmask going on here:
1949 			If type&1, it has a palette.
1950 			If type&2, it is in color.
1951 			If type&4, it has an alpha channel in the datastream.
1952 	*/
1953 	ubyte type = 6;
1954 
1955 	ubyte compressionMethod = 0; /// should be zero
1956 	ubyte filterMethod = 0; /// should be zero
1957 	/// 0 is non interlaced, 1 if Adam7. No more are defined in the spec
1958 	ubyte interlaceMethod = 0;
1959 
1960 	pure @safe // @nogc with -dip1008 too......
1961 	static PngHeader fromChunk(in Chunk c) {
1962 		if(c.stype != "IHDR")
1963 			throw new Exception("The chunk is not an image header");
1964 
1965 		PngHeader h;
1966 		auto data = c.payload;
1967 		int pos = 0;
1968 
1969 		if(data.length != 13)
1970 			throw new Exception("Malformed PNG file - the IHDR is the wrong size");
1971 
1972 		h.width |= data[pos++] << 24;
1973 		h.width |= data[pos++] << 16;
1974 		h.width |= data[pos++] << 8;
1975 		h.width |= data[pos++] << 0;
1976 
1977 		h.height |= data[pos++] << 24;
1978 		h.height |= data[pos++] << 16;
1979 		h.height |= data[pos++] << 8;
1980 		h.height |= data[pos++] << 0;
1981 
1982 		h.depth = data[pos++];
1983 		h.type = data[pos++];
1984 		h.compressionMethod = data[pos++];
1985 		h.filterMethod = data[pos++];
1986 		h.interlaceMethod = data[pos++];
1987 
1988 		return h;
1989 	}
1990 
1991 	Chunk* toChunk() {
1992 		ubyte[] data;
1993 		data.length = 13;
1994 		int pos = 0;
1995 
1996 		data[pos++] = width >> 24;
1997 		data[pos++] = (width >> 16) & 0xff;
1998 		data[pos++] = (width >> 8) & 0xff;
1999 		data[pos++] = width & 0xff;
2000 
2001 		data[pos++] = height >> 24;
2002 		data[pos++] = (height >> 16) & 0xff;
2003 		data[pos++] = (height >> 8) & 0xff;
2004 		data[pos++] = height & 0xff;
2005 
2006 		data[pos++] = depth;
2007 		data[pos++] = type;
2008 		data[pos++] = compressionMethod;
2009 		data[pos++] = filterMethod;
2010 		data[pos++] = interlaceMethod;
2011 
2012 		assert(pos == 13);
2013 
2014 		return Chunk.create("IHDR", data);
2015 	}
2016 }
2017 
2018 /// turns a range of png scanlines into a png file in the output range. really weird
2019 void writePngLazy(OutputRange, InputRange)(ref OutputRange where, InputRange image)
2020 	if(
2021 		isOutputRange!(OutputRange, ubyte[]) &&
2022 		isInputRange!(InputRange) &&
2023 		is(ElementType!InputRange == RgbaScanline))
2024 {
2025 	import std.zlib;
2026 	where.put(PNG_MAGIC_NUMBER);
2027 	PngHeader header;
2028 
2029 	assert(!image.empty());
2030 
2031 	// using the default values for header here... FIXME not super clear
2032 
2033 	header.width = image.front.pixels.length;
2034 	header.height = image.length;
2035 
2036 	enforce(header.width > 0, "Image width <= 0");
2037 	enforce(header.height > 0, "Image height <= 0");
2038 
2039 	where.put(header.toChunk().toArray());
2040 
2041 	auto compressor = new std.zlib.Compress();
2042 	const(void)[] compressedData;
2043 	int cnt;
2044 	foreach(line; image) {
2045 		// YOU'VE GOT TO BE FUCKING KIDDING ME!
2046 		// I have to /cast/ to void[]!??!?
2047 
2048 		ubyte[] data;
2049 		data.length = 1 + header.width * 4;
2050 		data[0] = 0; // filter type
2051 		int offset = 1;
2052 		foreach(pixel; line.pixels) {
2053 			data[offset++] = pixel.r;
2054 			data[offset++] = pixel.g;
2055 			data[offset++] = pixel.b;
2056 			data[offset++] = pixel.a;
2057 		}
2058 
2059 		compressedData ~= compressor.compress(cast(void[])
2060 			data);
2061 		if(compressedData.length > 2_000) {
2062 			where.put(Chunk.create("IDAT", cast(ubyte[])
2063 				compressedData).toArray());
2064 			compressedData = null;
2065 		}
2066 
2067 		cnt++;
2068 	}
2069 
2070 	assert(cnt == header.height, format("Got %d lines instead of %d", cnt, header.height));
2071 
2072 	compressedData ~= compressor.flush();
2073 	if(compressedData.length)
2074 		where.put(Chunk.create("IDAT", cast(ubyte[])
2075 			compressedData).toArray());
2076 
2077 	where.put(Chunk.create("IEND", null).toArray());
2078 }
2079 
2080 // bKGD - palette entry for background or the RGB (16 bits each) for that. or 16 bits of grey
2081 
2082 @trusted nothrow pure @nogc /* trusted because of the cast from char to ubyte */
2083 uint crcPng(in char[] chunkName, in ubyte[] buf){
2084 	uint c = update_crc(0xffffffffL, cast(ubyte[]) chunkName);
2085 	return update_crc(c, buf) ^ 0xffffffffL;
2086 }
2087 
2088 /++
2089 	Png files apply a filter to each line in the datastream, hoping to aid in compression. This undoes that as you load.
2090 +/
2091 immutable(ubyte)[] unfilter(ubyte filterType, in ubyte[] data, in ubyte[] previousLine, int bpp) {
2092 	// Note: the overflow arithmetic on the ubytes in here is intentional
2093 	switch(filterType) {
2094 		case 0:
2095 			return data.idup; // FIXME is copying really necessary?
2096 		case 1:
2097 			auto arr = data.dup;
2098 			// first byte gets zero added to it so nothing special
2099 			foreach(i; bpp .. arr.length) {
2100 				arr[i] += arr[i - bpp];
2101 			}
2102 
2103 			return assumeUnique(arr);
2104 		case 2:
2105 			auto arr = data.dup;
2106 			if(previousLine.length)
2107 			foreach(i; 0 .. arr.length) {
2108 				arr[i] += previousLine[i];
2109 			}
2110 
2111 			return assumeUnique(arr);
2112 		case 3:
2113 			auto arr = data.dup;
2114 			foreach(i; 0 .. arr.length) {
2115 				auto left = i < bpp ? 0 : arr[i - bpp];
2116 				auto above = previousLine.length ? previousLine[i] : 0;
2117 
2118 				arr[i] += cast(ubyte) ((left + above) / 2);
2119 			}
2120 
2121 			return assumeUnique(arr);
2122 		case 4:
2123 			auto arr = data.dup;
2124 			foreach(i; 0 .. arr.length) {
2125 				ubyte prev   = i < bpp ? 0 : arr[i - bpp];
2126 				ubyte prevLL = i < bpp ? 0 : (i < previousLine.length ? previousLine[i - bpp] : 0);
2127 
2128 				arr[i] += PaethPredictor(prev, (i < previousLine.length ? previousLine[i] : 0), prevLL);
2129 			}
2130 
2131 			return assumeUnique(arr);
2132 		default:
2133 			throw new Exception("invalid PNG file, bad filter type");
2134 	}
2135 }
2136 
2137 ubyte PaethPredictor(ubyte a, ubyte b, ubyte c) {
2138 	int p = cast(int) a + b - c;
2139 	auto pa = abs(p - a);
2140 	auto pb = abs(p - b);
2141 	auto pc = abs(p - c);
2142 
2143 	if(pa <= pb && pa <= pc)
2144 		return a;
2145 	if(pb <= pc)
2146 		return b;
2147 	return c;
2148 }
2149 
2150 ///
2151 int bytesPerPixel(PngHeader header) {
2152 	immutable bitsPerChannel = header.depth;
2153 
2154 	int bitsPerPixel = bitsPerChannel;
2155 	if(header.type & 2 && !(header.type & 1)) // in color, but no palette
2156 		bitsPerPixel *= 3;
2157 	if(header.type & 4) // has alpha channel
2158 		bitsPerPixel += bitsPerChannel;
2159 
2160 	return (bitsPerPixel + 7) / 8;
2161 }