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