Merge branch 'main' into libjpeg-turbo
|
@ -1 +1 @@
|
||||||
mypy==1.7.1
|
mypy==1.8.0
|
||||||
|
|
2
.github/workflows/test-mingw.yml
vendored
|
@ -67,10 +67,10 @@ jobs:
|
||||||
mingw-w64-x86_64-python3-cffi \
|
mingw-w64-x86_64-python3-cffi \
|
||||||
mingw-w64-x86_64-python3-numpy \
|
mingw-w64-x86_64-python3-numpy \
|
||||||
mingw-w64-x86_64-python3-olefile \
|
mingw-w64-x86_64-python3-olefile \
|
||||||
mingw-w64-x86_64-python3-pip \
|
|
||||||
mingw-w64-x86_64-python3-setuptools \
|
mingw-w64-x86_64-python3-setuptools \
|
||||||
mingw-w64-x86_64-python-pyqt6
|
mingw-w64-x86_64-python-pyqt6
|
||||||
|
|
||||||
|
python3 -m ensurepip
|
||||||
python3 -m pip install pyroma pytest pytest-cov pytest-timeout
|
python3 -m pip install pyroma pytest pytest-cov pytest-timeout
|
||||||
|
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
24
.github/workflows/wheels-dependencies.sh
vendored
|
@ -62,7 +62,7 @@ function build_brotli {
|
||||||
|
|
||||||
function build {
|
function build {
|
||||||
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||||
export BUILD_PREFIX="/usr/local"
|
sudo chown -R runner /usr/local
|
||||||
fi
|
fi
|
||||||
build_xz
|
build_xz
|
||||||
if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
|
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 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 libXau 1.0.11 https://www.x.org/pub/individual/lib
|
||||||
build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
|
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
|
if [[ "$CIBW_ARCHS" == "arm64" ]]; then
|
||||||
cp /Library/Frameworks/Python.framework/Versions/Current/share/pkgconfig/xcb-proto.pc /Library/Frameworks/Python.framework/Versions/Current/lib/pkgconfig/xcb-proto.pc
|
cp /usr/local/share/pkgconfig/xcb-proto.pc /usr/local/lib/pkgconfig
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc
|
sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc
|
||||||
|
@ -87,11 +87,6 @@ function build {
|
||||||
build_tiff
|
build_tiff
|
||||||
build_libpng
|
build_libpng
|
||||||
build_lcms2
|
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
|
build_openjpeg
|
||||||
if [ -f /usr/local/lib64/libopenjp2.so ]; then
|
if [ -f /usr/local/lib64/libopenjp2.so ]; then
|
||||||
cp /usr/local/lib64/libopenjp2.so /usr/local/lib
|
cp /usr/local/lib64/libopenjp2.so /usr/local/lib
|
||||||
|
@ -131,14 +126,19 @@ curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-de
|
||||||
untar pillow-depends-main.zip
|
untar pillow-depends-main.zip
|
||||||
|
|
||||||
if [[ -n "$IS_MACOS" ]]; then
|
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
|
# 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 cairo to fix building harfbuzz on arm64
|
||||||
# remove lcms2 and libpng to fix building openjpeg 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
|
# 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
|
brew install pkg-config
|
||||||
fi
|
fi
|
||||||
|
|
3
.github/workflows/wheels-test.sh
vendored
|
@ -4,6 +4,9 @@ set -e
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
brew install fribidi
|
brew install fribidi
|
||||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
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
|
elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then
|
||||||
apk add curl fribidi
|
apk add curl fribidi
|
||||||
else
|
else
|
||||||
|
|
4
.github/workflows/wheels.yml
vendored
|
@ -99,7 +99,7 @@ jobs:
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
macosx_deployment_target: "10.10"
|
macosx_deployment_target: "10.10"
|
||||||
- name: "macOS arm64"
|
- name: "macOS arm64"
|
||||||
os: macos-latest
|
os: macos-14
|
||||||
cibw_arch: arm64
|
cibw_arch: arm64
|
||||||
macosx_deployment_target: "11.0"
|
macosx_deployment_target: "11.0"
|
||||||
- name: "manylinux2014 and musllinux x86_64"
|
- name: "manylinux2014 and musllinux x86_64"
|
||||||
|
@ -132,7 +132,7 @@ jobs:
|
||||||
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
CIBW_SKIP: pp38-*
|
CIBW_SKIP: pp38-*
|
||||||
CIBW_TEST_SKIP: "*-macosx_arm64"
|
CIBW_TEST_SKIP: cp38-macosx_arm64
|
||||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
|
|
|
@ -5,6 +5,15 @@ Changelog (Pillow)
|
||||||
10.3.0 (unreleased)
|
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
|
- Release GIL while calling ``WebPAnimDecoderGetNext`` #7782
|
||||||
[evanmiller, radarhere]
|
[evanmiller, radarhere]
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,10 @@ def _get_mem_usage() -> float:
|
||||||
|
|
||||||
|
|
||||||
def _test_leak(
|
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:
|
) -> None:
|
||||||
mem_limit = None
|
mem_limit = None
|
||||||
for i in range(max_iterations):
|
for i in range(max_iterations):
|
||||||
|
|
|
@ -17,6 +17,7 @@ def test_ignore_dos_text() -> None:
|
||||||
finally:
|
finally:
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||||
|
|
||||||
|
assert isinstance(im, PngImagePlugin.PngImageFile)
|
||||||
for s in im.text.values():
|
for s in im.text.values():
|
||||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
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"
|
assert msg, "Decompressed Data Too Large"
|
||||||
return
|
return
|
||||||
|
|
||||||
|
assert isinstance(im, PngImagePlugin.PngImageFile)
|
||||||
for s in im.text.values():
|
for s in im.text.values():
|
||||||
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
|
||||||
|
|
||||||
|
@ -57,6 +59,7 @@ def test_dos_total_memory() -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
total_len = 0
|
total_len = 0
|
||||||
|
assert isinstance(im2, PngImagePlugin.PngImageFile)
|
||||||
for txt in im2.text.values():
|
for txt in im2.text.values():
|
||||||
total_len += len(txt)
|
total_len += len(txt)
|
||||||
assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M"
|
assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M"
|
||||||
|
|
|
@ -351,7 +351,7 @@ def is_mingw() -> bool:
|
||||||
|
|
||||||
|
|
||||||
class CachedProperty:
|
class CachedProperty:
|
||||||
def __init__(self, func: Callable[[Any], None]) -> None:
|
def __init__(self, func: Callable[[Any], Any]) -> None:
|
||||||
self.func = func
|
self.func = func
|
||||||
|
|
||||||
def __get__(self, instance: Any, cls: type[Any] | None = None) -> Any:
|
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
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 |
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import ContainerIO, Image
|
from PIL import ContainerIO, Image
|
||||||
|
@ -21,9 +23,16 @@ def test_isatty() -> None:
|
||||||
assert container.isatty() is False
|
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
|
# Arrange
|
||||||
mode = 0
|
|
||||||
with open(TEST_FILE, "rb") as fh:
|
with open(TEST_FILE, "rb") as fh:
|
||||||
container = ContainerIO.ContainerIO(fh, 22, 100)
|
container = ContainerIO.ContainerIO(fh, 22, 100)
|
||||||
|
|
||||||
|
@ -32,35 +41,7 @@ def test_seek_mode_0() -> None:
|
||||||
container.seek(33, mode)
|
container.seek(33, mode)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert container.tell() == 33
|
assert container.tell() == expected_position
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("bytesmode", (True, False))
|
@pytest.mark.parametrize("bytesmode", (True, False))
|
||||||
|
|
|
@ -1113,6 +1113,21 @@ def test_append_images(tmp_path: Path) -> None:
|
||||||
assert reread.n_frames == 10
|
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:
|
def test_transparent_optimize(tmp_path: Path) -> None:
|
||||||
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
|
# From issue #2195, if the transparent color is incorrectly optimized out, GIF loses
|
||||||
# transparency.
|
# transparency.
|
||||||
|
|
|
@ -6,7 +6,7 @@ import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -45,14 +45,20 @@ TEST_FILE = "Tests/images/hopper.jpg"
|
||||||
|
|
||||||
@skip_unless_feature("jpg")
|
@skip_unless_feature("jpg")
|
||||||
class TestFileJpeg:
|
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()
|
out = BytesIO()
|
||||||
im.save(out, "JPEG", **options)
|
im.save(out, "JPEG", **options)
|
||||||
test_bytes = out.tell()
|
test_bytes = out.tell()
|
||||||
out.seek(0)
|
out.seek(0)
|
||||||
im = Image.open(out)
|
reloaded = cast(JpegImagePlugin.JpegImageFile, Image.open(out))
|
||||||
im.bytes = test_bytes # for testing only
|
return reloaded, test_bytes
|
||||||
return im
|
|
||||||
|
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:
|
def gen_random_image(self, size: tuple[int, int], mode: str = "RGB") -> Image.Image:
|
||||||
"""Generates a very hard to compress file
|
"""Generates a very hard to compress file
|
||||||
|
@ -246,13 +252,13 @@ class TestFileJpeg:
|
||||||
im.save(f, progressive=True, quality=94, exif=b" " * 43668)
|
im.save(f, progressive=True, quality=94, exif=b" " * 43668)
|
||||||
|
|
||||||
def test_optimize(self) -> None:
|
def test_optimize(self) -> None:
|
||||||
im1 = self.roundtrip(hopper())
|
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
|
||||||
im2 = self.roundtrip(hopper(), optimize=0)
|
im2, im2_bytes = self.roundtrip_with_bytes(hopper(), optimize=0)
|
||||||
im3 = self.roundtrip(hopper(), optimize=1)
|
im3, im3_bytes = self.roundtrip_with_bytes(hopper(), optimize=1)
|
||||||
assert_image_equal(im1, im2)
|
assert_image_equal(im1, im2)
|
||||||
assert_image_equal(im1, im3)
|
assert_image_equal(im1, im3)
|
||||||
assert im1.bytes >= im2.bytes
|
assert im1_bytes >= im2_bytes
|
||||||
assert im1.bytes >= im3.bytes
|
assert im1_bytes >= im3_bytes
|
||||||
|
|
||||||
def test_optimize_large_buffer(self, tmp_path: Path) -> None:
|
def test_optimize_large_buffer(self, tmp_path: Path) -> None:
|
||||||
# https://github.com/python-pillow/Pillow/issues/148
|
# https://github.com/python-pillow/Pillow/issues/148
|
||||||
|
@ -262,15 +268,15 @@ class TestFileJpeg:
|
||||||
im.save(f, format="JPEG", optimize=True)
|
im.save(f, format="JPEG", optimize=True)
|
||||||
|
|
||||||
def test_progressive(self) -> None:
|
def test_progressive(self) -> None:
|
||||||
im1 = self.roundtrip(hopper())
|
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
|
||||||
im2 = self.roundtrip(hopper(), progressive=False)
|
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 im1.info.get("progressive")
|
||||||
assert not im2.info.get("progressive")
|
assert not im2.info.get("progressive")
|
||||||
assert im3.info.get("progressive")
|
assert im3.info.get("progressive")
|
||||||
|
|
||||||
assert_image_equal(im1, im3)
|
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:
|
def test_progressive_large_buffer(self, tmp_path: Path) -> None:
|
||||||
f = str(tmp_path / "temp.jpg")
|
f = str(tmp_path / "temp.jpg")
|
||||||
|
@ -341,6 +347,7 @@ class TestFileJpeg:
|
||||||
assert exif.get_ifd(0x8825) == {}
|
assert exif.get_ifd(0x8825) == {}
|
||||||
|
|
||||||
transposed = ImageOps.exif_transpose(im)
|
transposed = ImageOps.exif_transpose(im)
|
||||||
|
assert transposed is not None
|
||||||
exif = transposed.getexif()
|
exif = transposed.getexif()
|
||||||
assert exif.get_ifd(0x8825) == {}
|
assert exif.get_ifd(0x8825) == {}
|
||||||
|
|
||||||
|
@ -419,14 +426,14 @@ class TestFileJpeg:
|
||||||
assert im3.info.get("progression")
|
assert im3.info.get("progression")
|
||||||
|
|
||||||
def test_quality(self) -> None:
|
def test_quality(self) -> None:
|
||||||
im1 = self.roundtrip(hopper())
|
im1, im1_bytes = self.roundtrip_with_bytes(hopper())
|
||||||
im2 = self.roundtrip(hopper(), quality=50)
|
im2, im2_bytes = self.roundtrip_with_bytes(hopper(), quality=50)
|
||||||
assert_image(im1, im2.mode, im2.size)
|
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_image(im1, im3.mode, im3.size)
|
||||||
assert im2.bytes > im3.bytes
|
assert im2_bytes > im3_bytes
|
||||||
|
|
||||||
def test_smooth(self) -> None:
|
def test_smooth(self) -> None:
|
||||||
im1 = self.roundtrip(hopper())
|
im1 = self.roundtrip(hopper())
|
||||||
|
|
|
@ -40,10 +40,8 @@ test_card.load()
|
||||||
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
|
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im.save(out, "JPEG2000", **options)
|
im.save(out, "JPEG2000", **options)
|
||||||
test_bytes = out.tell()
|
|
||||||
out.seek(0)
|
out.seek(0)
|
||||||
with Image.open(out) as im:
|
with Image.open(out) as im:
|
||||||
im.bytes = test_bytes # for testing only
|
|
||||||
im.load()
|
im.load()
|
||||||
return im
|
return im
|
||||||
|
|
||||||
|
@ -77,7 +75,9 @@ def test_invalid_file() -> None:
|
||||||
def test_bytesio() -> None:
|
def test_bytesio() -> None:
|
||||||
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
|
with open("Tests/images/test-card-lossless.jp2", "rb") as f:
|
||||||
data = BytesIO(f.read())
|
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
|
# 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)
|
p.feed(data)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
|
assert p.image is not None
|
||||||
assert p.image.size == (640, 480)
|
assert p.image.size == (640, 480)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ from .helper import (
|
||||||
|
|
||||||
@skip_unless_feature("libtiff")
|
@skip_unless_feature("libtiff")
|
||||||
class LibTiffTestCase:
|
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"""
|
"""Helper tests that assert basic sanity about the g4 tiff reading"""
|
||||||
# 1 bit
|
# 1 bit
|
||||||
assert im.mode == "1"
|
assert im.mode == "1"
|
||||||
|
@ -524,7 +524,8 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
im.save(out, compression=compression)
|
im.save(out, compression=compression)
|
||||||
|
|
||||||
def test_fp_leak(self) -> None:
|
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()
|
fn = im.fp.fileno()
|
||||||
|
|
||||||
os.fstat(fn)
|
os.fstat(fn)
|
||||||
|
@ -716,6 +717,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
f.write(src.read())
|
f.write(src.read())
|
||||||
|
|
||||||
im = Image.open(tmpfile)
|
im = Image.open(tmpfile)
|
||||||
|
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||||
im.n_frames
|
im.n_frames
|
||||||
im.close()
|
im.close()
|
||||||
# Should not raise PermissionError.
|
# Should not raise PermissionError.
|
||||||
|
@ -1097,6 +1099,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
|
|
||||||
with Image.open(out) as im:
|
with Image.open(out) as im:
|
||||||
# Assert that there are multiple strips
|
# Assert that there are multiple strips
|
||||||
|
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||||
assert len(im.tag_v2[STRIPOFFSETS]) > 1
|
assert len(im.tag_v2[STRIPOFFSETS]) > 1
|
||||||
|
|
||||||
@pytest.mark.parametrize("argument", (True, False))
|
@pytest.mark.parametrize("argument", (True, False))
|
||||||
|
@ -1113,6 +1116,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
im.save(out, **arguments)
|
im.save(out, **arguments)
|
||||||
|
|
||||||
with Image.open(out) as im:
|
with Image.open(out) as im:
|
||||||
|
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||||
assert len(im.tag_v2[STRIPOFFSETS]) == 1
|
assert len(im.tag_v2[STRIPOFFSETS]) == 1
|
||||||
finally:
|
finally:
|
||||||
TiffImagePlugin.STRIP_SIZE = 65536
|
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/hydro/details/cmx3g8
|
||||||
# https://ghrc.nsstc.nasa.gov/pub/fieldCampaigns/camex3/cmx3g8/browse/
|
# https://ghrc.nsstc.nasa.gov/pub/fieldCampaigns/camex3/cmx3g8/browse/
|
||||||
test_file = "Tests/images/cmx3g8_wv_1998.260_0745_mcidas.ara"
|
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
|
# Act
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
|
|
|
@ -2,11 +2,11 @@ from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image, MpoImagePlugin
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -20,14 +20,11 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"]
|
||||||
pytestmark = skip_unless_feature("jpg")
|
pytestmark = skip_unless_feature("jpg")
|
||||||
|
|
||||||
|
|
||||||
def roundtrip(im: Image.Image, **options: Any) -> Image.Image:
|
def roundtrip(im: Image.Image, **options: Any) -> MpoImagePlugin.MpoImageFile:
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im.save(out, "MPO", **options)
|
im.save(out, "MPO", **options)
|
||||||
test_bytes = out.tell()
|
|
||||||
out.seek(0)
|
out.seek(0)
|
||||||
im = Image.open(out)
|
return cast(MpoImagePlugin.MpoImageFile, Image.open(out))
|
||||||
im.bytes = test_bytes # for testing only
|
|
||||||
return im
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("test_file", test_files)
|
@pytest.mark.parametrize("test_file", test_files)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import zlib
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -59,11 +59,11 @@ def load(data: bytes) -> Image.Image:
|
||||||
return Image.open(BytesIO(data))
|
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()
|
out = BytesIO()
|
||||||
im.save(out, "PNG", **options)
|
im.save(out, "PNG", **options)
|
||||||
out.seek(0)
|
out.seek(0)
|
||||||
return Image.open(out)
|
return cast(PngImagePlugin.PngImageFile, Image.open(out))
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("zlib")
|
@skip_unless_feature("zlib")
|
||||||
|
@ -102,7 +102,7 @@ class TestFilePng:
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
with Image.open(test_file) as reloaded:
|
with Image.open(test_file) as reloaded:
|
||||||
if mode in ("I;16", "I;16B"):
|
if mode in ("I", "I;16B"):
|
||||||
reloaded = reloaded.convert(mode)
|
reloaded = reloaded.convert(mode)
|
||||||
assert_image_equal(reloaded, im)
|
assert_image_equal(reloaded, im)
|
||||||
|
|
||||||
|
@ -304,8 +304,8 @@ class TestFilePng:
|
||||||
assert im.getcolors() == [(100, (0, 0, 0, 0))]
|
assert im.getcolors() == [(100, (0, 0, 0, 0))]
|
||||||
|
|
||||||
def test_save_grayscale_transparency(self, tmp_path: Path) -> None:
|
def test_save_grayscale_transparency(self, tmp_path: Path) -> None:
|
||||||
for mode, num_transparent in {"1": 1994, "L": 559, "I": 559}.items():
|
for mode, num_transparent in {"1": 1994, "L": 559, "I;16": 559}.items():
|
||||||
in_file = "Tests/images/" + mode.lower() + "_trns.png"
|
in_file = "Tests/images/" + mode.split(";")[0].lower() + "_trns.png"
|
||||||
with Image.open(in_file) as im:
|
with Image.open(in_file) as im:
|
||||||
assert im.mode == mode
|
assert im.mode == mode
|
||||||
assert im.info["transparency"] == 255
|
assert im.info["transparency"] == 255
|
||||||
|
@ -783,6 +783,18 @@ class TestFilePng:
|
||||||
with Image.open(mystdout) as reloaded:
|
with Image.open(mystdout) as reloaded:
|
||||||
assert_image_equal_tofile(reloaded, TEST_PNG_FILE)
|
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")
|
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
|
||||||
@skip_unless_feature("zlib")
|
@skip_unless_feature("zlib")
|
||||||
|
|
|
@ -88,7 +88,7 @@ def test_16bit_pgm() -> None:
|
||||||
assert im.size == (20, 100)
|
assert im.size == (20, 100)
|
||||||
assert im.get_format_mimetype() == "image/x-portable-graymap"
|
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:
|
def test_16bit_pgm_write(tmp_path: Path) -> None:
|
||||||
|
|
|
@ -9,7 +9,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageSequence, SpiderImagePlugin
|
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"
|
TEST_FILE = "Tests/images/hopper.spider"
|
||||||
|
|
||||||
|
@ -160,4 +160,5 @@ def test_odd_size() -> None:
|
||||||
im.save(data, format="SPIDER")
|
im.save(data, format="SPIDER")
|
||||||
|
|
||||||
data.seek(0)
|
data.seek(0)
|
||||||
assert_image_equal_tofile(im, data)
|
with Image.open(data) as im2:
|
||||||
|
assert_image_equal(im, im2)
|
||||||
|
|
|
@ -623,6 +623,7 @@ class TestFileTiff:
|
||||||
im.save(outfile, tiffinfo={278: 256})
|
im.save(outfile, tiffinfo={278: 256})
|
||||||
|
|
||||||
with Image.open(outfile) as im:
|
with Image.open(outfile) as im:
|
||||||
|
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||||
assert im.tag_v2[278] == 256
|
assert im.tag_v2[278] == 256
|
||||||
|
|
||||||
def test_strip_raw(self) -> None:
|
def test_strip_raw(self) -> None:
|
||||||
|
|
|
@ -138,13 +138,13 @@ class TestImage:
|
||||||
assert im.height == 2
|
assert im.height == 2
|
||||||
|
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
im.size = (3, 4)
|
im.size = (3, 4) # type: ignore[misc]
|
||||||
|
|
||||||
def test_set_mode(self) -> None:
|
def test_set_mode(self) -> None:
|
||||||
im = Image.new("RGB", (1, 1))
|
im = Image.new("RGB", (1, 1))
|
||||||
|
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
im.mode = "P"
|
im.mode = "P" # type: ignore[misc]
|
||||||
|
|
||||||
def test_invalid_image(self) -> None:
|
def test_invalid_image(self) -> None:
|
||||||
im = io.BytesIO(b"")
|
im = io.BytesIO(b"")
|
||||||
|
|
|
@ -14,6 +14,7 @@ from .helper import assert_image_equal, hopper, is_win32
|
||||||
|
|
||||||
# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
|
# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
|
||||||
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
|
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
|
||||||
|
cffi: ModuleType | None
|
||||||
if os.environ.get("PYTHONOPTIMIZE") == "2":
|
if os.environ.get("PYTHONOPTIMIZE") == "2":
|
||||||
cffi = None
|
cffi = None
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -148,9 +148,7 @@ def test_kernel_not_enough_coefficients() -> None:
|
||||||
@pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
|
@pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
|
||||||
def test_consistency_3x3(mode: str) -> None:
|
def test_consistency_3x3(mode: str) -> None:
|
||||||
with Image.open("Tests/images/hopper.bmp") as source:
|
with Image.open("Tests/images/hopper.bmp") as source:
|
||||||
reference_name = "hopper_emboss"
|
with Image.open("Tests/images/hopper_emboss.bmp") as reference:
|
||||||
reference_name += "_I.png" if mode == "I" else ".bmp"
|
|
||||||
with Image.open("Tests/images/" + reference_name) as reference:
|
|
||||||
kernel = ImageFilter.Kernel(
|
kernel = ImageFilter.Kernel(
|
||||||
(3, 3),
|
(3, 3),
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -160,23 +158,13 @@ def test_consistency_3x3(mode: str) -> None:
|
||||||
# fmt: on
|
# fmt: on
|
||||||
0.3,
|
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)
|
assert_image_equal(source.filter(kernel), reference)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
|
@pytest.mark.parametrize("mode", ("L", "LA", "I", "RGB", "CMYK"))
|
||||||
def test_consistency_5x5(mode: str) -> None:
|
def test_consistency_5x5(mode: str) -> None:
|
||||||
with Image.open("Tests/images/hopper.bmp") as source:
|
with Image.open("Tests/images/hopper.bmp") as source:
|
||||||
reference_name = "hopper_emboss_more"
|
with Image.open("Tests/images/hopper_emboss_more.bmp") as reference:
|
||||||
reference_name += "_I.png" if mode == "I" else ".bmp"
|
|
||||||
with Image.open("Tests/images/" + reference_name) as reference:
|
|
||||||
kernel = ImageFilter.Kernel(
|
kernel = ImageFilter.Kernel(
|
||||||
(5, 5),
|
(5, 5),
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -188,14 +176,6 @@ def test_consistency_5x5(mode: str) -> None:
|
||||||
# fmt: on
|
# fmt: on
|
||||||
0.3,
|
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)
|
assert_image_equal(source.filter(kernel), reference)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Generator
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -17,19 +16,16 @@ pytestmark = pytest.mark.skipif(
|
||||||
not ImageQt.qt_is_installed, reason="Qt bindings are not installed"
|
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]:
|
def teardown_module() -> None:
|
||||||
ims = [
|
for im in ims:
|
||||||
hopper(),
|
im.close()
|
||||||
Image.open("Tests/images/transparent.png"),
|
|
||||||
Image.open("Tests/images/7x13.png"),
|
|
||||||
]
|
|
||||||
try:
|
|
||||||
yield ims
|
|
||||||
finally:
|
|
||||||
for im in ims:
|
|
||||||
im.close()
|
|
||||||
|
|
||||||
|
|
||||||
def roundtrip(expected: Image.Image) -> None:
|
def roundtrip(expected: Image.Image) -> None:
|
||||||
|
@ -44,26 +40,26 @@ def roundtrip(expected: Image.Image) -> None:
|
||||||
assert_image_equal(result, expected.convert("RGB"))
|
assert_image_equal(result, expected.convert("RGB"))
|
||||||
|
|
||||||
|
|
||||||
def test_sanity_1(test_images: Generator[Image.Image, None, None]) -> None:
|
def test_sanity_1() -> None:
|
||||||
for im in test_images:
|
for im in ims:
|
||||||
roundtrip(im.convert("1"))
|
roundtrip(im.convert("1"))
|
||||||
|
|
||||||
|
|
||||||
def test_sanity_rgb(test_images: Generator[Image.Image, None, None]) -> None:
|
def test_sanity_rgb() -> None:
|
||||||
for im in test_images:
|
for im in ims:
|
||||||
roundtrip(im.convert("RGB"))
|
roundtrip(im.convert("RGB"))
|
||||||
|
|
||||||
|
|
||||||
def test_sanity_rgba(test_images: Generator[Image.Image, None, None]) -> None:
|
def test_sanity_rgba() -> None:
|
||||||
for im in test_images:
|
for im in ims:
|
||||||
roundtrip(im.convert("RGBA"))
|
roundtrip(im.convert("RGBA"))
|
||||||
|
|
||||||
|
|
||||||
def test_sanity_l(test_images: Generator[Image.Image, None, None]) -> None:
|
def test_sanity_l() -> None:
|
||||||
for im in test_images:
|
for im in ims:
|
||||||
roundtrip(im.convert("L"))
|
roundtrip(im.convert("L"))
|
||||||
|
|
||||||
|
|
||||||
def test_sanity_p(test_images: Generator[Image.Image, None, None]) -> None:
|
def test_sanity_p() -> None:
|
||||||
for im in test_images:
|
for im in ims:
|
||||||
roundtrip(im.convert("P"))
|
roundtrip(im.convert("P"))
|
||||||
|
|
|
@ -32,7 +32,7 @@ class TestImagingPaste:
|
||||||
def assert_9points_paste(
|
def assert_9points_paste(
|
||||||
self,
|
self,
|
||||||
im: Image.Image,
|
im: Image.Image,
|
||||||
im2: Image.Image,
|
im2: Image.Image | str | tuple[int, ...],
|
||||||
mask: Image.Image,
|
mask: Image.Image,
|
||||||
expected: list[tuple[int, int, int, int]],
|
expected: list[tuple[int, int, int, int]],
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
@ -237,7 +237,7 @@ class TestCoreResampleConsistency:
|
||||||
im = Image.new(mode, (512, 9), fill)
|
im = Image.new(mode, (512, 9), fill)
|
||||||
return im.resize((9, 512), Image.Resampling.LANCZOS), im.load()[0, 0]
|
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
|
channel, color = case
|
||||||
px = channel.load()
|
px = channel.load()
|
||||||
for x in range(channel.size[0]):
|
for x in range(channel.size[0]):
|
||||||
|
|
|
@ -154,7 +154,7 @@ class TestImagingCoreResize:
|
||||||
|
|
||||||
def test_unknown_filter(self) -> None:
|
def test_unknown_filter(self) -> None:
|
||||||
with pytest.raises(ValueError):
|
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:
|
def test_cross_platform(self, tmp_path: Path) -> None:
|
||||||
# This test is intended for only check for consistent behaviour across
|
# This test is intended for only check for consistent behaviour across
|
||||||
|
|
|
@ -753,7 +753,7 @@ def test_rectangle_I16(bbox: Coords) -> None:
|
||||||
draw.rectangle(bbox, outline=0xFFFF)
|
draw.rectangle(bbox, outline=0xFFFF)
|
||||||
|
|
||||||
# Assert
|
# 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)
|
@pytest.mark.parametrize("bbox", BBOX)
|
||||||
|
|
|
@ -73,15 +73,16 @@ def test_lut(op: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_no_operator_loaded() -> None:
|
def test_no_operator_loaded() -> None:
|
||||||
|
im = Image.new("L", (1, 1))
|
||||||
mop = ImageMorph.MorphOp()
|
mop = ImageMorph.MorphOp()
|
||||||
with pytest.raises(Exception) as e:
|
with pytest.raises(Exception) as e:
|
||||||
mop.apply(None)
|
mop.apply(im)
|
||||||
assert str(e.value) == "No operator loaded"
|
assert str(e.value) == "No operator loaded"
|
||||||
with pytest.raises(Exception) as e:
|
with pytest.raises(Exception) as e:
|
||||||
mop.match(None)
|
mop.match(im)
|
||||||
assert str(e.value) == "No operator loaded"
|
assert str(e.value) == "No operator loaded"
|
||||||
with pytest.raises(Exception) as e:
|
with pytest.raises(Exception) as e:
|
||||||
mop.save_lut(None)
|
mop.save_lut("")
|
||||||
assert str(e.value) == "No operator loaded"
|
assert str(e.value) == "No operator loaded"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,12 @@ from .helper import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Deformer:
|
class Deformer(ImageOps.SupportsGetMesh):
|
||||||
def getmesh(self, im: Image.Image) -> list[tuple[tuple[int, ...], tuple[int, ...]]]:
|
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
|
x, y = im.size
|
||||||
return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))]
|
return [((0, 0, x, y), (0, 0, x, 0, x, y, y, 0))]
|
||||||
|
|
||||||
|
@ -376,6 +380,7 @@ def test_exif_transpose() -> None:
|
||||||
else:
|
else:
|
||||||
original_exif = im.info["exif"]
|
original_exif = im.info["exif"]
|
||||||
transposed_im = ImageOps.exif_transpose(im)
|
transposed_im = ImageOps.exif_transpose(im)
|
||||||
|
assert transposed_im is not None
|
||||||
assert_image_similar(base_im, transposed_im, 17)
|
assert_image_similar(base_im, transposed_im, 17)
|
||||||
if orientation_im is base_im:
|
if orientation_im is base_im:
|
||||||
assert "exif" not in im.info
|
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
|
# Repeat the operation to test that it does not keep transposing
|
||||||
transposed_im2 = ImageOps.exif_transpose(transposed_im)
|
transposed_im2 = ImageOps.exif_transpose(transposed_im)
|
||||||
|
assert transposed_im2 is not None
|
||||||
assert_image_equal(transposed_im2, transposed_im)
|
assert_image_equal(transposed_im2, transposed_im)
|
||||||
|
|
||||||
check(base_im)
|
check(base_im)
|
||||||
|
@ -402,6 +408,7 @@ def test_exif_transpose() -> None:
|
||||||
assert im.getexif()[0x0112] == 3
|
assert im.getexif()[0x0112] == 3
|
||||||
|
|
||||||
transposed_im = ImageOps.exif_transpose(im)
|
transposed_im = ImageOps.exif_transpose(im)
|
||||||
|
assert transposed_im is not None
|
||||||
assert 0x0112 not in transposed_im.getexif()
|
assert 0x0112 not in transposed_im.getexif()
|
||||||
|
|
||||||
transposed_im._reload_exif()
|
transposed_im._reload_exif()
|
||||||
|
@ -414,12 +421,14 @@ def test_exif_transpose() -> None:
|
||||||
assert im.getexif()[0x0112] == 3
|
assert im.getexif()[0x0112] == 3
|
||||||
|
|
||||||
transposed_im = ImageOps.exif_transpose(im)
|
transposed_im = ImageOps.exif_transpose(im)
|
||||||
|
assert transposed_im is not None
|
||||||
assert 0x0112 not in transposed_im.getexif()
|
assert 0x0112 not in transposed_im.getexif()
|
||||||
|
|
||||||
# Orientation set directly on Image.Exif
|
# Orientation set directly on Image.Exif
|
||||||
im = hopper()
|
im = hopper()
|
||||||
im.getexif()[0x0112] = 3
|
im.getexif()[0x0112] = 3
|
||||||
transposed_im = ImageOps.exif_transpose(im)
|
transposed_im = ImageOps.exif_transpose(im)
|
||||||
|
assert transposed_im is not None
|
||||||
assert 0x0112 not in transposed_im.getexif()
|
assert 0x0112 not in transposed_im.getexif()
|
||||||
|
|
||||||
|
|
||||||
|
@ -499,7 +508,7 @@ def test_autocontrast_mask_real_input() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_autocontrast_preserve_tone() -> None:
|
def test_autocontrast_preserve_tone() -> None:
|
||||||
def autocontrast(mode: str, preserve_tone: bool) -> Image.Image:
|
def autocontrast(mode: str, preserve_tone: bool) -> list[int]:
|
||||||
im = hopper(mode)
|
im = hopper(mode)
|
||||||
return ImageOps.autocontrast(im, preserve_tone=preserve_tone).histogram()
|
return ImageOps.autocontrast(im, preserve_tone=preserve_tone).histogram()
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,8 @@ def test_filter_api(test_images: dict[str, Image.Image]) -> None:
|
||||||
assert i.mode == "RGB"
|
assert i.mode == "RGB"
|
||||||
assert i.size == (128, 128)
|
assert i.size == (128, 128)
|
||||||
|
|
||||||
test_filter = ImageFilter.UnsharpMask(2.0, 125, 8)
|
test_filter2 = ImageFilter.UnsharpMask(2.0, 125, 8)
|
||||||
i = im.filter(test_filter)
|
i = im.filter(test_filter2)
|
||||||
assert i.mode == "RGB"
|
assert i.mode == "RGB"
|
||||||
assert i.size == (128, 128)
|
assert i.size == (128, 128)
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ def test_sanity(tmp_path: Path) -> None:
|
||||||
assert index == 1
|
assert index == 1
|
||||||
|
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
ImageSequence.Iterator(0)
|
ImageSequence.Iterator(0) # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
def test_iterator() -> None:
|
def test_iterator() -> None:
|
||||||
|
@ -72,6 +72,7 @@ def test_consecutive() -> None:
|
||||||
for frame in ImageSequence.Iterator(im):
|
for frame in ImageSequence.Iterator(im):
|
||||||
if first_frame is None:
|
if first_frame is None:
|
||||||
first_frame = frame.copy()
|
first_frame = frame.copy()
|
||||||
|
assert first_frame is not None
|
||||||
for frame in ImageSequence.Iterator(im):
|
for frame in ImageSequence.Iterator(im):
|
||||||
assert_image_equal(frame, first_frame)
|
assert_image_equal(frame, first_frame)
|
||||||
break
|
break
|
||||||
|
|
|
@ -68,10 +68,11 @@ def test_show_without_viewers() -> None:
|
||||||
def test_viewer() -> None:
|
def test_viewer() -> None:
|
||||||
viewer = ImageShow.Viewer()
|
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):
|
with pytest.raises(NotImplementedError):
|
||||||
viewer.get_command(None)
|
viewer.get_command("")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("viewer", ImageShow._viewers)
|
@pytest.mark.parametrize("viewer", ImageShow._viewers)
|
||||||
|
|
|
@ -78,7 +78,7 @@ def test_basic(tmp_path: Path, mode: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_tobytes() -> None:
|
def test_tobytes() -> None:
|
||||||
def tobytes(mode: str) -> Image.Image:
|
def tobytes(mode: str) -> bytes:
|
||||||
return Image.new(mode, (1, 1), 1).tobytes()
|
return Image.new(mode, (1, 1), 1).tobytes()
|
||||||
|
|
||||||
order = 1 if Image._ENDIAN == "<" else -1
|
order = 1 if Image._ENDIAN == "<" else -1
|
||||||
|
|
|
@ -47,9 +47,8 @@ def test_tiff_crashes(test_file: str) -> None:
|
||||||
with Image.open(test_file) as im:
|
with Image.open(test_file) as im:
|
||||||
im.load()
|
im.load()
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
if not on_ci():
|
if on_ci():
|
||||||
pytest.skip("test image not found")
|
raise
|
||||||
return
|
pytest.skip("test image not found")
|
||||||
raise
|
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -23,7 +23,7 @@ Like PIL, Pillow is `licensed under the open source HPND License <https://raw.gi
|
||||||
Why a fork?
|
Why a fork?
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
PIL is not setuptools compatible. Please see `this Image-SIG post`_ for a more detailed explanation. Also, PIL's current bi-yearly (or greater) release schedule is too infrequent to accommodate the large number and frequency of issues reported.
|
PIL is not setuptools compatible. Please see `this Image-SIG post`_ for a more detailed explanation. Also, PIL's bi-yearly (or greater) release schedule was too infrequent to accommodate the large number and frequency of issues reported.
|
||||||
|
|
||||||
.. _this Image-SIG post: https://mail.python.org/pipermail/image-sig/2010-August/006480.html
|
.. _this Image-SIG post: https://mail.python.org/pipermail/image-sig/2010-August/006480.html
|
||||||
|
|
||||||
|
@ -35,4 +35,4 @@ What about PIL?
|
||||||
Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0
|
Prior to Pillow 2.0.0, very few image code changes were made. Pillow 2.0.0
|
||||||
added Python 3 support and includes many bug fixes from many contributors.
|
added Python 3 support and includes many bug fixes from many contributors.
|
||||||
|
|
||||||
As more time passes since the last PIL release (1.1.7 in 2009), the likelihood of a new PIL release decreases. However, we've yet to hear an official "PIL is dead" announcement.
|
The last PIL release was in 2009 (1.1.7) and `no future releases are expected <https://github.com/python-pillow/Pillow/issues/1535>`_. In January 2020, `the PyPI moderators exhausted the PEP 541 process for contacting the PIL project owner <https://github.com/python-pillow/Pillow/issues/1535#issuecomment-570308446>`_ and the `PIL project on PyPI <https://pypi.org/project/PIL>`_ was transferred to the `Pillow team <https://github.com/python-pillow/Pillow/graphs/contributors>`_. The Pillow team has no plans to update the PIL project on PyPI.
|
||||||
|
|
|
@ -93,7 +93,7 @@ The core image library is designed for fast access to data stored in a few basic
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
installation.rst
|
installation/index.rst
|
||||||
handbook/index.rst
|
handbook/index.rst
|
||||||
reference/index.rst
|
reference/index.rst
|
||||||
porting.rst
|
porting.rst
|
||||||
|
|
|
@ -1,597 +1,29 @@
|
||||||
|
:orphan:
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
|
|
||||||
.. raw:: html
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
activateTab(getOS());
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
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
|
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
|
Python Support
|
||||||
most common image formats. See :ref:`external-libraries` for a
|
--------------
|
||||||
full list of external libraries supported.
|
|
||||||
|
|
||||||
Install Pillow with :command:`pip`::
|
.. Note:: This section has moved to :ref:`python-support`. Please update references accordingly.
|
||||||
|
|
||||||
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**, **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
|
|
||||||
"""""""""""""
|
|
||||||
|
|
||||||
* Environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
|
|
||||||
multiprocessing to build the extension. Setting ``MAX_CONCURRENCY``
|
|
||||||
sets the number of CPUs to use, 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.
|
|
||||||
|
|
||||||
* Build flag: ``-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).
|
|
||||||
|
|
||||||
* Build flag: ``-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
|
|
||||||
|
|
||||||
Platform Support
|
Platform Support
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
Current platform support for Pillow. Binary distributions are
|
.. Note:: This section has moved to :ref:`platform-support`. Please update references accordingly.
|
||||||
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
|
Building From Source
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
--------------------
|
||||||
|
|
||||||
These platforms are built and tested for every change.
|
.. Note:: This section has moved to :ref:`building-from-source`. Please update references accordingly.
|
||||||
|
|
||||||
+----------------------------------+----------------------------+---------------------+
|
|
||||||
| 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 |
|
|
||||||
+----------------------------------+----------------------------+------------------+--------------+
|
|
||||||
|
|
||||||
Old Versions
|
Old Versions
|
||||||
------------
|
------------
|
||||||
|
|
||||||
You can download old distributions from the `release history at PyPI
|
.. Note:: This section has moved to :ref:`old-versions`. Please update references accordingly.
|
||||||
<https://pypi.org/project/pillow/#history>`_ and by direct URL access
|
|
||||||
eg. https://pypi.org/project/pillow/1.0/.
|
|
||||||
|
|
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
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
* Environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
|
||||||
|
multiprocessing to build the extension. Setting ``MAX_CONCURRENCY``
|
||||||
|
sets the number of CPUs to use, 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.
|
||||||
|
|
||||||
|
* Build flag: ``-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).
|
||||||
|
|
||||||
|
* Build flag: ``-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
|
170
docs/installation/platform-support.rst
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
.. _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 | x86-64 |
|
||||||
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
|
||||||
|
| | 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, 3.13, 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, 3.13, 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
|
|
@ -38,7 +38,7 @@ from ._deprecate import deprecate
|
||||||
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
||||||
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
||||||
|
|
||||||
gs_binary = None
|
gs_binary: str | bool | None = None
|
||||||
gs_windows_binary = None
|
gs_windows_binary = None
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -649,9 +649,7 @@ def _write_multiple_frames(im, fp, palette):
|
||||||
if "transparency" in encoderinfo:
|
if "transparency" in encoderinfo:
|
||||||
# When the delta is zero, fill the image with transparency
|
# When the delta is zero, fill the image with transparency
|
||||||
diff_frame = im_frame.copy()
|
diff_frame = im_frame.copy()
|
||||||
fill = Image.new(
|
fill = Image.new("P", delta.size, encoderinfo["transparency"])
|
||||||
"P", diff_frame.size, encoderinfo["transparency"]
|
|
||||||
)
|
|
||||||
if delta.mode == "RGBA":
|
if delta.mode == "RGBA":
|
||||||
r, g, b, a = delta.split()
|
r, g, b, a = delta.split()
|
||||||
mask = ImageMath.eval(
|
mask = ImageMath.eval(
|
||||||
|
|
|
@ -75,7 +75,7 @@ class DecompressionBombError(Exception):
|
||||||
|
|
||||||
|
|
||||||
# Limit to around a quarter gigabyte for a 24-bit (3 bpp) image
|
# Limit to around a quarter gigabyte for a 24-bit (3 bpp) image
|
||||||
MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 // 4 // 3)
|
MAX_IMAGE_PIXELS: int | None = int(1024 * 1024 * 1024 // 4 // 3)
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -978,7 +978,7 @@ class Image:
|
||||||
delete_trns = False
|
delete_trns = False
|
||||||
# transparency handling
|
# transparency handling
|
||||||
if has_transparency:
|
if has_transparency:
|
||||||
if (self.mode in ("1", "L", "I") and mode in ("LA", "RGBA")) or (
|
if (self.mode in ("1", "L", "I", "I;16") and mode in ("LA", "RGBA")) or (
|
||||||
self.mode == "RGB" and mode == "RGBA"
|
self.mode == "RGB" and mode == "RGBA"
|
||||||
):
|
):
|
||||||
# Use transparent conversion to promote from transparent
|
# Use transparent conversion to promote from transparent
|
||||||
|
|
|
@ -384,7 +384,7 @@ class Parser:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
incremental = None
|
incremental = None
|
||||||
image = None
|
image: Image.Image | None = None
|
||||||
data = None
|
data = None
|
||||||
decoder = None
|
decoder = None
|
||||||
offset = 0
|
offset = 0
|
||||||
|
|
|
@ -62,7 +62,7 @@ _MODES = {
|
||||||
(2, 0): ("L", "L;2"),
|
(2, 0): ("L", "L;2"),
|
||||||
(4, 0): ("L", "L;4"),
|
(4, 0): ("L", "L;4"),
|
||||||
(8, 0): ("L", "L"),
|
(8, 0): ("L", "L"),
|
||||||
(16, 0): ("I", "I;16B"),
|
(16, 0): ("I;16", "I;16B"),
|
||||||
# Truecolour
|
# Truecolour
|
||||||
(8, 2): ("RGB", "RGB"),
|
(8, 2): ("RGB", "RGB"),
|
||||||
(16, 2): ("RGB", "RGB;16B"),
|
(16, 2): ("RGB", "RGB;16B"),
|
||||||
|
@ -467,7 +467,7 @@ class PngStream(ChunkStream):
|
||||||
# otherwise, we have a byte string with one alpha value
|
# otherwise, we have a byte string with one alpha value
|
||||||
# for each palette entry
|
# for each palette entry
|
||||||
self.im_info["transparency"] = s
|
self.im_info["transparency"] = s
|
||||||
elif self.im_mode in ("1", "L", "I"):
|
elif self.im_mode in ("1", "L", "I;16"):
|
||||||
self.im_info["transparency"] = i16(s)
|
self.im_info["transparency"] = i16(s)
|
||||||
elif self.im_mode == "RGB":
|
elif self.im_mode == "RGB":
|
||||||
self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4)
|
self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4)
|
||||||
|
@ -981,7 +981,13 @@ class PngImageFile(ImageFile.ImageFile):
|
||||||
except EOFError:
|
except EOFError:
|
||||||
if cid == b"fdAT":
|
if cid == b"fdAT":
|
||||||
length -= 4
|
length -= 4
|
||||||
ImageFile._safe_read(self.fp, length)
|
try:
|
||||||
|
ImageFile._safe_read(self.fp, length)
|
||||||
|
except OSError as e:
|
||||||
|
if ImageFile.LOAD_TRUNCATED_IMAGES:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
logger.debug("%r %s %s (unknown)", cid, pos, length)
|
logger.debug("%r %s %s (unknown)", cid, pos, length)
|
||||||
s = ImageFile._safe_read(self.fp, length)
|
s = ImageFile._safe_read(self.fp, length)
|
||||||
|
@ -1350,7 +1356,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
|
||||||
transparency = max(0, min(255, transparency))
|
transparency = max(0, min(255, transparency))
|
||||||
alpha = b"\xFF" * transparency + b"\0"
|
alpha = b"\xFF" * transparency + b"\0"
|
||||||
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
||||||
elif im.mode in ("1", "L", "I"):
|
elif im.mode in ("1", "L", "I", "I;16"):
|
||||||
transparency = max(0, min(65535, transparency))
|
transparency = max(0, min(65535, transparency))
|
||||||
chunk(fp, b"tRNS", o16(transparency))
|
chunk(fp, b"tRNS", o16(transparency))
|
||||||
elif im.mode == "RGB":
|
elif im.mode == "RGB":
|
||||||
|
|
|
@ -878,6 +878,18 @@ I16B_L(UINT8 *out, const UINT8 *in, int xsize) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
I16_RGB(UINT8 *out, const UINT8 *in, int xsize) {
|
||||||
|
int x;
|
||||||
|
for (x = 0; x < xsize; x++, in += 2) {
|
||||||
|
UINT8 v = in[1] == 0 ? in[0] : 255;
|
||||||
|
*out++ = v;
|
||||||
|
*out++ = v;
|
||||||
|
*out++ = v;
|
||||||
|
*out++ = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
const char *from;
|
const char *from;
|
||||||
const char *to;
|
const char *to;
|
||||||
|
@ -978,6 +990,7 @@ static struct {
|
||||||
|
|
||||||
{"I", "I;16", I_I16L},
|
{"I", "I;16", I_I16L},
|
||||||
{"I;16", "I", I16L_I},
|
{"I;16", "I", I16L_I},
|
||||||
|
{"I;16", "RGB", I16_RGB},
|
||||||
{"L", "I;16", L_I16L},
|
{"L", "I;16", L_I16L},
|
||||||
{"I;16", "L", I16L_L},
|
{"I;16", "L", I16L_L},
|
||||||
|
|
||||||
|
@ -1678,6 +1691,7 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
|
||||||
convert = rgb2rgba;
|
convert = rgb2rgba;
|
||||||
} else if ((strcmp(imIn->mode, "1") == 0 ||
|
} else if ((strcmp(imIn->mode, "1") == 0 ||
|
||||||
strcmp(imIn->mode, "I") == 0 ||
|
strcmp(imIn->mode, "I") == 0 ||
|
||||||
|
strcmp(imIn->mode, "I;16") == 0 ||
|
||||||
strcmp(imIn->mode, "L") == 0
|
strcmp(imIn->mode, "L") == 0
|
||||||
) && (
|
) && (
|
||||||
strcmp(mode, "RGBA") == 0 ||
|
strcmp(mode, "RGBA") == 0 ||
|
||||||
|
@ -1687,6 +1701,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) {
|
||||||
convert = bit2rgb;
|
convert = bit2rgb;
|
||||||
} else if (strcmp(imIn->mode, "I") == 0) {
|
} else if (strcmp(imIn->mode, "I") == 0) {
|
||||||
convert = i2rgb;
|
convert = i2rgb;
|
||||||
|
} else if (strcmp(imIn->mode, "I;16") == 0) {
|
||||||
|
convert = I16_RGB;
|
||||||
} else {
|
} else {
|
||||||
convert = l2rgb;
|
convert = l2rgb;
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,7 @@ def cmd_msbuild(
|
||||||
file: str,
|
file: str,
|
||||||
configuration: str = "Release",
|
configuration: str = "Release",
|
||||||
target: str = "Build",
|
target: str = "Build",
|
||||||
platform: str = "{msbuild_arch}",
|
plat: str = "{msbuild_arch}",
|
||||||
) -> str:
|
) -> str:
|
||||||
return " ".join(
|
return " ".join(
|
||||||
[
|
[
|
||||||
|
@ -95,7 +95,7 @@ def cmd_msbuild(
|
||||||
f"{file}",
|
f"{file}",
|
||||||
f'/t:"{target}"',
|
f'/t:"{target}"',
|
||||||
f'/p:Configuration="{configuration}"',
|
f'/p:Configuration="{configuration}"',
|
||||||
f"/p:Platform={platform}",
|
f"/p:Platform={plat}",
|
||||||
"/m",
|
"/m",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -109,13 +109,32 @@ ARCHITECTURES = {
|
||||||
"ARM64": {"vcvars_arch": "x86_arm64", "msbuild_arch": "ARM64"},
|
"ARM64": {"vcvars_arch": "x86_arm64", "msbuild_arch": "ARM64"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
V = {
|
||||||
|
"BROTLI": "1.1.0",
|
||||||
|
"FREETYPE": "2.13.2",
|
||||||
|
"FRIBIDI": "1.0.13",
|
||||||
|
"HARFBUZZ": "8.3.0",
|
||||||
|
"JPEGTURBO": "3.0.2",
|
||||||
|
"LCMS2": "2.16",
|
||||||
|
"LIBPNG": "1.6.39",
|
||||||
|
"LIBWEBP": "1.3.2",
|
||||||
|
"OPENJPEG": "2.5.2",
|
||||||
|
"TIFF": "4.6.0",
|
||||||
|
"XZ": "5.4.5",
|
||||||
|
"ZLIB": "1.3",
|
||||||
|
}
|
||||||
|
V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "")
|
||||||
|
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
|
||||||
|
V["ZLIB_DOTLESS"] = V["ZLIB"].replace(".", "")
|
||||||
|
|
||||||
|
|
||||||
# dependencies, listed in order of compilation
|
# dependencies, listed in order of compilation
|
||||||
DEPS = {
|
DEPS = {
|
||||||
"libjpeg": {
|
"libjpeg": {
|
||||||
"url": SF_PROJECTS
|
"url": f"{SF_PROJECTS}/libjpeg-turbo/files/{V['JPEGTURBO']}/"
|
||||||
+ "/libjpeg-turbo/files/3.0.2/libjpeg-turbo-3.0.2.tar.gz/download",
|
f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz/download",
|
||||||
"filename": "libjpeg-turbo-3.0.2.tar.gz",
|
"filename": f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz",
|
||||||
"dir": "libjpeg-turbo-3.0.2",
|
"dir": f"libjpeg-turbo-{V['JPEGTURBO']}",
|
||||||
"license": ["README.ijg", "LICENSE.md"],
|
"license": ["README.ijg", "LICENSE.md"],
|
||||||
"license_pattern": (
|
"license_pattern": (
|
||||||
"(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n=========="
|
"(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n=========="
|
||||||
|
@ -143,9 +162,9 @@ DEPS = {
|
||||||
"bins": ["cjpeg.exe", "djpeg.exe"],
|
"bins": ["cjpeg.exe", "djpeg.exe"],
|
||||||
},
|
},
|
||||||
"zlib": {
|
"zlib": {
|
||||||
"url": "https://zlib.net/zlib13.zip",
|
"url": f"https://zlib.net/zlib{V['ZLIB_DOTLESS']}.zip",
|
||||||
"filename": "zlib13.zip",
|
"filename": f"zlib{V['ZLIB_DOTLESS']}.zip",
|
||||||
"dir": "zlib-1.3",
|
"dir": f"zlib-{V['ZLIB']}",
|
||||||
"license": "README",
|
"license": "README",
|
||||||
"license_pattern": "Copyright notice:\n\n(.+)$",
|
"license_pattern": "Copyright notice:\n\n(.+)$",
|
||||||
"build": [
|
"build": [
|
||||||
|
@ -157,9 +176,9 @@ DEPS = {
|
||||||
"libs": [r"*.lib"],
|
"libs": [r"*.lib"],
|
||||||
},
|
},
|
||||||
"xz": {
|
"xz": {
|
||||||
"url": SF_PROJECTS + "/lzmautils/files/xz-5.4.5.tar.gz/download",
|
"url": f"{SF_PROJECTS}/lzmautils/files/xz-{V['XZ']}.tar.gz/download",
|
||||||
"filename": "xz-5.4.5.tar.gz",
|
"filename": f"xz-{V['XZ']}.tar.gz",
|
||||||
"dir": "xz-5.4.5",
|
"dir": f"xz-{V['XZ']}",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"build": [
|
"build": [
|
||||||
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
||||||
|
@ -170,9 +189,9 @@ DEPS = {
|
||||||
"libs": [r"liblzma.lib"],
|
"libs": [r"liblzma.lib"],
|
||||||
},
|
},
|
||||||
"libwebp": {
|
"libwebp": {
|
||||||
"url": "http://downloads.webmproject.org/releases/webp/libwebp-1.3.2.tar.gz",
|
"url": f"http://downloads.webmproject.org/releases/webp/libwebp-{V['LIBWEBP']}.tar.gz",
|
||||||
"filename": "libwebp-1.3.2.tar.gz",
|
"filename": f"libwebp-{V['LIBWEBP']}.tar.gz",
|
||||||
"dir": "libwebp-1.3.2",
|
"dir": f"libwebp-{V['LIBWEBP']}",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"patch": {
|
"patch": {
|
||||||
r"src\enc\picture_csp_enc.c": {
|
r"src\enc\picture_csp_enc.c": {
|
||||||
|
@ -192,9 +211,9 @@ DEPS = {
|
||||||
"libs": [r"libsharpyuv.lib", r"libwebp*.lib"],
|
"libs": [r"libsharpyuv.lib", r"libwebp*.lib"],
|
||||||
},
|
},
|
||||||
"libtiff": {
|
"libtiff": {
|
||||||
"url": "https://download.osgeo.org/libtiff/tiff-4.6.0.tar.gz",
|
"url": f"https://download.osgeo.org/libtiff/tiff-{V['TIFF']}.tar.gz",
|
||||||
"filename": "tiff-4.6.0.tar.gz",
|
"filename": f"tiff-{V['TIFF']}.tar.gz",
|
||||||
"dir": "tiff-4.6.0",
|
"dir": f"tiff-{V['TIFF']}",
|
||||||
"license": "LICENSE.md",
|
"license": "LICENSE.md",
|
||||||
"patch": {
|
"patch": {
|
||||||
r"libtiff\tif_lzma.c": {
|
r"libtiff\tif_lzma.c": {
|
||||||
|
@ -224,21 +243,24 @@ DEPS = {
|
||||||
"libs": [r"libtiff\*.lib"],
|
"libs": [r"libtiff\*.lib"],
|
||||||
},
|
},
|
||||||
"libpng": {
|
"libpng": {
|
||||||
"url": SF_PROJECTS + "/libpng/files/libpng16/1.6.39/lpng1639.zip/download",
|
"url": f"{SF_PROJECTS}/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/"
|
||||||
"filename": "lpng1639.zip",
|
f"lpng{V['LIBPNG_DOTLESS']}.zip/download",
|
||||||
"dir": "lpng1639",
|
"filename": f"lpng{V['LIBPNG_DOTLESS']}.zip",
|
||||||
|
"dir": f"lpng{V['LIBPNG_DOTLESS']}",
|
||||||
"license": "LICENSE",
|
"license": "LICENSE",
|
||||||
"build": [
|
"build": [
|
||||||
*cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"),
|
*cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"),
|
||||||
cmd_copy("libpng16_static.lib", "libpng16.lib"),
|
cmd_copy(
|
||||||
|
f"libpng{V['LIBPNG_XY']}_static.lib", f"libpng{V['LIBPNG_XY']}.lib"
|
||||||
|
),
|
||||||
],
|
],
|
||||||
"headers": [r"png*.h"],
|
"headers": [r"png*.h"],
|
||||||
"libs": [r"libpng16.lib"],
|
"libs": [f"libpng{V['LIBPNG_XY']}.lib"],
|
||||||
},
|
},
|
||||||
"brotli": {
|
"brotli": {
|
||||||
"url": "https://github.com/google/brotli/archive/refs/tags/v1.1.0.tar.gz",
|
"url": f"https://github.com/google/brotli/archive/refs/tags/v{V['BROTLI']}.tar.gz",
|
||||||
"filename": "brotli-1.1.0.tar.gz",
|
"filename": f"brotli-{V['BROTLI']}.tar.gz",
|
||||||
"dir": "brotli-1.1.0",
|
"dir": f"brotli-{V['BROTLI']}",
|
||||||
"license": "LICENSE",
|
"license": "LICENSE",
|
||||||
"build": [
|
"build": [
|
||||||
*cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
*cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"),
|
||||||
|
@ -247,9 +269,9 @@ DEPS = {
|
||||||
"libs": ["*.lib"],
|
"libs": ["*.lib"],
|
||||||
},
|
},
|
||||||
"freetype": {
|
"freetype": {
|
||||||
"url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz",
|
"url": f"https://download.savannah.gnu.org/releases/freetype/freetype-{V['FREETYPE']}.tar.gz",
|
||||||
"filename": "freetype-2.13.2.tar.gz",
|
"filename": f"freetype-{V['FREETYPE']}.tar.gz",
|
||||||
"dir": "freetype-2.13.2",
|
"dir": f"freetype-{V['FREETYPE']}",
|
||||||
"license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"],
|
"license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"],
|
||||||
"patch": {
|
"patch": {
|
||||||
r"builds\windows\vc2010\freetype.vcxproj": {
|
r"builds\windows\vc2010\freetype.vcxproj": {
|
||||||
|
@ -262,7 +284,7 @@ DEPS = {
|
||||||
"<UserDefines></UserDefines>": "<UserDefines>FT_CONFIG_OPTION_SYSTEM_ZLIB;FT_CONFIG_OPTION_USE_PNG;FT_CONFIG_OPTION_USE_HARFBUZZ;FT_CONFIG_OPTION_USE_BROTLI</UserDefines>", # noqa: E501
|
"<UserDefines></UserDefines>": "<UserDefines>FT_CONFIG_OPTION_SYSTEM_ZLIB;FT_CONFIG_OPTION_USE_PNG;FT_CONFIG_OPTION_USE_HARFBUZZ;FT_CONFIG_OPTION_USE_BROTLI</UserDefines>", # noqa: E501
|
||||||
"<UserIncludeDirectories></UserIncludeDirectories>": r"<UserIncludeDirectories>{dir_harfbuzz}\src;{inc_dir}</UserIncludeDirectories>", # noqa: E501
|
"<UserIncludeDirectories></UserIncludeDirectories>": r"<UserIncludeDirectories>{dir_harfbuzz}\src;{inc_dir}</UserIncludeDirectories>", # noqa: E501
|
||||||
"<UserLibraryDirectories></UserLibraryDirectories>": "<UserLibraryDirectories>{lib_dir}</UserLibraryDirectories>", # noqa: E501
|
"<UserLibraryDirectories></UserLibraryDirectories>": "<UserLibraryDirectories>{lib_dir}</UserLibraryDirectories>", # noqa: E501
|
||||||
"<UserDependencies></UserDependencies>": "<UserDependencies>zlib.lib;libpng16.lib;brotlicommon.lib;brotlidec.lib</UserDependencies>", # noqa: E501
|
"<UserDependencies></UserDependencies>": f"<UserDependencies>zlib.lib;libpng{V['LIBPNG_XY']}.lib;brotlicommon.lib;brotlidec.lib</UserDependencies>", # noqa: E501
|
||||||
},
|
},
|
||||||
r"src/autofit/afshaper.c": {
|
r"src/autofit/afshaper.c": {
|
||||||
# link against harfbuzz.lib
|
# link against harfbuzz.lib
|
||||||
|
@ -282,9 +304,9 @@ DEPS = {
|
||||||
"libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],
|
"libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],
|
||||||
},
|
},
|
||||||
"lcms2": {
|
"lcms2": {
|
||||||
"url": SF_PROJECTS + "/lcms/files/lcms/2.16/lcms2-2.16.tar.gz/download",
|
"url": f"{SF_PROJECTS}/lcms/files/lcms/{V['LCMS2']}/lcms2-{V['LCMS2']}.tar.gz/download", # noqa: E501
|
||||||
"filename": "lcms2-2.16.tar.gz",
|
"filename": f"lcms2-{V['LCMS2']}.tar.gz",
|
||||||
"dir": "lcms2-2.16",
|
"dir": f"lcms2-{V['LCMS2']}",
|
||||||
"license": "LICENSE",
|
"license": "LICENSE",
|
||||||
"patch": {
|
"patch": {
|
||||||
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
|
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
|
||||||
|
@ -308,16 +330,16 @@ DEPS = {
|
||||||
"libs": [r"Lib\MS\*.lib"],
|
"libs": [r"Lib\MS\*.lib"],
|
||||||
},
|
},
|
||||||
"openjpeg": {
|
"openjpeg": {
|
||||||
"url": "https://github.com/uclouvain/openjpeg/archive/v2.5.2.tar.gz",
|
"url": f"https://github.com/uclouvain/openjpeg/archive/v{V['OPENJPEG']}.tar.gz",
|
||||||
"filename": "openjpeg-2.5.2.tar.gz",
|
"filename": f"openjpeg-{V['OPENJPEG']}.tar.gz",
|
||||||
"dir": "openjpeg-2.5.2",
|
"dir": f"openjpeg-{V['OPENJPEG']}",
|
||||||
"license": "LICENSE",
|
"license": "LICENSE",
|
||||||
"build": [
|
"build": [
|
||||||
*cmds_cmake(
|
*cmds_cmake(
|
||||||
"openjp2", "-DBUILD_CODEC:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF"
|
"openjp2", "-DBUILD_CODEC:BOOL=OFF", "-DBUILD_SHARED_LIBS:BOOL=OFF"
|
||||||
),
|
),
|
||||||
cmd_mkdir(r"{inc_dir}\openjpeg-2.5.2"),
|
cmd_mkdir(rf"{{inc_dir}}\openjpeg-{V['OPENJPEG']}"),
|
||||||
cmd_copy(r"src\lib\openjp2\*.h", r"{inc_dir}\openjpeg-2.5.2"),
|
cmd_copy(r"src\lib\openjp2\*.h", rf"{{inc_dir}}\openjpeg-{V['OPENJPEG']}"),
|
||||||
],
|
],
|
||||||
"libs": [r"bin\*.lib"],
|
"libs": [r"bin\*.lib"],
|
||||||
},
|
},
|
||||||
|
@ -343,9 +365,9 @@ DEPS = {
|
||||||
"libs": [r"imagequant.lib"],
|
"libs": [r"imagequant.lib"],
|
||||||
},
|
},
|
||||||
"harfbuzz": {
|
"harfbuzz": {
|
||||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/8.3.0.zip",
|
"url": f"https://github.com/harfbuzz/harfbuzz/archive/{V['HARFBUZZ']}.zip",
|
||||||
"filename": "harfbuzz-8.3.0.zip",
|
"filename": f"harfbuzz-{V['HARFBUZZ']}.zip",
|
||||||
"dir": "harfbuzz-8.3.0",
|
"dir": f"harfbuzz-{V['HARFBUZZ']}",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"build": [
|
"build": [
|
||||||
*cmds_cmake(
|
*cmds_cmake(
|
||||||
|
@ -358,12 +380,12 @@ DEPS = {
|
||||||
"libs": [r"*.lib"],
|
"libs": [r"*.lib"],
|
||||||
},
|
},
|
||||||
"fribidi": {
|
"fribidi": {
|
||||||
"url": "https://github.com/fribidi/fribidi/archive/v1.0.13.zip",
|
"url": f"https://github.com/fribidi/fribidi/archive/v{V['FRIBIDI']}.zip",
|
||||||
"filename": "fribidi-1.0.13.zip",
|
"filename": f"fribidi-{V['FRIBIDI']}.zip",
|
||||||
"dir": "fribidi-1.0.13",
|
"dir": f"fribidi-{V['FRIBIDI']}",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"build": [
|
"build": [
|
||||||
cmd_copy(r"COPYING", r"{bin_dir}\fribidi-1.0.13-COPYING"),
|
cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['FRIBIDI']}-COPYING"),
|
||||||
cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"),
|
cmd_copy(r"{winbuild_dir}\fribidi.cmake", r"CMakeLists.txt"),
|
||||||
# generated tab.i files cannot be cross-compiled
|
# generated tab.i files cannot be cross-compiled
|
||||||
" ^&^& ".join(
|
" ^&^& ".join(
|
||||||
|
@ -440,6 +462,7 @@ def find_msvs(architecture: str) -> dict[str, str] | None:
|
||||||
|
|
||||||
|
|
||||||
def download_dep(url: str, file: str) -> None:
|
def download_dep(url: str, file: str) -> None:
|
||||||
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
ex = None
|
ex = None
|
||||||
|
@ -456,11 +479,14 @@ def download_dep(url: str, file: str) -> None:
|
||||||
raise RuntimeError(ex)
|
raise RuntimeError(ex)
|
||||||
|
|
||||||
|
|
||||||
def extract_dep(url: str, filename: str) -> None:
|
def extract_dep(url: str, filename: str, prefs: dict[str, str]) -> None:
|
||||||
import tarfile
|
import tarfile
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
file = os.path.join(args.depends_dir, filename)
|
depends_dir = prefs["depends_dir"]
|
||||||
|
sources_dir = prefs["src_dir"]
|
||||||
|
|
||||||
|
file = os.path.join(depends_dir, filename)
|
||||||
if not os.path.exists(file):
|
if not os.path.exists(file):
|
||||||
# First try our mirror
|
# First try our mirror
|
||||||
mirror_url = (
|
mirror_url = (
|
||||||
|
@ -499,13 +525,15 @@ def extract_dep(url: str, filename: str) -> None:
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
|
||||||
def write_script(name: str, lines: list[str]) -> None:
|
def write_script(
|
||||||
name = os.path.join(args.build_dir, name)
|
name: str, lines: list[str], prefs: dict[str, str], verbose: bool
|
||||||
|
) -> None:
|
||||||
|
name = os.path.join(prefs["build_dir"], name)
|
||||||
lines = [line.format(**prefs) for line in lines]
|
lines = [line.format(**prefs) for line in lines]
|
||||||
print("Writing " + name)
|
print("Writing " + name)
|
||||||
with open(name, "w", newline="") as f:
|
with open(name, "w", newline="") as f:
|
||||||
f.write(os.linesep.join(lines))
|
f.write(os.linesep.join(lines))
|
||||||
if args.verbose:
|
if verbose:
|
||||||
for line in lines:
|
for line in lines:
|
||||||
print(" " + line)
|
print(" " + line)
|
||||||
|
|
||||||
|
@ -521,7 +549,7 @@ def get_footer(dep: dict) -> list[str]:
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def build_env() -> None:
|
def build_env(prefs: dict[str, str], verbose: bool) -> None:
|
||||||
lines = [
|
lines = [
|
||||||
"if defined DISTUTILS_USE_SDK goto end",
|
"if defined DISTUTILS_USE_SDK goto end",
|
||||||
cmd_set("INCLUDE", "{inc_dir}"),
|
cmd_set("INCLUDE", "{inc_dir}"),
|
||||||
|
@ -534,33 +562,35 @@ def build_env() -> None:
|
||||||
":end",
|
":end",
|
||||||
"@echo on",
|
"@echo on",
|
||||||
]
|
]
|
||||||
write_script("build_env.cmd", lines)
|
write_script("build_env.cmd", lines, prefs, verbose)
|
||||||
|
|
||||||
|
|
||||||
def build_dep(name: str) -> str:
|
def build_dep(name: str, prefs: dict[str, str], verbose: bool) -> str:
|
||||||
dep = DEPS[name]
|
dep = DEPS[name]
|
||||||
dir = dep["dir"]
|
directory = dep["dir"]
|
||||||
file = f"build_dep_{name}.cmd"
|
file = f"build_dep_{name}.cmd"
|
||||||
|
license_dir = prefs["license_dir"]
|
||||||
|
sources_dir = prefs["src_dir"]
|
||||||
|
|
||||||
extract_dep(dep["url"], dep["filename"])
|
extract_dep(dep["url"], dep["filename"], prefs)
|
||||||
|
|
||||||
licenses = dep["license"]
|
licenses = dep["license"]
|
||||||
if isinstance(licenses, str):
|
if isinstance(licenses, str):
|
||||||
licenses = [licenses]
|
licenses = [licenses]
|
||||||
license_text = ""
|
license_text = ""
|
||||||
for license_file in licenses:
|
for license_file in licenses:
|
||||||
with open(os.path.join(sources_dir, dir, license_file)) as f:
|
with open(os.path.join(sources_dir, directory, license_file)) as f:
|
||||||
license_text += f.read()
|
license_text += f.read()
|
||||||
if "license_pattern" in dep:
|
if "license_pattern" in dep:
|
||||||
match = re.search(dep["license_pattern"], license_text, re.DOTALL)
|
match = re.search(dep["license_pattern"], license_text, re.DOTALL)
|
||||||
license_text = "\n".join(match.groups())
|
license_text = "\n".join(match.groups())
|
||||||
assert len(license_text) > 50
|
assert len(license_text) > 50
|
||||||
with open(os.path.join(license_dir, f"{dir}.txt"), "w") as f:
|
with open(os.path.join(license_dir, f"{directory}.txt"), "w") as f:
|
||||||
print(f"Writing license {dir}.txt")
|
print(f"Writing license {directory}.txt")
|
||||||
f.write(license_text)
|
f.write(license_text)
|
||||||
|
|
||||||
for patch_file, patch_list in dep.get("patch", {}).items():
|
for patch_file, patch_list in dep.get("patch", {}).items():
|
||||||
patch_file = os.path.join(sources_dir, dir, patch_file.format(**prefs))
|
patch_file = os.path.join(sources_dir, directory, patch_file.format(**prefs))
|
||||||
with open(patch_file) as f:
|
with open(patch_file) as f:
|
||||||
text = f.read()
|
text = f.read()
|
||||||
for patch_from, patch_to in patch_list.items():
|
for patch_from, patch_to in patch_list.items():
|
||||||
|
@ -572,22 +602,22 @@ def build_dep(name: str) -> str:
|
||||||
print(f"Patching {patch_file}")
|
print(f"Patching {patch_file}")
|
||||||
f.write(text)
|
f.write(text)
|
||||||
|
|
||||||
banner = f"Building {name} ({dir})"
|
banner = f"Building {name} ({directory})"
|
||||||
lines = [
|
lines = [
|
||||||
r'call "{build_dir}\build_env.cmd"',
|
r'call "{build_dir}\build_env.cmd"',
|
||||||
"@echo " + ("=" * 70),
|
"@echo " + ("=" * 70),
|
||||||
f"@echo ==== {banner:<60} ====",
|
f"@echo ==== {banner:<60} ====",
|
||||||
"@echo " + ("=" * 70),
|
"@echo " + ("=" * 70),
|
||||||
cmd_cd(os.path.join(sources_dir, dir)),
|
cmd_cd(os.path.join(sources_dir, directory)),
|
||||||
*dep.get("build", []),
|
*dep.get("build", []),
|
||||||
*get_footer(dep),
|
*get_footer(dep),
|
||||||
]
|
]
|
||||||
|
|
||||||
write_script(file, lines)
|
write_script(file, lines, prefs, verbose)
|
||||||
return file
|
return file
|
||||||
|
|
||||||
|
|
||||||
def build_dep_all() -> None:
|
def build_dep_all(disabled: list[str], prefs: dict[str, str], verbose: bool) -> None:
|
||||||
lines = [r'call "{build_dir}\build_env.cmd"']
|
lines = [r'call "{build_dir}\build_env.cmd"']
|
||||||
gha_groups = "GITHUB_ACTIONS" in os.environ
|
gha_groups = "GITHUB_ACTIONS" in os.environ
|
||||||
for dep_name in DEPS:
|
for dep_name in DEPS:
|
||||||
|
@ -595,7 +625,7 @@ def build_dep_all() -> None:
|
||||||
if dep_name in disabled:
|
if dep_name in disabled:
|
||||||
print(f"Skipping disabled dependency {dep_name}")
|
print(f"Skipping disabled dependency {dep_name}")
|
||||||
continue
|
continue
|
||||||
script = build_dep(dep_name)
|
script = build_dep(dep_name, prefs, verbose)
|
||||||
if gha_groups:
|
if gha_groups:
|
||||||
lines.append(f"@echo ::group::Running {script}")
|
lines.append(f"@echo ::group::Running {script}")
|
||||||
lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"')
|
lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"')
|
||||||
|
@ -604,12 +634,11 @@ def build_dep_all() -> None:
|
||||||
lines.append("@echo ::endgroup::")
|
lines.append("@echo ::endgroup::")
|
||||||
print()
|
print()
|
||||||
lines.append("@echo All Pillow dependencies built successfully!")
|
lines.append("@echo All Pillow dependencies built successfully!")
|
||||||
write_script("build_dep_all.cmd", lines)
|
write_script("build_dep_all.cmd", lines, prefs, verbose)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def main() -> None:
|
||||||
winbuild_dir = os.path.dirname(os.path.realpath(__file__))
|
winbuild_dir = os.path.dirname(os.path.realpath(__file__))
|
||||||
pillow_dir = os.path.realpath(os.path.join(winbuild_dir, ".."))
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog="winbuild\\build_prepare.py",
|
prog="winbuild\\build_prepare.py",
|
||||||
|
@ -715,15 +744,15 @@ if __name__ == "__main__":
|
||||||
"architecture": args.architecture,
|
"architecture": args.architecture,
|
||||||
**arch_prefs,
|
**arch_prefs,
|
||||||
# Pillow paths
|
# Pillow paths
|
||||||
"pillow_dir": pillow_dir,
|
|
||||||
"winbuild_dir": winbuild_dir,
|
"winbuild_dir": winbuild_dir,
|
||||||
# Build paths
|
# Build paths
|
||||||
|
"bin_dir": bin_dir,
|
||||||
"build_dir": args.build_dir,
|
"build_dir": args.build_dir,
|
||||||
|
"depends_dir": args.depends_dir,
|
||||||
"inc_dir": inc_dir,
|
"inc_dir": inc_dir,
|
||||||
"lib_dir": lib_dir,
|
"lib_dir": lib_dir,
|
||||||
"bin_dir": bin_dir,
|
|
||||||
"src_dir": sources_dir,
|
|
||||||
"license_dir": license_dir,
|
"license_dir": license_dir,
|
||||||
|
"src_dir": sources_dir,
|
||||||
# Compilers / Tools
|
# Compilers / Tools
|
||||||
**msvs,
|
**msvs,
|
||||||
"cmake": "cmake.exe", # TODO find CMAKE automatically
|
"cmake": "cmake.exe", # TODO find CMAKE automatically
|
||||||
|
@ -736,6 +765,10 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
write_script(".gitignore", ["*"])
|
write_script(".gitignore", ["*"], prefs, args.verbose)
|
||||||
build_env()
|
build_env(prefs, args.verbose)
|
||||||
build_dep_all()
|
build_dep_all(disabled, prefs, args.verbose)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|