mirror of
https://github.com/python-pillow/Pillow.git
synced 2026-02-04 22:39:33 +03:00
Merge branch 'main' into imagegrab_resize
This commit is contained in:
commit
6d51ace325
|
|
@ -1 +1 @@
|
|||
cibuildwheel==3.2.1
|
||||
cibuildwheel==3.3.0
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
mypy==1.18.2
|
||||
mypy==1.19.0
|
||||
arro3-compute
|
||||
arro3-core
|
||||
IceSpringPySideStubs-PyQt6
|
||||
|
|
|
|||
4
.github/workflows/cifuzz.yml
vendored
4
.github/workflows/cifuzz.yml
vendored
|
|
@ -44,13 +44,13 @@ jobs:
|
|||
language: python
|
||||
dry-run: false
|
||||
- name: Upload New Crash
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
if: failure() && steps.build.outcome == 'success'
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./out/artifacts
|
||||
- name: Upload Legacy Crash
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
if: steps.run.outcome == 'success'
|
||||
with:
|
||||
name: crash
|
||||
|
|
|
|||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
|
|
@ -32,7 +32,7 @@ jobs:
|
|||
name: Docs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
|
|||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
name: Lint
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
|
|||
3
.github/workflows/test-docker.yml
vendored
3
.github/workflows/test-docker.yml
vendored
|
|
@ -50,6 +50,7 @@ jobs:
|
|||
debian-13-trixie-x86,
|
||||
debian-13-trixie-amd64,
|
||||
fedora-42-amd64,
|
||||
fedora-43-amd64,
|
||||
gentoo,
|
||||
ubuntu-22.04-jammy-amd64,
|
||||
ubuntu-24.04-noble-amd64,
|
||||
|
|
@ -67,7 +68,7 @@ jobs:
|
|||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
|
|||
2
.github/workflows/test-mingw.yml
vendored
2
.github/workflows/test-mingw.yml
vendored
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
|
|||
2
.github/workflows/test-valgrind-memory.yml
vendored
2
.github/workflows/test-valgrind-memory.yml
vendored
|
|
@ -41,7 +41,7 @@ jobs:
|
|||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
|
|||
2
.github/workflows/test-valgrind.yml
vendored
2
.github/workflows/test-valgrind.yml
vendored
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
|
|||
8
.github/workflows/test-windows.yml
vendored
8
.github/workflows/test-windows.yml
vendored
|
|
@ -47,19 +47,19 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout cached dependencies
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/pillow-depends
|
||||
path: winbuild\depends
|
||||
|
||||
- name: Checkout extra test images
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/test-images
|
||||
|
|
@ -216,7 +216,7 @@ jobs:
|
|||
shell: bash
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
|
|
|
|||
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
|
@ -65,7 +65,7 @@ jobs:
|
|||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ jobs:
|
|||
mkdir -p Tests/errors
|
||||
|
||||
- name: Upload errors
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
if: failure()
|
||||
with:
|
||||
name: errors
|
||||
|
|
|
|||
34
.github/workflows/wheels-dependencies.sh
vendored
34
.github/workflows/wheels-dependencies.sh
vendored
|
|
@ -32,7 +32,6 @@ if [[ "$CIBW_PLATFORM" == "ios" ]]; then
|
|||
# or `build/deps/iphonesimulator`
|
||||
WORKDIR=$(pwd)/build/$IOS_SDK
|
||||
BUILD_PREFIX=$(pwd)/build/deps/$IOS_SDK
|
||||
PATCH_DIR=$(pwd)/patches/iOS
|
||||
|
||||
# GNU tooling insists on using aarch64 rather than arm64
|
||||
if [[ $PLAT == "arm64" ]]; then
|
||||
|
|
@ -90,27 +89,29 @@ fi
|
|||
|
||||
ARCHIVE_SDIR=pillow-depends-main
|
||||
|
||||
# Package versions for fresh source builds. Version numbers with "Patched"
|
||||
# annotations have a source code patch that is required for some platforms. If
|
||||
# you change those versions, ensure the patch is also updated.
|
||||
# Package versions for fresh source builds.
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
FREETYPE_VERSION=2.13.3
|
||||
else
|
||||
FREETYPE_VERSION=2.14.1
|
||||
fi
|
||||
HARFBUZZ_VERSION=12.1.0
|
||||
LIBPNG_VERSION=1.6.50
|
||||
HARFBUZZ_VERSION=12.2.0
|
||||
LIBPNG_VERSION=1.6.51
|
||||
JPEGTURBO_VERSION=3.1.2
|
||||
OPENJPEG_VERSION=2.5.4
|
||||
XZ_VERSION=5.8.1
|
||||
ZSTD_VERSION=1.5.7
|
||||
TIFF_VERSION=4.7.1
|
||||
LCMS2_VERSION=2.17
|
||||
ZLIB_NG_VERSION=2.2.5
|
||||
if [[ "$MB_ML_VER" == 2014 ]] && [[ "$PLAT" == "aarch64" ]]; then
|
||||
ZLIB_NG_VERSION=2.2.5
|
||||
else
|
||||
ZLIB_NG_VERSION=2.3.1
|
||||
fi
|
||||
LIBWEBP_VERSION=1.6.0
|
||||
BZIP2_VERSION=1.0.8
|
||||
LIBXCB_VERSION=1.17.0
|
||||
BROTLI_VERSION=1.1.0 # Patched; next release won't need patching. See patch file.
|
||||
BROTLI_VERSION=1.2.0
|
||||
LIBAVIF_VERSION=1.3.0
|
||||
|
||||
function build_pkg_config {
|
||||
|
|
@ -149,18 +150,13 @@ function build_zlib_ng {
|
|||
ORIGINAL_HOST_CONFIGURE_FLAGS=$HOST_CONFIGURE_FLAGS
|
||||
unset HOST_CONFIGURE_FLAGS
|
||||
|
||||
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat
|
||||
if [[ "$ZLIB_NG_VERSION" == 2.2.5 ]]; then
|
||||
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --zlib-compat
|
||||
else
|
||||
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --installnamedir=$BUILD_PREFIX/lib --zlib-compat
|
||||
fi
|
||||
|
||||
HOST_CONFIGURE_FLAGS=$ORIGINAL_HOST_CONFIGURE_FLAGS
|
||||
|
||||
if [[ -n "$IS_MACOS" ]] && [[ -z "$IOS_SDK" ]]; then
|
||||
# Ensure that on macOS, the library name is an absolute path, not an
|
||||
# @rpath, so that delocate picks up the right library (and doesn't need
|
||||
# DYLD_LIBRARY_PATH to be set). The default Makefile doesn't have an
|
||||
# option to control the install_name. This isn't needed on iOS, as iOS
|
||||
# only builds the static library.
|
||||
install_name_tool -id $BUILD_PREFIX/lib/libz.1.dylib $BUILD_PREFIX/lib/libz.1.dylib
|
||||
fi
|
||||
touch zlib-stamp
|
||||
}
|
||||
|
||||
|
|
@ -168,7 +164,7 @@ function build_brotli {
|
|||
if [ -e brotli-stamp ]; then return; fi
|
||||
local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
|
||||
(cd $out_dir \
|
||||
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib $HOST_CMAKE_FLAGS . \
|
||||
&& cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib -DCMAKE_MACOSX_BUNDLE=OFF $HOST_CMAKE_FLAGS . \
|
||||
&& make -j4 install)
|
||||
touch brotli-stamp
|
||||
}
|
||||
|
|
|
|||
24
.github/workflows/wheels.yml
vendored
24
.github/workflows/wheels.yml
vendored
|
|
@ -100,14 +100,14 @@ jobs:
|
|||
cibw_arch: arm64_iphoneos
|
||||
- name: "iOS arm64 simulator"
|
||||
platform: ios
|
||||
os: macos-14
|
||||
os: macos-latest
|
||||
cibw_arch: arm64_iphonesimulator
|
||||
- name: "iOS x86_64 simulator"
|
||||
platform: ios
|
||||
os: macos-15-intel
|
||||
cibw_arch: x86_64_iphonesimulator
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
|
@ -134,7 +134,7 @@ jobs:
|
|||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: dist-${{ matrix.name }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
|
@ -154,12 +154,12 @@ jobs:
|
|||
- cibw_arch: ARM64
|
||||
os: windows-11-arm
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout extra test images
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
repository: python-pillow/test-images
|
||||
|
|
@ -220,13 +220,13 @@ jobs:
|
|||
shell: cmd
|
||||
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: dist-windows-${{ matrix.cibw_arch }}
|
||||
path: ./wheelhouse/*.whl
|
||||
|
||||
- name: Upload fribidi.dll
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: fribidi-windows-${{ matrix.cibw_arch }}
|
||||
path: winbuild\build\bin\fribidi*
|
||||
|
|
@ -235,7 +235,7 @@ jobs:
|
|||
if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
@ -246,7 +246,7 @@ jobs:
|
|||
|
||||
- run: make sdist
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: dist-sdist
|
||||
path: dist/*.tar.gz
|
||||
|
|
@ -256,7 +256,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
name: Count dists
|
||||
steps:
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
|
|
@ -275,7 +275,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
name: Upload wheels to scientific-python-nightly-wheels
|
||||
steps:
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: dist-!(sdist)*
|
||||
path: dist
|
||||
|
|
@ -297,7 +297,7 @@ jobs:
|
|||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v5
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.13.3
|
||||
rev: v0.14.3
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [--exit-non-zero-on-fix]
|
||||
|
|
@ -21,7 +21,7 @@ repos:
|
|||
rev: v1.5.5
|
||||
hooks:
|
||||
- id: remove-tabs
|
||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$|\.patch$)
|
||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v21.1.2
|
||||
|
|
@ -46,29 +46,29 @@ repos:
|
|||
- id: check-yaml
|
||||
args: [--allow-multiple-documents]
|
||||
- id: end-of-file-fixer
|
||||
exclude: ^Tests/images/|\.patch$
|
||||
exclude: ^Tests/images/
|
||||
- id: trailing-whitespace
|
||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/|\.patch$
|
||||
exclude: ^\.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.34.0
|
||||
rev: 0.34.1
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
- id: check-readthedocs
|
||||
- id: check-renovate
|
||||
|
||||
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
||||
rev: v1.14.2
|
||||
rev: v1.16.2
|
||||
hooks:
|
||||
- id: zizmor
|
||||
|
||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||
rev: v1.0.0
|
||||
rev: v1.0.1
|
||||
hooks:
|
||||
- id: sphinx-lint
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: v2.7.0
|
||||
rev: v2.11.0
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ repos:
|
|||
additional_dependencies: [trove-classifiers>=2024.10.12]
|
||||
|
||||
- repo: https://github.com/tox-dev/tox-ini-fmt
|
||||
rev: 1.6.0
|
||||
rev: 1.7.0
|
||||
hooks:
|
||||
- id: tox-ini-fmt
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ include tox.ini
|
|||
graft Tests
|
||||
graft Tests/images
|
||||
graft checks
|
||||
graft patches
|
||||
graft src
|
||||
graft depends
|
||||
graft winbuild
|
||||
|
|
|
|||
BIN
Tests/images/zero_mask_totals.dds
Normal file
BIN
Tests/images/zero_mask_totals.dds
Normal file
Binary file not shown.
|
|
@ -1,5 +1,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
|
@ -718,6 +719,25 @@ def test_apng_save_size(tmp_path: Path) -> None:
|
|||
assert reloaded.size == (200, 200)
|
||||
|
||||
|
||||
def test_compress_level() -> None:
|
||||
compress_level_sizes = {}
|
||||
for compress_level in (0, 9):
|
||||
out = BytesIO()
|
||||
|
||||
im = Image.new("L", (100, 100))
|
||||
im.save(
|
||||
out,
|
||||
"PNG",
|
||||
save_all=True,
|
||||
append_images=[Image.new("L", (200, 200))],
|
||||
compress_level=compress_level,
|
||||
)
|
||||
|
||||
compress_level_sizes[compress_level] = len(out.getvalue())
|
||||
|
||||
assert compress_level_sizes[0] > compress_level_sizes[9]
|
||||
|
||||
|
||||
def test_seek_after_close() -> None:
|
||||
im = Image.open("Tests/images/apng/delay.png")
|
||||
im.seek(1)
|
||||
|
|
|
|||
|
|
@ -380,6 +380,11 @@ def test_palette() -> None:
|
|||
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
|
||||
|
||||
|
||||
def test_zero_mask_totals() -> None:
|
||||
with Image.open("Tests/images/zero_mask_totals.dds") as im:
|
||||
im.load()
|
||||
|
||||
|
||||
def test_unsupported_header_size() -> None:
|
||||
with pytest.raises(OSError, match="Unsupported header size 0"):
|
||||
with Image.open(BytesIO(b"DDS " + b"\x00" * 4)):
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ def test_multiple_load_operations() -> None:
|
|||
assert_image_equal_tofile(im, "Tests/images/gbr.png")
|
||||
|
||||
|
||||
def create_gbr_image(info: dict[str, int] = {}, magic_number=b"") -> BytesIO:
|
||||
def create_gbr_image(info: dict[str, int] = {}, magic_number: bytes = b"") -> BytesIO:
|
||||
return BytesIO(
|
||||
b"".join(
|
||||
_binary.o32be(i)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ TEST_FILE = "Tests/images/iptc.jpg"
|
|||
|
||||
|
||||
def create_iptc_image(info: dict[str, int] = {}) -> BytesIO:
|
||||
def field(tag, value):
|
||||
def field(tag: tuple[int, int], value: bytes) -> bytes:
|
||||
return bytes((0x1C,) + tag + (0, len(value))) + value
|
||||
|
||||
data = field((3, 60), bytes((info.get("layers", 1), info.get("component", 0))))
|
||||
|
|
|
|||
|
|
@ -355,6 +355,35 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
# Should not segfault
|
||||
im.save(outfile)
|
||||
|
||||
@pytest.mark.parametrize("tagtype", (TiffTags.SIGNED_RATIONAL, TiffTags.IFD))
|
||||
def test_tag_type(
|
||||
self, tagtype: int, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
|
||||
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
ifd[37000] = 100
|
||||
ifd.tagtype[37000] = tagtype
|
||||
|
||||
out = tmp_path / "temp.tif"
|
||||
im = Image.new("L", (1, 1))
|
||||
im.save(out, tiffinfo=ifd)
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert reloaded.tag_v2[37000] == 100
|
||||
|
||||
def test_inknames_tag(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
monkeypatch.setattr(TiffImagePlugin, "WRITE_LIBTIFF", True)
|
||||
|
||||
out = tmp_path / "temp.tif"
|
||||
hopper("L").save(out, tiffinfo={333: "name\x00"})
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
|
||||
assert reloaded.tag_v2[333] in ("name", "name\x00")
|
||||
|
||||
def test_whitepoint_tag(
|
||||
self, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ def test_sanity() -> None:
|
|||
|
||||
# Adjust for the gamma of 2.2 encoded into the file
|
||||
lut = ImagePalette.make_gamma_lut(1 / 2.2)
|
||||
im = Image.merge("RGBA", [chan.point(lut) for chan in im.split()])
|
||||
im1 = Image.merge("RGBA", [chan.point(lut) for chan in im.split()])
|
||||
|
||||
im2 = hopper("RGBA")
|
||||
assert_image_similar(im, im2, 10)
|
||||
assert_image_similar(im1, im2, 10)
|
||||
|
||||
|
||||
def test_n_frames() -> None:
|
||||
|
|
|
|||
|
|
@ -300,12 +300,12 @@ def test_save_all() -> None:
|
|||
im_reloaded.seek(1)
|
||||
assert_image_similar(im, im_reloaded, 30)
|
||||
|
||||
im = Image.new("RGB", (1, 1))
|
||||
im_rgb = Image.new("RGB", (1, 1))
|
||||
for colors in (("#f00",), ("#f00", "#0f0")):
|
||||
append_images = [Image.new("RGB", (1, 1), color) for color in colors]
|
||||
im_reloaded = roundtrip(im, save_all=True, append_images=append_images)
|
||||
im_reloaded = roundtrip(im_rgb, save_all=True, append_images=append_images)
|
||||
|
||||
assert_image_equal(im, im_reloaded)
|
||||
assert_image_equal(im_rgb, im_reloaded)
|
||||
assert isinstance(im_reloaded, MpoImagePlugin.MpoImageFile)
|
||||
assert im_reloaded.mpinfo is not None
|
||||
assert im_reloaded.mpinfo[45056] == b"0100"
|
||||
|
|
@ -315,7 +315,7 @@ def test_save_all() -> None:
|
|||
assert_image_similar(im_reloaded, im_expected, 1)
|
||||
|
||||
# Test that a single frame image will not be saved as an MPO
|
||||
jpg = roundtrip(im, save_all=True)
|
||||
jpg = roundtrip(im_rgb, save_all=True)
|
||||
assert "mp" not in jpg.info
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -338,6 +338,15 @@ class TestFilePng:
|
|||
assert colors is not None
|
||||
assert colors[0][0] == num_transparent
|
||||
|
||||
def test_save_1_transparency(self, tmp_path: Path) -> None:
|
||||
out = tmp_path / "temp.png"
|
||||
|
||||
im = Image.new("1", (1, 1), 1)
|
||||
im.save(out, transparency=1)
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert reloaded.info["transparency"] == 255
|
||||
|
||||
def test_save_rgb_single_transparency(self, tmp_path: Path) -> None:
|
||||
in_file = "Tests/images/caption_6_33_22.png"
|
||||
with Image.open(in_file) as im:
|
||||
|
|
|
|||
|
|
@ -84,8 +84,8 @@ def test_rgbx() -> None:
|
|||
|
||||
with Image.open(io.BytesIO(data)) as im:
|
||||
r, g, b = im.split()
|
||||
im = Image.merge("RGB", (b, g, r))
|
||||
assert_image_equal_tofile(im, os.path.join(EXTRA_DIR, "32bpp.png"))
|
||||
im_rgb = Image.merge("RGB", (b, g, r))
|
||||
assert_image_equal_tofile(im_rgb, os.path.join(EXTRA_DIR, "32bpp.png"))
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
|
|
|
|||
|
|
@ -764,9 +764,9 @@ class TestFileTiff:
|
|||
|
||||
# Test appending images
|
||||
mp = BytesIO()
|
||||
im = Image.new("RGB", (100, 100), "#f00")
|
||||
im_rgb = Image.new("RGB", (100, 100), "#f00")
|
||||
ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]]
|
||||
im.copy().save(mp, format="TIFF", save_all=True, append_images=ims)
|
||||
im_rgb.copy().save(mp, format="TIFF", save_all=True, append_images=ims)
|
||||
|
||||
mp.seek(0, os.SEEK_SET)
|
||||
with Image.open(mp) as reread:
|
||||
|
|
@ -778,7 +778,7 @@ class TestFileTiff:
|
|||
yield from ims
|
||||
|
||||
mp = BytesIO()
|
||||
im.save(mp, format="TIFF", save_all=True, append_images=im_generator(ims))
|
||||
im_rgb.save(mp, format="TIFF", save_all=True, append_images=im_generator(ims))
|
||||
|
||||
mp.seek(0, os.SEEK_SET)
|
||||
with Image.open(mp) as reread:
|
||||
|
|
|
|||
|
|
@ -175,13 +175,13 @@ def test_change_stripbytecounts_tag_type(tmp_path: Path) -> None:
|
|||
del info[278]
|
||||
|
||||
# Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT
|
||||
im = im.resize((500, 500))
|
||||
info[TiffImagePlugin.IMAGEWIDTH] = im.width
|
||||
im_resized = im.resize((500, 500))
|
||||
info[TiffImagePlugin.IMAGEWIDTH] = im_resized.width
|
||||
|
||||
# STRIPBYTECOUNTS can be a SHORT or a LONG
|
||||
info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT
|
||||
|
||||
im.save(out, tiffinfo=info)
|
||||
im_resized.save(out, tiffinfo=info)
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert isinstance(reloaded, TiffImagePlugin.TiffImageFile)
|
||||
|
|
|
|||
|
|
@ -613,8 +613,8 @@ class TestImage:
|
|||
assert im.getpixel((0, 0)) == 0
|
||||
assert im.getpixel((255, 255)) == 255
|
||||
with Image.open(target_file) as target:
|
||||
target = target.convert(mode)
|
||||
assert_image_equal(im, target)
|
||||
im_target = target.convert(mode)
|
||||
assert_image_equal(im, im_target)
|
||||
|
||||
def test_radial_gradient_wrong_mode(self) -> None:
|
||||
# Arrange
|
||||
|
|
@ -638,8 +638,8 @@ class TestImage:
|
|||
assert im.getpixel((0, 0)) == 255
|
||||
assert im.getpixel((128, 128)) == 0
|
||||
with Image.open(target_file) as target:
|
||||
target = target.convert(mode)
|
||||
assert_image_equal(im, target)
|
||||
im_target = target.convert(mode)
|
||||
assert_image_equal(im, im_target)
|
||||
|
||||
def test_register_extensions(self) -> None:
|
||||
test_format = "a"
|
||||
|
|
@ -663,20 +663,20 @@ class TestImage:
|
|||
assert_image_equal(im, im.remap_palette(list(range(256))))
|
||||
|
||||
# Test identity transform with an RGBA palette
|
||||
im = Image.new("P", (256, 1))
|
||||
im_p = Image.new("P", (256, 1))
|
||||
for x in range(256):
|
||||
im.putpixel((x, 0), x)
|
||||
im.putpalette(list(range(256)) * 4, "RGBA")
|
||||
im_remapped = im.remap_palette(list(range(256)))
|
||||
assert_image_equal(im, im_remapped)
|
||||
assert im.palette is not None
|
||||
im_p.putpixel((x, 0), x)
|
||||
im_p.putpalette(list(range(256)) * 4, "RGBA")
|
||||
im_remapped = im_p.remap_palette(list(range(256)))
|
||||
assert_image_equal(im_p, im_remapped)
|
||||
assert im_p.palette is not None
|
||||
assert im_remapped.palette is not None
|
||||
assert im.palette.palette == im_remapped.palette.palette
|
||||
assert im_p.palette.palette == im_remapped.palette.palette
|
||||
|
||||
# Test illegal image mode
|
||||
with hopper() as im:
|
||||
with hopper() as im_hopper:
|
||||
with pytest.raises(ValueError):
|
||||
im.remap_palette([])
|
||||
im_hopper.remap_palette([])
|
||||
|
||||
def test_remap_palette_transparency(self) -> None:
|
||||
im = Image.new("P", (1, 2), (0, 0, 0))
|
||||
|
|
|
|||
|
|
@ -80,8 +80,8 @@ def test_16bit() -> None:
|
|||
_test_float_conversion(im)
|
||||
|
||||
for color in (65535, 65536):
|
||||
im = Image.new("I", (1, 1), color)
|
||||
im_i16 = im.convert("I;16")
|
||||
im_i = Image.new("I", (1, 1), color)
|
||||
im_i16 = im_i.convert("I;16")
|
||||
assert im_i16.getpixel((0, 0)) == 65535
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -78,13 +78,13 @@ def test_crop_crash() -> None:
|
|||
extents = (1, 1, 10, 10)
|
||||
# works prepatch
|
||||
with Image.open(test_img) as img:
|
||||
img2 = img.crop(extents)
|
||||
img2.load()
|
||||
img1 = img.crop(extents)
|
||||
img1.load()
|
||||
|
||||
# fail prepatch
|
||||
with Image.open(test_img) as img:
|
||||
img = img.crop(extents)
|
||||
img.load()
|
||||
img2 = img.crop(extents)
|
||||
img2.load()
|
||||
|
||||
|
||||
def test_crop_zero() -> None:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ def test_sanity() -> None:
|
|||
|
||||
|
||||
def test_mode() -> None:
|
||||
def getdata(mode: str) -> tuple[float | tuple[int, ...], int, int]:
|
||||
def getdata(mode: str) -> tuple[float | tuple[int, ...] | None, int, int]:
|
||||
im = hopper(mode).resize((32, 30), Image.Resampling.NEAREST)
|
||||
data = im.getdata()
|
||||
return data[0], len(data), len(list(data))
|
||||
|
|
|
|||
|
|
@ -58,8 +58,8 @@ def test_rgba_quantize() -> None:
|
|||
|
||||
def test_quantize() -> None:
|
||||
with Image.open("Tests/images/caption_6_33_22.png") as image:
|
||||
image = image.convert("RGB")
|
||||
converted = image.quantize()
|
||||
converted = image.convert("RGB")
|
||||
converted = converted.quantize()
|
||||
assert converted.mode == "P"
|
||||
assert_image_similar(converted.convert("RGB"), image, 1)
|
||||
|
||||
|
|
@ -67,13 +67,13 @@ def test_quantize() -> None:
|
|||
def test_quantize_no_dither() -> None:
|
||||
image = hopper()
|
||||
with Image.open("Tests/images/caption_6_33_22.png") as palette:
|
||||
palette = palette.convert("P")
|
||||
palette_p = palette.convert("P")
|
||||
|
||||
converted = image.quantize(dither=Image.Dither.NONE, palette=palette)
|
||||
converted = image.quantize(dither=Image.Dither.NONE, palette=palette_p)
|
||||
assert converted.mode == "P"
|
||||
assert converted.palette is not None
|
||||
assert palette.palette is not None
|
||||
assert converted.palette.palette == palette.palette.palette
|
||||
assert palette_p.palette is not None
|
||||
assert converted.palette.palette == palette_p.palette.palette
|
||||
|
||||
|
||||
def test_quantize_no_dither2() -> None:
|
||||
|
|
@ -97,10 +97,10 @@ def test_quantize_no_dither2() -> None:
|
|||
def test_quantize_dither_diff() -> None:
|
||||
image = hopper()
|
||||
with Image.open("Tests/images/caption_6_33_22.png") as palette:
|
||||
palette = palette.convert("P")
|
||||
palette_p = palette.convert("P")
|
||||
|
||||
dither = image.quantize(dither=Image.Dither.FLOYDSTEINBERG, palette=palette)
|
||||
nodither = image.quantize(dither=Image.Dither.NONE, palette=palette)
|
||||
dither = image.quantize(dither=Image.Dither.FLOYDSTEINBERG, palette=palette_p)
|
||||
nodither = image.quantize(dither=Image.Dither.NONE, palette=palette_p)
|
||||
|
||||
assert dither.tobytes() != nodither.tobytes()
|
||||
|
||||
|
|
|
|||
|
|
@ -314,8 +314,8 @@ class TestImageResize:
|
|||
@skip_unless_feature("libtiff")
|
||||
def test_transposed(self) -> None:
|
||||
with Image.open("Tests/images/g4_orientation_5.tif") as im:
|
||||
im = im.resize((64, 64))
|
||||
assert im.size == (64, 64)
|
||||
im_resized = im.resize((64, 64))
|
||||
assert im_resized.size == (64, 64)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mode", ("L", "RGB", "I", "I;16", "I;16L", "I;16B", "I;16N", "F")
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ def test_angle(angle: int) -> None:
|
|||
with Image.open("Tests/images/test-card.png") as im:
|
||||
rotate(im, im.mode, angle)
|
||||
|
||||
im = hopper()
|
||||
assert_image_equal(im.rotate(angle), im.rotate(angle, expand=1))
|
||||
im_hopper = hopper()
|
||||
assert_image_equal(im_hopper.rotate(angle), im_hopper.rotate(angle, expand=1))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("angle", (0, 45, 90, 180, 270))
|
||||
|
|
@ -76,9 +76,9 @@ def test_center_0() -> None:
|
|||
|
||||
with Image.open("Tests/images/hopper_45.png") as target:
|
||||
target_origin = target.size[1] / 2
|
||||
target = target.crop((0, target_origin, 128, target_origin + 128))
|
||||
im_target = target.crop((0, target_origin, 128, target_origin + 128))
|
||||
|
||||
assert_image_similar(im, target, 15)
|
||||
assert_image_similar(im, im_target, 15)
|
||||
|
||||
|
||||
def test_center_14() -> None:
|
||||
|
|
@ -87,22 +87,22 @@ def test_center_14() -> None:
|
|||
|
||||
with Image.open("Tests/images/hopper_45.png") as target:
|
||||
target_origin = target.size[1] / 2 - 14
|
||||
target = target.crop((6, target_origin, 128 + 6, target_origin + 128))
|
||||
im_target = target.crop((6, target_origin, 128 + 6, target_origin + 128))
|
||||
|
||||
assert_image_similar(im, target, 10)
|
||||
assert_image_similar(im, im_target, 10)
|
||||
|
||||
|
||||
def test_translate() -> None:
|
||||
im = hopper()
|
||||
with Image.open("Tests/images/hopper_45.png") as target:
|
||||
target_origin = (target.size[1] / 2 - 64) - 5
|
||||
target = target.crop(
|
||||
im_target = target.crop(
|
||||
(target_origin, target_origin, target_origin + 128, target_origin + 128)
|
||||
)
|
||||
|
||||
im = im.rotate(45, translate=(5, 5), resample=Image.Resampling.BICUBIC)
|
||||
|
||||
assert_image_similar(im, target, 1)
|
||||
assert_image_similar(im, im_target, 1)
|
||||
|
||||
|
||||
def test_fastpath_center() -> None:
|
||||
|
|
|
|||
|
|
@ -159,9 +159,9 @@ def test_reducing_gap_for_DCT_scaling() -> None:
|
|||
with Image.open("Tests/images/hopper.jpg") as ref:
|
||||
# thumbnail should call draft with reducing_gap scale
|
||||
ref.draft(None, (18 * 3, 18 * 3))
|
||||
ref = ref.resize((18, 18), Image.Resampling.BICUBIC)
|
||||
im_ref = ref.resize((18, 18), Image.Resampling.BICUBIC)
|
||||
|
||||
with Image.open("Tests/images/hopper.jpg") as im:
|
||||
im.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=3.0)
|
||||
|
||||
assert_image_similar(ref, im, 1.4)
|
||||
assert_image_similar(im_ref, im, 1.4)
|
||||
|
|
|
|||
|
|
@ -198,10 +198,10 @@ def test_bitmap() -> None:
|
|||
im = Image.new("RGB", (W, H))
|
||||
draw = ImageDraw.Draw(im)
|
||||
with Image.open("Tests/images/pil123rgba.png") as small:
|
||||
small = small.resize((50, 50), Image.Resampling.NEAREST)
|
||||
small_resized = small.resize((50, 50), Image.Resampling.NEAREST)
|
||||
|
||||
# Act
|
||||
draw.bitmap((10, 10), small)
|
||||
draw.bitmap((10, 10), small_resized)
|
||||
|
||||
# Assert
|
||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_bitmap.png")
|
||||
|
|
|
|||
|
|
@ -261,10 +261,10 @@ def test_colorize_2color() -> None:
|
|||
|
||||
# Open test image (256px by 10px, black to white)
|
||||
with Image.open("Tests/images/bw_gradient.png") as im:
|
||||
im = im.convert("L")
|
||||
im_l = im.convert("L")
|
||||
|
||||
# Create image with original 2-color functionality
|
||||
im_test = ImageOps.colorize(im, "red", "green")
|
||||
im_test = ImageOps.colorize(im_l, "red", "green")
|
||||
|
||||
# Test output image (2-color)
|
||||
left = (0, 1)
|
||||
|
|
@ -301,11 +301,11 @@ def test_colorize_2color_offset() -> None:
|
|||
|
||||
# Open test image (256px by 10px, black to white)
|
||||
with Image.open("Tests/images/bw_gradient.png") as im:
|
||||
im = im.convert("L")
|
||||
im_l = im.convert("L")
|
||||
|
||||
# Create image with original 2-color functionality with offsets
|
||||
im_test = ImageOps.colorize(
|
||||
im, black="red", white="green", blackpoint=50, whitepoint=100
|
||||
im_l, black="red", white="green", blackpoint=50, whitepoint=100
|
||||
)
|
||||
|
||||
# Test output image (2-color) with offsets
|
||||
|
|
@ -343,11 +343,11 @@ def test_colorize_3color_offset() -> None:
|
|||
|
||||
# Open test image (256px by 10px, black to white)
|
||||
with Image.open("Tests/images/bw_gradient.png") as im:
|
||||
im = im.convert("L")
|
||||
im_l = im.convert("L")
|
||||
|
||||
# Create image with new three color functionality with offsets
|
||||
im_test = ImageOps.colorize(
|
||||
im,
|
||||
im_l,
|
||||
black="red",
|
||||
white="green",
|
||||
mid="blue",
|
||||
|
|
|
|||
|
|
@ -49,6 +49,12 @@ def test_getcolor() -> None:
|
|||
palette.getcolor("unknown") # type: ignore[arg-type]
|
||||
|
||||
|
||||
def test_getcolor_rgba() -> None:
|
||||
palette = ImagePalette.ImagePalette("RGBA", (1, 2, 3, 4))
|
||||
palette.getcolor((5, 6, 7, 8))
|
||||
assert palette.palette == b"\x01\x02\x03\x04\x05\x06\x07\x08"
|
||||
|
||||
|
||||
def test_getcolor_rgba_color_rgb_palette() -> None:
|
||||
palette = ImagePalette.ImagePalette("RGB")
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageText
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageText, features
|
||||
|
||||
from .helper import assert_image_similar_tofile, skip_unless_feature
|
||||
|
||||
|
|
@ -20,37 +20,75 @@ def layout_engine(request: pytest.FixtureRequest) -> ImageFont.Layout:
|
|||
return request.param
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def font(layout_engine: ImageFont.Layout) -> ImageFont.FreeTypeFont:
|
||||
return ImageFont.truetype(FONT_PATH, 20, layout_engine=layout_engine)
|
||||
@pytest.fixture(
|
||||
scope="module",
|
||||
params=[
|
||||
None,
|
||||
pytest.param(ImageFont.Layout.BASIC, marks=skip_unless_feature("freetype2")),
|
||||
pytest.param(ImageFont.Layout.RAQM, marks=skip_unless_feature("raqm")),
|
||||
],
|
||||
)
|
||||
def font(
|
||||
request: pytest.FixtureRequest,
|
||||
) -> ImageFont.ImageFont | ImageFont.FreeTypeFont:
|
||||
layout_engine = request.param
|
||||
if layout_engine is None:
|
||||
return ImageFont.load_default_imagefont()
|
||||
else:
|
||||
return ImageFont.truetype(FONT_PATH, 20, layout_engine=layout_engine)
|
||||
|
||||
|
||||
def test_get_length(font: ImageFont.FreeTypeFont) -> None:
|
||||
assert ImageText.Text("A", font).get_length() == 12
|
||||
assert ImageText.Text("AB", font).get_length() == 24
|
||||
assert ImageText.Text("M", font).get_length() == 12
|
||||
assert ImageText.Text("y", font).get_length() == 12
|
||||
assert ImageText.Text("a", font).get_length() == 12
|
||||
def test_get_length(font: ImageFont.ImageFont | ImageFont.FreeTypeFont) -> None:
|
||||
factor = 1 if isinstance(font, ImageFont.ImageFont) else 2
|
||||
assert ImageText.Text("A", font).get_length() == 6 * factor
|
||||
assert ImageText.Text("AB", font).get_length() == 12 * factor
|
||||
assert ImageText.Text("M", font).get_length() == 6 * factor
|
||||
assert ImageText.Text("y", font).get_length() == 6 * factor
|
||||
assert ImageText.Text("a", font).get_length() == 6 * factor
|
||||
|
||||
text = ImageText.Text("\n", font)
|
||||
with pytest.raises(ValueError, match="can't measure length of multiline text"):
|
||||
text.get_length()
|
||||
|
||||
|
||||
def test_get_bbox(font: ImageFont.FreeTypeFont) -> None:
|
||||
assert ImageText.Text("A", font).get_bbox() == (0, 4, 12, 16)
|
||||
assert ImageText.Text("AB", font).get_bbox() == (0, 4, 24, 16)
|
||||
assert ImageText.Text("M", font).get_bbox() == (0, 4, 12, 16)
|
||||
assert ImageText.Text("y", font).get_bbox() == (0, 7, 12, 20)
|
||||
assert ImageText.Text("a", font).get_bbox() == (0, 7, 12, 16)
|
||||
@pytest.mark.parametrize(
|
||||
"text, expected",
|
||||
(
|
||||
("A", (0, 4, 12, 16)),
|
||||
("AB", (0, 4, 24, 16)),
|
||||
("M", (0, 4, 12, 16)),
|
||||
("y", (0, 7, 12, 20)),
|
||||
("a", (0, 7, 12, 16)),
|
||||
),
|
||||
)
|
||||
def test_get_bbox(
|
||||
font: ImageFont.ImageFont | ImageFont.FreeTypeFont,
|
||||
text: str,
|
||||
expected: tuple[int, int, int, int],
|
||||
) -> None:
|
||||
if isinstance(font, ImageFont.ImageFont):
|
||||
expected = (0, 0, expected[2] // 2, 11)
|
||||
assert ImageText.Text(text, font).get_bbox() == expected
|
||||
|
||||
|
||||
def test_standard_embedded_color(layout_engine: ImageFont.Layout) -> None:
|
||||
font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
|
||||
text = ImageText.Text("Hello World!", font)
|
||||
text.embed_color()
|
||||
if features.check_module("freetype2"):
|
||||
font = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
|
||||
text = ImageText.Text("Hello World!", font)
|
||||
text.embed_color()
|
||||
assert text.get_length() == 288
|
||||
|
||||
im = Image.new("RGB", (300, 64), "white")
|
||||
draw = ImageDraw.Draw(im)
|
||||
draw.text((10, 10), text, "#fa6")
|
||||
im = Image.new("RGB", (300, 64), "white")
|
||||
draw = ImageDraw.Draw(im)
|
||||
draw.text((10, 10), text, "#fa6")
|
||||
|
||||
assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1)
|
||||
assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1)
|
||||
|
||||
text = ImageText.Text("", mode="1")
|
||||
with pytest.raises(
|
||||
ValueError, match="Embedded color supported only in RGB and RGBA modes"
|
||||
):
|
||||
text.embed_color()
|
||||
|
||||
|
||||
@skip_unless_feature("freetype2")
|
||||
|
|
|
|||
|
|
@ -90,18 +90,18 @@ def test_pickle_la_mode_with_palette(tmp_path: Path) -> None:
|
|||
# Arrange
|
||||
filename = tmp_path / "temp.pkl"
|
||||
with Image.open("Tests/images/hopper.jpg") as im:
|
||||
im = im.convert("PA")
|
||||
im_pa = im.convert("PA")
|
||||
|
||||
# Act / Assert
|
||||
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
|
||||
im._mode = "LA"
|
||||
im_pa._mode = "LA"
|
||||
with open(filename, "wb") as f:
|
||||
pickle.dump(im, f, protocol)
|
||||
pickle.dump(im_pa, f, protocol)
|
||||
with open(filename, "rb") as f:
|
||||
loaded_im = pickle.load(f)
|
||||
|
||||
im._mode = "PA"
|
||||
assert im == loaded_im
|
||||
im_pa._mode = "PA"
|
||||
assert im_pa == loaded_im
|
||||
|
||||
|
||||
@skip_unless_feature("webp")
|
||||
|
|
|
|||
|
|
@ -49,11 +49,13 @@ class TestShellInjection:
|
|||
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
|
||||
def test_save_netpbm_filename_bmp_mode(self, tmp_path: Path) -> None:
|
||||
with Image.open(TEST_GIF) as im:
|
||||
im = im.convert("RGB")
|
||||
self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm)
|
||||
im_rgb = im.convert("RGB")
|
||||
self.assert_save_filename_check(
|
||||
tmp_path, im_rgb, GifImagePlugin._save_netpbm
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(not netpbm_available(), reason="Netpbm not available")
|
||||
def test_save_netpbm_filename_l_mode(self, tmp_path: Path) -> None:
|
||||
with Image.open(TEST_GIF) as im:
|
||||
im = im.convert("L")
|
||||
self.assert_save_filename_check(tmp_path, im, GifImagePlugin._save_netpbm)
|
||||
im_l = im.convert("L")
|
||||
self.assert_save_filename_check(tmp_path, im_l, GifImagePlugin._save_netpbm)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# install libimagequant
|
||||
|
||||
archive_name=libimagequant
|
||||
archive_version=4.4.0
|
||||
archive_version=4.4.1
|
||||
|
||||
archive=$archive_name-$archive_version
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ Many of Pillow's features require external libraries:
|
|||
|
||||
* **libimagequant** provides improved color quantization
|
||||
|
||||
* Pillow has been tested with libimagequant **2.6-4.4.0**
|
||||
* Pillow has been tested with libimagequant **2.6-4.4.1**
|
||||
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||
the Pillow license, therefore we will not be distributing binaries
|
||||
with libimagequant support enabled.
|
||||
|
|
@ -116,7 +116,7 @@ Many of Pillow's features require external libraries:
|
|||
|
||||
.. 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::
|
||||
Prerequisites for **Ubuntu 16.04 LTS - 24.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 \
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ These platforms are built and tested for every change.
|
|||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 42 | 3.13 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 43 | 3.14 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Gentoo | 3.12 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 15 Sequoia | 3.10 | x86-64 |
|
||||
|
|
|
|||
|
|
@ -47,9 +47,11 @@ or the clipboard to a PIL image memory.
|
|||
.. versionadded:: 7.1.0
|
||||
|
||||
:param window:
|
||||
HWND, to capture a single window. Windows only.
|
||||
Capture a single window. On Windows, this is a HWND. On macOS, this is a
|
||||
CGWindowID.
|
||||
|
||||
.. versionadded:: 11.2.1
|
||||
.. versionadded:: 11.2.1 Windows support
|
||||
.. versionadded:: 12.1.0 macOS support
|
||||
|
||||
:param scale_down: On macOS, Retina screens will provide images at 2x size by default. This will prevent that, and scale down to 1x.
|
||||
Keyword-only argument.
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
Although we try to use official sources for dependencies, sometimes the official
|
||||
sources don't support a platform (especially mobile platforms), or there's a bug
|
||||
fix/feature that is required to support Pillow's usage.
|
||||
|
||||
This folder contains patches that must be applied to official sources, organized
|
||||
by the platforms that need those patches.
|
||||
|
||||
Each patch is against the root of the unpacked official tarball, and is named by
|
||||
appending `.patch` to the end of the tarball that is to be patched. This
|
||||
includes the full version number; so if the version is bumped, the patch will
|
||||
at a minimum require a filename change.
|
||||
|
||||
Wherever possible, these patches should be contributed upstream, in the hope that
|
||||
future Pillow versions won't need to maintain these patches.
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
# Brotli 1.1.0 doesn't have explicit support for iOS as a CMAKE_SYSTEM_NAME.
|
||||
# That release was from 2023; there have been subsequent changes that allow
|
||||
# Brotli to build on iOS without any patches, as long as -DBROTLI_BUILD_TOOLS=NO
|
||||
# is specified on the command line.
|
||||
#
|
||||
diff -ru brotli-1.1.0-orig/CMakeLists.txt brotli-1.1.0/CMakeLists.txt
|
||||
--- brotli-1.1.0-orig/CMakeLists.txt 2023-08-29 19:00:29
|
||||
+++ brotli-1.1.0/CMakeLists.txt 2024-11-07 10:46:26
|
||||
@@ -114,6 +114,8 @@
|
||||
add_definitions(-DOS_MACOSX)
|
||||
set(CMAKE_MACOS_RPATH TRUE)
|
||||
set(CMAKE_INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib")
|
||||
+elseif(${CMAKE_SYSTEM_NAME} MATCHES "iOS")
|
||||
+ add_definitions(-DOS_IOS)
|
||||
endif()
|
||||
|
||||
if(BROTLI_EMSCRIPTEN)
|
||||
@@ -174,10 +176,12 @@
|
||||
|
||||
# Installation
|
||||
if(NOT BROTLI_BUNDLED_MODE)
|
||||
- install(
|
||||
- TARGETS brotli
|
||||
- RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||
- )
|
||||
+ if(NOT ${CMAKE_SYSTEM_NAME} MATCHES "iOS")
|
||||
+ install(
|
||||
+ TARGETS brotli
|
||||
+ RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
|
||||
+ )
|
||||
+ endif()
|
||||
|
||||
install(
|
||||
TARGETS ${BROTLI_LIBRARIES_CORE}
|
||||
diff -ru brotli-1.1.0-orig/c/common/platform.h brotli-1.1.0/c/common/platform.h
|
||||
--- brotli-1.1.0-orig/c/common/platform.h 2023-08-29 19:00:29
|
||||
+++ brotli-1.1.0/c/common/platform.h 2024-11-07 10:47:28
|
||||
@@ -33,7 +33,7 @@
|
||||
#include <endian.h>
|
||||
#elif defined(OS_FREEBSD)
|
||||
#include <machine/endian.h>
|
||||
-#elif defined(OS_MACOSX)
|
||||
+#elif defined(OS_MACOSX) || defined(OS_IOS)
|
||||
#include <machine/endian.h>
|
||||
/* Let's try and follow the Linux convention */
|
||||
#define BROTLI_X_BYTE_ORDER BYTE_ORDER
|
||||
|
|
@ -189,7 +189,6 @@ lint.ignore = [
|
|||
"PT012", # pytest-raises-with-multiple-statements
|
||||
"PT017", # pytest-assert-in-except
|
||||
"PYI034", # flake8-pyi: typing.Self added in Python 3.11
|
||||
"UP038", # pyupgrade: deprecated rule
|
||||
]
|
||||
lint.per-file-ignores."Tests/oss-fuzz/fuzz_font.py" = [
|
||||
"I002",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ https://creativecommons.org/publicdomain/zero/1.0/
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import struct
|
||||
import sys
|
||||
from enum import IntEnum, IntFlag
|
||||
|
|
@ -333,6 +332,7 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
format_description = "DirectDraw Surface"
|
||||
|
||||
def _open(self) -> None:
|
||||
assert self.fp is not None
|
||||
if not _accept(self.fp.read(4)):
|
||||
msg = "not a DDS file"
|
||||
raise SyntaxError(msg)
|
||||
|
|
@ -340,21 +340,20 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
if header_size != 124:
|
||||
msg = f"Unsupported header size {repr(header_size)}"
|
||||
raise OSError(msg)
|
||||
header_bytes = self.fp.read(header_size - 4)
|
||||
if len(header_bytes) != 120:
|
||||
msg = f"Incomplete header: {len(header_bytes)} bytes"
|
||||
header = self.fp.read(header_size - 4)
|
||||
if len(header) != 120:
|
||||
msg = f"Incomplete header: {len(header)} bytes"
|
||||
raise OSError(msg)
|
||||
header = io.BytesIO(header_bytes)
|
||||
|
||||
flags, height, width = struct.unpack("<3I", header.read(12))
|
||||
flags, height, width = struct.unpack("<3I", header[:12])
|
||||
self._size = (width, height)
|
||||
extents = (0, 0) + self.size
|
||||
|
||||
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
||||
struct.unpack("<11I", header.read(44)) # reserved
|
||||
pitch, depth, mipmaps = struct.unpack("<3I", header[12:24])
|
||||
struct.unpack("<11I", header[24:68]) # reserved
|
||||
|
||||
# pixel format
|
||||
pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16))
|
||||
pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header[68:84])
|
||||
n = 0
|
||||
rawmode = None
|
||||
if pfflags & DDPF.RGB:
|
||||
|
|
@ -366,7 +365,7 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
self._mode = "RGB"
|
||||
mask_count = 3
|
||||
|
||||
masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
|
||||
masks = struct.unpack(f"<{mask_count}I", header[84 : 84 + mask_count * 4])
|
||||
self.tile = [ImageFile._Tile("dds_rgb", extents, 0, (bitcount, masks))]
|
||||
return
|
||||
elif pfflags & DDPF.LUMINANCE:
|
||||
|
|
@ -516,6 +515,8 @@ class DdsRgbDecoder(ImageFile.PyDecoder):
|
|||
# Remove the zero padding, and scale it to 8 bits
|
||||
data += o8(
|
||||
int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255)
|
||||
if mask_totals[i]
|
||||
else 0
|
||||
)
|
||||
self.set_as_raw(data)
|
||||
return -1, 0
|
||||
|
|
|
|||
|
|
@ -17,6 +17,20 @@
|
|||
# <casadebender@gmail.com>.
|
||||
# https://code.google.com/archive/p/casadebender/wikis/Win32IconImagePlugin.wiki
|
||||
#
|
||||
# Copyright 2008 Bryan Davis
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Icon format references:
|
||||
# * https://en.wikipedia.org/wiki/ICO_(file_format)
|
||||
# * https://msdn.microsoft.com/en-us/library/ms997538.aspx
|
||||
|
|
|
|||
|
|
@ -127,11 +127,15 @@ class ImageFont:
|
|||
def _load_pilfont_data(self, file: IO[bytes], image: Image.Image) -> None:
|
||||
# check image
|
||||
if image.mode not in ("1", "L"):
|
||||
image.close()
|
||||
|
||||
msg = "invalid font image mode"
|
||||
raise TypeError(msg)
|
||||
|
||||
# read PILfont header
|
||||
if file.read(8) != b"PILfont\n":
|
||||
image.close()
|
||||
|
||||
msg = "Not a PILfont file"
|
||||
raise SyntaxError(msg)
|
||||
file.readline()
|
||||
|
|
|
|||
|
|
@ -45,17 +45,46 @@ def grab(
|
|||
fh, filepath = tempfile.mkstemp(".png")
|
||||
os.close(fh)
|
||||
args = ["screencapture"]
|
||||
if bbox:
|
||||
if window:
|
||||
args += ["-l", str(window)]
|
||||
elif bbox:
|
||||
left, top, right, bottom = bbox
|
||||
args += ["-R", f"{left},{top},{right-left},{bottom-top}"]
|
||||
subprocess.call(args + ["-x", filepath])
|
||||
im = Image.open(filepath)
|
||||
im.load()
|
||||
os.unlink(filepath)
|
||||
if bbox and scale_down:
|
||||
im_resized = im.resize((right - left, bottom - top))
|
||||
im.close()
|
||||
return im_resized
|
||||
if bbox:
|
||||
if window:
|
||||
# Determine if the window was in Retina mode or not
|
||||
# by capturing it without the shadow,
|
||||
# and checking how different the width is
|
||||
fh, filepath = tempfile.mkstemp(".png")
|
||||
os.close(fh)
|
||||
subprocess.call(
|
||||
["screencapture", "-l", str(window), "-o", "-x", filepath]
|
||||
)
|
||||
with Image.open(filepath) as im_no_shadow:
|
||||
retina = im.width - im_no_shadow.width > 100
|
||||
os.unlink(filepath)
|
||||
|
||||
# Since screencapture's -R does not work with -l,
|
||||
# crop the image manually
|
||||
if retina:
|
||||
left, top, right, bottom = bbox
|
||||
scale = 1 if scale_down else 2
|
||||
im_cropped = im.resize(
|
||||
((right - left) * scale, (bottom - top) * scale),
|
||||
box=tuple(coord * 2 for coord in bbox),
|
||||
)
|
||||
else:
|
||||
im_cropped = im.crop(bbox)
|
||||
im.close()
|
||||
return im_cropped
|
||||
elif scale_down:
|
||||
im_resized = im.resize((right - left, bottom - top))
|
||||
im.close()
|
||||
return im_resized
|
||||
return im
|
||||
elif sys.platform == "win32":
|
||||
if window is not None:
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ class ImagePalette:
|
|||
) -> int:
|
||||
if not isinstance(self.palette, bytearray):
|
||||
self._palette = bytearray(self.palette)
|
||||
index = len(self.palette) // 3
|
||||
index = len(self.palette) // len(self.mode)
|
||||
special_colors: tuple[int | tuple[int, ...] | None, ...] = ()
|
||||
if image:
|
||||
special_colors = (
|
||||
|
|
@ -168,11 +168,12 @@ class ImagePalette:
|
|||
index = self._new_color_index(image, e)
|
||||
assert isinstance(self._palette, bytearray)
|
||||
self.colors[color] = index
|
||||
if index * 3 < len(self.palette):
|
||||
mode_len = len(self.mode)
|
||||
if index * mode_len < len(self.palette):
|
||||
self._palette = (
|
||||
self._palette[: index * 3]
|
||||
self._palette[: index * mode_len]
|
||||
+ bytes(color)
|
||||
+ self._palette[index * 3 + 3 :]
|
||||
+ self._palette[index * mode_len + mode_len :]
|
||||
)
|
||||
else:
|
||||
self._palette += bytes(color)
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class Text:
|
|||
else:
|
||||
return "L"
|
||||
|
||||
def get_length(self):
|
||||
def get_length(self) -> float:
|
||||
"""
|
||||
Returns length (in pixels with 1/64 precision) of text.
|
||||
|
||||
|
|
@ -130,8 +130,11 @@ class Text:
|
|||
|
||||
:return: Either width for horizontal text, or height for vertical text.
|
||||
"""
|
||||
split_character = "\n" if isinstance(self.text, str) else b"\n"
|
||||
if split_character in self.text:
|
||||
if isinstance(self.text, str):
|
||||
multiline = "\n" in self.text
|
||||
else:
|
||||
multiline = b"\n" in self.text
|
||||
if multiline:
|
||||
msg = "can't measure length of multiline text"
|
||||
raise ValueError(msg)
|
||||
return self.font.getlength(
|
||||
|
|
@ -313,6 +316,5 @@ class Text:
|
|||
max(bbox[3], bbox_line[3]),
|
||||
)
|
||||
|
||||
if bbox is None:
|
||||
return xy[0], xy[1], xy[0], xy[1]
|
||||
assert bbox is not None
|
||||
return bbox
|
||||
|
|
|
|||
|
|
@ -509,7 +509,9 @@ class PngStream(ChunkStream):
|
|||
# otherwise, we have a byte string with one alpha value
|
||||
# for each palette entry
|
||||
self.im_info["transparency"] = s
|
||||
elif self.im_mode in ("1", "L", "I;16"):
|
||||
elif self.im_mode == "1":
|
||||
self.im_info["transparency"] = 255 if i16(s) else 0
|
||||
elif self.im_mode in ("L", "I;16"):
|
||||
self.im_info["transparency"] = i16(s)
|
||||
elif self.im_mode == "RGB":
|
||||
self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4)
|
||||
|
|
@ -1152,6 +1154,15 @@ class _fdat:
|
|||
self.seq_num += 1
|
||||
|
||||
|
||||
def _apply_encoderinfo(im: Image.Image, encoderinfo: dict[str, Any]) -> None:
|
||||
im.encoderconfig = (
|
||||
encoderinfo.get("optimize", False),
|
||||
encoderinfo.get("compress_level", -1),
|
||||
encoderinfo.get("compress_type", -1),
|
||||
encoderinfo.get("dictionary", b""),
|
||||
)
|
||||
|
||||
|
||||
class _Frame(NamedTuple):
|
||||
im: Image.Image
|
||||
bbox: tuple[int, int, int, int] | None
|
||||
|
|
@ -1245,10 +1256,10 @@ def _write_multiple_frames(
|
|||
|
||||
# default image IDAT (if it exists)
|
||||
if default_image:
|
||||
if im.mode != mode:
|
||||
im = im.convert(mode)
|
||||
default_im = im if im.mode == mode else im.convert(mode)
|
||||
_apply_encoderinfo(default_im, im.encoderinfo)
|
||||
ImageFile._save(
|
||||
im,
|
||||
default_im,
|
||||
cast(IO[bytes], _idat(fp, chunk)),
|
||||
[ImageFile._Tile("zip", (0, 0) + im.size, 0, rawmode)],
|
||||
)
|
||||
|
|
@ -1282,6 +1293,7 @@ def _write_multiple_frames(
|
|||
)
|
||||
seq_num += 1
|
||||
# frame data
|
||||
_apply_encoderinfo(im_frame, im.encoderinfo)
|
||||
if frame == 0 and not default_image:
|
||||
# first frame must be in IDAT chunks for backwards compatibility
|
||||
ImageFile._save(
|
||||
|
|
@ -1357,14 +1369,6 @@ def _save(
|
|||
bits = 4
|
||||
outmode += f";{bits}"
|
||||
|
||||
# encoder options
|
||||
im.encoderconfig = (
|
||||
im.encoderinfo.get("optimize", False),
|
||||
im.encoderinfo.get("compress_level", -1),
|
||||
im.encoderinfo.get("compress_type", -1),
|
||||
im.encoderinfo.get("dictionary", b""),
|
||||
)
|
||||
|
||||
# get the corresponding PNG mode
|
||||
try:
|
||||
rawmode, bit_depth, color_type = _OUTMODES[outmode]
|
||||
|
|
@ -1494,6 +1498,7 @@ def _save(
|
|||
im, fp, chunk, mode, rawmode, default_image, append_images
|
||||
)
|
||||
if single_im:
|
||||
_apply_encoderinfo(single_im, im.encoderinfo)
|
||||
ImageFile._save(
|
||||
single_im,
|
||||
cast(IO[bytes], _idat(fp, chunk)),
|
||||
|
|
|
|||
|
|
@ -558,7 +558,6 @@ LIBTIFF_CORE = {
|
|||
LIBTIFF_CORE.remove(255) # We don't have support for subfiletypes
|
||||
LIBTIFF_CORE.remove(322) # We don't have support for writing tiled images with libtiff
|
||||
LIBTIFF_CORE.remove(323) # Tiled images
|
||||
LIBTIFF_CORE.remove(333) # Ink Names either
|
||||
|
||||
# Note to advanced users: There may be combinations of these
|
||||
# parameters and values that when added properly, will work and
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Any
|
||||
|
||||
class ImagingCore:
|
||||
def __getitem__(self, index: int) -> float: ...
|
||||
def __getitem__(self, index: int) -> float | tuple[int, ...] | None: ...
|
||||
def __getattr__(self, name: str) -> Any: ...
|
||||
|
||||
class ImagingFont:
|
||||
|
|
|
|||
|
|
@ -543,12 +543,7 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) {
|
|||
case IMAGING_TYPE_FLOAT32:
|
||||
return PyFloat_FromDouble(pixel.f);
|
||||
case IMAGING_TYPE_SPECIAL:
|
||||
if (im->bands == 1) {
|
||||
return PyLong_FromLong(pixel.h);
|
||||
} else {
|
||||
return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]);
|
||||
}
|
||||
break;
|
||||
return PyLong_FromLong(pixel.h);
|
||||
}
|
||||
|
||||
/* unknown type */
|
||||
|
|
@ -665,26 +660,10 @@ getink(PyObject *color, Imaging im, char *ink) {
|
|||
memcpy(ink, &ftmp, sizeof(ftmp));
|
||||
return ink;
|
||||
case IMAGING_TYPE_SPECIAL:
|
||||
if (isModeI16(im->mode)) {
|
||||
ink[0] = (UINT8)r;
|
||||
ink[1] = (UINT8)(r >> 8);
|
||||
ink[2] = ink[3] = 0;
|
||||
return ink;
|
||||
} else {
|
||||
if (rIsInt) {
|
||||
b = (UINT8)(r >> 16);
|
||||
g = (UINT8)(r >> 8);
|
||||
r = (UINT8)r;
|
||||
} else if (tupleSize != 3) {
|
||||
PyErr_SetString(
|
||||
PyExc_TypeError,
|
||||
"color must be int, or tuple of one or three elements"
|
||||
);
|
||||
return NULL;
|
||||
} else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
ink[0] = (UINT8)r;
|
||||
ink[1] = (UINT8)(r >> 8);
|
||||
ink[2] = ink[3] = 0;
|
||||
return ink;
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_ValueError, wrong_mode);
|
||||
|
|
|
|||
20
src/encode.c
20
src/encode.c
|
|
@ -668,10 +668,10 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
int key_int, status, is_core_tag, is_var_length, num_core_tags, i;
|
||||
TIFFDataType type = TIFF_NOTYPE;
|
||||
// This list also exists in TiffTags.py
|
||||
const int core_tags[] = {256, 257, 258, 259, 262, 263, 266, 269, 274,
|
||||
277, 278, 280, 281, 340, 341, 282, 283, 284,
|
||||
286, 287, 296, 297, 320, 321, 338, 32995, 32998,
|
||||
32996, 339, 32997, 330, 531, 530, 65537, 301, 532};
|
||||
const int core_tags[] = {256, 257, 258, 259, 262, 263, 266, 269, 274, 277,
|
||||
278, 280, 281, 282, 283, 284, 286, 287, 296, 297,
|
||||
301, 320, 321, 330, 333, 338, 339, 340, 341, 530,
|
||||
531, 532, 32995, 32996, 32997, 32998, 65537};
|
||||
|
||||
Py_ssize_t tags_size;
|
||||
PyObject *item;
|
||||
|
|
@ -821,7 +821,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
}
|
||||
}
|
||||
|
||||
if (type == TIFF_BYTE || type == TIFF_UNDEFINED) {
|
||||
if (type == TIFF_BYTE || type == TIFF_UNDEFINED ||
|
||||
key_int == TIFFTAG_INKNAMES) {
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state,
|
||||
(ttag_t)key_int,
|
||||
|
|
@ -973,7 +974,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value)
|
||||
);
|
||||
} else if (type == TIFF_LONG) {
|
||||
} else if (type == TIFF_LONG || type == TIFF_IFD) {
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, (UINT32)PyLong_AsLong(value)
|
||||
);
|
||||
|
|
@ -989,10 +990,6 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value)
|
||||
);
|
||||
} else if (type == TIFF_DOUBLE) {
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
|
||||
);
|
||||
} else if (type == TIFF_SBYTE) {
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value)
|
||||
|
|
@ -1001,7 +998,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, PyBytes_AsString(value)
|
||||
);
|
||||
} else if (type == TIFF_RATIONAL) {
|
||||
} else if (type == TIFF_DOUBLE || type == TIFF_SRATIONAL ||
|
||||
type == TIFF_RATIONAL) {
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -714,14 +714,7 @@ getfilter(Imaging im, int filterid) {
|
|||
case IMAGING_TYPE_UINT8:
|
||||
return nearest_filter8;
|
||||
case IMAGING_TYPE_SPECIAL:
|
||||
switch (im->pixelsize) {
|
||||
case 1:
|
||||
return nearest_filter8;
|
||||
case 2:
|
||||
return nearest_filter16;
|
||||
case 4:
|
||||
return nearest_filter32;
|
||||
}
|
||||
return nearest_filter16;
|
||||
}
|
||||
} else {
|
||||
return nearest_filter32;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ const ModeData MODES[] = {
|
|||
|
||||
[IMAGING_MODE_I_16] = {"I;16"}, [IMAGING_MODE_I_16L] = {"I;16L"},
|
||||
[IMAGING_MODE_I_16B] = {"I;16B"}, [IMAGING_MODE_I_16N] = {"I;16N"},
|
||||
[IMAGING_MODE_I_32L] = {"I;32L"}, [IMAGING_MODE_I_32B] = {"I;32B"},
|
||||
};
|
||||
|
||||
const ModeID
|
||||
|
|
@ -76,7 +75,6 @@ const RawModeData RAWMODES[] = {
|
|||
[IMAGING_RAWMODE_I_16L] = {"I;16L"},
|
||||
[IMAGING_RAWMODE_I_16B] = {"I;16B"},
|
||||
[IMAGING_RAWMODE_I_16N] = {"I;16N"},
|
||||
[IMAGING_RAWMODE_I_32L] = {"I;32L"},
|
||||
[IMAGING_RAWMODE_I_32B] = {"I;32B"},
|
||||
|
||||
[IMAGING_RAWMODE_1_8] = {"1;8"},
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ typedef enum {
|
|||
IMAGING_MODE_I_16L,
|
||||
IMAGING_MODE_I_16B,
|
||||
IMAGING_MODE_I_16N,
|
||||
IMAGING_MODE_I_32L,
|
||||
IMAGING_MODE_I_32B,
|
||||
} ModeID;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -64,8 +62,6 @@ typedef enum {
|
|||
IMAGING_RAWMODE_I_16L,
|
||||
IMAGING_RAWMODE_I_16B,
|
||||
IMAGING_RAWMODE_I_16N,
|
||||
IMAGING_RAWMODE_I_32L,
|
||||
IMAGING_RAWMODE_I_32B,
|
||||
|
||||
// Rawmodes
|
||||
IMAGING_RAWMODE_1_8,
|
||||
|
|
@ -106,6 +102,7 @@ typedef enum {
|
|||
IMAGING_RAWMODE_C_I,
|
||||
IMAGING_RAWMODE_Cb,
|
||||
IMAGING_RAWMODE_Cr,
|
||||
IMAGING_RAWMODE_I_32B,
|
||||
IMAGING_RAWMODE_F_16,
|
||||
IMAGING_RAWMODE_F_16B,
|
||||
IMAGING_RAWMODE_F_16BS,
|
||||
|
|
|
|||
|
|
@ -113,20 +113,20 @@ ARCHITECTURES = {
|
|||
}
|
||||
|
||||
V = {
|
||||
"BROTLI": "1.1.0",
|
||||
"BROTLI": "1.2.0",
|
||||
"FREETYPE": "2.14.1",
|
||||
"FRIBIDI": "1.0.16",
|
||||
"HARFBUZZ": "12.1.0",
|
||||
"HARFBUZZ": "12.2.0",
|
||||
"JPEGTURBO": "3.1.2",
|
||||
"LCMS2": "2.17",
|
||||
"LIBAVIF": "1.3.0",
|
||||
"LIBIMAGEQUANT": "4.4.0",
|
||||
"LIBPNG": "1.6.50",
|
||||
"LIBIMAGEQUANT": "4.4.1",
|
||||
"LIBPNG": "1.6.51",
|
||||
"LIBWEBP": "1.6.0",
|
||||
"OPENJPEG": "2.5.4",
|
||||
"TIFF": "4.7.1",
|
||||
"XZ": "5.8.1",
|
||||
"ZLIBNG": "2.2.5",
|
||||
"ZLIBNG": "2.3.1",
|
||||
}
|
||||
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
|
||||
|
||||
|
|
@ -167,12 +167,12 @@ DEPS: dict[str, dict[str, Any]] = {
|
|||
"license": "LICENSE.md",
|
||||
"patch": {
|
||||
r"CMakeLists.txt": {
|
||||
"set_target_properties(zlib PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlib)", # noqa: E501
|
||||
"set_target_properties(zlib-ng PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib-ng PROPERTIES OUTPUT_NAME zlib)", # noqa: E501
|
||||
},
|
||||
},
|
||||
"build": [
|
||||
*cmds_cmake(
|
||||
"zlib", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON"
|
||||
"zlib-ng", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON"
|
||||
),
|
||||
],
|
||||
"headers": [r"z*.h"],
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user