---
add ci build dependencies
add brackets around switch local variable declarations
.builds/archlinux.yml | 1 +
.builds/debian.yml | 1 +
.builds/fedora.yml | 1 +
.builds/freebsd.yml | 1 +
.builds/ubuntu.yml | 1 +
AUTHORS | 1 +
README.md | 1 +
meson.build | 1 +
meson_options.txt | 8 ++
src/backend_libjxl.c | 282 ++++++++++++++++++++++++++++++++++++++++++
src/main.c | 5 +
11 files changed, 303 insertions(+)
create mode 100644 src/backend_libjxl.c
diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
index b6b9ef2..5588919 100644
--- a/.builds/archlinux.yml
+++ b/.builds/archlinux.yml
@@ -10,6 +10,7 @@ packages:
- libheif
- libinih
- libjpeg-turbo
+ - libjxl
- libnsgif
- libpng
- librsvg
diff --git a/.builds/debian.yml b/.builds/debian.yml
index 1c11913..b9366e1 100644
--- a/.builds/debian.yml
+++ b/.builds/debian.yml
@@ -9,6 +9,7 @@ packages:
- libheif-dev
- libicu-dev
- libinih-dev
+ - libjxl-dev
- libpango1.0-dev
- libpng-dev
- librsvg2-dev
diff --git a/.builds/fedora.yml b/.builds/fedora.yml
index d5fd4b3..cf54ee9 100644
--- a/.builds/fedora.yml
+++ b/.builds/fedora.yml
@@ -7,6 +7,7 @@ packages:
- libX11-devel
- libcmocka-devel
- libicu-devel
+ - libjxl-devel
- libpng-devel
- librsvg2-devel
- libtiff-devel
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
index 0d12b09..de9cf42 100644
--- a/.builds/freebsd.yml
+++ b/.builds/freebsd.yml
@@ -7,6 +7,7 @@ packages:
- graphics/libGLU
- graphics/libheif
- graphics/libjpeg-turbo
+ - graphics/libjxl
- graphics/libnsgif
- graphics/librsvg2-rust
- graphics/png
diff --git a/.builds/ubuntu.yml b/.builds/ubuntu.yml
index 7f212d7..28f6cd7 100644
--- a/.builds/ubuntu.yml
+++ b/.builds/ubuntu.yml
@@ -9,6 +9,7 @@ packages:
- libheif-dev
- libicu-dev
- libinih-dev
+ - libjxl-dev
- libpango1.0-dev
- libpng-dev
- librsvg2-dev
diff --git a/AUTHORS b/AUTHORS
index 2e13cf5..a1e30bf 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -8,6 +8,7 @@ People who have contributed to imv:
* Jose Diez
* Kenneth Hanley
* Julian Heinzel
+ * Fabio Henrique
* Hannes Körber
* Aleksandra Kosiacka
* Michal Koutenský
diff --git a/README.md b/README.md
index a0c4a03..b02fc52 100644
--- a/README.md
+++ b/README.md
@@ -139,6 +139,7 @@ Installation
| librsvg | >=v2.44 | Optional. Provides SVG support. |
| libnsgif | | Optional. Provides animated GIF support. |
| libheif | | Optional. Provides HEIF support. |
+| libjxl | | Optional. Provides JPEGXL support. |
Dependencies are determined by which backends and window systems are enabled
when building `imv`. You can find a summary of which backends are available
diff --git a/meson.build b/meson.build
index 67ea830..75dd7c3 100644
--- a/meson.build
+++ b/meson.build
@@ -127,6 +127,7 @@ foreach backend : [
['librsvg', 'dependency', 'librsvg-2.0', '>= 2.44'],
['libnsgif', 'dependency', 'libnsgif', []],
['libheif', 'dependency', 'libheif', []],
+ ['libjxl', 'dependency', 'libjxl', []],
]
_backend_name = backend[0]
_dep_type = backend[1]
diff --git a/meson_options.txt b/meson_options.txt
index c13ef7a..686ad68 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -89,3 +89,11 @@ option('libheif',
type : 'feature',
description : 'provides: heif'
)
+
+# libjxl https://jpeg.org/jpegxl/
+# depends: brotlidec, brotlienc, highway, lcms2
+# license: modified bsd
+option('libjxl',
+ type : 'feature',
+ description : 'provides: jxl'
+)
diff --git a/src/backend_libjxl.c b/src/backend_libjxl.c
new file mode 100644
index 0000000..560d204
--- /dev/null
+++ b/src/backend_libjxl.c
@@ -0,0 +1,282 @@
+#include "backend.h"
+#include "bitmap.h"
+#include "image.h"
+#include "log.h"
+#include "source.h"
+#include "source_private.h"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <limits.h>
+
+#include <jxl/decode.h>
+
+#define BACKEND_NB_CHANNELS 4
+#define BACKEND_DEFAULT_FRAMETIME 100
+#define BACKEND_NB_ALLOC_FRAMES 64
+
+struct jxl_frame {
+ void *data;
+ int frametime;
+};
+
+struct private {
+ void *data;
+ off_t data_len;
+ int owns_data;
+
+ int width;
+ int height;
+ int is_animation;
+
+ struct jxl_frame *frames;
+ int nb_allocd_frames;
+ int cur_frame;
+ int nb_frames;
+};
+
+static void free_private(void *raw_pvt)
+{
+ if (!raw_pvt)
+ return;
+
+ struct private *pvt = raw_pvt;
+
+ if (pvt->owns_data) {
+ if (pvt->data)
+ munmap(pvt->data, pvt->data_len);
+ }
+
+ if (pvt->frames) {
+ for (int i = 0; i != pvt->nb_allocd_frames; ++i)
+ if (pvt->frames[i].data)
+ free(pvt->frames[i].data);
+
+ free(pvt->frames);
+ }
+
+ free(pvt);
+}
+
+static void push_frame(struct private *pvt, struct imv_image **img, int *frametime)
+{
+ struct imv_bitmap *bmp = malloc(sizeof *bmp);
+ size_t sz = pvt->width * pvt->height * BACKEND_NB_CHANNELS;
+
+ bmp->width = pvt->width;
+ bmp->height = pvt->height;
+ bmp->format = IMV_ABGR;
+ bmp->data = malloc(sz);
+ memcpy(bmp->data, pvt->frames[pvt->cur_frame].data, sz);
+
+ *img = imv_image_create_from_bitmap(bmp);
+ *frametime = pvt->frames[pvt->cur_frame].frametime;
+}
+
+static void first_frame(void *raw_pvt, struct imv_image **img, int *frametime)
+{
+ *img = NULL;
+ *frametime = 0;
+
+ imv_log(IMV_DEBUG, "libjxl: first_frame called\n");
+
+ struct private *pvt = raw_pvt;
+ JxlDecoder *jxld = JxlDecoderCreate(NULL);
+ if (JxlDecoderSubscribeEvents(jxld, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) {
+ imv_log(IMV_ERROR, "libjxl: decoder failed to subscribe to events\n");
+ goto end;
+ }
+
+ JxlBasicInfo info;
+ JxlDecoderStatus sts = JxlDecoderSetInput(jxld, pvt->data, pvt->data_len);
+ if (sts != JXL_DEC_SUCCESS) {
+ imv_log(IMV_ERROR, "libjxl: decoder failed to set input\n");
+ goto end;
+ }
+ JxlDecoderCloseInput(jxld);
+
+ pvt->nb_frames = 0;
+ pvt->cur_frame = 0;
+
+ JxlPixelFormat fmt = { BACKEND_NB_CHANNELS, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 };
+ pvt->nb_allocd_frames = BACKEND_NB_ALLOC_FRAMES;
+ pvt->frames = calloc(pvt->nb_allocd_frames, sizeof *pvt->frames);
+ do {
+ sts = JxlDecoderProcessInput(jxld);
+
+ switch (sts) {
+ case JXL_DEC_SUCCESS:
+ break;
+ case JXL_DEC_ERROR:
+ imv_log(IMV_ERROR, "libjxl: decoder error\n");
+ goto end;
+ case JXL_DEC_NEED_MORE_INPUT:
+ imv_log(IMV_ERROR, "libjxl: decoder needs more input\n");
+ goto end;
+ case JXL_DEC_BASIC_INFO:
+ if (JxlDecoderGetBasicInfo(jxld, &info) != JXL_DEC_SUCCESS) {
+ imv_log(IMV_ERROR, "libjxl: decoder failed to get basic info\n");
+ goto end;
+ }
+ pvt->width = info.xsize;
+ pvt->height = info.ysize;
+ pvt->is_animation = info.have_animation == JXL_TRUE ? 1 : 0;
+ break;
+ case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
+ {
+ size_t buf_sz;
+ if (JxlDecoderImageOutBufferSize(jxld, &fmt, &buf_sz) != JXL_DEC_SUCCESS) {
+ imv_log(IMV_ERROR, "libjxl: decoder failed to get output buffer size\n");
+ goto end;
+ }
+
+ if (pvt->nb_frames == pvt->nb_allocd_frames) {
+ pvt->frames = realloc(pvt->frames,
+ sizeof(struct jxl_frame) * (pvt->nb_allocd_frames + BACKEND_NB_ALLOC_FRAMES));
+ pvt->nb_allocd_frames += BACKEND_NB_ALLOC_FRAMES;
+ memset(&pvt->frames[pvt->nb_frames], 0,
+ (pvt->nb_allocd_frames * sizeof(struct jxl_frame)) - (pvt->nb_frames * sizeof(struct jxl_frame)));
+ }
+
+ struct jxl_frame *cur_frame = &pvt->frames[pvt->nb_frames];
+ cur_frame->data = malloc(buf_sz);
+
+ if (pvt->is_animation) {
+ if (info.animation.tps_numerator && info.animation.tps_denominator)
+ cur_frame->frametime = info.animation.tps_numerator / info.animation.tps_denominator;
+ else {
+ imv_log(IMV_DEBUG, "libjxl: no frametime info for animation, using default\n");
+ cur_frame->frametime = BACKEND_DEFAULT_FRAMETIME;
+ }
+ } else
+ cur_frame->frametime = 0;
+
+ if (JxlDecoderSetImageOutBuffer(jxld, &fmt, cur_frame->data, buf_sz) != JXL_DEC_SUCCESS) {
+ imv_log(IMV_ERROR, "libjxl: JxlDecoderSetImageOutBuffer failed\n");
+ goto end;
+ }
+ break;
+ }
+ case JXL_DEC_FULL_IMAGE:
+ ++pvt->nb_frames;
+ continue;
+ default:
+ imv_log(IMV_ERROR, "libjxl: unknown decoder status\n");
+ goto end;
+ }
+ } while (sts != JXL_DEC_SUCCESS);
+
+ push_frame(pvt, img, frametime);
+
+end:
+ if (jxld)
+ JxlDecoderDestroy(jxld);
+}
+
+static void next_frame(void *raw_pvt, struct imv_image **img, int *frametime)
+{
+ *img = NULL;
+ *frametime = 0;
+
+ imv_log(IMV_DEBUG, "libjxl: next_frame called\n");
+
+ struct private *pvt = raw_pvt;
+
+ if (pvt->cur_frame == pvt->nb_frames - 1)
+ pvt->cur_frame = 0;
+ else
+ ++pvt->cur_frame;
+
+ push_frame(pvt, img, frametime);
+}
+
+static const struct imv_source_vtable vtable = {
+ .load_first_frame = first_frame,
+ .load_next_frame = next_frame,
+ .free = free_private,
+};
+
+static enum backend_result open_memory(void *data, size_t sz, struct imv_source **src)
+{
+ imv_log(IMV_DEBUG, "libjxl: open_memory called\n");
+
+ struct private *pvt = calloc(1, sizeof *pvt);
+
+ switch (JxlSignatureCheck(data, sz)) {
+ case JXL_SIG_NOT_ENOUGH_BYTES:
+ imv_log(IMV_DEBUG, "libjxl: not enough bytes to read\n");
+ case JXL_SIG_INVALID:
+ imv_log(IMV_DEBUG, "libjxl: valid jxl signature not found\n");
+ return BACKEND_UNSUPPORTED;
+ default:
+ pvt->owns_data = 0;
+ pvt->data = data;
+ pvt->data_len = sz;
+ break;
+ }
+
+ *src = imv_source_create(&vtable, pvt);
+
+ return BACKEND_SUCCESS;
+}
+
+static enum backend_result open_path(const char *path, struct imv_source **src)
+{
+ imv_log(IMV_DEBUG, "libjxl: open_path(%s)\n", path);
+
+ enum backend_result ret = BACKEND_SUCCESS;
+ struct private *pvt = calloc(1, sizeof *pvt);
+
+ int fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ ret = BACKEND_BAD_PATH;
+ goto end;
+ }
+
+ pvt->data_len = lseek(fd, 0, SEEK_END);
+ if (pvt->data_len < 0) {
+ ret = BACKEND_BAD_PATH;
+ goto end;
+ }
+ pvt->data = mmap(NULL, pvt->data_len, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (!pvt->data || pvt->data == MAP_FAILED) {
+ imv_log(IMV_ERROR, "libjxl: failed to map file into memory\n");
+ ret = BACKEND_BAD_PATH;
+ goto end;
+ }
+
+ switch (JxlSignatureCheck(pvt->data, pvt->data_len)) {
+ case JXL_SIG_NOT_ENOUGH_BYTES:
+ imv_log(IMV_DEBUG, "libjxl: not enough bytes to read\n");
+ case JXL_SIG_INVALID:
+ imv_log(IMV_DEBUG, "libjxl: valid jxl signature not found\n");
+ munmap(pvt->data, pvt->data_len);
+ ret = BACKEND_UNSUPPORTED;
+ goto end;
+ default:
+ pvt->owns_data = 1;
+ break;
+ }
+
+ *src = imv_source_create(&vtable, pvt);
+
+end:
+ if (fd >= 0)
+ close(fd);
+
+ return ret;
+}
+
+const struct imv_backend imv_backend_libjxl = {
+ .name = "libjxl",
+ .description = "The official JPEGXL reference implementation",
+ .website = "https://jpeg.org/jpegxl/",
+ .license = "The Modified BSD License",
+ .open_path = &open_path,
+ .open_memory = &open_memory,
+};
+
+/* vim:set ts=2 sts=2 sw=2 et: */
diff --git a/src/main.c b/src/main.c
index 06bd005..cf851d1 100644
--- a/src/main.c
+++ b/src/main.c
@@ -9,6 +9,7 @@ extern const struct imv_backend imv_backend_libtiff;
extern const struct imv_backend imv_backend_libjpeg;
extern const struct imv_backend imv_backend_libnsgif;
extern const struct imv_backend imv_backend_libheif;
+extern const struct imv_backend imv_backend_libjxl;
int main(int argc, char **argv)
{
@@ -46,6 +47,10 @@ int main(int argc, char **argv)
imv_install_backend(imv, &imv_backend_libheif);
#endif
+#ifdef IMV_BACKEND_LIBJXL
+ imv_install_backend(imv, &imv_backend_libjxl);
+#endif
+
if (!imv_load_config(imv)) {
imv_free(imv);
return 1;
--
2.42.1