mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-09-09 22:02:27 +03:00
Merge branch 'main' into fromarray_mode
This commit is contained in:
commit
1c70e716ce
|
@ -1 +1 @@
|
|||
cibuildwheel==3.1.2
|
||||
cibuildwheel==3.1.4
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
mypy==1.17.0
|
||||
mypy==1.17.1
|
||||
IceSpringPySideStubs-PyQt6
|
||||
IceSpringPySideStubs-PySide6
|
||||
ipython
|
||||
|
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
|||
name: Docs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
|||
name: Lint
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
4
.github/workflows/test-docker.yml
vendored
4
.github/workflows/test-docker.yml
vendored
|
@ -47,6 +47,8 @@ jobs:
|
|||
centos-stream-10-amd64,
|
||||
debian-12-bookworm-x86,
|
||||
debian-12-bookworm-amd64,
|
||||
debian-13-trixie-x86,
|
||||
debian-13-trixie-amd64,
|
||||
fedora-41-amd64,
|
||||
fedora-42-amd64,
|
||||
gentoo,
|
||||
|
@ -66,7 +68,7 @@ jobs:
|
|||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
2
.github/workflows/test-mingw.yml
vendored
2
.github/workflows/test-mingw.yml
vendored
|
@ -45,7 +45,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
2
.github/workflows/test-valgrind-memory.yml
vendored
2
.github/workflows/test-valgrind-memory.yml
vendored
|
@ -41,7 +41,7 @@ jobs:
|
|||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
2
.github/workflows/test-valgrind.yml
vendored
2
.github/workflows/test-valgrind.yml
vendored
|
@ -39,7 +39,7 @@ jobs:
|
|||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
6
.github/workflows/test-windows.yml
vendored
6
.github/workflows/test-windows.yml
vendored
|
@ -47,19 +47,19 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout cached dependencies
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/pillow-depends
|
||||
path: winbuild\depends
|
||||
|
||||
- name: Checkout extra test images
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/test-images
|
||||
|
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
@ -65,7 +65,7 @@ jobs:
|
|||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
@ -111,7 +111,7 @@ jobs:
|
|||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
|
||||
- name: Register gcc problem matcher
|
||||
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'"
|
||||
if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.14'"
|
||||
run: echo "::add-matcher::.github/problem-matchers/gcc.json"
|
||||
|
||||
- name: Build
|
||||
|
|
6
.github/workflows/wheels-dependencies.sh
vendored
6
.github/workflows/wheels-dependencies.sh
vendored
|
@ -94,7 +94,7 @@ ARCHIVE_SDIR=pillow-depends-main
|
|||
# annotations have a source code patch that is required for some platforms. If
|
||||
# you change those versions, ensure the patch is also updated.
|
||||
FREETYPE_VERSION=2.13.3
|
||||
HARFBUZZ_VERSION=11.2.1
|
||||
HARFBUZZ_VERSION=11.3.3
|
||||
LIBPNG_VERSION=1.6.50
|
||||
JPEGTURBO_VERSION=3.1.1
|
||||
OPENJPEG_VERSION=2.5.3
|
||||
|
@ -165,7 +165,7 @@ function build_brotli {
|
|||
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
|
||||
(cd $out_dir \
|
||||
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \
|
||||
&& make install)
|
||||
&& make -j4 install)
|
||||
touch brotli-stamp
|
||||
}
|
||||
|
||||
|
@ -249,7 +249,7 @@ function build_libavif {
|
|||
cp $WORKDIR/meson-cross.txt $out_dir/crossfile-apple.meson
|
||||
fi
|
||||
|
||||
(cd $out_dir && make install)
|
||||
(cd $out_dir && make -j4 install)
|
||||
|
||||
touch libavif-stamp
|
||||
}
|
||||
|
|
14
.github/workflows/wheels.yml
vendored
14
.github/workflows/wheels.yml
vendored
|
@ -99,14 +99,14 @@ jobs:
|
|||
cibw_arch: arm64_iphoneos
|
||||
- name: "iOS arm64 simulator"
|
||||
platform: ios
|
||||
os: macos-latest
|
||||
os: macos-14
|
||||
cibw_arch: arm64_iphonesimulator
|
||||
- name: "iOS x86_64 simulator"
|
||||
platform: ios
|
||||
os: macos-13
|
||||
cibw_arch: x86_64_iphonesimulator
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
@ -153,12 +153,12 @@ jobs:
|
|||
- cibw_arch: ARM64
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout extra test images
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/test-images
|
||||
|
@ -234,7 +234,7 @@ jobs:
|
|||
if: github.event_name != 'schedule'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
@ -256,7 +256,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
name: Upload wheels to scientific-python-nightly-wheels
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
|
@ -278,7 +278,7 @@ jobs:
|
|||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v5
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
|
|
2
.github/zizmor.yml
vendored
2
.github/zizmor.yml
vendored
|
@ -1,5 +1,5 @@
|
|||
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
|
||||
# https://woodruffw.github.io/zizmor/configuration/
|
||||
# https://docs.zizmor.sh/configuration/
|
||||
rules:
|
||||
unpinned-uses:
|
||||
config:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.2
|
||||
rev: v0.12.7
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [--exit-non-zero-on-fix]
|
||||
|
@ -24,7 +24,7 @@ repos:
|
|||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v20.1.7
|
||||
rev: v20.1.8
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types: [c]
|
||||
|
@ -57,7 +57,7 @@ repos:
|
|||
- id: check-readthedocs
|
||||
- id: check-renovate
|
||||
|
||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
||||
rev: v1.11.0
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
@ -79,7 +79,7 @@ repos:
|
|||
additional_dependencies: [trove-classifiers>=2024.10.12]
|
||||
|
||||
- repo: https://github.com/tox-dev/tox-ini-fmt
|
||||
rev: 1.5.0
|
||||
rev: 1.6.0
|
||||
hooks:
|
||||
- id: tox-ini-fmt
|
||||
|
||||
|
|
|
@ -175,6 +175,14 @@ def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
|||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||
|
||||
|
||||
def has_feature_version(feature: str, required: str) -> bool:
|
||||
version = features.version(feature)
|
||||
assert version is not None
|
||||
version_required = parse_version(required)
|
||||
version_available = parse_version(version)
|
||||
return version_available >= version_required
|
||||
|
||||
|
||||
def skip_unless_feature_version(
|
||||
feature: str, required: str, reason: str | None = None
|
||||
) -> pytest.MarkDecorator:
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import GbrImagePlugin, Image
|
||||
from PIL import GbrImagePlugin, Image, _binary
|
||||
|
||||
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")
|
||||
|
||||
|
||||
def test_invalid_file() -> None:
|
||||
invalid_file = "Tests/images/flower.jpg"
|
||||
def create_gbr_image(info: dict[str, int] = {}, magic_number=b"") -> BytesIO:
|
||||
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)
|
||||
|
||||
|
||||
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"
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def test_load_raw() -> None:
|
||||
with Image.open("Tests/images/hopper.pcd") as im:
|
||||
assert im.size == (768, 512)
|
||||
im.load() # should not segfault.
|
||||
|
||||
# Note that this image was created with a resized hopper
|
||||
|
@ -15,3 +20,13 @@ def test_load_raw() -> None:
|
|||
|
||||
# target = hopper().resize((768,512))
|
||||
# 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)
|
||||
|
|
|
@ -4,13 +4,13 @@ from collections.abc import Generator
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
from PIL import GifImagePlugin, Image, WebPImagePlugin, features
|
||||
from PIL import GifImagePlugin, Image, WebPImagePlugin
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
assert_image_similar,
|
||||
has_feature_version,
|
||||
is_big_endian,
|
||||
skip_unless_feature,
|
||||
)
|
||||
|
@ -53,11 +53,8 @@ def test_write_animation_L(tmp_path: Path) -> None:
|
|||
im.load()
|
||||
assert_image_similar(im, orig.convert("RGBA"), 32.9)
|
||||
|
||||
if is_big_endian():
|
||||
version = features.version_module("webp")
|
||||
assert version is not None
|
||||
if parse_version(version) < parse_version("1.2.2"):
|
||||
pytest.skip("Fails with libwebp earlier than 1.2.2")
|
||||
if is_big_endian() and not has_feature_version("webp", "1.2.2"):
|
||||
pytest.skip("Fails with libwebp earlier than 1.2.2")
|
||||
orig.seek(orig.n_frames - 1)
|
||||
im.seek(im.n_frames - 1)
|
||||
orig.load()
|
||||
|
@ -81,11 +78,8 @@ def test_write_animation_RGB(tmp_path: Path) -> None:
|
|||
assert_image_equal(im, frame1.convert("RGBA"))
|
||||
|
||||
# Compare second frame to original
|
||||
if is_big_endian():
|
||||
version = features.version_module("webp")
|
||||
assert version is not None
|
||||
if parse_version(version) < parse_version("1.2.2"):
|
||||
pytest.skip("Fails with libwebp earlier than 1.2.2")
|
||||
if is_big_endian() and not has_feature_version("webp", "1.2.2"):
|
||||
pytest.skip("Fails with libwebp earlier than 1.2.2")
|
||||
im.seek(1)
|
||||
im.load()
|
||||
assert_image_equal(im, frame2.convert("RGBA"))
|
||||
|
|
|
@ -388,6 +388,37 @@ class TestImage:
|
|||
assert img_colors is not None
|
||||
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:
|
||||
src = Image.new("RGBA", (128, 128), "blue")
|
||||
|
||||
|
@ -922,6 +953,17 @@ class TestImage:
|
|||
reloaded_exif.load(exif.tobytes())
|
||||
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:
|
||||
with Image.open("Tests/images/flower.jpg") as im:
|
||||
data = im.info["exif"]
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
from PIL import Image, features
|
||||
from PIL import Image
|
||||
|
||||
from .helper import assert_image_similar, hopper, is_ppc64le, skip_unless_feature
|
||||
from .helper import (
|
||||
assert_image_similar,
|
||||
has_feature_version,
|
||||
hopper,
|
||||
is_ppc64le,
|
||||
skip_unless_feature,
|
||||
)
|
||||
|
||||
|
||||
def test_sanity() -> None:
|
||||
|
@ -23,11 +28,8 @@ def test_sanity() -> None:
|
|||
@skip_unless_feature("libimagequant")
|
||||
def test_libimagequant_quantize() -> None:
|
||||
image = hopper()
|
||||
if is_ppc64le():
|
||||
version = features.version_feature("libimagequant")
|
||||
assert version is not None
|
||||
if parse_version(version) < parse_version("4"):
|
||||
pytest.skip("Fails with libimagequant earlier than 4.0.0 on ppc64le")
|
||||
if is_ppc64le() and not has_feature_version("libimagequant", "4"):
|
||||
pytest.skip("Fails with libimagequant earlier than 4.0.0 on ppc64le")
|
||||
converted = image.quantize(100, Image.Quantize.LIBIMAGEQUANT)
|
||||
assert converted.mode == "P"
|
||||
assert_image_similar(converted.convert("RGB"), image, 15)
|
||||
|
|
|
@ -7,6 +7,7 @@ from PIL import Image, ImageDraw, ImageFont
|
|||
from .helper import (
|
||||
assert_image_equal_tofile,
|
||||
assert_image_similar_tofile,
|
||||
has_feature_version,
|
||||
skip_unless_feature,
|
||||
)
|
||||
|
||||
|
@ -104,11 +105,9 @@ def test_text_direction_ttb() -> None:
|
|||
|
||||
im = Image.new(mode="RGB", size=(100, 300))
|
||||
draw = ImageDraw.Draw(im)
|
||||
try:
|
||||
draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb")
|
||||
except ValueError as ex:
|
||||
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
if not has_feature_version("raqm", "0.7"):
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
draw.text((0, 0), "English あい", font=ttf, fill=500, direction="ttb")
|
||||
|
||||
target = "Tests/images/test_direction_ttb.png"
|
||||
assert_image_similar_tofile(im, target, 2.8)
|
||||
|
@ -119,19 +118,17 @@ def test_text_direction_ttb_stroke() -> None:
|
|||
|
||||
im = Image.new(mode="RGB", size=(100, 300))
|
||||
draw = ImageDraw.Draw(im)
|
||||
try:
|
||||
draw.text(
|
||||
(27, 27),
|
||||
"あい",
|
||||
font=ttf,
|
||||
fill=500,
|
||||
direction="ttb",
|
||||
stroke_width=2,
|
||||
stroke_fill="#0f0",
|
||||
)
|
||||
except ValueError as ex:
|
||||
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
if not has_feature_version("raqm", "0.7"):
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
draw.text(
|
||||
(27, 27),
|
||||
"あい",
|
||||
font=ttf,
|
||||
fill=500,
|
||||
direction="ttb",
|
||||
stroke_width=2,
|
||||
stroke_fill="#0f0",
|
||||
)
|
||||
|
||||
target = "Tests/images/test_direction_ttb_stroke.png"
|
||||
assert_image_similar_tofile(im, target, 19.4)
|
||||
|
@ -219,14 +216,9 @@ def test_getlength(
|
|||
im = Image.new(mode, (1, 1), 0)
|
||||
d = ImageDraw.Draw(im)
|
||||
|
||||
try:
|
||||
assert d.textlength(text, ttf, direction) == expected
|
||||
except ValueError as ex:
|
||||
if (
|
||||
direction == "ttb"
|
||||
and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction"
|
||||
):
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
if direction == "ttb" and not has_feature_version("raqm", "0.7"):
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
assert d.textlength(text, ttf, direction) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("L", "1"))
|
||||
|
@ -242,17 +234,12 @@ def test_getlength_combine(mode: str, direction: str, text: str) -> None:
|
|||
|
||||
ttf = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
|
||||
|
||||
try:
|
||||
target = ttf.getlength("ii", mode, direction)
|
||||
actual = ttf.getlength(text, mode, direction)
|
||||
if direction == "ttb" and not has_feature_version("raqm", "0.7"):
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
target = ttf.getlength("ii", mode, direction)
|
||||
actual = ttf.getlength(text, mode, direction)
|
||||
|
||||
assert actual == target
|
||||
except ValueError as ex:
|
||||
if (
|
||||
direction == "ttb"
|
||||
and str(ex) == "libraqm 0.7 or greater required for 'ttb' direction"
|
||||
):
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
assert actual == target
|
||||
|
||||
|
||||
@pytest.mark.parametrize("anchor", ("lt", "mm", "rb", "sm"))
|
||||
|
@ -265,11 +252,9 @@ def test_anchor_ttb(anchor: str) -> None:
|
|||
d = ImageDraw.Draw(im)
|
||||
d.line(((0, 200), (200, 200)), "gray")
|
||||
d.line(((100, 0), (100, 400)), "gray")
|
||||
try:
|
||||
d.text((100, 200), text, fill="black", anchor=anchor, direction="ttb", font=f)
|
||||
except ValueError as ex:
|
||||
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
if not has_feature_version("raqm", "0.7"):
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
d.text((100, 200), text, fill="black", anchor=anchor, direction="ttb", font=f)
|
||||
|
||||
assert_image_similar_tofile(im, path, 1) # fails at 5
|
||||
|
||||
|
@ -310,10 +295,12 @@ combine_tests = (
|
|||
|
||||
# this tests various combining characters for anchor alignment and clipping
|
||||
@pytest.mark.parametrize(
|
||||
"name, text, anchor, dir, epsilon", combine_tests, ids=[r[0] for r in combine_tests]
|
||||
"name, text, anchor, direction, epsilon",
|
||||
combine_tests,
|
||||
ids=[r[0] for r in combine_tests],
|
||||
)
|
||||
def test_combine(
|
||||
name: str, text: str, dir: str | None, anchor: str | None, epsilon: float
|
||||
name: str, text: str, direction: str | None, anchor: str | None, epsilon: float
|
||||
) -> None:
|
||||
path = f"Tests/images/test_combine_{name}.png"
|
||||
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
|
||||
|
@ -322,11 +309,9 @@ def test_combine(
|
|||
d = ImageDraw.Draw(im)
|
||||
d.line(((0, 200), (400, 200)), "gray")
|
||||
d.line(((200, 0), (200, 400)), "gray")
|
||||
try:
|
||||
d.text((200, 200), text, fill="black", anchor=anchor, direction=dir, font=f)
|
||||
except ValueError as ex:
|
||||
if str(ex) == "libraqm 0.7 or greater required for 'ttb' direction":
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
if direction == "ttb" and not has_feature_version("raqm", "0.7"):
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
d.text((200, 200), text, fill="black", anchor=anchor, direction=direction, font=f)
|
||||
|
||||
assert_image_similar_tofile(im, path, epsilon)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import pytest
|
|||
|
||||
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:
|
||||
|
@ -266,16 +266,18 @@ def test_unknown_pattern() -> None:
|
|||
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
|
||||
lb = ImageMorph.LutBuilder(op_name="corner")
|
||||
new_patterns = ["a pattern with a syntax error"]
|
||||
new_patterns = [pattern]
|
||||
lb.add_patterns(new_patterns)
|
||||
|
||||
# Act / Assert
|
||||
with pytest.raises(
|
||||
Exception, match='Syntax error in pattern "a pattern with a syntax error"'
|
||||
):
|
||||
with pytest.raises(Exception, match='Syntax error in pattern "'):
|
||||
lb.build_lut()
|
||||
|
||||
|
||||
|
|
|
@ -57,3 +57,13 @@ def test_constant() -> None:
|
|||
assert st.rms[0] == 128
|
||||
assert st.var[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]
|
||||
|
|
|
@ -28,15 +28,13 @@ def test_numpy_to_image() -> None:
|
|||
a = numpy.array(data, dtype=dtype)
|
||||
a.shape = TEST_IMAGE_SIZE
|
||||
i = Image.fromarray(a)
|
||||
if list(i.getdata()) != data:
|
||||
print("data mismatch for", dtype)
|
||||
assert list(i.getdata()) == data
|
||||
else:
|
||||
data = list(range(100))
|
||||
a = numpy.array([[x] * bands for x in data], dtype=dtype)
|
||||
a.shape = TEST_IMAGE_SIZE[0], TEST_IMAGE_SIZE[1], bands
|
||||
i = Image.fromarray(a)
|
||||
if list(i.getchannel(0).getdata()) != list(range(100)):
|
||||
print("data mismatch for", dtype)
|
||||
assert list(i.getchannel(0).getdata()) == list(range(100))
|
||||
return i
|
||||
|
||||
# Check supported 1-bit integer formats
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
|
@ -8,12 +9,12 @@ import pytest
|
|||
|
||||
from PIL import Image
|
||||
|
||||
from .helper import is_win32
|
||||
|
||||
min_iterations = 100
|
||||
max_iterations = 10000
|
||||
|
||||
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
||||
pytestmark = pytest.mark.skipif(
|
||||
sys.platform.startswith("win32"), reason="requires Unix or macOS"
|
||||
)
|
||||
|
||||
|
||||
def _get_mem_usage() -> float:
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .helper import is_win32, skip_unless_feature
|
||||
from PIL import Image, features
|
||||
|
||||
# Limits for testing the leak
|
||||
mem_limit = 1024 * 1048576
|
||||
|
@ -15,8 +14,10 @@ iterations = int((mem_limit / stack_size) * 2)
|
|||
test_file = "Tests/images/rgb_trns_ycbc.jp2"
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.skipif(is_win32(), reason="requires Unix or macOS"),
|
||||
skip_unless_feature("jpg_2000"),
|
||||
pytest.mark.skipif(
|
||||
sys.platform.startswith("win32"), reason="requires Unix or macOS"
|
||||
),
|
||||
pytest.mark.skipif(not features.check("jpg_2000"), reason="jpg_2000 not available"),
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from io import BytesIO
|
||||
|
||||
import pytest
|
||||
|
||||
from .helper import hopper, is_win32
|
||||
from PIL import Image
|
||||
|
||||
iterations = 5000
|
||||
|
||||
|
@ -18,7 +19,9 @@ valgrind --tool=massif python test-installed.py -s -v checks/check_jpeg_leaks.py
|
|||
"""
|
||||
|
||||
|
||||
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
||||
pytestmark = pytest.mark.skipif(
|
||||
sys.platform.startswith("win32"), reason="requires Unix or macOS"
|
||||
)
|
||||
|
||||
"""
|
||||
pre patch:
|
||||
|
@ -112,10 +115,10 @@ standard_chrominance_qtable = (
|
|||
),
|
||||
)
|
||||
def test_qtables_leak(qtables: tuple[tuple[int, ...]] | list[tuple[int, ...]]) -> None:
|
||||
im = hopper("RGB")
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", qtables=qtables)
|
||||
with Image.open("Tests/images/hopper.ppm") as im:
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", qtables=qtables)
|
||||
|
||||
|
||||
def test_exif_leak() -> None:
|
||||
|
@ -173,12 +176,12 @@ def test_exif_leak() -> None:
|
|||
0 +----------------------------------------------------------------------->Gi
|
||||
0 11.33
|
||||
"""
|
||||
im = hopper("RGB")
|
||||
exif = b"12345678" * 4096
|
||||
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", exif=exif)
|
||||
with Image.open("Tests/images/hopper.ppm") as im:
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG", exif=exif)
|
||||
|
||||
|
||||
def test_base_save() -> None:
|
||||
|
@ -207,8 +210,7 @@ def test_base_save() -> None:
|
|||
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
|
||||
0 +----------------------------------------------------------------------->Gi
|
||||
0 7.882"""
|
||||
im = hopper("RGB")
|
||||
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG")
|
||||
with Image.open("Tests/images/hopper.ppm") as im:
|
||||
for _ in range(iterations):
|
||||
test_output = BytesIO()
|
||||
im.save(test_output, "JPEG")
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# install libimagequant
|
||||
|
||||
archive_name=libimagequant
|
||||
archive_version=4.3.4
|
||||
archive_version=4.4.0
|
||||
|
||||
archive=$archive_name-$archive_version
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ Here is a list of PyPI projects that offer additional plugins:
|
|||
* :pypi:`heif-image-plugin`: Simple HEIF/HEIC images plugin, based on the pyheif library.
|
||||
* :pypi:`jxlpy`: Introduces reading and writing support for JPEG XL.
|
||||
* :pypi:`pillow-heif`: Python bindings to libheif for working with HEIF images.
|
||||
* :pypi:`pillow-jpls`: Plugin for the JPEG-LS codec, based on the Charls JPEG-LS implemetation. Python bindings implemented using pybind11.
|
||||
* :pypi:`pillow-jpls`: Plugin for the JPEG-LS codec, based on the Charls JPEG-LS implementation. Python bindings implemented using pybind11.
|
||||
* :pypi:`pillow-jxl-plugin`: Plugin for JPEG-XL, using Rust for bindings.
|
||||
* :pypi:`pillow-mbm`: Adds support for KSP's proprietary MBM texture format.
|
||||
* :pypi:`pillow-svg`: Implements basic SVG read support. Supports basic paths, shapes, and text.
|
||||
|
|
|
@ -64,7 +64,7 @@ Many of Pillow's features require external libraries:
|
|||
|
||||
* **libimagequant** provides improved color quantization
|
||||
|
||||
* Pillow has been tested with libimagequant **2.6-4.3.4**
|
||||
* Pillow has been tested with libimagequant **2.6-4.4.0**
|
||||
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||
the Pillow license, therefore we will not be distributing binaries
|
||||
with libimagequant support enabled.
|
||||
|
|
|
@ -31,6 +31,8 @@ These platforms are built and tested for every change.
|
|||
+----------------------------------+----------------------------+---------------------+
|
||||
| Debian 12 Bookworm | 3.11 | x86, x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Debian 13 Trixie | 3.13 | x86, x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 41 | 3.13 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 42 | 3.13 | x86-64 |
|
||||
|
@ -39,7 +41,7 @@ These platforms are built and tested for every change.
|
|||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 13 Ventura | 3.10 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 14 Sonoma | 3.11, 3.12, 3.13, 3.14 | arm64 |
|
||||
| macOS 15 Sequoia | 3.11, 3.12, 3.13, 3.14 | arm64 |
|
||||
| | PyPy3 | |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
||||
|
|
|
@ -74,5 +74,6 @@ Constants
|
|||
---------
|
||||
|
||||
.. autodata:: PIL.ImageFile.LOAD_TRUNCATED_IMAGES
|
||||
.. autodata:: PIL.ImageFile.MAXBLOCK
|
||||
.. autodata:: PIL.ImageFile.ERRORS
|
||||
:annotation:
|
||||
|
|
|
@ -159,3 +159,10 @@ deprecated. Part of this functionality has been restored in Pillow 12.0.0. Since
|
|||
values do not contain information about palettes or color spaces, the parameter can be
|
||||
used to place grayscale L mode data within a P mode image, or read RGB data as YCbCr
|
||||
for example.
|
||||
|
||||
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:
|
||||
return (
|
||||
len(prefix) >= 6
|
||||
len(prefix) >= 16
|
||||
and i16(prefix, 4) in [0xAF11, 0xAF12]
|
||||
and i16(prefix, 14) in [0, 3] # flags
|
||||
)
|
||||
|
|
|
@ -54,7 +54,7 @@ class GbrImageFile(ImageFile.ImageFile):
|
|||
width = i32(self.fp.read(4))
|
||||
height = 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"
|
||||
raise SyntaxError(msg)
|
||||
if color_depth not in (1, 4):
|
||||
|
@ -71,7 +71,7 @@ class GbrImageFile(ImageFile.ImageFile):
|
|||
raise SyntaxError(msg)
|
||||
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:
|
||||
self._mode = "L"
|
||||
|
@ -80,8 +80,6 @@ class GbrImageFile(ImageFile.ImageFile):
|
|||
|
||||
self._size = width, height
|
||||
|
||||
self.info["comment"] = comment
|
||||
|
||||
# Image might not be small
|
||||
Image._decompression_bomb_check(self.size)
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ def register_handler(handler: ImageFile.StubHandler | None) -> None:
|
|||
|
||||
|
||||
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):
|
||||
|
|
|
@ -103,7 +103,6 @@ try:
|
|||
raise ImportError(msg)
|
||||
|
||||
except ImportError as v:
|
||||
core = DeferredError.new(ImportError("The _imaging C module is not installed."))
|
||||
# Explanations for ways that we know we might have an import error
|
||||
if str(v).startswith("Module use of python"):
|
||||
# The _imaging C module is present, but not compiled for
|
||||
|
@ -3569,9 +3568,8 @@ def alpha_composite(im1: Image, im2: Image) -> Image:
|
|||
"""
|
||||
Alpha composite im2 over im1.
|
||||
|
||||
:param im1: The first image. Must have mode RGBA.
|
||||
:param im2: The second image. Must have mode RGBA, and the same size as
|
||||
the first image.
|
||||
:param im1: The first image. Must have mode RGBA or LA.
|
||||
:param im2: The second image. Must have the same mode and size as the first image.
|
||||
:returns: An :py:class:`~PIL.Image.Image` object.
|
||||
"""
|
||||
|
||||
|
@ -4218,6 +4216,8 @@ class Exif(_ExifBase):
|
|||
del self._info[tag]
|
||||
else:
|
||||
del self._data[tag]
|
||||
if tag in self._ifds:
|
||||
del self._ifds[tag]
|
||||
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
keys = set(self._data)
|
||||
|
|
|
@ -46,6 +46,18 @@ if TYPE_CHECKING:
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
MAXBLOCK = 65536
|
||||
"""
|
||||
By default, Pillow processes image data in blocks. This helps to prevent excessive use
|
||||
of resources. Codecs may disable this behaviour with ``_pulls_fd`` or ``_pushes_fd``.
|
||||
|
||||
When reading an image, this is the number of bytes to read at once.
|
||||
|
||||
When writing an image, this is the number of bytes to write at once.
|
||||
If the image width times 4 is greater, then that will be used instead.
|
||||
Plugins may also set a greater number.
|
||||
|
||||
User code may set this to another number.
|
||||
"""
|
||||
|
||||
SAFEBLOCK = 1024 * 1024
|
||||
|
||||
|
|
|
@ -671,11 +671,7 @@ class FreeTypeFont:
|
|||
:returns: A list of the named styles in a variation font.
|
||||
:exception OSError: If the font is not a variation font.
|
||||
"""
|
||||
try:
|
||||
names = self.font.getvarnames()
|
||||
except AttributeError as e:
|
||||
msg = "FreeType 2.9.1 or greater is required"
|
||||
raise NotImplementedError(msg) from e
|
||||
names = self.font.getvarnames()
|
||||
return [name.replace(b"\x00", b"") for name in names]
|
||||
|
||||
def set_variation_by_name(self, name: str | bytes) -> None:
|
||||
|
@ -702,11 +698,7 @@ class FreeTypeFont:
|
|||
:returns: A list of the axes in a variation font.
|
||||
:exception OSError: If the font is not a variation font.
|
||||
"""
|
||||
try:
|
||||
axes = self.font.getvaraxes()
|
||||
except AttributeError as e:
|
||||
msg = "FreeType 2.9.1 or greater is required"
|
||||
raise NotImplementedError(msg) from e
|
||||
axes = self.font.getvaraxes()
|
||||
for axis in axes:
|
||||
if axis["name"]:
|
||||
axis["name"] = axis["name"].replace(b"\x00", b"")
|
||||
|
@ -717,11 +709,7 @@ class FreeTypeFont:
|
|||
:param axes: A list of values for each axis.
|
||||
:exception OSError: If the font is not a variation font.
|
||||
"""
|
||||
try:
|
||||
self.font.setvaraxes(axes)
|
||||
except AttributeError as e:
|
||||
msg = "FreeType 2.9.1 or greater is required"
|
||||
raise NotImplementedError(msg) from e
|
||||
self.font.setvaraxes(axes)
|
||||
|
||||
|
||||
class TransposedFont:
|
||||
|
|
|
@ -150,7 +150,7 @@ class LutBuilder:
|
|||
|
||||
# Parse and create symmetries of the patterns strings
|
||||
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:
|
||||
msg = 'Syntax error in pattern "' + p + '"'
|
||||
raise Exception(msg)
|
||||
|
|
|
@ -120,7 +120,7 @@ class Stat:
|
|||
@cached_property
|
||||
def mean(self) -> list[float]:
|
||||
"""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
|
||||
def median(self) -> list[int]:
|
||||
|
@ -141,13 +141,20 @@ class Stat:
|
|||
@cached_property
|
||||
def rms(self) -> list[float]:
|
||||
"""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
|
||||
def var(self) -> list[float]:
|
||||
"""Variance for each band in the image."""
|
||||
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
|
||||
]
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class PcdImageFile(ImageFile.ImageFile):
|
|||
assert self.fp is not None
|
||||
|
||||
self.fp.seek(2048)
|
||||
s = self.fp.read(2048)
|
||||
s = self.fp.read(1539)
|
||||
|
||||
if not s.startswith(b"PCD_"):
|
||||
msg = "not a PCD file"
|
||||
|
@ -46,14 +46,13 @@ class PcdImageFile(ImageFile.ImageFile):
|
|||
self.tile_post_rotate = -90
|
||||
|
||||
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)]
|
||||
|
||||
def load_end(self) -> None:
|
||||
if self.tile_post_rotate:
|
||||
# Handle rotated PCDs
|
||||
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:
|
||||
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:
|
||||
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"
|
||||
|
||||
|
||||
##
|
||||
|
|
|
@ -80,7 +80,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
|||
format_description = "Windows Metafile"
|
||||
|
||||
def _open(self) -> None:
|
||||
# check placable header
|
||||
# check placeable header
|
||||
s = self.fp.read(44)
|
||||
|
||||
if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"):
|
||||
|
|
|
@ -1221,8 +1221,6 @@ glyph_error:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
|
||||
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
||||
static PyObject *
|
||||
font_getvarnames(FontObject *self) {
|
||||
int error;
|
||||
|
@ -1432,7 +1430,6 @@ font_setvaraxes(FontObject *self, PyObject *args) {
|
|||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
font_dealloc(FontObject *self) {
|
||||
|
@ -1451,13 +1448,10 @@ static PyMethodDef font_methods[] = {
|
|||
{"render", (PyCFunction)font_render, METH_VARARGS},
|
||||
{"getsize", (PyCFunction)font_getsize, METH_VARARGS},
|
||||
{"getlength", (PyCFunction)font_getlength, METH_VARARGS},
|
||||
#if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 9) || \
|
||||
(FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 9 && FREETYPE_PATCH == 1)
|
||||
{"getvarnames", (PyCFunction)font_getvarnames, METH_NOARGS},
|
||||
{"getvaraxes", (PyCFunction)font_getvaraxes, METH_NOARGS},
|
||||
{"setvarname", (PyCFunction)font_setvarname, METH_VARARGS},
|
||||
{"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS},
|
||||
#endif
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
|
@ -25,13 +25,12 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) {
|
|||
int x, y;
|
||||
|
||||
/* Check arguments */
|
||||
if (!imDst || !imSrc || strcmp(imDst->mode, "RGBA") ||
|
||||
imDst->type != IMAGING_TYPE_UINT8 || imDst->bands != 4) {
|
||||
if (!imDst || !imSrc ||
|
||||
(strcmp(imDst->mode, "RGBA") && strcmp(imDst->mode, "LA"))) {
|
||||
return ImagingError_ModeError();
|
||||
}
|
||||
|
||||
if (strcmp(imDst->mode, imSrc->mode) || imDst->type != imSrc->type ||
|
||||
imDst->bands != imSrc->bands || imDst->xsize != imSrc->xsize ||
|
||||
if (strcmp(imDst->mode, imSrc->mode) || imDst->xsize != imSrc->xsize ||
|
||||
imDst->ysize != imSrc->ysize) {
|
||||
return ImagingError_Mismatch();
|
||||
}
|
||||
|
|
2
tox.ini
2
tox.ini
|
@ -30,4 +30,4 @@ skip_install = true
|
|||
deps =
|
||||
-r .ci/requirements-mypy.txt
|
||||
commands =
|
||||
mypy conftest.py selftest.py setup.py docs src winbuild Tests {posargs}
|
||||
mypy conftest.py selftest.py setup.py checks docs src winbuild Tests {posargs}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 42d761728d141d8462cd9943f4329f12fe62b155
|
||||
Subproject commit 64739327166fcad1fa41ad9b23fa910fa244c84f
|
|
@ -116,11 +116,11 @@ V = {
|
|||
"BROTLI": "1.1.0",
|
||||
"FREETYPE": "2.13.3",
|
||||
"FRIBIDI": "1.0.16",
|
||||
"HARFBUZZ": "11.2.1",
|
||||
"HARFBUZZ": "11.3.3",
|
||||
"JPEGTURBO": "3.1.1",
|
||||
"LCMS2": "2.17",
|
||||
"LIBAVIF": "1.3.0",
|
||||
"LIBIMAGEQUANT": "4.3.4",
|
||||
"LIBIMAGEQUANT": "4.4.0",
|
||||
"LIBPNG": "1.6.50",
|
||||
"LIBWEBP": "1.6.0",
|
||||
"OPENJPEG": "2.5.3",
|
||||
|
|
Loading…
Reference in New Issue
Block a user