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:
DEPLOY: YES
matrix:
- PYTHON: C:/Python310
- PYTHON: C:/Python311
ARCHITECTURE: x86
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
- PYTHON: C:/Python37-x64

View File

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

View File

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

View File

@ -2,7 +2,7 @@
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
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 pyroma
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
python3 -m pip install numpy
# extra test images

View File

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

View File

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

View File

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

View File

@ -15,13 +15,13 @@ jobs:
strategy:
fail-fast: false
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"]
include:
# PyPy 7.3.4+ only ships 64-bit binaries for Windows
- python-version: "pypy-3.7"
- python-version: "pypy3.8"
architecture: "x64"
- python-version: "pypy-3.8"
- python-version: "pypy3.9"
architecture: "x64"
timeout-minutes: 30
@ -65,7 +65,9 @@ jobs:
xcopy /S /Y winbuild\depends\test_images\* Tests\images\
# 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
- name: Cache build
@ -90,19 +92,28 @@ jobs:
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_zlib.cmd"
- name: Build dependencies / LibTiff
- name: Build dependencies / xz
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
if: steps.build-cache.outputs.cache-hit != 'true'
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
- name: Build dependencies / libpng
if: steps.build-cache.outputs.cache-hit != 'true'
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
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_freetype.cmd"
@ -130,7 +141,7 @@ jobs:
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_fribidi.cmd"
# trim ~150MB x 9
# trim ~150MB for each job
- name: Optimize build cache
if: steps.build-cache.outputs.cache-hit != 'true'
run: rmdir /S /Q winbuild\build\src
@ -185,16 +196,42 @@ jobs:
id: wheel
if: "github.event_name != 'pull_request'"
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
shell: cmd
- uses: actions/upload-artifact@v3
- name: Upload wheel
uses: actions/upload-artifact@v3
if: "github.event_name != 'pull_request'"
with:
name: ${{ steps.wheel.outputs.dist }}
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:
permissions:
contents: none

View File

@ -20,9 +20,9 @@ jobs:
"ubuntu-latest",
]
python-version: [
"pypy-3.8",
"pypy-3.7",
"3.11-dev",
"pypy3.9",
"pypy3.8",
"3.11",
"3.10",
"3.9",
"3.8",
@ -96,7 +96,7 @@ jobs:
path: Tests/errors
- name: Docs
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.10
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.11
run: |
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:
- repo: https://github.com/psf/black
rev: 22.8.0
rev: 22.12.0
hooks:
- id: black
args: ["--target-version", "py37"]
args: [--target-version=py37]
# Only .py files, until https://github.com/psf/black/issues/402 resolved
files: \.py$
types: []
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
rev: 5.11.1
hooks:
- 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
rev: v1.4.0
hooks:
@ -25,7 +32,7 @@ repos:
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
@ -37,16 +44,21 @@ repos:
- id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.4.0
hooks:
- id: check-merge-conflict
- id: check-json
- id: check-yaml
- repo: https://github.com/sphinx-contrib/sphinx-lint
rev: v0.6.1
rev: v0.6.7
hooks:
- id: sphinx-lint
- repo: https://github.com/tox-dev/tox-ini-fmt
rev: 0.5.2
hooks:
- id: tox-ini-fmt
ci:
autoupdate_schedule: monthly

View File

@ -2,9 +2,144 @@
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
[radarhere]

View File

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

View File

@ -53,12 +53,12 @@ inplace: clean
.PHONY: install
install:
python3 -m pip install .
python3 -m pip -v install .
python3 selftest.py
.PHONY: 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
.PHONY: debug
@ -67,7 +67,7 @@ debug:
# for our stuff, kills optimization, and redirects to dev null so we
# see any build failures.
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
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
alt="Code coverage"
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
alt="Tidelift Align"
src="https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml/badge.svg"></a>
<a href="https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:pillow"><img
alt="Fuzzing Status"
src="https://oss-fuzz-build-logs.storage.googleapis.com/badges/pillow.svg"></a>
</td>
</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
ter-x20b.pcf, from http://terminus-font.sourceforge.net/
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.

BIN
Tests/fonts/OpenSans.woff2 Normal file

Binary file not shown.

View File

@ -208,12 +208,11 @@ class PillowLeakTestCase:
# ru_maxrss
# This is the maximum resident set size utilized (in bytes).
return mem / 1024 # Kb
else:
# linux
# man 2 getrusage
# ru_maxrss (since Linux 2.6.32)
# This is the maximum resident set size used (in kilobytes).
return mem # Kb
# linux
# man 2 getrusage
# ru_maxrss (since Linux 2.6.32)
# This is the maximum resident set size used (in kilobytes).
return mem # Kb
def _test_leak(self, core):
start_mem = self._get_mem_usage()
@ -285,7 +284,7 @@ def magick_command():
if imagemagick and shutil.which(imagemagick[0]):
return imagemagick
elif graphicsmagick and shutil.which(graphicsmagick[0]):
if graphicsmagick and shutil.which(graphicsmagick[0]):
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.
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
fuzzer_basename=$(basename -s .py $fuzzer)
fuzzer_package=${fuzzer_basename}.pkg
pyinstaller \
compile_python_fuzzer $fuzzer \
--add-binary /usr/local/lib/libjpeg.so.62.3.0:. \
--add-binary /usr/local/lib/libfreetype.so.6:. \
--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/libwebpdemux.so.2:. \
--add-binary /usr/local/lib/libwebpmux.so.3:. \
--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
--add-binary /usr/local/lib/libxcb.so.1:.
done
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",
"rgb24prof.bmp",
"pal1p1.bmp",
"pal4rletrns.bmp",
"pal8offs.bmp",
"rgb24lprof.bmp",
"rgb32fakealpha.bmp",

View File

@ -553,18 +553,20 @@ def test_apng_save_disposal(tmp_path):
def test_apng_save_disposal_previous(tmp_path):
test_file = str(tmp_path / "temp.png")
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))
green = Image.new("RGBA", size, (0, 255, 0, 255))
# test OP_NONE
transparent.save(
blue.save(
test_file,
save_all=True,
append_images=[red, green],
disposal=PngImagePlugin.Disposal.OP_PREVIOUS,
)
with Image.open(test_file) as im:
assert im.getpixel((0, 0)) == (0, 0, 255, 255)
im.seek(2)
assert im.getpixel((0, 0)) == (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)
@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():
for enum, prefix in {
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:
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():
with Image.open("Tests/images/blp/blp2_raw.blp") as im:

View File

@ -176,6 +176,11 @@ def test_rle8():
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(
"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_SNORM = "Tests/images/bc5_snorm.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_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.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"))
@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():
"""Check DX10 images can be opened"""

View File

@ -4,7 +4,7 @@ import pytest
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
# save as...-> hopper.fli, default options.
@ -79,6 +79,12 @@ def test_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():
with Image.open(static_test_file) as im:
assert im.n_frames == 1

View File

@ -83,18 +83,40 @@ def test_l_mode_transparency():
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():
with Image.open("Tests/images/iss634.gif") as im:
expected_rgb_always = im.convert("RGB")
with Image.open("Tests/images/chi.gif") as im:
expected_zero = im.convert("RGB")
expected_rgb_always_rgba = im.convert("RGBA")
im.seek(1)
expected_one = im.convert("RGB")
expected_different = im.convert("RGB")
try:
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_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.LoadingStrategy.RGB_AFTER_DIFFERENT_PALETTE_ONLY
@ -105,7 +127,7 @@ def test_strategy():
im.seek(1)
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
with Image.open("Tests/images/iss634.gif") as im:
@ -769,6 +791,22 @@ def test_roundtrip_info_duration(tmp_path):
] == 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):
duration_list = [1000, 1500, 2000, 4000]
@ -837,14 +875,23 @@ def test_background(tmp_path):
im.info["background"] = 1
im.save(out)
with Image.open(out) as reread:
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"):
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)
def test_comment(tmp_path):
with Image.open(TEST_GIF) as im:

