Skip to content

Image Decoders

kazah-png edited this page Jul 27, 2026 · 3 revisions

Image Decoders

NyxOS decodes PNG, BMP, GIF (static and animated) and baseline JPEG into RGBA in the kernel, on top of a from-scratch DEFLATE implementation. The whole pipeline exists so Selene-Browser can draw the pictures on a real web page.

Format File Magic Self-test
PNG png.c 89 50 4E 47 pngtest
BMP bmp.c 42 4D (BM) bmptest
GIF gif.c GIF87a / GIF89a giftest
JPEG jpeg.c FF D8 jpegtest

Selene dispatches on those magic bytes, so a format is just another branch in one <img> pipeline.

See also: Selene-Browser, GUI-Subsystem, Shell

The common format

Every decoder produces the same thing, so callers stay format-agnostic:

typedef struct {
    uint32_t width, height;
    uint8_t* pixels;        // RGBA, 4 bytes per pixel, width*height*4, kmalloc'd
} image_t;

Selene picks a decoder from the content, holds an image_t, and draws it without caring which format it came from.

DEFLATE and zlib (inflate.c)

The decompression core PNG's IDAT stream needs, implemented against RFC 1951 and RFC 1950.

int inflate_raw (const uint8_t* src, uint32_t srclen,
                 uint8_t* dst, uint32_t dstcap, uint32_t* outlen);
int zlib_inflate(const uint8_t* src, uint32_t srclen,
                 uint8_t* dst, uint32_t dstcap, uint32_t* outlen);
Function Handles
inflate_raw Raw DEFLATE — stored, fixed-Huffman and dynamic-Huffman blocks
zlib_inflate The zlib wrapper: 2-byte header, DEFLATE body, and a verified trailing Adler-32

Both return 0 on success and a negative code on a corrupt stream or an output overflow.

Self-test: deflatetest — fixed, dynamic and stored blocks, plus a zlib case with Adler-32.

PNG (png.c)

int  png_decode(const uint8_t* src, uint32_t srclen, image_t* img);
void png_free(image_t* img);
Supported Not supported
8-bit depth 16-bit depth
Colour types: grayscale, RGB, palette, gray+alpha, RGBA Interlaced (Adam7) images
All five scanline filters: None, Sub, Up, Average, Paeth

Self-test: pngtest — an embedded PNG of each colour type, exercising every filter type.

BMP (bmp.c)

int bmp_decode(const uint8_t* src, uint32_t srclen, image_t* img);

Uncompressed (BI_RGB) BITMAPINFOHEADER bitmaps — the shapes still found on the web:

  • 24-bit BGR
  • 32-bit BGRX
  • 8-bit palette
  • Bottom-up and top-down row order

Self-test: bmptest — 24/32/8-bit plus a top-down image, against expected RGBA.

GIF (gif.c)

int gif_decode(const uint8_t* src, uint32_t srclen, image_t* img);

Decodes the first frame of a GIF87a or GIF89a, including a real LZW decompressor:

  • Global and local colour tables
  • LZW decompression with dictionary growth and clear codes
  • Interlaced images
  • A transparent colour index

Animation

typedef struct { uint8_t* pixels; uint16_t delay_cs; } gif_frame_t;
typedef struct { uint32_t width, height; int nframes; gif_frame_t* frames; int loop_count; } gif_anim_t;

int  gif_decode_anim(const uint8_t* src, uint32_t srclen, gif_anim_t* out);
void gif_anim_free(gif_anim_t* a);

gif_decode_anim walks every image descriptor and composites each onto a logical-screen canvas, honouring the per-frame sub-rectangle, transparent index and disposal method. Every frame therefore comes out as a ready-to-blit full-size image, rather than a delta the caller has to apply. A static GIF yields nframes == 1.

Delays are in centiseconds (1/100 s). Selene drives playback from the compositor's per-frame tick.

Loop count (v5.9.93). gif_decode_anim parses the NETSCAPE2.0 Application Extension (21 FF 0B "NETSCAPE2.0" 03 01 LL HH) into gif_anim_t.loop_count: 0 = infinite, N = play N times. A GIF without the extension defaults to 0, so nothing that used to loop regresses — only an explicit finite count stops. Selene counts completed loops and freezes on the last frame once a finite count is reached (a finished animation stops ticking, so it costs no CPU).

Self-test: giftest — a basic palette image, transparency, an interlaced image, one that grows the LZW dictionary, and a multi-frame animation checked for frame count, per-frame RGBA and delays.

JPEG (jpeg.c)

int jpeg_decode(const uint8_t* src, uint32_t srclen, image_t* img);

Baseline only: sequential DCT, Huffman-coded, 8-bit — the format of essentially every web photo.

Supported Rejected cleanly
Greyscale Progressive (SOF2)
YCbCr 4:4:4 Arithmetic coding
YCbCr 4:2:2 (h2v1) 12-bit precision
YCbCr 4:2:0 (h2v2) 4-component CMYK / YCCK
Restart markers (DRI, RSTn)

The decoder walks the marker segments — SOI, APPn, DQT, SOF0, DHT, DRI, SOS, EOI — builds canonical Huffman tables, and runs an integer IDCT.

Important

Unsupported variants fail rather than mis-decode. A progressive JPEG returns an error instead of producing garbage pixels.

Chroma upsampling

Subsampled chroma uses libjpeg's triangle-filter ("fancy") upsampling for the two common photo layouts, 4:2:2 and 4:2:0. The original implementation used nearest-neighbour — replicating each chroma sample across its block — which is visibly blocky at colour edges.

Self-test: jpegtest — greyscale, 4:4:4 and 4:2:0 images, each compared to a reference decode within a tolerance.

Note

Exact equality is impossible here. libjpeg's integer IDCT differs from any other implementation by a rounding step, so a byte-exact comparison would fail for a correct decoder. This is the one self-test in the project that is not an exact known-answer check.

Memory

Every decoder kmallocs img->pixels. Release it with png_free(&img) (or kfree(img.pixels)). Selene frees decoded images when a page is replaced or a tab closes.

Selene caps a per-image download at 512 KB and decodes at most 8 images per page load, one per compositor frame, which bounds what a hostile or merely enormous page can allocate.

Testing them all

nyx> deflatetest
nyx> pngtest
nyx> bmptest
nyx> giftest
nyx> jpegtest

Each prints a per-case pass/fail line and returns non-zero on failure.

History

v5.9.81 added inflate, v5.9.82 PNG, v5.9.83 wired real images into Selene, v5.9.84 made loading lazy and progressive, v5.9.85 added BMP and generalised the pipeline behind image_t, v5.9.86 added GIF, v5.9.87 made loading cooperative, v5.9.88 added animated GIF playback, v5.9.89v5.9.90 added JPEG and wired it into Selene, v5.9.91 improved JPEG chroma upsampling, and v5.9.93 honoured the GIF loop count. See Version-History.

See also

External resources

Clone this wiki locally