Files
subhobhai943andClaude Opus 4.8 fe33ddc79b feat(lib,gui): the browser shows images -- a PNG/BMP decoder, DEFLATE and all
The Web app could only render text. Now it decodes and displays images, so a
picture on the web actually shows up.

  - lib/inflate.c   DEFLATE / zlib decompressor (RFC 1950/1951): the algorithm
                    every PNG needs. Canonical Huffman, all three block types,
                    decodes into a caller buffer -- no allocation, integer only.
  - lib/image.c     PNG decoder (8-bit RGB/RGBA/grayscale/gray+alpha, and
                    1/2/4/8-bit grayscale & palette, non-interlaced; tRNS
                    transparency) plus BMP (24/32-bit). Parses chunks,
                    inflates IDAT, undoes the five scanline filters, and expands
                    to a flat 0xAARRGGBB buffer ready for gui_gfx_blit. A pixel
                    cap guards the heap.
  - Web app: if a response body sniffs as an image it is decoded once and drawn
    instead of text -- scaled to fit the panel width, vertically scrollable,
    alpha-composited over the background. The per-fetch cap grew to 128 KB so
    real images and longer pages fit.

Both decoders were verified on the host against generated references before
going in the kernel: inflate round-trips six zlib/raw streams (empty, back-
references, dynamic Huffman, stored blocks, 20 KB random, mixed); the image
decoder reproduces PIL-made PNGs of every supported colour type -- grayscale,
RGB, RGBA, 4-bit palette, gray+alpha -- and a BMP, with exact pixel matches.
In QEMU the Web app fetched a 360x240 PNG over HTTP and rendered it perfectly:
gradient, an antialiased red circle, a green box, a yellow diagonal, shown as
"image 360x240". Builds clean on x86_64, aarch64 and armv8i.

Still image-by-URL, not inline <img> within a page (that needs sub-resource
fetching and layout); and no JPEG/GIF (different codecs). PNG and BMP cover a
large share of the web's images.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
2026-08-27 18:53:38 +05:30

269 lines
8.8 KiB
C

