From 5b5281b8c7d564810aa1e1cbeec3f6c4e15a763d Mon Sep 17 00:00:00 2001 From: oupson Date: Mon, 22 Jan 2024 09:45:51 +0100 Subject: [PATCH 1/4] Basic implementation of JPEG XL loading using libjxl --- configure.ac | 15 ++ tools/chafa/Makefile.am | 10 +- tools/chafa/jxl-loader.c | 273 +++++++++++++++++++++++++++++++++++++ tools/chafa/jxl-loader.h | 45 ++++++ tools/chafa/media-loader.c | 16 +++ 5 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 tools/chafa/jxl-loader.c create mode 100644 tools/chafa/jxl-loader.h diff --git a/configure.ac b/configure.ac index 89f623ff..3e85d7a3 100644 --- a/configure.ac +++ b/configure.ac @@ -156,6 +156,18 @@ AS_IF([test "$with_tools" != no], [ missing_debs="$missing_debs libwebp-dev" with_webp=no)]) AS_IF([test "$with_webp" != no], [AC_DEFINE([HAVE_WEBP], [1], [Define if we have WebP support.])]) + + dnl libjxl (optional) + AC_ARG_WITH(jxl, + [AS_HELP_STRING([--without-jxl], [don't build JXL loader [default=on]])], + , + with_jxl=yes) + AS_IF([test "$with_jxl" != no], [ + PKG_CHECK_MODULES(JXL, [libjxl libjxl_threads],, + missing_rpms="$missing_rpms libjxl-devel" + missing_debs="$missing_debs libjxl-dev" + with_jxl=no)]) + AS_IF([test "$with_jxl" != no], [AC_DEFINE([HAVE_JXL], [1], [Define if we have JXL support.])]) ]) AM_CONDITIONAL([WANT_TOOLS], [test "$with_tools" != no]) @@ -164,6 +176,7 @@ AM_CONDITIONAL([HAVE_SVG], [test "$with_tools" != no -a "$with_svg" != no]) AM_CONDITIONAL([HAVE_TIFF], [test "$with_tools" != no -a "$with_tiff" != no]) AM_CONDITIONAL([HAVE_WEBP], [test "$with_tools" != no -a "$with_webp" != no]) AM_CONDITIONAL([HAVE_AVIF], [test "$with_tools" != no -a "$with_avif" != no]) +AM_CONDITIONAL([HAVE_JXL], [test "$with_tools" != no -a "$with_jxl" != no]) # Used by gtk-doc's fixxref. GLIB_PREFIX="`$PKG_CONFIG --variable=prefix glib-2.0`" @@ -492,6 +505,7 @@ colorize_vars=" with_svg with_tiff with_webp + with_jxl with_avif " @@ -556,6 +570,7 @@ echo >&AS_MESSAGE_FD "With QOI loader ............. $pyes (internal)" echo >&AS_MESSAGE_FD "With SVG loader ............. $pwith_svg" echo >&AS_MESSAGE_FD "With TIFF loader ............ $pwith_tiff" echo >&AS_MESSAGE_FD "With WebP loader ............ $pwith_webp" +echo >&AS_MESSAGE_FD "With JXL loader ............. $pwith_jxl" echo >&AS_MESSAGE_FD "With XWD loader ............. $pyes (internal)" fi diff --git a/tools/chafa/Makefile.am b/tools/chafa/Makefile.am index ca69601a..1672da77 100644 --- a/tools/chafa/Makefile.am +++ b/tools/chafa/Makefile.am @@ -57,17 +57,23 @@ chafa_SOURCES += \ webp-loader.h endif +if HAVE_JXL +chafa_SOURCES += \ + jxl-loader.c \ + jxl-loader.h +endif + # We can pass -rpath so the binary knows where to find libchafa.so when # installed outside /usr (e.g. the default /usr/local). This affects Ubuntu. # Resolved by running ldconfig. See Github issue #32. # # This is disabled by default. -chafa_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) $(JPEG_CFLAGS) $(SVG_CFLAGS) $(TIFF_CFLAGS) $(WEBP_CFLAGS) $(AVIF_CFLAGS) $(FREETYPE_CFLAGS) +chafa_CFLAGS = $(CHAFA_CFLAGS) $(GLIB_CFLAGS) $(JPEG_CFLAGS) $(SVG_CFLAGS) $(TIFF_CFLAGS) $(WEBP_CFLAGS) $(JXL_CFLAGS) $(AVIF_CFLAGS) $(FREETYPE_CFLAGS) if ENABLE_RPATH chafa_LDFLAGS = $(CHAFA_LDFLAGS) -rpath $(libdir) endif -chafa_LDADD = $(GLIB_LIBS) $(JPEG_LIBS) $(SVG_LIBS) $(TIFF_LIBS) $(WEBP_LIBS) $(AVIF_LIBS) $(FREETYPE_LIBS) $(top_builddir)/chafa/libchafa.la $(top_builddir)/libnsgif/libnsgif.la $(top_builddir)/lodepng/liblodepng.la $(WIN32_LDADD) +chafa_LDADD = $(GLIB_LIBS) $(JPEG_LIBS) $(SVG_LIBS) $(TIFF_LIBS) $(WEBP_LIBS) $(JXL_LIBS) $(AVIF_LIBS) $(FREETYPE_LIBS) $(top_builddir)/chafa/libchafa.la $(top_builddir)/libnsgif/libnsgif.la $(top_builddir)/lodepng/liblodepng.la $(WIN32_LDADD) # On Microsoft Windows, we compile a resource file with windres and link it in. # This enables UTF-8 support in filenames, environment variables, etc. diff --git a/tools/chafa/jxl-loader.c b/tools/chafa/jxl-loader.c new file mode 100644 index 00000000..f532902e --- /dev/null +++ b/tools/chafa/jxl-loader.c @@ -0,0 +1,273 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include "chafa.h" +#include "file-mapping.h" +#include "glib.h" +#include +#include +#include +#include +#include +#include +#include + +#include "jxl-loader.h" + +#define BUFFER_SIZE (1024) + +struct JxlLoader +{ + GList *frames; + int frame_count; + int index; +}; + +typedef struct JxlFrame +{ + uint8_t *buffer; + int width; + int height; + gboolean is_premul; + int frame_duration; +} JxlFrame; + +GList *get_frames (JxlDecoder * dec, JxlParallelRunner * runner, + struct FileMapping *mapping); + +static JxlLoader * +jxl_loader_new (void) +{ + return g_new0 (JxlLoader, 1); +} + +GList * +get_frames (JxlDecoder *dec, JxlParallelRunner *runner, + struct FileMapping *mapping) +{ + if (JXL_DEC_SUCCESS != + JxlDecoderSetParallelRunner (dec, JxlResizableParallelRunner, runner)) + { + return NULL; + } + + if (JXL_DEC_SUCCESS != + JxlDecoderSubscribeEvents (dec, + JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | + JXL_DEC_FRAME)) + { + return NULL; + } + + JxlPixelFormat format = { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 }; + + JxlDecoderStatus decode_status = JXL_DEC_NEED_MORE_INPUT; + uint8_t buffer[BUFFER_SIZE]; + + gpointer image_buffer = NULL; + GList *frame_list = NULL; + JxlBasicInfo info; + JxlFrameHeader frame_header; + + int read = 0; + + for (;;) + { + if (JXL_DEC_ERROR == decode_status) + { + break; + } + else if (JXL_DEC_NEED_MORE_INPUT == decode_status) + { + JxlDecoderReleaseInput (dec); + gsize nbr = file_mapping_read (mapping, buffer, read, BUFFER_SIZE); + read += nbr; + if (nbr > 0) + { + if (JXL_DEC_SUCCESS != JxlDecoderSetInput (dec, buffer, nbr)) + { + break; + } + } + else + { + break; + } + } + else if (JXL_DEC_BASIC_INFO == decode_status) + { + if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo (dec, &info)) + { + break; + } + image_buffer = g_malloc0 (info.xsize * info.ysize * 4); + } + else if (JXL_DEC_NEED_IMAGE_OUT_BUFFER == decode_status) + { + if (JXL_DEC_SUCCESS != + JxlDecoderSetImageOutBuffer (dec, &format, image_buffer, + info.xsize * info.ysize * 4)) + { + break; + } + } + else if (JXL_DEC_FRAME == decode_status) + { + if (JXL_DEC_SUCCESS != + JxlDecoderGetFrameHeader (dec, &frame_header)) + { + break; + } + } + else if (JXL_DEC_FULL_IMAGE == decode_status) + { + uint32_t num = (info.animation.tps_numerator == 0) + ? 1 : info.animation.tps_numerator; + JxlFrame *frame = g_new (JxlFrame, 1); + frame->buffer = image_buffer; + frame->width = info.xsize; + frame->height = info.ysize; + frame->is_premul = info.alpha_premultiplied; + frame->frame_duration = + frame_header.duration * 1000 * info.animation.tps_denominator / + num; + frame_list = g_list_append (frame_list, frame); + + image_buffer = g_malloc0 (info.xsize * info.ysize * 4); + } + else if (JXL_DEC_SUCCESS == decode_status) + { + return frame_list; // TODO; + } + + decode_status = JxlDecoderProcessInput (dec); + } + + if (image_buffer != NULL) + { + g_free (image_buffer); + } + + if (frame_list != NULL) + { + GList *list = frame_list; + g_free (((JxlFrame *) list->data)->buffer); + g_free (list->data); + while ((list = g_list_next (list)) != NULL) + { + g_free (((JxlFrame *) list->data)->buffer); + g_free (list->data); + } + g_list_free (frame_list); + } + + return NULL; +} + +JxlLoader * +jxl_loader_new_from_mapping (struct FileMapping *mapping) +{ + JxlLoader *loader = NULL; + gboolean success = FALSE; + + g_return_val_if_fail (mapping != NULL, NULL); + + if (!file_mapping_has_magic (mapping, 0, "\xFF\x0A", 2) && + !file_mapping_has_magic (mapping, 0, + "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", + 12)) + { + goto out; + } + + loader = jxl_loader_new (); + + JxlDecoder *decoder = JxlDecoderCreate (NULL); + JxlParallelRunner *runner = JxlResizableParallelRunnerCreate (NULL); + + GList *frames = get_frames (decoder, runner, mapping); + JxlDecoderDestroy (decoder); + JxlResizableParallelRunnerDestroy (runner); + + success = frames != NULL; + + if (success) + { + loader->frames = frames; + loader->frame_count = g_list_length (frames); + loader->index = 0; + } + out: + if (!success) + { + if (loader) + { + g_free (loader); + loader = NULL; + } + } + + return loader; +}; + +void +jxl_loader_destroy (JxlLoader *loader) +{ + GList *list = loader->frames; + g_free (((JxlFrame *) list->data)->buffer); + g_free (list->data); + while ((list = g_list_next (list)) != NULL) + { + g_free (((JxlFrame *) list->data)->buffer); + g_free (list->data); + } + g_list_free (loader->frames); + g_free (loader); +} + +gboolean +jxl_loader_get_is_animation (JxlLoader *loader) +{ + return loader->frame_count > 1; +} + +gconstpointer +jxl_loader_get_frame_data (JxlLoader *loader, + ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, + gint *rowstride_out) +{ + g_return_val_if_fail (loader != NULL, NULL); + JxlFrame *frame = g_list_nth_data (loader->frames, loader->index); + + *pixel_type_out = (frame->is_premul) ? CHAFA_PIXEL_RGBA8_PREMULTIPLIED + : CHAFA_PIXEL_RGBA8_UNASSOCIATED; + + *width_out = frame->width; + *height_out = frame->height; + *rowstride_out = frame->width * 4; + + return frame->buffer; +} + +gint +jxl_loader_get_frame_delay (JxlLoader *loader) +{ + g_return_val_if_fail (loader != NULL, 0); + return ((JxlFrame *) + g_list_nth_data (loader->frames, loader->index))->frame_duration; +} + +void +jxl_loader_goto_first_frame (JxlLoader *loader) +{ + g_return_if_fail (loader != NULL); + loader->index = 0; +} + +gboolean +jxl_loader_goto_next_frame (JxlLoader *loader) +{ + g_return_val_if_fail (loader != NULL, FALSE); + loader->index = loader->index + 1; + return loader->index < loader->frame_count; +} diff --git a/tools/chafa/jxl-loader.h b/tools/chafa/jxl-loader.h new file mode 100644 index 00000000..d3930d7c --- /dev/null +++ b/tools/chafa/jxl-loader.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Copyright (C) 2018-2023 Hans Petter Jansson + * + * This file is part of Chafa, a program that shows pictures on text terminals. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa 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 Lesser General Public License + * along with Chafa. If not, see . */ + +#ifndef __JXL_LOADER_H__ +#define __JXL_LOADER_H__ + +#include +#include "file-mapping.h" +#include "chafa-image.h" + +G_BEGIN_DECLS + +typedef struct JxlLoader JxlLoader; + +JxlLoader *jxl_loader_new_from_mapping (FileMapping *mapping); +void jxl_loader_destroy (JxlLoader *loader); + +gboolean jxl_loader_get_is_animation (JxlLoader *loader); + +gconstpointer jxl_loader_get_frame_data (JxlLoader *loader, ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, gint *rowstride_out); +gint jxl_loader_get_frame_delay (JxlLoader *loader); + +void jxl_loader_goto_first_frame (JxlLoader *loader); +gboolean jxl_loader_goto_next_frame (JxlLoader *loader); + +G_END_DECLS + +#endif /* __JXL_LOADER_H__ */ \ No newline at end of file diff --git a/tools/chafa/media-loader.c b/tools/chafa/media-loader.c index 277c5618..a36d652d 100644 --- a/tools/chafa/media-loader.c +++ b/tools/chafa/media-loader.c @@ -40,6 +40,7 @@ #include "tiff-loader.h" #include "webp-loader.h" #include "avif-loader.h" +#include "jxl-loader.h" typedef enum { @@ -52,6 +53,7 @@ typedef enum LOADER_TYPE_WEBP, LOADER_TYPE_AVIF, LOADER_TYPE_SVG, + LOADER_TYPE_JXL, LOADER_TYPE_LAST } @@ -189,6 +191,20 @@ loader_vtable [LOADER_TYPE_LAST] = (gint (*) (gpointer)) avif_loader_get_frame_delay }, #endif +#ifdef HAVE_JXL + [LOADER_TYPE_JXL] = + { + "JXL", + (gpointer (*)(gpointer)) jxl_loader_new_from_mapping, + (gpointer (*)(gconstpointer)) NULL, + (void (*)(gpointer)) jxl_loader_destroy, + (gboolean (*)(gpointer)) jxl_loader_get_is_animation, + (void (*)(gpointer)) jxl_loader_goto_first_frame, + (gboolean (*)(gpointer)) jxl_loader_goto_next_frame, + (gconstpointer (*) (gpointer, gpointer, gpointer, gpointer, gpointer)) jxl_loader_get_frame_data, + (gint (*) (gpointer)) jxl_loader_get_frame_delay + }, +#endif }; struct MediaLoader From 6a8f4c63e916075a45220905da3951641397418a Mon Sep 17 00:00:00 2001 From: oupson Date: Mon, 22 Jan 2024 10:45:33 +0100 Subject: [PATCH 2/4] Small refracto on jxl loading --- tools/chafa/jxl-loader.c | 143 ++++++++++++++++++++------------------- tools/chafa/jxl-loader.h | 11 +-- 2 files changed, 80 insertions(+), 74 deletions(-) diff --git a/tools/chafa/jxl-loader.c b/tools/chafa/jxl-loader.c index f532902e..db6ce4a7 100644 --- a/tools/chafa/jxl-loader.c +++ b/tools/chafa/jxl-loader.c @@ -1,20 +1,22 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* -*- Mode: C; tab-width: 4; ent su: nil; c-basic-offset: 4 -*- */ #include "chafa.h" #include "file-mapping.h" #include "glib.h" -#include #include #include -#include #include -#include #include #include "jxl-loader.h" #define BUFFER_SIZE (1024) +GList *jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, + struct FileMapping *mapping); + +void jxl_cleanup_frame_list (GList *frame_list); + struct JxlLoader { GList *frames; @@ -31,9 +33,6 @@ typedef struct JxlFrame int frame_duration; } JxlFrame; -GList *get_frames (JxlDecoder * dec, JxlParallelRunner * runner, - struct FileMapping *mapping); - static JxlLoader * jxl_loader_new (void) { @@ -41,24 +40,24 @@ jxl_loader_new (void) } GList * -get_frames (JxlDecoder *dec, JxlParallelRunner *runner, - struct FileMapping *mapping) +jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, + FileMapping *mapping) { - if (JXL_DEC_SUCCESS != - JxlDecoderSetParallelRunner (dec, JxlResizableParallelRunner, runner)) + if (JXL_DEC_SUCCESS + != JxlDecoderSetParallelRunner (dec, JxlResizableParallelRunner, + runner)) { return NULL; } - if (JXL_DEC_SUCCESS != - JxlDecoderSubscribeEvents (dec, - JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | - JXL_DEC_FRAME)) + if (JXL_DEC_SUCCESS + != JxlDecoderSubscribeEvents ( + dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME)) { return NULL; } - JxlPixelFormat format = { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 }; + const JxlPixelFormat format = { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 }; JxlDecoderStatus decode_status = JXL_DEC_NEED_MORE_INPUT; uint8_t buffer[BUFFER_SIZE]; @@ -70,20 +69,18 @@ get_frames (JxlDecoder *dec, JxlParallelRunner *runner, int read = 0; - for (;;) + while (JXL_DEC_ERROR != decode_status) { - if (JXL_DEC_ERROR == decode_status) - { - break; - } - else if (JXL_DEC_NEED_MORE_INPUT == decode_status) + if (JXL_DEC_NEED_MORE_INPUT == decode_status) { JxlDecoderReleaseInput (dec); - gsize nbr = file_mapping_read (mapping, buffer, read, BUFFER_SIZE); + const gsize nbr = file_mapping_read (mapping, buffer, read, + BUFFER_SIZE); read += nbr; if (nbr > 0) { - if (JXL_DEC_SUCCESS != JxlDecoderSetInput (dec, buffer, nbr)) + if (JXL_DEC_SUCCESS + != JxlDecoderSetInput (dec, buffer, nbr)) { break; } @@ -99,49 +96,53 @@ get_frames (JxlDecoder *dec, JxlParallelRunner *runner, { break; } - image_buffer = g_malloc0 (info.xsize * info.ysize * 4); + image_buffer = g_malloc (info.xsize * info.ysize * 4); } else if (JXL_DEC_NEED_IMAGE_OUT_BUFFER == decode_status) { - if (JXL_DEC_SUCCESS != - JxlDecoderSetImageOutBuffer (dec, &format, image_buffer, - info.xsize * info.ysize * 4)) + if (JXL_DEC_SUCCESS + != JxlDecoderSetImageOutBuffer ( + dec, &format, image_buffer, + info.xsize * info.ysize * 4)) { break; } } else if (JXL_DEC_FRAME == decode_status) { - if (JXL_DEC_SUCCESS != - JxlDecoderGetFrameHeader (dec, &frame_header)) + if (JXL_DEC_SUCCESS + != JxlDecoderGetFrameHeader (dec, &frame_header)) { break; } } else if (JXL_DEC_FULL_IMAGE == decode_status) { - uint32_t num = (info.animation.tps_numerator == 0) - ? 1 : info.animation.tps_numerator; + const uint32_t num = info.animation.tps_numerator == 0 + ? 1 + : info.animation.tps_numerator; JxlFrame *frame = g_new (JxlFrame, 1); frame->buffer = image_buffer; frame->width = info.xsize; frame->height = info.ysize; frame->is_premul = info.alpha_premultiplied; - frame->frame_duration = - frame_header.duration * 1000 * info.animation.tps_denominator / - num; - frame_list = g_list_append (frame_list, frame); + frame->frame_duration = frame_header.duration * 1000 + * info.animation.tps_denominator + / num; + frame_list = g_list_prepend (frame_list, frame); - image_buffer = g_malloc0 (info.xsize * info.ysize * 4); + image_buffer = g_malloc (info.xsize * info.ysize * 4); } else if (JXL_DEC_SUCCESS == decode_status) { - return frame_list; // TODO; + frame_list = g_list_reverse (frame_list); + return frame_list; } decode_status = JxlDecoderProcessInput (dec); } + // Decoding failed if (image_buffer != NULL) { g_free (image_buffer); @@ -149,14 +150,7 @@ get_frames (JxlDecoder *dec, JxlParallelRunner *runner, if (frame_list != NULL) { - GList *list = frame_list; - g_free (((JxlFrame *) list->data)->buffer); - g_free (list->data); - while ((list = g_list_next (list)) != NULL) - { - g_free (((JxlFrame *) list->data)->buffer); - g_free (list->data); - } + jxl_cleanup_frame_list (frame_list); g_list_free (frame_list); } @@ -164,19 +158,19 @@ get_frames (JxlDecoder *dec, JxlParallelRunner *runner, } JxlLoader * -jxl_loader_new_from_mapping (struct FileMapping *mapping) +jxl_loader_new_from_mapping (FileMapping *mapping) { JxlLoader *loader = NULL; gboolean success = FALSE; g_return_val_if_fail (mapping != NULL, NULL); - if (!file_mapping_has_magic (mapping, 0, "\xFF\x0A", 2) && - !file_mapping_has_magic (mapping, 0, - "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", - 12)) + if (!file_mapping_has_magic (mapping, 0, "\xFF\x0A", 2) + && !file_mapping_has_magic ( + mapping, 0, "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", + 12)) { - goto out; + return NULL; } loader = jxl_loader_new (); @@ -184,7 +178,8 @@ jxl_loader_new_from_mapping (struct FileMapping *mapping) JxlDecoder *decoder = JxlDecoderCreate (NULL); JxlParallelRunner *runner = JxlResizableParallelRunnerCreate (NULL); - GList *frames = get_frames (decoder, runner, mapping); + GList *frames = jxl_get_frames (decoder, runner, mapping); + JxlDecoderDestroy (decoder); JxlResizableParallelRunnerDestroy (runner); @@ -196,8 +191,7 @@ jxl_loader_new_from_mapping (struct FileMapping *mapping) loader->frame_count = g_list_length (frames); loader->index = 0; } - out: - if (!success) + else { if (loader) { @@ -210,16 +204,28 @@ jxl_loader_new_from_mapping (struct FileMapping *mapping) }; void -jxl_loader_destroy (JxlLoader *loader) +jxl_cleanup_frame_list (GList *frame_list) { - GList *list = loader->frames; - g_free (((JxlFrame *) list->data)->buffer); - g_free (list->data); - while ((list = g_list_next (list)) != NULL) + GList *frame = frame_list; + + while (frame != NULL) { - g_free (((JxlFrame *) list->data)->buffer); - g_free (list->data); + if (frame->data != NULL) + { + JxlFrame *jxl_frame = frame->data; + g_free (jxl_frame->buffer); + g_free (jxl_frame); + frame->data = NULL; + } + frame = frame->next; } +} + +void +jxl_loader_destroy (JxlLoader *loader) +{ + GList *list = loader->frames; + jxl_cleanup_frame_list (list); g_list_free (loader->frames); g_free (loader); } @@ -231,16 +237,15 @@ jxl_loader_get_is_animation (JxlLoader *loader) } gconstpointer -jxl_loader_get_frame_data (JxlLoader *loader, - ChafaPixelType *pixel_type_out, +jxl_loader_get_frame_data (JxlLoader *loader, ChafaPixelType *pixel_type_out, gint *width_out, gint *height_out, gint *rowstride_out) { g_return_val_if_fail (loader != NULL, NULL); - JxlFrame *frame = g_list_nth_data (loader->frames, loader->index); + const JxlFrame *frame = g_list_nth_data (loader->frames, loader->index); - *pixel_type_out = (frame->is_premul) ? CHAFA_PIXEL_RGBA8_PREMULTIPLIED - : CHAFA_PIXEL_RGBA8_UNASSOCIATED; + *pixel_type_out = frame->is_premul ? CHAFA_PIXEL_RGBA8_PREMULTIPLIED + : CHAFA_PIXEL_RGBA8_UNASSOCIATED; *width_out = frame->width; *height_out = frame->height; @@ -253,8 +258,8 @@ gint jxl_loader_get_frame_delay (JxlLoader *loader) { g_return_val_if_fail (loader != NULL, 0); - return ((JxlFrame *) - g_list_nth_data (loader->frames, loader->index))->frame_duration; + return ((JxlFrame *)g_list_nth_data (loader->frames, loader->index)) + ->frame_duration; } void diff --git a/tools/chafa/jxl-loader.h b/tools/chafa/jxl-loader.h index d3930d7c..23cf45b8 100644 --- a/tools/chafa/jxl-loader.h +++ b/tools/chafa/jxl-loader.h @@ -16,13 +16,12 @@ * * You should have received a copy of the GNU Lesser General Public License * along with Chafa. If not, see . */ - + #ifndef __JXL_LOADER_H__ #define __JXL_LOADER_H__ -#include #include "file-mapping.h" -#include "chafa-image.h" +#include G_BEGIN_DECLS @@ -33,8 +32,10 @@ void jxl_loader_destroy (JxlLoader *loader); gboolean jxl_loader_get_is_animation (JxlLoader *loader); -gconstpointer jxl_loader_get_frame_data (JxlLoader *loader, ChafaPixelType *pixel_type_out, - gint *width_out, gint *height_out, gint *rowstride_out); +gconstpointer jxl_loader_get_frame_data (JxlLoader *loader, + ChafaPixelType *pixel_type_out, + gint *width_out, gint *height_out, + gint *rowstride_out); gint jxl_loader_get_frame_delay (JxlLoader *loader); void jxl_loader_goto_first_frame (JxlLoader *loader); From eba49c5b5215e0c278215c6fa36f241ab8d1d973 Mon Sep 17 00:00:00 2001 From: oupson Date: Mon, 22 Jan 2024 14:03:34 +0100 Subject: [PATCH 3/4] Only use 4 channels when needed on jxl decoding --- tools/chafa/jxl-loader.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/tools/chafa/jxl-loader.c b/tools/chafa/jxl-loader.c index db6ce4a7..f4bee33d 100644 --- a/tools/chafa/jxl-loader.c +++ b/tools/chafa/jxl-loader.c @@ -29,6 +29,7 @@ typedef struct JxlFrame uint8_t *buffer; int width; int height; + gboolean have_alpha; gboolean is_premul; int frame_duration; } JxlFrame; @@ -57,7 +58,7 @@ jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, return NULL; } - const JxlPixelFormat format = { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 }; + JxlPixelFormat format = { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 }; JxlDecoderStatus decode_status = JXL_DEC_NEED_MORE_INPUT; uint8_t buffer[BUFFER_SIZE]; @@ -96,14 +97,19 @@ jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, { break; } - image_buffer = g_malloc (info.xsize * info.ysize * 4); + if (!info.alpha_bits) + { + format.num_channels = 3; + } + image_buffer = g_malloc (info.xsize * info.ysize + * format.num_channels); } else if (JXL_DEC_NEED_IMAGE_OUT_BUFFER == decode_status) { if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer ( dec, &format, image_buffer, - info.xsize * info.ysize * 4)) + info.xsize * info.ysize * format.num_channels)) { break; } @@ -125,13 +131,15 @@ jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, frame->buffer = image_buffer; frame->width = info.xsize; frame->height = info.ysize; + frame->have_alpha = format.num_channels == 4; frame->is_premul = info.alpha_premultiplied; frame->frame_duration = frame_header.duration * 1000 * info.animation.tps_denominator / num; frame_list = g_list_prepend (frame_list, frame); - image_buffer = g_malloc (info.xsize * info.ysize * 4); + image_buffer = g_malloc (info.xsize * info.ysize + * format.num_channels); } else if (JXL_DEC_SUCCESS == decode_status) { @@ -244,12 +252,23 @@ jxl_loader_get_frame_data (JxlLoader *loader, ChafaPixelType *pixel_type_out, g_return_val_if_fail (loader != NULL, NULL); const JxlFrame *frame = g_list_nth_data (loader->frames, loader->index); - *pixel_type_out = frame->is_premul ? CHAFA_PIXEL_RGBA8_PREMULTIPLIED - : CHAFA_PIXEL_RGBA8_UNASSOCIATED; + int num_channels; + if (frame->have_alpha) + { + num_channels = 4; + *pixel_type_out = frame->is_premul + ? CHAFA_PIXEL_RGBA8_PREMULTIPLIED + : CHAFA_PIXEL_RGBA8_UNASSOCIATED; + } + else + { + num_channels = 3; + *pixel_type_out = CHAFA_PIXEL_RGB8; + } *width_out = frame->width; *height_out = frame->height; - *rowstride_out = frame->width * 4; + *rowstride_out = frame->width * num_channels; return frame->buffer; } From 70f83fa0ba8a320be35916c114d04c95173ff0f0 Mon Sep 17 00:00:00 2001 From: oupson Date: Tue, 23 Jan 2024 09:10:32 +0100 Subject: [PATCH 4/4] jxl: fix indentation and add copyright / author --- AUTHORS | 3 +- tools/chafa/jxl-loader.c | 269 ++++++++++++++++++++------------------- tools/chafa/jxl-loader.h | 7 +- 3 files changed, 141 insertions(+), 138 deletions(-) diff --git a/AUTHORS b/AUTHORS index c1ce9513..fefde3b4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -7,7 +7,7 @@ git log. For a complete list, you could try: git log --pretty="format:%an <%ae>" | sort -f | uniq -Per 2024-01-08, this yields the following (sans duplicates): +Per 2024-01-23, this yields the following (sans duplicates): alkahest begasus @@ -35,3 +35,4 @@ Sudhakar Verma Tim Gates Wu Zhenyu Øyvind Kolås +oupson diff --git a/tools/chafa/jxl-loader.c b/tools/chafa/jxl-loader.c index f4bee33d..a907b50b 100644 --- a/tools/chafa/jxl-loader.c +++ b/tools/chafa/jxl-loader.c @@ -1,5 +1,22 @@ /* -*- Mode: C; tab-width: 4; ent su: nil; c-basic-offset: 4 -*- */ +/* Copyright (C) 2024 oupson + * + * This file is part of Chafa, a program that shows pictures on text terminals. + * + * Chafa is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Chafa 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 Lesser General Public License + * along with Chafa. If not, see . */ + #include "chafa.h" #include "file-mapping.h" #include "glib.h" @@ -12,8 +29,7 @@ #define BUFFER_SIZE (1024) -GList *jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, - struct FileMapping *mapping); +GList *jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, FileMapping *mapping); void jxl_cleanup_frame_list (GList *frame_list); @@ -41,27 +57,25 @@ jxl_loader_new (void) } GList * -jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, - FileMapping *mapping) +jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, FileMapping *mapping) { if (JXL_DEC_SUCCESS - != JxlDecoderSetParallelRunner (dec, JxlResizableParallelRunner, - runner)) - { - return NULL; - } + != JxlDecoderSetParallelRunner (dec, JxlResizableParallelRunner, runner)) + { + return NULL; + } if (JXL_DEC_SUCCESS - != JxlDecoderSubscribeEvents ( - dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE | JXL_DEC_FRAME)) - { - return NULL; - } + != JxlDecoderSubscribeEvents (dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE + | JXL_DEC_FRAME)) + { + return NULL; + } JxlPixelFormat format = { 4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0 }; JxlDecoderStatus decode_status = JXL_DEC_NEED_MORE_INPUT; - uint8_t buffer[BUFFER_SIZE]; + uint8_t buffer [BUFFER_SIZE]; gpointer image_buffer = NULL; GList *frame_list = NULL; @@ -71,96 +85,89 @@ jxl_get_frames (JxlDecoder *dec, JxlParallelRunner *runner, int read = 0; while (JXL_DEC_ERROR != decode_status) + { + if (JXL_DEC_NEED_MORE_INPUT == decode_status) { - if (JXL_DEC_NEED_MORE_INPUT == decode_status) - { - JxlDecoderReleaseInput (dec); - const gsize nbr = file_mapping_read (mapping, buffer, read, - BUFFER_SIZE); - read += nbr; - if (nbr > 0) - { - if (JXL_DEC_SUCCESS - != JxlDecoderSetInput (dec, buffer, nbr)) - { - break; - } - } - else - { - break; - } - } - else if (JXL_DEC_BASIC_INFO == decode_status) - { - if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo (dec, &info)) - { - break; - } - if (!info.alpha_bits) - { - format.num_channels = 3; - } - image_buffer = g_malloc (info.xsize * info.ysize - * format.num_channels); - } - else if (JXL_DEC_NEED_IMAGE_OUT_BUFFER == decode_status) - { - if (JXL_DEC_SUCCESS - != JxlDecoderSetImageOutBuffer ( - dec, &format, image_buffer, - info.xsize * info.ysize * format.num_channels)) - { - break; - } - } - else if (JXL_DEC_FRAME == decode_status) + JxlDecoderReleaseInput (dec); + const gsize nbr = file_mapping_read (mapping, buffer, read, BUFFER_SIZE); + read += nbr; + if (nbr > 0) + { + if (JXL_DEC_SUCCESS != JxlDecoderSetInput (dec, buffer, nbr)) { - if (JXL_DEC_SUCCESS - != JxlDecoderGetFrameHeader (dec, &frame_header)) - { - break; - } + break; } - else if (JXL_DEC_FULL_IMAGE == decode_status) - { - const uint32_t num = info.animation.tps_numerator == 0 - ? 1 - : info.animation.tps_numerator; - JxlFrame *frame = g_new (JxlFrame, 1); - frame->buffer = image_buffer; - frame->width = info.xsize; - frame->height = info.ysize; - frame->have_alpha = format.num_channels == 4; - frame->is_premul = info.alpha_premultiplied; - frame->frame_duration = frame_header.duration * 1000 - * info.animation.tps_denominator - / num; - frame_list = g_list_prepend (frame_list, frame); - - image_buffer = g_malloc (info.xsize * info.ysize - * format.num_channels); - } - else if (JXL_DEC_SUCCESS == decode_status) - { - frame_list = g_list_reverse (frame_list); - return frame_list; - } - - decode_status = JxlDecoderProcessInput (dec); + } + else + { + break; + } + } + else if (JXL_DEC_BASIC_INFO == decode_status) + { + if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo (dec, &info)) + { + break; + } + if (!info.alpha_bits) + { + format.num_channels = 3; + } + image_buffer = g_malloc (info.xsize * info.ysize * format.num_channels); + } + else if (JXL_DEC_NEED_IMAGE_OUT_BUFFER == decode_status) + { + if (JXL_DEC_SUCCESS + != JxlDecoderSetImageOutBuffer (dec, &format, image_buffer, + info.xsize * info.ysize + * format.num_channels)) + { + break; + } + } + else if (JXL_DEC_FRAME == decode_status) + { + if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader (dec, &frame_header)) + { + break; + } } + else if (JXL_DEC_FULL_IMAGE == decode_status) + { + const uint32_t num + = info.animation.tps_numerator == 0 ? 1 : info.animation.tps_numerator; + JxlFrame *frame = g_new (JxlFrame, 1); + frame->buffer = image_buffer; + frame->width = info.xsize; + frame->height = info.ysize; + frame->have_alpha = format.num_channels == 4; + frame->is_premul = info.alpha_premultiplied; + frame->frame_duration + = frame_header.duration * 1000 * info.animation.tps_denominator / num; + frame_list = g_list_prepend (frame_list, frame); + + image_buffer = g_malloc (info.xsize * info.ysize * format.num_channels); + } + else if (JXL_DEC_SUCCESS == decode_status) + { + frame_list = g_list_reverse (frame_list); + return frame_list; + } + + decode_status = JxlDecoderProcessInput (dec); + } // Decoding failed if (image_buffer != NULL) - { - g_free (image_buffer); - } + { + g_free (image_buffer); + } if (frame_list != NULL) - { - jxl_cleanup_frame_list (frame_list); - g_list_free (frame_list); - } + { + jxl_cleanup_frame_list (frame_list); + g_list_free (frame_list); + } return NULL; } @@ -175,11 +182,10 @@ jxl_loader_new_from_mapping (FileMapping *mapping) if (!file_mapping_has_magic (mapping, 0, "\xFF\x0A", 2) && !file_mapping_has_magic ( - mapping, 0, "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", - 12)) - { - return NULL; - } + mapping, 0, "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", 12)) + { + return NULL; + } loader = jxl_loader_new (); @@ -194,19 +200,19 @@ jxl_loader_new_from_mapping (FileMapping *mapping) success = frames != NULL; if (success) - { - loader->frames = frames; - loader->frame_count = g_list_length (frames); - loader->index = 0; - } + { + loader->frames = frames; + loader->frame_count = g_list_length (frames); + loader->index = 0; + } else + { + if (loader) { - if (loader) - { - g_free (loader); - loader = NULL; - } + g_free (loader); + loader = NULL; } + } return loader; }; @@ -217,16 +223,16 @@ jxl_cleanup_frame_list (GList *frame_list) GList *frame = frame_list; while (frame != NULL) + { + if (frame->data != NULL) { - if (frame->data != NULL) - { - JxlFrame *jxl_frame = frame->data; - g_free (jxl_frame->buffer); - g_free (jxl_frame); - frame->data = NULL; - } - frame = frame->next; + JxlFrame *jxl_frame = frame->data; + g_free (jxl_frame->buffer); + g_free (jxl_frame); + frame->data = NULL; } + frame = frame->next; + } } void @@ -246,25 +252,23 @@ jxl_loader_get_is_animation (JxlLoader *loader) gconstpointer jxl_loader_get_frame_data (JxlLoader *loader, ChafaPixelType *pixel_type_out, - gint *width_out, gint *height_out, - gint *rowstride_out) + gint *width_out, gint *height_out, gint *rowstride_out) { g_return_val_if_fail (loader != NULL, NULL); const JxlFrame *frame = g_list_nth_data (loader->frames, loader->index); int num_channels; if (frame->have_alpha) - { - num_channels = 4; - *pixel_type_out = frame->is_premul - ? CHAFA_PIXEL_RGBA8_PREMULTIPLIED - : CHAFA_PIXEL_RGBA8_UNASSOCIATED; - } + { + num_channels = 4; + *pixel_type_out = frame->is_premul ? CHAFA_PIXEL_RGBA8_PREMULTIPLIED : + CHAFA_PIXEL_RGBA8_UNASSOCIATED; + } else - { - num_channels = 3; - *pixel_type_out = CHAFA_PIXEL_RGB8; - } + { + num_channels = 3; + *pixel_type_out = CHAFA_PIXEL_RGB8; + } *width_out = frame->width; *height_out = frame->height; @@ -277,8 +281,7 @@ gint jxl_loader_get_frame_delay (JxlLoader *loader) { g_return_val_if_fail (loader != NULL, 0); - return ((JxlFrame *)g_list_nth_data (loader->frames, loader->index)) - ->frame_duration; + return ((JxlFrame *) g_list_nth_data (loader->frames, loader->index))->frame_duration; } void diff --git a/tools/chafa/jxl-loader.h b/tools/chafa/jxl-loader.h index 23cf45b8..4c4eae8f 100644 --- a/tools/chafa/jxl-loader.h +++ b/tools/chafa/jxl-loader.h @@ -1,6 +1,6 @@ /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ -/* Copyright (C) 2018-2023 Hans Petter Jansson +/* Copyright (C) 2024 oupson * * This file is part of Chafa, a program that shows pictures on text terminals. * @@ -33,9 +33,8 @@ void jxl_loader_destroy (JxlLoader *loader); gboolean jxl_loader_get_is_animation (JxlLoader *loader); gconstpointer jxl_loader_get_frame_data (JxlLoader *loader, - ChafaPixelType *pixel_type_out, - gint *width_out, gint *height_out, - gint *rowstride_out); + ChafaPixelType *pixel_type_out, gint *width_out, + gint *height_out, gint *rowstride_out); gint jxl_loader_get_frame_delay (JxlLoader *loader); void jxl_loader_goto_first_frame (JxlLoader *loader);