Merge branch 'main' into fix-qtables-and-quality-scaling

This commit is contained in:
Kylian Ronfleux--Corail 2025-06-16 13:13:58 +02:00 committed by GitHub
commit 31f70f0684
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 359 additions and 173 deletions

View File

@ -1 +1 @@
cibuildwheel==2.23.3 cibuildwheel==3.0.0

View File

@ -1,4 +1,4 @@
mypy==1.15.0 mypy==1.16.0
IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6 IceSpringPySideStubs-PySide6
ipython ipython

View File

@ -35,7 +35,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"] python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"]
architecture: ["x64"] architecture: ["x64"]
include: include:
# Test the oldest Python on 32-bit # Test the oldest Python on 32-bit

View File

@ -43,6 +43,7 @@ jobs:
python-version: [ python-version: [
"pypy3.11", "pypy3.11",
"pypy3.10", "pypy3.10",
"3.14t",
"3.14", "3.14",
"3.13t", "3.13t",
"3.13", "3.13",
@ -55,6 +56,7 @@ jobs:
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" } - { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
- { python-version: "3.10", PYTHONOPTIMIZE: 2 } - { python-version: "3.10", PYTHONOPTIMIZE: 2 }
# Free-threaded # Free-threaded
- { python-version: "3.14t", disable-gil: true }
- { python-version: "3.13t", disable-gil: true } - { python-version: "3.13t", disable-gil: true }
# M1 only available for 3.10+ # M1 only available for 3.10+
- { os: "macos-13", python-version: "3.9" } - { os: "macos-13", python-version: "3.9" }

View File

@ -39,8 +39,8 @@ ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds # Package versions for fresh source builds
FREETYPE_VERSION=2.13.3 FREETYPE_VERSION=2.13.3
HARFBUZZ_VERSION=11.2.1 HARFBUZZ_VERSION=11.2.1
LIBPNG_VERSION=1.6.48 LIBPNG_VERSION=1.6.49
JPEGTURBO_VERSION=3.1.0 JPEGTURBO_VERSION=3.1.1
OPENJPEG_VERSION=2.5.3 OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.8.1 XZ_VERSION=5.8.1
TIFF_VERSION=4.7.0 TIFF_VERSION=4.7.0

View File

@ -9,17 +9,21 @@ if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
} }
$env:path += ";$pillow\winbuild\build\bin\" $env:path += ";$pillow\winbuild\build\bin\"
& "$venv\Scripts\activate.ps1" if (Test-Path $venv\Scripts\pypy.exe) {
$python = "pypy.exe"
} else {
$python = "python.exe"
}
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f & reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
if ("$venv" -like "*\cibw-run-*-win_amd64\*") { if ("$venv" -like "*\cibw-run-*-win_amd64\*") {
& python -m pip install numpy & $venv\Scripts\$python -m pip install numpy
} }
cd $pillow cd $pillow
& python -VV & $venv\Scripts\$python -VV
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }
& python selftest.py & $venv\Scripts\$python selftest.py
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }
& python -m pytest -vx Tests\check_wheel.py & $venv\Scripts\$python -m pytest -vx Tests\check_wheel.py
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }
& python -m pytest -vx Tests & $venv\Scripts\$python -m pytest -vx Tests
if (!$?) { exit $LASTEXITCODE } if (!$?) { exit $LASTEXITCODE }

View File

@ -110,7 +110,6 @@ jobs:
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_SKIP: pp39-*
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
@ -188,7 +187,6 @@ jobs:
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
CIBW_CACHE_PATH: "C:\\cibw" CIBW_CACHE_PATH: "C:\\cibw"
CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
CIBW_SKIP: pp39-*
CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_COMMAND: 'docker run --rm CIBW_TEST_COMMAND: 'docker run --rm
-v {project}:C:\pillow -v {project}:C:\pillow

View File

@ -11,13 +11,14 @@ from .helper import is_pypy
def test_wheel_modules() -> None: def test_wheel_modules() -> None:
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"} expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
# tkinter is not available in cibuildwheel installed CPython on Windows if sys.platform == "win32":
try: # tkinter is not available in cibuildwheel installed CPython on Windows
import tkinter try:
import tkinter
assert tkinter assert tkinter
except ImportError: except ImportError:
expected_modules.remove("tkinter") expected_modules.remove("tkinter")
assert set(features.get_supported_modules()) == expected_modules assert set(features.get_supported_modules()) == expected_modules

BIN
Tests/images/op_index.qoi Normal file

Binary file not shown.

BIN
Tests/images/p_4_planes.pcx Normal file

Binary file not shown.

View File

@ -7,9 +7,8 @@ import pytest
from PIL import BlpImagePlugin, Image from PIL import BlpImagePlugin, Image
from .helper import ( from .helper import (
assert_image_equal,
assert_image_equal_tofile, assert_image_equal_tofile,
assert_image_similar, assert_image_similar_tofile,
hopper, hopper,
) )
@ -52,18 +51,16 @@ def test_save(tmp_path: Path) -> None:
im = hopper("P") im = hopper("P")
im.save(f, blp_version=version) im.save(f, blp_version=version)
with Image.open(f) as reloaded: assert_image_equal_tofile(im.convert("RGB"), f)
assert_image_equal(im.convert("RGB"), reloaded)
with Image.open("Tests/images/transparent.png") as im: with Image.open("Tests/images/transparent.png") as im:
f = tmp_path / "temp.blp" f = tmp_path / "temp.blp"
im.convert("P").save(f, blp_version=version) im.convert("P").save(f, blp_version=version)
with Image.open(f) as reloaded: assert_image_similar_tofile(im, f, 8)
assert_image_similar(im, reloaded, 8)
im = hopper() im = hopper()
with pytest.raises(ValueError): with pytest.raises(ValueError, match="Unsupported BLP image mode"):
im.save(f) im.save(f)

View File

