1 // Copyright 2019 Tero Hänninen. All rights reserved.
2 // SPDX-License-Identifier: BSD-2-Clause
3 module imagefmt;
4 
5 import core.stdc.stdio;
6 import cstd = core.stdc.stdlib;
7 import imagefmt.bmp;
8 import imagefmt.tga;
9 import imagefmt.png;
10 import imagefmt.jpeg;
11 
12 @nogc nothrow:
13 
14 /// Basic image information.
15 struct IFInfo {
16     int w;              /// width
17     int h;              /// height
18     ubyte c;            /// channels
19     ubyte e;            /// error code or zero
20 }
21 
22 /// Image returned from the read functions. Data is in buf8 or buf16.
23 struct IFImage {
24     int w;              /// width
25     int h;              /// height
26     ubyte c;            /// channels in buf, 1 = y, 2 = ya, 3 = rgb, 4 = rgba
27     ubyte cinfile;      /// channels found in file
28     ubyte bpc;          /// bits per channel, 8 or 16
29     ubyte e;            /// error code or zero
30     union {
31         ubyte[] buf8;       ///
32         ushort[] buf16;     ///
33     }
34 
35     @nogc nothrow:
36 
37     /// Frees the image data.
38     void free() {
39         _free(buf8.ptr);
40         buf8 = null;
41     }
42 }
43 
44 /// Read interface.
45 struct Read {
46     void* stream;
47     /// returns number of bytes read; tries to read n bytes
48     int function(void* stream, ubyte* buf, int n) @nogc nothrow read;
49     /// returns 0 on success, -1 on error;
50     /// sets cursor to off(set) from current position
51     int function(void* stream, int off) @nogc nothrow seek;
52 }
53 
54 /// Write interface.
55 struct Write {
56     void* stream;
57     /// returns the number of bytes written; tries to write all of buf.
58     int function(void* stream, ubyte[] buf) @nogc nothrow write;
59     /// returns 0 on success, -1 on error; forces a write of still unwritten data.
60     int function(void* stream) @nogc nothrow flush;
61 }
62 
63 int fileread(void* st, ubyte* buf, int n)
64 {
65     return cast(int) fread(buf, 1, n, cast(FILE*) st);
66 }
67 
68 int fileseek(void* st, int off)
69 {
70     return fseek(cast(FILE*) st, off, SEEK_CUR);
71 }
72 
73 int filewrite(void* st, ubyte[] buf)
74 {
75     return cast(int) fwrite(buf.ptr, 1, buf.length, cast(FILE*) st);
76 }
77 
78 int fileflush(void* st)
79 {
80     return fflush(cast(FILE*) st);
81 }
82 
83 /// Maximum size for the result buffer the loader functions
84 /// don't reject with a "too large" error.
85 ulong MAXIMUM_IMAGE_SIZE = 0x7fff_ffff;
86 
87 version(IF__CUSTOM_ALLOC) {
88     void* if__allocator;
89     void* function(void* al, size_t size)            if__malloc;
90     void* function(void* al, void* ptr, size_t size) if__realloc;
91     void function(void* al, void* ptr)               if__free;
92 
93     void* _malloc(size_t size)             { return if__malloc(if__allocator, size);       }
94     void* _realloc(void* ptr, size_t size) { return if__realloc(if__allocator, ptr, size); }
95     void _free(void* ptr)                  { return if__free(if__allocator, ptr);          }
96 } else {
97     void* _malloc(size_t size)             { return cstd.malloc(size);       }
98     void* _realloc(void* ptr, size_t size) { return cstd.realloc(ptr, size); }
99     void _free(void* ptr)                  { return cstd.free(ptr);          }
100 }
101 
102 /// Error values returned from the functions.
103 enum ERROR { fopen = 1, oom, stream, data, oddfmt, unsupp, dim, arg, bigimg,
104              nodata, lackdata, zinit, zstream }
105 
106 /// Descriptions for errors.
107 immutable string[ERROR.max + 1] IF_ERROR = [
108     0              : "no error",
109     ERROR.fopen    : "cannot open file",
110     ERROR.oom      : "out of memory",
111     ERROR.stream   : "stream error",
112     ERROR.data     : "bad data",
113     ERROR.oddfmt   : "unknown format",
114     ERROR.unsupp   : "unsupported",
115     ERROR.dim      : "invalid dimensions",
116     ERROR.arg      : "bad argument",
117     ERROR.bigimg   : "image too large",
118     ERROR.nodata   : "no data", // at all
119     ERROR.lackdata : "not enough data",
120     ERROR.zinit    : "zlib init failed",
121     ERROR.zstream  : "zlib stream error",
122 ];
123 
124 /// Reads basic information about an image.
125 IFInfo read_info(in char[] fname)
126 {
127     IFInfo info;
128     auto tmp = NTString(fname);
129     if (!tmp.ptr) {
130         info.e = ERROR.oom;
131         return info;
132     }
133     FILE* f = fopen(tmp.ptr, "rb");
134     tmp.drop();
135     if (!f) {
136         info.e = ERROR.fopen;
137         return info;
138     }
139     info = read_info(f);
140     fclose(f);
141     return info;
142 }
143 
144 /// Reads from f which must already be open. Does not close it afterwards.
145 IFInfo read_info(FILE* f)
146 {
147     Read io = { cast(void*) f, &fileread, &fileseek };
148     return read_info(io);
149 }
150 
151 /// Reads basic information about an image.
152 IFInfo read_info(Read io)
153 {
154     ubyte[256] iobuf;
155     IFInfo info;
156     Reader rc;
157     info.e = init_reader(&rc, io, iobuf[0..$]);
158     if (info.e) return info;
159     if (detect_png(&rc)) return read_png_info(&rc);
160     if (detect_bmp(&rc)) return read_bmp_info(&rc);
161     if (detect_jpeg(&rc)) return read_jpeg_info(&rc);
162     if (detect_tga(&rc)) return read_tga_info(&rc);
163     info.e = ERROR.oddfmt;
164     return info;
165 }
166 
167 /// Reads basic information about an image.
168 IFInfo read_info(in ubyte[] buf)
169 {
170     IFInfo info;
171     Reader rc;
172     Read io = { null, null, null };
173     info.e = init_reader(&rc, io, cast(ubyte[]) buf); // the cast? care is taken!
174     if (info.e) return info;
175     if (detect_png(&rc)) return read_png_info(&rc);
176     if (detect_bmp(&rc)) return read_bmp_info(&rc);
177     if (detect_jpeg(&rc)) return read_jpeg_info(&rc);
178     if (detect_tga(&rc)) return read_tga_info(&rc);
179     info.e = ERROR.oddfmt;
180     return info;
181 }
182 
183 /// Reads an image file, detecting its type.
184 IFImage read_image(in char[] fname, in int c = 0, in int bpc = 8)
185 {
186     IFImage image;
187     auto tmp = NTString(fname);
188     if (!tmp.ptr) {
189         image.e = ERROR.oom;
190         return image;
191     }
192     FILE* f = fopen(tmp.ptr, "rb");
193     tmp.drop();
194     if (f) {
195         image = read_image(f, c, bpc);
196         fclose(f);
197     } else
198         image.e = ERROR.fopen;
199     return image;
200 }
201 
202 /// Reads from f which must already be open. Does not close it afterwards.
203 IFImage read_image(FILE* f, in int c = 0, in int bpc = 8)
204 {
205     IFImage image;
206     Read io = { cast(void*) f, &fileread, &fileseek };
207     image = read_image(io, c, bpc);
208     return image;
209 }
210 
211 /// Reads an image using given io functions.
212 IFImage read_image(Read io, in int c = 0, in int bpc = 8)
213 {
214     IFImage image;
215     Reader rc;
216     if (!io.stream || !io.read || !io.seek) {
217         image.e = ERROR.arg;
218         return image;
219     }
220     ubyte e;
221     ubyte[] iobuf = new_buffer(4096, e);    if (e) return image;
222     scope(exit) _free(iobuf.ptr);
223     image.e = init_reader(&rc, io, iobuf);  if (image.e) return image;
224     if (detect_png(&rc))  { image.e = read_png(&rc, image, c, bpc);  return image; }
225     if (detect_bmp(&rc))  { image.e = read_bmp(&rc, image, c, bpc);  return image; }
226     if (detect_jpeg(&rc)) { image.e = read_jpeg(&rc, image, c, bpc); return image; }
227     if (detect_tga(&rc))  { image.e = read_tga(&rc, image, c, bpc);  return image; }
228     image.e = ERROR.oddfmt;
229     return image;
230 }
231 
232 /// Reads an image from buf.
233 IFImage read_image(in ubyte[] buf, in int c = 0, in int bpc = 8)
234 {
235     IFImage image;
236     Reader rc;
237     Read io = { null, null, null };
238     image.e = init_reader(&rc, io, cast(ubyte[]) buf); // the cast? care is taken!
239     if (image.e) return image;
240     if (detect_png(&rc))  { image.e = read_png(&rc, image, c, bpc);  return image; }
241     if (detect_bmp(&rc))  { image.e = read_bmp(&rc, image, c, bpc);  return image; }
242     if (detect_jpeg(&rc)) { image.e = read_jpeg(&rc, image, c, bpc); return image; }
243     if (detect_tga(&rc))  { image.e = read_tga(&rc, image, c, bpc);  return image; }
244     image.e = ERROR.oddfmt;
245     return image;
246 }
247 
248 /// Returns 0 on success, else an error code. Assumes RGB order for color components
249 /// in buf, if present.  Note: The file will remain even if the write fails.
250 ubyte write_image(in char[] fname, int w, int h, in ubyte[] buf, int reqchans = 0)
251 {
252     const int fmt = fname2fmt(fname);
253     if (fmt == -1)
254         return ERROR.unsupp;
255     auto tmp = NTString(fname);
256     if (!tmp.ptr)
257         return ERROR.oom;
258     FILE* f = fopen(tmp.ptr, "wb");
259     tmp.drop();
260     if (!f)
261         return ERROR.fopen;
262     ubyte e = write_image(fmt, f, w, h, buf, reqchans);
263     fclose(f);
264     return e;
265 }
266 
267 enum IF_BMP = 0;    /// the BMP format
268 enum IF_TGA = 1;    /// the TGA format
269 enum IF_PNG = 2;    /// the PNG format
270 enum IF_JPG = 3;    /// the JPEG format
271 
272 /// Writes to f which must already be open. Does not close it afterwards. Returns 0
273 /// on success, else an error code. Assumes RGB order for color components in buf, if
274 /// present. Note: The file will remain even if the write fails.
275 ubyte write_image(int fmt, FILE* f, int w, int h, in ubyte[] buf, int reqchans = 0)
276 {
277     Write io = { cast(void*) f, &filewrite, &fileflush };
278     return write_image(fmt, io, w, h, buf, reqchans);
279 }
280 
281 /// Returns 0 on success, else an error code. Assumes RGB order for color components
282 /// in buf, if present.
283 ubyte write_image(int fmt, Write io, int w, int h, in ubyte[] buf, int reqchans = 0)
284 {
285     Writer wc;
286     if (!io.stream || !io.write || !io.flush)
287         return ERROR.arg;
288     ubyte e;
289     ubyte[] iobuf = new_buffer(4096, e);                if (e) return e;
290     scope(exit) _free(iobuf.ptr);
291     e = init_writer(&wc, io, iobuf);                    if (e) return e;
292     e = _write_image(fmt, &wc, w, h, buf, reqchans);    if (e) return e;
293     e = fullflush(&wc);
294     return e;
295 }
296 
297 /// Returns null on error and the error code through e. Assumes RGB order for color
298 /// components in buf, if present.
299 ubyte[] write_image_mem(int fmt, int w, int h, in ubyte[] buf, int reqchans, out int e)
300 {
301     Writer wc;
302     Write io = { null, null, null };
303     e = init_writer(&wc, io, null);                     if (e) goto failure;
304     e = _write_image(fmt, &wc, w, h, buf, reqchans);    if (e) goto failure;
305 
306     if ((wc.cap - wc.n) * 100 / wc.cap > 20) {  // max 20% waste
307         ubyte* p = cast(ubyte*) _realloc(wc.buf, wc.n);
308         if (!p) goto failure;
309         wc.buf = p;
310     }
311 
312     return wc.buf[0..wc.n];
313 failure:
314     _free(wc.buf);
315     return null;
316 }
317 
318 /* ------------------ conversions ------------------ */
319 
320 /// Converts an 8-bit buffer to a 16-bit buffer in place.
321 /// On error, returns null and frees the original buffer.
322 ushort[] bpc8to16(ubyte[] b8)
323 {
324     ubyte* p8 = cast(ubyte*) _realloc(b8.ptr, b8.length * 2);
325     if (!p8) {
326         _free(b8.ptr);
327         return null;
328     }
329     ushort[] b16 = (cast(ushort*) p8)[0 .. b8.length];
330     for (size_t i = b8.length - 1; i < b8.length; --i)
331         b16[i] = p8[i] * 257;
332     return b16;
333 }
334 
335 /// Converts a 16-bit buffer to an 8-bit buffer in place.
336 /// On error, returns null and frees the original buffer.
337 ubyte[] bpc16to8(ushort[] b16)
338 {
339     ubyte[] b8 = (cast(ubyte*) b16.ptr)[0 .. b16.length];
340     for (size_t i = 0; i < b16.length; i++)
341         b8[i] = b16[i] >> 8;
342     ubyte* p8 = cast(ubyte*) _realloc(b16.ptr, b16.length);
343     if (!p8) {
344         _free(b16.ptr);
345         return null;
346     }
347     return p8[0 .. b8.length];
348 }
349 
350 alias conv8 = void function(in ubyte[] src, ubyte[] tgt) @nogc nothrow;
351 alias conv16 = void function(in ushort[] src, ushort[] tgt) @nogc nothrow;
352 
353 void* getconv(in int sc, in int tc, in int bpc)
354 {
355     if (sc == tc)
356         return bpc == 8 ? &copy : cast(void*) &copy16;
357     switch (16*sc + tc) with(CHANS) {
358         case 16*y    + ya   : return bpc == 8 ? &conv_y2ya      : cast(void*) &conv16_y2ya;
359         case 16*y    + rgb  : return bpc == 8 ? &conv_y2rgb     : cast(void*) &conv16_y2rgb;
360         case 16*y    + rgba : return bpc == 8 ? &conv_y2rgba    : cast(void*) &conv16_y2rgba;
361         case 16*y    + bgr  : return bpc == 8 ? &conv_y2rgb     : cast(void*) &conv16_y2rgb;     // reuse
362         case 16*y    + bgra : return bpc == 8 ? &conv_y2rgba    : cast(void*) &conv16_y2rgba;    // reuse
363         case 16*ya   + y    : return bpc == 8 ? &conv_ya2y      : cast(void*) &conv16_ya2y;
364         case 16*ya   + rgb  : return bpc == 8 ? &conv_ya2rgb    : cast(void*) &conv16_ya2rgb;
365         case 16*ya   + rgba : return bpc == 8 ? &conv_ya2rgba   : cast(void*) &conv16_ya2rgba;
366         case 16*ya   + bgr  : return bpc == 8 ? &conv_ya2rgb    : cast(void*) &conv16_ya2rgb;    // reuse
367         case 16*ya   + bgra : return bpc == 8 ? &conv_ya2rgba   : cast(void*) &conv16_ya2rgba;   // reuse
368         case 16*rgb  + y    : return bpc == 8 ? &conv_rgb2y     : cast(void*) &conv16_rgb2y;
369         case 16*rgb  + ya   : return bpc == 8 ? &conv_rgb2ya    : cast(void*) &conv16_rgb2ya;
370         case 16*rgb  + rgba : return bpc == 8 ? &conv_rgb2rgba  : cast(void*) &conv16_rgb2rgba;
371         case 16*rgb  + bgr  : return bpc == 8 ? &conv_rgb2bgr   : cast(void*) &conv16_rgb2bgr;
372         case 16*rgb  + bgra : return bpc == 8 ? &conv_rgb2bgra  : cast(void*) &conv16_rgb2bgra;
373         case 16*rgba + y    : return bpc == 8 ? &conv_rgba2y    : cast(void*) &conv16_rgba2y;
374         case 16*rgba + ya   : return bpc == 8 ? &conv_rgba2ya   : cast(void*) &conv16_rgba2ya;
375         case 16*rgba + rgb  : return bpc == 8 ? &conv_rgba2rgb  : cast(void*) &conv16_rgba2rgb;
376         case 16*rgba + bgr  : return bpc == 8 ? &conv_rgba2bgr  : cast(void*) &conv16_rgba2bgr;
377         case 16*rgba + bgra : return bpc == 8 ? &conv_rgba2bgra : cast(void*) &conv16_rgba2bgra;
378         case 16*bgr  + y    : return bpc == 8 ? &conv_bgr2y     : cast(void*) &conv16_bgr2y;
379         case 16*bgr  + ya   : return bpc == 8 ? &conv_bgr2ya    : cast(void*) &conv16_bgr2ya;
380         case 16*bgr  + rgb  : return bpc == 8 ? &conv_rgb2bgr   : cast(void*) &conv16_rgb2bgr;   // reuse
381         case 16*bgr  + rgba : return bpc == 8 ? &conv_rgb2bgra  : cast(void*) &conv16_rgb2bgra;  // reuse
382         case 16*bgra + y    : return bpc == 8 ? &conv_bgra2y    : cast(void*) &conv16_bgra2y;
383         case 16*bgra + ya   : return bpc == 8 ? &conv_bgra2ya   : cast(void*) &conv16_bgra2ya;
384         case 16*bgra + rgb  : return bpc == 8 ? &conv_rgba2bgr  : cast(void*) &conv16_rgba2bgr;  // reuse
385         case 16*bgra + rgba : return bpc == 8 ? &conv_rgba2bgra : cast(void*) &conv16_rgba2bgra; // reuse
386         default: assert(0);
387     }
388 }
389 
390 ubyte luminance(in ubyte r, in ubyte g, in ubyte b)
391 {
392     return cast(ubyte) (0.21*r + 0.64*g + 0.15*b); // arbitrary weights
393 }
394 
395 void copy(in ubyte[] src, ubyte[] tgt)
396 {
397     tgt[0..$] = src[0..$];
398 }
399 
400 void conv_y2ya(in ubyte[] src, ubyte[] tgt)
401 {
402     for (int k, t;   k < src.length;   k+=1, t+=2) {
403         tgt[t] = src[k];
404         tgt[t+1] = 255;
405     }
406 }
407 
408 void conv_y2rgb(in ubyte[] src, ubyte[] tgt)
409 {
410     for (int k, t;   k < src.length;   k+=1, t+=3)
411         tgt[t .. t+3] = src[k];
412 }
413 
414 void conv_y2rgba(in ubyte[] src, ubyte[] tgt)
415 {
416     for (int k, t;   k < src.length;   k+=1, t+=4) {
417         tgt[t .. t+3] = src[k];
418         tgt[t+3] = 255;
419     }
420 }
421 
422 void conv_ya2y(in ubyte[] src, ubyte[] tgt)
423 {
424     for (int k, t;   k < src.length;   k+=2, t+=1)
425         tgt[t] = src[k];
426 }
427 
428 void conv_ya2rgb(in ubyte[] src, ubyte[] tgt)
429 {
430     for (int k, t;   k < src.length;   k+=2, t+=3)
431         tgt[t .. t+3] = src[k];
432 }
433 
434 void conv_ya2rgba(in ubyte[] src, ubyte[] tgt)
435 {
436     for (int k, t;   k < src.length;   k+=2, t+=4) {
437         tgt[t .. t+3] = src[k];
438         tgt[t+3] = src[k+1];
439     }
440 }
441 
442 void conv_rgb2y(in ubyte[] src, ubyte[] tgt)
443 {
444     for (int k, t;   k < src.length;   k+=3, t+=1)
445         tgt[t] = luminance(src[k], src[k+1], src[k+2]);
446 }
447 
448 void conv_rgb2ya(in ubyte[] src, ubyte[] tgt)
449 {
450     for (int k, t;   k < src.length;   k+=3, t+=2) {
451         tgt[t] = luminance(src[k], src[k+1], src[k+2]);
452         tgt[t+1] = 255;
453     }
454 }
455 
456 void conv_rgb2rgba(in ubyte[] src, ubyte[] tgt)
457 {
458     for (int k, t;   k < src.length;   k+=3, t+=4) {
459         tgt[t .. t+3] = src[k .. k+3];
460         tgt[t+3] = 255;
461     }
462 }
463 
464 void conv_rgb2bgr(in ubyte[] src, ubyte[] tgt)
465 {
466     for (int k;   k < src.length;   k+=3) {
467         tgt[k  ] = src[k+2];
468         tgt[k+1] = src[k+1];
469         tgt[k+2] = src[k  ];
470     }
471 }
472 
473 void conv_rgb2bgra(in ubyte[] src, ubyte[] tgt)
474 {
475     for (int k, t;   k < src.length;   k+=3, t+=4) {
476         tgt[t  ] = src[k+2];
477         tgt[t+1] = src[k+1];
478         tgt[t+2] = src[k  ];
479         tgt[t+3] = 255;
480     }
481 }
482 
483 void conv_rgba2y(in ubyte[] src, ubyte[] tgt)
484 {
485     for (int k, t;   k < src.length;   k+=4, t+=1)
486         tgt[t] = luminance(src[k], src[k+1], src[k+2]);
487 }
488 
489 void conv_rgba2ya(in ubyte[] src, ubyte[] tgt)
490 {
491     for (int k, t;   k < src.length;   k+=4, t+=2) {
492         tgt[t] = luminance(src[k], src[k+1], src[k+2]);
493         tgt[t+1] = src[k+3];
494     }
495 }
496 
497 void conv_rgba2rgb(in ubyte[] src, ubyte[] tgt)
498 {
499     for (int k, t;   k < src.length;   k+=4, t+=3)
500         tgt[t .. t+3] = src[k .. k+3];
501 }
502 
503 void conv_rgba2bgr(in ubyte[] src, ubyte[] tgt)
504 {
505     for (int k, t;   k < src.length;   k+=4, t+=3) {
506         tgt[t  ] = src[k+2];
507         tgt[t+1] = src[k+1];
508         tgt[t+2] = src[k  ];
509     }
510 }
511 
512 void conv_rgba2bgra(in ubyte[] src, ubyte[] tgt)
513 {
514     for (int k, t;   k < src.length;   k+=4, t+=4) {
515         tgt[t  ] = src[k+2];
516         tgt[t+1] = src[k+1];
517         tgt[t+2] = src[k  ];
518         tgt[t+3] = src[k+3];
519     }
520 }
521 
522 void conv_bgr2y(in ubyte[] src, ubyte[] tgt)
523 {
524     for (int k, t;   k < src.length;   k+=3, t+=1)
525         tgt[t] = luminance(src[k+2], src[k+1], src[k+1]);
526 }
527 
528 void conv_bgr2ya(in ubyte[] src, ubyte[] tgt)
529 {
530     for (int k, t;   k < src.length;   k+=3, t+=2) {
531         tgt[t] = luminance(src[k+2], src[k+1], src[k+1]);
532         tgt[t+1] = 255;
533     }
534 }
535 
536 void conv_bgra2y(in ubyte[] src, ubyte[] tgt)
537 {
538     for (int k, t;   k < src.length;   k+=4, t+=1)
539         tgt[t] = luminance(src[k+2], src[k+1], src[k]);
540 }
541 
542 void conv_bgra2ya(in ubyte[] src, ubyte[] tgt)
543 {
544     for (int k, t;   k < src.length;   k+=4, t+=2) {
545         tgt[t] = luminance(src[k+2], src[k+1], src[k]);
546         tgt[t+1] = 255;
547     }
548 }
549 
550 /* --------------- 16-bit --------------- */
551 
552 ushort luminance16(in ushort r, in ushort g, in ushort b)
553 {
554     return cast(ushort) (0.21*r + 0.64*g + 0.15*b); // arbitrary weights
555 }
556 
557 void copy16(in ushort[] src, ushort[] tgt)
558 {
559     tgt[0..$] = src[0..$];
560 }
561 
562 void conv16_y2ya(in ushort[] src, ushort[] tgt)
563 {
564     for (int k, t;   k < src.length;   k+=1, t+=2) {
565         tgt[t] = src[k];
566         tgt[t+1] = 0xffff;
567     }
568 }
569 
570 void conv16_y2rgb(in ushort[] src, ushort[] tgt)
571 {
572     for (int k, t;   k < src.length;   k+=1, t+=3)
573         tgt[t .. t+3] = src[k];
574 }
575 
576 void conv16_y2rgba(in ushort[] src, ushort[] tgt)
577 {
578     for (int k, t;   k < src.length;   k+=1, t+=4) {
579         tgt[t .. t+3] = src[k];
580         tgt[t+3] = 0xffff;
581     }
582 }
583 
584 void conv16_ya2y(in ushort[] src, ushort[] tgt)
585 {
586     for (int k, t;   k < src.length;   k+=2, t+=1)
587         tgt[t] = src[k];
588 }
589 
590 void conv16_ya2rgb(in ushort[] src, ushort[] tgt)
591 {
592     for (int k, t;   k < src.length;   k+=2, t+=3)
593         tgt[t .. t+3] = src[k];
594 }
595 
596 void conv16_ya2rgba(in ushort[] src, ushort[] tgt)
597 {
598     for (int k, t;   k < src.length;   k+=2, t+=4) {
599         tgt[t .. t+3] = src[k];
600         tgt[t+3] = src[k+1];
601     }
602 }
603 
604 void conv16_rgb2y(in ushort[] src, ushort[] tgt)
605 {
606     for (int k, t;   k < src.length;   k+=3, t+=1)
607         tgt[t] = luminance16(src[k], src[k+1], src[k+2]);
608 }
609 
610 void conv16_rgb2ya(in ushort[] src, ushort[] tgt)
611 {
612     for (int k, t;   k < src.length;   k+=3, t+=2) {
613         tgt[t] = luminance16(src[k], src[k+1], src[k+2]);
614         tgt[t+1] = 0xffff;
615     }
616 }
617 
618 void conv16_rgb2rgba(in ushort[] src, ushort[] tgt)
619 {
620     for (int k, t;   k < src.length;   k+=3, t+=4) {
621         tgt[t .. t+3] = src[k .. k+3];
622         tgt[t+3] = 0xffff;
623     }
624 }
625 
626 void conv16_rgb2bgr(in ushort[] src, ushort[] tgt)
627 {
628     for (int k;   k < src.length;   k+=3) {
629         tgt[k  ] = src[k+2];
630         tgt[k+1] = src[k+1];
631         tgt[k+2] = src[k  ];
632     }
633 }
634 
635 void conv16_rgb2bgra(in ushort[] src, ushort[] tgt)
636 {
637     for (int k, t;   k < src.length;   k+=3, t+=4) {
638         tgt[t  ] = src[k+2];
639         tgt[t+1] = src[k+1];
640         tgt[t+2] = src[k  ];
641         tgt[t+3] = 0xffff;
642     }
643 }
644 
645 void conv16_rgba2y(in ushort[] src, ushort[] tgt)
646 {
647     for (int k, t;   k < src.length;   k+=4, t+=1)
648         tgt[t] = luminance16(src[k], src[k+1], src[k+2]);
649 }
650 
651 void conv16_rgba2ya(in ushort[] src, ushort[] tgt)
652 {
653     for (int k, t;   k < src.length;   k+=4, t+=2) {
654         tgt[t] = luminance16(src[k], src[k+1], src[k+2]);
655         tgt[t+1] = src[k+3];
656     }
657 }
658 
659 void conv16_rgba2rgb(in ushort[] src, ushort[] tgt)
660 {
661     for (int k, t;   k < src.length;   k+=4, t+=3)
662         tgt[t .. t+3] = src[k .. k+3];
663 }
664 
665 void conv16_rgba2bgr(in ushort[] src, ushort[] tgt)
666 {
667     for (int k, t;   k < src.length;   k+=4, t+=3) {
668         tgt[t  ] = src[k+2];
669         tgt[t+1] = src[k+1];
670         tgt[t+2] = src[k  ];
671     }
672 }
673 
674 void conv16_rgba2bgra(in ushort[] src, ushort[] tgt)
675 {
676     for (int k, t;   k < src.length;   k+=4, t+=4) {
677         tgt[t  ] = src[k+2];
678         tgt[t+1] = src[k+1];
679         tgt[t+2] = src[k  ];
680         tgt[t+3] = src[k+3];
681     }
682 }
683 
684 void conv16_bgr2y(in ushort[] src, ushort[] tgt)
685 {
686     for (int k, t;   k < src.length;   k+=3, t+=1)
687         tgt[t] = luminance16(src[k+2], src[k+1], src[k+1]);
688 }
689 
690 void conv16_bgr2ya(in ushort[] src, ushort[] tgt)
691 {
692     for (int k, t;   k < src.length;   k+=3, t+=2) {
693         tgt[t] = luminance16(src[k+2], src[k+1], src[k+1]);
694         tgt[t+1] = 0xffff;
695     }
696 }
697 
698 void conv16_bgra2y(in ushort[] src, ushort[] tgt)
699 {
700     for (int k, t;   k < src.length;   k+=4, t+=1)
701         tgt[t] = luminance16(src[k+2], src[k+1], src[k]);
702 }
703 
704 void conv16_bgra2ya(in ushort[] src, ushort[] tgt)
705 {
706     for (int k, t;   k < src.length;   k+=4, t+=2) {
707         tgt[t] = luminance16(src[k+2], src[k+1], src[k]);
708         tgt[t+1] = 0xffff;
709     }
710 }
711 
712 /*------------------------------*/ package: /*------------------------------*/
713 
714 ubyte _write_image(int fmt, Writer* wc, int w, int h, in ubyte[] buf, int reqchans)
715 {
716     switch (fmt) {
717         case IF_BMP: return write_bmp(wc, w, h, buf, reqchans);
718         case IF_TGA: return write_tga(wc, w, h, buf, reqchans);
719         case IF_PNG: return write_png(wc, w, h, buf, reqchans);
720 //        case IF_JPG: return write_jpg(wc, w, h, buf, reqchans);
721         default:
722             return ERROR.unsupp;
723     }
724 }
725 
726 enum CHANS { unknown, y, ya, rgb, rgba, bgr, bgra }
727 
728 struct Reader {
729 private:
730     Read    io;
731     ubyte*  buf;
732     int     cap;
733     int     a;
734     int     b;
735     int     bufpos;     // buffer's start position
736     int     iopos;      // position of the io cursor
737 public:
738     bool    fail;
739 }
740 
741 void function(Reader* rc)        fillbuf;
742 void function(Reader* rc)        reset2start;
743 void function(Reader* rc, int n) skip;
744 
745 ubyte init_reader(Reader* rc, Read io, ubyte[] buf)
746 {
747     if (buf.length < 16) {
748         if (io.stream) return ERROR.arg;
749         if (!buf.length) return ERROR.nodata;
750     }
751     rc.io = io;
752     rc.buf = buf.ptr;
753     rc.cap = cast(int) buf.length;
754     rc.a = 0;
755     rc.b = cast(int) buf.length;
756     rc.bufpos = 0;
757     rc.iopos = 0;
758     rc.fail = false;
759     if (rc.io.stream) {
760         fillbuf     = &fillbuf_io;
761         reset2start = &reset2start_io;
762         skip        = &skip_io;
763         fillbuf(rc);
764         if (rc.iopos == 0)
765             return ERROR.nodata;
766     } else {
767         fillbuf     = &fillbuf_mem;
768         reset2start = &reset2start_mem;
769         skip        = &skip_mem;
770     }
771     return 0;
772 }
773 
774 void fillbuf_mem(Reader* rc)
775 {
776     rc.a = 0;
777     rc.fail = true;
778 }
779 
780 void reset2start_mem(Reader* rc)
781 {
782     rc.a = 0;
783     rc.fail = false;
784 }
785 
786 void skip_mem(Reader* rc, int n)
787 {
788     if (n <= rc.b - rc.a) {
789         rc.a += n;
790         return;
791     }
792     rc.fail = true;
793 }
794 
795 void fillbuf_io(Reader* rc)
796 {
797     rc.a = 0;
798     if (rc.fail)
799         return;
800     int n = rc.io.read(rc.io.stream, rc.buf, rc.cap);
801     rc.bufpos = rc.iopos;
802     rc.iopos += n;
803     rc.b = n;
804     rc.fail = n == 0;
805 }
806 
807 void reset2start_io(Reader* rc)
808 {
809     rc.fail = false;
810     rc.a = 0;
811 
812     // this assumes buffer has been filled
813     const int off = -rc.iopos + rc.cap * (rc.bufpos == 0);
814 
815     if (rc.io.seek(rc.io.stream, off) == 0)
816         rc.iopos += off;
817     else
818         rc.fail = true;
819     if (rc.bufpos != 0)
820         fillbuf(rc);
821 }
822 
823 void skip_io(Reader* rc, int n)
824 {
825     if (n <= rc.b - rc.a) {
826         rc.a += n;
827         return;
828     }
829     if (rc.a < rc.b) {
830         n -= rc.b - rc.a;
831         rc.a = rc.b;
832     }
833     if (rc.io.seek(rc.io.stream, n) == 0)
834         rc.iopos += n;
835     else
836         rc.fail = true;
837 }
838 
839 // does not allow jumping backwards
840 void skipto(Reader* rc, int pos)
841 {
842     if (pos >= rc.bufpos + rc.b) {
843         skip(rc, pos - rc.bufpos + rc.a);
844         return;
845     }
846     if (pos >= rc.bufpos + rc.a) {
847         rc.a = pos - rc.bufpos;
848         return;
849     }
850     rc.fail = true;
851 }
852 
853 void read_block(Reader* rc, ubyte[] tgt)
854 {
855     int ti;
856     while (true) {
857         const size_t need = tgt.length - ti;
858         if (rc.a + need <= rc.b) {
859             tgt[ti .. $] = rc.buf[rc.a .. rc.a + need];
860             rc.a += need;
861             return;
862         }
863         if (rc.a < rc.b) {
864             const int got = rc.b - rc.a;
865             tgt[ti .. ti + got] = rc.buf[rc.a .. rc.b];
866             rc.a += got;
867             ti += got;
868         }
869         fillbuf(rc);
870     }
871 }
872 
873 // Returns a slice of fresh data in the buffer filling it
874 // first if no fresh bytes in it already.
875 ubyte[] read_slice(Reader* rc, in int maxn)
876 {
877     do {
878         if (rc.a < rc.b) {
879             const int a = rc.a;
880             const int avail = rc.b - rc.a;
881             const int take = maxn < avail ? maxn : avail;
882             rc.a += take;
883             return rc.buf[a .. a + take];
884         }
885         fillbuf(rc);
886     } while (!rc.fail);
887     return null;
888 }
889 
890 ubyte read_u8(Reader* rc)
891 {
892     if (rc.a < rc.b)
893         return rc.buf[rc.a++];
894     if (rc.b == rc.cap) {
895         fillbuf(rc);
896         return rc.buf[rc.a++];
897     }
898     rc.fail = true;
899     return 0;
900 }
901 
902 ushort read_u16le(Reader* rc)
903 {
904     ubyte a = read_u8(rc);
905     return (read_u8(rc) << 8) + a;
906 }
907 
908 ushort read_u16be(Reader* rc)
909 {
910     ubyte a = read_u8(rc);
911     return (a << 8) + read_u8(rc);
912 }
913 
914 uint read_u32le(Reader* rc)
915 {
916     ushort a = read_u16le(rc);
917     return (read_u16le(rc) << 16) + a;
918 }
919 
920 uint read_u32be(Reader* rc)
921 {
922     ushort a = read_u16be(rc);
923     return (a << 16) + read_u16be(rc);
924 }
925 
926 struct Writer {
927     Write   io;
928     ubyte*  buf;
929     int     cap;
930     int     n;
931     bool    fail;
932 }
933 
934 ubyte init_writer(Writer* wc, Write io, ubyte[] iobuf)
935 {
936     ubyte e = 0;
937     wc.io = io;
938     if (io.stream) {
939         wc.buf = iobuf.ptr;
940         wc.cap = cast(int) iobuf.length;
941     } else {
942         const int initcap = 32 * 1024;
943         wc.buf = new_buffer(initcap, e).ptr;
944         wc.cap = initcap;
945     }
946     wc.n = 0;
947     wc.fail = false;
948     return e;
949 }
950 
951 // Flushes writer's buffer and calls io.flush.
952 ubyte fullflush(Writer* wc)
953 {
954     if (!wc.io.stream)
955         return 0;
956     weakflush(wc);
957     wc.fail |= wc.io.flush(wc.io.stream) != 0;
958     return wc.fail ? ERROR.stream : 0;
959 }
960 
961 // Only flushes writer's buffer, does not call io.flush.
962 void weakflush(Writer* wc)
963 {
964     assert(wc.io.stream);
965     int c = 0;
966     while (c < wc.n) {
967         int written = wc.io.write(wc.io.stream, wc.buf[c..wc.n]);
968         c += written;
969         if (!written) {
970             wc.fail = true;
971             return;
972         }
973     }
974     wc.n = 0;
975 }
976 
977 void morespace(Writer* wc)
978 {
979     if (wc.io.stream) {
980         weakflush(wc);
981     } else if (wc.n == wc.cap) {
982         const int newcap = 2 * wc.cap;
983         ubyte* ptr = cast(ubyte*) _realloc(wc.buf, newcap);
984         if (!ptr) {
985             wc.fail = true;
986         } else {
987             wc.cap = newcap;
988             wc.buf = ptr;
989         }
990     }
991 }
992 
993 void write_u8(Writer* wc, in ubyte x)
994 {
995     if (wc.n < wc.cap) {
996         wc.buf[wc.n++] = x;
997         return;
998     }
999     morespace(wc);
1000     if (wc.n < wc.cap)
1001         wc.buf[wc.n++] = x;
1002 }
1003 
1004 void write_u16le(Writer* wc, in ushort x)
1005 {
1006     write_u8(wc, x & 0xff);
1007     write_u8(wc, x >> 8);
1008 }
1009 
1010 void write_u32le(Writer* wc, in uint x)
1011 {
1012     write_u16le(wc, x & 0xffff);
1013     write_u16le(wc, x >> 16);
1014 }
1015 
1016 void write_block(Writer* wc, in ubyte[] block)
1017 {
1018     int k = wc.cap - wc.n;
1019     int todo = cast(int) block.length;
1020     if (todo <= k) {
1021         wc.buf[wc.n .. wc.n + todo] = block[0..todo];
1022         wc.n += todo;
1023         return;
1024     }
1025     int amount = k;
1026     int bi = 0;
1027     do {
1028         wc.buf[wc.n .. wc.n + amount] = block[bi .. bi + amount];
1029         wc.n += amount;
1030         todo -= amount;
1031         if (!todo)
1032             return;
1033         bi += amount;
1034         morespace(wc);
1035         k = wc.cap - wc.n;
1036         amount = k < todo ? k : todo;
1037     } while (!wc.fail);
1038 }
1039 
1040 /* --------------- helper constructs --------------- */
1041 
1042 int findlast(in char[] s, in char c)
1043 {
1044     int i;
1045     for (i = cast(int) s.length - 1; i >= 0; i--) {
1046         if (s[i] == c)
1047             break;
1048     }
1049     return i;
1050 }
1051 
1052 int fname2fmt(in char[] fname)
1053 {
1054     int i = findlast(fname, '.');
1055     const int extlen = cast(int) fname.length - i - 1;  // exclude dot
1056     if (i < 0 || extlen < 3 || extlen > 4)
1057         return -1;
1058     char[4] extbuf;
1059     foreach (k, char c; fname[i+1 .. $])
1060         extbuf[k] = cast(char) (c >= 'A' && c <= 'Z' ? c + 'a' - 'A' : c);
1061     char[] ext = extbuf[0..extlen];
1062     switch (ext[0]) {
1063         case 't': if (ext == "tga") return IF_TGA; else return -1;
1064         case 'b': if (ext == "bmp") return IF_BMP; else return -1;
1065         case 'p': if (ext == "png") return IF_PNG; else return -1;
1066         case 'j': if (ext == "jpg" || ext == "jpeg") return IF_JPG; return -1;
1067         default: return -1;
1068     }
1069 }
1070 
1071 ubyte[] new_buffer(in size_t count, ref ubyte err)
1072 {
1073     ubyte* p = cast(ubyte*) _malloc(count);
1074     if (!p) {
1075         err = ERROR.oom;
1076         return null;
1077     }
1078     return p[0..count];
1079 }
1080 
1081 ushort[] new_buffer16(in size_t count, ref ubyte err)
1082 {
1083     ushort* p = cast(ushort*) _malloc(count * ushort.sizeof);
1084     if (!p) {
1085         err = ERROR.oom;
1086         return null;
1087     }
1088     return p[0..count];
1089 }
1090 
1091 struct NTString {
1092     const(char)* ptr;
1093     char[255]    tmp;
1094     bool         heap;
1095 
1096     @nogc nothrow:
1097 
1098     // Leaves ptr null on malloc error.
1099     this(in char[] s)
1100     {
1101         tmp[0..$] = 0;
1102         heap = false;
1103         if (!s.length) {
1104             ptr = cast(const(char*)) tmp.ptr;
1105         } else if (s[$-1] == 0) {
1106             ptr = s.ptr;
1107         } else if (s.length < tmp.length) {
1108             tmp[0 .. s.length] = s[0 .. $];
1109             ptr = cast(const(char*)) tmp.ptr;
1110         } else {
1111             ptr = cast(char*) _malloc(s.length + 1);
1112             if (!ptr)
1113                 return;
1114             heap = true;
1115             (cast(char*) ptr)[0..s.length] = s[0..$];
1116             (cast(char*) ptr)[s.length] = 0;
1117         }
1118     }
1119 
1120     void drop() {
1121         if (heap)
1122             _free(cast(void*) ptr);
1123     }
1124 }
1125 
1126 unittest {
1127     string png_path = "tests/pngsuite/";
1128     string tga_path = "tests/pngsuite-tga/";
1129     string bmp_path = "tests/pngsuite-bmp/";
1130 
1131     static files = [
1132         "basi0g08",    // PNG image data, 32 x 32, 8-bit grayscale, interlaced
1133         "basi2c08",    // PNG image data, 32 x 32, 8-bit/color RGB, interlaced
1134         "basi3p08",    // PNG image data, 32 x 32, 8-bit colormap, interlaced
1135         "basi4a08",    // PNG image data, 32 x 32, 8-bit gray+alpha, interlaced
1136         "basi6a08",    // PNG image data, 32 x 32, 8-bit/color RGBA, interlaced
1137         "basn0g08",    // PNG image data, 32 x 32, 8-bit grayscale, non-interlaced
1138         "basn2c08",    // PNG image data, 32 x 32, 8-bit/color RGB, non-interlaced
1139         "basn3p08",    // PNG image data, 32 x 32, 8-bit colormap, non-interlaced
1140         "basn4a08",    // PNG image data, 32 x 32, 8-bit gray+alpha, non-interlaced
1141         "basn6a08",    // PNG image data, 32 x 32, 8-bit/color RGBA, non-interlaced
1142     ];
1143 
1144     char[256] path;
1145 
1146     static char[] buildpath(ref char[256] path, in char[] dir, in char[] file, in char[] ext)
1147     {
1148         path[0 .. dir.length] = dir[0..$];
1149         path[dir.length .. dir.length + file.length] = file[0..$];
1150         const size_t ei = dir.length + file.length;
1151         path[ei .. ei + ext.length] = ext[0..$];
1152         return path[0 .. ei + ext.length];
1153     }
1154 
1155     foreach (file; files) {
1156         //writefln("%s", file);
1157         auto a = read_image(buildpath(path, png_path, file, ".png"), 4);
1158         auto b = read_image(buildpath(path, tga_path, file, ".tga"), 4);
1159         auto c = read_image(buildpath(path, bmp_path, file, ".bmp"), 4);
1160         scope(exit) {
1161             a.free();
1162             b.free();
1163             c.free();
1164         }
1165         assert(a.e + b.e + c.e == 0);
1166         assert(a.w == b.w && a.w == c.w);
1167         assert(a.h == b.h && a.h == c.h);
1168         assert(a.buf8.length == b.buf8.length && a.buf8.length == c.buf8.length);
1169         assert(a.buf8[0..$] == b.buf8[0..$], "png/tga");
1170         assert(a.buf8[0..$] == c.buf8[0..$], "png/bmp");
1171     }
1172 }