Merge branch 'main' into jxl-support2

This commit is contained in:
Andrew Murray 2025-12-20 12:12:27 +11:00 committed by GitHub
commit ffad6afb8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
79 changed files with 416 additions and 866 deletions

View File

@ -27,14 +27,13 @@ python3 -m pip install --upgrade wheel
python3 -m pip install coverage
python3 -m pip install defusedxml
python3 -m pip install ipython
python3 -m pip install numpy
python3 -m pip install olefile
python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
# optional test dependency, only install if there's a binary package.
# fails on beta 3.14 and PyPy
# optional test dependencies, only install if there's a binary package.
python3 -m pip install --only-binary=:all: numpy || true
python3 -m pip install --only-binary=:all: pyarrow || true
# PyQt6 doesn't support PyPy3

View File

@ -1,4 +1,4 @@
mypy==1.18.2
mypy==1.19.0
arro3-compute
arro3-core
IceSpringPySideStubs-PyQt6

View File

@ -32,7 +32,7 @@ jobs:
name: Docs
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false

View File

@ -20,7 +20,7 @@ jobs:
name: Lint
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false

View File

@ -26,9 +26,8 @@ python3 -m pip install -U pytest
python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma
python3 -m pip install numpy
# optional test dependency, only install if there's a binary package.
# fails on beta 3.14 and PyPy
# optional test dependencies, only install if there's a binary package.
python3 -m pip install --only-binary=:all: numpy || true
python3 -m pip install --only-binary=:all: pyarrow || true
# libavif

View File

@ -68,7 +68,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false

View File

@ -45,7 +45,7 @@ jobs:
steps:
- name: Checkout Pillow
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
persist-credentials: false

View File

@ -41,7 +41,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false

View File

@ -39,7 +39,7 @@ jobs:
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false

View File

@ -35,7 +35,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["pypy3.11", "3.11", "3.12", "3.13", "3.14"]
python-version: ["pypy3.11", "3.11", "3.12", "3.13", "3.14", "3.15"]
architecture: ["x64"]
include:
# Test the oldest Python on 32-bit
@ -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

View File

