Merge branch 'main' into init

This commit is contained in:
Andrew Murray 2024-07-28 21:38:58 +10:00 committed by GitHub
commit 1b92e78adf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
148 changed files with 3839 additions and 1386 deletions

View File

@ -37,12 +37,18 @@ python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma python3 -m pip install pyroma
if [[ $(uname) != CYGWIN* ]]; then if [[ $(uname) != CYGWIN* ]]; then
# TODO Update condition when NumPy supports free-threading
if [[ "$PYTHON_GIL" == "0" ]]; then
python3 -m pip install numpy --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
else
python3 -m pip install numpy python3 -m pip install numpy
fi
# PyQt6 doesn't support PyPy3 # PyQt6 doesn't support PyPy3
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0 sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
python3 -m pip install pyqt6 # TODO Update condition when pyqt6 supports free-threading
if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi
fi fi
# Pyroma uses non-isolated build and fails with old setuptools # Pyroma uses non-isolated build and fails with old setuptools

View File

@ -1 +1 @@
mypy==1.10.1 mypy==1.11.0

View File

@ -3,7 +3,7 @@
BasedOnStyle: Google BasedOnStyle: Google
AlwaysBreakAfterReturnType: All AlwaysBreakAfterReturnType: All
AllowShortIfStatementsOnASingleLine: false AllowShortIfStatementsOnASingleLine: false
AlignAfterOpenBracket: AlwaysBreak AlignAfterOpenBracket: BlockIndent
BinPackArguments: false BinPackArguments: false
BinPackParameters: false BinPackParameters: false
BreakBeforeBraces: Attach BreakBeforeBraces: Attach

View File

@ -24,6 +24,8 @@ concurrency:
jobs: jobs:
Fuzzing: Fuzzing:
# Disabled until google/oss-fuzz#11419 upgrades Python to 3.9+
if: false
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Build Fuzzers - name: Build Fuzzers

View File

