diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt
index 0e314b8bf..520b6e320 100644
--- a/.ci/requirements-cibw.txt
+++ b/.ci/requirements-cibw.txt
@@ -1 +1 @@
-cibuildwheel==2.23.3
+cibuildwheel==3.0.0
diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt
index a9c18ae2b..44b5badab 100644
--- a/.ci/requirements-mypy.txt
+++ b/.ci/requirements-mypy.txt
@@ -1,4 +1,4 @@
-mypy==1.16.0
+mypy==1.16.1
IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6
ipython
diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml
index 6b76351b0..6d8acc44f 100644
--- a/.github/workflows/test-windows.yml
+++ b/.github/workflows/test-windows.yml
@@ -35,7 +35,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"]
+ python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"]
architecture: ["x64"]
include:
# Test the oldest Python on 32-bit
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 006d574f3..b4b516228 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -43,6 +43,7 @@ jobs:
python-version: [
"pypy3.11",
"pypy3.10",
+ "3.14t",
"3.14",
"3.13t",
"3.13",
@@ -55,6 +56,7 @@ jobs:
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
# Free-threaded
+ - { python-version: "3.14t", disable-gil: true }
- { python-version: "3.13t", disable-gil: true }
# M1 only available for 3.10+
- { os: "macos-13", python-version: "3.9" }
diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh
index 1583435c1..5384a74c0 100755
--- a/.github/workflows/wheels-dependencies.sh
+++ b/.github/workflows/wheels-dependencies.sh
@@ -39,8 +39,8 @@ ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds
FREETYPE_VERSION=2.13.3
HARFBUZZ_VERSION=11.2.1
-LIBPNG_VERSION=1.6.48
-JPEGTURBO_VERSION=3.1.0
+LIBPNG_VERSION=1.6.49
+JPEGTURBO_VERSION=3.1.1
OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.8.1
TIFF_VERSION=4.7.0
@@ -51,6 +51,7 @@ LIBWEBP_VERSION=1.5.0
BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0
+LIBAVIF_VERSION=1.3.0
function build_pkg_config {
if [ -e pkg-config-stamp ]; then return; fi
@@ -98,6 +99,59 @@ function build_harfbuzz {
touch harfbuzz-stamp
}
+function build_libavif {
+ if [ -e libavif-stamp ]; then return; fi
+
+ python3 -m pip install meson ninja
+
+ if [[ "$PLAT" == "x86_64" ]] || [ -n "$SANITIZER" ]; then
+ build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
+ fi
+
+ local build_type=MinSizeRel
+ local lto=ON
+
+ local libavif_cmake_flags
+
+ if [ -n "$IS_MACOS" ]; then
+ lto=OFF
+ libavif_cmake_flags=(
+ -DCMAKE_C_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
+ -DCMAKE_CXX_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
+ -DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,-S,-x,-dead_strip_dylibs" \
+ )
+ else
+ if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "x86_64" ]]; then
+ build_type=Release
+ fi
+ libavif_cmake_flags=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now")
+ fi
+
+ local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz)
+ # CONFIG_AV1_HIGHBITDEPTH=0 is a flag for libaom (included as a subproject
+ # of libavif) that disables support for encoding high bit depth images.
+ (cd $out_dir \
+ && cmake \
+ -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \
+ -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib \
+ -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \
+ -DBUILD_SHARED_LIBS=ON \
+ -DAVIF_LIBSHARPYUV=LOCAL \
+ -DAVIF_LIBYUV=LOCAL \
+ -DAVIF_CODEC_AOM=LOCAL \
+ -DCONFIG_AV1_HIGHBITDEPTH=0 \
+ -DAVIF_CODEC_AOM_DECODE=OFF \
+ -DAVIF_CODEC_DAV1D=LOCAL \
+ -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=$lto \
+ -DCMAKE_C_VISIBILITY_PRESET=hidden \
+ -DCMAKE_CXX_VISIBILITY_PRESET=hidden \
+ -DCMAKE_BUILD_TYPE=$build_type \
+ "${libavif_cmake_flags[@]}" \
+ . \
+ && make install)
+ touch libavif-stamp
+}
+
function build {
build_xz
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
@@ -132,6 +186,7 @@ function build {
build_tiff
fi
+ build_libavif
build_libpng
build_lcms2
build_openjpeg
diff --git a/.github/workflows/wheels-test.ps1 b/.github/workflows/wheels-test.ps1
index a1edc14ef..54e7fbbfc 100644
--- a/.github/workflows/wheels-test.ps1
+++ b/.github/workflows/wheels-test.ps1
@@ -9,17 +9,21 @@ if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
}
$env:path += ";$pillow\winbuild\build\bin\"
-& "$venv\Scripts\activate.ps1"
+if (Test-Path $venv\Scripts\pypy.exe) {
+ $python = "pypy.exe"
+} else {
+ $python = "python.exe"
+}
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
if ("$venv" -like "*\cibw-run-*-win_amd64\*") {
- & python -m pip install numpy
+ & $venv\Scripts\$python -m pip install numpy
}
cd $pillow
-& python -VV
+& $venv\Scripts\$python -VV
if (!$?) { exit $LASTEXITCODE }
-& python selftest.py
+& $venv\Scripts\$python selftest.py
if (!$?) { exit $LASTEXITCODE }
-& python -m pytest -vx Tests\check_wheel.py
+& $venv\Scripts\$python -m pytest -vx Tests\check_wheel.py
if (!$?) { exit $LASTEXITCODE }
-& python -m pytest -vx Tests
+& $venv\Scripts\$python -m pytest -vx Tests
if (!$?) { exit $LASTEXITCODE }
diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml
index 33e1976f0..16c350a14 100644
--- a/.github/workflows/wheels.yml
+++ b/.github/workflows/wheels.yml
@@ -58,7 +58,7 @@ jobs:
- name: "macOS 10.13 x86_64"
os: macos-13
cibw_arch: x86_64
- build: "cp3{12,13}*"
+ build: "cp3{12,13,14}*"
macosx_deployment_target: "10.13"
- name: "macOS 10.15 x86_64"
os: macos-13
@@ -110,7 +110,6 @@ jobs:
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
- CIBW_SKIP: pp39-*
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
- uses: actions/upload-artifact@v4
@@ -160,7 +159,7 @@ jobs:
# Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images
- & python.exe winbuild\build_prepare.py -v --no-imagequant --no-avif --architecture=${{ matrix.cibw_arch }}
+ & python.exe winbuild\build_prepare.py -v --no-imagequant --architecture=${{ matrix.cibw_arch }}
shell: pwsh
- name: Build wheels
@@ -188,7 +187,6 @@ jobs:
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
CIBW_CACHE_PATH: "C:\\cibw"
CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
- CIBW_SKIP: pp39-*
CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_COMMAND: 'docker run --rm
-v {project}:C:\pillow
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a1a054e00..1b8fa7199 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -2,7 +2,7 @@ repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.12
hooks:
- - id: ruff
+ - id: ruff-check
args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
diff --git a/Tests/check_wheel.py b/Tests/check_wheel.py
index 8ba40ba3f..a78fb09b0 100644
--- a/Tests/check_wheel.py
+++ b/Tests/check_wheel.py
@@ -9,15 +9,20 @@ from .helper import is_pypy
def test_wheel_modules() -> None:
- expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
+ expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp", "avif"}
- # tkinter is not available in cibuildwheel installed CPython on Windows
- try:
- import tkinter
+ if sys.platform == "win32":
+ # tkinter is not available in cibuildwheel installed CPython on Windows
+ try:
+ import tkinter
- assert tkinter
- except ImportError:
- expected_modules.remove("tkinter")
+ assert tkinter
+ except ImportError:
+ expected_modules.remove("tkinter")
+
+ # libavif is not available on Windows for ARM64 architectures
+ if platform.machine() == "ARM64":
+ expected_modules.remove("avif")
assert set(features.get_supported_modules()) == expected_modules
diff --git a/Tests/images/imagedraw_rectangle_I.tiff b/Tests/images/imagedraw_rectangle_I.tiff
index 9b9eda883..f0cb534b6 100644
Binary files a/Tests/images/imagedraw_rectangle_I.tiff and b/Tests/images/imagedraw_rectangle_I.tiff differ
diff --git a/Tests/images/op_index.qoi b/Tests/images/op_index.qoi
new file mode 100644
index 000000000..e626aafe6
Binary files /dev/null and b/Tests/images/op_index.qoi differ
diff --git a/Tests/images/p_4_planes.pcx b/Tests/images/p_4_planes.pcx
new file mode 100644
index 000000000..8c5743a98
Binary files /dev/null and b/Tests/images/p_4_planes.pcx differ
diff --git a/Tests/test_file_blp.py b/Tests/test_file_blp.py
index 9f50df22d..5f6b263a1 100644
--- a/Tests/test_file_blp.py
+++ b/Tests/test_file_blp.py
@@ -7,9 +7,8 @@ import pytest
from PIL import BlpImagePlugin, Image
from .helper import (
- assert_image_equal,
assert_image_equal_tofile,
- assert_image_similar,
+ assert_image_similar_tofile,
hopper,
)
@@ -52,18 +51,16 @@ def test_save(tmp_path: Path) -> None:
im = hopper("P")
im.save(f, blp_version=version)
- with Image.open(f) as reloaded:
- assert_image_equal(im.convert("RGB"), reloaded)
+ assert_image_equal_tofile(im.convert("RGB"), f)
with Image.open("Tests/images/transparent.png") as im:
f = tmp_path / "temp.blp"
im.convert("P").save(f, blp_version=version)
- with Image.open(f) as reloaded:
- assert_image_similar(im, reloaded, 8)
+ assert_image_similar_tofile(im, f, 8)
im = hopper()
- with pytest.raises(ValueError):
+ with pytest.raises(ValueError, match="Unsupported BLP image mode"):
im.save(f)
diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py
index f62f882f5..f84f2ebc9 100644
--- a/Tests/test_file_jpeg.py
+++ b/Tests/test_file_jpeg.py
@@ -1078,10 +1078,16 @@ class TestFileJpeg:
for marker in b"\xff\xd8", b"\xff\xd9":
assert marker in data[1]
assert marker in data[2]
- # DHT, DQT
- for marker in b"\xff\xc4", b"\xff\xdb":
+
+ # DQT
+ markers = [b"\xff\xdb"]
+ if features.check_feature("libjpeg_turbo"):
+ # DHT
+ markers.append(b"\xff\xc4")
+ for marker in markers:
assert marker in data[1]
assert marker not in data[2]
+
# SOF0, SOS, APP0 (JFIF header)
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
assert marker not in data[1]
diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py
index 6f88d5a27..1e6557efc 100644
--- a/Tests/test_file_mpo.py
+++ b/Tests/test_file_mpo.py
@@ -188,7 +188,7 @@ def test_mp_attribute(test_file: str) -> None:
with Image.open(test_file) as im:
assert isinstance(im, MpoImagePlugin.MpoImageFile)
mpinfo = im._getmp()
- assert mpinfo is not None
+ assert mpinfo is not None
for frame_number, mpentry in enumerate(mpinfo[0xB002]):
mpattr = mpentry["Attribute"]
if frame_number:
diff --git a/Tests/test_file_pcx.py b/Tests/test_file_pcx.py
index 5d7fd1c1b..2e999eff6 100644
--- a/Tests/test_file_pcx.py
+++ b/Tests/test_file_pcx.py
@@ -37,6 +37,11 @@ def test_sanity(tmp_path: Path) -> None:
im.save(f)
+def test_p_4_planes() -> None:
+ with Image.open("Tests/images/p_4_planes.pcx") as im:
+ assert im.getpixel((0, 0)) == 3
+
+
def test_bad_image_size() -> None:
with open("Tests/images/pil184.pcx", "rb") as fp:
data = fp.read()
diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py
index 4714699b9..b7f8310cc 100644
--- a/Tests/test_file_png.py
+++ b/Tests/test_file_png.py
@@ -101,13 +101,13 @@ class TestFilePng:
assert im.format == "PNG"
assert im.get_format_mimetype() == "image/png"
- for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]:
+ for mode in ["1", "L", "P", "RGB", "I;16", "I;16B"]:
im = hopper(mode)
im.save(test_file)
reloaded: Image.Image
with Image.open(test_file) as reloaded:
- if mode in ("I", "I;16B"):
+ if mode == "I;16B":
reloaded = reloaded.convert(mode)
assert_image_equal(reloaded, im)
@@ -831,6 +831,16 @@ class TestFilePng:
with Image.open("Tests/images/truncated_end_chunk.png") as im:
assert_image_equal_tofile(im, "Tests/images/hopper.png")
+ def test_deprecation(self, tmp_path: Path) -> None:
+ test_file = tmp_path / "out.png"
+
+ im = hopper("I")
+ with pytest.warns(DeprecationWarning):
+ im.save(test_file)
+
+ with Image.open(test_file) as reloaded:
+ assert_image_equal(im, reloaded.convert("I"))
+
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
@skip_unless_feature("zlib")
diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py
index d399c0e95..0071586ae 100644
--- a/Tests/test_file_ppm.py
+++ b/Tests/test_file_ppm.py
@@ -288,14 +288,16 @@ def test_non_integer_token(tmp_path: Path) -> None:
pass
-def test_header_token_too_long(tmp_path: Path) -> None:
+@pytest.mark.parametrize("data", (b"P3\x0cAAAAAAAAAA\xee", b"P6\n 01234567890"))
+def test_header_token_too_long(tmp_path: Path, data: bytes) -> None:
path = tmp_path / "temp.ppm"
with open(path, "wb") as f:
- f.write(b"P6\n 01234567890")
+ f.write(data)
- with pytest.raises(ValueError, match="Token too long in file header: 01234567890"):
+ with pytest.raises(ValueError) as e:
with Image.open(path):
pass
+ assert "Token too long in file header: " in repr(e)
def test_truncated_file(tmp_path: Path) -> None:
diff --git a/Tests/test_file_qoi.py b/Tests/test_file_qoi.py
index fd4b981ce..b9becb24f 100644
--- a/Tests/test_file_qoi.py
+++ b/Tests/test_file_qoi.py
@@ -1,10 +1,12 @@
from __future__ import annotations
+from pathlib import Path
+
import pytest
from PIL import Image, QoiImagePlugin
-from .helper import assert_image_equal_tofile
+from .helper import assert_image_equal_tofile, hopper
def test_sanity() -> None:
@@ -28,3 +30,28 @@ def test_invalid_file() -> None:
with pytest.raises(SyntaxError):
QoiImagePlugin.QoiImageFile(invalid_file)
+
+
+def test_op_index() -> None:
+ # QOI_OP_INDEX as the first chunk
+ with Image.open("Tests/images/op_index.qoi") as im:
+ assert im.getpixel((0, 0)) == (0, 0, 0, 0)
+
+
+def test_save(tmp_path: Path) -> None:
+ f = tmp_path / "temp.qoi"
+
+ im = hopper()
+ im.save(f, colorspace="sRGB")
+
+ assert_image_equal_tofile(im, f)
+
+ for path in ("Tests/images/default_font.png", "Tests/images/pil123rgba.png"):
+ with Image.open(path) as im:
+ im.save(f)
+
+ assert_image_equal_tofile(im, f)
+
+ im = hopper("P")
+ with pytest.raises(ValueError, match="Unsupported QOI image mode"):
+ im.save(f)
diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py
index 0f336f904..17515cc56 100644
--- a/Tests/test_file_tiff.py
+++ b/Tests/test_file_tiff.py
@@ -14,6 +14,7 @@ from PIL import (
ImageFile,
JpegImagePlugin,
TiffImagePlugin,
+ TiffTags,
UnidentifiedImageError,
)
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
@@ -48,25 +49,10 @@ class TestFileTiff:
assert im.size == (128, 128)
assert im.format == "TIFF"
- hopper("1").save(filename)
- with Image.open(filename):
- pass
-
- hopper("L").save(filename)
- with Image.open(filename):
- pass
-
- hopper("P").save(filename)
- with Image.open(filename):
- pass
-
- hopper("RGB").save(filename)
- with Image.open(filename):
- pass
-
- hopper("I").save(filename)
- with Image.open(filename):
- pass
+ for mode in ("1", "L", "P", "RGB", "I", "I;16", "I;16L"):
+ hopper(mode).save(filename)
+ with Image.open(filename):
+ pass
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file(self) -> None:
@@ -901,6 +887,29 @@ class TestFileTiff:
assert description[0]["format"] == "image/tiff"
assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
+ def test_getxmp_undefined(self, tmp_path: Path) -> None:
+ tmpfile = tmp_path / "temp.tif"
+ im = Image.new("L", (1, 1))
+ ifd = TiffImagePlugin.ImageFileDirectory_v2()
+ ifd.tagtype[700] = TiffTags.UNDEFINED
+ with Image.open("Tests/images/lab.tif") as im_xmp:
+ ifd[700] = im_xmp.info["xmp"]
+ im.save(tmpfile, tiffinfo=ifd)
+
+ with Image.open(tmpfile) as im_reloaded:
+ if ElementTree is None:
+ with pytest.warns(
+ UserWarning,
+ match="XMP data cannot be read without defusedxml dependency",
+ ):
+ assert im_reloaded.getxmp() == {}
+ else:
+ assert "xmp" in im_reloaded.info
+ xmp = im_reloaded.getxmp()
+
+ description = xmp["xmpmeta"]["RDF"]["Description"]
+ assert description[0]["format"] == "image/tiff"
+
def test_get_photoshop_blocks(self) -> None:
with Image.open("Tests/images/lab.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
diff --git a/Tests/test_image.py b/Tests/test_image.py
index 103846cad..d2598a3c4 100644
--- a/Tests/test_image.py
+++ b/Tests/test_image.py
@@ -974,6 +974,11 @@ class TestImage:
assert tag not in exif.get_ifd(0x8769)
assert exif.get_ifd(0xA005)
+ def test_exif_from_xmp_bytes(self) -> None:
+ im = Image.new("RGB", (1, 1))
+ im.info["xmp"] = b'\xff tiff:Orientation="2"'
+ assert im.getexif()[274] == 2
+
def test_empty_xmp(self) -> None:
with Image.open("Tests/images/hopper.gif") as im:
if ElementTree is None:
@@ -990,7 +995,7 @@ class TestImage:
im = Image.new("RGB", (1, 1))
im.info["xmp"] = (
b'\n'
- b'\n\x00\x00'
+ b'\n\x00\x00 '
)
if ElementTree is None:
with pytest.warns(
diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py
index eb2309e0f..ed9a0e204 100644
--- a/Tests/test_image_array.py
+++ b/Tests/test_image_array.py
@@ -1,6 +1,6 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Any
+from typing import Any
import pytest
from packaging.version import parse as parse_version
@@ -13,6 +13,7 @@ numpy = pytest.importorskip("numpy", reason="NumPy not installed")
im = hopper().resize((128, 100))
+TYPE_CHECKING = False
if TYPE_CHECKING:
import numpy.typing as npt
@@ -101,7 +102,8 @@ def test_fromarray_strides_without_tobytes() -> None:
with pytest.raises(ValueError):
wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)})
- Image.fromarray(wrapped, "L")
+ with pytest.warns(DeprecationWarning):
+ Image.fromarray(wrapped, "L")
def test_fromarray_palette() -> None:
@@ -110,7 +112,8 @@ def test_fromarray_palette() -> None:
a = numpy.array(i)
# Act
- out = Image.fromarray(a, "P")
+ with pytest.warns(DeprecationWarning):
+ out = Image.fromarray(a, "P")
# Assert that the Python and C palettes match
assert out.palette is not None
diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py
index 994da24fb..cf5fc2041 100644
--- a/Tests/test_imagedraw.py
+++ b/Tests/test_imagedraw.py
@@ -784,9 +784,10 @@ def test_rectangle_I16(bbox: Coords) -> None:
draw = ImageDraw.Draw(im)
# Act
- draw.rectangle(bbox, outline=0xFFFF)
+ draw.rectangle(bbox, outline=0xCDEF)
# Assert
+ assert im.getpixel((X0, Y0)) == 0xCDEF
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff")
diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py
index c4ad19d23..ef54deeeb 100644
--- a/Tests/test_numpy.py
+++ b/Tests/test_numpy.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import warnings
-from typing import TYPE_CHECKING
import pytest
@@ -9,6 +8,7 @@ from PIL import Image, _typing
from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature
+TYPE_CHECKING = False
if TYPE_CHECKING:
import numpy
import numpy.typing as npt
diff --git a/Tests/test_qt_image_qapplication.py b/Tests/test_qt_image_qapplication.py
index 0ed9fbfa5..82a3e0741 100644
--- a/Tests/test_qt_image_qapplication.py
+++ b/Tests/test_qt_image_qapplication.py
@@ -1,7 +1,7 @@
from __future__ import annotations
from pathlib import Path
-from typing import TYPE_CHECKING, Union
+from typing import Union
import pytest
@@ -9,6 +9,7 @@ from PIL import Image, ImageQt
from .helper import assert_image_equal_tofile, assert_image_similar, hopper
+TYPE_CHECKING = False
if TYPE_CHECKING:
import PyQt6
import PySide6
diff --git a/Tests/test_tiff_crashes.py b/Tests/test_tiff_crashes.py
index 073e5415c..976f62384 100644
--- a/Tests/test_tiff_crashes.py
+++ b/Tests/test_tiff_crashes.py
@@ -52,3 +52,17 @@ def test_tiff_crashes(test_file: str) -> None:
pytest.skip("test image not found")
except OSError:
pass
+
+
+def test_tiff_mmap() -> None:
+ try:
+ with Image.open("Tests/images/crash_mmap.tif") as im:
+ im.seek(1)
+ im.load()
+
+ im.seek(0)
+ im.load()
+ except FileNotFoundError:
+ if on_ci():
+ raise
+ pytest.skip("test image not found")
diff --git a/docs/dater.py b/docs/dater.py
index f9fb0c1da..c0302b55c 100644
--- a/docs/dater.py
+++ b/docs/dater.py
@@ -8,8 +8,8 @@ from __future__ import annotations
import re
import subprocess
-from typing import TYPE_CHECKING
+TYPE_CHECKING = False
if TYPE_CHECKING:
from sphinx.application import Sphinx
diff --git a/docs/deprecations.rst b/docs/deprecations.rst
index 0490ba439..def98b80a 100644
--- a/docs/deprecations.rst
+++ b/docs/deprecations.rst
@@ -193,6 +193,28 @@ Image.Image.get_child_images()
method uses an image's file pointer, and so child images could only be retrieved from
an :py:class:`PIL.ImageFile.ImageFile` instance.
+Image.fromarray mode parameter
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. deprecated:: 11.3.0
+
+The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
+mode can be automatically determined from the object's shape and type instead.
+
+Saving I mode images as PNG
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. deprecated:: 11.3.0
+
+In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
+at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
+changing the data, this is now deprecated. Instead, the image can be converted to
+another mode before saving::
+
+ from PIL import Image
+ im = Image.new("I", (1, 1))
+ im.convert("I;16").save("out.png")
+
Removed features
----------------
diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst
index 5ca549c37..a15e84574 100644
--- a/docs/handbook/image-file-formats.rst
+++ b/docs/handbook/image-file-formats.rst
@@ -1082,6 +1082,26 @@ Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I
Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well.
+QOI
+^^^
+
+.. versionadded:: 9.5.0
+
+Pillow reads and writes images in Quite OK Image format using a Python codec. If you
+wish to write code specifically for this format, :pypi:`qoi` is an alternative library
+that uses C to decode the image and interfaces with NumPy.
+
+.. _qoi-saving:
+
+Saving
+~~~~~~
+
+The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
+
+**colorspace**
+ If set to "sRGB", the colorspace will be written as sRGB with linear alpha, instead
+ of all channels being linear.
+
SGI
^^^
@@ -1578,15 +1598,6 @@ PSD
Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.
-QOI
-^^^
-
-.. versionadded:: 9.5.0
-
-Pillow reads images in Quite OK Image format using a Python decoder. If you wish to
-write code specifically for this format, :pypi:`qoi` is an alternative library that
-uses C to decode the image and interfaces with NumPy.
-
SUN
^^^
diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst
index 57a2298f8..a56f94316 100644
--- a/docs/installation/platform-support.rst
+++ b/docs/installation/platform-support.rst
@@ -40,12 +40,12 @@ These platforms are built and tested for every change.
| macOS 13 Ventura | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
-| | PyPy3 | |
+| | 3.14, PyPy3 | |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 24.04 LTS (Noble) | 3.9, 3.10, 3.11, | x86-64 |
-| | 3.12, 3.13, PyPy3 | |
+| | 3.12, 3.13, 3.14, PyPy3 | |
| +----------------------------+---------------------+
| | 3.12 | arm64v8, ppc64le, |
| | | s390x |
@@ -53,7 +53,7 @@ These platforms are built and tested for every change.
| Windows Server 2022 | 3.9 | x86 |
| +----------------------------+---------------------+
| | 3.10, 3.11, 3.12, 3.13, | x86-64 |
-| | PyPy3 | |
+| | 3.14, PyPy3 | |
| +----------------------------+---------------------+
| | 3.12 (MinGW) | x86-64 |
| +----------------------------+---------------------+
diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst
index 8b2f92323..aac55fe6b 100644
--- a/docs/reference/ImageFont.rst
+++ b/docs/reference/ImageFont.rst
@@ -18,6 +18,9 @@ OpenType fonts (as well as other font formats supported by the FreeType
library). For earlier versions, TrueType support is only available as part of
the imToolkit package.
+When measuring text sizes, this module will not break at newline characters. For
+multiline text, see the :py:mod:`~PIL.ImageDraw` module.
+
.. warning::
To protect against potential DOS attacks when using arbitrary strings as
text input, Pillow will raise a :py:exc:`ValueError` if the number of characters
diff --git a/docs/releasenotes/11.3.0.rst b/docs/releasenotes/11.3.0.rst
new file mode 100644
index 000000000..654a7e6b6
--- /dev/null
+++ b/docs/releasenotes/11.3.0.rst
@@ -0,0 +1,95 @@
+11.3.0
+------
+
+Security
+========
+
+TODO
+^^^^
+
+TODO
+
+:cve:`YYYY-XXXXX`: TODO
+^^^^^^^^^^^^^^^^^^^^^^^
+
+TODO
+
+Backwards incompatible changes
+==============================
+
+TODO
+^^^^
+
+Deprecations
+============
+
+Image.fromarray mode parameter
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
+mode can be automatically determined from the object's shape and type instead.
+
+Saving I mode images as PNG
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
+at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
+changing the data, this is now deprecated. Instead, the image can be converted to
+another mode before saving::
+
+ from PIL import Image
+ im = Image.new("I", (1, 1))
+ im.convert("I;16").save("out.png")
+
+API changes
+===========
+
+TODO
+^^^^
+
+TODO
+
+API additions
+=============
+
+TODO
+^^^^
+
+TODO
+
+Other changes
+=============
+
+Added QOI saving
+^^^^^^^^^^^^^^^^
+
+Support has been added for saving QOI images. ``colorspace`` can be used to specify the
+colorspace as sRGB with linear alpha, e.g. ``im.save("out.qoi", colorspace="sRGB")``.
+By default, all channels will be linear.
+
+Support using more screenshot utilities with ImageGrab on Linux
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+:py:meth:`~PIL.ImageGrab.grab` is now able to use GNOME Screenshot, grim or Spectacle
+on Linux in order to take a snapshot of the screen.
+
+Do not build against libavif < 1
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Pillow only supports libavif 1.0.0 or later. In order to prevent errors when building
+from source, if a user happens to have an earlier libavif on their system, Pillow will
+now ignore it.
+
+AVIF support in wheels
+^^^^^^^^^^^^^^^^^^^^^^
+
+Support for reading and writing AVIF images is now included in Pillow's wheels, except
+for Windows ARM64. libaom is available as an encoder and dav1d as a decoder.
+
+Python 3.14 beta
+^^^^^^^^^^^^^^^^
+
+To help other projects prepare for Python 3.14, wheels are now built for the
+3.14 beta as a preview. This is not official support for Python 3.14, but rather
+an opportunity for you to test how Pillow works with the beta and report any
+problems.
diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst
index 5d7b21d59..a85f1e075 100644
--- a/docs/releasenotes/index.rst
+++ b/docs/releasenotes/index.rst
@@ -14,6 +14,7 @@ expected to be backported to earlier versions.
.. toctree::
:maxdepth: 2
+ 11.3.0
11.2.1
11.1.0
11.0.0
diff --git a/setup.py b/setup.py
index ab36c6b17..354e09f85 100644
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,6 @@ import subprocess
import sys
import warnings
from collections.abc import Iterator
-from typing import Any
from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext
@@ -148,7 +147,7 @@ class RequiredDependencyException(Exception):
PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version
-def _dbg(s: str, tp: Any = None) -> None:
+def _dbg(s: str, tp: str | tuple[str, ...] | None = None) -> None:
if DEBUG:
if tp:
print(s % tp)
@@ -163,7 +162,7 @@ def _find_library_dirs_ldconfig() -> list[str]:
args: list[str]
env: dict[str, str]
expr: str
- if sys.platform.startswith("linux") or sys.platform.startswith("gnu"):
+ if sys.platform.startswith(("linux", "gnu")):
if struct.calcsize("l") == 4:
machine = os.uname()[4] + "-32"
else:
@@ -509,11 +508,11 @@ class pil_build_ext(build_ext):
if root is None and pkg_config:
if isinstance(lib_name, str):
- _dbg(f"Looking for `{lib_name}` using pkg-config.")
+ _dbg("Looking for `%s` using pkg-config.", lib_name)
root = pkg_config(lib_name)
else:
for lib_name2 in lib_name:
- _dbg(f"Looking for `{lib_name2}` using pkg-config.")
+ _dbg("Looking for `%s` using pkg-config.", lib_name2)
root = pkg_config(lib_name2)
if root:
break
@@ -623,11 +622,7 @@ class pil_build_ext(build_ext):
for extension in self.extensions:
extension.extra_compile_args = ["-Wno-nullability-completeness"]
- elif (
- sys.platform.startswith("linux")
- or sys.platform.startswith("gnu")
- or sys.platform.startswith("freebsd")
- ):
+ elif sys.platform.startswith(("linux", "gnu", "freebsd")):
for dirname in _find_library_dirs_ldconfig():
_add_directory(library_dirs, dirname)
if sys.platform.startswith("linux") and os.environ.get("ANDROID_ROOT"):
@@ -736,7 +731,7 @@ class pil_build_ext(build_ext):
best_path = os.path.join(directory, name)
_dbg(
"Best openjpeg version %s so far in %s",
- (best_version, best_path),
+ (str(best_version), best_path),
)
if best_version and _find_library_file(self, "openjp2"):
@@ -758,12 +753,12 @@ class pil_build_ext(build_ext):
if feature.want("tiff"):
_dbg("Looking for tiff")
if _find_include_file(self, "tiff.h"):
- if _find_library_file(self, "tiff"):
- feature.set("tiff", "tiff")
if sys.platform in ["win32", "darwin"] and _find_library_file(
self, "libtiff"
):
feature.set("tiff", "libtiff")
+ elif _find_library_file(self, "tiff"):
+ feature.set("tiff", "tiff")
if feature.want("freetype"):
_dbg("Looking for freetype")
diff --git a/src/PIL/Image.py b/src/PIL/Image.py
index baf31c2fa..8bb892b86 100644
--- a/src/PIL/Image.py
+++ b/src/PIL/Image.py
@@ -1506,7 +1506,7 @@ class Image:
return {}
if "xmp" not in self.info:
return {}
- root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00"))
+ root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00 "))
return {get_name(root.tag): get_value(root)}
def getexif(self) -> Exif:
@@ -1541,10 +1541,11 @@ class Image:
# XMP tags
if ExifTags.Base.Orientation not in self._exif:
xmp_tags = self.info.get("XML:com.adobe.xmp")
+ pattern: str | bytes = r'tiff:Orientation(="|>)([0-9])'
if not xmp_tags and (xmp_tags := self.info.get("xmp")):
- xmp_tags = xmp_tags.decode("utf-8")
+ pattern = rb'tiff:Orientation(="|>)([0-9])'
if xmp_tags:
- match = re.search(r'tiff:Orientation(="|>)([0-9])', xmp_tags)
+ match = re.search(pattern, xmp_tags)
if match:
self._exif[ExifTags.Base.Orientation] = int(match[2])
@@ -3270,7 +3271,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
:param obj: Object with array interface
:param mode: Optional mode to use when reading ``obj``. Will be determined from
- type if ``None``.
+ type if ``None``. Deprecated.
This will not be used to convert the data after reading, but will be used to
change how the data is read::
@@ -3305,6 +3306,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
msg = f"Cannot handle this data type: {typekey_shape}, {typestr}"
raise TypeError(msg) from e
else:
+ deprecate("'mode' parameter", 13)
rawmode = mode
if mode in ["1", "L", "I", "P", "F"]:
ndmax = 2
diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py
index fdfbee789..a1584f111 100644
--- a/src/PIL/ImageCms.py
+++ b/src/PIL/ImageCms.py
@@ -248,6 +248,9 @@ class ImageCmsProfile:
low-level profile object
"""
+ self.filename = None
+ self.product_name = None # profile.product_name
+ self.product_info = None # profile.product_info
if isinstance(profile, str):
if sys.platform == "win32":
@@ -256,23 +259,18 @@ class ImageCmsProfile:
profile_bytes_path.decode("ascii")
except UnicodeDecodeError:
with open(profile, "rb") as f:
- self._set(core.profile_frombytes(f.read()))
+ self.profile = core.profile_frombytes(f.read())
return
- self._set(core.profile_open(profile), profile)
+ self.filename = profile
+ self.profile = core.profile_open(profile)
elif hasattr(profile, "read"):
- self._set(core.profile_frombytes(profile.read()))
+ self.profile = core.profile_frombytes(profile.read())
elif isinstance(profile, core.CmsProfile):
- self._set(profile)
+ self.profile = profile
else:
msg = "Invalid type for Profile" # type: ignore[unreachable]
raise TypeError(msg)
- def _set(self, profile: core.CmsProfile, filename: str | None = None) -> None:
- self.profile = profile
- self.filename = filename
- self.product_name = None # profile.product_name
- self.product_info = None # profile.product_info
-
def tobytes(self) -> bytes:
"""
Returns the profile in a format suitable for embedding in
diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py
index e6c7b0298..98ae67539 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -365,22 +365,10 @@ class ImageDraw:
# use the fill as a mask
mask = Image.new("1", self.im.size)
mask_ink = self._getink(1)[0]
-
- fill_im = mask.copy()
- draw = Draw(fill_im)
+ draw = Draw(mask)
draw.draw.draw_polygon(xy, mask_ink, 1)
- ink_im = mask.copy()
- draw = Draw(ink_im)
- width = width * 2 - 1
- draw.draw.draw_polygon(xy, mask_ink, 0, width)
-
- mask.paste(ink_im, mask=fill_im)
-
- im = Image.new(self.mode, self.im.size)
- draw = Draw(im)
- draw.draw.draw_polygon(xy, ink, 0, width)
- self.im.paste(im.im, (0, 0) + im.size, mask.im)
+ self.draw.draw_polygon(xy, ink, 0, width * 2 - 1, mask.im)
def regular_polygon(
self,
diff --git a/src/PIL/ImageGrab.py b/src/PIL/ImageGrab.py
index c29350b7a..1eb450734 100644
--- a/src/PIL/ImageGrab.py
+++ b/src/PIL/ImageGrab.py
@@ -134,10 +134,10 @@ def grabclipboard() -> Image.Image | list[str] | None:
import struct
o = struct.unpack_from("I", data)[0]
- if data[16] != 0:
- files = data[o:].decode("utf-16le").split("\0")
- else:
+ if data[16] == 0:
files = data[o:].decode("mbcs").split("\0")
+ else:
+ files = data[o:].decode("utf-16le").split("\0")
return files[: files.index("")]
if isinstance(data, bytes):
data = io.BytesIO(data)
diff --git a/src/PIL/ImageShow.py b/src/PIL/ImageShow.py
index dd240fb55..7705608e3 100644
--- a/src/PIL/ImageShow.py
+++ b/src/PIL/ImageShow.py
@@ -175,7 +175,9 @@ class MacViewer(Viewer):
if not os.path.exists(path):
raise FileNotFoundError
subprocess.call(["open", "-a", "Preview.app", path])
- executable = sys.executable or shutil.which("python3")
+
+ pyinstaller = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
+ executable = (not pyinstaller and sys.executable) or shutil.which("python3")
if executable:
subprocess.Popen(
[
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
index b9d493373..8627de4a8 100644
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -769,8 +769,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
extra = info.get("extra", b"")
MAX_BYTES_IN_MARKER = 65533
- xmp = info.get("xmp")
- if xmp:
+ if xmp := info.get("xmp"):
overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
if len(xmp) > max_data_bytes_in_marker:
@@ -779,8 +778,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
size = o16(2 + overhead_len + len(xmp))
extra += b"\xff\xe1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp
- icc_profile = info.get("icc_profile")
- if icc_profile:
+ if icc_profile := info.get("icc_profile"):
overhead_len = 14 # b"ICC_PROFILE\0" + o8(i) + o8(len(markers))
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
markers = []
@@ -838,7 +836,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
# channels*size, this is a value that's been used in a django patch.
# https://github.com/matthewwithanm/django-imagekit/issues/50
- bufsize = 0
if optimize or progressive:
# CMYK can be bigger
if im.mode == "CMYK":
@@ -855,7 +852,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
else:
# The EXIF info needs to be written as one block, + APP1, + one spare byte.
# Ensure that our buffer is big enough. Same with the icc_profile block.
- bufsize = max(bufsize, len(exif) + 5, len(extra) + 1)
+ bufsize = max(len(exif) + 5, len(extra) + 1)
ImageFile._save(
im, fp, [ImageFile._Tile("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize
diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py
index 299405ae0..458d586c4 100644
--- a/src/PIL/PcxImagePlugin.py
+++ b/src/PIL/PcxImagePlugin.py
@@ -54,7 +54,7 @@ class PcxImageFile(ImageFile.ImageFile):
# header
assert self.fp is not None
- s = self.fp.read(128)
+ s = self.fp.read(68)
if not _accept(s):
msg = "not a PCX file"
raise SyntaxError(msg)
@@ -66,6 +66,8 @@ class PcxImageFile(ImageFile.ImageFile):
raise SyntaxError(msg)
logger.debug("BBox: %s %s %s %s", *bbox)
+ offset = self.fp.tell() + 60
+
# format
version = s[1]
bits = s[3]
@@ -102,7 +104,6 @@ class PcxImageFile(ImageFile.ImageFile):
break
if mode == "P":
self.palette = ImagePalette.raw("RGB", s[1:])
- self.fp.seek(128)
elif version == 5 and bits == 8 and planes == 3:
mode = "RGB"
@@ -128,9 +129,7 @@ class PcxImageFile(ImageFile.ImageFile):
bbox = (0, 0) + self.size
logger.debug("size: %sx%s", *self.size)
- self.tile = [
- ImageFile._Tile("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))
- ]
+ self.tile = [ImageFile._Tile("pcx", bbox, offset, (rawmode, planes * stride))]
# --------------------------------------------------------------------
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
index ecc93627b..46b46bfa3 100644
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -48,6 +48,7 @@ from ._binary import i32be as i32
from ._binary import o8
from ._binary import o16be as o16
from ._binary import o32be as o32
+from ._deprecate import deprecate
from ._util import DeferredError
TYPE_CHECKING = False
@@ -1371,6 +1372,8 @@ def _save(
except KeyError as e:
msg = f"cannot write mode {mode} as PNG"
raise OSError(msg) from e
+ if outmode == "I":
+ deprecate("Saving I mode images as PNG", 13, stacklevel=4)
#
# write minimal PNG file
diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py
index 03afa2d2e..db34d107a 100644
--- a/src/PIL/PpmImagePlugin.py
+++ b/src/PIL/PpmImagePlugin.py
@@ -94,8 +94,8 @@ class PpmImageFile(ImageFile.ImageFile):
msg = "Reached EOF while reading header"
raise ValueError(msg)
elif len(token) > 10:
- msg = f"Token too long in file header: {token.decode()}"
- raise ValueError(msg)
+ msg_too_long = b"Token too long in file header: %s" % token
+ raise ValueError(msg_too_long)
return token
def _open(self) -> None:
diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py
index 9b06a973e..d0709b119 100644
--- a/src/PIL/QoiImagePlugin.py
+++ b/src/PIL/QoiImagePlugin.py
@@ -8,9 +8,12 @@
from __future__ import annotations
import os
+from typing import IO
from . import Image, ImageFile
from ._binary import i32be as i32
+from ._binary import o8
+from ._binary import o32be as o32
def _accept(prefix: bytes) -> bool:
@@ -52,7 +55,7 @@ class QoiDecoder(ImageFile.PyDecoder):
assert self.fd is not None
self._previously_seen_pixels = {}
- self._add_to_previous_pixels(bytearray((0, 0, 0, 255)))
+ self._previous_pixel = bytearray((0, 0, 0, 255))
data = bytearray()
bands = Image.getmodebands(self.mode)
@@ -111,6 +114,122 @@ class QoiDecoder(ImageFile.PyDecoder):
return -1, 0
+def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
+ if im.mode == "RGB":
+ channels = 3
+ elif im.mode == "RGBA":
+ channels = 4
+ else:
+ msg = "Unsupported QOI image mode"
+ raise ValueError(msg)
+
+ colorspace = 0 if im.encoderinfo.get("colorspace") == "sRGB" else 1
+
+ fp.write(b"qoif")
+ fp.write(o32(im.size[0]))
+ fp.write(o32(im.size[1]))
+ fp.write(o8(channels))
+ fp.write(o8(colorspace))
+
+ ImageFile._save(im, fp, [ImageFile._Tile("qoi", (0, 0) + im.size)])
+
+
+class QoiEncoder(ImageFile.PyEncoder):
+ _pushes_fd = True
+ _previous_pixel: tuple[int, int, int, int] | None = None
+ _previously_seen_pixels: dict[int, tuple[int, int, int, int]] = {}
+ _run = 0
+
+ def _write_run(self) -> bytes:
+ data = o8(0b11000000 | (self._run - 1)) # QOI_OP_RUN
+ self._run = 0
+ return data
+
+ def _delta(self, left: int, right: int) -> int:
+ result = (left - right) & 255
+ if result >= 128:
+ result -= 256
+ return result
+
+ def encode(self, bufsize: int) -> tuple[int, int, bytes]:
+ assert self.im is not None
+
+ self._previously_seen_pixels = {0: (0, 0, 0, 0)}
+ self._previous_pixel = (0, 0, 0, 255)
+
+ data = bytearray()
+ w, h = self.im.size
+ bands = Image.getmodebands(self.mode)
+
+ for y in range(h):
+ for x in range(w):
+ pixel = self.im.getpixel((x, y))
+ if bands == 3:
+ pixel = (*pixel, 255)
+
+ if pixel == self._previous_pixel:
+ self._run += 1
+ if self._run == 62:
+ data += self._write_run()
+ else:
+ if self._run:
+ data += self._write_run()
+
+ r, g, b, a = pixel
+ hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64
+ if self._previously_seen_pixels.get(hash_value) == pixel:
+ data += o8(hash_value) # QOI_OP_INDEX
+ elif self._previous_pixel:
+ self._previously_seen_pixels[hash_value] = pixel
+
+ prev_r, prev_g, prev_b, prev_a = self._previous_pixel
+ if prev_a == a:
+ delta_r = self._delta(r, prev_r)
+ delta_g = self._delta(g, prev_g)
+ delta_b = self._delta(b, prev_b)
+
+ if (
+ -2 <= delta_r < 2
+ and -2 <= delta_g < 2
+ and -2 <= delta_b < 2
+ ):
+ data += o8(
+ 0b01000000
+ | (delta_r + 2) << 4
+ | (delta_g + 2) << 2
+ | (delta_b + 2)
+ ) # QOI_OP_DIFF
+ else:
+ delta_gr = self._delta(delta_r, delta_g)
+ delta_gb = self._delta(delta_b, delta_g)
+ if (
+ -8 <= delta_gr < 8
+ and -32 <= delta_g < 32
+ and -8 <= delta_gb < 8
+ ):
+ data += o8(
+ 0b10000000 | (delta_g + 32)
+ ) # QOI_OP_LUMA
+ data += o8((delta_gr + 8) << 4 | (delta_gb + 8))
+ else:
+ data += o8(0b11111110) # QOI_OP_RGB
+ data += bytes(pixel[:3])
+ else:
+ data += o8(0b11111111) # QOI_OP_RGBA
+ data += bytes(pixel)
+
+ self._previous_pixel = pixel
+
+ if self._run:
+ data += self._write_run()
+ data += bytes((0, 0, 0, 0, 0, 0, 0, 1)) # padding
+
+ return len(data), 0, data
+
+
Image.register_open(QoiImageFile.format, QoiImageFile, _accept)
Image.register_decoder("qoi", QoiDecoder)
Image.register_extension(QoiImageFile.format, ".qoi")
+
+Image.register_save(QoiImageFile.format, _save)
+Image.register_encoder("qoi", QoiEncoder)
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index 20b77c563..fcec0ad73 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -1218,9 +1218,10 @@ class TiffImageFile(ImageFile.ImageFile):
return
self._seek(frame)
if self._im is not None and (
- self.im.size != self._tile_size or self.im.mode != self.mode
+ self.im.size != self._tile_size
+ or self.im.mode != self.mode
+ or self.readonly
):
- # The core image will no longer be used
self._im = None
def _seek(self, frame: int) -> None:
@@ -1260,7 +1261,10 @@ class TiffImageFile(ImageFile.ImageFile):
self.fp.seek(self._frame_pos[frame])
self.tag_v2.load(self.fp)
if XMP in self.tag_v2:
- self.info["xmp"] = self.tag_v2[XMP]
+ xmp = self.tag_v2[XMP]
+ if isinstance(xmp, tuple) and len(xmp) == 1:
+ xmp = xmp[0]
+ self.info["xmp"] = xmp
elif "xmp" in self.info:
del self.info["xmp"]
self._reload_exif()
@@ -1678,7 +1682,7 @@ SAVE_INFO = {
"PA": ("PA", II, 3, 1, (8, 8), 2),
"I": ("I;32S", II, 1, 2, (32,), None),
"I;16": ("I;16", II, 1, 1, (16,), None),
- "I;16S": ("I;16S", II, 1, 2, (16,), None),
+ "I;16L": ("I;16L", II, 1, 1, (16,), None),
"F": ("F;32F", II, 1, 3, (32,), None),
"RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
"RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
@@ -1686,10 +1690,7 @@ SAVE_INFO = {
"CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
"YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
"LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
- "I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
"I;16B": ("I;16B", MM, 1, 1, (16,), None),
- "I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
- "F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
}
@@ -1965,7 +1966,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# we're storing image byte order. So, if the rawmode
# contains I;16, we need to convert from native to image
# byte order.
- if im.mode in ("I;16B", "I;16"):
+ if im.mode in ("I;16", "I;16B", "I;16L"):
rawmode = "I;16N"
# Pass tags as sorted list so that the tags are set in a fixed order.
diff --git a/src/PIL/_deprecate.py b/src/PIL/_deprecate.py
index 9f9d8bbc9..170d44490 100644
--- a/src/PIL/_deprecate.py
+++ b/src/PIL/_deprecate.py
@@ -12,6 +12,7 @@ def deprecate(
*,
action: str | None = None,
plural: bool = False,
+ stacklevel: int = 3,
) -> None:
"""
Deprecations helper.
@@ -67,5 +68,5 @@ def deprecate(
warnings.warn(
f"{deprecated} {is_} deprecated and will be removed in {removed}{action}",
DeprecationWarning,
- stacklevel=3,
+ stacklevel=stacklevel,
)
diff --git a/src/_avif.c b/src/_avif.c
index 7e7bee703..3585297fe 100644
--- a/src/_avif.c
+++ b/src/_avif.c
@@ -881,26 +881,22 @@ setup_module(PyObject *m) {
return 0;
}
+static PyModuleDef_Slot slots[] = {
+ {Py_mod_exec, setup_module},
+#ifdef Py_GIL_DISABLED
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
+ {0, NULL}
+};
+
PyMODINIT_FUNC
PyInit__avif(void) {
- PyObject *m;
-
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "_avif",
- .m_size = -1,
.m_methods = avifMethods,
+ .m_slots = slots
};
- m = PyModule_Create(&module_def);
- if (setup_module(m) < 0) {
- Py_DECREF(m);
- return NULL;
- }
-
-#ifdef Py_GIL_DISABLED
- PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
-#endif
-
- return m;
+ return PyModuleDef_Init(&module_def);
}
diff --git a/src/_imaging.c b/src/_imaging.c
index 9213ba13d..6f13834a9 100644
--- a/src/_imaging.c
+++ b/src/_imaging.c
@@ -338,12 +338,6 @@ static const char *no_palette = "image has no palette";
static const char *readonly = "image is readonly";
/* static const char* no_content = "image has no content"; */
-void *
-ImagingError_OSError(void) {
- PyErr_SetString(PyExc_OSError, "error when accessing file");
- return NULL;
-}
-
void *
ImagingError_MemoryError(void) {
return PyErr_NoMemory();
@@ -369,11 +363,6 @@ ImagingError_ValueError(const char *message) {
return NULL;
}
-void
-ImagingError_Clear(void) {
- PyErr_Clear();
-}
-
/* -------------------------------------------------------------------- */
/* HELPERS */
/* -------------------------------------------------------------------- */
@@ -3220,7 +3209,8 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
(int)p[3],
&ink,
width,
- self->blend
+ self->blend,
+ NULL
) < 0) {
free(xy);
return NULL;
@@ -3358,7 +3348,10 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
int ink;
int fill = 0;
int width = 0;
- if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) {
+ ImagingObject *maskp = NULL;
+ if (!PyArg_ParseTuple(
+ args, "Oi|iiO!", &data, &ink, &fill, &width, &Imaging_Type, &maskp
+ )) {
return NULL;
}
@@ -3388,8 +3381,16 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
free(xy);
- if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, width, self->blend) <
- 0) {
+ if (ImagingDrawPolygon(
+ self->image->image,
+ n,
+ ixy,
+ &ink,
+ fill,
+ width,
+ self->blend,
+ maskp ? maskp->image : NULL
+ ) < 0) {
free(ixy);
return NULL;
}
@@ -4462,27 +4463,22 @@ setup_module(PyObject *m) {
return 0;
}
+static PyModuleDef_Slot slots[] = {
+ {Py_mod_exec, setup_module},
+#ifdef Py_GIL_DISABLED
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
+ {0, NULL}
+};
+
PyMODINIT_FUNC
PyInit__imaging(void) {
- PyObject *m;
-
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "_imaging",
- .m_size = -1,
.m_methods = functions,
+ .m_slots = slots
};
- m = PyModule_Create(&module_def);
-
- if (setup_module(m) < 0) {
- Py_DECREF(m);
- return NULL;
- }
-
-#ifdef Py_GIL_DISABLED
- PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
-#endif
-
- return m;
+ return PyModuleDef_Init(&module_def);
}
diff --git a/src/_imagingcms.c b/src/_imagingcms.c
index f93c1613b..e2f29d1b7 100644
--- a/src/_imagingcms.c
+++ b/src/_imagingcms.c
@@ -1463,28 +1463,24 @@ setup_module(PyObject *m) {
return 0;
}
+static PyModuleDef_Slot slots[] = {
+ {Py_mod_exec, setup_module},
+#ifdef Py_GIL_DISABLED
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
+ {0, NULL}
+};
+
PyMODINIT_FUNC
PyInit__imagingcms(void) {
- PyObject *m;
+ PyDateTime_IMPORT;
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "_imagingcms",
- .m_size = -1,
.m_methods = pyCMSdll_methods,
+ .m_slots = slots
};
- m = PyModule_Create(&module_def);
-
- if (setup_module(m) < 0) {
- return NULL;
- }
-
- PyDateTime_IMPORT;
-
-#ifdef Py_GIL_DISABLED
- PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
-#endif
-
- return m;
+ return PyModuleDef_Init(&module_def);
}
diff --git a/src/_imagingft.c b/src/_imagingft.c
index 0d70544a5..29d8e9e71 100644
--- a/src/_imagingft.c
+++ b/src/_imagingft.c
@@ -1601,26 +1601,22 @@ setup_module(PyObject *m) {
return 0;
}
+static PyModuleDef_Slot slots[] = {
+ {Py_mod_exec, setup_module},
+#ifdef Py_GIL_DISABLED
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
+ {0, NULL}
+};
+
PyMODINIT_FUNC
PyInit__imagingft(void) {
- PyObject *m;
-
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "_imagingft",
- .m_size = -1,
.m_methods = _functions,
+ .m_slots = slots
};
- m = PyModule_Create(&module_def);
-
- if (setup_module(m) < 0) {
- return NULL;
- }
-
-#ifdef Py_GIL_DISABLED
- PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
-#endif
-
- return m;
+ return PyModuleDef_Init(&module_def);
}
diff --git a/src/_imagingmath.c b/src/_imagingmath.c
index 4b9bf08ba..48c395900 100644
--- a/src/_imagingmath.c
+++ b/src/_imagingmath.c
@@ -302,26 +302,22 @@ setup_module(PyObject *m) {
return 0;
}
+static PyModuleDef_Slot slots[] = {
+ {Py_mod_exec, setup_module},
+#ifdef Py_GIL_DISABLED
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
+ {0, NULL}
+};
+
PyMODINIT_FUNC
PyInit__imagingmath(void) {
- PyObject *m;
-
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "_imagingmath",
- .m_size = -1,
.m_methods = _functions,
+ .m_slots = slots
};
- m = PyModule_Create(&module_def);
-
- if (setup_module(m) < 0) {
- return NULL;
- }
-
-#ifdef Py_GIL_DISABLED
- PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
-#endif
-
- return m;
+ return PyModuleDef_Init(&module_def);
}
diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c
index a20888294..5995f9d42 100644
--- a/src/_imagingmorph.c
+++ b/src/_imagingmorph.c
@@ -246,23 +246,22 @@ static PyMethodDef functions[] = {
{NULL, NULL, 0, NULL}
};
+static PyModuleDef_Slot slots[] = {
+#ifdef Py_GIL_DISABLED
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
+ {0, NULL}
+};
+
PyMODINIT_FUNC
PyInit__imagingmorph(void) {
- PyObject *m;
-
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "_imagingmorph",
.m_doc = "A module for doing image morphology",
- .m_size = -1,
.m_methods = functions,
+ .m_slots = slots
};
- m = PyModule_Create(&module_def);
-
-#ifdef Py_GIL_DISABLED
- PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
-#endif
-
- return m;
+ return PyModuleDef_Init(&module_def);
}
diff --git a/src/_imagingtk.c b/src/_imagingtk.c
index 4e06fe9b8..68d7bf4cd 100644
--- a/src/_imagingtk.c
+++ b/src/_imagingtk.c
@@ -46,24 +46,22 @@ static PyMethodDef functions[] = {
{NULL, NULL} /* sentinel */
};
+static PyModuleDef_Slot slots[] = {
+ {Py_mod_exec, load_tkinter_funcs},
+#ifdef Py_GIL_DISABLED
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
+ {0, NULL}
+};
+
PyMODINIT_FUNC
PyInit__imagingtk(void) {
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "_imagingtk",
- .m_size = -1,
.m_methods = functions,
+ .m_slots = slots
};
- PyObject *m;
- m = PyModule_Create(&module_def);
- if (load_tkinter_funcs() != 0) {
- Py_DECREF(m);
- return NULL;
- }
-#ifdef Py_GIL_DISABLED
- PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
-#endif
-
- return m;
+ return PyModuleDef_Init(&module_def);
}
diff --git a/src/_webp.c b/src/_webp.c
index a7809c40e..e84e786ed 100644
--- a/src/_webp.c
+++ b/src/_webp.c
@@ -780,26 +780,22 @@ setup_module(PyObject *m) {
return 0;
}
+static PyModuleDef_Slot slots[] = {
+ {Py_mod_exec, setup_module},
+#ifdef Py_GIL_DISABLED
+ {Py_mod_gil, Py_MOD_GIL_NOT_USED},
+#endif
+ {0, NULL}
+};
+
PyMODINIT_FUNC
PyInit__webp(void) {
- PyObject *m;
-
static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT,
.m_name = "_webp",
- .m_size = -1,
.m_methods = webpMethods,
+ .m_slots = slots
};
- m = PyModule_Create(&module_def);
- if (setup_module(m) < 0) {
- Py_DECREF(m);
- return NULL;
- }
-
-#ifdef Py_GIL_DISABLED
- PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
-#endif
-
- return m;
+ return PyModuleDef_Init(&module_def);
}
diff --git a/src/libImaging/Arrow.c b/src/libImaging/Arrow.c
index 0b8c89a07..ccafe33b9 100644
--- a/src/libImaging/Arrow.c
+++ b/src/libImaging/Arrow.c
@@ -101,7 +101,7 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
}
/* for now, single block images */
- if (!(im->blocks_count == 0 || im->blocks_count == 1)) {
+ if (im->blocks_count > 1) {
return IMAGING_ARROW_MEMORY_LAYOUT;
}
@@ -159,7 +159,7 @@ export_single_channel_array(Imaging im, struct ArrowArray *array) {
int length = im->xsize * im->ysize;
/* for now, single block images */
- if (!(im->blocks_count == 0 || im->blocks_count == 1)) {
+ if (im->blocks_count > 1) {
return IMAGING_ARROW_MEMORY_LAYOUT;
}
@@ -202,7 +202,7 @@ export_fixed_pixel_array(Imaging im, struct ArrowArray *array) {
int length = im->xsize * im->ysize;
/* for now, single block images */
- if (!(im->blocks_count == 0 || im->blocks_count == 1)) {
+ if (im->blocks_count > 1) {
return IMAGING_ARROW_MEMORY_LAYOUT;
}
diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c
index d5aff8709..27cac687e 100644
--- a/src/libImaging/Draw.c
+++ b/src/libImaging/Draw.c
@@ -63,7 +63,7 @@ typedef struct {
} Edge;
/* Type used in "polygon*" functions */
-typedef void (*hline_handler)(Imaging, int, int, int, int);
+typedef void (*hline_handler)(Imaging, int, int, int, int, Imaging);
static inline void
point8(Imaging im, int x, int y, int ink) {
@@ -103,9 +103,7 @@ point32rgba(Imaging im, int x, int y, int ink) {
}
static inline void
-hline8(Imaging im, int x0, int y0, int x1, int ink) {
- int pixelwidth;
-
+hline8(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
if (y0 >= 0 && y0 < im->ysize) {
if (x0 < 0) {
x0 = 0;
@@ -118,16 +116,41 @@ hline8(Imaging im, int x0, int y0, int x1, int ink) {
x1 = im->xsize - 1;
}
if (x0 <= x1) {
- pixelwidth = strncmp(im->mode, "I;16", 4) == 0 ? 2 : 1;
- memset(
- im->image8[y0] + x0 * pixelwidth, (UINT8)ink, (x1 - x0 + 1) * pixelwidth
- );
+ int bigendian = -1;
+ if (strncmp(im->mode, "I;16", 4) == 0) {
+ bigendian =
+ (
+#ifdef WORDS_BIGENDIAN
+ strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16L") == 0
+#else
+ strcmp(im->mode, "I;16B") == 0
+#endif
+ )
+ ? 1
+ : 0;
+ }
+ if (mask == NULL && bigendian == -1) {
+ memset(im->image8[y0] + x0, (UINT8)ink, (x1 - x0 + 1));
+ } else {
+ UINT8 *p = im->image8[y0];
+ while (x0 <= x1) {
+ if (mask == NULL || mask->image8[y0][x0]) {
+ if (bigendian == -1) {
+ p[x0] = ink;
+ } else {
+ p[x0 * 2 + (bigendian ? 1 : 0)] = ink;
+ p[x0 * 2 + (bigendian ? 0 : 1)] = ink >> 8;
+ }
+ }
+ x0++;
+ }
+ }
}
}
}
static inline void
-hline32(Imaging im, int x0, int y0, int x1, int ink) {
+hline32(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
INT32 *p;
if (y0 >= 0 && y0 < im->ysize) {
@@ -143,13 +166,16 @@ hline32(Imaging im, int x0, int y0, int x1, int ink) {
}
p = im->image32[y0];
while (x0 <= x1) {
- p[x0++] = ink;
+ if (mask == NULL || mask->image8[y0][x0]) {
+ p[x0] = ink;
+ }
+ x0++;
}
}
}
static inline void
-hline32rgba(Imaging im, int x0, int y0, int x1, int ink) {
+hline32rgba(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
unsigned int tmp;
if (y0 >= 0 && y0 < im->ysize) {
@@ -167,9 +193,11 @@ hline32rgba(Imaging im, int x0, int y0, int x1, int ink) {
UINT8 *out = (UINT8 *)im->image[y0] + x0 * 4;
UINT8 *in = (UINT8 *)&ink;
while (x0 <= x1) {
- out[0] = BLEND(in[3], out[0], in[0], tmp);
- out[1] = BLEND(in[3], out[1], in[1], tmp);
- out[2] = BLEND(in[3], out[2], in[2], tmp);
+ if (mask == NULL || mask->image8[y0][x0]) {
+ out[0] = BLEND(in[3], out[0], in[0], tmp);
+ out[1] = BLEND(in[3], out[1], in[1], tmp);
+ out[2] = BLEND(in[3], out[2], in[2], tmp);
+ }
x0++;
out += 4;
}
@@ -407,7 +435,14 @@ x_cmp(const void *x0, const void *x1) {
static void
draw_horizontal_lines(
- Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hline_handler hline
+ Imaging im,
+ int n,
+ Edge *e,
+ int ink,
+ int *x_pos,
+ int y,
+ hline_handler hline,
+ Imaging mask
) {
int i;
for (i = 0; i < n; i++) {
@@ -429,7 +464,7 @@ draw_horizontal_lines(
}
}
- (*hline)(im, xmin, e[i].ymin, xmax, ink);
+ (*hline)(im, xmin, e[i].ymin, xmax, ink, mask);
*x_pos = xmax + 1;
}
}
@@ -440,7 +475,7 @@ draw_horizontal_lines(
*/
static inline int
polygon_generic(
- Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, int hasAlpha
+ Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, Imaging mask
) {
Edge **edge_table;
float *xx;
@@ -461,6 +496,7 @@ polygon_generic(
return -1;
}
+ int hasAlpha = hline == hline32rgba;
for (i = 0; i < n; i++) {
if (ymin > e[i].ymin) {
ymin = e[i].ymin;
@@ -470,7 +506,7 @@ polygon_generic(
}
if (e[i].ymin == e[i].ymax) {
if (hasAlpha != 1) {
- (*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink);
+ (*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink, mask);
}
continue;
}
@@ -558,7 +594,7 @@ polygon_generic(
// Line would be before the current position
continue;
}
- draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline);
+ draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline, mask);
if (x_end < x_pos) {
// Line would be before the current position
continue;
@@ -574,13 +610,13 @@ polygon_generic(
continue;
}
}
- (*hline)(im, x_start, ymin, x_end, ink);
+ (*hline)(im, x_start, ymin, x_end, ink, mask);
x_pos = x_end + 1;
}
- draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline);
+ draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline, mask);
} else {
for (i = 1; i < j; i += 2) {
- (*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink);
+ (*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink, mask);
}
}
}
@@ -590,21 +626,6 @@ polygon_generic(
return 0;
}
-static inline int
-polygon8(Imaging im, int n, Edge *e, int ink, int eofill) {
- return polygon_generic(im, n, e, ink, eofill, hline8, 0);
-}
-
-static inline int
-polygon32(Imaging im, int n, Edge *e, int ink, int eofill) {
- return polygon_generic(im, n, e, ink, eofill, hline32, 0);
-}
-
-static inline int
-polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) {
- return polygon_generic(im, n, e, ink, eofill, hline32rgba, 1);
-}
-
static inline void
add_edge(Edge *e, int x0, int y0, int x1, int y1) {
/* printf("edge %d %d %d %d\n", x0, y0, x1, y1); */
@@ -639,14 +660,13 @@ add_edge(Edge *e, int x0, int y0, int x1, int y1) {
typedef struct {
void (*point)(Imaging im, int x, int y, int ink);
- void (*hline)(Imaging im, int x0, int y0, int x1, int ink);
+ void (*hline)(Imaging im, int x0, int y0, int x1, int ink, Imaging mask);
void (*line)(Imaging im, int x0, int y0, int x1, int y1, int ink);
- int (*polygon)(Imaging im, int n, Edge *e, int ink, int eofill);
} DRAW;
-DRAW draw8 = {point8, hline8, line8, polygon8};
-DRAW draw32 = {point32, hline32, line32, polygon32};
-DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba, polygon32rgba};
+DRAW draw8 = {point8, hline8, line8};
+DRAW draw32 = {point32, hline32, line32};
+DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba};
/* -------------------------------------------------------------------- */
/* Interface */
@@ -691,7 +711,15 @@ ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink_, in
int
ImagingDrawWideLine(
- Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int width, int op
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ const void *ink_,
+ int width,
+ int op,
+ Imaging mask
) {
DRAW *draw;
INT32 ink;
@@ -731,7 +759,7 @@ ImagingDrawWideLine(
add_edge(e + 2, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1]);
add_edge(e + 3, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1]);
- draw->polygon(im, 4, e, ink, 0);
+ polygon_generic(im, 4, e, ink, 0, draw->hline, mask);
}
return 0;
}
@@ -774,7 +802,7 @@ ImagingDrawRectangle(
}
for (y = y0; y <= y1; y++) {
- draw->hline(im, x0, y, x1, ink);
+ draw->hline(im, x0, y, x1, ink, NULL);
}
} else {
@@ -783,8 +811,8 @@ ImagingDrawRectangle(
width = 1;
}
for (i = 0; i < width; i++) {
- draw->hline(im, x0, y0 + i, x1, ink);
- draw->hline(im, x0, y1 - i, x1, ink);
+ draw->hline(im, x0, y0 + i, x1, ink, NULL);
+ draw->hline(im, x0, y1 - i, x1, ink, NULL);
draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink);
draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink);
}
@@ -795,7 +823,14 @@ ImagingDrawRectangle(
int
ImagingDrawPolygon(
- Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op
+ Imaging im,
+ int count,
+ int *xy,
+ const void *ink_,
+ int fill,
+ int width,
+ int op,
+ Imaging mask
) {
int i, n, x0, y0, x1, y1;
DRAW *draw;
@@ -839,7 +874,7 @@ ImagingDrawPolygon(
if (xy[i * 2] != xy[0] || xy[i * 2 + 1] != xy[1]) {
add_edge(&e[n++], xy[i * 2], xy[i * 2 + 1], xy[0], xy[1]);
}
- draw->polygon(im, n, e, ink, 0);
+ polygon_generic(im, n, e, ink, 0, draw->hline, mask);
free(e);
} else {
@@ -861,11 +896,12 @@ ImagingDrawPolygon(
xy[i * 2 + 3],
ink_,
width,
- op
+ op,
+ mask
);
}
ImagingDrawWideLine(
- im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op
+ im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op, mask
);
}
}
@@ -1536,7 +1572,9 @@ ellipseNew(
ellipse_init(&st, a, b, width);
int32_t X0, Y, X1;
while (ellipse_next(&st, &X0, &Y, &X1) != -1) {
- draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink);
+ draw->hline(
+ im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink, NULL
+ );
}
return 0;
}
@@ -1571,7 +1609,9 @@ clipEllipseNew(
int32_t X0, Y, X1;
int next_code;
while ((next_code = clip_ellipse_next(&st, &X0, &Y, &X1)) >= 0) {
- draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink);
+ draw->hline(
+ im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink, NULL
+ );
}
clip_ellipse_free(&st);
return next_code == -1 ? 0 : -1;
@@ -1989,7 +2029,7 @@ ImagingDrawOutline(
DRAWINIT();
- draw->polygon(im, outline->count, outline->edges, ink, 0);
+ polygon_generic(im, outline->count, outline->edges, ink, 0, draw->hline, NULL);
return 0;
}
diff --git a/src/libImaging/File.c b/src/libImaging/File.c
index 76d0abccc..901fe83ad 100644
--- a/src/libImaging/File.c
+++ b/src/libImaging/File.c
@@ -54,7 +54,7 @@ ImagingSavePPM(Imaging im, const char *outfile) {
fp = fopen(outfile, "wb");
if (!fp) {
- (void)ImagingError_OSError();
+ PyErr_SetString(PyExc_OSError, "error when accessing file");
return 0;
}
diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h
index 234f9943c..bfe67d462 100644
--- a/src/libImaging/Imaging.h
+++ b/src/libImaging/Imaging.h
@@ -270,8 +270,6 @@ ImagingSectionLeave(ImagingSectionCookie *cookie);
/* Exceptions */
/* ---------- */
-extern void *
-ImagingError_OSError(void);
extern void *
ImagingError_MemoryError(void);
extern void *
@@ -280,8 +278,6 @@ extern void *
ImagingError_Mismatch(void); /* maps to ValueError by default */
extern void *
ImagingError_ValueError(const char *message);
-extern void
-ImagingError_Clear(void);
/* Transform callbacks */
/* ------------------- */
@@ -510,7 +506,15 @@ extern int
ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink, int op);
extern int
ImagingDrawWideLine(
- Imaging im, int x0, int y0, int x1, int y1, const void *ink, int width, int op
+ Imaging im,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ const void *ink,
+ int width,
+ int op,
+ Imaging mask
);
extern int
ImagingDrawPieslice(
@@ -530,7 +534,14 @@ extern int
ImagingDrawPoint(Imaging im, int x, int y, const void *ink, int op);
extern int
ImagingDrawPolygon(
- Imaging im, int points, int *xy, const void *ink, int fill, int width, int op
+ Imaging im,
+ int points,
+ int *xy,
+ const void *ink,
+ int fill,
+ int width,
+ int op,
+ Imaging mask
);
extern int
ImagingDrawRectangle(
diff --git a/src/libImaging/PcxDecode.c b/src/libImaging/PcxDecode.c
index 942c8dc22..a65952fb1 100644
--- a/src/libImaging/PcxDecode.c
+++ b/src/libImaging/PcxDecode.c
@@ -60,15 +60,25 @@ ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
}
if (state->x >= state->bytes) {
- if (state->bytes % state->xsize && state->bytes > state->xsize) {
- int bands = state->bytes / state->xsize;
- int stride = state->bytes / bands;
+ int bands;
+ int xsize = 0;
+ int stride = 0;
+ if (state->bits == 2 || state->bits == 4) {
+ xsize = (state->xsize + 7) / 8;
+ bands = state->bits;
+ stride = state->bytes / state->bits;
+ } else {
+ xsize = state->xsize;
+ bands = state->bytes / state->xsize;
+ if (bands != 0) {
+ stride = state->bytes / bands;
+ }
+ }
+ if (stride > xsize) {
int i;
for (i = 1; i < bands; i++) { // note -- skipping first band
memmove(
- &state->buffer[i * state->xsize],
- &state->buffer[i * stride],
- state->xsize
+ &state->buffer[i * xsize], &state->buffer[i * stride], xsize
);
}
}
diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c
index 11d6c06cc..6fe26e1bd 100644
--- a/src/libImaging/Storage.c
+++ b/src/libImaging/Storage.c
@@ -645,7 +645,7 @@ ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) {
return im;
}
- ImagingError_Clear();
+ PyErr_Clear();
// Try to allocate the image once more with smallest possible block size
MUTEX_LOCK(&ImagingDefaultArena.mutex);
diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c
index 2e83fb847..e289ce405 100644
--- a/src/libImaging/TiffDecode.c
+++ b/src/libImaging/TiffDecode.c
@@ -1032,7 +1032,10 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
TRACE(("Encode Error, row %d\n", state->y));
state->errcode = IMAGING_CODEC_BROKEN;
- if (!clientstate->fp) {
+ if (clientstate->fp) {
+ TIFFCleanup(tiff);
+ clientstate->tiff = NULL;
+ } else {
free(clientstate->data);
}
return -1;
diff --git a/src/map.c b/src/map.c
index c66702981..9a3144ab9 100644
--- a/src/map.c
+++ b/src/map.c
@@ -137,6 +137,7 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) {
}
}
+ im->read_only = view.readonly;
im->destroy = mapping_destroy_buffer;
Py_INCREF(target);
diff --git a/tox.ini b/tox.ini
index 4065245ee..967d4b537 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,7 +3,7 @@ requires =
tox>=4.2
env_list =
lint
- py{py3, 313, 312, 311, 310, 39}
+ py{py3, 314, 313, 312, 311, 310, 39}
[testenv]
deps =
diff --git a/wheels/dependency_licenses/AOM.txt b/wheels/dependency_licenses/AOM.txt
new file mode 100644
index 000000000..3a2e46c26
--- /dev/null
+++ b/wheels/dependency_licenses/AOM.txt
@@ -0,0 +1,26 @@
+Copyright (c) 2016, Alliance for Open Media. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/wheels/dependency_licenses/DAV1D.txt b/wheels/dependency_licenses/DAV1D.txt
new file mode 100644
index 000000000..875b138ec
--- /dev/null
+++ b/wheels/dependency_licenses/DAV1D.txt
@@ -0,0 +1,23 @@
+Copyright © 2018-2019, VideoLAN and dav1d authors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/wheels/dependency_licenses/LIBAVIF.txt b/wheels/dependency_licenses/LIBAVIF.txt
new file mode 100644
index 000000000..350eb9d15
--- /dev/null
+++ b/wheels/dependency_licenses/LIBAVIF.txt
@@ -0,0 +1,387 @@
+Copyright 2019 Joe Drago. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+------------------------------------------------------------------------------
+
+Files: src/obu.c
+
+Copyright © 2018-2019, VideoLAN and dav1d authors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+------------------------------------------------------------------------------
+
+Files: third_party/iccjpeg/*
+
+In plain English:
+
+1. We don't promise that this software works. (But if you find any bugs,
+ please let us know!)
+2. You can use this software for whatever you want. You don't have to pay us.
+3. You may not pretend that you wrote this software. If you use it in a
+ program, you must acknowledge somewhere in your documentation that
+ you've used the IJG code.
+
+In legalese:
+
+The authors make NO WARRANTY or representation, either express or implied,
+with respect to this software, its quality, accuracy, merchantability, or
+fitness for a particular purpose. This software is provided "AS IS", and you,
+its user, assume the entire risk as to its quality and accuracy.
+
+This software is copyright (C) 1991-2013, Thomas G. Lane, Guido Vollbeding.
+All Rights Reserved except as specified below.
+
+Permission is hereby granted to use, copy, modify, and distribute this
+software (or portions thereof) for any purpose, without fee, subject to these
+conditions:
+(1) If any part of the source code for this software is distributed, then this
+README file must be included, with this copyright and no-warranty notice
+unaltered; and any additions, deletions, or changes to the original files
+must be clearly indicated in accompanying documentation.
+(2) If only executable code is distributed, then the accompanying
+documentation must state that "this software is based in part on the work of
+the Independent JPEG Group".
+(3) Permission for use of this software is granted only if the user accepts
+full responsibility for any undesirable consequences; the authors accept
+NO LIABILITY for damages of any kind.
+
+These conditions apply to any software derived from or based on the IJG code,
+not just to the unmodified library. If you use our work, you ought to
+acknowledge us.
+
+Permission is NOT granted for the use of any IJG author's name or company name
+in advertising or publicity relating to this software or products derived from
+it. This software may be referred to only as "the Independent JPEG Group's
+software".
+
+We specifically permit and encourage the use of this software as the basis of
+commercial products, provided that all warranty or liability claims are
+assumed by the product vendor.
+
+
+The Unix configuration script "configure" was produced with GNU Autoconf.
+It is copyright by the Free Software Foundation but is freely distributable.
+The same holds for its supporting scripts (config.guess, config.sub,
+ltmain.sh). Another support script, install-sh, is copyright by X Consortium
+but is also freely distributable.
+
+The IJG distribution formerly included code to read and write GIF files.
+To avoid entanglement with the Unisys LZW patent, GIF reading support has
+been removed altogether, and the GIF writer has been simplified to produce
+"uncompressed GIFs". This technique does not use the LZW algorithm; the
+resulting GIF files are larger than usual, but are readable by all standard
+GIF decoders.
+
+We are required to state that
+ "The Graphics Interchange Format(c) is the Copyright property of
+ CompuServe Incorporated. GIF(sm) is a Service Mark property of
+ CompuServe Incorporated."
+
+------------------------------------------------------------------------------
+
+Files: contrib/gdk-pixbuf/*
+
+Copyright 2020 Emmanuel Gil Peyrot. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+------------------------------------------------------------------------------
+
+Files: android_jni/gradlew*
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+------------------------------------------------------------------------------
+
+Files: third_party/libyuv/*
+
+Copyright 2011 The LibYuv Project Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Google nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/wheels/dependency_licenses/LIBYUV.txt b/wheels/dependency_licenses/LIBYUV.txt
new file mode 100644
index 000000000..c911747a6
--- /dev/null
+++ b/wheels/dependency_licenses/LIBYUV.txt
@@ -0,0 +1,29 @@
+Copyright 2011 The LibYuv Project Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Google nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py
index 6e176e29c..187d07b20 100644
--- a/winbuild/build_prepare.py
+++ b/winbuild/build_prepare.py
@@ -57,7 +57,10 @@ def cmd_nmake(
def cmds_cmake(
- target: str | tuple[str, ...] | list[str], *params: str, build_dir: str = "."
+ target: str | tuple[str, ...] | list[str],
+ *params: str,
+ build_dir: str = ".",
+ build_type: str = "Release",
) -> list[str]:
if not isinstance(target, str):
target = " ".join(target)
@@ -66,7 +69,7 @@ def cmds_cmake(
" ".join(
[
"{cmake}",
- "-DCMAKE_BUILD_TYPE=Release",
+ f"-DCMAKE_BUILD_TYPE={build_type}",
"-DCMAKE_VERBOSE_MAKEFILE=ON",
"-DCMAKE_RULE_MESSAGES:BOOL=OFF", # for NMake
"-DCMAKE_C_COMPILER=cl.exe", # for Ninja
@@ -114,11 +117,11 @@ V = {
"FREETYPE": "2.13.3",
"FRIBIDI": "1.0.16",
"HARFBUZZ": "11.2.1",
- "JPEGTURBO": "3.1.0",
+ "JPEGTURBO": "3.1.1",
"LCMS2": "2.17",
"LIBAVIF": "1.3.0",
"LIBIMAGEQUANT": "4.3.4",
- "LIBPNG": "1.6.48",
+ "LIBPNG": "1.6.49",
"LIBWEBP": "1.5.0",
"OPENJPEG": "2.5.3",
"TIFF": "4.7.0",
@@ -385,8 +388,8 @@ DEPS: dict[str, dict[str, Any]] = {
"bins": [r"*.dll"],
},
"libavif": {
- "url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['LIBAVIF']}.zip",
- "filename": f"libavif-{V['LIBAVIF']}.zip",
+ "url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['LIBAVIF']}.tar.gz",
+ "filename": f"libavif-{V['LIBAVIF']}.tar.gz",
"license": "LICENSE",
"build": [
"rustup update",
@@ -397,9 +400,11 @@ DEPS: dict[str, dict[str, Any]] = {
"-DAVIF_LIBSHARPYUV=LOCAL",
"-DAVIF_LIBYUV=LOCAL",
"-DAVIF_CODEC_AOM=LOCAL",
+ "-DCONFIG_AV1_HIGHBITDEPTH=0",
+ "-DAVIF_CODEC_AOM_DECODE=OFF",
"-DAVIF_CODEC_DAV1D=LOCAL",
- "-DAVIF_CODEC_RAV1E=LOCAL",
- "-DAVIF_CODEC_SVT=LOCAL",
+ "-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON",
+ build_type="MinSizeRel",
),
cmd_xcopy("include", "{inc_dir}"),
],
@@ -755,7 +760,7 @@ def main() -> None:
disabled += ["libimagequant"]
if args.no_fribidi:
disabled += ["fribidi"]
- if args.no_avif or args.architecture != "AMD64":
+ if args.no_avif or args.architecture == "ARM64":
disabled += ["libavif"]
prefs = {