@ -42,6 +42,8 @@ jobs:
]
python-version: [
"pypy3.11",
"3.15t",
"3.15",
"3.14t",
"3.14",
"3.13t",
@ -54,6 +56,7 @@ jobs:
- { python-version: "3.12", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
- { python-version: "3.11", PYTHONOPTIMIZE: 2 }
# Free-threaded
- { python-version: "3.15t", disable-gil: true }
- { python-version: "3.14t", disable-gil: true }
- { python-version: "3.13t", disable-gil: true }
# Intel
@ -65,7 +68,7 @@ jobs:
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false

View File

@ -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,28 +89,26 @@ 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.2.0
LIBPNG_VERSION=1.6.50
JPEGTURBO_VERSION=3.1.2
LIBPNG_VERSION=1.6.53
JPEGTURBO_VERSION=3.1.3
OPENJPEG_VERSION=2.5.4
JPEGXL_VERSION=0.11.1
XZ_VERSION=5.8.1
XZ_VERSION=5.8.2
ZSTD_VERSION=1.5.7
TIFF_VERSION=4.7.1
LCMS2_VERSION=2.17
ZLIB_NG_VERSION=2.2.5
ZLIB_NG_VERSION=2.3.2
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 {
@ -150,18 +147,9 @@ 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
build_github zlib-ng/zlib-ng $ZLIB_NG_VERSION --installnamedir=$BUILD_PREFIX/lib --zlib-compat
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
}
@ -169,7 +157,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
}

View File

@ -107,7 +107,7 @@ jobs:
os: macos-15-intel
cibw_arch: x86_64_iphonesimulator
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
with:
persist-credentials: false
submodules: true
@ -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
@ -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

2
.github/zizmor.yml vendored
View File

@ -1,6 +1,8 @@
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
# https://docs.zizmor.sh/configuration/
rules:
obfuscation:
disable: true
unpinned-uses:
config:
policies:

View File

@ -1,17 +1,17 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.3
rev: v0.14.7
hooks:
- id: ruff-check
args: [--exit-non-zero-on-fix]
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 25.9.0
rev: 25.11.0
hooks:
- id: black
- repo: https://github.com/PyCQA/bandit
rev: 1.8.6
rev: 1.9.2
hooks:
- id: bandit
args: [--severity-level=high]
@ -21,10 +21,10 @@ 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
rev: v21.1.6
hooks:
- id: clang-format
types: [c]
@ -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.1
rev: 0.35.0
hooks:
- id: check-github-workflows
- id: check-readthedocs
- id: check-renovate
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.16.2
rev: v1.18.0
hooks:
- id: zizmor
- repo: https://github.com/sphinx-contrib/sphinx-lint
rev: v1.0.1
rev: v1.0.2
hooks:
- id: sphinx-lint
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.11.0
rev: v2.11.1
hooks:
- id: pyproject-fmt

View File

@ -15,7 +15,6 @@ include tox.ini
graft Tests
graft Tests/images
graft checks
graft patches
graft src
graft depends
graft winbuild

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 B

View File

@ -1,578 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>BMP Suite Image List</title>
<style>
.b { background:url(bkgd.png); }
.q { background-color:#fff0e0; }
.bad { background-color:#ffa0a0; }
</style>
</head>
<body>
<h1>BMP Suite Image List</h1>
<p><i>For <a href="http://entropymine.com/jason/bmpsuite/">BMP Suite</a>
version 2.3</i></p>
<p>This document describes the images in <i>BMP Suite</i>, and shows what
I allege to be the correct way to interpret them. PNG and JPEG images are
used for reference.
</p>
<p>It also shows how your web browser displays the BMP images,
but that&rsquo;s not its main purpose.
BMP is poor image format to use on web pages, so a web browser&rsquo;s
level of support for it is arguably not important.</p>
<table border=1 cellpadding=8>
<tr>
<th>File</th>
<th>Ver.</th>
<th>Correct display</th>
<th>In your browser</th>
<th>Notes</th>
</tr>
<tr>
<td>g/pal1.bmp</td>
<td>3</td>
<td class=b><img src="pal1.png"></td>
<td class=b><img src="../g/pal1.bmp"></td>
<td>1 bit/pixel paletted image, in which black is the first color in
the palette.</td>
</tr>
<tr>
<td>g/pal1wb.bmp</td>
<td>3</td>
<td class=b><img src="pal1.png"></td>
<td class=b><img src="../g/pal1wb.bmp"></td>
<td>1 bit/pixel paletted image, in which white is the first color in
the palette.</td>
</tr>
<tr>
<td>g/pal1bg.bmp</td>
<td>3</td>
<td class=b><img src="pal1bg.png"></td>
<td class=b><img src="../g/pal1bg.bmp"></td>
<td>1 bit/pixel paletted image, with colors other than black and white.</td>
</tr>
<tr>
<td class=q>q/pal1p1.bmp</td>
<td>3</td>
<td class=b><img src="pal1p1.png"></td>
<td class=b><img src="../q/pal1p1.bmp"></td>
<td>1 bit/pixel paletted image, with only one color in the palette.
The documentation says that 1-bpp images have a palette size of 2
(not &ldquo;up to 2&rdquo;), but it would be silly for a viewer not to
support a size of 1.</td>
</tr>
<tr>
<td class=q>q/pal2.bmp</td>
<td>3</td>
<td class=b><img src="pal2.png"></td>
<td class=b><img src="../q/pal2.bmp"></td>
<td>A paletted image with 2 bits/pixel. Usually only 1, 4,
and 8 are allowed, but 2 is legal on Windows CE.</td>
</tr>
<tr>
<td>g/pal4.bmp</td>
<td>3</td>
<td class=b><img src="pal4.png"></td>
<td class=b><img src="../g/pal4.bmp"></td>
<td>Paletted image with 12 palette colors, and 4 bits/pixel.</td>
</tr>
<tr>
<td>g/pal4rle.bmp</td>
<td>3</td>
<td class=b><img src="pal4.png"></td>
<td class=b><img src="../g/pal4rle.bmp"></td>
<td>4-bit image that uses RLE compression.</td>
</tr>
<tr>
<td class=q>q/pal4rletrns.bmp</td>
<td>3</td>
<td class=b><img src="pal4rletrns.png"><br>
or<br><img src="pal4rletrns-0.png"><br>
or<br><img src="pal4rletrns-b.png"></td>
<td class=b><img src="../q/pal4rletrns.bmp"></td>
<td>An RLE-compressed image that used &ldquo;delta&rdquo;
codes to skip over some pixels, leaving them undefined. Some viewers
make undefined pixels transparent, others make them black, and
others assign them palette color 0 (purple, in this case).</td>
</tr>
<tr>
<td>g/pal8.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8.bmp"></td>
<td>Our standard paletted image, with 252 palette colors, and 8
bits/pixel.</td>
</tr>
<tr>
<td>g/pal8-0.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8-0.bmp"></td>
<td>Every field that can be set to 0 is set to 0: pixels/meter=0;
colors used=0 (meaning the default 256); size-of-image=0.</td>
</tr>
<tr>
<td>g/pal8rle.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8rle.bmp"></td>
<td>8-bit image that uses RLE compression.</td>
</tr>
<tr>
<td class=q>q/pal8rletrns.bmp</td>
<td>3</td>
<td class=b><img src="pal8rletrns.png"><br>
or<br><img src="pal8rletrns-0.png"><br>
or<br><img src="pal8rletrns-b.png"></td>
<td class=b><img src="../q/pal8rletrns.bmp"></td>
<td>8-bit version of q/pal4rletrns.bmp.</td>
</tr>
<tr>
<td>g/pal8w126.bmp</td>
<td>3</td>
<td class=b><img src="pal8w126.png"></td>
<td class=b><img src="../g/pal8w126.bmp"></td>
<td rowspan=3>Images with different widths and heights.
In BMP format, rows are padded to a multiple of four bytes, so we
test all four possibilities.</td>
</tr>
<tr>
<td>g/pal8w125.bmp</td>
<td>3</td>
<td class=b><img src="pal8w125.png"></td>
<td class=b><img src="../g/pal8w125.bmp"></td>
</tr>
<tr>
<td>g/pal8w124.bmp</td>
<td>3</td>
<td class=b><img src="pal8w124.png"></td>
<td class=b><img src="../g/pal8w124.bmp"></td>
</tr>
<tr>
<td>g/pal8topdown.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8topdown.bmp"></td>
<td>BMP images are normally stored from the bottom up, but
there is a way to store them from the top down.</td>
</tr>
<tr>
<td class=q>q/pal8offs.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8offs.bmp"></td>
<td>A file with some unused bytes between the palette and the
image. This is probably valid, but I&rsquo;m not 100% sure.</td>
</tr>
<tr>
<td class=q>q/pal8oversizepal.bmp</td>
<td>3</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8oversizepal.bmp"></td>
<td>An 8-bit image with 300 palette colors. This may be invalid,
because the documentation could
be interpreted to imply that 8-bit images aren&rsquo;t allowed
to have more than 256 colors.</td>
</tr>
<tr>
<td>g/pal8nonsquare.bmp</td>
<td>3</td>
<td class=b>
<img src="pal8nonsquare-v.png"><br>
or<br>
<img src="pal8nonsquare-e.png">
</td>
<td class=b><img src="../g/pal8nonsquare.bmp"></td>
<td>An image with non-square pixels: the X pixels/meter is twice
the Y pixels/meter. Image <i>editors</i> can be expected to
leave the image &ldquo;squashed&rdquo;; image <i>viewers</i> should
consider stretching it to its correct proportions.</td>
</tr>
<tr>
<td>g/pal8os2.bmp</td>
<td>OS/2v1</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8os2.bmp"></td>
<td>An OS/2-style bitmap.</td>
</tr>
<tr>
<td class=q>q/pal8os2sp.bmp</td>
<td>OS/2v1</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8os2sp.bmp"></td>
<td>An OS/2v1 with a less-than-full-sized palette.
Probably not valid, but such files have been seen in the wild.</td>
</tr>
<tr>
<td class=q>q/pal8os2v2.bmp</td>
<td>OS/2v2</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8os2v2.bmp"></td>
<td>My attempt to make an OS/2v2 bitmap.</td>
</tr>
<tr>
<td class=q>q/pal8os2v2-16.bmp</td>
<td>OS/2v2</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../q/pal8os2v2-16.bmp"></td>
<td>An OS/2v2 bitmap whose header has only 16 bytes, instead of the full 64.</td>
</tr>
<tr>
<td>g/pal8v4.bmp</td>
<td>4</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8v4.bmp"></td>
<td>A v4 bitmap. I&rsquo;m not sure that the gamma and chromaticity values in
this file are sensible, because I can&rsquo;t find any detailed documentation
of them.</td>
</tr>
<tr>
<td>g/pal8v5.bmp</td>
<td>5</td>
<td class=b><img src="pal8.png"></td>
<td class=b><img src="../g/pal8v5.bmp"></td>
<td>A v5 bitmap. Version 5 has additional colorspace options over v4, so it
is easier to create, and ought to be more portable.</td>
</tr>
<tr>
<td>g/rgb16.bmp</td>
<td>3</td>
<td class=b><img src="rgb16.png"></td>
<td class=b><img src="../g/rgb16.bmp"></td>
<td>A 16-bit image with the default color format: 5 bits each for red,
green, and blue, and 1 unused bit.
The whitest colors should (I assume) be displayed as pure white:
<span style="background-color:rgb(255,255,255)">(255,255,255)</span>, not
<span style="background-color:rgb(248,248,248)">(248,248,248)</span>.</td>
</tr>
<tr>
<td>g/rgb16-565.bmp</td>
<td>3</td>
<td class=b><img src="rgb16-565.png"></td>
<td class=b><img src="../g/rgb16-565.bmp"></td>
<td>A 16-bit image with a BITFIELDS segment indicating 5 red, 6 green,
and 5 blue bits. This is a standard 16-bit format, even supported by
old versions of Windows that don&rsquo;t support any other non-default 16-bit
formats.
The whitest colors should be displayed as pure white:
<span style="background-color:rgb(255,255,255)">(255,255,255)</span>, not
<span style="background-color:rgb(248,252,248)">(248,252,248)</span>.</td>
</tr>
<tr>
<td>g/rgb16-565pal.bmp</td>
<td>3</td>
<td class=b><img src="rgb16-565.png"></td>
<td class=b><img src="../g/rgb16-565pal.bmp"></td>
<td>A 16-bit image with both a BITFIELDS segment and a palette.</td>
</tr>
<tr>
<td class=q>q/rgb16-231.bmp</td>
<td>3</td>
<td class=b><img src="rgb16-231.png"></td>
<td class=b><img src="../q/rgb16-231.bmp"></td>
<td>An unusual and silly 16-bit image, with 2 red bits, 3 green bits, and 1
blue bit. Most viewers do support this image, but the colors may be darkened
with a yellow-green shadow. That&rsquo;s because they&rsquo;re doing simple
bit-shifting (possibly including one round of bit replication), instead of
proper scaling.</td>
</tr>
<tr>
<td class=q>q/rgba16-4444.bmp</td>
<td>5</td>
<td class=b><img src="rgba16-4444.png"></td>
<td class=b><img src="../q/rgba16-4444.bmp"></td>
<td>A 16-bit image with an alpha channel. There are 4 bits for each color
channel, and 4 bits for the alpha channel.
It&rsquo;s not clear if this is valid, but I can&rsquo;t find anything that
suggests it isn&rsquo;t.
</td>
</tr>
<tr>
<td>g/rgb24.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb24.bmp"></td>
<td>A perfectly ordinary 24-bit (truecolor) image.</td>
</tr>
<tr>
<td>g/rgb24pal.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb24pal.bmp"></td>
<td>A 24-bit image, with a palette containing 256 colors. There is little if
any reason for a truecolor image to contain a palette, but it is legal.</td>
</tr>
<tr>
<td class=q>q/rgb24largepal.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24largepal.bmp"></td>
<td>A 24-bit image, with a palette containing 300 colors.
The fact that the palette has more than 256 colors may cause some viewers
to complain, but the documentation does not mention a size limit.</td>
</tr>
<tr>
<td class=q>q/rgb24prof.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24prof.bmp"></td>
<td>My attempt to make a BMP file with an embedded color profile.</td>
</tr>
<tr>
<td class=q>q/rgb24lprof.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24lprof.bmp"></td>
<td>My attempt to make a BMP file with a linked color profile.</td>
</tr>
<tr>
<td class=q>q/rgb24jpeg.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.jpg"></td>
<td class=b><img src="../q/rgb24jpeg.bmp"></td>
<td rowspan=2>My attempt to make BMP files with embedded JPEG and PNG images.
These are not likely to be supported by much of anything (they&rsquo;re
intended for printers).</td>
</tr>
<tr>
<td class=q>q/rgb24png.bmp</td>
<td>5</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb24png.bmp"></td>
</tr>
<tr>
<td>g/rgb32.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb32.bmp"></td>
<td>A 32-bit image using the default color format for 32-bit images (no
BITFIELDS segment). There are 8 bits per color channel, and 8 unused
bits. The unused bits are set to 0.</td>
</tr>
<tr>
<td>g/rgb32bf.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../g/rgb32bf.bmp"></td>
<td>A 32-bit image with a BITFIELDS segment. As usual, there are 8 bits per
color channel, and 8 unused bits. But the color channels are in an unusual
order, so the viewer must read the BITFIELDS, and not just guess.</td>
</tr>
<tr>
<td class=q>q/rgb32fakealpha.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"><br>
or<br>
<img class=b src="fakealpha.png">
</td>
<td class=b><img src="../q/rgb32fakealpha.bmp"></td>
<td>Same as g/rgb32.bmp, except that the unused bits are set to something
other than 0.
If the image becomes transparent toward the bottom, it probably means
the viewer uses heuristics to guess whether the undefined
data represents transparency.</td>
</tr>
<tr>
<td class=q>q/rgb32-111110.bmp</td>
<td>3</td>
<td class=b><img src="rgb24.png"></td>
<td class=b><img src="../q/rgb32-111110.bmp"></td>
<td>A 32 bits/pixel image, with all 32 bits used: 11 each for red and
green, and 10 for blue. As far as I know, this is perfectly valid, but it
is unusual.</td>
</tr>
<tr>
<td class=q>q/rgba32.bmp</td>
<td>5</td>
<td class=b><img src="rgba32.png"></td>
<td class=b><img src="../q/rgba32.bmp"></td>
<td>A BMP with an alpha channel. Transparency is barely documented,
so it&rsquo;s <i>possible</i> that this file is not correctly formed.
The color channels are in an unusual order, to prevent viewers from
passing this test by making a lucky guess.</td>
</tr>
<tr>
<td class=q>q/rgba32abf.bmp</td>
<td>3</td>
<td class=b><img src="rgba32.png"></td>
<td class=b><img src="../q/rgba32abf.bmp"></td>
<td>An image of type BI_ALHPABITFIELDS. Supposedly, this was used on
Windows CE. I don&rsquo;t know whether it is constructed correctly.</td>
</tr>
<tr>
<td class=bad>b/badbitcount.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badbitcount.bmp"></td>
<td>Header indicates an absurdly large number of bits/pixel.</td>
</tr>
<tr>
<td class=bad>b/badbitssize.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badbitssize.bmp"></td>
<td>Header incorrectly indicates that the bitmap is several GB in size.</td>
</tr>
<tr>
<td class=bad>b/baddens1.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/baddens1.bmp"></td>
<td rowspan=2>Density (pixels per meter) suggests the image is <i>much</i>
larger in one dimension than the other.</td>
</tr>
<tr>
<td class=bad>b/baddens2.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/baddens2.bmp"></td>
</tr>
<tr>
<td class=bad>b/badfilesize.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badfilesize.bmp"></td>
<td>Header incorrectly indicates that the file is several GB in size.</td>
</tr>
<tr>
<td class=bad>b/badheadersize.bmp</td>
<td>?</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badheadersize.bmp"></td>
<td>Header size is 66 bytes, which is not a valid size for any known BMP
version.</td>
</tr>
<tr>
<td class=bad>b/badpalettesize.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badpalettesize.bmp"></td>
<td>Header incorrectly indicates that the palette contains an absurdly large
number of colors.</td>
</tr>
<tr>
<td class=bad>b/badplanes.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badplanes.bmp"></td>
<td>The &ldquo;planes&rdquo; setting, which is required to be 1, is not 1.</td>
</tr>
<tr>
<td class=bad>b/badrle.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badrle.bmp"></td>
<td>An invalid RLE-compressed image that tries to cause buffer overruns.</td>
</tr>
<tr>
<td class=bad>b/badwidth.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/badwidth.bmp"></td>
<td>The image claims to be a negative number of pixels in width.</td>
</tr>
<tr>
<td class=bad>b/pal8badindex.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/pal8badindex.bmp"></td>
<td>Many of the palette indices used in the image are not present in the
palette.</td>
</tr>
<tr>
<td class=bad>b/reallybig.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/reallybig.bmp"></td>
<td>An image with a very large reported width and height.</td>
</tr>
<tr>
<td class=bad>b/rletopdown.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/rletopdown.bmp"></td>
<td>An RLE-compressed image that tries to use top-down orientation,
which isn&rsquo;t allowed.</td>
</tr>
<tr>
<td class=bad>b/shortfile.bmp</td>
<td>3</td>
<td class=b>N/A</td>
<td class=b><img src="../b/shortfile.bmp"></td>
<td>A file that has been truncated in the middle of the bitmap.</td>
</tr>
</table>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -72,7 +72,7 @@ def test_good() -> None:
"pal8-0.bmp": "pal8.png",
"pal8rle.bmp": "pal8.png",
"pal8topdown.bmp": "pal8.png",
"pal8nonsquare.bmp": "pal8nonsquare-v.png",
"pal8nonsquare.bmp": "pal8nonsquare-e.png",
"pal8os2.bmp": "pal8.png",
"pal8os2sp.bmp": "pal8.png",
"pal8os2v2.bmp": "pal8.png",
@ -103,7 +103,7 @@ def test_good() -> None:
# with paletized image, since the palette might
# be differently ordered for an equivalent image.
im = im.convert("RGBA")
compare = im.convert("RGBA")
compare = compare.convert("RGBA")
assert_image_similar(im, compare, 5)
except Exception as msg:

View File

@ -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)

View File

@ -59,7 +59,7 @@ def test_handler(tmp_path: Path) -> None:
def open(self, im: Image.Image) -> None:
self.opened = True
def load(self, im: Image.Image) -> Image.Image:
def load(self, im: ImageFile.ImageFile) -> Image.Image:
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))

