Merge branch 'main' into mpo

This commit is contained in:
Andrew Murray 2025-06-28 01:20:15 +10:00 committed by GitHub
commit 646b4a4ecd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
117 changed files with 13458 additions and 579 deletions

View File

@ -66,7 +66,7 @@ if [[ $(uname) != CYGWIN* ]]; then
pushd depends && ./install_raqm.sh && popd pushd depends && ./install_raqm.sh && popd
# libavif # libavif
pushd depends && CMAKE_POLICY_VERSION_MINIMUM=3.5 ./install_libavif.sh && popd pushd depends && ./install_libavif.sh && popd
# extra test images # extra test images
pushd depends && ./install_extra_test_images.sh && popd pushd depends && ./install_extra_test_images.sh && popd

View File

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

View File

@ -1,9 +1,10 @@
mypy==1.15.0 mypy==1.16.1
IceSpringPySideStubs-PyQt6 IceSpringPySideStubs-PyQt6
IceSpringPySideStubs-PySide6 IceSpringPySideStubs-PySide6
ipython ipython
numpy numpy
packaging packaging
pyarrow-stubs
pytest pytest
sphinx sphinx
types-atheris types-atheris

View File

@ -0,0 +1,60 @@
name: Test Valgrind Memory Leaks
# like the Docker tests, but running valgrind only on *.c/*.h changes.
# this is very expensive. Only run on the pull request.
on:
# push:
# branches:
# - "**"
# paths:
# - ".github/workflows/test-valgrind.yml"
# - "**.c"
# - "**.h"
pull_request:
paths:
- ".github/workflows/test-valgrind.yml"
- "**.c"
- "**.h"
- "depends/docker-test-valgrind-memory.sh"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
docker: [
ubuntu-22.04-jammy-amd64-valgrind,
]
dockerTag: [main]
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Docker pull
run: |
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
- name: Build and Run Valgrind
run: |
# The Pillow user in the docker container is UID 1001
sudo chown -R 1001 $GITHUB_WORKSPACE
docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} /Pillow/depends/docker-test-valgrind-memory.sh
sudo chown -R runner $GITHUB_WORKSPACE

View File

@ -31,16 +31,15 @@ env:
jobs: jobs:
build: build:
runs-on: ${{ matrix.os }} runs-on: windows-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"] python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"]
architecture: ["x64"] architecture: ["x64"]
os: ["windows-latest"]
include: include:
# Test the oldest Python on 32-bit # Test the oldest Python on 32-bit
- { python-version: "3.9", architecture: "x86", os: "windows-2019" } - { python-version: "3.9", architecture: "x86" }
timeout-minutes: 45 timeout-minutes: 45

View File

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

View File