View File

@ -86,6 +86,18 @@ def test_roundtrip(mode, tmp_path):
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):
out = str(tmp_path / "temp.im")
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:
import defusedxml.ElementTree as ElementTree
from defusedxml import ElementTree
except ImportError:
ElementTree = None
@ -86,6 +86,33 @@ class TestFileJpeg:
assert len(im.applist) == 2
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):
# Test CMYK handling. Thanks to Tim and Charlie for test data,
@ -415,6 +442,13 @@ class TestFileJpeg:
info = im._getexif()
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):
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
assert im._getmp() is None

View File

@ -3,6 +3,7 @@ import io
import itertools
import os
import re
import sys
from collections import namedtuple
import pytest
@ -825,6 +826,44 @@ class TestFileLibTiff(LibTiffTestCase):
assert reloaded.mode == "F"
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):
with Image.open("Tests/images/hopper_lzw.tif") as im:
assert im.mode == "RGB"
@ -934,7 +973,7 @@ class TestFileLibTiff(LibTiffTestCase):
im.save(out, exif=tags, compression=compression)
with Image.open(out) as reloaded:
for tag in tags.keys():
for tag in tags:
assert tag not in reloaded.getexif()
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])
assert_image_equal(im, im_reloaded)
assert im_reloaded.mpinfo[45056] == b"0100"
im_reloaded.seek(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)
@pytest.mark.valgrind_known_error(reason="Temporary skip")
def test_monochrome(tmp_path):
# Arrange
mode = "1"

View File

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

View File

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

View File

