Skip to content

skeeto/slim

Repository files navigation

SLIM - Slate Image

SLIM is a lossless image and video codec focused on very fast encoding, primarily for scientific workloads. It is designed to compress imagery as quickly as possible during an experiment, with the understanding that the data can always be compressed further later. Under advantageous conditions, SLIM achieves compression ratios of 100x at speeds above 2 GB/s.

SLIM is built on top of QOIR, which is based on the exceptionally elegant QOI format. It modifies QOIR to support more data types, any number of channels, and to better handle images featuring large alpha gradients and low entropy regions.

SLIM is still experimental and under active development.

Features

  • Lossless encoding and decoding.
  • Fast U8 RGB/RGBA/grayscale image paths.
  • Support for integer and floating-point sample types, including U8, U16, U32, U64, S8, S16, S32, S64, F16, F32, and F64.
  • Multi-channel and byte-plane aware layouts.
  • Tile-parallel encoding with configurable thread count.
  • Delta-frame encoding against a previous frame/reference.
  • Optional lossy noise-floor controls for workflows that can tolerate bounded integer changes.
  • Bad-pixel masking and incompressible-byte handling.
  • Small C API with no required runtime allocation ownership surprises: returned buffers are freed with free() unless custom allocation hooks are used.

Building

Requirements:

  • CMake
  • A C99 compiler
  • make or another CMake-supported build tool

Build and run tests:

cmake -S . -B build
cmake --build build -j
ctest --test-dir build --output-on-failure

The main static library target is slim. Benchmark and utility binaries are also built under build/, including examples such as:

build/slim_perf_matrix
build/slim_delta_frames_bench
build/slim_png_bench
build/slim_recover

Minimal U8 RGB encode example

// ! API subject to change
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include "slim.h"

int main(void) {
    const uint32_t width = 2;
    const uint32_t height = 2;

    /* Interleaved RGB, 8 bits per channel. */
    uint8_t pixels[2 * 2 * 3] = {
        255, 0,   0,    0, 255, 0,
        0,   0, 255,  255, 255, 255,
    };

    slim_desc desc = {0};
    desc.width = width;
    desc.height = height;
    desc.channels = 3;
    desc.bytes_per_channel = 1;
    desc.premultiplied_alpha = 0;
    desc.data_type = SLIM_DATA_U8;

    slim_encode_options opt = {0};
    opt.nthreads = 4; /* Use 1 for deterministic single-thread benchmarking. */

    size_t encoded_len = 0;
    void *encoded = slim_encode_ex(pixels, &desc, &opt, &encoded_len);
    if (!encoded) {
        fprintf(stderr, "SLIM encode failed\n");
        return 1;
    }

    printf("encoded %zu raw bytes into %zu SLIM bytes\n",
           sizeof(pixels), encoded_len);

    free(encoded);
    return 0;
}

Decode with slim_decode() or slim_decode_into():

slim_desc decoded_desc = {0};
uint8_t *decoded = slim_decode(encoded, encoded_len, &decoded_desc);
/* ...use decoded... */
free(decoded);

Benchmarks

While most image codecs emphasize decode speed, encode speed is SLIM's primary design goal. SLIM's decode speed has not yet been optimized, but it will be very similar to its encode speed, which can reach several gigabytes per second.

Proper benchmarking will come once SLIM is more finalized, but I've tentatively tested SLIM against a few images using libpng, optipng, and jpeg-xl, in both t=1 serial and default t=4 multithreaded mode.

  libpng:  compression level 1
  optipng: libpng level 1 PNG + optipng -o0
  JPEG XL: cjxl --distance=0 --effort=1

Data

The FiveK dataset are high quality photographs from https://data.csail.mit.edu/graphics/fivek/

The Icons dataset are small simple images from https://qoiformat.org/benchmark/

The scientific dataset is a sequence of 17 high resolution images.

FiveK grayscale U8

