diff --git a/build.sh b/build.sh index 1c2c94893..655906927 100755 --- a/build.sh +++ b/build.sh @@ -116,7 +116,10 @@ VERSION_GLIB=2.73.3 # https://gitlab.gnome.org/GNOME/glib VERSION_EXPAT=2.4.8 # https://github.com/libexpat/libexpat VERSION_EXIF=0.6.24 # https://github.com/libexif/libexif VERSION_LCMS2=2.13.1 # https://github.com/mm2/Little-CMS +VERSION_HWY=1.0.1 # https://github.com/google/highway +VERSION_BROTLI=f4153a # https://github.com/google/brotli VERSION_JPEG=4.1.1 # https://github.com/mozilla/mozjpeg +VERSION_JXL=0.7rc # https://github.com/libjxl/libjxl VERSION_SPNG=0.7.2 # https://github.com/randy408/libspng VERSION_IMAGEQUANT=2.4.1 # https://github.com/lovell/libimagequant VERSION_CGIF=0.3.0 # https://github.com/dloebl/cgif @@ -252,6 +255,33 @@ test -f "$TARGET/lib/pkgconfig/lcms2.pc" || ( make install SUBDIRS='src include' ) +echo "=============================================" +echo "Compiling hwy" +echo "=============================================" +test -f "$TARGET/lib/pkgconfig/libhwy.pc" || ( + mkdir $DEPS/hwy + curl -Ls https://github.com/google/highway/archive/refs/tags/$VERSION_HWY.tar.gz | tar xzC $DEPS/hwy --strip-components=1 + cd $DEPS/hwy + emcmake cmake -B_build -H. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$TARGET -DBUILD_SHARED_LIBS=FALSE \ + -DBUILD_TESTING=FALSE -DHWY_ENABLE_CONTRIB=FALSE -DHWY_ENABLE_EXAMPLES=FALSE + make -C _build install +) + +echo "=============================================" +echo "Compiling brotli" +echo "=============================================" +test -f "$TARGET/lib/pkgconfig/libbrotlicommon.pc" || ( + mkdir $DEPS/brotli + curl -Ls https://github.com/google/brotli/archive/$VERSION_BROTLI.tar.gz | tar xzC $DEPS/brotli --strip-components=1 + cd $DEPS/brotli + # https://github.com/google/brotli/pull/655 + patch -p1 <$SOURCE_DIR/build/patches/brotli-655.patch + # Exclude internal dictionary, see: https://github.com/emscripten-core/emscripten/issues/9960 + emcmake cmake -B_build -H. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$TARGET -DBROTLI_DISABLE_TESTS=TRUE \ + -DCMAKE_C_FLAGS="$CFLAGS -DBROTLI_EXTERNAL_DICTIONARY_DATA" + make -C _build install +) + echo "=============================================" echo "Compiling jpeg" echo "=============================================" @@ -267,6 +297,24 @@ test -f "$TARGET/lib/pkgconfig/libjpeg.pc" || ( make -C _build install ) +echo "=============================================" +echo "Compiling jxl" +echo "=============================================" +test -f "$TARGET/lib/pkgconfig/libjxl.pc" || ( + mkdir $DEPS/jxl + curl -Ls https://github.com/libjxl/libjxl/archive/refs/tags/v$VERSION_JXL.tar.gz | tar xzC $DEPS/jxl --strip-components=1 + cd $DEPS/jxl + # Avoid bundling libpng + sed -i 's/JPEGXL_EMSCRIPTEN/& AND JPEGXL_BUNDLE_LIBPNG/' third_party/CMakeLists.txt + # CMake < 3.19 workaround, see: https://github.com/libjxl/libjxl/issues/1425 + sed -i 's/lcms2,INCLUDE_DIRECTORIES/lcms2,INTERFACE_INCLUDE_DIRECTORIES/' lib/jxl.cmake + emcmake cmake -B_build -H. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$TARGET -DCMAKE_FIND_ROOT_PATH=$TARGET \ + -DBUILD_SHARED_LIBS=FALSE -DBUILD_TESTING=FALSE -DJPEGXL_ENABLE_TOOLS=FALSE -DJPEGXL_ENABLE_DOXYGEN=FALSE \ + -DJPEGXL_ENABLE_MANPAGES=FALSE -DJPEGXL_ENABLE_EXAMPLES=FALSE -DJPEGXL_ENABLE_SJPEG=FALSE -DJPEGXL_ENABLE_SKCMS=FALSE \ + -DJPEGXL_BUNDLE_LIBPNG=FALSE -DJPEGXL_FORCE_SYSTEM_BROTLI=TRUE -DJPEGXL_FORCE_SYSTEM_LCMS2=TRUE -DJPEGXL_FORCE_SYSTEM_HWY=TRUE + make -C _build install +) + echo "=============================================" echo "Compiling spng" echo "=============================================" @@ -349,12 +397,14 @@ test -f "$TARGET/lib/pkgconfig/vips.pc" || ( patch -p1 <$SOURCE_DIR/build/patches/vips-remove-orc.patch patch -p1 <$SOURCE_DIR/build/patches/vips-1492-emscripten.patch patch -p1 <$SOURCE_DIR/build/patches/vips-disable-nls.patch + patch -p1 <$SOURCE_DIR/build/patches/vips-libjxl-disable-concurrency.patch + patch -p1 <$SOURCE_DIR/build/patches/vips-2988.patch #patch -p1 <$SOURCE_DIR/build/patches/vips-1492-profiler.patch # Disable building C++ bindings, man pages, gettext po files, tools, and (fuzz-)tests sed -i'.bak' "/subdir('cplusplus')/{N;N;N;N;N;d;}" meson.build meson setup _build --prefix=$TARGET --cross-file=$MESON_CROSS --default-library=static --buildtype=release \ -Ddeprecated=false -Dintrospection=false -Dauto_features=disabled -Dcgif=enabled -Dexif=enabled \ - -Dimagequant=enabled -Djpeg=enabled -Dlcms=enabled -Dspng=enabled -Dtiff=enabled -Dwebp=enabled \ + -Dimagequant=enabled -Djpeg=enabled -Djpeg-xl=enabled -Dlcms=enabled -Dspng=enabled -Dtiff=enabled -Dwebp=enabled \ -Dnsgif=true -Dppm=true -Danalyze=true -Dradiance=true ninja -C _build install ) diff --git a/build/patches/brotli-655.patch b/build/patches/brotli-655.patch new file mode 100644 index 000000000..334d281f2 --- /dev/null +++ b/build/patches/brotli-655.patch @@ -0,0 +1,130 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Adrian Perez de Castro +Date: Mon, 7 Sep 2020 12:14:22 +0300 +Subject: [PATCH 1/1] CMake: Allow using BUILD_SHARED_LIBS to choose static/shared + libs + +By convention projects using CMake which can build either static or +shared libraries use a BUILD_SHARED_LIBS flag to allow selecting between +both: the add_library() command automatically switches between both using +this variable when the library kind is not passed to add_library(). It +is also usual to expose the BUILD_SHARED_LIBS as an user-facing setting +with the option() command. + +This way, the following will both work as expected: + + % cmake -DBUILD_SHARED_LIBS=OFF ... + % cmake -DBUILS_SHARED_LIBS=ON ... + +This is helpful for distributions which need (or want) to build only +static libraries. + +Upstream-Status: Submitted [https://github.com/google/brotli/pull/655] + +[Fix: ensure libraries are still installed on Emscripten] +Signed-off-by: Kleis Auke Wolthuizen + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 1111111..2222222 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -11,6 +11,8 @@ cmake_minimum_required(VERSION 2.8.6) + cmake_policy(SET CMP0048 NEW) + project(brotli C) + ++option(BUILD_SHARED_LIBS "Build shared libraries" ON) ++ + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to Release as none was specified.") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) +@@ -142,10 +144,6 @@ set(BROTLI_LIBRARIES_CORE brotlienc brotlidec brotlicommon) + set(BROTLI_LIBRARIES ${BROTLI_LIBRARIES_CORE} ${LIBM_LIBRARY}) + mark_as_advanced(BROTLI_LIBRARIES) + +-set(BROTLI_LIBRARIES_CORE_STATIC brotlienc-static brotlidec-static brotlicommon-static) +-set(BROTLI_LIBRARIES_STATIC ${BROTLI_LIBRARIES_CORE_STATIC} ${LIBM_LIBRARY}) +-mark_as_advanced(BROTLI_LIBRARIES_STATIC) +- + if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + add_definitions(-DOS_LINUX) + elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") +@@ -166,29 +164,25 @@ transform_sources_list("scripts/sources.lst" "${CMAKE_CURRENT_BINARY_DIR}/source + include("${CMAKE_CURRENT_BINARY_DIR}/sources.lst.cmake") + + if(BROTLI_EMSCRIPTEN) +- set(BROTLI_SHARED_LIBS "") +-else() +- set(BROTLI_SHARED_LIBS brotlicommon brotlidec brotlienc) +- add_library(brotlicommon SHARED ${BROTLI_COMMON_C}) +- add_library(brotlidec SHARED ${BROTLI_DEC_C}) +- add_library(brotlienc SHARED ${BROTLI_ENC_C}) ++ set(BUILD_SHARED_LIBS OFF) + endif() + +-set(BROTLI_STATIC_LIBS brotlicommon-static brotlidec-static brotlienc-static) +-add_library(brotlicommon-static STATIC ${BROTLI_COMMON_C}) +-add_library(brotlidec-static STATIC ${BROTLI_DEC_C}) +-add_library(brotlienc-static STATIC ${BROTLI_ENC_C}) ++add_library(brotlicommon ${BROTLI_COMMON_C}) ++add_library(brotlidec ${BROTLI_DEC_C}) ++add_library(brotlienc ${BROTLI_ENC_C}) + + # Older CMake versions does not understand INCLUDE_DIRECTORIES property. + include_directories(${BROTLI_INCLUDE_DIRS}) + +-foreach(lib IN LISTS BROTLI_SHARED_LIBS) +- target_compile_definitions(${lib} PUBLIC "BROTLI_SHARED_COMPILATION" ) +- string(TOUPPER "${lib}" LIB) +- set_target_properties (${lib} PROPERTIES DEFINE_SYMBOL "${LIB}_SHARED_COMPILATION") +-endforeach() ++if(BUILD_SHARED_LIBS) ++ foreach(lib brotlicommon brotlidec brotlienc) ++ target_compile_definitions(${lib} PUBLIC "BROTLI_SHARED_COMPILATION" ) ++ string(TOUPPER "${lib}" LIB) ++ set_target_properties (${lib} PROPERTIES DEFINE_SYMBOL "${LIB}_SHARED_COMPILATION") ++ endforeach() ++endif() + +-foreach(lib IN LISTS BROTLI_SHARED_LIBS BROTLI_STATIC_LIBS) ++foreach(lib brotlicommon brotlidec brotlienc) + target_link_libraries(${lib} ${LIBM_LIBRARY}) + set_property(TARGET ${lib} APPEND PROPERTY INCLUDE_DIRECTORIES ${BROTLI_INCLUDE_DIRS}) + set_target_properties(${lib} PROPERTIES +@@ -205,9 +199,6 @@ target_link_libraries(brotlidec brotlicommon) + target_link_libraries(brotlienc brotlicommon) + endif() + +-target_link_libraries(brotlidec-static brotlicommon-static) +-target_link_libraries(brotlienc-static brotlicommon-static) +- + # For projects stuck on older versions of CMake, this will set the + # BROTLI_INCLUDE_DIRS and BROTLI_LIBRARIES variables so they still + # have a relatively easy way to use Brotli: +@@ -221,7 +212,7 @@ endif() + + # Build the brotli executable + add_executable(brotli ${BROTLI_CLI_C}) +-target_link_libraries(brotli ${BROTLI_LIBRARIES_STATIC}) ++target_link_libraries(brotli ${BROTLI_LIBRARIES}) + + # Installation + if(NOT BROTLI_BUNDLED_MODE) +@@ -230,17 +221,8 @@ if(NOT BROTLI_BUNDLED_MODE) + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + ) + +- if(NOT BROTLI_EMSCRIPTEN) +- install( +- TARGETS ${BROTLI_LIBRARIES_CORE} +- ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" +- LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" +- RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +- ) +- endif() # BROTLI_EMSCRIPTEN +- + install( +- TARGETS ${BROTLI_LIBRARIES_CORE_STATIC} ++ TARGETS ${BROTLI_LIBRARIES_CORE} + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" diff --git a/build/patches/vips-2988.patch b/build/patches/vips-2988.patch new file mode 100644 index 000000000..1ba256dbe --- /dev/null +++ b/build/patches/vips-2988.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kleis Auke Wolthuizen +Date: Sun, 14 Aug 2022 16:04:34 +0200 +Subject: [PATCH 1/1] jxlsave: correctly mark frame as last + +It's required to close the input, otherwise the encoder can't +know what the last frame is, resulting in an improper codestream. + +Resolves: #2987. + +Upstream-Status: Accepted [https://github.com/libvips/libvips/commit/34427d83a028690b098fd1f67e956f78bc724e22] + +diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c +index 1111111..2222222 100644 +--- a/libvips/foreign/jxlsave.c ++++ b/libvips/foreign/jxlsave.c +@@ -438,6 +438,11 @@ vips_foreign_save_jxl_build( VipsObject *object ) + return( -1 ); + } + ++ /* This function must be called after the final frame and/or box, ++ * otherwise the codestream will not be encoded correctly. ++ */ ++ JxlEncoderCloseInput( jxl->encoder ); ++ + do { + uint8_t *out; + size_t avail_out; diff --git a/build/patches/vips-libjxl-disable-concurrency.patch b/build/patches/vips-libjxl-disable-concurrency.patch new file mode 100644 index 000000000..30419213b --- /dev/null +++ b/build/patches/vips-libjxl-disable-concurrency.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kleis Auke Wolthuizen +Date: Sun, 14 Aug 2022 12:12:00 +0200 +Subject: [PATCH 1/1] Disable concurrency in jxl{load,save} + +Upstream-Status: Inappropriate [Emscripten specific] + +diff --git a/libvips/foreign/jxlload.c b/libvips/foreign/jxlload.c +index 1111111..2222222 100644 +--- a/libvips/foreign/jxlload.c ++++ b/libvips/foreign/jxlload.c +@@ -157,7 +157,7 @@ vips_foreign_load_jxl_build( VipsObject *object ) + #endif /*DEBUG*/ + + jxl->runner = JxlThreadParallelRunnerCreate( NULL, +- vips_concurrency_get() ); ++ 0 ); + jxl->decoder = JxlDecoderCreate( NULL ); + + if( JxlDecoderSubscribeEvents( jxl->decoder, +diff --git a/libvips/foreign/jxlsave.c b/libvips/foreign/jxlsave.c +index 1111111..2222222 100644 +--- a/libvips/foreign/jxlsave.c ++++ b/libvips/foreign/jxlsave.c +@@ -253,7 +253,7 @@ vips_foreign_save_jxl_build( VipsObject *object ) + jxl->lossless = TRUE; + + jxl->runner = JxlThreadParallelRunnerCreate( NULL, +- vips_concurrency_get() ); ++ 0 ); + jxl->encoder = JxlEncoderCreate( NULL ); + + if( JxlEncoderSetParallelRunner( jxl->encoder, diff --git a/playground/samples/filter/duotone/sample.js b/playground/samples/filter/duotone/sample.js index cca2f65ca..8c06fb212 100644 --- a/playground/samples/filter/duotone/sample.js +++ b/playground/samples/filter/duotone/sample.js @@ -20,6 +20,7 @@ lut = lut.colourspace(vips.Interpretation.srgb/* 'srgb' */, { // Image source: https://www.flickr.com/photos/jasonidzerda/3987784466 let im = vips.Image.newFromFile('owl.jpg'); // let im = vips.Image.newFromFile('owl.tif'); +// let im = vips.Image.newFromFile('owl.jxl'); // let im = vips.Image.newFromFile('transparency_demo.png'); // let im = vips.Image.newFromFile('banana.webp', { n: -1 }); // let im = vips.Image.newFromFile('banana.gif', { n: -1 }); diff --git a/playground/src/images/INFO.md b/playground/src/images/INFO.md index 331d85860..4411e4640 100644 --- a/playground/src/images/INFO.md +++ b/playground/src/images/INFO.md @@ -15,6 +15,11 @@ vips copy owl.jpg owl.tif[compression=jpeg,strip=true] vips copy owl.jpg owl.webp[strip=true] ``` +[`owl.jxl`](owl.jxl): +```bash +vips copy owl.jpg owl.jxl +``` + [`transparency_demo.png`](transparency_demo.png): ``` https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png diff --git a/playground/src/images/owl.jxl b/playground/src/images/owl.jxl new file mode 100644 index 000000000..3e5004b13 Binary files /dev/null and b/playground/src/images/owl.jxl differ diff --git a/playground/src/playground-runner.html b/playground/src/playground-runner.html index 474c282a8..0c186a553 100644 --- a/playground/src/playground-runner.html +++ b/playground/src/playground-runner.html @@ -67,7 +67,7 @@ // module.ENV.VIPS_INFO = '1'; // module.ENV.VIPS_LEAK = '1'; - for (const image of ['owl.jpg', 'owl.tif', 'owl.webp', 'banana.webp', 'banana.gif', 'transparency_demo.png']) + for (const image of ['owl.jpg', 'owl.tif', 'owl.webp', 'owl.jxl', 'banana.webp', 'banana.gif', 'transparency_demo.png']) module.FS.createPreloadedFile('/', image, 'assets/images/' + image, true, false); }, postRun: (module) => { @@ -90,6 +90,7 @@ console.log('Open files:'.padEnd(pad), module.Stats.files()); console.log('JPEG support:'.padEnd(pad), have('jpegload') ? 'yes' : 'no'); + console.log('JPEG XL support:'.padEnd(pad), have('jxlload') ? 'yes' : 'no'); console.log('PNG support:'.padEnd(pad), have('pngload') ? 'yes' : 'no'); console.log('TIFF support:'.padEnd(pad), have('tiffload') ? 'yes' : 'no'); console.log('WebP support:'.padEnd(pad), have('webpload') ? 'yes' : 'no'); diff --git a/playground/src/samples.js b/playground/src/samples.js index ed2f8cbac..1d5bda311 100644 --- a/playground/src/samples.js +++ b/playground/src/samples.js @@ -1,6 +1,7 @@ import './images/owl.webp'; import './images/owl.tif'; import './images/owl.jpg'; +import './images/owl.jxl'; import './images/banana.webp'; import './images/banana.gif'; import './images/transparency_demo.png'; diff --git a/playground/webpack.config.js b/playground/webpack.config.js index 6271c29a4..9664228a9 100644 --- a/playground/webpack.config.js +++ b/playground/webpack.config.js @@ -32,7 +32,7 @@ module.exports = { ] }, { - test: /\.(jpe?g|png|gif|tiff?|webp|svg)$/, + test: /\.(jpe?g|png|gif|tiff?|webp|jxl|svg)$/, type: 'asset/resource', generator: { filename: 'assets/images/[name][ext][query]' diff --git a/test/unit/helpers.js b/test/unit/helpers.js index 133ecedd6..408842131 100644 --- a/test/unit/helpers.js +++ b/test/unit/helpers.js @@ -1,6 +1,7 @@ 'use strict'; export const jpegFile = getPath('sample.jpg'); +export const jxlFile = getPath('sample.jxl'); export const truncatedFile = getPath('truncated.jpg'); export const pngFile = getPath('sample.png'); export const tifFile = getPath('sample.tif'); @@ -24,6 +25,7 @@ export const mosaicFiles = [ ]; export const testFiles = [ jpegFile, + jxlFile, truncatedFile, pngFile, tifFile, diff --git a/test/unit/images/sample.jxl b/test/unit/images/sample.jxl new file mode 100644 index 000000000..17ff8916e Binary files /dev/null and b/test/unit/images/sample.jxl differ diff --git a/test/unit/index.html b/test/unit/index.html index b964f71a8..8b04a9e50 100644 --- a/test/unit/index.html +++ b/test/unit/index.html @@ -132,6 +132,7 @@ console.log('Open files:'.padEnd(pad), module.Stats.files()); console.log('JPEG support:'.padEnd(pad), have('jpegload') ? 'yes' : 'no'); + console.log('JPEG XL support:'.padEnd(pad), have('jxlload') ? 'yes' : 'no'); console.log('PNG support:'.padEnd(pad), have('pngload') ? 'yes' : 'no'); console.log('TIFF support:'.padEnd(pad), have('tiffload') ? 'yes' : 'no'); console.log('WebP support:'.padEnd(pad), have('webpload') ? 'yes' : 'no'); diff --git a/test/unit/test_foreign.js b/test/unit/test_foreign.js index 021b263c3..710fa3fcc 100644 --- a/test/unit/test_foreign.js +++ b/test/unit/test_foreign.js @@ -14,6 +14,7 @@ describe('foreign', () => { const fileLoaders = { jpegload: file => vips.Image.jpegload(file), + jxlload: file => vips.Image.jxlload(file), pngload: file => vips.Image.pngload(file), webpload: file => vips.Image.webpload(file), tiffload: file => vips.Image.tiffload(file), @@ -23,6 +24,7 @@ describe('foreign', () => { const bufferLoaders = { jpegload_buffer: buffer => vips.Image.jpegloadBuffer(buffer), + jxlload_buffer: buffer => vips.Image.jxlloadBuffer(buffer), pngload_buffer: buffer => vips.Image.pngloadBuffer(buffer), webpload_buffer: buffer => vips.Image.webploadBuffer(buffer), tiffload_buffer: buffer => vips.Image.tiffloadBuffer(buffer), @@ -31,6 +33,7 @@ describe('foreign', () => { const bufferSavers = { jpegsave_buffer: (im, opts) => im.jpegsaveBuffer(opts), + jxlsave_buffer: (im, opts) => im.jxlsaveBuffer(opts), pngsave_buffer: (im, opts) => im.pngsaveBuffer(opts), tiffsave_buffer: (im, opts) => im.tiffsaveBuffer(opts), webpsave_buffer: (im, opts) => im.webpsaveBuffer(opts), @@ -855,6 +858,74 @@ describe('foreign', () => { saveBufferTempfile('radsave_buffer', '.hdr', rad, 0); }); + it('jxlload', function () { + // Needs JPEG XL support + if (!Helpers.have('jxlload')) { + return this.skip(); + } + + const jxlValid = (im) => { + const a = im.getpoint(10, 10); + // the delta might need to be adjusted up as new + // libjxl versions are released + Helpers.assertAlmostEqualObjects(a, [157, 129, 90], 0); + expect(im.width).to.equal(290); + expect(im.height).to.equal(442); + expect(im.bands).to.equal(4); + }; + + fileLoader('jxlload', Helpers.jxlFile, jxlValid); + bufferLoader('jxlload_buffer', Helpers.jxlFile, jxlValid); + }); + + it('jxlsave', function () { + // Needs JPEG XL support + if (!Helpers.have('jxlsave')) { + return this.skip(); + } + + // save and load with an icc profile + saveLoadBuffer('jxlsave_buffer', 'jxlload_buffer', + colour, 120); + + // with no icc profile + const noProfile = colour.copy(); + noProfile.remove('icc-profile-data'); + saveLoadBuffer('jxlsave_buffer', 'jxlload_buffer', + noProfile, 120); + + // scrgb mode + const scrgb = colour.colourspace('scrgb'); + saveLoadBuffer('jxlsave_buffer', 'jxlload_buffer', + scrgb, 120); + + // scrgb mode, no profile + const scrgbNoProfile = scrgb.copy(); + scrgbNoProfile.remove('icc-profile-data'); + saveLoadBuffer('jxlsave_buffer', 'jxlload_buffer', + scrgbNoProfile, 120); + + // 16-bit mode + const rgb16 = colour.colourspace('rgb16'); + saveLoadBuffer('jxlsave_buffer', 'jxlload_buffer', + rgb16, 30000); + + // repeat for lossless mode + saveLoadBuffer('jxlsave_buffer', 'jxlload_buffer', + colour, 0, { lossless: true }); + saveLoadBuffer('jxlsave_buffer', 'jxlload_buffer', + noProfile, 0, { lossless: true }); + saveLoadBuffer('jxlsave_buffer', 'jxlload_buffer', + scrgb, 0, { lossless: true }); + saveLoadBuffer('jxlsave_buffer', 'jxlload_buffer', + scrgbNoProfile, 0, { lossless: true }); + + // lossy should be much smaller than lossless + const lossy = colour.jxlsaveBuffer(); + const lossless = colour.jxlsaveBuffer({ lossless: true }); + expect(lossy.byteLength).to.be.below(lossless.byteLength / 5); + }); + it('fail_on', function () { // csvload should spot trunc correctly const target = vips.Target.newToMemory();