[gimp] Add JPEG XL plug-in



commit 7ce0f2d60e6002f2eb2cfee56adbc0332ab2ff1f
Author: Daniel Novomesky <dnovomesky gmail com>
Date:   Thu Aug 26 14:24:21 2021 +0200

    Add JPEG XL plug-in

 configure.ac                   |   28 +
 meson.build                    |   13 +
 meson_options.txt              |    1 +
 plug-ins/common/Makefile.am    |   25 +
 plug-ins/common/file-jpegxl.c  | 1132 ++++++++++++++++++++++++++++++++++++++++
 plug-ins/common/meson.build    |    7 +
 plug-ins/common/plugin-defs.pl |    1 +
 7 files changed, 1207 insertions(+)
---
diff --git a/configure.ac b/configure.ac
index 7e32eeecd4..3bc7cd02da 100644
--- a/configure.ac
+++ b/configure.ac
@@ -75,6 +75,7 @@ m4_define([json_glib_required_version], [1.2.6])
 m4_define([lcms_required_version], [2.8])
 m4_define([libgudev_required_version], [167])
 m4_define([libheif_required_version], [1.3.2])
+m4_define([libjxl_required_version], [0.5.0])
 m4_define([liblzma_required_version], [5.0.0])
 m4_define([libmypaint_required_version], [1.3.0])
 m4_define([libpng_required_version], [1.6.25])
@@ -176,6 +177,7 @@ HARFBUZZ_REQUIRED_VERSION=harfbuzz_required_version
 INTLTOOL_REQUIRED_VERSION=intltool_required_version
 LCMS_REQUIRED_VERSION=lcms_required_version
 LIBHEIF_REQUIRED_VERSION=libheif_required_version
+LIBJXL_REQUIRED_VERSION=libjxl_required_version
 LIBLZMA_REQUIRED_VERSION=liblzma_required_version
 LIBMYPAINT_REQUIRED_VERSION=libmypaint_required_version
 LIBPNG_REQUIRED_VERSION=libpng_required_version
@@ -211,6 +213,7 @@ AC_SUBST(HARFBUZZ_REQUIRED_VERSION)
 AC_SUBST(INTLTOOL_REQUIRED_VERSION)
 AC_SUBST(LCMS_REQUIRED_VERSION)
 AC_SUBST(LIBHEIF_REQUIRED_VERSION)
+AC_SUBST(LIBJXL_REQUIRED_VERSION)
 AC_SUBST(LIBLZMA_REQUIRED_VERSION)
 AC_SUBST(LIBMYPAINT_REQUIRED_VERSION)
 AC_SUBST(LIBPNG_REQUIRED_VERSION)
@@ -1846,6 +1849,30 @@ if test "x$have_libheif_1_4_0" = xyes; then
   fi
 fi
 
+###################
+# Check for JPEG XL
+###################
+
+AC_ARG_WITH(jpegxl, [  --without-jpegxl        build without JPEG XL support])
+
+have_jpegxl=no
+if test "x$with_jpegxl" != xno; then
+  PKG_CHECK_MODULES(JXL, libjxl >= libjxl_required_version,
+    [
+      PKG_CHECK_MODULES(JXL_THREADS, libjxl_threads >= libjxl_required_version,
+        [have_jpegxl=yes],
+        [have_jpegxl="no (libjxl_threads not found)"])
+    ],
+    [have_jpegxl="no (libjxl not found)"])
+fi
+
+if test "x$have_jpegxl" = xyes; then
+  MIME_TYPES="$MIME_TYPES;image/jxl"
+  FILE_JPEGXL='file-jpegxl$(EXEEXT)'
+fi
+
+AM_CONDITIONAL(HAVE_JPEGXL, test "x$have_jpegxl" = xyes)
+AC_SUBST(FILE_JPEGXL)
 
 ########################
 # Check for libbacktrace
@@ -3213,6 +3240,7 @@ Optional Plug-Ins:
   Ghostscript:               $have_gs
   Help Browser:              $have_webkit
   JPEG 2000:                 $have_openjpeg
+  JPEG XL:                   $have_jpegxl
   MNG:                       $have_libmng
   OpenEXR:                   $have_openexr
   WebP:                      $have_webp
diff --git a/meson.build b/meson.build
index 863ad521f3..626e67b46c 100644
--- a/meson.build
+++ b/meson.build
@@ -802,6 +802,18 @@ if openjpeg.found()
   MIMEtypes += [ 'image/jp2', 'image/jpeg2000', 'image/jpx', ]
 endif
 
+jpegxl_minver  = '0.5.0'
+libjxl         = dependency('libjxl',
+                            version: '>='+jpegxl_minver,
+                            required: get_option('jpeg-xl')
+)
+libjxl_threads = dependency('libjxl_threads',
+                            version: '>='+jpegxl_minver,
+                            required: get_option('jpeg-xl')
+)
+if libjxl.found() and libjxl_threads.found()
+  MIMEtypes += 'image/jxl'
+endif
 
 xmc = dependency('xcursor', required: get_option('xcursor'))
 if xmc.found()
