From 02860865e3391bc14114894c7f1df8dbf19dfca0 Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Mon, 9 Aug 2021 10:45:45 +0100 Subject: [PATCH] Fix #900: Support JPEG XL images https://github.com/BestImageViewer/geeqie/issues/900 At this time it is necessary for the user to compile libjxl for themselves. --- README.md | 2 +- configure.ac | 28 ++++- src/Makefile.am | 6 +- src/image-load.c | 10 ++ src/image_load_jpegxl.c | 249 ++++++++++++++++++++++++++++++++++++++++ src/image_load_jpegxl.h | 29 +++++ 6 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 src/image_load_jpegxl.c create mode 100644 src/image_load_jpegxl.h diff --git a/README.md b/README.md index dc555ed9..9f78709e 100644 --- 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. diff --git a/configure.ac b/configure.ac index 799128bc..e815ff6b 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 339af632..09a1b147 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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) diff --git a/src/image-load.c b/src/image-load.c index 07588cbb..74c3ca98 100644 --- a/src/image-load.c +++ b/src/image-load.c @@ -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 index 00000000..d315c543 --- /dev/null +++ b/src/image_load_jpegxl.c @@ -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 index 00000000..616f05de --- /dev/null +++ b/src/image_load_jpegxl.h @@ -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: */ -- 2.20.1