SkillAgentSearch skills...

Gamut

Image encoding and decoding library for D.

Install / Use

/learn @AuburnSounds/Gamut
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Gamut

Gamut (DUB package: gamut) is an image decoding/encoding library for D.

Inspired by the FreeImage design, the Image concept is monomorphic and can do it all.

Gamut tries to have the fastest and most memory-conscious image decoders available in pure D code. It is nothrow @nogc @safe for usage in -betterC and in disabled-runtime D.

Decoding

  • PNG: 8-bit and 16-bit, L/LA/RGB/RGBA
  • JPEG: 8-bit, L/RGB/RGBA, baseline and progressive
  • JPEG XL: 8-bit, RGB (no alpha support), encoded with cjxl -e 4 or lower
  • TGA: 8-bit, indexed, L/LA/RGB/RGBA
  • GIF: indexed, animation support
  • BMP: indexed 1/4/8-bit no-RLE, 8-bit RGB/RGBA
  • SQZ: 8-bit, RGB
  • QOI: 8-bit, RGB/RGBA
  • QOIX: 8-bit, 10-bit, L/LA/RGB/RGBA. Improvement upon QOI. This format may change between major Gamut tags, so is not a storage format.

Encoding

  • PNG. 8-bit, 16-bit, L/LA/RGB/RGBA
  • JPEG: 8-bit, greyscale/RGB, baseline
  • TGA: 8-bit, RGB/RGBA
  • GIF: 8-bit, RGBA, animation support
  • BMP: 8-bit, RGB/RGBA
  • SQZ: 8-bit, RGB (default to 2.5 bpp)
  • QOI: 8-bit, RGB/RGBA
  • QOIX: 8-bit, 10-bit, L/LA/RGB/RGBA, premultiplied alpha
  • DDS: BC7 encoded, 8-bit, RGB/RGBA

Changelog

  • v3.3 Added SQZ input and output. A MIT-licensed codec that beat guetzli-encoded JPEG by about 30%! It also encodes faster, but decodes 2x slower than baseline JPEG. Quality comparisons here.
  • v3 Added premultiplied alpha pixel types. BREAKING.
    • Decoders are now allowed to return any type if you do not specify LOAD_PREMUL or LOAD_NO_PREMUL. Update your loading code.
    • Introduce image.premultiply() and image.unpremultiply().
    • QOIX supports encoding premultiplied. Saves space and decoding times for transparent overlays. Only save space for 8-bit though.
  • v2.6 Added JPEG XL input. 8-bit, no alpha, cjxl --effort 4 or lower, raw streams not ISO BMFF.
  • v2.5 Added BMP input.
  • v2.4 Added BMP output.
  • v2.3 Added GIF input and GIF output. Added multilayer images.
  • v2.2 Added 16-bit PNG output.
  • v2.1 Added TGA format support.
  • v2 QOIX bitstream changed. Ways to disown and deallocate image allocation pointer. It's safe to update to latest tag in the same major version. Do keep a 16-bit source in case the bitstream changes.
  • v1 Initial release.

Why QOIX?

Our benchmark results for 8-bit color images:

| Codec | decode mpps | encode mpps | bit-per-pixel | |-------|-------------|-------------|---------------| | PNG (stb) | 89.73 | 14.34 | 10.29693 | | QOI | 201.9 | 150.8 | 10.35162 | | QOIX | 179.0 | 125.0 | 7.93607 |

  • QOIX and QOI generally outperforms PNG in decoding speed and encoding speed.
  • QOIX outperforms QOI in compression efficiency at the cost of speed:
    • because it's based upon better intra predictors
    • because it is followed by LZ4, which removes some of the QOI worst cases.
  • QOIX adds support for 8-bit greyscale and greyscale + alpha images, with a "QOI-plane" custom codec.
  • QOIX adds support for 10-bit images, with a "QOI-10b" custom codec. It drops the last 6 bits of precision (lossy) to outperform PNG 16-bit in every way for some use cases.
  • QOIX support for premultiplied alpha brings even more speed and compression for transparent images.

Use the convert tool to encode QOIX.

 


 

Gamut API documentation

1. Image basics

Key concept: The Image struct is where most of the public API resides.

1.1 Get the dimensions of an image:

Image image = Image(800, 600);
int w = image.width();
int h = image.height();
assert(w == 800 && h == 600);

1.2 Get the pixel format of an image:

Image image = Image(800, 600);
PixelType type = image.type();
assert(type == PixelType.rgba8); // rgba8 is default if not provided

Key concept: PixelType completely describes the pixel format, for example PixelType.rgb8 is a 24-bit format with one byte for red, green and blue components each (in that order). Nothing is specified about the color space though.

Here are the possible PixelType:

enum PixelType
{
    l8,
    l16,
    lf32,
    
    la8,
    la16,
    laf32,
    lap8,
    lap16,
    lapf32,

    rgb8, 
    rgb16,
    rgbf32,

    rgba8,
    rgba16,
    rgbaf32
    rgbap8,
    rgbap16,
    rgbapf32
}

