Merge branch 'main' into imagecms-typing

This commit is contained in:
Andrew Murray 2025-07-24 19:35:22 +10:00 committed by GitHub
commit 5cdaa1e46c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 153 additions and 152 deletions

View File

@ -1,10 +1,11 @@
mypy==1.16.1
mypy==1.17.0
IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6
ipython
numpy
packaging
pyarrow-stubs
pybind11
pytest
sphinx
types-atheris

View File

@ -60,7 +60,7 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then
# on using the Xcode builder, which isn't very helpful for most of Pillow's
# dependencies. Therefore, we lean on the OSX configurations, plus CC, CFLAGS
# etc. to ensure the right sysroot is selected.
HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO"
HOST_CMAKE_FLAGS="-DCMAKE_SYSTEM_NAME=$CMAKE_SYSTEM_NAME -DCMAKE_SYSTEM_PROCESSOR=$GNU_ARCH -DCMAKE_OSX_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET -DCMAKE_OSX_SYSROOT=$IOS_SDK_PATH -DBUILD_SHARED_LIBS=NO -DENABLE_SHARED=NO"
# Meson needs to be pointed at a cross-platform configuration file
# This will be generated once CC etc. have been evaluated.
@ -103,7 +103,7 @@ TIFF_VERSION=4.7.0
LCMS2_VERSION=2.17
ZLIB_VERSION=1.3.1
ZLIB_NG_VERSION=2.2.4
LIBWEBP_VERSION=1.5.0 # Patched; next release won't need patching. See patch file.
LIBWEBP_VERSION=1.6.0
BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0 # Patched; next release won't need patching. See patch file.
@ -280,7 +280,11 @@ function build {
if [[ -n "$IS_MACOS" ]]; then
webp_cflags="$webp_cflags -Wl,-headerpad_max_install_names"
fi
CFLAGS="$CFLAGS $webp_cflags" build_simple libwebp $LIBWEBP_VERSION \
webp_ldflags=""
if [[ -n "$IOS_SDK" ]]; then
webp_ldflags="$webp_ldflags -llzma -lz"
fi
CFLAGS="$CFLAGS $webp_cflags" LDFLAGS="$LDFLAGS $webp_ldflags" build_simple libwebp $LIBWEBP_VERSION \
https://storage.googleapis.com/downloads.webmproject.org/releases/webp tar.gz \
--enable-libwebpmux --enable-libwebpdemux
@ -380,6 +384,15 @@ fi
wrap_wheel_builder build
# A safety catch for iOS. iOS can't use dynamic libraries, but clang will prefer
# to link dynamic libraries to static libraries. The only way to reliably
# prevent this is to not have dynamic libraries available in the first place.
# The build process *shouldn't* generate any dylibs... but just in case, purge
# any dylibs that *have* been installed into the build prefix directory.
if [[ -n "$IOS_SDK" ]]; then
find "$BUILD_PREFIX" -name "*.dylib" -exec rm -rf {} \;
fi
# Return to the project root to finish the build
popd > /dev/null

View File

@ -13,6 +13,7 @@ include LICENSE
include Makefile
include tox.ini
graft Tests
graft Tests/images
graft checks
graft patches
graft src
@ -28,8 +29,19 @@ exclude .editorconfig
exclude .readthedocs.yml
exclude codecov.yml
exclude renovate.json
exclude Tests/images/README.md
exclude Tests/images/crash*.tif
exclude Tests/images/string_dimension.tiff
global-exclude .git*
global-exclude *.pyc
global-exclude *.so
prune .ci
prune wheels
prune winbuild/build
prune winbuild/depends
prune Tests/errors
prune Tests/images/jpeg2000
prune Tests/images/msp
prune Tests/images/picins
prune Tests/images/sunraster
prune Tests/test-images

View File

@ -291,16 +291,6 @@ def djpeg_available() -> bool:
return False
def cjpeg_available() -> bool:
if shutil.which("cjpeg"):
try:
subprocess.check_call(["cjpeg", "-version"])
return True
except subprocess.CalledProcessError: # pragma: no cover
return False
return False
def netpbm_available() -> bool:
return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))

