Merge branch 'main' into init
|
@ -37,12 +37,18 @@ python3 -m pip install -U pytest-timeout
|
|||
python3 -m pip install pyroma
|
||||
|
||||
if [[ $(uname) != CYGWIN* ]]; then
|
||||
python3 -m pip install numpy
|
||||
# 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
|
||||
fi
|
||||
|
||||
# PyQt6 doesn't support PyPy3
|
||||
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
|
||||
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
|
||||
|
||||
# Pyroma uses non-isolated build and fails with old setuptools
|
||||
|
|
|
@ -1 +1 @@
|
|||
mypy==1.10.1
|
||||
mypy==1.11.0
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
BasedOnStyle: Google
|
||||
AlwaysBreakAfterReturnType: All
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakBeforeBraces: Attach
|
||||
|
|
2
.github/workflows/cifuzz.yml
vendored
|
@ -24,6 +24,8 @@ concurrency:
|
|||
|
||||
jobs:
|
||||
Fuzzing:
|
||||
# Disabled until google/oss-fuzz#11419 upgrades Python to 3.9+
|
||||
if: false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Build Fuzzers
|
||||
|
|
30
.github/workflows/test.yml
vendored
|
@ -50,26 +50,24 @@ jobs:
|
|||
"3.9",
|
||||
]
|
||||
include:
|
||||
- python-version: "3.11"
|
||||
PYTHONOPTIMIZE: 1
|
||||
REVERSE: "--reverse"
|
||||
- python-version: "3.10"
|
||||
PYTHONOPTIMIZE: 2
|
||||
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
||||
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
|
||||
# Free-threaded
|
||||
- { os: "ubuntu-latest", python-version: "3.13-dev", disable-gil: true }
|
||||
# M1 only available for 3.10+
|
||||
- os: "macos-13"
|
||||
python-version: "3.9"
|
||||
- { os: "macos-13", python-version: "3.9" }
|
||||
exclude:
|
||||
- os: "macos-14"
|
||||
python-version: "3.9"
|
||||
- { os: "macos-14", python-version: "3.9" }
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }} ${{ matrix.disable-gil && 'free-threaded' || '' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
if: "${{ !matrix.disable-gil }}"
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
allow-prereleases: true
|
||||
|
@ -78,6 +76,18 @@ jobs:
|
|||
".ci/*.sh"
|
||||
"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
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
|
|
6
.github/workflows/wheels-test.sh
vendored
|
@ -12,8 +12,14 @@ elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
|
|||
else
|
||||
yum install -y fribidi
|
||||
fi
|
||||
|
||||
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
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -d "test-images-main" ]; then
|
||||
|
|
33
.github/workflows/wheels.yml
vendored
|
@ -1,6 +1,14 @@
|
|||
name: Wheels
|
||||
|
||||
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:
|
||||
paths:
|
||||
- ".ci/requirements-cibw.txt"
|
||||
|
@ -33,6 +41,7 @@ env:
|
|||
|
||||
jobs:
|
||||
build-1-QEMU-emulated-wheels:
|
||||
if: github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'
|
||||
name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
|
@ -88,6 +97,7 @@ jobs:
|
|||
path: ./wheelhouse/*.whl
|
||||
|
||||
build-2-native-wheels:
|
||||
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
|
@ -129,6 +139,7 @@ jobs:
|
|||
env:
|
||||
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||
CIBW_BUILD: ${{ matrix.build }}
|
||||
CIBW_FREE_THREADED_SUPPORT: True
|
||||
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_PRERELEASE_PYTHONS: True
|
||||
|
@ -140,6 +151,7 @@ jobs:
|
|||
path: ./wheelhouse/*.whl
|
||||
|
||||
windows:
|
||||
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
|
||||
name: Windows ${{ matrix.cibw_arch }}
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
|
@ -201,6 +213,7 @@ jobs:
|
|||
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
||||
CIBW_CACHE_PATH: "C:\\cibw"
|
||||
CIBW_FREE_THREADED_SUPPORT: True
|
||||
CIBW_PRERELEASE_PYTHONS: True
|
||||
CIBW_TEST_SKIP: "*-win_arm64"
|
||||
CIBW_TEST_COMMAND: 'docker run --rm
|
||||
|
@ -225,6 +238,7 @@ jobs:
|
|||
path: winbuild\build\bin\fribidi*
|
||||
|
||||
sdist:
|
||||
if: github.event_name != 'schedule'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -243,8 +257,25 @@ jobs:
|
|||
name: dist-sdist
|
||||
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:
|
||||
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]
|
||||
runs-on: ubuntu-latest
|
||||
name: Upload release to PyPI
|
||||
|
|
12
CHANGES.rst
|
@ -5,6 +5,18 @@ Changelog (Pillow)
|
|||
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
|
||||
[hugovk, radarhere]
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import ContainerIO, Image
|
||||
|
@ -23,6 +21,13 @@ def test_isatty() -> None:
|
|||
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(
|
||||
"mode, expected_position",
|
||||
(
|
||||
|
@ -31,7 +36,7 @@ def test_isatty() -> None:
|
|||
(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
|
||||
with open(TEST_FILE, "rb") as fh:
|
||||
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
|
||||
|
||||
|
||||
@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))
|
||||
def test_read_n0(bytesmode: bool) -> None:
|
||||
# Arrange
|
||||
|
@ -51,7 +64,7 @@ def test_read_n0(bytesmode: bool) -> None:
|
|||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||
|
||||
# Act
|
||||
container.seek(81)
|
||||
assert container.seek(81) == 81
|
||||
data = container.read()
|
||||
|
||||
# Assert
|
||||
|
@ -67,7 +80,7 @@ def test_read_n(bytesmode: bool) -> None:
|
|||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||
|
||||
# Act
|
||||
container.seek(81)
|
||||
assert container.seek(81) == 81
|
||||
data = container.read(3)
|
||||
|
||||
# Assert
|
||||
|
@ -83,7 +96,7 @@ def test_read_eof(bytesmode: bool) -> None:
|
|||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||
|
||||
# Act
|
||||
container.seek(100)
|
||||
assert container.seek(100) == 100
|
||||
data = container.read()
|
||||
|
||||
# Assert
|
||||
|
@ -94,21 +107,65 @@ def test_read_eof(bytesmode: bool) -> None:
|
|||
|
||||
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||
def test_readline(bytesmode: bool) -> None:
|
||||
# Arrange
|
||||
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||
|
||||
# Act
|
||||
data = container.readline()
|
||||
|
||||
# Assert
|
||||
if bytesmode:
|
||||
data = data.decode()
|
||||
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))
|
||||
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
|
||||
expected = [
|
||||
"This is line 1\n",
|
||||
|
@ -124,9 +181,21 @@ def test_readlines(bytesmode: bool) -> None:
|
|||
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||
|
||||
# Act
|
||||
data = container.readlines()
|
||||
data = []
|
||||
for line in container:
|
||||
data.append(line)
|
||||
|
||||
# Assert
|
||||
if bytesmode:
|
||||
data = [line.decode() for line in data]
|
||||
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()
|
||||
|
|
|
@ -57,6 +57,7 @@ def test_getiptcinfo_fotostation() -> None:
|
|||
iptc = IptcImagePlugin.getiptcinfo(im)
|
||||
|
||||
# Assert
|
||||
assert iptc is not None
|
||||
for tag in iptc.keys():
|
||||
if tag[0] == 240:
|
||||
return
|
||||
|
|
|
@ -829,7 +829,7 @@ class TestFileJpeg:
|
|||
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
|
||||
# Act / Assert
|
||||
# "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)
|
||||
|
||||
def test_invalid_exif(self) -> None:
|
||||
|
|
|
@ -240,9 +240,10 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
|
||||
new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
for tag, info in core_items.items():
|
||||
assert info.type is not None
|
||||
if info.length == 1:
|
||||
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))
|
||||
else:
|
||||
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
|
||||
|
|
|
@ -2,11 +2,11 @@ from __future__ import annotations
|
|||
|
||||
import warnings
|
||||
from io import BytesIO
|
||||
from typing import Any, cast
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, MpoImagePlugin
|
||||
from PIL import Image, ImageFile, MpoImagePlugin
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -20,11 +20,11 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"]
|
|||
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()
|
||||
im.save(out, "MPO", **options)
|
||||
out.seek(0)
|
||||
return cast(MpoImagePlugin.MpoImageFile, Image.open(out))
|
||||
return Image.open(out)
|
||||
|
||||
|
||||
@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())
|
||||
|
||||
for im in (im_original, im_reloaded):
|
||||
assert isinstance(im, MpoImagePlugin.MpoImageFile)
|
||||
info = im._getexif()
|
||||
assert info is not None
|
||||
assert info[272] == "Nintendo 3DS"
|
||||
assert info[296] == 2
|
||||
assert info[34665] == 188
|
||||
|
@ -226,6 +228,12 @@ def test_eoferror() -> None:
|
|||
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:
|
||||
with Image.open("Tests/images/ultrahdr.jpg") as im:
|
||||
assert im.format == "JPEG"
|
||||
|
@ -275,6 +283,8 @@ def test_save_all() -> None:
|
|||
im_reloaded = roundtrip(im, save_all=True, append_images=[im2])
|
||||
|
||||
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"
|
||||
|
||||
im_reloaded.seek(1)
|
||||
|
|
|
@ -229,6 +229,7 @@ def test_pdf_append_fails_on_nonexistent_file() -> 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)
|
||||
assert b"Parent" not in pages_info
|
||||
assert b"Kids" in pages_info
|
||||
|
|
|
@ -41,7 +41,7 @@ MAGIC = PngImagePlugin._MAGIC
|
|||
|
||||
def chunk(cid: bytes, *data: bytes) -> bytes:
|
||||
test_file = BytesIO()
|
||||
PngImagePlugin.putchunk(*(test_file, cid) + data)
|
||||
PngImagePlugin.putchunk(test_file, cid, *data)
|
||||
return test_file.getvalue()
|
||||
|
||||
|
||||
|
@ -424,8 +424,10 @@ class TestFilePng:
|
|||
im = roundtrip(im, pnginfo=info)
|
||||
assert im.info == {"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"].tkey == "Spam"
|
||||
assert isinstance(im.text["eggs"], PngImagePlugin.iTXt)
|
||||
assert im.text["eggs"].lang == "en"
|
||||
assert im.text["eggs"].tkey == "Eggs"
|
||||
|
||||
|
@ -776,7 +778,7 @@ class TestFilePng:
|
|||
|
||||
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:
|
||||
im.save(sys.stdout, "PNG")
|
||||
|
|
|
@ -373,7 +373,7 @@ def test_save_stdout(buffer: bool) -> None:
|
|||
|
||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||
|
||||
sys.stdout = mystdout # type: ignore[assignment]
|
||||
sys.stdout = mystdout
|
||||
|
||||
with Image.open(TEST_FILE) as im:
|
||||
im.save(sys.stdout, "PPM")
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import AnyStr
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -92,7 +93,7 @@ def test_textsize(request: pytest.FixtureRequest, tmp_path: Path) -> None:
|
|||
|
||||
|
||||
def _test_high_characters(
|
||||
request: pytest.FixtureRequest, tmp_path: Path, message: str | bytes
|
||||
request: pytest.FixtureRequest, tmp_path: Path, message: AnyStr
|
||||
) -> None:
|
||||
tempname = save_font(request, tmp_path)
|
||||
font = ImageFont.load(tempname)
|
||||
|
|
|
@ -90,6 +90,7 @@ class TestImageFile:
|
|||
data = f.read()
|
||||
with ImageFile.Parser() as p:
|
||||
p.feed(data)
|
||||
assert p.image is not None
|
||||
assert (48, 48) == p.image.size
|
||||
|
||||
@skip_unless_feature("webp")
|
||||
|
@ -103,6 +104,7 @@ class TestImageFile:
|
|||
assert not p.image
|
||||
|
||||
p.feed(f.read())
|
||||
assert p.image is not None
|
||||
assert (128, 128) == p.image.size
|
||||
|
||||
@skip_unless_feature("zlib")
|
||||
|
@ -125,7 +127,7 @@ class TestImageFile:
|
|||
def test_raise_typeerror(self) -> None:
|
||||
with pytest.raises(TypeError):
|
||||
parser = ImageFile.Parser()
|
||||
parser.feed(1)
|
||||
parser.feed(1) # type: ignore[arg-type]
|
||||
|
||||
def test_negative_stride(self) -> None:
|
||||
with open("Tests/images/raw_negative_stride.bin", "rb") as f:
|
||||
|
@ -303,7 +305,7 @@ class TestPyDecoder(CodecsTest):
|
|||
im.load()
|
||||
|
||||
def test_decode(self) -> None:
|
||||
decoder = ImageFile.PyDecoder(None)
|
||||
decoder = ImageFile.PyDecoder("")
|
||||
with pytest.raises(NotImplementedError):
|
||||
decoder.decode(b"")
|
||||
|
||||
|
@ -381,7 +383,7 @@ class TestPyEncoder(CodecsTest):
|
|||
)
|
||||
|
||||
def test_encode(self) -> None:
|
||||
encoder = ImageFile.PyEncoder(None)
|
||||
encoder = ImageFile.PyEncoder("")
|
||||
with pytest.raises(NotImplementedError):
|
||||
encoder.encode(0)
|
||||
|
||||
|
@ -393,8 +395,9 @@ class TestPyEncoder(CodecsTest):
|
|||
with pytest.raises(NotImplementedError):
|
||||
encoder.encode_to_pyfd()
|
||||
|
||||
fh = BytesIO()
|
||||
with pytest.raises(NotImplementedError):
|
||||
encoder.encode_to_file(None, None)
|
||||
encoder.encode_to_file(fh, 0)
|
||||
|
||||
def test_zero_height(self) -> None:
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
|
|
|
@ -717,14 +717,14 @@ def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:
|
|||
|
||||
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
|
||||
_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)
|
||||
assert font.getname()[1] == "Bold"
|
||||
_check_text(font, "Tests/images/variation_adobe_name.png", 16)
|
||||
|
||||
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
|
||||
_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)
|
||||
assert font.getname()[1] == "200"
|
||||
_check_text(font, "Tests/images/variation_tiny_name.png", 40)
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageMath
|
||||
|
||||
|
||||
|
@ -19,7 +23,7 @@ I = Image.new("I", (1, 1), 4) # noqa: E741
|
|||
A2 = A.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:
|
||||
|
@ -30,13 +34,13 @@ def test_sanity() -> None:
|
|||
== "I 3"
|
||||
)
|
||||
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"
|
||||
)
|
||||
assert (
|
||||
pixel(
|
||||
ImageMath.lambda_eval(
|
||||
lambda args: args["float"](args["A"]) + args["B"], images
|
||||
lambda args: args["float"](args["A"]) + args["B"], **images
|
||||
)
|
||||
)
|
||||
== "F 3.0"
|
||||
|
@ -44,42 +48,47 @@ def test_sanity() -> None:
|
|||
assert (
|
||||
pixel(
|
||||
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"
|
||||
)
|
||||
|
||||
|
||||
def test_options_deprecated() -> None:
|
||||
with pytest.warns(DeprecationWarning):
|
||||
assert ImageMath.lambda_eval(lambda args: 1, images) == 1
|
||||
|
||||
|
||||
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 (
|
||||
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images))
|
||||
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], **images))
|
||||
== "I 3"
|
||||
)
|
||||
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"
|
||||
)
|
||||
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"
|
||||
)
|
||||
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"
|
||||
)
|
||||
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 (
|
||||
pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, images))
|
||||
pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, **images))
|
||||
== "I 2147483647"
|
||||
)
|
||||
|
||||
assert (
|
||||
pixel(
|
||||
ImageMath.lambda_eval(
|
||||
lambda args: args["float"](args["A"]) + args["B"], images
|
||||
lambda args: args["float"](args["A"]) + args["B"], **images
|
||||
)
|
||||
)
|
||||
== "F 3.0"
|
||||
|
@ -87,7 +96,7 @@ def test_ops() -> None:
|
|||
assert (
|
||||
pixel(
|
||||
ImageMath.lambda_eval(
|
||||
lambda args: args["float"](args["A"]) - args["B"], images
|
||||
lambda args: args["float"](args["A"]) - args["B"], **images
|
||||
)
|
||||
)
|
||||
== "F -1.0"
|
||||
|
@ -95,7 +104,7 @@ def test_ops() -> None:
|
|||
assert (
|
||||
pixel(
|
||||
ImageMath.lambda_eval(
|
||||
lambda args: args["float"](args["A"]) * args["B"], images
|
||||
lambda args: args["float"](args["A"]) * args["B"], **images
|
||||
)
|
||||
)
|
||||
== "F 2.0"
|
||||
|
@ -103,31 +112,33 @@ def test_ops() -> None:
|
|||
assert (
|
||||
pixel(
|
||||
ImageMath.lambda_eval(
|
||||
lambda args: args["float"](args["A"]) / args["B"], images
|
||||
lambda args: args["float"](args["A"]) / args["B"], **images
|
||||
)
|
||||
)
|
||||
== "F 0.5"
|
||||
)
|
||||
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"
|
||||
)
|
||||
assert (
|
||||
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"
|
||||
)
|
||||
|
||||
|
||||
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 (
|
||||
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"
|
||||
)
|
||||
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"
|
||||
)
|
||||
|
||||
|
@ -136,7 +147,7 @@ def test_convert() -> None:
|
|||
assert (
|
||||
pixel(
|
||||
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"
|
||||
|
@ -144,7 +155,7 @@ def test_convert() -> None:
|
|||
assert (
|
||||
pixel(
|
||||
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"
|
||||
|
@ -152,7 +163,7 @@ def test_convert() -> None:
|
|||
assert (
|
||||
pixel(
|
||||
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)"
|
||||
|
@ -163,7 +174,7 @@ def test_compare() -> None:
|
|||
assert (
|
||||
pixel(
|
||||
ImageMath.lambda_eval(
|
||||
lambda args: args["min"](args["A"], args["B"]), images
|
||||
lambda args: args["min"](args["A"], args["B"]), **images
|
||||
)
|
||||
)
|
||||
== "I 1"
|
||||
|
@ -171,13 +182,13 @@ def test_compare() -> None:
|
|||
assert (
|
||||
pixel(
|
||||
ImageMath.lambda_eval(
|
||||
lambda args: args["max"](args["A"], args["B"]), images
|
||||
lambda args: args["max"](args["A"], args["B"]), **images
|
||||
)
|
||||
)
|
||||
== "I 2"
|
||||
)
|
||||
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"] == 1, **images)) == "I 1"
|
||||
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, **images)) == "I 0"
|
||||
|
||||
|
||||
def test_one_image_larger() -> None:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageMath
|
||||
|
@ -21,16 +23,16 @@ I = Image.new("I", (1, 1), 4) # noqa: E741
|
|||
A2 = A.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:
|
||||
assert ImageMath.unsafe_eval("1") == 1
|
||||
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", images)) == "I 3"
|
||||
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("A+B", **images)) == "I 3"
|
||||
assert pixel(ImageMath.unsafe_eval("float(A)+B", **images)) == "F 3.0"
|
||||
assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", **images)) == "I 3"
|
||||
|
||||
|
||||
def test_eval_deprecated() -> None:
|
||||
|
@ -38,23 +40,28 @@ def test_eval_deprecated() -> None:
|
|||
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:
|
||||
assert pixel(ImageMath.unsafe_eval("-A", images)) == "I -1"
|
||||
assert pixel(ImageMath.unsafe_eval("+B", images)) == "L 2"
|
||||
assert pixel(ImageMath.unsafe_eval("-A", **images)) == "I -1"
|
||||
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 -1"
|
||||
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("B**2", images)) == "I 4"
|
||||
assert pixel(ImageMath.unsafe_eval("B**33", images)) == "I 2147483647"
|
||||
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 2"
|
||||
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**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 -1.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(B)**2", images)) == "F 4.0"
|
||||
assert pixel(ImageMath.unsafe_eval("float(B)**33", images)) == "F 8589934592.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 2.0"
|
||||
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)**33", **images)) == "F 8589934592.0"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -72,33 +79,33 @@ def test_prevent_exec(expression: str) -> None:
|
|||
|
||||
def test_prevent_double_underscores() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
ImageMath.unsafe_eval("1", {"__": None})
|
||||
ImageMath.unsafe_eval("1", __=None)
|
||||
|
||||
|
||||
def test_prevent_builtins() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
ImageMath.unsafe_eval("(lambda: exec('exit()'))()", {"exec": None})
|
||||
ImageMath.unsafe_eval("(lambda: exec('exit()'))()", exec=None)
|
||||
|
||||
|
||||
def test_logical() -> None:
|
||||
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 or B", images)) == "L 1"
|
||||
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 or B", **images)) == "L 1"
|
||||
|
||||
|
||||
def test_convert() -> None:
|
||||
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, 'L')", **images)) == "L 3"
|
||||
assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", **images)) == "1 0"
|
||||
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:
|
||||
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("A == 1", images)) == "I 1"
|
||||
assert pixel(ImageMath.unsafe_eval("A == 2", images)) == "I 0"
|
||||
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("A == 1", **images)) == "I 1"
|
||||
assert pixel(ImageMath.unsafe_eval("A == 2", **images)) == "I 0"
|
||||
|
||||
|
||||
def test_one_image_larger() -> None:
|
||||
|
|
|
@ -109,3 +109,6 @@ def test_bitmapimage() -> None:
|
|||
|
||||
# reloaded = ImageTk.getimage(im_tk)
|
||||
# assert_image_equal(reloaded, im)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ImageTk.BitmapImage()
|
||||
|
|
|
@ -57,6 +57,9 @@ class TestImageWinDib:
|
|||
# Assert
|
||||
assert dib.size == (128, 128)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ImageWin.Dib(mode)
|
||||
|
||||
def test_dib_paste(self) -> None:
|
||||
# Arrange
|
||||
im = hopper()
|
||||
|
|
|
@ -59,7 +59,7 @@ def test_stdout(buffer: bool) -> None:
|
|||
|
||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||
|
||||
sys.stdout = mystdout # type: ignore[assignment]
|
||||
sys.stdout = mystdout
|
||||
|
||||
ps = PSDraw.PSDraw()
|
||||
_create_document(ps)
|
||||
|
|
|
@ -30,28 +30,6 @@ def test_is_not_path(tmp_path: Path) -> None:
|
|||
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:
|
||||
# Arrange
|
||||
|
||||
|
|
|
@ -109,6 +109,15 @@ ImageDraw.getdraw hints parameter
|
|||
|
||||
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
|
||||
----------------
|
||||
|
||||
|
|
BIN
docs/handbook/animated_hopper.gif
Normal file
After Width: | Height: | Size: 57 KiB |
BIN
docs/handbook/contrasted_hopper.jpg
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
docs/handbook/cropped_hopper.webp
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
docs/handbook/enhanced_hopper.webp
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
docs/handbook/flip_left_right_hopper.webp
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/handbook/flip_top_bottom_hopper.webp
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/handbook/hopper_ps.webp
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
docs/handbook/masked_hopper.webp
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/handbook/merged_hopper.webp
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
docs/handbook/pasted_hopper.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/handbook/rebanded_hopper.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/handbook/rolled_hopper.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/handbook/rotated_hopper_180.webp
Normal file
After Width: | Height: | Size: 3.9 KiB |
BIN
docs/handbook/rotated_hopper_270.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/handbook/rotated_hopper_90.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
docs/handbook/show_hopper.webp
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
docs/handbook/thumbnail_hopper.jpg
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
docs/handbook/transformed_hopper.webp
Normal file
After Width: | Height: | Size: 3.0 KiB |
|
@ -37,6 +37,9 @@ example, let’s display the image we just loaded::
|
|||
|
||||
>>> im.show()
|
||||
|
||||
.. image:: show_hopper.webp
|
||||
:align: center
|
||||
|
||||
.. note::
|
||||
|
||||
The standard version of :py:meth:`~PIL.Image.Image.show` is not very
|
||||
|
@ -79,6 +82,9 @@ Convert files to JPEG
|
|||
except OSError:
|
||||
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`
|
||||
method which explicitly specifies a file format. If you use a non-standard
|
||||
extension, you must always specify the format this way:
|
||||
|
@ -103,6 +109,9 @@ Create JPEG thumbnails
|
|||
except OSError:
|
||||
print("cannot create thumbnail for", infile)
|
||||
|
||||
.. image:: thumbnail_hopper.jpg
|
||||
:align: center
|
||||
|
||||
It is important to note that the library doesn’t decode or load the raster data
|
||||
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
|
||||
|
@ -140,16 +149,19 @@ Copying a subrectangle from an image
|
|||
|
||||
::
|
||||
|
||||
box = (100, 100, 400, 400)
|
||||
box = (0, 0, 64, 64)
|
||||
region = im.crop(box)
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
.. image:: cropped_hopper.webp
|
||||
:align: center
|
||||
|
||||
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 don’t
|
|||
the region is automatically converted before being pasted (see the section on
|
||||
:ref:`color-transforms` below for details).
|
||||
|
||||
.. image:: pasted_hopper.webp
|
||||
:align: center
|
||||
|
||||
Here’s an additional example:
|
||||
|
||||
Rolling an image
|
||||
|
@ -186,6 +201,9 @@ Rolling an image
|
|||
|
||||
return im
|
||||
|
||||
.. image:: rolled_hopper.webp
|
||||
:align: center
|
||||
|
||||
Or if you would like to merge two images into a wider image:
|
||||
|
||||
Merging images
|
||||
|
@ -203,6 +221,9 @@ Merging images
|
|||
|
||||
return im
|
||||
|
||||
.. image:: merged_hopper.webp
|
||||
:align: center
|
||||
|
||||
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
|
||||
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 to “RGB” first.
|
||||
|
||||
.. image:: rebanded_hopper.webp
|
||||
:align: center
|
||||
|
||||
Geometrical transforms
|
||||
----------------------
|
||||
|
||||
|
@ -245,6 +269,9 @@ Simple geometry transforms
|
|||
out = im.resize((128, 128))
|
||||
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
|
||||
:py:meth:`~PIL.Image.Image.rotate` method or the
|
||||
: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)
|
||||
|
||||
.. image:: flip_left_right_hopper.webp
|
||||
:align: center
|
||||
|
||||
::
|
||||
|
||||
out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
|
||||
|
||||
.. image:: flip_top_bottom_hopper.webp
|
||||
:align: center
|
||||
|
||||
::
|
||||
|
||||
out = im.transpose(Image.Transpose.ROTATE_90)
|
||||
|
||||
.. image:: rotated_hopper_90.webp
|
||||
:align: center
|
||||
|
||||
::
|
||||
|
||||
out = im.transpose(Image.Transpose.ROTATE_180)
|
||||
|
||||
.. image:: rotated_hopper_180.webp
|
||||
:align: center
|
||||
|
||||
::
|
||||
|
||||
out = im.transpose(Image.Transpose.ROTATE_270)
|
||||
|
||||
.. image:: rotated_hopper_270.webp
|
||||
:align: center
|
||||
|
||||
``transpose(ROTATE)`` operations can also be performed identically with
|
||||
:py:meth:`~PIL.Image.Image.rotate` operations, provided the ``expand`` flag is
|
||||
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
|
||||
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.cover(im, size).save("imageops_cover.webp")
|
||||
ImageOps.fit(im, size).save("imageops_fit.webp")
|
||||
|
@ -342,6 +396,9 @@ Applying filters
|
|||
from PIL import ImageFilter
|
||||
out = im.filter(ImageFilter.DETAIL)
|
||||
|
||||
.. image:: enhanced_hopper.webp
|
||||
:align: center
|
||||
|
||||
Point Operations
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -355,8 +412,11 @@ Applying point transforms
|
|||
|
||||
::
|
||||
|
||||
# multiply each pixel by 1.2
|
||||
out = im.point(lambda i: i * 1.2)
|
||||
# multiply each pixel by 20
|
||||
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
|
||||
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)
|
||||
|
||||
.. image:: masked_hopper.webp
|
||||
:align: center
|
||||
|
||||
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
|
||||
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.enhance(1.3).show("30% more contrast")
|
||||
|
||||
|
||||
.. image:: contrasted_hopper.jpg
|
||||
:align: center
|
||||
|
||||
Image sequences
|
||||
---------------
|
||||
|
||||
|
@ -444,10 +511,43 @@ Reading sequences
|
|||
As seen in this example, you’ll get an :py:exc:`EOFError` exception when the
|
||||
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:
|
||||
|
||||
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 PSDraw
|
||||
from PIL import Image, PSDraw
|
||||
import os
|
||||
|
||||
with Image.open("hopper.ppm") as im:
|
||||
title = "hopper"
|
||||
box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points
|
||||
# Define the PostScript file
|
||||
ps_file = open("hopper.ps", "wb")
|
||||
|
||||
ps = PSDraw.PSDraw() # default is sys.stdout or sys.stdout.buffer
|
||||
ps.begin_document(title)
|
||||
# Create a PSDraw object
|
||||
ps = PSDraw.PSDraw(ps_file)
|
||||
|
||||
# draw the image (75 dpi)
|
||||
ps.image(box, im, 75)
|
||||
ps.rectangle(box)
|
||||
# Start the document
|
||||
ps.begin_document()
|
||||
|
||||
# draw title
|
||||
ps.setfont("HelveticaNarrow-Bold", 36)
|
||||
ps.text((3 * 72, 4 * 72), title)
|
||||
# Set the text to be drawn
|
||||
text = "Hopper"
|
||||
|
||||
ps.end_document()
|
||||
# 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_file.close()
|
||||
|
||||
.. image:: hopper_ps.webp
|
||||
|
||||
.. note::
|
||||
|
||||
PostScript converted to PDF for display purposes
|
||||
|
||||
More on reading images
|
||||
----------------------
|
||||
|
@ -553,7 +689,7 @@ Reading from a tar archive
|
|||
|
||||
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)
|
||||
|
||||
|
||||
|
@ -568,7 +704,6 @@ in the current directory can be saved as JPEGs at reduced quality.
|
|||
import glob
|
||||
from PIL import Image
|
||||
|
||||
|
||||
def compress_image(source_path, dest_path):
|
||||
with Image.open(source_path) as img:
|
||||
if img.mode != "RGB":
|
||||
|
|
|
@ -37,6 +37,11 @@ Example: Parse an image
|
|||
Classes
|
||||
-------
|
||||
|
||||
.. autoclass:: PIL.ImageFile._Tile()
|
||||
:member-order: bysource
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: PIL.ImageFile.Parser()
|
||||
:members:
|
||||
|
||||
|
|
|
@ -91,3 +91,11 @@ Constants
|
|||
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
|
||||
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
||||
|
||||
Dictionaries
|
||||
------------
|
||||
|
||||
.. autoclass:: Axis
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
|
|
@ -31,20 +31,21 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
|
|||
b=im2
|
||||
)
|
||||
|
||||
.. py:function:: lambda_eval(expression, options)
|
||||
.. py:function:: lambda_eval(expression, options, **kw)
|
||||
|
||||
Returns the result of an image function.
|
||||
|
||||
:param expression: A function that receives a dictionary.
|
||||
:param options: Values to add to the function's dictionary, mapping image
|
||||
names to Image instances. You can use one or more keyword
|
||||
arguments instead of a dictionary, as shown in the above
|
||||
example. Note that the names must be valid Python
|
||||
identifiers.
|
||||
:param options: Values to add to the function's dictionary. Note that the names
|
||||
must be valid Python identifiers. Deprecated.
|
||||
You can instead use one or more keyword arguments, as
|
||||
shown in the above example.
|
||||
: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,
|
||||
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.
|
||||
|
||||
|
@ -61,11 +62,12 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
|
|||
:param expression: A string which uses the standard Python expression
|
||||
syntax. In addition to the standard operators, you can
|
||||
also use the functions described below.
|
||||
:param options: Values to add to the function's dictionary, mapping image
|
||||
names to Image instances. You can use one or more keyword
|
||||
arguments instead of a dictionary, as shown in the above
|
||||
example. Note that the names must be valid Python
|
||||
identifiers.
|
||||
:param options: Values to add to the evaluation context. Note that the names must
|
||||
be valid Python identifiers. Deprecated.
|
||||
You can instead use one or more keyword arguments, as
|
||||
shown in the above example.
|
||||
: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,
|
||||
or a pixel tuple, depending on the expression.
|
||||
|
||||
|
|
|
@ -78,3 +78,7 @@ on some Python versions.
|
|||
|
||||
An internal interface module previously known as :mod:`~PIL._imaging`,
|
||||
implemented in :file:`_imaging.c`.
|
||||
|
||||
.. py:class:: ImagingCore
|
||||
|
||||
A representation of the image data.
|
||||
|
|
|
@ -43,10 +43,12 @@ similarly removed.
|
|||
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
|
||||
===========
|
||||
|
|
|
@ -159,7 +159,4 @@ exclude = [
|
|||
'^Tests/oss-fuzz/fuzz_font.py$',
|
||||
'^Tests/oss-fuzz/fuzz_pillow.py$',
|
||||
'^Tests/test_qt_image_qapplication.py$',
|
||||
'^Tests/test_font_pcf_charsets.py$',
|
||||
'^Tests/test_font_pcf.py$',
|
||||
'^Tests/test_file_tar.py$',
|
||||
]
|
||||
|
|
|
@ -313,6 +313,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
|||
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||
|
||||
def _safe_read(self, length: int) -> bytes:
|
||||
assert self.fd is not None
|
||||
return ImageFile._safe_read(self.fd, length)
|
||||
|
||||
def _read_palette(self) -> list[tuple[int, int, int, int]]:
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import IO
|
||||
from typing import IO, Any
|
||||
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i16le as i16
|
||||
|
@ -72,16 +72,20 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
for k, v in COMPRESSIONS.items():
|
||||
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, seek = self.fp.read, self.fp.seek
|
||||
if header:
|
||||
seek(header)
|
||||
# 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
|
||||
# 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)
|
||||
|
||||
# ------------------------------- 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["planes"] = i16(header_data, 4)
|
||||
file_info["bits"] = i16(header_data, 6)
|
||||
file_info["compression"] = self.RAW
|
||||
file_info["compression"] = self.COMPRESSIONS["RAW"]
|
||||
file_info["palette_padding"] = 3
|
||||
|
||||
# --------------------------------------------- Windows Bitmap v3 to v5
|
||||
|
@ -122,8 +126,9 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
)
|
||||
file_info["colors"] = i32(header_data, 28)
|
||||
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"])
|
||||
if file_info["compression"] == self.BITFIELDS:
|
||||
if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]:
|
||||
masks = ["r_mask", "g_mask", "b_mask"]
|
||||
if len(header_data) >= 48:
|
||||
if len(header_data) >= 52:
|
||||
|
@ -144,6 +149,10 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
file_info["a_mask"] = 0x0
|
||||
for mask in masks:
|
||||
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["r_mask"],
|
||||
file_info["g_mask"],
|
||||
|
@ -164,24 +173,26 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
self._size = file_info["width"], file_info["height"]
|
||||
|
||||
# ------- If color count was not found in the header, compute from bits
|
||||
assert isinstance(file_info["bits"], int)
|
||||
file_info["colors"] = (
|
||||
file_info["colors"]
|
||||
if file_info.get("colors", 0)
|
||||
else (1 << file_info["bits"])
|
||||
)
|
||||
assert isinstance(file_info["colors"], int)
|
||||
if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8:
|
||||
offset += 4 * file_info["colors"]
|
||||
|
||||
# ---------------------- Check bit depth for unusual unsupported values
|
||||
self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
|
||||
if self.mode is None:
|
||||
self._mode, raw_mode = BIT2MODE.get(file_info["bits"], ("", ""))
|
||||
if not self.mode:
|
||||
msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
|
||||
raise OSError(msg)
|
||||
|
||||
# ---------------- Process BMP with Bitfields compression (not palette)
|
||||
decoder_name = "raw"
|
||||
if file_info["compression"] == self.BITFIELDS:
|
||||
SUPPORTED = {
|
||||
if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]:
|
||||
SUPPORTED: dict[int, list[tuple[int, ...]]] = {
|
||||
32: [
|
||||
(0xFF0000, 0xFF00, 0xFF, 0x0),
|
||||
(0xFF000000, 0xFF0000, 0xFF00, 0x0),
|
||||
|
@ -213,12 +224,14 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
file_info["bits"] == 32
|
||||
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"])]
|
||||
self._mode = "RGBA" if "A" in raw_mode else self.mode
|
||||
elif (
|
||||
file_info["bits"] in (24, 16)
|
||||
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"])]
|
||||
else:
|
||||
msg = "Unsupported BMP bitfields layout"
|
||||
|
@ -226,10 +239,13 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
else:
|
||||
msg = "Unsupported BMP bitfields layout"
|
||||
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
|
||||
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"
|
||||
else:
|
||||
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']})"
|
||||
raise OSError(msg)
|
||||
else:
|
||||
assert isinstance(file_info["palette_padding"], int)
|
||||
padding = file_info["palette_padding"]
|
||||
palette = read(padding * file_info["colors"])
|
||||
grayscale = True
|
||||
|
@ -269,10 +286,11 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
|
||||
# ---------------------------- Finally set the tile data for the plugin
|
||||
self.info["compression"] = file_info["compression"]
|
||||
args = [raw_mode]
|
||||
args: list[Any] = [raw_mode]
|
||||
if decoder_name == "bmp_rle":
|
||||
args.append(file_info["compression"] == self.RLE4)
|
||||
args.append(file_info["compression"] == self.COMPRESSIONS["RLE4"])
|
||||
else:
|
||||
assert isinstance(file_info["width"], int)
|
||||
args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
|
||||
args.append(file_info["direction"])
|
||||
self.tile = [
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
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
|
||||
file (for example a TAR file).
|
||||
|
@ -45,7 +46,10 @@ class ContainerIO(Generic[AnyStr]):
|
|||
def isatty(self) -> bool:
|
||||
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.
|
||||
|
||||
|
@ -53,6 +57,7 @@ class ContainerIO(Generic[AnyStr]):
|
|||
:param mode: Starting position. Use 0 for beginning of region, 1
|
||||
for current offset, and 2 for end of region. You cannot move
|
||||
the pointer outside the defined region.
|
||||
:returns: Offset from start of region, in bytes.
|
||||
"""
|
||||
if mode == 1:
|
||||
self.pos = self.pos + offset
|
||||
|
@ -63,6 +68,7 @@ class ContainerIO(Generic[AnyStr]):
|
|||
# clamp
|
||||
self.pos = max(0, min(self.pos, self.length))
|
||||
self.fh.seek(self.offset + self.pos)
|
||||
return self.pos
|
||||
|
||||
def tell(self) -> int:
|
||||
"""
|
||||
|
@ -72,27 +78,32 @@ class ContainerIO(Generic[AnyStr]):
|
|||
"""
|
||||
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.
|
||||
|
||||
: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.
|
||||
:returns: An 8-bit string.
|
||||
"""
|
||||
if n:
|
||||
if n > 0:
|
||||
n = min(n, self.length - self.pos)
|
||||
else:
|
||||
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]
|
||||
self.pos = self.pos + n
|
||||
return self.fh.read(n)
|
||||
|
||||
def readline(self) -> AnyStr:
|
||||
def readline(self, n: int = -1) -> AnyStr:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
|
||||
|
@ -102,14 +113,16 @@ class ContainerIO(Generic[AnyStr]):
|
|||
if not c:
|
||||
break
|
||||
s = s + c
|
||||
if c == newline_character:
|
||||
if c == newline_character or len(s) == n:
|
||||
break
|
||||
return s
|
||||
|
||||
def readlines(self) -> list[AnyStr]:
|
||||
def readlines(self, n: int | None = -1) -> list[AnyStr]:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
lines = []
|
||||
|
@ -118,4 +131,43 @@ class ContainerIO(Generic[AnyStr]):
|
|||
if not s:
|
||||
break
|
||||
lines.append(s)
|
||||
if len(lines) == n:
|
||||
break
|
||||
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()
|
||||
|
|
|
@ -65,7 +65,7 @@ def has_ghostscript() -> bool:
|
|||
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"""
|
||||
global gs_binary
|
||||
if not has_ghostscript():
|
||||
|
|
|
@ -25,7 +25,7 @@ from __future__ import annotations
|
|||
import warnings
|
||||
from io import BytesIO
|
||||
from math import ceil, log
|
||||
from typing import IO
|
||||
from typing import IO, NamedTuple
|
||||
|
||||
from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
|
||||
from ._binary import i16le as i16
|
||||
|
@ -119,8 +119,22 @@ def _accept(prefix: bytes) -> bool:
|
|||
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:
|
||||
def __init__(self, buf) -> None:
|
||||
def __init__(self, buf: IO[bytes]) -> None:
|
||||
"""
|
||||
Parse image from file-like object containing ico file data
|
||||
"""
|
||||
|
@ -141,51 +155,44 @@ class IcoFile:
|
|||
for i in range(self.nb_items):
|
||||
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
|
||||
for j in ("width", "height"):
|
||||
if not icon_header[j]:
|
||||
icon_header[j] = 256
|
||||
width = s[0] or 256
|
||||
height = s[1] or 256
|
||||
|
||||
# See Wikipedia notes about color depth.
|
||||
# We need this just to differ images with equal sizes
|
||||
icon_header["color_depth"] = (
|
||||
icon_header["bpp"]
|
||||
or (
|
||||
icon_header["nb_color"] != 0
|
||||
and ceil(log(icon_header["nb_color"], 2))
|
||||
)
|
||||
or 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.
|
||||
# We need this just to differ images with equal sizes
|
||||
color_depth=bpp or (nb_color != 0 and ceil(log(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 = 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
|
||||
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]]:
|
||||
"""
|
||||
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:
|
||||
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 0
|
||||
|
||||
|
@ -202,9 +209,9 @@ class IcoFile:
|
|||
|
||||
header = self.entry[idx]
|
||||
|
||||
self.buf.seek(header["offset"])
|
||||
self.buf.seek(header.offset)
|
||||
data = self.buf.read(8)
|
||||
self.buf.seek(header["offset"])
|
||||
self.buf.seek(header.offset)
|
||||
|
||||
im: Image.Image
|
||||
if data[:8] == PngImagePlugin._MAGIC:
|
||||
|
@ -222,8 +229,7 @@ class IcoFile:
|
|||
im.tile[0] = d, (0, 0) + im.size, o, a
|
||||
|
||||
# figure out where AND mask image starts
|
||||
bpp = header["bpp"]
|
||||
if 32 == bpp:
|
||||
if header.bpp == 32:
|
||||
# 32-bit color depth icon image allows semitransparent areas
|
||||
# PIL's DIB format ignores transparency bits, recover them.
|
||||
# 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
|
||||
|
||||
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)
|
||||
mask_data = self.buf.read(total_bytes)
|
||||
|
@ -307,7 +313,7 @@ class IcoImageFile(ImageFile.ImageFile):
|
|||
def _open(self) -> None:
|
||||
self.ico = IcoFile(self.fp)
|
||||
self.info["sizes"] = self.ico.sizes()
|
||||
self.size = self.ico.entry[0]["dim"]
|
||||
self.size = self.ico.entry[0].dim
|
||||
self.load()
|
||||
|
||||
@property
|
||||
|
|
116
src/PIL/Image.py
|
@ -38,7 +38,7 @@ import struct
|
|||
import sys
|
||||
import tempfile
|
||||
import warnings
|
||||
from collections.abc import Callable, MutableMapping, Sequence
|
||||
from collections.abc import Callable, Iterator, MutableMapping, Sequence
|
||||
from enum import IntEnum
|
||||
from types import ModuleType
|
||||
from typing import (
|
||||
|
@ -218,6 +218,8 @@ if hasattr(core, "DEFAULT_STRATEGY"):
|
|||
# Registries
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
from . import ImageFile, ImagePalette
|
||||
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
|
||||
ID: list[str] = []
|
||||
|
@ -241,9 +243,9 @@ ENCODERS: dict[str, type[ImageFile.PyEncoder]] = {}
|
|||
_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)
|
||||
shape = (im.height, im.width)
|
||||
shape: tuple[int, ...] = (im.height, im.width)
|
||||
extra = len(m.bands)
|
||||
if extra != 1:
|
||||
shape += (extra,)
|
||||
|
@ -466,40 +468,40 @@ def _getencoder(
|
|||
|
||||
|
||||
class _E:
|
||||
def __init__(self, scale, offset) -> None:
|
||||
def __init__(self, scale: float, offset: float) -> None:
|
||||
self.scale = scale
|
||||
self.offset = offset
|
||||
|
||||
def __neg__(self):
|
||||
def __neg__(self) -> _E:
|
||||
return _E(-self.scale, -self.offset)
|
||||
|
||||
def __add__(self, other):
|
||||
def __add__(self, other: _E | float) -> _E:
|
||||
if isinstance(other, _E):
|
||||
return _E(self.scale + other.scale, self.offset + other.offset)
|
||||
return _E(self.scale, self.offset + other)
|
||||
|
||||
__radd__ = __add__
|
||||
|
||||
def __sub__(self, other):
|
||||
def __sub__(self, other: _E | float) -> _E:
|
||||
return self + -other
|
||||
|
||||
def __rsub__(self, other):
|
||||
def __rsub__(self, other: _E | float) -> _E:
|
||||
return other + -self
|
||||
|
||||
def __mul__(self, other):
|
||||
def __mul__(self, other: _E | float) -> _E:
|
||||
if isinstance(other, _E):
|
||||
return NotImplemented
|
||||
return _E(self.scale * other, self.offset * other)
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __truediv__(self, other):
|
||||
def __truediv__(self, other: _E | float) -> _E:
|
||||
if isinstance(other, _E):
|
||||
return NotImplemented
|
||||
return _E(self.scale / other, self.offset / other)
|
||||
|
||||
|
||||
def _getscaleoffset(expr):
|
||||
def _getscaleoffset(expr) -> tuple[float, float]:
|
||||
a = expr(_E(1, 0))
|
||||
return (a.scale, a.offset) if isinstance(a, _E) else (0, a)
|
||||
|
||||
|
@ -728,9 +730,9 @@ class Image:
|
|||
return self._repr_image("JPEG")
|
||||
|
||||
@property
|
||||
def __array_interface__(self):
|
||||
def __array_interface__(self) -> dict[str, str | bytes | int | tuple[int, ...]]:
|
||||
# numpy array interface support
|
||||
new = {"version": 3}
|
||||
new: dict[str, str | bytes | int | tuple[int, ...]] = {"version": 3}
|
||||
try:
|
||||
if self.mode == "1":
|
||||
# Binary images need to be extended from bits to bytes
|
||||
|
@ -752,11 +754,11 @@ class Image:
|
|||
new["shape"], new["typestr"] = _conv_type_shape(self)
|
||||
return new
|
||||
|
||||
def __getstate__(self):
|
||||
def __getstate__(self) -> list[Any]:
|
||||
im_data = self.tobytes() # load image first
|
||||
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()
|
||||
info, mode, size, palette, data = state
|
||||
self.info = info
|
||||
|
@ -1428,7 +1430,7 @@ class Image:
|
|||
return out
|
||||
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
|
||||
containing pixel values. The sequence object is flattened, so
|
||||
|
@ -1477,8 +1479,8 @@ class Image:
|
|||
def get_name(tag: str) -> str:
|
||||
return re.sub("^{[^}]+}", "", tag)
|
||||
|
||||
def get_value(element):
|
||||
value = {get_name(k): v for k, v in element.attrib.items()}
|
||||
def get_value(element: Element) -> str | dict[str, Any] | None:
|
||||
value: dict[str, Any] = {get_name(k): v for k, v in element.attrib.items()}
|
||||
children = list(element)
|
||||
if children:
|
||||
for child in children:
|
||||
|
@ -1691,7 +1693,9 @@ class Image:
|
|||
x, y = self.im.getprojection()
|
||||
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
|
||||
list of pixel counts, one for each pixel value in the source
|
||||
|
@ -1717,12 +1721,14 @@ class Image:
|
|||
mask.load()
|
||||
return self.im.histogram((0, 0), mask.im)
|
||||
if self.mode in ("I", "F"):
|
||||
if extrema is None:
|
||||
extrema = self.getextrema()
|
||||
return self.im.histogram(extrema)
|
||||
return self.im.histogram(
|
||||
extrema if extrema is not None else self.getextrema()
|
||||
)
|
||||
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.
|
||||
|
||||
|
@ -1743,9 +1749,9 @@ class Image:
|
|||
mask.load()
|
||||
return self.im.entropy((0, 0), mask.im)
|
||||
if self.mode in ("I", "F"):
|
||||
if extrema is None:
|
||||
extrema = self.getextrema()
|
||||
return self.im.entropy(extrema)
|
||||
return self.im.entropy(
|
||||
extrema if extrema is not None else self.getextrema()
|
||||
)
|
||||
return self.im.entropy()
|
||||
|
||||
def paste(
|
||||
|
@ -2006,7 +2012,7 @@ class Image:
|
|||
|
||||
def putdata(
|
||||
self,
|
||||
data: Sequence[float] | Sequence[Sequence[int]] | NumpyArray,
|
||||
data: Sequence[float] | Sequence[Sequence[int]] | core.ImagingCore | NumpyArray,
|
||||
scale: float = 1.0,
|
||||
offset: float = 0.0,
|
||||
) -> None:
|
||||
|
@ -2194,7 +2200,12 @@ class Image:
|
|||
|
||||
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
|
||||
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_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 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)
|
||||
self = (
|
||||
self.reduce(factor, box=reduce_box)
|
||||
|
@ -2440,7 +2451,7 @@ class Image:
|
|||
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
|
||||
return a * x + b * y + c, d * x + e * y + f
|
||||
|
||||
|
@ -2455,9 +2466,9 @@ class Image:
|
|||
xx = []
|
||||
yy = []
|
||||
for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
|
||||
x, y = transform(x, y, matrix)
|
||||
xx.append(x)
|
||||
yy.append(y)
|
||||
transformed_x, transformed_y = transform(x, y, matrix)
|
||||
xx.append(transformed_x)
|
||||
yy.append(transformed_y)
|
||||
nw = math.ceil(max(xx)) - math.floor(min(xx))
|
||||
nh = math.ceil(max(yy)) - math.floor(min(yy))
|
||||
|
||||
|
@ -2715,7 +2726,7 @@ class Image:
|
|||
provided_size = tuple(map(math.floor, size))
|
||||
|
||||
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)
|
||||
|
||||
x, y = provided_size
|
||||
|
@ -2859,7 +2870,13 @@ class Image:
|
|||
return im
|
||||
|
||||
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]
|
||||
h = box[3] - box[1]
|
||||
|
@ -2909,11 +2926,12 @@ class Image:
|
|||
Resampling.BICUBIC,
|
||||
):
|
||||
if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS):
|
||||
msg = {
|
||||
unusable: dict[int, str] = {
|
||||
Resampling.BOX: "Image.Resampling.BOX",
|
||||
Resampling.HAMMING: "Image.Resampling.HAMMING",
|
||||
Resampling.LANCZOS: "Image.Resampling.LANCZOS",
|
||||
}[resample] + f" ({resample}) cannot be used."
|
||||
}
|
||||
msg = unusable[resample] + f" ({resample}) cannot be used."
|
||||
else:
|
||||
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)
|
||||
|
||||
|
||||
def fromqimage(im):
|
||||
def fromqimage(im) -> ImageFile.ImageFile:
|
||||
"""Creates an image instance from a QImage image"""
|
||||
from . import ImageQt
|
||||
|
||||
|
@ -3309,7 +3327,7 @@ def fromqimage(im):
|
|||
return ImageQt.fromqimage(im)
|
||||
|
||||
|
||||
def fromqpixmap(im):
|
||||
def fromqpixmap(im) -> ImageFile.ImageFile:
|
||||
"""Creates an image instance from a QPixmap image"""
|
||||
from . import ImageQt
|
||||
|
||||
|
@ -3856,7 +3874,7 @@ class Exif(_ExifBase):
|
|||
print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99
|
||||
"""
|
||||
|
||||
endian = None
|
||||
endian: str | None = None
|
||||
bigtiff = False
|
||||
_loaded = False
|
||||
|
||||
|
@ -3880,7 +3898,7 @@ class Exif(_ExifBase):
|
|||
# returns a dict with any single item tuples/lists as individual values
|
||||
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:
|
||||
# an offset pointer to the location of the nested embedded IFD.
|
||||
# It should be a long, but may be corrupted.
|
||||
|
@ -3894,7 +3912,7 @@ class Exif(_ExifBase):
|
|||
info.load(self.fp)
|
||||
return self._fixup_dict(info)
|
||||
|
||||
def _get_head(self):
|
||||
def _get_head(self) -> bytes:
|
||||
version = b"\x2B" if self.bigtiff else b"\x2A"
|
||||
if self.endian == "<":
|
||||
head = b"II" + version + b"\x00" + o32le(8)
|
||||
|
@ -3905,7 +3923,7 @@ class Exif(_ExifBase):
|
|||
head += b"\x00\x00\x00\x00"
|
||||
return head
|
||||
|
||||
def load(self, data):
|
||||
def load(self, data: bytes) -> None:
|
||||
# Extract EXIF information. This is highly experimental,
|
||||
# and is likely to be replaced with something better in a future
|
||||
# version.
|
||||
|
@ -3924,7 +3942,7 @@ class Exif(_ExifBase):
|
|||
self._info = None
|
||||
return
|
||||
|
||||
self.fp = io.BytesIO(data)
|
||||
self.fp: IO[bytes] = io.BytesIO(data)
|
||||
self.head = self.fp.read(8)
|
||||
# process dictionary
|
||||
from . import TiffImagePlugin
|
||||
|
@ -3934,7 +3952,7 @@ class Exif(_ExifBase):
|
|||
self.fp.seek(self._info.next)
|
||||
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._data.clear()
|
||||
self._hidden_data.clear()
|
||||
|
@ -4115,16 +4133,16 @@ class Exif(_ExifBase):
|
|||
keys.update(self._info)
|
||||
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:
|
||||
self._data[tag] = self._fixup(self._info[tag])
|
||||
del self._info[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)
|
||||
|
||||
def __setitem__(self, tag, value) -> None:
|
||||
def __setitem__(self, tag: int, value) -> None:
|
||||
if self._info is not None and tag in self._info:
|
||||
del self._info[tag]
|
||||
self._data[tag] = value
|
||||
|
@ -4135,7 +4153,7 @@ class Exif(_ExifBase):
|
|||
else:
|
||||
del self._data[tag]
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> Iterator[int]:
|
||||
keys = set(self._data)
|
||||
if self._info is not None:
|
||||
keys.update(self._info)
|
||||
|
|
|
@ -36,7 +36,7 @@ import numbers
|
|||
import struct
|
||||
from collections.abc import Sequence
|
||||
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 ._deprecate import deprecate
|
||||
|
@ -561,7 +561,12 @@ class ImageDraw:
|
|||
def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
|
||||
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 (
|
||||
self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
|
||||
+ stroke_width
|
||||
|
@ -571,25 +576,25 @@ class ImageDraw:
|
|||
def text(
|
||||
self,
|
||||
xy: tuple[float, float],
|
||||
text: str,
|
||||
fill=None,
|
||||
text: AnyStr,
|
||||
fill: _Ink | None = None,
|
||||
font: (
|
||||
ImageFont.ImageFont
|
||||
| ImageFont.FreeTypeFont
|
||||
| ImageFont.TransposedFont
|
||||
| None
|
||||
) = None,
|
||||
anchor=None,
|
||||
spacing=4,
|
||||
align="left",
|
||||
direction=None,
|
||||
features=None,
|
||||
language=None,
|
||||
stroke_width=0,
|
||||
stroke_fill=None,
|
||||
embedded_color=False,
|
||||
*args,
|
||||
**kwargs,
|
||||
anchor: str | None = None,
|
||||
spacing: float = 4,
|
||||
align: str = "left",
|
||||
direction: str | None = None,
|
||||
features: list[str] | None = None,
|
||||
language: str | None = None,
|
||||
stroke_width: float = 0,
|
||||
stroke_fill: _Ink | None = None,
|
||||
embedded_color: bool = False,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
"""Draw text."""
|
||||
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
||||
|
@ -623,15 +628,14 @@ class ImageDraw:
|
|||
return fill_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
|
||||
if stroke_width == 0 and embedded_color:
|
||||
mode = "RGBA"
|
||||
coord = []
|
||||
start = []
|
||||
for i in range(2):
|
||||
coord.append(int(xy[i]))
|
||||
start.append(math.modf(xy[i])[0])
|
||||
start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
|
||||
try:
|
||||
mask, offset = font.getmask2( # type: ignore[union-attr,misc]
|
||||
text,
|
||||
|
@ -664,8 +668,6 @@ class ImageDraw:
|
|||
)
|
||||
except TypeError:
|
||||
mask = font.getmask(text)
|
||||
if stroke_offset:
|
||||
coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]
|
||||
if mode == "RGBA":
|
||||
# font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
|
||||
# extract mask and set text alpha
|
||||
|
@ -699,25 +701,25 @@ class ImageDraw:
|
|||
def multiline_text(
|
||||
self,
|
||||
xy: tuple[float, float],
|
||||
text: str,
|
||||
fill=None,
|
||||
text: AnyStr,
|
||||
fill: _Ink | None = None,
|
||||
font: (
|
||||
ImageFont.ImageFont
|
||||
| ImageFont.FreeTypeFont
|
||||
| ImageFont.TransposedFont
|
||||
| None
|
||||
) = None,
|
||||
anchor=None,
|
||||
spacing=4,
|
||||
align="left",
|
||||
direction=None,
|
||||
features=None,
|
||||
language=None,
|
||||
stroke_width=0,
|
||||
stroke_fill=None,
|
||||
embedded_color=False,
|
||||
anchor: str | None = None,
|
||||
spacing: float = 4,
|
||||
align: str = "left",
|
||||
direction: str | None = None,
|
||||
features: list[str] | None = None,
|
||||
language: str | None = None,
|
||||
stroke_width: float = 0,
|
||||
stroke_fill: _Ink | None = None,
|
||||
embedded_color: bool = False,
|
||||
*,
|
||||
font_size=None,
|
||||
font_size: float | None = None,
|
||||
) -> None:
|
||||
if direction == "ttb":
|
||||
msg = "ttb direction is unsupported for multiline text"
|
||||
|
@ -790,19 +792,19 @@ class ImageDraw:
|
|||
|
||||
def textlength(
|
||||
self,
|
||||
text: str,
|
||||
text: AnyStr,
|
||||
font: (
|
||||
ImageFont.ImageFont
|
||||
| ImageFont.FreeTypeFont
|
||||
| ImageFont.TransposedFont
|
||||
| None
|
||||
) = None,
|
||||
direction=None,
|
||||
features=None,
|
||||
language=None,
|
||||
embedded_color=False,
|
||||
direction: str | None = None,
|
||||
features: list[str] | None = None,
|
||||
language: str | None = None,
|
||||
embedded_color: bool = False,
|
||||
*,
|
||||
font_size=None,
|
||||
font_size: float | None = None,
|
||||
) -> float:
|
||||
"""Get the length of a given string, in pixels with 1/64 precision."""
|
||||
if self._multiline_check(text):
|
||||
|
@ -819,20 +821,25 @@ class ImageDraw:
|
|||
|
||||
def textbbox(
|
||||
self,
|
||||
xy,
|
||||
text,
|
||||
font=None,
|
||||
anchor=None,
|
||||
spacing=4,
|
||||
align="left",
|
||||
direction=None,
|
||||
features=None,
|
||||
language=None,
|
||||
stroke_width=0,
|
||||
embedded_color=False,
|
||||
xy: tuple[float, float],
|
||||
text: AnyStr,
|
||||
font: (
|
||||
ImageFont.ImageFont
|
||||
| ImageFont.FreeTypeFont
|
||||
| ImageFont.TransposedFont
|
||||
| None
|
||||
) = None,
|
||||
anchor: str | None = None,
|
||||
spacing: float = 4,
|
||||
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,
|
||||
) -> tuple[int, int, int, int]:
|
||||
font_size: float | None = None,
|
||||
) -> tuple[float, float, float, float]:
|
||||
"""Get the bounding box of a given string, in pixels."""
|
||||
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
||||
msg = "Embedded color supported only in RGB and RGBA modes"
|
||||
|
@ -864,20 +871,25 @@ class ImageDraw:
|
|||
|
||||
def multiline_textbbox(
|
||||
self,
|
||||
xy,
|
||||
text,
|
||||
font=None,
|
||||
anchor=None,
|
||||
spacing=4,
|
||||
align="left",
|
||||
direction=None,
|
||||
features=None,
|
||||
language=None,
|
||||
stroke_width=0,
|
||||
embedded_color=False,
|
||||
xy: tuple[float, float],
|
||||
text: AnyStr,
|
||||
font: (
|
||||
ImageFont.ImageFont
|
||||
| ImageFont.FreeTypeFont
|
||||
| ImageFont.TransposedFont
|
||||
| None
|
||||
) = None,
|
||||
anchor: str | None = None,
|
||||
spacing: float = 4,
|
||||
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,
|
||||
) -> tuple[int, int, int, int]:
|
||||
font_size: float | None = None,
|
||||
) -> tuple[float, float, float, float]:
|
||||
if direction == "ttb":
|
||||
msg = "ttb direction is unsupported for multiline text"
|
||||
raise ValueError(msg)
|
||||
|
@ -916,7 +928,7 @@ class ImageDraw:
|
|||
elif anchor[1] == "d":
|
||||
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):
|
||||
left = xy[0]
|
||||
|
|
|
@ -24,10 +24,10 @@
|
|||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import BinaryIO
|
||||
from typing import Any, AnyStr, BinaryIO
|
||||
|
||||
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
||||
from ._typing import StrOrBytesPath
|
||||
from ._typing import Coords, StrOrBytesPath
|
||||
|
||||
|
||||
class Pen:
|
||||
|
@ -74,12 +74,14 @@ class Draw:
|
|||
image = Image.new(image, size, color)
|
||||
self.draw = ImageDraw.Draw(image)
|
||||
self.image = image
|
||||
self.transform = None
|
||||
self.transform: tuple[float, float, float, float, float, float] | None = None
|
||||
|
||||
def flush(self) -> Image.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
|
||||
outline = fill = None
|
||||
width = 1
|
||||
|
@ -95,20 +97,21 @@ class Draw:
|
|||
fill = pen.color
|
||||
# handle transformation
|
||||
if self.transform:
|
||||
xy = ImagePath.Path(xy)
|
||||
xy.transform(self.transform)
|
||||
path = ImagePath.Path(xy)
|
||||
path.transform(self.transform)
|
||||
xy = path
|
||||
# render the item
|
||||
if op == "line":
|
||||
self.draw.line(xy, fill=outline, width=width)
|
||||
else:
|
||||
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."""
|
||||
(xoffset, yoffset) = offset
|
||||
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
|
||||
angles, inside the given bounding box.
|
||||
|
@ -117,7 +120,7 @@ class Draw:
|
|||
"""
|
||||
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
|
||||
with a straight line.
|
||||
|
@ -126,7 +129,7 @@ class Draw:
|
|||
"""
|
||||
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.
|
||||
|
||||
|
@ -134,7 +137,7 @@ class Draw:
|
|||
"""
|
||||
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.
|
||||
|
||||
|
@ -142,7 +145,7 @@ class Draw:
|
|||
"""
|
||||
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
|
||||
center of the bounding box.
|
||||
|
@ -151,7 +154,7 @@ class Draw:
|
|||
"""
|
||||
self.render("pieslice", xy, start, end, *options)
|
||||
|
||||
def polygon(self, xy, *options):
|
||||
def polygon(self, xy: Coords, *options: Any) -> None:
|
||||
"""
|
||||
Draws a polygon.
|
||||
|
||||
|
@ -164,7 +167,7 @@ class Draw:
|
|||
"""
|
||||
self.render("polygon", xy, *options)
|
||||
|
||||
def rectangle(self, xy, *options):
|
||||
def rectangle(self, xy: Coords, *options) -> None:
|
||||
"""
|
||||
Draws a rectangle.
|
||||
|
||||
|
@ -172,18 +175,21 @@ class Draw:
|
|||
"""
|
||||
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.
|
||||
|
||||
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
|
||||
"""
|
||||
if self.transform:
|
||||
xy = ImagePath.Path(xy)
|
||||
xy.transform(self.transform)
|
||||
path = ImagePath.Path(xy)
|
||||
path.transform(self.transform)
|
||||
xy = path
|
||||
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.
|
||||
|
||||
|
@ -192,11 +198,12 @@ class Draw:
|
|||
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
|
||||
"""
|
||||
if self.transform:
|
||||
xy = ImagePath.Path(xy)
|
||||
xy.transform(self.transform)
|
||||
path = ImagePath.Path(xy)
|
||||
path.transform(self.transform)
|
||||
xy = path
|
||||
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.
|
||||
This is the amount by which following text should be offset.
|
||||
|
|
|
@ -86,7 +86,7 @@ def raise_oserror(error: int) -> OSError:
|
|||
raise _get_oserror(error, encoder=False)
|
||||
|
||||
|
||||
def _tilesort(t):
|
||||
def _tilesort(t) -> int:
|
||||
# sort on offset
|
||||
return t[2]
|
||||
|
||||
|
@ -161,7 +161,7 @@ class ImageFile(Image.Image):
|
|||
return Image.MIME.get(self.format.upper())
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
def __setstate__(self, state) -> None:
|
||||
self.tile = []
|
||||
super().__setstate__(state)
|
||||
|
||||
|
@ -333,14 +333,14 @@ class ImageFile(Image.Image):
|
|||
# def load_read(self, read_bytes: int) -> bytes:
|
||||
# pass
|
||||
|
||||
def _seek_check(self, frame):
|
||||
def _seek_check(self, frame: int) -> bool:
|
||||
if (
|
||||
frame < self._min_frame
|
||||
# Only check upper limit on frames if additional seek operations
|
||||
# are not required to do so
|
||||
or (
|
||||
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"
|
||||
|
@ -370,7 +370,7 @@ class StubImageFile(ImageFile):
|
|||
msg = "StubImageFile subclass must implement _open"
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
def load(self):
|
||||
def load(self) -> Image.core.PixelAccess | None:
|
||||
loader = self._load()
|
||||
if loader is None:
|
||||
msg = f"cannot find loader for this {self.format} file"
|
||||
|
@ -378,7 +378,7 @@ class StubImageFile(ImageFile):
|
|||
image = loader.load(self)
|
||||
assert image is not None
|
||||
# become the other object (!)
|
||||
self.__class__ = image.__class__
|
||||
self.__class__ = image.__class__ # type: ignore[assignment]
|
||||
self.__dict__ = image.__dict__
|
||||
return image.load()
|
||||
|
||||
|
@ -396,8 +396,8 @@ class Parser:
|
|||
|
||||
incremental = None
|
||||
image: Image.Image | None = None
|
||||
data = None
|
||||
decoder = None
|
||||
data: bytes | None = None
|
||||
decoder: Image.core.ImagingDecoder | PyDecoder | None = None
|
||||
offset = 0
|
||||
finished = 0
|
||||
|
||||
|
@ -409,7 +409,7 @@ class Parser:
|
|||
"""
|
||||
assert self.data is None, "cannot reuse parsers"
|
||||
|
||||
def feed(self, data):
|
||||
def feed(self, data: bytes) -> None:
|
||||
"""
|
||||
(Consumer) Feed data to the parser.
|
||||
|
||||
|
@ -485,13 +485,13 @@ class Parser:
|
|||
|
||||
self.image = im
|
||||
|
||||
def __enter__(self):
|
||||
def __enter__(self) -> Parser:
|
||||
return self
|
||||
|
||||
def __exit__(self, *args: object) -> None:
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
def close(self) -> Image.Image:
|
||||
"""
|
||||
(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
|
||||
|
||||
:param im: Image object.
|
||||
|
@ -553,7 +553,9 @@ def _save(im, fp, tile, bufsize=0) -> None:
|
|||
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:
|
||||
if offset > 0:
|
||||
fp.seek(offset)
|
||||
|
@ -580,7 +582,7 @@ def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None):
|
|||
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
|
||||
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"
|
||||
raise OSError(msg)
|
||||
return data
|
||||
data = []
|
||||
blocks: list[bytes] = []
|
||||
remaining_size = size
|
||||
while remaining_size > 0:
|
||||
block = fp.read(min(remaining_size, SAFEBLOCK))
|
||||
if not block:
|
||||
break
|
||||
data.append(block)
|
||||
blocks.append(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"
|
||||
raise OSError(msg)
|
||||
return b"".join(data)
|
||||
return b"".join(blocks)
|
||||
|
||||
|
||||
class PyCodecState:
|
||||
|
@ -629,18 +631,18 @@ class PyCodecState:
|
|||
class PyCodec:
|
||||
fd: IO[bytes] | None
|
||||
|
||||
def __init__(self, mode, *args):
|
||||
self.im = None
|
||||
def __init__(self, mode: str, *args: Any) -> None:
|
||||
self.im: Image.core.ImagingCore | None = None
|
||||
self.state = PyCodecState()
|
||||
self.fd = None
|
||||
self.mode = mode
|
||||
self.init(args)
|
||||
|
||||
def init(self, args):
|
||||
def init(self, args: tuple[Any, ...]) -> None:
|
||||
"""
|
||||
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
|
||||
"""
|
||||
self.args = args
|
||||
|
@ -653,7 +655,7 @@ class PyCodec:
|
|||
"""
|
||||
pass
|
||||
|
||||
def setfd(self, fd):
|
||||
def setfd(self, fd: IO[bytes]) -> None:
|
||||
"""
|
||||
Called from ImageFile to set the Python file-like object
|
||||
|
||||
|
@ -662,7 +664,7 @@ class PyCodec:
|
|||
"""
|
||||
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
|
||||
|
||||
|
@ -793,7 +795,7 @@ class PyEncoder(PyCodec):
|
|||
self.fd.write(data)
|
||||
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 bufsize: Buffer size.
|
||||
|
|
|
@ -34,7 +34,7 @@ import warnings
|
|||
from enum import IntEnum
|
||||
from io import BytesIO
|
||||
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 ._typing import StrOrBytesPath
|
||||
|
@ -46,6 +46,13 @@ if TYPE_CHECKING:
|
|||
from ._imagingft import Font
|
||||
|
||||
|
||||
class Axis(TypedDict):
|
||||
minimum: int | None
|
||||
default: int | None
|
||||
maximum: int | None
|
||||
name: bytes | None
|
||||
|
||||
|
||||
class Layout(IntEnum):
|
||||
BASIC = 0
|
||||
RAQM = 1
|
||||
|
@ -138,7 +145,9 @@ class ImageFont:
|
|||
|
||||
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.
|
||||
|
||||
|
@ -236,7 +245,7 @@ class FreeTypeFont:
|
|||
|
||||
self.layout_engine = layout_engine
|
||||
|
||||
def load_from_bytes(f):
|
||||
def load_from_bytes(f) -> None:
|
||||
self.font_bytes = f.read()
|
||||
self.font = core.getfont(
|
||||
"", size, index, encoding, self.font_bytes, layout_engine
|
||||
|
@ -283,7 +292,12 @@ class FreeTypeFont:
|
|||
return self.font.ascent, self.font.descent
|
||||
|
||||
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:
|
||||
"""
|
||||
Returns length (in pixels with 1/64 precision) of given text when rendered
|
||||
|
@ -424,16 +438,16 @@ class FreeTypeFont:
|
|||
|
||||
def getmask(
|
||||
self,
|
||||
text,
|
||||
mode="",
|
||||
direction=None,
|
||||
features=None,
|
||||
language=None,
|
||||
stroke_width=0,
|
||||
anchor=None,
|
||||
ink=0,
|
||||
start=None,
|
||||
):
|
||||
text: str | bytes,
|
||||
mode: str = "",
|
||||
direction: str | None = None,
|
||||
features: list[str] | None = None,
|
||||
language: str | None = None,
|
||||
stroke_width: float = 0,
|
||||
anchor: str | None = None,
|
||||
ink: int = 0,
|
||||
start: tuple[float, float] | None = None,
|
||||
) -> Image.core.ImagingCore:
|
||||
"""
|
||||
Create a bitmap for the text.
|
||||
|
||||
|
@ -516,17 +530,17 @@ class FreeTypeFont:
|
|||
def getmask2(
|
||||
self,
|
||||
text: str | bytes,
|
||||
mode="",
|
||||
direction=None,
|
||||
features=None,
|
||||
language=None,
|
||||
stroke_width=0,
|
||||
anchor=None,
|
||||
ink=0,
|
||||
start=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
mode: str = "",
|
||||
direction: str | None = None,
|
||||
features: list[str] | None = None,
|
||||
language: str | None = None,
|
||||
stroke_width: float = 0,
|
||||
anchor: str | None = None,
|
||||
ink: int = 0,
|
||||
start: tuple[float, float] | None = None,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> tuple[Image.core.ImagingCore, tuple[int, int]]:
|
||||
"""
|
||||
Create a bitmap for the text.
|
||||
|
||||
|
@ -599,7 +613,7 @@ class FreeTypeFont:
|
|||
if start is None:
|
||||
start = (0, 0)
|
||||
|
||||
def fill(width, height):
|
||||
def fill(width: int, height: int) -> Image.core.ImagingCore:
|
||||
size = (width, height)
|
||||
Image._decompression_bomb_check(size)
|
||||
return Image.core.fill("RGBA" if mode == "RGBA" else "L", size)
|
||||
|
@ -619,8 +633,13 @@ class FreeTypeFont:
|
|||
)
|
||||
|
||||
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,
|
||||
using any specified arguments to override the settings.
|
||||
|
@ -655,7 +674,7 @@ class FreeTypeFont:
|
|||
raise NotImplementedError(msg) from e
|
||||
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.
|
||||
:exception OSError: If the font is not a variation font.
|
||||
|
@ -674,7 +693,7 @@ class FreeTypeFont:
|
|||
|
||||
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.
|
||||
:exception OSError: If the font is not a variation font.
|
||||
|
@ -704,7 +723,9 @@ class FreeTypeFont:
|
|||
class TransposedFont:
|
||||
"""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
|
||||
object.
|
||||
|
@ -718,13 +739,17 @@ class TransposedFont:
|
|||
self.font = font
|
||||
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)
|
||||
if self.orientation is not None:
|
||||
return im.transpose(self.orientation)
|
||||
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)
|
||||
# this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont
|
||||
left, top, right, bottom = self.font.getbbox(text, *args, **kwargs)
|
||||
|
@ -734,7 +759,7 @@ class TransposedFont:
|
|||
return 0, 0, height, width
|
||||
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):
|
||||
msg = "text length is undefined for text rotated by 90 or 270 degrees"
|
||||
raise ValueError(msg)
|
||||
|
|
|
@ -249,14 +249,21 @@ def lambda_eval(
|
|||
:py:func:`~PIL.Image.merge` function.
|
||||
|
||||
:param expression: A function that receives a dictionary.
|
||||
:param options: Values to add to the function's dictionary. You
|
||||
can either use a dictionary, or one or more keyword
|
||||
arguments.
|
||||
:param options: Values to add to the function's dictionary. Deprecated.
|
||||
You can instead use one or more keyword arguments.
|
||||
:param **kw: Values to add to the function's dictionary.
|
||||
:return: The expression result. This is usually an image object, but can
|
||||
also be an integer, a floating point value, or a pixel tuple,
|
||||
depending on the expression.
|
||||
"""
|
||||
|
||||
if options:
|
||||
deprecate(
|
||||
"ImageMath.lambda_eval options",
|
||||
12,
|
||||
"ImageMath.lambda_eval keyword arguments",
|
||||
)
|
||||
|
||||
args: dict[str, Any] = ops.copy()
|
||||
args.update(options)
|
||||
args.update(kw)
|
||||
|
@ -287,14 +294,21 @@ def unsafe_eval(
|
|||
:py:func:`~PIL.Image.merge` function.
|
||||
|
||||
:param expression: A string containing a Python-style expression.
|
||||
:param options: Values to add to the evaluation context. You
|
||||
can either use a dictionary, or one or more keyword
|
||||
arguments.
|
||||
:param options: Values to add to the evaluation context. Deprecated.
|
||||
You can instead use one or more keyword arguments.
|
||||
:param **kw: Values to add to the evaluation context.
|
||||
:return: The evaluated expression. This is usually an image object, but can
|
||||
also be an integer, a floating point value, or a pixel tuple,
|
||||
depending on the expression.
|
||||
"""
|
||||
|
||||
if options:
|
||||
deprecate(
|
||||
"ImageMath.unsafe_eval options",
|
||||
12,
|
||||
"ImageMath.unsafe_eval keyword arguments",
|
||||
)
|
||||
|
||||
# build execution namespace
|
||||
args: dict[str, Any] = ops.copy()
|
||||
for k in list(options.keys()) + list(kw.keys()):
|
||||
|
|
|
@ -19,11 +19,14 @@ from __future__ import annotations
|
|||
|
||||
import sys
|
||||
from io import BytesIO
|
||||
from typing import Callable
|
||||
from typing import TYPE_CHECKING, Callable
|
||||
|
||||
from . import Image
|
||||
from ._util import is_path
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import ImageFile
|
||||
|
||||
qt_version: str | None
|
||||
qt_versions = [
|
||||
["6", "PyQt6"],
|
||||
|
@ -55,7 +58,7 @@ else:
|
|||
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."""
|
||||
# use qRgb to pack the colors, and then turn the resulting long
|
||||
# into a negative integer with the same bitpattern.
|
||||
|
@ -90,11 +93,11 @@ def fromqimage(im):
|
|||
return Image.open(b)
|
||||
|
||||
|
||||
def fromqpixmap(im):
|
||||
def fromqpixmap(im) -> ImageFile.ImageFile:
|
||||
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
|
||||
"""
|
||||
|
@ -172,7 +175,7 @@ def _toqclass_helper(im):
|
|||
if qt_is_installed:
|
||||
|
||||
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
|
||||
class.
|
||||
|
|
|
@ -33,7 +33,7 @@ class Iterator:
|
|||
:param im: An image object.
|
||||
"""
|
||||
|
||||
def __init__(self, im: Image.Image):
|
||||
def __init__(self, im: Image.Image) -> None:
|
||||
if not hasattr(im, "seek"):
|
||||
msg = "im must have seek method"
|
||||
raise AttributeError(msg)
|
||||
|
|
|
@ -28,7 +28,7 @@ from __future__ import annotations
|
|||
|
||||
import tkinter
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
from . import Image, ImageFile
|
||||
|
||||
|
@ -61,7 +61,9 @@ def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None:
|
|||
return Image.open(source)
|
||||
|
||||
|
||||
def _pyimagingtkcall(command, photo, id):
|
||||
def _pyimagingtkcall(
|
||||
command: str, photo: PhotoImage | tkinter.PhotoImage, id: int
|
||||
) -> None:
|
||||
tk = photo.tk
|
||||
try:
|
||||
tk.call(command, photo, id)
|
||||
|
@ -215,11 +217,14 @@ class BitmapImage:
|
|||
: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
|
||||
if image is None:
|
||||
image = _get_image_from_kw(kw)
|
||||
|
||||
if image is None:
|
||||
msg = "Image is required"
|
||||
raise ValueError(msg)
|
||||
self.__mode = image.mode
|
||||
self.__size = image.size
|
||||
|
||||
|
@ -278,18 +283,23 @@ def getimage(photo: PhotoImage) -> Image.Image:
|
|||
return im
|
||||
|
||||
|
||||
def _show(image, title):
|
||||
def _show(image: Image.Image, title: str | None) -> None:
|
||||
"""Helper for the Image.show method."""
|
||||
|
||||
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":
|
||||
self.image = BitmapImage(im, foreground="white", master=master)
|
||||
else:
|
||||
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"
|
||||
raise OSError(msg)
|
||||
top = tkinter.Toplevel()
|
||||
|
|
|
@ -70,11 +70,14 @@ class Dib:
|
|||
"""
|
||||
|
||||
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:
|
||||
if isinstance(image, str):
|
||||
mode = image
|
||||
image = ""
|
||||
if size is None:
|
||||
msg = "If first argument is mode, size is required"
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
mode = image.mode
|
||||
size = image.size
|
||||
|
@ -87,7 +90,7 @@ class Dib:
|
|||
assert not isinstance(image, str)
|
||||
self.paste(image)
|
||||
|
||||
def expose(self, handle):
|
||||
def expose(self, handle: int | HDC | HWND) -> None:
|
||||
"""
|
||||
Copy the bitmap contents to a device context.
|
||||
|
||||
|
@ -98,14 +101,18 @@ class Dib:
|
|||
if isinstance(handle, HWND):
|
||||
dc = self.image.getdc(handle)
|
||||
try:
|
||||
result = self.image.expose(dc)
|
||||
self.image.expose(dc)
|
||||
finally:
|
||||
self.image.releasedc(handle, dc)
|
||||
else:
|
||||
result = self.image.expose(handle)
|
||||
return result
|
||||
self.image.expose(handle)
|
||||
|
||||
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
|
||||
what part of it to draw.
|
||||
|
@ -115,19 +122,18 @@ class Dib:
|
|||
the destination have different sizes, the image is resized as
|
||||
necessary.
|
||||
"""
|
||||
if not src:
|
||||
if src is None:
|
||||
src = (0, 0) + self.size
|
||||
if isinstance(handle, HWND):
|
||||
dc = self.image.getdc(handle)
|
||||
try:
|
||||
result = self.image.draw(dc, dst, src)
|
||||
self.image.draw(dc, dst, src)
|
||||
finally:
|
||||
self.image.releasedc(handle, dc)
|
||||
else:
|
||||
result = self.image.draw(handle, dst, src)
|
||||
return result
|
||||
self.image.draw(handle, dst, src)
|
||||
|
||||
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
|
||||
context.
|
||||
|
@ -139,8 +145,8 @@ class Dib:
|
|||
|
||||
:param handle: Device context (HDC), cast to a Python integer, or an
|
||||
HDC or HWND instance.
|
||||
:return: A true value if one or more entries were changed (this
|
||||
indicates that the image should be redrawn).
|
||||
:return: The number of entries that were changed (if one or more entries,
|
||||
this indicates that the image should be redrawn).
|
||||
"""
|
||||
if isinstance(handle, HWND):
|
||||
handle = self.image.getdc(handle)
|
||||
|
@ -202,22 +208,22 @@ class Window:
|
|||
title, self.__dispatcher, width or 0, height or 0
|
||||
)
|
||||
|
||||
def __dispatcher(self, action, *args):
|
||||
return getattr(self, f"ui_handle_{action}")(*args)
|
||||
def __dispatcher(self, action: str, *args: int) -> None:
|
||||
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
|
||||
|
||||
def ui_handle_damage(self, x0, y0, x1, y1):
|
||||
def ui_handle_damage(self, x0: int, y0: int, x1: int, y1: int) -> None:
|
||||
pass
|
||||
|
||||
def ui_handle_destroy(self) -> None:
|
||||
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
|
||||
|
||||
def ui_handle_resize(self, width, height):
|
||||
def ui_handle_resize(self, width: int, height: int) -> None:
|
||||
pass
|
||||
|
||||
def mainloop(self) -> None:
|
||||
|
@ -227,12 +233,12 @@ class Window:
|
|||
class ImageWindow(Window):
|
||||
"""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):
|
||||
image = Dib(image)
|
||||
self.image = image
|
||||
width, height = image.size
|
||||
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))
|
||||
|
|
|
@ -18,6 +18,7 @@ from __future__ import annotations
|
|||
|
||||
from collections.abc import Sequence
|
||||
from io import BytesIO
|
||||
from typing import cast
|
||||
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i16be as i16
|
||||
|
@ -184,7 +185,9 @@ Image.register_open(IptcImageFile.format, IptcImageFile)
|
|||
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.
|
||||
|
||||
|
@ -221,16 +224,17 @@ def getiptcinfo(im):
|
|||
class FakeImage:
|
||||
pass
|
||||
|
||||
im = FakeImage()
|
||||
im.__class__ = IptcImageFile
|
||||
fake_im = FakeImage()
|
||||
fake_im.__class__ = IptcImageFile # type: ignore[assignment]
|
||||
iptc_im = cast(IptcImageFile, fake_im)
|
||||
|
||||
# parse the IPTC information chunk
|
||||
im.info = {}
|
||||
im.fp = BytesIO(data)
|
||||
iptc_im.info = {}
|
||||
iptc_im.fp = BytesIO(data)
|
||||
|
||||
try:
|
||||
im._open()
|
||||
iptc_im._open()
|
||||
except (IndexError, KeyError):
|
||||
pass # expected failure
|
||||
|
||||
return im.info
|
||||
return iptc_im.info
|
||||
|
|
|
@ -29,7 +29,7 @@ class BoxReader:
|
|||
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.has_length = length >= 0
|
||||
self.length = length
|
||||
|
@ -97,7 +97,7 @@ class BoxReader:
|
|||
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
|
||||
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)
|
||||
|
||||
|
||||
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,
|
||||
color space information, and optionally DPI information,
|
||||
returning a (size, mode, mimetype, dpi) tuple."""
|
||||
|
@ -155,6 +163,7 @@ def _parse_jp2_header(fp):
|
|||
elif tbox == b"ftyp":
|
||||
if reader.read_fields(">4s")[0] == b"jpx ":
|
||||
mimetype = "image/jpx"
|
||||
assert header is not None
|
||||
|
||||
size = None
|
||||
mode = None
|
||||
|
@ -168,6 +177,9 @@ def _parse_jp2_header(fp):
|
|||
|
||||
if tbox == b"ihdr":
|
||||
height, width, nc, bpc = header.read_fields(">IIHB")
|
||||
assert isinstance(height, int)
|
||||
assert isinstance(width, int)
|
||||
assert isinstance(bpc, int)
|
||||
size = (width, height)
|
||||
if nc == 1 and (bpc & 0x7F) > 8:
|
||||
mode = "I;16"
|
||||
|
@ -185,11 +197,21 @@ def _parse_jp2_header(fp):
|
|||
mode = "CMYK"
|
||||
elif tbox == b"pclr" and mode in ("L", "LA"):
|
||||
ne, npc = header.read_fields(">HB")
|
||||
bitdepths = header.read_fields(">" + ("B" * npc))
|
||||
if max(bitdepths) <= 8:
|
||||
assert isinstance(ne, int)
|
||||
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()
|
||||
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"
|
||||
elif tbox == b"res ":
|
||||
res = header.read_boxes()
|
||||
|
@ -197,6 +219,12 @@ def _parse_jp2_header(fp):
|
|||
tres = res.next_box_type()
|
||||
if tres == b"resc":
|
||||
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)
|
||||
vres = _res_to_dpi(vrcn, vrcd, vrce)
|
||||
if hres is not None and vres is not None:
|
||||
|
|
|
@ -60,7 +60,7 @@ def Skip(self: JpegImageFile, marker: int) -> None:
|
|||
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.
|
||||
# Also look for well-known application markers.
|
||||
|
@ -133,13 +133,14 @@ def APP(self, marker):
|
|||
offset += 4
|
||||
data = s[offset : offset + size]
|
||||
if code == 0x03ED: # ResolutionInfo
|
||||
data = {
|
||||
photoshop[code] = {
|
||||
"XResolution": i32(data, 0) / 65536,
|
||||
"DisplayedUnitsX": i16(data, 4),
|
||||
"YResolution": i32(data, 8) / 65536,
|
||||
"DisplayedUnitsY": i16(data, 12),
|
||||
}
|
||||
photoshop[code] = data
|
||||
else:
|
||||
photoshop[code] = data
|
||||
offset += size
|
||||
offset += offset & 1 # align
|
||||
except struct.error:
|
||||
|
@ -338,6 +339,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
|
||||
# Create attributes
|
||||
self.bits = self.layers = 0
|
||||
self._exif_offset = 0
|
||||
|
||||
# JPEG specifics (internal)
|
||||
self.layer = []
|
||||
|
@ -466,7 +468,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
|
||||
self.tile = []
|
||||
|
||||
def _getexif(self) -> dict[str, Any] | None:
|
||||
def _getexif(self) -> dict[int, Any] | None:
|
||||
return _getexif(self)
|
||||
|
||||
def _read_dpi_from_exif(self) -> None:
|
||||
|
@ -498,17 +500,17 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
):
|
||||
self.info["dpi"] = 72, 72
|
||||
|
||||
def _getmp(self):
|
||||
def _getmp(self) -> dict[int, Any] | None:
|
||||
return _getmp(self)
|
||||
|
||||
|
||||
def _getexif(self) -> dict[str, Any] | None:
|
||||
def _getexif(self: JpegImageFile) -> dict[int, Any] | None:
|
||||
if "exif" not in self.info:
|
||||
return None
|
||||
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
|
||||
# experimental" _getexif version that's been in use for years now,
|
||||
# itself based on the ImageFileDirectory class in the TIFF plugin.
|
||||
|
@ -616,7 +618,7 @@ samplings = {
|
|||
# fmt: on
|
||||
|
||||
|
||||
def get_sampling(im):
|
||||
def get_sampling(im: Image.Image) -> int:
|
||||
# There's no subsampling when images have only 1 layer
|
||||
# (grayscale images) or when they are CMYK (4 layers),
|
||||
# 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.
|
||||
# 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.
|
||||
if not hasattr(im, "layers") or im.layers in (1, 4):
|
||||
if not isinstance(im, JpegImageFile) or im.layers in (1, 4):
|
||||
return -1
|
||||
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
|
||||
return samplings.get(sampling, -1)
|
||||
|
@ -683,7 +685,11 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
raise ValueError(msg)
|
||||
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:
|
||||
return qtables
|
||||
if isinstance(qtables, str):
|
||||
|
@ -713,12 +719,12 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
if len(table) != 64:
|
||||
msg = "Invalid quantization table"
|
||||
raise TypeError(msg)
|
||||
table = array.array("H", table)
|
||||
table_array = array.array("H", table)
|
||||
except TypeError as e:
|
||||
msg = "Invalid quantization table"
|
||||
raise ValueError(msg) from e
|
||||
else:
|
||||
qtables[idx] = list(table)
|
||||
qtables[idx] = list(table_array)
|
||||
return qtables
|
||||
|
||||
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
|
||||
def jpeg_factory(fp=None, filename=None):
|
||||
def jpeg_factory(fp: IO[bytes] | None = None, filename: str | bytes | None = None):
|
||||
im = JpegImageFile(fp, filename)
|
||||
try:
|
||||
mpheader = im._getmp()
|
||||
if mpheader[45057] > 1:
|
||||
if mpheader is not None and mpheader[45057] > 1:
|
||||
for segment, content in im.applist:
|
||||
if segment == "APP1" and b' hdrgm:Version="' in content:
|
||||
# Ultra HDR images are not yet supported
|
||||
|
|
|
@ -37,7 +37,7 @@ You can get the subsampling of a JPEG with the
|
|||
:func:`.JpegImagePlugin.get_sampling` function.
|
||||
|
||||
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
|
||||
|
|
|
@ -22,7 +22,7 @@ from __future__ import annotations
|
|||
import itertools
|
||||
import os
|
||||
import struct
|
||||
from typing import IO
|
||||
from typing import IO, Any, cast
|
||||
|
||||
from . import (
|
||||
Image,
|
||||
|
@ -101,8 +101,11 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
JpegImagePlugin.JpegImageFile._open(self)
|
||||
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()
|
||||
if self.mpinfo is None:
|
||||
msg = "Image appears to be a malformed MPO file"
|
||||
raise ValueError(msg)
|
||||
self.n_frames = self.mpinfo[0xB001]
|
||||
self.__mpoffsets = [
|
||||
mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002]
|
||||
|
@ -149,7 +152,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
return self.__frame
|
||||
|
||||
@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
|
||||
an instance of MpoImageFile.
|
||||
|
@ -161,8 +167,9 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
|||
double call to _open.
|
||||
"""
|
||||
jpeg_instance.__class__ = MpoImageFile
|
||||
jpeg_instance._after_jpeg_open(mpheader)
|
||||
return jpeg_instance
|
||||
mpo_instance = cast(MpoImageFile, jpeg_instance)
|
||||
mpo_instance._after_jpeg_open(mpheader)
|
||||
return mpo_instance
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
|
|
@ -174,12 +174,15 @@ def _write_image(im, filename, existing_pdf, image_refs):
|
|||
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)
|
||||
filename_str = filename.decode() if isinstance(filename, bytes) else filename
|
||||
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:
|
||||
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")
|
||||
if dpi:
|
||||
|
@ -228,12 +231,7 @@ def _save(im, fp, filename, save_all=False):
|
|||
for im in ims:
|
||||
im_number_of_pages = 1
|
||||
if save_all:
|
||||
try:
|
||||
im_number_of_pages = im.n_frames
|
||||
except AttributeError:
|
||||
# Image format does not have n_frames.
|
||||
# It is a single frame image
|
||||
pass
|
||||
im_number_of_pages = getattr(im, "n_frames", 1)
|
||||
number_of_pages += im_number_of_pages
|
||||
for i in range(im_number_of_pages):
|
||||
image_refs.append(existing_pdf.next_object_id(0))
|
||||
|
@ -250,7 +248,9 @@ def _save(im, fp, filename, save_all=False):
|
|||
|
||||
page_number = 0
|
||||
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:
|
||||
image_ref, procset = _write_image(im, filename, existing_pdf, image_refs)
|
||||
|
||||
|
|
|
@ -38,8 +38,9 @@ import re
|
|||
import struct
|
||||
import warnings
|
||||
import zlib
|
||||
from collections.abc import Callable
|
||||
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 ._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()
|
||||
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
|
||||
if dobj.unconsumed_tail:
|
||||
|
@ -144,7 +145,7 @@ def _safe_zlib_decompress(s):
|
|||
return plaintext
|
||||
|
||||
|
||||
def _crc32(data, seed=0):
|
||||
def _crc32(data: bytes, seed: int = 0) -> int:
|
||||
return zlib.crc32(data, seed) & 0xFFFFFFFF
|
||||
|
||||
|
||||
|
@ -191,7 +192,7 @@ class ChunkStream:
|
|||
assert self.queue is not None
|
||||
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"""
|
||||
|
||||
logger.debug("STREAM %r %s %s", cid, pos, length)
|
||||
|
@ -230,6 +231,7 @@ class ChunkStream:
|
|||
|
||||
cids = []
|
||||
|
||||
assert self.fp is not None
|
||||
while True:
|
||||
try:
|
||||
cid, pos, length = self.read()
|
||||
|
@ -407,6 +409,7 @@ class PngStream(ChunkStream):
|
|||
|
||||
def chunk_iCCP(self, pos: int, length: int) -> bytes:
|
||||
# ICC profile
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
# according to PNG spec, the iCCP chunk contains:
|
||||
# Profile name 1-79 bytes (character string)
|
||||
|
@ -434,6 +437,7 @@ class PngStream(ChunkStream):
|
|||
|
||||
def chunk_IHDR(self, pos: int, length: int) -> bytes:
|
||||
# image header
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
if length < 13:
|
||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||
|
@ -471,6 +475,7 @@ class PngStream(ChunkStream):
|
|||
|
||||
def chunk_PLTE(self, pos: int, length: int) -> bytes:
|
||||
# palette
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
if self.im_mode == "P":
|
||||
self.im_palette = "RGB", s
|
||||
|
@ -478,6 +483,7 @@ class PngStream(ChunkStream):
|
|||
|
||||
def chunk_tRNS(self, pos: int, length: int) -> bytes:
|
||||
# transparency
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
if self.im_mode == "P":
|
||||
if _simple_palette.match(s):
|
||||
|
@ -498,6 +504,7 @@ class PngStream(ChunkStream):
|
|||
|
||||
def chunk_gAMA(self, pos: int, length: int) -> bytes:
|
||||
# gamma setting
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
self.im_info["gamma"] = i32(s) / 100000.0
|
||||
return s
|
||||
|
@ -506,6 +513,7 @@ class PngStream(ChunkStream):
|
|||
# chromaticity, 8 unsigned ints, actual value is scaled by 100,000
|
||||
# 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)
|
||||
raw_vals = struct.unpack(">%dI" % (len(s) // 4), s)
|
||||
self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
|
||||
|
@ -518,6 +526,7 @@ class PngStream(ChunkStream):
|
|||
# 2 saturation
|
||||
# 3 absolute colorimetric
|
||||
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
if length < 1:
|
||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||
|
@ -529,6 +538,7 @@ class PngStream(ChunkStream):
|
|||
|
||||
def chunk_pHYs(self, pos: int, length: int) -> bytes:
|
||||
# pixels per unit
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
if length < 9:
|
||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||
|
@ -546,6 +556,7 @@ class PngStream(ChunkStream):
|
|||
|
||||
def chunk_tEXt(self, pos: int, length: int) -> bytes:
|
||||
# text
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
try:
|
||||
k, v = s.split(b"\0", 1)
|
||||
|
@ -554,17 +565,18 @@ class PngStream(ChunkStream):
|
|||
k = s
|
||||
v = b""
|
||||
if k:
|
||||
k = k.decode("latin-1", "strict")
|
||||
k_str = k.decode("latin-1", "strict")
|
||||
v_str = v.decode("latin-1", "replace")
|
||||
|
||||
self.im_info[k] = v if k == "exif" else v_str
|
||||
self.im_text[k] = v_str
|
||||
self.im_info[k_str] = v if k == b"exif" else v_str
|
||||
self.im_text[k_str] = v_str
|
||||
self.check_text_memory(len(v_str))
|
||||
|
||||
return s
|
||||
|
||||
def chunk_zTXt(self, pos: int, length: int) -> bytes:
|
||||
# compressed text
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
try:
|
||||
k, v = s.split(b"\0", 1)
|
||||
|
@ -589,16 +601,17 @@ class PngStream(ChunkStream):
|
|||
v = b""
|
||||
|
||||
if k:
|
||||
k = k.decode("latin-1", "strict")
|
||||
v = v.decode("latin-1", "replace")
|
||||
k_str = k.decode("latin-1", "strict")
|
||||
v_str = v.decode("latin-1", "replace")
|
||||
|
||||
self.im_info[k] = self.im_text[k] = v
|
||||
self.check_text_memory(len(v))
|
||||
self.im_info[k_str] = self.im_text[k_str] = v_str
|
||||
self.check_text_memory(len(v_str))
|
||||
|
||||
return s
|
||||
|
||||
def chunk_iTXt(self, pos: int, length: int) -> bytes:
|
||||
# international text
|
||||
assert self.fp is not None
|
||||
r = s = ImageFile._safe_read(self.fp, length)
|
||||
try:
|
||||
k, r = r.split(b"\0", 1)
|
||||
|
@ -627,25 +640,27 @@ class PngStream(ChunkStream):
|
|||
if k == b"XML:com.adobe.xmp":
|
||||
self.im_info["xmp"] = v
|
||||
try:
|
||||
k = k.decode("latin-1", "strict")
|
||||
lang = lang.decode("utf-8", "strict")
|
||||
tk = tk.decode("utf-8", "strict")
|
||||
v = v.decode("utf-8", "strict")
|
||||
k_str = k.decode("latin-1", "strict")
|
||||
lang_str = lang.decode("utf-8", "strict")
|
||||
tk_str = tk.decode("utf-8", "strict")
|
||||
v_str = v.decode("utf-8", "strict")
|
||||
except UnicodeError:
|
||||
return s
|
||||
|
||||
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
|
||||
self.check_text_memory(len(v))
|
||||
self.im_info[k_str] = self.im_text[k_str] = iTXt(v_str, lang_str, tk_str)
|
||||
self.check_text_memory(len(v_str))
|
||||
|
||||
return s
|
||||
|
||||
def chunk_eXIf(self, pos: int, length: int) -> bytes:
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
self.im_info["exif"] = b"Exif\x00\x00" + s
|
||||
return s
|
||||
|
||||
# APNG chunks
|
||||
def chunk_acTL(self, pos: int, length: int) -> bytes:
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
if length < 8:
|
||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||
|
@ -666,6 +681,7 @@ class PngStream(ChunkStream):
|
|||
return s
|
||||
|
||||
def chunk_fcTL(self, pos: int, length: int) -> bytes:
|
||||
assert self.fp is not None
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
if length < 26:
|
||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||
|
@ -695,6 +711,7 @@ class PngStream(ChunkStream):
|
|||
return s
|
||||
|
||||
def chunk_fdAT(self, pos: int, length: int) -> bytes:
|
||||
assert self.fp is not None
|
||||
if length < 4:
|
||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||
s = ImageFile._safe_read(self.fp, length)
|
||||
|
@ -767,7 +784,7 @@ class PngImageFile(ImageFile.ImageFile):
|
|||
self._mode = self.png.im_mode
|
||||
self._size = self.png.im_size
|
||||
self.info = self.png.im_info
|
||||
self._text = None
|
||||
self._text: dict[str, str | iTXt] | None = None
|
||||
self.tile = self.png.im_tile
|
||||
self.custom_mimetype = self.png.im_custom_mimetype
|
||||
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
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
def text(self) -> dict[str, str | iTXt]:
|
||||
# experimental
|
||||
if self._text is None:
|
||||
# iTxt, tEXt and zTXt chunks may appear at the end of the file
|
||||
|
@ -806,6 +823,7 @@ class PngImageFile(ImageFile.ImageFile):
|
|||
self.load()
|
||||
if self.is_animated:
|
||||
self.seek(frame)
|
||||
assert self._text is not None
|
||||
return self._text
|
||||
|
||||
def verify(self) -> None:
|
||||
|
@ -1038,7 +1056,7 @@ class PngImageFile(ImageFile.ImageFile):
|
|||
self._prev_im.paste(updated, self.dispose_extent, mask)
|
||||
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:
|
||||
self.load()
|
||||
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)"""
|
||||
|
||||
data = b"".join(data)
|
||||
byte_data = b"".join(data)
|
||||
|
||||
fp.write(o32(len(data)) + cid)
|
||||
fp.write(data)
|
||||
crc = _crc32(data, _crc32(cid))
|
||||
fp.write(o32(len(byte_data)) + cid)
|
||||
fp.write(byte_data)
|
||||
crc = _crc32(byte_data, _crc32(cid))
|
||||
fp.write(o32(crc))
|
||||
|
||||
|
||||
class _idat:
|
||||
# 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.chunk = chunk
|
||||
|
||||
|
@ -1100,7 +1118,7 @@ class _idat:
|
|||
class _fdat:
|
||||
# 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.chunk = chunk
|
||||
self.seq_num = seq_num
|
||||
|
@ -1110,7 +1128,21 @@ class _fdat:
|
|||
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")
|
||||
loop = im.encoderinfo.get("loop", im.info.get("loop", 0))
|
||||
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:
|
||||
chain = itertools.chain([im], append_images)
|
||||
|
||||
im_frames = []
|
||||
im_frames: list[_Frame] = []
|
||||
frame_count = 0
|
||||
for im_seq in chain:
|
||||
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:
|
||||
previous = im_frames[-1]
|
||||
prev_disposal = previous["encoderinfo"].get("disposal")
|
||||
prev_blend = previous["encoderinfo"].get("blend")
|
||||
prev_disposal = previous.encoderinfo.get("disposal")
|
||||
prev_blend = previous.encoderinfo.get("blend")
|
||||
if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2:
|
||||
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))
|
||||
bbox = previous["bbox"]
|
||||
bbox = previous.bbox
|
||||
if bbox:
|
||||
dispose = dispose.crop(bbox)
|
||||
else:
|
||||
bbox = (0, 0) + im.size
|
||||
base_im.paste(dispose, bbox)
|
||||
elif prev_disposal == Disposal.OP_PREVIOUS:
|
||||
base_im = im_frames[-2]["im"]
|
||||
base_im = im_frames[-2].im
|
||||
else:
|
||||
base_im = previous["im"]
|
||||
base_im = previous.im
|
||||
delta = ImageChops.subtract_modulo(
|
||||
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 "duration" in encoderinfo
|
||||
):
|
||||
previous["encoderinfo"]["duration"] += encoderinfo["duration"]
|
||||
previous.encoderinfo["duration"] += encoderinfo["duration"]
|
||||
continue
|
||||
else:
|
||||
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:
|
||||
return im_frames[0]["im"]
|
||||
return im_frames[0].im
|
||||
|
||||
# animation control
|
||||
chunk(
|
||||
|
@ -1195,14 +1227,14 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i
|
|||
|
||||
seq_num = 0
|
||||
for frame, frame_data in enumerate(im_frames):
|
||||
im_frame = frame_data["im"]
|
||||
if not frame_data["bbox"]:
|
||||
im_frame = frame_data.im
|
||||
if not frame_data.bbox:
|
||||
bbox = (0, 0) + im_frame.size
|
||||
else:
|
||||
bbox = frame_data["bbox"]
|
||||
bbox = frame_data.bbox
|
||||
im_frame = im_frame.crop(bbox)
|
||||
size = im_frame.size
|
||||
encoderinfo = frame_data["encoderinfo"]
|
||||
encoderinfo = frame_data.encoderinfo
|
||||
frame_duration = int(round(encoderinfo.get("duration", 0)))
|
||||
frame_disposal = encoderinfo.get("disposal", disposal)
|
||||
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)],
|
||||
)
|
||||
seq_num = fdat_chunks.seq_num
|
||||
return None
|
||||
|
||||
|
||||
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||
_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)
|
||||
|
||||
if save_all:
|
||||
|
@ -1419,12 +1458,15 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
|||
exif = exif[6:]
|
||||
chunk(fp, b"eXIf", exif)
|
||||
|
||||
single_im: Image.Image | None = im
|
||||
if save_all:
|
||||
im = _write_multiple_frames(
|
||||
single_im = _write_multiple_frames(
|
||||
im, fp, chunk, mode, rawmode, default_image, append_images
|
||||
)
|
||||
if im:
|
||||
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
|
||||
if single_im:
|
||||
ImageFile._save(
|
||||
single_im, _idat(fp, chunk), [("zip", (0, 0) + single_im.size, 0, rawmode)]
|
||||
)
|
||||
|
||||
if info:
|
||||
for info_chunk in info.chunks:
|
||||
|
@ -1445,32 +1487,26 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
|||
# 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."""
|
||||
from io import BytesIO
|
||||
|
||||
class collector:
|
||||
data = []
|
||||
chunks = []
|
||||
|
||||
def write(self, data: bytes) -> None:
|
||||
pass
|
||||
def append(fp: IO[bytes], cid: bytes, *data: bytes) -> None:
|
||||
byte_data = b"".join(data)
|
||||
crc = o32(_crc32(byte_data, _crc32(cid)))
|
||||
chunks.append((cid, byte_data, crc))
|
||||
|
||||
def append(self, chunk: bytes) -> None:
|
||||
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()
|
||||
fp = BytesIO()
|
||||
|
||||
try:
|
||||
im.encoderinfo = params
|
||||
_save(im, fp, None, append)
|
||||
_save(im, fp, "", append)
|
||||
finally:
|
||||
del im.encoderinfo
|
||||
|
||||
return fp.data
|
||||
return chunks
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -19,6 +19,7 @@ from __future__ import annotations
|
|||
|
||||
import io
|
||||
from functools import cached_property
|
||||
from typing import IO
|
||||
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i8
|
||||
|
@ -142,7 +143,9 @@ class PsdImageFile(ImageFile.ImageFile):
|
|||
self._min_frame = 1
|
||||
|
||||
@cached_property
|
||||
def layers(self):
|
||||
def layers(
|
||||
self,
|
||||
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
|
||||
layers = []
|
||||
if self._layers_position is not None:
|
||||
self._fp.seek(self._layers_position)
|
||||
|
@ -181,11 +184,13 @@ class PsdImageFile(ImageFile.ImageFile):
|
|||
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
|
||||
layers = []
|
||||
|
||||
def read(size):
|
||||
def read(size: int) -> bytes:
|
||||
return ImageFile._safe_read(fp, size)
|
||||
|
||||
ct = si16(read(2))
|
||||
|
@ -203,7 +208,7 @@ def _layerinfo(fp, ct_bytes):
|
|||
x1 = si32(read(4))
|
||||
|
||||
# image info
|
||||
mode = []
|
||||
bands = []
|
||||
ct_types = i16(read(2))
|
||||
if ct_types > 4:
|
||||
fp.seek(ct_types * 6 + 12, io.SEEK_CUR)
|
||||
|
@ -215,23 +220,23 @@ def _layerinfo(fp, ct_bytes):
|
|||
type = i16(read(2))
|
||||
|
||||
if type == 65535:
|
||||
m = "A"
|
||||
b = "A"
|
||||
else:
|
||||
m = "RGBA"[type]
|
||||
b = "RGBA"[type]
|
||||
|
||||
mode.append(m)
|
||||
bands.append(b)
|
||||
read(4) # size
|
||||
|
||||
# figure out the image mode
|
||||
mode.sort()
|
||||
if mode == ["R"]:
|
||||
bands.sort()
|
||||
if bands == ["R"]:
|
||||
mode = "L"
|
||||
elif mode == ["B", "G", "R"]:
|
||||
elif bands == ["B", "G", "R"]:
|
||||
mode = "RGB"
|
||||
elif mode == ["A", "B", "G", "R"]:
|
||||
elif bands == ["A", "B", "G", "R"]:
|
||||
mode = "RGBA"
|
||||
else:
|
||||
mode = None # unknown
|
||||
mode = "" # unknown
|
||||
|
||||
# skip over blend flags and extra information
|
||||
read(12) # filler
|
||||
|
@ -258,19 +263,22 @@ def _layerinfo(fp, ct_bytes):
|
|||
layers.append((name, mode, (x0, y0, x1, y1)))
|
||||
|
||||
# get tiles
|
||||
layerinfo = []
|
||||
for i, (name, mode, bbox) in enumerate(layers):
|
||||
tile = []
|
||||
for m in mode:
|
||||
t = _maketile(fp, m, bbox, 1)
|
||||
if 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):
|
||||
tile = None
|
||||
def _maketile(
|
||||
file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int
|
||||
) -> list[ImageFile._Tile] | None:
|
||||
tiles = None
|
||||
read = file.read
|
||||
|
||||
compression = i16(read(2))
|
||||
|
@ -283,26 +291,26 @@ def _maketile(file, mode, bbox, channels):
|
|||
if compression == 0:
|
||||
#
|
||||
# raw compression
|
||||
tile = []
|
||||
tiles = []
|
||||
for channel in range(channels):
|
||||
layer = mode[channel]
|
||||
if mode == "CMYK":
|
||||
layer += ";I"
|
||||
tile.append(("raw", bbox, offset, layer))
|
||||
tiles.append(ImageFile._Tile("raw", bbox, offset, layer))
|
||||
offset = offset + xsize * ysize
|
||||
|
||||
elif compression == 1:
|
||||
#
|
||||
# packbits compression
|
||||
i = 0
|
||||
tile = []
|
||||
tiles = []
|
||||
bytecount = read(channels * ysize * 2)
|
||||
offset = file.tell()
|
||||
for channel in range(channels):
|
||||
layer = mode[channel]
|
||||
if mode == "CMYK":
|
||||
layer += ";I"
|
||||
tile.append(("packbits", bbox, offset, layer))
|
||||
tiles.append(ImageFile._Tile("packbits", bbox, offset, layer))
|
||||
for y in range(ysize):
|
||||
offset = offset + i16(bytecount, i)
|
||||
i += 2
|
||||
|
@ -312,7 +320,7 @@ def _maketile(file, mode, bbox, channels):
|
|||
if offset & 1:
|
||||
read(1) # padding
|
||||
|
||||
return tile
|
||||
return tiles
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -55,13 +55,3 @@ class TarIO(ContainerIO.ContainerIO[bytes]):
|
|||
|
||||
# Open region
|
||||
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()
|
||||
|
|
|
@ -334,12 +334,13 @@ class IFDRational(Rational):
|
|||
|
||||
__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
|
||||
float/rational/other number, or an IFDRational
|
||||
:param denominator: Optional integer denominator
|
||||
"""
|
||||
self._val: Fraction | float
|
||||
if isinstance(value, IFDRational):
|
||||
self._numerator = value.numerator
|
||||
self._denominator = value.denominator
|
||||
|
@ -444,7 +445,7 @@ class IFDRational(Rational):
|
|||
__int__ = _delegate("__int__")
|
||||
|
||||
|
||||
def _register_loader(idx, size):
|
||||
def _register_loader(idx: int, size: int):
|
||||
def decorator(func):
|
||||
from .TiffTags import TYPES
|
||||
|
||||
|
@ -456,7 +457,7 @@ def _register_loader(idx, size):
|
|||
return decorator
|
||||
|
||||
|
||||
def _register_writer(idx):
|
||||
def _register_writer(idx: int):
|
||||
def decorator(func):
|
||||
_write_dispatch[idx] = func # noqa: F821
|
||||
return func
|
||||
|
@ -464,7 +465,7 @@ def _register_writer(idx):
|
|||
return decorator
|
||||
|
||||
|
||||
def _register_basic(idx_fmt_name):
|
||||
def _register_basic(idx_fmt_name: tuple[int, str, str]) -> None:
|
||||
from .TiffTags import TYPES
|
||||
|
||||
idx, fmt, name = idx_fmt_name
|
||||
|
@ -583,8 +584,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
self.tagtype: dict[int, int] = {}
|
||||
""" Dictionary of tag types """
|
||||
self.reset()
|
||||
(self.next,) = (
|
||||
self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:])
|
||||
self.next = (
|
||||
self._unpack("Q", ifh[8:])[0]
|
||||
if self._bigtiff
|
||||
else self._unpack("L", ifh[4:])[0]
|
||||
)
|
||||
self._legacy_api = False
|
||||
|
||||
|
@ -636,13 +639,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
val = (val,)
|
||||
return val
|
||||
|
||||
def __contains__(self, tag):
|
||||
def __contains__(self, tag: object) -> bool:
|
||||
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)
|
||||
|
||||
def _setitem(self, tag, value, legacy_api):
|
||||
def _setitem(self, tag: int, value, legacy_api: bool) -> None:
|
||||
basetypes = (Number, bytes, str)
|
||||
|
||||
info = TiffTags.lookup(tag, self.group)
|
||||
|
@ -730,10 +733,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
def __iter__(self):
|
||||
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)
|
||||
|
||||
def _pack(self, fmt, *values):
|
||||
def _pack(self, fmt: str, *values):
|
||||
return struct.pack(self._endian + fmt, *values)
|
||||
|
||||
list(
|
||||
|
@ -754,11 +757,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
)
|
||||
|
||||
@_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
|
||||
|
||||
@_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):
|
||||
data = int(data)
|
||||
if isinstance(data, int):
|
||||
|
@ -766,13 +769,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
return data
|
||||
|
||||
@_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"):
|
||||
data = data[:-1]
|
||||
return data.decode("latin-1", "replace")
|
||||
|
||||
@_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
|
||||
if isinstance(value, int):
|
||||
value = str(value)
|
||||
|
@ -781,7 +784,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
return value + b"\0"
|
||||
|
||||
@_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)
|
||||
|
||||
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]))
|
||||
|
||||
@_register_writer(5)
|
||||
def write_rational(self, *values):
|
||||
def write_rational(self, *values) -> bytes:
|
||||
return b"".join(
|
||||
self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values
|
||||
)
|
||||
|
||||
@_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
|
||||
|
||||
@_register_writer(7)
|
||||
def write_undefined(self, value):
|
||||
def write_undefined(self, value: bytes | int | IFDRational) -> bytes:
|
||||
if isinstance(value, IFDRational):
|
||||
value = int(value)
|
||||
if isinstance(value, int):
|
||||
|
@ -808,7 +811,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
return value
|
||||
|
||||
@_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)
|
||||
|
||||
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]))
|
||||
|
||||
@_register_writer(10)
|
||||
def write_signed_rational(self, *values):
|
||||
def write_signed_rational(self, *values) -> bytes:
|
||||
return b"".join(
|
||||
self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31)))
|
||||
for frac in values
|
||||
)
|
||||
|
||||
def _ensure_read(self, fp, size):
|
||||
def _ensure_read(self, fp: IO[bytes], size: int) -> bytes:
|
||||
ret = fp.read(size)
|
||||
if len(ret) != size:
|
||||
msg = (
|
||||
|
@ -977,7 +980,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
|
||||
return result
|
||||
|
||||
def save(self, fp):
|
||||
def save(self, fp: IO[bytes]) -> int:
|
||||
if fp.tell() == 0: # skip TIFF header on subsequent pages
|
||||
# tiff header -- PIL always starts the first IFD at offset 8
|
||||
fp.write(self._prefix + self._pack("HL", 42, 8))
|
||||
|
@ -1017,7 +1020,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
|||
.. deprecated:: 3.0.0
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self._legacy_api = True
|
||||
|
||||
|
@ -1029,7 +1032,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
|||
"""Dictionary of tag types"""
|
||||
|
||||
@classmethod
|
||||
def from_v2(cls, original):
|
||||
def from_v2(cls, original: ImageFileDirectory_v2) -> ImageFileDirectory_v1:
|
||||
"""Returns an
|
||||
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
|
||||
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)
|
||||
return ifd
|
||||
|
||||
def __contains__(self, tag):
|
||||
def __contains__(self, tag: object) -> bool:
|
||||
return tag in self._tags_v1 or tag in self._tagdata
|
||||
|
||||
def __len__(self) -> int:
|
||||
|
@ -1072,7 +1075,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
|||
def __iter__(self):
|
||||
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):
|
||||
self._setitem(tag, value, legacy_api)
|
||||
|
||||
|
@ -1122,7 +1125,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
self.tag_v2 = ImageFileDirectory_v2(ifh)
|
||||
|
||||
# legacy IFD entries will be filled in later
|
||||
self.ifd = None
|
||||
self.ifd: ImageFileDirectory_v1 | None = None
|
||||
|
||||
# setup frame pointers
|
||||
self.__first = self.__next = self.tag_v2.next
|
||||
|
@ -1139,13 +1142,15 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
self._seek(0)
|
||||
|
||||
@property
|
||||
def n_frames(self):
|
||||
if self._n_frames is None:
|
||||
def n_frames(self) -> int:
|
||||
current_n_frames = self._n_frames
|
||||
if current_n_frames is None:
|
||||
current = self.tell()
|
||||
self._seek(len(self._frame_pos))
|
||||
while self._n_frames is None:
|
||||
self._seek(self.tell() + 1)
|
||||
self.seek(current)
|
||||
assert self._n_frames is not None
|
||||
return self._n_frames
|
||||
|
||||
def seek(self, frame: int) -> None:
|
||||
|
@ -1211,7 +1216,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
"""Return the current frame number"""
|
||||
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".
|
||||
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:
|
||||
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
|
||||
Calls out to libtiff"""
|
||||
|
||||
|
@ -1343,7 +1348,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
|
||||
return Image.Image.load(self)
|
||||
|
||||
def _setup(self):
|
||||
def _setup(self) -> None:
|
||||
"""Setup this image object based on current tags"""
|
||||
|
||||
if 0xBC01 in self.tag_v2:
|
||||
|
@ -1537,13 +1542,13 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
# adjust stride width accordingly
|
||||
stride /= bps_count
|
||||
|
||||
a = (tile_rawmode, int(stride), 1)
|
||||
args = (tile_rawmode, int(stride), 1)
|
||||
self.tile.append(
|
||||
(
|
||||
self._compression,
|
||||
(x, y, min(x + w, xsize), min(y + h, ysize)),
|
||||
offset,
|
||||
a,
|
||||
args,
|
||||
)
|
||||
)
|
||||
x = x + w
|
||||
|
@ -1938,7 +1943,7 @@ class AppendingTiffWriter:
|
|||
521, # JPEGACTables
|
||||
}
|
||||
|
||||
def __init__(self, fn, new=False):
|
||||
def __init__(self, fn, new: bool = False) -> None:
|
||||
if hasattr(fn, "read"):
|
||||
self.f = fn
|
||||
self.close_fp = False
|
||||
|
@ -2015,7 +2020,7 @@ class AppendingTiffWriter:
|
|||
def tell(self) -> int:
|
||||
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:
|
||||
offset += self.offsetOfNewPage
|
||||
|
||||
|
|
|
@ -32,17 +32,24 @@ class _TagInfo(NamedTuple):
|
|||
class TagInfo(_TagInfo):
|
||||
__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 {})
|
||||
|
||||
def cvt_enum(self, value):
|
||||
def cvt_enum(self, value: str) -> int | str:
|
||||
# Using get will call hash(value), which can be expensive
|
||||
# for some types (e.g. Fraction). Since self.enum is rarely
|
||||
# used, it's usually better to test it first.
|
||||
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 group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in
|
||||
|
@ -89,7 +96,7 @@ DOUBLE = 12
|
|||
IFD = 13
|
||||
LONG8 = 16
|
||||
|
||||
_tags_v2 = {
|
||||
_tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]] = {
|
||||
254: ("NewSubfileType", LONG, 1),
|
||||
255: ("SubfileType", SHORT, 1),
|
||||
256: ("ImageWidth", LONG, 1),
|
||||
|
@ -233,7 +240,7 @@ _tags_v2 = {
|
|||
50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one
|
||||
50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006
|
||||
}
|
||||
TAGS_V2_GROUPS = {
|
||||
_tags_v2_groups = {
|
||||
# ExifIFD
|
||||
34665: {
|
||||
36864: ("ExifVersion", UNDEFINED, 1),
|
||||
|
@ -281,7 +288,7 @@ TAGS_V2_GROUPS = {
|
|||
|
||||
# Legacy Tags structure
|
||||
# these tags aren't included above, but were in the previous versions
|
||||
TAGS = {
|
||||
TAGS: dict[int | tuple[int, int], str] = {
|
||||
347: "JPEGTables",
|
||||
700: "XMP",
|
||||
# Additional Exif Info
|
||||
|
@ -426,9 +433,10 @@ TAGS = {
|
|||
}
|
||||
|
||||
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():
|
||||
# Populate legacy structure.
|
||||
TAGS[k] = v[0]
|
||||
|
@ -438,9 +446,8 @@ def _populate():
|
|||
|
||||
TAGS_V2[k] = TagInfo(k, *v)
|
||||
|
||||
for tags in TAGS_V2_GROUPS.values():
|
||||
for k, v in tags.items():
|
||||
tags[k] = TagInfo(k, *v)
|
||||
for group, tags in _tags_v2_groups.items():
|
||||
TAGS_V2_GROUPS[group] = {k: TagInfo(k, *v) for k, v in tags.items()}
|
||||
|
||||
|
||||
_populate()
|
||||
|
|
|
@ -24,8 +24,11 @@ and has been tested with a few sample files found using google.
|
|||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import IO
|
||||
|
||||
from . import Image, ImageFile
|
||||
from ._binary import i32le as i32
|
||||
from ._typing import StrOrBytesPath
|
||||
|
||||
|
||||
class WalImageFile(ImageFile.ImageFile):
|
||||
|
@ -58,7 +61,7 @@ class WalImageFile(ImageFile.ImageFile):
|
|||
return Image.Image.load(self)
|
||||
|
||||
|
||||
def open(filename):
|
||||
def open(filename: StrOrBytesPath | IO[bytes]) -> WalImageFile:
|
||||
"""
|
||||
Load texture from a Quake2 WAL texture file.
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
|||
# Initialize seek state
|
||||
self._reset(reset=False)
|
||||
|
||||
def _getexif(self) -> dict[str, Any] | None:
|
||||
def _getexif(self) -> dict[int, Any] | None:
|
||||
if "exif" not in self.info:
|
||||
return None
|
||||
return self.getexif()._get_merged_dict()
|
||||
|
@ -115,7 +115,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
|||
self.__loaded = -1
|
||||
self.__timestamp = 0
|
||||
|
||||
def _get_next(self):
|
||||
def _get_next(self) -> tuple[bytes, int, int]:
|
||||
# Get next frame
|
||||
ret = self._decoder.get_next()
|
||||
self.__physical_frame += 1
|
||||
|
|
|
@ -152,7 +152,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
|||
def _load(self) -> ImageFile.StubHandler | None:
|
||||
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:
|
||||
self.info["dpi"] = dpi
|
||||
x0, y0, x1, y1 = self.info["wmf_bbox"]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Any
|
||||
|
||||
class ImagingCore:
|
||||
def __getitem__(self, index: int) -> float: ...
|
||||
def __getattr__(self, name: str) -> Any: ...
|
||||
|
||||
class ImagingFont:
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
from typing import Any, TypedDict
|
||||
from typing import Any, Callable
|
||||
|
||||
from . import _imaging
|
||||
|
||||
class _Axis(TypedDict):
|
||||
minimum: int | None
|
||||
default: int | None
|
||||
maximum: int | None
|
||||
name: bytes | None
|
||||
from . import ImageFont, _imaging
|
||||
|
||||
class Font:
|
||||
@property
|
||||
|
@ -28,42 +22,48 @@ class Font:
|
|||
def render(
|
||||
self,
|
||||
string: str | bytes,
|
||||
fill,
|
||||
mode=...,
|
||||
dir=...,
|
||||
features=...,
|
||||
lang=...,
|
||||
stroke_width=...,
|
||||
anchor=...,
|
||||
foreground_ink_long=...,
|
||||
x_start=...,
|
||||
y_start=...,
|
||||
fill: Callable[[int, int], _imaging.ImagingCore],
|
||||
mode: str,
|
||||
dir: str | None,
|
||||
features: list[str] | None,
|
||||
lang: str | None,
|
||||
stroke_width: float,
|
||||
anchor: str | None,
|
||||
foreground_ink_long: int,
|
||||
x_start: float,
|
||||
y_start: float,
|
||||
/,
|
||||
) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
|
||||
def getsize(
|
||||
self,
|
||||
string: str | bytes | bytearray,
|
||||
mode=...,
|
||||
dir=...,
|
||||
features=...,
|
||||
lang=...,
|
||||
anchor=...,
|
||||
mode: str,
|
||||
dir: str | None,
|
||||
features: list[str] | None,
|
||||
lang: str | None,
|
||||
anchor: str | None,
|
||||
/,
|
||||
) -> tuple[tuple[int, int], tuple[int, int]]: ...
|
||||
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: ...
|
||||
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 setvaraxes(self, axes: list[float], /) -> None: ...
|
||||
|
||||
def getfont(
|
||||
filename: str | bytes,
|
||||
size: float,
|
||||
index=...,
|
||||
encoding=...,
|
||||
font_bytes=...,
|
||||
layout_engine=...,
|
||||
index: int,
|
||||
encoding: str,
|
||||
font_bytes: bytes,
|
||||
layout_engine: int,
|
||||
) -> Font: ...
|
||||
def __getattr__(name: str) -> Any: ...
|
||||
|
|
3
src/PIL/_imagingtk.pyi
Normal file
|
@ -0,0 +1,3 @@
|
|||
from typing import Any
|
||||
|
||||
def __getattr__(name: str) -> Any: ...
|
|
@ -10,11 +10,6 @@ def is_path(f: Any) -> TypeGuard[StrOrBytesPath]:
|
|||
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:
|
||||
def __init__(self, ex: BaseException):
|
||||
self.ex = ex
|
||||
|
|
|
@ -80,7 +80,8 @@ typedef struct Tcl_Command_ *Tcl_Command;
|
|||
typedef void *ClientData;
|
||||
|
||||
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);
|
||||
|
||||
/* Typedefs derived from function signatures in Tcl header */
|
||||
|
@ -90,7 +91,8 @@ typedef Tcl_Command (*Tcl_CreateCommand_t)(
|
|||
const char *cmdName,
|
||||
Tcl_CmdProc *proc,
|
||||
ClientData clientData,
|
||||
Tcl_CmdDeleteProc *deleteProc);
|
||||
Tcl_CmdDeleteProc *deleteProc
|
||||
);
|
||||
/* Tcl_AppendResult */
|
||||
typedef void (*Tcl_AppendResult_t)(Tcl_Interp *interp, ...);
|
||||
|
||||
|
@ -127,7 +129,8 @@ typedef int (*Tk_PhotoPutBlock_t)(
|
|||
int y,
|
||||
int width,
|
||||
int height,
|
||||
int compRule);
|
||||
int compRule
|
||||
);
|
||||
/* Tk_FindPhoto */
|
||||
typedef Tk_PhotoHandle (*Tk_FindPhoto_t)(Tcl_Interp *interp, const char *imageName);
|
||||
/* Tk_PhotoGetImage */
|
||||
|
|
|
@ -73,14 +73,16 @@ ImagingFind(const char *name) {
|
|||
|
||||
static int
|
||||
PyImagingPhotoPut(
|
||||
ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) {
|
||||
ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv
|
||||
) {
|
||||
Imaging im;
|
||||
Tk_PhotoHandle photo;
|
||||
Tk_PhotoImageBlock block;
|
||||
|
||||
if (argc != 3) {
|
||||
TCL_APPEND_RESULT(
|
||||
interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL);
|
||||
interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL
|
||||
);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
|
@ -128,14 +130,16 @@ PyImagingPhotoPut(
|
|||
block.pixelPtr = (unsigned char *)im->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;
|
||||
}
|
||||
|
||||
static int
|
||||
PyImagingPhotoGet(
|
||||
ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) {
|
||||
ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv
|
||||
) {
|
||||
Imaging im;
|
||||
Tk_PhotoHandle photo;
|
||||
Tk_PhotoImageBlock block;
|
||||
|
@ -143,7 +147,8 @@ PyImagingPhotoGet(
|
|||
|
||||
if (argc != 3) {
|
||||
TCL_APPEND_RESULT(
|
||||
interp, "usage: ", argv[0], " srcPhoto destImage", (char *)NULL);
|
||||
interp, "usage: ", argv[0], " srcPhoto destImage", (char *)NULL
|
||||
);
|
||||
return TCL_ERROR;
|
||||
}
|
||||
|
||||
|
@ -183,13 +188,15 @@ TkImaging_Init(Tcl_Interp *interp) {
|
|||
"PyImagingPhoto",
|
||||
PyImagingPhotoPut,
|
||||
(ClientData)0,
|
||||
(Tcl_CmdDeleteProc *)NULL);
|
||||
(Tcl_CmdDeleteProc *)NULL
|
||||
);
|
||||
TCL_CREATE_COMMAND(
|
||||
interp,
|
||||
"PyImagingPhotoGet",
|
||||
PyImagingPhotoGet,
|
||||
(ClientData)0,
|
||||
(Tcl_CmdDeleteProc *)NULL);
|
||||
(Tcl_CmdDeleteProc *)NULL
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -394,7 +401,8 @@ _func_loader(void *lib) {
|
|||
}
|
||||
return (
|
||||
(TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)_dfunc(lib, "Tk_PhotoPutBlock")) ==
|
||||
NULL);
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
int
|
||||
|
|
184
src/_imaging.c
|
@ -290,7 +290,8 @@ ImagingError_ModeError(void) {
|
|||
void *
|
||||
ImagingError_ValueError(const char *message) {
|
||||
PyErr_SetString(
|
||||
PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value");
|
||||
PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value"
|
||||
);
|
||||
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]);
|
||||
case 4:
|
||||
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;
|
||||
case IMAGING_TYPE_INT32:
|
||||
|
@ -518,7 +520,8 @@ getink(PyObject *color, Imaging im, char *ink) {
|
|||
rIsInt = 1;
|
||||
} else if (im->bands == 1) {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError, "color must be int or single-element tuple");
|
||||
PyExc_TypeError, "color must be int or single-element tuple"
|
||||
);
|
||||
return NULL;
|
||||
} else if (tupleSize == -1) {
|
||||
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 (tupleSize != 1) {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
"color must be int or single-element tuple");
|
||||
PyExc_TypeError, "color must be int or single-element tuple"
|
||||
);
|
||||
return NULL;
|
||||
} else if (!PyArg_ParseTuple(color, "L", &r)) {
|
||||
return NULL;
|
||||
|
@ -556,7 +559,8 @@ getink(PyObject *color, Imaging im, char *ink) {
|
|||
if (tupleSize != 1 && tupleSize != 2) {
|
||||
PyErr_SetString(
|
||||
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;
|
||||
} else if (!PyArg_ParseTuple(color, "L|i", &r, &a)) {
|
||||
return NULL;
|
||||
|
@ -567,7 +571,8 @@ getink(PyObject *color, Imaging im, char *ink) {
|
|||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
"color must be int, or tuple of one, three or four "
|
||||
"elements");
|
||||
"elements"
|
||||
);
|
||||
return NULL;
|
||||
} else if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) {
|
||||
return NULL;
|
||||
|
@ -608,7 +613,8 @@ getink(PyObject *color, Imaging im, char *ink) {
|
|||
} else if (tupleSize != 3) {
|
||||
PyErr_SetString(
|
||||
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;
|
||||
} else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
|
||||
return NULL;
|
||||
|
@ -733,7 +739,8 @@ _alpha_composite(ImagingObject *self, PyObject *args) {
|
|||
ImagingObject *imagep2;
|
||||
|
||||
if (!PyArg_ParseTuple(
|
||||
args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) {
|
||||
args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -748,7 +755,8 @@ _blend(ImagingObject *self, PyObject *args) {
|
|||
|
||||
alpha = 0.5;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -827,7 +835,8 @@ _prepare_lut_table(PyObject *table, Py_ssize_t table_size) {
|
|||
break;
|
||||
case TYPE_FLOAT32:
|
||||
memcpy(
|
||||
&item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32));
|
||||
&item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32)
|
||||
);
|
||||
break;
|
||||
case TYPE_DOUBLE:
|
||||
memcpy(&dtmp, ((char *)table_data) + i * sizeof(dtmp), sizeof(dtmp));
|
||||
|
@ -878,7 +887,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
|
|||
&size1D,
|
||||
&size2D,
|
||||
&size3D,
|
||||
&table)) {
|
||||
&table
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -896,7 +906,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
|
|||
if (2 > size1D || size1D > 65 || 2 > size2D || size2D > 65 || 2 > size3D ||
|
||||
size3D > 65) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -913,13 +924,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
|
|||
}
|
||||
|
||||
if (!ImagingColorLUT3D_linear(
|
||||
imOut,
|
||||
self->image,
|
||||
table_channels,
|
||||
size1D,
|
||||
size2D,
|
||||
size3D,
|
||||
prepared_table)) {
|
||||
imOut, self->image, table_channels, size1D, size2D, size3D, prepared_table
|
||||
)) {
|
||||
free(prepared_table);
|
||||
ImagingDelete(imOut);
|
||||
return NULL;
|
||||
|
@ -943,7 +949,8 @@ _convert(ImagingObject *self, PyObject *args) {
|
|||
if (!PyImaging_Check(paletteimage)) {
|
||||
PyObject_Print((PyObject *)paletteimage, stderr, 0);
|
||||
PyErr_SetString(
|
||||
PyExc_ValueError, "palette argument must be image with mode 'P'");
|
||||
PyExc_ValueError, "palette argument must be image with mode 'P'"
|
||||
);
|
||||
return NULL;
|
||||
}
|
||||
if (paletteimage->image->palette == NULL) {
|
||||
|
@ -953,7 +960,8 @@ _convert(ImagingObject *self, PyObject *args) {
|
|||
}
|
||||
|
||||
return PyImagingNew(ImagingConvert(
|
||||
self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither));
|
||||
self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither
|
||||
));
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
|
@ -961,7 +969,8 @@ _convert2(ImagingObject *self, PyObject *args) {
|
|||
ImagingObject *imagep1;
|
||||
ImagingObject *imagep2;
|
||||
if (!PyArg_ParseTuple(
|
||||
args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) {
|
||||
args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -994,7 +1003,8 @@ _convert_matrix(ImagingObject *self, PyObject *args) {
|
|||
m + 8,
|
||||
m + 9,
|
||||
m + 10,
|
||||
m + 11)) {
|
||||
m + 11
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
@ -1055,7 +1065,8 @@ _filter(ImagingObject *self, PyObject *args) {
|
|||
float divisor, offset;
|
||||
PyObject *kernel = NULL;
|
||||
if (!PyArg_ParseTuple(
|
||||
args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel)) {
|
||||
args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -1138,7 +1149,8 @@ _getpalette(ImagingObject *self, PyObject *args) {
|
|||
}
|
||||
|
||||
pack(
|
||||
(UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize);
|
||||
(UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize
|
||||
);
|
||||
|
||||
return palette;
|
||||
}
|
||||
|
@ -1232,7 +1244,8 @@ union hist_extrema {
|
|||
|
||||
static union hist_extrema *
|
||||
parse_histogram_extremap(
|
||||
ImagingObject *self, PyObject *extremap, union hist_extrema *ep) {
|
||||
ImagingObject *self, PyObject *extremap, union hist_extrema *ep
|
||||
) {
|
||||
int i0, i1;
|
||||
double f0, f1;
|
||||
|
||||
|
@ -1392,7 +1405,8 @@ _paste(ImagingObject *self, PyObject *args) {
|
|||
int x0, y0, x1, y1;
|
||||
ImagingObject *maskp = NULL;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1404,14 +1418,16 @@ _paste(ImagingObject *self, PyObject *args) {
|
|||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1);
|
||||
y1
|
||||
);
|
||||
|
||||
} else {
|
||||
if (!getink(source, self->image, ink)) {
|
||||
return NULL;
|
||||
}
|
||||
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) {
|
||||
|
@ -1729,7 +1745,8 @@ _putpalette(ImagingObject *self, PyObject *args) {
|
|||
UINT8 *palette;
|
||||
Py_ssize_t palettesize;
|
||||
if (!PyArg_ParseTuple(
|
||||
args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize)) {
|
||||
args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -1887,7 +1904,8 @@ _resize(ImagingObject *self, PyObject *args) {
|
|||
&box[0],
|
||||
&box[1],
|
||||
&box[2],
|
||||
&box[3])) {
|
||||
&box[3]
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -1923,7 +1941,8 @@ _resize(ImagingObject *self, PyObject *args) {
|
|||
imOut = ImagingNewDirty(imIn->mode, xsize, ysize);
|
||||
|
||||
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 {
|
||||
imOut = ImagingResample(imIn, xsize, ysize, filter, box);
|
||||
}
|
||||
|
@ -1944,14 +1963,8 @@ _reduce(ImagingObject *self, PyObject *args) {
|
|||
box[3] = imIn->ysize;
|
||||
|
||||
if (!PyArg_ParseTuple(
|
||||
args,
|
||||
"(ii)|(iiii)",
|
||||
&xscale,
|
||||
&yscale,
|
||||
&box[0],
|
||||
&box[1],
|
||||
&box[2],
|
||||
&box[3])) {
|
||||
args, "(ii)|(iiii)", &xscale, &yscale, &box[0], &box[1], &box[2], &box[3]
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -2053,7 +2066,8 @@ _transform(ImagingObject *self, PyObject *args) {
|
|||
&method,
|
||||
&data,
|
||||
&filter,
|
||||
&fill)) {
|
||||
&fill
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -2077,7 +2091,8 @@ _transform(ImagingObject *self, PyObject *args) {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -2250,7 +2265,13 @@ _getcolors(ImagingObject *self, PyObject *args) {
|
|||
for (i = 0; i < colors; i++) {
|
||||
ImagingColorItem *v = &items[i];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -2311,14 +2332,16 @@ _getprojection(ImagingObject *self) {
|
|||
}
|
||||
|
||||
ImagingGetProjection(
|
||||
self->image, (unsigned char *)xprofile, (unsigned char *)yprofile);
|
||||
self->image, (unsigned char *)xprofile, (unsigned char *)yprofile
|
||||
);
|
||||
|
||||
result = Py_BuildValue(
|
||||
"y#y#",
|
||||
xprofile,
|
||||
(Py_ssize_t)self->image->xsize,
|
||||
yprofile,
|
||||
(Py_ssize_t)self->image->ysize);
|
||||
(Py_ssize_t)self->image->ysize
|
||||
);
|
||||
|
||||
free(xprofile);
|
||||
free(yprofile);
|
||||
|
@ -2392,7 +2415,8 @@ _merge(PyObject *self, PyObject *args) {
|
|||
&Imaging_Type,
|
||||
&band2,
|
||||
&Imaging_Type,
|
||||
&band3)) {
|
||||
&band3
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -2638,7 +2662,8 @@ _font_new(PyObject *self_, PyObject *args) {
|
|||
unsigned char *glyphdata;
|
||||
Py_ssize_t glyphdata_length;
|
||||
if (!PyArg_ParseTuple(
|
||||
args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length)) {
|
||||
args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -2796,7 +2821,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
|||
if (i == 0 || text[i] != text[i - 1]) {
|
||||
ImagingDelete(bitmap);
|
||||
bitmap = ImagingCrop(
|
||||
self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
|
||||
self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1
|
||||
);
|
||||
if (!bitmap) {
|
||||
goto failed;
|
||||
}
|
||||
|
@ -2808,7 +2834,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
|||
glyph->dx0 + x,
|
||||
glyph->dy0 + b,
|
||||
glyph->dx1 + x,
|
||||
glyph->dy1 + b);
|
||||
glyph->dy1 + b
|
||||
);
|
||||
if (status < 0) {
|
||||
goto failed;
|
||||
}
|
||||
|
@ -2947,7 +2974,8 @@ _draw_arc(ImagingDrawObject *self, PyObject *args) {
|
|||
end,
|
||||
&ink,
|
||||
width,
|
||||
self->blend);
|
||||
self->blend
|
||||
);
|
||||
|
||||
free(xy);
|
||||
|
||||
|
@ -2977,13 +3005,15 @@ _draw_bitmap(ImagingDrawObject *self, PyObject *args) {
|
|||
}
|
||||
if (n != 1) {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError, "coordinate list must contain exactly 1 coordinate");
|
||||
PyExc_TypeError, "coordinate list must contain exactly 1 coordinate"
|
||||
);
|
||||
free(xy);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -3039,7 +3069,8 @@ _draw_chord(ImagingDrawObject *self, PyObject *args) {
|
|||
&ink,
|
||||
fill,
|
||||
width,
|
||||
self->blend);
|
||||
self->blend
|
||||
);
|
||||
|
||||
free(xy);
|
||||
|
||||
|
@ -3093,7 +3124,8 @@ _draw_ellipse(ImagingDrawObject *self, PyObject *args) {
|
|||
&ink,
|
||||
fill,
|
||||
width,
|
||||
self->blend);
|
||||
self->blend
|
||||
);
|
||||
|
||||
free(xy);
|
||||
|
||||
|
@ -3133,14 +3165,16 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
|
|||
(int)p[2],
|
||||
(int)p[3],
|
||||
&ink,
|
||||
self->blend) < 0) {
|
||||
self->blend
|
||||
) < 0) {
|
||||
free(xy);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (p) { /* draw last point */
|
||||
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 {
|
||||
for (i = 0; i < n - 1; i++) {
|
||||
|
@ -3153,7 +3187,8 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
|
|||
(int)p[3],
|
||||
&ink,
|
||||
width,
|
||||
self->blend) < 0) {
|
||||
self->blend
|
||||
) < 0) {
|
||||
free(xy);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -3185,7 +3220,8 @@ _draw_points(ImagingDrawObject *self, PyObject *args) {
|
|||
for (i = 0; i < n; i++) {
|
||||
double *p = &xy[i + i];
|
||||
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);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -3274,7 +3310,8 @@ _draw_pieslice(ImagingDrawObject *self, PyObject *args) {
|
|||
&ink,
|
||||
fill,
|
||||
width,
|
||||
self->blend);
|
||||
self->blend
|
||||
);
|
||||
|
||||
free(xy);
|
||||
|
||||
|
@ -3306,7 +3343,8 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
|
|||
}
|
||||
if (n < 2) {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError, "coordinate list must contain at least 2 coordinates");
|
||||
PyExc_TypeError, "coordinate list must contain at least 2 coordinates"
|
||||
);
|
||||
free(xy);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -3379,7 +3417,8 @@ _draw_rectangle(ImagingDrawObject *self, PyObject *args) {
|
|||
&ink,
|
||||
fill,
|
||||
width,
|
||||
self->blend);
|
||||
self->blend
|
||||
);
|
||||
|
||||
free(xy);
|
||||
|
||||
|
@ -3519,7 +3558,8 @@ _effect_mandelbrot(ImagingObject *self, PyObject *args) {
|
|||
&extent[1],
|
||||
&extent[2],
|
||||
&extent[3],
|
||||
&quality)) {
|
||||
&quality
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -3747,7 +3787,8 @@ _getattr_unsafe_ptrs(ImagingObject *self, void *closure) {
|
|||
"image32",
|
||||
self->image->image32,
|
||||
"image",
|
||||
self->image->image);
|
||||
self->image->image
|
||||
);
|
||||
}
|
||||
|
||||
static struct PyGetSetDef getsetters[] = {
|
||||
|
@ -3757,7 +3798,8 @@ static struct PyGetSetDef getsetters[] = {
|
|||
{"id", (getter)_getattr_id},
|
||||
{"ptr", (getter)_getattr_ptr},
|
||||
{"unsafe_ptrs", (getter)_getattr_unsafe_ptrs},
|
||||
{NULL}};
|
||||
{NULL}
|
||||
};
|
||||
|
||||
/* basic sequence semantics */
|
||||
|
||||
|
@ -4066,9 +4108,8 @@ _set_blocks_max(PyObject *self, PyObject *args) {
|
|||
if (blocks_max < 0) {
|
||||
PyErr_SetString(PyExc_ValueError, "blocks_max should be greater than 0");
|
||||
return NULL;
|
||||
} else if (
|
||||
(unsigned long)blocks_max >
|
||||
SIZE_MAX / sizeof(ImagingDefaultArena.blocks_pool[0])) {
|
||||
} else if ((unsigned long)blocks_max >
|
||||
SIZE_MAX / sizeof(ImagingDefaultArena.blocks_pool[0])) {
|
||||
PyErr_SetString(PyExc_ValueError, "blocks_max is too large");
|
||||
return NULL;
|
||||
}
|
||||
|
@ -4423,7 +4464,8 @@ setup_module(PyObject *m) {
|
|||
|
||||
PyObject *pillow_version = PyUnicode_FromString(version);
|
||||
PyDict_SetItemString(
|
||||
d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None);
|
||||
d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None
|
||||
);
|
||||
Py_XDECREF(pillow_version);
|
||||
|
||||
return 0;
|
||||
|
@ -4448,5 +4490,9 @@ PyInit__imaging(void) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||
#endif
|
||||
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -331,7 +331,8 @@ pyCMScopyAux(cmsHTRANSFORM hTransform, Imaging imDst, const Imaging imSrc) {
|
|||
memcpy(
|
||||
pDstExtras + x * dstChunkSize,
|
||||
pSrcExtras + x * srcChunkSize,
|
||||
channelSize);
|
||||
channelSize
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -373,7 +374,8 @@ _buildTransform(
|
|||
char *sInMode,
|
||||
char *sOutMode,
|
||||
int iRenderingIntent,
|
||||
cmsUInt32Number cmsFLAGS) {
|
||||
cmsUInt32Number cmsFLAGS
|
||||
) {
|
||||
cmsHTRANSFORM hTransform;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
|
@ -385,7 +387,8 @@ _buildTransform(
|
|||
hOutputProfile,
|
||||
findLCMStype(sOutMode),
|
||||
iRenderingIntent,
|
||||
cmsFLAGS);
|
||||
cmsFLAGS
|
||||
);
|
||||
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
|
@ -405,7 +408,8 @@ _buildProofTransform(
|
|||
char *sOutMode,
|
||||
int iRenderingIntent,
|
||||
int iProofIntent,
|
||||
cmsUInt32Number cmsFLAGS) {
|
||||
cmsUInt32Number cmsFLAGS
|
||||
) {
|
||||
cmsHTRANSFORM hTransform;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
|
@ -419,7 +423,8 @@ _buildProofTransform(
|
|||
hProofProfile,
|
||||
iRenderingIntent,
|
||||
iProofIntent,
|
||||
cmsFLAGS);
|
||||
cmsFLAGS
|
||||
);
|
||||
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
|
@ -454,7 +459,8 @@ buildTransform(PyObject *self, PyObject *args) {
|
|||
&sInMode,
|
||||
&sOutMode,
|
||||
&iRenderingIntent,
|
||||
&cmsFLAGS)) {
|
||||
&cmsFLAGS
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -464,7 +470,8 @@ buildTransform(PyObject *self, PyObject *args) {
|
|||
sInMode,
|
||||
sOutMode,
|
||||
iRenderingIntent,
|
||||
cmsFLAGS);
|
||||
cmsFLAGS
|
||||
);
|
||||
|
||||
if (!transform) {
|
||||
return NULL;
|
||||
|
@ -499,7 +506,8 @@ buildProofTransform(PyObject *self, PyObject *args) {
|
|||
&sOutMode,
|
||||
&iRenderingIntent,
|
||||
&iProofIntent,
|
||||
&cmsFLAGS)) {
|
||||
&cmsFLAGS
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -511,7 +519,8 @@ buildProofTransform(PyObject *self, PyObject *args) {
|
|||
sOutMode,
|
||||
iRenderingIntent,
|
||||
iProofIntent,
|
||||
cmsFLAGS);
|
||||
cmsFLAGS
|
||||
);
|
||||
|
||||
if (!transform) {
|
||||
return NULL;
|
||||
|
@ -563,7 +572,8 @@ createProfile(PyObject *self, PyObject *args) {
|
|||
PyErr_SetString(
|
||||
PyExc_ValueError,
|
||||
"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;
|
||||
}
|
||||
hProfile = cmsCreateLab2Profile(&whitePoint);
|
||||
|
@ -624,7 +634,8 @@ cms_get_display_profile_win32(PyObject *self, PyObject *args) {
|
|||
HANDLE handle = 0;
|
||||
int is_dc = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -729,7 +740,8 @@ _xyz_py(cmsCIEXYZ *XYZ) {
|
|||
cmsCIExyY xyY;
|
||||
cmsXYZ2xyY(&xyY, XYZ);
|
||||
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 *
|
||||
|
@ -758,7 +770,8 @@ _xyz3_py(cmsCIEXYZ *XYZ) {
|
|||
xyY[1].Y,
|
||||
xyY[2].x,
|
||||
xyY[2].y,
|
||||
xyY[2].Y);
|
||||
xyY[2].Y
|
||||
);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
|
@ -809,7 +822,8 @@ _profile_read_ciexyy_triple(CmsProfileObject *self, cmsTagSignature info) {
|
|||
triple->Green.Y,
|
||||
triple->Blue.x,
|
||||
triple->Blue.y,
|
||||
triple->Blue.Y);
|
||||
triple->Blue.Y
|
||||
);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
|
@ -873,7 +887,8 @@ _calculate_rgb_primaries(CmsProfileObject *self, cmsCIEXYZTRIPLE *result) {
|
|||
hXYZ,
|
||||
TYPE_XYZ_DBL,
|
||||
INTENT_RELATIVE_COLORIMETRIC,
|
||||
cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
|
||||
cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE
|
||||
);
|
||||
cmsCloseProfile(hXYZ);
|
||||
if (hTransform == NULL) {
|
||||
return 0;
|
||||
|
@ -889,7 +904,8 @@ _check_intent(
|
|||
int clut,
|
||||
cmsHPROFILE hProfile,
|
||||
cmsUInt32Number Intent,
|
||||
cmsUInt32Number UsedDirection) {
|
||||
cmsUInt32Number UsedDirection
|
||||
) {
|
||||
if (clut) {
|
||||
return cmsIsCLUT(hProfile, Intent, UsedDirection);
|
||||
} else {
|
||||
|
@ -934,7 +950,8 @@ _is_intent_supported(CmsProfileObject *self, int clut) {
|
|||
_check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True
|
||||
: Py_False,
|
||||
_check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True
|
||||
: Py_False);
|
||||
: Py_False
|
||||
);
|
||||
if (id == NULL || entry == NULL) {
|
||||
Py_XDECREF(id);
|
||||
Py_XDECREF(entry);
|
||||
|
@ -968,7 +985,8 @@ static PyMethodDef pyCMSdll_methods[] = {
|
|||
{"get_display_profile_win32", cms_get_display_profile_win32, METH_VARARGS},
|
||||
#endif
|
||||
|
||||
{NULL, NULL}};
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
static struct PyMethodDef cms_profile_methods[] = {
|
||||
{"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(
|
||||
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 *
|
||||
|
@ -1106,13 +1125,15 @@ cms_profile_getattr_colorimetric_intent(CmsProfileObject *self, void *closure) {
|
|||
|
||||
static PyObject *
|
||||
cms_profile_getattr_perceptual_rendering_intent_gamut(
|
||||
CmsProfileObject *self, void *closure) {
|
||||
CmsProfileObject *self, void *closure
|
||||
) {
|
||||
return _profile_read_signature(self, cmsSigPerceptualRenderingIntentGamutTag);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
cms_profile_getattr_saturation_rendering_intent_gamut(
|
||||
CmsProfileObject *self, void *closure) {
|
||||
CmsProfileObject *self, void *closure
|
||||
) {
|
||||
return _profile_read_signature(self, cmsSigSaturationRenderingIntentGamutTag);
|
||||
}
|
||||
|
||||
|
@ -1145,7 +1166,8 @@ cms_profile_getattr_blue_colorant(CmsProfileObject *self, void *closure) {
|
|||
|
||||
static PyObject *
|
||||
cms_profile_getattr_media_white_point_temperature(
|
||||
CmsProfileObject *self, void *closure) {
|
||||
CmsProfileObject *self, void *closure
|
||||
) {
|
||||
cmsCIEXYZ *XYZ;
|
||||
cmsCIExyY xyY;
|
||||
cmsFloat64Number tempK;
|
||||
|
@ -1329,7 +1351,8 @@ cms_profile_getattr_icc_measurement_condition(CmsProfileObject *self, void *clos
|
|||
"flare",
|
||||
mc->Flare,
|
||||
"illuminant_type",
|
||||
_illu_map(mc->IlluminantType));
|
||||
_illu_map(mc->IlluminantType)
|
||||
);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
|
@ -1359,7 +1382,8 @@ cms_profile_getattr_icc_viewing_condition(CmsProfileObject *self, void *closure)
|
|||
vc->SurroundXYZ.Y,
|
||||
vc->SurroundXYZ.Z,
|
||||
"illuminant_type",
|
||||
_illu_map(vc->IlluminantType));
|
||||
_illu_map(vc->IlluminantType)
|
||||
);
|
||||
}
|
||||
|
||||
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},
|
||||
{"intent_supported", (getter)cms_profile_getattr_is_intent_supported},
|
||||
{"clut", (getter)cms_profile_getattr_is_clut},
|
||||
{"icc_measurement_condition",
|
||||
(getter)cms_profile_getattr_icc_measurement_condition},
|
||||
{"icc_measurement_condition", (getter)cms_profile_getattr_icc_measurement_condition
|
||||
},
|
||||
{"icc_viewing_condition", (getter)cms_profile_getattr_icc_viewing_condition},
|
||||
|
||||
{NULL}};
|
||||
{NULL}
|
||||
};
|
||||
|
||||
static PyTypeObject CmsProfile_Type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsProfile", /*tp_name*/
|
||||
|
@ -1538,5 +1563,9 @@ PyInit__imagingcms(void) {
|
|||
|
||||
PyDateTime_IMPORT;
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||
#endif
|
||||
|
||||
return m;
|
||||
}
|
||||
|
|
139
src/_imagingft.c
|
@ -20,6 +20,7 @@
|
|||
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include "Python.h"
|
||||
#include "thirdparty/pythoncapi_compat.h"
|
||||
#include "libImaging/Imaging.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
|
@ -125,7 +126,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
|||
unsigned char *font_bytes;
|
||||
Py_ssize_t font_bytes_size = 0;
|
||||
static char *kwlist[] = {
|
||||
"filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL};
|
||||
"filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL
|
||||
};
|
||||
|
||||
if (!library) {
|
||||
PyErr_SetString(PyExc_OSError, "failed to initialize FreeType library");
|
||||
|
@ -147,7 +149,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
|||
&encoding,
|
||||
&font_bytes,
|
||||
&font_bytes_size,
|
||||
&layout_engine)) {
|
||||
&layout_engine
|
||||
)) {
|
||||
PyConfig_Clear(&config);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -165,7 +168,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
|||
&encoding,
|
||||
&font_bytes,
|
||||
&font_bytes_size,
|
||||
&layout_engine)) {
|
||||
&layout_engine
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
@ -198,7 +202,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
|||
(FT_Byte *)self->font_bytes,
|
||||
font_bytes_size,
|
||||
index,
|
||||
&self->face);
|
||||
&self->face
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,7 +247,8 @@ text_layout_raqm(
|
|||
const char *dir,
|
||||
PyObject *features,
|
||||
const char *lang,
|
||||
GlyphInfo **glyph_info) {
|
||||
GlyphInfo **glyph_info
|
||||
) {
|
||||
size_t i = 0, count = 0, start = 0;
|
||||
raqm_t *rq;
|
||||
raqm_glyph_t *glyphs = NULL;
|
||||
|
@ -296,13 +302,14 @@ text_layout_raqm(
|
|||
#if !defined(RAQM_VERSION_ATLEAST)
|
||||
/* RAQM_VERSION_ATLEAST was added in Raqm 0.7.0 */
|
||||
PyErr_SetString(
|
||||
PyExc_ValueError,
|
||||
"libraqm 0.7 or greater required for 'ttb' direction");
|
||||
PyExc_ValueError, "libraqm 0.7 or greater required for 'ttb' direction"
|
||||
);
|
||||
goto failed;
|
||||
#endif
|
||||
} else {
|
||||
PyErr_SetString(
|
||||
PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'");
|
||||
PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"
|
||||
);
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
@ -398,7 +405,8 @@ text_layout_fallback(
|
|||
const char *lang,
|
||||
GlyphInfo **glyph_info,
|
||||
int mask,
|
||||
int color) {
|
||||
int color
|
||||
) {
|
||||
int error, load_flags, i;
|
||||
char *buffer = NULL;
|
||||
FT_ULong ch;
|
||||
|
@ -411,7 +419,8 @@ text_layout_fallback(
|
|||
PyErr_SetString(
|
||||
PyExc_KeyError,
|
||||
"setting text direction, language or font features is not supported "
|
||||
"without libraqm");
|
||||
"without libraqm"
|
||||
);
|
||||
}
|
||||
|
||||
if (PyUnicode_Check(string)) {
|
||||
|
@ -458,7 +467,8 @@ text_layout_fallback(
|
|||
last_index,
|
||||
(*glyph_info)[i].index,
|
||||
ft_kerning_default,
|
||||
&delta) == 0) {
|
||||
&delta
|
||||
) == 0) {
|
||||
(*glyph_info)[i - 1].x_advance += PIXEL(delta.x);
|
||||
(*glyph_info)[i - 1].y_advance += PIXEL(delta.y);
|
||||
}
|
||||
|
@ -482,7 +492,8 @@ text_layout(
|
|||
const char *lang,
|
||||
GlyphInfo **glyph_info,
|
||||
int mask,
|
||||
int color) {
|
||||
int color
|
||||
) {
|
||||
size_t count;
|
||||
#ifdef HAVE_RAQM
|
||||
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
|
||||
|
@ -491,7 +502,8 @@ text_layout(
|
|||
#endif
|
||||
{
|
||||
count = text_layout_fallback(
|
||||
string, self, dir, features, lang, glyph_info, mask, color);
|
||||
string, self, dir, features, lang, glyph_info, mask, color
|
||||
);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
@ -513,7 +525,8 @@ font_getlength(FontObject *self, PyObject *args) {
|
|||
/* calculate size and bearing for a given string */
|
||||
|
||||
if (!PyArg_ParseTuple(
|
||||
args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang)) {
|
||||
args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -555,7 +568,8 @@ bounding_box_and_anchors(
|
|||
int *width,
|
||||
int *height,
|
||||
int *x_offset,
|
||||
int *y_offset) {
|
||||
int *y_offset
|
||||
) {
|
||||
int position; /* pen position along primary axis, in 26.6 precision */
|
||||
int advanced; /* pen position along primary axis, 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
|
||||
y_anchor = PIXEL(
|
||||
(face->size->metrics.ascender + face->size->metrics.descender) /
|
||||
2);
|
||||
2
|
||||
);
|
||||
break;
|
||||
case 's': // horizontal baseline
|
||||
y_anchor = 0;
|
||||
|
@ -740,7 +755,8 @@ font_getsize(FontObject *self, PyObject *args) {
|
|||
/* calculate size and bearing for a given string */
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -772,7 +788,8 @@ font_getsize(FontObject *self, PyObject *args) {
|
|||
&width,
|
||||
&height,
|
||||
&x_offset,
|
||||
&y_offset);
|
||||
&y_offset
|
||||
);
|
||||
if (glyph_info) {
|
||||
PyMem_Free(glyph_info);
|
||||
glyph_info = NULL;
|
||||
|
@ -841,7 +858,8 @@ font_render(FontObject *self, PyObject *args) {
|
|||
&anchor,
|
||||
&foreground_ink_long,
|
||||
&x_start,
|
||||
&y_start)) {
|
||||
&y_start
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -888,7 +906,8 @@ font_render(FontObject *self, PyObject *args) {
|
|||
&width,
|
||||
&height,
|
||||
&x_offset,
|
||||
&y_offset);
|
||||
&y_offset
|
||||
);
|
||||
if (error) {
|
||||
PyMem_Del(glyph_info);
|
||||
return NULL;
|
||||
|
@ -928,7 +947,8 @@ font_render(FontObject *self, PyObject *args) {
|
|||
(FT_Fixed)stroke_width * 64,
|
||||
FT_STROKER_LINECAP_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);
|
||||
target[k * 4 + 3] = CLIP8(
|
||||
src_alpha +
|
||||
MULDIV255(
|
||||
target[k * 4 + 3], (255 - src_alpha), tmp));
|
||||
MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp)
|
||||
);
|
||||
} else {
|
||||
/* paste unpremultiplied RGBA values */
|
||||
target[k * 4 + 0] = src_red;
|
||||
|
@ -1122,15 +1142,20 @@ font_render(FontObject *self, PyObject *args) {
|
|||
if (src_alpha > 0) {
|
||||
if (target[k * 4 + 3] > 0) {
|
||||
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(
|
||||
src_alpha, target[k * 4 + 1], ink[1], tmp);
|
||||
src_alpha, target[k * 4 + 1], ink[1], tmp
|
||||
);
|
||||
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(
|
||||
src_alpha +
|
||||
MULDIV255(
|
||||
target[k * 4 + 3], (255 - src_alpha), tmp));
|
||||
target[k * 4 + 3], (255 - src_alpha), tmp
|
||||
)
|
||||
);
|
||||
} else {
|
||||
target[k * 4 + 0] = ink[0];
|
||||
target[k * 4 + 1] = ink[1];
|
||||
|
@ -1148,7 +1173,9 @@ font_render(FontObject *self, PyObject *args) {
|
|||
? CLIP8(
|
||||
src_alpha +
|
||||
MULDIV255(
|
||||
target[k], (255 - src_alpha), tmp))
|
||||
target[k], (255 - src_alpha), tmp
|
||||
)
|
||||
)
|
||||
: src_alpha;
|
||||
}
|
||||
}
|
||||
|
@ -1209,30 +1236,49 @@ font_getvarnames(FontObject *self) {
|
|||
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);
|
||||
for (i = 0; i < name_count; i++) {
|
||||
error = FT_Get_Sfnt_Name(self->face, i, &name);
|
||||
if (error) {
|
||||
PyMem_Free(list_names_filled);
|
||||
Py_DECREF(list_names);
|
||||
FT_Done_MM_Var(library, master);
|
||||
return geterror(error);
|
||||
}
|
||||
|
||||
for (j = 0; j < num_namedstyles; j++) {
|
||||
if (PyList_GetItem(list_names, j) != NULL) {
|
||||
if (list_names_filled[j]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (master->namedstyle[j].strid == name.name_id) {
|
||||
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);
|
||||
list_names_filled[j] = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyMem_Free(list_names_filled);
|
||||
FT_Done_MM_Var(library, master);
|
||||
|
||||
return list_names;
|
||||
}
|
||||
|
||||
|
@ -1289,9 +1335,14 @@ font_getvaraxes(FontObject *self) {
|
|||
|
||||
if (name.name_id == axis.strid) {
|
||||
axis_name = Py_BuildValue("y#", name.string, name.string_len);
|
||||
PyDict_SetItemString(
|
||||
list_axis, "name", axis_name ? axis_name : Py_None);
|
||||
Py_XDECREF(axis_name);
|
||||
if (axis_name == NULL) {
|
||||
Py_DECREF(list_axis);
|
||||
Py_DECREF(list_axes);
|
||||
FT_Done_MM_Var(library, master);
|
||||
return NULL;
|
||||
}
|
||||
PyDict_SetItemString(list_axis, "name", axis_name);
|
||||
Py_DECREF(axis_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1345,7 +1396,12 @@ font_setvaraxes(FontObject *self, PyObject *args) {
|
|||
return PyErr_NoMemory();
|
||||
}
|
||||
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)) {
|
||||
coord = PyFloat_AS_DOUBLE(item);
|
||||
} else if (PyLong_Check(item)) {
|
||||
|
@ -1353,10 +1409,12 @@ font_setvaraxes(FontObject *self, PyObject *args) {
|
|||
} else if (PyNumber_Check(item)) {
|
||||
coord = PyFloat_AsDouble(item);
|
||||
} else {
|
||||
Py_DECREF(item);
|
||||
free(coords);
|
||||
PyErr_SetString(PyExc_TypeError, "list must contain numbers");
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(item);
|
||||
coords[i] = coord * 65536;
|
||||
}
|
||||
|
||||
|
@ -1393,7 +1451,8 @@ static PyMethodDef font_methods[] = {
|
|||
{"setvarname", (PyCFunction)font_setvarname, METH_VARARGS},
|
||||
{"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS},
|
||||
#endif
|
||||
{NULL, NULL}};
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
static PyObject *
|
||||
font_getattr_family(FontObject *self, void *closure) {
|
||||
|
@ -1450,7 +1509,8 @@ static struct PyGetSetDef font_getsetters[] = {
|
|||
{"x_ppem", (getter)font_getattr_x_ppem},
|
||||
{"y_ppem", (getter)font_getattr_y_ppem},
|
||||
{"glyphs", (getter)font_getattr_glyphs},
|
||||
{NULL}};
|
||||
{NULL}
|
||||
};
|
||||
|
||||
static PyTypeObject Font_Type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0) "Font", /*tp_name*/
|
||||
|
@ -1486,7 +1546,8 @@ static PyTypeObject Font_Type = {
|
|||
};
|
||||
|
||||
static PyMethodDef _functions[] = {
|
||||
{"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}};
|
||||
{"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}
|
||||
};
|
||||
|
||||
static int
|
||||
setup_module(PyObject *m) {
|
||||
|
@ -1576,5 +1637,9 @@ PyInit__imagingft(void) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||
#endif
|
||||
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -209,7 +209,8 @@ _binop(PyObject *self, PyObject *args) {
|
|||
}
|
||||
|
||||
static PyMethodDef _functions[] = {
|
||||
{"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL}};
|
||||
{"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL}
|
||||
};
|
||||
|
||||
static void
|
||||
install(PyObject *d, char *name, void *value) {
|
||||
|
@ -290,5 +291,9 @@ PyInit__imagingmath(void) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||
#endif
|
||||
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -253,7 +253,8 @@ static PyMethodDef functions[] = {
|
|||
{"apply", (PyCFunction)apply, METH_VARARGS, NULL},
|
||||
{"get_on_pixels", (PyCFunction)get_on_pixels, METH_VARARGS, NULL},
|
||||
{"match", (PyCFunction)match, METH_VARARGS, NULL},
|
||||
{NULL, NULL, 0, NULL}};
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit__imagingmorph(void) {
|
||||
|
@ -269,5 +270,9 @@ PyInit__imagingmorph(void) {
|
|||
|
||||
m = PyModule_Create(&module_def);
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||
#endif
|
||||
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -62,5 +62,10 @@ PyInit__imagingtk(void) {
|
|||
Py_DECREF(m);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||
#endif
|
||||
|
||||
return m;
|
||||
}
|
||||
|
|
49
src/_webp.c
|
@ -42,7 +42,8 @@ static const char *const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = {
|
|||
"WEBP_MUX_INVALID_ARGUMENT",
|
||||
"WEBP_MUX_BAD_DATA",
|
||||
"WEBP_MUX_MEMORY_ERROR",
|
||||
"WEBP_MUX_NOT_ENOUGH_DATA"};
|
||||
"WEBP_MUX_NOT_ENOUGH_DATA"
|
||||
};
|
||||
|
||||
PyObject *
|
||||
HandleMuxError(WebPMuxError err, char *chunk) {
|
||||
|
@ -61,7 +62,8 @@ HandleMuxError(WebPMuxError err, char *chunk) {
|
|||
sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]);
|
||||
} else {
|
||||
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) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "failed to construct error message");
|
||||
|
@ -138,7 +140,8 @@ _anim_encoder_new(PyObject *self, PyObject *args) {
|
|||
&kmin,
|
||||
&kmax,
|
||||
&allow_mixed,
|
||||
&verbose)) {
|
||||
&verbose
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -214,7 +217,8 @@ _anim_encoder_add(PyObject *self, PyObject *args) {
|
|||
&lossless,
|
||||
&quality_factor,
|
||||
&alpha_quality_factor,
|
||||
&method)) {
|
||||
&method
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -283,7 +287,8 @@ _anim_encoder_assemble(PyObject *self, PyObject *args) {
|
|||
&exif_bytes,
|
||||
&exif_size,
|
||||
&xmp_bytes,
|
||||
&xmp_size)) {
|
||||
&xmp_size
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -421,7 +426,8 @@ _anim_decoder_get_info(PyObject *self) {
|
|||
info->loop_count,
|
||||
info->bgcolor,
|
||||
info->frame_count,
|
||||
decp->mode);
|
||||
decp->mode
|
||||
);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
|
@ -466,7 +472,8 @@ _anim_decoder_get_next(PyObject *self) {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
|
@ -621,7 +628,8 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
|||
&exif_bytes,
|
||||
&exif_size,
|
||||
&xmp_bytes,
|
||||
&xmp_size)) {
|
||||
&xmp_size
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -828,12 +836,14 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) {
|
|||
|
||||
if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) {
|
||||
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)) {
|
||||
exif = PyBytes_FromStringAndSize(
|
||||
(const char *)exif_data.bytes, exif_data.size);
|
||||
(const char *)exif_data.bytes, exif_data.size
|
||||
);
|
||||
}
|
||||
|
||||
WebPDataClear(&image.bitstream);
|
||||
|
@ -848,12 +858,14 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) {
|
|||
|
||||
if (config.output.colorspace < MODE_YUV) {
|
||||
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 {
|
||||
// Skipping YUV for now. Need Test Images.
|
||||
// UNDONE -- unclear if we'll ever get here if we set mode_rgb*
|
||||
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);
|
||||
|
@ -864,7 +876,8 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) {
|
|||
config.output.height,
|
||||
pymode,
|
||||
NULL == icc_profile ? Py_None : icc_profile,
|
||||
NULL == exif ? Py_None : exif);
|
||||
NULL == exif ? Py_None : exif
|
||||
);
|
||||
|
||||
end:
|
||||
WebPFreeDecBuffer(&config.output);
|
||||
|
@ -898,7 +911,8 @@ WebPDecoderVersion_str(void) {
|
|||
"%d.%d.%d",
|
||||
version_number >> 16,
|
||||
(version_number >> 8) % 0x100,
|
||||
version_number % 0x100);
|
||||
version_number % 0x100
|
||||
);
|
||||
return version;
|
||||
}
|
||||
|
||||
|
@ -932,7 +946,8 @@ static PyMethodDef webpMethods[] = {
|
|||
WebPDecoderBuggyAlpha_wrapper,
|
||||
METH_NOARGS,
|
||||
"WebPDecoderBuggyAlpha"},
|
||||
{NULL, NULL}};
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
void
|
||||
addMuxFlagToModule(PyObject *m) {
|
||||
|
@ -1005,5 +1020,9 @@ PyInit__webp(void) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef Py_GIL_DISABLED
|
||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||
#endif
|
||||
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,8 @@
|
|||
|
||||
typedef struct {
|
||||
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);
|
||||
struct ImagingCodecStateInstance state;
|
||||
Imaging im;
|
||||
|
@ -889,7 +890,8 @@ PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) {
|
|||
PY_LONG_LONG length = -1;
|
||||
|
||||
if (!PyArg_ParseTuple(
|
||||
args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length)) {
|
||||
args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,8 @@ _draw(ImagingDisplayObject *display, PyObject *args) {
|
|||
src + 0,
|
||||
src + 1,
|
||||
src + 2,
|
||||
src + 3)) {
|
||||
src + 3
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -221,7 +222,8 @@ _tobytes(ImagingDisplayObject *display, PyObject *args) {
|
|||
}
|
||||
|
||||
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[] = {
|
||||
|
@ -247,7 +249,8 @@ _getattr_size(ImagingDisplayObject *self, void *closure) {
|
|||
}
|
||||
|
||||
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 = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0) "ImagingDisplay", /*tp_name*/
|
||||
|
@ -341,9 +344,8 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
|
|||
// added in Windows 10 (1607)
|
||||
// loaded dynamically to avoid link errors
|
||||
user32 = LoadLibraryA("User32.dll");
|
||||
SetThreadDpiAwarenessContext_function =
|
||||
(Func_SetThreadDpiAwarenessContext)GetProcAddress(
|
||||
user32, "SetThreadDpiAwarenessContext");
|
||||
SetThreadDpiAwarenessContext_function = (Func_SetThreadDpiAwarenessContext
|
||||
)GetProcAddress(user32, "SetThreadDpiAwarenessContext");
|
||||
if (SetThreadDpiAwarenessContext_function != NULL) {
|
||||
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((DPI_CONTEXT_HANDLE)-3)
|
||||
dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE)-3);
|
||||
|
@ -403,7 +405,8 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
|
|||
height,
|
||||
PyBytes_AS_STRING(buffer),
|
||||
(BITMAPINFO *)&core,
|
||||
DIB_RGB_COLORS)) {
|
||||
DIB_RGB_COLORS
|
||||
)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
@ -547,7 +550,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
|||
ps.rcPaint.left,
|
||||
ps.rcPaint.top,
|
||||
ps.rcPaint.right,
|
||||
ps.rcPaint.bottom);
|
||||
ps.rcPaint.bottom
|
||||
);
|
||||
if (result) {
|
||||
Py_DECREF(result);
|
||||
} else {
|
||||
|
@ -562,7 +566,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
|||
0,
|
||||
0,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top);
|
||||
rect.bottom - rect.top
|
||||
);
|
||||
if (result) {
|
||||
Py_DECREF(result);
|
||||
} else {
|
||||
|
@ -577,7 +582,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
|||
0,
|
||||
0,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top);
|
||||
rect.bottom - rect.top
|
||||
);
|
||||
if (result) {
|
||||
Py_DECREF(result);
|
||||
} else {
|
||||
|
@ -591,7 +597,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
|||
case WM_SIZE:
|
||||
/* resize window */
|
||||
result = PyObject_CallFunction(
|
||||
callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam));
|
||||
callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam)
|
||||
);
|
||||
if (result) {
|
||||
InvalidateRect(wnd, NULL, 1);
|
||||
Py_DECREF(result);
|
||||
|
@ -670,7 +677,8 @@ PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
|
|||
HWND_DESKTOP,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
NULL
|
||||
);
|
||||
|
||||
if (!wnd) {
|
||||
PyErr_SetString(PyExc_OSError, "failed to create window");
|
||||
|
@ -732,7 +740,8 @@ PyImaging_DrawWmf(PyObject *self, PyObject *args) {
|
|||
&x0,
|
||||
&x1,
|
||||
&y0,
|
||||
&y1)) {
|
||||
&y1
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -844,7 +853,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
|
|||
PyErr_Format(
|
||||
PyExc_OSError,
|
||||
"X connection failed: error %i",
|
||||
xcb_connection_has_error(connection));
|
||||
xcb_connection_has_error(connection)
|
||||
);
|
||||
xcb_disconnect(connection);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -878,8 +888,10 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
|
|||
0,
|
||||
width,
|
||||
height,
|
||||
0x00ffffff),
|
||||
&error);
|
||||
0x00ffffff
|
||||
),
|
||||
&error
|
||||
);
|
||||
if (reply == NULL) {
|
||||
PyErr_Format(
|
||||
PyExc_OSError,
|
||||
|
@ -887,7 +899,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
|
|||
error->error_code,
|
||||
error->major_code,
|
||||
error->minor_code,
|
||||
error->resource_id);
|
||||
error->resource_id
|
||||
);
|
||||
free(error);
|
||||
xcb_disconnect(connection);
|
||||
return NULL;
|
||||
|
@ -897,7 +910,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
|
|||
|
||||
if (reply->depth == 24) {
|
||||
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 {
|
||||
PyErr_Format(PyExc_OSError, "unsupported bit depth: %i", reply->depth);
|
||||
}
|
||||
|
|
178
src/encode.c
|
@ -25,6 +25,7 @@
|
|||
#define PY_SSIZE_T_CLEAN
|
||||
#include "Python.h"
|
||||
|
||||
#include "thirdparty/pythoncapi_compat.h"
|
||||
#include "libImaging/Imaging.h"
|
||||
#include "libImaging/Gif.h"
|
||||
|
||||
|
@ -38,7 +39,8 @@
|
|||
|
||||
typedef struct {
|
||||
PyObject_HEAD int (*encode)(
|
||||
Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
|
||||
Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes
|
||||
);
|
||||
int (*cleanup)(ImagingCodecState state);
|
||||
struct ImagingCodecStateInstance state;
|
||||
Imaging im;
|
||||
|
@ -134,7 +136,8 @@ _encode(ImagingEncoderObject *encoder, PyObject *args) {
|
|||
}
|
||||
|
||||
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 */
|
||||
if (_PyBytes_Resize(&buf, (status > 0) ? status : 0) < 0) {
|
||||
|
@ -571,7 +574,8 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
|
|||
&compress_level,
|
||||
&compress_type,
|
||||
&dictionary,
|
||||
&dictionary_size)) {
|
||||
&dictionary_size
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -652,15 +656,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
PyObject *item;
|
||||
|
||||
if (!PyArg_ParseTuple(
|
||||
args,
|
||||
"sssnsOO",
|
||||
&mode,
|
||||
&rawmode,
|
||||
&compname,
|
||||
&fp,
|
||||
&filename,
|
||||
&tags,
|
||||
&types)) {
|
||||
args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -671,11 +668,17 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
tags_size = PyList_Size(tags);
|
||||
TRACE(("tags size: %d\n", (int)tags_size));
|
||||
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) {
|
||||
Py_DECREF(item);
|
||||
PyErr_SetString(PyExc_ValueError, "Invalid tags list");
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(item);
|
||||
}
|
||||
pos = 0;
|
||||
}
|
||||
|
@ -703,11 +706,17 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
|
||||
num_core_tags = sizeof(core_tags) / sizeof(int);
|
||||
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.
|
||||
key = PyTuple_GetItem(item, 0);
|
||||
key = PyTuple_GET_ITEM(item, 0);
|
||||
key_int = (int)PyLong_AsLong(key);
|
||||
value = PyTuple_GetItem(item, 1);
|
||||
value = PyTuple_GET_ITEM(item, 1);
|
||||
Py_DECREF(item);
|
||||
|
||||
status = 0;
|
||||
is_core_tag = 0;
|
||||
is_var_length = 0;
|
||||
|
@ -721,7 +730,10 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
}
|
||||
|
||||
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) {
|
||||
int type_int = PyLong_AsLong(tag_type);
|
||||
if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) {
|
||||
|
@ -769,7 +781,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
is_var_length = 1;
|
||||
}
|
||||
if (ImagingLibTiffMergeFieldInfo(
|
||||
&encoder->state, type, key_int, is_var_length)) {
|
||||
&encoder->state, type, key_int, is_var_length
|
||||
)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -779,7 +792,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
&encoder->state,
|
||||
(ttag_t)key_int,
|
||||
PyBytes_Size(value),
|
||||
PyBytes_AsString(value));
|
||||
PyBytes_AsString(value)
|
||||
);
|
||||
} else if (is_var_length) {
|
||||
Py_ssize_t len, i;
|
||||
TRACE(("Setting from Tuple: %d \n", key_int));
|
||||
|
@ -789,7 +803,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
int stride = 256;
|
||||
if (len != 768) {
|
||||
PyErr_SetString(
|
||||
PyExc_ValueError, "Requiring 768 items for Colormap");
|
||||
PyExc_ValueError, "Requiring 768 items for Colormap"
|
||||
);
|
||||
return NULL;
|
||||
}
|
||||
UINT16 *av;
|
||||
|
@ -804,7 +819,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
(ttag_t)key_int,
|
||||
av,
|
||||
av + stride,
|
||||
av + stride * 2);
|
||||
av + stride * 2
|
||||
);
|
||||
free(av);
|
||||
}
|
||||
} else if (key_int == TIFFTAG_YCBCRSUBSAMPLING) {
|
||||
|
@ -812,7 +828,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
&encoder->state,
|
||||
(ttag_t)key_int,
|
||||
(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) {
|
||||
UINT16 *av;
|
||||
/* 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));
|
||||
}
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, len, av);
|
||||
&encoder->state, (ttag_t)key_int, len, av
|
||||
);
|
||||
free(av);
|
||||
}
|
||||
} else if (type == TIFF_LONG) {
|
||||
|
@ -834,7 +852,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
av[i] = (UINT32)PyLong_AsLong(PyTuple_GetItem(value, i));
|
||||
}
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, len, av);
|
||||
&encoder->state, (ttag_t)key_int, len, av
|
||||
);
|
||||
free(av);
|
||||
}
|
||||
} else if (type == TIFF_SBYTE) {
|
||||
|
@ -846,7 +865,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
av[i] = (INT8)PyLong_AsLong(PyTuple_GetItem(value, i));
|
||||
}
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, len, av);
|
||||
&encoder->state, (ttag_t)key_int, len, av
|
||||
);
|
||||
free(av);
|
||||
}
|
||||
} else if (type == TIFF_SSHORT) {
|
||||
|
@ -858,7 +878,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
av[i] = (INT16)PyLong_AsLong(PyTuple_GetItem(value, i));
|
||||
}
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, len, av);
|
||||
&encoder->state, (ttag_t)key_int, len, av
|
||||
);
|
||||
free(av);
|
||||
}
|
||||
} else if (type == TIFF_SLONG) {
|
||||
|
@ -870,7 +891,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
av[i] = (INT32)PyLong_AsLong(PyTuple_GetItem(value, i));
|
||||
}
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, len, av);
|
||||
&encoder->state, (ttag_t)key_int, len, av
|
||||
);
|
||||
free(av);
|
||||
}
|
||||
} else if (type == TIFF_FLOAT) {
|
||||
|
@ -882,7 +904,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i));
|
||||
}
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, len, av);
|
||||
&encoder->state, (ttag_t)key_int, len, av
|
||||
);
|
||||
free(av);
|
||||
}
|
||||
} else if (type == TIFF_DOUBLE) {
|
||||
|
@ -894,43 +917,54 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
av[i] = PyFloat_AsDouble(PyTuple_GetItem(value, i));
|
||||
}
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, len, av);
|
||||
&encoder->state, (ttag_t)key_int, len, av
|
||||
);
|
||||
free(av);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (type == TIFF_SHORT) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value));
|
||||
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
|
||||
);
|
||||
} else {
|
||||
TRACE(
|
||||
("Unhandled type for key %d : %s \n",
|
||||
key_int,
|
||||
PyBytes_AsString(PyObject_Str(value))));
|
||||
PyBytes_AsString(PyObject_Str(value)))
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!status) {
|
||||
|
@ -991,7 +1025,8 @@ get_qtables_arrays(PyObject *qtables, int *qtablesLen) {
|
|||
if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) {
|
||||
PyErr_SetString(
|
||||
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);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1080,7 +1115,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
|||
&extra,
|
||||
&extra_size,
|
||||
&rawExif,
|
||||
&rawExifLen)) {
|
||||
&rawExifLen
|
||||
)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -1150,29 +1186,27 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
|||
|
||||
encoder->encode = ImagingJpegEncode;
|
||||
|
||||
strncpy(((JPEGENCODERSTATE *)encoder->state.context)->rawmode, rawmode, 8);
|
||||
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->keep_rgb = keep_rgb;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->quality = quality;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->qtables = qarrays;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->qtablesLen = qtablesLen;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->subsampling = subsampling;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->progressive = progressive;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->smooth = smooth;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->optimize = optimize;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_blocks =
|
||||
restart_marker_blocks;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_rows =
|
||||
restart_marker_rows;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->comment = comment;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->extra = extra;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->extra_size = extra_size;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->rawExif = rawExif;
|
||||
((JPEGENCODERSTATE *)encoder->state.context)->rawExifLen = rawExifLen;
|
||||
JPEGENCODERSTATE *jpeg_encoder_state = (JPEGENCODERSTATE *)encoder->state.context;
|
||||
strncpy(jpeg_encoder_state->rawmode, rawmode, 8);
|
||||
jpeg_encoder_state->keep_rgb = keep_rgb;
|
||||
jpeg_encoder_state->quality = quality;
|
||||
jpeg_encoder_state->qtables = qarrays;
|
||||
jpeg_encoder_state->qtablesLen = qtablesLen;
|
||||
jpeg_encoder_state->subsampling = subsampling;
|
||||
jpeg_encoder_state->progressive = progressive;
|
||||
jpeg_encoder_state->smooth = smooth;
|
||||
jpeg_encoder_state->optimize = optimize;
|
||||
jpeg_encoder_state->streamtype = streamtype;
|
||||
jpeg_encoder_state->xdpi = xdpi;
|
||||
jpeg_encoder_state->ydpi = ydpi;
|
||||
jpeg_encoder_state->restart_marker_blocks = restart_marker_blocks;
|
||||
jpeg_encoder_state->restart_marker_rows = restart_marker_rows;
|
||||
jpeg_encoder_state->comment = comment;
|
||||
jpeg_encoder_state->comment_size = comment_size;
|
||||
jpeg_encoder_state->extra = extra;
|
||||
jpeg_encoder_state->extra_size = extra_size;
|
||||
jpeg_encoder_state->rawExif = rawExif;
|
||||
jpeg_encoder_state->rawExifLen = rawExifLen;
|
||||
|
||||
return (PyObject *)encoder;
|
||||
}
|
||||
|
@ -1250,7 +1284,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
|
|||
&fd,
|
||||
&comment,
|
||||
&comment_size,
|
||||
&plt)) {
|
||||
&plt
|
||||
)) {
|
||||
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(
|
||||
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);
|
||||
|
||||
/* Error on illegal tile offsets */
|
||||
|
@ -1317,7 +1353,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
|
|||
PyErr_SetString(
|
||||
PyExc_ValueError,
|
||||
"JPEG 2000 tile offset too small; top left tile must "
|
||||
"intersect image area");
|
||||
"intersect image area"
|
||||
);
|
||||
Py_DECREF(encoder);
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1325,8 +1362,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
|
|||
if (context->tile_offset_x > context->offset_x ||
|
||||
context->tile_offset_y > context->offset_y) {
|
||||
PyErr_SetString(
|
||||
PyExc_ValueError,
|
||||
"JPEG 2000 tile offset too large to cover image area");
|
||||
PyExc_ValueError, "JPEG 2000 tile offset too large to cover image area"
|
||||
);
|
||||
Py_DECREF(encoder);
|
||||
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(
|
||||
precinct_size, &context->precinct_width, &context->precinct_height);
|
||||
precinct_size, &context->precinct_width, &context->precinct_height
|
||||
);
|
||||
|
||||
context->irreversible = PyObject_IsTrue(irreversible);
|
||||
context->progression = prog_order;
|
||||
|
|
|
@ -36,7 +36,8 @@ add_item(const char *mode) {
|
|||
"AccessInit: hash collision: %d for both %s and %s\n",
|
||||
i,
|
||||
mode,
|
||||
access_table[i].mode);
|
||||
access_table[i].mode
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
access_table[i].mode = mode;
|
||||
|
|
|
@ -243,7 +243,8 @@ static const bc7_mode_info bc7_modes[] = {
|
|||
{1, 0, 2, 1, 5, 6, 0, 0, 2, 3},
|
||||
{1, 0, 2, 0, 7, 8, 0, 0, 2, 2},
|
||||
{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:
|
||||
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,
|
||||
0x73ce, 0x13c8, 0x324c, 0x3bdc, 0x6996, 0xc33c, 0x9966, 0x0660, 0x0272, 0x04e4,
|
||||
0x4e40, 0x2720, 0xc936, 0x936c, 0x39c6, 0x639c, 0x9336, 0x9cc6, 0x817e, 0xe718,
|
||||
0xccf0, 0x0fcc, 0x7744, 0xee22};
|
||||
0xccf0, 0x0fcc, 0x7744, 0xee22
|
||||
};
|
||||
|
||||
/* Table.P3, 2 bits per index */
|
||||
static const UINT32 bc7_si3[] = {
|
||||
|
@ -267,20 +269,23 @@ static const UINT32 bc7_si3[] = {
|
|||
0x66660000, 0xa5a0a5a0, 0x50a050a0, 0x69286928, 0x44aaaa44, 0x66666600, 0xaa444444,
|
||||
0x54a854a8, 0x95809580, 0x96969600, 0xa85454a8, 0x80959580, 0xaa141414, 0x96960000,
|
||||
0xaaaa1414, 0xa05050a0, 0xa0a5a5a0, 0x96000000, 0x40804080, 0xa9a8a9a8, 0xaaaaaa44,
|
||||
0x2a4a5254};
|
||||
0x2a4a5254
|
||||
};
|
||||
|
||||
/* Anchor indices:
|
||||
Table.A2 */
|
||||
static const char bc7_ai0[] = {
|
||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 8, 2, 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, 15, 15, 6, 6, 2, 6, 8, 15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15};
|
||||
static const char bc7_ai0[] = {15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15, 15, 2, 8, 2, 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, 15, 15, 6, 6, 2, 6, 8,
|
||||
15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15};
|
||||
|
||||
/* Table.A3a */
|
||||
static const char bc7_ai1[] = {
|
||||
3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8, 15, 3, 3,
|
||||
6, 10, 5, 8, 8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8, 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};
|
||||
static const char bc7_ai1[] = {3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6,
|
||||
5, 3, 3, 3, 3, 8, 15, 3, 3, 6, 10, 5, 8,
|
||||
8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8,
|
||||
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 */
|
||||
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_weights3[] = {0, 9, 18, 27, 37, 46, 55, 64};
|
||||
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 *
|
||||
bc7_get_weights(int n) {
|
||||
|
@ -526,7 +532,8 @@ static const bc6_mode_info bc6_modes[] = {
|
|||
{1, 0, 0, 10, 10, 10, 10},
|
||||
{1, 1, 0, 11, 9, 9, 9},
|
||||
{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 */
|
||||
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},
|
||||
{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,
|
||||
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
|
||||
bc6_sign_extend(UINT16 *v, int prec) {
|
||||
|
@ -830,7 +838,8 @@ decode_bcn(
|
|||
int bytes,
|
||||
int N,
|
||||
int C,
|
||||
char *pixel_format) {
|
||||
char *pixel_format
|
||||
) {
|
||||
int ymax = state->ysize + state->yoff;
|
||||
const UINT8 *ptr = src;
|
||||
switch (N) {
|
||||
|
|
|
@ -13,7 +13,8 @@ void static inline ImagingLineBoxBlur32(
|
|||
int edgeA,
|
||||
int edgeB,
|
||||
UINT32 ww,
|
||||
UINT32 fw) {
|
||||
UINT32 fw
|
||||
) {
|
||||
int x;
|
||||
UINT32 acc[4];
|
||||
UINT32 bulk[4];
|
||||
|
@ -109,7 +110,8 @@ void static inline ImagingLineBoxBlur8(
|
|||
int edgeA,
|
||||
int edgeB,
|
||||
UINT32 ww,
|
||||
UINT32 fw) {
|
||||
UINT32 fw
|
||||
) {
|
||||
int x;
|
||||
UINT32 acc;
|
||||
UINT32 bulk;
|
||||
|
@ -198,7 +200,8 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) {
|
|||
edgeA,
|
||||
edgeB,
|
||||
ww,
|
||||
fw);
|
||||
fw
|
||||
);
|
||||
if (imIn == imOut) {
|
||||
// Commit.
|
||||
memcpy(imOut->image8[y], lineOut, imIn->xsize);
|
||||
|
@ -214,7 +217,8 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) {
|
|||
edgeA,
|
||||
edgeB,
|
||||
ww,
|
||||
fw);
|
||||
fw
|
||||
);
|
||||
if (imIn == imOut) {
|
||||
// Commit.
|
||||
memcpy(imOut->image32[y], lineOut, imIn->xsize * 4);
|
||||
|
@ -314,11 +318,13 @@ _gaussian_blur_radius(float radius, int passes) {
|
|||
|
||||
Imaging
|
||||
ImagingGaussianBlur(
|
||||
Imaging imOut, Imaging imIn, float xradius, float yradius, int passes) {
|
||||
Imaging imOut, Imaging imIn, float xradius, float yradius, int passes
|
||||
) {
|
||||
return ImagingBoxBlur(
|
||||
imOut,
|
||||
imIn,
|
||||
_gaussian_blur_radius(xradius, passes),
|
||||
_gaussian_blur_radius(yradius, passes),
|
||||
passes);
|
||||
passes
|
||||
);
|
||||
}
|
||||
|
|
|
@ -142,7 +142,8 @@ ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) {
|
|||
CHOP2(
|
||||
(((255 - in1[x]) * (in1[x] * in2[x])) / 65536) +
|
||||
(in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255,
|
||||
NULL);
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
Imaging
|
||||
|
@ -150,7 +151,8 @@ ImagingChopHardLight(Imaging imIn1, Imaging imIn2) {
|
|||
CHOP2(
|
||||
(in2[x] < 128) ? ((in1[x] * in2[x]) / 127)
|
||||
: 255 - (((255 - in2[x]) * (255 - in1[x])) / 127),
|
||||
NULL);
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
Imaging
|
||||
|
@ -158,5 +160,6 @@ ImagingOverlay(Imaging imIn1, Imaging imIn2) {
|
|||
CHOP2(
|
||||
(in1[x] < 128) ? ((in1[x] * in2[x]) / 127)
|
||||
: 255 - (((255 - in1[x]) * (255 - in2[x])) / 127),
|
||||
NULL);
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
|