Merge branch 'master' into jp2-decode-subsample

This commit is contained in:
Andrew Murray 2021-01-04 23:25:09 +11:00 committed by GitHub
commit 2341c6b933
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
226 changed files with 15644 additions and 14927 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
View 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

View File

@ -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
View File

@ -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
View 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

View File

@ -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
View 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 }}

View File

@ -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 }}

View File

@ -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"

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 .

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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.

View File

@ -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")

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

View File

@ -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):

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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")

View File

@ -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(

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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))

View 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

View File

@ -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())

View File

@ -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)

View File

@ -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:

View File

@ -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")

View File

@ -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.

View File

@ -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

View File

@ -1,5 +0,0 @@
#!/usr/bin/env bash
coverage xml
diff-cover coverage.xml
diff-quality --violation=pyflakes
diff-quality --violation=pycodestyle

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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?
----------- -----------

View File

@ -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")

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 |
| +------------------------------+--------------------------------+ + | +------------------------------+--------------------------------+ +

View File

@ -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).

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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);

View File

@ -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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -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.

View File

@ -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.

View File

@ -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
============= =============

View File

@ -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:

View File

@ -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'

View 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/

View 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.

View File

@ -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

View 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.

View File

@ -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);
} }

View 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;
}
}

View File

@ -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');

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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

View File

@ -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",

View File

@ -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)

View File

@ -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

View File

@ -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")

View 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

View File

@ -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:

View File

@ -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)
## ##

View File

@ -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

View File

@ -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:

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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