@ -39,8 +39,8 @@ ARCHIVE_SDIR=pillow-depends-main
# Package versions for fresh source builds # Package versions for fresh source builds
FREETYPE_VERSION=2.13.3 FREETYPE_VERSION=2.13.3
HARFBUZZ_VERSION=11.2.1 HARFBUZZ_VERSION=11.2.1
LIBPNG_VERSION=1.6.48 LIBPNG_VERSION=1.6.49
JPEGTURBO_VERSION=3.1.0 JPEGTURBO_VERSION=3.1.1
OPENJPEG_VERSION=2.5.3 OPENJPEG_VERSION=2.5.3
XZ_VERSION=5.8.1 XZ_VERSION=5.8.1
TIFF_VERSION=4.7.0 TIFF_VERSION=4.7.0
@ -51,6 +51,7 @@ LIBWEBP_VERSION=1.5.0
BZIP2_VERSION=1.0.8 BZIP2_VERSION=1.0.8
LIBXCB_VERSION=1.17.0 LIBXCB_VERSION=1.17.0
BROTLI_VERSION=1.1.0 BROTLI_VERSION=1.1.0
LIBAVIF_VERSION=1.3.0
function build_pkg_config { function build_pkg_config {
if [ -e pkg-config-stamp ]; then return; fi if [ -e pkg-config-stamp ]; then return; fi
@ -98,6 +99,59 @@ function build_harfbuzz {
touch harfbuzz-stamp touch harfbuzz-stamp
} }
function build_libavif {
if [ -e libavif-stamp ]; then return; fi
python3 -m pip install meson ninja
if [[ "$PLAT" == "x86_64" ]] || [ -n "$SANITIZER" ]; then
build_simple nasm 2.16.03 https://www.nasm.us/pub/nasm/releasebuilds/2.16.03
fi
local build_type=MinSizeRel
local lto=ON
local libavif_cmake_flags
if [ -n "$IS_MACOS" ]; then
lto=OFF
libavif_cmake_flags=(
-DCMAKE_C_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_CXX_FLAGS_MINSIZEREL="-Oz -DNDEBUG -flto" \
-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,-S,-x,-dead_strip_dylibs" \
)
else
if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "x86_64" ]]; then
build_type=Release
fi
libavif_cmake_flags=(-DCMAKE_SHARED_LINKER_FLAGS_INIT="-Wl,--strip-all,-z,relro,-z,now")
fi
local out_dir=$(fetch_unpack https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$LIBAVIF_VERSION.tar.gz libavif-$LIBAVIF_VERSION.tar.gz)
# CONFIG_AV1_HIGHBITDEPTH=0 is a flag for libaom (included as a subproject
# of libavif) that disables support for encoding high bit depth images.
(cd $out_dir \
&& cmake \
-DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX \
-DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib \
-DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib \
-DBUILD_SHARED_LIBS=ON \
-DAVIF_LIBSHARPYUV=LOCAL \
-DAVIF_LIBYUV=LOCAL \
-DAVIF_CODEC_AOM=LOCAL \
-DCONFIG_AV1_HIGHBITDEPTH=0 \
-DAVIF_CODEC_AOM_DECODE=OFF \
-DAVIF_CODEC_DAV1D=LOCAL \
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=$lto \
-DCMAKE_C_VISIBILITY_PRESET=hidden \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden \
-DCMAKE_BUILD_TYPE=$build_type \
"${libavif_cmake_flags[@]}" \
. \
&& make install)
touch libavif-stamp
}
function build { function build {
build_xz build_xz
if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then if [ -z "$IS_ALPINE" ] && [ -z "$SANITIZER" ] && [ -z "$IS_MACOS" ]; then
@ -132,6 +186,7 @@ function build {
build_tiff build_tiff
fi fi
build_libavif
build_libpng build_libpng
build_lcms2 build_lcms2
build_openjpeg build_openjpeg

View File

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

View File

@ -58,7 +58,7 @@ jobs:
- name: "macOS 10.13 x86_64" - name: "macOS 10.13 x86_64"
os: macos-13 os: macos-13
cibw_arch: x86_64 cibw_arch: x86_64
build: "cp3{12,13}*" build: "cp3{12,13,14}*"
macosx_deployment_target: "10.13" macosx_deployment_target: "10.13"
- name: "macOS 10.15 x86_64" - name: "macOS 10.15 x86_64"
os: macos-13 os: macos-13
@ -110,7 +110,6 @@ jobs:
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
CIBW_SKIP: pp39-*
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
@ -160,7 +159,7 @@ jobs:
# Install extra test images # Install extra test images
xcopy /S /Y Tests\test-images\* Tests\images xcopy /S /Y Tests\test-images\* Tests\images
& python.exe winbuild\build_prepare.py -v --no-imagequant --no-avif --architecture=${{ matrix.cibw_arch }} & python.exe winbuild\build_prepare.py -v --no-imagequant --architecture=${{ matrix.cibw_arch }}
shell: pwsh shell: pwsh
- name: Build wheels - name: Build wheels
@ -188,7 +187,6 @@ jobs:
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
CIBW_CACHE_PATH: "C:\\cibw" CIBW_CACHE_PATH: "C:\\cibw"
CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
CIBW_SKIP: pp39-*
CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_SKIP: "*-win_arm64"
CIBW_TEST_COMMAND: 'docker run --rm CIBW_TEST_COMMAND: 'docker run --rm
-v {project}:C:\pillow -v {project}:C:\pillow

View File

@ -1,8 +1,8 @@
repos: repos:
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.8 rev: v0.11.12
hooks: hooks:
- id: ruff - id: ruff-check
args: [--exit-non-zero-on-fix] args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror - repo: https://github.com/psf/black-pre-commit-mirror
@ -24,7 +24,7 @@ repos:
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/pre-commit/mirrors-clang-format
rev: v20.1.3 rev: v20.1.5
hooks: hooks:
- id: clang-format - id: clang-format
types: [c] types: [c]
@ -58,7 +58,7 @@ repos:
- id: check-renovate - id: check-renovate
- repo: https://github.com/woodruffw/zizmor-pre-commit - repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.6.0 rev: v1.9.0
hooks: hooks:
- id: zizmor - id: zizmor
@ -68,7 +68,7 @@ repos:
- id: sphinx-lint - id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt - repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.5.1 rev: v2.6.0
hooks: hooks:
- id: pyproject-fmt - id: pyproject-fmt

View File

@ -97,13 +97,27 @@ test:
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
python3 -m pytest -qq python3 -m pytest -qq
.PHONY: test-p
test-p:
python3 -c "import xdist" > /dev/null 2>&1 || python3 -m pip install pytest-xdist
python3 -m pytest -qq -n auto
.PHONY: valgrind .PHONY: valgrind
valgrind: valgrind:
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \ PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
--log-file=/tmp/valgrind-output \ --log-file=/tmp/valgrind-output \
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
.PHONY: valgrind-leak
valgrind-leak:
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp \
--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite \
--log-file=/tmp/valgrind-output \
python3 -m pytest -vv --valgrind --valgrind-log=/tmp/valgrind-output
.PHONY: readme .PHONY: readme
readme: readme:
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2 python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2

View File

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

View File

@ -161,6 +161,12 @@ def assert_tuple_approx_equal(
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets)) pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
def timeout_unless_slower_valgrind(timeout: float) -> pytest.MarkDecorator:
if "PILLOW_VALGRIND_TEST" in os.environ:
return pytest.mark.pil_noop_mark()
return pytest.mark.timeout(timeout)
def skip_unless_feature(feature: str) -> pytest.MarkDecorator: def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
reason = f"{feature} not available" reason = f"{feature} not available"
return pytest.mark.skipif(not features.check(feature), reason=reason) return pytest.mark.skipif(not features.check(feature), reason=reason)

View File

@ -0,0 +1,390 @@
/* XPM */
static const char *hopper[] = {
/* columns rows colors chars-per-pixel */
"128 128 256 2 ",
" c #0C0C0D",
". c #0A0708",
"X c #1C0A04",
"o c #120B0C",
"O c #170808",
"+ c #0B110D",
"@ c #16120C",
"# c #0D0D12",
"$ c #0D0D1A",
"% c #070A16",
"& c #120D13",
"* c #120E1A",
"= c #1A0C16",
"- c #0D1114",
"; c #0D121B",
": c #091518",
"> c #131215",
", c #14131B",
"< c #1A141C",
"1 c #1B191D",
"2 c #191517",
"3 c #250906",
"4 c #390904",
"5 c #27150A",
"6 c #250A18",
"7 c #251719",
"8 c #361410",
"9 c #342215",
"0 c #0C0C24",
"q c #0C0D2B",
"w c #060927",
"e c #130D24",
"r c #150D2A",
"t c #0C1225",
"y c #0C122C",
"u c #061227",
"i c #151422",
"p c #1A1522",
"a c #1C1B23",
"s c #13132C",
"d c #19172A",
"f c #0C0D35",
"g c #130E37",
"h c #0D1436",
"j c #131333",
"k c #13143C",
"l c #191838",
"z c #241926",
"x c #231B38",
"c c #2E1226",
"v c #372628",
"b c #292538",
"n c #362B37",
"m c #2F2A2F",
"M c #1A2233",
"N c #4C150D",
"B c #740F10",
"V c #512916",
"C c #793419",
"Z c #6D2C13",
"A c #4E1524",
"S c #741624",
"D c #4E332E",
"F c #6F3629",
"G c #574438",
"H c #744831",
"J c #775A2E",
"K c #0E1444",
"L c #141443",
"P c #1B1A44",
"I c #14144B",
"U c #1A1B4C",
"Y c #181747",
"T c #1B1B53",
"R c #181955",
"E c #0F0E44",
"W c #231C46",
"Q c #231C56",
"! c #1C234E",
"~ c #272547",
"^ c #2E2F52",
"/ c #2E3765",
"( c #483947",
") c #742D4A",
"_ c #364970",
"` c #534A51",
"' c #6E534D",
"] c #756654",
"[ c #53556D",
"{ c #6B5B69",
"} c #746B71",
"| c #5E616A",
" . c #880C15",
".. c #881217",
"X. c #8D0D0F",
"o. c #8B3218",
"O. c #8C3828",
"+. c #AC2F30",
"@. c #9A1825",
"#. c #CE202B",
"$. c #8A452A",
"%. c #974A2B",
"&. c #884934",
"*. c #954B35",
"=. c #995539",
"-. c #895736",
";. c #A75738",
":. c #A84E30",
">. c #996839",
",. c #B6683B",
"<. c #AE6835",
"1. c #A35419",
"2. c #D26D19",
"3. c #CC712E",
"4. c #CD6922",
"5. c #A83152",
"6. c #985845",
"7. c #8A5748",
"8. c #AE5A46",
"9. c #916A4F",
"0. c #A96647",
"q. c #B76947",
"w. c #BA744A",
"e. c #B97757",
"r. c #AB6F53",
"t. c #8D736D",
"y. c #B27669",
"u. c #91566F",
"i. c #C56B4A",
"p. c #C8764B",
"a. c #C87856",
"s. c #D47A59",
"d. c #C96E53",
"f. c #C77C64",
"g. c #D17969",
"h. c #D45D68",
"j. c #C52A46",
"k. c #D58932",
"l. c #B38355",
"z. c #968775",
"x. c #BA8667",
"c. c #B38C74",
"v. c #AB9C73",
"b. c #C9845A",
"n. c #D7855B",
"m. c #D39454",
"M. c #E28C5B",
"N. c #F7B251",
"B. c #C78867",
"V. c #D98866",
"C. c #D8956A",
"Z. c #C79878",
"A. c #D89876",
"S. c #CD8C70",
"D. c #E38A68",
"F. c #E5956A",
"G. c #E79776",
"H. c #ED9176",
"J. c #D6A371",
"K. c #E8A379",
"L. c #F3A677",
"P. c #D8A05D",
"I. c #3D65AB",
"U. c #3F67B2",
"Y. c #3B5C9C",
"T. c #506796",
"R. c #72748D",
"E. c #446AAE",
"W. c #4869A9",
"Q. c #4166B2",
"!. c #436BB3",
"~. c #496EB4",
"^. c #476DB9",
"/. c #4A71B6",
"(. c #4C73BA",
"). c #4772B6",
"_. c #5176BC",
"`. c #547BBD",
"'. c #577BB7",
"]. c #5572A9",
"[. c #6B7CAA",
"{. c #505B8C",
"}. c #557CC1",
"|. c #4C73C2",
" X c #897987",
".X c #9F7593",
"XX c #C46B87",
"oX c #5981BF",
"OX c #5884BD",
"+X c #768AB9",
"@X c #7288B5",
"#X c #5C83C3",
"$X c #5D8AC5",
"%X c #6186C5",
"&X c #648AC6",
"*X c #6B8DC6",
"=X c #668BC9",
"-X c #6B8ECA",
";X c #6586C6",
":X c #738DC7",
">X c #6D91CB",
",X c #6C94C6",
"<X c #7294CC",
"1X c #7895C8",
"2X c #6E92D1",
"3X c #7294D3",
"4X c #7698D5",
"5X c #708ED1",
"6X c #7799E3",
"7X c #9B9399",
"8X c #928890",
"9X c #B89887",
"0X c #A99191",
"qX c #B9A598",
"wX c #B1A394",
"eX c #8C8EAA",
"rX c #AB9AA6",
"tX c #ABA4A9",
"yX c #B7A9A8",
"uX c #B7ABB4",
"iX c #B6AFB7",
"pX c #C69B86",
"aX c #D4978B",
"sX c #EF9C83",
"dX c #CAA487",
"fX c #D7A787",
"gX c #C7A899",
"hX c #D1B294",
"jX c #E9A887",
"kX c #F8A886",
"lX c #F9B798",
"zX c #F1B291",
"xX c #C9B3AD",
"cX c #F4B9A5",
"vX c #D497B3",
"bX c #D5C6B1",
"nX c #FEC4A6",
"mX c #EAD0B2",
"MX c #EDD1A4",
"NX c #8399C8",
"BX c #B2B4CD",
"VX c #C7BBC7",
"CX c #D3CBCF",
"ZX c #ECDAD1",
"AX c #F6E6DA",
"SX c #F7EACF",
"DX c #D1D1E9",
"FX c #E7DDE4",
"GX c #E9E5E8",
"HX c #F7EAE6",
"JX c #FDF6E9",
"KX c #FEFCFE",
"LX c #FAF7F7",
"PX c #F1EBF6",
"IX c #DCE2E5",
"UX c #BEC5DF",
/* pixels */
"L k f k P l y j T R I I U U L U R Q T L E E E R R R E U j } GX9XfXpXxXR.j ~ ~ = V Z.G > b R.DXPXLXHXHXHXHXCX~ / T.Y.T.T.W.T.W.E.Q.I.E.I.I.E.E.I.I.I.I.I.Y.I.Q.^.Q.E.E.E.E.Q.Q.~.U.U.U.U.U.U.Q.Q.U.U.U.U.U.U.Q.Q.Q.Q.U.U.U.Q.~.~.Q.U.Q.~.^._._._._._._._._.(.(.(.",
"L k f L L k y h T R I L U U L U R R T L E E E R I R I U l XuX' fXV v [ / P h z V Z.G a y l [ 7XCXHXJXHXHXCXb ! {.{.T.{._ _ {.T.W.W.T.T.W.I.I.U.U.I.I.E.W.I.I.Q.Q.E.E.E.Q.Q.~.~.~.U.U.U.U.Q.Q.Q.Q.U.U.U.U.U.Q.~.~.~.U.U.U.U.U.Q.~.Q.Q.Q.~.~._._._.'.`._._._._._.",
"L k f L L k 0 h T T I E U U L T T T U L h h E U R R E U W R.{ D pXF z l L U ^ p F fXD i P W Y ~ n CXHXHXHXFX8Xl W ~ ~ l ^ b b ^ ^ / [ T.W._.U.^.U.U.Q.E.W.W.~.^.E.E.E.~.~.Q.~.~.~.~.~.~.~.~.Q.Q.Q.Q.U.U.U.U.U.U.~.Q.U.U.U.U.U.U.^.~.~.Q.~.~._._.`.`.`.`._._.|.|.",
"k k f L L k 0 h U T L h I U I T T T U k h k E U Q I E U k ` m ' hXV z k U I Q d V fX( j L U W W z VXLX8X XuX( z b x d ` X X` n n b b ! {.W.~.I.Q.Q.Q.E.W.].~.I.~.~.~.~.~.~.~.Q.~.~.~.~.~.~.~.~.~.Q.Q.Q.U.U.U.U.U.Q.U.U.~.~.Q.Q.Q.Q.Q.Q.Q.~._._._._._._._._.|.|.",
"k k f L L k q j U T L h U U I T R T U k h h E I E R I I k b p ' Z.V z k ! T U p H Z.c k U L U W n CXCXn z = c c v 7X8X` 8XPX} c R.tX` n b / {.].W.~.~.E.W.E.~.^.~.~.~.~.^.~.~.~.~.~.E.E.~.~.~.~.~.~.~.~.~.~.U.U.~.U.~.~.~.~.~.Q.U.Q.~.~.E.~.~./././.(.(.(.(.(.(.",
"h k h P L k q j U T L h U U I T R T U h h h E E I R I E k d p ' dXV x P U L L z J fXv l L L P j n IX` m = = 7 ' HXLXKXCX7XKXtXrXLXKXJXqXv n ^ {.T.T.T.W.].E.Q.^.~.~.~.~.^.^.~.~.E.E.E.E.E.~.~.~.~.~.~.~.~.~.U.U.~.~.~.~.U.U.U.U.Q.Q.~.~.E.~.~.E.~.~.~./././.(.(.",
"j k k P Y k q h U U k h U R I R R T U h h h E E R R E I Y d d ' Z.V p L ! ! Y = H Z.v j h ! l b n iXtX7Xa p t.LXZX0XHXPXKXKXPXLXLXCXbXAXVXn m 8XVXeX[.T.W.W.^.^.~.~.~.^.^.^.^.~.E.E.E.E.E.~.U.U.~.~.~.~.~.~.~.~.~.~.~.U.I.I.I.~.~.Q.Q.~.~.~.~.E.~.~.~./.(.(.(.(.",
"j k k U P k 0 y L U k h U R I R T Q T E f E E E E I I I f k z ` Z.V d U T L P z >.B.c j l l l } IXKXKX8Xp ` t.` t.' G ] tXIXIXwX] ' z.t.` { c n PXKXLXUX+X].E.~.~.~.~.~.^.^.^.~.Q.E.E.E.E.U.U.U.~.~.~.~.~.~.~.~.~.~.I.I.I.I.~.~.~.Q.Q.Q.~.~.~.~.~.~.~./.(.(.(.(.",
"j k R.~ k k q q Y T k f Q T I I I Q I L E L E E R I I E f d x ` dXV d T T T U z -.Z.c b s b CXLXKXLXKX} z 7 7 z.hXSXSXz.AXbXmXbX0XJXmXmX` 1 n b iXKXLXLXLXDX@XT.].W.E.(.U.|.^.^.~.~.W.E.~.~.~.~.~.~.U.~.~.~.~.~.~.U.I.I.I.U.~.~.Q.~.~.~.~.E.~.~.~.~.~.~.~.~.^.^.",
"j ~ DX[ W q s q Q I f h Q L R R R R I I L f E I I I I L P d x ` dXV d R R T Y z -.Z.7 0 ` GXLXKXJXLXJX` 7 7 7 t.hXMXmXJXLXJXJXJXJXSXSXmX' n z b 7XKXKXPXKXLXPXBX].T.W./.^.^.U.|.~.~.~.~.~.~.~.^.~.U.U.~.~.~.~.~.~.~.U.U.I.U.~.~.~.~.~.~.E.E.~.~.~.~.~.~.~.~.^.^.",
"r x DXIX~ s $ b L U L L Y Q L I I I T L f L U E T T L k k d x ` dXV x T R U L p 9.Z.7 | KXKXLXLXJXSXZXD v 7 7 D mXMXmXSXZXZXCXZXAXZXdXmXG v n n 7XKXLXKXKXKXLXKXDX[.T.]./.I.}.U.~.~.~.~.~.~.~.U.~.U.U.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.U.~.~.~.~.^.^.^.^.",
"r b CXPX X[ iX[ Y U L k P [.~ k U T U L f f f L I U U k f d x ` dXV z T T U L z 9.x.D LXHXJXJXZXqXqXmXD @ 7 7 9 ] mXbXJXKXKXKXLXJXJXMXv.9 7 7 7 } HXKXKXHXLXJXLXKXDX[.T.W.(.~.^.Q.Q.E.E.E.~.U.U.~.U.U.U.~.U.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.U.~.~.~.~.~.^.^.^.",
"d x VXKXPXGXCX` P U L Y ~ BX| l k P k k k P w k h L L P j d d ( dXZ z P ! L k z 9.B.mXJXJXSX9Xz.t.D 5 5 5 7 7 9 hXmXv.mXLXKXKXKXJXSXv.mX] v c v D t.xXZXJXJXJXLXPXKXUXT.W.E.~.~.Q.Q.E.E.E.E.Q.Q.~.I.I.~.~.E.E.~.~.~.~.~.~.~.~.~.~.~.~.I.~.~.U.U.~.~.~.~.^.^.^.(.",
"x 8XGXPXHXHXtXb k U U k l CXtXd b ~ | {.j q k f P / h k k d d ( dXF < k ! L k z 7.zXSXJXSXt.] V 3 3 X 5 @ 2 c 7 z.v.bXSXAXKXLXLXZXmXhXMX' 7 n 7 9 3 8 ] qXZXJXLXLXKXPX@X].W.I.^.~.~.~.E.E.~.~.~.E.I.E.~.~.E.E.~.~.~.~.~.~.~.~.~.~.~.~.~.~.U.U.U.~.~.~.~.^.^.(.(.",
"uXGXHXLXJXAX} & W Q g g ~ DXCX` [ VXDX[ s j s y ^ eX~ j j d l ( pXF 7 k ! L L z 7.nXJXAX] D 3 3 3 X ' ] 7 1 = 9 t.SXSXMX9XZXJXJXxXmXSXSXJ v v 7 9 ] 9 9 5 ' 9XxXJXHXGXDX{.'.).~.~.~.E.E.E.E.~.~.~.~.~.~.~.E.I.~.~.~.~.~.~.I.I.~.~.~.~.~.~.~.U.~.U.U.~.~.^.(.(.(.",
"iXFXPXLXLXLXyX( k W k ~ b CXGXFXPXGXtXl l s 0 j ^ DX` d d d x D pXF z P T L P z ' AXAXz.5 X 3 9.] 9 5 v.5 G ` 9 J hXhXhXmX9X' ] qXhXhXMX] 9 D 9 G z.5 ] t.8 8 G wXHXPXIX[.T.W.].~.~.~.~.E.E.~.~.~.~.~.~.~.E.E.~.I.~.~.~.~.I.I.I.~.~.~.~.U.U.U.~.U.U.U.~.^.(.(.(.",
"d n } LXLXCXVX[ W W d ` tXHXAXAXHXIX^ x j l w s ` GX7X7 n } ~ D dXH p k ! P k l ` xX8X2 @ 5 7 gXbXhXhXv.hXmXSXmXMX9.J 5 5 V 9 9 9 V G dXhXSXSXdXhXl.MXMXdXV J V 9 wXLXFXtXR.T.T.W.~.^.~./.E.E.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.).~.~.~.^.^.^.^.~.^.(.(.(.",
"d d [ LXVX( ^ ~ k ^ 7XFXLXHXJXHXAX} x l k w l i ` GXCX8XbX Xx v Z.H z k ! P d d i . & @ . 2 7 v z.v.V dXmXdXZ.mXSXSXSXbXt.` 7 D ] bXJXSXSXSXMXSXSXl.hXMXhXmXMXV 5 v xXxX} ^ ! {.W.~.^.U.).E.E.E.~.~.E.E.~.~.~.E.~.~.~.~.~.~.~.~.~.~.).).).).(.(.(.(.(.(.(.(.(.(.",
"f ~ ` PXR.l l j Y ~ { uXFXLXJXHXFXuX~ W f f d a } HXZXxXyXn d n Z.H 7 j P l j r p & o @ @ @ o 7 X 9 D ] V 5 hXbXqXv.] G D ` n ` G ' 0X9XmXmXz.G 9.9.3 8 ] hXgX9XwXv D z > $ l ! W.~.^.U.).E.~.E.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.~.)./.(././.(.(.(.(.(.(.(.(.(.(.",
"g W ^ DX( l l q L W r b ` CXLXLXPXPXR.k k ^ | 8XCXHXbXxX{ < d v Z.J 7 j l d r * . > 2 o . @ 2 = 7 X X 5 D 5 5 5 9 9 9 @ 7 7 2 v 7 7 v 9 v V D G qXgXxXD 3 3 ' z.D 9 2 > 1 # d u Y.W.~.~.).E.W.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.~.^.~.)././././.(.(.(.(.(.(.(.(.(.(.",
"U U / eXP P l f L L d r b CXPXR. XUXDX/ k ~ ` 7XCXZXZXyXv p k v B.-.o d l i * * & o o 2 @ . & . < & o 7 o 7 2 @ @ @ 7 2 v < z < z 7 2 7 9 7 5 9 ] z.ZXCX` 7 5 X @ @ & . o # % u _ W.E.E.E.E.E././././././.~.~.~.~.~.~.~.~.~.~.~././.(.(./.(.(.(.(.(.(.(.(.(.(.(.",
"f U ~ / L U k f U Y k x d DXVX~ x W {.[ f d 2 7 t.ZXZXxX} x k z x.-.3 d a $ & & . 2 o . @ . # p # , & . > & o z 7 2 2 2 o & < & < . > 7 > 7 7 7 v ' m 7 @ 2 . @ . + . > . > % y _ W.E.E.E.E.~./././.(.(././.~.~.(.(.~./.~.~.~.~.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"I T P P ^ ~ k k L U q l ~ DX{.W W Q Q ~ d * * o { HXxXVXCX^ q z x.>.5 i , # & & > o > . + + > . # . * * . < & . o o 2 7 & 2 2 2 > > < > 2 7 o o @ . @ . o . . + . . # & . . ; u / ].W././././.(././.(.(././././.(.(.(.(.(.(././.(.(.(.(.(.(.(.(.(.(.(.(.(.(.}.}.",
"E Q L P Q Q P f f L k k ^ BXU ~ P W T Y j i * * XFX` b 7XR.l 7 l.>.7 , # # < o o . > . - - - $ # # . & , . & . o o o . . o . o o . . . . . o o o @ . . . . 2 . + . - . > > . w _ ].W./././.~.(./.(.(.(.(././.(.(.(.(.(.(.(.(.(.(./.(.(.).).(.(.(.(.(.(.(.(.(.}.",
"I U U U T Q k h L L h P Q / T L U T T U j 0 0 r 7XuXd r d ^ l < r.0.5 ; - - - & . > # # - + % # . . + . # # . . . . . . . . . . & # . . o o o o . o o . . . . . + . # # . > ; w _ ].]./.E.(.(.}.(.(.(.(./././././.(.(.(.(.(.(.(././././.(.(.(.(._.(.(.(.(.(.(.(.",
"L U U U T Q k q k L k P U Q U U U T T U k q q r X( j d 0 t y 7 r.0.@ ; + - - & . > & . . - - , - + + + . . + > . . . . . . . . . . . . o o o o o o o . . . . . . . % . . . - t / ].].]./.`.(.|.(.(._._.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"L U U T T Q k q k k k P I I I T R R T U L f q f / L W q w s s 2 0.r.X ; % - # # > > . # > + . . . . . @ @ o . o o o o o o o o o o o o o o o X X o o o o o . # # - . # > o . # w ^ ].].'./.`.(.}.(._.`.`.`._._._.`.`.`.`._.(.(.(._._.(.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"L U I T R Q j q k k h L I I R T R R T U L f q f E T U f j j 0 7 0.l.X ; % - # . . # . . . . o @ X X X X X X X 3 3 3 3 3 o o 3 3 o o 3 3 3 3 3 3 3 3 X X o X o o . . . & o o - % ! ].].'./.(.(.}.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.(.`.`._.(.(.(.(.(.(.(.(.(.(.(.(.(.",
"k U U T R Q k q l k f L T I T T R R T U k f 0 q I R E L q q q 7 >.l.X , % + . . . & = o @ X X X 3 4 N N N V V V V N N N N N N N N N V V F F H H H H F V 8 3 3 3 o . & o . . > % P ].].'./.'._.}.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.(.(.(._._.`.(.(.(.(.(.(._._.",
"k P U T R Q k q k k k L T I T T R R T U k q 0 q I I T f w j j 6 >.r.3 - . + + . > . X @ X X 3 N F 6.r.y.y.y.y.y.y.r.r.0.6.7.6.7.7.6.6.0.r.y.B.B.y.y.x.x.y.7.V 3 X . & & @ . # % h ].].'.'.'.`.}.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`._._._._.(._._.}.",
"k P U T Q Q k q f l k L T E T T R R R U j 0 0 y k E I L k j q 7 0.<.3 # . + . . o o X X 3 9 ' c.Z.A.aXaXaXaXjXjXjXA.A.A.S.B.S.B.S.S.A.A.G.K.K.G.A.C.C.Z.A.Z.r.' 9 o X o o @ - ; u ].].'.'.'.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.}.`.`.`.`.`._._._.}.}.",
"d W U R R Q k q f k g L T I Q R T R R U j 0 0 y k L I I f f f 6 r.>.3 # . . . . X 7 7 8 G t.pXaXaXA.A.fXzXzXzXlXzXjXzXlXzXzXzXzXkXzXkXzXlXlXzXjXK.K.K.A.A.K.Z.c.t.G 5 3 X o . t u '.'.'.'.'.'.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.`.}.}.`.`.`.`.`.`.}.}.}.}.",
"` b U Q R Q k q q l L L L T T R T T R U k 0 0 t j U I E L f f 7 r.r.X . . @ o o v ' F H y.Z.fXfXK.jXjXzXzXzXzXlXcXlXzXlXzXzXnXcXzXlXlXlXzXnXnXcXlXjXzXA.jXA.J.B.c.t.-.D 3 X & % K '.'.].'.'.oX`.oX`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.}.`.}.}.}.#X#X",
"xXz W Q I _ ~ y j j f U I I U U U R T U k q 0 t k U I L L f f = 0.l.X @ @ . . 9 ' ' H 0.B.A.fXfXjXjXzXzXzXlXcXcXzXzXlXnXcXzXzXzXcXcXlXcXzXzXzXzXzXcXjXA.G.A.C.B.Z.y.e.-.D o @ % K '.'./.'.'.'.oXoXoX`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.`.}.}.}.}.}.}.}.}.}.}.",
"HX7Xx ^ eXBXM $ l x Y U R R I R I I T U k q 0 y k U I I k j j = 0.l.X > o o 7 D ' H 7.y.B.A.fXfXjXzXzXzXzXzXjXjXzXzXlXnXlXzXzXzXjXzXzXsXsXD.B.e.x.x.S.A.B.B.S.Z.Z.c.l.e.' 7 @ , ! [.'.`.'.'.oX#XoXoXoXoXoXoXoXoXoXoXoXoXoX`.`.`.`.}.}.#X#X}.#X#X}.}.}.#X}.#X}.}.",
"AXZX{ CXPX| d 0 ` R.d Y U R I U E L R U k q q y L U U U k h l o r.l.X > . o v D H H r.x.l.B.A.A.B.B.B.e.e.e.B.S.jXzXzXlXzXzXcXzXfXjXjXD.D.a.q.e.r.e.Z.A.jXA.Z.Z.Z.c.e.e.9.G 5 # ^ [.'.`.'.'.'.'.oXoXoXoX#X#XoXoX#XoXoXoXoXoXoXoX#X#X#X#X#X#X#X#X}.}.#X#X#X#X#X#X",
"ZXHXHXGXVXb i i ` FX^ W Y g ~ P k L U I k q q q L U I U k q y @ >.l.X $ & 7 9 F ' 7.r.e.x.Z.A.A.A.V.a.a.a.f.B.A.A.jXzXzXzXzXlXzXzXfXA.D.D.8.*.=.*.6.r.B.fXaXfXc.c.Z.B.9.t.` v 7 ^ @XoX'.'.#X%X}.#XoXoXoX#X#X#XoXoXoXoXoX#X#X#XoX#X#X#X#X#X#X}.}.#X#X}.}.#X#X$X$X",
"HXAXZXFX{ x b # { FXrXx x {.eX^ k L U L k q 0 q f L E E f 0 t @ >.<.X , & 7 G 7.7.9.r.x.Z.Z.S.S.B.e.0.6.6.0.0.0.B.A.jXK.A.jXlXzXjXfXA.g.i.:.%.*.O.Z F Z Z F F F H ' -.7.t.` v c [ @X@X'.oX=X#X#XoXoXoX#X#X#X#XoX#XoXoX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X;X;X",
"ZXAXZXGX` b & & ` ZXHX} uXGX8Xl k L U L j r r 0 r W Y f q 0 i 5 w.>.5 , . 9 7.9.-.-.9.c.c.x.B.B.x.r.9.7.7.6.r.y.r.B.A.jXG.jXlXlXzXK.B.e.0.=.C N Z &.6.*.&.H N V t.' V F ' { v v [ @XoX'.oX#X`.#X#X#XoX#X#X#X#XoX#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X$X",
"AXJXHXFXIX^ x = ] ZXAXAXCXVXb j k L U Y d i & & x ^ ~ f q q i X 0.<.5 & 7 D 9.9.-.J H ' G V V N 4 8 N N N N V Z $.r.G.lXlXzXlXnXlXK.b.0.C C 6.B.r.y.y.S.r.c.SX9.8 V A D ` ' D D R.eX+X%X$XoXoX$X#X#X#X#X#X#X#XoX&X#X#X#X#X#X#X#X#X#X#X#X%X%X#X#X#X#X#X#X#X#X#X#X",
"gXAXCXFXPXuXz D bXAXSXZXCX{ b k L L Y W r ` n v 7XtXx j w r r 3 0.>.8 2 9 ] y.9.H F 8 D D V N 0.cXaXy.r.r.B.S.*.O.Z <.n.kXkXL.L.F.p.;.0.jXy.9.V N N F F r.r.c.MXD V ' F D ' u.' XR.+X%X$X$XoX#X#X#X#X#X#X#X#X#X#X#X%X&X&X%X#X#X#X#X#X%X%X%X#X#X%X#X#X#X#X#X;X;X",
"z.ZX` ( { eX{ n CXZXZXZXxXm x k L U Y W r ` } z.iX` r w r r 0 3 0.>.8 D G 9.r.-.F V 3 8 3 N r.y.y.7.F V V N &.B.a.w.p.p.F.F.F.b.p.,.,.q.0.6.V H N V V N C $.H C H F V N V H r.9XgXrX[.*X&XOX&X=X%X%X%X#X#X#X#X#X&X&X&X&X&X&X%X%X%X%X%X%X%X%X%X#X#X#X#X%X%X;X=X=X",
"z.xXx ~ f x x z t.ZXAXAXCX} x Y U T U k p v ] ] } z r r 0 r s 6 r.r.8 D 9.r.6.C V V D D 8 7.9.r.' V N N N N Z F 0.;.;.s.n.p.a.p.3.1.p.p.;.Z O.V 4 N N N B o.o.%.0.-.H H &.r.f.6.y.yX@X:X'.oX-X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X%X%X%X%X#X%X%X%X&X=X=X=X",
"} } k W U k 0 & v CXAXCXGXCX~ f L T U k z D t.] 9 o p d w r d = >.l.N D c.e.0.6.F H V F V H F 6.V N V 4 4 N &.6.C O.%.s.i.F.zXkXF.n.M.s.;.i.8.=.&.O.F O.%.%.;.q.B.e.b.w.;.;.q.o.Z 0XeX:X[.@X-X'.&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X&X%X=X=X=X=X=X%X%X",
"[ ( Q L I U r * 7 CX8Xv } iXR.j L T U k r ( } t.{ . r r s 0 r 6 >.w.N ' y.$.$.=.6.6.6.r.6.$.O.O.O.C O.O.=.=.=.w.d.n.n.M.:.G.lXlXL.,.H.H.D.d.q.g.a.q.0.,.q.d.s.p.a.b.C.w.<.;.d.s.Z t. X[.:X;X;X=X&X&X&X&X&X&X&X&X=X=X=X=X&X&X&X&X&X&X&X=X=X=X=X=X&X=X=X=X=X;X;X%X",
"~ k L T T U 0 $ b CX` x x x ^ k L L U P d ( ' } 7X` r r s s 0 o 0.w.4 7.6.O.8.8.0.6.B.B.0.;.o.o.o.o.:.s.G.V.s.V.s.H.kXF.%.G.lXlXlXF.H.L.D.s.H.G.kXzXsXD.s.D.F.n.V.V.V.w.<.n.V.a.O.y. X[.:X;X;X2X=X=X=X=X=X=X=X=X=X=X=X=X=X&X&X&X&X&X=X=X=X=X=X=X%X=X=X=X=X=X=X=X",
"f k Y U L k 0 $ b 7Xj W Q L k q L U U Y r p 2 7 [ } d d 0 s t o r.r.4 F &.o.q.8.=.;.a.C.w.w.:.O.O.;.d.sXsXkXH.s.i.L.lXs.q.kXlXzXlXK.a.kXkXV.a.G.L.H.D.M.p.D.F.V.A.A.A.B.q.D.kXV.%.y.eXoX@X*X$X=X=X=X=X=X=X=X=X-X=X=X=X=X=X=X&X&X&X&X=X=X=X=X=X=X=X=X=X=X=X=X=X=X",
"k j L k P P q 0 x ^ Y Q R R L k h U U k j * # * p x k l q 0 t @ >.w.8 V 6.C a.g.q.%.w.n.p.p.d.q.:.i.i.V.s.i.q.d.lXkXH.,.V.lXlXlXzXlXs.G.K.lXkXn.n.i.p.i.p.p.G.C.B.V.B.B.a.q.V.A.=..X@X*XoX-X=X$X=X=X=X=X>X-X-X>X-X-X-X-X-X=X=X&X-X-X-X=X=X=X&X&X=X=X=X=X=X=X=X=X",
"j k L U U k q 0 j j Y U U L L L Y W L k y w ; $ 0 q h k q y y o >.w.4 8 r.O.V.s.8.%.d.s.n.p.n.q.%.i.p.a.a.f.sXlXlXzXF.q.kXlXnXcXnXnXjXB.zXlXlXnXlXkXG.V.V.G.L.F.V.n.b.b.8.o.i.D.r.t.@X+X@X*X-X>X>X-X*X*X*X-X*X*X*X*X-X>X-X*X&X*X-X-X-X-X=X=X&X&X=X=X=X=X=X=X=X=X",
"j k L U U k q 0 j f k Y Y k k L U W L k y u t 0 w j f f k y s 3 0.l.3 V r.=.D.s.:.%.i.i.n.n.V.q.,.s.G.kXlXnXnXcXlXnXb.C.zXlXcXnXnXlXnXS.G.zXlXlXlXlXzXK.K.K.F.V.V.n.,.C.d.o.;.S.c.8XeX:X@X;X-X=X>X-X*X*X*X-X*X*X>X-X-X-X-X*X*X*X-X-X-X-X=X=X=X=X=X=X=X=X=X-X-X=X",
"j k L L P j 0 0 j k L P Y k k L U U L h y 0 r r f f f f k w i 5 >.w.V 9XcXe.V.V.%.q.s.i.p.n.b.w.:.,.L.nXnXnXnXnXnXlXq.jXlXlXcXnXnXlXlXjXq.sXzXzXlXlXK.F.G.F.V.n.V.p.w.C.sX8.q.f.9X8X+X*X*X-X2X-X>X-X-X-X-X-X-X-X>X-X-X-X-X-X*X>X-X-X-X-X-X=X=X=X=X=X-X-X-X-X-X-X",
"h k L L P j 0 0 j k Y U P Y k Y Y U k h y w r r j r j f y t r X C C.c.hXcXA.K.p.w.A.C.q.<.p.a.b.p.i.F.lXcXnXlXzXjXq.q.G.kXlXcXlXnXnXnXzX0.:.s.H.G.G.G.K.kXkXF.n.a.e.b.C.kXD.q.y.0XeX+X*X*X-X-X=X>X>X-X-X-X-X-X-X>X-X-X-X>X-X-X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"h k L L k s $ $ y j L Y Y L L Y Y U k h y 0 r r r f k r r d & 9 w.C.c.hXcXA.G.n.C.jXG.q.<.p.w.b.D.i.p.D.kXkXV.i.;.:.D.H.kXlXnXcXcXlXlXlXsXi.8.8.:.;.a.G.G.G.F.a.a.a.l.C.kXs.q.S.8X@X:X>X-X-X>X=X>X-X-X-X-X>X-X-X-X-X*X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"k k L L l t $ $ y k L Y L L L Y Y P k h y 0 r r r f f s r # 7 t.J.P.c.hXMXfXC.G.C.K.K.a.q.i.w.p.F.M.i.:.%.;.;.:.o.s.kXH.kXzXcXlXlXlXzXzXsXH.8.H.H.H.sXkXD.V.V.a.b.e.B.A.G.q.V.B.8X@X*X-X-X-X2X2X>X>X-X-X>X>X>X-X-X-X-X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"k L L L l t % $ y k L I P P L L L Y h h y y s q r r r s * & ' hXK.J.x.hXcXx.B.K.C.A.J.b.i.i.p.b.a.F.D.s.V.H.V.i.:.H.D.V.G.sXzXzXjXG.sXV.s.s.:.q.H.kXkXH.D.n.n.V.b.e.B.A.b.V.G.c.eX:X>X-X&X*X*X&X>X>X-X-X>X>X>X>X>X-X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"k k L k j t # ; y k L I L L L k g L f h y y s 0 q r s r * D wXgXJ.P.x.cXMXl.b.C.jXA.C.e.q.i.a.b.p.n.V.H.sXkXV.%.o.;.O.%.q.q.f.B.B.f.8.:.Z O.B O.s.G.H.D.H.V.V.C.B.e.B.0.C.K.s.pXeX&X=X-X,X,X<X*X>X>X-X-X>X>X>X>X>X>X>X>X>X-X-X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"f P k f ~ 0 $ ; j w I T L L f L Y P k y y y y % 0 w y i ` 0XgXhXP.P.x.cXhXl.x.A.b.b.%.w.p.i.a.n.p.n.V.D.G.V.:.o.V.q.Z Z C o.$.$.$.O.=.o.%.g.:.B :.s.g.s.V.n.V.C.V.B.e.q.q.w.A.9X+X-X=X>X*X*X*X,X*X>X>X>X>X-X>X>X<X>X>X-X-X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X2X3X3X",
"d j f W s s $ % s k g L L k h g g k f y t t t t w y t | tXwXgXfXP.P.B.cXhXc.x.A.C.B.C.K.V.i.p.n.n.a.F.V.V.i.o.i.G.G.V.V.V.0.o.N C 8.g.V.g.D.i.Z B ;.d.s.s.s.V.C.B.C.kXG.K.C.fX0X+X=X=X-X*X*X*X:X*X>X>X>X>X-X>X>X>X>X>X-X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X>X>X",
"p d W / d $ i i 0 k P U P k L k g f q q i i $ i $ d 8XiXrXgXgXfXP.P.B.cXhXZ.x.S.jXA.jXkXsXa.p.n.F.n.n.V.i.O.:.D.H.D.H.V.H.H.g.q.a.V.s.V.s.a.V.q.B o.:.,.i.s.V.C.V.C.jXzXC.fXgX8X+X*X-X>X,X*X:X,X*X>X>X>X-X-X>X>X>X-X-X*X*X*X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X>X",
"( m X[ d s $ $ l q j k k k h f g k j 0 i , & * x 7XiXtXyXqXgXdXP.P.B.cXhXZ.r.e.jXzXjXlXjXa.b.V.C.n.a.p.%.o.s.D.H.H.G.H.G.G.sXH.sXH.V.V.D.V.H.s.O.B O.;.a.V.s.b.b.A.zXjXK.dX0X8X+X*X-X>X,X:X:X,X*X>X>X>X-X-X-X>X-X-X-X*X*X*X*X*X-X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"qX0X8Xx r r * $ 0 s j j j f g k g j j r * & 6 D XrXuXyXyXgXgXfXP.P.B.zXhXZ.9.=.A.jXjXzXr.e.V.b.F.b.n.<.o.:.s.D.G.sXsXkXsXkXlXzXzXsXsXD.H.G.G.V.s.O.o.:.i.s.V.C.B.B.zXjXB.c.7XeX+X*X-X>X>X*X*X-X-X>X>X>X-X-X-X>X-X-X*X*X*X*X-X-X-X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X",
"mXqX} z p z $ * b ` b j l w k k h f s i = 6 A u.rXrXyXxXqXqXxXfXJ.P.B.zXhXZ.-.H >.B.A.0.N -.e.C.C.C.n.:.o.p.d.H.G.sXG.kXsXkXlXzXzXkXsXsXsXG.G.V.V.i.o.d.n.V.F.b.B.*.r.B.e.9XeX@X+X*X-X-X-X*X*X-X-X-X>X>X-X-X-X>X-X-X-X-X-X-X-X-X*X*X*X*X-X-X*X*X-X-X-X-X-X-X-X-X",
"hXqXD z z e * ( R.[ d $ d s q q h h j r 6 c A u..XvXxXyXqXqXxXdXP.P.x.hXzXZ.7.H -.0.0.Z 4 H w.V.V.G.V.,.:.s.s.D.s.a.q.d.i.d.H.H.g.s.i.s.s.a.s.D.s.V.;.s.H.F.G.V.e.C F 6.fXwX+X+X+X+X-X-X-X*X-X-X-X-X>X-X-X*X-X>X>X>X-X-X-X-X>X>X*X*X*X*X*X*X*X*X-X-X-X-X-X-X-X-X",
"bXqX( z = p & ` } p d d 0 s q k q h j $ 6 c A &.y.gXxXxXyXqXgXhXP.P.B.fXzXZ.9.H H =.x.9.V V e.C.C.F.kXs.i.d.s.d.%.o.o.o.o.o.:.:.o.o.o.o.o.o.o.:.q.d.D.F.kXF.n.B.B.C *.c.c.z.eX+X:X:X-X5X-X-X>X>X-X-X>X-X-X-X-X>X>X>X>X-X-X>X>X>X-X*X*X*X*X*X-X*X*X-X-X-X-X-X-X-X",
"ZXxX.Xz z = . ` n x d s l q q h f j s r = A S S r.9XgXyXxXqXgXfXC.F.m.fXfXe.6.H F V D D V N 0.b.V.G.L.L.i.i.:.O.o.%.:.;.8.8.+.+.:.d.H.D.s.D.n.a.i.s.n.L.L.F.S.Z.e.H -.y.qXtX@X,X*X-X5X5X5X>X>X>X>X,X,X,X,X,X-X-X<X>X,X*X>X>X*X*X-X-X-X>X-X-X-X*X*X-X-X-X-X-X-X2X",
"0XyXuX( = p . { ^ * d j j h y s r j s r 6 A S B 6.pXgXyXgXgXgXfXF.F.m.jXZ.0.>.;.Z N 3 9 v V =.b.b.n.G.L.V.p.a.p.s.s.g.H.sXsXH.sXsXH.G.sXH.D.s.n.V.p.F.L.F.n.A.B.9.H 9.c.yXtX+X+X-X2X5X5X5X-X>X>X>X>X,X,X>X>X-X-X>X,X,X*X*X*X*X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X>X",
"` = { } z & < 7X{ * d r q j 0 s s r y r 6 A S B ..g.gXgXgXgXgXfXF.P.B.fXZ.>.;.,.<.Z N N N N F w.p.n.L.F.n.p.M.n.s.n.n.V.H.sXsXH.H.D.H.V.d.p.n.F.F.p.F.L.n.n.B.x.-.H 9.0XtXeX+X<X2X>X>X>X*X,X>X>X>X>X>X>X>X-X-X-X*X*X,X*X*X*X*X,X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X>X",
"z p r z r * & } Xx d r r s 0 s s s t * 6 N S B B r.gXgXgXgXhXfXP.P.B.J.Z.0.,.<.3.3.<.Z N N Z 0.a.b.V.F.M.3.n.M.s.p.p.p.i.q.8.;.:.:.8.q.p.D.F.V.V.a.M.M.M.a.B.r.J -.t.8X7X@X:X<X2X>X>X,X,X,X>X>X<X>X>X>X>X>X-X-X*X*X*X*X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"i d r s r * $ < } b d r d r 0 d y s i & 3 N B B B O.aXgXgXgXhXA.P.P.l.J.Z.0.,.<.3.2.k.k.<.Z N *.w.p.p.F.n.p.n.n.F.D.n.d.,.;.;.;.;.:.;.<.p.n.n.p.V.V.n.n.s.a.y.7.7.9.} 8XeX:X<X-X<X>X>X>X>X>X<X5X<X<X>X>X>X>X>X>X<X>X*X*X*X*X*X*X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"y q j q $ r * * b x d r d q i i t i = 6 N B B B B ..h.aXgXgXhXZ.P.m.b.J.A.>.<.<.3.2.2.2.3.1.B *.q.n.p.p.,.n.D.s.p.s.s.a.V.G.G.G.D.V.V.a.p.p.p.n.C.V.s.D.i.a.y.H 9.t.} eX+X1X<X>X>X>X,X>X>X<X3X5X<X<X<X>X>X>X>X>X<X>X>X*X>X>X>X*X>X-X-X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"l w h l s $ . & $ i s q j q y 0 t * 6 A B B B B B .+.aXaXaXgXZ.P.m.b.K.A.<.,.3.4.2.2.2.4.1...O.d.w.q.a.n.n.D.F.F.D.V.H.zXzXzXzXzXL.kXkXL.L.L.L.C.a.n.d.s.q.B.' } 8XeX+X+X,X,X,X,X,X,X>X2X3X3X3X<X<X<X<X>X<X<X<X<X<X>X>X>X>X>X>X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X-X",
"y l h s $ $ * & i 0 j f h q t ; , o 4 A S .B B B .. .g.aXaXgXZ.m.P.m.G.C.q.3.4.4.2.2.2.4.4.o.o.s.q.p.p.V.a.n.V.s.D.D.D.L.lXlXlXzXH.kXkXL.L.F.L.B.b.a.;.a.e.c.t.} eXeX+X>X>X,X,X,X,X,X2X2X3X3X3X<X<X<X<X<X<X<X<X>X>X<X>X>X>X>X<X>X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"y h j s r $ # # i t q y h q t , = c A S . .B B .X...+.aXpXpXx.k.N.m.n.n.,.p.4.4.2.2.2.4.3.Z B e.b.q.w.p.b.p.n.n.M.n.n.F.F.L.kXH.G.kXzXkXL.C.C.e.0.;.q.q.r.y.t. XeX:X1X3X2X1X,X,X,X,X>X2X2X3X3X<X<X<X<X<X<X<X<X>X>X<X<X>X>X>X>X<X>X>X>X>X-X-X-X-X-X-X-X-X-X-X-X",
"y s s s r $ # # $ d l q q q i = c A S ..X.X.B B .X...@.f.S.aXe.k.N.k.p.p.<.3.4.3.2.2.2.3.,.Z F A.b.;.;.w.p.,.p.n.n.n.p.n.n.F.V.V.D.G.A.F.F.n.e.0.$.%.o.%.x.y.D 7X[.+X5X3X3X-X>X,X,X,X,X>X>X<X3X<X<X<X<X<X<X<X<X<X<X<X>X>X>X>X<X<X>X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"r s s i r $ # # ; d j q r d = 6 A S S ..X.X. .X. .....B :.S.aXe.k.N.k.<.<.<.3.<.2.2.2.k.<.$.8 gXaXB.r.%.$.%.%.;.a.n.b.a.s.e.e.q.e.g.B.f.a.a.q.=.F C Z C r.Z.t.o ^ R.+X5X3X6X6X2X>X,X,X,X>X>X<X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X<X<X<X<X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"r s s r r $ # - ; i q r r 6 6 A S S ....X.X. . . ..... .o.g.S.b.k.N.k.<.<.1.3.3.k.2.2.3.;.4 9 AXc.S.f.*.Z N Z C =.0.q.0.=.$.&.=.6.6.6.=.=.%.%.o.Z N Z r.B.Z.t.X < M T.:X5X3X3X4X3X<X,X,X,X,X<X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X>X<X<X<X>X>X-X-X-X-X-X-X-X-X-X-X-X-X",
"s s s r r $ # - ; i r r 6 6 N S S .X.X.X.X. .B . . . .B i.V.b.k.N.k.<.<.:.3.3.3.4.k.<.4 X t.bX9.S.f.e.$.N N N Z F F Z V N V F F V N N Z Z C o.Z Z 0.B.B.Z.t.@ > $ y {.NX3X3X-X3X3X<X,X1X1X1X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X>X>X<X<X>X>X>X-X-X-X-X-X-X-X-X-X-X-X",
"j j j r 0 $ # ; i p p 6 8 A S .. .X.X.X.X. . .B B B ..B X.:.D.,.k.N.k.1.:.%.p.<.,.<.-.8 X X wXgXH S.f.f.r.C C Z Z Z N 4 4 8 8 4 4 4 4 4 N C $.$.F 6.f.a.Z.Z.t.o . - $ y [ +X1X6X3X4X4X<X1X1X<X>X<X<X<X<X<X<X<X<X<X<X<X<X<X>X>X>X<X<X<X>X>X>X>X-X>X>X>X>X2X2X2X2X",
"j j j q 0 $ # ; , * c c A S .. . .X.X.X.X. .B B B B .. .X.+.D.;.k.N.N.1.1.%.w.%.0.V 3 3 = & xXgXZ A.B.e.r.*.&.&.&.H V N 8 8 3 3 3 4 N V F =.6.*.*.0.e.B.B.pXz.X < # # ; % ^ @X,X>X3X4X3X1X<X>X>X<X<X<X<X<X<X<X<X1X<X<X<X<X<X>X>X<X<X<X<X>X>X>X>X>X>X>X>X2X2X2X2X",
"j j j q 0 $ # - , z ( ) S S ..X. .X.X.X.X.X.B B B S B ..X.X.g.;.m.N.P.1.%.$.r.H 3 3 @ o * . CXmXV C.B.e.e.;.$.6.=.7.F V N 8 8 8 4 N V F &.6.6.*.6.f.e.B.B.mXt.o # , & . - + h _ @X:X1XNX1X<X4X4X<X<X<X<X<X3X<X<X4X4X4X4X1X<X>X>X<X<X<X<X<X<X3X3X>X>X>X>X>X2X2X2X",
"s s d w r $ # . 7 } vXXXS ..X.X.X.X.X.X.X.B B B B B B X.....f.;.m.L.P.-.Z N 3 X o o # # # & CXZXF e.B.e.e.;.;.=.6.6.&.F Z V N 8 N N Z C =.6.*.*.0.f.e.B.hXZX} ; # # & & # # ; u w ! {.+XNX,X,X4X1X<X>X<X4X4X3X>X3X4X4X1X<X<X<X<X<X<X<X3X3X3X3X<X>X*X*X*X-X<X5X5X",
"i r s r s $ . v 8XxXgXy.*...X.X. . .X.X.X. .B B B B X.X. .B y.r.0.>.Z 3 X X . . # # # # > @ CXZXF 0.A.e.0.q.;.=.;.=.O.O.C Z V V F C O.%.6.;.%.;.e.e.B.Z.SXAX| % # # # # # # # # ; t u h _ +XNX+X<X<X<X<X<X<X4X4X4X4X4X4X<X<X<X<X<X<X<X3X3X3X3X<X<X,X,X,X<X5X5X5X",
"i r s s 0 # 2 } yXxX9Xy.g.O.....B ....X.X. .B B B B .X...S F V 3 3 X X X @ & # # # # # & @ CXZXV -.B.C.a.<.;.;.;.*.O.&.F F F H &.O.$.=.8.=.%.0.q.e.B.hXAXAX` # # # # # # # # # ; % t l h u / [.1X1X1X1X1X1X<X<X1X>X>X4X4X<X1X4X<X<X3X3X3X3X3X>X<X<X,X,X<X<X5X5X",
"d r s t % # 7 qXyXgX9Xc.aXr.@...B B ... .B B B B B S B N 4 3 X X o o o . . # - # # # & = X xXHXD F S.V.w.w.;.<.;.=.$.&.F F F =.=.*.*.;.0.;.;.q.e.e.pXAXZXGXn # # # # # # . # # > , i t t y h h ^ T.+X+X+X1X1X<X4X1X4X4X4X>X>X>X<X<X3X3X3X3X3X2X<X<X,X,X<X<X5X5X",
"d r 0 t ; , 7 qXxXgXqX9XgXaX8... . .....S B N 8 8 8 4 3 3 3 . . # # $ ; $ ; ; # # . . & = o tXHXhXV e.V.a.q.<.w.0.6.*.*.&.$.*.8.;.;.;.8.0.;.;.w.b.r.mXHXGXFX= > # # # # # # # # & # # i d i r t h w h {.eXNX1X1X,X,X1X1X1X<X>X4X<X<X3X3X3X3X3X3X<X>X,X,X<X<X5X5X",
"r r y t ; 2 7 9XqXxXqXqXxXcXaXO.S S B N N 4 3 X @ X o o o & % % $ $ $ % % # - . . . o > & o XFXHXt.&.f.a.,.w.<.q.0.=.&.*.*.=.8.;.:.;.0.q.;.;.e.0.pXAXZXPXVXo & # # # # # # # # & & < < , * , a i d y y l / T.+XNX1X1X1X,X,X1X4X<X<X3X3X3X2X2X2X>X>X>X-X5X<X5X5X",
"r s y 0 # o 9 gXgXxXqXqXxXbXcX7.N 8 4 3 X @ . + - . . . . . # # . # > > > + + + . . . & # # | CXHXZXF 0.s.,.<.w.w.;.=.&.=.*.;.8.;.:.;.w.q.;.0.r.r.ZXAXGXGX} = o # # # # # # # # & & & < < < , < , i i i t y h h ^ _ ].+X1XNX1X,X1X<X<X<X3X3X2X>X2X>X-X-X5X5X5X5X",
"s q q 0 , o 9 9XgXgXxXyXxXCXgXD 3 o o @ + + + . - . # - > # , . > . . . @ . . . . . . # # , | IXHXHXt.F e.a.,.<.w.;.=.&.=.%.;.8.;.;.;.q.;.=.=.-.hXAXHXGXGX` o & . # # . . . # # & < < * , < < > < < , , i d s y h l h h ! [ @XNX1X1X1X1X<X<X>X-X3X2X>X-X5X-X5X-X",
"j q s 0 * o 9 9XgXxXxXyXtX X7 o o o . + . . . . & # # - - - - + + . . . o o . . > . # . # # ` IXFXLXZXF =.e.p.<.w.q.=.*.*.o.;.;.;.=.,.q.;.*.F c.ZXJXHXGXFX< & . . . # . . # # # # # * , i i , , < < < p i i i i s d l l y q l ! T.NX1X,X,X1X*X<X>X>X2X2X-X-X5X5X",
"y y r r = o 5 9XqXbXwX7 2 2 . 2 & & & > # # & o & & # + + . . . . . . . o & & & # # , > - # n GXFXHXJXmX&.r.w.p.w.e.=.$.=.C $.$.$.=.%.w.;.C 9.ZXHXHXLXGX7X& $ # . . # . . . # # * , , , i i i i < < < < < p p p a i i d d s s j h ! T.NX1X,X,X*X<X>X2X2X-X-X5X5X",
"0 s r r p & @ qXbXyX7 2 o & > & & = & & & # & # . . . . o o o o . . . # # & * * * * * , # & z FXPXHXLXAXZX6.a.s.e.e.6.*.=.C $.*.$.O.0.0.$.7.AXHXKXPXPXGXD . $ # . . . # # # # # * ; ; ; i i i i , < p p < < < < z < < p a p i r y h L _ @XeX1X,X<X<X3X>X-X-X-X-X",
"i i $ r * & o wXxX` o < . # # # = = & & & & # - . . . . o o o o # # # # $ $ $ * * * * & . & 2 tXFXLXPXLXJXcX6.g.A.e.r.0.%.C *.&.$.&.e.0.H SXHXKXKXLXPXCX2 . # # & # # # . # # - # $ ; ; i t i i i i i i < < < < 7 < < < < < < p d s y h h ^ [.1X:X5X5X5X-X-X-X-X",
"p = * * * & o 0XyXo 7 . $ 1 # > & & & & & > - - . . . . o o o o # # # $ $ $ $ $ $ # * * & & & ` FXGXKXKXLXJXpX6.B.B.b.e.=.$.r.&.&.y.r.H SXLXLXKXKXKXLX X& & & & > # # # # - - - # - ; i i i i i i i i i , , < < < < < < < < p a a x i t y h / NX:X5X5X5X-X-X-X-X",
"v = * i # & = 0X' o . 1 , . & > - - & & & > & - . . . . . . . . # # # # # # # # # # & & , & < < xXFXPXKXKXLXJXc.r.B.A.B.e.0.y.0.r.y.7.AXLXLXKXKXKXKXPXn , > & > > > # # - - > > > , , i i i i i i i i i i , p < p , < , p i , , & & z i t y u [.:X<X5X5X5X-X-X-X",
"{ 7 & , # # = 8Xv o & . - > > . + - & & & & & & # # # # . . . . . o o . . . . + + + + . - # p * t.GXPXKXKXKXJXJXx.e.B.B.x.e.y.e.e.c.AXLXLXKXKXKXKXKXtX# , > > > , ; # # - > > > > > > , , , i i i i i i i i i p i i , i i i t $ 7 o 7 > $ s u _ :X5X5X5X5X-X-X-X",
"yXD & > # # o } o X > > - # o @ + + o o & & & & . # # # # # # . o o o o . . . . + . . . - # , p ` GXGXKXKXLXLXJXAXx.Z.fXMXnXcXcXcX9XAXKXKXKXKXKXKXLX| , , # < & , ; # - - > > > > > > > , , , i i i i i i i i i , i i i * t i i = 2 & > a % y K :X5X5X5X-X-X-X-X",
"xX8X3 o o o 2 n o o > > > > & & > - # & o . & # . . . # # # # . # o & o . . . . . . # # # & , , m IXGXLXKXKXKXKXLXJXHXAXAXZXxXwXD 5 D FXKXKXKXKXLXCX, , 1 > . > > > > > > > , , , , , , , , , , i i i i i i , , , , , , , , , , # > , , > - $ q @X:X5X3X-X>X*X>X",
"xXqXG X o o = z & & o > > & & # & # # . # # # # . . # # # # # . . # # - # # # # # # # # # # & & < tXPXPXLXKXKXKXKX8X` ` n v z < < < & m CXKXKXKXKX} , - > > > > > > > > , , , , , , , i , , , , i i i i i i , , i i i i , , * $ $ ; , , > & # 0 {.:X3X-X>X,X*X<X",
"gXgXt.D X 3 7 & & & o & & & # # & > # # # . . . - - - # # # . . . . # # # # # # # # # # # # # & < } FXPXKXLXKXLX} < . a > & < > & > z > m IXKXLXGXb 1 , > < > > , , , , , , , , , , i i i i , , p i p p p p i i p p i , , * $ $ $ , , , & & - t ! 1X3X*X>X*X>X>X",
"gXgXgXt.3 7 7 & & & o & & & # # # - # # # # # . > > > # . . . # . . # # # # # # . # # # # # # & > ` FXKXKXKXLX{ > & 1 1 < a < & 1 o . . a n GXKX8X< , 1 > < > > , , , , , , , , p p p p i , , , i i i p p p i i i i , , * * * $ $ , * * & & - t h +X*X<X*X*X3X-X",
"hX9XhX9Xv 3 o o o o o & & & & & # # # # # # . . > > # # . . . . . . - - # # # # . # # # # # # & , b CXPXLXKX| # & , , . > , > 1 > 2 < < > & ` GXn , > , > > < > , , , , , , , , a a p p p , , , , i i i i i i i i i , , , , * $ , * * * & # # $ q {.+X<X*X>X3X-X",
"hXpXgX0X' 7 X o o o o & & & & & # # # - > # # . > > # # # # . . . # - # # # . . # # # # # # # # & a R.PXKX| , , , # & & & > & > & & & & . z # ` 1 # 1 > > > 1 > < < , , , , , , p p p p p i , , , i i i i i i i i i i i , , , * * * * * & # # $ q ! 1X:X,X<X-X3X",
"fXpXpXc.t.5 o o o o & # # # & & # . > > > > . . # . . . - # # . . . # # # # # # # # # # # # # & # a ` PX} , # # & . > > . > > # > 2 < & a . , 1 a > z , > , < > , , , , , , , , p p p p i , , , i i i i i i i p i i i , i , , * $ $ * * & # # $ t u [.+X,X<X-X3X",
"hXpXpX9Xt.3 7 . o . # # # # # # # # - > > > . . . . . . - - # . . . # . # # # # . # # # # # > , , # z | & , > . # # > # . # . > & & < > # a , 1 , , , , & 1 > > , , , , , , , , p p p p i , , , , , , , , , , i , * , * , , , , $ $ * & & # # # % u _ +X,X-X3X3X",
"fXpXpX9Xt.9 X o o . . # # # & > # . . . . # # - > # . # # # . . . . . . # # # # # # # # # # # > , , a # - 1 . > 1 > > > < . > m 7 z m , , , , < , , , , , , , > , , , , , , i p p i , i p p , , , , , * * , , i , , , , , , , * $ $ $ # & & & - ; u P +X1X5X3X2X",
"fXpXpX9Xt.5 X o o . # # # # # # # - . . # # > > # . . # # # # . . . . . # # # # # # # # # # # & , , 1 > # # . > # # # 8XtXCXCXCXCXCXiX7Xa > , > , , , , , , , > , > > > , , , , , , , i p p < , , , , * * , , i i , , , , , * * $ $ $ # # # # # # % y [.:X<X3X3X",
"fXdXpX9Xt.3 X o o # # - # # # . # - - - # > > > - # . . . # # # # # . . # # # # # # # # # # # # # ; , , & , 1 & 1 , | CXPXKXKXKXKXKXPXIX| 1 > # > > > , , , , > , , > > & > , , , , , p p p , , , , , * * , , , i , , , * * * $ , , ; - # # # # # % % [ +X1X3X3X",
"fXdXpX9Xt.3 o o o # # # # # # . - - - - # > > > # . . . . # - # # # # # # # # # # & # # # # # # # ; ; , > , 1 # > 1 uXIXKXKXLXKXKXKXPXIX} 1 # , > > > , , , > > , , > > , , , , , , , p p i , , , , , * * * , , , , , * * * * $ , # # # # # # # # # % ^ :X1X2X3X",
"fXdX9X9Xt.X o o o # . . # # # # . . . # # # # # - # . . . # # # # - - - # # # # # # # # # # # # , # # , , > < a < { CXLXKXKXKXKXKXKXPXGX( > , ; , , , , , , , < , , , , , , , , , i p p i , & # , , , * $ $ * , , , * * * * * $ # # # # # $ $ # o . $ P NX1X3X2X",
"fXdXpX9X' X o o # # . . # # # & . . # # # # # # > > # . . # # # - - # - # # # # # # # # # # # # # ; , , a 1 > > n tXFXKXKXKXKXKXKXPXGXtX, , a # , , , , , , , < , , , , , , , , p p p i , * # # , , * * $ $ * , , * * * * * * # # # . # # $ * # o . ; u +X1X3X2X",
"fXpXpX9XG X o o # # # # # # # # . . . . - # # # > > - . # . . . . . # # # # # # # # # # # # # # # , , # , , & m 7XCXLXKXKXKXKXKXKXPXCX` , , 1 , , , 1 1 , , , , , < , , , , i i p p i , * $ & & , , , , * $ * , , , * * * * $ # # # . . # $ * & o . # w [.1X<X3X",
"fXpXpX9XD o o & # # # # # # # # . . # @ > @ - . > > - # # . . . . . # # # # # # # # # # # # # & , # , a a # m 7XCXGXKXKXLXKXKXKXPXCXuX< 1 a , i 1 1 1 1 , > > , 1 1 1 < 1 , p a p p < , $ # & * , , , , , ; , , , , , * & & # # # # . # # $ $ # & & # y {.1X<X3X",
"Z.pXpX9XD @ & . > & # . . # # # . . > . 1 @ . @ > . . > . . . . . 2 & . # # > . > > & # # # # & , # a , a , 7XCXGXKXLXKXKXKXKXLXCXVX( z a 1 , i a 1 , , > > > > 1 - 1 , , 1 , , , & = o , # * = < & , $ , , ; , , - ; & & & # # $ # & # # # $ $ ; # $ t [ NX4X3X",
"aXpXpX9X9 X & # & & & o . . # # > . . 1 . . 1 . @ @ o 2 . o > > . . . & , < . 1 & & & # # # # # # , , , i M R.CXFXKXKXKXKXKXPXIXrX} z < < a , 1 1 1 , < , > > > < > < a 1 , a < < = = = . & = 6 7 & a # - % ; , , # # # & & & - # # # & # # $ $ $ $ $ 0 _ NX4X3X",
"fXZ.9X9X5 X & # # # & o . . # # . o 8X8X8X} } | ` ` n m 2 > o . 2 & > & . < . # # & # # # # # # # 1 # , , a ` iXIXPXLXKXKXLXCX7X} n , , , a , , < < < < < > > , , a < < , , < = 6 6 6 c 6 c 6 6 6 6 , ; t : & & = * & & & & . # # # # & # # $ $ $ ; * 0 ^ NX4X4X",
"fXZ.9X9XX o & # # . o o # . # # 7 . 8XxXKXKXLX7X7X7XrXuXiXxXtXrX8X X{ ` & . 1 > # & # # # # # # # , # , , , a 7XVXHXKXLXLXFXrX{ ` i i a i a i , i < p < < , , , * x p p p < 1 ` D ) ) ) ) ) ) ) ) u.2 + + ; o = & & o > * # # - # # # o # # $ $ $ ; * 0 ! NX3X4X",
"dXZ.9Xz.X o & # . # o o & # # # . z 8XCXLXKXCXt.8X7X7XrXtXiXxXxXiXiXtXtX{ > . . & & > # # # # # & # , a i i , | iXFXLXHXZX7X} } n t i i a $ i p i p p p p , , , d r * < z & o 8X5.5.5.5.5.5.5.5.5.vX7 + ; , = = 2 > @ > $ % . . # # # & # # $ $ # ; # $ l +X<X3X",
"pXpXpXt.X o o # # . o o . # # # . v tXxXIXFXCXrX8XtXuXiXiXxXiXxXiXuXtXuX7X` > & & & & & # # - > , # , , i a , m uXVXZXFXtX X8X8Xi t i i a * p p p p p i i 1 , , ; i x = < < 6 0X5.5.5.5.5.5.5.5.5.vXv @ = = 8 3 3 3 X 2 , % # . # # # & & & ; ; ; - , $ h [.1X4X",
"pXpXpX' o o . # & & o o . . # # < D yXCXCXVXFX7X8XuXVXCXCXCXVXCXCXVXxXiXVX7X` . & & & & # & > > , , , , , i a & 8XCXFXxX8XrXVX` , i p , p p p p i a i i , 1 , , 1 > 6 7 { rXu.u.u.) .Xu.u.u.XXu..X7X' t.u.7.O.@.O.7.9.] ( r o o # # & & > > ; ; ; , < , y {.1X4X",
"pXpX9XD o o . & > o o o . . # # & ` XuXFXFX7Xt.8XrXrXrXtXuXuXiXiXuXyXtXuXtX7Xz & & & & # # > > # , # a i * a < ` VXxXrX8X8X7X= < * < * * p , , i a i 1 , , , > @ 5 A ' yXPXvXu..X} uX X.Xu.vX{ BX} ] cXXXh.+.j.+.h.pX9X' = o = & - & > > > ; ; - # , $ y [ 1X4X",
"dX9X9X9 o > # # . . o o . . # # & n { } } } X7X7X7X7XrXtXtXuXuXtXtXtXtXrXrX7Xz # & # & # # # # > , ; ; , , , < & uXyX} ` 8Xv & < * * * * , , , i , , , < < < 7 4 V O.u.vXPXvXu..X{ VX X7Xt.vX} BXR.9.aXg.h.j.#.#.h.g.pX' z & > & > > > > $ ; ; ; > , $ y / 1X4X",
"dXpX9X9 @ > . # # . o o o o & > . & & # . . < z m n n ` ` { } } 8X7X8X7X7X7X7Xz # # # # # # # # > > , , , * , * < ' 8X` D { & = , , , , , , , , ; ; , < = 6 6 8 Z *.8.8.5.h.5.u. XrX8X X X7Xu.t.8X} ' 8.5.j.#.#.#.#.+.r.A & . > # > > > , ; ; ; ; > > , y ^ NXNX",
"pXpXz.5 @ > . - . . o o o . # # # # . # & & # # & & & # . . # & & & 2 z m n n & # . # # # # # > > & # # $ * * & = 7 ` D ( c & & # - - - , , 1 1 1 1 , 2 6 c A F 0.G.G.D.+.j.5.XXrXVX` 7XiXGX( { uXrXu.5.j.#.#.#.#.#.+.h.c * # > # & > > , , ; ; ; - > i t h NX4X",
"pXpXt.X @ > . > . . . o . . # # . . . # # - # # # # # # # # # & & . o & . . # & # # . # # # # & > & # # $ $ # & & & 7 ( D . < & # - - - - ; 1 1 1 1 , = 6 c D F e.K.A.B.+.+.5.u.rXVX| 7XrXCX' t.uX.Xu.5.j.+.+.+.+.+.+.y.v ; . . # , > > , ; ; ; ; > > i t u NX4X",
"pX9X] X o & . # # . o o o . # # - > > . . . . # . . # # # # # # o o 2 . # < & . # # # # # # # # & & # # # # # # & & > m < > # , - - - - - - , , 1 , > 2 7 c A A F H F D A A A A ( ( a M b ` c v n m c 4 N N 4 4 N 4 N N O . - , ; , > & & $ ; ; ; , , , t u NX1X",
"pX9X' X o & . . . . o o o o . # # + - > > > > # # > > > > > > & > & < . . & . # # # # # # # # # & & & # # # # - > & > & . < # - - - - - > , < 1 , ; - > < 7 6 6 6 7 o 7 6 6 7 z z < i a z < , 1 < < 6 c o 2 @ @ 7 3 3 X = . - - ; , * & * $ ; ; ; , , * t u @X1X",
"pX9XD X o . . . . . . . . . . . > . . . . . # & # & # # . . . . . . > & , . . > # # # # # # # # & # # # # - - , & , > > # - , # - - , * < < p < , ; ; - , > < < p < z z a < = 7 z z a , p x d - , < < z < , , p = < z = = o . # , * * * * $ ; ; ; - , # t u {.NX",
"pXc.D X o . # # . . o o o o # & # . @ > - > & & & & & & & & & & > . > . > # , # # # # . # # # # & . # # # ; ; , , . # > . - , , , , , < z 6 = * # ; : : : , 1 1 i i p p ; a 7 7 < < < z z = i i , 1 < & , p i r r r * * o & & > $ * , , * , ; ; ; # , # i q _ NX"
};

11174
Tests/images/hopper_rgb.xpm Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
Tests/images/op_index.qoi Normal file

Binary file not shown.

BIN
Tests/images/p_4_planes.pcx Normal file

Binary file not shown.

View File

@ -14,3 +14,23 @@
fun:_TIFFReadEncodedTileAndAllocBuffer fun:_TIFFReadEncodedTileAndAllocBuffer
... ...
} }
{
<python_alloc_possible_leak>
Memcheck:Leak
match-leak-kinds: all
fun:malloc
fun:_PyMem_RawMalloc
fun:PyObject_Malloc
...
}
{
<python_realloc_possible_leak>
Memcheck:Leak
match-leak-kinds: all
fun:malloc
fun:_PyMem_RawRealloc
fun:PyMem_Realloc
...
}

