Merge branch 'main' into convert_mode

This commit is contained in:
Andrew Murray 2022-12-23 10:43:25 +11:00 committed by GitHub
commit a22b55a858
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
137 changed files with 2251 additions and 1445 deletions

View File

@ -10,7 +10,7 @@ environment:
TEST_OPTIONS: TEST_OPTIONS:
DEPLOY: YES DEPLOY: YES
matrix: matrix:
- PYTHON: C:/Python310 - PYTHON: C:/Python311
ARCHITECTURE: x86 ARCHITECTURE: x86
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
- PYTHON: C:/Python37-x64 - PYTHON: C:/Python37-x64

View File

@ -13,6 +13,10 @@ indent_style = space
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.rst]
# Four-space indentation
indent_size = 4
[*.yml] [*.yml]
# Two-space indentation # Two-space indentation
indent_size = 2 indent_size = 2

View File

@ -30,7 +30,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v4 uses: actions/setup-python@v4
with: with:
python-version: "3.10" python-version: "3.x"
cache: pip cache: pip
cache-dependency-path: "setup.py" cache-dependency-path: "setup.py"

View File

@ -2,7 +2,7 @@
set -e set -e
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype openblas libraqm brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
PYTHONOPTIMIZE=0 python3 -m pip install cffi PYTHONOPTIMIZE=0 python3 -m pip install cffi
python3 -m pip install coverage python3 -m pip install coverage
@ -13,7 +13,6 @@ python3 -m pip install -U pytest-cov
python3 -m pip install -U pytest-timeout python3 -m pip install -U pytest-timeout
python3 -m pip install pyroma python3 -m pip install pyroma
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
python3 -m pip install numpy python3 -m pip install numpy
# extra test images # extra test images

View File

@ -20,7 +20,7 @@ jobs:
steps: steps:
- name: "Check issues" - name: "Check issues"
uses: actions/stale@v6 uses: actions/stale@v7
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
only-labels: "Awaiting OP Action" only-labels: "Awaiting OP Action"

View File

@ -15,7 +15,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-minor-version: [7, 8, 9] python-minor-version: [8, 9]
timeout-minutes: 40 timeout-minutes: 40
@ -30,7 +30,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Install Cygwin - name: Install Cygwin
uses: cygwin/cygwin-install-action@v2 uses: cygwin/cygwin-install-action@v3
with: with:
platform: x86_64 platform: x86_64
packages: > packages: >
@ -48,7 +48,7 @@ jobs:
qt5-devel-tools subversion xorg-server-extra zlib-devel qt5-devel-tools subversion xorg-server-extra zlib-devel
- name: Add Lapack to PATH - name: Add Lapack to PATH
uses: egor-tensin/cleanup-path@v2 uses: egor-tensin/cleanup-path@v3
with: with:
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack' dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
@ -76,7 +76,7 @@ jobs:
- name: Build - name: Build
shell: bash.exe -eo pipefail -o igncr "{0}" shell: bash.exe -eo pipefail -o igncr "{0}"
run: | run: |
.ci/build.sh SETUPTOOLS_USE_DISTUTILS=stdlib .ci/build.sh
- name: Test - name: Test
run: | run: |

View File

@ -30,8 +30,8 @@ jobs:
centos-stream-9-amd64, centos-stream-9-amd64,
debian-10-buster-x86, debian-10-buster-x86,
debian-11-bullseye-x86, debian-11-bullseye-x86,
fedora-35-amd64,
fedora-36-amd64, fedora-36-amd64,
fedora-37-amd64,
gentoo, gentoo,
ubuntu-18.04-bionic-amd64, ubuntu-18.04-bionic-amd64,
ubuntu-20.04-focal-amd64, ubuntu-20.04-focal-amd64,

View File

@ -15,13 +15,13 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11-dev"] python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
architecture: ["x86", "x64"] architecture: ["x86", "x64"]
include: include:
# PyPy 7.3.4+ only ships 64-bit binaries for Windows # PyPy 7.3.4+ only ships 64-bit binaries for Windows
- python-version: "pypy-3.7" - python-version: "pypy3.8"
architecture: "x64" architecture: "x64"
- python-version: "pypy-3.8" - python-version: "pypy3.9"
architecture: "x64" architecture: "x64"
timeout-minutes: 30 timeout-minutes: 30
@ -65,7 +65,9 @@ jobs:
xcopy /S /Y winbuild\depends\test_images\* Tests\images\ xcopy /S /Y winbuild\depends\test_images\* Tests\images\
# make cache key depend on VS version # make cache key depend on VS version
& "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" | find """catalog_buildVersion""" | ForEach-Object { $a = $_.split(" ")[1]; echo "::set-output name=vs::$a" } & "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" `
| find """catalog_buildVersion""" `
| ForEach-Object { $a = $_.split(" ")[1]; echo "vs=$a" >> $env:GITHUB_OUTPUT }
shell: pwsh shell: pwsh
- name: Cache build - name: Cache build
@ -90,19 +92,28 @@ jobs:
if: steps.build-cache.outputs.cache-hit != 'true' if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_zlib.cmd" run: "& winbuild\\build\\build_dep_zlib.cmd"
- name: Build dependencies / LibTiff - name: Build dependencies / xz
if: steps.build-cache.outputs.cache-hit != 'true' if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libtiff.cmd" run: "& winbuild\\build\\build_dep_xz.cmd"
- name: Build dependencies / WebP - name: Build dependencies / WebP
if: steps.build-cache.outputs.cache-hit != 'true' if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libwebp.cmd" run: "& winbuild\\build\\build_dep_libwebp.cmd"
- name: Build dependencies / LibTiff
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libtiff.cmd"
# for FreeType CBDT/SBIX font support # for FreeType CBDT/SBIX font support
- name: Build dependencies / libpng - name: Build dependencies / libpng
if: steps.build-cache.outputs.cache-hit != 'true' if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libpng.cmd" run: "& winbuild\\build\\build_dep_libpng.cmd"
# for FreeType WOFF2 font support
- name: Build dependencies / brotli
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_brotli.cmd"
- name: Build dependencies / FreeType - name: Build dependencies / FreeType
if: steps.build-cache.outputs.cache-hit != 'true' if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_freetype.cmd" run: "& winbuild\\build\\build_dep_freetype.cmd"
@ -130,7 +141,7 @@ jobs:
if: steps.build-cache.outputs.cache-hit != 'true' if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_fribidi.cmd" run: "& winbuild\\build\\build_dep_fribidi.cmd"
# trim ~150MB x 9 # trim ~150MB for each job
- name: Optimize build cache - name: Optimize build cache
if: steps.build-cache.outputs.cache-hit != 'true' if: steps.build-cache.outputs.cache-hit != 'true'
run: rmdir /S /Q winbuild\build\src run: rmdir /S /Q winbuild\build\src
@ -185,16 +196,42 @@ jobs:
id: wheel id: wheel
if: "github.event_name != 'pull_request'" if: "github.event_name != 'pull_request'"
run: | run: |
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ::set-output name=dist::dist-%%a mkdir fribidi\${{ matrix.architecture }}
copy winbuild\build\bin\fribidi* fribidi\${{ matrix.architecture }}
setlocal EnableDelayedExpansion
for %%f in (winbuild\build\license\*) do (
set x=%%~nf
rem Skip FriBiDi license, it is not included in the wheel.
set fribidi=!x:~0,7!
if NOT !fribidi!==fribidi (
rem Skip imagequant license, it is not included in the wheel.
set libimagequant=!x:~0,13!
if NOT !libimagequant!==libimagequant (
echo. >> LICENSE
echo ===== %%~nf ===== >> LICENSE
echo. >> LICENSE
type %%f >> LICENSE
)
)
)
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo dist=dist-%%a >> %GITHUB_OUTPUT%
winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel
shell: cmd shell: cmd
- uses: actions/upload-artifact@v3 - name: Upload wheel
uses: actions/upload-artifact@v3
if: "github.event_name != 'pull_request'" if: "github.event_name != 'pull_request'"
with: with:
name: ${{ steps.wheel.outputs.dist }} name: ${{ steps.wheel.outputs.dist }}
path: dist\*.whl path: dist\*.whl
- name: Upload fribidi.dll
if: "github.event_name != 'pull_request' && matrix.python-version == 3.11"
uses: actions/upload-artifact@v3
with:
name: fribidi
path: fribidi\*
success: success:
permissions: permissions:
contents: none contents: none

View File

