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