@ -145,14 +145,16 @@ class TestFileJpeg:
assert k > 0.9 assert k > 0.9
# roundtrip, and check again # roundtrip, and check again
im = self.roundtrip(im) im = self.roundtrip(im)
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) cmyk = im.getpixel((0, 0))
assert isinstance(cmyk, tuple)
c, m, y, k = (x / 255.0 for x in cmyk)
assert c == 0.0 assert c == 0.0
assert m > 0.8 assert m > 0.8
assert y > 0.8 assert y > 0.8
assert k == 0.0 assert k == 0.0
c, m, y, k = ( cmyk = im.getpixel((im.size[0] - 1, im.size[1] - 1))
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) assert isinstance(cmyk, tuple)
) k = cmyk[3] / 255.0
assert k > 0.9 assert k > 0.9
def test_rgb(self) -> None: def test_rgb(self) -> None:

View File

@ -37,6 +37,11 @@ def test_sanity(tmp_path: Path) -> None:
im.save(f) im.save(f)
def test_p_4_planes() -> None:
with Image.open("Tests/images/p_4_planes.pcx") as im:
assert im.getpixel((0, 0)) == 3
def test_bad_image_size() -> None: def test_bad_image_size() -> None:
with open("Tests/images/pil184.pcx", "rb") as fp: with open("Tests/images/pil184.pcx", "rb") as fp:
data = fp.read() data = fp.read()

View File

@ -288,14 +288,16 @@ def test_non_integer_token(tmp_path: Path) -> None:
pass pass
def test_header_token_too_long(tmp_path: Path) -> None: @pytest.mark.parametrize("data", (b"P3\x0cAAAAAAAAAA\xee", b"P6\n 01234567890"))
def test_header_token_too_long(tmp_path: Path, data: bytes) -> None:
path = tmp_path / "temp.ppm" path = tmp_path / "temp.ppm"
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(b"P6\n 01234567890") f.write(data)
with pytest.raises(ValueError, match="Token too long in file header: 01234567890"): with pytest.raises(ValueError) as e:
with Image.open(path): with Image.open(path):
pass pass
assert "Token too long in file header: " in repr(e)
def test_truncated_file(tmp_path: Path) -> None: def test_truncated_file(tmp_path: Path) -> None:

View File

@ -28,3 +28,9 @@ def test_invalid_file() -> None:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
QoiImagePlugin.QoiImageFile(invalid_file) QoiImagePlugin.QoiImageFile(invalid_file)
def test_op_index() -> None:
# QOI_OP_INDEX as the first chunk
with Image.open("Tests/images/op_index.qoi") as im:
assert im.getpixel((0, 0)) == (0, 0, 0, 0)

View File

