[gthumb: 1/5] Add JPEG XL (*.jxl) read support




commit a1500ec17c21bfb360c34a9099968dc24333dc32
Author: Ian Tester <imroykun gmail com>
Date:   Tue Feb 23 19:56:28 2021 +1100

    Add JPEG XL (*.jxl) read support

 README.md                                     |   1 +
 data/org.gnome.gThumb.desktop.in.in           |   2 +-
 extensions/cairo_io/cairo-image-surface-jxl.c | 293 ++++++++++++++++++++++++++
 extensions/cairo_io/cairo-image-surface-jxl.h |  42 ++++
 extensions/cairo_io/main.c                    |   8 +
 extensions/cairo_io/meson.build               |   7 +-
 meson.build                                   |  13 ++
 meson_options.txt                             |   6 +
 8 files changed, 370 insertions(+), 2 deletions(-)
---
diff --git a/README.md b/README.md
index 1f320729..72e40816 100644
--- a/README.md
+++ b/README.md
@@ -91,6 +91,7 @@ More information can be found at <https://wiki.gnome.org/Apps/Gthumb>.
   * libraw - some support for RAW photos
   * librsvg - display SVG images
   * libwebp - display and save WebP images
+  * libjxl - display JPEG XL images
   * lcms2, colord - color profile support
   * champlain, champlain-gtk - view the place a photo was taken on a map
   * clutter, clutter-gtk - enhanced slideshow effects
diff --git a/data/org.gnome.gThumb.desktop.in.in b/data/org.gnome.gThumb.desktop.in.in
index 1e795106..28de3806 100644
--- a/data/org.gnome.gThumb.desktop.in.in
+++ b/data/org.gnome.gThumb.desktop.in.in
@@ -9,7 +9,7 @@ Categories=GNOME;GTK;Graphics;Viewer;RasterGraphics;2DGraphics;Photography;
 Exec=gthumb %U
 # Translators: Do NOT translate or transliterate this text (this is an icon file name)!
 Icon=org.gnome.gThumb
-MimeType=image/bmp;image/jpeg;image/gif;image/png;image/tiff;image/x-bmp;image/x-ico;image/x-png;image/x-pcx;image/x-tga;image/xpm;image/svg+xml;image/webp;
+MimeType=image/bmp;image/jpeg;image/gif;image/png;image/tiff;image/x-bmp;image/x-ico;image/x-png;image/x-pcx;image/x-tga;image/xpm;image/svg+xml;image/webp;image/jxl;
 StartupNotify=true
 Terminal=false
 Type=Application
