mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-09-24 13:07:00 +03:00
Merge branch 'main' into fp
This commit is contained in:
commit
cbb1e36bea
19
.github/workflows/wheels-dependencies.sh
vendored
19
.github/workflows/wheels-dependencies.sh
vendored
|
@ -96,13 +96,13 @@ ARCHIVE_SDIR=pillow-depends-main
|
||||||
FREETYPE_VERSION=2.13.3
|
FREETYPE_VERSION=2.13.3
|
||||||
HARFBUZZ_VERSION=11.3.3
|
HARFBUZZ_VERSION=11.3.3
|
||||||
LIBPNG_VERSION=1.6.50
|
LIBPNG_VERSION=1.6.50
|
||||||
JPEGTURBO_VERSION=3.1.1
|
JPEGTURBO_VERSION=3.1.2
|
||||||
OPENJPEG_VERSION=2.5.3
|
OPENJPEG_VERSION=2.5.3
|
||||||
XZ_VERSION=5.8.1
|
XZ_VERSION=5.8.1
|
||||||
|
ZSTD_VERSION=1.5.7
|
||||||
TIFF_VERSION=4.7.0
|
TIFF_VERSION=4.7.0
|
||||||
LCMS2_VERSION=2.17
|
LCMS2_VERSION=2.17
|
||||||
ZLIB_VERSION=1.3.1
|
ZLIB_NG_VERSION=2.2.5
|
||||||
ZLIB_NG_VERSION=2.2.4
|
|
||||||
LIBWEBP_VERSION=1.6.0
|
LIBWEBP_VERSION=1.6.0
|
||||||
BZIP2_VERSION=1.0.8
|
BZIP2_VERSION=1.0.8
|
||||||
LIBXCB_VERSION=1.17.0
|
LIBXCB_VERSION=1.17.0
|
||||||
|
@ -254,16 +254,20 @@ function build_libavif {
|
||||||
touch libavif-stamp
|
touch libavif-stamp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function build_zstd {
|
||||||
|
if [ -e zstd-stamp ]; then return; fi
|
||||||
|
local out_dir=$(fetch_unpack https://github.com/facebook/zstd/releases/download/v$ZSTD_VERSION/zstd-$ZSTD_VERSION.tar.gz)
|
||||||
|
(cd $out_dir \
|
||||||
|
&& make -j4 install)
|
||||||
|
touch zstd-stamp
|
||||||
|
}
|
||||||
|
|
||||||
function build {
|
function build {
|
||||||
build_xz
|
build_xz
|
||||||
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
|
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
|
||||||
yum remove -y zlib-devel
|
yum remove -y zlib-devel
|
||||||
fi
|
fi
|
||||||
if [[ -n "$IS_MACOS" ]] && [[ "$MACOSX_DEPLOYMENT_TARGET" == "10.10" || "$MACOSX_DEPLOYMENT_TARGET" == "10.13" ]]; then
|
|
||||||
build_new_zlib
|
|
||||||
else
|
|
||||||
build_zlib_ng
|
build_zlib_ng
|
||||||
fi
|
|
||||||
|
|
||||||
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
|
build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
|
||||||
if [[ -n "$IS_MACOS" ]]; then
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
|
@ -285,6 +289,7 @@ function build {
|
||||||
--with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \
|
--with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \
|
||||||
--disable-webp --disable-libdeflate --disable-zstd
|
--disable-webp --disable-libdeflate --disable-zstd
|
||||||
else
|
else
|
||||||
|
build_zstd
|
||||||
build_tiff
|
build_tiff
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.12.7
|
rev: v0.12.11
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff-check
|
- id: ruff-check
|
||||||
args: [--exit-non-zero-on-fix]
|
args: [--exit-non-zero-on-fix]
|
||||||
|
@ -24,7 +24,7 @@ repos:
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: v20.1.8
|
rev: v21.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
types: [c]
|
types: [c]
|
||||||
|
@ -36,7 +36,7 @@ repos:
|
||||||
- id: rst-backticks
|
- id: rst-backticks
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v5.0.0
|
rev: v6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-executables-have-shebangs
|
- id: check-executables-have-shebangs
|
||||||
- id: check-shebang-scripts-are-executable
|
- id: check-shebang-scripts-are-executable
|
||||||
|
@ -51,14 +51,14 @@ repos:
|
||||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
|
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
|
||||||
|
|
||||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||||
rev: 0.33.2
|
rev: 0.33.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-github-workflows
|
- id: check-github-workflows
|
||||||
- id: check-readthedocs
|
- id: check-readthedocs
|
||||||
- id: check-renovate
|
- id: check-renovate
|
||||||
|
|
||||||
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
||||||
rev: v1.11.0
|
rev: v1.12.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: zizmor
|
- id: zizmor
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import GbrImagePlugin, Image
|
from PIL import GbrImagePlugin, Image, _binary
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import assert_image_equal_tofile
|
||||||
|
|
||||||
|
@ -31,8 +33,49 @@ def test_multiple_load_operations() -> None:
|
||||||
assert_image_equal_tofile(im, "Tests/images/gbr.png")
|
assert_image_equal_tofile(im, "Tests/images/gbr.png")
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_file() -> None:
|
def create_gbr_image(info: dict[str, int] = {}, magic_number=b"") -> BytesIO:
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
return BytesIO(
|
||||||
|
b"".join(
|
||||||
|
_binary.o32be(i)
|
||||||
|
for i in [
|
||||||
|
info.get("header_size", 20),
|
||||||
|
info.get("version", 1),
|
||||||
|
info.get("width", 1),
|
||||||
|
info.get("height", 1),
|
||||||
|
info.get("color_depth", 1),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
+ magic_number
|
||||||
|
)
|
||||||
|
|
||||||
with pytest.raises(SyntaxError):
|
|
||||||
|
def test_invalid_file() -> None:
|
||||||
|
for f in [
|
||||||
|
create_gbr_image({"header_size": 0}),
|
||||||
|
create_gbr_image({"width": 0}),
|
||||||
|
create_gbr_image({"height": 0}),
|
||||||
|
]:
|
||||||
|
with pytest.raises(SyntaxError, match="not a GIMP brush"):
|
||||||
|
GbrImagePlugin.GbrImageFile(f)
|
||||||
|
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
with pytest.raises(SyntaxError, match="Unsupported GIMP brush version"):
|
||||||
GbrImagePlugin.GbrImageFile(invalid_file)
|
GbrImagePlugin.GbrImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsupported_gimp_brush() -> None:
|
||||||
|
f = create_gbr_image({"color_depth": 2})
|
||||||
|
with pytest.raises(SyntaxError, match="Unsupported GIMP brush color depth: 2"):
|
||||||
|
GbrImagePlugin.GbrImageFile(f)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bad_magic_number() -> None:
|
||||||
|
f = create_gbr_image({"version": 2}, magic_number=b"badm")
|
||||||
|
with pytest.raises(SyntaxError, match="not a GIMP brush, bad magic number"):
|
||||||
|
GbrImagePlugin.GbrImageFile(f)
|
||||||
|
|
||||||
|
|
||||||
|
def test_L() -> None:
|
||||||
|
f = create_gbr_image()
|
||||||
|
with Image.open(f) as im:
|
||||||
|
assert im.mode == "L"
|
||||||
|
|
|
@ -2,6 +2,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags
|
from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
@ -9,21 +11,78 @@ from .helper import assert_image_equal, hopper
|
||||||
TEST_FILE = "Tests/images/iptc.jpg"
|
TEST_FILE = "Tests/images/iptc.jpg"
|
||||||
|
|
||||||
|
|
||||||
|
def create_iptc_image(info: dict[str, int] = {}) -> BytesIO:
|
||||||
|
def field(tag, value):
|
||||||
|
return bytes((0x1C,) + tag + (0, len(value))) + value
|
||||||
|
|
||||||
|
data = field((3, 60), bytes((info.get("layers", 1), info.get("component", 0))))
|
||||||
|
data += field((3, 120), bytes((info.get("compression", 1),)))
|
||||||
|
if "band" in info:
|
||||||
|
data += field((3, 65), bytes((info["band"] + 1,)))
|
||||||
|
data += field((3, 20), b"\x01") # width
|
||||||
|
data += field((3, 30), b"\x01") # height
|
||||||
|
data += field(
|
||||||
|
(8, 10),
|
||||||
|
bytes((info.get("data", 0),)),
|
||||||
|
)
|
||||||
|
|
||||||
|
return BytesIO(data)
|
||||||
|
|
||||||
|
|
||||||
def test_open() -> None:
|
def test_open() -> None:
|
||||||
expected = Image.new("L", (1, 1))
|
expected = Image.new("L", (1, 1))
|
||||||
|
|
||||||
f = BytesIO(
|
f = create_iptc_image()
|
||||||
b"\x1c\x03<\x00\x02\x01\x00\x1c\x03x\x00\x01\x01\x1c\x03\x14\x00\x01\x01"
|
|
||||||
b"\x1c\x03\x1e\x00\x01\x01\x1c\x08\n\x00\x01\x00"
|
|
||||||
)
|
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")]
|
assert im.tile == [("iptc", (0, 0, 1, 1), 25, ("raw", None))]
|
||||||
assert_image_equal(im, expected)
|
assert_image_equal(im, expected)
|
||||||
|
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
assert im.load() is not None
|
assert im.load() is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_field_length() -> None:
|
||||||
|
f = create_iptc_image()
|
||||||
|
f.seek(28)
|
||||||
|
f.write(b"\xff")
|
||||||
|
with pytest.raises(OSError, match="illegal field length in IPTC/NAA file"):
|
||||||
|
with Image.open(f):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("layers, mode", ((3, "RGB"), (4, "CMYK")))
|
||||||
|
def test_layers(layers: int, mode: str) -> None:
|
||||||
|
for band in range(-1, layers):
|
||||||
|
info = {"layers": layers, "component": 1, "data": 5}
|
||||||
|
if band != -1:
|
||||||
|
info["band"] = band
|
||||||
|
f = create_iptc_image(info)
|
||||||
|
with Image.open(f) as im:
|
||||||
|
assert im.mode == mode
|
||||||
|
|
||||||
|
data = [0] * layers
|
||||||
|
data[max(band, 0)] = 5
|
||||||
|
assert im.getpixel((0, 0)) == tuple(data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_compression() -> None:
|
||||||
|
f = create_iptc_image({"compression": 2})
|
||||||
|
with pytest.raises(OSError, match="Unknown IPTC image compression"):
|
||||||
|
with Image.open(f):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_getiptcinfo() -> None:
|
||||||
|
f = create_iptc_image()
|
||||||
|
with Image.open(f) as im:
|
||||||
|
assert IptcImagePlugin.getiptcinfo(im) == {
|
||||||
|
(3, 60): b"\x01\x00",
|
||||||
|
(3, 120): b"\x01",
|
||||||
|
(3, 20): b"\x01",
|
||||||
|
(3, 30): b"\x01",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_getiptcinfo_jpg_none() -> None:
|
def test_getiptcinfo_jpg_none() -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
with hopper() as im:
|
with hopper() as im:
|
||||||
|
|
|
@ -373,7 +373,6 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
|
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
|
||||||
if 700 in reloaded.tag_v2:
|
|
||||||
assert reloaded.tag_v2[700] == b"xmlpacket tag"
|
assert reloaded.tag_v2[700] == b"xmlpacket tag"
|
||||||
|
|
||||||
def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
def test_int_dpi(self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
def test_load_raw() -> None:
|
def test_load_raw() -> None:
|
||||||
with Image.open("Tests/images/hopper.pcd") as im:
|
with Image.open("Tests/images/hopper.pcd") as im:
|
||||||
|
assert im.size == (768, 512)
|
||||||
im.load() # should not segfault.
|
im.load() # should not segfault.
|
||||||
|
|
||||||
# Note that this image was created with a resized hopper
|
# Note that this image was created with a resized hopper
|
||||||
|
@ -15,3 +20,13 @@ def test_load_raw() -> None:
|
||||||
|
|
||||||
# target = hopper().resize((768,512))
|
# target = hopper().resize((768,512))
|
||||||
# assert_image_similar(im, target, 10)
|
# assert_image_similar(im, target, 10)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("orientation", (1, 3))
|
||||||
|
def test_rotated(orientation: int) -> None:
|
||||||
|
with open("Tests/images/hopper.pcd", "rb") as fp:
|
||||||
|
data = bytearray(fp.read())
|
||||||
|
data[2048 + 1538] = orientation
|
||||||
|
f = BytesIO(data)
|
||||||
|
with Image.open(f) as im:
|
||||||
|
assert im.size == (512, 768)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import WalImageFile
|
from PIL import WalImageFile
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import assert_image_equal_tofile
|
||||||
|
@ -13,12 +15,22 @@ def test_open() -> None:
|
||||||
assert im.format_description == "Quake2 Texture"
|
assert im.format_description == "Quake2 Texture"
|
||||||
assert im.mode == "P"
|
assert im.mode == "P"
|
||||||
assert im.size == (128, 128)
|
assert im.size == (128, 128)
|
||||||
|
assert "next_name" not in im.info
|
||||||
|
|
||||||
assert isinstance(im, WalImageFile.WalImageFile)
|
assert isinstance(im, WalImageFile.WalImageFile)
|
||||||
|
|
||||||
assert_image_equal_tofile(im, "Tests/images/hopper_wal.png")
|
assert_image_equal_tofile(im, "Tests/images/hopper_wal.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_next_name() -> None:
|
||||||
|
with open(TEST_FILE, "rb") as fp:
|
||||||
|
data = bytearray(fp.read())
|
||||||
|
data[56:60] = b"Test"
|
||||||
|
f = BytesIO(data)
|
||||||
|
with WalImageFile.open(f) as im:
|
||||||
|
assert im.info["next_name"] == b"Test"
|
||||||
|
|
||||||
|
|
||||||
def test_load() -> None:
|
def test_load() -> None:
|
||||||
with WalImageFile.open(TEST_FILE) as im:
|
with WalImageFile.open(TEST_FILE) as im:
|
||||||
px = im.load()
|
px = im.load()
|
||||||
|
|
|
@ -9,7 +9,8 @@ from .helper import skip_unless_feature
|
||||||
|
|
||||||
class TestFontCrash:
|
class TestFontCrash:
|
||||||
def _fuzz_font(self, font: ImageFont.FreeTypeFont) -> None:
|
def _fuzz_font(self, font: ImageFont.FreeTypeFont) -> None:
|
||||||
# from fuzzers.fuzz_font
|
# Copy of the code from fuzz_font() in Tests/oss-fuzz/fuzzers.py
|
||||||
|
# that triggered a problem when fuzzing
|
||||||
font.getbbox("ABC")
|
font.getbbox("ABC")
|
||||||
font.getmask("test text")
|
font.getmask("test text")
|
||||||
with Image.new(mode="RGBA", size=(200, 200)) as im:
|
with Image.new(mode="RGBA", size=(200, 200)) as im:
|
||||||
|
|
|
@ -19,6 +19,7 @@ from PIL import (
|
||||||
ImageDraw,
|
ImageDraw,
|
||||||
ImageFile,
|
ImageFile,
|
||||||
ImagePalette,
|
ImagePalette,
|
||||||
|
ImageShow,
|
||||||
UnidentifiedImageError,
|
UnidentifiedImageError,
|
||||||
features,
|
features,
|
||||||
)
|
)
|
||||||
|
@ -388,6 +389,37 @@ class TestImage:
|
||||||
assert img_colors is not None
|
assert img_colors is not None
|
||||||
assert sorted(img_colors) == expected_colors
|
assert sorted(img_colors) == expected_colors
|
||||||
|
|
||||||
|
def test_alpha_composite_la(self) -> None:
|
||||||
|
# Arrange
|
||||||
|
expected_colors = sorted(
|
||||||
|
[
|
||||||
|
(3300, (255, 255)),
|
||||||
|
(1156, (170, 192)),
|
||||||
|
(1122, (128, 255)),
|
||||||
|
(1089, (0, 0)),
|
||||||
|
(1122, (255, 128)),
|
||||||
|
(1122, (0, 128)),
|
||||||
|
(1089, (0, 255)),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
dst = Image.new("LA", size=(100, 100), color=(0, 255))
|
||||||
|
draw = ImageDraw.Draw(dst)
|
||||||
|
draw.rectangle((0, 33, 100, 66), fill=(0, 128))
|
||||||
|
draw.rectangle((0, 67, 100, 100), fill=(0, 0))
|
||||||
|
src = Image.new("LA", size=(100, 100), color=(255, 255))
|
||||||
|
draw = ImageDraw.Draw(src)
|
||||||
|
draw.rectangle((33, 0, 66, 100), fill=(255, 128))
|
||||||
|
draw.rectangle((67, 0, 100, 100), fill=(255, 0))
|
||||||
|
|
||||||
|
# Act
|
||||||
|
img = Image.alpha_composite(dst, src)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
img_colors = img.getcolors()
|
||||||
|
assert img_colors is not None
|
||||||
|
assert sorted(img_colors) == expected_colors
|
||||||
|
|
||||||
def test_alpha_inplace(self) -> None:
|
def test_alpha_inplace(self) -> None:
|
||||||
src = Image.new("RGBA", (128, 128), "blue")
|
src = Image.new("RGBA", (128, 128), "blue")
|
||||||
|
|
||||||
|
@ -922,6 +954,17 @@ class TestImage:
|
||||||
reloaded_exif.load(exif.tobytes())
|
reloaded_exif.load(exif.tobytes())
|
||||||
assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769)
|
assert reloaded_exif.get_ifd(0x8769) == exif.get_ifd(0x8769)
|
||||||
|
|
||||||
|
def test_delete_ifd_tag(self) -> None:
|
||||||
|
with Image.open("Tests/images/flower.jpg") as im:
|
||||||
|
exif = im.getexif()
|
||||||
|
exif.get_ifd(0x8769)
|
||||||
|
assert 0x8769 in exif
|
||||||
|
del exif[0x8769]
|
||||||
|
|
||||||
|
reloaded_exif = Image.Exif()
|
||||||
|
reloaded_exif.load(exif.tobytes())
|
||||||
|
assert 0x8769 not in reloaded_exif
|
||||||
|
|
||||||
def test_exif_load_from_fp(self) -> None:
|
def test_exif_load_from_fp(self) -> None:
|
||||||
with Image.open("Tests/images/flower.jpg") as im:
|
with Image.open("Tests/images/flower.jpg") as im:
|
||||||
data = im.info["exif"]
|
data = im.info["exif"]
|
||||||
|
@ -1005,6 +1048,13 @@ class TestImage:
|
||||||
with pytest.warns(DeprecationWarning, match="Image.Image.get_child_images"):
|
with pytest.warns(DeprecationWarning, match="Image.Image.get_child_images"):
|
||||||
assert im.get_child_images() == []
|
assert im.get_child_images() == []
|
||||||
|
|
||||||
|
def test_show(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setattr(ImageShow, "_viewers", [])
|
||||||
|
|
||||||
|
im = Image.new("RGB", (1, 1))
|
||||||
|
with pytest.warns(DeprecationWarning, match="Image._show"):
|
||||||
|
Image._show(im)
|
||||||
|
|
||||||
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
|
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
|
||||||
def test_zero_tobytes(self, size: tuple[int, int]) -> None:
|
def test_zero_tobytes(self, size: tuple[int, int]) -> None:
|
||||||
im = Image.new("RGB", size)
|
im = Image.new("RGB", size)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageMorph, _imagingmorph
|
from PIL import Image, ImageMorph, _imagingmorph
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile, hopper
|
from .helper import assert_image_equal_tofile, hopper, timeout_unless_slower_valgrind
|
||||||
|
|
||||||
|
|
||||||
def string_to_img(image_string: str) -> Image.Image:
|
def string_to_img(image_string: str) -> Image.Image:
|
||||||
|
@ -266,16 +266,18 @@ def test_unknown_pattern() -> None:
|
||||||
ImageMorph.LutBuilder(op_name="unknown")
|
ImageMorph.LutBuilder(op_name="unknown")
|
||||||
|
|
||||||
|
|
||||||
def test_pattern_syntax_error() -> None:
|
@pytest.mark.parametrize(
|
||||||
|
"pattern", ("a pattern with a syntax error", "4:(" + "X" * 30000)
|
||||||
|
)
|
||||||
|
@timeout_unless_slower_valgrind(1)
|
||||||
|
def test_pattern_syntax_error(pattern: str) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
lb = ImageMorph.LutBuilder(op_name="corner")
|
lb = ImageMorph.LutBuilder(op_name="corner")
|
||||||
new_patterns = ["a pattern with a syntax error"]
|
new_patterns = [pattern]
|
||||||
lb.add_patterns(new_patterns)
|
lb.add_patterns(new_patterns)
|
||||||
|
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
with pytest.raises(
|
with pytest.raises(Exception, match='Syntax error in pattern "'):
|
||||||
Exception, match='Syntax error in pattern "a pattern with a syntax error"'
|
|
||||||
):
|
|
||||||
lb.build_lut()
|
lb.build_lut()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -57,3 +57,13 @@ def test_constant() -> None:
|
||||||
assert st.rms[0] == 128
|
assert st.rms[0] == 128
|
||||||
assert st.var[0] == 0
|
assert st.var[0] == 0
|
||||||
assert st.stddev[0] == 0
|
assert st.stddev[0] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_zero_count() -> None:
|
||||||
|
im = Image.new("L", (0, 0))
|
||||||
|
|
||||||
|
st = ImageStat.Stat(im)
|
||||||
|
|
||||||
|
assert st.mean == [0]
|
||||||
|
assert st.rms == [0]
|
||||||
|
assert st.var == [0]
|
||||||
|
|
|
@ -9,7 +9,7 @@ from PIL import __version__
|
||||||
pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed")
|
pyroma = pytest.importorskip("pyroma", reason="Pyroma not installed")
|
||||||
|
|
||||||
|
|
||||||
def map_metadata_keys(metadata):
|
def map_metadata_keys(md):
|
||||||
# Convert installed wheel metadata into canonical Core Metadata 2.4 format.
|
# Convert installed wheel metadata into canonical Core Metadata 2.4 format.
|
||||||
# This was a utility method in pyroma 4.3.3; it was removed in 5.0.
|
# This was a utility method in pyroma 4.3.3; it was removed in 5.0.
|
||||||
# This implementation is constructed from the relevant logic from
|
# This implementation is constructed from the relevant logic from
|
||||||
|
@ -17,8 +17,8 @@ def map_metadata_keys(metadata):
|
||||||
# upstream to Pyroma as https://github.com/regebro/pyroma/pull/116,
|
# upstream to Pyroma as https://github.com/regebro/pyroma/pull/116,
|
||||||
# so it may be possible to simplify this test in future.
|
# so it may be possible to simplify this test in future.
|
||||||
data = {}
|
data = {}
|
||||||
for key in set(metadata.keys()):
|
for key in set(md.keys()):
|
||||||
value = metadata.get_all(key)
|
value = md.get_all(key)
|
||||||
key = pyroma.projectdata.normalize(key)
|
key = pyroma.projectdata.normalize(key)
|
||||||
|
|
||||||
if len(value) == 1:
|
if len(value) == 1:
|
||||||
|
|
|
@ -4,7 +4,6 @@ import platform
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from PIL import features
|
from PIL import features
|
||||||
from Tests.helper import is_pypy
|
|
||||||
|
|
||||||
|
|
||||||
def test_wheel_modules() -> None:
|
def test_wheel_modules() -> None:
|
||||||
|
@ -48,8 +47,6 @@ def test_wheel_features() -> None:
|
||||||
|
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
expected_features.remove("xcb")
|
expected_features.remove("xcb")
|
||||||
elif sys.platform == "darwin" and not is_pypy() and platform.processor() != "arm":
|
|
||||||
expected_features.remove("zlib_ng")
|
|
||||||
elif sys.platform == "ios":
|
elif sys.platform == "ios":
|
||||||
# Can't distribute raqm due to licensing, and there's no system version;
|
# Can't distribute raqm due to licensing, and there's no system version;
|
||||||
# fribidi and harfbuzz won't be available if raqm isn't available.
|
# fribidi and harfbuzz won't be available if raqm isn't available.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# install raqm
|
# install raqm
|
||||||
|
|
||||||
|
|
||||||
archive=libraqm-0.10.2
|
archive=libraqm-0.10.3
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,14 @@ ImageCms.ImageCmsProfile.product_name and .product_info
|
||||||
``.product_info`` attributes have been deprecated, and will be removed in
|
``.product_info`` attributes have been deprecated, and will be removed in
|
||||||
Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
|
Pillow 13 (2026-10-15). They have been set to ``None`` since Pillow 2.3.0.
|
||||||
|
|
||||||
|
Image._show
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 12.0.0
|
||||||
|
|
||||||
|
``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15).
|
||||||
|
Use :py:meth:`~PIL.ImageShow.show` instead.
|
||||||
|
|
||||||
Removed features
|
Removed features
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,12 @@ vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`).
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
|
Image._show
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
``Image._show`` has been deprecated, and will be removed in Pillow 13 (2026-10-15).
|
||||||
|
Use :py:meth:`~PIL.ImageShow.show` instead.
|
||||||
|
|
||||||
ImageCms.ImageCmsProfile.product_name and .product_info
|
ImageCms.ImageCmsProfile.product_name and .product_info
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -150,3 +156,10 @@ others prepare for 3.14, and to ensure Pillow could be used immediately at the r
|
||||||
of 3.14.0 final (2025-10-07, :pep:`745`).
|
of 3.14.0 final (2025-10-07, :pep:`745`).
|
||||||
|
|
||||||
Pillow 12.0.0 now officially supports Python 3.14.
|
Pillow 12.0.0 now officially supports Python 3.14.
|
||||||
|
|
||||||
|
ImageMorph operations must have length 1
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Valid ImageMorph operations are 4, N, 1 and M. By limiting the length to 1 character
|
||||||
|
within Pillow, long execution times can be avoided if a user provided long pattern
|
||||||
|
strings. Reported by Jang Choi.
|
||||||
|
|
|
@ -30,7 +30,7 @@ from ._util import DeferredError
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
return (
|
return (
|
||||||
len(prefix) >= 6
|
len(prefix) >= 16
|
||||||
and i16(prefix, 4) in [0xAF11, 0xAF12]
|
and i16(prefix, 4) in [0xAF11, 0xAF12]
|
||||||
and i16(prefix, 14) in [0, 3] # flags
|
and i16(prefix, 14) in [0, 3] # flags
|
||||||
)
|
)
|
||||||
|
|
|
@ -55,7 +55,7 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
width = i32(self.fp.read(4))
|
width = i32(self.fp.read(4))
|
||||||
height = i32(self.fp.read(4))
|
height = i32(self.fp.read(4))
|
||||||
color_depth = i32(self.fp.read(4))
|
color_depth = i32(self.fp.read(4))
|
||||||
if width <= 0 or height <= 0:
|
if width == 0 or height == 0:
|
||||||
msg = "not a GIMP brush"
|
msg = "not a GIMP brush"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
if color_depth not in (1, 4):
|
if color_depth not in (1, 4):
|
||||||
|
@ -72,7 +72,7 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
self.info["spacing"] = i32(self.fp.read(4))
|
self.info["spacing"] = i32(self.fp.read(4))
|
||||||
|
|
||||||
comment = self.fp.read(comment_length)[:-1]
|
self.info["comment"] = self.fp.read(comment_length)[:-1]
|
||||||
|
|
||||||
if color_depth == 1:
|
if color_depth == 1:
|
||||||
self._mode = "L"
|
self._mode = "L"
|
||||||
|
@ -81,8 +81,6 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self._size = width, height
|
self._size = width, height
|
||||||
|
|
||||||
self.info["comment"] = comment
|
|
||||||
|
|
||||||
# Image might not be small
|
# Image might not be small
|
||||||
Image._decompression_bomb_check(self.size)
|
Image._decompression_bomb_check(self.size)
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ def register_handler(handler: ImageFile.StubHandler | None) -> None:
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
return prefix.startswith(b"GRIB") and prefix[7] == 1
|
return len(prefix) >= 8 and prefix.startswith(b"GRIB") and prefix[7] == 1
|
||||||
|
|
||||||
|
|
||||||
class GribStubImageFile(ImageFile.StubImageFile):
|
class GribStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
|
@ -2632,7 +2632,9 @@ class Image:
|
||||||
:param title: Optional title to use for the image window, where possible.
|
:param title: Optional title to use for the image window, where possible.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_show(self, title=title)
|
from . import ImageShow
|
||||||
|
|
||||||
|
ImageShow.show(self, title)
|
||||||
|
|
||||||
def split(self) -> tuple[Image, ...]:
|
def split(self) -> tuple[Image, ...]:
|
||||||
"""
|
"""
|
||||||
|
@ -3570,9 +3572,8 @@ def alpha_composite(im1: Image, im2: Image) -> Image:
|
||||||
"""
|
"""
|
||||||
Alpha composite im2 over im1.
|
Alpha composite im2 over im1.
|
||||||
|
|
||||||
:param im1: The first image. Must have mode RGBA.
|
:param im1: The first image. Must have mode RGBA or LA.
|
||||||
:param im2: The second image. Must have mode RGBA, and the same size as
|
:param im2: The second image. Must have the same mode and size as the first image.
|
||||||
the first image.
|
|
||||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -3798,6 +3799,7 @@ def register_encoder(name: str, encoder: type[ImageFile.PyEncoder]) -> None:
|
||||||
def _show(image: Image, **options: Any) -> None:
|
def _show(image: Image, **options: Any) -> None:
|
||||||
from . import ImageShow
|
from . import ImageShow
|
||||||
|
|
||||||
|
deprecate("Image._show", 13, "ImageShow.show")
|
||||||
ImageShow.show(image, **options)
|
ImageShow.show(image, **options)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4219,6 +4221,8 @@ class Exif(_ExifBase):
|
||||||
del self._info[tag]
|
del self._info[tag]
|
||||||
else:
|
else:
|
||||||
del self._data[tag]
|
del self._data[tag]
|
||||||
|
if tag in self._ifds:
|
||||||
|
del self._ifds[tag]
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[int]:
|
def __iter__(self) -> Iterator[int]:
|
||||||
keys = set(self._data)
|
keys = set(self._data)
|
||||||
|
|
|
@ -150,7 +150,7 @@ class LutBuilder:
|
||||||
|
|
||||||
# Parse and create symmetries of the patterns strings
|
# Parse and create symmetries of the patterns strings
|
||||||
for p in self.patterns:
|
for p in self.patterns:
|
||||||
m = re.search(r"(\w*):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", ""))
|
m = re.search(r"(\w):?\s*\((.+?)\)\s*->\s*(\d)", p.replace("\n", ""))
|
||||||
if not m:
|
if not m:
|
||||||
msg = 'Syntax error in pattern "' + p + '"'
|
msg = 'Syntax error in pattern "' + p + '"'
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
|
|
|
@ -120,7 +120,7 @@ class Stat:
|
||||||
@cached_property
|
@cached_property
|
||||||
def mean(self) -> list[float]:
|
def mean(self) -> list[float]:
|
||||||
"""Average (arithmetic mean) pixel level for each band in the image."""
|
"""Average (arithmetic mean) pixel level for each band in the image."""
|
||||||
return [self.sum[i] / self.count[i] for i in self.bands]
|
return [self.sum[i] / self.count[i] if self.count[i] else 0 for i in self.bands]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def median(self) -> list[int]:
|
def median(self) -> list[int]:
|
||||||
|
@ -141,13 +141,20 @@ class Stat:
|
||||||
@cached_property
|
@cached_property
|
||||||
def rms(self) -> list[float]:
|
def rms(self) -> list[float]:
|
||||||
"""RMS (root-mean-square) for each band in the image."""
|
"""RMS (root-mean-square) for each band in the image."""
|
||||||
return [math.sqrt(self.sum2[i] / self.count[i]) for i in self.bands]
|
return [
|
||||||
|
math.sqrt(self.sum2[i] / self.count[i]) if self.count[i] else 0
|
||||||
|
for i in self.bands
|
||||||
|
]
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def var(self) -> list[float]:
|
def var(self) -> list[float]:
|
||||||
"""Variance for each band in the image."""
|
"""Variance for each band in the image."""
|
||||||
return [
|
return [
|
||||||
|
(
|
||||||
(self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i]
|
(self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i]
|
||||||
|
if self.count[i]
|
||||||
|
else 0
|
||||||
|
)
|
||||||
for i in self.bands
|
for i in self.bands
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,6 @@ def _i(c: bytes) -> int:
|
||||||
return i32((b"\0\0\0\0" + c)[-4:])
|
return i32((b"\0\0\0\0" + c)[-4:])
|
||||||
|
|
||||||
|
|
||||||
def _i8(c: int | bytes) -> int:
|
|
||||||
return c if isinstance(c, int) else c[0]
|
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
|
# Image plugin for IPTC/NAA datastreams. To read IPTC/NAA fields
|
||||||
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
|
# from TIFF and JPEG files, use the <b>getiptcinfo</b> function.
|
||||||
|
@ -102,16 +98,18 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
# mode
|
# mode
|
||||||
layers = self.info[(3, 60)][0]
|
layers = self.info[(3, 60)][0]
|
||||||
component = self.info[(3, 60)][1]
|
component = self.info[(3, 60)][1]
|
||||||
if (3, 65) in self.info:
|
|
||||||
id = self.info[(3, 65)][0] - 1
|
|
||||||
else:
|
|
||||||
id = 0
|
|
||||||
if layers == 1 and not component:
|
if layers == 1 and not component:
|
||||||
self._mode = "L"
|
self._mode = "L"
|
||||||
elif layers == 3 and component:
|
band = None
|
||||||
self._mode = "RGB"[id]
|
else:
|
||||||
|
if layers == 3 and component:
|
||||||
|
self._mode = "RGB"
|
||||||
elif layers == 4 and component:
|
elif layers == 4 and component:
|
||||||
self._mode = "CMYK"[id]
|
self._mode = "CMYK"
|
||||||
|
if (3, 65) in self.info:
|
||||||
|
band = self.info[(3, 65)][0] - 1
|
||||||
|
else:
|
||||||
|
band = 0
|
||||||
|
|
||||||
# size
|
# size
|
||||||
self._size = self.getint((3, 20)), self.getint((3, 30))
|
self._size = self.getint((3, 20)), self.getint((3, 30))
|
||||||
|
@ -126,17 +124,17 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
# tile
|
# tile
|
||||||
if tag == (8, 10):
|
if tag == (8, 10):
|
||||||
self.tile = [
|
self.tile = [
|
||||||
ImageFile._Tile("iptc", (0, 0) + self.size, offset, compression)
|
ImageFile._Tile("iptc", (0, 0) + self.size, offset, (compression, band))
|
||||||
]
|
]
|
||||||
|
|
||||||
def load(self) -> Image.core.PixelAccess | None:
|
def load(self) -> Image.core.PixelAccess | None:
|
||||||
if len(self.tile) != 1 or self.tile[0][0] != "iptc":
|
if self.tile:
|
||||||
return ImageFile.ImageFile.load(self)
|
args = self.tile[0].args
|
||||||
|
assert isinstance(args, tuple)
|
||||||
offset, compression = self.tile[0][2:]
|
compression, band = args
|
||||||
|
|
||||||
assert self.fp is not None
|
assert self.fp is not None
|
||||||
self.fp.seek(offset)
|
self.fp.seek(self.tile[0].offset)
|
||||||
|
|
||||||
# Copy image data to temporary file
|
# Copy image data to temporary file
|
||||||
o = BytesIO()
|
o = BytesIO()
|
||||||
|
@ -156,10 +154,15 @@ class IptcImageFile(ImageFile.ImageFile):
|
||||||
size -= len(s)
|
size -= len(s)
|
||||||
|
|
||||||
with Image.open(o) as _im:
|
with Image.open(o) as _im:
|
||||||
|
if band is not None:
|
||||||
|
bands = [Image.new("L", _im.size)] * Image.getmodebands(self.mode)
|
||||||
|
bands[band] = _im
|
||||||
|
_im = Image.merge(self.mode, bands)
|
||||||
|
else:
|
||||||
_im.load()
|
_im.load()
|
||||||
self.im = _im.im
|
self.im = _im.im
|
||||||
self.tile = []
|
self.tile = []
|
||||||
return Image.Image.load(self)
|
return ImageFile.ImageFile.load(self)
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(IptcImageFile.format, IptcImageFile)
|
Image.register_open(IptcImageFile.format, IptcImageFile)
|
||||||
|
|
|
@ -32,7 +32,7 @@ class PcdImageFile(ImageFile.ImageFile):
|
||||||
assert self.fp is not None
|
assert self.fp is not None
|
||||||
|
|
||||||
self.fp.seek(2048)
|
self.fp.seek(2048)
|
||||||
s = self.fp.read(2048)
|
s = self.fp.read(1539)
|
||||||
|
|
||||||
if not s.startswith(b"PCD_"):
|
if not s.startswith(b"PCD_"):
|
||||||
msg = "not a PCD file"
|
msg = "not a PCD file"
|
||||||
|
@ -46,14 +46,13 @@ class PcdImageFile(ImageFile.ImageFile):
|
||||||
self.tile_post_rotate = -90
|
self.tile_post_rotate = -90
|
||||||
|
|
||||||
self._mode = "RGB"
|
self._mode = "RGB"
|
||||||
self._size = 768, 512 # FIXME: not correct for rotated images!
|
self._size = (512, 768) if orientation in (1, 3) else (768, 512)
|
||||||
self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
|
self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
|
||||||
|
|
||||||
def load_end(self) -> None:
|
def load_end(self) -> None:
|
||||||
if self.tile_post_rotate:
|
if self.tile_post_rotate:
|
||||||
# Handle rotated PCDs
|
# Handle rotated PCDs
|
||||||
self.im = self.im.rotate(self.tile_post_rotate)
|
self.im = self.im.rotate(self.tile_post_rotate)
|
||||||
self._size = self.im.size
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -39,7 +39,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]
|
return len(prefix) >= 2 and prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -47,7 +47,7 @@ MODES = {
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
return prefix.startswith(b"P") and prefix[1] in b"0123456fy"
|
return len(prefix) >= 2 and prefix.startswith(b"P") and prefix[1] in b"0123456fy"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -50,8 +50,7 @@ class WalImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# strings are null-terminated
|
# strings are null-terminated
|
||||||
self.info["name"] = header[:32].split(b"\0", 1)[0]
|
self.info["name"] = header[:32].split(b"\0", 1)[0]
|
||||||
next_name = header[56 : 56 + 32].split(b"\0", 1)[0]
|
if next_name := header[56 : 56 + 32].split(b"\0", 1)[0]:
|
||||||
if next_name:
|
|
||||||
self.info["next_name"] = next_name
|
self.info["next_name"] = next_name
|
||||||
|
|
||||||
def load(self) -> Image.core.PixelAccess | None:
|
def load(self) -> Image.core.PixelAccess | None:
|
||||||
|
|
|
@ -870,8 +870,6 @@ PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (strcmp(format, "j2k") == 0) {
|
if (strcmp(format, "j2k") == 0) {
|
||||||
codec_format = OPJ_CODEC_J2K;
|
codec_format = OPJ_CODEC_J2K;
|
||||||
} else if (strcmp(format, "jpt") == 0) {
|
|
||||||
codec_format = OPJ_CODEC_JPT;
|
|
||||||
} else if (strcmp(format, "jp2") == 0) {
|
} else if (strcmp(format, "jp2") == 0) {
|
||||||
codec_format = OPJ_CODEC_JP2;
|
codec_format = OPJ_CODEC_JP2;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -25,13 +25,12 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) {
|
||||||
int x, y;
|
int x, y;
|
||||||
|
|
||||||
/* Check arguments */
|
/* Check arguments */
|
||||||
if (!imDst || !imSrc || strcmp(imDst->mode, "RGBA") ||
|
if (!imDst || !imSrc ||
|
||||||
imDst->type != IMAGING_TYPE_UINT8 || imDst->bands != 4) {
|
(strcmp(imDst->mode, "RGBA") && strcmp(imDst->mode, "LA"))) {
|
||||||
return ImagingError_ModeError();
|
return ImagingError_ModeError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strcmp(imDst->mode, imSrc->mode) || imDst->type != imSrc->type ||
|
if (strcmp(imDst->mode, imSrc->mode) || imDst->xsize != imSrc->xsize ||
|
||||||
imDst->bands != imSrc->bands || imDst->xsize != imSrc->xsize ||
|
|
||||||
imDst->ysize != imSrc->ysize) {
|
imDst->ysize != imSrc->ysize) {
|
||||||
return ImagingError_Mismatch();
|
return ImagingError_Mismatch();
|
||||||
}
|
}
|
||||||
|
|
2
src/thirdparty/raqm/COPYING
vendored
2
src/thirdparty/raqm/COPYING
vendored
|
@ -1,7 +1,7 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om>
|
Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om>
|
||||||
Copyright © 2016-2023 Khaled Hosny <khaled@aliftype.com>
|
Copyright © 2016-2025 Khaled Hosny <khaled@aliftype.com>
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
16
src/thirdparty/raqm/NEWS
vendored
16
src/thirdparty/raqm/NEWS
vendored
|
@ -1,3 +1,19 @@
|
||||||
|
Overview of changes leading to 0.10.3
|
||||||
|
Tuesday, August 5, 2025
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Fix raqm_set_text_utf8/utf16 reading beyond len for multibyte.
|
||||||
|
|
||||||
|
Support building against SheenBidi 2.9.
|
||||||
|
|
||||||
|
Fix deprecation warning with latest HarfBuzz.
|
||||||
|
|
||||||
|
Overview of changes leading to 0.10.2
|
||||||
|
Sunday, September 22, 2024
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Fix Unicode codepoint conversion from UTF-16.
|
||||||
|
|
||||||
Overview of changes leading to 0.10.1
|
Overview of changes leading to 0.10.1
|
||||||
Wednesday, April 12, 2023
|
Wednesday, April 12, 2023
|
||||||
====================================
|
====================================
|
||||||
|
|
4
src/thirdparty/raqm/raqm-version.h
vendored
4
src/thirdparty/raqm/raqm-version.h
vendored
|
@ -33,9 +33,9 @@
|
||||||
|
|
||||||
#define RAQM_VERSION_MAJOR 0
|
#define RAQM_VERSION_MAJOR 0
|
||||||
#define RAQM_VERSION_MINOR 10
|
#define RAQM_VERSION_MINOR 10
|
||||||
#define RAQM_VERSION_MICRO 1
|
#define RAQM_VERSION_MICRO 3
|
||||||
|
|
||||||
#define RAQM_VERSION_STRING "0.10.1"
|
#define RAQM_VERSION_STRING "0.10.3"
|
||||||
|
|
||||||
#define RAQM_VERSION_ATLEAST(major,minor,micro) \
|
#define RAQM_VERSION_ATLEAST(major,minor,micro) \
|
||||||
((major)*10000+(minor)*100+(micro) <= \
|
((major)*10000+(minor)*100+(micro) <= \
|
||||||
|
|
82
src/thirdparty/raqm/raqm.c
vendored
82
src/thirdparty/raqm/raqm.c
vendored
|
@ -30,7 +30,11 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#ifdef RAQM_SHEENBIDI
|
#ifdef RAQM_SHEENBIDI
|
||||||
|
#ifdef RAQM_SHEENBIDI_GT_2_9
|
||||||
|
#include <SheenBidi/SheenBidi.h>
|
||||||
|
#else
|
||||||
#include <SheenBidi.h>
|
#include <SheenBidi.h>
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
#ifdef HAVE_FRIBIDI_SYSTEM
|
#ifdef HAVE_FRIBIDI_SYSTEM
|
||||||
#include <fribidi.h>
|
#include <fribidi.h>
|
||||||
|
@ -546,34 +550,32 @@ raqm_set_text (raqm_t *rq,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *
|
static const char *
|
||||||
_raqm_get_utf8_codepoint (const void *str,
|
_raqm_get_utf8_codepoint (const char *str,
|
||||||
uint32_t *out_codepoint)
|
uint32_t *out_codepoint)
|
||||||
{
|
{
|
||||||
const char *s = (const char *)str;
|
if (0xf0 == (0xf8 & str[0]))
|
||||||
|
|
||||||
if (0xf0 == (0xf8 & s[0]))
|
|
||||||
{
|
{
|
||||||
*out_codepoint = ((0x07 & s[0]) << 18) | ((0x3f & s[1]) << 12) | ((0x3f & s[2]) << 6) | (0x3f & s[3]);
|
*out_codepoint = ((0x07 & str[0]) << 18) | ((0x3f & str[1]) << 12) | ((0x3f & str[2]) << 6) | (0x3f & str[3]);
|
||||||
s += 4;
|
str += 4;
|
||||||
}
|
}
|
||||||
else if (0xe0 == (0xf0 & s[0]))
|
else if (0xe0 == (0xf0 & str[0]))
|
||||||
{
|
{
|
||||||
*out_codepoint = ((0x0f & s[0]) << 12) | ((0x3f & s[1]) << 6) | (0x3f & s[2]);
|
*out_codepoint = ((0x0f & str[0]) << 12) | ((0x3f & str[1]) << 6) | (0x3f & str[2]);
|
||||||
s += 3;
|
str += 3;
|
||||||
}
|
}
|
||||||
else if (0xc0 == (0xe0 & s[0]))
|
else if (0xc0 == (0xe0 & str[0]))
|
||||||
{
|
{
|
||||||
*out_codepoint = ((0x1f & s[0]) << 6) | (0x3f & s[1]);
|
*out_codepoint = ((0x1f & str[0]) << 6) | (0x3f & str[1]);
|
||||||
s += 2;
|
str += 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
*out_codepoint = s[0];
|
*out_codepoint = str[0];
|
||||||
s += 1;
|
str += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (void *)s;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
|
@ -585,42 +587,41 @@ _raqm_u8_to_u32 (const char *text, size_t len, uint32_t *unicode)
|
||||||
|
|
||||||
while ((*in_utf8 != '\0') && (in_len < len))
|
while ((*in_utf8 != '\0') && (in_len < len))
|
||||||
{
|
{
|
||||||
in_utf8 = _raqm_get_utf8_codepoint (in_utf8, out_utf32);
|
const char *out_utf8 = _raqm_get_utf8_codepoint (in_utf8, out_utf32);
|
||||||
|
in_len += out_utf8 - in_utf8;
|
||||||
|
in_utf8 = out_utf8;
|
||||||
++out_utf32;
|
++out_utf32;
|
||||||
++in_len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (out_utf32 - unicode);
|
return (out_utf32 - unicode);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *
|
static const uint16_t *
|
||||||
_raqm_get_utf16_codepoint (const void *str,
|
_raqm_get_utf16_codepoint (const uint16_t *str,
|
||||||
uint32_t *out_codepoint)
|
uint32_t *out_codepoint)
|
||||||
{
|
{
|
||||||
const uint16_t *s = (const uint16_t *)str;
|
if (str[0] >= 0xD800 && str[0] <= 0xDBFF)
|
||||||
|
|
||||||
if (s[0] > 0xD800 && s[0] < 0xDBFF)
|
|
||||||
{
|
{
|
||||||
if (s[1] > 0xDC00 && s[1] < 0xDFFF)
|
if (str[1] >= 0xDC00 && str[1] <= 0xDFFF)
|
||||||
{
|
{
|
||||||
uint32_t X = ((s[0] & ((1 << 6) -1)) << 10) | (s[1] & ((1 << 10) -1));
|
uint32_t X = ((str[0] & ((1 << 6) -1)) << 10) | (str[1] & ((1 << 10) -1));
|
||||||
uint32_t W = (s[0] >> 6) & ((1 << 5) - 1);
|
uint32_t W = (str[0] >> 6) & ((1 << 5) - 1);
|
||||||
*out_codepoint = (W+1) << 16 | X;
|
*out_codepoint = (W+1) << 16 | X;
|
||||||
s += 2;
|
str += 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* A single high surrogate, this is an error. */
|
/* A single high surrogate, this is an error. */
|
||||||
*out_codepoint = s[0];
|
*out_codepoint = str[0];
|
||||||
s += 1;
|
str += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
*out_codepoint = s[0];
|
*out_codepoint = str[0];
|
||||||
s += 1;
|
str += 1;
|
||||||
}
|
}
|
||||||
return (void *)s;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t
|
static size_t
|
||||||
|
@ -632,9 +633,10 @@ _raqm_u16_to_u32 (const uint16_t *text, size_t len, uint32_t *unicode)
|
||||||
|
|
||||||
while ((*in_utf16 != '\0') && (in_len < len))
|
while ((*in_utf16 != '\0') && (in_len < len))
|
||||||
{
|
{
|
||||||
in_utf16 = _raqm_get_utf16_codepoint (in_utf16, out_utf32);
|
const uint16_t *out_utf16 = _raqm_get_utf16_codepoint (in_utf16, out_utf32);
|
||||||
|
in_len += (out_utf16 - in_utf16);
|
||||||
|
in_utf16 = out_utf16;
|
||||||
++out_utf32;
|
++out_utf32;
|
||||||
++in_len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (out_utf32 - unicode);
|
return (out_utf32 - unicode);
|
||||||
|
@ -1114,12 +1116,12 @@ _raqm_set_spacing (raqm_t *rq,
|
||||||
{
|
{
|
||||||
if (_raqm_allowed_grapheme_boundary (rq->text[i], rq->text[i+1]))
|
if (_raqm_allowed_grapheme_boundary (rq->text[i], rq->text[i+1]))
|
||||||
{
|
{
|
||||||
/* CSS word seperators, word spacing is only applied on these.*/
|
/* CSS word separators, word spacing is only applied on these.*/
|
||||||
if (rq->text[i] == 0x0020 || /* Space */
|
if (rq->text[i] == 0x0020 || /* Space */
|
||||||
rq->text[i] == 0x00A0 || /* No Break Space */
|
rq->text[i] == 0x00A0 || /* No Break Space */
|
||||||
rq->text[i] == 0x1361 || /* Ethiopic Word Space */
|
rq->text[i] == 0x1361 || /* Ethiopic Word Space */
|
||||||
rq->text[i] == 0x10100 || /* Aegean Word Seperator Line */
|
rq->text[i] == 0x10100 || /* Aegean Word Separator Line */
|
||||||
rq->text[i] == 0x10101 || /* Aegean Word Seperator Dot */
|
rq->text[i] == 0x10101 || /* Aegean Word Separator Dot */
|
||||||
rq->text[i] == 0x1039F || /* Ugaric Word Divider */
|
rq->text[i] == 0x1039F || /* Ugaric Word Divider */
|
||||||
rq->text[i] == 0x1091F) /* Phoenician Word Separator */
|
rq->text[i] == 0x1091F) /* Phoenician Word Separator */
|
||||||
{
|
{
|
||||||
|
@ -2167,6 +2169,10 @@ _raqm_ft_transform (int *x,
|
||||||
*y = vector.y;
|
*y = vector.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !HB_VERSION_ATLEAST (10, 4, 0)
|
||||||
|
# define hb_ft_font_get_ft_face hb_ft_font_get_face
|
||||||
|
#endif
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
_raqm_shape (raqm_t *rq)
|
_raqm_shape (raqm_t *rq)
|
||||||
{
|
{
|
||||||
|
@ -2199,7 +2205,7 @@ _raqm_shape (raqm_t *rq)
|
||||||
hb_glyph_position_t *pos;
|
hb_glyph_position_t *pos;
|
||||||
unsigned int len;
|
unsigned int len;
|
||||||
|
|
||||||
FT_Get_Transform (hb_ft_font_get_face (run->font), &matrix, NULL);
|
FT_Get_Transform (hb_ft_font_get_ft_face (run->font), &matrix, NULL);
|
||||||
pos = hb_buffer_get_glyph_positions (run->buffer, &len);
|
pos = hb_buffer_get_glyph_positions (run->buffer, &len);
|
||||||
info = hb_buffer_get_glyph_infos (run->buffer, &len);
|
info = hb_buffer_get_glyph_infos (run->buffer, &len);
|
||||||
|
|
||||||
|
|
30
wheels/dependency_licenses/ZSTD.txt
Normal file
30
wheels/dependency_licenses/ZSTD.txt
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
BSD License
|
||||||
|
|
||||||
|
For Zstandard software
|
||||||
|
|
||||||
|
Copyright (c) Meta Platforms, Inc. and affiliates. 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 Facebook, nor Meta, 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.
|
|
@ -1 +1 @@
|
||||||
Subproject commit 42d761728d141d8462cd9943f4329f12fe62b155
|
Subproject commit 64739327166fcad1fa41ad9b23fa910fa244c84f
|
|
@ -117,7 +117,7 @@ V = {
|
||||||
"FREETYPE": "2.13.3",
|
"FREETYPE": "2.13.3",
|
||||||
"FRIBIDI": "1.0.16",
|
"FRIBIDI": "1.0.16",
|
||||||
"HARFBUZZ": "11.3.3",
|
"HARFBUZZ": "11.3.3",
|
||||||
"JPEGTURBO": "3.1.1",
|
"JPEGTURBO": "3.1.2",
|
||||||
"LCMS2": "2.17",
|
"LCMS2": "2.17",
|
||||||
"LIBAVIF": "1.3.0",
|
"LIBAVIF": "1.3.0",
|
||||||
"LIBIMAGEQUANT": "4.4.0",
|
"LIBIMAGEQUANT": "4.4.0",
|
||||||
|
@ -126,7 +126,7 @@ V = {
|
||||||
"OPENJPEG": "2.5.3",
|
"OPENJPEG": "2.5.3",
|
||||||
"TIFF": "4.7.0",
|
"TIFF": "4.7.0",
|
||||||
"XZ": "5.8.1",
|
"XZ": "5.8.1",
|
||||||
"ZLIBNG": "2.2.4",
|
"ZLIBNG": "2.2.5",
|
||||||
}
|
}
|
||||||
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
|
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user