Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support JPEG XL images #21

Merged
merged 20 commits into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 "============================================="
Expand All @@ -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 "============================================="
Expand Down Expand Up @@ -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
)
Expand Down
130 changes: 130 additions & 0 deletions build/patches/brotli-655.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Adrian Perez de Castro <[email protected]>
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 <[email protected]>

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}"
28 changes: 28 additions & 0 deletions build/patches/vips-2988.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Kleis Auke Wolthuizen <[email protected]>
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;
33 changes: 33 additions & 0 deletions build/patches/vips-libjxl-disable-concurrency.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Kleis Auke Wolthuizen <[email protected]>
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,
1 change: 1 addition & 0 deletions playground/samples/filter/duotone/sample.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
5 changes: 5 additions & 0 deletions playground/src/images/INFO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file added playground/src/images/owl.jxl
Binary file not shown.
3 changes: 2 additions & 1 deletion playground/src/playground-runner.html
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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');
Expand Down
1 change: 1 addition & 0 deletions playground/src/samples.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion playground/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]'
Expand Down
2 changes: 2 additions & 0 deletions test/unit/helpers.js
Original file line number Diff line number Diff line change
@@ -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');
Expand All @@ -24,6 +25,7 @@ export const mosaicFiles = [
];
export const testFiles = [
jpegFile,
jxlFile,
truncatedFile,
pngFile,
tifFile,
Expand Down
Binary file added test/unit/images/sample.jxl
Binary file not shown.
1 change: 1 addition & 0 deletions test/unit/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Loading