@@ -1774,6 +1786,7 @@ final_message = [
 '''  Ghostscript:         @0@'''.format(ghostscript.found()),
 '''  Help Browser:        @0@'''.format(webkit.found()),
 '''  JPEG 2000:           @0@'''.format(openjpeg.found()),
+'''  JPEG XL:             @0@'''.format(libjxl.found()),
 '''  MNG:                 @0@'''.format(libmng.found()),
 '''  OpenEXR:             @0@'''.format(openexr.found()),
 '''  WebP:                @0@'''.format(webp_found),
diff --git a/meson_options.txt b/meson_options.txt
index 6e7ae86228..6bb1c69afc 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -40,6 +40,7 @@ option('ghostscript',       type: 'feature', value: 'auto', description: 'Ghosts
 option('gudev',             type: 'feature', value: 'auto', description: 'Gudev support')
 option('heif',              type: 'feature', value: 'auto', description: 'HEIF support')
 option('jpeg2000',          type: 'feature', value: 'auto', description: 'Jpeg-2000 support')
+option('jpeg-xl',           type: 'feature', value: 'auto', description: 'JPEG XL support')
 option('mng',               type: 'feature', value: 'auto', description: 'Mng support')
 option('openexr',           type: 'feature', value: 'auto', description: 'Openexr support')
 option('png',               type: 'feature', value: 'auto', description: 'PNG support')
diff --git a/plug-ins/common/Makefile.am b/plug-ins/common/Makefile.am
index 05964bae82..30dc180126 100644
--- a/plug-ins/common/Makefile.am
+++ b/plug-ins/common/Makefile.am
@@ -79,6 +79,7 @@ file_header_libexecdir = $(gimpplugindir)/plug-ins/file-header
 file_heif_libexecdir = $(gimpplugindir)/plug-ins/file-heif
 file_html_table_libexecdir = $(gimpplugindir)/plug-ins/file-html-table
 file_jp2_load_libexecdir = $(gimpplugindir)/plug-ins/file-jp2-load
+file_jpegxl_libexecdir = $(gimpplugindir)/plug-ins/file-jpegxl
 file_mng_libexecdir = $(gimpplugindir)/plug-ins/file-mng
 file_pat_libexecdir = $(gimpplugindir)/plug-ins/file-pat
 file_pcx_libexecdir = $(gimpplugindir)/plug-ins/file-pcx
@@ -156,6 +157,7 @@ file_header_libexec_PROGRAMS = file-header
 file_heif_libexec_PROGRAMS = $(FILE_HEIF)
 file_html_table_libexec_PROGRAMS = file-html-table
 file_jp2_load_libexec_PROGRAMS = $(FILE_JP2_LOAD)
+file_jpegxl_libexec_PROGRAMS = $(FILE_JPEGXL)
 file_mng_libexec_PROGRAMS = $(FILE_MNG)
 file_pat_libexec_PROGRAMS = file-pat
 file_pcx_libexec_PROGRAMS = file-pcx
@@ -204,6 +206,7 @@ EXTRA_PROGRAMS = \
        file-aa \
        file-heif \
        file-jp2-load \
+       file-jpegxl \
        file-mng \
        file-pdf-save \
        file-ps \
@@ -821,6 +824,28 @@ file_jp2_load_LDADD = \
        $(INTLLIBS)             \
        $(file_jp2_load_RC)
 
+file_jpegxl_CFLAGS = $(JXL_CFLAGS)
+
+file_jpegxl_SOURCES = \
+       file-jpegxl.c
+
+file_jpegxl_LDADD = \
+       $(libgimpui)            \
+       $(libgimpwidgets)       \
+       $(libgimpmodule)        \
+       $(libgimp)              \
+       $(libgimpmath)          \
+       $(libgimpconfig)        \
+       $(libgimpcolor)         \
+       $(libgimpbase)          \
+       $(GTK_LIBS)             \
+       $(GEGL_LIBS)            \
+       $(JXL_THREADS_LIBS)             \
+       $(JXL_LIBS)             \
+       $(RT_LIBS)              \
+       $(INTLLIBS)             \
+       $(file_jpegxl_RC)
+
 file_mng_CFLAGS = $(MNG_CFLAGS)
 
 file_mng_SOURCES = \
diff --git a/plug-ins/common/file-jpegxl.c b/plug-ins/common/file-jpegxl.c
new file mode 100644
index 0000000000..d87f9ce422
--- /dev/null
+++ b/plug-ins/common/file-jpegxl.c
@@ -0,0 +1,1132 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * file-jpegxl - JPEG XL file format plug-in for the GIMP
+ * Copyright (C) 2021  Daniel Novomesky
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib/gstdio.h>
+
+#include <jxl/decode.h>
+#include <jxl/encode.h>
+#include <jxl/thread_parallel_runner.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+#define LOAD_PROC       "file-jpegxl-load"
+#define SAVE_PROC       "file-jpegxl-save"
+#define PLUG_IN_BINARY  "file-jpegxl"
+
+typedef struct _JpegXL      JpegXL;
+typedef struct _JpegXLClass JpegXLClass;
+
+struct _JpegXL
+{
+  GimpPlugIn      parent_instance;
+};
+
+struct _JpegXLClass
+{
+  GimpPlugInClass parent_class;
+};
+
+
+#define JPEGXL_TYPE  (jpegxl_get_type ())
+#define JPEGXL (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), JPEGXL_TYPE, JpegXL))
+
+GType                   jpegxl_get_type (void) G_GNUC_CONST;
+
+static GList           *jpegxl_query_procedures (GimpPlugIn           *plug_in);
+static GimpProcedure   *jpegxl_create_procedure (GimpPlugIn           *plug_in,
+                                                 const gchar          *name);
+
+static GimpValueArray *jpegxl_load (GimpProcedure        *procedure,
+                                    GimpRunMode           run_mode,
+                                    GFile                *file,
+                                    const GimpValueArray *args,
+                                    gpointer              run_data);
+static GimpValueArray *jpegxl_save (GimpProcedure        *procedure,
+                                    GimpRunMode           run_mode,
+                                    GimpImage            *image,
+                                    gint                  n_drawables,
+                                    GimpDrawable        **drawables,
+                                    GFile                *file,
+                                    const GimpValueArray *args,
+                                    gpointer              run_data);
+
+
+G_DEFINE_TYPE (JpegXL, jpegxl, GIMP_TYPE_PLUG_IN)
+
+GIMP_MAIN (JPEGXL_TYPE)
+
+static void
+jpegxl_class_init (JpegXLClass *klass)
+{
+  GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
+
+  plug_in_class->query_procedures = jpegxl_query_procedures;
+  plug_in_class->create_procedure = jpegxl_create_procedure;
+}
+
+static void
+jpegxl_init (JpegXL *jpeg_xl)
+{
+}
+
+static GList *
+jpegxl_query_procedures (GimpPlugIn *plug_in)
+{
+  GList *list = NULL;
+
+  list = g_list_append (list, g_strdup (LOAD_PROC));
+  list = g_list_append (list, g_strdup (SAVE_PROC));
+
+  return list;
+}
+
+static GimpProcedure *
+jpegxl_create_procedure (GimpPlugIn  *plug_in,
+                         const gchar *name)
+{
+  GimpProcedure *procedure = NULL;
+
+  if (! strcmp (name, LOAD_PROC))
+    {
+      procedure = gimp_load_procedure_new (plug_in, name,
+                                           GIMP_PDB_PROC_TYPE_PLUGIN,
+                                           jpegxl_load, NULL, NULL);
+
+      gimp_procedure_set_menu_label (procedure, N_("JPEG-XL image"));
+
+      gimp_procedure_set_documentation (procedure,
+                                        _("Loads files in the JPEG-XL file format"),
+                                        _("Loads files in the JPEG-XL file format"),
+                                        name);
+      gimp_procedure_set_attribution (procedure,
+                                      "Daniel Novomesky",
+                                      "(C) 2021 Daniel Novomesky",
+                                      "2021");
+
+      gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
+                                          "image/jxl");
+      gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
+                                          "jxl");
+      gimp_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
+                                      "0,string,\xFF\x0A,3,string,\x0CJXL");
+
+    }
+  else if (! strcmp (name, SAVE_PROC))
+    {
+      procedure = gimp_save_procedure_new (plug_in, name,
+                                           GIMP_PDB_PROC_TYPE_PLUGIN,
+                                           jpegxl_save, NULL, NULL);
+
+      gimp_procedure_set_image_types (procedure, "RGB*, GRAY*");
+
+      gimp_procedure_set_menu_label (procedure, N_("JPEG-XL image"));
+
+      gimp_procedure_set_documentation (procedure,
+                                        _("Saves files in the JPEG-XL file format"),
+                                        _("Saves files in the JPEG-XL file format"),
+                                        name);
+      gimp_procedure_set_attribution (procedure,
+                                      "Daniel Novomesky",
+                                      "(C) 2021 Daniel Novomesky",
+                                      "2021");
+
+      gimp_file_procedure_set_format_name (GIMP_FILE_PROCEDURE (procedure),
+                                           "JPEG-XL");
+      gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
+                                          "image/jxl");
+      gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
+                                          "jxl");
+
+      GIMP_PROC_ARG_BOOLEAN (procedure, "lossless",
+                             _("L_ossless"),
+                             _("Use lossless compression"),
+                             FALSE,
+                             G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_DOUBLE (procedure, "compression",
+                            _("Co_mpression/maxError"),
+                            _("Max. butteraugli distance, lower = higher quality. Range: 0 .. 15. 1.0 = 
visually lossless."),
+                            0, 15, 1,
+                            G_PARAM_READWRITE);
+
+      GIMP_PROC_AUX_ARG_INT (procedure, "speed",
+                             _("Effort/S_peed"),
+                             _("Encoder effort setting"),
+                             3, 9,
+                             7,
+                             G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_BOOLEAN (procedure, "uses-original-profile",
+                             _("Save ori_ginal profile"),
+                             _("Store ICC profile to exported JXL file"),
+                             FALSE,
+                             G_PARAM_READWRITE);
+    }
+
+  return procedure;
+}
+
+static GimpImage *load_image (GFile        *file,
+                              GimpRunMode   runmode,
+                              GError      **error)
+{
+  gchar            *filename = g_file_get_path (file);
+
+  FILE             *inputFile = g_fopen (filename, "rb");
+
+  gsize             inputFileSize;
+  gpointer          memory;
+
+  JxlSignature      signature;
+  JxlDecoder       *decoder;
+  void             *runner;
+  JxlBasicInfo      basicinfo;
+  JxlDecoderStatus  status;
+  JxlPixelFormat    pixel_format;
+  JxlColorEncoding  color_encoding;
+  size_t            icc_size = 0;
+  GimpColorProfile *profile = NULL;
+  gboolean          loadlinear = FALSE;
+  size_t            result_size;
+  gpointer          picture_buffer;
+
+  GimpImage        *image;
+  GimpLayer        *layer;
+  GeglBuffer       *buffer;
+
+  if (!inputFile)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "Cannot open file for read: %s\n", filename);
+      g_free (filename);
+      return NULL;
+    }
+
+  fseek (inputFile, 0, SEEK_END);
+  inputFileSize = ftell (inputFile);
+  fseek (inputFile, 0, SEEK_SET);
+
+  if (inputFileSize < 1)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "File too small: %s\n", filename);
+      fclose (inputFile);
+      g_free (filename);
+      return NULL;
+    }
+
+  memory = g_malloc (inputFileSize);
+  if (fread (memory, 1, inputFileSize, inputFile) != inputFileSize)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "Failed to read %zu bytes: %s\n", inputFileSize, filename);
+      fclose (inputFile);
+      g_free (memory);
+
+      g_free (filename);
+      return NULL;
+    }
+
+  fclose (inputFile);
+
+  signature = JxlSignatureCheck (memory, inputFileSize);
+  if (signature != JXL_SIG_CODESTREAM && signature != JXL_SIG_CONTAINER)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "File %s is probably not in JXL format!\n", filename);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  decoder = JxlDecoderCreate (NULL);
+  if (!decoder)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "ERROR: JxlDecoderCreate failed");
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  runner = JxlThreadParallelRunnerCreate (NULL, gimp_get_num_processors ());
+  if (JxlDecoderSetParallelRunner (decoder, JxlThreadParallelRunner, runner) != JXL_DEC_SUCCESS)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "ERROR: JxlDecoderSetParallelRunner failed");
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlDecoderDestroy (decoder);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  if (JxlDecoderSetInput (decoder, memory, inputFileSize) != JXL_DEC_SUCCESS)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "ERROR: JxlDecoderSetInput failed");
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlDecoderDestroy (decoder);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  if (JxlDecoderSubscribeEvents (decoder, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING | JXL_DEC_FULL_IMAGE) 
!= JXL_DEC_SUCCESS)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "ERROR: JxlDecoderSubscribeEvents failed");
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlDecoderDestroy (decoder);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  status = JxlDecoderProcessInput (decoder);
+  if (status == JXL_DEC_ERROR)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "ERROR: JXL decoding failed");
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlDecoderDestroy (decoder);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  if (status == JXL_DEC_NEED_MORE_INPUT)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "ERROR: JXL data incomplete");
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlDecoderDestroy (decoder);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  status = JxlDecoderGetBasicInfo (decoder, &basicinfo);
+  if (status != JXL_DEC_SUCCESS)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "ERROR: JXL basic info not available");
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlDecoderDestroy (decoder);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  if (basicinfo.xsize == 0 || basicinfo.ysize == 0)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "ERROR: JXL image has zero dimensions");
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlDecoderDestroy (decoder);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  status = JxlDecoderProcessInput (decoder);
+  if (status != JXL_DEC_COLOR_ENCODING)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "Unexpected event %d instead of JXL_DEC_COLOR_ENCODING", status);
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlDecoderDestroy (decoder);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  if (basicinfo.uses_original_profile == JXL_FALSE)
+    {
+      if (basicinfo.num_color_channels == 3)
+        {
+          JxlColorEncodingSetToSRGB (&color_encoding, JXL_FALSE);
+          JxlDecoderSetPreferredColorProfile (decoder, &color_encoding);
+        }
+      else if (basicinfo.num_color_channels == 1)
+        {
+          JxlColorEncodingSetToSRGB (&color_encoding, JXL_TRUE);
+          JxlDecoderSetPreferredColorProfile (decoder, &color_encoding);
+        }
+    }
+
+  pixel_format.endianness = JXL_NATIVE_ENDIAN;
+  pixel_format.align = 0;
+  pixel_format.data_type = JXL_TYPE_FLOAT;
+
+  if (basicinfo.num_color_channels == 1) /* grayscale */
+    {
+      if (basicinfo.alpha_bits > 0)
+        {
+          pixel_format.num_channels = 2;
+        }
+      else
+        {
+          pixel_format.num_channels = 1;
+        }
+    }
+  else /* RGB */
+    {
+
+      if (basicinfo.alpha_bits > 0) /* RGB with alpha */
+        {
+          pixel_format.num_channels = 4;
+        }
+      else /* RGB no alpha */
+        {
+          pixel_format.num_channels = 3;
+        }
+    }
+
+  result_size = 4 * pixel_format.num_channels * (size_t) basicinfo.xsize * (size_t) basicinfo.ysize;
+
+  if (JxlDecoderGetColorAsEncodedProfile (decoder, &pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, 
&color_encoding) == JXL_DEC_SUCCESS)
+    {
+      if (color_encoding.white_point == JXL_WHITE_POINT_D65)
+        {
+          switch (color_encoding.transfer_function)
+            {
+            case JXL_TRANSFER_FUNCTION_LINEAR:
+              loadlinear = TRUE;
+
+              switch (color_encoding.color_space)
+                {
+                case JXL_COLOR_SPACE_RGB:
+                  profile = gimp_color_profile_new_rgb_srgb_linear ();
+                  break;
+                case JXL_COLOR_SPACE_GRAY:
+                  profile = gimp_color_profile_new_d65_gray_linear ();
+                  break;
+                default:
+                  break;
+                }
+              break;
+            case JXL_TRANSFER_FUNCTION_SRGB:
+              switch (color_encoding.color_space)
+                {
+                case JXL_COLOR_SPACE_RGB:
+                  profile = gimp_color_profile_new_rgb_srgb ();
+                  break;
+                case JXL_COLOR_SPACE_GRAY:
+                  profile = gimp_color_profile_new_d65_gray_srgb_trc ();
+                  break;
+                default:
+                  break;
+                }
+              break;
+            default:
+              break;
+            }
+        }
+    }
+
+  if (!profile)
+    {
+      if (JxlDecoderGetICCProfileSize (decoder, &pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size) == 
JXL_DEC_SUCCESS)
+        {
+          if (icc_size > 0)
+            {
+              gpointer raw_icc_profile = g_malloc (icc_size);
+
+              if (JxlDecoderGetColorAsICCProfile (decoder, &pixel_format, JXL_COLOR_PROFILE_TARGET_DATA, 
raw_icc_profile, icc_size)
+                  == JXL_DEC_SUCCESS)
+                {
+                  profile = gimp_color_profile_new_from_icc_profile (raw_icc_profile, icc_size, error);
+                  if (profile)
+                    {
+                      loadlinear = gimp_color_profile_is_linear (profile);
+                    }
+                  else
+                    {
+                      g_printerr ("%s: Failed to read ICC profile: %s\n", G_STRFUNC, (*error)->message);
+                      g_clear_error (error);
+                    }
+                }
+              else
+                {
+                  g_printerr ("Failed to obtain data from JPEG XL decoder");
+                }
+
+              g_free (raw_icc_profile);
+            }
+          else
+            {
+              g_printerr ("Empty ICC data");
+            }
+        }
+      else
+        {
+          g_message ("no ICC, other color profile");
+        }
+    }
+
+  status = JxlDecoderProcessInput (decoder);
+  if (status != JXL_DEC_NEED_IMAGE_OUT_BUFFER)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "Unexpected event %d instead of JXL_DEC_NEED_IMAGE_OUT_BUFFER", status);
+      if (profile)
+        {
+          g_object_unref (profile);
+        }
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlDecoderDestroy (decoder);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  picture_buffer = g_malloc (result_size);
+  if (JxlDecoderSetImageOutBuffer (decoder, &pixel_format, picture_buffer, result_size) != JXL_DEC_SUCCESS)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "ERROR: JxlDecoderSetImageOutBuffer failed");
+      if (profile)
+        {
+          g_object_unref (profile);
+        }
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlDecoderDestroy (decoder);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+  status = JxlDecoderProcessInput (decoder);
+  if (status != JXL_DEC_FULL_IMAGE)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "Unexpected event %d instead of JXL_DEC_FULL_IMAGE", status);
+      g_free (picture_buffer);
+      if (profile)
+        {
+          g_object_unref (profile);
+        }
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlDecoderDestroy (decoder);
+      g_free (memory);
+      g_free (filename);
+      return NULL;
+    }
+
+
+  if (basicinfo.num_color_channels == 1) /* grayscale */
+    {
+      image = gimp_image_new_with_precision (basicinfo.xsize, basicinfo.ysize, GIMP_GRAY, loadlinear ? 
GIMP_PRECISION_FLOAT_LINEAR : GIMP_PRECISION_FLOAT_NON_LINEAR);
+
+      if (profile)
+        {
+          if (gimp_color_profile_is_gray (profile))
+            {
+              gimp_image_set_color_profile (image, profile);
+            }
+        }
+
+      layer = gimp_layer_new (image, "Background",
+                              basicinfo.xsize, basicinfo.ysize,
+                              (basicinfo.alpha_bits > 0) ? GIMP_GRAYA_IMAGE : GIMP_GRAY_IMAGE, 100,
+                              gimp_image_get_default_new_layer_mode (image));
+    }
+  else /* RGB */
+    {
+      image = gimp_image_new_with_precision (basicinfo.xsize, basicinfo.ysize, GIMP_RGB, loadlinear ? 
GIMP_PRECISION_FLOAT_LINEAR : GIMP_PRECISION_FLOAT_NON_LINEAR);
+
+      if (profile)
+        {
+          if (gimp_color_profile_is_rgb (profile))
+            {
+              gimp_image_set_color_profile (image, profile);
+            }
+        }
+
+      layer = gimp_layer_new (image, "Background",
+                              basicinfo.xsize, basicinfo.ysize,
+                              (basicinfo.alpha_bits > 0) ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE, 100,
+                              gimp_image_get_default_new_layer_mode (image));
+
+    }
+
+  gimp_image_insert_layer (image, layer, NULL, 0);
+
+  buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
+
+  gegl_buffer_set (buffer, GEGL_RECTANGLE (0, 0, basicinfo.xsize, basicinfo.ysize), 0,
+                   NULL, picture_buffer, GEGL_AUTO_ROWSTRIDE);
+
+  g_object_unref (buffer);
+
+
+  g_free (picture_buffer);
+  if (profile)
+    {
+      g_object_unref (profile);
+    }
+  JxlThreadParallelRunnerDestroy (runner);
+  JxlDecoderDestroy (decoder);
+  g_free (memory);
+  g_free (filename);
+  return image;
+}
+
+static GimpValueArray *
+jpegxl_load (GimpProcedure        *procedure,
+             GimpRunMode           run_mode,
+             GFile                *file,
+             const GimpValueArray *args,
+             gpointer              run_data)
+{
+  GimpValueArray *return_vals;
+  GimpImage      *image;
+  GError         *error             = NULL;
+
+  INIT_I18N ();
+  gegl_init (NULL, NULL);
+
+  switch (run_mode)
+    {
+    case GIMP_RUN_INTERACTIVE:
+    case GIMP_RUN_WITH_LAST_VALS:
+      gimp_ui_init (PLUG_IN_BINARY);
+      break;
+
+    default:
+      break;
+    }
+
+  image = load_image (file, run_mode, &error);
+
+  if (! image)
+    {
+      return gimp_procedure_new_return_values (procedure,
+                                               GIMP_PDB_EXECUTION_ERROR,
+                                               error);
+    }
+  return_vals = gimp_procedure_new_return_values (procedure,
+                                                  GIMP_PDB_SUCCESS,
+                                                  NULL);
+
+  GIMP_VALUES_SET_IMAGE (return_vals, 1, image);
+
+  return return_vals;
+}
+
+
+static gboolean    save_image (GFile                *file,
+                               GimpProcedureConfig *config,
+                               GimpImage            *image,
+                               GimpDrawable         *drawable,
+                               GError              **error)
+{
+  JxlEncoder        *encoder;
+  void              *runner;
+  JxlEncoderOptions *encoder_options;
+  JxlPixelFormat     pixel_format;
+  JxlBasicInfo       output_info;
+  JxlColorEncoding   color_profile;
+  JxlEncoderStatus   status;
+  size_t             buffer_size;
+
+  GByteArray        *compressed;
+
+  gchar             *filename;
+  FILE              *outfile;
+  GeglBuffer        *buffer;
+  GimpImageType      drawable_type;
+
+  gint               drawable_width;
+  gint               drawable_height;
+  gpointer           picture_buffer;
+
+  GimpColorProfile  *profile = NULL;
+  const Babl        *file_format = NULL;
+  const Babl        *space = NULL;
+  gboolean           out_linear = FALSE;
+
+  size_t             offset = 0;
+  uint8_t           *next_out;
+  size_t             avail_out;
+
+  gdouble            compression = 1.0;
+  gboolean           lossless = FALSE;
+  gint               speed = 7;
+  gboolean           uses_original_profile = FALSE;
+
+  filename = g_file_get_path (file);
+  gimp_progress_init_printf ("Exporting '%s'.", filename);
+
+  g_object_get (config,
+                "lossless",           &lossless,
+                "compression",        &compression,
+                "speed",              &speed,
+                "uses-original-profile", &uses_original_profile,
+                NULL);
+
+  drawable_type   = gimp_drawable_type (drawable);
+  drawable_width  = gimp_drawable_get_width (drawable);
+  drawable_height = gimp_drawable_get_height (drawable);
+
+  memset (&output_info, 0, sizeof output_info);
+
+  if (uses_original_profile)
+    {
+      output_info.uses_original_profile = JXL_TRUE;
+
+      profile = gimp_image_get_effective_color_profile (image);
+      out_linear = gimp_color_profile_is_linear (profile);
+
+      space = gimp_color_profile_get_space (profile,
+                                            GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
+                                            error);
+
+      if (error && *error)
+        {
+          g_printerr ("%s: error getting the profile space: %s\n", G_STRFUNC, (*error)->message);
+          g_free (filename);
+          return FALSE;
+        }
+    }
+  else
+    {
+      space = babl_space ("sRGB");
+      out_linear = FALSE;
+    }
+
+  pixel_format.data_type = JXL_TYPE_UINT16;
+  pixel_format.endianness = JXL_NATIVE_ENDIAN;
+  pixel_format.align = 0;
+
+  output_info.xsize = drawable_width;
+  output_info.ysize = drawable_height;
+  output_info.bits_per_sample = 16;
+  output_info.exponent_bits_per_sample = 0;
+  output_info.intensity_target = 255.0f;
+  output_info.orientation = JXL_ORIENT_IDENTITY;
+  output_info.animation.tps_numerator = 10;
+  output_info.animation.tps_denominator = 1;
+
+  switch (drawable_type)
+    {
+    case GIMP_GRAYA_IMAGE:
+      if (uses_original_profile && out_linear)
+        {
+          file_format = babl_format ("YA u16");
+          JxlColorEncodingSetToLinearSRGB (&color_profile, JXL_TRUE);
+        }
+      else
+        {
+          file_format = babl_format ("Y'A u16");
+          JxlColorEncodingSetToSRGB (&color_profile, JXL_TRUE);
+        }
+      pixel_format.num_channels = 2;
+      output_info.num_color_channels = 1;
+      output_info.alpha_bits = 16;
+      output_info.alpha_exponent_bits = 0;
+      output_info.num_extra_channels = 1;
+
+      uses_original_profile = FALSE;
+      break;
+    case GIMP_GRAY_IMAGE:
+      if (uses_original_profile && out_linear)
+        {
+          file_format = babl_format ("Y u16");
+          JxlColorEncodingSetToLinearSRGB (&color_profile, JXL_TRUE);
+        }
+      else
+        {
+          file_format = babl_format ("Y' u16");
+          JxlColorEncodingSetToSRGB (&color_profile, JXL_TRUE);
+        }
+      pixel_format.num_channels = 1;
+      output_info.num_color_channels = 1;
+      output_info.alpha_bits = 0;
+
+      uses_original_profile = FALSE;
+      break;
+    case GIMP_RGBA_IMAGE:
+      file_format = babl_format_with_space (out_linear ? "RGBA u16" : "R'G'B'A u16", space);
+      pixel_format.num_channels = 4;
+      JxlColorEncodingSetToSRGB (&color_profile, JXL_FALSE);
+      output_info.num_color_channels = 3;
+      output_info.alpha_bits = 16;
+      output_info.alpha_exponent_bits = 0;
+      output_info.num_extra_channels = 1;
+      break;
+    case GIMP_RGB_IMAGE:
+      file_format = babl_format_with_space (out_linear ? "RGB u16" : "R'G'B' u16", space);
+      pixel_format.num_channels = 3;
+      JxlColorEncodingSetToSRGB (&color_profile, JXL_FALSE);
+      output_info.num_color_channels = 3;
+      output_info.alpha_bits = 0;
+      break;
+    default:
+      if (profile)
+        {
+          g_object_unref (profile);
+        }
+      g_free (filename);
+      return FALSE;
+      break;
+    }
+
+
+  buffer_size = 2 * pixel_format.num_channels * (size_t) output_info.xsize * (size_t) output_info.ysize;
+  picture_buffer = g_malloc (buffer_size);
+
+  gimp_progress_update (0.3);
+
+  buffer = gimp_drawable_get_buffer (drawable);
+  gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0,
+                   drawable_width, drawable_height), 1.0,
+                   file_format, picture_buffer,
+                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+  g_object_unref (buffer);
+
+  gimp_progress_update (0.4);
+
+  encoder = JxlEncoderCreate (NULL);
+  if (!encoder)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "Failed to create Jxl encoder");
+      g_free (picture_buffer);
+      if (profile)
+        {
+          g_object_unref (profile);
+        }
+      g_free (filename);
+      return FALSE;
+    }
+
+  runner = JxlThreadParallelRunnerCreate (NULL, gimp_get_num_processors ());
+  if (JxlEncoderSetParallelRunner (encoder, JxlThreadParallelRunner, runner) != JXL_ENC_SUCCESS)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "JxlEncoderSetParallelRunner failed");
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlEncoderDestroy (encoder);
+      g_free (picture_buffer);
+      if (profile)
+        {
+          g_object_unref (profile);
+        }
+      g_free (filename);
+      return FALSE;
+    }
+
+  status = JxlEncoderSetBasicInfo (encoder, &output_info);
+  if (status != JXL_ENC_SUCCESS)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "JxlEncoderSetBasicInfo failed!");
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlEncoderDestroy (encoder);
+      g_free (picture_buffer);
+      if (profile)
+        {
+          g_object_unref (profile);
+        }
+      g_free (filename);
+      return FALSE;
+    }
+
+  if (uses_original_profile)
+    {
+      const uint8_t *icc_data = NULL;
+      size_t         icc_length = 0;
+
+      icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length);
+      status = JxlEncoderSetICCProfile (encoder, icc_data, icc_length);
+      g_object_unref (profile);
+      profile = NULL;
+
+      if (status != JXL_ENC_SUCCESS)
+        {
+          g_set_error (error, G_FILE_ERROR, 0,
+                       "JxlEncoderSetICCProfile failed!");
+          JxlThreadParallelRunnerDestroy (runner);
+          JxlEncoderDestroy (encoder);
+          g_free (picture_buffer);
+          g_free (filename);
+        }
+    }
+  else
+    {
+      if (profile)
+        {
+          g_object_unref (profile);
+          profile = NULL;
+        }
+
+      status = JxlEncoderSetColorEncoding (encoder, &color_profile);
+      if (status != JXL_ENC_SUCCESS)
+        {
+          g_set_error (error, G_FILE_ERROR, 0,
+                       "JxlEncoderSetColorEncoding failed!");
+          JxlThreadParallelRunnerDestroy (runner);
+          JxlEncoderDestroy (encoder);
+          g_free (picture_buffer);
+          g_free (filename);
+          return FALSE;
+        }
+    }
+
+  encoder_options = JxlEncoderOptionsCreate (encoder, NULL);
+
+  if (lossless)
+    {
+      JxlEncoderOptionsSetDistance (encoder_options, 0);
+      JxlEncoderOptionsSetLossless (encoder_options, JXL_TRUE);
+    }
+  else
+    {
+      JxlEncoderOptionsSetDistance (encoder_options, compression);
+      JxlEncoderOptionsSetLossless (encoder_options, JXL_FALSE);
+    }
+
+  status = JxlEncoderOptionsSetEffort (encoder_options, speed);
+  if (status != JXL_ENC_SUCCESS)
+    {
+      g_printerr ("JxlEncoderOptionsSetEffort failed to set effort %d", speed);
+    }
+
+  gimp_progress_update (0.5);
+
+  status = JxlEncoderAddImageFrame (encoder_options, &pixel_format, picture_buffer, buffer_size);
+  if (status != JXL_ENC_SUCCESS)
+    {
+      g_set_error (error, G_FILE_ERROR, 0,
+                   "JxlEncoderAddImageFrame failed!");
+      JxlThreadParallelRunnerDestroy (runner);
+      JxlEncoderDestroy (encoder);
+      g_free (picture_buffer);
+      g_free (filename);
+      return FALSE;
+    }
+
+  JxlEncoderCloseInput (encoder);
+
+  gimp_progress_update (0.7);
+
+  compressed = g_byte_array_sized_new (4096);
+  g_byte_array_set_size (compressed, 4096);
+  do
+    {
+      next_out = compressed->data + offset;
+      avail_out = compressed->len - offset;
+      status = JxlEncoderProcessOutput (encoder, &next_out, &avail_out);
+
+      if (status == JXL_ENC_NEED_MORE_OUTPUT)
+        {
+          offset = next_out - compressed->data;
+          g_byte_array_set_size (compressed, compressed->len * 2);
+        }
+      else if (status == JXL_ENC_ERROR)
+        {
+          g_set_error (error, G_FILE_ERROR, 0,
+                       "JxlEncoderProcessOutput failed!");
+          JxlThreadParallelRunnerDestroy (runner);
+          JxlEncoderDestroy (encoder);
+          g_free (picture_buffer);
+          g_free (filename);
+          return FALSE;
+        }
+    }
+  while (status != JXL_ENC_SUCCESS);
+
+  JxlThreadParallelRunnerDestroy (runner);
+  JxlEncoderDestroy (encoder);
+
+  g_free (picture_buffer);
+
+  g_byte_array_set_size (compressed, next_out - compressed->data);
+
+  gimp_progress_update (0.8);
+
+  if (compressed->len > 0)
+    {
+      outfile = g_fopen (filename, "wb");
+      if (!outfile)
+        {
+          g_set_error (error, G_FILE_ERROR, 0,
+                       "Could not open '%s' for writing!\n", filename);
+          g_free (filename);
+          g_byte_array_free (compressed, TRUE);
+          return FALSE;
+        }
+
+      g_free (filename);
+      fwrite (compressed->data, 1, compressed->len, outfile);
+      fclose (outfile);
+
+      gimp_progress_update (1.0);
+
+      g_byte_array_free (compressed, TRUE);
+      return TRUE;
+    }
+
+  g_set_error (error, G_FILE_ERROR, 0,
+               "No data to write");
+  g_byte_array_free (compressed, TRUE);
+  g_free (filename);
+  return FALSE;
+}
+
+static gboolean
+save_dialog (GimpImage     *image,
+             GimpProcedure *procedure,
+             GObject       *config)
+{
+  GtkWidget    *dialog;
+  GtkListStore *store;
+  gboolean      run;
+
+  dialog = gimp_save_procedure_dialog_new (GIMP_SAVE_PROCEDURE (procedure),
+                                           GIMP_PROCEDURE_CONFIG (config),
+                                           image);
+
+  gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
+                                    "lossless", GTK_TYPE_CHECK_BUTTON);
+
+  gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
+                                    "compression", GIMP_TYPE_SCALE_ENTRY);
+
+  store = gimp_int_store_new (_("falcon (faster)"),   3,
+                              _("cheetah"),           4,
+                              _("hare"),              5,
+                              _("wombat"),            6,
+                              _("squirrel"),          7,
+                              _("kitten"),            8,
+                              _("tortoise (slower)"), 9,
+                              NULL);
+
+  gimp_procedure_dialog_get_int_combo (GIMP_PROCEDURE_DIALOG (dialog),
+                                       "speed", GIMP_INT_STORE (store));
+  g_object_unref (store);
+
+  gimp_procedure_dialog_get_widget (GIMP_PROCEDURE_DIALOG (dialog),
+                                    "uses-original-profile", GTK_TYPE_CHECK_BUTTON);
+
+  gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
+                              "lossless", "compression",
+                              "speed", "uses-original-profile",
+                              NULL);
+
+  run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog));
+
+  gtk_widget_destroy (dialog);
+
+  return run;
+}
+
+
+static GimpValueArray *
+jpegxl_save (GimpProcedure        *procedure,
+             GimpRunMode           run_mode,
+             GimpImage            *image,
+             gint                  n_drawables,
+             GimpDrawable        **drawables,
+             GFile                *file,
+             const GimpValueArray *args,
+             gpointer              run_data)
+{
+  GimpPDBStatusType      status = GIMP_PDB_SUCCESS;
+  GimpProcedureConfig   *config;
+  GimpExportReturn       export = GIMP_EXPORT_CANCEL;
+  GError                *error  = NULL;
+
+  INIT_I18N ();
+  gegl_init (NULL, NULL);
+
+  config = gimp_procedure_create_config (procedure);
+  gimp_procedure_config_begin_run (config, image, run_mode, args);
+
+  switch (run_mode)
+    {
+    case GIMP_RUN_INTERACTIVE:
+    case GIMP_RUN_WITH_LAST_VALS:
+      gimp_ui_init (PLUG_IN_BINARY);
+
+      export = gimp_export_image (&image, &n_drawables, &drawables, "JPEG XL",
+                                  GIMP_EXPORT_CAN_HANDLE_RGB |
+                                  GIMP_EXPORT_CAN_HANDLE_GRAY |
+                                  GIMP_EXPORT_CAN_HANDLE_ALPHA);
+
+      if (export == GIMP_EXPORT_CANCEL)
+        {
+          return gimp_procedure_new_return_values (procedure,
+                 GIMP_PDB_CANCEL,
+                 NULL);
+        }
+      break;
+
+    default:
+      break;
+    }
+
+  if (n_drawables < 1)
+    {
+      g_set_error (&error, G_FILE_ERROR, 0,
+                   "No drawables to export");
+
+      return gimp_procedure_new_return_values (procedure,
+             GIMP_PDB_CALLING_ERROR,
+             error);
+    }
+
+  if (run_mode == GIMP_RUN_INTERACTIVE)
+    {
+      if (! save_dialog (image, procedure, G_OBJECT (config)))
+        {
+          status = GIMP_PDB_CANCEL;
+        }
+    }
+
+  if (status == GIMP_PDB_SUCCESS)
+    {
+      if (! save_image (file, config,
+                        image, drawables[0], &error))
+        {
+          status = GIMP_PDB_EXECUTION_ERROR;
+        }
+    }
+
+
+  gimp_procedure_config_end_run (config, status);
+  g_object_unref (config);
+
+  if (export == GIMP_EXPORT_EXPORT)
+    {
+      g_free (drawables);
+      gimp_image_delete (image);
+    }
+
+  return gimp_procedure_new_return_values (procedure, status, error);
+}
diff --git a/plug-ins/common/meson.build b/plug-ins/common/meson.build
index 74693a589c..c4b3b5e209 100644
--- a/plug-ins/common/meson.build
+++ b/plug-ins/common/meson.build
@@ -103,6 +103,13 @@ if openjpeg.found()
   }
 endif
 
