aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/ext_depends/imageformats
diff options
context:
space:
mode:
Diffstat (limited to 'src/ext_depends/imageformats')
-rw-r--r--src/ext_depends/imageformats/.travis.yml1
-rw-r--r--src/ext_depends/imageformats/LICENSE23
-rw-r--r--src/ext_depends/imageformats/README.md37
-rw-r--r--src/ext_depends/imageformats/dub.sdl7
-rw-r--r--src/ext_depends/imageformats/imageformats/bmp.d452
-rw-r--r--src/ext_depends/imageformats/imageformats/jpeg.d1038
-rw-r--r--src/ext_depends/imageformats/imageformats/package.d455
-rw-r--r--src/ext_depends/imageformats/imageformats/png.d789
-rw-r--r--src/ext_depends/imageformats/imageformats/tga.d456
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-bmp/basi0g08.bmpbin0 -> 3194 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-bmp/basi2c08.bmpbin0 -> 3194 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-bmp/basi3p08.bmpbin0 -> 3194 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-bmp/basi4a08.bmpbin0 -> 4218 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-bmp/basi6a08.bmpbin0 -> 4218 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-bmp/basn0g08.bmpbin0 -> 3194 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-bmp/basn2c08.bmpbin0 -> 3194 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-bmp/basn3p08.bmpbin0 -> 3194 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-bmp/basn4a08.bmpbin0 -> 4218 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-bmp/basn6a08.bmpbin0 -> 4218 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-tga/basi0g08.tgabin0 -> 1042 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-tga/basi2c08.tgabin0 -> 3090 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-tga/basi3p08.tgabin0 -> 3090 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-tga/basi4a08.tgabin0 -> 4114 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-tga/basi6a08.tgabin0 -> 4114 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-tga/basn0g08.tgabin0 -> 1042 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-tga/basn2c08.tgabin0 -> 3090 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-tga/basn3p08.tgabin0 -> 3090 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-tga/basn4a08.tgabin0 -> 4114 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite-tga/basn6a08.tgabin0 -> 4114 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/PngSuite.LICENSE9
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/PngSuite.README25
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/basi0g08.pngbin0 -> 254 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/basi2c08.pngbin0 -> 315 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/basi3p08.pngbin0 -> 1527 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/basi4a08.pngbin0 -> 214 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/basi6a08.pngbin0 -> 361 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/basn0g08.pngbin0 -> 138 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/basn2c08.pngbin0 -> 145 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/basn3p08.pngbin0 -> 1286 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/basn4a08.pngbin0 -> 126 bytes
-rw-r--r--src/ext_depends/imageformats/tests/pngsuite/basn6a08.pngbin0 -> 184 bytes
41 files changed, 3292 insertions, 0 deletions
diff --git a/src/ext_depends/imageformats/.travis.yml b/src/ext_depends/imageformats/.travis.yml
new file mode 100644
index 0000000..62d0c9e
--- /dev/null
+++ b/src/ext_depends/imageformats/.travis.yml
@@ -0,0 +1 @@
+language: d
diff --git a/src/ext_depends/imageformats/LICENSE b/src/ext_depends/imageformats/LICENSE
new file mode 100644
index 0000000..36b7cd9
--- /dev/null
+++ b/src/ext_depends/imageformats/LICENSE
@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/src/ext_depends/imageformats/README.md b/src/ext_depends/imageformats/README.md
new file mode 100644
index 0000000..c9c0b36
--- /dev/null
+++ b/src/ext_depends/imageformats/README.md
@@ -0,0 +1,37 @@
+# imageformats [![Build Status](https://travis-ci.org/lgvz/imageformats.svg)](https://travis-ci.org/lgvz/imageformats)
+
+- Returned image data is 8-bit except PNG can also return 16-bit.
+- Image data can be converted to Y, YA, RGB or RGBA.
+- There's a `@nogc` remake: [imagefmt](https://github.com/tjhann/imagefmt)
+
+**Decoders:**
+- PNG. 8-bit and 16-bit interlaced and paletted (+`tRNS` chunk)
+- TGA. 8-bit non-paletted
+- BMP. 8-bit uncompressed
+- JPEG. baseline
+
+**Encoders:**
+- PNG. 8-bit non-paletted non-interlaced
+- TGA. 8-bit non-paletted rle-compressed
+- BMP. 8-bit uncompressed
+
+```D
+import imageformats;
+
+void main() {
+ IFImage i0 = read_image("peruna.png");
+ IFImage i1 = read_image("peruna.png", ColFmt.YA); // convert
+ IFImage i2 = read_image("peruna.png", ColFmt.RGB);
+
+ write_image("peruna.tga", i0.w, i0.h, i0.pixels);
+ write_image("peruna.tga", i0.w, i0.h, i0.pixels, ColFmt.RGBA);
+
+ int w, h, chans;
+ read_image_info("peruna.png", w, h, chans);
+
+ // format specific functions
+ PNG_Header hdr = read_png_header("peruna.png");
+ IFImage i3 = read_jpeg("porkkana.jpg");
+ write_tga("porkkana.tga", i3.w, i3.h, i3.pixels);
+}
+```
diff --git a/src/ext_depends/imageformats/dub.sdl b/src/ext_depends/imageformats/dub.sdl
new file mode 100644
index 0000000..2814286
--- /dev/null
+++ b/src/ext_depends/imageformats/dub.sdl
@@ -0,0 +1,7 @@
+name "imageformats"
+description "Decoders for PNG, TGA, BMP, JPEG and encoders for PNG, TGA, BMP."
+authors "Tero Hänninen"
+license "BSL-1.0"
+targetName "imageformats"
+sourcePaths "imageformats"
+importPaths "."
diff --git a/src/ext_depends/imageformats/imageformats/bmp.d b/src/ext_depends/imageformats/imageformats/bmp.d
new file mode 100644
index 0000000..ace042a
--- /dev/null
+++ b/src/ext_depends/imageformats/imageformats/bmp.d
@@ -0,0 +1,452 @@
+module imageformats.bmp;
+
+import std.bitmanip : littleEndianToNative, nativeToLittleEndian;
+import std.stdio : File, SEEK_SET;
+import std.math : abs;
+import std.typecons : scoped;
+import imageformats;
+
+private:
+
+immutable bmp_header = ['B', 'M'];
+
+/// Reads a BMP image. req_chans defines the format of returned image
+/// (you can use ColFmt here).
+public IFImage read_bmp(in char[] filename, long req_chans = 0) {
+ auto reader = scoped!FileReader(filename);
+ return read_bmp(reader, req_chans);
+}
+
+/// Reads an image from a buffer containing a BMP image. req_chans defines the
+/// format of returned image (you can use ColFmt here).
+public IFImage read_bmp_from_mem(in ubyte[] source, long req_chans = 0) {
+ auto reader = scoped!MemReader(source);
+ return read_bmp(reader, req_chans);
+}
+
+/// Returns the header of a BMP file.
+public BMP_Header read_bmp_header(in char[] filename) {
+ auto reader = scoped!FileReader(filename);
+ return read_bmp_header(reader);
+}
+
+/// Reads the image header from a buffer containing a BMP image.
+public BMP_Header read_bmp_header_from_mem(in ubyte[] source) {
+ auto reader = scoped!MemReader(source);
+ return read_bmp_header(reader);
+}
+
+/// Header of a BMP file.
+public struct BMP_Header {
+ uint file_size;
+ uint pixel_data_offset;
+
+ uint dib_size;
+ int width;
+ int height;
+ ushort planes;
+ int bits_pp;
+ uint dib_version;
+ DibV1 dib_v1;
+ DibV2 dib_v2;
+ uint dib_v3_alpha_mask;
+ DibV4 dib_v4;
+ DibV5 dib_v5;
+}
+
+/// Part of BMP header, not always present.
+public struct DibV1 {
+ uint compression;
+ uint idat_size;
+ uint pixels_per_meter_x;
+ uint pixels_per_meter_y;
+ uint palette_length;
+ uint important_color_count;
+}
+
+/// Part of BMP header, not always present.
+public struct DibV2 {
+ uint red_mask;
+ uint green_mask;
+ uint blue_mask;
+}
+
+/// Part of BMP header, not always present.
+public struct DibV4 {
+ uint color_space_type;
+ ubyte[36] color_space_endpoints;
+ uint gamma_red;
+ uint gamma_green;
+ uint gamma_blue;
+}
+
+/// Part of BMP header, not always present.
+public struct DibV5 {
+ uint icc_profile_data;
+ uint icc_profile_size;
+}
+
+/// Returns width, height and color format information via w, h and chans.
+public void read_bmp_info(in char[] filename, out int w, out int h, out int chans) {
+ auto reader = scoped!FileReader(filename);
+ return read_bmp_info(reader, w, h, chans);
+}
+
+/// Returns width, height and color format information via w, h and chans.
+public void read_bmp_info_from_mem(in ubyte[] source, out int w, out int h, out int chans) {
+ auto reader = scoped!MemReader(source);
+ return read_bmp_info(reader, w, h, chans);
+}
+
+/// Writes a BMP image into a file.
+public void write_bmp(in char[] file, long w, long h, in ubyte[] data, long tgt_chans = 0)
+{
+ auto writer = scoped!FileWriter(file);
+ write_bmp(writer, w, h, data, tgt_chans);
+}
+
+/// Writes a BMP image into a buffer.
+public ubyte[] write_bmp_to_mem(long w, long h, in ubyte[] data, long tgt_chans = 0) {
+ auto writer = scoped!MemWriter();
+ write_bmp(writer, w, h, data, tgt_chans);
+ return writer.result;
+}
+
+// Detects whether a BMP image is readable from stream.
+package bool detect_bmp(Reader stream) {
+ try {
+ ubyte[18] tmp = void; // bmp header + size of dib header
+ stream.readExact(tmp, tmp.length);
+ size_t ds = littleEndianToNative!uint(tmp[14..18]);
+ return (tmp[0..2] == bmp_header
+ && (ds == 12 || ds == 40 || ds == 52 || ds == 56 || ds == 108 || ds == 124));
+ } catch (Throwable) {
+ return false;
+ } finally {
+ stream.seek(0, SEEK_SET);
+ }
+}
+
+BMP_Header read_bmp_header(Reader stream) {
+ ubyte[18] tmp = void; // bmp header + size of dib header
+ stream.readExact(tmp[], tmp.length);
+
+ if (tmp[0..2] != bmp_header)
+ throw new ImageIOException("corrupt header");
+
+ uint dib_size = littleEndianToNative!uint(tmp[14..18]);
+ uint dib_version;
+ switch (dib_size) {
+ case 12: dib_version = 0; break;
+ case 40: dib_version = 1; break;
+ case 52: dib_version = 2; break;
+ case 56: dib_version = 3; break;
+ case 108: dib_version = 4; break;
+ case 124: dib_version = 5; break;
+ default: throw new ImageIOException("unsupported dib version");
+ }
+ auto dib_header = new ubyte[dib_size-4];
+ stream.readExact(dib_header[], dib_header.length);
+
+ DibV1 dib_v1;
+ DibV2 dib_v2;
+ uint dib_v3_alpha_mask;
+ DibV4 dib_v4;
+ DibV5 dib_v5;
+
+ if (1 <= dib_version) {
+ DibV1 v1 = {
+ compression : littleEndianToNative!uint(dib_header[12..16]),
+ idat_size : littleEndianToNative!uint(dib_header[16..20]),
+ pixels_per_meter_x : littleEndianToNative!uint(dib_header[20..24]),
+ pixels_per_meter_y : littleEndianToNative!uint(dib_header[24..28]),
+ palette_length : littleEndianToNative!uint(dib_header[28..32]),
+ important_color_count : littleEndianToNative!uint(dib_header[32..36]),
+ };
+ dib_v1 = v1;
+ }
+
+ if (2 <= dib_version) {
+ DibV2 v2 = {
+ red_mask : littleEndianToNative!uint(dib_header[36..40]),
+ green_mask : littleEndianToNative!uint(dib_header[40..44]),
+ blue_mask : littleEndianToNative!uint(dib_header[44..48]),
+ };
+ dib_v2 = v2;
+ }
+
+ if (3 <= dib_version) {
+ dib_v3_alpha_mask = littleEndianToNative!uint(dib_header[48..52]);
+ }
+
+ if (4 <= dib_version) {
+ DibV4 v4 = {
+ color_space_type : littleEndianToNative!uint(dib_header[52..56]),
+ color_space_endpoints : dib_header[56..92],
+ gamma_red : littleEndianToNative!uint(dib_header[92..96]),
+ gamma_green : littleEndianToNative!uint(dib_header[96..100]),
+ gamma_blue : littleEndianToNative!uint(dib_header[100..104]),
+ };
+ dib_v4 = v4;
+ }
+
+ if (5 <= dib_version) {
+ DibV5 v5 = {
+ icc_profile_data : littleEndianToNative!uint(dib_header[108..112]),
+ icc_profile_size : littleEndianToNative!uint(dib_header[112..116]),
+ };
+ dib_v5 = v5;
+ }
+
+ int width, height; ushort planes; int bits_pp;
+ if (0 == dib_version) {
+ width = littleEndianToNative!ushort(dib_header[0..2]);
+ height = littleEndianToNative!ushort(dib_header[2..4]);
+ planes = littleEndianToNative!ushort(dib_header[4..6]);
+ bits_pp = littleEndianToNative!ushort(dib_header[6..8]);
+ } else {
+ width = littleEndianToNative!int(dib_header[0..4]);
+ height = littleEndianToNative!int(dib_header[4..8]);
+ planes = littleEndianToNative!ushort(dib_header[8..10]);
+ bits_pp = littleEndianToNative!ushort(dib_header[10..12]);
+ }
+
+ BMP_Header header = {
+ file_size : littleEndianToNative!uint(tmp[2..6]),
+ pixel_data_offset : littleEndianToNative!uint(tmp[10..14]),
+ width : width,
+ height : height,
+ planes : planes,
+ bits_pp : bits_pp,
+ dib_version : dib_version,
+ dib_v1 : dib_v1,
+ dib_v2 : dib_v2,
+ dib_v3_alpha_mask : dib_v3_alpha_mask,
+ dib_v4 : dib_v4,
+ dib_v5 : dib_v5,
+ };
+ return header;
+}
+
+enum CMP_RGB = 0;
+enum CMP_BITS = 3;
+
+package IFImage read_bmp(Reader stream, long req_chans = 0) {
+ if (req_chans < 0 || 4 < req_chans)
+ throw new ImageIOException("unknown color format");
+
+ BMP_Header hdr = read_bmp_header(stream);
+
+ if (hdr.width < 1 || hdr.height == 0)
+ throw new ImageIOException("invalid dimensions");
+ if (hdr.pixel_data_offset < (14 + hdr.dib_size)
+ || hdr.pixel_data_offset > 0xffffff /* arbitrary */) {
+ throw new ImageIOException("invalid pixel data offset");
+ }
+ if (hdr.planes != 1)
+ throw new ImageIOException("not supported");
+
+ auto bytes_pp = 1;
+ bool paletted = true;
+ size_t palette_length = 256;
+ bool rgb_masked = false;
+ auto pe_bytes_pp = 3;
+
+ if (1 <= hdr.dib_version) {
+ if (256 < hdr.dib_v1.palette_length)
+ throw new ImageIOException("ivnalid palette length");
+ if (hdr.bits_pp <= 8 &&
+ (hdr.dib_v1.palette_length == 0 || hdr.dib_v1.compression != CMP_RGB))
+ throw new ImageIOException("unsupported format");
+ if (hdr.dib_v1.compression != CMP_RGB && hdr.dib_v1.compression != CMP_BITS)
+ throw new ImageIOException("unsupported compression");
+
+ switch (hdr.bits_pp) {
+ case 8 : bytes_pp = 1; paletted = true; break;
+ case 24 : bytes_pp = 3; paletted = false; break;
+ case 32 : bytes_pp = 4; paletted = false; break;
+ default: throw new ImageIOException("not supported");
+ }
+
+ palette_length = hdr.dib_v1.palette_length;
+ rgb_masked = hdr.dib_v1.compression == CMP_BITS;
+ pe_bytes_pp = 4;
+ }
+
+ static size_t mask_to_idx(in uint mask) {
+ switch (mask) {
+ case 0xff00_0000: return 3;
+ case 0x00ff_0000: return 2;
+ case 0x0000_ff00: return 1;
+ case 0x0000_00ff: return 0;
+ default: throw new ImageIOException("unsupported mask");
+ }
+ }
+
+ size_t redi = 2;
+ size_t greeni = 1;
+ size_t bluei = 0;
+ if (rgb_masked) {
+ if (hdr.dib_version < 2)
+ throw new ImageIOException("invalid format");
+ redi = mask_to_idx(hdr.dib_v2.red_mask);
+ greeni = mask_to_idx(hdr.dib_v2.green_mask);
+ bluei = mask_to_idx(hdr.dib_v2.blue_mask);
+ }
+
+ bool alpha_masked = false;
+ size_t alphai = 0;
+ if (bytes_pp == 4 && 3 <= hdr.dib_version && hdr.dib_v3_alpha_mask != 0) {
+ alpha_masked = true;
+ alphai = mask_to_idx(hdr.dib_v3_alpha_mask);
+ }
+
+ ubyte[] depaletted_line = null;
+ ubyte[] palette = null;
+ if (paletted) {
+ depaletted_line = new ubyte[hdr.width * pe_bytes_pp];
+ palette = new ubyte[palette_length * pe_bytes_pp];
+ stream.readExact(palette[], palette.length);
+ }
+
+ stream.seek(hdr.pixel_data_offset, SEEK_SET);
+
+ const tgt_chans = (0 < req_chans) ? req_chans
+ : (alpha_masked) ? _ColFmt.RGBA
+ : _ColFmt.RGB;
+
+ const src_fmt = (!paletted || pe_bytes_pp == 4) ? _ColFmt.BGRA : _ColFmt.BGR;
+ const LineConv!ubyte convert = get_converter!ubyte(src_fmt, tgt_chans);
+
+ const size_t src_linesize = hdr.width * bytes_pp; // without padding
+ const size_t src_pad = 3 - ((src_linesize-1) % 4);
+ const ptrdiff_t tgt_linesize = (hdr.width * cast(int) tgt_chans);
+
+ const ptrdiff_t tgt_stride = (hdr.height < 0) ? tgt_linesize : -tgt_linesize;
+ ptrdiff_t ti = (hdr.height < 0) ? 0 : (hdr.height-1) * tgt_linesize;
+
+ auto src_line = new ubyte[src_linesize + src_pad];
+ auto bgra_line_buf = (paletted) ? null : new ubyte[hdr.width * 4];
+ auto result = new ubyte[hdr.width * abs(hdr.height) * cast(int) tgt_chans];
+
+ foreach (_; 0 .. abs(hdr.height)) {
+ stream.readExact(src_line[], src_line.length);
+
+ if (paletted) {
+ const size_t ps = pe_bytes_pp;
+ size_t di = 0;
+ foreach (idx; src_line[0..src_linesize]) {
+ if (idx > palette_length)
+ throw new ImageIOException("invalid palette index");
+ size_t i = idx * ps;
+ depaletted_line[di .. di+ps] = palette[i .. i+ps];
+ if (ps == 4) {
+ depaletted_line[di+3] = 255;
+ }
+ di += ps;
+ }
+ convert(depaletted_line[], result[ti .. ti + tgt_linesize]);
+ } else {
+ for (size_t si, di; si < src_linesize; si+=bytes_pp, di+=4) {
+ bgra_line_buf[di + 0] = src_line[si + bluei];
+ bgra_line_buf[di + 1] = src_line[si + greeni];
+ bgra_line_buf[di + 2] = src_line[si + redi];
+ bgra_line_buf[di + 3] = (alpha_masked) ? src_line[si + alphai]
+ : 255;
+ }
+ convert(bgra_line_buf[], result[ti .. ti + tgt_linesize]);
+ }
+
+ ti += tgt_stride;
+ }
+
+ IFImage ret = {
+ w : hdr.width,
+ h : abs(hdr.height),
+ c : cast(ColFmt) tgt_chans,
+ pixels : result,
+ };
+ return ret;
+}
+
+package void read_bmp_info(Reader stream, out int w, out int h, out int chans) {
+ BMP_Header hdr = read_bmp_header(stream);
+ w = abs(hdr.width);
+ h = abs(hdr.height);
+ chans = (hdr.dib_version >= 3 && hdr.dib_v3_alpha_mask != 0 && hdr.bits_pp == 32)
+ ? ColFmt.RGBA
+ : ColFmt.RGB;
+}
+
+// ----------------------------------------------------------------------
+// BMP encoder
+
+// Writes RGB or RGBA data.
+void write_bmp(Writer stream, long w, long h, in ubyte[] data, long tgt_chans = 0) {
+ if (w < 1 || h < 1 || 0x7fff < w || 0x7fff < h)
+ throw new ImageIOException("invalid dimensions");
+ size_t src_chans = data.length / cast(size_t) w / cast(size_t) h;
+ if (src_chans < 1 || 4 < src_chans)
+ throw new ImageIOException("invalid channel count");
+ if (tgt_chans != 0 && tgt_chans != 3 && tgt_chans != 4)
+ throw new ImageIOException("unsupported format for writing");
+ if (src_chans * w * h != data.length)
+ throw new ImageIOException("mismatching dimensions and length");
+
+ if (tgt_chans == 0)
+ tgt_chans = (src_chans == 1 || src_chans == 3) ? 3 : 4;
+
+ const dib_size = 108;
+ const size_t tgt_linesize = cast(size_t) (w * tgt_chans);
+ const size_t pad = 3 - ((tgt_linesize-1) & 3);
+ const size_t idat_offset = 14 + dib_size; // bmp file header + dib header
+ const size_t filesize = idat_offset + cast(size_t) h * (tgt_linesize + pad);
+ if (filesize > 0xffff_ffff) {
+ throw new ImageIOException("image too large");
+ }
+
+ ubyte[14+dib_size] hdr;
+ hdr[0] = 0x42;
+ hdr[1] = 0x4d;
+ hdr[2..6] = nativeToLittleEndian(cast(uint) filesize);
+ hdr[6..10] = 0; // reserved
+ hdr[10..14] = nativeToLittleEndian(cast(uint) idat_offset); // offset of pixel data
+ hdr[14..18] = nativeToLittleEndian(cast(uint) dib_size); // dib header size
+ hdr[18..22] = nativeToLittleEndian(cast(int) w);
+ hdr[22..26] = nativeToLittleEndian(cast(int) h); // positive -> bottom-up
+ hdr[26..28] = nativeToLittleEndian(cast(ushort) 1); // planes
+ hdr[28..30] = nativeToLittleEndian(cast(ushort) (tgt_chans * 8)); // bits per pixel
+ hdr[30..34] = nativeToLittleEndian((tgt_chans == 3) ? CMP_RGB : CMP_BITS);
+ hdr[34..54] = 0; // rest of dib v1
+ if (tgt_chans == 3) {
+ hdr[54..70] = 0; // dib v2 and v3
+ } else {
+ static immutable ubyte[16] b = [
+ 0, 0, 0xff, 0,
+ 0, 0xff, 0, 0,
+ 0xff, 0, 0, 0,
+ 0, 0, 0, 0xff
+ ];
+ hdr[54..70] = b;
+ }
+ static immutable ubyte[4] BGRs = ['B', 'G', 'R', 's'];
+ hdr[70..74] = BGRs;
+ hdr[74..122] = 0;
+ stream.rawWrite(hdr);
+
+ const LineConv!ubyte convert =
+ get_converter!ubyte(src_chans, (tgt_chans == 3) ? _ColFmt.BGR
+ : _ColFmt.BGRA);
+
+ auto tgt_line = new ubyte[tgt_linesize + pad];
+ const size_t src_linesize = cast(size_t) w * src_chans;
+ size_t si = cast(size_t) h * src_linesize;
+
+ foreach (_; 0..h) {
+ si -= src_linesize;
+ convert(data[si .. si + src_linesize], tgt_line[0..tgt_linesize]);
+ stream.rawWrite(tgt_line);
+ }
+
+ stream.flush();
+}
diff --git a/src/ext_depends/imageformats/imageformats/jpeg.d b/src/ext_depends/imageformats/imageformats/jpeg.d
new file mode 100644
index 0000000..6481d77
--- /dev/null
+++ b/src/ext_depends/imageformats/imageformats/jpeg.d
@@ -0,0 +1,1038 @@
+// Baseline JPEG decoder
+
+module imageformats.jpeg;
+
+import std.math : ceil;
+import std.bitmanip : bigEndianToNative;
+import std.stdio : File, SEEK_SET, SEEK_CUR;
+import std.typecons : scoped;
+import imageformats;
+
+private:
+
+/// Reads a JPEG image. req_chans defines the format of returned image
+/// (you can use ColFmt here).
+public IFImage read_jpeg(in char[] filename, long req_chans = 0) {
+ auto reader = scoped!FileReader(filename);
+ return read_jpeg(reader, req_chans);
+}
+
+/// Reads an image from a buffer containing a JPEG image. req_chans defines the
+/// format of returned image (you can use ColFmt here).
+public IFImage read_jpeg_from_mem(in ubyte[] source, long req_chans = 0) {
+ auto reader = scoped!MemReader(source);
+ return read_jpeg(reader, req_chans);
+}
+
+/// Returns width, height and color format information via w, h and chans.
+public void read_jpeg_info(in char[] filename, out int w, out int h, out int chans) {
+ auto reader = scoped!FileReader(filename);
+ return read_jpeg_info(reader, w, h, chans);
+}
+
+/// Returns width, height and color format information via w, h and chans.
+public void read_jpeg_info_from_mem(in ubyte[] source, out int w, out int h, out int chans) {
+ auto reader = scoped!MemReader(source);
+ return read_jpeg_info(reader, w, h, chans);
+}
+
+// Detects whether a JPEG image is readable from stream.
+package bool detect_jpeg(Reader stream) {
+ try {
+ int w, h, c;
+ read_jpeg_info(stream, w, h, c);
+ return true;
+ } catch (Throwable) {
+ return false;
+ } finally {
+ stream.seek(0, SEEK_SET);
+ }
+}
+
+package void read_jpeg_info(Reader stream, out int w, out int h, out int chans) {
+ ubyte[2] marker = void;
+ stream.readExact(marker, 2);
+
+ // SOI
+ if (marker[0] != 0xff || marker[1] != Marker.SOI)
+ throw new ImageIOException("not JPEG");
+
+ while (true) {
+ stream.readExact(marker, 2);
+
+ if (marker[0] != 0xff)
+ throw new ImageIOException("no frame header");
+ while (marker[1] == 0xff)
+ stream.readExact(marker[1..$], 1);
+
+ switch (marker[1]) with (Marker) {
+ case SOF0: .. case SOF3:
+ case SOF9: .. case SOF11:
+ ubyte[8] tmp;
+ stream.readExact(tmp[0..8], 8);
+ //int len = bigEndianToNative!ushort(tmp[0..2]);
+ w = bigEndianToNative!ushort(tmp[5..7]);
+ h = bigEndianToNative!ushort(tmp[3..5]);
+ chans = tmp[7];
+ return;
+ case SOS, EOI: throw new ImageIOException("no frame header");
+ case DRI, DHT, DQT, COM:
+ case APP0: .. case APPf:
+ ubyte[2] lenbuf = void;
+ stream.readExact(lenbuf, 2);
+ int skiplen = bigEndianToNative!ushort(lenbuf) - 2;
+ stream.seek(skiplen, SEEK_CUR);
+ break;
+ default: throw new ImageIOException("unsupported marker");
+ }
+ }
+ assert(0);
+}
+
+package IFImage read_jpeg(Reader stream, long req_chans = 0) {
+ if (req_chans < 0 || 4 < req_chans)
+ throw new ImageIOException("come on...");
+
+ // SOI
+ ubyte[2] tmp = void;
+ stream.readExact(tmp, tmp.length);
+ if (tmp[0] != 0xff || tmp[1] != Marker.SOI)
+ throw new ImageIOException("not JPEG");
+
+ JPEG_Decoder dc = { stream: stream };
+
+ read_markers(dc); // reads until first scan header or eoi
+ if (dc.eoi_reached)
+ throw new ImageIOException("no image data");
+
+ dc.tgt_chans = (req_chans == 0) ? dc.num_comps : cast(int) req_chans;
+
+ foreach (ref comp; dc.comps[0..dc.num_comps])
+ comp.data = new ubyte[dc.num_mcu_x*comp.sfx*8*dc.num_mcu_y*comp.sfy*8];
+
+ // E.7 -- Multiple scans are for progressive images which are not supported
+ //while (!dc.eoi_reached) {
+ decode_scan(dc); // E.2.3
+ //read_markers(dc); // reads until next scan header or eoi
+ //}
+
+ // throw away fill samples and convert to target format
+ ubyte[] pixels = dc.reconstruct();
+
+ IFImage result = {
+ w : dc.width,
+ h : dc.height,
+ c : cast(ColFmt) dc.tgt_chans,
+ pixels : pixels,
+ };
+ return result;
+}
+
+struct JPEG_Decoder {
+ Reader stream;
+
+ bool has_frame_header = false;
+ bool eoi_reached = false;
+
+ ubyte[64][4] qtables;
+ HuffTab[2] ac_tables;
+ HuffTab[2] dc_tables;
+
+ ubyte cb; // current byte (next bit always at MSB)
+ int bits_left; // num of unused bits in cb
+
+ bool correct_comp_ids;
+ Component[3] comps;
+ ubyte num_comps;
+ int tgt_chans;
+
+ int width, height;
+
+ int hmax, vmax;
+
+ ushort restart_interval; // number of MCUs in restart interval
+
+ // image component
+ struct Component {
+ ubyte sfx, sfy; // sampling factors, aka. h and v
+ size_t x, y; // total num of samples, without fill samples
+ ubyte qtable;
+ ubyte ac_table;
+ ubyte dc_table;
+ int pred; // dc prediction
+ ubyte[] data; // reconstructed samples
+ }
+
+ int num_mcu_x;
+ int num_mcu_y;
+}
+
+struct HuffTab {
+ ubyte[256] values;
+ ubyte[257] sizes;
+ short[16] mincode, maxcode;
+ short[16] valptr;
+}
+
+enum Marker : ubyte {
+ SOI = 0xd8, // start of image
+ SOF0 = 0xc0, // start of frame / baseline DCT
+ //SOF1 = 0xc1, // start of frame / extended seq.
+ //SOF2 = 0xc2, // start of frame / progressive DCT
+ SOF3 = 0xc3, // start of frame / lossless
+ SOF9 = 0xc9, // start of frame / extended seq., arithmetic
+ SOF11 = 0xcb, // start of frame / lossless, arithmetic
+ DHT = 0xc4, // define huffman tables
+ DQT = 0xdb, // define quantization tables
+ DRI = 0xdd, // define restart interval
+ SOS = 0xda, // start of scan
+ DNL = 0xdc, // define number of lines
+ RST0 = 0xd0, // restart entropy coded data
+ // ...
+ RST7 = 0xd7, // restart entropy coded data
+ APP0 = 0xe0, // application 0 segment
+ // ...
+ APPf = 0xef, // application f segment
+ //DAC = 0xcc, // define arithmetic conditioning table
+ COM = 0xfe, // comment
+ EOI = 0xd9, // end of image
+}
+
+void read_markers(ref JPEG_Decoder dc) {
+ bool has_next_scan_header = false;
+ while (!has_next_scan_header && !dc.eoi_reached) {
+ ubyte[2] marker;
+ dc.stream.readExact(marker, 2);
+
+ if (marker[0] != 0xff)
+ throw new ImageIOException("no marker");
+ while (marker[1] == 0xff)
+ dc.stream.readExact(marker[1..$], 1);
+
+ switch (marker[1]) with (Marker) {
+ case DHT: dc.read_huffman_tables(); break;
+ case DQT: dc.read_quantization_tables(); break;
+ case SOF0:
+ if (dc.has_frame_header)
+ throw new ImageIOException("extra frame header");
+ dc.read_frame_header();
+ dc.has_frame_header = true;
+ break;
+ case SOS:
+ if (!dc.has_frame_header)
+ throw new ImageIOException("no frame header");
+ dc.read_scan_header();
+ has_next_scan_header = true;
+ break;
+ case DRI: dc.read_restart_interval(); break;
+ case EOI: dc.eoi_reached = true; break;
+ case APP0: .. case APPf:
+ case COM:
+ ubyte[2] lenbuf = void;
+ dc.stream.readExact(lenbuf, lenbuf.length);
+ int len = bigEndianToNative!ushort(lenbuf) - 2;
+ dc.stream.seek(len, SEEK_CUR);
+ break;
+ default: throw new ImageIOException("invalid / unsupported marker");
+ }
+ }
+}
+
+// DHT -- define huffman tables
+void read_huffman_tables(ref JPEG_Decoder dc) {
+ ubyte[19] tmp = void;
+ dc.stream.readExact(tmp, 2);
+ int len = bigEndianToNative!ushort(tmp[0..2]);
+ len -= 2;
+
+ while (0 < len) {
+ dc.stream.readExact(tmp, 17); // info byte & the BITS
+ ubyte table_slot = tmp[0] & 0xf; // must be 0 or 1 for baseline
+ ubyte table_class = tmp[0] >> 4; // 0 = dc table, 1 = ac table
+ if (1 < table_slot || 1 < table_class)
+ throw new ImageIOException("invalid / not supported");
+
+ // compute total number of huffman codes
+ int mt = 0;
+ foreach (i; 1..17)
+ mt += tmp[i];
+ if (256 < mt) // TODO where in the spec?
+ throw new ImageIOException("invalid / not supported");
+
+ if (table_class == 0) {
+ dc.stream.readExact(dc.dc_tables[table_slot].values, mt);
+ derive_table(dc.dc_tables[table_slot], tmp[1..17]);
+ } else {
+ dc.stream.readExact(dc.ac_tables[table_slot].values, mt);
+ derive_table(dc.ac_tables[table_slot], tmp[1..17]);
+ }
+
+ len -= 17 + mt;
+ }
+}
+
+// num_values is the BITS
+void derive_table(ref HuffTab table, in ref ubyte[16] num_values) {
+ short[256] codes;
+
+ int k = 0;
+ foreach (i; 0..16) {
+ foreach (j; 0..num_values[i]) {
+ table.sizes[k] = cast(ubyte) (i + 1);
+ ++k;
+ }
+ }
+ table.sizes[k] = 0;
+
+ k = 0;
+ short code = 0;
+ ubyte si = table.sizes[k];
+ while (true) {
+ do {
+ codes[k] = code;
+ ++code;
+ ++k;
+ } while (si == table.sizes[k]);
+
+ if (table.sizes[k] == 0)
+ break;
+
+// assert(si < table.sizes[k]);
+ do {
+ code <<= 1;
+ ++si;
+ } while (si != table.sizes[k]);
+ }
+
+ derive_mincode_maxcode_valptr(
+ table.mincode, table.maxcode, table.valptr,
+ codes, num_values
+ );
+}
+
+// F.15
+void derive_mincode_maxcode_valptr(ref short[16] mincode, ref short[16] maxcode,
+ ref short[16] valptr, in ref short[256] codes, in ref ubyte[16] num_values) pure
+{
+ mincode[] = -1;
+ maxcode[] = -1;
+ valptr[] = -1;
+
+ int j = 0;
+ foreach (i; 0..16) {
+ if (num_values[i] != 0) {
+ valptr[i] = cast(short) j;
+ mincode[i] = codes[j];
+ j += num_values[i] - 1;
+ maxcode[i] = codes[j];
+ j += 1;
+ }
+ }
+}
+
+// DQT -- define quantization tables
+void read_quantization_tables(ref JPEG_Decoder dc) {
+ ubyte[2] tmp = void;
+ dc.stream.readExact(tmp, 2);
+ int len = bigEndianToNative!ushort(tmp[0..2]);
+ if (len % 65 != 2)
+ throw new ImageIOException("invalid / not supported");
+ len -= 2;
+ while (0 < len) {
+ dc.stream.readExact(tmp, 1);
+ ubyte table_info = tmp[0];
+ ubyte table_slot = table_info & 0xf;
+ ubyte precision = table_info >> 4; // 0 = 8 bit, 1 = 16 bit
+ if (3 < table_slot || precision != 0) // only 8 bit for baseline
+ throw new ImageIOException("invalid / not supported");
+
+ dc.stream.readExact(dc.qtables[table_slot], 64);
+ len -= 1 + 64;
+ }
+}
+
+// SOF0 -- start of frame
+void read_frame_header(ref JPEG_Decoder dc) {
+ ubyte[9] tmp = void;
+ dc.stream.readExact(tmp, 8);
+ int len = bigEndianToNative!ushort(tmp[0..2]); // 8 + num_comps*3
+ ubyte precision = tmp[2];
+ dc.height = bigEndianToNative!ushort(tmp[3..5]);
+ dc.width = bigEndianToNative!ushort(tmp[5..7]);
+ dc.num_comps = tmp[7];
+
+ if ( precision != 8 ||
+ (dc.num_comps != 1 && dc.num_comps != 3) ||
+ len != 8 + dc.num_comps*3 )
+ throw new ImageIOException("invalid / not supported");
+
+ dc.hmax = 0;
+ dc.vmax = 0;
+ int mcu_du = 0; // data units in one mcu
+ dc.stream.readExact(tmp, dc.num_comps*3);
+ foreach (i; 0..dc.num_comps) {
+ ubyte ci = tmp[i*3];
+ // JFIF says ci should be i+1, but there are images where ci is i. Normalize ids
+ // so that ci == i, always. So much for standards...
+ if (i == 0) { dc.correct_comp_ids = ci == i+1; }
+ if ((dc.correct_comp_ids && ci != i+1)
+ || (!dc.correct_comp_ids && ci != i))
+ throw new ImageIOException("invalid component id");
+
+ auto comp = &dc.comps[i];
+ ubyte sampling_factors = tmp[i*3 + 1];
+ comp.sfx = sampling_factors >> 4;
+ comp.sfy = sampling_factors & 0xf;
+ comp.qtable = tmp[i*3 + 2];
+ if ( comp.sfy < 1 || 4 < comp.sfy ||
+ comp.sfx < 1 || 4 < comp.sfx ||
+ 3 < comp.qtable )
+ throw new ImageIOException("invalid / not supported");
+
+ if (dc.hmax < comp.sfx) dc.hmax = comp.sfx;
+ if (dc.vmax < comp.sfy) dc.vmax = comp.sfy;
+
+ mcu_du += comp.sfx * comp.sfy;
+ }
+ if (10 < mcu_du)
+ throw new ImageIOException("invalid / not supported");
+
+ foreach (i; 0..dc.num_comps) {
+ dc.comps[i].x = cast(size_t) ceil(dc.width * (cast(double) dc.comps[i].sfx / dc.hmax));
+ dc.comps[i].y = cast(size_t) ceil(dc.height * (cast(double) dc.comps[i].sfy / dc.vmax));
+ }
+
+ size_t mcu_w = dc.hmax * 8;
+ size_t mcu_h = dc.vmax * 8;
+ dc.num_mcu_x = cast(int) ((dc.width + mcu_w-1) / mcu_w);
+ dc.num_mcu_y = cast(int) ((dc.height + mcu_h-1) / mcu_h);
+
+ debug(DebugJPEG) {
+ writefln("\tlen: %s", len);
+ writefln("\tprecision: %s", precision);
+ writefln("\tdimensions: %s x %s", dc.width, dc.height);
+ writefln("\tnum_comps: %s", dc.num_comps);
+ writefln("\tnum_mcu_x: %s", dc.num_mcu_x);
+ writefln("\tnum_mcu_y: %s", dc.num_mcu_y);
+ }
+
+}
+
+// SOS -- start of scan
+void read_scan_header(ref JPEG_Decoder dc) {
+ ubyte[3] tmp = void;
+ dc.stream.readExact(tmp, tmp.length);
+ ushort len = bigEndianToNative!ushort(tmp[0..2]);
+ ubyte num_scan_comps = tmp[2];
+
+ if ( num_scan_comps != dc.num_comps ||
+ len != (6+num_scan_comps*2) )
+ throw new ImageIOException("invalid / not supported");
+
+ ubyte[16] buf;
+ dc.stream.readExact(buf, len-3);
+
+ foreach (i; 0..num_scan_comps) {
+ uint ci = buf[i*2] - ((dc.correct_comp_ids) ? 1 : 0);
+ if (ci >= dc.num_comps)
+ throw new ImageIOException("invalid component id");
+
+ ubyte tables = buf[i*2+1];
+ dc.comps[ci].dc_table = tables >> 4;
+ dc.comps[ci].ac_table = tables & 0xf;
+ if ( 1 < dc.comps[ci].dc_table ||
+ 1 < dc.comps[ci].ac_table )
+ throw new ImageIOException("invalid / not supported");
+ }
+
+ // ignore these
+ //ubyte spectral_start = buf[$-3];
+ //ubyte spectral_end = buf[$-2];
+ //ubyte approx = buf[$-1];
+}
+
+void read_restart_interval(ref JPEG_Decoder dc) {
+ ubyte[4] tmp = void;
+ dc.stream.readExact(tmp, tmp.length);
+ ushort len = bigEndianToNative!ushort(tmp[0..2]);
+ if (len != 4)
+ throw new ImageIOException("invalid / not supported");
+ dc.restart_interval = bigEndianToNative!ushort(tmp[2..4]);
+}
+
+// E.2.3 and E.8 and E.9
+void decode_scan(ref JPEG_Decoder dc) {
+ int intervals, mcus;
+ if (0 < dc.restart_interval) {
+ int total_mcus = dc.num_mcu_x * dc.num_mcu_y;
+ intervals = (total_mcus + dc.restart_interval-1) / dc.restart_interval;
+ mcus = dc.restart_interval;
+ } else {
+ intervals = 1;
+ mcus = dc.num_mcu_x * dc.num_mcu_y;
+ }
+
+ foreach (mcu_j; 0 .. dc.num_mcu_y) {
+ foreach (mcu_i; 0 .. dc.num_mcu_x) {
+
+ // decode mcu
+ foreach (c; 0..dc.num_comps) {
+ auto comp = &dc.comps[c];
+ foreach (du_j; 0 .. comp.sfy) {
+ foreach (du_i; 0 .. comp.sfx) {
+ // decode entropy, dequantize & dezigzag
+ short[64] data = decode_block(dc, *comp, dc.qtables[comp.qtable]);
+ // idct & level-shift
+ int outx = (mcu_i * comp.sfx + du_i) * 8;
+ int outy = (mcu_j * comp.sfy + du_j) * 8;
+ int dst_stride = dc.num_mcu_x * comp.sfx*8;
+ ubyte* dst = comp.data.ptr + outy*dst_stride + outx;
+ stbi__idct_block(dst, dst_stride, data);
+ }
+ }
+ }
+
+ --mcus;
+
+ if (!mcus) {
+ --intervals;
+ if (!intervals)
+ return;
+
+ read_restart(dc.stream); // RSTx marker
+
+ if (intervals == 1) {
+ // last interval, may have fewer MCUs than defined by DRI
+ mcus = (dc.num_mcu_y - mcu_j - 1)
+ * dc.num_mcu_x + dc.num_mcu_x - mcu_i - 1;
+ } else {
+ mcus = dc.restart_interval;
+ }
+
+ // reset decoder
+ dc.cb = 0;
+ dc.bits_left = 0;
+ foreach (k; 0..dc.num_comps)
+ dc.comps[k].pred = 0;
+ }
+
+ }
+ }
+}
+
+// RST0-RST7
+void read_restart(Reader stream) {
+ ubyte[2] tmp = void;
+ stream.readExact(tmp, tmp.length);
+ if (tmp[0] != 0xff || tmp[1] < Marker.RST0 || Marker.RST7 < tmp[1])
+ throw new ImageIOException("reset marker missing");
+ // the markers should cycle 0 through 7, could check that here...
+}
+
+immutable ubyte[64] dezigzag = [
+ 0, 1, 8, 16, 9, 2, 3, 10,
+ 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63,
+];
+
+// decode entropy, dequantize & dezigzag (see section F.2)
+short[64] decode_block(ref JPEG_Decoder dc, ref JPEG_Decoder.Component comp,
+ in ref ubyte[64] qtable)
+{
+ short[64] res = 0;
+
+ ubyte t = decode_huff(dc, dc.dc_tables[comp.dc_table]);
+ int diff = t ? dc.receive_and_extend(t) : 0;
+
+ comp.pred = comp.pred + diff;
+ res[0] = cast(short) (comp.pred * qtable[0]);
+
+ int k = 1;
+ do {
+ ubyte rs = decode_huff(dc, dc.ac_tables[comp.ac_table]);
+ ubyte rrrr = rs >> 4;
+ ubyte ssss = rs & 0xf;
+
+ if (ssss == 0) {
+ if (rrrr != 0xf)
+ break; // end of block
+ k += 16; // run length is 16
+ continue;
+ }
+
+ k += rrrr;
+
+ if (63 < k)
+ throw new ImageIOException("corrupt block");
+ res[dezigzag[k]] = cast(short) (dc.receive_and_extend(ssss) * qtable[k]);
+ k += 1;
+ } while (k < 64);
+
+ return res;
+}
+
+int receive_and_extend(ref JPEG_Decoder dc, ubyte s) {
+ // receive
+ int symbol = 0;
+ foreach (_; 0..s)
+ symbol = (symbol << 1) + nextbit(dc);
+ // extend
+ int vt = 1 << (s-1);
+ if (symbol < vt)
+ return symbol + (-1 << s) + 1;
+ return symbol;
+}
+
+// F.16 -- the DECODE
+ubyte decode_huff(ref JPEG_Decoder dc, in ref HuffTab tab) {
+ short code = nextbit(dc);
+
+ int i = 0;
+ while (tab.maxcode[i] < code) {
+ code = cast(short) ((code << 1) + nextbit(dc));
+ i += 1;
+ if (tab.maxcode.length <= i)
+ throw new ImageIOException("corrupt huffman coding");
+ }
+ int j = tab.valptr[i] + code - tab.mincode[i];
+ if (tab.values.length <= cast(uint) j)
+ throw new ImageIOException("corrupt huffman coding");
+ return tab.values[j];
+}
+
+// F.2.2.5 and F.18
+ubyte nextbit(ref JPEG_Decoder dc) {
+ if (!dc.bits_left) {
+ ubyte[1] bytebuf;
+ dc.stream.readExact(bytebuf, 1);
+ dc.cb = bytebuf[0];
+ dc.bits_left = 8;
+
+ if (dc.cb == 0xff) {
+ dc.stream.readExact(bytebuf, 1);
+ if (bytebuf[0] != 0x0)
+ throw new ImageIOException("unexpected marker");
+ }
+ }
+
+ ubyte r = dc.cb >> 7;
+ dc.cb <<= 1;
+ dc.bits_left -= 1;
+ return r;
+}
+
+ubyte[] reconstruct(in ref JPEG_Decoder dc) {
+ auto result = new ubyte[dc.width * dc.height * dc.tgt_chans];
+
+ switch (dc.num_comps * 10 + dc.tgt_chans) {
+ case 34, 33:
+ // Use specialized bilinear filtering functions for the frequent cases where
+ // Cb & Cr channels have half resolution.
+ if ((dc.comps[0].sfx <= 2 && dc.comps[0].sfy <= 2)
+ && (dc.comps[0].sfx + dc.comps[0].sfy >= 3)
+ && dc.comps[1].sfx == 1 && dc.comps[1].sfy == 1
+ && dc.comps[2].sfx == 1 && dc.comps[2].sfy == 1) {
+ void function(in ubyte[], in ubyte[], ubyte[]) resample;
+ switch (dc.comps[0].sfx * 10 + dc.comps[0].sfy) {
+ case 22: resample = &upsample_h2_v2; break;
+ case 21: resample = &upsample_h2_v1; break;
+ case 12: resample = &upsample_h1_v2; break;
+ default: throw new ImageIOException("bug");
+ }
+
+ auto comp1 = new ubyte[dc.width];
+ auto comp2 = new ubyte[dc.width];
+
+ size_t s = 0;
+ size_t di = 0;
+ foreach (j; 0 .. dc.height) {
+ size_t mi = j / dc.comps[0].sfy;
+ size_t si = (mi == 0 || mi >= (dc.height-1)/dc.comps[0].sfy)
+ ? mi : mi - 1 + s * 2;
+ s = s ^ 1;
+
+ size_t cs = dc.num_mcu_x * dc.comps[1].sfx * 8;
+ size_t cl0 = mi * cs;
+ size_t cl1 = si * cs;
+ resample(dc.comps[1].data[cl0 .. cl0 + dc.comps[1].x],
+ dc.comps[1].data[cl1 .. cl1 + dc.comps[1].x],
+ comp1[]);
+ resample(dc.comps[2].data[cl0 .. cl0 + dc.comps[2].x],
+ dc.comps[2].data[cl1 .. cl1 + dc.comps[2].x],
+ comp2[]);
+
+ foreach (i; 0 .. dc.width) {
+ result[di .. di+3] = ycbcr_to_rgb(
+ dc.comps[0].data[j * dc.num_mcu_x * dc.comps[0].sfx * 8 + i],
+ comp1[i],
+ comp2[i],
+ );
+ if (dc.tgt_chans == 4)
+ result[di+3] = 255;
+ di += dc.tgt_chans;
+ }
+ }
+
+ return result;
+ }
+
+ foreach (const ref comp; dc.comps[0..dc.num_comps]) {
+ if (comp.sfx != dc.hmax || comp.sfy != dc.vmax)
+ return dc.upsample(result);
+ }
+
+ size_t si, di;
+ foreach (j; 0 .. dc.height) {
+ foreach (i; 0 .. dc.width) {
+ result[di .. di+3] = ycbcr_to_rgb(
+ dc.comps[0].data[si+i],
+ dc.comps[1].data[si+i],
+ dc.comps[2].data[si+i],
+ );
+ if (dc.tgt_chans == 4)
+ result[di+3] = 255;
+ di += dc.tgt_chans;
+ }
+ si += dc.num_mcu_x * dc.comps[0].sfx * 8;
+ }
+ return result;
+ case 32, 12, 31, 11:
+ const comp = &dc.comps[0];
+ if (comp.sfx == dc.hmax && comp.sfy == dc.vmax) {
+ size_t si, di;
+ if (dc.tgt_chans == 2) {
+ foreach (j; 0 .. dc.height) {
+ foreach (i; 0 .. dc.width) {
+ result[di++] = comp.data[si+i];
+ result[di++] = 255;
+ }
+ si += dc.num_mcu_x * comp.sfx * 8;
+ }
+ } else {
+ foreach (j; 0 .. dc.height) {
+ result[di .. di+dc.width] = comp.data[si .. si+dc.width];
+ si += dc.num_mcu_x * comp.sfx * 8;
+ di += dc.width;
+ }
+ }
+ return result;
+ } else {
+ // need to resample (haven't tested this...)
+ return dc.upsample_luma(result);
+ }
+ case 14, 13:
+ const comp = &dc.comps[0];
+ size_t si, di;
+ foreach (j; 0 .. dc.height) {
+ foreach (i; 0 .. dc.width) {
+ result[di .. di+3] = comp.data[si+i];
+ if (dc.tgt_chans == 4)
+ result[di+3] = 255;
+ di += dc.tgt_chans;
+ }
+ si += dc.num_mcu_x * comp.sfx * 8;
+ }
+ return result;
+ default: assert(0);
+ }
+}
+
+void upsample_h2_v2(in ubyte[] line0, in ubyte[] line1, ubyte[] result) {
+ ubyte mix(ubyte mm, ubyte ms, ubyte sm, ubyte ss) {
+ return cast(ubyte) (( cast(uint) mm * 3 * 3
+ + cast(uint) ms * 3 * 1
+ + cast(uint) sm * 1 * 3
+ + cast(uint) ss * 1 * 1
+ + 8) / 16);
+ }
+
+ result[0] = cast(ubyte) (( cast(uint) line0[0] * 3
+ + cast(uint) line1[0] * 1
+ + 2) / 4);
+ if (line0.length == 1) return;
+ result[1] = mix(line0[0], line0[1], line1[0], line1[1]);
+
+ size_t di = 2;
+ foreach (i; 1 .. line0.length) {
+ result[di] = mix(line0[i], line0[i-1], line1[i], line1[i-1]);
+ di += 1;
+ if (i == line0.length-1) {
+ if (di < result.length) {
+ result[di] = cast(ubyte) (( cast(uint) line0[i] * 3
+ + cast(uint) line1[i] * 1
+ + 2) / 4);
+ }
+ return;
+ }
+ result[di] = mix(line0[i], line0[i+1], line1[i], line1[i+1]);
+ di += 1;
+ }
+}
+
+void upsample_h2_v1(in ubyte[] line0, in ubyte[] _line1, ubyte[] result) {
+ result[0] = line0[0];
+ if (line0.length == 1) return;
+ result[1] = cast(ubyte) (( cast(uint) line0[0] * 3
+ + cast(uint) line0[1] * 1
+ + 2) / 4);
+ size_t di = 2;
+ foreach (i; 1 .. line0.length) {
+ result[di] = cast(ubyte) (( cast(uint) line0[i-1] * 1
+ + cast(uint) line0[i+0] * 3
+ + 2) / 4);
+ di += 1;
+ if (i == line0.length-1) {
+ if (di < result.length) result[di] = line0[i];
+ return;
+ }
+ result[di] = cast(ubyte) (( cast(uint) line0[i+0] * 3
+ + cast(uint) line0[i+1] * 1
+ + 2) / 4);
+ di += 1;
+ }
+}
+
+void upsample_h1_v2(in ubyte[] line0, in ubyte[] line1, ubyte[] result) {
+ foreach (i; 0 .. result.length) {
+ result[i] = cast(ubyte) (( cast(uint) line0[i] * 3
+ + cast(uint) line1[i] * 1
+ + 2) / 4);
+ }
+}
+
+// Nearest neighbor
+ubyte[] upsample_luma(in ref JPEG_Decoder dc, ubyte[] result) {
+ const size_t stride0 = dc.num_mcu_x * dc.comps[0].sfx * 8;
+ const y_step0 = cast(float) dc.comps[0].sfy / cast(float) dc.vmax;
+ const x_step0 = cast(float) dc.comps[0].sfx / cast(float) dc.hmax;
+
+ float y0 = y_step0 * 0.5f;
+ size_t y0i = 0;
+
+ size_t di;
+
+ foreach (j; 0 .. dc.height) {
+ float x0 = x_step0 * 0.5f;
+ size_t x0i = 0;
+ foreach (i; 0 .. dc.width) {
+ result[di] = dc.comps[0].data[y0i + x0i];
+ if (dc.tgt_chans == 2)
+ result[di+1] = 255;
+ di += dc.tgt_chans;
+ x0 += x_step0;
+ if (x0 >= 1.0f) { x0 -= 1.0f; x0i += 1; }
+ }
+ y0 += y_step0;
+ if (y0 >= 1.0f) { y0 -= 1.0f; y0i += stride0; }
+ }
+ return result;
+}
+
+// Nearest neighbor
+ubyte[] upsample(in ref JPEG_Decoder dc, ubyte[] result) {
+ const size_t stride0 = dc.num_mcu_x * dc.comps[0].sfx * 8;
+ const size_t stride1 = dc.num_mcu_x * dc.comps[1].sfx * 8;
+ const size_t stride2 = dc.num_mcu_x * dc.comps[2].sfx * 8;
+
+ const y_step0 = cast(float) dc.comps[0].sfy / cast(float) dc.vmax;
+ const y_step1 = cast(float) dc.comps[1].sfy / cast(float) dc.vmax;
+ const y_step2 = cast(float) dc.comps[2].sfy / cast(float) dc.vmax;
+ const x_step0 = cast(float) dc.comps[0].sfx / cast(float) dc.hmax;
+ const x_step1 = cast(float) dc.comps[1].sfx / cast(float) dc.hmax;
+ const x_step2 = cast(float) dc.comps[2].sfx / cast(float) dc.hmax;
+
+ float y0 = y_step0 * 0.5f;
+ float y1 = y_step1 * 0.5f;
+ float y2 = y_step2 * 0.5f;
+ size_t y0i = 0;
+ size_t y1i = 0;
+ size_t y2i = 0;
+
+ size_t di;
+
+ foreach (_j; 0 .. dc.height) {
+ float x0 = x_step0 * 0.5f;
+ float x1 = x_step1 * 0.5f;
+ float x2 = x_step2 * 0.5f;
+ size_t x0i = 0;
+ size_t x1i = 0;
+ size_t x2i = 0;
+ foreach (i; 0 .. dc.width) {
+ result[di .. di+3] = ycbcr_to_rgb(
+ dc.comps[0].data[y0i + x0i],
+ dc.comps[1].data[y1i + x1i],
+ dc.comps[2].data[y2i + x2i],
+ );
+ if (dc.tgt_chans == 4)
+ result[di+3] = 255;
+ di += dc.tgt_chans;
+ x0 += x_step0;
+ x1 += x_step1;
+ x2 += x_step2;
+ if (x0 >= 1.0) { x0 -= 1.0f; x0i += 1; }
+ if (x1 >= 1.0) { x1 -= 1.0f; x1i += 1; }
+ if (x2 >= 1.0) { x2 -= 1.0f; x2i += 1; }
+ }
+ y0 += y_step0;
+ y1 += y_step1;
+ y2 += y_step2;
+ if (y0 >= 1.0) { y0 -= 1.0f; y0i += stride0; }
+ if (y1 >= 1.0) { y1 -= 1.0f; y1i += stride1; }
+ if (y2 >= 1.0) { y2 -= 1.0f; y2i += stride2; }
+ }
+ return result;
+}
+
+ubyte[3] ycbcr_to_rgb(ubyte y, ubyte cb, ubyte cr) pure {
+ ubyte[3] rgb = void;
+ rgb[0] = clamp(y + 1.402*(cr-128));
+ rgb[1] = clamp(y - 0.34414*(cb-128) - 0.71414*(cr-128));
+ rgb[2] = clamp(y + 1.772*(cb-128));
+ return rgb;
+}
+
+ubyte clamp(float x) pure {
+ if (x < 0) return 0;
+ if (255 < x) return 255;
+ return cast(ubyte) x;
+}
+
+// ------------------------------------------------------------
+// The IDCT stuff here (to the next dashed line) is copied and adapted from
+// stb_image which is released under public domain. Many thanks to stb_image
+// author, Sean Barrett.
+// Link: https://github.com/nothings/stb/blob/master/stb_image.h
+
+pure int f2f(float x) { return cast(int) (x * 4096 + 0.5); }
+pure int fsh(int x) { return x << 12; }
+
+// from stb_image, derived from jidctint -- DCT_ISLOW
+pure void STBI__IDCT_1D(ref int t0, ref int t1, ref int t2, ref int t3,
+ ref int x0, ref int x1, ref int x2, ref int x3,
+ int s0, int s1, int s2, int s3, int s4, int s5, int s6, int s7)
+{
+ int p1,p2,p3,p4,p5;
+ //int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3;
+ p2 = s2;
+ p3 = s6;
+ p1 = (p2+p3) * f2f(0.5411961f);
+ t2 = p1 + p3 * f2f(-1.847759065f);
+ t3 = p1 + p2 * f2f( 0.765366865f);
+ p2 = s0;
+ p3 = s4;
+ t0 = fsh(p2+p3);
+ t1 = fsh(p2-p3);
+ x0 = t0+t3;
+ x3 = t0-t3;
+ x1 = t1+t2;
+ x2 = t1-t2;
+ t0 = s7;
+ t1 = s5;
+ t2 = s3;
+ t3 = s1;
+ p3 = t0+t2;
+ p4 = t1+t3;
+ p1 = t0+t3;
+ p2 = t1+t2;
+ p5 = (p3+p4)*f2f( 1.175875602f);
+ t0 = t0*f2f( 0.298631336f);
+ t1 = t1*f2f( 2.053119869f);
+ t2 = t2*f2f( 3.072711026f);
+ t3 = t3*f2f( 1.501321110f);
+ p1 = p5 + p1*f2f(-0.899976223f);
+ p2 = p5 + p2*f2f(-2.562915447f);
+ p3 = p3*f2f(-1.961570560f);
+ p4 = p4*f2f(-0.390180644f);
+ t3 += p1+p4;
+ t2 += p2+p3;
+ t1 += p2+p4;
+ t0 += p1+p3;
+}
+
+// idct and level-shift
+pure void stbi__idct_block(ubyte* dst, int dst_stride, in ref short[64] data) {
+ int i;
+ int[64] val;
+ int* v = val.ptr;
+ const(short)* d = data.ptr;
+
+ // columns
+ for (i=0; i < 8; ++i,++d, ++v) {
+ // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing
+ if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0
+ && d[40]==0 && d[48]==0 && d[56]==0) {
+ // no shortcut 0 seconds
+ // (1|2|3|4|5|6|7)==0 0 seconds
+ // all separate -0.047 seconds
+ // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds
+ int dcterm = d[0] << 2;
+ v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm;
+ } else {
+ int t0,t1,t2,t3,x0,x1,x2,x3;
+ STBI__IDCT_1D(
+ t0, t1, t2, t3,
+ x0, x1, x2, x3,
+ d[ 0], d[ 8], d[16], d[24],
+ d[32], d[40], d[48], d[56]
+ );
+ // constants scaled things up by 1<<12; let's bring them back
+ // down, but keep 2 extra bits of precision
+ x0 += 512; x1 += 512; x2 += 512; x3 += 512;
+ v[ 0] = (x0+t3) >> 10;
+ v[56] = (x0-t3) >> 10;
+ v[ 8] = (x1+t2) >> 10;
+ v[48] = (x1-t2) >> 10;
+ v[16] = (x2+t1) >> 10;
+ v[40] = (x2-t1) >> 10;
+ v[24] = (x3+t0) >> 10;
+ v[32] = (x3-t0) >> 10;
+ }
+ }
+
+ ubyte* o = dst;
+ for (i=0, v=val.ptr; i < 8; ++i,v+=8,o+=dst_stride) {
+ // no fast case since the first 1D IDCT spread components out
+ int t0,t1,t2,t3,x0,x1,x2,x3;
+ STBI__IDCT_1D(
+ t0, t1, t2, t3,
+ x0, x1, x2, x3,
+ v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]
+ );
+ // constants scaled things up by 1<<12, plus we had 1<<2 from first
+ // loop, plus horizontal and vertical each scale by sqrt(8) so together
+ // we've got an extra 1<<3, so 1<<17 total we need to remove.
+ // so we want to round that, which means adding 0.5 * 1<<17,
+ // aka 65536. Also, we'll end up with -128 to 127 that we want
+ // to encode as 0-255 by adding 128, so we'll add that before the shift
+ x0 += 65536 + (128<<17);
+ x1 += 65536 + (128<<17);
+ x2 += 65536 + (128<<17);
+ x3 += 65536 + (128<<17);
+ // tried computing the shifts into temps, or'ing the temps to see
+ // if any were out of range, but that was slower
+ o[0] = stbi__clamp((x0+t3) >> 17);
+ o[7] = stbi__clamp((x0-t3) >> 17);
+ o[1] = stbi__clamp((x1+t2) >> 17);
+ o[6] = stbi__clamp((x1-t2) >> 17);
+ o[2] = stbi__clamp((x2+t1) >> 17);
+ o[5] = stbi__clamp((x2-t1) >> 17);
+ o[3] = stbi__clamp((x3+t0) >> 17);
+ o[4] = stbi__clamp((x3-t0) >> 17);
+ }
+}
+
+// clamp to 0-255
+pure ubyte stbi__clamp(int x) {
+ if (cast(uint) x > 255) {
+ if (x < 0) return 0;
+ if (x > 255) return 255;
+ }
+ return cast(ubyte) x;
+}
+
+// ------------------------------------------------------------
diff --git a/src/ext_depends/imageformats/imageformats/package.d b/src/ext_depends/imageformats/imageformats/package.d
new file mode 100644
index 0000000..7f7414d
--- /dev/null
+++ b/src/ext_depends/imageformats/imageformats/package.d
@@ -0,0 +1,455 @@
+// Copyright (c) 2014-2018 Tero Hänninen
+// Boost Software License - Version 1.0 - August 17th, 2003
+module imageformats;
+
+import std.stdio : File, SEEK_SET, SEEK_CUR, SEEK_END;
+import std.string : toLower, lastIndexOf;
+import std.typecons : scoped;
+public import imageformats.png;
+public import imageformats.tga;
+public import imageformats.bmp;
+public import imageformats.jpeg;
+
+/// Image with 8-bit channels. Top-left corner at (0, 0).
+struct IFImage {
+ /// width
+ int w;
+ /// height
+ int h;
+ /// channels
+ ColFmt c;
+ /// buffer
+ ubyte[] pixels;
+}
+
+/// Image with 16-bit channels. Top-left corner at (0, 0).
+struct IFImage16 {
+ /// width
+ int w;
+ /// height
+ int h;
+ /// channels
+ ColFmt c;
+ /// buffer
+ ushort[] pixels;
+}
+
+/// Color format which you can pass to the read and write functions.
+enum ColFmt {
+ Y = 1, /// Gray
+ YA = 2, /// Gray + Alpha
+ RGB = 3, /// Truecolor
+ RGBA = 4, /// Truecolor + Alpha
+}
+
+/// Reads an image from file. req_chans defines the format of returned image
+/// (you can use ColFmt here).
+IFImage read_image(in char[] file, long req_chans = 0) {
+ auto reader = scoped!FileReader(file);
+ return read_image_from_reader(reader, req_chans);
+}
+
+/// Reads an image from a buffer. req_chans defines the format of returned
+/// image (you can use ColFmt here).
+IFImage read_image_from_mem(in ubyte[] source, long req_chans = 0) {
+ auto reader = scoped!MemReader(source);
+ return read_image_from_reader(reader, req_chans);
+}
+
+/// Writes an image to file. req_chans defines the format the image is saved in
+/// (you can use ColFmt here).
+void write_image(in char[] file, long w, long h, in ubyte[] data, long req_chans = 0) {
+ const char[] ext = extract_extension_lowercase(file);
+
+ void function(Writer, long, long, in ubyte[], long) write_image;
+ switch (ext) {
+ case "png": write_image = &write_png; break;
+ case "tga": write_image = &write_tga; break;
+ case "bmp": write_image = &write_bmp; break;
+ default: throw new ImageIOException("unknown image extension/type");
+ }
+ auto writer = scoped!FileWriter(file);
+ write_image(writer, w, h, data, req_chans);
+}
+
+/// Returns width, height and color format information via w, h and chans.
+/// If number of channels is unknown chans is set to zero, otherwise chans
+/// values map to those of ColFmt.
+void read_image_info(in char[] file, out int w, out int h, out int chans) {
+ auto reader = scoped!FileReader(file);
+ try {
+ return read_png_info(reader, w, h, chans);
+ } catch (Throwable) {
+ reader.seek(0, SEEK_SET);
+ }
+ try {
+ return read_jpeg_info(reader, w, h, chans);
+ } catch (Throwable) {
+ reader.seek(0, SEEK_SET);
+ }
+ try {
+ return read_bmp_info(reader, w, h, chans);
+ } catch (Throwable) {
+ reader.seek(0, SEEK_SET);
+ }
+ try {
+ return read_tga_info(reader, w, h, chans);
+ } catch (Throwable) {
+ reader.seek(0, SEEK_SET);
+ }
+ throw new ImageIOException("unknown image type");
+}
+
+/// Thrown from all the functions...
+class ImageIOException : Exception {
+ @safe pure const
+ this(string msg, string file = __FILE__, size_t line = __LINE__) {
+ super(msg, file, line);
+ }
+}
+
+private:
+
+IFImage read_image_from_reader(Reader reader, long req_chans) {
+ if (detect_png(reader)) return read_png(reader, req_chans);
+ if (detect_jpeg(reader)) return read_jpeg(reader, req_chans);
+ if (detect_bmp(reader)) return read_bmp(reader, req_chans);
+ if (detect_tga(reader)) return read_tga(reader, req_chans);
+ throw new ImageIOException("unknown image type");
+}
+
+// --------------------------------------------------------------------------------
+// Conversions
+
+package enum _ColFmt : int {
+ Unknown = 0,
+ Y = 1,
+ YA,
+ RGB,
+ RGBA,
+ BGR,
+ BGRA,
+}
+
+package alias LineConv(T) = void function(in T[] src, T[] tgt);
+
+package LineConv!T get_converter(T)(long src_chans, long tgt_chans) pure {
+ long combo(long a, long b) pure nothrow { return a*16 + b; }
+
+ if (src_chans == tgt_chans)
+ return &copy_line!T;
+
+ switch (combo(src_chans, tgt_chans)) with (_ColFmt) {
+ case combo(Y, YA) : return &Y_to_YA!T;
+ case combo(Y, RGB) : return &Y_to_RGB!T;
+ case combo(Y, RGBA) : return &Y_to_RGBA!T;
+ case combo(Y, BGR) : return &Y_to_BGR!T;
+ case combo(Y, BGRA) : return &Y_to_BGRA!T;
+ case combo(YA, Y) : return &YA_to_Y!T;
+ case combo(YA, RGB) : return &YA_to_RGB!T;
+ case combo(YA, RGBA) : return &YA_to_RGBA!T;
+ case combo(YA, BGR) : return &YA_to_BGR!T;
+ case combo(YA, BGRA) : return &YA_to_BGRA!T;
+ case combo(RGB, Y) : return &RGB_to_Y!T;
+ case combo(RGB, YA) : return &RGB_to_YA!T;
+ case combo(RGB, RGBA) : return &RGB_to_RGBA!T;
+ case combo(RGB, BGR) : return &RGB_to_BGR!T;
+ case combo(RGB, BGRA) : return &RGB_to_BGRA!T;
+ case combo(RGBA, Y) : return &RGBA_to_Y!T;
+ case combo(RGBA, YA) : return &RGBA_to_YA!T;
+ case combo(RGBA, RGB) : return &RGBA_to_RGB!T;
+ case combo(RGBA, BGR) : return &RGBA_to_BGR!T;
+ case combo(RGBA, BGRA) : return &RGBA_to_BGRA!T;
+ case combo(BGR, Y) : return &BGR_to_Y!T;
+ case combo(BGR, YA) : return &BGR_to_YA!T;
+ case combo(BGR, RGB) : return &BGR_to_RGB!T;
+ case combo(BGR, RGBA) : return &BGR_to_RGBA!T;
+ case combo(BGRA, Y) : return &BGRA_to_Y!T;
+ case combo(BGRA, YA) : return &BGRA_to_YA!T;
+ case combo(BGRA, RGB) : return &BGRA_to_RGB!T;
+ case combo(BGRA, RGBA) : return &BGRA_to_RGBA!T;
+ default : throw new ImageIOException("internal error");
+ }
+}
+
+void copy_line(T)(in T[] src, T[] tgt) pure nothrow {
+ tgt[0..$] = src[0..$];
+}
+
+T luminance(T)(T r, T g, T b) pure nothrow {
+ return cast(T) (0.21*r + 0.64*g + 0.15*b); // somewhat arbitrary weights
+}
+
+void Y_to_YA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=1, t+=2) {
+ tgt[t] = src[k];
+ tgt[t+1] = T.max;
+ }
+}
+
+alias Y_to_BGR = Y_to_RGB;
+void Y_to_RGB(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=1, t+=3)
+ tgt[t .. t+3] = src[k];
+}
+
+alias Y_to_BGRA = Y_to_RGBA;
+void Y_to_RGBA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=1, t+=4) {
+ tgt[t .. t+3] = src[k];
+ tgt[t+3] = T.max;
+ }
+}
+
+void YA_to_Y(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=2, t+=1)
+ tgt[t] = src[k];
+}
+
+alias YA_to_BGR = YA_to_RGB;
+void YA_to_RGB(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=2, t+=3)
+ tgt[t .. t+3] = src[k];
+}
+
+alias YA_to_BGRA = YA_to_RGBA;
+void YA_to_RGBA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=2, t+=4) {
+ tgt[t .. t+3] = src[k];
+ tgt[t+3] = src[k+1];
+ }
+}
+
+void RGB_to_Y(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=1)
+ tgt[t] = luminance(src[k], src[k+1], src[k+2]);
+}
+
+void RGB_to_YA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=2) {
+ tgt[t] = luminance(src[k], src[k+1], src[k+2]);
+ tgt[t+1] = T.max;
+ }
+}
+
+void RGB_to_RGBA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=4) {
+ tgt[t .. t+3] = src[k .. k+3];
+ tgt[t+3] = T.max;
+ }
+}
+
+void RGBA_to_Y(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=1)
+ tgt[t] = luminance(src[k], src[k+1], src[k+2]);
+}
+
+void RGBA_to_YA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=2) {
+ tgt[t] = luminance(src[k], src[k+1], src[k+2]);
+ tgt[t+1] = src[k+3];
+ }
+}
+
+void RGBA_to_RGB(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=3)
+ tgt[t .. t+3] = src[k .. k+3];
+}
+
+void BGR_to_Y(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=1)
+ tgt[t] = luminance(src[k+2], src[k+1], src[k+1]);
+}
+
+void BGR_to_YA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=2) {
+ tgt[t] = luminance(src[k+2], src[k+1], src[k+1]);
+ tgt[t+1] = T.max;
+ }
+}
+
+alias RGB_to_BGR = BGR_to_RGB;
+void BGR_to_RGB(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k; k < src.length; k+=3) {
+ tgt[k ] = src[k+2];
+ tgt[k+1] = src[k+1];
+ tgt[k+2] = src[k ];
+ }
+}
+
+alias RGB_to_BGRA = BGR_to_RGBA;
+void BGR_to_RGBA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=4) {
+ tgt[t ] = src[k+2];
+ tgt[t+1] = src[k+1];
+ tgt[t+2] = src[k ];
+ tgt[t+3] = T.max;
+ }
+}
+
+void BGRA_to_Y(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=1)
+ tgt[t] = luminance(src[k+2], src[k+1], src[k]);
+}
+
+void BGRA_to_YA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=2) {
+ tgt[t] = luminance(src[k+2], src[k+1], src[k]);
+ tgt[t+1] = T.max;
+ }
+}
+
+alias RGBA_to_BGR = BGRA_to_RGB;
+void BGRA_to_RGB(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=3) {
+ tgt[t ] = src[k+2];
+ tgt[t+1] = src[k+1];
+ tgt[t+2] = src[k ];
+ }
+}
+
+alias RGBA_to_BGRA = BGRA_to_RGBA;
+void BGRA_to_RGBA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=4) {
+ tgt[t ] = src[k+2];
+ tgt[t+1] = src[k+1];
+ tgt[t+2] = src[k ];
+ tgt[t+3] = src[k+3];
+ }
+}
+
+// --------------------------------------------------------------------------------
+
+package interface Reader {
+ void readExact(ubyte[], size_t);
+ void seek(ptrdiff_t, int);
+}
+
+package interface Writer {
+ void rawWrite(in ubyte[]);
+ void flush();
+}
+
+package class FileReader : Reader {
+ this(in char[] filename) {
+ this(File(filename.idup, "rb"));
+ }
+
+ this(File f) {
+ if (!f.isOpen) throw new ImageIOException("File not open");
+ this.f = f;
+ }
+
+ void readExact(ubyte[] buffer, size_t bytes) {
+ auto slice = this.f.rawRead(buffer[0..bytes]);
+ if (slice.length != bytes)
+ throw new Exception("not enough data");
+ }
+
+ void seek(ptrdiff_t offset, int origin) { this.f.seek(offset, origin); }
+
+ private File f;
+}
+
+package class MemReader : Reader {
+ this(in ubyte[] source) {
+ this.source = source;
+ }
+
+ void readExact(ubyte[] buffer, size_t bytes) {
+ if (source.length - cursor < bytes)
+ throw new Exception("not enough data");
+ buffer[0..bytes] = source[cursor .. cursor+bytes];
+ cursor += bytes;
+ }
+
+ void seek(ptrdiff_t offset, int origin) {
+ switch (origin) {
+ case SEEK_SET:
+ if (offset < 0 || source.length <= offset)
+ throw new Exception("seek error");
+ cursor = offset;
+ break;
+ case SEEK_CUR:
+ ptrdiff_t dst = cursor + offset;
+ if (dst < 0 || source.length <= dst)
+ throw new Exception("seek error");
+ cursor = dst;
+ break;
+ case SEEK_END:
+ if (0 <= offset || source.length < -offset)
+ throw new Exception("seek error");
+ cursor = cast(ptrdiff_t) source.length + offset;
+ break;
+ default: assert(0);
+ }
+ }
+
+ private const ubyte[] source;
+ private ptrdiff_t cursor;
+}
+
+package class FileWriter : Writer {
+ this(in char[] filename) {
+ this(File(filename.idup, "wb"));
+ }
+
+ this(File f) {
+ if (!f.isOpen) throw new ImageIOException("File not open");
+ this.f = f;
+ }
+
+ void rawWrite(in ubyte[] block) { this.f.rawWrite(block); }
+ void flush() { this.f.flush(); }
+
+ private File f;
+}
+
+package class MemWriter : Writer {
+ this() { }
+
+ ubyte[] result() { return buffer; }
+
+ void rawWrite(in ubyte[] block) { this.buffer ~= block; }
+ void flush() { }
+
+ private ubyte[] buffer;
+}
+
+const(char)[] extract_extension_lowercase(in char[] filename) {
+ ptrdiff_t di = filename.lastIndexOf('.');
+ return (0 < di && di+1 < filename.length) ? filename[di+1..$].toLower() : "";
+}
+
+unittest {
+ // The TGA and BMP files are not as varied in format as the PNG files, so
+ // not as well tested.
+ string png_path = "tests/pngsuite/";
+ string tga_path = "tests/pngsuite-tga/";
+ string bmp_path = "tests/pngsuite-bmp/";
+
+ auto files = [
+ "basi0g08", // PNG image data, 32 x 32, 8-bit grayscale, interlaced
+ "basi2c08", // PNG image data, 32 x 32, 8-bit/color RGB, interlaced
+ "basi3p08", // PNG image data, 32 x 32, 8-bit colormap, interlaced
+ "basi4a08", // PNG image data, 32 x 32, 8-bit gray+alpha, interlaced
+ "basi6a08", // PNG image data, 32 x 32, 8-bit/color RGBA, interlaced
+ "basn0g08", // PNG image data, 32 x 32, 8-bit grayscale, non-interlaced
+ "basn2c08", // PNG image data, 32 x 32, 8-bit/color RGB, non-interlaced
+ "basn3p08", // PNG image data, 32 x 32, 8-bit colormap, non-interlaced
+ "basn4a08", // PNG image data, 32 x 32, 8-bit gray+alpha, non-interlaced
+ "basn6a08", // PNG image data, 32 x 32, 8-bit/color RGBA, non-interlaced
+ ];
+
+ foreach (file; files) {
+ //writefln("%s", file);
+ auto a = read_image(png_path ~ file ~ ".png", ColFmt.RGBA);
+ auto b = read_image(tga_path ~ file ~ ".tga", ColFmt.RGBA);
+ auto c = read_image(bmp_path ~ file ~ ".bmp", ColFmt.RGBA);
+ assert(a.w == b.w && a.w == c.w);
+ assert(a.h == b.h && a.h == c.h);
+ assert(a.pixels.length == b.pixels.length && a.pixels.length == c.pixels.length);
+ assert(a.pixels[0..$] == b.pixels[0..$], "png/tga");
+ assert(a.pixels[0..$] == c.pixels[0..$], "png/bmp");
+ }
+}
diff --git a/src/ext_depends/imageformats/imageformats/png.d b/src/ext_depends/imageformats/imageformats/png.d
new file mode 100644
index 0000000..2fa9c56
--- /dev/null
+++ b/src/ext_depends/imageformats/imageformats/png.d
@@ -0,0 +1,789 @@
+module imageformats.png;
+
+import etc.c.zlib;
+import std.algorithm : min, reverse;
+import std.bitmanip : bigEndianToNative, nativeToBigEndian;
+import std.stdio : File, SEEK_SET;
+import std.digest.crc : CRC32, crc32Of;
+import std.typecons : scoped;
+import imageformats;
+
+private:
+
+/// Header of a PNG file.
+public struct PNG_Header {
+ int width;
+ int height;
+ ubyte bit_depth;
+ ubyte color_type;
+ ubyte compression_method;
+ ubyte filter_method;
+ ubyte interlace_method;
+}
+
+/// Returns the header of a PNG file.
+public PNG_Header read_png_header(in char[] filename) {
+ auto reader = scoped!FileReader(filename);
+ return read_png_header(reader);
+}
+
+/// Returns the header of the image in the buffer.
+public PNG_Header read_png_header_from_mem(in ubyte[] source) {
+ auto reader = scoped!MemReader(source);
+ return read_png_header(reader);
+}
+
+/// Reads an 8-bit or 16-bit PNG image and returns it as an 8-bit image.
+/// req_chans defines the format of returned image (you can use ColFmt here).
+public IFImage read_png(in char[] filename, long req_chans = 0) {
+ auto reader = scoped!FileReader(filename);
+ return read_png(reader, req_chans);
+}
+
+/// Reads an 8-bit or 16-bit PNG image from a buffer and returns it as an
+/// 8-bit image. req_chans defines the format of returned image (you can use
+/// ColFmt here).
+public IFImage read_png_from_mem(in ubyte[] source, long req_chans = 0) {
+ auto reader = scoped!MemReader(source);
+ return read_png(reader, req_chans);
+}
+
+/// Reads an 8-bit or 16-bit PNG image and returns it as a 16-bit image.
+/// req_chans defines the format of returned image (you can use ColFmt here).
+public IFImage16 read_png16(in char[] filename, long req_chans = 0) {
+ auto reader = scoped!FileReader(filename);
+ return read_png16(reader, req_chans);
+}
+
+/// Reads an 8-bit or 16-bit PNG image from a buffer and returns it as a
+/// 16-bit image. req_chans defines the format of returned image (you can use
+/// ColFmt here).
+public IFImage16 read_png16_from_mem(in ubyte[] source, long req_chans = 0) {
+ auto reader = scoped!MemReader(source);
+ return read_png16(reader, req_chans);
+}
+
+/// Writes a PNG image into a file.
+public void write_png(in char[] file, long w, long h, in ubyte[] data, long tgt_chans = 0)
+{
+ auto writer = scoped!FileWriter(file);
+ write_png(writer, w, h, data, tgt_chans);
+}
+
+/// Writes a PNG image into a buffer.
+public ubyte[] write_png_to_mem(long w, long h, in ubyte[] data, long tgt_chans = 0) {
+ auto writer = scoped!MemWriter();
+ write_png(writer, w, h, data, tgt_chans);
+ return writer.result;
+}
+
+/// Returns width, height and color format information via w, h and chans.
+public void read_png_info(in char[] filename, out int w, out int h, out int chans) {
+ auto reader = scoped!FileReader(filename);
+ return read_png_info(reader, w, h, chans);
+}
+
+/// Returns width, height and color format information via w, h and chans.
+public void read_png_info_from_mem(in ubyte[] source, out int w, out int h, out int chans) {
+ auto reader = scoped!MemReader(source);
+ return read_png_info(reader, w, h, chans);
+}
+
+// Detects whether a PNG image is readable from stream.
+package bool detect_png(Reader stream) {
+ try {
+ ubyte[8] tmp = void;
+ stream.readExact(tmp, tmp.length);
+ return (tmp[0..8] == png_file_header[0..$]);
+ } catch (Throwable) {
+ return false;
+ } finally {
+ stream.seek(0, SEEK_SET);
+ }
+}
+
+PNG_Header read_png_header(Reader stream) {
+ ubyte[33] tmp = void; // file header, IHDR len+type+data+crc
+ stream.readExact(tmp, tmp.length);
+
+ ubyte[4] crc = crc32Of(tmp[12..29]);
+ reverse(crc[]);
+ if ( tmp[0..8] != png_file_header[0..$] ||
+ tmp[8..16] != png_image_header ||
+ crc != tmp[29..33] )
+ throw new ImageIOException("corrupt header");
+
+ PNG_Header header = {
+ width : bigEndianToNative!int(tmp[16..20]),
+ height : bigEndianToNative!int(tmp[20..24]),
+ bit_depth : tmp[24],
+ color_type : tmp[25],
+ compression_method : tmp[26],
+ filter_method : tmp[27],
+ interlace_method : tmp[28],
+ };
+ return header;
+}
+
+package IFImage read_png(Reader stream, long req_chans = 0) {
+ PNG_Decoder dc = init_png_decoder(stream, req_chans, 8);
+ IFImage result = {
+ w : dc.w,
+ h : dc.h,
+ c : cast(ColFmt) dc.tgt_chans,
+ pixels : decode_png(dc).bpc8
+ };
+ return result;
+}
+
+IFImage16 read_png16(Reader stream, long req_chans = 0) {
+ PNG_Decoder dc = init_png_decoder(stream, req_chans, 16);
+ IFImage16 result = {
+ w : dc.w,
+ h : dc.h,
+ c : cast(ColFmt) dc.tgt_chans,
+ pixels : decode_png(dc).bpc16
+ };
+ return result;
+}
+
+PNG_Decoder init_png_decoder(Reader stream, long req_chans, int req_bpc) {
+ if (req_chans < 0 || 4 < req_chans)
+ throw new ImageIOException("come on...");
+
+ PNG_Header hdr = read_png_header(stream);
+
+ if (hdr.width < 1 || hdr.height < 1 || int.max < cast(ulong) hdr.width * hdr.height)
+ throw new ImageIOException("invalid dimensions");
+ if ((hdr.bit_depth != 8 && hdr.bit_depth != 16) || (req_bpc != 8 && req_bpc != 16))
+ throw new ImageIOException("only 8-bit and 16-bit images supported");
+ if (! (hdr.color_type == PNG_ColorType.Y ||
+ hdr.color_type == PNG_ColorType.RGB ||
+ hdr.color_type == PNG_ColorType.Idx ||
+ hdr.color_type == PNG_ColorType.YA ||
+ hdr.color_type == PNG_ColorType.RGBA) )
+ throw new ImageIOException("color type not supported");
+ if (hdr.compression_method != 0 || hdr.filter_method != 0 ||
+ (hdr.interlace_method != 0 && hdr.interlace_method != 1))
+ throw new ImageIOException("not supported");
+
+ PNG_Decoder dc = {
+ stream : stream,
+ src_indexed : (hdr.color_type == PNG_ColorType.Idx),
+ src_chans : channels(cast(PNG_ColorType) hdr.color_type),
+ bpc : hdr.bit_depth,
+ req_bpc : req_bpc,
+ ilace : hdr.interlace_method,
+ w : hdr.width,
+ h : hdr.height,
+ };
+ dc.tgt_chans = (req_chans == 0) ? dc.src_chans : cast(int) req_chans;
+ return dc;
+}
+
+immutable ubyte[8] png_file_header =
+ [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
+
+immutable ubyte[8] png_image_header =
+ [0x0, 0x0, 0x0, 0xd, 'I','H','D','R'];
+
+int channels(PNG_ColorType ct) pure nothrow {
+ final switch (ct) with (PNG_ColorType) {
+ case Y: return 1;
+ case RGB: return 3;
+ case YA: return 2;
+ case RGBA, Idx: return 4;
+ }
+}
+
+PNG_ColorType color_type(long channels) pure nothrow {
+ switch (channels) {
+ case 1: return PNG_ColorType.Y;
+ case 2: return PNG_ColorType.YA;
+ case 3: return PNG_ColorType.RGB;
+ case 4: return PNG_ColorType.RGBA;
+ default: assert(0);
+ }
+}
+
+struct PNG_Decoder {
+ Reader stream;
+ bool src_indexed;
+ int src_chans;
+ int tgt_chans;
+ int bpc;
+ int req_bpc;
+ int w, h;
+ ubyte ilace;
+
+ CRC32 crc;
+ ubyte[12] chunkmeta; // crc | length and type
+ ubyte[] read_buf;
+ ubyte[] palette;
+ ubyte[] transparency;
+
+ // decompression
+ z_stream* z; // zlib stream
+ uint avail_idat; // available bytes in current idat chunk
+ ubyte[] idat_window; // slice of read_buf
+}
+
+Buffer decode_png(ref PNG_Decoder dc) {
+ dc.read_buf = new ubyte[4096];
+
+ enum Stage {
+ IHDR_parsed,
+ PLTE_parsed,
+ IDAT_parsed,
+ IEND_parsed,
+ }
+
+ Buffer result;
+ auto stage = Stage.IHDR_parsed;
+ dc.stream.readExact(dc.chunkmeta[4..$], 8); // next chunk's len and type
+
+ while (stage != Stage.IEND_parsed) {
+ int len = bigEndianToNative!int(dc.chunkmeta[4..8]);
+ if (len < 0)
+ throw new ImageIOException("chunk too long");
+
+ // standard allows PLTE chunk for non-indexed images too but we don't
+ dc.crc.put(dc.chunkmeta[8..12]); // type
+ switch (cast(char[]) dc.chunkmeta[8..12]) { // chunk type
+ case "IDAT":
+ if (! (stage == Stage.IHDR_parsed ||
+ (stage == Stage.PLTE_parsed && dc.src_indexed)) )
+ throw new ImageIOException("corrupt chunk stream");
+ result = read_IDAT_stream(dc, len);
+ dc.stream.readExact(dc.chunkmeta, 12); // crc | len, type
+ ubyte[4] crc = dc.crc.finish;
+ reverse(crc[]);
+ if (crc != dc.chunkmeta[0..4])
+ throw new ImageIOException("corrupt chunk");
+ stage = Stage.IDAT_parsed;
+ break;
+ case "PLTE":
+ if (stage != Stage.IHDR_parsed)
+ throw new ImageIOException("corrupt chunk stream");
+ int entries = len / 3;
+ if (len % 3 != 0 || 256 < entries)
+ throw new ImageIOException("corrupt chunk");
+ dc.palette = new ubyte[len];
+ dc.stream.readExact(dc.palette, dc.palette.length);
+ dc.crc.put(dc.palette);
+ dc.stream.readExact(dc.chunkmeta, 12); // crc | len, type
+ ubyte[4] crc = dc.crc.finish;
+ reverse(crc[]);
+ if (crc != dc.chunkmeta[0..4])
+ throw new ImageIOException("corrupt chunk");
+ stage = Stage.PLTE_parsed;
+ break;
+ case "tRNS":
+ if (! (stage == Stage.IHDR_parsed ||
+ (stage == Stage.PLTE_parsed && dc.src_indexed)) )
+ throw new ImageIOException("corrupt chunk stream");
+ if (dc.src_indexed) {
+ size_t entries = dc.palette.length / 3;
+ if (len > entries)
+ throw new ImageIOException("corrupt chunk");
+ }
+ dc.transparency = new ubyte[len];
+ dc.stream.readExact(dc.transparency, dc.transparency.length);
+ dc.stream.readExact(dc.chunkmeta, 12);
+ dc.crc.put(dc.transparency);
+ ubyte[4] crc = dc.crc.finish;
+ reverse(crc[]);
+ if (crc != dc.chunkmeta[0..4])
+ throw new ImageIOException("corrupt chunk");
+ break;
+ case "IEND":
+ if (stage != Stage.IDAT_parsed)
+ throw new ImageIOException("corrupt chunk stream");
+ dc.stream.readExact(dc.chunkmeta, 4); // crc
+ static immutable ubyte[4] expectedCRC = [0xae, 0x42, 0x60, 0x82];
+ if (len != 0 || dc.chunkmeta[0..4] != expectedCRC)
+ throw new ImageIOException("corrupt chunk");
+ stage = Stage.IEND_parsed;
+ break;
+ case "IHDR":
+ throw new ImageIOException("corrupt chunk stream");
+ default:
+ // unknown chunk, ignore but check crc
+ while (0 < len) {
+ size_t bytes = min(len, dc.read_buf.length);
+ dc.stream.readExact(dc.read_buf, bytes);
+ len -= bytes;
+ dc.crc.put(dc.read_buf[0..bytes]);
+ }
+ dc.stream.readExact(dc.chunkmeta, 12); // crc | len, type
+ ubyte[4] crc = dc.crc.finish;
+ reverse(crc[]);
+ if (crc != dc.chunkmeta[0..4])
+ throw new ImageIOException("corrupt chunk");
+ }
+ }
+
+ return result;
+}
+
+enum PNG_ColorType : ubyte {
+ Y = 0,
+ RGB = 2,
+ Idx = 3,
+ YA = 4,
+ RGBA = 6,
+}
+
+enum PNG_FilterType : ubyte {
+ None = 0,
+ Sub = 1,
+ Up = 2,
+ Average = 3,
+ Paeth = 4,
+}
+
+enum InterlaceMethod {
+ None = 0, Adam7 = 1
+}
+
+union Buffer {
+ ubyte[] bpc8;
+ ushort[] bpc16;
+}
+
+Buffer read_IDAT_stream(ref PNG_Decoder dc, int len) {
+ assert(dc.req_bpc == 8 || dc.req_bpc == 16);
+
+ // initialize zlib stream
+ z_stream z = { zalloc: null, zfree: null, opaque: null };
+ if (inflateInit(&z) != Z_OK)
+ throw new ImageIOException("can't init zlib");
+ dc.z = &z;
+ dc.avail_idat = len;
+ scope(exit)
+ inflateEnd(&z);
+
+ const size_t filter_step = dc.src_indexed
+ ? 1 : dc.src_chans * (dc.bpc == 8 ? 1 : 2);
+
+ ubyte[] depaletted = dc.src_indexed ? new ubyte[dc.w * 4] : null;
+
+ auto cline = new ubyte[dc.w * filter_step + 1]; // +1 for filter type byte
+ auto pline = new ubyte[dc.w * filter_step + 1]; // +1 for filter type byte
+ auto cline8 = (dc.req_bpc == 8 && dc.bpc != 8) ? new ubyte[dc.w * dc.src_chans] : null;
+ auto cline16 = (dc.req_bpc == 16) ? new ushort[dc.w * dc.src_chans] : null;
+ ubyte[] result8 = (dc.req_bpc == 8) ? new ubyte[dc.w * dc.h * dc.tgt_chans] : null;
+ ushort[] result16 = (dc.req_bpc == 16) ? new ushort[dc.w * dc.h * dc.tgt_chans] : null;
+
+ const LineConv!ubyte convert8 = get_converter!ubyte(dc.src_chans, dc.tgt_chans);
+ const LineConv!ushort convert16 = get_converter!ushort(dc.src_chans, dc.tgt_chans);
+
+ if (dc.ilace == InterlaceMethod.None) {
+ const size_t src_linelen = dc.w * dc.src_chans;
+ const size_t tgt_linelen = dc.w * dc.tgt_chans;
+
+ size_t ti = 0; // target index
+ foreach (j; 0 .. dc.h) {
+ uncompress(dc, cline);
+ ubyte filter_type = cline[0];
+
+ recon(cline[1..$], pline[1..$], filter_type, filter_step);
+
+ ubyte[] bytes; // defiltered bytes or 8-bit samples from palette
+ if (dc.src_indexed) {
+ depalette(dc.palette, dc.transparency, cline[1..$], depaletted);
+ bytes = depaletted[0 .. src_linelen];
+ } else {
+ bytes = cline[1..$];
+ }
+
+ // convert colors
+ if (dc.req_bpc == 8) {
+ line8_from_bytes(bytes, dc.bpc, cline8);
+ convert8(cline8[0 .. src_linelen], result8[ti .. ti + tgt_linelen]);
+ } else {
+ line16_from_bytes(bytes, dc.bpc, cline16);
+ convert16(cline16[0 .. src_linelen], result16[ti .. ti + tgt_linelen]);
+ }
+
+ ti += tgt_linelen;
+
+ ubyte[] _swap = pline;
+ pline = cline;
+ cline = _swap;
+ }
+ } else {
+ // Adam7 interlacing
+
+ immutable size_t[7] redw = [(dc.w + 7) / 8,
+ (dc.w + 3) / 8,
+ (dc.w + 3) / 4,
+ (dc.w + 1) / 4,
+ (dc.w + 1) / 2,
+ (dc.w + 0) / 2,
+ (dc.w + 0) / 1];
+
+ immutable size_t[7] redh = [(dc.h + 7) / 8,
+ (dc.h + 7) / 8,
+ (dc.h + 3) / 8,
+ (dc.h + 3) / 4,
+ (dc.h + 1) / 4,
+ (dc.h + 1) / 2,
+ (dc.h + 0) / 2];
+
+ auto redline8 = (dc.req_bpc == 8) ? new ubyte[dc.w * dc.tgt_chans] : null;
+ auto redline16 = (dc.req_bpc == 16) ? new ushort[dc.w * dc.tgt_chans] : null;
+
+ foreach (pass; 0 .. 7) {
+ const A7_Catapult tgt_px = a7_catapults[pass]; // target pixel
+ const size_t src_linelen = redw[pass] * dc.src_chans;
+ ubyte[] cln = cline[0 .. redw[pass] * filter_step + 1];
+ ubyte[] pln = pline[0 .. redw[pass] * filter_step + 1];
+ pln[] = 0;
+
+ foreach (j; 0 .. redh[pass]) {
+ uncompress(dc, cln);
+ ubyte filter_type = cln[0];
+
+ recon(cln[1..$], pln[1..$], filter_type, filter_step);
+
+ ubyte[] bytes; // defiltered bytes or 8-bit samples from palette
+ if (dc.src_indexed) {
+ depalette(dc.palette, dc.transparency, cln[1..$], depaletted);
+ bytes = depaletted[0 .. src_linelen];
+ } else {
+ bytes = cln[1..$];
+ }
+
+ // convert colors and sling pixels from reduced image to final buffer
+ if (dc.req_bpc == 8) {
+ line8_from_bytes(bytes, dc.bpc, cline8);
+ convert8(cline8[0 .. src_linelen], redline8[0 .. redw[pass]*dc.tgt_chans]);
+ for (size_t i, redi; i < redw[pass]; ++i, redi += dc.tgt_chans) {
+ size_t tgt = tgt_px(i, j, dc.w) * dc.tgt_chans;
+ result8[tgt .. tgt + dc.tgt_chans] =
+ redline8[redi .. redi + dc.tgt_chans];
+ }
+ } else {
+ line16_from_bytes(bytes, dc.bpc, cline16);
+ convert16(cline16[0 .. src_linelen], redline16[0 .. redw[pass]*dc.tgt_chans]);
+ for (size_t i, redi; i < redw[pass]; ++i, redi += dc.tgt_chans) {
+ size_t tgt = tgt_px(i, j, dc.w) * dc.tgt_chans;
+ result16[tgt .. tgt + dc.tgt_chans] =
+ redline16[redi .. redi + dc.tgt_chans];
+ }
+ }
+
+ ubyte[] _swap = pln;
+ pln = cln;
+ cln = _swap;
+ }
+ }
+ }
+
+ Buffer result;
+ switch (dc.req_bpc) {
+ case 8: result.bpc8 = result8; return result;
+ case 16: result.bpc16 = result16; return result;
+ default: throw new ImageIOException("internal error");
+ }
+}
+
+void line8_from_bytes(ubyte[] src, int bpc, ref ubyte[] tgt) {
+ switch (bpc) {
+ case 8:
+ tgt = src;
+ break;
+ case 16:
+ for (size_t k, t; k < src.length; k+=2, t+=1) { tgt[t] = src[k]; /* truncate */ }
+ break;
+ default: throw new ImageIOException("unsupported bit depth (and bug)");
+ }
+}
+
+void line16_from_bytes(in ubyte[] src, int bpc, ushort[] tgt) {
+ switch (bpc) {
+ case 8:
+ for (size_t k; k < src.length; k+=1) { tgt[k] = src[k] * 256 + 128; }
+ break;
+ case 16:
+ for (size_t k, t; k < src.length; k+=2, t+=1) { tgt[t] = src[k] << 8 | src[k+1]; }
+ break;
+ default: throw new ImageIOException("unsupported bit depth (and bug)");
+ }
+}
+
+void depalette(in ubyte[] palette, in ubyte[] transparency, in ubyte[] src_line, ubyte[] depaletted) pure {
+ for (size_t s, d; s < src_line.length; s+=1, d+=4) {
+ ubyte pid = src_line[s];
+ size_t pidx = pid * 3;
+ if (palette.length < pidx + 3)
+ throw new ImageIOException("palette index wrong");
+ depaletted[d .. d+3] = palette[pidx .. pidx+3];
+ depaletted[d+3] = (pid < transparency.length) ? transparency[pid] : 255;
+ }
+}
+
+alias A7_Catapult = size_t function(size_t redx, size_t redy, size_t dstw);
+immutable A7_Catapult[7] a7_catapults = [
+ &a7_red1_to_dst,
+ &a7_red2_to_dst,
+ &a7_red3_to_dst,
+ &a7_red4_to_dst,
+ &a7_red5_to_dst,
+ &a7_red6_to_dst,
+ &a7_red7_to_dst,
+];
+
+pure nothrow {
+ size_t a7_red1_to_dst(size_t redx, size_t redy, size_t dstw) { return redy*8*dstw + redx*8; }
+ size_t a7_red2_to_dst(size_t redx, size_t redy, size_t dstw) { return redy*8*dstw + redx*8+4; }
+ size_t a7_red3_to_dst(size_t redx, size_t redy, size_t dstw) { return (redy*8+4)*dstw + redx*4; }
+ size_t a7_red4_to_dst(size_t redx, size_t redy, size_t dstw) { return redy*4*dstw + redx*4+2; }
+ size_t a7_red5_to_dst(size_t redx, size_t redy, size_t dstw) { return (redy*4+2)*dstw + redx*2; }
+ size_t a7_red6_to_dst(size_t redx, size_t redy, size_t dstw) { return redy*2*dstw + redx*2+1; }
+ size_t a7_red7_to_dst(size_t redx, size_t redy, size_t dstw) { return (redy*2+1)*dstw + redx; }
+}
+
+// Uncompresses a line from the IDAT stream into dst.
+void uncompress(ref PNG_Decoder dc, ubyte[] dst)
+{
+ dc.z.avail_out = cast(uint) dst.length;
+ dc.z.next_out = dst.ptr;
+
+ while (true) {
+ if (!dc.z.avail_in) {
+ if (!dc.avail_idat) {
+ dc.stream.readExact(dc.chunkmeta, 12); // crc | len & type
+ ubyte[4] crc = dc.crc.finish;
+ reverse(crc[]);
+ if (crc != dc.chunkmeta[0..4])
+ throw new ImageIOException("corrupt chunk");
+ dc.avail_idat = bigEndianToNative!uint(dc.chunkmeta[4..8]);
+ if (!dc.avail_idat)
+ throw new ImageIOException("invalid data");
+ if (dc.chunkmeta[8..12] != "IDAT")
+ throw new ImageIOException("not enough data");
+ dc.crc.put(dc.chunkmeta[8..12]);
+ }
+
+ const size_t n = min(dc.avail_idat, dc.read_buf.length);
+ dc.stream.readExact(dc.read_buf, n);
+ dc.idat_window = dc.read_buf[0..n];
+
+ if (!dc.idat_window)
+ throw new ImageIOException("TODO");
+ dc.crc.put(dc.idat_window);
+ dc.avail_idat -= cast(uint) dc.idat_window.length;
+ dc.z.avail_in = cast(uint) dc.idat_window.length;
+ dc.z.next_in = dc.idat_window.ptr;
+ }
+
+ int q = inflate(dc.z, Z_NO_FLUSH);
+
+ if (dc.z.avail_out == 0)
+ return;
+ if (q != Z_OK)
+ throw new ImageIOException("zlib error");
+ }
+}
+
+void recon(ubyte[] cline, in ubyte[] pline, ubyte ftype, size_t fstep) pure {
+ switch (ftype) with (PNG_FilterType) {
+ case None:
+ break;
+ case Sub:
+ foreach (k; fstep .. cline.length)
+ cline[k] += cline[k-fstep];
+ break;
+ case Up:
+ foreach (k; 0 .. cline.length)
+ cline[k] += pline[k];
+ break;
+ case Average:
+ foreach (k; 0 .. fstep)
+ cline[k] += pline[k] / 2;
+ foreach (k; fstep .. cline.length)
+ cline[k] += cast(ubyte)
+ ((cast(uint) cline[k-fstep] + cast(uint) pline[k]) / 2);
+ break;
+ case Paeth:
+ foreach (i; 0 .. fstep)
+ cline[i] += paeth(0, pline[i], 0);
+ foreach (i; fstep .. cline.length)
+ cline[i] += paeth(cline[i-fstep], pline[i], pline[i-fstep]);
+ break;
+ default:
+ throw new ImageIOException("filter type not supported");
+ }
+}
+
+ubyte paeth(ubyte a, ubyte b, ubyte c) pure nothrow {
+ int pc = c;
+ int pa = b - pc;
+ int pb = a - pc;
+ pc = pa + pb;
+ if (pa < 0) pa = -pa;
+ if (pb < 0) pb = -pb;
+ if (pc < 0) pc = -pc;
+
+ if (pa <= pb && pa <= pc) {
+ return a;
+ } else if (pb <= pc) {
+ return b;
+ }
+ return c;
+}
+
+// ----------------------------------------------------------------------
+// PNG encoder
+
+void write_png(Writer stream, long w, long h, in ubyte[] data, long tgt_chans = 0) {
+ if (w < 1 || h < 1 || int.max < w || int.max < h)
+ throw new ImageIOException("invalid dimensions");
+ uint src_chans = cast(uint) (data.length / w / h);
+ if (src_chans < 1 || 4 < src_chans || tgt_chans < 0 || 4 < tgt_chans)
+ throw new ImageIOException("invalid channel count");
+ if (src_chans * w * h != data.length)
+ throw new ImageIOException("mismatching dimensions and length");
+
+ PNG_Encoder ec = {
+ stream : stream,
+ w : cast(size_t) w,
+ h : cast(size_t) h,
+ src_chans : src_chans,
+ tgt_chans : tgt_chans ? cast(uint) tgt_chans : src_chans,
+ data : data,
+ };
+
+ write_png(ec);
+ stream.flush();
+}
+
+enum MAXIMUM_CHUNK_SIZE = 8192;
+
+struct PNG_Encoder {
+ Writer stream;
+ size_t w, h;
+ uint src_chans;
+ uint tgt_chans;
+ const(ubyte)[] data;
+
+ CRC32 crc;
+ z_stream* z;
+ ubyte[] idatbuf;
+}
+
+void write_png(ref PNG_Encoder ec) {
+ ubyte[33] hdr = void;
+ hdr[ 0 .. 8] = png_file_header;
+ hdr[ 8 .. 16] = png_image_header;
+ hdr[16 .. 20] = nativeToBigEndian(cast(uint) ec.w);
+ hdr[20 .. 24] = nativeToBigEndian(cast(uint) ec.h);
+ hdr[24 ] = 8; // bit depth
+ hdr[25 ] = color_type(ec.tgt_chans);
+ hdr[26 .. 29] = 0; // compression, filter and interlace methods
+ ec.crc.start();
+ ec.crc.put(hdr[12 .. 29]);
+ ubyte[4] crc = ec.crc.finish();
+ reverse(crc[]);
+ hdr[29 .. 33] = crc;
+ ec.stream.rawWrite(hdr);
+
+ write_IDATs(ec);
+
+ static immutable ubyte[12] iend =
+ [0, 0, 0, 0, 'I','E','N','D', 0xae, 0x42, 0x60, 0x82];
+ ec.stream.rawWrite(iend);
+}
+
+void write_IDATs(ref PNG_Encoder ec) {
+ // initialize zlib stream
+ z_stream z = { zalloc: null, zfree: null, opaque: null };
+ if (deflateInit(&z, Z_DEFAULT_COMPRESSION) != Z_OK)
+ throw new ImageIOException("zlib init error");
+ scope(exit)
+ deflateEnd(ec.z);
+ ec.z = &z;
+
+ const LineConv!ubyte convert = get_converter!ubyte(ec.src_chans, ec.tgt_chans);
+
+ const size_t filter_step = ec.tgt_chans; // step between pixels, in bytes
+ const size_t slinesz = ec.w * ec.src_chans;
+ const size_t tlinesz = ec.w * ec.tgt_chans + 1;
+ const size_t workbufsz = 3 * tlinesz + MAXIMUM_CHUNK_SIZE;
+
+ ubyte[] workbuf = new ubyte[workbufsz];
+ ubyte[] cline = workbuf[0 .. tlinesz];
+ ubyte[] pline = workbuf[tlinesz .. 2 * tlinesz];
+ ubyte[] filtered = workbuf[2 * tlinesz .. 3 * tlinesz];
+ ec.idatbuf = workbuf[$-MAXIMUM_CHUNK_SIZE .. $];
+ workbuf[0..$] = 0;
+ ec.z.avail_out = cast(uint) ec.idatbuf.length;
+ ec.z.next_out = ec.idatbuf.ptr;
+
+ const size_t ssize = ec.w * ec.src_chans * ec.h;
+
+ for (size_t si; si < ssize; si += slinesz) {
+ convert(ec.data[si .. si + slinesz], cline[1..$]);
+
+ // these loops could be merged with some extra space...
+ foreach (i; 1 .. filter_step+1)
+ filtered[i] = cast(ubyte) (cline[i] - paeth(0, pline[i], 0));
+ foreach (i; filter_step+1 .. tlinesz)
+ filtered[i] = cast(ubyte)
+ (cline[i] - paeth(cline[i-filter_step], pline[i], pline[i-filter_step]));
+ filtered[0] = PNG_FilterType.Paeth;
+
+ compress(ec, filtered);
+ ubyte[] _swap = pline;
+ pline = cline;
+ cline = _swap;
+ }
+
+ while (true) { // flush zlib
+ int q = deflate(ec.z, Z_FINISH);
+ if (ec.idatbuf.length - ec.z.avail_out > 0)
+ flush_idat(ec);
+ if (q == Z_STREAM_END) break;
+ if (q == Z_OK) continue; // not enough avail_out
+ throw new ImageIOException("zlib compression error");
+ }
+}
+
+void compress(ref PNG_Encoder ec, in ubyte[] line)
+{
+ ec.z.avail_in = cast(uint) line.length;
+ ec.z.next_in = line.ptr;
+ while (ec.z.avail_in) {
+ int q = deflate(ec.z, Z_NO_FLUSH);
+ if (q != Z_OK)
+ throw new ImageIOException("zlib compression error");
+ if (ec.z.avail_out == 0)
+ flush_idat(ec);
+ }
+}
+
+void flush_idat(ref PNG_Encoder ec) // writes an idat chunk
+{
+ const uint len = cast(uint) (ec.idatbuf.length - ec.z.avail_out);
+ ec.crc.put(cast(const(ubyte)[]) "IDAT");
+ ec.crc.put(ec.idatbuf[0 .. len]);
+ ubyte[8] meta;
+ meta[0..4] = nativeToBigEndian!uint(len);
+ meta[4..8] = cast(ubyte[4]) "IDAT";
+ ec.stream.rawWrite(meta);
+ ec.stream.rawWrite(ec.idatbuf[0 .. len]);
+ ubyte[4] crc = ec.crc.finish();
+ reverse(crc[]);
+ ec.stream.rawWrite(crc[0..$]);
+ ec.z.next_out = ec.idatbuf.ptr;
+ ec.z.avail_out = cast(uint) ec.idatbuf.length;
+}
+
+package void read_png_info(Reader stream, out int w, out int h, out int chans) {
+ PNG_Header hdr = read_png_header(stream);
+ w = hdr.width;
+ h = hdr.height;
+ chans = channels(cast(PNG_ColorType) hdr.color_type);
+}
diff --git a/src/ext_depends/imageformats/imageformats/tga.d b/src/ext_depends/imageformats/imageformats/tga.d
new file mode 100644
index 0000000..afc3d0e
--- /dev/null
+++ b/src/ext_depends/imageformats/imageformats/tga.d
@@ -0,0 +1,456 @@
+module imageformats.tga;
+
+import std.algorithm : min;
+import std.bitmanip : littleEndianToNative, nativeToLittleEndian;
+import std.stdio : File, SEEK_SET, SEEK_CUR;
+import std.typecons : scoped;
+import imageformats;
+
+private:
+
+/// Header of a TGA file.
+public struct TGA_Header {
+ ubyte id_length;
+ ubyte palette_type;
+ ubyte data_type;
+ ushort palette_start;
+ ushort palette_length;
+ ubyte palette_bits;
+ ushort x_origin;
+ ushort y_origin;
+ ushort width;
+ ushort height;
+ ubyte bits_pp;
+ ubyte flags;
+}
+
+/// Returns the header of a TGA file.
+public TGA_Header read_tga_header(in char[] filename) {
+ auto reader = scoped!FileReader(filename);
+ return read_tga_header(reader);
+}
+
+/// Reads the image header from a buffer containing a TGA image.
+public TGA_Header read_tga_header_from_mem(in ubyte[] source) {
+ auto reader = scoped!MemReader(source);
+ return read_tga_header(reader);
+}
+
+/// Reads a TGA image. req_chans defines the format of returned image
+/// (you can use ColFmt here).
+public IFImage read_tga(in char[] filename, long req_chans = 0) {
+ auto reader = scoped!FileReader(filename);
+ return read_tga(reader, req_chans);
+}
+
+/// Reads an image from a buffer containing a TGA image. req_chans defines the
+/// format of returned image (you can use ColFmt here).
+public IFImage read_tga_from_mem(in ubyte[] source, long req_chans = 0) {
+ auto reader = scoped!MemReader(source);
+ return read_tga(reader, req_chans);
+}
+
+/// Writes a TGA image into a file.
+public void write_tga(in char[] file, long w, long h, in ubyte[] data, long tgt_chans = 0)
+{
+ auto writer = scoped!FileWriter(file);
+ write_tga(writer, w, h, data, tgt_chans);
+}
+
+/// Writes a TGA image into a buffer.
+public ubyte[] write_tga_to_mem(long w, long h, in ubyte[] data, long tgt_chans = 0) {
+ auto writer = scoped!MemWriter();
+ write_tga(writer, w, h, data, tgt_chans);
+ return writer.result;
+}
+
+/// Returns width, height and color format information via w, h and chans.
+public void read_tga_info(in char[] filename, out int w, out int h, out int chans) {
+ auto reader = scoped!FileReader(filename);
+ return read_tga_info(reader, w, h, chans);
+}
+
+/// Returns width, height and color format information via w, h and chans.
+public void read_tga_info_from_mem(in ubyte[] source, out int w, out int h, out int chans) {
+ auto reader = scoped!MemReader(source);
+ return read_tga_info(reader, w, h, chans);
+}
+
+// Detects whether a TGA image is readable from stream.
+package bool detect_tga(Reader stream) {
+ try {
+ auto hdr = read_tga_header(stream);
+ return true;
+ } catch (Throwable) {
+ return false;
+ } finally {
+ stream.seek(0, SEEK_SET);
+ }
+}
+
+TGA_Header read_tga_header(Reader stream) {
+ ubyte[18] tmp = void;
+ stream.readExact(tmp, tmp.length);
+
+ TGA_Header hdr = {
+ id_length : tmp[0],
+ palette_type : tmp[1],
+ data_type : tmp[2],
+ palette_start : littleEndianToNative!ushort(tmp[3..5]),
+ palette_length : littleEndianToNative!ushort(tmp[5..7]),
+ palette_bits : tmp[7],
+ x_origin : littleEndianToNative!ushort(tmp[8..10]),
+ y_origin : littleEndianToNative!ushort(tmp[10..12]),
+ width : littleEndianToNative!ushort(tmp[12..14]),
+ height : littleEndianToNative!ushort(tmp[14..16]),
+ bits_pp : tmp[16],
+ flags : tmp[17],
+ };
+
+ if (hdr.width < 1 || hdr.height < 1 || hdr.palette_type > 1
+ || (hdr.palette_type == 0 && (hdr.palette_start
+ || hdr.palette_length
+ || hdr.palette_bits))
+ || (4 <= hdr.data_type && hdr.data_type <= 8) || 12 <= hdr.data_type)
+ throw new ImageIOException("corrupt TGA header");
+
+ return hdr;
+}
+
+package IFImage read_tga(Reader stream, long req_chans = 0) {
+ if (req_chans < 0 || 4 < req_chans)
+ throw new ImageIOException("come on...");
+
+ TGA_Header hdr = read_tga_header(stream);
+
+ if (hdr.width < 1 || hdr.height < 1)
+ throw new ImageIOException("invalid dimensions");
+ if (hdr.flags & 0xc0) // two bits
+ throw new ImageIOException("interlaced TGAs not supported");
+ if (hdr.flags & 0x10)
+ throw new ImageIOException("right-to-left TGAs not supported");
+ ubyte attr_bits_pp = (hdr.flags & 0xf);
+ if (! (attr_bits_pp == 0 || attr_bits_pp == 8)) // some set it 0 although data has 8
+ throw new ImageIOException("only 8-bit alpha/attribute(s) supported");
+ if (hdr.palette_type)
+ throw new ImageIOException("paletted TGAs not supported");
+
+ const bool rle = hdr.data_type == TGA_DataType.TrueColor_RLE // Idx_RLE
+ || hdr.data_type == TGA_DataType.Gray_RLE; // not supported
+
+ switch (hdr.data_type) with (TGA_DataType) {
+ case TrueColor:
+ case TrueColor_RLE:
+ if (hdr.bits_pp != 24 && hdr.bits_pp != 32)
+ throw new ImageIOException("not supported");
+ break;
+ case Gray:
+ case Gray_RLE:
+ if (hdr.bits_pp != 8 && !(hdr.bits_pp == 16 && attr_bits_pp == 8))
+ throw new ImageIOException("not supported");
+ break;
+ default:
+ throw new ImageIOException("not supported");
+ }
+
+ int src_chans = hdr.bits_pp / 8;
+
+ if (hdr.id_length)
+ stream.seek(hdr.id_length, SEEK_CUR);
+
+ TGA_Decoder dc = {
+ stream : stream,
+ w : hdr.width,
+ h : hdr.height,
+ origin_at_top : cast(bool) (hdr.flags & 0x20),
+ bytes_pp : hdr.bits_pp / 8,
+ rle : rle,
+ tgt_chans : (req_chans == 0) ? src_chans : cast(int) req_chans,
+ };
+
+ switch (dc.bytes_pp) {
+ case 1: dc.src_fmt = _ColFmt.Y; break;
+ case 2: dc.src_fmt = _ColFmt.YA; break;
+ case 3: dc.src_fmt = _ColFmt.BGR; break;
+ case 4: dc.src_fmt = _ColFmt.BGRA; break;
+ default: throw new ImageIOException("TGA: format not supported");
+ }
+
+ IFImage result = {
+ w : dc.w,
+ h : dc.h,
+ c : cast(ColFmt) dc.tgt_chans,
+ pixels : decode_tga(dc),
+ };
+ return result;
+}
+
+void write_tga(Writer stream, long w, long h, in ubyte[] data, long tgt_chans = 0) {
+ if (w < 1 || h < 1 || ushort.max < w || ushort.max < h)
+ throw new ImageIOException("invalid dimensions");
+ ulong src_chans = data.length / w / h;
+ if (src_chans < 1 || 4 < src_chans || tgt_chans < 0 || 4 < tgt_chans)
+ throw new ImageIOException("invalid channel count");
+ if (src_chans * w * h != data.length)
+ throw new ImageIOException("mismatching dimensions and length");
+
+ TGA_Encoder ec = {
+ stream : stream,
+ w : cast(ushort) w,
+ h : cast(ushort) h,
+ src_chans : cast(int) src_chans,
+ tgt_chans : cast(int) ((tgt_chans) ? tgt_chans : src_chans),
+ rle : true,
+ data : data,
+ };
+
+ write_tga(ec);
+ stream.flush();
+}
+
+struct TGA_Decoder {
+ Reader stream;
+ int w, h;
+ bool origin_at_top; // src
+ uint bytes_pp;
+ bool rle; // run length compressed
+ _ColFmt src_fmt;
+ uint tgt_chans;
+}
+
+ubyte[] decode_tga(ref TGA_Decoder dc) {
+ auto result = new ubyte[dc.w * dc.h * dc.tgt_chans];
+
+ const size_t tgt_linesize = dc.w * dc.tgt_chans;
+ const size_t src_linesize = dc.w * dc.bytes_pp;
+ auto src_line = new ubyte[src_linesize];
+
+ const ptrdiff_t tgt_stride = (dc.origin_at_top) ? tgt_linesize : -tgt_linesize;
+ ptrdiff_t ti = (dc.origin_at_top) ? 0 : (dc.h-1) * tgt_linesize;
+
+ const LineConv!ubyte convert = get_converter!ubyte(dc.src_fmt, dc.tgt_chans);
+
+ if (!dc.rle) {
+ foreach (_j; 0 .. dc.h) {
+ dc.stream.readExact(src_line, src_linesize);
+ convert(src_line, result[ti .. ti + tgt_linesize]);
+ ti += tgt_stride;
+ }
+ return result;
+ }
+
+ // ----- RLE -----
+
+ ubyte[4] rbuf;
+ size_t plen = 0; // packet length
+ bool its_rle = false;
+
+ foreach (_j; 0 .. dc.h) {
+ // fill src_line with uncompressed data (this works like a stream)
+ size_t wanted = src_linesize;
+ while (wanted) {
+ if (plen == 0) {
+ dc.stream.readExact(rbuf, 1);
+ its_rle = cast(bool) (rbuf[0] & 0x80);
+ plen = ((rbuf[0] & 0x7f) + 1) * dc.bytes_pp; // length in bytes
+ }
+ const size_t gotten = src_linesize - wanted;
+ const size_t copysize = min(plen, wanted);
+ if (its_rle) {
+ dc.stream.readExact(rbuf, dc.bytes_pp);
+ for (size_t p = gotten; p < gotten+copysize; p += dc.bytes_pp)
+ src_line[p .. p+dc.bytes_pp] = rbuf[0 .. dc.bytes_pp];
+ } else { // it's raw
+ auto slice = src_line[gotten .. gotten+copysize];
+ dc.stream.readExact(slice, copysize);
+ }
+ wanted -= copysize;
+ plen -= copysize;
+ }
+
+ convert(src_line, result[ti .. ti + tgt_linesize]);
+ ti += tgt_stride;
+ }
+
+ return result;
+}
+
+// ----------------------------------------------------------------------
+// TGA encoder
+
+immutable ubyte[18] tga_footer_sig =
+ cast(immutable(ubyte)[18]) "TRUEVISION-XFILE.\0";
+
+struct TGA_Encoder {
+ Writer stream;
+ ushort w, h;
+ int src_chans;
+ int tgt_chans;
+ bool rle; // run length compression
+ const(ubyte)[] data;
+}
+
+void write_tga(ref TGA_Encoder ec) {
+ ubyte data_type;
+ bool has_alpha = false;
+ switch (ec.tgt_chans) with (TGA_DataType) {
+ case 1: data_type = ec.rle ? Gray_RLE : Gray; break;
+ case 2: data_type = ec.rle ? Gray_RLE : Gray; has_alpha = true; break;
+ case 3: data_type = ec.rle ? TrueColor_RLE : TrueColor; break;
+ case 4: data_type = ec.rle ? TrueColor_RLE : TrueColor; has_alpha = true; break;
+ default: throw new ImageIOException("internal error");
+ }
+
+ ubyte[18] hdr = void;
+ hdr[0] = 0; // id length
+ hdr[1] = 0; // palette type
+ hdr[2] = data_type;
+ hdr[3..8] = 0; // palette start (2), len (2), bits per palette entry (1)
+ hdr[8..12] = 0; // x origin (2), y origin (2)
+ hdr[12..14] = nativeToLittleEndian(ec.w);
+ hdr[14..16] = nativeToLittleEndian(ec.h);
+ hdr[16] = cast(ubyte) (ec.tgt_chans * 8); // bits per pixel
+ hdr[17] = (has_alpha) ? 0x8 : 0x0; // flags: attr_bits_pp = 8
+ ec.stream.rawWrite(hdr);
+
+ write_image_data(ec);
+
+ ubyte[26] ftr = void;
+ ftr[0..4] = 0; // extension area offset
+ ftr[4..8] = 0; // developer directory offset
+ ftr[8..26] = tga_footer_sig;
+ ec.stream.rawWrite(ftr);
+}
+
+void write_image_data(ref TGA_Encoder ec) {
+ _ColFmt tgt_fmt;
+ switch (ec.tgt_chans) {
+ case 1: tgt_fmt = _ColFmt.Y; break;
+ case 2: tgt_fmt = _ColFmt.YA; break;
+ case 3: tgt_fmt = _ColFmt.BGR; break;
+ case 4: tgt_fmt = _ColFmt.BGRA; break;
+ default: throw new ImageIOException("internal error");
+ }
+
+ const LineConv!ubyte convert = get_converter!ubyte(ec.src_chans, tgt_fmt);
+
+ const size_t src_linesize = ec.w * ec.src_chans;
+ const size_t tgt_linesize = ec.w * ec.tgt_chans;
+ auto tgt_line = new ubyte[tgt_linesize];
+
+ ptrdiff_t si = (ec.h-1) * src_linesize; // origin at bottom
+
+ if (!ec.rle) {
+ foreach (_; 0 .. ec.h) {
+ convert(ec.data[si .. si + src_linesize], tgt_line);
+ ec.stream.rawWrite(tgt_line);
+ si -= src_linesize; // origin at bottom
+ }
+ return;
+ }
+
+ // ----- RLE -----
+
+ const bytes_pp = ec.tgt_chans;
+ const size_t max_packets_per_line = (tgt_linesize+127) / 128;
+ auto tgt_cmp = new ubyte[tgt_linesize + max_packets_per_line]; // compressed line
+ foreach (_; 0 .. ec.h) {
+ convert(ec.data[si .. si + src_linesize], tgt_line);
+ ubyte[] compressed_line = rle_compress(tgt_line, tgt_cmp, ec.w, bytes_pp);
+ ec.stream.rawWrite(compressed_line);
+ si -= src_linesize; // origin at bottom
+ }
+}
+
+ubyte[] rle_compress(in ubyte[] line, ubyte[] tgt_cmp, in size_t w, in int bytes_pp)
+{
+ const int rle_limit = (1 < bytes_pp) ? 2 : 3; // run len worth an RLE packet
+ size_t runlen = 0;
+ size_t rawlen = 0;
+ size_t raw_i = 0; // start of raw packet data in line
+ size_t cmp_i = 0;
+ size_t pixels_left = w;
+ const(ubyte)[] px;
+ for (size_t i = bytes_pp; pixels_left; i += bytes_pp) {
+ runlen = 1;
+ px = line[i-bytes_pp .. i];
+ while (i < line.length && line[i .. i+bytes_pp] == px[0..$] && runlen < 128) {
+ ++runlen;
+ i += bytes_pp;
+ }
+ pixels_left -= runlen;
+
+ if (runlen < rle_limit) {
+ // data goes to raw packet
+ rawlen += runlen;
+ if (128 <= rawlen) { // full packet, need to store it
+ size_t copysize = 128 * bytes_pp;
+ tgt_cmp[cmp_i++] = 0x7f; // raw packet header
+ tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize];
+ cmp_i += copysize;
+ raw_i += copysize;
+ rawlen -= 128;
+ }
+ } else {
+ // RLE packet is worth it
+
+ // store raw packet first, if any
+ if (rawlen) {
+ assert(rawlen < 128);
+ size_t copysize = rawlen * bytes_pp;
+ tgt_cmp[cmp_i++] = cast(ubyte) (rawlen-1); // raw packet header
+ tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize];
+ cmp_i += copysize;
+ rawlen = 0;
+ }
+
+ // store RLE packet
+ tgt_cmp[cmp_i++] = cast(ubyte) (0x80 | (runlen-1)); // packet header
+ tgt_cmp[cmp_i .. cmp_i+bytes_pp] = px[0..$]; // packet data
+ cmp_i += bytes_pp;
+ raw_i = i;
+ }
+ } // for
+
+ if (rawlen) { // last packet of the line
+ size_t copysize = rawlen * bytes_pp;
+ tgt_cmp[cmp_i++] = cast(ubyte) (rawlen-1); // raw packet header
+ tgt_cmp[cmp_i .. cmp_i+copysize] = line[raw_i .. raw_i+copysize];
+ cmp_i += copysize;
+ }
+ return tgt_cmp[0 .. cmp_i];
+}
+
+enum TGA_DataType : ubyte {
+ Idx = 1,
+ TrueColor = 2,
+ Gray = 3,
+ Idx_RLE = 9,
+ TrueColor_RLE = 10,
+ Gray_RLE = 11,
+}
+
+package void read_tga_info(Reader stream, out int w, out int h, out int chans) {
+ TGA_Header hdr = read_tga_header(stream);
+ w = hdr.width;
+ h = hdr.height;
+
+ // TGA is awkward...
+ auto dt = hdr.data_type;
+ if ((dt == TGA_DataType.TrueColor || dt == TGA_DataType.Gray ||
+ dt == TGA_DataType.TrueColor_RLE || dt == TGA_DataType.Gray_RLE)
+ && (hdr.bits_pp % 8) == 0)
+ {
+ chans = hdr.bits_pp / 8;
+ return;
+ } else if (dt == TGA_DataType.Idx || dt == TGA_DataType.Idx_RLE) {
+ switch (hdr.palette_bits) {
+ case 15: chans = 3; return;
+ case 16: chans = 3; return; // one bit could be for some "interrupt control"
+ case 24: chans = 3; return;
+ case 32: chans = 4; return;
+ default:
+ }
+ }
+ chans = 0; // unknown
+}
diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basi0g08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi0g08.bmp
new file mode 100644
index 0000000..eaaff1b
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi0g08.bmp
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basi2c08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi2c08.bmp
new file mode 100644
index 0000000..ff66a65
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi2c08.bmp
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basi3p08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi3p08.bmp
new file mode 100644
index 0000000..ffe79fe
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi3p08.bmp
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basi4a08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi4a08.bmp
new file mode 100644
index 0000000..e24c9ea
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi4a08.bmp
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basi6a08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi6a08.bmp
new file mode 100644
index 0000000..4fc819f
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi6a08.bmp
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basn0g08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn0g08.bmp
new file mode 100644
index 0000000..eaaff1b
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn0g08.bmp
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basn2c08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn2c08.bmp
new file mode 100644
index 0000000..ff66a65
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn2c08.bmp
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basn3p08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn3p08.bmp
new file mode 100644
index 0000000..ffe79fe
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn3p08.bmp
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basn4a08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn4a08.bmp
new file mode 100644
index 0000000..e24c9ea
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn4a08.bmp
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basn6a08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn6a08.bmp
new file mode 100644
index 0000000..4fc819f
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn6a08.bmp
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basi0g08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basi0g08.tga
new file mode 100644
index 0000000..188dab5
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basi0g08.tga
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basi2c08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basi2c08.tga
new file mode 100644
index 0000000..388c86a
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basi2c08.tga
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basi3p08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basi3p08.tga
new file mode 100644
index 0000000..d79e009
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basi3p08.tga
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basi4a08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basi4a08.tga
new file mode 100644
index 0000000..064241a
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basi4a08.tga
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basi6a08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basi6a08.tga
new file mode 100644
index 0000000..594d70f
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basi6a08.tga
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basn0g08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basn0g08.tga
new file mode 100644
index 0000000..188dab5
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basn0g08.tga
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basn2c08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basn2c08.tga
new file mode 100644
index 0000000..388c86a
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basn2c08.tga
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basn3p08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basn3p08.tga
new file mode 100644
index 0000000..d79e009
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basn3p08.tga
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basn4a08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basn4a08.tga
new file mode 100644
index 0000000..064241a
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basn4a08.tga
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basn6a08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basn6a08.tga
new file mode 100644
index 0000000..594d70f
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basn6a08.tga
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite/PngSuite.LICENSE b/src/ext_depends/imageformats/tests/pngsuite/PngSuite.LICENSE
new file mode 100644
index 0000000..8d4d1d0
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/PngSuite.LICENSE
@@ -0,0 +1,9 @@
+PngSuite
+--------
+
+Permission to use, copy, modify and distribute these images for any
+purpose and without fee is hereby granted.
+
+
+(c) Willem van Schaik, 1996, 2011
+
diff --git a/src/ext_depends/imageformats/tests/pngsuite/PngSuite.README b/src/ext_depends/imageformats/tests/pngsuite/PngSuite.README
new file mode 100644
index 0000000..3ef8f24
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/PngSuite.README
@@ -0,0 +1,25 @@
+ PNGSUITE
+----------------
+
+ testset for PNG-(de)coders
+ created by Willem van Schaik
+------------------------------------
+
+This is a collection of graphics images created to test the png applications
+like viewers, converters and editors. All (as far as that is possible)
+formats supported by the PNG standard are represented.
+
+The suite consists of the following files:
+
+- PngSuite.README - this file
+- PngSuite.LICENSE - the PngSuite is freeware
+- PngSuite.png - image with PngSuite logo
+- PngSuite.tgz - archive of all PNG testfiles
+- PngSuite.zip - same in .zip format for PCs
+
+
+--------
+ (c) Willem van Schaik
+ willem@schaik.com
+ Calgary, April 2011
+
diff --git a/src/ext_depends/imageformats/tests/pngsuite/basi0g08.png b/src/ext_depends/imageformats/tests/pngsuite/basi0g08.png
new file mode 100644
index 0000000..faed8be
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/basi0g08.png
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite/basi2c08.png b/src/ext_depends/imageformats/tests/pngsuite/basi2c08.png
new file mode 100644
index 0000000..2aab44d
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/basi2c08.png
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite/basi3p08.png b/src/ext_depends/imageformats/tests/pngsuite/basi3p08.png
new file mode 100644
index 0000000..50a6d1c
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/basi3p08.png
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite/basi4a08.png b/src/ext_depends/imageformats/tests/pngsuite/basi4a08.png
new file mode 100644
index 0000000..398132b
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/basi4a08.png
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite/basi6a08.png b/src/ext_depends/imageformats/tests/pngsuite/basi6a08.png
new file mode 100644
index 0000000..aecb32e
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/basi6a08.png
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite/basn0g08.png b/src/ext_depends/imageformats/tests/pngsuite/basn0g08.png
new file mode 100644
index 0000000..23c8237
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/basn0g08.png
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite/basn2c08.png b/src/ext_depends/imageformats/tests/pngsuite/basn2c08.png
new file mode 100644
index 0000000..db5ad15
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/basn2c08.png
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite/basn3p08.png b/src/ext_depends/imageformats/tests/pngsuite/basn3p08.png
new file mode 100644
index 0000000..0ddad07
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/basn3p08.png
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite/basn4a08.png b/src/ext_depends/imageformats/tests/pngsuite/basn4a08.png
new file mode 100644
index 0000000..3e13052
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/basn4a08.png
Binary files differ
diff --git a/src/ext_depends/imageformats/tests/pngsuite/basn6a08.png b/src/ext_depends/imageformats/tests/pngsuite/basn6a08.png
new file mode 100644
index 0000000..e608738
--- /dev/null
+++ b/src/ext_depends/imageformats/tests/pngsuite/basn6a08.png
Binary files differ