View File

@ -61,7 +61,7 @@ def test_handler(tmp_path: Path) -> None:
def open(self, im: Image.Image) -> None:
self.opened = True
def load(self, im: Image.Image) -> Image.Image:
def load(self, im: ImageFile.ImageFile) -> Image.Image:
self.loaded = True
im.fp.close()
return Image.new("RGB", (1, 1))

View File

@ -6,7 +6,7 @@ import pytest
from PIL import Image, IptcImagePlugin, TiffImagePlugin, TiffTags
from .helper import assert_image_equal, hopper
from .helper import assert_image_equal
TEST_FILE = "Tests/images/iptc.jpg"
@ -85,7 +85,7 @@ def test_getiptcinfo() -> None:
def test_getiptcinfo_jpg_none() -> None:
# Arrange
with hopper() as im:
with Image.open("Tests/images/hopper.jpg") as im:
# Act
iptc = IptcImagePlugin.getiptcinfo(im)
@ -143,6 +143,7 @@ def test_getiptcinfo_tiff() -> None:
# Test with LONG tag type
with Image.open("Tests/images/hopper.Lab.tif") as im:
assert isinstance(im, TiffImagePlugin.TiffImageFile)
im.tag_v2.tagtype[TiffImagePlugin.IPTC_NAA_CHUNK] = TiffTags.LONG
iptc = IptcImagePlugin.getiptcinfo(im)

