aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/ext_depends/imageformats/imageformats/package.d
diff options
context:
space:
mode:
Diffstat (limited to 'src/ext_depends/imageformats/imageformats/package.d')
-rw-r--r--src/ext_depends/imageformats/imageformats/package.d455
1 files changed, 455 insertions, 0 deletions
diff --git a/src/ext_depends/imageformats/imageformats/package.d b/src/ext_depends/imageformats/imageformats/package.d
new file mode 100644
index 0000000..7f7414d
--- /dev/null
+++ b/src/ext_depends/imageformats/imageformats/package.d
@@ -0,0 +1,455 @@
+// Copyright (c) 2014-2018 Tero Hänninen
+// Boost Software License - Version 1.0 - August 17th, 2003
+module imageformats;
+
+import std.stdio : File, SEEK_SET, SEEK_CUR, SEEK_END;
+import std.string : toLower, lastIndexOf;
+import std.typecons : scoped;
+public import imageformats.png;
+public import imageformats.tga;
+public import imageformats.bmp;
+public import imageformats.jpeg;
+
+/// Image with 8-bit channels. Top-left corner at (0, 0).
+struct IFImage {
+ /// width
+ int w;
+ /// height
+ int h;
+ /// channels
+ ColFmt c;
+ /// buffer
+ ubyte[] pixels;
+}
+
+/// Image with 16-bit channels. Top-left corner at (0, 0).
+struct IFImage16 {
+ /// width
+ int w;
+ /// height
+ int h;
+ /// channels
+ ColFmt c;
+ /// buffer
+ ushort[] pixels;
+}
+
+/// Color format which you can pass to the read and write functions.
+enum ColFmt {
+ Y = 1, /// Gray
+ YA = 2, /// Gray + Alpha
+ RGB = 3, /// Truecolor
+ RGBA = 4, /// Truecolor + Alpha
+}
+
+/// Reads an image from file. req_chans defines the format of returned image
+/// (you can use ColFmt here).
+IFImage read_image(in char[] file, long req_chans = 0) {
+ auto reader = scoped!FileReader(file);
+ return read_image_from_reader(reader, req_chans);
+}
+
+/// Reads an image from a buffer. req_chans defines the format of returned
+/// image (you can use ColFmt here).
+IFImage read_image_from_mem(in ubyte[] source, long req_chans = 0) {
+ auto reader = scoped!MemReader(source);
+ return read_image_from_reader(reader, req_chans);
+}
+
+/// Writes an image to file. req_chans defines the format the image is saved in
+/// (you can use ColFmt here).
+void write_image(in char[] file, long w, long h, in ubyte[] data, long req_chans = 0) {
+ const char[] ext = extract_extension_lowercase(file);
+
+ void function(Writer, long, long, in ubyte[], long) write_image;
+ switch (ext) {
+ case "png": write_image = &write_png; break;
+ case "tga": write_image = &write_tga; break;
+ case "bmp": write_image = &write_bmp; break;
+ default: throw new ImageIOException("unknown image extension/type");
+ }
+ auto writer = scoped!FileWriter(file);
+ write_image(writer, w, h, data, req_chans);
+}
+
+/// Returns width, height and color format information via w, h and chans.
+/// If number of channels is unknown chans is set to zero, otherwise chans
+/// values map to those of ColFmt.
+void read_image_info(in char[] file, out int w, out int h, out int chans) {
+ auto reader = scoped!FileReader(file);
+ try {
+ return read_png_info(reader, w, h, chans);
+ } catch (Throwable) {
+ reader.seek(0, SEEK_SET);
+ }
+ try {
+ return read_jpeg_info(reader, w, h, chans);
+ } catch (Throwable) {
+ reader.seek(0, SEEK_SET);
+ }
+ try {
+ return read_bmp_info(reader, w, h, chans);
+ } catch (Throwable) {
+ reader.seek(0, SEEK_SET);
+ }
+ try {
+ return read_tga_info(reader, w, h, chans);
+ } catch (Throwable) {
+ reader.seek(0, SEEK_SET);
+ }
+ throw new ImageIOException("unknown image type");
+}
+
+/// Thrown from all the functions...
+class ImageIOException : Exception {
+ @safe pure const
+ this(string msg, string file = __FILE__, size_t line = __LINE__) {
+ super(msg, file, line);
+ }
+}
+
+private:
+
+IFImage read_image_from_reader(Reader reader, long req_chans) {
+ if (detect_png(reader)) return read_png(reader, req_chans);
+ if (detect_jpeg(reader)) return read_jpeg(reader, req_chans);
+ if (detect_bmp(reader)) return read_bmp(reader, req_chans);
+ if (detect_tga(reader)) return read_tga(reader, req_chans);
+ throw new ImageIOException("unknown image type");
+}
+
+// --------------------------------------------------------------------------------
+// Conversions
+
+package enum _ColFmt : int {
+ Unknown = 0,
+ Y = 1,
+ YA,
+ RGB,
+ RGBA,
+ BGR,
+ BGRA,
+}
+
+package alias LineConv(T) = void function(in T[] src, T[] tgt);
+
+package LineConv!T get_converter(T)(long src_chans, long tgt_chans) pure {
+ long combo(long a, long b) pure nothrow { return a*16 + b; }
+
+ if (src_chans == tgt_chans)
+ return &copy_line!T;
+
+ switch (combo(src_chans, tgt_chans)) with (_ColFmt) {
+ case combo(Y, YA) : return &Y_to_YA!T;
+ case combo(Y, RGB) : return &Y_to_RGB!T;
+ case combo(Y, RGBA) : return &Y_to_RGBA!T;
+ case combo(Y, BGR) : return &Y_to_BGR!T;
+ case combo(Y, BGRA) : return &Y_to_BGRA!T;
+ case combo(YA, Y) : return &YA_to_Y!T;
+ case combo(YA, RGB) : return &YA_to_RGB!T;
+ case combo(YA, RGBA) : return &YA_to_RGBA!T;
+ case combo(YA, BGR) : return &YA_to_BGR!T;
+ case combo(YA, BGRA) : return &YA_to_BGRA!T;
+ case combo(RGB, Y) : return &RGB_to_Y!T;
+ case combo(RGB, YA) : return &RGB_to_YA!T;
+ case combo(RGB, RGBA) : return &RGB_to_RGBA!T;
+ case combo(RGB, BGR) : return &RGB_to_BGR!T;
+ case combo(RGB, BGRA) : return &RGB_to_BGRA!T;
+ case combo(RGBA, Y) : return &RGBA_to_Y!T;
+ case combo(RGBA, YA) : return &RGBA_to_YA!T;
+ case combo(RGBA, RGB) : return &RGBA_to_RGB!T;
+ case combo(RGBA, BGR) : return &RGBA_to_BGR!T;
+ case combo(RGBA, BGRA) : return &RGBA_to_BGRA!T;
+ case combo(BGR, Y) : return &BGR_to_Y!T;
+ case combo(BGR, YA) : return &BGR_to_YA!T;
+ case combo(BGR, RGB) : return &BGR_to_RGB!T;
+ case combo(BGR, RGBA) : return &BGR_to_RGBA!T;
+ case combo(BGRA, Y) : return &BGRA_to_Y!T;
+ case combo(BGRA, YA) : return &BGRA_to_YA!T;
+ case combo(BGRA, RGB) : return &BGRA_to_RGB!T;
+ case combo(BGRA, RGBA) : return &BGRA_to_RGBA!T;
+ default : throw new ImageIOException("internal error");
+ }
+}
+
+void copy_line(T)(in T[] src, T[] tgt) pure nothrow {
+ tgt[0..$] = src[0..$];
+}
+
+T luminance(T)(T r, T g, T b) pure nothrow {
+ return cast(T) (0.21*r + 0.64*g + 0.15*b); // somewhat arbitrary weights
+}
+
+void Y_to_YA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=1, t+=2) {
+ tgt[t] = src[k];
+ tgt[t+1] = T.max;
+ }
+}
+
+alias Y_to_BGR = Y_to_RGB;
+void Y_to_RGB(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=1, t+=3)
+ tgt[t .. t+3] = src[k];
+}
+
+alias Y_to_BGRA = Y_to_RGBA;
+void Y_to_RGBA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=1, t+=4) {
+ tgt[t .. t+3] = src[k];
+ tgt[t+3] = T.max;
+ }
+}
+
+void YA_to_Y(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=2, t+=1)
+ tgt[t] = src[k];
+}
+
+alias YA_to_BGR = YA_to_RGB;
+void YA_to_RGB(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=2, t+=3)
+ tgt[t .. t+3] = src[k];
+}
+
+alias YA_to_BGRA = YA_to_RGBA;
+void YA_to_RGBA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=2, t+=4) {
+ tgt[t .. t+3] = src[k];
+ tgt[t+3] = src[k+1];
+ }
+}
+
+void RGB_to_Y(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=1)
+ tgt[t] = luminance(src[k], src[k+1], src[k+2]);
+}
+
+void RGB_to_YA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=2) {
+ tgt[t] = luminance(src[k], src[k+1], src[k+2]);
+ tgt[t+1] = T.max;
+ }
+}
+
+void RGB_to_RGBA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=4) {
+ tgt[t .. t+3] = src[k .. k+3];
+ tgt[t+3] = T.max;
+ }
+}
+
+void RGBA_to_Y(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=1)
+ tgt[t] = luminance(src[k], src[k+1], src[k+2]);
+}
+
+void RGBA_to_YA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=2) {
+ tgt[t] = luminance(src[k], src[k+1], src[k+2]);
+ tgt[t+1] = src[k+3];
+ }
+}
+
+void RGBA_to_RGB(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=3)
+ tgt[t .. t+3] = src[k .. k+3];
+}
+
+void BGR_to_Y(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=1)
+ tgt[t] = luminance(src[k+2], src[k+1], src[k+1]);
+}
+
+void BGR_to_YA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=2) {
+ tgt[t] = luminance(src[k+2], src[k+1], src[k+1]);
+ tgt[t+1] = T.max;
+ }
+}
+
+alias RGB_to_BGR = BGR_to_RGB;
+void BGR_to_RGB(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k; k < src.length; k+=3) {
+ tgt[k ] = src[k+2];
+ tgt[k+1] = src[k+1];
+ tgt[k+2] = src[k ];
+ }
+}
+
+alias RGB_to_BGRA = BGR_to_RGBA;
+void BGR_to_RGBA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=3, t+=4) {
+ tgt[t ] = src[k+2];
+ tgt[t+1] = src[k+1];
+ tgt[t+2] = src[k ];
+ tgt[t+3] = T.max;
+ }
+}
+
+void BGRA_to_Y(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=1)
+ tgt[t] = luminance(src[k+2], src[k+1], src[k]);
+}
+
+void BGRA_to_YA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=2) {
+ tgt[t] = luminance(src[k+2], src[k+1], src[k]);
+ tgt[t+1] = T.max;
+ }
+}
+
+alias RGBA_to_BGR = BGRA_to_RGB;
+void BGRA_to_RGB(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=3) {
+ tgt[t ] = src[k+2];
+ tgt[t+1] = src[k+1];
+ tgt[t+2] = src[k ];
+ }
+}
+
+alias RGBA_to_BGRA = BGRA_to_RGBA;
+void BGRA_to_RGBA(T)(in T[] src, T[] tgt) pure nothrow {
+ for (size_t k, t; k < src.length; k+=4, t+=4) {
+ tgt[t ] = src[k+2];
+ tgt[t+1] = src[k+1];
+ tgt[t+2] = src[k ];
+ tgt[t+3] = src[k+3];
+ }
+}
+
+// --------------------------------------------------------------------------------
+
+package interface Reader {
+ void readExact(ubyte[], size_t);
+ void seek(ptrdiff_t, int);
+}
+
+package interface Writer {
+ void rawWrite(in ubyte[]);
+ void flush();
+}
+
+package class FileReader : Reader {
+ this(in char[] filename) {
+ this(File(filename.idup, "rb"));
+ }
+
+ this(File f) {
+ if (!f.isOpen) throw new ImageIOException("File not open");
+ this.f = f;
+ }
+
+ void readExact(ubyte[] buffer, size_t bytes) {
+ auto slice = this.f.rawRead(buffer[0..bytes]);
+ if (slice.length != bytes)
+ throw new Exception("not enough data");
+ }
+
+ void seek(ptrdiff_t offset, int origin) { this.f.seek(offset, origin); }
+
+ private File f;
+}
+
+package class MemReader : Reader {
+ this(in ubyte[] source) {
+ this.source = source;
+ }
+
+ void readExact(ubyte[] buffer, size_t bytes) {
+ if (source.length - cursor < bytes)
+ throw new Exception("not enough data");
+ buffer[0..bytes] = source[cursor .. cursor+bytes];
+ cursor += bytes;
+ }
+
+ void seek(ptrdiff_t offset, int origin) {
+ switch (origin) {
+ case SEEK_SET:
+ if (offset < 0 || source.length <= offset)
+ throw new Exception("seek error");
+ cursor = offset;
+ break;
+ case SEEK_CUR:
+ ptrdiff_t dst = cursor + offset;
+ if (dst < 0 || source.length <= dst)
+ throw new Exception("seek error");
+ cursor = dst;
+ break;
+ case SEEK_END:
+ if (0 <= offset || source.length < -offset)
+ throw new Exception("seek error");
+ cursor = cast(ptrdiff_t) source.length + offset;
+ break;
+ default: assert(0);
+ }
+ }
+
+ private const ubyte[] source;
+ private ptrdiff_t cursor;
+}
+
+package class FileWriter : Writer {
+ this(in char[] filename) {
+ this(File(filename.idup, "wb"));
+ }
+
+ this(File f) {
+ if (!f.isOpen) throw new ImageIOException("File not open");
+ this.f = f;
+ }
+
+ void rawWrite(in ubyte[] block) { this.f.rawWrite(block); }
+ void flush() { this.f.flush(); }
+
+ private File f;
+}
+
+package class MemWriter : Writer {
+ this() { }
+
+ ubyte[] result() { return buffer; }
+
+ void rawWrite(in ubyte[] block) { this.buffer ~= block; }
+ void flush() { }
+
+ private ubyte[] buffer;
+}
+
+const(char)[] extract_extension_lowercase(in char[] filename) {
+ ptrdiff_t di = filename.lastIndexOf('.');
+ return (0 < di && di+1 < filename.length) ? filename[di+1..$].toLower() : "";
+}
+
+unittest {
+ // The TGA and BMP files are not as varied in format as the PNG files, so
+ // not as well tested.
+ string png_path = "tests/pngsuite/";
+ string tga_path = "tests/pngsuite-tga/";
+ string bmp_path = "tests/pngsuite-bmp/";
+
+ auto files = [
+ "basi0g08", // PNG image data, 32 x 32, 8-bit grayscale, interlaced
+ "basi2c08", // PNG image data, 32 x 32, 8-bit/color RGB, interlaced
+ "basi3p08", // PNG image data, 32 x 32, 8-bit colormap, interlaced
+ "basi4a08", // PNG image data, 32 x 32, 8-bit gray+alpha, interlaced
+ "basi6a08", // PNG image data, 32 x 32, 8-bit/color RGBA, interlaced
+ "basn0g08", // PNG image data, 32 x 32, 8-bit grayscale, non-interlaced
+ "basn2c08", // PNG image data, 32 x 32, 8-bit/color RGB, non-interlaced
+ "basn3p08", // PNG image data, 32 x 32, 8-bit colormap, non-interlaced
+ "basn4a08", // PNG image data, 32 x 32, 8-bit gray+alpha, non-interlaced
+ "basn6a08", // PNG image data, 32 x 32, 8-bit/color RGBA, non-interlaced
+ ];
+
+ foreach (file; files) {
+ //writefln("%s", file);
+ auto a = read_image(png_path ~ file ~ ".png", ColFmt.RGBA);
+ auto b = read_image(tga_path ~ file ~ ".tga", ColFmt.RGBA);
+ auto c = read_image(bmp_path ~ file ~ ".bmp", ColFmt.RGBA);
+ assert(a.w == b.w && a.w == c.w);
+ assert(a.h == b.h && a.h == c.h);
+ assert(a.pixels.length == b.pixels.length && a.pixels.length == c.pixels.length);
+ assert(a.pixels[0..$] == b.pixels[0..$], "png/tga");
+ assert(a.pixels[0..$] == c.pixels[0..$], "png/bmp");
+ }
+}