1 // Copyright (c) 2014 Tero Hänninen
2 // Boost Software License - Version 1.0 - August 17th, 2003
3 module imageformats;    // version 2.0.1
4 
5 import std.algorithm;   // min
6 import std.bitmanip;   // endianness stuff
7 import std.stdio;    // File
8 import std.string;  // toLower, lastIndexOf
9 
10 struct IFImage {
11     long        w, h;
12     ColFmt      c;
13     AlphaType   atype;
14     ubyte[]     pixels;
15 }
16 
17 enum ColFmt {
18     Y = 1,
19     YA = 2,
20     RGB = 3,
21     RGBA = 4,
22 }
23 
24 enum AlphaType {
25     Plain,
26     Premul,
27     Other
28 }
29 
30 IFImage read_image(in char[] file, long req_chans = 0) {
31     const(char)[] ext = extract_extension_lowercase(file);
32 
33     if (ext in register) {
34         ImageIOFuncs funcs = register[ext];
35         if (funcs.read_image is null)
36             throw new ImageIOException("null function pointer");
37         scope reader = new Reader(file);
38         return funcs.read_image(reader, req_chans);
39     }
40 
41     throw new ImageIOException("unknown image extension/type");
42 }
43 
44 void write_image(in char[] filename, long w, long h, in ubyte[] data, int req_chans = 0) {
45     const(char)[] ext = extract_extension_lowercase(filename);
46 
47     if (ext in register) {
48         ImageIOFuncs funcs = register[ext];
49         if (funcs.write_image is null)
50             throw new ImageIOException("null function pointer");
51         scope writer = new Writer(filename);
52         funcs.write_image(writer, w, h, data, req_chans);
53         return;
54     }
55 
56     throw new ImageIOException("unknown image extension/type");
57 }
58 
59 // chans is set to zero if num of channels is unknown
60 void read_image_info(in char[] filename, out long w, out long h, out long chans) {
61     const(char)[] ext = extract_extension_lowercase(filename);
62 
63     if (ext in register) {
64         ImageIOFuncs funcs = register[ext];
65         if (funcs.read_info is null)
66             throw new ImageIOException("null function pointer");
67         scope reader = new Reader(filename);
68         funcs.read_info(reader, w, h, chans);
69         return;
70     }
71 
72     throw new ImageIOException("unknown image extension/type");
73 }
74 
75 class ImageIOException : Exception {
76    @safe pure const
77    this(string msg, string file = __FILE__, size_t line = __LINE__) {
78        super(msg, file, line);
79    }
80 }
81 
82 // From here, things are private by default and public only explicitly.
83 private:
84 
85 // --------------------------------------------------------------------------------
86 // PNG
87 
88 import std.digest.crc;
89 import std.zlib;
90 
91 public struct PNG_Header {
92     int     width;
93     int     height;
94     ubyte   bit_depth;
95     ubyte   color_type;
96     ubyte   compression_method;
97     ubyte   filter_method;
98     ubyte   interlace_method;
99 }
100 
101 public PNG_Header read_png_header(in char[] filename) {
102     scope reader = new Reader(filename);
103     return read_png_header(reader);
104 }
105 
106 PNG_Header read_png_header(Reader stream) {
107     ubyte[33] tmp = void;  // file header, IHDR len+type+data+crc
108     stream.readExact(tmp, tmp.length);
109 
110     if ( tmp[0..8] != png_file_header[0..$]              ||
111          tmp[8..16] != [0x0,0x0,0x0,0xd,'I','H','D','R'] ||
112          crc32Of(tmp[12..29]).reverse != tmp[29..33] )
113         throw new ImageIOException("corrupt header");
114 
115     PNG_Header header = {
116         width              : bigEndianToNative!int(tmp[16..20]),
117         height             : bigEndianToNative!int(tmp[20..24]),
118         bit_depth          : tmp[24],
119         color_type         : tmp[25],
120         compression_method : tmp[26],
121         filter_method      : tmp[27],
122         interlace_method   : tmp[28],
123     };
124     return header;
125 }
126 
127 public IFImage read_png(in char[] filename, long req_chans = 0) {
128     scope reader = new Reader(filename);
129     return read_png(reader, req_chans);
130 }
131 
132 public IFImage read_png_from_mem(in ubyte[] source, long req_chans = 0) {
133     scope reader = new Reader(source);
134     return read_png(reader, req_chans);
135 }
136 
137 IFImage read_png(Reader stream, long req_chans = 0) {
138     if (req_chans < 0 || 4 < req_chans)
139         throw new ImageIOException("come on...");
140 
141     PNG_Header hdr = read_png_header(stream);
142 
143     if (hdr.width < 1 || hdr.height < 1 || int.max < cast(ulong) hdr.width * hdr.height)
144         throw new ImageIOException("invalid dimensions");
145     if (hdr.bit_depth != 8)
146         throw new ImageIOException("only 8-bit images supported");
147     if (! (hdr.color_type == PNG_ColorType.Y    ||
148            hdr.color_type == PNG_ColorType.RGB  ||
149            hdr.color_type == PNG_ColorType.Idx  ||
150            hdr.color_type == PNG_ColorType.YA   ||
151            hdr.color_type == PNG_ColorType.RGBA) )
152         throw new ImageIOException("color type not supported");
153     if (hdr.compression_method != 0 || hdr.filter_method != 0 ||
154         (hdr.interlace_method != 0 && hdr.interlace_method != 1))
155         throw new ImageIOException("not supported");
156 
157     PNG_Decoder dc = {
158         stream      : stream,
159         src_indexed : (hdr.color_type == PNG_ColorType.Idx),
160         src_chans   : channels(cast(PNG_ColorType) hdr.color_type),
161         ilace       : hdr.interlace_method,
162         w           : hdr.width,
163         h           : hdr.height,
164     };
165     dc.tgt_chans = (req_chans == 0) ? dc.src_chans : cast(int) req_chans;
166 
167     IFImage result = {
168         w      : dc.w,
169         h      : dc.h,
170         c      : cast(ColFmt) dc.tgt_chans,
171         atype  : AlphaType.Plain,
172         pixels : decode_png(dc)
173     };
174     return result;
175 }
176 
177 public void write_png(in char[] file, long w, long h, in ubyte[] data, long tgt_chans = 0)
178 {
179     scope writer = new Writer(file);
180     write_png(writer, w, h, data, tgt_chans);
181 }
182 
183 public ubyte[] write_png_to_mem(long w, long h, in ubyte[] data, long tgt_chans = 0) {
184     scope writer = new Writer();
185     write_png(writer, w, h, data, tgt_chans);
186     return writer.result;
187 }
188 
189 void write_png(Writer stream, long w, long h, in ubyte[] data, long tgt_chans = 0) {
190     if (w < 1 || h < 1 || int.max < w || int.max < h)
191         throw new ImageIOException("invalid dimensions");
192     ulong src_chans = data.length / w / h;
193     if (src_chans < 1 || 4 < src_chans || tgt_chans < 0 || 4 < tgt_chans)
194         throw new ImageIOException("invalid channel count");
195     if (src_chans * w * h != data.length)
196         throw new ImageIOException("mismatching dimensions and length");
197 
198     PNG_Encoder ec = {
199         stream    : stream,
200         w         : cast(int) w,
201         h         : cast(int) h,
202         src_chans : cast(int) src_chans,
203         tgt_chans : cast(int) ((tgt_chans) ? tgt_chans : src_chans),
204         data      : data,
205     };
206 
207     write_png(ec);
208     stream.flush();
209 }
210 
211 immutable ubyte[8] png_file_header =
212     [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
213 
214 int channels(PNG_ColorType ct) pure nothrow {
215     final switch (ct) with (PNG_ColorType) {
216         case Y: return 1;
217         case RGB, Idx: return 3;
218         case YA: return 2;
219         case RGBA: return 4;
220     }
221 }
222 
223 PNG_ColorType color_type(int channels) pure nothrow {
224     switch (channels) {
225         case 1: return PNG_ColorType.Y;
226         case 2: return PNG_ColorType.YA;
227         case 3: return PNG_ColorType.RGB;
228         case 4: return PNG_ColorType.RGBA;
229         default: assert(0);
230     }
231 }
232 
233 struct PNG_Decoder {
234     Reader stream;
235     bool src_indexed;
236     int src_chans;
237     int tgt_chans;
238     long w, h;
239     ubyte ilace;
240 
241     UnCompress uc;
242     CRC32 crc;
243     ubyte[12] chunkmeta;  // crc | length and type
244     ubyte[] read_buf;
245     ubyte[] uc_buf;     // uncompressed
246     ubyte[] palette;
247 }
248 
249 ubyte[] decode_png(ref PNG_Decoder dc) {
250     dc.uc = new UnCompress(HeaderFormat.deflate);
251     dc.read_buf = new ubyte[4096];
252 
253     enum Stage {
254         IHDR_parsed,
255         PLTE_parsed,
256         IDAT_parsed,
257         IEND_parsed,
258     }
259 
260     ubyte[] result;
261     auto stage = Stage.IHDR_parsed;
262     dc.stream.readExact(dc.chunkmeta[4..$], 8);  // next chunk's len and type
263 
264     while (stage != Stage.IEND_parsed) {
265         int len = bigEndianToNative!int(dc.chunkmeta[4..8]);
266         if (len < 0)
267             throw new ImageIOException("chunk too long");
268 
269         // standard allows PLTE chunk for non-indexed images too but we don't
270         switch (cast(char[]) dc.chunkmeta[8..12]) {    // chunk type
271             case "IDAT":
272                 if (! (stage == Stage.IHDR_parsed ||
273                       (stage == Stage.PLTE_parsed && dc.src_indexed)) )
274                     throw new ImageIOException("corrupt chunk stream");
275                 dc.crc.put(dc.chunkmeta[8..12]);  // type
276                 result = read_IDAT_stream(dc, len);
277                 stage = Stage.IDAT_parsed;
278                 break;
279             case "PLTE":
280                 if (stage != Stage.IHDR_parsed)
281                     throw new ImageIOException("corrupt chunk stream");
282                 int entries = len / 3;
283                 if (len % 3 != 0 || 256 < entries)
284                     throw new ImageIOException("corrupt chunk");
285                 dc.palette = new ubyte[len];
286                 dc.stream.readExact(dc.palette, dc.palette.length);
287                 dc.crc.put(dc.chunkmeta[8..12]);  // type
288                 dc.crc.put(dc.palette);
289                 dc.stream.readExact(dc.chunkmeta, 12); // crc | len, type
290                 if (dc.crc.finish.reverse != dc.chunkmeta[0..4])
291                     throw new ImageIOException("corrupt chunk");
292                 stage = Stage.PLTE_parsed;
293                 break;
294             case "IEND":
295                 if (stage != Stage.IDAT_parsed)
296                     throw new ImageIOException("corrupt chunk stream");
297                 dc.stream.readExact(dc.chunkmeta, 4); // crc
298                 if (len != 0 || dc.chunkmeta[0..4] != [0xae, 0x42, 0x60, 0x82])
299                     throw new ImageIOException("corrupt chunk");
300                 stage = Stage.IEND_parsed;
301                 break;
302             case "IHDR":
303                 throw new ImageIOException("corrupt chunk stream");
304             default:
305                 // unknown chunk, ignore but check crc
306                 dc.crc.put(dc.chunkmeta[8..12]);  // type
307                 while (0 < len) {
308                     size_t bytes = min(len, dc.read_buf.length);
309                     dc.stream.readExact(dc.read_buf, bytes);
310                     len -= bytes;
311                     dc.crc.put(dc.read_buf[0..bytes]);
312                 }
313                 dc.stream.readExact(dc.chunkmeta, 12); // crc | len, type
314                 if (dc.crc.finish.reverse != dc.chunkmeta[0..4])
315                     throw new ImageIOException("corrupt chunk");
316         }
317     }
318 
319     return result;
320 }
321 
322 enum PNG_ColorType : ubyte {
323     Y    = 0,
324     RGB  = 2,
325     Idx  = 3,
326     YA   = 4,
327     RGBA = 6,
328 }
329 
330 enum PNG_FilterType : ubyte {
331     None    = 0,
332     Sub     = 1,
333     Up      = 2,
334     Average = 3,
335     Paeth   = 4,
336 }
337 
338 enum InterlaceMethod {
339     None = 0, Adam7 = 1
340 }
341 
342 ubyte[] read_IDAT_stream(ref PNG_Decoder dc, int len) {
343     bool metaready = false;     // chunk len, type, crc
344 
345     immutable int filter_step = dc.src_indexed ? 1 : dc.src_chans;
346     immutable long tgt_sl_size = dc.w * dc.tgt_chans;
347 
348     ubyte[] depaletted_line = dc.src_indexed ? new ubyte[dc.w * 3] : null;
349     ubyte[] result = new ubyte[dc.w * dc.h * dc.tgt_chans];
350 
351     const chan_convert = get_converter(dc.src_chans, dc.tgt_chans);
352 
353     void depalette_convert(in ubyte[] src_line, ubyte[] tgt_line) {
354         for (long s, d;  s < src_line.length;  s+=1, d+=3) {
355             long pidx = src_line[s] * 3;
356             if (dc.palette.length < pidx + 3)
357                 throw new ImageIOException("palette idx wrong");
358             depaletted_line[d .. d+3] = dc.palette[pidx .. pidx+3];
359         }
360         chan_convert(depaletted_line[0 .. src_line.length*3], tgt_line);
361     }
362 
363     void simple_convert(in ubyte[] src_line, ubyte[] tgt_line) {
364         chan_convert(src_line, tgt_line);
365     }
366 
367     const convert = dc.src_indexed ? &depalette_convert : &simple_convert;
368 
369     if (dc.ilace == InterlaceMethod.None) {
370         immutable long src_sl_size = dc.w * filter_step;
371         auto cline = new ubyte[src_sl_size+1];   // current line + filter byte
372         auto pline = new ubyte[src_sl_size+1];   // previous line, inited to 0
373         debug(DebugPNG) assert(pline[0] == 0);
374 
375         long tgt_si = 0;    // scanline index in target buffer
376         foreach (j; 0 .. dc.h) {
377             uncompress_line(dc, len, metaready, cline);
378             ubyte filter_type = cline[0];
379 
380             recon(cline[1..$], pline[1..$], filter_type, filter_step);
381             convert(cline[1 .. $], result[tgt_si .. tgt_si + tgt_sl_size]);
382             tgt_si += tgt_sl_size;
383 
384             ubyte[] _swap = pline;
385             pline = cline;
386             cline = _swap;
387         }
388     } else {
389         // Adam7 interlacing
390 
391         immutable long[7] redw = [
392             (dc.w + 7) / 8,
393             (dc.w + 3) / 8,
394             (dc.w + 3) / 4,
395             (dc.w + 1) / 4,
396             (dc.w + 1) / 2,
397             (dc.w + 0) / 2,
398             (dc.w + 0) / 1,
399         ];
400         immutable long[7] redh = [
401             (dc.h + 7) / 8,
402             (dc.h + 7) / 8,
403             (dc.h + 3) / 8,
404             (dc.h + 3) / 4,
405             (dc.h + 1) / 4,
406             (dc.h + 1) / 2,
407             (dc.h + 0) / 2,
408         ];
409 
410         const long max_scanline_size = dc.w * filter_step;
411         const linebuf0 = new ubyte[max_scanline_size+1]; // +1 for filter type byte
412         const linebuf1 = new ubyte[max_scanline_size+1]; // +1 for filter type byte
413         auto redlinebuf = new ubyte[dc.w * dc.tgt_chans];
414 
415         foreach (pass; 0 .. 7) {
416             const A7_Catapult tgt_px = a7_catapults[pass];   // target pixel
417             const long src_linesize = redw[pass] * filter_step;
418             auto cline = cast(ubyte[]) linebuf0[0 .. src_linesize+1];
419             auto pline = cast(ubyte[]) linebuf1[0 .. src_linesize+1];
420 
421             foreach (j; 0 .. redh[pass]) {
422                 uncompress_line(dc, len, metaready, cline);
423                 ubyte filter_type = cline[0];
424 
425                 recon(cline[1..$], pline[1..$], filter_type, filter_step);
426                 convert(cline[1 .. $], redlinebuf[0 .. redw[pass]*dc.tgt_chans]);
427 
428                 for (long i, redi; i < redw[pass]; ++i, redi += dc.tgt_chans) {
429                     long tgt = tgt_px(i, j, dc.w) * dc.tgt_chans;
430                     result[tgt .. tgt + dc.tgt_chans] =
431                         redlinebuf[redi .. redi + dc.tgt_chans];
432                 }
433 
434                 ubyte[] _swap = pline;
435                 pline = cline;
436                 cline = _swap;
437             }
438         }
439     }
440 
441     if (!metaready) {
442         dc.stream.readExact(dc.chunkmeta, 12);   // crc | len & type
443         if (dc.crc.finish.reverse != dc.chunkmeta[0..4])
444             throw new ImageIOException("corrupt chunk");
445     }
446     return result;
447 }
448 
449 alias A7_Catapult = long function(long redx, long redy, long dstw);
450 immutable A7_Catapult[7] a7_catapults = [
451     &a7_red1_to_dst,
452     &a7_red2_to_dst,
453     &a7_red3_to_dst,
454     &a7_red4_to_dst,
455     &a7_red5_to_dst,
456     &a7_red6_to_dst,
457     &a7_red7_to_dst,
458 ];
459 
460 pure nothrow {
461   long a7_red1_to_dst(long redx, long redy, long dstw) { return redy*8*dstw + redx*8;     }
462   long a7_red2_to_dst(long redx, long redy, long dstw) { return redy*8*dstw + redx*8+4;   }
463   long a7_red3_to_dst(long redx, long redy, long dstw) { return (redy*8+4)*dstw + redx*4; }
464   long a7_red4_to_dst(long redx, long redy, long dstw) { return redy*4*dstw + redx*4+2;   }
465   long a7_red5_to_dst(long redx, long redy, long dstw) { return (redy*4+2)*dstw + redx*2; }
466   long a7_red6_to_dst(long redx, long redy, long dstw) { return redy*2*dstw + redx*2+1;   }
467   long a7_red7_to_dst(long redx, long redy, long dstw) { return (redy*2+1)*dstw + redx;   }
468 }
469 
470 void uncompress_line(ref PNG_Decoder dc, ref int length, ref bool metaready, ubyte[] dst) {
471     size_t readysize = min(dst.length, dc.uc_buf.length);
472     dst[0 .. readysize] = dc.uc_buf[0 .. readysize];
473     dc.uc_buf = dc.uc_buf[readysize .. $];
474 
475     if (readysize == dst.length)
476         return;
477 
478     while (readysize != dst.length) {
479         // need new data for dc.uc_buf...
480         if (length <= 0) {  // IDAT is read -> read next chunks meta
481             dc.stream.readExact(dc.chunkmeta, 12);   // crc | len & type
482             if (dc.crc.finish.reverse != dc.chunkmeta[0..4])
483                 throw new ImageIOException("corrupt chunk");
484 
485             length = bigEndianToNative!int(dc.chunkmeta[4..8]);
486             if (dc.chunkmeta[8..12] != "IDAT") {
487                 // no new IDAT chunk so flush, this is the end of the IDAT stream
488                 metaready = true;
489                 dc.uc_buf = cast(ubyte[]) dc.uc.flush();
490                 size_t part2 = dst.length - readysize;
491                 if (dc.uc_buf.length < part2)
492                     throw new ImageIOException("not enough data");
493                 dst[readysize .. readysize+part2] = dc.uc_buf[0 .. part2];
494                 dc.uc_buf = dc.uc_buf[part2 .. $];
495                 return;
496             }
497             if (length <= 0)    // empty IDAT chunk
498                 throw new ImageIOException("not enough data");
499             dc.crc.put(dc.chunkmeta[8..12]);  // type
500         }
501 
502         size_t bytes = min(length, dc.read_buf.length);
503         dc.stream.readExact(dc.read_buf, bytes);
504         length -= bytes;
505         dc.crc.put(dc.read_buf[0..bytes]);
506 
507         if (bytes <= 0)
508             throw new ImageIOException("not enough data");
509 
510         dc.uc_buf = cast(ubyte[]) dc.uc.uncompress(dc.read_buf[0..bytes].dup);
511 
512         size_t part2 = min(dst.length - readysize, dc.uc_buf.length);
513         dst[readysize .. readysize+part2] = dc.uc_buf[0 .. part2];
514         dc.uc_buf = dc.uc_buf[part2 .. $];
515         readysize += part2;
516     }
517 }
518 
519 void recon(ubyte[] cline, in ubyte[] pline, ubyte ftype, int fstep) pure {
520     switch (ftype) with (PNG_FilterType) {
521         case None:
522             break;
523         case Sub:
524             foreach (k; fstep .. cline.length)
525                 cline[k] += cline[k-fstep];
526             break;
527         case Up:
528             foreach (k; 0 .. cline.length)
529                 cline[k] += pline[k];
530             break;
531         case Average:
532             foreach (k; 0 .. fstep)
533                 cline[k] += pline[k] / 2;
534             foreach (k; fstep .. cline.length)
535                 cline[k] += cast(ubyte)
536                     ((cast(uint) cline[k-fstep] + cast(uint) pline[k]) / 2);
537             break;
538         case Paeth:
539             foreach (i; 0 .. fstep)
540                 cline[i] += paeth(0, pline[i], 0);
541             foreach (i; fstep .. cline.length)
542                 cline[i] += paeth(cline[i-fstep], pline[i], pline[i-fstep]);
543             break;
544         default:
545             throw new ImageIOException("filter type not supported");
546     }
547 }
548 
549 ubyte paeth(ubyte a, ubyte b, ubyte c) pure nothrow {
550     int pc = cast(int) c;
551     int pa = cast(int) b - pc;
552     int pb = cast(int) a - pc;
553     pc = pa + pb;
554     if (pa < 0) pa = -pa;
555     if (pb < 0) pb = -pb;
556     if (pc < 0) pc = -pc;
557 
558     if (pa <= pb && pa <= pc) {
559         return a;
560     } else if (pb <= pc) {
561         return b;
562     }
563     return c;
564 }
565 
566 // ----------------------------------------------------------------------
567 // PNG encoder
568 
569 struct PNG_Encoder {
570     Writer stream;
571     int w, h;
572     int src_chans;
573     int tgt_chans;
574     const(ubyte)[] data;
575 
576     CRC32 crc;
577 
578     uint writelen;      // how much written of current idat data
579     ubyte[] chunk_buf;  // len type data crc
580     ubyte[] data_buf;   // slice of chunk_buf, for just chunk data
581 }
582 
583 void write_png(ref PNG_Encoder ec) {
584     ubyte[33] hdr = void;
585     hdr[ 0 ..  8] = png_file_header;
586     hdr[ 8 .. 16] = [0x0, 0x0, 0x0, 0xd, 'I','H','D','R'];
587     hdr[16 .. 20] = nativeToBigEndian(cast(uint) ec.w);
588     hdr[20 .. 24] = nativeToBigEndian(cast(uint) ec.h);
589     hdr[24      ] = 8;  // bit depth
590     hdr[25      ] = color_type(ec.tgt_chans);
591     hdr[26 .. 29] = 0;  // compression, filter and interlace methods
592     ec.crc.start();
593     ec.crc.put(hdr[12 .. 29]);
594     hdr[29 .. 33] = ec.crc.finish().reverse;
595     ec.stream.rawWrite(hdr);
596 
597     write_IDATs(ec);
598 
599     static immutable ubyte[12] iend =
600         [0, 0, 0, 0, 'I','E','N','D', 0xae, 0x42, 0x60, 0x82];
601     ec.stream.rawWrite(iend);
602 }
603 
604 void write_IDATs(ref PNG_Encoder ec) {
605     static immutable ubyte[4] IDAT_type = ['I','D','A','T'];
606     immutable long max_idatlen = 4 * 4096;
607     ec.writelen = 0;
608     ec.chunk_buf = new ubyte[8 + max_idatlen + 4];
609     ec.data_buf = ec.chunk_buf[8 .. 8 + max_idatlen];
610     ec.chunk_buf[4 .. 8] = IDAT_type;
611 
612     immutable long linesize = ec.w * ec.tgt_chans + 1; // +1 for filter type
613     ubyte[] cline = new ubyte[linesize];
614     ubyte[] pline = new ubyte[linesize];
615     debug(DebugPNG) assert(pline[0] == 0);
616 
617     ubyte[] filtered_line = new ubyte[linesize];
618     ubyte[] filtered_image;
619 
620     const void function(in ubyte[] src_line, ubyte[] tgt_line)
621         convert = get_converter(ec.src_chans, ec.tgt_chans);
622 
623     immutable int filter_step = ec.tgt_chans;   // step between pixels, in bytes
624     immutable long src_line_size = ec.w * ec.src_chans;
625 
626     long si = 0;
627     foreach (j; 0 .. ec.h) {
628         convert(ec.data[si .. si+src_line_size], cline[1..$]);
629         si += src_line_size;
630 
631         foreach (i; 1 .. filter_step+1)
632             filtered_line[i] = cast(ubyte) (cline[i] - paeth(0, pline[i], 0));
633         foreach (i; filter_step+1 .. cline.length)
634             filtered_line[i] = cast(ubyte)
635                 (cline[i] - paeth(cline[i-filter_step], pline[i], pline[i-filter_step]));
636 
637         filtered_line[0] = PNG_FilterType.Paeth;
638 
639         filtered_image ~= filtered_line;
640 
641         ubyte[] _swap = pline;
642         pline = cline;
643         cline = _swap;
644     }
645 
646     const (void)[] xx = compress(filtered_image, 6);
647 
648     ec.write_to_IDAT_stream(xx);
649     if (0 < ec.writelen)
650         ec.write_IDAT_chunk();
651 }
652 
653 void write_to_IDAT_stream(ref PNG_Encoder ec, in void[] _compressed) {
654     ubyte[] compressed = cast(ubyte[]) _compressed;
655     while (compressed.length) {
656         long space_left = ec.data_buf.length - ec.writelen;
657         long writenow_len = min(space_left, compressed.length);
658         ec.data_buf[ec.writelen .. ec.writelen + writenow_len] =
659             compressed[0 .. writenow_len];
660         ec.writelen += writenow_len;
661         compressed = compressed[writenow_len .. $];
662         if (ec.writelen == ec.data_buf.length)
663             ec.write_IDAT_chunk();
664     }
665 }
666 
667 // chunk: len type data crc, type is already in buf
668 void write_IDAT_chunk(ref PNG_Encoder ec) {
669     ec.chunk_buf[0 .. 4] = nativeToBigEndian!uint(ec.writelen);
670     ec.crc.put(ec.chunk_buf[4 .. 8 + ec.writelen]);   // crc of type and data
671     ec.chunk_buf[8 + ec.writelen .. 8 + ec.writelen + 4] = ec.crc.finish().reverse;
672     ec.stream.rawWrite(ec.chunk_buf[0 .. 8 + ec.writelen + 4]);
673     ec.writelen = 0;
674 }
675 
676 void read_png_info(Reader stream, out long w, out long h, out long chans) {
677     PNG_Header hdr = read_png_header(stream);
678     w = hdr.width;
679     h = hdr.height;
680     chans = channels(cast(PNG_ColorType) hdr.color_type);
681 }
682 
683 static this() {
684     register["png"] = ImageIOFuncs(&read_png, &write_png, &read_png_info);
685 }
686 
687 // --------------------------------------------------------------------------------
688 // TGA
689 
690 public struct TGA_Header {
691    ubyte id_length;
692    ubyte palette_type;
693    ubyte data_type;
694    ushort palette_start;
695    ushort palette_length;
696    ubyte palette_bits;
697    ushort x_origin;
698    ushort y_origin;
699    ushort width;
700    ushort height;
701    ubyte bits_pp;
702    ubyte flags;
703 }
704 
705 public TGA_Header read_tga_header(in char[] filename) {
706     scope reader = new Reader(filename);
707     return read_tga_header(reader);
708 }
709 
710 TGA_Header read_tga_header(Reader stream) {
711     ubyte[18] tmp = void;
712     stream.readExact(tmp, tmp.length);
713 
714     TGA_Header header = {
715         id_length       : tmp[0],
716         palette_type    : tmp[1],
717         data_type       : tmp[2],
718         palette_start   : littleEndianToNative!ushort(tmp[3..5]),
719         palette_length  : littleEndianToNative!ushort(tmp[5..7]),
720         palette_bits    : tmp[7],
721         x_origin        : littleEndianToNative!ushort(tmp[8..10]),
722         y_origin        : littleEndianToNative!ushort(tmp[10..12]),
723         width           : littleEndianToNative!ushort(tmp[12..14]),
724         height          : littleEndianToNative!ushort(tmp[14..16]),
725         bits_pp         : tmp[16],
726         flags           : tmp[17],
727     };
728     return header;
729 }
730 
731 public IFImage read_tga(in char[] filename, long req_chans = 0) {
732     scope reader = new Reader(filename);
733     return read_tga(reader, req_chans);
734 }
735 
736 public IFImage read_tga_from_mem(in ubyte[] source, long req_chans = 0) {
737     scope reader = new Reader(source);
738     return read_tga(reader, req_chans);
739 }
740 
741 IFImage read_tga(Reader stream, long req_chans = 0) {
742     if (req_chans < 0 || 4 < req_chans)
743         throw new ImageIOException("come on...");
744 
745     TGA_Header hdr = read_tga_header(stream);
746 
747     if (hdr.width < 1 || hdr.height < 1)
748         throw new ImageIOException("invalid dimensions");
749     if (hdr.flags & 0xc0)   // two bits
750         throw new ImageIOException("interlaced TGAs not supported");
751     if (hdr.flags & 0x10)
752         throw new ImageIOException("right-to-left TGAs not supported");
753     ubyte attr_bits_pp = (hdr.flags & 0xf);
754     if (! (attr_bits_pp == 0 || attr_bits_pp == 8)) // some set it 0 although data has 8
755         throw new ImageIOException("only 8-bit alpha/attribute(s) supported");
756     if (hdr.palette_type)
757         throw new ImageIOException("paletted TGAs not supported");
758 
759     bool rle = false;
760     switch (hdr.data_type) with (TGA_DataType) {
761         //case 1: ;   // paletted, uncompressed
762         case TrueColor:
763             if (! (hdr.bits_pp == 24 || hdr.bits_pp == 32))
764                 throw new ImageIOException("not supported");
765             break;
766         case Gray:
767             if (! (hdr.bits_pp == 8 || (hdr.bits_pp == 16 && attr_bits_pp == 8)))
768                 throw new ImageIOException("not supported");
769             break;
770         //case 9: ;   // paletted, RLE
771         case TrueColor_RLE:
772             if (! (hdr.bits_pp == 24 || hdr.bits_pp == 32))
773                 throw new ImageIOException("not supported");
774             rle = true;
775             break;
776         case Gray_RLE:
777             if (! (hdr.bits_pp == 8 || (hdr.bits_pp == 16 && attr_bits_pp == 8)))
778                 throw new ImageIOException("not supported");
779             rle = true;
780             break;
781         default: throw new ImageIOException("data type not supported");
782     }
783 
784     int src_chans = hdr.bits_pp / 8;
785 
786     if (hdr.id_length)
787         stream.seek(hdr.id_length, SEEK_CUR);
788 
789     TGA_Decoder dc = {
790         stream         : stream,
791         w              : hdr.width,
792         h              : hdr.height,
793         origin_at_top  : cast(bool) (hdr.flags & 0x20),
794         bytes_pp       : hdr.bits_pp / 8,
795         rle            : rle,
796         tgt_chans      : (req_chans == 0) ? src_chans : cast(int) req_chans,
797     };
798 
799     switch (dc.bytes_pp) {
800         case 1: dc.src_fmt = _ColFmt.Y; break;
801         case 2: dc.src_fmt = _ColFmt.YA; break;
802         case 3: dc.src_fmt = _ColFmt.BGR; break;
803         case 4: dc.src_fmt = _ColFmt.BGRA; break;
804         default: throw new ImageIOException("TGA: format not supported");
805     }
806 
807     IFImage result = {
808         w      : dc.w,
809         h      : dc.h,
810         c      : cast(ColFmt) dc.tgt_chans,
811         atype  : AlphaType.Plain, // guess it's plain alpha if can't fetch it
812         pixels : decode_tga(dc),
813     };
814 
815     if (dc.src_fmt != _ColFmt.YA && dc.src_fmt != _ColFmt.BGRA)
816         return result;
817 
818     // fetch attribute type (plain/premultiplied/undefined alpha)
819     try {
820         ubyte[26] ftr = void;
821         stream.seek(-26, SEEK_END);
822         stream.readExact(ftr, 26);
823         if (ftr[8..26] == tga_footer_sig) {
824             uint extarea = littleEndianToNative!uint(ftr[0..4]);
825             stream.seek(extarea + 494, SEEK_SET);
826             stream.readExact(ftr, 1);
827             switch (ftr[0]) {
828                 case 3: result.atype = AlphaType.Plain; break;
829                 case 4: result.atype = AlphaType.Premul; break;
830                 default: result.atype = AlphaType.Other; break;
831             }
832         }
833     } catch { }
834     return result;
835 }
836 
837 public void write_tga(in char[] file, long w, long h, in ubyte[] data, long tgt_chans = 0)
838 {
839     scope writer = new Writer(file);
840     write_tga(writer, w, h, data, tgt_chans);
841 }
842 
843 public ubyte[] write_tga_to_mem(long w, long h, in ubyte[] data, long tgt_chans = 0) {
844     scope writer = new Writer();
845     write_tga(writer, w, h, data, tgt_chans);
846     return writer.result;
847 }
848 
849 void write_tga(Writer stream, long w, long h, in ubyte[] data, long tgt_chans = 0) {
850     if (w < 1 || h < 1 || ushort.max < w || ushort.max < h)
851         throw new ImageIOException("invalid dimensions");
852     ulong src_chans = data.length / w / h;
853     if (src_chans < 1 || 4 < src_chans || tgt_chans < 0 || 4 < tgt_chans)
854         throw new ImageIOException("invalid channel count");
855     if (src_chans * w * h != data.length)
856         throw new ImageIOException("mismatching dimensions and length");
857 
858     TGA_Encoder ec = {
859         stream    : stream,
860         w         : cast(ushort) w,
861         h         : cast(ushort) h,
862         src_chans : cast(int) src_chans,
863         tgt_chans : cast(int) ((tgt_chans) ? tgt_chans : src_chans),
864         rle       : true,
865         data      : data,
866     };
867 
868     write_tga(ec);
869     stream.flush();
870 }
871 
872 struct TGA_Decoder {
873     Reader stream;
874     long w, h;
875     bool origin_at_top;    // src
876     int bytes_pp;
877     bool rle;   // run length compressed
878     _ColFmt src_fmt;
879     int tgt_chans;
880 }
881 
882 ubyte[] decode_tga(ref TGA_Decoder dc) {
883     auto result = new ubyte[dc.w * dc.h * dc.tgt_chans];
884 
885     immutable long tgt_linesize = dc.w * dc.tgt_chans;
886     immutable long src_linesize = dc.w * dc.bytes_pp;
887     auto src_line = new ubyte[src_linesize];
888 
889     immutable long tgt_stride = (dc.origin_at_top) ? tgt_linesize : -tgt_linesize;
890     long ti                   = (dc.origin_at_top) ? 0 : (dc.h-1) * tgt_linesize;
891 
892     void function(in ubyte[] src_line, ubyte[] tgt_line) convert;
893     convert = get_converter(dc.src_fmt, dc.tgt_chans);
894 
895     if (!dc.rle) {
896         foreach (_j; 0 .. dc.h) {
897             dc.stream.readExact(src_line, src_linesize);
898             convert(src_line, result[ti .. ti + tgt_linesize]);
899             ti += tgt_stride;
900         }
901         return result;
902     }
903 
904     // ----- RLE  -----
905 
906     auto rbuf = new ubyte[src_linesize];
907     long plen = 0;      // packet length
908     bool its_rle = false;
909 
910     foreach (_j; 0 .. dc.h) {
911         // fill src_line with uncompressed data (this works like a stream)
912         long wanted = src_linesize;
913         while (wanted) {
914             if (plen == 0) {
915                 dc.stream.readExact(rbuf, 1);
916                 its_rle = cast(bool) (rbuf[0] & 0x80);
917                 plen = ((rbuf[0] & 0x7f) + 1) * dc.bytes_pp; // length in bytes
918             }
919             const long gotten = src_linesize - wanted;
920             const long copysize = min(plen, wanted);
921             if (its_rle) {
922                 dc.stream.readExact(rbuf, dc.bytes_pp);
923                 for (long p = gotten; p < gotten+copysize; p += dc.bytes_pp)
924                     src_line[p .. p+dc.bytes_pp] = rbuf[0 .. dc.bytes_pp];
925             } else {    // it's raw
926                 auto slice = src_line[gotten .. gotten+copysize];
927                 dc.stream.readExact(slice, copysize);
928             }
929             wanted -= copysize;
930             plen -= copysize;
931         }
932 
933         convert(src_line, result[ti .. ti + tgt_linesize]);
934         ti += tgt_stride;
935     }
936 
937     return result;
938 }
939 
940 // ----------------------------------------------------------------------
941 // TGA encoder
942 
943 immutable ubyte[18] tga_footer_sig =
944     ['T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.', 0];
945 
946 struct TGA_Encoder {
947     Writer stream;
948     ushort w, h;
949     int src_chans;
950     int tgt_chans;
951     bool rle;   // run length compression
952     const(ubyte)[] data;
953 }
954 
955 void write_tga(ref TGA_Encoder ec) {
956     ubyte data_type;
957     bool has_alpha = false;
958     switch (ec.tgt_chans) with (TGA_DataType) {
959         case 1: data_type = ec.rle ? Gray_RLE : Gray;                             break;
960         case 2: data_type = ec.rle ? Gray_RLE : Gray;           has_alpha = true; break;
961         case 3: data_type = ec.rle ? TrueColor_RLE : TrueColor;                   break;
962         case 4: data_type = ec.rle ? TrueColor_RLE : TrueColor; has_alpha = true; break;
963         default: throw new ImageIOException("internal error");
964     }
965 
966     ubyte[18] hdr = void;
967     hdr[0] = 0;         // id length
968     hdr[1] = 0;         // palette type
969     hdr[2] = data_type;
970     hdr[3..8] = 0;         // palette start (2), len (2), bits per palette entry (1)
971     hdr[8..12] = 0;     // x origin (2), y origin (2)
972     hdr[12..14] = nativeToLittleEndian(ec.w);
973     hdr[14..16] = nativeToLittleEndian(ec.h);
974     hdr[16] = cast(ubyte) (ec.tgt_chans * 8);     // bits per pixel
975     hdr[17] = (has_alpha) ? 0x8 : 0x0;     // flags: attr_bits_pp = 8
976     ec.stream.rawWrite(hdr);
977 
978     write_image_data(ec);
979 
980     ubyte[26] ftr = void;
981     ftr[0..4] = 0;   // extension area offset
982     ftr[4..8] = 0;   // developer directory offset
983     ftr[8..26] = tga_footer_sig;
984     ec.stream.rawWrite(ftr);
985 }
986 
987 void write_image_data(ref TGA_Encoder ec) {
988     _ColFmt tgt_fmt;
989     switch (ec.tgt_chans) {
990         case 1: tgt_fmt = _ColFmt.Y; break;
991         case 2: tgt_fmt = _ColFmt.YA; break;
992         case 3: tgt_fmt = _ColFmt.BGR; break;
993         case 4: tgt_fmt = _ColFmt.BGRA; break;
994         default: throw new ImageIOException("internal error");
995     }
996 
997     void function(in ubyte[] src_line, ubyte[] tgt_line) convert;
998     convert = get_converter(ec.src_chans, tgt_fmt);
999 
1000     immutable long src_linesize = ec.w * ec.src_chans;
1001     immutable long tgt_linesize = ec.w * ec.tgt_chans;
1002     auto tgt_line = new ubyte[tgt_linesize];
1003 
1004     long si = (ec.h-1) * src_linesize;     // origin at bottom
1005 
1006     if (!ec.rle) {
1007         foreach (_; 0 .. ec.h) {
1008             convert(ec.data[si .. si + src_linesize], tgt_line);
1009             ec.stream.rawWrite(tgt_line);
1010             si -= src_linesize; // origin at bottom
1011         }
1012         return;
1013     }
1014 
1015     // ----- RLE  -----
1016 
1017     immutable bytes_pp = ec.tgt_chans;
1018     immutable long max_packets_per_line = (tgt_linesize+127) / 128;
1019     auto tgt_cmp = new ubyte[tgt_linesize + max_packets_per_line];  // compressed line
1020     foreach (_; 0 .. ec.h) {
1021         convert(ec.data[si .. si + src_linesize], tgt_line);
1022         ubyte[] compressed_line = rle_compress(tgt_line, tgt_cmp, ec.w, bytes_pp);
1023         ec.stream.rawWrite(compressed_line);
1024         si -= src_linesize; // origin at bottom
1025     }
1026 }
1027 
1028 ubyte[] rle_compress(in ubyte[] line, ubyte[] tgt_cmp, in long w, in int bytes_pp) pure {
1029     immutable int rle_limit = (1 < bytes_pp) ? 2 : 3;  // run len worth an RLE packet
1030     long runlen = 0;
1031     long rawlen = 0;
1032     long raw_i = 0; // start of raw packet data in line
1033     long cmp_i = 0;
1034     long pixels_left = w;
1035     const (ubyte)[] px;
1036     for (long i = bytes_pp; pixels_left; i += bytes_pp) {
1037         runlen = 1;
1038         px = line[i-bytes_pp .. i];
1039         while (i < line.length && line[i .. i+bytes_pp] == px[0..$] && runlen < 128) {
1040             ++runlen;
1041             i += bytes_pp;
1042         }
1043         pixels_left -= runlen;
1044 
1045         if (runlen < rle_limit) {
1046             // data goes to raw packet
1047             rawlen += runlen;
1048             if (128 <= rawlen) {     // full packet, need to store it
1049                 long copysize = 128 * bytes_pp;
1050                 tgt_cmp[cmp_i++] = 0x7f; // raw packet header
1051                 tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize];
1052                 cmp_i += copysize;
1053                 raw_i += copysize;
1054                 rawlen -= 128;
1055             }
1056         } else {
1057             // RLE packet is worth it
1058 
1059             // store raw packet first, if any
1060             if (rawlen) {
1061                 assert(rawlen < 128);
1062                 long copysize = rawlen * bytes_pp;
1063                 tgt_cmp[cmp_i++] = cast(ubyte) (rawlen-1); // raw packet header
1064                 tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize];
1065                 cmp_i += copysize;
1066                 rawlen = 0;
1067             }
1068 
1069             // store RLE packet
1070             tgt_cmp[cmp_i++] = cast(ubyte) (0x80 | (runlen-1)); // packet header
1071             tgt_cmp[cmp_i .. cmp_i+bytes_pp] = px[0..$];       // packet data
1072             cmp_i += bytes_pp;
1073             raw_i = i;
1074         }
1075     }   // for
1076 
1077     if (rawlen) {   // last packet of the line
1078         long copysize = rawlen * bytes_pp;
1079         tgt_cmp[cmp_i++] = cast(ubyte) (rawlen-1); // raw packet header
1080         tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize];
1081         cmp_i += copysize;
1082     }
1083     return tgt_cmp[0 .. cmp_i];
1084 }
1085 
1086 enum TGA_DataType : ubyte {
1087     Idx           = 1,
1088     TrueColor     = 2,
1089     Gray          = 3,
1090     Idx_RLE       = 9,
1091     TrueColor_RLE = 10,
1092     Gray_RLE      = 11,
1093 }
1094 
1095 void read_tga_info(Reader stream, out long w, out long h, out long chans) {
1096     TGA_Header hdr = read_tga_header(stream);
1097     w = hdr.width;
1098     h = hdr.height;
1099 
1100     // TGA is awkward...
1101     auto dt = hdr.data_type;
1102     if ((dt == TGA_DataType.TrueColor     || dt == TGA_DataType.Gray ||
1103          dt == TGA_DataType.TrueColor_RLE || dt == TGA_DataType.Gray_RLE)
1104          && (hdr.bits_pp % 8) == 0)
1105     {
1106         chans = hdr.bits_pp / 8;
1107         return;
1108     } else if (dt == TGA_DataType.Idx || dt == TGA_DataType.Idx_RLE) {
1109         switch (hdr.palette_bits) {
1110             case 15: chans = 3; return;
1111             case 16: chans = 3; return; // one bit could be for some "interrupt control"
1112             case 24: chans = 3; return;
1113             case 32: chans = 4; return;
1114             default:
1115         }
1116     }
1117     chans = 0;  // unknown
1118 }
1119 
1120 static this() {
1121     register["tga"] = ImageIOFuncs(&read_tga, &write_tga, &read_tga_info);
1122 }
1123 
1124 // --------------------------------------------------------------------------------
1125 /*
1126     Baseline JPEG/JFIF decoder
1127     - not quite optimized but should be well usable already. seems to be
1128     something like 1.66 times slower with ldc and 1.78 times slower with gdc
1129     than stb_image.
1130     - memory use could be reduced by processing MCU-row at a time, and, if
1131     only grayscale result is requested, the Cb and Cr components could be
1132     discarded much earlier.
1133 */
1134 
1135 import std.math;    // floor, ceil
1136 import core.stdc.stdlib : alloca;
1137 
1138 //debug = DebugJPEG;
1139 
1140 public struct JPEG_Header {    // JFIF
1141     ubyte version_major;
1142     ubyte version_minor;
1143     ushort width, height;
1144     ubyte num_comps;
1145     ubyte precision;    // sample precision
1146     ubyte density_unit;     // 0 = no units but aspect ratio, 1 = dots/inch, 2 = dots/cm
1147     ushort density_x;
1148     ushort density_y;
1149     ubyte type; // 0xc0 = baseline, 0xc2 = progressive, ..., see Marker
1150 }
1151 
1152 public JPEG_Header read_jpeg_header(in char[] filename) {
1153     scope reader = new Reader(filename);
1154     return read_jpeg_header(reader);
1155 }
1156 
1157 JPEG_Header read_jpeg_header(Reader stream) {
1158     ubyte[20 + 8] tmp = void;   // SOI, APP0 + SOF0
1159     stream.readExact(tmp, 20);
1160 
1161     ushort len = bigEndianToNative!ushort(tmp[4..6]);
1162     if ( tmp[0..4] != [0xff,0xd8,0xff,0xe0] ||
1163          tmp[6..11] != ['J','F','I','F',0]  ||
1164          len < 16 )
1165         throw new ImageIOException("not JPEG/JFIF");
1166 
1167     int thumbsize = tmp[18] * tmp[19] * 3;
1168     if (thumbsize != cast(int) len - 16)
1169         throw new ImageIOException("corrupt header");
1170     if (thumbsize)
1171         stream.seek(thumbsize, SEEK_CUR);
1172 
1173     JPEG_Header header = {
1174         version_major      : tmp[11],
1175         version_minor      : tmp[12],
1176         density_unit       : tmp[13],
1177         density_x          : bigEndianToNative!ushort(tmp[14..16]),
1178         density_y          : bigEndianToNative!ushort(tmp[16..18]),
1179     };
1180 
1181     while (true) {
1182         ubyte[2] marker;
1183         stream.readExact(marker, 2);
1184 
1185         if (marker[0] != 0xff)
1186             throw new ImageIOException("no frame header");
1187         while (marker[1] == 0xff)
1188             stream.readExact(marker[1..$], 1);
1189 
1190         enum SKIP = 0xff;
1191         switch (marker[1]) with (Marker) {
1192             case SOF0: .. case SOF3: goto case;
1193             case SOF9: .. case SOF11:
1194                 header.type = marker[1];
1195                 stream.readExact(tmp[20..28], 8);
1196                 //int len = bigEndianToNative!ushort(tmp[20..22]);
1197                 header.precision = tmp[22];
1198                 header.height = bigEndianToNative!ushort(tmp[23..25]);
1199                 header.width = bigEndianToNative!ushort(tmp[25..27]);
1200                 header.num_comps = tmp[27];
1201                 // ignore the rest
1202                 return header;
1203             case SOS, EOI: throw new ImageIOException("no frame header");
1204             case DRI, DHT, DQT, COM: goto case SKIP;
1205             case APP0: .. case APPf: goto case SKIP;
1206             case SKIP:
1207                 ubyte[2] lenbuf = void;
1208                 stream.readExact(lenbuf, 2);
1209                 int skiplen = bigEndianToNative!ushort(lenbuf) - 2;
1210                 stream.seek(skiplen, SEEK_CUR);
1211                 break;
1212             default: throw new ImageIOException("unsupported marker");
1213         }
1214     }
1215     assert(0);
1216 }
1217 
1218 public IFImage read_jpeg(in char[] filename, long req_chans = 0) {
1219     scope reader = new Reader(filename);
1220     return read_jpeg(reader, req_chans);
1221 }
1222 
1223 public IFImage read_jpeg_from_mem(in ubyte[] source, long req_chans = 0) {
1224     scope reader = new Reader(source);
1225     return read_jpeg(reader, req_chans);
1226 }
1227 
1228 IFImage read_jpeg(Reader stream, long req_chans = 0) {
1229     if (req_chans < 0 || 4 < req_chans)
1230         throw new ImageIOException("come on...");
1231 
1232     ubyte[20] tmp = void;   // SOI, APP0, len, data
1233     stream.readExact(tmp, tmp.length);
1234 
1235     ushort len = bigEndianToNative!ushort(tmp[4..6]);
1236     if ( tmp[0..4] != [0xff,0xd8,0xff,0xe0] ||
1237          tmp[6..11] != ['J','F','I','F',0]  ||
1238          len < 16 )
1239         throw new ImageIOException("not JPEG/JFIF");
1240 
1241     if (tmp[11] != 1)   // major version (minor is at tmp[12])
1242         throw new ImageIOException("version not supported");
1243 
1244     //ubyte density_unit = tmp[13];
1245     //int density_x = bigEndianToNative!ushort(tmp[14..16]);
1246     //int density_y = bigEndianToNative!ushort(tmp[16..18]);
1247 
1248     int thumbsize = tmp[18] * tmp[19] * 3;
1249     if (thumbsize != cast(int) len - 16)
1250         throw new ImageIOException("corrupt header");
1251     if (thumbsize)
1252         stream.seek(thumbsize, SEEK_CUR);
1253 
1254     JPEG_Decoder dc = { stream: stream };
1255 
1256     read_markers(dc);   // reads until first scan header or eoi
1257     if (dc.eoi_reached)
1258         throw new ImageIOException("no image data");
1259 
1260     dc.tgt_chans = (req_chans == 0) ? dc.num_comps : cast(int) req_chans;
1261 
1262     IFImage result = {
1263         w      : dc.width,
1264         h      : dc.height,
1265         c      : cast(ColFmt) dc.tgt_chans,
1266         atype  : AlphaType.Plain,
1267         pixels : decode_jpeg(dc),
1268     };
1269     return result;
1270 }
1271 
1272 struct JPEG_Decoder {
1273     Reader stream;
1274 
1275     bool has_frame_header = false;
1276     bool eoi_reached = false;
1277 
1278     ubyte[64][4] qtables;
1279     HuffTab[2] ac_tables;
1280     HuffTab[2] dc_tables;
1281 
1282     ubyte cb;  // current byte (next bit always at MSB)
1283     int bits_left;   // num of unused bits in cb
1284 
1285     Component[3] comps;
1286     ubyte num_comps;
1287     int[3] index_for;   // index_for[0] is index of comp that comes first in stream
1288     int tgt_chans;
1289 
1290     long width, height;
1291 
1292     int hmax, vmax;
1293 
1294     ushort restart_interval;    // number of MCUs in restart interval
1295 
1296     // image component
1297     struct Component {
1298         ubyte id;
1299         ubyte sfx, sfy;   // sampling factors, aka. h and v
1300         long x, y;       // total num of samples, without fill samples
1301         ubyte qtable;
1302         ubyte ac_table;
1303         ubyte dc_table;
1304         int pred;                // dc prediction
1305         ubyte[] data;   // reconstructed samples
1306     }
1307 
1308     int num_mcu_x;
1309     int num_mcu_y;
1310 }
1311 
1312 struct HuffTab {
1313     // TODO where in the spec does it say 256 values/codes at most?
1314     ubyte[256] values;
1315     ubyte[257] sizes;
1316     short[16] mincode, maxcode;
1317     short[16] valptr;
1318 }
1319 
1320 enum Marker : ubyte {
1321     SOI = 0xd8,     // start of image
1322     SOF0 = 0xc0,    // start of frame / baseline DCT
1323     //SOF1 = 0xc1,    // start of frame / extended seq.
1324     //SOF2 = 0xc2,    // start of frame / progressive DCT
1325     SOF3 = 0xc3,    // start of frame / lossless
1326     SOF9 = 0xc9,    // start of frame / extended seq., arithmetic
1327     SOF11 = 0xcb,    // start of frame / lossless, arithmetic
1328     DHT = 0xc4,     // define huffman tables
1329     DQT = 0xdb,     // define quantization tables
1330     DRI = 0xdd,     // define restart interval
1331     SOS = 0xda,     // start of scan
1332     DNL = 0xdc,     // define number of lines
1333     RST0 = 0xd0,    // restart entropy coded data
1334     // ...
1335     RST7 = 0xd7,    // restart entropy coded data
1336     APP0 = 0xe0,    // application 0 segment
1337     // ...
1338     APPf = 0xef,    // application f segment
1339     //DAC = 0xcc,     // define arithmetic conditioning table
1340     COM = 0xfe,     // comment
1341     EOI = 0xd9,     // end of image
1342 }
1343 
1344 void read_markers(ref JPEG_Decoder dc) {
1345     bool has_next_scan_header = false;
1346     while (!has_next_scan_header && !dc.eoi_reached) {
1347         ubyte[2] marker;
1348         dc.stream.readExact(marker, 2);
1349 
1350         if (marker[0] != 0xff)
1351             throw new ImageIOException("no marker");
1352         while (marker[1] == 0xff)
1353             dc.stream.readExact(marker[1..$], 1);
1354 
1355         debug(DebugJPEG) writefln("marker: %s (%1$x)\t", cast(Marker) marker[1]);
1356         switch (marker[1]) with (Marker) {
1357             case DHT: dc.read_huffman_tables(); break;
1358             case DQT: dc.read_quantization_tables(); break;
1359             case SOF0:
1360                 if (dc.has_frame_header)
1361                     throw new ImageIOException("extra frame header");
1362                 debug(DebugJPEG) writeln();
1363                 dc.read_frame_header();
1364                 dc.has_frame_header = true;
1365                 break;
1366             case SOS:
1367                 if (!dc.has_frame_header)
1368                     throw new ImageIOException("no frame header");
1369                 dc.read_scan_header();
1370                 has_next_scan_header = true;
1371                 break;
1372             case DRI: dc.read_restart_interval(); break;
1373             case EOI: dc.eoi_reached = true; break;
1374             case APP0: .. case APPf: goto case;
1375             case COM:
1376                 debug(DebugJPEG) writefln("-> skipping segment");
1377                 ubyte[2] lenbuf = void;
1378                 dc.stream.readExact(lenbuf, lenbuf.length);
1379                 int len = bigEndianToNative!ushort(lenbuf) - 2;
1380                 dc.stream.seek(len, SEEK_CUR);
1381                 break;
1382             default: throw new ImageIOException("invalid / unsupported marker");
1383         }
1384     }
1385 }
1386 
1387 // DHT -- define huffman tables
1388 void read_huffman_tables(ref JPEG_Decoder dc) {
1389     ubyte[19] tmp = void;
1390     dc.stream.readExact(tmp, 2);
1391     int len = bigEndianToNative!ushort(tmp[0..2]);
1392     len -= 2;
1393 
1394     while (0 < len) {
1395         dc.stream.readExact(tmp, 17);   // info byte & the BITS
1396         ubyte table_slot = tmp[0] & 0xf; // must be 0 or 1 for baseline
1397         ubyte table_class = tmp[0] >> 4;  // 0 = dc table, 1 = ac table
1398         if (1 < table_slot || 1 < table_class)
1399             throw new ImageIOException("invalid / not supported");
1400 
1401         // compute total number of huffman codes
1402         int mt = 0;
1403         foreach (i; 1..17)
1404             mt += tmp[i];
1405         if (256 < mt)   // TODO where in the spec?
1406             throw new ImageIOException("invalid / not supported");
1407 
1408         if (table_class == 0) {
1409             dc.stream.readExact(dc.dc_tables[table_slot].values, mt);
1410             derive_table(dc.dc_tables[table_slot], tmp[1..17]);
1411         } else {
1412             dc.stream.readExact(dc.ac_tables[table_slot].values, mt);
1413             derive_table(dc.ac_tables[table_slot], tmp[1..17]);
1414         }
1415 
1416         len -= 17 + mt;
1417     }
1418 }
1419 
1420 // num_values is the BITS
1421 void derive_table(ref HuffTab table, in ref ubyte[16] num_values) {
1422     short[256] codes;
1423 
1424     int k = 0;
1425     foreach (i; 0..16) {
1426         foreach (j; 0..num_values[i]) {
1427             table.sizes[k] = cast(ubyte) (i + 1);
1428             ++k;
1429         }
1430     }
1431     table.sizes[k] = 0;
1432 
1433     k = 0;
1434     short code = 0;
1435     ubyte si = table.sizes[k];
1436     while (true) {
1437         do {
1438             codes[k] = code;
1439             ++code;
1440             ++k;
1441         } while (si == table.sizes[k]);
1442 
1443         if (table.sizes[k] == 0)
1444             break;
1445 
1446         debug(DebugJPEG) assert(si < table.sizes[k]);
1447         do {
1448             code <<= 1;
1449             ++si;
1450         } while (si != table.sizes[k]);
1451     }
1452 
1453     derive_mincode_maxcode_valptr(
1454         table.mincode, table.maxcode, table.valptr,
1455         codes, num_values
1456     );
1457 }
1458 
1459 // F.15
1460 void derive_mincode_maxcode_valptr(
1461         ref short[16] mincode, ref short[16] maxcode, ref short[16] valptr,
1462         in ref short[256] codes, in ref ubyte[16] num_values) pure
1463 {
1464     mincode[] = -1;
1465     maxcode[] = -1;
1466     valptr[] = -1;
1467 
1468     int j = 0;
1469     foreach (i; 0..16) {
1470         if (num_values[i] != 0) {
1471             valptr[i] = cast(short) j;
1472             mincode[i] = codes[j];
1473             j += num_values[i] - 1;
1474             maxcode[i] = codes[j];
1475             j += 1;
1476         }
1477     }
1478 }
1479 
1480 // DQT -- define quantization tables
1481 void read_quantization_tables(ref JPEG_Decoder dc) {
1482     ubyte[2] tmp = void;
1483     dc.stream.readExact(tmp, 2);
1484     int len = bigEndianToNative!ushort(tmp[0..2]);
1485     if (len % 65 != 2)
1486         throw new ImageIOException("invalid / not supported");
1487     len -= 2;
1488     while (0 < len) {
1489         dc.stream.readExact(tmp, 1);
1490         ubyte table_info = tmp[0];
1491         ubyte table_slot = table_info & 0xf;
1492         ubyte precision = table_info >> 4;  // 0 = 8 bit, 1 = 16 bit
1493         if (3 < table_slot || precision != 0)    // only 8 bit for baseline
1494             throw new ImageIOException("invalid / not supported");
1495 
1496         dc.stream.readExact(dc.qtables[table_slot], 64);
1497         len -= 1 + 64;
1498     }
1499 }
1500 
1501 // SOF0 -- start of frame
1502 void read_frame_header(ref JPEG_Decoder dc) {
1503     ubyte[9] tmp = void;
1504     dc.stream.readExact(tmp, 8);
1505     int len = bigEndianToNative!ushort(tmp[0..2]);  // 8 + num_comps*3
1506     ubyte precision = tmp[2];
1507     dc.height = bigEndianToNative!ushort(tmp[3..5]);
1508     dc.width = bigEndianToNative!ushort(tmp[5..7]);
1509     dc.num_comps = tmp[7];
1510 
1511     if ( precision != 8 ||
1512          (dc.num_comps != 1 && dc.num_comps != 3) ||
1513          len != 8 + dc.num_comps*3 )
1514         throw new ImageIOException("invalid / not supported");
1515 
1516     dc.hmax = 0;
1517     dc.vmax = 0;
1518     int mcu_du = 0; // data units in one mcu
1519     dc.stream.readExact(tmp, dc.num_comps*3);
1520     foreach (i; 0..dc.num_comps) {
1521         uint ci = tmp[i*3]-1;
1522         if (dc.num_comps <= ci)
1523             throw new ImageIOException("invalid / not supported");
1524         dc.index_for[i] = ci;
1525         auto comp = &dc.comps[ci];
1526         comp.id = tmp[i*3];
1527         ubyte sampling_factors = tmp[i*3 + 1];
1528         comp.sfx = sampling_factors >> 4;
1529         comp.sfy = sampling_factors & 0xf;
1530         comp.qtable = tmp[i*3 + 2];
1531         if ( comp.sfy < 1 || 4 < comp.sfy ||
1532              comp.sfx < 1 || 4 < comp.sfx ||
1533              3 < comp.qtable )
1534             throw new ImageIOException("invalid / not supported");
1535 
1536         if (dc.hmax < comp.sfx) dc.hmax = comp.sfx;
1537         if (dc.vmax < comp.sfy) dc.vmax = comp.sfy;
1538 
1539         mcu_du += comp.sfx * comp.sfy;
1540     }
1541     if (10 < mcu_du)
1542         throw new ImageIOException("invalid / not supported");
1543 
1544     foreach (i; 0..dc.num_comps) {
1545         dc.comps[i].x = cast(long) ceil(dc.width * (cast(double) dc.comps[i].sfx / dc.hmax));
1546         dc.comps[i].y = cast(long) ceil(dc.height * (cast(double) dc.comps[i].sfy / dc.vmax));
1547 
1548         debug(DebugJPEG) writefln("%d comp %d sfx/sfy: %d/%d", i, dc.comps[i].id,
1549                                                                   dc.comps[i].sfx,
1550                                                                   dc.comps[i].sfy);
1551     }
1552 
1553     long mcu_w = dc.hmax * 8;
1554     long mcu_h = dc.vmax * 8;
1555     dc.num_mcu_x = cast(int) ((dc.width + mcu_w-1) / mcu_w);
1556     dc.num_mcu_y = cast(int) ((dc.height + mcu_h-1) / mcu_h);
1557 
1558     debug(DebugJPEG) {
1559         writefln("\tlen: %s", len);
1560         writefln("\tprecision: %s", precision);
1561         writefln("\tdimensions: %s x %s", dc.width, dc.height);
1562         writefln("\tnum_comps: %s", dc.num_comps);
1563         writefln("\tnum_mcu_x: %s", dc.num_mcu_x);
1564         writefln("\tnum_mcu_y: %s", dc.num_mcu_y);
1565     }
1566 
1567 }
1568 
1569 // SOS -- start of scan
1570 void read_scan_header(ref JPEG_Decoder dc) {
1571     ubyte[3] tmp = void;
1572     dc.stream.readExact(tmp, tmp.length);
1573     ushort len = bigEndianToNative!ushort(tmp[0..2]);
1574     ubyte num_scan_comps = tmp[2];
1575 
1576     if ( num_scan_comps != dc.num_comps ||
1577          len != (6+num_scan_comps*2) )
1578         throw new ImageIOException("invalid / not supported");
1579 
1580     auto buf = (cast(ubyte*) alloca((len-3) * ubyte.sizeof))[0..len-3];
1581     dc.stream.readExact(buf, buf.length);
1582 
1583     foreach (i; 0..num_scan_comps) {
1584         ubyte comp_id = buf[i*2];
1585         int ci;    // component index
1586         while (ci < dc.num_comps && dc.comps[ci].id != comp_id) ++ci;
1587         if (dc.num_comps <= ci)
1588             throw new ImageIOException("invalid / not supported");
1589 
1590         ubyte tables = buf[i*2+1];
1591         dc.comps[ci].dc_table = tables >> 4;
1592         dc.comps[ci].ac_table = tables & 0xf;
1593         if ( 1 < dc.comps[ci].dc_table ||
1594              1 < dc.comps[ci].ac_table )
1595             throw new ImageIOException("invalid / not supported");
1596     }
1597 
1598     // ignore these
1599     //ubyte spectral_start = buf[$-3];
1600     //ubyte spectral_end = buf[$-2];
1601     //ubyte approx = buf[$-1];
1602 }
1603 
1604 void read_restart_interval(ref JPEG_Decoder dc) {
1605     ubyte[4] tmp = void;
1606     dc.stream.readExact(tmp, tmp.length);
1607     ushort len = bigEndianToNative!ushort(tmp[0..2]);
1608     if (len != 4)
1609         throw new ImageIOException("invalid / not supported");
1610     dc.restart_interval = bigEndianToNative!ushort(tmp[2..4]);
1611     debug(DebugJPEG) writeln("restart interval set to: ", dc.restart_interval);
1612 }
1613 
1614 // reads data after the SOS segment
1615 ubyte[] decode_jpeg(ref JPEG_Decoder dc) {
1616     foreach (ref comp; dc.comps[0..dc.num_comps])
1617         comp.data = new ubyte[dc.num_mcu_x*comp.sfx*8*dc.num_mcu_y*comp.sfy*8];
1618 
1619     // E.7 -- Multiple scans are for progressive images which are not supported
1620     //while (!dc.eoi_reached) {
1621         decode_scan(dc);    // E.2.3
1622         //read_markers(dc);   // reads until next scan header or eoi
1623     //}
1624 
1625     immutable conversion = dc.num_comps * 10 + dc.tgt_chans;
1626     switch (conversion) {
1627         case 34: return dc.reconstruct_image_rgba();
1628         case 33: return dc.reconstruct_image_rgb();
1629         case 32, 12:
1630             auto comp = &dc.comps[0];
1631             auto result = new ubyte[dc.width * dc.height * 2];
1632             if (comp.sfx == dc.hmax && comp.sfy == dc.vmax) {
1633                 long si, di;
1634                 foreach (j; 0 .. dc.height) {
1635                     si = j * dc.num_mcu_x * comp.sfx * 8;
1636                     foreach (i; 0 .. dc.width) {
1637                         result[di++] = comp.data[si++];
1638                         result[di++] = 255;
1639                     }
1640                 }
1641                 return result;
1642             } else {
1643                 // need to resample (haven't tested this...)
1644                 dc.upsample_gray_add_alpha(result);
1645                 return result;
1646             }
1647         case 31, 11:
1648             auto comp = &dc.comps[0];
1649             if (comp.sfx == dc.hmax && comp.sfy == dc.vmax) {
1650                 if (comp.data.length == dc.width * dc.height)
1651                     return comp.data;    // lucky!
1652                 auto result = new ubyte[dc.width * dc.height];
1653                 long si;
1654                 foreach (j; 0 .. dc.height) {
1655                     result[j*dc.width .. (j+1)*dc.width] =
1656                         comp.data[si .. si+dc.width];
1657                     si += dc.num_mcu_x * comp.sfx * 8;
1658                 }
1659                 return result;
1660             } else {
1661                 // need to resample (haven't tested this...)
1662                 auto result = new ubyte[dc.width * dc.height];
1663                 dc.upsample_gray(result);
1664                 return result;
1665             }
1666         case 14:
1667             auto result = new ubyte[dc.width * dc.height * 4];
1668             long di;
1669             foreach (j; 0 .. dc.height) {
1670                 long si = j * dc.num_mcu_x * dc.comps[0].sfx * 8;
1671                 foreach (i; 0 .. dc.width) {
1672                     result[di .. di+3] = dc.comps[0].data[si++];
1673                     result[di+3] = 255;
1674                     di += 4;
1675                 }
1676             }
1677             return result;
1678         case 13:
1679             auto result = new ubyte[dc.width * dc.height * 3];
1680             long di;
1681             foreach (j; 0 .. dc.height) {
1682                 long si = j * dc.num_mcu_x * dc.comps[0].sfx * 8;
1683                 foreach (i; 0 .. dc.width) {
1684                     result[di .. di+3] = dc.comps[0].data[si++];
1685                     di += 3;
1686                 }
1687             }
1688             return result;
1689         default: assert(0);
1690     }
1691 }
1692 
1693 // E.2.3 and E.8 and E.9
1694 void decode_scan(ref JPEG_Decoder dc) {
1695     debug(DebugJPEG) writeln("decode scan...");
1696 
1697     int intervals, mcus;
1698     if (0 < dc.restart_interval) {
1699         int total_mcus = dc.num_mcu_x * dc.num_mcu_y;
1700         intervals = (total_mcus + dc.restart_interval-1) / dc.restart_interval;
1701         mcus = dc.restart_interval;
1702     } else {
1703         intervals = 1;
1704         mcus = dc.num_mcu_x * dc.num_mcu_y;
1705     }
1706     debug(DebugJPEG) writeln("intervals: ", intervals);
1707 
1708     foreach (mcu_j; 0 .. dc.num_mcu_y) {
1709         foreach (mcu_i; 0 .. dc.num_mcu_x) {
1710 
1711             // decode mcu
1712             foreach (_c; 0..dc.num_comps) {
1713                 auto comp = &dc.comps[dc.index_for[_c]];
1714                 foreach (du_j; 0 .. comp.sfy) {
1715                     foreach (du_i; 0 .. comp.sfx) {
1716                         // decode entropy, dequantize & dezigzag
1717                         short[64] data = decode_block(dc, *comp, dc.qtables[comp.qtable]);
1718                         // idct & level-shift
1719                         int outx = (mcu_i * comp.sfx + du_i) * 8;
1720                         int outy = (mcu_j * comp.sfy + du_j) * 8;
1721                         int dst_stride = dc.num_mcu_x * comp.sfx*8;
1722                         ubyte* dst = comp.data.ptr + outy*dst_stride + outx;
1723                         stbi__idct_block(dst, dst_stride, data);
1724                     }
1725                 }
1726             }
1727 
1728             --mcus;
1729 
1730             if (!mcus) {
1731                 --intervals;
1732                 if (!intervals)
1733                     break;
1734 
1735                 read_restart(dc.stream);    // RSTx marker
1736 
1737                 if (intervals == 1) {
1738                     // last interval, may have fewer MCUs than defined by DRI
1739                     mcus = (dc.num_mcu_y - mcu_j - 1) * dc.num_mcu_x + dc.num_mcu_x - mcu_i - 1;
1740                 } else {
1741                     mcus = dc.restart_interval;
1742                 }
1743 
1744                 // reset decoder
1745                 dc.cb = 0;
1746                 dc.bits_left = 0;
1747                 foreach (k; 0..dc.num_comps)
1748                     dc.comps[k].pred = 0;
1749             }
1750 
1751         }
1752     }
1753 }
1754 
1755 // RST0-RST7
1756 void read_restart(Reader stream) {
1757     ubyte[2] tmp = void;
1758     stream.readExact(tmp, tmp.length);
1759     if (tmp[0] != 0xff || tmp[1] < Marker.RST0 || Marker.RST7 < tmp[1])
1760         throw new ImageIOException("reset marker missing");
1761     // the markers should cycle 0 through 7, could check that here...
1762 }
1763 
1764 immutable ubyte[64] dezigzag = [
1765      0,  1,  8, 16,  9,  2,  3, 10,
1766     17, 24, 32, 25, 18, 11,  4,  5,
1767     12, 19, 26, 33, 40, 48, 41, 34,
1768     27, 20, 13,  6,  7, 14, 21, 28,
1769     35, 42, 49, 56, 57, 50, 43, 36,
1770     29, 22, 15, 23, 30, 37, 44, 51,
1771     58, 59, 52, 45, 38, 31, 39, 46,
1772     53, 60, 61, 54, 47, 55, 62, 63,
1773 ];
1774 
1775 // decode entropy, dequantize & dezigzag (see section F.2)
1776 short[64] decode_block(ref JPEG_Decoder dc, ref JPEG_Decoder.Component comp,
1777                                                     in ref ubyte[64] qtable)
1778 {
1779     short[64] res;
1780 
1781     ubyte t = decode_huff(dc, dc.dc_tables[comp.dc_table]);
1782     int diff = t ? dc.receive_and_extend(t) : 0;
1783 
1784     comp.pred = comp.pred + diff;
1785     res[0] = cast(short) (comp.pred * qtable[0]);
1786 
1787     res[1..64] = 0;
1788     int k = 1;
1789     do {
1790         ubyte rs = decode_huff(dc, dc.ac_tables[comp.ac_table]);
1791         ubyte rrrr = rs >> 4;
1792         ubyte ssss = rs & 0xf;
1793 
1794         if (ssss == 0) {
1795             if (rrrr != 0xf)
1796                 break;      // end of block
1797             k += 16;    // run length is 16
1798             continue;
1799         }
1800 
1801         k += rrrr;
1802 
1803         if (63 < k)
1804             throw new ImageIOException("corrupt block");
1805         res[dezigzag[k]] = cast(short) (dc.receive_and_extend(ssss) * qtable[k]);
1806         k += 1;
1807     } while (k < 64);
1808 
1809     return res;
1810 }
1811 
1812 int receive_and_extend(ref JPEG_Decoder dc, ubyte s) {
1813     // receive
1814     int symbol = 0;
1815     foreach (_; 0..s)
1816         symbol = (symbol << 1) + nextbit(dc);
1817     // extend
1818     int vt = 1 << (s-1);
1819     if (symbol < vt)
1820         return symbol + (-1 << s) + 1;
1821     return symbol;
1822 }
1823 
1824 // F.16 -- the DECODE
1825 ubyte decode_huff(ref JPEG_Decoder dc, in ref HuffTab tab) {
1826     short code = nextbit(dc);
1827 
1828     int i = 0;
1829     while (tab.maxcode[i] < code) {
1830         code = cast(short) ((code << 1) + nextbit(dc));
1831         i += 1;
1832         if (tab.maxcode.length <= i)
1833             throw new ImageIOException("corrupt huffman coding");
1834     }
1835     int j = tab.valptr[i] + code - tab.mincode[i];
1836     if (tab.values.length <= cast(uint) j)
1837         throw new ImageIOException("corrupt huffman coding");
1838     return tab.values[j];
1839 }
1840 
1841 // F.2.2.5 and F.18
1842 ubyte nextbit(ref JPEG_Decoder dc) {
1843     if (!dc.bits_left) {
1844         ubyte[1] bytebuf;
1845         dc.stream.readExact(bytebuf, 1);
1846         dc.cb = bytebuf[0];
1847         dc.bits_left = 8;
1848 
1849         if (dc.cb == 0xff) {
1850             ubyte b2;
1851             dc.stream.readExact(bytebuf, 1);
1852             b2 = bytebuf[0];
1853 
1854             if (b2 != 0x0) {
1855                 throw new ImageIOException("unexpected marker");
1856             }
1857         }
1858     }
1859 
1860     ubyte r = dc.cb >> 7;
1861     dc.cb <<= 1;
1862     dc.bits_left -= 1;
1863     return r;
1864 }
1865 
1866 ubyte clamp(float x) pure {
1867     if (x < 0) return 0;
1868     else if (255 < x) return 255;
1869     return cast(ubyte) x;
1870 }
1871 
1872 ubyte[3] ycbcr_to_rgb(ubyte y, ubyte cb, ubyte cr) pure {
1873     ubyte[3] rgb = void;
1874     rgb[0] = clamp(y + 1.402*(cr-128));
1875     rgb[1] = clamp(y - 0.34414*(cb-128) - 0.71414*(cr-128));
1876     rgb[2] = clamp(y + 1.772*(cb-128));
1877     return rgb;
1878 }
1879 
1880 // ------------------------------------------------------------
1881 // The IDCT stuff here (to the next dashed line) is copied and adapted from
1882 // stb_image which is released under public domain.  Many thanks to stb_image
1883 // author, Sean Barrett.
1884 // Link: https://github.com/nothings/stb/blob/master/stb_image.h
1885 
1886 pure int f2f(float x) { return cast(int) (x * 4096 + 0.5); }
1887 pure int fsh(int x) { return x << 12; }
1888 
1889 // from stb_image, derived from jidctint -- DCT_ISLOW
1890 pure void STBI__IDCT_1D(ref int t0, ref int t1, ref int t2, ref int t3,
1891                         ref int x0, ref int x1, ref int x2, ref int x3,
1892         int s0, int s1, int s2, int s3, int s4, int s5, int s6, int s7)
1893 {
1894    int p1,p2,p3,p4,p5;
1895    //int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3;
1896    p2 = s2;
1897    p3 = s6;
1898    p1 = (p2+p3) * f2f(0.5411961f);
1899    t2 = p1 + p3 * f2f(-1.847759065f);
1900    t3 = p1 + p2 * f2f( 0.765366865f);
1901    p2 = s0;
1902    p3 = s4;
1903    t0 = fsh(p2+p3);
1904    t1 = fsh(p2-p3);
1905    x0 = t0+t3;
1906    x3 = t0-t3;
1907    x1 = t1+t2;
1908    x2 = t1-t2;
1909    t0 = s7;
1910    t1 = s5;
1911    t2 = s3;
1912    t3 = s1;
1913    p3 = t0+t2;
1914    p4 = t1+t3;
1915    p1 = t0+t3;
1916    p2 = t1+t2;
1917    p5 = (p3+p4)*f2f( 1.175875602f);
1918    t0 = t0*f2f( 0.298631336f);
1919    t1 = t1*f2f( 2.053119869f);
1920    t2 = t2*f2f( 3.072711026f);
1921    t3 = t3*f2f( 1.501321110f);
1922    p1 = p5 + p1*f2f(-0.899976223f);
1923    p2 = p5 + p2*f2f(-2.562915447f);
1924    p3 = p3*f2f(-1.961570560f);
1925    p4 = p4*f2f(-0.390180644f);
1926    t3 += p1+p4;
1927    t2 += p2+p3;
1928    t1 += p2+p4;
1929    t0 += p1+p3;
1930 }
1931 
1932 // idct and level-shift
1933 pure void stbi__idct_block(ubyte* dst, int dst_stride, in ref short[64] data) {
1934    int i;
1935    int[64] val;
1936    int* v = val.ptr;
1937    const(short)* d = data.ptr;
1938 
1939    // columns
1940    for (i=0; i < 8; ++i,++d, ++v) {
1941       // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing
1942       if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0
1943            && d[40]==0 && d[48]==0 && d[56]==0) {
1944          //    no shortcut                 0     seconds
1945          //    (1|2|3|4|5|6|7)==0          0     seconds
1946          //    all separate               -0.047 seconds
1947          //    1 && 2|3 && 4|5 && 6|7:    -0.047 seconds
1948          int dcterm = d[0] << 2;
1949          v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm;
1950       } else {
1951          int t0,t1,t2,t3,x0,x1,x2,x3;
1952          STBI__IDCT_1D(
1953              t0, t1, t2, t3,
1954              x0, x1, x2, x3,
1955              d[ 0], d[ 8], d[16], d[24],
1956              d[32], d[40], d[48], d[56]
1957          );
1958          // constants scaled things up by 1<<12; let's bring them back
1959          // down, but keep 2 extra bits of precision
1960          x0 += 512; x1 += 512; x2 += 512; x3 += 512;
1961          v[ 0] = (x0+t3) >> 10;
1962          v[56] = (x0-t3) >> 10;
1963          v[ 8] = (x1+t2) >> 10;
1964          v[48] = (x1-t2) >> 10;
1965          v[16] = (x2+t1) >> 10;
1966          v[40] = (x2-t1) >> 10;
1967          v[24] = (x3+t0) >> 10;
1968          v[32] = (x3-t0) >> 10;
1969       }
1970    }
1971 
1972    ubyte* o = dst;
1973    for (i=0, v=val.ptr; i < 8; ++i,v+=8,o+=dst_stride) {
1974       // no fast case since the first 1D IDCT spread components out
1975       int t0,t1,t2,t3,x0,x1,x2,x3;
1976       STBI__IDCT_1D(
1977           t0, t1, t2, t3,
1978           x0, x1, x2, x3,
1979           v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]
1980       );
1981       // constants scaled things up by 1<<12, plus we had 1<<2 from first
1982       // loop, plus horizontal and vertical each scale by sqrt(8) so together
1983       // we've got an extra 1<<3, so 1<<17 total we need to remove.
1984       // so we want to round that, which means adding 0.5 * 1<<17,
1985       // aka 65536. Also, we'll end up with -128 to 127 that we want
1986       // to encode as 0-255 by adding 128, so we'll add that before the shift
1987       x0 += 65536 + (128<<17);
1988       x1 += 65536 + (128<<17);
1989       x2 += 65536 + (128<<17);
1990       x3 += 65536 + (128<<17);
1991       // tried computing the shifts into temps, or'ing the temps to see
1992       // if any were out of range, but that was slower
1993       o[0] = stbi__clamp((x0+t3) >> 17);
1994       o[7] = stbi__clamp((x0-t3) >> 17);
1995       o[1] = stbi__clamp((x1+t2) >> 17);
1996       o[6] = stbi__clamp((x1-t2) >> 17);
1997       o[2] = stbi__clamp((x2+t1) >> 17);
1998       o[5] = stbi__clamp((x2-t1) >> 17);
1999       o[3] = stbi__clamp((x3+t0) >> 17);
2000       o[4] = stbi__clamp((x3-t0) >> 17);
2001    }
2002 }
2003 
2004 // clamp to 0-255
2005 pure ubyte stbi__clamp(int x) {
2006    if (cast(uint) x > 255) {
2007       if (x < 0) return 0;
2008       if (x > 255) return 255;
2009    }
2010    return cast(ubyte) x;
2011 }
2012 
2013 // the above is adapted from stb_image
2014 // ------------------------------------------------------------
2015 
2016 ubyte[] reconstruct_image_rgb(ref JPEG_Decoder dc) {
2017     bool resample = false;
2018     foreach (const ref comp; dc.comps[0..dc.num_comps]) {
2019         if (comp.sfx != dc.hmax || comp.sfy != dc.vmax) {
2020             resample = true;
2021             break;
2022         }
2023     }
2024 
2025     ubyte[] result = new ubyte[dc.width * dc.height * 3];
2026 
2027     if (resample) {
2028         debug(DebugJPEG) writeln("resampling...");
2029         dc.upsample_nearest(result);
2030         return result;
2031     }
2032 
2033     long stride = dc.num_mcu_x * dc.comps[0].sfx * 8;
2034     foreach (j; 0 .. dc.height) {
2035         foreach (i; 0 .. dc.width) {
2036             long di = (j*dc.width + i) * 3;
2037             long si = j*stride + i;
2038             result[di .. di+3] = ycbcr_to_rgb(
2039                 dc.comps[0].data[si],
2040                 dc.comps[1].data[si],
2041                 dc.comps[2].data[si],
2042             );
2043         }
2044     }
2045     return result;
2046 }
2047 
2048 ubyte[] reconstruct_image_rgba(ref JPEG_Decoder dc) {
2049     bool resample = false;
2050     foreach (const ref comp; dc.comps[0..dc.num_comps]) {
2051         if (comp.sfx != dc.hmax || comp.sfy != dc.vmax) {
2052             resample = true;
2053             break;
2054         }
2055     }
2056 
2057     ubyte[] result = new ubyte[dc.width * dc.height * 4];
2058 
2059     if (resample) {
2060         debug(DebugJPEG) writeln("resampling...");
2061         dc.upsample_nearest(result);
2062         return result;
2063     }
2064 
2065     long stride = dc.num_mcu_x * dc.comps[0].sfx * 8;
2066     foreach (j; 0 .. dc.height) {
2067         foreach (i; 0 .. dc.width) {
2068             long di = (j*dc.width + i) * 4;
2069             long si = j*stride + i;
2070             result[di .. di+3] = ycbcr_to_rgb(
2071                 dc.comps[0].data[si],
2072                 dc.comps[1].data[si],
2073                 dc.comps[2].data[si],
2074             );
2075             result[di+3] = 255;
2076         }
2077     }
2078     return result;
2079 }
2080 
2081 void upsample_gray(ref JPEG_Decoder dc, ubyte[] result) {
2082     long stride0 = dc.num_mcu_x * dc.comps[0].sfx * 8;
2083     double si0yratio = cast(double) dc.comps[0].y / dc.height;
2084     double si0xratio = cast(double) dc.comps[0].x / dc.width;
2085     long si0;
2086 
2087     foreach (j; 0 .. dc.height) {
2088         si0 = cast(long) floor(j * si0yratio) * stride0;
2089         foreach (i; 0 .. dc.width) {
2090             long di = (j*dc.width + i);
2091             result[di] =
2092                 dc.comps[0].data[si0 + cast(long) floor(i * si0xratio)];
2093         }
2094     }
2095 }
2096 
2097 void upsample_gray_add_alpha(ref JPEG_Decoder dc, ubyte[] result) {
2098     long stride0 = dc.num_mcu_x * dc.comps[0].sfx * 8;
2099     double si0yratio = cast(double) dc.comps[0].y / dc.height;
2100     double si0xratio = cast(double) dc.comps[0].x / dc.width;
2101     long si0, di;
2102 
2103     foreach (j; 0 .. dc.height) {
2104         si0 = cast(long) floor(j * si0yratio) * stride0;
2105         foreach (i; 0 .. dc.width) {
2106             result[di++] = dc.comps[0].data[si0 + cast(long) floor(i * si0xratio)];
2107             result[di++] = 255;
2108         }
2109     }
2110 }
2111 
2112 void upsample_nearest(ref JPEG_Decoder dc, ubyte[] result) {
2113     long stride0 = dc.num_mcu_x * dc.comps[0].sfx * 8;
2114     long stride1 = dc.num_mcu_x * dc.comps[1].sfx * 8;
2115     long stride2 = dc.num_mcu_x * dc.comps[2].sfx * 8;
2116 
2117     double si0yratio = cast(double) dc.comps[0].y / dc.height;
2118     double si1yratio = cast(double) dc.comps[1].y / dc.height;
2119     double si2yratio = cast(double) dc.comps[2].y / dc.height;
2120     double si0xratio = cast(double) dc.comps[0].x / dc.width;
2121     double si1xratio = cast(double) dc.comps[1].x / dc.width;
2122     double si2xratio = cast(double) dc.comps[2].x / dc.width;
2123     long si0, si1, si2, di;
2124 
2125     foreach (j; 0 .. dc.height) {
2126         si0 = cast(long) floor(j * si0yratio) * stride0;
2127         si1 = cast(long) floor(j * si1yratio) * stride1;
2128         si2 = cast(long) floor(j * si2yratio) * stride2;
2129 
2130         foreach (i; 0 .. dc.width) {
2131             result[di .. di+3] = ycbcr_to_rgb(
2132                 dc.comps[0].data[si0 + cast(long) floor(i * si0xratio)],
2133                 dc.comps[1].data[si1 + cast(long) floor(i * si1xratio)],
2134                 dc.comps[2].data[si2 + cast(long) floor(i * si2xratio)],
2135             );
2136             if (dc.tgt_chans == 4)
2137                 result[di+3] = 255;
2138             di += dc.tgt_chans;
2139         }
2140     }
2141 }
2142 
2143 void read_jpeg_info(Reader stream, out long w, out long h, out long chans) {
2144     JPEG_Header hdr = read_jpeg_header(stream);
2145     w = hdr.width;
2146     h = hdr.height;
2147     chans = hdr.num_comps;
2148 }
2149 
2150 static this() {
2151     register["jpg"] = ImageIOFuncs(&read_jpeg, null, &read_jpeg_info);
2152     register["jpeg"] = ImageIOFuncs(&read_jpeg, null, &read_jpeg_info);
2153 }
2154 
2155 // --------------------------------------------------------------------------------
2156 // Conversions
2157 
2158 enum _ColFmt : int {
2159     Unknown = 0,
2160     Y = 1,
2161     YA,
2162     RGB,
2163     RGBA,
2164     BGR,
2165     BGRA,
2166 }
2167 
2168 pure
2169 void function(in ubyte[] src, ubyte[] tgt) get_converter(int src_chans, int tgt_chans) {
2170     int combo(int a, int b) pure nothrow { return a*16 + b; }
2171 
2172     if (src_chans == tgt_chans)
2173         return &copy_line;
2174 
2175     switch (combo(src_chans, tgt_chans)) with (_ColFmt) {
2176         case combo(Y, YA)      : return &Y_to_YA;
2177         case combo(Y, RGB)     : return &Y_to_RGB;
2178         case combo(Y, RGBA)    : return &Y_to_RGBA;
2179         case combo(Y, BGR)     : return &Y_to_BGR;
2180         case combo(Y, BGRA)    : return &Y_to_BGRA;
2181         case combo(YA, Y)      : return &YA_to_Y;
2182         case combo(YA, RGB)    : return &YA_to_RGB;
2183         case combo(YA, RGBA)   : return &YA_to_RGBA;
2184         case combo(YA, BGR)    : return &YA_to_BGR;
2185         case combo(YA, BGRA)   : return &YA_to_BGRA;
2186         case combo(RGB, Y)     : return &RGB_to_Y;
2187         case combo(RGB, YA)    : return &RGB_to_YA;
2188         case combo(RGB, RGBA)  : return &RGB_to_RGBA;
2189         case combo(RGB, BGR)   : return &RGB_to_BGR;
2190         case combo(RGB, BGRA)  : return &RGB_to_BGRA;
2191         case combo(RGBA, Y)    : return &RGBA_to_Y;
2192         case combo(RGBA, YA)   : return &RGBA_to_YA;
2193         case combo(RGBA, RGB)  : return &RGBA_to_RGB;
2194         case combo(RGBA, BGR)  : return &RGBA_to_BGR;
2195         case combo(RGBA, BGRA) : return &RGBA_to_BGRA;
2196         case combo(BGR, Y)     : return &BGR_to_Y;
2197         case combo(BGR, YA)    : return &BGR_to_YA;
2198         case combo(BGR, RGB)   : return &BGR_to_RGB;
2199         case combo(BGR, RGBA)  : return &BGR_to_RGBA;
2200         case combo(BGRA, Y)    : return &BGRA_to_Y;
2201         case combo(BGRA, YA)   : return &BGRA_to_YA;
2202         case combo(BGRA, RGB)  : return &BGRA_to_RGB;
2203         case combo(BGRA, RGBA) : return &BGRA_to_RGBA;
2204         default                : throw new ImageIOException("internal error");
2205     }
2206 }
2207 
2208 void copy_line(in ubyte[] src, ubyte[] tgt) pure nothrow {
2209     tgt[0..$] = src[0..$];
2210 }
2211 
2212 ubyte luminance(ubyte r, ubyte g, ubyte b) pure nothrow {
2213     return cast(ubyte) (0.21*r + 0.64*g + 0.15*b); // somewhat arbitrary weights
2214 }
2215 
2216 void Y_to_YA(in ubyte[] src, ubyte[] tgt) pure nothrow {
2217     for (long k, t;   k < src.length;   k+=1, t+=2) {
2218         tgt[t] = src[k];
2219         tgt[t+1] = 255;
2220     }
2221 }
2222 
2223 alias Y_to_BGR = Y_to_RGB;
2224 void Y_to_RGB(in ubyte[] src, ubyte[] tgt) pure nothrow {
2225     for (long k, t;   k < src.length;   k+=1, t+=3)
2226         tgt[t .. t+3] = src[k];
2227 }
2228 
2229 alias Y_to_BGRA = Y_to_RGBA;
2230 void Y_to_RGBA(in ubyte[] src, ubyte[] tgt) pure nothrow {
2231     for (long k, t;   k < src.length;   k+=1, t+=4) {
2232         tgt[t .. t+3] = src[k];
2233         tgt[t+3] = 255;
2234     }
2235 }
2236 
2237 void YA_to_Y(in ubyte[] src, ubyte[] tgt) pure nothrow {
2238     for (long k, t;   k < src.length;   k+=2, t+=1)
2239         tgt[t] = src[k];
2240 }
2241 
2242 alias YA_to_BGR = YA_to_RGB;
2243 void YA_to_RGB(in ubyte[] src, ubyte[] tgt) pure nothrow {
2244     for (long k, t;   k < src.length;   k+=2, t+=3)
2245         tgt[t .. t+3] = src[k];
2246 }
2247 
2248 alias YA_to_BGRA = YA_to_RGBA;
2249 void YA_to_RGBA(in ubyte[] src, ubyte[] tgt) pure nothrow {
2250     for (long k, t;   k < src.length;   k+=2, t+=4) {
2251         tgt[t .. t+3] = src[k];
2252         tgt[t+3] = src[k+1];
2253     }
2254 }
2255 
2256 void RGB_to_Y(in ubyte[] src, ubyte[] tgt) pure nothrow {
2257     for (long k, t;   k < src.length;   k+=3, t+=1)
2258         tgt[t] = luminance(src[k], src[k+1], src[k+2]);
2259 }
2260 
2261 void RGB_to_YA(in ubyte[] src, ubyte[] tgt) pure nothrow {
2262     for (long k, t;   k < src.length;   k+=3, t+=2) {
2263         tgt[t] = luminance(src[k], src[k+1], src[k+2]);
2264         tgt[t+1] = 255;
2265     }
2266 }
2267 
2268 void RGB_to_RGBA(in ubyte[] src, ubyte[] tgt) pure nothrow {
2269     for (long k, t;   k < src.length;   k+=3, t+=4) {
2270         tgt[t .. t+3] = src[k .. k+3];
2271         tgt[t+3] = 255;
2272     }
2273 }
2274 
2275 void RGBA_to_Y(in ubyte[] src, ubyte[] tgt) pure nothrow {
2276     for (long k, t;   k < src.length;   k+=4, t+=1)
2277         tgt[t] = luminance(src[k], src[k+1], src[k+2]);
2278 }
2279 
2280 void RGBA_to_YA(in ubyte[] src, ubyte[] tgt) pure nothrow {
2281     for (long k, t;   k < src.length;   k+=4, t+=2) {
2282         tgt[t] = luminance(src[k], src[k+1], src[k+2]);
2283         tgt[t+1] = src[k+3];
2284     }
2285 }
2286 
2287 void RGBA_to_RGB(in ubyte[] src, ubyte[] tgt) pure nothrow {
2288     for (long k, t;   k < src.length;   k+=4, t+=3)
2289         tgt[t .. t+3] = src[k .. k+3];
2290 }
2291 
2292 void BGR_to_Y(in ubyte[] src, ubyte[] tgt) pure nothrow {
2293     for (long k, t;   k < src.length;   k+=3, t+=1)
2294         tgt[t] = luminance(src[k+2], src[k+1], src[k+1]);
2295 }
2296 
2297 void BGR_to_YA(in ubyte[] src, ubyte[] tgt) pure nothrow {
2298     for (long k, t;   k < src.length;   k+=3, t+=2) {
2299         tgt[t] = luminance(src[k+2], src[k+1], src[k+1]);
2300         tgt[t+1] = 255;
2301     }
2302 }
2303 
2304 alias RGB_to_BGR = BGR_to_RGB;
2305 void BGR_to_RGB(in ubyte[] src, ubyte[] tgt) pure nothrow {
2306     for (long k;   k < src.length;   k+=3) {
2307         tgt[k  ] = src[k+2];
2308         tgt[k+1] = src[k+1];
2309         tgt[k+2] = src[k  ];
2310     }
2311 }
2312 
2313 alias RGB_to_BGRA = BGR_to_RGBA;
2314 void BGR_to_RGBA(in ubyte[] src, ubyte[] tgt) pure nothrow {
2315     for (long k, t;   k < src.length;   k+=3, t+=4) {
2316         tgt[t  ] = src[k+2];
2317         tgt[t+1] = src[k+1];
2318         tgt[t+2] = src[k  ];
2319         tgt[t+3] = 255;
2320     }
2321 }
2322 
2323 void BGRA_to_Y(in ubyte[] src, ubyte[] tgt) pure nothrow {
2324     for (long k, t;   k < src.length;   k+=4, t+=1)
2325         tgt[t] = luminance(src[k+2], src[k+1], src[k]);
2326 }
2327 
2328 void BGRA_to_YA(in ubyte[] src, ubyte[] tgt) pure nothrow {
2329     for (long k, t;   k < src.length;   k+=4, t+=2) {
2330         tgt[t] = luminance(src[k+2], src[k+1], src[k]);
2331         tgt[t+1] = 255;
2332     }
2333 }
2334 
2335 alias RGBA_to_BGR = BGRA_to_RGB;
2336 void BGRA_to_RGB(in ubyte[] src, ubyte[] tgt) pure nothrow {
2337     for (long k, t;   k < src.length;   k+=4, t+=3) {
2338         tgt[t  ] = src[k+2];
2339         tgt[t+1] = src[k+1];
2340         tgt[t+2] = src[k  ];
2341     }
2342 }
2343 
2344 alias RGBA_to_BGRA = BGRA_to_RGBA;
2345 void BGRA_to_RGBA(in ubyte[] src, ubyte[] tgt) pure nothrow {
2346     for (long k, t;   k < src.length;   k+=4, t+=4) {
2347         tgt[t  ] = src[k+2];
2348         tgt[t+1] = src[k+1];
2349         tgt[t+2] = src[k  ];
2350         tgt[t+3] = src[k+3];
2351     }
2352 }
2353 
2354 // --------------------------------------------------------------------------------
2355 
2356 class Reader {
2357     const void delegate(ubyte[], size_t) readExact;
2358     const void delegate(long, int) seek;
2359 
2360     this(in char[] filename) {
2361         this(File(filename.idup, "rb"));
2362     }
2363 
2364     this(File f) {
2365         if (!f.isOpen) throw new ImageIOException("File not open");
2366         this.f = f;
2367         this.readExact = &file_readExact;
2368         this.seek = &file_seek;
2369         this.source = null;
2370     }
2371 
2372     this(in ubyte[] source) {
2373         this.source = source;
2374         this.readExact = &mem_readExact;
2375         this.seek = &mem_seek;
2376     }
2377 
2378     private:
2379 
2380     File f;
2381     void file_readExact(ubyte[] buffer, size_t bytes) {
2382         auto slice = this.f.rawRead(buffer[0..bytes]);
2383         if (slice.length != bytes)
2384             throw new Exception("not enough data");
2385     }
2386     void file_seek(long offset, int origin) { this.f.seek(offset, origin); }
2387 
2388     const ubyte[] source;
2389     long cursor;
2390     void mem_readExact(ubyte[] buffer, size_t bytes) {
2391         if (source.length - cursor < bytes)
2392             throw new Exception("not enough data");
2393         buffer[0..bytes] = source[cursor .. cursor+bytes];
2394         cursor += bytes;
2395     }
2396     void mem_seek(long offset, int origin) {
2397         switch (origin) {
2398             case SEEK_SET:
2399                 if (offset < 0 || source.length <= offset)
2400                     throw new Exception("seek error");
2401                 cursor = offset;
2402                 break;
2403             case SEEK_CUR:
2404                 long dst = cursor + offset;
2405                 if (dst < 0 || source.length <= dst)
2406                     throw new Exception("seek error");
2407                 cursor = dst;
2408                 break;
2409             case SEEK_END:
2410                 if (0 <= offset || source.length < -offset)
2411                     throw new Exception("seek error");
2412                 cursor = cast(long) source.length + offset;
2413                 break;
2414             default: assert(0);
2415         }
2416     }
2417 }
2418 
2419 class Writer {
2420     const void delegate(in ubyte[]) rawWrite;
2421     const void delegate() flush;
2422 
2423     this(in char[] filename) {
2424         this(File(filename.idup, "wb"));
2425     }
2426 
2427     this(File f) {
2428         if (!f.isOpen) throw new ImageIOException("File not open");
2429         this.f = f;
2430         this.rawWrite = &file_rawWrite;
2431         this.flush = &file_flush;
2432     }
2433 
2434     this() {
2435         this.rawWrite = &mem_rawWrite;
2436         this.flush = &mem_flush;
2437     }
2438 
2439     @property ubyte[] result() { return buffer; }
2440 
2441     private:
2442 
2443     File f;
2444     void file_rawWrite(in ubyte[] block) { this.f.rawWrite(block); }
2445     void file_flush() { this.f.flush(); }
2446 
2447     ubyte[] buffer;
2448     void mem_rawWrite(in ubyte[] block) { this.buffer ~= block; }
2449     void mem_flush() { }
2450 }
2451 
2452 const(char)[] extract_extension_lowercase(in char[] filename) {
2453     ptrdiff_t di = filename.lastIndexOf('.');
2454     return (0 < di && di+1 < filename.length) ? filename[di+1..$].toLower() : "";
2455 }
2456 
2457 immutable ImageIOFuncs[string] register;
2458 
2459 struct ImageIOFuncs {
2460     IFImage function(Reader s, long req_chans) read_image;
2461     void function(Writer s, long w, long h, in ubyte[] data, long req_chans) write_image;
2462     void function(Reader s, out long w, out long h, out long c) read_info;
2463 }