View File

@ -47,7 +47,6 @@ def test_unknown_version() -> None:
], ],
) )
def test_old_version(deprecated: str, plural: bool, expected: str) -> None: def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
expected = r""
with pytest.raises(RuntimeError, match=expected): with pytest.raises(RuntimeError, match=expected):
_deprecate.deprecate(deprecated, 1, plural=plural) _deprecate.deprecate(deprecated, 1, plural=plural)

View File

@ -254,7 +254,9 @@ class TestFileAvif:
assert_image(im, "RGBA", (64, 64)) assert_image(im, "RGBA", (64, 64))
# image has 876 transparent pixels # image has 876 transparent pixels
assert im.getchannel("A").getcolors()[0] == (876, 0) colors = im.getchannel("A").getcolors()
assert colors is not None
assert colors[0] == (876, 0)
def test_save_transparent(self, tmp_path: Path) -> None: def test_save_transparent(self, tmp_path: Path) -> None:
im = Image.new("RGBA", (10, 10), (0, 0, 0, 0)) im = Image.new("RGBA", (10, 10), (0, 0, 0, 0))

View File

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

View File

@ -511,3 +511,20 @@ def test_save_dx10_bc5(tmp_path: Path) -> None:
im = hopper("L") im = hopper("L")
with pytest.raises(OSError, match="only RGB mode can be written as BC5"): with pytest.raises(OSError, match="only RGB mode can be written as BC5"):
im.save(out, pixel_format="BC5") im.save(out, pixel_format="BC5")
@pytest.mark.parametrize(
"pixel_format, mode",
(
("DXT1", "RGBA"),
("DXT3", "RGBA"),
("DXT5", "RGBA"),
("BC2", "RGBA"),
("BC3", "RGBA"),
("BC5", "RGB"),
),
)
def test_save_large_file(tmp_path: Path, pixel_format: str, mode: str) -> None:
im = hopper(mode).resize((440, 440))
# should not error in valgrind
im.save(tmp_path / "img.dds", pixel_format=pixel_format)

