Merge branch 'master' into gif-transparency

This commit is contained in:
Andrew Murray 2021-04-02 17:40:13 +11:00
commit f96b405dad
999 changed files with 62790 additions and 38073 deletions

View File

@ -6,82 +6,52 @@ init:
# Uncomment previous line to get RDP access during the build. # Uncomment previous line to get RDP access during the build.
environment: environment:
X64_EXT: -x64
EXECUTABLE: python.exe EXECUTABLE: python.exe
PIP_DIR: Scripts
VENV: NO
TEST_OPTIONS: TEST_OPTIONS:
DEPLOY: YES DEPLOY: YES
matrix: matrix:
- PYTHON: C:/vp/pypy2 - PYTHON: C:/Python39
EXECUTABLE: bin/pypy.exe ARCHITECTURE: x86
PIP_DIR: bin APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
VENV: YES
- PYTHON: C:/Python27-x64
- PYTHON: C:/Python37
- PYTHON: C:/Python27
- PYTHON: C:/Python37-x64
- PYTHON: C:/Python36
- PYTHON: C:/Python36-x64 - PYTHON: C:/Python36-x64
- PYTHON: C:/Python35 ARCHITECTURE: x64
- PYTHON: C:/Python35-x64 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
- PYTHON: C:/msys64/mingw32
EXECUTABLE: bin/python3
PIP_DIR: bin
TEST_OPTIONS: --processes=0
DEPLOY: NO
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 c:\pillow-depends\*.zip c:\pillow\winbuild\ - xcopy /S /Y c:\pillow-depends\test_images\* c:\pillow\tests\images
- xcopy c:\pillow-depends\*.tar.gz c:\pillow\winbuild\ - 7z x ..\pillow-depends\nasm-2.14.02-win64.zip -oc:\
- xcopy /s c:\pillow-depends\test_images\* c:\pillow\tests\images - ..\pillow-depends\gs9540w32.exe /S
- path c:\nasm-2.14.02;C:\Program Files (x86)\gs\gs9.54.0\bin;%PATH%
- cd c:\pillow\winbuild\ - cd c:\pillow\winbuild\
- ps: | - ps: |
if ($env:PYTHON -eq "c:/vp/pypy2") c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
{ c:\pillow\winbuild\build\build_dep_all.cmd
c:\pillow\winbuild\appveyor_install_pypy.cmd
}
- ps: |
if ($env:PYTHON -eq "c:/msys64/mingw32")
{
c:\msys64\usr\bin\bash -l -c c:\\pillow\\winbuild\\appveyor_install_msys2_deps.sh
}
else
{
c:\python34\python.exe c:\pillow\winbuild\build_dep.py
c:\pillow\winbuild\build_deps.cmd
$host.SetShouldExit(0) $host.SetShouldExit(0)
} - path C:\pillow\winbuild\build\bin;%PATH%
- '%PYTHON%\%EXECUTABLE% -m pip install -U setuptools'
build_script: build_script:
- ps: | - ps: |
if ($env:PYTHON -eq "c:/msys64/mingw32") c:\pillow\winbuild\build\build_pillow.cmd install
{
c:\msys64\usr\bin\bash -l -c c:\\pillow\\winbuild\\appveyor_build_msys2.sh
Write-Host "through install"
$host.SetShouldExit(0) $host.SetShouldExit(0)
}
else
{
& $env:PYTHON/$env:EXECUTABLE c:\pillow\winbuild\build.py
$host.SetShouldExit(0)
}
- cd c:\pillow - cd c:\pillow
- '%PYTHON%\%EXECUTABLE% selftest.py --installed' - '%PYTHON%\%EXECUTABLE% selftest.py --installed'
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'
- '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov-report term --cov-report xml Tests' - c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE%
- '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"'
- '%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% - codecov --file coverage.xml --name %PYTHON% --flags AppVeyor
matrix: matrix:
fast_finish: true fast_finish: true
@ -97,9 +67,9 @@ 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\
- '%PYTHON%\%EXECUTABLE% c:\pillow\winbuild\build.py --wheel' - c:\pillow\winbuild\build\build_pillow.cmd bdist_wheel
- cd c:\pillow - cd c:\pillow
- ps: Get-ChildItem .\dist\*.* | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name } - ps: Get-ChildItem .\dist\*.* | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }

View File

@ -1,28 +0,0 @@
parameters:
name: '' # defaults for any parameters that aren't specified
vmImage: ''
jobs:
- job: ${{ parameters.name }}
pool:
vmImage: ${{ parameters.vmImage }}
strategy:
matrix:
Python37:
python.version: '3.7'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
architecture: 'x64'
- script: |
python -m pip install --upgrade tox
displayName: 'Install dependencies'
- script: |
tox -e lint
displayName: 'Lint'

View File

@ -1,22 +0,0 @@
parameters:
docker: '' # defaults for any parameters that aren't specified
dockerTag: 'master'
name: ''
vmImage: 'Ubuntu-16.04'
jobs:
- job: ${{ parameters.name }}
pool:
vmImage: ${{ parameters.vmImage }}
steps:
- script: |
docker pull pythonpillow/${{ parameters.docker }}:${{ parameters.dockerTag }}
displayName: 'Docker pull'
- script: |
# The Pillow user in the docker container is UID 1000
sudo chown -R 1000 $(Build.SourcesDirectory)
docker run -v $(Build.SourcesDirectory):/Pillow pythonpillow/${{ parameters.docker }}:${{ parameters.dockerTag }}
displayName: 'Docker build'

9
.ci/after_success.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
# gather the coverage data
pip3 install codecov
if [[ $MATRIX_DOCKER ]]; then
coverage xml --ignore-errors
else
coverage xml
fi

10
.ci/build.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
set -e
coverage erase
if [ $(uname) == "Darwin" ]; then
export CPPFLAGS="-I/usr/local/miniconda/include";
fi
make clean
make install-coverage

59
.ci/install.sh Executable file
View File

@ -0,0 +1,59 @@
#!/bin/bash
aptget_update()
{
if [ ! -z $1 ]; then
echo ""
echo "Retrying apt-get update..."
echo ""
fi
output=`sudo apt-get update 2>&1`
echo "$output"
if [[ $output == *[WE]:\ * ]]; then
return 1
fi
}
aptget_update || aptget_update retry || aptget_update retry
set -e
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
cmake imagemagick libharfbuzz-dev libfribidi-dev
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 -U pytest-timeout
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
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
if [ "$GHA_PYTHON_VERSION" == "3.8" ]; then python3 -m 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
# PyQt5 doesn't support PyPy3
# Wheel doesn't yet support 3.10
if [[ $GHA_PYTHON_VERSION == 3.* && $GHA_PYTHON_VERSION != "3.10-dev" ]]; then
# arm64, ppc64le, s390x CPUs:
# "ERROR: Could not find a version that satisfies the requirement pyqt5"
sudo apt-get -qq install libxcb-xinerama0 pyqt5-dev-tools
python3 -m pip install pyqt5
fi
# webp
pushd depends && ./install_webp.sh && popd
# libimagequant
pushd depends && ./install_imagequant.sh && popd
# raqm
pushd depends && ./install_raqm.sh && popd
# extra test images
pushd depends && ./install_extra_test_images.sh && popd

7
.ci/test.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
set -e
python3 -c "from PIL import Image"
python3 -bb -m pytest -v -x -W always --cov PIL --cov Tests --cov-report term Tests

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

@ -1,9 +0,0 @@
# Documentation: https://docs.codecov.io/docs/codecov-yaml
codecov:
# Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]"
# https://github.com/codecov/support/issues/363
# https://docs.codecov.io/v4.3.6/docs/comparing-commits
allow_coverage_offsets: true
comment: off

View File

