Merge branch 'python-pillow'
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
4
.github/workflows/lint.yml
vendored
|
@ -5,7 +5,7 @@ on: [push, pull_request, workflow_dispatch]
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
|
3
.github/workflows/macos-install.sh
vendored
|
@ -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
|
||||
|
|
2
.github/workflows/stale.yml
vendored
|
@ -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"
|
||||
|
|
10
.github/workflows/test-cygwin.yml
vendored
|
@ -5,7 +5,7 @@ on: [push, pull_request, workflow_dispatch]
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
@ -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: |
|
||||
|
|
4
.github/workflows/test-docker.yml
vendored
|
@ -5,7 +5,7 @@ on: [push, pull_request, workflow_dispatch]
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
@ -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,
|
||||
|
|
57
.github/workflows/test-windows.yml
vendored
|
@ -5,7 +5,7 @@ on: [push, pull_request, workflow_dispatch]
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
@ -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
|
||||
|
|
10
.github/workflows/test.yml
vendored
|
@ -5,7 +5,7 @@ on: [push, pull_request, workflow_dispatch]
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
|
36
.github/workflows/tidelift.yml
vendored
|
@ -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
|
|
@ -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,10 +32,11 @@ 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]
|
||||
additional_dependencies:
|
||||
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat]
|
||||
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.9.0
|
||||
|
@ -37,16 +45,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
|
||||
|
|
131
CHANGES.rst
|
@ -2,9 +2,138 @@
|
|||
Changelog (Pillow)
|
||||
==================
|
||||
|
||||
9.3.0 (unreleased)
|
||||
9.4.0 (unreleased)
|
||||
------------------
|
||||
|
||||
- Improve exception traceback readability #6836
|
||||
[hugovk, radarhere]
|
||||
|
||||
- Do not attempt to read IFD1 if absent #6840
|
||||
[radarhere]
|
||||
|
||||
- Fixed writing int as ASCII tag #6800
|
||||
[radarhere]
|
||||
|
||||
- If available, use wl-paste or xclip for grabclipboard() on Linux #6783
|
||||
[radarhere]
|
||||
|
||||
- Added signed option when saving JPEG2000 images #6709
|
||||
[radarhere]
|
||||
|
||||
- Patch OpenJPEG to include ARM64 fix #6718
|
||||
[radarhere]
|
||||
|
||||
- Added support for I;16 modes in putdata() #6825
|
||||
[radarhere]
|
||||
|
||||
- Added conversion from RGBa to RGB #6708
|
||||
[radarhere]
|
||||
|
||||
- Added DDS support for uncompressed L and LA images #6820
|
||||
[radarhere, REDxEYE]
|
||||
|
||||
- Added LightSource tag values to ExifTags #6749
|
||||
[radarhere]
|
||||
|
||||
- Fixed PyAccess after changing ICO size #6821
|
||||
[radarhere]
|
||||
|
||||
- Do not use EXIF from info when saving PNG images #6819
|
||||
[radarhere]
|
||||
|
||||
- Fixed saving EXIF data to MPO #6817
|
||||
[radarhere]
|
||||
|
||||
- Added Exif hide_offsets() #6762
|
||||
[radarhere]
|
||||
|
||||
- Only compare to previous frame when checking for duplicate GIF frames while saving #6787
|
||||
[radarhere]
|
||||
|
||||
- Always initialize all plugins in registered_extensions() #6811
|
||||
[radarhere]
|
||||
|
||||
- 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]
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
6
Makefile
|
@ -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
|
@ -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
|
@ -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": {}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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
BIN
Tests/images/duplicate_frame.gif
Normal file
After Width: | Height: | Size: 138 B |
BIN
Tests/images/flower_thumbnail.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
Tests/images/hopper_lzma.tif
Normal file
BIN
Tests/images/hopper_webp.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
Tests/images/hopper_webp.tif
Normal file
BIN
Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif
Normal file
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
BIN
Tests/images/test_woff2.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 807 B After Width: | Height: | Size: 809 B |
BIN
Tests/images/uncompressed_l.dds
Normal file
BIN
Tests/images/uncompressed_l.png
Normal file
After Width: | Height: | Size: 861 B |
BIN
Tests/images/uncompressed_la.dds
Normal file
BIN
Tests/images/uncompressed_la.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -19,29 +19,17 @@ 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:. \
|
||||
--add-binary /usr/local/lib/libopenjp2.so.7:. \
|
||||
--add-binary /usr/local/lib/libpng16.so.16:. \
|
||||
--add-binary /usr/local/lib/libtiff.so.5:. \
|
||||
--add-binary /usr/local/lib/libtiff.so.6:. \
|
||||
--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 -@
|
||||
|
|
|
@ -35,6 +35,7 @@ def test_questionable():
|
|||
"pal8os2v2.bmp",
|
||||
"rgb24prof.bmp",
|
||||
"pal1p1.bmp",
|
||||
"pal4rletrns.bmp",
|
||||
"pal8offs.bmp",
|
||||
"rgb24lprof.bmp",
|
||||
"rgb32fakealpha.bmp",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -22,6 +22,8 @@ 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"
|
||||
TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB = "Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.dds"
|
||||
TEST_FILE_UNCOMPRESSED_L = "Tests/images/uncompressed_l.dds"
|
||||
TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA = "Tests/images/uncompressed_la.dds"
|
||||
TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/hopper.dds"
|
||||
TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
|
||||
|
||||
|
@ -194,26 +196,24 @@ def test_unimplemented_dxgi_format():
|
|||
pass
|
||||
|
||||
|
||||
def test_uncompressed_rgb():
|
||||
"""Check uncompressed RGB images can be opened"""
|
||||
@pytest.mark.parametrize(
|
||||
("mode", "size", "test_file"),
|
||||
[
|
||||
("L", (128, 128), TEST_FILE_UNCOMPRESSED_L),
|
||||
("LA", (128, 128), TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA),
|
||||
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_RGB),
|
||||
("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA),
|
||||
],
|
||||
)
|
||||
def test_uncompressed(mode, size, test_file):
|
||||
"""Check uncompressed images can be opened"""
|
||||
|
||||
# convert -format dds -define dds:compression=none hopper.jpg hopper.dds
|
||||
with Image.open(TEST_FILE_UNCOMPRESSED_RGB) as im:
|
||||
with Image.open(test_file) as im:
|
||||
assert im.format == "DDS"
|
||||
assert im.mode == "RGB"
|
||||
assert im.size == (128, 128)
|
||||
assert im.mode == mode
|
||||
assert im.size == size
|
||||
|
||||
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
||||
|
||||
# Test image with alpha
|
||||
with Image.open(TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA) as im:
|
||||
assert im.format == "DDS"
|
||||
assert im.mode == "RGBA"
|
||||
assert im.size == (800, 600)
|
||||
|
||||
assert_image_equal_tofile(
|
||||
im, TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA.replace(".dds", ".png")
|
||||
)
|
||||
assert_image_equal_tofile(im, test_file.replace(".dds", ".png"))
|
||||
|
||||
|
||||
def test__accept_true():
|
||||
|
@ -305,6 +305,8 @@ def test_save_unsupported_mode(tmp_path):
|
|||
@pytest.mark.parametrize(
|
||||
("mode", "test_file"),
|
||||
[
|
||||
("L", "Tests/images/linear_gradient.png"),
|
||||
("LA", "Tests/images/uncompressed_la.png"),
|
||||
("RGB", "Tests/images/hopper.png"),
|
||||
("RGBA", "Tests/images/pil123rgba.png"),
|
||||
],
|
||||
|
|
|
@ -677,6 +677,24 @@ def test_dispose2_background(tmp_path):
|
|||
assert im.getpixel((0, 0)) == (255, 0, 0)
|
||||
|
||||
|
||||
def test_dispose2_background_frame(tmp_path):
|
||||
out = str(tmp_path / "temp.gif")
|
||||
|
||||
im_list = [Image.new("RGBA", (1, 20))]
|
||||
|
||||
different_frame = Image.new("RGBA", (1, 20))
|
||||
different_frame.putpixel((0, 10), (255, 0, 0, 255))
|
||||
im_list.append(different_frame)
|
||||
|
||||
# Frame that matches the background
|
||||
im_list.append(Image.new("RGBA", (1, 20)))
|
||||
|
||||
im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2)
|
||||
|
||||
with Image.open(out) as im:
|
||||
assert im.n_frames == 3
|
||||
|
||||
|
||||
def test_transparency_in_second_frame(tmp_path):
|
||||
out = str(tmp_path / "temp.gif")
|
||||
with Image.open("Tests/images/different_transparency.gif") as im:
|
||||
|
@ -791,6 +809,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]
|
||||
|
||||
|
@ -859,14 +893,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:
|
||||
|
|
|
@ -71,6 +71,19 @@ def test_save_to_bytes():
|
|||
)
|
||||
|
||||
|
||||
def test_getpixel(tmp_path):
|
||||
temp_file = str(tmp_path / "temp.ico")
|
||||
|
||||
im = hopper()
|
||||
im.save(temp_file, "ico", sizes=[(32, 32), (64, 64)])
|
||||
|
||||
with Image.open(temp_file) as reloaded:
|
||||
reloaded.load()
|
||||
reloaded.size = (32, 32)
|
||||
|
||||
assert reloaded.getpixel((0, 0)) == (18, 20, 62)
|
||||
|
||||
|
||||
def test_no_duplicates(tmp_path):
|
||||
temp_file = str(tmp_path / "temp.ico")
|
||||
temp_file2 = str(tmp_path / "temp2.ico")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -252,6 +252,20 @@ def test_mct():
|
|||
assert_image_similar(im, jp2, 1.0e-3)
|
||||
|
||||
|
||||
def test_sgnd(tmp_path):
|
||||
outfile = str(tmp_path / "temp.jp2")
|
||||
|
||||
im = Image.new("L", (1, 1))
|
||||
im.save(outfile)
|
||||
with Image.open(outfile) as reloaded:
|
||||
assert reloaded.getpixel((0, 0)) == 0
|
||||
|
||||
im = Image.new("L", (1, 1))
|
||||
im.save(outfile, signed=True)
|
||||
with Image.open(outfile) as reloaded_signed:
|
||||
assert reloaded_signed.getpixel((0, 0)) == 128
|
||||
|
||||
|
||||
def test_rgba():
|
||||
# Arrange
|
||||
with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -80,7 +80,10 @@ def test_app(test_file):
|
|||
|
||||
@pytest.mark.parametrize("test_file", test_files)
|
||||
def test_exif(test_file):
|
||||
with Image.open(test_file) as im:
|
||||
with Image.open(test_file) as im_original:
|
||||
im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif())
|
||||
|
||||
for im in (im_original, im_reloaded):
|
||||
info = im._getexif()
|
||||
assert info[272] == "Nintendo 3DS"
|
||||
assert info[296] == 2
|
||||
|
@ -268,6 +271,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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -706,10 +706,18 @@ class TestFilePng:
|
|||
assert exif[274] == 3
|
||||
|
||||
def test_exif_save(self, tmp_path):
|
||||
# Test exif is not saved from info
|
||||
test_file = str(tmp_path / "temp.png")
|
||||
with Image.open("Tests/images/exif.png") as im:
|
||||
test_file = str(tmp_path / "temp.png")
|
||||
im.save(test_file)
|
||||
|
||||
with Image.open(test_file) as reloaded:
|
||||
assert reloaded._getexif() is None
|
||||
|
||||
# Test passing in exif
|
||||
with Image.open("Tests/images/exif.png") as im:
|
||||
im.save(test_file, exif=im.getexif())
|
||||
|
||||
with Image.open(test_file) as reloaded:
|
||||
exif = reloaded._getexif()
|
||||
assert exif[274] == 1
|
||||
|
@ -720,7 +728,7 @@ class TestFilePng:
|
|||
def test_exif_from_jpg(self, tmp_path):
|
||||
with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
|
||||
test_file = str(tmp_path / "temp.png")
|
||||
im.save(test_file)
|
||||
im.save(test_file, exif=im.getexif())
|
||||
|
||||
with Image.open(test_file) as reloaded:
|
||||
exif = reloaded._getexif()
|
||||
|
|
|
@ -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 (
|
||||
|
@ -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:
|
||||
|
|
|
@ -185,20 +185,37 @@ def test_iptc(tmp_path):
|
|||
im.save(out)
|
||||
|
||||
|
||||
def test_writing_bytes_to_ascii(tmp_path):
|
||||
im = hopper()
|
||||
@pytest.mark.parametrize("value, expected", ((b"test", "test"), (1, "1")))
|
||||
def test_writing_other_types_to_ascii(value, expected, tmp_path):
|
||||
info = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
|
||||
tag = TiffTags.TAGS_V2[271]
|
||||
assert tag.type == TiffTags.ASCII
|
||||
|
||||
info[271] = b"test"
|
||||
info[271] = value
|
||||
|
||||
im = hopper()
|
||||
out = str(tmp_path / "temp.tiff")
|
||||
im.save(out, tiffinfo=info)
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert reloaded.tag_v2[271] == expected
|
||||
|
||||
|
||||
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[271] == "test"
|
||||
assert reloaded.tag_v2[700] == b"\x01"
|
||||
|
||||
|
||||
def test_undefined_zero(tmp_path):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -11,6 +11,11 @@ pytestmark = [
|
|||
skip_unless_feature("webp_mux"),
|
||||
]
|
||||
|
||||
try:
|
||||
from defusedxml import ElementTree
|
||||
except ImportError:
|
||||
ElementTree = None
|
||||
|
||||
|
||||
def test_read_exif_metadata():
|
||||
|
||||
|
@ -110,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>"
|
||||
|
|
|
@ -7,7 +7,14 @@ import warnings
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageDraw, ImagePalette, UnidentifiedImageError, features
|
||||
from PIL import (
|
||||
ExifTags,
|
||||
Image,
|
||||
ImageDraw,
|
||||
ImagePalette,
|
||||
UnidentifiedImageError,
|
||||
features,
|
||||
)
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -394,8 +401,6 @@ class TestImage:
|
|||
def test_registered_extensions_uninitialized(self):
|
||||
# Arrange
|
||||
Image._initialized = 0
|
||||
extension = Image.EXTENSION
|
||||
Image.EXTENSION = {}
|
||||
|
||||
# Act
|
||||
Image.registered_extensions()
|
||||
|
@ -403,10 +408,6 @@ class TestImage:
|
|||
# Assert
|
||||
assert Image._initialized == 2
|
||||
|
||||
# Restore the original state and assert
|
||||
Image.EXTENSION = extension
|
||||
assert Image.EXTENSION
|
||||
|
||||
def test_registered_extensions(self):
|
||||
# Arrange
|
||||
# Open an image to trigger plugin registration
|
||||
|
@ -808,6 +809,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()
|
||||
|
@ -838,6 +851,31 @@ class TestImage:
|
|||
34665: 196,
|
||||
}
|
||||
|
||||
def test_exif_hide_offsets(self):
|
||||
with Image.open("Tests/images/flower.jpg") as im:
|
||||
exif = im.getexif()
|
||||
|
||||
# Check offsets are present initially
|
||||
assert 0x8769 in exif
|
||||
for tag in (0xA005, 0x927C):
|
||||
assert tag in exif.get_ifd(0x8769)
|
||||
assert exif.get_ifd(0xA005)
|
||||
loaded_exif = exif
|
||||
|
||||
with Image.open("Tests/images/flower.jpg") as im:
|
||||
new_exif = im.getexif()
|
||||
|
||||
for exif in (loaded_exif, new_exif):
|
||||
exif.hide_offsets()
|
||||
|
||||
# Assert they are hidden afterwards,
|
||||
# but that the IFDs are still available
|
||||
assert 0x8769 not in exif
|
||||
assert exif.get_ifd(0x8769)
|
||||
for tag in (0xA005, 0x927C):
|
||||
assert tag not in exif.get_ifd(0x8769)
|
||||
assert exif.get_ifd(0xA005)
|
||||
|
||||
@pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0)))
|
||||
def test_zero_tobytes(self, size):
|
||||
im = Image.new("RGB", size)
|
||||
|
|
|
@ -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
|
||||
|
@ -406,14 +405,13 @@ 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
|
||||
|
||||
from setuptools.command.build_ext import new_compiler
|
||||
|
||||
with open("embed_pil.c", "w", encoding="utf-8") as fh:
|
||||
fh.write(
|
||||
"""
|
||||
|
|
|
@ -104,6 +104,13 @@ def test_rgba_p():
|
|||
assert_image_similar(im, comparable, 20)
|
||||
|
||||
|
||||
def test_rgba():
|
||||
with Image.open("Tests/images/transparent.png") as im:
|
||||
assert im.mode == "RGBA"
|
||||
|
||||
assert_image_similar(im.convert("RGBa").convert("RGB"), im.convert("RGB"), 1.5)
|
||||
|
||||
|
||||
def test_trns_p(tmp_path):
|
||||
im = hopper("P")
|
||||
im.info["transparency"] = 0
|
||||
|
|
|
@ -55,10 +55,11 @@ def test_mode_with_L_with_float():
|
|||
assert im.getpixel((0, 0)) == 2
|
||||
|
||||
|
||||
def test_mode_i():
|
||||
@pytest.mark.parametrize("mode", ("I", "I;16", "I;16L", "I;16B"))
|
||||
def test_mode_i(mode):
|
||||
src = hopper("L")
|
||||
data = list(src.getdata())
|
||||
im = Image.new("I", src.size, 0)
|
||||
im = Image.new(mode, src.size, 0)
|
||||
im.putdata(data, 2, 256)
|
||||
|
||||
target = [2 * elt + 256 for elt in data]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
@ -1063,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)
|
||||
|
|
|
@ -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")
|
||||
|
@ -61,9 +64,13 @@ $bmp = New-Object Drawing.Bitmap 200, 200
|
|||
)
|
||||
p.communicate()
|
||||
else:
|
||||
with pytest.raises(NotImplementedError) as e:
|
||||
ImageGrab.grabclipboard()
|
||||
assert str(e.value) == "ImageGrab.grabclipboard() is macOS and Windows only"
|
||||
if not shutil.which("wl-paste"):
|
||||
with pytest.raises(
|
||||
NotImplementedError,
|
||||
match="wl-paste or xclip is required for"
|
||||
r" ImageGrab.grabclipboard\(\) on Linux",
|
||||
):
|
||||
ImageGrab.grabclipboard()
|
||||
return
|
||||
|
||||
ImageGrab.grabclipboard()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -211,13 +211,16 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
|
||||
def _open(self):
|
||||
if not _accept(self.fp.read(4)):
|
||||
raise SyntaxError("not a DDS file")
|
||||
msg = "not a DDS file"
|
||||
raise SyntaxError(msg)
|
||||
(header_size,) = struct.unpack("<I", self.fp.read(4))
|
||||
if header_size != 124:
|
||||
raise OSError(f"Unsupported header size {repr(header_size)}")
|
||||
msg = f"Unsupported header size {repr(header_size)}"
|
||||
raise OSError(msg)
|
||||
header_bytes = self.fp.read(header_size - 4)
|
||||
if len(header_bytes) != 120:
|
||||
raise OSError(f"Incomplete header: {len(header_bytes)} bytes")
|
||||
msg = f"Incomplete header: {len(header_bytes)} bytes"
|
||||
raise OSError(msg)
|
||||
header = BytesIO(header_bytes)
|
||||
|
||||
flags, height, width = struct.unpack("<3I", header.read(12))
|
||||
|
@ -237,7 +240,8 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
elif fourcc == b"DXT5":
|
||||
self.decoder = "DXT5"
|
||||
else:
|
||||
raise NotImplementedError(f"Unimplemented pixel format {fourcc}")
|
||||
msg = f"Unimplemented pixel format {fourcc}"
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
self.tile = [(self.decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
||||
|
||||
|
@ -252,7 +256,8 @@ class DXT1Decoder(ImageFile.PyDecoder):
|
|||
try:
|
||||
self.set_as_raw(_dxt1(self.fd, self.state.xsize, self.state.ysize))
|
||||
except struct.error as e:
|
||||
raise OSError("Truncated DDS file") from e
|
||||
msg = "Truncated DDS file"
|
||||
raise OSError(msg) from e
|
||||
return -1, 0
|
||||
|
||||
|
||||
|
@ -263,7 +268,8 @@ class DXT5Decoder(ImageFile.PyDecoder):
|
|||
try:
|
||||
self.set_as_raw(_dxt5(self.fd, self.state.xsize, self.state.ysize))
|
||||
except struct.error as e:
|
||||
raise OSError("Truncated DDS file") from e
|
||||
msg = "Truncated DDS file"
|
||||
raise OSError(msg) from e
|
||||
return -1, 0
|
||||
|
||||
|
||||
|
|
|
@ -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,6 +64,12 @@ 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)
|
||||
|
||||
Premultiplied alpha is where the values for each other channel have been
|
||||
multiplied by the alpha. For example, an RGBA pixel of ``(10, 20, 30, 127)``
|
||||
would convert to an RGBa pixel of ``(5, 10, 15, 127)``. The values of the R,
|
||||
G and B channels are halved as a result of the half transparency in the alpha
|
||||
channel.
|
||||
|
||||
Apart from these additional modes, Pillow doesn't yet support multichannel
|
||||
images with a depth of more than 8 bits per channel.
|
||||
|
||||
|
@ -107,6 +117,18 @@ the file format handler (see the chapter on :ref:`image-file-formats`). Most
|
|||
handlers add properties to the :py:attr:`~PIL.Image.Image.info` attribute when
|
||||
loading an image, but ignore it when saving images.
|
||||
|
||||
Transparency
|
||||
------------
|
||||
|
||||
If an image does not have an alpha band, transparency may be specified in the
|
||||
:py:attr:`~PIL.Image.Image.info` attribute with a "transparency" key.
|
||||
|
||||
Most of the time, the "transparency" value is a single integer, describing
|
||||
which pixel value is transparent in a "1", "L", "I" or "P" mode image.
|
||||
However, PNG images may have three values, one for each channel in an "RGB"
|
||||
mode image, or can have a byte string for a "P" mode image, to specify the
|
||||
alpha value for each palette entry.
|
||||
|
||||
Orientation
|
||||
-----------
|
||||
|
||||
|
|
|
@ -474,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::
|
||||
|
||||
|
@ -563,6 +568,11 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
|||
encoded using RLCP mode will have increasing resolutions decoded as they
|
||||
arrive, and so on.
|
||||
|
||||
**signed**
|
||||
If true, then tell the encoder to save the image as signed.
|
||||
|
||||
.. versionadded:: 9.4.0
|
||||
|
||||
**cinema_mode**
|
||||
Set the encoder to produce output compliant with the digital cinema
|
||||
specifications. The options here are ``"no"`` (the default),
|
||||
|
@ -1124,6 +1134,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.
|
||||
|
|
|
@ -78,7 +78,8 @@ true color.
|
|||
elif bits == 24:
|
||||
self.mode = "RGB"
|
||||
else:
|
||||
raise SyntaxError("unknown number of bits")
|
||||
msg = "unknown number of bits"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
# data descriptor
|
||||
self.tile = [("raw", (0, 0) + self.size, 128, (self.mode, 0, 1))]
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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
|
||||
|
@ -140,14 +143,14 @@ Many of Pillow's features require external libraries:
|
|||
|
||||
* **libtiff** provides compressed TIFF functionality
|
||||
|
||||
* Pillow has been tested with libtiff versions **3.x** and **4.0-4.4**
|
||||
* Pillow has been tested with libtiff versions **3.x** and **4.0-4.5**
|
||||
|
||||
* **libfreetype** provides type related services
|
||||
|
||||
* **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 | |
|
||||
+----------------------------------+---------------------------+------------------+--------------+
|
||||
|
|
|
@ -4,14 +4,56 @@
|
|||
: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
|
||||
|
||||
.. py:data:: LightSource
|
||||
|
||||
>>> from PIL.ExifTags import LightSource
|
||||
>>> LightSource.Unknown.value
|
||||
0
|
||||
>>> LightSource(0).name
|
||||
'Unknown'
|
||||
|
||||
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 +62,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]
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
9.3.0
|
||||
-----
|
||||
|
||||
Backwards Incompatible Changes
|
||||
==============================
|
||||
|
||||
API Additions
|
||||
=============
|
||||
|
||||
|
@ -32,10 +29,23 @@ 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
|
||||
========
|
||||
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -43,18 +53,55 @@ 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.
|
||||
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.
|
||||
|
||||
|
||||
Other Changes
|
||||
=============
|
||||
|
||||
Python 3.11 wheels
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
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
|
||||
|
|
111
docs/releasenotes/9.4.0.rst
Normal file
|
@ -0,0 +1,111 @@
|
|||
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.
|
||||
|
||||
Added ``signed`` option when saving JPEG2000
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If the ``signed`` keyword argument is present and true when saving JPEG2000
|
||||
images, then tell the encoder to save the image as signed.
|
||||
|
||||
Added IFD, Interop and LightSource ExifTags enums
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:py:data:`~PIL.ExifTags.IFD` has been added, allowing enums to be used with
|
||||
:py:meth:`~PIL.Image.Exif.get_ifd`::
|
||||
|
||||
from PIL import Image, ExifTags
|
||||
im = Image.open("Tests/images/flower.jpg")
|
||||
print(im.getexif().get_ifd(ExifTags.IFD.Exif))
|
||||
|
||||
``IFD1`` can also be used with :py:meth:`~PIL.Image.Exif.get_ifd`, but it should
|
||||
not be used in other contexts, as the enum value is only internally meaningful.
|
||||
|
||||
:py:data:`~PIL.ExifTags.Interop` has been added for tags within the Interop IFD::
|
||||
|
||||
from PIL import Image, ExifTags
|
||||
im = Image.open("Tests/images/flower.jpg")
|
||||
interop_ifd = im.getexif().get_ifd(ExifTags.IFD.Interop)
|
||||
print(interop_ifd.get(ExifTags.Interop.InteropIndex)) # R98
|
||||
|
||||
:py:data:`~PIL.ExifTags.LightSource` has been added for values within the LightSource
|
||||
tag::
|
||||
|
||||
from PIL import Image, ExifTags
|
||||
im = Image.open("Tests/images/iptc.jpg")
|
||||
exif_ifd = im.getexif().get_ifd(ExifTags.IFD.Exif)
|
||||
print(ExifTags.LightSource(exif_ifd[0x9208])) # LightSource.Unknown
|
||||
|
||||
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
|
||||
=============
|
||||
|
||||
Added support for DDS L and LA images
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Support has been added to read and write L and LA DDS images in the uncompressed
|
||||
format, known as "luminance" textures.
|
|
@ -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
|
||||
|
|
|
@ -46,6 +46,7 @@ docs =
|
|||
olefile
|
||||
sphinx>=2.4
|
||||
sphinx-copybutton
|
||||
sphinx-inline-tabs
|
||||
sphinx-issues>=3.0.1
|
||||
sphinx-removed-in
|
||||
sphinxext-opengraph
|
||||
|
|
23
setup.py
|
@ -15,9 +15,7 @@ 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
|
||||
|
||||
|
||||
|
@ -364,15 +362,15 @@ class pil_build_ext(build_ext):
|
|||
self.feature.required.discard(x)
|
||||
_dbg("Disabling %s", x)
|
||||
if getattr(self, f"enable_{x}"):
|
||||
raise ValueError(
|
||||
f"Conflicting options: --enable-{x} and --disable-{x}"
|
||||
)
|
||||
msg = f"Conflicting options: --enable-{x} and --disable-{x}"
|
||||
raise ValueError(msg)
|
||||
if x == "freetype":
|
||||
_dbg("--disable-freetype implies --disable-raqm")
|
||||
if getattr(self, "enable_raqm"):
|
||||
raise ValueError(
|
||||
msg = (
|
||||
"Conflicting options: --enable-raqm and --disable-freetype"
|
||||
)
|
||||
raise ValueError(msg)
|
||||
setattr(self, "disable_raqm", True)
|
||||
if getattr(self, f"enable_{x}"):
|
||||
_dbg("Requiring %s", x)
|
||||
|
@ -383,13 +381,11 @@ class pil_build_ext(build_ext):
|
|||
for x in ("raqm", "fribidi"):
|
||||
if getattr(self, f"vendor_{x}"):
|
||||
if getattr(self, "disable_raqm"):
|
||||
raise ValueError(
|
||||
f"Conflicting options: --vendor-{x} and --disable-raqm"
|
||||
)
|
||||
msg = f"Conflicting options: --vendor-{x} and --disable-raqm"
|
||||
raise ValueError(msg)
|
||||
if x == "fribidi" and not getattr(self, "vendor_raqm"):
|
||||
raise ValueError(
|
||||
f"Conflicting options: --vendor-{x} and not --vendor-raqm"
|
||||
)
|
||||
msg = f"Conflicting options: --vendor-{x} and not --vendor-raqm"
|
||||
raise ValueError(msg)
|
||||
_dbg("Using vendored version of %s", x)
|
||||
self.feature.vendor.add(x)
|
||||
|
||||
|
@ -852,7 +848,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:
|
||||
|
|
|
@ -22,7 +22,8 @@ def _save_frame(im: Image.Image, fp: BytesIO, filename: str, info: dict):
|
|||
h = info.get("hotspots", [(0, 0) for _ in range(len(s))])
|
||||
|
||||
if len(h) != len(s):
|
||||
raise ValueError("Number of hotspots must be equal to number of cursor sizes")
|
||||
msg = "Number of hotspots must be equal to number of cursor sizes"
|
||||
raise ValueError(msg)
|
||||
|
||||
# sort and remove duplicate sizes
|
||||
sizes, hotspots = [], []
|
||||
|
@ -154,7 +155,8 @@ def _write_multiple_frames(im: Image.Image, fp: BytesIO, filename: str):
|
|||
seq_offset = fp.tell()
|
||||
for i in seq:
|
||||
if i >= len(frames):
|
||||
raise ValueError("Sequence index out of animation frame bounds")
|
||||
msg = "Sequence index out of animation frame bounds"
|
||||
raise ValueError(msg)
|
||||
|
||||
fp.write(o32(i))
|
||||
|
||||
|
@ -172,10 +174,12 @@ def _write_multiple_frames(im: Image.Image, fp: BytesIO, filename: str):
|
|||
|
||||
if seq:
|
||||
if len(rate) != len(seq):
|
||||
raise ValueError("Length of rate must match length of sequence")
|
||||
msg = "Length of rate must match length of sequence"
|
||||
raise ValueError(msg)
|
||||
else:
|
||||
if len(rate) != len(frames):
|
||||
raise ValueError("Length of rate must match number of frames")
|
||||
msg = "Length of rate must match number of frames"
|
||||
raise ValueError(msg)
|
||||
|
||||
for r in rate:
|
||||
fp.write(o32(r))
|
||||
|
@ -220,12 +224,14 @@ def _write_info(im: Image.Image, fp: BytesIO, filename: str):
|
|||
if isinstance(inam, str):
|
||||
inam = inam.encode()
|
||||
if not isinstance(inam, bytes):
|
||||
raise TypeError("'inam' argument must be either a string or bytes")
|
||||
msg = "'inam' argument must be either a string or bytes"
|
||||
raise TypeError(msg)
|
||||
|
||||
if isinstance(iart, str):
|
||||
iart = iart.encode()
|
||||
if not isinstance(iart, bytes):
|
||||
raise TypeError("'iart' argument must be either a string or bytes")
|
||||
msg = "'iart' argument must be either a string or bytes"
|
||||
raise TypeError(msg)
|
||||
|
||||
fp.write(b"INFO")
|
||||
fp.write(b"INAM" + o32(0))
|
||||
|
@ -335,7 +341,8 @@ class AniFile:
|
|||
listOffset = listOffset + lSize + 8
|
||||
|
||||
if self.anih is None:
|
||||
raise SyntaxError("not an ANI file")
|
||||
msg = "not an ANI file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
if self.seq is None:
|
||||
self.seq = [i for i in range(self.anih["nFrames"])]
|
||||
|
@ -345,7 +352,8 @@ class AniFile:
|
|||
|
||||
def frame(self, frame):
|
||||
if frame > self.anih["nFrames"]:
|
||||
raise EOFError("Frame index out of animation bounds")
|
||||
msg = "Frame index out of animation bounds"
|
||||
raise EOFError(msg)
|
||||
|
||||
offset, size = self.image_data[frame].values()
|
||||
self.buf.seek(offset)
|
||||
|
@ -406,7 +414,8 @@ class AniImageFile(ImageFile.ImageFile):
|
|||
@size.setter
|
||||
def size(self, value):
|
||||
if value not in self.info["sizes"]:
|
||||
raise ValueError("This is not one of the allowed sizes of this image")
|
||||
msg = "This is not one of the allowed sizes of this image"
|
||||
raise ValueError(msg)
|
||||
self._size = value
|
||||
|
||||
def load(self):
|
||||
|
@ -419,7 +428,8 @@ class AniImageFile(ImageFile.ImageFile):
|
|||
def seek(self, frame):
|
||||
|
||||
if frame > self.info["frames"] - 1 or frame < 0:
|
||||
raise EOFError("Frame index out of animation bounds")
|
||||
msg = "Frame index out of animation bounds"
|
||||
raise EOFError(msg)
|
||||
|
||||
self.frame = frame
|
||||
self.load()
|
||||
|
|
|
@ -86,7 +86,8 @@ class BdfFontFile(FontFile.FontFile):
|
|||
|
||||
s = fp.readline()
|
||||
if s[:13] != b"STARTFONT 2.1":
|
||||
raise SyntaxError("not a valid BDF file")
|
||||
msg = "not a valid BDF file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
props = {}
|
||||
comments = []
|
||||
|
|
|
@ -65,7 +65,8 @@ def __getattr__(name):
|
|||
if name in enum.__members__:
|
||||
deprecate(f"{prefix}{name}", 10, f"{enum.__name__}.{name}")
|
||||
return enum[name]
|
||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||
msg = f"module '{__name__}' has no attribute '{name}'"
|
||||
raise AttributeError(msg)
|
||||
|
||||
|
||||
def unpack_565(i):
|
||||
|
@ -278,7 +279,8 @@ class BlpImageFile(ImageFile.ImageFile):
|
|||
if self.magic in (b"BLP1", b"BLP2"):
|
||||
decoder = self.magic.decode()
|
||||
else:
|
||||
raise BLPFormatError(f"Bad BLP magic {repr(self.magic)}")
|
||||
msg = f"Bad BLP magic {repr(self.magic)}"
|
||||
raise BLPFormatError(msg)
|
||||
|
||||
self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
||||
self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
||||
|
@ -292,7 +294,8 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
|||
self._read_blp_header()
|
||||
self._load()
|
||||
except struct.error as e:
|
||||
raise OSError("Truncated BLP file") from e
|
||||
msg = "Truncated BLP file"
|
||||
raise OSError(msg) from e
|
||||
return -1, 0
|
||||
|
||||
def _read_blp_header(self):
|
||||
|
@ -354,13 +357,11 @@ class BLP1Decoder(_BLPBaseDecoder):
|
|||
data = self._read_bgra(palette)
|
||||
self.set_as_raw(bytes(data))
|
||||
else:
|
||||
raise BLPFormatError(
|
||||
f"Unsupported BLP encoding {repr(self._blp_encoding)}"
|
||||
)
|
||||
msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}"
|
||||
raise BLPFormatError(msg)
|
||||
else:
|
||||
raise BLPFormatError(
|
||||
f"Unsupported BLP compression {repr(self._blp_encoding)}"
|
||||
)
|
||||
msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
|
||||
raise BLPFormatError(msg)
|
||||
|
||||
def _decode_jpeg_stream(self):
|
||||
from .JpegImagePlugin import JpegImageFile
|
||||
|
@ -373,6 +374,9 @@ class BLP1Decoder(_BLPBaseDecoder):
|
|||
data = BytesIO(data)
|
||||
image = JpegImageFile(data)
|
||||
Image._decompression_bomb_check(image.size)
|
||||
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())
|
||||
|
@ -412,16 +416,15 @@ class BLP2Decoder(_BLPBaseDecoder):
|
|||
for d in decode_dxt5(self._safe_read(linesize)):
|
||||
data += d
|
||||
else:
|
||||
raise BLPFormatError(
|
||||
f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
|
||||
)
|
||||
msg = f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
|
||||
raise BLPFormatError(msg)
|
||||
else:
|
||||
raise BLPFormatError(f"Unknown BLP encoding {repr(self._blp_encoding)}")
|
||||
msg = f"Unknown BLP encoding {repr(self._blp_encoding)}"
|
||||
raise BLPFormatError(msg)
|
||||
|
||||
else:
|
||||
raise BLPFormatError(
|
||||
f"Unknown BLP compression {repr(self._blp_compression)}"
|
||||
)
|
||||
msg = f"Unknown BLP compression {repr(self._blp_compression)}"
|
||||
raise BLPFormatError(msg)
|
||||
|
||||
self.set_as_raw(bytes(data))
|
||||
|
||||
|
@ -457,7 +460,8 @@ class BLPEncoder(ImageFile.PyEncoder):
|
|||
|
||||
def _save(im, fp, filename, save_all=False):
|
||||
if im.mode != "P":
|
||||
raise ValueError("Unsupported BLP image mode")
|
||||
msg = "Unsupported BLP image mode"
|
||||
raise ValueError(msg)
|
||||
|
||||
magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
|
||||
fp.write(magic)
|
||||
|
|
|
@ -146,7 +146,8 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
file_info["a_mask"],
|
||||
)
|
||||
else:
|
||||
raise OSError(f"Unsupported BMP header type ({file_info['header_size']})")
|
||||
msg = f"Unsupported BMP header type ({file_info['header_size']})"
|
||||
raise OSError(msg)
|
||||
|
||||
# ------------------ Special case : header is reported 40, which
|
||||
# ---------------------- is shorter than real size for bpp >= 16
|
||||
|
@ -164,7 +165,8 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
# ---------------------- Check bit depth for unusual unsupported values
|
||||
self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
|
||||
if self.mode is None:
|
||||
raise OSError(f"Unsupported BMP pixel depth ({file_info['bits']})")
|
||||
msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
|
||||
raise OSError(msg)
|
||||
|
||||
# ---------------- Process BMP with Bitfields compression (not palette)
|
||||
decoder_name = "raw"
|
||||
|
@ -205,9 +207,11 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
):
|
||||
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])]
|
||||
else:
|
||||
raise OSError("Unsupported BMP bitfields layout")
|
||||
msg = "Unsupported BMP bitfields layout"
|
||||
raise OSError(msg)
|
||||
else:
|
||||
raise OSError("Unsupported BMP bitfields layout")
|
||||
msg = "Unsupported BMP bitfields layout"
|
||||
raise OSError(msg)
|
||||
elif file_info["compression"] == self.RAW:
|
||||
try:
|
||||
if file_info["bits"] == 32 and self.alpha:
|
||||
|
@ -217,14 +221,16 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
elif file_info["compression"] in (self.RLE8, self.RLE4):
|
||||
decoder_name = "bmp_rle"
|
||||
else:
|
||||
raise OSError(f"Unsupported BMP compression ({file_info['compression']})")
|
||||
msg = f"Unsupported BMP compression ({file_info['compression']})"
|
||||
raise OSError(msg)
|
||||
|
||||
# --------------- Once the header is processed, process the palette/LUT
|
||||
if self.mode == "P": # Paletted for 1, 4 and 8 bit images
|
||||
|
||||
# ---------------------------------------------------- 1-bit images
|
||||
if not (0 < file_info["colors"] <= 65536):
|
||||
raise OSError(f"Unsupported BMP Palette size ({file_info['colors']})")
|
||||
msg = f"Unsupported BMP Palette size ({file_info['colors']})"
|
||||
raise OSError(msg)
|
||||
else:
|
||||
padding = file_info["palette_padding"]
|
||||
palette = read(padding * file_info["colors"])
|
||||
|
@ -274,7 +280,8 @@ class BmpImageFile(ImageFile.ImageFile):
|
|||
head_data = self.fp.read(14)
|
||||
# choke if the file does not have the required magic bytes
|
||||
if not _accept(head_data):
|
||||
raise SyntaxError("Not a BMP file")
|
||||
msg = "Not a BMP file"
|
||||
raise SyntaxError(msg)
|
||||
# read the start position of the BMP image data (u32)
|
||||
offset = i32(head_data, 10)
|
||||
# load bitmap information (offset=raster info)
|
||||
|
@ -386,7 +393,8 @@ def _save(im, fp, filename, bitmap_header=True):
|
|||
try:
|
||||
rawmode, bits, colors = SAVE[im.mode]
|
||||
except KeyError as e:
|
||||
raise OSError(f"cannot write mode {im.mode} as BMP") from e
|
||||
msg = f"cannot write mode {im.mode} as BMP"
|
||||
raise OSError(msg) from e
|
||||
|
||||
info = im.encoderinfo
|
||||
|
||||
|
@ -414,7 +422,8 @@ def _save(im, fp, filename, bitmap_header=True):
|
|||
offset = 14 + header + colors * 4
|
||||
file_size = offset + image
|
||||
if file_size > 2**32 - 1:
|
||||
raise ValueError("File size is too large for the BMP format")
|
||||
msg = "File size is too large for the BMP format"
|
||||
raise ValueError(msg)
|
||||
fp.write(
|
||||
b"BM" # file type (magic)
|
||||
+ o32(file_size) # file size
|
||||
|
|
|
@ -42,7 +42,8 @@ class BufrStubImageFile(ImageFile.StubImageFile):
|
|||
offset = self.fp.tell()
|
||||
|
||||
if not _accept(self.fp.read(4)):
|
||||
raise SyntaxError("Not a BUFR file")
|
||||
msg = "Not a BUFR file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
self.fp.seek(offset)
|
||||
|
||||
|
@ -60,7 +61,8 @@ class BufrStubImageFile(ImageFile.StubImageFile):
|
|||
|
||||
def _save(im, fp, filename):
|
||||
if _handler is None or not hasattr(_handler, "save"):
|
||||
raise OSError("BUFR save handler not installed")
|
||||
msg = "BUFR save handler not installed"
|
||||
raise OSError(msg)
|
||||
_handler.save(im, fp, filename)
|
||||
|
||||
|
||||
|
|
|
@ -40,7 +40,8 @@ def _save(im: Image.Image, fp: BytesIO, filename: str):
|
|||
h = im.encoderinfo.get("hotspots", [(0, 0) for i in range(len(s))])
|
||||
|
||||
if len(h) != len(s):
|
||||
raise ValueError("Number of hotspots must be equal to number of cursor sizes")
|
||||
msg = "Number of hotspots must be equal to number of cursor sizes"
|
||||
raise ValueError(msg)
|
||||
|
||||
# sort and remove duplicate sizes
|
||||
sizes, hotspots = [], []
|
||||
|
@ -117,7 +118,8 @@ class CurFile(IcoImagePlugin.IcoFile):
|
|||
# check if CUR
|
||||
s = buf.read(6)
|
||||
if not _accept(s):
|
||||
raise SyntaxError("not a CUR file")
|
||||
msg = "not a CUR file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
self.buf = buf
|
||||
self.entry = []
|
||||
|
@ -211,7 +213,8 @@ class CurImageFile(IcoImagePlugin.IcoImageFile):
|
|||
if len(self.ico.entry) > 0:
|
||||
self.size = self.ico.entry[0]["dim"]
|
||||
else:
|
||||
raise TypeError("No cursors were found")
|
||||
msg = "No cursors were found"
|
||||
raise TypeError(msg)
|
||||
self.load()
|
||||
|
||||
|
||||
|
|
|
@ -47,7 +47,8 @@ class DcxImageFile(PcxImageFile):
|
|||
# Header
|
||||
s = self.fp.read(4)
|
||||
if not _accept(s):
|
||||
raise SyntaxError("not a DCX file")
|
||||
msg = "not a DCX file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
# Component directory
|
||||
self._offset = []
|
||||
|
|
|
@ -114,13 +114,16 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
|
||||
def _open(self):
|
||||
if not _accept(self.fp.read(4)):
|
||||
raise SyntaxError("not a DDS file")
|
||||
msg = "not a DDS file"
|
||||
raise SyntaxError(msg)
|
||||
(header_size,) = struct.unpack("<I", self.fp.read(4))
|
||||
if header_size != 124:
|
||||
raise OSError(f"Unsupported header size {repr(header_size)}")
|
||||
msg = f"Unsupported header size {repr(header_size)}"
|
||||
raise OSError(msg)
|
||||
header_bytes = self.fp.read(header_size - 4)
|
||||
if len(header_bytes) != 120:
|
||||
raise OSError(f"Incomplete header: {len(header_bytes)} bytes")
|
||||
msg = f"Incomplete header: {len(header_bytes)} bytes"
|
||||
raise OSError(msg)
|
||||
header = BytesIO(header_bytes)
|
||||
|
||||
flags, height, width = struct.unpack("<3I", header.read(12))
|
||||
|
@ -135,11 +138,19 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
fourcc = header.read(4)
|
||||
(bitcount,) = struct.unpack("<I", header.read(4))
|
||||
masks = struct.unpack("<4I", header.read(16))
|
||||
if pfflags & DDPF_RGB:
|
||||
if pfflags & DDPF_LUMINANCE:
|
||||
# Texture contains uncompressed L or LA data
|
||||
if pfflags & DDPF_ALPHAPIXELS:
|
||||
self.mode = "LA"
|
||||
else:
|
||||
self.mode = "L"
|
||||
|
||||
self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
||||
elif pfflags & DDPF_RGB:
|
||||
# Texture contains uncompressed RGB data
|
||||
masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
|
||||
rawmode = ""
|
||||
if bitcount == 32:
|
||||
if pfflags & DDPF_ALPHAPIXELS:
|
||||
rawmode += masks[0xFF000000]
|
||||
else:
|
||||
self.mode = "RGB"
|
||||
|
@ -208,11 +219,11 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
self.info["gamma"] = 1 / 2.2
|
||||
return
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"Unimplemented DXGI format {dxgi_format}"
|
||||
)
|
||||
msg = f"Unimplemented DXGI format {dxgi_format}"
|
||||
raise NotImplementedError(msg)
|
||||
else:
|
||||
raise NotImplementedError(f"Unimplemented pixel format {repr(fourcc)}")
|
||||
msg = f"Unimplemented pixel format {repr(fourcc)}"
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
self.tile = [
|
||||
("bcn", (0, 0) + self.size, data_start, (n, self.pixel_format))
|
||||
|
@ -223,8 +234,24 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
if im.mode not in ("RGB", "RGBA"):
|
||||
raise OSError(f"cannot write mode {im.mode} as DDS")
|
||||
if im.mode not in ("RGB", "RGBA", "L", "LA"):
|
||||
msg = f"cannot write mode {im.mode} as DDS"
|
||||
raise OSError(msg)
|
||||
|
||||
rawmode = im.mode
|
||||
masks = [0xFF0000, 0xFF00, 0xFF]
|
||||
if im.mode in ("L", "LA"):
|
||||
pixel_flags = DDPF_LUMINANCE
|
||||
else:
|
||||
pixel_flags = DDPF_RGB
|
||||
rawmode = rawmode[::-1]
|
||||
if im.mode in ("LA", "RGBA"):
|
||||
pixel_flags |= DDPF_ALPHAPIXELS
|
||||
masks.append(0xFF000000)
|
||||
|
||||
bitcount = len(masks) * 8
|
||||
while len(masks) < 4:
|
||||
masks.append(0)
|
||||
|
||||
fp.write(
|
||||
o32(DDS_MAGIC)
|
||||
|
@ -234,18 +261,15 @@ def _save(im, fp, filename):
|
|||
) # flags
|
||||
+ o32(im.height)
|
||||
+ o32(im.width)
|
||||
+ o32((im.width * (32 if im.mode == "RGBA" else 24) + 7) // 8) # pitch
|
||||
+ o32((im.width * bitcount + 7) // 8) # pitch
|
||||
+ o32(0) # depth
|
||||
+ o32(0) # mipmaps
|
||||
+ o32(0) * 11 # reserved
|
||||
+ o32(32) # pfsize
|
||||
+ o32(DDS_RGBA if im.mode == "RGBA" else DDPF_RGB) # pfflags
|
||||
+ o32(pixel_flags) # pfflags
|
||||
+ o32(0) # fourcc
|
||||
+ o32(32 if im.mode == "RGBA" else 24) # bitcount
|
||||
+ o32(0xFF0000) # rbitmask
|
||||
+ o32(0xFF00) # gbitmask
|
||||
+ o32(0xFF) # bbitmask
|
||||
+ o32(0xFF000000 if im.mode == "RGBA" else 0) # abitmask
|
||||
+ o32(bitcount) # bitcount
|
||||
+ b"".join(o32(mask) for mask in masks) # rgbabitmask
|
||||
+ o32(DDSCAPS_TEXTURE) # dwCaps
|
||||
+ o32(0) # dwCaps2
|
||||
+ o32(0) # dwCaps3
|
||||
|
@ -255,7 +279,7 @@ def _save(im, fp, filename):
|
|||
if im.mode == "RGBA":
|
||||
r, g, b, a = im.split()
|
||||
im = Image.merge("RGBA", (a, r, g, b))
|
||||
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (im.mode[::-1], 0, 1))])
|
||||
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
|
||||
|
||||
|
||||
def _accept(prefix):
|
||||
|
|
|
@ -133,7 +133,8 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
|||
|
||||
if gs_windows_binary is not None:
|
||||
if not gs_windows_binary:
|
||||
raise OSError("Unable to locate Ghostscript on paths")
|
||||
msg = "Unable to locate Ghostscript on paths"
|
||||
raise OSError(msg)
|
||||
command[0] = gs_windows_binary
|
||||
|
||||
# push data through Ghostscript
|
||||
|
@ -229,12 +230,14 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
while s_raw:
|
||||
if s:
|
||||
if len(s) > 255:
|
||||
raise SyntaxError("not an EPS file")
|
||||
msg = "not an EPS file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
try:
|
||||
m = split.match(s)
|
||||
except re.error as e:
|
||||
raise SyntaxError("not an EPS file") from e
|
||||
msg = "not an EPS file"
|
||||
raise SyntaxError(msg) from e
|
||||
|
||||
if m:
|
||||
k, v = m.group(1, 2)
|
||||
|
@ -268,7 +271,8 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
# tools mistakenly put in the Comments section
|
||||
pass
|
||||
else:
|
||||
raise OSError("bad EPS header")
|
||||
msg = "bad EPS header"
|
||||
raise OSError(msg)
|
||||
|
||||
s_raw = fp.readline()
|
||||
s = s_raw.strip("\r\n")
|
||||
|
@ -282,7 +286,8 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
while s[:1] == "%":
|
||||
|
||||
if len(s) > 255:
|
||||
raise SyntaxError("not an EPS file")
|
||||
msg = "not an EPS file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
if s[:11] == "%ImageData:":
|
||||
# Encoded bitmapped image.
|
||||
|
@ -306,7 +311,8 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
break
|
||||
|
||||
if not box:
|
||||
raise OSError("cannot determine EPS bounding box")
|
||||
msg = "cannot determine EPS bounding box"
|
||||
raise OSError(msg)
|
||||
|
||||
def _find_offset(self, fp):
|
||||
|
||||
|
@ -326,7 +332,8 @@ class EpsImageFile(ImageFile.ImageFile):
|
|||
offset = i32(s, 4)
|
||||
length = i32(s, 8)
|
||||
else:
|
||||
raise SyntaxError("not an EPS file")
|
||||
msg = "not an EPS file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
return length, offset
|
||||
|
||||
|
@ -365,7 +372,8 @@ def _save(im, fp, filename, eps=1):
|
|||
elif im.mode == "CMYK":
|
||||
operator = (8, 4, b"false 4 colorimage")
|
||||
else:
|
||||
raise ValueError("image mode is not supported")
|
||||
msg = "image mode is not supported"
|
||||
raise ValueError(msg)
|
||||
|
||||
if eps:
|
||||
#
|
||||
|
|
|
@ -14,318 +14,367 @@ 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
|
||||
|
||||
|
||||
class LightSource(IntEnum):
|
||||
Unknown = 0
|
||||
Daylight = 1
|
||||
Fluorescent = 2
|
||||
Tungsten = 3
|
||||
Flash = 4
|
||||
Fine = 9
|
||||
Cloudy = 10
|
||||
Shade = 11
|
||||
DaylightFluorescent = 12
|
||||
DayWhiteFluorescent = 13
|
||||
CoolWhiteFluorescent = 14
|
||||
WhiteFluorescent = 15
|
||||
StandardLightA = 17
|
||||
StandardLightB = 18
|
||||
StandardLightC = 19
|
||||
D55 = 20
|
||||
D65 = 21
|
||||
D75 = 22
|
||||
D50 = 23
|
||||
ISO = 24
|
||||
Other = 255
|
||||
|
|
|
@ -28,7 +28,8 @@ class FitsImageFile(ImageFile.ImageFile):
|
|||
while True:
|
||||
header = self.fp.read(80)
|
||||
if not header:
|
||||
raise OSError("Truncated FITS file")
|
||||
msg = "Truncated FITS file"
|
||||
raise OSError(msg)
|
||||
keyword = header[:8].strip()
|
||||
if keyword == b"END":
|
||||
break
|
||||
|
@ -36,12 +37,14 @@ class FitsImageFile(ImageFile.ImageFile):
|
|||
if value.startswith(b"="):
|
||||
value = value[1:].strip()
|
||||
if not headers and (not _accept(keyword) or value != b"T"):
|
||||
raise SyntaxError("Not a FITS file")
|
||||
msg = "Not a FITS file"
|
||||
raise SyntaxError(msg)
|
||||
headers[keyword] = value
|
||||
|
||||
naxis = int(headers[b"NAXIS"])
|
||||
if naxis == 0:
|
||||
raise ValueError("No image data")
|
||||
msg = "No image data"
|
||||
raise ValueError(msg)
|
||||
elif naxis == 1:
|
||||
self._size = 1, int(headers[b"NAXIS1"])
|
||||
else:
|
||||
|
|
|
@ -67,7 +67,8 @@ class FITSStubImageFile(ImageFile.StubImageFile):
|
|||
|
||||
|
||||
def _save(im, fp, filename):
|
||||
raise OSError("FITS save handler not installed")
|
||||
msg = "FITS save handler not installed"
|
||||
raise OSError(msg)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
|
|
@ -50,7 +50,8 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
# HEAD
|
||||
s = self.fp.read(128)
|
||||
if not (_accept(s) and s[20:22] == b"\x00\x00"):
|
||||
raise SyntaxError("not an FLI/FLC file")
|
||||
msg = "not an FLI/FLC file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
# frames
|
||||
self.n_frames = i16(s, 6)
|
||||
|
@ -141,7 +142,8 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
self.load()
|
||||
|
||||
if frame != self.__frame + 1:
|
||||
raise ValueError(f"cannot seek to frame {frame}")
|
||||
msg = f"cannot seek to frame {frame}"
|
||||
raise ValueError(msg)
|
||||
self.__frame = frame
|
||||
|
||||
# move to next frame
|
||||
|
|
|
@ -60,10 +60,12 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
try:
|
||||
self.ole = olefile.OleFileIO(self.fp)
|
||||
except OSError as e:
|
||||
raise SyntaxError("not an FPX file; invalid OLE file") from e
|
||||
msg = "not an FPX file; invalid OLE file"
|
||||
raise SyntaxError(msg) from e
|
||||
|
||||
if self.ole.root.clsid != "56616700-C154-11CE-8553-00AA00A1F95B":
|
||||
raise SyntaxError("not an FPX file; bad root CLSID")
|
||||
msg = "not an FPX file; bad root CLSID"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
self._open_index(1)
|
||||
|
||||
|
@ -99,7 +101,8 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
colors = []
|
||||
bands = i32(s, 4)
|
||||
if bands > 4:
|
||||
raise OSError("Invalid number of bands")
|
||||
msg = "Invalid number of bands"
|
||||
raise OSError(msg)
|
||||
for i in range(bands):
|
||||
# note: for now, we ignore the "uncalibrated" flag
|
||||
colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
|
||||
|
@ -141,7 +144,8 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
length = i32(s, 32)
|
||||
|
||||
if size != self.size:
|
||||
raise OSError("subimage mismatch")
|
||||
msg = "subimage mismatch"
|
||||
raise OSError(msg)
|
||||
|
||||
# get tile descriptors
|
||||
fp.seek(28 + offset)
|
||||
|
@ -217,7 +221,8 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
self.tile_prefix = self.jpeg[jpeg_tables]
|
||||
|
||||
else:
|
||||
raise OSError("unknown/invalid compression")
|
||||
msg = "unknown/invalid compression"
|
||||
raise OSError(msg)
|
||||
|
||||
x = x + xtile
|
||||
if x >= xsize:
|
||||
|
|
|
@ -73,7 +73,8 @@ def __getattr__(name):
|
|||
if name in enum.__members__:
|
||||
deprecate(f"{prefix}{name}", 10, f"{enum.__name__}.{name}")
|
||||
return enum[name]
|
||||
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
||||
msg = f"module '{__name__}' has no attribute '{name}'"
|
||||
raise AttributeError(msg)
|
||||
|
||||
|
||||
class FtexImageFile(ImageFile.ImageFile):
|
||||
|
@ -82,7 +83,8 @@ class FtexImageFile(ImageFile.ImageFile):
|
|||
|
||||
def _open(self):
|
||||
if not _accept(self.fp.read(4)):
|
||||
raise SyntaxError("not an FTEX file")
|
||||
msg = "not an FTEX file"
|
||||
raise SyntaxError(msg)
|
||||
struct.unpack("<i", self.fp.read(4)) # version
|
||||
self._size = struct.unpack("<2i", self.fp.read(8))
|
||||
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
|
||||
|
@ -105,7 +107,8 @@ class FtexImageFile(ImageFile.ImageFile):
|
|||
elif format == Format.UNCOMPRESSED:
|
||||
self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
|
||||
else:
|
||||
raise ValueError(f"Invalid texture compression format: {repr(format)}")
|
||||
msg = f"Invalid texture compression format: {repr(format)}"
|
||||
raise ValueError(msg)
|
||||
|
||||
self.fp.close()
|
||||
self.fp = BytesIO(data)
|
||||
|
|
|
@ -44,18 +44,22 @@ class GbrImageFile(ImageFile.ImageFile):
|
|||
def _open(self):
|
||||
header_size = i32(self.fp.read(4))
|
||||
if header_size < 20:
|
||||
raise SyntaxError("not a GIMP brush")
|
||||
msg = "not a GIMP brush"
|
||||
raise SyntaxError(msg)
|
||||
version = i32(self.fp.read(4))
|
||||
if version not in (1, 2):
|
||||
raise SyntaxError(f"Unsupported GIMP brush version: {version}")
|
||||
msg = f"Unsupported GIMP brush version: {version}"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
width = i32(self.fp.read(4))
|
||||
height = i32(self.fp.read(4))
|
||||
color_depth = i32(self.fp.read(4))
|
||||
if width <= 0 or height <= 0:
|
||||
raise SyntaxError("not a GIMP brush")
|
||||
msg = "not a GIMP brush"
|
||||
raise SyntaxError(msg)
|
||||
if color_depth not in (1, 4):
|
||||
raise SyntaxError(f"Unsupported GIMP brush color depth: {color_depth}")
|
||||
msg = f"Unsupported GIMP brush color depth: {color_depth}"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
if version == 1:
|
||||
comment_length = header_size - 20
|
||||
|
@ -63,7 +67,8 @@ class GbrImageFile(ImageFile.ImageFile):
|
|||
comment_length = header_size - 28
|
||||
magic_number = self.fp.read(4)
|
||||
if magic_number != b"GIMP":
|
||||
raise SyntaxError("not a GIMP brush, bad magic number")
|
||||
msg = "not a GIMP brush, bad magic number"
|
||||
raise SyntaxError(msg)
|
||||
self.info["spacing"] = i32(self.fp.read(4))
|
||||
|
||||
comment = self.fp.read(comment_length)[:-1]
|
||||
|
|
|
@ -49,7 +49,8 @@ class GdImageFile(ImageFile.ImageFile):
|
|||
s = self.fp.read(1037)
|
||||
|
||||
if not i16(s) in [65534, 65535]:
|
||||
raise SyntaxError("Not a valid GD 2.x .gd file")
|
||||
msg = "Not a valid GD 2.x .gd file"
|
||||
raise SyntaxError(msg)
|
||||
|
||||
self.mode = "L" # FIXME: "P"
|
||||
self._size = i16(s, 2), i16(s, 4)
|
||||
|
@ -87,9 +88,11 @@ def open(fp, mode="r"):
|
|||
:raises OSError: If the image could not be read.
|
||||
"""
|
||||
if mode != "r":
|
||||
raise ValueError("bad mode")
|
||||
msg = "bad mode"
|
||||
raise ValueError(msg)
|
||||
|
||||
try:
|
||||
return GdImageFile(fp)
|
||||
except SyntaxError as e:
|
||||
raise UnidentifiedImageError("cannot identify this image file") from e
|
||||
msg = "cannot identify this image file"
|
||||
raise UnidentifiedImageError(msg) from e
|
||||
|
|