Fix #900: Support JPEG XL images
authorColin Clark <colin.clark@cclark.uk>
Mon, 9 Aug 2021 09:45:45 +0000 (10:45 +0100)
committerColin Clark <colin.clark@cclark.uk>
Mon, 9 Aug 2021 09:45:45 +0000 (10:45 +0100)
https://github.com/BestImageViewer/geeqie/issues/900

At this time it is necessary for the user to compile libjxl for
themselves.

README.md
configure.ac
src/Makefile.am
src/image-load.c
src/image_load_jpegxl.c [new file with mode: 0644]
src/image_load_jpegxl.h [new file with mode: 0644]

index dc555ed..9f78709 100644 (file)
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ Geeqie is a graphics file viewer. Basic features:
     * output: single image, anaglyph, SBS, mirror, SBS half size (3DTV)
 
 *   Viewing raster and vector images, in the following formats:
-3FR, ANI, APM, ARW, AVIF, BMP, CR2, CR3, CRW, CUR, DDS, DjVu, DNG, ERF, GIF, HEIC, HEIF, ICNS, ICO, JP2. JPE/JPEG/JPG, JPS, KDC, MEF, MOS, MPO, MRW, NEF, ORF, PBM/PGM/PNM/PPM, PEF, PNG, PSD, PTX, QIF/QTIF (QuickTime Image Format), RAF, RAW, RW2, SR2, SRF, SVG/SVGZ, TGA/TARGA, TIF/TIFF, WEBP, WMF, XBM, XPM.
+3FR, ANI, APM, ARW, AVIF, BMP, CR2, CR3, CRW, CUR, DDS, DjVu, DNG, ERF, GIF, HEIC, HEIF, ICNS, ICO, JP2. JPE/JPEG/JPG, JPEG XL, JPS, KDC, MEF, MOS, MPO, MRW, NEF, ORF, PBM/PGM/PNM/PPM, PEF, PNG, PSD, PTX, QIF/QTIF (QuickTime Image Format), RAF, RAW, RW2, SR2, SRF, SVG/SVGZ, TGA/TARGA, TIF/TIFF, WEBP, WMF, XBM, XPM.
 Animated GIFs are supported.
 
 * Preview and thumbnails of video clips can be displayed. Clips can be run via a defined external program.
index 799128b..e815ff6 100644 (file)
@@ -710,6 +710,31 @@ AM_CONDITIONAL(HAVE_DJVU, [test "x$HAVE_DJVU" = xyes])
 AC_SUBST(DJVU_CFLAGS)
 AC_SUBST(DJVU_LIBS)
 
+#  JPEG XL support
+# ----------------------------------------------------------------------
+
+AC_ARG_ENABLE([jpegxl],
+  AC_HELP_STRING([--disable-jpegxl], [disable jpeg xl support]),
+    [libjxl=$enableval], [libjxl=auto])
+
+if test "x${libjxl}" != "xno"; then
+  PKG_CHECK_MODULES(JPEGXL, libjxl >= 0.3.7,
+    [
+      HAVE_JPEGXL=yes
+      AC_DEFINE(HAVE_JPEGXL, 1, [define to enable JPEG XL support])
+    ],
+    [
+      HAVE_JPEGXL=no
+      AC_MSG_WARN([$JPEGXL_PKG_ERRORS])
+    ])
+else
+    HAVE_JPEGXL=disabled
+fi
+
+AM_CONDITIONAL(HAVE_JPEGXL, [test "x$HAVE_JPEGXL" = xyes])
+AC_SUBST(JPEGXL_CFLAGS)
+AC_SUBST(JPEGXL_LIBS)
+
 #  Markdown support
 # ----------------------------------------------------------------------
 
@@ -809,7 +834,7 @@ Flags:
   Gtk:           $GTK_CFLAGS
   Glib:          $GLIB_CFLAGS
   Thread:        $GTHREAD_LIBS