/*
* DEFLATE / zlib decompressor (RFC 1950/1951).
*
* A compact, self-contained inflate -- the algorithm every PNG needs. No
* allocation, no floating point: it decodes straight into a caller-provided
* output buffer, so it is safe in the freestanding kernel. Structure follows
* the well-known "tinf" design (canonical Huffman tables, three block types).
*/
#include <lib/inflate.h>
#include <lib/string.h>
#define TINF_OK 0
#define TINF_DATA_ERROR (-3)
#define TINF_BUF_ERROR (-5)
typedef struct {
uint16_t counts[16]; // number of codes of each length
uint16_t symbols[288]; // symbols sorted by code
} tinf_tree;
typedef struct {
const uint8_t* src;
const uint8_t* src_end;
uint32_t tag;
int bitcount;
uint8_t* dst;
uint8_t* dst_start;
uint8_t* dst_end;
tinf_tree ltree; // literal/length codes
tinf_tree dtree; // distance codes
} tinf;
// Special ordering of code-length codes (RFC 1951 3.2.7).
static const uint8_t clcidx[19] = {
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
};
static const uint16_t length_base[30] = {
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0
};
static const uint8_t length_bits[30] = {
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0
};
static const uint16_t dist_base[30] = {
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577
};
static const uint8_t dist_bits[30] = {
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13
};
static int getbit(tinf* d) {
if (d->bitcount == 0) {
if (d->src >= d->src_end) return -1; // underflow flagged as a set bit? no: caller checks
d->tag = *d->src++;
d->bitcount = 8;
}
int bit = d->tag & 1;
d->tag >>= 1;
d->bitcount--;
return bit;
}
// Read `num` bits LSB-first and add `base`.
static int read_bits(tinf* d, int num, int base) {
int val = 0;
for (int i = 0; i < num; i++) {
int b = getbit(d);
if (b < 0) return -1;
val |= b << i;
}
return base + val;
}
// Build a canonical Huffman tree from a list of code lengths.
static void build_tree(tinf_tree* t, const uint8_t* lengths, unsigned num) {
unsigned offs[16];
for (int i = 0; i < 16; i++) t->counts[i] = 0;
for (unsigned i = 0; i < num; i++) t->counts[lengths[i]]++;
t->counts[0] = 0;
offs[0] = 0;
for (int i = 1; i < 16; i++) offs[i] = offs[i - 1] + t->counts[i - 1];
for (unsigned i = 0; i < num; i++)
if (lengths[i]) t->symbols[offs[lengths[i]]++] = (uint16_t)i;
}
static void build_fixed_trees(tinf_tree* lt, tinf_tree* dt) {
uint8_t lengths[288];
int i;
for (i = 0; i < 144; i++) lengths[i] = 8;
for (; i < 256; i++) lengths[i] = 9;
for (; i < 280; i++) lengths[i] = 7;
for (; i < 288; i++) lengths[i] = 8;
build_tree(lt, lengths, 288);
for (i = 0; i < 30; i++) lengths[i] = 5;
build_tree(dt, lengths, 30);
}
static int decode_symbol(tinf* d, const tinf_tree* t) {
int sum = 0, cur = 0, len = 0;
do {
int b = getbit(d);
if (b < 0) return -1;
cur = 2 * cur + b;
len++;
if (len >= 16) return -1;
sum += t->counts[len];
cur -= t->counts[len];
} while (cur >= 0);
int idx = sum + cur;
if (idx < 0 || idx >= 288) return -1;
return t->symbols[idx];
}
// Decode the dynamic Huffman trees at the start of a type-2 block.
static int decode_trees(tinf* d, tinf_tree* lt, tinf_tree* dt) {
uint8_t lengths[288 + 32];
int hlit = read_bits(d, 5, 257);
int hdist = read_bits(d, 5, 1);
int hclen = read_bits(d, 4, 4);
if (hlit < 0 || hdist < 0 || hclen < 0) return TINF_DATA_ERROR;
if (hlit > 286 || hdist > 30) return TINF_DATA_ERROR;
for (int i = 0; i < 19; i++) lengths[i] = 0;
for (int i = 0; i < hclen; i++) {
int clen = read_bits(d, 3, 0);
if (clen < 0) return TINF_DATA_ERROR;
lengths[clcidx[i]] = (uint8_t)clen;
}
tinf_tree code_tree;
build_tree(&code_tree, lengths, 19);
int num = 0;
while (num < hlit + hdist) {
int sym = decode_symbol(d, &code_tree);
if (sym < 0) return TINF_DATA_ERROR;
switch (sym) {
case 16: { // copy previous length 3..6 times
if (num == 0) return TINF_DATA_ERROR;
int prev = lengths[num - 1];
int n = read_bits(d, 2, 3);
if (n < 0) return TINF_DATA_ERROR;
while (n-- && num < hlit + hdist) lengths[num++] = (uint8_t)prev;
break;
}
case 17: { // repeat 0 for 3..10 times
int n = read_bits(d, 3, 3);
if (n < 0) return TINF_DATA_ERROR;
while (n-- && num < hlit + hdist) lengths[num++] = 0;
break;
}
case 18: { // repeat 0 for 11..138 times
int n = read_bits(d, 7, 11);
if (n < 0) return TINF_DATA_ERROR;
while (n-- && num < hlit + hdist) lengths[num++] = 0;
break;
}
default:
if (sym > 15) return TINF_DATA_ERROR;
lengths[num++] = (uint8_t)sym;
break;
}
}
build_tree(lt, lengths, hlit);
build_tree(dt, lengths + hlit, hdist);
return TINF_OK;
}
static int inflate_block_data(tinf* d, const tinf_tree* lt, const tinf_tree* dt) {
for (;;) {
int sym = decode_symbol(d, lt);
if (sym < 0) return TINF_DATA_ERROR;
if (sym == 256) return TINF_OK; // end of block
if (sym < 256) { // literal byte
if (d->dst >= d->dst_end) return TINF_BUF_ERROR;
*d->dst++ = (uint8_t)sym;
continue;
}
// length/distance pair
sym -= 257;
if (sym >= 29) return TINF_DATA_ERROR;
int length = read_bits(d, length_bits[sym], length_base[sym]);
if (length < 0) return TINF_DATA_ERROR;
int dsym = decode_symbol(d, dt);
if (dsym < 0 || dsym >= 30) return TINF_DATA_ERROR;
int dist = read_bits(d, dist_bits[dsym], dist_base[dsym]);
if (dist < 0) return TINF_DATA_ERROR;
uint8_t* from = d->dst - dist;
if (from < d->dst_start) return TINF_DATA_ERROR;
if (d->dst + length > d->dst_end) return TINF_BUF_ERROR;
for (int i = 0; i < length; i++) *d->dst++ = *from++;
}
}
static int inflate_uncompressed_block(tinf* d) {
d->bitcount = 0; // byte-align
if (d->src + 4 > d->src_end) return TINF_DATA_ERROR;
unsigned len = d->src[0] | (d->src[1] << 8);
unsigned nlen = d->src[2] | (d->src[3] << 8);
d->src += 4;
if ((len ^ 0xffff) != nlen) return TINF_DATA_ERROR;
if (d->src + len > d->src_end) return TINF_DATA_ERROR;
if (d->dst + len > d->dst_end) return TINF_BUF_ERROR;
for (unsigned i = 0; i < len; i++) *d->dst++ = *d->src++;
return TINF_OK;
}
// Decompress a raw DEFLATE stream. On success *dest_len is the byte count.
int inflate_raw(uint8_t* dest, unsigned* dest_len,
const uint8_t* src, unsigned src_len) {
tinf d;
d.src = src; d.src_end = src + src_len;
d.tag = 0; d.bitcount = 0;
d.dst = dest; d.dst_start = dest; d.dst_end = dest + *dest_len;
int bfinal;
do {
bfinal = getbit(&d);
if (bfinal < 0) return TINF_DATA_ERROR;
int btype = read_bits(&d, 2, 0);
int res;
switch (btype) {
case 0:
res = inflate_uncompressed_block(&d);
break;
case 1:
build_fixed_trees(&d.ltree, &d.dtree);
res = inflate_block_data(&d, &d.ltree, &d.dtree);
break;
case 2:
res = decode_trees(&d, &d.ltree, &d.dtree);
if (res == TINF_OK) res = inflate_block_data(&d, &d.ltree, &d.dtree);
break;
default:
return TINF_DATA_ERROR;
}
if (res != TINF_OK) return res;
} while (!bfinal);
*dest_len = (unsigned)(d.dst - d.dst_start);
return TINF_OK;
}
// Decompress a zlib stream (2-byte header, deflate body, 4-byte adler trailer).
int inflate_zlib(uint8_t* dest, unsigned* dest_len,
const uint8_t* src, unsigned src_len) {
if (src_len < 2) return TINF_DATA_ERROR;
int cmf = src[0], flg = src[1];
if ((cmf & 0x0f) != 8) return TINF_DATA_ERROR; // not deflate
if (((cmf << 8) | flg) % 31 != 0) return TINF_DATA_ERROR;
if (flg & 0x20) return TINF_DATA_ERROR; // preset dictionary unsupported
return inflate_raw(dest, dest_len, src + 2, src_len - 2);
}