Skip to content

Commit

Permalink
Add JPEG image loader
Browse files Browse the repository at this point in the history
A preliminary JPEG image loader is implemented, capable of converting
an arbitrary JPEG file into an ARGB32 pixmap. The exception handling
should be improved to deliver corresponding error codes rather than just
dumping the message to the console.
  • Loading branch information
jserv committed Jul 23, 2024
1 parent 83fb1ee commit 2d85c45
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 3 deletions.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# FIXME: make these entries configurable
CONFIG_BACKEND_SDL := y
CONFIG_LOADER_JPEG := y
CONFIG_LOADER_PNG := y

# Rules
Expand Down Expand Up @@ -51,6 +52,12 @@ libtwin.a_includes-y := include

# Image loaders

ifeq ($(CONFIG_LOADER_JPEG), y)
libtwin.a_files-y += src/image-jpeg.c
libtwin.a_cflags-y += $(shell pkg-config --cflags libjpeg)
TARGET_LIBS += $(shell pkg-config --libs libjpeg)
endif

ifeq ($(CONFIG_LOADER_PNG), y)
libtwin.a_files-y += src/image-png.c
libtwin.a_cflags-y += $(shell pkg-config --cflags libpng)
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ benefiting the entire application stack.
`Mado` is built with a minimalist design in mind. However, its verification
relies on certain third-party packages for full functionality and access to all
its features. To ensure proper operation, the development environment should
have the [SDL2 library](https://www.libsdl.org/) and [libpng](https://github.com/pnggroup/libpng) installed.
* macOS: `brew install sdl2 libpng`
* Ubuntu Linux / Debian: `sudo apt install libsdl2-dev libpng-dev`
have the [SDL2 library](https://www.libsdl.org/), [libjpeg](https://www.ijg.org/), and [libpng](https://github.com/pnggroup/libpng) installed.
* macOS: `brew install sdl2 jpeg libpng`
* Ubuntu Linux / Debian: `sudo apt install libsdl2-dev libjpeg-dev libpng-dev`

Build the library and demo program.
```shell
Expand Down
6 changes: 6 additions & 0 deletions include/twin.h
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,12 @@ void twin_icon_draw(twin_pixmap_t *pixmap,
twin_icon_t icon,
twin_matrix_t matrix);

/*
* image-jpeg.c
*/

twin_pixmap_t *twin_jpeg_to_pixmap(const char *filepath, twin_format_t fmt);

/*
* image-png.c
*/
Expand Down
125 changes: 125 additions & 0 deletions src/image-jpeg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Twin - A Tiny Window System
* Copyright (c) 2024 National Cheng Kung University, Taiwan
* All rights reserved.
*/

#include <setjmp.h>
#include <stdio.h>
#include <stdint.h>

#include <jpeglib.h>

#include "twin_private.h"

/* colorspace definitions matching libjpeg */
typedef enum {
TWIN_JCS_UNKNOWN, /* error/unspecified */
TWIN_JCS_GRAYSCALE, /* monochrome */
TWIN_JCS_RGB, /* red/green/blue */
TWIN_JCS_YCbCr, /* Y/Cb/Cr (also known as YUV) */
TWIN_JCS_CMYK, /* C/M/Y/K */
TWIN_JCS_YCCK /* Y/Cb/Cr/K */
} twin_jpeg_cspace_t;

#if BITS_IN_JSAMPLE != 8
#error This implementation supports only libjpeg with 8 bits per sample.
#endif

struct twin_jpeg_err_mgr {
struct jpeg_error_mgr mgr;
jmp_buf jbuf;
};

static void twin_jpeg_error_exit(j_common_ptr cinfo)
{
struct twin_jpeg_err_mgr *jerr = (struct twin_jpeg_err_mgr *) cinfo->err;

/* Optionally display the error message */
(*cinfo->err->output_message)(cinfo);

longjmp(jerr->jbuf, 1);
}

twin_pixmap_t *twin_jpeg_to_pixmap(const char *filepath, twin_format_t fmt)
{
twin_pixmap_t *pix = NULL;

/* Current implementation only produces TWIN_ARGB32 and TWIN_A8 */
if (fmt != TWIN_ARGB32 && fmt != TWIN_A8)
return NULL;

FILE *infile = fopen(filepath, "rb");
if (!infile) {
fprintf(stderr, "Failed to open %s\n", filepath);
return NULL;
}

/* Error handling */
struct jpeg_decompress_struct cinfo;
memset(&cinfo, 0, sizeof(cinfo));
struct twin_jpeg_err_mgr jerr = {.mgr.error_exit = twin_jpeg_error_exit};
cinfo.err = jpeg_std_error(&jerr.mgr);
if (setjmp(jerr.jbuf)) {
fprintf(stderr, "Failed to decode %s\n", filepath);
if (pix)
twin_pixmap_destroy(pix);
jpeg_destroy_decompress(&cinfo);
fclose(infile);
return NULL;
}

/* Initialize libjpeg, hook it up to file, and read header */
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, infile);
(void) jpeg_read_header(&cinfo, true);

/* Configure */
twin_coord_t width = cinfo.image_width, height = cinfo.image_height;
if (fmt == TWIN_ARGB32)
cinfo.out_color_space = JCS_RGB;
else
cinfo.out_color_space = JCS_GRAYSCALE;

/* Allocate pixmap */
pix = twin_pixmap_create(fmt, width, height);
if (!pix)
longjmp(jerr.jbuf, 1);

/* Start decompressing */
(void) jpeg_start_decompress(&cinfo);

if ((fmt == TWIN_A8 && cinfo.output_components != 1) ||
(fmt == TWIN_ARGB32 &&
(cinfo.output_components != 3 && cinfo.output_components != 4)))
longjmp(jerr.jbuf, 1);

int rowstride = cinfo.output_width * cinfo.output_components;

JSAMPARRAY rowbuf = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo,
JPOOL_IMAGE, rowstride, 1);

/* Process rows */
while (cinfo.output_scanline < cinfo.output_height) {
twin_pointer_t p = twin_pixmap_pointer(pix, 0, cinfo.output_scanline);
(void) jpeg_read_scanlines(&cinfo, rowbuf, 1);
if (fmt == TWIN_A8 || cinfo.output_components == 4)
memcpy(p.a8, rowbuf, rowstride);
else {
JSAMPLE *s = *rowbuf;
for (int i = 0; i < width; i++) {
uint32_t r = *(s++);
uint32_t g = *(s++);
uint32_t b = *(s++);
*(p.argb32++) = 0xFF000000U | (r << 16) | (g << 8) | b;
}
}
}

/* clean up */
(void) jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
fclose(infile);

return pix;
}

0 comments on commit 2d85c45

Please sign in to comment.