@ -50,26 +50,24 @@ jobs:
"3.9", "3.9",
] ]
include: include:
- python-version: "3.11" - { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
PYTHONOPTIMIZE: 1 - { python-version: "3.10", PYTHONOPTIMIZE: 2 }
REVERSE: "--reverse" # Free-threaded
- python-version: "3.10" - { os: "ubuntu-latest", python-version: "3.13-dev", disable-gil: true }
PYTHONOPTIMIZE: 2
# M1 only available for 3.10+ # M1 only available for 3.10+
- os: "macos-13" - { os: "macos-13", python-version: "3.9" }
python-version: "3.9"
exclude: exclude:
- os: "macos-14" - { os: "macos-14", python-version: "3.9" }
python-version: "3.9"
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} ${{ matrix.disable-gil && 'free-threaded' || '' }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: actions/setup-python@v5
if: "${{ !matrix.disable-gil }}"
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
allow-prereleases: true allow-prereleases: true
@ -78,6 +76,18 @@ jobs:
".ci/*.sh" ".ci/*.sh"
"pyproject.toml" "pyproject.toml"
- name: Set up Python ${{ matrix.python-version }} (free-threaded)
uses: deadsnakes/action@v3.1.0
if: "${{ matrix.disable-gil }}"
with:
python-version: ${{ matrix.python-version }}
nogil: ${{ matrix.disable-gil }}
- name: Set PYTHON_GIL
if: "${{ matrix.disable-gil }}"
run: |
echo "PYTHON_GIL=0" >> $GITHUB_ENV
- name: Build system information - name: Build system information
run: python3 .github/workflows/system-info.py run: python3 .github/workflows/system-info.py

View File

@ -12,9 +12,15 @@ elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
else else
yum install -y fribidi yum install -y fribidi
fi fi
if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; then if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; then
# TODO Update condition when NumPy supports free-threading
if [ $(python3 -c "import sysconfig;print(sysconfig.get_config_var('Py_GIL_DISABLED'))") == "1" ]; then
python3 -m pip install numpy --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
else
python3 -m pip install numpy python3 -m pip install numpy
fi fi
fi
if [ ! -d "test-images-main" ]; then if [ ! -d "test-images-main" ]; then
curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip

View File

@ -1,6 +1,14 @@
name: Wheels name: Wheels
on: on:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
# │ │ ┌───────────── day of the month (1 - 31)
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
# │ │ │ │ │
- cron: "42 1 * * 0,3"
push: push:
paths: paths:
- ".ci/requirements-cibw.txt" - ".ci/requirements-cibw.txt"
@ -33,6 +41,7 @@ env:
jobs: jobs:
build-1-QEMU-emulated-wheels: build-1-QEMU-emulated-wheels:
if: github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'
name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }} name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
@ -88,6 +97,7 @@ jobs:
path: ./wheelhouse/*.whl path: ./wheelhouse/*.whl
build-2-native-wheels: build-2-native-wheels:
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
name: ${{ matrix.name }} name: ${{ matrix.name }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@ -129,6 +139,7 @@ jobs:
env: env:
CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_ARCHS: ${{ matrix.cibw_arch }}
CIBW_BUILD: ${{ matrix.build }} CIBW_BUILD: ${{ matrix.build }}
CIBW_FREE_THREADED_SUPPORT: True
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_PRERELEASE_PYTHONS: True CIBW_PRERELEASE_PYTHONS: True
@ -140,6 +151,7 @@ jobs:
path: ./wheelhouse/*.whl path: ./wheelhouse/*.whl
windows: windows:
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
name: Windows ${{ matrix.cibw_arch }} name: Windows ${{ matrix.cibw_arch }}
runs-on: windows-latest runs-on: windows-latest
strategy: strategy:
@ -201,6 +213,7 @@ jobs:
CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_ARCHS: ${{ matrix.cibw_arch }}
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_FREE_THREADED_SUPPORT: True
CIBW_PRERELEASE_PYTHONS: True CIBW_PRERELEASE_PYTHONS: True
CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_COMMAND: 'docker run --rm CIBW_TEST_COMMAND: 'docker run --rm
@ -225,6 +238,7 @@ jobs:
path: winbuild\build\bin\fribidi* path: winbuild\build\bin\fribidi*
sdist: sdist:
if: github.event_name != 'schedule'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -243,8 +257,25 @@ jobs:
name: dist-sdist name: dist-sdist
path: dist/*.tar.gz path: dist/*.tar.gz
scientific-python-nightly-wheels-publish:
if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
needs: [build-2-native-wheels, windows]
runs-on: ubuntu-latest
name: Upload wheels to scientific-python-nightly-wheels
steps:
- uses: actions/download-artifact@v4
with:
pattern: dist-*
path: dist
merge-multiple: true
- name: Upload wheels to scientific-python-nightly-wheels
uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0
with:
artifacts_path: dist
anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}
pypi-publish: pypi-publish:
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist] needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist]
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Upload release to PyPI name: Upload release to PyPI

View File

@ -5,6 +5,18 @@ Changelog (Pillow)
11.0.0 (unreleased) 11.0.0 (unreleased)
------------------- -------------------
- Deprecate ImageMath lambda_eval and unsafe_eval options argument #8242
[radarhere]
- Changed ContainerIO to subclass IO #8240
[radarhere]
- Move away from APIs that use borrowed references under the free-threaded build #8216
[hugovk, lysnikolaou]
- Allow size argument to resize() to be a NumPy array #8201
[radarhere]
- Drop support for Python 3.8 #8183 - Drop support for Python 3.8 #8183
[hugovk, radarhere] [hugovk, radarhere]

View File

@ -1,7 +1,5 @@
from __future__ import annotations from __future__ import annotations
from typing import Literal
import pytest import pytest
from PIL import ContainerIO, Image from PIL import ContainerIO, Image
@ -23,6 +21,13 @@ def test_isatty() -> None:
assert container.isatty() is False assert container.isatty() is False
def test_seekable() -> None:
with hopper() as im:
container = ContainerIO.ContainerIO(im, 0, 0)
assert container.seekable() is True
@pytest.mark.parametrize( @pytest.mark.parametrize(
"mode, expected_position", "mode, expected_position",
( (
@ -31,7 +36,7 @@ def test_isatty() -> None:
(2, 100), (2, 100),
), ),
) )
def test_seek_mode(mode: Literal[0, 1, 2], expected_position: int) -> None: def test_seek_mode(mode: int, expected_position: int) -> None:
# Arrange # Arrange
with open(TEST_FILE, "rb") as fh: with open(TEST_FILE, "rb") as fh:
container = ContainerIO.ContainerIO(fh, 22, 100) container = ContainerIO.ContainerIO(fh, 22, 100)
@ -44,6 +49,14 @@ def test_seek_mode(mode: Literal[0, 1, 2], expected_position: int) -> None:
assert container.tell() == expected_position assert container.tell() == expected_position
@pytest.mark.parametrize("bytesmode", (True, False))
def test_readable(bytesmode: bool) -> None:
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 0, 120)
assert container.readable() is True
@pytest.mark.parametrize("bytesmode", (True, False)) @pytest.mark.parametrize("bytesmode", (True, False))
def test_read_n0(bytesmode: bool) -> None: def test_read_n0(bytesmode: bool) -> None:
# Arrange # Arrange
@ -51,7 +64,7 @@ def test_read_n0(bytesmode: bool) -> None:
container = ContainerIO.ContainerIO(fh, 22, 100) container = ContainerIO.ContainerIO(fh, 22, 100)
# Act # Act
container.seek(81) assert container.seek(81) == 81
data = container.read() data = container.read()
# Assert # Assert
@ -67,7 +80,7 @@ def test_read_n(bytesmode: bool) -> None:
container = ContainerIO.ContainerIO(fh, 22, 100) container = ContainerIO.ContainerIO(fh, 22, 100)
# Act # Act
container.seek(81) assert container.seek(81) == 81
data = container.read(3) data = container.read(3)
# Assert # Assert
@ -83,7 +96,7 @@ def test_read_eof(bytesmode: bool) -> None:
container = ContainerIO.ContainerIO(fh, 22, 100) container = ContainerIO.ContainerIO(fh, 22, 100)
# Act # Act
container.seek(100) assert container.seek(100) == 100
data = container.read() data = container.read()
# Assert # Assert
@ -94,21 +107,65 @@ def test_read_eof(bytesmode: bool) -> None:
@pytest.mark.parametrize("bytesmode", (True, False)) @pytest.mark.parametrize("bytesmode", (True, False))
def test_readline(bytesmode: bool) -> None: def test_readline(bytesmode: bool) -> None:
# Arrange
with open(TEST_FILE, "rb" if bytesmode else "r") as fh: with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 0, 120) container = ContainerIO.ContainerIO(fh, 0, 120)
# Act
data = container.readline() data = container.readline()
# Assert
if bytesmode: if bytesmode:
data = data.decode() data = data.decode()
assert data == "This is line 1\n" assert data == "This is line 1\n"
data = container.readline(4)
if bytesmode:
data = data.decode()
assert data == "This"
@pytest.mark.parametrize("bytesmode", (True, False)) @pytest.mark.parametrize("bytesmode", (True, False))
def test_readlines(bytesmode: bool) -> None: def test_readlines(bytesmode: bool) -> None:
expected = [
"This is line 1\n",
"This is line 2\n",
"This is line 3\n",
"This is line 4\n",
"This is line 5\n",
"This is line 6\n",
"This is line 7\n",
"This is line 8\n",
]
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 0, 120)
data = container.readlines()
if bytesmode:
data = [line.decode() for line in data]
assert data == expected
assert container.seek(0) == 0
data = container.readlines(2)
if bytesmode:
data = [line.decode() for line in data]
assert data == expected[:2]
@pytest.mark.parametrize("bytesmode", (True, False))
def test_write(bytesmode: bool) -> None:
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 0, 120)
assert container.writable() is False
with pytest.raises(NotImplementedError):
container.write(b"" if bytesmode else "")
with pytest.raises(NotImplementedError):
container.writelines([])
with pytest.raises(NotImplementedError):
container.truncate()
@pytest.mark.parametrize("bytesmode", (True, False))
def test_iter(bytesmode: bool) -> None:
# Arrange # Arrange
expected = [ expected = [
"This is line 1\n", "This is line 1\n",
@ -124,9 +181,21 @@ def test_readlines(bytesmode: bool) -> None:
container = ContainerIO.ContainerIO(fh, 0, 120) container = ContainerIO.ContainerIO(fh, 0, 120)
# Act # Act
data = container.readlines() data = []
for line in container:
data.append(line)
# Assert # Assert
if bytesmode: if bytesmode:
data = [line.decode() for line in data] data = [line.decode() for line in data]
assert data == expected assert data == expected
@pytest.mark.parametrize("bytesmode", (True, False))
def test_file(bytesmode: bool) -> None:
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
container = ContainerIO.ContainerIO(fh, 0, 120)
assert isinstance(container.fileno(), int)
container.flush()
container.close()

View File

@ -57,6 +57,7 @@ def test_getiptcinfo_fotostation() -> None:
iptc = IptcImagePlugin.getiptcinfo(im) iptc = IptcImagePlugin.getiptcinfo(im)
# Assert # Assert
assert iptc is not None
for tag in iptc.keys(): for tag in iptc.keys():
if tag[0] == 240: if tag[0] == 240:
return return

View File

@ -829,7 +829,7 @@ class TestFileJpeg:
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
# Act / Assert # Act / Assert
# "When the image resolution is unknown, 72 [dpi] is designated." # "When the image resolution is unknown, 72 [dpi] is designated."
# https://web.archive.org/web/20240227115053/https://exiv2.org/tags.html # https://exiv2.org/tags.html
assert im.info.get("dpi") == (72, 72) assert im.info.get("dpi") == (72, 72)
def test_invalid_exif(self) -> None: def test_invalid_exif(self) -> None:

View File

@ -240,9 +240,10 @@ class TestFileLibTiff(LibTiffTestCase):
new_ifd = TiffImagePlugin.ImageFileDirectory_v2() new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
for tag, info in core_items.items(): for tag, info in core_items.items():
assert info.type is not None
if info.length == 1: if info.length == 1:
new_ifd[tag] = values[info.type] new_ifd[tag] = values[info.type]
if info.length == 0: elif not info.length:
new_ifd[tag] = tuple(values[info.type] for _ in range(3)) new_ifd[tag] = tuple(values[info.type] for _ in range(3))
else: else:
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length)) new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))

View File

@ -2,11 +2,11 @@ from __future__ import annotations
import warnings import warnings
from io import BytesIO from io import BytesIO
from typing import Any, cast from typing import Any
import pytest import pytest
from PIL import Image, MpoImagePlugin from PIL import Image, ImageFile, MpoImagePlugin
from .helper import ( from .helper import (
assert_image_equal, assert_image_equal,
@ -20,11 +20,11 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"]
pytestmark = skip_unless_feature("jpg") pytestmark = skip_unless_feature("jpg")
def roundtrip(im: Image.Image, **options: Any) -> MpoImagePlugin.MpoImageFile: def roundtrip(im: Image.Image, **options: Any) -> ImageFile.ImageFile:
out = BytesIO() out = BytesIO()
im.save(out, "MPO", **options) im.save(out, "MPO", **options)
out.seek(0) out.seek(0)
return cast(MpoImagePlugin.MpoImageFile, Image.open(out)) return Image.open(out)
@pytest.mark.parametrize("test_file", test_files) @pytest.mark.parametrize("test_file", test_files)
@ -85,7 +85,9 @@ def test_exif(test_file: str) -> None:
im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif()) im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif())
for im in (im_original, im_reloaded): for im in (im_original, im_reloaded):
assert isinstance(im, MpoImagePlugin.MpoImageFile)
info = im._getexif() info = im._getexif()
assert info is not None
assert info[272] == "Nintendo 3DS" assert info[272] == "Nintendo 3DS"
assert info[296] == 2 assert info[296] == 2
assert info[34665] == 188 assert info[34665] == 188
@ -226,6 +228,12 @@ def test_eoferror() -> None:
im.seek(n_frames - 1) im.seek(n_frames - 1)
def test_adopt_jpeg() -> None:
with Image.open("Tests/images/hopper.jpg") as im:
with pytest.raises(ValueError):
MpoImagePlugin.MpoImageFile.adopt(im)
def test_ultra_hdr() -> None: def test_ultra_hdr() -> None:
with Image.open("Tests/images/ultrahdr.jpg") as im: with Image.open("Tests/images/ultrahdr.jpg") as im:
assert im.format == "JPEG" assert im.format == "JPEG"
@ -275,6 +283,8 @@ def test_save_all() -> None:
im_reloaded = roundtrip(im, save_all=True, append_images=[im2]) im_reloaded = roundtrip(im, save_all=True, append_images=[im2])
assert_image_equal(im, im_reloaded) assert_image_equal(im, im_reloaded)
assert isinstance(im_reloaded, MpoImagePlugin.MpoImageFile)
assert im_reloaded.mpinfo is not None
assert im_reloaded.mpinfo[45056] == b"0100" assert im_reloaded.mpinfo[45056] == b"0100"
im_reloaded.seek(1) im_reloaded.seek(1)

View File

@ -229,6 +229,7 @@ def test_pdf_append_fails_on_nonexistent_file() -> None:
def check_pdf_pages_consistency(pdf: PdfParser.PdfParser) -> None: def check_pdf_pages_consistency(pdf: PdfParser.PdfParser) -> None:
assert pdf.pages_ref is not None
pages_info = pdf.read_indirect(pdf.pages_ref) pages_info = pdf.read_indirect(pdf.pages_ref)
assert b"Parent" not in pages_info assert b"Parent" not in pages_info
assert b"Kids" in pages_info assert b"Kids" in pages_info

View File

@ -41,7 +41,7 @@ MAGIC = PngImagePlugin._MAGIC
def chunk(cid: bytes, *data: bytes) -> bytes: def chunk(cid: bytes, *data: bytes) -> bytes:
test_file = BytesIO() test_file = BytesIO()
PngImagePlugin.putchunk(*(test_file, cid) + data) PngImagePlugin.putchunk(test_file, cid, *data)
return test_file.getvalue() return test_file.getvalue()
@ -424,8 +424,10 @@ class TestFilePng:
im = roundtrip(im, pnginfo=info) im = roundtrip(im, pnginfo=info)
assert im.info == {"spam": "Eggs", "eggs": "Spam"} assert im.info == {"spam": "Eggs", "eggs": "Spam"}
assert im.text == {"spam": "Eggs", "eggs": "Spam"} assert im.text == {"spam": "Eggs", "eggs": "Spam"}
assert isinstance(im.text["spam"], PngImagePlugin.iTXt)
assert im.text["spam"].lang == "en" assert im.text["spam"].lang == "en"
assert im.text["spam"].tkey == "Spam" assert im.text["spam"].tkey == "Spam"
assert isinstance(im.text["eggs"], PngImagePlugin.iTXt)
assert im.text["eggs"].lang == "en" assert im.text["eggs"].lang == "en"
assert im.text["eggs"].tkey == "Eggs" assert im.text["eggs"].tkey == "Eggs"
@ -776,7 +778,7 @@ class TestFilePng:
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO() mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
sys.stdout = mystdout # type: ignore[assignment] sys.stdout = mystdout
with Image.open(TEST_PNG_FILE) as im: with Image.open(TEST_PNG_FILE) as im:
im.save(sys.stdout, "PNG") im.save(sys.stdout, "PNG")

View File

@ -373,7 +373,7 @@ def test_save_stdout(buffer: bool) -> None:
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO() mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
sys.stdout = mystdout # type: ignore[assignment] sys.stdout = mystdout
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
im.save(sys.stdout, "PPM") im.save(sys.stdout, "PPM")

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import os import os
from pathlib import Path from pathlib import Path
from typing import AnyStr
import pytest import pytest
@ -92,7 +93,7 @@ def test_textsize(request: pytest.FixtureRequest, tmp_path: Path) -> None:
def _test_high_characters( def _test_high_characters(
request: pytest.FixtureRequest, tmp_path: Path, message: str | bytes request: pytest.FixtureRequest, tmp_path: Path, message: AnyStr
) -> None: ) -> None:
tempname = save_font(request, tmp_path) tempname = save_font(request, tmp_path)
font = ImageFont.load(tempname) font = ImageFont.load(tempname)

View File

@ -90,6 +90,7 @@ class TestImageFile:
data = f.read() data = f.read()
with ImageFile.Parser() as p: with ImageFile.Parser() as p:
p.feed(data) p.feed(data)
assert p.image is not None
assert (48, 48) == p.image.size assert (48, 48) == p.image.size
@skip_unless_feature("webp") @skip_unless_feature("webp")
@ -103,6 +104,7 @@ class TestImageFile:
assert not p.image assert not p.image
p.feed(f.read()) p.feed(f.read())
assert p.image is not None
assert (128, 128) == p.image.size assert (128, 128) == p.image.size
@skip_unless_feature("zlib") @skip_unless_feature("zlib")
@ -125,7 +127,7 @@ class TestImageFile:
def test_raise_typeerror(self) -> None: def test_raise_typeerror(self) -> None:
with pytest.raises(TypeError): with pytest.raises(TypeError):
parser = ImageFile.Parser() parser = ImageFile.Parser()
parser.feed(1) parser.feed(1) # type: ignore[arg-type]
def test_negative_stride(self) -> None: def test_negative_stride(self) -> None:
with open("Tests/images/raw_negative_stride.bin", "rb") as f: with open("Tests/images/raw_negative_stride.bin", "rb") as f:
@ -303,7 +305,7 @@ class TestPyDecoder(CodecsTest):
im.load() im.load()
def test_decode(self) -> None: def test_decode(self) -> None:
decoder = ImageFile.PyDecoder(None) decoder = ImageFile.PyDecoder("")
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
decoder.decode(b"") decoder.decode(b"")
@ -381,7 +383,7 @@ class TestPyEncoder(CodecsTest):
) )
def test_encode(self) -> None: def test_encode(self) -> None:
encoder = ImageFile.PyEncoder(None) encoder = ImageFile.PyEncoder("")
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
encoder.encode(0) encoder.encode(0)
@ -393,8 +395,9 @@ class TestPyEncoder(CodecsTest):
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
encoder.encode_to_pyfd() encoder.encode_to_pyfd()
fh = BytesIO()
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
encoder.encode_to_file(None, None) encoder.encode_to_file(fh, 0)
def test_zero_height(self) -> None: def test_zero_height(self) -> None:
with pytest.raises(UnidentifiedImageError): with pytest.raises(UnidentifiedImageError):

View File

@ -717,14 +717,14 @@ def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
_check_text(font, "Tests/images/variation_adobe.png", 11) _check_text(font, "Tests/images/variation_adobe.png", 11)
for name in ["Bold", b"Bold"]: for name in ("Bold", b"Bold"):
font.set_variation_by_name(name) font.set_variation_by_name(name)
assert font.getname()[1] == "Bold" assert font.getname()[1] == "Bold"
_check_text(font, "Tests/images/variation_adobe_name.png", 16) _check_text(font, "Tests/images/variation_adobe_name.png", 16)
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
_check_text(font, "Tests/images/variation_tiny.png", 40) _check_text(font, "Tests/images/variation_tiny.png", 40)
for name in ["200", b"200"]: for name in ("200", b"200"):
font.set_variation_by_name(name) font.set_variation_by_name(name)
assert font.getname()[1] == "200" assert font.getname()[1] == "200"
_check_text(font, "Tests/images/variation_tiny_name.png", 40) _check_text(font, "Tests/images/variation_tiny_name.png", 40)

View File

@ -1,5 +1,9 @@
from __future__ import annotations from __future__ import annotations
from typing import Any
import pytest
from PIL import Image, ImageMath from PIL import Image, ImageMath
@ -19,7 +23,7 @@ I = Image.new("I", (1, 1), 4) # noqa: E741
A2 = A.resize((2, 2)) A2 = A.resize((2, 2))
B2 = B.resize((2, 2)) B2 = B.resize((2, 2))
images = {"A": A, "B": B, "F": F, "I": I} images: dict[str, Any] = {"A": A, "B": B, "F": F, "I": I}
def test_sanity() -> None: def test_sanity() -> None:
@ -30,13 +34,13 @@ def test_sanity() -> None:
== "I 3" == "I 3"
) )
assert ( assert (
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images)) pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], **images))
== "I 3" == "I 3"
) )
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval( ImageMath.lambda_eval(
lambda args: args["float"](args["A"]) + args["B"], images lambda args: args["float"](args["A"]) + args["B"], **images
) )
) )
== "F 3.0" == "F 3.0"
@ -44,42 +48,47 @@ def test_sanity() -> None:
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval( ImageMath.lambda_eval(
lambda args: args["int"](args["float"](args["A"]) + args["B"]), images lambda args: args["int"](args["float"](args["A"]) + args["B"]), **images
) )
) )
== "I 3" == "I 3"
) )
def test_options_deprecated() -> None:
with pytest.warns(DeprecationWarning):
assert ImageMath.lambda_eval(lambda args: 1, images) == 1
def test_ops() -> None: def test_ops() -> None:
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, images)) == "I -1" assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, **images)) == "I -1"
assert ( assert (
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images)) pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], **images))
== "I 3" == "I 3"
) )
assert ( assert (
pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], images)) pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], **images))
== "I -1" == "I -1"
) )
assert ( assert (
pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], images)) pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], **images))
== "I 2" == "I 2"
) )
assert ( assert (
pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], images)) pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], **images))
== "I 0" == "I 0"
) )
assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, images)) == "I 4" assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, **images)) == "I 4"
assert ( assert (
pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, images)) pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, **images))
== "I 2147483647" == "I 2147483647"
) )
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval( ImageMath.lambda_eval(
lambda args: args["float"](args["A"]) + args["B"], images lambda args: args["float"](args["A"]) + args["B"], **images
) )
) )
== "F 3.0" == "F 3.0"
@ -87,7 +96,7 @@ def test_ops() -> None:
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval( ImageMath.lambda_eval(
lambda args: args["float"](args["A"]) - args["B"], images lambda args: args["float"](args["A"]) - args["B"], **images
) )
) )
== "F -1.0" == "F -1.0"
@ -95,7 +104,7 @@ def test_ops() -> None:
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval( ImageMath.lambda_eval(
lambda args: args["float"](args["A"]) * args["B"], images lambda args: args["float"](args["A"]) * args["B"], **images
) )
) )
== "F 2.0" == "F 2.0"
@ -103,31 +112,33 @@ def test_ops() -> None:
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval( ImageMath.lambda_eval(
lambda args: args["float"](args["A"]) / args["B"], images lambda args: args["float"](args["A"]) / args["B"], **images
) )
) )
== "F 0.5" == "F 0.5"
) )
assert ( assert (
pixel(ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, images)) pixel(
ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, **images)
)
== "F 4.0" == "F 4.0"
) )
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, images) ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, **images)
) )
== "F 8589934592.0" == "F 8589934592.0"
) )
def test_logical() -> None: def test_logical() -> None:
assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], images)) == 0 assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], **images)) == 0
assert ( assert (
pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], images)) pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], **images))
== "L 2" == "L 2"
) )
assert ( assert (
pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], images)) pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], **images))
== "L 1" == "L 1"
) )
@ -136,7 +147,7 @@ def test_convert() -> None:
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval( ImageMath.lambda_eval(
lambda args: args["convert"](args["A"] + args["B"], "L"), images lambda args: args["convert"](args["A"] + args["B"], "L"), **images
) )
) )
== "L 3" == "L 3"
@ -144,7 +155,7 @@ def test_convert() -> None:
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval( ImageMath.lambda_eval(
lambda args: args["convert"](args["A"] + args["B"], "1"), images lambda args: args["convert"](args["A"] + args["B"], "1"), **images
) )
) )
== "1 0" == "1 0"
@ -152,7 +163,7 @@ def test_convert() -> None:
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval( ImageMath.lambda_eval(
lambda args: args["convert"](args["A"] + args["B"], "RGB"), images lambda args: args["convert"](args["A"] + args["B"], "RGB"), **images
) )
) )
== "RGB (3, 3, 3)" == "RGB (3, 3, 3)"
@ -163,7 +174,7 @@ def test_compare() -> None:
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval( ImageMath.lambda_eval(
lambda args: args["min"](args["A"], args["B"]), images lambda args: args["min"](args["A"], args["B"]), **images
) )
) )
== "I 1" == "I 1"
@ -171,13 +182,13 @@ def test_compare() -> None:
assert ( assert (
pixel( pixel(
ImageMath.lambda_eval( ImageMath.lambda_eval(
lambda args: args["max"](args["A"], args["B"]), images lambda args: args["max"](args["A"], args["B"]), **images
) )
) )
== "I 2" == "I 2"
) )
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, images)) == "I 1" assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, **images)) == "I 1"
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, images)) == "I 0" assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, **images)) == "I 0"
def test_one_image_larger() -> None: def test_one_image_larger() -> None:

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from typing import Any
import pytest import pytest
from PIL import Image, ImageMath from PIL import Image, ImageMath
@ -21,16 +23,16 @@ I = Image.new("I", (1, 1), 4) # noqa: E741
A2 = A.resize((2, 2)) A2 = A.resize((2, 2))
B2 = B.resize((2, 2)) B2 = B.resize((2, 2))
images = {"A": A, "B": B, "F": F, "I": I} images: dict[str, Any] = {"A": A, "B": B, "F": F, "I": I}
def test_sanity() -> None: def test_sanity() -> None:
assert ImageMath.unsafe_eval("1") == 1 assert ImageMath.unsafe_eval("1") == 1
assert ImageMath.unsafe_eval("1+A", A=2) == 3 assert ImageMath.unsafe_eval("1+A", A=2) == 3
assert pixel(ImageMath.unsafe_eval("A+B", A=A, B=B)) == "I 3" assert pixel(ImageMath.unsafe_eval("A+B", A=A, B=B)) == "I 3"
assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3" assert pixel(ImageMath.unsafe_eval("A+B", **images)) == "I 3"
assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0" assert pixel(ImageMath.unsafe_eval("float(A)+B", **images)) == "F 3.0"
assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", images)) == "I 3" assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", **images)) == "I 3"
def test_eval_deprecated() -> None: def test_eval_deprecated() -> None:
@ -38,23 +40,28 @@ def test_eval_deprecated() -> None:
assert ImageMath.eval("1") == 1 assert ImageMath.eval("1") == 1
def test_options_deprecated() -> None:
with pytest.warns(DeprecationWarning):
assert ImageMath.unsafe_eval("1", images) == 1
def test_ops() -> None: def test_ops() -> None:
assert pixel(ImageMath.unsafe_eval("-A", images)) == "I -1" assert pixel(ImageMath.unsafe_eval("-A", **images)) == "I -1"
assert pixel(ImageMath.unsafe_eval("+B", images)) == "L 2" assert pixel(ImageMath.unsafe_eval("+B", **images)) == "L 2"
assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3" assert pixel(ImageMath.unsafe_eval("A+B", **images)) == "I 3"
assert pixel(ImageMath.unsafe_eval("A-B", images)) == "I -1" assert pixel(ImageMath.unsafe_eval("A-B", **images)) == "I -1"
assert pixel(ImageMath.unsafe_eval("A*B", images)) == "I 2" assert pixel(ImageMath.unsafe_eval("A*B", **images)) == "I 2"
assert pixel(ImageMath.unsafe_eval("A/B", images)) == "I 0" assert pixel(ImageMath.unsafe_eval("A/B", **images)) == "I 0"
assert pixel(ImageMath.unsafe_eval("B**2", images)) == "I 4" assert pixel(ImageMath.unsafe_eval("B**2", **images)) == "I 4"
assert pixel(ImageMath.unsafe_eval("B**33", images)) == "I 2147483647" assert pixel(ImageMath.unsafe_eval("B**33", **images)) == "I 2147483647"
assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0" assert pixel(ImageMath.unsafe_eval("float(A)+B", **images)) == "F 3.0"
assert pixel(ImageMath.unsafe_eval("float(A)-B", images)) == "F -1.0" assert pixel(ImageMath.unsafe_eval("float(A)-B", **images)) == "F -1.0"
assert pixel(ImageMath.unsafe_eval("float(A)*B", images)) == "F 2.0" assert pixel(ImageMath.unsafe_eval("float(A)*B", **images)) == "F 2.0"
assert pixel(ImageMath.unsafe_eval("float(A)/B", images)) == "F 0.5" assert pixel(ImageMath.unsafe_eval("float(A)/B", **images)) == "F 0.5"
assert pixel(ImageMath.unsafe_eval("float(B)**2", images)) == "F 4.0" assert pixel(ImageMath.unsafe_eval("float(B)**2", **images)) == "F 4.0"
assert pixel(ImageMath.unsafe_eval("float(B)**33", images)) == "F 8589934592.0" assert pixel(ImageMath.unsafe_eval("float(B)**33", **images)) == "F 8589934592.0"
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -72,33 +79,33 @@ def test_prevent_exec(expression: str) -> None:
def test_prevent_double_underscores() -> None: def test_prevent_double_underscores() -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
ImageMath.unsafe_eval("1", {"__": None}) ImageMath.unsafe_eval("1", __=None)
def test_prevent_builtins() -> None: def test_prevent_builtins() -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
ImageMath.unsafe_eval("(lambda: exec('exit()'))()", {"exec": None}) ImageMath.unsafe_eval("(lambda: exec('exit()'))()", exec=None)
def test_logical() -> None: def test_logical() -> None:
assert pixel(ImageMath.unsafe_eval("not A", images)) == 0 assert pixel(ImageMath.unsafe_eval("not A", **images)) == 0
assert pixel(ImageMath.unsafe_eval("A and B", images)) == "L 2" assert pixel(ImageMath.unsafe_eval("A and B", **images)) == "L 2"
assert pixel(ImageMath.unsafe_eval("A or B", images)) == "L 1" assert pixel(ImageMath.unsafe_eval("A or B", **images)) == "L 1"
def test_convert() -> None: def test_convert() -> None:
assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", images)) == "L 3" assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", **images)) == "L 3"
assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", images)) == "1 0" assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", **images)) == "1 0"
assert ( assert (
pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", images)) == "RGB (3, 3, 3)" pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", **images)) == "RGB (3, 3, 3)"
) )
def test_compare() -> None: def test_compare() -> None:
assert pixel(ImageMath.unsafe_eval("min(A, B)", images)) == "I 1" assert pixel(ImageMath.unsafe_eval("min(A, B)", **images)) == "I 1"
assert pixel(ImageMath.unsafe_eval("max(A, B)", images)) == "I 2" assert pixel(ImageMath.unsafe_eval("max(A, B)", **images)) == "I 2"
assert pixel(ImageMath.unsafe_eval("A == 1", images)) == "I 1" assert pixel(ImageMath.unsafe_eval("A == 1", **images)) == "I 1"
assert pixel(ImageMath.unsafe_eval("A == 2", images)) == "I 0" assert pixel(ImageMath.unsafe_eval("A == 2", **images)) == "I 0"
def test_one_image_larger() -> None: def test_one_image_larger() -> None:

View File

@ -109,3 +109,6 @@ def test_bitmapimage() -> None:
# reloaded = ImageTk.getimage(im_tk) # reloaded = ImageTk.getimage(im_tk)
# assert_image_equal(reloaded, im) # assert_image_equal(reloaded, im)
with pytest.raises(ValueError):
ImageTk.BitmapImage()

View File

@ -57,6 +57,9 @@ class TestImageWinDib:
# Assert # Assert
assert dib.size == (128, 128) assert dib.size == (128, 128)
with pytest.raises(ValueError):
ImageWin.Dib(mode)
def test_dib_paste(self) -> None: def test_dib_paste(self) -> None:
# Arrange # Arrange
im = hopper() im = hopper()

View File

@ -59,7 +59,7 @@ def test_stdout(buffer: bool) -> None:
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO() mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
sys.stdout = mystdout # type: ignore[assignment] sys.stdout = mystdout
ps = PSDraw.PSDraw() ps = PSDraw.PSDraw()
_create_document(ps) _create_document(ps)

View File

@ -30,28 +30,6 @@ def test_is_not_path(tmp_path: Path) -> None:
assert not it_is_not assert not it_is_not
def test_is_directory() -> None:
# Arrange
directory = "Tests"
# Act
it_is = _util.is_directory(directory)
# Assert
assert it_is
def test_is_not_directory() -> None:
# Arrange
text = "abc"
# Act
it_is_not = _util.is_directory(text)
# Assert
assert not it_is_not
def test_deferred_error() -> None: def test_deferred_error() -> None:
# Arrange # Arrange

View File

@ -109,6 +109,15 @@ ImageDraw.getdraw hints parameter
The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated.
ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 11.0.0
The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and
:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more keyword
arguments can be used instead.
Removed features Removed features
---------------- ----------------

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -37,6 +37,9 @@ example, lets display the image we just loaded::
>>> im.show() >>> im.show()
.. image:: show_hopper.webp
:align: center
.. note:: .. note::
The standard version of :py:meth:`~PIL.Image.Image.show` is not very The standard version of :py:meth:`~PIL.Image.Image.show` is not very
@ -79,6 +82,9 @@ Convert files to JPEG
except OSError: except OSError:
print("cannot convert", infile) print("cannot convert", infile)
.. image:: ../../Tests/images/hopper.jpg
:align: center
A second argument can be supplied to the :py:meth:`~PIL.Image.Image.save` A second argument can be supplied to the :py:meth:`~PIL.Image.Image.save`
method which explicitly specifies a file format. If you use a non-standard method which explicitly specifies a file format. If you use a non-standard
extension, you must always specify the format this way: extension, you must always specify the format this way:
@ -103,6 +109,9 @@ Create JPEG thumbnails
except OSError: except OSError:
print("cannot create thumbnail for", infile) print("cannot create thumbnail for", infile)
.. image:: thumbnail_hopper.jpg
:align: center
It is important to note that the library doesnt decode or load the raster data It is important to note that the library doesnt decode or load the raster data
unless it really has to. When you open a file, the file header is read to unless it really has to. When you open a file, the file header is read to
determine the file format and extract things like mode, size, and other determine the file format and extract things like mode, size, and other
@ -140,16 +149,19 @@ Copying a subrectangle from an image
:: ::
box = (100, 100, 400, 400) box = (0, 0, 64, 64)
region = im.crop(box) region = im.crop(box)
The region is defined by a 4-tuple, where coordinates are (left, upper, right, The region is defined by a 4-tuple, where coordinates are (left, upper, right,
lower). The Python Imaging Library uses a coordinate system with (0, 0) in the lower). The Python Imaging Library uses a coordinate system with (0, 0) in the
upper left corner. Also note that coordinates refer to positions between the upper left corner. Also note that coordinates refer to positions between the
pixels, so the region in the above example is exactly 300x300 pixels. pixels, so the region in the above example is exactly 64x64 pixels.
The region could now be processed in a certain manner and pasted back. The region could now be processed in a certain manner and pasted back.
.. image:: cropped_hopper.webp
:align: center
Processing a subrectangle, and pasting it back Processing a subrectangle, and pasting it back
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -164,6 +176,9 @@ modes of the original image and the region do not need to match. If they dont
the region is automatically converted before being pasted (see the section on the region is automatically converted before being pasted (see the section on
:ref:`color-transforms` below for details). :ref:`color-transforms` below for details).
.. image:: pasted_hopper.webp
:align: center
Heres an additional example: Heres an additional example:
Rolling an image Rolling an image
@ -186,6 +201,9 @@ Rolling an image
return im return im
.. image:: rolled_hopper.webp
:align: center
Or if you would like to merge two images into a wider image: Or if you would like to merge two images into a wider image:
Merging images Merging images
@ -203,6 +221,9 @@ Merging images
return im return im
.. image:: merged_hopper.webp
:align: center
For more advanced tricks, the paste method can also take a transparency mask as For more advanced tricks, the paste method can also take a transparency mask as
an optional argument. In this mask, the value 255 indicates that the pasted an optional argument. In this mask, the value 255 indicates that the pasted
image is opaque in that position (that is, the pasted image should be used as image is opaque in that position (that is, the pasted image should be used as
@ -229,6 +250,9 @@ Note that for a single-band image, :py:meth:`~PIL.Image.Image.split` returns
the image itself. To work with individual color bands, you may want to convert the image itself. To work with individual color bands, you may want to convert
the image to “RGB” first. the image to “RGB” first.
.. image:: rebanded_hopper.webp
:align: center
Geometrical transforms Geometrical transforms
---------------------- ----------------------
@ -245,6 +269,9 @@ Simple geometry transforms
out = im.resize((128, 128)) out = im.resize((128, 128))
out = im.rotate(45) # degrees counter-clockwise out = im.rotate(45) # degrees counter-clockwise
.. image:: rotated_hopper_90.webp
:align: center
To rotate the image in 90 degree steps, you can either use the To rotate the image in 90 degree steps, you can either use the
:py:meth:`~PIL.Image.Image.rotate` method or the :py:meth:`~PIL.Image.Image.rotate` method or the
:py:meth:`~PIL.Image.Image.transpose` method. The latter can also be used to :py:meth:`~PIL.Image.Image.transpose` method. The latter can also be used to
@ -256,11 +283,38 @@ Transposing an image
:: ::
out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
.. image:: flip_left_right_hopper.webp
:align: center
::
out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM) out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
.. image:: flip_top_bottom_hopper.webp
:align: center
::
out = im.transpose(Image.Transpose.ROTATE_90) out = im.transpose(Image.Transpose.ROTATE_90)
.. image:: rotated_hopper_90.webp
:align: center
::
out = im.transpose(Image.Transpose.ROTATE_180) out = im.transpose(Image.Transpose.ROTATE_180)
.. image:: rotated_hopper_180.webp
:align: center
::
out = im.transpose(Image.Transpose.ROTATE_270) out = im.transpose(Image.Transpose.ROTATE_270)
.. image:: rotated_hopper_270.webp
:align: center
``transpose(ROTATE)`` operations can also be performed identically with ``transpose(ROTATE)`` operations can also be performed identically with
:py:meth:`~PIL.Image.Image.rotate` operations, provided the ``expand`` flag is :py:meth:`~PIL.Image.Image.rotate` operations, provided the ``expand`` flag is
true, to provide for the same changes to the image's size. true, to provide for the same changes to the image's size.
@ -278,7 +332,7 @@ choose to resize relative to a given size.
from PIL import Image, ImageOps from PIL import Image, ImageOps
size = (100, 150) size = (100, 150)
with Image.open("Tests/images/hopper.webp") as im: with Image.open("hopper.webp") as im:
ImageOps.contain(im, size).save("imageops_contain.webp") ImageOps.contain(im, size).save("imageops_contain.webp")
ImageOps.cover(im, size).save("imageops_cover.webp") ImageOps.cover(im, size).save("imageops_cover.webp")
ImageOps.fit(im, size).save("imageops_fit.webp") ImageOps.fit(im, size).save("imageops_fit.webp")
@ -342,6 +396,9 @@ Applying filters
from PIL import ImageFilter from PIL import ImageFilter
out = im.filter(ImageFilter.DETAIL) out = im.filter(ImageFilter.DETAIL)
.. image:: enhanced_hopper.webp
:align: center
Point Operations Point Operations
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
@ -355,8 +412,11 @@ Applying point transforms
:: ::
# multiply each pixel by 1.2 # multiply each pixel by 20
out = im.point(lambda i: i * 1.2) out = im.point(lambda i: i * 20)
.. image:: transformed_hopper.webp
:align: center
Using the above technique, you can quickly apply any simple expression to an Using the above technique, you can quickly apply any simple expression to an
image. You can also combine the :py:meth:`~PIL.Image.Image.point` and image. You can also combine the :py:meth:`~PIL.Image.Image.point` and
@ -388,6 +448,9 @@ Note the syntax used to create the mask::
imout = im.point(lambda i: expression and 255) imout = im.point(lambda i: expression and 255)
.. image:: masked_hopper.webp
:align: center
Python only evaluates the portion of a logical expression as is necessary to Python only evaluates the portion of a logical expression as is necessary to
determine the outcome, and returns the last value examined as the result of the determine the outcome, and returns the last value examined as the result of the
expression. So if the expression above is false (0), Python does not look at expression. So if the expression above is false (0), Python does not look at
@ -412,6 +475,10 @@ Enhancing images
enh = ImageEnhance.Contrast(im) enh = ImageEnhance.Contrast(im)
enh.enhance(1.3).show("30% more contrast") enh.enhance(1.3).show("30% more contrast")
.. image:: contrasted_hopper.jpg
:align: center
Image sequences Image sequences
--------------- ---------------
@ -444,10 +511,43 @@ Reading sequences
As seen in this example, youll get an :py:exc:`EOFError` exception when the As seen in this example, youll get an :py:exc:`EOFError` exception when the
sequence ends. sequence ends.
Writing sequences
^^^^^^^^^^^^^^^^^
You can create animated GIFs with Pillow, e.g.
::
from PIL import Image
# List of image filenames
image_filenames = [
"hopper.jpg",
"rotated_hopper_270.jpg",
"rotated_hopper_180.jpg",
"rotated_hopper_90.jpg",
]
# Open images and create a list
images = [Image.open(filename) for filename in image_filenames]
# Save the images as an animated GIF
images[0].save(
"animated_hopper.gif",
save_all=True,
append_images=images[1:],
duration=500, # duration of each frame in milliseconds
loop=0, # loop forever
)
.. image:: animated_hopper.gif
:align: center
The following class lets you use the for-statement to loop over the sequence: The following class lets you use the for-statement to loop over the sequence:
Using the ImageSequence Iterator class Using the :py:class:`~PIL.ImageSequence.Iterator` class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:: ::
@ -467,25 +567,61 @@ Drawing PostScript
:: ::
from PIL import Image from PIL import Image, PSDraw
from PIL import PSDraw import os
with Image.open("hopper.ppm") as im: # Define the PostScript file
title = "hopper" ps_file = open("hopper.ps", "wb")
box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points
ps = PSDraw.PSDraw() # default is sys.stdout or sys.stdout.buffer # Create a PSDraw object
ps.begin_document(title) ps = PSDraw.PSDraw(ps_file)
# draw the image (75 dpi) # Start the document
ps.image(box, im, 75) ps.begin_document()
ps.rectangle(box)
# draw title # Set the text to be drawn
ps.setfont("HelveticaNarrow-Bold", 36) text = "Hopper"
ps.text((3 * 72, 4 * 72), title)
# Define the PostScript font
font_name = "Helvetica-Narrow-Bold"
font_size = 36
# Calculate text size (approximation as PSDraw doesn't provide direct method)
# Assuming average character width as 0.6 of the font size
text_width = len(text) * font_size * 0.6
text_height = font_size
# Set the position (top-center)
page_width, page_height = 595, 842 # A4 size in points
text_x = (page_width - text_width) // 2
text_y = page_height - text_height - 50 # Distance from the top of the page
# Load the image
image_path = "hopper.ppm" # Update this with your image path
with Image.open(image_path) as im:
# Resize the image if it's too large
im.thumbnail((page_width - 100, page_height // 2))
# Define the box where the image will be placed
img_x = (page_width - im.width) // 2
img_y = text_y + text_height - 200 # 200 points below the text
# Draw the image (75 dpi)
ps.image((img_x, img_y, img_x + im.width, img_y + im.height), im, 75)
# Draw the text
ps.setfont(font_name, font_size)
ps.text((text_x, text_y), text)
# End the document
ps.end_document() ps.end_document()
ps_file.close()
.. image:: hopper_ps.webp
.. note::
PostScript converted to PDF for display purposes
More on reading images More on reading images
---------------------- ----------------------
@ -553,7 +689,7 @@ Reading from a tar archive
from PIL import Image, TarIO from PIL import Image, TarIO
fp = TarIO.TarIO("Tests/images/hopper.tar", "hopper.jpg") fp = TarIO.TarIO("hopper.tar", "hopper.jpg")
im = Image.open(fp) im = Image.open(fp)
@ -568,7 +704,6 @@ in the current directory can be saved as JPEGs at reduced quality.
import glob import glob
from PIL import Image from PIL import Image
def compress_image(source_path, dest_path): def compress_image(source_path, dest_path):
with Image.open(source_path) as img: with Image.open(source_path) as img:
if img.mode != "RGB": if img.mode != "RGB":

View File

@ -37,6 +37,11 @@ Example: Parse an image
Classes Classes
------- -------
.. autoclass:: PIL.ImageFile._Tile()
:member-order: bysource
:members:
:show-inheritance:
.. autoclass:: PIL.ImageFile.Parser() .. autoclass:: PIL.ImageFile.Parser()
:members: :members:

View File

@ -91,3 +91,11 @@ Constants
Set to 1,000,000, to protect against potential DOS attacks. Pillow will Set to 1,000,000, to protect against potential DOS attacks. Pillow will
raise a :py:exc:`ValueError` if the number of characters is over this limit. The raise a :py:exc:`ValueError` if the number of characters is over this limit. The
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``. check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
Dictionaries
------------
.. autoclass:: Axis
:members:
:undoc-members:
:show-inheritance:

View File

@ -31,20 +31,21 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
b=im2 b=im2
) )
.. py:function:: lambda_eval(expression, options) .. py:function:: lambda_eval(expression, options, **kw)
Returns the result of an image function. Returns the result of an image function.
:param expression: A function that receives a dictionary. :param expression: A function that receives a dictionary.
:param options: Values to add to the function's dictionary, mapping image :param options: Values to add to the function's dictionary. Note that the names
names to Image instances. You can use one or more keyword must be valid Python identifiers. Deprecated.
arguments instead of a dictionary, as shown in the above You can instead use one or more keyword arguments, as
example. Note that the names must be valid Python shown in the above example.
identifiers. :param \**kw: Values to add to the function's dictionary, mapping image names to
Image instances.
:return: An image, an integer value, a floating point value, :return: An image, an integer value, a floating point value,
or a pixel tuple, depending on the expression. or a pixel tuple, depending on the expression.
.. py:function:: unsafe_eval(expression, options) .. py:function:: unsafe_eval(expression, options, **kw)
Evaluates an image expression. Evaluates an image expression.
@ -61,11 +62,12 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
:param expression: A string which uses the standard Python expression :param expression: A string which uses the standard Python expression
syntax. In addition to the standard operators, you can syntax. In addition to the standard operators, you can
also use the functions described below. also use the functions described below.
:param options: Values to add to the function's dictionary, mapping image :param options: Values to add to the evaluation context. Note that the names must
names to Image instances. You can use one or more keyword be valid Python identifiers. Deprecated.
arguments instead of a dictionary, as shown in the above You can instead use one or more keyword arguments, as
example. Note that the names must be valid Python shown in the above example.
identifiers. :param \**kw: Values to add to the evaluation context, mapping image names to Image
instances.
:return: An image, an integer value, a floating point value, :return: An image, an integer value, a floating point value,
or a pixel tuple, depending on the expression. or a pixel tuple, depending on the expression.

View File

@ -78,3 +78,7 @@ on some Python versions.
An internal interface module previously known as :mod:`~PIL._imaging`, An internal interface module previously known as :mod:`~PIL._imaging`,
implemented in :file:`_imaging.c`. implemented in :file:`_imaging.c`.
.. py:class:: ImagingCore
A representation of the image data.

View File

@ -43,10 +43,12 @@ similarly removed.
Deprecations Deprecations
============ ============
TODO ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TODO The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and
:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more
keyword arguments can be used instead.
API Changes API Changes
=========== ===========

View File

@ -159,7 +159,4 @@ exclude = [
'^Tests/oss-fuzz/fuzz_font.py$', '^Tests/oss-fuzz/fuzz_font.py$',
'^Tests/oss-fuzz/fuzz_pillow.py$', '^Tests/oss-fuzz/fuzz_pillow.py$',
'^Tests/test_qt_image_qapplication.py$', '^Tests/test_qt_image_qapplication.py$',
'^Tests/test_font_pcf_charsets.py$',
'^Tests/test_font_pcf.py$',
'^Tests/test_file_tar.py$',
] ]

View File

@ -313,6 +313,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4)) self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
def _safe_read(self, length: int) -> bytes: def _safe_read(self, length: int) -> bytes:
assert self.fd is not None
return ImageFile._safe_read(self.fd, length) return ImageFile._safe_read(self.fd, length)
def _read_palette(self) -> list[tuple[int, int, int, int]]: def _read_palette(self) -> list[tuple[int, int, int, int]]:

View File

@ -25,7 +25,7 @@
from __future__ import annotations from __future__ import annotations
import os import os
from typing import IO from typing import IO, Any
from . import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from ._binary import i16le as i16 from ._binary import i16le as i16
@ -72,16 +72,20 @@ class BmpImageFile(ImageFile.ImageFile):
for k, v in COMPRESSIONS.items(): for k, v in COMPRESSIONS.items():
vars()[k] = v vars()[k] = v
def _bitmap(self, header=0, offset=0): def _bitmap(self, header: int = 0, offset: int = 0) -> None:
"""Read relevant info about the BMP""" """Read relevant info about the BMP"""
read, seek = self.fp.read, self.fp.seek read, seek = self.fp.read, self.fp.seek
if header: if header:
seek(header) seek(header)
# read bmp header size @offset 14 (this is part of the header size) # read bmp header size @offset 14 (this is part of the header size)
file_info = {"header_size": i32(read(4)), "direction": -1} file_info: dict[str, bool | int | tuple[int, ...]] = {
"header_size": i32(read(4)),
"direction": -1,
}
# -------------------- If requested, read header at a specific position # -------------------- If requested, read header at a specific position
# read the rest of the bmp header, without its size # read the rest of the bmp header, without its size
assert isinstance(file_info["header_size"], int)
header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4) header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
# ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1 # ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1
@ -92,7 +96,7 @@ class BmpImageFile(ImageFile.ImageFile):
file_info["height"] = i16(header_data, 2) file_info["height"] = i16(header_data, 2)
file_info["planes"] = i16(header_data, 4) file_info["planes"] = i16(header_data, 4)
file_info["bits"] = i16(header_data, 6) file_info["bits"] = i16(header_data, 6)
file_info["compression"] = self.RAW file_info["compression"] = self.COMPRESSIONS["RAW"]
file_info["palette_padding"] = 3 file_info["palette_padding"] = 3
# --------------------------------------------- Windows Bitmap v3 to v5 # --------------------------------------------- Windows Bitmap v3 to v5
@ -122,8 +126,9 @@ class BmpImageFile(ImageFile.ImageFile):
) )
file_info["colors"] = i32(header_data, 28) file_info["colors"] = i32(header_data, 28)
file_info["palette_padding"] = 4 file_info["palette_padding"] = 4
assert isinstance(file_info["pixels_per_meter"], tuple)
self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"]) self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
if file_info["compression"] == self.BITFIELDS: if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]:
masks = ["r_mask", "g_mask", "b_mask"] masks = ["r_mask", "g_mask", "b_mask"]
if len(header_data) >= 48: if len(header_data) >= 48:
if len(header_data) >= 52: if len(header_data) >= 52:
@ -144,6 +149,10 @@ class BmpImageFile(ImageFile.ImageFile):
file_info["a_mask"] = 0x0 file_info["a_mask"] = 0x0
for mask in masks: for mask in masks:
file_info[mask] = i32(read(4)) file_info[mask] = i32(read(4))
assert isinstance(file_info["r_mask"], int)
assert isinstance(file_info["g_mask"], int)
assert isinstance(file_info["b_mask"], int)
assert isinstance(file_info["a_mask"], int)
file_info["rgb_mask"] = ( file_info["rgb_mask"] = (
file_info["r_mask"], file_info["r_mask"],
file_info["g_mask"], file_info["g_mask"],
@ -164,24 +173,26 @@ class BmpImageFile(ImageFile.ImageFile):
self._size = file_info["width"], file_info["height"] self._size = file_info["width"], file_info["height"]
# ------- If color count was not found in the header, compute from bits # ------- If color count was not found in the header, compute from bits
assert isinstance(file_info["bits"], int)
file_info["colors"] = ( file_info["colors"] = (
file_info["colors"] file_info["colors"]
if file_info.get("colors", 0) if file_info.get("colors", 0)
else (1 << file_info["bits"]) else (1 << file_info["bits"])
) )
assert isinstance(file_info["colors"], int)
if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8: if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8:
offset += 4 * file_info["colors"] offset += 4 * file_info["colors"]
# ---------------------- Check bit depth for unusual unsupported values # ---------------------- Check bit depth for unusual unsupported values
self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) self._mode, raw_mode = BIT2MODE.get(file_info["bits"], ("", ""))
if self.mode is None: if not self.mode:
msg = f"Unsupported BMP pixel depth ({file_info['bits']})" msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
raise OSError(msg) raise OSError(msg)
# ---------------- Process BMP with Bitfields compression (not palette) # ---------------- Process BMP with Bitfields compression (not palette)
decoder_name = "raw" decoder_name = "raw"
if file_info["compression"] == self.BITFIELDS: if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]:
SUPPORTED = { SUPPORTED: dict[int, list[tuple[int, ...]]] = {
32: [ 32: [
(0xFF0000, 0xFF00, 0xFF, 0x0), (0xFF0000, 0xFF00, 0xFF, 0x0),
(0xFF000000, 0xFF0000, 0xFF00, 0x0), (0xFF000000, 0xFF0000, 0xFF00, 0x0),
@ -213,12 +224,14 @@ class BmpImageFile(ImageFile.ImageFile):
file_info["bits"] == 32 file_info["bits"] == 32
and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]] and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
): ):
assert isinstance(file_info["rgba_mask"], tuple)
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])] raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
self._mode = "RGBA" if "A" in raw_mode else self.mode self._mode = "RGBA" if "A" in raw_mode else self.mode
elif ( elif (
file_info["bits"] in (24, 16) file_info["bits"] in (24, 16)
and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]] and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
): ):
assert isinstance(file_info["rgb_mask"], tuple)
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])] raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
else: else:
msg = "Unsupported BMP bitfields layout" msg = "Unsupported BMP bitfields layout"
@ -226,10 +239,13 @@ class BmpImageFile(ImageFile.ImageFile):
else: else:
msg = "Unsupported BMP bitfields layout" msg = "Unsupported BMP bitfields layout"
raise OSError(msg) raise OSError(msg)
elif file_info["compression"] == self.RAW: elif file_info["compression"] == self.COMPRESSIONS["RAW"]:
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
raw_mode, self._mode = "BGRA", "RGBA" raw_mode, self._mode = "BGRA", "RGBA"
elif file_info["compression"] in (self.RLE8, self.RLE4): elif file_info["compression"] in (
self.COMPRESSIONS["RLE8"],
self.COMPRESSIONS["RLE4"],
):
decoder_name = "bmp_rle" decoder_name = "bmp_rle"
else: else:
msg = f"Unsupported BMP compression ({file_info['compression']})" msg = f"Unsupported BMP compression ({file_info['compression']})"
@ -242,6 +258,7 @@ class BmpImageFile(ImageFile.ImageFile):
msg = f"Unsupported BMP Palette size ({file_info['colors']})" msg = f"Unsupported BMP Palette size ({file_info['colors']})"
raise OSError(msg) raise OSError(msg)
else: else:
assert isinstance(file_info["palette_padding"], int)
padding = file_info["palette_padding"] padding = file_info["palette_padding"]
palette = read(padding * file_info["colors"]) palette = read(padding * file_info["colors"])
grayscale = True grayscale = True
@ -269,10 +286,11 @@ class BmpImageFile(ImageFile.ImageFile):
# ---------------------------- Finally set the tile data for the plugin # ---------------------------- Finally set the tile data for the plugin
self.info["compression"] = file_info["compression"] self.info["compression"] = file_info["compression"]
args = [raw_mode] args: list[Any] = [raw_mode]
if decoder_name == "bmp_rle": if decoder_name == "bmp_rle":
args.append(file_info["compression"] == self.RLE4) args.append(file_info["compression"] == self.COMPRESSIONS["RLE4"])
else: else:
assert isinstance(file_info["width"], int)
args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3)) args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
args.append(file_info["direction"]) args.append(file_info["direction"])
self.tile = [ self.tile = [

View File

@ -16,10 +16,11 @@
from __future__ import annotations from __future__ import annotations
import io import io
from typing import IO, AnyStr, Generic, Literal from collections.abc import Iterable
from typing import IO, AnyStr, NoReturn
class ContainerIO(Generic[AnyStr]): class ContainerIO(IO[AnyStr]):
""" """
A file object that provides read access to a part of an existing A file object that provides read access to a part of an existing
file (for example a TAR file). file (for example a TAR file).
@ -45,7 +46,10 @@ class ContainerIO(Generic[AnyStr]):
def isatty(self) -> bool: def isatty(self) -> bool:
return False return False
def seek(self, offset: int, mode: Literal[0, 1, 2] = io.SEEK_SET) -> None: def seekable(self) -> bool:
return True
def seek(self, offset: int, mode: int = io.SEEK_SET) -> int:
""" """
Move file pointer. Move file pointer.
@ -53,6 +57,7 @@ class ContainerIO(Generic[AnyStr]):
:param mode: Starting position. Use 0 for beginning of region, 1 :param mode: Starting position. Use 0 for beginning of region, 1
for current offset, and 2 for end of region. You cannot move for current offset, and 2 for end of region. You cannot move
the pointer outside the defined region. the pointer outside the defined region.
:returns: Offset from start of region, in bytes.
""" """
if mode == 1: if mode == 1:
self.pos = self.pos + offset self.pos = self.pos + offset
@ -63,6 +68,7 @@ class ContainerIO(Generic[AnyStr]):
# clamp # clamp
self.pos = max(0, min(self.pos, self.length)) self.pos = max(0, min(self.pos, self.length))
self.fh.seek(self.offset + self.pos) self.fh.seek(self.offset + self.pos)
return self.pos
def tell(self) -> int: def tell(self) -> int:
""" """
@ -72,27 +78,32 @@ class ContainerIO(Generic[AnyStr]):
""" """
return self.pos return self.pos
def read(self, n: int = 0) -> AnyStr: def readable(self) -> bool:
return True
def read(self, n: int = -1) -> AnyStr:
""" """
Read data. Read data.
:param n: Number of bytes to read. If omitted or zero, :param n: Number of bytes to read. If omitted, zero or negative,
read until end of region. read until end of region.
:returns: An 8-bit string. :returns: An 8-bit string.
""" """
if n: if n > 0:
n = min(n, self.length - self.pos) n = min(n, self.length - self.pos)
else: else:
n = self.length - self.pos n = self.length - self.pos
if not n: # EOF if n <= 0: # EOF
return b"" if "b" in self.fh.mode else "" # type: ignore[return-value] return b"" if "b" in self.fh.mode else "" # type: ignore[return-value]
self.pos = self.pos + n self.pos = self.pos + n
return self.fh.read(n) return self.fh.read(n)
def readline(self) -> AnyStr: def readline(self, n: int = -1) -> AnyStr:
""" """
Read a line of text. Read a line of text.
:param n: Number of bytes to read. If omitted, zero or negative,
read until end of line.
:returns: An 8-bit string. :returns: An 8-bit string.
""" """
s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment] s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
@ -102,14 +113,16 @@ class ContainerIO(Generic[AnyStr]):
if not c: if not c:
break break
s = s + c s = s + c
if c == newline_character: if c == newline_character or len(s) == n:
break break
return s return s
def readlines(self) -> list[AnyStr]: def readlines(self, n: int | None = -1) -> list[AnyStr]:
""" """
Read multiple lines of text. Read multiple lines of text.
:param n: Number of lines to read. If omitted, zero, negative or None,
read until end of region.
:returns: A list of 8-bit strings. :returns: A list of 8-bit strings.
""" """
lines = [] lines = []
@ -118,4 +131,43 @@ class ContainerIO(Generic[AnyStr]):
if not s: if not s:
break break
lines.append(s) lines.append(s)
if len(lines) == n:
break
return lines return lines
def writable(self) -> bool:
return False
def write(self, b: AnyStr) -> NoReturn:
raise NotImplementedError()
def writelines(self, lines: Iterable[AnyStr]) -> NoReturn:
raise NotImplementedError()
def truncate(self, size: int | None = None) -> int:
raise NotImplementedError()
def __enter__(self) -> ContainerIO[AnyStr]:
return self
def __exit__(self, *args: object) -> None:
self.close()
def __iter__(self) -> ContainerIO[AnyStr]:
return self
def __next__(self) -> AnyStr:
line = self.readline()
if not line:
msg = "end of region"
raise StopIteration(msg)
return line
def fileno(self) -> int:
return self.fh.fileno()
def flush(self) -> None:
self.fh.flush()
def close(self) -> None:
self.fh.close()

View File

@ -65,7 +65,7 @@ def has_ghostscript() -> bool:
return gs_binary is not False return gs_binary is not False
def Ghostscript(tile, size, fp, scale=1, transparency=False): def Ghostscript(tile, size, fp, scale=1, transparency: bool = False) -> Image.Image:
"""Render an image using Ghostscript""" """Render an image using Ghostscript"""
global gs_binary global gs_binary
if not has_ghostscript(): if not has_ghostscript():

View File

@ -25,7 +25,7 @@ from __future__ import annotations
import warnings import warnings
from io import BytesIO from io import BytesIO
from math import ceil, log from math import ceil, log
from typing import IO from typing import IO, NamedTuple
from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
from ._binary import i16le as i16 from ._binary import i16le as i16
@ -119,8 +119,22 @@ def _accept(prefix: bytes) -> bool:
return prefix[:4] == _MAGIC return prefix[:4] == _MAGIC
class IconHeader(NamedTuple):
width: int
height: int
nb_color: int
reserved: int
planes: int
bpp: int
size: int
offset: int
dim: tuple[int, int]
square: int
color_depth: int
class IcoFile: class IcoFile:
def __init__(self, buf) -> None: def __init__(self, buf: IO[bytes]) -> None:
""" """
Parse image from file-like object containing ico file data Parse image from file-like object containing ico file data
""" """
@ -141,51 +155,44 @@ class IcoFile:
for i in range(self.nb_items): for i in range(self.nb_items):
s = buf.read(16) s = buf.read(16)
icon_header = {
"width": s[0],
"height": s[1],
"nb_color": s[2], # No. of colors in image (0 if >=8bpp)
"reserved": s[3],
"planes": i16(s, 4),
"bpp": i16(s, 6),
"size": i32(s, 8),
"offset": i32(s, 12),
}
# See Wikipedia # See Wikipedia
for j in ("width", "height"): width = s[0] or 256
if not icon_header[j]: height = s[1] or 256
icon_header[j] = 256
# No. of colors in image (0 if >=8bpp)
nb_color = s[2]
bpp = i16(s, 6)
icon_header = IconHeader(
width=width,
height=height,
nb_color=nb_color,
reserved=s[3],
planes=i16(s, 4),
bpp=i16(s, 6),
size=i32(s, 8),
offset=i32(s, 12),
dim=(width, height),
square=width * height,
# See Wikipedia notes about color depth. # See Wikipedia notes about color depth.
# We need this just to differ images with equal sizes # We need this just to differ images with equal sizes
icon_header["color_depth"] = ( color_depth=bpp or (nb_color != 0 and ceil(log(nb_color, 2))) or 256,
icon_header["bpp"]
or (
icon_header["nb_color"] != 0
and ceil(log(icon_header["nb_color"], 2))
) )
or 256
)
icon_header["dim"] = (icon_header["width"], icon_header["height"])
icon_header["square"] = icon_header["width"] * icon_header["height"]
self.entry.append(icon_header) self.entry.append(icon_header)
self.entry = sorted(self.entry, key=lambda x: x["color_depth"]) self.entry = sorted(self.entry, key=lambda x: x.color_depth)
# ICO images are usually squares # ICO images are usually squares
self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True) self.entry = sorted(self.entry, key=lambda x: x.square, reverse=True)
def sizes(self) -> set[tuple[int, int]]: def sizes(self) -> set[tuple[int, int]]:
""" """
Get a list of all available icon sizes and color depths. Get a set of all available icon sizes and color depths.
""" """
return {(h["width"], h["height"]) for h in self.entry} return {(h.width, h.height) for h in self.entry}
def getentryindex(self, size: tuple[int, int], bpp: int | bool = False) -> int: def getentryindex(self, size: tuple[int, int], bpp: int | bool = False) -> int:
for i, h in enumerate(self.entry): for i, h in enumerate(self.entry):
if size == h["dim"] and (bpp is False or bpp == h["color_depth"]): if size == h.dim and (bpp is False or bpp == h.color_depth):
return i return i
return 0 return 0
@ -202,9 +209,9 @@ class IcoFile:
header = self.entry[idx] header = self.entry[idx]
self.buf.seek(header["offset"]) self.buf.seek(header.offset)
data = self.buf.read(8) data = self.buf.read(8)
self.buf.seek(header["offset"]) self.buf.seek(header.offset)
im: Image.Image im: Image.Image
if data[:8] == PngImagePlugin._MAGIC: if data[:8] == PngImagePlugin._MAGIC:
@ -222,8 +229,7 @@ class IcoFile:
im.tile[0] = d, (0, 0) + im.size, o, a im.tile[0] = d, (0, 0) + im.size, o, a
# figure out where AND mask image starts # figure out where AND mask image starts
bpp = header["bpp"] if header.bpp == 32:
if 32 == bpp:
# 32-bit color depth icon image allows semitransparent areas # 32-bit color depth icon image allows semitransparent areas
# PIL's DIB format ignores transparency bits, recover them. # PIL's DIB format ignores transparency bits, recover them.
# The DIB is packed in BGRX byte order where X is the alpha # The DIB is packed in BGRX byte order where X is the alpha
@ -253,7 +259,7 @@ class IcoFile:
# padded row size * height / bits per char # padded row size * height / bits per char
total_bytes = int((w * im.size[1]) / 8) total_bytes = int((w * im.size[1]) / 8)
and_mask_offset = header["offset"] + header["size"] - total_bytes and_mask_offset = header.offset + header.size - total_bytes
self.buf.seek(and_mask_offset) self.buf.seek(and_mask_offset)
mask_data = self.buf.read(total_bytes) mask_data = self.buf.read(total_bytes)
@ -307,7 +313,7 @@ class IcoImageFile(ImageFile.ImageFile):
def _open(self) -> None: def _open(self) -> None:
self.ico = IcoFile(self.fp) self.ico = IcoFile(self.fp)
self.info["sizes"] = self.ico.sizes() self.info["sizes"] = self.ico.sizes()
self.size = self.ico.entry[0]["dim"] self.size = self.ico.entry[0].dim
self.load() self.load()
@property @property

View File

@ -38,7 +38,7 @@ import struct
import sys import sys
import tempfile import tempfile
import warnings import warnings
from collections.abc import Callable, MutableMapping, Sequence from collections.abc import Callable, Iterator, MutableMapping, Sequence
from enum import IntEnum from enum import IntEnum
from types import ModuleType from types import ModuleType
from typing import ( from typing import (
@ -218,6 +218,8 @@ if hasattr(core, "DEFAULT_STRATEGY"):
# Registries # Registries
if TYPE_CHECKING: if TYPE_CHECKING:
from xml.etree.ElementTree import Element
from . import ImageFile, ImagePalette from . import ImageFile, ImagePalette
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
ID: list[str] = [] ID: list[str] = []
@ -241,9 +243,9 @@ ENCODERS: dict[str, type[ImageFile.PyEncoder]] = {}
_ENDIAN = "<" if sys.byteorder == "little" else ">" _ENDIAN = "<" if sys.byteorder == "little" else ">"
def _conv_type_shape(im): def _conv_type_shape(im: Image) -> tuple[tuple[int, ...], str]:
m = ImageMode.getmode(im.mode) m = ImageMode.getmode(im.mode)
shape = (im.height, im.width) shape: tuple[int, ...] = (im.height, im.width)
extra = len(m.bands) extra = len(m.bands)
if extra != 1: if extra != 1:
shape += (extra,) shape += (extra,)
@ -466,40 +468,40 @@ def _getencoder(
class _E: class _E:
def __init__(self, scale, offset) -> None: def __init__(self, scale: float, offset: float) -> None:
self.scale = scale self.scale = scale
self.offset = offset self.offset = offset
def __neg__(self): def __neg__(self) -> _E:
return _E(-self.scale, -self.offset) return _E(-self.scale, -self.offset)
def __add__(self, other): def __add__(self, other: _E | float) -> _E:
if isinstance(other, _E): if isinstance(other, _E):
return _E(self.scale + other.scale, self.offset + other.offset) return _E(self.scale + other.scale, self.offset + other.offset)
return _E(self.scale, self.offset + other) return _E(self.scale, self.offset + other)
__radd__ = __add__ __radd__ = __add__
def __sub__(self, other): def __sub__(self, other: _E | float) -> _E:
return self + -other return self + -other
def __rsub__(self, other): def __rsub__(self, other: _E | float) -> _E:
return other + -self return other + -self
def __mul__(self, other): def __mul__(self, other: _E | float) -> _E:
if isinstance(other, _E): if isinstance(other, _E):
return NotImplemented return NotImplemented
return _E(self.scale * other, self.offset * other) return _E(self.scale * other, self.offset * other)
__rmul__ = __mul__ __rmul__ = __mul__
def __truediv__(self, other): def __truediv__(self, other: _E | float) -> _E:
if isinstance(other, _E): if isinstance(other, _E):
return NotImplemented return NotImplemented
return _E(self.scale / other, self.offset / other) return _E(self.scale / other, self.offset / other)
def _getscaleoffset(expr): def _getscaleoffset(expr) -> tuple[float, float]:
a = expr(_E(1, 0)) a = expr(_E(1, 0))
return (a.scale, a.offset) if isinstance(a, _E) else (0, a) return (a.scale, a.offset) if isinstance(a, _E) else (0, a)
@ -728,9 +730,9 @@ class Image:
return self._repr_image("JPEG") return self._repr_image("JPEG")
@property @property
def __array_interface__(self): def __array_interface__(self) -> dict[str, str | bytes | int | tuple[int, ...]]:
# numpy array interface support # numpy array interface support
new = {"version": 3} new: dict[str, str | bytes | int | tuple[int, ...]] = {"version": 3}
try: try:
if self.mode == "1": if self.mode == "1":
# Binary images need to be extended from bits to bytes # Binary images need to be extended from bits to bytes
@ -752,11 +754,11 @@ class Image:
new["shape"], new["typestr"] = _conv_type_shape(self) new["shape"], new["typestr"] = _conv_type_shape(self)
return new return new
def __getstate__(self): def __getstate__(self) -> list[Any]:
im_data = self.tobytes() # load image first im_data = self.tobytes() # load image first
return [self.info, self.mode, self.size, self.getpalette(), im_data] return [self.info, self.mode, self.size, self.getpalette(), im_data]
def __setstate__(self, state) -> None: def __setstate__(self, state: list[Any]) -> None:
self._prepare() self._prepare()
info, mode, size, palette, data = state info, mode, size, palette, data = state
self.info = info self.info = info
@ -1428,7 +1430,7 @@ class Image:
return out return out
return self.im.getcolors(maxcolors) return self.im.getcolors(maxcolors)
def getdata(self, band: int | None = None): def getdata(self, band: int | None = None) -> core.ImagingCore:
""" """
Returns the contents of this image as a sequence object Returns the contents of this image as a sequence object
containing pixel values. The sequence object is flattened, so containing pixel values. The sequence object is flattened, so
@ -1477,8 +1479,8 @@ class Image:
def get_name(tag: str) -> str: def get_name(tag: str) -> str:
return re.sub("^{[^}]+}", "", tag) return re.sub("^{[^}]+}", "", tag)
def get_value(element): def get_value(element: Element) -> str | dict[str, Any] | None:
value = {get_name(k): v for k, v in element.attrib.items()} value: dict[str, Any] = {get_name(k): v for k, v in element.attrib.items()}
children = list(element) children = list(element)
if children: if children:
for child in children: for child in children:
@ -1691,7 +1693,9 @@ class Image:
x, y = self.im.getprojection() x, y = self.im.getprojection()
return list(x), list(y) return list(x), list(y)
def histogram(self, mask: Image | None = None, extrema=None) -> list[int]: def histogram(
self, mask: Image | None = None, extrema: tuple[float, float] | None = None
) -> list[int]:
""" """
Returns a histogram for the image. The histogram is returned as a Returns a histogram for the image. The histogram is returned as a
list of pixel counts, one for each pixel value in the source list of pixel counts, one for each pixel value in the source
@ -1717,12 +1721,14 @@ class Image:
mask.load() mask.load()
return self.im.histogram((0, 0), mask.im) return self.im.histogram((0, 0), mask.im)
if self.mode in ("I", "F"): if self.mode in ("I", "F"):
if extrema is None: return self.im.histogram(
extrema = self.getextrema() extrema if extrema is not None else self.getextrema()
return self.im.histogram(extrema) )
return self.im.histogram() return self.im.histogram()
def entropy(self, mask=None, extrema=None): def entropy(
self, mask: Image | None = None, extrema: tuple[float, float] | None = None
) -> float:
""" """
Calculates and returns the entropy for the image. Calculates and returns the entropy for the image.
@ -1743,9 +1749,9 @@ class Image:
mask.load() mask.load()
return self.im.entropy((0, 0), mask.im) return self.im.entropy((0, 0), mask.im)
if self.mode in ("I", "F"): if self.mode in ("I", "F"):
if extrema is None: return self.im.entropy(
extrema = self.getextrema() extrema if extrema is not None else self.getextrema()
return self.im.entropy(extrema) )
return self.im.entropy() return self.im.entropy()
def paste( def paste(
@ -2006,7 +2012,7 @@ class Image:
def putdata( def putdata(
self, self,
data: Sequence[float] | Sequence[Sequence[int]] | NumpyArray, data: Sequence[float] | Sequence[Sequence[int]] | core.ImagingCore | NumpyArray,
scale: float = 1.0, scale: float = 1.0,
offset: float = 0.0, offset: float = 0.0,
) -> None: ) -> None:
@ -2194,7 +2200,12 @@ class Image:
return m_im return m_im
def _get_safe_box(self, size, resample, box): def _get_safe_box(
self,
size: tuple[int, int],
resample: Resampling,
box: tuple[float, float, float, float],
) -> tuple[int, int, int, int]:
"""Expands the box so it includes adjacent pixels """Expands the box so it includes adjacent pixels
that may be used by resampling with the given resampling filter. that may be used by resampling with the given resampling filter.
""" """
@ -2304,7 +2315,7 @@ class Image:
factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1 factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1
factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1 factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1
if factor_x > 1 or factor_y > 1: if factor_x > 1 or factor_y > 1:
reduce_box = self._get_safe_box(size, resample, box) reduce_box = self._get_safe_box(size, cast(Resampling, resample), box)
factor = (factor_x, factor_y) factor = (factor_x, factor_y)
self = ( self = (
self.reduce(factor, box=reduce_box) self.reduce(factor, box=reduce_box)
@ -2440,7 +2451,7 @@ class Image:
0.0, 0.0,
] ]
def transform(x, y, matrix): def transform(x: float, y: float, matrix: list[float]) -> tuple[float, float]:
(a, b, c, d, e, f) = matrix (a, b, c, d, e, f) = matrix
return a * x + b * y + c, d * x + e * y + f return a * x + b * y + c, d * x + e * y + f
@ -2455,9 +2466,9 @@ class Image:
xx = [] xx = []
yy = [] yy = []
for x, y in ((0, 0), (w, 0), (w, h), (0, h)): for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
x, y = transform(x, y, matrix) transformed_x, transformed_y = transform(x, y, matrix)
xx.append(x) xx.append(transformed_x)
yy.append(y) yy.append(transformed_y)
nw = math.ceil(max(xx)) - math.floor(min(xx)) nw = math.ceil(max(xx)) - math.floor(min(xx))
nh = math.ceil(max(yy)) - math.floor(min(yy)) nh = math.ceil(max(yy)) - math.floor(min(yy))
@ -2715,7 +2726,7 @@ class Image:
provided_size = tuple(map(math.floor, size)) provided_size = tuple(map(math.floor, size))
def preserve_aspect_ratio() -> tuple[int, int] | None: def preserve_aspect_ratio() -> tuple[int, int] | None:
def round_aspect(number, key): def round_aspect(number: float, key: Callable[[int], float]) -> int:
return max(min(math.floor(number), math.ceil(number), key=key), 1) return max(min(math.floor(number), math.ceil(number), key=key), 1)
x, y = provided_size x, y = provided_size
@ -2859,7 +2870,13 @@ class Image:
return im return im
def __transformer( def __transformer(
self, box, image, method, data, resample=Resampling.NEAREST, fill=1 self,
box: tuple[int, int, int, int],
image: Image,
method,
data,
resample: int = Resampling.NEAREST,
fill: bool = True,
): ):
w = box[2] - box[0] w = box[2] - box[0]
h = box[3] - box[1] h = box[3] - box[1]
@ -2909,11 +2926,12 @@ class Image:
Resampling.BICUBIC, Resampling.BICUBIC,
): ):
if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS): if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS):
msg = { unusable: dict[int, str] = {
Resampling.BOX: "Image.Resampling.BOX", Resampling.BOX: "Image.Resampling.BOX",
Resampling.HAMMING: "Image.Resampling.HAMMING", Resampling.HAMMING: "Image.Resampling.HAMMING",
Resampling.LANCZOS: "Image.Resampling.LANCZOS", Resampling.LANCZOS: "Image.Resampling.LANCZOS",
}[resample] + f" ({resample}) cannot be used." }
msg = unusable[resample] + f" ({resample}) cannot be used."
else: else:
msg = f"Unknown resampling filter ({resample})." msg = f"Unknown resampling filter ({resample})."
@ -3299,7 +3317,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1) return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
def fromqimage(im): def fromqimage(im) -> ImageFile.ImageFile:
"""Creates an image instance from a QImage image""" """Creates an image instance from a QImage image"""
from . import ImageQt from . import ImageQt
@ -3309,7 +3327,7 @@ def fromqimage(im):
return ImageQt.fromqimage(im) return ImageQt.fromqimage(im)
def fromqpixmap(im): def fromqpixmap(im) -> ImageFile.ImageFile:
"""Creates an image instance from a QPixmap image""" """Creates an image instance from a QPixmap image"""
from . import ImageQt from . import ImageQt
@ -3856,7 +3874,7 @@ class Exif(_ExifBase):
print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99 print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99
""" """
endian = None endian: str | None = None
bigtiff = False bigtiff = False
_loaded = False _loaded = False
@ -3880,7 +3898,7 @@ class Exif(_ExifBase):
# returns a dict with any single item tuples/lists as individual values # returns a dict with any single item tuples/lists as individual values
return {k: self._fixup(v) for k, v in src_dict.items()} return {k: self._fixup(v) for k, v in src_dict.items()}
def _get_ifd_dict(self, offset, group=None): def _get_ifd_dict(self, offset: int, group: int | None = None):
try: try:
# an offset pointer to the location of the nested embedded IFD. # an offset pointer to the location of the nested embedded IFD.
# It should be a long, but may be corrupted. # It should be a long, but may be corrupted.
@ -3894,7 +3912,7 @@ class Exif(_ExifBase):
info.load(self.fp) info.load(self.fp)
return self._fixup_dict(info) return self._fixup_dict(info)
def _get_head(self): 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)
@ -3905,7 +3923,7 @@ class Exif(_ExifBase):
head += b"\x00\x00\x00\x00" head += b"\x00\x00\x00\x00"
return head return head
def load(self, data): def load(self, data: bytes) -> None:
# Extract EXIF information. This is highly experimental, # Extract EXIF information. This is highly experimental,
# and is likely to be replaced with something better in a future # and is likely to be replaced with something better in a future
# version. # version.
@ -3924,7 +3942,7 @@ class Exif(_ExifBase):
self._info = None self._info = None
return return
self.fp = io.BytesIO(data) self.fp: IO[bytes] = io.BytesIO(data)
self.head = self.fp.read(8) self.head = self.fp.read(8)
# process dictionary # process dictionary
from . import TiffImagePlugin from . import TiffImagePlugin
@ -3934,7 +3952,7 @@ class Exif(_ExifBase):
self.fp.seek(self._info.next) self.fp.seek(self._info.next)
self._info.load(self.fp) self._info.load(self.fp)
def load_from_fp(self, fp, offset=None): def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None:
self._loaded_exif = None self._loaded_exif = None
self._data.clear() self._data.clear()
self._hidden_data.clear() self._hidden_data.clear()
@ -4115,16 +4133,16 @@ class Exif(_ExifBase):
keys.update(self._info) keys.update(self._info)
return len(keys) return len(keys)
def __getitem__(self, tag): def __getitem__(self, tag: int):
if self._info is not None and tag not in self._data and tag in self._info: if self._info is not None and tag not in self._data and tag in self._info:
self._data[tag] = self._fixup(self._info[tag]) self._data[tag] = self._fixup(self._info[tag])
del self._info[tag] del self._info[tag]
return self._data[tag] return self._data[tag]
def __contains__(self, tag) -> bool: def __contains__(self, tag: object) -> bool:
return tag in self._data or (self._info is not None and tag in self._info) return tag in self._data or (self._info is not None and tag in self._info)
def __setitem__(self, tag, value) -> None: def __setitem__(self, tag: int, value) -> None:
if self._info is not None and tag in self._info: if self._info is not None and tag in self._info:
del self._info[tag] del self._info[tag]
self._data[tag] = value self._data[tag] = value
@ -4135,7 +4153,7 @@ class Exif(_ExifBase):
else: else:
del self._data[tag] del self._data[tag]
def __iter__(self): def __iter__(self) -> Iterator[int]:
keys = set(self._data) keys = set(self._data)
if self._info is not None: if self._info is not None:
keys.update(self._info) keys.update(self._info)

View File

@ -36,7 +36,7 @@ import numbers
import struct import struct
from collections.abc import Sequence from collections.abc import Sequence
from types import ModuleType from types import ModuleType
from typing import TYPE_CHECKING, AnyStr, Callable, Union, cast from typing import TYPE_CHECKING, Any, AnyStr, Callable, Union, cast
from . import Image, ImageColor from . import Image, ImageColor
from ._deprecate import deprecate from ._deprecate import deprecate
@ -561,7 +561,12 @@ class ImageDraw:
def _multiline_split(self, text: AnyStr) -> list[AnyStr]: def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
return text.split("\n" if isinstance(text, str) else b"\n") return text.split("\n" if isinstance(text, str) else b"\n")
def _multiline_spacing(self, font, spacing, stroke_width): def _multiline_spacing(
self,
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
spacing: float,
stroke_width: float,
) -> float:
return ( return (
self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3] self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
+ stroke_width + stroke_width
@ -571,25 +576,25 @@ class ImageDraw:
def text( def text(
self, self,
xy: tuple[float, float], xy: tuple[float, float],
text: str, text: AnyStr,
fill=None, fill: _Ink | None = None,
font: ( font: (
ImageFont.ImageFont ImageFont.ImageFont
| ImageFont.FreeTypeFont | ImageFont.FreeTypeFont
| ImageFont.TransposedFont | ImageFont.TransposedFont
| None | None
) = None, ) = None,
anchor=None, anchor: str | None = None,
spacing=4, spacing: float = 4,
align="left", align: str = "left",
direction=None, direction: str | None = None,
features=None, features: list[str] | None = None,
language=None, language: str | None = None,
stroke_width=0, stroke_width: float = 0,
stroke_fill=None, stroke_fill: _Ink | None = None,
embedded_color=False, embedded_color: bool = False,
*args, *args: Any,
**kwargs, **kwargs: Any,
) -> None: ) -> None:
"""Draw text.""" """Draw text."""
if embedded_color and self.mode not in ("RGB", "RGBA"): if embedded_color and self.mode not in ("RGB", "RGBA"):
@ -623,15 +628,14 @@ class ImageDraw:
return fill_ink return fill_ink
return ink return ink
def draw_text(ink, stroke_width=0, stroke_offset=None) -> None: def draw_text(ink: int, stroke_width: float = 0) -> None:
mode = self.fontmode mode = self.fontmode
if stroke_width == 0 and embedded_color: if stroke_width == 0 and embedded_color:
mode = "RGBA" mode = "RGBA"
coord = [] coord = []
start = []
for i in range(2): for i in range(2):
coord.append(int(xy[i])) coord.append(int(xy[i]))
start.append(math.modf(xy[i])[0]) start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
try: try:
mask, offset = font.getmask2( # type: ignore[union-attr,misc] mask, offset = font.getmask2( # type: ignore[union-attr,misc]
text, text,
@ -664,8 +668,6 @@ class ImageDraw:
) )
except TypeError: except TypeError:
mask = font.getmask(text) mask = font.getmask(text)
if stroke_offset:
coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]
if mode == "RGBA": if mode == "RGBA":
# font.getmask2(mode="RGBA") returns color in RGB bands and mask in A # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
# extract mask and set text alpha # extract mask and set text alpha
@ -699,25 +701,25 @@ class ImageDraw:
def multiline_text( def multiline_text(
self, self,
xy: tuple[float, float], xy: tuple[float, float],
text: str, text: AnyStr,
fill=None, fill: _Ink | None = None,
font: ( font: (
ImageFont.ImageFont ImageFont.ImageFont
| ImageFont.FreeTypeFont | ImageFont.FreeTypeFont
| ImageFont.TransposedFont | ImageFont.TransposedFont
| None | None
) = None, ) = None,
anchor=None, anchor: str | None = None,
spacing=4, spacing: float = 4,
align="left", align: str = "left",
direction=None, direction: str | None = None,
features=None, features: list[str] | None = None,
language=None, language: str | None = None,
stroke_width=0, stroke_width: float = 0,
stroke_fill=None, stroke_fill: _Ink | None = None,
embedded_color=False, embedded_color: bool = False,
*, *,
font_size=None, font_size: float | None = None,
) -> None: ) -> None:
if direction == "ttb": if direction == "ttb":
msg = "ttb direction is unsupported for multiline text" msg = "ttb direction is unsupported for multiline text"
@ -790,19 +792,19 @@ class ImageDraw:
def textlength( def textlength(
self, self,
text: str, text: AnyStr,
font: ( font: (
ImageFont.ImageFont ImageFont.ImageFont
| ImageFont.FreeTypeFont | ImageFont.FreeTypeFont
| ImageFont.TransposedFont | ImageFont.TransposedFont
| None | None
) = None, ) = None,
direction=None, direction: str | None = None,
features=None, features: list[str] | None = None,
language=None, language: str | None = None,
embedded_color=False, embedded_color: bool = False,
*, *,
font_size=None, font_size: float | None = None,
) -> float: ) -> float:
"""Get the length of a given string, in pixels with 1/64 precision.""" """Get the length of a given string, in pixels with 1/64 precision."""
if self._multiline_check(text): if self._multiline_check(text):
@ -819,20 +821,25 @@ class ImageDraw:
def textbbox( def textbbox(
self, self,
xy, xy: tuple[float, float],
text, text: AnyStr,
font=None, font: (
anchor=None, ImageFont.ImageFont
spacing=4, | ImageFont.FreeTypeFont
align="left", | ImageFont.TransposedFont
direction=None, | None
features=None, ) = None,
language=None, anchor: str | None = None,
stroke_width=0, spacing: float = 4,
embedded_color=False, align: str = "left",
direction: str | None = None,
features: list[str] | None = None,
language: str | None = None,
stroke_width: float = 0,
embedded_color: bool = False,
*, *,
font_size=None, font_size: float | None = None,
) -> tuple[int, int, int, int]: ) -> tuple[float, float, float, float]:
"""Get the bounding box of a given string, in pixels.""" """Get the bounding box of a given string, in pixels."""
if embedded_color and self.mode not in ("RGB", "RGBA"): if embedded_color and self.mode not in ("RGB", "RGBA"):
msg = "Embedded color supported only in RGB and RGBA modes" msg = "Embedded color supported only in RGB and RGBA modes"
@ -864,20 +871,25 @@ class ImageDraw:
def multiline_textbbox( def multiline_textbbox(
self, self,
xy, xy: tuple[float, float],
text, text: AnyStr,
font=None, font: (
anchor=None, ImageFont.ImageFont
spacing=4, | ImageFont.FreeTypeFont
align="left", | ImageFont.TransposedFont
direction=None, | None
features=None, ) = None,
language=None, anchor: str | None = None,
stroke_width=0, spacing: float = 4,
embedded_color=False, align: str = "left",
direction: str | None = None,
features: list[str] | None = None,
language: str | None = None,
stroke_width: float = 0,
embedded_color: bool = False,
*, *,
font_size=None, font_size: float | None = None,
) -> tuple[int, int, int, int]: ) -> tuple[float, float, float, float]:
if direction == "ttb": if direction == "ttb":
msg = "ttb direction is unsupported for multiline text" msg = "ttb direction is unsupported for multiline text"
raise ValueError(msg) raise ValueError(msg)
@ -916,7 +928,7 @@ class ImageDraw:
elif anchor[1] == "d": elif anchor[1] == "d":
top -= (len(lines) - 1) * line_spacing top -= (len(lines) - 1) * line_spacing
bbox: tuple[int, int, int, int] | None = None bbox: tuple[float, float, float, float] | None = None
for idx, line in enumerate(lines): for idx, line in enumerate(lines):
left = xy[0] left = xy[0]

View File

@ -24,10 +24,10 @@
""" """
from __future__ import annotations from __future__ import annotations
from typing import BinaryIO from typing import Any, AnyStr, BinaryIO
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
from ._typing import StrOrBytesPath from ._typing import Coords, StrOrBytesPath
class Pen: class Pen:
@ -74,12 +74,14 @@ class Draw:
image = Image.new(image, size, color) image = Image.new(image, size, color)
self.draw = ImageDraw.Draw(image) self.draw = ImageDraw.Draw(image)
self.image = image self.image = image
self.transform = None self.transform: tuple[float, float, float, float, float, float] | None = None
def flush(self) -> Image.Image: def flush(self) -> Image.Image:
return self.image return self.image
def render(self, op, xy, pen, brush=None): def render(
self, op: str, xy: Coords, pen: Pen | Brush, brush: Brush | Pen | None = None
) -> None:
# handle color arguments # handle color arguments
outline = fill = None outline = fill = None
width = 1 width = 1
@ -95,20 +97,21 @@ class Draw:
fill = pen.color fill = pen.color
# handle transformation # handle transformation
if self.transform: if self.transform:
xy = ImagePath.Path(xy) path = ImagePath.Path(xy)
xy.transform(self.transform) path.transform(self.transform)
xy = path
# render the item # render the item
if op == "line": if op == "line":
self.draw.line(xy, fill=outline, width=width) self.draw.line(xy, fill=outline, width=width)
else: else:
getattr(self.draw, op)(xy, fill=fill, outline=outline) getattr(self.draw, op)(xy, fill=fill, outline=outline)
def settransform(self, offset): def settransform(self, offset: tuple[float, float]) -> None:
"""Sets a transformation offset.""" """Sets a transformation offset."""
(xoffset, yoffset) = offset (xoffset, yoffset) = offset
self.transform = (1, 0, xoffset, 0, 1, yoffset) self.transform = (1, 0, xoffset, 0, 1, yoffset)
def arc(self, xy, start, end, *options): def arc(self, xy: Coords, start, end, *options: Any) -> None:
""" """
Draws an arc (a portion of a circle outline) between the start and end Draws an arc (a portion of a circle outline) between the start and end
angles, inside the given bounding box. angles, inside the given bounding box.
@ -117,7 +120,7 @@ class Draw:
""" """
self.render("arc", xy, start, end, *options) self.render("arc", xy, start, end, *options)
def chord(self, xy, start, end, *options): def chord(self, xy: Coords, start, end, *options: Any) -> None:
""" """
Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
with a straight line. with a straight line.
@ -126,7 +129,7 @@ class Draw:
""" """
self.render("chord", xy, start, end, *options) self.render("chord", xy, start, end, *options)
def ellipse(self, xy, *options): def ellipse(self, xy: Coords, *options: Any) -> None:
""" """
Draws an ellipse inside the given bounding box. Draws an ellipse inside the given bounding box.
@ -134,7 +137,7 @@ class Draw:
""" """
self.render("ellipse", xy, *options) self.render("ellipse", xy, *options)
def line(self, xy, *options): def line(self, xy: Coords, *options: Any) -> None:
""" """
Draws a line between the coordinates in the ``xy`` list. Draws a line between the coordinates in the ``xy`` list.
@ -142,7 +145,7 @@ class Draw:
""" """
self.render("line", xy, *options) self.render("line", xy, *options)
def pieslice(self, xy, start, end, *options): def pieslice(self, xy: Coords, start, end, *options: Any) -> None:
""" """
Same as arc, but also draws straight lines between the end points and the Same as arc, but also draws straight lines between the end points and the
center of the bounding box. center of the bounding box.
@ -151,7 +154,7 @@ class Draw:
""" """
self.render("pieslice", xy, start, end, *options) self.render("pieslice", xy, start, end, *options)
def polygon(self, xy, *options): def polygon(self, xy: Coords, *options: Any) -> None:
""" """
Draws a polygon. Draws a polygon.
@ -164,7 +167,7 @@ class Draw:
""" """
self.render("polygon", xy, *options) self.render("polygon", xy, *options)
def rectangle(self, xy, *options): def rectangle(self, xy: Coords, *options) -> None:
""" """
Draws a rectangle. Draws a rectangle.
@ -172,18 +175,21 @@ class Draw:
""" """
self.render("rectangle", xy, *options) self.render("rectangle", xy, *options)
def text(self, xy, text, font): def text(self, xy: tuple[float, float], text: AnyStr, font: Font) -> None:
""" """
Draws the string at the given position. Draws the string at the given position.
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text` .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
""" """
if self.transform: if self.transform:
xy = ImagePath.Path(xy) path = ImagePath.Path(xy)
xy.transform(self.transform) path.transform(self.transform)
xy = path
self.draw.text(xy, text, font=font.font, fill=font.color) self.draw.text(xy, text, font=font.font, fill=font.color)
def textbbox(self, xy, text, font): def textbbox(
self, xy: tuple[float, float], text: AnyStr, font: Font
) -> tuple[float, float, float, float]:
""" """
Returns bounding box (in pixels) of given text. Returns bounding box (in pixels) of given text.
@ -192,11 +198,12 @@ class Draw:
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox` .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
""" """
if self.transform: if self.transform:
xy = ImagePath.Path(xy) path = ImagePath.Path(xy)
xy.transform(self.transform) path.transform(self.transform)
xy = path
return self.draw.textbbox(xy, text, font=font.font) return self.draw.textbbox(xy, text, font=font.font)
def textlength(self, text, font): def textlength(self, text: AnyStr, font: Font) -> float:
""" """
Returns length (in pixels) of given text. Returns length (in pixels) of given text.
This is the amount by which following text should be offset. This is the amount by which following text should be offset.

View File

@ -86,7 +86,7 @@ def raise_oserror(error: int) -> OSError:
raise _get_oserror(error, encoder=False) raise _get_oserror(error, encoder=False)
def _tilesort(t): def _tilesort(t) -> int:
# sort on offset # sort on offset
return t[2] return t[2]
@ -161,7 +161,7 @@ class ImageFile(Image.Image):
return Image.MIME.get(self.format.upper()) return Image.MIME.get(self.format.upper())
return None return None
def __setstate__(self, state): def __setstate__(self, state) -> None:
self.tile = [] self.tile = []
super().__setstate__(state) super().__setstate__(state)
@ -333,14 +333,14 @@ class ImageFile(Image.Image):
# def load_read(self, read_bytes: int) -> bytes: # def load_read(self, read_bytes: int) -> bytes:
# pass # pass
def _seek_check(self, frame): def _seek_check(self, frame: int) -> bool:
if ( if (
frame < self._min_frame frame < self._min_frame
# Only check upper limit on frames if additional seek operations # Only check upper limit on frames if additional seek operations
# are not required to do so # are not required to do so
or ( or (
not (hasattr(self, "_n_frames") and self._n_frames is None) not (hasattr(self, "_n_frames") and self._n_frames is None)
and frame >= self.n_frames + self._min_frame and frame >= getattr(self, "n_frames") + self._min_frame
) )
): ):
msg = "attempt to seek outside sequence" msg = "attempt to seek outside sequence"
@ -370,7 +370,7 @@ class StubImageFile(ImageFile):
msg = "StubImageFile subclass must implement _open" msg = "StubImageFile subclass must implement _open"
raise NotImplementedError(msg) raise NotImplementedError(msg)
def load(self): def load(self) -> Image.core.PixelAccess | None:
loader = self._load() loader = self._load()
if loader is None: if loader is None:
msg = f"cannot find loader for this {self.format} file" msg = f"cannot find loader for this {self.format} file"
@ -378,7 +378,7 @@ class StubImageFile(ImageFile):
image = loader.load(self) image = loader.load(self)
assert image is not None assert image is not None
# become the other object (!) # become the other object (!)
self.__class__ = image.__class__ self.__class__ = image.__class__ # type: ignore[assignment]
self.__dict__ = image.__dict__ self.__dict__ = image.__dict__
return image.load() return image.load()
@ -396,8 +396,8 @@ class Parser:
incremental = None incremental = None
image: Image.Image | None = None image: Image.Image | None = None
data = None data: bytes | None = None
decoder = None decoder: Image.core.ImagingDecoder | PyDecoder | None = None
offset = 0 offset = 0
finished = 0 finished = 0
@ -409,7 +409,7 @@ class Parser:
""" """
assert self.data is None, "cannot reuse parsers" assert self.data is None, "cannot reuse parsers"
def feed(self, data): def feed(self, data: bytes) -> None:
""" """
(Consumer) Feed data to the parser. (Consumer) Feed data to the parser.
@ -485,13 +485,13 @@ class Parser:
self.image = im self.image = im
def __enter__(self): def __enter__(self) -> Parser:
return self return self
def __exit__(self, *args: object) -> None: def __exit__(self, *args: object) -> None:
self.close() self.close()
def close(self): def close(self) -> Image.Image:
""" """
(Consumer) Close the stream. (Consumer) Close the stream.
@ -525,7 +525,7 @@ class Parser:
# -------------------------------------------------------------------- # --------------------------------------------------------------------
def _save(im, fp, tile, bufsize=0) -> None: def _save(im, fp, tile, bufsize: int = 0) -> None:
"""Helper to save image based on tile list """Helper to save image based on tile list
:param im: Image object. :param im: Image object.
@ -553,7 +553,9 @@ def _save(im, fp, tile, bufsize=0) -> None:
fp.flush() fp.flush()
def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None): def _encode_tile(
im, fp: IO[bytes], tile: list[_Tile], bufsize: int, fh, exc=None
) -> None:
for encoder_name, extents, offset, args in tile: for encoder_name, extents, offset, args in tile:
if offset > 0: if offset > 0:
fp.seek(offset) fp.seek(offset)
@ -580,7 +582,7 @@ def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None):
encoder.cleanup() encoder.cleanup()
def _safe_read(fp, size): def _safe_read(fp: IO[bytes], size: int) -> bytes:
""" """
Reads large blocks in a safe way. Unlike fp.read(n), this function Reads large blocks in a safe way. Unlike fp.read(n), this function
doesn't trust the user. If the requested size is larger than doesn't trust the user. If the requested size is larger than
@ -601,18 +603,18 @@ def _safe_read(fp, size):
msg = "Truncated File Read" msg = "Truncated File Read"
raise OSError(msg) raise OSError(msg)
return data return data
data = [] blocks: list[bytes] = []
remaining_size = size remaining_size = size
while remaining_size > 0: while remaining_size > 0:
block = fp.read(min(remaining_size, SAFEBLOCK)) block = fp.read(min(remaining_size, SAFEBLOCK))
if not block: if not block:
break break
data.append(block) blocks.append(block)
remaining_size -= len(block) remaining_size -= len(block)
if sum(len(d) for d in data) < size: if sum(len(block) for block in blocks) < size:
msg = "Truncated File Read" msg = "Truncated File Read"
raise OSError(msg) raise OSError(msg)
return b"".join(data) return b"".join(blocks)
class PyCodecState: class PyCodecState:
@ -629,18 +631,18 @@ class PyCodecState:
class PyCodec: class PyCodec:
fd: IO[bytes] | None fd: IO[bytes] | None
def __init__(self, mode, *args): def __init__(self, mode: str, *args: Any) -> None:
self.im = None self.im: Image.core.ImagingCore | None = None
self.state = PyCodecState() self.state = PyCodecState()
self.fd = None self.fd = None
self.mode = mode self.mode = mode
self.init(args) self.init(args)
def init(self, args): def init(self, args: tuple[Any, ...]) -> None:
""" """
Override to perform codec specific initialization Override to perform codec specific initialization
:param args: Array of args items from the tile entry :param args: Tuple of arg items from the tile entry
:returns: None :returns: None
""" """
self.args = args self.args = args
@ -653,7 +655,7 @@ class PyCodec:
""" """
pass pass
def setfd(self, fd): def setfd(self, fd: IO[bytes]) -> None:
""" """
Called from ImageFile to set the Python file-like object Called from ImageFile to set the Python file-like object
@ -662,7 +664,7 @@ class PyCodec:
""" """
self.fd = fd self.fd = fd
def setimage(self, im, extents: tuple[int, int, int, int] | None = None) -> None: def setimage(self, im, extents=None):
""" """
Called from ImageFile to set the core output image for the codec Called from ImageFile to set the core output image for the codec
@ -793,7 +795,7 @@ class PyEncoder(PyCodec):
self.fd.write(data) self.fd.write(data)
return bytes_consumed, errcode return bytes_consumed, errcode
def encode_to_file(self, fh, bufsize): def encode_to_file(self, fh: IO[bytes], bufsize: int) -> int:
""" """
:param fh: File handle. :param fh: File handle.
:param bufsize: Buffer size. :param bufsize: Buffer size.

View File

@ -34,7 +34,7 @@ import warnings
from enum import IntEnum from enum import IntEnum
from io import BytesIO from io import BytesIO
from types import ModuleType from types import ModuleType
from typing import IO, TYPE_CHECKING, Any, BinaryIO from typing import IO, TYPE_CHECKING, Any, BinaryIO, TypedDict
from . import Image from . import Image
from ._typing import StrOrBytesPath from ._typing import StrOrBytesPath
@ -46,6 +46,13 @@ if TYPE_CHECKING:
from ._imagingft import Font from ._imagingft import Font
class Axis(TypedDict):
minimum: int | None
default: int | None
maximum: int | None
name: bytes | None
class Layout(IntEnum): class Layout(IntEnum):
BASIC = 0 BASIC = 0
RAQM = 1 RAQM = 1
@ -138,7 +145,9 @@ class ImageFont:
self.font = Image.core.font(image.im, data) self.font = Image.core.font(image.im, data)
def getmask(self, text, mode="", *args, **kwargs): def getmask(
self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any
) -> Image.core.ImagingCore:
""" """
Create a bitmap for the text. Create a bitmap for the text.
@ -236,7 +245,7 @@ class FreeTypeFont:
self.layout_engine = layout_engine self.layout_engine = layout_engine
def load_from_bytes(f): def load_from_bytes(f) -> None:
self.font_bytes = f.read() self.font_bytes = f.read()
self.font = core.getfont( self.font = core.getfont(
"", size, index, encoding, self.font_bytes, layout_engine "", size, index, encoding, self.font_bytes, layout_engine
@ -283,7 +292,12 @@ class FreeTypeFont:
return self.font.ascent, self.font.descent return self.font.ascent, self.font.descent
def getlength( def getlength(
self, text: str | bytes, mode="", direction=None, features=None, language=None self,
text: str | bytes,
mode: str = "",
direction: str | None = None,
features: list[str] | None = None,
language: str | None = None,
) -> float: ) -> float:
""" """
Returns length (in pixels with 1/64 precision) of given text when rendered Returns length (in pixels with 1/64 precision) of given text when rendered
@ -424,16 +438,16 @@ class FreeTypeFont:
def getmask( def getmask(
self, self,
text, text: str | bytes,
mode="", mode: str = "",
direction=None, direction: str | None = None,
features=None, features: list[str] | None = None,
language=None, language: str | None = None,
stroke_width=0, stroke_width: float = 0,
anchor=None, anchor: str | None = None,
ink=0, ink: int = 0,
start=None, start: tuple[float, float] | None = None,
): ) -> Image.core.ImagingCore:
""" """
Create a bitmap for the text. Create a bitmap for the text.
@ -516,17 +530,17 @@ class FreeTypeFont:
def getmask2( def getmask2(
self, self,
text: str | bytes, text: str | bytes,
mode="", mode: str = "",
direction=None, direction: str | None = None,
features=None, features: list[str] | None = None,
language=None, language: str | None = None,
stroke_width=0, stroke_width: float = 0,
anchor=None, anchor: str | None = None,
ink=0, ink: int = 0,
start=None, start: tuple[float, float] | None = None,
*args, *args: Any,
**kwargs, **kwargs: Any,
): ) -> tuple[Image.core.ImagingCore, tuple[int, int]]:
""" """
Create a bitmap for the text. Create a bitmap for the text.
@ -599,7 +613,7 @@ class FreeTypeFont:
if start is None: if start is None:
start = (0, 0) start = (0, 0)
def fill(width, height): def fill(width: int, height: int) -> Image.core.ImagingCore:
size = (width, height) size = (width, height)
Image._decompression_bomb_check(size) Image._decompression_bomb_check(size)
return Image.core.fill("RGBA" if mode == "RGBA" else "L", size) return Image.core.fill("RGBA" if mode == "RGBA" else "L", size)
@ -619,8 +633,13 @@ class FreeTypeFont:
) )
def font_variant( def font_variant(
self, font=None, size=None, index=None, encoding=None, layout_engine=None self,
): font: StrOrBytesPath | BinaryIO | None = None,
size: float | None = None,
index: int | None = None,
encoding: str | None = None,
layout_engine: Layout | None = None,
) -> FreeTypeFont:
""" """
Create a copy of this FreeTypeFont object, Create a copy of this FreeTypeFont object,
using any specified arguments to override the settings. using any specified arguments to override the settings.
@ -655,7 +674,7 @@ class FreeTypeFont:
raise NotImplementedError(msg) from e raise NotImplementedError(msg) from e
return [name.replace(b"\x00", b"") for name in names] return [name.replace(b"\x00", b"") for name in names]
def set_variation_by_name(self, name): def set_variation_by_name(self, name: str | bytes) -> None:
""" """
:param name: The name of the style. :param name: The name of the style.
:exception OSError: If the font is not a variation font. :exception OSError: If the font is not a variation font.
@ -674,7 +693,7 @@ class FreeTypeFont:
self.font.setvarname(index) self.font.setvarname(index)
def get_variation_axes(self): def get_variation_axes(self) -> list[Axis]:
""" """
:returns: A list of the axes in a variation font. :returns: A list of the axes in a variation font.
:exception OSError: If the font is not a variation font. :exception OSError: If the font is not a variation font.
@ -704,7 +723,9 @@ class FreeTypeFont:
class TransposedFont: class TransposedFont:
"""Wrapper for writing rotated or mirrored text""" """Wrapper for writing rotated or mirrored text"""
def __init__(self, font, orientation=None): def __init__(
self, font: ImageFont | FreeTypeFont, orientation: Image.Transpose | None = None
):
""" """
Wrapper that creates a transposed font from any existing font Wrapper that creates a transposed font from any existing font
object. object.
@ -718,13 +739,17 @@ class TransposedFont:
self.font = font self.font = font
self.orientation = orientation # any 'transpose' argument, or None self.orientation = orientation # any 'transpose' argument, or None
def getmask(self, text, mode="", *args, **kwargs): def getmask(
self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any
) -> Image.core.ImagingCore:
im = self.font.getmask(text, mode, *args, **kwargs) im = self.font.getmask(text, mode, *args, **kwargs)
if self.orientation is not None: if self.orientation is not None:
return im.transpose(self.orientation) return im.transpose(self.orientation)
return im return im
def getbbox(self, text, *args, **kwargs): def getbbox(
self, text: str | bytes, *args: Any, **kwargs: Any
) -> tuple[int, int, float, float]:
# TransposedFont doesn't support getmask2, move top-left point to (0, 0) # TransposedFont doesn't support getmask2, move top-left point to (0, 0)
# this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont # this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont
left, top, right, bottom = self.font.getbbox(text, *args, **kwargs) left, top, right, bottom = self.font.getbbox(text, *args, **kwargs)
@ -734,7 +759,7 @@ class TransposedFont:
return 0, 0, height, width return 0, 0, height, width
return 0, 0, width, height return 0, 0, width, height
def getlength(self, text: str | bytes, *args, **kwargs) -> float: def getlength(self, text: str | bytes, *args: Any, **kwargs: Any) -> float:
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270): if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
msg = "text length is undefined for text rotated by 90 or 270 degrees" msg = "text length is undefined for text rotated by 90 or 270 degrees"
raise ValueError(msg) raise ValueError(msg)

View File

@ -249,14 +249,21 @@ def lambda_eval(
:py:func:`~PIL.Image.merge` function. :py:func:`~PIL.Image.merge` function.
:param expression: A function that receives a dictionary. :param expression: A function that receives a dictionary.
:param options: Values to add to the function's dictionary. You :param options: Values to add to the function's dictionary. Deprecated.
can either use a dictionary, or one or more keyword You can instead use one or more keyword arguments.
arguments. :param **kw: Values to add to the function's dictionary.
:return: The expression result. This is usually an image object, but can :return: The expression result. This is usually an image object, but can
also be an integer, a floating point value, or a pixel tuple, also be an integer, a floating point value, or a pixel tuple,
depending on the expression. depending on the expression.
""" """
if options:
deprecate(
"ImageMath.lambda_eval options",
12,
"ImageMath.lambda_eval keyword arguments",
)
args: dict[str, Any] = ops.copy() args: dict[str, Any] = ops.copy()
args.update(options) args.update(options)
args.update(kw) args.update(kw)
@ -287,14 +294,21 @@ def unsafe_eval(
:py:func:`~PIL.Image.merge` function. :py:func:`~PIL.Image.merge` function.
:param expression: A string containing a Python-style expression. :param expression: A string containing a Python-style expression.
:param options: Values to add to the evaluation context. You :param options: Values to add to the evaluation context. Deprecated.
can either use a dictionary, or one or more keyword You can instead use one or more keyword arguments.
arguments. :param **kw: Values to add to the evaluation context.
:return: The evaluated expression. This is usually an image object, but can :return: The evaluated expression. This is usually an image object, but can
also be an integer, a floating point value, or a pixel tuple, also be an integer, a floating point value, or a pixel tuple,
depending on the expression. depending on the expression.
""" """
if options:
deprecate(
"ImageMath.unsafe_eval options",
12,
"ImageMath.unsafe_eval keyword arguments",
)
# build execution namespace # build execution namespace
args: dict[str, Any] = ops.copy() args: dict[str, Any] = ops.copy()
for k in list(options.keys()) + list(kw.keys()): for k in list(options.keys()) + list(kw.keys()):

View File

@ -19,11 +19,14 @@ from __future__ import annotations
import sys import sys
from io import BytesIO from io import BytesIO
from typing import Callable from typing import TYPE_CHECKING, Callable
from . import Image from . import Image
from ._util import is_path from ._util import is_path
if TYPE_CHECKING:
from . import ImageFile
qt_version: str | None qt_version: str | None
qt_versions = [ qt_versions = [
["6", "PyQt6"], ["6", "PyQt6"],
@ -55,7 +58,7 @@ else:
qt_version = None qt_version = None
def rgb(r, g, b, a=255): def rgb(r: int, g: int, b: int, a: int = 255) -> int:
"""(Internal) Turns an RGB color into a Qt compatible color integer.""" """(Internal) Turns an RGB color into a Qt compatible color integer."""
# use qRgb to pack the colors, and then turn the resulting long # use qRgb to pack the colors, and then turn the resulting long
# into a negative integer with the same bitpattern. # into a negative integer with the same bitpattern.
@ -90,11 +93,11 @@ def fromqimage(im):
return Image.open(b) return Image.open(b)
def fromqpixmap(im): def fromqpixmap(im) -> ImageFile.ImageFile:
return fromqimage(im) return fromqimage(im)
def align8to32(bytes, width, mode): def align8to32(bytes: bytes, width: int, mode: str) -> bytes:
""" """
converts each scanline of data from 8 bit to 32 bit aligned converts each scanline of data from 8 bit to 32 bit aligned
""" """
@ -172,7 +175,7 @@ def _toqclass_helper(im):
if qt_is_installed: if qt_is_installed:
class ImageQt(QImage): class ImageQt(QImage):
def __init__(self, im): def __init__(self, im) -> None:
""" """
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
class. class.

View File

@ -33,7 +33,7 @@ class Iterator:
:param im: An image object. :param im: An image object.
""" """
def __init__(self, im: Image.Image): def __init__(self, im: Image.Image) -> None:
if not hasattr(im, "seek"): if not hasattr(im, "seek"):
msg = "im must have seek method" msg = "im must have seek method"
raise AttributeError(msg) raise AttributeError(msg)

View File

@ -28,7 +28,7 @@ from __future__ import annotations
import tkinter import tkinter
from io import BytesIO from io import BytesIO
from typing import Any from typing import TYPE_CHECKING, Any, cast
from . import Image, ImageFile from . import Image, ImageFile
@ -61,7 +61,9 @@ def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None:
return Image.open(source) return Image.open(source)
def _pyimagingtkcall(command, photo, id): def _pyimagingtkcall(
command: str, photo: PhotoImage | tkinter.PhotoImage, id: int
) -> None:
tk = photo.tk tk = photo.tk
try: try:
tk.call(command, photo, id) tk.call(command, photo, id)
@ -215,11 +217,14 @@ class BitmapImage:
:param image: A PIL image. :param image: A PIL image.
""" """
def __init__(self, image=None, **kw): def __init__(self, image: Image.Image | None = None, **kw: Any) -> None:
# Tk compatibility: file or data # Tk compatibility: file or data
if image is None: if image is None:
image = _get_image_from_kw(kw) image = _get_image_from_kw(kw)
if image is None:
msg = "Image is required"
raise ValueError(msg)
self.__mode = image.mode self.__mode = image.mode
self.__size = image.size self.__size = image.size
@ -278,18 +283,23 @@ def getimage(photo: PhotoImage) -> Image.Image:
return im return im
def _show(image, title): def _show(image: Image.Image, title: str | None) -> None:
"""Helper for the Image.show method.""" """Helper for the Image.show method."""
class UI(tkinter.Label): class UI(tkinter.Label):
def __init__(self, master, im): def __init__(self, master: tkinter.Toplevel, im: Image.Image) -> None:
self.image: BitmapImage | PhotoImage
if im.mode == "1": if im.mode == "1":
self.image = BitmapImage(im, foreground="white", master=master) self.image = BitmapImage(im, foreground="white", master=master)
else: else:
self.image = PhotoImage(im, master=master) self.image = PhotoImage(im, master=master)
super().__init__(master, image=self.image, bg="black", bd=0) if TYPE_CHECKING:
image = cast(tkinter._Image, self.image)
else:
image = self.image
super().__init__(master, image=image, bg="black", bd=0)
if not tkinter._default_root: if not getattr(tkinter, "_default_root"):
msg = "tkinter not initialized" msg = "tkinter not initialized"
raise OSError(msg) raise OSError(msg)
top = tkinter.Toplevel() top = tkinter.Toplevel()

View File

@ -70,11 +70,14 @@ class Dib:
""" """
def __init__( def __init__(
self, image: Image.Image | str, size: tuple[int, int] | list[int] | None = None self, image: Image.Image | str, size: tuple[int, int] | None = None
) -> None: ) -> None:
if isinstance(image, str): if isinstance(image, str):
mode = image mode = image
image = "" image = ""
if size is None:
msg = "If first argument is mode, size is required"
raise ValueError(msg)
else: else:
mode = image.mode mode = image.mode
size = image.size size = image.size
@ -87,7 +90,7 @@ class Dib:
assert not isinstance(image, str) assert not isinstance(image, str)
self.paste(image) self.paste(image)
def expose(self, handle): def expose(self, handle: int | HDC | HWND) -> None:
""" """
Copy the bitmap contents to a device context. Copy the bitmap contents to a device context.
@ -98,14 +101,18 @@ class Dib:
if isinstance(handle, HWND): if isinstance(handle, HWND):
dc = self.image.getdc(handle) dc = self.image.getdc(handle)
try: try:
result = self.image.expose(dc) self.image.expose(dc)
finally: finally:
self.image.releasedc(handle, dc) self.image.releasedc(handle, dc)
else: else:
result = self.image.expose(handle) self.image.expose(handle)
return result
def draw(self, handle, dst, src=None): def draw(
self,
handle: int | HDC | HWND,
dst: tuple[int, int, int, int],
src: tuple[int, int, int, int] | None = None,
) -> None:
""" """
Same as expose, but allows you to specify where to draw the image, and Same as expose, but allows you to specify where to draw the image, and
what part of it to draw. what part of it to draw.
@ -115,19 +122,18 @@ class Dib:
the destination have different sizes, the image is resized as the destination have different sizes, the image is resized as
necessary. necessary.
""" """
if not src: if src is None:
src = (0, 0) + self.size src = (0, 0) + self.size
if isinstance(handle, HWND): if isinstance(handle, HWND):
dc = self.image.getdc(handle) dc = self.image.getdc(handle)
try: try:
result = self.image.draw(dc, dst, src) self.image.draw(dc, dst, src)
finally: finally:
self.image.releasedc(handle, dc) self.image.releasedc(handle, dc)
else: else:
result = self.image.draw(handle, dst, src) self.image.draw(handle, dst, src)
return result
def query_palette(self, handle): def query_palette(self, handle: int | HDC | HWND) -> int:
""" """
Installs the palette associated with the image in the given device Installs the palette associated with the image in the given device
context. context.
@ -139,8 +145,8 @@ class Dib:
:param handle: Device context (HDC), cast to a Python integer, or an :param handle: Device context (HDC), cast to a Python integer, or an
HDC or HWND instance. HDC or HWND instance.
:return: A true value if one or more entries were changed (this :return: The number of entries that were changed (if one or more entries,
indicates that the image should be redrawn). this indicates that the image should be redrawn).
""" """
if isinstance(handle, HWND): if isinstance(handle, HWND):
handle = self.image.getdc(handle) handle = self.image.getdc(handle)
@ -202,22 +208,22 @@ class Window:
title, self.__dispatcher, width or 0, height or 0 title, self.__dispatcher, width or 0, height or 0
) )
def __dispatcher(self, action, *args): def __dispatcher(self, action: str, *args: int) -> None:
return getattr(self, f"ui_handle_{action}")(*args) getattr(self, f"ui_handle_{action}")(*args)
def ui_handle_clear(self, dc, x0, y0, x1, y1): def ui_handle_clear(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
pass pass
def ui_handle_damage(self, x0, y0, x1, y1): def ui_handle_damage(self, x0: int, y0: int, x1: int, y1: int) -> None:
pass pass
def ui_handle_destroy(self) -> None: def ui_handle_destroy(self) -> None:
pass pass
def ui_handle_repair(self, dc, x0, y0, x1, y1): def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
pass pass
def ui_handle_resize(self, width, height): def ui_handle_resize(self, width: int, height: int) -> None:
pass pass
def mainloop(self) -> None: def mainloop(self) -> None:
@ -227,12 +233,12 @@ class Window:
class ImageWindow(Window): class ImageWindow(Window):
"""Create an image window which displays the given image.""" """Create an image window which displays the given image."""
def __init__(self, image, title="PIL"): def __init__(self, image: Image.Image | Dib, title: str = "PIL") -> None:
if not isinstance(image, Dib): if not isinstance(image, Dib):
image = Dib(image) image = Dib(image)
self.image = image self.image = image
width, height = image.size width, height = image.size
super().__init__(title, width=width, height=height) super().__init__(title, width=width, height=height)
def ui_handle_repair(self, dc, x0, y0, x1, y1): def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
self.image.draw(dc, (x0, y0, x1, y1)) self.image.draw(dc, (x0, y0, x1, y1))

View File

@ -18,6 +18,7 @@ from __future__ import annotations
from collections.abc import Sequence from collections.abc import Sequence
from io import BytesIO from io import BytesIO
from typing import cast
from . import Image, ImageFile from . import Image, ImageFile
from ._binary import i16be as i16 from ._binary import i16be as i16
@ -184,7 +185,9 @@ Image.register_open(IptcImageFile.format, IptcImageFile)
Image.register_extension(IptcImageFile.format, ".iim") Image.register_extension(IptcImageFile.format, ".iim")
def getiptcinfo(im): def getiptcinfo(
im: ImageFile.ImageFile,
) -> dict[tuple[int, int], bytes | list[bytes]] | None:
""" """
Get IPTC information from TIFF, JPEG, or IPTC file. Get IPTC information from TIFF, JPEG, or IPTC file.
@ -221,16 +224,17 @@ def getiptcinfo(im):
class FakeImage: class FakeImage:
pass pass
im = FakeImage() fake_im = FakeImage()
im.__class__ = IptcImageFile fake_im.__class__ = IptcImageFile # type: ignore[assignment]
iptc_im = cast(IptcImageFile, fake_im)
# parse the IPTC information chunk # parse the IPTC information chunk
im.info = {} iptc_im.info = {}
im.fp = BytesIO(data) iptc_im.fp = BytesIO(data)
try: try:
im._open() iptc_im._open()
except (IndexError, KeyError): except (IndexError, KeyError):
pass # expected failure pass # expected failure
return im.info return iptc_im.info

View File

@ -29,7 +29,7 @@ class BoxReader:
and to easily step into and read sub-boxes. and to easily step into and read sub-boxes.
""" """
def __init__(self, fp, length=-1): def __init__(self, fp: IO[bytes], length: int = -1) -> None:
self.fp = fp self.fp = fp
self.has_length = length >= 0 self.has_length = length >= 0
self.length = length self.length = length
@ -97,7 +97,7 @@ class BoxReader:
return tbox return tbox
def _parse_codestream(fp) -> tuple[tuple[int, int], str]: def _parse_codestream(fp: IO[bytes]) -> tuple[tuple[int, int], str]:
"""Parse the JPEG 2000 codestream to extract the size and component """Parse the JPEG 2000 codestream to extract the size and component
count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
@ -137,7 +137,15 @@ def _res_to_dpi(num: int, denom: int, exp: int) -> float | None:
return (254 * num * (10**exp)) / (10000 * denom) return (254 * num * (10**exp)) / (10000 * denom)
def _parse_jp2_header(fp): def _parse_jp2_header(
fp: IO[bytes],
) -> tuple[
tuple[int, int],
str,
str | None,
tuple[float, float] | None,
ImagePalette.ImagePalette | None,
]:
"""Parse the JP2 header box to extract size, component count, """Parse the JP2 header box to extract size, component count,
color space information, and optionally DPI information, color space information, and optionally DPI information,
returning a (size, mode, mimetype, dpi) tuple.""" returning a (size, mode, mimetype, dpi) tuple."""
@ -155,6 +163,7 @@ def _parse_jp2_header(fp):
elif tbox == b"ftyp": elif tbox == b"ftyp":
if reader.read_fields(">4s")[0] == b"jpx ": if reader.read_fields(">4s")[0] == b"jpx ":
mimetype = "image/jpx" mimetype = "image/jpx"
assert header is not None
size = None size = None
mode = None mode = None
@ -168,6 +177,9 @@ def _parse_jp2_header(fp):
if tbox == b"ihdr": if tbox == b"ihdr":
height, width, nc, bpc = header.read_fields(">IIHB") height, width, nc, bpc = header.read_fields(">IIHB")
assert isinstance(height, int)
assert isinstance(width, int)
assert isinstance(bpc, int)
size = (width, height) size = (width, height)
if nc == 1 and (bpc & 0x7F) > 8: if nc == 1 and (bpc & 0x7F) > 8:
mode = "I;16" mode = "I;16"
@ -185,11 +197,21 @@ def _parse_jp2_header(fp):
mode = "CMYK" mode = "CMYK"
elif tbox == b"pclr" and mode in ("L", "LA"): elif tbox == b"pclr" and mode in ("L", "LA"):
ne, npc = header.read_fields(">HB") ne, npc = header.read_fields(">HB")
bitdepths = header.read_fields(">" + ("B" * npc)) assert isinstance(ne, int)
if max(bitdepths) <= 8: assert isinstance(npc, int)
max_bitdepth = 0
for bitdepth in header.read_fields(">" + ("B" * npc)):
assert isinstance(bitdepth, int)
if bitdepth > max_bitdepth:
max_bitdepth = bitdepth
if max_bitdepth <= 8:
palette = ImagePalette.ImagePalette() palette = ImagePalette.ImagePalette()
for i in range(ne): for i in range(ne):
palette.getcolor(header.read_fields(">" + ("B" * npc))) color: list[int] = []
for value in header.read_fields(">" + ("B" * npc)):
assert isinstance(value, int)
color.append(value)
palette.getcolor(tuple(color))
mode = "P" if mode == "L" else "PA" mode = "P" if mode == "L" else "PA"
elif tbox == b"res ": elif tbox == b"res ":
res = header.read_boxes() res = header.read_boxes()
@ -197,6 +219,12 @@ def _parse_jp2_header(fp):
tres = res.next_box_type() tres = res.next_box_type()
if tres == b"resc": if tres == b"resc":
vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB") vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB")
assert isinstance(vrcn, int)
assert isinstance(vrcd, int)
assert isinstance(hrcn, int)
assert isinstance(hrcd, int)
assert isinstance(vrce, int)
assert isinstance(hrce, int)
hres = _res_to_dpi(hrcn, hrcd, hrce) hres = _res_to_dpi(hrcn, hrcd, hrce)
vres = _res_to_dpi(vrcn, vrcd, vrce) vres = _res_to_dpi(vrcn, vrcd, vrce)
if hres is not None and vres is not None: if hres is not None and vres is not None:

View File

@ -60,7 +60,7 @@ def Skip(self: JpegImageFile, marker: int) -> None:
ImageFile._safe_read(self.fp, n) ImageFile._safe_read(self.fp, n)
def APP(self, marker): def APP(self: JpegImageFile, marker: int) -> None:
# #
# Application marker. Store these in the APP dictionary. # Application marker. Store these in the APP dictionary.
# Also look for well-known application markers. # Also look for well-known application markers.
@ -133,12 +133,13 @@ def APP(self, marker):
offset += 4 offset += 4
data = s[offset : offset + size] data = s[offset : offset + size]
if code == 0x03ED: # ResolutionInfo if code == 0x03ED: # ResolutionInfo
data = { photoshop[code] = {
"XResolution": i32(data, 0) / 65536, "XResolution": i32(data, 0) / 65536,
"DisplayedUnitsX": i16(data, 4), "DisplayedUnitsX": i16(data, 4),
"YResolution": i32(data, 8) / 65536, "YResolution": i32(data, 8) / 65536,
"DisplayedUnitsY": i16(data, 12), "DisplayedUnitsY": i16(data, 12),
} }
else:
photoshop[code] = data photoshop[code] = data
offset += size offset += size
offset += offset & 1 # align offset += offset & 1 # align
@ -338,6 +339,7 @@ class JpegImageFile(ImageFile.ImageFile):
# Create attributes # Create attributes
self.bits = self.layers = 0 self.bits = self.layers = 0
self._exif_offset = 0
# JPEG specifics (internal) # JPEG specifics (internal)
self.layer = [] self.layer = []
@ -466,7 +468,7 @@ class JpegImageFile(ImageFile.ImageFile):
self.tile = [] self.tile = []
def _getexif(self) -> dict[str, Any] | None: def _getexif(self) -> dict[int, Any] | None:
return _getexif(self) return _getexif(self)
def _read_dpi_from_exif(self) -> None: def _read_dpi_from_exif(self) -> None:
@ -498,17 +500,17 @@ class JpegImageFile(ImageFile.ImageFile):
): ):
self.info["dpi"] = 72, 72 self.info["dpi"] = 72, 72
def _getmp(self): def _getmp(self) -> dict[int, Any] | None:
return _getmp(self) return _getmp(self)
def _getexif(self) -> dict[str, Any] | None: def _getexif(self: JpegImageFile) -> dict[int, Any] | None:
if "exif" not in self.info: if "exif" not in self.info:
return None return None
return self.getexif()._get_merged_dict() return self.getexif()._get_merged_dict()
def _getmp(self): def _getmp(self: JpegImageFile) -> dict[int, Any] | None:
# Extract MP information. This method was inspired by the "highly # Extract MP information. This method was inspired by the "highly
# experimental" _getexif version that's been in use for years now, # experimental" _getexif version that's been in use for years now,
# itself based on the ImageFileDirectory class in the TIFF plugin. # itself based on the ImageFileDirectory class in the TIFF plugin.
@ -616,7 +618,7 @@ samplings = {
# fmt: on # fmt: on
def get_sampling(im): def get_sampling(im: Image.Image) -> int:
# There's no subsampling when images have only 1 layer # There's no subsampling when images have only 1 layer
# (grayscale images) or when they are CMYK (4 layers), # (grayscale images) or when they are CMYK (4 layers),
# so set subsampling to the default value. # so set subsampling to the default value.
@ -624,7 +626,7 @@ def get_sampling(im):
# NOTE: currently Pillow can't encode JPEG to YCCK format. # NOTE: currently Pillow can't encode JPEG to YCCK format.
# If YCCK support is added in the future, subsampling code will have # If YCCK support is added in the future, subsampling code will have
# to be updated (here and in JpegEncode.c) to deal with 4 layers. # to be updated (here and in JpegEncode.c) to deal with 4 layers.
if not hasattr(im, "layers") or im.layers in (1, 4): if not isinstance(im, JpegImageFile) or im.layers in (1, 4):
return -1 return -1
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
return samplings.get(sampling, -1) return samplings.get(sampling, -1)
@ -683,7 +685,11 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
raise ValueError(msg) raise ValueError(msg)
subsampling = get_sampling(im) subsampling = get_sampling(im)
def validate_qtables(qtables): def validate_qtables(
qtables: (
str | tuple[list[int], ...] | list[list[int]] | dict[int, list[int]] | None
)
) -> list[list[int]] | None:
if qtables is None: if qtables is None:
return qtables return qtables
if isinstance(qtables, str): if isinstance(qtables, str):
@ -713,12 +719,12 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if len(table) != 64: if len(table) != 64:
msg = "Invalid quantization table" msg = "Invalid quantization table"
raise TypeError(msg) raise TypeError(msg)
table = array.array("H", table) table_array = array.array("H", table)
except TypeError as e: except TypeError as e:
msg = "Invalid quantization table" msg = "Invalid quantization table"
raise ValueError(msg) from e raise ValueError(msg) from e
else: else:
qtables[idx] = list(table) qtables[idx] = list(table_array)
return qtables return qtables
if qtables == "keep": if qtables == "keep":
@ -825,11 +831,11 @@ def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
## ##
# Factory for making JPEG and MPO instances # Factory for making JPEG and MPO instances
def jpeg_factory(fp=None, filename=None): def jpeg_factory(fp: IO[bytes] | None = None, filename: str | bytes | None = None):
im = JpegImageFile(fp, filename) im = JpegImageFile(fp, filename)
try: try:
mpheader = im._getmp() mpheader = im._getmp()
if mpheader[45057] > 1: if mpheader is not None and mpheader[45057] > 1:
for segment, content in im.applist: for segment, content in im.applist:
if segment == "APP1" and b' hdrgm:Version="' in content: if segment == "APP1" and b' hdrgm:Version="' in content:
# Ultra HDR images are not yet supported # Ultra HDR images are not yet supported

View File

@ -37,7 +37,7 @@ You can get the subsampling of a JPEG with the
:func:`.JpegImagePlugin.get_sampling` function. :func:`.JpegImagePlugin.get_sampling` function.
In JPEG compressed data a JPEG marker is used instead of an EXIF tag. In JPEG compressed data a JPEG marker is used instead of an EXIF tag.
(ref.: https://web.archive.org/web/20240227115053/https://exiv2.org/tags.html) (ref.: https://exiv2.org/tags.html)
Quantization tables Quantization tables

View File

@ -22,7 +22,7 @@ from __future__ import annotations
import itertools import itertools
import os import os
import struct import struct
from typing import IO from typing import IO, Any, cast
from . import ( from . import (
Image, Image,
@ -101,8 +101,11 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
JpegImagePlugin.JpegImageFile._open(self) JpegImagePlugin.JpegImageFile._open(self)
self._after_jpeg_open() self._after_jpeg_open()
def _after_jpeg_open(self, mpheader=None): def _after_jpeg_open(self, mpheader: dict[int, Any] | None = None) -> None:
self.mpinfo = mpheader if mpheader is not None else self._getmp() self.mpinfo = mpheader if mpheader is not None else self._getmp()
if self.mpinfo is None:
msg = "Image appears to be a malformed MPO file"
raise ValueError(msg)
self.n_frames = self.mpinfo[0xB001] self.n_frames = self.mpinfo[0xB001]
self.__mpoffsets = [ self.__mpoffsets = [
mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002] mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002]
@ -149,7 +152,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
return self.__frame return self.__frame
@staticmethod @staticmethod
def adopt(jpeg_instance, mpheader=None): def adopt(
jpeg_instance: JpegImagePlugin.JpegImageFile,
mpheader: dict[int, Any] | None = None,
) -> MpoImageFile:
""" """
Transform the instance of JpegImageFile into Transform the instance of JpegImageFile into
an instance of MpoImageFile. an instance of MpoImageFile.
@ -161,8 +167,9 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
double call to _open. double call to _open.
""" """
jpeg_instance.__class__ = MpoImageFile jpeg_instance.__class__ = MpoImageFile
jpeg_instance._after_jpeg_open(mpheader) mpo_instance = cast(MpoImageFile, jpeg_instance)
return jpeg_instance mpo_instance._after_jpeg_open(mpheader)
return mpo_instance
# --------------------------------------------------------------------- # ---------------------------------------------------------------------

View File

@ -174,12 +174,15 @@ def _write_image(im, filename, existing_pdf, image_refs):
return image_ref, procset return image_ref, procset
def _save(im, fp, filename, save_all=False): def _save(
im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False
) -> None:
is_appending = im.encoderinfo.get("append", False) is_appending = im.encoderinfo.get("append", False)
filename_str = filename.decode() if isinstance(filename, bytes) else filename
if is_appending: if is_appending:
existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="r+b") existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="r+b")
else: else:
existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b") existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="w+b")
dpi = im.encoderinfo.get("dpi") dpi = im.encoderinfo.get("dpi")
if dpi: if dpi:
@ -228,12 +231,7 @@ def _save(im, fp, filename, save_all=False):
for im in ims: for im in ims:
im_number_of_pages = 1 im_number_of_pages = 1
if save_all: if save_all:
try: im_number_of_pages = getattr(im, "n_frames", 1)
im_number_of_pages = im.n_frames
except AttributeError:
# Image format does not have n_frames.
# It is a single frame image
pass
number_of_pages += im_number_of_pages number_of_pages += im_number_of_pages
for i in range(im_number_of_pages): for i in range(im_number_of_pages):
image_refs.append(existing_pdf.next_object_id(0)) image_refs.append(existing_pdf.next_object_id(0))
@ -250,7 +248,9 @@ def _save(im, fp, filename, save_all=False):
page_number = 0 page_number = 0
for im_sequence in ims: for im_sequence in ims:
im_pages = ImageSequence.Iterator(im_sequence) if save_all else [im_sequence] im_pages: ImageSequence.Iterator | list[Image.Image] = (
ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
)
for im in im_pages: for im in im_pages:
image_ref, procset = _write_image(im, filename, existing_pdf, image_refs) image_ref, procset = _write_image(im, filename, existing_pdf, image_refs)

View File

@ -38,8 +38,9 @@ import re
import struct import struct
import warnings import warnings
import zlib import zlib
from collections.abc import Callable
from enum import IntEnum from enum import IntEnum
from typing import IO, TYPE_CHECKING, Any, NoReturn from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
from ._binary import i16be as i16 from ._binary import i16be as i16
@ -135,7 +136,7 @@ class Blend(IntEnum):
""" """
def _safe_zlib_decompress(s): def _safe_zlib_decompress(s: bytes) -> bytes:
dobj = zlib.decompressobj() dobj = zlib.decompressobj()
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK) plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
if dobj.unconsumed_tail: if dobj.unconsumed_tail:
@ -144,7 +145,7 @@ def _safe_zlib_decompress(s):
return plaintext return plaintext
def _crc32(data, seed=0): def _crc32(data: bytes, seed: int = 0) -> int:
return zlib.crc32(data, seed) & 0xFFFFFFFF return zlib.crc32(data, seed) & 0xFFFFFFFF
@ -191,7 +192,7 @@ class ChunkStream:
assert self.queue is not None assert self.queue is not None
self.queue.append((cid, pos, length)) self.queue.append((cid, pos, length))
def call(self, cid, pos, length): def call(self, cid: bytes, pos: int, length: int) -> bytes:
"""Call the appropriate chunk handler""" """Call the appropriate chunk handler"""
logger.debug("STREAM %r %s %s", cid, pos, length) logger.debug("STREAM %r %s %s", cid, pos, length)
@ -230,6 +231,7 @@ class ChunkStream:
cids = [] cids = []
assert self.fp is not None
while True: while True:
try: try:
cid, pos, length = self.read() cid, pos, length = self.read()
@ -407,6 +409,7 @@ class PngStream(ChunkStream):
def chunk_iCCP(self, pos: int, length: int) -> bytes: def chunk_iCCP(self, pos: int, length: int) -> bytes:
# ICC profile # ICC profile
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
# according to PNG spec, the iCCP chunk contains: # according to PNG spec, the iCCP chunk contains:
# Profile name 1-79 bytes (character string) # Profile name 1-79 bytes (character string)
@ -434,6 +437,7 @@ class PngStream(ChunkStream):
def chunk_IHDR(self, pos: int, length: int) -> bytes: def chunk_IHDR(self, pos: int, length: int) -> bytes:
# image header # image header
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
if length < 13: if length < 13:
if ImageFile.LOAD_TRUNCATED_IMAGES: if ImageFile.LOAD_TRUNCATED_IMAGES:
@ -471,6 +475,7 @@ class PngStream(ChunkStream):
def chunk_PLTE(self, pos: int, length: int) -> bytes: def chunk_PLTE(self, pos: int, length: int) -> bytes:
# palette # palette
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P": if self.im_mode == "P":
self.im_palette = "RGB", s self.im_palette = "RGB", s
@ -478,6 +483,7 @@ class PngStream(ChunkStream):
def chunk_tRNS(self, pos: int, length: int) -> bytes: def chunk_tRNS(self, pos: int, length: int) -> bytes:
# transparency # transparency
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
if self.im_mode == "P": if self.im_mode == "P":
if _simple_palette.match(s): if _simple_palette.match(s):
@ -498,6 +504,7 @@ class PngStream(ChunkStream):
def chunk_gAMA(self, pos: int, length: int) -> bytes: def chunk_gAMA(self, pos: int, length: int) -> bytes:
# gamma setting # gamma setting
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
self.im_info["gamma"] = i32(s) / 100000.0 self.im_info["gamma"] = i32(s) / 100000.0
return s return s
@ -506,6 +513,7 @@ class PngStream(ChunkStream):
# chromaticity, 8 unsigned ints, actual value is scaled by 100,000 # chromaticity, 8 unsigned ints, actual value is scaled by 100,000
# WP x,y, Red x,y, Green x,y Blue x,y # WP x,y, Red x,y, Green x,y Blue x,y
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
raw_vals = struct.unpack(">%dI" % (len(s) // 4), s) raw_vals = struct.unpack(">%dI" % (len(s) // 4), s)
self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals) self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
@ -518,6 +526,7 @@ class PngStream(ChunkStream):
# 2 saturation # 2 saturation
# 3 absolute colorimetric # 3 absolute colorimetric
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
if length < 1: if length < 1:
if ImageFile.LOAD_TRUNCATED_IMAGES: if ImageFile.LOAD_TRUNCATED_IMAGES:
@ -529,6 +538,7 @@ class PngStream(ChunkStream):
def chunk_pHYs(self, pos: int, length: int) -> bytes: def chunk_pHYs(self, pos: int, length: int) -> bytes:
# pixels per unit # pixels per unit
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
if length < 9: if length < 9:
if ImageFile.LOAD_TRUNCATED_IMAGES: if ImageFile.LOAD_TRUNCATED_IMAGES:
@ -546,6 +556,7 @@ class PngStream(ChunkStream):
def chunk_tEXt(self, pos: int, length: int) -> bytes: def chunk_tEXt(self, pos: int, length: int) -> bytes:
# text # text
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
@ -554,17 +565,18 @@ class PngStream(ChunkStream):
k = s k = s
v = b"" v = b""
if k: if k:
k = k.decode("latin-1", "strict") k_str = k.decode("latin-1", "strict")
v_str = v.decode("latin-1", "replace") v_str = v.decode("latin-1", "replace")
self.im_info[k] = v if k == "exif" else v_str self.im_info[k_str] = v if k == b"exif" else v_str
self.im_text[k] = v_str self.im_text[k_str] = v_str
self.check_text_memory(len(v_str)) self.check_text_memory(len(v_str))
return s return s
def chunk_zTXt(self, pos: int, length: int) -> bytes: def chunk_zTXt(self, pos: int, length: int) -> bytes:
# compressed text # compressed text
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
try: try:
k, v = s.split(b"\0", 1) k, v = s.split(b"\0", 1)
@ -589,16 +601,17 @@ class PngStream(ChunkStream):
v = b"" v = b""
if k: if k:
k = k.decode("latin-1", "strict") k_str = k.decode("latin-1", "strict")
v = v.decode("latin-1", "replace") v_str = v.decode("latin-1", "replace")
self.im_info[k] = self.im_text[k] = v self.im_info[k_str] = self.im_text[k_str] = v_str
self.check_text_memory(len(v)) self.check_text_memory(len(v_str))
return s return s
def chunk_iTXt(self, pos: int, length: int) -> bytes: def chunk_iTXt(self, pos: int, length: int) -> bytes:
# international text # international text
assert self.fp is not None
r = s = ImageFile._safe_read(self.fp, length) r = s = ImageFile._safe_read(self.fp, length)
try: try:
k, r = r.split(b"\0", 1) k, r = r.split(b"\0", 1)
@ -627,25 +640,27 @@ class PngStream(ChunkStream):
if k == b"XML:com.adobe.xmp": if k == b"XML:com.adobe.xmp":
self.im_info["xmp"] = v self.im_info["xmp"] = v
try: try:
k = k.decode("latin-1", "strict") k_str = k.decode("latin-1", "strict")
lang = lang.decode("utf-8", "strict") lang_str = lang.decode("utf-8", "strict")
tk = tk.decode("utf-8", "strict") tk_str = tk.decode("utf-8", "strict")
v = v.decode("utf-8", "strict") v_str = v.decode("utf-8", "strict")
except UnicodeError: except UnicodeError:
return s return s
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk) self.im_info[k_str] = self.im_text[k_str] = iTXt(v_str, lang_str, tk_str)
self.check_text_memory(len(v)) self.check_text_memory(len(v_str))
return s return s
def chunk_eXIf(self, pos: int, length: int) -> bytes: def chunk_eXIf(self, pos: int, length: int) -> bytes:
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
self.im_info["exif"] = b"Exif\x00\x00" + s self.im_info["exif"] = b"Exif\x00\x00" + s
return s return s
# APNG chunks # APNG chunks
def chunk_acTL(self, pos: int, length: int) -> bytes: def chunk_acTL(self, pos: int, length: int) -> bytes:
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
if length < 8: if length < 8:
if ImageFile.LOAD_TRUNCATED_IMAGES: if ImageFile.LOAD_TRUNCATED_IMAGES:
@ -666,6 +681,7 @@ class PngStream(ChunkStream):
return s return s
def chunk_fcTL(self, pos: int, length: int) -> bytes: def chunk_fcTL(self, pos: int, length: int) -> bytes:
assert self.fp is not None
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
if length < 26: if length < 26:
if ImageFile.LOAD_TRUNCATED_IMAGES: if ImageFile.LOAD_TRUNCATED_IMAGES:
@ -695,6 +711,7 @@ class PngStream(ChunkStream):
return s return s
def chunk_fdAT(self, pos: int, length: int) -> bytes: def chunk_fdAT(self, pos: int, length: int) -> bytes:
assert self.fp is not None
if length < 4: if length < 4:
if ImageFile.LOAD_TRUNCATED_IMAGES: if ImageFile.LOAD_TRUNCATED_IMAGES:
s = ImageFile._safe_read(self.fp, length) s = ImageFile._safe_read(self.fp, length)
@ -767,7 +784,7 @@ class PngImageFile(ImageFile.ImageFile):
self._mode = self.png.im_mode self._mode = self.png.im_mode
self._size = self.png.im_size self._size = self.png.im_size
self.info = self.png.im_info self.info = self.png.im_info
self._text = None self._text: dict[str, str | iTXt] | None = None
self.tile = self.png.im_tile self.tile = self.png.im_tile
self.custom_mimetype = self.png.im_custom_mimetype self.custom_mimetype = self.png.im_custom_mimetype
self.n_frames = self.png.im_n_frames or 1 self.n_frames = self.png.im_n_frames or 1
@ -794,7 +811,7 @@ class PngImageFile(ImageFile.ImageFile):
self.is_animated = self.n_frames > 1 self.is_animated = self.n_frames > 1
@property @property
def text(self): def text(self) -> dict[str, str | iTXt]:
# experimental # experimental
if self._text is None: if self._text is None:
# iTxt, tEXt and zTXt chunks may appear at the end of the file # iTxt, tEXt and zTXt chunks may appear at the end of the file
@ -806,6 +823,7 @@ class PngImageFile(ImageFile.ImageFile):
self.load() self.load()
if self.is_animated: if self.is_animated:
self.seek(frame) self.seek(frame)
assert self._text is not None
return self._text return self._text
def verify(self) -> None: def verify(self) -> None:
@ -1038,7 +1056,7 @@ class PngImageFile(ImageFile.ImageFile):
self._prev_im.paste(updated, self.dispose_extent, mask) self._prev_im.paste(updated, self.dispose_extent, mask)
self.im = self._prev_im self.im = self._prev_im
def _getexif(self) -> dict[str, Any] | None: def _getexif(self) -> dict[int, Any] | None:
if "exif" not in self.info: if "exif" not in self.info:
self.load() self.load()
if "exif" not in self.info and "Raw profile type exif" not in self.info: if "exif" not in self.info and "Raw profile type exif" not in self.info:
@ -1075,21 +1093,21 @@ _OUTMODES = {
} }
def putchunk(fp, cid, *data): def putchunk(fp: IO[bytes], cid: bytes, *data: bytes) -> None:
"""Write a PNG chunk (including CRC field)""" """Write a PNG chunk (including CRC field)"""
data = b"".join(data) byte_data = b"".join(data)
fp.write(o32(len(data)) + cid) fp.write(o32(len(byte_data)) + cid)
fp.write(data) fp.write(byte_data)
crc = _crc32(data, _crc32(cid)) crc = _crc32(byte_data, _crc32(cid))
fp.write(o32(crc)) fp.write(o32(crc))
class _idat: class _idat:
# wrap output from the encoder in IDAT chunks # wrap output from the encoder in IDAT chunks
def __init__(self, fp, chunk): def __init__(self, fp: IO[bytes], chunk: Callable[..., None]) -> None:
self.fp = fp self.fp = fp
self.chunk = chunk self.chunk = chunk
@ -1100,7 +1118,7 @@ class _idat:
class _fdat: class _fdat:
# wrap encoder output in fdAT chunks # wrap encoder output in fdAT chunks
def __init__(self, fp, chunk, seq_num): def __init__(self, fp: IO[bytes], chunk: Callable[..., None], seq_num: int) -> None:
self.fp = fp self.fp = fp
self.chunk = chunk self.chunk = chunk
self.seq_num = seq_num self.seq_num = seq_num
@ -1110,7 +1128,21 @@ class _fdat:
self.seq_num += 1 self.seq_num += 1
def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_images): class _Frame(NamedTuple):
im: Image.Image
bbox: tuple[int, int, int, int] | None
encoderinfo: dict[str, Any]
def _write_multiple_frames(
im: Image.Image,
fp: IO[bytes],
chunk: Callable[..., None],
mode: str,
rawmode: str,
default_image: Image.Image | None,
append_images: list[Image.Image],
) -> Image.Image | None:
duration = im.encoderinfo.get("duration") duration = im.encoderinfo.get("duration")
loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) loop = im.encoderinfo.get("loop", im.info.get("loop", 0))
disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE)) disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE))
@ -1121,7 +1153,7 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i
else: else:
chain = itertools.chain([im], append_images) chain = itertools.chain([im], append_images)
im_frames = [] im_frames: list[_Frame] = []
frame_count = 0 frame_count = 0
for im_seq in chain: for im_seq in chain:
for im_frame in ImageSequence.Iterator(im_seq): for im_frame in ImageSequence.Iterator(im_seq):
@ -1142,24 +1174,24 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i
if im_frames: if im_frames:
previous = im_frames[-1] previous = im_frames[-1]
prev_disposal = previous["encoderinfo"].get("disposal") prev_disposal = previous.encoderinfo.get("disposal")
prev_blend = previous["encoderinfo"].get("blend") prev_blend = previous.encoderinfo.get("blend")
if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2: if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2:
prev_disposal = Disposal.OP_BACKGROUND prev_disposal = Disposal.OP_BACKGROUND
if prev_disposal == Disposal.OP_BACKGROUND: if prev_disposal == Disposal.OP_BACKGROUND:
base_im = previous["im"].copy() base_im = previous.im.copy()
dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0)) dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0))
bbox = previous["bbox"] bbox = previous.bbox
if bbox: if bbox:
dispose = dispose.crop(bbox) dispose = dispose.crop(bbox)
else: else:
bbox = (0, 0) + im.size bbox = (0, 0) + im.size
base_im.paste(dispose, bbox) base_im.paste(dispose, bbox)
elif prev_disposal == Disposal.OP_PREVIOUS: elif prev_disposal == Disposal.OP_PREVIOUS:
base_im = im_frames[-2]["im"] base_im = im_frames[-2].im
else: else:
base_im = previous["im"] base_im = previous.im
delta = ImageChops.subtract_modulo( delta = ImageChops.subtract_modulo(
im_frame.convert("RGBA"), base_im.convert("RGBA") im_frame.convert("RGBA"), base_im.convert("RGBA")
) )
@ -1170,14 +1202,14 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i
and prev_blend == encoderinfo.get("blend") and prev_blend == encoderinfo.get("blend")
and "duration" in encoderinfo and "duration" in encoderinfo
): ):
previous["encoderinfo"]["duration"] += encoderinfo["duration"] previous.encoderinfo["duration"] += encoderinfo["duration"]
continue continue
else: else:
bbox = None bbox = None
im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) im_frames.append(_Frame(im_frame, bbox, encoderinfo))
if len(im_frames) == 1 and not default_image: if len(im_frames) == 1 and not default_image:
return im_frames[0]["im"] return im_frames[0].im
# animation control # animation control
chunk( chunk(
@ -1195,14 +1227,14 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i
seq_num = 0 seq_num = 0
for frame, frame_data in enumerate(im_frames): for frame, frame_data in enumerate(im_frames):
im_frame = frame_data["im"] im_frame = frame_data.im
if not frame_data["bbox"]: if not frame_data.bbox:
bbox = (0, 0) + im_frame.size bbox = (0, 0) + im_frame.size
else: else:
bbox = frame_data["bbox"] bbox = frame_data.bbox
im_frame = im_frame.crop(bbox) im_frame = im_frame.crop(bbox)
size = im_frame.size size = im_frame.size
encoderinfo = frame_data["encoderinfo"] encoderinfo = frame_data.encoderinfo
frame_duration = int(round(encoderinfo.get("duration", 0))) frame_duration = int(round(encoderinfo.get("duration", 0)))
frame_disposal = encoderinfo.get("disposal", disposal) frame_disposal = encoderinfo.get("disposal", disposal)
frame_blend = encoderinfo.get("blend", blend) frame_blend = encoderinfo.get("blend", blend)
@ -1237,13 +1269,20 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i
[("zip", (0, 0) + im_frame.size, 0, rawmode)], [("zip", (0, 0) + im_frame.size, 0, rawmode)],
) )
seq_num = fdat_chunks.seq_num seq_num = fdat_chunks.seq_num
return None
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
_save(im, fp, filename, save_all=True) _save(im, fp, filename, save_all=True)
def _save(im, fp, filename, chunk=putchunk, save_all=False): def _save(
im: Image.Image,
fp: IO[bytes],
filename: str | bytes,
chunk: Callable[..., None] = putchunk,
save_all: bool = False,
) -> None:
# save an image to disk (called by the save method) # save an image to disk (called by the save method)
if save_all: if save_all:
@ -1419,12 +1458,15 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
exif = exif[6:] exif = exif[6:]
chunk(fp, b"eXIf", exif) chunk(fp, b"eXIf", exif)
single_im: Image.Image | None = im
if save_all: if save_all:
im = _write_multiple_frames( single_im = _write_multiple_frames(
im, fp, chunk, mode, rawmode, default_image, append_images im, fp, chunk, mode, rawmode, default_image, append_images
) )
if im: if single_im:
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) ImageFile._save(
single_im, _idat(fp, chunk), [("zip", (0, 0) + single_im.size, 0, rawmode)]
)
if info: if info:
for info_chunk in info.chunks: for info_chunk in info.chunks:
@ -1445,32 +1487,26 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
# PNG chunk converter # PNG chunk converter
def getchunks(im, **params): def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]]:
"""Return a list of PNG chunks representing this image.""" """Return a list of PNG chunks representing this image."""
from io import BytesIO
class collector: chunks = []
data = []
def write(self, data: bytes) -> None: def append(fp: IO[bytes], cid: bytes, *data: bytes) -> None:
pass byte_data = b"".join(data)
crc = o32(_crc32(byte_data, _crc32(cid)))
chunks.append((cid, byte_data, crc))
def append(self, chunk: bytes) -> None: fp = BytesIO()
self.data.append(chunk)
def append(fp, cid, *data):
data = b"".join(data)
crc = o32(_crc32(data, _crc32(cid)))
fp.append((cid, data, crc))
fp = collector()
try: try:
im.encoderinfo = params im.encoderinfo = params
_save(im, fp, None, append) _save(im, fp, "", append)
finally: finally:
del im.encoderinfo del im.encoderinfo
return fp.data return chunks
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -19,6 +19,7 @@ from __future__ import annotations
import io import io
from functools import cached_property from functools import cached_property
from typing import IO
from . import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from ._binary import i8 from ._binary import i8
@ -142,7 +143,9 @@ class PsdImageFile(ImageFile.ImageFile):
self._min_frame = 1 self._min_frame = 1
@cached_property @cached_property
def layers(self): def layers(
self,
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
layers = [] layers = []
if self._layers_position is not None: if self._layers_position is not None:
self._fp.seek(self._layers_position) self._fp.seek(self._layers_position)
@ -181,11 +184,13 @@ class PsdImageFile(ImageFile.ImageFile):
return self.frame return self.frame
def _layerinfo(fp, ct_bytes): def _layerinfo(
fp: IO[bytes], ct_bytes: int
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
# read layerinfo block # read layerinfo block
layers = [] layers = []
def read(size): def read(size: int) -> bytes:
return ImageFile._safe_read(fp, size) return ImageFile._safe_read(fp, size)
ct = si16(read(2)) ct = si16(read(2))
@ -203,7 +208,7 @@ def _layerinfo(fp, ct_bytes):
x1 = si32(read(4)) x1 = si32(read(4))
# image info # image info
mode = [] bands = []
ct_types = i16(read(2)) ct_types = i16(read(2))
if ct_types > 4: if ct_types > 4:
fp.seek(ct_types * 6 + 12, io.SEEK_CUR) fp.seek(ct_types * 6 + 12, io.SEEK_CUR)
@ -215,23 +220,23 @@ def _layerinfo(fp, ct_bytes):
type = i16(read(2)) type = i16(read(2))
if type == 65535: if type == 65535:
m = "A" b = "A"
else: else:
m = "RGBA"[type] b = "RGBA"[type]
mode.append(m) bands.append(b)
read(4) # size read(4) # size
# figure out the image mode # figure out the image mode
mode.sort() bands.sort()
if mode == ["R"]: if bands == ["R"]:
mode = "L" mode = "L"
elif mode == ["B", "G", "R"]: elif bands == ["B", "G", "R"]:
mode = "RGB" mode = "RGB"
elif mode == ["A", "B", "G", "R"]: elif bands == ["A", "B", "G", "R"]:
mode = "RGBA" mode = "RGBA"
else: else:
mode = None # unknown mode = "" # unknown
# skip over blend flags and extra information # skip over blend flags and extra information
read(12) # filler read(12) # filler
@ -258,19 +263,22 @@ def _layerinfo(fp, ct_bytes):
layers.append((name, mode, (x0, y0, x1, y1))) layers.append((name, mode, (x0, y0, x1, y1)))
# get tiles # get tiles
layerinfo = []
for i, (name, mode, bbox) in enumerate(layers): for i, (name, mode, bbox) in enumerate(layers):
tile = [] tile = []
for m in mode: for m in mode:
t = _maketile(fp, m, bbox, 1) t = _maketile(fp, m, bbox, 1)
if t: if t:
tile.extend(t) tile.extend(t)
layers[i] = name, mode, bbox, tile layerinfo.append((name, mode, bbox, tile))
return layers return layerinfo
def _maketile(file, mode, bbox, channels): def _maketile(
tile = None file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int
) -> list[ImageFile._Tile] | None:
tiles = None
read = file.read read = file.read
compression = i16(read(2)) compression = i16(read(2))
@ -283,26 +291,26 @@ def _maketile(file, mode, bbox, channels):
if compression == 0: if compression == 0:
# #
# raw compression # raw compression
tile = [] tiles = []
for channel in range(channels): for channel in range(channels):
layer = mode[channel] layer = mode[channel]
if mode == "CMYK": if mode == "CMYK":
layer += ";I" layer += ";I"
tile.append(("raw", bbox, offset, layer)) tiles.append(ImageFile._Tile("raw", bbox, offset, layer))
offset = offset + xsize * ysize offset = offset + xsize * ysize
elif compression == 1: elif compression == 1:
# #
# packbits compression # packbits compression
i = 0 i = 0
tile = [] tiles = []
bytecount = read(channels * ysize * 2) bytecount = read(channels * ysize * 2)
offset = file.tell() offset = file.tell()
for channel in range(channels): for channel in range(channels):
layer = mode[channel] layer = mode[channel]
if mode == "CMYK": if mode == "CMYK":
layer += ";I" layer += ";I"
tile.append(("packbits", bbox, offset, layer)) tiles.append(ImageFile._Tile("packbits", bbox, offset, layer))
for y in range(ysize): for y in range(ysize):
offset = offset + i16(bytecount, i) offset = offset + i16(bytecount, i)
i += 2 i += 2
@ -312,7 +320,7 @@ def _maketile(file, mode, bbox, channels):
if offset & 1: if offset & 1:
read(1) # padding read(1) # padding
return tile return tiles
# -------------------------------------------------------------------- # --------------------------------------------------------------------

View File

@ -55,13 +55,3 @@ class TarIO(ContainerIO.ContainerIO[bytes]):
# Open region # Open region
super().__init__(self.fh, self.fh.tell(), size) super().__init__(self.fh, self.fh.tell(), size)
# Context manager support
def __enter__(self) -> TarIO:
return self
def __exit__(self, *args: object) -> None:
self.close()
def close(self) -> None:
self.fh.close()

View File

@ -334,12 +334,13 @@ class IFDRational(Rational):
__slots__ = ("_numerator", "_denominator", "_val") __slots__ = ("_numerator", "_denominator", "_val")
def __init__(self, value, denominator=1): def __init__(self, value, denominator: int = 1) -> None:
""" """
:param value: either an integer numerator, a :param value: either an integer numerator, a
float/rational/other number, or an IFDRational float/rational/other number, or an IFDRational
:param denominator: Optional integer denominator :param denominator: Optional integer denominator
""" """
self._val: Fraction | float
if isinstance(value, IFDRational): if isinstance(value, IFDRational):
self._numerator = value.numerator self._numerator = value.numerator
self._denominator = value.denominator self._denominator = value.denominator
@ -444,7 +445,7 @@ class IFDRational(Rational):
__int__ = _delegate("__int__") __int__ = _delegate("__int__")
def _register_loader(idx, size): def _register_loader(idx: int, size: int):
def decorator(func): def decorator(func):
from .TiffTags import TYPES from .TiffTags import TYPES
@ -456,7 +457,7 @@ def _register_loader(idx, size):
return decorator return decorator
def _register_writer(idx): def _register_writer(idx: int):
def decorator(func): def decorator(func):
_write_dispatch[idx] = func # noqa: F821 _write_dispatch[idx] = func # noqa: F821
return func return func
@ -464,7 +465,7 @@ def _register_writer(idx):
return decorator return decorator
def _register_basic(idx_fmt_name): def _register_basic(idx_fmt_name: tuple[int, str, str]) -> None:
from .TiffTags import TYPES from .TiffTags import TYPES
idx, fmt, name = idx_fmt_name idx, fmt, name = idx_fmt_name
@ -583,8 +584,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
self.tagtype: dict[int, int] = {} self.tagtype: dict[int, int] = {}
""" Dictionary of tag types """ """ Dictionary of tag types """
self.reset() self.reset()
(self.next,) = ( self.next = (
self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:]) self._unpack("Q", ifh[8:])[0]
if self._bigtiff
else self._unpack("L", ifh[4:])[0]
) )
self._legacy_api = False self._legacy_api = False
@ -636,13 +639,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
val = (val,) val = (val,)
return val return val
def __contains__(self, tag): def __contains__(self, tag: object) -> bool:
return tag in self._tags_v2 or tag in self._tagdata return tag in self._tags_v2 or tag in self._tagdata
def __setitem__(self, tag, value): def __setitem__(self, tag: int, value) -> None:
self._setitem(tag, value, self.legacy_api) self._setitem(tag, value, self.legacy_api)
def _setitem(self, tag, value, legacy_api): def _setitem(self, tag: int, value, legacy_api: bool) -> None:
basetypes = (Number, bytes, str) basetypes = (Number, bytes, str)
info = TiffTags.lookup(tag, self.group) info = TiffTags.lookup(tag, self.group)
@ -730,10 +733,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
def __iter__(self): def __iter__(self):
return iter(set(self._tagdata) | set(self._tags_v2)) return iter(set(self._tagdata) | set(self._tags_v2))
def _unpack(self, fmt, data): def _unpack(self, fmt: str, data: bytes):
return struct.unpack(self._endian + fmt, data) return struct.unpack(self._endian + fmt, data)
def _pack(self, fmt, *values): def _pack(self, fmt: str, *values):
return struct.pack(self._endian + fmt, *values) return struct.pack(self._endian + fmt, *values)
list( list(
@ -754,11 +757,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
) )
@_register_loader(1, 1) # Basic type, except for the legacy API. @_register_loader(1, 1) # Basic type, except for the legacy API.
def load_byte(self, data, legacy_api=True): def load_byte(self, data: bytes, legacy_api: bool = True) -> bytes:
return data return data
@_register_writer(1) # Basic type, except for the legacy API. @_register_writer(1) # Basic type, except for the legacy API.
def write_byte(self, data): def write_byte(self, data: bytes | int | IFDRational) -> bytes:
if isinstance(data, IFDRational): if isinstance(data, IFDRational):
data = int(data) data = int(data)
if isinstance(data, int): if isinstance(data, int):
@ -766,13 +769,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
return data return data
@_register_loader(2, 1) @_register_loader(2, 1)
def load_string(self, data, legacy_api=True): def load_string(self, data: bytes, legacy_api: bool = True) -> str:
if data.endswith(b"\0"): if data.endswith(b"\0"):
data = data[:-1] data = data[:-1]
return data.decode("latin-1", "replace") return data.decode("latin-1", "replace")
@_register_writer(2) @_register_writer(2)
def write_string(self, value): def write_string(self, value: str | bytes | int) -> bytes:
# remerge of https://github.com/python-pillow/Pillow/pull/1416 # remerge of https://github.com/python-pillow/Pillow/pull/1416
if isinstance(value, int): if isinstance(value, int):
value = str(value) value = str(value)
@ -781,7 +784,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return value + b"\0" return value + b"\0"
@_register_loader(5, 8) @_register_loader(5, 8)
def load_rational(self, data, legacy_api=True): def load_rational(self, data, legacy_api: bool = True):
vals = self._unpack(f"{len(data) // 4}L", data) vals = self._unpack(f"{len(data) // 4}L", data)
def combine(a, b): def combine(a, b):
@ -790,17 +793,17 @@ class ImageFileDirectory_v2(_IFDv2Base):
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(5) @_register_writer(5)
def write_rational(self, *values): def write_rational(self, *values) -> bytes:
return b"".join( return b"".join(
self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values
) )
@_register_loader(7, 1) @_register_loader(7, 1)
def load_undefined(self, data, legacy_api=True): def load_undefined(self, data: bytes, legacy_api: bool = True) -> bytes:
return data return data
@_register_writer(7) @_register_writer(7)
def write_undefined(self, value): def write_undefined(self, value: bytes | int | IFDRational) -> bytes:
if isinstance(value, IFDRational): if isinstance(value, IFDRational):
value = int(value) value = int(value)
if isinstance(value, int): if isinstance(value, int):
@ -808,7 +811,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return value return value
@_register_loader(10, 8) @_register_loader(10, 8)
def load_signed_rational(self, data, legacy_api=True): def load_signed_rational(self, data: bytes, legacy_api: bool = True):
vals = self._unpack(f"{len(data) // 4}l", data) vals = self._unpack(f"{len(data) // 4}l", data)
def combine(a, b): def combine(a, b):
@ -817,13 +820,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
@_register_writer(10) @_register_writer(10)
def write_signed_rational(self, *values): def write_signed_rational(self, *values) -> bytes:
return b"".join( return b"".join(
self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31))) self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31)))
for frac in values for frac in values
) )
def _ensure_read(self, fp, size): def _ensure_read(self, fp: IO[bytes], size: int) -> bytes:
ret = fp.read(size) ret = fp.read(size)
if len(ret) != size: if len(ret) != size:
msg = ( msg = (
@ -977,7 +980,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
return result return result
def save(self, fp): def save(self, fp: IO[bytes]) -> int:
if fp.tell() == 0: # skip TIFF header on subsequent pages if fp.tell() == 0: # skip TIFF header on subsequent pages
# tiff header -- PIL always starts the first IFD at offset 8 # tiff header -- PIL always starts the first IFD at offset 8
fp.write(self._prefix + self._pack("HL", 42, 8)) fp.write(self._prefix + self._pack("HL", 42, 8))
@ -1017,7 +1020,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
.. deprecated:: 3.0.0 .. deprecated:: 3.0.0
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._legacy_api = True self._legacy_api = True
@ -1029,7 +1032,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
"""Dictionary of tag types""" """Dictionary of tag types"""
@classmethod @classmethod
def from_v2(cls, original): def from_v2(cls, original: ImageFileDirectory_v2) -> ImageFileDirectory_v1:
"""Returns an """Returns an
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
instance with the same data as is contained in the original instance with the same data as is contained in the original
@ -1063,7 +1066,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
ifd._tags_v2 = dict(self._tags_v2) ifd._tags_v2 = dict(self._tags_v2)
return ifd return ifd
def __contains__(self, tag): def __contains__(self, tag: object) -> bool:
return tag in self._tags_v1 or tag in self._tagdata return tag in self._tags_v1 or tag in self._tagdata
def __len__(self) -> int: def __len__(self) -> int:
@ -1072,7 +1075,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
def __iter__(self): def __iter__(self):
return iter(set(self._tagdata) | set(self._tags_v1)) return iter(set(self._tagdata) | set(self._tags_v1))
def __setitem__(self, tag, value): def __setitem__(self, tag: int, value) -> None:
for legacy_api in (False, True): for legacy_api in (False, True):
self._setitem(tag, value, legacy_api) self._setitem(tag, value, legacy_api)
@ -1122,7 +1125,7 @@ class TiffImageFile(ImageFile.ImageFile):
self.tag_v2 = ImageFileDirectory_v2(ifh) self.tag_v2 = ImageFileDirectory_v2(ifh)
# legacy IFD entries will be filled in later # legacy IFD entries will be filled in later
self.ifd = None self.ifd: ImageFileDirectory_v1 | None = None
# setup frame pointers # setup frame pointers
self.__first = self.__next = self.tag_v2.next self.__first = self.__next = self.tag_v2.next
@ -1139,13 +1142,15 @@ class TiffImageFile(ImageFile.ImageFile):
self._seek(0) self._seek(0)
@property @property
def n_frames(self): def n_frames(self) -> int:
if self._n_frames is None: current_n_frames = self._n_frames
if current_n_frames is None:
current = self.tell() current = self.tell()
self._seek(len(self._frame_pos)) self._seek(len(self._frame_pos))
while self._n_frames is None: while self._n_frames is None:
self._seek(self.tell() + 1) self._seek(self.tell() + 1)
self.seek(current) self.seek(current)
assert self._n_frames is not None
return self._n_frames return self._n_frames
def seek(self, frame: int) -> None: def seek(self, frame: int) -> None:
@ -1211,7 +1216,7 @@ class TiffImageFile(ImageFile.ImageFile):
"""Return the current frame number""" """Return the current frame number"""
return self.__frame return self.__frame
def get_photoshop_blocks(self): def get_photoshop_blocks(self) -> dict[int, dict[str, bytes]]:
""" """
Returns a dictionary of Photoshop "Image Resource Blocks". Returns a dictionary of Photoshop "Image Resource Blocks".
The keys are the image resource ID. For more information, see The keys are the image resource ID. For more information, see
@ -1258,7 +1263,7 @@ class TiffImageFile(ImageFile.ImageFile):
if ExifTags.Base.Orientation in self.tag_v2: if ExifTags.Base.Orientation in self.tag_v2:
del self.tag_v2[ExifTags.Base.Orientation] del self.tag_v2[ExifTags.Base.Orientation]
def _load_libtiff(self): def _load_libtiff(self) -> Image.core.PixelAccess | None:
"""Overload method triggered when we detect a compressed tiff """Overload method triggered when we detect a compressed tiff
Calls out to libtiff""" Calls out to libtiff"""
@ -1343,7 +1348,7 @@ class TiffImageFile(ImageFile.ImageFile):
return Image.Image.load(self) return Image.Image.load(self)
def _setup(self): def _setup(self) -> None:
"""Setup this image object based on current tags""" """Setup this image object based on current tags"""
if 0xBC01 in self.tag_v2: if 0xBC01 in self.tag_v2:
@ -1537,13 +1542,13 @@ class TiffImageFile(ImageFile.ImageFile):
# adjust stride width accordingly # adjust stride width accordingly
stride /= bps_count stride /= bps_count
a = (tile_rawmode, int(stride), 1) args = (tile_rawmode, int(stride), 1)
self.tile.append( self.tile.append(
( (
self._compression, self._compression,
(x, y, min(x + w, xsize), min(y + h, ysize)), (x, y, min(x + w, xsize), min(y + h, ysize)),
offset, offset,
a, args,
) )
) )
x = x + w x = x + w
@ -1938,7 +1943,7 @@ class AppendingTiffWriter:
521, # JPEGACTables 521, # JPEGACTables
} }
def __init__(self, fn, new=False): def __init__(self, fn, new: bool = False) -> None:
if hasattr(fn, "read"): if hasattr(fn, "read"):
self.f = fn self.f = fn
self.close_fp = False self.close_fp = False
@ -2015,7 +2020,7 @@ class AppendingTiffWriter:
def tell(self) -> int: def tell(self) -> int:
return self.f.tell() - self.offsetOfNewPage return self.f.tell() - self.offsetOfNewPage
def seek(self, offset, whence=io.SEEK_SET): def seek(self, offset: int, whence=io.SEEK_SET) -> int:
if whence == os.SEEK_SET: if whence == os.SEEK_SET:
offset += self.offsetOfNewPage offset += self.offsetOfNewPage

View File

@ -32,17 +32,24 @@ class _TagInfo(NamedTuple):
class TagInfo(_TagInfo): class TagInfo(_TagInfo):
__slots__: list[str] = [] __slots__: list[str] = []
def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None): def __new__(
cls,
value: int | None = None,
name: str = "unknown",
type: int | None = None,
length: int | None = None,
enum: dict[str, int] | None = None,
) -> TagInfo:
return super().__new__(cls, value, name, type, length, enum or {}) return super().__new__(cls, value, name, type, length, enum or {})
def cvt_enum(self, value): def cvt_enum(self, value: str) -> int | str:
# Using get will call hash(value), which can be expensive # Using get will call hash(value), which can be expensive
# for some types (e.g. Fraction). Since self.enum is rarely # for some types (e.g. Fraction). Since self.enum is rarely
# used, it's usually better to test it first. # used, it's usually better to test it first.
return self.enum.get(value, value) if self.enum else value return self.enum.get(value, value) if self.enum else value
def lookup(tag, group=None): def lookup(tag: int, group: int | None = None) -> TagInfo:
""" """
:param tag: Integer tag number :param tag: Integer tag number
:param group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in :param group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in
@ -89,7 +96,7 @@ DOUBLE = 12
IFD = 13 IFD = 13
LONG8 = 16 LONG8 = 16
_tags_v2 = { _tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]] = {
254: ("NewSubfileType", LONG, 1), 254: ("NewSubfileType", LONG, 1),
255: ("SubfileType", SHORT, 1), 255: ("SubfileType", SHORT, 1),
256: ("ImageWidth", LONG, 1), 256: ("ImageWidth", LONG, 1),
@ -233,7 +240,7 @@ _tags_v2 = {
50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one 50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one
50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006 50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006
} }
TAGS_V2_GROUPS = { _tags_v2_groups = {
# ExifIFD # ExifIFD
34665: { 34665: {
36864: ("ExifVersion", UNDEFINED, 1), 36864: ("ExifVersion", UNDEFINED, 1),
@ -281,7 +288,7 @@ TAGS_V2_GROUPS = {
# Legacy Tags structure # Legacy Tags structure
# these tags aren't included above, but were in the previous versions # these tags aren't included above, but were in the previous versions
TAGS = { TAGS: dict[int | tuple[int, int], str] = {
347: "JPEGTables", 347: "JPEGTables",
700: "XMP", 700: "XMP",
# Additional Exif Info # Additional Exif Info
@ -426,9 +433,10 @@ TAGS = {
} }
TAGS_V2: dict[int, TagInfo] = {} TAGS_V2: dict[int, TagInfo] = {}
TAGS_V2_GROUPS: dict[int, dict[int, TagInfo]] = {}
def _populate(): def _populate() -> None:
for k, v in _tags_v2.items(): for k, v in _tags_v2.items():
# Populate legacy structure. # Populate legacy structure.
TAGS[k] = v[0] TAGS[k] = v[0]
@ -438,9 +446,8 @@ def _populate():
TAGS_V2[k] = TagInfo(k, *v) TAGS_V2[k] = TagInfo(k, *v)
for tags in TAGS_V2_GROUPS.values(): for group, tags in _tags_v2_groups.items():
for k, v in tags.items(): TAGS_V2_GROUPS[group] = {k: TagInfo(k, *v) for k, v in tags.items()}
tags[k] = TagInfo(k, *v)
_populate() _populate()

View File

@ -24,8 +24,11 @@ and has been tested with a few sample files found using google.
""" """
from __future__ import annotations from __future__ import annotations
from typing import IO
from . import Image, ImageFile from . import Image, ImageFile
from ._binary import i32le as i32 from ._binary import i32le as i32
from ._typing import StrOrBytesPath
class WalImageFile(ImageFile.ImageFile): class WalImageFile(ImageFile.ImageFile):
@ -58,7 +61,7 @@ class WalImageFile(ImageFile.ImageFile):
return Image.Image.load(self) return Image.Image.load(self)
def open(filename): def open(filename: StrOrBytesPath | IO[bytes]) -> WalImageFile:
""" """
Load texture from a Quake2 WAL texture file. Load texture from a Quake2 WAL texture file.

View File

@ -96,7 +96,7 @@ class WebPImageFile(ImageFile.ImageFile):
# Initialize seek state # Initialize seek state
self._reset(reset=False) self._reset(reset=False)
def _getexif(self) -> dict[str, Any] | None: def _getexif(self) -> dict[int, Any] | None:
if "exif" not in self.info: if "exif" not in self.info:
return None return None
return self.getexif()._get_merged_dict() return self.getexif()._get_merged_dict()
@ -115,7 +115,7 @@ class WebPImageFile(ImageFile.ImageFile):
self.__loaded = -1 self.__loaded = -1
self.__timestamp = 0 self.__timestamp = 0
def _get_next(self): def _get_next(self) -> tuple[bytes, int, int]:
# Get next frame # Get next frame
ret = self._decoder.get_next() ret = self._decoder.get_next()
self.__physical_frame += 1 self.__physical_frame += 1

View File

@ -152,7 +152,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
def _load(self) -> ImageFile.StubHandler | None: def _load(self) -> ImageFile.StubHandler | None:
return _handler return _handler
def load(self, dpi=None): def load(self, dpi: int | None = None) -> Image.core.PixelAccess | None:
if dpi is not None and self._inch is not None: if dpi is not None and self._inch is not None:
self.info["dpi"] = dpi self.info["dpi"] = dpi
x0, y0, x1, y1 = self.info["wmf_bbox"] x0, y0, x1, y1 = self.info["wmf_bbox"]

View File

@ -1,6 +1,7 @@
from typing import Any from typing import Any
class ImagingCore: class ImagingCore:
def __getitem__(self, index: int) -> float: ...
def __getattr__(self, name: str) -> Any: ... def __getattr__(self, name: str) -> Any: ...
class ImagingFont: class ImagingFont:

View File

@ -1,12 +1,6 @@
from typing import Any, TypedDict from typing import Any, Callable
from . import _imaging from . import ImageFont, _imaging
class _Axis(TypedDict):
minimum: int | None
default: int | None
maximum: int | None
name: bytes | None
class Font: class Font:
@property @property
@ -28,42 +22,48 @@ class Font:
def render( def render(
self, self,
string: str | bytes, string: str | bytes,
fill, fill: Callable[[int, int], _imaging.ImagingCore],
mode=..., mode: str,
dir=..., dir: str | None,
features=..., features: list[str] | None,
lang=..., lang: str | None,
stroke_width=..., stroke_width: float,
anchor=..., anchor: str | None,
foreground_ink_long=..., foreground_ink_long: int,
x_start=..., x_start: float,
y_start=..., y_start: float,
/, /,
) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ... ) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
def getsize( def getsize(
self, self,
string: str | bytes | bytearray, string: str | bytes | bytearray,
mode=..., mode: str,
dir=..., dir: str | None,
features=..., features: list[str] | None,
lang=..., lang: str | None,
anchor=..., anchor: str | None,
/, /,
) -> tuple[tuple[int, int], tuple[int, int]]: ... ) -> tuple[tuple[int, int], tuple[int, int]]: ...
def getlength( def getlength(
self, string: str | bytes, mode=..., dir=..., features=..., lang=..., / self,
string: str | bytes,
mode: str,
dir: str | None,
features: list[str] | None,
lang: str | None,
/,
) -> float: ... ) -> float: ...
def getvarnames(self) -> list[bytes]: ... def getvarnames(self) -> list[bytes]: ...
def getvaraxes(self) -> list[_Axis] | None: ... def getvaraxes(self) -> list[ImageFont.Axis]: ...
def setvarname(self, instance_index: int, /) -> None: ... def setvarname(self, instance_index: int, /) -> None: ...
def setvaraxes(self, axes: list[float], /) -> None: ... def setvaraxes(self, axes: list[float], /) -> None: ...
def getfont( def getfont(
filename: str | bytes, filename: str | bytes,
size: float, size: float,
index=..., index: int,
encoding=..., encoding: str,
font_bytes=..., font_bytes: bytes,
layout_engine=..., layout_engine: int,
) -> Font: ... ) -> Font: ...
def __getattr__(name: str) -> Any: ... def __getattr__(name: str) -> Any: ...

3
src/PIL/_imagingtk.pyi Normal file
View File

@ -0,0 +1,3 @@
from typing import Any
def __getattr__(name: str) -> Any: ...

View File

@ -10,11 +10,6 @@ def is_path(f: Any) -> TypeGuard[StrOrBytesPath]:
return isinstance(f, (bytes, str, os.PathLike)) return isinstance(f, (bytes, str, os.PathLike))
def is_directory(f: Any) -> TypeGuard[StrOrBytesPath]:
"""Checks if an object is a string, and that it points to a directory."""
return is_path(f) and os.path.isdir(f)
class DeferredError: class DeferredError:
def __init__(self, ex: BaseException): def __init__(self, ex: BaseException):
self.ex = ex self.ex = ex

View File

@ -80,7 +80,8 @@ typedef struct Tcl_Command_ *Tcl_Command;
typedef void *ClientData; typedef void *ClientData;
typedef int(Tcl_CmdProc)( typedef int(Tcl_CmdProc)(
ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]); ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]
);
typedef void(Tcl_CmdDeleteProc)(ClientData clientData); typedef void(Tcl_CmdDeleteProc)(ClientData clientData);
/* Typedefs derived from function signatures in Tcl header */ /* Typedefs derived from function signatures in Tcl header */
@ -90,7 +91,8 @@ typedef Tcl_Command (*Tcl_CreateCommand_t)(
const char *cmdName, const char *cmdName,
Tcl_CmdProc *proc, Tcl_CmdProc *proc,
ClientData clientData, ClientData clientData,
Tcl_CmdDeleteProc *deleteProc); Tcl_CmdDeleteProc *deleteProc
);
/* Tcl_AppendResult */ /* Tcl_AppendResult */
typedef void (*Tcl_AppendResult_t)(Tcl_Interp *interp, ...); typedef void (*Tcl_AppendResult_t)(Tcl_Interp *interp, ...);
@ -127,7 +129,8 @@ typedef int (*Tk_PhotoPutBlock_t)(
int y, int y,
int width, int width,
int height, int height,
int compRule); int compRule
);
/* Tk_FindPhoto */ /* Tk_FindPhoto */
typedef Tk_PhotoHandle (*Tk_FindPhoto_t)(Tcl_Interp *interp, const char *imageName); typedef Tk_PhotoHandle (*Tk_FindPhoto_t)(Tcl_Interp *interp, const char *imageName);
/* Tk_PhotoGetImage */ /* Tk_PhotoGetImage */

