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