@ -10,5 +10,11 @@ exclude_lines =
if 0: if 0:
if __name__ == .__main__.: if __name__ == .__main__.:
# Don't complain about debug code # Don't complain about debug code
if Image.DEBUG:
if DEBUG: if DEBUG:
[run]
omit =
Tests/32bit_segfault_check.py
Tests/bench_cffi_access.py
Tests/check_*.py
Tests/createfontdatachunk.py

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 on Python 2.7 and 3.x. 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 [Coveralls](https://coveralls.io/repos/new) 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,8 @@ 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.
- Include [release notes](https://github.com/python-pillow/Pillow/tree/master/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests.
## Reporting Issues ## Reporting Issues
@ -34,6 +35,4 @@ The best reproductions are self-contained scripts with minimal dependencies. If
## Security vulnerabilities ## Security vulnerabilities
To report sensitive vulnerability information, email security@python-pillow.org. Please see our [security policy](https://github.com/python-pillow/Pillow/blob/master/.github/SECURITY.md).
If your organisation/employer is a distributor of Pillow and would like advance notification of security-related bugs, please let us know your preferred contact method.

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
tidelift: "pypi/Pillow"

View File

@ -1,19 +0,0 @@
### What did you do?
### What did you expect to happen?
### What actually happened?
### What are your OS, Python and Pillow versions?
* OS:
* Python:
* Pillow:
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as plone, Django, or buildout, try to replicate the issue just using Pillow.
```python
code goes here
```

59
.github/ISSUE_TEMPLATE/ISSUE_REPORT.md vendored Normal file
View File

@ -0,0 +1,59 @@
---
name: Issue report
about: Create a report to help us improve Pillow
---
<!--
Thank you for reporting an issue.
Follow these guidelines to ensure your issue is handled properly.
If you have a ...
1. General question: consider asking the question on Stack Overflow
with the python-imaging-library tag:
* https://stackoverflow.com/questions/tagged/python-imaging-library
Do not ask a question in both places.
If you think you have found a bug or have an unexplained exception
then file a bug report here.
2. Bug report: include a self-contained, copy-pastable example that
generates the issue if possible. Be concise with code posted.
Guidelines on how to provide a good bug report:
* https://stackoverflow.com/help/mcve
Bug reports which follow these guidelines are easier to diagnose,
and are often handled much more quickly.
3. Feature request: do a quick search of existing issues
to make sure this has not been asked before.
We know asking good questions takes effort, and we appreciate your time.
Thank you.
-->
### What did you do?
### What did you expect to happen?
### What actually happened?
### What are your OS, Python and Pillow versions?
* OS:
* Python:
* Pillow:
<!--
Please include **code** that reproduces the issue and whenever possible, an **image** that demonstrates the issue. Please upload images to GitHub, not to third-party file hosting sites. If necessary, add the image to a zip or tar archive.
The best reproductions are self-contained scripts with minimal dependencies. If you are using a framework such as Plone, Django, or Buildout, try to replicate the issue just using Pillow.
-->
```python
code goes here
```

5
.github/SECURITY.md vendored Normal file
View File

@ -0,0 +1,5 @@
# Security policy
To report sensitive vulnerability information, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
If your organisation/employer is a distributor of Pillow and would like advance notification of security-related bugs, please let us know your preferred contact method.

13
.github/mergify.yml vendored Normal file
View File

@ -0,0 +1,13 @@
pull_request_rules:
- name: Automatic merge
conditions:
- "#approved-reviews-by>=1"
- label=automerge
- status-success=Lint
- status-success=Test Successful
- status-success=Docker Test Successful
- status-success=Windows Test Successful
- status-success=continuous-integration/appveyor/pr
actions:
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

47
.github/workflows/cifuzz.yml vendored Normal file
View File

@ -0,0 +1,47 @@
name: CIFuzz
on:
push:
paths:
- "**.c"
- "**.h"
pull_request:
paths:
- "**.c"
- "**.h"
jobs:
Fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build Fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'pillow'
language: python
dry-run: false
- name: Run Fuzzers
id: run
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'pillow'
fuzz-seconds: 600
language: python
dry-run: false
- name: Upload New Crash
uses: actions/upload-artifact@v2
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
- name: Upload Legacy Crash
uses: actions/upload-artifact@v2
if: steps.run.outcome == 'success'
with:
name: crash
path: ./out/crash*
- name: Fail on legacy crash
if: success()
run: |
[ ! -e out/crash-* ]
echo No legacy crash detected

48
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,48 @@
name: Lint
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
name: Lint
steps:
- uses: actions/checkout@v2
- name: pip cache
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: lint-pip-${{ hashFiles('**/setup.py') }}
restore-keys: |
lint-pip-
- name: pre-commit cache
uses: actions/cache@v2
with:
path: ~/.cache/pre-commit
key: lint-pre-commit-${{ hashFiles('**/.pre-commit-config.yaml') }}
restore-keys: |
lint-pre-commit-
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Install dependencies
run: |
python3 -m pip install -U pip
python3 -m pip install -U tox
- name: Lint
run: tox -e lint
env:
PRE_COMMIT_COLOR: always

25
.github/workflows/macos-install.sh vendored Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
set -e
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype openblas libraqm
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 -U pytest-timeout
python3 -m pip install pyroma
python3 -m pip install test-image-results
echo -e "[openblas]\nlibraries = openblas\nlibrary_dirs = /usr/local/opt/openblas/lib" >> ~/.numpy-site.cfg
# 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+:
if [ "$GHA_PYTHON_VERSION" == "3.8" ]; then python3 -m 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
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 }}

25
.github/workflows/system-info.py vendored Normal file
View File

@ -0,0 +1,25 @@
"""
Print out some handy system info like Travis CI does.
This sort of info is missing from GitHub Actions.
Requested here:
https://github.com/actions/virtual-environments/issues/79
"""
import os
import platform
import sys
print("Build system information")
print()
print("sys.version\t\t", sys.version.split("\n"))
print("os.name\t\t\t", os.name)
print("sys.platform\t\t", sys.platform)
print("platform.system()\t", platform.system())
print("platform.machine()\t", platform.machine())
print("platform.platform()\t", platform.platform())
print("platform.version()\t", platform.version())
print("platform.uname()\t", platform.uname())
if sys.platform == "darwin":
print("platform.mac_ver()\t", platform.mac_ver())

86
.github/workflows/test-docker.yml vendored Normal file
View File

@ -0,0 +1,86 @@
name: Test Docker
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
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,
amazon-2-amd64,
arch,
centos-7-amd64,
centos-8-amd64,
debian-10-buster-x86,
fedora-32-amd64,
fedora-33-amd64,
ubuntu-18.04-bionic-amd64,
ubuntu-20.04-focal-amd64,
]
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 }}
steps:
- uses: actions/checkout@v2
- name: Build system information
run: python3 .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
run: |
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
- name: Docker build
run: |
# The Pillow user in the docker container is UID 1000
sudo chown -R 1000 $GITHUB_WORKSPACE
docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
sudo chown -R runner $GITHUB_WORKSPACE
- name: After success
run: |
PATH="$PATH:~/.local/bin"
docker start pillow_container
pil_path=`docker exec pillow_container /vpy3/bin/python -c 'import os, PIL;print(os.path.realpath(os.path.dirname(PIL.__file__)))'`
docker stop pillow_container
sudo mkdir -p $pil_path
sudo cp src/PIL/*.py $pil_path
.ci/after_success.sh
env:
MATRIX_DOCKER: ${{ matrix.docker }}
- name: Upload coverage
uses: codecov/codecov-action@v1
with:
flags: GHA_Docker
name: ${{ matrix.docker }}
success:
needs: build
runs-on: ubuntu-latest
name: Docker Test Successful
steps:
- name: Success
run: echo Docker Test Successful

52
.github/workflows/test-valgrind.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: Test Valgrind
# like the docker tests, but running valgrind only on *.c/*.h changes.
on:
push:
paths:
- "**.c"
- "**.h"
pull_request:
paths:
- "**.c"
- "**.h"
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
docker: [
ubuntu-20.04-focal-amd64-valgrind,
]
dockerTag: [master]
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v2
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Docker pull
run: |
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
- name: Build and Run Valgrind
run: |
# The Pillow user in the docker container is UID 1000
sudo chown -R 1000 $GITHUB_WORKSPACE
docker run --name pillow_container -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
sudo chown -R runner $GITHUB_WORKSPACE
success:
needs: build
runs-on: ubuntu-latest
name: Valgrind Test Successful
steps:
- name: Success
run: echo Valgrind Test Successful

287
.github/workflows/test-windows.yml vendored Normal file
View File

@ -0,0 +1,287 @@
name: Test Windows
on: [push, pull_request]
jobs:
build:
runs-on: windows-2019
strategy:
fail-fast: false
matrix:
python-version: ["pypy-3.6", "pypy-3.7", "3.6", "3.7", "3.8", "3.9", "3.10-dev"]
architecture: ["x86", "x64"]
include:
- architecture: "x86"
platform-vcvars: "x86"
platform-msbuild: "Win32"
- architecture: "x64"
platform-vcvars: "x86_amd64"
platform-msbuild: "x64"
exclude:
# PyPy does not support 64-bit on Windows
- python-version: "pypy-3.6"
architecture: "x64"
- python-version: "pypy-3.7"
architecture: "x64"
timeout-minutes: 30
name: Python ${{ matrix.python-version }} ${{ matrix.architecture }}
steps:
- name: Checkout Pillow
uses: actions/checkout@v2
- name: Checkout cached dependencies
uses: actions/checkout@v2
with:
repository: python-pillow/pillow-depends
path: winbuild\depends
- name: Cache pip
uses: actions/cache@v2
with:
path: ~\AppData\Local\pip\Cache
key:
${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.architecture }}-${{ hashFiles('**/.github/workflows/test-windows.yml') }}
restore-keys: |
${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.architecture }}-
${{ runner.os }}-${{ matrix.python-version }}-
# sets env: pythonLocation
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }}
- name: Print build system information
run: python .github/workflows/system-info.py
- name: python -m pip install wheel pytest pytest-cov pytest-timeout
run: python -m pip install wheel pytest pytest-cov pytest-timeout
# TODO Remove when 3.8 / 3.9 includes setuptools 49.3.2+:
- name: Upgrade setuptools
if: "contains(matrix.python-version, '3.8') || contains(matrix.python-version, '3.9')"
run: python -m pip install -U "setuptools>=49.3.2"
- name: Install dependencies
id: install
run: |
7z x winbuild\depends\nasm-2.15.05-win64.zip "-o$env:RUNNER_WORKSPACE\"
echo "$env:RUNNER_WORKSPACE\nasm-2.15.05" >> $env:GITHUB_PATH
winbuild\depends\gs9540w32.exe /S
echo "C:\Program Files (x86)\gs\gs9.54.0\bin" >> $env:GITHUB_PATH
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
- name: Cache build
id: build-cache
uses: actions/cache@v2
with:
path: winbuild\build
key:
${{ hashFiles('winbuild\build_prepare.py') }}-${{ hashFiles('.github\workflows\test-windows.yml') }}-${{ env.pythonLocation }}-${{ steps.install.outputs.vs }}
- name: Prepare build
if: steps.build-cache.outputs.cache-hit != 'true'
run: |
& python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation --srcdir
shell: pwsh
- name: Build dependencies / libjpeg-turbo
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libjpeg.cmd"
- name: Build dependencies / zlib
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_zlib.cmd"
- name: Build dependencies / LibTiff
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libtiff.cmd"
- name: Build dependencies / WebP
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libwebp.cmd"
# for FreeType CBDT/SBIX font support
- name: Build dependencies / libpng
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libpng.cmd"
- name: Build dependencies / FreeType
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_freetype.cmd"
- name: Build dependencies / LCMS2
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_lcms2.cmd"
- name: Build dependencies / OpenJPEG
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_openjpeg.cmd"
# GPL licensed
- name: Build dependencies / libimagequant
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_libimagequant.cmd"
# Raqm dependencies
- name: Build dependencies / HarfBuzz
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_harfbuzz.cmd"
# Raqm dependencies
- name: Build dependencies / FriBidi
if: steps.build-cache.outputs.cache-hit != 'true'
run: "& winbuild\\build\\build_dep_fribidi.cmd"
# trim ~150MB x 9
- name: Optimize build cache
if: steps.build-cache.outputs.cache-hit != 'true'
run: rmdir /S /Q winbuild\build\src
shell: cmd
- name: Build Pillow
run: |
$FLAGS=""
if ('${{ github.event_name }}' -eq 'push') { $FLAGS="--disable-imagequant" }
& winbuild\build\build_pillow.cmd $FLAGS install
& $env:pythonLocation\python.exe selftest.py --installed
shell: pwsh
# failing with PyPy3
- name: Enable heap verification
if: "!contains(matrix.python-version, 'pypy')"
run: "& 'C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86\\gflags.exe' /p /enable $env:pythonLocation\\python.exe"
- name: Test Pillow
run: |
path %GITHUB_WORKSPACE%\\winbuild\\build\\bin;%PATH%
python.exe -m pytest -vx -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests
shell: cmd
- name: Prepare to upload errors
if: failure()
run: |
mkdir -p Tests/errors
shell: bash
- name: Upload errors
uses: actions/upload-artifact@v2
if: failure()
with:
name: errors
path: Tests/errors
- name: After success
run: |
.ci/after_success.sh
shell: pwsh
- name: Upload coverage
uses: codecov/codecov-action@v1
with:
file: ./coverage.xml
flags: GHA_Windows
name: ${{ runner.os }} Python ${{ matrix.python-version }} ${{ matrix.architecture }}
- name: Build wheel
id: wheel
if: "github.event_name == 'push'"
run: |
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ::set-output name=dist::dist-%%a
winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel
shell: cmd
- uses: actions/upload-artifact@v2
if: "github.event_name == 'push'"
with:
name: ${{ steps.wheel.outputs.dist }}
path: dist\*.whl
msys:
runs-on: windows-2019
strategy:
fail-fast: false
matrix:
mingw: ["MINGW32", "MINGW64"]
include:
- mingw: "MINGW32"
name: "MSYS2 MinGW 32-bit"
package: "mingw-w64-i686"
- mingw: "MINGW64"
name: "MSYS2 MinGW 64-bit"
package: "mingw-w64-x86_64"
defaults:
run:
shell: bash.exe --login -eo pipefail "{0}"
env:
MSYSTEM: ${{ matrix.mingw }}
CHERE_INVOKING: 1
timeout-minutes: 30
name: ${{ matrix.name }}
steps:
- uses: actions/checkout@v2
- name: Set up shell
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
shell: pwsh
- name: Install Dependencies
run: |
pacman -S --noconfirm \
${{ matrix.package }}-python3-cffi \
${{ matrix.package }}-python3-numpy \
${{ matrix.package }}-python3-olefile \
${{ matrix.package }}-python3-pip \
${{ matrix.package }}-python3-pyqt5 \
${{ matrix.package }}-python3-setuptools \
${{ matrix.package }}-freetype \
${{ matrix.package }}-ghostscript \
${{ matrix.package }}-lcms2 \
${{ matrix.package }}-libimagequant \
${{ matrix.package }}-libjpeg-turbo \
${{ matrix.package }}-libraqm \
${{ matrix.package }}-libtiff \
${{ matrix.package }}-libwebp \
${{ matrix.package }}-openjpeg2 \
subversion
python3 -m pip install pyroma pytest pytest-cov
pushd depends && ./install_extra_test_images.sh && popd
- name: Build Pillow
run: CFLAGS="-coverage" python3 setup.py build_ext install
- name: Test Pillow
run: |
python3 selftest.py --installed
python3 -c "from PIL import Image"
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
- name: Upload coverage
run: |
python3 -m pip install codecov
bash <(curl -s https://codecov.io/bash) -F GHA_Windows
env:
CODECOV_NAME: ${{ matrix.name }}
success:
needs: [build, msys]
runs-on: ubuntu-latest
name: Windows Test Successful
steps:
- name: Success
run: echo Windows Test Successful

124
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,124 @@
name: Test
on: [push, pull_request]
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [
"ubuntu-latest",
"macOS-latest",
]
python-version: [
"pypy-3.7",
"pypy-3.6",
"3.10-dev",
"3.9",
"3.8",
"3.7",
"3.6",
]
include:
- python-version: "3.6"
PYTHONOPTIMIZE: 1
- python-version: "3.7"
PYTHONOPTIMIZE: 2
# Include new variables for Codecov
- os: ubuntu-latest
codecov-flag: GHA_Ubuntu
- os: macOS-latest
codecov-flag: GHA_macOS
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Get pip cache dir
id: pip-cache
run: |
echo "::set-output name=dir::$(python3 -m pip cache dir)"
- name: pip cache
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.dir }}
key:
${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.ci/*.sh') }}
restore-keys: |
${{ matrix.os }}-${{ matrix.python-version }}-
- name: Build system information
run: python3 .github/workflows/system-info.py
- name: Install Linux dependencies
if: startsWith(matrix.os, 'ubuntu')
run: |
.ci/install.sh
env:
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
- name: Install macOS dependencies
if: startsWith(matrix.os, 'macOS')
run: |
.github/workflows/macos-install.sh
env:
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
- name: Build
run: |
.ci/build.sh
- name: Test
run: |
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
if: failure()
run: |
mkdir -p Tests/errors
- name: Upload errors
uses: actions/upload-artifact@v2
if: failure()
with:
name: errors
path: Tests/errors
- name: Docs
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.9
run: |
python3 -m pip install sphinx-issues sphinx-removed-in sphinx-rtd-theme
make doccheck
- name: After success
run: |
.ci/after_success.sh
- name: Upload coverage
run: bash <(curl -s https://codecov.io/bash) -F ${{ matrix.codecov-flag }}
env:
CODECOV_NAME: ${{ matrix.os }} Python ${{ matrix.python-version }}
success:
needs: build
runs-on: ubuntu-latest
name: Test Successful
steps:
- name: Success
run: echo Test Successful

10
.gitignore vendored
View File

@ -67,6 +67,9 @@ docs/_build/
\#*# \#*#
.#* .#*
#VS Code
.vscode
#Komodo #Komodo
*.komodoproject *.komodoproject
@ -78,6 +81,13 @@ 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
# pyinstaller
*.spec

View File

@ -1,3 +0,0 @@
strictness: medium
test-warnings: yes
max-line-length: 80

43
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,43 @@
repos:
- repo: https://github.com/psf/black
rev: e66be67b9b6811913470f70c28b4d50f94d05b22 # frozen: 20.8b1
hooks:
- id: black
args: ["--target-version", "py36"]
# Only .py files, until https://github.com/psf/black/issues/402 resolved
files: \.py$
types: []
- repo: https://github.com/PyCQA/isort
rev: 377d260ffa6f746693f97b46d95025afc4bd8275 # frozen: 5.4.2
hooks:
- id: isort
- repo: https://github.com/asottile/yesqa
rev: 7a009f3ee493c796827ee334f9058b110a0e0db8 # frozen: v1.2.1
hooks:
- id: yesqa
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: f30f4974a08a6b2f6a1eeaf30a4d501cf909163a # frozen: v1.1.9
hooks:
- id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
- repo: https://gitlab.com/pycqa/flake8
rev: 05f6544aef321e2fee03a1277ce2eef8880fb927 # frozen: 3.8.3
hooks:
- id: flake8
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: eae6397e4c259ed3d057511f6dd5330b92867e62 # frozen: v1.6.0
hooks:
- id: python-check-blanket-noqa
- id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: e1668fe86af3810fbca72b8653fe478e66a0afdc # frozen: v3.2.0
hooks:
- id: check-merge-conflict
- id: check-yaml

View File

@ -1,102 +0,0 @@
dist: xenial
language: python
cache: pip
notifications:
irc: "chat.freenode.net#pil"
# Run fast lint first to get fast feedback.
# Run slow PyPy* next, to give them a headstart and reduce waiting time.
# Run latest 3.x and 2.x next, to get quick compatibility results.
# Then run the remainder, with fastest Docker jobs last.
matrix:
fast_finish: true
include:
- python: "3.6"
name: "Lint"
env: LINT="true"
- python: "pypy2.7-6.0"
name: "PyPy2 Xenial"
dist: xenial
- python: "pypy3.5-6.0"
name: "PyPy3 Xenial"
dist: xenial
- python: '3.7'
name: "3.7 Xenial"
- python: '2.7'
name: "2.7 Xenial"
- python: '2.7'
name: "2.7 Trusty"
dist: trusty
- python: "2.7_with_system_site_packages" # For PyQt4
name: "2.7_with_system_site_packages Xenial"
services: xvfb
- python: "2.7_with_system_site_packages" # For PyQt4
name: "2.7_with_system_site_packages Trusty"
dist: trusty
- python: '3.6'
name: "3.6 Xenial"
- python: '3.6'
name: "3.6 Trusty PYTHONOPTIMIZE=1"
dist: trusty
env: PYTHONOPTIMIZE=1
- python: '3.5'
name: "3.5 Xenial"
- python: '3.5'
name: "3.5 Trusty PYTHONOPTIMIZE=2"
dist: trusty
env: PYTHONOPTIMIZE=2
- python: "3.8-dev"
name: "3.8-dev Xenial"
- env: DOCKER="alpine" DOCKER_TAG="master"
- env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5
- env: DOCKER="ubuntu-trusty-x86" DOCKER_TAG="master"
- env: DOCKER="ubuntu-xenial-amd64" DOCKER_TAG="master"
- env: DOCKER="debian-stretch-x86" DOCKER_TAG="master"
- env: DOCKER="centos-6-amd64" DOCKER_TAG="master"
- env: DOCKER="centos-7-amd64" DOCKER_TAG="master"
- env: DOCKER="amazon-1-amd64" DOCKER_TAG="master"
- env: DOCKER="amazon-2-amd64" DOCKER_TAG="master"
- env: DOCKER="fedora-28-amd64" DOCKER_TAG="master"
- env: DOCKER="fedora-29-amd64" DOCKER_TAG="master"
services:
- docker
before_install:
- if [ "$DOCKER" ]; then travis_retry docker pull pythonpillow/$DOCKER:$DOCKER_TAG; fi
install:
- |
if [ "$LINT" == "true" ]; then
pip install tox
elif [ "$DOCKER" == "" ]; then
.travis/install.sh;
fi
before_script:
# Qt needs a display for some of the tests, and it's only run on the system site packages install
- |
if [ "$TRAVIS_JOB_NAME" == "2.7_with_system_site_packages Trusty" ]; then
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
fi
script:
- |
if [ "$LINT" == "true" ]; then
tox -e lint
elif [ "$DOCKER" == "" ]; then
.travis/script.sh
elif [ "$DOCKER" ]; then
# the Pillow user in the docker container is UID 1000
sudo chown -R 1000 $TRAVIS_BUILD_DIR
docker run -v $TRAVIS_BUILD_DIR:/Pillow pythonpillow/$DOCKER:$DOCKER_TAG
fi
after_success:
- |
if [ "$LINT" == "" ]; then
.travis/after_success.sh
fi

View File

@ -1,23 +0,0 @@
#!/bin/bash
# gather the coverage data
sudo apt-get -qq install lcov
lcov --capture --directory . -b . --output-file coverage.info
# filter to remove system headers
lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
# convert to json
gem install coveralls-lcov
coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
coverage report
pip install codecov
pip install coveralls-merge
coveralls-merge coverage.c.json
codecov
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ] && [ "$DOCKER" == "" ]; then
# Coverage and quality reports on just the latest diff.
# (Installation is very slow on Py3, so just do it for Py2.)
depends/diffcover-install.sh
depends/diffcover-run.sh
fi

View File

@ -1,37 +0,0 @@
#!/bin/bash
set -e
sudo apt-get update
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk\
python-qt4 ghostscript libffi-dev libjpeg-turbo-progs cmake imagemagick\
libharfbuzz-dev libfribidi-dev
PYTHONOPTIMIZE=0 pip install cffi
pip install check-manifest
pip install coverage
pip install olefile
pip install -U pytest
pip install -U pytest-cov
pip install pyroma
pip install test-image-results
pip install numpy
# docs only on Python 2.7
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi
# clean checkout for manifest
mkdir /tmp/check-manifest && cp -a . /tmp/check-manifest
# webp
pushd depends && ./install_webp.sh && popd
# openjpeg
pushd depends && ./install_openjpeg.sh && popd
# libimagequant
pushd depends && ./install_imagequant.sh && popd
# extra test images
pushd depends && ./install_extra_test_images.sh && popd

View File

@ -1,15 +0,0 @@
#!/bin/bash
set -e
coverage erase
make clean
make install-coverage
python selftest.py
python -m pytest -vx --cov PIL --cov-report term Tests
pushd /tmp/check-manifest && check-manifest --ignore ".coveragerc,.editorconfig,*.yml,*.yaml,tox.ini" && popd
# Docs
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then make doccheck; fi

File diff suppressed because it is too large Load Diff

24
LICENSE
View File

@ -5,12 +5,26 @@ The Python Imaging Library (PIL) is
Pillow is the friendly PIL fork. It is Pillow is the friendly PIL fork. It is
Copyright © 2010-2019 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 documentation, you agree that you have read, understood, and will comply with the following terms and conditions: By obtaining, using, and/or copying this software and/or its associated
documentation, you agree that you have read, understood, and will comply
with the following terms and conditions:
Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. Permission to use, copy, modify, and distribute this software and its
associated documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appears in all copies, and that
both that copyright notice and this permission notice appear in supporting
documentation, and that the name of Secret Labs AB or the author not be
used in advertising or publicity pertaining to distribution of the software
without specific, written prior permission.
SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL,
INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

View File

@ -1,4 +1,3 @@
include *.c include *.c
include *.h include *.h
include *.in include *.in
@ -7,26 +6,24 @@ include *.py
include *.rst include *.rst
include *.sh include *.sh
include *.txt include *.txt
include *.yaml
include LICENSE include LICENSE
include Makefile include Makefile
include tox.ini
graft Tests graft Tests
graft src graft src
graft depends graft depends
graft winbuild graft winbuild
graft docs graft docs
prune docs/_static
# build/src control detritus # build/src control detritus
exclude .appveyor.yml exclude .appveyor.yml
exclude .clang-format
exclude .coveragerc exclude .coveragerc
exclude .codecov.yml
exclude .editorconfig exclude .editorconfig
exclude .landscape.yaml
exclude .readthedocs.yml exclude .readthedocs.yml
exclude azure-pipelines.yml exclude codecov.yml
exclude tox.ini
global-exclude .git* global-exclude .git*
global-exclude *.pyc global-exclude *.pyc
global-exclude *.so global-exclude *.so
prune .azure-pipelines prune .ci
prune .travis

View File

@ -1,37 +1,34 @@
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html .DEFAULT_GOAL := help
.PHONY: clean coverage doc docserve help inplace install install-req release-test sdist test upload upload-test
.DEFAULT_GOAL := release-test
.PHONY: clean
clean: clean:
python setup.py clean python3 setup.py clean
rm src/PIL/*.so || true rm src/PIL/*.so || true
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: coverage
co:
-for i in $(BRANCHES) ; do \
git checkout -t $$i ; \
done
coverage: coverage:
python selftest.py pytest -qq
python setup.py test
rm -r htmlcov || true rm -r htmlcov || true
coverage report coverage report
.PHONY: doc
doc: doc:
$(MAKE) -C docs html $(MAKE) -C docs html
.PHONY: doccheck
doccheck: doccheck:
$(MAKE) -C docs html $(MAKE) -C docs html
# Don't make our tests rely on the links in the docs being up every single build. # Don't make our tests rely on the links in the docs being up every single build.
# We don't control them. But do check, and update them to the target of their redirects. # We don't control them. But do check, and update them to the target of their redirects.
$(MAKE) -C docs linkcheck || true $(MAKE) -C docs linkcheck || true
.PHONY: docserve
docserve: docserve:
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null& cd docs/_build/html && python3 -m http.server 2> /dev/null&
.PHONY: help
help: help:
@echo "Welcome to Pillow development. Please use \`make <target>\` where <target> is one of" @echo "Welcome to Pillow development. Please use \`make <target>\` where <target> is one of"
@echo " clean remove build products" @echo " clean remove build products"
@ -43,64 +40,79 @@ 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"
@echo " upload-test build and upload sdists to test.pythonpackages.com" @echo " upload-test build and upload sdists to test.pythonpackages.com"
.PHONY: inplace
inplace: clean inplace: clean
python setup.py develop build_ext --inplace python3 setup.py develop build_ext --inplace
.PHONY: install
install: install:
python setup.py install python3 setup.py install
python selftest.py python3 selftest.py
.PHONY: install-coverage
install-coverage: install-coverage:
CFLAGS="-coverage" python setup.py build_ext install CFLAGS="-coverage -Werror=implicit-function-declaration" python3 setup.py build_ext install
python selftest.py python3 selftest.py
.PHONY: debug
debug: debug:
# make a debug version if we don't have a -dbg python. Leaves in symbols # make a debug version if we don't have a -dbg python. Leaves in symbols
# for our stuff, kills optimization, and redirects to dev null so we # for our stuff, kills optimization, and redirects to dev null so we
# see any build failures. # see any build failures.
make clean > /dev/null make clean > /dev/null
CFLAGS='-g -O0' python setup.py build_ext install > /dev/null CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null
.PHONY: install-req
install-req: install-req:
pip install -r requirements.txt python3 -m pip install -r requirements.txt
.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
.PHONY: release-test
release-test: release-test:
$(MAKE) install-req $(MAKE) install-req
python setup.py develop python3 setup.py develop
python selftest.py python3 selftest.py
python -m pytest Tests python3 -m pytest Tests
python setup.py install python3 setup.py install
python -m pytest -qq -rm dist/*.egg
-rmdir dist
python3 -m pytest -qq
check-manifest check-manifest
pyroma . pyroma .
viewdoc $(MAKE) readme
.PHONY: sdist
sdist: sdist:
python setup.py sdist --format=gztar python3 setup.py sdist --format=gztar
.PHONY: test
test: test:
pytest -qq pytest -qq
# https://docs.python.org/3/distutils/packageindex.html#the-pypirc-file .PHONY: readme
upload-test:
# [test]
# username:
# password:
# repository = http://test.pythonpackages.com
python setup.py sdist --format=gztar upload -r test
upload:
python setup.py sdist --format=gztar upload
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 .

102
README.md Normal file
View File

@ -0,0 +1,102 @@
<p align="center">
<img width="248" height="250" src="https://raw.githubusercontent.com/python-pillow/pillow-logo/master/pillow-logo-248x250.png" alt="Pillow logo">
</p>
# Pillow
## Python Imaging Library (Fork)
Pillow is the friendly PIL fork by [Alex Clark and
Contributors](https://github.com/python-pillow/Pillow/graphs/contributors).
PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
As of 2019, Pillow development is
[supported by Tidelift](https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=readme&utm_campaign=enterprise).
<table>
<tr>
<th>docs</th>
<td>
<a href="https://pillow.readthedocs.io/?badge=latest"><img
alt="Documentation Status"
src="https://readthedocs.org/projects/pillow/badge/?version=latest"></a>
</td>
</tr>
<tr>
<th>tests</th>
<td>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3ALint"><img
alt="GitHub Actions build status (Lint)"
src="https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3ATest"><img
alt="GitHub Actions build status (Test Linux and macOS)"
src="https://github.com/python-pillow/Pillow/workflows/Test/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Windows%22"><img
alt="GitHub Actions build status (Test Windows)"
src="https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg"></a>
<a href="https://github.com/python-pillow/Pillow/actions?query=workflow%3A%22Test+Docker%22"><img
alt="GitHub Actions build status (Test Docker)"
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
alt="Code coverage"
src="https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg"></a>
</td>
</tr>
<tr>
<th>package</th>
<td>
<a href="https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow"><img
alt="Zenodo"
src="https://zenodo.org/badge/17549/python-pillow/Pillow.svg"></a>
<a href="https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=badge"><img
alt="Tidelift"
src="https://tidelift.com/badges/package/pypi/Pillow?style=flat"></a>
<a href="https://pypi.org/project/Pillow/"><img
alt="Newest PyPI version"
src="https://img.shields.io/pypi/v/pillow.svg"></a>
<a href="https://pypi.org/project/Pillow/"><img
alt="Number of PyPI downloads"
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
</td>
</tr>
<tr>
<th>social</th>
<td>
<a href="https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img
alt="Join the chat at https://gitter.im/python-pillow/Pillow"
src="https://badges.gitter.im/python-pillow/Pillow.svg"></a>
<a href="https://twitter.com/PythonPillow"><img
alt="Follow on https://twitter.com/PythonPillow"
src="https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg"></a>
</td>
</tr>
</table>
## Overview
The Python Imaging Library adds image processing capabilities to your Python interpreter.
This library provides extensive file format support, an efficient internal representation, and fairly powerful image processing capabilities.
The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool.
## More Information
- [Documentation](https://pillow.readthedocs.io/)
- [Installation](https://pillow.readthedocs.io/en/latest/installation.html)
- [Handbook](https://pillow.readthedocs.io/en/latest/handbook/index.html)
- [Contribute](https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md)
- [Issues](https://github.com/python-pillow/Pillow/issues)
- [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)
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#pre-fork)
## Report a Vulnerability
To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security).

View File

@ -1,84 +0,0 @@
Pillow
======
Python Imaging Library (Fork)
-----------------------------
Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.com/python-pillow/Pillow/graphs/contributors>`_. PIL is the Python Imaging Library by Fredrik Lundh and Contributors.
.. start-badges
.. list-table::
:stub-columns: 1
* - docs
- |docs|
* - tests
- |linux| |macos| |windows| |coverage|
* - package
- |zenodo| |tidelift| |version| |downloads|
* - social
- |gitter| |twitter|
.. |docs| image:: https://readthedocs.org/projects/pillow/badge/?version=latest
:target: https://pillow.readthedocs.io/?badge=latest
:alt: Documentation Status
.. |linux| 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)
.. |macos| 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)
.. |windows| image:: https://img.shields.io/appveyor/ci/python-pillow/Pillow/master.svg?label=Windows%20build
:target: https://ci.appveyor.com/project/python-pillow/Pillow
:alt: AppVeyor CI build status (Windows)
.. |coverage| image:: https://coveralls.io/repos/python-pillow/Pillow/badge.svg?branch=master&service=github
:target: https://coveralls.io/github/python-pillow/Pillow?branch=master
:alt: Code coverage
.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
.. |tidelift| image:: https://tidelift.com/badges/github/python-pillow/Pillow?style=flat
:target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=referral&utm_campaign=readme
.. |version| image:: https://img.shields.io/pypi/v/pillow.svg
:target: https://pypi.org/project/Pillow/
:alt: Latest PyPI version
.. |downloads| image:: https://img.shields.io/pypi/dm/pillow.svg
:target: https://pypi.org/project/Pillow/
:alt: Number of PyPI downloads
.. |gitter| image:: https://badges.gitter.im/python-pillow/Pillow.svg
:target: https://gitter.im/python-pillow/Pillow?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
:alt: Join the chat at https://gitter.im/python-pillow/Pillow
.. |twitter| image:: https://img.shields.io/badge/tweet-on%20Twitter-00aced.svg
:target: https://twitter.com/PythonPillow
:alt: Follow on https://twitter.com/PythonPillow
.. end-badges
More Information
----------------
- `Documentation <https://pillow.readthedocs.io/>`_
- `Installation <https://pillow.readthedocs.io/en/latest/installation.html>`_
- `Handbook <https://pillow.readthedocs.io/en/latest/handbook/index.html>`_
- `Contribute <https://github.com/python-pillow/Pillow/blob/master/.github/CONTRIBUTING.md>`_
- `Issues <https://github.com/python-pillow/Pillow/issues>`_
- `Pull requests <https://github.com/python-pillow/Pillow/pulls>`_
- `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>`_

View File

@ -1,13 +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 the first day of January, April, July, October. 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 [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `master` branch. * [ ] 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.
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI. * [ ] 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.
* [ ] 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.
@ -18,13 +21,18 @@ Released quarterly on the first day of January, April, July, October.
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
@ -37,21 +45,31 @@ 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 [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. `5.2.x`.
* [ ] 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`.
* [ ] 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.:
```bash ```bash
git tag 5.2.1 git tag 5.2.1
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)
* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new) * [ ] Check and upload all binaries and source distributions e.g.:
```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
@ -70,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):
@ -90,11 +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 distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/). * [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases)
```bash and copy into `dist/`
wget -m -A 'Pillow-<VERSION>*' \
http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com
```
## Publicize Release ## Publicize Release
@ -102,4 +118,13 @@ Released as needed privately to individual vendors for critical security-related
## Documentation ## Documentation
* [ ] Make sure the default version for Read the Docs is the latest tagged release e.g. `d2d43879` (5.4.0) * [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
## Docker Images
* [ ] Update Pillow in the Docker Images repository
```bash
git clone https://github.com/python-pillow/docker-images
cd docker-images
./update-pillow-tag.sh [[release tag]]
```

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
from PIL import Image
import sys import sys
from PIL import Image
if sys.maxsize < 2 ** 32: if sys.maxsize < 2 ** 32:
im = Image.new('L', (999999, 999999), 0) im = Image.new("L", (999999, 999999), 0)

View File

@ -1,14 +1,14 @@
Pillow Tests Pillow Tests
============ ============
Test scripts are named ``test_xxx.py`` and use the ``unittest`` module. A base class and helper functions can be found in ``helper.py``. Test scripts are named ``test_xxx.py``. Helper classes and functions can be found in ``helper.py``.
Dependencies Dependencies
----------- ------------
Install:: Install::
pip install pytest pytest-cov python3 -m pip install pytest pytest-cov
Execution Execution
--------- ---------
@ -27,6 +27,6 @@ Run all the tests from the root of the Pillow source distribution::
Or with coverage:: Or with coverage::
pytest --cov PIL --cov-report term pytest --cov PIL --cov Tests --cov-report term
coverage html coverage html
open htmlcov/index.html open htmlcov/index.html

View File

@ -1,10 +1,10 @@
from .helper import unittest, PillowTestCase, hopper import time
# Not running this test by default. No DOS against Travis CI.
from PIL import PyAccess from PIL import PyAccess
import time from .helper import hopper
# Not running this test by default. No DOS against CI.
def iterate_get(size, access): def iterate_get(size, access):
@ -27,32 +27,32 @@ def timer(func, label, *args):
for x in range(iterations): for x in range(iterations):
func(*args) func(*args)
if time.time() - starttime > 10: if time.time() - starttime > 10:
print("%s: breaking at %s iterations, %.6f per iteration" % ( print(
label, x+1, (time.time()-starttime)/(x+1.0))) "{}: breaking at {} iterations, {:.6f} per iteration".format(
label, x + 1, (time.time() - starttime) / (x + 1.0)
)
)
break break
if x == iterations - 1: if x == iterations - 1:
endtime = time.time() endtime = time.time()
print("%s: %.4f s %.6f per iteration" % ( print(
label, endtime-starttime, (endtime-starttime)/(x+1.0))) "{}: {:.4f} s {:.6f} per iteration".format(
label, endtime - starttime, (endtime - starttime) / (x + 1.0)
)
)
class BenchCffiAccess(PillowTestCase): def test_direct():
def test_direct(self):
im = hopper() im = hopper()
im.load() im.load()
# im = Image.new( "RGB", (2000, 2000), (1, 3, 2)) # im = Image.new( "RGB", (2000, 2000), (1, 3, 2))
caccess = im.im.pixel_access(False) caccess = im.im.pixel_access(False)
access = PyAccess.new(im, False) access = PyAccess.new(im, False)
self.assertEqual(caccess[(0, 0)], access[(0, 0)]) assert caccess[(0, 0)] == access[(0, 0)]
print("Size: %sx%s" % im.size) print("Size: %sx%s" % im.size)
timer(iterate_get, 'PyAccess - get', im.size, access) timer(iterate_get, "PyAccess - get", im.size, access)
timer(iterate_set, 'PyAccess - set', im.size, access) timer(iterate_set, "PyAccess - set", im.size, access)
timer(iterate_get, 'C-api - get', im.size, caccess) timer(iterate_get, "C-api - get", im.size, caccess)
timer(iterate_set, 'C-api - set', im.size, caccess) timer(iterate_set, "C-api - set", im.size, caccess)
if __name__ == '__main__':
unittest.main()

View File

@ -1,22 +0,0 @@
from . import helper
import timeit
import sys
sys.path.insert(0, ".")
def bench(mode):
im = helper.hopper(mode)
get = im.im.getpixel
xy = 50, 50 # position shouldn't really matter
t0 = timeit.default_timer()
for _ in range(1000000):
get(xy)
print(mode, timeit.default_timer() - t0, "us")
bench("L")
bench("I")
bench("I;16")
bench("F")
bench("RGB")

68
Tests/check_fli_oob.py Normal file
View File

@ -0,0 +1,68 @@
#!/usr/bin/env python
from PIL import Image
repro_ss2 = (
"images/fli_oob/06r/06r00.fli",
"images/fli_oob/06r/others/06r01.fli",
"images/fli_oob/06r/others/06r02.fli",
"images/fli_oob/06r/others/06r03.fli",
"images/fli_oob/06r/others/06r04.fli",
)
repro_lc = (
"images/fli_oob/05r/05r00.fli",
"images/fli_oob/05r/others/05r03.fli",
"images/fli_oob/05r/others/05r06.fli",
"images/fli_oob/05r/others/05r05.fli",
"images/fli_oob/05r/others/05r01.fli",
"images/fli_oob/05r/others/05r04.fli",
"images/fli_oob/05r/others/05r02.fli",
"images/fli_oob/05r/others/05r07.fli",
"images/fli_oob/patch0/000000",
"images/fli_oob/patch0/000001",
"images/fli_oob/patch0/000002",
"images/fli_oob/patch0/000003",
)
repro_advance = (
"images/fli_oob/03r/03r00.fli",
"images/fli_oob/03r/others/03r01.fli",
"images/fli_oob/03r/others/03r09.fli",
"images/fli_oob/03r/others/03r11.fli",
"images/fli_oob/03r/others/03r05.fli",
"images/fli_oob/03r/others/03r10.fli",
"images/fli_oob/03r/others/03r06.fli",
"images/fli_oob/03r/others/03r08.fli",
"images/fli_oob/03r/others/03r03.fli",
"images/fli_oob/03r/others/03r07.fli",
"images/fli_oob/03r/others/03r02.fli",
"images/fli_oob/03r/others/03r04.fli",
)
repro_brun = (
"images/fli_oob/04r/initial.fli",
"images/fli_oob/04r/others/04r02.fli",
"images/fli_oob/04r/others/04r05.fli",
"images/fli_oob/04r/others/04r04.fli",
"images/fli_oob/04r/others/04r03.fli",
"images/fli_oob/04r/others/04r01.fli",
"images/fli_oob/04r/04r00.fli",
)
repro_copy = (
"images/fli_oob/02r/others/02r02.fli",
"images/fli_oob/02r/others/02r04.fli",
"images/fli_oob/02r/others/02r03.fli",
"images/fli_oob/02r/others/02r01.fli",
"images/fli_oob/02r/02r00.fli",
)
for path in repro_ss2 + repro_lc + repro_advance + repro_brun + repro_copy:
im = Image.open(path)
try:
im.load()
except Exception as msg:
print(msg)

View File

@ -1,16 +1,10 @@
from .helper import unittest, PillowTestCase
from PIL import Image from PIL import Image
TEST_FILE = "Tests/images/fli_overflow.fli" TEST_FILE = "Tests/images/fli_overflow.fli"
class TestFliOverflow(PillowTestCase): def test_fli_overflow():
def test_fli_overflow(self):
# this should not crash with a malloc error or access violation # this should not crash with a malloc error or access violation
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
im.load() im.load()
if __name__ == '__main__':
unittest.main()

View File

@ -1,12 +1,9 @@
# Tests potential DOS of IcnsImagePlugin with 0 length block. # Tests potential DOS of IcnsImagePlugin with 0 length block.
# Run from anywhere that PIL is importable. # Run from anywhere that PIL is importable.
from PIL import Image
from PIL._util import py3
from io import BytesIO from io import BytesIO
if py3: from PIL import Image
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00',
'latin-1'))) with Image.open(BytesIO(b"icns\x00\x00\x00\x10hang\x00\x00\x00\x00")):
else: pass
Image.open(BytesIO(bytes('icns\x00\x00\x00\x10hang\x00\x00\x00\x00')))

View File

@ -1,44 +1,45 @@
#!/usr/bin/env python #!/usr/bin/env python
import pytest
from __future__ import division
from .helper import unittest, PillowTestCase
import sys
from PIL import Image from PIL import Image
from .helper import is_win32
min_iterations = 100 min_iterations = 100
max_iterations = 10000 max_iterations = 10000
pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS")
class TestImagingLeaks(PillowTestCase):
def _get_mem_usage(self): def _get_mem_usage():
from resource import getpagesize, getrusage, RUSAGE_SELF from resource import RUSAGE_SELF, getpagesize, getrusage
mem = getrusage(RUSAGE_SELF).ru_maxrss mem = getrusage(RUSAGE_SELF).ru_maxrss
return mem * getpagesize() / 1024 / 1024 return mem * getpagesize() / 1024 / 1024
def _test_leak(self, min_iterations, max_iterations, fn, *args, **kwargs):
def _test_leak(min_iterations, max_iterations, fn, *args, **kwargs):
mem_limit = None mem_limit = None
for i in range(max_iterations): for i in range(max_iterations):
fn(*args, **kwargs) fn(*args, **kwargs)
mem = self._get_mem_usage() mem = _get_mem_usage()
if i < min_iterations: if i < min_iterations:
mem_limit = mem + 1 mem_limit = mem + 1
continue continue
msg = 'memory usage limit exceeded after %d iterations' % (i + 1) msg = f"memory usage limit exceeded after {i + 1} iterations"
self.assertLessEqual(mem, mem_limit, msg) assert mem <= mem_limit, msg
def test_leak_putdata(self):
im = Image.new('RGB', (25, 25))
self._test_leak(min_iterations, max_iterations,
im.putdata, im.getdata())
def test_leak_getlist(self): def test_leak_putdata():
im = Image.new('P', (25, 25)) im = Image.new("RGB", (25, 25))
self._test_leak(min_iterations, max_iterations, _test_leak(min_iterations, max_iterations, im.putdata, im.getdata())
def test_leak_getlist():
im = Image.new("P", (25, 25))
_test_leak(
min_iterations,
max_iterations,
# Pass a new list at each iteration. # Pass a new list at each iteration.
lambda: im.point(range(256))) lambda: im.point(range(256)),
)
if __name__ == '__main__':
unittest.main()

View File

@ -1,15 +1,11 @@
# Tests potential DOS of Jpeg2kImagePlugin with 0 length block. # Tests potential DOS of Jpeg2kImagePlugin with 0 length block.
# Run from anywhere that PIL is importable. # Run from anywhere that PIL is importable.
from PIL import Image
from PIL._util import py3
from io import BytesIO from io import BytesIO
if py3: from PIL import Image
Image.open(BytesIO(bytes(
'\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang',
'latin-1')))
else: with Image.open(
Image.open(BytesIO(bytes( BytesIO(b"\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang")
'\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang'))) ):
pass

View File

@ -1,32 +1,36 @@
from .helper import unittest, PillowTestCase
import sys
from PIL import Image
from io import BytesIO from io import BytesIO
import pytest
from PIL import Image
from .helper import is_win32, skip_unless_feature
# Limits for testing the leak # Limits for testing the leak
mem_limit = 1024 * 1048576 mem_limit = 1024 * 1048576
stack_size = 8 * 1048576 stack_size = 8 * 1048576
iterations = int((mem_limit / stack_size) * 2) iterations = int((mem_limit / stack_size) * 2)
codecs = dir(Image.core)
test_file = "Tests/images/rgb_trns_ycbc.jp2" test_file = "Tests/images/rgb_trns_ycbc.jp2"
pytestmark = [
pytest.mark.skipif(is_win32(), reason="requires Unix or macOS"),
skip_unless_feature("jpg_2000"),
]
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS")
class TestJpegLeaks(PillowTestCase):
def setUp(self):
if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs:
self.skipTest('JPEG 2000 support not available')
def test_leak_load(self): def test_leak_load():
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
setrlimit(RLIMIT_STACK, (stack_size, stack_size)) setrlimit(RLIMIT_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
for _ in range(iterations): for _ in range(iterations):
with Image.open(test_file) as im: with Image.open(test_file) as im:
im.load() im.load()
def test_leak_save(self):
from resource import setrlimit, RLIMIT_AS, RLIMIT_STACK def test_leak_save():
from resource import RLIMIT_AS, RLIMIT_STACK, setrlimit
setrlimit(RLIMIT_STACK, (stack_size, stack_size)) setrlimit(RLIMIT_STACK, (stack_size, stack_size))
setrlimit(RLIMIT_AS, (mem_limit, mem_limit)) setrlimit(RLIMIT_AS, (mem_limit, mem_limit))
for _ in range(iterations): for _ in range(iterations):
@ -36,7 +40,3 @@ class TestJpegLeaks(PillowTestCase):
im.save(test_output, "JPEG2000") im.save(test_output, "JPEG2000")
test_output.seek(0) test_output.seek(0)
test_output.read() test_output.read()
if __name__ == '__main__':
unittest.main()

View File

@ -1,15 +1,10 @@
import pytest
from PIL import Image from PIL import Image
from .helper import unittest, PillowTestCase
class TestJ2kEncodeOverflow(PillowTestCase): def test_j2k_overflow(tmp_path):
def test_j2k_overflow(self): im = Image.new("RGBA", (1024, 131584))
target = str(tmp_path / "temp.jpc")
im = Image.new('RGBA', (1024, 131584)) with pytest.raises(OSError):
target = self.tempfile('temp.jpc')
with self.assertRaises(IOError):
im.save(target) im.save(target)
if __name__ == '__main__':
unittest.main()

26
Tests/check_jp2_overflow.py Executable file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env python
# Reproductions/tests for OOB read errors in FliDecode.c
# When run in python, all of these images should fail for
# one reason or another, either as a buffer overrun,
# unrecognized datastream, or truncated image file.
# There shouldn't be any segfaults.
#
# if run like
# `valgrind --tool=memcheck python check_jp2_overflow.py 2>&1 | grep Decode.c`
# the output should be empty. There may be python issues
# in the valgrind especially if run in a debug python
# version.
from PIL import Image
repro = ("00r0_gray_l.jp2", "00r1_graya_la.jp2")
for path in repro:
im = Image.open(path)
try:
im.load()
except Exception as msg:
print(msg)

View File

@ -1,6 +1,8 @@
from .helper import unittest, PillowTestCase, hopper
from io import BytesIO from io import BytesIO
import sys
import pytest
from .helper import hopper, is_win32
iterations = 5000 iterations = 5000
@ -14,8 +16,7 @@ valgrind --tool=massif python test-installed.py -s -v Tests/check_jpeg_leaks.py
""" """
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS") pytestmark = pytest.mark.skipif(is_win32(), reason="requires Unix or macOS")
class TestJpegLeaks(PillowTestCase):
""" """
pre patch: pre patch:
@ -73,10 +74,13 @@ post-patch:
""" """
def test_qtables_leak(self):
im = hopper('RGB')
standard_l_qtable = [int(s) for s in """ def test_qtables_leak():
im = hopper("RGB")
standard_l_qtable = [
int(s)
for s in """
16 11 10 16 24 40 51 61 16 11 10 16 24 40 51 61
12 12 14 19 26 58 60 55 12 12 14 19 26 58 60 55
14 13 16 24 40 57 69 56 14 13 16 24 40 57 69 56
@ -85,9 +89,14 @@ post-patch:
24 35 55 64 81 104 113 92 24 35 55 64 81 104 113 92
49 64 78 87 103 121 120 101 49 64 78 87 103 121 120 101
72 92 95 98 112 100 103 99 72 92 95 98 112 100 103 99
""".split(None)] """.split(
None
)
]
standard_chrominance_qtable = [int(s) for s in """ standard_chrominance_qtable = [
int(s)
for s in """
17 18 24 47 99 99 99 99 17 18 24 47 99 99 99 99
18 21 26 66 99 99 99 99 18 21 26 66 99 99 99 99
24 26 56 99 99 99 99 99 24 26 56 99 99 99 99 99
@ -96,16 +105,19 @@ post-patch:
99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99
99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99
""".split(None)] """.split(
None
)
]
qtables = [standard_l_qtable, qtables = [standard_l_qtable, standard_chrominance_qtable]
standard_chrominance_qtable]
for _ in range(iterations): for _ in range(iterations):
test_output = BytesIO() test_output = BytesIO()
im.save(test_output, "JPEG", qtables=qtables) im.save(test_output, "JPEG", qtables=qtables)
def test_exif_leak(self):
def test_exif_leak():
""" """
pre patch: pre patch:
@ -159,16 +171,16 @@ post patch:
| @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@:::::: | @ @@@# ::: ::::: : ::::::::::@::::@::::@::::@::::@::::@:::::::::@::::::
0 +----------------------------------------------------------------------->Gi 0 +----------------------------------------------------------------------->Gi
0 11.33 0 11.33
""" """
im = hopper('RGB') im = hopper("RGB")
exif = b'12345678'*4096 exif = b"12345678" * 4096
for _ in range(iterations): for _ in range(iterations):
test_output = BytesIO() test_output = BytesIO()
im.save(test_output, "JPEG", exif=exif) im.save(test_output, "JPEG", exif=exif)
def test_base_save(self):
def test_base_save():
""" """
base case: base case:
MB MB
@ -193,14 +205,9 @@ base case:
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
| :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@::: | :@ @@ @ # : : :: :: @:: :::: :::: :::: : : : : : : :::::::::::: :::@:::
0 +----------------------------------------------------------------------->Gi 0 +----------------------------------------------------------------------->Gi
0 7.882 0 7.882"""
""" im = hopper("RGB")
im = hopper('RGB')
for _ in range(iterations): for _ in range(iterations):
test_output = BytesIO() test_output = BytesIO()
im.save(test_output, "JPEG") im.save(test_output, "JPEG")
if __name__ == '__main__':
unittest.main()

View File

@ -1,6 +1,8 @@
import sys import sys
from .helper import unittest, PillowTestCase import pytest
from PIL import Image
# This test is not run automatically. # This test is not run automatically.
# #
@ -11,27 +13,36 @@ from .helper import unittest, PillowTestCase
# Raspberry Pis). It does succeed on a 3gb Ubuntu 12.04x64 VM on Python # Raspberry Pis). It does succeed on a 3gb Ubuntu 12.04x64 VM on Python
# 2.7 and 3.2. # 2.7 and 3.2.
from PIL import Image
try:
import numpy
except ImportError:
numpy = None
YDIM = 32769 YDIM = 32769
XDIM = 48000 XDIM = 48000
@unittest.skipIf(sys.maxsize <= 2**32, "requires 64-bit system") pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system")
class LargeMemoryTest(PillowTestCase):
def _write_png(self, xdim, ydim):
f = self.tempfile('temp.png') def _write_png(tmp_path, xdim, ydim):
im = Image.new('L', (xdim, ydim), 0) f = str(tmp_path / "temp.png")
im = Image.new("L", (xdim, ydim), 0)
im.save(f) im.save(f)
def test_large(self):
def test_large(tmp_path):
""" succeeded prepatch""" """ succeeded prepatch"""
self._write_png(XDIM, YDIM) _write_png(tmp_path, XDIM, YDIM)
def test_2gpx(self):
def test_2gpx(tmp_path):
"""failed prepatch""" """failed prepatch"""
self._write_png(XDIM, XDIM) _write_png(tmp_path, XDIM, XDIM)
if __name__ == '__main__': @pytest.mark.skipif(numpy is None, reason="Numpy is not installed")
unittest.main() def test_size_greater_than_int():
arr = numpy.ndarray(shape=(16394, 16394))
Image.fromarray(arr)

View File

@ -1,6 +1,8 @@
import sys import sys
from .helper import unittest, PillowTestCase import pytest
from PIL import Image
# This test is not run automatically. # This test is not run automatically.
# #
@ -10,34 +12,29 @@ from .helper import unittest, PillowTestCase
# on any 32-bit machine, as well as any smallish things (like # on any 32-bit machine, as well as any smallish things (like
# Raspberry Pis). # Raspberry Pis).
from PIL import Image
try: np = pytest.importorskip("numpy", reason="NumPy not installed")
import numpy as np
except ImportError:
raise unittest.SkipTest("numpy not installed")
YDIM = 32769 YDIM = 32769
XDIM = 48000 XDIM = 48000
@unittest.skipIf(sys.maxsize <= 2**32, "requires 64-bit system") pytestmark = pytest.mark.skipif(sys.maxsize <= 2 ** 32, reason="requires 64-bit system")
class LargeMemoryNumpyTest(PillowTestCase):
def _write_png(self, xdim, ydim):
def _write_png(tmp_path, xdim, ydim):
dtype = np.uint8 dtype = np.uint8
a = np.zeros((xdim, ydim), dtype=dtype) a = np.zeros((xdim, ydim), dtype=dtype)
f = self.tempfile('temp.png') f = str(tmp_path / "temp.png")
im = Image.fromarray(a, 'L') im = Image.fromarray(a, "L")
im.save(f) im.save(f)
def test_large(self):
def test_large(tmp_path):
""" succeeded prepatch""" """ succeeded prepatch"""
self._write_png(XDIM, YDIM) _write_png(tmp_path, XDIM, YDIM)
def test_2gpx(self):
def test_2gpx(tmp_path):
"""failed prepatch""" """failed prepatch"""
self._write_png(XDIM, XDIM) _write_png(tmp_path, XDIM, XDIM)
if __name__ == '__main__':
unittest.main()

View File

@ -1,19 +1,15 @@
from .helper import unittest, PillowTestCase import pytest
from PIL import Image from PIL import Image
TEST_FILE = "Tests/images/libtiff_segfault.tif" TEST_FILE = "Tests/images/libtiff_segfault.tif"
class TestLibtiffSegfault(PillowTestCase): def test_libtiff_segfault():
def test_segfault(self):
"""This test should not segfault. It will on Pillow <= 3.1.0 and """This test should not segfault. It will on Pillow <= 3.1.0 and
libtiff >= 4.0.0 libtiff >= 4.0.0
""" """
with self.assertRaises(IOError): with pytest.raises(OSError):
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
im.load() im.load()
if __name__ == '__main__':
unittest.main()

View File

@ -1,13 +1,12 @@
from .helper import unittest, PillowTestCase
from PIL import Image, PngImagePlugin, ImageFile
from io import BytesIO
import zlib import zlib
from io import BytesIO
from PIL import Image, ImageFile, PngImagePlugin
TEST_FILE = "Tests/images/png_decompression_dos.png" TEST_FILE = "Tests/images/png_decompression_dos.png"
class TestPngDos(PillowTestCase): def test_ignore_dos_text():
def test_ignore_dos_text(self):
ImageFile.LOAD_TRUNCATED_IMAGES = True ImageFile.LOAD_TRUNCATED_IMAGES = True
try: try:
@ -17,49 +16,46 @@ class TestPngDos(PillowTestCase):
ImageFile.LOAD_TRUNCATED_IMAGES = False ImageFile.LOAD_TRUNCATED_IMAGES = False
for s in im.text.values(): for s in im.text.values():
self.assertLess(len(s), 1024*1024, "Text chunk larger than 1M") assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
for s in im.info.values(): for s in im.info.values():
self.assertLess(len(s), 1024*1024, "Text chunk larger than 1M") assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
def test_dos_text(self):
def test_dos_text():
try: try:
im = Image.open(TEST_FILE) im = Image.open(TEST_FILE)
im.load() im.load()
except ValueError as msg: except ValueError as msg:
self.assertTrue(msg, "Decompressed Data Too Large") assert msg, "Decompressed Data Too Large"
return return
for s in im.text.values(): for s in im.text.values():
self.assertLess(len(s), 1024*1024, "Text chunk larger than 1M") assert len(s) < 1024 * 1024, "Text chunk larger than 1M"
def test_dos_total_memory(self):
im = Image.new('L', (1, 1)) def test_dos_total_memory():
compressed_data = zlib.compress(b'a'*1024*1023) im = Image.new("L", (1, 1))
compressed_data = zlib.compress(b"a" * 1024 * 1023)
info = PngImagePlugin.PngInfo() info = PngImagePlugin.PngInfo()
for x in range(64): for x in range(64):
info.add_text('t%s' % x, compressed_data, zip=True) info.add_text(f"t{x}", compressed_data, zip=True)
info.add_itxt('i%s' % x, compressed_data, zip=True) info.add_itxt(f"i{x}", compressed_data, zip=True)
b = BytesIO() b = BytesIO()
im.save(b, 'PNG', pnginfo=info) im.save(b, "PNG", pnginfo=info)
b.seek(0) b.seek(0)
try: try:
im2 = Image.open(b) im2 = Image.open(b)
except ValueError as msg: except ValueError as msg:
self.assertIn("Too much memory", msg) assert "Too much memory" in msg
return return
total_len = 0 total_len = 0
for txt in im2.text.values(): for txt in im2.text.values():
total_len += len(txt) total_len += len(txt)
self.assertLess(total_len, 64*1024*1024, assert total_len < 64 * 1024 * 1024, "Total text chunks greater than 64M"
"Total text chunks greater than 64M")
if __name__ == '__main__':
unittest.main()

26
Tests/conftest.py Normal file
View File

@ -0,0 +1,26 @@
import io
def pytest_report_header(config):
try:
from PIL import features
with io.StringIO() as out:
features.pilinfo(out=out, supported_formats=False)
return out.getvalue()
except Exception as e:
return f"pytest_report_header failed: {e}"
def pytest_configure(config):
# We're marking some tests to ignore valgrind errors and XFAIL them.
# Ensure that the mark is defined
# even in cases where pytest-valgrind isn't installed
try:
config.addinivalue_line(
"markers",
"valgrind_known_error: Tests that have known issues with valgrind",
)
except Exception:
# valgrind is already installed
pass

View File

@ -1,5 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import print_function
import base64 import base64
import os import os
@ -7,7 +6,7 @@ if __name__ == "__main__":
# create font data chunk for embedding # create font data chunk for embedding
font = "Tests/images/courB08" font = "Tests/images/courB08"
print(" f._load_pilfont_data(") print(" f._load_pilfont_data(")
print(" # %s" % os.path.basename(font)) print(f" # {os.path.basename(font)}")
print(" BytesIO(base64.decodestring(b'''") print(" BytesIO(base64.decodestring(b'''")
with open(font + ".pil", "rb") as fp: with open(font + ".pil", "rb") as fp:
print(base64.b64encode(fp.read()).decode()) print(base64.b64encode(fp.read()).decode())

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,40 @@
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.
DejaVu Fonts — License
Fonts are © Bitstream (see below). DejaVu changes are in public domain. Explanation of copyright is on Gnome page on Bitstream Vera fonts. Glyphs imported from Arev fonts are © Tavmjung Bah (see below)
Bitstream Vera Fonts Copyright
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera".
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names.
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.
Arev Fonts Copyright
Original text
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the modifications to the Bitstream Vera Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Tavmjong Bah" or the word "Arev".
This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Tavmjong Bah Arev" names.
The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.

Binary file not shown.

View File

@ -1,13 +1,26 @@
NotoNastaliqUrdu-Regular.ttf: NotoNastaliqUrdu-Regular.ttf and NotoSansSymbols-Regular.ttf, from https://github.com/googlei18n/noto-fonts
NotoSans-Regular.ttf, from https://www.google.com/get/noto/
NotoSansJP-Thin.otf, from https://www.google.com/get/noto/help/cjk/
NotoColorEmoji.ttf, from https://github.com/googlefonts/noto-emoji
AdobeVFPrototype.ttf, from https://github.com/adobe-fonts/adobe-variable-font-prototype
TINY5x3GX.ttf, from http://velvetyne.fr/fonts/tiny
ArefRuqaa-Regular.ttf, from https://github.com/google/fonts/tree/master/ofl/arefruqaa
ter-x20b.pcf, from http://terminus-font.sourceforge.net/
BungeeColor-Regular_colr_Windows.ttf, from https://github.com/djrrb/bungee
(from https://github.com/googlei18n/noto-fonts) 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 Noto 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)
10x20-ISO8859-1.pcf chromacheck-sbix.woff, from https://github.com/RoelN/ChromaCheck, under The MIT License (MIT), Copyright (c) 2018 Roel Nieskens, https://pixelambacht.nl Copyright (c) 2018 Google LLC
(from https://packages.ubuntu.com/xenial/xfonts-base) KhmerOSBattambang-Regular.ttf is licensed under LGPL-2.1 or later.
FreeMono.ttf is licensed under GPLv3.
10x20-ISO8859-1.pcf, from https://packages.ubuntu.com/xenial/xfonts-base
"Public domain font. Share and enjoy." "Public domain font. Share and enjoy."

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Tests/fonts/TINY5x3GX.ttf Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

BIN
Tests/fonts/ter-x20b.pcf Normal file

Binary file not shown.

View File

@ -1,33 +1,54 @@
""" """
Helper functions. Helper functions.
""" """
from __future__ import print_function
import sys
import tempfile
import os
import unittest
from PIL import Image, ImageMath
from PIL._util import py3
import logging import logging
import os
import shutil
import sys
import sysconfig
import tempfile
from io import BytesIO
import pytest
from packaging.version import parse as parse_version
from PIL import Image, ImageMath, features
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
HAS_UPLOADER = False HAS_UPLOADER = False
if os.environ.get('SHOW_ERRORS', None): if os.environ.get("SHOW_ERRORS", None):
# local img.show for errors. # local img.show for errors.
HAS_UPLOADER = True HAS_UPLOADER = True
class test_image_results: class test_image_results:
@classmethod @staticmethod
def upload(self, a, b): def upload(a, b):
a.show() a.show()
b.show() b.show()
elif "GITHUB_ACTIONS" in os.environ:
HAS_UPLOADER = True
class test_image_results:
@staticmethod
def upload(a, b):
dir_errors = os.path.join(os.path.dirname(__file__), "errors")
os.makedirs(dir_errors, exist_ok=True)
tmpdir = tempfile.mkdtemp(dir=dir_errors)
a.save(os.path.join(tmpdir, "a.png"))
b.save(os.path.join(tmpdir, "b.png"))
return tmpdir
else: else:
try: try:
import test_image_results import test_image_results
HAS_UPLOADER = True HAS_UPLOADER = True
except ImportError: except ImportError:
pass pass
@ -35,207 +56,125 @@ else:
def convert_to_comparable(a, b): def convert_to_comparable(a, b):
new_a, new_b = a, b new_a, new_b = a, b
if a.mode == 'P': if a.mode == "P":
new_a = Image.new('L', a.size) new_a = Image.new("L", a.size)
new_b = Image.new('L', b.size) new_b = Image.new("L", b.size)
new_a.putdata(a.getdata()) new_a.putdata(a.getdata())
new_b.putdata(b.getdata()) new_b.putdata(b.getdata())
elif a.mode == 'I;16': elif a.mode == "I;16":
new_a = a.convert('I') new_a = a.convert("I")
new_b = b.convert('I') new_b = b.convert("I")
return new_a, new_b return new_a, new_b
class PillowTestCase(unittest.TestCase): def assert_deep_equal(a, b, msg=None):
def __init__(self, *args, **kwargs):
unittest.TestCase.__init__(self, *args, **kwargs)
# holds last result object passed to run method:
self.currentResult = None
def run(self, result=None):
self.currentResult = result # remember result for use later
unittest.TestCase.run(self, result) # call superclass run method
def delete_tempfile(self, path):
try: try:
ok = self.currentResult.wasSuccessful() assert len(a) == len(b), msg or f"got length {len(a)}, expected {len(b)}"
except AttributeError: # for pytest
ok = True
if ok:
# only clean out tempfiles if test passed
try:
os.remove(path)
except OSError:
pass # report?
else:
print("=== orphaned temp file: %s" % path)
def assert_deep_equal(self, a, b, msg=None):
try:
self.assertEqual(
len(a), len(b),
msg or "got length %s, expected %s" % (len(a), len(b)))
self.assertTrue(
all(x == y for x, y in zip(a, b)),
msg or "got %s, expected %s" % (a, b))
except Exception: except Exception:
self.assertEqual(a, b, msg) assert a == b, msg
def assert_image(self, im, mode, size, msg=None):
def assert_image(im, mode, size, msg=None):
if mode is not None: if mode is not None:
self.assertEqual( assert im.mode == mode, (
im.mode, mode, msg or f"got mode {repr(im.mode)}, expected {repr(mode)}"
msg or "got mode %r, expected %r" % (im.mode, mode)) )
if size is not None: if size is not None:
self.assertEqual( assert im.size == size, (
im.size, size, msg or f"got size {repr(im.size)}, expected {repr(size)}"
msg or "got size %r, expected %r" % (im.size, size)) )
def assert_image_equal(self, a, b, msg=None):
self.assertEqual( def assert_image_equal(a, b, msg=None):
a.mode, b.mode, assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
msg or "got mode %r, expected %r" % (a.mode, b.mode)) assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
self.assertEqual(
a.size, b.size,
msg or "got size %r, expected %r" % (a.size, b.size))
if a.tobytes() != b.tobytes(): if a.tobytes() != b.tobytes():
if HAS_UPLOADER: if HAS_UPLOADER:
try: try:
url = test_image_results.upload(a, b) url = test_image_results.upload(a, b)
logger.error("Url for test images: %s" % url) logger.error(f"Url for test images: {url}")
except Exception: except Exception:
pass pass
self.fail(msg or "got different content") assert False, msg or "got different content"
def assert_image_equal_tofile(self, a, filename, msg=None, mode=None):
def assert_image_equal_tofile(a, filename, msg=None, mode=None):
with Image.open(filename) as img: with Image.open(filename) as img:
if mode: if mode:
img = img.convert(mode) img = img.convert(mode)
self.assert_image_equal(a, img, msg) assert_image_equal(a, img, msg)
def assert_image_similar(self, a, b, epsilon, msg=None):
epsilon = float(epsilon) def assert_image_similar(a, b, epsilon, msg=None):
self.assertEqual( assert a.mode == b.mode, msg or f"got mode {repr(a.mode)}, expected {repr(b.mode)}"
a.mode, b.mode, assert a.size == b.size, msg or f"got size {repr(a.size)}, expected {repr(b.size)}"
msg or "got mode %r, expected %r" % (a.mode, b.mode))
self.assertEqual(
a.size, b.size,
msg or "got size %r, expected %r" % (a.size, b.size))
a, b = convert_to_comparable(a, b) a, b = convert_to_comparable(a, b)
diff = 0 diff = 0
for ach, bch in zip(a.split(), b.split()): for ach, bch in zip(a.split(), b.split()):
chdiff = ImageMath.eval("abs(a - b)", a=ach, b=bch).convert('L') chdiff = ImageMath.eval("abs(a - b)", a=ach, b=bch).convert("L")
diff += sum(i * num for i, num in enumerate(chdiff.histogram())) diff += sum(i * num for i, num in enumerate(chdiff.histogram()))
ave_diff = float(diff)/(a.size[0]*a.size[1]) ave_diff = diff / (a.size[0] * a.size[1])
try: try:
self.assertGreaterEqual( assert epsilon >= ave_diff, (
epsilon, ave_diff, (msg or "")
(msg or '') + + f" average pixel value difference {ave_diff:.4f} > epsilon {epsilon:.4f}"
" average pixel value difference %.4f > epsilon %.4f" % ( )
ave_diff, epsilon))
except Exception as e: except Exception as e:
if HAS_UPLOADER: if HAS_UPLOADER:
try: try:
url = test_image_results.upload(a, b) url = test_image_results.upload(a, b)
logger.error("Url for test images: %s" % url) logger.error(f"Url for test images: {url}")
except Exception: except Exception:
pass pass
raise e raise e
def assert_image_similar_tofile(self, a, filename, epsilon, msg=None,
mode=None): def assert_image_similar_tofile(a, filename, epsilon, msg=None, mode=None):
with Image.open(filename) as img: with Image.open(filename) as img:
if mode: if mode:
img = img.convert(mode) img = img.convert(mode)
self.assert_image_similar(a, img, epsilon, msg) assert_image_similar(a, img, epsilon, msg)
def assert_warning(self, warn_class, func, *args, **kwargs):
import warnings
with warnings.catch_warnings(record=True) as w: def assert_all_same(items, msg=None):
# Cause all warnings to always be triggered. assert items.count(items[0]) == len(items), msg
warnings.simplefilter("always")
# Hopefully trigger a warning.
result = func(*args, **kwargs)
# Verify some things. def assert_not_all_same(items, msg=None):
if warn_class is None: assert items.count(items[0]) != len(items), msg
self.assertEqual(len(w), 0,
"Expected no warnings, got %s" %
[v.category for v in w])
else:
self.assertGreaterEqual(len(w), 1)
found = False
for v in w:
if issubclass(v.category, warn_class):
found = True
break
self.assertTrue(found)
return result
def assert_all_same(self, items, msg=None):
self.assertEqual(items.count(items[0]), len(items), msg)
def assert_not_all_same(self, items, msg=None): def assert_tuple_approx_equal(actuals, targets, threshold, msg):
self.assertNotEqual(items.count(items[0]), len(items), msg)
def assert_tuple_approx_equal(self, actuals, targets, threshold, msg):
"""Tests if actuals has values within threshold from targets""" """Tests if actuals has values within threshold from targets"""
value = True value = True
for i, target in enumerate(targets): for i, target in enumerate(targets):
value *= (target - threshold <= actuals[i] <= target + threshold) value *= target - threshold <= actuals[i] <= target + threshold
self.assertTrue(value, assert value, msg + ": " + repr(actuals) + " != " + repr(targets)
msg + ': ' + repr(actuals) + ' != ' + repr(targets))
def skipKnownBadTest(self, msg=None, platform=None,
travis=None, interpreter=None):
# Skip if platform/travis matches, and
# PILLOW_RUN_KNOWN_BAD is not true in the environment.
if os.environ.get('PILLOW_RUN_KNOWN_BAD', False):
print(os.environ.get('PILLOW_RUN_KNOWN_BAD', False))
return
skip = True
if platform is not None:
skip = sys.platform.startswith(platform)
if travis is not None:
skip = skip and (travis == bool(os.environ.get('TRAVIS', False)))
if interpreter is not None:
skip = skip and (interpreter == 'pypy' and
hasattr(sys, 'pypy_version_info'))
if skip:
self.skipTest(msg or "Known Bad Test")
def tempfile(self, template):
assert template[:5] in ("temp.", "temp_")
fd, path = tempfile.mkstemp(template[4:], template[:4])
os.close(fd)
self.addCleanup(self.delete_tempfile, path)
return path
def open_withImagemagick(self, f):
if not imagemagick_available():
raise IOError()
outfile = self.tempfile("temp.png")
if command_succeeds([IMCONVERT, f, outfile]):
return Image.open(outfile)
raise IOError()
@unittest.skipIf(sys.platform.startswith('win32'), "requires Unix or macOS") def skip_unless_feature(feature):
class PillowLeakTestCase(PillowTestCase): reason = f"{feature} not available"
return pytest.mark.skipif(not features.check(feature), reason=reason)
def skip_unless_feature_version(feature, version_required, reason=None):
if not features.check(feature):
return pytest.mark.skip(f"{feature} not available")
if reason is None:
reason = f"{feature} is older than {version_required}"
version_required = parse_version(version_required)
version_available = parse_version(features.version(feature))
return pytest.mark.skipif(version_available < version_required, reason=reason)
@pytest.mark.skipif(sys.platform.startswith("win32"), reason="Requires Unix or macOS")
class PillowLeakTestCase:
# requires unix/macOS # requires unix/macOS
iterations = 100 # count iterations = 100 # count
mem_limit = 512 # k mem_limit = 512 # k
@ -248,9 +187,10 @@ class PillowLeakTestCase(PillowTestCase):
:returns: memory usage in kilobytes :returns: memory usage in kilobytes
""" """
from resource import getrusage, RUSAGE_SELF from resource import RUSAGE_SELF, getrusage
mem = getrusage(RUSAGE_SELF).ru_maxrss mem = getrusage(RUSAGE_SELF).ru_maxrss
if sys.platform == 'darwin': if sys.platform == "darwin":
# man 2 getrusage: # man 2 getrusage:
# ru_maxrss # ru_maxrss
# This is the maximum resident set size utilized (in bytes). # This is the maximum resident set size utilized (in bytes).
@ -266,26 +206,19 @@ class PillowLeakTestCase(PillowTestCase):
start_mem = self._get_mem_usage() start_mem = self._get_mem_usage()
for cycle in range(self.iterations): for cycle in range(self.iterations):
core() core()
mem = (self._get_mem_usage() - start_mem) mem = self._get_mem_usage() - start_mem
msg = 'memory usage limit exceeded in iteration %d' % cycle msg = f"memory usage limit exceeded in iteration {cycle}"
self.assertLess(mem, self.mem_limit, msg) assert mem < self.mem_limit, msg
# helpers # helpers
if not py3:
# Remove DeprecationWarning in Python 3
PillowTestCase.assertRaisesRegex = PillowTestCase.assertRaisesRegexp
PillowTestCase.assertRegex = PillowTestCase.assertRegexpMatches
def fromstring(data): def fromstring(data):
from io import BytesIO
return Image.open(BytesIO(data)) return Image.open(BytesIO(data))
def tostring(im, string_format, **options): def tostring(im, string_format, **options):
from io import BytesIO
out = BytesIO() out = BytesIO()
im.save(out, string_format, **options) im.save(out, string_format, **options)
return out.getvalue() return out.getvalue()
@ -312,58 +245,73 @@ def hopper(mode=None, cache={}):
return im.copy() return im.copy()
def command_succeeds(cmd):
"""
Runs the command, which must be a list of strings. Returns True if the
command succeeds, or False if an OSError was raised by subprocess.Popen.
"""
import subprocess
with open(os.devnull, 'wb') as f:
try:
subprocess.call(cmd, stdout=f, stderr=subprocess.STDOUT)
except OSError:
return False
return True
def djpeg_available(): def djpeg_available():
return command_succeeds(['djpeg', '-version']) return bool(shutil.which("djpeg"))
def cjpeg_available(): def cjpeg_available():
return command_succeeds(['cjpeg', '-version']) return bool(shutil.which("cjpeg"))
def netpbm_available(): def netpbm_available():
return (command_succeeds(["ppmquant", "--version"]) and return bool(shutil.which("ppmquant") and shutil.which("ppmtogif"))
command_succeeds(["ppmtogif", "--version"]))
def imagemagick_available(): def magick_command():
return IMCONVERT and command_succeeds([IMCONVERT, '-version']) if sys.platform == "win32":
magickhome = os.environ.get("MAGICK_HOME", "")
if magickhome:
imagemagick = [os.path.join(magickhome, "convert.exe")]
graphicsmagick = [os.path.join(magickhome, "gm.exe"), "convert"]
else:
imagemagick = None
graphicsmagick = None
else:
imagemagick = ["convert"]
graphicsmagick = ["gm", "convert"]
if imagemagick and shutil.which(imagemagick[0]):
return imagemagick
elif graphicsmagick and shutil.which(graphicsmagick[0]):
return graphicsmagick
def on_appveyor(): def on_appveyor():
return 'APPVEYOR' in os.environ return "APPVEYOR" in os.environ
if sys.platform == 'win32': def on_github_actions():
IMCONVERT = os.environ.get('MAGICK_HOME', '') return "GITHUB_ACTIONS" in os.environ
if IMCONVERT:
IMCONVERT = os.path.join(IMCONVERT, 'convert.exe')
else:
IMCONVERT = 'convert'
def distro(): def on_ci():
if os.path.exists('/etc/os-release'): # GitHub Actions and AppVeyor have "CI"
with open('/etc/os-release', 'r') as f: return "CI" in os.environ
for line in f:
if 'ID=' in line:
return line.strip().split('=')[1]
class cached_property(object): def is_big_endian():
return sys.byteorder == "big"
def is_ppc64le():
import platform
return platform.machine() == "ppc64le"
def is_win32():
return sys.platform.startswith("win32")
def is_pypy():
return hasattr(sys, "pypy_translation_info")
def is_mingw():
return sysconfig.get_platform() == "mingw"
class cached_property:
def __init__(self, func): def __init__(self, func):
self.func = func self.func = func

Binary file not shown.

Binary file not shown.

BIN
Tests/images/01r_00.pcx Normal file

Binary file not shown.

BIN
Tests/images/1_trns.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

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