mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-25 16:52:29 +00:00
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
This commit is contained in:
1 parent
8a55ac345b
commit
fe33ddc79b
6 files changed
+660
-2
No files matched your search
@@ -129,6 +129,7 @@ CORE_SRCS = kernel/main.c kernel/task.c kernel/sync.c kernel/timer.c kernel/prin
|
||||
kernel/wait.c kernel/futex.c kernel/rcu.c kernel/ktest.c kernel/time/ktime.c \
|
||||
kernel/elf.c kernel/exec.c \
|
||||
lib/string.c lib/vsprintf.c lib/bitmap.c lib/rbtree.c lib/kfifo.c lib/hashtable.c lib/font8x8.c \
|
||||
lib/inflate.c lib/image.c \
|
||||
init/cmdline.c init/service.c init/bootlogo.c init/logo_data.c usr/initramfs.c block/block.c block/elevator.c \
|
||||
ipc/sem.c ipc/shm.c ipc/shm_posix.c ipc/pipe.c ipc/ipc.c ipc/msg.c \
|
||||
crypto/sha256.c crypto/md5.c crypto/crc32.c crypto/prng.c \
|
||||
|
||||
+80
-2
@@ -24,9 +24,10 @@
|
||||
#include <lib/printf.h>
|
||||
#include <net/http_client.h>
|
||||
#include <net/net.h>
|
||||
#include <lib/image.h>
|
||||
#include <drivers/keyboard.h>
|
||||
|
||||
#define WEB_FETCH_CAP 16384 // response bytes retained
|
||||
#define WEB_FETCH_CAP 131072 // response bytes retained (fits images and long pages)
|
||||
#define WEB_MAX_LINES 2048 // rendered display lines retained
|
||||
#define WEB_MAX_LINKS 400 // hyperlinks tracked per page
|
||||
#define WEB_HREF_POOL 6144 // bytes of href strings per page
|
||||
@@ -70,6 +71,11 @@ typedef struct {
|
||||
char hist[WEB_HISTORY][256];
|
||||
int hist_len;
|
||||
|
||||
// When the response is an image, it is decoded once into here and shown
|
||||
// instead of text.
|
||||
bool has_image;
|
||||
image_t image;
|
||||
|
||||
const char* cache_src; // job->buf the cache was built from
|
||||
int cache_len;
|
||||
bool cache_reader;
|
||||
@@ -307,6 +313,25 @@ static void web_refresh_cache(web_data_t* wd, int cpl) {
|
||||
wd->base_path[sizeof(wd->base_path) - 1] = '\0';
|
||||
wd->base_port = j->port;
|
||||
|
||||
// If the body is an image, decode it once and show it instead of text.
|
||||
if (wd->has_image) image_free(&wd->image);
|
||||
wd->has_image = false;
|
||||
{
|
||||
const uint8_t* body = (const uint8_t*)j->buf;
|
||||
char* sep = strstr(j->buf, "\r\n\r\n");
|
||||
if (sep) body = (const uint8_t*)(sep + 4);
|
||||
int blen = j->len - (int)((const char*)body - j->buf);
|
||||
if (blen > 0 && image_sniff(body, blen)) {
|
||||
if (image_decode(body, blen, &wd->image) == 0) wd->has_image = true;
|
||||
}
|
||||
}
|
||||
if (wd->has_image) {
|
||||
wd->nlines = 0;
|
||||
wd->cache_src = j->buf; wd->cache_len = j->len;
|
||||
wd->cache_reader = wd->reader; wd->cache_cpl = cpl;
|
||||
return; // image branch: no text layout
|
||||
}
|
||||
|
||||
if (wd->reader) {
|
||||
// Reader view: skip the headers, then strip markup from the body,
|
||||
// recording anchors as links as it goes.
|
||||
@@ -514,7 +539,59 @@ static void web_paint(gui_window_t* win) {
|
||||
|
||||
web_refresh_cache(wd, cpl);
|
||||
|
||||
if (wd->job && wd->job->state == HTTP_STATE_DONE && wd->nlines > 0) {
|
||||
if (wd->has_image && wd->job && wd->job->state == HTTP_STATE_DONE) {
|
||||
image_t* im = &wd->image;
|
||||
|
||||
int avail_w = (w - 20) - 12; // panel minus scrollbar
|
||||
if (avail_w < 16) avail_w = 16;
|
||||
int sw = im->width, sh = im->height;
|
||||
if (sw > avail_w) { sh = (int)((long)sh * avail_w / sw); sw = avail_w; }
|
||||
if (sh < 1) sh = 1;
|
||||
|
||||
int page = view_h - 6;
|
||||
if (wd->scroll > sh - 1) wd->scroll = sh - 1;
|
||||
if (wd->scroll < 0) wd->scroll = 0;
|
||||
|
||||
int draw_x = ox + view_x + 6;
|
||||
int draw_y0 = oy + view_y + 4;
|
||||
|
||||
// Column map (screen column -> source column) so the inner loop has no
|
||||
// divide. Bounded by the panel width.
|
||||
static int xmap[1920];
|
||||
int cols = sw < (int)(sizeof(xmap) / sizeof(xmap[0])) ? sw
|
||||
: (int)(sizeof(xmap) / sizeof(xmap[0]));
|
||||
for (int sx = 0; sx < cols; sx++) xmap[sx] = (int)((long)sx * im->width / sw);
|
||||
|
||||
for (int r = 0; r < page; r++) {
|
||||
int scaled_y = wd->scroll + r;
|
||||
if (scaled_y >= sh) break;
|
||||
int iy = (int)((long)scaled_y * im->height / sh);
|
||||
if (iy >= im->height) break;
|
||||
const uint32_t* srow = im->pixels + (long)iy * im->width;
|
||||
int py = draw_y0 + r;
|
||||
for (int sx = 0; sx < cols; sx++) {
|
||||
uint32_t px = srow[xmap[sx]];
|
||||
uint8_t a = (uint8_t)(px >> 24);
|
||||
uint32_t rgb = 0xFF000000u | (px & 0x00FFFFFFu);
|
||||
uint32_t col = (a == 255) ? rgb
|
||||
: gui_color_alpha_blend(GUI_THEME_BG_DARK, rgb, a);
|
||||
gui_gfx_draw_pixel(draw_x + sx, py, col);
|
||||
}
|
||||
}
|
||||
|
||||
gui_scrollbar(4, w - 18, view_y, view_h, &wd->scroll, sh, page);
|
||||
|
||||
if (in->focus_id != WEB_URL_FIELD) {
|
||||
if (in->key == KEY_DOWN) wd->scroll += 16;
|
||||
else if (in->key == KEY_UP) wd->scroll -= 16;
|
||||
else if (in->key == KEY_PAGE_DOWN) wd->scroll += page;
|
||||
else if (in->key == KEY_PAGE_UP) wd->scroll -= page;
|
||||
}
|
||||
|
||||
char hint[48];
|
||||
snprintf(hint, sizeof(hint), "image %dx%d", im->width, im->height);
|
||||
gui_label_aligned(0, bar_y, w - 12, hint, GUI_THEME_ACCENT, GUI_ALIGN_RIGHT);
|
||||
} else if (wd->job && wd->job->state == HTTP_STATE_DONE && wd->nlines > 0) {
|
||||
if (wd->scroll > wd->nlines - 1) wd->scroll = wd->nlines - 1;
|
||||
if (wd->scroll < 0) wd->scroll = 0;
|
||||
|
||||
@@ -613,6 +690,7 @@ static void web_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
if (ev->type == GUI_EVENT_CLOSE && win->user_data) {
|
||||
web_data_t* wd = (web_data_t*)win->user_data;
|
||||
if (wd->job) http_fetch_release(wd->job); // safe mid-fetch
|
||||
if (wd->has_image) image_free(&wd->image);
|
||||
if (wd->text) kfree(wd->text);
|
||||
kfree(wd);
|
||||
win->user_data = NULL;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef _LIB_IMAGE_H
|
||||
#define _LIB_IMAGE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Decoded image: a heap-allocated buffer of 0xAARRGGBB pixels, row-major, ready
|
||||
// to hand to gui_gfx_blit. Free with image_free.
|
||||
typedef struct {
|
||||
int width;
|
||||
int height;
|
||||
uint32_t* pixels;
|
||||
} image_t;
|
||||
|
||||
// True if the bytes begin with a signature this decoder supports (PNG or BMP).
|
||||
bool image_sniff(const uint8_t* data, unsigned len);
|
||||
|
||||
// Decode PNG (8-bit grayscale/RGB/RGBA/palette, non-interlaced) or BMP
|
||||
// (24/32-bit uncompressed) into img. Returns 0 on success. On failure img is
|
||||
// left zeroed and nothing needs freeing.
|
||||
int image_decode(const uint8_t* data, unsigned len, image_t* img);
|
||||
|
||||
void image_free(image_t* img);
|
||||
|
||||
#endif // _LIB_IMAGE_H
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef _LIB_INFLATE_H
|
||||
#define _LIB_INFLATE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// DEFLATE / zlib decompression (RFC 1950/1951). Both write into a
|
||||
// caller-provided buffer; *dest_len is the buffer capacity on entry and the
|
||||
// decompressed length on success. Return 0 on success, negative on error.
|
||||
int inflate_raw(uint8_t* dest, unsigned* dest_len,
|
||||
const uint8_t* src, unsigned src_len);
|
||||
int inflate_zlib(uint8_t* dest, unsigned* dest_len,
|
||||
const uint8_t* src, unsigned src_len);
|
||||
|
||||
#endif // _LIB_INFLATE_H
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Image decoder: PNG (8-bit grayscale / RGB / RGBA / palette, non-interlaced)
|
||||
* and BMP (24/32-bit uncompressed). Output is a flat 0xAARRGGBB buffer ready
|
||||
* for gui_gfx_blit, so the Web app can show pictures.
|
||||
*
|
||||
* PNG = parse chunks, concatenate IDAT, zlib-inflate, undo the per-scanline
|
||||
* filters, then expand to ARGB. All integer, heap-allocated (never on the
|
||||
* worker's small stack).
|
||||
*/
|
||||
#include <lib/image.h>
|
||||
#include <lib/inflate.h>
|
||||
#include <lib/string.h>
|
||||
#include <mm/kmalloc.h>
|
||||
|
||||
// Cap the pixel count so a hostile or huge image cannot exhaust the heap.
|
||||
#define IMAGE_MAX_PIXELS (4 * 1024 * 1024) // 4 Mpx -> 16 MB output max
|
||||
|
||||
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 >= 2 && d[0] == 'B' && d[1] == 'M') return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void image_free(image_t* img) {
|
||||
if (img && img->pixels) { kfree(img->pixels); img->pixels = NULL; }
|
||||
}
|
||||
|
||||
static inline uint32_t rd32be(const uint8_t* p) {
|
||||
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
|
||||
((uint32_t)p[2] << 8) | (uint32_t)p[3];
|
||||
}
|
||||
static inline uint32_t rd32le(const uint8_t* p) {
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
|
||||
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
||||
}
|
||||
static inline uint32_t argb(uint8_t a, uint8_t r, uint8_t g, uint8_t b) {
|
||||
return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// PNG
|
||||
// ===========================================================================
|
||||
static int iabs(int x) { return x < 0 ? -x : x; }
|
||||
|
||||
static int paeth(int a, int b, int c) {
|
||||
int p = a + b - c;
|
||||
int pa = iabs(p - a), pb = iabs(p - b), pc = iabs(p - c);
|
||||
if (pa <= pb && pa <= pc) return a;
|
||||
if (pb <= pc) return b;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int decode_png(const uint8_t* data, unsigned len, image_t* img) {
|
||||
if (len < 8 + 25 || memcmp(data, png_sig, 8) != 0) return -1;
|
||||
|
||||
unsigned pos = 8;
|
||||
int width = 0, height = 0, depth = 0, color = 0, interlace = 0;
|
||||
uint8_t palette[256][3];
|
||||
uint8_t trns[256];
|
||||
int have_plte = 0, plte_n = 0, have_trns = 0, trns_n = 0;
|
||||
memset(trns, 255, sizeof(trns));
|
||||
|
||||
// Compressed IDAT is gathered into one buffer.
|
||||
uint8_t* idat = NULL;
|
||||
unsigned idat_len = 0, idat_cap = 0;
|
||||
|
||||
for (;;) {
|
||||
if (pos + 8 > len) { if (idat) kfree(idat); return -1; }
|
||||
uint32_t clen = rd32be(data + pos);
|
||||
const uint8_t* ctype = data + pos + 4;
|
||||
const uint8_t* cdata = data + pos + 8;
|
||||
if (pos + 12 + clen > len) { if (idat) kfree(idat); return -1; }
|
||||
|
||||
if (memcmp(ctype, "IHDR", 4) == 0) {
|
||||
if (clen < 13) { if (idat) kfree(idat); return -1; }
|
||||
width = (int)rd32be(cdata);
|
||||
height = (int)rd32be(cdata + 4);
|
||||
depth = cdata[8];
|
||||
color = cdata[9];
|
||||
interlace = cdata[12];
|
||||
if (width <= 0 || height <= 0) { if (idat) kfree(idat); return -1; }
|
||||
if ((long)width * height > IMAGE_MAX_PIXELS) { if (idat) kfree(idat); return -1; }
|
||||
if (interlace != 0) { if (idat) kfree(idat); return -1; } // no Adam7
|
||||
if (color != 0 && color != 2 && color != 3 && color != 4 && color != 6) {
|
||||
if (idat) kfree(idat);
|
||||
return -1;
|
||||
}
|
||||
// Grayscale (0) and palette (3) may use 1/2/4/8-bit samples; the
|
||||
// truecolor types are supported at 8-bit only (16-bit rejected).
|
||||
bool sub_ok = (color == 0 || color == 3) &&
|
||||
(depth == 1 || depth == 2 || depth == 4 || depth == 8);
|
||||
if (depth != 8 && !sub_ok) { if (idat) kfree(idat); return -1; }
|
||||
} else if (memcmp(ctype, "PLTE", 4) == 0) {
|
||||
plte_n = clen / 3;
|
||||
if (plte_n > 256) plte_n = 256;
|
||||
for (int i = 0; i < plte_n; i++) {
|
||||
palette[i][0] = cdata[i * 3 + 0];
|
||||
palette[i][1] = cdata[i * 3 + 1];
|
||||
palette[i][2] = cdata[i * 3 + 2];
|
||||
}
|
||||
have_plte = 1;
|
||||
} else if (memcmp(ctype, "tRNS", 4) == 0) {
|
||||
trns_n = clen > 256 ? 256 : (int)clen;
|
||||
for (int i = 0; i < trns_n; i++) trns[i] = cdata[i];
|
||||
have_trns = 1;
|
||||
} else if (memcmp(ctype, "IDAT", 4) == 0) {
|
||||
if (idat_len + clen > idat_cap) {
|
||||
unsigned ncap = (idat_len + clen) * 2 + 1024;
|
||||
uint8_t* n = (uint8_t*)kmalloc(ncap);
|
||||
if (!n) { if (idat) kfree(idat); return -1; }
|
||||
if (idat) { memcpy(n, idat, idat_len); kfree(idat); }
|
||||
idat = n; idat_cap = ncap;
|
||||
}
|
||||
memcpy(idat + idat_len, cdata, clen);
|
||||
idat_len += clen;
|
||||
} else if (memcmp(ctype, "IEND", 4) == 0) {
|
||||
break;
|
||||
}
|
||||
pos += 12 + clen; // length + type + data + crc
|
||||
}
|
||||
|
||||
if (!idat || width == 0) { if (idat) kfree(idat); return -1; }
|
||||
if (color == 3 && !have_plte) { kfree(idat); return -1; }
|
||||
|
||||
int channels = (color == 0) ? 1 : (color == 2) ? 3 : (color == 3) ? 1 :
|
||||
(color == 4) ? 2 : 4;
|
||||
int bits_pp = channels * depth;
|
||||
int bpp = (bits_pp + 7) / 8; // filter unit, >= 1 byte
|
||||
if (bpp < 1) bpp = 1;
|
||||
unsigned stride = ((unsigned)width * bits_pp + 7) / 8;
|
||||
unsigned raw_len = (stride + 1) * (unsigned)height; // +1 filter byte per row
|
||||
|
||||
uint8_t* raw = (uint8_t*)kmalloc(raw_len);
|
||||
if (!raw) { kfree(idat); return -1; }
|
||||
|
||||
unsigned got = raw_len;
|
||||
int ir = inflate_zlib(raw, &got, idat, idat_len);
|
||||
kfree(idat);
|
||||
if (ir != 0 || got != raw_len) { kfree(raw); return -1; }
|
||||
|
||||
// Unfilter each scanline in place, writing reconstructed bytes over the
|
||||
// filtered ones (the filter byte at the row start is consumed).
|
||||
uint8_t* unf = (uint8_t*)kmalloc(stride * (unsigned)height);
|
||||
if (!unf) { kfree(raw); return -1; }
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
uint8_t filter = raw[y * (stride + 1)];
|
||||
const uint8_t* rowin = raw + y * (stride + 1) + 1;
|
||||
uint8_t* row = unf + (unsigned)y * stride;
|
||||
uint8_t* prev = (y > 0) ? unf + (unsigned)(y - 1) * stride : NULL;
|
||||
|
||||
for (unsigned x = 0; x < stride; x++) {
|
||||
int a = (x >= (unsigned)bpp) ? row[x - bpp] : 0;
|
||||
int b = prev ? prev[x] : 0;
|
||||
int c = (prev && x >= (unsigned)bpp) ? prev[x - bpp] : 0;
|
||||
int val = rowin[x];
|
||||
switch (filter) {
|
||||
case 0: break; // None
|
||||
case 1: val += a; break; // Sub
|
||||
case 2: val += b; break; // Up
|
||||
case 3: val += (a + b) / 2; break; // Average
|
||||
case 4: val += paeth(a, b, c); break;// Paeth
|
||||
default: kfree(raw); kfree(unf); return -1;
|
||||
}
|
||||
row[x] = (uint8_t)val;
|
||||
}
|
||||
}
|
||||
kfree(raw);
|
||||
|
||||
uint32_t* out = (uint32_t*)kmalloc((unsigned)width * height * 4);
|
||||
if (!out) { kfree(unf); return -1; }
|
||||
|
||||
int maxv = (1 << depth) - 1;
|
||||
for (int y = 0; y < height; y++) {
|
||||
const uint8_t* row = unf + (unsigned)y * stride;
|
||||
uint32_t* dst = out + (unsigned)y * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
if (depth < 8) {
|
||||
// Grayscale or palette with sub-byte samples, MSB-first.
|
||||
int bitpos = x * depth;
|
||||
int sample = (row[bitpos >> 3] >> (8 - depth - (bitpos & 7))) & maxv;
|
||||
if (color == 3) {
|
||||
int idx = sample;
|
||||
if (idx >= plte_n) idx = 0;
|
||||
uint8_t alpha = (have_trns && idx < trns_n) ? trns[idx] : 255;
|
||||
dst[x] = argb(alpha, palette[idx][0], palette[idx][1], palette[idx][2]);
|
||||
} else {
|
||||
uint8_t g = (uint8_t)(sample * 255 / maxv);
|
||||
dst[x] = argb(255, g, g, g);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint8_t* px = row + x * bpp;
|
||||
switch (color) {
|
||||
case 0: dst[x] = argb(255, px[0], px[0], px[0]); break; // gray
|
||||
case 4: dst[x] = argb(px[1], px[0], px[0], px[0]); break; // gray+alpha
|
||||
case 2: dst[x] = argb(255, px[0], px[1], px[2]); break; // RGB
|
||||
case 6: dst[x] = argb(px[3], px[0], px[1], px[2]); break; // RGBA
|
||||
case 3: { // palette
|
||||
int idx = px[0];
|
||||
if (idx >= plte_n) idx = 0;
|
||||
uint8_t alpha = (have_trns && idx < trns_n) ? trns[idx] : 255;
|
||||
dst[x] = argb(alpha, palette[idx][0], palette[idx][1], palette[idx][2]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
kfree(unf);
|
||||
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
img->pixels = out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// BMP (24/32-bit uncompressed, BI_RGB)
|
||||
// ===========================================================================
|
||||
static int decode_bmp(const uint8_t* data, unsigned len, image_t* img) {
|
||||
if (len < 54 || data[0] != 'B' || data[1] != 'M') return -1;
|
||||
uint32_t offset = rd32le(data + 10);
|
||||
uint32_t dib = rd32le(data + 14);
|
||||
int width = (int)rd32le(data + 18);
|
||||
int height = (int)rd32le(data + 22);
|
||||
int bpp = data[28] | (data[29] << 8);
|
||||
uint32_t compression = rd32le(data + 30);
|
||||
if (dib < 40 || compression != 0) return -1;
|
||||
if (bpp != 24 && bpp != 32) return -1;
|
||||
|
||||
bool top_down = height < 0;
|
||||
if (top_down) height = -height;
|
||||
if (width <= 0 || height <= 0) return -1;
|
||||
if ((long)width * height > IMAGE_MAX_PIXELS) return -1;
|
||||
|
||||
int bytes_pp = bpp / 8;
|
||||
unsigned row_size = (((unsigned)width * bytes_pp + 3) & ~3u);
|
||||
if (offset + row_size * (unsigned)height > len) return -1;
|
||||
|
||||
uint32_t* out = (uint32_t*)kmalloc((unsigned)width * height * 4);
|
||||
if (!out) return -1;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
int srow = top_down ? y : (height - 1 - y);
|
||||
const uint8_t* row = data + offset + (unsigned)srow * row_size;
|
||||
uint32_t* dst = out + (unsigned)y * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
const uint8_t* px = row + x * bytes_pp;
|
||||
uint8_t a = (bytes_pp == 4) ? px[3] : 255;
|
||||
dst[x] = argb(a, px[2], px[1], px[0]); // BMP is BGR(A)
|
||||
}
|
||||
}
|
||||
|
||||
img->width = width;
|
||||
img->height = height;
|
||||
img->pixels = out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
int image_decode(const uint8_t* data, unsigned len, image_t* img) {
|
||||
if (!data || !img) return -1;
|
||||
img->width = img->height = 0;
|
||||
img->pixels = NULL;
|
||||
|
||||
if (len >= 8 && memcmp(data, png_sig, 8) == 0) return decode_png(data, len, img);
|
||||
if (len >= 2 && data[0] == 'B' && data[1] == 'M') return decode_bmp(data, len, img);
|
||||
return -1;
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
Reference in new issue
Block a user