┌────────────┬───────────────┬──────────────┐
│ Codec      │ Median encode │ Median ratio │
├────────────┼───────────────┼──────────────┤
│ libpng l1  │ 65.6 MiB/s    │ 0.485        │
├────────────┼───────────────┼──────────────┤
│ optipng o0 │ 50.6 MiB/s    │ 0.484        │
├────────────┼───────────────┼──────────────┤
│ jpeg-xl e1 │ 112.0 MiB/s   │ 0.737        │
├────────────┼───────────────┼──────────────┤
│ SLIM t1    │ 357.2 MiB/s   │ 0.630        │
├────────────┼───────────────┼──────────────┤
│ SLIM t4    │ 1116.1 MiB/s  │ 0.630        │
└────────────┴───────────────┴──────────────┘

SLIM t4: 17.1× faster than libpng, 10.0× faster than JXL.

FiveK grayscale U16

┌────────────┬───────────────┬──────────────┐
│ Codec      │ Median encode │ Median ratio │
├────────────┼───────────────┼──────────────┤
│ libpng l1  │ 45.4 MiB/s    │ 0.773        │
├────────────┼───────────────┼──────────────┤
│ optipng o0 │ 36.5 MiB/s    │ 0.772        │
├────────────┼───────────────┼──────────────┤
│ jpeg-xl e1 │ 164.4 MiB/s   │ 1.364        │
├────────────┼───────────────┼──────────────┤
│ SLIM t1    │ 297.3 MiB/s   │ 0.816        │
├────────────┼───────────────┼──────────────┤
│ SLIM t4    │ 957.8 MiB/s   │ 0.816        │
└────────────┴───────────────┴──────────────┘

SLIM t4: 21.9× faster than libpng, 5.6× faster than JXL.

JXL had pathological performance on the chosen 16-bit images. A different image subset showed 573.7 MiB/s to achieve a 0.922 ratio. 🤷

FiveK RGB U8

┌────────────┬───────────────┬──────────────┐
│ Codec      │ Median encode │ Median ratio │
├────────────┼───────────────┼──────────────┤
│ libpng l1  │ 68.6 MiB/s    │ 0.363        │
├────────────┼───────────────┼──────────────┤
│ optipng o0 │ 56.1 MiB/s    │ 0.362        │
├────────────┼───────────────┼──────────────┤
│ jpeg-xl e1 │ 240.0 MiB/s   │ 0.285        │
├────────────┼───────────────┼──────────────┤
│ SLIM t1    │ 448.9 MiB/s   │ 0.376        │
├────────────┼───────────────┼──────────────┤
│ SLIM t4    │ 1238.6 MiB/s  │ 0.376        │
└────────────┴───────────────┴──────────────┘

SLIM t4: 17.7× faster than libpng, 5.4× faster than JXL.

FiveK RGB U16

┌────────────┬───────────────┬──────────────┐
│ Codec      │ Median encode │ Median ratio │
├────────────┼───────────────┼──────────────┤
│ libpng l1  │ 45.3 MiB/s    │ 0.776        │
├────────────┼───────────────┼──────────────┤
│ optipng o0 │ 36.2 MiB/s    │ 0.775        │
├────────────┼───────────────┼──────────────┤
│ jpeg-xl e1 │ 163.0 MiB/s   │ 1.368        │
├────────────┼───────────────┼──────────────┤
│ SLIM t1    │ 387.6 MiB/s   │ 0.688        │
├────────────┼───────────────┼──────────────┤
│ SLIM t4    │ 933.5 MiB/s   │ 0.688        │
└────────────┴───────────────┴──────────────┘

SLIM t4: 20.2× faster than libpng, 5.7× faster than JXL.

JXL again had pathological behavior on this image set. On a different image set it got 834.7 MiB/s for 0.602 median ratio.

Icons RGBA U8