@ -14,6 +14,7 @@ from PIL import (
ImageFile, ImageFile,
JpegImagePlugin, JpegImagePlugin,
TiffImagePlugin, TiffImagePlugin,
TiffTags,
UnidentifiedImageError, UnidentifiedImageError,
) )
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
@ -900,6 +901,29 @@ class TestFileTiff:
assert description[0]["format"] == "image/tiff" assert description[0]["format"] == "image/tiff"
assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"] assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
def test_getxmp_undefined(self, tmp_path: Path) -> None:
tmpfile = tmp_path / "temp.tif"
im = Image.new("L", (1, 1))
ifd = TiffImagePlugin.ImageFileDirectory_v2()
ifd.tagtype[700] = TiffTags.UNDEFINED
with Image.open("Tests/images/lab.tif") as im_xmp:
ifd[700] = im_xmp.info["xmp"]
im.save(tmpfile, tiffinfo=ifd)
with Image.open(tmpfile) as im_reloaded:
if ElementTree is None:
with pytest.warns(
UserWarning,
match="XMP data cannot be read without defusedxml dependency",
):
assert im_reloaded.getxmp() == {}
else:
assert "xmp" in im_reloaded.info
xmp = im_reloaded.getxmp()
description = xmp["xmpmeta"]["RDF"]["Description"]
assert description[0]["format"] == "image/tiff"
def test_get_photoshop_blocks(self) -> None: def test_get_photoshop_blocks(self) -> None:
with Image.open("Tests/images/lab.tif") as im: with Image.open("Tests/images/lab.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile) assert isinstance(im, TiffImagePlugin.TiffImageFile)

View File

@ -671,6 +671,7 @@ class TestImage:
im_remapped = im.remap_palette(list(range(256))) im_remapped = im.remap_palette(list(range(256)))
assert_image_equal(im, im_remapped) assert_image_equal(im, im_remapped)
assert im.palette is not None assert im.palette is not None
assert im_remapped.palette is not None
assert im.palette.palette == im_remapped.palette.palette assert im.palette.palette == im_remapped.palette.palette
# Test illegal image mode # Test illegal image mode
@ -973,6 +974,11 @@ class TestImage:
assert tag not in exif.get_ifd(0x8769) assert tag not in exif.get_ifd(0x8769)
assert exif.get_ifd(0xA005) assert exif.get_ifd(0xA005)
def test_exif_from_xmp_bytes(self) -> None:
im = Image.new("RGB", (1, 1))
im.info["xmp"] = b'\xff tiff:Orientation="2"'
assert im.getexif()[274] == 2
def test_empty_xmp(self) -> None: def test_empty_xmp(self) -> None:
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
if ElementTree is None: if ElementTree is None:
@ -989,7 +995,7 @@ class TestImage:
im = Image.new("RGB", (1, 1)) im = Image.new("RGB", (1, 1))
im.info["xmp"] = ( im.info["xmp"] = (
b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n' b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00' b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00 '
) )
if ElementTree is None: if ElementTree is None:
with pytest.warns( with pytest.warns(

View File

@ -783,9 +783,10 @@ def test_rectangle_I16(bbox: Coords) -> None:
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
# Act # Act
draw.rectangle(bbox, outline=0xFFFF) draw.rectangle(bbox, outline=0xCDEF)
# Assert # Assert
assert im.getpixel((X0, Y0)) == 0xCDEF
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff") assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff")

View File

@ -52,3 +52,17 @@ def test_tiff_crashes(test_file: str) -> None:
pytest.skip("test image not found") pytest.skip("test image not found")
except OSError: except OSError:
pass pass
def test_tiff_mmap() -> None:
try:
with Image.open("Tests/images/crash_mmap.tif") as im:
im.seek(1)
im.load()
im.seek(0)
im.load()
except FileNotFoundError:
if on_ci():
raise
pytest.skip("test image not found")

View File

@ -40,12 +40,12 @@ These platforms are built and tested for every change.
| macOS 13 Ventura | 3.9 | x86-64 | | macOS 13 Ventura | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 | | macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
| | PyPy3 | | | | 3.14, PyPy3 | |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 | | Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Ubuntu Linux 24.04 LTS (Noble) | 3.9, 3.10, 3.11, | x86-64 | | Ubuntu Linux 24.04 LTS (Noble) | 3.9, 3.10, 3.11, | x86-64 |
| | 3.12, 3.13, PyPy3 | | | | 3.12, 3.13, 3.14, PyPy3 | |
| +----------------------------+---------------------+ | +----------------------------+---------------------+
| | 3.12 | arm64v8, ppc64le, | | | 3.12 | arm64v8, ppc64le, |
| | | s390x | | | | s390x |
@ -53,7 +53,7 @@ These platforms are built and tested for every change.
| Windows Server 2022 | 3.9 | x86 | | Windows Server 2022 | 3.9 | x86 |
| +----------------------------+---------------------+ | +----------------------------+---------------------+
| | 3.10, 3.11, 3.12, 3.13, | x86-64 | | | 3.10, 3.11, 3.12, 3.13, | x86-64 |
| | PyPy3 | | | | 3.14, PyPy3 | |
| +----------------------------+---------------------+ | +----------------------------+---------------------+
| | 3.12 (MinGW) | x86-64 | | | 3.12 (MinGW) | x86-64 |
| +----------------------------+---------------------+ | +----------------------------+---------------------+

View File

@ -18,6 +18,9 @@ OpenType fonts (as well as other font formats supported by the FreeType
library). For earlier versions, TrueType support is only available as part of library). For earlier versions, TrueType support is only available as part of
the imToolkit package. the imToolkit package.
When measuring text sizes, this module will not break at newline characters. For
multiline text, see the :py:mod:`~PIL.ImageDraw` module.
.. warning:: .. warning::
To protect against potential DOS attacks when using arbitrary strings as To protect against potential DOS attacks when using arbitrary strings as
text input, Pillow will raise a :py:exc:`ValueError` if the number of characters text input, Pillow will raise a :py:exc:`ValueError` if the number of characters

View File

@ -0,0 +1,69 @@
11.3.0
------
Security
========
TODO
^^^^
TODO
:cve:`YYYY-XXXXX`: TODO
^^^^^^^^^^^^^^^^^^^^^^^
TODO
Backwards incompatible changes
==============================
TODO
^^^^
Deprecations
============
TODO
^^^^
TODO
API changes
===========
TODO
^^^^
TODO
API additions
=============
TODO
^^^^
TODO
Other changes
=============
Support using more screenshot utilities with ImageGrab on Linux
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:py:meth:`~PIL.ImageGrab.grab` is now able to use GNOME Screenshot, grim or Spectacle
on Linux in order to take a snapshot of the screen.
Do not build against libavif < 1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Pillow only supports libavif 1.0.0 or later. In order to prevent errors when building
from source, if a user happens to have an earlier libavif on their system, Pillow will
now ignore it.
Python 3.14 beta
^^^^^^^^^^^^^^^^
To help other projects prepare for Python 3.14, wheels are now built for the
3.14 beta as a preview. This is not official support for Python 3.14, but rather
an opportunity for you to test how Pillow works with the beta and report any
problems.

View File

@ -14,6 +14,7 @@ expected to be backported to earlier versions.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
11.3.0
11.2.1 11.2.1
11.1.0 11.1.0
11.0.0 11.0.0

View File

@ -163,7 +163,7 @@ def _find_library_dirs_ldconfig() -> list[str]:
args: list[str] args: list[str]
env: dict[str, str] env: dict[str, str]
expr: str expr: str
if sys.platform.startswith("linux") or sys.platform.startswith("gnu"): if sys.platform.startswith(("linux", "gnu")):
if struct.calcsize("l") == 4: if struct.calcsize("l") == 4:
machine = os.uname()[4] + "-32" machine = os.uname()[4] + "-32"
else: else:
@ -623,11 +623,7 @@ class pil_build_ext(build_ext):
for extension in self.extensions: for extension in self.extensions:
extension.extra_compile_args = ["-Wno-nullability-completeness"] extension.extra_compile_args = ["-Wno-nullability-completeness"]
elif ( elif sys.platform.startswith(("linux", "gnu", "freebsd")):
sys.platform.startswith("linux")
or sys.platform.startswith("gnu")
or sys.platform.startswith("freebsd")
):
for dirname in _find_library_dirs_ldconfig(): for dirname in _find_library_dirs_ldconfig():
_add_directory(library_dirs, dirname) _add_directory(library_dirs, dirname)
if sys.platform.startswith("linux") and os.environ.get("ANDROID_ROOT"): if sys.platform.startswith("linux") and os.environ.get("ANDROID_ROOT"):

View File

@ -31,7 +31,7 @@ import os
import subprocess import subprocess
from enum import IntEnum from enum import IntEnum
from functools import cached_property from functools import cached_property
from typing import IO, Any, Literal, NamedTuple, Union from typing import IO, Any, Literal, NamedTuple, Union, cast
from . import ( from . import (
Image, Image,
@ -350,12 +350,15 @@ class GifImageFile(ImageFile.ImageFile):
if self._frame_palette: if self._frame_palette:
if color * 3 + 3 > len(self._frame_palette.palette): if color * 3 + 3 > len(self._frame_palette.palette):
color = 0 color = 0
return tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]) return cast(
tuple[int, int, int],
tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]),
)
else: else:
return (color, color, color) return (color, color, color)
self.dispose = None self.dispose = None
self.dispose_extent = frame_dispose_extent self.dispose_extent: tuple[int, int, int, int] | None = frame_dispose_extent
if self.dispose_extent and self.disposal_method >= 2: if self.dispose_extent and self.disposal_method >= 2:
try: try:
if self.disposal_method == 2: if self.disposal_method == 2:

