Fabio Henrique: 1 add jpegxl support 11 files changed, 303 insertions(+), 0 deletions(-)
Build for Ubuntu fails because libjxl is not available in LTS https://repology.org/project/libjxl/versions, it's only available in ubuntu:rolling
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~exec64/imv-devel/patches/46807/mbox | git am -3Learn more about email & git
--- 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
builds.sr.ht <builds@sr.ht>imv/patches: FAILED in 3m44s [add jpegxl support][0] v2 from [Fabio Henrique][1] [0]: https://lists.sr.ht/~exec64/imv-devel/patches/46807 [1]: mailto:dev@kz6wk9.com ✗ #1096353 FAILED imv/patches/archlinux.yml https://builds.sr.ht/~exec64/job/1096353 ✓ #1096351 SUCCESS imv/patches/debian.yml https://builds.sr.ht/~exec64/job/1096351 ✓ #1096350 SUCCESS imv/patches/freebsd.yml https://builds.sr.ht/~exec64/job/1096350 ✗ #1096352 FAILED imv/patches/ubuntu.yml https://builds.sr.ht/~exec64/job/1096352