diff --git a/extensions/cairo_io/cairo-image-surface-jxl.c b/extensions/cairo_io/cairo-image-surface-jxl.c
new file mode 100644
index 00000000..264492be
--- /dev/null
+++ b/extensions/cairo_io/cairo-image-surface-jxl.c
@@ -0,0 +1,293 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2021 Free Software Foundation, Inc.
+ *
+ *  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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <jxl/decode.h>
+#include <jxl/thread_parallel_runner.h>
+#if HAVE_LCMS2
+#include <lcms2.h>
+#endif
+#include <gthumb.h>
+#include "cairo-image-surface-jxl.h"
+
+void convert_pixels(int width, int height, guchar* buffer) {
+       int x, y;
+       guchar *p = buffer, r, g, b, a;
+
+       for (y = 0; y < height; y++)
+               for (x = width; x; x--, p += 4) {
+                       a = p[3];
+                       if (a == 0) {
+                               *(guint32*)p = 0;
+                               continue;
+                       }
+
+                       r = p[0];
+                       g = p[1];
+                       b = p[2];
+                       if (a < 0xff) {
+                               r = _cairo_multiply_alpha(r, a);
+                               g = _cairo_multiply_alpha(g, a);
+                               b = _cairo_multiply_alpha(b, a);
+                       }
+                       *(guint32*)p = CAIRO_RGBA_TO_UINT32(r, g, b, a);
+               }
+}
+
+#define BUFFER_SIZE (1024*1024)
+
+GthImage *
+_cairo_image_surface_create_from_jxl(GInputStream  *istream,
+                                    GthFileData   *file_data,
+                                    int            requested_size,
+                                    int           *original_width,
+                                    int           *original_height,
+                                    gboolean      *loaded_original,
+                                    gpointer       user_data,
+                                    GCancellable  *cancellable,
+                                    GError       **error)
+{
+       GthImage                  *image;
+       guchar                    *filebuffer;
+       gsize                      filebuffer_size, read_size, buffer_read, unprocessed_len, processed_len;
+       int                        width = 0, height = 0;
+       cairo_surface_t           *surface = NULL;
+       cairo_surface_metadata_t  *metadata;
+       JxlDecoder                *dec;
+       void                      *runner;
+       JxlDecoderStatus           status;
+       JxlBasicInfo               info;
+       JxlPixelFormat             pixel_format;
+       guchar                    *surface_data = NULL;
+
+       image = gth_image_new();
+
+       dec = JxlDecoderCreate(NULL);
+       if (dec == NULL) {
+               g_error("Could not create JXL decoder.\n");
+               return image;
+       }
+
+       filebuffer_size = read_size = JxlDecoderSizeHintBasicInfo(dec);
+       filebuffer = g_new(guchar, filebuffer_size);
+       if (! g_input_stream_read_all(istream,
+                                     filebuffer,
+                                     filebuffer_size,
+                                     &buffer_read,
+                                     cancellable,
+                                     error))
+       {
+               g_error("Could not read start of JXL file.\n");
+               JxlDecoderDestroy(dec);
+               g_free(filebuffer);
+               return image;
+       }
+
+       if (JxlSignatureCheck(filebuffer, buffer_read) < JXL_SIG_CODESTREAM) {
+               g_error("Signature does not match for JPEG XL codestream or container.\n");
+               JxlDecoderDestroy(dec);
+               g_free(filebuffer);
+               return image;
+       }
+
+       read_size = 1048576;
+
+       runner = JxlThreadParallelRunnerCreate(NULL, JxlThreadParallelRunnerDefaultNumWorkerThreads());
+       if (runner == NULL) {
+               g_error("Could not create threaded parallel runner.\n");
+               JxlDecoderDestroy(dec);
+               g_free(filebuffer);
+               return image;
+       }
+
+       if (JxlDecoderSetParallelRunner(dec,
+                                       JxlThreadParallelRunner,
+                                       runner) > 0) {
+               g_error("Could not set parallel runner.\n");
+               JxlThreadParallelRunnerDestroy(runner);
+               JxlDecoderDestroy(dec);
+               g_free(filebuffer);
+               return image;
+       }
+
+       if (JxlDecoderSubscribeEvents(dec,
+                                     JXL_DEC_BASIC_INFO
+                                     | JXL_DEC_COLOR_ENCODING
+                                     | JXL_DEC_FULL_IMAGE) > 0) {
+               g_error("Could not subscribe to decoder events.\n");
+               JxlThreadParallelRunnerDestroy(runner);
+               JxlDecoderDestroy(dec);
+               g_free(filebuffer);
+               return image;
+       }
+
+       if (JxlDecoderSetInput(dec, filebuffer, buffer_read) > 0) {
+               g_error("Could not set decoder input.\n");
+               JxlThreadParallelRunnerDestroy(runner);
+               JxlDecoderDestroy(dec);
+               g_free(filebuffer);
+               return image;
+       }
+
+       status = JXL_DEC_NEED_MORE_INPUT;
+       while (status != JXL_DEC_SUCCESS) {
+               status = JxlDecoderProcessInput(dec);
+
+               switch (status) {
+               case JXL_DEC_ERROR:
+                       g_error("jxl: decoder error.\n");
+                       JxlThreadParallelRunnerDestroy(runner);
+                       JxlDecoderDestroy(dec);
+                       g_free(filebuffer);
+                       return image;
+
+               case JXL_DEC_NEED_MORE_INPUT:
+                       if (buffer_read == 0) {
+                               g_message("Reached end of file but decoder still wants more.\n");
+                               status = JXL_DEC_SUCCESS;
+                               break;
+                       }
+
+                       {
+                               unprocessed_len = JxlDecoderReleaseInput(dec);
+                               processed_len = filebuffer_size - unprocessed_len;
+
+                               guchar *old_filebuffer = filebuffer;
+                               filebuffer_size = unprocessed_len + read_size;
+                               filebuffer = g_new(guchar, filebuffer_size);
+                               if (unprocessed_len > 0)
+                                       memcpy(filebuffer, old_filebuffer + processed_len, unprocessed_len);
+                               g_free(old_filebuffer);
+
+                               gssize signed_buffer_read = g_input_stream_read(istream,
+                                                                               filebuffer + unprocessed_len,
+                                                                               read_size,
+                                                                               cancellable,
+                                                                               error);
+                               if (signed_buffer_read <= 0) {
+                                       buffer_read = 0;
+                                       break;
+                               }
+                               buffer_read = signed_buffer_read;
+
+                               if (JxlDecoderSetInput(dec, filebuffer, unprocessed_len + buffer_read) > 0) {
+                                       g_error("Could not set decoder input.\n");
+                                       JxlThreadParallelRunnerDestroy(runner);
+                                       JxlDecoderDestroy(dec);
+                                       g_free(filebuffer);
+                                       return image;
+                               }
+                       }
+                       break;
+
+               case JXL_DEC_BASIC_INFO:
+                       if (JxlDecoderGetBasicInfo(dec, &info) > 0) {
+                               g_error("Could not get basic info from decoder.\n");
+                               JxlThreadParallelRunnerDestroy(runner);
+                               JxlDecoderDestroy(dec);
+                               g_free(filebuffer);
+                               return image;
+                       }
+
+                       pixel_format.num_channels = 4;
+                       pixel_format.data_type = JXL_TYPE_UINT8;
+                       pixel_format.endianness = JXL_NATIVE_ENDIAN;
+                       pixel_format.align = 0;
+
+                       width = info.xsize;
+                       height = info.ysize;
+
+                       if (original_width != NULL)
+                               *original_width = width;
+                       if (original_height != NULL)
+                               *original_height = height;
+
+                       surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+                       surface_data = _cairo_image_surface_flush_and_get_data(surface);
+                       metadata = _cairo_image_surface_get_metadata(surface);
+                       _cairo_metadata_set_has_alpha(metadata, info.num_extra_channels);
+
+                       break;
+
+               case JXL_DEC_COLOR_ENCODING:
+#if HAVE_LCMS2
+                       if (JxlDecoderGetColorAsEncodedProfile(dec, &pixel_format, 
JXL_COLOR_PROFILE_TARGET_DATA, NULL) == JXL_DEC_SUCCESS)
+                               break;
+
+                       {
+                               gsize profile_size;
+                               if (JxlDecoderGetICCProfileSize(dec, &pixel_format, 
JXL_COLOR_PROFILE_TARGET_DATA, &profile_size) > 0) {
+                                       g_message("Could not get ICC profile size.\n");
+                                       break;
+                               }
+
+                               guchar *profile_data = g_new(guchar, profile_size);
+                               if (JxlDecoderGetColorAsICCProfile(dec, &pixel_format, 
JXL_COLOR_PROFILE_TARGET_DATA, profile_data, profile_size) > 0) {
+                                       g_message("Could not get ICC profile.\n");
+                                       g_free(profile_data);
+                                       break;
+                               }
+
+                               GthICCProfile *profile = NULL;
+                               profile = gth_icc_profile_new(GTH_ICC_PROFILE_ID_UNKNOWN, 
cmsOpenProfileFromMem(profile_data, profile_size));
+                               if (profile != NULL) {
+                                       gth_image_set_icc_profile(image, profile);
+                                       g_object_unref(profile);
+                               }
+                       }
+#endif
+                       break;
+
+               case JXL_DEC_NEED_IMAGE_OUT_BUFFER:
+                       if (JxlDecoderSetImageOutBuffer(dec, &pixel_format, surface_data, width * height * 4) 
0) {
+                               g_error("Could not set image-out buffer.\n");
+                               JxlThreadParallelRunnerDestroy(runner);
+                               JxlDecoderDestroy(dec);
+                               g_free(filebuffer);
+                               return image;
+                       }
+
+                       break;
+
+               case JXL_DEC_SUCCESS:
+                       continue;
+
+               case JXL_DEC_FULL_IMAGE:
+                       continue;
+
+               default:
+                       break;
+               }
+       }
+
+       JxlThreadParallelRunnerDestroy(runner);
+       JxlDecoderDestroy(dec);
+       g_free(filebuffer);
+
+       convert_pixels(width, height, surface_data);
+
+       cairo_surface_mark_dirty(surface);
+       if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS)
+               gth_image_set_cairo_surface(image, surface);
+
+       return image;
+}
diff --git a/extensions/cairo_io/cairo-image-surface-jxl.h b/extensions/cairo_io/cairo-image-surface-jxl.h
new file mode 100644
index 00000000..2920fd50
--- /dev/null
+++ b/extensions/cairo_io/cairo-image-surface-jxl.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2021 Free Software Foundation, Inc.
+ *
+ *  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 2 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CAIRO_IMAGE_SURFACE_JXL_H
+#define CAIRO_IMAGE_SURFACE_JXL_H
+
+#include <gtk/gtk.h>
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+GthImage *  _cairo_image_surface_create_from_jxl (GInputStream  *istream,
+                                                 GthFileData   *file_data,
+                                                 int            requested_size,
+                                                 int           *original_width,
+                                                 int           *original_height,
+                                                 gboolean      *loaded_original,
+                                                 gpointer       user_data,
+                                                 GCancellable  *cancellable,
+                                                 GError       **error);
+
+G_END_DECLS
+
+#endif /* CAIRO_IMAGE_SURFACE_JXL_H */
diff --git a/extensions/cairo_io/main.c b/extensions/cairo_io/main.c
index 1661b2ac..a2f89829 100644
--- a/extensions/cairo_io/main.c
+++ b/extensions/cairo_io/main.c
@@ -27,6 +27,7 @@
 #include "cairo-image-surface-svg.h"
 #include "cairo-image-surface-tiff.h"
 #include "cairo-image-surface-webp.h"