Binary file not shown.

View File

@ -380,21 +380,28 @@ def test_palette() -> None:
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
def test_unsupported_header_size() -> None:
with pytest.raises(OSError, match="Unsupported header size 0"):
with Image.open(BytesIO(b"DDS " + b"\x00" * 4)):
pass
def test_unsupported_bitcount() -> None:
with pytest.raises(OSError):
with pytest.raises(OSError, match="Unsupported bitcount 24 for 131072"):
with Image.open("Tests/images/unsupported_bitcount.dds"):
pass
@pytest.mark.parametrize(
"test_file",
"test_file, message",
(
"Tests/images/unimplemented_dxgi_format.dds",
"Tests/images/unimplemented_pfflags.dds",
("Tests/images/unimplemented_dxgi_format.dds", "Unimplemented DXGI format 93"),
("Tests/images/unimplemented_pixel_format.dds", "Unimplemented pixel format 0"),
("Tests/images/unimplemented_pfflags.dds", "Unknown pixel format flags 8"),
),
)
def test_not_implemented(test_file: str) -> None:
with pytest.raises(NotImplementedError):
def test_not_implemented(test_file: str, message: str) -> None:
with pytest.raises(NotImplementedError, match=message):
with Image.open(test_file):
pass

View File

@ -26,7 +26,6 @@ from .helper import (
assert_image_equal_tofile,
assert_image_similar,
assert_image_similar_tofile,
cjpeg_available,
djpeg_available,
hopper,
is_win32,
@ -731,14 +730,6 @@ class TestFileJpeg:
img.load_djpeg()
assert_image_similar_tofile(img, TEST_FILE, 5)
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
def test_save_cjpeg(self, tmp_path: Path) -> None:
with Image.open(TEST_FILE) as img:
tempfile = str(tmp_path / "temp.jpg")
JpegImagePlugin._save_cjpeg(img, BytesIO(), tempfile)
# Default save quality is 75%, so a tiny bit of difference is alright
assert_image_similar_tofile(img, tempfile, 17)
def test_no_duplicate_0x1001_tag(self) -> None:
# Arrange
tag_ids = {v: k for k, v in ExifTags.TAGS.items()}

View File

@ -873,8 +873,8 @@ class TestFileLibTiff(LibTiffTestCase):
assert im.mode == "RGB"
assert im.size == (128, 128)
assert im.format == "TIFF"
im2 = hopper()
assert_image_similar(im, im2, 5)
with hopper() as im2:
assert_image_similar(im, im2, 5)
except OSError:
captured = capfd.readouterr()
if "LZMA compression support is not configured" in captured.err:

View File

@ -44,6 +44,18 @@ def test_load_zero_inch() -> None:
pass
def test_load_unsupported_wmf() -> None:
b = BytesIO(b"\xd7\xcd\xc6\x9a\x00\x00" + b"\x01" * 10)
with pytest.raises(SyntaxError, match="Unsupported WMF file format"):
WmfImagePlugin.WmfStubImageFile(b)
def test_load_unsupported() -> None:
b = BytesIO(b"\x01\x00\x00\x00")
with pytest.raises(SyntaxError, match="Unsupported file format"):
WmfImagePlugin.WmfStubImageFile(b)
def test_render() -> None:
with open("Tests/images/drawing.emf", "rb") as fp:
data = fp.read()

View File

@ -315,3 +315,6 @@ int main(int argc, char* argv[])
process = subprocess.Popen(["embed_pil.exe"], env=env)
process.communicate()
assert process.returncode == 0
def teardown_method(self) -> None:
os.remove("embed_pil.c")

View File

@ -2,7 +2,9 @@ from __future__ import annotations
from typing import Any
from PIL import Image, ImageMath
import pytest
from PIL import Image, ImageMath, _imagingmath
def pixel(im: Image.Image | int) -> str | int:
@ -498,3 +500,31 @@ def test_logical_not_equal() -> None:
)
== "I 1"
)
def test_reflected_operands() -> None:
assert pixel(ImageMath.lambda_eval(lambda args: 1 + args["A"], **images)) == "I 2"
assert pixel(ImageMath.lambda_eval(lambda args: 1 - args["A"], **images)) == "I 0"
assert pixel(ImageMath.lambda_eval(lambda args: 1 * args["A"], **images)) == "I 1"
assert pixel(ImageMath.lambda_eval(lambda args: 1 / args["A"], **images)) == "I 1"
assert pixel(ImageMath.lambda_eval(lambda args: 1 % args["A"], **images)) == "I 0"
assert pixel(ImageMath.lambda_eval(lambda args: 1 ** args["A"], **images)) == "I 1"
assert pixel(ImageMath.lambda_eval(lambda args: 1 & args["A"], **images)) == "I 1"
assert pixel(ImageMath.lambda_eval(lambda args: 1 | args["A"], **images)) == "I 1"
assert pixel(ImageMath.lambda_eval(lambda args: 1 ^ args["A"], **images)) == "I 0"
def test_unsupported_mode() -> None:
im = Image.new("RGB", (1, 1))
with pytest.raises(ValueError, match="unsupported mode: RGB"):
ImageMath.lambda_eval(lambda args: args["im"] + 1, im=im)
def test_bad_operand_type(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delattr(_imagingmath, "abs_I")
with pytest.raises(TypeError, match="bad operand type for 'abs'"):
ImageMath.lambda_eval(lambda args: abs(args["I"]), I=I)
monkeypatch.delattr(_imagingmath, "max_F")
with pytest.raises(TypeError, match="bad operand type for 'max'"):
ImageMath.lambda_eval(lambda args: args["max"](args["I"], args["F"]), I=I, F=F)

View File

@ -1,7 +1,5 @@
from __future__ import annotations
from importlib.metadata import metadata
import pytest
from PIL import __version__
@ -11,7 +9,7 @@ pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed")
def test_pyroma() -> None:
# Arrange
data = pyroma.projectdata.map_metadata_keys(metadata("Pillow"))
data = pyroma.projectdata.get_data(".")
# Act
rating = pyroma.ratings.rate(data)

View File

@ -9,7 +9,7 @@ import pytest
from PIL import GifImagePlugin, Image, JpegImagePlugin
from .helper import cjpeg_available, djpeg_available, is_win32, netpbm_available
from .helper import djpeg_available, is_win32, netpbm_available
TEST_JPG = "Tests/images/hopper.jpg"
TEST_GIF = "Tests/images/hopper.gif"
@ -42,11 +42,6 @@ class TestShellInjection:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
im.load_djpeg()
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
def test_save_cjpeg_filename(self, tmp_path: Path) -> None:
with Image.open(TEST_JPG) as im:
self.assert_save_filename_check(tmp_path, im, JpegImagePlugin._save_cjpeg)
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None:
with Image.open(TEST_GIF) as im:

View File

@ -1,7 +1,7 @@
#!/bin/bash
# install webp
archive=libwebp-1.5.0
archive=libwebp-1.6.0
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz

View File

@ -12,13 +12,6 @@ Deprecated features
Below are features which are considered deprecated. Where appropriate,
a :py:exc:`DeprecationWarning` is issued.
ImageDraw.getdraw hints parameter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 10.4.0
The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated.
ExifTags.IFD.Makernote
^^^^^^^^^^^^^^^^^^^^^^
@ -195,6 +188,7 @@ ICNS (width, height, scale) sizes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 11.0.0
.. versionremoved:: 12.0.0
Setting an ICNS image size to ``(width, height, scale)`` before loading has been
removed. Instead, ``load(scale)`` can be used.

View File

@ -101,6 +101,28 @@ Palette
The palette mode (``P``) uses a color palette to define the actual color for
each pixel.
.. _colors:
Colors
------
To specify colors, you can use tuples with a value for each channel in the image, e.g.
``Image.new("RGB", (1, 1), (255, 0, 0))``.
If an image has a single channel, you can use a single number instead, e.g.
``Image.new("L", (1, 1), 255)``. For "F" mode images, floating point values are also
accepted. In the case of "P" mode images, these will be indexes for the color palette.
If a single value is used for an image with more than one channel, it will still be
parsed::
>>> from PIL import Image
>>> im = Image.new("RGBA", (1, 1), 0x04030201)
>>> im.getpixel((0, 0))
(1, 2, 3, 4)
Some methods accept other forms, such as color names. See :ref:`color-names`.
Info
----

View File

@ -44,7 +44,7 @@ Many of Pillow's features require external libraries:
* **libtiff** provides compressed TIFF functionality
* Pillow has been tested with libtiff versions **3.x** and **4.0-4.7.0**
* Pillow has been tested with libtiff versions **4.0-4.7.0**
* **libfreetype** provides type related services
@ -276,10 +276,9 @@ Build options
* Config setting: ``-C parallel=n``. Can also be given
with environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
multiprocessing to build the extension. Setting ``-C parallel=n``
multiprocessing to build the extensions. Setting ``-C parallel=n``
sets the number of CPUs to use to ``n``, or can disable parallel building by
using a setting of 1. By default, it uses 4 CPUs, or if 4 are not
available, as many as are present.
using a setting of 1. By default, it uses as many CPUs as are present.
* Config settings: ``-C zlib=disable``, ``-C jpeg=disable``,
``-C tiff=disable``, ``-C freetype=disable``, ``-C raqm=disable``,

View File

@ -45,9 +45,7 @@ Colors
^^^^^^
To specify colors, you can use numbers or tuples just as you would use with
:py:meth:`PIL.Image.new` or :py:meth:`PIL.Image.Image.putpixel`. For “1”,
“L”, and “I” images, use integers. For “RGB” images, use a 3-tuple containing
integer values. For “F” images, use integer or floating point values.
:py:meth:`PIL.Image.new`. See :ref:`colors` for more information.
For palette images (mode “P”), use integers as color indexes. In 1.1.4 and
later, you can also use RGB 3-tuples or color names (see below). The drawing

View File

@ -59,7 +59,7 @@ Access using negative indexes is also possible. ::
Modifies the pixel at x,y. The color is given as a single
numerical value for single band images, and a tuple for
multi-band images.
multi-band images. See :ref:`colors` for more information.
:param xy: The pixel coordinate, given as (x, y).
:param color: The pixel value according to its mode,

View File

@ -1,42 +0,0 @@
# libwebp example binaries require dependencies that aren't available for iOS builds.
# There's also no easy way to invoke the build to *exclude* the example builds.
# Since we don't need the examples anyway, remove them from the Makefile.
#
# As a point of reference, libwebp provides an XCFramework build script that involves
# 7 separate invocations of make to avoid building the examples. Patching the Makefile
# to remove the examples is a simpler approach, and one that is more compatible with
# the existing multibuild infrastructure.
#
# In the next release, it should be possible to pass --disable-libwebpexamples
# instead of applying this patch.
#
diff -ur libwebp-1.5.0-orig/Makefile.am libwebp-1.5.0/Makefile.am
--- libwebp-1.5.0-orig/Makefile.am 2024-12-20 09:17:50
+++ libwebp-1.5.0/Makefile.am 2025-01-09 11:24:17
@@ -5,5 +5,3 @@
if BUILD_EXTRAS
SUBDIRS += extras
endif
-
-SUBDIRS += examples
diff -ur libwebp-1.5.0-orig/Makefile.in libwebp-1.5.0/Makefile.in
--- libwebp-1.5.0-orig/Makefile.in 2024-12-20 09:52:53
+++ libwebp-1.5.0/Makefile.in 2025-01-09 11:24:17
@@ -156,7 +156,7 @@
unique=`for i in $$list; do \
if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
done | $(am__uniquify_input)`
-DIST_SUBDIRS = sharpyuv src imageio man extras examples
+DIST_SUBDIRS = sharpyuv src imageio man extras
am__DIST_COMMON = $(srcdir)/Makefile.in \
$(top_srcdir)/src/webp/config.h.in AUTHORS COPYING ChangeLog \
NEWS README.md ar-lib compile config.guess config.sub \
@@ -351,7 +351,7 @@
top_srcdir = @top_srcdir@
webp_libname_prefix = @webp_libname_prefix@
ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = sharpyuv src imageio man $(am__append_1) examples
+SUBDIRS = sharpyuv src imageio man $(am__append_1)
EXTRA_DIST = COPYING autogen.sh
all: all-recursive

View File

@ -1,6 +1,7 @@
[build-system]
build-backend = "backend"
requires = [
"pybind11",
"setuptools>=77",
]
backend-path = [

View File

@ -17,9 +17,20 @@ import sys
import warnings
from collections.abc import Iterator
from pybind11.setup_helpers import ParallelCompile
from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext
configuration: dict[str, list[str]] = {}
# parse configuration from _custom_build/backend.py
while sys.argv[-1].startswith("--pillow-configuration="):
_, key, value = sys.argv.pop().split("=", 2)
configuration.setdefault(key, []).append(value)
default = int(configuration.get("parallel", ["0"])[-1])
ParallelCompile("MAX_CONCURRENCY", default).install()
def get_version() -> str:
version_file = "src/PIL/_version.py"
@ -27,9 +38,6 @@ def get_version() -> str:
return f.read().split('"')[1]
configuration: dict[str, list[str]] = {}
PILLOW_VERSION = get_version()
AVIF_ROOT = None
FREETYPE_ROOT = None
@ -386,9 +394,7 @@ class pil_build_ext(build_ext):
cpu_count = os.cpu_count()
if cpu_count is not None:
try:
self.parallel = int(
os.environ.get("MAX_CONCURRENCY", min(4, cpu_count))
)
self.parallel = int(os.environ.get("MAX_CONCURRENCY", cpu_count))
except TypeError:
pass
for x in self.feature:
@ -1083,11 +1089,6 @@ ext_modules = [
]
# parse configuration from _custom_build/backend.py
while sys.argv[-1].startswith("--pillow-configuration="):
_, key, value = sys.argv.pop().split("=", 2)
configuration.setdefault(key, []).append(value)
try:
setup(
cmdclass={"build_ext": pil_build_ext},

View File

@ -1730,9 +1730,10 @@ class Image:
details).
Instead of an image, the source can be a integer or tuple
containing pixel values. The method then fills the region
with the given color. When creating RGB images, you can
also use color strings as supported by the ImageColor module.
containing pixel values. The method then fills the region
with the given color. When creating RGB images, you can
also use color strings as supported by the ImageColor module. See
:ref:`colors` for more information.
If a mask is given, this method updates only the regions
indicated by the mask. You can use either "1", "L", "LA", "RGBA"
@ -1988,7 +1989,8 @@ class Image:
sequence ends. The scale and offset values are used to adjust the
sequence values: **pixel = value*scale + offset**.
:param data: A flattened sequence object.
:param data: A flattened sequence object. See :ref:`colors` for more
information about values.
:param scale: An optional scale value. The default is 1.0.
:param offset: An optional offset value. The default is 0.0.
"""
@ -2047,7 +2049,7 @@ class Image:
Modifies the pixel at the given position. The color is given as
a single numerical value for single-band images, and a tuple for
multi-band images. In addition to this, RGB and RGBA tuples are
accepted for P and PA images.
accepted for P and PA images. See :ref:`colors` for more information.
Note that this method is relatively slow. For more extensive changes,
use :py:meth:`~PIL.Image.Image.paste` or the :py:mod:`~PIL.ImageDraw`
@ -3055,12 +3057,12 @@ def new(
:param mode: The mode to use for the new image. See:
:ref:`concept-modes`.
:param size: A 2-tuple, containing (width, height) in pixels.
:param color: What color to use for the image. Default is black.
If given, this should be a single integer or floating point value
for single-band modes, and a tuple for multi-band modes (one value
per band). When creating RGB or HSV images, you can also use color
strings as supported by the ImageColor module. If the color is
None, the image is not initialised.
:param color: What color to use for the image. Default is black. If given,
this should be a single integer or floating point value for single-band
modes, and a tuple for multi-band modes (one value per band). When
creating RGB or HSV images, you can also use color strings as supported
by the ImageColor module. See :ref:`colors` for more information. If the
color is None, the image is not initialised.
:returns: An :py:class:`~PIL.Image.Image` object.
"""

View File

@ -845,16 +845,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
)
def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# ALTERNATIVE: handle JPEGs via the IJG command line utilities.
tempfile = im._dump()
subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
try:
os.unlink(tempfile)
except OSError:
pass
##
# Factory for making JPEG and MPO instances
def jpeg_factory(

View File

@ -9,7 +9,6 @@ from typing import IO
import PIL
from . import Image
from ._deprecate import deprecate
modules = {
"pil": ("PIL._imaging", "PILLOW_VERSION"),
@ -120,7 +119,7 @@ def get_supported_codecs() -> list[str]:
return [f for f in codecs if check_codec(f)]
features: dict[str, tuple[str, str | bool, str | None]] = {
features: dict[str, tuple[str, str, str | None]] = {
"raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"),
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
@ -146,12 +145,8 @@ def check_feature(feature: str) -> bool | None:
module, flag, ver = features[feature]
if isinstance(flag, bool):
deprecate(f'check_feature("{feature}")', 12)
try:
imported_module = __import__(module, fromlist=["PIL"])
if isinstance(flag, bool):
return flag
return getattr(imported_module, flag)
except ModuleNotFoundError:
return None
@ -181,17 +176,7 @@ def get_supported_features() -> list[str]:
"""
:returns: A list of all supported features.
"""
supported_features = []
for f, (module, flag, _) in features.items():
if flag is True:
for feature, (feature_module, _) in modules.items():
if feature_module == module:
if check_module(feature):
supported_features.append(f)
break
elif check_feature(f):
supported_features.append(f)
return supported_features
return [f for f in features if check_feature(f)]
def check(feature: str) -> bool | None:

View File

@ -122,7 +122,7 @@ V = {
"LIBAVIF": "1.3.0",
"LIBIMAGEQUANT": "4.3.4",
"LIBPNG": "1.6.49",
"LIBWEBP": "1.5.0",
"LIBWEBP": "1.6.0",
"OPENJPEG": "2.5.3",
"TIFF": "4.7.0",
"XZ": "5.8.1",
@ -149,18 +149,17 @@ DEPS: dict[str, dict[str, Any]] = {
},
"build": [
*cmds_cmake(
("jpeg-static", "cjpeg-static", "djpeg-static"),
("jpeg-static", "djpeg-static"),
"-DENABLE_SHARED:BOOL=FALSE",
"-DWITH_JPEG8:BOOL=TRUE",
"-DWITH_CRT_DLL:BOOL=TRUE",
),
cmd_copy("jpeg-static.lib", "libjpeg.lib"),
cmd_copy("cjpeg-static.exe", "cjpeg.exe"),
cmd_copy("djpeg-static.exe", "djpeg.exe"),
],
"headers": ["jconfig.h", r"src\j*.h"],
"libs": ["libjpeg.lib"],
"bins": ["cjpeg.exe", "djpeg.exe"],
"bins": ["djpeg.exe"],
},
"zlib": {
"url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.tar.gz",