mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-03-12 16:55:47 +03:00
Merge branch 'main' into fp
This commit is contained in:
commit
48f97f44bf
6
.github/workflows/macos-install.sh
vendored
6
.github/workflows/macos-install.sh
vendored
|
@ -10,15 +10,11 @@ brew install \
|
||||||
ghostscript \
|
ghostscript \
|
||||||
jpeg-turbo \
|
jpeg-turbo \
|
||||||
libimagequant \
|
libimagequant \
|
||||||
|
libraqm \
|
||||||
libtiff \
|
libtiff \
|
||||||
little-cms2 \
|
little-cms2 \
|
||||||
openjpeg \
|
openjpeg \
|
||||||
webp
|
webp
|
||||||
if [[ "$ImageOS" == "macos13" ]]; then
|
|
||||||
brew install --ignore-dependencies libraqm
|
|
||||||
else
|
|
||||||
brew install libraqm
|
|
||||||
fi
|
|
||||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||||
|
|
||||||
python3 -m pip install coverage
|
python3 -m pip install coverage
|
||||||
|
|
1
.github/workflows/test-mingw.yml
vendored
1
.github/workflows/test-mingw.yml
vendored
|
@ -60,7 +60,6 @@ jobs:
|
||||||
mingw-w64-x86_64-gcc \
|
mingw-w64-x86_64-gcc \
|
||||||
mingw-w64-x86_64-ghostscript \
|
mingw-w64-x86_64-ghostscript \
|
||||||
mingw-w64-x86_64-lcms2 \
|
mingw-w64-x86_64-lcms2 \
|
||||||
mingw-w64-x86_64-libimagequant \
|
|
||||||
mingw-w64-x86_64-libjpeg-turbo \
|
mingw-w64-x86_64-libjpeg-turbo \
|
||||||
mingw-w64-x86_64-libraqm \
|
mingw-w64-x86_64-libraqm \
|
||||||
mingw-w64-x86_64-libtiff \
|
mingw-w64-x86_64-libtiff \
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.8.6
|
rev: v0.9.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--exit-non-zero-on-fix]
|
args: [--exit-non-zero-on-fix]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
rev: 24.10.0
|
rev: 25.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/bandit
|
- repo: https://github.com/PyCQA/bandit
|
||||||
rev: 1.8.0
|
rev: 1.8.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: bandit
|
- id: bandit
|
||||||
args: [--severity-level=high]
|
args: [--severity-level=high]
|
||||||
|
@ -24,7 +24,7 @@ repos:
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: v19.1.6
|
rev: v19.1.7
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
types: [c]
|
types: [c]
|
||||||
|
@ -50,14 +50,14 @@ repos:
|
||||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||||
|
|
||||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||||
rev: 0.30.0
|
rev: 0.31.1
|
||||||
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/woodruffw/zizmor-pre-commit
|
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||||
rev: v1.0.0
|
rev: v1.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: zizmor
|
- id: zizmor
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ repos:
|
||||||
additional_dependencies: [trove-classifiers>=2024.10.12]
|
additional_dependencies: [trove-classifiers>=2024.10.12]
|
||||||
|
|
||||||
- repo: https://github.com/tox-dev/tox-ini-fmt
|
- repo: https://github.com/tox-dev/tox-ini-fmt
|
||||||
rev: 1.4.1
|
rev: 1.5.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: tox-ini-fmt
|
- id: tox-ini-fmt
|
||||||
|
|
||||||
|
|
BIN
Tests/images/multiline_text_justify.png
Normal file
BIN
Tests/images/multiline_text_justify.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
|
@ -19,7 +19,7 @@ except ImportError:
|
||||||
class TestColorLut3DCoreAPI:
|
class TestColorLut3DCoreAPI:
|
||||||
def generate_identity_table(
|
def generate_identity_table(
|
||||||
self, channels: int, size: int | tuple[int, int, int]
|
self, channels: int, size: int | tuple[int, int, int]
|
||||||
) -> tuple[int, int, int, int, list[float]]:
|
) -> tuple[int, tuple[int, int, int], list[float]]:
|
||||||
if isinstance(size, tuple):
|
if isinstance(size, tuple):
|
||||||
size_1d, size_2d, size_3d = size
|
size_1d, size_2d, size_3d = size
|
||||||
else:
|
else:
|
||||||
|
@ -39,9 +39,7 @@ class TestColorLut3DCoreAPI:
|
||||||
]
|
]
|
||||||
return (
|
return (
|
||||||
channels,
|
channels,
|
||||||
size_1d,
|
(size_1d, size_2d, size_3d),
|
||||||
size_2d,
|
|
||||||
size_3d,
|
|
||||||
[item for sublist in table for item in sublist],
|
[item for sublist in table for item in sublist],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,21 +87,21 @@ class TestColorLut3DCoreAPI:
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
|
with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, 0] * 7
|
"RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), [0, 0, 0] * 7
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
|
with pytest.raises(ValueError, match=r"size1D \* size2D \* size3D"):
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, 0] * 9
|
"RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), [0, 0, 0] * 9
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
im.im.color_lut_3d(
|
im.im.color_lut_3d(
|
||||||
"RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, [0, 0, "0"] * 8
|
"RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), [0, 0, "0"] * 8
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, 2, 2, 2, 16)
|
im.im.color_lut_3d("RGB", Image.Resampling.BILINEAR, 3, (2, 2, 2), 16)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"lut_mode, table_channels, table_size",
|
"lut_mode, table_channels, table_size",
|
||||||
|
@ -264,7 +262,7 @@ class TestColorLut3DCoreAPI:
|
||||||
assert_image_equal(
|
assert_image_equal(
|
||||||
Image.merge('RGB', im.split()[::-1]),
|
Image.merge('RGB', im.split()[::-1]),
|
||||||
im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
|
im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
|
||||||
3, 2, 2, 2, [
|
3, (2, 2, 2), [
|
||||||
0, 0, 0, 0, 0, 1,
|
0, 0, 0, 0, 0, 1,
|
||||||
0, 1, 0, 0, 1, 1,
|
0, 1, 0, 0, 1, 1,
|
||||||
|
|
||||||
|
@ -286,7 +284,7 @@ class TestColorLut3DCoreAPI:
|
||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
|
transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
|
||||||
3, 2, 2, 2,
|
3, (2, 2, 2),
|
||||||
[
|
[
|
||||||
-1, -1, -1, 2, -1, -1,
|
-1, -1, -1, 2, -1, -1,
|
||||||
-1, 2, -1, 2, 2, -1,
|
-1, 2, -1, 2, 2, -1,
|
||||||
|
@ -307,7 +305,7 @@ class TestColorLut3DCoreAPI:
|
||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
|
transformed = im._new(im.im.color_lut_3d('RGB', Image.Resampling.BILINEAR,
|
||||||
3, 2, 2, 2,
|
3, (2, 2, 2),
|
||||||
[
|
[
|
||||||
-3, -3, -3, 5, -3, -3,
|
-3, -3, -3, 5, -3, -3,
|
||||||
-3, 5, -3, 5, 5, -3,
|
-3, 5, -3, 5, 5, -3,
|
||||||
|
|
|
@ -86,12 +86,12 @@ def test_invalid_file() -> None:
|
||||||
def test_l_mode_transparency() -> None:
|
def test_l_mode_transparency() -> None:
|
||||||
with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
|
with Image.open("Tests/images/no_palette_with_transparency.gif") as im:
|
||||||
assert im.mode == "L"
|
assert im.mode == "L"
|
||||||
assert im.load()[0, 0] == 128
|
assert im.getpixel((0, 0)) == 128
|
||||||
assert im.info["transparency"] == 255
|
assert im.info["transparency"] == 255
|
||||||
|
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.mode == "L"
|
assert im.mode == "L"
|
||||||
assert im.load()[0, 0] == 128
|
assert im.getpixel((0, 0)) == 128
|
||||||
|
|
||||||
|
|
||||||
def test_l_mode_after_rgb() -> None:
|
def test_l_mode_after_rgb() -> None:
|
||||||
|
@ -311,7 +311,7 @@ def test_loading_multiple_palettes(path: str, mode: str) -> None:
|
||||||
with Image.open(path) as im:
|
with Image.open(path) as im:
|
||||||
assert im.mode == "P"
|
assert im.mode == "P"
|
||||||
first_frame_colors = im.palette.colors.keys()
|
first_frame_colors = im.palette.colors.keys()
|
||||||
original_color = im.convert("RGB").load()[0, 0]
|
original_color = im.convert("RGB").getpixel((0, 0))
|
||||||
|
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
assert im.mode == mode
|
assert im.mode == mode
|
||||||
|
@ -319,10 +319,10 @@ def test_loading_multiple_palettes(path: str, mode: str) -> None:
|
||||||
im = im.convert("RGB")
|
im = im.convert("RGB")
|
||||||
|
|
||||||
# Check a color only from the old palette
|
# Check a color only from the old palette
|
||||||
assert im.load()[0, 0] == original_color
|
assert im.getpixel((0, 0)) == original_color
|
||||||
|
|
||||||
# Check a color from the new palette
|
# Check a color from the new palette
|
||||||
assert im.load()[24, 24] not in first_frame_colors
|
assert im.getpixel((24, 24)) not in first_frame_colors
|
||||||
|
|
||||||
|
|
||||||
def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None:
|
def test_headers_saving_for_animated_gifs(tmp_path: Path) -> None:
|
||||||
|
@ -488,8 +488,7 @@ def test_eoferror() -> None:
|
||||||
|
|
||||||
def test_first_frame_transparency() -> None:
|
def test_first_frame_transparency() -> None:
|
||||||
with Image.open("Tests/images/first_frame_transparency.gif") as im:
|
with Image.open("Tests/images/first_frame_transparency.gif") as im:
|
||||||
px = im.load()
|
assert im.getpixel((0, 0)) == im.info["transparency"]
|
||||||
assert px[0, 0] == im.info["transparency"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_dispose_none() -> None:
|
def test_dispose_none() -> None:
|
||||||
|
|
|
@ -934,7 +934,7 @@ class TestFileJpeg:
|
||||||
|
|
||||||
def test_jpeg_magic_number(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_jpeg_magic_number(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
size = 4097
|
size = 4097
|
||||||
buffer = BytesIO(b"\xFF" * size) # Many xFF bytes
|
buffer = BytesIO(b"\xff" * size) # Many xff bytes
|
||||||
max_pos = 0
|
max_pos = 0
|
||||||
orig_read = buffer.read
|
orig_read = buffer.read
|
||||||
|
|
||||||
|
|
|
@ -317,7 +317,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
def check_tags(
|
def check_tags(
|
||||||
tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str]
|
tiffinfo: TiffImagePlugin.ImageFileDirectory_v2 | dict[int, str],
|
||||||
) -> None:
|
) -> None:
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
||||||
|
|
|
@ -264,7 +264,7 @@ def test_pdf_append(tmp_path: Path) -> None:
|
||||||
# append some info
|
# append some info
|
||||||
pdf.info.Title = "abc"
|
pdf.info.Title = "abc"
|
||||||
pdf.info.Author = "def"
|
pdf.info.Author = "def"
|
||||||
pdf.info.Subject = "ghi\uABCD"
|
pdf.info.Subject = "ghi\uabcd"
|
||||||
pdf.info.Keywords = "qw)e\\r(ty"
|
pdf.info.Keywords = "qw)e\\r(ty"
|
||||||
pdf.info.Creator = "hopper()"
|
pdf.info.Creator = "hopper()"
|
||||||
pdf.start_writing()
|
pdf.start_writing()
|
||||||
|
@ -292,7 +292,7 @@ def test_pdf_append(tmp_path: Path) -> None:
|
||||||
assert pdf.info.Title == "abc"
|
assert pdf.info.Title == "abc"
|
||||||
assert pdf.info.Producer == "PdfParser"
|
assert pdf.info.Producer == "PdfParser"
|
||||||
assert pdf.info.Keywords == "qw)e\\r(ty"
|
assert pdf.info.Keywords == "qw)e\\r(ty"
|
||||||
assert pdf.info.Subject == "ghi\uABCD"
|
assert pdf.info.Subject == "ghi\uabcd"
|
||||||
assert b"CreationDate" in pdf.info
|
assert b"CreationDate" in pdf.info
|
||||||
assert b"ModDate" in pdf.info
|
assert b"ModDate" in pdf.info
|
||||||
check_pdf_pages_consistency(pdf)
|
check_pdf_pages_consistency(pdf)
|
||||||
|
|
|
@ -49,7 +49,7 @@ def test_sanity() -> None:
|
||||||
(b"P5 3 1 257 \x00\x00\x00\x80\x01\x01", "I", (0, 32640, 65535)),
|
(b"P5 3 1 257 \x00\x00\x00\x80\x01\x01", "I", (0, 32640, 65535)),
|
||||||
# P6 with maxval < 255
|
# P6 with maxval < 255
|
||||||
(
|
(
|
||||||
b"P6 3 1 17 \x00\x01\x02\x08\x09\x0A\x0F\x10\x11",
|
b"P6 3 1 17 \x00\x01\x02\x08\x09\x0a\x0f\x10\x11",
|
||||||
"RGB",
|
"RGB",
|
||||||
(
|
(
|
||||||
(0, 15, 30),
|
(0, 15, 30),
|
||||||
|
@ -60,7 +60,7 @@ def test_sanity() -> None:
|
||||||
# P6 with maxval > 255
|
# P6 with maxval > 255
|
||||||
(
|
(
|
||||||
b"P6 3 1 257 \x00\x00\x00\x01\x00\x02"
|
b"P6 3 1 257 \x00\x00\x00\x01\x00\x02"
|
||||||
b"\x00\x80\x00\x81\x00\x82\x01\x00\x01\x01\xFF\xFF",
|
b"\x00\x80\x00\x81\x00\x82\x01\x00\x01\x01\xff\xff",
|
||||||
"RGB",
|
"RGB",
|
||||||
(
|
(
|
||||||
(0, 1, 2),
|
(0, 1, 2),
|
||||||
|
|
|
@ -746,7 +746,7 @@ class TestFileTiff:
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
|
||||||
def test_fixoffsets(self) -> None:
|
def test_fixoffsets(self) -> None:
|
||||||
b = BytesIO(b"II\x2A\x00\x00\x00\x00\x00")
|
b = BytesIO(b"II\x2a\x00\x00\x00\x00\x00")
|
||||||
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
b.seek(0)
|
b.seek(0)
|
||||||
a.fixOffsets(1, isShort=True)
|
a.fixOffsets(1, isShort=True)
|
||||||
|
@ -759,14 +759,14 @@ class TestFileTiff:
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
a.fixOffsets(1)
|
a.fixOffsets(1)
|
||||||
|
|
||||||
b = BytesIO(b"II\x2A\x00\x00\x00\x00\x00")
|
b = BytesIO(b"II\x2a\x00\x00\x00\x00\x00")
|
||||||
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
a.offsetOfNewPage = 2**16
|
a.offsetOfNewPage = 2**16
|
||||||
|
|
||||||
b.seek(0)
|
b.seek(0)
|
||||||
a.fixOffsets(1, isShort=True)
|
a.fixOffsets(1, isShort=True)
|
||||||
|
|
||||||
b = BytesIO(b"II\x2B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
b = BytesIO(b"II\x2b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||||
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
a.offsetOfNewPage = 2**32
|
a.offsetOfNewPage = 2**32
|
||||||
|
|
||||||
|
@ -777,18 +777,20 @@ class TestFileTiff:
|
||||||
a.fixOffsets(1, isLong=True)
|
a.fixOffsets(1, isLong=True)
|
||||||
|
|
||||||
def test_appending_tiff_writer_writelong(self) -> None:
|
def test_appending_tiff_writer_writelong(self) -> None:
|
||||||
data = b"II\x2A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
data = b"II\x2a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||||
b = BytesIO(data)
|
b = BytesIO(data)
|
||||||
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
|
a.seek(-4, os.SEEK_CUR)
|
||||||
a.writeLong(2**32 - 1)
|
a.writeLong(2**32 - 1)
|
||||||
assert b.getvalue() == data + b"\xff\xff\xff\xff"
|
assert b.getvalue() == data[:-4] + b"\xff\xff\xff\xff"
|
||||||
|
|
||||||
def test_appending_tiff_writer_rewritelastshorttolong(self) -> None:
|
def test_appending_tiff_writer_rewritelastshorttolong(self) -> None:
|
||||||
data = b"II\x2A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
data = b"II\x2a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||||
b = BytesIO(data)
|
b = BytesIO(data)
|
||||||
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
|
a.seek(-2, os.SEEK_CUR)
|
||||||
a.rewriteLastShortToLong(2**32 - 1)
|
a.rewriteLastShortToLong(2**32 - 1)
|
||||||
assert b.getvalue() == data[:-2] + b"\xff\xff\xff\xff"
|
assert b.getvalue() == data[:-4] + b"\xff\xff\xff\xff"
|
||||||
|
|
||||||
def test_saving_icc_profile(self, tmp_path: Path) -> None:
|
def test_saving_icc_profile(self, tmp_path: Path) -> None:
|
||||||
# Tests saving TIFF with icc_profile set.
|
# Tests saving TIFF with icc_profile set.
|
||||||
|
|
|
@ -71,7 +71,7 @@ def test_load_float_dpi() -> None:
|
||||||
|
|
||||||
with open("Tests/images/drawing.emf", "rb") as fp:
|
with open("Tests/images/drawing.emf", "rb") as fp:
|
||||||
data = fp.read()
|
data = fp.read()
|
||||||
b = BytesIO(data[:8] + b"\x06\xFA" + data[10:])
|
b = BytesIO(data[:8] + b"\x06\xfa" + data[10:])
|
||||||
with Image.open(b) as im:
|
with Image.open(b) as im:
|
||||||
assert im.info["dpi"][0] == 2540
|
assert im.info["dpi"][0] == 2540
|
||||||
|
|
||||||
|
|
|
@ -578,9 +578,7 @@ class TestImage:
|
||||||
def test_one_item_tuple(self) -> None:
|
def test_one_item_tuple(self) -> None:
|
||||||
for mode in ("I", "F", "L"):
|
for mode in ("I", "F", "L"):
|
||||||
im = Image.new(mode, (100, 100), (5,))
|
im = Image.new(mode, (100, 100), (5,))
|
||||||
px = im.load()
|
assert im.getpixel((0, 0)) == 5
|
||||||
assert px is not None
|
|
||||||
assert px[0, 0] == 5
|
|
||||||
|
|
||||||
def test_linear_gradient_wrong_mode(self) -> None:
|
def test_linear_gradient_wrong_mode(self) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
|
@ -222,9 +222,7 @@ def test_l_macro_rounding(convert_mode: str) -> None:
|
||||||
im.palette.getcolor((0, 1, 2))
|
im.palette.getcolor((0, 1, 2))
|
||||||
|
|
||||||
converted_im = im.convert(convert_mode)
|
converted_im = im.convert(convert_mode)
|
||||||
px = converted_im.load()
|
converted_color = converted_im.getpixel((0, 0))
|
||||||
assert px is not None
|
|
||||||
converted_color = px[0, 0]
|
|
||||||
if convert_mode == "LA":
|
if convert_mode == "LA":
|
||||||
assert isinstance(converted_color, tuple)
|
assert isinstance(converted_color, tuple)
|
||||||
converted_color = converted_color[0]
|
converted_color = converted_color[0]
|
||||||
|
|
|
@ -148,10 +148,8 @@ def test_palette(method: Image.Quantize, color: tuple[int, ...]) -> None:
|
||||||
im = Image.new("RGBA" if len(color) == 4 else "RGB", (1, 1), color)
|
im = Image.new("RGBA" if len(color) == 4 else "RGB", (1, 1), color)
|
||||||
|
|
||||||
converted = im.quantize(method=method)
|
converted = im.quantize(method=method)
|
||||||
converted_px = converted.load()
|
|
||||||
assert converted_px is not None
|
|
||||||
assert converted.palette is not None
|
assert converted.palette is not None
|
||||||
assert converted_px[0, 0] == converted.palette.colors[color]
|
assert converted.getpixel((0, 0)) == converted.palette.colors[color]
|
||||||
|
|
||||||
|
|
||||||
def test_small_palette() -> None:
|
def test_small_palette() -> None:
|
||||||
|
|
|
@ -812,7 +812,7 @@ def test_rounded_rectangle(
|
||||||
tuple[int, int, int, int]
|
tuple[int, int, int, int]
|
||||||
| tuple[list[int]]
|
| tuple[list[int]]
|
||||||
| tuple[tuple[int, int], tuple[int, int]]
|
| tuple[tuple[int, int], tuple[int, int]]
|
||||||
)
|
),
|
||||||
) -> None:
|
) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (200, 200))
|
im = Image.new("RGB", (200, 200))
|
||||||
|
|
|
@ -254,7 +254,8 @@ def test_render_multiline_text(font: ImageFont.FreeTypeFont) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"align, ext", (("left", ""), ("center", "_center"), ("right", "_right"))
|
"align, ext",
|
||||||
|
(("left", ""), ("center", "_center"), ("right", "_right"), ("justify", "_justify")),
|
||||||
)
|
)
|
||||||
def test_render_multiline_text_align(
|
def test_render_multiline_text_align(
|
||||||
font: ImageFont.FreeTypeFont, align: str, ext: str
|
font: ImageFont.FreeTypeFont, align: str, ext: str
|
||||||
|
@ -557,7 +558,7 @@ def test_render_empty(font: ImageFont.FreeTypeFont) -> None:
|
||||||
|
|
||||||
def test_unicode_extended(layout_engine: ImageFont.Layout) -> None:
|
def test_unicode_extended(layout_engine: ImageFont.Layout) -> None:
|
||||||
# issue #3777
|
# issue #3777
|
||||||
text = "A\u278A\U0001F12B"
|
text = "A\u278a\U0001f12b"
|
||||||
target = "Tests/images/unicode_extended.png"
|
target = "Tests/images/unicode_extended.png"
|
||||||
|
|
||||||
ttf = ImageFont.truetype(
|
ttf = ImageFont.truetype(
|
||||||
|
@ -1026,7 +1027,7 @@ def test_sbix(layout_engine: ImageFont.Layout) -> None:
|
||||||
im = Image.new("RGB", (400, 400), "white")
|
im = Image.new("RGB", (400, 400), "white")
|
||||||
d = ImageDraw.Draw(im)
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
d.text((50, 50), "\uE901", font=font, embedded_color=True)
|
d.text((50, 50), "\ue901", font=font, embedded_color=True)
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix.png", 1)
|
||||||
except OSError as e: # pragma: no cover
|
except OSError as e: # pragma: no cover
|
||||||
|
@ -1043,7 +1044,7 @@ def test_sbix_mask(layout_engine: ImageFont.Layout) -> None:
|
||||||
im = Image.new("RGB", (400, 400), "white")
|
im = Image.new("RGB", (400, 400), "white")
|
||||||
d = ImageDraw.Draw(im)
|
d = ImageDraw.Draw(im)
|
||||||
|
|
||||||
d.text((50, 50), "\uE901", (100, 0, 0), font=font)
|
d.text((50, 50), "\ue901", (100, 0, 0), font=font)
|
||||||
|
|
||||||
assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1)
|
assert_image_similar_tofile(im, "Tests/images/chromacheck-sbix_mask.png", 1)
|
||||||
except OSError as e: # pragma: no cover
|
except OSError as e: # pragma: no cover
|
||||||
|
|
|
@ -229,7 +229,7 @@ def test_getlength(
|
||||||
@pytest.mark.parametrize("direction", ("ltr", "ttb"))
|
@pytest.mark.parametrize("direction", ("ltr", "ttb"))
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"text",
|
"text",
|
||||||
("i" + ("\u030C" * 15) + "i", "i" + "\u032C" * 15 + "i", "\u035Cii", "i\u0305i"),
|
("i" + ("\u030c" * 15) + "i", "i" + "\u032c" * 15 + "i", "\u035cii", "i\u0305i"),
|
||||||
ids=("caron-above", "caron-below", "double-breve", "overline"),
|
ids=("caron-above", "caron-below", "double-breve", "overline"),
|
||||||
)
|
)
|
||||||
def test_getlength_combine(mode: str, direction: str, text: str) -> None:
|
def test_getlength_combine(mode: str, direction: str, text: str) -> None:
|
||||||
|
@ -272,27 +272,27 @@ def test_anchor_ttb(anchor: str) -> None:
|
||||||
|
|
||||||
combine_tests = (
|
combine_tests = (
|
||||||
# extends above (e.g. issue #4553)
|
# extends above (e.g. issue #4553)
|
||||||
("caron", "a\u030C\u030C\u030C\u030C\u030Cb", None, None, 0.08),
|
("caron", "a\u030c\u030c\u030c\u030c\u030cb", None, None, 0.08),
|
||||||
("caron_la", "a\u030C\u030C\u030C\u030C\u030Cb", "la", None, 0.08),
|
("caron_la", "a\u030c\u030c\u030c\u030c\u030cb", "la", None, 0.08),
|
||||||
("caron_lt", "a\u030C\u030C\u030C\u030C\u030Cb", "lt", None, 0.08),
|
("caron_lt", "a\u030c\u030c\u030c\u030c\u030cb", "lt", None, 0.08),
|
||||||
("caron_ls", "a\u030C\u030C\u030C\u030C\u030Cb", "ls", None, 0.08),
|
("caron_ls", "a\u030c\u030c\u030c\u030c\u030cb", "ls", None, 0.08),
|
||||||
("caron_ttb", "ca" + ("\u030C" * 15) + "b", None, "ttb", 0.3),
|
("caron_ttb", "ca" + ("\u030c" * 15) + "b", None, "ttb", 0.3),
|
||||||
("caron_ttb_lt", "ca" + ("\u030C" * 15) + "b", "lt", "ttb", 0.3),
|
("caron_ttb_lt", "ca" + ("\u030c" * 15) + "b", "lt", "ttb", 0.3),
|
||||||
# extends below
|
# extends below
|
||||||
("caron_below", "a\u032C\u032C\u032C\u032C\u032Cb", None, None, 0.02),
|
("caron_below", "a\u032c\u032c\u032c\u032c\u032cb", None, None, 0.02),
|
||||||
("caron_below_ld", "a\u032C\u032C\u032C\u032C\u032Cb", "ld", None, 0.02),
|
("caron_below_ld", "a\u032c\u032c\u032c\u032c\u032cb", "ld", None, 0.02),
|
||||||
("caron_below_lb", "a\u032C\u032C\u032C\u032C\u032Cb", "lb", None, 0.02),
|
("caron_below_lb", "a\u032c\u032c\u032c\u032c\u032cb", "lb", None, 0.02),
|
||||||
("caron_below_ls", "a\u032C\u032C\u032C\u032C\u032Cb", "ls", None, 0.02),
|
("caron_below_ls", "a\u032c\u032c\u032c\u032c\u032cb", "ls", None, 0.02),
|
||||||
("caron_below_ttb", "a" + ("\u032C" * 15) + "b", None, "ttb", 0.03),
|
("caron_below_ttb", "a" + ("\u032c" * 15) + "b", None, "ttb", 0.03),
|
||||||
("caron_below_ttb_lb", "a" + ("\u032C" * 15) + "b", "lb", "ttb", 0.03),
|
("caron_below_ttb_lb", "a" + ("\u032c" * 15) + "b", "lb", "ttb", 0.03),
|
||||||
# extends to the right (e.g. issue #3745)
|
# extends to the right (e.g. issue #3745)
|
||||||
("double_breve_below", "a\u035Ci", None, None, 0.02),
|
("double_breve_below", "a\u035ci", None, None, 0.02),
|
||||||
("double_breve_below_ma", "a\u035Ci", "ma", None, 0.02),
|
("double_breve_below_ma", "a\u035ci", "ma", None, 0.02),
|
||||||
("double_breve_below_ra", "a\u035Ci", "ra", None, 0.02),
|
("double_breve_below_ra", "a\u035ci", "ra", None, 0.02),
|
||||||
("double_breve_below_ttb", "a\u035Cb", None, "ttb", 0.02),
|
("double_breve_below_ttb", "a\u035cb", None, "ttb", 0.02),
|
||||||
("double_breve_below_ttb_rt", "a\u035Cb", "rt", "ttb", 0.02),
|
("double_breve_below_ttb_rt", "a\u035cb", "rt", "ttb", 0.02),
|
||||||
("double_breve_below_ttb_mt", "a\u035Cb", "mt", "ttb", 0.02),
|
("double_breve_below_ttb_mt", "a\u035cb", "mt", "ttb", 0.02),
|
||||||
("double_breve_below_ttb_st", "a\u035Cb", "st", "ttb", 0.02),
|
("double_breve_below_ttb_st", "a\u035cb", "st", "ttb", 0.02),
|
||||||
# extends to the left (fail=0.064)
|
# extends to the left (fail=0.064)
|
||||||
("overline", "i\u0305", None, None, 0.02),
|
("overline", "i\u0305", None, None, 0.02),
|
||||||
("overline_la", "i\u0305", "la", None, 0.02),
|
("overline_la", "i\u0305", "la", None, 0.02),
|
||||||
|
@ -346,7 +346,7 @@ def test_combine_multiline(anchor: str, align: str) -> None:
|
||||||
|
|
||||||
path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png"
|
path = f"Tests/images/test_combine_multiline_{anchor}_{align}.png"
|
||||||
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
|
f = ImageFont.truetype("Tests/fonts/NotoSans-Regular.ttf", 48)
|
||||||
text = "i\u0305\u035C\ntext" # i with overline and double breve, and a word
|
text = "i\u0305\u035c\ntext" # i with overline and double breve, and a word
|
||||||
|
|
||||||
im = Image.new("RGB", (400, 400), "white")
|
im = Image.new("RGB", (400, 400), "white")
|
||||||
d = ImageDraw.Draw(im)
|
d = ImageDraw.Draw(im)
|
||||||
|
|
|
@ -165,14 +165,10 @@ def test_pad() -> None:
|
||||||
def test_pad_round() -> None:
|
def test_pad_round() -> None:
|
||||||
im = Image.new("1", (1, 1), 1)
|
im = Image.new("1", (1, 1), 1)
|
||||||
new_im = ImageOps.pad(im, (4, 1))
|
new_im = ImageOps.pad(im, (4, 1))
|
||||||
px = new_im.load()
|
assert new_im.getpixel((2, 0)) == 1
|
||||||
assert px is not None
|
|
||||||
assert px[2, 0] == 1
|
|
||||||
|
|
||||||
new_im = ImageOps.pad(im, (1, 4))
|
new_im = ImageOps.pad(im, (1, 4))
|
||||||
px = new_im.load()
|
assert new_im.getpixel((0, 2)) == 1
|
||||||
assert px is not None
|
|
||||||
assert px[0, 2] == 1
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||||
|
|
|
@ -189,7 +189,7 @@ def test_2bit_palette(tmp_path: Path) -> None:
|
||||||
|
|
||||||
rgb = b"\x00" * 2 + b"\x01" * 2 + b"\x02" * 2
|
rgb = b"\x00" * 2 + b"\x01" * 2 + b"\x02" * 2
|
||||||
img = Image.frombytes("P", (6, 1), rgb)
|
img = Image.frombytes("P", (6, 1), rgb)
|
||||||
img.putpalette(b"\xFF\x00\x00\x00\xFF\x00\x00\x00\xFF") # RGB
|
img.putpalette(b"\xff\x00\x00\x00\xff\x00\x00\x00\xff") # RGB
|
||||||
img.save(outfile, format="PNG")
|
img.save(outfile, format="PNG")
|
||||||
|
|
||||||
assert_image_equal_tofile(img, outfile)
|
assert_image_equal_tofile(img, outfile)
|
||||||
|
|
|
@ -79,7 +79,7 @@ def test_path_constructors(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_invalid_path_constructors(
|
def test_invalid_path_constructors(
|
||||||
coords: tuple[str, str] | Sequence[Sequence[int]]
|
coords: tuple[str, str] | Sequence[Sequence[int]],
|
||||||
) -> None:
|
) -> None:
|
||||||
# Act
|
# Act
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises(ValueError) as e:
|
||||||
|
|
|
@ -141,9 +141,7 @@ def test_save_tiff_uint16() -> None:
|
||||||
a.shape = TEST_IMAGE_SIZE
|
a.shape = TEST_IMAGE_SIZE
|
||||||
img = Image.fromarray(a)
|
img = Image.fromarray(a)
|
||||||
|
|
||||||
img_px = img.load()
|
assert img.getpixel((0, 0)) == pixel_value
|
||||||
assert img_px is not None
|
|
||||||
assert img_px[0, 0] == pixel_value
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
|
@ -20,10 +20,10 @@ from PIL.PdfParser import (
|
||||||
|
|
||||||
|
|
||||||
def test_text_encode_decode() -> None:
|
def test_text_encode_decode() -> None:
|
||||||
assert encode_text("abc") == b"\xFE\xFF\x00a\x00b\x00c"
|
assert encode_text("abc") == b"\xfe\xff\x00a\x00b\x00c"
|
||||||
assert decode_text(b"\xFE\xFF\x00a\x00b\x00c") == "abc"
|
assert decode_text(b"\xfe\xff\x00a\x00b\x00c") == "abc"
|
||||||
assert decode_text(b"abc") == "abc"
|
assert decode_text(b"abc") == "abc"
|
||||||
assert decode_text(b"\x1B a \x1C") == "\u02D9 a \u02DD"
|
assert decode_text(b"\x1b a \x1c") == "\u02d9 a \u02dd"
|
||||||
|
|
||||||
|
|
||||||
def test_indirect_refs() -> None:
|
def test_indirect_refs() -> None:
|
||||||
|
@ -45,8 +45,8 @@ def test_parsing() -> None:
|
||||||
assert PdfParser.get_value(b"false%", 0) == (False, 5)
|
assert PdfParser.get_value(b"false%", 0) == (False, 5)
|
||||||
assert PdfParser.get_value(b"null<", 0) == (None, 4)
|
assert PdfParser.get_value(b"null<", 0) == (None, 4)
|
||||||
assert PdfParser.get_value(b"%cmt\n %cmt\n 123\n", 0) == (123, 15)
|
assert PdfParser.get_value(b"%cmt\n %cmt\n 123\n", 0) == (123, 15)
|
||||||
assert PdfParser.get_value(b"<901FA3>", 0) == (b"\x90\x1F\xA3", 8)
|
assert PdfParser.get_value(b"<901FA3>", 0) == (b"\x90\x1f\xa3", 8)
|
||||||
assert PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3) == (b"\x90\x1F\xA0", 17)
|
assert PdfParser.get_value(b"asd < 9 0 1 f A > qwe", 3) == (b"\x90\x1f\xa0", 17)
|
||||||
assert PdfParser.get_value(b"(asd)", 0) == (b"asd", 5)
|
assert PdfParser.get_value(b"(asd)", 0) == (b"asd", 5)
|
||||||
assert PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0) == (b"asd(qwe)zxc", 13)
|
assert PdfParser.get_value(b"(asd(qwe)zxc)zzz(aaa)", 0) == (b"asd(qwe)zxc", 13)
|
||||||
assert PdfParser.get_value(b"(Two \\\nwords.)", 0) == (b"Two words.", 14)
|
assert PdfParser.get_value(b"(Two \\\nwords.)", 0) == (b"Two words.", 14)
|
||||||
|
@ -56,9 +56,9 @@ def test_parsing() -> None:
|
||||||
assert PdfParser.get_value(b"(One\\(paren).", 0) == (b"One(paren", 12)
|
assert PdfParser.get_value(b"(One\\(paren).", 0) == (b"One(paren", 12)
|
||||||
assert PdfParser.get_value(b"(One\\)paren).", 0) == (b"One)paren", 12)
|
assert PdfParser.get_value(b"(One\\)paren).", 0) == (b"One)paren", 12)
|
||||||
assert PdfParser.get_value(b"(\\0053)", 0) == (b"\x053", 7)
|
assert PdfParser.get_value(b"(\\0053)", 0) == (b"\x053", 7)
|
||||||
assert PdfParser.get_value(b"(\\053)", 0) == (b"\x2B", 6)
|
assert PdfParser.get_value(b"(\\053)", 0) == (b"\x2b", 6)
|
||||||
assert PdfParser.get_value(b"(\\53)", 0) == (b"\x2B", 5)
|
assert PdfParser.get_value(b"(\\53)", 0) == (b"\x2b", 5)
|
||||||
assert PdfParser.get_value(b"(\\53a)", 0) == (b"\x2Ba", 6)
|
assert PdfParser.get_value(b"(\\53a)", 0) == (b"\x2ba", 6)
|
||||||
assert PdfParser.get_value(b"(\\1111)", 0) == (b"\x491", 7)
|
assert PdfParser.get_value(b"(\\1111)", 0) == (b"\x491", 7)
|
||||||
assert PdfParser.get_value(b" 123 (", 0) == (123, 4)
|
assert PdfParser.get_value(b" 123 (", 0) == (123, 4)
|
||||||
assert round(abs(PdfParser.get_value(b" 123.4 %", 0)[0] - 123.4), 7) == 0
|
assert round(abs(PdfParser.get_value(b" 123.4 %", 0)[0] - 123.4), 7) == 0
|
||||||
|
@ -118,7 +118,7 @@ def test_pdf_repr() -> None:
|
||||||
assert pdf_repr(None) == b"null"
|
assert pdf_repr(None) == b"null"
|
||||||
assert pdf_repr(b"a)/b\\(c") == rb"(a\)/b\\\(c)"
|
assert pdf_repr(b"a)/b\\(c") == rb"(a\)/b\\\(c)"
|
||||||
assert pdf_repr([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]"
|
assert pdf_repr([123, True, {"a": PdfName(b"b")}]) == b"[ 123 true <<\n/a /b\n>> ]"
|
||||||
assert pdf_repr(PdfBinary(b"\x90\x1F\xA0")) == b"<901FA0>"
|
assert pdf_repr(PdfBinary(b"\x90\x1f\xa0")) == b"<901FA0>"
|
||||||
|
|
||||||
|
|
||||||
def test_duplicate_xref_entry() -> None:
|
def test_duplicate_xref_entry() -> None:
|
||||||
|
|
|
@ -387,8 +387,11 @@ Methods
|
||||||
the number of pixels between lines.
|
the number of pixels between lines.
|
||||||
:param align: If the text is passed on to
|
:param align: If the text is passed on to
|
||||||
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,
|
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,
|
||||||
``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
|
``"left"``, ``"center"``, ``"right"`` or ``"justify"``. Determines
|
||||||
Use the ``anchor`` parameter to specify the alignment to ``xy``.
|
the relative alignment of lines. Use the ``anchor`` parameter to
|
||||||
|
specify the alignment to ``xy``.
|
||||||
|
|
||||||
|
.. versionadded:: 11.2.0 ``"justify"``
|
||||||
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
||||||
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
|
@ -455,8 +458,11 @@ Methods
|
||||||
of Pillow, but implemented only in version 8.0.0.
|
of Pillow, but implemented only in version 8.0.0.
|
||||||
|
|
||||||
:param spacing: The number of pixels between lines.
|
:param spacing: The number of pixels between lines.
|
||||||
:param align: ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
|
:param align: ``"left"``, ``"center"``, ``"right"`` or ``"justify"``. Determines
|
||||||
Use the ``anchor`` parameter to specify the alignment to ``xy``.
|
the relative alignment of lines. Use the ``anchor`` parameter to
|
||||||
|
specify the alignment to ``xy``.
|
||||||
|
|
||||||
|
.. versionadded:: 11.2.0 ``"justify"``
|
||||||
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
||||||
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
|
@ -599,8 +605,11 @@ Methods
|
||||||
the number of pixels between lines.
|
the number of pixels between lines.
|
||||||
:param align: If the text is passed on to
|
:param align: If the text is passed on to
|
||||||
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`,
|
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textbbox`,
|
||||||
``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
|
``"left"``, ``"center"``, ``"right"`` or ``"justify"``. Determines
|
||||||
Use the ``anchor`` parameter to specify the alignment to ``xy``.
|
the relative alignment of lines. Use the ``anchor`` parameter to
|
||||||
|
specify the alignment to ``xy``.
|
||||||
|
|
||||||
|
.. versionadded:: 11.2.0 ``"justify"``
|
||||||
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
||||||
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
|
@ -650,8 +659,11 @@ Methods
|
||||||
vertical text. See :ref:`text-anchors` for details.
|
vertical text. See :ref:`text-anchors` for details.
|
||||||
This parameter is ignored for non-TrueType fonts.
|
This parameter is ignored for non-TrueType fonts.
|
||||||
:param spacing: The number of pixels between lines.
|
:param spacing: The number of pixels between lines.
|
||||||
:param align: ``"left"``, ``"center"`` or ``"right"``. Determines the relative alignment of lines.
|
:param align: ``"left"``, ``"center"``, ``"right"`` or ``"justify"``. Determines
|
||||||
Use the ``anchor`` parameter to specify the alignment to ``xy``.
|
the relative alignment of lines. Use the ``anchor`` parameter to
|
||||||
|
specify the alignment to ``xy``.
|
||||||
|
|
||||||
|
.. versionadded:: 11.2.0 ``"justify"``
|
||||||
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
:param direction: Direction of the text. It can be ``"rtl"`` (right to
|
||||||
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
|
|
|
@ -44,6 +44,18 @@ TODO
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
``"justify"`` multiline text alignment
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In addition to ``"left"``, ``"center"`` and ``"right"``, multiline text can also be
|
||||||
|
aligned using ``"justify"`` in :py:mod:`~PIL.ImageDraw`::
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
im = Image.new("RGB", (50, 25))
|
||||||
|
draw = ImageDraw.Draw(im)
|
||||||
|
draw.multiline_text((0, 0), "Multiline\ntext 1", align="justify")
|
||||||
|
draw.multiline_textbbox((0, 0), "Multiline\ntext 2", align="justify")
|
||||||
|
|
||||||
Check for MozJPEG
|
Check for MozJPEG
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -146,7 +146,7 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
if s == b"\r":
|
if s == b"\r":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not s or s == b"\0" or s == b"\x1A":
|
if not s or s == b"\0" or s == b"\x1a":
|
||||||
break
|
break
|
||||||
|
|
||||||
# FIXME: this may read whole file if not a text file
|
# FIXME: this may read whole file if not a text file
|
||||||
|
@ -210,7 +210,7 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
self._mode = self.info[MODE]
|
self._mode = self.info[MODE]
|
||||||
|
|
||||||
# Skip forward to start of image data
|
# Skip forward to start of image data
|
||||||
while s and s[:1] != b"\x1A":
|
while s and s[:1] != b"\x1a":
|
||||||
s = self.fp.read(1)
|
s = self.fp.read(1)
|
||||||
if not s:
|
if not s:
|
||||||
msg = "File truncated"
|
msg = "File truncated"
|
||||||
|
|
|
@ -514,7 +514,7 @@ class ImagePointTransform:
|
||||||
|
|
||||||
|
|
||||||
def _getscaleoffset(
|
def _getscaleoffset(
|
||||||
expr: Callable[[ImagePointTransform], ImagePointTransform | float]
|
expr: Callable[[ImagePointTransform], ImagePointTransform | float],
|
||||||
) -> tuple[float, float]:
|
) -> tuple[float, float]:
|
||||||
a = expr(ImagePointTransform(1, 0))
|
a = expr(ImagePointTransform(1, 0))
|
||||||
return (a.scale, a.offset) if isinstance(a, ImagePointTransform) else (0, a)
|
return (a.scale, a.offset) if isinstance(a, ImagePointTransform) else (0, a)
|
||||||
|
@ -3884,7 +3884,7 @@ class Exif(_ExifBase):
|
||||||
return self._fixup_dict(dict(info))
|
return self._fixup_dict(dict(info))
|
||||||
|
|
||||||
def _get_head(self) -> bytes:
|
def _get_head(self) -> bytes:
|
||||||
version = b"\x2B" if self.bigtiff else b"\x2A"
|
version = b"\x2b" if self.bigtiff else b"\x2a"
|
||||||
if self.endian == "<":
|
if self.endian == "<":
|
||||||
head = b"II" + version + b"\x00" + o32le(8)
|
head = b"II" + version + b"\x00" + o32le(8)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -557,21 +557,6 @@ class ImageDraw:
|
||||||
|
|
||||||
return split_character in text
|
return split_character in text
|
||||||
|
|
||||||
def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
|
|
||||||
return text.split("\n" if isinstance(text, str) else b"\n")
|
|
||||||
|
|
||||||
def _multiline_spacing(
|
|
||||||
self,
|
|
||||||
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
|
|
||||||
spacing: float,
|
|
||||||
stroke_width: float,
|
|
||||||
) -> float:
|
|
||||||
return (
|
|
||||||
self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
|
|
||||||
+ stroke_width
|
|
||||||
+ spacing
|
|
||||||
)
|
|
||||||
|
|
||||||
def text(
|
def text(
|
||||||
self,
|
self,
|
||||||
xy: tuple[float, float],
|
xy: tuple[float, float],
|
||||||
|
@ -699,6 +684,119 @@ class ImageDraw:
|
||||||
# Only draw normal text
|
# Only draw normal text
|
||||||
draw_text(ink)
|
draw_text(ink)
|
||||||
|
|
||||||
|
def _prepare_multiline_text(
|
||||||
|
self,
|
||||||
|
xy: tuple[float, float],
|
||||||
|
text: AnyStr,
|
||||||
|
font: (
|
||||||
|
ImageFont.ImageFont
|
||||||
|
| ImageFont.FreeTypeFont
|
||||||
|
| ImageFont.TransposedFont
|
||||||
|
| None
|
||||||
|
),
|
||||||
|
anchor: str | None,
|
||||||
|
spacing: float,
|
||||||
|
align: str,
|
||||||
|
direction: str | None,
|
||||||
|
features: list[str] | None,
|
||||||
|
language: str | None,
|
||||||
|
stroke_width: float,
|
||||||
|
embedded_color: bool,
|
||||||
|
font_size: float | None,
|
||||||
|
) -> tuple[
|
||||||
|
ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
|
||||||
|
str,
|
||||||
|
list[tuple[tuple[float, float], AnyStr]],
|
||||||
|
]:
|
||||||
|
if direction == "ttb":
|
||||||
|
msg = "ttb direction is unsupported for multiline text"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
if anchor is None:
|
||||||
|
anchor = "la"
|
||||||
|
elif len(anchor) != 2:
|
||||||
|
msg = "anchor must be a 2 character string"
|
||||||
|
raise ValueError(msg)
|
||||||
|
elif anchor[1] in "tb":
|
||||||
|
msg = "anchor not supported for multiline text"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
if font is None:
|
||||||
|
font = self._getfont(font_size)
|
||||||
|
|
||||||
|
widths = []
|
||||||
|
max_width: float = 0
|
||||||
|
lines = text.split("\n" if isinstance(text, str) else b"\n")
|
||||||
|
line_spacing = (
|
||||||
|
self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
|
||||||
|
+ stroke_width
|
||||||
|
+ spacing
|
||||||
|
)
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
line_width = self.textlength(
|
||||||
|
line,
|
||||||
|
font,
|
||||||
|
direction=direction,
|
||||||
|
features=features,
|
||||||
|
language=language,
|
||||||
|
embedded_color=embedded_color,
|
||||||
|
)
|
||||||
|
widths.append(line_width)
|
||||||
|
max_width = max(max_width, line_width)
|
||||||
|
|
||||||
|
top = xy[1]
|
||||||
|
if anchor[1] == "m":
|
||||||
|
top -= (len(lines) - 1) * line_spacing / 2.0
|
||||||
|
elif anchor[1] == "d":
|
||||||
|
top -= (len(lines) - 1) * line_spacing
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
for idx, line in enumerate(lines):
|
||||||
|
left = xy[0]
|
||||||
|
width_difference = max_width - widths[idx]
|
||||||
|
|
||||||
|
# first align left by anchor
|
||||||
|
if anchor[0] == "m":
|
||||||
|
left -= width_difference / 2.0
|
||||||
|
elif anchor[0] == "r":
|
||||||
|
left -= width_difference
|
||||||
|
|
||||||
|
# then align by align parameter
|
||||||
|
if align in ("left", "justify"):
|
||||||
|
pass
|
||||||
|
elif align == "center":
|
||||||
|
left += width_difference / 2.0
|
||||||
|
elif align == "right":
|
||||||
|
left += width_difference
|
||||||
|
else:
|
||||||
|
msg = 'align must be "left", "center", "right" or "justify"'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
if align == "justify" and width_difference != 0:
|
||||||
|
words = line.split(" " if isinstance(text, str) else b" ")
|
||||||
|
word_widths = [
|
||||||
|
self.textlength(
|
||||||
|
word,
|
||||||
|
font,
|
||||||
|
direction=direction,
|
||||||
|
features=features,
|
||||||
|
language=language,
|
||||||
|
embedded_color=embedded_color,
|
||||||
|
)
|
||||||
|
for word in words
|
||||||
|
]
|
||||||
|
width_difference = max_width - sum(word_widths)
|
||||||
|
for i, word in enumerate(words):
|
||||||
|
parts.append(((left, top), word))
|
||||||
|
left += word_widths[i] + width_difference / (len(words) - 1)
|
||||||
|
else:
|
||||||
|
parts.append(((left, top), line))
|
||||||
|
|
||||||
|
top += line_spacing
|
||||||
|
|
||||||
|
return font, anchor, parts
|
||||||
|
|
||||||
def multiline_text(
|
def multiline_text(
|
||||||
self,
|
self,
|
||||||
xy: tuple[float, float],
|
xy: tuple[float, float],
|
||||||
|
@ -722,62 +820,24 @@ class ImageDraw:
|
||||||
*,
|
*,
|
||||||
font_size: float | None = None,
|
font_size: float | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if direction == "ttb":
|
font, anchor, lines = self._prepare_multiline_text(
|
||||||
msg = "ttb direction is unsupported for multiline text"
|
xy,
|
||||||
raise ValueError(msg)
|
text,
|
||||||
|
font,
|
||||||
if anchor is None:
|
anchor,
|
||||||
anchor = "la"
|
spacing,
|
||||||
elif len(anchor) != 2:
|
align,
|
||||||
msg = "anchor must be a 2 character string"
|
direction,
|
||||||
raise ValueError(msg)
|
features,
|
||||||
elif anchor[1] in "tb":
|
language,
|
||||||
msg = "anchor not supported for multiline text"
|
stroke_width,
|
||||||
raise ValueError(msg)
|
embedded_color,
|
||||||
|
font_size,
|
||||||
if font is None:
|
)
|
||||||
font = self._getfont(font_size)
|
|
||||||
|
|
||||||
widths = []
|
|
||||||
max_width: float = 0
|
|
||||||
lines = self._multiline_split(text)
|
|
||||||
line_spacing = self._multiline_spacing(font, spacing, stroke_width)
|
|
||||||
for line in lines:
|
|
||||||
line_width = self.textlength(
|
|
||||||
line, font, direction=direction, features=features, language=language
|
|
||||||
)
|
|
||||||
widths.append(line_width)
|
|
||||||
max_width = max(max_width, line_width)
|
|
||||||
|
|
||||||
top = xy[1]
|
|
||||||
if anchor[1] == "m":
|
|
||||||
top -= (len(lines) - 1) * line_spacing / 2.0
|
|
||||||
elif anchor[1] == "d":
|
|
||||||
top -= (len(lines) - 1) * line_spacing
|
|
||||||
|
|
||||||
for idx, line in enumerate(lines):
|
|
||||||
left = xy[0]
|
|
||||||
width_difference = max_width - widths[idx]
|
|
||||||
|
|
||||||
# first align left by anchor
|
|
||||||
if anchor[0] == "m":
|
|
||||||
left -= width_difference / 2.0
|
|
||||||
elif anchor[0] == "r":
|
|
||||||
left -= width_difference
|
|
||||||
|
|
||||||
# then align by align parameter
|
|
||||||
if align == "left":
|
|
||||||
pass
|
|
||||||
elif align == "center":
|
|
||||||
left += width_difference / 2.0
|
|
||||||
elif align == "right":
|
|
||||||
left += width_difference
|
|
||||||
else:
|
|
||||||
msg = 'align must be "left", "center" or "right"'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
|
for xy, line in lines:
|
||||||
self.text(
|
self.text(
|
||||||
(left, top),
|
xy,
|
||||||
line,
|
line,
|
||||||
fill,
|
fill,
|
||||||
font,
|
font,
|
||||||
|
@ -789,7 +849,6 @@ class ImageDraw:
|
||||||
stroke_fill=stroke_fill,
|
stroke_fill=stroke_fill,
|
||||||
embedded_color=embedded_color,
|
embedded_color=embedded_color,
|
||||||
)
|
)
|
||||||
top += line_spacing
|
|
||||||
|
|
||||||
def textlength(
|
def textlength(
|
||||||
self,
|
self,
|
||||||
|
@ -891,69 +950,26 @@ class ImageDraw:
|
||||||
*,
|
*,
|
||||||
font_size: float | None = None,
|
font_size: float | None = None,
|
||||||
) -> tuple[float, float, float, float]:
|
) -> tuple[float, float, float, float]:
|
||||||
if direction == "ttb":
|
font, anchor, lines = self._prepare_multiline_text(
|
||||||
msg = "ttb direction is unsupported for multiline text"
|
xy,
|
||||||
raise ValueError(msg)
|
text,
|
||||||
|
font,
|
||||||
if anchor is None:
|
anchor,
|
||||||
anchor = "la"
|
spacing,
|
||||||
elif len(anchor) != 2:
|
align,
|
||||||
msg = "anchor must be a 2 character string"
|
direction,
|
||||||
raise ValueError(msg)
|
features,
|
||||||
elif anchor[1] in "tb":
|
language,
|
||||||
msg = "anchor not supported for multiline text"
|
stroke_width,
|
||||||
raise ValueError(msg)
|
embedded_color,
|
||||||
|
font_size,
|
||||||
if font is None:
|
)
|
||||||
font = self._getfont(font_size)
|
|
||||||
|
|
||||||
widths = []
|
|
||||||
max_width: float = 0
|
|
||||||
lines = self._multiline_split(text)
|
|
||||||
line_spacing = self._multiline_spacing(font, spacing, stroke_width)
|
|
||||||
for line in lines:
|
|
||||||
line_width = self.textlength(
|
|
||||||
line,
|
|
||||||
font,
|
|
||||||
direction=direction,
|
|
||||||
features=features,
|
|
||||||
language=language,
|
|
||||||
embedded_color=embedded_color,
|
|
||||||
)
|
|
||||||
widths.append(line_width)
|
|
||||||
max_width = max(max_width, line_width)
|
|
||||||
|
|
||||||
top = xy[1]
|
|
||||||
if anchor[1] == "m":
|
|
||||||
top -= (len(lines) - 1) * line_spacing / 2.0
|
|
||||||
elif anchor[1] == "d":
|
|
||||||
top -= (len(lines) - 1) * line_spacing
|
|
||||||
|
|
||||||
bbox: tuple[float, float, float, float] | None = None
|
bbox: tuple[float, float, float, float] | None = None
|
||||||
|
|
||||||
for idx, line in enumerate(lines):
|
for xy, line in lines:
|
||||||
left = xy[0]
|
|
||||||
width_difference = max_width - widths[idx]
|
|
||||||
|
|
||||||
# first align left by anchor
|
|
||||||
if anchor[0] == "m":
|
|
||||||
left -= width_difference / 2.0
|
|
||||||
elif anchor[0] == "r":
|
|
||||||
left -= width_difference
|
|
||||||
|
|
||||||
# then align by align parameter
|
|
||||||
if align == "left":
|
|
||||||
pass
|
|
||||||
elif align == "center":
|
|
||||||
left += width_difference / 2.0
|
|
||||||
elif align == "right":
|
|
||||||
left += width_difference
|
|
||||||
else:
|
|
||||||
msg = 'align must be "left", "center" or "right"'
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
bbox_line = self.textbbox(
|
bbox_line = self.textbbox(
|
||||||
(left, top),
|
xy,
|
||||||
line,
|
line,
|
||||||
font,
|
font,
|
||||||
anchor,
|
anchor,
|
||||||
|
@ -973,8 +989,6 @@ class ImageDraw:
|
||||||
max(bbox[3], bbox_line[3]),
|
max(bbox[3], bbox_line[3]),
|
||||||
)
|
)
|
||||||
|
|
||||||
top += line_spacing
|
|
||||||
|
|
||||||
if bbox is None:
|
if bbox is None:
|
||||||
return xy[0], xy[1], xy[0], xy[1]
|
return xy[0], xy[1], xy[0], xy[1]
|
||||||
return bbox
|
return bbox
|
||||||
|
|
|
@ -598,8 +598,6 @@ class Color3DLUT(MultibandFilter):
|
||||||
self.mode or image.mode,
|
self.mode or image.mode,
|
||||||
Image.Resampling.BILINEAR,
|
Image.Resampling.BILINEAR,
|
||||||
self.channels,
|
self.channels,
|
||||||
self.size[0],
|
self.size,
|
||||||
self.size[1],
|
|
||||||
self.size[2],
|
|
||||||
self.table,
|
self.table,
|
||||||
)
|
)
|
||||||
|
|
|
@ -647,8 +647,7 @@ class FreeTypeFont:
|
||||||
kwargs.get("stroke_filled", False),
|
kwargs.get("stroke_filled", False),
|
||||||
anchor,
|
anchor,
|
||||||
ink,
|
ink,
|
||||||
start[0],
|
start,
|
||||||
start[1],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def font_variant(
|
def font_variant(
|
||||||
|
|
|
@ -48,9 +48,9 @@ class AffineTransform(Transform):
|
||||||
Define an affine image transform.
|
Define an affine image transform.
|
||||||
|
|
||||||
This function takes a 6-tuple (a, b, c, d, e, f) which contain the first
|
This function takes a 6-tuple (a, b, c, d, e, f) which contain the first
|
||||||
two rows from an affine transform matrix. For each pixel (x, y) in the
|
two rows from the inverse of an affine transform matrix. For each pixel
|
||||||
output image, the new value is taken from a position (a x + b y + c,
|
(x, y) in the output image, the new value is taken from a position (a x +
|
||||||
d x + e y + f) in the input image, rounded to nearest pixel.
|
b y + c, d x + e y + f) in the input image, rounded to nearest pixel.
|
||||||
|
|
||||||
This function can be used to scale, translate, rotate, and shear the
|
This function can be used to scale, translate, rotate, and shear the
|
||||||
original image.
|
original image.
|
||||||
|
@ -58,7 +58,7 @@ class AffineTransform(Transform):
|
||||||
See :py:meth:`.Image.transform`
|
See :py:meth:`.Image.transform`
|
||||||
|
|
||||||
:param matrix: A 6-tuple (a, b, c, d, e, f) containing the first two rows
|
:param matrix: A 6-tuple (a, b, c, d, e, f) containing the first two rows
|
||||||
from an affine transform matrix.
|
from the inverse of an affine transform matrix.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
method = Image.Transform.AFFINE
|
method = Image.Transform.AFFINE
|
||||||
|
|
|
@ -55,7 +55,7 @@ class ImtImageFile(ImageFile.ImageFile):
|
||||||
if not s:
|
if not s:
|
||||||
break
|
break
|
||||||
|
|
||||||
if s == b"\x0C":
|
if s == b"\x0c":
|
||||||
# image data begins
|
# image data begins
|
||||||
self.tile = [
|
self.tile = [
|
||||||
ImageFile._Tile(
|
ImageFile._Tile(
|
||||||
|
|
|
@ -330,7 +330,7 @@ MARKER = {
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
# Magic number was taken from https://en.wikipedia.org/wiki/JPEG
|
# Magic number was taken from https://en.wikipedia.org/wiki/JPEG
|
||||||
return prefix[:3] == b"\xFF\xD8\xFF"
|
return prefix[:3] == b"\xff\xd8\xff"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -348,7 +348,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
if not _accept(s):
|
if not _accept(s):
|
||||||
msg = "not a JPEG file"
|
msg = "not a JPEG file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
s = b"\xFF"
|
s = b"\xff"
|
||||||
|
|
||||||
# Create attributes
|
# Create attributes
|
||||||
self.bits = self.layers = 0
|
self.bits = self.layers = 0
|
||||||
|
@ -424,7 +424,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
# Premature EOF.
|
# Premature EOF.
|
||||||
# Pretend file is finished adding EOI marker
|
# Pretend file is finished adding EOI marker
|
||||||
self._ended = True
|
self._ended = True
|
||||||
return b"\xFF\xD9"
|
return b"\xff\xd9"
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
@ -719,7 +719,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
def validate_qtables(
|
def validate_qtables(
|
||||||
qtables: (
|
qtables: (
|
||||||
str | tuple[list[int], ...] | list[list[int]] | dict[int, list[int]] | None
|
str | tuple[list[int], ...] | list[list[int]] | dict[int, list[int]] | None
|
||||||
)
|
),
|
||||||
) -> list[list[int]] | None:
|
) -> list[list[int]] | None:
|
||||||
if qtables is None:
|
if qtables is None:
|
||||||
return qtables
|
return qtables
|
||||||
|
@ -776,7 +776,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
msg = "XMP data is too long"
|
msg = "XMP data is too long"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
size = o16(2 + overhead_len + len(xmp))
|
size = o16(2 + overhead_len + len(xmp))
|
||||||
extra += b"\xFF\xE1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp
|
extra += b"\xff\xe1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp
|
||||||
|
|
||||||
icc_profile = info.get("icc_profile")
|
icc_profile = info.get("icc_profile")
|
||||||
if icc_profile:
|
if icc_profile:
|
||||||
|
@ -790,7 +790,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
for marker in markers:
|
for marker in markers:
|
||||||
size = o16(2 + overhead_len + len(marker))
|
size = o16(2 + overhead_len + len(marker))
|
||||||
extra += (
|
extra += (
|
||||||
b"\xFF\xE2"
|
b"\xff\xe2"
|
||||||
+ size
|
+ size
|
||||||
+ b"ICC_PROFILE\0"
|
+ b"ICC_PROFILE\0"
|
||||||
+ o8(i)
|
+ o8(i)
|
||||||
|
@ -823,8 +823,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
optimize,
|
optimize,
|
||||||
info.get("keep_rgb", False),
|
info.get("keep_rgb", False),
|
||||||
info.get("streamtype", 0),
|
info.get("streamtype", 0),
|
||||||
dpi[0],
|
dpi,
|
||||||
dpi[1],
|
|
||||||
subsampling,
|
subsampling,
|
||||||
info.get("restart_marker_blocks", 0),
|
info.get("restart_marker_blocks", 0),
|
||||||
info.get("restart_marker_rows", 0),
|
info.get("restart_marker_rows", 0),
|
||||||
|
|
|
@ -51,7 +51,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
if not offsets:
|
if not offsets:
|
||||||
# APP2 marker
|
# APP2 marker
|
||||||
im_frame.encoderinfo["extra"] = (
|
im_frame.encoderinfo["extra"] = (
|
||||||
b"\xFF\xE2" + struct.pack(">H", 6 + 82) + b"MPF\0" + b" " * 82
|
b"\xff\xe2" + struct.pack(">H", 6 + 82) + b"MPF\0" + b" " * 82
|
||||||
)
|
)
|
||||||
exif = im_frame.encoderinfo.get("exif")
|
exif = im_frame.encoderinfo.get("exif")
|
||||||
if isinstance(exif, Image.Exif):
|
if isinstance(exif, Image.Exif):
|
||||||
|
@ -84,7 +84,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
ifd[0xB002] = mpentries
|
ifd[0xB002] = mpentries
|
||||||
|
|
||||||
fp.seek(mpf_offset)
|
fp.seek(mpf_offset)
|
||||||
fp.write(b"II\x2A\x00" + o32le(8) + ifd.tobytes(8))
|
fp.write(b"II\x2a\x00" + o32le(8) + ifd.tobytes(8))
|
||||||
fp.seek(0, os.SEEK_END)
|
fp.seek(0, os.SEEK_END)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -188,7 +188,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
+ o16(dpi[0])
|
+ o16(dpi[0])
|
||||||
+ o16(dpi[1])
|
+ o16(dpi[1])
|
||||||
+ b"\0" * 24
|
+ b"\0" * 24
|
||||||
+ b"\xFF" * 24
|
+ b"\xff" * 24
|
||||||
+ b"\0"
|
+ b"\0"
|
||||||
+ o8(planes)
|
+ o8(planes)
|
||||||
+ o16(stride)
|
+ o16(stride)
|
||||||
|
|
|
@ -19,14 +19,14 @@ def encode_text(s: str) -> bytes:
|
||||||
|
|
||||||
PDFDocEncoding = {
|
PDFDocEncoding = {
|
||||||
0x16: "\u0017",
|
0x16: "\u0017",
|
||||||
0x18: "\u02D8",
|
0x18: "\u02d8",
|
||||||
0x19: "\u02C7",
|
0x19: "\u02c7",
|
||||||
0x1A: "\u02C6",
|
0x1A: "\u02c6",
|
||||||
0x1B: "\u02D9",
|
0x1B: "\u02d9",
|
||||||
0x1C: "\u02DD",
|
0x1C: "\u02dd",
|
||||||
0x1D: "\u02DB",
|
0x1D: "\u02db",
|
||||||
0x1E: "\u02DA",
|
0x1E: "\u02da",
|
||||||
0x1F: "\u02DC",
|
0x1F: "\u02dc",
|
||||||
0x80: "\u2022",
|
0x80: "\u2022",
|
||||||
0x81: "\u2020",
|
0x81: "\u2020",
|
||||||
0x82: "\u2021",
|
0x82: "\u2021",
|
||||||
|
@ -36,29 +36,29 @@ PDFDocEncoding = {
|
||||||
0x86: "\u0192",
|
0x86: "\u0192",
|
||||||
0x87: "\u2044",
|
0x87: "\u2044",
|
||||||
0x88: "\u2039",
|
0x88: "\u2039",
|
||||||
0x89: "\u203A",
|
0x89: "\u203a",
|
||||||
0x8A: "\u2212",
|
0x8A: "\u2212",
|
||||||
0x8B: "\u2030",
|
0x8B: "\u2030",
|
||||||
0x8C: "\u201E",
|
0x8C: "\u201e",
|
||||||
0x8D: "\u201C",
|
0x8D: "\u201c",
|
||||||
0x8E: "\u201D",
|
0x8E: "\u201d",
|
||||||
0x8F: "\u2018",
|
0x8F: "\u2018",
|
||||||
0x90: "\u2019",
|
0x90: "\u2019",
|
||||||
0x91: "\u201A",
|
0x91: "\u201a",
|
||||||
0x92: "\u2122",
|
0x92: "\u2122",
|
||||||
0x93: "\uFB01",
|
0x93: "\ufb01",
|
||||||
0x94: "\uFB02",
|
0x94: "\ufb02",
|
||||||
0x95: "\u0141",
|
0x95: "\u0141",
|
||||||
0x96: "\u0152",
|
0x96: "\u0152",
|
||||||
0x97: "\u0160",
|
0x97: "\u0160",
|
||||||
0x98: "\u0178",
|
0x98: "\u0178",
|
||||||
0x99: "\u017D",
|
0x99: "\u017d",
|
||||||
0x9A: "\u0131",
|
0x9A: "\u0131",
|
||||||
0x9B: "\u0142",
|
0x9B: "\u0142",
|
||||||
0x9C: "\u0153",
|
0x9C: "\u0153",
|
||||||
0x9D: "\u0161",
|
0x9D: "\u0161",
|
||||||
0x9E: "\u017E",
|
0x9E: "\u017e",
|
||||||
0xA0: "\u20AC",
|
0xA0: "\u20ac",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1436,7 +1436,7 @@ def _save(
|
||||||
chunk(fp, b"tRNS", transparency[:alpha_bytes])
|
chunk(fp, b"tRNS", transparency[:alpha_bytes])
|
||||||
else:
|
else:
|
||||||
transparency = max(0, min(255, transparency))
|
transparency = max(0, min(255, transparency))
|
||||||
alpha = b"\xFF" * transparency + b"\0"
|
alpha = b"\xff" * transparency + b"\0"
|
||||||
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
||||||
elif im.mode in ("1", "L", "I", "I;16"):
|
elif im.mode in ("1", "L", "I", "I;16"):
|
||||||
transparency = max(0, min(65535, transparency))
|
transparency = max(0, min(65535, transparency))
|
||||||
|
|
|
@ -230,7 +230,7 @@ class PpmPlainDecoder(ImageFile.PyDecoder):
|
||||||
msg = b"Invalid token for this mode: %s" % bytes([token])
|
msg = b"Invalid token for this mode: %s" % bytes([token])
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
data = (data + tokens)[:total_bytes]
|
data = (data + tokens)[:total_bytes]
|
||||||
invert = bytes.maketrans(b"01", b"\xFF\x00")
|
invert = bytes.maketrans(b"01", b"\xff\x00")
|
||||||
return data.translate(invert)
|
return data.translate(invert)
|
||||||
|
|
||||||
def _decode_blocks(self, maxval: int) -> bytearray:
|
def _decode_blocks(self, maxval: int) -> bytearray:
|
||||||
|
|
|
@ -275,12 +275,12 @@ OPEN_INFO = {
|
||||||
MAX_SAMPLESPERPIXEL = max(len(key_tp[4]) for key_tp in OPEN_INFO)
|
MAX_SAMPLESPERPIXEL = max(len(key_tp[4]) for key_tp in OPEN_INFO)
|
||||||
|
|
||||||
PREFIXES = [
|
PREFIXES = [
|
||||||
b"MM\x00\x2A", # Valid TIFF header with big-endian byte order
|
b"MM\x00\x2a", # Valid TIFF header with big-endian byte order
|
||||||
b"II\x2A\x00", # Valid TIFF header with little-endian byte order
|
b"II\x2a\x00", # Valid TIFF header with little-endian byte order
|
||||||
b"MM\x2A\x00", # Invalid TIFF header, assume big-endian
|
b"MM\x2a\x00", # Invalid TIFF header, assume big-endian
|
||||||
b"II\x00\x2A", # Invalid TIFF header, assume little-endian
|
b"II\x00\x2a", # Invalid TIFF header, assume little-endian
|
||||||
b"MM\x00\x2B", # BigTIFF with big-endian byte order
|
b"MM\x00\x2b", # BigTIFF with big-endian byte order
|
||||||
b"II\x2B\x00", # BigTIFF with little-endian byte order
|
b"II\x2b\x00", # BigTIFF with little-endian byte order
|
||||||
]
|
]
|
||||||
|
|
||||||
if not getattr(Image.core, "libtiff_support_custom_tags", True):
|
if not getattr(Image.core, "libtiff_support_custom_tags", True):
|
||||||
|
@ -582,7 +582,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ifh: bytes = b"II\x2A\x00\x00\x00\x00\x00",
|
ifh: bytes = b"II\x2a\x00\x00\x00\x00\x00",
|
||||||
prefix: bytes | None = None,
|
prefix: bytes | None = None,
|
||||||
group: int | None = None,
|
group: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -2049,7 +2049,7 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
self.offsetOfNewPage = 0
|
self.offsetOfNewPage = 0
|
||||||
|
|
||||||
self.IIMM = iimm = self.f.read(4)
|
self.IIMM = iimm = self.f.read(4)
|
||||||
self._bigtiff = b"\x2B" in iimm
|
self._bigtiff = b"\x2b" in iimm
|
||||||
if not iimm:
|
if not iimm:
|
||||||
# empty file - first page
|
# empty file - first page
|
||||||
self.isFirst = True
|
self.isFirst = True
|
||||||
|
|
|
@ -224,8 +224,7 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
||||||
# Setup the WebP animation encoder
|
# Setup the WebP animation encoder
|
||||||
enc = _webp.WebPAnimEncoder(
|
enc = _webp.WebPAnimEncoder(
|
||||||
im.size[0],
|
im.size,
|
||||||
im.size[1],
|
|
||||||
background,
|
background,
|
||||||
loop,
|
loop,
|
||||||
minimize_size,
|
minimize_size,
|
||||||
|
|
|
@ -31,8 +31,7 @@ class Font:
|
||||||
stroke_filled: bool,
|
stroke_filled: bool,
|
||||||
anchor: str | None,
|
anchor: str | None,
|
||||||
foreground_ink_long: int,
|
foreground_ink_long: int,
|
||||||
x_start: float,
|
start: tuple[float, float],
|
||||||
y_start: float,
|
|
||||||
/,
|
/,
|
||||||
) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
|
) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
|
||||||
def getsize(
|
def getsize(
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
""" Find compiled module linking to Tcl / Tk libraries
|
"""Find compiled module linking to Tcl / Tk libraries"""
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
|
@ -866,7 +866,7 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"siiiiiO:color_lut_3d",
|
"sii(iii)O:color_lut_3d",
|
||||||
&mode,
|
&mode,
|
||||||
&filter,
|
&filter,
|
||||||
&table_channels,
|
&table_channels,
|
||||||
|
@ -1013,10 +1013,6 @@ _convert_transparent(ImagingObject *self, PyObject *args) {
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
_copy(ImagingObject *self, PyObject *args) {
|
_copy(ImagingObject *self, PyObject *args) {
|
||||||
if (!PyArg_ParseTuple(args, "")) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PyImagingNew(ImagingCopy(self->image));
|
return PyImagingNew(ImagingCopy(self->image));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4439,10 +4435,9 @@ PyInit__imaging(void) {
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"_imaging", /* m_name */
|
.m_name = "_imaging",
|
||||||
NULL, /* m_doc */
|
.m_size = -1,
|
||||||
-1, /* m_size */
|
.m_methods = functions,
|
||||||
functions, /* m_methods */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
m = PyModule_Create(&module_def);
|
||||||
|
|
|
@ -1520,10 +1520,9 @@ PyInit__imagingcms(void) {
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"_imagingcms", /* m_name */
|
.m_name = "_imagingcms",
|
||||||
NULL, /* m_doc */
|
.m_size = -1,
|
||||||
-1, /* m_size */
|
.m_methods = pyCMSdll_methods,
|
||||||
pyCMSdll_methods, /* m_methods */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
m = PyModule_Create(&module_def);
|
||||||
|
|
|
@ -854,7 +854,7 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"OO|zzOzfpzLffO:render",
|
"OO|zzOzfpzL(ff):render",
|
||||||
&string,
|
&string,
|
||||||
&fill,
|
&fill,
|
||||||
&mode,
|
&mode,
|
||||||
|
@ -1630,10 +1630,9 @@ PyInit__imagingft(void) {
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"_imagingft", /* m_name */
|
.m_name = "_imagingft",
|
||||||
NULL, /* m_doc */
|
.m_size = -1,
|
||||||
-1, /* m_size */
|
.m_methods = _functions,
|
||||||
_functions, /* m_methods */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
m = PyModule_Create(&module_def);
|
||||||
|
|
|
@ -308,10 +308,9 @@ PyInit__imagingmath(void) {
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"_imagingmath", /* m_name */
|
.m_name = "_imagingmath",
|
||||||
NULL, /* m_doc */
|
.m_size = -1,
|
||||||
-1, /* m_size */
|
.m_methods = _functions,
|
||||||
_functions, /* m_methods */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
m = PyModule_Create(&module_def);
|
||||||
|
|
|
@ -252,10 +252,10 @@ PyInit__imagingmorph(void) {
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"_imagingmorph", /* m_name */
|
.m_name = "_imagingmorph",
|
||||||
"A module for doing image morphology", /* m_doc */
|
.m_doc = "A module for doing image morphology",
|
||||||
-1, /* m_size */
|
.m_size = -1,
|
||||||
functions, /* m_methods */
|
.m_methods = functions,
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
m = PyModule_Create(&module_def);
|
||||||
|
|
|
@ -50,10 +50,9 @@ PyMODINIT_FUNC
|
||||||
PyInit__imagingtk(void) {
|
PyInit__imagingtk(void) {
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"_imagingtk", /* m_name */
|
.m_name = "_imagingtk",
|
||||||
NULL, /* m_doc */
|
.m_size = -1,
|
||||||
-1, /* m_size */
|
.m_methods = functions,
|
||||||
functions, /* m_methods */
|
|
||||||
};
|
};
|
||||||
PyObject *m;
|
PyObject *m;
|
||||||
m = PyModule_Create(&module_def);
|
m = PyModule_Create(&module_def);
|
||||||
|
|
|
@ -164,7 +164,7 @@ _anim_encoder_new(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"iiIiiiiii",
|
"(ii)Iiiiiii",
|
||||||
&width,
|
&width,
|
||||||
&height,
|
&height,
|
||||||
&bgcolor,
|
&bgcolor,
|
||||||
|
@ -835,10 +835,9 @@ PyInit__webp(void) {
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"_webp", /* m_name */
|
.m_name = "_webp",
|
||||||
NULL, /* m_doc */
|
.m_size = -1,
|
||||||
-1, /* m_size */
|
.m_methods = webpMethods,
|
||||||
webpMethods, /* m_methods */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
m = PyModule_Create(&module_def);
|
||||||
|
|
|
@ -1097,7 +1097,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args,
|
||||||
"ss|nnnnpnnnnnnOz#y#y#",
|
"ss|nnnnpn(nn)nnnOz#y#y#",
|
||||||
&mode,
|
&mode,
|
||||||
&rawmode,
|
&rawmode,
|
||||||
&quality,
|
&quality,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user