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 	p.magic[0..8] = data[0..8];
746 
747 	if(p.magic != [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
748 		throw new Exception("not a png, header wrong");
749 
750 	size_t pos = 8;
751 
752 	while(pos < data.length) {
753 		Chunk n;
754 		n.size |= data[pos++] << 24;
755 		n.size |= data[pos++] << 16;
756 		n.size |= data[pos++] << 8;
757 		n.size |= data[pos++] << 0;
758 		n.type[0..4] = data[pos..pos+4];
759 		pos += 4;
760 		n.payload.length = n.size;
761 		if(pos + n.size > data.length)
762 			throw new Exception(format("malformed png, chunk '%s' %d @ %d longer than data %d", n.type, n.size, pos, data.length));
763 		if(pos + n.size < pos)
764 			throw new Exception("uint overflow: chunk too large");
765 		n.payload[0..n.size] = data[pos..pos+n.size];
766 		pos += n.size;
767 
768 		n.checksum |= data[pos++] << 24;
769 		n.checksum |= data[pos++] << 16;
770 		n.checksum |= data[pos++] << 8;
771 		n.checksum |= data[pos++] << 0;
772 
773 		p.chunks ~= n;
774 	}
775 
776 	return p;
777 }
778 
779 /++
780 	Creates a new [PNG] object from the given header parameters, ready to receive data.
781 +/
782 PNG* blankPNG(PngHeader h) {
783 	auto p = new PNG;
784 	p.magic = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
785 
786 	Chunk c;
787 
788 	c.size = 13;
789 	c.type = ['I', 'H', 'D', 'R'];
790 
791 	c.payload.length = 13;
792 	size_t pos = 0;
793 
794 	c.payload[pos++] = h.width >> 24;
795 	c.payload[pos++] = (h.width >> 16) & 0xff;
796 	c.payload[pos++] = (h.width >> 8) & 0xff;
797 	c.payload[pos++] = h.width & 0xff;
798 
799 	c.payload[pos++] = h.height >> 24;
800 	c.payload[pos++] = (h.height >> 16) & 0xff;
801 	c.payload[pos++] = (h.height >> 8) & 0xff;
802 	c.payload[pos++] = h.height & 0xff;
803 
804 	c.payload[pos++] = h.depth;
805 	c.payload[pos++] = h.type;
806 	c.payload[pos++] = h.compressionMethod;
807 	c.payload[pos++] = h.filterMethod;
808 	c.payload[pos++] = h.interlaceMethod;
809 
810 
811 	c.checksum = crc("IHDR", c.payload);
812 
813 	p.chunks ~= c;
814 
815 	return p;
816 }
817 
818 /+
819 	Implementation helper for creating png files.
820 
821 	Its API is subject to change; it would be private except it might be useful to you.
822 +/
823 // should NOT have any idata already.
824 // FIXME: doesn't handle palettes
825 void addImageDatastreamToPng(const(ubyte)[] data, PNG* png, bool addIend = true) {
826 	// we need to go through the lines and add the filter byte
827 	// then compress it into an IDAT chunk
828 	// then add the IEND chunk
829 	import std.zlib;
830 
831 	PngHeader h = getHeader(png);
832 
833 	if(h.depth == 0)
834 		throw new Exception("depth of zero makes no sense");
835 	if(h.width == 0)
836 		throw new Exception("width zero?!!?!?!");
837 
838 	int multiplier;
839 	size_t bytesPerLine;
840 	switch(h.type) {
841 		case 0:
842 			multiplier = 1;
843 		break;
844 		case 2:
845 			multiplier = 3;
846 		break;
847 		case 3:
848 			multiplier = 1;
849 		break;
850 		case 4:
851 			multiplier = 2;
852 		break;
853 		case 6:
854 			multiplier = 4;
855 		break;
856 		default: assert(0);
857 	}
858 
859 	bytesPerLine = h.width * multiplier * h.depth / 8;
860 	if((h.width * multiplier * h.depth) % 8 != 0)
861 		bytesPerLine += 1;
862 
863 	assert(bytesPerLine >= 1);
864 	Chunk dat;
865 	dat.type = ['I', 'D', 'A', 'T'];
866 	size_t pos = 0;
867 
868 	const(ubyte)[] output;
869 	while(pos+bytesPerLine <= data.length) {
870 		output ~= 0;
871 		output ~= data[pos..pos+bytesPerLine];
872 		pos += bytesPerLine;
873 	}
874 
875 	auto com = cast(ubyte[]) compress(output);
876 	dat.size = cast(int) com.length;
877 	dat.payload = com;
878 	dat.checksum = crc("IDAT", dat.payload);
879 
880 	png.chunks ~= dat;
881 
882 	if(addIend) {
883 		Chunk c;
884 
885 		c.size = 0;
886 		c.type = ['I', 'E', 'N', 'D'];
887 		c.checksum = crc("IEND", c.payload);
888 
889 		png.chunks ~= c;
890 	}
891 
892 }
893 
894 deprecated alias PngHeader PNGHeader;
895 
896 // bKGD - palette entry for background or the RGB (16 bits each) for that. or 16 bits of grey
897 
898 /+
899 	Uncompresses the raw datastream out of the file chunks, but does not continue processing it, so the scanlines are still filtered, etc.
900 +/
901 ubyte[] getDatastream(PNG* p) {
902 	import std.zlib;
903 	ubyte[] compressed;
904 
905 	foreach(c; p.chunks) {
906 		if(c.stype != "IDAT")
907 			continue;
908 		compressed ~= c.payload;
909 	}
910 
911 	return cast(ubyte[]) uncompress(compressed);
912 }
913 
914 /+
915 	Gets a raw datastream out of a 8 bpp png. See also [getANDMask]
916 +/
917 // FIXME: Assuming 8 bits per pixel
918 ubyte[] getUnfilteredDatastream(PNG* p) {
919 	PngHeader h = getHeader(p);
920 	assert(h.filterMethod == 0);
921 
922 	assert(h.type == 3); // FIXME
923 	assert(h.depth == 8); // FIXME
924 
925 	ubyte[] data = getDatastream(p);
926 	ubyte[] ufdata = new ubyte[data.length - h.height];
927 
928 	int bytesPerLine = cast(int) ufdata.length / h.height;
929 
930 	int pos = 0, pos2 = 0;
931 	for(int a = 0; a < h.height; a++) {
932 		assert(data[pos2] == 0);
933 		ufdata[pos..pos+bytesPerLine] = data[pos2+1..pos2+bytesPerLine+1];
934 		pos+= bytesPerLine;
935 		pos2+= bytesPerLine + 1;
936 	}
937 
938 	return ufdata;
939 }
940 
941 /+
942 	Gets the unfiltered raw datastream for conversion to Windows ico files. See also [getANDMask] and [fetchPaletteWin32].
943 +/
944 ubyte[] getFlippedUnfilteredDatastream(PNG* p) {
945 	PngHeader h = getHeader(p);
946 	assert(h.filterMethod == 0);
947 
948 	assert(h.type == 3); // FIXME
949 	assert(h.depth == 8 || h.depth == 4); // FIXME
950 
951 	ubyte[] data = getDatastream(p);
952 	ubyte[] ufdata = new ubyte[data.length - h.height];
953 
954 	int bytesPerLine = cast(int) ufdata.length / h.height;
955 
956 
957 	int pos = cast(int) ufdata.length - bytesPerLine, pos2 = 0;
958 	for(int a = 0; a < h.height; a++) {
959 		assert(data[pos2] == 0);
960 		ufdata[pos..pos+bytesPerLine] = data[pos2+1..pos2+bytesPerLine+1];
961 		pos-= bytesPerLine;
962 		pos2+= bytesPerLine + 1;
963 	}
964 
965 	return ufdata;
966 }
967 
968 ubyte getHighNybble(ubyte a) {
969 	return cast(ubyte)(a >> 4); // FIXME
970 }
971 
972 ubyte getLowNybble(ubyte a) {
973 	return a & 0x0f;
974 }
975 
976 /++
977 	Takes the transparency info and returns an AND mask suitable for use in a Windows ico
978 +/
979 ubyte[] getANDMask(PNG* p) {
980 	PngHeader h = getHeader(p);
981 	assert(h.filterMethod == 0);
982 
983 	assert(h.type == 3); // FIXME
984 	assert(h.depth == 8 || h.depth == 4); // FIXME
985 
986 	assert(h.width % 8 == 0); // might actually be %2
987 
988 	ubyte[] data = getDatastream(p);
989 	ubyte[] ufdata = new ubyte[h.height*((((h.width+7)/8)+3)&~3)]; // gotta pad to DWORDs...
990 
991 	Color[] colors = fetchPalette(p);
992 
993 	int pos = 0, pos2 = (h.width/((h.depth == 8) ? 1 : 2)+1)*(h.height-1);
994 	bool bits = false;
995 	for(int a = 0; a < h.height; a++) {
996 		assert(data[pos2++] == 0);
997 		for(int b = 0; b < h.width; b++) {
998 			if(h.depth == 4) {
999 				ufdata[pos/8] |= ((colors[bits? getLowNybble(data[pos2]) : getHighNybble(data[pos2])].a <= 30) << (7-(pos%8)));
1000 			} else
1001 				ufdata[pos/8] |= ((colors[data[pos2]].a == 0) << (7-(pos%8)));
1002 			pos++;
1003 			if(h.depth == 4) {
1004 				if(bits) {
1005 					pos2++;
1006 				}
1007 				bits = !bits;
1008 			} else
1009 				pos2++;
1010 		}
1011 
1012 		int pad = 0;
1013 		for(; pad < ((pos/8) % 4); pad++) {
1014 			ufdata[pos/8] = 0;
1015 			pos+=8;
1016 		}
1017 		if(h.depth == 4)
1018 			pos2 -= h.width + 2;
1019 		else
1020 			pos2-= 2*(h.width) +2;
1021 	}
1022 
1023 	return ufdata;
1024 }
1025 
1026 // Done with assumption
1027 
1028 /++
1029 	Gets the parsed [PngHeader] data out of the [PNG] object.
1030 +/
1031 @nogc @safe pure
1032 PngHeader getHeader(PNG* p) {
1033 	PngHeader h;
1034 	ubyte[] data = p.getChunkNullable("IHDR").payload;
1035 
1036 	int pos = 0;
1037 
1038 	h.width |= data[pos++] << 24;
1039 	h.width |= data[pos++] << 16;
1040 	h.width |= data[pos++] << 8;
1041 	h.width |= data[pos++] << 0;
1042 
1043 	h.height |= data[pos++] << 24;
1044 	h.height |= data[pos++] << 16;
1045 	h.height |= data[pos++] << 8;
1046 	h.height |= data[pos++] << 0;
1047 
1048 	h.depth = data[pos++];
1049 	h.type = data[pos++];
1050 	h.compressionMethod = data[pos++];
1051 	h.filterMethod = data[pos++];
1052 	h.interlaceMethod = data[pos++];
1053 
1054 	return h;
1055 }
1056 
1057 /*
1058 struct Color {
1059 	ubyte r;
1060 	ubyte g;
1061 	ubyte b;
1062 	ubyte a;
1063 }
1064 */
1065 
1066 /+
1067 class Image {
1068 	Color[][] trueColorData;
1069 	ubyte[] indexData;
1070 
1071 	Color[] palette;
1072 
1073 	uint width;
1074 	uint height;
1075 
1076 	this(uint w, uint h) {}
1077 }
1078 
1079 Image fromPNG(PNG* p) {
1080 
1081 }
1082 
1083 PNG* toPNG(Image i) {
1084 
1085 }
1086 +/		struct RGBQUAD {
1087 			ubyte rgbBlue;
1088 			ubyte rgbGreen;
1089 			ubyte rgbRed;
1090 			ubyte rgbReserved;
1091 		}
1092 
1093 /+
1094 	Gets the palette out of the format Windows expects for bmp and ico files.
1095 
1096 	See also getANDMask
1097 +/
1098 RGBQUAD[] fetchPaletteWin32(PNG* p) {
1099 	RGBQUAD[] colors;
1100 
1101 	auto palette = p.getChunk("PLTE");
1102 
1103 	colors.length = (palette.size) / 3;
1104 
1105 	for(int i = 0; i < colors.length; i++) {
1106 		colors[i].rgbRed = palette.payload[i*3+0];
1107 		colors[i].rgbGreen = palette.payload[i*3+1];
1108 		colors[i].rgbBlue = palette.payload[i*3+2];
1109 		colors[i].rgbReserved = 0;
1110 	}
1111 
1112 	return colors;
1113 
1114 }
1115 
1116 /++
1117 	Extracts the palette chunk from a PNG object as an array of RGBA quads.
1118 
1119 	See_Also:
1120 		[replacePalette]
1121 +/
1122 Color[] fetchPalette(PNG* p) {
1123 	Color[] colors;
1124 
1125 	auto header = getHeader(p);
1126 	if(header.type == 0) { // greyscale
1127 		colors.length = 256;
1128 		foreach(i; 0..256)
1129 			colors[i] = Color(cast(ubyte) i, cast(ubyte) i, cast(ubyte) i);
1130 		return colors;
1131 	}
1132 
1133 	// assuming this is indexed
1134 	assert(header.type == 3);
1135 
1136 	auto palette = p.getChunk("PLTE");
1137 
1138 	Chunk* alpha = p.getChunkNullable("tRNS");
1139 
1140 	colors.length = palette.size / 3;
1141 
1142 	for(int i = 0; i < colors.length; i++) {
1143 		colors[i].r = palette.payload[i*3+0];
1144 		colors[i].g = palette.payload[i*3+1];
1145 		colors[i].b = palette.payload[i*3+2];
1146 		if(alpha !is null && i < alpha.size)
1147 			colors[i].a = alpha.payload[i];
1148 		else
1149 			colors[i].a = 255;
1150 
1151 		//writefln("%2d: %3d %3d %3d %3d", i, colors[i].r, colors[i].g, colors[i].b, colors[i].a);
1152 	}
1153 
1154 	return colors;
1155 }
1156 
1157 /++
1158 	Replaces the palette data in a [PNG] object.
1159 
1160 	See_Also:
1161 		[fetchPalette]
1162 +/
1163 void replacePalette(PNG* p, Color[] colors) {
1164 	auto palette = p.getChunk("PLTE");
1165 	auto alpha = p.getChunkNullable("tRNS");
1166 
1167 	//import std.string;
1168 	//assert(0, format("%s %s", colors.length, alpha.size));
1169 	//assert(colors.length == alpha.size);
1170 	if(alpha) {
1171 		alpha.size = cast(int) colors.length;
1172 		alpha.payload.length = colors.length; // we make sure there's room for our simple method below
1173 	}
1174 	p.length = 0; // so write will recalculate
1175 
1176 	for(int i = 0; i < colors.length; i++) {
1177 		palette.payload[i*3+0] = colors[i].r;
1178 		palette.payload[i*3+1] = colors[i].g;
1179 		palette.payload[i*3+2] = colors[i].b;
1180 		if(alpha)
1181 			alpha.payload[i] = colors[i].a;
1182 	}
1183 
1184 	palette.checksum = crc("PLTE", palette.payload);
1185 	if(alpha)
1186 		alpha.checksum = crc("tRNS", alpha.payload);
1187 }
1188 
1189 @safe nothrow pure @nogc
1190 uint update_crc(in uint crc, in ubyte[] buf){
1191 	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];
1192 
1193 	uint c = crc;
1194 
1195 	foreach(b; buf)
1196 		c = crc_table[(c ^ b) & 0xff] ^ (c >> 8);
1197 
1198 	return c;
1199 }
1200 
1201 /+
1202 	Figures out the crc for a chunk. Used internally.
1203 
1204 	lol is just the chunk name
1205 +/
1206 uint crc(in string lol, in ubyte[] buf){
1207 	uint c = update_crc(0xffffffffL, cast(ubyte[]) lol);
1208 	return update_crc(c, buf) ^ 0xffffffffL;
1209 }
1210 
1211 
1212 /* former module arsd.lazypng follows */
1213 
1214 // this is like png.d but all range based so more complicated...
1215 // and I don't remember how to actually use it.
1216 
1217 // some day I'll prolly merge it with png.d but for now just throwing it up there
1218 
1219 //module arsd.lazypng;
1220 
1221 //import arsd.color;
1222 
1223 //import std.stdio;
1224 
1225 import std.range;
1226 import std.traits;
1227 import std.exception;
1228 import std.string;
1229 //import std.conv;
1230 
1231 /*
1232 struct Color {
1233 	ubyte r;
1234 	ubyte g;
1235 	ubyte b;
1236 	ubyte a;
1237 
1238 	string toString() {
1239 		return format("#%2x%2x%2x %2x", r, g, b, a);
1240 	}
1241 }
1242 */
1243 
1244 //import arsd.simpledisplay;
1245 
1246 struct RgbaScanline {
1247 	Color[] pixels;
1248 }
1249 
1250 
1251 // lazy range to convert some png scanlines into greyscale. part of an experiment i didn't do much with but still use sometimes.
1252 auto convertToGreyscale(ImageLines)(ImageLines lines)
1253 	if(isInputRange!ImageLines && is(ElementType!ImageLines == RgbaScanline))
1254 {
1255 	struct GreyscaleLines {
1256 		ImageLines lines;
1257 		bool isEmpty;
1258 		this(ImageLines lines) {
1259 			this.lines = lines;
1260 			if(!empty())
1261 				popFront(); // prime
1262 		}
1263 
1264 		int length() {
1265 			return lines.length;
1266 		}
1267 
1268 		bool empty() {
1269 			return isEmpty;
1270 		}
1271 
1272 		RgbaScanline current;
1273 		RgbaScanline front() {
1274 			return current;
1275 		}
1276 
1277 		void popFront() {
1278 			if(lines.empty()) {
1279 				isEmpty = true;
1280 				return;
1281 			}
1282 			auto old = lines.front();
1283 			current.pixels.length = old.pixels.length;
1284 			foreach(i, c; old.pixels) {
1285 				ubyte v = cast(ubyte) (
1286 					cast(int) c.r * 0.30 +
1287 					cast(int) c.g * 0.59 +
1288 					cast(int) c.b * 0.11);
1289 				current.pixels[i] = Color(v, v, v, c.a);
1290 			}
1291 			lines.popFront;
1292 		}
1293 	}
1294 
1295 	return GreyscaleLines(lines);
1296 }
1297 
1298 
1299 
1300 
1301 /// Lazily breaks the buffered input range into
1302 /// png chunks, as defined in the PNG spec
1303 ///
1304 /// Note: bufferedInputRange is defined in this file too.
1305 LazyPngChunks!(Range) readPngChunks(Range)(Range r)
1306 	if(isBufferedInputRange!(Range) && is(ElementType!(Range) == ubyte[]))
1307 {
1308 	// First, we need to check the header
1309 	// Then we'll lazily pull the chunks
1310 
1311 	while(r.front.length < 8) {
1312 		enforce(!r.empty(), "This isn't big enough to be a PNG file");
1313 		r.appendToFront();
1314 	}
1315 
1316 	enforce(r.front[0..8] == PNG_MAGIC_NUMBER,
1317 		"The file's magic number doesn't look like PNG");
1318 
1319 	r.consumeFromFront(8);
1320 
1321 	return LazyPngChunks!Range(r);
1322 }
1323 
1324 /// Same as above, but takes a regular input range instead of a buffered one.
1325 /// Provided for easier compatibility with standard input ranges
1326 /// (for example, std.stdio.File.byChunk)
1327 auto readPngChunks(Range)(Range r)
1328 	if(!isBufferedInputRange!(Range) && isInputRange!(Range))
1329 {
1330 	return readPngChunks(BufferedInputRange!Range(r));
1331 }
1332 
1333 /// Given an input range of bytes, return a lazy PNG file
1334 auto pngFromBytes(Range)(Range r)
1335 	if(isInputRange!(Range) && is(ElementType!Range == ubyte[]))
1336 {
1337 	auto chunks = readPngChunks(r);
1338 	auto file = LazyPngFile!(typeof(chunks))(chunks);
1339 
1340 	return file;
1341 }
1342 
1343 /// See: [readPngChunks]
1344 struct LazyPngChunks(T)
1345 	if(isBufferedInputRange!(T) && is(ElementType!T == ubyte[]))
1346 {
1347 	T bytes;
1348 	Chunk current;
1349 
1350 	this(T range) {
1351 		bytes = range;
1352 		popFront(); // priming it
1353 	}
1354 
1355 	Chunk front() {
1356 		return current;
1357 	}
1358 
1359 	bool empty() {
1360 		return (bytes.front.length == 0 && bytes.empty);
1361 	}
1362 
1363 	void popFront() {
1364 		enforce(!empty());
1365 
1366 		while(bytes.front().length < 4) {
1367 			enforce(!bytes.empty,
1368 				format("Malformed PNG file - chunk size too short (%s < 4)",
1369 					bytes.front().length));
1370 			bytes.appendToFront();
1371 		}
1372 
1373 		Chunk n;
1374 		n.size |= bytes.front()[0] << 24;
1375 		n.size |= bytes.front()[1] << 16;
1376 		n.size |= bytes.front()[2] << 8;
1377 		n.size |= bytes.front()[3] << 0;
1378 
1379 		bytes.consumeFromFront(4);
1380 
1381 		while(bytes.front().length < n.size + 8) {
1382 			enforce(!bytes.empty,
1383 				format("Malformed PNG file - chunk too short (%s < %s)",
1384 					bytes.front.length, n.size));
1385 			bytes.appendToFront();
1386 		}
1387 		n.type[0 .. 4] = bytes.front()[0 .. 4];
1388 		bytes.consumeFromFront(4);
1389 
1390 		n.payload.length = n.size;
1391 		n.payload[0 .. n.size] = bytes.front()[0 .. n.size];
1392 		bytes.consumeFromFront(n.size);
1393 
1394 		n.checksum |= bytes.front()[0] << 24;
1395 		n.checksum |= bytes.front()[1] << 16;
1396 		n.checksum |= bytes.front()[2] << 8;
1397 		n.checksum |= bytes.front()[3] << 0;
1398 
1399 		bytes.consumeFromFront(4);
1400 
1401 		enforce(n.checksum == crcPng(n.stype, n.payload), "Chunk checksum didn't match");
1402 
1403 		current = n;
1404 	}
1405 }
1406 
1407 /// Lazily reads out basic info from a png (header, palette, image data)
1408 /// It will only allocate memory to read a palette, and only copies on
1409 /// the header and the palette. It ignores everything else.
1410 ///
1411 /// FIXME: it doesn't handle interlaced files.
1412 struct LazyPngFile(LazyPngChunksProvider)
1413 	if(isInputRange!(LazyPngChunksProvider) &&
1414 		is(ElementType!(LazyPngChunksProvider) == Chunk))
1415 {
1416 	LazyPngChunksProvider chunks;
1417 
1418 	this(LazyPngChunksProvider chunks) {
1419 		enforce(!chunks.empty(), "There are no chunks in this png");
1420 
1421 		header = PngHeader.fromChunk(chunks.front());
1422 		chunks.popFront();
1423 
1424 		// And now, find the datastream so we're primed for lazy
1425 		// reading, saving the palette and transparency info, if
1426 		// present
1427 
1428 		chunkLoop:
1429 		while(!chunks.empty()) {
1430 			auto chunk = chunks.front();
1431 			switch(chunks.front.stype) {
1432 				case "PLTE":
1433 					// if it is in color, palettes are
1434 					// always stored as 8 bit per channel
1435 					// RGB triplets Alpha is stored elsewhere.
1436 
1437 					// FIXME: doesn't do greyscale palettes!
1438 
1439 					enforce(chunk.size % 3 == 0);
1440 					palette.length = chunk.size / 3;
1441 
1442 					auto offset = 0;
1443 					foreach(i; 0 .. palette.length) {
1444 						palette[i] = Color(
1445 							chunk.payload[offset+0],
1446 							chunk.payload[offset+1],
1447 							chunk.payload[offset+2],
1448 							255);
1449 						offset += 3;
1450 					}
1451 				break;
1452 				case "tRNS":
1453 					// 8 bit channel in same order as
1454 					// palette
1455 
1456 					if(chunk.size > palette.length)
1457 						palette.length = chunk.size;
1458 
1459 					foreach(i, a; chunk.payload)
1460 						palette[i].a = a;
1461 				break;
1462 				case "IDAT":
1463 					// leave the datastream for later
1464 					break chunkLoop;
1465 				default:
1466 					// ignore chunks we don't care about
1467 			}
1468 			chunks.popFront();
1469 		}
1470 
1471 		this.chunks = chunks;
1472 		enforce(!chunks.empty() && chunks.front().stype == "IDAT",
1473 			"Malformed PNG file - no image data is present");
1474 	}
1475 
1476 	/// Lazily reads and decompresses the image datastream, returning chunkSize bytes of
1477 	/// it per front. It does *not* change anything, so the filter byte is still there.
1478 	///
1479 	/// If chunkSize == 0, it automatically calculates chunk size to give you data by line.
1480 	auto rawDatastreamByChunk(int chunkSize = 0) {
1481 		assert(chunks.front().stype == "IDAT");
1482 
1483 		if(chunkSize == 0)
1484 			chunkSize = bytesPerLine();
1485 
1486 		struct DatastreamByChunk(T) {
1487 			private import etc.c.zlib;
1488 			z_stream* zs; // we have to malloc this too, as dmd can move the struct, and zlib 1.2.10 is intolerant to that
1489 			int chunkSize;
1490 			int bufpos;
1491 			int plpos; // bytes eaten in current chunk payload
1492 			T chunks;
1493 			bool eoz;
1494 
1495 			this(int cs, T chunks) {
1496 				import core.stdc.stdlib : malloc;
1497 				import core.stdc.string : memset;
1498 				this.chunkSize = cs;
1499 				this.chunks = chunks;
1500 				assert(chunkSize > 0);
1501 				buffer = (cast(ubyte*)malloc(chunkSize))[0..chunkSize];
1502 				pkbuf = (cast(ubyte*)malloc(32768))[0..32768]; // arbitrary number
1503 				zs = cast(z_stream*)malloc(z_stream.sizeof);
1504 				memset(zs, 0, z_stream.sizeof);
1505 				zs.avail_in = 0;
1506 				zs.avail_out = 0;
1507 				auto res = inflateInit2(zs, 15);
1508 				assert(res == Z_OK);
1509 				popFront(); // priming
1510 			}
1511 
1512 			~this () {
1513 				version(arsdpng_debug) { import core.stdc.stdio : printf; printf("destroying lazy PNG reader...\n"); }
1514 				import core.stdc.stdlib : free;
1515 				if (zs !is null) { inflateEnd(zs); free(zs); }
1516 				if (pkbuf.ptr !is null) free(pkbuf.ptr);
1517 				if (buffer.ptr !is null) free(buffer.ptr);
1518 			}
1519 
1520 			@disable this (this); // no copies!
1521 
1522 			ubyte[] front () { return (bufpos > 0 ? buffer[0..bufpos] : null); }
1523 
1524 			ubyte[] buffer;
1525 			ubyte[] pkbuf; // we will keep some packed data here in case payload moves, lol
1526 
1527 			void popFront () {
1528 				bufpos = 0;
1529 				while (plpos != plpos.max && bufpos < chunkSize) {
1530 					// do we have some bytes in zstream?
1531 					if (zs.avail_in > 0) {
1532 						// just unpack
1533 						zs.next_out = cast(typeof(zs.next_out))(buffer.ptr+bufpos);
1534 						int rd = chunkSize-bufpos;
1535 						zs.avail_out = rd;
1536 						auto err = inflate(zs, Z_SYNC_FLUSH);
1537 						if (err != Z_STREAM_END && err != Z_OK) throw new Exception("PNG unpack error");
1538 						if (err == Z_STREAM_END) {
1539 							if(zs.avail_in != 0) {
1540 								// this thing is malformed..
1541 								// libpng would warn here "libpng warning: IDAT: Extra compressed data"
1542 								// i used to just throw with the assertion on the next line
1543 								// but now just gonna discard the extra data to be a bit more permissive
1544 								zs.avail_in = 0;
1545 							}
1546 							assert(zs.avail_in == 0);
1547 							eoz = true;
1548 						}
1549 						bufpos += rd-zs.avail_out;
1550 						continue;
1551 					}
1552 					// no more zstream bytes; do we have something in current chunk?
1553 					if (plpos == plpos.max || plpos >= chunks.front.payload.length) {
1554 						// current chunk is complete, do we have more chunks?
1555 						if (chunks.front.stype != "IDAT") break; // this chunk is not IDAT, that means that... alas
1556 						chunks.popFront(); // remove current IDAT
1557 						plpos = 0;
1558 						if (chunks.empty || chunks.front.stype != "IDAT") plpos = plpos.max; // special value
1559 						continue;
1560 					}
1561 					if (plpos < chunks.front.payload.length) {
1562 						// current chunk is not complete, get some more bytes from it
1563 						int rd = cast(int)(chunks.front.payload.length-plpos <= pkbuf.length ? chunks.front.payload.length-plpos : pkbuf.length);
1564 						assert(rd > 0);
1565 						pkbuf[0..rd] = chunks.front.payload[plpos..plpos+rd];
1566 						plpos += rd;
1567 						if (eoz) {
1568 							// we did hit end-of-stream, reinit zlib (well, well, i know that we can reset it... meh)
1569 							inflateEnd(zs);
1570 							zs.avail_in = 0;
1571 							zs.avail_out = 0;
1572 							auto res = inflateInit2(zs, 15);
1573 							assert(res == Z_OK);
1574 							eoz = false;
1575 						}
1576 						// setup read pointer
1577 						zs.next_in = cast(typeof(zs.next_in))pkbuf.ptr;
1578 						zs.avail_in = cast(uint)rd;
1579 						continue;
1580 					}
1581 					assert(0, "wtf?! we should not be here!");
1582 				}
1583 			}
1584 
1585 			bool empty () { return (bufpos == 0); }
1586 		}
1587 
1588 		return DatastreamByChunk!(typeof(chunks))(chunkSize, chunks);
1589 	}
1590 
1591 	// FIXME: no longer compiles
1592 	version(none)
1593 	auto byRgbaScanline() {
1594 		static struct ByRgbaScanline {
1595 			ReturnType!(rawDatastreamByChunk) datastream;
1596 			RgbaScanline current;
1597 			PngHeader header;
1598 			int bpp;
1599 			Color[] palette;
1600 
1601 			bool isEmpty = false;
1602 
1603 			bool empty() {
1604 				return isEmpty;
1605 			}
1606 
1607 			@property int length() {
1608 				return header.height;
1609 			}
1610 
1611 			// This is needed for the filter algorithms
1612 			immutable(ubyte)[] previousLine;
1613 
1614 			// FIXME: I think my range logic got screwed somewhere
1615 			// in the stack... this is messed up.
1616 			void popFront() {
1617 				assert(!empty());
1618 				if(datastream.empty()) {
1619 					isEmpty = true;
1620 					return;
1621 				}
1622 				current.pixels.length = header.width;
1623 
1624 				// ensure it is primed
1625 				if(datastream.front.length == 0)
1626 					datastream.popFront;
1627 
1628 				auto rawData = datastream.front();
1629 				auto filter = rawData[0];
1630 				auto data = unfilter(filter, rawData[1 .. $], previousLine, bpp);
1631 
1632 				if(data.length == 0) {
1633 					isEmpty = true;
1634 					return;
1635 				}
1636 
1637 				assert(data.length);
1638 
1639 				previousLine = data;
1640 
1641 				// FIXME: if it's rgba, this could probably be faster
1642 				assert(header.depth == 8,
1643 					"Sorry, depths other than 8 aren't implemented yet.");
1644 
1645 				auto offset = 0;
1646 				foreach(i; 0 .. header.width) {
1647 					switch(header.type) {
1648 						case 0: // greyscale
1649 						case 4: // grey with alpha
1650 							auto value = data[offset++];
1651 							current.pixels[i] = Color(
1652 								value,
1653 								value,
1654 								value,
1655 								(header.type == 4)
1656 									? data[offset++] : 255
1657 							);
1658 						break;
1659 						case 3: // indexed
1660 							current.pixels[i] = palette[data[offset++]];
1661 						break;
1662 						case 2: // truecolor
1663 						case 6: // true with alpha
1664 							current.pixels[i] = Color(
1665 								data[offset++],
1666 								data[offset++],
1667 								data[offset++],
1668 								(header.type == 6)
1669 									? data[offset++] : 255
1670 							);
1671 						break;
1672 						default:
1673 							throw new Exception("invalid png file");
1674 					}
1675 				}
1676 
1677 				assert(offset == data.length);
1678 				if(!datastream.empty())
1679 					datastream.popFront();
1680 			}
1681 
1682 			RgbaScanline front() {
1683 				return current;
1684 			}
1685 		}
1686 
1687 		assert(chunks.front.stype == "IDAT");
1688 
1689 		ByRgbaScanline range;
1690 		range.header = header;
1691 		range.bpp = bytesPerPixel;
1692 		range.palette = palette;
1693 		range.datastream = rawDatastreamByChunk(bytesPerLine());
1694 		range.popFront();
1695 
1696 		return range;
1697 	}
1698 
1699 	int bytesPerPixel() {
1700 		return .bytesPerPixel(header);
1701 	}
1702 
1703 	int bytesPerLine() {
1704 		return .bytesPerLineOfPng(header.depth, header.type, header.width);
1705 	}
1706 
1707 	PngHeader header;
1708 	Color[] palette;
1709 }
1710 
1711 // FIXME: doesn't handle interlacing... I think
1712 // note it returns the length including the filter byte!!
1713 @nogc @safe pure nothrow
1714 int bytesPerLineOfPng(ubyte depth, ubyte type, uint width) {
1715 	immutable bitsPerChannel = depth;
1716 
1717 	int bitsPerPixel = bitsPerChannel;
1718 	if(type & 2 && !(type & 1)) // in color, but no palette
1719 		bitsPerPixel *= 3;
1720 	if(type & 4) // has alpha channel
1721 		bitsPerPixel += bitsPerChannel;
1722 
1723 	immutable int sizeInBits = width * bitsPerPixel;
1724 
1725 	// need to round up to the nearest byte
1726 	int sizeInBytes = (sizeInBits + 7) / 8;
1727 
1728 	return sizeInBytes + 1; // the +1 is for the filter byte that precedes all lines
1729 }
1730 
1731 /**************************************************
1732  * Buffered input range - generic, non-image code
1733 ***************************************************/
1734 
1735 /// Is the given range a buffered input range? That is, an input range
1736 /// that also provides consumeFromFront(int) and appendToFront()
1737 ///
1738 /// THIS IS BAD CODE. I wrote it before understanding how ranges are supposed to work.
1739 template isBufferedInputRange(R) {
1740 	enum bool isBufferedInputRange =
1741 		isInputRange!(R) && is(typeof(
1742 	{
1743 		R r;
1744 		r.consumeFromFront(0);
1745 		r.appendToFront();
1746 	}()));
1747 }
1748 
1749 /// Allows appending to front on a regular input range, if that range is
1750 /// an array. It appends to the array rather than creating an array of
1751 /// arrays; it's meant to make the illusion of one continuous front rather
1752 /// than simply adding capability to walk backward to an existing input range.
1753 ///
1754 /// I think something like this should be standard; I find File.byChunk
1755 /// to be almost useless without this capability.
1756 
1757 // FIXME: what if Range is actually an array itself? We should just use
1758 // slices right into it... I guess maybe r.front() would be the whole
1759 // thing in that case though, so we would indeed be slicing in right now.
1760 // Gotta check it though.
1761 struct BufferedInputRange(Range)
1762 	if(isInputRange!(Range) && isArray!(ElementType!(Range)))
1763 {
1764 	private Range underlyingRange;
1765 	private ElementType!(Range) buffer;
1766 
1767 	/// Creates a buffer for the given range. You probably shouldn't
1768 	/// keep using the underlying range directly.
1769 	///
1770 	/// It assumes the underlying range has already been primed.
1771 	this(Range r) {
1772 		underlyingRange = r;
1773 		// Is this really correct? Want to make sure r.front
1774 		// is valid but it doesn't necessarily need to have
1775 		// more elements...
1776 		enforce(!r.empty());
1777 
1778 		buffer = r.front();
1779 		usingUnderlyingBuffer = true;
1780 	}
1781 
1782 	/// Forwards to the underlying range's empty function
1783 	bool empty() {
1784 		return underlyingRange.empty();
1785 	}
1786 
1787 	/// Returns the current buffer
1788 	ElementType!(Range) front() {
1789 		return buffer;
1790 	}
1791 
1792 	// actually, not terribly useful IMO. appendToFront calls it
1793 	// implicitly when necessary
1794 
1795 	/// Discard the current buffer and get the next item off the
1796 	/// underlying range. Be sure to call at least once to prime
1797 	/// the range (after checking if it is empty, of course)
1798 	void popFront() {
1799 		enforce(!empty());
1800 		underlyingRange.popFront();
1801 		buffer = underlyingRange.front();
1802 		usingUnderlyingBuffer = true;
1803 	}
1804 
1805 	bool usingUnderlyingBuffer = false;
1806 
1807 	/// Remove the first count items from the buffer
1808 	void consumeFromFront(int count) {
1809 		buffer = buffer[count .. $];
1810 	}
1811 
1812 	/// Append the next item available on the underlying range to
1813 	/// our buffer.
1814 	void appendToFront() {
1815 		if(buffer.length == 0) {
1816 			// may let us reuse the underlying range's buffer,
1817 			// hopefully avoiding an extra allocation
1818 			popFront();
1819 		} else {
1820 			enforce(!underlyingRange.empty());
1821 
1822 			// need to make sure underlyingRange.popFront doesn't overwrite any
1823 			// of our buffer...
1824 			if(usingUnderlyingBuffer) {
1825 				buffer = buffer.dup;
1826 				usingUnderlyingBuffer = false;
1827 			}
1828 
1829 			underlyingRange.popFront();
1830 
1831 			buffer ~= underlyingRange.front();
1832 		}
1833 	}
1834 }
1835 
1836 /**************************************************
1837  * Lower level implementations of image formats.
1838  * and associated helper functions.
1839  *
1840  * Related to the module, but not particularly
1841  * interesting, so it's at the bottom.
1842 ***************************************************/
1843 
1844 
1845 /* PNG file format implementation */
1846 
1847 //import std.zlib;
1848 import std.math;
1849 
1850 /// All PNG files are supposed to open with these bytes according to the spec
1851 static immutable(ubyte[]) PNG_MAGIC_NUMBER = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
1852 
1853 /// A PNG file consists of the magic number then a stream of chunks. This
1854 /// struct represents those chunks.
1855 struct Chunk {
1856 	uint size;
1857 	ubyte[4] type;
1858 	ubyte[] payload;
1859 	uint checksum;
1860 
1861 	/// returns the type as a string for easier comparison with literals
1862 	@nogc @safe nothrow pure
1863 	const(char)[] stype() return const {
1864 		return cast(const(char)[]) type;
1865 	}
1866 
1867 	@trusted nothrow pure /* trusted because of the cast of name to ubyte. It is copied into a new buffer anyway though so obviously harmless. */
1868 	static Chunk* create(string type, ubyte[] payload)
1869 		in {
1870 			assert(type.length == 4);
1871 		}
1872 	do {
1873 		Chunk* c = new Chunk;
1874 		c.size = cast(int) payload.length;
1875 		c.type[] = (cast(ubyte[]) type)[];
1876 		c.payload = payload;
1877 
1878 		c.checksum = crcPng(type, payload);
1879 
1880 		return c;
1881 	}
1882 
1883 	/// Puts it into the format for outputting to a file
1884 	@safe nothrow pure
1885 	ubyte[] toArray() {
1886 		ubyte[] a;
1887 		a.length = size + 12;
1888 
1889 		int pos = 0;
1890 
1891 		a[pos++] = (size & 0xff000000) >> 24;
1892 		a[pos++] = (size & 0x00ff0000) >> 16;
1893 		a[pos++] = (size & 0x0000ff00) >> 8;
1894 		a[pos++] = (size & 0x000000ff) >> 0;
1895 
1896 		a[pos .. pos + 4] = type[0 .. 4];
1897 		pos += 4;
1898 
1899 		a[pos .. pos + size] = payload[0 .. size];
1900 
1901 		pos += size;
1902 
1903 		assert(checksum);
1904 
1905 		a[pos++] = (checksum & 0xff000000) >> 24;
1906 		a[pos++] = (checksum & 0x00ff0000) >> 16;
1907 		a[pos++] = (checksum & 0x0000ff00) >> 8;
1908 		a[pos++] = (checksum & 0x000000ff) >> 0;
1909 
1910 		return a;
1911 	}
1912 }
1913 
1914 /// The first chunk in a PNG file is a header that contains this info
1915 struct PngHeader {
1916 	/// Width of the image, in pixels.
1917 	uint width;
1918 
1919 	/// Height of the image, in pixels.
1920 	uint height;
1921 
1922 	/**
1923 		This is bits per channel - per color for truecolor or grey
1924 		and per pixel for palette.
1925 
1926 		Indexed ones can have depth of 1,2,4, or 8,
1927 
1928 		Greyscale can be 1,2,4,8,16
1929 
1930 		Everything else must be 8 or 16.
1931 	*/
1932 	ubyte depth = 8;
1933 
1934 	/** Types from the PNG spec:
1935 		0 - greyscale
1936 		2 - truecolor
1937 		3 - indexed color
1938 		4 - grey with alpha
1939 		6 - true with alpha
1940 
1941 		1, 5, and 7 are invalid.
1942 
1943 		There's a kind of bitmask going on here:
1944 			If type&1, it has a palette.
1945 			If type&2, it is in color.
1946 			If type&4, it has an alpha channel in the datastream.
1947 	*/
1948 	ubyte type = 6;
1949 
1950 	ubyte compressionMethod = 0; /// should be zero
1951 	ubyte filterMethod = 0; /// should be zero
1952 	/// 0 is non interlaced, 1 if Adam7. No more are defined in the spec
1953 	ubyte interlaceMethod = 0;
1954 
1955 	pure @safe // @nogc with -dip1008 too......
1956 	static PngHeader fromChunk(in Chunk c) {
1957 		if(c.stype != "IHDR")
1958 			throw new Exception("The chunk is not an image header");
1959 
1960 		PngHeader h;
1961 		auto data = c.payload;
1962 		int pos = 0;
1963 
1964 		if(data.length != 13)
1965 			throw new Exception("Malformed PNG file - the IHDR is the wrong size");
1966 
1967 		h.width |= data[pos++] << 24;
1968 		h.width |= data[pos++] << 16;
1969 		h.width |= data[pos++] << 8;
1970 		h.width |= data[pos++] << 0;
1971 
1972 		h.height |= data[pos++] << 24;
1973 		h.height |= data[pos++] << 16;
1974 		h.height |= data[pos++] << 8;
1975 		h.height |= data[pos++] << 0;
1976 
1977 		h.depth = data[pos++];
1978 		h.type = data[pos++];
1979 		h.compressionMethod = data[pos++];
1980 		h.filterMethod = data[pos++];
1981 		h.interlaceMethod = data[pos++];
1982 
1983 		return h;
1984 	}
1985 
1986 	Chunk* toChunk() {
1987 		ubyte[] data;
1988 		data.length = 13;
1989 		int pos = 0;
1990 
1991 		data[pos++] = width >> 24;
1992 		data[pos++] = (width >> 16) & 0xff;
1993 		data[pos++] = (width >> 8) & 0xff;
1994 		data[pos++] = width & 0xff;
1995 
1996 		data[pos++] = height >> 24;
1997 		data[pos++] = (height >> 16) & 0xff;
1998 		data[pos++] = (height >> 8) & 0xff;
1999 		data[pos++] = height & 0xff;
2000 
2001 		data[pos++] = depth;
2002 		data[pos++] = type;
2003 		data[pos++] = compressionMethod;
2004 		data[pos++] = filterMethod;
2005 		data[pos++] = interlaceMethod;
2006 
2007 		assert(pos == 13);
2008 
2009 		return Chunk.create("IHDR", data);
2010 	}
2011 }
2012 
2013 /// turns a range of png scanlines into a png file in the output range. really weird
2014 void writePngLazy(OutputRange, InputRange)(ref OutputRange where, InputRange image)
2015 	if(
2016 		isOutputRange!(OutputRange, ubyte[]) &&
2017 		isInputRange!(InputRange) &&
2018 		is(ElementType!InputRange == RgbaScanline))
2019 {
2020 	import std.zlib;
2021 	where.put(PNG_MAGIC_NUMBER);
2022 	PngHeader header;
2023 
2024 	assert(!image.empty());
2025 
2026 	// using the default values for header here... FIXME not super clear
2027 
2028 	header.width = image.front.pixels.length;
2029 	header.height = image.length;
2030 
2031 	enforce(header.width > 0, "Image width <= 0");
2032 	enforce(header.height > 0, "Image height <= 0");
2033 
2034 	where.put(header.toChunk().toArray());
2035 
2036 	auto compressor = new std.zlib.Compress();
2037 	const(void)[] compressedData;
2038 	int cnt;
2039 	foreach(line; image) {
2040 		// YOU'VE GOT TO BE FUCKING KIDDING ME!
2041 		// I have to /cast/ to void[]!??!?
2042 
2043 		ubyte[] data;
2044 		data.length = 1 + header.width * 4;
2045 		data[0] = 0; // filter type
2046 		int offset = 1;
2047 		foreach(pixel; line.pixels) {
2048 			data[offset++] = pixel.r;
2049 			data[offset++] = pixel.g;
2050 			data[offset++] = pixel.b;
2051 			data[offset++] = pixel.a;
2052 		}
2053 
2054 		compressedData ~= compressor.compress(cast(void[])
2055 			data);
2056 		if(compressedData.length > 2_000) {
2057 			where.put(Chunk.create("IDAT", cast(ubyte[])
2058 				compressedData).toArray());
2059 			compressedData = null;
2060 		}
2061 
2062 		cnt++;
2063 	}
2064 
2065 	assert(cnt == header.height, format("Got %d lines instead of %d", cnt, header.height));
2066 
2067 	compressedData ~= compressor.flush();
2068 	if(compressedData.length)
2069 		where.put(Chunk.create("IDAT", cast(ubyte[])
2070 			compressedData).toArray());
2071 
2072 	where.put(Chunk.create("IEND", null).toArray());
2073 }
2074 
2075 // bKGD - palette entry for background or the RGB (16 bits each) for that. or 16 bits of grey
2076 
2077 @trusted nothrow pure @nogc /* trusted because of the cast from char to ubyte */
2078 uint crcPng(in char[] chunkName, in ubyte[] buf){
2079 	uint c = update_crc(0xffffffffL, cast(ubyte[]) chunkName);
2080 	return update_crc(c, buf) ^ 0xffffffffL;
2081 }
2082 
2083 /++
2084 	Png files apply a filter to each line in the datastream, hoping to aid in compression. This undoes that as you load.
2085 +/
2086 immutable(ubyte)[] unfilter(ubyte filterType, in ubyte[] data, in ubyte[] previousLine, int bpp) {
2087 	// Note: the overflow arithmetic on the ubytes in here is intentional
2088 	switch(filterType) {
2089 		case 0:
2090 			return data.idup; // FIXME is copying really necessary?
2091 		case 1:
2092 			auto arr = data.dup;
2093 			// first byte gets zero added to it so nothing special
2094 			foreach(i; bpp .. arr.length) {
2095 				arr[i] += arr[i - bpp];
2096 			}
2097 
2098 			return assumeUnique(arr);
2099 		case 2:
2100 			auto arr = data.dup;
2101 			if(previousLine.length)
2102 			foreach(i; 0 .. arr.length) {
2103 				arr[i] += previousLine[i];
2104 			}
2105 
2106 			return assumeUnique(arr);
2107 		case 3:
2108 			auto arr = data.dup;
2109 			if(previousLine.length)
2110 			foreach(i; 0 .. arr.length) {
2111 				auto prev = i < bpp ? 0 : arr[i - bpp];
2112 				arr[i] += cast(ubyte)
2113 					/*std.math.floor*/( cast(int) (prev + (previousLine.length ? previousLine[i] : 0)) / 2);
2114 			}
2115 
2116 			return assumeUnique(arr);
2117 		case 4:
2118 			auto arr = data.dup;
2119 			foreach(i; 0 .. arr.length) {
2120 				ubyte prev   = i < bpp ? 0 : arr[i - bpp];
2121 				ubyte prevLL = i < bpp ? 0 : (i < previousLine.length ? previousLine[i - bpp] : 0);
2122 
2123 				arr[i] += PaethPredictor(prev, (i < previousLine.length ? previousLine[i] : 0), prevLL);
2124 			}
2125 
2126 			return assumeUnique(arr);
2127 		default:
2128 			throw new Exception("invalid PNG file, bad filter type");
2129 	}
2130 }
2131 
2132 ubyte PaethPredictor(ubyte a, ubyte b, ubyte c) {
2133 	int p = cast(int) a + b - c;
2134 	auto pa = abs(p - a);
2135 	auto pb = abs(p - b);
2136 	auto pc = abs(p - c);
2137 
2138 	if(pa <= pb && pa <= pc)
2139 		return a;
2140 	if(pb <= pc)
2141 		return b;
2142 	return c;
2143 }
2144 
2145 ///
2146 int bytesPerPixel(PngHeader header) {
2147 	immutable bitsPerChannel = header.depth;
2148 
2149 	int bitsPerPixel = bitsPerChannel;
2150 	if(header.type & 2 && !(header.type & 1)) // in color, but no palette
2151 		bitsPerPixel *= 3;
2152 	if(header.type & 4) // has alpha channel
2153 		bitsPerPixel += bitsPerChannel;
2154 
2155 	return (bitsPerPixel + 7) / 8;
2156 }