Merge branch 'main' into build-editable
|
@ -6,6 +6,7 @@ init:
|
|||
# Uncomment previous line to get RDP access during the build.
|
||||
|
||||
environment:
|
||||
COVERAGE_CORE: sysmon
|
||||
EXECUTABLE: python.exe
|
||||
TEST_OPTIONS:
|
||||
DEPLOY: YES
|
||||
|
|
1
.ci/requirements-mypy.txt
Normal file
|
@ -0,0 +1 @@
|
|||
mypy==1.8.0
|
2
.github/FUNDING.yml
vendored
|
@ -1 +1 @@
|
|||
tidelift: "pypi/Pillow"
|
||||
tidelift: "pypi/pillow"
|
||||
|
|
2
.github/workflows/docs.yml
vendored
|
@ -7,10 +7,12 @@ on:
|
|||
paths:
|
||||
- ".github/workflows/docs.yml"
|
||||
- "docs/**"
|
||||
- "src/PIL/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/docs.yml"
|
||||
- "docs/**"
|
||||
- "src/PIL/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
|
3
.github/workflows/test-cygwin.yml
vendored
|
@ -26,6 +26,9 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
COVERAGE_CORE: sysmon
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
|
5
.github/workflows/test-mingw.yml
vendored
|
@ -26,6 +26,9 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
COVERAGE_CORE: sysmon
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
@ -64,10 +67,10 @@ jobs:
|
|||
mingw-w64-x86_64-python3-cffi \
|
||||
mingw-w64-x86_64-python3-numpy \
|
||||
mingw-w64-x86_64-python3-olefile \
|
||||
mingw-w64-x86_64-python3-pip \
|
||||
mingw-w64-x86_64-python3-setuptools \
|
||||
mingw-w64-x86_64-python-pyqt6
|
||||
|
||||
python3 -m ensurepip
|
||||
python3 -m pip install pyroma pytest pytest-cov pytest-timeout
|
||||
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
|
17
.github/workflows/test-windows.yml
vendored
|
@ -26,13 +26,16 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
COVERAGE_CORE: sysmon
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
|
||||
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13.0-alpha.3"]
|
||||
|
||||
timeout-minutes: 30
|
||||
|
||||
|
@ -66,8 +69,16 @@ jobs:
|
|||
- name: Print build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma
|
||||
run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma
|
||||
- name: Install Python dependencies
|
||||
run: >
|
||||
python3 -m pip install
|
||||
coverage>=7.4.2
|
||||
defusedxml
|
||||
olefile
|
||||
pyroma
|
||||
pytest
|
||||
pytest-cov
|
||||
pytest-timeout
|
||||
|
||||
- name: Install dependencies
|
||||
id: install
|
||||
|
|
1
.github/workflows/test.yml
vendored
|
@ -27,6 +27,7 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
COVERAGE_CORE: sysmon
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
|
|
31
.github/workflows/wheels-dependencies.sh
vendored
|
@ -19,7 +19,7 @@ FREETYPE_VERSION=2.13.2
|
|||
HARFBUZZ_VERSION=8.3.0
|
||||
LIBPNG_VERSION=1.6.40
|
||||
JPEGTURBO_VERSION=3.0.1
|
||||
OPENJPEG_VERSION=2.5.0
|
||||
OPENJPEG_VERSION=2.5.2
|
||||
XZ_VERSION=5.4.5
|
||||
TIFF_VERSION=4.6.0
|
||||
LCMS2_VERSION=2.16
|
||||
|
@ -40,7 +40,7 @@ BROTLI_VERSION=1.1.0
|
|||
|
||||
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "x86_64" ]]; then
|
||||
function build_openjpeg {
|
||||
local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz openjpeg-2.5.0.tar.gz)
|
||||
local out_dir=$(fetch_unpack https://github.com/uclouvain/openjpeg/archive/v${OPENJPEG_VERSION}.tar.gz openjpeg-${OPENJPEG_VERSION}.tar.gz)
|
||||
(cd $out_dir \
|
||||
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \
|
||||
&& make install)
|
||||
|
@ -62,7 +62,7 @@ function build_brotli {
|
|||
|
||||
function build {
|
||||
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||
export BUILD_PREFIX="/usr/local"
|
||||
sudo chown -R runner /usr/local
|
||||
fi
|
||||
build_xz
|
||||
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
|
||||
|
@ -75,8 +75,8 @@ function build {
|
|||
build_simple xorgproto 2023.2 https://www.x.org/pub/individual/proto
|
||||
build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib
|
||||
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
|
||||
if [ -f /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc ]; then
|
||||
cp /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc /Library/Frameworks/Python.framework/Versions/Current/lib/pkgconfig/xcb-proto.pc
|
||||
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||
cp /usr/local/share/pkgconfig/xcb-proto.pc /usr/local/lib/pkgconfig
|
||||
fi
|
||||
else
|
||||
sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc
|
||||
|
@ -87,12 +87,10 @@ function build {
|
|||
build_tiff
|
||||
build_libpng
|
||||
build_lcms2
|
||||
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||
for dylib in libjpeg.dylib libtiff.dylib liblcms2.dylib; do
|
||||
cp $BUILD_PREFIX/lib/$dylib /opt/arm64-builds/lib
|
||||
done
|
||||
fi
|
||||
build_openjpeg
|
||||
if [ -f /usr/local/lib64/libopenjp2.so ]; then
|
||||
cp /usr/local/lib64/libopenjp2.so /usr/local/lib
|
||||
fi
|
||||
|
||||
ORIGINAL_CFLAGS=$CFLAGS
|
||||
CFLAGS="$CFLAGS -O3 -DNDEBUG"
|
||||
|
@ -128,14 +126,19 @@ curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-de
|
|||
untar pillow-depends-main.zip
|
||||
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
# webp, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb
|
||||
# libtiff and libxcb cause a conflict with building libtiff and libxcb
|
||||
# libxau and libxdmcp cause an issue on macOS < 11
|
||||
# if php is installed, brew tries to reinstall these after installing openblas
|
||||
# remove cairo to fix building harfbuzz on arm64
|
||||
# remove lcms2 and libpng to fix building openjpeg on arm64
|
||||
# remove zstd to avoid inclusion on x86_64
|
||||
# remove jpeg-turbo to avoid inclusion on arm64
|
||||
# remove webp and zstd to avoid inclusion on x86_64
|
||||
# curl from brew requires zstd, use system curl
|
||||
brew remove --ignore-dependencies webp libpng libtiff libxcb libxau libxdmcp curl php cairo lcms2 ghostscript zstd
|
||||
brew remove --ignore-dependencies libpng libtiff libxcb libxau libxdmcp curl cairo lcms2 zstd
|
||||
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||
brew remove --ignore-dependencies jpeg-turbo
|
||||
else
|
||||
brew remove --ignore-dependencies webp
|
||||
fi
|
||||
|
||||
brew install pkg-config
|
||||
fi
|
||||
|
|
3
.github/workflows/wheels-test.sh
vendored
|
@ -4,6 +4,9 @@ set -e
|
|||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
brew install fribidi
|
||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||
if [ -f /opt/homebrew/lib/libfribidi.dylib ]; then
|
||||
sudo cp /opt/homebrew/lib/libfribidi.dylib /usr/local/lib
|
||||
fi
|
||||
elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
|
||||
apk add curl fribidi
|
||||
else
|
||||
|
|
4
.github/workflows/wheels.yml
vendored
|
@ -101,7 +101,7 @@ jobs:
|
|||
cibw_arch: x86_64
|
||||
macosx_deployment_target: "10.10"
|
||||
- name: "macOS arm64"
|
||||
os: macos-latest
|
||||
os: macos-14
|
||||
cibw_arch: arm64
|
||||
macosx_deployment_target: "11.0"
|
||||
- name: "manylinux2014 and musllinux x86_64"
|
||||
|
@ -134,7 +134,7 @@ jobs:
|
|||
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_SKIP: pp38-*
|
||||
CIBW_TEST_SKIP: "*-macosx_arm64"
|
||||
CIBW_TEST_SKIP: cp38-macosx_arm64
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
|
|
15
CHANGES.rst
|
@ -5,6 +5,21 @@ Changelog (Pillow)
|
|||
10.3.0 (unreleased)
|
||||
-------------------
|
||||
|
||||
- Open 16-bit grayscale PNGs as I;16 #7849
|
||||
[radarhere]
|
||||
|
||||
- Handle truncated chunks at the end of PNG images #7709
|
||||
[lajiyuan, radarhere]
|
||||
|
||||
- Match mask size to pasted image size in GifImagePlugin #7779
|
||||
[radarhere]
|
||||
|
||||
- Release GIL while calling ``WebPAnimDecoderGetNext`` #7782
|
||||
[evanmiller, radarhere]
|
||||
|
||||
- Fixed reading FLI/FLC images with a prefix chunk #7804
|
||||
[twolife]
|
||||
|
||||
- Update wl-paste handling and return None for some errors in grabclipboard() on Linux #7745
|
||||
[nik012003, radarhere]
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ As of 2019, Pillow development is
|
|||
src="https://zenodo.org/badge/17549/python-pillow/Pillow.svg"></a>
|
||||
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
|
||||
alt="Tidelift"
|
||||
src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
|
||||
src="https://tidelift.com/badges/package/pypi/pillow?style=flat"></a>
|
||||
<a href="https://pypi.org/project/pillow/"><img
|
||||
alt="Newest PyPI version"
|
||||
src="https://img.shields.io/pypi/v/pillow.svg"></a>
|
||||
|
@ -82,9 +82,6 @@ As of 2019, Pillow development is
|
|||
<a href="https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img
|
||||
alt="Join the chat at https://gitter.im/python-pillow/Pillow"
|
||||
src="https://badges.gitter.im/python-pillow/Pillow.svg"></a>
|
||||
<a href="https://twitter.com/PythonPillow"><img
|
||||
alt="Follow on https://twitter.com/PythonPillow"
|
||||
src="https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg"></a>
|
||||
<a href="https://fosstodon.org/@pillow"><img
|
||||
alt="Follow on https://fosstodon.org/@pillow"
|
||||
src="https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg"
|
||||
|
|
|
@ -86,7 +86,7 @@ Released as needed privately to individual vendors for critical security-related
|
|||
|
||||
## Publicize Release
|
||||
|
||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
|
||||
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
|
||||
|
||||
## Documentation
|
||||
|
||||
|
|
|
@ -23,7 +23,10 @@ def _get_mem_usage() -> float:
|
|||
|
||||
|
||||
def _test_leak(
|
||||
min_iterations: int, max_iterations: int, fn: Callable[..., None], *args: Any
|
||||
min_iterations: int,
|
||||
max_iterations: int,
|
||||
fn: Callable[..., Image.Image | None],
|
||||
*args: Any,
|
||||
) -> None:
|
||||
mem_limit = None
|
||||
for i in range(max_iterations):
|
||||
|
|
|
@ -17,6 +17,7 @@ def test_ignore_dos_text() -> None:
|
|||
finally:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||
|
||||
assert isinstance(im, PngImagePlugin.PngImageFile)
|
||||
for s in im.text.values():
|
||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||
|
||||
|
@ -32,6 +33,7 @@ def test_dos_text() -> None:
|
|||
assert msg, "Decompressed Data Too Large"
|
||||
return
|
||||
|
||||
assert isinstance(im, PngImagePlugin.PngImageFile)
|
||||
for s in im.text.values():
|
||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||
|
||||
|
@ -57,6 +59,7 @@ def test_dos_total_memory() -> None:
|
|||
return
|
||||
|
||||
total_len = 0
|
||||
assert isinstance(im2, PngImagePlugin.PngImageFile)
|
||||
for txt in im2.text.values():
|
||||
total_len += len(txt)
|
||||
assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M"
|
||||
|
|
|
@ -351,7 +351,7 @@ def is_mingw() -> bool:
|
|||
|
||||
|
||||
class CachedProperty:
|
||||
def __init__(self, func: Callable[[Any], None]) -> None:
|
||||
def __init__(self, func: Callable[[Any], Any]) -> None:
|
||||
self.func = func
|
||||
|
||||
def __get__(self, instance: Any, cls: type[Any] | None = None) -> Any:
|
||||
|
|
Before Width: | Height: | Size: 578 B |
BIN
Tests/images/16_bit_binary_pgm.tiff
Normal file
BIN
Tests/images/2422.flc
Normal file
Before Width: | Height: | Size: 298 KiB |
BIN
Tests/images/cmx3g8_wv_1998.260_0745_mcidas.tiff
Normal file
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 180 B |
BIN
Tests/images/imagedraw_rectangle_I.tiff
Normal file
BIN
Tests/images/truncated_end_chunk.png
Normal file
After Width: | Height: | Size: 30 KiB |
|
@ -20,7 +20,7 @@ from PIL import _deprecate
|
|||
),
|
||||
],
|
||||
)
|
||||
def test_version(version, expected) -> None:
|
||||
def test_version(version: int | None, expected: str) -> None:
|
||||
with pytest.warns(DeprecationWarning, match=expected):
|
||||
_deprecate.deprecate("Old thing", version, "new thing")
|
||||
|
||||
|
@ -46,7 +46,7 @@ def test_unknown_version() -> None:
|
|||
),
|
||||
],
|
||||
)
|
||||
def test_old_version(deprecated, plural, expected) -> None:
|
||||
def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
|
||||
expected = r""
|
||||
with pytest.raises(RuntimeError, match=expected):
|
||||
_deprecate.deprecate(deprecated, 1, plural=plural)
|
||||
|
@ -76,7 +76,7 @@ def test_replacement_and_action() -> None:
|
|||
"Upgrade to new thing.",
|
||||
],
|
||||
)
|
||||
def test_action(action) -> None:
|
||||
def test_action(action: str) -> None:
|
||||
expected = (
|
||||
r"Old thing is deprecated and will be removed in Pillow 11 \(2024-10-15\)\. "
|
||||
r"Upgrade to new thing\."
|
||||
|
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import io
|
||||
import re
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -29,7 +30,7 @@ def test_version() -> None:
|
|||
# Check the correctness of the convenience function
|
||||
# and the format of version numbers
|
||||
|
||||
def test(name, function) -> None:
|
||||
def test(name: str, function: Callable[[str], bool]) -> None:
|
||||
version = features.version(name)
|
||||
if not features.check(name):
|
||||
assert version is None
|
||||
|
@ -73,12 +74,12 @@ def test_libimagequant_version() -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("feature", features.modules)
|
||||
def test_check_modules(feature) -> None:
|
||||
def test_check_modules(feature: str) -> None:
|
||||
assert features.check_module(feature) in [True, False]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("feature", features.codecs)
|
||||
def test_check_codecs(feature) -> None:
|
||||
def test_check_codecs(feature: str) -> None:
|
||||
assert features.check_codec(feature) in [True, False]
|
||||
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ def test_save(tmp_path: Path) -> None:
|
|||
"Tests/images/timeout-ef9112a065e7183fa7faa2e18929b03e44ee16bf.blp",
|
||||
],
|
||||
)
|
||||
def test_crashes(test_file) -> None:
|
||||
def test_crashes(test_file: str) -> None:
|
||||
with open(test_file, "rb") as f:
|
||||
with Image.open(f) as im:
|
||||
with pytest.raises(OSError):
|
||||
|
|
|
@ -16,7 +16,7 @@ from .helper import (
|
|||
|
||||
|
||||
def test_sanity(tmp_path: Path) -> None:
|
||||
def roundtrip(im) -> None:
|
||||
def roundtrip(im: Image.Image) -> None:
|
||||
outfile = str(tmp_path / "temp.bmp")
|
||||
|
||||
im.save(outfile, "BMP")
|
||||
|
@ -194,7 +194,7 @@ def test_rle4() -> None:
|
|||
("Tests/images/bmp/g/pal8rle.bmp", 1064),
|
||||
),
|
||||
)
|
||||
def test_rle8_eof(file_name, length) -> None:
|
||||
def test_rle8_eof(file_name: str, length: int) -> None:
|
||||
with open(file_name, "rb") as fp:
|
||||
data = fp.read(length)
|
||||
with Image.open(io.BytesIO(data)) as im:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import ContainerIO, Image
|
||||
|
@ -21,9 +23,16 @@ def test_isatty() -> None:
|
|||
assert container.isatty() is False
|
||||
|
||||
|
||||
def test_seek_mode_0() -> None:
|
||||
@pytest.mark.parametrize(
|
||||
"mode, expected_position",
|
||||
(
|
||||
(0, 33),
|
||||
(1, 66),
|
||||
(2, 100),
|
||||
),
|
||||
)
|
||||
def test_seek_mode(mode: Literal[0, 1, 2], expected_position: int) -> None:
|
||||
# Arrange
|
||||
mode = 0
|
||||
with open(TEST_FILE, "rb") as fh:
|
||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||
|
||||
|
@ -32,35 +41,7 @@ def test_seek_mode_0() -> None:
|
|||
container.seek(33, mode)
|
||||
|
||||
# Assert
|
||||
assert container.tell() == 33
|
||||
|
||||
|
||||
def test_seek_mode_1() -> None:
|
||||
# Arrange
|
||||
mode = 1
|
||||
with open(TEST_FILE, "rb") as fh:
|
||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||
|
||||
# Act
|
||||
container.seek(33, mode)
|
||||
container.seek(33, mode)
|
||||
|
||||
# Assert
|
||||
assert container.tell() == 66
|
||||
|
||||
|
||||
def test_seek_mode_2() -> None:
|
||||
# Arrange
|
||||
mode = 2
|
||||
with open(TEST_FILE, "rb") as fh:
|
||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||
|
||||
# Act
|
||||
container.seek(33, mode)
|
||||
container.seek(33, mode)
|
||||
|
||||
# Assert
|
||||
assert container.tell() == 100
|
||||
assert container.tell() == expected_position
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||
|
|
|
@ -4,7 +4,7 @@ import warnings
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import FliImagePlugin, Image
|
||||
from PIL import FliImagePlugin, Image, ImageFile
|
||||
|
||||
from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy
|
||||
|
||||
|
@ -12,9 +12,12 @@ from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy
|
|||
# save as...-> hopper.fli, default options.
|
||||
static_test_file = "Tests/images/hopper.fli"
|
||||
|
||||
# From https://samples.libav.org/fli-flc/
|
||||
# From https://samples.ffmpeg.org/fli-flc/
|
||||
animated_test_file = "Tests/images/a.fli"
|
||||
|
||||
# From https://samples.ffmpeg.org/fli-flc/
|
||||
animated_test_file_with_prefix_chunk = "Tests/images/2422.flc"
|
||||
|
||||
|
||||
def test_sanity() -> None:
|
||||
with Image.open(static_test_file) as im:
|
||||
|
@ -32,6 +35,24 @@ def test_sanity() -> None:
|
|||
assert im.is_animated
|
||||
|
||||
|
||||
def test_prefix_chunk() -> None:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
try:
|
||||
with Image.open(animated_test_file_with_prefix_chunk) as im:
|
||||
assert im.mode == "P"
|
||||
assert im.size == (320, 200)
|
||||
assert im.format == "FLI"
|
||||
assert im.info["duration"] == 171
|
||||
assert im.is_animated
|
||||
|
||||
palette = im.getpalette()
|
||||
assert palette[3:6] == [255, 255, 255]
|
||||
assert palette[381:384] == [204, 204, 12]
|
||||
assert palette[765:] == [252, 0, 0]
|
||||
finally:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
|
||||
def test_unclosed_file() -> None:
|
||||
def open() -> None:
|
||||
|
|
|
@ -1113,6 +1113,21 @@ def test_append_images(tmp_path: Path) -> None:
|
|||
assert reread.n_frames == 10
|
||||
|
||||
|
||||
def test_append_different_size_image(tmp_path: Path) -> None:
|
||||
out = str(tmp_path / "temp.gif")
|
||||
|
||||
im = Image.new("RGB", (100, 100))
|
||||
bigger_im = Image.new("RGB", (200, 200), "#f00")
|
||||
|
||||
im.save(out, save_all=True, append_images=[bigger_im])
|
||||
|
||||
with Image.open(out) as reread:
|
||||
assert reread.size == (100, 100)
|
||||
|
||||
reread.seek(1)
|
||||
assert reread.size == (100, 100)
|
||||
|
||||
|
||||
def test_transparent_optimize(tmp_path: Path) -> None:
|
||||
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
|
||||
# transparency.
|
||||
|
|
|
@ -135,7 +135,7 @@ def test_different_bit_depths(tmp_path: Path) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB", "RGBA"))
|
||||
def test_save_to_bytes_bmp(mode) -> None:
|
||||
def test_save_to_bytes_bmp(mode: str) -> None:
|
||||
output = io.BytesIO()
|
||||
im = hopper(mode)
|
||||
im.save(output, "ico", bitmap_format="bmp", sizes=[(32, 32), (64, 64)])
|
||||
|
|
|
@ -82,7 +82,7 @@ def test_eoferror() -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("RGB", "P", "PA"))
|
||||
def test_roundtrip(mode, tmp_path: Path) -> None:
|
||||
def test_roundtrip(mode: str, tmp_path: Path) -> None:
|
||||
out = str(tmp_path / "temp.im")
|
||||
im = hopper(mode)
|
||||
im.save(out)
|
||||
|
|
|
@ -98,7 +98,7 @@ def test_i() -> None:
|
|||
assert ret == 97
|
||||
|
||||
|
||||
def test_dump(monkeypatch) -> None:
|
||||
def test_dump(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# Arrange
|
||||
c = b"abc"
|
||||
# Temporarily redirect stdout
|
||||
|
|
|
@ -6,7 +6,7 @@ import warnings
|
|||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -45,14 +45,20 @@ TEST_FILE = "Tests/images/hopper.jpg"
|
|||
|
||||
@skip_unless_feature("jpg")
|
||||
class TestFileJpeg:
|
||||
def roundtrip(self, im: Image.Image, **options: Any) -> Image.Image:
|
||||
def roundtrip_with_bytes(
|
||||
self, im: Image.Image, **options: Any
|
||||
) -> tuple[JpegImagePlugin.JpegImageFile, int]:
|
||||
out = BytesIO()
|
||||
im.save(out, "JPEG", **options)
|
||||
test_bytes = out.tell()
|
||||
out.seek(0)
|
||||
im = Image.open(out)
|
||||
im.bytes = test_bytes # for testing only
|
||||
return im
|
||||
reloaded = cast(JpegImagePlugin.JpegImageFile, Image.open(out))
|
||||
return reloaded, test_bytes
|
||||
|
||||
def roundtrip(
|
||||
self, im: Image.Image, **options: Any
|
||||
) -> JpegImagePlugin.JpegImageFile:
|
||||
return self.roundtrip_with_bytes(im, **options)[0]
|
||||
|
||||
def gen_random_image(self, size: tuple[int, int], mode: str = "RGB") -> Image.Image:
|
||||
"""Generates a very hard to compress file
|
||||
|
@ -246,13 +252,13 @@ class TestFileJpeg:
|
|||
im.save(f, progressive=True, quality=94, exif=b" " * 43668)
|
||||
|
||||
def test_optimize(self) -> None:
|
||||
im1 = self.roundtrip(hopper())
|
||||
im2 = self.roundtrip(hopper(), optimize=0)
|
||||
im3 = self.roundtrip(hopper(), optimize=1)
|
||||
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
|
||||
im2, im2_bytes = self.roundtrip_with_bytes(hopper(), optimize=0)
|
||||
im3, im3_bytes = self.roundtrip_with_bytes(hopper(), optimize=1)
|
||||
assert_image_equal(im1, im2)
|
||||
assert_image_equal(im1, im3)
|
||||
assert im1.bytes >= im2.bytes
|
||||
assert im1.bytes >= im3.bytes
|
||||
assert im1_bytes >= im2_bytes
|
||||
assert im1_bytes >= im3_bytes
|
||||
|
||||
def test_optimize_large_buffer(self, tmp_path: Path) -> None:
|
||||
# https://github.com/python-pillow/Pillow/issues/148
|
||||
|
@ -262,15 +268,15 @@ class TestFileJpeg:
|
|||
im.save(f, format="JPEG", optimize=True)
|
||||
|
||||
def test_progressive(self) -> None:
|
||||
im1 = self.roundtrip(hopper())
|
||||
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
|
||||
im2 = self.roundtrip(hopper(), progressive=False)
|
||||
im3 = self.roundtrip(hopper(), progressive=True)
|
||||
im3, im3_bytes = self.roundtrip_with_bytes(hopper(), progressive=True)
|
||||
assert not im1.info.get("progressive")
|
||||
assert not im2.info.get("progressive")
|
||||
assert im3.info.get("progressive")
|
||||
|
||||
assert_image_equal(im1, im3)
|
||||
assert im1.bytes >= im3.bytes
|
||||
assert im1_bytes >= im3_bytes
|
||||
|
||||
def test_progressive_large_buffer(self, tmp_path: Path) -> None:
|
||||
f = str(tmp_path / "temp.jpg")
|
||||
|
@ -341,6 +347,7 @@ class TestFileJpeg:
|
|||
assert exif.get_ifd(0x8825) == {}
|
||||
|
||||
transposed = ImageOps.exif_transpose(im)
|
||||
assert transposed is not None
|
||||
exif = transposed.getexif()
|
||||
assert exif.get_ifd(0x8825) == {}
|
||||
|
||||
|
@ -419,14 +426,14 @@ class TestFileJpeg:
|
|||
assert im3.info.get("progression")
|
||||
|
||||
def test_quality(self) -> None:
|
||||
im1 = self.roundtrip(hopper())
|
||||
im2 = self.roundtrip(hopper(), quality=50)
|
||||
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
|
||||
im2, im2_bytes = self.roundtrip_with_bytes(hopper(), quality=50)
|
||||
assert_image(im1, im2.mode, im2.size)
|
||||
assert im1.bytes >= im2.bytes
|
||||
assert im1_bytes >= im2_bytes
|
||||
|
||||
im3 = self.roundtrip(hopper(), quality=0)
|
||||
im3, im3_bytes = self.roundtrip_with_bytes(hopper(), quality=0)
|
||||
assert_image(im1, im3.mode, im3.size)
|
||||
assert im2.bytes > im3.bytes
|
||||
assert im2_bytes > im3_bytes
|
||||
|
||||
def test_smooth(self) -> None:
|
||||
im1 = self.roundtrip(hopper())
|
||||
|
|
|
@ -40,10 +40,8 @@ test_card.load()
|
|||
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
|
||||
out = BytesIO()
|
||||
im.save(out, "JPEG2000", **options)
|
||||
test_bytes = out.tell()
|
||||
out.seek(0)
|
||||
with Image.open(out) as im:
|
||||
im.bytes = test_bytes # for testing only
|
||||
im.load()
|
||||
return im
|
||||
|
||||
|
@ -77,7 +75,9 @@ def test_invalid_file() -> None:
|
|||
def test_bytesio() -> None:
|
||||
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
|
||||
data = BytesIO(f.read())
|
||||
assert_image_similar_tofile(test_card, data, 1.0e-3)
|
||||
with Image.open(data) as im:
|
||||
im.load()
|
||||
assert_image_similar(im, test_card, 1.0e-3)
|
||||
|
||||
|
||||
# These two test pre-written JPEG 2000 files that were not written with
|
||||
|
@ -340,6 +340,7 @@ def test_parser_feed() -> None:
|
|||
p.feed(data)
|
||||
|
||||
# Assert
|
||||
assert p.image is not None
|
||||
assert p.image.size == (640, 480)
|
||||
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ from .helper import (
|
|||
|
||||
@skip_unless_feature("libtiff")
|
||||
class LibTiffTestCase:
|
||||
def _assert_noerr(self, tmp_path: Path, im: Image.Image) -> None:
|
||||
def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> None:
|
||||
"""Helper tests that assert basic sanity about the g4 tiff reading"""
|
||||
# 1 bit
|
||||
assert im.mode == "1"
|
||||
|
@ -524,7 +524,8 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
im.save(out, compression=compression)
|
||||
|
||||
def test_fp_leak(self) -> None:
|
||||
im = Image.open("Tests/images/hopper_g4_500.tif")
|
||||
im: Image.Image | None = Image.open("Tests/images/hopper_g4_500.tif")
|
||||
assert im is not None
|
||||
fn = im.fp.fileno()
|
||||
|
||||
os.fstat(fn)
|
||||
|
@ -716,6 +717,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
f.write(src.read())
|
||||
|
||||
im = Image.open(tmpfile)
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
im.n_frames
|
||||
im.close()
|
||||
# Should not raise PermissionError.
|
||||
|
@ -1097,6 +1099,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
|
||||
with Image.open(out) as im:
|
||||
# Assert that there are multiple strips
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
assert len(im.tag_v2[STRIPOFFSETS]) > 1
|
||||
|
||||
@pytest.mark.parametrize("argument", (True, False))
|
||||
|
@ -1113,6 +1116,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
im.save(out, **arguments)
|
||||
|
||||
with Image.open(out) as im:
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
assert len(im.tag_v2[STRIPOFFSETS]) == 1
|
||||
finally:
|
||||
TiffImagePlugin.STRIP_SIZE = 65536
|
||||
|
|
|
@ -19,7 +19,7 @@ def test_valid_file() -> None:
|
|||
# https://ghrc.nsstc.nasa.gov/hydro/details/cmx3g8
|
||||
# https://ghrc.nsstc.nasa.gov/pub/fieldCampaigns/camex3/cmx3g8/browse/
|
||||
test_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.ara"
|
||||
saved_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.png"
|
||||
saved_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.tiff"
|
||||
|
||||
# Act
|
||||
with Image.open(test_file) as im:
|
||||
|
|
|
@ -2,11 +2,11 @@ from __future__ import annotations
|
|||
|
||||
import warnings
|
||||
from io import BytesIO
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
from PIL import Image, MpoImagePlugin
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -20,14 +20,11 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"]
|
|||
pytestmark = skip_unless_feature("jpg")
|
||||
|
||||
|
||||
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
|
||||
def roundtrip(im: Image.Image, **options: Any) -> MpoImagePlugin.MpoImageFile:
|
||||
out = BytesIO()
|
||||
im.save(out, "MPO", **options)
|
||||
test_bytes = out.tell()
|
||||
out.seek(0)
|
||||
im = Image.open(out)
|
||||
im.bytes = test_bytes # for testing only
|
||||
return im
|
||||
return cast(MpoImagePlugin.MpoImageFile, Image.open(out))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_file", test_files)
|
||||
|
|
|
@ -52,7 +52,7 @@ def test_open_windows_v1() -> None:
|
|||
assert isinstance(im, MspImagePlugin.MspImageFile)
|
||||
|
||||
|
||||
def _assert_file_image_equal(source_path, target_path) -> None:
|
||||
def _assert_file_image_equal(source_path: str, target_path: str) -> None:
|
||||
with Image.open(source_path) as im:
|
||||
assert_image_equal_tofile(im, target_path)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from PIL import Image, ImageFile, PcxImagePlugin
|
|||
from .helper import assert_image_equal, hopper
|
||||
|
||||
|
||||
def _roundtrip(tmp_path: Path, im) -> None:
|
||||
def _roundtrip(tmp_path: Path, im: Image.Image) -> None:
|
||||
f = str(tmp_path / "temp.pcx")
|
||||
im.save(f)
|
||||
with Image.open(f) as im2:
|
||||
|
@ -44,7 +44,7 @@ def test_invalid_file() -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("1", "L", "P", "RGB"))
|
||||
def test_odd(tmp_path: Path, mode) -> None:
|
||||
def test_odd(tmp_path: Path, mode: str) -> None:
|
||||
# See issue #523, odd sized images should have a stride that's even.
|
||||
# Not that ImageMagick or GIMP write PCX that way.
|
||||
# We were not handling properly.
|
||||
|
@ -89,7 +89,7 @@ def test_large_count(tmp_path: Path) -> None:
|
|||
_roundtrip(tmp_path, im)
|
||||
|
||||
|
||||
def _test_buffer_overflow(tmp_path: Path, im, size: int = 1024) -> None:
|
||||
def _test_buffer_overflow(tmp_path: Path, im: Image.Image, size: int = 1024) -> None:
|
||||
_last = ImageFile.MAXBLOCK
|
||||
ImageFile.MAXBLOCK = size
|
||||
try:
|
||||
|
|
|
@ -6,6 +6,7 @@ import os.path
|
|||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any, Generator
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -14,7 +15,7 @@ from PIL import Image, PdfParser, features
|
|||
from .helper import hopper, mark_if_feature_version, skip_unless_feature
|
||||
|
||||
|
||||
def helper_save_as_pdf(tmp_path: Path, mode, **kwargs):
|
||||
def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str:
|
||||
# Arrange
|
||||
im = hopper(mode)
|
||||
outfile = str(tmp_path / ("temp_" + mode + ".pdf"))
|
||||
|
@ -41,13 +42,13 @@ def helper_save_as_pdf(tmp_path: Path, mode, **kwargs):
|
|||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("L", "P", "RGB", "CMYK"))
|
||||
def test_save(tmp_path: Path, mode) -> None:
|
||||
def test_save(tmp_path: Path, mode: str) -> None:
|
||||
helper_save_as_pdf(tmp_path, mode)
|
||||
|
||||
|
||||
@skip_unless_feature("jpg_2000")
|
||||
@pytest.mark.parametrize("mode", ("LA", "RGBA"))
|
||||
def test_save_alpha(tmp_path: Path, mode) -> None:
|
||||
def test_save_alpha(tmp_path: Path, mode: str) -> None:
|
||||
helper_save_as_pdf(tmp_path, mode)
|
||||
|
||||
|
||||
|
@ -112,7 +113,7 @@ def test_resolution(tmp_path: Path) -> None:
|
|||
{"dpi": (75, 150), "resolution": 200},
|
||||
),
|
||||
)
|
||||
def test_dpi(params, tmp_path: Path) -> None:
|
||||
def test_dpi(params: dict[str, int | tuple[int, int]], tmp_path: Path) -> None:
|
||||
im = hopper()
|
||||
|
||||
outfile = str(tmp_path / "temp.pdf")
|
||||
|
@ -156,7 +157,7 @@ def test_save_all(tmp_path: Path) -> None:
|
|||
assert os.path.getsize(outfile) > 0
|
||||
|
||||
# Test appending using a generator
|
||||
def im_generator(ims):
|
||||
def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
|
||||
yield from ims
|
||||
|
||||
im.save(outfile, save_all=True, append_images=im_generator(ims))
|
||||
|
@ -226,7 +227,7 @@ def test_pdf_append_fails_on_nonexistent_file() -> None:
|
|||
im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True)
|
||||
|
||||
|
||||
def check_pdf_pages_consistency(pdf) -> None:
|
||||
def check_pdf_pages_consistency(pdf: PdfParser.PdfParser) -> None:
|
||||
pages_info = pdf.read_indirect(pdf.pages_ref)
|
||||
assert b"Parent" not in pages_info
|
||||
assert b"Kids" in pages_info
|
||||
|
@ -339,7 +340,7 @@ def test_pdf_append_to_bytesio() -> None:
|
|||
@pytest.mark.timeout(1)
|
||||
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
|
||||
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
|
||||
def test_redos(newline) -> None:
|
||||
def test_redos(newline: bytes) -> None:
|
||||
malicious = b" trailer<<>>" + newline * 3456
|
||||
|
||||
# This particular exception isn't relevant here.
|
||||
|
|
|
@ -6,7 +6,8 @@ import warnings
|
|||
import zlib
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from types import ModuleType
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -23,6 +24,7 @@ from .helper import (
|
|||
skip_unless_feature,
|
||||
)
|
||||
|
||||
ElementTree: ModuleType | None
|
||||
try:
|
||||
from defusedxml import ElementTree
|
||||
except ImportError:
|
||||
|
@ -57,11 +59,11 @@ def load(data: bytes) -> Image.Image:
|
|||
return Image.open(BytesIO(data))
|
||||
|
||||
|
||||
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
|
||||
def roundtrip(im: Image.Image, **options: Any) -> PngImagePlugin.PngImageFile:
|
||||
out = BytesIO()
|
||||
im.save(out, "PNG", **options)
|
||||
out.seek(0)
|
||||
return Image.open(out)
|
||||
return cast(PngImagePlugin.PngImageFile, Image.open(out))
|
||||
|
||||
|
||||
@skip_unless_feature("zlib")
|
||||
|
@ -100,7 +102,7 @@ class TestFilePng:
|
|||
im = hopper(mode)
|
||||
im.save(test_file)
|
||||
with Image.open(test_file) as reloaded:
|
||||
if mode in ("I;16", "I;16B"):
|
||||
if mode in ("I", "I;16B"):
|
||||
reloaded = reloaded.convert(mode)
|
||||
assert_image_equal(reloaded, im)
|
||||
|
||||
|
@ -302,8 +304,8 @@ class TestFilePng:
|
|||
assert im.getcolors() == [(100, (0, 0, 0, 0))]
|
||||
|
||||
def test_save_grayscale_transparency(self, tmp_path: Path) -> None:
|
||||
for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items():
|
||||
in_file = "Tests/images/" + mode.lower() + "_trns.png"
|
||||
for mode, num_transparent in {"1": 1994, "L": 559, "I;16": 559}.items():
|
||||
in_file = "Tests/images/" + mode.split(";")[0].lower() + "_trns.png"
|
||||
with Image.open(in_file) as im:
|
||||
assert im.mode == mode
|
||||
assert im.info["transparency"] == 255
|
||||
|
@ -781,6 +783,18 @@ class TestFilePng:
|
|||
with Image.open(mystdout) as reloaded:
|
||||
assert_image_equal_tofile(reloaded, TEST_PNG_FILE)
|
||||
|
||||
def test_truncated_end_chunk(self) -> None:
|
||||
with Image.open("Tests/images/truncated_end_chunk.png") as im:
|
||||
with pytest.raises(OSError):
|
||||
im.load()
|
||||
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||
try:
|
||||
with Image.open("Tests/images/truncated_end_chunk.png") as im:
|
||||
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
||||
finally:
|
||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
|
||||
@skip_unless_feature("zlib")
|
||||
|
|
|
@ -88,7 +88,7 @@ def test_16bit_pgm() -> None:
|
|||
assert im.size == (20, 100)
|
||||
assert im.get_format_mimetype() == "image/x-portable-graymap"
|
||||
|
||||
assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.png")
|
||||
assert_image_equal_tofile(im, "Tests/images/16_bit_binary_pgm.tiff")
|
||||
|
||||
|
||||
def test_16bit_pgm_write(tmp_path: Path) -> None:
|
||||
|
|
|
@ -157,7 +157,7 @@ def test_combined_larger_than_size() -> None:
|
|||
("Tests/images/timeout-dedc7a4ebd856d79b4359bbcc79e8ef231ce38f6.psd", OSError),
|
||||
],
|
||||
)
|
||||
def test_crashes(test_file, raises) -> None:
|
||||
def test_crashes(test_file: str, raises) -> None:
|
||||
with open(test_file, "rb") as f:
|
||||
with pytest.raises(raises):
|
||||
with Image.open(f):
|
||||
|
|
|
@ -9,7 +9,7 @@ import pytest
|
|||
|
||||
from PIL import Image, ImageSequence, SpiderImagePlugin
|
||||
|
||||
from .helper import assert_image_equal_tofile, hopper, is_pypy
|
||||
from .helper import assert_image_equal, hopper, is_pypy
|
||||
|
||||
TEST_FILE = "Tests/images/hopper.spider"
|
||||
|
||||
|
@ -160,4 +160,5 @@ def test_odd_size() -> None:
|
|||
im.save(data, format="SPIDER")
|
||||
|
||||
data.seek(0)
|
||||
assert_image_equal_tofile(im, data)
|
||||
with Image.open(data) as im2:
|
||||
assert_image_equal(im, im2)
|
||||
|
|
|
@ -22,8 +22,8 @@ _ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
|
|||
|
||||
|
||||
@pytest.mark.parametrize("mode", _MODES)
|
||||
def test_sanity(mode, tmp_path: Path) -> None:
|
||||
def roundtrip(original_im) -> None:
|
||||
def test_sanity(mode: str, tmp_path: Path) -> None:
|
||||
def roundtrip(original_im: Image.Image) -> None:
|
||||
out = str(tmp_path / "temp.tga")
|
||||
|
||||
original_im.save(out, rle=rle)
|
||||
|
|
|
@ -4,6 +4,8 @@ import os
|
|||
import warnings
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Generator
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -20,6 +22,7 @@ from .helper import (
|
|||
is_win32,
|
||||
)
|
||||
|
||||
ElementTree: ModuleType | None
|
||||
try:
|
||||
from defusedxml import ElementTree
|
||||
except ImportError:
|
||||
|
@ -156,7 +159,7 @@ class TestFileTiff:
|
|||
"resolution_unit, dpi",
|
||||
[(None, 72.8), (2, 72.8), (3, 184.912)],
|
||||
)
|
||||
def test_load_float_dpi(self, resolution_unit, dpi) -> None:
|
||||
def test_load_float_dpi(self, resolution_unit: int | None, dpi: float) -> None:
|
||||
with Image.open(
|
||||
"Tests/images/hopper_float_dpi_" + str(resolution_unit) + ".tif"
|
||||
) as im:
|
||||
|
@ -284,7 +287,7 @@ class TestFileTiff:
|
|||
("Tests/images/multipage.tiff", 3),
|
||||
),
|
||||
)
|
||||
def test_n_frames(self, path, n_frames) -> None:
|
||||
def test_n_frames(self, path: str, n_frames: int) -> None:
|
||||
with Image.open(path) as im:
|
||||
assert im.n_frames == n_frames
|
||||
assert im.is_animated == (n_frames != 1)
|
||||
|
@ -402,7 +405,7 @@ class TestFileTiff:
|
|||
assert len_before == len_after + 1
|
||||
|
||||
@pytest.mark.parametrize("legacy_api", (False, True))
|
||||
def test_load_byte(self, legacy_api) -> None:
|
||||
def test_load_byte(self, legacy_api: bool) -> None:
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
data = b"abc"
|
||||
ret = ifd.load_byte(data, legacy_api)
|
||||
|
@ -431,7 +434,7 @@ class TestFileTiff:
|
|||
assert 0x8825 in im.tag_v2
|
||||
|
||||
def test_exif(self, tmp_path: Path) -> None:
|
||||
def check_exif(exif) -> None:
|
||||
def check_exif(exif: Image.Exif) -> None:
|
||||
assert sorted(exif.keys()) == [
|
||||
256,
|
||||
257,
|
||||
|
@ -511,7 +514,7 @@ class TestFileTiff:
|
|||
assert im.getexif()[273] == (1408, 1907)
|
||||
|
||||
@pytest.mark.parametrize("mode", ("1", "L"))
|
||||
def test_photometric(self, mode, tmp_path: Path) -> None:
|
||||
def test_photometric(self, mode: str, tmp_path: Path) -> None:
|
||||
filename = str(tmp_path / "temp.tif")
|
||||
im = hopper(mode)
|
||||
im.save(filename, tiffinfo={262: 0})
|
||||
|
@ -620,6 +623,7 @@ class TestFileTiff:
|
|||
im.save(outfile, tiffinfo={278: 256})
|
||||
|
||||
with Image.open(outfile) as im:
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
assert im.tag_v2[278] == 256
|
||||
|
||||
def test_strip_raw(self) -> None:
|
||||
|
@ -660,7 +664,7 @@ class TestFileTiff:
|
|||
assert_image_equal_tofile(reloaded, infile)
|
||||
|
||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||
def test_palette(self, mode, tmp_path: Path) -> None:
|
||||
def test_palette(self, mode: str, tmp_path: Path) -> None:
|
||||
outfile = str(tmp_path / "temp.tif")
|
||||
|
||||
im = hopper(mode)
|
||||
|
@ -689,7 +693,7 @@ class TestFileTiff:
|
|||
assert reread.n_frames == 3
|
||||
|
||||
# Test appending using a generator
|
||||
def im_generator(ims):
|
||||
def im_generator(ims: list[Image.Image]) -> Generator[Image.Image, None, None]:
|
||||
yield from ims
|
||||
|
||||
mp = BytesIO()
|
||||
|
@ -860,7 +864,7 @@ class TestFileTiff:
|
|||
],
|
||||
)
|
||||
@pytest.mark.timeout(2)
|
||||
def test_oom(self, test_file) -> None:
|
||||
def test_oom(self, test_file: str) -> None:
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
with pytest.warns(UserWarning):
|
||||
with Image.open(test_file):
|
||||
|
|
|
@ -189,7 +189,9 @@ def test_iptc(tmp_path: Path) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("value, expected", ((b"test", "test"), (1, "1")))
|
||||
def test_writing_other_types_to_ascii(value, expected, tmp_path: Path) -> None:
|
||||
def test_writing_other_types_to_ascii(
|
||||
value: bytes | int, expected: str, tmp_path: Path
|
||||
) -> None:
|
||||
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
|
||||
tag = TiffTags.TAGS_V2[271]
|
||||
|
@ -206,7 +208,7 @@ def test_writing_other_types_to_ascii(value, expected, tmp_path: Path) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("value", (1, IFDRational(1)))
|
||||
def test_writing_other_types_to_bytes(value, tmp_path: Path) -> None:
|
||||
def test_writing_other_types_to_bytes(value: int | IFDRational, tmp_path: Path) -> None:
|
||||
im = hopper()
|
||||
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
|
||||
|
|
|
@ -2,21 +2,18 @@ from __future__ import annotations
|
|||
|
||||
import colorsys
|
||||
import itertools
|
||||
from typing import Callable
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .helper import assert_image_similar, hopper
|
||||
|
||||
|
||||
def int_to_float(i):
|
||||
def int_to_float(i: int) -> float:
|
||||
return i / 255
|
||||
|
||||
|
||||
def str_to_float(i):
|
||||
return ord(i) / 255
|
||||
|
||||
|
||||
def tuple_to_ints(tp):
|
||||
def tuple_to_ints(tp: tuple[float, float, float]) -> tuple[int, int, int]:
|
||||
x, y, z = tp
|
||||
return int(x * 255.0), int(y * 255.0), int(z * 255.0)
|
||||
|
||||
|
@ -25,7 +22,7 @@ def test_sanity() -> None:
|
|||
Image.new("HSV", (100, 100))
|
||||
|
||||
|
||||
def wedge():
|
||||
def wedge() -> Image.Image:
|
||||
w = Image._wedge()
|
||||
w90 = w.rotate(90)
|
||||
|
||||
|
@ -49,7 +46,11 @@ def wedge():
|
|||
return img
|
||||
|
||||
|
||||
def to_xxx_colorsys(im, func, mode):
|
||||
def to_xxx_colorsys(
|
||||
im: Image.Image,
|
||||
func: Callable[[float, float, float], tuple[float, float, float]],
|
||||
mode: str,
|
||||
) -> Image.Image:
|
||||
# convert the hard way using the library colorsys routines.
|
||||
|
||||
(r, g, b) = im.split()
|
||||
|
@ -70,11 +71,11 @@ def to_xxx_colorsys(im, func, mode):
|
|||
return hsv
|
||||
|
||||
|
||||
def to_hsv_colorsys(im):
|
||||
def to_hsv_colorsys(im: Image.Image) -> Image.Image:
|
||||
return to_xxx_colorsys(im, colorsys.rgb_to_hsv, "HSV")
|
||||
|
||||
|
||||
def to_rgb_colorsys(im):
|
||||
def to_rgb_colorsys(im: Image.Image) -> Image.Image:
|
||||
return to_xxx_colorsys(im, colorsys.hsv_to_rgb, "RGB")
|
||||
|
||||
|
||||
|
|
|
@ -138,13 +138,13 @@ class TestImage:
|
|||
assert im.height == 2
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
im.size = (3, 4)
|
||||
im.size = (3, 4) # type: ignore[misc]
|
||||
|
||||
def test_set_mode(self) -> None:
|
||||
im = Image.new("RGB", (1, 1))
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
im.mode = "P"
|
||||
im.mode = "P" # type: ignore[misc]
|
||||
|
||||
def test_invalid_image(self) -> None:
|
||||
im = io.BytesIO(b"")
|
||||
|
@ -685,15 +685,18 @@ class TestImage:
|
|||
_make_new(im, blank_p, ImagePalette.ImagePalette())
|
||||
_make_new(im, blank_pa, ImagePalette.ImagePalette())
|
||||
|
||||
def test_p_from_rgb_rgba(self) -> None:
|
||||
for mode, color in [
|
||||
@pytest.mark.parametrize(
|
||||
"mode, color",
|
||||
(
|
||||
("RGB", "#DDEEFF"),
|
||||
("RGB", (221, 238, 255)),
|
||||
("RGBA", (221, 238, 255, 255)),
|
||||
]:
|
||||
im = Image.new("P", (100, 100), color)
|
||||
expected = Image.new(mode, (100, 100), color)
|
||||
assert_image_equal(im.convert(mode), expected)
|
||||
),
|
||||
)
|
||||
def test_p_from_rgb_rgba(self, mode: str, color: str | tuple[int, ...]) -> None:
|
||||
im = Image.new("P", (100, 100), color)
|
||||
expected = Image.new(mode, (100, 100), color)
|
||||
assert_image_equal(im.convert(mode), expected)
|
||||
|
||||
def test_no_resource_warning_on_save(self, tmp_path: Path) -> None:
|
||||
# https://github.com/python-pillow/Pillow/issues/835
|
||||
|
|
|
@ -14,6 +14,7 @@ from .helper import assert_image_equal, hopper, is_win32
|
|||
|
||||
# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
|
||||
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
|
||||
cffi: ModuleType | None
|
||||
if os.environ.get("PYTHONOPTIMIZE") == "2":
|
||||
cffi = None
|
||||
else:
|
||||
|
|
|
@ -148,9 +148,7 @@ def test_kernel_not_enough_coefficients() -> None:
|
|||
@pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
|
||||
def test_consistency_3x3(mode: str) -> None:
|
||||
with Image.open("Tests/images/hopper.bmp") as source:
|
||||
reference_name = "hopper_emboss"
|
||||
reference_name += "_I.png" if mode == "I" else ".bmp"
|
||||
with Image.open("Tests/images/" + reference_name) as reference:
|
||||
with Image.open("Tests/images/hopper_emboss.bmp") as reference:
|
||||
kernel = ImageFilter.Kernel(
|
||||
(3, 3),
|
||||
# fmt: off
|
||||
|
@ -160,23 +158,13 @@ def test_consistency_3x3(mode: str) -> None:
|
|||
# fmt: on
|
||||
0.3,
|
||||
)
|
||||
source = source.split() * 2
|
||||
reference = reference.split() * 2
|
||||
|
||||
if mode == "I":
|
||||
source = source[0].convert(mode)
|
||||
else:
|
||||
source = Image.merge(mode, source[: len(mode)])
|
||||
reference = Image.merge(mode, reference[: len(mode)])
|
||||
assert_image_equal(source.filter(kernel), reference)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
|
||||
def test_consistency_5x5(mode: str) -> None:
|
||||
with Image.open("Tests/images/hopper.bmp") as source:
|
||||
reference_name = "hopper_emboss_more"
|
||||
reference_name += "_I.png" if mode == "I" else ".bmp"
|
||||
with Image.open("Tests/images/" + reference_name) as reference:
|
||||
with Image.open("Tests/images/hopper_emboss_more.bmp") as reference:
|
||||
kernel = ImageFilter.Kernel(
|
||||
(5, 5),
|
||||
# fmt: off
|
||||
|
@ -188,14 +176,6 @@ def test_consistency_5x5(mode: str) -> None:
|
|||
# fmt: on
|
||||
0.3,
|
||||
)
|
||||
source = source.split() * 2
|
||||
reference = reference.split() * 2
|
||||
|
||||
if mode == "I":
|
||||
source = source[0].convert(mode)
|
||||
else:
|
||||
source = Image.merge(mode, source[: len(mode)])
|
||||
reference = Image.merge(mode, reference[: len(mode)])
|
||||
assert_image_equal(source.filter(kernel), reference)
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from typing import Generator
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -17,19 +16,16 @@ pytestmark = pytest.mark.skipif(
|
|||
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
||||
)
|
||||
|
||||
ims = [
|
||||
hopper(),
|
||||
Image.open("Tests/images/transparent.png"),
|
||||
Image.open("Tests/images/7x13.png"),
|
||||
]
|
||||
|
||||
@pytest.fixture
|
||||
def test_images() -> Generator[Image.Image, None, None]:
|
||||
ims = [
|
||||
hopper(),
|
||||
Image.open("Tests/images/transparent.png"),
|
||||
Image.open("Tests/images/7x13.png"),
|
||||
]
|
||||
try:
|
||||
yield ims
|
||||
finally:
|
||||
for im in ims:
|
||||
im.close()
|
||||
|
||||
def teardown_module() -> None:
|
||||
for im in ims:
|
||||
im.close()
|
||||
|
||||
|
||||
def roundtrip(expected: Image.Image) -> None:
|
||||
|
@ -44,26 +40,26 @@ def roundtrip(expected: Image.Image) -> None:
|
|||
assert_image_equal(result, expected.convert("RGB"))
|
||||
|
||||
|
||||
def test_sanity_1(test_images: Generator[Image.Image, None, None]) -> None:
|
||||
for im in test_images:
|
||||
def test_sanity_1() -> None:
|
||||
for im in ims:
|
||||
roundtrip(im.convert("1"))
|
||||
|
||||
|
||||
def test_sanity_rgb(test_images: Generator[Image.Image, None, None]) -> None:
|
||||
for im in test_images:
|
||||
def test_sanity_rgb() -> None:
|
||||
for im in ims:
|
||||
roundtrip(im.convert("RGB"))
|
||||
|
||||
|
||||
def test_sanity_rgba(test_images: Generator[Image.Image, None, None]) -> None:
|
||||
for im in test_images:
|
||||
def test_sanity_rgba() -> None:
|
||||
for im in ims:
|
||||
roundtrip(im.convert("RGBA"))
|
||||
|
||||
|
||||
def test_sanity_l(test_images: Generator[Image.Image, None, None]) -> None:
|
||||
for im in test_images:
|
||||
def test_sanity_l() -> None:
|
||||
for im in ims:
|
||||
roundtrip(im.convert("L"))
|
||||
|
||||
|
||||
def test_sanity_p(test_images: Generator[Image.Image, None, None]) -> None:
|
||||
for im in test_images:
|
||||
def test_sanity_p() -> None:
|
||||
for im in ims:
|
||||
roundtrip(im.convert("P"))
|
||||
|
|
|
@ -8,7 +8,6 @@ from .helper import CachedProperty, assert_image_equal
|
|||
|
||||
|
||||
class TestImagingPaste:
|
||||
masks = {}
|
||||
size = 128
|
||||
|
||||
def assert_9points_image(
|
||||
|
@ -33,7 +32,7 @@ class TestImagingPaste:
|
|||
def assert_9points_paste(
|
||||
self,
|
||||
im: Image.Image,
|
||||
im2: Image.Image,
|
||||
im2: Image.Image | str | tuple[int, ...],
|
||||
mask: Image.Image,
|
||||
expected: list[tuple[int, int, int, int]],
|
||||
) -> None:
|
||||
|
|
|
@ -237,7 +237,7 @@ class TestCoreResampleConsistency:
|
|||
im = Image.new(mode, (512, 9), fill)
|
||||
return im.resize((9, 512), Image.Resampling.LANCZOS), im.load()[0, 0]
|
||||
|
||||
def run_case(self, case: tuple[Image.Image, Image.Image]) -> None:
|
||||
def run_case(self, case: tuple[Image.Image, int | tuple[int, ...]]) -> None:
|
||||
channel, color = case
|
||||
px = channel.load()
|
||||
for x in range(channel.size[0]):
|
||||
|
|
|
@ -154,7 +154,7 @@ class TestImagingCoreResize:
|
|||
|
||||
def test_unknown_filter(self) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
self.resize(hopper(), (10, 10), 9)
|
||||
self.resize(hopper(), (10, 10), 9) # type: ignore[arg-type]
|
||||
|
||||
def test_cross_platform(self, tmp_path: Path) -> None:
|
||||
# This test is intended for only check for consistent behaviour across
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Callable
|
||||
|
||||
from PIL import Image, ImageChops
|
||||
|
||||
from .helper import assert_image_equal, hopper
|
||||
|
@ -387,7 +389,9 @@ def test_overlay() -> None:
|
|||
|
||||
|
||||
def test_logical() -> None:
|
||||
def table(op, a, b):
|
||||
def table(
|
||||
op: Callable[[Image.Image, Image.Image], Image.Image], a: int, b: int
|
||||
) -> tuple[int, int, int, int]:
|
||||
out = []
|
||||
for x in (a, b):
|
||||
imx = Image.new("1", (1, 1), x)
|
||||
|
|
|
@ -6,6 +6,7 @@ import re
|
|||
import shutil
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -237,7 +238,7 @@ def test_invalid_color_temperature() -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("flag", ("my string", -1))
|
||||
def test_invalid_flag(flag) -> None:
|
||||
def test_invalid_flag(flag: str | int) -> None:
|
||||
with hopper() as im:
|
||||
with pytest.raises(
|
||||
ImageCms.PyCMSError, match="flags must be an integer between 0 and "
|
||||
|
@ -335,19 +336,21 @@ def test_extended_information() -> None:
|
|||
o = ImageCms.getOpenProfile(SRGB)
|
||||
p = o.profile
|
||||
|
||||
def assert_truncated_tuple_equal(tup1, tup2, digits: int = 10) -> None:
|
||||
def assert_truncated_tuple_equal(
|
||||
tup1: tuple[Any, ...], tup2: tuple[Any, ...], digits: int = 10
|
||||
) -> None:
|
||||
# Helper function to reduce precision of tuples of floats
|
||||
# recursively and then check equality.
|
||||
power = 10**digits
|
||||
|
||||
def truncate_tuple(tuple_or_float):
|
||||
def truncate_tuple(tuple_value: tuple[Any, ...]) -> tuple[Any, ...]:
|
||||
return tuple(
|
||||
(
|
||||
truncate_tuple(val)
|
||||
if isinstance(val, tuple)
|
||||
else int(val * power) / power
|
||||
)
|
||||
for val in tuple_or_float
|
||||
for val in tuple_value
|
||||
)
|
||||
|
||||
assert truncate_tuple(tup1) == truncate_tuple(tup2)
|
||||
|
@ -504,8 +507,10 @@ def test_profile_typesafety() -> None:
|
|||
ImageCms.ImageCmsProfile(1).tobytes()
|
||||
|
||||
|
||||
def assert_aux_channel_preserved(mode, transform_in_place, preserved_channel) -> None:
|
||||
def create_test_image():
|
||||
def assert_aux_channel_preserved(
|
||||
mode: str, transform_in_place: bool, preserved_channel: str
|
||||
) -> None:
|
||||
def create_test_image() -> Image.Image:
|
||||
# set up test image with something interesting in the tested aux channel.
|
||||
# fmt: off
|
||||
nine_grid_deltas = [
|
||||
|
@ -633,7 +638,7 @@ def test_auxiliary_channels_isolated() -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
|
||||
def test_rgb_lab(mode) -> None:
|
||||
def test_rgb_lab(mode: str) -> None:
|
||||
im = Image.new(mode, (1, 1))
|
||||
converted_im = im.convert("LAB")
|
||||
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
|
||||
|
|
|
@ -753,7 +753,7 @@ def test_rectangle_I16(bbox: Coords) -> None:
|
|||
draw.rectangle(bbox, outline=0xFFFF)
|
||||
|
||||
# Assert
|
||||
assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
|
||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("bbox", BBOX)
|
||||
|
|
|
@ -5,6 +5,7 @@ import os.path
|
|||
import pytest
|
||||
|
||||
from PIL import Image, ImageDraw, ImageDraw2, features
|
||||
from PIL._typing import Coords
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -56,7 +57,7 @@ def test_sanity() -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("bbox", BBOX)
|
||||
def test_ellipse(bbox) -> None:
|
||||
def test_ellipse(bbox: Coords) -> None:
|
||||
# Arrange
|
||||
im = Image.new("RGB", (W, H))
|
||||
draw = ImageDraw2.Draw(im)
|
||||
|
@ -84,7 +85,7 @@ def test_ellipse_edge() -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("points", POINTS)
|
||||
def test_line(points) -> None:
|
||||
def test_line(points: Coords) -> None:
|
||||
# Arrange
|
||||
im = Image.new("RGB", (W, H))
|
||||
draw = ImageDraw2.Draw(im)
|
||||
|
@ -98,7 +99,7 @@ def test_line(points) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("points", POINTS)
|
||||
def test_line_pen_as_brush(points) -> None:
|
||||
def test_line_pen_as_brush(points: Coords) -> None:
|
||||
# Arrange
|
||||
im = Image.new("RGB", (W, H))
|
||||
draw = ImageDraw2.Draw(im)
|
||||
|
@ -114,7 +115,7 @@ def test_line_pen_as_brush(points) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("points", POINTS)
|
||||
def test_polygon(points) -> None:
|
||||
def test_polygon(points: Coords) -> None:
|
||||
# Arrange
|
||||
im = Image.new("RGB", (W, H))
|
||||
draw = ImageDraw2.Draw(im)
|
||||
|
@ -129,7 +130,7 @@ def test_polygon(points) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("bbox", BBOX)
|
||||
def test_rectangle(bbox) -> None:
|
||||
def test_rectangle(bbox: Coords) -> None:
|
||||
# Arrange
|
||||
im = Image.new("RGB", (W, H))
|
||||
draw = ImageDraw2.Draw(im)
|
||||
|
|
|
@ -22,7 +22,7 @@ def test_crash() -> None:
|
|||
ImageEnhance.Sharpness(im).enhance(0.5)
|
||||
|
||||
|
||||
def _half_transparent_image():
|
||||
def _half_transparent_image() -> Image.Image:
|
||||
# returns an image, half transparent, half solid
|
||||
im = hopper("RGB")
|
||||
|
||||
|
@ -34,7 +34,9 @@ def _half_transparent_image():
|
|||
return im
|
||||
|
||||
|
||||
def _check_alpha(im, original, op, amount) -> None:
|
||||
def _check_alpha(
|
||||
im: Image.Image, original: Image.Image, op: str, amount: float
|
||||
) -> None:
|
||||
assert im.getbands() == original.getbands()
|
||||
assert_image_equal(
|
||||
im.getchannel("A"),
|
||||
|
@ -44,7 +46,7 @@ def _check_alpha(im, original, op, amount) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("op", ("Color", "Brightness", "Contrast", "Sharpness"))
|
||||
def test_alpha(op) -> None:
|
||||
def test_alpha(op: str) -> None:
|
||||
# Issue https://github.com/python-pillow/Pillow/issues/899
|
||||
# Is alpha preserved through image enhancement?
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ SAFEBLOCK = ImageFile.SAFEBLOCK
|
|||
|
||||
class TestImageFile:
|
||||
def test_parser(self) -> None:
|
||||
def roundtrip(format):
|
||||
def roundtrip(format: str) -> tuple[Image.Image, Image.Image]:
|
||||
im = hopper("L").resize((1000, 1000), Image.Resampling.NEAREST)
|
||||
if format in ("MSP", "XBM"):
|
||||
im = im.convert("1")
|
||||
|
|
|
@ -7,11 +7,13 @@ import shutil
|
|||
import sys
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Any, BinaryIO
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont, features
|
||||
from PIL._typing import StrOrBytesPath
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -42,16 +44,16 @@ def test_sanity() -> None:
|
|||
pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")),
|
||||
],
|
||||
)
|
||||
def layout_engine(request):
|
||||
def layout_engine(request: pytest.FixtureRequest) -> ImageFont.Layout:
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def font(layout_engine):
|
||||
def font(layout_engine: ImageFont.Layout) -> ImageFont.FreeTypeFont:
|
||||
return ImageFont.truetype(FONT_PATH, FONT_SIZE, layout_engine=layout_engine)
|
||||
|
||||
|
||||
def test_font_properties(font) -> None:
|
||||
def test_font_properties(font: ImageFont.FreeTypeFont) -> None:
|
||||
assert font.path == FONT_PATH
|
||||
assert font.size == FONT_SIZE
|
||||
|
||||
|
@ -67,7 +69,9 @@ def test_font_properties(font) -> None:
|
|||
assert font_copy.path == second_font_path
|
||||
|
||||
|
||||
def _render(font, layout_engine):
|
||||
def _render(
|
||||
font: StrOrBytesPath | BinaryIO, layout_engine: ImageFont.Layout
|
||||
) -> Image.Image:
|
||||
txt = "Hello World!"
|
||||
ttf = ImageFont.truetype(font, FONT_SIZE, layout_engine=layout_engine)
|
||||
ttf.getbbox(txt)
|
||||
|
@ -80,12 +84,12 @@ def _render(font, layout_engine):
|
|||
|
||||
|
||||
@pytest.mark.parametrize("font", (FONT_PATH, Path(FONT_PATH)))
|
||||
def test_font_with_name(layout_engine, font) -> None:
|
||||
def test_font_with_name(layout_engine: ImageFont.Layout, font: str | Path) -> None:
|
||||
_render(font, layout_engine)
|
||||
|
||||
|
||||
def test_font_with_filelike(layout_engine) -> None:
|
||||
def _font_as_bytes():
|
||||
def test_font_with_filelike(layout_engine: ImageFont.Layout) -> None:
|
||||
def _font_as_bytes() -> BytesIO:
|
||||
with open(FONT_PATH, "rb") as f:
|
||||
font_bytes = BytesIO(f.read())
|
||||
return font_bytes
|
||||
|
@ -102,12 +106,12 @@ def test_font_with_filelike(layout_engine) -> None:
|
|||
# _render(shared_bytes)
|
||||
|
||||
|
||||
def test_font_with_open_file(layout_engine) -> None:
|
||||
def test_font_with_open_file(layout_engine: ImageFont.Layout) -> None:
|
||||
with open(FONT_PATH, "rb") as f:
|
||||
_render(f, layout_engine)
|
||||
|
||||
|
||||
def test_render_equal(layout_engine) -> None:
|
||||
def test_render_equal(layout_engine: ImageFont.Layout) -> None:
|
||||
img_path = _render(FONT_PATH, layout_engine)
|
||||
with open(FONT_PATH, "rb") as f:
|
||||
font_filelike = BytesIO(f.read())
|
||||
|
@ -116,7 +120,7 @@ def test_render_equal(layout_engine) -> None:
|
|||
assert_image_equal(img_path, img_filelike)
|
||||
|
||||
|
||||
def test_non_ascii_path(tmp_path: Path, layout_engine) -> None:
|
||||
def test_non_ascii_path(tmp_path: Path, layout_engine: ImageFont.Layout) -> None:
|
||||
tempfile = str(tmp_path / ("temp_" + chr(128) + ".ttf"))
|
||||
try:
|
||||
shutil.copy(FONT_PATH, tempfile)
|
||||
|
@ -126,7 +130,7 @@ def test_non_ascii_path(tmp_path: Path, layout_engine) -> None:
|
|||
ImageFont.truetype(tempfile, FONT_SIZE, layout_engine=layout_engine)
|
||||
|
||||
|
||||
def test_transparent_background(font) -> None:
|
||||
def test_transparent_background(font: ImageFont.FreeTypeFont) -> None:
|
||||
im = Image.new(mode="RGBA", size=(300, 100))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
|
@ -140,7 +144,7 @@ def test_transparent_background(font) -> None:
|
|||
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
||||
|
||||
|
||||
def test_I16(font) -> None:
|
||||
def test_I16(font: ImageFont.FreeTypeFont) -> None:
|
||||
im = Image.new(mode="I;16", size=(300, 100))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
|
@ -153,7 +157,7 @@ def test_I16(font) -> None:
|
|||
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
||||
|
||||
|
||||
def test_textbbox_equal(font) -> None:
|
||||
def test_textbbox_equal(font: ImageFont.FreeTypeFont) -> None:
|
||||
im = Image.new(mode="RGB", size=(300, 100))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
|
@ -181,7 +185,13 @@ def test_textbbox_equal(font) -> None:
|
|||
),
|
||||
)
|
||||
def test_getlength(
|
||||
text, mode, fontname, size, layout_engine, length_basic, length_raqm
|
||||
text: str,
|
||||
mode: str,
|
||||
fontname: str,
|
||||
size: int,
|
||||
layout_engine: ImageFont.Layout,
|
||||
length_basic: int,
|
||||
length_raqm: float,
|
||||
) -> None:
|
||||
f = ImageFont.truetype("Tests/fonts/" + fontname, size, layout_engine=layout_engine)
|
||||
|
||||
|
@ -207,7 +217,7 @@ def test_float_size() -> None:
|
|||
assert lengths[0] != lengths[1] != lengths[2]
|
||||
|
||||
|
||||
def test_render_multiline(font) -> None:
|
||||
def test_render_multiline(font: ImageFont.FreeTypeFont) -> None:
|
||||
im = Image.new(mode="RGB", size=(300, 100))
|
||||
draw = ImageDraw.Draw(im)
|
||||
line_spacing = font.getbbox("A")[3] + 4
|
||||
|
@ -223,7 +233,7 @@ def test_render_multiline(font) -> None:
|
|||
assert_image_similar_tofile(im, "Tests/images/multiline_text.png", 6.2)
|
||||
|
||||
|
||||
def test_render_multiline_text(font) -> None:
|
||||
def test_render_multiline_text(font: ImageFont.FreeTypeFont) -> None:
|
||||
# Test that text() correctly connects to multiline_text()
|
||||
# and that align defaults to left
|
||||
im = Image.new(mode="RGB", size=(300, 100))
|
||||
|
@ -243,7 +253,9 @@ def test_render_multiline_text(font) -> None:
|
|||
@pytest.mark.parametrize(
|
||||
"align, ext", (("left", ""), ("center", "_center"), ("right", "_right"))
|
||||
)
|
||||
def test_render_multiline_text_align(font, align, ext) -> None:
|
||||
def test_render_multiline_text_align(
|
||||
font: ImageFont.FreeTypeFont, align: str, ext: str
|
||||
) -> None:
|
||||
im = Image.new(mode="RGB", size=(300, 100))
|
||||
draw = ImageDraw.Draw(im)
|
||||
draw.multiline_text((0, 0), TEST_TEXT, font=font, align=align)
|
||||
|
@ -251,7 +263,7 @@ def test_render_multiline_text_align(font, align, ext) -> None:
|
|||
assert_image_similar_tofile(im, f"Tests/images/multiline_text{ext}.png", 0.01)
|
||||
|
||||
|
||||
def test_unknown_align(font) -> None:
|
||||
def test_unknown_align(font: ImageFont.FreeTypeFont) -> None:
|
||||
im = Image.new(mode="RGB", size=(300, 100))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
|
@ -260,14 +272,14 @@ def test_unknown_align(font) -> None:
|
|||
draw.multiline_text((0, 0), TEST_TEXT, font=font, align="unknown")
|
||||
|
||||
|
||||
def test_draw_align(font) -> None:
|
||||
def test_draw_align(font: ImageFont.FreeTypeFont) -> None:
|
||||
im = Image.new("RGB", (300, 100), "white")
|
||||
draw = ImageDraw.Draw(im)
|
||||
line = "some text"
|
||||
draw.text((100, 40), line, (0, 0, 0), font=font, align="left")
|
||||
|
||||
|
||||
def test_multiline_bbox(font) -> None:
|
||||
def test_multiline_bbox(font: ImageFont.FreeTypeFont) -> None:
|
||||
im = Image.new(mode="RGB", size=(300, 100))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
|
@ -285,7 +297,7 @@ def test_multiline_bbox(font) -> None:
|
|||
draw.textbbox((0, 0), TEST_TEXT, font=font, spacing=4)
|
||||
|
||||
|
||||
def test_multiline_width(font) -> None:
|
||||
def test_multiline_width(font: ImageFont.FreeTypeFont) -> None:
|
||||
im = Image.new(mode="RGB", size=(300, 100))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
|
@ -295,7 +307,7 @@ def test_multiline_width(font) -> None:
|
|||
)
|
||||
|
||||
|
||||
def test_multiline_spacing(font) -> None:
|
||||
def test_multiline_spacing(font: ImageFont.FreeTypeFont) -> None:
|
||||
im = Image.new(mode="RGB", size=(300, 100))
|
||||
draw = ImageDraw.Draw(im)
|
||||
draw.multiline_text((0, 0), TEST_TEXT, font=font, spacing=10)
|
||||
|
@ -306,7 +318,9 @@ def test_multiline_spacing(font) -> None:
|
|||
@pytest.mark.parametrize(
|
||||
"orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270)
|
||||
)
|
||||
def test_rotated_transposed_font(font, orientation) -> None:
|
||||
def test_rotated_transposed_font(
|
||||
font: ImageFont.FreeTypeFont, orientation: Image.Transpose
|
||||
) -> None:
|
||||
img_gray = Image.new("L", (100, 100))
|
||||
draw = ImageDraw.Draw(img_gray)
|
||||
word = "testing"
|
||||
|
@ -347,7 +361,9 @@ def test_rotated_transposed_font(font, orientation) -> None:
|
|||
Image.Transpose.FLIP_TOP_BOTTOM,
|
||||
),
|
||||
)
|
||||
def test_unrotated_transposed_font(font, orientation) -> None:
|
||||
def test_unrotated_transposed_font(
|
||||
font: ImageFont.FreeTypeFont, orientation: Image.Transpose
|
||||
) -> None:
|
||||
img_gray = Image.new("L", (100, 100))
|
||||
draw = ImageDraw.Draw(img_gray)
|
||||
word = "testing"
|
||||
|
@ -382,7 +398,9 @@ def test_unrotated_transposed_font(font, orientation) -> None:
|
|||
@pytest.mark.parametrize(
|
||||
"orientation", (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270)
|
||||
)
|
||||
def test_rotated_transposed_font_get_mask(font, orientation) -> None:
|
||||
def test_rotated_transposed_font_get_mask(
|
||||
font: ImageFont.FreeTypeFont, orientation: Image.Transpose
|
||||
) -> None:
|
||||
# Arrange
|
||||
text = "mask this"
|
||||
transposed_font = ImageFont.TransposedFont(font, orientation=orientation)
|
||||
|
@ -403,7 +421,9 @@ def test_rotated_transposed_font_get_mask(font, orientation) -> None:
|
|||
Image.Transpose.FLIP_TOP_BOTTOM,
|
||||
),
|
||||
)
|
||||
def test_unrotated_transposed_font_get_mask(font, orientation) -> None:
|
||||
def test_unrotated_transposed_font_get_mask(
|
||||
font: ImageFont.FreeTypeFont, orientation: Image.Transpose
|
||||
) -> None:
|
||||
# Arrange
|
||||
text = "mask this"
|
||||
transposed_font = ImageFont.TransposedFont(font, orientation=orientation)
|
||||
|
@ -415,11 +435,11 @@ def test_unrotated_transposed_font_get_mask(font, orientation) -> None:
|
|||
assert mask.size == (108, 13)
|
||||
|
||||
|
||||
def test_free_type_font_get_name(font) -> None:
|
||||
def test_free_type_font_get_name(font: ImageFont.FreeTypeFont) -> None:
|
||||
assert ("FreeMono", "Regular") == font.getname()
|
||||
|
||||
|
||||
def test_free_type_font_get_metrics(font) -> None:
|
||||
def test_free_type_font_get_metrics(font: ImageFont.FreeTypeFont) -> None:
|
||||
ascent, descent = font.getmetrics()
|
||||
|
||||
assert isinstance(ascent, int)
|
||||
|
@ -427,7 +447,7 @@ def test_free_type_font_get_metrics(font) -> None:
|
|||
assert (ascent, descent) == (16, 4)
|
||||
|
||||
|
||||
def test_free_type_font_get_mask(font) -> None:
|
||||
def test_free_type_font_get_mask(font: ImageFont.FreeTypeFont) -> None:
|
||||
# Arrange
|
||||
text = "mask this"
|
||||
|
||||
|
@ -473,16 +493,16 @@ def test_default_font() -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("mode", (None, "1", "RGBA"))
|
||||
def test_getbbox(font, mode) -> None:
|
||||
def test_getbbox(font: ImageFont.FreeTypeFont, mode: str | None) -> None:
|
||||
assert (0, 4, 12, 16) == font.getbbox("A", mode)
|
||||
|
||||
|
||||
def test_getbbox_empty(font) -> None:
|
||||
def test_getbbox_empty(font: ImageFont.FreeTypeFont) -> None:
|
||||
# issue #2614, should not crash.
|
||||
assert (0, 0, 0, 0) == font.getbbox("")
|
||||
|
||||
|
||||
def test_render_empty(font) -> None:
|
||||
def test_render_empty(font: ImageFont.FreeTypeFont) -> None:
|
||||
# issue 2666
|
||||
im = Image.new(mode="RGB", size=(300, 100))
|
||||
target = im.copy()
|
||||
|
@ -492,7 +512,7 @@ def test_render_empty(font) -> None:
|
|||
assert_image_equal(im, target)
|
||||
|
||||
|
||||
def test_unicode_extended(layout_engine) -> None:
|
||||
def test_unicode_extended(layout_engine: ImageFont.Layout) -> None:
|
||||
# issue #3777
|
||||
text = "A\u278A\U0001F12B"
|
||||
target = "Tests/images/unicode_extended.png"
|
||||
|
@ -515,21 +535,23 @@ def test_unicode_extended(layout_engine) -> None:
|
|||
(("linux", "/usr/local/share/fonts"), ("darwin", "/System/Library/Fonts")),
|
||||
)
|
||||
@pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
|
||||
def test_find_font(monkeypatch, platform, font_directory) -> None:
|
||||
def _test_fake_loading_font(path_to_fake, fontname) -> None:
|
||||
def test_find_font(
|
||||
monkeypatch: pytest.MonkeyPatch, platform: str, font_directory: str
|
||||
) -> None:
|
||||
def _test_fake_loading_font(path_to_fake: str, fontname: str) -> None:
|
||||
# Make a copy of FreeTypeFont so we can patch the original
|
||||
free_type_font = copy.deepcopy(ImageFont.FreeTypeFont)
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr(ImageFont, "_FreeTypeFont", free_type_font, raising=False)
|
||||
|
||||
def loadable_font(filepath, size, index, encoding, *args, **kwargs):
|
||||
def loadable_font(
|
||||
filepath: str, size: int, index: int, encoding: str, *args: Any
|
||||
):
|
||||
if filepath == path_to_fake:
|
||||
return ImageFont._FreeTypeFont(
|
||||
FONT_PATH, size, index, encoding, *args, **kwargs
|
||||
FONT_PATH, size, index, encoding, *args
|
||||
)
|
||||
return ImageFont._FreeTypeFont(
|
||||
filepath, size, index, encoding, *args, **kwargs
|
||||
)
|
||||
return ImageFont._FreeTypeFont(filepath, size, index, encoding, *args)
|
||||
|
||||
m.setattr(ImageFont, "FreeTypeFont", loadable_font)
|
||||
font = ImageFont.truetype(fontname)
|
||||
|
@ -543,7 +565,7 @@ def test_find_font(monkeypatch, platform, font_directory) -> None:
|
|||
if platform == "linux":
|
||||
monkeypatch.setenv("XDG_DATA_DIRS", "/usr/share/:/usr/local/share/")
|
||||
|
||||
def fake_walker(path):
|
||||
def fake_walker(path: str) -> list[tuple[str, list[str], list[str]]]:
|
||||
if path == font_directory:
|
||||
return [
|
||||
(
|
||||
|
@ -567,7 +589,7 @@ def test_find_font(monkeypatch, platform, font_directory) -> None:
|
|||
_test_fake_loading_font(font_directory + "/Duplicate.ttf", "Duplicate")
|
||||
|
||||
|
||||
def test_imagefont_getters(font) -> None:
|
||||
def test_imagefont_getters(font: ImageFont.FreeTypeFont) -> None:
|
||||
assert font.getmetrics() == (16, 4)
|
||||
assert font.font.ascent == 16
|
||||
assert font.font.descent == 4
|
||||
|
@ -588,7 +610,7 @@ def test_imagefont_getters(font) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("stroke_width", (0, 2))
|
||||
def test_getsize_stroke(font, stroke_width) -> None:
|
||||
def test_getsize_stroke(font: ImageFont.FreeTypeFont, stroke_width: int) -> None:
|
||||
assert font.getbbox("A", stroke_width=stroke_width) == (
|
||||
0 - stroke_width,
|
||||
4 - stroke_width,
|
||||
|
@ -607,7 +629,7 @@ def test_complex_font_settings() -> None:
|
|||
t.getmask("абвг", language="sr")
|
||||
|
||||
|
||||
def test_variation_get(font) -> None:
|
||||
def test_variation_get(font: ImageFont.FreeTypeFont) -> None:
|
||||
freetype = parse_version(features.version_module("freetype2"))
|
||||
if freetype < parse_version("2.9.1"):
|
||||
with pytest.raises(NotImplementedError):
|
||||
|
@ -662,7 +684,7 @@ def test_variation_get(font) -> None:
|
|||
]
|
||||
|
||||
|
||||
def _check_text(font, path, epsilon):
|
||||
def _check_text(font: ImageFont.FreeTypeFont, path: str, epsilon: float) -> None:
|
||||
im = Image.new("RGB", (100, 75), "white")
|
||||
d = ImageDraw.Draw(im)
|
||||
d.text((10, 10), "Text", font=font, fill="black")
|
||||
|
@ -677,7 +699,7 @@ def _check_text(font, path, epsilon):
|
|||
raise
|
||||
|
||||
|
||||
def test_variation_set_by_name(font) -> None:
|
||||
def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None:
|
||||
freetype = parse_version(features.version_module("freetype2"))
|
||||
if freetype < parse_version("2.9.1"):
|
||||
with pytest.raises(NotImplementedError):
|
||||
|
@ -702,7 +724,7 @@ def test_variation_set_by_name(font) -> None:
|
|||
_check_text(font, "Tests/images/variation_tiny_name.png", 40)
|
||||
|
||||
|
||||
def test_variation_set_by_axes(font) -> None:
|
||||
def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None:
|
||||
freetype = parse_version(features.version_module("freetype2"))
|
||||
if freetype < parse_version("2.9.1"):
|
||||
with pytest.raises(NotImplementedError):
|
||||
|
@ -737,7 +759,9 @@ def test_variation_set_by_axes(font) -> None:
|
|||
),
|
||||
ids=("ls", "ms", "rs", "ma", "mt", "mm", "mb", "md"),
|
||||
)
|
||||
def test_anchor(layout_engine, anchor, left, top) -> None:
|
||||
def test_anchor(
|
||||
layout_engine: ImageFont.Layout, anchor: str, left: int, top: int
|
||||
) -> None:
|
||||
name, text = "quick", "Quick"
|
||||
path = f"Tests/images/test_anchor_{name}_{anchor}.png"
|
||||
|
||||
|
@ -782,7 +806,9 @@ def test_anchor(layout_engine, anchor, left, top) -> None:
|
|||
("md", "center"),
|
||||
),
|
||||
)
|
||||
def test_anchor_multiline(layout_engine, anchor, align) -> None:
|
||||
def test_anchor_multiline(
|
||||
layout_engine: ImageFont.Layout, anchor: str, align: str
|
||||
) -> None:
|
||||
target = f"Tests/images/test_anchor_multiline_{anchor}_{align}.png"
|
||||
text = "a\nlong\ntext sample"
|
||||
|
||||
|
@ -800,7 +826,7 @@ def test_anchor_multiline(layout_engine, anchor, align) -> None:
|
|||
assert_image_similar_tofile(im, target, 4)
|
||||
|
||||
|
||||
def test_anchor_invalid(font) -> None:
|
||||
def test_anchor_invalid(font: ImageFont.FreeTypeFont) -> None:
|
||||
im = Image.new("RGB", (100, 100), "white")
|
||||
d = ImageDraw.Draw(im)
|
||||
d.font = font
|
||||
|
@ -826,7 +852,7 @@ def test_anchor_invalid(font) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("bpp", (1, 2, 4, 8))
|
||||
def test_bitmap_font(layout_engine, bpp) -> None:
|
||||
def test_bitmap_font(layout_engine: ImageFont.Layout, bpp: int) -> None:
|
||||
text = "Bitmap Font"
|
||||
layout_name = ["basic", "raqm"][layout_engine]
|
||||
target = f"Tests/images/bitmap_font_{bpp}_{layout_name}.png"
|
||||
|
@ -843,7 +869,7 @@ def test_bitmap_font(layout_engine, bpp) -> None:
|
|||
assert_image_equal_tofile(im, target)
|
||||
|
||||
|
||||
def test_bitmap_font_stroke(layout_engine) -> None:
|
||||
def test_bitmap_font_stroke(layout_engine: ImageFont.Layout) -> None:
|
||||
text = "Bitmap Font"
|
||||
layout_name = ["basic", "raqm"][layout_engine]
|
||||
target = f"Tests/images/bitmap_font_stroke_{layout_name}.png"
|
||||
|
@ -861,7 +887,7 @@ def test_bitmap_font_stroke(layout_engine) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("embedded_color", (False, True))
|
||||
def test_bitmap_blend(layout_engine, embedded_color) -> None:
|
||||
def test_bitmap_blend(layout_engine: ImageFont.Layout, embedded_color: bool) -> None:
|
||||
font = ImageFont.truetype(
|
||||
"Tests/fonts/EBDTTestFont.ttf", size=64, layout_engine=layout_engine
|
||||
)
|
||||
|
@ -873,7 +899,7 @@ def test_bitmap_blend(layout_engine, embedded_color) -> None:
|
|||
assert_image_equal_tofile(im, "Tests/images/bitmap_font_blend.png")
|
||||
|
||||
|
||||
def test_standard_embedded_color(layout_engine) -> None:
|
||||
def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None:
|
||||
txt = "Hello World!"
|
||||
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
|
||||
ttf.getbbox(txt)
|
||||
|
@ -886,7 +912,7 @@ def test_standard_embedded_color(layout_engine) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("fontmode", ("1", "L", "RGBA"))
|
||||
def test_float_coord(layout_engine, fontmode):
|
||||
def test_float_coord(layout_engine: ImageFont.Layout, fontmode: str) -> None:
|
||||
txt = "Hello World!"
|
||||
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
|
||||
|
||||
|
@ -908,7 +934,7 @@ def test_float_coord(layout_engine, fontmode):
|
|||
raise
|
||||
|
||||
|
||||
def test_cbdt(layout_engine) -> None:
|
||||
def test_cbdt(layout_engine: ImageFont.Layout) -> None:
|
||||
try:
|
||||
font = ImageFont.truetype(
|
||||
"Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine
|
||||
|
@ -925,7 +951,7 @@ def test_cbdt(layout_engine) -> None:
|
|||
pytest.skip("freetype compiled without libpng or CBDT support")
|
||||
|
||||
|
||||
def test_cbdt_mask(layout_engine) -> None:
|
||||
def test_cbdt_mask(layout_engine: ImageFont.Layout) -> None:
|
||||
try:
|
||||
font = ImageFont.truetype(
|
||||
"Tests/fonts/CBDTTestFont.ttf", size=64, layout_engine=layout_engine
|
||||
|
@ -942,7 +968,7 @@ def test_cbdt_mask(layout_engine) -> None:
|
|||
pytest.skip("freetype compiled without libpng or CBDT support")
|
||||
|
||||
|
||||
def test_sbix(layout_engine) -> None:
|
||||
def test_sbix(layout_engine: ImageFont.Layout) -> None:
|
||||
try:
|
||||
font = ImageFont.truetype(
|
||||
"Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine
|
||||
|
@ -959,7 +985,7 @@ def test_sbix(layout_engine) -> None:
|
|||
pytest.skip("freetype compiled without libpng or SBIX support")
|
||||
|
||||
|
||||
def test_sbix_mask(layout_engine) -> None:
|
||||
def test_sbix_mask(layout_engine: ImageFont.Layout) -> None:
|
||||
try:
|
||||
font = ImageFont.truetype(
|
||||
"Tests/fonts/chromacheck-sbix.woff", size=300, layout_engine=layout_engine
|
||||
|
@ -977,7 +1003,7 @@ def test_sbix_mask(layout_engine) -> None:
|
|||
|
||||
|
||||
@skip_unless_feature_version("freetype2", "2.10.0")
|
||||
def test_colr(layout_engine) -> None:
|
||||
def test_colr(layout_engine: ImageFont.Layout) -> None:
|
||||
font = ImageFont.truetype(
|
||||
"Tests/fonts/BungeeColor-Regular_colr_Windows.ttf",
|
||||
size=64,
|
||||
|
@ -993,7 +1019,7 @@ def test_colr(layout_engine) -> None:
|
|||
|
||||
|
||||
@skip_unless_feature_version("freetype2", "2.10.0")
|
||||
def test_colr_mask(layout_engine) -> None:
|
||||
def test_colr_mask(layout_engine: ImageFont.Layout) -> None:
|
||||
font = ImageFont.truetype(
|
||||
"Tests/fonts/BungeeColor-Regular_colr_Windows.ttf",
|
||||
size=64,
|
||||
|
@ -1008,7 +1034,7 @@ def test_colr_mask(layout_engine) -> None:
|
|||
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22)
|
||||
|
||||
|
||||
def test_woff2(layout_engine) -> None:
|
||||
def test_woff2(layout_engine: ImageFont.Layout) -> None:
|
||||
try:
|
||||
font = ImageFont.truetype(
|
||||
"Tests/fonts/OpenSans.woff2",
|
||||
|
@ -1042,7 +1068,7 @@ def test_render_mono_size() -> None:
|
|||
assert_image_equal_tofile(im, "Tests/images/text_mono.gif")
|
||||
|
||||
|
||||
def test_too_many_characters(font) -> None:
|
||||
def test_too_many_characters(font: ImageFont.FreeTypeFont) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
font.getlength("A" * 1_000_001)
|
||||
with pytest.raises(ValueError):
|
||||
|
@ -1070,14 +1096,14 @@ def test_too_many_characters(font) -> None:
|
|||
"Tests/fonts/oom-4da0210eb7081b0bf15bf16cc4c52ce02c1e1bbc.ttf",
|
||||
],
|
||||
)
|
||||
def test_oom(test_file) -> None:
|
||||
def test_oom(test_file: str) -> None:
|
||||
with open(test_file, "rb") as f:
|
||||
font = ImageFont.truetype(BytesIO(f.read()))
|
||||
with pytest.raises(Image.DecompressionBombError):
|
||||
font.getmask("Test Text")
|
||||
|
||||
|
||||
def test_raqm_missing_warning(monkeypatch) -> None:
|
||||
def test_raqm_missing_warning(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(ImageFont.core, "HAVE_RAQM", False)
|
||||
with pytest.warns(UserWarning) as record:
|
||||
font = ImageFont.truetype(
|
||||
|
@ -1091,6 +1117,8 @@ def test_raqm_missing_warning(monkeypatch) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("size", [-1, 0])
|
||||
def test_invalid_truetype_sizes_raise_valueerror(layout_engine, size) -> None:
|
||||
def test_invalid_truetype_sizes_raise_valueerror(
|
||||
layout_engine: ImageFont.Layout, size: int
|
||||
) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine)
|
||||
|
|
|
@ -84,6 +84,7 @@ $bmp = New-Object Drawing.Bitmap 200, 200
|
|||
@pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
|
||||
def test_grabclipboard_file(self) -> None:
|
||||
p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE)
|
||||
assert p.stdin is not None
|
||||
p.stdin.write(rb'Set-Clipboard -Path "Tests\images\hopper.gif"')
|
||||
p.communicate()
|
||||
|
||||
|
@ -94,6 +95,7 @@ $bmp = New-Object Drawing.Bitmap 200, 200
|
|||
@pytest.mark.skipif(sys.platform != "win32", reason="Windows only")
|
||||
def test_grabclipboard_png(self) -> None:
|
||||
p = subprocess.Popen(["powershell", "-command", "-"], stdin=subprocess.PIPE)
|
||||
assert p.stdin is not None
|
||||
p.stdin.write(
|
||||
rb"""$bytes = [System.IO.File]::ReadAllBytes("Tests\images\hopper.png")
|
||||
$ms = new-object System.IO.MemoryStream(, $bytes)
|
||||
|
@ -113,7 +115,7 @@ $ms = new-object System.IO.MemoryStream(, $bytes)
|
|||
reason="Linux with wl-clipboard only",
|
||||
)
|
||||
@pytest.mark.parametrize("ext", ("gif", "png", "ico"))
|
||||
def test_grabclipboard_wl_clipboard(self, ext) -> None:
|
||||
def test_grabclipboard_wl_clipboard(self, ext: str) -> None:
|
||||
image_path = "Tests/images/hopper." + ext
|
||||
with open(image_path, "rb") as fp:
|
||||
subprocess.call(["wl-copy"], stdin=fp)
|
||||
|
@ -128,6 +130,6 @@ $ms = new-object System.IO.MemoryStream(, $bytes)
|
|||
reason="Linux with wl-clipboard only",
|
||||
)
|
||||
@pytest.mark.parametrize("arg", ("text", "--clear"))
|
||||
def test_grabclipboard_wl_clipboard_errors(self, arg):
|
||||
def test_grabclipboard_wl_clipboard_errors(self, arg: str) -> None:
|
||||
subprocess.call(["wl-copy", arg])
|
||||
assert ImageGrab.grabclipboard() is None
|
||||
|
|
|
@ -73,15 +73,16 @@ def test_lut(op: str) -> None:
|
|||
|
||||
|
||||
def test_no_operator_loaded() -> None:
|
||||
im = Image.new("L", (1, 1))
|
||||
mop = ImageMorph.MorphOp()
|
||||
with pytest.raises(Exception) as e:
|
||||
mop.apply(None)
|
||||
mop.apply(im)
|
||||
assert str(e.value) == "No operator loaded"
|
||||
with pytest.raises(Exception) as e:
|
||||
mop.match(None)
|
||||
mop.match(im)
|
||||
assert str(e.value) == "No operator loaded"
|
||||
with pytest.raises(Exception) as e:
|
||||
mop.save_lut(None)
|
||||
mop.save_lut("")
|
||||
assert str(e.value) == "No operator loaded"
|
||||
|
||||
|
||||
|
|
|
@ -13,8 +13,12 @@ from .helper import (
|
|||
)
|
||||
|
||||
|
||||
class Deformer:
|
||||
def getmesh(self, im):
|
||||
class Deformer(ImageOps.SupportsGetMesh):
|
||||
def getmesh(
|
||||
self, im: Image.Image
|
||||
) -> list[
|
||||
tuple[tuple[int, int, int, int], tuple[int, int, int, int, int, int, int, int]]
|
||||
]:
|
||||
x, y = im.size
|
||||
return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))]
|
||||
|
||||
|
@ -108,7 +112,7 @@ def test_fit_same_ratio() -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("new_size", ((256, 256), (512, 256), (256, 512)))
|
||||
def test_contain(new_size) -> None:
|
||||
def test_contain(new_size: tuple[int, int]) -> None:
|
||||
im = hopper()
|
||||
new_im = ImageOps.contain(im, new_size)
|
||||
assert new_im.size == (256, 256)
|
||||
|
@ -132,7 +136,7 @@ def test_contain_round() -> None:
|
|||
("hopper.png", (256, 256)), # square
|
||||
),
|
||||
)
|
||||
def test_cover(image_name, expected_size) -> None:
|
||||
def test_cover(image_name: str, expected_size: tuple[int, int]) -> None:
|
||||
with Image.open("Tests/images/" + image_name) as im:
|
||||
new_im = ImageOps.cover(im, (256, 256))
|
||||
assert new_im.size == expected_size
|
||||
|
@ -168,7 +172,7 @@ def test_pad_round() -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("P", "PA"))
|
||||
def test_palette(mode) -> None:
|
||||
def test_palette(mode: str) -> None:
|
||||
im = hopper(mode)
|
||||
|
||||
# Expand
|
||||
|
@ -210,7 +214,7 @@ def test_scale() -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("border", (10, (1, 2, 3, 4)))
|
||||
def test_expand_palette(border) -> None:
|
||||
def test_expand_palette(border: int | tuple[int, int, int, int]) -> None:
|
||||
with Image.open("Tests/images/p_16.tga") as im:
|
||||
im_expanded = ImageOps.expand(im, border, (255, 0, 0))
|
||||
|
||||
|
@ -366,7 +370,7 @@ def test_exif_transpose() -> None:
|
|||
for ext in exts:
|
||||
with Image.open("Tests/images/hopper" + ext) as base_im:
|
||||
|
||||
def check(orientation_im) -> None:
|
||||
def check(orientation_im: Image.Image) -> None:
|
||||
for im in [
|
||||
orientation_im,
|
||||
orientation_im.copy(),
|
||||
|
@ -376,6 +380,7 @@ def test_exif_transpose() -> None:
|
|||
else:
|
||||
original_exif = im.info["exif"]
|
||||
transposed_im = ImageOps.exif_transpose(im)
|
||||
assert transposed_im is not None
|
||||
assert_image_similar(base_im, transposed_im, 17)
|
||||
if orientation_im is base_im:
|
||||
assert "exif" not in im.info
|
||||
|
@ -387,6 +392,7 @@ def test_exif_transpose() -> None:
|
|||
|
||||
# Repeat the operation to test that it does not keep transposing
|
||||
transposed_im2 = ImageOps.exif_transpose(transposed_im)
|
||||
assert transposed_im2 is not None
|
||||
assert_image_equal(transposed_im2, transposed_im)
|
||||
|
||||
check(base_im)
|
||||
|
@ -402,6 +408,7 @@ def test_exif_transpose() -> None:
|
|||
assert im.getexif()[0x0112] == 3
|
||||
|
||||
transposed_im = ImageOps.exif_transpose(im)
|
||||
assert transposed_im is not None
|
||||
assert 0x0112 not in transposed_im.getexif()
|
||||
|
||||
transposed_im._reload_exif()
|
||||
|
@ -414,12 +421,14 @@ def test_exif_transpose() -> None:
|
|||
assert im.getexif()[0x0112] == 3
|
||||
|
||||
transposed_im = ImageOps.exif_transpose(im)
|
||||
assert transposed_im is not None
|
||||
assert 0x0112 not in transposed_im.getexif()
|
||||
|
||||
# Orientation set directly on Image.Exif
|
||||
im = hopper()
|
||||
im.getexif()[0x0112] = 3
|
||||
transposed_im = ImageOps.exif_transpose(im)
|
||||
assert transposed_im is not None
|
||||
assert 0x0112 not in transposed_im.getexif()
|
||||
|
||||
|
||||
|
@ -445,7 +454,7 @@ def test_autocontrast_cutoff() -> None:
|
|||
# Test the cutoff argument of autocontrast
|
||||
with Image.open("Tests/images/bw_gradient.png") as img:
|
||||
|
||||
def autocontrast(cutoff):
|
||||
def autocontrast(cutoff: int | tuple[int, int]):
|
||||
return ImageOps.autocontrast(img, cutoff).histogram()
|
||||
|
||||
assert autocontrast(10) == autocontrast((10, 10))
|
||||
|
@ -486,20 +495,20 @@ def test_autocontrast_mask_real_input() -> None:
|
|||
assert result_nomask != result
|
||||
assert_tuple_approx_equal(
|
||||
ImageStat.Stat(result, mask=rect_mask).median,
|
||||
[195, 202, 184],
|
||||
(195, 202, 184),
|
||||
threshold=2,
|
||||
msg="autocontrast with mask pixel incorrect",
|
||||
)
|
||||
assert_tuple_approx_equal(
|
||||
ImageStat.Stat(result_nomask).median,
|
||||
[119, 106, 79],
|
||||
(119, 106, 79),
|
||||
threshold=2,
|
||||
msg="autocontrast without mask pixel incorrect",
|
||||
)
|
||||
|
||||
|
||||
def test_autocontrast_preserve_tone() -> None:
|
||||
def autocontrast(mode, preserve_tone):
|
||||
def autocontrast(mode: str, preserve_tone: bool) -> list[int]:
|
||||
im = hopper(mode)
|
||||
return ImageOps.autocontrast(im, preserve_tone=preserve_tone).histogram()
|
||||
|
||||
|
@ -533,7 +542,7 @@ def test_autocontrast_preserve_gradient() -> None:
|
|||
@pytest.mark.parametrize(
|
||||
"color", ((255, 255, 255), (127, 255, 0), (127, 127, 127), (0, 0, 0))
|
||||
)
|
||||
def test_autocontrast_preserve_one_color(color) -> None:
|
||||
def test_autocontrast_preserve_one_color(color: tuple[int, int, int]) -> None:
|
||||
img = Image.new("RGB", (10, 10), color)
|
||||
|
||||
# single color images shouldn't change
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Generator
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageFilter
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_images():
|
||||
def test_images() -> Generator[dict[str, Image.Image], None, None]:
|
||||
ims = {
|
||||
"im": Image.open("Tests/images/hopper.ppm"),
|
||||
"snakes": Image.open("Tests/images/color_snakes.png"),
|
||||
|
@ -18,7 +20,7 @@ def test_images():
|
|||
im.close()
|
||||
|
||||
|
||||
def test_filter_api(test_images) -> None:
|
||||
def test_filter_api(test_images: dict[str, Image.Image]) -> None:
|
||||
im = test_images["im"]
|
||||
|
||||
test_filter = ImageFilter.GaussianBlur(2.0)
|
||||
|
@ -26,13 +28,13 @@ def test_filter_api(test_images) -> None:
|
|||
assert i.mode == "RGB"
|
||||
assert i.size == (128, 128)
|
||||
|
||||
test_filter = ImageFilter.UnsharpMask(2.0, 125, 8)
|
||||
i = im.filter(test_filter)
|
||||
test_filter2 = ImageFilter.UnsharpMask(2.0, 125, 8)
|
||||
i = im.filter(test_filter2)
|
||||
assert i.mode == "RGB"
|
||||
assert i.size == (128, 128)
|
||||
|
||||
|
||||
def test_usm_formats(test_images) -> None:
|
||||
def test_usm_formats(test_images: dict[str, Image.Image]) -> None:
|
||||
im = test_images["im"]
|
||||
|
||||
usm = ImageFilter.UnsharpMask
|
||||
|
@ -50,7 +52,7 @@ def test_usm_formats(test_images) -> None:
|
|||
im.convert("YCbCr").filter(usm)
|
||||
|
||||
|
||||
def test_blur_formats(test_images) -> None:
|
||||
def test_blur_formats(test_images: dict[str, Image.Image]) -> None:
|
||||
im = test_images["im"]
|
||||
|
||||
blur = ImageFilter.GaussianBlur
|
||||
|
@ -68,7 +70,7 @@ def test_blur_formats(test_images) -> None:
|
|||
im.convert("YCbCr").filter(blur)
|
||||
|
||||
|
||||
def test_usm_accuracy(test_images) -> None:
|
||||
def test_usm_accuracy(test_images: dict[str, Image.Image]) -> None:
|
||||
snakes = test_images["snakes"]
|
||||
|
||||
src = snakes.convert("RGB")
|
||||
|
@ -77,7 +79,7 @@ def test_usm_accuracy(test_images) -> None:
|
|||
assert i.tobytes() == src.tobytes()
|
||||
|
||||
|
||||
def test_blur_accuracy(test_images) -> None:
|
||||
def test_blur_accuracy(test_images: dict[str, Image.Image]) -> None:
|
||||
snakes = test_images["snakes"]
|
||||
|
||||
i = snakes.filter(ImageFilter.GaussianBlur(0.4))
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
import array
|
||||
import math
|
||||
import struct
|
||||
from typing import Sequence
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -57,7 +58,9 @@ def test_path() -> None:
|
|||
ImagePath.Path((0, 1)),
|
||||
),
|
||||
)
|
||||
def test_path_constructors(coords) -> None:
|
||||
def test_path_constructors(
|
||||
coords: Sequence[float] | array.array[float] | ImagePath.Path,
|
||||
) -> None:
|
||||
# Arrange / Act
|
||||
p = ImagePath.Path(coords)
|
||||
|
||||
|
@ -75,7 +78,9 @@ def test_path_constructors(coords) -> None:
|
|||
[[0.0, 1.0]],
|
||||
),
|
||||
)
|
||||
def test_invalid_path_constructors(coords) -> None:
|
||||
def test_invalid_path_constructors(
|
||||
coords: tuple[str, str] | Sequence[Sequence[int]]
|
||||
) -> None:
|
||||
# Act
|
||||
with pytest.raises(ValueError) as e:
|
||||
ImagePath.Path(coords)
|
||||
|
@ -93,7 +98,7 @@ def test_invalid_path_constructors(coords) -> None:
|
|||
[0, 1, 2],
|
||||
),
|
||||
)
|
||||
def test_path_odd_number_of_coordinates(coords) -> None:
|
||||
def test_path_odd_number_of_coordinates(coords: Sequence[int]) -> None:
|
||||
# Act
|
||||
with pytest.raises(ValueError) as e:
|
||||
ImagePath.Path(coords)
|
||||
|
@ -111,7 +116,9 @@ def test_path_odd_number_of_coordinates(coords) -> None:
|
|||
(1, (0.0, 0.0, 0.0, 0.0)),
|
||||
],
|
||||
)
|
||||
def test_getbbox(coords, expected) -> None:
|
||||
def test_getbbox(
|
||||
coords: int | list[int], expected: tuple[float, float, float, float]
|
||||
) -> None:
|
||||
# Arrange
|
||||
p = ImagePath.Path(coords)
|
||||
|
||||
|
@ -135,7 +142,7 @@ def test_getbbox_no_args() -> None:
|
|||
(list(range(6)), [(0.0, 3.0), (4.0, 9.0), (8.0, 15.0)]),
|
||||
],
|
||||
)
|
||||
def test_map(coords, expected) -> None:
|
||||
def test_map(coords: int | list[int], expected: list[tuple[float, float]]) -> None:
|
||||
# Arrange
|
||||
p = ImagePath.Path(coords)
|
||||
|
||||
|
@ -201,9 +208,9 @@ class Evil:
|
|||
def __init__(self) -> None:
|
||||
self.corrupt = Image.core.path(0x4000000000000000)
|
||||
|
||||
def __getitem__(self, i):
|
||||
def __getitem__(self, i: int) -> bytes:
|
||||
x = self.corrupt[i]
|
||||
return struct.pack("dd", x[0], x[1])
|
||||
|
||||
def __setitem__(self, i, x) -> None:
|
||||
def __setitem__(self, i: int, x: bytes) -> None:
|
||||
self.corrupt[i] = struct.unpack("dd", x)
|
||||
|
|
|
@ -28,7 +28,7 @@ def test_rgb() -> None:
|
|||
|
||||
assert qRgb(0, 0, 0) == qRgba(0, 0, 0, 255)
|
||||
|
||||
def checkrgb(r, g, b) -> None:
|
||||
def checkrgb(r: int, g: int, b: int) -> None:
|
||||
val = ImageQt.rgb(r, g, b)
|
||||
val = val % 2**24 # drop the alpha
|
||||
assert val >> 16 == r
|
||||
|
|
|
@ -26,7 +26,7 @@ def test_sanity(tmp_path: Path) -> None:
|
|||
assert index == 1
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
ImageSequence.Iterator(0)
|
||||
ImageSequence.Iterator(0) # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_iterator() -> None:
|
||||
|
@ -72,6 +72,7 @@ def test_consecutive() -> None:
|
|||
for frame in ImageSequence.Iterator(im):
|
||||
if first_frame is None:
|
||||
first_frame = frame.copy()
|
||||
assert first_frame is not None
|
||||
for frame in ImageSequence.Iterator(im):
|
||||
assert_image_equal(frame, first_frame)
|
||||
break
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageShow
|
||||
|
@ -24,9 +26,9 @@ def test_register() -> None:
|
|||
"order",
|
||||
[-1, 0],
|
||||
)
|
||||
def test_viewer_show(order) -> None:
|
||||
def test_viewer_show(order: int) -> None:
|
||||
class TestViewer(ImageShow.Viewer):
|
||||
def show_image(self, image, **options) -> bool:
|
||||
def show_image(self, image: Image.Image, **options: Any) -> bool:
|
||||
self.methodCalled = True
|
||||
return True
|
||||
|
||||
|
@ -48,7 +50,7 @@ def test_viewer_show(order) -> None:
|
|||
reason="Only run on CIs; hangs on Windows CIs",
|
||||
)
|
||||
@pytest.mark.parametrize("mode", ("1", "I;16", "LA", "RGB", "RGBA"))
|
||||
def test_show(mode) -> None:
|
||||
def test_show(mode: str) -> None:
|
||||
im = hopper(mode)
|
||||
assert ImageShow.show(im)
|
||||
|
||||
|
@ -66,14 +68,15 @@ def test_show_without_viewers() -> None:
|
|||
def test_viewer() -> None:
|
||||
viewer = ImageShow.Viewer()
|
||||
|
||||
assert viewer.get_format(None) is None
|
||||
im = Image.new("L", (1, 1))
|
||||
assert viewer.get_format(im) is None
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
viewer.get_command(None)
|
||||
viewer.get_command("")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("viewer", ImageShow._viewers)
|
||||
def test_viewers(viewer) -> None:
|
||||
def test_viewers(viewer: ImageShow.Viewer) -> None:
|
||||
try:
|
||||
viewer.get_command("test.jpg")
|
||||
except NotImplementedError:
|
||||
|
|
|
@ -70,7 +70,7 @@ if is_win32():
|
|||
]
|
||||
CreateDIBSection.restype = ctypes.wintypes.HBITMAP
|
||||
|
||||
def serialize_dib(bi, pixels):
|
||||
def serialize_dib(bi, pixels) -> bytearray:
|
||||
bf = BITMAPFILEHEADER()
|
||||
bf.bfType = 0x4D42
|
||||
bf.bfOffBits = ctypes.sizeof(bf) + bi.biSize
|
||||
|
|
|
@ -78,7 +78,7 @@ def test_basic(tmp_path: Path, mode: str) -> None:
|
|||
|
||||
|
||||
def test_tobytes() -> None:
|
||||
def tobytes(mode: str) -> Image.Image:
|
||||
def tobytes(mode: str) -> bytes:
|
||||
return Image.new(mode, (1, 1), 1).tobytes()
|
||||
|
||||
order = 1 if Image._ENDIAN == "<" else -1
|
||||
|
|
|
@ -14,7 +14,7 @@ TEST_IMAGE_SIZE = (10, 10)
|
|||
|
||||
|
||||
def test_numpy_to_image() -> None:
|
||||
def to_image(dtype, bands: int = 1, boolean: int = 0):
|
||||
def to_image(dtype, bands: int = 1, boolean: int = 0) -> Image.Image:
|
||||
if bands == 1:
|
||||
if boolean:
|
||||
data = [0, 255] * 50
|
||||
|
@ -99,7 +99,7 @@ def test_1d_array() -> None:
|
|||
assert_image(Image.fromarray(a), "L", (1, 5))
|
||||
|
||||
|
||||
def _test_img_equals_nparray(img, np) -> None:
|
||||
def _test_img_equals_nparray(img: Image.Image, np) -> None:
|
||||
assert len(np.shape) >= 2
|
||||
np_size = np.shape[1], np.shape[0]
|
||||
assert img.size == np_size
|
||||
|
@ -157,7 +157,7 @@ def test_save_tiff_uint16() -> None:
|
|||
("HSV", numpy.uint8),
|
||||
),
|
||||
)
|
||||
def test_to_array(mode, dtype) -> None:
|
||||
def test_to_array(mode: str, dtype) -> None:
|
||||
img = hopper(mode)
|
||||
|
||||
# Resize to non-square
|
||||
|
|
|
@ -4,7 +4,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import ImageQt
|
||||
from PIL import Image, ImageQt
|
||||
|
||||
from .helper import assert_image_equal_tofile, assert_image_similar, hopper
|
||||
|
||||
|
@ -37,7 +37,7 @@ if ImageQt.qt_is_installed:
|
|||
lbl.setPixmap(pixmap1.copy())
|
||||
|
||||
|
||||
def roundtrip(expected) -> None:
|
||||
def roundtrip(expected: Image.Image) -> None:
|
||||
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
|
||||
# Qt saves all pixmaps as rgb
|
||||
assert_image_similar(result, expected.convert("RGB"), 1)
|
||||
|
|
|
@ -17,7 +17,7 @@ if ImageQt.qt_is_installed:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "L", "P", "1"))
|
||||
def test_sanity(mode, tmp_path: Path) -> None:
|
||||
def test_sanity(mode: str, tmp_path: Path) -> None:
|
||||
src = hopper(mode)
|
||||
data = ImageQt.toqimage(src)
|
||||
|
||||
|
|
|
@ -47,9 +47,8 @@ def test_tiff_crashes(test_file: str) -> None:
|
|||
with Image.open(test_file) as im:
|
||||
im.load()
|
||||
except FileNotFoundError:
|
||||
if not on_ci():
|
||||
pytest.skip("test image not found")
|
||||
return
|
||||
raise
|
||||
if on_ci():
|
||||
raise
|
||||
pytest.skip("test image not found")
|
||||
except OSError:
|
||||
pass
|
||||
|
|
|
@ -10,7 +10,7 @@ from PIL import _util
|
|||
@pytest.mark.parametrize(
|
||||
"test_path", ["filename.ext", Path("filename.ext"), PurePath("filename.ext")]
|
||||
)
|
||||
def test_is_path(test_path) -> None:
|
||||
def test_is_path(test_path: str | Path | PurePath) -> None:
|
||||
# Act
|
||||
it_is = _util.is_path(test_path)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
# install openjpeg
|
||||
|
||||
archive=openjpeg-2.5.0
|
||||
archive=openjpeg-2.5.2
|
||||
|
||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||
|
||||
|
|
|
@ -326,7 +326,7 @@ linkcheck_allowed_redirects = {
|
|||
r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*",
|
||||
r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest",
|
||||
r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/",
|
||||
r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*",
|
||||
r"https://tidelift.com/badges/package/pypi/pillow?.*": r"https://img.shields.io/badge/.*",
|
||||
r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg",
|
||||
r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+",
|
||||
}
|
||||
|
|
|
@ -504,3 +504,27 @@ PIL.OleFileIO
|
|||
the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError` in 5.0.0
|
||||
(2018-01). The deprecated file has now been removed from Pillow. If needed, install from
|
||||
PyPI (eg. ``python3 -m pip install olefile``).
|
||||
|
||||
import _imaging
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 2.1.0
|
||||
|
||||
Pillow >= 2.1.0 no longer supports ``import _imaging``.
|
||||
Please use ``from PIL.Image import core as _imaging`` instead.
|
||||
|
||||
Pillow and PIL
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 1.0.0
|
||||
|
||||
Pillow and PIL cannot co-exist in the same environment.
|
||||
Before installing Pillow, please uninstall PIL.
|
||||
|
||||
import Image
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 1.0.0
|
||||
|
||||
Pillow >= 1.0 no longer supports ``import Image``.
|
||||
Please use ``from PIL import Image`` instead.
|
||||
|
|
|
@ -49,7 +49,7 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
|
|||
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
|
||||
:alt: Zenodo
|
||||
|
||||
.. image:: https://tidelift.com/badges/package/pypi/Pillow?style=flat
|
||||
.. image:: https://tidelift.com/badges/package/pypi/pillow?style=flat
|
||||
:target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge
|
||||
:alt: Tidelift
|
||||
|
||||
|
@ -73,10 +73,6 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
|
|||
:target: https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
||||
:alt: Join the chat at https://gitter.im/python-pillow/Pillow
|
||||
|
||||
.. image:: https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg
|
||||
:target: https://twitter.com/PythonPillow
|
||||
:alt: Follow on https://twitter.com/PythonPillow
|
||||
|
||||
.. image:: https://img.shields.io/badge/publish-on%20Mastodon-595aff.svg
|
||||
:target: https://fosstodon.org/@pillow
|
||||
:alt: Follow on https://fosstodon.org/@pillow
|
||||
|
@ -97,7 +93,7 @@ The core image library is designed for fast access to data stored in a few basic
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
installation.rst
|
||||
installation/index.rst
|
||||
handbook/index.rst
|
||||
reference/index.rst
|
||||
porting.rst
|
||||
|
|
|
@ -1,606 +1,29 @@
|
|||
:orphan:
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
activateTab(getOS());
|
||||
});
|
||||
</script>
|
||||
|
||||
Warnings
|
||||
--------
|
||||
|
||||
.. warning:: Pillow and PIL cannot co-exist in the same environment. Before installing Pillow, please uninstall PIL.
|
||||
|
||||
.. warning:: Pillow >= 1.0 no longer supports ``import Image``. Please use ``from PIL import Image`` instead.
|
||||
|
||||
.. warning:: Pillow >= 2.1.0 no longer supports ``import _imaging``. Please use ``from PIL.Image import core as _imaging`` instead.
|
||||
|
||||
Python Support
|
||||
--------------
|
||||
|
||||
Pillow supports these Python versions.
|
||||
|
||||
.. csv-table:: Newer versions
|
||||
:file: newer-versions.csv
|
||||
:header-rows: 1
|
||||
|
||||
.. csv-table:: Older versions
|
||||
:file: older-versions.csv
|
||||
:header-rows: 1
|
||||
|
||||
.. _Linux Installation:
|
||||
.. _macOS Installation:
|
||||
.. _Windows Installation:
|
||||
.. _FreeBSD Installation:
|
||||
|
||||
Basic Installation
|
||||
------------------
|
||||
|
||||
.. note::
|
||||
.. Note:: This section has moved to :ref:`basic-installation`. Please update references accordingly.
|
||||
|
||||
The following instructions will install Pillow with support for
|
||||
most common image formats. See :ref:`external-libraries` for a
|
||||
full list of external libraries supported.
|
||||
Python Support
|
||||
--------------
|
||||
|
||||
Install Pillow with :command:`pip`::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade Pillow
|
||||
|
||||
Optionally, install :pypi:`defusedxml` for Pillow to read XMP data,
|
||||
and :pypi:`olefile` for Pillow to read FPX and MIC images::
|
||||
|
||||
python3 -m pip install --upgrade defusedxml olefile
|
||||
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
We provide binaries for Linux for each of the supported Python
|
||||
versions in the manylinux wheel format. These include support for all
|
||||
optional libraries except libimagequant. Raqm support requires
|
||||
FriBiDi to be installed separately::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade Pillow
|
||||
|
||||
Most major Linux distributions, including Fedora, Ubuntu and ArchLinux
|
||||
also include Pillow in packages that previously contained PIL e.g.
|
||||
``python-imaging``. Debian splits it into two packages, ``python3-pil``
|
||||
and ``python3-pil.imagetk``.
|
||||
|
||||
.. tab:: macOS
|
||||
|
||||
We provide binaries for macOS for each of the supported Python
|
||||
versions in the wheel format. These include support for all optional
|
||||
libraries except libimagequant. Raqm support requires
|
||||
FriBiDi to be installed separately::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade Pillow
|
||||
|
||||
While we provide binaries for both x86-64 and arm64, we do not provide universal2
|
||||
binaries. However, it is simple to combine our current binaries to create one::
|
||||
|
||||
python3 -m pip download --only-binary=:all: --platform macosx_10_10_x86_64 Pillow
|
||||
python3 -m pip download --only-binary=:all: --platform macosx_11_0_arm64 Pillow
|
||||
python3 -m pip install delocate
|
||||
|
||||
Then, with the names of the downloaded wheels, use Python to combine them::
|
||||
|
||||
from delocate.fuse import fuse_wheels
|
||||
fuse_wheels('Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_universal2.whl')
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
We provide Pillow binaries for Windows compiled for the matrix of supported
|
||||
Pythons in the wheel format. These include x86, x86-64 and arm64 versions
|
||||
(with the exception of Python 3.8 on arm64). These binaries include support
|
||||
for all optional libraries except libimagequant and libxcb. Raqm support
|
||||
requires FriBiDi to be installed separately::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade Pillow
|
||||
|
||||
To install Pillow in MSYS2, see `Building on Windows using MSYS2/MinGW`_.
|
||||
|
||||
.. tab:: FreeBSD
|
||||
|
||||
Pillow can be installed on FreeBSD via the official Ports or Packages systems:
|
||||
|
||||
**Ports**::
|
||||
|
||||
cd /usr/ports/graphics/py-pillow && make install clean
|
||||
|
||||
**Packages**::
|
||||
|
||||
pkg install py38-pillow
|
||||
|
||||
.. note::
|
||||
|
||||
The `Pillow FreeBSD port
|
||||
<https://www.freshports.org/graphics/py-pillow/>`_ and packages
|
||||
are tested by the ports team with all supported FreeBSD versions.
|
||||
|
||||
|
||||
.. _Building on Linux:
|
||||
.. _Building on macOS:
|
||||
.. _Building on Windows:
|
||||
.. _Building on Windows using MSYS2/MinGW:
|
||||
.. _Building on FreeBSD:
|
||||
.. _Building on Android:
|
||||
|
||||
Building From Source
|
||||
--------------------
|
||||
|
||||
.. _external-libraries:
|
||||
|
||||
External Libraries
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. note::
|
||||
|
||||
You **do not need to install all supported external libraries** to
|
||||
use Pillow's basic features. **Zlib** and **libjpeg** are required
|
||||
by default.
|
||||
|
||||
.. note::
|
||||
|
||||
There are Dockerfiles in our `Docker images repo
|
||||
<https://github.com/python-pillow/docker-images>`_ to install the
|
||||
dependencies for some operating systems.
|
||||
|
||||
Many of Pillow's features require external libraries:
|
||||
|
||||
* **libjpeg** provides JPEG functionality.
|
||||
|
||||
* Pillow has been tested with libjpeg versions **6b**, **8**, **9-9d** and
|
||||
libjpeg-turbo version **8**.
|
||||
* Starting with Pillow 3.0.0, libjpeg is required by default. It can be
|
||||
disabled with the ``-C jpeg=disable`` flag.
|
||||
|
||||
* **zlib** provides access to compressed PNGs
|
||||
|
||||
* Starting with Pillow 3.0.0, zlib is required by default. It can be
|
||||
disabled with the ``-C zlib=disable`` flag.
|
||||
|
||||
* **libtiff** provides compressed TIFF functionality
|
||||
|
||||
* Pillow has been tested with libtiff versions **3.x** and **4.0-4.6.0**
|
||||
|
||||
* **libfreetype** provides type related services
|
||||
|
||||
* **littlecms** provides color management
|
||||
|
||||
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
|
||||
above uses liblcms2. Tested with **1.19** and **2.7-2.16**.
|
||||
|
||||
* **libwebp** provides the WebP format.
|
||||
|
||||
* Pillow has been tested with version **0.1.3**, which does not read
|
||||
transparent WebP files. Versions **0.3.0** and above support
|
||||
transparency.
|
||||
|
||||
* **openjpeg** provides JPEG 2000 functionality.
|
||||
|
||||
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
|
||||
**2.4.0** and **2.5.0**.
|
||||
* Pillow does **not** support the earlier **1.5** series which ships
|
||||
with Debian Jessie.
|
||||
|
||||
* **libimagequant** provides improved color quantization
|
||||
|
||||
* Pillow has been tested with libimagequant **2.6-4.2.2**
|
||||
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||
the Pillow license, therefore we will not be distributing binaries
|
||||
with libimagequant support enabled.
|
||||
|
||||
* **libraqm** provides complex text layout support.
|
||||
|
||||
* libraqm provides bidirectional text support (using FriBiDi),
|
||||
shaping (using HarfBuzz), and proper script itemization. As a
|
||||
result, Raqm can support most writing systems covered by Unicode.
|
||||
* libraqm depends on the following libraries: FreeType, HarfBuzz,
|
||||
FriBiDi, make sure that you install them before installing libraqm
|
||||
if not available as package in your system.
|
||||
* Setting text direction or font features is not supported without libraqm.
|
||||
* Pillow wheels since version 8.2.0 include a modified version of libraqm that
|
||||
loads libfribidi at runtime if it is installed.
|
||||
On Windows this requires compiling FriBiDi and installing ``fribidi.dll``
|
||||
into a directory listed in the `Dynamic-link library search order (Microsoft Learn)
|
||||
<https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#search-order-for-unpackaged-apps>`_
|
||||
(``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected).
|
||||
See `Build Options`_ to see how to build this version.
|
||||
* Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime.
|
||||
|
||||
* **libxcb** provides X11 screengrab support.
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
If you didn't build Python from source, make sure you have Python's
|
||||
development libraries installed.
|
||||
|
||||
In Debian or Ubuntu::
|
||||
|
||||
sudo apt-get install python3-dev python3-setuptools
|
||||
|
||||
In Fedora, the command is::
|
||||
|
||||
sudo dnf install python3-devel redhat-rpm-config
|
||||
|
||||
In Alpine, the command is::
|
||||
|
||||
sudo apk add python3-dev py3-setuptools
|
||||
|
||||
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
|
||||
|
||||
Prerequisites for **Ubuntu 16.04 LTS - 22.04 LTS** are installed with::
|
||||
|
||||
sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
|
||||
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
|
||||
libharfbuzz-dev libfribidi-dev libxcb1-dev
|
||||
|
||||
To install libraqm, ``sudo apt-get install meson`` and then see
|
||||
``depends/install_raqm.sh``.
|
||||
|
||||
Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with::
|
||||
|
||||
sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \
|
||||
freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \
|
||||
harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel libxcb-devel
|
||||
|
||||
Note that the package manager may be yum or DNF, depending on the
|
||||
exact distribution.
|
||||
|
||||
Prerequisites are installed for **Alpine** with::
|
||||
|
||||
sudo apk add tiff-dev jpeg-dev openjpeg-dev zlib-dev freetype-dev lcms2-dev \
|
||||
libwebp-dev tcl-dev tk-dev harfbuzz-dev fribidi-dev libimagequant-dev \
|
||||
libxcb-dev libpng-dev
|
||||
|
||||
See also the ``Dockerfile``\s in the Test Infrastructure repo
|
||||
(https://github.com/python-pillow/docker-images) for a known working
|
||||
install process for other tested distros.
|
||||
|
||||
.. tab:: macOS
|
||||
|
||||
The Xcode command line tools are required to compile portions of
|
||||
Pillow. The tools are installed by running ``xcode-select --install``
|
||||
from the command line. The command line tools are required even if you
|
||||
have the full Xcode package installed. It may be necessary to run
|
||||
``sudo xcodebuild -license`` to accept the license prior to using the
|
||||
tools.
|
||||
|
||||
The easiest way to install external libraries is via `Homebrew
|
||||
<https://brew.sh/>`_. After you install Homebrew, run::
|
||||
|
||||
brew install libjpeg libtiff little-cms2 openjpeg webp
|
||||
|
||||
To install libraqm on macOS use Homebrew to install its dependencies::
|
||||
|
||||
brew install freetype harfbuzz fribidi
|
||||
|
||||
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
We recommend you use prebuilt wheels from PyPI.
|
||||
If you wish to compile Pillow manually, you can use the build scripts
|
||||
in the ``winbuild`` directory used for CI testing and development.
|
||||
These scripts require Visual Studio 2017 or newer and NASM.
|
||||
|
||||
The scripts also install Pillow from the local copy of the source code, so the
|
||||
`Installing`_ instructions will not be necessary afterwards.
|
||||
|
||||
.. tab:: Windows using MSYS2/MinGW
|
||||
|
||||
To build Pillow using MSYS2, make sure you run the **MSYS2 MinGW 32-bit** or
|
||||
**MSYS2 MinGW 64-bit** console, *not* **MSYS2** directly.
|
||||
|
||||
The following instructions target the 64-bit build, for 32-bit
|
||||
replace all occurrences of ``mingw-w64-x86_64-`` with ``mingw-w64-i686-``.
|
||||
|
||||
Make sure you have Python and GCC installed::
|
||||
|
||||
pacman -S \
|
||||
mingw-w64-x86_64-gcc \
|
||||
mingw-w64-x86_64-python3 \
|
||||
mingw-w64-x86_64-python3-pip \
|
||||
mingw-w64-x86_64-python3-setuptools
|
||||
|
||||
Prerequisites are installed on **MSYS2 MinGW 64-bit** with::
|
||||
|
||||
pacman -S \
|
||||
mingw-w64-x86_64-libjpeg-turbo \
|
||||
mingw-w64-x86_64-zlib \
|
||||
mingw-w64-x86_64-libtiff \
|
||||
mingw-w64-x86_64-freetype \
|
||||
mingw-w64-x86_64-lcms2 \
|
||||
mingw-w64-x86_64-libwebp \
|
||||
mingw-w64-x86_64-openjpeg2 \
|
||||
mingw-w64-x86_64-libimagequant \
|
||||
mingw-w64-x86_64-libraqm
|
||||
|
||||
https://www.msys2.org/docs/python/ states that setuptools >= 60 does not work with
|
||||
MSYS2. To workaround this, before installing Pillow you must run::
|
||||
|
||||
export SETUPTOOLS_USE_DISTUTILS=stdlib
|
||||
|
||||
.. tab:: FreeBSD
|
||||
|
||||
.. Note:: Only FreeBSD 10 and 11 tested
|
||||
|
||||
Make sure you have Python's development libraries installed::
|
||||
|
||||
sudo pkg install python3
|
||||
|
||||
Prerequisites are installed on **FreeBSD 10 or 11** with::
|
||||
|
||||
sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi libxcb
|
||||
|
||||
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
|
||||
|
||||
.. tab:: Android
|
||||
|
||||
Basic Android support has been added for compilation within the Termux
|
||||
environment. The dependencies can be installed by::
|
||||
|
||||
pkg install -y python ndk-sysroot clang make \
|
||||
libjpeg-turbo
|
||||
|
||||
This has been tested within the Termux app on ChromeOS, on x86.
|
||||
|
||||
Installing
|
||||
^^^^^^^^^^
|
||||
|
||||
Once you have installed the prerequisites, to install Pillow from the source
|
||||
code on PyPI, run::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade Pillow --no-binary :all:
|
||||
|
||||
If the prerequisites are installed in the standard library locations
|
||||
for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no
|
||||
additional configuration should be required. If they are installed in
|
||||
a non-standard location, you may need to configure setuptools to use
|
||||
those locations by editing :file:`setup.py` or
|
||||
:file:`pyproject.toml`, or by adding environment variables on the command
|
||||
line::
|
||||
|
||||
CFLAGS="-I/usr/pkg/include" python3 -m pip install --upgrade Pillow --no-binary :all:
|
||||
|
||||
If Pillow has been previously built without the required
|
||||
prerequisites, it may be necessary to manually clear the pip cache or
|
||||
build without cache using the ``--no-cache-dir`` option to force a
|
||||
build with newly installed external libraries.
|
||||
|
||||
If you would like to install from a local copy of the source code instead, you
|
||||
can clone from GitHub with ``git clone https://github.com/python-pillow/Pillow``
|
||||
or download and extract the `compressed archive from PyPI`_.
|
||||
|
||||
After navigating to the Pillow directory, run::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install .
|
||||
|
||||
.. _compressed archive from PyPI: https://pypi.org/project/pillow/#files
|
||||
|
||||
Build Options
|
||||
"""""""""""""
|
||||
|
||||
* Config setting: ``-C parallel=n``. Can also be given
|
||||
with environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
|
||||
multiprocessing to build the extension. Setting ``-C parallel=n``
|
||||
sets the number of CPUs to use to ``n``, or can disable parallel building by
|
||||
using a setting of 1. By default, it uses 4 CPUs, or if 4 are not
|
||||
available, as many as are present.
|
||||
|
||||
* Config settings: ``-C zlib=disable``, ``-C jpeg=disable``,
|
||||
``-C tiff=disable``, ``-C freetype=disable``, ``-C raqm=disable``,
|
||||
``-C lcms=disable``, ``-C webp=disable``, ``-C webpmux=disable``,
|
||||
``-C jpeg2000=disable``, ``-C imagequant=disable``, ``-C xcb=disable``.
|
||||
Disable building the corresponding feature even if the development
|
||||
libraries are present on the building machine.
|
||||
|
||||
* Config settings: ``-C zlib=enable``, ``-C jpeg=enable``,
|
||||
``-C tiff=enable``, ``-C freetype=enable``, ``-C raqm=enable``,
|
||||
``-C lcms=enable``, ``-C webp=enable``, ``-C webpmux=enable``,
|
||||
``-C jpeg2000=enable``, ``-C imagequant=enable``, ``-C xcb=enable``.
|
||||
Require that the corresponding feature is built. The build will raise
|
||||
an exception if the libraries are not found. Webpmux (WebP metadata)
|
||||
relies on WebP support. Tcl and Tk also must be used together.
|
||||
|
||||
* Config settings: ``-C raqm=vendor``, ``-C fribidi=vendor``.
|
||||
These flags are used to compile a modified version of libraqm and
|
||||
a shim that dynamically loads libfribidi at runtime. These are
|
||||
used to compile the standard Pillow wheels. Compiling libraqm requires
|
||||
a C99-compliant compiler.
|
||||
|
||||
* Config setting: ``-C platform-guessing=disable``. Skips all of the
|
||||
platform dependent guessing of include and library directories for
|
||||
automated build systems that configure the proper paths in the
|
||||
environment variables (e.g. Buildroot).
|
||||
|
||||
* Config setting: ``-C debug=true``. Adds a debugging flag to the include and
|
||||
library search process to dump all paths searched for and found to stdout.
|
||||
|
||||
|
||||
Sample usage::
|
||||
|
||||
python3 -m pip install --upgrade Pillow -C [feature]=enable
|
||||
.. Note:: This section has moved to :ref:`python-support`. Please update references accordingly.
|
||||
|
||||
Platform Support
|
||||
----------------
|
||||
|
||||
Current platform support for Pillow. Binary distributions are
|
||||
contributed for each release on a volunteer basis, but the source
|
||||
should compile and run everywhere platform support is listed. In
|
||||
general, we aim to support all current versions of Linux, macOS, and
|
||||
Windows.
|
||||
.. Note:: This section has moved to :ref:`platform-support`. Please update references accordingly.
|
||||
|
||||
Continuous Integration Targets
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Building From Source
|
||||
--------------------
|
||||
|
||||
These platforms are built and tested for every change.
|
||||
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Operating system | Tested Python versions | Tested architecture |
|
||||
+==================================+============================+=====================+
|
||||
| Alpine | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Amazon Linux 2 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Amazon Linux 2023 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Arch | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| CentOS 7 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| CentOS Stream 8 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| CentOS Stream 9 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Debian 11 Bullseye | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Debian 12 Bookworm | 3.11 | x86, x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 38 | 3.11 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 39 | 3.12 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Gentoo | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||
| | 3.12, PyPy3 | |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||
| | 3.12, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.10 | arm64v8, ppc64le, |
|
||||
| | | s390x |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2016 | 3.8 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||
| | 3.12, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.12 | x86 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.9 (MinGW) | x86-64 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.8, 3.9 (Cygwin) | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
|
||||
|
||||
Other Platforms
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
These platforms have been reported to work at the versions mentioned.
|
||||
|
||||
.. note::
|
||||
|
||||
Contributors please test Pillow on your platform then update this
|
||||
document and send a pull request.
|
||||
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Operating system | | Tested Python | | Latest tested | | Tested |
|
||||
| | | versions | | Pillow version | | processors |
|
||||
+==================================+============================+==================+==============+
|
||||
| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.2.0 |arm |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 3.7 | 9.5.0 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
|
||||
| +----------------------------+------------------+--------------+
|
||||
| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 3.6 | 8.4.0 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 3.5 | 7.2.0 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 2.7 | 6.0.0 | |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 3.4 | 5.4.1 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 3.3 | 4.1.0 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Redhat Linux 6 | 2.6 | |x86 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| CentOS 6.3 | 2.7, 3.3 | |x86 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| CentOS 8 | 3.9 | 9.0.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 |
|
||||
| | | PyPy5.3.1, PyPy3 v2.4.0 | | |
|
||||
| +----------------------------+------------------+--------------+
|
||||
| | 2.7 | 4.3.0 |x86-64 |
|
||||
| +----------------------------+------------------+--------------+
|
||||
| | 2.7, 3.2 | 3.4.1 |ppc |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 2.7 | 6.2.2 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 11 | 3.9, 3.10, 3.11, 3.12 | 10.2.0 |arm64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 11 Pro | 3.11, 3.12 | 10.2.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 10 | 3.7 | 7.1.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
.. Note:: This section has moved to :ref:`building-from-source`. Please update references accordingly.
|
||||
|
||||
Old Versions
|
||||
------------
|
||||
|
||||
You can download old distributions from the `release history at PyPI
|
||||
<https://pypi.org/project/pillow/#history>`_ and by direct URL access
|
||||
eg. https://pypi.org/project/pillow/1.0/.
|
||||
.. Note:: This section has moved to :ref:`old-versions`. Please update references accordingly.
|
||||
|
|
97
docs/installation/basic-installation.rst
Normal file
|
@ -0,0 +1,97 @@
|
|||
.. raw:: html
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
activateTab(getOS());
|
||||
});
|
||||
</script>
|
||||
|
||||
.. _basic-installation:
|
||||
|
||||
Basic Installation
|
||||
==================
|
||||
|
||||
.. note::
|
||||
|
||||
The following instructions will install Pillow with support for
|
||||
most common image formats. See :ref:`external-libraries` for a
|
||||
full list of external libraries supported.
|
||||
|
||||
Install Pillow with :command:`pip`::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade Pillow
|
||||
|
||||
Optionally, install :pypi:`defusedxml` for Pillow to read XMP data,
|
||||
and :pypi:`olefile` for Pillow to read FPX and MIC images::
|
||||
|
||||
python3 -m pip install --upgrade defusedxml olefile
|
||||
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
We provide binaries for Linux for each of the supported Python
|
||||
versions in the manylinux wheel format. These include support for all
|
||||
optional libraries except libimagequant. Raqm support requires
|
||||
FriBiDi to be installed separately::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade Pillow
|
||||
|
||||
Most major Linux distributions, including Fedora, Ubuntu and ArchLinux
|
||||
also include Pillow in packages that previously contained PIL e.g.
|
||||
``python-imaging``. Debian splits it into two packages, ``python3-pil``
|
||||
and ``python3-pil.imagetk``.
|
||||
|
||||
.. tab:: macOS
|
||||
|
||||
We provide binaries for macOS for each of the supported Python
|
||||
versions in the wheel format. These include support for all optional
|
||||
libraries except libimagequant. Raqm support requires
|
||||
FriBiDi to be installed separately::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade Pillow
|
||||
|
||||
While we provide binaries for both x86-64 and arm64, we do not provide universal2
|
||||
binaries. However, it is simple to combine our current binaries to create one::
|
||||
|
||||
python3 -m pip download --only-binary=:all: --platform macosx_10_10_x86_64 Pillow
|
||||
python3 -m pip download --only-binary=:all: --platform macosx_11_0_arm64 Pillow
|
||||
python3 -m pip install delocate
|
||||
|
||||
Then, with the names of the downloaded wheels, use Python to combine them::
|
||||
|
||||
from delocate.fuse import fuse_wheels
|
||||
fuse_wheels('Pillow-9.4.0-2-cp39-cp39-macosx_10_10_x86_64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_arm64.whl', 'Pillow-9.4.0-cp39-cp39-macosx_11_0_universal2.whl')
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
We provide Pillow binaries for Windows compiled for the matrix of supported
|
||||
Pythons in the wheel format. These include x86, x86-64 and arm64 versions
|
||||
(with the exception of Python 3.8 on arm64). These binaries include support
|
||||
for all optional libraries except libimagequant and libxcb. Raqm support
|
||||
requires FriBiDi to be installed separately::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade Pillow
|
||||
|
||||
To install Pillow in MSYS2, see :ref:`building-from-source`.
|
||||
|
||||
.. tab:: FreeBSD
|
||||
|
||||
Pillow can be installed on FreeBSD via the official Ports or Packages systems:
|
||||
|
||||
**Ports**::
|
||||
|
||||
cd /usr/ports/graphics/py-pillow && make install clean
|
||||
|
||||
**Packages**::
|
||||
|
||||
pkg install py38-pillow
|
||||
|
||||
.. note::
|
||||
|
||||
The `Pillow FreeBSD port
|
||||
<https://www.freshports.org/graphics/py-pillow/>`_ and packages
|
||||
are tested by the ports team with all supported FreeBSD versions.
|
317
docs/installation/building-from-source.rst
Normal file
|
@ -0,0 +1,317 @@
|
|||
.. raw:: html
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
activateTab(getOS());
|
||||
});
|
||||
</script>
|
||||
|
||||
.. _building-from-source:
|
||||
|
||||
Building From Source
|
||||
====================
|
||||
|
||||
.. _external-libraries:
|
||||
|
||||
External Libraries
|
||||
------------------
|
||||
|
||||
.. note::
|
||||
|
||||
You **do not need to install all supported external libraries** to
|
||||
use Pillow's basic features. **Zlib** and **libjpeg** are required
|
||||
by default.
|
||||
|
||||
.. note::
|
||||
|
||||
There are Dockerfiles in our `Docker images repo
|
||||
<https://github.com/python-pillow/docker-images>`_ to install the
|
||||
dependencies for some operating systems.
|
||||
|
||||
Many of Pillow's features require external libraries:
|
||||
|
||||
* **libjpeg** provides JPEG functionality.
|
||||
|
||||
* Pillow has been tested with libjpeg versions **6b**, **8**, **9-9d** and
|
||||
libjpeg-turbo version **8**.
|
||||
* Starting with Pillow 3.0.0, libjpeg is required by default. It can be
|
||||
disabled with the ``-C jpeg=disable`` flag.
|
||||
|
||||
* **zlib** provides access to compressed PNGs
|
||||
|
||||
* Starting with Pillow 3.0.0, zlib is required by default. It can be
|
||||
disabled with the ``-C zlib=disable`` flag.
|
||||
|
||||
* **libtiff** provides compressed TIFF functionality
|
||||
|
||||
* Pillow has been tested with libtiff versions **3.x** and **4.0-4.6.0**
|
||||
|
||||
* **libfreetype** provides type related services
|
||||
|
||||
* **littlecms** provides color management
|
||||
|
||||
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
|
||||
above uses liblcms2. Tested with **1.19** and **2.7-2.16**.
|
||||
|
||||
* **libwebp** provides the WebP format.
|
||||
|
||||
* Pillow has been tested with version **0.1.3**, which does not read
|
||||
transparent WebP files. Versions **0.3.0** and above support
|
||||
transparency.
|
||||
|
||||
* **openjpeg** provides JPEG 2000 functionality.
|
||||
|
||||
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**,
|
||||
**2.4.0**, **2.5.0** and **2.5.2**.
|
||||
* Pillow does **not** support the earlier **1.5** series which ships
|
||||
with Debian Jessie.
|
||||
|
||||
* **libimagequant** provides improved color quantization
|
||||
|
||||
* Pillow has been tested with libimagequant **2.6-4.2.2**
|
||||
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||
the Pillow license, therefore we will not be distributing binaries
|
||||
with libimagequant support enabled.
|
||||
|
||||
* **libraqm** provides complex text layout support.
|
||||
|
||||
* libraqm provides bidirectional text support (using FriBiDi),
|
||||
shaping (using HarfBuzz), and proper script itemization. As a
|
||||
result, Raqm can support most writing systems covered by Unicode.
|
||||
* libraqm depends on the following libraries: FreeType, HarfBuzz,
|
||||
FriBiDi, make sure that you install them before installing libraqm
|
||||
if not available as package in your system.
|
||||
* Setting text direction or font features is not supported without libraqm.
|
||||
* Pillow wheels since version 8.2.0 include a modified version of libraqm that
|
||||
loads libfribidi at runtime if it is installed.
|
||||
On Windows this requires compiling FriBiDi and installing ``fribidi.dll``
|
||||
into a directory listed in the `Dynamic-link library search order (Microsoft Learn)
|
||||
<https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order#search-order-for-unpackaged-apps>`_
|
||||
(``fribidi-0.dll`` or ``libfribidi-0.dll`` are also detected).
|
||||
See `Build Options`_ to see how to build this version.
|
||||
* Previous versions of Pillow (5.0.0 to 8.1.2) linked libraqm dynamically at runtime.
|
||||
|
||||
* **libxcb** provides X11 screengrab support.
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
If you didn't build Python from source, make sure you have Python's
|
||||
development libraries installed.
|
||||
|
||||
In Debian or Ubuntu::
|
||||
|
||||
sudo apt-get install python3-dev python3-setuptools
|
||||
|
||||
In Fedora, the command is::
|
||||
|
||||
sudo dnf install python3-devel redhat-rpm-config
|
||||
|
||||
In Alpine, the command is::
|
||||
|
||||
sudo apk add python3-dev py3-setuptools
|
||||
|
||||
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
|
||||
|
||||
Prerequisites for **Ubuntu 16.04 LTS - 22.04 LTS** are installed with::
|
||||
|
||||
sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
|
||||
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
|
||||
libharfbuzz-dev libfribidi-dev libxcb1-dev
|
||||
|
||||
To install libraqm, ``sudo apt-get install meson`` and then see
|
||||
``depends/install_raqm.sh``.
|
||||
|
||||
Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with::
|
||||
|
||||
sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \
|
||||
freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \
|
||||
harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel libxcb-devel
|
||||
|
||||
Note that the package manager may be yum or DNF, depending on the
|
||||
exact distribution.
|
||||
|
||||
Prerequisites are installed for **Alpine** with::
|
||||
|
||||
sudo apk add tiff-dev jpeg-dev openjpeg-dev zlib-dev freetype-dev lcms2-dev \
|
||||
libwebp-dev tcl-dev tk-dev harfbuzz-dev fribidi-dev libimagequant-dev \
|
||||
libxcb-dev libpng-dev
|
||||
|
||||
See also the ``Dockerfile``\s in the Test Infrastructure repo
|
||||
(https://github.com/python-pillow/docker-images) for a known working
|
||||
install process for other tested distros.
|
||||
|
||||
.. tab:: macOS
|
||||
|
||||
The Xcode command line tools are required to compile portions of
|
||||
Pillow. The tools are installed by running ``xcode-select --install``
|
||||
from the command line. The command line tools are required even if you
|
||||
have the full Xcode package installed. It may be necessary to run
|
||||
``sudo xcodebuild -license`` to accept the license prior to using the
|
||||
tools.
|
||||
|
||||
The easiest way to install external libraries is via `Homebrew
|
||||
<https://brew.sh/>`_. After you install Homebrew, run::
|
||||
|
||||
brew install libjpeg libtiff little-cms2 openjpeg webp
|
||||
|
||||
To install libraqm on macOS use Homebrew to install its dependencies::
|
||||
|
||||
brew install freetype harfbuzz fribidi
|
||||
|
||||
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
We recommend you use prebuilt wheels from PyPI.
|
||||
If you wish to compile Pillow manually, you can use the build scripts
|
||||
in the ``winbuild`` directory used for CI testing and development.
|
||||
These scripts require Visual Studio 2017 or newer and NASM.
|
||||
|
||||
The scripts also install Pillow from the local copy of the source code, so the
|
||||
`Installing`_ instructions will not be necessary afterwards.
|
||||
|
||||
.. tab:: Windows using MSYS2/MinGW
|
||||
|
||||
To build Pillow using MSYS2, make sure you run the **MSYS2 MinGW 32-bit** or
|
||||
**MSYS2 MinGW 64-bit** console, *not* **MSYS2** directly.
|
||||
|
||||
The following instructions target the 64-bit build, for 32-bit
|
||||
replace all occurrences of ``mingw-w64-x86_64-`` with ``mingw-w64-i686-``.
|
||||
|
||||
Make sure you have Python and GCC installed::
|
||||
|
||||
pacman -S \
|
||||
mingw-w64-x86_64-gcc \
|
||||
mingw-w64-x86_64-python3 \
|
||||
mingw-w64-x86_64-python3-pip \
|
||||
mingw-w64-x86_64-python3-setuptools
|
||||
|
||||
Prerequisites are installed on **MSYS2 MinGW 64-bit** with::
|
||||
|
||||
pacman -S \
|
||||
mingw-w64-x86_64-libjpeg-turbo \
|
||||
mingw-w64-x86_64-zlib \
|
||||
mingw-w64-x86_64-libtiff \
|
||||
mingw-w64-x86_64-freetype \
|
||||
mingw-w64-x86_64-lcms2 \
|
||||
mingw-w64-x86_64-libwebp \
|
||||
mingw-w64-x86_64-openjpeg2 \
|
||||
mingw-w64-x86_64-libimagequant \
|
||||
mingw-w64-x86_64-libraqm
|
||||
|
||||
https://www.msys2.org/docs/python/ states that setuptools >= 60 does not work with
|
||||
MSYS2. To workaround this, before installing Pillow you must run::
|
||||
|
||||
export SETUPTOOLS_USE_DISTUTILS=stdlib
|
||||
|
||||
.. tab:: FreeBSD
|
||||
|
||||
.. Note:: Only FreeBSD 10 and 11 tested
|
||||
|
||||
Make sure you have Python's development libraries installed::
|
||||
|
||||
sudo pkg install python3
|
||||
|
||||
Prerequisites are installed on **FreeBSD 10 or 11** with::
|
||||
|
||||
sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi libxcb
|
||||
|
||||
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
|
||||
|
||||
.. tab:: Android
|
||||
|
||||
Basic Android support has been added for compilation within the Termux
|
||||
environment. The dependencies can be installed by::
|
||||
|
||||
pkg install -y python ndk-sysroot clang make \
|
||||
libjpeg-turbo
|
||||
|
||||
This has been tested within the Termux app on ChromeOS, on x86.
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
Once you have installed the prerequisites, to install Pillow from the source
|
||||
code on PyPI, run::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade Pillow --no-binary :all:
|
||||
|
||||
If the prerequisites are installed in the standard library locations
|
||||
for your machine (e.g. :file:`/usr` or :file:`/usr/local`), no
|
||||
additional configuration should be required. If they are installed in
|
||||
a non-standard location, you may need to configure setuptools to use
|
||||
those locations by editing :file:`setup.py` or
|
||||
:file:`pyproject.toml`, or by adding environment variables on the command
|
||||
line::
|
||||
|
||||
CFLAGS="-I/usr/pkg/include" python3 -m pip install --upgrade Pillow --no-binary :all:
|
||||
|
||||
If Pillow has been previously built without the required
|
||||
prerequisites, it may be necessary to manually clear the pip cache or
|
||||
build without cache using the ``--no-cache-dir`` option to force a
|
||||
build with newly installed external libraries.
|
||||
|
||||
If you would like to install from a local copy of the source code instead, you
|
||||
can clone from GitHub with ``git clone https://github.com/python-pillow/Pillow``
|
||||
or download and extract the `compressed archive from PyPI`_.
|
||||
|
||||
After navigating to the Pillow directory, run::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install .
|
||||
|
||||
.. _compressed archive from PyPI: https://pypi.org/project/pillow/#files
|
||||
|
||||
Build Options
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
* Config setting: ``-C parallel=n``. Can also be given
|
||||
with environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
|
||||
multiprocessing to build the extension. Setting ``-C parallel=n``
|
||||
sets the number of CPUs to use to ``n``, or can disable parallel building by
|
||||
using a setting of 1. By default, it uses 4 CPUs, or if 4 are not
|
||||
available, as many as are present.
|
||||
|
||||
* Config settings: ``-C zlib=disable``, ``-C jpeg=disable``,
|
||||
``-C tiff=disable``, ``-C freetype=disable``, ``-C raqm=disable``,
|
||||
``-C lcms=disable``, ``-C webp=disable``, ``-C webpmux=disable``,
|
||||
``-C jpeg2000=disable``, ``-C imagequant=disable``, ``-C xcb=disable``.
|
||||
Disable building the corresponding feature even if the development
|
||||
libraries are present on the building machine.
|
||||
|
||||
* Config settings: ``-C zlib=enable``, ``-C jpeg=enable``,
|
||||
``-C tiff=enable``, ``-C freetype=enable``, ``-C raqm=enable``,
|
||||
``-C lcms=enable``, ``-C webp=enable``, ``-C webpmux=enable``,
|
||||
``-C jpeg2000=enable``, ``-C imagequant=enable``, ``-C xcb=enable``.
|
||||
Require that the corresponding feature is built. The build will raise
|
||||
an exception if the libraries are not found. Webpmux (WebP metadata)
|
||||
relies on WebP support. Tcl and Tk also must be used together.
|
||||
|
||||
* Config settings: ``-C raqm=vendor``, ``-C fribidi=vendor``.
|
||||
These flags are used to compile a modified version of libraqm and
|
||||
a shim that dynamically loads libfribidi at runtime. These are
|
||||
used to compile the standard Pillow wheels. Compiling libraqm requires
|
||||
a C99-compliant compiler.
|
||||
|
||||
* Config setting: ``-C platform-guessing=disable``. Skips all of the
|
||||
platform dependent guessing of include and library directories for
|
||||
automated build systems that configure the proper paths in the
|
||||
environment variables (e.g. Buildroot).
|
||||
|
||||
* Config setting: ``-C debug=true``. Adds a debugging flag to the include and
|
||||
library search process to dump all paths searched for and found to stdout.
|
||||
|
||||
|
||||
Sample usage::
|
||||
|
||||
python3 -m pip install --upgrade Pillow -C [feature]=enable
|
||||
|
||||
.. _old-versions:
|
||||
|
||||
Old Versions
|
||||
============
|
||||
|
||||
You can download old distributions from the `release history at PyPI
|
||||
<https://pypi.org/project/pillow/#history>`_ and by direct URL access
|
||||
eg. https://pypi.org/project/pillow/1.0/.
|
10
docs/installation/index.rst
Normal file
|
@ -0,0 +1,10 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
basic-installation
|
||||
python-support
|
||||
platform-support
|
||||
building-from-source
|
168
docs/installation/platform-support.rst
Normal file
|
@ -0,0 +1,168 @@
|
|||
.. _platform-support:
|
||||
|
||||
Platform Support
|
||||
================
|
||||
|
||||
Current platform support for Pillow. Binary distributions are
|
||||
contributed for each release on a volunteer basis, but the source
|
||||
should compile and run everywhere platform support is listed. In
|
||||
general, we aim to support all current versions of Linux, macOS, and
|
||||
Windows.
|
||||
|
||||
Continuous Integration Targets
|
||||
------------------------------
|
||||
|
||||
These platforms are built and tested for every change.
|
||||
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Operating system | Tested Python versions | Tested architecture |
|
||||
+==================================+============================+=====================+
|
||||
| Alpine | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Amazon Linux 2 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Amazon Linux 2023 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Arch | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| CentOS 7 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| CentOS Stream 8 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| CentOS Stream 9 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Debian 11 Bullseye | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Debian 12 Bookworm | 3.11 | x86, x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 38 | 3.11 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 39 | 3.12 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Gentoo | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||
| | 3.12, PyPy3 | |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 20.04 LTS (Focal) | 3.8 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||
| | 3.12, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.10 | arm64v8, ppc64le, |
|
||||
| | | s390x |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2016 | 3.8 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||
| | 3.12, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.12 | x86 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.9 (MinGW) | x86-64 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.8, 3.9 (Cygwin) | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
|
||||
|
||||
Other Platforms
|
||||
---------------
|
||||
|
||||
These platforms have been reported to work at the versions mentioned.
|
||||
|
||||
.. note::
|
||||
|
||||
Contributors please test Pillow on your platform then update this
|
||||
document and send a pull request.
|
||||
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Operating system | | Tested Python | | Latest tested | | Tested |
|
||||
| | | versions | | Pillow version | | processors |
|
||||
+==================================+============================+==================+==============+
|
||||
| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.2.0 |arm |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 3.7 | 9.5.0 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
|
||||
| +----------------------------+------------------+--------------+
|
||||
| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.4.0 |x86-64 |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 3.6 | 8.4.0 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.3.2 |x86-64 |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 3.5 | 7.2.0 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 2.7 | 6.0.0 | |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 3.4 | 5.4.1 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 10.13 High Sierra | 2.7, 3.4, 3.5, 3.6 | 4.2.1 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| macOS 10.12 Sierra | 2.7, 3.4, 3.5, 3.6 | 4.1.1 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Mac OS X 10.11 El Capitan | 2.7, 3.4, 3.5, 3.6, 3.7 | 5.4.1 |x86-64 |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 3.3 | 4.1.0 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Mac OS X 10.9 Mavericks | 2.7, 3.2, 3.3, 3.4 | 3.0.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Mac OS X 10.8 Mountain Lion | 2.6, 2.7, 3.2, 3.3 | |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Redhat Linux 6 | 2.6 | |x86 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| CentOS 6.3 | 2.7, 3.3 | |x86 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| CentOS 8 | 3.9 | 9.0.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Fedora 23 | 2.7, 3.4 | 3.1.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Ubuntu Linux 12.04 LTS (Precise) | | 2.6, 3.2, 3.3, 3.4, 3.5 | 3.4.1 |x86,x86-64 |
|
||||
| | | PyPy5.3.1, PyPy3 v2.4.0 | | |
|
||||
| +----------------------------+------------------+--------------+
|
||||
| | 2.7 | 4.3.0 |x86-64 |
|
||||
| +----------------------------+------------------+--------------+
|
||||
| | 2.7, 3.2 | 3.4.1 |ppc |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Ubuntu Linux 10.04 LTS (Lucid) | 2.6 | 2.3.0 |x86,x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Debian 8.2 Jessie | 2.7, 3.4 | 3.1.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Raspbian Jessie | 2.7, 3.4 | 3.1.0 |arm |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Raspbian Stretch | 2.7, 3.5 | 4.0.0 |arm |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Raspberry Pi OS | 3.6, 3.7, 3.8, 3.9 | 8.2.0 |arm |
|
||||
| +----------------------------+------------------+ |
|
||||
| | 2.7 | 6.2.2 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Gentoo Linux | 2.7, 3.2 | 2.1.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| FreeBSD 11.1 | 2.7, 3.4, 3.5, 3.6 | 4.3.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| FreeBSD 10.3 | 2.7, 3.4, 3.5 | 4.2.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 11 | 3.9, 3.10, 3.11, 3.12 | 10.2.0 |arm64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 11 Pro | 3.11, 3.12 | 10.2.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 10 | 3.7 | 7.1.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
14
docs/installation/python-support.rst
Normal file
|
@ -0,0 +1,14 @@
|
|||
.. _python-support:
|
||||
|
||||
Python Support
|
||||
==============
|
||||
|
||||
Pillow supports these Python versions.
|
||||
|
||||
.. csv-table:: Newer versions
|
||||
:file: newer-versions.csv
|
||||
:header-rows: 1
|
||||
|
||||
.. csv-table:: Older versions
|
||||
:file: older-versions.csv
|
||||
:header-rows: 1
|
|
@ -14,6 +14,8 @@ only work on L and RGB images.
|
|||
.. autofunction:: colorize
|
||||
.. autofunction:: crop
|
||||
.. autofunction:: scale
|
||||
.. autoclass:: SupportsGetMesh
|
||||
:show-inheritance:
|
||||
.. autofunction:: deform
|
||||
.. autofunction:: equalize
|
||||
.. autofunction:: expand
|
||||
|
|
|
@ -79,3 +79,9 @@ Portable FloatMap (PFM) images
|
|||
|
||||
Support has been added for reading and writing grayscale (Pf format)
|
||||
Portable FloatMap (PFM) files containing ``F`` data.
|
||||
|
||||
Release GIL when fetching WebP frames
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Python's Global Interpreter Lock is now released when fetching WebP frames from
|
||||
the libwebp decoder.
|
||||
|
|
|
@ -33,6 +33,7 @@ classifiers = [
|
|||
"Topic :: Multimedia :: Graphics :: Capture :: Screen Capture",
|
||||
"Topic :: Multimedia :: Graphics :: Graphics Conversion",
|
||||
"Topic :: Multimedia :: Graphics :: Viewers",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dynamic = [
|
||||
"version",
|
||||
|
@ -79,7 +80,6 @@ Homepage = "https://python-pillow.org"
|
|||
Mastodon = "https://fosstodon.org/@pillow"
|
||||
"Release notes" = "https://pillow.readthedocs.io/en/stable/releasenotes/index.html"
|
||||
Source = "https://github.com/python-pillow/Pillow"
|
||||
Twitter = "https://twitter.com/PythonPillow"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["PIL"]
|
||||
|
@ -140,7 +140,3 @@ follow_imports = "silent"
|
|||
warn_redundant_casts = true
|
||||
warn_unreachable = true
|
||||
warn_unused_ignores = true
|
||||
exclude = [
|
||||
'^src/PIL/FpxImagePlugin.py$',
|
||||
'^src/PIL/MicImagePlugin.py$',
|
||||
]
|
||||
|
|
|
@ -38,7 +38,7 @@ from ._deprecate import deprecate
|
|||
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
||||
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
||||
|
||||
gs_binary = None
|
||||
gs_binary: str | bool | None = None
|
||||
gs_windows_binary = None
|
||||
|
||||
|
||||
|
|