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