View File

@ -1511,7 +1511,7 @@ class Image:
return {} return {}
if "xmp" not in self.info: if "xmp" not in self.info:
return {} return {}
root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00")) root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00 "))
return {get_name(root.tag): get_value(root)} return {get_name(root.tag): get_value(root)}
def getexif(self) -> Exif: def getexif(self) -> Exif:
@ -1542,10 +1542,11 @@ class Image:
# XMP tags # XMP tags
if ExifTags.Base.Orientation not in self._exif: if ExifTags.Base.Orientation not in self._exif:
xmp_tags = self.info.get("XML:com.adobe.xmp") xmp_tags = self.info.get("XML:com.adobe.xmp")
pattern: str | bytes = r'tiff:Orientation(="|>)([0-9])'
if not xmp_tags and (xmp_tags := self.info.get("xmp")): if not xmp_tags and (xmp_tags := self.info.get("xmp")):
xmp_tags = xmp_tags.decode("utf-8") pattern = rb'tiff:Orientation(="|>)([0-9])'
if xmp_tags: if xmp_tags:
match = re.search(r'tiff:Orientation(="|>)([0-9])', xmp_tags) match = re.search(pattern, xmp_tags)
if match: if match:
self._exif[ExifTags.Base.Orientation] = int(match[2]) self._exif[ExifTags.Base.Orientation] = int(match[2])

View File

