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