~exec64/imv-devel

imv: add jpegxl support v2 PROPOSED

Fabio Henrique: 1
 add jpegxl support

 11 files changed, 303 insertions(+), 0 deletions(-)
#1096350 freebsd.yml success
#1096351 debian.yml success
#1096352 ubuntu.yml failed
#1096353 archlinux.yml failed
Build for Ubuntu fails because libjxl is not available in LTS
https://repology.org/project/libjxl/versions, it's only available in
ubuntu:rolling
Export patchset (mbox)
How do I use this?

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 -3
Learn more about email & git

[PATCH imv v2] add jpegxl support Export this patch

---
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
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