View File

@ -73,14 +73,16 @@ ImagingFind(const char *name) {
static int static int
PyImagingPhotoPut( PyImagingPhotoPut(
ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) { ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv
) {
Imaging im; Imaging im;
Tk_PhotoHandle photo; Tk_PhotoHandle photo;
Tk_PhotoImageBlock block; Tk_PhotoImageBlock block;
if (argc != 3) { if (argc != 3) {
TCL_APPEND_RESULT( TCL_APPEND_RESULT(
interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL); interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL
);
return TCL_ERROR; return TCL_ERROR;
} }
@ -128,14 +130,16 @@ PyImagingPhotoPut(
block.pixelPtr = (unsigned char *)im->block; block.pixelPtr = (unsigned char *)im->block;
TK_PHOTO_PUT_BLOCK( TK_PHOTO_PUT_BLOCK(
interp, photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET); interp, photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET
);
return TCL_OK; return TCL_OK;
} }
static int static int
PyImagingPhotoGet( PyImagingPhotoGet(
ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) { ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv
) {
Imaging im; Imaging im;
Tk_PhotoHandle photo; Tk_PhotoHandle photo;
Tk_PhotoImageBlock block; Tk_PhotoImageBlock block;
@ -143,7 +147,8 @@ PyImagingPhotoGet(
if (argc != 3) { if (argc != 3) {
TCL_APPEND_RESULT( TCL_APPEND_RESULT(
interp, "usage: ", argv[0], " srcPhoto destImage", (char *)NULL); interp, "usage: ", argv[0], " srcPhoto destImage", (char *)NULL
);
return TCL_ERROR; return TCL_ERROR;
} }
@ -183,13 +188,15 @@ TkImaging_Init(Tcl_Interp *interp) {
"PyImagingPhoto", "PyImagingPhoto",
PyImagingPhotoPut, PyImagingPhotoPut,
(ClientData)0, (ClientData)0,
(Tcl_CmdDeleteProc *)NULL); (Tcl_CmdDeleteProc *)NULL
);
TCL_CREATE_COMMAND( TCL_CREATE_COMMAND(
interp, interp,
"PyImagingPhotoGet", "PyImagingPhotoGet",
PyImagingPhotoGet, PyImagingPhotoGet,
(ClientData)0, (ClientData)0,
(Tcl_CmdDeleteProc *)NULL); (Tcl_CmdDeleteProc *)NULL
);
} }
/* /*
@ -394,7 +401,8 @@ _func_loader(void *lib) {
} }
return ( return (
(TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)_dfunc(lib, "Tk_PhotoPutBlock")) == (TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)_dfunc(lib, "Tk_PhotoPutBlock")) ==
NULL); NULL
);
} }
int int

View File

@ -290,7 +290,8 @@ ImagingError_ModeError(void) {
void * void *
ImagingError_ValueError(const char *message) { ImagingError_ValueError(const char *message) {
PyErr_SetString( PyErr_SetString(
PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value"); PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value"
);
return NULL; return NULL;
} }
@ -467,7 +468,8 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) {
return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]); return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]);
case 4: case 4:
return Py_BuildValue( return Py_BuildValue(
"BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3]); "BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3]
);
} }
break; break;
case IMAGING_TYPE_INT32: case IMAGING_TYPE_INT32:
@ -518,7 +520,8 @@ getink(PyObject *color, Imaging im, char *ink) {
rIsInt = 1; rIsInt = 1;
} else if (im->bands == 1) { } else if (im->bands == 1) {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, "color must be int or single-element tuple"); PyExc_TypeError, "color must be int or single-element tuple"
);
return NULL; return NULL;
} else if (tupleSize == -1) { } else if (tupleSize == -1) {
PyErr_SetString(PyExc_TypeError, "color must be int or tuple"); PyErr_SetString(PyExc_TypeError, "color must be int or tuple");
@ -534,8 +537,8 @@ getink(PyObject *color, Imaging im, char *ink) {
if (rIsInt != 1) { if (rIsInt != 1) {
if (tupleSize != 1) { if (tupleSize != 1) {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, PyExc_TypeError, "color must be int or single-element tuple"
"color must be int or single-element tuple"); );
return NULL; return NULL;
} else if (!PyArg_ParseTuple(color, "L", &r)) { } else if (!PyArg_ParseTuple(color, "L", &r)) {
return NULL; return NULL;
@ -556,7 +559,8 @@ getink(PyObject *color, Imaging im, char *ink) {
if (tupleSize != 1 && tupleSize != 2) { if (tupleSize != 1 && tupleSize != 2) {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, PyExc_TypeError,
"color must be int, or tuple of one or two elements"); "color must be int, or tuple of one or two elements"
);
return NULL; return NULL;
} else if (!PyArg_ParseTuple(color, "L|i", &r, &a)) { } else if (!PyArg_ParseTuple(color, "L|i", &r, &a)) {
return NULL; return NULL;
@ -567,7 +571,8 @@ getink(PyObject *color, Imaging im, char *ink) {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, PyExc_TypeError,
"color must be int, or tuple of one, three or four " "color must be int, or tuple of one, three or four "
"elements"); "elements"
);
return NULL; return NULL;
} else if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) { } else if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) {
return NULL; return NULL;
@ -608,7 +613,8 @@ getink(PyObject *color, Imaging im, char *ink) {
} else if (tupleSize != 3) { } else if (tupleSize != 3) {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, PyExc_TypeError,
"color must be int, or tuple of one or three elements"); "color must be int, or tuple of one or three elements"
);
return NULL; return NULL;
} else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) { } else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
return NULL; return NULL;
@ -733,7 +739,8 @@ _alpha_composite(ImagingObject *self, PyObject *args) {
ImagingObject *imagep2; ImagingObject *imagep2;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) { args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2
)) {
return NULL; return NULL;
} }
@ -748,7 +755,8 @@ _blend(ImagingObject *self, PyObject *args) {
alpha = 0.5; alpha = 0.5;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "O!O!|d", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2, &alpha)) { args, "O!O!|d", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2, &alpha
)) {
return NULL; return NULL;
} }
@ -827,7 +835,8 @@ _prepare_lut_table(PyObject *table, Py_ssize_t table_size) {
break; break;
case TYPE_FLOAT32: case TYPE_FLOAT32:
memcpy( memcpy(
&item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32)); &item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32)
);
break; break;
case TYPE_DOUBLE: case TYPE_DOUBLE:
memcpy(&dtmp, ((char *)table_data) + i * sizeof(dtmp), sizeof(dtmp)); memcpy(&dtmp, ((char *)table_data) + i * sizeof(dtmp), sizeof(dtmp));
@ -878,7 +887,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
&size1D, &size1D,
&size2D, &size2D,
&size3D, &size3D,
&table)) { &table
)) {
return NULL; return NULL;
} }
@ -896,7 +906,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
if (2 > size1D || size1D > 65 || 2 > size2D || size2D > 65 || 2 > size3D || if (2 > size1D || size1D > 65 || 2 > size2D || size2D > 65 || 2 > size3D ||
size3D > 65) { size3D > 65) {
PyErr_SetString( PyErr_SetString(
PyExc_ValueError, "Table size in any dimension should be from 2 to 65"); PyExc_ValueError, "Table size in any dimension should be from 2 to 65"
);
return NULL; return NULL;
} }
@ -913,13 +924,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
} }
if (!ImagingColorLUT3D_linear( if (!ImagingColorLUT3D_linear(
imOut, imOut, self->image, table_channels, size1D, size2D, size3D, prepared_table
self->image, )) {
table_channels,
size1D,
size2D,
size3D,
prepared_table)) {
free(prepared_table); free(prepared_table);
ImagingDelete(imOut); ImagingDelete(imOut);
return NULL; return NULL;
@ -943,7 +949,8 @@ _convert(ImagingObject *self, PyObject *args) {
if (!PyImaging_Check(paletteimage)) { if (!PyImaging_Check(paletteimage)) {
PyObject_Print((PyObject *)paletteimage, stderr, 0); PyObject_Print((PyObject *)paletteimage, stderr, 0);
PyErr_SetString( PyErr_SetString(
PyExc_ValueError, "palette argument must be image with mode 'P'"); PyExc_ValueError, "palette argument must be image with mode 'P'"
);
return NULL; return NULL;
} }
if (paletteimage->image->palette == NULL) { if (paletteimage->image->palette == NULL) {
@ -953,7 +960,8 @@ _convert(ImagingObject *self, PyObject *args) {
} }
return PyImagingNew(ImagingConvert( return PyImagingNew(ImagingConvert(
self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither)); self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither
));
} }
static PyObject * static PyObject *
@ -961,7 +969,8 @@ _convert2(ImagingObject *self, PyObject *args) {
ImagingObject *imagep1; ImagingObject *imagep1;
ImagingObject *imagep2; ImagingObject *imagep2;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) { args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2
)) {
return NULL; return NULL;
} }
@ -994,7 +1003,8 @@ _convert_matrix(ImagingObject *self, PyObject *args) {
m + 8, m + 8,
m + 9, m + 9,
m + 10, m + 10,
m + 11)) { m + 11
)) {
return NULL; return NULL;
} }
} }
@ -1055,7 +1065,8 @@ _filter(ImagingObject *self, PyObject *args) {
float divisor, offset; float divisor, offset;
PyObject *kernel = NULL; PyObject *kernel = NULL;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel)) { args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel
)) {
return NULL; return NULL;
} }
@ -1138,7 +1149,8 @@ _getpalette(ImagingObject *self, PyObject *args) {
} }
pack( pack(
(UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize); (UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize
);
return palette; return palette;
} }
@ -1232,7 +1244,8 @@ union hist_extrema {
static union hist_extrema * static union hist_extrema *
parse_histogram_extremap( parse_histogram_extremap(
ImagingObject *self, PyObject *extremap, union hist_extrema *ep) { ImagingObject *self, PyObject *extremap, union hist_extrema *ep
) {
int i0, i1; int i0, i1;
double f0, f1; double f0, f1;
@ -1392,7 +1405,8 @@ _paste(ImagingObject *self, PyObject *args) {
int x0, y0, x1, y1; int x0, y0, x1, y1;
ImagingObject *maskp = NULL; ImagingObject *maskp = NULL;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "O(iiii)|O!", &source, &x0, &y0, &x1, &y1, &Imaging_Type, &maskp)) { args, "O(iiii)|O!", &source, &x0, &y0, &x1, &y1, &Imaging_Type, &maskp
)) {
return NULL; return NULL;
} }
@ -1404,14 +1418,16 @@ _paste(ImagingObject *self, PyObject *args) {
x0, x0,
y0, y0,
x1, x1,
y1); y1
);
} else { } else {
if (!getink(source, self->image, ink)) { if (!getink(source, self->image, ink)) {
return NULL; return NULL;
} }
status = ImagingFill2( status = ImagingFill2(
self->image, ink, (maskp) ? maskp->image : NULL, x0, y0, x1, y1); self->image, ink, (maskp) ? maskp->image : NULL, x0, y0, x1, y1
);
} }
if (status < 0) { if (status < 0) {
@ -1729,7 +1745,8 @@ _putpalette(ImagingObject *self, PyObject *args) {
UINT8 *palette; UINT8 *palette;
Py_ssize_t palettesize; Py_ssize_t palettesize;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize)) { args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize
)) {
return NULL; return NULL;
} }
@ -1887,7 +1904,8 @@ _resize(ImagingObject *self, PyObject *args) {
&box[0], &box[0],
&box[1], &box[1],
&box[2], &box[2],
&box[3])) { &box[3]
)) {
return NULL; return NULL;
} }
@ -1923,7 +1941,8 @@ _resize(ImagingObject *self, PyObject *args) {
imOut = ImagingNewDirty(imIn->mode, xsize, ysize); imOut = ImagingNewDirty(imIn->mode, xsize, ysize);
imOut = ImagingTransform( imOut = ImagingTransform(
imOut, imIn, IMAGING_TRANSFORM_AFFINE, 0, 0, xsize, ysize, a, filter, 1); imOut, imIn, IMAGING_TRANSFORM_AFFINE, 0, 0, xsize, ysize, a, filter, 1
);
} else { } else {
imOut = ImagingResample(imIn, xsize, ysize, filter, box); imOut = ImagingResample(imIn, xsize, ysize, filter, box);
} }
@ -1944,14 +1963,8 @@ _reduce(ImagingObject *self, PyObject *args) {
box[3] = imIn->ysize; box[3] = imIn->ysize;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, args, "(ii)|(iiii)", &xscale, &yscale, &box[0], &box[1], &box[2], &box[3]
"(ii)|(iiii)", )) {
&xscale,
&yscale,
&box[0],
&box[1],
&box[2],
&box[3])) {
return NULL; return NULL;
} }
@ -2053,7 +2066,8 @@ _transform(ImagingObject *self, PyObject *args) {
&method, &method,
&data, &data,
&filter, &filter,
&fill)) { &fill
)) {
return NULL; return NULL;
} }
@ -2077,7 +2091,8 @@ _transform(ImagingObject *self, PyObject *args) {
} }
imOut = ImagingTransform( imOut = ImagingTransform(
self->image, imagep->image, method, x0, y0, x1, y1, a, filter, fill); self->image, imagep->image, method, x0, y0, x1, y1, a, filter, fill
);
free(a); free(a);
@ -2250,7 +2265,13 @@ _getcolors(ImagingObject *self, PyObject *args) {
for (i = 0; i < colors; i++) { for (i = 0; i < colors; i++) {
ImagingColorItem *v = &items[i]; ImagingColorItem *v = &items[i];
PyObject *item = Py_BuildValue( PyObject *item = Py_BuildValue(
"iN", v->count, getpixel(self->image, self->access, v->x, v->y)); "iN", v->count, getpixel(self->image, self->access, v->x, v->y)
);
if (item == NULL) {
Py_DECREF(out);
free(items);
return NULL;
}
PyList_SetItem(out, i, item); PyList_SetItem(out, i, item);
} }
} }
@ -2311,14 +2332,16 @@ _getprojection(ImagingObject *self) {
} }
ImagingGetProjection( ImagingGetProjection(
self->image, (unsigned char *)xprofile, (unsigned char *)yprofile); self->image, (unsigned char *)xprofile, (unsigned char *)yprofile
);
result = Py_BuildValue( result = Py_BuildValue(
"y#y#", "y#y#",
xprofile, xprofile,
(Py_ssize_t)self->image->xsize, (Py_ssize_t)self->image->xsize,
yprofile, yprofile,
(Py_ssize_t)self->image->ysize); (Py_ssize_t)self->image->ysize
);
free(xprofile); free(xprofile);
free(yprofile); free(yprofile);
@ -2392,7 +2415,8 @@ _merge(PyObject *self, PyObject *args) {
&Imaging_Type, &Imaging_Type,
&band2, &band2,
&Imaging_Type, &Imaging_Type,
&band3)) { &band3
)) {
return NULL; return NULL;
} }
@ -2638,7 +2662,8 @@ _font_new(PyObject *self_, PyObject *args) {
unsigned char *glyphdata; unsigned char *glyphdata;
Py_ssize_t glyphdata_length; Py_ssize_t glyphdata_length;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length)) { args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length
)) {
return NULL; return NULL;
} }
@ -2796,7 +2821,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
if (i == 0 || text[i] != text[i - 1]) { if (i == 0 || text[i] != text[i - 1]) {
ImagingDelete(bitmap); ImagingDelete(bitmap);
bitmap = ImagingCrop( bitmap = ImagingCrop(
self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1); self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1
);
if (!bitmap) { if (!bitmap) {
goto failed; goto failed;
} }
@ -2808,7 +2834,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
glyph->dx0 + x, glyph->dx0 + x,
glyph->dy0 + b, glyph->dy0 + b,
glyph->dx1 + x, glyph->dx1 + x,
glyph->dy1 + b); glyph->dy1 + b
);
if (status < 0) { if (status < 0) {
goto failed; goto failed;
} }
@ -2947,7 +2974,8 @@ _draw_arc(ImagingDrawObject *self, PyObject *args) {
end, end,
&ink, &ink,
width, width,
self->blend); self->blend
);
free(xy); free(xy);
@ -2977,13 +3005,15 @@ _draw_bitmap(ImagingDrawObject *self, PyObject *args) {
} }
if (n != 1) { if (n != 1) {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, "coordinate list must contain exactly 1 coordinate"); PyExc_TypeError, "coordinate list must contain exactly 1 coordinate"
);
free(xy); free(xy);
return NULL; return NULL;
} }
n = ImagingDrawBitmap( n = ImagingDrawBitmap(
self->image->image, (int)xy[0], (int)xy[1], bitmap->image, &ink, self->blend); self->image->image, (int)xy[0], (int)xy[1], bitmap->image, &ink, self->blend
);
free(xy); free(xy);
@ -3039,7 +3069,8 @@ _draw_chord(ImagingDrawObject *self, PyObject *args) {
&ink, &ink,
fill, fill,
width, width,
self->blend); self->blend
);
free(xy); free(xy);
@ -3093,7 +3124,8 @@ _draw_ellipse(ImagingDrawObject *self, PyObject *args) {
&ink, &ink,
fill, fill,
width, width,
self->blend); self->blend
);
free(xy); free(xy);
@ -3133,14 +3165,16 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
(int)p[2], (int)p[2],
(int)p[3], (int)p[3],
&ink, &ink,
self->blend) < 0) { self->blend
) < 0) {
free(xy); free(xy);
return NULL; return NULL;
} }
} }
if (p) { /* draw last point */ if (p) { /* draw last point */
ImagingDrawPoint( ImagingDrawPoint(
self->image->image, (int)p[2], (int)p[3], &ink, self->blend); self->image->image, (int)p[2], (int)p[3], &ink, self->blend
);
} }
} else { } else {
for (i = 0; i < n - 1; i++) { for (i = 0; i < n - 1; i++) {
@ -3153,7 +3187,8 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
(int)p[3], (int)p[3],
&ink, &ink,
width, width,
self->blend) < 0) { self->blend
) < 0) {
free(xy); free(xy);
return NULL; return NULL;
} }
@ -3185,7 +3220,8 @@ _draw_points(ImagingDrawObject *self, PyObject *args) {
for (i = 0; i < n; i++) { for (i = 0; i < n; i++) {
double *p = &xy[i + i]; double *p = &xy[i + i];
if (ImagingDrawPoint( if (ImagingDrawPoint(
self->image->image, (int)p[0], (int)p[1], &ink, self->blend) < 0) { self->image->image, (int)p[0], (int)p[1], &ink, self->blend
) < 0) {
free(xy); free(xy);
return NULL; return NULL;
} }
@ -3274,7 +3310,8 @@ _draw_pieslice(ImagingDrawObject *self, PyObject *args) {
&ink, &ink,
fill, fill,
width, width,
self->blend); self->blend
);
free(xy); free(xy);
@ -3306,7 +3343,8 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
} }
if (n < 2) { if (n < 2) {
PyErr_SetString( PyErr_SetString(
PyExc_TypeError, "coordinate list must contain at least 2 coordinates"); PyExc_TypeError, "coordinate list must contain at least 2 coordinates"
);
free(xy); free(xy);
return NULL; return NULL;
} }
@ -3379,7 +3417,8 @@ _draw_rectangle(ImagingDrawObject *self, PyObject *args) {
&ink, &ink,
fill, fill,
width, width,
self->blend); self->blend
);
free(xy); free(xy);
@ -3519,7 +3558,8 @@ _effect_mandelbrot(ImagingObject *self, PyObject *args) {
&extent[1], &extent[1],
&extent[2], &extent[2],
&extent[3], &extent[3],
&quality)) { &quality
)) {
return NULL; return NULL;
} }
@ -3747,7 +3787,8 @@ _getattr_unsafe_ptrs(ImagingObject *self, void *closure) {
"image32", "image32",
self->image->image32, self->image->image32,
"image", "image",
self->image->image); self->image->image
);
} }
static struct PyGetSetDef getsetters[] = { static struct PyGetSetDef getsetters[] = {
@ -3757,7 +3798,8 @@ static struct PyGetSetDef getsetters[] = {
{"id", (getter)_getattr_id}, {"id", (getter)_getattr_id},
{"ptr", (getter)_getattr_ptr}, {"ptr", (getter)_getattr_ptr},
{"unsafe_ptrs", (getter)_getattr_unsafe_ptrs}, {"unsafe_ptrs", (getter)_getattr_unsafe_ptrs},
{NULL}}; {NULL}
};
/* basic sequence semantics */ /* basic sequence semantics */
@ -4066,8 +4108,7 @@ _set_blocks_max(PyObject *self, PyObject *args) {
if (blocks_max < 0) { if (blocks_max < 0) {
PyErr_SetString(PyExc_ValueError, "blocks_max should be greater than 0"); PyErr_SetString(PyExc_ValueError, "blocks_max should be greater than 0");
return NULL; return NULL;
} else if ( } else if ((unsigned long)blocks_max >
(unsigned long)blocks_max >
SIZE_MAX / sizeof(ImagingDefaultArena.blocks_pool[0])) { SIZE_MAX / sizeof(ImagingDefaultArena.blocks_pool[0])) {
PyErr_SetString(PyExc_ValueError, "blocks_max is too large"); PyErr_SetString(PyExc_ValueError, "blocks_max is too large");
return NULL; return NULL;
@ -4423,7 +4464,8 @@ setup_module(PyObject *m) {
PyObject *pillow_version = PyUnicode_FromString(version); PyObject *pillow_version = PyUnicode_FromString(version);
PyDict_SetItemString( PyDict_SetItemString(
d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None); d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None
);
Py_XDECREF(pillow_version); Py_XDECREF(pillow_version);
return 0; return 0;
@ -4448,5 +4490,9 @@ PyInit__imaging(void) {
return NULL; return NULL;
} }
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m; return m;
} }

View File

@ -331,7 +331,8 @@ pyCMScopyAux(cmsHTRANSFORM hTransform, Imaging imDst, const Imaging imSrc) {
memcpy( memcpy(
pDstExtras + x * dstChunkSize, pDstExtras + x * dstChunkSize,
pSrcExtras + x * srcChunkSize, pSrcExtras + x * srcChunkSize,
channelSize); channelSize
);
} }
} }
} }
@ -373,7 +374,8 @@ _buildTransform(
char *sInMode, char *sInMode,
char *sOutMode, char *sOutMode,
int iRenderingIntent, int iRenderingIntent,
cmsUInt32Number cmsFLAGS) { cmsUInt32Number cmsFLAGS
) {
cmsHTRANSFORM hTransform; cmsHTRANSFORM hTransform;
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
@ -385,7 +387,8 @@ _buildTransform(
hOutputProfile, hOutputProfile,
findLCMStype(sOutMode), findLCMStype(sOutMode),
iRenderingIntent, iRenderingIntent,
cmsFLAGS); cmsFLAGS
);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
@ -405,7 +408,8 @@ _buildProofTransform(
char *sOutMode, char *sOutMode,
int iRenderingIntent, int iRenderingIntent,
int iProofIntent, int iProofIntent,
cmsUInt32Number cmsFLAGS) { cmsUInt32Number cmsFLAGS
) {
cmsHTRANSFORM hTransform; cmsHTRANSFORM hTransform;
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
@ -419,7 +423,8 @@ _buildProofTransform(
hProofProfile, hProofProfile,
iRenderingIntent, iRenderingIntent,
iProofIntent, iProofIntent,
cmsFLAGS); cmsFLAGS
);
Py_END_ALLOW_THREADS; Py_END_ALLOW_THREADS;
@ -454,7 +459,8 @@ buildTransform(PyObject *self, PyObject *args) {
&sInMode, &sInMode,
&sOutMode, &sOutMode,
&iRenderingIntent, &iRenderingIntent,
&cmsFLAGS)) { &cmsFLAGS
)) {
return NULL; return NULL;
} }
@ -464,7 +470,8 @@ buildTransform(PyObject *self, PyObject *args) {
sInMode, sInMode,
sOutMode, sOutMode,
iRenderingIntent, iRenderingIntent,
cmsFLAGS); cmsFLAGS
);
if (!transform) { if (!transform) {
return NULL; return NULL;
@ -499,7 +506,8 @@ buildProofTransform(PyObject *self, PyObject *args) {
&sOutMode, &sOutMode,
&iRenderingIntent, &iRenderingIntent,
&iProofIntent, &iProofIntent,
&cmsFLAGS)) { &cmsFLAGS
)) {
return NULL; return NULL;
} }
@ -511,7 +519,8 @@ buildProofTransform(PyObject *self, PyObject *args) {
sOutMode, sOutMode,
iRenderingIntent, iRenderingIntent,
iProofIntent, iProofIntent,
cmsFLAGS); cmsFLAGS
);
if (!transform) { if (!transform) {
return NULL; return NULL;
@ -563,7 +572,8 @@ createProfile(PyObject *self, PyObject *args) {
PyErr_SetString( PyErr_SetString(
PyExc_ValueError, PyExc_ValueError,
"ERROR: Could not calculate white point from color temperature " "ERROR: Could not calculate white point from color temperature "
"provided, must be float in degrees Kelvin"); "provided, must be float in degrees Kelvin"
);
return NULL; return NULL;
} }
hProfile = cmsCreateLab2Profile(&whitePoint); hProfile = cmsCreateLab2Profile(&whitePoint);
@ -624,7 +634,8 @@ cms_get_display_profile_win32(PyObject *self, PyObject *args) {
HANDLE handle = 0; HANDLE handle = 0;
int is_dc = 0; int is_dc = 0;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "|" F_HANDLE "i:get_display_profile", &handle, &is_dc)) { args, "|" F_HANDLE "i:get_display_profile", &handle, &is_dc
)) {
return NULL; return NULL;
} }
@ -729,7 +740,8 @@ _xyz_py(cmsCIEXYZ *XYZ) {
cmsCIExyY xyY; cmsCIExyY xyY;
cmsXYZ2xyY(&xyY, XYZ); cmsXYZ2xyY(&xyY, XYZ);
return Py_BuildValue( return Py_BuildValue(
"((d,d,d),(d,d,d))", XYZ->X, XYZ->Y, XYZ->Z, xyY.x, xyY.y, xyY.Y); "((d,d,d),(d,d,d))", XYZ->X, XYZ->Y, XYZ->Z, xyY.x, xyY.y, xyY.Y
);
} }
static PyObject * static PyObject *
@ -758,7 +770,8 @@ _xyz3_py(cmsCIEXYZ *XYZ) {
xyY[1].Y, xyY[1].Y,
xyY[2].x, xyY[2].x,
xyY[2].y, xyY[2].y,
xyY[2].Y); xyY[2].Y
);
} }
static PyObject * static PyObject *
@ -809,7 +822,8 @@ _profile_read_ciexyy_triple(CmsProfileObject *self, cmsTagSignature info) {
triple->Green.Y, triple->Green.Y,
triple->Blue.x, triple->Blue.x,
triple->Blue.y, triple->Blue.y,
triple->Blue.Y); triple->Blue.Y
);
} }
static PyObject * static PyObject *
@ -873,7 +887,8 @@ _calculate_rgb_primaries(CmsProfileObject *self, cmsCIEXYZTRIPLE *result) {
hXYZ, hXYZ,
TYPE_XYZ_DBL, TYPE_XYZ_DBL,
INTENT_RELATIVE_COLORIMETRIC, INTENT_RELATIVE_COLORIMETRIC,
cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE); cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE
);
cmsCloseProfile(hXYZ); cmsCloseProfile(hXYZ);
if (hTransform == NULL) { if (hTransform == NULL) {
return 0; return 0;
@ -889,7 +904,8 @@ _check_intent(
int clut, int clut,
cmsHPROFILE hProfile, cmsHPROFILE hProfile,
cmsUInt32Number Intent, cmsUInt32Number Intent,
cmsUInt32Number UsedDirection) { cmsUInt32Number UsedDirection
) {
if (clut) { if (clut) {
return cmsIsCLUT(hProfile, Intent, UsedDirection); return cmsIsCLUT(hProfile, Intent, UsedDirection);
} else { } else {
@ -934,7 +950,8 @@ _is_intent_supported(CmsProfileObject *self, int clut) {
_check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True _check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True
: Py_False, : Py_False,
_check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True _check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True
: Py_False); : Py_False
);
if (id == NULL || entry == NULL) { if (id == NULL || entry == NULL) {
Py_XDECREF(id); Py_XDECREF(id);
Py_XDECREF(entry); Py_XDECREF(entry);
@ -968,7 +985,8 @@ static PyMethodDef pyCMSdll_methods[] = {
{"get_display_profile_win32", cms_get_display_profile_win32, METH_VARARGS}, {"get_display_profile_win32", cms_get_display_profile_win32, METH_VARARGS},
#endif #endif
{NULL, NULL}}; {NULL, NULL}
};
static struct PyMethodDef cms_profile_methods[] = { static struct PyMethodDef cms_profile_methods[] = {
{"is_intent_supported", (PyCFunction)cms_profile_is_intent_supported, METH_VARARGS}, {"is_intent_supported", (PyCFunction)cms_profile_is_intent_supported, METH_VARARGS},
@ -1028,7 +1046,8 @@ cms_profile_getattr_creation_date(CmsProfileObject *self, void *closure) {
} }
return PyDateTime_FromDateAndTime( return PyDateTime_FromDateAndTime(
1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, ct.tm_hour, ct.tm_min, ct.tm_sec, 0); 1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, ct.tm_hour, ct.tm_min, ct.tm_sec, 0
);
} }
static PyObject * static PyObject *
@ -1106,13 +1125,15 @@ cms_profile_getattr_colorimetric_intent(CmsProfileObject *self, void *closure) {
static PyObject * static PyObject *
cms_profile_getattr_perceptual_rendering_intent_gamut( cms_profile_getattr_perceptual_rendering_intent_gamut(
CmsProfileObject *self, void *closure) { CmsProfileObject *self, void *closure
) {
return _profile_read_signature(self, cmsSigPerceptualRenderingIntentGamutTag); return _profile_read_signature(self, cmsSigPerceptualRenderingIntentGamutTag);
} }
static PyObject * static PyObject *
cms_profile_getattr_saturation_rendering_intent_gamut( cms_profile_getattr_saturation_rendering_intent_gamut(
CmsProfileObject *self, void *closure) { CmsProfileObject *self, void *closure
) {
return _profile_read_signature(self, cmsSigSaturationRenderingIntentGamutTag); return _profile_read_signature(self, cmsSigSaturationRenderingIntentGamutTag);
} }
@ -1145,7 +1166,8 @@ cms_profile_getattr_blue_colorant(CmsProfileObject *self, void *closure) {
static PyObject * static PyObject *
cms_profile_getattr_media_white_point_temperature( cms_profile_getattr_media_white_point_temperature(
CmsProfileObject *self, void *closure) { CmsProfileObject *self, void *closure
) {
cmsCIEXYZ *XYZ; cmsCIEXYZ *XYZ;
cmsCIExyY xyY; cmsCIExyY xyY;
cmsFloat64Number tempK; cmsFloat64Number tempK;
@ -1329,7 +1351,8 @@ cms_profile_getattr_icc_measurement_condition(CmsProfileObject *self, void *clos
"flare", "flare",
mc->Flare, mc->Flare,
"illuminant_type", "illuminant_type",
_illu_map(mc->IlluminantType)); _illu_map(mc->IlluminantType)
);
} }
static PyObject * static PyObject *
@ -1359,7 +1382,8 @@ cms_profile_getattr_icc_viewing_condition(CmsProfileObject *self, void *closure)
vc->SurroundXYZ.Y, vc->SurroundXYZ.Y,
vc->SurroundXYZ.Z, vc->SurroundXYZ.Z,
"illuminant_type", "illuminant_type",
_illu_map(vc->IlluminantType)); _illu_map(vc->IlluminantType)
);
} }
static struct PyGetSetDef cms_profile_getsetters[] = { static struct PyGetSetDef cms_profile_getsetters[] = {
@ -1407,11 +1431,12 @@ static struct PyGetSetDef cms_profile_getsetters[] = {
{"colorant_table_out", (getter)cms_profile_getattr_colorant_table_out}, {"colorant_table_out", (getter)cms_profile_getattr_colorant_table_out},
{"intent_supported", (getter)cms_profile_getattr_is_intent_supported}, {"intent_supported", (getter)cms_profile_getattr_is_intent_supported},
{"clut", (getter)cms_profile_getattr_is_clut}, {"clut", (getter)cms_profile_getattr_is_clut},
{"icc_measurement_condition", {"icc_measurement_condition", (getter)cms_profile_getattr_icc_measurement_condition
(getter)cms_profile_getattr_icc_measurement_condition}, },
{"icc_viewing_condition", (getter)cms_profile_getattr_icc_viewing_condition}, {"icc_viewing_condition", (getter)cms_profile_getattr_icc_viewing_condition},
{NULL}}; {NULL}
};
static PyTypeObject CmsProfile_Type = { static PyTypeObject CmsProfile_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsProfile", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsProfile", /*tp_name*/
@ -1538,5 +1563,9 @@ PyInit__imagingcms(void) {
PyDateTime_IMPORT; PyDateTime_IMPORT;
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m; return m;
} }

View File

@ -20,6 +20,7 @@
#define PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN
#include "Python.h" #include "Python.h"
#include "thirdparty/pythoncapi_compat.h"
#include "libImaging/Imaging.h" #include "libImaging/Imaging.h"
#include <ft2build.h> #include <ft2build.h>
@ -125,7 +126,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
unsigned char *font_bytes; unsigned char *font_bytes;
Py_ssize_t font_bytes_size = 0; Py_ssize_t font_bytes_size = 0;
static char *kwlist[] = { static char *kwlist[] = {
"filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL}; "filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL
};
if (!library) { if (!library) {
PyErr_SetString(PyExc_OSError, "failed to initialize FreeType library"); PyErr_SetString(PyExc_OSError, "failed to initialize FreeType library");
@ -147,7 +149,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
&encoding, &encoding,
&font_bytes, &font_bytes,
&font_bytes_size, &font_bytes_size,
&layout_engine)) { &layout_engine
)) {
PyConfig_Clear(&config); PyConfig_Clear(&config);
return NULL; return NULL;
} }
@ -165,7 +168,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
&encoding, &encoding,
&font_bytes, &font_bytes,
&font_bytes_size, &font_bytes_size,
&layout_engine)) { &layout_engine
)) {
return NULL; return NULL;
} }
#endif #endif
@ -198,7 +202,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
(FT_Byte *)self->font_bytes, (FT_Byte *)self->font_bytes,
font_bytes_size, font_bytes_size,
index, index,
&self->face); &self->face
);
} }
} }
@ -242,7 +247,8 @@ text_layout_raqm(
const char *dir, const char *dir,
PyObject *features, PyObject *features,
const char *lang, const char *lang,
GlyphInfo **glyph_info) { GlyphInfo **glyph_info
) {
size_t i = 0, count = 0, start = 0; size_t i = 0, count = 0, start = 0;
raqm_t *rq; raqm_t *rq;
raqm_glyph_t *glyphs = NULL; raqm_glyph_t *glyphs = NULL;
@ -296,13 +302,14 @@ text_layout_raqm(
#if !defined(RAQM_VERSION_ATLEAST) #if !defined(RAQM_VERSION_ATLEAST)
/* RAQM_VERSION_ATLEAST was added in Raqm 0.7.0 */ /* RAQM_VERSION_ATLEAST was added in Raqm 0.7.0 */
PyErr_SetString( PyErr_SetString(
PyExc_ValueError, PyExc_ValueError, "libraqm 0.7 or greater required for 'ttb' direction"
"libraqm 0.7 or greater required for 'ttb' direction"); );
goto failed; goto failed;
#endif #endif
} else { } else {
PyErr_SetString( PyErr_SetString(
PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"
);
goto failed; goto failed;
} }
} }
@ -398,7 +405,8 @@ text_layout_fallback(
const char *lang, const char *lang,
GlyphInfo **glyph_info, GlyphInfo **glyph_info,
int mask, int mask,
int color) { int color
) {
int error, load_flags, i; int error, load_flags, i;
char *buffer = NULL; char *buffer = NULL;
FT_ULong ch; FT_ULong ch;
@ -411,7 +419,8 @@ text_layout_fallback(
PyErr_SetString( PyErr_SetString(
PyExc_KeyError, PyExc_KeyError,
"setting text direction, language or font features is not supported " "setting text direction, language or font features is not supported "
"without libraqm"); "without libraqm"
);
} }
if (PyUnicode_Check(string)) { if (PyUnicode_Check(string)) {
@ -458,7 +467,8 @@ text_layout_fallback(
last_index, last_index,
(*glyph_info)[i].index, (*glyph_info)[i].index,
ft_kerning_default, ft_kerning_default,
&delta) == 0) { &delta
) == 0) {
(*glyph_info)[i - 1].x_advance += PIXEL(delta.x); (*glyph_info)[i - 1].x_advance += PIXEL(delta.x);
(*glyph_info)[i - 1].y_advance += PIXEL(delta.y); (*glyph_info)[i - 1].y_advance += PIXEL(delta.y);
} }
@ -482,7 +492,8 @@ text_layout(
const char *lang, const char *lang,
GlyphInfo **glyph_info, GlyphInfo **glyph_info,
int mask, int mask,
int color) { int color
) {
size_t count; size_t count;
#ifdef HAVE_RAQM #ifdef HAVE_RAQM
if (have_raqm && self->layout_engine == LAYOUT_RAQM) { if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
@ -491,7 +502,8 @@ text_layout(
#endif #endif
{ {
count = text_layout_fallback( count = text_layout_fallback(
string, self, dir, features, lang, glyph_info, mask, color); string, self, dir, features, lang, glyph_info, mask, color
);
} }
return count; return count;
} }
@ -513,7 +525,8 @@ font_getlength(FontObject *self, PyObject *args) {
/* calculate size and bearing for a given string */ /* calculate size and bearing for a given string */
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang)) { args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang
)) {
return NULL; return NULL;
} }
@ -555,7 +568,8 @@ bounding_box_and_anchors(
int *width, int *width,
int *height, int *height,
int *x_offset, int *x_offset,
int *y_offset) { int *y_offset
) {
int position; /* pen position along primary axis, in 26.6 precision */ int position; /* pen position along primary axis, in 26.6 precision */
int advanced; /* pen position along primary axis, in pixels */ int advanced; /* pen position along primary axis, in pixels */
int px, py; /* position of current glyph, in pixels */ int px, py; /* position of current glyph, in pixels */
@ -660,7 +674,8 @@ bounding_box_and_anchors(
case 'm': // middle (ascender + descender) / 2 case 'm': // middle (ascender + descender) / 2
y_anchor = PIXEL( y_anchor = PIXEL(
(face->size->metrics.ascender + face->size->metrics.descender) / (face->size->metrics.ascender + face->size->metrics.descender) /
2); 2
);
break; break;
case 's': // horizontal baseline case 's': // horizontal baseline
y_anchor = 0; y_anchor = 0;
@ -740,7 +755,8 @@ font_getsize(FontObject *self, PyObject *args) {
/* calculate size and bearing for a given string */ /* calculate size and bearing for a given string */
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) { args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor
)) {
return NULL; return NULL;
} }
@ -772,7 +788,8 @@ font_getsize(FontObject *self, PyObject *args) {
&width, &width,
&height, &height,
&x_offset, &x_offset,
&y_offset); &y_offset
);
if (glyph_info) { if (glyph_info) {
PyMem_Free(glyph_info); PyMem_Free(glyph_info);
glyph_info = NULL; glyph_info = NULL;
@ -841,7 +858,8 @@ font_render(FontObject *self, PyObject *args) {
&anchor, &anchor,
&foreground_ink_long, &foreground_ink_long,
&x_start, &x_start,
&y_start)) { &y_start
)) {
return NULL; return NULL;
} }
@ -888,7 +906,8 @@ font_render(FontObject *self, PyObject *args) {
&width, &width,
&height, &height,
&x_offset, &x_offset,
&y_offset); &y_offset
);
if (error) { if (error) {
PyMem_Del(glyph_info); PyMem_Del(glyph_info);
return NULL; return NULL;
@ -928,7 +947,8 @@ font_render(FontObject *self, PyObject *args) {
(FT_Fixed)stroke_width * 64, (FT_Fixed)stroke_width * 64,
FT_STROKER_LINECAP_ROUND, FT_STROKER_LINECAP_ROUND,
FT_STROKER_LINEJOIN_ROUND, FT_STROKER_LINEJOIN_ROUND,
0); 0
);
} }
/* /*
@ -1103,8 +1123,8 @@ font_render(FontObject *self, PyObject *args) {
BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp); BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp);
target[k * 4 + 3] = CLIP8( target[k * 4 + 3] = CLIP8(
src_alpha + src_alpha +
MULDIV255( MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp)
target[k * 4 + 3], (255 - src_alpha), tmp)); );
} else { } else {
/* paste unpremultiplied RGBA values */ /* paste unpremultiplied RGBA values */
target[k * 4 + 0] = src_red; target[k * 4 + 0] = src_red;
@ -1122,15 +1142,20 @@ font_render(FontObject *self, PyObject *args) {
if (src_alpha > 0) { if (src_alpha > 0) {
if (target[k * 4 + 3] > 0) { if (target[k * 4 + 3] > 0) {
target[k * 4 + 0] = BLEND( target[k * 4 + 0] = BLEND(
src_alpha, target[k * 4 + 0], ink[0], tmp); src_alpha, target[k * 4 + 0], ink[0], tmp
);
target[k * 4 + 1] = BLEND( target[k * 4 + 1] = BLEND(
src_alpha, target[k * 4 + 1], ink[1], tmp); src_alpha, target[k * 4 + 1], ink[1], tmp
);
target[k * 4 + 2] = BLEND( target[k * 4 + 2] = BLEND(
src_alpha, target[k * 4 + 2], ink[2], tmp); src_alpha, target[k * 4 + 2], ink[2], tmp
);
target[k * 4 + 3] = CLIP8( target[k * 4 + 3] = CLIP8(
src_alpha + src_alpha +
MULDIV255( MULDIV255(
target[k * 4 + 3], (255 - src_alpha), tmp)); target[k * 4 + 3], (255 - src_alpha), tmp
)
);
} else { } else {
target[k * 4 + 0] = ink[0]; target[k * 4 + 0] = ink[0];
target[k * 4 + 1] = ink[1]; target[k * 4 + 1] = ink[1];
@ -1148,7 +1173,9 @@ font_render(FontObject *self, PyObject *args) {
? CLIP8( ? CLIP8(
src_alpha + src_alpha +
MULDIV255( MULDIV255(
target[k], (255 - src_alpha), tmp)) target[k], (255 - src_alpha), tmp
)
)
: src_alpha; : src_alpha;
} }
} }
@ -1209,30 +1236,49 @@ font_getvarnames(FontObject *self) {
return NULL; return NULL;
} }
int *list_names_filled = PyMem_Malloc(num_namedstyles * sizeof(int));
if (list_names_filled == NULL) {
Py_DECREF(list_names);
FT_Done_MM_Var(library, master);
return PyErr_NoMemory();
}
for (int i = 0; i < num_namedstyles; i++) {
list_names_filled[i] = 0;
}
name_count = FT_Get_Sfnt_Name_Count(self->face); name_count = FT_Get_Sfnt_Name_Count(self->face);
for (i = 0; i < name_count; i++) { for (i = 0; i < name_count; i++) {
error = FT_Get_Sfnt_Name(self->face, i, &name); error = FT_Get_Sfnt_Name(self->face, i, &name);
if (error) { if (error) {
PyMem_Free(list_names_filled);
Py_DECREF(list_names); Py_DECREF(list_names);
FT_Done_MM_Var(library, master); FT_Done_MM_Var(library, master);
return geterror(error); return geterror(error);
} }
for (j = 0; j < num_namedstyles; j++) { for (j = 0; j < num_namedstyles; j++) {
if (PyList_GetItem(list_names, j) != NULL) { if (list_names_filled[j]) {
continue; continue;
} }
if (master->namedstyle[j].strid == name.name_id) { if (master->namedstyle[j].strid == name.name_id) {
list_name = Py_BuildValue("y#", name.string, name.string_len); list_name = Py_BuildValue("y#", name.string, name.string_len);
if (list_name == NULL) {
PyMem_Free(list_names_filled);
Py_DECREF(list_names);
FT_Done_MM_Var(library, master);
return NULL;
}
PyList_SetItem(list_names, j, list_name); PyList_SetItem(list_names, j, list_name);
list_names_filled[j] = 1;
break; break;
} }
} }
} }
PyMem_Free(list_names_filled);
FT_Done_MM_Var(library, master); FT_Done_MM_Var(library, master);
return list_names; return list_names;
} }
@ -1289,9 +1335,14 @@ font_getvaraxes(FontObject *self) {
if (name.name_id == axis.strid) { if (name.name_id == axis.strid) {
axis_name = Py_BuildValue("y#", name.string, name.string_len); axis_name = Py_BuildValue("y#", name.string, name.string_len);
PyDict_SetItemString( if (axis_name == NULL) {
list_axis, "name", axis_name ? axis_name : Py_None); Py_DECREF(list_axis);
Py_XDECREF(axis_name); Py_DECREF(list_axes);
FT_Done_MM_Var(library, master);
return NULL;
}
PyDict_SetItemString(list_axis, "name", axis_name);
Py_DECREF(axis_name);
break; break;
} }
} }
@ -1345,7 +1396,12 @@ font_setvaraxes(FontObject *self, PyObject *args) {
return PyErr_NoMemory(); return PyErr_NoMemory();
} }
for (i = 0; i < num_coords; i++) { for (i = 0; i < num_coords; i++) {
item = PyList_GET_ITEM(axes, i); item = PyList_GetItemRef(axes, i);
if (item == NULL) {
free(coords);
return NULL;
}
if (PyFloat_Check(item)) { if (PyFloat_Check(item)) {
coord = PyFloat_AS_DOUBLE(item); coord = PyFloat_AS_DOUBLE(item);
} else if (PyLong_Check(item)) { } else if (PyLong_Check(item)) {
@ -1353,10 +1409,12 @@ font_setvaraxes(FontObject *self, PyObject *args) {
} else if (PyNumber_Check(item)) { } else if (PyNumber_Check(item)) {
coord = PyFloat_AsDouble(item); coord = PyFloat_AsDouble(item);
} else { } else {
Py_DECREF(item);
free(coords); free(coords);
PyErr_SetString(PyExc_TypeError, "list must contain numbers"); PyErr_SetString(PyExc_TypeError, "list must contain numbers");
return NULL; return NULL;
} }
Py_DECREF(item);
coords[i] = coord * 65536; coords[i] = coord * 65536;
} }
@ -1393,7 +1451,8 @@ static PyMethodDef font_methods[] = {
{"setvarname", (PyCFunction)font_setvarname, METH_VARARGS}, {"setvarname", (PyCFunction)font_setvarname, METH_VARARGS},
{"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS}, {"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS},
#endif #endif
{NULL, NULL}}; {NULL, NULL}
};
static PyObject * static PyObject *
font_getattr_family(FontObject *self, void *closure) { font_getattr_family(FontObject *self, void *closure) {
@ -1450,7 +1509,8 @@ static struct PyGetSetDef font_getsetters[] = {
{"x_ppem", (getter)font_getattr_x_ppem}, {"x_ppem", (getter)font_getattr_x_ppem},
{"y_ppem", (getter)font_getattr_y_ppem}, {"y_ppem", (getter)font_getattr_y_ppem},
{"glyphs", (getter)font_getattr_glyphs}, {"glyphs", (getter)font_getattr_glyphs},
{NULL}}; {NULL}
};
static PyTypeObject Font_Type = { static PyTypeObject Font_Type = {
PyVarObject_HEAD_INIT(NULL, 0) "Font", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0) "Font", /*tp_name*/
@ -1486,7 +1546,8 @@ static PyTypeObject Font_Type = {
}; };
static PyMethodDef _functions[] = { static PyMethodDef _functions[] = {
{"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}}; {"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}
};
static int static int
setup_module(PyObject *m) { setup_module(PyObject *m) {
@ -1576,5 +1637,9 @@ PyInit__imagingft(void) {
return NULL; return NULL;
} }
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m; return m;
} }

View File

@ -209,7 +209,8 @@ _binop(PyObject *self, PyObject *args) {
} }
static PyMethodDef _functions[] = { static PyMethodDef _functions[] = {
{"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL}}; {"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL}
};
static void static void
install(PyObject *d, char *name, void *value) { install(PyObject *d, char *name, void *value) {
@ -290,5 +291,9 @@ PyInit__imagingmath(void) {
return NULL; return NULL;
} }
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m; return m;
} }