@ -20,9 +20,9 @@ jobs:
"ubuntu-latest", "ubuntu-latest",
] ]
python-version: [ python-version: [
"pypy-3.8", "pypy3.9",
"pypy-3.7", "pypy3.8",
"3.11-dev", "3.11",
"3.10", "3.10",
"3.9", "3.9",
"3.8", "3.8",
@ -96,7 +96,7 @@ jobs:
path: Tests/errors path: Tests/errors
- name: Docs - name: Docs
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.10 if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.11
run: | run: |
make doccheck make doccheck

View File

@ -1,36 +0,0 @@
name: Tidelift Align
on:
schedule:
- cron: "30 2 * * *" # daily at 02:30 UTC
push:
paths:
- "Pipfile*"
- ".github/workflows/tidelift.yml"
pull_request:
paths:
- "Pipfile*"
- ".github/workflows/tidelift.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
if: github.repository_owner == 'python-pillow'
name: Run Tidelift to ensure approved open source packages are in use
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Scan
uses: tidelift/alignment-action@main
env:
TIDELIFT_API_KEY: ${{ secrets.TIDELIFT_API_KEY }}
TIDELIFT_ORGANIZATION: team/aclark4life
TIDELIFT_PROJECT: pillow

View File

@ -1,18 +1,25 @@
repos: repos:
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 22.8.0 rev: 22.12.0
hooks: hooks:
- id: black - id: black
args: ["--target-version", "py37"] args: [--target-version=py37]
# Only .py files, until https://github.com/psf/black/issues/402 resolved # Only .py files, until https://github.com/psf/black/issues/402 resolved
files: \.py$ files: \.py$
types: [] types: []
- repo: https://github.com/PyCQA/isort - repo: https://github.com/PyCQA/isort
rev: 5.10.1 rev: 5.11.1
hooks: hooks:
- id: isort - id: isort
- repo: https://github.com/PyCQA/bandit
rev: 1.7.4
hooks:
- id: bandit
args: [--severity-level=high]
files: ^src/
- repo: https://github.com/asottile/yesqa - repo: https://github.com/asottile/yesqa
rev: v1.4.0 rev: v1.4.0
hooks: hooks:
@ -25,7 +32,7 @@ repos:
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$) exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 5.0.4 rev: 6.0.0
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: [flake8-2020, flake8-implicit-str-concat] additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
@ -37,16 +44,21 @@ repos:
- id: rst-backticks - id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0 rev: v4.4.0
hooks: hooks:
- id: check-merge-conflict - id: check-merge-conflict
- id: check-json - id: check-json
- id: check-yaml - id: check-yaml
- repo: https://github.com/sphinx-contrib/sphinx-lint - repo: https://github.com/sphinx-contrib/sphinx-lint
rev: v0.6.1 rev: v0.6.7
hooks: hooks:
- id: sphinx-lint - id: sphinx-lint
- repo: https://github.com/tox-dev/tox-ini-fmt
rev: 0.5.2
hooks:
- id: tox-ini-fmt
ci: ci:
autoupdate_schedule: monthly autoupdate_schedule: monthly

View File

@ -2,9 +2,144 @@
Changelog (Pillow) Changelog (Pillow)
================== ==================
9.3.0 (unreleased) 9.4.0 (unreleased)
------------------ ------------------
- Ignore non-opaque WebP background when saving as GIF #6792
[radarhere]
- Only set tile in ImageFile __setstate__ #6793
[radarhere]
- When reading BLP, do not trust JPEG decoder to determine image is CMYK #6767
[radarhere]
- Added IFD enum to ExifTags #6748
[radarhere]
- Fixed bug combining GIF frame durations #6779
[radarhere]
- Support saving JPEG comments #6774
[smason, radarhere]
- Added getxmp() to WebPImagePlugin #6758
[radarhere]
- Added "exact" option when saving WebP #6747
[ashafaei, radarhere]
- Use fractional coordinates when drawing text #6722
[radarhere]
- Fixed writing int as BYTE tag #6740
[radarhere]
- Added MP Format Version when saving MPO #6735
[radarhere]
- Added Interop to ExifTags #6724
[radarhere]
- CVE-2007-4559 patch when building on Windows #6704
[TrellixVulnTeam, nulano, radarhere]
- Fix compiler warning: accessing 64 bytes in a region of size 48 #6714
[wiredfool]
- Use verbose flag for pip install #6713
[wiredfool, radarhere]
9.3.0 (2022-10-29)
------------------
- Limit SAMPLESPERPIXEL to avoid runtime DOS #6700
[wiredfool]
- Initialize libtiff buffer when saving #6699
[radarhere]
- Inline fname2char to fix memory leak #6329
[nulano]
- Fix memory leaks related to text features #6330
[nulano]
- Use double quotes for version check on old CPython on Windows #6695
[hugovk]
- Remove backup implementation of Round for Windows platforms #6693
[cgohlke]
- Fixed set_variation_by_name offset #6445
[radarhere]
- Fix malloc in _imagingft.c:font_setvaraxes #6690
[cgohlke]
- Release Python GIL when converting images using matrix operations #6418
[hmaarrfk]
- Added ExifTags enums #6630
[radarhere]
- Do not modify previous frame when calculating delta in PNG #6683
[radarhere]
- Added support for reading BMP images with RLE4 compression #6674
[npjg, radarhere]
- Decode JPEG compressed BLP1 data in original mode #6678
[radarhere]
- Added GPS TIFF tag info #6661
[radarhere]
- Added conversion between RGB/RGBA/RGBX and LAB #6647
[radarhere]
- Do not attempt normalization if mode is already normal #6644
[radarhere]
- Fixed seeking to an L frame in a GIF #6576
[radarhere]
- Consider all frames when selecting mode for PNG save_all #6610
[radarhere]
- Don't reassign crc on ChunkStream close #6627
[wiredfool, radarhere]
- Raise a warning if NumPy failed to raise an error during conversion #6594
[radarhere]
- Show all frames in ImageShow #6611
[radarhere]
- Allow FLI palette chunk to not be first #6626
[radarhere]
- If first GIF frame has transparency for RGB_ALWAYS loading strategy, use RGBA mode #6592
[radarhere]
- Round box position to integer when pasting embedded color #6517
[radarhere, nulano]
- Removed EXIF prefix when saving WebP #6582
[radarhere]
- Pad IM palette to 768 bytes when saving #6579
[radarhere]
- Added DDS BC6H reading #6449
[ShadelessFox, REDxEYE, radarhere]
- Added support for opening WhiteIsZero 16-bit integer TIFF images #6642
[JayWiz, radarhere]
- Raise an error when allocating translucent color to RGB palette #6654
[jsbueno, radarhere]
- Added reading of TIFF child images #6569 - Added reading of TIFF child images #6569
[radarhere] [radarhere]

View File

@ -1,7 +1,6 @@
include *.c include *.c
include *.h include *.h
include *.in include *.in
include *.lock
include *.md include *.md
include *.py include *.py
include *.rst include *.rst
@ -10,7 +9,6 @@ include *.txt
include *.yaml include *.yaml
include LICENSE include LICENSE
include Makefile include Makefile
include Pipfile
include tox.ini include tox.ini
graft Tests graft Tests
graft src graft src

View File

@ -53,12 +53,12 @@ inplace: clean
.PHONY: install .PHONY: install
install: install:
python3 -m pip install . python3 -m pip -v install .
python3 selftest.py python3 selftest.py
.PHONY: install-coverage .PHONY: install-coverage
install-coverage: install-coverage:
CFLAGS="-coverage -Werror=implicit-function-declaration" python3 -m pip install --global-option="build_ext" . CFLAGS="-coverage -Werror=implicit-function-declaration" python3 -m pip -v install --global-option="build_ext" .
python3 selftest.py python3 selftest.py
.PHONY: debug .PHONY: debug
@ -67,7 +67,7 @@ debug:
# for our stuff, kills optimization, and redirects to dev null so we # for our stuff, kills optimization, and redirects to dev null so we
# see any build failures. # see any build failures.
make clean > /dev/null make clean > /dev/null
CFLAGS='-g -O0' python3 -m pip install --global-option="build_ext" . > /dev/null CFLAGS='-g -O0' python3 -m pip -v install --global-option="build_ext" . > /dev/null
.PHONY: release-test .PHONY: release-test
release-test: release-test:

22
Pipfile
View File

@ -1,22 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
black = "*"
check-manifest = "*"
coverage = "*"
defusedxml = "*"
packaging = "*"
markdown2 = "*"
olefile = "*"
pyroma = "*"
pytest = "*"
pytest-cov = "*"
pytest-timeout = "*"
[dev-packages]
[requires]
python_version = "3.9"

324
Pipfile.lock generated
View File

@ -1,324 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "e5cad23bf4187647d53b613a64dc4792b7064bf86b08dfb5737580e32943f54d"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.9"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"attrs": {
"hashes": [
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0"
},
"black": {
"hashes": [
"sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3",
"sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"
],
"index": "pypi",
"version": "==21.12b0"
},
"build": {
"hashes": [
"sha256:1aaadcd69338252ade4f7ec1265e1a19184bf916d84c9b7df095f423948cb89f",
"sha256:21b7ebbd1b22499c4dac536abc7606696ea4d909fd755e00f09f3c0f2c05e3c8"
],
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
"certifi": {
"hashes": [
"sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
"sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
],
"version": "==2021.10.8"
},
"charset-normalizer": {
"hashes": [
"sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721",
"sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c"
],
"markers": "python_version >= '3'",
"version": "==2.0.9"
},
"check-manifest": {
"hashes": [
"sha256:365c94d65de4c927d9d8b505371d08ee19f9f369c86b9ac3db97c2754c827c95",
"sha256:56dadd260a9c7d550b159796d2894b6d0bcc176a94cbc426d9bb93e5e48d12ce"
],
"index": "pypi",
"version": "==0.47"
},
"click": {
"hashes": [
"sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3",
"sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"
],
"markers": "python_version >= '3.6'",
"version": "==8.0.3"
},
"coverage": {
"hashes": [
"sha256:01774a2c2c729619760320270e42cd9e797427ecfddd32c2a7b639cdc481f3c0",
"sha256:03b20e52b7d31be571c9c06b74746746d4eb82fc260e594dc662ed48145e9efd",
"sha256:0a7726f74ff63f41e95ed3a89fef002916c828bb5fcae83b505b49d81a066884",
"sha256:1219d760ccfafc03c0822ae2e06e3b1248a8e6d1a70928966bafc6838d3c9e48",
"sha256:13362889b2d46e8d9f97c421539c97c963e34031ab0cb89e8ca83a10cc71ac76",
"sha256:174cf9b4bef0db2e8244f82059a5a72bd47e1d40e71c68ab055425172b16b7d0",
"sha256:17e6c11038d4ed6e8af1407d9e89a2904d573be29d51515f14262d7f10ef0a64",
"sha256:215f8afcc02a24c2d9a10d3790b21054b58d71f4b3c6f055d4bb1b15cecce685",
"sha256:22e60a3ca5acba37d1d4a2ee66e051f5b0e1b9ac950b5b0cf4aa5366eda41d47",
"sha256:2641f803ee9f95b1f387f3e8f3bf28d83d9b69a39e9911e5bfee832bea75240d",
"sha256:276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840",
"sha256:3f7c17209eef285c86f819ff04a6d4cbee9b33ef05cbcaae4c0b4e8e06b3ec8f",
"sha256:3feac4084291642165c3a0d9eaebedf19ffa505016c4d3db15bfe235718d4971",
"sha256:49dbff64961bc9bdd2289a2bda6a3a5a331964ba5497f694e2cbd540d656dc1c",
"sha256:4e547122ca2d244f7c090fe3f4b5a5861255ff66b7ab6d98f44a0222aaf8671a",
"sha256:5829192582c0ec8ca4a2532407bc14c2f338d9878a10442f5d03804a95fac9de",
"sha256:5d6b09c972ce9200264c35a1d53d43ca55ef61836d9ec60f0d44273a31aa9f17",
"sha256:600617008aa82032ddeace2535626d1bc212dfff32b43989539deda63b3f36e4",
"sha256:619346d57c7126ae49ac95b11b0dc8e36c1dd49d148477461bb66c8cf13bb521",
"sha256:63c424e6f5b4ab1cf1e23a43b12f542b0ec2e54f99ec9f11b75382152981df57",
"sha256:6dbc1536e105adda7a6312c778f15aaabe583b0e9a0b0a324990334fd458c94b",
"sha256:6e1394d24d5938e561fbeaa0cd3d356207579c28bd1792f25a068743f2d5b282",
"sha256:86f2e78b1eff847609b1ca8050c9e1fa3bd44ce755b2ec30e70f2d3ba3844644",
"sha256:8bdfe9ff3a4ea37d17f172ac0dff1e1c383aec17a636b9b35906babc9f0f5475",
"sha256:8e2c35a4c1f269704e90888e56f794e2d9c0262fb0c1b1c8c4ee44d9b9e77b5d",
"sha256:92b8c845527eae547a2a6617d336adc56394050c3ed8a6918683646328fbb6da",
"sha256:9365ed5cce5d0cf2c10afc6add145c5037d3148585b8ae0e77cc1efdd6aa2953",
"sha256:9a29311bd6429be317c1f3fe4bc06c4c5ee45e2fa61b2a19d4d1d6111cb94af2",
"sha256:9a2b5b52be0a8626fcbffd7e689781bf8c2ac01613e77feda93d96184949a98e",
"sha256:a4bdeb0a52d1d04123b41d90a4390b096f3ef38eee35e11f0b22c2d031222c6c",
"sha256:a9c8c4283e17690ff1a7427123ffb428ad6a52ed720d550e299e8291e33184dc",
"sha256:b637c57fdb8be84e91fac60d9325a66a5981f8086c954ea2772efe28425eaf64",
"sha256:bf154ba7ee2fd613eb541c2bc03d3d9ac667080a737449d1a3fb342740eb1a74",
"sha256:c254b03032d5a06de049ce8bca8338a5185f07fb76600afff3c161e053d88617",
"sha256:c332d8f8d448ded473b97fefe4a0983265af21917d8b0cdcb8bb06b2afe632c3",
"sha256:c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d",
"sha256:cfd9386c1d6f13b37e05a91a8583e802f8059bebfccde61a418c5808dea6bbfa",
"sha256:d5d2033d5db1d58ae2d62f095e1aefb6988af65b4b12cb8987af409587cc0739",
"sha256:dca38a21e4423f3edb821292e97cec7ad38086f84313462098568baedf4331f8",
"sha256:e2cad8093172b7d1595b4ad66f24270808658e11acf43a8f95b41276162eb5b8",
"sha256:e3db840a4dee542e37e09f30859f1612da90e1c5239a6a2498c473183a50e781",
"sha256:edcada2e24ed68f019175c2b2af2a8b481d3d084798b8c20d15d34f5c733fa58",
"sha256:f467bbb837691ab5a8ca359199d3429a11a01e6dfb3d9dcc676dc035ca93c0a9",
"sha256:f506af4f27def639ba45789fa6fde45f9a217da0be05f8910458e4557eed020c",
"sha256:f614fc9956d76d8a88a88bb41ddc12709caa755666f580af3a688899721efecd",
"sha256:f9afb5b746781fc2abce26193d1c817b7eb0e11459510fba65d2bd77fe161d9e",
"sha256:fb8b8ee99b3fffe4fd86f4c81b35a6bf7e4462cba019997af2fe679365db0c49"
],
"index": "pypi",
"version": "==6.2"
},
"defusedxml": {
"hashes": [
"sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69",
"sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"
],
"index": "pypi",
"version": "==0.7.1"
},
"docutils": {
"hashes": [
"sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c",
"sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==0.18.1"
},
"idna": {
"hashes": [
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
],
"markers": "python_version >= '3'",
"version": "==3.3"
},
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
],
"version": "==1.1.1"
},
"markdown2": {
"hashes": [
"sha256:8f4ac8d9a124ab408c67361090ed512deda746c04362c36c2ec16190c720c2b0",
"sha256:91113caf23aa662570fe21984f08fe74f814695c0a0ea8e863a8b4c4f63f9f6e"
],
"index": "pypi",
"version": "==2.4.2"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"olefile": {
"hashes": [
"sha256:133b031eaf8fd2c9399b78b8bc5b8fcbe4c31e85295749bb17a87cba8f3c3964"
],
"index": "pypi",
"version": "==0.46"
},
"packaging": {
"hashes": [
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
],
"index": "pypi",
"version": "==21.3"
},
"pathspec": {
"hashes": [
"sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
"sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"
],
"version": "==0.9.0"
},
"pep517": {
"hashes": [
"sha256:931378d93d11b298cf511dd634cf5ea4cb249a28ef84160b3247ee9afb4e8ab0",
"sha256:dd884c326898e2c6e11f9e0b64940606a93eb10ea022a2e067959f3a110cf161"
],
"version": "==0.12.0"
},
"platformdirs": {
"hashes": [
"sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2",
"sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"
],
"markers": "python_version >= '3.6'",
"version": "==2.4.0"
},
"pluggy": {
"hashes": [
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
],
"markers": "python_version >= '3.6'",
"version": "==1.0.0"
},
"py": {
"hashes": [
"sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719",
"sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.11.0"
},
"pygments": {
"hashes": [
"sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380",
"sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"
],
"markers": "python_version >= '3.5'",
"version": "==2.10.0"
},
"pyparsing": {
"hashes": [
"sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4",
"sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.6"
},
"pyroma": {
"hashes": [
"sha256:0fba67322913026091590e68e0d9e0d4fbd6420fcf34d315b2ad6985ab104d65",
"sha256:f8c181e0d5d292f11791afc18f7d0218a83c85cf64d6f8fb1571ce9d29a24e4a"
],
"index": "pypi",
"version": "==3.2"
},
"pytest": {
"hashes": [
"sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
"sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
],
"index": "pypi",
"version": "==6.2.5"
},
"pytest-cov": {
"hashes": [
"sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6",
"sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"
],
"index": "pypi",
"version": "==3.0.0"
},
"pytest-timeout": {
"hashes": [
"sha256:e6f98b54dafde8d70e4088467ff621260b641eb64895c4195b6e5c8f45638112",
"sha256:fe9c3d5006c053bb9e062d60f641e6a76d6707aedb645350af9593e376fcc717"
],
"index": "pypi",
"version": "==2.0.2"
},
"requests": {
"hashes": [
"sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
"sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==2.26.0"
},
"setuptools": {
"hashes": [
"sha256:5ec2bbb534ed160b261acbbdd1b463eb3cf52a8d223d96a8ab9981f63798e85c",
"sha256:75fd345a47ce3d79595b27bf57e6f49c2ca7904f3c7ce75f8a87012046c86b0b"
],
"markers": "python_version >= '3.7'",
"version": "==60.0.0"
},
"toml": {
"hashes": [
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
"version": "==0.10.2"
},
"tomli": {
"hashes": [
"sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f",
"sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"
],
"markers": "python_version >= '3.6'",
"version": "==1.2.3"
},
"typing-extensions": {
"hashes": [
"sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e",
"sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"
],
"markers": "python_version >= '3.6'",
"version": "==4.0.1"
},
"urllib3": {
"hashes": [
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
"version": "==1.26.7"
}
},
"develop": {}
}

View File

@ -54,9 +54,9 @@ As of 2019, Pillow development is
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img <a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
alt="Code coverage" alt="Code coverage"
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a> src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml"><img <a href="https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:pillow"><img
alt="Tidelift Align" alt="Fuzzing Status"
src="https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml/badge.svg"></a> src="https://oss-fuzz-build-logs.storage.googleapis.com/badges/pillow.svg"></a>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -8,6 +8,7 @@ TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
ter-x20b.pcf, from http://terminus-font.sourceforge.net/ ter-x20b.pcf, from http://terminus-font.sourceforge.net/
BungeeColor-Regular_colr_Windows.ttf, from https://github.com/djrrb/bungee BungeeColor-Regular_colr_Windows.ttf, from https://github.com/djrrb/bungee
OpenSans.woff2, from https://fonts.googleapis.com/css?family=Open+Sans
All of the above fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to. All of the above fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to.

BIN
Tests/fonts/OpenSans.woff2 Normal file

Binary file not shown.

View File

@ -208,7 +208,6 @@ class PillowLeakTestCase:
# ru_maxrss # ru_maxrss
# This is the maximum resident set size utilized (in bytes). # This is the maximum resident set size utilized (in bytes).
return mem / 1024 # Kb return mem / 1024 # Kb
else:
# linux # linux
# man 2 getrusage # man 2 getrusage
# ru_maxrss (since Linux 2.6.32) # ru_maxrss (since Linux 2.6.32)
@ -285,7 +284,7 @@ def magick_command():
if imagemagick and shutil.which(imagemagick[0]): if imagemagick and shutil.which(imagemagick[0]):
return imagemagick return imagemagick
elif graphicsmagick and shutil.which(graphicsmagick[0]): if graphicsmagick and shutil.which(graphicsmagick[0]):
return graphicsmagick return graphicsmagick

BIN
Tests/images/bc6h.dds Normal file

Binary file not shown.

BIN
Tests/images/bc6h.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
Tests/images/bc6h_sf.dds Normal file

Binary file not shown.

BIN
Tests/images/bc6h_sf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
Tests/images/test_woff2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -19,9 +19,7 @@ python3 setup.py build --build-base=/tmp/build install
# Build fuzzers in $OUT. # Build fuzzers in $OUT.
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
fuzzer_basename=$(basename -s .py $fuzzer) compile_python_fuzzer $fuzzer \
fuzzer_package=${fuzzer_basename}.pkg
pyinstaller \
--add-binary /usr/local/lib/libjpeg.so.62.3.0:. \ --add-binary /usr/local/lib/libjpeg.so.62.3.0:. \
--add-binary /usr/local/lib/libfreetype.so.6:. \ --add-binary /usr/local/lib/libfreetype.so.6:. \
--add-binary /usr/local/lib/liblcms2.so.2:. \ --add-binary /usr/local/lib/liblcms2.so.2:. \
@ -31,17 +29,7 @@ for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
--add-binary /usr/local/lib/libwebp.so.7:. \ --add-binary /usr/local/lib/libwebp.so.7:. \
--add-binary /usr/local/lib/libwebpdemux.so.2:. \ --add-binary /usr/local/lib/libwebpdemux.so.2:. \
--add-binary /usr/local/lib/libwebpmux.so.3:. \ --add-binary /usr/local/lib/libwebpmux.so.3:. \
--add-binary /usr/local/lib/libxcb.so.1:. \ --add-binary /usr/local/lib/libxcb.so.1:.
--distpath $OUT --onefile --name $fuzzer_package $fuzzer
# Create execution wrapper.
echo "#!/bin/sh
# LLVMFuzzerTestOneInput for fuzzer detection.
this_dir=\$(dirname \"\$0\")
LD_PRELOAD=\$this_dir/sanitizer_with_fuzzer.so \
ASAN_OPTIONS=\$ASAN_OPTIONS:symbolize=1:external_symbolizer_path=\$this_dir/llvm-symbolizer:detect_leaks=0 \
\$this_dir/$fuzzer_package \$@" > $OUT/$fuzzer_basename
chmod u+x $OUT/$fuzzer_basename
done done
find Tests/images Tests/icc -print | zip -q $OUT/fuzz_pillow_seed_corpus.zip -@ find Tests/images Tests/icc -print | zip -q $OUT/fuzz_pillow_seed_corpus.zip -@

View File

@ -35,6 +35,7 @@ def test_questionable():
"pal8os2v2.bmp", "pal8os2v2.bmp",
"rgb24prof.bmp", "rgb24prof.bmp",
"pal1p1.bmp", "pal1p1.bmp",
"pal4rletrns.bmp",
"pal8offs.bmp", "pal8offs.bmp",
"rgb24lprof.bmp", "rgb24lprof.bmp",
"rgb32fakealpha.bmp", "rgb32fakealpha.bmp",

View File

@ -553,18 +553,20 @@ def test_apng_save_disposal(tmp_path):
def test_apng_save_disposal_previous(tmp_path): def test_apng_save_disposal_previous(tmp_path):
test_file = str(tmp_path / "temp.png") test_file = str(tmp_path / "temp.png")
size = (128, 64) size = (128, 64)
transparent = Image.new("RGBA", size, (0, 0, 0, 0)) blue = Image.new("RGBA", size, (0, 0, 255, 255))
red = Image.new("RGBA", size, (255, 0, 0, 255)) red = Image.new("RGBA", size, (255, 0, 0, 255))
green = Image.new("RGBA", size, (0, 255, 0, 255)) green = Image.new("RGBA", size, (0, 255, 0, 255))
# test OP_NONE # test OP_NONE
transparent.save( blue.save(
test_file, test_file,
save_all=True, save_all=True,
append_images=[red, green], append_images=[red, green],
disposal=PngImagePlugin.Disposal.OP_PREVIOUS, disposal=PngImagePlugin.Disposal.OP_PREVIOUS,
) )
with Image.open(test_file) as im: with Image.open(test_file) as im:
assert im.getpixel((0, 0)) == (0, 0, 255, 255)
im.seek(2) im.seek(2)
assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((0, 0)) == (0, 255, 0, 255)
assert im.getpixel((64, 32)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255)
@ -647,6 +649,16 @@ def test_seek_after_close():
im.seek(0) im.seek(0)
@pytest.mark.parametrize("mode", ("RGBA", "RGB", "P"))
def test_different_modes_in_later_frames(mode, tmp_path):
test_file = str(tmp_path / "temp.png")
im = Image.new("L", (1, 1))
im.save(test_file, save_all=True, append_images=[Image.new(mode, (1, 1))])
with Image.open(test_file) as reloaded:
assert reloaded.mode == mode
def test_constants_deprecation(): def test_constants_deprecation():
for enum, prefix in { for enum, prefix in {
PngImagePlugin.Disposal: "APNG_DISPOSE_", PngImagePlugin.Disposal: "APNG_DISPOSE_",

View File

@ -14,6 +14,9 @@ def test_load_blp1():
with Image.open("Tests/images/blp/blp1_jpeg.blp") as im: with Image.open("Tests/images/blp/blp1_jpeg.blp") as im:
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png") assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
with Image.open("Tests/images/blp/blp1_jpeg2.blp") as im:
im.load()
def test_load_blp2_raw(): def test_load_blp2_raw():
with Image.open("Tests/images/blp/blp2_raw.blp") as im: with Image.open("Tests/images/blp/blp2_raw.blp") as im:

View File

@ -176,6 +176,11 @@ def test_rle8():
im.load() im.load()
def test_rle4():
with Image.open("Tests/images/bmp/g/pal4rle.bmp") as im:
assert_image_similar_tofile(im, "Tests/images/bmp/g/pal4.bmp", 12)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"file_name,length", "file_name,length",
( (

View File

@ -16,6 +16,8 @@ TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds"
TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
TEST_FILE_BC5S = "Tests/images/bc5s.dds" TEST_FILE_BC5S = "Tests/images/bc5s.dds"
TEST_FILE_BC6H = "Tests/images/bc6h.dds"
TEST_FILE_BC6HS = "Tests/images/bc6h_sf.dds"
TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds" TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds" TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds"
TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds" TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds"
@ -114,6 +116,20 @@ def test_dx10_bc5(image_path, expected_path):
assert_image_equal_tofile(im, expected_path.replace(".dds", ".png")) assert_image_equal_tofile(im, expected_path.replace(".dds", ".png"))
@pytest.mark.parametrize("image_path", (TEST_FILE_BC6H, TEST_FILE_BC6HS))
def test_dx10_bc6h(image_path):
"""Check DX10 BC6H/BC6HS images can be opened"""
with Image.open(image_path) as im:
im.load()
assert im.format == "DDS"
assert im.mode == "RGB"
assert im.size == (128, 128)
assert_image_equal_tofile(im, image_path.replace(".dds", ".png"))
def test_dx10_bc7(): def test_dx10_bc7():
"""Check DX10 images can be opened""" """Check DX10 images can be opened"""

View File

@ -4,7 +4,7 @@ import pytest
from PIL import FliImagePlugin, Image from PIL import FliImagePlugin, Image
from .helper import assert_image_equal_tofile, is_pypy from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy
# created as an export of a palette image from Gimp2.6 # created as an export of a palette image from Gimp2.6
# save as...-> hopper.fli, default options. # save as...-> hopper.fli, default options.
@ -79,6 +79,12 @@ def test_invalid_file():
FliImagePlugin.FliImageFile(invalid_file) FliImagePlugin.FliImageFile(invalid_file)
def test_palette_chunk_second():
with Image.open("Tests/images/hopper_palette_chunk_second.fli") as im:
with Image.open(static_test_file) as expected:
assert_image_equal(im.convert("RGB"), expected.convert("RGB"))
def test_n_frames(): def test_n_frames():
with Image.open(static_test_file) as im: with Image.open(static_test_file) as im:
assert im.n_frames == 1 assert im.n_frames == 1

View File

@ -83,18 +83,40 @@ def test_l_mode_transparency():
assert im.load()[0, 0] == 128 assert im.load()[0, 0] == 128
def test_l_mode_after_rgb():
with Image.open("Tests/images/no_palette_after_rgb.gif") as im:
im.seek(1)
assert im.mode == "RGB"
im.seek(2)
assert im.mode == "RGB"
def test_palette_not_needed_for_second_frame():
with Image.open("Tests/images/palette_not_needed_for_second_frame.gif") as im:
im.seek(1)
assert_image_similar(im, hopper("L").convert("RGB"), 8)
def test_strategy(): def test_strategy():
with Image.open("Tests/images/iss634.gif") as im:
expected_rgb_always = im.convert("RGB")
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/chi.gif") as im:
expected_zero = im.convert("RGB") expected_rgb_always_rgba = im.convert("RGBA")
im.seek(1) im.seek(1)
expected_one = im.convert("RGB") expected_different = im.convert("RGB")
try: try:
GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS GifImagePlugin.LOADING_STRATEGY = GifImagePlugin.LoadingStrategy.RGB_ALWAYS
with Image.open("Tests/images/chi.gif") as im: with Image.open("Tests/images/iss634.gif") as im:
assert im.mode == "RGB" assert im.mode == "RGB"
assert_image_equal(im, expected_zero) assert_image_equal(im, expected_rgb_always)
with Image.open("Tests/images/chi.gif") as im:
assert im.mode == "RGBA"
assert_image_equal(im, expected_rgb_always_rgba)
GifImagePlugin.LOADING_STRATEGY = ( GifImagePlugin.LOADING_STRATEGY = (
GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY GifImagePlugin.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
@ -105,7 +127,7 @@ def test_strategy():
im.seek(1) im.seek(1)
assert im.mode == "P" assert im.mode == "P"
assert_image_equal(im.convert("RGB"), expected_one) assert_image_equal(im.convert("RGB"), expected_different)
# Change to RGB mode when a frame has an individual palette # Change to RGB mode when a frame has an individual palette
with Image.open("Tests/images/iss634.gif") as im: with Image.open("Tests/images/iss634.gif") as im:
@ -769,6 +791,22 @@ def test_roundtrip_info_duration(tmp_path):
] == duration_list ] == duration_list
def test_roundtrip_info_duration_combined(tmp_path):
out = str(tmp_path / "temp.gif")
with Image.open("Tests/images/duplicate_frame.gif") as im:
assert [frame.info["duration"] for frame in ImageSequence.Iterator(im)] == [
1000,
1000,
1000,
]
im.save(out, save_all=True)
with Image.open(out) as reloaded:
assert [
frame.info["duration"] for frame in ImageSequence.Iterator(reloaded)
] == [1000, 2000]
def test_identical_frames(tmp_path): def test_identical_frames(tmp_path):
duration_list = [1000, 1500, 2000, 4000] duration_list = [1000, 1500, 2000, 4000]
@ -837,12 +875,21 @@ def test_background(tmp_path):
im.info["background"] = 1 im.info["background"] = 1
im.save(out) im.save(out)
with Image.open(out) as reread: with Image.open(out) as reread:
assert reread.info["background"] == im.info["background"] assert reread.info["background"] == im.info["background"]
def test_webp_background(tmp_path):
out = str(tmp_path / "temp.gif")
# Test opaque WebP background
if features.check("webp") and features.check("webp_anim"): if features.check("webp") and features.check("webp_anim"):
with Image.open("Tests/images/hopper.webp") as im: with Image.open("Tests/images/hopper.webp") as im:
assert isinstance(im.info["background"], tuple) assert im.info["background"] == (255, 255, 255, 255)
im.save(out)
# Test non-opaque WebP background
im = Image.new("L", (100, 100), "#000")
im.info["background"] = (0, 0, 0, 0)
im.save(out) im.save(out)

View File

@ -86,6 +86,18 @@ def test_roundtrip(mode, tmp_path):
assert_image_equal_tofile(im, out) assert_image_equal_tofile(im, out)
def test_small_palette(tmp_path):
im = Image.new("P", (1, 1))
colors = [0, 1, 2]
im.putpalette(colors)
out = str(tmp_path / "temp.im")
im.save(out)
with Image.open(out) as reloaded:
assert reloaded.getpalette() == colors + [0] * 765
def test_save_unsupported_mode(tmp_path): def test_save_unsupported_mode(tmp_path):
out = str(tmp_path / "temp.im") out = str(tmp_path / "temp.im")
im = hopper("HSV") im = hopper("HSV")

19
Tests/test_file_imt.py Normal file
View File

@ -0,0 +1,19 @@
import io
import pytest
from PIL import Image, ImtImagePlugin
from .helper import assert_image_equal_tofile
def test_sanity():
with Image.open("Tests/images/bw_gradient.imt") as im:
assert_image_equal_tofile(im, "Tests/images/bw_gradient.png")
@pytest.mark.parametrize("data", (b"\n", b"\n-", b"width 1\n"))
def test_invalid_file(data):
with io.BytesIO(data) as fp:
with pytest.raises(SyntaxError):
ImtImagePlugin.ImtImageFile(fp)

View File

@ -30,7 +30,7 @@ from .helper import (
) )
try: try:
import defusedxml.ElementTree as ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:
ElementTree = None ElementTree = None
@ -86,6 +86,33 @@ class TestFileJpeg:
assert len(im.applist) == 2 assert len(im.applist) == 2
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00" assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
assert im.app["COM"] == im.info["comment"]
def test_comment_write(self):
with Image.open(TEST_FILE) as im:
assert im.info["comment"] == b"File written by Adobe Photoshop\xa8 4.0\x00"
# Test that existing comment is saved by default
out = BytesIO()
im.save(out, format="JPEG")
with Image.open(out) as reloaded:
assert im.info["comment"] == reloaded.info["comment"]
# Ensure that a blank comment causes any existing comment to be removed
for comment in ("", b"", None):
out = BytesIO()
im.save(out, format="JPEG", comment=comment)
with Image.open(out) as reloaded:
assert "comment" not in reloaded.info
# Test that a comment argument overrides the default comment
for comment in ("Test comment text", b"Text comment text"):
out = BytesIO()
im.save(out, format="JPEG", comment=comment)
with Image.open(out) as reloaded:
if not isinstance(comment, bytes):
comment = comment.encode()
assert reloaded.info["comment"] == comment
def test_cmyk(self): def test_cmyk(self):
# Test CMYK handling. Thanks to Tim and Charlie for test data, # Test CMYK handling. Thanks to Tim and Charlie for test data,
@ -415,6 +442,13 @@ class TestFileJpeg:
info = im._getexif() info = im._getexif()
assert info[305] == "Adobe Photoshop CS Macintosh" assert info[305] == "Adobe Photoshop CS Macintosh"
def test_get_child_images(self):
with Image.open("Tests/images/flower.jpg") as im:
ims = im.get_child_images()
assert len(ims) == 1
assert_image_equal_tofile(ims[0], "Tests/images/flower_thumbnail.png")
def test_mp(self): def test_mp(self):
with Image.open("Tests/images/pil_sample_rgb.jpg") as im: with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
assert im._getmp() is None assert im._getmp() is None

View File

@ -3,6 +3,7 @@ import io
import itertools import itertools
import os import os
import re import re
import sys
from collections import namedtuple from collections import namedtuple
import pytest import pytest
@ -825,6 +826,44 @@ class TestFileLibTiff(LibTiffTestCase):
assert reloaded.mode == "F" assert reloaded.mode == "F"
assert reloaded.getexif()[SAMPLEFORMAT] == 3 assert reloaded.getexif()[SAMPLEFORMAT] == 3
def test_lzma(self, capfd):
try:
with Image.open("Tests/images/hopper_lzma.tif") as im:
assert im.mode == "RGB"
assert im.size == (128, 128)
assert im.format == "TIFF"
im2 = hopper()
assert_image_similar(im, im2, 5)
except OSError:
captured = capfd.readouterr()
if "LZMA compression support is not configured" in captured.err:
pytest.skip("LZMA compression support is not configured")
sys.stdout.write(captured.out)
sys.stderr.write(captured.err)
raise
def test_webp(self, capfd):
try:
with Image.open("Tests/images/hopper_webp.tif") as im:
assert im.mode == "RGB"
assert im.size == (128, 128)
assert im.format == "TIFF"
assert_image_similar_tofile(im, "Tests/images/hopper_webp.png", 1)
except OSError:
captured = capfd.readouterr()
if "WEBP compression support is not configured" in captured.err:
pytest.skip("WEBP compression support is not configured")
if (
"Compression scheme 50001 strip decoding is not implemented"
in captured.err
):
pytest.skip(
"Compression scheme 50001 strip decoding is not implemented"
)
sys.stdout.write(captured.out)
sys.stderr.write(captured.err)
raise
def test_lzw(self): def test_lzw(self):
with Image.open("Tests/images/hopper_lzw.tif") as im: with Image.open("Tests/images/hopper_lzw.tif") as im:
assert im.mode == "RGB" assert im.mode == "RGB"
@ -934,7 +973,7 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(out, exif=tags, compression=compression) im.save(out, exif=tags, compression=compression)
with Image.open(out) as reloaded: with Image.open(out) as reloaded:
for tag in tags.keys(): for tag in tags:
assert tag not in reloaded.getexif() assert tag not in reloaded.getexif()
def test_old_style_jpeg(self): def test_old_style_jpeg(self):

View File

@ -268,6 +268,7 @@ def test_save_all():
im_reloaded = roundtrip(im, save_all=True, append_images=[im2]) im_reloaded = roundtrip(im, save_all=True, append_images=[im2])
assert_image_equal(im, im_reloaded) assert_image_equal(im, im_reloaded)
assert im_reloaded.mpinfo[45056] == b"0100"
im_reloaded.seek(1) im_reloaded.seek(1)
assert_image_similar(im2, im_reloaded, 1) assert_image_similar(im2, im_reloaded, 1)

View File

@ -42,7 +42,6 @@ def test_save(tmp_path, mode):
helper_save_as_pdf(tmp_path, mode) helper_save_as_pdf(tmp_path, mode)
@pytest.mark.valgrind_known_error(reason="Temporary skip")
def test_monochrome(tmp_path): def test_monochrome(tmp_path):
# Arrange # Arrange
mode = "1" mode = "1"

View File

@ -20,7 +20,7 @@ from .helper import (
) )
try: try:
import defusedxml.ElementTree as ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:
ElementTree = None ElementTree = None

View File

@ -240,8 +240,8 @@ def test_header_token_too_long(tmp_path):
def test_truncated_file(tmp_path): def test_truncated_file(tmp_path):
# Test EOF in header # Test EOF in header
path = str(tmp_path / "temp.pgm") path = str(tmp_path / "temp.pgm")
with open(path, "w") as f: with open(path, "wb") as f:
f.write("P6") f.write(b"P6")
with pytest.raises(ValueError) as e: with pytest.raises(ValueError) as e:
with Image.open(path): with Image.open(path):
@ -256,11 +256,11 @@ def test_truncated_file(tmp_path):
im.load() im.load()
@pytest.mark.parametrize("maxval", (0, 65536)) @pytest.mark.parametrize("maxval", (b"0", b"65536"))
def test_invalid_maxval(maxval, tmp_path): def test_invalid_maxval(maxval, tmp_path):
path = str(tmp_path / "temp.ppm") path = str(tmp_path / "temp.ppm")
with open(path, "w") as f: with open(path, "wb") as f:
f.write("P6\n3 1 " + str(maxval)) f.write(b"P6\n3 1 " + maxval)
with pytest.raises(ValueError) as e: with pytest.raises(ValueError) as e:
with Image.open(path): with Image.open(path):
@ -283,13 +283,13 @@ def test_neg_ppm():
def test_mimetypes(tmp_path): def test_mimetypes(tmp_path):
path = str(tmp_path / "temp.pgm") path = str(tmp_path / "temp.pgm")
with open(path, "w") as f: with open(path, "wb") as f:
f.write("P4\n128 128\n255") f.write(b"P4\n128 128\n255")
with Image.open(path) as im: with Image.open(path) as im:
assert im.get_format_mimetype() == "image/x-portable-bitmap" assert im.get_format_mimetype() == "image/x-portable-bitmap"
with open(path, "w") as f: with open(path, "wb") as f:
f.write("PyCMYK\n128 128\n255") f.write(b"PyCMYK\n128 128\n255")
with Image.open(path) as im: with Image.open(path) as im:
assert im.get_format_mimetype() == "image/x-portable-anymap" assert im.get_format_mimetype() == "image/x-portable-anymap"

View File

@ -4,7 +4,7 @@ from io import BytesIO
import pytest import pytest
from PIL import Image, ImageFile, TiffImagePlugin from PIL import Image, ImageFile, TiffImagePlugin, UnidentifiedImageError
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
from .helper import ( from .helper import (
@ -18,7 +18,7 @@ from .helper import (
) )
try: try:
import defusedxml.ElementTree as ElementTree from defusedxml import ElementTree
except ImportError: except ImportError:
ElementTree = None ElementTree = None
@ -858,6 +858,19 @@ class TestFileTiff:
im.load() im.load()
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False
@pytest.mark.parametrize(
"test_file",
[
"Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif",
],
)
@pytest.mark.timeout(2)
def test_oom(self, test_file):
with pytest.raises(UnidentifiedImageError):
with pytest.warns(UserWarning):
with Image.open(test_file):
pass
@pytest.mark.skipif(not is_win32(), reason="Windows only") @pytest.mark.skipif(not is_win32(), reason="Windows only")
class TestFileTiffW32: class TestFileTiffW32:

View File

@ -201,6 +201,22 @@ def test_writing_bytes_to_ascii(tmp_path):
assert reloaded.tag_v2[271] == "test" assert reloaded.tag_v2[271] == "test"
def test_writing_int_to_bytes(tmp_path):
im = hopper()
info = TiffImagePlugin.ImageFileDirectory_v2()
tag = TiffTags.TAGS_V2[700]
assert tag.type == TiffTags.BYTE
info[700] = 1
out = str(tmp_path / "temp.tiff")
im.save(out, tiffinfo=info)
with Image.open(out) as reloaded:
assert reloaded.tag_v2[700] == b"\x01"
def test_undefined_zero(tmp_path): def test_undefined_zero(tmp_path):
# Check that the tag has not been changed since this test was created # Check that the tag has not been changed since this test was created
tag = TiffTags.TAGS_V2[45059] tag = TiffTags.TAGS_V2[45059]

View File

@ -97,6 +97,35 @@ def test_write_rgba(tmp_path):
assert_image_similar(image, pil_image, 1.0) assert_image_similar(image, pil_image, 1.0)
def test_keep_rgb_values_when_transparent(tmp_path):
"""
Saving transparent pixels should retain their original RGB values
when using the "exact" parameter.
"""
image = hopper("RGB")
# create a copy of the image
# with the left half transparent
half_transparent_image = image.copy()
new_alpha = Image.new("L", (128, 128), 255)
new_alpha.paste(0, (0, 0, 64, 128))
half_transparent_image.putalpha(new_alpha)
# save with transparent area preserved
temp_file = str(tmp_path / "temp.webp")
half_transparent_image.save(temp_file, exact=True, lossless=True)
with Image.open(temp_file) as reloaded:
assert reloaded.mode == "RGBA"
assert reloaded.format == "WEBP"
# even though it is lossless, if we don't use exact=True
# in libwebp >= 0.5, the transparent area will be filled with black
# (or something more conducive to compression)
assert_image_equal(reloaded.convert("RGB"), image)
def test_write_unsupported_mode_PA(tmp_path): def test_write_unsupported_mode_PA(tmp_path):
""" """
Saving a palette-based file with transparency to WebP format Saving a palette-based file with transparency to WebP format

View File

@ -11,6 +11,11 @@ pytestmark = [
skip_unless_feature("webp_mux"), skip_unless_feature("webp_mux"),
] ]
try:
from defusedxml import ElementTree
except ImportError:
ElementTree = None
def test_read_exif_metadata(): def test_read_exif_metadata():
@ -55,9 +60,7 @@ def test_write_exif_metadata():
test_buffer.seek(0) test_buffer.seek(0)
with Image.open(test_buffer) as webp_image: with Image.open(test_buffer) as webp_image:
webp_exif = webp_image.info.get("exif", None) webp_exif = webp_image.info.get("exif", None)
assert webp_exif assert webp_exif == expected_exif[6:], "WebP EXIF didn't match"
if webp_exif:
assert webp_exif == expected_exif, "WebP EXIF didn't match"
def test_read_icc_profile(): def test_read_icc_profile():
@ -112,6 +115,22 @@ def test_read_no_exif():
assert not webp_image._getexif() assert not webp_image._getexif()
def test_getxmp():
with Image.open("Tests/images/flower.webp") as im:
assert "xmp" not in im.info
assert im.getxmp() == {}
with Image.open("Tests/images/flower2.webp") as im:
if ElementTree is None:
with pytest.warns(UserWarning):
assert im.getxmp() == {}
else:
assert (
im.getxmp()["xmpmeta"]["xmptk"]
== "Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27 "
)
@skip_unless_feature("webp_anim") @skip_unless_feature("webp_anim")
def test_write_animated_metadata(tmp_path): def test_write_animated_metadata(tmp_path):
iccp_data = b"<iccp_data>" iccp_data = b"<iccp_data>"

View File

@ -8,6 +8,7 @@ import warnings
import pytest import pytest
from PIL import ( from PIL import (
ExifTags,
Image, Image,
ImageDraw, ImageDraw,
ImagePalette, ImagePalette,
@ -765,15 +766,15 @@ class TestImage:
def test_empty_exif(self): def test_empty_exif(self):
with Image.open("Tests/images/exif.png") as im: with Image.open("Tests/images/exif.png") as im:
exif = im.getexif() exif = im.getexif()
assert dict(exif) != {} assert dict(exif)
# Test that exif data is cleared after another load # Test that exif data is cleared after another load
exif.load(None) exif.load(None)
assert dict(exif) == {} assert not dict(exif)
# Test loading just the EXIF header # Test loading just the EXIF header
exif.load(b"Exif\x00\x00") exif.load(b"Exif\x00\x00")
assert dict(exif) == {} assert not dict(exif)
@mark_if_feature_version( @mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
@ -876,6 +877,18 @@ class TestImage:
reloaded_exif.load(exif.tobytes()) reloaded_exif.load(exif.tobytes())
assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005) assert reloaded_exif.get_ifd(0xA005) == exif.get_ifd(0xA005)
def test_exif_ifd1(self):
with Image.open("Tests/images/flower.jpg") as im:
exif = im.getexif()
assert exif.get_ifd(ExifTags.IFD.IFD1) == {
513: 2036,
514: 5448,
259: 6,
296: 2,
282: 180.0,
283: 180.0,
}
def test_exif_ifd(self): def test_exif_ifd(self):
with Image.open("Tests/images/flower.jpg") as im: with Image.open("Tests/images/flower.jpg") as im:
exif = im.getexif() exif = im.getexif()

View File

@ -4,11 +4,10 @@ import sys
import sysconfig import sysconfig
import pytest import pytest
from setuptools.command.build_ext import new_compiler
from PIL import Image from PIL import Image
from .helper import assert_image_equal, hopper, is_win32, on_ci from .helper import assert_image_equal, hopper, is_win32
# CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2 # CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670 # https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
@ -131,7 +130,6 @@ class TestImageGetPixel(AccessTest):
bands = Image.getmodebands(mode) bands = Image.getmodebands(mode)
if bands == 1: if bands == 1:
return 1 return 1
else:
return tuple(range(1, bands + 1)) return tuple(range(1, bands + 1))
def check(self, mode, c=None): def check(self, mode, c=None):
@ -345,13 +343,14 @@ class TestCffi(AccessTest):
@pytest.mark.parametrize("mode", ("P", "PA")) @pytest.mark.parametrize("mode", ("P", "PA"))
def test_p_putpixel_rgb_rgba(self, mode): def test_p_putpixel_rgb_rgba(self, mode):
for color in [(255, 0, 0), (255, 0, 0, 127)]: for color in ((255, 0, 0), (255, 0, 0, 127 if mode == "PA" else 255)):
im = Image.new(mode, (1, 1)) im = Image.new(mode, (1, 1))
access = PyAccess.new(im, False) access = PyAccess.new(im, False)
access.putpixel((0, 0), color) access.putpixel((0, 0), color)
alpha = color[3] if len(color) == 4 and mode == "PA" else 255 if len(color) == 3:
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha) color += (255,)
assert im.convert("RGBA").getpixel((0, 0)) == color
class TestImagePutPixelError(AccessTest): class TestImagePutPixelError(AccessTest):
@ -406,15 +405,14 @@ class TestImagePutPixelError(AccessTest):
class TestEmbeddable: class TestEmbeddable:
@pytest.mark.skipif( @pytest.mark.xfail(reason="failing test")
not is_win32() or on_ci(), @pytest.mark.skipif(not is_win32(), reason="requires Windows")
reason="Failing on AppVeyor / GitHub Actions when run from subprocess, "
"not from shell",
)
def test_embeddable(self): def test_embeddable(self):
import ctypes import ctypes
with open("embed_pil.c", "w") as fh: from setuptools.command.build_ext import new_compiler
with open("embed_pil.c", "w", encoding="utf-8") as fh:
fh.write( fh.write(
""" """
#include "Python.h" #include "Python.h"

View File

@ -35,10 +35,13 @@ def test_toarray():
test_with_dtype(numpy.float64) test_with_dtype(numpy.float64)
test_with_dtype(numpy.uint8) test_with_dtype(numpy.uint8)
if parse_version(numpy.__version__) >= parse_version("1.23"):
with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated: with Image.open("Tests/images/truncated_jpeg.jpg") as im_truncated:
if parse_version(numpy.__version__) >= parse_version("1.23"):
with pytest.raises(OSError): with pytest.raises(OSError):
numpy.array(im_truncated) numpy.array(im_truncated)
else:
with pytest.warns(UserWarning):
numpy.array(im_truncated)
def test_fromarray(): def test_fromarray():

View File

@ -38,6 +38,12 @@ def test_sanity():
convert(im, output_mode) convert(im, output_mode)
def test_unsupported_conversion():
im = hopper()
with pytest.raises(ValueError):
im.convert("INVALID")
def test_default(): def test_default():
im = hopper("P") im = hopper("P")
@ -242,6 +248,17 @@ def test_p2pa_palette():
assert im_pa.getpalette() == im.getpalette() assert im_pa.getpalette() == im.getpalette()
@pytest.mark.parametrize("mode", ("RGB", "RGBA", "RGBX"))
def test_rgb_lab(mode):
im = Image.new(mode, (1, 1))
converted_im = im.convert("LAB")
assert converted_im.getpixel((0, 0)) == (0, 128, 128)
im = Image.new("LAB", (1, 1), (255, 0, 0))
converted_im = im.convert(mode)
assert converted_im.getpixel((0, 0))[:3] == (0, 255, 255)
def test_matrix_illegal_conversion(): def test_matrix_illegal_conversion():
# Arrange # Arrange
im = hopper("CMYK") im = hopper("CMYK")

View File

@ -1238,6 +1238,27 @@ def test_stroke_descender():
assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_descender.png", 6.76) assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_descender.png", 6.76)
@skip_unless_feature("freetype2")
def test_split_word():
# Arrange
im = Image.new("RGB", (230, 55))
expected = im.copy()
expected_draw = ImageDraw.Draw(expected)
font = ImageFont.truetype("Tests/fonts/FreeMono.ttf", 48)
expected_draw.text((0, 0), "paradise", font=font)
draw = ImageDraw.Draw(im)
# Act
draw.text((0, 0), "par", font=font)
length = draw.textlength("par", font=font)
draw.text((length, 0), "adise", font=font)
# Assert
assert_image_equal(im, expected)
@skip_unless_feature("freetype2") @skip_unless_feature("freetype2")
def test_stroke_multiline(): def test_stroke_multiline():
# Arrange # Arrange

View File

@ -746,12 +746,14 @@ def test_variation_set_by_name(font):
_check_text(font, "Tests/images/variation_adobe.png", 11) _check_text(font, "Tests/images/variation_adobe.png", 11)
for name in ["Bold", b"Bold"]: for name in ["Bold", b"Bold"]:
font.set_variation_by_name(name) font.set_variation_by_name(name)
_check_text(font, "Tests/images/variation_adobe_name.png", 11) assert font.getname()[1] == "Bold"
_check_text(font, "Tests/images/variation_adobe_name.png", 16)
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
_check_text(font, "Tests/images/variation_tiny.png", 40) _check_text(font, "Tests/images/variation_tiny.png", 40)
for name in ["200", b"200"]: for name in ["200", b"200"]:
font.set_variation_by_name(name) font.set_variation_by_name(name)
assert font.getname()[1] == "200"
_check_text(font, "Tests/images/variation_tiny_name.png", 40) _check_text(font, "Tests/images/variation_tiny_name.png", 40)
@ -935,7 +937,30 @@ def test_standard_embedded_color(layout_engine):
d = ImageDraw.Draw(im) d = ImageDraw.Draw(im)
d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True) d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True)
assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 6.2) assert_image_similar_tofile(im, "Tests/images/standard_embedded.png", 3.1)
@pytest.mark.parametrize("fontmode", ("1", "L", "RGBA"))
def test_float_coord(layout_engine, fontmode):
txt = "Hello World!"
ttf = ImageFont.truetype(FONT_PATH, 40, layout_engine=layout_engine)
im = Image.new("RGB", (300, 64), "white")
d = ImageDraw.Draw(im)
if fontmode == "1":
d.fontmode = "1"
embedded_color = fontmode == "RGBA"
d.text((9.5, 9.5), txt, font=ttf, fill="#fa6", embedded_color=embedded_color)
try:
assert_image_similar_tofile(im, "Tests/images/text_float_coord.png", 3.9)
except AssertionError:
if fontmode == "1" and layout_engine == ImageFont.Layout.BASIC:
assert_image_similar_tofile(
im, "Tests/images/text_float_coord_1_alt.png", 1
)
else:
raise
def test_cbdt(layout_engine): def test_cbdt(layout_engine):
@ -1040,6 +1065,25 @@ def test_colr_mask(layout_engine):
assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22) assert_image_similar_tofile(im, "Tests/images/colr_bungee_mask.png", 22)
def test_woff2(layout_engine):
try:
font = ImageFont.truetype(
"Tests/fonts/OpenSans.woff2",
size=64,
layout_engine=layout_engine,
)
except OSError as e:
assert str(e) in ("unimplemented feature", "unknown file format")
pytest.skip("FreeType compiled without brotli or WOFF2 support")
im = Image.new("RGB", (350, 100), "white")
d = ImageDraw.Draw(im)
d.text((15, 5), "OpenSans", "black", font=font)
assert_image_similar_tofile(im, "Tests/images/test_woff2.png", 5)
def test_fill_deprecation(font): def test_fill_deprecation(font):
with pytest.warns(DeprecationWarning): with pytest.warns(DeprecationWarning):
font.getmask2("Hello world", fill=Image.core.fill) font.getmask2("Hello world", fill=Image.core.fill)

View File

@ -1,4 +1,5 @@
import os import os
import shutil
import subprocess import subprocess
import sys import sys
@ -33,7 +34,9 @@ class TestImageGrab:
@pytest.mark.skipif(Image.core.HAVE_XCB, reason="tests missing XCB") @pytest.mark.skipif(Image.core.HAVE_XCB, reason="tests missing XCB")
def test_grab_no_xcb(self): def test_grab_no_xcb(self):
if sys.platform not in ("win32", "darwin"): if sys.platform not in ("win32", "darwin") and not shutil.which(
"gnome-screenshot"
):
with pytest.raises(OSError) as e: with pytest.raises(OSError) as e:
ImageGrab.grab() ImageGrab.grab()
assert str(e.value).startswith("Pillow was built without XCB support") assert str(e.value).startswith("Pillow was built without XCB support")

View File

@ -6,10 +6,8 @@ from PIL import Image, ImageMath
def pixel(im): def pixel(im):
if hasattr(im, "im"): if hasattr(im, "im"):
return f"{im.mode} {repr(im.getpixel((0, 0)))}" return f"{im.mode} {repr(im.getpixel((0, 0)))}"
else:
if isinstance(im, int): if isinstance(im, int):
return int(im) # hack to deal with booleans return int(im) # hack to deal with booleans
print(im)
A = Image.new("L", (1, 1), 1) A = Image.new("L", (1, 1), 1)

View File

@ -50,6 +50,16 @@ def test_getcolor():
palette.getcolor("unknown") palette.getcolor("unknown")
def test_getcolor_rgba_color_rgb_palette():
palette = ImagePalette.ImagePalette("RGB")
# Opaque RGBA colors are converted
assert palette.getcolor((0, 0, 0, 255)) == palette.getcolor((0, 0, 0))
with pytest.raises(ValueError):
palette.getcolor((0, 0, 0, 128))
@pytest.mark.parametrize( @pytest.mark.parametrize(
"index, palette", "index, palette",
[ [

View File

@ -34,7 +34,7 @@ def test_numpy_to_image():
# Check supported 1-bit integer formats # Check supported 1-bit integer formats
assert_image(to_image(bool, 1, 1), "1", TEST_IMAGE_SIZE) assert_image(to_image(bool, 1, 1), "1", TEST_IMAGE_SIZE)
assert_image(to_image(numpy.bool8, 1, 1), "1", TEST_IMAGE_SIZE) assert_image(to_image(numpy.bool_, 1, 1), "1", TEST_IMAGE_SIZE)
# Check supported 8-bit integer formats # Check supported 8-bit integer formats
assert_image(to_image(numpy.uint8), "L", TEST_IMAGE_SIZE) assert_image(to_image(numpy.uint8), "L", TEST_IMAGE_SIZE)
@ -193,7 +193,7 @@ def test_putdata():
"dtype", "dtype",
( (
bool, bool,
numpy.bool8, numpy.bool_,
numpy.int8, numpy.int8,
numpy.int16, numpy.int16,
numpy.int32, numpy.int32,

View File

@ -15,11 +15,12 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others # the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext .PHONY: help
help: help:
@echo "Please use \`make <target>' where <target> is one of" @echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files" @echo " html to make standalone HTML files"
@echo " serve to start a local server for viewing docs"
@echo " livehtml to start a local server for viewing docs and auto-reload on change"
@echo " dirhtml to make HTML files named index.html in directories" @echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file" @echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files" @echo " pickle to make pickle files"
@ -39,42 +40,49 @@ help:
@echo " linkcheck to check all external links for integrity" @echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " doctest to run all doctests embedded in the documentation (if enabled)"
.PHONY: clean
clean: clean:
-rm -rf $(BUILDDIR)/* -rm -rf $(BUILDDIR)/*
install-sphinx: install-sphinx:
$(PYTHON) -m pip install --quiet sphinx sphinx-copybutton sphinx-issues sphinx-removed-in sphinxext-opengraph furo olefile $(PYTHON) -m pip install --quiet furo olefile sphinx sphinx-copybutton sphinx-inline-tabs sphinx-issues sphinx-removed-in sphinxext-opengraph
.PHONY: html
html: html:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b html -W --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html $(SPHINXBUILD) -b html -W --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo @echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: dirhtml
dirhtml: dirhtml:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo @echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml
singlehtml: singlehtml:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo @echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle: pickle:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo @echo
@echo "Build finished; now you can process the pickle files." @echo "Build finished; now you can process the pickle files."
.PHONY: json
json: json:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo @echo
@echo "Build finished; now you can process the JSON files." @echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp: htmlhelp:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@ -82,6 +90,7 @@ htmlhelp:
@echo "Build finished; now you can run HTML Help Workshop with the" \ @echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp." ".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp: qthelp:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@ -92,6 +101,7 @@ qthelp:
@echo "To view the help file:" @echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PillowPILfork.qhc" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PillowPILfork.qhc"
.PHONY: devhelp
devhelp: devhelp:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@ -102,12 +112,14 @@ devhelp:
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PillowPILfork" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PillowPILfork"
@echo "# devhelp" @echo "# devhelp"
.PHONY: epub
epub: epub:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo @echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub." @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: latex
latex: latex:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@ -116,6 +128,7 @@ latex:
@echo "Run \`make' in that directory to run these through (pdf)latex" \ @echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)." "(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf: latexpdf:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@ -123,18 +136,21 @@ latexpdf:
$(MAKE) -C $(BUILDDIR)/latex all-pdf $(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text: text:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo @echo
@echo "Build finished. The text files are in $(BUILDDIR)/text." @echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man: man:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo @echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man." @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo: texinfo:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@ -143,6 +159,7 @@ texinfo:
@echo "Run \`make' in that directory to run these through makeinfo" \ @echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)." "(use \`make info' here to do that automatically)."
.PHONY: info
info: info:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@ -150,18 +167,21 @@ info:
make -C $(BUILDDIR)/texinfo info make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext: gettext:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo @echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes: changes:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo @echo
@echo "The overview file is in $(BUILDDIR)/changes." @echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
linkcheck: linkcheck:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck -j auto $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck -j auto
@ -169,14 +189,17 @@ linkcheck:
@echo "Link check complete; look for any errors in the above output " \ @echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt." "or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest
doctest: doctest:
$(MAKE) install-sphinx $(MAKE) install-sphinx
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \ @echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt." "results in $(BUILDDIR)/doctest/output.txt."
.PHONY: livehtml
livehtml: html livehtml: html
livereload $(BUILDDIR)/html -p 33233 livereload $(BUILDDIR)/html -p 33233
.PHONY: serve
serve: serve:
cd $(BUILDDIR)/html; $(PYTHON) -m http.server cd $(BUILDDIR)/html; $(PYTHON) -m http.server

View File

@ -27,12 +27,13 @@ needs_sphinx = "2.4"
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = [
"sphinx_copybutton",
"sphinx_issues",
"sphinx_removed_in",
"sphinx.ext.autodoc", "sphinx.ext.autodoc",
"sphinx.ext.intersphinx", "sphinx.ext.intersphinx",
"sphinx.ext.viewcode", "sphinx.ext.viewcode",
"sphinx_copybutton",
"sphinx_inline_tabs",
"sphinx_issues",
"sphinx_removed_in",
"sphinxext.opengraph", "sphinxext.opengraph",
] ]

View File

@ -24,9 +24,10 @@ To get the number and names of bands in an image, use the
Modes Modes
----- -----
The ``mode`` of an image is a string which defines the type and depth of a pixel in the image. The ``mode`` of an image is a string which defines the type and depth of a pixel in the
Each pixel uses the full range of the bit depth. So a 1-bit pixel has a range image. Each pixel uses the full range of the bit depth. So a 1-bit pixel has a range of
of 0-1, an 8-bit pixel has a range of 0-255 and so on. The current release 0-1, an 8-bit pixel has a range of 0-255, a 32-signed integer pixel has the range of
INT32 and a 32-bit floating point pixel has the range of FLOAT32. The current release
supports the following standard modes: supports the following standard modes:
* ``1`` (1-bit pixels, black and white, stored with one pixel per byte) * ``1`` (1-bit pixels, black and white, stored with one pixel per byte)
@ -41,6 +42,9 @@ supports the following standard modes:
* ``LAB`` (3x8-bit pixels, the L*a*b color space) * ``LAB`` (3x8-bit pixels, the L*a*b color space)
* ``HSV`` (3x8-bit pixels, Hue, Saturation, Value color space) * ``HSV`` (3x8-bit pixels, Hue, Saturation, Value color space)
* Hue's range of 0-255 is a scaled version of 0 degrees <= Hue < 360 degrees
* ``I`` (32-bit signed integer pixels) * ``I`` (32-bit signed integer pixels)
* ``F`` (32-bit floating point pixels) * ``F`` (32-bit floating point pixels)
@ -60,7 +64,10 @@ Pillow also provides limited support for a few additional modes, including:
* ``BGR;24`` (24-bit reversed true colour) * ``BGR;24`` (24-bit reversed true colour)
* ``BGR;32`` (32-bit reversed true colour) * ``BGR;32`` (32-bit reversed true colour)
However, Pillow doesnt support user-defined modes; if you need to handle band Apart from these additional modes, Pillow doesn't yet support multichannel
images with a depth of more than 8 bits per channel.
Pillow also doesnt support user-defined modes; if you need to handle band
combinations that are not listed above, use a sequence of Image objects. combinations that are not listed above, use a sequence of Image objects.
You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode` You can read the mode of an image through the :py:attr:`~PIL.Image.Image.mode`

View File

@ -45,9 +45,9 @@ BMP
^^^ ^^^
Pillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``, Pillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``,
or ``RGB`` data. 16-colour images are read as ``P`` images. 4-bit run-length encoding or ``RGB`` data. 16-colour images are read as ``P`` images.
is not supported. Support for reading 8-bit run-length encoding was added in Pillow Support for reading 8-bit run-length encoding was added in Pillow 9.1.0.
9.1.0. Support for reading 4-bit run-length encoding was added in Pillow 9.3.0.
Opening Opening
~~~~~~~ ~~~~~~~
@ -56,7 +56,8 @@ The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties: :py:attr:`~PIL.Image.Image.info` properties:
**compression** **compression**
Set to ``bmp_rle`` if the file is run-length encoded. Set to 1 if the file is a 256-color run-length encoded image.
Set to 2 if the file is a 16-color run-length encoded image.
DDS DDS
^^^ ^^^
@ -473,6 +474,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
.. versionadded:: 2.5.0 .. versionadded:: 2.5.0
**comment**
A comment about the image.
.. versionadded:: 9.4.0
.. note:: .. note::
@ -1123,6 +1129,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
**method** **method**
Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 4. Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 4.
**exact**
If true, preserve the transparent RGB values. Otherwise, discard
invisible RGB values for better compression. Defaults to false.
Requires libwebp 0.5.0 or later.
**icc_profile** **icc_profile**
The ICC Profile to include in the saved file. Only supported if The ICC Profile to include in the saved file. Only supported if
the system WebP library was built with webpmux support. the system WebP library was built with webpmux support.

View File

@ -57,9 +57,9 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
:target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge :target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge
:alt: Tidelift :alt: Tidelift
.. image:: https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml/badge.svg .. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/pillow.svg
:target: https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:pillow
:alt: Tidelift Align :alt: Fuzzing Status
.. image:: https://img.shields.io/pypi/v/pillow.svg .. image:: https://img.shields.io/pypi/v/pillow.svg
:target: https://pypi.org/project/Pillow/ :target: https://pypi.org/project/Pillow/

View File

@ -23,6 +23,11 @@ Pillow supports these Python versions.
:file: older-versions.csv :file: older-versions.csv
:header-rows: 1 :header-rows: 1
.. _Linux Installation:
.. _macOS Installation:
.. _Windows Installation:
.. _FreeBSD Installation:
Basic Installation Basic Installation
------------------ ------------------
@ -38,75 +43,73 @@ Install Pillow with :command:`pip`::
python3 -m pip install --upgrade Pillow python3 -m pip install --upgrade Pillow
Windows Installation .. tab:: Linux
^^^^^^^^^^^^^^^^^^^^
We provide Pillow binaries for Windows compiled for the matrix of We provide binaries for Linux for each of the supported Python
supported Pythons in both 32 and 64-bit versions in the wheel format. versions in the manylinux wheel format. These include support for all
These binaries include support for all optional libraries except optional libraries except libimagequant. Raqm support requires
libimagequant and libxcb. Raqm support requires FriBiDi to be installed separately::
FriBiDi to be installed separately::
python3 -m pip install --upgrade pip python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow python3 -m pip install --upgrade Pillow
To install Pillow in MSYS2, see `Building on Windows using MSYS2/MinGW`_. Most major Linux distributions, including Fedora, Ubuntu and ArchLinux
also include Pillow in packages that previously contained PIL e.g.
``python-imaging``. Debian splits it into two packages, ``python3-pil``
and ``python3-pil.imagetk``.
.. tab:: macOS
macOS Installation We provide binaries for macOS for each of the supported Python
^^^^^^^^^^^^^^^^^^ versions in the wheel format. These include support for all optional
libraries except libimagequant. Raqm support requires
We provide binaries for macOS for each of the supported Python FriBiDi to be installed separately::
versions in the wheel format. These include support for all optional
libraries except libimagequant. Raqm support requires
FriBiDi to be installed separately::
python3 -m pip install --upgrade pip python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow python3 -m pip install --upgrade Pillow
Linux Installation .. tab:: Windows
^^^^^^^^^^^^^^^^^^
We provide binaries for Linux for each of the supported Python We provide Pillow binaries for Windows compiled for the matrix of
versions in the manylinux wheel format. These include support for all supported Pythons in both 32 and 64-bit versions in the wheel format.
optional libraries except libimagequant. Raqm support requires These binaries include support for all optional libraries except
FriBiDi to be installed separately:: libimagequant and libxcb. Raqm support requires
FriBiDi to be installed separately::
python3 -m pip install --upgrade pip python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow python3 -m pip install --upgrade Pillow
Most major Linux distributions, including Fedora, Ubuntu and ArchLinux To install Pillow in MSYS2, see `Building on Windows using MSYS2/MinGW`_.
also include Pillow in packages that previously contained PIL e.g.
``python-imaging``. Debian splits it into two packages, ``python3-pil``
and ``python3-pil.imagetk``.
FreeBSD Installation .. tab:: FreeBSD
^^^^^^^^^^^^^^^^^^^^
Pillow can be installed on FreeBSD via the official Ports or Packages systems: Pillow can be installed on FreeBSD via the official Ports or Packages systems:
**Ports**:: **Ports**::
cd /usr/ports/graphics/py-pillow && make install clean cd /usr/ports/graphics/py-pillow && make install clean
**Packages**:: **Packages**::
pkg install py38-pillow pkg install py38-pillow
.. note:: .. note::
The `Pillow FreeBSD port The `Pillow FreeBSD port
<https://www.freshports.org/graphics/py-pillow/>`_ and packages <https://www.freshports.org/graphics/py-pillow/>`_ and packages
are tested by the ports team with all supported FreeBSD versions. are tested by the ports team with all supported FreeBSD versions.
.. _Building on Linux:
.. _Building on macOS:
.. _Building on Windows:
.. _Building on Windows using MSYS2/MinGW:
.. _Building on FreeBSD:
.. _Building on Android:
Building From Source Building From Source
-------------------- --------------------
Download and extract the `compressed archive from PyPI`_.
.. _compressed archive from PyPI: https://pypi.org/project/Pillow/
.. _external-libraries: .. _external-libraries:
External Libraries External Libraries
@ -147,7 +150,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management * **littlecms** provides color management
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
above uses liblcms2. Tested with **1.19** and **2.7-2.13.1**. above uses liblcms2. Tested with **1.19** and **2.7-2.14**.
* **libwebp** provides the WebP format. * **libwebp** provides the WebP format.
@ -191,7 +194,141 @@ Many of Pillow's features require external libraries:
* **libxcb** provides X11 screengrab support. * **libxcb** provides X11 screengrab support.
Once you have installed the prerequisites, run:: .. tab:: Linux
If you didn't build Python from source, make sure you have Python's
development libraries installed.
In Debian or Ubuntu::
sudo apt-get install python3-dev python3-setuptools
In Fedora, the command is::
sudo dnf install python3-devel redhat-rpm-config
In Alpine, the command is::
sudo apk add python3-dev py3-setuptools
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
Prerequisites for **Ubuntu 16.04 LTS - 22.04 LTS** are installed with::
sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
libharfbuzz-dev libfribidi-dev libxcb1-dev
To install libraqm, ``sudo apt-get install meson`` and then see
``depends/install_raqm.sh``.
Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with::
sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \
freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \
harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel libxcb-devel
Note that the package manager may be yum or DNF, depending on the
exact distribution.
Prerequisites are installed for **Alpine** with::
sudo apk add tiff-dev jpeg-dev openjpeg-dev zlib-dev freetype-dev lcms2-dev \
libwebp-dev tcl-dev tk-dev harfbuzz-dev fribidi-dev libimagequant-dev \
libxcb-dev libpng-dev
See also the ``Dockerfile``\s in the Test Infrastructure repo
(https://github.com/python-pillow/docker-images) for a known working
install process for other tested distros.
.. tab:: macOS
The Xcode command line tools are required to compile portions of
Pillow. The tools are installed by running ``xcode-select --install``
from the command line. The command line tools are required even if you
have the full Xcode package installed. It may be necessary to run
``sudo xcodebuild -license`` to accept the license prior to using the
tools.
The easiest way to install external libraries is via `Homebrew
<https://brew.sh/>`_. After you install Homebrew, run::
brew install libjpeg libtiff little-cms2 openjpeg webp
To install libraqm on macOS use Homebrew to install its dependencies::
brew install freetype harfbuzz fribidi
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
.. tab:: Windows
We recommend you use prebuilt wheels from PyPI.
If you wish to compile Pillow manually, you can use the build scripts
in the ``winbuild`` directory used for CI testing and development.
These scripts require Visual Studio 2017 or newer and NASM.
The scripts also install Pillow from the local copy of the source code, so the
`Installing`_ instructions will not be necessary afterwards.
.. tab:: Windows using MSYS2/MinGW
To build Pillow using MSYS2, make sure you run the **MSYS2 MinGW 32-bit** or
**MSYS2 MinGW 64-bit** console, *not* **MSYS2** directly.
The following instructions target the 64-bit build, for 32-bit
replace all occurrences of ``mingw-w64-x86_64-`` with ``mingw-w64-i686-``.
Make sure you have Python and GCC installed::
pacman -S \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-python3 \
mingw-w64-x86_64-python3-pip \
mingw-w64-x86_64-python3-setuptools
Prerequisites are installed on **MSYS2 MinGW 64-bit** with::
pacman -S \
mingw-w64-x86_64-libjpeg-turbo \
mingw-w64-x86_64-zlib \
mingw-w64-x86_64-libtiff \
mingw-w64-x86_64-freetype \
mingw-w64-x86_64-lcms2 \
mingw-w64-x86_64-libwebp \
mingw-w64-x86_64-openjpeg2 \
mingw-w64-x86_64-libimagequant \
mingw-w64-x86_64-libraqm
.. tab:: FreeBSD
.. Note:: Only FreeBSD 10 and 11 tested
Make sure you have Python's development libraries installed::
sudo pkg install python3
Prerequisites are installed on **FreeBSD 10 or 11** with::
sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi libxcb
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
.. tab:: Android
Basic Android support has been added for compilation within the Termux
environment. The dependencies can be installed by::
pkg install -y python ndk-sysroot clang make \
libjpeg-turbo
This has been tested within the Termux app on ChromeOS, on x86.
Installing
^^^^^^^^^^
Once you have installed the prerequisites, to install Pillow from the source
code on PyPI, run::
python3 -m pip install --upgrade pip python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow --no-binary :all: python3 -m pip install --upgrade Pillow --no-binary :all:
@ -211,9 +348,19 @@ prerequisites, it may be necessary to manually clear the pip cache or
build without cache using the ``--no-cache-dir`` option to force a build without cache using the ``--no-cache-dir`` option to force a
build with newly installed external libraries. build with newly installed external libraries.
If you would like to install from a local copy of the source code instead, you
can clone from GitHub with ``git clone https://github.com/python-pillow/Pillow``
or download and extract the `compressed archive from PyPI`_.
After navigating to the Pillow directory, run::
python3 -m pip install --upgrade pip
python3 -m pip install .
.. _compressed archive from PyPI: https://pypi.org/project/Pillow/#files
Build Options Build Options
^^^^^^^^^^^^^ """""""""""""
* Environment variable: ``MAX_CONCURRENCY=n``. Pillow can use * Environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
multiprocessing to build the extension. Setting ``MAX_CONCURRENCY`` multiprocessing to build the extension. Setting ``MAX_CONCURRENCY``
@ -256,157 +403,6 @@ Sample usage::
python3 -m pip install --upgrade Pillow --global-option="build_ext" --global-option="--enable-[feature]" python3 -m pip install --upgrade Pillow --global-option="build_ext" --global-option="--enable-[feature]"
Building on macOS
^^^^^^^^^^^^^^^^^
The Xcode command line tools are required to compile portions of
Pillow. The tools are installed by running ``xcode-select --install``
from the command line. The command line tools are required even if you
have the full Xcode package installed. It may be necessary to run
``sudo xcodebuild -license`` to accept the license prior to using the
tools.
The easiest way to install external libraries is via `Homebrew
<https://brew.sh/>`_. After you install Homebrew, run::
brew install libjpeg libtiff little-cms2 openjpeg webp
To install libraqm on macOS use Homebrew to install its dependencies::
brew install freetype harfbuzz fribidi
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
Now install Pillow with::
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow --no-binary :all:
or from within the uncompressed source directory::
python3 -m pip install .
Building on Windows
^^^^^^^^^^^^^^^^^^^
We recommend you use prebuilt wheels from PyPI.
If you wish to compile Pillow manually, you can use the build scripts
in the ``winbuild`` directory used for CI testing and development.
These scripts require Visual Studio 2017 or newer and NASM.
Building on Windows using MSYS2/MinGW
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To build Pillow using MSYS2, make sure you run the **MSYS2 MinGW 32-bit** or
**MSYS2 MinGW 64-bit** console, *not* **MSYS2** directly.
The following instructions target the 64-bit build, for 32-bit
replace all occurrences of ``mingw-w64-x86_64-`` with ``mingw-w64-i686-``.
Make sure you have Python and GCC installed::
pacman -S \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-python3 \
mingw-w64-x86_64-python3-pip \
mingw-w64-x86_64-python3-setuptools
Prerequisites are installed on **MSYS2 MinGW 64-bit** with::
pacman -S \
mingw-w64-x86_64-libjpeg-turbo \
mingw-w64-x86_64-zlib \
mingw-w64-x86_64-libtiff \
mingw-w64-x86_64-freetype \
mingw-w64-x86_64-lcms2 \
mingw-w64-x86_64-libwebp \
mingw-w64-x86_64-openjpeg2 \
mingw-w64-x86_64-libimagequant \
mingw-w64-x86_64-libraqm
Now install Pillow with::
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow --no-binary :all:
Building on FreeBSD
^^^^^^^^^^^^^^^^^^^
.. Note:: Only FreeBSD 10 and 11 tested
Make sure you have Python's development libraries installed::
sudo pkg install python3
Prerequisites are installed on **FreeBSD 10 or 11** with::
sudo pkg install jpeg-turbo tiff webp lcms2 freetype2 openjpeg harfbuzz fribidi libxcb
Then see ``depends/install_raqm_cmake.sh`` to install libraqm.
Building on Linux
^^^^^^^^^^^^^^^^^
If you didn't build Python from source, make sure you have Python's
development libraries installed.
In Debian or Ubuntu::
sudo apt-get install python3-dev python3-setuptools
In Fedora, the command is::
sudo dnf install python3-devel redhat-rpm-config
In Alpine, the command is::
sudo apk add python3-dev py3-setuptools
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
Prerequisites for **Ubuntu 16.04 LTS - 22.04 LTS** are installed with::
sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
libharfbuzz-dev libfribidi-dev libxcb1-dev
To install libraqm, ``sudo apt-get install meson`` and then see
``depends/install_raqm.sh``.
Prerequisites are installed on recent **Red Hat**, **CentOS** or **Fedora** with::
sudo dnf install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel \
freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel \
harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel libxcb-devel
Note that the package manager may be yum or DNF, depending on the
exact distribution.
Prerequisites are installed for **Alpine** with::
sudo apk add tiff-dev jpeg-dev openjpeg-dev zlib-dev freetype-dev lcms2-dev \
libwebp-dev tcl-dev tk-dev harfbuzz-dev fribidi-dev libimagequant-dev \
libxcb-dev libpng-dev
See also the ``Dockerfile``\s in the Test Infrastructure repo
(https://github.com/python-pillow/docker-images) for a known working
install process for other tested distros.
Building on Android
^^^^^^^^^^^^^^^^^^^
Basic Android support has been added for compilation within the Termux
environment. The dependencies can be installed by::
pkg install -y python ndk-sysroot clang make \
libjpeg-turbo
This has been tested within the Termux app on ChromeOS, on x86.
Platform Support Platform Support
---------------- ----------------
@ -440,10 +436,10 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Debian 11 Bullseye | 3.9 | x86 | | Debian 11 Bullseye | 3.9 | x86 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Fedora 35 | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 36 | 3.10 | x86-64 | | Fedora 36 | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| Fedora 37 | 3.11 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Gentoo | 3.9 | x86-64 | | Gentoo | 3.9 | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 | | macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11, | x86-64 |
@ -464,7 +460,7 @@ These platforms are built and tested for every change.
| +----------------------------+---------------------+ | +----------------------------+---------------------+
| | 3.9 (MinGW) | x86, x86-64 | | | 3.9 (MinGW) | x86, x86-64 |
| +----------------------------+---------------------+ | +----------------------------+---------------------+
| | 3.7, 3.8, 3.9 (Cygwin) | x86-64 | | | 3.8, 3.9 (Cygwin) | x86-64 |
+----------------------------------+----------------------------+---------------------+ +----------------------------------+----------------------------+---------------------+
@ -482,11 +478,13 @@ These platforms have been reported to work at the versions mentioned.
| Operating system | | Tested Python | | Latest tested | | Tested | | Operating system | | Tested Python | | Latest tested | | Tested |
| | | versions | | Pillow version | | processors | | | | versions | | Pillow version | | processors |
+==================================+===========================+==================+==============+ +==================================+===========================+==================+==============+
| macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10 | 9.2.0 |arm | | macOS 13 Ventura | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
+----------------------------------+---------------------------+------------------+--------------+
| macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
+----------------------------------+---------------------------+------------------+--------------+ +----------------------------------+---------------------------+------------------+--------------+
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm | | macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
| +---------------------------+------------------+--------------+ | +---------------------------+------------------+--------------+
| | 3.7, 3.8, 3.9, 3.10 | 9.2.0 |x86-64 | | | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |x86-64 |
| +---------------------------+------------------+ | | +---------------------------+------------------+ |
| | 3.6 | 8.4.0 | | | | 3.6 | 8.4.0 | |
+----------------------------------+---------------------------+------------------+--------------+ +----------------------------------+---------------------------+------------------+--------------+

View File

@ -4,8 +4,42 @@
:py:mod:`~PIL.ExifTags` Module :py:mod:`~PIL.ExifTags` Module
============================== ==============================
The :py:mod:`~PIL.ExifTags` module exposes two dictionaries which The :py:mod:`~PIL.ExifTags` module exposes several ``enum.IntEnum`` classes
provide constants and clear-text names for various well-known EXIF tags. which provide constants and clear-text names for various well-known EXIF tags.
.. py:data:: Base
>>> from PIL.ExifTags import Base
>>> Base.ImageDescription.value
270
>>> Base(270).name
'ImageDescription'
.. py:data:: GPS
>>> from PIL.ExifTags import GPS
>>> GPS.GPSDestLatitude.value
20
>>> GPS(20).name
'GPSDestLatitude'
.. py:data:: Interop
>>> from PIL.ExifTags import Interop
>>> Interop.RelatedImageFileFormat.value
4096
>>> Interop(4096).name
'RelatedImageFileFormat'
.. py:data:: IFD
>>> from PIL.ExifTags import IFD
>>> IFD.Exif.value
34665
>>> IFD(34665).name
'Exif'
Two of these values are also exposed as dictionaries.
.. py:data:: TAGS .. py:data:: TAGS
:type: dict :type: dict
@ -20,7 +54,7 @@ provide constants and clear-text names for various well-known EXIF tags.
.. py:data:: GPSTAGS .. py:data:: GPSTAGS
:type: dict :type: dict
The GPSTAGS dictionary maps 8-bit integer EXIF gps enumerations to The GPSTAGS dictionary maps 8-bit integer EXIF GPS enumerations to
descriptive string names. For instance: descriptive string names. For instance:
>>> from PIL.ExifTags import GPSTAGS >>> from PIL.ExifTags import GPSTAGS

View File

@ -139,17 +139,50 @@ Functions
must be the same as the image mode. If omitted, the mode must be the same as the image mode. If omitted, the mode
defaults to the mode of the image. defaults to the mode of the image.
Attributes
----------
.. py:attribute:: ImageDraw.fill
:type: bool
:value: False
Selects whether :py:attr:`ImageDraw.ink` should be used as a fill or outline color.
.. py:attribute:: ImageDraw.font
The current default font.
Can be set per instance::
from PIL import ImageDraw, ImageFont
draw = ImageDraw.Draw(image)
draw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")
Or globally for all future ImageDraw instances::
from PIL import ImageDraw, ImageFont
ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")
.. py:attribute:: ImageDraw.fontmode
The current font drawing mode.
Set to ``"1"`` to disable antialiasing or ``"L"`` to enable it.
.. py:attribute:: ImageDraw.ink
:type: int
The internal representation of the current default color.
Methods Methods
------- -------
.. py:method:: ImageDraw.getfont() .. py:method:: ImageDraw.getfont()
Get the current default font. Get the current default font, :py:attr:`ImageDraw.font`.
To set the default font for all future ImageDraw instances:: If the current default font is ``None``,
it is initialized with :py:func:`.ImageFont.load_default`.
from PIL import ImageDraw, ImageFont
ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")
:returns: An image font. :returns: An image font.

View File

@ -202,7 +202,7 @@ Pillow now builds binary wheels for musllinux, suitable for Linux distributions
(rather than the glibc library used by manylinux wheels). See :pep:`656`. (rather than the glibc library used by manylinux wheels). See :pep:`656`.
ImageShow temporary files on Unix ImageShow temporary files on Unix
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When calling :py:meth:`~PIL.Image.Image.show` or using :py:mod:`~PIL.ImageShow`, When calling :py:meth:`~PIL.Image.Image.show` or using :py:mod:`~PIL.ImageShow`,
a temporary file is created from the image. On Unix, Pillow will no longer delete these a temporary file is created from the image. On Unix, Pillow will no longer delete these

View File

@ -1,28 +1,6 @@
9.3.0 9.3.0
----- -----
Backwards Incompatible Changes
==============================
TODO
^^^^
Deprecations
============
TODO
^^^^
TODO
API Changes
===========
TODO
^^^^
TODO
API Additions API Additions
============= =============
@ -51,19 +29,79 @@ Additional images can also be appended when saving, by combining the
im.save(out, save_all=True, append_images=[im1, im2, ...]) im.save(out, save_all=True, append_images=[im1, im2, ...])
Added ExifTags enums
^^^^^^^^^^^^^^^^^^^^
The data from :py:data:`~PIL.ExifTags.TAGS` and
:py:data:`~PIL.ExifTags.GPSTAGS` is now also exposed as ``enum.IntEnum``
classes: :py:data:`~PIL.ExifTags.Base` and :py:data:`~PIL.ExifTags.GPS`.
Security Security
======== ========
TODO Initialize libtiff buffer when saving
^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When saving a TIFF image to a file object using libtiff, the buffer was not
initialized. This behaviour introduced in Pillow 2.0.0, and has now been fixed.
Decode JPEG compressed BLP1 data in original mode
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Within the BLP image format, BLP1 data may use JPEG compression. Instead of
telling the JPEG library that this data is in BGRX mode, Pillow will now
decode the data in its natural CMYK mode, then convert it to RGB and rearrange
the channels afterwards. Trying to load the data in an incorrect mode could
result in a segmentation fault. This issue was introduced in Pillow 9.1.0.
Limit SAMPLESPERPIXEL to avoid runtime DOS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A large value in the ``SAMPLESPERPIXEL`` tag could lead to a memory and runtime DOS in
``TiffImagePlugin.py`` when setting up the context for image decoding.
This was introduced in Pillow 9.2.0, found with `OSS-Fuzz`_ and fixed by limiting
``SAMPLESPERPIXEL`` to the number of planes that we can decode.
TODO
Other Changes Other Changes
============= =============
Added DDS ATI1 and ATI2 reading Python 3.11 wheels
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
Support has been added to read the ATI1 and ATI2 formats of DDS images. Pillow 9.2.0 had wheels built against Python 3.11 beta, available as a preview to help
others prepare for 3.11, and ensure Pillow can be used immediately on release day of
3.11.0 final (2022-10-24, :pep:`664`).
Pillow 9.3.0 now officially includes binary wheels for Python 3.11 final.
Windows wheels
^^^^^^^^^^^^^^
This release contains wheels for Windows built using GitHub Actions.
Previously they were built by `Christoph Gohlke <https://www.cgohlke.com/>`_.
A huge thanks to Christoph for building Windows binaries for us for around a decade,
plus testing, and fixing over a hundred bug fixes along the way, in addition to building
and hosting unofficial Windows binaries for hundreds of Python projects!
Added DDS ATI1, ATI2 and BC6H reading
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Support has been added to read the ATI1, ATI2 and BC6H formats of DDS images.
Release GIL when converting images using matrix operations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Python's Global Interpreter Lock is now released when converting images using matrix
operations.
Show all frames with ImageShow
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When calling :py:meth:`~PIL.Image.Image.show` or using
:py:mod:`~PIL.ImageShow`, all frames will now be shown.
.. _OSS-Fuzz: https://github.com/google/oss-fuzz

View File

@ -0,0 +1,76 @@
9.4.0
-----
Backwards Incompatible Changes
==============================
TODO
^^^^
TODO
Deprecations
============
TODO
^^^^
TODO
API Changes
===========
TODO
^^^^
TODO
API Additions
=============
Added start position for getmask and getmask2
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Text may render differently when starting at fractional coordinates, so
:py:meth:`.FreeTypeFont.getmask` and :py:meth:`.FreeTypeFont.getmask2` now
support a ``start`` argument. This tuple of horizontal and vertical offset
will be used internally by :py:meth:`.ImageDraw.text` to more accurately place
text at the ``xy`` coordinates.
Added the ``exact`` encoding option for WebP
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``exact`` encoding option for WebP is now supported. The WebP encoder
removes the hidden RGB values for better compression by default in libwebp 0.5
or later. By setting this option to ``True``, the encoder will keep the hidden
RGB values.
getxmp()
^^^^^^^^
`XMP data <https://en.wikipedia.org/wiki/Extensible_Metadata_Platform>`_ can now be
decoded for WEBP images through ``getxmp()``.
Writing JPEG comments
^^^^^^^^^^^^^^^^^^^^^
When saving a JPEG image, a comment can now be written from
:py:attr:`~PIL.Image.Image.info`, or by using an argument when saving::
im.save(out, comment="Test comment")
Security
========
TODO
^^^^
TODO
Other Changes
=============
TODO
^^^^
TODO

View File

@ -14,6 +14,7 @@ expected to be backported to earlier versions.
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
9.4.0
9.3.0 9.3.0
9.2.0 9.2.0
9.1.1 9.1.1

View File

@ -46,6 +46,7 @@ docs =
olefile olefile
sphinx>=2.4 sphinx>=2.4
sphinx-copybutton sphinx-copybutton
sphinx-inline-tabs
sphinx-issues>=3.0.1 sphinx-issues>=3.0.1
sphinx-removed-in sphinx-removed-in
sphinxext-opengraph sphinxext-opengraph

View File

@ -15,15 +15,13 @@ import subprocess
import sys import sys
import warnings import warnings
from setuptools import Extension from setuptools import Extension, setup
from setuptools import __version__ as setuptools_version
from setuptools import setup
from setuptools.command.build_ext import build_ext from setuptools.command.build_ext import build_ext
def get_version(): def get_version():
version_file = "src/PIL/_version.py" version_file = "src/PIL/_version.py"
with open(version_file) as f: with open(version_file, encoding="utf-8") as f:
exec(compile(f.read(), version_file, "exec")) exec(compile(f.read(), version_file, "exec"))
return locals()["__version__"] return locals()["__version__"]
@ -852,7 +850,6 @@ class pil_build_ext(build_ext):
sys.platform == "win32" sys.platform == "win32"
and sys.version_info < (3, 9) and sys.version_info < (3, 9)
and not (PLATFORM_PYPY or PLATFORM_MINGW) and not (PLATFORM_PYPY or PLATFORM_MINGW)
and int(setuptools_version.split(".")[0]) < 60
): ):
defs.append(("PILLOW_VERSION", f'"\\"{PILLOW_VERSION}\\""')) defs.append(("PILLOW_VERSION", f'"\\"{PILLOW_VERSION}\\""'))
else: else:

View File

@ -373,8 +373,11 @@ class BLP1Decoder(_BLPBaseDecoder):
data = BytesIO(data) data = BytesIO(data)
image = JpegImageFile(data) image = JpegImageFile(data)
Image._decompression_bomb_check(image.size) Image._decompression_bomb_check(image.size)
image.mode = "RGB" if image.mode == "CMYK":
image.tile = [("jpeg", (0, 0) + self.size, 0, ("BGRX", ""))] decoder_name, extents, offset, args = image.tile[0]
image.tile = [(decoder_name, extents, offset, (args[0], "CMYK"))]
r, g, b = image.convert("RGB").split()
image = Image.merge("RGB", (b, g, r))
self.set_as_raw(image.tobytes()) self.set_as_raw(image.tobytes())

View File

@ -211,7 +211,7 @@ class BmpImageFile(ImageFile.ImageFile):
elif file_info["compression"] == self.RAW: elif file_info["compression"] == self.RAW:
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
raw_mode, self.mode = "BGRA", "RGBA" raw_mode, self.mode = "BGRA", "RGBA"
elif file_info["compression"] == self.RLE8: elif file_info["compression"] in (self.RLE8, self.RLE4):
decoder_name = "bmp_rle" decoder_name = "bmp_rle"
else: else:
raise OSError(f"Unsupported BMP compression ({file_info['compression']})") raise OSError(f"Unsupported BMP compression ({file_info['compression']})")
@ -250,16 +250,18 @@ class BmpImageFile(ImageFile.ImageFile):
# ---------------------------- Finally set the tile data for the plugin # ---------------------------- Finally set the tile data for the plugin
self.info["compression"] = file_info["compression"] self.info["compression"] = file_info["compression"]
args = [raw_mode]
if decoder_name == "bmp_rle":
args.append(file_info["compression"] == self.RLE4)
else:
args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3))
args.append(file_info["direction"])
self.tile = [ self.tile = [
( (
decoder_name, decoder_name,
(0, 0, file_info["width"], file_info["height"]), (0, 0, file_info["width"], file_info["height"]),
offset or self.fp.tell(), offset or self.fp.tell(),
( tuple(args),
raw_mode,
((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3),
file_info["direction"],
),
) )
] ]
@ -280,6 +282,7 @@ class BmpRleDecoder(ImageFile.PyDecoder):
_pulls_fd = True _pulls_fd = True
def decode(self, buffer): def decode(self, buffer):
rle4 = self.args[1]
data = bytearray() data = bytearray()
x = 0 x = 0
while len(data) < self.state.xsize * self.state.ysize: while len(data) < self.state.xsize * self.state.ysize:
@ -293,6 +296,15 @@ class BmpRleDecoder(ImageFile.PyDecoder):
if x + num_pixels > self.state.xsize: if x + num_pixels > self.state.xsize:
# Too much data for row # Too much data for row
num_pixels = max(0, self.state.xsize - x) num_pixels = max(0, self.state.xsize - x)
if rle4:
first_pixel = o8(byte[0] >> 4)
second_pixel = o8(byte[0] & 0x0F)
for index in range(num_pixels):
if index % 2 == 0:
data += first_pixel
else:
data += second_pixel
else:
data += byte * num_pixels data += byte * num_pixels
x += num_pixels x += num_pixels
else: else:
@ -314,9 +326,18 @@ class BmpRleDecoder(ImageFile.PyDecoder):
x = len(data) % self.state.xsize x = len(data) % self.state.xsize
else: else:
# absolute mode # absolute mode
bytes_read = self.fd.read(byte[0]) if rle4:
# 2 pixels per byte
byte_count = byte[0] // 2
bytes_read = self.fd.read(byte_count)
for byte_read in bytes_read:
data += o8(byte_read >> 4)
data += o8(byte_read & 0x0F)
else:
byte_count = byte[0]
bytes_read = self.fd.read(byte_count)
data += bytes_read data += bytes_read
if len(bytes_read) < byte[0]: if len(bytes_read) < byte_count:
break break
x += byte[0] x += byte[0]

View File

@ -101,6 +101,8 @@ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
DXGI_FORMAT_BC5_TYPELESS = 82 DXGI_FORMAT_BC5_TYPELESS = 82
DXGI_FORMAT_BC5_UNORM = 83 DXGI_FORMAT_BC5_UNORM = 83
DXGI_FORMAT_BC5_SNORM = 84 DXGI_FORMAT_BC5_SNORM = 84
DXGI_FORMAT_BC6H_UF16 = 95
DXGI_FORMAT_BC6H_SF16 = 96
DXGI_FORMAT_BC7_TYPELESS = 97 DXGI_FORMAT_BC7_TYPELESS = 97
DXGI_FORMAT_BC7_UNORM = 98 DXGI_FORMAT_BC7_UNORM = 98
DXGI_FORMAT_BC7_UNORM_SRGB = 99 DXGI_FORMAT_BC7_UNORM_SRGB = 99
@ -181,6 +183,14 @@ class DdsImageFile(ImageFile.ImageFile):
self.pixel_format = "BC5S" self.pixel_format = "BC5S"
n = 5 n = 5
self.mode = "RGB" self.mode = "RGB"
elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
self.pixel_format = "BC6H"
n = 6
self.mode = "RGB"
elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
self.pixel_format = "BC6HS"
n = 6
self.mode = "RGB"
elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM): elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
self.pixel_format = "BC7" self.pixel_format = "BC7"
n = 7 n = 7

View File

@ -14,318 +14,343 @@ This module provides constants and clear-text names for various
well-known EXIF tags. well-known EXIF tags.
""" """
from enum import IntEnum
TAGS = {
class Base(IntEnum):
# possibly incomplete # possibly incomplete
0x0001: "InteropIndex", InteropIndex = 0x0001
0x000B: "ProcessingSoftware", ProcessingSoftware = 0x000B
0x00FE: "NewSubfileType", NewSubfileType = 0x00FE
0x00FF: "SubfileType", SubfileType = 0x00FF
0x0100: "ImageWidth", ImageWidth = 0x0100
0x0101: "ImageLength", ImageLength = 0x0101
0x0102: "BitsPerSample", BitsPerSample = 0x0102
0x0103: "Compression", Compression = 0x0103
0x0106: "PhotometricInterpretation", PhotometricInterpretation = 0x0106
0x0107: "Thresholding", Thresholding = 0x0107
0x0108: "CellWidth", CellWidth = 0x0108
0x0109: "CellLength", CellLength = 0x0109
0x010A: "FillOrder", FillOrder = 0x010A
0x010D: "DocumentName", DocumentName = 0x010D
0x010E: "ImageDescription", ImageDescription = 0x010E
0x010F: "Make", Make = 0x010F
0x0110: "Model", Model = 0x0110
0x0111: "StripOffsets", StripOffsets = 0x0111
0x0112: "Orientation", Orientation = 0x0112
0x0115: "SamplesPerPixel", SamplesPerPixel = 0x0115
0x0116: "RowsPerStrip", RowsPerStrip = 0x0116
0x0117: "StripByteCounts", StripByteCounts = 0x0117
0x0118: "MinSampleValue", MinSampleValue = 0x0118
0x0119: "MaxSampleValue", MaxSampleValue = 0x0119
0x011A: "XResolution", XResolution = 0x011A
0x011B: "YResolution", YResolution = 0x011B
0x011C: "PlanarConfiguration", PlanarConfiguration = 0x011C
0x011D: "PageName", PageName = 0x011D
0x0120: "FreeOffsets", FreeOffsets = 0x0120
0x0121: "FreeByteCounts", FreeByteCounts = 0x0121
0x0122: "GrayResponseUnit", GrayResponseUnit = 0x0122
0x0123: "GrayResponseCurve", GrayResponseCurve = 0x0123
0x0124: "T4Options", T4Options = 0x0124
0x0125: "T6Options", T6Options = 0x0125
0x0128: "ResolutionUnit", ResolutionUnit = 0x0128
0x0129: "PageNumber", PageNumber = 0x0129
0x012D: "TransferFunction", TransferFunction = 0x012D
0x0131: "Software", Software = 0x0131
0x0132: "DateTime", DateTime = 0x0132
0x013B: "Artist", Artist = 0x013B
0x013C: "HostComputer", HostComputer = 0x013C
0x013D: "Predictor", Predictor = 0x013D
0x013E: "WhitePoint", WhitePoint = 0x013E
0x013F: "PrimaryChromaticities", PrimaryChromaticities = 0x013F
0x0140: "ColorMap", ColorMap = 0x0140
0x0141: "HalftoneHints", HalftoneHints = 0x0141
0x0142: "TileWidth", TileWidth = 0x0142
0x0143: "TileLength", TileLength = 0x0143
0x0144: "TileOffsets", TileOffsets = 0x0144
0x0145: "TileByteCounts", TileByteCounts = 0x0145
0x014A: "SubIFDs", SubIFDs = 0x014A
0x014C: "InkSet", InkSet = 0x014C
0x014D: "InkNames", InkNames = 0x014D
0x014E: "NumberOfInks", NumberOfInks = 0x014E
0x0150: "DotRange", DotRange = 0x0150
0x0151: "TargetPrinter", TargetPrinter = 0x0151
0x0152: "ExtraSamples", ExtraSamples = 0x0152
0x0153: "SampleFormat", SampleFormat = 0x0153
0x0154: "SMinSampleValue", SMinSampleValue = 0x0154
0x0155: "SMaxSampleValue", SMaxSampleValue = 0x0155
0x0156: "TransferRange", TransferRange = 0x0156
0x0157: "ClipPath", ClipPath = 0x0157
0x0158: "XClipPathUnits", XClipPathUnits = 0x0158
0x0159: "YClipPathUnits", YClipPathUnits = 0x0159
0x015A: "Indexed", Indexed = 0x015A
0x015B: "JPEGTables", JPEGTables = 0x015B
0x015F: "OPIProxy", OPIProxy = 0x015F
0x0200: "JPEGProc", JPEGProc = 0x0200
0x0201: "JpegIFOffset", JpegIFOffset = 0x0201
0x0202: "JpegIFByteCount", JpegIFByteCount = 0x0202
0x0203: "JpegRestartInterval", JpegRestartInterval = 0x0203
0x0205: "JpegLosslessPredictors", JpegLosslessPredictors = 0x0205
0x0206: "JpegPointTransforms", JpegPointTransforms = 0x0206
0x0207: "JpegQTables", JpegQTables = 0x0207
0x0208: "JpegDCTables", JpegDCTables = 0x0208
0x0209: "JpegACTables", JpegACTables = 0x0209
0x0211: "YCbCrCoefficients", YCbCrCoefficients = 0x0211
0x0212: "YCbCrSubSampling", YCbCrSubSampling = 0x0212
0x0213: "YCbCrPositioning", YCbCrPositioning = 0x0213
0x0214: "ReferenceBlackWhite", ReferenceBlackWhite = 0x0214
0x02BC: "XMLPacket", XMLPacket = 0x02BC
0x1000: "RelatedImageFileFormat", RelatedImageFileFormat = 0x1000
0x1001: "RelatedImageWidth", RelatedImageWidth = 0x1001
0x1002: "RelatedImageLength", RelatedImageLength = 0x1002
0x4746: "Rating", Rating = 0x4746
0x4749: "RatingPercent", RatingPercent = 0x4749
0x800D: "ImageID", ImageID = 0x800D
0x828D: "CFARepeatPatternDim", CFARepeatPatternDim = 0x828D
0x828E: "CFAPattern", BatteryLevel = 0x828F
0x828F: "BatteryLevel", Copyright = 0x8298
0x8298: "Copyright", ExposureTime = 0x829A
0x829A: "ExposureTime", FNumber = 0x829D
0x829D: "FNumber", IPTCNAA = 0x83BB
0x83BB: "IPTCNAA", ImageResources = 0x8649
0x8649: "ImageResources", ExifOffset = 0x8769
0x8769: "ExifOffset", InterColorProfile = 0x8773
0x8773: "InterColorProfile", ExposureProgram = 0x8822
0x8822: "ExposureProgram", SpectralSensitivity = 0x8824
0x8824: "SpectralSensitivity", GPSInfo = 0x8825
0x8825: "GPSInfo", ISOSpeedRatings = 0x8827
0x8827: "ISOSpeedRatings", OECF = 0x8828
0x8828: "OECF", Interlace = 0x8829
0x8829: "Interlace", TimeZoneOffset = 0x882A
0x882A: "TimeZoneOffset", SelfTimerMode = 0x882B
0x882B: "SelfTimerMode", SensitivityType = 0x8830
0x8830: "SensitivityType", StandardOutputSensitivity = 0x8831
0x8831: "StandardOutputSensitivity", RecommendedExposureIndex = 0x8832
0x8832: "RecommendedExposureIndex", ISOSpeed = 0x8833
0x8833: "ISOSpeed", ISOSpeedLatitudeyyy = 0x8834
0x8834: "ISOSpeedLatitudeyyy", ISOSpeedLatitudezzz = 0x8835
0x8835: "ISOSpeedLatitudezzz", ExifVersion = 0x9000
0x9000: "ExifVersion", DateTimeOriginal = 0x9003
0x9003: "DateTimeOriginal", DateTimeDigitized = 0x9004
0x9004: "DateTimeDigitized", OffsetTime = 0x9010
0x9010: "OffsetTime", OffsetTimeOriginal = 0x9011
0x9011: "OffsetTimeOriginal", OffsetTimeDigitized = 0x9012
0x9012: "OffsetTimeDigitized", ComponentsConfiguration = 0x9101
0x9101: "ComponentsConfiguration", CompressedBitsPerPixel = 0x9102
0x9102: "CompressedBitsPerPixel", ShutterSpeedValue = 0x9201
0x9201: "ShutterSpeedValue", ApertureValue = 0x9202
0x9202: "ApertureValue", BrightnessValue = 0x9203
0x9203: "BrightnessValue", ExposureBiasValue = 0x9204
0x9204: "ExposureBiasValue", MaxApertureValue = 0x9205
0x9205: "MaxApertureValue", SubjectDistance = 0x9206
0x9206: "SubjectDistance", MeteringMode = 0x9207
0x9207: "MeteringMode", LightSource = 0x9208
0x9208: "LightSource", Flash = 0x9209
0x9209: "Flash", FocalLength = 0x920A
0x920A: "FocalLength", Noise = 0x920D
0x920B: "FlashEnergy", ImageNumber = 0x9211
SecurityClassification = 0x9212
ImageHistory = 0x9213
TIFFEPStandardID = 0x9216
MakerNote = 0x927C
UserComment = 0x9286
SubsecTime = 0x9290
SubsecTimeOriginal = 0x9291
SubsecTimeDigitized = 0x9292
AmbientTemperature = 0x9400
Humidity = 0x9401
Pressure = 0x9402
WaterDepth = 0x9403
Acceleration = 0x9404
CameraElevationAngle = 0x9405
XPTitle = 0x9C9B
XPComment = 0x9C9C
XPAuthor = 0x9C9D
XPKeywords = 0x9C9E
XPSubject = 0x9C9F
FlashPixVersion = 0xA000
ColorSpace = 0xA001
ExifImageWidth = 0xA002
ExifImageHeight = 0xA003
RelatedSoundFile = 0xA004
ExifInteroperabilityOffset = 0xA005
FlashEnergy = 0xA20B
SpatialFrequencyResponse = 0xA20C
FocalPlaneXResolution = 0xA20E
FocalPlaneYResolution = 0xA20F
FocalPlaneResolutionUnit = 0xA210
SubjectLocation = 0xA214
ExposureIndex = 0xA215
SensingMethod = 0xA217
FileSource = 0xA300
SceneType = 0xA301
CFAPattern = 0xA302
CustomRendered = 0xA401
ExposureMode = 0xA402
WhiteBalance = 0xA403
DigitalZoomRatio = 0xA404
FocalLengthIn35mmFilm = 0xA405
SceneCaptureType = 0xA406
GainControl = 0xA407
Contrast = 0xA408
Saturation = 0xA409
Sharpness = 0xA40A
DeviceSettingDescription = 0xA40B
SubjectDistanceRange = 0xA40C
ImageUniqueID = 0xA420
CameraOwnerName = 0xA430
BodySerialNumber = 0xA431
LensSpecification = 0xA432
LensMake = 0xA433
LensModel = 0xA434
LensSerialNumber = 0xA435
CompositeImage = 0xA460
CompositeImageCount = 0xA461
CompositeImageExposureTimes = 0xA462
Gamma = 0xA500
PrintImageMatching = 0xC4A5
DNGVersion = 0xC612
DNGBackwardVersion = 0xC613
UniqueCameraModel = 0xC614
LocalizedCameraModel = 0xC615
CFAPlaneColor = 0xC616
CFALayout = 0xC617
LinearizationTable = 0xC618
BlackLevelRepeatDim = 0xC619
BlackLevel = 0xC61A
BlackLevelDeltaH = 0xC61B
BlackLevelDeltaV = 0xC61C
WhiteLevel = 0xC61D
DefaultScale = 0xC61E
DefaultCropOrigin = 0xC61F
DefaultCropSize = 0xC620
ColorMatrix1 = 0xC621
ColorMatrix2 = 0xC622
CameraCalibration1 = 0xC623
CameraCalibration2 = 0xC624
ReductionMatrix1 = 0xC625
ReductionMatrix2 = 0xC626
AnalogBalance = 0xC627
AsShotNeutral = 0xC628
AsShotWhiteXY = 0xC629
BaselineExposure = 0xC62A
BaselineNoise = 0xC62B
BaselineSharpness = 0xC62C
BayerGreenSplit = 0xC62D
LinearResponseLimit = 0xC62E
CameraSerialNumber = 0xC62F
LensInfo = 0xC630
ChromaBlurRadius = 0xC631
AntiAliasStrength = 0xC632
ShadowScale = 0xC633
DNGPrivateData = 0xC634
MakerNoteSafety = 0xC635
CalibrationIlluminant1 = 0xC65A
CalibrationIlluminant2 = 0xC65B
BestQualityScale = 0xC65C
RawDataUniqueID = 0xC65D
OriginalRawFileName = 0xC68B
OriginalRawFileData = 0xC68C
ActiveArea = 0xC68D
MaskedAreas = 0xC68E
AsShotICCProfile = 0xC68F
AsShotPreProfileMatrix = 0xC690
CurrentICCProfile = 0xC691
CurrentPreProfileMatrix = 0xC692
ColorimetricReference = 0xC6BF
CameraCalibrationSignature = 0xC6F3
ProfileCalibrationSignature = 0xC6F4
AsShotProfileName = 0xC6F6
NoiseReductionApplied = 0xC6F7
ProfileName = 0xC6F8
ProfileHueSatMapDims = 0xC6F9
ProfileHueSatMapData1 = 0xC6FA
ProfileHueSatMapData2 = 0xC6FB
ProfileToneCurve = 0xC6FC
ProfileEmbedPolicy = 0xC6FD
ProfileCopyright = 0xC6FE
ForwardMatrix1 = 0xC714
ForwardMatrix2 = 0xC715
PreviewApplicationName = 0xC716
PreviewApplicationVersion = 0xC717
PreviewSettingsName = 0xC718
PreviewSettingsDigest = 0xC719
PreviewColorSpace = 0xC71A
PreviewDateTime = 0xC71B
RawImageDigest = 0xC71C
OriginalRawFileDigest = 0xC71D
SubTileBlockSize = 0xC71E
RowInterleaveFactor = 0xC71F
ProfileLookTableDims = 0xC725
ProfileLookTableData = 0xC726
OpcodeList1 = 0xC740
OpcodeList2 = 0xC741
OpcodeList3 = 0xC74E
NoiseProfile = 0xC761
"""Maps EXIF tags to tag names."""
TAGS = {
**{i.value: i.name for i in Base},
0x920C: "SpatialFrequencyResponse", 0x920C: "SpatialFrequencyResponse",
0x920D: "Noise",
0x9211: "ImageNumber",
0x9212: "SecurityClassification",
0x9213: "ImageHistory",
0x9214: "SubjectLocation", 0x9214: "SubjectLocation",
0x9215: "ExposureIndex", 0x9215: "ExposureIndex",
0x828E: "CFAPattern",
0x920B: "FlashEnergy",
0x9216: "TIFF/EPStandardID", 0x9216: "TIFF/EPStandardID",
0x927C: "MakerNote",
0x9286: "UserComment",
0x9290: "SubsecTime",
0x9291: "SubsecTimeOriginal",
0x9292: "SubsecTimeDigitized",
0x9400: "AmbientTemperature",
0x9401: "Humidity",
0x9402: "Pressure",
0x9403: "WaterDepth",
0x9404: "Acceleration",
0x9405: "CameraElevationAngle",
0x9C9B: "XPTitle",
0x9C9C: "XPComment",
0x9C9D: "XPAuthor",
0x9C9E: "XPKeywords",
0x9C9F: "XPSubject",
0xA000: "FlashPixVersion",
0xA001: "ColorSpace",
0xA002: "ExifImageWidth",
0xA003: "ExifImageHeight",
0xA004: "RelatedSoundFile",
0xA005: "ExifInteroperabilityOffset",
0xA20B: "FlashEnergy",
0xA20C: "SpatialFrequencyResponse",
0xA20E: "FocalPlaneXResolution",
0xA20F: "FocalPlaneYResolution",
0xA210: "FocalPlaneResolutionUnit",
0xA214: "SubjectLocation",
0xA215: "ExposureIndex",
0xA217: "SensingMethod",
0xA300: "FileSource",
0xA301: "SceneType",
0xA302: "CFAPattern",
0xA401: "CustomRendered",
0xA402: "ExposureMode",
0xA403: "WhiteBalance",
0xA404: "DigitalZoomRatio",
0xA405: "FocalLengthIn35mmFilm",
0xA406: "SceneCaptureType",
0xA407: "GainControl",
0xA408: "Contrast",
0xA409: "Saturation",
0xA40A: "Sharpness",
0xA40B: "DeviceSettingDescription",
0xA40C: "SubjectDistanceRange",
0xA420: "ImageUniqueID",
0xA430: "CameraOwnerName",
0xA431: "BodySerialNumber",
0xA432: "LensSpecification",
0xA433: "LensMake",
0xA434: "LensModel",
0xA435: "LensSerialNumber",
0xA460: "CompositeImage",
0xA461: "CompositeImageCount",
0xA462: "CompositeImageExposureTimes",
0xA500: "Gamma",
0xC4A5: "PrintImageMatching",
0xC612: "DNGVersion",
0xC613: "DNGBackwardVersion",
0xC614: "UniqueCameraModel",
0xC615: "LocalizedCameraModel",
0xC616: "CFAPlaneColor",
0xC617: "CFALayout",
0xC618: "LinearizationTable",
0xC619: "BlackLevelRepeatDim",
0xC61A: "BlackLevel",
0xC61B: "BlackLevelDeltaH",
0xC61C: "BlackLevelDeltaV",
0xC61D: "WhiteLevel",
0xC61E: "DefaultScale",
0xC61F: "DefaultCropOrigin",
0xC620: "DefaultCropSize",
0xC621: "ColorMatrix1",
0xC622: "ColorMatrix2",
0xC623: "CameraCalibration1",
0xC624: "CameraCalibration2",
0xC625: "ReductionMatrix1",
0xC626: "ReductionMatrix2",
0xC627: "AnalogBalance",
0xC628: "AsShotNeutral",
0xC629: "AsShotWhiteXY",
0xC62A: "BaselineExposure",
0xC62B: "BaselineNoise",
0xC62C: "BaselineSharpness",
0xC62D: "BayerGreenSplit",
0xC62E: "LinearResponseLimit",
0xC62F: "CameraSerialNumber",
0xC630: "LensInfo",
0xC631: "ChromaBlurRadius",
0xC632: "AntiAliasStrength",
0xC633: "ShadowScale",
0xC634: "DNGPrivateData",
0xC635: "MakerNoteSafety",
0xC65A: "CalibrationIlluminant1",
0xC65B: "CalibrationIlluminant2",
0xC65C: "BestQualityScale",
0xC65D: "RawDataUniqueID",
0xC68B: "OriginalRawFileName",
0xC68C: "OriginalRawFileData",
0xC68D: "ActiveArea",
0xC68E: "MaskedAreas",
0xC68F: "AsShotICCProfile",
0xC690: "AsShotPreProfileMatrix",
0xC691: "CurrentICCProfile",
0xC692: "CurrentPreProfileMatrix",
0xC6BF: "ColorimetricReference",
0xC6F3: "CameraCalibrationSignature",
0xC6F4: "ProfileCalibrationSignature",
0xC6F6: "AsShotProfileName",
0xC6F7: "NoiseReductionApplied",
0xC6F8: "ProfileName",
0xC6F9: "ProfileHueSatMapDims",
0xC6FA: "ProfileHueSatMapData1",
0xC6FB: "ProfileHueSatMapData2",
0xC6FC: "ProfileToneCurve",
0xC6FD: "ProfileEmbedPolicy",
0xC6FE: "ProfileCopyright",
0xC714: "ForwardMatrix1",
0xC715: "ForwardMatrix2",
0xC716: "PreviewApplicationName",
0xC717: "PreviewApplicationVersion",
0xC718: "PreviewSettingsName",
0xC719: "PreviewSettingsDigest",
0xC71A: "PreviewColorSpace",
0xC71B: "PreviewDateTime",
0xC71C: "RawImageDigest",
0xC71D: "OriginalRawFileDigest",
0xC71E: "SubTileBlockSize",
0xC71F: "RowInterleaveFactor",
0xC725: "ProfileLookTableDims",
0xC726: "ProfileLookTableData",
0xC740: "OpcodeList1",
0xC741: "OpcodeList2",
0xC74E: "OpcodeList3",
0xC761: "NoiseProfile",
} }
"""Maps EXIF tags to tag names."""
GPSTAGS = { class GPS(IntEnum):
0: "GPSVersionID", GPSVersionID = 0
1: "GPSLatitudeRef", GPSLatitudeRef = 1
2: "GPSLatitude", GPSLatitude = 2
3: "GPSLongitudeRef", GPSLongitudeRef = 3
4: "GPSLongitude", GPSLongitude = 4
5: "GPSAltitudeRef", GPSAltitudeRef = 5
6: "GPSAltitude", GPSAltitude = 6
7: "GPSTimeStamp", GPSTimeStamp = 7
8: "GPSSatellites", GPSSatellites = 8
9: "GPSStatus", GPSStatus = 9
10: "GPSMeasureMode", GPSMeasureMode = 10
11: "GPSDOP", GPSDOP = 11
12: "GPSSpeedRef", GPSSpeedRef = 12
13: "GPSSpeed", GPSSpeed = 13
14: "GPSTrackRef", GPSTrackRef = 14
15: "GPSTrack", GPSTrack = 15
16: "GPSImgDirectionRef", GPSImgDirectionRef = 16
17: "GPSImgDirection", GPSImgDirection = 17
18: "GPSMapDatum", GPSMapDatum = 18
19: "GPSDestLatitudeRef", GPSDestLatitudeRef = 19
20: "GPSDestLatitude", GPSDestLatitude = 20
21: "GPSDestLongitudeRef", GPSDestLongitudeRef = 21
22: "GPSDestLongitude", GPSDestLongitude = 22
23: "GPSDestBearingRef", GPSDestBearingRef = 23
24: "GPSDestBearing", GPSDestBearing = 24
25: "GPSDestDistanceRef", GPSDestDistanceRef = 25
26: "GPSDestDistance", GPSDestDistance = 26
27: "GPSProcessingMethod", GPSProcessingMethod = 27
28: "GPSAreaInformation", GPSAreaInformation = 28
29: "GPSDateStamp", GPSDateStamp = 29
30: "GPSDifferential", GPSDifferential = 30
31: "GPSHPositioningError", GPSHPositioningError = 31
}
"""Maps EXIF GPS tags to tag names.""" """Maps EXIF GPS tags to tag names."""
GPSTAGS = {i.value: i.name for i in GPS}
class Interop(IntEnum):
InteropIndex = 1
InteropVersion = 2
RelatedImageFileFormat = 4096
RelatedImageWidth = 4097
RleatedImageHeight = 4098
class IFD(IntEnum):
Exif = 34665
GPSInfo = 34853
Makernote = 37500
Interop = 40965
IFD1 = -1

View File

@ -15,6 +15,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
import os
from . import Image, ImageFile, ImagePalette from . import Image, ImageFile, ImagePalette
from ._binary import i16le as i16 from ._binary import i16le as i16
@ -80,11 +81,19 @@ class FliImageFile(ImageFile.ImageFile):
if i16(s, 4) == 0xF1FA: if i16(s, 4) == 0xF1FA:
# look for palette chunk # look for palette chunk
number_of_subchunks = i16(s, 6)
chunk_size = None
for _ in range(number_of_subchunks):
if chunk_size is not None:
self.fp.seek(chunk_size - 6, os.SEEK_CUR)
s = self.fp.read(6) s = self.fp.read(6)
if i16(s, 4) == 11: chunk_type = i16(s, 4)
self._palette(palette, 2) if chunk_type in (4, 11):
elif i16(s, 4) == 4: self._palette(palette, 2 if chunk_type == 11 else 0)
self._palette(palette, 0) break
chunk_size = i32(s)
if not chunk_size:
break
palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette] palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
self.palette = ImagePalette.raw("RGB", b"".join(palette)) self.palette = ImagePalette.raw("RGB", b"".join(palette))

Some files were not shown because too many files have changed in this diff Show More