@ -365,22 +365,10 @@ class ImageDraw:
# use the fill as a mask # use the fill as a mask
mask = Image.new("1", self.im.size) mask = Image.new("1", self.im.size)
mask_ink = self._getink(1)[0] mask_ink = self._getink(1)[0]
draw = Draw(mask)
fill_im = mask.copy()
draw = Draw(fill_im)
draw.draw.draw_polygon(xy, mask_ink, 1) draw.draw.draw_polygon(xy, mask_ink, 1)
ink_im = mask.copy() self.draw.draw_polygon(xy, ink, 0, width * 2 - 1, mask.im)
draw = Draw(ink_im)
width = width * 2 - 1
draw.draw.draw_polygon(xy, mask_ink, 0, width)
mask.paste(ink_im, mask=fill_im)
im = Image.new(self.mode, self.im.size)
draw = Draw(im)
draw.draw.draw_polygon(xy, ink, 0, width)
self.im.paste(im.im, (0, 0) + im.size, mask.im)
def regular_polygon( def regular_polygon(
self, self,

View File

@ -134,10 +134,10 @@ def grabclipboard() -> Image.Image | list[str] | None:
import struct import struct
o = struct.unpack_from("I", data)[0] o = struct.unpack_from("I", data)[0]
if data[16] != 0: if data[16] == 0:
files = data[o:].decode("utf-16le").split("\0")
else:
files = data[o:].decode("mbcs").split("\0") files = data[o:].decode("mbcs").split("\0")
else:
files = data[o:].decode("utf-16le").split("\0")
return files[: files.index("")] return files[: files.index("")]
if isinstance(data, bytes): if isinstance(data, bytes):
data = io.BytesIO(data) data = io.BytesIO(data)

View File

@ -762,8 +762,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
extra = info.get("extra", b"") extra = info.get("extra", b"")
MAX_BYTES_IN_MARKER = 65533 MAX_BYTES_IN_MARKER = 65533
xmp = info.get("xmp") if xmp := info.get("xmp"):
if xmp:
overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00" overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
if len(xmp) > max_data_bytes_in_marker: if len(xmp) > max_data_bytes_in_marker:
@ -772,8 +771,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
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") if icc_profile := info.get("icc_profile"):
if icc_profile:
overhead_len = 14 # b"ICC_PROFILE\0" + o8(i) + o8(len(markers)) overhead_len = 14 # b"ICC_PROFILE\0" + o8(i) + o8(len(markers))
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
markers = [] markers = []
@ -831,7 +829,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# in a shot. Guessing on the size, at im.size bytes. (raw pixel size is # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
# channels*size, this is a value that's been used in a django patch. # channels*size, this is a value that's been used in a django patch.
# https://github.com/matthewwithanm/django-imagekit/issues/50 # https://github.com/matthewwithanm/django-imagekit/issues/50
bufsize = 0
if optimize or progressive: if optimize or progressive:
# CMYK can be bigger # CMYK can be bigger
if im.mode == "CMYK": if im.mode == "CMYK":
@ -848,7 +845,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
else: else:
# The EXIF info needs to be written as one block, + APP1, + one spare byte. # The EXIF info needs to be written as one block, + APP1, + one spare byte.
# Ensure that our buffer is big enough. Same with the icc_profile block. # Ensure that our buffer is big enough. Same with the icc_profile block.
bufsize = max(bufsize, len(exif) + 5, len(extra) + 1) bufsize = max(len(exif) + 5, len(extra) + 1)
ImageFile._save( ImageFile._save(
im, fp, [ImageFile._Tile("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize im, fp, [ImageFile._Tile("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize

View File

@ -54,7 +54,7 @@ class PcxImageFile(ImageFile.ImageFile):
# header # header
assert self.fp is not None assert self.fp is not None
s = self.fp.read(128) s = self.fp.read(68)
if not _accept(s): if not _accept(s):
msg = "not a PCX file" msg = "not a PCX file"
raise SyntaxError(msg) raise SyntaxError(msg)
@ -66,6 +66,8 @@ class PcxImageFile(ImageFile.ImageFile):
raise SyntaxError(msg) raise SyntaxError(msg)
logger.debug("BBox: %s %s %s %s", *bbox) logger.debug("BBox: %s %s %s %s", *bbox)
offset = self.fp.tell() + 60
# format # format
version = s[1] version = s[1]
bits = s[3] bits = s[3]
@ -102,7 +104,6 @@ class PcxImageFile(ImageFile.ImageFile):
break break
if mode == "P": if mode == "P":
self.palette = ImagePalette.raw("RGB", s[1:]) self.palette = ImagePalette.raw("RGB", s[1:])
self.fp.seek(128)
elif version == 5 and bits == 8 and planes == 3: elif version == 5 and bits == 8 and planes == 3:
mode = "RGB" mode = "RGB"
@ -128,9 +129,7 @@ class PcxImageFile(ImageFile.ImageFile):
bbox = (0, 0) + self.size bbox = (0, 0) + self.size
logger.debug("size: %sx%s", *self.size) logger.debug("size: %sx%s", *self.size)
self.tile = [ self.tile = [ImageFile._Tile("pcx", bbox, offset, (rawmode, planes * stride))]
ImageFile._Tile("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))
]
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -94,8 +94,8 @@ class PpmImageFile(ImageFile.ImageFile):
msg = "Reached EOF while reading header" msg = "Reached EOF while reading header"
raise ValueError(msg) raise ValueError(msg)
elif len(token) > 10: elif len(token) > 10:
msg = f"Token too long in file header: {token.decode()}" msg_too_long = b"Token too long in file header: %s" % token
raise ValueError(msg) raise ValueError(msg_too_long)
return token return token
def _open(self) -> None: def _open(self) -> None:

View File

@ -51,7 +51,7 @@ class QoiDecoder(ImageFile.PyDecoder):
assert self.fd is not None assert self.fd is not None
self._previously_seen_pixels = {} self._previously_seen_pixels = {}
self._add_to_previous_pixels(bytearray((0, 0, 0, 255))) self._previous_pixel = bytearray((0, 0, 0, 255))
data = bytearray() data = bytearray()
bands = Image.getmodebands(self.mode) bands = Image.getmodebands(self.mode)

View File

@ -1217,9 +1217,10 @@ class TiffImageFile(ImageFile.ImageFile):
return return
self._seek(frame) self._seek(frame)
if self._im is not None and ( if self._im is not None and (
self.im.size != self._tile_size or self.im.mode != self.mode self.im.size != self._tile_size
or self.im.mode != self.mode
or self.readonly
): ):
# The core image will no longer be used
self._im = None self._im = None
def _seek(self, frame: int) -> None: def _seek(self, frame: int) -> None:
@ -1259,7 +1260,10 @@ class TiffImageFile(ImageFile.ImageFile):
self.fp.seek(self._frame_pos[frame]) self.fp.seek(self._frame_pos[frame])
self.tag_v2.load(self.fp) self.tag_v2.load(self.fp)
if XMP in self.tag_v2: if XMP in self.tag_v2:
self.info["xmp"] = self.tag_v2[XMP] xmp = self.tag_v2[XMP]
if isinstance(xmp, tuple) and len(xmp) == 1:
xmp = xmp[0]
self.info["xmp"] = xmp
elif "xmp" in self.info: elif "xmp" in self.info:
del self.info["xmp"] del self.info["xmp"]
self._reload_exif() self._reload_exif()

View File

@ -338,12 +338,6 @@ static const char *no_palette = "image has no palette";
static const char *readonly = "image is readonly"; static const char *readonly = "image is readonly";
/* static const char* no_content = "image has no content"; */ /* static const char* no_content = "image has no content"; */
void *
ImagingError_OSError(void) {
PyErr_SetString(PyExc_OSError, "error when accessing file");
return NULL;
}
void * void *
ImagingError_MemoryError(void) { ImagingError_MemoryError(void) {
return PyErr_NoMemory(); return PyErr_NoMemory();
@ -369,11 +363,6 @@ ImagingError_ValueError(const char *message) {
return NULL; return NULL;
} }
void
ImagingError_Clear(void) {
PyErr_Clear();
}
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/* HELPERS */ /* HELPERS */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
@ -3220,7 +3209,8 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
(int)p[3], (int)p[3],
&ink, &ink,
width, width,
self->blend self->blend,
NULL
) < 0) { ) < 0) {
free(xy); free(xy);
return NULL; return NULL;
@ -3358,7 +3348,10 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
int ink; int ink;
int fill = 0; int fill = 0;
int width = 0; int width = 0;
if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) { ImagingObject *maskp = NULL;
if (!PyArg_ParseTuple(
args, "Oi|iiO!", &data, &ink, &fill, &width, &Imaging_Type, &maskp
)) {
return NULL; return NULL;
} }
@ -3388,8 +3381,16 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
free(xy); free(xy);
if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, width, self->blend) < if (ImagingDrawPolygon(
0) { self->image->image,
n,
ixy,
&ink,
fill,
width,
self->blend,
maskp ? maskp->image : NULL
) < 0) {
free(ixy); free(ixy);
return NULL; return NULL;
} }

View File

@ -101,7 +101,7 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
} }
/* for now, single block images */ /* for now, single block images */
if (!(im->blocks_count == 0 || im->blocks_count == 1)) { if (im->blocks_count > 1) {
return IMAGING_ARROW_MEMORY_LAYOUT; return IMAGING_ARROW_MEMORY_LAYOUT;
} }
@ -159,7 +159,7 @@ export_single_channel_array(Imaging im, struct ArrowArray *array) {
int length = im->xsize * im->ysize; int length = im->xsize * im->ysize;
/* for now, single block images */ /* for now, single block images */
if (!(im->blocks_count == 0 || im->blocks_count == 1)) { if (im->blocks_count > 1) {
return IMAGING_ARROW_MEMORY_LAYOUT; return IMAGING_ARROW_MEMORY_LAYOUT;
} }
@ -202,7 +202,7 @@ export_fixed_pixel_array(Imaging im, struct ArrowArray *array) {
int length = im->xsize * im->ysize; int length = im->xsize * im->ysize;
/* for now, single block images */ /* for now, single block images */
if (!(im->blocks_count == 0 || im->blocks_count == 1)) { if (im->blocks_count > 1) {
return IMAGING_ARROW_MEMORY_LAYOUT; return IMAGING_ARROW_MEMORY_LAYOUT;
} }

View File

@ -63,7 +63,7 @@ typedef struct {
} Edge; } Edge;
/* Type used in "polygon*" functions */ /* Type used in "polygon*" functions */
typedef void (*hline_handler)(Imaging, int, int, int, int); typedef void (*hline_handler)(Imaging, int, int, int, int, Imaging);
static inline void static inline void
point8(Imaging im, int x, int y, int ink) { point8(Imaging im, int x, int y, int ink) {
@ -103,9 +103,7 @@ point32rgba(Imaging im, int x, int y, int ink) {
} }
static inline void static inline void
hline8(Imaging im, int x0, int y0, int x1, int ink) { hline8(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
int pixelwidth;
if (y0 >= 0 && y0 < im->ysize) { if (y0 >= 0 && y0 < im->ysize) {
if (x0 < 0) { if (x0 < 0) {
x0 = 0; x0 = 0;
@ -118,16 +116,41 @@ hline8(Imaging im, int x0, int y0, int x1, int ink) {
x1 = im->xsize - 1; x1 = im->xsize - 1;
} }
if (x0 <= x1) { if (x0 <= x1) {
pixelwidth = strncmp(im->mode, "I;16", 4) == 0 ? 2 : 1; int bigendian = -1;
memset( if (strncmp(im->mode, "I;16", 4) == 0) {
im->image8[y0] + x0 * pixelwidth, (UINT8)ink, (x1 - x0 + 1) * pixelwidth bigendian =
); (
#ifdef WORDS_BIGENDIAN
strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16L") == 0
#else
strcmp(im->mode, "I;16B") == 0
#endif
)
? 1
: 0;
}
if (mask == NULL && bigendian == -1) {
memset(im->image8[y0] + x0, (UINT8)ink, (x1 - x0 + 1));
} else {
UINT8 *p = im->image8[y0];
while (x0 <= x1) {
if (mask == NULL || mask->image8[y0][x0]) {
if (bigendian == -1) {
p[x0] = ink;
} else {
p[x0 * 2 + (bigendian ? 1 : 0)] = ink;
p[x0 * 2 + (bigendian ? 0 : 1)] = ink >> 8;
}
}
x0++;
}
}
} }
} }
} }
static inline void static inline void
hline32(Imaging im, int x0, int y0, int x1, int ink) { hline32(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
INT32 *p; INT32 *p;
if (y0 >= 0 && y0 < im->ysize) { if (y0 >= 0 && y0 < im->ysize) {
@ -143,13 +166,16 @@ hline32(Imaging im, int x0, int y0, int x1, int ink) {
} }
p = im->image32[y0]; p = im->image32[y0];
while (x0 <= x1) { while (x0 <= x1) {
p[x0++] = ink; if (mask == NULL || mask->image8[y0][x0]) {
p[x0] = ink;
}
x0++;
} }
} }
} }
static inline void static inline void
hline32rgba(Imaging im, int x0, int y0, int x1, int ink) { hline32rgba(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
unsigned int tmp; unsigned int tmp;
if (y0 >= 0 && y0 < im->ysize) { if (y0 >= 0 && y0 < im->ysize) {
@ -167,9 +193,11 @@ hline32rgba(Imaging im, int x0, int y0, int x1, int ink) {
UINT8 *out = (UINT8 *)im->image[y0] + x0 * 4; UINT8 *out = (UINT8 *)im->image[y0] + x0 * 4;
UINT8 *in = (UINT8 *)&ink; UINT8 *in = (UINT8 *)&ink;
while (x0 <= x1) { while (x0 <= x1) {
out[0] = BLEND(in[3], out[0], in[0], tmp); if (mask == NULL || mask->image8[y0][x0]) {
out[1] = BLEND(in[3], out[1], in[1], tmp); out[0] = BLEND(in[3], out[0], in[0], tmp);
out[2] = BLEND(in[3], out[2], in[2], tmp); out[1] = BLEND(in[3], out[1], in[1], tmp);
out[2] = BLEND(in[3], out[2], in[2], tmp);
}
x0++; x0++;
out += 4; out += 4;
} }
@ -407,7 +435,14 @@ x_cmp(const void *x0, const void *x1) {
static void static void
draw_horizontal_lines( draw_horizontal_lines(
Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hline_handler hline Imaging im,
int n,
Edge *e,
int ink,
int *x_pos,
int y,
hline_handler hline,
Imaging mask
) { ) {
int i; int i;
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
@ -429,7 +464,7 @@ draw_horizontal_lines(
} }
} }
(*hline)(im, xmin, e[i].ymin, xmax, ink); (*hline)(im, xmin, e[i].ymin, xmax, ink, mask);
*x_pos = xmax + 1; *x_pos = xmax + 1;
} }
} }
@ -440,7 +475,7 @@ draw_horizontal_lines(
*/ */
static inline int static inline int
polygon_generic( polygon_generic(
Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, int hasAlpha Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, Imaging mask
) { ) {
Edge **edge_table; Edge **edge_table;
float *xx; float *xx;
@ -461,6 +496,7 @@ polygon_generic(
return -1; return -1;
} }
int hasAlpha = hline == hline32rgba;
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
if (ymin > e[i].ymin) { if (ymin > e[i].ymin) {
ymin = e[i].ymin; ymin = e[i].ymin;
@ -470,7 +506,7 @@ polygon_generic(
} }
if (e[i].ymin == e[i].ymax) { if (e[i].ymin == e[i].ymax) {
if (hasAlpha != 1) { if (hasAlpha != 1) {
(*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink); (*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink, mask);
} }
continue; continue;
} }
@ -558,7 +594,7 @@ polygon_generic(
// Line would be before the current position // Line would be before the current position
continue; continue;
} }
draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline); draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline, mask);
if (x_end < x_pos) { if (x_end < x_pos) {
// Line would be before the current position // Line would be before the current position
continue; continue;
@ -574,13 +610,13 @@ polygon_generic(
continue; continue;
} }
} }
(*hline)(im, x_start, ymin, x_end, ink); (*hline)(im, x_start, ymin, x_end, ink, mask);
x_pos = x_end + 1; x_pos = x_end + 1;
} }
draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline); draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline, mask);
} else { } else {
for (i = 1; i < j; i += 2) { for (i = 1; i < j; i += 2) {
(*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink); (*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink, mask);
} }
} }
} }
@ -590,21 +626,6 @@ polygon_generic(
return 0; return 0;
} }
static inline int
polygon8(Imaging im, int n, Edge *e, int ink, int eofill) {
return polygon_generic(im, n, e, ink, eofill, hline8, 0);
}
static inline int
polygon32(Imaging im, int n, Edge *e, int ink, int eofill) {
return polygon_generic(im, n, e, ink, eofill, hline32, 0);
}
static inline int
polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) {
return polygon_generic(im, n, e, ink, eofill, hline32rgba, 1);
}
static inline void static inline void
add_edge(Edge *e, int x0, int y0, int x1, int y1) { add_edge(Edge *e, int x0, int y0, int x1, int y1) {
/* printf("edge %d %d %d %d\n", x0, y0, x1, y1); */ /* printf("edge %d %d %d %d\n", x0, y0, x1, y1); */
@ -639,14 +660,13 @@ add_edge(Edge *e, int x0, int y0, int x1, int y1) {
typedef struct { typedef struct {
void (*point)(Imaging im, int x, int y, int ink); void (*point)(Imaging im, int x, int y, int ink);
void (*hline)(Imaging im, int x0, int y0, int x1, int ink); void (*hline)(Imaging im, int x0, int y0, int x1, int ink, Imaging mask);
void (*line)(Imaging im, int x0, int y0, int x1, int y1, int ink); void (*line)(Imaging im, int x0, int y0, int x1, int y1, int ink);
int (*polygon)(Imaging im, int n, Edge *e, int ink, int eofill);
} DRAW; } DRAW;
DRAW draw8 = {point8, hline8, line8, polygon8}; DRAW draw8 = {point8, hline8, line8};
DRAW draw32 = {point32, hline32, line32, polygon32}; DRAW draw32 = {point32, hline32, line32};
DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba, polygon32rgba}; DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba};
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/* Interface */ /* Interface */
@ -691,7 +711,15 @@ ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink_, in
int int
ImagingDrawWideLine( ImagingDrawWideLine(
Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int width, int op Imaging im,
int x0,
int y0,
int x1,
int y1,
const void *ink_,
int width,
int op,
Imaging mask
) { ) {
DRAW *draw; DRAW *draw;
INT32 ink; INT32 ink;
@ -731,7 +759,7 @@ ImagingDrawWideLine(
add_edge(e + 2, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1]); add_edge(e + 2, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1]);
add_edge(e + 3, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1]); add_edge(e + 3, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1]);
draw->polygon(im, 4, e, ink, 0); polygon_generic(im, 4, e, ink, 0, draw->hline, mask);
} }
return 0; return 0;
} }
@ -774,7 +802,7 @@ ImagingDrawRectangle(
} }
for (y = y0; y <= y1; y++) { for (y = y0; y <= y1; y++) {
draw->hline(im, x0, y, x1, ink); draw->hline(im, x0, y, x1, ink, NULL);
} }
} else { } else {
@ -783,8 +811,8 @@ ImagingDrawRectangle(
width = 1; width = 1;
} }
for (i = 0; i < width; i++) { for (i = 0; i < width; i++) {
draw->hline(im, x0, y0 + i, x1, ink); draw->hline(im, x0, y0 + i, x1, ink, NULL);
draw->hline(im, x0, y1 - i, x1, ink); draw->hline(im, x0, y1 - i, x1, ink, NULL);
draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink); draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink);
draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink); draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink);
} }
@ -795,7 +823,14 @@ ImagingDrawRectangle(
int int
ImagingDrawPolygon( ImagingDrawPolygon(
Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op Imaging im,
int count,
int *xy,
const void *ink_,
int fill,
int width,
int op,
Imaging mask
) { ) {
int i, n, x0, y0, x1, y1; int i, n, x0, y0, x1, y1;
DRAW *draw; DRAW *draw;
@ -839,7 +874,7 @@ ImagingDrawPolygon(
if (xy[i * 2] != xy[0] || xy[i * 2 + 1] != xy[1]) { if (xy[i * 2] != xy[0] || xy[i * 2 + 1] != xy[1]) {
add_edge(&e[n++], xy[i * 2], xy[i * 2 + 1], xy[0], xy[1]); add_edge(&e[n++], xy[i * 2], xy[i * 2 + 1], xy[0], xy[1]);
} }
draw->polygon(im, n, e, ink, 0); polygon_generic(im, n, e, ink, 0, draw->hline, mask);
free(e); free(e);
} else { } else {
@ -861,11 +896,12 @@ ImagingDrawPolygon(
xy[i * 2 + 3], xy[i * 2 + 3],
ink_, ink_,
width, width,
op op,
mask
); );
} }
ImagingDrawWideLine( ImagingDrawWideLine(
im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op, mask
); );
} }
} }
@ -1536,7 +1572,9 @@ ellipseNew(
ellipse_init(&st, a, b, width); ellipse_init(&st, a, b, width);
int32_t X0, Y, X1; int32_t X0, Y, X1;
while (ellipse_next(&st, &X0, &Y, &X1) != -1) { while (ellipse_next(&st, &X0, &Y, &X1) != -1) {
draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink); draw->hline(
im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink, NULL
);
} }
return 0; return 0;
} }
@ -1571,7 +1609,9 @@ clipEllipseNew(
int32_t X0, Y, X1; int32_t X0, Y, X1;
int next_code; int next_code;
while ((next_code = clip_ellipse_next(&st, &X0, &Y, &X1)) >= 0) { while ((next_code = clip_ellipse_next(&st, &X0, &Y, &X1)) >= 0) {
draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink); draw->hline(
im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink, NULL
);
} }
clip_ellipse_free(&st); clip_ellipse_free(&st);
return next_code == -1 ? 0 : -1; return next_code == -1 ? 0 : -1;
@ -1989,7 +2029,7 @@ ImagingDrawOutline(
DRAWINIT(); DRAWINIT();
draw->polygon(im, outline->count, outline->edges, ink, 0); polygon_generic(im, outline->count, outline->edges, ink, 0, draw->hline, NULL);
return 0; return 0;
} }