View File

@ -253,7 +253,8 @@ static PyMethodDef functions[] = {
{"apply", (PyCFunction)apply, METH_VARARGS, NULL}, {"apply", (PyCFunction)apply, METH_VARARGS, NULL},
{"get_on_pixels", (PyCFunction)get_on_pixels, METH_VARARGS, NULL}, {"get_on_pixels", (PyCFunction)get_on_pixels, METH_VARARGS, NULL},
{"match", (PyCFunction)match, METH_VARARGS, NULL}, {"match", (PyCFunction)match, METH_VARARGS, NULL},
{NULL, NULL, 0, NULL}}; {NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imagingmorph(void) { PyInit__imagingmorph(void) {
@ -269,5 +270,9 @@ PyInit__imagingmorph(void) {
m = PyModule_Create(&module_def); m = PyModule_Create(&module_def);
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m; return m;
} }

View File

@ -62,5 +62,10 @@ PyInit__imagingtk(void) {
Py_DECREF(m); Py_DECREF(m);
return NULL; return NULL;
} }
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m; return m;
} }

View File

@ -42,7 +42,8 @@ static const char *const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = {
"WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_INVALID_ARGUMENT",
"WEBP_MUX_BAD_DATA", "WEBP_MUX_BAD_DATA",
"WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_MEMORY_ERROR",
"WEBP_MUX_NOT_ENOUGH_DATA"}; "WEBP_MUX_NOT_ENOUGH_DATA"
};
PyObject * PyObject *
HandleMuxError(WebPMuxError err, char *chunk) { HandleMuxError(WebPMuxError err, char *chunk) {
@ -61,7 +62,8 @@ HandleMuxError(WebPMuxError err, char *chunk) {
sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]); sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]);
} else { } else {
message_len = sprintf( message_len = sprintf(
message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]); message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]
);
} }
if (message_len < 0) { if (message_len < 0) {
PyErr_SetString(PyExc_RuntimeError, "failed to construct error message"); PyErr_SetString(PyExc_RuntimeError, "failed to construct error message");
@ -138,7 +140,8 @@ _anim_encoder_new(PyObject *self, PyObject *args) {
&kmin, &kmin,
&kmax, &kmax,
&allow_mixed, &allow_mixed,
&verbose)) { &verbose
)) {
return NULL; return NULL;
} }
@ -214,7 +217,8 @@ _anim_encoder_add(PyObject *self, PyObject *args) {
&lossless, &lossless,
&quality_factor, &quality_factor,
&alpha_quality_factor, &alpha_quality_factor,
&method)) { &method
)) {
return NULL; return NULL;
} }
@ -283,7 +287,8 @@ _anim_encoder_assemble(PyObject *self, PyObject *args) {
&exif_bytes, &exif_bytes,
&exif_size, &exif_size,
&xmp_bytes, &xmp_bytes,
&xmp_size)) { &xmp_size
)) {
return NULL; return NULL;
} }
@ -421,7 +426,8 @@ _anim_decoder_get_info(PyObject *self) {
info->loop_count, info->loop_count,
info->bgcolor, info->bgcolor,
info->frame_count, info->frame_count,
decp->mode); decp->mode
);
} }
PyObject * PyObject *
@ -466,7 +472,8 @@ _anim_decoder_get_next(PyObject *self) {
} }
bytes = PyBytes_FromStringAndSize( bytes = PyBytes_FromStringAndSize(
(char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height); (char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height
);
ret = Py_BuildValue("Si", bytes, timestamp); ret = Py_BuildValue("Si", bytes, timestamp);
@ -621,7 +628,8 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
&exif_bytes, &exif_bytes,
&exif_size, &exif_size,
&xmp_bytes, &xmp_bytes,
&xmp_size)) { &xmp_size
)) {
return NULL; return NULL;
} }
@ -828,12 +836,14 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) {
if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) { if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) {
icc_profile = PyBytes_FromStringAndSize( icc_profile = PyBytes_FromStringAndSize(
(const char *)icc_profile_data.bytes, icc_profile_data.size); (const char *)icc_profile_data.bytes, icc_profile_data.size
);
} }
if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data)) { if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data)) {
exif = PyBytes_FromStringAndSize( exif = PyBytes_FromStringAndSize(
(const char *)exif_data.bytes, exif_data.size); (const char *)exif_data.bytes, exif_data.size
);
} }
WebPDataClear(&image.bitstream); WebPDataClear(&image.bitstream);
@ -848,12 +858,14 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) {
if (config.output.colorspace < MODE_YUV) { if (config.output.colorspace < MODE_YUV) {
bytes = PyBytes_FromStringAndSize( bytes = PyBytes_FromStringAndSize(
(char *)config.output.u.RGBA.rgba, config.output.u.RGBA.size); (char *)config.output.u.RGBA.rgba, config.output.u.RGBA.size
);
} else { } else {
// Skipping YUV for now. Need Test Images. // Skipping YUV for now. Need Test Images.
// UNDONE -- unclear if we'll ever get here if we set mode_rgb* // UNDONE -- unclear if we'll ever get here if we set mode_rgb*
bytes = PyBytes_FromStringAndSize( bytes = PyBytes_FromStringAndSize(
(char *)config.output.u.YUVA.y, config.output.u.YUVA.y_size); (char *)config.output.u.YUVA.y, config.output.u.YUVA.y_size
);
} }
pymode = PyUnicode_FromString(mode); pymode = PyUnicode_FromString(mode);
@ -864,7 +876,8 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) {
config.output.height, config.output.height,
pymode, pymode,
NULL == icc_profile ? Py_None : icc_profile, NULL == icc_profile ? Py_None : icc_profile,
NULL == exif ? Py_None : exif); NULL == exif ? Py_None : exif
);
end: end:
WebPFreeDecBuffer(&config.output); WebPFreeDecBuffer(&config.output);
@ -898,7 +911,8 @@ WebPDecoderVersion_str(void) {
"%d.%d.%d", "%d.%d.%d",
version_number >> 16, version_number >> 16,
(version_number >> 8) % 0x100, (version_number >> 8) % 0x100,
version_number % 0x100); version_number % 0x100
);
return version; return version;
} }
@ -932,7 +946,8 @@ static PyMethodDef webpMethods[] = {
WebPDecoderBuggyAlpha_wrapper, WebPDecoderBuggyAlpha_wrapper,
METH_NOARGS, METH_NOARGS,
"WebPDecoderBuggyAlpha"}, "WebPDecoderBuggyAlpha"},
{NULL, NULL}}; {NULL, NULL}
};
void void
addMuxFlagToModule(PyObject *m) { addMuxFlagToModule(PyObject *m) {
@ -1005,5 +1020,9 @@ PyInit__webp(void) {
return NULL; return NULL;
} }
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m; return m;
} }