View File

@ -15,6 +15,7 @@ from .helper import (
is_win32, is_win32,
mark_if_feature_version, mark_if_feature_version,
skip_unless_feature, skip_unless_feature,
timeout_unless_slower_valgrind,
) )
HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript() HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript()
@ -398,7 +399,7 @@ def test_emptyline() -> None:
assert image.format == "EPS" assert image.format == "EPS"
@pytest.mark.timeout(timeout=5) @timeout_unless_slower_valgrind(5)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"test_file", "test_file",
["Tests/images/eps/timeout-d675703545fee17acab56e5fec644c19979175de.eps"], ["Tests/images/eps/timeout-d675703545fee17acab56e5fec644c19979175de.eps"],

View File

@ -7,7 +7,12 @@ import pytest
from PIL import FliImagePlugin, Image, ImageFile from PIL import FliImagePlugin, Image, ImageFile
from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy from .helper import (
assert_image_equal,
assert_image_equal_tofile,
is_pypy,
timeout_unless_slower_valgrind,
)
# created as an export of a palette image from Gimp2.6 # created as an export of a palette image from Gimp2.6
# save as...-> hopper.fli, default options. # save as...-> hopper.fli, default options.
@ -189,7 +194,7 @@ def test_seek() -> None:
"Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli", "Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli",
], ],
) )
@pytest.mark.timeout(timeout=3) @timeout_unless_slower_valgrind(3)
def test_timeouts(test_file: str) -> None: def test_timeouts(test_file: str) -> None:
with open(test_file, "rb") as f: with open(test_file, "rb") as f:
with Image.open(f) as im: with Image.open(f) as im:

View File

@ -224,6 +224,7 @@ def test_optimize_if_palette_can_be_reduced_by_half() -> None:
out = BytesIO() out = BytesIO()
im.save(out, "GIF", optimize=optimize) im.save(out, "GIF", optimize=optimize)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
assert reloaded.palette is not None
assert len(reloaded.palette.palette) // 3 == colors assert len(reloaded.palette.palette) // 3 == colors
@ -540,7 +541,9 @@ def test_dispose_background_transparency() -> None:
img.seek(2) img.seek(2)
px = img.load() px = img.load()
assert px is not None assert px is not None
assert px[35, 30][3] == 0 value = px[35, 30]
assert isinstance(value, tuple)
assert value[3] == 0
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -1359,6 +1362,7 @@ def test_palette_save_all_P(tmp_path: Path) -> None:
# Assert that the frames are correct, and each frame has the same palette # Assert that the frames are correct, and each frame has the same palette
assert_image_equal(im.convert("RGB"), frames[0].convert("RGB")) assert_image_equal(im.convert("RGB"), frames[0].convert("RGB"))
assert im.palette is not None assert im.palette is not None
assert im.global_palette is not None
assert im.palette.palette == im.global_palette.palette assert im.palette.palette == im.global_palette.palette
im.seek(1) im.seek(1)
@ -1422,7 +1426,9 @@ def test_getdata(monkeypatch: pytest.MonkeyPatch) -> None:
def test_lzw_bits() -> None: def test_lzw_bits() -> None:
# see https://github.com/python-pillow/Pillow/issues/2811 # see https://github.com/python-pillow/Pillow/issues/2811
with Image.open("Tests/images/issue_2811.gif") as im: with Image.open("Tests/images/issue_2811.gif") as im:
assert im.tile[0][3][0] == 11 # LZW bits args = im.tile[0][3]
assert isinstance(args, tuple)
assert args[0] == 11 # LZW bits
# codec error prepatch # codec error prepatch
im.load() im.load()
@ -1477,7 +1483,11 @@ def test_saving_rgba(tmp_path: Path) -> None:
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
reloaded_rgba = reloaded.convert("RGBA") reloaded_rgba = reloaded.convert("RGBA")
assert reloaded_rgba.load()[0, 0][3] == 0 px = reloaded_rgba.load()
assert px is not None
value = px[0, 0]
assert isinstance(value, tuple)
assert value[3] == 0
@pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False})) @pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False}))

View File

@ -99,6 +99,7 @@ def test_getpixel(tmp_path: Path) -> None:
reloaded.load() reloaded.load()
reloaded.size = (32, 32) reloaded.size = (32, 32)
assert reloaded.load() is not None
assert reloaded.getpixel((0, 0)) == (18, 20, 62) assert reloaded.getpixel((0, 0)) == (18, 20, 62)

