diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 7afafe07c..a5ddfb73d 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -175,6 +175,14 @@ jobs: if: steps.build-cache.outputs.cache-hit != 'true' run: "& winbuild\\build\\build_dep_libimagequant.cmd" + - name: Build dependencies / highway + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_highway.cmd" + + - name: Build dependencies / libjxl + if: steps.build-cache.outputs.cache-hit != 'true' + run: "& winbuild\\build\\build_dep_libjxl.cmd" + # Raqm dependencies - name: Build dependencies / HarfBuzz if: steps.build-cache.outputs.cache-hit != 'true' diff --git a/checks/check_wheel.py b/checks/check_wheel.py index 9d5574630..ef3981149 100644 --- a/checks/check_wheel.py +++ b/checks/check_wheel.py @@ -8,7 +8,15 @@ from PIL import features def test_wheel_modules() -> None: - expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp", "avif"} + expected_modules = { + "pil", + "tkinter", + "freetype2", + "littlecms2", + "webp", + "avif", + "jpegxl", + } if sys.platform == "win32": # tkinter is not available in cibuildwheel installed CPython on Windows @@ -19,15 +27,17 @@ def test_wheel_modules() -> None: except ImportError: expected_modules.remove("tkinter") + expected_modules.remove("jpegxl") + # libavif is not available on Windows for ARM64 architectures if platform.machine() == "ARM64": expected_modules.remove("avif") elif sys.platform == "ios": # tkinter is not available on iOS - expected_modules.remove("tkinter") - elif os.environ.get("AUDITWHEEL_POLICY") != "manylinux2014": - expected_modules.add("jpegxl") + expected_modules -= {"tkinter", "jpegxl"} + elif os.environ.get("AUDITWHEEL_POLICY") == "manylinux2014": + expected_modules.remove("jpegxl") assert set(features.get_supported_modules()) == expected_modules diff --git a/setup.py b/setup.py index 43a0ffdf2..fbb6df17c 100644 --- a/setup.py +++ b/setup.py @@ -445,6 +445,7 @@ class pil_build_ext(build_ext): libraries: list[str] | list[str | bool | None], define_macros: list[tuple[str, str | None]] | None = None, sources: list[str] | None = None, + args: list[str] | None = None, ) -> None: for extension in self.extensions: if extension.name == name: @@ -453,6 +454,8 @@ class pil_build_ext(build_ext): extension.define_macros += define_macros if sources is not None: extension.sources += sources + if args is not None: + extension.extra_compile_args += args if FUZZING_BUILD: extension.language = "c++" extension.extra_link_args = ["--stdlib=libc++"] @@ -782,8 +785,8 @@ class pil_build_ext(build_ext): if feature.want("jpegxl"): _dbg("Looking for jpegxl") - if _find_include_file(self, "jxl/encode.h") and _find_include_file( - self, "jxl/decode.h" + if _find_include_file(self, "jxl/decode.h") and _find_include_file( + self, "jxl/thread_parallel_runner.h" ): if _find_library_file(self, "jxl") and _find_library_file( self, "jxl_threads" @@ -1017,7 +1020,11 @@ class pil_build_ext(build_ext): jpegxl = feature.get("jpegxl") if isinstance(jpegxl, str): libs = [jpegxl, jpegxl + "_threads"] - self._update_extension("PIL._jpegxl", libs) + args: list[str] | None = None + if sys.platform == "win32": + libs.extend(["brotlicommon", "brotlidec", "brotlienc", "hwy"]) + args = ["-DJXL_STATIC_DEFINE"] + self._update_extension("PIL._jpegxl", libs, args=args) else: self._remove_extension("PIL._jpegxl") diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 30fe26d14..1ceabd970 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -117,7 +117,9 @@ V = { "FREETYPE": "2.14.1", "FRIBIDI": "1.0.16", "HARFBUZZ": "12.2.0", + "HIGHWAY": "1.3.0", "JPEGTURBO": "3.1.3", + "JPEGXL": "0.11.1", "LCMS2": "2.17", "LIBAVIF": "1.3.0", "LIBIMAGEQUANT": "4.4.1", @@ -255,7 +257,10 @@ DEPS: dict[str, dict[str, Any]] = { "filename": f"brotli-{V['BROTLI']}.tar.gz", "license": "LICENSE", "build": [ - *cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"), + *cmds_cmake( + ("brotlicommon", "brotlidec", "brotlienc"), + "-DBUILD_SHARED_LIBS:BOOL=OFF", + ), cmd_xcopy(r"c\include", "{inc_dir}"), ], "libs": ["*.lib"], @@ -332,6 +337,35 @@ DEPS: dict[str, dict[str, Any]] = { ], "libs": [r"bin\*.lib"], }, + "highway": { + "url": f"https://github.com/google/highway/archive/{V['HIGHWAY']}.tar.gz", + "filename": f"highway-{V['HIGHWAY']}.tar.gz", + "license": "LICENSE", + "build": [*cmds_cmake("hwy")], + "libs": ["hwy.lib"], + }, + "libjxl": { + "url": f"https://github.com/libjxl/libjxl/archive/v{V['JPEGXL']}.tar.gz", + "filename": f"libjxl-{V['JPEGXL']}.tar.gz", + "license": "LICENSE", + "build": [ + *cmds_cmake( + "jxl", + rf"-DHWY_INCLUDE_DIR=..\highway-{V['HIGHWAY']}", + r"-DLCMS2_LIBRARY=..\..\lib\lcms2_static", + r"-DLCMS2_INCLUDE_DIR=..\..\inc", + "-DJPEGXL_ENABLE_SJPEG:BOOL=OFF", + "-DJPEGXL_ENABLE_SKCMS:BOOL=OFF", + "-DBUILD_TESTING:BOOL=OFF", + "-DBUILD_SHARED_LIBS:BOOL=OFF", + ), + cmd_copy(r"lib\jxl.lib", "{lib_dir}"), + *cmds_cmake("jxl_threads"), + cmd_copy(r"lib\jxl_threads.lib", "{lib_dir}"), + cmd_mkdir(r"{inc_dir}\jxl"), + cmd_copy(r"lib\include\jxl\*.h", r"{inc_dir}\jxl"), + ], + }, "libimagequant": { "url": "https://github.com/ImageOptim/libimagequant/archive/{V['LIBIMAGEQUANT']}.tar.gz", "filename": f"libimagequant-{V['LIBIMAGEQUANT']}.tar.gz", @@ -630,6 +664,8 @@ def build_dep_all(disabled: list[str], prefs: dict[str, str], verbose: bool) -> print(f"Skipping disabled dependency {dep_name}") continue script = build_dep(dep_name, prefs, verbose) + if dep_name in ("highway", "libjxl"): + continue if gha_groups: lines.append(f"@echo ::group::Running {script}") lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"')