diff options
Diffstat (limited to 'src/ext_depends/imageformats')
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  [](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 ©_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 Binary files differnew file mode 100644 index 0000000..eaaff1b --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi0g08.bmp diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basi2c08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi2c08.bmp Binary files differnew file mode 100644 index 0000000..ff66a65 --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi2c08.bmp diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basi3p08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi3p08.bmp Binary files differnew file mode 100644 index 0000000..ffe79fe --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi3p08.bmp diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basi4a08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi4a08.bmp Binary files differnew file mode 100644 index 0000000..e24c9ea --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi4a08.bmp diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basi6a08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi6a08.bmp Binary files differnew file mode 100644 index 0000000..4fc819f --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basi6a08.bmp diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basn0g08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn0g08.bmp Binary files differnew file mode 100644 index 0000000..eaaff1b --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn0g08.bmp diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basn2c08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn2c08.bmp Binary files differnew file mode 100644 index 0000000..ff66a65 --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn2c08.bmp diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basn3p08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn3p08.bmp Binary files differnew file mode 100644 index 0000000..ffe79fe --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn3p08.bmp diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basn4a08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn4a08.bmp Binary files differnew file mode 100644 index 0000000..e24c9ea --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn4a08.bmp diff --git a/src/ext_depends/imageformats/tests/pngsuite-bmp/basn6a08.bmp b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn6a08.bmp Binary files differnew file mode 100644 index 0000000..4fc819f --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-bmp/basn6a08.bmp diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basi0g08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basi0g08.tga Binary files differnew file mode 100644 index 0000000..188dab5 --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basi0g08.tga diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basi2c08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basi2c08.tga Binary files differnew file mode 100644 index 0000000..388c86a --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basi2c08.tga diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basi3p08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basi3p08.tga Binary files differnew file mode 100644 index 0000000..d79e009 --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basi3p08.tga diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basi4a08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basi4a08.tga Binary files differnew file mode 100644 index 0000000..064241a --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basi4a08.tga diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basi6a08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basi6a08.tga Binary files differnew file mode 100644 index 0000000..594d70f --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basi6a08.tga diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basn0g08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basn0g08.tga Binary files differnew file mode 100644 index 0000000..188dab5 --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basn0g08.tga diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basn2c08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basn2c08.tga Binary files differnew file mode 100644 index 0000000..388c86a --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basn2c08.tga diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basn3p08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basn3p08.tga Binary files differnew file mode 100644 index 0000000..d79e009 --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basn3p08.tga diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basn4a08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basn4a08.tga Binary files differnew file mode 100644 index 0000000..064241a --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basn4a08.tga diff --git a/src/ext_depends/imageformats/tests/pngsuite-tga/basn6a08.tga b/src/ext_depends/imageformats/tests/pngsuite-tga/basn6a08.tga Binary files differnew file mode 100644 index 0000000..594d70f --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite-tga/basn6a08.tga 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 Binary files differnew file mode 100644 index 0000000..faed8be --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite/basi0g08.png diff --git a/src/ext_depends/imageformats/tests/pngsuite/basi2c08.png b/src/ext_depends/imageformats/tests/pngsuite/basi2c08.png Binary files differnew file mode 100644 index 0000000..2aab44d --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite/basi2c08.png diff --git a/src/ext_depends/imageformats/tests/pngsuite/basi3p08.png b/src/ext_depends/imageformats/tests/pngsuite/basi3p08.png Binary files differnew file mode 100644 index 0000000..50a6d1c --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite/basi3p08.png diff --git a/src/ext_depends/imageformats/tests/pngsuite/basi4a08.png b/src/ext_depends/imageformats/tests/pngsuite/basi4a08.png Binary files differnew file mode 100644 index 0000000..398132b --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite/basi4a08.png diff --git a/src/ext_depends/imageformats/tests/pngsuite/basi6a08.png b/src/ext_depends/imageformats/tests/pngsuite/basi6a08.png Binary files differnew file mode 100644 index 0000000..aecb32e --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite/basi6a08.png diff --git a/src/ext_depends/imageformats/tests/pngsuite/basn0g08.png b/src/ext_depends/imageformats/tests/pngsuite/basn0g08.png Binary files differnew file mode 100644 index 0000000..23c8237 --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite/basn0g08.png diff --git a/src/ext_depends/imageformats/tests/pngsuite/basn2c08.png b/src/ext_depends/imageformats/tests/pngsuite/basn2c08.png Binary files differnew file mode 100644 index 0000000..db5ad15 --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite/basn2c08.png diff --git a/src/ext_depends/imageformats/tests/pngsuite/basn3p08.png b/src/ext_depends/imageformats/tests/pngsuite/basn3p08.png Binary files differnew file mode 100644 index 0000000..0ddad07 --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite/basn3p08.png diff --git a/src/ext_depends/imageformats/tests/pngsuite/basn4a08.png b/src/ext_depends/imageformats/tests/pngsuite/basn4a08.png Binary files differnew file mode 100644 index 0000000..3e13052 --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite/basn4a08.png diff --git a/src/ext_depends/imageformats/tests/pngsuite/basn6a08.png b/src/ext_depends/imageformats/tests/pngsuite/basn6a08.png Binary files differnew file mode 100644 index 0000000..e608738 --- /dev/null +++ b/src/ext_depends/imageformats/tests/pngsuite/basn6a08.png  | 
