mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-14 19:36:38 +03:00
Merge branch 'master' into jp2-decode-subsample
This commit is contained in:
commit
2341c6b933
|
@ -1,5 +1,4 @@
|
||||||
version: '{build}'
|
version: '{build}'
|
||||||
image: Visual Studio 2017
|
|
||||||
clone_folder: c:\pillow
|
clone_folder: c:\pillow
|
||||||
init:
|
init:
|
||||||
- ECHO %PYTHON%
|
- ECHO %PYTHON%
|
||||||
|
@ -8,21 +7,22 @@ init:
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
EXECUTABLE: python.exe
|
EXECUTABLE: python.exe
|
||||||
PIP_DIR: Scripts
|
|
||||||
TEST_OPTIONS:
|
TEST_OPTIONS:
|
||||||
DEPLOY: YES
|
DEPLOY: YES
|
||||||
matrix:
|
matrix:
|
||||||
- PYTHON: C:/Python38
|
- PYTHON: C:/Python39
|
||||||
ARCHITECTURE: x86
|
ARCHITECTURE: x86
|
||||||
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
|
||||||
- PYTHON: C:/Python36-x64
|
- PYTHON: C:/Python36-x64
|
||||||
ARCHITECTURE: x64
|
ARCHITECTURE: x64
|
||||||
|
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||||
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/master.zip
|
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/master.zip
|
||||||
- 7z x pillow-depends.zip -oc:\
|
- 7z x pillow-depends.zip -oc:\
|
||||||
- mv c:\pillow-depends-master c:\pillow-depends
|
- mv c:\pillow-depends-master c:\pillow-depends
|
||||||
- xcopy /s c:\pillow-depends\test_images\* c:\pillow\tests\images
|
- xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
|
||||||
- 7z x ..\pillow-depends\nasm-2.14.02-win64.zip -oc:\
|
- 7z x ..\pillow-depends\nasm-2.14.02-win64.zip -oc:\
|
||||||
- ..\pillow-depends\gs9533w32.exe /S
|
- ..\pillow-depends\gs9533w32.exe /S
|
||||||
- path c:\nasm-2.14.02;C:\Program Files (x86)\gs\gs9.53.3\bin;%PATH%
|
- path c:\nasm-2.14.02;C:\Program Files (x86)\gs\gs9.53.3\bin;%PATH%
|
||||||
|
@ -32,6 +32,7 @@ install:
|
||||||
c:\pillow\winbuild\build\build_dep_all.cmd
|
c:\pillow\winbuild\build\build_dep_all.cmd
|
||||||
$host.SetShouldExit(0)
|
$host.SetShouldExit(0)
|
||||||
- path C:\pillow\winbuild\build\bin;%PATH%
|
- path C:\pillow\winbuild\build\bin;%PATH%
|
||||||
|
- '%PYTHON%\%EXECUTABLE% -m pip install -U "setuptools>=49.3.2"'
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- ps: |
|
- ps: |
|
||||||
|
@ -42,13 +43,13 @@ build_script:
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- cd c:\pillow
|
- cd c:\pillow
|
||||||
- '%PYTHON%\%PIP_DIR%\pip.exe install pytest pytest-cov'
|
- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov'
|
||||||
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
- c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
|
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests'
|
||||||
#- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest?
|
#- '%PYTHON%\%EXECUTABLE% test-installed.py -v -s %TEST_OPTIONS%' TODO TEST_OPTIONS with pytest?
|
||||||
|
|
||||||
after_test:
|
after_test:
|
||||||
- pip install codecov
|
- python -m pip install codecov
|
||||||
- codecov --file coverage.xml --name %PYTHON% --flags AppVeyor
|
- codecov --file coverage.xml --name %PYTHON% --flags AppVeyor
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -65,7 +66,7 @@ artifacts:
|
||||||
|
|
||||||
before_deploy:
|
before_deploy:
|
||||||
- cd c:\pillow
|
- cd c:\pillow
|
||||||
- '%PYTHON%\%PIP_DIR%\pip.exe install wheel'
|
- '%PYTHON%\%EXECUTABLE% -m pip install wheel'
|
||||||
- cd c:\pillow\winbuild\
|
- cd c:\pillow\winbuild\
|
||||||
- c:\pillow\winbuild\build\build_pillow.cmd bdist_wheel
|
- c:\pillow\winbuild\build\build_pillow.cmd bdist_wheel
|
||||||
- cd c:\pillow
|
- cd c:\pillow
|
||||||
|
|
|
@ -7,13 +7,3 @@ if [[ $MATRIX_DOCKER ]]; then
|
||||||
else
|
else
|
||||||
coverage xml
|
coverage xml
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $TRAVIS ]]; then
|
|
||||||
codecov --flags TravisCI
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ]; then
|
|
||||||
# Coverage and quality reports on just the latest diff.
|
|
||||||
depends/diffcover-install.sh
|
|
||||||
depends/diffcover-run.sh
|
|
||||||
fi
|
|
||||||
|
|
|
@ -21,35 +21,30 @@ sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
||||||
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
||||||
cmake imagemagick libharfbuzz-dev libfribidi-dev
|
cmake imagemagick libharfbuzz-dev libfribidi-dev
|
||||||
|
|
||||||
if [[ $TRAVIS_CPU_ARCH == "s390x" ]]; then sudo chown $USER ~/.cache/pip/wheels ; fi
|
python3 -m pip install --upgrade pip
|
||||||
|
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
||||||
|
python3 -m pip install coverage
|
||||||
|
python3 -m pip install olefile
|
||||||
|
python3 -m pip install -U pytest
|
||||||
|
python3 -m pip install -U pytest-cov
|
||||||
|
python3 -m pip install pyroma
|
||||||
|
python3 -m pip install test-image-results
|
||||||
|
# TODO Remove condition when numpy supports 3.10
|
||||||
|
if ! [ "$GHA_PYTHON_VERSION" == "3.10-dev" ]; then python3 -m pip install numpy ; fi
|
||||||
|
|
||||||
pip install --upgrade pip
|
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
|
||||||
PYTHONOPTIMIZE=0 pip install cffi
|
if [ "$GHA_PYTHON_VERSION" == "3.8" ]; then python3 -m pip install -U "setuptools>=49.3.2" ; fi
|
||||||
pip install coverage
|
if [ "$GHA_PYTHON_VERSION" == "3.9" ]; then python3 -m pip install -U "setuptools>=49.3.2" ; fi
|
||||||
pip install olefile
|
|
||||||
pip install -U pytest
|
|
||||||
pip install -U pytest-cov
|
|
||||||
pip install pyroma
|
|
||||||
pip install test-image-results
|
|
||||||
pip install numpy
|
|
||||||
|
|
||||||
# TODO Remove when 3.8 / 3.9 / PyPy3 includes setuptools 49.3.2+:
|
# PyQt5 doesn't support PyPy3
|
||||||
if [ "$GHA_PYTHON_VERSION" == "3.8" ]; then pip install -U "setuptools>=49.3.2" ; fi
|
# Wheel doesn't yet support 3.10
|
||||||
if [ "$GHA_PYTHON_VERSION" == "3.9" ]; then pip install -U "setuptools>=49.3.2" ; fi
|
if [[ $GHA_PYTHON_VERSION == 3.* && $GHA_PYTHON_VERSION != "3.10-dev" ]]; then
|
||||||
if [ "$TRAVIS_PYTHON_VERSION" == "pypy3.6-7.3.1" ]; then pip install -U "setuptools>=49.3.2" ; fi
|
|
||||||
|
|
||||||
if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then
|
|
||||||
# arm64, ppc64le, s390x CPUs:
|
# arm64, ppc64le, s390x CPUs:
|
||||||
# "ERROR: Could not find a version that satisfies the requirement pyqt5"
|
# "ERROR: Could not find a version that satisfies the requirement pyqt5"
|
||||||
if [[ $TRAVIS_CPU_ARCH == "amd64" ]]; then
|
|
||||||
sudo apt-get -qq install libxcb-xinerama0 pyqt5-dev-tools
|
sudo apt-get -qq install libxcb-xinerama0 pyqt5-dev-tools
|
||||||
pip install pyqt5
|
python3 -m pip install pyqt5
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# docs only on Python 3.8
|
|
||||||
if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ]; then pip install -r requirements.txt ; fi
|
|
||||||
|
|
||||||
# webp
|
# webp
|
||||||
pushd depends && ./install_webp.sh && popd
|
pushd depends && ./install_webp.sh && popd
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,3 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
python -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests
|
python -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests
|
||||||
|
|
||||||
# Docs
|
|
||||||
if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ] && [ "$TRAVIS_CPU_ARCH" == "amd64" ]; then
|
|
||||||
make doccheck
|
|
||||||
fi
|
|
||||||
|
|
20
.clang-format
Normal file
20
.clang-format
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# A clang-format style that approximates Python's PEP 7
|
||||||
|
# Useful for IDE integration
|
||||||
|
BasedOnStyle: Google
|
||||||
|
AlwaysBreakAfterReturnType: All
|
||||||
|
AllowShortIfStatementsOnASingleLine: false
|
||||||
|
AlignAfterOpenBracket: AlwaysBreak
|
||||||
|
BinPackArguments: false
|
||||||
|
BinPackParameters: false
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
ColumnLimit: 88
|
||||||
|
DerivePointerAlignment: false
|
||||||
|
IndentWidth: 4
|
||||||
|
Language: Cpp
|
||||||
|
PointerAlignment: Right
|
||||||
|
ReflowComments: true
|
||||||
|
SortIncludes: false
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
SpacesInParentheses: false
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Never
|
4
.github/CONTRIBUTING.md
vendored
4
.github/CONTRIBUTING.md
vendored
|
@ -9,7 +9,7 @@ Please send a pull request to the master branch. Please include [documentation](
|
||||||
- Fork the Pillow repository.
|
- Fork the Pillow repository.
|
||||||
- Create a branch from master.
|
- Create a branch from master.
|
||||||
- Develop bug fixes, features, tests, etc.
|
- Develop bug fixes, features, tests, etc.
|
||||||
- Run the test suite. You can enable [Travis CI](https://travis-ci.org/profile/) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
|
- Run the test suite. You can enable GitHub Actions (https://github.com/MY-USERNAME/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
|
||||||
- Create a pull request to pull the changes from your branch to the Pillow master.
|
- Create a pull request to pull the changes from your branch to the Pillow master.
|
||||||
|
|
||||||
### Guidelines
|
### Guidelines
|
||||||
|
@ -17,7 +17,7 @@ Please send a pull request to the master branch. Please include [documentation](
|
||||||
- Separate code commits from reformatting commits.
|
- Separate code commits from reformatting commits.
|
||||||
- Provide tests for any newly added code.
|
- Provide tests for any newly added code.
|
||||||
- Follow PEP 8.
|
- Follow PEP 8.
|
||||||
- When committing only documentation changes please include [ci skip] in the commit message to avoid running tests on Travis-CI and AppVeyor.
|
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor.
|
||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
|
|
1
.github/mergify.yml
vendored
1
.github/mergify.yml
vendored
|
@ -8,7 +8,6 @@ pull_request_rules:
|
||||||
- status-success=Docker Test Successful
|
- status-success=Docker Test Successful
|
||||||
- status-success=Windows Test Successful
|
- status-success=Windows Test Successful
|
||||||
- status-success=continuous-integration/appveyor/pr
|
- status-success=continuous-integration/appveyor/pr
|
||||||
- status-success=continuous-integration/travis-ci/pr
|
|
||||||
actions:
|
actions:
|
||||||
merge:
|
merge:
|
||||||
method: merge
|
method: merge
|
||||||
|
|
26
.github/release-drafter.yml
vendored
Normal file
26
.github/release-drafter.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
name-template: "$NEXT_MINOR_VERSION"
|
||||||
|
tag-template: "$NEXT_MINOR_VERSION"
|
||||||
|
change-template: '- $TITLE #$NUMBER [@$AUTHOR]'
|
||||||
|
|
||||||
|
categories:
|
||||||
|
- title: "Dependencies"
|
||||||
|
label: "Dependency"
|
||||||
|
- title: "Deprecations"
|
||||||
|
label: "Deprecation"
|
||||||
|
- title: "Documentation"
|
||||||
|
label: "Documentation"
|
||||||
|
- title: "Removals"
|
||||||
|
label: "Removal"
|
||||||
|
- title: "Testing"
|
||||||
|
label: "Testing"
|
||||||
|
|
||||||
|
exclude-labels:
|
||||||
|
- "changelog: skip"
|
||||||
|
|
||||||
|
template: |
|
||||||
|
|
||||||
|
https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
$CHANGES
|
23
.github/workflows/macos-install.sh
vendored
23
.github/workflows/macos-install.sh
vendored
|
@ -2,22 +2,23 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype openblas
|
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype openblas libraqm
|
||||||
|
|
||||||
PYTHONOPTIMIZE=0 pip install cffi
|
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
||||||
pip install coverage
|
python3 -m pip install coverage
|
||||||
pip install olefile
|
python3 -m pip install olefile
|
||||||
pip install -U pytest
|
python3 -m pip install -U pytest
|
||||||
pip install -U pytest-cov
|
python3 -m pip install -U pytest-cov
|
||||||
pip install pyroma
|
python3 -m pip install pyroma
|
||||||
pip install test-image-results
|
python3 -m pip install test-image-results
|
||||||
|
|
||||||
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
|
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
|
||||||
pip install numpy
|
# TODO Remove condition when numpy supports 3.10
|
||||||
|
if ! [ "$GHA_PYTHON_VERSION" == "3.10-dev" ]; then python3 -m pip install numpy ; fi
|
||||||
|
|
||||||
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
|
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
|
||||||
if [ "$GHA_PYTHON_VERSION" == "3.8" ]; then pip install -U "setuptools>=49.3.2" ; fi
|
if [ "$GHA_PYTHON_VERSION" == "3.8" ]; then python3 -m pip install -U "setuptools>=49.3.2" ; fi
|
||||||
if [ "$GHA_PYTHON_VERSION" == "3.9" ]; then pip install -U "setuptools>=49.3.2" ; fi
|
if [ "$GHA_PYTHON_VERSION" == "3.9" ]; then python3 -m pip install -U "setuptools>=49.3.2" ; fi
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
17
.github/workflows/release-drafter.yml
vendored
Normal file
17
.github/workflows/release-drafter.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
name: Release drafter
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# branches to consider in the event; optional, defaults to all
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update_release_draft:
|
||||||
|
if: github.repository == 'python-pillow/Pillow'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# Drafts your next release notes as pull requests are merged into "master"
|
||||||
|
- uses: release-drafter/release-drafter@v5
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
28
.github/workflows/test-docker.yml
vendored
28
.github/workflows/test-docker.yml
vendored
|
@ -10,19 +10,30 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
docker: [
|
docker: [
|
||||||
|
# Run slower jobs first to give them a headstart and reduce waiting time
|
||||||
|
ubuntu-20.04-focal-arm64v8,
|
||||||
|
ubuntu-20.04-focal-ppc64le,
|
||||||
|
ubuntu-20.04-focal-s390x,
|
||||||
|
# Then run the remainder
|
||||||
alpine,
|
alpine,
|
||||||
|
amazon-2-amd64,
|
||||||
arch,
|
arch,
|
||||||
ubuntu-18.04-bionic-amd64,
|
|
||||||
ubuntu-20.04-focal-amd64,
|
|
||||||
debian-10-buster-x86,
|
|
||||||
centos-6-amd64,
|
|
||||||
centos-7-amd64,
|
centos-7-amd64,
|
||||||
centos-8-amd64,
|
centos-8-amd64,
|
||||||
amazon-1-amd64,
|
debian-10-buster-x86,
|
||||||
amazon-2-amd64,
|
|
||||||
fedora-32-amd64,
|
fedora-32-amd64,
|
||||||
|
fedora-33-amd64,
|
||||||
|
ubuntu-18.04-bionic-amd64,
|
||||||
|
ubuntu-20.04-focal-amd64,
|
||||||
]
|
]
|
||||||
dockerTag: [master]
|
dockerTag: [master]
|
||||||
|
include:
|
||||||
|
- docker: "ubuntu-20.04-focal-arm64v8"
|
||||||
|
qemu-arch: "aarch64"
|
||||||
|
- docker: "ubuntu-20.04-focal-ppc64le"
|
||||||
|
qemu-arch: "ppc64le"
|
||||||
|
- docker: "ubuntu-20.04-focal-s390x"
|
||||||
|
qemu-arch: "s390x"
|
||||||
|
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
|
@ -32,6 +43,11 @@ jobs:
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python .github/workflows/system-info.py
|
run: python .github/workflows/system-info.py
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
if: "matrix.qemu-arch"
|
||||||
|
run: |
|
||||||
|
docker run --rm --privileged aptman/qus -s -- -p ${{ matrix.qemu-arch }}
|
||||||
|
|
||||||
- name: Docker pull
|
- name: Docker pull
|
||||||
run: |
|
run: |
|
||||||
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
||||||
|
|
25
.github/workflows/test-windows.yml
vendored
25
.github/workflows/test-windows.yml
vendored
|
@ -8,7 +8,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.6", "3.7", "3.8", "3.9", "pypy3"]
|
python-version: ["pypy-3.6", "pypy-3.7", "3.6", "3.7", "3.8", "3.9", "3.10-dev"]
|
||||||
architecture: ["x86", "x64"]
|
architecture: ["x86", "x64"]
|
||||||
include:
|
include:
|
||||||
- architecture: "x86"
|
- architecture: "x86"
|
||||||
|
@ -19,7 +19,9 @@ jobs:
|
||||||
platform-msbuild: "x64"
|
platform-msbuild: "x64"
|
||||||
exclude:
|
exclude:
|
||||||
# PyPy does not support 64-bit on Windows
|
# PyPy does not support 64-bit on Windows
|
||||||
- python-version: "pypy3"
|
- python-version: "pypy-3.6"
|
||||||
|
architecture: "x64"
|
||||||
|
- python-version: "pypy-3.7"
|
||||||
architecture: "x64"
|
architecture: "x64"
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
@ -55,7 +57,7 @@ jobs:
|
||||||
- name: Print build system information
|
- name: Print build system information
|
||||||
run: python .github/workflows/system-info.py
|
run: python .github/workflows/system-info.py
|
||||||
|
|
||||||
- name: pip install wheel pytest pytest-cov
|
- name: python -m pip install wheel pytest pytest-cov
|
||||||
run: python -m pip install wheel pytest pytest-cov
|
run: python -m pip install wheel pytest pytest-cov
|
||||||
|
|
||||||
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
|
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
|
||||||
|
@ -64,6 +66,7 @@ jobs:
|
||||||
run: python -m pip install -U "setuptools>=49.3.2"
|
run: python -m pip install -U "setuptools>=49.3.2"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
id: install
|
||||||
run: |
|
run: |
|
||||||
7z x winbuild\depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
7z x winbuild\depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
||||||
echo "$env:RUNNER_WORKSPACE\nasm-2.14.02" >> $env:GITHUB_PATH
|
echo "$env:RUNNER_WORKSPACE\nasm-2.14.02" >> $env:GITHUB_PATH
|
||||||
|
@ -71,7 +74,10 @@ jobs:
|
||||||
winbuild\depends\gs9533w32.exe /S
|
winbuild\depends\gs9533w32.exe /S
|
||||||
echo "C:\Program Files (x86)\gs\gs9.53.3\bin" >> $env:GITHUB_PATH
|
echo "C:\Program Files (x86)\gs\gs9.53.3\bin" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
xcopy /s winbuild\depends\test_images\* Tests\images\
|
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" }
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Cache build
|
- name: Cache build
|
||||||
|
@ -80,7 +86,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
path: winbuild\build
|
path: winbuild\build
|
||||||
key:
|
key:
|
||||||
${{ hashFiles('winbuild\build_prepare.py') }}-${{ hashFiles('.github\workflows\test-windows.yml') }}-${{ env.pythonLocation }}
|
${{ hashFiles('winbuild\build_prepare.py') }}-${{ hashFiles('.github\workflows\test-windows.yml') }}-${{ env.pythonLocation }}-${{ steps.install.outputs.vs }}
|
||||||
|
|
||||||
- name: Prepare build
|
- name: Prepare build
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
|
@ -91,25 +97,32 @@ jobs:
|
||||||
- name: Build dependencies / libjpeg-turbo
|
- name: Build dependencies / libjpeg-turbo
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_libjpeg.cmd"
|
run: "& winbuild\\build\\build_dep_libjpeg.cmd"
|
||||||
|
|
||||||
- name: Build dependencies / zlib
|
- name: Build dependencies / zlib
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_zlib.cmd"
|
run: "& winbuild\\build\\build_dep_zlib.cmd"
|
||||||
|
|
||||||
- name: Build dependencies / LibTiff
|
- name: Build dependencies / LibTiff
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_libtiff.cmd"
|
run: "& winbuild\\build\\build_dep_libtiff.cmd"
|
||||||
|
|
||||||
- name: Build dependencies / WebP
|
- name: Build dependencies / WebP
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_libwebp.cmd"
|
run: "& winbuild\\build\\build_dep_libwebp.cmd"
|
||||||
|
|
||||||
# for FreeType CBDT font support
|
# for FreeType CBDT font support
|
||||||
- name: Build dependencies / libpng
|
- name: Build dependencies / libpng
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_libpng.cmd"
|
run: "& winbuild\\build\\build_dep_libpng.cmd"
|
||||||
|
|
||||||
- name: Build dependencies / FreeType
|
- name: Build dependencies / FreeType
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_freetype.cmd"
|
run: "& winbuild\\build\\build_dep_freetype.cmd"
|
||||||
|
|
||||||
- name: Build dependencies / LCMS2
|
- name: Build dependencies / LCMS2
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_lcms2.cmd"
|
run: "& winbuild\\build\\build_dep_lcms2.cmd"
|
||||||
|
|
||||||
- name: Build dependencies / OpenJPEG
|
- name: Build dependencies / OpenJPEG
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_openjpeg.cmd"
|
run: "& winbuild\\build\\build_dep_openjpeg.cmd"
|
||||||
|
@ -123,9 +136,11 @@ jobs:
|
||||||
- name: Build dependencies / HarfBuzz
|
- name: Build dependencies / HarfBuzz
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_harfbuzz.cmd"
|
run: "& winbuild\\build\\build_dep_harfbuzz.cmd"
|
||||||
|
|
||||||
- name: Build dependencies / FriBidi
|
- name: Build dependencies / FriBidi
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_fribidi.cmd"
|
run: "& winbuild\\build\\build_dep_fribidi.cmd"
|
||||||
|
|
||||||
- name: Build dependencies / Raqm
|
- name: Build dependencies / Raqm
|
||||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||||
run: "& winbuild\\build\\build_dep_libraqm.cmd"
|
run: "& winbuild\\build\\build_dep_libraqm.cmd"
|
||||||
|
|
22
.github/workflows/test.yml
vendored
22
.github/workflows/test.yml
vendored
|
@ -13,7 +13,9 @@ jobs:
|
||||||
"macOS-latest",
|
"macOS-latest",
|
||||||
]
|
]
|
||||||
python-version: [
|
python-version: [
|
||||||
"pypy3",
|
"pypy-3.7",
|
||||||
|
"pypy-3.6",
|
||||||
|
"3.10-dev",
|
||||||
"3.9",
|
"3.9",
|
||||||
"3.8",
|
"3.8",
|
||||||
"3.7",
|
"3.7",
|
||||||
|
@ -21,9 +23,9 @@ jobs:
|
||||||
]
|
]
|
||||||
include:
|
include:
|
||||||
- python-version: "3.6"
|
- python-version: "3.6"
|
||||||
env: PYTHONOPTIMIZE=1
|
PYTHONOPTIMIZE: 1
|
||||||
- python-version: "3.7"
|
- python-version: "3.7"
|
||||||
env: PYTHONOPTIMIZE=2
|
PYTHONOPTIMIZE: 2
|
||||||
# Include new variables for Codecov
|
# Include new variables for Codecov
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
codecov-flag: GHA_Ubuntu
|
codecov-flag: GHA_Ubuntu
|
||||||
|
@ -44,7 +46,7 @@ jobs:
|
||||||
- name: Get pip cache dir
|
- name: Get pip cache dir
|
||||||
id: pip-cache
|
id: pip-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=dir::$(pip cache dir)"
|
echo "::set-output name=dir::$(python3 -m pip cache dir)"
|
||||||
|
|
||||||
- name: pip cache
|
- name: pip cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
|
@ -78,7 +80,13 @@ jobs:
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
.ci/test.sh
|
if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
|
||||||
|
xvfb-run -s '-screen 0 1024x768x24' .ci/test.sh
|
||||||
|
else
|
||||||
|
.ci/test.sh
|
||||||
|
fi
|
||||||
|
env:
|
||||||
|
PYTHONOPTIMIZE: ${{ matrix.PYTHONOPTIMIZE }}
|
||||||
|
|
||||||
- name: Prepare to upload errors
|
- name: Prepare to upload errors
|
||||||
if: failure()
|
if: failure()
|
||||||
|
@ -94,9 +102,9 @@ jobs:
|
||||||
path: Tests/errors
|
path: Tests/errors
|
||||||
|
|
||||||
- name: Docs
|
- name: Docs
|
||||||
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.8
|
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.9
|
||||||
run: |
|
run: |
|
||||||
pip install sphinx-removed-in sphinx-rtd-theme
|
python3 -m pip install sphinx-issues sphinx-removed-in sphinx-rtd-theme
|
||||||
make doccheck
|
make doccheck
|
||||||
|
|
||||||
- name: After success
|
- name: After success
|
||||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -81,6 +81,10 @@ docs/_build/
|
||||||
|
|
||||||
# Extra test images installed from pillow-depends/test_images
|
# Extra test images installed from pillow-depends/test_images
|
||||||
Tests/images/README.md
|
Tests/images/README.md
|
||||||
|
Tests/images/crash_1.tif
|
||||||
|
Tests/images/crash_2.tif
|
||||||
|
Tests/images/string_dimension.tiff
|
||||||
|
Tests/images/jpeg2000
|
||||||
Tests/images/msp
|
Tests/images/msp
|
||||||
Tests/images/picins
|
Tests/images/picins
|
||||||
Tests/images/sunraster
|
Tests/images/sunraster
|
||||||
|
|
|
@ -8,7 +8,7 @@ repos:
|
||||||
files: \.py$
|
files: \.py$
|
||||||
types: []
|
types: []
|
||||||
|
|
||||||
- repo: https://github.com/timothycrosley/isort
|
- repo: https://github.com/PyCQA/isort
|
||||||
rev: 377d260ffa6f746693f97b46d95025afc4bd8275 # frozen: 5.4.2
|
rev: 377d260ffa6f746693f97b46d95025afc4bd8275 # frozen: 5.4.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
|
|
70
.travis.yml
70
.travis.yml
|
@ -1,70 +0,0 @@
|
||||||
dist: xenial
|
|
||||||
language: python
|
|
||||||
cache:
|
|
||||||
pip: true
|
|
||||||
directories:
|
|
||||||
- $HOME/.cache/pre-commit
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
irc: "chat.freenode.net#pil"
|
|
||||||
|
|
||||||
# Run fast lint first to get fast feedback.
|
|
||||||
# Run slower CPUs next, to give them a headstart and reduce waiting time.
|
|
||||||
# Then run the remainder.
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
include:
|
|
||||||
- python: "3.6"
|
|
||||||
name: "Lint"
|
|
||||||
env: LINT="true"
|
|
||||||
|
|
||||||
- python: "3.6"
|
|
||||||
arch: arm64
|
|
||||||
- python: "3.7"
|
|
||||||
arch: ppc64le
|
|
||||||
- python: "3.8"
|
|
||||||
arch: s390x
|
|
||||||
|
|
||||||
- python: "pypy3.6-7.3.1"
|
|
||||||
name: "PyPy3 Xenial"
|
|
||||||
- python: "3.9-dev"
|
|
||||||
name: "3.9-dev Xenial"
|
|
||||||
services: xvfb
|
|
||||||
- python: "3.8"
|
|
||||||
name: "3.8 Xenial"
|
|
||||||
services: xvfb
|
|
||||||
- python: '3.7'
|
|
||||||
name: "3.7 Xenial PYTHONOPTIMIZE=2"
|
|
||||||
env: PYTHONOPTIMIZE=2
|
|
||||||
services: xvfb
|
|
||||||
- python: '3.6'
|
|
||||||
name: "3.6 Xenial PYTHONOPTIMIZE=1"
|
|
||||||
env: PYTHONOPTIMIZE=1
|
|
||||||
services: xvfb
|
|
||||||
|
|
||||||
allow_failures:
|
|
||||||
- python: "3.9-dev"
|
|
||||||
|
|
||||||
install:
|
|
||||||
- |
|
|
||||||
if [ "$LINT" == "true" ]; then
|
|
||||||
pip install tox
|
|
||||||
else
|
|
||||||
.ci/install.sh;
|
|
||||||
fi
|
|
||||||
|
|
||||||
script:
|
|
||||||
- |
|
|
||||||
if [ "$LINT" == "true" ]; then
|
|
||||||
tox -e lint
|
|
||||||
else
|
|
||||||
.ci/build.sh
|
|
||||||
.ci/test.sh
|
|
||||||
fi
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- |
|
|
||||||
if [ "$LINT" == "" ]; then
|
|
||||||
.ci/after_success.sh
|
|
||||||
fi
|
|
72
CHANGES.rst
72
CHANGES.rst
|
@ -2,12 +2,78 @@
|
||||||
Changelog (Pillow)
|
Changelog (Pillow)
|
||||||
==================
|
==================
|
||||||
|
|
||||||
8.1.0 (unreleased)
|
8.1.0 (2020-01-02)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
- Fix TIFF OOB Write error. CVE-2020-35654 #5175
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Fix for Read Overflow in PCX Decoding. CVE-2020-35653 #5174
|
||||||
|
[wiredfool, radarhere]
|
||||||
|
|
||||||
|
- Fix for SGI Decode buffer overrun. CVE-2020-35655 #5173
|
||||||
|
[wiredfool, radarhere]
|
||||||
|
|
||||||
|
- Fix OOB Read when saving GIF of xsize=1 #5149
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Makefile updates #5159
|
||||||
|
[wiredfool, radarhere]
|
||||||
|
|
||||||
|
- Add support for PySide6 #5161
|
||||||
|
[hugovk]
|
||||||
|
|
||||||
|
- Use disposal settings from previous frame in APNG #5126
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Added exception explaining that _repr_png_ saves to PNG #5139
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Use previous disposal method in GIF load_end #5125
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Allow putpalette to accept 1024 integers to include alpha values #5089
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fix OOB Read when writing TIFF with custom Metadata #5148
|
||||||
|
[wiredfool]
|
||||||
|
|
||||||
|
- Added append_images support for ICO #4568
|
||||||
|
[ziplantil, radarhere]
|
||||||
|
|
||||||
|
- Block TIFFTAG_SUBIFD #5120
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed dereferencing potential null pointers #5108, #5111
|
||||||
|
[cgohlke, radarhere]
|
||||||
|
|
||||||
|
- Deprecate FreeType 2.7 #5098
|
||||||
|
[hugovk, radarhere]
|
||||||
|
|
||||||
|
- Moved warning to end of execution #4965
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Removed unused fromstring and tostring C methods #5026
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- init() if one of the formats is unrecognised #5037
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Moved string_dimension CVE image to pillow-depends #4993
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
- Support raw rgba8888 for DDS #4760
|
- Support raw rgba8888 for DDS #4760
|
||||||
[qiankanglai]
|
[qiankanglai]
|
||||||
|
|
||||||
|
8.0.1 (2020-10-22)
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Update FreeType used in binary wheels to 2.10.4 to fix CVE-2020-15999.
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Moved string_dimension image to pillow-depends #4993
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
8.0.0 (2020-10-15)
|
8.0.0 (2020-10-15)
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
@ -4030,8 +4096,8 @@ Changelog (Pillow)
|
||||||
1.0 (07/30/2010)
|
1.0 (07/30/2010)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- Remove support for ``import Image``, etc. from the standard namespace. ``from PIL import Image`` etc. now required.
|
- Remove support for ``import Image``. ``from PIL import Image`` now required.
|
||||||
- Forked PIL based on `Hanno Schlichting's re-packaging <https://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
|
- Forked PIL based on `Chris McDonough and Hanno Schlichting's setuptools compatible re-packaging <https://dist.plone.org/thirdparty/PIL-1.1.7.tar.gz>`_
|
||||||
[aclark4life]
|
[aclark4life]
|
||||||
|
|
||||||
Pre-fork
|
Pre-fork
|
||||||
|
|
4
LICENSE
4
LICENSE
|
@ -5,9 +5,9 @@ The Python Imaging Library (PIL) is
|
||||||
|
|
||||||
Pillow is the friendly PIL fork. It is
|
Pillow is the friendly PIL fork. It is
|
||||||
|
|
||||||
Copyright © 2010-2020 by Alex Clark and contributors
|
Copyright © 2010-2021 by Alex Clark and contributors
|
||||||
|
|
||||||
Like PIL, Pillow is licensed under the open source PIL Software License:
|
Like PIL, Pillow is licensed under the open source HPND License:
|
||||||
|
|
||||||
By obtaining, using, and/or copying this software and/or its associated
|
By obtaining, using, and/or copying this software and/or its associated
|
||||||
documentation, you agree that you have read, understood, and will comply
|
documentation, you agree that you have read, understood, and will comply
|
||||||
|
|
|
@ -18,6 +18,7 @@ graft docs
|
||||||
|
|
||||||
# build/src control detritus
|
# build/src control detritus
|
||||||
exclude .appveyor.yml
|
exclude .appveyor.yml
|
||||||
|
exclude .clang-format
|
||||||
exclude .coveragerc
|
exclude .coveragerc
|
||||||
exclude .editorconfig
|
exclude .editorconfig
|
||||||
exclude .readthedocs.yml
|
exclude .readthedocs.yml
|
||||||
|
|
31
Makefile
31
Makefile
|
@ -1,4 +1,4 @@
|
||||||
.DEFAULT_GOAL := release-test
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
|
@ -7,13 +7,6 @@ clean:
|
||||||
rm -r build || true
|
rm -r build || true
|
||||||
find . -name __pycache__ | xargs rm -r || true
|
find . -name __pycache__ | xargs rm -r || true
|
||||||
|
|
||||||
BRANCHES=`git branch -a | grep -v HEAD | grep -v master | grep remote`
|
|
||||||
.PHONY: co
|
|
||||||
co:
|
|
||||||
-for i in $(BRANCHES) ; do \
|
|
||||||
git checkout -t $$i ; \
|
|
||||||
done
|
|
||||||
|
|
||||||
.PHONY: coverage
|
.PHONY: coverage
|
||||||
coverage:
|
coverage:
|
||||||
pytest -qq
|
pytest -qq
|
||||||
|
@ -33,7 +26,7 @@ doccheck:
|
||||||
|
|
||||||
.PHONY: docserve
|
.PHONY: docserve
|
||||||
docserve:
|
docserve:
|
||||||
cd docs/_build/html && python3 -mSimpleHTTPServer 2> /dev/null&
|
cd docs/_build/html && python3 -m http.server 2> /dev/null&
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help:
|
help:
|
||||||
|
@ -47,7 +40,9 @@ help:
|
||||||
@echo " install make and install"
|
@echo " install make and install"
|
||||||
@echo " install-coverage make and install with C coverage"
|
@echo " install-coverage make and install with C coverage"
|
||||||
@echo " install-req install documentation and test dependencies"
|
@echo " install-req install documentation and test dependencies"
|
||||||
@echo " install-venv install in virtualenv"
|
@echo " install-venv (deprecated) install in virtualenv"
|
||||||
|
@echo " lint run the lint checks"
|
||||||
|
@echo " lint-fix run black and isort to (mostly) fix lint issues."
|
||||||
@echo " release-test run code and package tests before release"
|
@echo " release-test run code and package tests before release"
|
||||||
@echo " test run tests on installed pillow"
|
@echo " test run tests on installed pillow"
|
||||||
@echo " upload build and upload sdists to PyPI"
|
@echo " upload build and upload sdists to PyPI"
|
||||||
|
@ -81,6 +76,7 @@ install-req:
|
||||||
|
|
||||||
.PHONY: install-venv
|
.PHONY: install-venv
|
||||||
install-venv:
|
install-venv:
|
||||||
|
echo "'install-venv' is deprecated and will be removed in a future Pillow release"
|
||||||
virtualenv .
|
virtualenv .
|
||||||
bin/pip install -r requirements.txt
|
bin/pip install -r requirements.txt
|
||||||
|
|
||||||
|
@ -96,7 +92,7 @@ release-test:
|
||||||
python3 -m pytest -qq
|
python3 -m pytest -qq
|
||||||
check-manifest
|
check-manifest
|
||||||
pyroma .
|
pyroma .
|
||||||
viewdoc
|
$(MAKE) readme
|
||||||
|
|
||||||
.PHONY: sdist
|
.PHONY: sdist
|
||||||
sdist:
|
sdist:
|
||||||
|
@ -108,4 +104,15 @@ test:
|
||||||
|
|
||||||
.PHONY: readme
|
.PHONY: readme
|
||||||
readme:
|
readme:
|
||||||
viewdoc
|
python3 setup.py --long-description | markdown2 > .long-description.html && open .long-description.html
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint:
|
||||||
|
tox --help > /dev/null || python3 -m pip install tox
|
||||||
|
tox -e lint
|
||||||
|
|
||||||
|
.PHONY: lint-fix
|
||||||
|
lint-fix:
|
||||||
|
black --target-version py36 .
|
||||||
|
isort .
|
||||||
|
|
16
README.md
16
README.md
|
@ -24,15 +24,6 @@ As of 2019, Pillow development is
|
||||||
<tr>
|
<tr>
|
||||||
<th>tests</th>
|
<th>tests</th>
|
||||||
<td>
|
<td>
|
||||||
<a href="https://travis-ci.org/python-pillow/Pillow"><img
|
|
||||||
alt="Travis CI build status (Linux)"
|
|
||||||
src="https://img.shields.io/travis/python-pillow/Pillow/master.svg?label=Linux%20build"></a>
|
|
||||||
<a href="https://travis-ci.org/python-pillow/pillow-wheels"><img
|
|
||||||
alt="Travis CI build status (macOS)"
|
|
||||||
src="https://img.shields.io/travis/python-pillow/pillow-wheels/master.svg?label=macOS%20build"></a>
|
|
||||||
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
|
|
||||||
alt="AppVeyor CI build status (Windows)"
|
|
||||||
src="https://img.shields.io/appveyor/build/python-pillow/Pillow/master.svg?label=Windows%20build"></a>
|
|
||||||
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint"><img
|
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint"><img
|
||||||
alt="GitHub Actions build status (Lint)"
|
alt="GitHub Actions build status (Lint)"
|
||||||
src="https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg"></a>
|
||||||
|
@ -45,6 +36,12 @@ As of 2019, Pillow development is
|
||||||
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Docker%22"><img
|
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Docker%22"><img
|
||||||
alt="GitHub Actions build status (Test Docker)"
|
alt="GitHub Actions build status (Test Docker)"
|
||||||
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg"></a>
|
||||||
|
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
|
||||||
|
alt="AppVeyor CI build status (Windows)"
|
||||||
|
src="https://img.shields.io/appveyor/build/python-pillow/Pillow/master.svg?label=Windows%20build"></a>
|
||||||
|
<a href="https://travis-ci.com/github/python-pillow/pillow-wheels"><img
|
||||||
|
alt="Travis CI build status (macOS)"
|
||||||
|
src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/master.svg?label=macOS%20build"></a>
|
||||||
<a href="https://codecov.io/gh/python-pillow/Pillow"><img
|
<a href="https://codecov.io/gh/python-pillow/Pillow"><img
|
||||||
alt="Code coverage"
|
alt="Code coverage"
|
||||||
src="https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg"></a>
|
src="https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg"></a>
|
||||||
|
@ -96,6 +93,7 @@ The core image library is designed for fast access to data stored in a few basic
|
||||||
- [Contribute](https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md)
|
- [Contribute](https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md)
|
||||||
- [Issues](https://github.com/python-pillow/Pillow/issues)
|
- [Issues](https://github.com/python-pillow/Pillow/issues)
|
||||||
- [Pull requests](https://github.com/python-pillow/Pillow/pulls)
|
- [Pull requests](https://github.com/python-pillow/Pillow/pulls)
|
||||||
|
- [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html)
|
||||||
- [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst)
|
- [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst)
|
||||||
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork)
|
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork)
|
||||||
|
|
||||||
|
|
49
RELEASING.md
49
RELEASING.md
|
@ -1,16 +1,16 @@
|
||||||
# Release Checklist
|
# Release Checklist
|
||||||
|
|
||||||
|
See https://pillow.readthedocs.io/en/stable/releasenotes/versioning.html for
|
||||||
|
information about how the version numbers line up with releases.
|
||||||
|
|
||||||
## Main Release
|
## Main Release
|
||||||
|
|
||||||
Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
|
|
||||||
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||||
* [ ] Develop and prepare release in `master` branch.
|
* [ ] Develop and prepare release in `master` branch.
|
||||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions),
|
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `master` branch.
|
||||||
[Travis CI](https://travis-ci.org/github/python-pillow/Pillow) and
|
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI and GitHub Actions.
|
||||||
[AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm
|
|
||||||
passing tests in `master` branch.
|
|
||||||
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI.
|
|
||||||
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
|
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||||
* [ ] Update `CHANGES.rst`.
|
* [ ] Update `CHANGES.rst`.
|
||||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||||
|
@ -21,13 +21,18 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
git push --all
|
git push --all
|
||||||
git push --tags
|
git push --tags
|
||||||
```
|
```
|
||||||
* [ ] Create source distributions e.g.:
|
* [ ] Create and check source distribution:
|
||||||
```bash
|
```bash
|
||||||
make sdist
|
make sdist
|
||||||
|
twine check dist/*
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions)
|
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions)
|
||||||
* [ ] Upload all binaries and source distributions e.g. `twine upload dist/Pillow-5.2.0*`
|
* [ ] Check and upload all binaries and source distributions e.g.:
|
||||||
* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new)
|
```bash
|
||||||
|
twine check dist/*
|
||||||
|
twine upload dist/Pillow-5.2.0*
|
||||||
|
```
|
||||||
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
||||||
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py`
|
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py`
|
||||||
|
|
||||||
## Point Release
|
## Point Release
|
||||||
|
@ -40,14 +45,11 @@ Released as needed for security, installation or critical bug fixes.
|
||||||
```bash
|
```bash
|
||||||
git checkout -t remotes/origin/5.2.x
|
git checkout -t remotes/origin/5.2.x
|
||||||
```
|
```
|
||||||
* [ ] Cherry pick individual commits from `master` branch to release branch e.g. `5.2.x`.
|
* [ ] Cherry pick individual commits from `master` branch to release branch e.g. `5.2.x`, then `git push`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions),
|
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in release branch e.g. `5.2.x`.
|
||||||
[Travis CI](https://travis-ci.org/github/python-pillow/Pillow) and
|
|
||||||
[AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm
|
|
||||||
passing tests in release branch e.g. `5.2.x`.
|
|
||||||
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
|
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||||
* [ ] Run pre-release check via `make release-test`.
|
* [ ] Run pre-release check via `make release-test`.
|
||||||
* [ ] Create tag for release e.g.:
|
* [ ] Create tag for release e.g.:
|
||||||
|
@ -56,13 +58,18 @@ Released as needed for security, installation or critical bug fixes.
|
||||||
git push
|
git push
|
||||||
git push --tags
|
git push --tags
|
||||||
```
|
```
|
||||||
* [ ] Create source distributions e.g.:
|
* [ ] Create and check source distribution:
|
||||||
```bash
|
```bash
|
||||||
make sdist
|
make sdist
|
||||||
|
twine check dist/*
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions)
|
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions)
|
||||||
* [ ] Upload all binaries and source distributions e.g. `twine upload dist/Pillow-5.2.1*`
|
* [ ] Check and upload all binaries and source distributions e.g.:
|
||||||
* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new)
|
```bash
|
||||||
|
twine check dist/*
|
||||||
|
twine upload dist/Pillow-5.2.1*
|
||||||
|
```
|
||||||
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
||||||
|
|
||||||
## Embargoed Release
|
## Embargoed Release
|
||||||
|
|
||||||
|
@ -81,18 +88,19 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
git push origin 2.5.x
|
git push origin 2.5.x
|
||||||
git push origin --tags
|
git push origin --tags
|
||||||
```
|
```
|
||||||
* [ ] Create source distributions e.g.:
|
* [ ] Create and check source distribution:
|
||||||
```bash
|
```bash
|
||||||
make sdist
|
make sdist
|
||||||
|
twine check dist/*
|
||||||
```
|
```
|
||||||
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions)
|
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions)
|
||||||
* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new)
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases)
|
||||||
|
|
||||||
## Binary Distributions
|
## Binary Distributions
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
* [ ] Contact `@cgohlke` for Windows binaries via release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174.
|
* [ ] Contact `@cgohlke` for Windows binaries via release ticket e.g. https://github.com/python-pillow/Pillow/issues/1174.
|
||||||
* [ ] Download and extract tarball from `@cgohlke` and `twine upload *`.
|
* [ ] Download and extract tarball from `@cgohlke` and copy into `dist/`
|
||||||
|
|
||||||
### Mac and Linux
|
### Mac and Linux
|
||||||
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
||||||
|
@ -101,7 +109,8 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
cd pillow-wheels
|
cd pillow-wheels
|
||||||
./update-pillow-tag.sh [[release tag]]
|
./update-pillow-tag.sh [[release tag]]
|
||||||
```
|
```
|
||||||
* [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases).
|
* [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases)
|
||||||
|
and copy into `dist/`
|
||||||
|
|
||||||
## Publicize Release
|
## Publicize Release
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from PIL import PyAccess
|
||||||
|
|
||||||
from .helper import hopper
|
from .helper import hopper
|
||||||
|
|
||||||
# Not running this test by default. No DOS against Travis CI.
|
# Not running this test by default. No DOS against CI.
|
||||||
|
|
||||||
|
|
||||||
def iterate_get(size, access):
|
def iterate_get(size, access):
|
||||||
|
|
|
@ -11,6 +11,8 @@ BungeeColor-Regular_colr_Windows.ttf, from https://github.com/djrrb/bungee
|
||||||
|
|
||||||
All of the above fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to.
|
All of the above fonts are published under the SIL Open Font License (OFL) v1.1 (http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL), which allows you to copy, modify, and redistribute them if you need to.
|
||||||
|
|
||||||
|
FreeMono.ttf is licensed under GPLv3, with the GPL font exception.
|
||||||
|
|
||||||
OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
OpenSansCondensed-LightItalic.tt, from https://fonts.google.com/specimen/Open+Sans, under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
||||||
DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range.
|
DejaVuSans-24-{1,2,4,8}-stripped.ttf are based on DejaVuSans.ttf converted using FontForge to add bitmap strikes and keep only the ASCII range.
|
||||||
|
|
|
@ -270,7 +270,7 @@ def on_github_actions():
|
||||||
|
|
||||||
|
|
||||||
def on_ci():
|
def on_ci():
|
||||||
# GitHub Actions, Travis and AppVeyor have "CI"
|
# GitHub Actions and AppVeyor have "CI"
|
||||||
return "CI" in os.environ
|
return "CI" in os.environ
|
||||||
|
|
||||||
|
|
||||||
|
@ -278,6 +278,12 @@ def is_big_endian():
|
||||||
return sys.byteorder == "big"
|
return sys.byteorder == "big"
|
||||||
|
|
||||||
|
|
||||||
|
def is_ppc64le():
|
||||||
|
import platform
|
||||||
|
|
||||||
|
return platform.machine() == "ppc64le"
|
||||||
|
|
||||||
|
|
||||||
def is_win32():
|
def is_win32():
|
||||||
return sys.platform.startswith("win32")
|
return sys.platform.startswith("win32")
|
||||||
|
|
||||||
|
|
BIN
Tests/images/apng/dispose_op_previous_frame.png
Normal file
BIN
Tests/images/apng/dispose_op_previous_frame.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 582 B |
BIN
Tests/images/crash-2020-10-test.tif
Normal file
BIN
Tests/images/crash-2020-10-test.tif
Normal file
Binary file not shown.
BIN
Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi
Normal file
BIN
Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi
Normal file
Binary file not shown.
BIN
Tests/images/dispose_none_load_end.gif
Normal file
BIN
Tests/images/dispose_none_load_end.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
BIN
Tests/images/dispose_none_load_end_second.gif
Normal file
BIN
Tests/images/dispose_none_load_end_second.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
Tests/images/ossfuzz-4836216264589312.pcx
Normal file
BIN
Tests/images/ossfuzz-4836216264589312.pcx
Normal file
Binary file not shown.
BIN
Tests/images/ossfuzz-5730089102868480.sgi
Normal file
BIN
Tests/images/ossfuzz-5730089102868480.sgi
Normal file
Binary file not shown.
|
@ -11,7 +11,7 @@ ORIGINAL_LIMIT = Image.MAX_IMAGE_PIXELS
|
||||||
|
|
||||||
class TestDecompressionBomb:
|
class TestDecompressionBomb:
|
||||||
@classmethod
|
@classmethod
|
||||||
def teardown_class(self):
|
def teardown_class(cls):
|
||||||
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
|
Image.MAX_IMAGE_PIXELS = ORIGINAL_LIMIT
|
||||||
|
|
||||||
def test_no_warning_small_file(self):
|
def test_no_warning_small_file(self):
|
||||||
|
|
|
@ -105,6 +105,31 @@ def test_apng_dispose_region():
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def test_apng_dispose_op_previous_frame():
|
||||||
|
# Test that the dispose settings being used are from the previous frame
|
||||||
|
#
|
||||||
|
# Image created with:
|
||||||
|
# red = Image.new("RGBA", (128, 64), (255, 0, 0, 255))
|
||||||
|
# green = red.copy()
|
||||||
|
# green.paste(Image.new("RGBA", (64, 32), (0, 255, 0, 255)))
|
||||||
|
# blue = red.copy()
|
||||||
|
# blue.paste(Image.new("RGBA", (64, 32), (0, 255, 0, 255)), (64, 32))
|
||||||
|
#
|
||||||
|
# red.save(
|
||||||
|
# "Tests/images/apng/dispose_op_previous_frame.png",
|
||||||
|
# save_all=True,
|
||||||
|
# append_images=[green, blue],
|
||||||
|
# disposal=[
|
||||||
|
# PngImagePlugin.APNG_DISPOSE_OP_NONE,
|
||||||
|
# PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS,
|
||||||
|
# PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS
|
||||||
|
# ],
|
||||||
|
# )
|
||||||
|
with Image.open("Tests/images/apng/dispose_op_previous_frame.png") as im:
|
||||||
|
im.seek(im.n_frames - 1)
|
||||||
|
assert im.getpixel((0, 0)) == (255, 0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
def test_apng_dispose_op_background_p_mode():
|
def test_apng_dispose_op_background_p_mode():
|
||||||
with Image.open("Tests/images/apng/dispose_op_background_p_mode.png") as im:
|
with Image.open("Tests/images/apng/dispose_op_background_p_mode.png") as im:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
|
|
|
@ -74,10 +74,10 @@ def test_optimize():
|
||||||
im.save(test_file, "GIF", optimize=optimize)
|
im.save(test_file, "GIF", optimize=optimize)
|
||||||
return len(test_file.getvalue())
|
return len(test_file.getvalue())
|
||||||
|
|
||||||
assert test_grayscale(0) == 800
|
assert test_grayscale(0) == 799
|
||||||
assert test_grayscale(1) == 44
|
assert test_grayscale(1) == 43
|
||||||
assert test_bilevel(0) == 800
|
assert test_bilevel(0) == 799
|
||||||
assert test_bilevel(1) == 800
|
assert test_bilevel(1) == 799
|
||||||
|
|
||||||
|
|
||||||
def test_optimize_correctness():
|
def test_optimize_correctness():
|
||||||
|
@ -307,6 +307,20 @@ def test_dispose_none():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_dispose_none_load_end():
|
||||||
|
# Test image created with:
|
||||||
|
#
|
||||||
|
# im = Image.open("transparent.gif")
|
||||||
|
# im_rotated = im.rotate(180)
|
||||||
|
# im.save("dispose_none_load_end.gif",
|
||||||
|
# save_all=True, append_images=[im_rotated], disposal=[1,2])
|
||||||
|
with Image.open("Tests/images/dispose_none_load_end.gif") as img:
|
||||||
|
img.seek(1)
|
||||||
|
|
||||||
|
with Image.open("Tests/images/dispose_none_load_end_second.gif") as expected:
|
||||||
|
assert_image_equal(img, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_dispose_background():
|
def test_dispose_background():
|
||||||
with Image.open("Tests/images/dispose_bgnd.gif") as img:
|
with Image.open("Tests/images/dispose_bgnd.gif") as img:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -86,6 +86,20 @@ def test_only_save_relevant_sizes(tmp_path):
|
||||||
assert im_saved.info["sizes"] == {(16, 16), (24, 24), (32, 32), (48, 48)}
|
assert im_saved.info["sizes"] == {(16, 16), (24, 24), (32, 32), (48, 48)}
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_append_images(tmp_path):
|
||||||
|
# append_images should be used for scaled down versions of the image
|
||||||
|
im = hopper("RGBA")
|
||||||
|
provided_im = Image.new("RGBA", (32, 32), (255, 0, 0))
|
||||||
|
outfile = str(tmp_path / "temp_saved_multi_icon.ico")
|
||||||
|
im.save(outfile, sizes=[(32, 32), (128, 128)], append_images=[provided_im])
|
||||||
|
|
||||||
|
with Image.open(outfile) as reread:
|
||||||
|
assert_image_equal(reread, hopper("RGBA"))
|
||||||
|
|
||||||
|
reread.size = (32, 32)
|
||||||
|
assert_image_equal(reread, provided_im)
|
||||||
|
|
||||||
|
|
||||||
def test_unexpected_size():
|
def test_unexpected_size():
|
||||||
# This image has been manually hexedited to state that it is 16x32
|
# This image has been manually hexedited to state that it is 16x32
|
||||||
# while the image within is still 16x16
|
# while the image within is still 16x16
|
||||||
|
|
|
@ -16,6 +16,7 @@ from .helper import (
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
assert_image_similar_tofile,
|
assert_image_similar_tofile,
|
||||||
hopper,
|
hopper,
|
||||||
|
is_big_endian,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -813,11 +814,13 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
||||||
|
|
||||||
|
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
||||||
def test_strip_ycbcr_jpeg_2x2_sampling(self):
|
def test_strip_ycbcr_jpeg_2x2_sampling(self):
|
||||||
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
|
infile = "Tests/images/tiff_strip_ycbcr_jpeg_2x2_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
||||||
|
|
||||||
|
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
||||||
def test_strip_ycbcr_jpeg_1x1_sampling(self):
|
def test_strip_ycbcr_jpeg_1x1_sampling(self):
|
||||||
infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
|
infile = "Tests/images/tiff_strip_ycbcr_jpeg_1x1_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
|
@ -828,16 +831,19 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
assert_image_similar_tofile(im, "Tests/images/pil_sample_cmyk.jpg", 0.5)
|
||||||
|
|
||||||
|
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
||||||
def test_tiled_ycbcr_jpeg_1x1_sampling(self):
|
def test_tiled_ycbcr_jpeg_1x1_sampling(self):
|
||||||
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
|
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_1x1_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/flower2.jpg")
|
assert_image_equal_tofile(im, "Tests/images/flower2.jpg")
|
||||||
|
|
||||||
|
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
||||||
def test_tiled_ycbcr_jpeg_2x2_sampling(self):
|
def test_tiled_ycbcr_jpeg_2x2_sampling(self):
|
||||||
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
|
infile = "Tests/images/tiff_tiled_ycbcr_jpeg_2x2_sampling.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
assert_image_similar_tofile(im, "Tests/images/flower.jpg", 0.5)
|
||||||
|
|
||||||
|
@pytest.mark.xfail(is_big_endian(), reason="Fails on big-endian")
|
||||||
def test_old_style_jpeg(self):
|
def test_old_style_jpeg(self):
|
||||||
infile = "Tests/images/old-style-jpeg-compression.tif"
|
infile = "Tests/images/old-style-jpeg-compression.tif"
|
||||||
with Image.open(infile) as im:
|
with Image.open(infile) as im:
|
||||||
|
|
|
@ -537,6 +537,12 @@ class TestFilePng:
|
||||||
assert repr_png.format == "PNG"
|
assert repr_png.format == "PNG"
|
||||||
assert_image_equal(im, repr_png)
|
assert_image_equal(im, repr_png)
|
||||||
|
|
||||||
|
def test_repr_png_error(self):
|
||||||
|
im = hopper("F")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im._repr_png_()
|
||||||
|
|
||||||
def test_chunk_order(self, tmp_path):
|
def test_chunk_order(self, tmp_path):
|
||||||
with Image.open("Tests/images/icc_profile.png") as im:
|
with Image.open("Tests/images/icc_profile.png") as im:
|
||||||
test_file = str(tmp_path / "temp.png")
|
test_file = str(tmp_path / "temp.png")
|
||||||
|
|
|
@ -4,7 +4,7 @@ from io import BytesIO
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, TiffImagePlugin
|
from PIL import Image, TiffImagePlugin
|
||||||
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
|
from PIL.TiffImagePlugin import RESOLUTION_UNIT, SUBIFD, X_RESOLUTION, Y_RESOLUTION
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -161,6 +161,14 @@ class TestFileTiff:
|
||||||
reloaded.load()
|
reloaded.load()
|
||||||
assert (round(dpi), round(dpi)) == reloaded.info["dpi"]
|
assert (round(dpi), round(dpi)) == reloaded.info["dpi"]
|
||||||
|
|
||||||
|
def test_subifd(self, tmp_path):
|
||||||
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
with Image.open("Tests/images/g4_orientation_6.tif") as im:
|
||||||
|
im.tag_v2[SUBIFD] = 10000
|
||||||
|
|
||||||
|
# Should not segfault
|
||||||
|
im.save(outfile)
|
||||||
|
|
||||||
def test_save_setting_missing_resolution(self):
|
def test_save_setting_missing_resolution(self):
|
||||||
b = BytesIO()
|
b = BytesIO()
|
||||||
Image.open("Tests/images/10ct_32bit_128.tiff").save(
|
Image.open("Tests/images/10ct_32bit_128.tiff").save(
|
||||||
|
|
|
@ -775,26 +775,34 @@ class TestImage:
|
||||||
with pytest.warns(DeprecationWarning):
|
with pytest.warns(DeprecationWarning):
|
||||||
assert test_module.PILLOW_VERSION > "7.0.0"
|
assert test_module.PILLOW_VERSION > "7.0.0"
|
||||||
|
|
||||||
def test_overrun(self):
|
@pytest.mark.parametrize(
|
||||||
"""For overrun completeness, test as:
|
"path",
|
||||||
valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c
|
[
|
||||||
"""
|
|
||||||
for file in [
|
|
||||||
"fli_overrun.bin",
|
"fli_overrun.bin",
|
||||||
"sgi_overrun.bin",
|
"sgi_overrun.bin",
|
||||||
"sgi_overrun_expandrow.bin",
|
"sgi_overrun_expandrow.bin",
|
||||||
"sgi_overrun_expandrow2.bin",
|
"sgi_overrun_expandrow2.bin",
|
||||||
"pcx_overrun.bin",
|
"pcx_overrun.bin",
|
||||||
"pcx_overrun2.bin",
|
"pcx_overrun2.bin",
|
||||||
|
"ossfuzz-4836216264589312.pcx",
|
||||||
"01r_00.pcx",
|
"01r_00.pcx",
|
||||||
]:
|
],
|
||||||
with Image.open(os.path.join("Tests/images", file)) as im:
|
)
|
||||||
try:
|
def test_overrun(self, path):
|
||||||
im.load()
|
"""For overrun completeness, test as:
|
||||||
assert False
|
valgrind pytest -qq Tests/test_image.py::TestImage::test_overrun | grep decode.c
|
||||||
except OSError as e:
|
"""
|
||||||
assert str(e) == "buffer overrun when reading image file"
|
with Image.open(os.path.join("Tests/images", path)) as im:
|
||||||
|
try:
|
||||||
|
im.load()
|
||||||
|
assert False
|
||||||
|
except OSError as e:
|
||||||
|
buffer_overrun = str(e) == "buffer overrun when reading image file"
|
||||||
|
truncated = "image file is truncated" in str(e)
|
||||||
|
|
||||||
|
assert buffer_overrun or truncated
|
||||||
|
|
||||||
|
def test_fli_overrun2(self):
|
||||||
with Image.open("Tests/images/fli_overrun2.bin") as im:
|
with Image.open("Tests/images/fli_overrun2.bin") as im:
|
||||||
try:
|
try:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import ImagePalette
|
from PIL import Image, ImagePalette
|
||||||
|
|
||||||
from .helper import hopper
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
|
||||||
def test_putpalette():
|
def test_putpalette():
|
||||||
|
@ -39,3 +39,20 @@ def test_imagepalette():
|
||||||
im.putpalette(ImagePalette.random())
|
im.putpalette(ImagePalette.random())
|
||||||
im.putpalette(ImagePalette.sepia())
|
im.putpalette(ImagePalette.sepia())
|
||||||
im.putpalette(ImagePalette.wedge())
|
im.putpalette(ImagePalette.wedge())
|
||||||
|
|
||||||
|
|
||||||
|
def test_putpalette_with_alpha_values():
|
||||||
|
with Image.open("Tests/images/transparent.gif") as im:
|
||||||
|
expected = im.convert("RGBA")
|
||||||
|
|
||||||
|
palette = im.getpalette()
|
||||||
|
transparency = im.info.pop("transparency")
|
||||||
|
|
||||||
|
palette_with_alpha_values = []
|
||||||
|
for i in range(256):
|
||||||
|
color = palette[i * 3 : i * 3 + 3]
|
||||||
|
alpha = 0 if i == transparency else 255
|
||||||
|
palette_with_alpha_values += color + [alpha]
|
||||||
|
im.putpalette(palette_with_alpha_values, "RGBA")
|
||||||
|
|
||||||
|
assert_image_equal(im.convert("RGBA"), expected)
|
||||||
|
|
|
@ -2,7 +2,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .helper import assert_image, assert_image_similar, hopper
|
from .helper import assert_image, assert_image_similar, hopper, is_ppc64le
|
||||||
|
|
||||||
|
|
||||||
def test_sanity():
|
def test_sanity():
|
||||||
|
@ -17,11 +17,12 @@ def test_sanity():
|
||||||
assert_image_similar(converted.convert("RGB"), image, 60)
|
assert_image_similar(converted.convert("RGB"), image, 60)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(is_ppc64le(), reason="failing on ppc64le on GHA")
|
||||||
def test_libimagequant_quantize():
|
def test_libimagequant_quantize():
|
||||||
image = hopper()
|
image = hopper()
|
||||||
try:
|
try:
|
||||||
converted = image.quantize(100, Image.LIBIMAGEQUANT)
|
converted = image.quantize(100, Image.LIBIMAGEQUANT)
|
||||||
except ValueError as ex:
|
except ValueError as ex: # pragma: no cover
|
||||||
if "dependency" in str(ex).lower():
|
if "dependency" in str(ex).lower():
|
||||||
pytest.skip("libimagequant support not available")
|
pytest.skip("libimagequant support not available")
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -32,58 +32,6 @@ pytestmark = skip_unless_feature("freetype2")
|
||||||
class TestImageFont:
|
class TestImageFont:
|
||||||
LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC
|
LAYOUT_ENGINE = ImageFont.LAYOUT_BASIC
|
||||||
|
|
||||||
# Freetype has different metrics depending on the version.
|
|
||||||
# (and, other things, but first things first)
|
|
||||||
METRICS = {
|
|
||||||
(">=2.3", "<2.4"): {
|
|
||||||
"multiline": 30,
|
|
||||||
"textsize": 12,
|
|
||||||
"getters": (13, 16),
|
|
||||||
"mask": (107, 13),
|
|
||||||
"multiline-anchor": 6,
|
|
||||||
"getlength": (36, 27, 27, 33),
|
|
||||||
},
|
|
||||||
(">=2.7",): {
|
|
||||||
"multiline": 6.2,
|
|
||||||
"textsize": 2.5,
|
|
||||||
"getters": (12, 16),
|
|
||||||
"mask": (108, 13),
|
|
||||||
"multiline-anchor": 4,
|
|
||||||
"getlength": (36, 21, 24, 33),
|
|
||||||
},
|
|
||||||
"Default": {
|
|
||||||
"multiline": 0.5,
|
|
||||||
"textsize": 0.5,
|
|
||||||
"getters": (12, 16),
|
|
||||||
"mask": (108, 13),
|
|
||||||
"multiline-anchor": 4,
|
|
||||||
"getlength": (36, 24, 24, 33),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setup_class(self):
|
|
||||||
freetype = parse_version(features.version_module("freetype2"))
|
|
||||||
|
|
||||||
self.metrics = self.METRICS["Default"]
|
|
||||||
for conditions, metrics in self.METRICS.items():
|
|
||||||
if not isinstance(conditions, tuple):
|
|
||||||
continue
|
|
||||||
|
|
||||||
for condition in conditions:
|
|
||||||
version = parse_version(re.sub("[<=>]", "", condition))
|
|
||||||
if (condition.startswith(">=") and freetype >= version) or (
|
|
||||||
condition.startswith("<") and freetype < version
|
|
||||||
):
|
|
||||||
# Condition was met
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Condition failed
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# All conditions were met
|
|
||||||
self.metrics = metrics
|
|
||||||
|
|
||||||
def get_font(self):
|
def get_font(self):
|
||||||
return ImageFont.truetype(
|
return ImageFont.truetype(
|
||||||
FONT_PATH, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE
|
FONT_PATH, FONT_SIZE, layout_engine=self.LAYOUT_ENGINE
|
||||||
|
@ -199,24 +147,24 @@ class TestImageFont:
|
||||||
with Image.open(target) as target_img:
|
with Image.open(target) as target_img:
|
||||||
|
|
||||||
# Epsilon ~.5 fails with FreeType 2.7
|
# Epsilon ~.5 fails with FreeType 2.7
|
||||||
assert_image_similar(im, target_img, self.metrics["textsize"])
|
assert_image_similar(im, target_img, 2.5)
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"text, mode, font, size, length_basic_index, length_raqm",
|
"text, mode, font, size, length_basic, length_raqm",
|
||||||
(
|
(
|
||||||
# basic test
|
# basic test
|
||||||
("text", "L", "FreeMono.ttf", 15, 0, 36),
|
("text", "L", "FreeMono.ttf", 15, 36, 36),
|
||||||
("text", "1", "FreeMono.ttf", 15, 0, 36),
|
("text", "1", "FreeMono.ttf", 15, 36, 36),
|
||||||
# issue 4177
|
# issue 4177
|
||||||
("rrr", "L", "DejaVuSans.ttf", 18, 1, 22.21875),
|
("rrr", "L", "DejaVuSans.ttf", 18, 21, 22.21875),
|
||||||
("rrr", "1", "DejaVuSans.ttf", 18, 2, 22.21875),
|
("rrr", "1", "DejaVuSans.ttf", 18, 24, 22.21875),
|
||||||
# test 'l' not including extra margin
|
# test 'l' not including extra margin
|
||||||
# using exact value 2047 / 64 for raqm, checked with debugger
|
# using exact value 2047 / 64 for raqm, checked with debugger
|
||||||
("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 3, 31.984375),
|
("ill", "L", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375),
|
||||||
("ill", "1", "OpenSansCondensed-LightItalic.ttf", 63, 3, 31.984375),
|
("ill", "1", "OpenSansCondensed-LightItalic.ttf", 63, 33, 31.984375),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_getlength(self, text, mode, font, size, length_basic_index, length_raqm):
|
def test_getlength(self, text, mode, font, size, length_basic, length_raqm):
|
||||||
f = ImageFont.truetype(
|
f = ImageFont.truetype(
|
||||||
"Tests/fonts/" + font, size, layout_engine=self.LAYOUT_ENGINE
|
"Tests/fonts/" + font, size, layout_engine=self.LAYOUT_ENGINE
|
||||||
)
|
)
|
||||||
|
@ -226,7 +174,7 @@ class TestImageFont:
|
||||||
|
|
||||||
if self.LAYOUT_ENGINE == ImageFont.LAYOUT_BASIC:
|
if self.LAYOUT_ENGINE == ImageFont.LAYOUT_BASIC:
|
||||||
length = d.textlength(text, f)
|
length = d.textlength(text, f)
|
||||||
assert length == self.metrics["getlength"][length_basic_index]
|
assert length == length_basic
|
||||||
else:
|
else:
|
||||||
# disable kerning, kerning metrics changed
|
# disable kerning, kerning metrics changed
|
||||||
length = d.textlength(text, f, features=["-kern"])
|
length = d.textlength(text, f, features=["-kern"])
|
||||||
|
@ -249,7 +197,7 @@ class TestImageFont:
|
||||||
# some versions of freetype have different horizontal spacing.
|
# some versions of freetype have different horizontal spacing.
|
||||||
# setting a tight epsilon, I'm showing the original test failure
|
# setting a tight epsilon, I'm showing the original test failure
|
||||||
# at epsilon = ~38.
|
# at epsilon = ~38.
|
||||||
assert_image_similar(im, target_img, self.metrics["multiline"])
|
assert_image_similar(im, target_img, 6.2)
|
||||||
|
|
||||||
def test_render_multiline_text(self):
|
def test_render_multiline_text(self):
|
||||||
ttf = self.get_font()
|
ttf = self.get_font()
|
||||||
|
@ -264,7 +212,7 @@ class TestImageFont:
|
||||||
with Image.open(target) as target_img:
|
with Image.open(target) as target_img:
|
||||||
|
|
||||||
# Epsilon ~.5 fails with FreeType 2.7
|
# Epsilon ~.5 fails with FreeType 2.7
|
||||||
assert_image_similar(im, target_img, self.metrics["multiline"])
|
assert_image_similar(im, target_img, 6.2)
|
||||||
|
|
||||||
# Test that text() can pass on additional arguments
|
# Test that text() can pass on additional arguments
|
||||||
# to multiline_text()
|
# to multiline_text()
|
||||||
|
@ -283,7 +231,7 @@ class TestImageFont:
|
||||||
with Image.open(target) as target_img:
|
with Image.open(target) as target_img:
|
||||||
|
|
||||||
# Epsilon ~.5 fails with FreeType 2.7
|
# Epsilon ~.5 fails with FreeType 2.7
|
||||||
assert_image_similar(im, target_img, self.metrics["multiline"])
|
assert_image_similar(im, target_img, 6.2)
|
||||||
|
|
||||||
def test_unknown_align(self):
|
def test_unknown_align(self):
|
||||||
im = Image.new(mode="RGB", size=(300, 100))
|
im = Image.new(mode="RGB", size=(300, 100))
|
||||||
|
@ -341,7 +289,7 @@ class TestImageFont:
|
||||||
with Image.open(target) as target_img:
|
with Image.open(target) as target_img:
|
||||||
|
|
||||||
# Epsilon ~.5 fails with FreeType 2.7
|
# Epsilon ~.5 fails with FreeType 2.7
|
||||||
assert_image_similar(im, target_img, self.metrics["multiline"])
|
assert_image_similar(im, target_img, 6.2)
|
||||||
|
|
||||||
def test_rotated_transposed_font(self):
|
def test_rotated_transposed_font(self):
|
||||||
img_grey = Image.new("L", (100, 100))
|
img_grey = Image.new("L", (100, 100))
|
||||||
|
@ -395,7 +343,7 @@ class TestImageFont:
|
||||||
mask = transposed_font.getmask(text)
|
mask = transposed_font.getmask(text)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert mask.size == self.metrics["mask"][::-1]
|
assert mask.size == (13, 108)
|
||||||
|
|
||||||
def test_unrotated_transposed_font_get_mask(self):
|
def test_unrotated_transposed_font_get_mask(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -408,7 +356,7 @@ class TestImageFont:
|
||||||
mask = transposed_font.getmask(text)
|
mask = transposed_font.getmask(text)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert mask.size == self.metrics["mask"]
|
assert mask.size == (108, 13)
|
||||||
|
|
||||||
def test_free_type_font_get_name(self):
|
def test_free_type_font_get_name(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -452,7 +400,7 @@ class TestImageFont:
|
||||||
mask = font.getmask(text)
|
mask = font.getmask(text)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert mask.size == self.metrics["mask"]
|
assert mask.size == (108, 13)
|
||||||
|
|
||||||
def test_load_path_not_found(self):
|
def test_load_path_not_found(self):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -633,7 +581,7 @@ class TestImageFont:
|
||||||
assert t.font.glyphs == 4177
|
assert t.font.glyphs == 4177
|
||||||
assert t.getsize("A") == (12, 16)
|
assert t.getsize("A") == (12, 16)
|
||||||
assert t.getsize("AB") == (24, 16)
|
assert t.getsize("AB") == (24, 16)
|
||||||
assert t.getsize("M") == self.metrics["getters"]
|
assert t.getsize("M") == (12, 16)
|
||||||
assert t.getsize("y") == (12, 20)
|
assert t.getsize("y") == (12, 20)
|
||||||
assert t.getsize("a") == (12, 16)
|
assert t.getsize("a") == (12, 16)
|
||||||
assert t.getsize_multiline("A") == (12, 16)
|
assert t.getsize_multiline("A") == (12, 16)
|
||||||
|
@ -869,7 +817,7 @@ class TestImageFont:
|
||||||
)
|
)
|
||||||
|
|
||||||
with Image.open(target) as expected:
|
with Image.open(target) as expected:
|
||||||
assert_image_similar(im, expected, self.metrics["multiline-anchor"])
|
assert_image_similar(im, expected, 4)
|
||||||
|
|
||||||
def test_anchor_invalid(self):
|
def test_anchor_invalid(self):
|
||||||
font = self.get_font()
|
font = self.get_font()
|
||||||
|
@ -928,7 +876,7 @@ class TestImageFont:
|
||||||
d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True)
|
d.text((10, 10), txt, font=ttf, fill="#fa6", embedded_color=True)
|
||||||
|
|
||||||
with Image.open("Tests/images/standard_embedded.png") as expected:
|
with Image.open("Tests/images/standard_embedded.png") as expected:
|
||||||
assert_image_similar(im, expected, max(self.metrics["multiline"], 3))
|
assert_image_similar(im, expected, 6.2)
|
||||||
|
|
||||||
@skip_unless_feature_version("freetype2", "2.5.0")
|
@skip_unless_feature_version("freetype2", "2.5.0")
|
||||||
def test_cbdt(self):
|
def test_cbdt(self):
|
||||||
|
@ -945,7 +893,7 @@ class TestImageFont:
|
||||||
d.text((10, 10), "\U0001f469", embedded_color=True, font=font)
|
d.text((10, 10), "\U0001f469", embedded_color=True, font=font)
|
||||||
|
|
||||||
with Image.open("Tests/images/cbdt_notocoloremoji.png") as expected:
|
with Image.open("Tests/images/cbdt_notocoloremoji.png") as expected:
|
||||||
assert_image_similar(im, expected, self.metrics["multiline"])
|
assert_image_similar(im, expected, 6.2)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
assert str(e) in ("unimplemented feature", "unknown file format")
|
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||||
pytest.skip("freetype compiled without libpng or unsupported")
|
pytest.skip("freetype compiled without libpng or unsupported")
|
||||||
|
@ -965,7 +913,7 @@ class TestImageFont:
|
||||||
d.text((10, 10), "\U0001f469", "black", font=font)
|
d.text((10, 10), "\U0001f469", "black", font=font)
|
||||||
|
|
||||||
with Image.open("Tests/images/cbdt_notocoloremoji_mask.png") as expected:
|
with Image.open("Tests/images/cbdt_notocoloremoji_mask.png") as expected:
|
||||||
assert_image_similar(im, expected, self.metrics["multiline"])
|
assert_image_similar(im, expected, 6.2)
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
assert str(e) in ("unimplemented feature", "unknown file format")
|
assert str(e) in ("unimplemented feature", "unknown file format")
|
||||||
pytest.skip("freetype compiled without libpng or unsupported")
|
pytest.skip("freetype compiled without libpng or unsupported")
|
||||||
|
@ -1020,3 +968,15 @@ def test_render_mono_size():
|
||||||
|
|
||||||
draw.text((10, 10), "r" * 10, "black", ttf)
|
draw.text((10, 10), "r" * 10, "black", ttf)
|
||||||
assert_image_equal_tofile(im, "Tests/images/text_mono.gif")
|
assert_image_equal_tofile(im, "Tests/images/text_mono.gif")
|
||||||
|
|
||||||
|
|
||||||
|
def test_freetype_deprecation(monkeypatch):
|
||||||
|
# Arrange: mock features.version_module to return fake FreeType version
|
||||||
|
def fake_version_module(module):
|
||||||
|
return "2.7"
|
||||||
|
|
||||||
|
monkeypatch.setattr(features, "version_module", fake_version_module)
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
ImageFont.truetype(FONT_PATH, FONT_SIZE)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import array
|
import array
|
||||||
|
import math
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -6,75 +7,175 @@ import pytest
|
||||||
from PIL import Image, ImagePath
|
from PIL import Image, ImagePath
|
||||||
|
|
||||||
|
|
||||||
class TestImagePath:
|
def test_path():
|
||||||
def test_path(self):
|
|
||||||
|
|
||||||
p = ImagePath.Path(list(range(10)))
|
p = ImagePath.Path(list(range(10)))
|
||||||
|
|
||||||
# sequence interface
|
# sequence interface
|
||||||
assert len(p) == 5
|
assert len(p) == 5
|
||||||
assert p[0] == (0.0, 1.0)
|
assert p[0] == (0.0, 1.0)
|
||||||
assert p[-1] == (8.0, 9.0)
|
assert p[-1] == (8.0, 9.0)
|
||||||
assert list(p[:1]) == [(0.0, 1.0)]
|
assert list(p[:1]) == [(0.0, 1.0)]
|
||||||
with pytest.raises(TypeError) as cm:
|
with pytest.raises(TypeError) as cm:
|
||||||
p["foo"]
|
p["foo"]
|
||||||
assert str(cm.value) == "Path indices must be integers, not str"
|
assert str(cm.value) == "Path indices must be integers, not str"
|
||||||
assert list(p) == [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)]
|
assert list(p) == [(0.0, 1.0), (2.0, 3.0), (4.0, 5.0), (6.0, 7.0), (8.0, 9.0)]
|
||||||
|
|
||||||
# method sanity check
|
# method sanity check
|
||||||
assert p.tolist() == [
|
assert p.tolist() == [
|
||||||
(0.0, 1.0),
|
(0.0, 1.0),
|
||||||
(2.0, 3.0),
|
(2.0, 3.0),
|
||||||
(4.0, 5.0),
|
(4.0, 5.0),
|
||||||
(6.0, 7.0),
|
(6.0, 7.0),
|
||||||
(8.0, 9.0),
|
(8.0, 9.0),
|
||||||
]
|
]
|
||||||
assert p.tolist(1) == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
|
assert p.tolist(1) == [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
|
||||||
|
|
||||||
assert p.getbbox() == (0.0, 1.0, 8.0, 9.0)
|
assert p.getbbox() == (0.0, 1.0, 8.0, 9.0)
|
||||||
|
|
||||||
assert p.compact(5) == 2
|
assert p.compact(5) == 2
|
||||||
assert list(p) == [(0.0, 1.0), (4.0, 5.0), (8.0, 9.0)]
|
assert list(p) == [(0.0, 1.0), (4.0, 5.0), (8.0, 9.0)]
|
||||||
|
|
||||||
p.transform((1, 0, 1, 0, 1, 1))
|
p.transform((1, 0, 1, 0, 1, 1))
|
||||||
assert list(p) == [(1.0, 2.0), (5.0, 6.0), (9.0, 10.0)]
|
assert list(p) == [(1.0, 2.0), (5.0, 6.0), (9.0, 10.0)]
|
||||||
|
|
||||||
# alternative constructors
|
# alternative constructors
|
||||||
p = ImagePath.Path([0, 1])
|
p = ImagePath.Path([0, 1])
|
||||||
assert list(p) == [(0.0, 1.0)]
|
assert list(p) == [(0.0, 1.0)]
|
||||||
p = ImagePath.Path([0.0, 1.0])
|
p = ImagePath.Path([0.0, 1.0])
|
||||||
assert list(p) == [(0.0, 1.0)]
|
assert list(p) == [(0.0, 1.0)]
|
||||||
p = ImagePath.Path([0, 1])
|
p = ImagePath.Path([0, 1])
|
||||||
assert list(p) == [(0.0, 1.0)]
|
assert list(p) == [(0.0, 1.0)]
|
||||||
p = ImagePath.Path([(0, 1)])
|
p = ImagePath.Path([(0, 1)])
|
||||||
assert list(p) == [(0.0, 1.0)]
|
assert list(p) == [(0.0, 1.0)]
|
||||||
p = ImagePath.Path(p)
|
p = ImagePath.Path(p)
|
||||||
assert list(p) == [(0.0, 1.0)]
|
assert list(p) == [(0.0, 1.0)]
|
||||||
p = ImagePath.Path(p.tolist(0))
|
p = ImagePath.Path(p.tolist(0))
|
||||||
assert list(p) == [(0.0, 1.0)]
|
assert list(p) == [(0.0, 1.0)]
|
||||||
p = ImagePath.Path(p.tolist(1))
|
p = ImagePath.Path(p.tolist(1))
|
||||||
assert list(p) == [(0.0, 1.0)]
|
assert list(p) == [(0.0, 1.0)]
|
||||||
p = ImagePath.Path(array.array("f", [0, 1]))
|
p = ImagePath.Path(array.array("f", [0, 1]))
|
||||||
assert list(p) == [(0.0, 1.0)]
|
assert list(p) == [(0.0, 1.0)]
|
||||||
|
|
||||||
arr = array.array("f", [0, 1])
|
arr = array.array("f", [0, 1])
|
||||||
if hasattr(arr, "tobytes"):
|
if hasattr(arr, "tobytes"):
|
||||||
p = ImagePath.Path(arr.tobytes())
|
p = ImagePath.Path(arr.tobytes())
|
||||||
else:
|
else:
|
||||||
p = ImagePath.Path(arr.tostring())
|
p = ImagePath.Path(arr.tostring())
|
||||||
assert list(p) == [(0.0, 1.0)]
|
assert list(p) == [(0.0, 1.0)]
|
||||||
|
|
||||||
def test_overflow_segfault(self):
|
|
||||||
# Some Pythons fail getting the argument as an integer, and it falls
|
|
||||||
# through to the sequence. Seeing this on 32-bit Windows.
|
|
||||||
with pytest.raises((TypeError, MemoryError)):
|
|
||||||
# post patch, this fails with a memory error
|
|
||||||
x = evil()
|
|
||||||
|
|
||||||
# This fails due to the invalid malloc above,
|
def test_invalid_coords():
|
||||||
# and segfaults
|
# Arrange
|
||||||
for i in range(200000):
|
coords = ["a", "b"]
|
||||||
x[i] = b"0" * 16
|
|
||||||
|
# Act / Assert
|
||||||
|
with pytest.raises(SystemError):
|
||||||
|
ImagePath.Path(coords)
|
||||||
|
|
||||||
|
|
||||||
|
def test_path_odd_number_of_coordinates():
|
||||||
|
# Arrange
|
||||||
|
coords = [0]
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
with pytest.raises(ValueError) as e:
|
||||||
|
ImagePath.Path(coords)
|
||||||
|
|
||||||
|
assert str(e.value) == "wrong number of coordinates"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"coords, expected",
|
||||||
|
[
|
||||||
|
([0, 1, 2, 3], (0.0, 1.0, 2.0, 3.0)),
|
||||||
|
([3, 2, 1, 0], (1.0, 0.0, 3.0, 2.0)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_getbbox(coords, expected):
|
||||||
|
# Arrange
|
||||||
|
p = ImagePath.Path(coords)
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
assert p.getbbox() == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_getbbox_no_args():
|
||||||
|
# Arrange
|
||||||
|
p = ImagePath.Path([0, 1, 2, 3])
|
||||||
|
|
||||||
|
# Act / Assert
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
p.getbbox(1)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"coords, expected",
|
||||||
|
[
|
||||||
|
(0, []),
|
||||||
|
(list(range(6)), [(0.0, 3.0), (4.0, 9.0), (8.0, 15.0)]),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_map(coords, expected):
|
||||||
|
# Arrange
|
||||||
|
p = ImagePath.Path(coords)
|
||||||
|
|
||||||
|
# Act
|
||||||
|
# Modifies the path in-place
|
||||||
|
p.map(lambda x, y: (x * 2, y * 3))
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert list(p) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_transform():
|
||||||
|
# Arrange
|
||||||
|
p = ImagePath.Path([0, 1, 2, 3])
|
||||||
|
theta = math.pi / 15
|
||||||
|
|
||||||
|
# Act
|
||||||
|
# Affine transform, in-place
|
||||||
|
p.transform(
|
||||||
|
(math.cos(theta), math.sin(theta), 20, -math.sin(theta), math.cos(theta), 20),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert p.tolist() == [
|
||||||
|
(20.20791169081776, 20.978147600733806),
|
||||||
|
(22.58003027392089, 22.518619420565898),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_transform_with_wrap():
|
||||||
|
# Arrange
|
||||||
|
p = ImagePath.Path([0, 1, 2, 3])
|
||||||
|
theta = math.pi / 15
|
||||||
|
|
||||||
|
# Act
|
||||||
|
# Affine transform, in-place, with wrap parameter
|
||||||
|
p.transform(
|
||||||
|
(math.cos(theta), math.sin(theta), 20, -math.sin(theta), math.cos(theta), 20),
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert p.tolist() == [
|
||||||
|
(0.20791169081775962, 20.978147600733806),
|
||||||
|
(0.5800302739208902, 22.518619420565898),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_overflow_segfault():
|
||||||
|
# Some Pythons fail getting the argument as an integer, and it falls
|
||||||
|
# through to the sequence. Seeing this on 32-bit Windows.
|
||||||
|
with pytest.raises((TypeError, MemoryError)):
|
||||||
|
# post patch, this fails with a memory error
|
||||||
|
x = evil()
|
||||||
|
|
||||||
|
# This fails due to the invalid malloc above,
|
||||||
|
# and segfaults
|
||||||
|
for i in range(200000):
|
||||||
|
x[i] = b"0" * 16
|
||||||
|
|
||||||
|
|
||||||
class evil:
|
class evil:
|
||||||
|
|
|
@ -8,34 +8,15 @@ if ImageQt.qt_is_installed:
|
||||||
from PIL.ImageQt import qRgba
|
from PIL.ImageQt import qRgba
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
|
|
||||||
class PillowQPixmapTestCase:
|
|
||||||
@classmethod
|
|
||||||
def setup_class(self):
|
|
||||||
try:
|
|
||||||
if ImageQt.qt_version == "5":
|
|
||||||
from PyQt5.QtGui import QGuiApplication
|
|
||||||
elif ImageQt.qt_version == "side2":
|
|
||||||
from PySide2.QtGui import QGuiApplication
|
|
||||||
except ImportError:
|
|
||||||
pytest.skip("QGuiApplication not installed")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.app = QGuiApplication([])
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def teardown_class(self):
|
|
||||||
self.app.quit()
|
|
||||||
self.app = None
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
|
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
|
||||||
def test_rgb():
|
def test_rgb():
|
||||||
# from https://doc.qt.io/archives/qt-4.8/qcolor.html
|
# from https://doc.qt.io/archives/qt-4.8/qcolor.html
|
||||||
# typedef QRgb
|
# typedef QRgb
|
||||||
# An ARGB quadruplet on the format #AARRGGBB,
|
# An ARGB quadruplet on the format #AARRGGBB,
|
||||||
# equivalent to an unsigned int.
|
# equivalent to an unsigned int.
|
||||||
if ImageQt.qt_version == "5":
|
if ImageQt.qt_version == "side6":
|
||||||
|
from PySide6.QtGui import qRgb
|
||||||
|
elif ImageQt.qt_version == "5":
|
||||||
from PyQt5.QtGui import qRgb
|
from PyQt5.QtGui import qRgb
|
||||||
elif ImageQt.qt_version == "side2":
|
elif ImageQt.qt_version == "side2":
|
||||||
from PySide2.QtGui import qRgb
|
from PySide2.QtGui import qRgb
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
from PIL import ImageQt
|
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper
|
|
||||||
from .test_imageqt import PillowQPixmapTestCase
|
|
||||||
|
|
||||||
|
|
||||||
class TestFromQPixmap(PillowQPixmapTestCase):
|
|
||||||
def roundtrip(self, expected):
|
|
||||||
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
|
|
||||||
# Qt saves all pixmaps as rgb
|
|
||||||
assert_image_equal(result, expected.convert("RGB"))
|
|
||||||
|
|
||||||
def test_sanity(self):
|
|
||||||
for mode in ("1", "RGB", "RGBA", "L", "P"):
|
|
||||||
self.roundtrip(hopper(mode))
|
|
63
Tests/test_qt_image_qapplication.py
Normal file
63
Tests/test_qt_image_qapplication.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from PIL import ImageQt
|
||||||
|
|
||||||
|
from .helper import assert_image_equal, hopper
|
||||||
|
|
||||||
|
if ImageQt.qt_is_installed:
|
||||||
|
from PIL.ImageQt import QPixmap
|
||||||
|
|
||||||
|
if ImageQt.qt_version == "side6":
|
||||||
|
from PySide6.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
||||||
|
elif ImageQt.qt_version == "5":
|
||||||
|
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
||||||
|
elif ImageQt.qt_version == "side2":
|
||||||
|
from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
||||||
|
|
||||||
|
class Example(QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
img = hopper().resize((1000, 1000))
|
||||||
|
|
||||||
|
qimage = ImageQt.ImageQt(img)
|
||||||
|
|
||||||
|
pixmap1 = ImageQt.QPixmap.fromImage(qimage)
|
||||||
|
|
||||||
|
QHBoxLayout(self) # hbox
|
||||||
|
|
||||||
|
lbl = QLabel(self)
|
||||||
|
# Segfault in the problem
|
||||||
|
lbl.setPixmap(pixmap1.copy())
|
||||||
|
|
||||||
|
|
||||||
|
def roundtrip(expected):
|
||||||
|
result = ImageQt.fromqpixmap(ImageQt.toqpixmap(expected))
|
||||||
|
# Qt saves all pixmaps as rgb
|
||||||
|
assert_image_equal(result, expected.convert("RGB"))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not ImageQt.qt_is_installed, reason="Qt bindings are not installed")
|
||||||
|
def test_sanity(tmp_path):
|
||||||
|
# Segfault test
|
||||||
|
app = QApplication([])
|
||||||
|
ex = Example()
|
||||||
|
assert app # Silence warning
|
||||||
|
assert ex # Silence warning
|
||||||
|
|
||||||
|
for mode in ("1", "RGB", "RGBA", "L", "P"):
|
||||||
|
# to QPixmap
|
||||||
|
data = ImageQt.toqpixmap(hopper(mode))
|
||||||
|
|
||||||
|
assert isinstance(data, QPixmap)
|
||||||
|
assert not data.isNull()
|
||||||
|
|
||||||
|
# Test saving the file
|
||||||
|
tempfile = str(tmp_path / f"temp_{mode}.png")
|
||||||
|
data.save(tempfile)
|
||||||
|
|
||||||
|
# from QPixmap
|
||||||
|
roundtrip(hopper(mode))
|
||||||
|
|
||||||
|
app.quit()
|
||||||
|
app = None
|
|
@ -11,13 +11,6 @@ pytestmark = pytest.mark.skipif(
|
||||||
if ImageQt.qt_is_installed:
|
if ImageQt.qt_is_installed:
|
||||||
from PIL.ImageQt import QImage
|
from PIL.ImageQt import QImage
|
||||||
|
|
||||||
try:
|
|
||||||
from PyQt5 import QtGui
|
|
||||||
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
|
||||||
except (ImportError, RuntimeError):
|
|
||||||
from PySide2 import QtGui
|
|
||||||
from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
|
|
||||||
|
|
||||||
|
|
||||||
def test_sanity(tmp_path):
|
def test_sanity(tmp_path):
|
||||||
for mode in ("RGB", "RGBA", "L", "P", "1"):
|
for mode in ("RGB", "RGBA", "L", "P", "1"):
|
||||||
|
@ -49,29 +42,3 @@ def test_sanity(tmp_path):
|
||||||
# Check that it actually worked.
|
# Check that it actually worked.
|
||||||
with Image.open(tempfile) as reloaded:
|
with Image.open(tempfile) as reloaded:
|
||||||
assert_image_equal(reloaded, src)
|
assert_image_equal(reloaded, src)
|
||||||
|
|
||||||
|
|
||||||
def test_segfault():
|
|
||||||
app = QApplication([])
|
|
||||||
ex = Example()
|
|
||||||
assert app # Silence warning
|
|
||||||
assert ex # Silence warning
|
|
||||||
|
|
||||||
|
|
||||||
if ImageQt.qt_is_installed:
|
|
||||||
|
|
||||||
class Example(QWidget):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
img = hopper().resize((1000, 1000))
|
|
||||||
|
|
||||||
qimage = ImageQt.ImageQt(img)
|
|
||||||
|
|
||||||
pixmap1 = QtGui.QPixmap.fromImage(qimage)
|
|
||||||
|
|
||||||
QHBoxLayout(self) # hbox
|
|
||||||
|
|
||||||
lbl = QLabel(self)
|
|
||||||
# Segfault in the problem
|
|
||||||
lbl.setPixmap(pixmap1.copy())
|
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
from PIL import ImageQt
|
|
||||||
|
|
||||||
from .helper import hopper
|
|
||||||
from .test_imageqt import PillowQPixmapTestCase
|
|
||||||
|
|
||||||
if ImageQt.qt_is_installed:
|
|
||||||
from PIL.ImageQt import QPixmap
|
|
||||||
|
|
||||||
|
|
||||||
class TestToQPixmap(PillowQPixmapTestCase):
|
|
||||||
def test_sanity(self, tmp_path):
|
|
||||||
for mode in ("1", "RGB", "RGBA", "L", "P"):
|
|
||||||
data = ImageQt.toqpixmap(hopper(mode))
|
|
||||||
|
|
||||||
assert isinstance(data, QPixmap)
|
|
||||||
assert not data.isNull()
|
|
||||||
|
|
||||||
# Test saving the file
|
|
||||||
tempfile = str(tmp_path / f"temp_{mode}.png")
|
|
||||||
data.save(tempfile)
|
|
|
@ -6,7 +6,12 @@ from PIL import Image
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_file",
|
"test_file",
|
||||||
["Tests/images/sgi_overrun_expandrowF04.bin", "Tests/images/sgi_crash.bin"],
|
[
|
||||||
|
"Tests/images/sgi_overrun_expandrowF04.bin",
|
||||||
|
"Tests/images/sgi_crash.bin",
|
||||||
|
"Tests/images/crash-6b7f2244da6d0ae297ee0754a424213444e92778.sgi",
|
||||||
|
"Tests/images/ossfuzz-5730089102868480.sgi",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
def test_crashes(test_file):
|
def test_crashes(test_file):
|
||||||
with open(test_file, "rb") as f:
|
with open(test_file, "rb") as f:
|
||||||
|
|
|
@ -19,7 +19,12 @@ from .helper import on_ci
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_file", ["Tests/images/crash_1.tif", "Tests/images/crash_2.tif"]
|
"test_file",
|
||||||
|
[
|
||||||
|
"Tests/images/crash_1.tif",
|
||||||
|
"Tests/images/crash_2.tif",
|
||||||
|
"Tests/images/crash-2020-10-test.tif",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data")
|
@pytest.mark.filterwarnings("ignore:Possibly corrupt EXIF data")
|
||||||
@pytest.mark.filterwarnings("ignore:Metadata warning")
|
@pytest.mark.filterwarnings("ignore:Metadata warning")
|
||||||
|
|
|
@ -3,7 +3,7 @@ Depends
|
||||||
|
|
||||||
``install_openjpeg.sh``, ``install_webp.sh``, ``install_imagequant.sh``,
|
``install_openjpeg.sh``, ``install_webp.sh``, ``install_imagequant.sh``,
|
||||||
``install_raqm.sh`` and ``install_raqm_cmake.sh`` can be used to download,
|
``install_raqm.sh`` and ``install_raqm_cmake.sh`` can be used to download,
|
||||||
build & install non-packaged dependencies; useful for testing with Travis CI.
|
build & install non-packaged dependencies; useful for testing on CI.
|
||||||
|
|
||||||
``install_extra_test_images.sh`` can be used to install additional test images
|
``install_extra_test_images.sh`` can be used to install additional test images
|
||||||
that are used for Travis CI and AppVeyor.
|
that are used by CI.
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Fetch the remote master branch before running diff-cover on Travis CI.
|
|
||||||
# https://github.com/Bachmann1234/diff-cover#troubleshooting
|
|
||||||
git fetch origin master:refs/remotes/origin/master
|
|
||||||
|
|
||||||
# CFLAGS=-O0 means build with no optimisation.
|
|
||||||
# Makes build much quicker for lxml and other dependencies.
|
|
||||||
time CFLAGS=-O0 pip install diff_cover
|
|
|
@ -1,5 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
coverage xml
|
|
||||||
diff-cover coverage.xml
|
|
||||||
diff-quality --violation=pyflakes
|
|
||||||
diff-quality --violation=pycodestyle
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install libimagequant
|
# install libimagequant
|
||||||
|
|
||||||
archive=libimagequant-2.13.0
|
archive=libimagequant-2.13.1
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install openjpeg
|
# install openjpeg
|
||||||
|
|
||||||
archive=openjpeg-2.3.1
|
archive=openjpeg-2.4.0
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# install raqm
|
# install raqm
|
||||||
|
|
||||||
|
|
||||||
archive=raqm-0.7.0
|
archive=raqm-0.7.1
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/master/$archive.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is
|
||||||
|
|
||||||
Pillow is the friendly PIL fork. It is
|
Pillow is the friendly PIL fork. It is
|
||||||
|
|
||||||
Copyright © 2010-2020 by Alex Clark and contributors
|
Copyright © 2010-2021 by Alex Clark and contributors
|
||||||
|
|
||||||
Like PIL, Pillow is licensed under the open source PIL
|
Like PIL, Pillow is licensed under the open source PIL
|
||||||
Software License:
|
Software License:
|
||||||
|
|
|
@ -156,4 +156,4 @@ livehtml: html
|
||||||
livereload $(BUILDDIR)/html -p 33233
|
livereload $(BUILDDIR)/html -p 33233
|
||||||
|
|
||||||
serve:
|
serve:
|
||||||
cd $(BUILDDIR)/html; python -m SimpleHTTPServer
|
cd $(BUILDDIR)/html; python3 -m http.server
|
||||||
|
|
|
@ -6,20 +6,20 @@ Goals
|
||||||
|
|
||||||
The fork author's goal is to foster and support active development of PIL through:
|
The fork author's goal is to foster and support active development of PIL through:
|
||||||
|
|
||||||
- Continuous integration testing via `Travis CI`_, `AppVeyor`_ and `GitHub Actions`_
|
- Continuous integration testing via `GitHub Actions`_, `AppVeyor`_ and `Travis CI`_
|
||||||
- Publicized development activity on `GitHub`_
|
- Publicized development activity on `GitHub`_
|
||||||
- Regular releases to the `Python Package Index`_
|
- Regular releases to the `Python Package Index`_
|
||||||
|
|
||||||
.. _Travis CI: https://travis-ci.org/python-pillow/Pillow
|
|
||||||
.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow
|
|
||||||
.. _GitHub Actions: https://github.com/python-pillow/Pillow/actions
|
.. _GitHub Actions: https://github.com/python-pillow/Pillow/actions
|
||||||
|
.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow
|
||||||
|
.. _Travis CI: https://travis-ci.com/github/python-pillow/pillow-wheels
|
||||||
.. _GitHub: https://github.com/python-pillow/Pillow
|
.. _GitHub: https://github.com/python-pillow/Pillow
|
||||||
.. _Python Package Index: https://pypi.org/project/Pillow/
|
.. _Python Package Index: https://pypi.org/project/Pillow/
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Like PIL, Pillow is `licensed under the open source PIL Software License <https://raw.githubusercontent.com/python-pillow/Pillow/master/LICENSE>`_
|
Like PIL, Pillow is `licensed under the open source HPND License <https://raw.githubusercontent.com/python-pillow/Pillow/master/LICENSE>`_
|
||||||
|
|
||||||
Why a fork?
|
Why a fork?
|
||||||
-----------
|
-----------
|
||||||
|
|
|
@ -32,6 +32,7 @@ extensions = [
|
||||||
"sphinx.ext.autodoc",
|
"sphinx.ext.autodoc",
|
||||||
"sphinx.ext.intersphinx",
|
"sphinx.ext.intersphinx",
|
||||||
"sphinx.ext.viewcode",
|
"sphinx.ext.viewcode",
|
||||||
|
"sphinx_issues",
|
||||||
"sphinx_removed_in",
|
"sphinx_removed_in",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ master_doc = "index"
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = "Pillow (PIL Fork)"
|
project = "Pillow (PIL Fork)"
|
||||||
copyright = "1995-2011 Fredrik Lundh, 2010-2020 Alex Clark and Contributors"
|
copyright = "1995-2011 Fredrik Lundh, 2010-2021 Alex Clark and Contributors"
|
||||||
author = "Fredrik Lundh, Alex Clark and Contributors"
|
author = "Fredrik Lundh, Alex Clark and Contributors"
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
@ -143,7 +144,7 @@ html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
# of the sidebar.
|
# of the sidebar.
|
||||||
# html_logo = None
|
html_logo = "resources/pillow-logo.png"
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
|
@ -310,3 +311,4 @@ texinfo_documents = [
|
||||||
def setup(app):
|
def setup(app):
|
||||||
app.add_js_file("js/script.js")
|
app.add_js_file("js/script.js")
|
||||||
app.add_css_file("css/dark.css")
|
app.add_css_file("css/dark.css")
|
||||||
|
app.add_css_file("css/light.css")
|
||||||
|
|
|
@ -12,12 +12,25 @@ Deprecated features
|
||||||
Below are features which are considered deprecated. Where appropriate,
|
Below are features which are considered deprecated. Where appropriate,
|
||||||
a ``DeprecationWarning`` is issued.
|
a ``DeprecationWarning`` is issued.
|
||||||
|
|
||||||
|
FreeType 2.7
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. deprecated:: 8.1.0
|
||||||
|
|
||||||
|
Support for FreeType 2.7 is deprecated and will be removed in Pillow 9.0.0 (2022-01-02),
|
||||||
|
when FreeType 2.8 will be the minimum supported.
|
||||||
|
|
||||||
|
We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe
|
||||||
|
vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`).
|
||||||
|
|
||||||
|
.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/
|
||||||
|
|
||||||
Image.show command parameter
|
Image.show command parameter
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. deprecated:: 7.2.0
|
.. deprecated:: 7.2.0
|
||||||
|
|
||||||
The ``command`` parameter was deprecated and will be removed in a future release.
|
The ``command`` parameter will be removed in Pillow 9.0.0 (2022-01-02).
|
||||||
Use a subclass of :py:class:`.ImageShow.Viewer` instead.
|
Use a subclass of :py:class:`.ImageShow.Viewer` instead.
|
||||||
|
|
||||||
Image._showxv
|
Image._showxv
|
||||||
|
@ -25,26 +38,26 @@ Image._showxv
|
||||||
|
|
||||||
.. deprecated:: 7.2.0
|
.. deprecated:: 7.2.0
|
||||||
|
|
||||||
``Image._showxv`` has been deprecated. Use :py:meth:`.Image.Image.show`
|
``Image._showxv`` will be removed in Pillow 9.0.0 (2022-01-02).
|
||||||
instead. If custom behaviour is required, use :py:func:`.ImageShow.register` to add
|
Use :py:meth:`.Image.Image.show` instead. If custom behaviour is required, use
|
||||||
a custom :py:class:`.ImageShow.Viewer` class.
|
:py:func:`.ImageShow.register` to add a custom :py:class:`.ImageShow.Viewer` class.
|
||||||
|
|
||||||
ImageFile.raise_ioerror
|
ImageFile.raise_ioerror
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. deprecated:: 7.2.0
|
.. deprecated:: 7.2.0
|
||||||
|
|
||||||
``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror``
|
``IOError`` was merged into ``OSError`` in Python 3.3.
|
||||||
is now deprecated and will be removed in a future release. Use
|
So, ``ImageFile.raise_ioerror`` will be removed in Pillow 9.0.0 (2022-01-02).
|
||||||
``ImageFile.raise_oserror`` instead.
|
Use ``ImageFile.raise_oserror`` instead.
|
||||||
|
|
||||||
PILLOW_VERSION constant
|
PILLOW_VERSION constant
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
.. deprecated:: 5.2.0
|
.. deprecated:: 5.2.0
|
||||||
|
|
||||||
``PILLOW_VERSION`` has been deprecated and will be removed in a future release. Use
|
``PILLOW_VERSION`` will be removed in Pillow 9.0.0 (2022-01-02).
|
||||||
``__version__`` instead.
|
Use ``__version__`` instead.
|
||||||
|
|
||||||
It was initially removed in Pillow 7.0.0, but brought back in 7.1.0 to give projects
|
It was initially removed in Pillow 7.0.0, but brought back in 7.1.0 to give projects
|
||||||
more time to upgrade.
|
more time to upgrade.
|
||||||
|
|
|
@ -125,10 +125,10 @@ following options are available::
|
||||||
**append_images**
|
**append_images**
|
||||||
A list of images to append as additional frames. Each of the
|
A list of images to append as additional frames. Each of the
|
||||||
images in the list can be single or multiframe images.
|
images in the list can be single or multiframe images.
|
||||||
This is currently supported for GIF, PDF, TIFF, and WebP.
|
This is currently supported for GIF, PDF, PNG, TIFF, and WebP.
|
||||||
|
|
||||||
It is also supported for ICNS. If images are passed in of relevant sizes,
|
It is also supported for ICO and ICNS. If images are passed in of relevant
|
||||||
they will be used instead of scaling down the main image.
|
sizes, they will be used instead of scaling down the main image.
|
||||||
|
|
||||||
**include_color_table**
|
**include_color_table**
|
||||||
Whether or not to include local color table.
|
Whether or not to include local color table.
|
||||||
|
@ -238,6 +238,15 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
(64, 64), (128, 128), (256, 256)]``. Any sizes bigger than the original
|
(64, 64), (128, 128), (256, 256)]``. Any sizes bigger than the original
|
||||||
size or 256 will be ignored.
|
size or 256 will be ignored.
|
||||||
|
|
||||||
|
The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
|
||||||
|
|
||||||
|
**append_images**
|
||||||
|
A list of images to replace the scaled down versions of the image.
|
||||||
|
The order of the images does not matter, as their use is determined by
|
||||||
|
the size of each image.
|
||||||
|
|
||||||
|
.. versionadded:: 8.1.0
|
||||||
|
|
||||||
IM
|
IM
|
||||||
^^
|
^^
|
||||||
|
|
||||||
|
@ -947,9 +956,10 @@ Saving sequences
|
||||||
library is v0.5.0 or later. You can check webp animation support at
|
library is v0.5.0 or later. You can check webp animation support at
|
||||||
runtime by calling ``features.check("webp_anim")``.
|
runtime by calling ``features.check("webp_anim")``.
|
||||||
|
|
||||||
When calling :py:meth:`~PIL.Image.Image.save` to write a WebP file, the
|
When calling :py:meth:`~PIL.Image.Image.save` to write a WebP file, by default
|
||||||
following options are available when the ``save_all`` argument is present and
|
only the first frame of a multiframe image will be saved. If the ``save_all``
|
||||||
true.
|
argument is present and true, then all frames will be saved, and the following
|
||||||
|
options will also be available.
|
||||||
|
|
||||||
**append_images**
|
**append_images**
|
||||||
A list of images to append as additional frames. Each of the
|
A list of images to append as additional frames. Each of the
|
||||||
|
|
|
@ -157,7 +157,7 @@ The raw decoder
|
||||||
The ``raw`` decoder is used to read uncompressed data from an image file. It
|
The ``raw`` decoder is used to read uncompressed data from an image file. It
|
||||||
can be used with most uncompressed file formats, such as PPM, BMP, uncompressed
|
can be used with most uncompressed file formats, such as PPM, BMP, uncompressed
|
||||||
TIFF, and many others. To use the raw decoder with the
|
TIFF, and many others. To use the raw decoder with the
|
||||||
:py:func:`PIL.Image.frombytes` function, use the following syntax::
|
:py:func:`PIL.Image.frombytes` function, use the following syntax:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
|
|
@ -9,18 +9,6 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
|
||||||
:target: https://pillow.readthedocs.io/?badge=latest
|
:target: https://pillow.readthedocs.io/?badge=latest
|
||||||
:alt: Documentation Status
|
:alt: Documentation Status
|
||||||
|
|
||||||
.. image:: https://img.shields.io/travis/python-pillow/Pillow/master.svg?label=Linux%20build
|
|
||||||
:target: https://travis-ci.org/python-pillow/Pillow
|
|
||||||
:alt: Travis CI build status (Linux)
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/travis/python-pillow/pillow-wheels/master.svg?label=macOS%20build
|
|
||||||
:target: https://travis-ci.org/python-pillow/pillow-wheels
|
|
||||||
:alt: Travis CI build status (macOS)
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/appveyor/build/python-pillow/Pillow/master.svg?label=Windows%20build
|
|
||||||
:target: https://ci.appveyor.com/project/python-pillow/Pillow
|
|
||||||
:alt: AppVeyor CI build status (Windows)
|
|
||||||
|
|
||||||
.. image:: https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg
|
.. image:: https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg
|
||||||
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint
|
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint
|
||||||
:alt: GitHub Actions build status (Lint)
|
:alt: GitHub Actions build status (Lint)
|
||||||
|
@ -37,6 +25,14 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
|
||||||
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Windows%22
|
:target: https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Windows%22
|
||||||
:alt: GitHub Actions build status (Test Windows)
|
:alt: GitHub Actions build status (Test Windows)
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/appveyor/build/python-pillow/Pillow/master.svg?label=Windows%20build
|
||||||
|
:target: https://ci.appveyor.com/project/python-pillow/Pillow
|
||||||
|
:alt: AppVeyor CI build status (Windows)
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/travis/com/python-pillow/pillow-wheels/master.svg?label=macOS%20build
|
||||||
|
:target: https://travis-ci.com/github/python-pillow/pillow-wheels
|
||||||
|
:alt: Travis CI build status (macOS)
|
||||||
|
|
||||||
.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg
|
.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg
|
||||||
:target: https://codecov.io/gh/python-pillow/Pillow
|
:target: https://codecov.io/gh/python-pillow/Pillow
|
||||||
:alt: Code coverage
|
:alt: Code coverage
|
||||||
|
|
|
@ -171,13 +171,13 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
* **openjpeg** provides JPEG 2000 functionality.
|
* **openjpeg** provides JPEG 2000 functionality.
|
||||||
|
|
||||||
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0** and **2.3.1**.
|
* Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1** and **2.4.0**.
|
||||||
* Pillow does **not** support the earlier **1.5** series which ships
|
* Pillow does **not** support the earlier **1.5** series which ships
|
||||||
with Debian Jessie.
|
with Debian Jessie.
|
||||||
|
|
||||||
* **libimagequant** provides improved color quantization
|
* **libimagequant** provides improved color quantization
|
||||||
|
|
||||||
* Pillow has been tested with libimagequant **2.6-2.13.0**
|
* Pillow has been tested with libimagequant **2.6-2.13.1**
|
||||||
* Libimagequant is licensed GPLv3, which is more restrictive than
|
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||||
the Pillow license, therefore we will not be distributing binaries
|
the Pillow license, therefore we will not be distributing binaries
|
||||||
with libimagequant support enabled.
|
with libimagequant support enabled.
|
||||||
|
@ -422,12 +422,8 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Arch | 3.8 |x86-64 |
|
| Arch | 3.8 |x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Amazon Linux 1 | 3.6 |x86-64 |
|
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
|
||||||
| Amazon Linux 2 | 3.7 |x86-64 |
|
| Amazon Linux 2 | 3.7 |x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| CentOS 6 | 3.6 |x86-64 |
|
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
|
||||||
| CentOS 7 | 3.6 |x86-64 |
|
| CentOS 7 | 3.6 |x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| CentOS 8 | 3.6 |x86-64 |
|
| CentOS 8 | 3.6 |x86-64 |
|
||||||
|
@ -436,17 +432,17 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Fedora 32 | 3.8 |x86-64 |
|
| Fedora 32 | 3.8 |x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
|
| Fedora 33 | 3.9 |x86-64 |
|
||||||
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9, PyPy3|x86-64 |
|
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9, PyPy3|x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Ubuntu Linux 16.04 LTS (Xenial) | 3.6, 3.7, 3.8, PyPy3 |x86-64 |
|
| Ubuntu Linux 16.04 LTS (Xenial) | 3.6, 3.7, 3.8, 3.9, PyPy3|x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Ubuntu Linux 18.04 LTS (Bionic) | 3.6, 3.7, 3.8, 3.9, PyPy3|x86-64 |
|
| Ubuntu Linux 18.04 LTS (Bionic) | 3.6, 3.7, 3.8, 3.9, PyPy3|x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Ubuntu Linux 20.04 LTS (Focal) | 3.8 |x86-64 |
|
| Ubuntu Linux 20.04 LTS (Focal) | 3.8 |x86-64 |
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Windows Server 2016 | 3.8 |x86 |
|
| Windows Server 2016 | 3.6 |x86-64 |
|
||||||
| +--------------------------+-----------------------+
|
|
||||||
| | 3.6 |x86-64 |
|
|
||||||
+----------------------------------+--------------------------+-----------------------+
|
+----------------------------------+--------------------------+-----------------------+
|
||||||
| Windows Server 2019 | 3.6, 3.7, 3.8, 3.9 |x86, x86-64 |
|
| Windows Server 2019 | 3.6, 3.7, 3.8, 3.9 |x86, x86-64 |
|
||||||
| +--------------------------+-----------------------+
|
| +--------------------------+-----------------------+
|
||||||
|
@ -469,7 +465,13 @@ These platforms have been reported to work at the versions mentioned.
|
||||||
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
||||||
|**Operating system** |**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** |
|
|**Operating system** |**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** |
|
||||||
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
||||||
| macOS 10.15 Catalina | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
|
| macOS 11.0 Big Sur | 3.8, 3.9 | 8.0.1 |arm |
|
||||||
|
| +------------------------------+--------------------------------+-----------------------+
|
||||||
|
| | 3.6, 3.7, 3.8, 3.9 | 8.0.1 |x86-64 |
|
||||||
|
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
||||||
|
| macOS 10.15 Catalina | 3.6, 3.7, 3.8, 3.9 | 8.0.1 |x86-64 |
|
||||||
|
| +------------------------------+--------------------------------+ +
|
||||||
|
| | 3.5 | 7.2.0 | |
|
||||||
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
||||||
| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
|
| macOS 10.14 Mojave | 3.5, 3.6, 3.7, 3.8 | 7.2.0 |x86-64 |
|
||||||
| +------------------------------+--------------------------------+ +
|
| +------------------------------+--------------------------------+ +
|
||||||
|
|
|
@ -200,7 +200,7 @@ can be easily displayed in a chromaticity diagram, for example).
|
||||||
|
|
||||||
The chromatic adaption matrix converts a color measured using the
|
The chromatic adaption matrix converts a color measured using the
|
||||||
actual illumination conditions and relative to the actual adopted
|
actual illumination conditions and relative to the actual adopted
|
||||||
white, to an color relative to the PCS adopted white, with
|
white, to a color relative to the PCS adopted white, with
|
||||||
complete adaptation from the actual adopted white chromaticity to
|
complete adaptation from the actual adopted white chromaticity to
|
||||||
the PCS adopted white chromaticity (see 9.2.15 of ICC.1:2010).
|
the PCS adopted white chromaticity (see 9.2.15 of ICC.1:2010).
|
||||||
|
|
||||||
|
|
|
@ -296,7 +296,7 @@ Methods
|
||||||
Draws the string at the given position.
|
Draws the string at the given position.
|
||||||
|
|
||||||
:param xy: The anchor coordinates of the text.
|
:param xy: The anchor coordinates of the text.
|
||||||
:param text: Text to be drawn. If it contains any newline characters,
|
:param text: String to be drawn. If it contains any newline characters,
|
||||||
the text is passed on to
|
the text is passed on to
|
||||||
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`.
|
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`.
|
||||||
:param fill: Color to use for the text.
|
:param fill: Color to use for the text.
|
||||||
|
@ -362,7 +362,7 @@ Methods
|
||||||
Draws the string at the given position.
|
Draws the string at the given position.
|
||||||
|
|
||||||
:param xy: The anchor coordinates of the text.
|
:param xy: The anchor coordinates of the text.
|
||||||
:param text: Text to be drawn.
|
:param text: String to be drawn.
|
||||||
:param fill: Color to use for the text.
|
:param fill: Color to use for the text.
|
||||||
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@ The :py:mod:`~PIL.ImageFont` module defines a class with the same name. Instance
|
||||||
this class store bitmap fonts, and are used with the
|
this class store bitmap fonts, and are used with the
|
||||||
:py:meth:`PIL.ImageDraw.ImageDraw.text` method.
|
:py:meth:`PIL.ImageDraw.ImageDraw.text` method.
|
||||||
|
|
||||||
PIL uses its own font file format to store bitmap fonts. You can use the
|
PIL uses its own font file format to store bitmap fonts, limited to 256 characters. You can use
|
||||||
:command:`pilfont` utility from
|
`pilfont.py <https://github.com/python-pillow/pillow-scripts/blob/master/Scripts/pilfont.py>`_
|
||||||
`pillow-scripts <https://pypi.org/project/pillow-scripts/>`_
|
from `pillow-scripts <https://pypi.org/project/pillow-scripts/>`_ to convert BDF and
|
||||||
to convert BDF and PCF font descriptors (X window font formats) to this format.
|
PCF font descriptors (X window font formats) to this format.
|
||||||
|
|
||||||
Starting with version 1.1.4, PIL can be configured to support TrueType and
|
Starting with version 1.1.4, PIL can be configured to support TrueType and
|
||||||
OpenType fonts (as well as other font formats supported by the FreeType
|
OpenType fonts (as well as other font formats supported by the FreeType
|
||||||
|
|
|
@ -6,7 +6,7 @@ CVE-2016-0740 -- Buffer overflow in TiffDecode.c
|
||||||
------------------------------------------------
|
------------------------------------------------
|
||||||
|
|
||||||
Pillow 3.1.0 and earlier when linked against libtiff >= 4.0.0 on x64
|
Pillow 3.1.0 and earlier when linked against libtiff >= 4.0.0 on x64
|
||||||
may overflow a buffer when reading a specially crafted tiff file.
|
may overflow a buffer when reading a specially crafted tiff file (:cve:`CVE-2016-0740`).
|
||||||
|
|
||||||
Specifically, libtiff >= 4.0.0 changed the return type of
|
Specifically, libtiff >= 4.0.0 changed the return type of
|
||||||
``TIFFScanlineSize`` from ``int32`` to machine dependent
|
``TIFFScanlineSize`` from ``int32`` to machine dependent
|
||||||
|
@ -24,9 +24,11 @@ CVE-2016-0775 -- Buffer overflow in FliDecode.c
|
||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
|
|
||||||
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
||||||
release, FliDecode.c has a buffer overflow error.
|
release, FliDecode.c has a buffer overflow error (:cve:`CVE-2016-0775`).
|
||||||
|
|
||||||
Around line 192::
|
Around line 192:
|
||||||
|
|
||||||
|
.. code-block:: c
|
||||||
|
|
||||||
case 16:
|
case 16:
|
||||||
/* COPY chunk */
|
/* COPY chunk */
|
||||||
|
@ -45,13 +47,13 @@ is a set of row pointers to segments of memory that are the size of
|
||||||
the row. At the max ``y``, this will write the contents of the line
|
the row. At the max ``y``, this will write the contents of the line
|
||||||
off the end of the memory buffer, causing a segfault.
|
off the end of the memory buffer, causing a segfault.
|
||||||
|
|
||||||
This issue was found by Alyssa Besseling at Atlassian
|
This issue was found by Alyssa Besseling at Atlassian.
|
||||||
|
|
||||||
CVE-2016-2533 -- Buffer overflow in PcdDecode.c
|
CVE-2016-2533 -- Buffer overflow in PcdDecode.c
|
||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
|
|
||||||
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
In all versions of Pillow, dating back at least to the last PIL 1.1.7
|
||||||
release, ``PcdDecode.c`` has a buffer overflow error.
|
release, ``PcdDecode.c`` has a buffer overflow error (:cve:`CVE-2016-2533`).
|
||||||
|
|
||||||
The ``state.buffer`` for ``PcdDecode.c`` is allocated based on a 3
|
The ``state.buffer`` for ``PcdDecode.c`` is allocated based on a 3
|
||||||
bytes per pixel sizing, where ``PcdDecode.c`` wrote into the buffer
|
bytes per pixel sizing, where ``PcdDecode.c`` wrote into the buffer
|
||||||
|
@ -63,14 +65,16 @@ Integer overflow in Resample.c
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
If a large value was passed into the new size for an image, it is
|
If a large value was passed into the new size for an image, it is
|
||||||
possible to overflow an int32 value passed into malloc.
|
possible to overflow an ``int32`` value passed into malloc.
|
||||||
|
|
||||||
kk = malloc(xsize * kmax * sizeof(float));
|
.. code-block:: c
|
||||||
...
|
|
||||||
xbounds = malloc(xsize * 2 * sizeof(int));
|
kk = malloc(xsize * kmax * sizeof(float));
|
||||||
|
...
|
||||||
|
xbounds = malloc(xsize * 2 * sizeof(int));
|
||||||
|
|
||||||
``xsize`` is trusted user input. These multiplications can overflow,
|
``xsize`` is trusted user input. These multiplications can overflow,
|
||||||
leading the malloc'd buffer to be undersized. These allocations are
|
leading the ``malloc``'d buffer to be undersized. These allocations are
|
||||||
followed by a loop that writes out of bounds. This can lead to
|
followed by a loop that writes out of bounds. This can lead to
|
||||||
corruption on the heap of the Python process with attacker controlled
|
corruption on the heap of the Python process with attacker controlled
|
||||||
float data.
|
float data.
|
||||||
|
|
|
@ -7,9 +7,11 @@ CVE-2016-3076 -- Buffer overflow in Jpeg2KEncode.c
|
||||||
|
|
||||||
Pillow between 2.5.0 and 3.1.1 may overflow a buffer when writing
|
Pillow between 2.5.0 and 3.1.1 may overflow a buffer when writing
|
||||||
large Jpeg2000 files, allowing for code execution or other memory
|
large Jpeg2000 files, allowing for code execution or other memory
|
||||||
corruption.
|
corruption (:cve:`CVE-2016-3076`).
|
||||||
|
|
||||||
This occurs specifically in the function ``j2k_encode_entry``, at the line::
|
This occurs specifically in the function ``j2k_encode_entry``, at the line:
|
||||||
|
|
||||||
|
.. code-block:: c
|
||||||
|
|
||||||
state->buffer = malloc (tile_width * tile_height * components * prec / 8);
|
state->buffer = malloc (tile_width * tile_height * components * prec / 8);
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ Removed deprecated PIL.OleFileIO
|
||||||
PIL.OleFileIO was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of
|
PIL.OleFileIO was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of
|
||||||
the upstream olefile Python package, and replaced with an ``ImportError``. The
|
the upstream olefile Python package, and replaced with an ``ImportError``. The
|
||||||
deprecated file has now been removed from Pillow. If needed, install from PyPI (eg.
|
deprecated file has now been removed from Pillow. If needed, install from PyPI (eg.
|
||||||
``pip install olefile``).
|
``python3 -m pip install olefile``).
|
||||||
|
|
||||||
Removed deprecated ImageOps functions
|
Removed deprecated ImageOps functions
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -73,7 +73,7 @@ Security
|
||||||
========
|
========
|
||||||
|
|
||||||
This release catches several buffer overruns, as well as addressing
|
This release catches several buffer overruns, as well as addressing
|
||||||
CVE-2019-16865. The CVE is regarding DOS problems, such as consuming large
|
:cve:`CVE-2019-16865`. The CVE is regarding DOS problems, such as consuming large
|
||||||
amounts of memory, or taking a large amount of time to process an image.
|
amounts of memory, or taking a large amount of time to process an image.
|
||||||
|
|
||||||
In RawDecode.c, an error is now thrown if skip is calculated to be less than
|
In RawDecode.c, an error is now thrown if skip is calculated to be less than
|
||||||
|
@ -96,14 +96,14 @@ Other Changes
|
||||||
Removed bdist_wininst .exe installers
|
Removed bdist_wininst .exe installers
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
.exe installers fell out of favour with PEP 527, and will be deprecated in
|
.exe installers fell out of favour with :pep:`527`, and will be deprecated in
|
||||||
Python 3.8. Pillow will no longer be distributing them. Wheels should be used
|
Python 3.8. Pillow will no longer be distributing them. Wheels should be used
|
||||||
instead.
|
instead.
|
||||||
|
|
||||||
Flags for libwebp in wheels
|
Flags for libwebp in wheels
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
When building libwebp for inclusion in wheels, Pillow now adds the -O3 and
|
When building libwebp for inclusion in wheels, Pillow now adds the ``-O3`` and
|
||||||
-DNDEBUG CFLAGS. These flags would be used by default if building libwebp
|
``-DNDEBUG`` CFLAGS. These flags would be used by default if building libwebp
|
||||||
without debugging, and using them fixes a significant decrease in speed when
|
without debugging, and using them fixes a significant decrease in speed when
|
||||||
a wheel-installed copy of Pillow performs libwebp operations.
|
a wheel-installed copy of Pillow performs libwebp operations.
|
||||||
|
|
|
@ -6,12 +6,13 @@ Security
|
||||||
|
|
||||||
This release addresses several security problems.
|
This release addresses several security problems.
|
||||||
|
|
||||||
CVE-2019-19911 is regarding FPX images. If an image reports that it has a large number
|
:cve:`CVE-2019-19911` is regarding FPX images. If an image reports that it has a large
|
||||||
of bands, a large amount of resources will be used when trying to process the
|
number of bands, a large amount of resources will be used when trying to process the
|
||||||
image. This is fixed by limiting the number of bands to those usable by Pillow.
|
image. This is fixed by limiting the number of bands to those usable by Pillow.
|
||||||
|
|
||||||
Buffer overruns were found when processing an SGI (CVE-2020-5311), PCX (CVE-2020-5312)
|
Buffer overruns were found when processing an SGI (:cve:`CVE-2020-5311`),
|
||||||
or FLI image (CVE-2020-5313). Checks have been added to prevent this.
|
PCX (:cve:`CVE-2020-5312`) or FLI image (:cve:`CVE-2020-5313`). Checks have been added
|
||||||
|
to prevent this.
|
||||||
|
|
||||||
CVE-2020-5310: Overflow checks have been added when calculating the size of a memory
|
:cve:`CVE-2020-5310`: Overflow checks have been added when calculating the size of a
|
||||||
block to be reallocated in the processing of a TIFF image.
|
memory block to be reallocated in the processing of a TIFF image.
|
||||||
|
|
|
@ -74,11 +74,11 @@ Security
|
||||||
|
|
||||||
This release includes security fixes.
|
This release includes security fixes.
|
||||||
|
|
||||||
* CVE-2020-10177 Fix multiple OOB reads in FLI decoding
|
* :cve:`CVE-2020-10177` Fix multiple OOB reads in FLI decoding
|
||||||
* CVE-2020-10378 Fix bounds overflow in PCX decoding
|
* :cve:`CVE-2020-10378` Fix bounds overflow in PCX decoding
|
||||||
* CVE-2020-10379 Fix two buffer overflows in TIFF decoding
|
* :cve:`CVE-2020-10379` Fix two buffer overflows in TIFF decoding
|
||||||
* CVE-2020-10994 Fix bounds overflow in JPEG 2000 decoding
|
* :cve:`CVE-2020-10994` Fix bounds overflow in JPEG 2000 decoding
|
||||||
* CVE-2020-11538 Fix buffer overflow in SGI-RLE decoding
|
* :cve:`CVE-2020-11538` Fix buffer overflow in SGI-RLE decoding
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -7,7 +7,7 @@ Fix regression seeking PNG files
|
||||||
This fixes a regression introduced in 7.1.0 when adding support for APNG files when calling
|
This fixes a regression introduced in 7.1.0 when adding support for APNG files when calling
|
||||||
``seek`` and ``tell``:
|
``seek`` and ``tell``:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: pycon
|
||||||
|
|
||||||
>>> from PIL import Image
|
>>> from PIL import Image
|
||||||
>>> with Image.open("Tests/images/hopper.png") as im:
|
>>> with Image.open("Tests/images/hopper.png") as im:
|
||||||
|
|
|
@ -9,7 +9,7 @@ This fixes a regression introduced in 7.1.0 when adding support for APNG files.
|
||||||
When calling ``seek(n)`` on a regular PNG where ``n > 0``, it failed to raise an
|
When calling ``seek(n)`` on a regular PNG where ``n > 0``, it failed to raise an
|
||||||
``EOFError`` as it should have done, resulting in:
|
``EOFError`` as it should have done, resulting in:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: pycon
|
||||||
|
|
||||||
AttributeError: 'NoneType' object has no attribute 'read'
|
AttributeError: 'NoneType' object has no attribute 'read'
|
||||||
|
|
||||||
|
|
22
docs/releasenotes/8.0.1.rst
Normal file
22
docs/releasenotes/8.0.1.rst
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
8.0.1
|
||||||
|
-----
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
Update FreeType used in binary wheels to `2.10.4`_ to fix :cve:`CVE-2020-15999`:
|
||||||
|
|
||||||
|
- A heap buffer overflow has been found in the handling of embedded PNG bitmaps,
|
||||||
|
introduced in FreeType version 2.6.
|
||||||
|
|
||||||
|
If you use option ``FT_CONFIG_OPTION_USE_PNG`` you should upgrade immediately.
|
||||||
|
|
||||||
|
We strongly recommend updating to Pillow 8.0.1 if you are using Pillow 8.0.0, which improved support for bitmap fonts.
|
||||||
|
|
||||||
|
In Pillow 7.2.0 and earlier bitmap fonts were disabled with ``FT_LOAD_NO_BITMAP``, but it is not
|
||||||
|
clear if this prevents the exploit and we recommend updating to Pillow 8.0.1.
|
||||||
|
|
||||||
|
Pillow 8.0.0 and earlier are potentially vulnerable releases, including the last release
|
||||||
|
to support Python 2.7, namely Pillow 6.2.2.
|
||||||
|
|
||||||
|
.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/
|
89
docs/releasenotes/8.1.0.rst
Normal file
89
docs/releasenotes/8.1.0.rst
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
8.1.0
|
||||||
|
-----
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
============
|
||||||
|
|
||||||
|
FreeType 2.7
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Support for FreeType 2.7 is deprecated and will be removed in Pillow 9.0.0 (2022-01-02),
|
||||||
|
when FreeType 2.8 will be the minimum supported.
|
||||||
|
|
||||||
|
We recommend upgrading to at least FreeType `2.10.4`_, which fixed a severe
|
||||||
|
vulnerability introduced in FreeType 2.6 (:cve:`CVE-2020-15999`).
|
||||||
|
|
||||||
|
.. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/
|
||||||
|
|
||||||
|
Makefile
|
||||||
|
^^^^^^^^
|
||||||
|
|
||||||
|
The 'install-venv' target has been deprecated.
|
||||||
|
|
||||||
|
API Additions
|
||||||
|
=============
|
||||||
|
|
||||||
|
Append images to ICO
|
||||||
|
^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When saving an ICO image, the file may contain versions of the image at different
|
||||||
|
sizes. By default, Pillow will scale down the main image to create these copies.
|
||||||
|
|
||||||
|
With this release, a list of images can be provided to the ``append_images`` parameter
|
||||||
|
when saving, to replace the scaled down versions. This is the same functionality that
|
||||||
|
already exists for the ICNS format.
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
This release includes security fixes.
|
||||||
|
|
||||||
|
* An out-of-bounds read when saving TIFFs with custom metadata through LibTIFF
|
||||||
|
* An out-of-bounds read when saving a GIF of 1px width
|
||||||
|
* :cve:`CVE-2020-35653` Buffer read overrun in PCX decoding
|
||||||
|
|
||||||
|
The PCX image decoder used the reported image stride to calculate the row buffer,
|
||||||
|
rather than calculating it from the image size. This issue dates back to the PIL fork.
|
||||||
|
Thanks to Google's `OSS-Fuzz`_ project for finding this.
|
||||||
|
|
||||||
|
* :cve:`CVE-2020-35654` Fix TIFF OOB Write error
|
||||||
|
|
||||||
|
OOB Write in TiffDecode.c when reading corrupt YCbCr files in some LibTIFF versions
|
||||||
|
(4.1.0/Ubuntu 20.04, but not 4.0.9/Ubuntu 18.04). In some cases LibTIFF's
|
||||||
|
interpretation of the file is different when reading in RGBA mode, leading to an Out of
|
||||||
|
bounds write in TiffDecode.c. This potentially affects Pillow versions from 6.0.0 to
|
||||||
|
8.0.1, depending on the version of LibTIFF. This was reported through `Tidelift`_.
|
||||||
|
|
||||||
|
* :cve:`CVE-2020-35655` Fix for SGI Decode buffer overrun
|
||||||
|
|
||||||
|
4 byte read overflow in SGIRleDecode.c, where the code was not correctly checking the
|
||||||
|
offsets and length tables. Independently reported through `Tidelift`_ and Google's
|
||||||
|
`OSS-Fuzz`_. This vulnerability covers Pillow versions 4.3.0->8.0.1.
|
||||||
|
|
||||||
|
.. _Tidelift: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pillow&utm_medium=referral&utm_campaign=docs
|
||||||
|
.. _OSS-Fuzz: https://github.com/google/oss-fuzz
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
OpenJPEG in the macOS and Linux wheels has been updated from 2.3.1 to 2.4.0, including
|
||||||
|
security fixes.
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Makefile
|
||||||
|
^^^^^^^^
|
||||||
|
|
||||||
|
The 'co' target has been removed.
|
||||||
|
|
||||||
|
PyPy wheels
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
Wheels have been added for PyPy 3.7.
|
||||||
|
|
||||||
|
PySide6
|
||||||
|
^^^^^^^
|
||||||
|
|
||||||
|
Support has been added for PySide6. If it is installed, it will be used instead of
|
||||||
|
PyQt5 or PySide2, since it is based on a newer Qt.
|
|
@ -3,7 +3,8 @@ Release Notes
|
||||||
|
|
||||||
Pillow is released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
Pillow is released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
Patch releases are created if the latest release contains severe bugs, or if security
|
Patch releases are created if the latest release contains severe bugs, or if security
|
||||||
fixes are put together before a scheduled release.
|
fixes are put together before a scheduled release. See :ref:`versioning` for more
|
||||||
|
information.
|
||||||
|
|
||||||
Please use the latest version of Pillow. Functionality and security fixes should not be
|
Please use the latest version of Pillow. Functionality and security fixes should not be
|
||||||
expected to be backported to earlier versions.
|
expected to be backported to earlier versions.
|
||||||
|
@ -13,6 +14,8 @@ expected to be backported to earlier versions.
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
8.1.0
|
||||||
|
8.0.1
|
||||||
8.0.0
|
8.0.0
|
||||||
7.2.0
|
7.2.0
|
||||||
7.1.2
|
7.1.2
|
||||||
|
@ -46,3 +49,4 @@ expected to be backported to earlier versions.
|
||||||
3.0.0
|
3.0.0
|
||||||
2.8.0
|
2.8.0
|
||||||
2.7.0
|
2.7.0
|
||||||
|
versioning
|
||||||
|
|
30
docs/releasenotes/versioning.rst
Normal file
30
docs/releasenotes/versioning.rst
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
.. _versioning:
|
||||||
|
|
||||||
|
Versioning
|
||||||
|
==========
|
||||||
|
|
||||||
|
Pillow follows [Semantic Versioning](https://semver.org/):
|
||||||
|
|
||||||
|
Given a version number MAJOR.MINOR.PATCH, increment the:
|
||||||
|
|
||||||
|
1. MAJOR version when you make incompatible API changes,
|
||||||
|
2. MINOR version when you add functionality in a backwards compatible manner, and
|
||||||
|
3. PATCH version when you make backwards compatible bug fixes.
|
||||||
|
|
||||||
|
Quarterly releases ("`Main Release <https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#main-release>`_")
|
||||||
|
bump at least the MINOR version, as new functionality has likely been added in the
|
||||||
|
prior three months.
|
||||||
|
|
||||||
|
A quarterly release bumps the MAJOR version when incompatible API changes are
|
||||||
|
made, such as removing deprecated APIs or dropping an EOL Python version. In practice,
|
||||||
|
these occur every 12-18 months, guided by
|
||||||
|
`Python's EOL schedule <https://devguide.python.org/#status-of-python-branches>`_, and
|
||||||
|
any APIs that have been deprecated for at least a year are removed at the same time.
|
||||||
|
|
||||||
|
PATCH versions ("`Point Release <https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#point-release>`_"
|
||||||
|
or "`Embargoed Release <https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#embargoed-release>`_")
|
||||||
|
are for security, installation or critical bug fixes. These are less common as it is
|
||||||
|
preferred to stick to quarterly releases.
|
||||||
|
|
||||||
|
Between quarterly releases, ".dev0" is appended to the "master" branch, indicating that
|
||||||
|
this is not a formally released copy.
|
|
@ -1196,6 +1196,11 @@
|
||||||
color: rgb(166, 158, 146);
|
color: rgb(166, 158, 146);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wy-menu-vertical li.toctree-l2.current a,
|
||||||
|
.wy-menu-vertical li.toctree-l3.current a {
|
||||||
|
background-color: #363636;
|
||||||
|
}
|
||||||
|
|
||||||
.wy-menu-vertical li ul li a {
|
.wy-menu-vertical li ul li a {
|
||||||
color: rgb(208, 204, 198);
|
color: rgb(208, 204, 198);
|
||||||
}
|
}
|
||||||
|
|
8
docs/resources/css/light.css
Normal file
8
docs/resources/css/light.css
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
|
||||||
|
.wy-menu-vertical li.toctree-l2.current a,
|
||||||
|
.wy-menu-vertical li.toctree-l3.current a {
|
||||||
|
background-color: #c9c9c9;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,13 +24,11 @@ jQuery(document).ready(function ($) {
|
||||||
var $upperA = $sidebarItem.parent().children('a');
|
var $upperA = $sidebarItem.parent().children('a');
|
||||||
var $upperAParent = $upperA.parent();
|
var $upperAParent = $upperA.parent();
|
||||||
if ($upperAParent.hasClass('toctree-l2')) {
|
if ($upperAParent.hasClass('toctree-l2')) {
|
||||||
$a.css('background-color', '#c9c9c9');
|
|
||||||
$a.css('padding-left', '4em');
|
$a.css('padding-left', '4em');
|
||||||
} else if ($upperAParent.hasClass('toctree-l3')) {
|
} else if ($upperAParent.hasClass('toctree-l3')) {
|
||||||
if (!$upperA.find('.toctree-expand').length) {
|
if (!$upperA.find('.toctree-expand').length) {
|
||||||
$upperA.prepend($('<span />').addClass('toctree-expand'));
|
$upperA.prepend($('<span />').addClass('toctree-expand'));
|
||||||
}
|
}
|
||||||
$a.css('background-color', '#c9c9c9');
|
|
||||||
$a.css('padding-left', '5em');
|
$a.css('padding-left', '5em');
|
||||||
} else {
|
} else {
|
||||||
$a.css('background-color', '#bdbdbd');
|
$a.css('background-color', '#bdbdbd');
|
||||||
|
|
BIN
docs/resources/pillow-logo.png
Normal file
BIN
docs/resources/pillow-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -2,13 +2,13 @@
|
||||||
black
|
black
|
||||||
check-manifest
|
check-manifest
|
||||||
coverage
|
coverage
|
||||||
jarn.viewdoc
|
markdown2
|
||||||
olefile
|
olefile
|
||||||
pycodestyle
|
packaging
|
||||||
pyflakes
|
|
||||||
pyroma
|
pyroma
|
||||||
pytest
|
pytest
|
||||||
pytest-cov
|
pytest-cov
|
||||||
sphinx>=2.4
|
sphinx>=2.4
|
||||||
|
sphinx-issues
|
||||||
sphinx-removed-in
|
sphinx-removed-in
|
||||||
sphinx-rtd-theme
|
sphinx-rtd-theme
|
||||||
|
|
20
setup.py
20
setup.py
|
@ -38,12 +38,16 @@ ZLIB_ROOT = None
|
||||||
|
|
||||||
|
|
||||||
if sys.platform == "win32" and sys.version_info >= (3, 10):
|
if sys.platform == "win32" and sys.version_info >= (3, 10):
|
||||||
warnings.warn(
|
import atexit
|
||||||
f"Pillow {PILLOW_VERSION} does not support Python "
|
|
||||||
f"{sys.version_info.major}.{sys.version_info.minor} and does not provide "
|
atexit.register(
|
||||||
"prebuilt Windows binaries. We do not recommend building from source on "
|
lambda: warnings.warn(
|
||||||
"Windows.",
|
f"Pillow {PILLOW_VERSION} does not support Python "
|
||||||
RuntimeWarning,
|
f"{sys.version_info.major}.{sys.version_info.minor} and does not provide "
|
||||||
|
"prebuilt Windows binaries. We do not recommend building from source on "
|
||||||
|
"Windows.",
|
||||||
|
RuntimeWarning,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -873,6 +877,10 @@ try:
|
||||||
"Source": "https://github.com/python-pillow/Pillow",
|
"Source": "https://github.com/python-pillow/Pillow",
|
||||||
"Funding": "https://tidelift.com/subscription/pkg/pypi-pillow?"
|
"Funding": "https://tidelift.com/subscription/pkg/pypi-pillow?"
|
||||||
"utm_source=pypi-pillow&utm_medium=pypi",
|
"utm_source=pypi-pillow&utm_medium=pypi",
|
||||||
|
"Release notes": "https://pillow.readthedocs.io/en/stable/releasenotes/"
|
||||||
|
"index.html",
|
||||||
|
"Changelog": "https://github.com/python-pillow/Pillow/blob/master/"
|
||||||
|
"CHANGES.rst",
|
||||||
},
|
},
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 6 - Mature",
|
"Development Status :: 6 - Mature",
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
|
|
||||||
|
|
||||||
from . import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import i8
|
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
|
@ -52,7 +51,7 @@ def _accept(prefix):
|
||||||
|
|
||||||
|
|
||||||
def _dib_accept(prefix):
|
def _dib_accept(prefix):
|
||||||
return i32(prefix[:4]) in [12, 40, 64, 108, 124]
|
return i32(prefix) in [12, 40, 64, 108, 124]
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
@ -87,34 +86,34 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
# -------------------------------------------------- IBM OS/2 Bitmap v1
|
# -------------------------------------------------- IBM OS/2 Bitmap v1
|
||||||
# ----- This format has different offsets because of width/height types
|
# ----- This format has different offsets because of width/height types
|
||||||
if file_info["header_size"] == 12:
|
if file_info["header_size"] == 12:
|
||||||
file_info["width"] = i16(header_data[0:2])
|
file_info["width"] = i16(header_data, 0)
|
||||||
file_info["height"] = i16(header_data[2:4])
|
file_info["height"] = i16(header_data, 2)
|
||||||
file_info["planes"] = i16(header_data[4:6])
|
file_info["planes"] = i16(header_data, 4)
|
||||||
file_info["bits"] = i16(header_data[6:8])
|
file_info["bits"] = i16(header_data, 6)
|
||||||
file_info["compression"] = self.RAW
|
file_info["compression"] = self.RAW
|
||||||
file_info["palette_padding"] = 3
|
file_info["palette_padding"] = 3
|
||||||
|
|
||||||
# --------------------------------------------- Windows Bitmap v2 to v5
|
# --------------------------------------------- Windows Bitmap v2 to v5
|
||||||
# v3, OS/2 v2, v4, v5
|
# v3, OS/2 v2, v4, v5
|
||||||
elif file_info["header_size"] in (40, 64, 108, 124):
|
elif file_info["header_size"] in (40, 64, 108, 124):
|
||||||
file_info["y_flip"] = i8(header_data[7]) == 0xFF
|
file_info["y_flip"] = header_data[7] == 0xFF
|
||||||
file_info["direction"] = 1 if file_info["y_flip"] else -1
|
file_info["direction"] = 1 if file_info["y_flip"] else -1
|
||||||
file_info["width"] = i32(header_data[0:4])
|
file_info["width"] = i32(header_data, 0)
|
||||||
file_info["height"] = (
|
file_info["height"] = (
|
||||||
i32(header_data[4:8])
|
i32(header_data, 4)
|
||||||
if not file_info["y_flip"]
|
if not file_info["y_flip"]
|
||||||
else 2 ** 32 - i32(header_data[4:8])
|
else 2 ** 32 - i32(header_data, 4)
|
||||||
)
|
)
|
||||||
file_info["planes"] = i16(header_data[8:10])
|
file_info["planes"] = i16(header_data, 8)
|
||||||
file_info["bits"] = i16(header_data[10:12])
|
file_info["bits"] = i16(header_data, 10)
|
||||||
file_info["compression"] = i32(header_data[12:16])
|
file_info["compression"] = i32(header_data, 12)
|
||||||
# byte size of pixel data
|
# byte size of pixel data
|
||||||
file_info["data_size"] = i32(header_data[16:20])
|
file_info["data_size"] = i32(header_data, 16)
|
||||||
file_info["pixels_per_meter"] = (
|
file_info["pixels_per_meter"] = (
|
||||||
i32(header_data[20:24]),
|
i32(header_data, 20),
|
||||||
i32(header_data[24:28]),
|
i32(header_data, 24),
|
||||||
)
|
)
|
||||||
file_info["colors"] = i32(header_data[28:32])
|
file_info["colors"] = i32(header_data, 28)
|
||||||
file_info["palette_padding"] = 4
|
file_info["palette_padding"] = 4
|
||||||
self.info["dpi"] = tuple(
|
self.info["dpi"] = tuple(
|
||||||
int(x / 39.3701 + 0.5) for x in file_info["pixels_per_meter"]
|
int(x / 39.3701 + 0.5) for x in file_info["pixels_per_meter"]
|
||||||
|
@ -124,7 +123,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
for idx, mask in enumerate(
|
for idx, mask in enumerate(
|
||||||
["r_mask", "g_mask", "b_mask", "a_mask"]
|
["r_mask", "g_mask", "b_mask", "a_mask"]
|
||||||
):
|
):
|
||||||
file_info[mask] = i32(header_data[36 + idx * 4 : 40 + idx * 4])
|
file_info[mask] = i32(header_data, 36 + idx * 4)
|
||||||
else:
|
else:
|
||||||
# 40 byte headers only have the three components in the
|
# 40 byte headers only have the three components in the
|
||||||
# bitfields masks, ref:
|
# bitfields masks, ref:
|
||||||
|
@ -267,7 +266,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
if not _accept(head_data):
|
if not _accept(head_data):
|
||||||
raise SyntaxError("Not a BMP file")
|
raise SyntaxError("Not a BMP file")
|
||||||
# read the start position of the BMP image data (u32)
|
# read the start position of the BMP image data (u32)
|
||||||
offset = i32(head_data[10:14])
|
offset = i32(head_data, 10)
|
||||||
# load bitmap information (offset=raster info)
|
# load bitmap information (offset=raster info)
|
||||||
self._bitmap(offset=offset)
|
self._bitmap(offset=offset)
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
from . import BmpImagePlugin, Image
|
from . import BmpImagePlugin, Image
|
||||||
from ._binary import i8
|
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
|
|
||||||
|
@ -48,17 +47,17 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
||||||
|
|
||||||
# pick the largest cursor in the file
|
# pick the largest cursor in the file
|
||||||
m = b""
|
m = b""
|
||||||
for i in range(i16(s[4:])):
|
for i in range(i16(s, 4)):
|
||||||
s = self.fp.read(16)
|
s = self.fp.read(16)
|
||||||
if not m:
|
if not m:
|
||||||
m = s
|
m = s
|
||||||
elif i8(s[0]) > i8(m[0]) and i8(s[1]) > i8(m[1]):
|
elif s[0] > m[0] and s[1] > m[1]:
|
||||||
m = s
|
m = s
|
||||||
if not m:
|
if not m:
|
||||||
raise TypeError("No cursors were found")
|
raise TypeError("No cursors were found")
|
||||||
|
|
||||||
# load as bitmap
|
# load as bitmap
|
||||||
self._bitmap(i32(m[12:]) + offset)
|
self._bitmap(i32(m, 12) + offset)
|
||||||
|
|
||||||
# patch up the bitmap height
|
# patch up the bitmap height
|
||||||
self._size = self.size[0], self.size[1] // 2
|
self._size = self.size[0], self.size[1] // 2
|
||||||
|
|
|
@ -312,14 +312,14 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
fp.seek(0, io.SEEK_END)
|
fp.seek(0, io.SEEK_END)
|
||||||
length = fp.tell()
|
length = fp.tell()
|
||||||
offset = 0
|
offset = 0
|
||||||
elif i32(s[0:4]) == 0xC6D3D0C5:
|
elif i32(s, 0) == 0xC6D3D0C5:
|
||||||
# FIX for: Some EPS file not handled correctly / issue #302
|
# FIX for: Some EPS file not handled correctly / issue #302
|
||||||
# EPS can contain binary data
|
# EPS can contain binary data
|
||||||
# or start directly with latin coding
|
# or start directly with latin coding
|
||||||
# more info see:
|
# more info see:
|
||||||
# https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
# https://web.archive.org/web/20160528181353/http://partners.adobe.com/public/developer/en/ps/5002.EPSF_Spec.pdf
|
||||||
offset = i32(s[4:8])
|
offset = i32(s, 4)
|
||||||
length = i32(s[8:12])
|
length = i32(s, 8)
|
||||||
else:
|
else:
|
||||||
raise SyntaxError("not an EPS file")
|
raise SyntaxError("not an EPS file")
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
|
|
||||||
from . import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import i8
|
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
|
@ -27,7 +26,7 @@ from ._binary import o8
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return len(prefix) >= 6 and i16(prefix[4:6]) in [0xAF11, 0xAF12]
|
return len(prefix) >= 6 and i16(prefix, 4) in [0xAF11, 0xAF12]
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -47,22 +46,22 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
s = self.fp.read(128)
|
s = self.fp.read(128)
|
||||||
if not (
|
if not (
|
||||||
_accept(s)
|
_accept(s)
|
||||||
and i16(s[14:16]) in [0, 3] # flags
|
and i16(s, 14) in [0, 3] # flags
|
||||||
and s[20:22] == b"\x00\x00" # reserved
|
and s[20:22] == b"\x00\x00" # reserved
|
||||||
):
|
):
|
||||||
raise SyntaxError("not an FLI/FLC file")
|
raise SyntaxError("not an FLI/FLC file")
|
||||||
|
|
||||||
# frames
|
# frames
|
||||||
self.n_frames = i16(s[6:8])
|
self.n_frames = i16(s, 6)
|
||||||
self.is_animated = self.n_frames > 1
|
self.is_animated = self.n_frames > 1
|
||||||
|
|
||||||
# image characteristics
|
# image characteristics
|
||||||
self.mode = "P"
|
self.mode = "P"
|
||||||
self._size = i16(s[8:10]), i16(s[10:12])
|
self._size = i16(s, 8), i16(s, 10)
|
||||||
|
|
||||||
# animation speed
|
# animation speed
|
||||||
duration = i32(s[16:20])
|
duration = i32(s, 16)
|
||||||
magic = i16(s[4:6])
|
magic = i16(s, 4)
|
||||||
if magic == 0xAF11:
|
if magic == 0xAF11:
|
||||||
duration = (duration * 1000) // 70
|
duration = (duration * 1000) // 70
|
||||||
self.info["duration"] = duration
|
self.info["duration"] = duration
|
||||||
|
@ -74,17 +73,17 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self.__offset = 128
|
self.__offset = 128
|
||||||
|
|
||||||
if i16(s[4:6]) == 0xF100:
|
if i16(s, 4) == 0xF100:
|
||||||
# prefix chunk; ignore it
|
# prefix chunk; ignore it
|
||||||
self.__offset = self.__offset + i32(s)
|
self.__offset = self.__offset + i32(s)
|
||||||
s = self.fp.read(16)
|
s = self.fp.read(16)
|
||||||
|
|
||||||
if i16(s[4:6]) == 0xF1FA:
|
if i16(s, 4) == 0xF1FA:
|
||||||
# look for palette chunk
|
# look for palette chunk
|
||||||
s = self.fp.read(6)
|
s = self.fp.read(6)
|
||||||
if i16(s[4:6]) == 11:
|
if i16(s, 4) == 11:
|
||||||
self._palette(palette, 2)
|
self._palette(palette, 2)
|
||||||
elif i16(s[4:6]) == 4:
|
elif i16(s, 4) == 4:
|
||||||
self._palette(palette, 0)
|
self._palette(palette, 0)
|
||||||
|
|
||||||
palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
|
palette = [o8(r) + o8(g) + o8(b) for (r, g, b) in palette]
|
||||||
|
@ -102,15 +101,15 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
i = 0
|
i = 0
|
||||||
for e in range(i16(self.fp.read(2))):
|
for e in range(i16(self.fp.read(2))):
|
||||||
s = self.fp.read(2)
|
s = self.fp.read(2)
|
||||||
i = i + i8(s[0])
|
i = i + s[0]
|
||||||
n = i8(s[1])
|
n = s[1]
|
||||||
if n == 0:
|
if n == 0:
|
||||||
n = 256
|
n = 256
|
||||||
s = self.fp.read(n * 3)
|
s = self.fp.read(n * 3)
|
||||||
for n in range(0, len(s), 3):
|
for n in range(0, len(s), 3):
|
||||||
r = i8(s[n]) << shift
|
r = s[n] << shift
|
||||||
g = i8(s[n + 1]) << shift
|
g = s[n + 1] << shift
|
||||||
b = i8(s[n + 2]) << shift
|
b = s[n + 2] << shift
|
||||||
palette[i] = (r, g, b)
|
palette[i] = (r, g, b)
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
import olefile
|
import olefile
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from ._binary import i8
|
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
|
|
||||||
# we map from colour field tuples to (mode, rawmode) descriptors
|
# we map from colour field tuples to (mode, rawmode) descriptors
|
||||||
|
@ -181,8 +180,8 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
elif compression == 2:
|
elif compression == 2:
|
||||||
|
|
||||||
internal_color_conversion = i8(s[14])
|
internal_color_conversion = s[14]
|
||||||
jpeg_tables = i8(s[15])
|
jpeg_tables = s[15]
|
||||||
rawmode = self.rawmode
|
rawmode = self.rawmode
|
||||||
|
|
||||||
if internal_color_conversion:
|
if internal_color_conversion:
|
||||||
|
|
|
@ -29,7 +29,7 @@ from ._binary import i32be as i32
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return len(prefix) >= 8 and i32(prefix[:4]) >= 20 and i32(prefix[4:8]) in (1, 2)
|
return len(prefix) >= 8 and i32(prefix, 0) >= 20 and i32(prefix, 4) in (1, 2)
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
|
|
||||||
|
|
||||||
from . import ImageFile, ImagePalette, UnidentifiedImageError
|
from . import ImageFile, ImagePalette, UnidentifiedImageError
|
||||||
from ._binary import i8
|
|
||||||
from ._binary import i16be as i16
|
from ._binary import i16be as i16
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
|
|
||||||
|
@ -49,17 +48,17 @@ class GdImageFile(ImageFile.ImageFile):
|
||||||
# Header
|
# Header
|
||||||
s = self.fp.read(1037)
|
s = self.fp.read(1037)
|
||||||
|
|
||||||
if not i16(s[:2]) in [65534, 65535]:
|
if not i16(s) in [65534, 65535]:
|
||||||
raise SyntaxError("Not a valid GD 2.x .gd file")
|
raise SyntaxError("Not a valid GD 2.x .gd file")
|
||||||
|
|
||||||
self.mode = "L" # FIXME: "P"
|
self.mode = "L" # FIXME: "P"
|
||||||
self._size = i16(s[2:4]), i16(s[4:6])
|
self._size = i16(s, 2), i16(s, 4)
|
||||||
|
|
||||||
trueColor = i8(s[6])
|
trueColor = s[6]
|
||||||
trueColorOffset = 2 if trueColor else 0
|
trueColorOffset = 2 if trueColor else 0
|
||||||
|
|
||||||
# transparency index
|
# transparency index
|
||||||
tindex = i32(s[7 + trueColorOffset : 7 + trueColorOffset + 4])
|
tindex = i32(s, 7 + trueColorOffset)
|
||||||
if tindex < 256:
|
if tindex < 256:
|
||||||
self.info["transparency"] = tindex
|
self.info["transparency"] = tindex
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
|
from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence
|
||||||
from ._binary import i8
|
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from ._binary import o16le as o16
|
from ._binary import o16le as o16
|
||||||
|
@ -58,8 +57,8 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
def data(self):
|
def data(self):
|
||||||
s = self.fp.read(1)
|
s = self.fp.read(1)
|
||||||
if s and i8(s):
|
if s and s[0]:
|
||||||
return self.fp.read(i8(s))
|
return self.fp.read(s[0])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
|
@ -70,18 +69,18 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
raise SyntaxError("not a GIF file")
|
raise SyntaxError("not a GIF file")
|
||||||
|
|
||||||
self.info["version"] = s[:6]
|
self.info["version"] = s[:6]
|
||||||
self._size = i16(s[6:]), i16(s[8:])
|
self._size = i16(s, 6), i16(s, 8)
|
||||||
self.tile = []
|
self.tile = []
|
||||||
flags = i8(s[10])
|
flags = s[10]
|
||||||
bits = (flags & 7) + 1
|
bits = (flags & 7) + 1
|
||||||
|
|
||||||
if flags & 128:
|
if flags & 128:
|
||||||
# get global palette
|
# get global palette
|
||||||
self.info["background"] = i8(s[11])
|
self.info["background"] = s[11]
|
||||||
# check if palette contains colour indices
|
# check if palette contains colour indices
|
||||||
p = self.fp.read(3 << bits)
|
p = self.fp.read(3 << bits)
|
||||||
for i in range(0, len(p), 3):
|
for i in range(0, len(p), 3):
|
||||||
if not (i // 3 == i8(p[i]) == i8(p[i + 1]) == i8(p[i + 2])):
|
if not (i // 3 == p[i] == p[i + 1] == p[i + 2]):
|
||||||
p = ImagePalette.raw("RGB", p)
|
p = ImagePalette.raw("RGB", p)
|
||||||
self.global_palette = self.palette = p
|
self.global_palette = self.palette = p
|
||||||
break
|
break
|
||||||
|
@ -187,14 +186,14 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
#
|
#
|
||||||
s = self.fp.read(1)
|
s = self.fp.read(1)
|
||||||
block = self.data()
|
block = self.data()
|
||||||
if i8(s) == 249:
|
if s[0] == 249:
|
||||||
#
|
#
|
||||||
# graphic control extension
|
# graphic control extension
|
||||||
#
|
#
|
||||||
flags = i8(block[0])
|
flags = block[0]
|
||||||
if flags & 1:
|
if flags & 1:
|
||||||
info["transparency"] = i8(block[3])
|
info["transparency"] = block[3]
|
||||||
info["duration"] = i16(block[1:3]) * 10
|
info["duration"] = i16(block, 1) * 10
|
||||||
|
|
||||||
# disposal method - find the value of bits 4 - 6
|
# disposal method - find the value of bits 4 - 6
|
||||||
dispose_bits = 0b00011100 & flags
|
dispose_bits = 0b00011100 & flags
|
||||||
|
@ -205,7 +204,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
# correct, but it seems to prevent the last
|
# correct, but it seems to prevent the last
|
||||||
# frame from looking odd for some animations
|
# frame from looking odd for some animations
|
||||||
self.disposal_method = dispose_bits
|
self.disposal_method = dispose_bits
|
||||||
elif i8(s) == 254:
|
elif s[0] == 254:
|
||||||
#
|
#
|
||||||
# comment extension
|
# comment extension
|
||||||
#
|
#
|
||||||
|
@ -216,15 +215,15 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
info["comment"] = block
|
info["comment"] = block
|
||||||
block = self.data()
|
block = self.data()
|
||||||
continue
|
continue
|
||||||
elif i8(s) == 255:
|
elif s[0] == 255:
|
||||||
#
|
#
|
||||||
# application extension
|
# application extension
|
||||||
#
|
#
|
||||||
info["extension"] = block, self.fp.tell()
|
info["extension"] = block, self.fp.tell()
|
||||||
if block[:11] == b"NETSCAPE2.0":
|
if block[:11] == b"NETSCAPE2.0":
|
||||||
block = self.data()
|
block = self.data()
|
||||||
if len(block) >= 3 and i8(block[0]) == 1:
|
if len(block) >= 3 and block[0] == 1:
|
||||||
info["loop"] = i16(block[1:3])
|
info["loop"] = i16(block, 1)
|
||||||
while self.data():
|
while self.data():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -235,12 +234,12 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
s = self.fp.read(9)
|
s = self.fp.read(9)
|
||||||
|
|
||||||
# extent
|
# extent
|
||||||
x0, y0 = i16(s[0:]), i16(s[2:])
|
x0, y0 = i16(s, 0), i16(s, 2)
|
||||||
x1, y1 = x0 + i16(s[4:]), y0 + i16(s[6:])
|
x1, y1 = x0 + i16(s, 4), y0 + i16(s, 6)
|
||||||
if x1 > self.size[0] or y1 > self.size[1]:
|
if x1 > self.size[0] or y1 > self.size[1]:
|
||||||
self._size = max(x1, self.size[0]), max(y1, self.size[1])
|
self._size = max(x1, self.size[0]), max(y1, self.size[1])
|
||||||
self.dispose_extent = x0, y0, x1, y1
|
self.dispose_extent = x0, y0, x1, y1
|
||||||
flags = i8(s[8])
|
flags = s[8]
|
||||||
|
|
||||||
interlace = (flags & 64) != 0
|
interlace = (flags & 64) != 0
|
||||||
|
|
||||||
|
@ -249,7 +248,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.palette = ImagePalette.raw("RGB", self.fp.read(3 << bits))
|
self.palette = ImagePalette.raw("RGB", self.fp.read(3 << bits))
|
||||||
|
|
||||||
# image data
|
# image data
|
||||||
bits = i8(self.fp.read(1))
|
bits = self.fp.read(1)[0]
|
||||||
self.__offset = self.fp.tell()
|
self.__offset = self.fp.tell()
|
||||||
self.tile = [
|
self.tile = [
|
||||||
("gif", (x0, y0, x1, y1), self.__offset, (bits, interlace))
|
("gif", (x0, y0, x1, y1), self.__offset, (bits, interlace))
|
||||||
|
@ -258,7 +257,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
# raise OSError, "illegal GIF tag `%x`" % i8(s)
|
# raise OSError, "illegal GIF tag `%x`" % s[0]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.disposal_method < 2:
|
if self.disposal_method < 2:
|
||||||
|
@ -301,13 +300,14 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# if the disposal method is 'do not dispose', transparent
|
# if the disposal method is 'do not dispose', transparent
|
||||||
# pixels should show the content of the previous frame
|
# pixels should show the content of the previous frame
|
||||||
if self._prev_im and self.disposal_method == 1:
|
if self._prev_im and self._prev_disposal_method == 1:
|
||||||
# we do this by pasting the updated area onto the previous
|
# we do this by pasting the updated area onto the previous
|
||||||
# frame which we then use as the current image content
|
# frame which we then use as the current image content
|
||||||
updated = self._crop(self.im, self.dispose_extent)
|
updated = self._crop(self.im, self.dispose_extent)
|
||||||
self._prev_im.paste(updated, self.dispose_extent, updated.convert("RGBA"))
|
self._prev_im.paste(updated, self.dispose_extent, updated.convert("RGBA"))
|
||||||
self.im = self._prev_im
|
self.im = self._prev_im
|
||||||
self._prev_im = self.im.copy()
|
self._prev_im = self.im.copy()
|
||||||
|
self._prev_disposal_method = self.disposal_method
|
||||||
|
|
||||||
def _close__fp(self):
|
def _close__fp(self):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from ._binary import i8
|
|
||||||
|
|
||||||
_handler = None
|
_handler = None
|
||||||
|
|
||||||
|
@ -30,7 +29,7 @@ def register_handler(handler):
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
return prefix[0:4] == b"GRIB" and i8(prefix[7]) == 1
|
return prefix[0:4] == b"GRIB" and prefix[7] == 1
|
||||||
|
|
||||||
|
|
||||||
class GribStubImageFile(ImageFile.StubImageFile):
|
class GribStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
|
@ -24,7 +24,6 @@ import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from PIL import Image, ImageFile, PngImagePlugin, features
|
from PIL import Image, ImageFile, PngImagePlugin, features
|
||||||
from PIL._binary import i8
|
|
||||||
|
|
||||||
enable_jpeg2k = features.check_codec("jpg_2000")
|
enable_jpeg2k = features.check_codec("jpg_2000")
|
||||||
if enable_jpeg2k:
|
if enable_jpeg2k:
|
||||||
|
@ -70,7 +69,7 @@ def read_32(fobj, start_length, size):
|
||||||
byte = fobj.read(1)
|
byte = fobj.read(1)
|
||||||
if not byte:
|
if not byte:
|
||||||
break
|
break
|
||||||
byte = i8(byte)
|
byte = byte[0]
|
||||||
if byte & 0x80:
|
if byte & 0x80:
|
||||||
blocksize = byte - 125
|
blocksize = byte - 125
|
||||||
byte = fobj.read(1)
|
byte = fobj.read(1)
|
||||||
|
|
|
@ -28,7 +28,6 @@ from io import BytesIO
|
||||||
from math import ceil, log
|
from math import ceil, log
|
||||||
|
|
||||||
from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
|
from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin
|
||||||
from ._binary import i8
|
|
||||||
from ._binary import i16le as i16
|
from ._binary import i16le as i16
|
||||||
from ._binary import i32le as i32
|
from ._binary import i32le as i32
|
||||||
|
|
||||||
|
@ -54,6 +53,7 @@ def _save(im, fp, filename):
|
||||||
sizes = list(sizes)
|
sizes = list(sizes)
|
||||||
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
|
fp.write(struct.pack("<H", len(sizes))) # idCount(2)
|
||||||
offset = fp.tell() + len(sizes) * 16
|
offset = fp.tell() + len(sizes) * 16
|
||||||
|
provided_images = {im.size: im for im in im.encoderinfo.get("append_images", [])}
|
||||||
for size in sizes:
|
for size in sizes:
|
||||||
width, height = size
|
width, height = size
|
||||||
# 0 means 256
|
# 0 means 256
|
||||||
|
@ -65,9 +65,11 @@ def _save(im, fp, filename):
|
||||||
fp.write(struct.pack("<H", 32)) # wBitCount(2)
|
fp.write(struct.pack("<H", 32)) # wBitCount(2)
|
||||||
|
|
||||||
image_io = BytesIO()
|
image_io = BytesIO()
|
||||||
# TODO: invent a more convenient method for proportional scalings
|
tmp = provided_images.get(size)
|
||||||
tmp = im.copy()
|
if not tmp:
|
||||||
tmp.thumbnail(size, Image.LANCZOS, reducing_gap=None)
|
# TODO: invent a more convenient method for proportional scalings
|
||||||
|
tmp = im.copy()
|
||||||
|
tmp.thumbnail(size, Image.LANCZOS, reducing_gap=None)
|
||||||
tmp.save(image_io, "png")
|
tmp.save(image_io, "png")
|
||||||
image_io.seek(0)
|
image_io.seek(0)
|
||||||
image_bytes = image_io.read()
|
image_bytes = image_io.read()
|
||||||
|
@ -100,21 +102,21 @@ class IcoFile:
|
||||||
self.entry = []
|
self.entry = []
|
||||||
|
|
||||||
# Number of items in file
|
# Number of items in file
|
||||||
self.nb_items = i16(s[4:])
|
self.nb_items = i16(s, 4)
|
||||||
|
|
||||||
# Get headers for each item
|
# Get headers for each item
|
||||||
for i in range(self.nb_items):
|
for i in range(self.nb_items):
|
||||||
s = buf.read(16)
|
s = buf.read(16)
|
||||||
|
|
||||||
icon_header = {
|
icon_header = {
|
||||||
"width": i8(s[0]),
|
"width": s[0],
|
||||||
"height": i8(s[1]),
|
"height": s[1],
|
||||||
"nb_color": i8(s[2]), # No. of colors in image (0 if >=8bpp)
|
"nb_color": s[2], # No. of colors in image (0 if >=8bpp)
|
||||||
"reserved": i8(s[3]),
|
"reserved": s[3],
|
||||||
"planes": i16(s[4:]),
|
"planes": i16(s, 4),
|
||||||
"bpp": i16(s[6:]),
|
"bpp": i16(s, 6),
|
||||||
"size": i32(s[8:]),
|
"size": i32(s, 8),
|
||||||
"offset": i32(s[12:]),
|
"offset": i32(s, 12),
|
||||||
}
|
}
|
||||||
|
|
||||||
# See Wikipedia
|
# See Wikipedia
|
||||||
|
|
|
@ -30,7 +30,6 @@ import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from . import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import i8
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# Standard tags
|
# Standard tags
|
||||||
|
@ -223,14 +222,14 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
linear = 1 # linear greyscale palette
|
linear = 1 # linear greyscale palette
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
if palette[i] == palette[i + 256] == palette[i + 512]:
|
if palette[i] == palette[i + 256] == palette[i + 512]:
|
||||||
if i8(palette[i]) != i:
|
if palette[i] != i:
|
||||||
linear = 0
|
linear = 0
|
||||||
else:
|
else:
|
||||||
greyscale = 0
|
greyscale = 0
|
||||||
if self.mode in ["L", "LA", "P", "PA"]:
|
if self.mode in ["L", "LA", "P", "PA"]:
|
||||||
if greyscale:
|
if greyscale:
|
||||||
if not linear:
|
if not linear:
|
||||||
self.lut = [i8(c) for c in palette[:256]]
|
self.lut = list(palette[:256])
|
||||||
else:
|
else:
|
||||||
if self.mode in ["L", "P"]:
|
if self.mode in ["L", "P"]:
|
||||||
self.mode = self.rawmode = "P"
|
self.mode = self.rawmode = "P"
|
||||||
|
@ -240,7 +239,7 @@ class ImImageFile(ImageFile.ImageFile):
|
||||||
self.palette = ImagePalette.raw("RGB;L", palette)
|
self.palette = ImagePalette.raw("RGB;L", palette)
|
||||||
elif self.mode == "RGB":
|
elif self.mode == "RGB":
|
||||||
if not greyscale or not linear:
|
if not greyscale or not linear:
|
||||||
self.lut = [i8(c) for c in palette]
|
self.lut = list(palette)
|
||||||
|
|
||||||
self.frame = 0
|
self.frame = 0
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ from . import (
|
||||||
_plugins,
|
_plugins,
|
||||||
_raise_version_warning,
|
_raise_version_warning,
|
||||||
)
|
)
|
||||||
from ._binary import i8, i32le
|
from ._binary import i32le
|
||||||
from ._util import deferred_error, isPath
|
from ._util import deferred_error, isPath
|
||||||
|
|
||||||
if sys.version_info >= (3, 7):
|
if sys.version_info >= (3, 7):
|
||||||
|
@ -670,7 +670,10 @@ class Image:
|
||||||
:returns: png version of the image as bytes
|
:returns: png version of the image as bytes
|
||||||
"""
|
"""
|
||||||
b = io.BytesIO()
|
b = io.BytesIO()
|
||||||
self.save(b, "PNG")
|
try:
|
||||||
|
self.save(b, "PNG")
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError("Could not save to PNG for display") from e
|
||||||
return b.getvalue()
|
return b.getvalue()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -815,9 +818,15 @@ class Image:
|
||||||
"""
|
"""
|
||||||
if self.im and self.palette and self.palette.dirty:
|
if self.im and self.palette and self.palette.dirty:
|
||||||
# realize palette
|
# realize palette
|
||||||
self.im.putpalette(*self.palette.getdata())
|
mode, arr = self.palette.getdata()
|
||||||
|
if mode == "RGBA":
|
||||||
|
mode = "RGB"
|
||||||
|
self.info["transparency"] = arr[3::4]
|
||||||
|
arr = bytes(
|
||||||
|
value for (index, value) in enumerate(arr) if index % 4 != 3
|
||||||
|
)
|
||||||
|
self.im.putpalette(mode, arr)
|
||||||
self.palette.dirty = 0
|
self.palette.dirty = 0
|
||||||
self.palette.mode = "RGB"
|
|
||||||
self.palette.rawmode = None
|
self.palette.rawmode = None
|
||||||
if "transparency" in self.info:
|
if "transparency" in self.info:
|
||||||
if isinstance(self.info["transparency"], int):
|
if isinstance(self.info["transparency"], int):
|
||||||
|
@ -825,6 +834,8 @@ class Image:
|
||||||
else:
|
else:
|
||||||
self.im.putpalettealphas(self.info["transparency"])
|
self.im.putpalettealphas(self.info["transparency"])
|
||||||
self.palette.mode = "RGBA"
|
self.palette.mode = "RGBA"
|
||||||
|
else:
|
||||||
|
self.palette.mode = "RGB"
|
||||||
|
|
||||||
if self.im:
|
if self.im:
|
||||||
if cffi and USE_CFFI_ACCESS:
|
if cffi and USE_CFFI_ACCESS:
|
||||||
|
@ -1367,7 +1378,7 @@ class Image:
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
x, y = self.im.getprojection()
|
x, y = self.im.getprojection()
|
||||||
return [i8(c) for c in x], [i8(c) for c in y]
|
return list(x), list(y)
|
||||||
|
|
||||||
def histogram(self, mask=None, extrema=None):
|
def histogram(self, mask=None, extrema=None):
|
||||||
"""
|
"""
|
||||||
|
@ -1626,7 +1637,7 @@ class Image:
|
||||||
self.im = im
|
self.im = im
|
||||||
self.pyaccess = None
|
self.pyaccess = None
|
||||||
self.mode = self.im.mode
|
self.mode = self.im.mode
|
||||||
except (KeyError, ValueError) as e:
|
except KeyError as e:
|
||||||
raise ValueError("illegal image mode") from e
|
raise ValueError("illegal image mode") from e
|
||||||
|
|
||||||
if self.mode in ("LA", "PA"):
|
if self.mode in ("LA", "PA"):
|
||||||
|
@ -1672,12 +1683,14 @@ class Image:
|
||||||
|
|
||||||
def putpalette(self, data, rawmode="RGB"):
|
def putpalette(self, data, rawmode="RGB"):
|
||||||
"""
|
"""
|
||||||
Attaches a palette to this image. The image must be a "P",
|
Attaches a palette to this image. The image must be a "P", "PA", "L"
|
||||||
"PA", "L" or "LA" image, and the palette sequence must contain
|
or "LA" image.
|
||||||
768 integer values, where each group of three values represent
|
|
||||||
the red, green, and blue values for the corresponding pixel
|
The palette sequence must contain either 768 integer values, or 1024
|
||||||
index. Instead of an integer sequence, you can use an 8-bit
|
integer values if alpha is included. Each group of values represents
|
||||||
string.
|
the red, green, blue (and alpha if included) values for the
|
||||||
|
corresponding pixel index. Instead of an integer sequence, you can use
|
||||||
|
an 8-bit string.
|
||||||
|
|
||||||
:param data: A palette sequence (either a list or a string).
|
:param data: A palette sequence (either a list or a string).
|
||||||
:param rawmode: The raw mode of the palette.
|
:param rawmode: The raw mode of the palette.
|
||||||
|
@ -2197,8 +2210,8 @@ class Image:
|
||||||
|
|
||||||
if command is not None:
|
if command is not None:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"The command parameter is deprecated and will be removed in a future "
|
"The command parameter is deprecated and will be removed in Pillow 9 "
|
||||||
"release. Use a subclass of ImageShow.Viewer instead.",
|
"(2022-01-02). Use a subclass of ImageShow.Viewer instead.",
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2905,6 +2918,8 @@ def open(fp, mode="r", formats=None):
|
||||||
|
|
||||||
def _open_core(fp, filename, prefix, formats):
|
def _open_core(fp, filename, prefix, formats):
|
||||||
for i in formats:
|
for i in formats:
|
||||||
|
if i not in OPEN:
|
||||||
|
init()
|
||||||
try:
|
try:
|
||||||
factory, accept = OPEN[i]
|
factory, accept = OPEN[i]
|
||||||
result = not accept or accept(prefix)
|
result = not accept or accept(prefix)
|
||||||
|
@ -3174,7 +3189,7 @@ def _showxv(image, title=None, **options):
|
||||||
del options["_internal_pillow"]
|
del options["_internal_pillow"]
|
||||||
else:
|
else:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"_showxv is deprecated and will be removed in a future release. "
|
"_showxv is deprecated and will be removed in Pillow 9 (2022-01-02). "
|
||||||
"Use Image.show instead.",
|
"Use Image.show instead.",
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
)
|
)
|
||||||
|
@ -3359,7 +3374,7 @@ class Exif(MutableMapping):
|
||||||
|
|
||||||
if self[0x927C][:8] == b"FUJIFILM":
|
if self[0x927C][:8] == b"FUJIFILM":
|
||||||
exif_data = self[0x927C]
|
exif_data = self[0x927C]
|
||||||
ifd_offset = i32le(exif_data[8:12])
|
ifd_offset = i32le(exif_data, 8)
|
||||||
ifd_data = exif_data[ifd_offset:]
|
ifd_data = exif_data[ifd_offset:]
|
||||||
|
|
||||||
makernote = {}
|
makernote = {}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user