Merge branch 'main' into init
|
@ -37,12 +37,18 @@ python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
if [[ $(uname) != CYGWIN* ]]; then
|
||||||
|
# TODO Update condition when NumPy supports free-threading
|
||||||
|
if [[ "$PYTHON_GIL" == "0" ]]; then
|
||||||
|
python3 -m pip install numpy --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
|
||||||
|
else
|
||||||
python3 -m pip install numpy
|
python3 -m pip install numpy
|
||||||
|
fi
|
||||||
|
|
||||||
# PyQt6 doesn't support PyPy3
|
# PyQt6 doesn't support PyPy3
|
||||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||||
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
||||||
python3 -m pip install pyqt6
|
# TODO Update condition when pyqt6 supports free-threading
|
||||||
|
if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Pyroma uses non-isolated build and fails with old setuptools
|
# Pyroma uses non-isolated build and fails with old setuptools
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
mypy==1.10.1
|
mypy==1.11.0
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
BasedOnStyle: Google
|
BasedOnStyle: Google
|
||||||
AlwaysBreakAfterReturnType: All
|
AlwaysBreakAfterReturnType: All
|
||||||
AllowShortIfStatementsOnASingleLine: false
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
AlignAfterOpenBracket: AlwaysBreak
|
AlignAfterOpenBracket: BlockIndent
|
||||||
BinPackArguments: false
|
BinPackArguments: false
|
||||||
BinPackParameters: false
|
BinPackParameters: false
|
||||||
BreakBeforeBraces: Attach
|
BreakBeforeBraces: Attach
|
||||||
|
|
2
.github/workflows/cifuzz.yml
vendored
|
@ -24,6 +24,8 @@ concurrency:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Fuzzing:
|
Fuzzing:
|
||||||
|
# Disabled until google/oss-fuzz#11419 upgrades Python to 3.9+
|
||||||
|
if: false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Build Fuzzers
|
- name: Build Fuzzers
|
||||||
|
|
30
.github/workflows/test.yml
vendored
|
@ -50,26 +50,24 @@ jobs:
|
||||||
"3.9",
|
"3.9",
|
||||||
]
|
]
|
||||||
include:
|
include:
|
||||||
- python-version: "3.11"
|
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
||||||
PYTHONOPTIMIZE: 1
|
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
|
||||||
REVERSE: "--reverse"
|
# Free-threaded
|
||||||
- python-version: "3.10"
|
- { os: "ubuntu-latest", python-version: "3.13-dev", disable-gil: true }
|
||||||
PYTHONOPTIMIZE: 2
|
|
||||||
# M1 only available for 3.10+
|
# M1 only available for 3.10+
|
||||||
- os: "macos-13"
|
- { os: "macos-13", python-version: "3.9" }
|
||||||
python-version: "3.9"
|
|
||||||
exclude:
|
exclude:
|
||||||
- os: "macos-14"
|
- { os: "macos-14", python-version: "3.9" }
|
||||||
python-version: "3.9"
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
name: ${{ matrix.os }} Python ${{ matrix.python-version }} ${{ matrix.disable-gil && 'free-threaded' || '' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
|
if: "${{ !matrix.disable-gil }}"
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
allow-prereleases: true
|
allow-prereleases: true
|
||||||
|
@ -78,6 +76,18 @@ jobs:
|
||||||
".ci/*.sh"
|
".ci/*.sh"
|
||||||
"pyproject.toml"
|
"pyproject.toml"
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }} (free-threaded)
|
||||||
|
uses: deadsnakes/action@v3.1.0
|
||||||
|
if: "${{ matrix.disable-gil }}"
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
nogil: ${{ matrix.disable-gil }}
|
||||||
|
|
||||||
|
- name: Set PYTHON_GIL
|
||||||
|
if: "${{ matrix.disable-gil }}"
|
||||||
|
run: |
|
||||||
|
echo "PYTHON_GIL=0" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
|
|
6
.github/workflows/wheels-test.sh
vendored
|
@ -12,9 +12,15 @@ elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
|
||||||
else
|
else
|
||||||
yum install -y fribidi
|
yum install -y fribidi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; then
|
if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; then
|
||||||
|
# TODO Update condition when NumPy supports free-threading
|
||||||
|
if [ $(python3 -c "import sysconfig;print(sysconfig.get_config_var('Py_GIL_DISABLED'))") == "1" ]; then
|
||||||
|
python3 -m pip install numpy --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple
|
||||||
|
else
|
||||||
python3 -m pip install numpy
|
python3 -m pip install numpy
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ ! -d "test-images-main" ]; then
|
if [ ! -d "test-images-main" ]; then
|
||||||
curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
|
curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
|
||||||
|
|
33
.github/workflows/wheels.yml
vendored
|
@ -1,6 +1,14 @@
|
||||||
name: Wheels
|
name: Wheels
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
schedule:
|
||||||
|
# ┌───────────── minute (0 - 59)
|
||||||
|
# │ ┌───────────── hour (0 - 23)
|
||||||
|
# │ │ ┌───────────── day of the month (1 - 31)
|
||||||
|
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||||
|
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||||
|
# │ │ │ │ │
|
||||||
|
- cron: "42 1 * * 0,3"
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- ".ci/requirements-cibw.txt"
|
- ".ci/requirements-cibw.txt"
|
||||||
|
@ -33,6 +41,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-1-QEMU-emulated-wheels:
|
build-1-QEMU-emulated-wheels:
|
||||||
|
if: github.event_name != 'schedule' && github.event_name != 'workflow_dispatch'
|
||||||
name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }}
|
name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -88,6 +97,7 @@ jobs:
|
||||||
path: ./wheelhouse/*.whl
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
build-2-native-wheels:
|
build-2-native-wheels:
|
||||||
|
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -129,6 +139,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||||
CIBW_BUILD: ${{ matrix.build }}
|
CIBW_BUILD: ${{ matrix.build }}
|
||||||
|
CIBW_FREE_THREADED_SUPPORT: True
|
||||||
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
CIBW_PRERELEASE_PYTHONS: True
|
CIBW_PRERELEASE_PYTHONS: True
|
||||||
|
@ -140,6 +151,7 @@ jobs:
|
||||||
path: ./wheelhouse/*.whl
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
|
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
|
||||||
name: Windows ${{ matrix.cibw_arch }}
|
name: Windows ${{ matrix.cibw_arch }}
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
|
@ -201,6 +213,7 @@ jobs:
|
||||||
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||||
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
||||||
CIBW_CACHE_PATH: "C:\\cibw"
|
CIBW_CACHE_PATH: "C:\\cibw"
|
||||||
|
CIBW_FREE_THREADED_SUPPORT: True
|
||||||
CIBW_PRERELEASE_PYTHONS: True
|
CIBW_PRERELEASE_PYTHONS: True
|
||||||
CIBW_TEST_SKIP: "*-win_arm64"
|
CIBW_TEST_SKIP: "*-win_arm64"
|
||||||
CIBW_TEST_COMMAND: 'docker run --rm
|
CIBW_TEST_COMMAND: 'docker run --rm
|
||||||
|
@ -225,6 +238,7 @@ jobs:
|
||||||
path: winbuild\build\bin\fribidi*
|
path: winbuild\build\bin\fribidi*
|
||||||
|
|
||||||
sdist:
|
sdist:
|
||||||
|
if: github.event_name != 'schedule'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
@ -243,8 +257,25 @@ jobs:
|
||||||
name: dist-sdist
|
name: dist-sdist
|
||||||
path: dist/*.tar.gz
|
path: dist/*.tar.gz
|
||||||
|
|
||||||
|
scientific-python-nightly-wheels-publish:
|
||||||
|
if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
|
||||||
|
needs: [build-2-native-wheels, windows]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Upload wheels to scientific-python-nightly-wheels
|
||||||
|
steps:
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
pattern: dist-*
|
||||||
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
- name: Upload wheels to scientific-python-nightly-wheels
|
||||||
|
uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0
|
||||||
|
with:
|
||||||
|
artifacts_path: dist
|
||||||
|
anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }}
|
||||||
|
|
||||||
pypi-publish:
|
pypi-publish:
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||||
needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist]
|
needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Upload release to PyPI
|
name: Upload release to PyPI
|
||||||
|
|
12
CHANGES.rst
|
@ -5,6 +5,18 @@ Changelog (Pillow)
|
||||||
11.0.0 (unreleased)
|
11.0.0 (unreleased)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
- Deprecate ImageMath lambda_eval and unsafe_eval options argument #8242
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Changed ContainerIO to subclass IO #8240
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Move away from APIs that use borrowed references under the free-threaded build #8216
|
||||||
|
[hugovk, lysnikolaou]
|
||||||
|
|
||||||
|
- Allow size argument to resize() to be a NumPy array #8201
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Drop support for Python 3.8 #8183
|
- Drop support for Python 3.8 #8183
|
||||||
[hugovk, radarhere]
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import ContainerIO, Image
|
from PIL import ContainerIO, Image
|
||||||
|
@ -23,6 +21,13 @@ def test_isatty() -> None:
|
||||||
assert container.isatty() is False
|
assert container.isatty() is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_seekable() -> None:
|
||||||
|
with hopper() as im:
|
||||||
|
container = ContainerIO.ContainerIO(im, 0, 0)
|
||||||
|
|
||||||
|
assert container.seekable() is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"mode, expected_position",
|
"mode, expected_position",
|
||||||
(
|
(
|
||||||
|
@ -31,7 +36,7 @@ def test_isatty() -> None:
|
||||||
(2, 100),
|
(2, 100),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_seek_mode(mode: Literal[0, 1, 2], expected_position: int) -> None:
|
def test_seek_mode(mode: int, expected_position: int) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
with open(TEST_FILE, "rb") as fh:
|
with open(TEST_FILE, "rb") as fh:
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
@ -44,6 +49,14 @@ def test_seek_mode(mode: Literal[0, 1, 2], expected_position: int) -> None:
|
||||||
assert container.tell() == expected_position
|
assert container.tell() == expected_position
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_readable(bytesmode: bool) -> None:
|
||||||
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
|
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||||
|
|
||||||
|
assert container.readable() is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bytesmode", (True, False))
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
def test_read_n0(bytesmode: bool) -> None:
|
def test_read_n0(bytesmode: bool) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -51,7 +64,7 @@ def test_read_n0(bytesmode: bool) -> None:
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
container.seek(81)
|
assert container.seek(81) == 81
|
||||||
data = container.read()
|
data = container.read()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
|
@ -67,7 +80,7 @@ def test_read_n(bytesmode: bool) -> None:
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
container.seek(81)
|
assert container.seek(81) == 81
|
||||||
data = container.read(3)
|
data = container.read(3)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
|
@ -83,7 +96,7 @@ def test_read_eof(bytesmode: bool) -> None:
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
container.seek(100)
|
assert container.seek(100) == 100
|
||||||
data = container.read()
|
data = container.read()
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
|
@ -94,21 +107,65 @@ def test_read_eof(bytesmode: bool) -> None:
|
||||||
|
|
||||||
@pytest.mark.parametrize("bytesmode", (True, False))
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
def test_readline(bytesmode: bool) -> None:
|
def test_readline(bytesmode: bool) -> None:
|
||||||
# Arrange
|
|
||||||
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
container = ContainerIO.ContainerIO(fh, 0, 120)
|
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||||
|
|
||||||
# Act
|
|
||||||
data = container.readline()
|
data = container.readline()
|
||||||
|
|
||||||
# Assert
|
|
||||||
if bytesmode:
|
if bytesmode:
|
||||||
data = data.decode()
|
data = data.decode()
|
||||||
assert data == "This is line 1\n"
|
assert data == "This is line 1\n"
|
||||||
|
|
||||||
|
data = container.readline(4)
|
||||||
|
if bytesmode:
|
||||||
|
data = data.decode()
|
||||||
|
assert data == "This"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bytesmode", (True, False))
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
def test_readlines(bytesmode: bool) -> None:
|
def test_readlines(bytesmode: bool) -> None:
|
||||||
|
expected = [
|
||||||
|
"This is line 1\n",
|
||||||
|
"This is line 2\n",
|
||||||
|
"This is line 3\n",
|
||||||
|
"This is line 4\n",
|
||||||
|
"This is line 5\n",
|
||||||
|
"This is line 6\n",
|
||||||
|
"This is line 7\n",
|
||||||
|
"This is line 8\n",
|
||||||
|
]
|
||||||
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
|
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||||
|
|
||||||
|
data = container.readlines()
|
||||||
|
if bytesmode:
|
||||||
|
data = [line.decode() for line in data]
|
||||||
|
assert data == expected
|
||||||
|
|
||||||
|
assert container.seek(0) == 0
|
||||||
|
|
||||||
|
data = container.readlines(2)
|
||||||
|
if bytesmode:
|
||||||
|
data = [line.decode() for line in data]
|
||||||
|
assert data == expected[:2]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_write(bytesmode: bool) -> None:
|
||||||
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
|
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||||
|
|
||||||
|
assert container.writable() is False
|
||||||
|
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
container.write(b"" if bytesmode else "")
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
container.writelines([])
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
container.truncate()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_iter(bytesmode: bool) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
expected = [
|
expected = [
|
||||||
"This is line 1\n",
|
"This is line 1\n",
|
||||||
|
@ -124,9 +181,21 @@ def test_readlines(bytesmode: bool) -> None:
|
||||||
container = ContainerIO.ContainerIO(fh, 0, 120)
|
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
data = container.readlines()
|
data = []
|
||||||
|
for line in container:
|
||||||
|
data.append(line)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
if bytesmode:
|
if bytesmode:
|
||||||
data = [line.decode() for line in data]
|
data = [line.decode() for line in data]
|
||||||
assert data == expected
|
assert data == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
def test_file(bytesmode: bool) -> None:
|
||||||
|
with open(TEST_FILE, "rb" if bytesmode else "r") as fh:
|
||||||
|
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||||
|
|
||||||
|
assert isinstance(container.fileno(), int)
|
||||||
|
container.flush()
|
||||||
|
container.close()
|
||||||
|
|
|
@ -57,6 +57,7 @@ def test_getiptcinfo_fotostation() -> None:
|
||||||
iptc = IptcImagePlugin.getiptcinfo(im)
|
iptc = IptcImagePlugin.getiptcinfo(im)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
|
assert iptc is not None
|
||||||
for tag in iptc.keys():
|
for tag in iptc.keys():
|
||||||
if tag[0] == 240:
|
if tag[0] == 240:
|
||||||
return
|
return
|
||||||
|
|
|
@ -829,7 +829,7 @@ class TestFileJpeg:
|
||||||
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
|
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im:
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
# "When the image resolution is unknown, 72 [dpi] is designated."
|
# "When the image resolution is unknown, 72 [dpi] is designated."
|
||||||
# https://web.archive.org/web/20240227115053/https://exiv2.org/tags.html
|
# https://exiv2.org/tags.html
|
||||||
assert im.info.get("dpi") == (72, 72)
|
assert im.info.get("dpi") == (72, 72)
|
||||||
|
|
||||||
def test_invalid_exif(self) -> None:
|
def test_invalid_exif(self) -> None:
|
||||||
|
|
|
@ -240,9 +240,10 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
new_ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
for tag, info in core_items.items():
|
for tag, info in core_items.items():
|
||||||
|
assert info.type is not None
|
||||||
if info.length == 1:
|
if info.length == 1:
|
||||||
new_ifd[tag] = values[info.type]
|
new_ifd[tag] = values[info.type]
|
||||||
if info.length == 0:
|
elif not info.length:
|
||||||
new_ifd[tag] = tuple(values[info.type] for _ in range(3))
|
new_ifd[tag] = tuple(values[info.type] for _ in range(3))
|
||||||
else:
|
else:
|
||||||
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
|
new_ifd[tag] = tuple(values[info.type] for _ in range(info.length))
|
||||||
|
|
|
@ -2,11 +2,11 @@ from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Any, cast
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, MpoImagePlugin
|
from PIL import Image, ImageFile, MpoImagePlugin
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -20,11 +20,11 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"]
|
||||||
pytestmark = skip_unless_feature("jpg")
|
pytestmark = skip_unless_feature("jpg")
|
||||||
|
|
||||||
|
|
||||||
def roundtrip(im: Image.Image, **options: Any) -> MpoImagePlugin.MpoImageFile:
|
def roundtrip(im: Image.Image, **options: Any) -> ImageFile.ImageFile:
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im.save(out, "MPO", **options)
|
im.save(out, "MPO", **options)
|
||||||
out.seek(0)
|
out.seek(0)
|
||||||
return cast(MpoImagePlugin.MpoImageFile, Image.open(out))
|
return Image.open(out)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("test_file", test_files)
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
|
@ -85,7 +85,9 @@ def test_exif(test_file: str) -> None:
|
||||||
im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif())
|
im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif())
|
||||||
|
|
||||||
for im in (im_original, im_reloaded):
|
for im in (im_original, im_reloaded):
|
||||||
|
assert isinstance(im, MpoImagePlugin.MpoImageFile)
|
||||||
info = im._getexif()
|
info = im._getexif()
|
||||||
|
assert info is not None
|
||||||
assert info[272] == "Nintendo 3DS"
|
assert info[272] == "Nintendo 3DS"
|
||||||
assert info[296] == 2
|
assert info[296] == 2
|
||||||
assert info[34665] == 188
|
assert info[34665] == 188
|
||||||
|
@ -226,6 +228,12 @@ def test_eoferror() -> None:
|
||||||
im.seek(n_frames - 1)
|
im.seek(n_frames - 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_adopt_jpeg() -> None:
|
||||||
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
MpoImagePlugin.MpoImageFile.adopt(im)
|
||||||
|
|
||||||
|
|
||||||
def test_ultra_hdr() -> None:
|
def test_ultra_hdr() -> None:
|
||||||
with Image.open("Tests/images/ultrahdr.jpg") as im:
|
with Image.open("Tests/images/ultrahdr.jpg") as im:
|
||||||
assert im.format == "JPEG"
|
assert im.format == "JPEG"
|
||||||
|
@ -275,6 +283,8 @@ def test_save_all() -> None:
|
||||||
im_reloaded = roundtrip(im, save_all=True, append_images=[im2])
|
im_reloaded = roundtrip(im, save_all=True, append_images=[im2])
|
||||||
|
|
||||||
assert_image_equal(im, im_reloaded)
|
assert_image_equal(im, im_reloaded)
|
||||||
|
assert isinstance(im_reloaded, MpoImagePlugin.MpoImageFile)
|
||||||
|
assert im_reloaded.mpinfo is not None
|
||||||
assert im_reloaded.mpinfo[45056] == b"0100"
|
assert im_reloaded.mpinfo[45056] == b"0100"
|
||||||
|
|
||||||
im_reloaded.seek(1)
|
im_reloaded.seek(1)
|
||||||
|
|
|
@ -229,6 +229,7 @@ def test_pdf_append_fails_on_nonexistent_file() -> None:
|
||||||
|
|
||||||
|
|
||||||
def check_pdf_pages_consistency(pdf: PdfParser.PdfParser) -> None:
|
def check_pdf_pages_consistency(pdf: PdfParser.PdfParser) -> None:
|
||||||
|
assert pdf.pages_ref is not None
|
||||||
pages_info = pdf.read_indirect(pdf.pages_ref)
|
pages_info = pdf.read_indirect(pdf.pages_ref)
|
||||||
assert b"Parent" not in pages_info
|
assert b"Parent" not in pages_info
|
||||||
assert b"Kids" in pages_info
|
assert b"Kids" in pages_info
|
||||||
|
|
|
@ -41,7 +41,7 @@ MAGIC = PngImagePlugin._MAGIC
|
||||||
|
|
||||||
def chunk(cid: bytes, *data: bytes) -> bytes:
|
def chunk(cid: bytes, *data: bytes) -> bytes:
|
||||||
test_file = BytesIO()
|
test_file = BytesIO()
|
||||||
PngImagePlugin.putchunk(*(test_file, cid) + data)
|
PngImagePlugin.putchunk(test_file, cid, *data)
|
||||||
return test_file.getvalue()
|
return test_file.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
@ -424,8 +424,10 @@ class TestFilePng:
|
||||||
im = roundtrip(im, pnginfo=info)
|
im = roundtrip(im, pnginfo=info)
|
||||||
assert im.info == {"spam": "Eggs", "eggs": "Spam"}
|
assert im.info == {"spam": "Eggs", "eggs": "Spam"}
|
||||||
assert im.text == {"spam": "Eggs", "eggs": "Spam"}
|
assert im.text == {"spam": "Eggs", "eggs": "Spam"}
|
||||||
|
assert isinstance(im.text["spam"], PngImagePlugin.iTXt)
|
||||||
assert im.text["spam"].lang == "en"
|
assert im.text["spam"].lang == "en"
|
||||||
assert im.text["spam"].tkey == "Spam"
|
assert im.text["spam"].tkey == "Spam"
|
||||||
|
assert isinstance(im.text["eggs"], PngImagePlugin.iTXt)
|
||||||
assert im.text["eggs"].lang == "en"
|
assert im.text["eggs"].lang == "en"
|
||||||
assert im.text["eggs"].tkey == "Eggs"
|
assert im.text["eggs"].tkey == "Eggs"
|
||||||
|
|
||||||
|
@ -776,7 +778,7 @@ class TestFilePng:
|
||||||
|
|
||||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||||
|
|
||||||
sys.stdout = mystdout # type: ignore[assignment]
|
sys.stdout = mystdout
|
||||||
|
|
||||||
with Image.open(TEST_PNG_FILE) as im:
|
with Image.open(TEST_PNG_FILE) as im:
|
||||||
im.save(sys.stdout, "PNG")
|
im.save(sys.stdout, "PNG")
|
||||||
|
|
|
@ -373,7 +373,7 @@ def test_save_stdout(buffer: bool) -> None:
|
||||||
|
|
||||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||||
|
|
||||||
sys.stdout = mystdout # type: ignore[assignment]
|
sys.stdout = mystdout
|
||||||
|
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
im.save(sys.stdout, "PPM")
|
im.save(sys.stdout, "PPM")
|
||||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import AnyStr
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -92,7 +93,7 @@ def test_textsize(request: pytest.FixtureRequest, tmp_path: Path) -> None:
|
||||||
|
|
||||||
|
|
||||||
def _test_high_characters(
|
def _test_high_characters(
|
||||||
request: pytest.FixtureRequest, tmp_path: Path, message: str | bytes
|
request: pytest.FixtureRequest, tmp_path: Path, message: AnyStr
|
||||||
) -> None:
|
) -> None:
|
||||||
tempname = save_font(request, tmp_path)
|
tempname = save_font(request, tmp_path)
|
||||||
font = ImageFont.load(tempname)
|
font = ImageFont.load(tempname)
|
||||||
|
|
|
@ -90,6 +90,7 @@ class TestImageFile:
|
||||||
data = f.read()
|
data = f.read()
|
||||||
with ImageFile.Parser() as p:
|
with ImageFile.Parser() as p:
|
||||||
p.feed(data)
|
p.feed(data)
|
||||||
|
assert p.image is not None
|
||||||
assert (48, 48) == p.image.size
|
assert (48, 48) == p.image.size
|
||||||
|
|
||||||
@skip_unless_feature("webp")
|
@skip_unless_feature("webp")
|
||||||
|
@ -103,6 +104,7 @@ class TestImageFile:
|
||||||
assert not p.image
|
assert not p.image
|
||||||
|
|
||||||
p.feed(f.read())
|
p.feed(f.read())
|
||||||
|
assert p.image is not None
|
||||||
assert (128, 128) == p.image.size
|
assert (128, 128) == p.image.size
|
||||||
|
|
||||||
@skip_unless_feature("zlib")
|
@skip_unless_feature("zlib")
|
||||||
|
@ -125,7 +127,7 @@ class TestImageFile:
|
||||||
def test_raise_typeerror(self) -> None:
|
def test_raise_typeerror(self) -> None:
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
parser = ImageFile.Parser()
|
parser = ImageFile.Parser()
|
||||||
parser.feed(1)
|
parser.feed(1) # type: ignore[arg-type]
|
||||||
|
|
||||||
def test_negative_stride(self) -> None:
|
def test_negative_stride(self) -> None:
|
||||||
with open("Tests/images/raw_negative_stride.bin", "rb") as f:
|
with open("Tests/images/raw_negative_stride.bin", "rb") as f:
|
||||||
|
@ -303,7 +305,7 @@ class TestPyDecoder(CodecsTest):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
def test_decode(self) -> None:
|
def test_decode(self) -> None:
|
||||||
decoder = ImageFile.PyDecoder(None)
|
decoder = ImageFile.PyDecoder("")
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
decoder.decode(b"")
|
decoder.decode(b"")
|
||||||
|
|
||||||
|
@ -381,7 +383,7 @@ class TestPyEncoder(CodecsTest):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_encode(self) -> None:
|
def test_encode(self) -> None:
|
||||||
encoder = ImageFile.PyEncoder(None)
|
encoder = ImageFile.PyEncoder("")
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
encoder.encode(0)
|
encoder.encode(0)
|
||||||
|
|
||||||
|
@ -393,8 +395,9 @@ class TestPyEncoder(CodecsTest):
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
encoder.encode_to_pyfd()
|
encoder.encode_to_pyfd()
|
||||||
|
|
||||||
|
fh = BytesIO()
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
encoder.encode_to_file(None, None)
|
encoder.encode_to_file(fh, 0)
|
||||||
|
|
||||||
def test_zero_height(self) -> None:
|
def test_zero_height(self) -> None:
|
||||||
with pytest.raises(UnidentifiedImageError):
|
with pytest.raises(UnidentifiedImageError):
|
||||||
|
|
|
@ -717,14 +717,14 @@ def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:
|
||||||
|
|
||||||
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
|
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
|
||||||
_check_text(font, "Tests/images/variation_adobe.png", 11)
|
_check_text(font, "Tests/images/variation_adobe.png", 11)
|
||||||
for name in ["Bold", b"Bold"]:
|
for name in ("Bold", b"Bold"):
|
||||||
font.set_variation_by_name(name)
|
font.set_variation_by_name(name)
|
||||||
assert font.getname()[1] == "Bold"
|
assert font.getname()[1] == "Bold"
|
||||||
_check_text(font, "Tests/images/variation_adobe_name.png", 16)
|
_check_text(font, "Tests/images/variation_adobe_name.png", 16)
|
||||||
|
|
||||||
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
|
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
|
||||||
_check_text(font, "Tests/images/variation_tiny.png", 40)
|
_check_text(font, "Tests/images/variation_tiny.png", 40)
|
||||||
for name in ["200", b"200"]:
|
for name in ("200", b"200"):
|
||||||
font.set_variation_by_name(name)
|
font.set_variation_by_name(name)
|
||||||
assert font.getname()[1] == "200"
|
assert font.getname()[1] == "200"
|
||||||
_check_text(font, "Tests/images/variation_tiny_name.png", 40)
|
_check_text(font, "Tests/images/variation_tiny_name.png", 40)
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageMath
|
from PIL import Image, ImageMath
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,7 +23,7 @@ I = Image.new("I", (1, 1), 4) # noqa: E741
|
||||||
A2 = A.resize((2, 2))
|
A2 = A.resize((2, 2))
|
||||||
B2 = B.resize((2, 2))
|
B2 = B.resize((2, 2))
|
||||||
|
|
||||||
images = {"A": A, "B": B, "F": F, "I": I}
|
images: dict[str, Any] = {"A": A, "B": B, "F": F, "I": I}
|
||||||
|
|
||||||
|
|
||||||
def test_sanity() -> None:
|
def test_sanity() -> None:
|
||||||
|
@ -30,13 +34,13 @@ def test_sanity() -> None:
|
||||||
== "I 3"
|
== "I 3"
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images))
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], **images))
|
||||||
== "I 3"
|
== "I 3"
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(
|
ImageMath.lambda_eval(
|
||||||
lambda args: args["float"](args["A"]) + args["B"], images
|
lambda args: args["float"](args["A"]) + args["B"], **images
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== "F 3.0"
|
== "F 3.0"
|
||||||
|
@ -44,42 +48,47 @@ def test_sanity() -> None:
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(
|
ImageMath.lambda_eval(
|
||||||
lambda args: args["int"](args["float"](args["A"]) + args["B"]), images
|
lambda args: args["int"](args["float"](args["A"]) + args["B"]), **images
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== "I 3"
|
== "I 3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_options_deprecated() -> None:
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert ImageMath.lambda_eval(lambda args: 1, images) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_ops() -> None:
|
def test_ops() -> None:
|
||||||
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, images)) == "I -1"
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, **images)) == "I -1"
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images))
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], **images))
|
||||||
== "I 3"
|
== "I 3"
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], images))
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], **images))
|
||||||
== "I -1"
|
== "I -1"
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], images))
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], **images))
|
||||||
== "I 2"
|
== "I 2"
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], images))
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], **images))
|
||||||
== "I 0"
|
== "I 0"
|
||||||
)
|
)
|
||||||
assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, images)) == "I 4"
|
assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, **images)) == "I 4"
|
||||||
assert (
|
assert (
|
||||||
pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, images))
|
pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, **images))
|
||||||
== "I 2147483647"
|
== "I 2147483647"
|
||||||
)
|
)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(
|
ImageMath.lambda_eval(
|
||||||
lambda args: args["float"](args["A"]) + args["B"], images
|
lambda args: args["float"](args["A"]) + args["B"], **images
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== "F 3.0"
|
== "F 3.0"
|
||||||
|
@ -87,7 +96,7 @@ def test_ops() -> None:
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(
|
ImageMath.lambda_eval(
|
||||||
lambda args: args["float"](args["A"]) - args["B"], images
|
lambda args: args["float"](args["A"]) - args["B"], **images
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== "F -1.0"
|
== "F -1.0"
|
||||||
|
@ -95,7 +104,7 @@ def test_ops() -> None:
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(
|
ImageMath.lambda_eval(
|
||||||
lambda args: args["float"](args["A"]) * args["B"], images
|
lambda args: args["float"](args["A"]) * args["B"], **images
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== "F 2.0"
|
== "F 2.0"
|
||||||
|
@ -103,31 +112,33 @@ def test_ops() -> None:
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(
|
ImageMath.lambda_eval(
|
||||||
lambda args: args["float"](args["A"]) / args["B"], images
|
lambda args: args["float"](args["A"]) / args["B"], **images
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== "F 0.5"
|
== "F 0.5"
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
pixel(ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, images))
|
pixel(
|
||||||
|
ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, **images)
|
||||||
|
)
|
||||||
== "F 4.0"
|
== "F 4.0"
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, images)
|
ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, **images)
|
||||||
)
|
)
|
||||||
== "F 8589934592.0"
|
== "F 8589934592.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_logical() -> None:
|
def test_logical() -> None:
|
||||||
assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], images)) == 0
|
assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], **images)) == 0
|
||||||
assert (
|
assert (
|
||||||
pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], images))
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], **images))
|
||||||
== "L 2"
|
== "L 2"
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], images))
|
pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], **images))
|
||||||
== "L 1"
|
== "L 1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -136,7 +147,7 @@ def test_convert() -> None:
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(
|
ImageMath.lambda_eval(
|
||||||
lambda args: args["convert"](args["A"] + args["B"], "L"), images
|
lambda args: args["convert"](args["A"] + args["B"], "L"), **images
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== "L 3"
|
== "L 3"
|
||||||
|
@ -144,7 +155,7 @@ def test_convert() -> None:
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(
|
ImageMath.lambda_eval(
|
||||||
lambda args: args["convert"](args["A"] + args["B"], "1"), images
|
lambda args: args["convert"](args["A"] + args["B"], "1"), **images
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== "1 0"
|
== "1 0"
|
||||||
|
@ -152,7 +163,7 @@ def test_convert() -> None:
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(
|
ImageMath.lambda_eval(
|
||||||
lambda args: args["convert"](args["A"] + args["B"], "RGB"), images
|
lambda args: args["convert"](args["A"] + args["B"], "RGB"), **images
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== "RGB (3, 3, 3)"
|
== "RGB (3, 3, 3)"
|
||||||
|
@ -163,7 +174,7 @@ def test_compare() -> None:
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(
|
ImageMath.lambda_eval(
|
||||||
lambda args: args["min"](args["A"], args["B"]), images
|
lambda args: args["min"](args["A"], args["B"]), **images
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== "I 1"
|
== "I 1"
|
||||||
|
@ -171,13 +182,13 @@ def test_compare() -> None:
|
||||||
assert (
|
assert (
|
||||||
pixel(
|
pixel(
|
||||||
ImageMath.lambda_eval(
|
ImageMath.lambda_eval(
|
||||||
lambda args: args["max"](args["A"], args["B"]), images
|
lambda args: args["max"](args["A"], args["B"]), **images
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
== "I 2"
|
== "I 2"
|
||||||
)
|
)
|
||||||
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, images)) == "I 1"
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, **images)) == "I 1"
|
||||||
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, images)) == "I 0"
|
assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, **images)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
def test_one_image_larger() -> None:
|
def test_one_image_larger() -> None:
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageMath
|
from PIL import Image, ImageMath
|
||||||
|
@ -21,16 +23,16 @@ I = Image.new("I", (1, 1), 4) # noqa: E741
|
||||||
A2 = A.resize((2, 2))
|
A2 = A.resize((2, 2))
|
||||||
B2 = B.resize((2, 2))
|
B2 = B.resize((2, 2))
|
||||||
|
|
||||||
images = {"A": A, "B": B, "F": F, "I": I}
|
images: dict[str, Any] = {"A": A, "B": B, "F": F, "I": I}
|
||||||
|
|
||||||
|
|
||||||
def test_sanity() -> None:
|
def test_sanity() -> None:
|
||||||
assert ImageMath.unsafe_eval("1") == 1
|
assert ImageMath.unsafe_eval("1") == 1
|
||||||
assert ImageMath.unsafe_eval("1+A", A=2) == 3
|
assert ImageMath.unsafe_eval("1+A", A=2) == 3
|
||||||
assert pixel(ImageMath.unsafe_eval("A+B", A=A, B=B)) == "I 3"
|
assert pixel(ImageMath.unsafe_eval("A+B", A=A, B=B)) == "I 3"
|
||||||
assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3"
|
assert pixel(ImageMath.unsafe_eval("A+B", **images)) == "I 3"
|
||||||
assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0"
|
assert pixel(ImageMath.unsafe_eval("float(A)+B", **images)) == "F 3.0"
|
||||||
assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", images)) == "I 3"
|
assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", **images)) == "I 3"
|
||||||
|
|
||||||
|
|
||||||
def test_eval_deprecated() -> None:
|
def test_eval_deprecated() -> None:
|
||||||
|
@ -38,23 +40,28 @@ def test_eval_deprecated() -> None:
|
||||||
assert ImageMath.eval("1") == 1
|
assert ImageMath.eval("1") == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_options_deprecated() -> None:
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
assert ImageMath.unsafe_eval("1", images) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_ops() -> None:
|
def test_ops() -> None:
|
||||||
assert pixel(ImageMath.unsafe_eval("-A", images)) == "I -1"
|
assert pixel(ImageMath.unsafe_eval("-A", **images)) == "I -1"
|
||||||
assert pixel(ImageMath.unsafe_eval("+B", images)) == "L 2"
|
assert pixel(ImageMath.unsafe_eval("+B", **images)) == "L 2"
|
||||||
|
|
||||||
assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3"
|
assert pixel(ImageMath.unsafe_eval("A+B", **images)) == "I 3"
|
||||||
assert pixel(ImageMath.unsafe_eval("A-B", images)) == "I -1"
|
assert pixel(ImageMath.unsafe_eval("A-B", **images)) == "I -1"
|
||||||
assert pixel(ImageMath.unsafe_eval("A*B", images)) == "I 2"
|
assert pixel(ImageMath.unsafe_eval("A*B", **images)) == "I 2"
|
||||||
assert pixel(ImageMath.unsafe_eval("A/B", images)) == "I 0"
|
assert pixel(ImageMath.unsafe_eval("A/B", **images)) == "I 0"
|
||||||
assert pixel(ImageMath.unsafe_eval("B**2", images)) == "I 4"
|
assert pixel(ImageMath.unsafe_eval("B**2", **images)) == "I 4"
|
||||||
assert pixel(ImageMath.unsafe_eval("B**33", images)) == "I 2147483647"
|
assert pixel(ImageMath.unsafe_eval("B**33", **images)) == "I 2147483647"
|
||||||
|
|
||||||
assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0"
|
assert pixel(ImageMath.unsafe_eval("float(A)+B", **images)) == "F 3.0"
|
||||||
assert pixel(ImageMath.unsafe_eval("float(A)-B", images)) == "F -1.0"
|
assert pixel(ImageMath.unsafe_eval("float(A)-B", **images)) == "F -1.0"
|
||||||
assert pixel(ImageMath.unsafe_eval("float(A)*B", images)) == "F 2.0"
|
assert pixel(ImageMath.unsafe_eval("float(A)*B", **images)) == "F 2.0"
|
||||||
assert pixel(ImageMath.unsafe_eval("float(A)/B", images)) == "F 0.5"
|
assert pixel(ImageMath.unsafe_eval("float(A)/B", **images)) == "F 0.5"
|
||||||
assert pixel(ImageMath.unsafe_eval("float(B)**2", images)) == "F 4.0"
|
assert pixel(ImageMath.unsafe_eval("float(B)**2", **images)) == "F 4.0"
|
||||||
assert pixel(ImageMath.unsafe_eval("float(B)**33", images)) == "F 8589934592.0"
|
assert pixel(ImageMath.unsafe_eval("float(B)**33", **images)) == "F 8589934592.0"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -72,33 +79,33 @@ def test_prevent_exec(expression: str) -> None:
|
||||||
|
|
||||||
def test_prevent_double_underscores() -> None:
|
def test_prevent_double_underscores() -> None:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ImageMath.unsafe_eval("1", {"__": None})
|
ImageMath.unsafe_eval("1", __=None)
|
||||||
|
|
||||||
|
|
||||||
def test_prevent_builtins() -> None:
|
def test_prevent_builtins() -> None:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
ImageMath.unsafe_eval("(lambda: exec('exit()'))()", {"exec": None})
|
ImageMath.unsafe_eval("(lambda: exec('exit()'))()", exec=None)
|
||||||
|
|
||||||
|
|
||||||
def test_logical() -> None:
|
def test_logical() -> None:
|
||||||
assert pixel(ImageMath.unsafe_eval("not A", images)) == 0
|
assert pixel(ImageMath.unsafe_eval("not A", **images)) == 0
|
||||||
assert pixel(ImageMath.unsafe_eval("A and B", images)) == "L 2"
|
assert pixel(ImageMath.unsafe_eval("A and B", **images)) == "L 2"
|
||||||
assert pixel(ImageMath.unsafe_eval("A or B", images)) == "L 1"
|
assert pixel(ImageMath.unsafe_eval("A or B", **images)) == "L 1"
|
||||||
|
|
||||||
|
|
||||||
def test_convert() -> None:
|
def test_convert() -> None:
|
||||||
assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", images)) == "L 3"
|
assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", **images)) == "L 3"
|
||||||
assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", images)) == "1 0"
|
assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", **images)) == "1 0"
|
||||||
assert (
|
assert (
|
||||||
pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", images)) == "RGB (3, 3, 3)"
|
pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", **images)) == "RGB (3, 3, 3)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_compare() -> None:
|
def test_compare() -> None:
|
||||||
assert pixel(ImageMath.unsafe_eval("min(A, B)", images)) == "I 1"
|
assert pixel(ImageMath.unsafe_eval("min(A, B)", **images)) == "I 1"
|
||||||
assert pixel(ImageMath.unsafe_eval("max(A, B)", images)) == "I 2"
|
assert pixel(ImageMath.unsafe_eval("max(A, B)", **images)) == "I 2"
|
||||||
assert pixel(ImageMath.unsafe_eval("A == 1", images)) == "I 1"
|
assert pixel(ImageMath.unsafe_eval("A == 1", **images)) == "I 1"
|
||||||
assert pixel(ImageMath.unsafe_eval("A == 2", images)) == "I 0"
|
assert pixel(ImageMath.unsafe_eval("A == 2", **images)) == "I 0"
|
||||||
|
|
||||||
|
|
||||||
def test_one_image_larger() -> None:
|
def test_one_image_larger() -> None:
|
||||||
|
|
|
@ -109,3 +109,6 @@ def test_bitmapimage() -> None:
|
||||||
|
|
||||||
# reloaded = ImageTk.getimage(im_tk)
|
# reloaded = ImageTk.getimage(im_tk)
|
||||||
# assert_image_equal(reloaded, im)
|
# assert_image_equal(reloaded, im)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageTk.BitmapImage()
|
||||||
|
|
|
@ -57,6 +57,9 @@ class TestImageWinDib:
|
||||||
# Assert
|
# Assert
|
||||||
assert dib.size == (128, 128)
|
assert dib.size == (128, 128)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ImageWin.Dib(mode)
|
||||||
|
|
||||||
def test_dib_paste(self) -> None:
|
def test_dib_paste(self) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
im = hopper()
|
im = hopper()
|
||||||
|
|
|
@ -59,7 +59,7 @@ def test_stdout(buffer: bool) -> None:
|
||||||
|
|
||||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||||
|
|
||||||
sys.stdout = mystdout # type: ignore[assignment]
|
sys.stdout = mystdout
|
||||||
|
|
||||||
ps = PSDraw.PSDraw()
|
ps = PSDraw.PSDraw()
|
||||||
_create_document(ps)
|
_create_document(ps)
|
||||||
|
|
|
@ -30,28 +30,6 @@ def test_is_not_path(tmp_path: Path) -> None:
|
||||||
assert not it_is_not
|
assert not it_is_not
|
||||||
|
|
||||||
|
|
||||||
def test_is_directory() -> None:
|
|
||||||
# Arrange
|
|
||||||
directory = "Tests"
|
|
||||||
|
|
||||||
# Act
|
|
||||||
it_is = _util.is_directory(directory)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert it_is
|
|
||||||
|
|
||||||
|
|
||||||
def test_is_not_directory() -> None:
|
|
||||||
# Arrange
|
|
||||||
text = "abc"
|
|
||||||
|
|
||||||
# Act
|
|
||||||
it_is_not = _util.is_directory(text)
|
|
||||||
|
|
||||||
# Assert
|
|
||||||
assert not it_is_not
|
|
||||||
|
|
||||||
|
|
||||||
def test_deferred_error() -> None:
|
def test_deferred_error() -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,15 @@ ImageDraw.getdraw hints parameter
|
||||||
|
|
||||||
The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated.
|
The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated.
|
||||||
|
|
||||||
|
ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.0.0
|
||||||
|
|
||||||
|
The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and
|
||||||
|
:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more keyword
|
||||||
|
arguments can be used instead.
|
||||||
|
|
||||||
Removed features
|
Removed features
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
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()
|
>>> im.show()
|
||||||
|
|
||||||
|
.. image:: show_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The standard version of :py:meth:`~PIL.Image.Image.show` is not very
|
The standard version of :py:meth:`~PIL.Image.Image.show` is not very
|
||||||
|
@ -79,6 +82,9 @@ Convert files to JPEG
|
||||||
except OSError:
|
except OSError:
|
||||||
print("cannot convert", infile)
|
print("cannot convert", infile)
|
||||||
|
|
||||||
|
.. image:: ../../Tests/images/hopper.jpg
|
||||||
|
:align: center
|
||||||
|
|
||||||
A second argument can be supplied to the :py:meth:`~PIL.Image.Image.save`
|
A second argument can be supplied to the :py:meth:`~PIL.Image.Image.save`
|
||||||
method which explicitly specifies a file format. If you use a non-standard
|
method which explicitly specifies a file format. If you use a non-standard
|
||||||
extension, you must always specify the format this way:
|
extension, you must always specify the format this way:
|
||||||
|
@ -103,6 +109,9 @@ Create JPEG thumbnails
|
||||||
except OSError:
|
except OSError:
|
||||||
print("cannot create thumbnail for", infile)
|
print("cannot create thumbnail for", infile)
|
||||||
|
|
||||||
|
.. image:: thumbnail_hopper.jpg
|
||||||
|
:align: center
|
||||||
|
|
||||||
It is important to note that the library doesn’t decode or load the raster data
|
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
|
unless it really has to. When you open a file, the file header is read to
|
||||||
determine the file format and extract things like mode, size, and other
|
determine the file format and extract things like mode, size, and other
|
||||||
|
@ -140,16 +149,19 @@ Copying a subrectangle from an image
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
box = (100, 100, 400, 400)
|
box = (0, 0, 64, 64)
|
||||||
region = im.crop(box)
|
region = im.crop(box)
|
||||||
|
|
||||||
The region is defined by a 4-tuple, where coordinates are (left, upper, right,
|
The region is defined by a 4-tuple, where coordinates are (left, upper, right,
|
||||||
lower). The Python Imaging Library uses a coordinate system with (0, 0) in the
|
lower). The Python Imaging Library uses a coordinate system with (0, 0) in the
|
||||||
upper left corner. Also note that coordinates refer to positions between the
|
upper left corner. Also note that coordinates refer to positions between the
|
||||||
pixels, so the region in the above example is exactly 300x300 pixels.
|
pixels, so the region in the above example is exactly 64x64 pixels.
|
||||||
|
|
||||||
The region could now be processed in a certain manner and pasted back.
|
The region could now be processed in a certain manner and pasted back.
|
||||||
|
|
||||||
|
.. image:: cropped_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Processing a subrectangle, and pasting it back
|
Processing a subrectangle, and pasting it back
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -164,6 +176,9 @@ modes of the original image and the region do not need to match. If they don’t
|
||||||
the region is automatically converted before being pasted (see the section on
|
the region is automatically converted before being pasted (see the section on
|
||||||
:ref:`color-transforms` below for details).
|
:ref:`color-transforms` below for details).
|
||||||
|
|
||||||
|
.. image:: pasted_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Here’s an additional example:
|
Here’s an additional example:
|
||||||
|
|
||||||
Rolling an image
|
Rolling an image
|
||||||
|
@ -186,6 +201,9 @@ Rolling an image
|
||||||
|
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
.. image:: rolled_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Or if you would like to merge two images into a wider image:
|
Or if you would like to merge two images into a wider image:
|
||||||
|
|
||||||
Merging images
|
Merging images
|
||||||
|
@ -203,6 +221,9 @@ Merging images
|
||||||
|
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
.. image:: merged_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
For more advanced tricks, the paste method can also take a transparency mask as
|
For more advanced tricks, the paste method can also take a transparency mask as
|
||||||
an optional argument. In this mask, the value 255 indicates that the pasted
|
an optional argument. In this mask, the value 255 indicates that the pasted
|
||||||
image is opaque in that position (that is, the pasted image should be used as
|
image is opaque in that position (that is, the pasted image should be used as
|
||||||
|
@ -229,6 +250,9 @@ Note that for a single-band image, :py:meth:`~PIL.Image.Image.split` returns
|
||||||
the image itself. To work with individual color bands, you may want to convert
|
the image itself. To work with individual color bands, you may want to convert
|
||||||
the image to “RGB” first.
|
the image to “RGB” first.
|
||||||
|
|
||||||
|
.. image:: rebanded_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Geometrical transforms
|
Geometrical transforms
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
@ -245,6 +269,9 @@ Simple geometry transforms
|
||||||
out = im.resize((128, 128))
|
out = im.resize((128, 128))
|
||||||
out = im.rotate(45) # degrees counter-clockwise
|
out = im.rotate(45) # degrees counter-clockwise
|
||||||
|
|
||||||
|
.. image:: rotated_hopper_90.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
To rotate the image in 90 degree steps, you can either use the
|
To rotate the image in 90 degree steps, you can either use the
|
||||||
:py:meth:`~PIL.Image.Image.rotate` method or the
|
:py:meth:`~PIL.Image.Image.rotate` method or the
|
||||||
:py:meth:`~PIL.Image.Image.transpose` method. The latter can also be used to
|
:py:meth:`~PIL.Image.Image.transpose` method. The latter can also be used to
|
||||||
|
@ -256,11 +283,38 @@ Transposing an image
|
||||||
::
|
::
|
||||||
|
|
||||||
out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
||||||
|
|
||||||
|
.. image:: flip_left_right_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
|
out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
|
||||||
|
|
||||||
|
.. image:: flip_top_bottom_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
out = im.transpose(Image.Transpose.ROTATE_90)
|
out = im.transpose(Image.Transpose.ROTATE_90)
|
||||||
|
|
||||||
|
.. image:: rotated_hopper_90.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
out = im.transpose(Image.Transpose.ROTATE_180)
|
out = im.transpose(Image.Transpose.ROTATE_180)
|
||||||
|
|
||||||
|
.. image:: rotated_hopper_180.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
out = im.transpose(Image.Transpose.ROTATE_270)
|
out = im.transpose(Image.Transpose.ROTATE_270)
|
||||||
|
|
||||||
|
.. image:: rotated_hopper_270.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
``transpose(ROTATE)`` operations can also be performed identically with
|
``transpose(ROTATE)`` operations can also be performed identically with
|
||||||
:py:meth:`~PIL.Image.Image.rotate` operations, provided the ``expand`` flag is
|
:py:meth:`~PIL.Image.Image.rotate` operations, provided the ``expand`` flag is
|
||||||
true, to provide for the same changes to the image's size.
|
true, to provide for the same changes to the image's size.
|
||||||
|
@ -278,7 +332,7 @@ choose to resize relative to a given size.
|
||||||
|
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
size = (100, 150)
|
size = (100, 150)
|
||||||
with Image.open("Tests/images/hopper.webp") as im:
|
with Image.open("hopper.webp") as im:
|
||||||
ImageOps.contain(im, size).save("imageops_contain.webp")
|
ImageOps.contain(im, size).save("imageops_contain.webp")
|
||||||
ImageOps.cover(im, size).save("imageops_cover.webp")
|
ImageOps.cover(im, size).save("imageops_cover.webp")
|
||||||
ImageOps.fit(im, size).save("imageops_fit.webp")
|
ImageOps.fit(im, size).save("imageops_fit.webp")
|
||||||
|
@ -342,6 +396,9 @@ Applying filters
|
||||||
from PIL import ImageFilter
|
from PIL import ImageFilter
|
||||||
out = im.filter(ImageFilter.DETAIL)
|
out = im.filter(ImageFilter.DETAIL)
|
||||||
|
|
||||||
|
.. image:: enhanced_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Point Operations
|
Point Operations
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
@ -355,8 +412,11 @@ Applying point transforms
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
# multiply each pixel by 1.2
|
# multiply each pixel by 20
|
||||||
out = im.point(lambda i: i * 1.2)
|
out = im.point(lambda i: i * 20)
|
||||||
|
|
||||||
|
.. image:: transformed_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Using the above technique, you can quickly apply any simple expression to an
|
Using the above technique, you can quickly apply any simple expression to an
|
||||||
image. You can also combine the :py:meth:`~PIL.Image.Image.point` and
|
image. You can also combine the :py:meth:`~PIL.Image.Image.point` and
|
||||||
|
@ -388,6 +448,9 @@ Note the syntax used to create the mask::
|
||||||
|
|
||||||
imout = im.point(lambda i: expression and 255)
|
imout = im.point(lambda i: expression and 255)
|
||||||
|
|
||||||
|
.. image:: masked_hopper.webp
|
||||||
|
:align: center
|
||||||
|
|
||||||
Python only evaluates the portion of a logical expression as is necessary to
|
Python only evaluates the portion of a logical expression as is necessary to
|
||||||
determine the outcome, and returns the last value examined as the result of the
|
determine the outcome, and returns the last value examined as the result of the
|
||||||
expression. So if the expression above is false (0), Python does not look at
|
expression. So if the expression above is false (0), Python does not look at
|
||||||
|
@ -412,6 +475,10 @@ Enhancing images
|
||||||
enh = ImageEnhance.Contrast(im)
|
enh = ImageEnhance.Contrast(im)
|
||||||
enh.enhance(1.3).show("30% more contrast")
|
enh.enhance(1.3).show("30% more contrast")
|
||||||
|
|
||||||
|
|
||||||
|
.. image:: contrasted_hopper.jpg
|
||||||
|
:align: center
|
||||||
|
|
||||||
Image sequences
|
Image sequences
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -444,10 +511,43 @@ Reading sequences
|
||||||
As seen in this example, you’ll get an :py:exc:`EOFError` exception when the
|
As seen in this example, you’ll get an :py:exc:`EOFError` exception when the
|
||||||
sequence ends.
|
sequence ends.
|
||||||
|
|
||||||
|
Writing sequences
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
You can create animated GIFs with Pillow, e.g.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# List of image filenames
|
||||||
|
image_filenames = [
|
||||||
|
"hopper.jpg",
|
||||||
|
"rotated_hopper_270.jpg",
|
||||||
|
"rotated_hopper_180.jpg",
|
||||||
|
"rotated_hopper_90.jpg",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Open images and create a list
|
||||||
|
images = [Image.open(filename) for filename in image_filenames]
|
||||||
|
|
||||||
|
# Save the images as an animated GIF
|
||||||
|
images[0].save(
|
||||||
|
"animated_hopper.gif",
|
||||||
|
save_all=True,
|
||||||
|
append_images=images[1:],
|
||||||
|
duration=500, # duration of each frame in milliseconds
|
||||||
|
loop=0, # loop forever
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
.. image:: animated_hopper.gif
|
||||||
|
:align: center
|
||||||
|
|
||||||
The following class lets you use the for-statement to loop over the sequence:
|
The following class lets you use the for-statement to loop over the sequence:
|
||||||
|
|
||||||
Using the ImageSequence Iterator class
|
Using the :py:class:`~PIL.ImageSequence.Iterator` class
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
@ -467,25 +567,61 @@ Drawing PostScript
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image, PSDraw
|
||||||
from PIL import PSDraw
|
import os
|
||||||
|
|
||||||
with Image.open("hopper.ppm") as im:
|
# Define the PostScript file
|
||||||
title = "hopper"
|
ps_file = open("hopper.ps", "wb")
|
||||||
box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points
|
|
||||||
|
|
||||||
ps = PSDraw.PSDraw() # default is sys.stdout or sys.stdout.buffer
|
# Create a PSDraw object
|
||||||
ps.begin_document(title)
|
ps = PSDraw.PSDraw(ps_file)
|
||||||
|
|
||||||
# draw the image (75 dpi)
|
# Start the document
|
||||||
ps.image(box, im, 75)
|
ps.begin_document()
|
||||||
ps.rectangle(box)
|
|
||||||
|
|
||||||
# draw title
|
# Set the text to be drawn
|
||||||
ps.setfont("HelveticaNarrow-Bold", 36)
|
text = "Hopper"
|
||||||
ps.text((3 * 72, 4 * 72), title)
|
|
||||||
|
|
||||||
|
# Define the PostScript font
|
||||||
|
font_name = "Helvetica-Narrow-Bold"
|
||||||
|
font_size = 36
|
||||||
|
|
||||||
|
# Calculate text size (approximation as PSDraw doesn't provide direct method)
|
||||||
|
# Assuming average character width as 0.6 of the font size
|
||||||
|
text_width = len(text) * font_size * 0.6
|
||||||
|
text_height = font_size
|
||||||
|
|
||||||
|
# Set the position (top-center)
|
||||||
|
page_width, page_height = 595, 842 # A4 size in points
|
||||||
|
text_x = (page_width - text_width) // 2
|
||||||
|
text_y = page_height - text_height - 50 # Distance from the top of the page
|
||||||
|
|
||||||
|
# Load the image
|
||||||
|
image_path = "hopper.ppm" # Update this with your image path
|
||||||
|
with Image.open(image_path) as im:
|
||||||
|
# Resize the image if it's too large
|
||||||
|
im.thumbnail((page_width - 100, page_height // 2))
|
||||||
|
|
||||||
|
# Define the box where the image will be placed
|
||||||
|
img_x = (page_width - im.width) // 2
|
||||||
|
img_y = text_y + text_height - 200 # 200 points below the text
|
||||||
|
|
||||||
|
# Draw the image (75 dpi)
|
||||||
|
ps.image((img_x, img_y, img_x + im.width, img_y + im.height), im, 75)
|
||||||
|
|
||||||
|
# Draw the text
|
||||||
|
ps.setfont(font_name, font_size)
|
||||||
|
ps.text((text_x, text_y), text)
|
||||||
|
|
||||||
|
# End the document
|
||||||
ps.end_document()
|
ps.end_document()
|
||||||
|
ps_file.close()
|
||||||
|
|
||||||
|
.. image:: hopper_ps.webp
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
PostScript converted to PDF for display purposes
|
||||||
|
|
||||||
More on reading images
|
More on reading images
|
||||||
----------------------
|
----------------------
|
||||||
|
@ -553,7 +689,7 @@ Reading from a tar archive
|
||||||
|
|
||||||
from PIL import Image, TarIO
|
from PIL import Image, TarIO
|
||||||
|
|
||||||
fp = TarIO.TarIO("Tests/images/hopper.tar", "hopper.jpg")
|
fp = TarIO.TarIO("hopper.tar", "hopper.jpg")
|
||||||
im = Image.open(fp)
|
im = Image.open(fp)
|
||||||
|
|
||||||
|
|
||||||
|
@ -568,7 +704,6 @@ in the current directory can be saved as JPEGs at reduced quality.
|
||||||
import glob
|
import glob
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
def compress_image(source_path, dest_path):
|
def compress_image(source_path, dest_path):
|
||||||
with Image.open(source_path) as img:
|
with Image.open(source_path) as img:
|
||||||
if img.mode != "RGB":
|
if img.mode != "RGB":
|
||||||
|
|
|
@ -37,6 +37,11 @@ Example: Parse an image
|
||||||
Classes
|
Classes
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
.. autoclass:: PIL.ImageFile._Tile()
|
||||||
|
:member-order: bysource
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
.. autoclass:: PIL.ImageFile.Parser()
|
.. autoclass:: PIL.ImageFile.Parser()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
|
|
@ -91,3 +91,11 @@ Constants
|
||||||
Set to 1,000,000, to protect against potential DOS attacks. Pillow will
|
Set to 1,000,000, to protect against potential DOS attacks. Pillow will
|
||||||
raise a :py:exc:`ValueError` if the number of characters is over this limit. The
|
raise a :py:exc:`ValueError` if the number of characters is over this limit. The
|
||||||
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
||||||
|
|
||||||
|
Dictionaries
|
||||||
|
------------
|
||||||
|
|
||||||
|
.. autoclass:: Axis
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
|
@ -31,20 +31,21 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
|
||||||
b=im2
|
b=im2
|
||||||
)
|
)
|
||||||
|
|
||||||
.. py:function:: lambda_eval(expression, options)
|
.. py:function:: lambda_eval(expression, options, **kw)
|
||||||
|
|
||||||
Returns the result of an image function.
|
Returns the result of an image function.
|
||||||
|
|
||||||
:param expression: A function that receives a dictionary.
|
:param expression: A function that receives a dictionary.
|
||||||
:param options: Values to add to the function's dictionary, mapping image
|
:param options: Values to add to the function's dictionary. Note that the names
|
||||||
names to Image instances. You can use one or more keyword
|
must be valid Python identifiers. Deprecated.
|
||||||
arguments instead of a dictionary, as shown in the above
|
You can instead use one or more keyword arguments, as
|
||||||
example. Note that the names must be valid Python
|
shown in the above example.
|
||||||
identifiers.
|
:param \**kw: Values to add to the function's dictionary, mapping image names to
|
||||||
|
Image instances.
|
||||||
:return: An image, an integer value, a floating point value,
|
:return: An image, an integer value, a floating point value,
|
||||||
or a pixel tuple, depending on the expression.
|
or a pixel tuple, depending on the expression.
|
||||||
|
|
||||||
.. py:function:: unsafe_eval(expression, options)
|
.. py:function:: unsafe_eval(expression, options, **kw)
|
||||||
|
|
||||||
Evaluates an image expression.
|
Evaluates an image expression.
|
||||||
|
|
||||||
|
@ -61,11 +62,12 @@ Example: Using the :py:mod:`~PIL.ImageMath` module
|
||||||
:param expression: A string which uses the standard Python expression
|
:param expression: A string which uses the standard Python expression
|
||||||
syntax. In addition to the standard operators, you can
|
syntax. In addition to the standard operators, you can
|
||||||
also use the functions described below.
|
also use the functions described below.
|
||||||
:param options: Values to add to the function's dictionary, mapping image
|
:param options: Values to add to the evaluation context. Note that the names must
|
||||||
names to Image instances. You can use one or more keyword
|
be valid Python identifiers. Deprecated.
|
||||||
arguments instead of a dictionary, as shown in the above
|
You can instead use one or more keyword arguments, as
|
||||||
example. Note that the names must be valid Python
|
shown in the above example.
|
||||||
identifiers.
|
:param \**kw: Values to add to the evaluation context, mapping image names to Image
|
||||||
|
instances.
|
||||||
:return: An image, an integer value, a floating point value,
|
:return: An image, an integer value, a floating point value,
|
||||||
or a pixel tuple, depending on the expression.
|
or a pixel tuple, depending on the expression.
|
||||||
|
|
||||||
|
|
|
@ -78,3 +78,7 @@ on some Python versions.
|
||||||
|
|
||||||
An internal interface module previously known as :mod:`~PIL._imaging`,
|
An internal interface module previously known as :mod:`~PIL._imaging`,
|
||||||
implemented in :file:`_imaging.c`.
|
implemented in :file:`_imaging.c`.
|
||||||
|
|
||||||
|
.. py:class:: ImagingCore
|
||||||
|
|
||||||
|
A representation of the image data.
|
||||||
|
|
|
@ -43,10 +43,12 @@ similarly removed.
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
TODO
|
ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and
|
||||||
|
:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more
|
||||||
|
keyword arguments can be used instead.
|
||||||
|
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
|
|
@ -159,7 +159,4 @@ exclude = [
|
||||||
'^Tests/oss-fuzz/fuzz_font.py$',
|
'^Tests/oss-fuzz/fuzz_font.py$',
|
||||||
'^Tests/oss-fuzz/fuzz_pillow.py$',
|
'^Tests/oss-fuzz/fuzz_pillow.py$',
|
||||||
'^Tests/test_qt_image_qapplication.py$',
|
'^Tests/test_qt_image_qapplication.py$',
|
||||||
'^Tests/test_font_pcf_charsets.py$',
|
|
||||||
'^Tests/test_font_pcf.py$',
|
|
||||||
'^Tests/test_file_tar.py$',
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -313,6 +313,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||||
|
|
||||||
def _safe_read(self, length: int) -> bytes:
|
def _safe_read(self, length: int) -> bytes:
|
||||||
|
assert self.fd is not None
|
||||||
return ImageFile._safe_read(self.fd, length)
|
return ImageFile._safe_read(self.fd, length)
|
||||||
|
|
||||||
def _read_palette(self) -> list[tuple[int, int, int, int]]:
|
def _read_palette(self) -> list[tuple[int, int, int, int]]:
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import IO
|
from typing import IO, Any
|
||||||
|
|
||||||
from . import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
|
@ -72,16 +72,20 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
for k, v in COMPRESSIONS.items():
|
for k, v in COMPRESSIONS.items():
|
||||||
vars()[k] = v
|
vars()[k] = v
|
||||||
|
|
||||||
def _bitmap(self, header=0, offset=0):
|
def _bitmap(self, header: int = 0, offset: int = 0) -> None:
|
||||||
"""Read relevant info about the BMP"""
|
"""Read relevant info about the BMP"""
|
||||||
read, seek = self.fp.read, self.fp.seek
|
read, seek = self.fp.read, self.fp.seek
|
||||||
if header:
|
if header:
|
||||||
seek(header)
|
seek(header)
|
||||||
# read bmp header size @offset 14 (this is part of the header size)
|
# read bmp header size @offset 14 (this is part of the header size)
|
||||||
file_info = {"header_size": i32(read(4)), "direction": -1}
|
file_info: dict[str, bool | int | tuple[int, ...]] = {
|
||||||
|
"header_size": i32(read(4)),
|
||||||
|
"direction": -1,
|
||||||
|
}
|
||||||
|
|
||||||
# -------------------- If requested, read header at a specific position
|
# -------------------- If requested, read header at a specific position
|
||||||
# read the rest of the bmp header, without its size
|
# read the rest of the bmp header, without its size
|
||||||
|
assert isinstance(file_info["header_size"], int)
|
||||||
header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
|
header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
|
||||||
|
|
||||||
# ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1
|
# ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1
|
||||||
|
@ -92,7 +96,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
file_info["height"] = i16(header_data, 2)
|
file_info["height"] = i16(header_data, 2)
|
||||||
file_info["planes"] = i16(header_data, 4)
|
file_info["planes"] = i16(header_data, 4)
|
||||||
file_info["bits"] = i16(header_data, 6)
|
file_info["bits"] = i16(header_data, 6)
|
||||||
file_info["compression"] = self.RAW
|
file_info["compression"] = self.COMPRESSIONS["RAW"]
|
||||||
file_info["palette_padding"] = 3
|
file_info["palette_padding"] = 3
|
||||||
|
|
||||||
# --------------------------------------------- Windows Bitmap v3 to v5
|
# --------------------------------------------- Windows Bitmap v3 to v5
|
||||||
|
@ -122,8 +126,9 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
)
|
)
|
||||||
file_info["colors"] = i32(header_data, 28)
|
file_info["colors"] = i32(header_data, 28)
|
||||||
file_info["palette_padding"] = 4
|
file_info["palette_padding"] = 4
|
||||||
|
assert isinstance(file_info["pixels_per_meter"], tuple)
|
||||||
self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
|
self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
|
||||||
if file_info["compression"] == self.BITFIELDS:
|
if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]:
|
||||||
masks = ["r_mask", "g_mask", "b_mask"]
|
masks = ["r_mask", "g_mask", "b_mask"]
|
||||||
if len(header_data) >= 48:
|
if len(header_data) >= 48:
|
||||||
if len(header_data) >= 52:
|
if len(header_data) >= 52:
|
||||||
|
@ -144,6 +149,10 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
file_info["a_mask"] = 0x0
|
file_info["a_mask"] = 0x0
|
||||||
for mask in masks:
|
for mask in masks:
|
||||||
file_info[mask] = i32(read(4))
|
file_info[mask] = i32(read(4))
|
||||||
|
assert isinstance(file_info["r_mask"], int)
|
||||||
|
assert isinstance(file_info["g_mask"], int)
|
||||||
|
assert isinstance(file_info["b_mask"], int)
|
||||||
|
assert isinstance(file_info["a_mask"], int)
|
||||||
file_info["rgb_mask"] = (
|
file_info["rgb_mask"] = (
|
||||||
file_info["r_mask"],
|
file_info["r_mask"],
|
||||||
file_info["g_mask"],
|
file_info["g_mask"],
|
||||||
|
@ -164,24 +173,26 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
self._size = file_info["width"], file_info["height"]
|
self._size = file_info["width"], file_info["height"]
|
||||||
|
|
||||||
# ------- If color count was not found in the header, compute from bits
|
# ------- If color count was not found in the header, compute from bits
|
||||||
|
assert isinstance(file_info["bits"], int)
|
||||||
file_info["colors"] = (
|
file_info["colors"] = (
|
||||||
file_info["colors"]
|
file_info["colors"]
|
||||||
if file_info.get("colors", 0)
|
if file_info.get("colors", 0)
|
||||||
else (1 << file_info["bits"])
|
else (1 << file_info["bits"])
|
||||||
)
|
)
|
||||||
|
assert isinstance(file_info["colors"], int)
|
||||||
if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8:
|
if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8:
|
||||||
offset += 4 * file_info["colors"]
|
offset += 4 * file_info["colors"]
|
||||||
|
|
||||||
# ---------------------- Check bit depth for unusual unsupported values
|
# ---------------------- Check bit depth for unusual unsupported values
|
||||||
self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
|
self._mode, raw_mode = BIT2MODE.get(file_info["bits"], ("", ""))
|
||||||
if self.mode is None:
|
if not self.mode:
|
||||||
msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
|
msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
|
||||||
# ---------------- Process BMP with Bitfields compression (not palette)
|
# ---------------- Process BMP with Bitfields compression (not palette)
|
||||||
decoder_name = "raw"
|
decoder_name = "raw"
|
||||||
if file_info["compression"] == self.BITFIELDS:
|
if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]:
|
||||||
SUPPORTED = {
|
SUPPORTED: dict[int, list[tuple[int, ...]]] = {
|
||||||
32: [
|
32: [
|
||||||
(0xFF0000, 0xFF00, 0xFF, 0x0),
|
(0xFF0000, 0xFF00, 0xFF, 0x0),
|
||||||
(0xFF000000, 0xFF0000, 0xFF00, 0x0),
|
(0xFF000000, 0xFF0000, 0xFF00, 0x0),
|
||||||
|
@ -213,12 +224,14 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
file_info["bits"] == 32
|
file_info["bits"] == 32
|
||||||
and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
|
and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
|
||||||
):
|
):
|
||||||
|
assert isinstance(file_info["rgba_mask"], tuple)
|
||||||
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
|
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
|
||||||
self._mode = "RGBA" if "A" in raw_mode else self.mode
|
self._mode = "RGBA" if "A" in raw_mode else self.mode
|
||||||
elif (
|
elif (
|
||||||
file_info["bits"] in (24, 16)
|
file_info["bits"] in (24, 16)
|
||||||
and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
|
and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
|
||||||
):
|
):
|
||||||
|
assert isinstance(file_info["rgb_mask"], tuple)
|
||||||
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
|
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
|
||||||
else:
|
else:
|
||||||
msg = "Unsupported BMP bitfields layout"
|
msg = "Unsupported BMP bitfields layout"
|
||||||
|
@ -226,10 +239,13 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
else:
|
else:
|
||||||
msg = "Unsupported BMP bitfields layout"
|
msg = "Unsupported BMP bitfields layout"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
elif file_info["compression"] == self.RAW:
|
elif file_info["compression"] == self.COMPRESSIONS["RAW"]:
|
||||||
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
|
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
|
||||||
raw_mode, self._mode = "BGRA", "RGBA"
|
raw_mode, self._mode = "BGRA", "RGBA"
|
||||||
elif file_info["compression"] in (self.RLE8, self.RLE4):
|
elif file_info["compression"] in (
|
||||||
|
self.COMPRESSIONS["RLE8"],
|
||||||
|
self.COMPRESSIONS["RLE4"],
|
||||||
|
):
|
||||||
decoder_name = "bmp_rle"
|
decoder_name = "bmp_rle"
|
||||||
else:
|
else:
|
||||||
msg = f"Unsupported BMP compression ({file_info['compression']})"
|
msg = f"Unsupported BMP compression ({file_info['compression']})"
|
||||||
|
@ -242,6 +258,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
msg = f"Unsupported BMP Palette size ({file_info['colors']})"
|
msg = f"Unsupported BMP Palette size ({file_info['colors']})"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
else:
|
else:
|
||||||
|
assert isinstance(file_info["palette_padding"], int)
|
||||||
padding = file_info["palette_padding"]
|
padding = file_info["palette_padding"]
|
||||||
palette = read(padding * file_info["colors"])
|
palette = read(padding * file_info["colors"])
|
||||||
grayscale = True
|
grayscale = True
|
||||||
|
@ -269,10 +286,11 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# ---------------------------- Finally set the tile data for the plugin
|
# ---------------------------- Finally set the tile data for the plugin
|
||||||
self.info["compression"] = file_info["compression"]
|
self.info["compression"] = file_info["compression"]
|
||||||
args = [raw_mode]
|
args: list[Any] = [raw_mode]
|
||||||
if decoder_name == "bmp_rle":
|
if decoder_name == "bmp_rle":
|
||||||
args.append(file_info["compression"] == self.RLE4)
|
args.append(file_info["compression"] == self.COMPRESSIONS["RLE4"])
|
||||||
else:
|
else:
|
||||||
|
assert isinstance(file_info["width"], int)
|
||||||
args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
|
args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
|
||||||
args.append(file_info["direction"])
|
args.append(file_info["direction"])
|
||||||
self.tile = [
|
self.tile = [
|
||||||
|
|
|
@ -16,10 +16,11 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
from typing import IO, AnyStr, Generic, Literal
|
from collections.abc import Iterable
|
||||||
|
from typing import IO, AnyStr, NoReturn
|
||||||
|
|
||||||
|
|
||||||
class ContainerIO(Generic[AnyStr]):
|
class ContainerIO(IO[AnyStr]):
|
||||||
"""
|
"""
|
||||||
A file object that provides read access to a part of an existing
|
A file object that provides read access to a part of an existing
|
||||||
file (for example a TAR file).
|
file (for example a TAR file).
|
||||||
|
@ -45,7 +46,10 @@ class ContainerIO(Generic[AnyStr]):
|
||||||
def isatty(self) -> bool:
|
def isatty(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def seek(self, offset: int, mode: Literal[0, 1, 2] = io.SEEK_SET) -> None:
|
def seekable(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def seek(self, offset: int, mode: int = io.SEEK_SET) -> int:
|
||||||
"""
|
"""
|
||||||
Move file pointer.
|
Move file pointer.
|
||||||
|
|
||||||
|
@ -53,6 +57,7 @@ class ContainerIO(Generic[AnyStr]):
|
||||||
:param mode: Starting position. Use 0 for beginning of region, 1
|
:param mode: Starting position. Use 0 for beginning of region, 1
|
||||||
for current offset, and 2 for end of region. You cannot move
|
for current offset, and 2 for end of region. You cannot move
|
||||||
the pointer outside the defined region.
|
the pointer outside the defined region.
|
||||||
|
:returns: Offset from start of region, in bytes.
|
||||||
"""
|
"""
|
||||||
if mode == 1:
|
if mode == 1:
|
||||||
self.pos = self.pos + offset
|
self.pos = self.pos + offset
|
||||||
|
@ -63,6 +68,7 @@ class ContainerIO(Generic[AnyStr]):
|
||||||
# clamp
|
# clamp
|
||||||
self.pos = max(0, min(self.pos, self.length))
|
self.pos = max(0, min(self.pos, self.length))
|
||||||
self.fh.seek(self.offset + self.pos)
|
self.fh.seek(self.offset + self.pos)
|
||||||
|
return self.pos
|
||||||
|
|
||||||
def tell(self) -> int:
|
def tell(self) -> int:
|
||||||
"""
|
"""
|
||||||
|
@ -72,27 +78,32 @@ class ContainerIO(Generic[AnyStr]):
|
||||||
"""
|
"""
|
||||||
return self.pos
|
return self.pos
|
||||||
|
|
||||||
def read(self, n: int = 0) -> AnyStr:
|
def readable(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def read(self, n: int = -1) -> AnyStr:
|
||||||
"""
|
"""
|
||||||
Read data.
|
Read data.
|
||||||
|
|
||||||
:param n: Number of bytes to read. If omitted or zero,
|
:param n: Number of bytes to read. If omitted, zero or negative,
|
||||||
read until end of region.
|
read until end of region.
|
||||||
:returns: An 8-bit string.
|
:returns: An 8-bit string.
|
||||||
"""
|
"""
|
||||||
if n:
|
if n > 0:
|
||||||
n = min(n, self.length - self.pos)
|
n = min(n, self.length - self.pos)
|
||||||
else:
|
else:
|
||||||
n = self.length - self.pos
|
n = self.length - self.pos
|
||||||
if not n: # EOF
|
if n <= 0: # EOF
|
||||||
return b"" if "b" in self.fh.mode else "" # type: ignore[return-value]
|
return b"" if "b" in self.fh.mode else "" # type: ignore[return-value]
|
||||||
self.pos = self.pos + n
|
self.pos = self.pos + n
|
||||||
return self.fh.read(n)
|
return self.fh.read(n)
|
||||||
|
|
||||||
def readline(self) -> AnyStr:
|
def readline(self, n: int = -1) -> AnyStr:
|
||||||
"""
|
"""
|
||||||
Read a line of text.
|
Read a line of text.
|
||||||
|
|
||||||
|
:param n: Number of bytes to read. If omitted, zero or negative,
|
||||||
|
read until end of line.
|
||||||
:returns: An 8-bit string.
|
:returns: An 8-bit string.
|
||||||
"""
|
"""
|
||||||
s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
|
s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment]
|
||||||
|
@ -102,14 +113,16 @@ class ContainerIO(Generic[AnyStr]):
|
||||||
if not c:
|
if not c:
|
||||||
break
|
break
|
||||||
s = s + c
|
s = s + c
|
||||||
if c == newline_character:
|
if c == newline_character or len(s) == n:
|
||||||
break
|
break
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def readlines(self) -> list[AnyStr]:
|
def readlines(self, n: int | None = -1) -> list[AnyStr]:
|
||||||
"""
|
"""
|
||||||
Read multiple lines of text.
|
Read multiple lines of text.
|
||||||
|
|
||||||
|
:param n: Number of lines to read. If omitted, zero, negative or None,
|
||||||
|
read until end of region.
|
||||||
:returns: A list of 8-bit strings.
|
:returns: A list of 8-bit strings.
|
||||||
"""
|
"""
|
||||||
lines = []
|
lines = []
|
||||||
|
@ -118,4 +131,43 @@ class ContainerIO(Generic[AnyStr]):
|
||||||
if not s:
|
if not s:
|
||||||
break
|
break
|
||||||
lines.append(s)
|
lines.append(s)
|
||||||
|
if len(lines) == n:
|
||||||
|
break
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
def writable(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def write(self, b: AnyStr) -> NoReturn:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def writelines(self, lines: Iterable[AnyStr]) -> NoReturn:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def truncate(self, size: int | None = None) -> int:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def __enter__(self) -> ContainerIO[AnyStr]:
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args: object) -> None:
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def __iter__(self) -> ContainerIO[AnyStr]:
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self) -> AnyStr:
|
||||||
|
line = self.readline()
|
||||||
|
if not line:
|
||||||
|
msg = "end of region"
|
||||||
|
raise StopIteration(msg)
|
||||||
|
return line
|
||||||
|
|
||||||
|
def fileno(self) -> int:
|
||||||
|
return self.fh.fileno()
|
||||||
|
|
||||||
|
def flush(self) -> None:
|
||||||
|
self.fh.flush()
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
self.fh.close()
|
||||||
|
|
|
@ -65,7 +65,7 @@ def has_ghostscript() -> bool:
|
||||||
return gs_binary is not False
|
return gs_binary is not False
|
||||||
|
|
||||||
|
|
||||||
def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
def Ghostscript(tile, size, fp, scale=1, transparency: bool = False) -> Image.Image:
|
||||||
"""Render an image using Ghostscript"""
|
"""Render an image using Ghostscript"""
|
||||||
global gs_binary
|
global gs_binary
|
||||||
if not has_ghostscript():
|
if not has_ghostscript():
|
||||||
|
|
|
@ -25,7 +25,7 @@ from __future__ import annotations
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from math import ceil, log
|
from math import ceil, log
|
||||||
from typing import IO
|
from typing import IO, NamedTuple
|
||||||
|
|
||||||
from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
|
from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
|
@ -119,8 +119,22 @@ def _accept(prefix: bytes) -> bool:
|
||||||
return prefix[:4] == _MAGIC
|
return prefix[:4] == _MAGIC
|
||||||
|
|
||||||
|
|
||||||
|
class IconHeader(NamedTuple):
|
||||||
|
width: int
|
||||||
|
height: int
|
||||||
|
nb_color: int
|
||||||
|
reserved: int
|
||||||
|
planes: int
|
||||||
|
bpp: int
|
||||||
|
size: int
|
||||||
|
offset: int
|
||||||
|
dim: tuple[int, int]
|
||||||
|
square: int
|
||||||
|
color_depth: int
|
||||||
|
|
||||||
|
|
||||||
class IcoFile:
|
class IcoFile:
|
||||||
def __init__(self, buf) -> None:
|
def __init__(self, buf: IO[bytes]) -> None:
|
||||||
"""
|
"""
|
||||||
Parse image from file-like object containing ico file data
|
Parse image from file-like object containing ico file data
|
||||||
"""
|
"""
|
||||||
|
@ -141,51 +155,44 @@ class IcoFile:
|
||||||
for i in range(self.nb_items):
|
for i in range(self.nb_items):
|
||||||
s = buf.read(16)
|
s = buf.read(16)
|
||||||
|
|
||||||
icon_header = {
|
|
||||||
"width": s[0],
|
|
||||||
"height": s[1],
|
|
||||||
"nb_color": s[2], # No. of colors in image (0 if >=8bpp)
|
|
||||||
"reserved": s[3],
|
|
||||||
"planes": i16(s, 4),
|
|
||||||
"bpp": i16(s, 6),
|
|
||||||
"size": i32(s, 8),
|
|
||||||
"offset": i32(s, 12),
|
|
||||||
}
|
|
||||||
|
|
||||||
# See Wikipedia
|
# See Wikipedia
|
||||||
for j in ("width", "height"):
|
width = s[0] or 256
|
||||||
if not icon_header[j]:
|
height = s[1] or 256
|
||||||
icon_header[j] = 256
|
|
||||||
|
|
||||||
|
# No. of colors in image (0 if >=8bpp)
|
||||||
|
nb_color = s[2]
|
||||||
|
bpp = i16(s, 6)
|
||||||
|
icon_header = IconHeader(
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
nb_color=nb_color,
|
||||||
|
reserved=s[3],
|
||||||
|
planes=i16(s, 4),
|
||||||
|
bpp=i16(s, 6),
|
||||||
|
size=i32(s, 8),
|
||||||
|
offset=i32(s, 12),
|
||||||
|
dim=(width, height),
|
||||||
|
square=width * height,
|
||||||
# See Wikipedia notes about color depth.
|
# See Wikipedia notes about color depth.
|
||||||
# We need this just to differ images with equal sizes
|
# We need this just to differ images with equal sizes
|
||||||
icon_header["color_depth"] = (
|
color_depth=bpp or (nb_color != 0 and ceil(log(nb_color, 2))) or 256,
|
||||||
icon_header["bpp"]
|
|
||||||
or (
|
|
||||||
icon_header["nb_color"] != 0
|
|
||||||
and ceil(log(icon_header["nb_color"], 2))
|
|
||||||
)
|
)
|
||||||
or 256
|
|
||||||
)
|
|
||||||
|
|
||||||
icon_header["dim"] = (icon_header["width"], icon_header["height"])
|
|
||||||
icon_header["square"] = icon_header["width"] * icon_header["height"]
|
|
||||||
|
|
||||||
self.entry.append(icon_header)
|
self.entry.append(icon_header)
|
||||||
|
|
||||||
self.entry = sorted(self.entry, key=lambda x: x["color_depth"])
|
self.entry = sorted(self.entry, key=lambda x: x.color_depth)
|
||||||
# ICO images are usually squares
|
# ICO images are usually squares
|
||||||
self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True)
|
self.entry = sorted(self.entry, key=lambda x: x.square, reverse=True)
|
||||||
|
|
||||||
def sizes(self) -> set[tuple[int, int]]:
|
def sizes(self) -> set[tuple[int, int]]:
|
||||||
"""
|
"""
|
||||||
Get a list of all available icon sizes and color depths.
|
Get a set of all available icon sizes and color depths.
|
||||||
"""
|
"""
|
||||||
return {(h["width"], h["height"]) for h in self.entry}
|
return {(h.width, h.height) for h in self.entry}
|
||||||
|
|
||||||
def getentryindex(self, size: tuple[int, int], bpp: int | bool = False) -> int:
|
def getentryindex(self, size: tuple[int, int], bpp: int | bool = False) -> int:
|
||||||
for i, h in enumerate(self.entry):
|
for i, h in enumerate(self.entry):
|
||||||
if size == h["dim"] and (bpp is False or bpp == h["color_depth"]):
|
if size == h.dim and (bpp is False or bpp == h.color_depth):
|
||||||
return i
|
return i
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -202,9 +209,9 @@ class IcoFile:
|
||||||
|
|
||||||
header = self.entry[idx]
|
header = self.entry[idx]
|
||||||
|
|
||||||
self.buf.seek(header["offset"])
|
self.buf.seek(header.offset)
|
||||||
data = self.buf.read(8)
|
data = self.buf.read(8)
|
||||||
self.buf.seek(header["offset"])
|
self.buf.seek(header.offset)
|
||||||
|
|
||||||
im: Image.Image
|
im: Image.Image
|
||||||
if data[:8] == PngImagePlugin._MAGIC:
|
if data[:8] == PngImagePlugin._MAGIC:
|
||||||
|
@ -222,8 +229,7 @@ class IcoFile:
|
||||||
im.tile[0] = d, (0, 0) + im.size, o, a
|
im.tile[0] = d, (0, 0) + im.size, o, a
|
||||||
|
|
||||||
# figure out where AND mask image starts
|
# figure out where AND mask image starts
|
||||||
bpp = header["bpp"]
|
if header.bpp == 32:
|
||||||
if 32 == bpp:
|
|
||||||
# 32-bit color depth icon image allows semitransparent areas
|
# 32-bit color depth icon image allows semitransparent areas
|
||||||
# PIL's DIB format ignores transparency bits, recover them.
|
# PIL's DIB format ignores transparency bits, recover them.
|
||||||
# The DIB is packed in BGRX byte order where X is the alpha
|
# The DIB is packed in BGRX byte order where X is the alpha
|
||||||
|
@ -253,7 +259,7 @@ class IcoFile:
|
||||||
# padded row size * height / bits per char
|
# padded row size * height / bits per char
|
||||||
|
|
||||||
total_bytes = int((w * im.size[1]) / 8)
|
total_bytes = int((w * im.size[1]) / 8)
|
||||||
and_mask_offset = header["offset"] + header["size"] - total_bytes
|
and_mask_offset = header.offset + header.size - total_bytes
|
||||||
|
|
||||||
self.buf.seek(and_mask_offset)
|
self.buf.seek(and_mask_offset)
|
||||||
mask_data = self.buf.read(total_bytes)
|
mask_data = self.buf.read(total_bytes)
|
||||||
|
@ -307,7 +313,7 @@ class IcoImageFile(ImageFile.ImageFile):
|
||||||
def _open(self) -> None:
|
def _open(self) -> None:
|
||||||
self.ico = IcoFile(self.fp)
|
self.ico = IcoFile(self.fp)
|
||||||
self.info["sizes"] = self.ico.sizes()
|
self.info["sizes"] = self.ico.sizes()
|
||||||
self.size = self.ico.entry[0]["dim"]
|
self.size = self.ico.entry[0].dim
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
116
src/PIL/Image.py
|
@ -38,7 +38,7 @@ import struct
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
from collections.abc import Callable, MutableMapping, Sequence
|
from collections.abc import Callable, Iterator, MutableMapping, Sequence
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import (
|
from typing import (
|
||||||
|
@ -218,6 +218,8 @@ if hasattr(core, "DEFAULT_STRATEGY"):
|
||||||
# Registries
|
# Registries
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from xml.etree.ElementTree import Element
|
||||||
|
|
||||||
from . import ImageFile, ImagePalette
|
from . import ImageFile, ImagePalette
|
||||||
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
|
from ._typing import NumpyArray, StrOrBytesPath, TypeGuard
|
||||||
ID: list[str] = []
|
ID: list[str] = []
|
||||||
|
@ -241,9 +243,9 @@ ENCODERS: dict[str, type[ImageFile.PyEncoder]] = {}
|
||||||
_ENDIAN = "<" if sys.byteorder == "little" else ">"
|
_ENDIAN = "<" if sys.byteorder == "little" else ">"
|
||||||
|
|
||||||
|
|
||||||
def _conv_type_shape(im):
|
def _conv_type_shape(im: Image) -> tuple[tuple[int, ...], str]:
|
||||||
m = ImageMode.getmode(im.mode)
|
m = ImageMode.getmode(im.mode)
|
||||||
shape = (im.height, im.width)
|
shape: tuple[int, ...] = (im.height, im.width)
|
||||||
extra = len(m.bands)
|
extra = len(m.bands)
|
||||||
if extra != 1:
|
if extra != 1:
|
||||||
shape += (extra,)
|
shape += (extra,)
|
||||||
|
@ -466,40 +468,40 @@ def _getencoder(
|
||||||
|
|
||||||
|
|
||||||
class _E:
|
class _E:
|
||||||
def __init__(self, scale, offset) -> None:
|
def __init__(self, scale: float, offset: float) -> None:
|
||||||
self.scale = scale
|
self.scale = scale
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
|
|
||||||
def __neg__(self):
|
def __neg__(self) -> _E:
|
||||||
return _E(-self.scale, -self.offset)
|
return _E(-self.scale, -self.offset)
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other: _E | float) -> _E:
|
||||||
if isinstance(other, _E):
|
if isinstance(other, _E):
|
||||||
return _E(self.scale + other.scale, self.offset + other.offset)
|
return _E(self.scale + other.scale, self.offset + other.offset)
|
||||||
return _E(self.scale, self.offset + other)
|
return _E(self.scale, self.offset + other)
|
||||||
|
|
||||||
__radd__ = __add__
|
__radd__ = __add__
|
||||||
|
|
||||||
def __sub__(self, other):
|
def __sub__(self, other: _E | float) -> _E:
|
||||||
return self + -other
|
return self + -other
|
||||||
|
|
||||||
def __rsub__(self, other):
|
def __rsub__(self, other: _E | float) -> _E:
|
||||||
return other + -self
|
return other + -self
|
||||||
|
|
||||||
def __mul__(self, other):
|
def __mul__(self, other: _E | float) -> _E:
|
||||||
if isinstance(other, _E):
|
if isinstance(other, _E):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return _E(self.scale * other, self.offset * other)
|
return _E(self.scale * other, self.offset * other)
|
||||||
|
|
||||||
__rmul__ = __mul__
|
__rmul__ = __mul__
|
||||||
|
|
||||||
def __truediv__(self, other):
|
def __truediv__(self, other: _E | float) -> _E:
|
||||||
if isinstance(other, _E):
|
if isinstance(other, _E):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return _E(self.scale / other, self.offset / other)
|
return _E(self.scale / other, self.offset / other)
|
||||||
|
|
||||||
|
|
||||||
def _getscaleoffset(expr):
|
def _getscaleoffset(expr) -> tuple[float, float]:
|
||||||
a = expr(_E(1, 0))
|
a = expr(_E(1, 0))
|
||||||
return (a.scale, a.offset) if isinstance(a, _E) else (0, a)
|
return (a.scale, a.offset) if isinstance(a, _E) else (0, a)
|
||||||
|
|
||||||
|
@ -728,9 +730,9 @@ class Image:
|
||||||
return self._repr_image("JPEG")
|
return self._repr_image("JPEG")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def __array_interface__(self):
|
def __array_interface__(self) -> dict[str, str | bytes | int | tuple[int, ...]]:
|
||||||
# numpy array interface support
|
# numpy array interface support
|
||||||
new = {"version": 3}
|
new: dict[str, str | bytes | int | tuple[int, ...]] = {"version": 3}
|
||||||
try:
|
try:
|
||||||
if self.mode == "1":
|
if self.mode == "1":
|
||||||
# Binary images need to be extended from bits to bytes
|
# Binary images need to be extended from bits to bytes
|
||||||
|
@ -752,11 +754,11 @@ class Image:
|
||||||
new["shape"], new["typestr"] = _conv_type_shape(self)
|
new["shape"], new["typestr"] = _conv_type_shape(self)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self) -> list[Any]:
|
||||||
im_data = self.tobytes() # load image first
|
im_data = self.tobytes() # load image first
|
||||||
return [self.info, self.mode, self.size, self.getpalette(), im_data]
|
return [self.info, self.mode, self.size, self.getpalette(), im_data]
|
||||||
|
|
||||||
def __setstate__(self, state) -> None:
|
def __setstate__(self, state: list[Any]) -> None:
|
||||||
self._prepare()
|
self._prepare()
|
||||||
info, mode, size, palette, data = state
|
info, mode, size, palette, data = state
|
||||||
self.info = info
|
self.info = info
|
||||||
|
@ -1428,7 +1430,7 @@ class Image:
|
||||||
return out
|
return out
|
||||||
return self.im.getcolors(maxcolors)
|
return self.im.getcolors(maxcolors)
|
||||||
|
|
||||||
def getdata(self, band: int | None = None):
|
def getdata(self, band: int | None = None) -> core.ImagingCore:
|
||||||
"""
|
"""
|
||||||
Returns the contents of this image as a sequence object
|
Returns the contents of this image as a sequence object
|
||||||
containing pixel values. The sequence object is flattened, so
|
containing pixel values. The sequence object is flattened, so
|
||||||
|
@ -1477,8 +1479,8 @@ class Image:
|
||||||
def get_name(tag: str) -> str:
|
def get_name(tag: str) -> str:
|
||||||
return re.sub("^{[^}]+}", "", tag)
|
return re.sub("^{[^}]+}", "", tag)
|
||||||
|
|
||||||
def get_value(element):
|
def get_value(element: Element) -> str | dict[str, Any] | None:
|
||||||
value = {get_name(k): v for k, v in element.attrib.items()}
|
value: dict[str, Any] = {get_name(k): v for k, v in element.attrib.items()}
|
||||||
children = list(element)
|
children = list(element)
|
||||||
if children:
|
if children:
|
||||||
for child in children:
|
for child in children:
|
||||||
|
@ -1691,7 +1693,9 @@ class Image:
|
||||||
x, y = self.im.getprojection()
|
x, y = self.im.getprojection()
|
||||||
return list(x), list(y)
|
return list(x), list(y)
|
||||||
|
|
||||||
def histogram(self, mask: Image | None = None, extrema=None) -> list[int]:
|
def histogram(
|
||||||
|
self, mask: Image | None = None, extrema: tuple[float, float] | None = None
|
||||||
|
) -> list[int]:
|
||||||
"""
|
"""
|
||||||
Returns a histogram for the image. The histogram is returned as a
|
Returns a histogram for the image. The histogram is returned as a
|
||||||
list of pixel counts, one for each pixel value in the source
|
list of pixel counts, one for each pixel value in the source
|
||||||
|
@ -1717,12 +1721,14 @@ class Image:
|
||||||
mask.load()
|
mask.load()
|
||||||
return self.im.histogram((0, 0), mask.im)
|
return self.im.histogram((0, 0), mask.im)
|
||||||
if self.mode in ("I", "F"):
|
if self.mode in ("I", "F"):
|
||||||
if extrema is None:
|
return self.im.histogram(
|
||||||
extrema = self.getextrema()
|
extrema if extrema is not None else self.getextrema()
|
||||||
return self.im.histogram(extrema)
|
)
|
||||||
return self.im.histogram()
|
return self.im.histogram()
|
||||||
|
|
||||||
def entropy(self, mask=None, extrema=None):
|
def entropy(
|
||||||
|
self, mask: Image | None = None, extrema: tuple[float, float] | None = None
|
||||||
|
) -> float:
|
||||||
"""
|
"""
|
||||||
Calculates and returns the entropy for the image.
|
Calculates and returns the entropy for the image.
|
||||||
|
|
||||||
|
@ -1743,9 +1749,9 @@ class Image:
|
||||||
mask.load()
|
mask.load()
|
||||||
return self.im.entropy((0, 0), mask.im)
|
return self.im.entropy((0, 0), mask.im)
|
||||||
if self.mode in ("I", "F"):
|
if self.mode in ("I", "F"):
|
||||||
if extrema is None:
|
return self.im.entropy(
|
||||||
extrema = self.getextrema()
|
extrema if extrema is not None else self.getextrema()
|
||||||
return self.im.entropy(extrema)
|
)
|
||||||
return self.im.entropy()
|
return self.im.entropy()
|
||||||
|
|
||||||
def paste(
|
def paste(
|
||||||
|
@ -2006,7 +2012,7 @@ class Image:
|
||||||
|
|
||||||
def putdata(
|
def putdata(
|
||||||
self,
|
self,
|
||||||
data: Sequence[float] | Sequence[Sequence[int]] | NumpyArray,
|
data: Sequence[float] | Sequence[Sequence[int]] | core.ImagingCore | NumpyArray,
|
||||||
scale: float = 1.0,
|
scale: float = 1.0,
|
||||||
offset: float = 0.0,
|
offset: float = 0.0,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -2194,7 +2200,12 @@ class Image:
|
||||||
|
|
||||||
return m_im
|
return m_im
|
||||||
|
|
||||||
def _get_safe_box(self, size, resample, box):
|
def _get_safe_box(
|
||||||
|
self,
|
||||||
|
size: tuple[int, int],
|
||||||
|
resample: Resampling,
|
||||||
|
box: tuple[float, float, float, float],
|
||||||
|
) -> tuple[int, int, int, int]:
|
||||||
"""Expands the box so it includes adjacent pixels
|
"""Expands the box so it includes adjacent pixels
|
||||||
that may be used by resampling with the given resampling filter.
|
that may be used by resampling with the given resampling filter.
|
||||||
"""
|
"""
|
||||||
|
@ -2304,7 +2315,7 @@ class Image:
|
||||||
factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1
|
factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1
|
||||||
factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1
|
factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1
|
||||||
if factor_x > 1 or factor_y > 1:
|
if factor_x > 1 or factor_y > 1:
|
||||||
reduce_box = self._get_safe_box(size, resample, box)
|
reduce_box = self._get_safe_box(size, cast(Resampling, resample), box)
|
||||||
factor = (factor_x, factor_y)
|
factor = (factor_x, factor_y)
|
||||||
self = (
|
self = (
|
||||||
self.reduce(factor, box=reduce_box)
|
self.reduce(factor, box=reduce_box)
|
||||||
|
@ -2440,7 +2451,7 @@ class Image:
|
||||||
0.0,
|
0.0,
|
||||||
]
|
]
|
||||||
|
|
||||||
def transform(x, y, matrix):
|
def transform(x: float, y: float, matrix: list[float]) -> tuple[float, float]:
|
||||||
(a, b, c, d, e, f) = matrix
|
(a, b, c, d, e, f) = matrix
|
||||||
return a * x + b * y + c, d * x + e * y + f
|
return a * x + b * y + c, d * x + e * y + f
|
||||||
|
|
||||||
|
@ -2455,9 +2466,9 @@ class Image:
|
||||||
xx = []
|
xx = []
|
||||||
yy = []
|
yy = []
|
||||||
for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
|
for x, y in ((0, 0), (w, 0), (w, h), (0, h)):
|
||||||
x, y = transform(x, y, matrix)
|
transformed_x, transformed_y = transform(x, y, matrix)
|
||||||
xx.append(x)
|
xx.append(transformed_x)
|
||||||
yy.append(y)
|
yy.append(transformed_y)
|
||||||
nw = math.ceil(max(xx)) - math.floor(min(xx))
|
nw = math.ceil(max(xx)) - math.floor(min(xx))
|
||||||
nh = math.ceil(max(yy)) - math.floor(min(yy))
|
nh = math.ceil(max(yy)) - math.floor(min(yy))
|
||||||
|
|
||||||
|
@ -2715,7 +2726,7 @@ class Image:
|
||||||
provided_size = tuple(map(math.floor, size))
|
provided_size = tuple(map(math.floor, size))
|
||||||
|
|
||||||
def preserve_aspect_ratio() -> tuple[int, int] | None:
|
def preserve_aspect_ratio() -> tuple[int, int] | None:
|
||||||
def round_aspect(number, key):
|
def round_aspect(number: float, key: Callable[[int], float]) -> int:
|
||||||
return max(min(math.floor(number), math.ceil(number), key=key), 1)
|
return max(min(math.floor(number), math.ceil(number), key=key), 1)
|
||||||
|
|
||||||
x, y = provided_size
|
x, y = provided_size
|
||||||
|
@ -2859,7 +2870,13 @@ class Image:
|
||||||
return im
|
return im
|
||||||
|
|
||||||
def __transformer(
|
def __transformer(
|
||||||
self, box, image, method, data, resample=Resampling.NEAREST, fill=1
|
self,
|
||||||
|
box: tuple[int, int, int, int],
|
||||||
|
image: Image,
|
||||||
|
method,
|
||||||
|
data,
|
||||||
|
resample: int = Resampling.NEAREST,
|
||||||
|
fill: bool = True,
|
||||||
):
|
):
|
||||||
w = box[2] - box[0]
|
w = box[2] - box[0]
|
||||||
h = box[3] - box[1]
|
h = box[3] - box[1]
|
||||||
|
@ -2909,11 +2926,12 @@ class Image:
|
||||||
Resampling.BICUBIC,
|
Resampling.BICUBIC,
|
||||||
):
|
):
|
||||||
if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS):
|
if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS):
|
||||||
msg = {
|
unusable: dict[int, str] = {
|
||||||
Resampling.BOX: "Image.Resampling.BOX",
|
Resampling.BOX: "Image.Resampling.BOX",
|
||||||
Resampling.HAMMING: "Image.Resampling.HAMMING",
|
Resampling.HAMMING: "Image.Resampling.HAMMING",
|
||||||
Resampling.LANCZOS: "Image.Resampling.LANCZOS",
|
Resampling.LANCZOS: "Image.Resampling.LANCZOS",
|
||||||
}[resample] + f" ({resample}) cannot be used."
|
}
|
||||||
|
msg = unusable[resample] + f" ({resample}) cannot be used."
|
||||||
else:
|
else:
|
||||||
msg = f"Unknown resampling filter ({resample})."
|
msg = f"Unknown resampling filter ({resample})."
|
||||||
|
|
||||||
|
@ -3299,7 +3317,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
|
||||||
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
|
return frombuffer(mode, size, obj, "raw", rawmode, 0, 1)
|
||||||
|
|
||||||
|
|
||||||
def fromqimage(im):
|
def fromqimage(im) -> ImageFile.ImageFile:
|
||||||
"""Creates an image instance from a QImage image"""
|
"""Creates an image instance from a QImage image"""
|
||||||
from . import ImageQt
|
from . import ImageQt
|
||||||
|
|
||||||
|
@ -3309,7 +3327,7 @@ def fromqimage(im):
|
||||||
return ImageQt.fromqimage(im)
|
return ImageQt.fromqimage(im)
|
||||||
|
|
||||||
|
|
||||||
def fromqpixmap(im):
|
def fromqpixmap(im) -> ImageFile.ImageFile:
|
||||||
"""Creates an image instance from a QPixmap image"""
|
"""Creates an image instance from a QPixmap image"""
|
||||||
from . import ImageQt
|
from . import ImageQt
|
||||||
|
|
||||||
|
@ -3856,7 +3874,7 @@ class Exif(_ExifBase):
|
||||||
print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99
|
print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99
|
||||||
"""
|
"""
|
||||||
|
|
||||||
endian = None
|
endian: str | None = None
|
||||||
bigtiff = False
|
bigtiff = False
|
||||||
_loaded = False
|
_loaded = False
|
||||||
|
|
||||||
|
@ -3880,7 +3898,7 @@ class Exif(_ExifBase):
|
||||||
# returns a dict with any single item tuples/lists as individual values
|
# returns a dict with any single item tuples/lists as individual values
|
||||||
return {k: self._fixup(v) for k, v in src_dict.items()}
|
return {k: self._fixup(v) for k, v in src_dict.items()}
|
||||||
|
|
||||||
def _get_ifd_dict(self, offset, group=None):
|
def _get_ifd_dict(self, offset: int, group: int | None = None):
|
||||||
try:
|
try:
|
||||||
# an offset pointer to the location of the nested embedded IFD.
|
# an offset pointer to the location of the nested embedded IFD.
|
||||||
# It should be a long, but may be corrupted.
|
# It should be a long, but may be corrupted.
|
||||||
|
@ -3894,7 +3912,7 @@ class Exif(_ExifBase):
|
||||||
info.load(self.fp)
|
info.load(self.fp)
|
||||||
return self._fixup_dict(info)
|
return self._fixup_dict(info)
|
||||||
|
|
||||||
def _get_head(self):
|
def _get_head(self) -> bytes:
|
||||||
version = b"\x2B" if self.bigtiff else b"\x2A"
|
version = b"\x2B" if self.bigtiff else b"\x2A"
|
||||||
if self.endian == "<":
|
if self.endian == "<":
|
||||||
head = b"II" + version + b"\x00" + o32le(8)
|
head = b"II" + version + b"\x00" + o32le(8)
|
||||||
|
@ -3905,7 +3923,7 @@ class Exif(_ExifBase):
|
||||||
head += b"\x00\x00\x00\x00"
|
head += b"\x00\x00\x00\x00"
|
||||||
return head
|
return head
|
||||||
|
|
||||||
def load(self, data):
|
def load(self, data: bytes) -> None:
|
||||||
# Extract EXIF information. This is highly experimental,
|
# Extract EXIF information. This is highly experimental,
|
||||||
# and is likely to be replaced with something better in a future
|
# and is likely to be replaced with something better in a future
|
||||||
# version.
|
# version.
|
||||||
|
@ -3924,7 +3942,7 @@ class Exif(_ExifBase):
|
||||||
self._info = None
|
self._info = None
|
||||||
return
|
return
|
||||||
|
|
||||||
self.fp = io.BytesIO(data)
|
self.fp: IO[bytes] = io.BytesIO(data)
|
||||||
self.head = self.fp.read(8)
|
self.head = self.fp.read(8)
|
||||||
# process dictionary
|
# process dictionary
|
||||||
from . import TiffImagePlugin
|
from . import TiffImagePlugin
|
||||||
|
@ -3934,7 +3952,7 @@ class Exif(_ExifBase):
|
||||||
self.fp.seek(self._info.next)
|
self.fp.seek(self._info.next)
|
||||||
self._info.load(self.fp)
|
self._info.load(self.fp)
|
||||||
|
|
||||||
def load_from_fp(self, fp, offset=None):
|
def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None:
|
||||||
self._loaded_exif = None
|
self._loaded_exif = None
|
||||||
self._data.clear()
|
self._data.clear()
|
||||||
self._hidden_data.clear()
|
self._hidden_data.clear()
|
||||||
|
@ -4115,16 +4133,16 @@ class Exif(_ExifBase):
|
||||||
keys.update(self._info)
|
keys.update(self._info)
|
||||||
return len(keys)
|
return len(keys)
|
||||||
|
|
||||||
def __getitem__(self, tag):
|
def __getitem__(self, tag: int):
|
||||||
if self._info is not None and tag not in self._data and tag in self._info:
|
if self._info is not None and tag not in self._data and tag in self._info:
|
||||||
self._data[tag] = self._fixup(self._info[tag])
|
self._data[tag] = self._fixup(self._info[tag])
|
||||||
del self._info[tag]
|
del self._info[tag]
|
||||||
return self._data[tag]
|
return self._data[tag]
|
||||||
|
|
||||||
def __contains__(self, tag) -> bool:
|
def __contains__(self, tag: object) -> bool:
|
||||||
return tag in self._data or (self._info is not None and tag in self._info)
|
return tag in self._data or (self._info is not None and tag in self._info)
|
||||||
|
|
||||||
def __setitem__(self, tag, value) -> None:
|
def __setitem__(self, tag: int, value) -> None:
|
||||||
if self._info is not None and tag in self._info:
|
if self._info is not None and tag in self._info:
|
||||||
del self._info[tag]
|
del self._info[tag]
|
||||||
self._data[tag] = value
|
self._data[tag] = value
|
||||||
|
@ -4135,7 +4153,7 @@ class Exif(_ExifBase):
|
||||||
else:
|
else:
|
||||||
del self._data[tag]
|
del self._data[tag]
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> Iterator[int]:
|
||||||
keys = set(self._data)
|
keys = set(self._data)
|
||||||
if self._info is not None:
|
if self._info is not None:
|
||||||
keys.update(self._info)
|
keys.update(self._info)
|
||||||
|
|
|
@ -36,7 +36,7 @@ import numbers
|
||||||
import struct
|
import struct
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import TYPE_CHECKING, AnyStr, Callable, Union, cast
|
from typing import TYPE_CHECKING, Any, AnyStr, Callable, Union, cast
|
||||||
|
|
||||||
from . import Image, ImageColor
|
from . import Image, ImageColor
|
||||||
from ._deprecate import deprecate
|
from ._deprecate import deprecate
|
||||||
|
@ -561,7 +561,12 @@ class ImageDraw:
|
||||||
def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
|
def _multiline_split(self, text: AnyStr) -> list[AnyStr]:
|
||||||
return text.split("\n" if isinstance(text, str) else b"\n")
|
return text.split("\n" if isinstance(text, str) else b"\n")
|
||||||
|
|
||||||
def _multiline_spacing(self, font, spacing, stroke_width):
|
def _multiline_spacing(
|
||||||
|
self,
|
||||||
|
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
|
||||||
|
spacing: float,
|
||||||
|
stroke_width: float,
|
||||||
|
) -> float:
|
||||||
return (
|
return (
|
||||||
self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
|
self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3]
|
||||||
+ stroke_width
|
+ stroke_width
|
||||||
|
@ -571,25 +576,25 @@ class ImageDraw:
|
||||||
def text(
|
def text(
|
||||||
self,
|
self,
|
||||||
xy: tuple[float, float],
|
xy: tuple[float, float],
|
||||||
text: str,
|
text: AnyStr,
|
||||||
fill=None,
|
fill: _Ink | None = None,
|
||||||
font: (
|
font: (
|
||||||
ImageFont.ImageFont
|
ImageFont.ImageFont
|
||||||
| ImageFont.FreeTypeFont
|
| ImageFont.FreeTypeFont
|
||||||
| ImageFont.TransposedFont
|
| ImageFont.TransposedFont
|
||||||
| None
|
| None
|
||||||
) = None,
|
) = None,
|
||||||
anchor=None,
|
anchor: str | None = None,
|
||||||
spacing=4,
|
spacing: float = 4,
|
||||||
align="left",
|
align: str = "left",
|
||||||
direction=None,
|
direction: str | None = None,
|
||||||
features=None,
|
features: list[str] | None = None,
|
||||||
language=None,
|
language: str | None = None,
|
||||||
stroke_width=0,
|
stroke_width: float = 0,
|
||||||
stroke_fill=None,
|
stroke_fill: _Ink | None = None,
|
||||||
embedded_color=False,
|
embedded_color: bool = False,
|
||||||
*args,
|
*args: Any,
|
||||||
**kwargs,
|
**kwargs: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw text."""
|
"""Draw text."""
|
||||||
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
||||||
|
@ -623,15 +628,14 @@ class ImageDraw:
|
||||||
return fill_ink
|
return fill_ink
|
||||||
return ink
|
return ink
|
||||||
|
|
||||||
def draw_text(ink, stroke_width=0, stroke_offset=None) -> None:
|
def draw_text(ink: int, stroke_width: float = 0) -> None:
|
||||||
mode = self.fontmode
|
mode = self.fontmode
|
||||||
if stroke_width == 0 and embedded_color:
|
if stroke_width == 0 and embedded_color:
|
||||||
mode = "RGBA"
|
mode = "RGBA"
|
||||||
coord = []
|
coord = []
|
||||||
start = []
|
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
coord.append(int(xy[i]))
|
coord.append(int(xy[i]))
|
||||||
start.append(math.modf(xy[i])[0])
|
start = (math.modf(xy[0])[0], math.modf(xy[1])[0])
|
||||||
try:
|
try:
|
||||||
mask, offset = font.getmask2( # type: ignore[union-attr,misc]
|
mask, offset = font.getmask2( # type: ignore[union-attr,misc]
|
||||||
text,
|
text,
|
||||||
|
@ -664,8 +668,6 @@ class ImageDraw:
|
||||||
)
|
)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
mask = font.getmask(text)
|
mask = font.getmask(text)
|
||||||
if stroke_offset:
|
|
||||||
coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]]
|
|
||||||
if mode == "RGBA":
|
if mode == "RGBA":
|
||||||
# font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
|
# font.getmask2(mode="RGBA") returns color in RGB bands and mask in A
|
||||||
# extract mask and set text alpha
|
# extract mask and set text alpha
|
||||||
|
@ -699,25 +701,25 @@ class ImageDraw:
|
||||||
def multiline_text(
|
def multiline_text(
|
||||||
self,
|
self,
|
||||||
xy: tuple[float, float],
|
xy: tuple[float, float],
|
||||||
text: str,
|
text: AnyStr,
|
||||||
fill=None,
|
fill: _Ink | None = None,
|
||||||
font: (
|
font: (
|
||||||
ImageFont.ImageFont
|
ImageFont.ImageFont
|
||||||
| ImageFont.FreeTypeFont
|
| ImageFont.FreeTypeFont
|
||||||
| ImageFont.TransposedFont
|
| ImageFont.TransposedFont
|
||||||
| None
|
| None
|
||||||
) = None,
|
) = None,
|
||||||
anchor=None,
|
anchor: str | None = None,
|
||||||
spacing=4,
|
spacing: float = 4,
|
||||||
align="left",
|
align: str = "left",
|
||||||
direction=None,
|
direction: str | None = None,
|
||||||
features=None,
|
features: list[str] | None = None,
|
||||||
language=None,
|
language: str | None = None,
|
||||||
stroke_width=0,
|
stroke_width: float = 0,
|
||||||
stroke_fill=None,
|
stroke_fill: _Ink | None = None,
|
||||||
embedded_color=False,
|
embedded_color: bool = False,
|
||||||
*,
|
*,
|
||||||
font_size=None,
|
font_size: float | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if direction == "ttb":
|
if direction == "ttb":
|
||||||
msg = "ttb direction is unsupported for multiline text"
|
msg = "ttb direction is unsupported for multiline text"
|
||||||
|
@ -790,19 +792,19 @@ class ImageDraw:
|
||||||
|
|
||||||
def textlength(
|
def textlength(
|
||||||
self,
|
self,
|
||||||
text: str,
|
text: AnyStr,
|
||||||
font: (
|
font: (
|
||||||
ImageFont.ImageFont
|
ImageFont.ImageFont
|
||||||
| ImageFont.FreeTypeFont
|
| ImageFont.FreeTypeFont
|
||||||
| ImageFont.TransposedFont
|
| ImageFont.TransposedFont
|
||||||
| None
|
| None
|
||||||
) = None,
|
) = None,
|
||||||
direction=None,
|
direction: str | None = None,
|
||||||
features=None,
|
features: list[str] | None = None,
|
||||||
language=None,
|
language: str | None = None,
|
||||||
embedded_color=False,
|
embedded_color: bool = False,
|
||||||
*,
|
*,
|
||||||
font_size=None,
|
font_size: float | None = None,
|
||||||
) -> float:
|
) -> float:
|
||||||
"""Get the length of a given string, in pixels with 1/64 precision."""
|
"""Get the length of a given string, in pixels with 1/64 precision."""
|
||||||
if self._multiline_check(text):
|
if self._multiline_check(text):
|
||||||
|
@ -819,20 +821,25 @@ class ImageDraw:
|
||||||
|
|
||||||
def textbbox(
|
def textbbox(
|
||||||
self,
|
self,
|
||||||
xy,
|
xy: tuple[float, float],
|
||||||
text,
|
text: AnyStr,
|
||||||
font=None,
|
font: (
|
||||||
anchor=None,
|
ImageFont.ImageFont
|
||||||
spacing=4,
|
| ImageFont.FreeTypeFont
|
||||||
align="left",
|
| ImageFont.TransposedFont
|
||||||
direction=None,
|
| None
|
||||||
features=None,
|
) = None,
|
||||||
language=None,
|
anchor: str | None = None,
|
||||||
stroke_width=0,
|
spacing: float = 4,
|
||||||
embedded_color=False,
|
align: str = "left",
|
||||||
|
direction: str | None = None,
|
||||||
|
features: list[str] | None = None,
|
||||||
|
language: str | None = None,
|
||||||
|
stroke_width: float = 0,
|
||||||
|
embedded_color: bool = False,
|
||||||
*,
|
*,
|
||||||
font_size=None,
|
font_size: float | None = None,
|
||||||
) -> tuple[int, int, int, int]:
|
) -> tuple[float, float, float, float]:
|
||||||
"""Get the bounding box of a given string, in pixels."""
|
"""Get the bounding box of a given string, in pixels."""
|
||||||
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
if embedded_color and self.mode not in ("RGB", "RGBA"):
|
||||||
msg = "Embedded color supported only in RGB and RGBA modes"
|
msg = "Embedded color supported only in RGB and RGBA modes"
|
||||||
|
@ -864,20 +871,25 @@ class ImageDraw:
|
||||||
|
|
||||||
def multiline_textbbox(
|
def multiline_textbbox(
|
||||||
self,
|
self,
|
||||||
xy,
|
xy: tuple[float, float],
|
||||||
text,
|
text: AnyStr,
|
||||||
font=None,
|
font: (
|
||||||
anchor=None,
|
ImageFont.ImageFont
|
||||||
spacing=4,
|
| ImageFont.FreeTypeFont
|
||||||
align="left",
|
| ImageFont.TransposedFont
|
||||||
direction=None,
|
| None
|
||||||
features=None,
|
) = None,
|
||||||
language=None,
|
anchor: str | None = None,
|
||||||
stroke_width=0,
|
spacing: float = 4,
|
||||||
embedded_color=False,
|
align: str = "left",
|
||||||
|
direction: str | None = None,
|
||||||
|
features: list[str] | None = None,
|
||||||
|
language: str | None = None,
|
||||||
|
stroke_width: float = 0,
|
||||||
|
embedded_color: bool = False,
|
||||||
*,
|
*,
|
||||||
font_size=None,
|
font_size: float | None = None,
|
||||||
) -> tuple[int, int, int, int]:
|
) -> tuple[float, float, float, float]:
|
||||||
if direction == "ttb":
|
if direction == "ttb":
|
||||||
msg = "ttb direction is unsupported for multiline text"
|
msg = "ttb direction is unsupported for multiline text"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
@ -916,7 +928,7 @@ class ImageDraw:
|
||||||
elif anchor[1] == "d":
|
elif anchor[1] == "d":
|
||||||
top -= (len(lines) - 1) * line_spacing
|
top -= (len(lines) - 1) * line_spacing
|
||||||
|
|
||||||
bbox: tuple[int, int, int, int] | None = None
|
bbox: tuple[float, float, float, float] | None = None
|
||||||
|
|
||||||
for idx, line in enumerate(lines):
|
for idx, line in enumerate(lines):
|
||||||
left = xy[0]
|
left = xy[0]
|
||||||
|
|
|
@ -24,10 +24,10 @@
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import BinaryIO
|
from typing import Any, AnyStr, BinaryIO
|
||||||
|
|
||||||
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath
|
||||||
from ._typing import StrOrBytesPath
|
from ._typing import Coords, StrOrBytesPath
|
||||||
|
|
||||||
|
|
||||||
class Pen:
|
class Pen:
|
||||||
|
@ -74,12 +74,14 @@ class Draw:
|
||||||
image = Image.new(image, size, color)
|
image = Image.new(image, size, color)
|
||||||
self.draw = ImageDraw.Draw(image)
|
self.draw = ImageDraw.Draw(image)
|
||||||
self.image = image
|
self.image = image
|
||||||
self.transform = None
|
self.transform: tuple[float, float, float, float, float, float] | None = None
|
||||||
|
|
||||||
def flush(self) -> Image.Image:
|
def flush(self) -> Image.Image:
|
||||||
return self.image
|
return self.image
|
||||||
|
|
||||||
def render(self, op, xy, pen, brush=None):
|
def render(
|
||||||
|
self, op: str, xy: Coords, pen: Pen | Brush, brush: Brush | Pen | None = None
|
||||||
|
) -> None:
|
||||||
# handle color arguments
|
# handle color arguments
|
||||||
outline = fill = None
|
outline = fill = None
|
||||||
width = 1
|
width = 1
|
||||||
|
@ -95,20 +97,21 @@ class Draw:
|
||||||
fill = pen.color
|
fill = pen.color
|
||||||
# handle transformation
|
# handle transformation
|
||||||
if self.transform:
|
if self.transform:
|
||||||
xy = ImagePath.Path(xy)
|
path = ImagePath.Path(xy)
|
||||||
xy.transform(self.transform)
|
path.transform(self.transform)
|
||||||
|
xy = path
|
||||||
# render the item
|
# render the item
|
||||||
if op == "line":
|
if op == "line":
|
||||||
self.draw.line(xy, fill=outline, width=width)
|
self.draw.line(xy, fill=outline, width=width)
|
||||||
else:
|
else:
|
||||||
getattr(self.draw, op)(xy, fill=fill, outline=outline)
|
getattr(self.draw, op)(xy, fill=fill, outline=outline)
|
||||||
|
|
||||||
def settransform(self, offset):
|
def settransform(self, offset: tuple[float, float]) -> None:
|
||||||
"""Sets a transformation offset."""
|
"""Sets a transformation offset."""
|
||||||
(xoffset, yoffset) = offset
|
(xoffset, yoffset) = offset
|
||||||
self.transform = (1, 0, xoffset, 0, 1, yoffset)
|
self.transform = (1, 0, xoffset, 0, 1, yoffset)
|
||||||
|
|
||||||
def arc(self, xy, start, end, *options):
|
def arc(self, xy: Coords, start, end, *options: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Draws an arc (a portion of a circle outline) between the start and end
|
Draws an arc (a portion of a circle outline) between the start and end
|
||||||
angles, inside the given bounding box.
|
angles, inside the given bounding box.
|
||||||
|
@ -117,7 +120,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("arc", xy, start, end, *options)
|
self.render("arc", xy, start, end, *options)
|
||||||
|
|
||||||
def chord(self, xy, start, end, *options):
|
def chord(self, xy: Coords, start, end, *options: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
|
Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points
|
||||||
with a straight line.
|
with a straight line.
|
||||||
|
@ -126,7 +129,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("chord", xy, start, end, *options)
|
self.render("chord", xy, start, end, *options)
|
||||||
|
|
||||||
def ellipse(self, xy, *options):
|
def ellipse(self, xy: Coords, *options: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Draws an ellipse inside the given bounding box.
|
Draws an ellipse inside the given bounding box.
|
||||||
|
|
||||||
|
@ -134,7 +137,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("ellipse", xy, *options)
|
self.render("ellipse", xy, *options)
|
||||||
|
|
||||||
def line(self, xy, *options):
|
def line(self, xy: Coords, *options: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Draws a line between the coordinates in the ``xy`` list.
|
Draws a line between the coordinates in the ``xy`` list.
|
||||||
|
|
||||||
|
@ -142,7 +145,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("line", xy, *options)
|
self.render("line", xy, *options)
|
||||||
|
|
||||||
def pieslice(self, xy, start, end, *options):
|
def pieslice(self, xy: Coords, start, end, *options: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Same as arc, but also draws straight lines between the end points and the
|
Same as arc, but also draws straight lines between the end points and the
|
||||||
center of the bounding box.
|
center of the bounding box.
|
||||||
|
@ -151,7 +154,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("pieslice", xy, start, end, *options)
|
self.render("pieslice", xy, start, end, *options)
|
||||||
|
|
||||||
def polygon(self, xy, *options):
|
def polygon(self, xy: Coords, *options: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Draws a polygon.
|
Draws a polygon.
|
||||||
|
|
||||||
|
@ -164,7 +167,7 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("polygon", xy, *options)
|
self.render("polygon", xy, *options)
|
||||||
|
|
||||||
def rectangle(self, xy, *options):
|
def rectangle(self, xy: Coords, *options) -> None:
|
||||||
"""
|
"""
|
||||||
Draws a rectangle.
|
Draws a rectangle.
|
||||||
|
|
||||||
|
@ -172,18 +175,21 @@ class Draw:
|
||||||
"""
|
"""
|
||||||
self.render("rectangle", xy, *options)
|
self.render("rectangle", xy, *options)
|
||||||
|
|
||||||
def text(self, xy, text, font):
|
def text(self, xy: tuple[float, float], text: AnyStr, font: Font) -> None:
|
||||||
"""
|
"""
|
||||||
Draws the string at the given position.
|
Draws the string at the given position.
|
||||||
|
|
||||||
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
|
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text`
|
||||||
"""
|
"""
|
||||||
if self.transform:
|
if self.transform:
|
||||||
xy = ImagePath.Path(xy)
|
path = ImagePath.Path(xy)
|
||||||
xy.transform(self.transform)
|
path.transform(self.transform)
|
||||||
|
xy = path
|
||||||
self.draw.text(xy, text, font=font.font, fill=font.color)
|
self.draw.text(xy, text, font=font.font, fill=font.color)
|
||||||
|
|
||||||
def textbbox(self, xy, text, font):
|
def textbbox(
|
||||||
|
self, xy: tuple[float, float], text: AnyStr, font: Font
|
||||||
|
) -> tuple[float, float, float, float]:
|
||||||
"""
|
"""
|
||||||
Returns bounding box (in pixels) of given text.
|
Returns bounding box (in pixels) of given text.
|
||||||
|
|
||||||
|
@ -192,11 +198,12 @@ class Draw:
|
||||||
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
|
.. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox`
|
||||||
"""
|
"""
|
||||||
if self.transform:
|
if self.transform:
|
||||||
xy = ImagePath.Path(xy)
|
path = ImagePath.Path(xy)
|
||||||
xy.transform(self.transform)
|
path.transform(self.transform)
|
||||||
|
xy = path
|
||||||
return self.draw.textbbox(xy, text, font=font.font)
|
return self.draw.textbbox(xy, text, font=font.font)
|
||||||
|
|
||||||
def textlength(self, text, font):
|
def textlength(self, text: AnyStr, font: Font) -> float:
|
||||||
"""
|
"""
|
||||||
Returns length (in pixels) of given text.
|
Returns length (in pixels) of given text.
|
||||||
This is the amount by which following text should be offset.
|
This is the amount by which following text should be offset.
|
||||||
|
|
|
@ -86,7 +86,7 @@ def raise_oserror(error: int) -> OSError:
|
||||||
raise _get_oserror(error, encoder=False)
|
raise _get_oserror(error, encoder=False)
|
||||||
|
|
||||||
|
|
||||||
def _tilesort(t):
|
def _tilesort(t) -> int:
|
||||||
# sort on offset
|
# sort on offset
|
||||||
return t[2]
|
return t[2]
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ class ImageFile(Image.Image):
|
||||||
return Image.MIME.get(self.format.upper())
|
return Image.MIME.get(self.format.upper())
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state) -> None:
|
||||||
self.tile = []
|
self.tile = []
|
||||||
super().__setstate__(state)
|
super().__setstate__(state)
|
||||||
|
|
||||||
|
@ -333,14 +333,14 @@ class ImageFile(Image.Image):
|
||||||
# def load_read(self, read_bytes: int) -> bytes:
|
# def load_read(self, read_bytes: int) -> bytes:
|
||||||
# pass
|
# pass
|
||||||
|
|
||||||
def _seek_check(self, frame):
|
def _seek_check(self, frame: int) -> bool:
|
||||||
if (
|
if (
|
||||||
frame < self._min_frame
|
frame < self._min_frame
|
||||||
# Only check upper limit on frames if additional seek operations
|
# Only check upper limit on frames if additional seek operations
|
||||||
# are not required to do so
|
# are not required to do so
|
||||||
or (
|
or (
|
||||||
not (hasattr(self, "_n_frames") and self._n_frames is None)
|
not (hasattr(self, "_n_frames") and self._n_frames is None)
|
||||||
and frame >= self.n_frames + self._min_frame
|
and frame >= getattr(self, "n_frames") + self._min_frame
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
msg = "attempt to seek outside sequence"
|
msg = "attempt to seek outside sequence"
|
||||||
|
@ -370,7 +370,7 @@ class StubImageFile(ImageFile):
|
||||||
msg = "StubImageFile subclass must implement _open"
|
msg = "StubImageFile subclass must implement _open"
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
def load(self):
|
def load(self) -> Image.core.PixelAccess | None:
|
||||||
loader = self._load()
|
loader = self._load()
|
||||||
if loader is None:
|
if loader is None:
|
||||||
msg = f"cannot find loader for this {self.format} file"
|
msg = f"cannot find loader for this {self.format} file"
|
||||||
|
@ -378,7 +378,7 @@ class StubImageFile(ImageFile):
|
||||||
image = loader.load(self)
|
image = loader.load(self)
|
||||||
assert image is not None
|
assert image is not None
|
||||||
# become the other object (!)
|
# become the other object (!)
|
||||||
self.__class__ = image.__class__
|
self.__class__ = image.__class__ # type: ignore[assignment]
|
||||||
self.__dict__ = image.__dict__
|
self.__dict__ = image.__dict__
|
||||||
return image.load()
|
return image.load()
|
||||||
|
|
||||||
|
@ -396,8 +396,8 @@ class Parser:
|
||||||
|
|
||||||
incremental = None
|
incremental = None
|
||||||
image: Image.Image | None = None
|
image: Image.Image | None = None
|
||||||
data = None
|
data: bytes | None = None
|
||||||
decoder = None
|
decoder: Image.core.ImagingDecoder | PyDecoder | None = None
|
||||||
offset = 0
|
offset = 0
|
||||||
finished = 0
|
finished = 0
|
||||||
|
|
||||||
|
@ -409,7 +409,7 @@ class Parser:
|
||||||
"""
|
"""
|
||||||
assert self.data is None, "cannot reuse parsers"
|
assert self.data is None, "cannot reuse parsers"
|
||||||
|
|
||||||
def feed(self, data):
|
def feed(self, data: bytes) -> None:
|
||||||
"""
|
"""
|
||||||
(Consumer) Feed data to the parser.
|
(Consumer) Feed data to the parser.
|
||||||
|
|
||||||
|
@ -485,13 +485,13 @@ class Parser:
|
||||||
|
|
||||||
self.image = im
|
self.image = im
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self) -> Parser:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *args: object) -> None:
|
def __exit__(self, *args: object) -> None:
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def close(self):
|
def close(self) -> Image.Image:
|
||||||
"""
|
"""
|
||||||
(Consumer) Close the stream.
|
(Consumer) Close the stream.
|
||||||
|
|
||||||
|
@ -525,7 +525,7 @@ class Parser:
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, tile, bufsize=0) -> None:
|
def _save(im, fp, tile, bufsize: int = 0) -> None:
|
||||||
"""Helper to save image based on tile list
|
"""Helper to save image based on tile list
|
||||||
|
|
||||||
:param im: Image object.
|
:param im: Image object.
|
||||||
|
@ -553,7 +553,9 @@ def _save(im, fp, tile, bufsize=0) -> None:
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
|
|
||||||
def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None):
|
def _encode_tile(
|
||||||
|
im, fp: IO[bytes], tile: list[_Tile], bufsize: int, fh, exc=None
|
||||||
|
) -> None:
|
||||||
for encoder_name, extents, offset, args in tile:
|
for encoder_name, extents, offset, args in tile:
|
||||||
if offset > 0:
|
if offset > 0:
|
||||||
fp.seek(offset)
|
fp.seek(offset)
|
||||||
|
@ -580,7 +582,7 @@ def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None):
|
||||||
encoder.cleanup()
|
encoder.cleanup()
|
||||||
|
|
||||||
|
|
||||||
def _safe_read(fp, size):
|
def _safe_read(fp: IO[bytes], size: int) -> bytes:
|
||||||
"""
|
"""
|
||||||
Reads large blocks in a safe way. Unlike fp.read(n), this function
|
Reads large blocks in a safe way. Unlike fp.read(n), this function
|
||||||
doesn't trust the user. If the requested size is larger than
|
doesn't trust the user. If the requested size is larger than
|
||||||
|
@ -601,18 +603,18 @@ def _safe_read(fp, size):
|
||||||
msg = "Truncated File Read"
|
msg = "Truncated File Read"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
return data
|
return data
|
||||||
data = []
|
blocks: list[bytes] = []
|
||||||
remaining_size = size
|
remaining_size = size
|
||||||
while remaining_size > 0:
|
while remaining_size > 0:
|
||||||
block = fp.read(min(remaining_size, SAFEBLOCK))
|
block = fp.read(min(remaining_size, SAFEBLOCK))
|
||||||
if not block:
|
if not block:
|
||||||
break
|
break
|
||||||
data.append(block)
|
blocks.append(block)
|
||||||
remaining_size -= len(block)
|
remaining_size -= len(block)
|
||||||
if sum(len(d) for d in data) < size:
|
if sum(len(block) for block in blocks) < size:
|
||||||
msg = "Truncated File Read"
|
msg = "Truncated File Read"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
return b"".join(data)
|
return b"".join(blocks)
|
||||||
|
|
||||||
|
|
||||||
class PyCodecState:
|
class PyCodecState:
|
||||||
|
@ -629,18 +631,18 @@ class PyCodecState:
|
||||||
class PyCodec:
|
class PyCodec:
|
||||||
fd: IO[bytes] | None
|
fd: IO[bytes] | None
|
||||||
|
|
||||||
def __init__(self, mode, *args):
|
def __init__(self, mode: str, *args: Any) -> None:
|
||||||
self.im = None
|
self.im: Image.core.ImagingCore | None = None
|
||||||
self.state = PyCodecState()
|
self.state = PyCodecState()
|
||||||
self.fd = None
|
self.fd = None
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.init(args)
|
self.init(args)
|
||||||
|
|
||||||
def init(self, args):
|
def init(self, args: tuple[Any, ...]) -> None:
|
||||||
"""
|
"""
|
||||||
Override to perform codec specific initialization
|
Override to perform codec specific initialization
|
||||||
|
|
||||||
:param args: Array of args items from the tile entry
|
:param args: Tuple of arg items from the tile entry
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
self.args = args
|
self.args = args
|
||||||
|
@ -653,7 +655,7 @@ class PyCodec:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def setfd(self, fd):
|
def setfd(self, fd: IO[bytes]) -> None:
|
||||||
"""
|
"""
|
||||||
Called from ImageFile to set the Python file-like object
|
Called from ImageFile to set the Python file-like object
|
||||||
|
|
||||||
|
@ -662,7 +664,7 @@ class PyCodec:
|
||||||
"""
|
"""
|
||||||
self.fd = fd
|
self.fd = fd
|
||||||
|
|
||||||
def setimage(self, im, extents: tuple[int, int, int, int] | None = None) -> None:
|
def setimage(self, im, extents=None):
|
||||||
"""
|
"""
|
||||||
Called from ImageFile to set the core output image for the codec
|
Called from ImageFile to set the core output image for the codec
|
||||||
|
|
||||||
|
@ -793,7 +795,7 @@ class PyEncoder(PyCodec):
|
||||||
self.fd.write(data)
|
self.fd.write(data)
|
||||||
return bytes_consumed, errcode
|
return bytes_consumed, errcode
|
||||||
|
|
||||||
def encode_to_file(self, fh, bufsize):
|
def encode_to_file(self, fh: IO[bytes], bufsize: int) -> int:
|
||||||
"""
|
"""
|
||||||
:param fh: File handle.
|
:param fh: File handle.
|
||||||
:param bufsize: Buffer size.
|
:param bufsize: Buffer size.
|
||||||
|
|
|
@ -34,7 +34,7 @@ import warnings
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import IO, TYPE_CHECKING, Any, BinaryIO
|
from typing import IO, TYPE_CHECKING, Any, BinaryIO, TypedDict
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._typing import StrOrBytesPath
|
from ._typing import StrOrBytesPath
|
||||||
|
@ -46,6 +46,13 @@ if TYPE_CHECKING:
|
||||||
from ._imagingft import Font
|
from ._imagingft import Font
|
||||||
|
|
||||||
|
|
||||||
|
class Axis(TypedDict):
|
||||||
|
minimum: int | None
|
||||||
|
default: int | None
|
||||||
|
maximum: int | None
|
||||||
|
name: bytes | None
|
||||||
|
|
||||||
|
|
||||||
class Layout(IntEnum):
|
class Layout(IntEnum):
|
||||||
BASIC = 0
|
BASIC = 0
|
||||||
RAQM = 1
|
RAQM = 1
|
||||||
|
@ -138,7 +145,9 @@ class ImageFont:
|
||||||
|
|
||||||
self.font = Image.core.font(image.im, data)
|
self.font = Image.core.font(image.im, data)
|
||||||
|
|
||||||
def getmask(self, text, mode="", *args, **kwargs):
|
def getmask(
|
||||||
|
self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any
|
||||||
|
) -> Image.core.ImagingCore:
|
||||||
"""
|
"""
|
||||||
Create a bitmap for the text.
|
Create a bitmap for the text.
|
||||||
|
|
||||||
|
@ -236,7 +245,7 @@ class FreeTypeFont:
|
||||||
|
|
||||||
self.layout_engine = layout_engine
|
self.layout_engine = layout_engine
|
||||||
|
|
||||||
def load_from_bytes(f):
|
def load_from_bytes(f) -> None:
|
||||||
self.font_bytes = f.read()
|
self.font_bytes = f.read()
|
||||||
self.font = core.getfont(
|
self.font = core.getfont(
|
||||||
"", size, index, encoding, self.font_bytes, layout_engine
|
"", size, index, encoding, self.font_bytes, layout_engine
|
||||||
|
@ -283,7 +292,12 @@ class FreeTypeFont:
|
||||||
return self.font.ascent, self.font.descent
|
return self.font.ascent, self.font.descent
|
||||||
|
|
||||||
def getlength(
|
def getlength(
|
||||||
self, text: str | bytes, mode="", direction=None, features=None, language=None
|
self,
|
||||||
|
text: str | bytes,
|
||||||
|
mode: str = "",
|
||||||
|
direction: str | None = None,
|
||||||
|
features: list[str] | None = None,
|
||||||
|
language: str | None = None,
|
||||||
) -> float:
|
) -> float:
|
||||||
"""
|
"""
|
||||||
Returns length (in pixels with 1/64 precision) of given text when rendered
|
Returns length (in pixels with 1/64 precision) of given text when rendered
|
||||||
|
@ -424,16 +438,16 @@ class FreeTypeFont:
|
||||||
|
|
||||||
def getmask(
|
def getmask(
|
||||||
self,
|
self,
|
||||||
text,
|
text: str | bytes,
|
||||||
mode="",
|
mode: str = "",
|
||||||
direction=None,
|
direction: str | None = None,
|
||||||
features=None,
|
features: list[str] | None = None,
|
||||||
language=None,
|
language: str | None = None,
|
||||||
stroke_width=0,
|
stroke_width: float = 0,
|
||||||
anchor=None,
|
anchor: str | None = None,
|
||||||
ink=0,
|
ink: int = 0,
|
||||||
start=None,
|
start: tuple[float, float] | None = None,
|
||||||
):
|
) -> Image.core.ImagingCore:
|
||||||
"""
|
"""
|
||||||
Create a bitmap for the text.
|
Create a bitmap for the text.
|
||||||
|
|
||||||
|
@ -516,17 +530,17 @@ class FreeTypeFont:
|
||||||
def getmask2(
|
def getmask2(
|
||||||
self,
|
self,
|
||||||
text: str | bytes,
|
text: str | bytes,
|
||||||
mode="",
|
mode: str = "",
|
||||||
direction=None,
|
direction: str | None = None,
|
||||||
features=None,
|
features: list[str] | None = None,
|
||||||
language=None,
|
language: str | None = None,
|
||||||
stroke_width=0,
|
stroke_width: float = 0,
|
||||||
anchor=None,
|
anchor: str | None = None,
|
||||||
ink=0,
|
ink: int = 0,
|
||||||
start=None,
|
start: tuple[float, float] | None = None,
|
||||||
*args,
|
*args: Any,
|
||||||
**kwargs,
|
**kwargs: Any,
|
||||||
):
|
) -> tuple[Image.core.ImagingCore, tuple[int, int]]:
|
||||||
"""
|
"""
|
||||||
Create a bitmap for the text.
|
Create a bitmap for the text.
|
||||||
|
|
||||||
|
@ -599,7 +613,7 @@ class FreeTypeFont:
|
||||||
if start is None:
|
if start is None:
|
||||||
start = (0, 0)
|
start = (0, 0)
|
||||||
|
|
||||||
def fill(width, height):
|
def fill(width: int, height: int) -> Image.core.ImagingCore:
|
||||||
size = (width, height)
|
size = (width, height)
|
||||||
Image._decompression_bomb_check(size)
|
Image._decompression_bomb_check(size)
|
||||||
return Image.core.fill("RGBA" if mode == "RGBA" else "L", size)
|
return Image.core.fill("RGBA" if mode == "RGBA" else "L", size)
|
||||||
|
@ -619,8 +633,13 @@ class FreeTypeFont:
|
||||||
)
|
)
|
||||||
|
|
||||||
def font_variant(
|
def font_variant(
|
||||||
self, font=None, size=None, index=None, encoding=None, layout_engine=None
|
self,
|
||||||
):
|
font: StrOrBytesPath | BinaryIO | None = None,
|
||||||
|
size: float | None = None,
|
||||||
|
index: int | None = None,
|
||||||
|
encoding: str | None = None,
|
||||||
|
layout_engine: Layout | None = None,
|
||||||
|
) -> FreeTypeFont:
|
||||||
"""
|
"""
|
||||||
Create a copy of this FreeTypeFont object,
|
Create a copy of this FreeTypeFont object,
|
||||||
using any specified arguments to override the settings.
|
using any specified arguments to override the settings.
|
||||||
|
@ -655,7 +674,7 @@ class FreeTypeFont:
|
||||||
raise NotImplementedError(msg) from e
|
raise NotImplementedError(msg) from e
|
||||||
return [name.replace(b"\x00", b"") for name in names]
|
return [name.replace(b"\x00", b"") for name in names]
|
||||||
|
|
||||||
def set_variation_by_name(self, name):
|
def set_variation_by_name(self, name: str | bytes) -> None:
|
||||||
"""
|
"""
|
||||||
:param name: The name of the style.
|
:param name: The name of the style.
|
||||||
:exception OSError: If the font is not a variation font.
|
:exception OSError: If the font is not a variation font.
|
||||||
|
@ -674,7 +693,7 @@ class FreeTypeFont:
|
||||||
|
|
||||||
self.font.setvarname(index)
|
self.font.setvarname(index)
|
||||||
|
|
||||||
def get_variation_axes(self):
|
def get_variation_axes(self) -> list[Axis]:
|
||||||
"""
|
"""
|
||||||
:returns: A list of the axes in a variation font.
|
:returns: A list of the axes in a variation font.
|
||||||
:exception OSError: If the font is not a variation font.
|
:exception OSError: If the font is not a variation font.
|
||||||
|
@ -704,7 +723,9 @@ class FreeTypeFont:
|
||||||
class TransposedFont:
|
class TransposedFont:
|
||||||
"""Wrapper for writing rotated or mirrored text"""
|
"""Wrapper for writing rotated or mirrored text"""
|
||||||
|
|
||||||
def __init__(self, font, orientation=None):
|
def __init__(
|
||||||
|
self, font: ImageFont | FreeTypeFont, orientation: Image.Transpose | None = None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Wrapper that creates a transposed font from any existing font
|
Wrapper that creates a transposed font from any existing font
|
||||||
object.
|
object.
|
||||||
|
@ -718,13 +739,17 @@ class TransposedFont:
|
||||||
self.font = font
|
self.font = font
|
||||||
self.orientation = orientation # any 'transpose' argument, or None
|
self.orientation = orientation # any 'transpose' argument, or None
|
||||||
|
|
||||||
def getmask(self, text, mode="", *args, **kwargs):
|
def getmask(
|
||||||
|
self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any
|
||||||
|
) -> Image.core.ImagingCore:
|
||||||
im = self.font.getmask(text, mode, *args, **kwargs)
|
im = self.font.getmask(text, mode, *args, **kwargs)
|
||||||
if self.orientation is not None:
|
if self.orientation is not None:
|
||||||
return im.transpose(self.orientation)
|
return im.transpose(self.orientation)
|
||||||
return im
|
return im
|
||||||
|
|
||||||
def getbbox(self, text, *args, **kwargs):
|
def getbbox(
|
||||||
|
self, text: str | bytes, *args: Any, **kwargs: Any
|
||||||
|
) -> tuple[int, int, float, float]:
|
||||||
# TransposedFont doesn't support getmask2, move top-left point to (0, 0)
|
# TransposedFont doesn't support getmask2, move top-left point to (0, 0)
|
||||||
# this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont
|
# this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont
|
||||||
left, top, right, bottom = self.font.getbbox(text, *args, **kwargs)
|
left, top, right, bottom = self.font.getbbox(text, *args, **kwargs)
|
||||||
|
@ -734,7 +759,7 @@ class TransposedFont:
|
||||||
return 0, 0, height, width
|
return 0, 0, height, width
|
||||||
return 0, 0, width, height
|
return 0, 0, width, height
|
||||||
|
|
||||||
def getlength(self, text: str | bytes, *args, **kwargs) -> float:
|
def getlength(self, text: str | bytes, *args: Any, **kwargs: Any) -> float:
|
||||||
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
|
if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270):
|
||||||
msg = "text length is undefined for text rotated by 90 or 270 degrees"
|
msg = "text length is undefined for text rotated by 90 or 270 degrees"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
|
@ -249,14 +249,21 @@ def lambda_eval(
|
||||||
:py:func:`~PIL.Image.merge` function.
|
:py:func:`~PIL.Image.merge` function.
|
||||||
|
|
||||||
:param expression: A function that receives a dictionary.
|
:param expression: A function that receives a dictionary.
|
||||||
:param options: Values to add to the function's dictionary. You
|
:param options: Values to add to the function's dictionary. Deprecated.
|
||||||
can either use a dictionary, or one or more keyword
|
You can instead use one or more keyword arguments.
|
||||||
arguments.
|
:param **kw: Values to add to the function's dictionary.
|
||||||
:return: The expression result. This is usually an image object, but can
|
:return: The expression result. This is usually an image object, but can
|
||||||
also be an integer, a floating point value, or a pixel tuple,
|
also be an integer, a floating point value, or a pixel tuple,
|
||||||
depending on the expression.
|
depending on the expression.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if options:
|
||||||
|
deprecate(
|
||||||
|
"ImageMath.lambda_eval options",
|
||||||
|
12,
|
||||||
|
"ImageMath.lambda_eval keyword arguments",
|
||||||
|
)
|
||||||
|
|
||||||
args: dict[str, Any] = ops.copy()
|
args: dict[str, Any] = ops.copy()
|
||||||
args.update(options)
|
args.update(options)
|
||||||
args.update(kw)
|
args.update(kw)
|
||||||
|
@ -287,14 +294,21 @@ def unsafe_eval(
|
||||||
:py:func:`~PIL.Image.merge` function.
|
:py:func:`~PIL.Image.merge` function.
|
||||||
|
|
||||||
:param expression: A string containing a Python-style expression.
|
:param expression: A string containing a Python-style expression.
|
||||||
:param options: Values to add to the evaluation context. You
|
:param options: Values to add to the evaluation context. Deprecated.
|
||||||
can either use a dictionary, or one or more keyword
|
You can instead use one or more keyword arguments.
|
||||||
arguments.
|
:param **kw: Values to add to the evaluation context.
|
||||||
:return: The evaluated expression. This is usually an image object, but can
|
:return: The evaluated expression. This is usually an image object, but can
|
||||||
also be an integer, a floating point value, or a pixel tuple,
|
also be an integer, a floating point value, or a pixel tuple,
|
||||||
depending on the expression.
|
depending on the expression.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if options:
|
||||||
|
deprecate(
|
||||||
|
"ImageMath.unsafe_eval options",
|
||||||
|
12,
|
||||||
|
"ImageMath.unsafe_eval keyword arguments",
|
||||||
|
)
|
||||||
|
|
||||||
# build execution namespace
|
# build execution namespace
|
||||||
args: dict[str, Any] = ops.copy()
|
args: dict[str, Any] = ops.copy()
|
||||||
for k in list(options.keys()) + list(kw.keys()):
|
for k in list(options.keys()) + list(kw.keys()):
|
||||||
|
|
|
@ -19,11 +19,14 @@ from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Callable
|
from typing import TYPE_CHECKING, Callable
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._util import is_path
|
from ._util import is_path
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from . import ImageFile
|
||||||
|
|
||||||
qt_version: str | None
|
qt_version: str | None
|
||||||
qt_versions = [
|
qt_versions = [
|
||||||
["6", "PyQt6"],
|
["6", "PyQt6"],
|
||||||
|
@ -55,7 +58,7 @@ else:
|
||||||
qt_version = None
|
qt_version = None
|
||||||
|
|
||||||
|
|
||||||
def rgb(r, g, b, a=255):
|
def rgb(r: int, g: int, b: int, a: int = 255) -> int:
|
||||||
"""(Internal) Turns an RGB color into a Qt compatible color integer."""
|
"""(Internal) Turns an RGB color into a Qt compatible color integer."""
|
||||||
# use qRgb to pack the colors, and then turn the resulting long
|
# use qRgb to pack the colors, and then turn the resulting long
|
||||||
# into a negative integer with the same bitpattern.
|
# into a negative integer with the same bitpattern.
|
||||||
|
@ -90,11 +93,11 @@ def fromqimage(im):
|
||||||
return Image.open(b)
|
return Image.open(b)
|
||||||
|
|
||||||
|
|
||||||
def fromqpixmap(im):
|
def fromqpixmap(im) -> ImageFile.ImageFile:
|
||||||
return fromqimage(im)
|
return fromqimage(im)
|
||||||
|
|
||||||
|
|
||||||
def align8to32(bytes, width, mode):
|
def align8to32(bytes: bytes, width: int, mode: str) -> bytes:
|
||||||
"""
|
"""
|
||||||
converts each scanline of data from 8 bit to 32 bit aligned
|
converts each scanline of data from 8 bit to 32 bit aligned
|
||||||
"""
|
"""
|
||||||
|
@ -172,7 +175,7 @@ def _toqclass_helper(im):
|
||||||
if qt_is_installed:
|
if qt_is_installed:
|
||||||
|
|
||||||
class ImageQt(QImage):
|
class ImageQt(QImage):
|
||||||
def __init__(self, im):
|
def __init__(self, im) -> None:
|
||||||
"""
|
"""
|
||||||
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
|
An PIL image wrapper for Qt. This is a subclass of PyQt's QImage
|
||||||
class.
|
class.
|
||||||
|
|
|
@ -33,7 +33,7 @@ class Iterator:
|
||||||
:param im: An image object.
|
:param im: An image object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, im: Image.Image):
|
def __init__(self, im: Image.Image) -> None:
|
||||||
if not hasattr(im, "seek"):
|
if not hasattr(im, "seek"):
|
||||||
msg = "im must have seek method"
|
msg = "im must have seek method"
|
||||||
raise AttributeError(msg)
|
raise AttributeError(msg)
|
||||||
|
|
|
@ -28,7 +28,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import tkinter
|
import tkinter
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Any
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
|
||||||
|
@ -61,7 +61,9 @@ def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None:
|
||||||
return Image.open(source)
|
return Image.open(source)
|
||||||
|
|
||||||
|
|
||||||
def _pyimagingtkcall(command, photo, id):
|
def _pyimagingtkcall(
|
||||||
|
command: str, photo: PhotoImage | tkinter.PhotoImage, id: int
|
||||||
|
) -> None:
|
||||||
tk = photo.tk
|
tk = photo.tk
|
||||||
try:
|
try:
|
||||||
tk.call(command, photo, id)
|
tk.call(command, photo, id)
|
||||||
|
@ -215,11 +217,14 @@ class BitmapImage:
|
||||||
:param image: A PIL image.
|
:param image: A PIL image.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, image=None, **kw):
|
def __init__(self, image: Image.Image | None = None, **kw: Any) -> None:
|
||||||
# Tk compatibility: file or data
|
# Tk compatibility: file or data
|
||||||
if image is None:
|
if image is None:
|
||||||
image = _get_image_from_kw(kw)
|
image = _get_image_from_kw(kw)
|
||||||
|
|
||||||
|
if image is None:
|
||||||
|
msg = "Image is required"
|
||||||
|
raise ValueError(msg)
|
||||||
self.__mode = image.mode
|
self.__mode = image.mode
|
||||||
self.__size = image.size
|
self.__size = image.size
|
||||||
|
|
||||||
|
@ -278,18 +283,23 @@ def getimage(photo: PhotoImage) -> Image.Image:
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
|
||||||
def _show(image, title):
|
def _show(image: Image.Image, title: str | None) -> None:
|
||||||
"""Helper for the Image.show method."""
|
"""Helper for the Image.show method."""
|
||||||
|
|
||||||
class UI(tkinter.Label):
|
class UI(tkinter.Label):
|
||||||
def __init__(self, master, im):
|
def __init__(self, master: tkinter.Toplevel, im: Image.Image) -> None:
|
||||||
|
self.image: BitmapImage | PhotoImage
|
||||||
if im.mode == "1":
|
if im.mode == "1":
|
||||||
self.image = BitmapImage(im, foreground="white", master=master)
|
self.image = BitmapImage(im, foreground="white", master=master)
|
||||||
else:
|
else:
|
||||||
self.image = PhotoImage(im, master=master)
|
self.image = PhotoImage(im, master=master)
|
||||||
super().__init__(master, image=self.image, bg="black", bd=0)
|
if TYPE_CHECKING:
|
||||||
|
image = cast(tkinter._Image, self.image)
|
||||||
|
else:
|
||||||
|
image = self.image
|
||||||
|
super().__init__(master, image=image, bg="black", bd=0)
|
||||||
|
|
||||||
if not tkinter._default_root:
|
if not getattr(tkinter, "_default_root"):
|
||||||
msg = "tkinter not initialized"
|
msg = "tkinter not initialized"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
top = tkinter.Toplevel()
|
top = tkinter.Toplevel()
|
||||||
|
|
|
@ -70,11 +70,14 @@ class Dib:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, image: Image.Image | str, size: tuple[int, int] | list[int] | None = None
|
self, image: Image.Image | str, size: tuple[int, int] | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
if isinstance(image, str):
|
if isinstance(image, str):
|
||||||
mode = image
|
mode = image
|
||||||
image = ""
|
image = ""
|
||||||
|
if size is None:
|
||||||
|
msg = "If first argument is mode, size is required"
|
||||||
|
raise ValueError(msg)
|
||||||
else:
|
else:
|
||||||
mode = image.mode
|
mode = image.mode
|
||||||
size = image.size
|
size = image.size
|
||||||
|
@ -87,7 +90,7 @@ class Dib:
|
||||||
assert not isinstance(image, str)
|
assert not isinstance(image, str)
|
||||||
self.paste(image)
|
self.paste(image)
|
||||||
|
|
||||||
def expose(self, handle):
|
def expose(self, handle: int | HDC | HWND) -> None:
|
||||||
"""
|
"""
|
||||||
Copy the bitmap contents to a device context.
|
Copy the bitmap contents to a device context.
|
||||||
|
|
||||||
|
@ -98,14 +101,18 @@ class Dib:
|
||||||
if isinstance(handle, HWND):
|
if isinstance(handle, HWND):
|
||||||
dc = self.image.getdc(handle)
|
dc = self.image.getdc(handle)
|
||||||
try:
|
try:
|
||||||
result = self.image.expose(dc)
|
self.image.expose(dc)
|
||||||
finally:
|
finally:
|
||||||
self.image.releasedc(handle, dc)
|
self.image.releasedc(handle, dc)
|
||||||
else:
|
else:
|
||||||
result = self.image.expose(handle)
|
self.image.expose(handle)
|
||||||
return result
|
|
||||||
|
|
||||||
def draw(self, handle, dst, src=None):
|
def draw(
|
||||||
|
self,
|
||||||
|
handle: int | HDC | HWND,
|
||||||
|
dst: tuple[int, int, int, int],
|
||||||
|
src: tuple[int, int, int, int] | None = None,
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Same as expose, but allows you to specify where to draw the image, and
|
Same as expose, but allows you to specify where to draw the image, and
|
||||||
what part of it to draw.
|
what part of it to draw.
|
||||||
|
@ -115,19 +122,18 @@ class Dib:
|
||||||
the destination have different sizes, the image is resized as
|
the destination have different sizes, the image is resized as
|
||||||
necessary.
|
necessary.
|
||||||
"""
|
"""
|
||||||
if not src:
|
if src is None:
|
||||||
src = (0, 0) + self.size
|
src = (0, 0) + self.size
|
||||||
if isinstance(handle, HWND):
|
if isinstance(handle, HWND):
|
||||||
dc = self.image.getdc(handle)
|
dc = self.image.getdc(handle)
|
||||||
try:
|
try:
|
||||||
result = self.image.draw(dc, dst, src)
|
self.image.draw(dc, dst, src)
|
||||||
finally:
|
finally:
|
||||||
self.image.releasedc(handle, dc)
|
self.image.releasedc(handle, dc)
|
||||||
else:
|
else:
|
||||||
result = self.image.draw(handle, dst, src)
|
self.image.draw(handle, dst, src)
|
||||||
return result
|
|
||||||
|
|
||||||
def query_palette(self, handle):
|
def query_palette(self, handle: int | HDC | HWND) -> int:
|
||||||
"""
|
"""
|
||||||
Installs the palette associated with the image in the given device
|
Installs the palette associated with the image in the given device
|
||||||
context.
|
context.
|
||||||
|
@ -139,8 +145,8 @@ class Dib:
|
||||||
|
|
||||||
:param handle: Device context (HDC), cast to a Python integer, or an
|
:param handle: Device context (HDC), cast to a Python integer, or an
|
||||||
HDC or HWND instance.
|
HDC or HWND instance.
|
||||||
:return: A true value if one or more entries were changed (this
|
:return: The number of entries that were changed (if one or more entries,
|
||||||
indicates that the image should be redrawn).
|
this indicates that the image should be redrawn).
|
||||||
"""
|
"""
|
||||||
if isinstance(handle, HWND):
|
if isinstance(handle, HWND):
|
||||||
handle = self.image.getdc(handle)
|
handle = self.image.getdc(handle)
|
||||||
|
@ -202,22 +208,22 @@ class Window:
|
||||||
title, self.__dispatcher, width or 0, height or 0
|
title, self.__dispatcher, width or 0, height or 0
|
||||||
)
|
)
|
||||||
|
|
||||||
def __dispatcher(self, action, *args):
|
def __dispatcher(self, action: str, *args: int) -> None:
|
||||||
return getattr(self, f"ui_handle_{action}")(*args)
|
getattr(self, f"ui_handle_{action}")(*args)
|
||||||
|
|
||||||
def ui_handle_clear(self, dc, x0, y0, x1, y1):
|
def ui_handle_clear(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def ui_handle_damage(self, x0, y0, x1, y1):
|
def ui_handle_damage(self, x0: int, y0: int, x1: int, y1: int) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def ui_handle_destroy(self) -> None:
|
def ui_handle_destroy(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def ui_handle_repair(self, dc, x0, y0, x1, y1):
|
def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def ui_handle_resize(self, width, height):
|
def ui_handle_resize(self, width: int, height: int) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def mainloop(self) -> None:
|
def mainloop(self) -> None:
|
||||||
|
@ -227,12 +233,12 @@ class Window:
|
||||||
class ImageWindow(Window):
|
class ImageWindow(Window):
|
||||||
"""Create an image window which displays the given image."""
|
"""Create an image window which displays the given image."""
|
||||||
|
|
||||||
def __init__(self, image, title="PIL"):
|
def __init__(self, image: Image.Image | Dib, title: str = "PIL") -> None:
|
||||||
if not isinstance(image, Dib):
|
if not isinstance(image, Dib):
|
||||||
image = Dib(image)
|
image = Dib(image)
|
||||||
self.image = image
|
self.image = image
|
||||||
width, height = image.size
|
width, height = image.size
|
||||||
super().__init__(title, width=width, height=height)
|
super().__init__(title, width=width, height=height)
|
||||||
|
|
||||||
def ui_handle_repair(self, dc, x0, y0, x1, y1):
|
def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None:
|
||||||
self.image.draw(dc, (x0, y0, x1, y1))
|
self.image.draw(dc, (x0, y0, x1, y1))
|
||||||
|
|
|
@ -18,6 +18,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
|
@ -184,7 +185,9 @@ Image.register_open(IptcImageFile.format, IptcImageFile)
|
||||||
Image.register_extension(IptcImageFile.format, ".iim")
|
Image.register_extension(IptcImageFile.format, ".iim")
|
||||||
|
|
||||||
|
|
||||||
def getiptcinfo(im):
|
def getiptcinfo(
|
||||||
|
im: ImageFile.ImageFile,
|
||||||
|
) -> dict[tuple[int, int], bytes | list[bytes]] | None:
|
||||||
"""
|
"""
|
||||||
Get IPTC information from TIFF, JPEG, or IPTC file.
|
Get IPTC information from TIFF, JPEG, or IPTC file.
|
||||||
|
|
||||||
|
@ -221,16 +224,17 @@ def getiptcinfo(im):
|
||||||
class FakeImage:
|
class FakeImage:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
im = FakeImage()
|
fake_im = FakeImage()
|
||||||
im.__class__ = IptcImageFile
|
fake_im.__class__ = IptcImageFile # type: ignore[assignment]
|
||||||
|
iptc_im = cast(IptcImageFile, fake_im)
|
||||||
|
|
||||||
# parse the IPTC information chunk
|
# parse the IPTC information chunk
|
||||||
im.info = {}
|
iptc_im.info = {}
|
||||||
im.fp = BytesIO(data)
|
iptc_im.fp = BytesIO(data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
im._open()
|
iptc_im._open()
|
||||||
except (IndexError, KeyError):
|
except (IndexError, KeyError):
|
||||||
pass # expected failure
|
pass # expected failure
|
||||||
|
|
||||||
return im.info
|
return iptc_im.info
|
||||||
|
|
|
@ -29,7 +29,7 @@ class BoxReader:
|
||||||
and to easily step into and read sub-boxes.
|
and to easily step into and read sub-boxes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, fp, length=-1):
|
def __init__(self, fp: IO[bytes], length: int = -1) -> None:
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
self.has_length = length >= 0
|
self.has_length = length >= 0
|
||||||
self.length = length
|
self.length = length
|
||||||
|
@ -97,7 +97,7 @@ class BoxReader:
|
||||||
return tbox
|
return tbox
|
||||||
|
|
||||||
|
|
||||||
def _parse_codestream(fp) -> tuple[tuple[int, int], str]:
|
def _parse_codestream(fp: IO[bytes]) -> tuple[tuple[int, int], str]:
|
||||||
"""Parse the JPEG 2000 codestream to extract the size and component
|
"""Parse the JPEG 2000 codestream to extract the size and component
|
||||||
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
count from the SIZ marker segment, returning a PIL (size, mode) tuple."""
|
||||||
|
|
||||||
|
@ -137,7 +137,15 @@ def _res_to_dpi(num: int, denom: int, exp: int) -> float | None:
|
||||||
return (254 * num * (10**exp)) / (10000 * denom)
|
return (254 * num * (10**exp)) / (10000 * denom)
|
||||||
|
|
||||||
|
|
||||||
def _parse_jp2_header(fp):
|
def _parse_jp2_header(
|
||||||
|
fp: IO[bytes],
|
||||||
|
) -> tuple[
|
||||||
|
tuple[int, int],
|
||||||
|
str,
|
||||||
|
str | None,
|
||||||
|
tuple[float, float] | None,
|
||||||
|
ImagePalette.ImagePalette | None,
|
||||||
|
]:
|
||||||
"""Parse the JP2 header box to extract size, component count,
|
"""Parse the JP2 header box to extract size, component count,
|
||||||
color space information, and optionally DPI information,
|
color space information, and optionally DPI information,
|
||||||
returning a (size, mode, mimetype, dpi) tuple."""
|
returning a (size, mode, mimetype, dpi) tuple."""
|
||||||
|
@ -155,6 +163,7 @@ def _parse_jp2_header(fp):
|
||||||
elif tbox == b"ftyp":
|
elif tbox == b"ftyp":
|
||||||
if reader.read_fields(">4s")[0] == b"jpx ":
|
if reader.read_fields(">4s")[0] == b"jpx ":
|
||||||
mimetype = "image/jpx"
|
mimetype = "image/jpx"
|
||||||
|
assert header is not None
|
||||||
|
|
||||||
size = None
|
size = None
|
||||||
mode = None
|
mode = None
|
||||||
|
@ -168,6 +177,9 @@ def _parse_jp2_header(fp):
|
||||||
|
|
||||||
if tbox == b"ihdr":
|
if tbox == b"ihdr":
|
||||||
height, width, nc, bpc = header.read_fields(">IIHB")
|
height, width, nc, bpc = header.read_fields(">IIHB")
|
||||||
|
assert isinstance(height, int)
|
||||||
|
assert isinstance(width, int)
|
||||||
|
assert isinstance(bpc, int)
|
||||||
size = (width, height)
|
size = (width, height)
|
||||||
if nc == 1 and (bpc & 0x7F) > 8:
|
if nc == 1 and (bpc & 0x7F) > 8:
|
||||||
mode = "I;16"
|
mode = "I;16"
|
||||||
|
@ -185,11 +197,21 @@ def _parse_jp2_header(fp):
|
||||||
mode = "CMYK"
|
mode = "CMYK"
|
||||||
elif tbox == b"pclr" and mode in ("L", "LA"):
|
elif tbox == b"pclr" and mode in ("L", "LA"):
|
||||||
ne, npc = header.read_fields(">HB")
|
ne, npc = header.read_fields(">HB")
|
||||||
bitdepths = header.read_fields(">" + ("B" * npc))
|
assert isinstance(ne, int)
|
||||||
if max(bitdepths) <= 8:
|
assert isinstance(npc, int)
|
||||||
|
max_bitdepth = 0
|
||||||
|
for bitdepth in header.read_fields(">" + ("B" * npc)):
|
||||||
|
assert isinstance(bitdepth, int)
|
||||||
|
if bitdepth > max_bitdepth:
|
||||||
|
max_bitdepth = bitdepth
|
||||||
|
if max_bitdepth <= 8:
|
||||||
palette = ImagePalette.ImagePalette()
|
palette = ImagePalette.ImagePalette()
|
||||||
for i in range(ne):
|
for i in range(ne):
|
||||||
palette.getcolor(header.read_fields(">" + ("B" * npc)))
|
color: list[int] = []
|
||||||
|
for value in header.read_fields(">" + ("B" * npc)):
|
||||||
|
assert isinstance(value, int)
|
||||||
|
color.append(value)
|
||||||
|
palette.getcolor(tuple(color))
|
||||||
mode = "P" if mode == "L" else "PA"
|
mode = "P" if mode == "L" else "PA"
|
||||||
elif tbox == b"res ":
|
elif tbox == b"res ":
|
||||||
res = header.read_boxes()
|
res = header.read_boxes()
|
||||||
|
@ -197,6 +219,12 @@ def _parse_jp2_header(fp):
|
||||||
tres = res.next_box_type()
|
tres = res.next_box_type()
|
||||||
if tres == b"resc":
|
if tres == b"resc":
|
||||||
vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB")
|
vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB")
|
||||||
|
assert isinstance(vrcn, int)
|
||||||
|
assert isinstance(vrcd, int)
|
||||||
|
assert isinstance(hrcn, int)
|
||||||
|
assert isinstance(hrcd, int)
|
||||||
|
assert isinstance(vrce, int)
|
||||||
|
assert isinstance(hrce, int)
|
||||||
hres = _res_to_dpi(hrcn, hrcd, hrce)
|
hres = _res_to_dpi(hrcn, hrcd, hrce)
|
||||||
vres = _res_to_dpi(vrcn, vrcd, vrce)
|
vres = _res_to_dpi(vrcn, vrcd, vrce)
|
||||||
if hres is not None and vres is not None:
|
if hres is not None and vres is not None:
|
||||||
|
|
|
@ -60,7 +60,7 @@ def Skip(self: JpegImageFile, marker: int) -> None:
|
||||||
ImageFile._safe_read(self.fp, n)
|
ImageFile._safe_read(self.fp, n)
|
||||||
|
|
||||||
|
|
||||||
def APP(self, marker):
|
def APP(self: JpegImageFile, marker: int) -> None:
|
||||||
#
|
#
|
||||||
# Application marker. Store these in the APP dictionary.
|
# Application marker. Store these in the APP dictionary.
|
||||||
# Also look for well-known application markers.
|
# Also look for well-known application markers.
|
||||||
|
@ -133,12 +133,13 @@ def APP(self, marker):
|
||||||
offset += 4
|
offset += 4
|
||||||
data = s[offset : offset + size]
|
data = s[offset : offset + size]
|
||||||
if code == 0x03ED: # ResolutionInfo
|
if code == 0x03ED: # ResolutionInfo
|
||||||
data = {
|
photoshop[code] = {
|
||||||
"XResolution": i32(data, 0) / 65536,
|
"XResolution": i32(data, 0) / 65536,
|
||||||
"DisplayedUnitsX": i16(data, 4),
|
"DisplayedUnitsX": i16(data, 4),
|
||||||
"YResolution": i32(data, 8) / 65536,
|
"YResolution": i32(data, 8) / 65536,
|
||||||
"DisplayedUnitsY": i16(data, 12),
|
"DisplayedUnitsY": i16(data, 12),
|
||||||
}
|
}
|
||||||
|
else:
|
||||||
photoshop[code] = data
|
photoshop[code] = data
|
||||||
offset += size
|
offset += size
|
||||||
offset += offset & 1 # align
|
offset += offset & 1 # align
|
||||||
|
@ -338,6 +339,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# Create attributes
|
# Create attributes
|
||||||
self.bits = self.layers = 0
|
self.bits = self.layers = 0
|
||||||
|
self._exif_offset = 0
|
||||||
|
|
||||||
# JPEG specifics (internal)
|
# JPEG specifics (internal)
|
||||||
self.layer = []
|
self.layer = []
|
||||||
|
@ -466,7 +468,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self.tile = []
|
self.tile = []
|
||||||
|
|
||||||
def _getexif(self) -> dict[str, Any] | None:
|
def _getexif(self) -> dict[int, Any] | None:
|
||||||
return _getexif(self)
|
return _getexif(self)
|
||||||
|
|
||||||
def _read_dpi_from_exif(self) -> None:
|
def _read_dpi_from_exif(self) -> None:
|
||||||
|
@ -498,17 +500,17 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
):
|
):
|
||||||
self.info["dpi"] = 72, 72
|
self.info["dpi"] = 72, 72
|
||||||
|
|
||||||
def _getmp(self):
|
def _getmp(self) -> dict[int, Any] | None:
|
||||||
return _getmp(self)
|
return _getmp(self)
|
||||||
|
|
||||||
|
|
||||||
def _getexif(self) -> dict[str, Any] | None:
|
def _getexif(self: JpegImageFile) -> dict[int, Any] | None:
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
return None
|
return None
|
||||||
return self.getexif()._get_merged_dict()
|
return self.getexif()._get_merged_dict()
|
||||||
|
|
||||||
|
|
||||||
def _getmp(self):
|
def _getmp(self: JpegImageFile) -> dict[int, Any] | None:
|
||||||
# Extract MP information. This method was inspired by the "highly
|
# Extract MP information. This method was inspired by the "highly
|
||||||
# experimental" _getexif version that's been in use for years now,
|
# experimental" _getexif version that's been in use for years now,
|
||||||
# itself based on the ImageFileDirectory class in the TIFF plugin.
|
# itself based on the ImageFileDirectory class in the TIFF plugin.
|
||||||
|
@ -616,7 +618,7 @@ samplings = {
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
def get_sampling(im):
|
def get_sampling(im: Image.Image) -> int:
|
||||||
# There's no subsampling when images have only 1 layer
|
# There's no subsampling when images have only 1 layer
|
||||||
# (grayscale images) or when they are CMYK (4 layers),
|
# (grayscale images) or when they are CMYK (4 layers),
|
||||||
# so set subsampling to the default value.
|
# so set subsampling to the default value.
|
||||||
|
@ -624,7 +626,7 @@ def get_sampling(im):
|
||||||
# NOTE: currently Pillow can't encode JPEG to YCCK format.
|
# NOTE: currently Pillow can't encode JPEG to YCCK format.
|
||||||
# If YCCK support is added in the future, subsampling code will have
|
# If YCCK support is added in the future, subsampling code will have
|
||||||
# to be updated (here and in JpegEncode.c) to deal with 4 layers.
|
# to be updated (here and in JpegEncode.c) to deal with 4 layers.
|
||||||
if not hasattr(im, "layers") or im.layers in (1, 4):
|
if not isinstance(im, JpegImageFile) or im.layers in (1, 4):
|
||||||
return -1
|
return -1
|
||||||
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
|
sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
|
||||||
return samplings.get(sampling, -1)
|
return samplings.get(sampling, -1)
|
||||||
|
@ -683,7 +685,11 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
subsampling = get_sampling(im)
|
subsampling = get_sampling(im)
|
||||||
|
|
||||||
def validate_qtables(qtables):
|
def validate_qtables(
|
||||||
|
qtables: (
|
||||||
|
str | tuple[list[int], ...] | list[list[int]] | dict[int, list[int]] | None
|
||||||
|
)
|
||||||
|
) -> list[list[int]] | None:
|
||||||
if qtables is None:
|
if qtables is None:
|
||||||
return qtables
|
return qtables
|
||||||
if isinstance(qtables, str):
|
if isinstance(qtables, str):
|
||||||
|
@ -713,12 +719,12 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
if len(table) != 64:
|
if len(table) != 64:
|
||||||
msg = "Invalid quantization table"
|
msg = "Invalid quantization table"
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
table = array.array("H", table)
|
table_array = array.array("H", table)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
msg = "Invalid quantization table"
|
msg = "Invalid quantization table"
|
||||||
raise ValueError(msg) from e
|
raise ValueError(msg) from e
|
||||||
else:
|
else:
|
||||||
qtables[idx] = list(table)
|
qtables[idx] = list(table_array)
|
||||||
return qtables
|
return qtables
|
||||||
|
|
||||||
if qtables == "keep":
|
if qtables == "keep":
|
||||||
|
@ -825,11 +831,11 @@ def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
||||||
##
|
##
|
||||||
# Factory for making JPEG and MPO instances
|
# Factory for making JPEG and MPO instances
|
||||||
def jpeg_factory(fp=None, filename=None):
|
def jpeg_factory(fp: IO[bytes] | None = None, filename: str | bytes | None = None):
|
||||||
im = JpegImageFile(fp, filename)
|
im = JpegImageFile(fp, filename)
|
||||||
try:
|
try:
|
||||||
mpheader = im._getmp()
|
mpheader = im._getmp()
|
||||||
if mpheader[45057] > 1:
|
if mpheader is not None and mpheader[45057] > 1:
|
||||||
for segment, content in im.applist:
|
for segment, content in im.applist:
|
||||||
if segment == "APP1" and b' hdrgm:Version="' in content:
|
if segment == "APP1" and b' hdrgm:Version="' in content:
|
||||||
# Ultra HDR images are not yet supported
|
# Ultra HDR images are not yet supported
|
||||||
|
|
|
@ -37,7 +37,7 @@ You can get the subsampling of a JPEG with the
|
||||||
:func:`.JpegImagePlugin.get_sampling` function.
|
:func:`.JpegImagePlugin.get_sampling` function.
|
||||||
|
|
||||||
In JPEG compressed data a JPEG marker is used instead of an EXIF tag.
|
In JPEG compressed data a JPEG marker is used instead of an EXIF tag.
|
||||||
(ref.: https://web.archive.org/web/20240227115053/https://exiv2.org/tags.html)
|
(ref.: https://exiv2.org/tags.html)
|
||||||
|
|
||||||
|
|
||||||
Quantization tables
|
Quantization tables
|
||||||
|
|
|
@ -22,7 +22,7 @@ from __future__ import annotations
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
from typing import IO
|
from typing import IO, Any, cast
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
Image,
|
Image,
|
||||||
|
@ -101,8 +101,11 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
JpegImagePlugin.JpegImageFile._open(self)
|
JpegImagePlugin.JpegImageFile._open(self)
|
||||||
self._after_jpeg_open()
|
self._after_jpeg_open()
|
||||||
|
|
||||||
def _after_jpeg_open(self, mpheader=None):
|
def _after_jpeg_open(self, mpheader: dict[int, Any] | None = None) -> None:
|
||||||
self.mpinfo = mpheader if mpheader is not None else self._getmp()
|
self.mpinfo = mpheader if mpheader is not None else self._getmp()
|
||||||
|
if self.mpinfo is None:
|
||||||
|
msg = "Image appears to be a malformed MPO file"
|
||||||
|
raise ValueError(msg)
|
||||||
self.n_frames = self.mpinfo[0xB001]
|
self.n_frames = self.mpinfo[0xB001]
|
||||||
self.__mpoffsets = [
|
self.__mpoffsets = [
|
||||||
mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002]
|
mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002]
|
||||||
|
@ -149,7 +152,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def adopt(jpeg_instance, mpheader=None):
|
def adopt(
|
||||||
|
jpeg_instance: JpegImagePlugin.JpegImageFile,
|
||||||
|
mpheader: dict[int, Any] | None = None,
|
||||||
|
) -> MpoImageFile:
|
||||||
"""
|
"""
|
||||||
Transform the instance of JpegImageFile into
|
Transform the instance of JpegImageFile into
|
||||||
an instance of MpoImageFile.
|
an instance of MpoImageFile.
|
||||||
|
@ -161,8 +167,9 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile):
|
||||||
double call to _open.
|
double call to _open.
|
||||||
"""
|
"""
|
||||||
jpeg_instance.__class__ = MpoImageFile
|
jpeg_instance.__class__ = MpoImageFile
|
||||||
jpeg_instance._after_jpeg_open(mpheader)
|
mpo_instance = cast(MpoImageFile, jpeg_instance)
|
||||||
return jpeg_instance
|
mpo_instance._after_jpeg_open(mpheader)
|
||||||
|
return mpo_instance
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
|
|
|
@ -174,12 +174,15 @@ def _write_image(im, filename, existing_pdf, image_refs):
|
||||||
return image_ref, procset
|
return image_ref, procset
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename, save_all=False):
|
def _save(
|
||||||
|
im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False
|
||||||
|
) -> None:
|
||||||
is_appending = im.encoderinfo.get("append", False)
|
is_appending = im.encoderinfo.get("append", False)
|
||||||
|
filename_str = filename.decode() if isinstance(filename, bytes) else filename
|
||||||
if is_appending:
|
if is_appending:
|
||||||
existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="r+b")
|
existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="r+b")
|
||||||
else:
|
else:
|
||||||
existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b")
|
existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="w+b")
|
||||||
|
|
||||||
dpi = im.encoderinfo.get("dpi")
|
dpi = im.encoderinfo.get("dpi")
|
||||||
if dpi:
|
if dpi:
|
||||||
|
@ -228,12 +231,7 @@ def _save(im, fp, filename, save_all=False):
|
||||||
for im in ims:
|
for im in ims:
|
||||||
im_number_of_pages = 1
|
im_number_of_pages = 1
|
||||||
if save_all:
|
if save_all:
|
||||||
try:
|
im_number_of_pages = getattr(im, "n_frames", 1)
|
||||||
im_number_of_pages = im.n_frames
|
|
||||||
except AttributeError:
|
|
||||||
# Image format does not have n_frames.
|
|
||||||
# It is a single frame image
|
|
||||||
pass
|
|
||||||
number_of_pages += im_number_of_pages
|
number_of_pages += im_number_of_pages
|
||||||
for i in range(im_number_of_pages):
|
for i in range(im_number_of_pages):
|
||||||
image_refs.append(existing_pdf.next_object_id(0))
|
image_refs.append(existing_pdf.next_object_id(0))
|
||||||
|
@ -250,7 +248,9 @@ def _save(im, fp, filename, save_all=False):
|
||||||
|
|
||||||
page_number = 0
|
page_number = 0
|
||||||
for im_sequence in ims:
|
for im_sequence in ims:
|
||||||
im_pages = ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
|
im_pages: ImageSequence.Iterator | list[Image.Image] = (
|
||||||
|
ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
|
||||||
|
)
|
||||||
for im in im_pages:
|
for im in im_pages:
|
||||||
image_ref, procset = _write_image(im, filename, existing_pdf, image_refs)
|
image_ref, procset = _write_image(im, filename, existing_pdf, image_refs)
|
||||||
|
|
||||||
|
|
|
@ -38,8 +38,9 @@ import re
|
||||||
import struct
|
import struct
|
||||||
import warnings
|
import warnings
|
||||||
import zlib
|
import zlib
|
||||||
|
from collections.abc import Callable
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from typing import IO, TYPE_CHECKING, Any, NoReturn
|
from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn
|
||||||
|
|
||||||
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
|
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
|
@ -135,7 +136,7 @@ class Blend(IntEnum):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def _safe_zlib_decompress(s):
|
def _safe_zlib_decompress(s: bytes) -> bytes:
|
||||||
dobj = zlib.decompressobj()
|
dobj = zlib.decompressobj()
|
||||||
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
|
plaintext = dobj.decompress(s, MAX_TEXT_CHUNK)
|
||||||
if dobj.unconsumed_tail:
|
if dobj.unconsumed_tail:
|
||||||
|
@ -144,7 +145,7 @@ def _safe_zlib_decompress(s):
|
||||||
return plaintext
|
return plaintext
|
||||||
|
|
||||||
|
|
||||||
def _crc32(data, seed=0):
|
def _crc32(data: bytes, seed: int = 0) -> int:
|
||||||
return zlib.crc32(data, seed) & 0xFFFFFFFF
|
return zlib.crc32(data, seed) & 0xFFFFFFFF
|
||||||
|
|
||||||
|
|
||||||
|
@ -191,7 +192,7 @@ class ChunkStream:
|
||||||
assert self.queue is not None
|
assert self.queue is not None
|
||||||
self.queue.append((cid, pos, length))
|
self.queue.append((cid, pos, length))
|
||||||
|
|
||||||
def call(self, cid, pos, length):
|
def call(self, cid: bytes, pos: int, length: int) -> bytes:
|
||||||
"""Call the appropriate chunk handler"""
|
"""Call the appropriate chunk handler"""
|
||||||
|
|
||||||
logger.debug("STREAM %r %s %s", cid, pos, length)
|
logger.debug("STREAM %r %s %s", cid, pos, length)
|
||||||
|
@ -230,6 +231,7 @@ class ChunkStream:
|
||||||
|
|
||||||
cids = []
|
cids = []
|
||||||
|
|
||||||
|
assert self.fp is not None
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
cid, pos, length = self.read()
|
cid, pos, length = self.read()
|
||||||
|
@ -407,6 +409,7 @@ class PngStream(ChunkStream):
|
||||||
|
|
||||||
def chunk_iCCP(self, pos: int, length: int) -> bytes:
|
def chunk_iCCP(self, pos: int, length: int) -> bytes:
|
||||||
# ICC profile
|
# ICC profile
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
# according to PNG spec, the iCCP chunk contains:
|
# according to PNG spec, the iCCP chunk contains:
|
||||||
# Profile name 1-79 bytes (character string)
|
# Profile name 1-79 bytes (character string)
|
||||||
|
@ -434,6 +437,7 @@ class PngStream(ChunkStream):
|
||||||
|
|
||||||
def chunk_IHDR(self, pos: int, length: int) -> bytes:
|
def chunk_IHDR(self, pos: int, length: int) -> bytes:
|
||||||
# image header
|
# image header
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
if length < 13:
|
if length < 13:
|
||||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||||
|
@ -471,6 +475,7 @@ class PngStream(ChunkStream):
|
||||||
|
|
||||||
def chunk_PLTE(self, pos: int, length: int) -> bytes:
|
def chunk_PLTE(self, pos: int, length: int) -> bytes:
|
||||||
# palette
|
# palette
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
if self.im_mode == "P":
|
if self.im_mode == "P":
|
||||||
self.im_palette = "RGB", s
|
self.im_palette = "RGB", s
|
||||||
|
@ -478,6 +483,7 @@ class PngStream(ChunkStream):
|
||||||
|
|
||||||
def chunk_tRNS(self, pos: int, length: int) -> bytes:
|
def chunk_tRNS(self, pos: int, length: int) -> bytes:
|
||||||
# transparency
|
# transparency
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
if self.im_mode == "P":
|
if self.im_mode == "P":
|
||||||
if _simple_palette.match(s):
|
if _simple_palette.match(s):
|
||||||
|
@ -498,6 +504,7 @@ class PngStream(ChunkStream):
|
||||||
|
|
||||||
def chunk_gAMA(self, pos: int, length: int) -> bytes:
|
def chunk_gAMA(self, pos: int, length: int) -> bytes:
|
||||||
# gamma setting
|
# gamma setting
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
self.im_info["gamma"] = i32(s) / 100000.0
|
self.im_info["gamma"] = i32(s) / 100000.0
|
||||||
return s
|
return s
|
||||||
|
@ -506,6 +513,7 @@ class PngStream(ChunkStream):
|
||||||
# chromaticity, 8 unsigned ints, actual value is scaled by 100,000
|
# chromaticity, 8 unsigned ints, actual value is scaled by 100,000
|
||||||
# WP x,y, Red x,y, Green x,y Blue x,y
|
# WP x,y, Red x,y, Green x,y Blue x,y
|
||||||
|
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
raw_vals = struct.unpack(">%dI" % (len(s) // 4), s)
|
raw_vals = struct.unpack(">%dI" % (len(s) // 4), s)
|
||||||
self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
|
self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals)
|
||||||
|
@ -518,6 +526,7 @@ class PngStream(ChunkStream):
|
||||||
# 2 saturation
|
# 2 saturation
|
||||||
# 3 absolute colorimetric
|
# 3 absolute colorimetric
|
||||||
|
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
if length < 1:
|
if length < 1:
|
||||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||||
|
@ -529,6 +538,7 @@ class PngStream(ChunkStream):
|
||||||
|
|
||||||
def chunk_pHYs(self, pos: int, length: int) -> bytes:
|
def chunk_pHYs(self, pos: int, length: int) -> bytes:
|
||||||
# pixels per unit
|
# pixels per unit
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
if length < 9:
|
if length < 9:
|
||||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||||
|
@ -546,6 +556,7 @@ class PngStream(ChunkStream):
|
||||||
|
|
||||||
def chunk_tEXt(self, pos: int, length: int) -> bytes:
|
def chunk_tEXt(self, pos: int, length: int) -> bytes:
|
||||||
# text
|
# text
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
try:
|
try:
|
||||||
k, v = s.split(b"\0", 1)
|
k, v = s.split(b"\0", 1)
|
||||||
|
@ -554,17 +565,18 @@ class PngStream(ChunkStream):
|
||||||
k = s
|
k = s
|
||||||
v = b""
|
v = b""
|
||||||
if k:
|
if k:
|
||||||
k = k.decode("latin-1", "strict")
|
k_str = k.decode("latin-1", "strict")
|
||||||
v_str = v.decode("latin-1", "replace")
|
v_str = v.decode("latin-1", "replace")
|
||||||
|
|
||||||
self.im_info[k] = v if k == "exif" else v_str
|
self.im_info[k_str] = v if k == b"exif" else v_str
|
||||||
self.im_text[k] = v_str
|
self.im_text[k_str] = v_str
|
||||||
self.check_text_memory(len(v_str))
|
self.check_text_memory(len(v_str))
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def chunk_zTXt(self, pos: int, length: int) -> bytes:
|
def chunk_zTXt(self, pos: int, length: int) -> bytes:
|
||||||
# compressed text
|
# compressed text
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
try:
|
try:
|
||||||
k, v = s.split(b"\0", 1)
|
k, v = s.split(b"\0", 1)
|
||||||
|
@ -589,16 +601,17 @@ class PngStream(ChunkStream):
|
||||||
v = b""
|
v = b""
|
||||||
|
|
||||||
if k:
|
if k:
|
||||||
k = k.decode("latin-1", "strict")
|
k_str = k.decode("latin-1", "strict")
|
||||||
v = v.decode("latin-1", "replace")
|
v_str = v.decode("latin-1", "replace")
|
||||||
|
|
||||||
self.im_info[k] = self.im_text[k] = v
|
self.im_info[k_str] = self.im_text[k_str] = v_str
|
||||||
self.check_text_memory(len(v))
|
self.check_text_memory(len(v_str))
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def chunk_iTXt(self, pos: int, length: int) -> bytes:
|
def chunk_iTXt(self, pos: int, length: int) -> bytes:
|
||||||
# international text
|
# international text
|
||||||
|
assert self.fp is not None
|
||||||
r = s = ImageFile._safe_read(self.fp, length)
|
r = s = ImageFile._safe_read(self.fp, length)
|
||||||
try:
|
try:
|
||||||
k, r = r.split(b"\0", 1)
|
k, r = r.split(b"\0", 1)
|
||||||
|
@ -627,25 +640,27 @@ class PngStream(ChunkStream):
|
||||||
if k == b"XML:com.adobe.xmp":
|
if k == b"XML:com.adobe.xmp":
|
||||||
self.im_info["xmp"] = v
|
self.im_info["xmp"] = v
|
||||||
try:
|
try:
|
||||||
k = k.decode("latin-1", "strict")
|
k_str = k.decode("latin-1", "strict")
|
||||||
lang = lang.decode("utf-8", "strict")
|
lang_str = lang.decode("utf-8", "strict")
|
||||||
tk = tk.decode("utf-8", "strict")
|
tk_str = tk.decode("utf-8", "strict")
|
||||||
v = v.decode("utf-8", "strict")
|
v_str = v.decode("utf-8", "strict")
|
||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
return s
|
return s
|
||||||
|
|
||||||
self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk)
|
self.im_info[k_str] = self.im_text[k_str] = iTXt(v_str, lang_str, tk_str)
|
||||||
self.check_text_memory(len(v))
|
self.check_text_memory(len(v_str))
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def chunk_eXIf(self, pos: int, length: int) -> bytes:
|
def chunk_eXIf(self, pos: int, length: int) -> bytes:
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
self.im_info["exif"] = b"Exif\x00\x00" + s
|
self.im_info["exif"] = b"Exif\x00\x00" + s
|
||||||
return s
|
return s
|
||||||
|
|
||||||
# APNG chunks
|
# APNG chunks
|
||||||
def chunk_acTL(self, pos: int, length: int) -> bytes:
|
def chunk_acTL(self, pos: int, length: int) -> bytes:
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
if length < 8:
|
if length < 8:
|
||||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||||
|
@ -666,6 +681,7 @@ class PngStream(ChunkStream):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def chunk_fcTL(self, pos: int, length: int) -> bytes:
|
def chunk_fcTL(self, pos: int, length: int) -> bytes:
|
||||||
|
assert self.fp is not None
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
if length < 26:
|
if length < 26:
|
||||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||||
|
@ -695,6 +711,7 @@ class PngStream(ChunkStream):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def chunk_fdAT(self, pos: int, length: int) -> bytes:
|
def chunk_fdAT(self, pos: int, length: int) -> bytes:
|
||||||
|
assert self.fp is not None
|
||||||
if length < 4:
|
if length < 4:
|
||||||
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
|
@ -767,7 +784,7 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self._mode = self.png.im_mode
|
self._mode = self.png.im_mode
|
||||||
self._size = self.png.im_size
|
self._size = self.png.im_size
|
||||||
self.info = self.png.im_info
|
self.info = self.png.im_info
|
||||||
self._text = None
|
self._text: dict[str, str | iTXt] | None = None
|
||||||
self.tile = self.png.im_tile
|
self.tile = self.png.im_tile
|
||||||
self.custom_mimetype = self.png.im_custom_mimetype
|
self.custom_mimetype = self.png.im_custom_mimetype
|
||||||
self.n_frames = self.png.im_n_frames or 1
|
self.n_frames = self.png.im_n_frames or 1
|
||||||
|
@ -794,7 +811,7 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self.is_animated = self.n_frames > 1
|
self.is_animated = self.n_frames > 1
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def text(self):
|
def text(self) -> dict[str, str | iTXt]:
|
||||||
# experimental
|
# experimental
|
||||||
if self._text is None:
|
if self._text is None:
|
||||||
# iTxt, tEXt and zTXt chunks may appear at the end of the file
|
# iTxt, tEXt and zTXt chunks may appear at the end of the file
|
||||||
|
@ -806,6 +823,7 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self.load()
|
self.load()
|
||||||
if self.is_animated:
|
if self.is_animated:
|
||||||
self.seek(frame)
|
self.seek(frame)
|
||||||
|
assert self._text is not None
|
||||||
return self._text
|
return self._text
|
||||||
|
|
||||||
def verify(self) -> None:
|
def verify(self) -> None:
|
||||||
|
@ -1038,7 +1056,7 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
self._prev_im.paste(updated, self.dispose_extent, mask)
|
self._prev_im.paste(updated, self.dispose_extent, mask)
|
||||||
self.im = self._prev_im
|
self.im = self._prev_im
|
||||||
|
|
||||||
def _getexif(self) -> dict[str, Any] | None:
|
def _getexif(self) -> dict[int, Any] | None:
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
self.load()
|
self.load()
|
||||||
if "exif" not in self.info and "Raw profile type exif" not in self.info:
|
if "exif" not in self.info and "Raw profile type exif" not in self.info:
|
||||||
|
@ -1075,21 +1093,21 @@ _OUTMODES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def putchunk(fp, cid, *data):
|
def putchunk(fp: IO[bytes], cid: bytes, *data: bytes) -> None:
|
||||||
"""Write a PNG chunk (including CRC field)"""
|
"""Write a PNG chunk (including CRC field)"""
|
||||||
|
|
||||||
data = b"".join(data)
|
byte_data = b"".join(data)
|
||||||
|
|
||||||
fp.write(o32(len(data)) + cid)
|
fp.write(o32(len(byte_data)) + cid)
|
||||||
fp.write(data)
|
fp.write(byte_data)
|
||||||
crc = _crc32(data, _crc32(cid))
|
crc = _crc32(byte_data, _crc32(cid))
|
||||||
fp.write(o32(crc))
|
fp.write(o32(crc))
|
||||||
|
|
||||||
|
|
||||||
class _idat:
|
class _idat:
|
||||||
# wrap output from the encoder in IDAT chunks
|
# wrap output from the encoder in IDAT chunks
|
||||||
|
|
||||||
def __init__(self, fp, chunk):
|
def __init__(self, fp: IO[bytes], chunk: Callable[..., None]) -> None:
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
self.chunk = chunk
|
self.chunk = chunk
|
||||||
|
|
||||||
|
@ -1100,7 +1118,7 @@ class _idat:
|
||||||
class _fdat:
|
class _fdat:
|
||||||
# wrap encoder output in fdAT chunks
|
# wrap encoder output in fdAT chunks
|
||||||
|
|
||||||
def __init__(self, fp, chunk, seq_num):
|
def __init__(self, fp: IO[bytes], chunk: Callable[..., None], seq_num: int) -> None:
|
||||||
self.fp = fp
|
self.fp = fp
|
||||||
self.chunk = chunk
|
self.chunk = chunk
|
||||||
self.seq_num = seq_num
|
self.seq_num = seq_num
|
||||||
|
@ -1110,7 +1128,21 @@ class _fdat:
|
||||||
self.seq_num += 1
|
self.seq_num += 1
|
||||||
|
|
||||||
|
|
||||||
def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_images):
|
class _Frame(NamedTuple):
|
||||||
|
im: Image.Image
|
||||||
|
bbox: tuple[int, int, int, int] | None
|
||||||
|
encoderinfo: dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
|
def _write_multiple_frames(
|
||||||
|
im: Image.Image,
|
||||||
|
fp: IO[bytes],
|
||||||
|
chunk: Callable[..., None],
|
||||||
|
mode: str,
|
||||||
|
rawmode: str,
|
||||||
|
default_image: Image.Image | None,
|
||||||
|
append_images: list[Image.Image],
|
||||||
|
) -> Image.Image | None:
|
||||||
duration = im.encoderinfo.get("duration")
|
duration = im.encoderinfo.get("duration")
|
||||||
loop = im.encoderinfo.get("loop", im.info.get("loop", 0))
|
loop = im.encoderinfo.get("loop", im.info.get("loop", 0))
|
||||||
disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE))
|
disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE))
|
||||||
|
@ -1121,7 +1153,7 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i
|
||||||
else:
|
else:
|
||||||
chain = itertools.chain([im], append_images)
|
chain = itertools.chain([im], append_images)
|
||||||
|
|
||||||
im_frames = []
|
im_frames: list[_Frame] = []
|
||||||
frame_count = 0
|
frame_count = 0
|
||||||
for im_seq in chain:
|
for im_seq in chain:
|
||||||
for im_frame in ImageSequence.Iterator(im_seq):
|
for im_frame in ImageSequence.Iterator(im_seq):
|
||||||
|
@ -1142,24 +1174,24 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i
|
||||||
|
|
||||||
if im_frames:
|
if im_frames:
|
||||||
previous = im_frames[-1]
|
previous = im_frames[-1]
|
||||||
prev_disposal = previous["encoderinfo"].get("disposal")
|
prev_disposal = previous.encoderinfo.get("disposal")
|
||||||
prev_blend = previous["encoderinfo"].get("blend")
|
prev_blend = previous.encoderinfo.get("blend")
|
||||||
if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2:
|
if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2:
|
||||||
prev_disposal = Disposal.OP_BACKGROUND
|
prev_disposal = Disposal.OP_BACKGROUND
|
||||||
|
|
||||||
if prev_disposal == Disposal.OP_BACKGROUND:
|
if prev_disposal == Disposal.OP_BACKGROUND:
|
||||||
base_im = previous["im"].copy()
|
base_im = previous.im.copy()
|
||||||
dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0))
|
dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0))
|
||||||
bbox = previous["bbox"]
|
bbox = previous.bbox
|
||||||
if bbox:
|
if bbox:
|
||||||
dispose = dispose.crop(bbox)
|
dispose = dispose.crop(bbox)
|
||||||
else:
|
else:
|
||||||
bbox = (0, 0) + im.size
|
bbox = (0, 0) + im.size
|
||||||
base_im.paste(dispose, bbox)
|
base_im.paste(dispose, bbox)
|
||||||
elif prev_disposal == Disposal.OP_PREVIOUS:
|
elif prev_disposal == Disposal.OP_PREVIOUS:
|
||||||
base_im = im_frames[-2]["im"]
|
base_im = im_frames[-2].im
|
||||||
else:
|
else:
|
||||||
base_im = previous["im"]
|
base_im = previous.im
|
||||||
delta = ImageChops.subtract_modulo(
|
delta = ImageChops.subtract_modulo(
|
||||||
im_frame.convert("RGBA"), base_im.convert("RGBA")
|
im_frame.convert("RGBA"), base_im.convert("RGBA")
|
||||||
)
|
)
|
||||||
|
@ -1170,14 +1202,14 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i
|
||||||
and prev_blend == encoderinfo.get("blend")
|
and prev_blend == encoderinfo.get("blend")
|
||||||
and "duration" in encoderinfo
|
and "duration" in encoderinfo
|
||||||
):
|
):
|
||||||
previous["encoderinfo"]["duration"] += encoderinfo["duration"]
|
previous.encoderinfo["duration"] += encoderinfo["duration"]
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
bbox = None
|
bbox = None
|
||||||
im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo})
|
im_frames.append(_Frame(im_frame, bbox, encoderinfo))
|
||||||
|
|
||||||
if len(im_frames) == 1 and not default_image:
|
if len(im_frames) == 1 and not default_image:
|
||||||
return im_frames[0]["im"]
|
return im_frames[0].im
|
||||||
|
|
||||||
# animation control
|
# animation control
|
||||||
chunk(
|
chunk(
|
||||||
|
@ -1195,14 +1227,14 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i
|
||||||
|
|
||||||
seq_num = 0
|
seq_num = 0
|
||||||
for frame, frame_data in enumerate(im_frames):
|
for frame, frame_data in enumerate(im_frames):
|
||||||
im_frame = frame_data["im"]
|
im_frame = frame_data.im
|
||||||
if not frame_data["bbox"]:
|
if not frame_data.bbox:
|
||||||
bbox = (0, 0) + im_frame.size
|
bbox = (0, 0) + im_frame.size
|
||||||
else:
|
else:
|
||||||
bbox = frame_data["bbox"]
|
bbox = frame_data.bbox
|
||||||
im_frame = im_frame.crop(bbox)
|
im_frame = im_frame.crop(bbox)
|
||||||
size = im_frame.size
|
size = im_frame.size
|
||||||
encoderinfo = frame_data["encoderinfo"]
|
encoderinfo = frame_data.encoderinfo
|
||||||
frame_duration = int(round(encoderinfo.get("duration", 0)))
|
frame_duration = int(round(encoderinfo.get("duration", 0)))
|
||||||
frame_disposal = encoderinfo.get("disposal", disposal)
|
frame_disposal = encoderinfo.get("disposal", disposal)
|
||||||
frame_blend = encoderinfo.get("blend", blend)
|
frame_blend = encoderinfo.get("blend", blend)
|
||||||
|
@ -1237,13 +1269,20 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i
|
||||||
[("zip", (0, 0) + im_frame.size, 0, rawmode)],
|
[("zip", (0, 0) + im_frame.size, 0, rawmode)],
|
||||||
)
|
)
|
||||||
seq_num = fdat_chunks.seq_num
|
seq_num = fdat_chunks.seq_num
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
_save(im, fp, filename, save_all=True)
|
_save(im, fp, filename, save_all=True)
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
def _save(
|
||||||
|
im: Image.Image,
|
||||||
|
fp: IO[bytes],
|
||||||
|
filename: str | bytes,
|
||||||
|
chunk: Callable[..., None] = putchunk,
|
||||||
|
save_all: bool = False,
|
||||||
|
) -> None:
|
||||||
# save an image to disk (called by the save method)
|
# save an image to disk (called by the save method)
|
||||||
|
|
||||||
if save_all:
|
if save_all:
|
||||||
|
@ -1419,12 +1458,15 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
||||||
exif = exif[6:]
|
exif = exif[6:]
|
||||||
chunk(fp, b"eXIf", exif)
|
chunk(fp, b"eXIf", exif)
|
||||||
|
|
||||||
|
single_im: Image.Image | None = im
|
||||||
if save_all:
|
if save_all:
|
||||||
im = _write_multiple_frames(
|
single_im = _write_multiple_frames(
|
||||||
im, fp, chunk, mode, rawmode, default_image, append_images
|
im, fp, chunk, mode, rawmode, default_image, append_images
|
||||||
)
|
)
|
||||||
if im:
|
if single_im:
|
||||||
ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)])
|
ImageFile._save(
|
||||||
|
single_im, _idat(fp, chunk), [("zip", (0, 0) + single_im.size, 0, rawmode)]
|
||||||
|
)
|
||||||
|
|
||||||
if info:
|
if info:
|
||||||
for info_chunk in info.chunks:
|
for info_chunk in info.chunks:
|
||||||
|
@ -1445,32 +1487,26 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
||||||
# PNG chunk converter
|
# PNG chunk converter
|
||||||
|
|
||||||
|
|
||||||
def getchunks(im, **params):
|
def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]]:
|
||||||
"""Return a list of PNG chunks representing this image."""
|
"""Return a list of PNG chunks representing this image."""
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
class collector:
|
chunks = []
|
||||||
data = []
|
|
||||||
|
|
||||||
def write(self, data: bytes) -> None:
|
def append(fp: IO[bytes], cid: bytes, *data: bytes) -> None:
|
||||||
pass
|
byte_data = b"".join(data)
|
||||||
|
crc = o32(_crc32(byte_data, _crc32(cid)))
|
||||||
|
chunks.append((cid, byte_data, crc))
|
||||||
|
|
||||||
def append(self, chunk: bytes) -> None:
|
fp = BytesIO()
|
||||||
self.data.append(chunk)
|
|
||||||
|
|
||||||
def append(fp, cid, *data):
|
|
||||||
data = b"".join(data)
|
|
||||||
crc = o32(_crc32(data, _crc32(cid)))
|
|
||||||
fp.append((cid, data, crc))
|
|
||||||
|
|
||||||
fp = collector()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
im.encoderinfo = params
|
im.encoderinfo = params
|
||||||
_save(im, fp, None, append)
|
_save(im, fp, "", append)
|
||||||
finally:
|
finally:
|
||||||
del im.encoderinfo
|
del im.encoderinfo
|
||||||
|
|
||||||
return fp.data
|
return chunks
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
|
@ -19,6 +19,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
from typing import IO
|
||||||
|
|
||||||
from . import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import i8
|
from ._binary import i8
|
||||||
|
@ -142,7 +143,9 @@ class PsdImageFile(ImageFile.ImageFile):
|
||||||
self._min_frame = 1
|
self._min_frame = 1
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def layers(self):
|
def layers(
|
||||||
|
self,
|
||||||
|
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
|
||||||
layers = []
|
layers = []
|
||||||
if self._layers_position is not None:
|
if self._layers_position is not None:
|
||||||
self._fp.seek(self._layers_position)
|
self._fp.seek(self._layers_position)
|
||||||
|
@ -181,11 +184,13 @@ class PsdImageFile(ImageFile.ImageFile):
|
||||||
return self.frame
|
return self.frame
|
||||||
|
|
||||||
|
|
||||||
def _layerinfo(fp, ct_bytes):
|
def _layerinfo(
|
||||||
|
fp: IO[bytes], ct_bytes: int
|
||||||
|
) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]:
|
||||||
# read layerinfo block
|
# read layerinfo block
|
||||||
layers = []
|
layers = []
|
||||||
|
|
||||||
def read(size):
|
def read(size: int) -> bytes:
|
||||||
return ImageFile._safe_read(fp, size)
|
return ImageFile._safe_read(fp, size)
|
||||||
|
|
||||||
ct = si16(read(2))
|
ct = si16(read(2))
|
||||||
|
@ -203,7 +208,7 @@ def _layerinfo(fp, ct_bytes):
|
||||||
x1 = si32(read(4))
|
x1 = si32(read(4))
|
||||||
|
|
||||||
# image info
|
# image info
|
||||||
mode = []
|
bands = []
|
||||||
ct_types = i16(read(2))
|
ct_types = i16(read(2))
|
||||||
if ct_types > 4:
|
if ct_types > 4:
|
||||||
fp.seek(ct_types * 6 + 12, io.SEEK_CUR)
|
fp.seek(ct_types * 6 + 12, io.SEEK_CUR)
|
||||||
|
@ -215,23 +220,23 @@ def _layerinfo(fp, ct_bytes):
|
||||||
type = i16(read(2))
|
type = i16(read(2))
|
||||||
|
|
||||||
if type == 65535:
|
if type == 65535:
|
||||||
m = "A"
|
b = "A"
|
||||||
else:
|
else:
|
||||||
m = "RGBA"[type]
|
b = "RGBA"[type]
|
||||||
|
|
||||||
mode.append(m)
|
bands.append(b)
|
||||||
read(4) # size
|
read(4) # size
|
||||||
|
|
||||||
# figure out the image mode
|
# figure out the image mode
|
||||||
mode.sort()
|
bands.sort()
|
||||||
if mode == ["R"]:
|
if bands == ["R"]:
|
||||||
mode = "L"
|
mode = "L"
|
||||||
elif mode == ["B", "G", "R"]:
|
elif bands == ["B", "G", "R"]:
|
||||||
mode = "RGB"
|
mode = "RGB"
|
||||||
elif mode == ["A", "B", "G", "R"]:
|
elif bands == ["A", "B", "G", "R"]:
|
||||||
mode = "RGBA"
|
mode = "RGBA"
|
||||||
else:
|
else:
|
||||||
mode = None # unknown
|
mode = "" # unknown
|
||||||
|
|
||||||
# skip over blend flags and extra information
|
# skip over blend flags and extra information
|
||||||
read(12) # filler
|
read(12) # filler
|
||||||
|
@ -258,19 +263,22 @@ def _layerinfo(fp, ct_bytes):
|
||||||
layers.append((name, mode, (x0, y0, x1, y1)))
|
layers.append((name, mode, (x0, y0, x1, y1)))
|
||||||
|
|
||||||
# get tiles
|
# get tiles
|
||||||
|
layerinfo = []
|
||||||
for i, (name, mode, bbox) in enumerate(layers):
|
for i, (name, mode, bbox) in enumerate(layers):
|
||||||
tile = []
|
tile = []
|
||||||
for m in mode:
|
for m in mode:
|
||||||
t = _maketile(fp, m, bbox, 1)
|
t = _maketile(fp, m, bbox, 1)
|
||||||
if t:
|
if t:
|
||||||
tile.extend(t)
|
tile.extend(t)
|
||||||
layers[i] = name, mode, bbox, tile
|
layerinfo.append((name, mode, bbox, tile))
|
||||||
|
|
||||||
return layers
|
return layerinfo
|
||||||
|
|
||||||
|
|
||||||
def _maketile(file, mode, bbox, channels):
|
def _maketile(
|
||||||
tile = None
|
file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int
|
||||||
|
) -> list[ImageFile._Tile] | None:
|
||||||
|
tiles = None
|
||||||
read = file.read
|
read = file.read
|
||||||
|
|
||||||
compression = i16(read(2))
|
compression = i16(read(2))
|
||||||
|
@ -283,26 +291,26 @@ def _maketile(file, mode, bbox, channels):
|
||||||
if compression == 0:
|
if compression == 0:
|
||||||
#
|
#
|
||||||
# raw compression
|
# raw compression
|
||||||
tile = []
|
tiles = []
|
||||||
for channel in range(channels):
|
for channel in range(channels):
|
||||||
layer = mode[channel]
|
layer = mode[channel]
|
||||||
if mode == "CMYK":
|
if mode == "CMYK":
|
||||||
layer += ";I"
|
layer += ";I"
|
||||||
tile.append(("raw", bbox, offset, layer))
|
tiles.append(ImageFile._Tile("raw", bbox, offset, layer))
|
||||||
offset = offset + xsize * ysize
|
offset = offset + xsize * ysize
|
||||||
|
|
||||||
elif compression == 1:
|
elif compression == 1:
|
||||||
#
|
#
|
||||||
# packbits compression
|
# packbits compression
|
||||||
i = 0
|
i = 0
|
||||||
tile = []
|
tiles = []
|
||||||
bytecount = read(channels * ysize * 2)
|
bytecount = read(channels * ysize * 2)
|
||||||
offset = file.tell()
|
offset = file.tell()
|
||||||
for channel in range(channels):
|
for channel in range(channels):
|
||||||
layer = mode[channel]
|
layer = mode[channel]
|
||||||
if mode == "CMYK":
|
if mode == "CMYK":
|
||||||
layer += ";I"
|
layer += ";I"
|
||||||
tile.append(("packbits", bbox, offset, layer))
|
tiles.append(ImageFile._Tile("packbits", bbox, offset, layer))
|
||||||
for y in range(ysize):
|
for y in range(ysize):
|
||||||
offset = offset + i16(bytecount, i)
|
offset = offset + i16(bytecount, i)
|
||||||
i += 2
|
i += 2
|
||||||
|
@ -312,7 +320,7 @@ def _maketile(file, mode, bbox, channels):
|
||||||
if offset & 1:
|
if offset & 1:
|
||||||
read(1) # padding
|
read(1) # padding
|
||||||
|
|
||||||
return tile
|
return tiles
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
|
@ -55,13 +55,3 @@ class TarIO(ContainerIO.ContainerIO[bytes]):
|
||||||
|
|
||||||
# Open region
|
# Open region
|
||||||
super().__init__(self.fh, self.fh.tell(), size)
|
super().__init__(self.fh, self.fh.tell(), size)
|
||||||
|
|
||||||
# Context manager support
|
|
||||||
def __enter__(self) -> TarIO:
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, *args: object) -> None:
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def close(self) -> None:
|
|
||||||
self.fh.close()
|
|
||||||
|
|
|
@ -334,12 +334,13 @@ class IFDRational(Rational):
|
||||||
|
|
||||||
__slots__ = ("_numerator", "_denominator", "_val")
|
__slots__ = ("_numerator", "_denominator", "_val")
|
||||||
|
|
||||||
def __init__(self, value, denominator=1):
|
def __init__(self, value, denominator: int = 1) -> None:
|
||||||
"""
|
"""
|
||||||
:param value: either an integer numerator, a
|
:param value: either an integer numerator, a
|
||||||
float/rational/other number, or an IFDRational
|
float/rational/other number, or an IFDRational
|
||||||
:param denominator: Optional integer denominator
|
:param denominator: Optional integer denominator
|
||||||
"""
|
"""
|
||||||
|
self._val: Fraction | float
|
||||||
if isinstance(value, IFDRational):
|
if isinstance(value, IFDRational):
|
||||||
self._numerator = value.numerator
|
self._numerator = value.numerator
|
||||||
self._denominator = value.denominator
|
self._denominator = value.denominator
|
||||||
|
@ -444,7 +445,7 @@ class IFDRational(Rational):
|
||||||
__int__ = _delegate("__int__")
|
__int__ = _delegate("__int__")
|
||||||
|
|
||||||
|
|
||||||
def _register_loader(idx, size):
|
def _register_loader(idx: int, size: int):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
from .TiffTags import TYPES
|
from .TiffTags import TYPES
|
||||||
|
|
||||||
|
@ -456,7 +457,7 @@ def _register_loader(idx, size):
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def _register_writer(idx):
|
def _register_writer(idx: int):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
_write_dispatch[idx] = func # noqa: F821
|
_write_dispatch[idx] = func # noqa: F821
|
||||||
return func
|
return func
|
||||||
|
@ -464,7 +465,7 @@ def _register_writer(idx):
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def _register_basic(idx_fmt_name):
|
def _register_basic(idx_fmt_name: tuple[int, str, str]) -> None:
|
||||||
from .TiffTags import TYPES
|
from .TiffTags import TYPES
|
||||||
|
|
||||||
idx, fmt, name = idx_fmt_name
|
idx, fmt, name = idx_fmt_name
|
||||||
|
@ -583,8 +584,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
self.tagtype: dict[int, int] = {}
|
self.tagtype: dict[int, int] = {}
|
||||||
""" Dictionary of tag types """
|
""" Dictionary of tag types """
|
||||||
self.reset()
|
self.reset()
|
||||||
(self.next,) = (
|
self.next = (
|
||||||
self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:])
|
self._unpack("Q", ifh[8:])[0]
|
||||||
|
if self._bigtiff
|
||||||
|
else self._unpack("L", ifh[4:])[0]
|
||||||
)
|
)
|
||||||
self._legacy_api = False
|
self._legacy_api = False
|
||||||
|
|
||||||
|
@ -636,13 +639,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
val = (val,)
|
val = (val,)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def __contains__(self, tag):
|
def __contains__(self, tag: object) -> bool:
|
||||||
return tag in self._tags_v2 or tag in self._tagdata
|
return tag in self._tags_v2 or tag in self._tagdata
|
||||||
|
|
||||||
def __setitem__(self, tag, value):
|
def __setitem__(self, tag: int, value) -> None:
|
||||||
self._setitem(tag, value, self.legacy_api)
|
self._setitem(tag, value, self.legacy_api)
|
||||||
|
|
||||||
def _setitem(self, tag, value, legacy_api):
|
def _setitem(self, tag: int, value, legacy_api: bool) -> None:
|
||||||
basetypes = (Number, bytes, str)
|
basetypes = (Number, bytes, str)
|
||||||
|
|
||||||
info = TiffTags.lookup(tag, self.group)
|
info = TiffTags.lookup(tag, self.group)
|
||||||
|
@ -730,10 +733,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(set(self._tagdata) | set(self._tags_v2))
|
return iter(set(self._tagdata) | set(self._tags_v2))
|
||||||
|
|
||||||
def _unpack(self, fmt, data):
|
def _unpack(self, fmt: str, data: bytes):
|
||||||
return struct.unpack(self._endian + fmt, data)
|
return struct.unpack(self._endian + fmt, data)
|
||||||
|
|
||||||
def _pack(self, fmt, *values):
|
def _pack(self, fmt: str, *values):
|
||||||
return struct.pack(self._endian + fmt, *values)
|
return struct.pack(self._endian + fmt, *values)
|
||||||
|
|
||||||
list(
|
list(
|
||||||
|
@ -754,11 +757,11 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
)
|
)
|
||||||
|
|
||||||
@_register_loader(1, 1) # Basic type, except for the legacy API.
|
@_register_loader(1, 1) # Basic type, except for the legacy API.
|
||||||
def load_byte(self, data, legacy_api=True):
|
def load_byte(self, data: bytes, legacy_api: bool = True) -> bytes:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@_register_writer(1) # Basic type, except for the legacy API.
|
@_register_writer(1) # Basic type, except for the legacy API.
|
||||||
def write_byte(self, data):
|
def write_byte(self, data: bytes | int | IFDRational) -> bytes:
|
||||||
if isinstance(data, IFDRational):
|
if isinstance(data, IFDRational):
|
||||||
data = int(data)
|
data = int(data)
|
||||||
if isinstance(data, int):
|
if isinstance(data, int):
|
||||||
|
@ -766,13 +769,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@_register_loader(2, 1)
|
@_register_loader(2, 1)
|
||||||
def load_string(self, data, legacy_api=True):
|
def load_string(self, data: bytes, legacy_api: bool = True) -> str:
|
||||||
if data.endswith(b"\0"):
|
if data.endswith(b"\0"):
|
||||||
data = data[:-1]
|
data = data[:-1]
|
||||||
return data.decode("latin-1", "replace")
|
return data.decode("latin-1", "replace")
|
||||||
|
|
||||||
@_register_writer(2)
|
@_register_writer(2)
|
||||||
def write_string(self, value):
|
def write_string(self, value: str | bytes | int) -> bytes:
|
||||||
# remerge of https://github.com/python-pillow/Pillow/pull/1416
|
# remerge of https://github.com/python-pillow/Pillow/pull/1416
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
value = str(value)
|
value = str(value)
|
||||||
|
@ -781,7 +784,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
return value + b"\0"
|
return value + b"\0"
|
||||||
|
|
||||||
@_register_loader(5, 8)
|
@_register_loader(5, 8)
|
||||||
def load_rational(self, data, legacy_api=True):
|
def load_rational(self, data, legacy_api: bool = True):
|
||||||
vals = self._unpack(f"{len(data) // 4}L", data)
|
vals = self._unpack(f"{len(data) // 4}L", data)
|
||||||
|
|
||||||
def combine(a, b):
|
def combine(a, b):
|
||||||
|
@ -790,17 +793,17 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
|
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
|
||||||
|
|
||||||
@_register_writer(5)
|
@_register_writer(5)
|
||||||
def write_rational(self, *values):
|
def write_rational(self, *values) -> bytes:
|
||||||
return b"".join(
|
return b"".join(
|
||||||
self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values
|
self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values
|
||||||
)
|
)
|
||||||
|
|
||||||
@_register_loader(7, 1)
|
@_register_loader(7, 1)
|
||||||
def load_undefined(self, data, legacy_api=True):
|
def load_undefined(self, data: bytes, legacy_api: bool = True) -> bytes:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@_register_writer(7)
|
@_register_writer(7)
|
||||||
def write_undefined(self, value):
|
def write_undefined(self, value: bytes | int | IFDRational) -> bytes:
|
||||||
if isinstance(value, IFDRational):
|
if isinstance(value, IFDRational):
|
||||||
value = int(value)
|
value = int(value)
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
|
@ -808,7 +811,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@_register_loader(10, 8)
|
@_register_loader(10, 8)
|
||||||
def load_signed_rational(self, data, legacy_api=True):
|
def load_signed_rational(self, data: bytes, legacy_api: bool = True):
|
||||||
vals = self._unpack(f"{len(data) // 4}l", data)
|
vals = self._unpack(f"{len(data) // 4}l", data)
|
||||||
|
|
||||||
def combine(a, b):
|
def combine(a, b):
|
||||||
|
@ -817,13 +820,13 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
|
return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))
|
||||||
|
|
||||||
@_register_writer(10)
|
@_register_writer(10)
|
||||||
def write_signed_rational(self, *values):
|
def write_signed_rational(self, *values) -> bytes:
|
||||||
return b"".join(
|
return b"".join(
|
||||||
self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31)))
|
self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31)))
|
||||||
for frac in values
|
for frac in values
|
||||||
)
|
)
|
||||||
|
|
||||||
def _ensure_read(self, fp, size):
|
def _ensure_read(self, fp: IO[bytes], size: int) -> bytes:
|
||||||
ret = fp.read(size)
|
ret = fp.read(size)
|
||||||
if len(ret) != size:
|
if len(ret) != size:
|
||||||
msg = (
|
msg = (
|
||||||
|
@ -977,7 +980,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def save(self, fp):
|
def save(self, fp: IO[bytes]) -> int:
|
||||||
if fp.tell() == 0: # skip TIFF header on subsequent pages
|
if fp.tell() == 0: # skip TIFF header on subsequent pages
|
||||||
# tiff header -- PIL always starts the first IFD at offset 8
|
# tiff header -- PIL always starts the first IFD at offset 8
|
||||||
fp.write(self._prefix + self._pack("HL", 42, 8))
|
fp.write(self._prefix + self._pack("HL", 42, 8))
|
||||||
|
@ -1017,7 +1020,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
||||||
.. deprecated:: 3.0.0
|
.. deprecated:: 3.0.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._legacy_api = True
|
self._legacy_api = True
|
||||||
|
|
||||||
|
@ -1029,7 +1032,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
||||||
"""Dictionary of tag types"""
|
"""Dictionary of tag types"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_v2(cls, original):
|
def from_v2(cls, original: ImageFileDirectory_v2) -> ImageFileDirectory_v1:
|
||||||
"""Returns an
|
"""Returns an
|
||||||
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
|
:py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
|
||||||
instance with the same data as is contained in the original
|
instance with the same data as is contained in the original
|
||||||
|
@ -1063,7 +1066,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
||||||
ifd._tags_v2 = dict(self._tags_v2)
|
ifd._tags_v2 = dict(self._tags_v2)
|
||||||
return ifd
|
return ifd
|
||||||
|
|
||||||
def __contains__(self, tag):
|
def __contains__(self, tag: object) -> bool:
|
||||||
return tag in self._tags_v1 or tag in self._tagdata
|
return tag in self._tags_v1 or tag in self._tagdata
|
||||||
|
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
|
@ -1072,7 +1075,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2):
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return iter(set(self._tagdata) | set(self._tags_v1))
|
return iter(set(self._tagdata) | set(self._tags_v1))
|
||||||
|
|
||||||
def __setitem__(self, tag, value):
|
def __setitem__(self, tag: int, value) -> None:
|
||||||
for legacy_api in (False, True):
|
for legacy_api in (False, True):
|
||||||
self._setitem(tag, value, legacy_api)
|
self._setitem(tag, value, legacy_api)
|
||||||
|
|
||||||
|
@ -1122,7 +1125,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self.tag_v2 = ImageFileDirectory_v2(ifh)
|
self.tag_v2 = ImageFileDirectory_v2(ifh)
|
||||||
|
|
||||||
# legacy IFD entries will be filled in later
|
# legacy IFD entries will be filled in later
|
||||||
self.ifd = None
|
self.ifd: ImageFileDirectory_v1 | None = None
|
||||||
|
|
||||||
# setup frame pointers
|
# setup frame pointers
|
||||||
self.__first = self.__next = self.tag_v2.next
|
self.__first = self.__next = self.tag_v2.next
|
||||||
|
@ -1139,13 +1142,15 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self._seek(0)
|
self._seek(0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def n_frames(self):
|
def n_frames(self) -> int:
|
||||||
if self._n_frames is None:
|
current_n_frames = self._n_frames
|
||||||
|
if current_n_frames is None:
|
||||||
current = self.tell()
|
current = self.tell()
|
||||||
self._seek(len(self._frame_pos))
|
self._seek(len(self._frame_pos))
|
||||||
while self._n_frames is None:
|
while self._n_frames is None:
|
||||||
self._seek(self.tell() + 1)
|
self._seek(self.tell() + 1)
|
||||||
self.seek(current)
|
self.seek(current)
|
||||||
|
assert self._n_frames is not None
|
||||||
return self._n_frames
|
return self._n_frames
|
||||||
|
|
||||||
def seek(self, frame: int) -> None:
|
def seek(self, frame: int) -> None:
|
||||||
|
@ -1211,7 +1216,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
"""Return the current frame number"""
|
"""Return the current frame number"""
|
||||||
return self.__frame
|
return self.__frame
|
||||||
|
|
||||||
def get_photoshop_blocks(self):
|
def get_photoshop_blocks(self) -> dict[int, dict[str, bytes]]:
|
||||||
"""
|
"""
|
||||||
Returns a dictionary of Photoshop "Image Resource Blocks".
|
Returns a dictionary of Photoshop "Image Resource Blocks".
|
||||||
The keys are the image resource ID. For more information, see
|
The keys are the image resource ID. For more information, see
|
||||||
|
@ -1258,7 +1263,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
if ExifTags.Base.Orientation in self.tag_v2:
|
if ExifTags.Base.Orientation in self.tag_v2:
|
||||||
del self.tag_v2[ExifTags.Base.Orientation]
|
del self.tag_v2[ExifTags.Base.Orientation]
|
||||||
|
|
||||||
def _load_libtiff(self):
|
def _load_libtiff(self) -> Image.core.PixelAccess | None:
|
||||||
"""Overload method triggered when we detect a compressed tiff
|
"""Overload method triggered when we detect a compressed tiff
|
||||||
Calls out to libtiff"""
|
Calls out to libtiff"""
|
||||||
|
|
||||||
|
@ -1343,7 +1348,7 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
return Image.Image.load(self)
|
return Image.Image.load(self)
|
||||||
|
|
||||||
def _setup(self):
|
def _setup(self) -> None:
|
||||||
"""Setup this image object based on current tags"""
|
"""Setup this image object based on current tags"""
|
||||||
|
|
||||||
if 0xBC01 in self.tag_v2:
|
if 0xBC01 in self.tag_v2:
|
||||||
|
@ -1537,13 +1542,13 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# adjust stride width accordingly
|
# adjust stride width accordingly
|
||||||
stride /= bps_count
|
stride /= bps_count
|
||||||
|
|
||||||
a = (tile_rawmode, int(stride), 1)
|
args = (tile_rawmode, int(stride), 1)
|
||||||
self.tile.append(
|
self.tile.append(
|
||||||
(
|
(
|
||||||
self._compression,
|
self._compression,
|
||||||
(x, y, min(x + w, xsize), min(y + h, ysize)),
|
(x, y, min(x + w, xsize), min(y + h, ysize)),
|
||||||
offset,
|
offset,
|
||||||
a,
|
args,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
x = x + w
|
x = x + w
|
||||||
|
@ -1938,7 +1943,7 @@ class AppendingTiffWriter:
|
||||||
521, # JPEGACTables
|
521, # JPEGACTables
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, fn, new=False):
|
def __init__(self, fn, new: bool = False) -> None:
|
||||||
if hasattr(fn, "read"):
|
if hasattr(fn, "read"):
|
||||||
self.f = fn
|
self.f = fn
|
||||||
self.close_fp = False
|
self.close_fp = False
|
||||||
|
@ -2015,7 +2020,7 @@ class AppendingTiffWriter:
|
||||||
def tell(self) -> int:
|
def tell(self) -> int:
|
||||||
return self.f.tell() - self.offsetOfNewPage
|
return self.f.tell() - self.offsetOfNewPage
|
||||||
|
|
||||||
def seek(self, offset, whence=io.SEEK_SET):
|
def seek(self, offset: int, whence=io.SEEK_SET) -> int:
|
||||||
if whence == os.SEEK_SET:
|
if whence == os.SEEK_SET:
|
||||||
offset += self.offsetOfNewPage
|
offset += self.offsetOfNewPage
|
||||||
|
|
||||||
|
|
|
@ -32,17 +32,24 @@ class _TagInfo(NamedTuple):
|
||||||
class TagInfo(_TagInfo):
|
class TagInfo(_TagInfo):
|
||||||
__slots__: list[str] = []
|
__slots__: list[str] = []
|
||||||
|
|
||||||
def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None):
|
def __new__(
|
||||||
|
cls,
|
||||||
|
value: int | None = None,
|
||||||
|
name: str = "unknown",
|
||||||
|
type: int | None = None,
|
||||||
|
length: int | None = None,
|
||||||
|
enum: dict[str, int] | None = None,
|
||||||
|
) -> TagInfo:
|
||||||
return super().__new__(cls, value, name, type, length, enum or {})
|
return super().__new__(cls, value, name, type, length, enum or {})
|
||||||
|
|
||||||
def cvt_enum(self, value):
|
def cvt_enum(self, value: str) -> int | str:
|
||||||
# Using get will call hash(value), which can be expensive
|
# Using get will call hash(value), which can be expensive
|
||||||
# for some types (e.g. Fraction). Since self.enum is rarely
|
# for some types (e.g. Fraction). Since self.enum is rarely
|
||||||
# used, it's usually better to test it first.
|
# used, it's usually better to test it first.
|
||||||
return self.enum.get(value, value) if self.enum else value
|
return self.enum.get(value, value) if self.enum else value
|
||||||
|
|
||||||
|
|
||||||
def lookup(tag, group=None):
|
def lookup(tag: int, group: int | None = None) -> TagInfo:
|
||||||
"""
|
"""
|
||||||
:param tag: Integer tag number
|
:param tag: Integer tag number
|
||||||
:param group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in
|
:param group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in
|
||||||
|
@ -89,7 +96,7 @@ DOUBLE = 12
|
||||||
IFD = 13
|
IFD = 13
|
||||||
LONG8 = 16
|
LONG8 = 16
|
||||||
|
|
||||||
_tags_v2 = {
|
_tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]] = {
|
||||||
254: ("NewSubfileType", LONG, 1),
|
254: ("NewSubfileType", LONG, 1),
|
||||||
255: ("SubfileType", SHORT, 1),
|
255: ("SubfileType", SHORT, 1),
|
||||||
256: ("ImageWidth", LONG, 1),
|
256: ("ImageWidth", LONG, 1),
|
||||||
|
@ -233,7 +240,7 @@ _tags_v2 = {
|
||||||
50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one
|
50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one
|
||||||
50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006
|
50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006
|
||||||
}
|
}
|
||||||
TAGS_V2_GROUPS = {
|
_tags_v2_groups = {
|
||||||
# ExifIFD
|
# ExifIFD
|
||||||
34665: {
|
34665: {
|
||||||
36864: ("ExifVersion", UNDEFINED, 1),
|
36864: ("ExifVersion", UNDEFINED, 1),
|
||||||
|
@ -281,7 +288,7 @@ TAGS_V2_GROUPS = {
|
||||||
|
|
||||||
# Legacy Tags structure
|
# Legacy Tags structure
|
||||||
# these tags aren't included above, but were in the previous versions
|
# these tags aren't included above, but were in the previous versions
|
||||||
TAGS = {
|
TAGS: dict[int | tuple[int, int], str] = {
|
||||||
347: "JPEGTables",
|
347: "JPEGTables",
|
||||||
700: "XMP",
|
700: "XMP",
|
||||||
# Additional Exif Info
|
# Additional Exif Info
|
||||||
|
@ -426,9 +433,10 @@ TAGS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
TAGS_V2: dict[int, TagInfo] = {}
|
TAGS_V2: dict[int, TagInfo] = {}
|
||||||
|
TAGS_V2_GROUPS: dict[int, dict[int, TagInfo]] = {}
|
||||||
|
|
||||||
|
|
||||||
def _populate():
|
def _populate() -> None:
|
||||||
for k, v in _tags_v2.items():
|
for k, v in _tags_v2.items():
|
||||||
# Populate legacy structure.
|
# Populate legacy structure.
|
||||||
TAGS[k] = v[0]
|
TAGS[k] = v[0]
|
||||||
|
@ -438,9 +446,8 @@ def _populate():
|
||||||
|
|
||||||
TAGS_V2[k] = TagInfo(k, *v)
|
TAGS_V2[k] = TagInfo(k, *v)
|
||||||
|
|
||||||
for tags in TAGS_V2_GROUPS.values():
|
for group, tags in _tags_v2_groups.items():
|
||||||
for k, v in tags.items():
|
TAGS_V2_GROUPS[group] = {k: TagInfo(k, *v) for k, v in tags.items()}
|
||||||
tags[k] = TagInfo(k, *v)
|
|
||||||
|
|
||||||
|
|
||||||
_populate()
|
_populate()
|
||||||
|
|
|
@ -24,8 +24,11 @@ and has been tested with a few sample files found using google.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import IO
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
|
from ._typing import StrOrBytesPath
|
||||||
|
|
||||||
|
|
||||||
class WalImageFile(ImageFile.ImageFile):
|
class WalImageFile(ImageFile.ImageFile):
|
||||||
|
@ -58,7 +61,7 @@ class WalImageFile(ImageFile.ImageFile):
|
||||||
return Image.Image.load(self)
|
return Image.Image.load(self)
|
||||||
|
|
||||||
|
|
||||||
def open(filename):
|
def open(filename: StrOrBytesPath | IO[bytes]) -> WalImageFile:
|
||||||
"""
|
"""
|
||||||
Load texture from a Quake2 WAL texture file.
|
Load texture from a Quake2 WAL texture file.
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
# Initialize seek state
|
# Initialize seek state
|
||||||
self._reset(reset=False)
|
self._reset(reset=False)
|
||||||
|
|
||||||
def _getexif(self) -> dict[str, Any] | None:
|
def _getexif(self) -> dict[int, Any] | None:
|
||||||
if "exif" not in self.info:
|
if "exif" not in self.info:
|
||||||
return None
|
return None
|
||||||
return self.getexif()._get_merged_dict()
|
return self.getexif()._get_merged_dict()
|
||||||
|
@ -115,7 +115,7 @@ class WebPImageFile(ImageFile.ImageFile):
|
||||||
self.__loaded = -1
|
self.__loaded = -1
|
||||||
self.__timestamp = 0
|
self.__timestamp = 0
|
||||||
|
|
||||||
def _get_next(self):
|
def _get_next(self) -> tuple[bytes, int, int]:
|
||||||
# Get next frame
|
# Get next frame
|
||||||
ret = self._decoder.get_next()
|
ret = self._decoder.get_next()
|
||||||
self.__physical_frame += 1
|
self.__physical_frame += 1
|
||||||
|
|
|
@ -152,7 +152,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
def _load(self) -> ImageFile.StubHandler | None:
|
def _load(self) -> ImageFile.StubHandler | None:
|
||||||
return _handler
|
return _handler
|
||||||
|
|
||||||
def load(self, dpi=None):
|
def load(self, dpi: int | None = None) -> Image.core.PixelAccess | None:
|
||||||
if dpi is not None and self._inch is not None:
|
if dpi is not None and self._inch is not None:
|
||||||
self.info["dpi"] = dpi
|
self.info["dpi"] = dpi
|
||||||
x0, y0, x1, y1 = self.info["wmf_bbox"]
|
x0, y0, x1, y1 = self.info["wmf_bbox"]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
class ImagingCore:
|
class ImagingCore:
|
||||||
|
def __getitem__(self, index: int) -> float: ...
|
||||||
def __getattr__(self, name: str) -> Any: ...
|
def __getattr__(self, name: str) -> Any: ...
|
||||||
|
|
||||||
class ImagingFont:
|
class ImagingFont:
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
from typing import Any, TypedDict
|
from typing import Any, Callable
|
||||||
|
|
||||||
from . import _imaging
|
from . import ImageFont, _imaging
|
||||||
|
|
||||||
class _Axis(TypedDict):
|
|
||||||
minimum: int | None
|
|
||||||
default: int | None
|
|
||||||
maximum: int | None
|
|
||||||
name: bytes | None
|
|
||||||
|
|
||||||
class Font:
|
class Font:
|
||||||
@property
|
@property
|
||||||
|
@ -28,42 +22,48 @@ class Font:
|
||||||
def render(
|
def render(
|
||||||
self,
|
self,
|
||||||
string: str | bytes,
|
string: str | bytes,
|
||||||
fill,
|
fill: Callable[[int, int], _imaging.ImagingCore],
|
||||||
mode=...,
|
mode: str,
|
||||||
dir=...,
|
dir: str | None,
|
||||||
features=...,
|
features: list[str] | None,
|
||||||
lang=...,
|
lang: str | None,
|
||||||
stroke_width=...,
|
stroke_width: float,
|
||||||
anchor=...,
|
anchor: str | None,
|
||||||
foreground_ink_long=...,
|
foreground_ink_long: int,
|
||||||
x_start=...,
|
x_start: float,
|
||||||
y_start=...,
|
y_start: float,
|
||||||
/,
|
/,
|
||||||
) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
|
) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ...
|
||||||
def getsize(
|
def getsize(
|
||||||
self,
|
self,
|
||||||
string: str | bytes | bytearray,
|
string: str | bytes | bytearray,
|
||||||
mode=...,
|
mode: str,
|
||||||
dir=...,
|
dir: str | None,
|
||||||
features=...,
|
features: list[str] | None,
|
||||||
lang=...,
|
lang: str | None,
|
||||||
anchor=...,
|
anchor: str | None,
|
||||||
/,
|
/,
|
||||||
) -> tuple[tuple[int, int], tuple[int, int]]: ...
|
) -> tuple[tuple[int, int], tuple[int, int]]: ...
|
||||||
def getlength(
|
def getlength(
|
||||||
self, string: str | bytes, mode=..., dir=..., features=..., lang=..., /
|
self,
|
||||||
|
string: str | bytes,
|
||||||
|
mode: str,
|
||||||
|
dir: str | None,
|
||||||
|
features: list[str] | None,
|
||||||
|
lang: str | None,
|
||||||
|
/,
|
||||||
) -> float: ...
|
) -> float: ...
|
||||||
def getvarnames(self) -> list[bytes]: ...
|
def getvarnames(self) -> list[bytes]: ...
|
||||||
def getvaraxes(self) -> list[_Axis] | None: ...
|
def getvaraxes(self) -> list[ImageFont.Axis]: ...
|
||||||
def setvarname(self, instance_index: int, /) -> None: ...
|
def setvarname(self, instance_index: int, /) -> None: ...
|
||||||
def setvaraxes(self, axes: list[float], /) -> None: ...
|
def setvaraxes(self, axes: list[float], /) -> None: ...
|
||||||
|
|
||||||
def getfont(
|
def getfont(
|
||||||
filename: str | bytes,
|
filename: str | bytes,
|
||||||
size: float,
|
size: float,
|
||||||
index=...,
|
index: int,
|
||||||
encoding=...,
|
encoding: str,
|
||||||
font_bytes=...,
|
font_bytes: bytes,
|
||||||
layout_engine=...,
|
layout_engine: int,
|
||||||
) -> Font: ...
|
) -> Font: ...
|
||||||
def __getattr__(name: str) -> Any: ...
|
def __getattr__(name: str) -> Any: ...
|
||||||
|
|
3
src/PIL/_imagingtk.pyi
Normal file
|
@ -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))
|
return isinstance(f, (bytes, str, os.PathLike))
|
||||||
|
|
||||||
|
|
||||||
def is_directory(f: Any) -> TypeGuard[StrOrBytesPath]:
|
|
||||||
"""Checks if an object is a string, and that it points to a directory."""
|
|
||||||
return is_path(f) and os.path.isdir(f)
|
|
||||||
|
|
||||||
|
|
||||||
class DeferredError:
|
class DeferredError:
|
||||||
def __init__(self, ex: BaseException):
|
def __init__(self, ex: BaseException):
|
||||||
self.ex = ex
|
self.ex = ex
|
||||||
|
|
|
@ -80,7 +80,8 @@ typedef struct Tcl_Command_ *Tcl_Command;
|
||||||
typedef void *ClientData;
|
typedef void *ClientData;
|
||||||
|
|
||||||
typedef int(Tcl_CmdProc)(
|
typedef int(Tcl_CmdProc)(
|
||||||
ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]);
|
ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]
|
||||||
|
);
|
||||||
typedef void(Tcl_CmdDeleteProc)(ClientData clientData);
|
typedef void(Tcl_CmdDeleteProc)(ClientData clientData);
|
||||||
|
|
||||||
/* Typedefs derived from function signatures in Tcl header */
|
/* Typedefs derived from function signatures in Tcl header */
|
||||||
|
@ -90,7 +91,8 @@ typedef Tcl_Command (*Tcl_CreateCommand_t)(
|
||||||
const char *cmdName,
|
const char *cmdName,
|
||||||
Tcl_CmdProc *proc,
|
Tcl_CmdProc *proc,
|
||||||
ClientData clientData,
|
ClientData clientData,
|
||||||
Tcl_CmdDeleteProc *deleteProc);
|
Tcl_CmdDeleteProc *deleteProc
|
||||||
|
);
|
||||||
/* Tcl_AppendResult */
|
/* Tcl_AppendResult */
|
||||||
typedef void (*Tcl_AppendResult_t)(Tcl_Interp *interp, ...);
|
typedef void (*Tcl_AppendResult_t)(Tcl_Interp *interp, ...);
|
||||||
|
|
||||||
|
@ -127,7 +129,8 @@ typedef int (*Tk_PhotoPutBlock_t)(
|
||||||
int y,
|
int y,
|
||||||
int width,
|
int width,
|
||||||
int height,
|
int height,
|
||||||
int compRule);
|
int compRule
|
||||||
|
);
|
||||||
/* Tk_FindPhoto */
|
/* Tk_FindPhoto */
|
||||||
typedef Tk_PhotoHandle (*Tk_FindPhoto_t)(Tcl_Interp *interp, const char *imageName);
|
typedef Tk_PhotoHandle (*Tk_FindPhoto_t)(Tcl_Interp *interp, const char *imageName);
|
||||||
/* Tk_PhotoGetImage */
|
/* Tk_PhotoGetImage */
|
||||||
|
|
|
@ -73,14 +73,16 @@ ImagingFind(const char *name) {
|
||||||
|
|
||||||
static int
|
static int
|
||||||
PyImagingPhotoPut(
|
PyImagingPhotoPut(
|
||||||
ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) {
|
ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv
|
||||||
|
) {
|
||||||
Imaging im;
|
Imaging im;
|
||||||
Tk_PhotoHandle photo;
|
Tk_PhotoHandle photo;
|
||||||
Tk_PhotoImageBlock block;
|
Tk_PhotoImageBlock block;
|
||||||
|
|
||||||
if (argc != 3) {
|
if (argc != 3) {
|
||||||
TCL_APPEND_RESULT(
|
TCL_APPEND_RESULT(
|
||||||
interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL);
|
interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL
|
||||||
|
);
|
||||||
return TCL_ERROR;
|
return TCL_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,14 +130,16 @@ PyImagingPhotoPut(
|
||||||
block.pixelPtr = (unsigned char *)im->block;
|
block.pixelPtr = (unsigned char *)im->block;
|
||||||
|
|
||||||
TK_PHOTO_PUT_BLOCK(
|
TK_PHOTO_PUT_BLOCK(
|
||||||
interp, photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET);
|
interp, photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET
|
||||||
|
);
|
||||||
|
|
||||||
return TCL_OK;
|
return TCL_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
PyImagingPhotoGet(
|
PyImagingPhotoGet(
|
||||||
ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) {
|
ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv
|
||||||
|
) {
|
||||||
Imaging im;
|
Imaging im;
|
||||||
Tk_PhotoHandle photo;
|
Tk_PhotoHandle photo;
|
||||||
Tk_PhotoImageBlock block;
|
Tk_PhotoImageBlock block;
|
||||||
|
@ -143,7 +147,8 @@ PyImagingPhotoGet(
|
||||||
|
|
||||||
if (argc != 3) {
|
if (argc != 3) {
|
||||||
TCL_APPEND_RESULT(
|
TCL_APPEND_RESULT(
|
||||||
interp, "usage: ", argv[0], " srcPhoto destImage", (char *)NULL);
|
interp, "usage: ", argv[0], " srcPhoto destImage", (char *)NULL
|
||||||
|
);
|
||||||
return TCL_ERROR;
|
return TCL_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,13 +188,15 @@ TkImaging_Init(Tcl_Interp *interp) {
|
||||||
"PyImagingPhoto",
|
"PyImagingPhoto",
|
||||||
PyImagingPhotoPut,
|
PyImagingPhotoPut,
|
||||||
(ClientData)0,
|
(ClientData)0,
|
||||||
(Tcl_CmdDeleteProc *)NULL);
|
(Tcl_CmdDeleteProc *)NULL
|
||||||
|
);
|
||||||
TCL_CREATE_COMMAND(
|
TCL_CREATE_COMMAND(
|
||||||
interp,
|
interp,
|
||||||
"PyImagingPhotoGet",
|
"PyImagingPhotoGet",
|
||||||
PyImagingPhotoGet,
|
PyImagingPhotoGet,
|
||||||
(ClientData)0,
|
(ClientData)0,
|
||||||
(Tcl_CmdDeleteProc *)NULL);
|
(Tcl_CmdDeleteProc *)NULL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -394,7 +401,8 @@ _func_loader(void *lib) {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
(TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)_dfunc(lib, "Tk_PhotoPutBlock")) ==
|
(TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)_dfunc(lib, "Tk_PhotoPutBlock")) ==
|
||||||
NULL);
|
NULL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|
182
src/_imaging.c
|
@ -290,7 +290,8 @@ ImagingError_ModeError(void) {
|
||||||
void *
|
void *
|
||||||
ImagingError_ValueError(const char *message) {
|
ImagingError_ValueError(const char *message) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value");
|
PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value"
|
||||||
|
);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,7 +468,8 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) {
|
||||||
return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]);
|
return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]);
|
||||||
case 4:
|
case 4:
|
||||||
return Py_BuildValue(
|
return Py_BuildValue(
|
||||||
"BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3]);
|
"BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case IMAGING_TYPE_INT32:
|
case IMAGING_TYPE_INT32:
|
||||||
|
@ -518,7 +520,8 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
rIsInt = 1;
|
rIsInt = 1;
|
||||||
} else if (im->bands == 1) {
|
} else if (im->bands == 1) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_TypeError, "color must be int or single-element tuple");
|
PyExc_TypeError, "color must be int or single-element tuple"
|
||||||
|
);
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if (tupleSize == -1) {
|
} else if (tupleSize == -1) {
|
||||||
PyErr_SetString(PyExc_TypeError, "color must be int or tuple");
|
PyErr_SetString(PyExc_TypeError, "color must be int or tuple");
|
||||||
|
@ -534,8 +537,8 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
if (rIsInt != 1) {
|
if (rIsInt != 1) {
|
||||||
if (tupleSize != 1) {
|
if (tupleSize != 1) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_TypeError,
|
PyExc_TypeError, "color must be int or single-element tuple"
|
||||||
"color must be int or single-element tuple");
|
);
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if (!PyArg_ParseTuple(color, "L", &r)) {
|
} else if (!PyArg_ParseTuple(color, "L", &r)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -556,7 +559,8 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
if (tupleSize != 1 && tupleSize != 2) {
|
if (tupleSize != 1 && tupleSize != 2) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_TypeError,
|
PyExc_TypeError,
|
||||||
"color must be int, or tuple of one or two elements");
|
"color must be int, or tuple of one or two elements"
|
||||||
|
);
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if (!PyArg_ParseTuple(color, "L|i", &r, &a)) {
|
} else if (!PyArg_ParseTuple(color, "L|i", &r, &a)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -567,7 +571,8 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_TypeError,
|
PyExc_TypeError,
|
||||||
"color must be int, or tuple of one, three or four "
|
"color must be int, or tuple of one, three or four "
|
||||||
"elements");
|
"elements"
|
||||||
|
);
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) {
|
} else if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -608,7 +613,8 @@ getink(PyObject *color, Imaging im, char *ink) {
|
||||||
} else if (tupleSize != 3) {
|
} else if (tupleSize != 3) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_TypeError,
|
PyExc_TypeError,
|
||||||
"color must be int, or tuple of one or three elements");
|
"color must be int, or tuple of one or three elements"
|
||||||
|
);
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
|
} else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -733,7 +739,8 @@ _alpha_composite(ImagingObject *self, PyObject *args) {
|
||||||
ImagingObject *imagep2;
|
ImagingObject *imagep2;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) {
|
args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -748,7 +755,8 @@ _blend(ImagingObject *self, PyObject *args) {
|
||||||
|
|
||||||
alpha = 0.5;
|
alpha = 0.5;
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "O!O!|d", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2, &alpha)) {
|
args, "O!O!|d", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2, &alpha
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -827,7 +835,8 @@ _prepare_lut_table(PyObject *table, Py_ssize_t table_size) {
|
||||||
break;
|
break;
|
||||||
case TYPE_FLOAT32:
|
case TYPE_FLOAT32:
|
||||||
memcpy(
|
memcpy(
|
||||||
&item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32));
|
&item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32)
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case TYPE_DOUBLE:
|
case TYPE_DOUBLE:
|
||||||
memcpy(&dtmp, ((char *)table_data) + i * sizeof(dtmp), sizeof(dtmp));
|
memcpy(&dtmp, ((char *)table_data) + i * sizeof(dtmp), sizeof(dtmp));
|
||||||
|
@ -878,7 +887,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
|
||||||
&size1D,
|
&size1D,
|
||||||
&size2D,
|
&size2D,
|
||||||
&size3D,
|
&size3D,
|
||||||
&table)) {
|
&table
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -896,7 +906,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
|
||||||
if (2 > size1D || size1D > 65 || 2 > size2D || size2D > 65 || 2 > size3D ||
|
if (2 > size1D || size1D > 65 || 2 > size2D || size2D > 65 || 2 > size3D ||
|
||||||
size3D > 65) {
|
size3D > 65) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError, "Table size in any dimension should be from 2 to 65");
|
PyExc_ValueError, "Table size in any dimension should be from 2 to 65"
|
||||||
|
);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -913,13 +924,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ImagingColorLUT3D_linear(
|
if (!ImagingColorLUT3D_linear(
|
||||||
imOut,
|
imOut, self->image, table_channels, size1D, size2D, size3D, prepared_table
|
||||||
self->image,
|
)) {
|
||||||
table_channels,
|
|
||||||
size1D,
|
|
||||||
size2D,
|
|
||||||
size3D,
|
|
||||||
prepared_table)) {
|
|
||||||
free(prepared_table);
|
free(prepared_table);
|
||||||
ImagingDelete(imOut);
|
ImagingDelete(imOut);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -943,7 +949,8 @@ _convert(ImagingObject *self, PyObject *args) {
|
||||||
if (!PyImaging_Check(paletteimage)) {
|
if (!PyImaging_Check(paletteimage)) {
|
||||||
PyObject_Print((PyObject *)paletteimage, stderr, 0);
|
PyObject_Print((PyObject *)paletteimage, stderr, 0);
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError, "palette argument must be image with mode 'P'");
|
PyExc_ValueError, "palette argument must be image with mode 'P'"
|
||||||
|
);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (paletteimage->image->palette == NULL) {
|
if (paletteimage->image->palette == NULL) {
|
||||||
|
@ -953,7 +960,8 @@ _convert(ImagingObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return PyImagingNew(ImagingConvert(
|
return PyImagingNew(ImagingConvert(
|
||||||
self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither));
|
self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -961,7 +969,8 @@ _convert2(ImagingObject *self, PyObject *args) {
|
||||||
ImagingObject *imagep1;
|
ImagingObject *imagep1;
|
||||||
ImagingObject *imagep2;
|
ImagingObject *imagep2;
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) {
|
args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -994,7 +1003,8 @@ _convert_matrix(ImagingObject *self, PyObject *args) {
|
||||||
m + 8,
|
m + 8,
|
||||||
m + 9,
|
m + 9,
|
||||||
m + 10,
|
m + 10,
|
||||||
m + 11)) {
|
m + 11
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1055,7 +1065,8 @@ _filter(ImagingObject *self, PyObject *args) {
|
||||||
float divisor, offset;
|
float divisor, offset;
|
||||||
PyObject *kernel = NULL;
|
PyObject *kernel = NULL;
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel)) {
|
args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1138,7 +1149,8 @@ _getpalette(ImagingObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pack(
|
pack(
|
||||||
(UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize);
|
(UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize
|
||||||
|
);
|
||||||
|
|
||||||
return palette;
|
return palette;
|
||||||
}
|
}
|
||||||
|
@ -1232,7 +1244,8 @@ union hist_extrema {
|
||||||
|
|
||||||
static union hist_extrema *
|
static union hist_extrema *
|
||||||
parse_histogram_extremap(
|
parse_histogram_extremap(
|
||||||
ImagingObject *self, PyObject *extremap, union hist_extrema *ep) {
|
ImagingObject *self, PyObject *extremap, union hist_extrema *ep
|
||||||
|
) {
|
||||||
int i0, i1;
|
int i0, i1;
|
||||||
double f0, f1;
|
double f0, f1;
|
||||||
|
|
||||||
|
@ -1392,7 +1405,8 @@ _paste(ImagingObject *self, PyObject *args) {
|
||||||
int x0, y0, x1, y1;
|
int x0, y0, x1, y1;
|
||||||
ImagingObject *maskp = NULL;
|
ImagingObject *maskp = NULL;
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "O(iiii)|O!", &source, &x0, &y0, &x1, &y1, &Imaging_Type, &maskp)) {
|
args, "O(iiii)|O!", &source, &x0, &y0, &x1, &y1, &Imaging_Type, &maskp
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1404,14 +1418,16 @@ _paste(ImagingObject *self, PyObject *args) {
|
||||||
x0,
|
x0,
|
||||||
y0,
|
y0,
|
||||||
x1,
|
x1,
|
||||||
y1);
|
y1
|
||||||
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (!getink(source, self->image, ink)) {
|
if (!getink(source, self->image, ink)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
status = ImagingFill2(
|
status = ImagingFill2(
|
||||||
self->image, ink, (maskp) ? maskp->image : NULL, x0, y0, x1, y1);
|
self->image, ink, (maskp) ? maskp->image : NULL, x0, y0, x1, y1
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
|
@ -1729,7 +1745,8 @@ _putpalette(ImagingObject *self, PyObject *args) {
|
||||||
UINT8 *palette;
|
UINT8 *palette;
|
||||||
Py_ssize_t palettesize;
|
Py_ssize_t palettesize;
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize)) {
|
args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1887,7 +1904,8 @@ _resize(ImagingObject *self, PyObject *args) {
|
||||||
&box[0],
|
&box[0],
|
||||||
&box[1],
|
&box[1],
|
||||||
&box[2],
|
&box[2],
|
||||||
&box[3])) {
|
&box[3]
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1923,7 +1941,8 @@ _resize(ImagingObject *self, PyObject *args) {
|
||||||
imOut = ImagingNewDirty(imIn->mode, xsize, ysize);
|
imOut = ImagingNewDirty(imIn->mode, xsize, ysize);
|
||||||
|
|
||||||
imOut = ImagingTransform(
|
imOut = ImagingTransform(
|
||||||
imOut, imIn, IMAGING_TRANSFORM_AFFINE, 0, 0, xsize, ysize, a, filter, 1);
|
imOut, imIn, IMAGING_TRANSFORM_AFFINE, 0, 0, xsize, ysize, a, filter, 1
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
imOut = ImagingResample(imIn, xsize, ysize, filter, box);
|
imOut = ImagingResample(imIn, xsize, ysize, filter, box);
|
||||||
}
|
}
|
||||||
|
@ -1944,14 +1963,8 @@ _reduce(ImagingObject *self, PyObject *args) {
|
||||||
box[3] = imIn->ysize;
|
box[3] = imIn->ysize;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args, "(ii)|(iiii)", &xscale, &yscale, &box[0], &box[1], &box[2], &box[3]
|
||||||
"(ii)|(iiii)",
|
)) {
|
||||||
&xscale,
|
|
||||||
&yscale,
|
|
||||||
&box[0],
|
|
||||||
&box[1],
|
|
||||||
&box[2],
|
|
||||||
&box[3])) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2053,7 +2066,8 @@ _transform(ImagingObject *self, PyObject *args) {
|
||||||
&method,
|
&method,
|
||||||
&data,
|
&data,
|
||||||
&filter,
|
&filter,
|
||||||
&fill)) {
|
&fill
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2077,7 +2091,8 @@ _transform(ImagingObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
imOut = ImagingTransform(
|
imOut = ImagingTransform(
|
||||||
self->image, imagep->image, method, x0, y0, x1, y1, a, filter, fill);
|
self->image, imagep->image, method, x0, y0, x1, y1, a, filter, fill
|
||||||
|
);
|
||||||
|
|
||||||
free(a);
|
free(a);
|
||||||
|
|
||||||
|
@ -2250,7 +2265,13 @@ _getcolors(ImagingObject *self, PyObject *args) {
|
||||||
for (i = 0; i < colors; i++) {
|
for (i = 0; i < colors; i++) {
|
||||||
ImagingColorItem *v = &items[i];
|
ImagingColorItem *v = &items[i];
|
||||||
PyObject *item = Py_BuildValue(
|
PyObject *item = Py_BuildValue(
|
||||||
"iN", v->count, getpixel(self->image, self->access, v->x, v->y));
|
"iN", v->count, getpixel(self->image, self->access, v->x, v->y)
|
||||||
|
);
|
||||||
|
if (item == NULL) {
|
||||||
|
Py_DECREF(out);
|
||||||
|
free(items);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
PyList_SetItem(out, i, item);
|
PyList_SetItem(out, i, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2311,14 +2332,16 @@ _getprojection(ImagingObject *self) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ImagingGetProjection(
|
ImagingGetProjection(
|
||||||
self->image, (unsigned char *)xprofile, (unsigned char *)yprofile);
|
self->image, (unsigned char *)xprofile, (unsigned char *)yprofile
|
||||||
|
);
|
||||||
|
|
||||||
result = Py_BuildValue(
|
result = Py_BuildValue(
|
||||||
"y#y#",
|
"y#y#",
|
||||||
xprofile,
|
xprofile,
|
||||||
(Py_ssize_t)self->image->xsize,
|
(Py_ssize_t)self->image->xsize,
|
||||||
yprofile,
|
yprofile,
|
||||||
(Py_ssize_t)self->image->ysize);
|
(Py_ssize_t)self->image->ysize
|
||||||
|
);
|
||||||
|
|
||||||
free(xprofile);
|
free(xprofile);
|
||||||
free(yprofile);
|
free(yprofile);
|
||||||
|
@ -2392,7 +2415,8 @@ _merge(PyObject *self, PyObject *args) {
|
||||||
&Imaging_Type,
|
&Imaging_Type,
|
||||||
&band2,
|
&band2,
|
||||||
&Imaging_Type,
|
&Imaging_Type,
|
||||||
&band3)) {
|
&band3
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2638,7 +2662,8 @@ _font_new(PyObject *self_, PyObject *args) {
|
||||||
unsigned char *glyphdata;
|
unsigned char *glyphdata;
|
||||||
Py_ssize_t glyphdata_length;
|
Py_ssize_t glyphdata_length;
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length)) {
|
args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2796,7 +2821,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
||||||
if (i == 0 || text[i] != text[i - 1]) {
|
if (i == 0 || text[i] != text[i - 1]) {
|
||||||
ImagingDelete(bitmap);
|
ImagingDelete(bitmap);
|
||||||
bitmap = ImagingCrop(
|
bitmap = ImagingCrop(
|
||||||
self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1);
|
self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1
|
||||||
|
);
|
||||||
if (!bitmap) {
|
if (!bitmap) {
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
@ -2808,7 +2834,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) {
|
||||||
glyph->dx0 + x,
|
glyph->dx0 + x,
|
||||||
glyph->dy0 + b,
|
glyph->dy0 + b,
|
||||||
glyph->dx1 + x,
|
glyph->dx1 + x,
|
||||||
glyph->dy1 + b);
|
glyph->dy1 + b
|
||||||
|
);
|
||||||
if (status < 0) {
|
if (status < 0) {
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
|
@ -2947,7 +2974,8 @@ _draw_arc(ImagingDrawObject *self, PyObject *args) {
|
||||||
end,
|
end,
|
||||||
&ink,
|
&ink,
|
||||||
width,
|
width,
|
||||||
self->blend);
|
self->blend
|
||||||
|
);
|
||||||
|
|
||||||
free(xy);
|
free(xy);
|
||||||
|
|
||||||
|
@ -2977,13 +3005,15 @@ _draw_bitmap(ImagingDrawObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
if (n != 1) {
|
if (n != 1) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_TypeError, "coordinate list must contain exactly 1 coordinate");
|
PyExc_TypeError, "coordinate list must contain exactly 1 coordinate"
|
||||||
|
);
|
||||||
free(xy);
|
free(xy);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
n = ImagingDrawBitmap(
|
n = ImagingDrawBitmap(
|
||||||
self->image->image, (int)xy[0], (int)xy[1], bitmap->image, &ink, self->blend);
|
self->image->image, (int)xy[0], (int)xy[1], bitmap->image, &ink, self->blend
|
||||||
|
);
|
||||||
|
|
||||||
free(xy);
|
free(xy);
|
||||||
|
|
||||||
|
@ -3039,7 +3069,8 @@ _draw_chord(ImagingDrawObject *self, PyObject *args) {
|
||||||
&ink,
|
&ink,
|
||||||
fill,
|
fill,
|
||||||
width,
|
width,
|
||||||
self->blend);
|
self->blend
|
||||||
|
);
|
||||||
|
|
||||||
free(xy);
|
free(xy);
|
||||||
|
|
||||||
|
@ -3093,7 +3124,8 @@ _draw_ellipse(ImagingDrawObject *self, PyObject *args) {
|
||||||
&ink,
|
&ink,
|
||||||
fill,
|
fill,
|
||||||
width,
|
width,
|
||||||
self->blend);
|
self->blend
|
||||||
|
);
|
||||||
|
|
||||||
free(xy);
|
free(xy);
|
||||||
|
|
||||||
|
@ -3133,14 +3165,16 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
|
||||||
(int)p[2],
|
(int)p[2],
|
||||||
(int)p[3],
|
(int)p[3],
|
||||||
&ink,
|
&ink,
|
||||||
self->blend) < 0) {
|
self->blend
|
||||||
|
) < 0) {
|
||||||
free(xy);
|
free(xy);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (p) { /* draw last point */
|
if (p) { /* draw last point */
|
||||||
ImagingDrawPoint(
|
ImagingDrawPoint(
|
||||||
self->image->image, (int)p[2], (int)p[3], &ink, self->blend);
|
self->image->image, (int)p[2], (int)p[3], &ink, self->blend
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (i = 0; i < n - 1; i++) {
|
for (i = 0; i < n - 1; i++) {
|
||||||
|
@ -3153,7 +3187,8 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
|
||||||
(int)p[3],
|
(int)p[3],
|
||||||
&ink,
|
&ink,
|
||||||
width,
|
width,
|
||||||
self->blend) < 0) {
|
self->blend
|
||||||
|
) < 0) {
|
||||||
free(xy);
|
free(xy);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -3185,7 +3220,8 @@ _draw_points(ImagingDrawObject *self, PyObject *args) {
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
double *p = &xy[i + i];
|
double *p = &xy[i + i];
|
||||||
if (ImagingDrawPoint(
|
if (ImagingDrawPoint(
|
||||||
self->image->image, (int)p[0], (int)p[1], &ink, self->blend) < 0) {
|
self->image->image, (int)p[0], (int)p[1], &ink, self->blend
|
||||||
|
) < 0) {
|
||||||
free(xy);
|
free(xy);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -3274,7 +3310,8 @@ _draw_pieslice(ImagingDrawObject *self, PyObject *args) {
|
||||||
&ink,
|
&ink,
|
||||||
fill,
|
fill,
|
||||||
width,
|
width,
|
||||||
self->blend);
|
self->blend
|
||||||
|
);
|
||||||
|
|
||||||
free(xy);
|
free(xy);
|
||||||
|
|
||||||
|
@ -3306,7 +3343,8 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
if (n < 2) {
|
if (n < 2) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_TypeError, "coordinate list must contain at least 2 coordinates");
|
PyExc_TypeError, "coordinate list must contain at least 2 coordinates"
|
||||||
|
);
|
||||||
free(xy);
|
free(xy);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -3379,7 +3417,8 @@ _draw_rectangle(ImagingDrawObject *self, PyObject *args) {
|
||||||
&ink,
|
&ink,
|
||||||
fill,
|
fill,
|
||||||
width,
|
width,
|
||||||
self->blend);
|
self->blend
|
||||||
|
);
|
||||||
|
|
||||||
free(xy);
|
free(xy);
|
||||||
|
|
||||||
|
@ -3519,7 +3558,8 @@ _effect_mandelbrot(ImagingObject *self, PyObject *args) {
|
||||||
&extent[1],
|
&extent[1],
|
||||||
&extent[2],
|
&extent[2],
|
||||||
&extent[3],
|
&extent[3],
|
||||||
&quality)) {
|
&quality
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3747,7 +3787,8 @@ _getattr_unsafe_ptrs(ImagingObject *self, void *closure) {
|
||||||
"image32",
|
"image32",
|
||||||
self->image->image32,
|
self->image->image32,
|
||||||
"image",
|
"image",
|
||||||
self->image->image);
|
self->image->image
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct PyGetSetDef getsetters[] = {
|
static struct PyGetSetDef getsetters[] = {
|
||||||
|
@ -3757,7 +3798,8 @@ static struct PyGetSetDef getsetters[] = {
|
||||||
{"id", (getter)_getattr_id},
|
{"id", (getter)_getattr_id},
|
||||||
{"ptr", (getter)_getattr_ptr},
|
{"ptr", (getter)_getattr_ptr},
|
||||||
{"unsafe_ptrs", (getter)_getattr_unsafe_ptrs},
|
{"unsafe_ptrs", (getter)_getattr_unsafe_ptrs},
|
||||||
{NULL}};
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
/* basic sequence semantics */
|
/* basic sequence semantics */
|
||||||
|
|
||||||
|
@ -4066,8 +4108,7 @@ _set_blocks_max(PyObject *self, PyObject *args) {
|
||||||
if (blocks_max < 0) {
|
if (blocks_max < 0) {
|
||||||
PyErr_SetString(PyExc_ValueError, "blocks_max should be greater than 0");
|
PyErr_SetString(PyExc_ValueError, "blocks_max should be greater than 0");
|
||||||
return NULL;
|
return NULL;
|
||||||
} else if (
|
} else if ((unsigned long)blocks_max >
|
||||||
(unsigned long)blocks_max >
|
|
||||||
SIZE_MAX / sizeof(ImagingDefaultArena.blocks_pool[0])) {
|
SIZE_MAX / sizeof(ImagingDefaultArena.blocks_pool[0])) {
|
||||||
PyErr_SetString(PyExc_ValueError, "blocks_max is too large");
|
PyErr_SetString(PyExc_ValueError, "blocks_max is too large");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -4423,7 +4464,8 @@ setup_module(PyObject *m) {
|
||||||
|
|
||||||
PyObject *pillow_version = PyUnicode_FromString(version);
|
PyObject *pillow_version = PyUnicode_FromString(version);
|
||||||
PyDict_SetItemString(
|
PyDict_SetItemString(
|
||||||
d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None);
|
d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None
|
||||||
|
);
|
||||||
Py_XDECREF(pillow_version);
|
Py_XDECREF(pillow_version);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -4448,5 +4490,9 @@ PyInit__imaging(void) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||||
|
#endif
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
|
@ -331,7 +331,8 @@ pyCMScopyAux(cmsHTRANSFORM hTransform, Imaging imDst, const Imaging imSrc) {
|
||||||
memcpy(
|
memcpy(
|
||||||
pDstExtras + x * dstChunkSize,
|
pDstExtras + x * dstChunkSize,
|
||||||
pSrcExtras + x * srcChunkSize,
|
pSrcExtras + x * srcChunkSize,
|
||||||
channelSize);
|
channelSize
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -373,7 +374,8 @@ _buildTransform(
|
||||||
char *sInMode,
|
char *sInMode,
|
||||||
char *sOutMode,
|
char *sOutMode,
|
||||||
int iRenderingIntent,
|
int iRenderingIntent,
|
||||||
cmsUInt32Number cmsFLAGS) {
|
cmsUInt32Number cmsFLAGS
|
||||||
|
) {
|
||||||
cmsHTRANSFORM hTransform;
|
cmsHTRANSFORM hTransform;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
@ -385,7 +387,8 @@ _buildTransform(
|
||||||
hOutputProfile,
|
hOutputProfile,
|
||||||
findLCMStype(sOutMode),
|
findLCMStype(sOutMode),
|
||||||
iRenderingIntent,
|
iRenderingIntent,
|
||||||
cmsFLAGS);
|
cmsFLAGS
|
||||||
|
);
|
||||||
|
|
||||||
Py_END_ALLOW_THREADS;
|
Py_END_ALLOW_THREADS;
|
||||||
|
|
||||||
|
@ -405,7 +408,8 @@ _buildProofTransform(
|
||||||
char *sOutMode,
|
char *sOutMode,
|
||||||
int iRenderingIntent,
|
int iRenderingIntent,
|
||||||
int iProofIntent,
|
int iProofIntent,
|
||||||
cmsUInt32Number cmsFLAGS) {
|
cmsUInt32Number cmsFLAGS
|
||||||
|
) {
|
||||||
cmsHTRANSFORM hTransform;
|
cmsHTRANSFORM hTransform;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS
|
||||||
|
@ -419,7 +423,8 @@ _buildProofTransform(
|
||||||
hProofProfile,
|
hProofProfile,
|
||||||
iRenderingIntent,
|
iRenderingIntent,
|
||||||
iProofIntent,
|
iProofIntent,
|
||||||
cmsFLAGS);
|
cmsFLAGS
|
||||||
|
);
|
||||||
|
|
||||||
Py_END_ALLOW_THREADS;
|
Py_END_ALLOW_THREADS;
|
||||||
|
|
||||||
|
@ -454,7 +459,8 @@ buildTransform(PyObject *self, PyObject *args) {
|
||||||
&sInMode,
|
&sInMode,
|
||||||
&sOutMode,
|
&sOutMode,
|
||||||
&iRenderingIntent,
|
&iRenderingIntent,
|
||||||
&cmsFLAGS)) {
|
&cmsFLAGS
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,7 +470,8 @@ buildTransform(PyObject *self, PyObject *args) {
|
||||||
sInMode,
|
sInMode,
|
||||||
sOutMode,
|
sOutMode,
|
||||||
iRenderingIntent,
|
iRenderingIntent,
|
||||||
cmsFLAGS);
|
cmsFLAGS
|
||||||
|
);
|
||||||
|
|
||||||
if (!transform) {
|
if (!transform) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -499,7 +506,8 @@ buildProofTransform(PyObject *self, PyObject *args) {
|
||||||
&sOutMode,
|
&sOutMode,
|
||||||
&iRenderingIntent,
|
&iRenderingIntent,
|
||||||
&iProofIntent,
|
&iProofIntent,
|
||||||
&cmsFLAGS)) {
|
&cmsFLAGS
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,7 +519,8 @@ buildProofTransform(PyObject *self, PyObject *args) {
|
||||||
sOutMode,
|
sOutMode,
|
||||||
iRenderingIntent,
|
iRenderingIntent,
|
||||||
iProofIntent,
|
iProofIntent,
|
||||||
cmsFLAGS);
|
cmsFLAGS
|
||||||
|
);
|
||||||
|
|
||||||
if (!transform) {
|
if (!transform) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -563,7 +572,8 @@ createProfile(PyObject *self, PyObject *args) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError,
|
PyExc_ValueError,
|
||||||
"ERROR: Could not calculate white point from color temperature "
|
"ERROR: Could not calculate white point from color temperature "
|
||||||
"provided, must be float in degrees Kelvin");
|
"provided, must be float in degrees Kelvin"
|
||||||
|
);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
hProfile = cmsCreateLab2Profile(&whitePoint);
|
hProfile = cmsCreateLab2Profile(&whitePoint);
|
||||||
|
@ -624,7 +634,8 @@ cms_get_display_profile_win32(PyObject *self, PyObject *args) {
|
||||||
HANDLE handle = 0;
|
HANDLE handle = 0;
|
||||||
int is_dc = 0;
|
int is_dc = 0;
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "|" F_HANDLE "i:get_display_profile", &handle, &is_dc)) {
|
args, "|" F_HANDLE "i:get_display_profile", &handle, &is_dc
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -729,7 +740,8 @@ _xyz_py(cmsCIEXYZ *XYZ) {
|
||||||
cmsCIExyY xyY;
|
cmsCIExyY xyY;
|
||||||
cmsXYZ2xyY(&xyY, XYZ);
|
cmsXYZ2xyY(&xyY, XYZ);
|
||||||
return Py_BuildValue(
|
return Py_BuildValue(
|
||||||
"((d,d,d),(d,d,d))", XYZ->X, XYZ->Y, XYZ->Z, xyY.x, xyY.y, xyY.Y);
|
"((d,d,d),(d,d,d))", XYZ->X, XYZ->Y, XYZ->Z, xyY.x, xyY.y, xyY.Y
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -758,7 +770,8 @@ _xyz3_py(cmsCIEXYZ *XYZ) {
|
||||||
xyY[1].Y,
|
xyY[1].Y,
|
||||||
xyY[2].x,
|
xyY[2].x,
|
||||||
xyY[2].y,
|
xyY[2].y,
|
||||||
xyY[2].Y);
|
xyY[2].Y
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -809,7 +822,8 @@ _profile_read_ciexyy_triple(CmsProfileObject *self, cmsTagSignature info) {
|
||||||
triple->Green.Y,
|
triple->Green.Y,
|
||||||
triple->Blue.x,
|
triple->Blue.x,
|
||||||
triple->Blue.y,
|
triple->Blue.y,
|
||||||
triple->Blue.Y);
|
triple->Blue.Y
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -873,7 +887,8 @@ _calculate_rgb_primaries(CmsProfileObject *self, cmsCIEXYZTRIPLE *result) {
|
||||||
hXYZ,
|
hXYZ,
|
||||||
TYPE_XYZ_DBL,
|
TYPE_XYZ_DBL,
|
||||||
INTENT_RELATIVE_COLORIMETRIC,
|
INTENT_RELATIVE_COLORIMETRIC,
|
||||||
cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
|
cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE
|
||||||
|
);
|
||||||
cmsCloseProfile(hXYZ);
|
cmsCloseProfile(hXYZ);
|
||||||
if (hTransform == NULL) {
|
if (hTransform == NULL) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -889,7 +904,8 @@ _check_intent(
|
||||||
int clut,
|
int clut,
|
||||||
cmsHPROFILE hProfile,
|
cmsHPROFILE hProfile,
|
||||||
cmsUInt32Number Intent,
|
cmsUInt32Number Intent,
|
||||||
cmsUInt32Number UsedDirection) {
|
cmsUInt32Number UsedDirection
|
||||||
|
) {
|
||||||
if (clut) {
|
if (clut) {
|
||||||
return cmsIsCLUT(hProfile, Intent, UsedDirection);
|
return cmsIsCLUT(hProfile, Intent, UsedDirection);
|
||||||
} else {
|
} else {
|
||||||
|
@ -934,7 +950,8 @@ _is_intent_supported(CmsProfileObject *self, int clut) {
|
||||||
_check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True
|
_check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True
|
||||||
: Py_False,
|
: Py_False,
|
||||||
_check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True
|
_check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True
|
||||||
: Py_False);
|
: Py_False
|
||||||
|
);
|
||||||
if (id == NULL || entry == NULL) {
|
if (id == NULL || entry == NULL) {
|
||||||
Py_XDECREF(id);
|
Py_XDECREF(id);
|
||||||
Py_XDECREF(entry);
|
Py_XDECREF(entry);
|
||||||
|
@ -968,7 +985,8 @@ static PyMethodDef pyCMSdll_methods[] = {
|
||||||
{"get_display_profile_win32", cms_get_display_profile_win32, METH_VARARGS},
|
{"get_display_profile_win32", cms_get_display_profile_win32, METH_VARARGS},
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{NULL, NULL}};
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
static struct PyMethodDef cms_profile_methods[] = {
|
static struct PyMethodDef cms_profile_methods[] = {
|
||||||
{"is_intent_supported", (PyCFunction)cms_profile_is_intent_supported, METH_VARARGS},
|
{"is_intent_supported", (PyCFunction)cms_profile_is_intent_supported, METH_VARARGS},
|
||||||
|
@ -1028,7 +1046,8 @@ cms_profile_getattr_creation_date(CmsProfileObject *self, void *closure) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return PyDateTime_FromDateAndTime(
|
return PyDateTime_FromDateAndTime(
|
||||||
1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, ct.tm_hour, ct.tm_min, ct.tm_sec, 0);
|
1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, ct.tm_hour, ct.tm_min, ct.tm_sec, 0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -1106,13 +1125,15 @@ cms_profile_getattr_colorimetric_intent(CmsProfileObject *self, void *closure) {
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
cms_profile_getattr_perceptual_rendering_intent_gamut(
|
cms_profile_getattr_perceptual_rendering_intent_gamut(
|
||||||
CmsProfileObject *self, void *closure) {
|
CmsProfileObject *self, void *closure
|
||||||
|
) {
|
||||||
return _profile_read_signature(self, cmsSigPerceptualRenderingIntentGamutTag);
|
return _profile_read_signature(self, cmsSigPerceptualRenderingIntentGamutTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
cms_profile_getattr_saturation_rendering_intent_gamut(
|
cms_profile_getattr_saturation_rendering_intent_gamut(
|
||||||
CmsProfileObject *self, void *closure) {
|
CmsProfileObject *self, void *closure
|
||||||
|
) {
|
||||||
return _profile_read_signature(self, cmsSigSaturationRenderingIntentGamutTag);
|
return _profile_read_signature(self, cmsSigSaturationRenderingIntentGamutTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1145,7 +1166,8 @@ cms_profile_getattr_blue_colorant(CmsProfileObject *self, void *closure) {
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
cms_profile_getattr_media_white_point_temperature(
|
cms_profile_getattr_media_white_point_temperature(
|
||||||
CmsProfileObject *self, void *closure) {
|
CmsProfileObject *self, void *closure
|
||||||
|
) {
|
||||||
cmsCIEXYZ *XYZ;
|
cmsCIEXYZ *XYZ;
|
||||||
cmsCIExyY xyY;
|
cmsCIExyY xyY;
|
||||||
cmsFloat64Number tempK;
|
cmsFloat64Number tempK;
|
||||||
|
@ -1329,7 +1351,8 @@ cms_profile_getattr_icc_measurement_condition(CmsProfileObject *self, void *clos
|
||||||
"flare",
|
"flare",
|
||||||
mc->Flare,
|
mc->Flare,
|
||||||
"illuminant_type",
|
"illuminant_type",
|
||||||
_illu_map(mc->IlluminantType));
|
_illu_map(mc->IlluminantType)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
|
@ -1359,7 +1382,8 @@ cms_profile_getattr_icc_viewing_condition(CmsProfileObject *self, void *closure)
|
||||||
vc->SurroundXYZ.Y,
|
vc->SurroundXYZ.Y,
|
||||||
vc->SurroundXYZ.Z,
|
vc->SurroundXYZ.Z,
|
||||||
"illuminant_type",
|
"illuminant_type",
|
||||||
_illu_map(vc->IlluminantType));
|
_illu_map(vc->IlluminantType)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct PyGetSetDef cms_profile_getsetters[] = {
|
static struct PyGetSetDef cms_profile_getsetters[] = {
|
||||||
|
@ -1407,11 +1431,12 @@ static struct PyGetSetDef cms_profile_getsetters[] = {
|
||||||
{"colorant_table_out", (getter)cms_profile_getattr_colorant_table_out},
|
{"colorant_table_out", (getter)cms_profile_getattr_colorant_table_out},
|
||||||
{"intent_supported", (getter)cms_profile_getattr_is_intent_supported},
|
{"intent_supported", (getter)cms_profile_getattr_is_intent_supported},
|
||||||
{"clut", (getter)cms_profile_getattr_is_clut},
|
{"clut", (getter)cms_profile_getattr_is_clut},
|
||||||
{"icc_measurement_condition",
|
{"icc_measurement_condition", (getter)cms_profile_getattr_icc_measurement_condition
|
||||||
(getter)cms_profile_getattr_icc_measurement_condition},
|
},
|
||||||
{"icc_viewing_condition", (getter)cms_profile_getattr_icc_viewing_condition},
|
{"icc_viewing_condition", (getter)cms_profile_getattr_icc_viewing_condition},
|
||||||
|
|
||||||
{NULL}};
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
static PyTypeObject CmsProfile_Type = {
|
static PyTypeObject CmsProfile_Type = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsProfile", /*tp_name*/
|
PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsProfile", /*tp_name*/
|
||||||
|
@ -1538,5 +1563,9 @@ PyInit__imagingcms(void) {
|
||||||
|
|
||||||
PyDateTime_IMPORT;
|
PyDateTime_IMPORT;
|
||||||
|
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||||
|
#endif
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
139
src/_imagingft.c
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
#define PY_SSIZE_T_CLEAN
|
#define PY_SSIZE_T_CLEAN
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
|
#include "thirdparty/pythoncapi_compat.h"
|
||||||
#include "libImaging/Imaging.h"
|
#include "libImaging/Imaging.h"
|
||||||
|
|
||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
|
@ -125,7 +126,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
||||||
unsigned char *font_bytes;
|
unsigned char *font_bytes;
|
||||||
Py_ssize_t font_bytes_size = 0;
|
Py_ssize_t font_bytes_size = 0;
|
||||||
static char *kwlist[] = {
|
static char *kwlist[] = {
|
||||||
"filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL};
|
"filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL
|
||||||
|
};
|
||||||
|
|
||||||
if (!library) {
|
if (!library) {
|
||||||
PyErr_SetString(PyExc_OSError, "failed to initialize FreeType library");
|
PyErr_SetString(PyExc_OSError, "failed to initialize FreeType library");
|
||||||
|
@ -147,7 +149,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
||||||
&encoding,
|
&encoding,
|
||||||
&font_bytes,
|
&font_bytes,
|
||||||
&font_bytes_size,
|
&font_bytes_size,
|
||||||
&layout_engine)) {
|
&layout_engine
|
||||||
|
)) {
|
||||||
PyConfig_Clear(&config);
|
PyConfig_Clear(&config);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -165,7 +168,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
||||||
&encoding,
|
&encoding,
|
||||||
&font_bytes,
|
&font_bytes,
|
||||||
&font_bytes_size,
|
&font_bytes_size,
|
||||||
&layout_engine)) {
|
&layout_engine
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -198,7 +202,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) {
|
||||||
(FT_Byte *)self->font_bytes,
|
(FT_Byte *)self->font_bytes,
|
||||||
font_bytes_size,
|
font_bytes_size,
|
||||||
index,
|
index,
|
||||||
&self->face);
|
&self->face
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +247,8 @@ text_layout_raqm(
|
||||||
const char *dir,
|
const char *dir,
|
||||||
PyObject *features,
|
PyObject *features,
|
||||||
const char *lang,
|
const char *lang,
|
||||||
GlyphInfo **glyph_info) {
|
GlyphInfo **glyph_info
|
||||||
|
) {
|
||||||
size_t i = 0, count = 0, start = 0;
|
size_t i = 0, count = 0, start = 0;
|
||||||
raqm_t *rq;
|
raqm_t *rq;
|
||||||
raqm_glyph_t *glyphs = NULL;
|
raqm_glyph_t *glyphs = NULL;
|
||||||
|
@ -296,13 +302,14 @@ text_layout_raqm(
|
||||||
#if !defined(RAQM_VERSION_ATLEAST)
|
#if !defined(RAQM_VERSION_ATLEAST)
|
||||||
/* RAQM_VERSION_ATLEAST was added in Raqm 0.7.0 */
|
/* RAQM_VERSION_ATLEAST was added in Raqm 0.7.0 */
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError,
|
PyExc_ValueError, "libraqm 0.7 or greater required for 'ttb' direction"
|
||||||
"libraqm 0.7 or greater required for 'ttb' direction");
|
);
|
||||||
goto failed;
|
goto failed;
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'");
|
PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"
|
||||||
|
);
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -398,7 +405,8 @@ text_layout_fallback(
|
||||||
const char *lang,
|
const char *lang,
|
||||||
GlyphInfo **glyph_info,
|
GlyphInfo **glyph_info,
|
||||||
int mask,
|
int mask,
|
||||||
int color) {
|
int color
|
||||||
|
) {
|
||||||
int error, load_flags, i;
|
int error, load_flags, i;
|
||||||
char *buffer = NULL;
|
char *buffer = NULL;
|
||||||
FT_ULong ch;
|
FT_ULong ch;
|
||||||
|
@ -411,7 +419,8 @@ text_layout_fallback(
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_KeyError,
|
PyExc_KeyError,
|
||||||
"setting text direction, language or font features is not supported "
|
"setting text direction, language or font features is not supported "
|
||||||
"without libraqm");
|
"without libraqm"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PyUnicode_Check(string)) {
|
if (PyUnicode_Check(string)) {
|
||||||
|
@ -458,7 +467,8 @@ text_layout_fallback(
|
||||||
last_index,
|
last_index,
|
||||||
(*glyph_info)[i].index,
|
(*glyph_info)[i].index,
|
||||||
ft_kerning_default,
|
ft_kerning_default,
|
||||||
&delta) == 0) {
|
&delta
|
||||||
|
) == 0) {
|
||||||
(*glyph_info)[i - 1].x_advance += PIXEL(delta.x);
|
(*glyph_info)[i - 1].x_advance += PIXEL(delta.x);
|
||||||
(*glyph_info)[i - 1].y_advance += PIXEL(delta.y);
|
(*glyph_info)[i - 1].y_advance += PIXEL(delta.y);
|
||||||
}
|
}
|
||||||
|
@ -482,7 +492,8 @@ text_layout(
|
||||||
const char *lang,
|
const char *lang,
|
||||||
GlyphInfo **glyph_info,
|
GlyphInfo **glyph_info,
|
||||||
int mask,
|
int mask,
|
||||||
int color) {
|
int color
|
||||||
|
) {
|
||||||
size_t count;
|
size_t count;
|
||||||
#ifdef HAVE_RAQM
|
#ifdef HAVE_RAQM
|
||||||
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
|
if (have_raqm && self->layout_engine == LAYOUT_RAQM) {
|
||||||
|
@ -491,7 +502,8 @@ text_layout(
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
count = text_layout_fallback(
|
count = text_layout_fallback(
|
||||||
string, self, dir, features, lang, glyph_info, mask, color);
|
string, self, dir, features, lang, glyph_info, mask, color
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
@ -513,7 +525,8 @@ font_getlength(FontObject *self, PyObject *args) {
|
||||||
/* calculate size and bearing for a given string */
|
/* calculate size and bearing for a given string */
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang)) {
|
args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,7 +568,8 @@ bounding_box_and_anchors(
|
||||||
int *width,
|
int *width,
|
||||||
int *height,
|
int *height,
|
||||||
int *x_offset,
|
int *x_offset,
|
||||||
int *y_offset) {
|
int *y_offset
|
||||||
|
) {
|
||||||
int position; /* pen position along primary axis, in 26.6 precision */
|
int position; /* pen position along primary axis, in 26.6 precision */
|
||||||
int advanced; /* pen position along primary axis, in pixels */
|
int advanced; /* pen position along primary axis, in pixels */
|
||||||
int px, py; /* position of current glyph, in pixels */
|
int px, py; /* position of current glyph, in pixels */
|
||||||
|
@ -660,7 +674,8 @@ bounding_box_and_anchors(
|
||||||
case 'm': // middle (ascender + descender) / 2
|
case 'm': // middle (ascender + descender) / 2
|
||||||
y_anchor = PIXEL(
|
y_anchor = PIXEL(
|
||||||
(face->size->metrics.ascender + face->size->metrics.descender) /
|
(face->size->metrics.ascender + face->size->metrics.descender) /
|
||||||
2);
|
2
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case 's': // horizontal baseline
|
case 's': // horizontal baseline
|
||||||
y_anchor = 0;
|
y_anchor = 0;
|
||||||
|
@ -740,7 +755,8 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
/* calculate size and bearing for a given string */
|
/* calculate size and bearing for a given string */
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) {
|
args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -772,7 +788,8 @@ font_getsize(FontObject *self, PyObject *args) {
|
||||||
&width,
|
&width,
|
||||||
&height,
|
&height,
|
||||||
&x_offset,
|
&x_offset,
|
||||||
&y_offset);
|
&y_offset
|
||||||
|
);
|
||||||
if (glyph_info) {
|
if (glyph_info) {
|
||||||
PyMem_Free(glyph_info);
|
PyMem_Free(glyph_info);
|
||||||
glyph_info = NULL;
|
glyph_info = NULL;
|
||||||
|
@ -841,7 +858,8 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
&anchor,
|
&anchor,
|
||||||
&foreground_ink_long,
|
&foreground_ink_long,
|
||||||
&x_start,
|
&x_start,
|
||||||
&y_start)) {
|
&y_start
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -888,7 +906,8 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
&width,
|
&width,
|
||||||
&height,
|
&height,
|
||||||
&x_offset,
|
&x_offset,
|
||||||
&y_offset);
|
&y_offset
|
||||||
|
);
|
||||||
if (error) {
|
if (error) {
|
||||||
PyMem_Del(glyph_info);
|
PyMem_Del(glyph_info);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -928,7 +947,8 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
(FT_Fixed)stroke_width * 64,
|
(FT_Fixed)stroke_width * 64,
|
||||||
FT_STROKER_LINECAP_ROUND,
|
FT_STROKER_LINECAP_ROUND,
|
||||||
FT_STROKER_LINEJOIN_ROUND,
|
FT_STROKER_LINEJOIN_ROUND,
|
||||||
0);
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1103,8 +1123,8 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp);
|
BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp);
|
||||||
target[k * 4 + 3] = CLIP8(
|
target[k * 4 + 3] = CLIP8(
|
||||||
src_alpha +
|
src_alpha +
|
||||||
MULDIV255(
|
MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp)
|
||||||
target[k * 4 + 3], (255 - src_alpha), tmp));
|
);
|
||||||
} else {
|
} else {
|
||||||
/* paste unpremultiplied RGBA values */
|
/* paste unpremultiplied RGBA values */
|
||||||
target[k * 4 + 0] = src_red;
|
target[k * 4 + 0] = src_red;
|
||||||
|
@ -1122,15 +1142,20 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
if (src_alpha > 0) {
|
if (src_alpha > 0) {
|
||||||
if (target[k * 4 + 3] > 0) {
|
if (target[k * 4 + 3] > 0) {
|
||||||
target[k * 4 + 0] = BLEND(
|
target[k * 4 + 0] = BLEND(
|
||||||
src_alpha, target[k * 4 + 0], ink[0], tmp);
|
src_alpha, target[k * 4 + 0], ink[0], tmp
|
||||||
|
);
|
||||||
target[k * 4 + 1] = BLEND(
|
target[k * 4 + 1] = BLEND(
|
||||||
src_alpha, target[k * 4 + 1], ink[1], tmp);
|
src_alpha, target[k * 4 + 1], ink[1], tmp
|
||||||
|
);
|
||||||
target[k * 4 + 2] = BLEND(
|
target[k * 4 + 2] = BLEND(
|
||||||
src_alpha, target[k * 4 + 2], ink[2], tmp);
|
src_alpha, target[k * 4 + 2], ink[2], tmp
|
||||||
|
);
|
||||||
target[k * 4 + 3] = CLIP8(
|
target[k * 4 + 3] = CLIP8(
|
||||||
src_alpha +
|
src_alpha +
|
||||||
MULDIV255(
|
MULDIV255(
|
||||||
target[k * 4 + 3], (255 - src_alpha), tmp));
|
target[k * 4 + 3], (255 - src_alpha), tmp
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
target[k * 4 + 0] = ink[0];
|
target[k * 4 + 0] = ink[0];
|
||||||
target[k * 4 + 1] = ink[1];
|
target[k * 4 + 1] = ink[1];
|
||||||
|
@ -1148,7 +1173,9 @@ font_render(FontObject *self, PyObject *args) {
|
||||||
? CLIP8(
|
? CLIP8(
|
||||||
src_alpha +
|
src_alpha +
|
||||||
MULDIV255(
|
MULDIV255(
|
||||||
target[k], (255 - src_alpha), tmp))
|
target[k], (255 - src_alpha), tmp
|
||||||
|
)
|
||||||
|
)
|
||||||
: src_alpha;
|
: src_alpha;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1209,30 +1236,49 @@ font_getvarnames(FontObject *self) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int *list_names_filled = PyMem_Malloc(num_namedstyles * sizeof(int));
|
||||||
|
if (list_names_filled == NULL) {
|
||||||
|
Py_DECREF(list_names);
|
||||||
|
FT_Done_MM_Var(library, master);
|
||||||
|
return PyErr_NoMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < num_namedstyles; i++) {
|
||||||
|
list_names_filled[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
name_count = FT_Get_Sfnt_Name_Count(self->face);
|
name_count = FT_Get_Sfnt_Name_Count(self->face);
|
||||||
for (i = 0; i < name_count; i++) {
|
for (i = 0; i < name_count; i++) {
|
||||||
error = FT_Get_Sfnt_Name(self->face, i, &name);
|
error = FT_Get_Sfnt_Name(self->face, i, &name);
|
||||||
if (error) {
|
if (error) {
|
||||||
|
PyMem_Free(list_names_filled);
|
||||||
Py_DECREF(list_names);
|
Py_DECREF(list_names);
|
||||||
FT_Done_MM_Var(library, master);
|
FT_Done_MM_Var(library, master);
|
||||||
return geterror(error);
|
return geterror(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (j = 0; j < num_namedstyles; j++) {
|
for (j = 0; j < num_namedstyles; j++) {
|
||||||
if (PyList_GetItem(list_names, j) != NULL) {
|
if (list_names_filled[j]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (master->namedstyle[j].strid == name.name_id) {
|
if (master->namedstyle[j].strid == name.name_id) {
|
||||||
list_name = Py_BuildValue("y#", name.string, name.string_len);
|
list_name = Py_BuildValue("y#", name.string, name.string_len);
|
||||||
|
if (list_name == NULL) {
|
||||||
|
PyMem_Free(list_names_filled);
|
||||||
|
Py_DECREF(list_names);
|
||||||
|
FT_Done_MM_Var(library, master);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
PyList_SetItem(list_names, j, list_name);
|
PyList_SetItem(list_names, j, list_name);
|
||||||
|
list_names_filled[j] = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyMem_Free(list_names_filled);
|
||||||
FT_Done_MM_Var(library, master);
|
FT_Done_MM_Var(library, master);
|
||||||
|
|
||||||
return list_names;
|
return list_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1289,9 +1335,14 @@ font_getvaraxes(FontObject *self) {
|
||||||
|
|
||||||
if (name.name_id == axis.strid) {
|
if (name.name_id == axis.strid) {
|
||||||
axis_name = Py_BuildValue("y#", name.string, name.string_len);
|
axis_name = Py_BuildValue("y#", name.string, name.string_len);
|
||||||
PyDict_SetItemString(
|
if (axis_name == NULL) {
|
||||||
list_axis, "name", axis_name ? axis_name : Py_None);
|
Py_DECREF(list_axis);
|
||||||
Py_XDECREF(axis_name);
|
Py_DECREF(list_axes);
|
||||||
|
FT_Done_MM_Var(library, master);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyDict_SetItemString(list_axis, "name", axis_name);
|
||||||
|
Py_DECREF(axis_name);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1345,7 +1396,12 @@ font_setvaraxes(FontObject *self, PyObject *args) {
|
||||||
return PyErr_NoMemory();
|
return PyErr_NoMemory();
|
||||||
}
|
}
|
||||||
for (i = 0; i < num_coords; i++) {
|
for (i = 0; i < num_coords; i++) {
|
||||||
item = PyList_GET_ITEM(axes, i);
|
item = PyList_GetItemRef(axes, i);
|
||||||
|
if (item == NULL) {
|
||||||
|
free(coords);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (PyFloat_Check(item)) {
|
if (PyFloat_Check(item)) {
|
||||||
coord = PyFloat_AS_DOUBLE(item);
|
coord = PyFloat_AS_DOUBLE(item);
|
||||||
} else if (PyLong_Check(item)) {
|
} else if (PyLong_Check(item)) {
|
||||||
|
@ -1353,10 +1409,12 @@ font_setvaraxes(FontObject *self, PyObject *args) {
|
||||||
} else if (PyNumber_Check(item)) {
|
} else if (PyNumber_Check(item)) {
|
||||||
coord = PyFloat_AsDouble(item);
|
coord = PyFloat_AsDouble(item);
|
||||||
} else {
|
} else {
|
||||||
|
Py_DECREF(item);
|
||||||
free(coords);
|
free(coords);
|
||||||
PyErr_SetString(PyExc_TypeError, "list must contain numbers");
|
PyErr_SetString(PyExc_TypeError, "list must contain numbers");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
Py_DECREF(item);
|
||||||
coords[i] = coord * 65536;
|
coords[i] = coord * 65536;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1393,7 +1451,8 @@ static PyMethodDef font_methods[] = {
|
||||||
{"setvarname", (PyCFunction)font_setvarname, METH_VARARGS},
|
{"setvarname", (PyCFunction)font_setvarname, METH_VARARGS},
|
||||||
{"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS},
|
{"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS},
|
||||||
#endif
|
#endif
|
||||||
{NULL, NULL}};
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
font_getattr_family(FontObject *self, void *closure) {
|
font_getattr_family(FontObject *self, void *closure) {
|
||||||
|
@ -1450,7 +1509,8 @@ static struct PyGetSetDef font_getsetters[] = {
|
||||||
{"x_ppem", (getter)font_getattr_x_ppem},
|
{"x_ppem", (getter)font_getattr_x_ppem},
|
||||||
{"y_ppem", (getter)font_getattr_y_ppem},
|
{"y_ppem", (getter)font_getattr_y_ppem},
|
||||||
{"glyphs", (getter)font_getattr_glyphs},
|
{"glyphs", (getter)font_getattr_glyphs},
|
||||||
{NULL}};
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
static PyTypeObject Font_Type = {
|
static PyTypeObject Font_Type = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0) "Font", /*tp_name*/
|
PyVarObject_HEAD_INIT(NULL, 0) "Font", /*tp_name*/
|
||||||
|
@ -1486,7 +1546,8 @@ static PyTypeObject Font_Type = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static PyMethodDef _functions[] = {
|
static PyMethodDef _functions[] = {
|
||||||
{"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}};
|
{"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
static int
|
static int
|
||||||
setup_module(PyObject *m) {
|
setup_module(PyObject *m) {
|
||||||
|
@ -1576,5 +1637,9 @@ PyInit__imagingft(void) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||||
|
#endif
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
|
@ -209,7 +209,8 @@ _binop(PyObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyMethodDef _functions[] = {
|
static PyMethodDef _functions[] = {
|
||||||
{"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL}};
|
{"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
install(PyObject *d, char *name, void *value) {
|
install(PyObject *d, char *name, void *value) {
|
||||||
|
@ -290,5 +291,9 @@ PyInit__imagingmath(void) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||||
|
#endif
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
|
@ -253,7 +253,8 @@ static PyMethodDef functions[] = {
|
||||||
{"apply", (PyCFunction)apply, METH_VARARGS, NULL},
|
{"apply", (PyCFunction)apply, METH_VARARGS, NULL},
|
||||||
{"get_on_pixels", (PyCFunction)get_on_pixels, METH_VARARGS, NULL},
|
{"get_on_pixels", (PyCFunction)get_on_pixels, METH_VARARGS, NULL},
|
||||||
{"match", (PyCFunction)match, METH_VARARGS, NULL},
|
{"match", (PyCFunction)match, METH_VARARGS, NULL},
|
||||||
{NULL, NULL, 0, NULL}};
|
{NULL, NULL, 0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
PyMODINIT_FUNC
|
||||||
PyInit__imagingmorph(void) {
|
PyInit__imagingmorph(void) {
|
||||||
|
@ -269,5 +270,9 @@ PyInit__imagingmorph(void) {
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
m = PyModule_Create(&module_def);
|
||||||
|
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||||
|
#endif
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,5 +62,10 @@ PyInit__imagingtk(void) {
|
||||||
Py_DECREF(m);
|
Py_DECREF(m);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||||
|
#endif
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
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_INVALID_ARGUMENT",
|
||||||
"WEBP_MUX_BAD_DATA",
|
"WEBP_MUX_BAD_DATA",
|
||||||
"WEBP_MUX_MEMORY_ERROR",
|
"WEBP_MUX_MEMORY_ERROR",
|
||||||
"WEBP_MUX_NOT_ENOUGH_DATA"};
|
"WEBP_MUX_NOT_ENOUGH_DATA"
|
||||||
|
};
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
HandleMuxError(WebPMuxError err, char *chunk) {
|
HandleMuxError(WebPMuxError err, char *chunk) {
|
||||||
|
@ -61,7 +62,8 @@ HandleMuxError(WebPMuxError err, char *chunk) {
|
||||||
sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]);
|
sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]);
|
||||||
} else {
|
} else {
|
||||||
message_len = sprintf(
|
message_len = sprintf(
|
||||||
message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]);
|
message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (message_len < 0) {
|
if (message_len < 0) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "failed to construct error message");
|
PyErr_SetString(PyExc_RuntimeError, "failed to construct error message");
|
||||||
|
@ -138,7 +140,8 @@ _anim_encoder_new(PyObject *self, PyObject *args) {
|
||||||
&kmin,
|
&kmin,
|
||||||
&kmax,
|
&kmax,
|
||||||
&allow_mixed,
|
&allow_mixed,
|
||||||
&verbose)) {
|
&verbose
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +217,8 @@ _anim_encoder_add(PyObject *self, PyObject *args) {
|
||||||
&lossless,
|
&lossless,
|
||||||
&quality_factor,
|
&quality_factor,
|
||||||
&alpha_quality_factor,
|
&alpha_quality_factor,
|
||||||
&method)) {
|
&method
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,7 +287,8 @@ _anim_encoder_assemble(PyObject *self, PyObject *args) {
|
||||||
&exif_bytes,
|
&exif_bytes,
|
||||||
&exif_size,
|
&exif_size,
|
||||||
&xmp_bytes,
|
&xmp_bytes,
|
||||||
&xmp_size)) {
|
&xmp_size
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,7 +426,8 @@ _anim_decoder_get_info(PyObject *self) {
|
||||||
info->loop_count,
|
info->loop_count,
|
||||||
info->bgcolor,
|
info->bgcolor,
|
||||||
info->frame_count,
|
info->frame_count,
|
||||||
decp->mode);
|
decp->mode
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
|
@ -466,7 +472,8 @@ _anim_decoder_get_next(PyObject *self) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes = PyBytes_FromStringAndSize(
|
bytes = PyBytes_FromStringAndSize(
|
||||||
(char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height);
|
(char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height
|
||||||
|
);
|
||||||
|
|
||||||
ret = Py_BuildValue("Si", bytes, timestamp);
|
ret = Py_BuildValue("Si", bytes, timestamp);
|
||||||
|
|
||||||
|
@ -621,7 +628,8 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
||||||
&exif_bytes,
|
&exif_bytes,
|
||||||
&exif_size,
|
&exif_size,
|
||||||
&xmp_bytes,
|
&xmp_bytes,
|
||||||
&xmp_size)) {
|
&xmp_size
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -828,12 +836,14 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) {
|
if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) {
|
||||||
icc_profile = PyBytes_FromStringAndSize(
|
icc_profile = PyBytes_FromStringAndSize(
|
||||||
(const char *)icc_profile_data.bytes, icc_profile_data.size);
|
(const char *)icc_profile_data.bytes, icc_profile_data.size
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data)) {
|
if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data)) {
|
||||||
exif = PyBytes_FromStringAndSize(
|
exif = PyBytes_FromStringAndSize(
|
||||||
(const char *)exif_data.bytes, exif_data.size);
|
(const char *)exif_data.bytes, exif_data.size
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebPDataClear(&image.bitstream);
|
WebPDataClear(&image.bitstream);
|
||||||
|
@ -848,12 +858,14 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (config.output.colorspace < MODE_YUV) {
|
if (config.output.colorspace < MODE_YUV) {
|
||||||
bytes = PyBytes_FromStringAndSize(
|
bytes = PyBytes_FromStringAndSize(
|
||||||
(char *)config.output.u.RGBA.rgba, config.output.u.RGBA.size);
|
(char *)config.output.u.RGBA.rgba, config.output.u.RGBA.size
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Skipping YUV for now. Need Test Images.
|
// Skipping YUV for now. Need Test Images.
|
||||||
// UNDONE -- unclear if we'll ever get here if we set mode_rgb*
|
// UNDONE -- unclear if we'll ever get here if we set mode_rgb*
|
||||||
bytes = PyBytes_FromStringAndSize(
|
bytes = PyBytes_FromStringAndSize(
|
||||||
(char *)config.output.u.YUVA.y, config.output.u.YUVA.y_size);
|
(char *)config.output.u.YUVA.y, config.output.u.YUVA.y_size
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pymode = PyUnicode_FromString(mode);
|
pymode = PyUnicode_FromString(mode);
|
||||||
|
@ -864,7 +876,8 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) {
|
||||||
config.output.height,
|
config.output.height,
|
||||||
pymode,
|
pymode,
|
||||||
NULL == icc_profile ? Py_None : icc_profile,
|
NULL == icc_profile ? Py_None : icc_profile,
|
||||||
NULL == exif ? Py_None : exif);
|
NULL == exif ? Py_None : exif
|
||||||
|
);
|
||||||
|
|
||||||
end:
|
end:
|
||||||
WebPFreeDecBuffer(&config.output);
|
WebPFreeDecBuffer(&config.output);
|
||||||
|
@ -898,7 +911,8 @@ WebPDecoderVersion_str(void) {
|
||||||
"%d.%d.%d",
|
"%d.%d.%d",
|
||||||
version_number >> 16,
|
version_number >> 16,
|
||||||
(version_number >> 8) % 0x100,
|
(version_number >> 8) % 0x100,
|
||||||
version_number % 0x100);
|
version_number % 0x100
|
||||||
|
);
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -932,7 +946,8 @@ static PyMethodDef webpMethods[] = {
|
||||||
WebPDecoderBuggyAlpha_wrapper,
|
WebPDecoderBuggyAlpha_wrapper,
|
||||||
METH_NOARGS,
|
METH_NOARGS,
|
||||||
"WebPDecoderBuggyAlpha"},
|
"WebPDecoderBuggyAlpha"},
|
||||||
{NULL, NULL}};
|
{NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
addMuxFlagToModule(PyObject *m) {
|
addMuxFlagToModule(PyObject *m) {
|
||||||
|
@ -1005,5 +1020,9 @@ PyInit__webp(void) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
||||||
|
#endif
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,8 @@
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD int (*decode)(
|
PyObject_HEAD int (*decode)(
|
||||||
Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes);
|
Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes
|
||||||
|
);
|
||||||
int (*cleanup)(ImagingCodecState state);
|
int (*cleanup)(ImagingCodecState state);
|
||||||
struct ImagingCodecStateInstance state;
|
struct ImagingCodecStateInstance state;
|
||||||
Imaging im;
|
Imaging im;
|
||||||
|
@ -889,7 +890,8 @@ PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) {
|
||||||
PY_LONG_LONG length = -1;
|
PY_LONG_LONG length = -1;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length)) {
|
args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,8 @@ _draw(ImagingDisplayObject *display, PyObject *args) {
|
||||||
src + 0,
|
src + 0,
|
||||||
src + 1,
|
src + 1,
|
||||||
src + 2,
|
src + 2,
|
||||||
src + 3)) {
|
src + 3
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +222,8 @@ _tobytes(ImagingDisplayObject *display, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return PyBytes_FromStringAndSize(
|
return PyBytes_FromStringAndSize(
|
||||||
display->dib->bits, display->dib->ysize * display->dib->linesize);
|
display->dib->bits, display->dib->ysize * display->dib->linesize
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct PyMethodDef methods[] = {
|
static struct PyMethodDef methods[] = {
|
||||||
|
@ -247,7 +249,8 @@ _getattr_size(ImagingDisplayObject *self, void *closure) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct PyGetSetDef getsetters[] = {
|
static struct PyGetSetDef getsetters[] = {
|
||||||
{"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {NULL}};
|
{"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {NULL}
|
||||||
|
};
|
||||||
|
|
||||||
static PyTypeObject ImagingDisplayType = {
|
static PyTypeObject ImagingDisplayType = {
|
||||||
PyVarObject_HEAD_INIT(NULL, 0) "ImagingDisplay", /*tp_name*/
|
PyVarObject_HEAD_INIT(NULL, 0) "ImagingDisplay", /*tp_name*/
|
||||||
|
@ -341,9 +344,8 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
|
||||||
// added in Windows 10 (1607)
|
// added in Windows 10 (1607)
|
||||||
// loaded dynamically to avoid link errors
|
// loaded dynamically to avoid link errors
|
||||||
user32 = LoadLibraryA("User32.dll");
|
user32 = LoadLibraryA("User32.dll");
|
||||||
SetThreadDpiAwarenessContext_function =
|
SetThreadDpiAwarenessContext_function = (Func_SetThreadDpiAwarenessContext
|
||||||
(Func_SetThreadDpiAwarenessContext)GetProcAddress(
|
)GetProcAddress(user32, "SetThreadDpiAwarenessContext");
|
||||||
user32, "SetThreadDpiAwarenessContext");
|
|
||||||
if (SetThreadDpiAwarenessContext_function != NULL) {
|
if (SetThreadDpiAwarenessContext_function != NULL) {
|
||||||
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((DPI_CONTEXT_HANDLE)-3)
|
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((DPI_CONTEXT_HANDLE)-3)
|
||||||
dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE)-3);
|
dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE)-3);
|
||||||
|
@ -403,7 +405,8 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
|
||||||
height,
|
height,
|
||||||
PyBytes_AS_STRING(buffer),
|
PyBytes_AS_STRING(buffer),
|
||||||
(BITMAPINFO *)&core,
|
(BITMAPINFO *)&core,
|
||||||
DIB_RGB_COLORS)) {
|
DIB_RGB_COLORS
|
||||||
|
)) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,7 +550,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||||
ps.rcPaint.left,
|
ps.rcPaint.left,
|
||||||
ps.rcPaint.top,
|
ps.rcPaint.top,
|
||||||
ps.rcPaint.right,
|
ps.rcPaint.right,
|
||||||
ps.rcPaint.bottom);
|
ps.rcPaint.bottom
|
||||||
|
);
|
||||||
if (result) {
|
if (result) {
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
} else {
|
} else {
|
||||||
|
@ -562,7 +566,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
rect.right - rect.left,
|
rect.right - rect.left,
|
||||||
rect.bottom - rect.top);
|
rect.bottom - rect.top
|
||||||
|
);
|
||||||
if (result) {
|
if (result) {
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
} else {
|
} else {
|
||||||
|
@ -577,7 +582,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
rect.right - rect.left,
|
rect.right - rect.left,
|
||||||
rect.bottom - rect.top);
|
rect.bottom - rect.top
|
||||||
|
);
|
||||||
if (result) {
|
if (result) {
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
} else {
|
} else {
|
||||||
|
@ -591,7 +597,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||||
case WM_SIZE:
|
case WM_SIZE:
|
||||||
/* resize window */
|
/* resize window */
|
||||||
result = PyObject_CallFunction(
|
result = PyObject_CallFunction(
|
||||||
callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam));
|
callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam)
|
||||||
|
);
|
||||||
if (result) {
|
if (result) {
|
||||||
InvalidateRect(wnd, NULL, 1);
|
InvalidateRect(wnd, NULL, 1);
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
|
@ -670,7 +677,8 @@ PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
|
||||||
HWND_DESKTOP,
|
HWND_DESKTOP,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NULL);
|
NULL
|
||||||
|
);
|
||||||
|
|
||||||
if (!wnd) {
|
if (!wnd) {
|
||||||
PyErr_SetString(PyExc_OSError, "failed to create window");
|
PyErr_SetString(PyExc_OSError, "failed to create window");
|
||||||
|
@ -732,7 +740,8 @@ PyImaging_DrawWmf(PyObject *self, PyObject *args) {
|
||||||
&x0,
|
&x0,
|
||||||
&x1,
|
&x1,
|
||||||
&y0,
|
&y0,
|
||||||
&y1)) {
|
&y1
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -844,7 +853,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
|
||||||
PyErr_Format(
|
PyErr_Format(
|
||||||
PyExc_OSError,
|
PyExc_OSError,
|
||||||
"X connection failed: error %i",
|
"X connection failed: error %i",
|
||||||
xcb_connection_has_error(connection));
|
xcb_connection_has_error(connection)
|
||||||
|
);
|
||||||
xcb_disconnect(connection);
|
xcb_disconnect(connection);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -878,8 +888,10 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
|
||||||
0,
|
0,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
0x00ffffff),
|
0x00ffffff
|
||||||
&error);
|
),
|
||||||
|
&error
|
||||||
|
);
|
||||||
if (reply == NULL) {
|
if (reply == NULL) {
|
||||||
PyErr_Format(
|
PyErr_Format(
|
||||||
PyExc_OSError,
|
PyExc_OSError,
|
||||||
|
@ -887,7 +899,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
|
||||||
error->error_code,
|
error->error_code,
|
||||||
error->major_code,
|
error->major_code,
|
||||||
error->minor_code,
|
error->minor_code,
|
||||||
error->resource_id);
|
error->resource_id
|
||||||
|
);
|
||||||
free(error);
|
free(error);
|
||||||
xcb_disconnect(connection);
|
xcb_disconnect(connection);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -897,7 +910,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
if (reply->depth == 24) {
|
if (reply->depth == 24) {
|
||||||
buffer = PyBytes_FromStringAndSize(
|
buffer = PyBytes_FromStringAndSize(
|
||||||
(char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply));
|
(char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
PyErr_Format(PyExc_OSError, "unsupported bit depth: %i", reply->depth);
|
PyErr_Format(PyExc_OSError, "unsupported bit depth: %i", reply->depth);
|
||||||
}
|
}
|
||||||
|
|
178
src/encode.c
|
@ -25,6 +25,7 @@
|
||||||
#define PY_SSIZE_T_CLEAN
|
#define PY_SSIZE_T_CLEAN
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
|
|
||||||
|
#include "thirdparty/pythoncapi_compat.h"
|
||||||
#include "libImaging/Imaging.h"
|
#include "libImaging/Imaging.h"
|
||||||
#include "libImaging/Gif.h"
|
#include "libImaging/Gif.h"
|
||||||
|
|
||||||
|
@ -38,7 +39,8 @@
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD int (*encode)(
|
PyObject_HEAD int (*encode)(
|
||||||
Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes);
|
Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes
|
||||||
|
);
|
||||||
int (*cleanup)(ImagingCodecState state);
|
int (*cleanup)(ImagingCodecState state);
|
||||||
struct ImagingCodecStateInstance state;
|
struct ImagingCodecStateInstance state;
|
||||||
Imaging im;
|
Imaging im;
|
||||||
|
@ -134,7 +136,8 @@ _encode(ImagingEncoderObject *encoder, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
status = encoder->encode(
|
status = encoder->encode(
|
||||||
encoder->im, &encoder->state, (UINT8 *)PyBytes_AsString(buf), bufsize);
|
encoder->im, &encoder->state, (UINT8 *)PyBytes_AsString(buf), bufsize
|
||||||
|
);
|
||||||
|
|
||||||
/* adjust string length to avoid slicing in encoder */
|
/* adjust string length to avoid slicing in encoder */
|
||||||
if (_PyBytes_Resize(&buf, (status > 0) ? status : 0) < 0) {
|
if (_PyBytes_Resize(&buf, (status > 0) ? status : 0) < 0) {
|
||||||
|
@ -571,7 +574,8 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) {
|
||||||
&compress_level,
|
&compress_level,
|
||||||
&compress_type,
|
&compress_type,
|
||||||
&dictionary,
|
&dictionary,
|
||||||
&dictionary_size)) {
|
&dictionary_size
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -652,15 +656,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
PyObject *item;
|
PyObject *item;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(
|
if (!PyArg_ParseTuple(
|
||||||
args,
|
args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types
|
||||||
"sssnsOO",
|
)) {
|
||||||
&mode,
|
|
||||||
&rawmode,
|
|
||||||
&compname,
|
|
||||||
&fp,
|
|
||||||
&filename,
|
|
||||||
&tags,
|
|
||||||
&types)) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -671,11 +668,17 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
tags_size = PyList_Size(tags);
|
tags_size = PyList_Size(tags);
|
||||||
TRACE(("tags size: %d\n", (int)tags_size));
|
TRACE(("tags size: %d\n", (int)tags_size));
|
||||||
for (pos = 0; pos < tags_size; pos++) {
|
for (pos = 0; pos < tags_size; pos++) {
|
||||||
item = PyList_GetItem(tags, pos);
|
item = PyList_GetItemRef(tags, pos);
|
||||||
|
if (item == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (!PyTuple_Check(item) || PyTuple_Size(item) != 2) {
|
if (!PyTuple_Check(item) || PyTuple_Size(item) != 2) {
|
||||||
|
Py_DECREF(item);
|
||||||
PyErr_SetString(PyExc_ValueError, "Invalid tags list");
|
PyErr_SetString(PyExc_ValueError, "Invalid tags list");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
Py_DECREF(item);
|
||||||
}
|
}
|
||||||
pos = 0;
|
pos = 0;
|
||||||
}
|
}
|
||||||
|
@ -703,11 +706,17 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
num_core_tags = sizeof(core_tags) / sizeof(int);
|
num_core_tags = sizeof(core_tags) / sizeof(int);
|
||||||
for (pos = 0; pos < tags_size; pos++) {
|
for (pos = 0; pos < tags_size; pos++) {
|
||||||
item = PyList_GetItem(tags, pos);
|
item = PyList_GetItemRef(tags, pos);
|
||||||
|
if (item == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
// We already checked that tags is a 2-tuple list.
|
// We already checked that tags is a 2-tuple list.
|
||||||
key = PyTuple_GetItem(item, 0);
|
key = PyTuple_GET_ITEM(item, 0);
|
||||||
key_int = (int)PyLong_AsLong(key);
|
key_int = (int)PyLong_AsLong(key);
|
||||||
value = PyTuple_GetItem(item, 1);
|
value = PyTuple_GET_ITEM(item, 1);
|
||||||
|
Py_DECREF(item);
|
||||||
|
|
||||||
status = 0;
|
status = 0;
|
||||||
is_core_tag = 0;
|
is_core_tag = 0;
|
||||||
is_var_length = 0;
|
is_var_length = 0;
|
||||||
|
@ -721,7 +730,10 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_core_tag) {
|
if (!is_core_tag) {
|
||||||
PyObject *tag_type = PyDict_GetItem(types, key);
|
PyObject *tag_type;
|
||||||
|
if (PyDict_GetItemRef(types, key, &tag_type) < 0) {
|
||||||
|
return NULL; // Exception has been already set
|
||||||
|
}
|
||||||
if (tag_type) {
|
if (tag_type) {
|
||||||
int type_int = PyLong_AsLong(tag_type);
|
int type_int = PyLong_AsLong(tag_type);
|
||||||
if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) {
|
if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) {
|
||||||
|
@ -769,7 +781,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
is_var_length = 1;
|
is_var_length = 1;
|
||||||
}
|
}
|
||||||
if (ImagingLibTiffMergeFieldInfo(
|
if (ImagingLibTiffMergeFieldInfo(
|
||||||
&encoder->state, type, key_int, is_var_length)) {
|
&encoder->state, type, key_int, is_var_length
|
||||||
|
)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -779,7 +792,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
&encoder->state,
|
&encoder->state,
|
||||||
(ttag_t)key_int,
|
(ttag_t)key_int,
|
||||||
PyBytes_Size(value),
|
PyBytes_Size(value),
|
||||||
PyBytes_AsString(value));
|
PyBytes_AsString(value)
|
||||||
|
);
|
||||||
} else if (is_var_length) {
|
} else if (is_var_length) {
|
||||||
Py_ssize_t len, i;
|
Py_ssize_t len, i;
|
||||||
TRACE(("Setting from Tuple: %d \n", key_int));
|
TRACE(("Setting from Tuple: %d \n", key_int));
|
||||||
|
@ -789,7 +803,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
int stride = 256;
|
int stride = 256;
|
||||||
if (len != 768) {
|
if (len != 768) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError, "Requiring 768 items for Colormap");
|
PyExc_ValueError, "Requiring 768 items for Colormap"
|
||||||
|
);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
UINT16 *av;
|
UINT16 *av;
|
||||||
|
@ -804,7 +819,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
(ttag_t)key_int,
|
(ttag_t)key_int,
|
||||||
av,
|
av,
|
||||||
av + stride,
|
av + stride,
|
||||||
av + stride * 2);
|
av + stride * 2
|
||||||
|
);
|
||||||
free(av);
|
free(av);
|
||||||
}
|
}
|
||||||
} else if (key_int == TIFFTAG_YCBCRSUBSAMPLING) {
|
} else if (key_int == TIFFTAG_YCBCRSUBSAMPLING) {
|
||||||
|
@ -812,7 +828,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
&encoder->state,
|
&encoder->state,
|
||||||
(ttag_t)key_int,
|
(ttag_t)key_int,
|
||||||
(UINT16)PyLong_AsLong(PyTuple_GetItem(value, 0)),
|
(UINT16)PyLong_AsLong(PyTuple_GetItem(value, 0)),
|
||||||
(UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1)));
|
(UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1))
|
||||||
|
);
|
||||||
} else if (type == TIFF_SHORT) {
|
} else if (type == TIFF_SHORT) {
|
||||||
UINT16 *av;
|
UINT16 *av;
|
||||||
/* malloc check ok, calloc checks for overflow */
|
/* malloc check ok, calloc checks for overflow */
|
||||||
|
@ -822,7 +839,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
av[i] = (UINT16)PyLong_AsLong(PyTuple_GetItem(value, i));
|
av[i] = (UINT16)PyLong_AsLong(PyTuple_GetItem(value, i));
|
||||||
}
|
}
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, len, av);
|
&encoder->state, (ttag_t)key_int, len, av
|
||||||
|
);
|
||||||
free(av);
|
free(av);
|
||||||
}
|
}
|
||||||
} else if (type == TIFF_LONG) {
|
} else if (type == TIFF_LONG) {
|
||||||
|
@ -834,7 +852,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
av[i] = (UINT32)PyLong_AsLong(PyTuple_GetItem(value, i));
|
av[i] = (UINT32)PyLong_AsLong(PyTuple_GetItem(value, i));
|
||||||
}
|
}
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, len, av);
|
&encoder->state, (ttag_t)key_int, len, av
|
||||||
|
);
|
||||||
free(av);
|
free(av);
|
||||||
}
|
}
|
||||||
} else if (type == TIFF_SBYTE) {
|
} else if (type == TIFF_SBYTE) {
|
||||||
|
@ -846,7 +865,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
av[i] = (INT8)PyLong_AsLong(PyTuple_GetItem(value, i));
|
av[i] = (INT8)PyLong_AsLong(PyTuple_GetItem(value, i));
|
||||||
}
|
}
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, len, av);
|
&encoder->state, (ttag_t)key_int, len, av
|
||||||
|
);
|
||||||
free(av);
|
free(av);
|
||||||
}
|
}
|
||||||
} else if (type == TIFF_SSHORT) {
|
} else if (type == TIFF_SSHORT) {
|
||||||
|
@ -858,7 +878,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
av[i] = (INT16)PyLong_AsLong(PyTuple_GetItem(value, i));
|
av[i] = (INT16)PyLong_AsLong(PyTuple_GetItem(value, i));
|
||||||
}
|
}
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, len, av);
|
&encoder->state, (ttag_t)key_int, len, av
|
||||||
|
);
|
||||||
free(av);
|
free(av);
|
||||||
}
|
}
|
||||||
} else if (type == TIFF_SLONG) {
|
} else if (type == TIFF_SLONG) {
|
||||||
|
@ -870,7 +891,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
av[i] = (INT32)PyLong_AsLong(PyTuple_GetItem(value, i));
|
av[i] = (INT32)PyLong_AsLong(PyTuple_GetItem(value, i));
|
||||||
}
|
}
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, len, av);
|
&encoder->state, (ttag_t)key_int, len, av
|
||||||
|
);
|
||||||
free(av);
|
free(av);
|
||||||
}
|
}
|
||||||
} else if (type == TIFF_FLOAT) {
|
} else if (type == TIFF_FLOAT) {
|
||||||
|
@ -882,7 +904,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i));
|
av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i));
|
||||||
}
|
}
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, len, av);
|
&encoder->state, (ttag_t)key_int, len, av
|
||||||
|
);
|
||||||
free(av);
|
free(av);
|
||||||
}
|
}
|
||||||
} else if (type == TIFF_DOUBLE) {
|
} else if (type == TIFF_DOUBLE) {
|
||||||
|
@ -894,43 +917,54 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
av[i] = PyFloat_AsDouble(PyTuple_GetItem(value, i));
|
av[i] = PyFloat_AsDouble(PyTuple_GetItem(value, i));
|
||||||
}
|
}
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, len, av);
|
&encoder->state, (ttag_t)key_int, len, av
|
||||||
|
);
|
||||||
free(av);
|
free(av);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (type == TIFF_SHORT) {
|
if (type == TIFF_SHORT) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value));
|
&encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value)
|
||||||
|
);
|
||||||
} else if (type == TIFF_LONG) {
|
} else if (type == TIFF_LONG) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value));
|
&encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value)
|
||||||
|
);
|
||||||
} else if (type == TIFF_SSHORT) {
|
} else if (type == TIFF_SSHORT) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, (INT16)PyLong_AsLong(value));
|
&encoder->state, (ttag_t)key_int, (INT16)PyLong_AsLong(value)
|
||||||
|
);
|
||||||
} else if (type == TIFF_SLONG) {
|
} else if (type == TIFF_SLONG) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, (INT32)PyLong_AsLong(value));
|
&encoder->state, (ttag_t)key_int, (INT32)PyLong_AsLong(value)
|
||||||
|
);
|
||||||
} else if (type == TIFF_FLOAT) {
|
} else if (type == TIFF_FLOAT) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value));
|
&encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value)
|
||||||
|
);
|
||||||
} else if (type == TIFF_DOUBLE) {
|
} else if (type == TIFF_DOUBLE) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value));
|
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
|
||||||
|
);
|
||||||
} else if (type == TIFF_SBYTE) {
|
} else if (type == TIFF_SBYTE) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value));
|
&encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value)
|
||||||
|
);
|
||||||
} else if (type == TIFF_ASCII) {
|
} else if (type == TIFF_ASCII) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, PyBytes_AsString(value));
|
&encoder->state, (ttag_t)key_int, PyBytes_AsString(value)
|
||||||
|
);
|
||||||
} else if (type == TIFF_RATIONAL) {
|
} else if (type == TIFF_RATIONAL) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value));
|
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
TRACE(
|
TRACE(
|
||||||
("Unhandled type for key %d : %s \n",
|
("Unhandled type for key %d : %s \n",
|
||||||
key_int,
|
key_int,
|
||||||
PyBytes_AsString(PyObject_Str(value))));
|
PyBytes_AsString(PyObject_Str(value)))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!status) {
|
if (!status) {
|
||||||
|
@ -991,7 +1025,8 @@ get_qtables_arrays(PyObject *qtables, int *qtablesLen) {
|
||||||
if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) {
|
if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError,
|
PyExc_ValueError,
|
||||||
"Not a valid number of quantization tables. Should be between 1 and 4.");
|
"Not a valid number of quantization tables. Should be between 1 and 4."
|
||||||
|
);
|
||||||
Py_DECREF(tables);
|
Py_DECREF(tables);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -1080,7 +1115,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
&extra,
|
&extra,
|
||||||
&extra_size,
|
&extra_size,
|
||||||
&rawExif,
|
&rawExif,
|
||||||
&rawExifLen)) {
|
&rawExifLen
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1150,29 +1186,27 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
encoder->encode = ImagingJpegEncode;
|
encoder->encode = ImagingJpegEncode;
|
||||||
|
|
||||||
strncpy(((JPEGENCODERSTATE *)encoder->state.context)->rawmode, rawmode, 8);
|
JPEGENCODERSTATE *jpeg_encoder_state = (JPEGENCODERSTATE *)encoder->state.context;
|
||||||
|
strncpy(jpeg_encoder_state->rawmode, rawmode, 8);
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->keep_rgb = keep_rgb;
|
jpeg_encoder_state->keep_rgb = keep_rgb;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->quality = quality;
|
jpeg_encoder_state->quality = quality;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->qtables = qarrays;
|
jpeg_encoder_state->qtables = qarrays;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->qtablesLen = qtablesLen;
|
jpeg_encoder_state->qtablesLen = qtablesLen;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->subsampling = subsampling;
|
jpeg_encoder_state->subsampling = subsampling;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->progressive = progressive;
|
jpeg_encoder_state->progressive = progressive;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->smooth = smooth;
|
jpeg_encoder_state->smooth = smooth;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->optimize = optimize;
|
jpeg_encoder_state->optimize = optimize;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype;
|
jpeg_encoder_state->streamtype = streamtype;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi;
|
jpeg_encoder_state->xdpi = xdpi;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi;
|
jpeg_encoder_state->ydpi = ydpi;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_blocks =
|
jpeg_encoder_state->restart_marker_blocks = restart_marker_blocks;
|
||||||
restart_marker_blocks;
|
jpeg_encoder_state->restart_marker_rows = restart_marker_rows;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_rows =
|
jpeg_encoder_state->comment = comment;
|
||||||
restart_marker_rows;
|
jpeg_encoder_state->comment_size = comment_size;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->comment = comment;
|
jpeg_encoder_state->extra = extra;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size;
|
jpeg_encoder_state->extra_size = extra_size;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->extra = extra;
|
jpeg_encoder_state->rawExif = rawExif;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->extra_size = extra_size;
|
jpeg_encoder_state->rawExifLen = rawExifLen;
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->rawExif = rawExif;
|
|
||||||
((JPEGENCODERSTATE *)encoder->state.context)->rawExifLen = rawExifLen;
|
|
||||||
|
|
||||||
return (PyObject *)encoder;
|
return (PyObject *)encoder;
|
||||||
}
|
}
|
||||||
|
@ -1250,7 +1284,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
|
||||||
&fd,
|
&fd,
|
||||||
&comment,
|
&comment,
|
||||||
&comment_size,
|
&comment_size,
|
||||||
&plt)) {
|
&plt
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1307,7 +1342,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y);
|
j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y);
|
||||||
j2k_decode_coord_tuple(
|
j2k_decode_coord_tuple(
|
||||||
tile_offset, &context->tile_offset_x, &context->tile_offset_y);
|
tile_offset, &context->tile_offset_x, &context->tile_offset_y
|
||||||
|
);
|
||||||
j2k_decode_coord_tuple(tile_size, &context->tile_size_x, &context->tile_size_y);
|
j2k_decode_coord_tuple(tile_size, &context->tile_size_x, &context->tile_size_y);
|
||||||
|
|
||||||
/* Error on illegal tile offsets */
|
/* Error on illegal tile offsets */
|
||||||
|
@ -1317,7 +1353,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError,
|
PyExc_ValueError,
|
||||||
"JPEG 2000 tile offset too small; top left tile must "
|
"JPEG 2000 tile offset too small; top left tile must "
|
||||||
"intersect image area");
|
"intersect image area"
|
||||||
|
);
|
||||||
Py_DECREF(encoder);
|
Py_DECREF(encoder);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -1325,8 +1362,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
|
||||||
if (context->tile_offset_x > context->offset_x ||
|
if (context->tile_offset_x > context->offset_x ||
|
||||||
context->tile_offset_y > context->offset_y) {
|
context->tile_offset_y > context->offset_y) {
|
||||||
PyErr_SetString(
|
PyErr_SetString(
|
||||||
PyExc_ValueError,
|
PyExc_ValueError, "JPEG 2000 tile offset too large to cover image area"
|
||||||
"JPEG 2000 tile offset too large to cover image area");
|
);
|
||||||
Py_DECREF(encoder);
|
Py_DECREF(encoder);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -1360,7 +1397,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
|
||||||
|
|
||||||
j2k_decode_coord_tuple(cblk_size, &context->cblk_width, &context->cblk_height);
|
j2k_decode_coord_tuple(cblk_size, &context->cblk_width, &context->cblk_height);
|
||||||
j2k_decode_coord_tuple(
|
j2k_decode_coord_tuple(
|
||||||
precinct_size, &context->precinct_width, &context->precinct_height);
|
precinct_size, &context->precinct_width, &context->precinct_height
|
||||||
|
);
|
||||||
|
|
||||||
context->irreversible = PyObject_IsTrue(irreversible);
|
context->irreversible = PyObject_IsTrue(irreversible);
|
||||||
context->progression = prog_order;
|
context->progression = prog_order;
|
||||||
|
|
|
@ -36,7 +36,8 @@ add_item(const char *mode) {
|
||||||
"AccessInit: hash collision: %d for both %s and %s\n",
|
"AccessInit: hash collision: %d for both %s and %s\n",
|
||||||
i,
|
i,
|
||||||
mode,
|
mode,
|
||||||
access_table[i].mode);
|
access_table[i].mode
|
||||||
|
);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
access_table[i].mode = mode;
|
access_table[i].mode = mode;
|
||||||
|
|
|
@ -243,7 +243,8 @@ static const bc7_mode_info bc7_modes[] = {
|
||||||
{1, 0, 2, 1, 5, 6, 0, 0, 2, 3},
|
{1, 0, 2, 1, 5, 6, 0, 0, 2, 3},
|
||||||
{1, 0, 2, 0, 7, 8, 0, 0, 2, 2},
|
{1, 0, 2, 0, 7, 8, 0, 0, 2, 2},
|
||||||
{1, 0, 0, 0, 7, 7, 1, 0, 4, 0},
|
{1, 0, 0, 0, 7, 7, 1, 0, 4, 0},
|
||||||
{2, 6, 0, 0, 5, 5, 1, 0, 2, 0}};
|
{2, 6, 0, 0, 5, 5, 1, 0, 2, 0}
|
||||||
|
};
|
||||||
|
|
||||||
/* Subset indices:
|
/* Subset indices:
|
||||||
Table.P2, 1 bit per index */
|
Table.P2, 1 bit per index */
|
||||||
|
@ -254,7 +255,8 @@ static const UINT16 bc7_si2[] = {
|
||||||
0x718e, 0x399c, 0xaaaa, 0xf0f0, 0x5a5a, 0x33cc, 0x3c3c, 0x55aa, 0x9696, 0xa55a,
|
0x718e, 0x399c, 0xaaaa, 0xf0f0, 0x5a5a, 0x33cc, 0x3c3c, 0x55aa, 0x9696, 0xa55a,
|
||||||
0x73ce, 0x13c8, 0x324c, 0x3bdc, 0x6996, 0xc33c, 0x9966, 0x0660, 0x0272, 0x04e4,
|
0x73ce, 0x13c8, 0x324c, 0x3bdc, 0x6996, 0xc33c, 0x9966, 0x0660, 0x0272, 0x04e4,
|
||||||
0x4e40, 0x2720, 0xc936, 0x936c, 0x39c6, 0x639c, 0x9336, 0x9cc6, 0x817e, 0xe718,
|
0x4e40, 0x2720, 0xc936, 0x936c, 0x39c6, 0x639c, 0x9336, 0x9cc6, 0x817e, 0xe718,
|
||||||
0xccf0, 0x0fcc, 0x7744, 0xee22};
|
0xccf0, 0x0fcc, 0x7744, 0xee22
|
||||||
|
};
|
||||||
|
|
||||||
/* Table.P3, 2 bits per index */
|
/* Table.P3, 2 bits per index */
|
||||||
static const UINT32 bc7_si3[] = {
|
static const UINT32 bc7_si3[] = {
|
||||||
|
@ -267,20 +269,23 @@ static const UINT32 bc7_si3[] = {
|
||||||
0x66660000, 0xa5a0a5a0, 0x50a050a0, 0x69286928, 0x44aaaa44, 0x66666600, 0xaa444444,
|
0x66660000, 0xa5a0a5a0, 0x50a050a0, 0x69286928, 0x44aaaa44, 0x66666600, 0xaa444444,
|
||||||
0x54a854a8, 0x95809580, 0x96969600, 0xa85454a8, 0x80959580, 0xaa141414, 0x96960000,
|
0x54a854a8, 0x95809580, 0x96969600, 0xa85454a8, 0x80959580, 0xaa141414, 0x96960000,
|
||||||
0xaaaa1414, 0xa05050a0, 0xa0a5a5a0, 0x96000000, 0x40804080, 0xa9a8a9a8, 0xaaaaaa44,
|
0xaaaa1414, 0xa05050a0, 0xa0a5a5a0, 0x96000000, 0x40804080, 0xa9a8a9a8, 0xaaaaaa44,
|
||||||
0x2a4a5254};
|
0x2a4a5254
|
||||||
|
};
|
||||||
|
|
||||||
/* Anchor indices:
|
/* Anchor indices:
|
||||||
Table.A2 */
|
Table.A2 */
|
||||||
static const char bc7_ai0[] = {
|
static const char bc7_ai0[] = {15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||||
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 8, 2, 2, 8,
|
15, 15, 15, 15, 2, 8, 2, 2, 8, 8, 15, 2, 8,
|
||||||
8, 15, 2, 8, 2, 2, 8, 8, 2, 2, 15, 15, 6, 8, 2, 8, 15, 15, 2, 8, 2, 2,
|
2, 2, 8, 8, 2, 2, 15, 15, 6, 8, 2, 8, 15,
|
||||||
2, 15, 15, 6, 6, 2, 6, 8, 15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15};
|
15, 2, 8, 2, 2, 2, 15, 15, 6, 6, 2, 6, 8,
|
||||||
|
15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15};
|
||||||
|
|
||||||
/* Table.A3a */
|
/* Table.A3a */
|
||||||
static const char bc7_ai1[] = {
|
static const char bc7_ai1[] = {3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6,
|
||||||
3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8, 15, 3, 3,
|
5, 3, 3, 3, 3, 8, 15, 3, 3, 6, 10, 5, 8,
|
||||||
6, 10, 5, 8, 8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8, 15, 15, 3, 15, 5,
|
8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8,
|
||||||
15, 15, 15, 15, 3, 15, 5, 5, 5, 8, 5, 10, 5, 10, 8, 13, 15, 12, 3, 3};
|
15, 15, 3, 15, 5, 15, 15, 15, 15, 3, 15, 5, 5,
|
||||||
|
5, 8, 5, 10, 5, 10, 8, 13, 15, 12, 3, 3};
|
||||||
|
|
||||||
/* Table.A3b */
|
/* Table.A3b */
|
||||||
static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 15,
|
static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 15,
|
||||||
|
@ -293,7 +298,8 @@ static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 1
|
||||||
static const char bc7_weights2[] = {0, 21, 43, 64};
|
static const char bc7_weights2[] = {0, 21, 43, 64};
|
||||||
static const char bc7_weights3[] = {0, 9, 18, 27, 37, 46, 55, 64};
|
static const char bc7_weights3[] = {0, 9, 18, 27, 37, 46, 55, 64};
|
||||||
static const char bc7_weights4[] = {
|
static const char bc7_weights4[] = {
|
||||||
0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64};
|
0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64
|
||||||
|
};
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
bc7_get_weights(int n) {
|
bc7_get_weights(int n) {
|
||||||
|
@ -526,7 +532,8 @@ static const bc6_mode_info bc6_modes[] = {
|
||||||
{1, 0, 0, 10, 10, 10, 10},
|
{1, 0, 0, 10, 10, 10, 10},
|
||||||
{1, 1, 0, 11, 9, 9, 9},
|
{1, 1, 0, 11, 9, 9, 9},
|
||||||
{1, 1, 0, 12, 8, 8, 8},
|
{1, 1, 0, 12, 8, 8, 8},
|
||||||
{1, 1, 0, 16, 4, 4, 4}};
|
{1, 1, 0, 16, 4, 4, 4}
|
||||||
|
};
|
||||||
|
|
||||||
/* Table.F, encoded as a sequence of bit indices */
|
/* Table.F, encoded as a sequence of bit indices */
|
||||||
static const UINT8 bc6_bit_packings[][75] = {
|
static const UINT8 bc6_bit_packings[][75] = {
|
||||||
|
@ -591,7 +598,8 @@ static const UINT8 bc6_bit_packings[][75] = {
|
||||||
64, 65, 66, 67, 68, 69, 70, 71, 27, 26, 80, 81, 82, 83, 84, 85, 86, 87, 43, 42},
|
64, 65, 66, 67, 68, 69, 70, 71, 27, 26, 80, 81, 82, 83, 84, 85, 86, 87, 43, 42},
|
||||||
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
|
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
|
||||||
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 15, 14, 13, 12, 11, 10,
|
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 15, 14, 13, 12, 11, 10,
|
||||||
64, 65, 66, 67, 31, 30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, 42}};
|
64, 65, 66, 67, 31, 30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, 42}
|
||||||
|
};
|
||||||
|
|
||||||
static void
|
static void
|
||||||
bc6_sign_extend(UINT16 *v, int prec) {
|
bc6_sign_extend(UINT16 *v, int prec) {
|
||||||
|
@ -830,7 +838,8 @@ decode_bcn(
|
||||||
int bytes,
|
int bytes,
|
||||||
int N,
|
int N,
|
||||||
int C,
|
int C,
|
||||||
char *pixel_format) {
|
char *pixel_format
|
||||||
|
) {
|
||||||
int ymax = state->ysize + state->yoff;
|
int ymax = state->ysize + state->yoff;
|
||||||
const UINT8 *ptr = src;
|
const UINT8 *ptr = src;
|
||||||
switch (N) {
|
switch (N) {
|
||||||
|
|
|
@ -13,7 +13,8 @@ void static inline ImagingLineBoxBlur32(
|
||||||
int edgeA,
|
int edgeA,
|
||||||
int edgeB,
|
int edgeB,
|
||||||
UINT32 ww,
|
UINT32 ww,
|
||||||
UINT32 fw) {
|
UINT32 fw
|
||||||
|
) {
|
||||||
int x;
|
int x;
|
||||||
UINT32 acc[4];
|
UINT32 acc[4];
|
||||||
UINT32 bulk[4];
|
UINT32 bulk[4];
|
||||||
|
@ -109,7 +110,8 @@ void static inline ImagingLineBoxBlur8(
|
||||||
int edgeA,
|
int edgeA,
|
||||||
int edgeB,
|
int edgeB,
|
||||||
UINT32 ww,
|
UINT32 ww,
|
||||||
UINT32 fw) {
|
UINT32 fw
|
||||||
|
) {
|
||||||
int x;
|
int x;
|
||||||
UINT32 acc;
|
UINT32 acc;
|
||||||
UINT32 bulk;
|
UINT32 bulk;
|
||||||
|
@ -198,7 +200,8 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) {
|
||||||
edgeA,
|
edgeA,
|
||||||
edgeB,
|
edgeB,
|
||||||
ww,
|
ww,
|
||||||
fw);
|
fw
|
||||||
|
);
|
||||||
if (imIn == imOut) {
|
if (imIn == imOut) {
|
||||||
// Commit.
|
// Commit.
|
||||||
memcpy(imOut->image8[y], lineOut, imIn->xsize);
|
memcpy(imOut->image8[y], lineOut, imIn->xsize);
|
||||||
|
@ -214,7 +217,8 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) {
|
||||||
edgeA,
|
edgeA,
|
||||||
edgeB,
|
edgeB,
|
||||||
ww,
|
ww,
|
||||||
fw);
|
fw
|
||||||
|
);
|
||||||
if (imIn == imOut) {
|
if (imIn == imOut) {
|
||||||
// Commit.
|
// Commit.
|
||||||
memcpy(imOut->image32[y], lineOut, imIn->xsize * 4);
|
memcpy(imOut->image32[y], lineOut, imIn->xsize * 4);
|
||||||
|
@ -314,11 +318,13 @@ _gaussian_blur_radius(float radius, int passes) {
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
ImagingGaussianBlur(
|
ImagingGaussianBlur(
|
||||||
Imaging imOut, Imaging imIn, float xradius, float yradius, int passes) {
|
Imaging imOut, Imaging imIn, float xradius, float yradius, int passes
|
||||||
|
) {
|
||||||
return ImagingBoxBlur(
|
return ImagingBoxBlur(
|
||||||
imOut,
|
imOut,
|
||||||
imIn,
|
imIn,
|
||||||
_gaussian_blur_radius(xradius, passes),
|
_gaussian_blur_radius(xradius, passes),
|
||||||
_gaussian_blur_radius(yradius, passes),
|
_gaussian_blur_radius(yradius, passes),
|
||||||
passes);
|
passes
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,8 @@ ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) {
|
||||||
CHOP2(
|
CHOP2(
|
||||||
(((255 - in1[x]) * (in1[x] * in2[x])) / 65536) +
|
(((255 - in1[x]) * (in1[x] * in2[x])) / 65536) +
|
||||||
(in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255,
|
(in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255,
|
||||||
NULL);
|
NULL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
|
@ -150,7 +151,8 @@ ImagingChopHardLight(Imaging imIn1, Imaging imIn2) {
|
||||||
CHOP2(
|
CHOP2(
|
||||||
(in2[x] < 128) ? ((in1[x] * in2[x]) / 127)
|
(in2[x] < 128) ? ((in1[x] * in2[x]) / 127)
|
||||||
: 255 - (((255 - in2[x]) * (255 - in1[x])) / 127),
|
: 255 - (((255 - in2[x]) * (255 - in1[x])) / 127),
|
||||||
NULL);
|
NULL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
|
@ -158,5 +160,6 @@ ImagingOverlay(Imaging imIn1, Imaging imIn2) {
|
||||||
CHOP2(
|
CHOP2(
|
||||||
(in1[x] < 128) ? ((in1[x] * in2[x]) / 127)
|
(in1[x] < 128) ? ((in1[x] * in2[x]) / 127)
|
||||||
: 255 - (((255 - in1[x]) * (255 - in2[x])) / 127),
|
: 255 - (((255 - in1[x]) * (255 - in2[x])) / 127),
|
||||||
NULL);
|
NULL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|