mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-09-24 13:07:00 +03:00
Merge branch 'main' into has_characters
This commit is contained in:
commit
4bc6dedcf9
|
@ -1 +1 @@
|
|||
cibuildwheel==3.1.3
|
||||
cibuildwheel==3.1.4
|
||||
|
|
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
|
||||
|
||||
|
|
2
.github/workflows/test-docker.yml
vendored
2
.github/workflows/test-docker.yml
vendored
|
@ -68,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
|
||||
|
|
2
.github/workflows/test.yml
vendored
2
.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
|
||||
|
||||
|
|
10
.github/workflows/wheels.yml
vendored
10
.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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,10 +53,7 @@ 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"):
|
||||
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)
|
||||
|
@ -81,10 +78,7 @@ 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"):
|
||||
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()
|
||||
|
|
|
@ -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,10 +28,7 @@ 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"):
|
||||
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"
|
||||
|
|
|
@ -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":
|
||||
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,7 +118,8 @@ def test_text_direction_ttb_stroke() -> None:
|
|||
|
||||
im = Image.new(mode="RGB", size=(100, 300))
|
||||
draw = ImageDraw.Draw(im)
|
||||
try:
|
||||
if not has_feature_version("raqm", "0.7"):
|
||||
pytest.skip("libraqm 0.7 or greater not available")
|
||||
draw.text(
|
||||
(27, 27),
|
||||
"あい",
|
||||
|
@ -129,9 +129,6 @@ def test_text_direction_ttb_stroke() -> None:
|
|||
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")
|
||||
|
||||
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"
|
||||
):
|
||||
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:
|
||||
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")
|
||||
|
||||
|
||||
@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":
|
||||
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":
|
||||
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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -74,5 +74,6 @@ Constants
|
|||
---------
|
||||
|
||||
.. autodata:: PIL.ImageFile.LOAD_TRUNCATED_IMAGES
|
||||
.. autodata:: PIL.ImageFile.MAXBLOCK
|
||||
.. autodata:: PIL.ImageFile.ERRORS
|
||||
:annotation:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -681,11 +681,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
|
||||
return [name.replace(b"\x00", b"") for name in names]
|
||||
|
||||
def set_variation_by_name(self, name: str | bytes) -> None:
|
||||
|
@ -712,11 +708,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
|
||||
for axis in axes:
|
||||
if axis["name"]:
|
||||
axis["name"] = axis["name"].replace(b"\x00", b"")
|
||||
|
@ -727,11 +719,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
|
||||
|
||||
|
||||
class TransposedFont:
|
||||
|
|
|
@ -1259,8 +1259,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;
|
||||
|
@ -1470,7 +1468,6 @@ font_setvaraxes(FontObject *self, PyObject *args) {
|
|||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
font_dealloc(FontObject *self) {
|
||||
|
@ -1490,13 +1487,10 @@ static PyMethodDef font_methods[] = {
|
|||
{"getsize", (PyCFunction)font_getsize, METH_VARARGS},
|
||||
{"getlength", (PyCFunction)font_getlength, METH_VARARGS},
|
||||
{"hascharacters", (PyCFunction)font_hascharacters, 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}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user