View File

@ -23,6 +23,9 @@ def test_open() -> None:
assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")] assert im.tile == [("iptc", (0, 0, 1, 1), 25, "raw")]
assert_image_equal(im, expected) assert_image_equal(im, expected)
with Image.open(f) as im:
assert im.load() is not None
def test_getiptcinfo_jpg_none() -> None: def test_getiptcinfo_jpg_none() -> None:
# Arrange # Arrange

View File

@ -32,6 +32,7 @@ from .helper import (
is_win32, is_win32,
mark_if_feature_version, mark_if_feature_version,
skip_unless_feature, skip_unless_feature,
timeout_unless_slower_valgrind,
) )
ElementTree: ModuleType | None ElementTree: ModuleType | None
@ -129,30 +130,26 @@ class TestFileJpeg:
def test_cmyk(self) -> None: def test_cmyk(self) -> None:
# Test CMYK handling. Thanks to Tim and Charlie for test data, # Test CMYK handling. Thanks to Tim and Charlie for test data,
# Michael for getting me to look one more time. # Michael for getting me to look one more time.
f = "Tests/images/pil_sample_cmyk.jpg" def check(im: ImageFile.ImageFile) -> None:
with Image.open(f) as im: cmyk = im.getpixel((0, 0))
# the source image has red pixels in the upper left corner. assert isinstance(cmyk, tuple)
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0))) c, m, y, k = (x / 255.0 for x in cmyk)
assert c == 0.0 assert c == 0.0
assert m > 0.8 assert m > 0.8
assert y > 0.8 assert y > 0.8
assert k == 0.0 assert k == 0.0
# the opposite corner is black # the opposite corner is black
c, m, y, k = ( cmyk = im.getpixel((im.size[0] - 1, im.size[1] - 1))
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1)) assert isinstance(cmyk, tuple)
) k = cmyk[3] / 255.0
assert k > 0.9 assert k > 0.9
with Image.open("Tests/images/pil_sample_cmyk.jpg") as im:
# the source image has red pixels in the upper left corner.
check(im)
# roundtrip, and check again # roundtrip, and check again
im = self.roundtrip(im) check(self.roundtrip(im))
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))
assert c == 0.0
assert m > 0.8
assert y > 0.8
assert k == 0.0
c, m, y, k = (
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))
)
assert k > 0.9
def test_rgb(self) -> None: def test_rgb(self) -> None:
def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, ...]: def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, ...]:
@ -1033,7 +1030,7 @@ class TestFileJpeg:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.save(f, xmp=b"1" * 65505) im.save(f, xmp=b"1" * 65505)
@pytest.mark.timeout(timeout=1) @timeout_unless_slower_valgrind(1)
def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None: def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Even though this decoder never says that it is finished # Even though this decoder never says that it is finished
# the image should still end when there is no new data # the image should still end when there is no new data
@ -1064,10 +1061,16 @@ class TestFileJpeg:
for marker in b"\xff\xd8", b"\xff\xd9": for marker in b"\xff\xd8", b"\xff\xd9":
assert marker in data[1] assert marker in data[1]
assert marker in data[2] assert marker in data[2]
# DHT, DQT
for marker in b"\xff\xc4", b"\xff\xdb": # DQT
markers = [b"\xff\xdb"]
if features.check_feature("libjpeg_turbo"):
# DHT
markers.append(b"\xff\xc4")
for marker in markers:
assert marker in data[1] assert marker in data[1]
assert marker not in data[2] assert marker not in data[2]
# SOF0, SOS, APP0 (JFIF header) # SOF0, SOS, APP0 (JFIF header)
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0": for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
assert marker not in data[1] assert marker not in data[1]

View File

@ -156,6 +156,7 @@ def test_reload_exif_after_seek() -> None:
def test_mp(test_file: str) -> None: def test_mp(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None
assert mpinfo[45056] == b"0100" assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2 assert mpinfo[45057] == 2
@ -165,6 +166,7 @@ def test_mp_offset() -> None:
# in APP2 data, in contrast to normal 8 # in APP2 data, in contrast to normal 8
with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im: with Image.open("Tests/images/sugarshack_ifd_offset.mpo") as im:
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None
assert mpinfo[45056] == b"0100" assert mpinfo[45056] == b"0100"
assert mpinfo[45057] == 2 assert mpinfo[45057] == 2
@ -181,6 +183,7 @@ def test_mp_no_data() -> None:
def test_mp_attribute(test_file: str) -> None: def test_mp_attribute(test_file: str) -> None:
with Image.open(test_file) as im: with Image.open(test_file) as im:
mpinfo = im._getmp() mpinfo = im._getmp()
assert mpinfo is not None
for frame_number, mpentry in enumerate(mpinfo[0xB002]): for frame_number, mpentry in enumerate(mpinfo[0xB002]):
mpattr = mpentry["Attribute"] mpattr = mpentry["Attribute"]
if frame_number: if frame_number:
@ -317,6 +320,9 @@ def test_save_xmp() -> None:
im2.encoderinfo = {"xmp": b"Second frame"} im2.encoderinfo = {"xmp": b"Second frame"}
im_reloaded = roundtrip(im, xmp=b"First frame", save_all=True, append_images=[im2]) im_reloaded = roundtrip(im, xmp=b"First frame", save_all=True, append_images=[im2])
# Test that encoderinfo is unchanged
assert im2.encoderinfo == {"xmp": b"Second frame"}
assert im_reloaded.info["xmp"] == b"First frame" assert im_reloaded.info["xmp"] == b"First frame"
im_reloaded.seek(1) im_reloaded.seek(1)

View File

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

View File

@ -13,7 +13,12 @@ import pytest
from PIL import Image, PdfParser, features from PIL import Image, PdfParser, features
from .helper import hopper, mark_if_feature_version, skip_unless_feature from .helper import (
hopper,
mark_if_feature_version,
skip_unless_feature,
timeout_unless_slower_valgrind,
)
def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str: def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str:
@ -339,8 +344,7 @@ def test_pdf_append_to_bytesio() -> None:
assert len(f.getvalue()) > initial_size assert len(f.getvalue()) > initial_size
@pytest.mark.timeout(1) @timeout_unless_slower_valgrind(1)
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
@pytest.mark.parametrize("newline", (b"\r", b"\n")) @pytest.mark.parametrize("newline", (b"\r", b"\n"))
def test_redos(newline: bytes) -> None: def test_redos(newline: bytes) -> None:
malicious = b" trailer<<>>" + newline * 3456 malicious = b" trailer<<>>" + newline * 3456

View File

@ -100,11 +100,11 @@ class TestFilePng:
assert im.format == "PNG" assert im.format == "PNG"
assert im.get_format_mimetype() == "image/png" assert im.get_format_mimetype() == "image/png"
for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]: for mode in ["1", "L", "P", "RGB", "I;16", "I;16B"]:
im = hopper(mode) im = hopper(mode)
im.save(test_file) im.save(test_file)
with Image.open(test_file) as reloaded: with Image.open(test_file) as reloaded:
if mode in ("I", "I;16B"): if mode == "I;16B":
reloaded = reloaded.convert(mode) reloaded = reloaded.convert(mode)
assert_image_equal(reloaded, im) assert_image_equal(reloaded, im)
@ -801,6 +801,16 @@ class TestFilePng:
with Image.open("Tests/images/truncated_end_chunk.png") as im: with Image.open("Tests/images/truncated_end_chunk.png") as im:
assert_image_equal_tofile(im, "Tests/images/hopper.png") assert_image_equal_tofile(im, "Tests/images/hopper.png")
def test_deprecation(self, tmp_path: Path) -> None:
test_file = tmp_path / "out.png"
im = hopper("I")
with pytest.warns(DeprecationWarning):
im.save(test_file)
with Image.open(test_file) as reloaded:
assert_image_equal(im, reloaded.convert("I"))
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS") @pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
@skip_unless_feature("zlib") @skip_unless_feature("zlib")

View File

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

View File

@ -1,10 +1,12 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
import pytest import pytest
from PIL import Image, QoiImagePlugin from PIL import Image, QoiImagePlugin
from .helper import assert_image_equal_tofile from .helper import assert_image_equal_tofile, hopper
def test_sanity() -> None: def test_sanity() -> None:
@ -28,3 +30,28 @@ def test_invalid_file() -> None:
with pytest.raises(SyntaxError): with pytest.raises(SyntaxError):
QoiImagePlugin.QoiImageFile(invalid_file) QoiImagePlugin.QoiImageFile(invalid_file)
def test_op_index() -> None:
# QOI_OP_INDEX as the first chunk
with Image.open("Tests/images/op_index.qoi") as im:
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
def test_save(tmp_path: Path) -> None:
f = tmp_path / "temp.qoi"
im = hopper()
im.save(f, colorspace="sRGB")
assert_image_equal_tofile(im, f)
for path in ("Tests/images/default_font.png", "Tests/images/pil123rgba.png"):
with Image.open(path) as im:
im.save(f)
assert_image_equal_tofile(im, f)
im = hopper("P")
with pytest.raises(ValueError, match="Unsupported QOI image mode"):
im.save(f)

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
from io import BytesIO
from pathlib import Path from pathlib import Path
import pytest import pytest
@ -71,6 +72,15 @@ def test_invalid_file() -> None:
SgiImagePlugin.SgiImageFile(invalid_file) SgiImagePlugin.SgiImageFile(invalid_file)
def test_unsupported_image_mode() -> None:
with open("Tests/images/hopper.rgb", "rb") as fp:
data = fp.read()
data = data[:3] + b"\x03" + data[4:]
with pytest.raises(ValueError, match="Unsupported SGI image mode"):
with Image.open(BytesIO(data)):
pass
def roundtrip(img: Image.Image, tmp_path: Path) -> None: def roundtrip(img: Image.Image, tmp_path: Path) -> None:
out = tmp_path / "temp.sgi" out = tmp_path / "temp.sgi"
img.save(out, format="sgi") img.save(out, format="sgi")
@ -109,3 +119,11 @@ def test_unsupported_mode(tmp_path: Path) -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
im.save(out, format="sgi") im.save(out, format="sgi")
def test_unsupported_number_of_bytes_per_pixel(tmp_path: Path) -> None:
im = hopper()
out = tmp_path / "temp.sgi"
with pytest.raises(ValueError, match="Unsupported number of bytes per pixel"):
im.save(out, bpc=3)

View File

@ -220,12 +220,16 @@ def test_horizontal_orientations() -> None:
with Image.open("Tests/images/rgb32rle_top_right.tga") as im: with Image.open("Tests/images/rgb32rle_top_right.tga") as im:
px = im.load() px = im.load()
assert px is not None assert px is not None
assert px[90, 90][:3] == (0, 0, 0) value = px[90, 90]
assert isinstance(value, tuple)
assert value[:3] == (0, 0, 0)
with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im: with Image.open("Tests/images/rgb32rle_bottom_right.tga") as im:
px = im.load() px = im.load()
assert px is not None assert px is not None
assert px[90, 90][:3] == (0, 255, 0) value = px[90, 90]
assert isinstance(value, tuple)
assert value[:3] == (0, 255, 0)
def test_save_rle(tmp_path: Path) -> None: def test_save_rle(tmp_path: Path) -> None:

View File

@ -14,6 +14,7 @@ from PIL import (
ImageFile, ImageFile,
JpegImagePlugin, JpegImagePlugin,
TiffImagePlugin, TiffImagePlugin,
TiffTags,
UnidentifiedImageError, UnidentifiedImageError,
) )
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
@ -26,6 +27,7 @@ from .helper import (
hopper, hopper,
is_pypy, is_pypy,
is_win32, is_win32,
timeout_unless_slower_valgrind,
) )
ElementTree: ModuleType | None ElementTree: ModuleType | None
@ -47,25 +49,10 @@ class TestFileTiff:
assert im.size == (128, 128) assert im.size == (128, 128)
assert im.format == "TIFF" assert im.format == "TIFF"
hopper("1").save(filename) for mode in ("1", "L", "P", "RGB", "I", "I;16", "I;16L"):
with Image.open(filename): hopper(mode).save(filename)
pass with Image.open(filename):
pass
hopper("L").save(filename)
with Image.open(filename):
pass
hopper("P").save(filename)
with Image.open(filename):
pass
hopper("RGB").save(filename)
with Image.open(filename):
pass
hopper("I").save(filename)
with Image.open(filename):
pass
@pytest.mark.skipif(is_pypy(), reason="Requires CPython") @pytest.mark.skipif(is_pypy(), reason="Requires CPython")
def test_unclosed_file(self) -> None: def test_unclosed_file(self) -> None:
@ -899,6 +886,29 @@ class TestFileTiff:
assert description[0]["format"] == "image/tiff" assert description[0]["format"] == "image/tiff"
assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"] assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
def test_getxmp_undefined(self, tmp_path: Path) -> None:
tmpfile = tmp_path / "temp.tif"
im = Image.new("L", (1, 1))
ifd = TiffImagePlugin.ImageFileDirectory_v2()
ifd.tagtype[700] = TiffTags.UNDEFINED
with Image.open("Tests/images/lab.tif") as im_xmp:
ifd[700] = im_xmp.info["xmp"]
im.save(tmpfile, tiffinfo=ifd)
with Image.open(tmpfile) as im_reloaded:
if ElementTree is None:
with pytest.warns(
UserWarning,
match="XMP data cannot be read without defusedxml dependency",
):
assert im_reloaded.getxmp() == {}
else:
assert "xmp" in im_reloaded.info
xmp = im_reloaded.getxmp()
description = xmp["xmpmeta"]["RDF"]["Description"]
assert description[0]["format"] == "image/tiff"
def test_get_photoshop_blocks(self) -> None: def test_get_photoshop_blocks(self) -> None:
with Image.open("Tests/images/lab.tif") as im: with Image.open("Tests/images/lab.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile) assert isinstance(im, TiffImagePlugin.TiffImageFile)
@ -988,7 +998,7 @@ class TestFileTiff:
with pytest.raises(OSError): with pytest.raises(OSError):
im.load() im.load()
@pytest.mark.timeout(6) @timeout_unless_slower_valgrind(6)
@pytest.mark.filterwarnings("ignore:Truncated File Read") @pytest.mark.filterwarnings("ignore:Truncated File Read")
def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None: def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None:
with Image.open("Tests/images/timeout-6646305047838720") as im: with Image.open("Tests/images/timeout-6646305047838720") as im:
@ -1001,7 +1011,7 @@ class TestFileTiff:
"Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif", "Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif",
], ],
) )
@pytest.mark.timeout(2) @timeout_unless_slower_valgrind(2)
def test_oom(self, test_file: str) -> None: def test_oom(self, test_file: str) -> None:
with pytest.raises(UnidentifiedImageError): with pytest.raises(UnidentifiedImageError):
with pytest.warns(UserWarning): with pytest.warns(UserWarning):

View File

@ -219,6 +219,7 @@ class TestFileWebp:
# Save P mode GIF with background # Save P mode GIF with background
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
original_value = im.convert("RGB").getpixel((1, 1)) original_value = im.convert("RGB").getpixel((1, 1))
assert isinstance(original_value, tuple)
# Save as WEBP # Save as WEBP
im.save(out_webp, save_all=True) im.save(out_webp, save_all=True)
@ -230,6 +231,7 @@ class TestFileWebp:
with Image.open(out_gif) as reread: with Image.open(out_gif) as reread:
reread_value = reread.convert("RGB").getpixel((1, 1)) reread_value = reread.convert("RGB").getpixel((1, 1))
assert isinstance(reread_value, tuple)
difference = sum(abs(original_value[i] - reread_value[i]) for i in range(3)) difference = sum(abs(original_value[i] - reread_value[i]) for i in range(3))
assert difference < 5 assert difference < 5

View File

@ -1,5 +1,7 @@
from __future__ import annotations from __future__ import annotations
from io import BytesIO
import pytest import pytest
from PIL import Image, XpmImagePlugin from PIL import Image, XpmImagePlugin
@ -17,7 +19,45 @@ def test_sanity() -> None:
assert im.format == "XPM" assert im.format == "XPM"
# large error due to quantization->44 colors. # large error due to quantization->44 colors.
assert_image_similar(im.convert("RGB"), hopper("RGB"), 60) assert_image_similar(im.convert("RGB"), hopper(), 23)
def test_bpp2() -> None:
with Image.open("Tests/images/hopper_bpp2.xpm") as im:
assert_image_similar(im.convert("RGB"), hopper(), 11)
def test_rgb() -> None:
with Image.open("Tests/images/hopper_rgb.xpm") as im:
assert im.mode == "RGB"
assert_image_similar(im, hopper(), 16)
def test_truncated_header() -> None:
data = b"/* XPM */"
with pytest.raises(SyntaxError, match="broken XPM file"):
with XpmImagePlugin.XpmImageFile(BytesIO(data)):
pass
def test_cannot_read_color() -> None:
with open(TEST_FILE, "rb") as fp:
data = fp.read().split(b"#")[0]
with pytest.raises(ValueError, match="cannot read this XPM file"):
with Image.open(BytesIO(data)):
pass
with pytest.raises(ValueError, match="cannot read this XPM file"):
with Image.open(BytesIO(data + b"invalid")):
pass
def test_not_enough_image_data() -> None:
with open(TEST_FILE, "rb") as fp:
data = fp.read().split(b"/* pixels */")[0]
with Image.open(BytesIO(data)) as im:
with pytest.raises(ValueError, match="not enough image data"):
im.load()
def test_invalid_file() -> None: def test_invalid_file() -> None:

View File

@ -34,6 +34,7 @@ from .helper import (
is_win32, is_win32,
mark_if_feature_version, mark_if_feature_version,
skip_unless_feature, skip_unless_feature,
timeout_unless_slower_valgrind,
) )
ElementTree: ModuleType | None ElementTree: ModuleType | None
@ -572,10 +573,7 @@ class TestImage:
i = Image.new("RGB", [1, 1]) i = Image.new("RGB", [1, 1])
assert isinstance(i.size, tuple) assert isinstance(i.size, tuple)
@pytest.mark.timeout(0.75) @timeout_unless_slower_valgrind(0.75)
@pytest.mark.skipif(
"PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower"
)
@pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0))) @pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
def test_empty_image(self, size: tuple[int, int]) -> None: def test_empty_image(self, size: tuple[int, int]) -> None:
Image.new("RGB", size) Image.new("RGB", size)
@ -673,6 +671,7 @@ class TestImage:
im_remapped = im.remap_palette(list(range(256))) im_remapped = im.remap_palette(list(range(256)))
assert_image_equal(im, im_remapped) assert_image_equal(im, im_remapped)
assert im.palette is not None assert im.palette is not None
assert im_remapped.palette is not None
assert im.palette.palette == im_remapped.palette.palette assert im.palette.palette == im_remapped.palette.palette
# Test illegal image mode # Test illegal image mode
@ -975,6 +974,11 @@ class TestImage:
assert tag not in exif.get_ifd(0x8769) assert tag not in exif.get_ifd(0x8769)
assert exif.get_ifd(0xA005) assert exif.get_ifd(0xA005)
def test_exif_from_xmp_bytes(self) -> None:
im = Image.new("RGB", (1, 1))
im.info["xmp"] = b'\xff tiff:Orientation="2"'
assert im.getexif()[274] == 2
def test_empty_xmp(self) -> None: def test_empty_xmp(self) -> None:
with Image.open("Tests/images/hopper.gif") as im: with Image.open("Tests/images/hopper.gif") as im:
if ElementTree is None: if ElementTree is None:
@ -991,7 +995,7 @@ class TestImage:
im = Image.new("RGB", (1, 1)) im = Image.new("RGB", (1, 1))
im.info["xmp"] = ( im.info["xmp"] = (
b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n' b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00' b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00 '
) )
if ElementTree is None: if ElementTree is None:
with pytest.warns( with pytest.warns(

View File

@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Any from typing import Any
import pytest import pytest
from packaging.version import parse as parse_version from packaging.version import parse as parse_version
@ -13,6 +13,7 @@ numpy = pytest.importorskip("numpy", reason="NumPy not installed")
im = hopper().resize((128, 100)) im = hopper().resize((128, 100))
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
import numpy.typing as npt import numpy.typing as npt
@ -101,7 +102,8 @@ def test_fromarray_strides_without_tobytes() -> None:
with pytest.raises(ValueError): with pytest.raises(ValueError):
wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)}) wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)})
Image.fromarray(wrapped, "L") with pytest.warns(DeprecationWarning):
Image.fromarray(wrapped, "L")
def test_fromarray_palette() -> None: def test_fromarray_palette() -> None:
@ -110,7 +112,8 @@ def test_fromarray_palette() -> None:
a = numpy.array(i) a = numpy.array(i)
# Act # Act
out = Image.fromarray(a, "P") with pytest.warns(DeprecationWarning):
out = Image.fromarray(a, "P")
# Assert that the Python and C palettes match # Assert that the Python and C palettes match
assert out.palette is not None assert out.palette is not None

View File

@ -70,6 +70,7 @@ def test_quantize_no_dither() -> None:
converted = image.quantize(dither=Image.Dither.NONE, palette=palette) converted = image.quantize(dither=Image.Dither.NONE, palette=palette)
assert converted.mode == "P" assert converted.mode == "P"
assert converted.palette is not None assert converted.palette is not None
assert palette.palette is not None
assert converted.palette.palette == palette.palette.palette assert converted.palette.palette == palette.palette.palette