+if libjxl.found() and libjxl_threads.found()
+  common_plugins_list += {
+    'name': 'file-jpegxl',
+    'deps': [ gtk3, gegl, libjxl, libjxl_threads, ],
+  }
+endif
+
 if libmng.found()
   common_plugins_list += { 'name': 'file-mng',
     'deps': [ gtk3, gegl, libmng, libpng, ],
diff --git a/plug-ins/common/plugin-defs.pl b/plug-ins/common/plugin-defs.pl
index f1999a32df..dd39401b71 100644
--- a/plug-ins/common/plugin-defs.pl
+++ b/plug-ins/common/plugin-defs.pl
@@ -32,6 +32,7 @@
     'file-heif' => { ui => 1, optional => 1, gegl => 1, libdep => 'GEXIV2:LCMS', libs => 'LIBHEIF_LIBS', 
cflags => 'LIBHEIF_CFLAGS' },
     'file-html-table' => { ui => 1, gegl => 1 },
     'file-jp2-load' => { ui => 1, optional => 1, gegl => 1, libs => 'OPENJPEG_LIBS', cflags => 
'OPENJPEG_CFLAGS' },
+    'file-jpegxl' => { ui => 1, optional => 1, gegl => 1, libdep => 'JXL:JXL_THREADS', cflags => 
'JXL_CFLAGS' },
     'file-mng' => { ui => 1, gegl => 1, optional => 1, libs => 'MNG_LIBS', cflags => 'MNG_CFLAGS' },
     'file-pat' => { ui => 1, gegl => 1 },
     'file-pcx' => { ui => 1, gegl => 1 },


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]