From a55f36133d575a46a19d45718b81378078085037 Mon Sep 17 00:00:00 2001 From: Kim Woelders Date: Wed, 26 Apr 2023 07:18:48 +0200 Subject: [PATCH] Add JXL saver https://git.enlightenment.org/old/legacy-imlib2/issues/9 --- src/lib/Imlib2_Loader.h | 12 +++ src/modules/loaders/loader_jxl.c | 180 ++++++++++++++++++++++++++++++- test/test_save.cpp | 2 +- 3 files changed, 192 insertions(+), 2 deletions(-) diff --git a/src/lib/Imlib2_Loader.h b/src/lib/Imlib2_Loader.h index 8279e2b..910b3c6 100644 --- a/src/lib/Imlib2_Loader.h +++ b/src/lib/Imlib2_Loader.h @@ -65,6 +65,18 @@ typedef struct _ImlibImage ImlibImage; #define PIXEL_G(argb) (((argb) >> 8) & 0xff) #define PIXEL_B(argb) (((argb) ) & 0xff) +#ifndef WORDS_BIGENDIAN +#define A_VAL(p) ((uint8_t *)(p))[3] +#define R_VAL(p) ((uint8_t *)(p))[2] +#define G_VAL(p) ((uint8_t *)(p))[1] +#define B_VAL(p) ((uint8_t *)(p))[0] +#else +#define A_VAL(p) ((uint8_t *)(p))[0] +#define R_VAL(p) ((uint8_t *)(p))[1] +#define G_VAL(p) ((uint8_t *)(p))[2] +#define B_VAL(p) ((uint8_t *)(p))[3] +#endif + /* debug.h */ #if IMLIB2_DEBUG diff --git a/src/modules/loaders/loader_jxl.c b/src/modules/loaders/loader_jxl.c index 96ad698..c9037c1 100644 --- a/src/modules/loaders/loader_jxl.c +++ b/src/modules/loaders/loader_jxl.c @@ -4,6 +4,7 @@ #define MAX_RUNNERS 4 /* Maybe set to Ncpu/2? */ #include +#include #if MAX_RUNNERS > 0 #include #endif @@ -215,4 +216,181 @@ _load(ImlibImage * im, int load_data) return rc; } -IMLIB_LOADER(_formats, _load, NULL); +static int +_save(ImlibImage * im) +{ + int rc; + JxlEncoderStatus jst; + JxlEncoder *enc; + JxlBasicInfo info; + JxlEncoderFrameSettings *opts; + + JxlPixelFormat pbuf_fmt = { + .data_type = JXL_TYPE_UINT8, + .endianness = JXL_NATIVE_ENDIAN, + }; + ImlibImageTag *tag; + const uint32_t *ptr; + uint8_t *buffer = NULL, *buf_ptr; + size_t buf_len, i, npix; + +#if MAX_RUNNERS > 0 + size_t n_runners; + JxlParallelRunner *runner = NULL; +#endif + + rc = LOAD_FAIL; + + enc = JxlEncoderCreate(NULL); + if (!enc) + goto quit; + +#if MAX_RUNNERS > 0 + n_runners = JxlThreadParallelRunnerDefaultNumWorkerThreads(); + if (n_runners > MAX_RUNNERS) + n_runners = MAX_RUNNERS; + D("n_runners = %ld\n", n_runners); + runner = JxlThreadParallelRunnerCreate(NULL, n_runners); + if (!runner) + goto quit; + + jst = JxlEncoderSetParallelRunner(enc, JxlThreadParallelRunner, runner); + if (jst != JXL_ENC_SUCCESS) + goto quit; +#endif /* MAX_RUNNERS */ + + JxlEncoderInitBasicInfo(&info); + info.xsize = im->w; + info.ysize = im->h; + if (im->has_alpha) + { + info.alpha_bits = 8; + info.num_extra_channels = 1; + } + + jst = JxlEncoderSetBasicInfo(enc, &info); + if (jst != JXL_ENC_SUCCESS) + goto quit; + + opts = JxlEncoderFrameSettingsCreate(enc, NULL); + if (!opts) + goto quit; + + tag = __imlib_GetTag(im, "quality"); + if (tag) + { + int quality; + float distance; + + quality = (tag->val) >= 0 ? tag->val : 0; + if (quality >= 100) + { + D("Quality=%d: Lossless\n", quality); + JxlEncoderSetFrameDistance(opts, 0.f); + JxlEncoderSetFrameLossless(opts, JXL_TRUE); + } + else + { + distance = 15.f * (1.f - .01 * quality); // 0 - 100 -> 15 - 0 + D("Quality=%d: Distance=%.1f\n", quality, distance); + JxlEncoderSetFrameLossless(opts, JXL_FALSE); + JxlEncoderSetFrameDistance(opts, distance); + } + } + + tag = __imlib_GetTag(im, "compression"); + if (tag) + { + int compression; + + compression = (tag->val < 1) ? 1 : (tag->val > 9) ? 9 : tag->val; + D("Compression=%d\n", compression); + JxlEncoderFrameSettingsSetOption(opts, JXL_ENC_FRAME_SETTING_EFFORT, + compression); + } + + // Create buffer for format conversion and output + pbuf_fmt.num_channels = (im->has_alpha) ? 4 : 3; + npix = im->w * im->h; + buf_len = pbuf_fmt.num_channels * npix; + if (buf_len < 4096) + buf_len = 4096; // Not too small for output + + buffer = malloc(buf_len); + if (!buffer) + QUIT_WITH_RC(LOAD_OOM); + + // Convert format for libjxl + ptr = im->data; + buf_ptr = buffer; + if (pbuf_fmt.num_channels == 3) + { + for (i = 0; i < npix; i++, ptr++, buf_ptr += 3) + { + buf_ptr[0] = R_VAL(ptr); + buf_ptr[1] = G_VAL(ptr); + buf_ptr[2] = B_VAL(ptr); + } + } + else + { + for (i = 0; i < npix; i++, ptr++, buf_ptr += 4) + { + buf_ptr[0] = R_VAL(ptr); + buf_ptr[1] = G_VAL(ptr); + buf_ptr[2] = B_VAL(ptr); + buf_ptr[3] = A_VAL(ptr); + } + } + + jst = JxlEncoderAddImageFrame(opts, &pbuf_fmt, buffer, buf_len); + if (jst != JXL_ENC_SUCCESS) + goto quit; + + JxlEncoderCloseInput(enc); + + for (;;) + { + uint8_t *next_out; + size_t avail_out; + + next_out = buffer; + avail_out = buf_len; + jst = JxlEncoderProcessOutput(enc, &next_out, &avail_out); + switch (jst) + { + default: + goto quit; + case JXL_ENC_SUCCESS: + case JXL_ENC_NEED_MORE_OUTPUT: + D("Write: jst=%d %d\n", jst, (int)(buf_len - avail_out)); + if (next_out == buffer) + goto quit; + + if (fwrite(buffer, 1, buf_len - avail_out, im->fi->fp) != + buf_len - avail_out) + goto quit; + + if (jst == JXL_ENC_SUCCESS) + goto done; + + break; + } + } + done: + + rc = LOAD_SUCCESS; + + quit: + free(buffer); +#if MAX_RUNNERS > 0 + if (runner) + JxlThreadParallelRunnerDestroy(runner); +#endif + if (enc) + JxlEncoderDestroy(enc); + + return rc; +} + +IMLIB_LOADER(_formats, _load, _save); diff --git a/test/test_save.cpp b/test/test_save.cpp index fdb4b96..ab4375e 100644 --- a/test/test_save.cpp +++ b/test/test_save.cpp @@ -27,7 +27,7 @@ static const test_rec_t exts[] = { // { "id3", { 0, 0 } }, // { "j2k", { 0, 0 } }, { "jpeg", { 2458451111, 3483232328 } }, -// { "jxl", { 0, 0 } }, + { "jxl", { 2681286418, 774897965 } }, // { "lbm", { 0, 0 } }, // { "lzma", { 0, 0 } }, { "png", { 1153555547, 2937827957 } },