From e1c3724d65ef4ce13df57f27960efe0ed30fa417 Mon Sep 17 00:00:00 2001 From: ziltoid1991 Date: Tue, 25 Aug 2020 18:21:21 +0200 Subject: [PATCH] Massive refactoring --- README.md | 86 +- {source/dimage => leftout}/pcx.d | 3 +- source/dimage/base.d | 1764 ++++++++++++++++-------------- source/dimage/bmp.d | 247 +++-- source/dimage/exceptions.d | 86 ++ source/dimage/gif.d | 12 +- source/dimage/png.d | 200 ++-- source/dimage/tga.d | 410 ++++--- source/dimage/tiff.d | 435 +++++++- source/dimage/types.d | 422 +++++++ test/bmp/TRU256_I.bmp | Bin 0 -> 17462 bytes test/tga/test.tga | Bin 0 -> 8210 bytes 12 files changed, 2499 insertions(+), 1166 deletions(-) rename {source/dimage => leftout}/pcx.d (99%) create mode 100644 source/dimage/exceptions.d create mode 100644 source/dimage/types.d create mode 100644 test/bmp/TRU256_I.bmp create mode 100644 test/tga/test.tga diff --git a/README.md b/README.md index e73e90d..3d43f30 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # dimage -Image file handling library for D by László Szerémi (laszloszeremi@outlook.com, https://twitter.com/ziltoid1991, https://www.patreon.com/ShapeshiftingLizard, https://ko-fi.com/D1D45NEN). +Image file handling library for D by László Szerémi (laszloszeremi@outlook.com, https://twitter.com/ziltoid1991, +https://www.patreon.com/ShapeshiftingLizard, https://ko-fi.com/D1D45NEN). # Supported formats and capabilities @@ -13,7 +14,8 @@ Image file handling library for D by László Szerémi (laszloszeremi@outlook.co * RLE compression and decompression works mostly fine, but needs further testing. * Capable of reading and writing embedded data (developer area). (untested) * Capable of accessing extension area and generating scanline table. (untested) -* Extra features not present in standard: less than 8 bit indexed images, scanline boundary ignorance compressing RLE at the sacrifice of easy scanline accessing. +* Extra features not present in standard: less than 8 bit indexed images, scanline boundary ignorance compressing RLE at the +sacrifice of easy scanline accessing. ## Portable Network Graphics (png) @@ -21,14 +23,90 @@ Image file handling library for D by László Szerémi (laszloszeremi@outlook.co * Output mostly works. Certain ancillary chunks generate bad checksums on writing, which might not be liked by certain readers. * No interlace support yet. * Basic processing is fully supported, unsupported chunks are stored as extra embedded data. +* Currently truecolor images are only supported with up to 8 bit per channel. + +## Windows Bitmap (bmp) + +* All versions are supported with some caveats (e.g. 1.x has no built in palette support, so normal reads are problematic). +* Currently truecolor images are only supported with up to 8 bit per channel. + +# Usage + +## Loading images + +The following example shows how to load a TGA image file: + +```d +File source = File("example.tga"); +Image texture = TGA.load(source); +``` + +The file can be replaced with any other structure that uses the same functions, such as `VFile`, which enables it to load files +from memory e.g. after loading from a package. Be noted that while there may be size reduction in compressing already compressed +image formats, this will have diminishing results in space saved (and might end up with bigger files), and will slow down access +speeds. + +## Accessing data + +Most images should have `imageData` return the imagedata as the interface `IImageData`, which have some basic function that +could be sufficient in certain cases, especially when data must be handled by some other library. + +```d +ARGB8888 backgroundColor = texture.read(0,0); +assert(texture.getPixelFormat == PixelFormat.ARGB8888, "Unsupported pixel format!"); +myFancyTextureLoader(texture.raw.ptr, texture.width, texture.height); +``` + +`IImageData` can be casted to different other types, which enable more operations. However one must be careful with the types, +which can be checked by using the property `pixelFormat`. + +```d +if (indexedImageData.pixelformat == PixelFormat.Indexed) +{ + IndexedImageData!ubyte iif = cast(IndexedImageData!ubyte)indexedImageData; + for (int y ; y < iif.height; ; y++) + { + for (int x ; x < iif.width ; x++) + { + iif[x, y] = 0; + } + } +} +``` + +## Palettes + +If an image format supports palettes, then it can be accessed with the `palette` property. + +The `IPalette` interface provides the `length` property and the `read(size_t pos)` function, which can be used to create a +for loop: + +```d +IPalette pal = indexedImage.palette; +for(size_t i ; i < pal.length ; i++) +{ + //Do things that require reading the palette. +} +``` + +A more complex method is casting the `IPalette` interface into a more exact type, which gives access to the original pixel format, +an `opIndex` function, and basic range functionality: + +```d +Palette!RGBA5551 pal = cast(Palette!RGBA5551)indexedImage.palette; +foreach(colorIndex ; pal) +{ + //Do things with the palette +} +``` # Planned features * Better memory safety. +* Use of floating points for conversion. ## Planned formats -* BMP -* TIFF (requires LZW) +* TIFF (requires LZW and JPEG codec) * GIF (requires LZW) * JPEG (requires codec) diff --git a/source/dimage/pcx.d b/leftout/pcx.d similarity index 99% rename from source/dimage/pcx.d rename to leftout/pcx.d index 08e4a01..d6b05bf 100644 --- a/source/dimage/pcx.d +++ b/leftout/pcx.d @@ -5,7 +5,7 @@ * Copyright under Boost Software License. */ -module dimage.pcx; +module leftout.pcx; import dimage.base; import dimage.util; @@ -15,6 +15,7 @@ import std.bitmanip; /** * Implements ZSoft PCX file handling. + * Dummied out due to complexity. */ public class PCX : Image { /** diff --git a/source/dimage/base.d b/source/dimage/base.d index 895bbc6..9af54b4 100644 --- a/source/dimage/base.d +++ b/source/dimage/base.d @@ -15,12 +15,14 @@ import dimage.util; import bitleveld.datatypes; +public import dimage.types; +public import dimage.exceptions; /** * Interface for accessing metadata within images. * Any metadata that's not supported should return null. */ -public interface ImageMetadata{ +public interface ImageMetadata { public string getID() @safe pure; public string getAuthor() @safe pure; public string getComment() @safe pure; @@ -35,900 +37,1028 @@ public interface ImageMetadata{ public string setSoftwareVersion(string val) @safe pure; } /** - * Interface for common milti-image (eg. animation) functions. + * Interface for common multi-image (eg. animation) functions. */ -public interface Animation { - public uint getCurrentFrame() @safe pure; - public uint setCurrentFrame(uint frame) @safe pure; - public uint nOfFrames() @property @safe @nogc pure; - public uint frameTime() @property @safe @nogc pure; - public bool isAnimation() @property @safe @nogc pure; +public interface MultiImage { + ///Returns which image is being set to be worked on. + public uint getCurrentImage() @safe pure; + ///Sets which image is being set to be worked on. + public uint setCurrentImage(uint frame) @safe pure; + ///Number of images in a given multi-image. + public uint nOfImages() @property @safe @nogc pure const; + ///Returns the frame duration in hmsec if animation for the given frame. + ///Returns 0 if not an animation. + public uint frameTime() @property @safe @nogc pure const; + ///Returns true if the multi-image is animated + public bool isAnimation() @property @safe @nogc pure const; } /** - * All image classes should be derived from this base. - * Implements some basic functionality, such as reading and writing pixels, basic data storage, and basic information. - * Pixeldata should be stored decompressed, but indexing should be preserved on loading with the opinion of upconverting - * to truecolor. + * Basic palette wrapper. */ -abstract class Image{ - //these static fields will be deprecated by version 0.4.0! - protected static const ubyte[2] pixelOrder4BitLE = [0xF0, 0x0F]; - protected static const ubyte[2] pixelOrder4BitBE = [0x0F, 0xF0]; - protected static const ubyte[2] pixelShift4BitLE = [0x04, 0x00]; - protected static const ubyte[2] pixelShift4BitBE = [0x00, 0x04]; - protected static const ubyte[4] pixelOrder2BitLE = [0b1100_0000, 0b0011_0000, 0b0000_1100, 0b0000_0011]; - protected static const ubyte[4] pixelOrder2BitBE = [0b0000_0011, 0b0000_1100, 0b0011_0000, 0b1100_0000]; - protected static const ubyte[4] pixelShift2BitLE = [0x06, 0x04, 0x02, 0x00]; - protected static const ubyte[4] pixelShift2BitBE = [0x00, 0x02, 0x04, 0x06]; - protected static const ubyte[8] pixelOrder1BitLE = [0b1000_0000, 0b0100_0000, 0b0010_0000, 0b0001_0000, - 0b0000_1000, 0b0000_0100, 0b0000_0010, 0b0000_0001]; - protected static const ubyte[8] pixelOrder1BitBE = [0b0000_0001, 0b0000_0010, 0b0000_0100, 0b0000_1000, - 0b0001_0000, 0b0010_0000, 0b0100_0000, 0b1000_0000]; - protected static const ubyte[8] pixelShift1BitLE = [0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00]; - protected static const ubyte[8] pixelShift1BitBE = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]; - /** - * Can be used for foreach through all palette indicles. - * Might replace the old method of safe accessing the palette. - */ - //struct PaletteRange { - class PaletteRange : InputRange!Pixel32Bit { - protected size_t pos; ///Indicates the current position in the palette - protected size_t paletteBitdepth; ///Bitdepth of the palette's indicles - protected PixelFormat format; ///Format of the palette - protected ubyte[] _palette; ///The source that is being read - ///CTOR - this (size_t paletteBitdepth, PixelFormat format, ubyte[] _palette) @safe { - //writeln(0); - assert(paletteBitdepth); - this.paletteBitdepth = paletteBitdepth; - this.format = format; - assert(_palette); - this._palette = _palette; - } - ///Returns the current element - @property Pixel32Bit front() pure @safe { - const size_t offset = pos * (paletteBitdepth / 8); - ubyte[] colorInput = _palette[offset..offset + (paletteBitdepth / 8)]; - Pixel32Bit colorOutput; - switch (format) { - case PixelFormat.ARGB8888, PixelFormat.XRGB8888: - colorOutput = reinterpretGet!Pixel32Bit(colorInput); - break; - case PixelFormat.RGBA8888, PixelFormat.RGBX8888: - colorOutput = Pixel32Bit(reinterpretGet!Pixel32BitRGBALE(colorInput)); - break; - case PixelFormat.RGBA5551, PixelFormat.RGBX5551: - colorOutput = Pixel32Bit(reinterpretGet!PixelRGBA5551(colorInput)); - break; - case PixelFormat.RGB565: - colorOutput = Pixel32Bit(reinterpretGet!PixelRGB565(colorInput)); - break; - case PixelFormat.RGB888: - colorOutput = Pixel32Bit(reinterpretGet!Pixel24Bit(colorInput)); - break; - default: - throw new ImageFormatException("Unknown format"); - } - return colorOutput; - } - ///Returns the first element and increases the position by one - Pixel32Bit moveFront() pure @safe { - Pixel32Bit colorOutput = front; - popFront; - return colorOutput; - } - ///Increases the index by one if the end hasn't been reached. - void popFront() pure @safe @nogc nothrow { - if (_palette.length != pos * (paletteBitdepth / 8)) pos++; - } - ///Returns true if the end has been reached. - @property bool empty() pure @safe @nogc nothrow { - return _palette.length == pos * (paletteBitdepth / 8); - } - ///Used for random access. - Pixel32Bit opIndex(size_t index) pure @safe { - const size_t offset = index * (paletteBitdepth / 8); - ubyte[] colorInput = _palette[offset..offset + (paletteBitdepth / 8)]; - Pixel32Bit colorOutput; - switch (format) { - case PixelFormat.ARGB8888, PixelFormat.XRGB8888: - colorOutput = reinterpretGet!Pixel32Bit(colorInput); - break; - case PixelFormat.RGBA8888, PixelFormat.RGBX8888: - colorOutput = Pixel32Bit(reinterpretGet!Pixel32BitRGBALE(colorInput)); - break; - case PixelFormat.RGBA5551, PixelFormat.RGBX5551: - colorOutput = Pixel32Bit(reinterpretGet!PixelRGBA5551(colorInput)); - break; - case PixelFormat.RGB565: - colorOutput = Pixel32Bit(reinterpretGet!PixelRGB565(colorInput)); - break; - case PixelFormat.RGB888: - colorOutput = Pixel32Bit(reinterpretGet!Pixel24Bit(colorInput)); - break; - default: - throw new ImageFormatException("Unknown format"); - } - return colorOutput; - } - ///Returns the length of the palette - @property size_t length() pure @safe @nogc nothrow { - return _palette.length / (paletteBitdepth / 8); - } - /// - alias opDollar = length; - /**`foreach` iteration uses opApply, since one delegate call per loop - * iteration is faster than three virtual function calls. - */ - int opApply(scope int delegate(Pixel32Bit) dlg){ - if (empty) return 0; - else { - return (dlg(moveFront)); - } +public interface IPalette { + ///Returns the number of indexes within the palette. + public @property size_t length() @nogc @safe pure nothrow const; + ///Returns the bitdepth of the palette. + public @property ubyte bitDepth() @nogc @safe pure nothrow const; + ///Returns the color format of the palette. + public @property uint paletteFormat() @nogc @safe pure nothrow const; + ///Converts the palette to the given format if supported + public IPalette convTo(uint format) @safe; + ///Reads palette in standard format + public ARGB8888 read(size_t index) @safe pure; + ///Returns the raw data cast to ubyte + public ubyte[] raw() @safe pure; +} +/** + * Contains palette information. + * Implements some range capabilities. + */ +public class Palette(T) : IPalette { + protected T[] data; ///Raw data + //protected size_t _length;///The number of items in the palette (should be less than 65536) + protected size_t begin, end; + protected uint format; + protected ubyte _bitDepth; + ///CTOR + this(T[] data, uint format, ubyte bitDepth) @nogc @safe pure nothrow { + this.data = data; + this.format = format; + _bitDepth = bitDepth; + end = data.length; + } + ///Copy CTOR + this(Palette!T src) @nogc @safe pure nothrow { + data = src.data; + format = src.format; + _bitDepth = src._bitDepth; + end = data.length; + } + ///Returns the number of indexes within the palette. + public @property size_t length() @nogc @safe pure nothrow const { + return data.length; + } + ///Returns the bitdepth of the palette. + public @property ubyte bitDepth() @nogc @safe pure nothrow const { + return _bitDepth; + } + ///Returns the color format of the palette. + public @property uint paletteFormat() @nogc @safe pure nothrow const { + return format; + } + ///Converts the palette to the given format if supported. + public IPalette convTo(uint format) @safe { + IPalette result; + void converter(OutputType)() @safe { + OutputType[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) + array[i] = OutputType(data[i]); + result = new Palette!OutputType(array, format, getBitDepth(format)); } - - /// Ditto - int opApply(scope int delegate(size_t, Pixel32Bit) dlg){ - if (empty) return 0; - else { - return (dlg(pos, moveFront)); - } + switch (format & !(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) { + case PixelFormat.RGB888: + if(format & PixelFormat.BigEndian) + converter!(RGB888BE); + else + converter!(RGB888); + break; + case PixelFormat.RGBX8888: + if(format & PixelFormat.BigEndian) + converter!(RGBA8888BE); + else + converter!(RGBA8888); + break; + case PixelFormat.XRGB8888: + if(format & PixelFormat.BigEndian) + converter!(ARGB8888BE); + else + converter!(ARGB8888); + break; + case PixelFormat.RGB565: + converter!(RGB565); + break; + case PixelFormat.RGBX5551: + converter!(RGBA5551); + break; + default: + throw new ImageFormatException("Format not supported"); } + return result; } - /** - * Raw image data. Cast the data to whatever data you need at the moment. - * Data less than 8 bit should have each scanline to be padded to byte boundary. - */ - protected ubyte[] imageData; - /** - * Raw palette data. Null if image is not indexed. - */ - protected ubyte[] paletteData; - - protected ubyte mod; ///used for fast access of indexes DEPRECATED! - protected ubyte shift; ///used for fast access of indexes DEPRECATED! - - protected @safe pure ubyte delegate(uint x, uint y) indexReader8Bit; ///Used for bypassing typechecking when reading pixels - protected @safe pure ushort delegate(uint x, uint y) indexReader16bit; ///Used for bypassing typechecking when reading pixels - protected @safe pure ubyte delegate(uint x, uint y, ubyte val) indexWriter8Bit; ///Used for bypassing typechecking when writing pixels - protected @safe pure ushort delegate(uint x, uint y, ushort val) indexWriter16bit; ///Used for bypassing typechecking when writing pixels - protected @safe pure Pixel32Bit delegate(uint x, uint y) pixelReader; //Used for bypassing typechecking - protected @safe pure Pixel32Bit delegate(ushort i) paletteReader; //Used for bypassing typechecking - - /+protected uint pitch; ///Contains the precalculated scanline size with the occassional padding for 8bit values.+/ - - abstract uint width() @nogc @safe @property const pure; - abstract uint height() @nogc @safe @property const pure; - abstract bool isIndexed() @nogc @safe @property const pure; - abstract ubyte getBitdepth() @nogc @safe @property const pure; - abstract ubyte getPaletteBitdepth() @nogc @safe @property const pure; - abstract uint getPixelFormat() @nogc @safe @property const pure; - abstract uint getPalettePixelFormat() @nogc @safe @property const pure; - /** - * Returns the number of planes the image have. - * If bitdepth is 1, then the image is a planar indexed image. - */ - public ubyte getBitplanes() @nogc @safe @property const pure { - return 1; + ///Returns the raw data as an array. + public T[] getRawData() @nogc @safe pure nothrow { + return data; } - /** - * Returns a palette range, which can be used to read the palette. - */ - public PaletteRange palette() @safe /+ @property pure+/ { - return new PaletteRange(getPaletteBitdepth, cast(PixelFormat)getPalettePixelFormat, paletteData); + ///Reads palette in standard format. + public ARGB8888 read(size_t index) @safe pure { + if (index < data.length) return ARGB8888(data[index]); + else throw new PaletteBoundsException("Palette is being read out of bounds!"); } - /** - * Returns the pixel order for bitdepths less than 8. Almost excusively used for indexed bitmaps. - * Returns null if ordering not needed. - * Will be deprecated by version 0.4.0! - */ - deprecated public ubyte[] getPixelOrder() @safe @property const pure { - return []; + ///Palette indexing. + public ref T opIndex(size_t index) @safe pure { + if (index < data.length) return data[index]; + else throw new PaletteBoundsException("Palette is being read out of bounds!"); } - /** - * Returns which pixel how much needs to be shifted right after a byteread. - * Will be deprecated by version 0.4.0! - */ - deprecated public ubyte[] getPixelOrderBitshift() @safe @property const pure { - return []; + /+ + ///Assigns a value to the given index + public T opIndexAssign(T value, size_t index) @safe pure { + if (index < data.length) return data[index] = value; + else throw new PaletteBoundsException("Palette is being read out of bounds!"); + }+/ + ///Returns true if the range have reached its end + public @property bool empty() @nogc @safe pure nothrow const { + return begin == end; } - - /* - * Various internal functions for pixel reading can be found here. - * Use these through delegates to enable external access. - */ - ///8Bit indexed/monochrome reading - protected ubyte _readPixel_8bit (uint x, uint y) @safe pure { - return imageData[x + (width * y)]; + ///Returns the element at the front + public ref T front() @nogc @safe pure nothrow { + return data[begin]; } - ///16Bit indexed reading - protected ushort _readPixel_16bit (uint x, uint y) @safe pure { - return reinterpretCast!ushort(imageData)[x + (width * y)]; + alias opDollar = length; + ///Moves the front pointer forward by one + public void popFront() @nogc @safe pure nothrow { + if (begin != end) begin++; } - ///Template for reading pixels in a given format - protected PixelType _readPixel (PixelType) (uint x, uint y) @safe pure { - return reinterpretCast!PixelType(imageData)[x + (width * y)]; + ///Moves the front pointer forward by one and returns the element + public ref T moveFront() @nogc @safe pure nothrow { + if (begin != end) return data[begin++]; + else return data[begin]; } - ///Template for upconversion - protected Pixel32Bit _readPixelAndUpconv (SourceType) (uint x, uint y) @safe pure { - return Pixel32Bit(reinterpretCast!SourceType(imageData)[x + (width * y)]); + ///Returns the element at the back + public ref T back() @nogc @safe pure nothrow { + return data[end - 1]; } - ///Read and lookup function - protected Pixel32Bit _readAndLookup (uint x, uint y) @safe pure { - return readPalette(indexReader16bit(x, y)); + ///Moves the back pointer backward by one + public void popBack() @nogc @safe pure nothrow { + if (begin != end) end--; } - ///Reroutes 8 bit lookup into 16 bit delegates - protected ushort _indexReadUpconv (uint x, uint y) @safe pure { - return indexReader8Bit(x, y); + ///Moves the back pointer backward and returns the element + public ref T moveBack() @nogc @safe pure nothrow { + if (begin != end) return data[--end]; + else return data[end]; } - ///Palette upconversion template - protected Pixel32Bit _readPaletteAndUpconv (SourceType) (ushort index) @safe pure { - return Pixel32Bit(reinterpretCast!SourceType(paletteData)[index]); + ///Creates a copy with the front and back pointers reset + public Palette!T save() @safe pure nothrow { + return new Palette!T(this); } - ///Template for writing pixels in a given format - protected PixelType _writePixel (PixelType) (uint x, uint y, PixelType val) @safe pure { - return reinterpretCast!PixelType(imageData)[x + width * y] = val; + ///Returns the raw data cast to ubyte + public ubyte[] raw() @safe pure { + return reinterpretCast!ubyte(data); + } +} +/** + * Basic imagedata wrapper. + */ +public interface IImageData { + ///Returns the width of the image. + public @property uint width() @nogc @safe pure nothrow const; + ///Returns the height of the image. + public @property uint height() @nogc @safe pure nothrow const; + ///Returns the bitdepth of the image. + public @property ubyte bitDepth() @nogc @safe pure nothrow const; + ///Returns the number of bitplanes per image. + ///Default should be 1. + public @property ubyte bitplanes() @nogc @safe pure nothrow const; + ///Returns the color format of the image. + public @property uint pixelFormat() @nogc @safe pure nothrow const; + ///Converts the imagedata to the given format if supported + public IImageData convTo(uint format) @safe; + ///Reads the image at the given point in ARGB32 format. + ///Does palette lookup if needed. + public ARGB8888 read(uint x, uint y) @safe pure; + ///Flips the image horizontally + public void flipHorizontal() @safe pure; + ///Flips the image vertically + public void flipVertical() @safe pure; + ///Returns the raw data cast to ubyte + public ubyte[] raw() @safe pure; +} +/** + * Imagedata container. + */ +public class ImageData(T) : IImageData { + protected T[] data; + protected uint _width, _height, _pixelFormat; + protected ubyte _bitDepth; + ///CTOR + public this(T[] data, uint _width, uint _height, uint _pixelFormat, ubyte _bitDepth) { + //assert(data.length == _width * _height); + this.data = data; + this._width = _width; + this._height = _height; + this._pixelFormat = _pixelFormat; + this._bitDepth = _bitDepth; + } + ///Returns the raw data + public @property T[] getData() @nogc @safe pure nothrow { + return data; + } + ///Returns the raw data cast to ubyte + public ubyte[] raw() @safe pure { + return reinterpretCast!ubyte(data); + } + ///Returns the width of the image. + public @property uint width() @nogc @safe pure nothrow const { + return _width; + } + ///Returns the height of the image. + public @property uint height() @nogc @safe pure nothrow const { + return _height; + } + ///Returns the bitdepth of the image. + public @property ubyte bitDepth() @nogc @safe pure nothrow const { + return _bitDepth; } - /** - * Reads a single 32bit pixel. If the image is indexed, a color lookup will be done. - */ - public Pixel32Bit readPixel(uint x, uint y) @safe pure { - if(x >= width || y >= height || x < 0 || y < 0){ - throw new ImageBoundsException("Image is being read out of bounds"); - } - return pixelReader(x, y); + public @property ubyte bitplanes() @nogc @safe pure nothrow const { + return 1; } - /** - * Reads the given type of pixel from the image. - * TODO: Check for format validity - */ - public T readPixel(T)(uint x, uint y) @safe pure { - if(x >= width || y >= height || x < 0 || y < 0){ - throw new ImageBoundsException("Image is being read out of bounds"); - } - if(isIndexed){ - const ushort index = readPixelIndex!ushort(x, y); - return readPalette!(T)(index); - }else{ - //TODO: Check for format validity - T data = reinterpretCast!T(imageData)[x + y * width]; - return data; - } + + public @property uint pixelFormat() @nogc @safe pure nothrow const { + return _pixelFormat; } - /** - * Reads an index, if the image isn't indexed throws an ImageFormatException. - */ - public T readPixelIndex(T = ubyte)(uint x, uint y) @safe pure - if(T.stringof == ushort.stringof || T.stringof == ubyte.stringof) { - if(x >= width || y >= height){ - throw new ImageBoundsException("Image is being read out of bounds!"); - } - if(!isIndexed){ - throw new ImageFormatException("Image isn't indexed!"); + + public IImageData convTo(uint format) @safe { + IImageData result; + void converter(OutputType)() @safe { + OutputType[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) + array[i] = OutputType(data[i]); + result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format)); } - static if(T.stringof == ubyte.stringof){ - if (indexReader8Bit !is null) { - return indexReader8Bit (x, y); + void monochrome8bitConverter() @safe { + static if (is(T == ubyte)) { + result = new ImageData!ubyte(data, _width, _height, format, getBitDepth(format)); } else { - throw new ImageFormatException("Indexed image cannot be read with 8bit values."); + ubyte[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) + array[i] = cast(ubyte)((data[i].r + data[i].g + data[i].b) / 3); + result = new ImageData!ubyte(array, _width, _height, format, getBitDepth(format)); } - }else static if(T.stringof == ushort.stringof){ - if (indexReader16bit !is null) { - return indexReader16bit (x, y); - } else { - throw new ImageFormatException("Indexed image cannot be read with 16bit values."); + } + switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) { + case PixelFormat.Grayscale8Bit: + monochrome8bitConverter; + break; + case PixelFormat.YX88: + if(format & PixelFormat.BigEndian) + converter!(YA88BE); + else + converter!(YA88); + break; + case PixelFormat.RGB888: + if(format & PixelFormat.BigEndian) + converter!(RGB888BE); + else + converter!(RGB888); + break; + case PixelFormat.RGBX8888: + if(format & PixelFormat.BigEndian) + converter!(RGBA8888BE); + else + converter!(RGBA8888); + break; + case PixelFormat.XRGB8888: + if(format & PixelFormat.BigEndian) + converter!(ARGB8888BE); + else + converter!(ARGB8888); + break; + case PixelFormat.RGB565: + converter!(RGB565); + break; + case PixelFormat.RGBX5551: + converter!(RGBA5551); + break; + default: + throw new ImageFormatException("Format not supported"); + } + return result; + } + public ARGB8888 read(uint x, uint y) @safe pure { + if(x < _width && y < _height) return ARGB8888(data[x + (y * _width)]); + else throw new ImageBoundsException("Image is being read out of bounds!"); + } + public ref T opIndex(uint x, uint y) @safe pure { + if(x < _width && y < _height) return data[x + (y * _width)]; + else throw new ImageBoundsException("Image is being read out of bounds!"); + } + ///Flips the image horizontally + public void flipHorizontal() @safe pure { + for (uint y ; y < _height ; y++) { + for (uint x ; x < _width / 2 ; x++) { + const T tmp = opIndex(x, y); + opIndex(x, y) = opIndex(_width - x, y); + opIndex(_width - x, y) = tmp; } - }else static assert(0, "Use either ubyte or ushort!"); + } } - /** - * Looks up the index on the palette, then returns the color value as a 32 bit value. - */ - public Pixel32Bit readPalette(ushort index) @safe pure { - if(!isIndexed) - throw new ImageFormatException("Image isn't indexed!"); - return paletteReader(index); - /+final switch(getPixelFormat){ - case 8: - if(index > paletteData.length) - throw new PaletteBoundsException("Palette index is too high!"); - const ubyte data = paletteData[index]; - return Pixel32Bit(data, data, data, 0xFF); - case 16: - if(index<<1 > paletteData.length) - throw new PaletteBoundsException("Palette index is too high!"); - PixelRGBA5551 data = reinterpretCast!PixelRGBA5551(paletteData)[index]; - return Pixel32Bit(data); - case 24: - if(index * 3 > paletteData.length) - throw new PaletteBoundsException("Palette index is too high!"); - Pixel24Bit data = reinterpretCast!Pixel24Bit(paletteData)[index]; - return Pixel32Bit(data); - case 32: - if(index<<2 > paletteData.length) - throw new PaletteBoundsException("Palette index is too high!"); - Pixel32Bit data = reinterpretCast!Pixel32Bit(paletteData)[index]; - return data; - }+/ + ///Flips the image vertically + public void flipVertical() @safe pure { + import std.algorithm.mutation : swapRanges; + for (uint y ; y < _height / 2 ; y++) { + const uint y0 = _height - y - 1; + T[] a = data[(y * _width)..((y + 1) * _width)]; + T[] b = data[(y0 * _width)..((y0 + 1) * _width)]; + swapRanges(a, b); + } } - /** - * Template for index reading. - */ - public Pixel32Bit _paletteReader(T)(ushort index) @safe pure { - return Pixel32Bit(reinterpretCast!T(paletteData)[index]); +} +/** + * Indexed imagedata container for ubyte and ushort based formats + */ +public class IndexedImageData (T) : IImageData { + protected T[] data; + public IPalette palette; + protected uint _width, _height; + ///CTOR + public this(T[] data, IPalette palette, uint _width, uint _height) { + this.data = data; + this.palette = palette; + this._width = _width; + this._height = _height; } - /** - * Looks up the index on the palette, then returns the color value in the requested format. - */ - public T readPalette(T)(ushort index) @safe pure { - if(!isIndexed) - throw new ImageFormatException("Image isn't indexed!"); - if(T.sizeof * 8 != getPaletteBitdepth) - throw new ImageFormatException("Palette format mismatch!"); - if(paletteData.length / T.sizeof <= index) - throw new PaletteBoundsException("Palette index is too high!"); - T data = reinterpretCast!T(paletteData)[index]; + ///Returns the raw data + public @property T[] getData() @nogc @safe pure nothrow { return data; } - /** - * Writes a single pixel. - * ubyte: most indexed formats. - * ushort: all 16bit indexed formats. - * Any other pixel structs are used for direct color. - */ - public T writePixel(T)(uint x, uint y, T pixel) @safe pure { - if(x >= width || y >= height) - throw new ImageBoundsException("Image is being written out of bounds!"); - - static if(T.stringof == ubyte.stringof || T.stringof == ushort.stringof){ - /*if(!isIndexed) - throw new ImageFormatException("Image isn't indexed!");*/ - - static if(T.stringof == ubyte.stringof) - if(getBitdepth == 16) - throw new ImageFormatException("Image cannot be written as 8 bit!"); - static if(T.stringof == ushort.stringof) - if(getBitdepth <= 8) - throw new ImageFormatException("Image cannot be written as 16 bit!"); - return reinterpretCast!T(imageData)[x + (y * width)] = pixel; - }else{ - T[] pixels = reinterpretCast!T(imageData); - if(T.sizeof != getBitdepth / 8) - throw new ImageFormatException("Image format mismatch exception"); - return pixels[x + (y * width)] = pixel; - } + ///Returns the raw data cast to ubyte + public ubyte[] raw() @safe pure { + return reinterpretCast!ubyte(data); } - /** - * Writes to the palette. - */ - /** - * Returns the raw image data. - */ - public ubyte[] getImageData() @nogc @safe nothrow pure { - return imageData; + ///Returns the width of the image. + public @property uint width() @nogc @safe pure nothrow const { + return _width; } - /** - * Returns the raw palette data. - */ - public ubyte[] getPaletteData() @nogc @safe nothrow pure { - return paletteData; + ///Returns the height of the image. + public @property uint height() @nogc @safe pure nothrow const { + return _height; } - /** - * Flips the image on the vertical axis. Useful to set images to the correct top-left screen origin point. - */ - public void flipVertical() @safe pure { - import std.algorithm.mutation : swapRanges; - //header.topOrigin = !header.topOrigin; - const size_t workLength = (width * getBitdepth)>>3; - for(int y ; y < height>>1 ; y++){ - const int rev = height - y; - swapRanges(imageData[workLength * y..workLength * (y + 1)], imageData[workLength * (rev - 1)..workLength * rev]); - } + + public @property ubyte bitDepth() @nogc @safe pure nothrow const { + static if(is(T == ubyte)) return 8; + else return 16; } - /** - * Flips the image on the vertical axis. Useful to set images to the correct top-left screen origin point. - */ - public void flipHorizontal() @safe pure { - if(isIndexed){ - if(getBitdepth == 16){ - for(int y ; y < height ; y++){ - for(int x ; x < width>>1 ; x++){ - const int x0 = width - x; - const ushort temp = readPixelIndex!(ushort)(x, y); - writePixel!(ushort)(x, y, readPixelIndex!(ushort)(x0, y)); - writePixel!(ushort)(x0, y, temp); - } - } - }else{ - for(int y ; y < height ; y++){ - for(int x ; x < width>>1 ; x++){ - const int x0 = width - x; - const ubyte temp = readPixelIndex!(ubyte)(x, y); - writePixel!(ubyte)(x, y, readPixelIndex!(ubyte)(x0, y)); - writePixel!(ubyte)(x0, y, temp); - } - } - } - }else{ - final switch(getBitdepth){ - case 8: - for(int y ; y < height ; y++){ - for(int x ; x < width>>1 ; x++){ - const int x0 = width - x; - const ubyte temp = readPixel!(ubyte)(x, y); - writePixel!(ubyte)(x, y, readPixel!(ubyte)(x0, y)); - writePixel!(ubyte)(x0, y, temp); - } - } - break; - case 16: - for(int y ; y < height ; y++){ - for(int x ; x < width>>1 ; x++){ - const int x0 = width - x; - const ushort temp = readPixel!(ushort)(x, y); - writePixel!(ushort)(x, y, readPixel!(ushort)(x0, y)); - writePixel!(ushort)(x0, y, temp); - } - } - break; - case 24: - for(int y ; y < height ; y++){ - for(int x ; x < width>>1 ; x++){ - const int x0 = width - x; - const Pixel24Bit temp = readPixel!(Pixel24Bit)(x, y); - writePixel!(Pixel24Bit)(x, y, readPixel!(Pixel24Bit)(x0, y)); - writePixel!(Pixel24Bit)(x0, y, temp); - } - } - break; - case 32: - for(int y ; y < height ; y++){ - for(int x ; x < width>>1 ; x++){ - const int x0 = width - x; - const Pixel32Bit temp = readPixel!(Pixel32Bit)(x, y); - writePixel!(Pixel32Bit)(x, y, readPixel!(Pixel32Bit)(x0, y)); - writePixel!(Pixel32Bit)(x0, y, temp); - } - } - break; + + public @property ubyte bitplanes() @nogc @safe pure nothrow const { + return 1; + } + + public @property uint pixelFormat() @nogc @safe pure nothrow const { + static if(is(T == ubyte)) return PixelFormat.Indexed8Bit; + else return PixelFormat.Indexed16Bit; + } + + public IImageData convTo(uint format) @safe { + IImageData result; + void converter(OutputType)() @safe { + OutputType[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) + array[i] = OutputType(palette.read(data[i])); + result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format)); + } + void monochrome8bitConverter() @safe { + ubyte[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) { + const auto c = palette.read(data[i]); + array[i] = cast(ubyte)((c.r + c.g + c.b) / 3); } + result = new ImageData!ubyte(array, _width, _height, format, getBitDepth(format)); + } + void upconv(OutputType)() @safe { + OutputType[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) + array[i] = data[i]; + result = new IndexedImageData!OutputType(array, palette, _width, _height); + } + switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) { + case PixelFormat.Indexed16Bit: + upconv!ushort; + break; + case PixelFormat.Grayscale8Bit: + monochrome8bitConverter; + break; + case PixelFormat.YX88: + if(format & PixelFormat.BigEndian) + converter!(YA88BE); + else + converter!(YA88); + break; + case PixelFormat.RGB888: + if(format & PixelFormat.BigEndian) + converter!(RGB888BE); + else + converter!(RGB888); + break; + case PixelFormat.RGBX8888: + if(format & PixelFormat.BigEndian) + converter!(RGBA8888BE); + else + converter!(RGBA8888); + break; + case PixelFormat.XRGB8888: + if(format & PixelFormat.BigEndian) + converter!(ARGB8888BE); + else + converter!(ARGB8888); + break; + case PixelFormat.RGB565: + converter!(RGB565); + break; + case PixelFormat.RGBX5551: + converter!(RGBA5551); + break; + default: + throw new ImageFormatException("Format not supported"); } + return result; } - ///Returns true if the image originates from the top - public bool topOrigin() @property @nogc @safe pure const { - return false; + public ARGB8888 read(uint x, uint y) @safe pure { + if(x < _width && y < _height) return palette.read(data[x + (y * _width)]); + else throw new ImageBoundsException("Image is being read out of bounds!"); } - ///Returns true if the image originates from the right - public bool rightSideOrigin() @property @nogc @safe pure const { - return false; + public ref T opIndex(uint x, uint y) @safe pure { + if(x < _width && y < _height) return data[x + (y * _width)]; + else throw new ImageBoundsException("Image is being read out of bounds!"); } -} -/** - * Mixin template for planar readers and writers - */ -mixin template PlanarAccess3Bit () { - protected Bitplane!3 bitplanes3bit; - //Bitplane!i mixin("bitplane" ~ i); - protected ubyte _readIndex_planar_3bit (uint x, uint y) @safe pure { - ubyte internalWrapper() @trusted pure { - return bitplanes3bit[x + y * pitch]; + ///Flips the image horizontally + public void flipHorizontal() @safe pure { + for (uint y ; y < _height ; y++) { + for (uint x ; x < _width>>>1 ; x++) { + const T tmp = opIndex(x, y); + opIndex(x, y) = opIndex(_width - x, y); + opIndex(_width - x, y) = tmp; + } } - return internalWrapper(); } - protected ubyte _writeIndex_planar_3bit (uint x, uint y, ubyte val) @safe pure { - ubyte internalWrapper() @trusted pure { - return bitplanes3bit[x + y * pitch] = val; + ///Flips the image vertically + public void flipVertical() @safe pure { + import std.algorithm.mutation : swapRanges; + for (uint y ; y < _height / 2 ; y++) { + const uint y0 = _height - y - 1; + T[] a = data[(y * _width)..((y + 1) * _width)]; + T[] b = data[(y0 * _width)..((y0 + 1) * _width)]; + swapRanges(a, b); } - return internalWrapper(); } } /** - * Mixin template for planar readers and writers + * 4 Bit indexed image data. */ -mixin template PlanarAccess4Bit () { - protected Bitplane!4 bitplanes4bit; - //Bitplane!i mixin("bitplane" ~ i); - protected ubyte _readIndex_planar_4bit (uint x, uint y) @safe pure { - ubyte internalWrapper() @trusted pure { - return bitplanes4bit[x + y * pitch]; +public class IndexedImageData4Bit : IImageData { + protected ubyte[] data; + protected NibbleArray accessor; + public IPalette palette; + protected uint _width, _height, _pitch; + ///CTOR + public this(ubyte[] data, IPalette _alette, uint _width, uint _height) { + this.data = data; + this.palette = palette; + this._width = _width; + this._height = _height; + _pitch = _width + (_width % 2); + accessor = NibbleArray(data, _pitch * _height); + } + ///Returns the raw data + public @property NibbleArray getData() @nogc @safe pure nothrow { + return accessor; + } + ///Returns the raw data cast to ubyte + public ubyte[] raw() @safe pure { + return data; + } + ///Returns the width of the image. + public @property uint width() @nogc @safe pure nothrow const { + return _width; + } + ///Returns the height of the image. + public @property uint height() @nogc @safe pure nothrow const { + return _height; + } + + public @property ubyte bitDepth() @nogc @safe pure nothrow const { + return 4; + } + + public @property ubyte bitplanes() @nogc @safe pure nothrow const { + return 1; + } + + public @property uint pixelFormat() @nogc @safe pure nothrow const { + return PixelFormat.Indexed4Bit; + } + + public IImageData convTo(uint format) @safe { + IImageData result; + void converter(OutputType)() @safe { + OutputType[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) + array[i] = OutputType(palette.read(data[i])); + result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format)); + } + void monochrome8bitConverter() @safe { + ubyte[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) { + const auto c = palette.read(data[i]); + array[i] = cast(ubyte)((c.r + c.g + c.b) / 3); + } + result = new ImageData!ubyte(array, _width, _height, format, getBitDepth(format)); + } + void upconv(OutputType)() @safe { + OutputType[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) + array[i] = data[i]; + result = new IndexedImageData!OutputType(array, palette, _width, _height); } - return internalWrapper(); + switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) { + case PixelFormat.Indexed16Bit: + upconv!ushort; + break; + case PixelFormat.Indexed8Bit: + upconv!ubyte; + break; + case PixelFormat.Grayscale8Bit: + monochrome8bitConverter; + break; + case PixelFormat.YX88: + if(format & PixelFormat.BigEndian) + converter!(YA88BE); + else + converter!(YA88); + break; + case PixelFormat.RGB888: + if(format & PixelFormat.BigEndian) + converter!(RGB888BE); + else + converter!(RGB888); + break; + case PixelFormat.RGBX8888: + if(format & PixelFormat.BigEndian) + converter!(RGBA8888BE); + else + converter!(RGBA8888); + break; + case PixelFormat.XRGB8888: + if(format & PixelFormat.BigEndian) + converter!(ARGB8888BE); + else + converter!(ARGB8888); + break; + case PixelFormat.RGB565: + converter!(RGB565); + break; + case PixelFormat.RGBX5551: + converter!(RGBA5551); + break; + default: + throw new ImageFormatException("Format not supported"); + } + return result; + } + public ARGB8888 read(uint x, uint y) @safe pure { + if(x < _width && y < _height) return palette.read(data[x + (y * _pitch)]); + else throw new ImageBoundsException("Image is being read out of bounds!"); + } + public ubyte opIndex(uint x, uint y) @safe pure { + if(x < _width && y < _height) return data[x + (y * _pitch)]; + else throw new ImageBoundsException("Image is being read out of bounds!"); } - protected ubyte _writeIndex_planar_4bit (uint x, uint y, ubyte val) @safe pure { - ubyte internalWrapper() @trusted pure { - return bitplanes4bit[x + y * pitch] = val; + public ubyte opIndexAssign(uint x, uint y, ubyte val) @safe pure { + if(x < _width && y < _height) return data[x + (y * _pitch)] = val; + else throw new ImageBoundsException("Image is being read out of bounds!"); + } + ///Flips the image horizontally + public void flipHorizontal() @safe pure { + for (uint y ; y < _height ; y++) { + for (uint x ; x < _width>>>1 ; x++) { + const ubyte tmp = opIndex(x, y); + opIndexAssign(x, y, opIndex(_width - x, y)); + opIndexAssign(_width - x, y, tmp); + } + } + } + ///Flips the image vertically + public void flipVertical() @safe pure { + import std.algorithm.mutation : swapRanges; + for (uint y ; y < _height / 2 ; y++) { + const uint y0 = _height - y - 1; + ubyte[] a = data[(y * _pitch/2)..((y + 1) * _pitch/2)]; + ubyte[] b = data[(y0 * _pitch/2)..((y0 + 1) * _pitch/2)]; + swapRanges(a, b); } - return internalWrapper(); } } /** - * Mixin template for 4 bit indexed image + * 2 Bit indexed image data. */ -mixin template ChunkyAccess4Bit () { - protected NibbleArray chunks4bit; - protected ubyte _readIndex_4bit (uint x, uint y) @safe pure { - return chunks4bit[x + y * pitch]; +public class IndexedImageData2Bit : IImageData { + protected ubyte[] data; + protected QuadArray accessor; + public IPalette palette; + protected uint _width, _height, _pitch; + ///CTOR + public this(ubyte[] data, IPalette palette, uint _width, uint _height) { + this.data = data; + this.palette = palette; + this._width = _width; + this._height = _height; + _pitch = _width; + _pitch += width % 4 ? 4 - width % 4 : 0; + accessor = QuadArray(data, _pitch * _height); } - protected ubyte _writeIndex_4bit (uint x, uint y, ubyte val) @safe pure { - return chunks4bit[x + y * pitch] = val; + ///Returns the raw data + public @property QuadArray getData() @nogc @safe pure nothrow { + return accessor; } -} -/** - * Mixin template for 4 bit indexed image - */ -mixin template ChunkyAccess4BitR () { - protected NibbleArrayR chunks4bitR; - protected ubyte _readIndex_4bitR (uint x, uint y) @safe pure { - return chunks4bit[x + y * pitch]; + ///Returns the raw data cast to ubyte + public ubyte[] raw() @safe pure { + return data; } - protected ubyte _writeIndex_4bitR (uint x, uint y, ubyte val) @safe pure { - return chunks4bit[x + y * pitch] = val; + ///Returns the width of the image. + public @property uint width() @nogc @safe pure nothrow const { + return _width; } -} -/** - * Mixin template for 2 bit indexed image - */ -mixin template ChunkyAccess2Bit () { - protected QuadArray chunks2bit; - protected ubyte _readIndex_2bit (uint x, uint y) @safe pure { - return chunks2bit[x + y * pitch]; + ///Returns the height of the image. + public @property uint height() @nogc @safe pure nothrow const { + return _height; } - protected ubyte _writeIndex_2bit (uint x, uint y, ubyte val) @safe pure { - return chunks2bit[x + y * pitch] = val; + + public @property ubyte bitDepth() @nogc @safe pure nothrow const { + return 2; } -} -/** - * Mixin template for 2 bit indexed image - */ -mixin template ChunkyAccess2BitR () { - protected QuadArrayR chunks2bitR; - protected ubyte _readIndex_2bitR (uint x, uint y) @safe pure { - return chunks2bit[x + y * pitch]; + + public @property ubyte bitplanes() @nogc @safe pure nothrow const { + return 1; } - protected ubyte _writeIndex_2bitR (uint x, uint y, ubyte val) @safe pure { - return chunks2bit[x + y * pitch] = val; + + public @property uint pixelFormat() @nogc @safe pure nothrow const { + return PixelFormat.Indexed2Bit; } -} -/** - * Mixin template for 1 bit monochrome image - */ -mixin template MonochromeAccess () { - protected BitArray bitArray; - protected ubyte _readIndex_1bit (uint x, uint y) @safe pure { - ubyte internalWrapper() @trusted pure { - return bitArray[x + y * pitch]; + + public IImageData convTo(uint format) @safe { + IImageData result; + void converter(OutputType)() @safe { + OutputType[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) + array[i] = OutputType(palette.read(data[i])); + result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format)); + } + void monochrome8bitConverter() @safe { + ubyte[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) { + const auto c = palette.read(data[i]); + array[i] = cast(ubyte)((c.r + c.g + c.b) / 3); + } + result = new ImageData!ubyte(array, _width, _height, format, getBitDepth(format)); + } + void upconv(OutputType)() @safe { + OutputType[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) + array[i] = data[i]; + result = new IndexedImageData!OutputType(array, palette, _width, _height); + } + switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) { + case PixelFormat.Indexed16Bit: + upconv!ushort; + break; + case PixelFormat.Indexed8Bit: + upconv!ubyte; + break; + case PixelFormat.Grayscale8Bit: + monochrome8bitConverter; + break; + case PixelFormat.YX88: + if(format & PixelFormat.BigEndian) + converter!(YA88BE); + else + converter!(YA88); + break; + case PixelFormat.RGB888: + if(format & PixelFormat.BigEndian) + converter!(RGB888BE); + else + converter!(RGB888); + break; + case PixelFormat.RGBX8888: + if(format & PixelFormat.BigEndian) + converter!(RGBA8888BE); + else + converter!(RGBA8888); + break; + case PixelFormat.XRGB8888: + if(format & PixelFormat.BigEndian) + converter!(ARGB8888BE); + else + converter!(ARGB8888); + break; + case PixelFormat.RGB565: + converter!(RGB565); + break; + case PixelFormat.RGBX5551: + converter!(RGBA5551); + break; + default: + throw new ImageFormatException("Format not supported"); + } + return result; + } + public ARGB8888 read(uint x, uint y) @safe pure { + if(x < _width && y < _height) return palette.read(data[x + (y * _pitch)]); + else throw new ImageBoundsException("Image is being read out of bounds!"); + } + public ubyte opIndex(uint x, uint y) @safe pure { + if(x < _width && y < _height) return data[x + (y * _pitch)]; + else throw new ImageBoundsException("Image is being read out of bounds!"); + } + public ubyte opIndexAssign(uint x, uint y, ubyte val) @safe pure { + if(x < _width && y < _height) return data[x + (y * _pitch)] = val; + else throw new ImageBoundsException("Image is being read out of bounds!"); + } + ///Flips the image horizontally + public void flipHorizontal() @safe pure { + for (uint y ; y < _height ; y++) { + for (uint x ; x < _width>>>1 ; x++) { + const ubyte tmp = opIndex(x, y); + opIndexAssign(x, y, opIndex(_width - x, y)); + opIndexAssign(_width - x, y, tmp); + } } - return internalWrapper; } - protected ubyte _writeIndex_1bit (uint x, uint y, ubyte val) @safe pure { - ubyte internalWrapper() @trusted pure { - return bitArray[x + y * pitch] = val ? true : false; + ///Flips the image vertically + public void flipVertical() @safe pure { + import std.algorithm.mutation : swapRanges; + for (uint y ; y < _height / 2 ; y++) { + const uint y0 = _height - y - 1; + ubyte[] a = data[(y * _pitch/4)..((y + 1) * _pitch/4)]; + ubyte[] b = data[(y0 * _pitch/4)..((y0 + 1) * _pitch/4)]; + swapRanges(a, b); } - return internalWrapper; } } - -alias Pixel32Bit = Pixel32BitARGB!false; -alias Pixel32BitBE = Pixel32BitARGB!true; - /** - * Standard 32 bit pixel representation. + * Monochrome 1 bit access */ -struct Pixel32BitARGB (bool bigEndian = false) { - union{ - ubyte[4] bytes; /// BGRA - uint base; /// Direct address - } - static if (bigEndian) { - ///Red - @safe @nogc @property pure ref auto r() inout { return bytes[1]; } - ///Green - @safe @nogc @property pure ref auto g() inout { return bytes[2]; } - ///Blue - @safe @nogc @property pure ref auto b() inout { return bytes[3]; } - ///Alpha - @safe @nogc @property pure ref auto a() inout { return bytes[0]; } - } else { - ///Red - @safe @nogc @property pure ref auto r() inout { return bytes[2]; } - ///Green - @safe @nogc @property pure ref auto g() inout { return bytes[1]; } - ///Blue - @safe @nogc @property pure ref auto b() inout { return bytes[0]; } - ///Alpha - @safe @nogc @property pure ref auto a() inout { return bytes[3]; } - } - ///Creates a standard pixel representation out from a 4 element array - this(ubyte[4] bytes) @safe @nogc pure{ - this.bytes = bytes; - } - ///Creates a standard pixel representation out from 4 separate values - this(ubyte r, ubyte g, ubyte b, ubyte a) @safe @nogc pure { - this.b = b; - this.g = g; - this.r = r; - this.a = a; - } - ///Template for pixel conversion - this(T)(T p) @safe @nogc pure { - this.b = p.b; - this.g = p.g; - this.r = p.r; - this.a = p.a; - } - /// - this(ubyte p) @safe @nogc pure { - this.b = p; - this.g = p; - this.r = p; - this.a = 0xFF; - } - ///String representation of this struct - string toString() @safe pure { - import std.conv : to; - return to!string(r) ~ "," ~ to!string(g) ~ "," ~ to!string(b) ~ "," ~ to!string(a); +public class IndexedImageData1Bit : IImageData { + protected ubyte[] data; + protected BitArray accessor; + public IPalette palette; + protected uint _width, _height, _pitch; + ///CTOR + public this(ubyte[] data, IPalette palette, uint _width, uint _height) { + this.data = data; + this.palette = palette; + this._width = _width; + this._height = _height; + _pitch = _width; + _pitch += width % 8 ? 8 - width % 8 : 0; + accessor = BitArray(data, _pitch * _height); + } + ///Returns the raw data + public @property BitArray getData() @nogc @safe pure nothrow { + return accessor; + } + ///Returns the raw data cast to ubyte + public ubyte[] raw() @safe pure { + return data; + } + ///Returns the width of the image. + public @property uint width() @nogc @safe pure nothrow const { + return _width; + } + ///Returns the height of the image. + public @property uint height() @nogc @safe pure nothrow const { + return _height; } -} -alias Pixel32BitRGBABE = Pixel32BitRGBA!true; -alias Pixel32BitRGBALE = Pixel32BitRGBA!false; -/** - * Standard 32 bit pixel representation. - */ -struct Pixel32BitRGBA (bool bigEndian = false) { - union{ - ubyte[4] bytes; /// RGBA - uint base; /// Direct address - } - static if (bigEndian) { - ///Red - @safe @nogc @property pure ref auto r() inout { return bytes[0]; } - ///Green - @safe @nogc @property pure ref auto g() inout { return bytes[1]; } - ///Blue - @safe @nogc @property pure ref auto b() inout { return bytes[2]; } - ///Alpha - @safe @nogc @property pure ref auto a() inout { return bytes[3]; } - } else { - ///Red - @safe @nogc @property pure ref auto r() inout { return bytes[3]; } - ///Green - @safe @nogc @property pure ref auto g() inout { return bytes[2]; } - ///Blue - @safe @nogc @property pure ref auto b() inout { return bytes[1]; } - ///Alpha - @safe @nogc @property pure ref auto a() inout { return bytes[0]; } - } - ///Creates a standard pixel representation out from a 4 element array - @nogc this(ubyte[4] bytes) @safe{ - this.bytes = bytes; - } - ///Creates a standard pixel representation out from 4 separate values - @nogc this(ubyte r, ubyte g, ubyte b, ubyte a) @safe{ - bytes[0] = r; - bytes[1] = g; - bytes[2] = b; - bytes[3] = a; - } - ///Template for pixel conversion - this(T)(T p) @nogc @safe pure { - this.b = p.b; - this.g = p.g; - this.r = p.r; - this.a = p.a; + public @property ubyte bitDepth() @nogc @safe pure nothrow const { + return 1; } -} -/** - * For monochrome images with a single channel - */ -struct PixelYA88{ - union{ - ushort base; /// direct access - ubyte[2] channels; /// individual access - } - /// luminance - @safe @nogc @property pure ref auto y() inout { return channels[0]; } - /// alpha - @safe @nogc @property pure ref auto a() inout { return channels[1]; } - /// pseudo-red (output only) - @safe @nogc @property pure ubyte r() const { return y; } - /// pseudo-green (output only) - @safe @nogc @property pure ubyte g() const { return y; } - /// pseudo-blue (output only) - @safe @nogc @property pure ubyte b() const { return y; } -} -/** - * 16 Bit colorspace with a single bit alpha. This is should be used with RGBX5551 with channel a ignored - */ -struct PixelRGBA5551{ - union{ - ushort base; /// direct access - mixin(bitfields!( - ubyte, "_b", 5, - ubyte, "_g", 5, - ubyte, "_r", 5, - bool, "_a", 1, - )); - } - /// upconverted-red (output only) - @safe @nogc @property pure ubyte r() const { return cast(ubyte)(_r << 3 | _r >>> 2); } - /// upconverted-green (output only) - @safe @nogc @property pure ubyte g() const { return cast(ubyte)(_g << 3 | _g >>> 2); } - /// upconverted-blue (output only) - @safe @nogc @property pure ubyte b() const { return cast(ubyte)(_b << 3 | _b >>> 2); } - /// upconverted-alpha (output only) - @safe @nogc @property pure ubyte a() const { return _a ? 0xFF : 0x00; } -} -/** - * 16 Bit RGB565 colorspace with no alpha. - */ -struct PixelRGB565{ - union{ - ushort base; /// direct access - mixin(bitfields!( - ubyte, "_b", 5, - ubyte, "_g", 6, - ubyte, "_r", 5, - )); - } - /// upconverted-red (output only) - @safe @nogc @property pure ubyte r() const { return cast(ubyte)(_r << 3 | _r >>> 2); } - /// upconverted-green (output only) - @safe @nogc @property pure ubyte g() const { return cast(ubyte)(_g << 2 | _g >>> 4); } - /// upconverted-blue (output only) - @safe @nogc @property pure ubyte b() const { return cast(ubyte)(_b << 3 | _b >>> 2); } - //pseudo-alpha (output only) - @safe @nogc @property pure ubyte a() const { return 0xFF; } -} -/** - * 24 Bit colorspace - */ -align(1) struct Pixel24Bit { - ubyte[3] bytes; ///individual access - ///red - @safe @nogc @property pure ref auto r() inout { return bytes[2]; } - ///green - @safe @nogc @property pure ref auto g() inout { return bytes[1]; } - ///blue - @safe @nogc @property pure ref auto b() inout { return bytes[0]; } - //pseudo-alpha (output only) - @safe @nogc @property pure ubyte a() const { return 0xFF; } - ///direct access read - @safe @nogc @property pure uint base(){ return 0xff_00_00_00 | bytes[2] | bytes[1] | bytes[0]; } -} -/** - * 24 Bit colorspace - */ -align(1) struct Pixel24BitBE { - ubyte[3] bytes; ///individual access - ///red - @safe @nogc @property pure ref auto r() inout { return bytes[0]; } - ///green - @safe @nogc @property pure ref auto g() inout { return bytes[1]; } - ///blue - @safe @nogc @property pure ref auto b() inout { return bytes[2]; } - //pseudo-alpha (output only) - @safe @nogc @property pure ubyte a() const { return 0xFF; } - ///direct access read - @safe @nogc @property pure uint base(){ return 0xff_00_00_00 | bytes[0] | bytes[1] | bytes[2]; } -} -/** - * Pixel formats where its needed. - * Undefined should be used for all indexed bitmaps, except 16 bit big endian ones, in which case a single BigEndian bit should be set high. - * Lower 16 bits should be used for general identification, upper 16 bits are general identificators (endianness, valid alpha channel, etc). - * 0x01 - 0x1F are reserved for 16 bit truecolor, 0x20 - 0x2F are reserved for 24 bit truecolor, 0x30 - 3F are reserved for integer grayscale, - * 0x40 - 0x5F are reserved for 32 bit truecolor, 0xF00-0xF0F are reserved for "chunky" indexed images, 0xF10-0xF1F are reserved for planar - * indexed images. - */ -enum PixelFormat : uint { - BigEndian = 0x00_01_00_00, ///Always little endian if bit not set - ValidAlpha = 0x00_02_00_00, ///If high, alpha is used - RGBX5551 = 0x1, - RGBA5551 = RGBX5551 | ValidAlpha, - RGB565 = 0x2, - RGB888 = 0x20, - YX88 = 0x30, - YA88 = YX88 | ValidAlpha, - RGBX8888 = 0x40, - RGBA8888 = RGBX8888 | ValidAlpha, - XRGB8888 = 0x41, - ARGB8888 = XRGB8888 | ValidAlpha, - Indexed1Bit = 0xF00, - Indexed2Bit = 0xF01, - Indexed4Bit = 0xF02, - Indexed8Bit = 0xF03, - Indexed16Bit = 0xF04, - Planar2Color = 0xF10, - Planar4Color = 0xF11, - Planar8Color = 0xF12, - Planar16Color = 0xF13, - Planar32Color = 0xF14, - Planar64Color = 0xF15, - Planar128Color = 0xF16, - Planar256Color = 0xF17, - Planar512Color = 0xF18, - Planar1024Color = 0xF19, - Planar2048Color = 0xF1A, - Planar4096Color = 0xF1B, - Planar8192Color = 0xF1C, - Planar16384Color= 0xF1D, - Planar32768Color= 0xF1E, - Planar65536Color= 0xF1F, - - Undefined = 0, -} -/+/** - * Function pointer to set up external virtual file readers from e.g. archives if extra files need to be read. - * If null, then the file will be read from disk instead from the same folder as the image file that needs the extra file. - */ -shared VFile delegate(string filename) getVFile @trusted;+/ -/** - * Thrown if image is being read or written out of bounds. - */ -class ImageBoundsException : Exception{ - @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) - { - super(msg, file, line, nextInChain); - } - @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line, nextInChain); - } -} -/** - * Thrown if palette is being read or written out of bounds. - */ -class PaletteBoundsException : Exception{ - @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) - { - super(msg, file, line, nextInChain); - } + public @property ubyte bitplanes() @nogc @safe pure nothrow const { + return 1; + } - @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line, nextInChain); - } -} -/** - * Thrown if image format doesn't match. - */ -class ImageFormatException : Exception{ - @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) - { - super(msg, file, line, nextInChain); - } + public @property uint pixelFormat() @nogc @safe pure nothrow const { + return PixelFormat.Indexed1Bit; + } - @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line, nextInChain); - } -} -/** - * Thrown on image file reading/writing errors. - */ -class ImageFileException : Exception{ - @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) - { - super(msg, file, line, nextInChain); - } + public IImageData convTo(uint format) @safe { + IImageData result; + void converter(OutputType)() @safe { + OutputType[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) + array[i] = OutputType(palette.read(data[i])); + result = new ImageData!OutputType(array, _width, _height, format, getBitDepth(format)); + } + void monochrome8bitConverter() @safe { + ubyte[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) { + const auto c = palette.read(data[i]); + array[i] = cast(ubyte)((c.r + c.g + c.b) / 3); + } + result = new ImageData!ubyte(array, _width, _height, format, getBitDepth(format)); + } + void upconv(OutputType)() @safe { + OutputType[] array; + array.length = data.length; + for(int i ; i < data.length ; i++) + array[i] = data[i]; + result = new IndexedImageData!OutputType(array, palette, _width, _height); + } + switch (format & ~(PixelFormat.BigEndian | PixelFormat.ValidAlpha)) { + case PixelFormat.Indexed16Bit: + upconv!ushort; + break; + case PixelFormat.Grayscale8Bit: + monochrome8bitConverter; + break; + case PixelFormat.YX88: + if(format & PixelFormat.BigEndian) + converter!(YA88BE); + else + converter!(YA88); + break; + case PixelFormat.RGB888: + if(format & PixelFormat.BigEndian) + converter!(RGB888BE); + else + converter!(RGB888); + break; + case PixelFormat.RGBX8888: + if(format & PixelFormat.BigEndian) + converter!(RGBA8888BE); + else + converter!(RGBA8888); + break; + case PixelFormat.XRGB8888: + if(format & PixelFormat.BigEndian) + converter!(ARGB8888BE); + else + converter!(ARGB8888); + break; + case PixelFormat.RGB565: + converter!(RGB565); + break; + case PixelFormat.RGBX5551: + converter!(RGBA5551); + break; + default: + throw new ImageFormatException("Format not supported"); + } + return result; + } - @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line, nextInChain); - } + public ARGB8888 read(uint x, uint y) @safe pure { + if(x < _width && y < _height) return palette.read(opIndex(x, y)); + else throw new ImageBoundsException("Image is being read out of bounds!"); + } + public bool opIndex(uint x, uint y) @trusted pure { + if(x < _width && y < _height) return accessor[x + (y * _pitch)]; + else throw new ImageBoundsException("Image is being read out of bounds!"); + } + public bool opIndexAssign(uint x, uint y, bool val) @trusted pure { + if(x < _width && y < _height) return accessor[x + (y * _pitch)] = val; + else throw new ImageBoundsException("Image is being read out of bounds!"); + } + ///Flips the image horizontally + public void flipHorizontal() @safe pure { + for (uint y ; y < _height ; y++) { + for (uint x ; x < _width>>>1 ; x++) { + const bool tmp = opIndex(x, y); + opIndexAssign(x, y, opIndex(_width - x, y)); + opIndexAssign(_width - x, y, tmp); + } + } + } + ///Flips the image vertically + public void flipVertical() @safe pure { + import std.algorithm.mutation : swapRanges; + for (uint y ; y < _height / 2 ; y++) { + const uint y0 = _height - y - 1; + ubyte[] a = data[(y * _pitch/8)..((y + 1) * _pitch/8)]; + ubyte[] b = data[(y0 * _pitch/8)..((y0 + 1) * _pitch/8)]; + swapRanges(a, b); + } + } } /** - * Thrown if the file has a checksum error. + * All image classes should be derived from this base. + * Implements some basic functionality, such as reading and writing pixels, basic data storage, and basic information. + * Pixeldata should be stored decompressed, but indexing should be preserved on loading with the opinion of upconverting + * to truecolor. */ -public class ChecksumMismatchException : Exception{ - @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) - { - super(msg, file, line, nextInChain); - } +abstract class Image{ + /** + * Contains palette data and information + */ + protected IPalette _palette; + /** + * Contains image data and information. + */ + protected IImageData _imageData; + protected ubyte mod; ///used for fast access of indexes DEPRECATED! + protected ubyte shift; ///used for fast access of indexes DEPRECATED! - @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line, nextInChain); - } + /+protected @safe pure ubyte delegate(uint x, uint y) indexReader8Bit; ///Used for bypassing typechecking when reading pixels + protected @safe pure ushort delegate(uint x, uint y) indexReader16bit; ///Used for bypassing typechecking when reading pixels + protected @safe pure ubyte delegate(uint x, uint y, ubyte val) indexWriter8Bit; ///Used for bypassing typechecking when writing pixels + protected @safe pure ushort delegate(uint x, uint y, ushort val) indexWriter16bit; ///Used for bypassing typechecking when writing pixels + protected @safe pure ARGB8888 delegate(uint x, uint y) pixelReader; //Used for bypassing typechecking + protected @safe pure ARGB8888 delegate(ushort i) paletteReader; //Used for bypassing typechecking + +/ + + /+protected uint pitch; ///Contains the precalculated scanline size with the occassional padding for 8bit values.+/ + ///Returns the width of the image in pixels. + @property uint width() @nogc @safe pure nothrow const { + return _imageData.width; + } + ///Returns the height of the image in pixels. + @property uint height() @nogc @safe pure nothrow const { + return _imageData.height; + } + ///Returns true if the image is indexed. + @property bool isIndexed() @nogc @safe pure nothrow const { + return _palette !is null; + } + ///Returns the number of bits used per sample. + @property ubyte getBitdepth() @nogc @safe pure nothrow const { + return _imageData.bitDepth; + } + ///Returns the number of bits used per colormap entry. + @property ubyte getPaletteBitdepth() @nogc @safe pure nothrow const { + if (_palette) return _palette.bitDepth; + else return 0; + } + ///Returns the pixelformat of the image. See enumerator `PixelFormat` for more info. + @property uint getPixelFormat() @nogc @safe pure nothrow const { + return _imageData.pixelFormat; + } + ///Returns the pixelformat of the palette. See enumerator `PixelFormat` for more info. + @property uint getPalettePixelFormat() @nogc @safe pure nothrow const { + if (_palette) return _palette.paletteFormat; + else return PixelFormat.Undefined; + } + /** + * Returns the number of planes the image have. + * Default is one. + */ + public ubyte getBitplanes() @safe pure { + return _imageData.bitplanes; + } + /** + * Returns a palette range, which can be used to read the palette. + */ + public IPalette palette() @safe @property pure { + return _palette; + } + /** + * Returns the image data. + */ + public IImageData imageData() @safe @property pure { + return _imageData; + } + /** + * Reads a single 32bit pixel. If the image is indexed, a color lookup will be done. + */ + public ARGB8888 readPixel(uint x, uint y) @safe pure { + return _imageData.read(x, y); + } + /** + * Looks up the index on the palette, then returns the color value as a 32 bit value. + */ + public ARGB8888 readPalette(size_t index) @safe pure { + return _palette.read(index); + } + /** + * Flips the image on the vertical axis. Useful to set images to the correct top-left screen origin point. + */ + public void flipVertical() @safe pure { + _imageData.flipVertical; + } + /** + * Flips the image on the vertical axis. Useful to set images to the correct top-left screen origin point. + */ + public void flipHorizontal() @safe pure { + _imageData.flipVertical; + } + ///Returns true if the image originates from the top + public bool topOrigin() @property @nogc @safe pure const { + return false; + } + ///Returns true if the image originates from the right + public bool rightSideOrigin() @property @nogc @safe pure const { + return false; + } } \ No newline at end of file diff --git a/source/dimage/bmp.d b/source/dimage/bmp.d index 41f40a5..4f7913b 100644 --- a/source/dimage/bmp.d +++ b/source/dimage/bmp.d @@ -130,9 +130,7 @@ public class BMP : Image { WinNTBitmapHeaderExt shortext; } protected HeaderExt headerExt; - protected size_t pitch; - mixin ChunkyAccess4Bit; - mixin MonochromeAccess; + //protected size_t pitch; /** * Creates a blank for loading. */ @@ -142,17 +140,95 @@ public class BMP : Image { /** * Creates a new bitmap from supplied data. */ - public this (int width, int height, ubyte bitDepth, PixelFormat format, ) { - + public this (IImageData imgDat, IPalette pal = null, BMPVersion vers = BMPVersion.Win4X) @safe pure { + if (vers == BMPVersion.Win2X) { + if (pal) { + if (pal.paletteFormat != PixelFormat.RGB888) throw new ImageFormatException("Unsupported palette format!"); + if (!(imgDat.pixelFormat == PixelFormat.Indexed1Bit || imgDat.pixelFormat == PixelFormat.Indexed4Bit || + imgDat.pixelFormat == PixelFormat.Indexed8Bit)) throw new ImageFormatException("Image format not supported by this + version of BMP!"); + bitmapHeaderLength = vers; + _imageData = imgDat; + _palette = pal; + bitmapHeader.oldType.width = cast(short)_imageData.width; + bitmapHeader.oldType.height = cast(short)_imageData.height; + bitmapHeader.oldType.planes = 1; + bitmapHeader.oldType.bitsPerPixel = _imageData.bitDepth; + header.newType.bitmapOffset = cast(uint)pal.raw.length; + } else throw new ImageFormatException("This version of BMP must have a palette!"); + } else if (vers == BMPVersion.Win1X) { + if (pal) throw new ImageFormatException("This version of BMP doesn't have a palette!"); + _imageData = imgDat; + header.oldType.width = cast(ushort)_imageData.width; + header.oldType.height = cast(ushort)_imageData.height; + header.oldType.byteWidth = cast(ushort)(_imageData.width * 8 / _imageData.bitDepth); + if (_imageData.bitDepth == 4 && _imageData.width & 1) header.oldType.byteWidth++; + if (_imageData.bitDepth == 1 && _imageData.width & 7) header.oldType.byteWidth++; + header.oldType.bitsPerPixel = _imageData.bitDepth; + header.oldType.planes = 1; + } else { + if (pal) { + if (pal.paletteFormat != PixelFormat.XRGB8888) throw new ImageFormatException("Unsupported palette format!"); + if (!(imgDat.pixelFormat == PixelFormat.Indexed1Bit || imgDat.pixelFormat == PixelFormat.Indexed4Bit || + imgDat.pixelFormat == PixelFormat.Indexed8Bit)) throw new ImageFormatException("Unsupported indexed image type!"); + _palette = pal; + header.newType.bitmapOffset = cast(uint)pal.raw.length; + } else { + if (!(imgDat.pixelFormat == PixelFormat.RGB888 || imgDat.pixelFormat == PixelFormat.ARGB8888 || + imgDat.pixelFormat == PixelFormat.RGB565 || imgDat.pixelFormat == PixelFormat.RGBA5551)) throw new + ImageFormatException("Unsupported truecolor image type!"); + } + _imageData = imgDat; + bitmapHeaderLength = vers; + bitmapHeader.newType.planes = 1; + bitmapHeader.newType.compression = 0; + bitmapHeader.newType.bitsPerPixel = _imageData.bitDepth; + bitmapHeader.newType.width = _imageData.width; + bitmapHeader.newType.height = _imageData.height; + bitmapHeader.newType.horizResolution = 72; + bitmapHeader.newType.vertResolution = 72; + bitmapHeader.newType.colorsUsed = 1 << bitmapHeader.newType.bitsPerPixel; + bitmapHeader.newType.colorsimportant = bitmapHeader.newType.colorsUsed; //ALL THE COLORS!!! :) + if (vers == BMPVersion.Win4X || vers == BMPVersion.WinNT) { + switch(_imageData.pixelFormat) { + case PixelFormat.RGB565: + headerExt.longext.redMask = 0xF8_00_00_00; + headerExt.longext.greenMask = 0x07_E0_00_00; + headerExt.longext.blueMask = 0x00_1F_00_00; + break; + case PixelFormat.RGBX5551: + headerExt.longext.redMask = 0xF8_00_00_00; + headerExt.longext.greenMask = 0x07_C0_00_00; + headerExt.longext.blueMask = 0x00_3E_00_00; + break; + case PixelFormat.RGB888: + headerExt.longext.redMask = 0xff_00_00_00; + headerExt.longext.greenMask = 0x00_ff_00_00; + headerExt.longext.blueMask = 0x00_00_ff_00; + break; + case PixelFormat.ARGB8888: + headerExt.longext.alphaMask = 0xff_00_00_00; + headerExt.longext.redMask = 0x00_ff_00_00; + headerExt.longext.greenMask = 0x00_00_ff_00; + headerExt.longext.blueMask = 0x00_00_00_ff; + break; + default: break; + } + } + } + if (vers != BMPVersion.Win1X) { + header.newType.bitmapOffset += 2 + WinBMPHeader.sizeof + bitmapHeaderLength; + header.newType.fileSize = header.newType.bitmapOffset + cast(uint)_imageData.raw.length; + } } /** * Loads an image from a file. * Only uncompressed and 8bit RLE are supported. */ - public static BMP load (F = std.stdio.file) (F file) { + public static BMP load (F = std.stdio.file) (ref F file) { import std.math : abs; BMP result = new BMP(); - ubyte[] buffer; + ubyte[] buffer, imageBuffer; void loadUncompressedImageData (int bitsPerPixel, size_t width, size_t height) { size_t scanlineSize = (width * bitsPerPixel) / 8; scanlineSize += ((width * bitsPerPixel) % 32) / 8; //Padding @@ -161,11 +237,11 @@ public class BMP : Image { for(int i ; i < height ; i++) { file.rawRead(localBuffer); assert(localBuffer.length == scanlineSize, "Scanline mismatch"); - result.imageData ~= localBuffer[0..(width * bitsPerPixel) / 8]; + imageBuffer ~= localBuffer[0..(width * bitsPerPixel) / 8]; } - assert(result.imageData.length == (width * height) / 8, "Scanline mismatch"); + //assert(imageBuffer.length == (width * height) / 8, "Scanline mismatch"); if (result.bitmapHeaderLength >> 16) - assert(result.imageData.length == result.bitmapHeader.newType.sizeOfBitmap, "Size mismatch"); + assert(imageBuffer.length == result.bitmapHeader.newType.sizeOfBitmap, "Size mismatch"); } void load8BitRLEImageData (size_t width, size_t height) { size_t remaining = width * height; @@ -173,7 +249,7 @@ public class BMP : Image { ubyte[] scanlineBuffer; localBuffer.length = 2; scanlineBuffer.reserve(width); - result.imageData.reserve(width * height); + imageBuffer.reserve(width * height); while (remaining) { localBuffer = file.rawRead(localBuffer); assert(localBuffer.length == 2, "End of File error"); @@ -185,7 +261,7 @@ public class BMP : Image { } else if (localBuffer[1] == 1) { //End of bitmap data marker //flush current scanline scanlineBuffer.length = width; - result.imageData ~= scanlineBuffer; + imageBuffer ~= scanlineBuffer; break; } else if (localBuffer[1] == 2) { //Run offset marker localBuffer = file.rawRead(localBuffer); @@ -193,10 +269,10 @@ public class BMP : Image { remaining -= localBuffer[0] + (localBuffer[1] * width); //flush current scanline scanlineBuffer.length = width; - result.imageData ~= scanlineBuffer; + imageBuffer ~= scanlineBuffer; while (localBuffer[1]) { localBuffer[1]--; - result.imageData ~= new ubyte[](width); + imageBuffer ~= new ubyte[](width); } //clear current scanline scanlineBuffer.length = 0; @@ -214,12 +290,12 @@ public class BMP : Image { scanlineBuffer.length += width - (scanlineBuffer.length % width); //flush current scanline scanlineBuffer.length = width; - result.imageData ~= scanlineBuffer; + imageBuffer ~= scanlineBuffer; //clear current scanline scanlineBuffer.length = 0; } } - result.imageData.length = width * height; + imageBuffer.length = width * height; } void loadImageDataWin3x () { switch (result.bitmapHeader.newType.compression) { @@ -237,26 +313,32 @@ public class BMP : Image { result.bitmapHeader.newType = reinterpretGet!Win3xBitmapHeader(buffer); } void loadPalette (int bitsPerPixel, int bytesPerPaletteEntry) { + ubyte[] paletteBuffer; switch (bitsPerPixel) { case 1: - result.paletteData.length = 2 * bytesPerPaletteEntry; + paletteBuffer.length = 2 * bytesPerPaletteEntry; break; case 4: - result.paletteData.length = 16 * bytesPerPaletteEntry; + paletteBuffer.length = 16 * bytesPerPaletteEntry; break; case 8: - result.paletteData.length = 256 * bytesPerPaletteEntry; + paletteBuffer.length = 256 * bytesPerPaletteEntry; break; default: return; } - result.paletteData = file.rawRead(result.paletteData); + paletteBuffer = file.rawRead(paletteBuffer); + if(bytesPerPaletteEntry == 3) { + result._palette = new Palette!RGB888(reinterpretCast!RGB888(paletteBuffer), PixelFormat.RGB888, 24); + } else { + result._palette = new Palette!ARGB8888(reinterpretCast!ARGB8888(paletteBuffer), PixelFormat.XRGB8888, 32); + } } buffer.length = 2; buffer = file.rawRead(buffer); result.fileVersionIndicator = reinterpretGet!ushort(buffer); //Decide file version, if first two byte is "BM" it's 2.0 or later, if not it's 1.x - if (!result.fileVersionIndicator) { + if (result.fileVersionIndicator) { buffer.length = WinBMPHeader.sizeof; buffer = file.rawRead(buffer); result.header.newType = reinterpretGet!WinBMPHeader(buffer); @@ -313,40 +395,71 @@ public class BMP : Image { loadUncompressedImageData(result.header.oldType.bitsPerPixel, result.header.oldType.width, result.header.oldType.height); } - result.setupDelegates(); + //Set up image data + //std.stdio.writeln(result.getPixelFormat); + switch (result.getPixelFormat) { + case PixelFormat.Indexed1Bit: + result._imageData = new IndexedImageData1Bit(imageBuffer, result._palette, result.width, result.height); + break; + case PixelFormat.Indexed4Bit: + result._imageData = new IndexedImageData4Bit(imageBuffer, result._palette, result.width, result.height); + break; + case PixelFormat.Indexed8Bit: + result._imageData = new IndexedImageData!ubyte(imageBuffer, result._palette, result.width, result.height); + break; + case PixelFormat.RGB565: + result._imageData = new ImageData!RGB565(reinterpretCast!RGB565(imageBuffer), result.width, result.height, + PixelFormat.RGB565, 16); + break; + case PixelFormat.RGBX5551: + result._imageData = new ImageData!RGBA5551(reinterpretCast!RGBA5551(imageBuffer), result.width, result.height, + PixelFormat.RGBA5551, 16); + break; + case PixelFormat.RGB888: + result._imageData = new ImageData!RGB888(reinterpretCast!RGB888(imageBuffer), result.width, result.height, + PixelFormat.RGB888, 24); + break; + case PixelFormat.ARGB8888: + result._imageData = new ImageData!ARGB8888(reinterpretCast!ARGB8888(imageBuffer), result.width, result.height, + PixelFormat.ARGB8888, 32); + break; + default: throw new ImageFileException("Unknown image format!"); + } return result; } ///Saves the image into the given file. ///Only uncompressed bitmaps are supported currently. - public void save (F = std.stdio.file)(F file) { - ubyte[] buffer; + public void save (F = std.stdio.file)(ref F file) { + ubyte[] buffer, paletteData; + if (_palette) paletteData = _palette.raw; void saveUncompressed () { - const size_t pitch = (width * bitDepth) / 8; + const size_t pitch = (width * getBitdepth) / 8; + ubyte[] imageBuffer = _imageData.raw; for (int i ; i < height ; i++) { - buffer = imageData[pitch * i .. pitch * (i + 1)]; + buffer = imageBuffer[pitch * i .. pitch * (i + 1)]; while (buffer.length & 0b0000_0011) buffer ~= 0b0; file.rawWrite(buffer); } } void saveWin3xHeader () { - buffer = reinterpretCast!ubyte([BMPTYPE]); + buffer = reinterpretAsArray!ubyte(BMPTYPE); file.rawWrite(buffer); - buffer = reinterpretCast!ubyte([header.newType]); + buffer = reinterpretAsArray!ubyte(header.newType); file.rawWrite(buffer); - buffer = reinterpretCast!ubyte([bitmapHeaderLength]); - buffer ~= reinterpretCast!ubyte([bitmapHeader.newType]); + buffer = reinterpretAsArray!ubyte(bitmapHeaderLength); + buffer ~= reinterpretAsArray!ubyte(bitmapHeader.newType); file.rawWrite(buffer); } buffer.length = 2; switch (bitmapHeaderLength) { case BMPVersion.Win2X: - buffer = reinterpretCast!ubyte([BMPTYPE]); + buffer = reinterpretAsArray!ubyte(BMPTYPE); file.rawWrite(buffer); - buffer = reinterpretCast!ubyte([header.newType]); + buffer = reinterpretAsArray!ubyte(header.newType); file.rawWrite(buffer); - buffer = reinterpretCast!ubyte([bitmapHeaderLength]); - buffer ~= reinterpretCast!ubyte([bitmapHeader.oldType]); + buffer = reinterpretAsArray!ubyte(bitmapHeaderLength); + buffer ~= reinterpretAsArray!ubyte(bitmapHeader.oldType); file.rawWrite(buffer); if (paletteData.length) file.rawWrite(paletteData); @@ -360,7 +473,7 @@ public class BMP : Image { break; case BMPVersion.Win4X: saveWin3xHeader(); - buffer = reinterpretCast!ubyte([headerExt.longext]); + buffer = reinterpretAsArray!ubyte(headerExt.longext); file.rawWrite(buffer); if (paletteData.length) file.rawWrite(paletteData); @@ -368,7 +481,7 @@ public class BMP : Image { break; case BMPVersion.WinNT: saveWin3xHeader(); - buffer = reinterpretCast!ubyte([headerExt.shortext]); + buffer = reinterpretAsArray!ubyte(headerExt.shortext); file.rawWrite(buffer); if (paletteData.length) file.rawWrite(paletteData); @@ -376,48 +489,12 @@ public class BMP : Image { break; default: //Must be Win1X file.rawWrite(buffer); - buffer = reinterpretCast!ubyte([header.oldType]); + buffer = reinterpretAsArray!ubyte(header.oldType); file.rawWrite(buffer); saveUncompressed; break; } } - /** - * Sets up all the function pointers automatically. - */ - protected void setupDelegates() @safe pure { - switch (getPixelFormat) { - case PixelFormat.Indexed1Bit: - indexReader8Bit = &_readIndex_1bit; - indexWriter8Bit = &_writeIndex_1bit; - indexReader16bit = &_indexReadUpconv; - pixelReader = &_readAndLookup; - pitch = width + width % 8 ? 8 - (width % 8) : 0; - break; - case PixelFormat.Indexed4Bit: - indexReader8Bit = &_readIndex_4bit; - indexWriter8Bit = &_writeIndex_4bit; - indexReader16bit = &_indexReadUpconv; - pixelReader = &_readAndLookup; - pitch = width + width % 2 ? 1 : 0; - break; - case PixelFormat.Indexed8Bit: - indexReader8Bit = &_readPixel_8bit; - indexWriter8Bit = &_writePixel!(ubyte); - indexReader16bit = &_indexReadUpconv; - pixelReader = &_readAndLookup; - break; - case PixelFormat.RGBA8888, PixelFormat.RGBX8888: - pixelReader = &_readPixelAndUpconv!(Pixel32BitRGBALE); - break; - case PixelFormat.RGB888: - pixelReader = &_readPixelAndUpconv!(Pixel24Bit); - break; - default: - break; - } - - } override uint width() @nogc @safe @property const pure { import std.math : abs; if (fileVersionIndicator) { @@ -489,11 +566,11 @@ public class BMP : Image { case 16: if (headerExt.shortext.redMask == 0xF8000000 && headerExt.shortext.greenMask == 0x07E00000 && headerExt.shortext.blueMask == 0x001F0000) - return PixelFormat.RGB565 || bitmapHeaderLength == 4 + Win3xBitmapHeader.sizeof + WinNTBitmapHeaderExt.sizeof ? - PixelFormat.BigEndian : 0; + return PixelFormat.RGB565 | (bitmapHeaderLength == 4 + Win3xBitmapHeader.sizeof + WinNTBitmapHeaderExt.sizeof ? + PixelFormat.BigEndian : 0); else - return PixelFormat.RGBX5551 || bitmapHeaderLength == 4 + Win3xBitmapHeader.sizeof + WinNTBitmapHeaderExt.sizeof ? - PixelFormat.BigEndian : 0; + return PixelFormat.RGBX5551 | (bitmapHeaderLength == 4 + Win3xBitmapHeader.sizeof + WinNTBitmapHeaderExt.sizeof ? + PixelFormat.BigEndian : 0); case 24: return PixelFormat.RGB888; case 32: @@ -515,9 +592,23 @@ public class BMP : Image { return PixelFormat.Undefined; } } + unittest { + import vfile; { std.stdio.File testFile1 = std.stdio.File("./test/bmp/TRU256.BMP"); - BMP test = BMP.load(testFile1); + std.stdio.File testFile2 = std.stdio.File("./test/bmp/TRU256_I.BMP"); + BMP test1 = BMP.load(testFile1); + BMP test2 = BMP.load(testFile2); + compareImages!true(test1, test2); + VFile backup1, backup2; + test1.save(backup1); + test2.save(backup2); + backup1.seek(0); + backup2.seek(0); + BMP test01 = BMP.load(backup1); + BMP test02 = BMP.load(backup2); + compareImages!true(test1, test01); + compareImages!true(test2, test02); } } \ No newline at end of file diff --git a/source/dimage/exceptions.d b/source/dimage/exceptions.d new file mode 100644 index 0000000..6257c1a --- /dev/null +++ b/source/dimage/exceptions.d @@ -0,0 +1,86 @@ +module dimage.exceptions; + +/** + * Thrown if image is being read or written out of bounds. + */ +class ImageBoundsException : Exception { + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + { + super(msg, file, line, nextInChain); + } + + @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line, nextInChain); + } +} +/** + * Thrown if palette is being read or written out of bounds. + */ +class PaletteBoundsException : Exception { + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + { + super(msg, file, line, nextInChain); + } + + @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line, nextInChain); + } +} +/** + * Thrown if image format doesn't match. + */ +class ImageFormatException : Exception { + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + { + super(msg, file, line, nextInChain); + } + + @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line, nextInChain); + } +} +/** + * Thrown on image file reading/writing errors. + */ +class ImageFileException : Exception { + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + { + super(msg, file, line, nextInChain); + } + + @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line, nextInChain); + } +} +/** + * Thrown if compression error is encountered with a file. + */ +public class ImageFileCompressionException : ImageFileException { + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + { + super(msg, file, line, nextInChain); + } + + @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line, nextInChain); + } +} +/** + * Thrown if the file has a checksum error. + */ +public class ChecksumMismatchException : ImageFileException { + @nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null) + { + super(msg, file, line, nextInChain); + } + + @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line, nextInChain); + } +} \ No newline at end of file diff --git a/source/dimage/gif.d b/source/dimage/gif.d index be0e561..33d7a43 100644 --- a/source/dimage/gif.d +++ b/source/dimage/gif.d @@ -1,3 +1,10 @@ +/* + * dimage - png.d + * by Laszlo Szeremi + * + * Copyright under Boost Software License. + */ + module dimage.gif; import std.bitmanip; @@ -9,12 +16,13 @@ static import std.stdio; /** * Implements reader/writer for *.GIF-files. - * Animation is accessed from a sliding window, and + * Animation is accessed from a sliding window of setting the required frame. + * Requires linking against the ncompress library for LZW support. */ version (lzwsupport) { import ncompress42; - public class GIF : Image, Animation { + public class GIF : Image, MultiImage { /** * Header for both 87a and 89a versions. */ diff --git a/source/dimage/png.d b/source/dimage/png.d index 3095bf0..e69be60 100644 --- a/source/dimage/png.d +++ b/source/dimage/png.d @@ -24,6 +24,24 @@ import std.conv : to; * Implements the Portable Network Graphics file format as a class. */ public class PNG : Image{ + ///Chunk initializer IDs. + static enum ChunkInitializers : char[4] { + Header = "IHDR", ///Standard, for header, before image data + Palette = "PLTE", ///Standard, for palette, after header and before image data + Data = "IDAT", ///Standard, for image data + End = "IEND", ///Standard, file end marker + //Ancillary chunks: + Background = "bKGD", ///Background color + Chromaticies = "cHRM", ///Primary chromaticities and white point + Gamma = "gAMA", ///Image gamma + Histogram = "hIST", ///Image histogram + PixDim = "pHYs", ///Physical pixel dimensions + SignifBits = "sBIT", ///Significant bits + TextData = "tEXt", ///Textual data + Time = "tIME", ///Last modification date + Transparency = "tRNS", ///Transparency + CompTextData = "zTXt", ///Compressed textual data + } static enum HEADER_INIT = "IHDR"; ///Initializes header in the file static enum PALETTE_INIT = "PLTE"; ///Initializes palette in the file static enum DATA_INIT = "IDAT"; ///Initializes image data in the file @@ -145,15 +163,29 @@ public class PNG : Image{ public EmbeddedData[] ancillaryChunks; ///Stores ancilliary chunks that are not essential for image processing protected ubyte[] filterBytes; ///Filterbytes for each scanline protected size_t pitch; - mixin ChunkyAccess4Bit; - mixin ChunkyAccess2Bit; - mixin MonochromeAccess; /** * Creates an empty PNG file in memory */ - public this(int width, int height, ubyte bitDepth, ColorType colorType, ubyte compression, ubyte[] imageData, - ubyte[] paletteData = []){ - header = Header(width, height, bitDepth, colorType, compression, 0, 0); + public this(IImageData imgDat, IPalette pal) @safe pure { + //header = Header(width, height, bitDepth, colorType, compression, 0, 0); + _imageData = imgDat; + _palette = pal; + switch(imgDat.pixelFormat) { + case PixelFormat.Indexed1Bit: .. case PixelFormat.Indexed8Bit: + header.colorType = ColorType.Indexed; + break; + case PixelFormat.YA88 | PixelFormat.BigEndian: + header.colorType = ColorType.GreyscaleWithAlpha; + break; + case PixelFormat.RGB888 | PixelFormat.BigEndian, PixelFormat.RGBX5551 | PixelFormat.BigEndian: + header.colorType = ColorType.TrueColor; + break; + case PixelFormat.RGBA8888 | PixelFormat.BigEndian: + header.colorType = ColorType.TrueColorWithAlpha; + break; + default: throw new ImageFormatException("Image format currently not supported!"); + } + header.bitDepth = imgDat.bitDepth; } protected this(){ @@ -179,12 +211,12 @@ public class PNG : Image{ if(ret != zlib.Z_OK) throw new Exception("Decompressor initialization error"); - ubyte[] readBuffer, imageBuffer; + ubyte[] readBuffer, imageBuffer, imageTemp, paletteTemp; readBuffer.length = 8; file.rawRead(readBuffer); for(int i ; i < 8 ; i++) if(readBuffer[i] != PNG_SIGNATURE[i]) - throw new ImageFormatException("Invalid PNG file signature"); + throw new ImageFileException("Invalid PNG file signature"); do{ ubyte[4] crc; file.rawRead(readBuffer); @@ -202,13 +234,13 @@ public class PNG : Image{ result.pitch = (result.header.width * result.getBitdepth) / 8; version (unittest) std.stdio.writeln("Pitch length: ", result.pitch); imageBuffer.length = result.pitch + 1; - version (unittest) std.stdio.writeln(imageBuffer.length); + version (unittest) std.stdio.writeln(result.getPixelFormat); strm.next_out = imageBuffer.ptr; strm.avail_out = cast(uint)imageBuffer.length; //result.pitch = result.header.width; break; case PALETTE_INIT: - result.paletteData = readBuffer.dup; + paletteTemp = readBuffer.dup; pos = EmbeddedData.DataPosition.BeforeIDAT; break; case DATA_INIT: @@ -224,14 +256,14 @@ public class PNG : Image{ if(!(ret == zlib.Z_OK || ret == zlib.Z_STREAM_END)){ version(unittest) std.stdio.writeln(ret); zlib.inflateEnd(&strm); - throw new Exception("Decompression error"); + throw new ImageFileException("Decompression error"); }else if(imageBuffer.length == strm.total_out){ pos = EmbeddedData.DataPosition.AfterIDAT; } if (!strm.avail_out) {//flush scanline into imagedata version (unittest) scanlineCounter++; version (unittest) assert (scanlineCounter <= result.header.height, "Scanline overflow!"); - result.imageData ~= imageBuffer[1..$]; + imageTemp ~= imageBuffer[1..$]; result.filterBytes ~= imageBuffer[0]; strm.next_out = imageBuffer.ptr; strm.avail_out = cast(uint)imageBuffer.length; @@ -241,13 +273,13 @@ public class PNG : Image{ break; case END_INIT: version (unittest) assert(result.header.height == scanlineCounter, "Scanline count mismatch"); - if(result.imageData.length + result.filterBytes.length > strm.total_out){ + if(imageTemp.length + result.filterBytes.length > strm.total_out){ zlib.inflateEnd(&strm); //version(unittest) std.stdio.writeln() throw new Exception("Decompression error! Image ended at: " ~ to!string(strm.total_out) ~ - "; Required length: " ~ to!string(result.imageData.length)); + "; Required length: " ~ to!string(imageTemp.length)); } - result.imageData.reserve(result.height * result.pitch); + imageTemp.reserve(result.height * result.pitch); result.filterBytes.reserve(result.height); iend = true; break; @@ -261,6 +293,9 @@ public class PNG : Image{ } break; } + if(paletteTemp) { + result._palette = new Palette!RGB888BE(reinterpretCast!RGB888BE(paletteTemp), PixelFormat.RGB888, 24); + } //calculate crc //if(curChunk.dataLength){ crc = crc32Of((cast(ubyte[])curChunk.identifier) ~ readBuffer); @@ -280,79 +315,62 @@ public class PNG : Image{ } while(!iend); zlib.inflateEnd(&strm);//should have no data remaining /+for (int line ; line < result.height ; line++) { - result.imageData ~= imageBuffer[0..result.pitch]; + imageTemp ~= imageBuffer[0..result.pitch]; result.filterBytes ~= imageBuffer[result.pitch]; if (imageBuffer.length) imageBuffer = imageBuffer[result.pitch + 1..$]; }+/ - assert(result.imageData.length == result.pitch * result.header.height, "Image size mismatch. Expected size: " ~ - to!string(result.pitch * result.header.height) ~ "; Actual size: " ~ to!string(result.imageData.length) ~ + assert(imageTemp.length == result.pitch * result.header.height, "Image size mismatch. Expected size: " ~ + to!string(result.pitch * result.header.height) ~ "; Actual size: " ~ to!string(imageTemp.length) ~ "; Pitch length: " ~ to!string(result.pitch) ~ "; N of scanlines: " ~ to!string(result.header.height)); - result.setupDelegates(); - return result; - } - /** - * Sets up all the function pointers automatically. - */ - protected void setupDelegates() @safe pure { - if (header.colorType == ColorType.Greyscale || header.colorType == ColorType.Indexed) { - switch (header.bitDepth) { - case 1: - indexReader8Bit = &_readIndex_1bit; - indexWriter8Bit = &_writeIndex_1bit; - indexReader16bit = &_indexReadUpconv; - pixelReader = &_readAndLookup; - break; - case 2: - indexReader8Bit = &_readIndex_2bit; - indexWriter8Bit = &_writeIndex_2bit; - indexReader16bit = &_indexReadUpconv; - pixelReader = &_readAndLookup; - break; - case 4: - indexReader8Bit = &_readIndex_4bit; - indexWriter8Bit = &_writeIndex_4bit; - indexReader16bit = &_indexReadUpconv; - pixelReader = &_readAndLookup; - break; - case 8: - if (header.colorType == ColorType.Greyscale) { - pixelReader = &_readPixelAndUpconv!(ubyte); - } else { - indexReader8Bit = &_readPixel_8bit; - indexWriter8Bit = &_writePixel!(ubyte); - indexReader16bit = &_indexReadUpconv; - pixelReader = &_readAndLookup; - } - break; - default: - break; - } - if (header.colorType == ColorType.Indexed) { - paletteReader = &_paletteReader!Pixel24BitBE; - } - } else { - switch (getPixelFormat()){ - case PixelFormat.RGBA8888: - pixelReader = &_readPixelAndUpconv!(Pixel32BitRGBABE); - break; - case PixelFormat.RGB888: - pixelReader = &_readPixelAndUpconv!(Pixel24BitBE); - break; - case PixelFormat.YA88: - pixelReader = &_readPixelAndUpconv!(PixelYA88); - break; - default: - break; - } + //setup imagedata + switch (result.getPixelFormat & ~PixelFormat.BigEndian) { + case PixelFormat.Indexed1Bit: + result._imageData = new IndexedImageData1Bit(imageTemp, result._palette, result.width, result.height); + break; + case PixelFormat.Indexed2Bit: + result._imageData = new IndexedImageData2Bit(imageTemp, result._palette, result.width, result.height); + break; + case PixelFormat.Indexed4Bit: + result._imageData = new IndexedImageData4Bit(imageTemp, result._palette, result.width, result.height); + break; + case PixelFormat.Indexed8Bit: + result._imageData = new IndexedImageData!ubyte(imageTemp, result._palette, result.width, result.height); + break; + case PixelFormat.YA88: + result._imageData = new ImageData!YA88BE(reinterpretCast!YA88BE(imageTemp), result.width, result.height, + result.getPixelFormat, result.getBitdepth); + break; + case PixelFormat.RGB888: + result._imageData = new ImageData!RGB888BE(reinterpretCast!RGB888BE(imageTemp), result.width, result.height, + result.getPixelFormat, result.getBitdepth); + break; + case PixelFormat.RGBX5551: + result._imageData = new ImageData!RGBA5551(reinterpretCast!RGBA5551(imageTemp), result.width, result.height, + result.getPixelFormat, result.getBitdepth); + break; + case PixelFormat.RGBA8888: + result._imageData = new ImageData!RGBA8888BE(reinterpretCast!RGBA8888BE(imageTemp), result.width, result.height, + result.getPixelFormat, result.getBitdepth); + break; + case PixelFormat.Grayscale8Bit: + result._imageData = new ImageData!ubyte(reinterpretCast!ubyte(imageTemp), result.width, result.height, + result.getPixelFormat, result.getBitdepth); + break; + default: throw new ImageFileException("Unsupported image format!"); } + return result; } + /** * Saves the file to the disk. * Currently interlaced mode is unsupported. */ - public void save(F = std.stdio.File)(ref F file, int compLevel = 6){ + public void save(F = std.stdio.File)(ref F file, int compLevel = 6) { ubyte[] crc; + ubyte[] paletteData; + if (_palette) paletteData = _palette.raw; + ubyte[] imageTemp = _imageData.raw; //write PNG signature into file file.rawWrite(PNG_SIGNATURE); //write Header into file @@ -398,18 +416,18 @@ public class PNG : Image{ { int ret; ubyte[] input, output; - input.reserve(imageData.length + filterBytes.length); + input.reserve(imageTemp.length + filterBytes.length); for (int line ; line < height ; line++){ const size_t offset = line * pitch; input ~= filterBytes[line]; - input ~= imageData[offset..offset+pitch]; + input ~= imageTemp[offset..offset+pitch]; } zlib.z_stream strm; ret = zlib.deflateInit(&strm, compLevel); if (ret != zlib.Z_OK) throw new Exception("Compressor initialization error"); - output.length = 32 * 1024;//cast(uint)imageData.length; + output.length = 32 * 1024;//cast(uint)imageTemp.length; strm.next_in = input.ptr; strm.avail_in = cast(uint)input.length; strm.next_out = output.ptr; @@ -496,14 +514,20 @@ public class PNG : Image{ } override uint getPixelFormat() @nogc @safe @property const pure{ switch(header.colorType){ - case ColorType.GreyscaleWithAlpha: - return PixelFormat.YA88; + case ColorType.Indexed: + switch(header.bitDepth) { + case 1: return PixelFormat.Indexed1Bit; + case 2: return PixelFormat.Indexed2Bit; + case 4: return PixelFormat.Indexed4Bit; + case 8: return PixelFormat.Indexed8Bit; + default: return PixelFormat.Undefined; + } + case ColorType.GreyscaleWithAlpha: return PixelFormat.YA88 | PixelFormat.BigEndian; + case ColorType.Greyscale: return PixelFormat.Grayscale8Bit; case ColorType.TrueColor: - return header.bitDepth == 8 ? PixelFormat.RGB888 : PixelFormat.RGBX5551; - case ColorType.TrueColorWithAlpha: - return PixelFormat.RGBA8888; - default: - return PixelFormat.Undefined; + return header.bitDepth == 8 ? PixelFormat.RGB888 | PixelFormat.BigEndian : PixelFormat.RGBX5551; + case ColorType.TrueColorWithAlpha: return PixelFormat.RGBA8888 | PixelFormat.BigEndian; + default: return PixelFormat.Undefined; } } override uint getPalettePixelFormat() @nogc @safe @property const pure{ @@ -573,7 +597,7 @@ unittest{ std.stdio.writeln("The two images' output match"); } //test against TGA versions of the same images - { + /+{ std.stdio.File tgaSource = std.stdio.File("./test/tga/mapped_8.tga"); std.stdio.File pngSource = std.stdio.File("./test/png/mapped_8.png"); std.stdio.writeln("Loading ", tgaSource.name); @@ -599,5 +623,5 @@ unittest{ std.stdio.writeln("Loading ", pngSource.name); PNG pngImage = PNG.load(pngSource); compareImages!true(tgaImage, pngImage); - } + }+/ } \ No newline at end of file diff --git a/source/dimage/tga.d b/source/dimage/tga.d index 7ea47ca..d46b018 100644 --- a/source/dimage/tga.d +++ b/source/dimage/tga.d @@ -52,7 +52,7 @@ public class TGA : Image, ImageMetadata{ UncompressedMapped = 1, UncompressedTrueColor = 2, UncompressedGrayscale = 3, - RLEMapped = 9, /// RLE in 8 bit chunks + RLEMapped = 9, /// RLE in 8 or 16 bit chunks RLETrueColor = 10, RLEGrayscale = 11, /** @@ -98,6 +98,80 @@ public class TGA : Image, ImageMetadata{ bool , "topOrigin", 1, ubyte, "interleaving", 2, )); + ///Sets the colormapdepth of the image + public void setColorMapDepth(uint format) @safe pure { + switch (format) { + case PixelFormat.Grayscale8Bit: + colorMapDepth = 8; + break; + case PixelFormat.RGBX5551, PixelFormat.RGBA5551: + colorMapDepth = 16; + break; + /+case PixelFormat.RGB565: + colorMapDepth = 16; + break;+/ + case PixelFormat.RGB888: + colorMapDepth = 24; + break; + case PixelFormat.RGBX8888, PixelFormat.RGBA8888: + colorMapDepth = 32; + break; + default: throw new ImageFormatException("Palette format not supported!"); + } + } + ///Sets the pixeldepth of the image + public void setImageType(uint format, bool isRLE) @safe pure { + switch (format) { + case PixelFormat.Grayscale8Bit: + pixelDepth = 8; + if (isRLE) imageType = ImageType.RLEGrayscale; + else imageType = ImageType.UncompressedGrayscale; + break; + case PixelFormat.Indexed8Bit: + pixelDepth = 8; + if (isRLE) imageType = ImageType.RLEMapped; + else imageType = ImageType.UncompressedMapped; + break; + case PixelFormat.Indexed16Bit: + pixelDepth = 16; + if (isRLE) imageType = ImageType.RLEMapped; + else imageType = ImageType.UncompressedMapped; + break; + case PixelFormat.Indexed4Bit: + pixelDepth = 4; + if (isRLE) imageType = ImageType.RLE4BitMapped; + else imageType = ImageType.UncompressedMapped; + break; + case PixelFormat.Indexed2Bit: + pixelDepth = 2; + if (isRLE) imageType = ImageType.RLEMapped; + else imageType = ImageType.UncompressedMapped; + break; + case PixelFormat.Indexed1Bit: + pixelDepth = 1; + if (isRLE) imageType = ImageType.RLE1BitMapped; + else imageType = ImageType.UncompressedMapped; + break; + case PixelFormat.RGBA5551, PixelFormat.RGBX5551: + pixelDepth = 16; + alphaChannelBits = 1; + if (isRLE) imageType = ImageType.RLETrueColor; + else imageType = ImageType.UncompressedTrueColor; + break; + case PixelFormat.RGB888: + pixelDepth = 24; + if (isRLE) imageType = ImageType.RLETrueColor; + else imageType = ImageType.UncompressedTrueColor; + break; + case PixelFormat.RGBX8888, PixelFormat.RGBA8888: + pixelDepth = 32; + alphaChannelBits = 8; + if (isRLE) imageType = ImageType.RLETrueColor; + else imageType = ImageType.UncompressedTrueColor; + break; + default: throw new ImageFormatException("Palette format not supported!"); + } + } public string toString(){ import std.conv : to; return @@ -112,6 +186,7 @@ public class TGA : Image, ImageMetadata{ "width:" ~ to!string(width) ~ "\n" ~ "height:" ~ to!string(height) ~ "\n" ~ "pixelDepth:" ~ to!string(pixelDepth); + } } /** @@ -197,7 +272,6 @@ public class TGA : Image, ImageMetadata{ */ struct DevArea{ ubyte[] data; - /** * Returns the data as a certain type (preferrably struct) if available */ @@ -217,26 +291,43 @@ public class TGA : Image, ImageMetadata{ public uint[] scanlineTable; ///stores offset to scanlines public ubyte[] postageStampImage; ///byte 0 is width, byte 1 is height public ushort[] colorCorrectionTable; ///color correction table - mixin ChunkyAccess4Bit; - mixin ChunkyAccess2Bit; - mixin MonochromeAccess; + ///Empty CTOR for loading + protected this() @nogc @safe pure nothrow { + + } /** * Creates a TGA object without a footer. */ - public this(Header header, ubyte[] imageData, ubyte[] paletteData = null, char[] imageID = null){ + public this(Header header, IImageData _imageData, IPalette _palette = null, char[] imageID = null) @safe { this.header = header; - this.imageData = imageData; - this.paletteData = paletteData; + this._imageData = _imageData; + this._palette = _palette; this.imageID = imageID; } + /** + * Creates a TGA file safely with automatically generated header. + */ + public this(IImageData _imageData, IPalette _palette = null, char[] imageID = null, bool isRLE = false) @safe { + this._imageData = _imageData; + this._palette = _palette; + this.imageID = imageID; + header.width = cast(ubyte)_imageData.width; + header.height = cast(ubyte)_imageData.height; + if(_palette) { + header.colorMapLength = cast(ushort)this._palette.length; + header.colorMapDepth = this._palette.bitDepth; + header.colorMapType = Header.ColorMapType.ColorMapPresent; + } + header.setImageType(_imageData.pixelFormat, isRLE); + } /** * Creates a TGA object with a footer. */ - public this(Header header, Footer footer, ubyte[] imageData, ubyte[] paletteData = null, char[] imageID = null){ + public this(Header header, Footer footer, IImageData _imageData, IPalette _palette = null, char[] imageID = null) @safe { this.header = header; this.footer = footer; - this.imageData = imageData; - this.paletteData = paletteData; + this._imageData = _imageData; + this._palette = _palette; this.imageID = imageID; } /** @@ -245,7 +336,8 @@ public class TGA : Image, ImageMetadata{ */ public static TGA load(FILE = std.stdio.File, bool loadDevArea = true, bool loadExtArea = true)(ref FILE file){ import std.stdio; - ubyte[] loadRLEImageData(const ref Header header){ + TGA result = new TGA(); + ubyte[] loadRLEImageData(const ref Header header) { size_t target = header.width * header.height; const size_t bytedepth = header.pixelDepth >= 8 ? (header.pixelDepth / 8) : 1; target >>= header.pixelDepth < 8 ? 1 : 0; @@ -253,7 +345,7 @@ public class TGA : Image, ImageMetadata{ target >>= header.pixelDepth < 2 ? 1 : 0; ubyte[] result, dataBuffer; result.reserve(header.pixelDepth >= 8 ? target * bytedepth : target); - switch(header.pixelDepth){ + switch(header.pixelDepth) { case 15, 16: dataBuffer.length = 3; break; @@ -291,37 +383,76 @@ public class TGA : Image, ImageMetadata{ assert(result.length == (header.width * header.height * bytedepth), "RLE length mismatch error!"); return result; } - Header headerLoad; ubyte[] readBuffer; readBuffer.length = Header.sizeof; file.rawRead(readBuffer); - headerLoad = reinterpretCast!Header(readBuffer)[0]; - char[] imageIDLoad; - imageIDLoad.length = headerLoad.idLength; - if(imageIDLoad.length) file.rawRead(imageIDLoad); - //version(unittest) std.stdio.writeln(imageIDLoad); + result.header = reinterpretGet!Header(readBuffer); + result.imageID.length = result.header.idLength; + if (result.imageID.length) file.rawRead(result.imageID); ubyte[] palette; - palette.length = headerLoad.colorMapLength * (headerLoad.colorMapDepth / 8); - if(palette.length) file.rawRead(palette); + palette.length = result.header.colorMapLength * (result.getPaletteBitdepth / 8); + if (palette.length) { + file.rawRead(palette); + switch (result.getPalettePixelFormat) { + case PixelFormat.Grayscale8Bit: + result._palette = new Palette!ubyte(palette, PixelFormat.Grayscale8Bit, 8); + break; + case PixelFormat.RGBA5551: + result._palette = new Palette!RGBA5551(reinterpretCast!RGBA5551(palette), PixelFormat.RGBA5551, 16); + break; + case PixelFormat.RGB888: + result._palette = new Palette!RGB888(reinterpretCast!RGB888(palette), PixelFormat.RGB888, 24); + break; + case PixelFormat.ARGB8888: + result._palette = new Palette!ARGB8888(reinterpretCast!ARGB8888(palette), PixelFormat.ARGB8888, 32); + break; + default: throw new ImageFileException("Unknown palette type!"); + } + } ubyte[] image; - if(headerLoad.imageType >= Header.ImageType.RLEMapped && headerLoad.imageType <= Header.ImageType.RLEGrayscale){ - image = loadRLEImageData(headerLoad); - }else{ - image.length = (headerLoad.width * headerLoad.height * headerLoad.pixelDepth) / 8; - //version(unittest) std.stdio.writeln(headerLoad.toString); + if (result.header.imageType >= Header.ImageType.RLEMapped && result.header.imageType <= Header.ImageType.RLEGrayscale) { + image = loadRLEImageData(result.header); + } else { + image.length = (result.header.width * result.header.height * result.header.pixelDepth) / 8; if(image.length) file.rawRead(image); } + if (image.length) { + switch (result.getPixelFormat) { + case PixelFormat.Grayscale8Bit: + result._imageData = new ImageData!ubyte(image, result.width, result.height, PixelFormat.Grayscale8Bit, 8); + break; + case PixelFormat.RGBA5551: + result._imageData = new ImageData!RGBA5551(reinterpretCast!RGBA5551(image), result.width, result.height, + PixelFormat.RGBA5551, 16); + break; + case PixelFormat.RGB888: + result._imageData = new ImageData!RGB888(reinterpretCast!RGB888(image), result.width, result.height, + PixelFormat.RGB888, 24); + break; + case PixelFormat.ARGB8888: + result._imageData = new ImageData!ARGB8888(reinterpretCast!ARGB8888(image), result.width, result.height, + PixelFormat.ARGB8888, 32); + break; + case PixelFormat.Indexed8Bit: + result._imageData = new IndexedImageData!ubyte(image, result._palette, result.width, result.height); + break; + case PixelFormat.Indexed16Bit: + result._imageData = new IndexedImageData!ushort(reinterpretCast!ushort(image), result._palette, result.width, + result.height); + break; + default: throw new ImageFileException("Unknown Image type!"); + } + } static if(loadExtArea || loadDevArea){ - Footer footerLoad; readBuffer.length = Footer.sizeof; - file.seek(Footer.sizeof * -1, SEEK_END); + file.seek(cast(int)Footer.sizeof * -1, SEEK_END); file.rawRead(readBuffer); - footerLoad = reinterpretCast!Footer(readBuffer)[0]; - TGA result = new TGA(headerLoad, footerLoad, image, palette, imageIDLoad); - if(footerLoad.isValid){ + result.footer = reinterpretGet!Footer(readBuffer); + //TGA result = new TGA(result.header, footerLoad, image, palette, imageIDLoad); + if(result.footer.isValid){ static if(loadDevArea){ - if(footerLoad.developerAreaOffset){ - file.seek(footerLoad.developerAreaOffset); + if(result.footer.developerAreaOffset){ + file.seek(result.footer.developerAreaOffset); result.developerAreaTags.length = 1; file.rawRead(result.developerAreaTags); result.developerAreaTags.length = result.developerAreaTags[0].reserved; @@ -337,8 +468,8 @@ public class TGA : Image, ImageMetadata{ } } static if(loadExtArea){ - if(footerLoad.extensionAreaOffset){ - file.seek(footerLoad.extensionAreaOffset); + if(result.footer.extensionAreaOffset){ + file.seek(result.footer.extensionAreaOffset); result.extensionArea.length = 1; file.rawRead(result.extensionArea); if(result.extensionArea[0].postageStampOffset){ @@ -354,18 +485,16 @@ public class TGA : Image, ImageMetadata{ file.rawRead(result.colorCorrectionTable); } if(result.extensionArea[0].scanlineOffset){ - result.scanlineTable.length = headerLoad.height; + result.scanlineTable.length = result.header.height; file.seek(result.extensionArea[0].scanlineOffset); file.rawRead(result.scanlineTable); } } } } - result.setupDelegates(); return result; }else{ - TGA result = new TGA(headerLoad, image, palette, imageIDLoad); - result.setupDelegates(); + //TGA result = new TGA(result.header, image, palette, imageIDLoad); return result; } @@ -378,7 +507,9 @@ public class TGA : Image, ImageMetadata{ public void save(FILE = std.stdio.File, bool saveDevArea = false, bool saveExtArea = false, bool ignoreScanlineBounds = false)(ref FILE file){ import std.stdio; - void compressRLE(){ + ubyte[] imageBuffer; + if(_imageData) imageBuffer = _imageData.raw; + void compressRLE(){ //Help!!! I haven't used templates for this and I can't debug!!! static if(!ignoreScanlineBounds){ const uint maxScanlineLength = header.width; scanlineTable.length = 0; @@ -390,8 +521,8 @@ public class TGA : Image, ImageMetadata{ uint currScanlineLength; switch(header.pixelDepth){ case 16: - ushort* src = cast(ushort*)(cast(void*)imageData.ptr); - const ushort* dest = src + (imageData.length / 2); + ushort* src = cast(ushort*)(cast(void*)imageBuffer.ptr); + const ushort* dest = src + (imageBuffer.length / 2); writeBuff.length = 257; ushort* writeBuff0 = cast(ushort*)(cast(void*)writeBuff.ptr + 1); while(src < dest){ @@ -460,13 +591,13 @@ public class TGA : Image, ImageMetadata{ } break; case 24: - Pixel24Bit* src = cast(Pixel24Bit*)(cast(void*)imageData.ptr); - const Pixel24Bit* dest = src + (imageData.length / 3); + RGB888* src = cast(RGB888*)(cast(void*)imageBuffer.ptr); + const RGB888* dest = src + (imageBuffer.length / 3); writeBuff.length = 1; - Pixel24Bit[] writeBuff0; + RGB888[] writeBuff0; writeBuff0.length = 128; while(src < dest){ - Pixel24Bit* currBlockBegin = src, currBlockEnd = src; + RGB888* currBlockBegin = src, currBlockEnd = src; if(currBlockBegin[0] == currBlockBegin[1]){ //RLE block ubyte blockLength; //while(src < dest && currBlockEnd[0] == currBlockEnd[1]){ @@ -532,8 +663,8 @@ public class TGA : Image, ImageMetadata{ } break; case 32: - uint* src = cast(uint*)(cast(void*)imageData.ptr); - const uint* dest = src + (imageData.length / 4); + uint* src = cast(uint*)(cast(void*)imageBuffer.ptr); + const uint* dest = src + (imageBuffer.length / 4); writeBuff.length = 1; uint[] writeBuff0; writeBuff0.length = 128; @@ -604,8 +735,8 @@ public class TGA : Image, ImageMetadata{ } break; default: - ubyte* src = imageData.ptr; - const ubyte* dest = src + imageData.length; + ubyte* src = imageBuffer.ptr; + const ubyte* dest = src + imageBuffer.length; writeBuff.length = 129; while(src < dest){ ubyte* currBlockBegin = src, currBlockEnd = src; @@ -630,7 +761,7 @@ public class TGA : Image, ImageMetadata{ version(unittest){ import std.conv : to; pixelCount += blockLength; - assert(pixelCount <= imageData.length, "Required size: " ~ to!string(imageData.length) + assert(pixelCount <= imageBuffer.length, "Required size: " ~ to!string(imageBuffer.length) ~ " Current size:" ~ to!string(pixelCount)); } blockLength--; @@ -662,7 +793,7 @@ public class TGA : Image, ImageMetadata{ version(unittest){ import std.conv : to; pixelCount += blockLength; - assert(pixelCount <= imageData.length, "Required size: " ~ to!string(imageData.length) + assert(pixelCount <= imageBuffer.length, "Required size: " ~ to!string(imageBuffer.length) ~ " Current size:" ~ to!string(pixelCount)); } blockLength--; @@ -681,11 +812,11 @@ public class TGA : Image, ImageMetadata{ //write most of the data into the file file.rawWrite([header]); file.rawWrite(imageID); - file.rawWrite(paletteData); + if (_palette) file.rawWrite(_palette.raw); if(header.imageType >= Header.ImageType.RLEMapped && header.imageType <= Header.ImageType.RLEGrayscale){ compressRLE(); }else{ - file.rawWrite(imageData); + file.rawWrite(_imageData.raw); } static if(saveDevArea){ if(developerAreaTags.length){ @@ -748,79 +879,6 @@ public class TGA : Image, ImageMetadata{ file.rawWrite([footer]); } } - /** - * Sets up all the function pointers automatically. - */ - protected void setupDelegates() @safe pure { - void _createBitArray() @trusted pure { - bitArray = BitArray(reinterpretCast!void(imageData), pitch * header.width); - } - switch(header.pixelDepth){ - case 1: - indexReader8Bit = &_readIndex_1bit; - indexWriter8Bit = &_writeIndex_1bit; - indexReader16bit = &_indexReadUpconv; - pitch = width + (header.width % 8 ? 8 - header.width % 8 : 0); - _createBitArray; - pixelReader = &_readAndLookup; - break; - case 2: - indexReader8Bit = &_readIndex_2bit; - indexWriter8Bit = &_writeIndex_2bit; - indexReader16bit = &_indexReadUpconv; - pixelReader = &_readAndLookup; - break; - case 4: - indexReader8Bit = &_readIndex_4bit; - indexWriter8Bit = &_writeIndex_4bit; - indexReader16bit = &_indexReadUpconv; - pixelReader = &_readAndLookup; - break; - case 8: - if (header.colorMapDepth) { - indexReader8Bit = &_readPixel_8bit; - indexWriter8Bit = &_writePixel!(ubyte); - indexReader16bit = &_indexReadUpconv; - pixelReader = &_readAndLookup; - } else { - pixelReader = &_readPixelAndUpconv!(ubyte); - } - break; - case 16: - if (header.colorMapDepth) { - indexWriter16bit = &_writePixel!(ushort); - indexReader16bit = &_readPixel_16bit; - pixelReader = &_readAndLookup; - } else { - pixelReader = &_readPixelAndUpconv!(PixelRGBA5551); - } - break; - case 24: - pixelReader = &_readPixelAndUpconv!(Pixel24Bit); - break; - case 32: - pixelReader = &_readPixel!(Pixel32Bit); - break; - default: - break; - } - switch (header.colorMapDepth) { - case 8: - paletteReader = &_paletteReader!ubyte; - break; - case 16: - paletteReader = &_paletteReader!PixelRGBA5551; - break; - case 24: - paletteReader = &_paletteReader!Pixel24Bit; - break; - case 32: - paletteReader = &_paletteReader!Pixel32Bit; - break; - default: - break; - } - } override uint width() @nogc @safe @property const pure{ return header.width; } @@ -833,15 +891,27 @@ public class TGA : Image, ImageMetadata{ override ubyte getBitdepth() @nogc @safe @property const pure{ return header.pixelDepth; } - override ubyte getPaletteBitdepth() @nogc @safe @property const pure{ + override ubyte getPaletteBitdepth() @nogc @safe @property const pure { return header.colorMapDepth; } - override uint getPixelFormat() @nogc @safe @property const pure{ - if(!Header.ColorMapType.NoColorMapPresent){ - return PixelFormat.Undefined; - }else{ + override uint getPixelFormat() @nogc @safe @property const pure { + if (header.imageType == Header.ImageType.UncompressedMapped || header.imageType == Header.ImageType.RLEMapped || + header.imageType == Header.ImageType.RLE4BitMapped || header.imageType == Header.ImageType.RLE1BitMapped) { + switch (header.pixelDepth) { + case 1: return PixelFormat.Indexed1Bit; + case 2: return PixelFormat.Indexed2Bit; + case 4: return PixelFormat.Indexed4Bit; + case 8: return PixelFormat.Indexed8Bit; + case 16: return PixelFormat.Indexed16Bit; + default: return PixelFormat.Undefined; + } + } else if (header.imageType == Header.ImageType.RLEGrayscale || + header.imageType == Header.ImageType.UncompressedGrayscale) { + if (header.pixelDepth == 8) return PixelFormat.Grayscale8Bit; + else return PixelFormat.Undefined; + } else { //Must be truecolor switch(header.pixelDepth){ - case 16: + case 15, 16: return PixelFormat.RGBA5551; case 24: return PixelFormat.RGB888; @@ -857,14 +927,11 @@ public class TGA : Image, ImageMetadata{ return PixelFormat.Undefined; }else{ switch(header.colorMapDepth){ - case 16: - return PixelFormat.RGBA5551; - case 24: - return PixelFormat.RGB888; - case 32: - return PixelFormat.ARGB8888; - default: - return PixelFormat.Undefined; + case 8: return PixelFormat.Grayscale8Bit; + case 15, 16: return PixelFormat.RGBA5551; + case 24: return PixelFormat.RGB888; + case 32: return PixelFormat.ARGB8888; + default: return PixelFormat.Undefined; } } } @@ -1031,23 +1098,6 @@ public class TGA : Image, ImageMetadata{ unittest{ import std.conv : to; import vfile; - /+void compareImages(Image a, Image b){ - assert(a.width == b.width); - assert(a.height == b.height); - //Check if the data in the two are identical - for (ushort y ; y < a.height ; y++) { - for (ushort x ; x < a.width ; x++) { - assert(a.readPixel(x,y) == b.readPixel(x,y), "Error at position (" ~ to!string(x) ~ "," ~ to!string(y) ~ ")!"); - } - } - if (a.isIndexed && b.isIndexed) { - auto aPal = a.palette; - auto bPal = b.palette; - for (ushort i ; i < aPal.length ; i++) { - assert(aPal[i] == bPal[i], "Error at position " ~ to!string(i) ~ "!"); - } - } - }+/ assert(TGA.Header.sizeof == 18); //void[] tempStream; //test 8 bit RLE load for 8 bit greyscale and indexed @@ -1074,30 +1124,34 @@ unittest{ compareImages(greyscaleUnc, greyscaleRLE); } { - std.stdio.File greyscaleUncFile = std.stdio.File("test/tga/mapped_8.tga"); - std.stdio.writeln("Loading ", greyscaleUncFile.name); - TGA greyscaleUnc = TGA.load(greyscaleUncFile); - std.stdio.writeln("File `", greyscaleUncFile.name, "` successfully loaded"); - std.stdio.File greyscaleRLEFile = std.stdio.File("test/tga/mapped_8_rle.tga"); - std.stdio.writeln("Loading ", greyscaleRLEFile.name); - TGA greyscaleRLE = TGA.load(greyscaleRLEFile); - std.stdio.writeln("File `", greyscaleRLEFile.name, "` successfully loaded"); - compareImages(greyscaleUnc, greyscaleRLE); + std.stdio.File mappedUncFile = std.stdio.File("test/tga/mapped_8.tga"); + std.stdio.writeln("Loading ", mappedUncFile.name); + TGA mappedUnc = TGA.load(mappedUncFile); + std.stdio.writeln("File `", mappedUncFile.name, "` successfully loaded"); + std.stdio.File mappedRLEFile = std.stdio.File("test/tga/mapped_8_rle.tga"); + std.stdio.writeln("Loading ", mappedRLEFile.name); + TGA mappedRLE = TGA.load(mappedRLEFile); + std.stdio.writeln("File `", mappedRLEFile.name, "` successfully loaded"); + compareImages(mappedUnc, mappedRLE); //store the uncompressed one as a VFile in the memory using RLE, then restore it and check if it's working. - greyscaleUnc.getHeader.imageType = TGA.Header.ImageType.RLEMapped; + mappedUnc.getHeader.imageType = TGA.Header.ImageType.RLEMapped; VFile virtualFile;// = VFile(tempStream); //std.stdio.File virtualFile = std.stdio.File("test/tga/grey_8_rle_gen.tga", "wb"); - greyscaleUnc.save!(VFile, false, false, true)(virtualFile); + mappedUnc.save!(VFile, false, false, true)(virtualFile); std.stdio.writeln("Save to virtual file was successful"); std.stdio.writeln(virtualFile.size); virtualFile.seek(0); - greyscaleRLE = TGA.load!VFile(virtualFile); + mappedRLE = TGA.load!VFile(virtualFile); std.stdio.writeln("Load from virtual file was successful"); - compareImages(greyscaleUnc, greyscaleRLE); - //std.stdio.writeln(greyscaleUnc.palette); - /+foreach (c ; greyscaleUnc.palette) { - std.stdio.writeln(); - }+/ + compareImages(mappedUnc, mappedRLE); + { + Palette!RGBA5551 p = cast(Palette!RGBA5551)mappedUnc.palette; + //std.stdio.writeln(mappedUnc.getHeader); + foreach_reverse (c ; p) { + } + foreach (c ; p) { + } + } } { std.stdio.File greyscaleUncFile = std.stdio.File("test/tga/concreteGUIE3.tga"); @@ -1142,6 +1196,12 @@ unittest{ greyscaleRLE = TGA.load!VFile(virtualFile); std.stdio.writeln("Load from virtual file was successful"); compareImages(greyscaleUnc, greyscaleRLE); + //Test upconversion and handling of 16 bit images + ImageData!RGBA5551 imageData = cast(ImageData!RGBA5551)greyscaleUnc.imageData; + TGA upconvToRGB888 = new TGA(imageData.convTo(PixelFormat.RGB888), null, null, true); + + std.stdio.File output = std.stdio.File("test/tga/test.tga", "wb"); + upconvToRGB888.save(output); } { std.stdio.File greyscaleUncFile = std.stdio.File("test/tga/truecolor_24.tga"); diff --git a/source/dimage/tiff.d b/source/dimage/tiff.d index 98d94cb..c690cfa 100644 --- a/source/dimage/tiff.d +++ b/source/dimage/tiff.d @@ -1 +1,434 @@ -module dimage.tiff; \ No newline at end of file +/* + * dimage - png.d + * by Laszlo Szeremi + * + * Copyright under Boost Software License. + */ + +module dimage.tiff; + +import dimage.base; +import bitleveld.datatypes; +import dimage.util; +import std.bitmanip; + +static import std.stdio; + +/** + * Implements *.TIFF file handling. + * LZW compression support requires linking abainst ncompress42. + * JPEG support requires a codec of some sort. + */ +public class TIFF : Image, MultiImage, ImageMetadata { + /** + * Byte order identification for header. + * LE means little, BE means big endianness + */ + public enum ByteOrderIdentifier : ushort { + LE = 0x4949, + BE = 0x4D4D, + } + /** + * Standard TIFF header. Loaded as a bytestream at once. + */ + public struct Header { + align(1): + ushort identifier; ///Byte order identifier. + ushort vers = 0x2A; ///Version number. + uint offset; ///Offset of first image file directory. + /** + * Switches endianness if needed. + * Also changes identifier to the host machine's native endianness to avoid problems from conversion. + */ + void switchEndian() { + version (LittleEndian) { + if (identifier == ByteOrderIdentifier.BE) { + identifier = ByteOrderIdentifier.LE; + vers = swapEndian(vers); + offset = swapEndian(offset); + } + } else { + if (identifier == ByteOrderIdentifier.LE) { + identifier = ByteOrderIdentifier.BE; + vers = swapEndian(vers); + offset = swapEndian(offset); + } + } + } + } + /** + * TIFF Image File Directory. Loaded manually due to the nature of this field. + */ + public struct IFD { + /** + * TIFF data types. + */ + public enum DataType : ushort { + NULL = 0, + BYTE = 1, ///unsigned byte + ASCII = 2, ///null terminated string, ASCII + SHORT = 3, ///unsigned short + LONG = 4, ///unsigned int + RATIONAL = 5, ///two unsigned ints + + SBYTE = 6, ///signed byte + UNDEFINED = 7, ///byte + SSHORT = 8, ///signed short + SLONG = 9, ///signed int + SRATIONAL = 10, ///two signed ints + FLOAT = 11, ///IEEE single-precision floating-point + DOUBLE = 12, ///IEEE double-precision floating-point + } + /** + * Common TIFF entry identifiers. + */ + public enum DataEntryID : ushort { + Artist = 315, + BadFaxLines = 326, + BitsPerSample = 258, + CellLength = 265, + CellWidth = 264, + CleanFaxData = 327, + ColorMap = 320, + ColorResponseCurve = 301, + ColorResponseUnit = 300, + Compression = 259, + ConsecuitiveBadFaxLines = 328, + Copyright = 33_432, + DateTime = 306, + DocumentName = 269, + DotRange = 336, + ExtraSamples = 338, + FillOrder = 266, + FreeByteCounts = 289, + FreeOffsets = 288, + GrayResponseCurve = 291, + GrayResponseUnit = 290, + HalftoneHints = 321, + HostComputer = 316, + ImageDescription= 270, + ImageHeight = 257, + ImageWidth = 256, + InkNames = 333, + InkSet = 332, + JPEGACTTables = 521, + JPEGDCTTables = 520, + JPEGInterchangeFormat = 513, + JPEGInterchangeFormatLength = 514, + JPEGLosslessPredictors = 517, + JPEGPointTransforms = 518, + JPEGProc = 512, + JPEGRestartInterval = 515, + JPEGQTables = 519, + Make = 271, + MaxSampleValue = 281, + MinSampleValue = 280, + Model = 272, + NewSubFileType = 254, + NumberOfInks = 334, + Orientation = 274, + PageName = 285, + PageNumber = 297, + PhotometricInterpretation = 262, + WhiteIsZero = 0, + BlackIsZero = 1, + RGB = 2, + RGBPalette = 3, + TransparencyMask= 4, + CMYK = 5, + YCbCr = 6, + CIELab = 8, + PlanarConfiguration = 284, + Predictor = 317, + PrimaryChromaticities = 319, + ReferenceBlackWhite = 532, + ResolutionUnit = 296, + RowsPerStrip = 278, + SampleFormat = 339, + SamplesPerPixel = 277, + SMaxSampleValue = 341, + SMinSampleValue = 340, + Software = 305, + StripByteCounts = 279, + StripOffsets = 273, + SubFileType = 255, + T4Options = 292, + T6Options = 293, + TargetPrinter = 337, + Thresholding = 263, + TileByteCounts = 325, + TileLength = 323, + TileOffsets = 324, + TileWidth = 322, + TransferFunction= 301, + TransferRange = 342, + XPosition = 286, + XResolution = 282, + YCbCrCoefficients = 529, + YCbCrPositioning= 531, + YCbCrSubSampling= 530, + YPosition = 287, + YResolution = 283, + WhitePoint = 318, + } + /** + * TIFF tag. Loaded as bytestream at once. + */ + public struct Tag { + align(1): + ushort tagID; ///Tag identifier. + ushort dataType; ///Type of the data items. + uint dataCount; ///The amount of data stored within this tag. + uint dataOffset; ///The offset of data in bytes. + + void switchEndian() @nogc @safe pure nothrow { + tagID = swapEndian(tagID); + dataType = swapEndian(dataType); + dataCount = swapEndian(dataCount); + dataOffset = swapEndian(dataOffset); + } + ubyte[] switchEndianOfData(ubyte[] data) @safe pure { + switch(dataType){ + case DataType.SHORT, DataType.SSHORT: + ushort[] workPad = reinterpretCast!ushort(data); + for (size_t i ; i < workPad.length ; i++) + workPad[i] = swapEndian(workPad[i]); + return reinterpretCast!ubyte(workPad); + case DataType.LONG, DataType.SLONG, DataType.FLOAT, DataType.RATIONAL, DataType.SRATIONAL: + uint[] workPad = reinterpretCast!uint(data); + for (size_t i ; i < workPad.length ; i++) + workPad[i] = swapEndian(workPad[i]); + return reinterpretCast!ubyte(workPad); + case DataType.DOUBLE: + ulong[] workPad = reinterpretCast!ulong(data); + for (size_t i ; i < workPad.length ; i++) + workPad[i] = swapEndian(workPad[i]); + return reinterpretCast!ubyte(workPad); + default: return []; + } + } + @property size_t dataSize() @nogc @safe pure nothrow const { + switch(dataType){ + case DataType.SHORT, DataType.SSHORT: + return dataCount * 2; + case DataType.LONG, DataType.SLONG, DataType.FLOAT: + return dataCount * 4; + case DataType.RATIONAL, DataType.SRATIONAL, DataType.DOUBLE: + return dataCount * 8; + default: + return dataCount; + } + } + } + align(1): + ushort numDirEntries; ///Number of entries. + Tag[] tagList; ///List of tags in this field. + ubyte[][] tagData; ///Stores each datafields. + uint nextOffset; ///Offset of next IFD + + /** + * Returns the first instance of a given tag if exists, or returns uint.max if not. + */ + uint getTagNum(ushort tagID) @nogc @safe nothrow pure const { + foreach (size_t key, Tag elem; tagList) { + if (elem.tagID == tagID) return cast(uint)key; + } + return uint.max; + } + } + protected Header header; ///TIFF file header. + protected IFD[] directoryEntries; ///Image data. + protected uint currentImg; ///Current image selected with the MultiImage interface's functions. + protected uint _width, _height; ///Sizes of the current image + protected ubyte _bitdepth, _palbitdepth; ///Bitdepths of the current image + protected uint _pixelFormat, _palettePixelFormat; ///Pixelformat of the current image + ///CTOR for loader + protected this() @nogc @safe pure nothrow {} + + /** + * Loads a TIFF file from either disk or memory. + */ + public static TIFF load(FILE = std.stdio.File, bool keepJPEG = false)(ref FILE file) { + TIFF result = new TIFF(); + ubyte[] buffer; + buffer.length = Header.sizeof; + buffer = file.rawRead(buffer); + if(buffer.length != Header.sizeof) throw new ImageFileException("File doesn't contain TIFF header!"); + result.header = reinterpretGet!Header(buffer); + result.header.switchEndian(); + if(!result.header.offset) throw new ImageFileException("File doesn't contain any images!"); + size_t pos = result.header.offset; + + //Load Image Data + while(pos) { + file.seek(pos, std.stdio.SEEK_CUR); + buffer.length = ushort.sizeof; + IFD entry; + buffer = file.rawRead(buffer); + if(buffer.length != ushort.sizeof) throw new ImageFileException("File access error or missing parts!"); + version (LittleEndian){ + if(result.header.identifier == ByteEndianness.BE) entry.numDirEntries = swapEndian(reinterpretGet!ushort(buffer)); + } else { + if(result.header.identifier == ByteEndianness.LE) entry.numDirEntries = swapEndian(reinterpretGet!ushort(buffer)); + } + IFD.tagData.length = entry.numDirEntries; + for (ushort i ; i < entry.numDirEntries ; i++){ + buffer.length = IFD.Tag.sizeof; + buffer = file.rawRead(buffer); + if(buffer.length == IFD.Tag.sizeof) + throw new ImageFileException("File access error or missing parts!"); + IFD.Tag tag = reinterpretCast!IFD.Tag(buffer); + version(LittleEndian) { + if(result.header.identifier == ByteOrderIdentifier.BE) { + tag.switchEndian(); + } + } else { + if(result.header.identifier == ByteOrderIdentifier.LE) { + tag.switchEndian(); + } + } + if(tag.dataSize > 4) { + file.seek(tag.dataOffset, std.stdio.SEEK_CUR); + buffer.length = tag.dataSize; + buffer = file.rawRead(buffer); + if(buffer.length == tag.dataSize) + throw new ImageFileException("File access error or missing parts!"); + version (LittleEndian) { + if (result.header.identifier == ByteEndianness.LE) entry.tagData[i] = buffer; + else entry.tagData[i] = tag.switchEndianOfData(buffer); + } else { + if (result.header.identifier == ByteEndianness.BE) entry.tagData[i] = buffer; + else entry.tagData[i] = tag.switchEndianOfData(buffer); + } + file.seek((tag.dataOffset + tag.dataSize) * -1, std.stdio.SEEK_CUR); + } else { + entry.tagData[i] = reinterpretAsArray!ubyte(tag.dataOffset); + entry.tagData[i].length = tag.dataSize; + version (LittleEndian) { + if (result.header.identifier == ByteEndianness.BE) entry.tagData[i] = tag.switchEndianOfData(entry.tagData[i]); + } else { + if (result.header.identifier == ByteEndianness.LE) entry.tagData[i] = tag.switchEndianOfData(entry.tagData[i]); + } + } + entry.tagList ~= tag; + } + //stitch image together if uncompressed, or decompress if needed. + + buffer.length = uint.sizeof; + buffer = file.rawRead(buffer); + if(buffer.length != uint.sizeof) throw new ImageFileException("File access error or missing parts!"); + //entry.nextOffset = reinterpretGet!uint(buffer); + version (LittleEndian){ + if(result.header.identifier == ByteEndianness.LE) entry.nextOffset = reinterpretGet!uint(buffer); + else entry.nextOffset = swapEndian(reinterpretGet!uint(buffer)); + } else { + if(result.header.identifier == ByteEndianness.BE) entry.nextOffset = reinterpretGet!uint(buffer); + else entry.nextOffset = swapEndian(reinterpretGet!uint(buffer)); + } + pos = entry.nextOffset; + result.directoryEntries ~= entry; + } + + return result; + } + + override uint width() @safe @property pure const { + return _width; + } + + override uint height() @safe @property pure const { + return _height; + } + + override bool isIndexed() @nogc @safe @property pure const { + return _palbitdepth != 0; + } + + override ubyte getBitdepth() @nogc @safe @property pure const { + return _bitdepth; + } + + override ubyte getPaletteBitdepth() @nogc @safe @property pure const { + if(isIndexed) return 48; + return ubyte.init; + } + + override uint getPixelFormat() @nogc @safe @property pure const { + return _pixelFormat; + } + + override uint getPalettePixelFormat() @nogc @safe @property pure const { + if (isIndexed) return PixelFormat.RGB16_16_16; + return uint.init; // TODO: implement + } + + public uint getCurrentImage() @safe pure { + return currentImg; // TODO: implement + } + + public uint setCurrentImage(uint frame) @safe pure { + return currentImg = frame; // TODO: implement + } + + public uint nOfImages() @property @safe @nogc pure const { + return cast(uint)directoryEntries.length; + } + + public uint frameTime() @property @safe @nogc pure const { + return uint.init; + } + + public bool isAnimation() @property @safe @nogc pure const { + return false; + } + + public string getID() @safe pure { + return string.init; // TODO: implement + } + + public string getAuthor() @safe pure { + return string.init; // TODO: implement + } + + public string getComment() @safe pure { + return string.init; // TODO: implement + } + + public string getJobName() @safe pure { + return string.init; // TODO: implement + } + + public string getSoftwareInfo() @safe pure { + return string.init; // TODO: implement + } + + public string getSoftwareVersion() @safe pure { + return string.init; // TODO: implement + } + + public string setID(string val) @safe pure { + return string.init; // TODO: implement + } + + public string setAuthor(string val) @safe pure { + return string.init; // TODO: implement + } + + public string setComment(string val) @safe pure { + return string.init; // TODO: implement + } + + public string setJobName(string val) @safe pure { + return string.init; // TODO: implement + } + + public string setSoftwareInfo(string val) @safe pure { + return string.init; // TODO: implement + } + + public string setSoftwareVersion(string val) @safe pure { + return string.init; // TODO: implement + } + + +} \ No newline at end of file diff --git a/source/dimage/types.d b/source/dimage/types.d new file mode 100644 index 0000000..0969530 --- /dev/null +++ b/source/dimage/types.d @@ -0,0 +1,422 @@ +module dimage.types; + +import std.bitmanip : bitfields; + +///Sets the byteorder of +enum Endianness { + Little, + Big +} + +alias ARGB8888 = ARGB8888Templ!(Endianness.Little); +alias ARGB8888BE = ARGB8888Templ!(Endianness.Big); + +/** + * Standard 32 bit pixel representation. + */ +struct ARGB8888Templ (Endianness byteOrder = Endianness.Little) { + union{ + ubyte[4] bytes; /// BGRA + uint base; /// Direct address + } + static if (byteOrder == Endianness.Big) { + ///Red + @safe @nogc @property pure ref auto r() inout { return bytes[1]; } + ///Green + @safe @nogc @property pure ref auto g() inout { return bytes[2]; } + ///Blue + @safe @nogc @property pure ref auto b() inout { return bytes[3]; } + ///Alpha + @safe @nogc @property pure ref auto a() inout { return bytes[0]; } + } else { + ///Red + @safe @nogc @property pure ref auto r() inout { return bytes[2]; } + ///Green + @safe @nogc @property pure ref auto g() inout { return bytes[1]; } + ///Blue + @safe @nogc @property pure ref auto b() inout { return bytes[0]; } + ///Alpha + @safe @nogc @property pure ref auto a() inout { return bytes[3]; } + } + ///Creates a standard pixel representation out from a 4 element array + this(ubyte[4] bytes) @safe @nogc pure{ + this.bytes = bytes; + } + ///Creates a standard pixel representation out from 4 separate values + this(ubyte r, ubyte g, ubyte b, ubyte a) @safe @nogc pure { + this.b = b; + this.g = g; + this.r = r; + this.a = a; + } + ///Template for pixel conversion + this(T)(T p) @safe @nogc pure { + this.b = p.b; + this.g = p.g; + this.r = p.r; + this.a = p.a; + } + ///Conversion from 8bit monochrome + this(ubyte p) @safe @nogc pure { + this.b = p; + this.g = p; + this.r = p; + this.a = 0xFF; + } + ///String representation of this struct + string toString() @safe pure { + import std.conv : to; + return to!string(r) ~ "," ~ to!string(g) ~ "," ~ to!string(b) ~ "," ~ to!string(a); + } + static bool hasAlphaChannelSupport() {return true;} +} + +alias RGBA8888BE = RGBA8888Templ!(Endianness.Big); +alias RGBA8888 = RGBA8888Templ!(Endianness.Little); +/** + * Standard 32 bit pixel representation. + */ +struct RGBA8888Templ (Endianness byteOrder = Endianness.Little) { + union{ + ubyte[4] bytes; /// RGBA + uint base; /// Direct address + } + static if (byteOrder == Endianness.Big) { + ///Red + @safe @nogc @property pure ref auto r() inout { return bytes[0]; } + ///Green + @safe @nogc @property pure ref auto g() inout { return bytes[1]; } + ///Blue + @safe @nogc @property pure ref auto b() inout { return bytes[2]; } + ///Alpha + @safe @nogc @property pure ref auto a() inout { return bytes[3]; } + } else { + ///Red + @safe @nogc @property pure ref auto r() inout { return bytes[3]; } + ///Green + @safe @nogc @property pure ref auto g() inout { return bytes[2]; } + ///Blue + @safe @nogc @property pure ref auto b() inout { return bytes[1]; } + ///Alpha + @safe @nogc @property pure ref auto a() inout { return bytes[0]; } + } + ///Creates a standard pixel representation out from a 4 element array + this(ubyte[4] bytes) @safe @nogc pure nothrow { + this.bytes = bytes; + } + ///Creates a standard pixel representation out from 4 separate values + this(ubyte r, ubyte g, ubyte b, ubyte a) @safe @nogc pure nothrow { + bytes[0] = r; + bytes[1] = g; + bytes[2] = b; + bytes[3] = a; + } + ///Template for pixel conversion + this(T)(T p) @safe @nogc pure nothrow { + this.b = p.b; + this.g = p.g; + this.r = p.r; + this.a = p.a; + } + ///Conversion from 8bit monochrome + this(ubyte p) @safe @nogc pure { + this.b = p; + this.g = p; + this.r = p; + this.a = 0xFF; + } + static bool hasAlphaChannelSupport() {return true;} +} +alias YA88 = YA88Templ!(Endianness.Little); +alias YA88BE = YA88Templ!(Endianness.Big); +/** + * For monochrome images with a single channel + */ +struct YA88Templ (Endianness byteOrder = Endianness.Little) { + union{ + ushort base; /// direct access + ubyte[2] channels; /// individual access + } + ///Standard CTOR + this(ubyte y, ubyte a) @safe @nogc pure nothrow { + this.y = y; + this.a = a; + } + /// Converter CTOR + /// Uses mathematical average to calculate luminance value + this(T)(T src) @safe @nogc pure nothrow { + y = (src.r + src.g + src.b) / 3; + a = src.a; + } + ///Conversion from 8bit monochrome + this(ubyte p) @safe @nogc pure { + this.y = p; + this.a = 0xFF; + } + /// luminance + nothrow @safe @nogc @property pure ref auto y() inout { + static if(byteOrder == Endianness.Big) { + return channels[1]; + } else { + return channels[0]; + } + } + /// alpha + nothrow @safe @nogc @property pure ref auto a() inout { + static if(byteOrder == Endianness.Big) { + return channels[0]; + } else { + return channels[1]; + } + } + /// pseudo-red (output only) + nothrow @safe @nogc @property pure ubyte r() const { return y; } + /// pseudo-green (output only) + nothrow @safe @nogc @property pure ubyte g() const { return y; } + /// pseudo-blue (output only) + nothrow @safe @nogc @property pure ubyte b() const { return y; } + static bool hasAlphaChannelSupport() {return true;} +} +/** + * 16 Bit colorspace with a single bit alpha. This is should be used with RGBX5551 with channel `a` ignored + */ +struct RGBA5551 { + union{ + ushort base; /// direct access + mixin(bitfields!( + ubyte, "_b", 5, + ubyte, "_g", 5, + ubyte, "_r", 5, + bool, "_a", 1, + )); + } + /// Standard CTOR with 8bit normalized inputs + this(ubyte r, ubyte g, ubyte b, ubyte a) @safe @nogc pure nothrow { + _r = r>>3; + _g = g>>3; + _b = b>>3; + _a = a != 0; + } + /// Convertion CTOR with 8 bit normalized inputs + this(T)(T src) @safe @nogc pure nothrow { + _r = src.r>>3; + _g = src.g>>3; + _b = src.b>>3; + _a = src.a != 0; + } + ///Conversion from 8bit monochrome + this(ubyte p) @safe @nogc pure { + _b = p>>3; + _g = p>>3; + _r = p>>3; + _a = true; + } + /// upconverted-red (output only) + nothrow @safe @nogc @property pure ubyte r() const { return cast(ubyte)(_r << 3 | _r >>> 2); } + /// upconverted-green (output only) + nothrow @safe @nogc @property pure ubyte g() const { return cast(ubyte)(_g << 3 | _g >>> 2); } + /// upconverted-blue (output only) + nothrow @safe @nogc @property pure ubyte b() const { return cast(ubyte)(_b << 3 | _b >>> 2); } + /// upconverted-alpha + nothrow @safe @nogc @property pure ubyte a() const { return _a ? 0xFF : 0x00; } + static bool hasAlphaChannelSupport() {return true;} +} +/** + * 16 Bit RGB565 colorspace with no alpha. + */ +struct RGB565 { + union{ + ushort base; /// direct access + mixin(bitfields!( + ubyte, "_b", 5, + ubyte, "_g", 6, + ubyte, "_r", 5, + )); + } + /// Standard CTOR with 8bit normalized inputs + this(ubyte r, ubyte g, ubyte b) @safe @nogc pure nothrow { + _r = r>>3; + _g = g>>2; + _b = b>>3; + } + /// Convertion CTOR with 8 bit normalized inputs + this(T)(T src) @safe @nogc pure nothrow { + _r = src.r>>3; + _g = src.g>>2; + _b = src.b>>3; + } + ///Conversion from 8bit monochrome + this(ubyte p) @safe @nogc pure { + _b = p>>3; + _g = p>>2; + _r = p>>3; + } + /// upconverted-red (output only) + nothrow @safe @nogc @property pure ubyte r() const { return cast(ubyte)(_r << 3 | _r >>> 2); } + /// upconverted-green (output only) + nothrow @safe @nogc @property pure ubyte g() const { return cast(ubyte)(_g << 2 | _g >>> 4); } + /// upconverted-blue (output only) + nothrow @safe @nogc @property pure ubyte b() const { return cast(ubyte)(_b << 3 | _b >>> 2); } + //pseudo-alpha (output only) + nothrow @safe @nogc @property pure ubyte a() const { return 0xFF; } + static bool hasAlphaChannelSupport() {return false;} +} +alias RGB888 = RGB888Templ!(Endianness.Little); +alias RGB888BE = RGB888Templ!(Endianness.Big); +/** + * 24 Bit colorspace + */ +align(1) struct RGB888Templ (Endianness byteOrder = Endianness.Little) { + ubyte[3] bytes; ///individual access + static if (byteOrder == Endianness.Big) { + ///red + nothrow @safe @nogc @property pure ref auto r() inout { return bytes[0]; } + ///green + nothrow @safe @nogc @property pure ref auto g() inout { return bytes[1]; } + ///blue + nothrow @safe @nogc @property pure ref auto b() inout { return bytes[2]; } + } else { + ///red + nothrow @safe @nogc @property pure ref auto r() inout { return bytes[2]; } + ///green + nothrow @safe @nogc @property pure ref auto g() inout { return bytes[1]; } + ///blue + nothrow @safe @nogc @property pure ref auto b() inout { return bytes[0]; } + } + ///Standard CTOR + this(ubyte r, ubyte g, ubyte b) pure nothrow @safe @nogc { + this.r = r; + this.g = g; + this.b = b; + } + ///Conversion CTOR + this(T)(T src) pure nothrow @safe @nogc { + this.r = src.r; + this.g = src.g; + this.b = src.b; + } + ///Conversion from 8bit monochrome + this(ubyte p) @safe @nogc pure { + this.b = p; + this.g = p; + this.r = p; + } + //pseudo-alpha (output only) + nothrow @safe @nogc @property pure ubyte a() const { return 0xFF; } + ///direct access read + nothrow @safe @nogc @property pure uint base(){ return 0xff_00_00_00 | r << 16 | g << 8 | b; } +} +/** + * 48 bit RGB colorspace with 16 bit per channel. + * Does not easily convert to 8 bit at the moment. + */ +public struct RGB16_16_16Templ (Endianness byteOrder = Endianness.Little) { + ushort[3] bytes; ///individual access + static if (byteOrder == Endianness.Big) { + ///red + nothrow @safe @nogc @property pure ref auto r() inout { return bytes[0]; } + ///green + nothrow @safe @nogc @property pure ref auto g() inout { return bytes[1]; } + ///blue + nothrow @safe @nogc @property pure ref auto b() inout { return bytes[2]; } + } else { + ///red + nothrow @safe @nogc @property pure ref auto r() inout { return bytes[2]; } + ///green + nothrow @safe @nogc @property pure ref auto g() inout { return bytes[1]; } + ///blue + nothrow @safe @nogc @property pure ref auto b() inout { return bytes[0]; } + } + ///Standard CTOR + this(ushort r, ushort g, ushort b) pure nothrow @safe @nogc { + this.r = r; + this.g = g; + this.b = b; + } + /+///Conversion CTOR + this(T)(T src) pure nothrow @safe @nogc { + this.r = src.r; + this.g = src.g; + this.b = src.b; + }+/ + /+//pseudo-alpha (output only) + nothrow @safe @nogc @property pure ubyte a() const { return 0xFF; } + ///direct access read + nothrow @safe @nogc @property pure uint base(){ return 0xff_00_00_00 | r << 16 | g << 8 | b; }+/ +} +/** + * Pixel format flags. + * Undefined should be used for all indexed bitmaps, except 16 bit big endian ones, in which case a single BigEndian bit should be set high. + * Lower 16 bits should be used for general identification, upper 16 bits are general identificators (endianness, valid alpha channel, etc). + * 0x01 - 0x1F are reserved for 16 bit truecolor, 0x20 - 0x2F are reserved for 24 bit truecolor, 0x30 - 3F are reserved for integer grayscale, + * 0x40 - 0x5F are reserved for 32 bit truecolor, 0xF00-0xF0F are reserved for "chunky" indexed images, 0xF10-0xF1F are reserved for planar + * indexed images. + */ +enum PixelFormat : uint { + BigEndian = 0x00_01_00_00, ///Always little endian if bit not set + ValidAlpha = 0x00_02_00_00, ///If set, alpha is used + RGBX5551 = 0x1, + RGBA5551 = RGBX5551 | ValidAlpha, + RGB565 = 0x2, + RGB888 = 0x20, + Grayscale8Bit = 0x30, + Grayscale4Bit = 0x31, + YX88 = 0x3A, + YA88 = YX88 | ValidAlpha, + RGBX8888 = 0x40, + RGBA8888 = RGBX8888 | ValidAlpha, + XRGB8888 = 0x41, + ARGB8888 = XRGB8888 | ValidAlpha, + RGB16_16_16 = 0x60, + Indexed1Bit = 0xF00, + Indexed2Bit = 0xF01, + Indexed4Bit = 0xF02, + Indexed8Bit = 0xF03, + Indexed16Bit = 0xF04, + Planar2Color = 0xF10, + Planar4Color = 0xF11, + Planar8Color = 0xF12, + Planar16Color = 0xF13, + Planar32Color = 0xF14, + Planar64Color = 0xF15, + Planar128Color = 0xF16, + Planar256Color = 0xF17, + Planar512Color = 0xF18, + Planar1024Color = 0xF19, + Planar2048Color = 0xF1A, + Planar4096Color = 0xF1B, + Planar8192Color = 0xF1C, + Planar16384Color= 0xF1D, + Planar32768Color= 0xF1E, + Planar65536Color= 0xF1F, + + Undefined = 0, +} +/** + * Returns the bitdepth of a format. + */ +public ubyte getBitDepth(uint format) @nogc @safe pure nothrow { + format &= 0xFF_FF; + switch (format) { + case 0x00_01: .. case 0x00_1F: case 0x0F_04, 0x00_3A: + return 16; + case 0x00_20: .. case 0x00_2F: + return 24; + case 0x00_40: .. case 0x00_5F: + return 32; + case 0x00_60: + return 48; + case 0x0F_00: + return 1; + case 0x0F_01: + return 2; + case 0x0F_02, 0x00_31: + return 4; + case 0x0F_03, 0x00_30: + return 8; + case 0x0F_10: .. case 0x0F_1F: + return cast(ubyte)(format & 0x0F); + default: + return 0; + } +} \ No newline at end of file diff --git a/test/bmp/TRU256_I.bmp b/test/bmp/TRU256_I.bmp new file mode 100644 index 0000000000000000000000000000000000000000..218d3b177b6ef88285419845426e002f6966b6ad GIT binary patch literal 17462 zcmeI(Wt$~65QgDeSYUB?YkYAZ9Ky0Vi@OGQcMtCF9^BnMxVw9BcX!@Q9{$9+a_{t{ zt173@bai+7(+(NdH`My|4Ky2E_6C@_{$WswYEbw3YyYR5ix!+ym@Eae5Q08e;*wW3 zdF7qPk`Jnvy!Kt0uMb#OUi+!!ljf!!-Tt>|`$*McEWU6aRrn)O* zsXL8Xu`?GmV*D2QAsLgBNd&N$9&G%0EGG93zGpUwT z3tiBd6bDnFpQuqaU&g37Y0Qc>TBlh1+D+vcsnd>rlG88MT#22|(`)qRENcupKW!;$ zY8jLM-cDoIVZHun)A?j`!SuDA;!ab&g;aJ+UU8?QbDH3k*Blj{_MPUcK^n_^#isq> z^?jy%&~eLDQJ?e5EB4xV*^+7K^ebp|L35Jo zSz}hx>|bnz=kN*sz+$)`AEEys`t>_@!Nzz2Ghq`fjwx{g24M(x!{&Gev*0&;g{g2M z24X1g!M14UUpvsQf9*iK|7!=@^ItpA*1y_;w*J=+^xFIHE^LaIa5`?n5_kZU;9y*Y zcd;d2!_(LdOJW*aj0f=|_QLjf3uj_#tc&SzDGtRYxDUIcoqz2>yZ*HU?f$PFXwQG` zKwJN62ip2yJJ4(Izdf)u-oR{l6HDVE9FGyP6u!fbcn5P}3oMHna5;v415?5dVj=;D04BKH1%#EAz1Af8iI1WGJd`yoGF)wb#(YOlZ;uN&=uN`RD zzjmPA|Fr|{`L7*l>tF3aTmNeZdhPu;JdVQmSP*YxEj)!2F%DM6m^cAHV`1!obub@p z$9PyB=n!5YJ zhxfq`{HQ;C`l0?PAIL}Yqx@`sFh7|e&CmW1IuFQ)@?rD95B$(P@B=?I5B$In%>zI1 TL-W87{Lnn`13xqmKm2e3QHL<* literal 0 HcmV?d00001