@ -4,7 +4,7 @@ from io import BytesIO
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 .helper import (
@ -18,7 +18,7 @@ from .helper import (
)
try:
import defusedxml.ElementTree as ElementTree
from defusedxml import ElementTree
except ImportError:
ElementTree = None
@ -858,6 +858,19 @@ class TestFileTiff:
im.load()
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")
class TestFileTiffW32:

View File

@ -201,6 +201,22 @@ def test_writing_bytes_to_ascii(tmp_path):
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):
# Check that the tag has not been changed since this test was created
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)
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):
"""
Saving a palette-based file with transparency to WebP format

View File

@ -11,6 +11,11 @@ pytestmark = [
skip_unless_feature("webp_mux"),
]
try:
from defusedxml import ElementTree
except ImportError:
ElementTree = None
def test_read_exif_metadata():
@ -55,9 +60,7 @@ def test_write_exif_metadata():
test_buffer.seek(0)
with Image.open(test_buffer) as webp_image:
webp_exif = webp_image.info.get("exif", None)
assert webp_exif
if webp_exif:
assert webp_exif == expected_exif, "WebP EXIF didn't match"
assert webp_exif == expected_exif[6:], "WebP EXIF didn't match"
def test_read_icc_profile():
@ -112,6 +115,22 @@ def test_read_no_exif():
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")
def test_write_animated_metadata(tmp_path):
iccp_data = b"<iccp_data>"

View File

@ -8,6 +8,7 @@ import warnings
import pytest
from PIL import (
ExifTags,
Image,
ImageDraw,
ImagePalette,
@ -765,15 +766,15 @@ class TestImage:
def test_empty_exif(self):
with Image.open("Tests/images/exif.png") as im:
exif = im.getexif()
assert dict(exif) != {}
assert dict(exif)
# Test that exif data is cleared after another load
exif.load(None)
assert dict(exif) == {}
assert not dict(exif)
# Test loading just the EXIF header
exif.load(b"Exif\x00\x00")
assert dict(exif) == {}
assert not dict(exif)
@mark_if_feature_version(
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
@ -876,6 +877,18 @@ class TestImage:
reloaded_exif.load(exif.tobytes())
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):
with Image.open("Tests/images/flower.jpg") as im:
exif = im.getexif()

View File

@ -4,11 +4,10 @@ import sys
import sysconfig
import pytest
from setuptools.command.build_ext import new_compiler
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
# https://github.com/eliben/pycparser/pull/198#issuecomment-317001670
@ -131,8 +130,7 @@ class TestImageGetPixel(AccessTest):
bands = Image.getmodebands(mode)
if bands == 1:
return 1
else:
return tuple(range(1, bands + 1))
return tuple(range(1, bands + 1))
def check(self, mode, c=None):
if not c:
@ -345,13 +343,14 @@ class TestCffi(AccessTest):
@pytest.mark.parametrize("mode", ("P", "PA"))
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))
access = PyAccess.new(im, False)
access.putpixel((0, 0), color)
alpha = color[3] if len(color) == 4 and mode == "PA" else 255
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
if len(color) == 3:
color += (255,)
assert im.convert("RGBA").getpixel((0, 0)) == color
class TestImagePutPixelError(AccessTest):
@ -406,15 +405,14 @@ class TestImagePutPixelError(AccessTest):
class TestEmbeddable:
@pytest.mark.skipif(
not is_win32() or on_ci(),
reason="Failing on AppVeyor / GitHub Actions when run from subprocess, "
"not from shell",
)
@pytest.mark.xfail(reason="failing test")
@pytest.mark.skipif(not is_win32(), reason="requires Windows")
def test_embeddable(self):
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(
"""
#include "Python.h"

View File

@ -35,10 +35,13 @@ def test_toarray():
test_with_dtype(numpy.float64)
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):
numpy.array(im_truncated)
else:
with pytest.warns(UserWarning):
numpy.array(im_truncated)
def test_fromarray():

View File

@ -38,6 +38,12 @@ def test_sanity():
convert(im, output_mode)
def test_unsupported_conversion():
im = hopper()
with pytest.raises(ValueError):
im.convert("INVALID")
def test_default():
im = hopper("P")
@ -242,6 +248,17 @@ def test_p2pa_palette():
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():
# Arrange
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)
@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")
def test_stroke_multiline():
# Arrange

View File

@ -746,12 +746,14 @@ def test_variation_set_by_name(font):
_check_text(font, "Tests/images/variation_adobe.png", 11)
for name in ["Bold", b"Bold"]:
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)
_check_text(font, "Tests/images/variation_tiny.png", 40)
for name in ["200", b"200"]:
font.set_variation_by_name(name)
assert font.getname()[1] == "200"
_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.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):
@ -1040,6 +1065,25 @@ def test_colr_mask(layout_engine):
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):
with pytest.warns(DeprecationWarning):
font.getmask2("Hello world", fill=Image.core.fill)

View File

@ -1,4 +1,5 @@
import os
import shutil
import subprocess
import sys
@ -33,7 +34,9 @@ class TestImageGrab:
@pytest.mark.skipif(Image.core.HAVE_XCB, reason="tests missing XCB")
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:
ImageGrab.grab()
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):
if hasattr(im, "im"):
return f"{im.mode} {repr(im.getpixel((0, 0)))}"
else:
if isinstance(im, int):
return int(im) # hack to deal with booleans
print(im)
if isinstance(im, int):
return int(im) # hack to deal with booleans
A = Image.new("L", (1, 1), 1)

View File

