feat(lib): baseline JPEG decoder -- the browser shows photos

Adds a from-scratch baseline JPEG decoder (ITU-T T.81, SOF0 sequential DCT),
so the Web app now displays the web's dominant photo format alongside PNG and
BMP. image_decode dispatches on the JPEG magic, so the browser needs no change
beyond that.

lib/jpeg.c handles grayscale and YCbCr, arbitrary chroma subsampling
(4:4:4/4:2:2/4:2:0), restart markers, and multiple DQT/DHT tables. Huffman
uses canonical tables (counts + symbols, like the inflate decoder) to avoid a
large fast-table; the transform is the standard fixed-point integer row/column
IDCT; colour is integer YCbCr->RGB. All heap-allocated, no floating point, so
it is safe in the freestanding kernel. Progressive (SOF2) and arithmetic-coded
JPEGs are rejected rather than mis-decoded.

Verified on the host against PIL-encoded references before going in the kernel,
the same way the crypto and PNG decoders were: grayscale decodes at mean
absolute error 0.9/channel, 4:4:4 at 1.7 (pure IDCT rounding vs libjpeg), and
4:2:0 at 4.3 (nearest-neighbour chroma upsampling softens colour edges -- a
known, visually-fine artifact). In QEMU the Web app fetched a 400x260 JPEG over
HTTP and rendered it correctly -- gradient, an antialiased red circle, a green
box, a yellow diagonal. Builds clean on x86_64, aarch64 and armv8i.

Limits: baseline only (no progressive), and chroma upsampling is
nearest-neighbour rather than the smoother interpolation libjpeg uses. The
browser still shows images by URL, not inline in a page.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
This commit is contained in:
subhobhai943andClaude Opus 4.8 committed 2026-08-27 19:24:13 +05:30
1 parent fe33ddc79b
commit db962de248
4 files changed
+486 -1

No files matched your search

+4
View File
@@ -9,6 +9,7 @@
*/
#include <lib/image.h>
#include <lib/inflate.h>
#include <lib/jpeg.h>
#include <lib/string.h>
#include <mm/kmalloc.h>
@@ -19,6 +20,7 @@ static const uint8_t png_sig[8] = { 0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a
bool image_sniff(const uint8_t* d, unsigned len) {
if (len >= 8 && memcmp(d, png_sig, 8) == 0) return true;
if (len >= 3 && d[0] == 0xFF && d[1] == 0xD8 && d[2] == 0xFF) return true; // JPEG
if (len >= 2 && d[0] == 'B' && d[1] == 'M') return true;
return false;
}
@@ -267,6 +269,8 @@ int image_decode(const uint8_t* data, unsigned len, image_t* img) {
img->pixels = NULL;
if (len >= 8 && memcmp(data, png_sig, 8) == 0) return decode_png(data, len, img);
if (len >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF)
return jpeg_decode(data, len, img);
if (len >= 2 && data[0] == 'B' && data[1] == 'M') return decode_bmp(data, len, img);
return -1;
}