View File

@ -46,7 +46,8 @@
typedef struct { typedef struct {
PyObject_HEAD int (*decode)( PyObject_HEAD int (*decode)(
Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes
);
int (*cleanup)(ImagingCodecState state); int (*cleanup)(ImagingCodecState state);
struct ImagingCodecStateInstance state; struct ImagingCodecStateInstance state;
Imaging im; Imaging im;
@ -889,7 +890,8 @@ PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) {
PY_LONG_LONG length = -1; PY_LONG_LONG length = -1;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length)) { args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length
)) {
return NULL; return NULL;
} }

View File

@ -105,7 +105,8 @@ _draw(ImagingDisplayObject *display, PyObject *args) {
src + 0, src + 0,
src + 1, src + 1,
src + 2, src + 2,
src + 3)) { src + 3
)) {
return NULL; return NULL;
} }
@ -221,7 +222,8 @@ _tobytes(ImagingDisplayObject *display, PyObject *args) {
} }
return PyBytes_FromStringAndSize( return PyBytes_FromStringAndSize(
display->dib->bits, display->dib->ysize * display->dib->linesize); display->dib->bits, display->dib->ysize * display->dib->linesize
);
} }
static struct PyMethodDef methods[] = { static struct PyMethodDef methods[] = {
@ -247,7 +249,8 @@ _getattr_size(ImagingDisplayObject *self, void *closure) {
} }
static struct PyGetSetDef getsetters[] = { static struct PyGetSetDef getsetters[] = {
{"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {NULL}}; {"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {NULL}
};
static PyTypeObject ImagingDisplayType = { static PyTypeObject ImagingDisplayType = {
PyVarObject_HEAD_INIT(NULL, 0) "ImagingDisplay", /*tp_name*/ PyVarObject_HEAD_INIT(NULL, 0) "ImagingDisplay", /*tp_name*/
@ -341,9 +344,8 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
// added in Windows 10 (1607) // added in Windows 10 (1607)
// loaded dynamically to avoid link errors // loaded dynamically to avoid link errors
user32 = LoadLibraryA("User32.dll"); user32 = LoadLibraryA("User32.dll");
SetThreadDpiAwarenessContext_function = SetThreadDpiAwarenessContext_function = (Func_SetThreadDpiAwarenessContext
(Func_SetThreadDpiAwarenessContext)GetProcAddress( )GetProcAddress(user32, "SetThreadDpiAwarenessContext");
user32, "SetThreadDpiAwarenessContext");
if (SetThreadDpiAwarenessContext_function != NULL) { if (SetThreadDpiAwarenessContext_function != NULL) {
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((DPI_CONTEXT_HANDLE)-3) // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((DPI_CONTEXT_HANDLE)-3)
dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE)-3); dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE)-3);
@ -403,7 +405,8 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
height, height,
PyBytes_AS_STRING(buffer), PyBytes_AS_STRING(buffer),
(BITMAPINFO *)&core, (BITMAPINFO *)&core,
DIB_RGB_COLORS)) { DIB_RGB_COLORS
)) {
goto error; goto error;
} }
@ -547,7 +550,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
ps.rcPaint.left, ps.rcPaint.left,
ps.rcPaint.top, ps.rcPaint.top,
ps.rcPaint.right, ps.rcPaint.right,
ps.rcPaint.bottom); ps.rcPaint.bottom
);
if (result) { if (result) {
Py_DECREF(result); Py_DECREF(result);
} else { } else {
@ -562,7 +566,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
0, 0,
0, 0,
rect.right - rect.left, rect.right - rect.left,
rect.bottom - rect.top); rect.bottom - rect.top
);
if (result) { if (result) {
Py_DECREF(result); Py_DECREF(result);
} else { } else {
@ -577,7 +582,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
0, 0,
0, 0,
rect.right - rect.left, rect.right - rect.left,
rect.bottom - rect.top); rect.bottom - rect.top
);
if (result) { if (result) {
Py_DECREF(result); Py_DECREF(result);
} else { } else {
@ -591,7 +597,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
case WM_SIZE: case WM_SIZE:
/* resize window */ /* resize window */
result = PyObject_CallFunction( result = PyObject_CallFunction(
callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam)); callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam)
);
if (result) { if (result) {
InvalidateRect(wnd, NULL, 1); InvalidateRect(wnd, NULL, 1);
Py_DECREF(result); Py_DECREF(result);
@ -670,7 +677,8 @@ PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
HWND_DESKTOP, HWND_DESKTOP,
NULL, NULL,
NULL, NULL,
NULL); NULL
);
if (!wnd) { if (!wnd) {
PyErr_SetString(PyExc_OSError, "failed to create window"); PyErr_SetString(PyExc_OSError, "failed to create window");
@ -732,7 +740,8 @@ PyImaging_DrawWmf(PyObject *self, PyObject *args) {
&x0, &x0,
&x1, &x1,
&y0, &y0,
&y1)) { &y1
)) {
return NULL; return NULL;
} }
@ -844,7 +853,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
PyErr_Format( PyErr_Format(
PyExc_OSError, PyExc_OSError,
"X connection failed: error %i", "X connection failed: error %i",
xcb_connection_has_error(connection)); xcb_connection_has_error(connection)
);
xcb_disconnect(connection); xcb_disconnect(connection);
return NULL; return NULL;
} }
@ -878,8 +888,10 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
0, 0,
width, width,
height, height,
0x00ffffff), 0x00ffffff
&error); ),
&error
);
if (reply == NULL) { if (reply == NULL) {
PyErr_Format( PyErr_Format(
PyExc_OSError, PyExc_OSError,
@ -887,7 +899,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
error->error_code, error->error_code,
error->major_code, error->major_code,
error->minor_code, error->minor_code,
error->resource_id); error->resource_id
);
free(error); free(error);
xcb_disconnect(connection); xcb_disconnect(connection);
return NULL; return NULL;
@ -897,7 +910,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
if (reply->depth == 24) { if (reply->depth == 24) {
buffer = PyBytes_FromStringAndSize( buffer = PyBytes_FromStringAndSize(
(char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply)); (char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply)
);
} else { } else {
PyErr_Format(PyExc_OSError, "unsupported bit depth: %i", reply->depth); PyErr_Format(PyExc_OSError, "unsupported bit depth: %i", reply->depth);
} }

View File

@ -25,6 +25,7 @@
#define PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN
#include "Python.h" #include "Python.h"
#include "thirdparty/pythoncapi_compat.h"
#include "libImaging/Imaging.h" #include "libImaging/Imaging.h"
#include "libImaging/Gif.h" #include "libImaging/Gif.h"
@ -38,7 +39,8 @@
typedef struct { typedef struct {
PyObject_HEAD int (*encode)( PyObject_HEAD int (*encode)(
Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes
);
int (*cleanup)(ImagingCodecState state); int (*cleanup)(ImagingCodecState state);
struct ImagingCodecStateInstance state; struct ImagingCodecStateInstance state;
Imaging im; Imaging im;
@ -134,7 +136,8 @@ _encode(ImagingEncoderObject *encoder, PyObject *args) {
} }
status = encoder->encode( status = encoder->encode(
encoder->im, &encoder->state, (UINT8 *)PyBytes_AsString(buf), bufsize); encoder->im, &encoder->state, (UINT8 *)PyBytes_AsString(buf), bufsize
);
/* adjust string length to avoid slicing in encoder */ /* adjust string length to avoid slicing in encoder */
if (_PyBytes_Resize(&buf, (status > 0) ? status : 0) < 0) { if (_PyBytes_Resize(&buf, (status > 0) ? status : 0) < 0) {
@ -571,7 +574,8 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
&compress_level, &compress_level,
&compress_type, &compress_type,
&dictionary, &dictionary,
&dictionary_size)) { &dictionary_size
)) {
return NULL; return NULL;
} }
@ -652,15 +656,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
PyObject *item; PyObject *item;
if (!PyArg_ParseTuple( if (!PyArg_ParseTuple(
args, args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types
"sssnsOO", )) {
&mode,
&rawmode,
&compname,
&fp,
&filename,
&tags,
&types)) {
return NULL; return NULL;
} }
@ -671,11 +668,17 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
tags_size = PyList_Size(tags); tags_size = PyList_Size(tags);
TRACE(("tags size: %d\n", (int)tags_size)); TRACE(("tags size: %d\n", (int)tags_size));
for (pos = 0; pos < tags_size; pos++) { for (pos = 0; pos < tags_size; pos++) {
item = PyList_GetItem(tags, pos); item = PyList_GetItemRef(tags, pos);
if (item == NULL) {
return NULL;
}
if (!PyTuple_Check(item) || PyTuple_Size(item) != 2) { if (!PyTuple_Check(item) || PyTuple_Size(item) != 2) {
Py_DECREF(item);
PyErr_SetString(PyExc_ValueError, "Invalid tags list"); PyErr_SetString(PyExc_ValueError, "Invalid tags list");
return NULL; return NULL;
} }
Py_DECREF(item);
} }
pos = 0; pos = 0;
} }
@ -703,11 +706,17 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
num_core_tags = sizeof(core_tags) / sizeof(int); num_core_tags = sizeof(core_tags) / sizeof(int);
for (pos = 0; pos < tags_size; pos++) { for (pos = 0; pos < tags_size; pos++) {
item = PyList_GetItem(tags, pos); item = PyList_GetItemRef(tags, pos);
if (item == NULL) {
return NULL;
}
// We already checked that tags is a 2-tuple list. // We already checked that tags is a 2-tuple list.
key = PyTuple_GetItem(item, 0); key = PyTuple_GET_ITEM(item, 0);
key_int = (int)PyLong_AsLong(key); key_int = (int)PyLong_AsLong(key);
value = PyTuple_GetItem(item, 1); value = PyTuple_GET_ITEM(item, 1);
Py_DECREF(item);
status = 0; status = 0;
is_core_tag = 0; is_core_tag = 0;
is_var_length = 0; is_var_length = 0;
@ -721,7 +730,10 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
} }
if (!is_core_tag) { if (!is_core_tag) {
PyObject *tag_type = PyDict_GetItem(types, key); PyObject *tag_type;
if (PyDict_GetItemRef(types, key, &tag_type) < 0) {
return NULL; // Exception has been already set
}
if (tag_type) { if (tag_type) {
int type_int = PyLong_AsLong(tag_type); int type_int = PyLong_AsLong(tag_type);
if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) { if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) {
@ -769,7 +781,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
is_var_length = 1; is_var_length = 1;
} }
if (ImagingLibTiffMergeFieldInfo( if (ImagingLibTiffMergeFieldInfo(
&encoder->state, type, key_int, is_var_length)) { &encoder->state, type, key_int, is_var_length
)) {
continue; continue;
} }
} }
@ -779,7 +792,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
&encoder->state, &encoder->state,
(ttag_t)key_int, (ttag_t)key_int,
PyBytes_Size(value), PyBytes_Size(value),
PyBytes_AsString(value)); PyBytes_AsString(value)
);
} else if (is_var_length) { } else if (is_var_length) {
Py_ssize_t len, i; Py_ssize_t len, i;
TRACE(("Setting from Tuple: %d \n", key_int)); TRACE(("Setting from Tuple: %d \n", key_int));
@ -789,7 +803,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
int stride = 256; int stride = 256;
if (len != 768) { if (len != 768) {
PyErr_SetString( PyErr_SetString(
PyExc_ValueError, "Requiring 768 items for Colormap"); PyExc_ValueError, "Requiring 768 items for Colormap"
);
return NULL; return NULL;
} }
UINT16 *av; UINT16 *av;
@ -804,7 +819,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
(ttag_t)key_int, (ttag_t)key_int,
av, av,
av + stride, av + stride,
av + stride * 2); av + stride * 2
);
free(av); free(av);
} }
} else if (key_int == TIFFTAG_YCBCRSUBSAMPLING) { } else if (key_int == TIFFTAG_YCBCRSUBSAMPLING) {
@ -812,7 +828,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
&encoder->state, &encoder->state,
(ttag_t)key_int, (ttag_t)key_int,
(UINT16)PyLong_AsLong(PyTuple_GetItem(value, 0)), (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 0)),
(UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1))); (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1))
);
} else if (type == TIFF_SHORT) { } else if (type == TIFF_SHORT) {
UINT16 *av; UINT16 *av;
/* malloc check ok, calloc checks for overflow */ /* malloc check ok, calloc checks for overflow */
@ -822,7 +839,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (UINT16)PyLong_AsLong(PyTuple_GetItem(value, i)); av[i] = (UINT16)PyLong_AsLong(PyTuple_GetItem(value, i));
} }
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, len, av); &encoder->state, (ttag_t)key_int, len, av
);
free(av); free(av);
} }
} else if (type == TIFF_LONG) { } else if (type == TIFF_LONG) {
@ -834,7 +852,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (UINT32)PyLong_AsLong(PyTuple_GetItem(value, i)); av[i] = (UINT32)PyLong_AsLong(PyTuple_GetItem(value, i));
} }
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, len, av); &encoder->state, (ttag_t)key_int, len, av
);
free(av); free(av);
} }
} else if (type == TIFF_SBYTE) { } else if (type == TIFF_SBYTE) {
@ -846,7 +865,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (INT8)PyLong_AsLong(PyTuple_GetItem(value, i)); av[i] = (INT8)PyLong_AsLong(PyTuple_GetItem(value, i));
} }
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, len, av); &encoder->state, (ttag_t)key_int, len, av
);
free(av); free(av);
} }
} else if (type == TIFF_SSHORT) { } else if (type == TIFF_SSHORT) {
@ -858,7 +878,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (INT16)PyLong_AsLong(PyTuple_GetItem(value, i)); av[i] = (INT16)PyLong_AsLong(PyTuple_GetItem(value, i));
} }
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, len, av); &encoder->state, (ttag_t)key_int, len, av
);
free(av); free(av);
} }
} else if (type == TIFF_SLONG) { } else if (type == TIFF_SLONG) {
@ -870,7 +891,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (INT32)PyLong_AsLong(PyTuple_GetItem(value, i)); av[i] = (INT32)PyLong_AsLong(PyTuple_GetItem(value, i));
} }
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, len, av); &encoder->state, (ttag_t)key_int, len, av
);
free(av); free(av);
} }
} else if (type == TIFF_FLOAT) { } else if (type == TIFF_FLOAT) {
@ -882,7 +904,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i)); av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i));
} }
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, len, av); &encoder->state, (ttag_t)key_int, len, av
);
free(av); free(av);
} }
} else if (type == TIFF_DOUBLE) { } else if (type == TIFF_DOUBLE) {
@ -894,43 +917,54 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
av[i] = PyFloat_AsDouble(PyTuple_GetItem(value, i)); av[i] = PyFloat_AsDouble(PyTuple_GetItem(value, i));
} }
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, len, av); &encoder->state, (ttag_t)key_int, len, av
);
free(av); free(av);
} }
} }
} else { } else {
if (type == TIFF_SHORT) { if (type == TIFF_SHORT) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value)); &encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value)
);
} else if (type == TIFF_LONG) { } else if (type == TIFF_LONG) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value)); &encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value)
);
} else if (type == TIFF_SSHORT) { } else if (type == TIFF_SSHORT) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (INT16)PyLong_AsLong(value)); &encoder->state, (ttag_t)key_int, (INT16)PyLong_AsLong(value)
);
} else if (type == TIFF_SLONG) { } else if (type == TIFF_SLONG) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (INT32)PyLong_AsLong(value)); &encoder->state, (ttag_t)key_int, (INT32)PyLong_AsLong(value)
);
} else if (type == TIFF_FLOAT) { } else if (type == TIFF_FLOAT) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value)); &encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value)
);
} else if (type == TIFF_DOUBLE) { } else if (type == TIFF_DOUBLE) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)); &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
);
} else if (type == TIFF_SBYTE) { } else if (type == TIFF_SBYTE) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value)); &encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value)
);
} else if (type == TIFF_ASCII) { } else if (type == TIFF_ASCII) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, PyBytes_AsString(value)); &encoder->state, (ttag_t)key_int, PyBytes_AsString(value)
);
} else if (type == TIFF_RATIONAL) { } else if (type == TIFF_RATIONAL) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)); &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
);
} else { } else {
TRACE( TRACE(
("Unhandled type for key %d : %s \n", ("Unhandled type for key %d : %s \n",
key_int, key_int,
PyBytes_AsString(PyObject_Str(value)))); PyBytes_AsString(PyObject_Str(value)))
);
} }
} }
if (!status) { if (!status) {
@ -991,7 +1025,8 @@ get_qtables_arrays(PyObject *qtables, int *qtablesLen) {
if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) { if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) {
PyErr_SetString( PyErr_SetString(
PyExc_ValueError, PyExc_ValueError,
"Not a valid number of quantization tables. Should be between 1 and 4."); "Not a valid number of quantization tables. Should be between 1 and 4."
);
Py_DECREF(tables); Py_DECREF(tables);
return NULL; return NULL;
} }
@ -1080,7 +1115,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
&extra, &extra,
&extra_size, &extra_size,
&rawExif, &rawExif,
&rawExifLen)) { &rawExifLen
)) {
return NULL; return NULL;
} }
@ -1150,29 +1186,27 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
encoder->encode = ImagingJpegEncode; encoder->encode = ImagingJpegEncode;
strncpy(((JPEGENCODERSTATE *)encoder->state.context)->rawmode, rawmode, 8); JPEGENCODERSTATE *jpeg_encoder_state = (JPEGENCODERSTATE *)encoder->state.context;
strncpy(jpeg_encoder_state->rawmode, rawmode, 8);
((JPEGENCODERSTATE *)encoder->state.context)->keep_rgb = keep_rgb; jpeg_encoder_state->keep_rgb = keep_rgb;
((JPEGENCODERSTATE *)encoder->state.context)->quality = quality; jpeg_encoder_state->quality = quality;
((JPEGENCODERSTATE *)encoder->state.context)->qtables = qarrays; jpeg_encoder_state->qtables = qarrays;
((JPEGENCODERSTATE *)encoder->state.context)->qtablesLen = qtablesLen; jpeg_encoder_state->qtablesLen = qtablesLen;
((JPEGENCODERSTATE *)encoder->state.context)->subsampling = subsampling; jpeg_encoder_state->subsampling = subsampling;
((JPEGENCODERSTATE *)encoder->state.context)->progressive = progressive; jpeg_encoder_state->progressive = progressive;
((JPEGENCODERSTATE *)encoder->state.context)->smooth = smooth; jpeg_encoder_state->smooth = smooth;
((JPEGENCODERSTATE *)encoder->state.context)->optimize = optimize; jpeg_encoder_state->optimize = optimize;
((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype; jpeg_encoder_state->streamtype = streamtype;
((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi; jpeg_encoder_state->xdpi = xdpi;
((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi; jpeg_encoder_state->ydpi = ydpi;
((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_blocks = jpeg_encoder_state->restart_marker_blocks = restart_marker_blocks;
restart_marker_blocks; jpeg_encoder_state->restart_marker_rows = restart_marker_rows;
((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_rows = jpeg_encoder_state->comment = comment;
restart_marker_rows; jpeg_encoder_state->comment_size = comment_size;
((JPEGENCODERSTATE *)encoder->state.context)->comment = comment; jpeg_encoder_state->extra = extra;
((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size; jpeg_encoder_state->extra_size = extra_size;
((JPEGENCODERSTATE *)encoder->state.context)->extra = extra; jpeg_encoder_state->rawExif = rawExif;
((JPEGENCODERSTATE *)encoder->state.context)->extra_size = extra_size; jpeg_encoder_state->rawExifLen = rawExifLen;
((JPEGENCODERSTATE *)encoder->state.context)->rawExif = rawExif;
((JPEGENCODERSTATE *)encoder->state.context)->rawExifLen = rawExifLen;
return (PyObject *)encoder; return (PyObject *)encoder;
} }
@ -1250,7 +1284,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
&fd, &fd,
&comment, &comment,
&comment_size, &comment_size,
&plt)) { &plt
)) {
return NULL; return NULL;
} }
@ -1307,7 +1342,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y); j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y);
j2k_decode_coord_tuple( j2k_decode_coord_tuple(
tile_offset, &context->tile_offset_x, &context->tile_offset_y); tile_offset, &context->tile_offset_x, &context->tile_offset_y
);
j2k_decode_coord_tuple(tile_size, &context->tile_size_x, &context->tile_size_y); j2k_decode_coord_tuple(tile_size, &context->tile_size_x, &context->tile_size_y);
/* Error on illegal tile offsets */ /* Error on illegal tile offsets */
@ -1317,7 +1353,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
PyErr_SetString( PyErr_SetString(
PyExc_ValueError, PyExc_ValueError,
"JPEG 2000 tile offset too small; top left tile must " "JPEG 2000 tile offset too small; top left tile must "
"intersect image area"); "intersect image area"
);
Py_DECREF(encoder); Py_DECREF(encoder);
return NULL; return NULL;
} }
@ -1325,8 +1362,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
if (context->tile_offset_x > context->offset_x || if (context->tile_offset_x > context->offset_x ||
context->tile_offset_y > context->offset_y) { context->tile_offset_y > context->offset_y) {
PyErr_SetString( PyErr_SetString(
PyExc_ValueError, PyExc_ValueError, "JPEG 2000 tile offset too large to cover image area"
"JPEG 2000 tile offset too large to cover image area"); );
Py_DECREF(encoder); Py_DECREF(encoder);
return NULL; return NULL;
} }
@ -1360,7 +1397,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
j2k_decode_coord_tuple(cblk_size, &context->cblk_width, &context->cblk_height); j2k_decode_coord_tuple(cblk_size, &context->cblk_width, &context->cblk_height);
j2k_decode_coord_tuple( j2k_decode_coord_tuple(
precinct_size, &context->precinct_width, &context->precinct_height); precinct_size, &context->precinct_width, &context->precinct_height
);
context->irreversible = PyObject_IsTrue(irreversible); context->irreversible = PyObject_IsTrue(irreversible);
context->progression = prog_order; context->progression = prog_order;

View File

@ -36,7 +36,8 @@ add_item(const char *mode) {
"AccessInit: hash collision: %d for both %s and %s\n", "AccessInit: hash collision: %d for both %s and %s\n",
i, i,
mode, mode,
access_table[i].mode); access_table[i].mode
);
exit(1); exit(1);
} }
access_table[i].mode = mode; access_table[i].mode = mode;

