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