Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Miscellaneous:
(UEFI only).
* `graphics` - If set to `no`, force text mode for the boot menu, else use
a video mode.
* `wallpaper` - Path to a file to use as a wallpaper. BMP, PNG, and JPEG
* `wallpaper` - Path to a file to use as a wallpaper. BMP, PNG, JPEG, and QOI
formats are supported. There can be multiple of this option, in which case
the wallpaper will be randomly selected from the provided options.
* `wallpaper_style` - The style which will be used to display the wallpaper
Expand Down
50 changes: 35 additions & 15 deletions common/lib/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <lib/config.h>
#include <lib/misc.h>
#include <mm/pmm.h>
#include <lib/qoi.h>
#include <lib/stb_image.h>

void image_make_centered(struct image *image, int frame_x_size, int frame_y_size, uint32_t back_colour) {
Expand All @@ -22,6 +23,14 @@ void image_make_stretched(struct image *image, int new_x_size, int new_y_size) {
image->y_size = new_y_size;
}

static void free_image_data(struct image *image) {
if (image->isQoi) {
qoi_free(image->img);
} else {
stbi_image_free(image->img);
}
}

struct image *image_open(struct file_handle *file) {
struct image *image = ext_mem_alloc(sizeof(struct image));

Expand All @@ -31,33 +40,44 @@ struct image *image_open(struct file_handle *file) {

fread(file, src, 0, file->size);

int x, y, bpp;

image->img = stbi_load_from_memory(src, file->size, &x, &y, &bpp, 4);
int x = 0, y = 0;
image->isQoi = file->size >= 4
&& ((const uint8_t *)src)[0] == 'q'
&& ((const uint8_t *)src)[1] == 'o'
&& ((const uint8_t *)src)[2] == 'i'
&& ((const uint8_t *)src)[3] == 'f';

if (image->isQoi) {
image->img = qoi_decode(src, file->size, &x, &y);
} else {
int bpp;
image->img = stbi_load_from_memory(src, file->size, &x, &y, &bpp, 4);
}

pmm_free(src, file->size);

if (image->img == NULL || x == 0 || y == 0) {
if (image->img != NULL) {
// stbi allocated but dimensions are degenerate
stbi_image_free(image->img);
}
free_image_data(image);
pmm_free(image, sizeof(struct image));
return NULL;
}

// Convert ABGR to XRGB
uint32_t *pptr = (void *)image->img;
size_t pixel_count = CHECKED_MUL((size_t)x, (size_t)y,
({ stbi_image_free(image->img); pmm_free(image, sizeof(struct image)); return NULL; }));
for (size_t i = 0; i < pixel_count; i++) {
pptr[i] = (pptr[i] & 0x0000ff00) | ((pptr[i] & 0x00ff0000) >> 16) | ((pptr[i] & 0x000000ff) << 16);
// stb_image returns RGBA bytes (little-endian uint32 ABGR); convert to
// the framebuffer-native XRGB layout. The QOI decoder already produces
// XRGB, so this step is skipped on that path.
if (!image->isQoi) {
uint32_t *pptr = (void *)image->img;
size_t pixel_count = CHECKED_MUL((size_t)x, (size_t)y,
({ free_image_data(image); pmm_free(image, sizeof(struct image)); return NULL; }));
for (size_t i = 0; i < pixel_count; i++) {
pptr[i] = (pptr[i] & 0x0000ff00) | ((pptr[i] & 0x00ff0000) >> 16) | ((pptr[i] & 0x000000ff) << 16);
}
}

image->x_size = x;
image->y_size = y;
image->pitch = (int)CHECKED_MUL((size_t)x, 4,
({ stbi_image_free(image->img); pmm_free(image, sizeof(struct image)); return NULL; }));
({ free_image_data(image); pmm_free(image, sizeof(struct image)); return NULL; }));
image->bpp = 32;
image->img_width = x;
image->img_height = y;
Expand All @@ -66,6 +86,6 @@ struct image *image_open(struct file_handle *file) {
}

void image_close(struct image *image) {
stbi_image_free(image->img);
free_image_data(image);
pmm_free(image, sizeof(struct image));
}
5 changes: 2 additions & 3 deletions common/lib/image.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ struct image {
int64_t x_displacement;
int64_t y_displacement;
uint32_t back_colour;
char isQoi;
};

enum {
IMAGE_TILED,
IMAGE_CENTERED,
IMAGE_STRETCHED
IMAGE_TILED, IMAGE_CENTERED, IMAGE_STRETCHED
};

void image_make_centered(struct image *image, int frame_x_size, int frame_y_size, uint32_t back_colour);
Expand Down
98 changes: 98 additions & 0 deletions common/lib/qoi.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#include <stdint.h>
#include <stddef.h>
#include <lib/qoi.h>
#include <lib/misc.h>
#include <mm/pmm.h>

#define QOI_OP_INDEX 0x00
#define QOI_OP_DIFF 0x40
#define QOI_OP_LUMA 0x80
#define QOI_OP_RUN 0xc0
#define QOI_OP_RGB 0xfe
#define QOI_OP_RGBA 0xff
#define QOI_OP_MASK 0xc0
#define QOI_HEADER_SIZE 14
#define QOI_PADDING_SIZE 8
#define QOI_MAX_DIM 65536u
#define QOI_MAX_PIXELS ((size_t)400000000)
#define QOI_HASH(r, g, b, a) \
((((unsigned)(r) * 3u) + ((unsigned)(g) * 5u) + \
((unsigned)(b) * 7u) + ((unsigned)(a) * 11u)) & 63u)

static uint32_t qoi_be32(const uint8_t *p) {
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) | (uint32_t)p[3];
}

/* The decoded buffer is laid out as [total_bytes][padding to 16][pixels...].
The 16-byte header lets qoi_free() recover the original allocation size
without the caller having to track it. */
static uint32_t *qoi_alloc_xrgb(size_t pixels) {
size_t bytes = CHECKED_MUL(pixels, (size_t)4, return NULL);
size_t total = CHECKED_ADD(bytes, (size_t)16, return NULL);
void *raw = ext_mem_alloc(total);
*(size_t *) raw = total;
return (uint32_t *)((uint8_t *) raw + 16);
}

void qoi_free(uint8_t *buf) {
if (buf) { uint8_t *raw = buf - 16; pmm_free(raw, *(size_t *) raw); }
}

uint8_t *qoi_decode(const void *src, size_t src_size,
int *out_w, int *out_h) {
if (!src || src_size < QOI_HEADER_SIZE + QOI_PADDING_SIZE) return NULL;
const uint8_t * p = src;
if (p[0] != 'q' || p[1] != 'o' || p[2] != 'i' || p[3] != 'f')
return NULL;
uint32_t w = qoi_be32(p + 4), h = qoi_be32(p + 8);
uint8_t channels = p[12]; /* p[13] is the colorspace tag, ignored. */
if (w == 0 || h == 0 || w > QOI_MAX_DIM || h > QOI_MAX_DIM) return NULL;
if (channels != 3 && channels != 4) return NULL;
size_t pixels = CHECKED_MUL((size_t)w, (size_t)h, return NULL);
if (pixels > QOI_MAX_PIXELS) return NULL;
uint32_t *out = qoi_alloc_xrgb(pixels);
if (out == NULL) return NULL;
uint32_t index[64] = { 0 }, v; int run = 0, dg;
uint8_t r = 0, g = 0, b = 0, a = 0xff, b2;
size_t pos = QOI_HEADER_SIZE, end = src_size - QOI_PADDING_SIZE;
for (size_t px = 0; px < pixels; px++) {
if (run > 0) run--; else {
if (pos >= end) goto fail;
uint8_t op = p[pos++];
if (op == QOI_OP_RGB) {
if (end - pos < 3) goto fail;
r = p[pos++]; g = p[pos++]; b = p[pos++];
} else if (op == QOI_OP_RGBA) {
if (end - pos < 4) goto fail;
r = p[pos++]; g = p[pos++]; b = p[pos++]; a = p[pos++];
} else switch (op & QOI_OP_MASK) {
case QOI_OP_INDEX:
v = index[op & 0x3f];
r = v; g = v >> 8; b = v >> 16; a = v >> 24;
break;
case QOI_OP_DIFF:
r += (int)((op >> 4) & 3u) - 2;
g += (int)((op >> 2) & 3u) - 2;
b += (int)( op & 3u) - 2;
break;
case QOI_OP_LUMA:
if (pos >= end) goto fail;
b2 = p[pos++];
dg = (int)(op & 0x3f) - 32;
r += dg + (int)((b2 >> 4) & 0xf) - 8;
g += dg;
b += dg + (int)( b2 & 0xf) - 8;
break;
case QOI_OP_RUN: run = op & 0x3f; break;
}
index[QOI_HASH(r, g, b, a)] =
(uint32_t)r | ((uint32_t)g << 8) |
((uint32_t)b << 16) | ((uint32_t)a << 24);
}
out[px] = ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
}
*out_w = (int)w; *out_h = (int)h; return (uint8_t *) out;
fail:
qoi_free((uint8_t *) out); return NULL;
}
19 changes: 19 additions & 0 deletions common/lib/qoi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef LIB__QOI_H__
#define LIB__QOI_H__

#include <stdint.h>
#include <stddef.h>

/* Decodes a QOI image (https://qoiformat.org) from `src` (size `src_size`)
into a freshly allocated XRGB8 pixel buffer. Each output pixel is a 32-bit
little-endian word laid out as 0x00RRGGBB; the QOI alpha is dropped.
On success, returns the buffer and writes the decoded width/height into
*out_w / *out_h. Returns NULL on malformed input. The returned buffer
must be released with qoi_free(). */
uint8_t *qoi_decode(const void *src, size_t src_size,
int *out_w, int *out_h);

/* Releases a buffer returned by qoi_decode(). NULL is accepted. */
void qoi_free(uint8_t *buf);

#endif