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