┌────────────┬───────────────┬──────────────┐
│ Codec      │ Median encode │ Median ratio │
├────────────┼───────────────┼──────────────┤
│ libpng l1  │ 133.9 MiB/s   │ 0.081        │
├────────────┼───────────────┼──────────────┤
│ optipng o0 │ 86.2 MiB/s    │ 0.081        │
├────────────┼───────────────┼──────────────┤
│ jpeg-xl e1 │ 98.0 MiB/s    │ 0.069        │
├────────────┼───────────────┼──────────────┤
│ SLIM t1    │ 1295.3 MiB/s  │ 0.078        │
├────────────┼───────────────┼──────────────┤
│ SLIM t4    │ 2792.3 MiB/s  │ 0.078        │
└────────────┴───────────────┴──────────────┘

SLIM t4: 21.6× faster than libpng, 28.9× faster than JXL.

Scientific data set:

┌────────────────┬───────────────┬───────────┬─────────────┬──────────────┐
│ Codec / mode   │ Encoded bytes │ Raw ratio │ Encode time │ Encode speed │
├────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ libpng level 1 │ 9,115,366     │ 0.02634   │ 1.950 s     │ 169.2 MiB/s  │
├────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ optipng -o0    │ 9,102,154     │ 0.02630   │ 2.834 s     │ 116.5 MiB/s  │
├────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ jpeg-xl e1     │ 5,356,755     │ 0.01548   │ 1.565 s     │ 210.9 MiB/s  │
├────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ SLIM intra t1  │ 6,829,905     │ 0.01973   │ 0.202 s     │ 1634.6 MiB/s │
├────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ SLIM intra t4  │ 6,829,905     │ 0.01973   │ 0.070 s     │ 4726.9 MiB/s │
├────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ SLIM delta t1  │ 5,500,414     │ 0.01589   │ 0.641 s     │ 514.6 MiB/s  │
├────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ SLIM delta t4  │ 5,500,414     │ 0.01589   │ 0.457 s     │ 722.9 MiB/s  │
└────────────────┴───────────────┴───────────┴─────────────┴──────────────┘

SLIM intra indicates each image was processed independently. SLIM delta indicates temporal information was exploited. One quarter of 64x64 tiles did not change between frames on this dataset.

Note, the delta code-path is not yet optimized.

Scientific data set (lossy):

┌─────────────────────────┬───────────────┬───────────┬─────────────┬──────────────┐
│ Codec / mode            │ Encoded bytes │ Raw ratio │ Encode time │ Encode speed │
├─────────────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ libpng level 1          │ 6,880,842     │ 0.019882  │ 1.995 s     │ 165.4 MiB/s  │
├─────────────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ optipng -o0             │ 6,870,846     │ 0.019853  │ 2.848 s     │ 115.9 MiB/s  │
├─────────────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ jpeg-xl e1              │ 3,624,673     │ 0.010473  │ 1.566 s     │ 210.8 MiB/s  │
├─────────────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ SLIM intra t1           │ 4,915,442     │ 0.014203  │ 0.306 s     │ 1080.3 MiB/s │
├─────────────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ SLIM intra t4           │ 4,915,442     │ 0.014203  │ 0.163 s     │ 2025.4 MiB/s │
├─────────────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ SLIM delta all-modes t1 │ 3,512,229     │ 0.010148  │ 0.877 s     │ 376.5 MiB/s  │
├─────────────────────────┼───────────────┼───────────┼─────────────┼──────────────┤
│ SLIM delta all-modes t4 │ 3,512,229     │ 0.010148  │ 0.649 s     │ 508.4 MiB/s  │
└─────────────────────────┴───────────────┴───────────┴─────────────┴──────────────┘

This variant changes pixel of value exactly 1 to 0, to aid in compression and mitigate thermal sensor noise. libpng, optipng, and jpeg-xl all had their data altered before being passed to them, while SLIM's timings are inclusive of the cost of this alteration logic.

In this final benchmark, SLIM's delta mode was set to a higher-effort mode.

Releases

Packages

Contributors

Languages