-  Others:       $JPEG_LIBS $TIFF_LIBS $LCMS_LIBS $EXIV2_LIBS $CLUTTER_LIBS $CLUTTER_GTK_LIBS $LIBCHAMPLAIN_LIBS $LIBCHAMPLAIN_GTK_LIBS $LUA_LIBS $RAW_LIBS
+  Others:       $JPEG_LIBS $TIFF_LIBS $LCMS_LIBS $EXIV2_LIBS $CLUTTER_LIBS $CLUTTER_GTK_LIBS $LIBCHAMPLAIN_LIBS $LIBCHAMPLAIN_GTK_LIBS $LUA_LIBS $RAW_LIBS $JPEGXL_LIBS
 
 Localization:
   NLS support:   $USE_NLS
@@ -836,6 +861,7 @@ Support:
   DjVu:                 $HAVE_DJVU
   J2K:          $HAVE_J2K
   LibRaw:        $HAVE_RAW
+  Libjxl:        $HAVE_JPEGXL
 
 Documentation:
   Doxygen:       $DX_DOXYGEN
index 339af63..09a1b14 100644 (file)
@@ -15,6 +15,7 @@ AM_CFLAGS =                           \
        $(HEIF_CFLAGS)  \
        $(WEBP_CFLAGS)  \
        $(J2K_CFLAGS)   \
+       $(JPEGXL_CFLAGS)        \
        -I$(top_srcdir)                 \
        -I$(top_builddir)
 
@@ -33,6 +34,7 @@ AM_CXXFLAGS =                         \
        $(HEIF_CFLAGS)  \
        $(WEBP_CFLAGS)  \
        $(J2K_CFLAGS)   \
+       $(JPEGXL_CFLAGS)        \
        -I$(top_srcdir)                 \
        -I$(top_builddir)
 
@@ -207,6 +209,8 @@ geeqie_SOURCES = \
        image_load_psd.h\
        image_load_j2k.c\
        image_load_j2k.h\
+       image_load_jpegxl.c\
+       image_load_jpegxl.h\
        image_load_libraw.c\
        image_load_libraw.h\
        image_load_svgz.c\
@@ -303,7 +307,7 @@ geeqie_SOURCES = \
        zonedetect.c    \
        zonedetect.h
 
-geeqie_LDADD = $(GTK_LIBS) $(GLIB_LIBS) $(INTLLIBS) $(JPEG_LIBS) $(TIFF_LIBS) $(LCMS_LIBS) $(EXIV2_LIBS) $(LIBCHAMPLAIN_LIBS) $(LIBCHAMPLAIN_GTK_LIBS) $(LUA_LIBS) $(CLUTTER_LIBS) $(CLUTTER_GTK_LIBS) $(FFMPEGTHUMBNAILER_LIBS) $(PDF_LIBS) $(HEIF_LIBS) $(WEBP_LIBS) $(DJVU_LIBS) $(J2K_LIBS) $(RAW_LIBS)
+geeqie_LDADD = $(GTK_LIBS) $(GLIB_LIBS) $(INTLLIBS) $(JPEG_LIBS) $(TIFF_LIBS) $(LCMS_LIBS) $(EXIV2_LIBS) $(LIBCHAMPLAIN_LIBS) $(LIBCHAMPLAIN_GTK_LIBS) $(LUA_LIBS) $(CLUTTER_LIBS) $(CLUTTER_GTK_LIBS) $(FFMPEGTHUMBNAILER_LIBS) $(PDF_LIBS) $(HEIF_LIBS) $(WEBP_LIBS) $(DJVU_LIBS) $(J2K_LIBS) $(RAW_LIBS) $(JPEGXL_LIBS)
 
 EXTRA_DIST = \
        $(extra_SLIK)
index 07588cb..74c3ca9 100644 (file)
@@ -35,6 +35,7 @@
 #include "image_load_collection.h"
 #include "image_load_webp.h"
 #include "image_load_j2k.h"
+#include "image_load_jpegxl.h"
 #include "image_load_libraw.h"
 #include "image_load_svgz.h"
 #include "misc.h"
@@ -817,6 +818,15 @@ static void image_loader_setup_loader(ImageLoader *il)
                image_loader_backend_set_j2k(&il->backend);
                }
        else