View File

@ -48,6 +48,7 @@ class TestImageTransform:
im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0] im.size, Image.Transform.AFFINE, [1, 0, 0, 0, 1, 0]
) )
assert im.palette is not None assert im.palette is not None
assert transformed.palette is not None
assert im.palette.palette == transformed.palette.palette assert im.palette.palette == transformed.palette.palette
def test_extent(self) -> None: def test_extent(self) -> None:

View File

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

View File

@ -267,6 +267,23 @@ def test_render_multiline_text_align(
assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01) assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01)
def test_render_multiline_text_justify_anchor(
font: ImageFont.FreeTypeFont,
) -> None:
im = Image.new("RGB", (280, 240))
draw = ImageDraw.Draw(im)
for xy, anchor in (((0, 0), "la"), ((140, 80), "ma"), ((280, 160), "ra")):
draw.multiline_text(
xy,
"hey you you are awesome\nthis looks awkward\nthis\nlooks awkward",
font=font,
anchor=anchor,
align="justify",
)
assert_image_equal_tofile(im, "Tests/images/multiline_text_justify_anchor.png")
def test_unknown_align(font: ImageFont.FreeTypeFont) -> None: def test_unknown_align(font: ImageFont.FreeTypeFont) -> None:
im = Image.new(mode="RGB", size=(300, 100)) im = Image.new(mode="RGB", size=(300, 100))
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)

View File

@ -7,7 +7,7 @@ import pytest
from PIL import Image, ImageDraw, ImageFont, _util, features from PIL import Image, ImageDraw, ImageFont, _util, features
from .helper import assert_image_equal_tofile from .helper import assert_image_equal_tofile, timeout_unless_slower_valgrind
fonts = [ImageFont.load_default_imagefont()] fonts = [ImageFont.load_default_imagefont()]
if not features.check_module("freetype2"): if not features.check_module("freetype2"):
@ -72,7 +72,7 @@ def test_decompression_bomb() -> None:
font.getmask("A" * 1_000_000) font.getmask("A" * 1_000_000)
@pytest.mark.timeout(4) @timeout_unless_slower_valgrind(4)
def test_oom() -> None: def test_oom() -> None:
glyph = struct.pack( glyph = struct.pack(
">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767 ">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767

View File

@ -1,7 +1,6 @@
from __future__ import annotations from __future__ import annotations
import warnings import warnings
from typing import TYPE_CHECKING
import pytest import pytest
@ -9,6 +8,7 @@ from PIL import Image, _typing
from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature from .helper import assert_deep_equal, assert_image, hopper, skip_unless_feature
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
import numpy import numpy
import numpy.typing as npt import numpy.typing as npt

View File

@ -1,6 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import Any # undone from typing import Any, NamedTuple
import pytest import pytest
@ -10,30 +10,73 @@ from .helper import (
assert_deep_equal, assert_deep_equal,
assert_image_equal, assert_image_equal,
hopper, hopper,
is_big_endian,
) )
pyarrow = pytest.importorskip("pyarrow", reason="PyArrow not installed") TYPE_CHECKING = False
if TYPE_CHECKING:
import pyarrow
else:
pyarrow = pytest.importorskip("pyarrow", reason="PyArrow not installed")
TEST_IMAGE_SIZE = (10, 10) TEST_IMAGE_SIZE = (10, 10)
def _test_img_equals_pyarray( def _test_img_equals_pyarray(
img: Image.Image, arr: Any, mask: list[int] | None img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
) -> None: ) -> None:
assert img.height * img.width == len(arr) assert img.height * img.width * elts_per_pixel == len(arr)
px = img.load() px = img.load()
assert px is not None assert px is not None
if elts_per_pixel > 1 and mask is None:
# have to do element-wise comparison when we're comparing
# flattened r,g,b,a to a pixel.
mask = list(range(elts_per_pixel))
for x in range(0, img.size[0], int(img.size[0] / 10)): for x in range(0, img.size[0], int(img.size[0] / 10)):
for y in range(0, img.size[1], int(img.size[1] / 10)): for y in range(0, img.size[1], int(img.size[1] / 10)):
if mask: if mask:
pixel = px[x, y]
assert isinstance(pixel, tuple)
for ix, elt in enumerate(mask): for ix, elt in enumerate(mask):
pixel = px[x, y] if elts_per_pixel == 1:
assert isinstance(pixel, tuple) assert pixel[ix] == arr[y * img.width + x].as_py()[elt]
assert pixel[ix] == arr[y * img.width + x].as_py()[elt] else:
assert (
pixel[ix]
== arr[(y * img.width + x) * elts_per_pixel + elt].as_py()
)
else: else:
assert_deep_equal(px[x, y], arr[y * img.width + x].as_py()) assert_deep_equal(px[x, y], arr[y * img.width + x].as_py())
def _test_img_equals_int32_pyarray(
img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
) -> None:
assert img.height * img.width * elts_per_pixel == len(arr)
px = img.load()
assert px is not None
if mask is None:
# have to do element-wise comparison when we're comparing
# flattened rgba in an uint32 to a pixel.
mask = list(range(elts_per_pixel))
for x in range(0, img.size[0], int(img.size[0] / 10)):
for y in range(0, img.size[1], int(img.size[1] / 10)):
pixel = px[x, y]
assert isinstance(pixel, tuple)
arr_pixel_int = arr[y * img.width + x].as_py()
arr_pixel_tuple = (
arr_pixel_int % 256,
(arr_pixel_int // 256) % 256,
(arr_pixel_int // 256**2) % 256,
(arr_pixel_int // 256**3),
)
if is_big_endian():
arr_pixel_tuple = arr_pixel_tuple[::-1]
for ix, elt in enumerate(mask):
assert pixel[ix] == arr_pixel_tuple[elt]
# really hard to get a non-nullable list type # really hard to get a non-nullable list type
fl_uint8_4_type = pyarrow.field( fl_uint8_4_type = pyarrow.field(
"_", pyarrow.list_(pyarrow.field("_", pyarrow.uint8()).with_nullable(False), 4) "_", pyarrow.list_(pyarrow.field("_", pyarrow.uint8()).with_nullable(False), 4)
@ -55,14 +98,14 @@ fl_uint8_4_type = pyarrow.field(
("HSV", fl_uint8_4_type, [0, 1, 2]), ("HSV", fl_uint8_4_type, [0, 1, 2]),
), ),
) )
def test_to_array(mode: str, dtype: Any, mask: list[int] | None) -> None: def test_to_array(mode: str, dtype: pyarrow.DataType, mask: list[int] | None) -> None:
img = hopper(mode) img = hopper(mode)
# Resize to non-square # Resize to non-square
img = img.crop((3, 0, 124, 127)) img = img.crop((3, 0, 124, 127))
assert img.size == (121, 127) assert img.size == (121, 127)
arr = pyarrow.array(img) arr = pyarrow.array(img) # type: ignore[call-overload]
_test_img_equals_pyarray(img, arr, mask) _test_img_equals_pyarray(img, arr, mask)
assert arr.type == dtype assert arr.type == dtype
@ -79,8 +122,8 @@ def test_lifetime() -> None:
img = hopper("L") img = hopper("L")
arr_1 = pyarrow.array(img) arr_1 = pyarrow.array(img) # type: ignore[call-overload]
arr_2 = pyarrow.array(img) arr_2 = pyarrow.array(img) # type: ignore[call-overload]
del img del img
@ -97,8 +140,8 @@ def test_lifetime2() -> None:
img = hopper("L") img = hopper("L")
arr_1 = pyarrow.array(img) arr_1 = pyarrow.array(img) # type: ignore[call-overload]
arr_2 = pyarrow.array(img) arr_2 = pyarrow.array(img) # type: ignore[call-overload]
assert arr_1.sum().as_py() > 0 assert arr_1.sum().as_py() > 0
del arr_1 del arr_1
@ -110,3 +153,94 @@ def test_lifetime2() -> None:
px = img2.load() px = img2.load()
assert px # make mypy happy assert px # make mypy happy
assert isinstance(px[0, 0], int) assert isinstance(px[0, 0], int)
class DataShape(NamedTuple):
dtype: pyarrow.DataType
# Strictly speaking, elt should be a pixel or pixel component, so
# list[uint8][4], float, int, uint32, uint8, etc. But more
# correctly, it should be exactly the dtype from the line above.
elt: Any
elts_per_pixel: int
UINT_ARR = DataShape(
dtype=fl_uint8_4_type,
elt=[1, 2, 3, 4], # array of 4 uint8 per pixel
elts_per_pixel=1, # only one array per pixel
)
UINT = DataShape(
dtype=pyarrow.uint8(),
elt=3, # one uint8,
elts_per_pixel=4, # but repeated 4x per pixel
)
UINT32 = DataShape(
dtype=pyarrow.uint32(),
elt=0xABCDEF45, # one packed int, doesn't fit in a int32 > 0x80000000
elts_per_pixel=1, # one per pixel
)
INT32 = DataShape(
dtype=pyarrow.uint32(),
elt=0x12CDEF45, # one packed int
elts_per_pixel=1, # one per pixel
)
@pytest.mark.parametrize(
"mode, data_tp, mask",
(
("L", DataShape(pyarrow.uint8(), 3, 1), None),
("I", DataShape(pyarrow.int32(), 1 << 24, 1), None),
("F", DataShape(pyarrow.float32(), 3.14159, 1), None),
("LA", UINT_ARR, [0, 3]),
("LA", UINT, [0, 3]),
("RGB", UINT_ARR, [0, 1, 2]),
("RGBA", UINT_ARR, None),
("CMYK", UINT_ARR, None),
("YCbCr", UINT_ARR, [0, 1, 2]),
("HSV", UINT_ARR, [0, 1, 2]),
("RGB", UINT, [0, 1, 2]),
("RGBA", UINT, None),
("CMYK", UINT, None),
("YCbCr", UINT, [0, 1, 2]),
("HSV", UINT, [0, 1, 2]),
),
)
def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
(dtype, elt, elts_per_pixel) = data_tp
ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
arr = pyarrow.array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
_test_img_equals_pyarray(img, arr, mask, elts_per_pixel)
@pytest.mark.parametrize(
"mode, data_tp, mask",
(
("LA", UINT32, [0, 3]),
("RGB", UINT32, [0, 1, 2]),
("RGBA", UINT32, None),
("CMYK", UINT32, None),
("YCbCr", UINT32, [0, 1, 2]),
("HSV", UINT32, [0, 1, 2]),
("LA", INT32, [0, 3]),
("RGB", INT32, [0, 1, 2]),
("RGBA", INT32, None),
("CMYK", INT32, None),
("YCbCr", INT32, [0, 1, 2]),
("HSV", INT32, [0, 1, 2]),
),
)
def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
(dtype, elt, elts_per_pixel) = data_tp
ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
arr = pyarrow.array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
_test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel)

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING, Union from typing import Union
import pytest import pytest
@ -9,6 +9,7 @@ from PIL import Image, ImageQt
from .helper import assert_image_equal_tofile, assert_image_similar, hopper from .helper import assert_image_equal_tofile, assert_image_similar, hopper
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
import PyQt6 import PyQt6
import PySide6 import PySide6

View File

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

View File

@ -0,0 +1,11 @@
#!/bin/bash
## Run this as the test script in the Docker valgrind image.
## Note -- can be included directly into the Docker image,
## but requires the current python.supp.
source /vpy3/bin/activate
cd /Pillow
make clean
make install
make valgrind-leak

View File

@ -8,8 +8,8 @@ from __future__ import annotations
import re import re
import subprocess import subprocess
from typing import TYPE_CHECKING
TYPE_CHECKING = False
if TYPE_CHECKING: if TYPE_CHECKING:
from sphinx.application import Sphinx from sphinx.application import Sphinx

View File

@ -193,6 +193,28 @@ Image.Image.get_child_images()
method uses an image's file pointer, and so child images could only be retrieved from method uses an image's file pointer, and so child images could only be retrieved from
an :py:class:`PIL.ImageFile.ImageFile` instance. an :py:class:`PIL.ImageFile.ImageFile` instance.
Image.fromarray mode parameter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 11.3.0
The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
mode can be automatically determined from the object's shape and type instead.
Saving I mode images as PNG
^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. deprecated:: 11.3.0
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
changing the data, this is now deprecated. Instead, the image can be converted to
another mode before saving::
from PIL import Image
im = Image.new("I", (1, 1))
im.convert("I;16").save("out.png")
Removed features Removed features
---------------- ----------------

View File

@ -1082,6 +1082,26 @@ Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I
Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well. Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well.
QOI
^^^
.. versionadded:: 9.5.0
Pillow reads and writes images in Quite OK Image format using a Python codec. If you
wish to write code specifically for this format, :pypi:`qoi` is an alternative library
that uses C to decode the image and interfaces with NumPy.
.. _qoi-saving:
Saving
~~~~~~
The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
**colorspace**
If set to "sRGB", the colorspace will be written as sRGB with linear alpha, instead
of all channels being linear.
SGI SGI
^^^ ^^^
@ -1578,15 +1598,6 @@ PSD
Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0. Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.
QOI
^^^
.. versionadded:: 9.5.0
Pillow reads images in Quite OK Image format using a Python decoder. If you wish to
write code specifically for this format, :pypi:`qoi` is an alternative library that
uses C to decode the image and interfaces with NumPy.
SUN SUN
^^^ ^^^
@ -1650,7 +1661,8 @@ handler. ::
XPM XPM
^^^ ^^^
Pillow reads X pixmap files (mode ``P``) with 256 colors or less. Pillow reads X pixmap files as P mode images if there are 256 colors or less, and as
RGB images otherwise.
.. _xpm-opening: .. _xpm-opening:

View File

@ -194,9 +194,9 @@ Many of Pillow's features require external libraries:
pacman -S \ pacman -S \
mingw-w64-x86_64-gcc \ mingw-w64-x86_64-gcc \
mingw-w64-x86_64-python3 \ mingw-w64-x86_64-python \
mingw-w64-x86_64-python3-pip \ mingw-w64-x86_64-python-pip \
mingw-w64-x86_64-python3-setuptools mingw-w64-x86_64-python-setuptools
Prerequisites are installed on **MSYS2 MinGW 64-bit** with:: Prerequisites are installed on **MSYS2 MinGW 64-bit** with::

View File

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

View File

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

View File

@ -0,0 +1,79 @@
11.3.0
------
Security
========
:cve:`2025-48379`: Write buffer overflow on BCn encoding
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There is a heap buffer overflow when writing a sufficiently large (>64k encoded with
default settings) image in the DDS format due to writing into a buffer without checking
for available space.
This only affects users who save untrusted data as a compressed DDS image.
* Unclear how large the potential write could be. It is likely limited by process
segfault, so it's not necessarily deterministic. It may be practically unbounded.
* Unclear if there's a restriction on the bytes that could be emitted. It's likely that
the only restriction is that the bytes would be emitted in chunks of 8 or 16.
This was introduced in Pillow 11.2.0 when the feature was added.
Deprecations
============
Image.fromarray mode parameter
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
mode can be automatically determined from the object's shape and type instead.
Saving I mode images as PNG
^^^^^^^^^^^^^^^^^^^^^^^^^^^
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
changing the data, this is now deprecated. Instead, the image can be converted to
another mode before saving::
from PIL import Image
im = Image.new("I", (1, 1))
im.convert("I;16").save("out.png")
Other changes
=============
Added QOI saving
^^^^^^^^^^^^^^^^
Support has been added for saving QOI images. ``colorspace`` can be used to specify the
colorspace as sRGB with linear alpha, e.g. ``im.save("out.qoi", colorspace="sRGB")``.
By default, all channels will be linear.
Support using more screenshot utilities with ImageGrab on Linux
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:py:meth:`~PIL.ImageGrab.grab` is now able to use GNOME Screenshot, grim or Spectacle
on Linux in order to take a snapshot of the screen.
Do not build against libavif < 1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Pillow only supports libavif 1.0.0 or later. In order to prevent errors when building
from source, if a user happens to have an earlier libavif on their system, Pillow will
now ignore it.
AVIF support in wheels
^^^^^^^^^^^^^^^^^^^^^^
Support for reading and writing AVIF images is now included in Pillow's wheels, except
for Windows ARM64. libaom is available as an encoder and dav1d as a decoder.
Python 3.14 beta
^^^^^^^^^^^^^^^^
To help other projects prepare for Python 3.14, wheels are now built for the
3.14 beta as a preview. This is not official support for Python 3.14, but rather
an opportunity for you to test how Pillow works with the beta and report any
problems.

View File

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

View File

@ -70,6 +70,7 @@ optional-dependencies.tests = [
"pytest", "pytest",
"pytest-cov", "pytest-cov",
"pytest-timeout", "pytest-timeout",
"pytest-xdist",
"trove-classifiers>=2024.10.12", "trove-classifiers>=2024.10.12",
] ]

View File

@ -16,7 +16,6 @@ import subprocess
import sys import sys
import warnings import warnings
from collections.abc import Iterator from collections.abc import Iterator
from typing import Any
from setuptools import Extension, setup from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext from setuptools.command.build_ext import build_ext
@ -148,7 +147,7 @@ class RequiredDependencyException(Exception):
PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version
def _dbg(s: str, tp: Any = None) -> None: def _dbg(s: str, tp: str | tuple[str, ...] | None = None) -> None:
if DEBUG: if DEBUG:
if tp: if tp:
print(s % tp) print(s % tp)
@ -163,7 +162,7 @@ def _find_library_dirs_ldconfig() -> list[str]:
args: list[str] args: list[str]
env: dict[str, str] env: dict[str, str]
expr: str expr: str
if sys.platform.startswith("linux") or sys.platform.startswith("gnu"): if sys.platform.startswith(("linux", "gnu")):
if struct.calcsize("l") == 4: if struct.calcsize("l") == 4:
machine = os.uname()[4] + "-32" machine = os.uname()[4] + "-32"
else: else:
@ -509,11 +508,11 @@ class pil_build_ext(build_ext):
if root is None and pkg_config: if root is None and pkg_config:
if isinstance(lib_name, str): if isinstance(lib_name, str):
_dbg(f"Looking for `{lib_name}` using pkg-config.") _dbg("Looking for `%s` using pkg-config.", lib_name)
root = pkg_config(lib_name) root = pkg_config(lib_name)
else: else:
for lib_name2 in lib_name: for lib_name2 in lib_name:
_dbg(f"Looking for `{lib_name2}` using pkg-config.") _dbg("Looking for `%s` using pkg-config.", lib_name2)
root = pkg_config(lib_name2) root = pkg_config(lib_name2)
if root: if root:
break break
@ -623,11 +622,7 @@ class pil_build_ext(build_ext):
for extension in self.extensions: for extension in self.extensions:
extension.extra_compile_args = ["-Wno-nullability-completeness"] extension.extra_compile_args = ["-Wno-nullability-completeness"]
elif ( elif sys.platform.startswith(("linux", "gnu", "freebsd")):
sys.platform.startswith("linux")
or sys.platform.startswith("gnu")
or sys.platform.startswith("freebsd")
):
for dirname in _find_library_dirs_ldconfig(): for dirname in _find_library_dirs_ldconfig():
_add_directory(library_dirs, dirname) _add_directory(library_dirs, dirname)
if sys.platform.startswith("linux") and os.environ.get("ANDROID_ROOT"): if sys.platform.startswith("linux") and os.environ.get("ANDROID_ROOT"):
@ -736,7 +731,7 @@ class pil_build_ext(build_ext):
best_path = os.path.join(directory, name) best_path = os.path.join(directory, name)
_dbg( _dbg(
"Best openjpeg version %s so far in %s", "Best openjpeg version %s so far in %s",
(best_version, best_path), (str(best_version), best_path),
) )
if best_version and _find_library_file(self, "openjp2"): if best_version and _find_library_file(self, "openjp2"):
@ -758,12 +753,12 @@ class pil_build_ext(build_ext):
if feature.want("tiff"): if feature.want("tiff"):
_dbg("Looking for tiff") _dbg("Looking for tiff")
if _find_include_file(self, "tiff.h"): if _find_include_file(self, "tiff.h"):
if _find_library_file(self, "tiff"):
feature.set("tiff", "tiff")
if sys.platform in ["win32", "darwin"] and _find_library_file( if sys.platform in ["win32", "darwin"] and _find_library_file(
self, "libtiff" self, "libtiff"
): ):
feature.set("tiff", "libtiff") feature.set("tiff", "libtiff")
elif _find_library_file(self, "tiff"):
feature.set("tiff", "tiff")
if feature.want("freetype"): if feature.want("freetype"):
_dbg("Looking for freetype") _dbg("Looking for freetype")

View File

@ -445,9 +445,9 @@ def _save(
image = stride * im.size[1] image = stride * im.size[1]
if im.mode == "1": if im.mode == "1":
palette = b"".join(o8(i) * 4 for i in (0, 255)) palette = b"".join(o8(i) * 3 + b"\x00" for i in (0, 255))
elif im.mode == "L": elif im.mode == "L":
palette = b"".join(o8(i) * 4 for i in range(256)) palette = b"".join(o8(i) * 3 + b"\x00" for i in range(256))
elif im.mode == "P": elif im.mode == "P":
palette = im.im.getpalette("RGB", "BGRX") palette = im.im.getpalette("RGB", "BGRX")
colors = len(palette) // 4 colors = len(palette) // 4

View File

@ -1,5 +1,5 @@
""" """
A Pillow loader for .dds files (S3TC-compressed aka DXTC) A Pillow plugin for .dds files (S3TC-compressed aka DXTC)
Jerome Leclanche <jerome@leclan.ch> Jerome Leclanche <jerome@leclan.ch>
Documentation: Documentation:

View File

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

View File

@ -362,7 +362,7 @@ class IcoImageFile(ImageFile.ImageFile):
self.info["sizes"] = set(sizes) self.info["sizes"] = set(sizes)
self.size = im.size self.size = im.size
return None return Image.Image.load(self)
def load_seek(self, pos: int) -> None: def load_seek(self, pos: int) -> None:
# Flag the ImageFile.Parser so that it # Flag the ImageFile.Parser so that it

View File

@ -767,18 +767,20 @@ class Image:
.. warning:: .. warning::
This method returns the raw image data from the internal This method returns raw image data derived from Pillow's internal
storage. For compressed image data (e.g. PNG, JPEG) use storage. For compressed image data (e.g. PNG, JPEG) use
:meth:`~.save`, with a BytesIO parameter for in-memory :meth:`~.save`, with a BytesIO parameter for in-memory data.
data.
:param encoder_name: What encoder to use. The default is to :param encoder_name: What encoder to use.
use the standard "raw" encoder.
A list of C encoders can be seen under The default is to use the standard "raw" encoder.
codecs section of the function array in To see how this packs pixel data into the returned
:file:`_imaging.c`. Python encoders are bytes, see :file:`libImaging/Pack.c`.
registered within the relevant plugins.
A list of C encoders can be seen under codecs
section of the function array in
:file:`_imaging.c`. Python encoders are registered
within the relevant plugins.
:param args: Extra arguments to the encoder. :param args: Extra arguments to the encoder.
:returns: A :py:class:`bytes` object. :returns: A :py:class:`bytes` object.
""" """
@ -800,7 +802,9 @@ class Image:
e = _getencoder(self.mode, encoder_name, encoder_args) e = _getencoder(self.mode, encoder_name, encoder_args)
e.setimage(self.im) e.setimage(self.im)
bufsize = max(65536, self.size[0] * 4) # see RawEncode.c from . import ImageFile
bufsize = max(ImageFile.MAXBLOCK, self.size[0] * 4) # see RawEncode.c
output = [] output = []
while True: while True:
@ -1507,7 +1511,7 @@ class Image:
return {} return {}
if "xmp" not in self.info: if "xmp" not in self.info:
return {} return {}
root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00")) root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00 "))
return {get_name(root.tag): get_value(root)} return {get_name(root.tag): get_value(root)}
def getexif(self) -> Exif: def getexif(self) -> Exif:
@ -1538,10 +1542,11 @@ class Image:
# XMP tags # XMP tags
if ExifTags.Base.Orientation not in self._exif: if ExifTags.Base.Orientation not in self._exif:
xmp_tags = self.info.get("XML:com.adobe.xmp") xmp_tags = self.info.get("XML:com.adobe.xmp")
pattern: str | bytes = r'tiff:Orientation(="|>)([0-9])'
if not xmp_tags and (xmp_tags := self.info.get("xmp")): if not xmp_tags and (xmp_tags := self.info.get("xmp")):
xmp_tags = xmp_tags.decode("utf-8") pattern = rb'tiff:Orientation(="|>)([0-9])'
if xmp_tags: if xmp_tags:
match = re.search(r'tiff:Orientation(="|>)([0-9])', xmp_tags) match = re.search(pattern, xmp_tags)
if match: if match:
self._exif[ExifTags.Base.Orientation] = int(match[2]) self._exif[ExifTags.Base.Orientation] = int(match[2])
@ -2551,7 +2556,8 @@ class Image:
self.load() self.load()
save_all = params.pop("save_all", None) save_all = params.pop("save_all", None)
self.encoderinfo = {**getattr(self, "encoderinfo", {}), **params} encoderinfo = getattr(self, "encoderinfo", {})
self.encoderinfo = {**encoderinfo, **params}
self.encoderconfig: tuple[Any, ...] = () self.encoderconfig: tuple[Any, ...] = ()
if format.upper() not in SAVE: if format.upper() not in SAVE:
@ -2589,10 +2595,7 @@ class Image:
pass pass
raise raise
finally: finally:
try: self.encoderinfo = encoderinfo
del self.encoderinfo
except AttributeError:
pass
if open_fp: if open_fp:
fp.close() fp.close()
@ -3267,7 +3270,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
:param obj: Object with array interface :param obj: Object with array interface
:param mode: Optional mode to use when reading ``obj``. Will be determined from :param mode: Optional mode to use when reading ``obj``. Will be determined from
type if ``None``. type if ``None``. Deprecated.
This will not be used to convert the data after reading, but will be used to This will not be used to convert the data after reading, but will be used to
change how the data is read:: change how the data is read::
@ -3302,6 +3305,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
msg = f"Cannot handle this data type: {typekey_shape}, {typestr}" msg = f"Cannot handle this data type: {typekey_shape}, {typestr}"
raise TypeError(msg) from e raise TypeError(msg) from e
else: else:
deprecate("'mode' parameter", 13)
rawmode = mode rawmode = mode
if mode in ["1", "L", "I", "P", "F"]: if mode in ["1", "L", "I", "P", "F"]:
ndmax = 2 ndmax = 2