For now, all pixels format have one to four components:

  • 1 component is implicitely Greyscale
  • 2 components is implicitely Greyscale + alpha
  • 3 components is implicitely Red + Green + Blue
  • 4 components is implicitely Red + Green + Blue + Alpha

Bit-depth: Each of these components can be represented in 8-bit, 16-bit, or 32-bit floating-point (0.0f to 1.0f range).

Alpha premultiplication: When an alpha channel exist, both premultiplied and non-premultiplied variants exist.

1.3 Create an image:

Different ways to create an Image:

  • create() or regular constructor this() creates a new owned image filled with zeros.
  • createNoInit() or setSize() creates a new owned uninitialized image.
  • createView() creates a view into existing data.
  • createNoData() creates a new image with no data pointed to (still has a type, size...).
// Create with transparent black.
Image image = Image(640, 480, PixelType.rgba8); 
image.create(640, 480, PixelType.rgba8);

// Create with no initialization.
image.setSize(640, 480, PixelType.rgba8);
image.createNoInit(640, 480, PixelType.rgba8);

// Create view into existing data. Existing data is borrowed.
image.createView(data.ptr, w, h, PixelType.rgb8, pitchbytes);
  • At creation time, the Image forgets about its former life, and leaves any isError() state or former data/type
  • Image.init is in isError() state
  • isValid() can be used instead of !isError()
  • Being valid == not being error == having a PixelType

 


 

2. Loading and saving an image

2.1 Load an Image from a file:

Another way to create an Image is to load an encoded image.

Image image;
image.loadFromFile("logo.png");
if (image.isError)
    throw new Exception(image.errorMessage);

You can then read width(), height(), type(), etc...

There is no exceptions in Gamut. Instead the Image itself has an error API:

  • bool isError() return true if the Image is in an error state. In an error state, the image can't be used anymore until recreated (for example, loading another file).
  • const(char)[] errorMessage() is then available, and is guaranteed to be zero-terminated with an extra byte.

2.2 Load an image from memory:

auto pngBytes = cast(const(ubyte)[]) import("logo.png"); 
Image image;
image.loadFromMemory(pngBytes);
if (!image.isValid) 
    throw new Exception(image.errorMessage());

Key concept: You can force the loaded image to be a certain type using LoadFlags, or call convertTo() after load.

Here are the possible LoadFlags:

LOAD_NORMAL      // Default: preserve type from original.

LOAD_ALPHA       // Force one alpha channel.
LOAD_NO_ALPHA    // Force zero alpha channel.

LOAD_GREYSCALE   // Force greyscale.
LOAD_RGB         // Force RGB values.

LOAD_8BIT        // Force 8-bit `ubyte` per component.
LOAD_16BIT       // Force 16-bit `ushort` per component.
LOAD_FP32        // Force 32-bit `float` per component.

LOAD_PREMUL      // Force premultiplied alpha representation (if alpha exists)
LOAD_NO_PREMUL   // Force non-premultiplied alpha representation (if alpha exists)

Example:

Image image;  
image.loadFromMemory(pngBytes, LOAD_RGB | LOAD_ALPHA | LOAD_8BIT | LOAD_NO_PREMUL);  // force PixelType.rgba8 

Not all load flags are compatible, for example LOAD_8BIT and LOAD_16BIT cannot be used together.

2.3 Convert to another PixelType:

However, load flags are not the only way to select a PixelType, you can provide one explicitely with convertTo.

// Convert to grey + one alpha channel, 16-bit
image.convertTo(PixelType.la16); 

// Convert to RGB + one alpha channel, 8-bit
image.convertTo(PixelType.rgba8); 

2.4 Save an image to a file:

Image image;
if (!image.saveToFile("output.png"))
    throw new Exception("Writing output.png failed");

Key concept: ImageFormat is simply the codecs/containers files Gamut encode and decodes to.

enum ImageFormat
{
    unknown,
    JPEG,
    PNG,
    QOI,
    QOIX,
    DDS,
    TGA,
    GIF,
    JXL,
    SQZ
}

This can be used to avoid inferring the output format from the filename:

Image image;
if (!image.saveToFile(ImageFormat.PNG, "output.png"))
    throw new Exception("Writing output.png failed");

2.5 Save an image to memory:

Image image;
ubyte[] qoixEncoded = image.saveToMemory(ImageFormat.QOIX);
scope(exit) freeEncodedImage(qoixEncoded);

The returned slice must be freed up with freeEncodedImage.

2.6 Convert an image to QOIX for faster load

  Image image;
  image.loadFromFile("input.png");
  image.saveToFile("output.qoix"); // .qoix loads faster

 


 

3. Accessing image pixels

3.1 Get the row pitch, in bytes:

int pitch = image.pitchInBytes();

Key concept: The image pitch is the distance between the start of two consecutive scanlines, in bytes. IMPORTANT: This pitch can be negative.

3.2 Access a row of pixels:

void* scan = image.scanptr(y);  // get pointer to start of pixel row
void[] row = image.scanline(y); // get sli
View on GitHub
GitHub Stars49
CategoryDevelopment
Updated2mo ago
Forks5

Languages

D

Security Score

95/100

Audited on Jan 23, 2026

No findings