View File

@ -54,7 +54,7 @@ ImagingSavePPM(Imaging im, const char *outfile) {
fp = fopen(outfile, "wb"); fp = fopen(outfile, "wb");
if (!fp) { if (!fp) {
(void)ImagingError_OSError(); PyErr_SetString(PyExc_OSError, "error when accessing file");
return 0; return 0;
} }

View File

@ -270,8 +270,6 @@ ImagingSectionLeave(ImagingSectionCookie *cookie);
/* Exceptions */ /* Exceptions */
/* ---------- */ /* ---------- */
extern void *
ImagingError_OSError(void);
extern void * extern void *
ImagingError_MemoryError(void); ImagingError_MemoryError(void);
extern void * extern void *
@ -280,8 +278,6 @@ extern void *
ImagingError_Mismatch(void); /* maps to ValueError by default */ ImagingError_Mismatch(void); /* maps to ValueError by default */
extern void * extern void *
ImagingError_ValueError(const char *message); ImagingError_ValueError(const char *message);
extern void
ImagingError_Clear(void);
/* Transform callbacks */ /* Transform callbacks */
/* ------------------- */ /* ------------------- */
@ -510,7 +506,15 @@ extern int
ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink, int op); ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink, int op);
extern int extern int
ImagingDrawWideLine( ImagingDrawWideLine(
Imaging im, int x0, int y0, int x1, int y1, const void *ink, int width, int op Imaging im,
int x0,
int y0,
int x1,
int y1,
const void *ink,
int width,
int op,
Imaging mask
); );
extern int extern int
ImagingDrawPieslice( ImagingDrawPieslice(
@ -530,7 +534,14 @@ extern int
ImagingDrawPoint(Imaging im, int x, int y, const void *ink, int op); ImagingDrawPoint(Imaging im, int x, int y, const void *ink, int op);
extern int extern int
ImagingDrawPolygon( ImagingDrawPolygon(
Imaging im, int points, int *xy, const void *ink, int fill, int width, int op Imaging im,
int points,
int *xy,
const void *ink,
int fill,
int width,
int op,
Imaging mask
); );
extern int extern int
ImagingDrawRectangle( ImagingDrawRectangle(

View File

@ -60,15 +60,25 @@ ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
} }
if (state->x >= state->bytes) { if (state->x >= state->bytes) {
if (state->bytes % state->xsize && state->bytes > state->xsize) { int bands;
int bands = state->bytes / state->xsize; int xsize = 0;
int stride = state->bytes / bands; int stride = 0;
if (state->bits == 2 || state->bits == 4) {
xsize = (state->xsize + 7) / 8;
bands = state->bits;
stride = state->bytes / state->bits;
} else {
xsize = state->xsize;
bands = state->bytes / state->xsize;
if (bands != 0) {
stride = state->bytes / bands;
}
}
if (stride > xsize) {
int i; int i;
for (i = 1; i < bands; i++) { // note -- skipping first band for (i = 1; i < bands; i++) { // note -- skipping first band
memmove( memmove(
&state->buffer[i * state->xsize], &state->buffer[i * xsize], &state->buffer[i * stride], xsize
&state->buffer[i * stride],
state->xsize
); );
} }
} }

View File

@ -645,7 +645,7 @@ ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) {
return im; return im;
} }
ImagingError_Clear(); PyErr_Clear();
// Try to allocate the image once more with smallest possible block size // Try to allocate the image once more with smallest possible block size
MUTEX_LOCK(&ImagingDefaultArena.mutex); MUTEX_LOCK(&ImagingDefaultArena.mutex);

View File

@ -137,6 +137,7 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) {
} }
} }
im->read_only = view.readonly;
im->destroy = mapping_destroy_buffer; im->destroy = mapping_destroy_buffer;
Py_INCREF(target); Py_INCREF(target);

View File

@ -3,7 +3,7 @@ requires =
tox>=4.2 tox>=4.2
env_list = env_list =
lint lint
py{py3, 313, 312, 311, 310, 39} py{py3, 314, 313, 312, 311, 310, 39}
[testenv] [testenv]
deps = deps =

View File

@ -114,11 +114,11 @@ V = {
"FREETYPE": "2.13.3", "FREETYPE": "2.13.3",
"FRIBIDI": "1.0.16", "FRIBIDI": "1.0.16",
"HARFBUZZ": "11.2.1", "HARFBUZZ": "11.2.1",
"JPEGTURBO": "3.1.0", "JPEGTURBO": "3.1.1",
"LCMS2": "2.17", "LCMS2": "2.17",
"LIBAVIF": "1.3.0", "LIBAVIF": "1.3.0",
"LIBIMAGEQUANT": "4.3.4", "LIBIMAGEQUANT": "4.3.4",
"LIBPNG": "1.6.48", "LIBPNG": "1.6.49",
"LIBWEBP": "1.5.0", "LIBWEBP": "1.5.0",
"OPENJPEG": "2.5.3", "OPENJPEG": "2.5.3",
"TIFF": "4.7.0", "TIFF": "4.7.0",