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