View File

@ -11,7 +11,15 @@ from typing import Any, NamedTuple
import pytest
from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features
from PIL import (
Image,
ImageFile,
ImageFilter,
ImageOps,
TiffImagePlugin,
TiffTags,
features,
)
from PIL.TiffImagePlugin import OSUBFILETYPE, SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
from .helper import (
@ -27,7 +35,7 @@ from .helper import (
@skip_unless_feature("libtiff")
class LibTiffTestCase:
def _assert_noerr(self, tmp_path: Path, im: TiffImagePlugin.TiffImageFile) -> None:
def _assert_noerr(self, tmp_path: Path, im: ImageFile.ImageFile) -> None:
"""Helper tests that assert basic sanity about the g4 tiff reading"""
# 1 bit
assert im.mode == "1"
@ -355,6 +363,36 @@ 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 isinstance(reloaded, TiffImagePlugin.TiffImageFile)
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:
@ -1117,9 +1155,9 @@ class TestFileLibTiff(LibTiffTestCase):
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
for i in range(2, 9):
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
im = ImageOps.exif_transpose(im)
im_transposed = ImageOps.exif_transpose(im)
assert_image_similar(base_im, im, 0.7)
assert_image_similar(base_im, im_transposed, 0.7)
@pytest.mark.parametrize(
"test_file",

View File

@ -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:

View File

@ -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

View File

@ -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:
@ -778,7 +787,9 @@ class TestFilePng:
im.save(test_file, exif=im.getexif())
with Image.open(test_file) as reloaded:
assert isinstance(reloaded, PngImagePlugin.PngImageFile)
exif = reloaded._getexif()
assert exif is not None
assert exif[305] == "Adobe Photoshop CS Macintosh"
def test_exif_argument(self, tmp_path: Path) -> None:

View File

@ -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(

View File

@ -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:

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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:

View File

@ -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()

View File

@ -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")

View File

@ -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:

View File

@ -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)

View File

@ -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")

View File

@ -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",

View File

@ -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")

View File

@ -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,42 +20,69 @@ 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()
assert text.get_length() == 288
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(

View File

@ -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")

View File

@ -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)

View File

@ -2,7 +2,7 @@
# install libimagequant
archive_name=libimagequant
archive_version=4.4.0
archive_version=4.4.1
archive=$archive_name-$archive_version

View File

@ -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.
@ -120,7 +120,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 \

View File

@ -44,9 +44,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
:return: An image
.. py:function:: grabclipboard()

View File

@ -0,0 +1,59 @@
12.1.0
------
Security
========
TODO
^^^^
TODO
:cve:`YYYY-XXXXX`: TODO
^^^^^^^^^^^^^^^^^^^^^^^
TODO
Backwards incompatible changes
==============================
TODO
^^^^
TODO
Deprecations
============
TODO
^^^^
TODO
API changes
===========
TODO
^^^^
TODO
API additions
=============
Specify window in ImageGrab on macOS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When using :py:meth:`~PIL.ImageGrab.grab`, a specific window can now be selected on
macOS in addition to Windows. On macOS, this is a CGWindowID::
from PIL import ImageGrab
ImageGrab.grab(window=cgwindowid)
Other changes
=============
TODO
^^^^
TODO

View File

@ -14,6 +14,8 @@ expected to be backported to earlier versions.
.. toctree::
:maxdepth: 2
versioning
12.1.0
12.0.0
11.3.0
11.2.1
@ -79,4 +81,3 @@ expected to be backported to earlier versions.
2.5.2
2.3.2
2.3.1
versioning

View File

@ -17,8 +17,8 @@ prior three months.
A quarterly release bumps the MAJOR version when incompatible API changes are
made, such as removing deprecated APIs or dropping an EOL Python version. In practice,
these occur every 12-18 months, guided by
`Python's EOL schedule <https://devguide.python.org/#status-of-python-branches>`_, and
these occur every October, guided by
`Python's EOL schedule <https://devguide.python.org/versions/>`__, and
any APIs that have been deprecated for at least a year are removed at the same time.
PATCH versions ("`Point Release <https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#user-content-point-release>`_"

View File

@ -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.

View File

@ -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

View File

@ -40,12 +40,14 @@ def testimage() -> None:
>>> with Image.open("Tests/images/hopper.gif") as im:
... _info(im)
('GIF', 'P', (128, 128))
>>> _info(Image.open("Tests/images/hopper.ppm"))
>>> with Image.open("Tests/images/hopper.ppm") as im:
... _info(im)
('PPM', 'RGB', (128, 128))
>>> try:
... _info(Image.open("Tests/images/hopper.jpg"))
... with Image.open("Tests/images/hopper.jpg") as im:
... _info(im)
... except OSError as v:
... print(v)
... print(v)
('JPEG', 'RGB', (128, 128))
PIL doesn't actually load the image data until it's needed,

View File

@ -116,8 +116,8 @@ class GifImageFile(ImageFile.ImageFile):
# check if palette contains colour indices
p = self.fp.read(3 << bits)
if self._is_palette_needed(p):
p = ImagePalette.raw("RGB", p)
self.global_palette = self.palette = p
palette = ImagePalette.raw("RGB", p)
self.global_palette = self.palette = palette
self._fp = self.fp # FIXME: hack
self.__rewind = self.fp.tell()
@ -256,7 +256,7 @@ class GifImageFile(ImageFile.ImageFile):
info["comment"] += b"\n" + comment
else:
info["comment"] = comment
s = None
s = b""
continue
elif s[0] == 255 and frame == 0 and block is not None:
#
@ -299,7 +299,7 @@ class GifImageFile(ImageFile.ImageFile):
bits = self.fp.read(1)[0]
self.__offset = self.fp.tell()
break
s = None
s = b""
if interlace is None:
msg = "image not found in GIF frame"

View File

@ -1519,6 +1519,9 @@ class Image:
"".join(self.info["Raw profile type exif"].split("\n")[3:])
)
elif hasattr(self, "tag_v2"):
from . import TiffImagePlugin
assert isinstance(self, TiffImagePlugin.TiffImageFile)
self._exif.bigtiff = self.tag_v2._bigtiff
self._exif.endian = self.tag_v2._endian
self._exif.load_from_fp(self.fp, self.tag_v2._offset)

View File

@ -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()

View File

@ -43,7 +43,9 @@ 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])
@ -51,9 +53,35 @@ def grab(
im.load()
os.unlink(filepath)
if bbox:
im_resized = im.resize((right - left, bottom - top))
im.close()
return im_resized
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
im_cropped = im.resize(
(right - left, bottom - top),
box=tuple(coord * 2 for coord in bbox),
)
else:
im_cropped = im.crop(bbox)
im.close()
return im_cropped
else:
im_resized = im.resize((right - left, bottom - top))
im.close()
return im_resized
return im
elif sys.platform == "win32":
if window is not None:

View File

@ -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)

View File

@ -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)),