View File

@ -248,6 +248,9 @@ class ImageCmsProfile:
low-level profile object low-level profile object
""" """
self.filename = None
self.product_name = None # profile.product_name
self.product_info = None # profile.product_info
if isinstance(profile, str): if isinstance(profile, str):
if sys.platform == "win32": if sys.platform == "win32":
@ -256,23 +259,18 @@ class ImageCmsProfile:
profile_bytes_path.decode("ascii") profile_bytes_path.decode("ascii")
except UnicodeDecodeError: except UnicodeDecodeError:
with open(profile, "rb") as f: with open(profile, "rb") as f:
self._set(core.profile_frombytes(f.read())) self.profile = core.profile_frombytes(f.read())
return return
self._set(core.profile_open(profile), profile) self.filename = profile
self.profile = core.profile_open(profile)
elif hasattr(profile, "read"): elif hasattr(profile, "read"):
self._set(core.profile_frombytes(profile.read())) self.profile = core.profile_frombytes(profile.read())
elif isinstance(profile, core.CmsProfile): elif isinstance(profile, core.CmsProfile):
self._set(profile) self.profile = profile
else: else:
msg = "Invalid type for Profile" # type: ignore[unreachable] msg = "Invalid type for Profile" # type: ignore[unreachable]
raise TypeError(msg) raise TypeError(msg)
def _set(self, profile: core.CmsProfile, filename: str | None = None) -> None:
self.profile = profile
self.filename = filename
self.product_name = None # profile.product_name
self.product_info = None # profile.product_info
def tobytes(self) -> bytes: def tobytes(self) -> bytes:
""" """
Returns the profile in a format suitable for embedding in Returns the profile in a format suitable for embedding in

View File