@ -50,6 +50,16 @@ def test_getcolor():
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(
"index, palette",
[

View File

@ -34,7 +34,7 @@ def test_numpy_to_image():
# Check supported 1-bit integer formats
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
assert_image(to_image(numpy.uint8), "L", TEST_IMAGE_SIZE)
@ -193,7 +193,7 @@ def test_putdata():
"dtype",
(
bool,
numpy.bool8,
numpy.bool_,
numpy.int8,
numpy.int16,
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
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:
@echo "Please use \`make <target>' where <target> is one of"
@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 " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@ -39,42 +40,49 @@ help:
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
.PHONY: clean
clean:
-rm -rf $(BUILDDIR)/*
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:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b html -W --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: dirhtml
dirhtml:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml
singlehtml:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@ -82,6 +90,7 @@ htmlhelp:
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@ -92,6 +101,7 @@ qthelp:
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PillowPILfork.qhc"
.PHONY: devhelp
devhelp:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@ -102,12 +112,14 @@ devhelp:
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PillowPILfork"
@echo "# devhelp"
.PHONY: epub
epub:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: latex
latex:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@ -116,6 +128,7 @@ latex:
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@ -123,18 +136,21 @@ latexpdf:
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@ -143,6 +159,7 @@ texinfo:
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
info:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@ -150,18 +167,21 @@ info:
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
linkcheck:
$(MAKE) install-sphinx
$(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 " \
"or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest
doctest:
$(MAKE) install-sphinx
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: livehtml
livehtml: html
livereload $(BUILDDIR)/html -p 33233
.PHONY: serve
serve:
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
# ones.
extensions = [
"sphinx_copybutton",
"sphinx_issues",
"sphinx_removed_in",
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinx_copybutton",
"sphinx_inline_tabs",
"sphinx_issues",
"sphinx_removed_in",
"sphinxext.opengraph",
]

View File

@ -24,9 +24,10 @@ To get the number and names of bands in an image, use the
Modes
-----
The ``mode`` of an image is a string which defines the type and depth of a pixel in the image.
Each pixel uses the full range of the bit depth. So a 1-bit pixel has a range
of 0-1, an 8-bit pixel has a range of 0-255 and so on. The current release
The ``mode`` of an image is a string which defines the type and depth of a pixel in the
image. Each pixel uses the full range of the bit depth. So a 1-bit pixel has a range of
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:
* ``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)
* ``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)
* ``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;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.
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``,
or ``RGB`` data. 16-colour images are read as ``P`` images. 4-bit run-length encoding
is not supported. Support for reading 8-bit run-length encoding was added in Pillow
9.1.0.
or ``RGB`` data. 16-colour images are read as ``P`` images.
Support for reading 8-bit run-length encoding was added in Pillow 9.1.0.
Support for reading 4-bit run-length encoding was added in Pillow 9.3.0.
Opening
~~~~~~~
@ -56,7 +56,8 @@ The :py:meth:`~PIL.Image.open` method sets the following
:py:attr:`~PIL.Image.Image.info` properties:
**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
^^^
@ -473,6 +474,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
.. versionadded:: 2.5.0
**comment**
A comment about the image.
.. versionadded:: 9.4.0
.. note::
@ -1123,6 +1129,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
**method**
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**
The ICC Profile to include in the saved file. Only supported if
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
:alt: Tidelift
.. image:: https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml/badge.svg
:target: https://github.com/python-pillow/Pillow/actions/workflows/tidelift.yml
:alt: Tidelift Align
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/pillow.svg
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:pillow
:alt: Fuzzing Status
.. image:: https://img.shields.io/pypi/v/pillow.svg
:target: https://pypi.org/project/Pillow/

View File

@ -23,6 +23,11 @@ Pillow supports these Python versions.
:file: older-versions.csv
:header-rows: 1
.. _Linux Installation:
.. _macOS Installation:
.. _Windows Installation:
.. _FreeBSD Installation:
Basic Installation
------------------
@ -38,75 +43,73 @@ Install Pillow with :command:`pip`::
python3 -m pip install --upgrade Pillow
Windows Installation
^^^^^^^^^^^^^^^^^^^^
.. tab:: Linux
We provide Pillow binaries for Windows compiled for the matrix of
supported Pythons in both 32 and 64-bit versions in the wheel format.
These binaries include support for all optional libraries except
libimagequant and libxcb. Raqm support requires
FriBiDi to be installed separately::
We provide binaries for Linux for each of the supported Python
versions in the manylinux wheel format. These include support for all
optional libraries except libimagequant. Raqm support requires
FriBiDi to be installed separately::
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow
python3 -m pip install --upgrade pip
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
We provide binaries for macOS for each of the supported Python
versions in the wheel format. These include support for all optional
libraries except libimagequant. Raqm support requires
FriBiDi to be installed separately::
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow
.. tab:: Windows
We provide Pillow binaries for Windows compiled for the matrix of
supported Pythons in both 32 and 64-bit versions in the wheel format.
These binaries include support for all optional libraries except
libimagequant and libxcb. Raqm support requires
FriBiDi to be installed separately::
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow
To install Pillow in MSYS2, see `Building on Windows using MSYS2/MinGW`_.
.. tab:: FreeBSD
Pillow can be installed on FreeBSD via the official Ports or Packages systems:
**Ports**::
cd /usr/ports/graphics/py-pillow && make install clean
**Packages**::
pkg install py38-pillow
.. note::
The `Pillow FreeBSD port
<https://www.freshports.org/graphics/py-pillow/>`_ and packages
are tested by the ports team with all supported FreeBSD versions.
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
FriBiDi to be installed separately::
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow
Linux Installation
^^^^^^^^^^^^^^^^^^
We provide binaries for Linux for each of the supported Python
versions in the manylinux wheel format. These include support for all
optional libraries except libimagequant. Raqm support requires
FriBiDi to be installed separately::
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow
Most major Linux distributions, including Fedora, Ubuntu and ArchLinux
also include Pillow in packages that previously contained PIL e.g.
``python-imaging``. Debian splits it into two packages, ``python3-pil``
and ``python3-pil.imagetk``.
FreeBSD Installation
^^^^^^^^^^^^^^^^^^^^
Pillow can be installed on FreeBSD via the official Ports or Packages systems:
**Ports**::
cd /usr/ports/graphics/py-pillow && make install clean
**Packages**::
pkg install py38-pillow
.. note::
The `Pillow FreeBSD port
<https://www.freshports.org/graphics/py-pillow/>`_ and packages
are tested by the ports team with all supported FreeBSD versions.
.. _Building on Linux:
.. _Building on macOS:
.. _Building on Windows:
.. _Building on Windows using MSYS2/MinGW:
.. _Building on FreeBSD:
.. _Building on Android:
Building From Source
--------------------
Download and extract the `compressed archive from PyPI`_.
.. _compressed archive from PyPI: https://pypi.org/project/Pillow/
.. _external-libraries:
External Libraries
@ -147,7 +150,7 @@ Many of Pillow's features require external libraries:
* **littlecms** provides color management
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
above uses liblcms2. Tested with **1.19** and **2.7-2.13.1**.
above uses liblcms2. Tested with **1.19** and **2.7-2.14**.
* **libwebp** provides the WebP format.
@ -191,7 +194,141 @@ Many of Pillow's features require external libraries:
* **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 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 with newly installed external libraries.
If you would like to install from a local copy of the source code instead, you
can clone from GitHub with ``git clone https://github.com/python-pillow/Pillow``
or download and extract the `compressed archive from PyPI`_.
After navigating to the Pillow directory, run::
python3 -m pip install --upgrade pip
python3 -m pip install .
.. _compressed archive from PyPI: https://pypi.org/project/Pillow/#files
Build Options
^^^^^^^^^^^^^
"""""""""""""
* Environment variable: ``MAX_CONCURRENCY=n``. Pillow can use
multiprocessing to build the extension. Setting ``MAX_CONCURRENCY``
@ -256,157 +403,6 @@ Sample usage::
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
----------------
@ -440,10 +436,10 @@ These platforms are built and tested for every change.
+----------------------------------+----------------------------+---------------------+
| Debian 11 Bullseye | 3.9 | x86 |
+----------------------------------+----------------------------+---------------------+
| Fedora 35 | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 36 | 3.10 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Fedora 37 | 3.11 | x86-64 |
+----------------------------------+----------------------------+---------------------+
| Gentoo | 3.9 | 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.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 |
| | | 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 |
| +---------------------------+------------------+--------------+
| | 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 | |
+----------------------------------+---------------------------+------------------+--------------+

View File

@ -4,14 +4,48 @@
:py:mod:`~PIL.ExifTags` Module
==============================
The :py:mod:`~PIL.ExifTags` module exposes two dictionaries which
provide constants and clear-text names for various well-known EXIF tags.
The :py:mod:`~PIL.ExifTags` module exposes several ``enum.IntEnum`` classes
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
:type: dict
The TAGS dictionary maps 16-bit integer EXIF tag enumerations to
descriptive string names. For instance:
descriptive string names. For instance:
>>> from PIL.ExifTags import TAGS
>>> TAGS[0x010e]
@ -20,8 +54,8 @@ provide constants and clear-text names for various well-known EXIF tags.
.. py:data:: GPSTAGS
:type: dict
The GPSTAGS dictionary maps 8-bit integer EXIF gps enumerations to
descriptive string names. For instance:
The GPSTAGS dictionary maps 8-bit integer EXIF GPS enumerations to
descriptive string names. For instance:
>>> from PIL.ExifTags import GPSTAGS
>>> GPSTAGS[20]

View File

@ -139,17 +139,50 @@ Functions
must be the same as the image mode. If omitted, the mode
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
-------
.. 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::
from PIL import ImageDraw, ImageFont
ImageDraw.ImageDraw.font = ImageFont.truetype("Tests/fonts/FreeMono.ttf")
If the current default font is ``None``,
it is initialized with :py:func:`.ImageFont.load_default`.
: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`.
ImageShow temporary files on Unix
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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

View File

@ -1,28 +1,6 @@
9.3.0
-----
Backwards Incompatible Changes
==============================
TODO
^^^^
Deprecations
============
TODO
^^^^
TODO
API Changes
===========
TODO
^^^^
TODO
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, ...])
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
========
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
=============
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::
:maxdepth: 2
9.4.0
9.3.0
9.2.0
9.1.1

View File

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

View File

@ -15,15 +15,13 @@ import subprocess
import sys
import warnings
from setuptools import Extension
from setuptools import __version__ as setuptools_version
from setuptools import setup
from setuptools import Extension, setup
from setuptools.command.build_ext import build_ext
def get_version():
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"))
return locals()["__version__"]
@ -852,7 +850,6 @@ class pil_build_ext(build_ext):
sys.platform == "win32"
and sys.version_info < (3, 9)
and not (PLATFORM_PYPY or PLATFORM_MINGW)
and int(setuptools_version.split(".")[0]) < 60
):
defs.append(("PILLOW_VERSION", f'"\\"{PILLOW_VERSION}\\""'))
else:

View File

@ -373,8 +373,11 @@ class BLP1Decoder(_BLPBaseDecoder):
data = BytesIO(data)
image = JpegImageFile(data)
Image._decompression_bomb_check(image.size)
image.mode = "RGB"
image.tile = [("jpeg", (0, 0) + self.size, 0, ("BGRX", ""))]
if image.mode == "CMYK":
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())

View File

@ -211,7 +211,7 @@ class BmpImageFile(ImageFile.ImageFile):
elif file_info["compression"] == self.RAW:
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
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"
else:
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
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 = [
(
decoder_name,
(0, 0, file_info["width"], file_info["height"]),
offset or self.fp.tell(),
(
raw_mode,
((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3),
file_info["direction"],
),
tuple(args),
)
]
@ -280,6 +282,7 @@ class BmpRleDecoder(ImageFile.PyDecoder):
_pulls_fd = True
def decode(self, buffer):
rle4 = self.args[1]
data = bytearray()
x = 0
while len(data) < self.state.xsize * self.state.ysize:
@ -293,7 +296,16 @@ class BmpRleDecoder(ImageFile.PyDecoder):
if x + num_pixels > self.state.xsize:
# Too much data for row
num_pixels = max(0, self.state.xsize - x)
data += byte * num_pixels
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
x += num_pixels
else:
if byte[0] == 0:
@ -314,9 +326,18 @@ class BmpRleDecoder(ImageFile.PyDecoder):
x = len(data) % self.state.xsize
else:
# absolute mode
bytes_read = self.fd.read(byte[0])
data += bytes_read
if len(bytes_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
if len(bytes_read) < byte_count:
break
x += byte[0]

View File

@ -101,6 +101,8 @@ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
DXGI_FORMAT_BC5_TYPELESS = 82
DXGI_FORMAT_BC5_UNORM = 83
DXGI_FORMAT_BC5_SNORM = 84
DXGI_FORMAT_BC6H_UF16 = 95
DXGI_FORMAT_BC6H_SF16 = 96
DXGI_FORMAT_BC7_TYPELESS = 97
DXGI_FORMAT_BC7_UNORM = 98
DXGI_FORMAT_BC7_UNORM_SRGB = 99
@ -181,6 +183,14 @@ class DdsImageFile(ImageFile.ImageFile):
self.pixel_format = "BC5S"
n = 5
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):
self.pixel_format = "BC7"
n = 7

View File

@ -14,318 +14,343 @@ This module provides constants and clear-text names for various
well-known EXIF tags.
"""
from enum import IntEnum
TAGS = {
class Base(IntEnum):
# possibly incomplete
0x0001: "InteropIndex",
0x000B: "ProcessingSoftware",
0x00FE: "NewSubfileType",
0x00FF: "SubfileType",
0x0100: "ImageWidth",
0x0101: "ImageLength",
0x0102: "BitsPerSample",
0x0103: "Compression",
0x0106: "PhotometricInterpretation",
0x0107: "Thresholding",
0x0108: "CellWidth",
0x0109: "CellLength",
0x010A: "FillOrder",
0x010D: "DocumentName",
0x010E: "ImageDescription",
0x010F: "Make",
0x0110: "Model",
0x0111: "StripOffsets",
0x0112: "Orientation",
0x0115: "SamplesPerPixel",
0x0116: "RowsPerStrip",
0x0117: "StripByteCounts",
0x0118: "MinSampleValue",
0x0119: "MaxSampleValue",
0x011A: "XResolution",
0x011B: "YResolution",
0x011C: "PlanarConfiguration",
0x011D: "PageName",
0x0120: "FreeOffsets",
0x0121: "FreeByteCounts",
0x0122: "GrayResponseUnit",
0x0123: "GrayResponseCurve",
0x0124: "T4Options",
0x0125: "T6Options",
0x0128: "ResolutionUnit",
0x0129: "PageNumber",
0x012D: "TransferFunction",
0x0131: "Software",
0x0132: "DateTime",
0x013B: "Artist",
0x013C: "HostComputer",
0x013D: "Predictor",
0x013E: "WhitePoint",
0x013F: "PrimaryChromaticities",
0x0140: "ColorMap",
0x0141: "HalftoneHints",
0x0142: "TileWidth",
0x0143: "TileLength",
0x0144: "TileOffsets",
0x0145: "TileByteCounts",
0x014A: "SubIFDs",
0x014C: "InkSet",
0x014D: "InkNames",
0x014E: "NumberOfInks",
0x0150: "DotRange",
0x0151: "TargetPrinter",
0x0152: "ExtraSamples",
0x0153: "SampleFormat",
0x0154: "SMinSampleValue",
0x0155: "SMaxSampleValue",
0x0156: "TransferRange",
0x0157: "ClipPath",
0x0158: "XClipPathUnits",
0x0159: "YClipPathUnits",
0x015A: "Indexed",
0x015B: "JPEGTables",
0x015F: "OPIProxy",
0x0200: "JPEGProc",
0x0201: "JpegIFOffset",
0x0202: "JpegIFByteCount",
0x0203: "JpegRestartInterval",
0x0205: "JpegLosslessPredictors",
0x0206: "JpegPointTransforms",
0x0207: "JpegQTables",
0x0208: "JpegDCTables",
0x0209: "JpegACTables",
0x0211: "YCbCrCoefficients",
0x0212: "YCbCrSubSampling",
0x0213: "YCbCrPositioning",
0x0214: "ReferenceBlackWhite",
0x02BC: "XMLPacket",
0x1000: "RelatedImageFileFormat",
0x1001: "RelatedImageWidth",
0x1002: "RelatedImageLength",
0x4746: "Rating",
0x4749: "RatingPercent",
0x800D: "ImageID",
0x828D: "CFARepeatPatternDim",
0x828E: "CFAPattern",
0x828F: "BatteryLevel",
0x8298: "Copyright",
0x829A: "ExposureTime",
0x829D: "FNumber",
0x83BB: "IPTCNAA",
0x8649: "ImageResources",
0x8769: "ExifOffset",
0x8773: "InterColorProfile",
0x8822: "ExposureProgram",
0x8824: "SpectralSensitivity",
0x8825: "GPSInfo",
0x8827: "ISOSpeedRatings",
0x8828: "OECF",
0x8829: "Interlace",
0x882A: "TimeZoneOffset",
0x882B: "SelfTimerMode",
0x8830: "SensitivityType",
0x8831: "StandardOutputSensitivity",
0x8832: "RecommendedExposureIndex",
0x8833: "ISOSpeed",
0x8834: "ISOSpeedLatitudeyyy",
0x8835: "ISOSpeedLatitudezzz",
0x9000: "ExifVersion",
0x9003: "DateTimeOriginal",
0x9004: "DateTimeDigitized",
0x9010: "OffsetTime",
0x9011: "OffsetTimeOriginal",
0x9012: "OffsetTimeDigitized",
0x9101: "ComponentsConfiguration",
0x9102: "CompressedBitsPerPixel",
0x9201: "ShutterSpeedValue",
0x9202: "ApertureValue",
0x9203: "BrightnessValue",
0x9204: "ExposureBiasValue",
0x9205: "MaxApertureValue",
0x9206: "SubjectDistance",
0x9207: "MeteringMode",
0x9208: "LightSource",
0x9209: "Flash",
0x920A: "FocalLength",
0x920B: "FlashEnergy",
InteropIndex = 0x0001
ProcessingSoftware = 0x000B
NewSubfileType = 0x00FE
SubfileType = 0x00FF
ImageWidth = 0x0100
ImageLength = 0x0101
BitsPerSample = 0x0102
Compression = 0x0103
PhotometricInterpretation = 0x0106
Thresholding = 0x0107
CellWidth = 0x0108
CellLength = 0x0109
FillOrder = 0x010A
DocumentName = 0x010D
ImageDescription = 0x010E
Make = 0x010F
Model = 0x0110
StripOffsets = 0x0111
Orientation = 0x0112
SamplesPerPixel = 0x0115
RowsPerStrip = 0x0116
StripByteCounts = 0x0117
MinSampleValue = 0x0118
MaxSampleValue = 0x0119
XResolution = 0x011A
YResolution = 0x011B
PlanarConfiguration = 0x011C
PageName = 0x011D
FreeOffsets = 0x0120
FreeByteCounts = 0x0121
GrayResponseUnit = 0x0122
GrayResponseCurve = 0x0123
T4Options = 0x0124
T6Options = 0x0125
ResolutionUnit = 0x0128
PageNumber = 0x0129
TransferFunction = 0x012D
Software = 0x0131
DateTime = 0x0132
Artist = 0x013B
HostComputer = 0x013C
Predictor = 0x013D
WhitePoint = 0x013E
PrimaryChromaticities = 0x013F
ColorMap = 0x0140
HalftoneHints = 0x0141
TileWidth = 0x0142
TileLength = 0x0143
TileOffsets = 0x0144
TileByteCounts = 0x0145
SubIFDs = 0x014A
InkSet = 0x014C
InkNames = 0x014D
NumberOfInks = 0x014E
DotRange = 0x0150
TargetPrinter = 0x0151
ExtraSamples = 0x0152
SampleFormat = 0x0153
SMinSampleValue = 0x0154
SMaxSampleValue = 0x0155
TransferRange = 0x0156
ClipPath = 0x0157
XClipPathUnits = 0x0158
YClipPathUnits = 0x0159
Indexed = 0x015A
JPEGTables = 0x015B
OPIProxy = 0x015F
JPEGProc = 0x0200
JpegIFOffset = 0x0201
JpegIFByteCount = 0x0202
JpegRestartInterval = 0x0203
JpegLosslessPredictors = 0x0205
JpegPointTransforms = 0x0206
JpegQTables = 0x0207
JpegDCTables = 0x0208
JpegACTables = 0x0209
YCbCrCoefficients = 0x0211
YCbCrSubSampling = 0x0212
YCbCrPositioning = 0x0213
ReferenceBlackWhite = 0x0214
XMLPacket = 0x02BC
RelatedImageFileFormat = 0x1000
RelatedImageWidth = 0x1001
RelatedImageLength = 0x1002
Rating = 0x4746
RatingPercent = 0x4749
ImageID = 0x800D
CFARepeatPatternDim = 0x828D
BatteryLevel = 0x828F
Copyright = 0x8298
ExposureTime = 0x829A
FNumber = 0x829D
IPTCNAA = 0x83BB
ImageResources = 0x8649
ExifOffset = 0x8769
InterColorProfile = 0x8773
ExposureProgram = 0x8822
SpectralSensitivity = 0x8824
GPSInfo = 0x8825
ISOSpeedRatings = 0x8827
OECF = 0x8828
Interlace = 0x8829
TimeZoneOffset = 0x882A
SelfTimerMode = 0x882B
SensitivityType = 0x8830
StandardOutputSensitivity = 0x8831
RecommendedExposureIndex = 0x8832
ISOSpeed = 0x8833
ISOSpeedLatitudeyyy = 0x8834
ISOSpeedLatitudezzz = 0x8835
ExifVersion = 0x9000
DateTimeOriginal = 0x9003
DateTimeDigitized = 0x9004
OffsetTime = 0x9010
OffsetTimeOriginal = 0x9011
OffsetTimeDigitized = 0x9012
ComponentsConfiguration = 0x9101
CompressedBitsPerPixel = 0x9102
ShutterSpeedValue = 0x9201
ApertureValue = 0x9202
BrightnessValue = 0x9203
ExposureBiasValue = 0x9204
MaxApertureValue = 0x9205
SubjectDistance = 0x9206
MeteringMode = 0x9207
LightSource = 0x9208
Flash = 0x9209
FocalLength = 0x920A
Noise = 0x920D
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",
0x920D: "Noise",
0x9211: "ImageNumber",
0x9212: "SecurityClassification",
0x9213: "ImageHistory",
0x9214: "SubjectLocation",
0x9215: "ExposureIndex",
0x828E: "CFAPattern",
0x920B: "FlashEnergy",
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 = {
0: "GPSVersionID",
1: "GPSLatitudeRef",
2: "GPSLatitude",
3: "GPSLongitudeRef",
4: "GPSLongitude",
5: "GPSAltitudeRef",
6: "GPSAltitude",
7: "GPSTimeStamp",
8: "GPSSatellites",
9: "GPSStatus",
10: "GPSMeasureMode",
11: "GPSDOP",
12: "GPSSpeedRef",
13: "GPSSpeed",
14: "GPSTrackRef",
15: "GPSTrack",
16: "GPSImgDirectionRef",
17: "GPSImgDirection",
18: "GPSMapDatum",
19: "GPSDestLatitudeRef",
20: "GPSDestLatitude",
21: "GPSDestLongitudeRef",
22: "GPSDestLongitude",
23: "GPSDestBearingRef",
24: "GPSDestBearing",
25: "GPSDestDistanceRef",
26: "GPSDestDistance",
27: "GPSProcessingMethod",
28: "GPSAreaInformation",
29: "GPSDateStamp",
30: "GPSDifferential",
31: "GPSHPositioningError",
}
class GPS(IntEnum):
GPSVersionID = 0
GPSLatitudeRef = 1
GPSLatitude = 2
GPSLongitudeRef = 3
GPSLongitude = 4
GPSAltitudeRef = 5
GPSAltitude = 6
GPSTimeStamp = 7
GPSSatellites = 8
GPSStatus = 9
GPSMeasureMode = 10
GPSDOP = 11
GPSSpeedRef = 12
GPSSpeed = 13
GPSTrackRef = 14
GPSTrack = 15
GPSImgDirectionRef = 16
GPSImgDirection = 17
GPSMapDatum = 18
GPSDestLatitudeRef = 19
GPSDestLatitude = 20
GPSDestLongitudeRef = 21
GPSDestLongitude = 22
GPSDestBearingRef = 23
GPSDestBearing = 24
GPSDestDistanceRef = 25
GPSDestDistance = 26
GPSProcessingMethod = 27
GPSAreaInformation = 28
GPSDateStamp = 29
GPSDifferential = 30
GPSHPositioningError = 31
"""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.
#
import os
from . import Image, ImageFile, ImagePalette
from ._binary import i16le as i16
@ -80,11 +81,19 @@ class FliImageFile(ImageFile.ImageFile):
if i16(s, 4) == 0xF1FA:
# look for palette chunk
s = self.fp.read(6)
if i16(s, 4) == 11:
self._palette(palette, 2)
elif i16(s, 4) == 4:
self._palette(palette, 0)
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)
chunk_type = i16(s, 4)
if chunk_type in (4, 11):
self._palette(palette, 2 if chunk_type == 11 else 0)
break
chunk_size = i32(s)
if not chunk_size:
break
palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in 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