View File

@ -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

View File

@ -66,10 +66,10 @@ class XVThumbImageFile(ImageFile.ImageFile):
break
# parse header line (already read)
s = s.strip().split()
w, h = s.strip().split(maxsplit=2)[:2]
self._mode = "P"
self._size = int(s[0]), int(s[1])
self._size = int(w), int(h)
self.palette = ImagePalette.raw("RGB", PALETTE)

View File

@ -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)
);

View File

@ -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"},

View File

@ -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,

View File

@ -3,7 +3,7 @@ requires =
tox>=4.2
env_list =
lint
py{py3, 314, 313, 312, 311, 310}
py{py3, 315, 314, 313, 312, 311, 310}
[testenv]
deps =

View File

@ -113,20 +113,20 @@ ARCHITECTURES = {
}
V = {
"BROTLI": "1.1.0",
"BROTLI": "1.2.0",
"FREETYPE": "2.14.1",
"FRIBIDI": "1.0.16",
"HARFBUZZ": "12.2.0",
"JPEGTURBO": "3.1.2",
"JPEGTURBO": "3.1.3",
"LCMS2": "2.17",
"LIBAVIF": "1.3.0",
"LIBIMAGEQUANT": "4.4.0",
"LIBPNG": "1.6.50",
"LIBIMAGEQUANT": "4.4.1",
"LIBPNG": "1.6.53",
"LIBWEBP": "1.6.0",
"OPENJPEG": "2.5.4",
"TIFF": "4.7.1",
"XZ": "5.8.1",
"ZLIBNG": "2.2.5",
"XZ": "5.8.2",
"ZLIBNG": "2.3.2",
}
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"],
@ -183,11 +183,7 @@ DEPS: dict[str, dict[str, Any]] = {
"filename": f"xz-{V['XZ']}.tar.gz",
"license": "COPYING",
"build": [
*cmds_cmake(
"liblzma",
"-DBUILD_SHARED_LIBS:BOOL=OFF"
+ (" -DXZ_CLMUL_CRC:BOOL=OFF" if struct.calcsize("P") == 4 else ""),
),
*cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
cmd_mkdir(r"{inc_dir}\lzma"),
cmd_copy(r"src\liblzma\api\lzma\*.h", r"{inc_dir}\lzma"),
],