Merge branch 'main' into jxl-support2
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
mypy==1.18.2
|
||||
mypy==1.19.0
|
||||
arro3-compute
|
||||
arro3-core
|
||||
IceSpringPySideStubs-PyQt6
|
||||
|
|
|
|||
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
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
name: Lint
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
|
|
|||
5
.github/workflows/macos-install.sh
vendored
|
|
@ -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
|
||||
|
|
|
|||
2
.github/workflows/test-docker.yml
vendored
|
|
@ -68,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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
5
.github/workflows/test.yml
vendored
|
|
@ -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
|
||||
|
||||
|
|
|
|||
28
.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,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
|
||||
}
|
||||
|
|
|
|||
8
.github/workflows/wheels.yml
vendored
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ include tox.ini
|
|||
graft Tests
|
||||
graft Tests/images
|
||||
graft checks
|
||||
graft patches
|
||||
graft src
|
||||
graft depends
|
||||
graft winbuild
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 126 B |
|
|
@ -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’s not its main purpose.
|
||||
BMP is poor image format to use on web pages, so a web browser’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 “up to 2”), 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 “delta”
|
||||
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’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’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 “squashed”; 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’m not sure that the gamma and chromaticity values in
|
||||
this file are sensible, because I can’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’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’s because they’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’s not clear if this is valid, but I can’t find anything that
|
||||
suggests it isn’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’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’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’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 “planes” 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’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>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 124 B |
|
Before Width: | Height: | Size: 961 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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,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(
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
59
docs/releasenotes/12.1.0.rst
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>`_"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
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)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
2
tox.ini
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
],
|
||||
|
|
|
|||