+#endif
+#ifdef HAVE_JPEGXL
+       if (il->bytes_total >= 12 &&
+               (memcmp(il->mapped_file, "\0\0\0\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", 12) == 0))
+               {
+               DEBUG_1("Using custom jpeg xl loader");
+               image_loader_backend_set_jpegxl(&il->backend);
+               }
+       else
 #endif
        if (il->fd->format_class == FORMAT_CLASS_COLLECTION)
                {
diff --git a/src/image_load_jpegxl.c b/src/image_load_jpegxl.c
new file mode 100644 (file)
index 0000000..d315c54
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2021- The Geeqie Team
+ *
+ * Author: Colin Clark
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * 
+ * Including parts:
+ * 
+ * Copyright (c) the JPEG XL Project Authors.
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 
+ * 3. Neither the name of the copyright holder nor the names of its
+ *    contributors may be used to endorse or promote products derived from
+ *    this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ */
+
+#include "main.h"
+
+#include "image-load.h"
+#include "image_load_jpegxl.h"
+
+#ifdef HAVE_JPEGXL
+
+#include "jxl/decode.h"
+
+typedef struct _ImageLoaderJPEGXL ImageLoaderJPEGXL;
+struct _ImageLoaderJPEGXL {
+       ImageLoaderBackendCbAreaUpdated area_updated_cb;
+       ImageLoaderBackendCbSize size_cb;
+       ImageLoaderBackendCbAreaPrepared area_prepared_cb;
+       gpointer data;
+       GdkPixbuf *pixbuf;
+       guint requested_width;
+       guint requested_height;
+       gboolean abort;
+};
+
+static void free_buffer(guchar *pixels, gpointer data)
+{
+       g_free(pixels);
+}
+
+static uint8_t *JxlMemoryToPixels(const uint8_t *next_in, size_t size, size_t *stride,
+                           size_t *xsize, size_t *ysize, int *has_alpha) {
+  JxlDecoder *dec = JxlDecoderCreate(NULL);
+  *has_alpha = 1;
+  uint8_t *pixels = NULL;
+  if (!dec) {
+    log_printf("JxlDecoderCreate failed\n");
+    return 0;
+  }
+  if (JXL_DEC_SUCCESS !=
+      JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE)) {
+    log_printf("JxlDecoderSubscribeEvents failed\n");
+    JxlDecoderDestroy(dec);
+    return 0;
+  }
+
+  JxlBasicInfo info;
+  int success = 0;
+  JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
+  JxlDecoderSetInput(dec, next_in, size);
+
+  for (;;) {
+    JxlDecoderStatus status = JxlDecoderProcessInput(dec);
+
+    if (status == JXL_DEC_ERROR) {
+      log_printf("Decoder error\n");
+      break;
+    } else if (status == JXL_DEC_NEED_MORE_INPUT) {
+      log_printf("Error, already provided all input\n");
+      break;
+    } else if (status == JXL_DEC_BASIC_INFO) {
+      if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &info)) {
+        log_printf("JxlDecoderGetBasicInfo failed\n");
+        break;
+      }
+      *xsize = info.xsize;
+      *ysize = info.ysize;
+      *stride = info.xsize * 4;
+    } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
+      size_t buffer_size;
+      if (JXL_DEC_SUCCESS !=
+          JxlDecoderImageOutBufferSize(dec, &format, &buffer_size)) {
+        log_printf("JxlDecoderImageOutBufferSize failed\n");
+        break;
+      }
+      if (buffer_size != *stride * *ysize) {
+        log_printf("Invalid out buffer size %zu %zu\n", buffer_size,
+                *stride * *ysize);
+        break;
+      }
+      size_t pixels_buffer_size = buffer_size * sizeof(uint8_t);
+      pixels = malloc(pixels_buffer_size);
+      void *pixels_buffer = (void *)pixels;
+      if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec, &format,
+                                                         pixels_buffer,
+                                                         pixels_buffer_size)) {
+        log_printf("JxlDecoderSetImageOutBuffer failed\n");
+        break;
+      }
+    } else if (status == JXL_DEC_FULL_IMAGE) {
+      // This means the decoder has decoded all pixels into the buffer.
+      success = 1;
+      break;
+    } else if (status == JXL_DEC_SUCCESS) {
+      log_printf("Decoding finished before receiving pixel data\n");
+      break;
+    } else {
+      log_printf("Unexpected decoder status: %d\n", status);
+      break;
+    }
+  }
+  if (success){
+    return pixels;
+  } else {
+    free(pixels);
+    return NULL;
+  }
+}
+
+static gboolean image_loader_jpegxl_load(gpointer loader, const guchar *buf, gsize count, GError **error)
+{
+       ImageLoaderJPEGXL *ld = (ImageLoaderJPEGXL *) loader;
+       gboolean ret = FALSE;
+       size_t stride;
+       size_t xsize;
+       size_t ysize;
+       int has_alpha;
+       uint8_t *decoded = NULL;
+
+       decoded = JxlMemoryToPixels(buf, count, &stride, &xsize, &ysize, &has_alpha);
+
+       if (decoded)
+               {
+               ld->pixbuf = gdk_pixbuf_new_from_data(decoded, GDK_COLORSPACE_RGB, has_alpha, 8, xsize, ysize, stride, free_buffer, NULL);
+
+               ld->area_updated_cb(loader, 0, 0, xsize, ysize, ld->data);
+
+               ret = TRUE;
+               }
+
+       return ret;
+}
+
+static gpointer image_loader_jpegxl_new(ImageLoaderBackendCbAreaUpdated area_updated_cb, ImageLoaderBackendCbSize size_cb, ImageLoaderBackendCbAreaPrepared area_prepared_cb, gpointer data)
+{
+       ImageLoaderJPEGXL *loader = g_new0(ImageLoaderJPEGXL, 1);
+       loader->area_updated_cb = area_updated_cb;
+       loader->size_cb = size_cb;
+       loader->area_prepared_cb = area_prepared_cb;
+       loader->data = data;
+       return (gpointer) loader;
+}
+
+static void image_loader_jpegxl_set_size(gpointer loader, int width, int height)
+{
+       ImageLoaderJPEGXL *ld = (ImageLoaderJPEGXL *) loader;
+       ld->requested_width = width;
+       ld->requested_height = height;
+}
+
+static GdkPixbuf* image_loader_jpegxl_get_pixbuf(gpointer loader)
+{
+       ImageLoaderJPEGXL *ld = (ImageLoaderJPEGXL *) loader;
+       return ld->pixbuf;
+}
+
+static gchar* image_loader_jpegxl_get_format_name(gpointer loader)
+{
+       return g_strdup("jxl");
+}
+
+static gchar** image_loader_jpegxl_get_format_mime_types(gpointer loader)
+{
+       static gchar *mime[] = {"image/jxl", NULL};
+       return g_strdupv(mime);
+}
+
+static gboolean image_loader_jpegxl_close(gpointer loader, GError **error)
+{
+       return TRUE;
+}
+
+static void image_loader_jpegxl_abort(gpointer loader)
+{
+       ImageLoaderJPEGXL *ld = (ImageLoaderJPEGXL *) loader;
+       ld->abort = TRUE;
+}
+
+static void image_loader_jpegxl_free(gpointer loader)
+{
+       ImageLoaderJPEGXL *ld = (ImageLoaderJPEGXL *) loader;
+       if (ld->pixbuf) g_object_unref(ld->pixbuf);
+       g_free(ld);
+}
+
+void image_loader_backend_set_jpegxl(ImageLoaderBackend *funcs)
+{
+       funcs->loader_new = image_loader_jpegxl_new;
+       funcs->set_size = image_loader_jpegxl_set_size;
+       funcs->load = image_loader_jpegxl_load;
+       funcs->write = NULL;
+       funcs->get_pixbuf = image_loader_jpegxl_get_pixbuf;
+       funcs->close = image_loader_jpegxl_close;
+       funcs->abort = image_loader_jpegxl_abort;
+       funcs->free = image_loader_jpegxl_free;
+       funcs->get_format_name = image_loader_jpegxl_get_format_name;
+       funcs->get_format_mime_types = image_loader_jpegxl_get_format_mime_types;
+}
+
+#endif
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
diff --git a/src/image_load_jpegxl.h b/src/image_load_jpegxl.h
new file mode 100644 (file)
index 0000000..616f05d
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 - The Geeqie Team
+ *
+ * Author: Colin Clark
+ *
+ * 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef IMAGE_LOAD_JPEGXL_H
+#define IMAGE_LOAD_JPEGXL_H
+
+#ifdef HAVE_JPEGXL
+void image_loader_backend_set_jpegxl(ImageLoaderBackend *funcs);
+#endif
+
+#endif
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */