diff --git a/configure.ac b/configure.ac index 75f25f2e..e0f860a7 100644 --- a/configure.ac +++ b/configure.ac @@ -766,6 +766,19 @@ m4_divert_pop([INIT_PREPARE])dnl WM_IMGFMT_CHECK_JPEG +dnl JXL Support +dnl ============ +m4_divert_push([INIT_PREPARE])dnl +AC_ARG_ENABLE([jxl], + [AS_HELP_STRING([--disable-jxl], [disable JXL support through libjxl])], + [AS_CASE(["$enableval"], + [yes|no], [], + [AC_MSG_ERROR([bad value $enableval for --enable-jxl])] )], + [enable_jxl=auto]) +m4_divert_pop([INIT_PREPARE])dnl +WM_IMGFMT_CHECK_JXL + + dnl GIF Support dnl ============ m4_divert_push([INIT_PREPARE])dnl diff --git a/doc/build/Compilation.texi b/doc/build/Compilation.texi index 2265e4fd..0046384a 100644 --- a/doc/build/Compilation.texi +++ b/doc/build/Compilation.texi @@ -204,6 +204,11 @@ Note that if you don't have it, @command{configure} will issue a big warning in this is because JPEG images are often used in themes and for background images so you probably want this format supported. +@item @emph{libjxl} 0.7.0 or newer + +For @emph{JXL} image support, +@uref{https://github.com/libjxl/libjxl} + @item @emph{libgif} 2.2 or @emph{libungif} For @emph{GIF} image support, @@ -477,6 +482,9 @@ Disable GIF support in @emph{WRaster} library; when enabled use @file{libgif} or @item --disable-jpeg Disable JPEG support in @emph{WRaster} library; when enabled use @file{libjpeg}. +@item --disable-jxl +Disable JPEG-XL support in @emph{WRaster} library; when enabled use @file{libjxl}. + @item --without-libbsd Refuse use of the @file{libbsd} compatibility library in @emph{WINGs} utility library, even if your system provides it. diff --git a/m4/wm_imgfmt_check.m4 b/m4/wm_imgfmt_check.m4 index 88939f48..dce5b0d5 100644 --- a/m4/wm_imgfmt_check.m4 +++ b/m4/wm_imgfmt_check.m4 @@ -113,6 +113,37 @@ AC_DEFUN_ONCE([WM_IMGFMT_CHECK_JPEG], ]) dnl AC_DEFUN +# WM_IMGFMT_CHECK_JXL +# ------------------- +# +# Check for JXL (JPEG XL) file support through 'libjxl' +# The check depends on variable 'enable_jxl' being either: +# yes - detect, fail if not found +# no - do not detect, disable support +# auto - detect, disable if not found +# +# When found, append appropriate stuff in GFXLIBS, and append info to +# the variable 'supported_gfx' +# When not found, append info to variable 'unsupported' +AC_DEFUN_ONCE([WM_IMGFMT_CHECK_JXL], +[WM_LIB_CHECK([JXL], [-ljxl], [JxlDecoderCreate], [$XLFLAGS $XLIBS], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM( + [@%:@include +@%:@include ], + [ JxlDecoder* dec = JxlDecoderCreate(NULL); + JxlDecoderDestroy(dec);])], + [], + [AS_ECHO([failed]) + AS_ECHO(["$as_me: error: found $CACHEVAR but cannot compile header"]) + AS_ECHO(["$as_me: error: - does header 'jxl/decode.h' exists? (is package 'libjxl-dev' missing?)"]) + AS_ECHO(["$as_me: error: - version of header is not supported? (report to dev team)"]) + AC_MSG_ERROR([JXL library is not usable, cannot continue])]) + ], + [supported_gfx], [GFXLIBS])dnl +]) dnl AC_DEFUN + + # WM_IMGFMT_CHECK_PNG # ------------------- # diff --git a/wrlib/Makefile.am b/wrlib/Makefile.am index a82bb9a0..68f045ce 100644 --- a/wrlib/Makefile.am +++ b/wrlib/Makefile.am @@ -58,6 +58,10 @@ libwraster_la_SOURCES += load_jpeg.c libwraster_la_SOURCES += save_jpeg.c endif +if USE_JXL +libwraster_la_SOURCES += load_jxl.c +endif + if USE_PNG libwraster_la_SOURCES += load_png.c libwraster_la_SOURCES += save_png.c diff --git a/wrlib/imgformat.h b/wrlib/imgformat.h index 52aa93df..209b375f 100644 --- a/wrlib/imgformat.h +++ b/wrlib/imgformat.h @@ -38,12 +38,13 @@ typedef enum { IM_PPM = 4, IM_JPEG = 5, IM_GIF = 6, - IM_WEBP = 7 + IM_WEBP = 7, + IM_JXL = 8 } WRImgFormat; /* How many image types we have. */ /* Increase this when adding new image types! */ -#define IM_TYPES 7 +#define IM_TYPES 8 /* * Function for Loading in a specific format @@ -64,6 +65,10 @@ RImage *RLoadPNG(RContext *context, const char *file); RImage *RLoadJPEG(const char *file); #endif +#ifdef USE_JXL +RImage *RLoadJXL(const char *file); +#endif + #ifdef USE_GIF RImage *RLoadGIF(const char *file, int index); #endif diff --git a/wrlib/load.c b/wrlib/load.c index 1f94f0e4..ef4e3b94 100644 --- a/wrlib/load.c +++ b/wrlib/load.c @@ -3,7 +3,7 @@ * Raster graphics library * * Copyright (c) 1997-2003 Alfredo K. Kojima - * Copyright (c) 2014-2021 Window Maker Team + * Copyright (c) 2014-2025 Window Maker Team * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -93,6 +93,9 @@ char **RSupportedFileFormats(void) #ifdef USE_JPEG tmp[i++] = "JPEG"; #endif +#ifdef USE_JXL + tmp[i++] = "JXL"; +#endif #ifdef USE_GIF tmp[i++] = "GIF"; #endif @@ -219,6 +222,12 @@ RImage *RLoadImage(RContext *context, const char *file, int index) break; #endif /* USE_JPEG */ +#ifdef USE_JXL + case IM_JXL: + image = RLoadJXL(file); + break; +#endif /* USE_JXL */ + #ifdef USE_GIF case IM_GIF: image = RLoadGIF(file, index); @@ -305,6 +314,11 @@ char *RGetImageFileFormat(const char *file) return "JPEG"; #endif /* USE_JPEG */ +#ifdef USE_JXL + case IM_JXL: + return "JXL"; +#endif /* USE_JXL */ + #ifdef USE_GIF case IM_GIF: return "GIF"; @@ -377,6 +391,13 @@ static WRImgFormat identFile(const char *path) if (buffer[0] == 0xff && buffer[1] == 0xd8) return IM_JPEG; + /* check for JXL */ + if ((buffer[0] == 0xff && buffer[1] == 0x0a) || /* naked codestream */ + (buffer[0] == 0x00 && buffer[1] == 0x00 && buffer[2] == 0x00 && buffer[3] == 0x0c && /* container format */ + buffer[4] == 0x4a && buffer[5] == 0x58 && buffer[6] == 0x4c && buffer[7] == 0x20 && + buffer[8] == 0x0d && buffer[9] == 0x0a && buffer[10] == 0x87 && buffer[11] == 0x0a)) + return IM_JXL; + /* check for GIF */ if (buffer[0] == 'G' && buffer[1] == 'I' && buffer[2] == 'F' && buffer[3] == '8' && (buffer[4] == '7' || buffer[4] == '9') && buffer[5] == 'a') diff --git a/wrlib/load_jxl.c b/wrlib/load_jxl.c new file mode 100644 index 00000000..c62ad49e --- /dev/null +++ b/wrlib/load_jxl.c @@ -0,0 +1,211 @@ +/* load_jxl.c - load JXL (JPEG XL) image from file + * + * Raster graphics library + * + * Copyright (c) 2025 Window Maker Team + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include + +#ifdef USE_JXL + +#include +#include +#include +#include + +#include + +#include "wraster.h" +#include "imgformat.h" +#include "wr_i18n.h" + +static unsigned char *do_read_file(const char *filename, size_t *size) +{ + FILE *file; + struct stat st; + unsigned char *data; + + if (stat(filename, &st) != 0) { + RErrorCode = RERR_OPEN; + return NULL; + } + + file = fopen(filename, "rb"); + if (!file) { + RErrorCode = RERR_OPEN; + return NULL; + } + + *size = st.st_size; + data = malloc(*size); + if (!data) { + RErrorCode = RERR_NOMEMORY; + fclose(file); + return NULL; + } + + if (fread(data, 1, *size, file) != *size) { + RErrorCode = RERR_READ; + free(data); + fclose(file); + return NULL; + } + + fclose(file); + return data; +} + +RImage *RLoadJXL(const char *file) +{ + RImage *image = NULL; + unsigned char *data = NULL, *pixels = NULL; + size_t size; + JxlDecoder *dec = NULL; + JxlDecoderStatus status; + JxlBasicInfo info; + JxlPixelFormat format; + size_t buffer_size; + int width = 0, height = 0; + int has_alpha = 0; + + /* Load file data */ + data = do_read_file(file, &size); + if (!data) + return NULL; + + /* Create decoder */ + dec = JxlDecoderCreate(NULL); + if (!dec) { + RErrorCode = RERR_NOMEMORY; + goto error; + } + + /* Subscribe to basic info and full image */ + if (JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE) != JXL_DEC_SUCCESS) { + RErrorCode = RERR_BADIMAGEFILE; + goto error; + } + + /* Set input data */ + if (JxlDecoderSetInput(dec, data, size) != JXL_DEC_SUCCESS) { + RErrorCode = RERR_BADIMAGEFILE; + goto error; + } + + /* Process events */ + for (;;) { + status = JxlDecoderProcessInput(dec); + + if (status == JXL_DEC_ERROR) { + RErrorCode = RERR_BADIMAGEFILE; + goto error; + } + + if (status == JXL_DEC_NEED_MORE_INPUT) { + RErrorCode = RERR_BADIMAGEFILE; + goto error; + } + + if (status == JXL_DEC_BASIC_INFO) { + if (JxlDecoderGetBasicInfo(dec, &info) != JXL_DEC_SUCCESS) { + RErrorCode = RERR_BADIMAGEFILE; + goto error; + } + + width = info.xsize; + height = info.ysize; + + if (width < 1 || height < 1) { + RErrorCode = RERR_BADIMAGEFILE; + goto error; + } + + /* Check if image has alpha channel */ + has_alpha = (info.alpha_bits > 0); + + /* Set pixel format based on alpha channel presence */ + if (has_alpha) { + format.num_channels = 4; /* RGBA */ + } else { + format.num_channels = 3; /* RGB */ + } + format.data_type = JXL_TYPE_UINT8; + format.endianness = JXL_NATIVE_ENDIAN; + format.align = 0; + + } else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) { + /* Allocate image with or without alpha */ + image = RCreateImage(width, height, has_alpha ? True : False); + if (!image) { + RErrorCode = RERR_NOMEMORY; + goto error; + } + + /* Determine buffer size */ + if (JxlDecoderImageOutBufferSize(dec, &format, &buffer_size) != JXL_DEC_SUCCESS) { + RErrorCode = RERR_BADIMAGEFILE; + goto error; + } + + /* Allocate buffer */ + pixels = malloc(buffer_size); + if (!pixels) { + RErrorCode = RERR_NOMEMORY; + goto error; + } + + /* Set output buffer */ + if (JxlDecoderSetImageOutBuffer(dec, &format, pixels, buffer_size) != JXL_DEC_SUCCESS) { + RErrorCode = RERR_BADIMAGEFILE; + goto error; + } + } else if (status == JXL_DEC_FULL_IMAGE) { + /* Image is ready, copy data directly for RGB or RGBA */ + if (has_alpha) { + /* RGBA format - copy directly */ + memcpy(image->data, pixels, width * height * 4); + } else { + /* RGB format - copy directly */ + memcpy(image->data, pixels, width * height * 3); + } + break; + } else if (status == JXL_DEC_SUCCESS) { + /* All done */ + break; + } + } + + free(data); + free(pixels); + JxlDecoderDestroy(dec); + return image; + +error: + if (data) + free(data); + if (pixels) + free(pixels); + if (image) + RReleaseImage(image); + if (dec) + JxlDecoderDestroy(dec); + return NULL; +} + +#endif /* USE_JXL */ \ No newline at end of file diff --git a/wrlib/po/Makefile.am b/wrlib/po/Makefile.am index 4320e751..647294b6 100644 --- a/wrlib/po/Makefile.am +++ b/wrlib/po/Makefile.am @@ -29,6 +29,7 @@ POTFILES = \ $(top_srcdir)/wrlib/load_ppm.c \ $(top_srcdir)/wrlib/load_gif.c \ $(top_srcdir)/wrlib/load_jpeg.c \ + $(top_srcdir)/wrlib/load_jxl.c \ $(top_srcdir)/wrlib/load_png.c \ $(top_srcdir)/wrlib/load_tiff.c \ $(top_srcdir)/wrlib/load_xpm.c \