@ -365,22 +365,10 @@ class ImageDraw:
# use the fill as a mask # use the fill as a mask
mask = Image.new("1", self.im.size) mask = Image.new("1", self.im.size)
mask_ink = self._getink(1)[0] mask_ink = self._getink(1)[0]
draw = Draw(mask)
fill_im = mask.copy()
draw = Draw(fill_im)
draw.draw.draw_polygon(xy, mask_ink, 1) draw.draw.draw_polygon(xy, mask_ink, 1)
ink_im = mask.copy() self.draw.draw_polygon(xy, ink, 0, width * 2 - 1, mask.im)
draw = Draw(ink_im)
width = width * 2 - 1
draw.draw.draw_polygon(xy, mask_ink, 0, width)
mask.paste(ink_im, mask=fill_im)
im = Image.new(self.mode, self.im.size)
draw = Draw(im)
draw.draw.draw_polygon(xy, ink, 0, width)
self.im.paste(im.im, (0, 0) + im.size, mask.im)
def regular_polygon( def regular_polygon(
self, self,
@ -702,8 +690,7 @@ class ImageDraw:
font_size: float | None, font_size: float | None,
) -> tuple[ ) -> tuple[
ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont, ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
str, list[tuple[tuple[float, float], str, AnyStr]],
list[tuple[tuple[float, float], AnyStr]],
]: ]:
if direction == "ttb": if direction == "ttb":
msg = "ttb direction is unsupported for multiline text" msg = "ttb direction is unsupported for multiline text"
@ -753,13 +740,7 @@ class ImageDraw:
left = xy[0] left = xy[0]
width_difference = max_width - widths[idx] width_difference = max_width - widths[idx]
# first align left by anchor # align by align parameter
if anchor[0] == "m":
left -= width_difference / 2.0
elif anchor[0] == "r":
left -= width_difference
# then align by align parameter
if align in ("left", "justify"): if align in ("left", "justify"):
pass pass
elif align == "center": elif align == "center":
@ -770,29 +751,43 @@ class ImageDraw:
msg = 'align must be "left", "center", "right" or "justify"' msg = 'align must be "left", "center", "right" or "justify"'
raise ValueError(msg) raise ValueError(msg)
if align == "justify" and width_difference != 0: if align == "justify" and width_difference != 0 and idx != len(lines) - 1:
words = line.split(" " if isinstance(text, str) else b" ") words = line.split(" " if isinstance(text, str) else b" ")
word_widths = [ if len(words) > 1:
self.textlength( # align left by anchor
word, if anchor[0] == "m":
font, left -= max_width / 2.0
direction=direction, elif anchor[0] == "r":
features=features, left -= max_width
language=language,
embedded_color=embedded_color,
)
for word in words
]
width_difference = max_width - sum(word_widths)
for i, word in enumerate(words):
parts.append(((left, top), word))
left += word_widths[i] + width_difference / (len(words) - 1)
else:
parts.append(((left, top), line))
word_widths = [
self.textlength(
word,
font,
direction=direction,
features=features,
language=language,
embedded_color=embedded_color,
)
for word in words
]
word_anchor = "l" + anchor[1]
width_difference = max_width - sum(word_widths)
for i, word in enumerate(words):
parts.append(((left, top), word_anchor, word))
left += word_widths[i] + width_difference / (len(words) - 1)
top += line_spacing
continue
# align left by anchor
if anchor[0] == "m":
left -= width_difference / 2.0
elif anchor[0] == "r":
left -= width_difference
parts.append(((left, top), anchor, line))
top += line_spacing top += line_spacing
return font, anchor, parts return font, parts
def multiline_text( def multiline_text(
self, self,
@ -817,7 +812,7 @@ class ImageDraw:
*, *,
font_size: float | None = None, font_size: float | None = None,
) -> None: ) -> None:
font, anchor, lines = self._prepare_multiline_text( font, lines = self._prepare_multiline_text(
xy, xy,
text, text,
font, font,
@ -832,7 +827,7 @@ class ImageDraw:
font_size, font_size,
) )
for xy, line in lines: for xy, anchor, line in lines:
self.text( self.text(
xy, xy,
line, line,
@ -947,7 +942,7 @@ class ImageDraw:
*, *,
font_size: float | None = None, font_size: float | None = None,
) -> tuple[float, float, float, float]: ) -> tuple[float, float, float, float]:
font, anchor, lines = self._prepare_multiline_text( font, lines = self._prepare_multiline_text(
xy, xy,
text, text,
font, font,
@ -964,7 +959,7 @@ class ImageDraw:
bbox: tuple[float, float, float, float] | None = None bbox: tuple[float, float, float, float] | None = None
for xy, line in lines: for xy, anchor, line in lines:
bbox_line = self.textbbox( bbox_line = self.textbbox(
xy, xy,
line, line,

View File

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

View File

@ -175,7 +175,9 @@ class MacViewer(Viewer):
if not os.path.exists(path): if not os.path.exists(path):
raise FileNotFoundError raise FileNotFoundError
subprocess.call(["open", "-a", "Preview.app", path]) subprocess.call(["open", "-a", "Preview.app", path])
executable = sys.executable or shutil.which("python3")
pyinstaller = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
executable = (not pyinstaller and sys.executable) or shutil.which("python3")
if executable: if executable:
subprocess.Popen( subprocess.Popen(
[ [

View File

@ -179,7 +179,8 @@ class IptcImageFile(ImageFile.ImageFile):
with Image.open(o) as _im: with Image.open(o) as _im:
_im.load() _im.load()
self.im = _im.im self.im = _im.im
return None self.tile = []
return Image.Image.load(self)
Image.register_open(IptcImageFile.format, IptcImageFile) Image.register_open(IptcImageFile.format, IptcImageFile)

View File

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

View File

@ -33,11 +33,7 @@ class BitStream:
def peek(self, bits: int) -> int: def peek(self, bits: int) -> int:
while self.bits < bits: while self.bits < bits:
c = self.next() self.bitbuffer = (self.bitbuffer << 8) + self.next()
if c < 0:
self.bits = 0
continue
self.bitbuffer = (self.bitbuffer << 8) + c
self.bits += 8 self.bits += 8
return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1 return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1

View File

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

View File

@ -48,6 +48,7 @@ from ._binary import i32be as i32
from ._binary import o8 from ._binary import o8
from ._binary import o16be as o16 from ._binary import o16be as o16
from ._binary import o32be as o32 from ._binary import o32be as o32
from ._deprecate import deprecate
from ._util import DeferredError from ._util import DeferredError
TYPE_CHECKING = False TYPE_CHECKING = False
@ -1368,6 +1369,8 @@ def _save(
except KeyError as e: except KeyError as e:
msg = f"cannot write mode {mode} as PNG" msg = f"cannot write mode {mode} as PNG"
raise OSError(msg) from e raise OSError(msg) from e
if outmode == "I":
deprecate("Saving I mode images as PNG", 13, stacklevel=4)
# #
# write minimal PNG file # write minimal PNG file

View File

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

View File

@ -8,9 +8,12 @@
from __future__ import annotations from __future__ import annotations
import os import os
from typing import IO
from . import Image, ImageFile from . import Image, ImageFile
from ._binary import i32be as i32 from ._binary import i32be as i32
from ._binary import o8
from ._binary import o32be as o32
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:
@ -51,7 +54,7 @@ class QoiDecoder(ImageFile.PyDecoder):
assert self.fd is not None assert self.fd is not None
self._previously_seen_pixels = {} self._previously_seen_pixels = {}
self._add_to_previous_pixels(bytearray((0, 0, 0, 255))) self._previous_pixel = bytearray((0, 0, 0, 255))
data = bytearray() data = bytearray()
bands = Image.getmodebands(self.mode) bands = Image.getmodebands(self.mode)
@ -110,6 +113,122 @@ class QoiDecoder(ImageFile.PyDecoder):
return -1, 0 return -1, 0
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if im.mode == "RGB":
channels = 3
elif im.mode == "RGBA":
channels = 4
else:
msg = "Unsupported QOI image mode"
raise ValueError(msg)
colorspace = 0 if im.encoderinfo.get("colorspace") == "sRGB" else 1
fp.write(b"qoif")
fp.write(o32(im.size[0]))
fp.write(o32(im.size[1]))
fp.write(o8(channels))
fp.write(o8(colorspace))
ImageFile._save(im, fp, [ImageFile._Tile("qoi", (0, 0) + im.size)])
class QoiEncoder(ImageFile.PyEncoder):
_pushes_fd = True
_previous_pixel: tuple[int, int, int, int] | None = None
_previously_seen_pixels: dict[int, tuple[int, int, int, int]] = {}
_run = 0
def _write_run(self) -> bytes:
data = o8(0b11000000 | (self._run - 1)) # QOI_OP_RUN
self._run = 0
return data
def _delta(self, left: int, right: int) -> int:
result = (left - right) & 255
if result >= 128:
result -= 256
return result
def encode(self, bufsize: int) -> tuple[int, int, bytes]:
assert self.im is not None
self._previously_seen_pixels = {0: (0, 0, 0, 0)}
self._previous_pixel = (0, 0, 0, 255)
data = bytearray()
w, h = self.im.size
bands = Image.getmodebands(self.mode)
for y in range(h):
for x in range(w):
pixel = self.im.getpixel((x, y))
if bands == 3:
pixel = (*pixel, 255)
if pixel == self._previous_pixel:
self._run += 1
if self._run == 62:
data += self._write_run()
else:
if self._run:
data += self._write_run()
r, g, b, a = pixel
hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64
if self._previously_seen_pixels.get(hash_value) == pixel:
data += o8(hash_value) # QOI_OP_INDEX
elif self._previous_pixel:
self._previously_seen_pixels[hash_value] = pixel
prev_r, prev_g, prev_b, prev_a = self._previous_pixel
if prev_a == a:
delta_r = self._delta(r, prev_r)
delta_g = self._delta(g, prev_g)
delta_b = self._delta(b, prev_b)
if (
-2 <= delta_r < 2
and -2 <= delta_g < 2
and -2 <= delta_b < 2
):
data += o8(
0b01000000
| (delta_r + 2) << 4
| (delta_g + 2) << 2
| (delta_b + 2)
) # QOI_OP_DIFF
else:
delta_gr = self._delta(delta_r, delta_g)
delta_gb = self._delta(delta_b, delta_g)
if (
-8 <= delta_gr < 8
and -32 <= delta_g < 32
and -8 <= delta_gb < 8
):
data += o8(
0b10000000 | (delta_g + 32)
) # QOI_OP_LUMA
data += o8((delta_gr + 8) << 4 | (delta_gb + 8))
else:
data += o8(0b11111110) # QOI_OP_RGB
data += bytes(pixel[:3])
else:
data += o8(0b11111111) # QOI_OP_RGBA
data += bytes(pixel)
self._previous_pixel = pixel
if self._run:
data += self._write_run()
data += bytes((0, 0, 0, 0, 0, 0, 0, 1)) # padding
return len(data), 0, data
Image.register_open(QoiImageFile.format, QoiImageFile, _accept) Image.register_open(QoiImageFile.format, QoiImageFile, _accept)
Image.register_decoder("qoi", QoiDecoder) Image.register_decoder("qoi", QoiDecoder)
Image.register_extension(QoiImageFile.format, ".qoi") Image.register_extension(QoiImageFile.format, ".qoi")
Image.register_save(QoiImageFile.format, _save)
Image.register_encoder("qoi", QoiEncoder)

View File

@ -82,17 +82,10 @@ class SgiImageFile(ImageFile.ImageFile):
# zsize : channels count # zsize : channels count
zsize = i16(s, 10) zsize = i16(s, 10)
# layout
layout = bpc, dimension, zsize
# determine mode from bits/zsize # determine mode from bits/zsize
rawmode = ""
try: try:
rawmode = MODES[layout] rawmode = MODES[(bpc, dimension, zsize)]
except KeyError: except KeyError:
pass
if rawmode == "":
msg = "Unsupported SGI image mode" msg = "Unsupported SGI image mode"
raise ValueError(msg) raise ValueError(msg)
@ -156,24 +149,15 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# Run-Length Encoding Compression - Unsupported at this time # Run-Length Encoding Compression - Unsupported at this time
rle = 0 rle = 0
# Number of dimensions (x,y,z)
dim = 3
# X Dimension = width / Y Dimension = height # X Dimension = width / Y Dimension = height
x, y = im.size x, y = im.size
if im.mode == "L" and y == 1:
dim = 1
elif im.mode == "L":
dim = 2
# Z Dimension: Number of channels # Z Dimension: Number of channels
z = len(im.mode) z = len(im.mode)
# Number of dimensions (x,y,z)
if dim in {1, 2}: if im.mode == "L":
z = 1 dimension = 1 if y == 1 else 2
else:
# assert we've got the right number of bands. dimension = 3
if len(im.getbands()) != z:
msg = f"incorrect number of bands in SGI write: {z} vs {len(im.getbands())}"
raise ValueError(msg)
# Minimum Byte value # Minimum Byte value
pinmin = 0 pinmin = 0
@ -188,7 +172,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
fp.write(struct.pack(">h", magic_number)) fp.write(struct.pack(">h", magic_number))
fp.write(o8(rle)) fp.write(o8(rle))
fp.write(o8(bpc)) fp.write(o8(bpc))
fp.write(struct.pack(">H", dim)) fp.write(struct.pack(">H", dimension))
fp.write(struct.pack(">H", x)) fp.write(struct.pack(">H", x))
fp.write(struct.pack(">H", y)) fp.write(struct.pack(">H", y))
fp.write(struct.pack(">H", z)) fp.write(struct.pack(">H", z))

View File

@ -1217,9 +1217,10 @@ class TiffImageFile(ImageFile.ImageFile):
return return
self._seek(frame) self._seek(frame)
if self._im is not None and ( if self._im is not None and (
self.im.size != self._tile_size or self.im.mode != self.mode self.im.size != self._tile_size
or self.im.mode != self.mode
or self.readonly
): ):
# The core image will no longer be used
self._im = None self._im = None
def _seek(self, frame: int) -> None: def _seek(self, frame: int) -> None:
@ -1259,7 +1260,10 @@ class TiffImageFile(ImageFile.ImageFile):
self.fp.seek(self._frame_pos[frame]) self.fp.seek(self._frame_pos[frame])
self.tag_v2.load(self.fp) self.tag_v2.load(self.fp)
if XMP in self.tag_v2: if XMP in self.tag_v2:
self.info["xmp"] = self.tag_v2[XMP] xmp = self.tag_v2[XMP]
if isinstance(xmp, tuple) and len(xmp) == 1:
xmp = xmp[0]
self.info["xmp"] = xmp
elif "xmp" in self.info: elif "xmp" in self.info:
del self.info["xmp"] del self.info["xmp"]
self._reload_exif() self._reload_exif()
@ -1676,7 +1680,7 @@ SAVE_INFO = {
"PA": ("PA", II, 3, 1, (8, 8), 2), "PA": ("PA", II, 3, 1, (8, 8), 2),
"I": ("I;32S", II, 1, 2, (32,), None), "I": ("I;32S", II, 1, 2, (32,), None),
"I;16": ("I;16", II, 1, 1, (16,), None), "I;16": ("I;16", II, 1, 1, (16,), None),
"I;16S": ("I;16S", II, 1, 2, (16,), None), "I;16L": ("I;16L", II, 1, 1, (16,), None),
"F": ("F;32F", II, 1, 3, (32,), None), "F": ("F;32F", II, 1, 3, (32,), None),
"RGB": ("RGB", II, 2, 1, (8, 8, 8), None), "RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
"RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0), "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
@ -1684,10 +1688,7 @@ SAVE_INFO = {
"CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None), "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
"YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None), "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
"LAB": ("LAB", II, 8, 1, (8, 8, 8), None), "LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
"I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
"I;16B": ("I;16B", MM, 1, 1, (16,), None), "I;16B": ("I;16B", MM, 1, 1, (16,), None),
"I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
"F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
} }
@ -1963,7 +1964,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
# we're storing image byte order. So, if the rawmode # we're storing image byte order. So, if the rawmode
# contains I;16, we need to convert from native to image # contains I;16, we need to convert from native to image
# byte order. # byte order.
if im.mode in ("I;16B", "I;16"): if im.mode in ("I;16", "I;16B", "I;16L"):
rawmode = "I;16N" rawmode = "I;16N"
# Pass tags as sorted list so that the tags are set in a fixed order. # Pass tags as sorted list so that the tags are set in a fixed order.

View File

@ -81,7 +81,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
def _open(self) -> None: def _open(self) -> None:
# check placable header # check placable header
s = self.fp.read(80) s = self.fp.read(44)
if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"): if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"):
# placeable windows metafile # placeable windows metafile

View File

@ -37,43 +37,36 @@ class XpmImageFile(ImageFile.ImageFile):
format_description = "X11 Pixel Map" format_description = "X11 Pixel Map"
def _open(self) -> None: def _open(self) -> None:
assert self.fp is not None
if not _accept(self.fp.read(9)): if not _accept(self.fp.read(9)):
msg = "not an XPM file" msg = "not an XPM file"
raise SyntaxError(msg) raise SyntaxError(msg)
# skip forward to next string # skip forward to next string
while True: while True:
s = self.fp.readline() line = self.fp.readline()
if not s: if not line:
msg = "broken XPM file" msg = "broken XPM file"
raise SyntaxError(msg) raise SyntaxError(msg)
m = xpm_head.match(s) m = xpm_head.match(line)
if m: if m:
break break
self._size = int(m.group(1)), int(m.group(2)) self._size = int(m.group(1)), int(m.group(2))
pal = int(m.group(3)) palette_length = int(m.group(3))
bpp = int(m.group(4)) bpp = int(m.group(4))
if pal > 256 or bpp != 1:
msg = "cannot read this XPM file"
raise ValueError(msg)
# #
# load palette description # load palette description
palette = [b"\0\0\0"] * 256 palette = {}
for _ in range(pal): for _ in range(palette_length):
s = self.fp.readline() line = self.fp.readline().rstrip()
if s.endswith(b"\r\n"):
s = s[:-2]
elif s.endswith((b"\r", b"\n")):
s = s[:-1]
c = s[1] c = line[1 : bpp + 1]
s = s[2:-2].split() s = line[bpp + 1 : -2].split()
for i in range(0, len(s), 2): for i in range(0, len(s), 2):
if s[i] == b"c": if s[i] == b"c":
@ -82,10 +75,11 @@ class XpmImageFile(ImageFile.ImageFile):
if rgb == b"None": if rgb == b"None":
self.info["transparency"] = c self.info["transparency"] = c
elif rgb.startswith(b"#"): elif rgb.startswith(b"#"):
# FIXME: handle colour names (see ImagePalette.py) rgb_int = int(rgb[1:], 16)
rgb = int(rgb[1:], 16)
palette[c] = ( palette[c] = (
o8((rgb >> 16) & 255) + o8((rgb >> 8) & 255) + o8(rgb & 255) o8((rgb_int >> 16) & 255)
+ o8((rgb_int >> 8) & 255)
+ o8(rgb_int & 255)
) )
else: else:
# unknown colour # unknown colour
@ -98,10 +92,16 @@ class XpmImageFile(ImageFile.ImageFile):
msg = "cannot read this XPM file" msg = "cannot read this XPM file"
raise ValueError(msg) raise ValueError(msg)
self._mode = "P" args: tuple[int, dict[bytes, bytes] | tuple[bytes, ...]]
self.palette = ImagePalette.raw("RGB", b"".join(palette)) if palette_length > 256:
self._mode = "RGB"
args = (bpp, palette)
else:
self._mode = "P"
self.palette = ImagePalette.raw("RGB", b"".join(palette.values()))
args = (bpp, tuple(palette.keys()))
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), "P")] self.tile = [ImageFile._Tile("xpm", (0, 0) + self.size, self.fp.tell(), args)]
def load_read(self, read_bytes: int) -> bytes: def load_read(self, read_bytes: int) -> bytes:
# #
@ -109,16 +109,48 @@ class XpmImageFile(ImageFile.ImageFile):
xsize, ysize = self.size xsize, ysize = self.size
assert self.fp is not None
s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)] s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)]
return b"".join(s) return b"".join(s)
class XpmDecoder(ImageFile.PyDecoder):
_pulls_fd = True
def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
assert self.fd is not None
data = bytearray()
bpp, palette = self.args
dest_length = self.state.xsize * self.state.ysize
if self.mode == "RGB":
dest_length *= 3
pixel_header = False
while len(data) < dest_length:
line = self.fd.readline()
if not line:
break
if line.rstrip() == b"/* pixels */" and not pixel_header:
pixel_header = True
continue
line = b'"'.join(line.split(b'"')[1:-1])
for i in range(0, len(line), bpp):
key = line[i : i + bpp]
if self.mode == "RGB":
data += palette[key]
else:
data += o8(palette.index(key))
self.set_as_raw(bytes(data))
return -1, 0
# #
# Registry # Registry
Image.register_open(XpmImageFile.format, XpmImageFile, _accept) Image.register_open(XpmImageFile.format, XpmImageFile, _accept)
Image.register_decoder("xpm", XpmDecoder)
Image.register_extension(XpmImageFile.format, ".xpm") Image.register_extension(XpmImageFile.format, ".xpm")

View File

@ -12,6 +12,7 @@ def deprecate(
*, *,
action: str | None = None, action: str | None = None,
plural: bool = False, plural: bool = False,
stacklevel: int = 3,
) -> None: ) -> None:
""" """
Deprecations helper. Deprecations helper.
@ -67,5 +68,5 @@ def deprecate(
warnings.warn( warnings.warn(
f"{deprecated} {is_} deprecated and will be removed in {removed}{action}", f"{deprecated} {is_} deprecated and will be removed in {removed}{action}",
DeprecationWarning, DeprecationWarning,
stacklevel=3, stacklevel=stacklevel,
) )

View File

@ -881,26 +881,22 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__avif(void) { PyInit__avif(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_avif", .m_name = "_avif",
.m_size = -1,
.m_methods = avifMethods, .m_methods = avifMethods,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
Py_DECREF(m);
return NULL;
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -308,9 +308,9 @@ _new_arrow(PyObject *self, PyObject *args) {
} }
// ImagingBorrowArrow is responsible for retaining the array_capsule // ImagingBorrowArrow is responsible for retaining the array_capsule
ret = ret = PyImagingNew(
PyImagingNew(ImagingNewArrow(mode, xsize, ysize, schema_capsule, array_capsule) ImagingNewArrow(mode, xsize, ysize, schema_capsule, array_capsule)
); );
if (!ret) { if (!ret) {
return ImagingError_ValueError("Invalid Arrow array mode or size mismatch"); return ImagingError_ValueError("Invalid Arrow array mode or size mismatch");
} }
@ -338,12 +338,6 @@ static const char *no_palette = "image has no palette";
static const char *readonly = "image is readonly"; static const char *readonly = "image is readonly";
/* static const char* no_content = "image has no content"; */ /* static const char* no_content = "image has no content"; */
void *
ImagingError_OSError(void) {
PyErr_SetString(PyExc_OSError, "error when accessing file");
return NULL;
}
void * void *
ImagingError_MemoryError(void) { ImagingError_MemoryError(void) {
return PyErr_NoMemory(); return PyErr_NoMemory();
@ -369,11 +363,6 @@ ImagingError_ValueError(const char *message) {
return NULL; return NULL;
} }
void
ImagingError_Clear(void) {
PyErr_Clear();
}
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
/* HELPERS */ /* HELPERS */
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
@ -1665,7 +1654,8 @@ _putdata(ImagingObject *self, PyObject *args) {
int bigendian = 0; int bigendian = 0;
if (image->type == IMAGING_TYPE_SPECIAL) { if (image->type == IMAGING_TYPE_SPECIAL) {
// I;16* // I;16*
if (strcmp(image->mode, "I;16B") == 0 if (
strcmp(image->mode, "I;16B") == 0
#ifdef WORDS_BIGENDIAN #ifdef WORDS_BIGENDIAN
|| strcmp(image->mode, "I;16N") == 0 || strcmp(image->mode, "I;16N") == 0
#endif #endif
@ -2226,6 +2216,7 @@ _unsharp_mask(ImagingObject *self, PyObject *args) {
} }
if (!ImagingUnsharpMask(imOut, imIn, radius, percent, threshold)) { if (!ImagingUnsharpMask(imOut, imIn, radius, percent, threshold)) {
ImagingDelete(imOut);
return NULL; return NULL;
} }
@ -3218,7 +3209,8 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
(int)p[3], (int)p[3],
&ink, &ink,
width, width,
self->blend self->blend,
NULL
) < 0) { ) < 0) {
free(xy); free(xy);
return NULL; return NULL;
@ -3356,7 +3348,10 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
int ink; int ink;
int fill = 0; int fill = 0;
int width = 0; int width = 0;
if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) { ImagingObject *maskp = NULL;
if (!PyArg_ParseTuple(
args, "Oi|iiO!", &data, &ink, &fill, &width, &Imaging_Type, &maskp
)) {
return NULL; return NULL;
} }
@ -3386,8 +3381,16 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
free(xy); free(xy);
if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, width, self->blend) < if (ImagingDrawPolygon(
0) { self->image->image,
n,
ixy,
&ink,
fill,
width,
self->blend,
maskp ? maskp->image : NULL
) < 0) {
free(ixy); free(ixy);
return NULL; return NULL;
} }
@ -4460,27 +4463,22 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imaging(void) { PyInit__imaging(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imaging", .m_name = "_imaging",
.m_size = -1,
.m_methods = functions, .m_methods = functions,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
Py_DECREF(m);
return NULL;
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -1463,28 +1463,24 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imagingcms(void) { PyInit__imagingcms(void) {
PyObject *m; PyDateTime_IMPORT;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imagingcms", .m_name = "_imagingcms",
.m_size = -1,
.m_methods = pyCMSdll_methods, .m_methods = pyCMSdll_methods,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
return NULL;
}
PyDateTime_IMPORT;
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -275,6 +275,7 @@ text_layout_raqm(
if (!text || !size) { if (!text || !size) {
/* return 0 and clean up, no glyphs==no size, /* return 0 and clean up, no glyphs==no size,
and raqm fails with empty strings */ and raqm fails with empty strings */
PyMem_Free(text);
goto failed; goto failed;
} }
set_text = raqm_set_text(rq, text, size); set_text = raqm_set_text(rq, text, size);
@ -425,6 +426,7 @@ text_layout_fallback(
"setting text direction, language or font features is not supported " "setting text direction, language or font features is not supported "
"without libraqm" "without libraqm"
); );
return 0;
} }
if (PyUnicode_Check(string)) { if (PyUnicode_Check(string)) {
@ -1599,26 +1601,22 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imagingft(void) { PyInit__imagingft(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imagingft", .m_name = "_imagingft",
.m_size = -1,
.m_methods = _functions, .m_methods = _functions,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
return NULL;
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -302,26 +302,22 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imagingmath(void) { PyInit__imagingmath(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imagingmath", .m_name = "_imagingmath",
.m_size = -1,
.m_methods = _functions, .m_methods = _functions,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
return NULL;
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -246,23 +246,22 @@ static PyMethodDef functions[] = {
{NULL, NULL, 0, NULL} {NULL, NULL, 0, NULL}
}; };
static PyModuleDef_Slot slots[] = {
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imagingmorph(void) { PyInit__imagingmorph(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imagingmorph", .m_name = "_imagingmorph",
.m_doc = "A module for doing image morphology", .m_doc = "A module for doing image morphology",
.m_size = -1,
.m_methods = functions, .m_methods = functions,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -46,24 +46,22 @@ static PyMethodDef functions[] = {
{NULL, NULL} /* sentinel */ {NULL, NULL} /* sentinel */
}; };
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, load_tkinter_funcs},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__imagingtk(void) { PyInit__imagingtk(void) {
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_imagingtk", .m_name = "_imagingtk",
.m_size = -1,
.m_methods = functions, .m_methods = functions,
.m_slots = slots
}; };
PyObject *m;
m = PyModule_Create(&module_def);
if (load_tkinter_funcs() != 0) {
Py_DECREF(m);
return NULL;
}
#ifdef Py_GIL_DISABLED return PyModuleDef_Init(&module_def);
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -641,6 +641,10 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
ImagingSectionLeave(&cookie); ImagingSectionLeave(&cookie);
WebPPictureFree(&pic); WebPPictureFree(&pic);
output = writer.mem;
ret_size = writer.size;
if (!ok) { if (!ok) {
int error_code = (&pic)->error_code; int error_code = (&pic)->error_code;
char message[50] = ""; char message[50] = "";
@ -652,10 +656,9 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
); );
} }
PyErr_Format(PyExc_ValueError, "encoding error %d%s", error_code, message); PyErr_Format(PyExc_ValueError, "encoding error %d%s", error_code, message);
free(output);
return NULL; return NULL;
} }
output = writer.mem;
ret_size = writer.size;
{ {
/* I want to truncate the *_size items that get passed into WebP /* I want to truncate the *_size items that get passed into WebP
@ -777,26 +780,22 @@ setup_module(PyObject *m) {
return 0; return 0;
} }
static PyModuleDef_Slot slots[] = {
{Py_mod_exec, setup_module},
#ifdef Py_GIL_DISABLED
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
{0, NULL}
};
PyMODINIT_FUNC PyMODINIT_FUNC
PyInit__webp(void) { PyInit__webp(void) {
PyObject *m;
static PyModuleDef module_def = { static PyModuleDef module_def = {
PyModuleDef_HEAD_INIT, PyModuleDef_HEAD_INIT,
.m_name = "_webp", .m_name = "_webp",
.m_size = -1,
.m_methods = webpMethods, .m_methods = webpMethods,
.m_slots = slots
}; };
m = PyModule_Create(&module_def); return PyModuleDef_Init(&module_def);
if (setup_module(m) < 0) {
Py_DECREF(m);
return NULL;
}
#ifdef Py_GIL_DISABLED
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
#endif
return m;
} }

View File

@ -327,11 +327,11 @@ 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 = (Func_SetThreadDpiAwarenessContext SetThreadDpiAwarenessContext_function = (Func_SetThreadDpiAwarenessContext)
)GetProcAddress(user32, "SetThreadDpiAwarenessContext"); GetProcAddress(user32, "SetThreadDpiAwarenessContext");
if (SetThreadDpiAwarenessContext_function != NULL) { if (SetThreadDpiAwarenessContext_function != NULL) {
GetWindowDpiAwarenessContext_function = (Func_GetWindowDpiAwarenessContext GetWindowDpiAwarenessContext_function = (Func_GetWindowDpiAwarenessContext)
)GetProcAddress(user32, "GetWindowDpiAwarenessContext"); GetProcAddress(user32, "GetWindowDpiAwarenessContext");
if (screens == -1 && GetWindowDpiAwarenessContext_function != NULL) { if (screens == -1 && GetWindowDpiAwarenessContext_function != NULL) {
dpiAwareness = GetWindowDpiAwarenessContext_function(wnd); dpiAwareness = GetWindowDpiAwarenessContext_function(wnd);
} }

View File

@ -703,6 +703,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
encoder->cleanup = ImagingLibTiffEncodeCleanup;
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_GetItemRef(tags, pos); item = PyList_GetItemRef(tags, pos);

View File

@ -36,7 +36,10 @@ ReleaseExportedSchema(struct ArrowSchema *array) {
child->release(child); child->release(child);
child->release = NULL; child->release = NULL;
} }
// UNDONE -- should I be releasing the children? free(array->children[i]);
}
if (array->children) {
free(array->children);
} }
// Release dictionary // Release dictionary
@ -98,7 +101,7 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
} }
/* for now, single block images */ /* for now, single block images */
if (!(im->blocks_count == 0 || im->blocks_count == 1)) { if (im->blocks_count > 1) {
return IMAGING_ARROW_MEMORY_LAYOUT; return IMAGING_ARROW_MEMORY_LAYOUT;
} }
@ -117,6 +120,7 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
retval = export_named_type(schema->children[0], im->arrow_band_format, "pixel"); retval = export_named_type(schema->children[0], im->arrow_band_format, "pixel");
if (retval != 0) { if (retval != 0) {
free(schema->children[0]); free(schema->children[0]);
free(schema->children);
schema->release(schema); schema->release(schema);
return retval; return retval;
} }
@ -127,9 +131,7 @@ static void
release_const_array(struct ArrowArray *array) { release_const_array(struct ArrowArray *array) {
Imaging im = (Imaging)array->private_data; Imaging im = (Imaging)array->private_data;
if (array->n_children == 0) { ImagingDelete(im);
ImagingDelete(im);
}
// Free the buffers and the buffers array // Free the buffers and the buffers array
if (array->buffers) { if (array->buffers) {
@ -157,7 +159,7 @@ export_single_channel_array(Imaging im, struct ArrowArray *array) {
int length = im->xsize * im->ysize; int length = im->xsize * im->ysize;
/* for now, single block images */ /* for now, single block images */
if (!(im->blocks_count == 0 || im->blocks_count == 1)) { if (im->blocks_count > 1) {
return IMAGING_ARROW_MEMORY_LAYOUT; return IMAGING_ARROW_MEMORY_LAYOUT;
} }
@ -200,7 +202,7 @@ export_fixed_pixel_array(Imaging im, struct ArrowArray *array) {
int length = im->xsize * im->ysize; int length = im->xsize * im->ysize;
/* for now, single block images */ /* for now, single block images */
if (!(im->blocks_count == 0 || im->blocks_count == 1)) { if (im->blocks_count > 1) {
return IMAGING_ARROW_MEMORY_LAYOUT; return IMAGING_ARROW_MEMORY_LAYOUT;
} }

View File

@ -258,6 +258,10 @@ ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
UINT8 *dst = buf; UINT8 *dst = buf;
for (;;) { for (;;) {
// Loop writes a max of 16 bytes per iteration
if (dst + 16 >= bytes + buf) {
break;
}
if (n == 5) { if (n == 5) {
encode_bc3_alpha(im, state, dst, 0); encode_bc3_alpha(im, state, dst, 0);
dst += 8; dst += 8;

View File

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

View File

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

View File

@ -118,8 +118,9 @@ ImagingFillRadialGradient(const char *mode) {
for (y = 0; y < 256; y++) { for (y = 0; y < 256; y++) {
for (x = 0; x < 256; x++) { for (x = 0; x < 256; x++) {
d = (int d = (int)sqrt(
)sqrt((double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0); (double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0
);
if (d >= 255) { if (d >= 255) {
d = 255; d = 255;
} }

View File

@ -155,7 +155,8 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
} else { } else {
int bigendian = 0; int bigendian = 0;
if (im->type == IMAGING_TYPE_SPECIAL) { if (im->type == IMAGING_TYPE_SPECIAL) {
if (strcmp(im->mode, "I;16B") == 0 if (
strcmp(im->mode, "I;16B") == 0
#ifdef WORDS_BIGENDIAN #ifdef WORDS_BIGENDIAN
|| strcmp(im->mode, "I;16N") == 0 || strcmp(im->mode, "I;16N") == 0
#endif #endif
@ -308,7 +309,8 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
} else { } else {
int bigendian = 0; int bigendian = 0;
if (im->type == IMAGING_TYPE_SPECIAL) { if (im->type == IMAGING_TYPE_SPECIAL) {
if (strcmp(im->mode, "I;16B") == 0 if (
strcmp(im->mode, "I;16B") == 0
#ifdef WORDS_BIGENDIAN #ifdef WORDS_BIGENDIAN
|| strcmp(im->mode, "I;16N") == 0 || strcmp(im->mode, "I;16N") == 0
#endif #endif

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