+#include "cairo-image-surface-jxl.h"
 #include "cairo-image-surface-xcf.h"
 #include "gth-image-saver-jpeg.h"
 #include "gth-image-saver-png.h"
@@ -78,6 +79,13 @@ gthumb_extension_activate (void)
        gth_main_register_type ("image-saver", GTH_TYPE_IMAGE_SAVER_WEBP);
 #endif
 
+#ifdef HAVE_LIBJXL
+       gth_main_register_image_loader_func (_cairo_image_surface_create_from_jxl,
+                                            GTH_IMAGE_FORMAT_CAIRO_SURFACE,
+                                            "image/jxl",
+                                            NULL);
+#endif
+
        gth_main_register_image_loader_func (_cairo_image_surface_create_from_xcf,
                                             GTH_IMAGE_FORMAT_CAIRO_SURFACE,
                                             "image/x-xcf",
diff --git a/extensions/cairo_io/meson.build b/extensions/cairo_io/meson.build
index 90c75e5d..870842a4 100644
--- a/extensions/cairo_io/meson.build
+++ b/extensions/cairo_io/meson.build
@@ -22,6 +22,10 @@ if use_libtiff
   source_files += files('cairo-image-surface-tiff.c')
 endif
 
+if use_libjxl
+  source_files += files('cairo-image-surface-jxl.c')
+endif
+
 enum_files = gnome.mkenums_simple('cairo-io-enum-types', sources: [ 'preferences.h' ])
 
 shared_module('cairo_io',
@@ -31,7 +35,8 @@ shared_module('cairo_io',
     jpeg_deps,
     use_libtiff ? tiff_deps : [],
     use_librsvg ? librsvg_dep : [],
-    use_libwebp ? libwebp_dep : []
+    use_libwebp ? libwebp_dep : [],
+    use_libjxl ? libjxl_deps : []
   ],
   include_directories : [ config_inc, gthumb_inc ],
   c_args : c_args,
diff --git a/meson.build b/meson.build
index 1108735d..21295ed6 100644
--- a/meson.build
+++ b/meson.build
@@ -17,6 +17,8 @@ libbrasero_version = '>=3.2.0'
 libchamplain_version = '>=0.12.0'
 librsvg_version = '>=2.34.0'
 libwebp_version = '>=0.2.0'
+libjxl_version = '>=0.3.0'
+libjxl_threads_version = '>=0.3.0'
 libjson_glib_version = '>=0.15.0'
 webkit2_version = '>=1.10.0'
 lcms2_version = '>=2.6'
@@ -160,6 +162,13 @@ else
   use_libwebp = false
 endif
 
+if get_option('libjxl')
+  libjxl_deps = [ dependency('libjxl', version : libjxl_version, required : false), 
dependency('libjxl_threads', version : libjxl_version, required : false) ]
+  use_libjxl = libjxl_deps[0].found()
+else
+  use_libjxl = false
+endif
+
 if get_option('libraw')
   libraw_dep = dependency('libraw', version : libraw_version, required : false)
   use_libraw = libraw_dep.found()
@@ -282,6 +291,9 @@ if use_libwebp
   config_data.set('HAVE_LIBWEBP', 1)
   config_data.set('WEBP_IS_UNKNOWN_TO_GLIB', 1) # Define to 1 if webp images area not recognized by the glib 
functions
 endif
+if use_libjxl
+  config_data.set('HAVE_LIBJXL', 1)
+endif
 if use_libraw
   config_data.set('HAVE_LIBRAW', 1)
 endif
@@ -371,6 +383,7 @@ summary = [
   '  progressive jpeg: @0@'.format(have_progressive_jpeg),
   '              tiff: @0@'.format(use_libtiff),
   '              webp: @0@'.format(use_libwebp),
+  '               jxl: @0@'.format(use_libjxl),
   '               raw: @0@'.format(use_libraw),
   '               svg: @0@'.format(use_librsvg),
   '      web services: @0@'.format(with_webservices),
diff --git a/meson_options.txt b/meson_options.txt
index e34b80c6..d7655fb3 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -58,6 +58,12 @@ option('libwebp',
   description : 'Use libwebp to load WebP images'
 )
 
+option('libjxl',
+  type : 'boolean',
+  value : true,
+  description : 'Use libjxl to load JPEG XL images'
+)
+
 option('libraw',
   type : 'boolean',
   value : true,


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