View File

@ -243,7 +243,8 @@ static const bc7_mode_info bc7_modes[] = {
{1, 0, 2, 1, 5, 6, 0, 0, 2, 3}, {1, 0, 2, 1, 5, 6, 0, 0, 2, 3},
{1, 0, 2, 0, 7, 8, 0, 0, 2, 2}, {1, 0, 2, 0, 7, 8, 0, 0, 2, 2},
{1, 0, 0, 0, 7, 7, 1, 0, 4, 0}, {1, 0, 0, 0, 7, 7, 1, 0, 4, 0},
{2, 6, 0, 0, 5, 5, 1, 0, 2, 0}}; {2, 6, 0, 0, 5, 5, 1, 0, 2, 0}
};
/* Subset indices: /* Subset indices:
Table.P2, 1 bit per index */ Table.P2, 1 bit per index */
@ -254,7 +255,8 @@ static const UINT16 bc7_si2[] = {
0x718e, 0x399c, 0xaaaa, 0xf0f0, 0x5a5a, 0x33cc, 0x3c3c, 0x55aa, 0x9696, 0xa55a, 0x718e, 0x399c, 0xaaaa, 0xf0f0, 0x5a5a, 0x33cc, 0x3c3c, 0x55aa, 0x9696, 0xa55a,
0x73ce, 0x13c8, 0x324c, 0x3bdc, 0x6996, 0xc33c, 0x9966, 0x0660, 0x0272, 0x04e4, 0x73ce, 0x13c8, 0x324c, 0x3bdc, 0x6996, 0xc33c, 0x9966, 0x0660, 0x0272, 0x04e4,
0x4e40, 0x2720, 0xc936, 0x936c, 0x39c6, 0x639c, 0x9336, 0x9cc6, 0x817e, 0xe718, 0x4e40, 0x2720, 0xc936, 0x936c, 0x39c6, 0x639c, 0x9336, 0x9cc6, 0x817e, 0xe718,
0xccf0, 0x0fcc, 0x7744, 0xee22}; 0xccf0, 0x0fcc, 0x7744, 0xee22
};
/* Table.P3, 2 bits per index */ /* Table.P3, 2 bits per index */
static const UINT32 bc7_si3[] = { static const UINT32 bc7_si3[] = {
@ -267,20 +269,23 @@ static const UINT32 bc7_si3[] = {
0x66660000, 0xa5a0a5a0, 0x50a050a0, 0x69286928, 0x44aaaa44, 0x66666600, 0xaa444444, 0x66660000, 0xa5a0a5a0, 0x50a050a0, 0x69286928, 0x44aaaa44, 0x66666600, 0xaa444444,
0x54a854a8, 0x95809580, 0x96969600, 0xa85454a8, 0x80959580, 0xaa141414, 0x96960000, 0x54a854a8, 0x95809580, 0x96969600, 0xa85454a8, 0x80959580, 0xaa141414, 0x96960000,
0xaaaa1414, 0xa05050a0, 0xa0a5a5a0, 0x96000000, 0x40804080, 0xa9a8a9a8, 0xaaaaaa44, 0xaaaa1414, 0xa05050a0, 0xa0a5a5a0, 0x96000000, 0x40804080, 0xa9a8a9a8, 0xaaaaaa44,
0x2a4a5254}; 0x2a4a5254
};
/* Anchor indices: /* Anchor indices:
Table.A2 */ Table.A2 */
static const char bc7_ai0[] = { static const char bc7_ai0[] = {15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 8, 2, 2, 8, 15, 15, 15, 15, 2, 8, 2, 2, 8, 8, 15, 2, 8,
8, 15, 2, 8, 2, 2, 8, 8, 2, 2, 15, 15, 6, 8, 2, 8, 15, 15, 2, 8, 2, 2, 2, 2, 8, 8, 2, 2, 15, 15, 6, 8, 2, 8, 15,
2, 15, 15, 6, 6, 2, 6, 8, 15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15}; 15, 2, 8, 2, 2, 2, 15, 15, 6, 6, 2, 6, 8,
15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15};
/* Table.A3a */ /* Table.A3a */
static const char bc7_ai1[] = { static const char bc7_ai1[] = {3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6,
3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8, 15, 3, 3, 5, 3, 3, 3, 3, 8, 15, 3, 3, 6, 10, 5, 8,
6, 10, 5, 8, 8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8, 15, 15, 3, 15, 5, 8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8,
15, 15, 15, 15, 3, 15, 5, 5, 5, 8, 5, 10, 5, 10, 8, 13, 15, 12, 3, 3}; 15, 15, 3, 15, 5, 15, 15, 15, 15, 3, 15, 5, 5,
5, 8, 5, 10, 5, 10, 8, 13, 15, 12, 3, 3};
/* Table.A3b */ /* Table.A3b */
static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 15, static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 15,
@ -293,7 +298,8 @@ static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 1
static const char bc7_weights2[] = {0, 21, 43, 64}; static const char bc7_weights2[] = {0, 21, 43, 64};
static const char bc7_weights3[] = {0, 9, 18, 27, 37, 46, 55, 64}; static const char bc7_weights3[] = {0, 9, 18, 27, 37, 46, 55, 64};
static const char bc7_weights4[] = { static const char bc7_weights4[] = {
0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64}; 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64
};
static const char * static const char *
bc7_get_weights(int n) { bc7_get_weights(int n) {
@ -526,7 +532,8 @@ static const bc6_mode_info bc6_modes[] = {
{1, 0, 0, 10, 10, 10, 10}, {1, 0, 0, 10, 10, 10, 10},
{1, 1, 0, 11, 9, 9, 9}, {1, 1, 0, 11, 9, 9, 9},
{1, 1, 0, 12, 8, 8, 8}, {1, 1, 0, 12, 8, 8, 8},
{1, 1, 0, 16, 4, 4, 4}}; {1, 1, 0, 16, 4, 4, 4}
};
/* Table.F, encoded as a sequence of bit indices */ /* Table.F, encoded as a sequence of bit indices */
static const UINT8 bc6_bit_packings[][75] = { static const UINT8 bc6_bit_packings[][75] = {
@ -591,7 +598,8 @@ static const UINT8 bc6_bit_packings[][75] = {
64, 65, 66, 67, 68, 69, 70, 71, 27, 26, 80, 81, 82, 83, 84, 85, 86, 87, 43, 42}, 64, 65, 66, 67, 68, 69, 70, 71, 27, 26, 80, 81, 82, 83, 84, 85, 86, 87, 43, 42},
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 15, 14, 13, 12, 11, 10, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 15, 14, 13, 12, 11, 10,
64, 65, 66, 67, 31, 30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, 42}}; 64, 65, 66, 67, 31, 30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, 42}
};
static void static void
bc6_sign_extend(UINT16 *v, int prec) { bc6_sign_extend(UINT16 *v, int prec) {
@ -830,7 +838,8 @@ decode_bcn(
int bytes, int bytes,
int N, int N,
int C, int C,
char *pixel_format) { char *pixel_format
) {
int ymax = state->ysize + state->yoff; int ymax = state->ysize + state->yoff;
const UINT8 *ptr = src; const UINT8 *ptr = src;
switch (N) { switch (N) {

View File

@ -13,7 +13,8 @@ void static inline ImagingLineBoxBlur32(
int edgeA, int edgeA,
int edgeB, int edgeB,
UINT32 ww, UINT32 ww,
UINT32 fw) { UINT32 fw
) {
int x; int x;
UINT32 acc[4]; UINT32 acc[4];
UINT32 bulk[4]; UINT32 bulk[4];
@ -109,7 +110,8 @@ void static inline ImagingLineBoxBlur8(
int edgeA, int edgeA,
int edgeB, int edgeB,
UINT32 ww, UINT32 ww,
UINT32 fw) { UINT32 fw
) {
int x; int x;
UINT32 acc; UINT32 acc;
UINT32 bulk; UINT32 bulk;
@ -198,7 +200,8 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) {
edgeA, edgeA,
edgeB, edgeB,
ww, ww,
fw); fw
);
if (imIn == imOut) { if (imIn == imOut) {
// Commit. // Commit.
memcpy(imOut->image8[y], lineOut, imIn->xsize); memcpy(imOut->image8[y], lineOut, imIn->xsize);
@ -214,7 +217,8 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) {
edgeA, edgeA,
edgeB, edgeB,
ww, ww,
fw); fw
);
if (imIn == imOut) { if (imIn == imOut) {
// Commit. // Commit.
memcpy(imOut->image32[y], lineOut, imIn->xsize * 4); memcpy(imOut->image32[y], lineOut, imIn->xsize * 4);
@ -314,11 +318,13 @@ _gaussian_blur_radius(float radius, int passes) {
Imaging Imaging
ImagingGaussianBlur( ImagingGaussianBlur(
Imaging imOut, Imaging imIn, float xradius, float yradius, int passes) { Imaging imOut, Imaging imIn, float xradius, float yradius, int passes
) {
return ImagingBoxBlur( return ImagingBoxBlur(
imOut, imOut,
imIn, imIn,
_gaussian_blur_radius(xradius, passes), _gaussian_blur_radius(xradius, passes),
_gaussian_blur_radius(yradius, passes), _gaussian_blur_radius(yradius, passes),
passes); passes
);
} }

View File

@ -142,7 +142,8 @@ ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) {
CHOP2( CHOP2(
(((255 - in1[x]) * (in1[x] * in2[x])) / 65536) + (((255 - in1[x]) * (in1[x] * in2[x])) / 65536) +
(in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255, (in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255,
NULL); NULL
);
} }
Imaging Imaging
@ -150,7 +151,8 @@ ImagingChopHardLight(Imaging imIn1, Imaging imIn2) {
CHOP2( CHOP2(
(in2[x] < 128) ? ((in1[x] * in2[x]) / 127) (in2[x] < 128) ? ((in1[x] * in2[x]) / 127)
: 255 - (((255 - in2[x]) * (255 - in1[x])) / 127), : 255 - (((255 - in2[x]) * (255 - in1[x])) / 127),
NULL); NULL
);
} }
Imaging Imaging
@ -158,5 +160,6 @@ ImagingOverlay(Imaging imIn1, Imaging imIn2) {
CHOP2( CHOP2(
(in1[x] < 128) ? ((in1[x] * in2[x]) / 127) (in1[x] < 128) ? ((in1[x] * in2[x]) / 127)
: 255 - (((255 - in1[x]) * (255 - in2[x])) / 127), : 255 - (((255 - in1[x]) * (255 - in2[x])) / 127),
NULL); NULL
);
} }

Some files were not shown because too many files have changed in this diff Show More