Merge branch 'master' into exif-writing-fixes

This commit is contained in:
Andrew Murray 2019-12-31 11:38:33 +11:00 committed by GitHub
commit 6d3fb7b083
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
367 changed files with 9358 additions and 6458 deletions

View File

@ -13,16 +13,8 @@ environment:
TEST_OPTIONS: TEST_OPTIONS:
DEPLOY: YES DEPLOY: YES
matrix: matrix:
- PYTHON: C:/vp/pypy2 - PYTHON: C:/Python38
EXECUTABLE: bin/pypy.exe - PYTHON: C:/Python38-x64
PIP_DIR: bin
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:/Python35 - PYTHON: C:/Python35
- PYTHON: C:/Python35-x64 - PYTHON: C:/Python35-x64
- PYTHON: C:/msys64/mingw32 - PYTHON: C:/msys64/mingw32
@ -30,6 +22,10 @@ environment:
PIP_DIR: bin PIP_DIR: bin
TEST_OPTIONS: --processes=0 TEST_OPTIONS: --processes=0
DEPLOY: NO DEPLOY: NO
- PYTHON: C:/vp/pypy3
EXECUTABLE: bin/pypy.exe
PIP_DIR: bin
VENV: YES
install: install:
@ -41,9 +37,9 @@ install:
- xcopy /s c:\pillow-depends\test_images\* c:\pillow\tests\images - xcopy /s c:\pillow-depends\test_images\* c:\pillow\tests\images
- cd c:\pillow\winbuild\ - cd c:\pillow\winbuild\
- ps: | - ps: |
if ($env:PYTHON -eq "c:/vp/pypy2") if ($env:PYTHON -eq "c:/vp/pypy3")
{ {
c:\pillow\winbuild\appveyor_install_pypy.cmd c:\pillow\winbuild\appveyor_install_pypy3.cmd
} }
- ps: | - ps: |
if ($env:PYTHON -eq "c:/msys64/mingw32") if ($env:PYTHON -eq "c:/msys64/mingw32")
@ -56,6 +52,9 @@ install:
c:\pillow\winbuild\build_deps.cmd c:\pillow\winbuild\build_deps.cmd
$host.SetShouldExit(0) $host.SetShouldExit(0)
} }
- curl -fsSL -o gs950.exe https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs950/gs950w32.exe
- gs950.exe /S
- path %path%;C:\Program Files (x86)\gs\gs9.50\bin
build_script: build_script:
- ps: | - ps: |
@ -76,7 +75,8 @@ build_script:
test_script: test_script:
- cd c:\pillow - cd c:\pillow
- '%PYTHON%\%PIP_DIR%\pip.exe install pytest pytest-cov' - '%PYTHON%\%PIP_DIR%\pip.exe 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% -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:

View File

@ -3,7 +3,14 @@
codecov: codecov:
# Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]" # Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]"
# https://github.com/codecov/support/issues/363 # https://github.com/codecov/support/issues/363
# https://docs.codecov.io/v4.3.6/docs/comparing-commits # https://docs.codecov.io/docs/comparing-commits
allow_coverage_offsets: true allow_coverage_offsets: true
comment: off comment: off
# Matches 'omit:' in .coveragerc
ignore:
- "Tests/32bit_segfault_check.py"
- "Tests/bench_cffi_access.py"
- "Tests/check_*.py"
- "Tests/createfontdatachunk.py"

View File

@ -12,3 +12,10 @@ exclude_lines =
# Don't complain about debug code # Don't complain about debug code
if Image.DEBUG: 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,14 +9,14 @@ 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 [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests. - Run the test suite. You can enable [Travis CI](https://travis-ci.org/profile/) and [AppVeyor](https://ci.appveyor.com/projects/new) on your repo to catch test failures prior to the pull request, and [Codecov](https://codecov.io/gh) to see if the changed code is covered by tests.
- 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
- 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 PEP8. - 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 Travis-CI and AppVeyor.
## Reporting Issues ## Reporting Issues

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
tidelift: pypi/pillow tidelift: "pypi/Pillow"

18
.github/codecov-upstream.yml vendored Normal file
View File

@ -0,0 +1,18 @@
# 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/docs/comparing-commits
allow_coverage_offsets: true
token: 6dafc396-e7f5-4221-a38a-8b07a49fbdae
comment: off
# Matches 'omit:' in .coveragerc
ignore:
- "Tests/32bit_segfault_check.py"
- "Tests/bench_cffi_access.py"
- "Tests/check_*.py"
- "Tests/createfontdatachunk.py"

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

@ -0,0 +1,32 @@
name: Lint
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8"]
name: Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Build system information
run: python .github/workflows/system-info.py
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install --upgrade tox
- name: Lint
run: tox -e lint

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

@ -0,0 +1,17 @@
#!/bin/bash
set -e
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype
PYTHONOPTIMIZE=0 pip install cffi
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
# extra test images
pushd depends && ./install_extra_test_images.sh && popd

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

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

@ -0,0 +1,66 @@
name: Test Docker
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
docker: [
alpine,
arch,
ubuntu-16.04-xenial-amd64,
ubuntu-18.04-bionic-amd64,
debian-9-stretch-x86,
debian-10-buster-x86,
centos-6-amd64,
centos-7-amd64,
centos-8-amd64,
amazon-1-amd64,
amazon-2-amd64,
fedora-30-amd64,
fedora-31-amd64,
]
dockerTag: [master]
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v1
- name: Build system information
run: python .github/workflows/system-info.py
- 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 -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
sudo chown -R runner $GITHUB_WORKSPACE
- name: After success
if: success()
run: |
pip install wheel
sudo apt-get install -qq ruby-dev
PATH="$PATH:~/.local/bin"
.travis/after_success.sh
env:
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
- name: Prepare coverage token
if: success() && github.repository == 'python-pillow/Pillow'
run: cp .github/codecov-upstream.yml .codecov.yml
- name: Upload coverage
if: success()
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: ${{ matrix.docker }}

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

@ -0,0 +1,399 @@
name: Test Windows
on: [push, pull_request]
jobs:
build:
runs-on: windows-2019
strategy:
fail-fast: false
matrix:
python-version: ["3.5", "3.6", "3.7", "3.8", "pypy3.6"]
architecture: ["x86", "x64"]
include:
- architecture: "x86"
platform-vcvars: "x86"
platform-msbuild: "Win32"
- architecture: "x64"
platform-vcvars: "x86_amd64"
platform-msbuild: "x64"
- python-version: "pypy3.6"
pypy-version: "pypy3.6-v7.3.0-win32"
pypy-url: "https://bitbucket.org/pypy/pypy/downloads/pypy3.6-v7.3.0-win32.zip"
exclude:
- python-version: "pypy3.6"
architecture: "x64"
timeout-minutes: 30
name: Python ${{ matrix.python-version }} ${{ matrix.architecture }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v1
with:
repository: python-pillow/pillow-depends
ref: master
- name: Cache
uses: actions/cache@v1
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 }}-
- name: Install PyPy
if: "contains(matrix.python-version, 'pypy')"
run: |
curl -fsSL -o pypy3.zip "${{ matrix.pypy-url }}"
7z x pypy3.zip "-o$env:RUNNER_WORKSPACE\"
mv "$env:RUNNER_WORKSPACE\${{ matrix.pypy-version }}" "$env:RUNNER_WORKSPACE\${{ matrix.python-version }}"
$env:PYTHON="$env:RUNNER_WORKSPACE\${{ matrix.python-version }}"
# set env: pythonLocation
Write-Host "`#`#[set-env name=pythonLocation;]$env:PYTHON" # old syntax https://github.com/actions/toolkit/issues/61
Write-Host "::set-env name=pythonLocation::$env:PYTHON" # new syntax https://github.com/actions/toolkit/blob/5bb77ec03fea98332e41f9347c8fbb1ce1e48f4a/docs/commands.md
New-Item -ItemType SymbolicLink -Path "$env:PYTHON\python.exe" -Target "$env:PYTHON\pypy3.exe"
curl -fsSL -o get-pip.py https://bootstrap.pypa.io/get-pip.py
$env:PATH = "$env:PYTHON\bin;$env:PATH"
& $env:PYTHON\python.exe get-pip.py
shell: pwsh
# sets env: pythonLocation
- name: Set up Python
if: "!contains(matrix.python-version, 'pypy')"
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }}
- name: Build system information
run: python .github/workflows/system-info.py
- name: pip install wheel pytest pytest-cov
run: |
"%pythonLocation%\python.exe" -m pip install wheel pytest pytest-cov
shell: cmd
- name: Fetch dependencies
run: |
7z x ..\pillow-depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\"
Write-Host "`#`#[add-path]$env:RUNNER_WORKSPACE\nasm-2.14.02"
Write-Host "::add-path::$env:RUNNER_WORKSPACE\nasm-2.14.02"
..\pillow-depends\gs950w32.exe /S
Write-Host "`#`#[add-path]C:\Program Files (x86)\gs\gs9.50\bin"
Write-Host "::add-path::C:\Program Files (x86)\gs\gs9.50\bin"
$env:PYTHON=$env:pythonLocation
xcopy ..\pillow-depends\*.zip $env:GITHUB_WORKSPACE\winbuild\
xcopy ..\pillow-depends\*.tar.gz $env:GITHUB_WORKSPACE\winbuild\
xcopy /s ..\pillow-depends\test_images\* $env:GITHUB_WORKSPACE\tests\images\
cd $env:GITHUB_WORKSPACE/winbuild/
python.exe $env:GITHUB_WORKSPACE\winbuild\build_dep.py
env:
EXECUTABLE: bin\python.exe
shell: pwsh
- name: Build dependencies / libjpeg
if: false
run: |
REM FIXME uses /MT not /MD, see makefile.vc and win32.mak for more info
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
cd /D %BUILD%\jpeg-9c
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
nmake -nologo -f makefile.vc setup-vc6
nmake -nologo -f makefile.vc clean
nmake -nologo -f makefile.vc nodebug=1 libjpeg.lib cjpeg.exe djpeg.exe
copy /Y /B j*.h %INCLIB%
copy /Y /B *.lib %INCLIB%
copy /Y /B *.exe %INCLIB%
shell: cmd
- name: Build dependencies / libjpeg-turbo
run: |
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
cd /D %BUILD%\libjpeg-turbo-2.0.3
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF
set CMAKE=%CMAKE% -DENABLE_SHARED:BOOL=OFF -DWITH_JPEG8:BOOL=TRUE -DWITH_CRT_DLL:BOOL=TRUE -DCMAKE_BUILD_TYPE=Release
%CMAKE% -G "NMake Makefiles" .
nmake -nologo -f Makefile clean
nmake -nologo -f Makefile jpeg-static cjpeg-static djpeg-static
copy /Y /B j*.h %INCLIB%
copy /Y /B jpeg-static.lib %INCLIB%\libjpeg.lib
copy /Y /B cjpeg-static.exe %INCLIB%\cjpeg.exe
copy /Y /B djpeg-static.exe %INCLIB%\djpeg.exe
shell: cmd
- name: Build dependencies / zlib
run: |
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
cd /D %BUILD%\zlib-1.2.11
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
nmake -nologo -f win32\Makefile.msc clean
nmake -nologo -f win32\Makefile.msc zlib.lib
copy /Y /B z*.h %INCLIB%
copy /Y /B *.lib %INCLIB%
copy /Y /B zlib.lib %INCLIB%\z.lib
shell: cmd
- name: Build dependencies / LibTIFF
run: |
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
cd /D %BUILD%\tiff-4.1.0
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
copy %GITHUB_WORKSPACE%\winbuild\tiff.opt nmake.opt
nmake -nologo -f makefile.vc clean
nmake -nologo -f makefile.vc lib
copy /Y /B libtiff\tiff*.h %INCLIB%
copy /Y /B libtiff\*.dll %INCLIB%
copy /Y /B libtiff\*.lib %INCLIB%
shell: cmd
- name: Build dependencies / WebP
run: |
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
cd /D %BUILD%\libwebp-1.0.3
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
rmdir /S /Q output\release-static
nmake -nologo -f Makefile.vc CFG=release-static OBJDIR=output ARCH=${{ matrix.architecture }} all
mkdir %INCLIB%\webp
copy /Y /B src\webp\*.h %INCLIB%\webp
copy /Y /B output\release-static\${{ matrix.architecture }}\lib\* %INCLIB%
shell: cmd
- name: Build dependencies / FreeType
run: |
REM Toolkit v100 not available; missing VCTargetsPath; Clean fails
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
cd /D %BUILD%\freetype-2.10.1
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
rmdir /S /Q objs
set DefaultPlatformToolset=v142
set VCTargetsPath=C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\VC\v160\
set MSBUILD="C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe"
powershell -Command "(gc builds\windows\vc2010\freetype.vcxproj) -replace 'MultiThreaded<', 'MultiThreadedDLL<' | Out-File -encoding ASCII builds\windows\vc2010\freetype.vcxproj"
%MSBUILD% builds\windows\vc2010\freetype.sln /t:Build /p:Configuration="Release Static" /p:Platform=${{ matrix.platform-msbuild }} /m
xcopy /Y /E /Q include %INCLIB%
copy /Y /B "objs\${{ matrix.platform-msbuild }}\Release Static\freetype.lib" %INCLIB%
shell: cmd
- name: Build dependencies / LCMS2
run: |
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
cd /D %BUILD%\lcms2-2.8
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
rmdir /S /Q Lib
rmdir /S /Q Projects\VC2015\Release
set VCTargetsPath=C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Microsoft\VC\v160\
set MSBUILD="C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Current\Bin\MSBuild.exe"
powershell %GITHUB_WORKSPACE%\winbuild\lcms2_patch.ps1
%MSBUILD% Projects\VC2015\lcms2.sln /t:Clean;lcms2_static /p:Configuration="Release" /p:Platform=${{ matrix.platform-msbuild }} /m
xcopy /Y /E /Q include %INCLIB%
copy /Y /B Lib\MS\*.lib %INCLIB%
shell: cmd
- name: Build dependencies / OpenJPEG
run: |
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
cd /D %BUILD%\openjpeg-2.3.1msvcr10-x32
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF
set CMAKE=%CMAKE% -DBUILD_THIRDPARTY:BOOL=OFF -DBUILD_SHARED_LIBS:BOOL=OFF
set CMAKE=%CMAKE% -DCMAKE_BUILD_TYPE=Release
%CMAKE% -G "NMake Makefiles" .
nmake -nologo -f Makefile clean
nmake -nologo -f Makefile
mkdir %INCLIB%\openjpeg-2.3.1
copy /Y /B src\lib\openjp2\*.h %INCLIB%\openjpeg-2.3.1
copy /Y /B bin\*.lib %INCLIB%
shell: cmd
# GPL licensed; skip if building wheels
- name: Build dependencies / libimagequant
if: "github.event_name != 'push' || contains(matrix.python-version, 'pypy')"
run: |
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
rem e5d454b: Merge tag '2.12.6' into msvc
cd /D %BUILD%\libimagequant-e5d454bc7f5eb63ee50c84a83a7fa5ac94f68ec4
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
echo (gc CMakeLists.txt) -replace 'add_library', "add_compile_options(-openmp-)`r`nadd_library" ^| Out-File -encoding ASCII CMakeLists.txt > patch.ps1
echo (gc CMakeLists.txt) -replace ' SHARED', ' STATIC' ^| Out-File -encoding ASCII CMakeLists.txt >> patch.ps1
powershell .\patch.ps1
set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF
set CMAKE=%CMAKE% -DCMAKE_BUILD_TYPE=Release
%CMAKE% -G "NMake Makefiles" .
nmake -nologo -f Makefile clean
nmake -nologo -f Makefile
copy /Y /B *.h %INCLIB%
copy /Y /B *.lib %INCLIB%
shell: cmd
# for Raqm
- name: Build dependencies / HarfBuzz
run: |
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
set INCLUDE=%INCLUDE%;%INCLIB%
set LIB=%LIB%;%INCLIB%
cd /D %BUILD%\harfbuzz-2.6.1
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF
set CMAKE=%CMAKE% -DHB_HAVE_FREETYPE:BOOL=ON -DCMAKE_BUILD_TYPE=Release
%CMAKE% -G "NMake Makefiles" .
nmake -nologo -f Makefile clean
nmake -nologo -f Makefile harfbuzz
copy /Y /B src\*.h %INCLIB%
copy /Y /B *.lib %INCLIB%
shell: cmd
# for Raqm
- name: Build dependencies / FriBidi
run: |
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
cd /D %BUILD%\fribidi-1.0.7
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
copy /Y /B %GITHUB_WORKSPACE%\winbuild\fribidi.cmake CMakeLists.txt
set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF
set CMAKE=%CMAKE% -DCMAKE_BUILD_TYPE=Release
%CMAKE% -G "NMake Makefiles" .
nmake -nologo -f Makefile clean
nmake -nologo -f Makefile fribidi
copy /Y /B lib\*.h %INCLIB%
copy /Y /B *.lib %INCLIB%
shell: cmd
- name: Build dependencies / Raqm
run: |
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
set BUILD=%GITHUB_WORKSPACE%\winbuild\build
set INCLUDE=%INCLUDE%;%INCLIB%
set LIB=%LIB%;%INCLIB%
cd /D %BUILD%\libraqm-0.7.0
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
echo on
copy /Y /B %GITHUB_WORKSPACE%\winbuild\raqm.cmake CMakeLists.txt
set CMAKE=cmake.exe -DCMAKE_VERBOSE_MAKEFILE=ON -DCMAKE_RULE_MESSAGES:BOOL=OFF
set CMAKE=%CMAKE% -DCMAKE_BUILD_TYPE=Release
%CMAKE% -G "NMake Makefiles" .
nmake -nologo -f Makefile clean
nmake -nologo -f Makefile libraqm
copy /Y /B src\*.h %INCLIB%
copy /Y /B libraqm.dll %INCLIB%
shell: cmd
- name: Build Pillow
run: |
set PYTHON=%pythonLocation%
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set MPLSRC=%GITHUB_WORKSPACE%
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
cd /D %GITHUB_WORKSPACE%
set LIB=%INCLIB%;%PYTHON%\tcl
set INCLUDE=%INCLIB%;%GITHUB_WORKSPACE%\depends\tcl86\include;%INCLUDE%
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
set MSSdk=1
set DISTUTILS_USE_SDK=1
set py_vcruntime_redist=true
%PYTHON%\python.exe setup.py build_ext install
rem Add libraqm.dll (copied to INCLIB) to PATH.
path %INCLIB%;%PATH%
%PYTHON%\python.exe selftest.py --installed
shell: cmd
# 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 %PYTHON%\python.exe
shell: cmd
- name: Test Pillow
run: |
set PYTHON=%pythonLocation%
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
rem Add libraqm.dll (copied to INCLIB) to PATH.
path %INCLIB%;%PATH%
cd /D %GITHUB_WORKSPACE%
%PYTHON%\python.exe -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
shell: cmd
- name: Upload errors
uses: actions/upload-artifact@v1
if: failure()
with:
name: errors
path: Tests/errors
- name: Prepare coverage token
if: success() && github.repository == 'python-pillow/Pillow'
run: cp .github/codecov-upstream.yml .codecov.yml
- name: Upload coverage
if: success()
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: ${{ runner.os }} Python ${{ matrix.python-version }}
- name: Build wheel
id: wheel
if: "github.event_name == 'push' && !contains(matrix.python-version, 'pypy')"
run: |
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ##[set-output name=dist;]dist-%%a
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo ::set-output name=dist::dist-%%a
set PYTHON=%pythonLocation%
set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\V7.1A\Include
set MPLSRC=%GITHUB_WORKSPACE%
set INCLIB=%GITHUB_WORKSPACE%\winbuild\depends\msvcr10-x32
cd /D %GITHUB_WORKSPACE%
set LIB=%INCLIB%;%PYTHON%\tcl
set INCLUDE=%INCLIB%;%GITHUB_WORKSPACE%\depends\tcl86\include;%INCLUDE%
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" ${{ matrix.platform-vcvars }}
%PYTHON%\python.exe setup.py bdist_wheel
shell: cmd
- uses: actions/upload-artifact@v1
if: "github.event_name == 'push' && !contains(matrix.python-version, 'pypy')"
with:
name: ${{ steps.wheel.outputs.dist }}
path: dist

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

@ -0,0 +1,103 @@
name: Test
on: [push, pull_request]
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [
"ubuntu-latest",
"macOS-latest",
]
python-version: [
"pypy3",
"3.8",
"3.7",
"3.6",
"3.5",
]
include:
- python-version: "3.5"
env: PYTHONOPTIMIZE=2
- python-version: "3.6"
env: PYTHONOPTIMIZE=1
runs-on: ${{ matrix.os }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v1
- name: Ubuntu cache
uses: actions/cache@v1
if: startsWith(matrix.os, 'ubuntu')
with:
path: ~/.cache/pip
key:
${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.travis/*.sh') }}
restore-keys: |
${{ matrix.os }}-${{ matrix.python-version }}-
- name: macOS cache
uses: actions/cache@v1
if: startsWith(matrix.os, 'macOS')
with:
path: ~/Library/Caches/pip
key:
${{ matrix.os }}-${{ matrix.python-version }}-${{ hashFiles('**/.travis/*.sh') }}
restore-keys: |
${{ matrix.os }}-${{ matrix.python-version }}-
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Build system information
run: python .github/workflows/system-info.py
- name: Install Linux dependencies
if: startsWith(matrix.os, 'ubuntu')
run: |
.travis/install.sh
- name: Install macOS dependencies
if: startsWith(matrix.os, 'macOS')
run: |
.github/workflows/macos-install.sh
- name: Build
run: |
.travis/build.sh
- name: Test
run: |
.travis/test.sh
- name: Upload errors
uses: actions/upload-artifact@v1
if: failure()
with:
name: errors
path: Tests/errors
- name: After success
if: success()
run: |
.travis/after_success.sh
env:
MATRIX_OS: ${{ matrix.os }}
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
- name: Prepare coverage token
if: success() && github.repository == 'python-pillow/Pillow'
run: cp .github/codecov-upstream.yml .codecov.yml
- name: Upload coverage
if: success()
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
name: ${{ matrix.os }} Python ${{ matrix.python-version }}

View File

@ -6,8 +6,8 @@ notifications:
irc: "chat.freenode.net#pil" irc: "chat.freenode.net#pil"
# Run fast lint first to get fast feedback. # Run fast lint first to get fast feedback.
# Run slow PyPy* next, to give them a headstart and reduce waiting time. # Run slow PyPy next, to give it a headstart and reduce waiting time.
# Run latest 3.x and 2.x next, to get quick compatibility results. # Run latest 3.x next, to get quick compatibility results.
# Then run the remainder, with fastest Docker jobs last. # Then run the remainder, with fastest Docker jobs last.
matrix: matrix:
@ -16,25 +16,22 @@ matrix:
- python: "3.6" - python: "3.6"
name: "Lint" name: "Lint"
env: LINT="true" env: LINT="true"
- python: "pypy"
name: "PyPy2 Xenial"
- python: "pypy3" - python: "pypy3"
name: "PyPy3 Xenial" name: "PyPy3 Xenial"
- python: "3.8"
name: "3.8 Xenial"
services: xvfb
- python: '3.7' - python: '3.7'
name: "3.7 Xenial" name: "3.7 Xenial"
- python: '2.7'
name: "2.7 Xenial"
- python: "2.7_with_system_site_packages" # For PyQt4
name: "2.7_with_system_site_packages Xenial"
services: xvfb services: xvfb
- python: '3.6' - python: '3.6'
name: "3.6 Xenial PYTHONOPTIMIZE=1" name: "3.6 Xenial PYTHONOPTIMIZE=1"
env: PYTHONOPTIMIZE=1 env: PYTHONOPTIMIZE=1
services: xvfb
- python: '3.5' - python: '3.5'
name: "3.5 Xenial PYTHONOPTIMIZE=2" name: "3.5 Xenial PYTHONOPTIMIZE=2"
env: PYTHONOPTIMIZE=2 env: PYTHONOPTIMIZE=2
- python: "3.8-dev" services: xvfb
name: "3.8-dev Xenial"
- env: DOCKER="alpine" DOCKER_TAG="master" - env: DOCKER="alpine" DOCKER_TAG="master"
- env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5 - env: DOCKER="arch" DOCKER_TAG="master" # contains PyQt5
- env: DOCKER="ubuntu-16.04-xenial-amd64" DOCKER_TAG="master" - env: DOCKER="ubuntu-16.04-xenial-amd64" DOCKER_TAG="master"
@ -43,10 +40,11 @@ matrix:
- env: DOCKER="debian-10-buster-x86" DOCKER_TAG="master" - env: DOCKER="debian-10-buster-x86" DOCKER_TAG="master"
- env: DOCKER="centos-6-amd64" DOCKER_TAG="master" - env: DOCKER="centos-6-amd64" DOCKER_TAG="master"
- env: DOCKER="centos-7-amd64" DOCKER_TAG="master" - env: DOCKER="centos-7-amd64" DOCKER_TAG="master"
- env: DOCKER="centos-8-amd64" DOCKER_TAG="master"
- env: DOCKER="amazon-1-amd64" DOCKER_TAG="master" - env: DOCKER="amazon-1-amd64" DOCKER_TAG="master"
- env: DOCKER="amazon-2-amd64" DOCKER_TAG="master" - env: DOCKER="amazon-2-amd64" DOCKER_TAG="master"
- env: DOCKER="fedora-29-amd64" DOCKER_TAG="master"
- env: DOCKER="fedora-30-amd64" DOCKER_TAG="master" - env: DOCKER="fedora-30-amd64" DOCKER_TAG="master"
- env: DOCKER="fedora-31-amd64" DOCKER_TAG="master"
services: services:
- docker - docker
@ -67,7 +65,8 @@ script:
if [ "$LINT" == "true" ]; then if [ "$LINT" == "true" ]; then
tox -e lint tox -e lint
elif [ "$DOCKER" == "" ]; then elif [ "$DOCKER" == "" ]; then
.travis/script.sh .travis/build.sh
.travis/test.sh
elif [ "$DOCKER" ]; then elif [ "$DOCKER" ]; then
# the Pillow user in the docker container is UID 1000 # the Pillow user in the docker container is UID 1000
sudo chown -R 1000 $TRAVIS_BUILD_DIR sudo chown -R 1000 $TRAVIS_BUILD_DIR

View File

@ -1,26 +1,30 @@
#!/bin/bash #!/bin/bash
# gather the coverage data # gather the coverage data
sudo apt-get -qq install lcov if [[ "$MATRIX_OS" == "macOS-latest" ]]; then
brew install lcov
else
sudo apt-get -qq install lcov
fi
lcov --capture --directory . -b . --output-file coverage.info lcov --capture --directory . -b . --output-file coverage.info
# filter to remove system headers # filter to remove system headers
lcov --remove coverage.info '/usr/*' -o coverage.filtered.info lcov --remove coverage.info '/usr/*' -o coverage.filtered.info
# convert to json # convert to json
gem install coveralls-lcov sudo gem install coveralls-lcov
coveralls-lcov -v -n coverage.filtered.info > coverage.c.json coveralls-lcov -v -n coverage.filtered.info > coverage.c.json
coverage report
pip install codecov pip install codecov
if [[ $TRAVIS_PYTHON_VERSION != "2.7_with_system_site_packages" ]]; then coverage report
# Not working here. Just skip it, it's being removed soon.
pip install coveralls-merge
coveralls-merge coverage.c.json
fi
codecov
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ] && [ "$DOCKER" == "" ]; then pip install coveralls-merge
coveralls-merge coverage.c.json
if [[ $TRAVIS ]]; then
codecov
fi
if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ] && [ "$DOCKER" == "" ]; then
# Coverage and quality reports on just the latest diff. # 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-install.sh
depends/diffcover-run.sh depends/diffcover-run.sh
fi fi

10
.travis/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

View File

@ -3,7 +3,7 @@
set -e set -e
sudo apt-get update sudo apt-get update
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python-tk python-qt4\ sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\ ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
cmake imagemagick libharfbuzz-dev libfribidi-dev cmake imagemagick libharfbuzz-dev libfribidi-dev
@ -15,9 +15,13 @@ pip install -U pytest-cov
pip install pyroma pip install pyroma
pip install test-image-results pip install test-image-results
pip install numpy pip install numpy
if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then
sudo apt-get -qq install pyqt5-dev-tools
pip install pyqt5
fi
# docs only on Python 2.7 # docs only on Python 3.8
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then pip install -r requirements.txt ; fi if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ]; then pip install -r requirements.txt ; fi
# webp # webp
pushd depends && ./install_webp.sh && popd pushd depends && ./install_webp.sh && popd

View File

@ -1,12 +0,0 @@
#!/bin/bash
set -e
coverage erase
make clean
make install-coverage
python -m pytest -v -x --cov PIL --cov-report term Tests
# Docs
if [ "$TRAVIS_PYTHON_VERSION" == "2.7" ]; then make doccheck; fi

8
.travis/test.sh Executable file
View File

@ -0,0 +1,8 @@
#!/bin/bash
set -e
python -m pytest -v -x --cov PIL --cov Tests --cov-report term Tests
# Docs
if [ "$TRAVIS_PYTHON_VERSION" == "3.8" ]; then make doccheck; fi

View File

@ -2,11 +2,152 @@
Changelog (Pillow) Changelog (Pillow)
================== ==================
6.2.0 (unreleased) 7.0.0 (unreleased)
------------------
- Allow loading of WMF images at a given DPI #4311
[radarhere]
- Added reduce operation #4251
[homm]
- Raise ValueError for io.StringIO in Image.open #4302
[radarhere, hugovk]
- Fix thumbnail geometry when DCT scaling is used #4231
[homm, radarhere]
- Use default DPI when exif provides invalid x_resolution #4147
[beipang2, radarhere]
- Change default resize resampling filter from NEAREST to BICUBIC #4255
[homm]
- Fixed black lines on upscaled images with the BOX filter #4278
[homm]
- Better thumbnail aspect ratio preservation #4256
[homm]
- Add La mode packing and unpacking #4248
[homm]
- Include tests in coverage reports #4173
[hugovk]
- Handle broken Photoshop data #4239
[radarhere]
- Raise a specific exception if no data is found for an MPO frame #4240
[radarhere]
- Fix Unicode support for PyPy #4145
[nulano]
- Drop support for EOL Python 2.7 #4109
[hugovk, radarhere, jdufresne]
- Added UnidentifiedImageError #4182
[radarhere, hugovk]
- Remove deprecated __version__ from plugins #4197
[hugovk, radarhere]
- Fixed freeing unallocated pointer when resizing with height too large #4116
[radarhere]
- Copy info in Image.transform #4128
[radarhere]
- Corrected DdsImagePlugin setting info gamma #4171
[radarhere]
- Depends: Update libtiff to 4.1.0 #4195, Tk Tcl to 8.6.10 #4229, libimagequant to 2.12.6 #4318
[radarhere]
- Improve handling of file resources #3577
[jdufresne]
- Removed CI testing of Fedora 29 #4165
[hugovk]
- Added pypy3 to tox envlist #4137
[jdufresne]
- Drop support for EOL PyQt4 and PySide #4108
[hugovk, radarhere]
- Removed deprecated setting of TIFF image sizes #4114
[radarhere]
- Removed deprecated PILLOW_VERSION #4107
[hugovk]
- Changed default frombuffer raw decoder args #1730
[radarhere]
6.2.1 (2019-10-21)
------------------ ------------------
- This is the last Pillow release to support Python 2.7 #3642 - This is the last Pillow release to support Python 2.7 #3642
- Add support for Python 3.8 #4141
[hugovk]
6.2.0 (2019-10-01)
------------------
- Catch buffer overruns #4104
[radarhere]
- Initialize rows_per_strip when RowsPerStrip tag is missing #4034
[cgohlke, radarhere]
- Raise error if TIFF dimension is a string #4103
[radarhere]
- Added decompression bomb checks #4102
[radarhere]
- Fix ImageGrab.grab DPI scaling on Windows 10 version 1607+ #4000
[nulano, radarhere]
- Corrected negative seeks #4101
[radarhere]
- Added argument to capture all screens on Windows #3950
[nulano, radarhere]
- Updated warning to specify when Image.frombuffer defaults will change #4086
[radarhere]
- Changed WindowsViewer format to PNG #4080
[radarhere]
- Use TIFF orientation #4063
[radarhere]
- Raise the same error if a truncated image is loaded a second time #3965
[radarhere]
- Lazily use ImageFileDirectory_v1 values from Exif #4031
[radarhere]
- Improved HSV conversion #4004
[radarhere]
- Added text stroking #3978
[radarhere, hugovk]
- No more deprecated bdist_wininst .exe installers #4029
[hugovk]
- Do not allow floodfill to extend into negative coordinates #4017
[radarhere]
- Fixed arc drawing bug for a non-whole number of degrees #4014
[radarhere]
- Fix bug when merging identical images to GIF with a list of durations #4003 - Fix bug when merging identical images to GIF with a list of durations #4003
[djy0, radarhere] [djy0, radarhere]
@ -70,7 +211,7 @@ Changelog (Pillow)
- Updated TIFF tile descriptors to match current decoding functionality #3795 - Updated TIFF tile descriptors to match current decoding functionality #3795
[dmnisson] [dmnisson]
- Added an `image.entropy()` method (second revision) #3608 - Added an ``image.entropy()`` method (second revision) #3608
[fish2000] [fish2000]
- Pass the correct types to PyArg_ParseTuple #3880 - Pass the correct types to PyArg_ParseTuple #3880
@ -706,7 +847,7 @@ Changelog (Pillow)
- Enable background colour parameter on rotate #3057 - Enable background colour parameter on rotate #3057
[storesource] [storesource]
- Remove unnecessary `#if 1` directive #3072 - Remove unnecessary ``#if 1`` directive #3072
[jdufresne] [jdufresne]
- Remove unused Python class, Path #3070 - Remove unused Python class, Path #3070
@ -1243,7 +1384,7 @@ Changelog (Pillow)
- Add decompression bomb check to Image.crop #2410 - Add decompression bomb check to Image.crop #2410
[wiredfool] [wiredfool]
- ImageFile: Ensure that the `err_code` variable is initialized in case of exception. #2363 - ImageFile: Ensure that the ``err_code`` variable is initialized in case of exception. #2363
[alexkiro] [alexkiro]
- Tiff: Support append_images for saving multipage TIFFs #2406 - Tiff: Support append_images for saving multipage TIFFs #2406
@ -1480,7 +1621,7 @@ Changelog (Pillow)
- Removed PIL 1.0 era TK readme that concerns Windows 95/NT #2360 - Removed PIL 1.0 era TK readme that concerns Windows 95/NT #2360
[wiredfool] [wiredfool]
- Prevent `nose -v` printing docstrings #2369 - Prevent ``nose -v`` printing docstrings #2369
[hugovk] [hugovk]
- Replaced absolute PIL imports with relative imports #2349 - Replaced absolute PIL imports with relative imports #2349
@ -1925,7 +2066,7 @@ Changelog (Pillow)
- Changed depends/install_*.sh urls to point to github pillow-depends repo #1983 - Changed depends/install_*.sh urls to point to github pillow-depends repo #1983
[wiredfool] [wiredfool]
- Allow ICC profile from `encoderinfo` while saving PNGs #1909 - Allow ICC profile from ``encoderinfo`` while saving PNGs #1909
[homm] [homm]
- Fix integer overflow on ILP32 systems (32-bit Linux). #1975 - Fix integer overflow on ILP32 systems (32-bit Linux). #1975
@ -2368,7 +2509,7 @@ Changelog (Pillow)
- Added PDF multipage saving #1445 - Added PDF multipage saving #1445
[radarhere] [radarhere]
- Removed deprecated code, Image.tostring, Image.fromstring, Image.offset, ImageDraw.setink, ImageDraw.setfill, ImageFileIO, ImageFont.FreeTypeFont and ImageFont.truetype `file` kwarg, ImagePalette private _make functions, ImageWin.fromstring and ImageWin.tostring #1343 - Removed deprecated code, Image.tostring, Image.fromstring, Image.offset, ImageDraw.setink, ImageDraw.setfill, ImageFileIO, ImageFont.FreeTypeFont and ImageFont.truetype ``file`` kwarg, ImagePalette private _make functions, ImageWin.fromstring and ImageWin.tostring #1343
[radarhere] [radarhere]
- Load more broken images #1428 - Load more broken images #1428
@ -2860,7 +3001,7 @@ Changelog (Pillow)
- Doc cleanup - Doc cleanup
[wiredfool] [wiredfool]
- Fix `ImageStat` docs #796 - Fix ``ImageStat`` docs #796
[akx] [akx]
- Added docs for ExifTags #794 - Added docs for ExifTags #794
@ -3297,7 +3438,7 @@ Changelog (Pillow)
- Add RGBA support to ImageColor #309 - Add RGBA support to ImageColor #309
[yoavweiss] [yoavweiss]
- Test for `str`, not `"utf-8"` #306 (fixes #304) - Test for ``str``, not ``"utf-8"`` #306 (fixes #304)
[mjpieters] [mjpieters]
- Fix missing import os in _util.py #303 - Fix missing import os in _util.py #303
@ -3403,7 +3544,7 @@ Changelog (Pillow)
- Partial work to add a wrapper for WebPGetFeatures to correctly support #220 (fixes #204) - Partial work to add a wrapper for WebPGetFeatures to correctly support #220 (fixes #204)
- Significant performance improvement of `alpha_composite` function #156 - Significant performance improvement of ``alpha_composite`` function #156
[homm] [homm]
- Support explicitly disabling features via --disable-* options #240 - Support explicitly disabling features via --disable-* options #240

View File

@ -3,7 +3,7 @@
.DEFAULT_GOAL := release-test .DEFAULT_GOAL := release-test
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
@ -15,8 +15,8 @@ co:
done done
coverage: coverage:
python selftest.py python3 selftest.py
python setup.py test python3 setup.py test
rm -r htmlcov || true rm -r htmlcov || true
coverage report coverage report
@ -30,7 +30,7 @@ doccheck:
$(MAKE) -C docs linkcheck || true $(MAKE) -C docs linkcheck || true
docserve: docserve:
cd docs/_build/html && python -mSimpleHTTPServer 2> /dev/null& cd docs/_build/html && python3 -mSimpleHTTPServer 2> /dev/null&
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"
@ -50,22 +50,22 @@ help:
@echo " upload-test build and upload sdists to test.pythonpackages.com" @echo " upload-test build and upload sdists to test.pythonpackages.com"
inplace: clean inplace: clean
python setup.py develop build_ext --inplace python3 setup.py develop build_ext --inplace
install: install:
python setup.py install python3 setup.py install
python selftest.py python3 selftest.py
install-coverage: install-coverage:
CFLAGS="-coverage" python setup.py build_ext install CFLAGS="-coverage" python3 setup.py build_ext install
python selftest.py python3 selftest.py
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
install-req: install-req:
pip install -r requirements.txt pip install -r requirements.txt
@ -76,17 +76,17 @@ install-venv:
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 python3 -m pytest -qq
check-manifest check-manifest
pyroma . pyroma .
viewdoc viewdoc
sdist: sdist:
python setup.py sdist --format=gztar python3 setup.py sdist --format=gztar
test: test:
pytest -qq pytest -qq
@ -97,10 +97,10 @@ upload-test:
# username: # username:
# password: # password:
# repository = http://test.pythonpackages.com # repository = http://test.pythonpackages.com
python setup.py sdist --format=gztar upload -r test python3 setup.py sdist --format=gztar upload -r test
upload: upload:
python setup.py sdist --format=gztar upload python3 setup.py sdist --format=gztar upload
readme: readme:
viewdoc viewdoc

View File

@ -14,7 +14,7 @@ Pillow is the friendly PIL fork by `Alex Clark and Contributors <https://github.
* - docs * - docs
- |docs| - |docs|
* - tests * - tests
- |linux| |macos| |windows| |coverage| - |linux| |macos| |windows| |gha_lint| |gha| |gha_windows| |gha_docker| |coverage|
* - package * - package
- |zenodo| |tidelift| |version| |downloads| - |zenodo| |tidelift| |version| |downloads|
* - social * - social
@ -60,6 +60,18 @@ To report a security vulnerability, please follow the procedure described in the
:target: https://ci.appveyor.com/project/python-pillow/Pillow :target: https://ci.appveyor.com/project/python-pillow/Pillow
:alt: AppVeyor CI build status (Windows) :alt: AppVeyor CI build status (Windows)
.. |gha_lint| image:: https://github.com/python-pillow/Pillow/workflows/Lint/badge.svg
:alt: GitHub Actions build status (Lint)
.. |gha_docker| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Docker/badge.svg
:alt: GitHub Actions build status (Test Docker)
.. |gha| image:: https://github.com/python-pillow/Pillow/workflows/Test/badge.svg
:alt: GitHub Actions build status (Test Linux and macOS)
.. |gha_windows| image:: https://github.com/python-pillow/Pillow/workflows/Test%20Windows/badge.svg
:alt: GitHub Actions build status (Test Windows)
.. |coverage| image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg .. |coverage| image:: https://codecov.io/gh/python-pillow/Pillow/branch/master/graph/badge.svg
:target: https://codecov.io/gh/python-pillow/Pillow :target: https://codecov.io/gh/python-pillow/Pillow
:alt: Code coverage :alt: Code coverage
@ -67,7 +79,7 @@ To report a security vulnerability, please follow the procedure described in the
.. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg .. |zenodo| image:: https://zenodo.org/badge/17549/python-pillow/Pillow.svg
:target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow :target: https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow
.. |tidelift| image:: https://tidelift.com/badges/github/python-pillow/Pillow?style=flat .. |tidelift| image:: https://tidelift.com/badges/package/pypi/Pillow?style=flat
:target: https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=referral&utm_campaign=readme :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 .. |version| image:: https://img.shields.io/pypi/v/pillow.svg

View File

@ -51,6 +51,7 @@ Released as needed for security, installation or critical bug fixes.
make sdist make sdist
``` ```
* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions) * [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/master/RELEASING.md#binary-distributions)
* [ ] Upload all binaries and source distributions e.g. `twine upload dist/Pillow-5.2.1*`
* [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new) * [ ] Create a [new release on GitHub](https://github.com/python-pillow/Pillow/releases/new)
## Embargoed Release ## Embargoed Release
@ -92,7 +93,7 @@ Released as needed privately to individual vendors for critical security-related
``` ```
* [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/). * [ ] Download distributions from the [Pillow Wheel Builder container](http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com/).
```bash ```bash
wget -m -A 'Pillow-<VERSION>*' \ wget -m -A 'Pillow-<VERSION>-*' \
http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com http://a365fff413fe338398b6-1c8a9b3114517dc5fe17b7c3f8c63a43.r19.cf2.rackcdn.com
``` ```

View File

@ -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,8 +1,9 @@
import time import time
import unittest
from PIL import PyAccess from PIL import PyAccess
from .helper import PillowTestCase, hopper, unittest from .helper import PillowTestCase, hopper
# Not running this test by default. No DOS against Travis CI. # Not running this test by default. No DOS against Travis CI.

View File

@ -1,23 +0,0 @@
import sys
import timeit
from . import helper
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")

View File

@ -1,6 +1,8 @@
import unittest
from PIL import Image from PIL import Image
from .helper import PillowTestCase, unittest from .helper import PillowTestCase
TEST_FILE = "Tests/images/fli_overflow.fli" TEST_FILE = "Tests/images/fli_overflow.fli"
@ -9,8 +11,8 @@ class TestFliOverflow(PillowTestCase):
def test_fli_overflow(self): 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__": if __name__ == "__main__":

View File

@ -4,9 +4,5 @@
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image
from PIL._util import py3
if py3: Image.open(BytesIO(b"icns\x00\x00\x00\x10hang\x00\x00\x00\x00"))
Image.open(BytesIO(bytes("icns\x00\x00\x00\x10hang\x00\x00\x00\x00", "latin-1")))
else:
Image.open(BytesIO(bytes("icns\x00\x00\x00\x10hang\x00\x00\x00\x00")))

View File

@ -1,18 +1,15 @@
#!/usr/bin/env python #!/usr/bin/env python
import unittest
from __future__ import division
import sys
from PIL import Image from PIL import Image
from .helper import PillowTestCase, unittest from .helper import PillowTestCase, is_win32
min_iterations = 100 min_iterations = 100
max_iterations = 10000 max_iterations = 10000
@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") @unittest.skipIf(is_win32(), "requires Unix or macOS")
class TestImagingLeaks(PillowTestCase): class TestImagingLeaks(PillowTestCase):
def _get_mem_usage(self): def _get_mem_usage(self):
from resource import getpagesize, getrusage, RUSAGE_SELF from resource import getpagesize, getrusage, RUSAGE_SELF

View File

@ -4,19 +4,5 @@
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image
from PIL._util import py3
if py3: Image.open(BytesIO(b"\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang"))
Image.open(
BytesIO(
bytes(
"\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang",
"latin-1",
)
)
)
else:
Image.open(
BytesIO(bytes("\x00\x00\x00\x0cjP\x20\x20\x0d\x0a\x87\x0a\x00\x00\x00\x00hang"))
)

View File

@ -1,9 +1,9 @@
import sys import unittest
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image
from .helper import PillowTestCase, unittest from .helper import PillowTestCase, is_win32
# Limits for testing the leak # Limits for testing the leak
mem_limit = 1024 * 1048576 mem_limit = 1024 * 1048576
@ -13,7 +13,7 @@ codecs = dir(Image.core)
test_file = "Tests/images/rgb_trns_ycbc.jp2" test_file = "Tests/images/rgb_trns_ycbc.jp2"
@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") @unittest.skipIf(is_win32(), "requires Unix or macOS")
class TestJpegLeaks(PillowTestCase): class TestJpegLeaks(PillowTestCase):
def setUp(self): def setUp(self):
if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs: if "jpeg2k_encoder" not in codecs or "jpeg2k_decoder" not in codecs:

View File

@ -1,6 +1,8 @@
import unittest
from PIL import Image from PIL import Image
from .helper import PillowTestCase, unittest from .helper import PillowTestCase
class TestJ2kEncodeOverflow(PillowTestCase): class TestJ2kEncodeOverflow(PillowTestCase):

View File

@ -1,7 +1,7 @@
import sys import unittest
from io import BytesIO from io import BytesIO
from .helper import PillowTestCase, hopper, unittest from .helper import PillowTestCase, hopper, is_win32
iterations = 5000 iterations = 5000
@ -15,7 +15,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") @unittest.skipIf(is_win32(), "requires Unix or macOS")
class TestJpegLeaks(PillowTestCase): class TestJpegLeaks(PillowTestCase):
""" """

View File

@ -1,8 +1,9 @@
import sys import sys
import unittest
from PIL import Image from PIL import Image
from .helper import PillowTestCase, unittest from .helper import PillowTestCase
# This test is not run automatically. # This test is not run automatically.
# #

View File

@ -1,8 +1,9 @@
import sys import sys
import unittest
from PIL import Image from PIL import Image
from .helper import PillowTestCase, unittest from .helper import PillowTestCase
# This test is not run automatically. # This test is not run automatically.
# #

View File

@ -1,6 +1,8 @@
import unittest
from PIL import Image from PIL import Image
from .helper import PillowTestCase, unittest from .helper import PillowTestCase
TEST_FILE = "Tests/images/libtiff_segfault.tif" TEST_FILE = "Tests/images/libtiff_segfault.tif"
@ -12,8 +14,8 @@ class TestLibtiffSegfault(PillowTestCase):
""" """
with self.assertRaises(IOError): with self.assertRaises(IOError):
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
im.load() im.load()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,9 +1,10 @@
import unittest
import zlib import zlib
from io import BytesIO from io import BytesIO
from PIL import Image, ImageFile, PngImagePlugin from PIL import Image, ImageFile, PngImagePlugin
from .helper import PillowTestCase, unittest from .helper import PillowTestCase
TEST_FILE = "Tests/images/png_decompression_dos.png" TEST_FILE = "Tests/images/png_decompression_dos.png"

11
Tests/conftest.py Normal file
View File

@ -0,0 +1,11 @@
def pytest_report_header(config):
import io
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 "pytest_report_header failed: %s" % str(e)

View File

@ -1,6 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import print_function
import base64 import base64
import os import os

View File

@ -1,16 +1,16 @@
""" """
Helper functions. Helper functions.
""" """
from __future__ import print_function
import logging import logging
import os import os
import subprocess
import sys import sys
import tempfile import tempfile
import unittest import unittest
from io import BytesIO
from PIL import Image, ImageMath from PIL import Image, ImageMath
from PIL._util import py3
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -22,12 +22,26 @@ if os.environ.get("SHOW_ERRORS", None):
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
@ -51,37 +65,22 @@ def convert_to_comparable(a, b):
class PillowTestCase(unittest.TestCase): class PillowTestCase(unittest.TestCase):
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): def delete_tempfile(self, path):
try: try:
ok = self.currentResult.wasSuccessful() os.remove(path)
except AttributeError: # for pytest except OSError:
ok = True pass # report?
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): def assert_deep_equal(self, a, b, msg=None):
try: try:
self.assertEqual( self.assertEqual(
len(a), len(b), msg or "got length %s, expected %s" % (len(a), len(b)) len(a),
len(b),
msg or "got length {}, expected {}".format(len(a), len(b)),
) )
self.assertTrue( self.assertTrue(
all(x == y for x, y in zip(a, b)), msg or "got %s, expected %s" % (a, b) all(x == y for x, y in zip(a, b)),
msg or "got {}, expected {}".format(a, b),
) )
except Exception: except Exception:
self.assertEqual(a, b, msg) self.assertEqual(a, b, msg)
@ -89,20 +88,24 @@ class PillowTestCase(unittest.TestCase):
def assert_image(self, im, mode, size, msg=None): def assert_image(self, im, mode, size, msg=None):
if mode is not None: if mode is not None:
self.assertEqual( self.assertEqual(
im.mode, mode, msg or "got mode %r, expected %r" % (im.mode, mode) im.mode,
mode,
msg or "got mode {!r}, expected {!r}".format(im.mode, mode),
) )
if size is not None: if size is not None:
self.assertEqual( self.assertEqual(
im.size, size, msg or "got size %r, expected %r" % (im.size, size) im.size,
size,
msg or "got size {!r}, expected {!r}".format(im.size, size),
) )
def assert_image_equal(self, a, b, msg=None): def assert_image_equal(self, a, b, msg=None):
self.assertEqual( self.assertEqual(
a.mode, b.mode, msg or "got mode %r, expected %r" % (a.mode, b.mode) a.mode, b.mode, msg or "got mode {!r}, expected {!r}".format(a.mode, b.mode)
) )
self.assertEqual( self.assertEqual(
a.size, b.size, msg or "got size %r, expected %r" % (a.size, b.size) a.size, b.size, msg or "got size {!r}, expected {!r}".format(a.size, b.size)
) )
if a.tobytes() != b.tobytes(): if a.tobytes() != b.tobytes():
if HAS_UPLOADER: if HAS_UPLOADER:
@ -123,10 +126,10 @@ class PillowTestCase(unittest.TestCase):
def assert_image_similar(self, a, b, epsilon, msg=None): def assert_image_similar(self, a, b, epsilon, msg=None):
epsilon = float(epsilon) epsilon = float(epsilon)
self.assertEqual( self.assertEqual(
a.mode, b.mode, msg or "got mode %r, expected %r" % (a.mode, b.mode) a.mode, b.mode, msg or "got mode {!r}, expected {!r}".format(a.mode, b.mode)
) )
self.assertEqual( self.assertEqual(
a.size, b.size, msg or "got size %r, expected %r" % (a.size, b.size) a.size, b.size, msg or "got size {!r}, expected {!r}".format(a.size, b.size)
) )
a, b = convert_to_comparable(a, b) a, b = convert_to_comparable(a, b)
@ -200,24 +203,13 @@ class PillowTestCase(unittest.TestCase):
self.assertTrue(value, msg + ": " + repr(actuals) + " != " + repr(targets)) self.assertTrue(value, msg + ": " + repr(actuals) + " != " + repr(targets))
def skipKnownBadTest(self, msg=None, platform=None, travis=None, interpreter=None): def skipKnownBadTest(self, msg=None):
# Skip if platform/travis matches, and # Skip if PILLOW_RUN_KNOWN_BAD is not true in the environment.
# PILLOW_RUN_KNOWN_BAD is not true in the environment.
if os.environ.get("PILLOW_RUN_KNOWN_BAD", False): if os.environ.get("PILLOW_RUN_KNOWN_BAD", False):
print(os.environ.get("PILLOW_RUN_KNOWN_BAD", False)) print(os.environ.get("PILLOW_RUN_KNOWN_BAD", False))
return return
skip = True self.skipTest(msg or "Known Bad Test")
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): def tempfile(self, template):
assert template[:5] in ("temp.", "temp_") assert template[:5] in ("temp.", "temp_")
@ -229,12 +221,12 @@ class PillowTestCase(unittest.TestCase):
def open_withImagemagick(self, f): def open_withImagemagick(self, f):
if not imagemagick_available(): if not imagemagick_available():
raise IOError() raise OSError()
outfile = self.tempfile("temp.png") outfile = self.tempfile("temp.png")
if command_succeeds([IMCONVERT, f, outfile]): if command_succeeds([IMCONVERT, f, outfile]):
return Image.open(outfile) return Image.open(outfile)
raise IOError() raise OSError()
@unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS") @unittest.skipIf(sys.platform.startswith("win32"), "requires Unix or macOS")
@ -277,21 +269,12 @@ class PillowLeakTestCase(PillowTestCase):
# 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()
@ -323,13 +306,10 @@ def command_succeeds(cmd):
Runs the command, which must be a list of strings. Returns True if the 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. command succeeds, or False if an OSError was raised by subprocess.Popen.
""" """
import subprocess try:
subprocess.call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
with open(os.devnull, "wb") as f: except OSError:
try: return False
subprocess.call(cmd, stdout=f, stderr=subprocess.STDOUT)
except OSError:
return False
return True return True
@ -355,6 +335,27 @@ def on_appveyor():
return "APPVEYOR" in os.environ return "APPVEYOR" in os.environ
def on_github_actions():
return "GITHUB_ACTIONS" in os.environ
def on_ci():
# Travis and AppVeyor have "CI"
# Azure Pipelines has "TF_BUILD"
# GitHub Actions has "GITHUB_ACTIONS"
return (
"CI" in os.environ or "TF_BUILD" in os.environ or "GITHUB_ACTIONS" in os.environ
)
def is_win32():
return sys.platform.startswith("win32")
def is_pypy():
return hasattr(sys, "pypy_translation_info")
if sys.platform == "win32": if sys.platform == "win32":
IMCONVERT = os.environ.get("MAGICK_HOME", "") IMCONVERT = os.environ.get("MAGICK_HOME", "")
if IMCONVERT: if IMCONVERT:
@ -371,7 +372,7 @@ def distro():
return line.strip().split("=")[1] return line.strip().split("=")[1]
class cached_property(object): 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.

After

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

BIN
Tests/images/g4_orientation_1.tif Executable file

Binary file not shown.

BIN
Tests/images/g4_orientation_2.tif Executable file

Binary file not shown.

BIN
Tests/images/g4_orientation_3.tif Executable file

Binary file not shown.

BIN
Tests/images/g4_orientation_4.tif Executable file

Binary file not shown.

BIN
Tests/images/g4_orientation_5.tif Executable file

Binary file not shown.

BIN
Tests/images/g4_orientation_6.tif Executable file

Binary file not shown.

BIN
Tests/images/g4_orientation_7.tif Executable file

Binary file not shown.

BIN
Tests/images/g4_orientation_8.tif Executable file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -1,16 +0,0 @@
from __future__ import print_function
import glob
import os
import sys
import traceback
sys.path.insert(0, ".")
for file in glob.glob("src/PIL/*.py"):
module = os.path.basename(file)[:-3]
try:
exec("from PIL import " + module)
except (ImportError, SyntaxError):
print("===", "failed to import", module)
traceback.print_exc()

View File

@ -1,68 +0,0 @@
# brute-force search for access descriptor hash table
from __future__ import print_function
modes = [
"1",
"L",
"LA",
"La",
"I",
"I;16",
"I;16L",
"I;16B",
"I;32L",
"I;32B",
"F",
"P",
"PA",
"RGB",
"RGBA",
"RGBa",
"RGBX",
"CMYK",
"YCbCr",
"LAB",
"HSV",
]
def hash(s, i):
# djb2 hash: multiply by 33 and xor character
for c in s:
i = (((i << 5) + i) ^ ord(c)) & 0xFFFFFFFF
return i
def check(size, i0):
h = [None] * size
for m in modes:
i = hash(m, i0)
i = i % size
if h[i]:
return 0
h[i] = m
return h
min_start = 0
# 1) find the smallest table size with no collisions
for min_size in range(len(modes), 16384):
if check(min_size, 0):
print(len(modes), "modes fit in", min_size, "slots")
break
# 2) see if we can do better with a different initial value
for i0 in range(65556):
for size in range(1, min_size):
if check(size, i0):
if size < min_size:
print(len(modes), "modes fit in", size, "slots with start", i0)
min_size = size
min_start = i0
print()
print("#define ACCESS_TABLE_SIZE", min_size)
print("#define ACCESS_TABLE_HASH", min_start)

View File

@ -1,5 +1,3 @@
from __future__ import print_function
import os import os
from PIL import Image from PIL import Image
@ -24,8 +22,8 @@ class TestBmpReference(PillowTestCase):
def open(f): def open(f):
try: try:
im = Image.open(f) with Image.open(f) as im:
im.load() im.load()
except Exception: # as msg: except Exception: # as msg:
pass pass
@ -48,8 +46,8 @@ class TestBmpReference(PillowTestCase):
] ]
for f in self.get_files("q"): for f in self.get_files("q"):
try: try:
im = Image.open(f) with Image.open(f) as im:
im.load() im.load()
if os.path.basename(f) not in supported: if os.path.basename(f) not in supported:
print("Please add %s to the partially supported bmp specs." % f) print("Please add %s to the partially supported bmp specs." % f)
except Exception: # as msg: except Exception: # as msg:
@ -89,17 +87,17 @@ class TestBmpReference(PillowTestCase):
for f in self.get_files("g"): for f in self.get_files("g"):
try: try:
im = Image.open(f) with Image.open(f) as im:
im.load() im.load()
compare = Image.open(get_compare(f)) with Image.open(get_compare(f)) as compare:
compare.load() compare.load()
if im.mode == "P": if im.mode == "P":
# assert image similar doesn't really work # assert image similar doesn't really work
# with paletized image, since the palette might # with paletized image, since the palette might
# be differently ordered for an equivalent image. # be differently ordered for an equivalent image.
im = im.convert("RGBA") im = im.convert("RGBA")
compare = im.convert("RGBA") compare = im.convert("RGBA")
self.assert_image_similar(im, compare, 5) self.assert_image_similar(im, compare, 5)
except Exception as msg: except Exception as msg:
# there are three here that are unsupported: # there are three here that are unsupported:
@ -109,4 +107,4 @@ class TestBmpReference(PillowTestCase):
os.path.join(base, "g", "pal4rle.bmp"), os.path.join(base, "g", "pal4rle.bmp"),
) )
if f not in unsupported: if f not in unsupported:
self.fail("Unsupported Image %s: %s" % (f, msg)) self.fail("Unsupported Image {}: {}".format(f, msg))

View File

@ -1,10 +1,9 @@
from __future__ import division import unittest
from array import array from array import array
from PIL import Image, ImageFilter from PIL import Image, ImageFilter
from .helper import PillowTestCase, unittest from .helper import PillowTestCase
try: try:
import numpy import numpy

View File

@ -1,12 +1,9 @@
from __future__ import division, print_function
import sys import sys
import unittest
from PIL import Image from PIL import Image
from .helper import PillowTestCase, unittest from .helper import PillowTestCase, is_pypy
is_pypy = hasattr(sys, "pypy_version_info")
class TestCoreStats(PillowTestCase): class TestCoreStats(PillowTestCase):
@ -87,7 +84,7 @@ class TestCoreMemory(PillowTestCase):
stats = Image.core.get_stats() stats = Image.core.get_stats()
self.assertGreaterEqual(stats["new_count"], 1) self.assertGreaterEqual(stats["new_count"], 1)
self.assertGreaterEqual(stats["allocated_blocks"], 64) self.assertGreaterEqual(stats["allocated_blocks"], 64)
if not is_pypy: if not is_pypy():
self.assertGreaterEqual(stats["freed_blocks"], 64) self.assertGreaterEqual(stats["freed_blocks"], 64)
def test_get_blocks_max(self): def test_get_blocks_max(self):
@ -108,7 +105,7 @@ class TestCoreMemory(PillowTestCase):
if sys.maxsize < 2 ** 32: if sys.maxsize < 2 ** 32:
self.assertRaises(ValueError, Image.core.set_blocks_max, 2 ** 29) self.assertRaises(ValueError, Image.core.set_blocks_max, 2 ** 29)
@unittest.skipIf(is_pypy, "images are not collected") @unittest.skipIf(is_pypy(), "images are not collected")
def test_set_blocks_max_stats(self): def test_set_blocks_max_stats(self):
Image.core.reset_stats() Image.core.reset_stats()
Image.core.set_blocks_max(128) Image.core.set_blocks_max(128)
@ -123,7 +120,7 @@ class TestCoreMemory(PillowTestCase):
self.assertEqual(stats["freed_blocks"], 0) self.assertEqual(stats["freed_blocks"], 0)
self.assertEqual(stats["blocks_cached"], 64) self.assertEqual(stats["blocks_cached"], 64)
@unittest.skipIf(is_pypy, "images are not collected") @unittest.skipIf(is_pypy(), "images are not collected")
def test_clear_cache_stats(self): def test_clear_cache_stats(self):
Image.core.reset_stats() Image.core.reset_stats()
Image.core.clear_cache() Image.core.clear_cache()
@ -153,7 +150,7 @@ class TestCoreMemory(PillowTestCase):
self.assertGreaterEqual(stats["allocated_blocks"], 16) self.assertGreaterEqual(stats["allocated_blocks"], 16)
self.assertGreaterEqual(stats["reused_blocks"], 0) self.assertGreaterEqual(stats["reused_blocks"], 0)
self.assertEqual(stats["blocks_cached"], 0) self.assertEqual(stats["blocks_cached"], 0)
if not is_pypy: if not is_pypy():
self.assertGreaterEqual(stats["freed_blocks"], 16) self.assertGreaterEqual(stats["freed_blocks"], 16)

View File

@ -14,7 +14,8 @@ class TestDecompressionBomb(PillowTestCase):
def test_no_warning_small_file(self): def test_no_warning_small_file(self):
# Implicit assert: no warning. # Implicit assert: no warning.
# A warning would cause a failure. # A warning would cause a failure.
Image.open(TEST_FILE) with Image.open(TEST_FILE):
pass
def test_no_warning_no_limit(self): def test_no_warning_no_limit(self):
# Arrange # Arrange
@ -25,26 +26,42 @@ class TestDecompressionBomb(PillowTestCase):
# Act / Assert # Act / Assert
# Implicit assert: no warning. # Implicit assert: no warning.
# A warning would cause a failure. # A warning would cause a failure.
Image.open(TEST_FILE) with Image.open(TEST_FILE):
pass
def test_warning(self): def test_warning(self):
# Set limit to trigger warning on the test file # Set limit to trigger warning on the test file
Image.MAX_IMAGE_PIXELS = 128 * 128 - 1 Image.MAX_IMAGE_PIXELS = 128 * 128 - 1
self.assertEqual(Image.MAX_IMAGE_PIXELS, 128 * 128 - 1) self.assertEqual(Image.MAX_IMAGE_PIXELS, 128 * 128 - 1)
self.assert_warning(Image.DecompressionBombWarning, Image.open, TEST_FILE) def open():
with Image.open(TEST_FILE):
pass
self.assert_warning(Image.DecompressionBombWarning, open)
def test_exception(self): def test_exception(self):
# Set limit to trigger exception on the test file # Set limit to trigger exception on the test file
Image.MAX_IMAGE_PIXELS = 64 * 128 - 1 Image.MAX_IMAGE_PIXELS = 64 * 128 - 1
self.assertEqual(Image.MAX_IMAGE_PIXELS, 64 * 128 - 1) self.assertEqual(Image.MAX_IMAGE_PIXELS, 64 * 128 - 1)
self.assertRaises(Image.DecompressionBombError, lambda: Image.open(TEST_FILE)) with self.assertRaises(Image.DecompressionBombError):
with Image.open(TEST_FILE):
pass
def test_exception_ico(self):
with self.assertRaises(Image.DecompressionBombError):
Image.open("Tests/images/decompression_bomb.ico")
def test_exception_gif(self):
with self.assertRaises(Image.DecompressionBombError):
Image.open("Tests/images/decompression_bomb.gif")
class TestDecompressionCrop(PillowTestCase): class TestDecompressionCrop(PillowTestCase):
def setUp(self): def setUp(self):
self.src = hopper() self.src = hopper()
self.addCleanup(self.src.close)
Image.MAX_IMAGE_PIXELS = self.src.height * self.src.width * 4 - 1 Image.MAX_IMAGE_PIXELS = self.src.height * self.src.width * 4 - 1
def tearDown(self): def tearDown(self):

View File

@ -1,10 +1,9 @@
from __future__ import unicode_literals
import io import io
import unittest
from PIL import features from PIL import features
from .helper import PillowTestCase, unittest from .helper import PillowTestCase
try: try:
from PIL import _webp from PIL import _webp
@ -70,11 +69,14 @@ class TestFeatures(PillowTestCase):
lines = out.splitlines() lines = out.splitlines()
self.assertEqual(lines[0], "-" * 68) self.assertEqual(lines[0], "-" * 68)
self.assertTrue(lines[1].startswith("Pillow ")) self.assertTrue(lines[1].startswith("Pillow "))
self.assertEqual(lines[2], "-" * 68) self.assertTrue(lines[2].startswith("Python "))
self.assertTrue(lines[3].startswith("Python modules loaded from ")) lines = lines[3:]
self.assertTrue(lines[4].startswith("Binary modules loaded from ")) while lines[0].startswith(" "):
self.assertEqual(lines[5], "-" * 68) lines = lines[1:]
self.assertTrue(lines[6].startswith("Python ")) self.assertEqual(lines[0], "-" * 68)
self.assertTrue(lines[1].startswith("Python modules loaded from "))
self.assertTrue(lines[2].startswith("Binary modules loaded from "))
self.assertEqual(lines[3], "-" * 68)
jpeg = ( jpeg = (
"\n" "\n"
+ "-" * 68 + "-" * 68

View File

@ -5,16 +5,16 @@ from .helper import PillowTestCase
class TestFileBlp(PillowTestCase): class TestFileBlp(PillowTestCase):
def test_load_blp2_raw(self): def test_load_blp2_raw(self):
im = Image.open("Tests/images/blp/blp2_raw.blp") with Image.open("Tests/images/blp/blp2_raw.blp") as im:
target = Image.open("Tests/images/blp/blp2_raw.png") with Image.open("Tests/images/blp/blp2_raw.png") as target:
self.assert_image_equal(im, target) self.assert_image_equal(im, target)
def test_load_blp2_dxt1(self): def test_load_blp2_dxt1(self):
im = Image.open("Tests/images/blp/blp2_dxt1.blp") with Image.open("Tests/images/blp/blp2_dxt1.blp") as im:
target = Image.open("Tests/images/blp/blp2_dxt1.png") with Image.open("Tests/images/blp/blp2_dxt1.png") as target:
self.assert_image_equal(im, target) self.assert_image_equal(im, target)
def test_load_blp2_dxt1a(self): def test_load_blp2_dxt1a(self):
im = Image.open("Tests/images/blp/blp2_dxt1a.blp") with Image.open("Tests/images/blp/blp2_dxt1a.blp") as im:
target = Image.open("Tests/images/blp/blp2_dxt1a.png") with Image.open("Tests/images/blp/blp2_dxt1a.png") as target:
self.assert_image_equal(im, target) self.assert_image_equal(im, target)

View File

@ -11,12 +11,12 @@ class TestFileBmp(PillowTestCase):
im.save(outfile, "BMP") im.save(outfile, "BMP")
reloaded = Image.open(outfile) with Image.open(outfile) as reloaded:
reloaded.load() reloaded.load()
self.assertEqual(im.mode, reloaded.mode) self.assertEqual(im.mode, reloaded.mode)
self.assertEqual(im.size, reloaded.size) self.assertEqual(im.size, reloaded.size)
self.assertEqual(reloaded.format, "BMP") self.assertEqual(reloaded.format, "BMP")
self.assertEqual(reloaded.get_format_mimetype(), "image/bmp") self.assertEqual(reloaded.get_format_mimetype(), "image/bmp")
def test_sanity(self): def test_sanity(self):
self.roundtrip(hopper()) self.roundtrip(hopper())
@ -36,89 +36,93 @@ class TestFileBmp(PillowTestCase):
im.save(output, "BMP") im.save(output, "BMP")
output.seek(0) output.seek(0)
reloaded = Image.open(output) with Image.open(output) as reloaded:
self.assertEqual(im.mode, reloaded.mode)
self.assertEqual(im.size, reloaded.size)
self.assertEqual(reloaded.format, "BMP")
self.assertEqual(im.mode, reloaded.mode) def test_save_too_large(self):
self.assertEqual(im.size, reloaded.size) outfile = self.tempfile("temp.bmp")
self.assertEqual(reloaded.format, "BMP") with Image.new("RGB", (1, 1)) as im:
im._size = (37838, 37838)
with self.assertRaises(ValueError):
im.save(outfile)
def test_dpi(self): def test_dpi(self):
dpi = (72, 72) dpi = (72, 72)
output = io.BytesIO() output = io.BytesIO()
im = hopper() with hopper() as im:
im.save(output, "BMP", dpi=dpi) im.save(output, "BMP", dpi=dpi)
output.seek(0) output.seek(0)
reloaded = Image.open(output) with Image.open(output) as reloaded:
self.assertEqual(reloaded.info["dpi"], dpi)
self.assertEqual(reloaded.info["dpi"], dpi)
def test_save_bmp_with_dpi(self): def test_save_bmp_with_dpi(self):
# Test for #1301 # Test for #1301
# Arrange # Arrange
outfile = self.tempfile("temp.jpg") outfile = self.tempfile("temp.jpg")
im = Image.open("Tests/images/hopper.bmp") with Image.open("Tests/images/hopper.bmp") as im:
# Act # Act
im.save(outfile, "JPEG", dpi=im.info["dpi"]) im.save(outfile, "JPEG", dpi=im.info["dpi"])
# Assert # Assert
reloaded = Image.open(outfile) with Image.open(outfile) as reloaded:
reloaded.load() reloaded.load()
self.assertEqual(im.info["dpi"], reloaded.info["dpi"]) self.assertEqual(im.info["dpi"], reloaded.info["dpi"])
self.assertEqual(im.size, reloaded.size) self.assertEqual(im.size, reloaded.size)
self.assertEqual(reloaded.format, "JPEG") self.assertEqual(reloaded.format, "JPEG")
def test_load_dpi_rounding(self): def test_load_dpi_rounding(self):
# Round up # Round up
im = Image.open("Tests/images/hopper.bmp") with Image.open("Tests/images/hopper.bmp") as im:
self.assertEqual(im.info["dpi"], (96, 96)) self.assertEqual(im.info["dpi"], (96, 96))
# Round down # Round down
im = Image.open("Tests/images/hopper_roundDown.bmp") with Image.open("Tests/images/hopper_roundDown.bmp") as im:
self.assertEqual(im.info["dpi"], (72, 72)) self.assertEqual(im.info["dpi"], (72, 72))
def test_save_dpi_rounding(self): def test_save_dpi_rounding(self):
outfile = self.tempfile("temp.bmp") outfile = self.tempfile("temp.bmp")
im = Image.open("Tests/images/hopper.bmp") with Image.open("Tests/images/hopper.bmp") as im:
im.save(outfile, dpi=(72.2, 72.2))
with Image.open(outfile) as reloaded:
self.assertEqual(reloaded.info["dpi"], (72, 72))
im.save(outfile, dpi=(72.2, 72.2)) im.save(outfile, dpi=(72.8, 72.8))
reloaded = Image.open(outfile) with Image.open(outfile) as reloaded:
self.assertEqual(reloaded.info["dpi"], (72, 72)) self.assertEqual(reloaded.info["dpi"], (73, 73))
im.save(outfile, dpi=(72.8, 72.8))
reloaded = Image.open(outfile)
self.assertEqual(reloaded.info["dpi"], (73, 73))
def test_load_dib(self): def test_load_dib(self):
# test for #1293, Imagegrab returning Unsupported Bitfields Format # test for #1293, Imagegrab returning Unsupported Bitfields Format
im = Image.open("Tests/images/clipboard.dib") with Image.open("Tests/images/clipboard.dib") as im:
self.assertEqual(im.format, "DIB") self.assertEqual(im.format, "DIB")
self.assertEqual(im.get_format_mimetype(), "image/bmp") self.assertEqual(im.get_format_mimetype(), "image/bmp")
target = Image.open("Tests/images/clipboard_target.png") with Image.open("Tests/images/clipboard_target.png") as target:
self.assert_image_equal(im, target) self.assert_image_equal(im, target)
def test_save_dib(self): def test_save_dib(self):
outfile = self.tempfile("temp.dib") outfile = self.tempfile("temp.dib")
im = Image.open("Tests/images/clipboard.dib") with Image.open("Tests/images/clipboard.dib") as im:
im.save(outfile) im.save(outfile)
reloaded = Image.open(outfile) with Image.open(outfile) as reloaded:
self.assertEqual(reloaded.format, "DIB") self.assertEqual(reloaded.format, "DIB")
self.assertEqual(reloaded.get_format_mimetype(), "image/bmp") self.assertEqual(reloaded.get_format_mimetype(), "image/bmp")
self.assert_image_equal(im, reloaded) self.assert_image_equal(im, reloaded)
def test_rgba_bitfields(self): def test_rgba_bitfields(self):
# This test image has been manually hexedited # This test image has been manually hexedited
# to change the bitfield compression in the header from XBGR to RGBA # to change the bitfield compression in the header from XBGR to RGBA
im = Image.open("Tests/images/rgb32bf-rgba.bmp") with Image.open("Tests/images/rgb32bf-rgba.bmp") as im:
# So before the comparing the image, swap the channels # So before the comparing the image, swap the channels
b, g, r = im.split()[1:] b, g, r = im.split()[1:]
im = Image.merge("RGB", (r, g, b)) im = Image.merge("RGB", (r, g, b))
target = Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp") with Image.open("Tests/images/bmp/q/rgb32bf-xbgr.bmp") as target:
self.assert_image_equal(im, target) self.assert_image_equal(im, target)

View File

@ -8,14 +8,14 @@ TEST_FILE = "Tests/images/gfs.t06z.rassda.tm00.bufr_d"
class TestFileBufrStub(PillowTestCase): class TestFileBufrStub(PillowTestCase):
def test_open(self): def test_open(self):
# Act # Act
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
# Assert # Assert
self.assertEqual(im.format, "BUFR") self.assertEqual(im.format, "BUFR")
# Dummy data from the stub # Dummy data from the stub
self.assertEqual(im.mode, "F") self.assertEqual(im.mode, "F")
self.assertEqual(im.size, (1, 1)) self.assertEqual(im.size, (1, 1))
def test_invalid_file(self): def test_invalid_file(self):
# Arrange # Arrange
@ -28,10 +28,10 @@ class TestFileBufrStub(PillowTestCase):
def test_load(self): def test_load(self):
# Arrange # Arrange
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler # Act / Assert: stub cannot load without an implemented handler
self.assertRaises(IOError, im.load) self.assertRaises(IOError, im.load)
def test_save(self): def test_save(self):
# Arrange # Arrange

View File

@ -11,8 +11,8 @@ class TestFileContainer(PillowTestCase):
dir(ContainerIO) dir(ContainerIO)
def test_isatty(self): def test_isatty(self):
im = hopper() with hopper() as im:
container = ContainerIO.ContainerIO(im, 0, 0) container = ContainerIO.ContainerIO(im, 0, 0)
self.assertFalse(container.isatty()) self.assertFalse(container.isatty())

View File

@ -7,14 +7,13 @@ TEST_FILE = "Tests/images/deerstalker.cur"
class TestFileCur(PillowTestCase): class TestFileCur(PillowTestCase):
def test_sanity(self): def test_sanity(self):
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
self.assertEqual(im.size, (32, 32))
self.assertEqual(im.size, (32, 32)) self.assertIsInstance(im, CurImagePlugin.CurImageFile)
self.assertIsInstance(im, CurImagePlugin.CurImageFile) # Check some pixel colors to ensure image is loaded properly
# Check some pixel colors to ensure image is loaded properly self.assertEqual(im.getpixel((10, 1)), (0, 0, 0, 0))
self.assertEqual(im.getpixel((10, 1)), (0, 0, 0, 0)) self.assertEqual(im.getpixel((11, 1)), (253, 254, 254, 1))
self.assertEqual(im.getpixel((11, 1)), (253, 254, 254, 1)) self.assertEqual(im.getpixel((16, 16)), (84, 87, 86, 255))
self.assertEqual(im.getpixel((16, 16)), (84, 87, 86, 255))
def test_invalid_file(self): def test_invalid_file(self):
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"

View File

@ -1,6 +1,8 @@
import unittest
from PIL import DcxImagePlugin, Image from PIL import DcxImagePlugin, Image
from .helper import PillowTestCase, hopper from .helper import PillowTestCase, hopper, is_pypy
# Created with ImageMagick: convert hopper.ppm hopper.dcx # Created with ImageMagick: convert hopper.ppm hopper.dcx
TEST_FILE = "Tests/images/hopper.dcx" TEST_FILE = "Tests/images/hopper.dcx"
@ -11,19 +13,35 @@ class TestFileDcx(PillowTestCase):
# Arrange # Arrange
# Act # Act
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
# Assert # Assert
self.assertEqual(im.size, (128, 128)) self.assertEqual(im.size, (128, 128))
self.assertIsInstance(im, DcxImagePlugin.DcxImageFile) self.assertIsInstance(im, DcxImagePlugin.DcxImageFile)
orig = hopper() orig = hopper()
self.assert_image_equal(im, orig) self.assert_image_equal(im, orig)
@unittest.skipIf(is_pypy(), "Requires CPython")
def test_unclosed_file(self): def test_unclosed_file(self):
def open(): def open():
im = Image.open(TEST_FILE) im = Image.open(TEST_FILE)
im.load() im.load()
self.assert_warning(ResourceWarning, open)
def test_closed_file(self):
def open():
im = Image.open(TEST_FILE)
im.load()
im.close()
self.assert_warning(None, open)
def test_context_manager(self):
def open():
with Image.open(TEST_FILE) as im:
im.load()
self.assert_warning(None, open) self.assert_warning(None, open)
def test_invalid_file(self): def test_invalid_file(self):
@ -32,34 +50,34 @@ class TestFileDcx(PillowTestCase):
def test_tell(self): def test_tell(self):
# Arrange # Arrange
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
# Act # Act
frame = im.tell() frame = im.tell()
# Assert # Assert
self.assertEqual(frame, 0) self.assertEqual(frame, 0)
def test_n_frames(self): def test_n_frames(self):
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
self.assertEqual(im.n_frames, 1) self.assertEqual(im.n_frames, 1)
self.assertFalse(im.is_animated) self.assertFalse(im.is_animated)
def test_eoferror(self): def test_eoferror(self):
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
n_frames = im.n_frames n_frames = im.n_frames
# Test seeking past the last frame # Test seeking past the last frame
self.assertRaises(EOFError, im.seek, n_frames) self.assertRaises(EOFError, im.seek, n_frames)
self.assertLess(im.tell(), n_frames) self.assertLess(im.tell(), n_frames)
# Test that seeking to the last frame does not raise an error # Test that seeking to the last frame does not raise an error
im.seek(n_frames - 1) im.seek(n_frames - 1)
def test_seek_too_far(self): def test_seek_too_far(self):
# Arrange # Arrange
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
frame = 999 # too big on purpose frame = 999 # too big on purpose
# Act / Assert # Act / Assert
self.assertRaises(EOFError, im.seek, frame) self.assertRaises(EOFError, im.seek, frame)

View File

@ -8,6 +8,7 @@ TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds" TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds"
TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds" TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds"
TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/uncompressed_rgb.dds" TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/uncompressed_rgb.dds"
@ -16,58 +17,71 @@ class TestFileDds(PillowTestCase):
def test_sanity_dxt1(self): def test_sanity_dxt1(self):
"""Check DXT1 images can be opened""" """Check DXT1 images can be opened"""
target = Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target:
target = target.convert("RGBA")
with Image.open(TEST_FILE_DXT1) as im:
im.load()
im = Image.open(TEST_FILE_DXT1) self.assertEqual(im.format, "DDS")
im.load() self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (256, 256))
self.assertEqual(im.format, "DDS") self.assert_image_equal(im, target)
self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (256, 256))
self.assert_image_equal(target.convert("RGBA"), im)
def test_sanity_dxt5(self): def test_sanity_dxt5(self):
"""Check DXT5 images can be opened""" """Check DXT5 images can be opened"""
target = Image.open(TEST_FILE_DXT5.replace(".dds", ".png")) with Image.open(TEST_FILE_DXT5) as im:
im.load()
im = Image.open(TEST_FILE_DXT5)
im.load()
self.assertEqual(im.format, "DDS") self.assertEqual(im.format, "DDS")
self.assertEqual(im.mode, "RGBA") self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (256, 256)) self.assertEqual(im.size, (256, 256))
self.assert_image_equal(target, im) with Image.open(TEST_FILE_DXT5.replace(".dds", ".png")) as target:
self.assert_image_equal(target, im)
def test_sanity_dxt3(self): def test_sanity_dxt3(self):
"""Check DXT3 images can be opened""" """Check DXT3 images can be opened"""
target = Image.open(TEST_FILE_DXT3.replace(".dds", ".png")) with Image.open(TEST_FILE_DXT3.replace(".dds", ".png")) as target:
with Image.open(TEST_FILE_DXT3) as im:
im.load()
im = Image.open(TEST_FILE_DXT3) self.assertEqual(im.format, "DDS")
im.load() self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (256, 256))
self.assertEqual(im.format, "DDS") self.assert_image_equal(target, im)
self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (256, 256))
self.assert_image_equal(target, im)
def test_dx10_bc7(self): def test_dx10_bc7(self):
"""Check DX10 images can be opened""" """Check DX10 images can be opened"""
target = Image.open(TEST_FILE_DX10_BC7.replace(".dds", ".png")) with Image.open(TEST_FILE_DX10_BC7) as im:
im.load()
im = Image.open(TEST_FILE_DX10_BC7) self.assertEqual(im.format, "DDS")
im.load() self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (256, 256))
self.assertEqual(im.format, "DDS") with Image.open(TEST_FILE_DX10_BC7.replace(".dds", ".png")) as target:
self.assertEqual(im.mode, "RGBA") self.assert_image_equal(target, im)
self.assertEqual(im.size, (256, 256))
self.assert_image_equal(target, im) def test_dx10_bc7_unorm_srgb(self):
"""Check DX10 unsigned normalized integer images can be opened"""
with Image.open(TEST_FILE_DX10_BC7_UNORM_SRGB) as im:
im.load()
self.assertEqual(im.format, "DDS")
self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (16, 16))
self.assertEqual(im.info["gamma"], 1 / 2.2)
with Image.open(
TEST_FILE_DX10_BC7_UNORM_SRGB.replace(".dds", ".png")
) as target:
self.assert_image_equal(target, im)
def test_unimplemented_dxgi_format(self): def test_unimplemented_dxgi_format(self):
self.assertRaises( self.assertRaises(
@ -79,16 +93,17 @@ class TestFileDds(PillowTestCase):
def test_uncompressed_rgb(self): def test_uncompressed_rgb(self):
"""Check uncompressed RGB images can be opened""" """Check uncompressed RGB images can be opened"""
target = Image.open(TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png")) with Image.open(TEST_FILE_UNCOMPRESSED_RGB) as im:
im.load()
im = Image.open(TEST_FILE_UNCOMPRESSED_RGB) self.assertEqual(im.format, "DDS")
im.load() self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (800, 600))
self.assertEqual(im.format, "DDS") with Image.open(
self.assertEqual(im.mode, "RGBA") TEST_FILE_UNCOMPRESSED_RGB.replace(".dds", ".png")
self.assertEqual(im.size, (800, 600)) ) as target:
self.assert_image_equal(target, im)
self.assert_image_equal(target, im)
def test__validate_true(self): def test__validate_true(self):
"""Check valid prefix""" """Check valid prefix"""
@ -129,8 +144,8 @@ class TestFileDds(PillowTestCase):
img_file = f.read() img_file = f.read()
def short_file(): def short_file():
im = Image.open(BytesIO(img_file[:-100])) with Image.open(BytesIO(img_file[:-100])) as im:
im.load() im.load()
self.assertRaises(IOError, short_file) self.assertRaises(IOError, short_file)

View File

@ -1,8 +1,9 @@
import io import io
import unittest
from PIL import EpsImagePlugin, Image from PIL import EpsImagePlugin, Image
from .helper import PillowTestCase, hopper, unittest from .helper import PillowTestCase, hopper
HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript() HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript()
@ -25,30 +26,30 @@ class TestFileEps(PillowTestCase):
@unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available")
def test_sanity(self): def test_sanity(self):
# Regular scale # Regular scale
image1 = Image.open(file1) with Image.open(file1) as image1:
image1.load() image1.load()
self.assertEqual(image1.mode, "RGB") self.assertEqual(image1.mode, "RGB")
self.assertEqual(image1.size, (460, 352)) self.assertEqual(image1.size, (460, 352))
self.assertEqual(image1.format, "EPS") self.assertEqual(image1.format, "EPS")
image2 = Image.open(file2) with Image.open(file2) as image2:
image2.load() image2.load()
self.assertEqual(image2.mode, "RGB") self.assertEqual(image2.mode, "RGB")
self.assertEqual(image2.size, (360, 252)) self.assertEqual(image2.size, (360, 252))
self.assertEqual(image2.format, "EPS") self.assertEqual(image2.format, "EPS")
# Double scale # Double scale
image1_scale2 = Image.open(file1) with Image.open(file1) as image1_scale2:
image1_scale2.load(scale=2) image1_scale2.load(scale=2)
self.assertEqual(image1_scale2.mode, "RGB") self.assertEqual(image1_scale2.mode, "RGB")
self.assertEqual(image1_scale2.size, (920, 704)) self.assertEqual(image1_scale2.size, (920, 704))
self.assertEqual(image1_scale2.format, "EPS") self.assertEqual(image1_scale2.format, "EPS")
image2_scale2 = Image.open(file2) with Image.open(file2) as image2_scale2:
image2_scale2.load(scale=2) image2_scale2.load(scale=2)
self.assertEqual(image2_scale2.mode, "RGB") self.assertEqual(image2_scale2.mode, "RGB")
self.assertEqual(image2_scale2.size, (720, 504)) self.assertEqual(image2_scale2.size, (720, 504))
self.assertEqual(image2_scale2.format, "EPS") self.assertEqual(image2_scale2.format, "EPS")
def test_invalid_file(self): def test_invalid_file(self):
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
@ -57,55 +58,55 @@ class TestFileEps(PillowTestCase):
@unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available")
def test_cmyk(self): def test_cmyk(self):
cmyk_image = Image.open("Tests/images/pil_sample_cmyk.eps") with Image.open("Tests/images/pil_sample_cmyk.eps") as cmyk_image:
self.assertEqual(cmyk_image.mode, "CMYK") self.assertEqual(cmyk_image.mode, "CMYK")
self.assertEqual(cmyk_image.size, (100, 100)) self.assertEqual(cmyk_image.size, (100, 100))
self.assertEqual(cmyk_image.format, "EPS") self.assertEqual(cmyk_image.format, "EPS")
cmyk_image.load() cmyk_image.load()
self.assertEqual(cmyk_image.mode, "RGB") self.assertEqual(cmyk_image.mode, "RGB")
if "jpeg_decoder" in dir(Image.core): if "jpeg_decoder" in dir(Image.core):
target = Image.open("Tests/images/pil_sample_rgb.jpg") with Image.open("Tests/images/pil_sample_rgb.jpg") as target:
self.assert_image_similar(cmyk_image, target, 10) self.assert_image_similar(cmyk_image, target, 10)
@unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available")
def test_showpage(self): def test_showpage(self):
# See https://github.com/python-pillow/Pillow/issues/2615 # See https://github.com/python-pillow/Pillow/issues/2615
plot_image = Image.open("Tests/images/reqd_showpage.eps") with Image.open("Tests/images/reqd_showpage.eps") as plot_image:
target = Image.open("Tests/images/reqd_showpage.png") with Image.open("Tests/images/reqd_showpage.png") as target:
# should not crash/hang
# should not crash/hang plot_image.load()
plot_image.load() # fonts could be slightly different
# fonts could be slightly different self.assert_image_similar(plot_image, target, 6)
self.assert_image_similar(plot_image, target, 6)
@unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available")
def test_file_object(self): def test_file_object(self):
# issue 479 # issue 479
image1 = Image.open(file1) with Image.open(file1) as image1:
with open(self.tempfile("temp_file.eps"), "wb") as fh: with open(self.tempfile("temp_file.eps"), "wb") as fh:
image1.save(fh, "EPS") image1.save(fh, "EPS")
@unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available")
def test_iobase_object(self): def test_iobase_object(self):
# issue 479 # issue 479
image1 = Image.open(file1) with Image.open(file1) as image1:
with io.open(self.tempfile("temp_iobase.eps"), "wb") as fh: with open(self.tempfile("temp_iobase.eps"), "wb") as fh:
image1.save(fh, "EPS") image1.save(fh, "EPS")
@unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available")
def test_bytesio_object(self): def test_bytesio_object(self):
with open(file1, "rb") as f: with open(file1, "rb") as f:
img_bytes = io.BytesIO(f.read()) img_bytes = io.BytesIO(f.read())
img = Image.open(img_bytes) with Image.open(img_bytes) as img:
img.load() img.load()
image1_scale1_compare = Image.open(file1_compare).convert("RGB") with Image.open(file1_compare) as image1_scale1_compare:
image1_scale1_compare.load() image1_scale1_compare = image1_scale1_compare.convert("RGB")
self.assert_image_similar(img, image1_scale1_compare, 5) image1_scale1_compare.load()
self.assert_image_similar(img, image1_scale1_compare, 5)
def test_image_mode_not_supported(self): def test_image_mode_not_supported(self):
im = hopper("RGBA") im = hopper("RGBA")
@ -120,18 +121,20 @@ class TestFileEps(PillowTestCase):
self.skipTest("zip/deflate support not available") self.skipTest("zip/deflate support not available")
# Zero bounding box # Zero bounding box
image1_scale1 = Image.open(file1) with Image.open(file1) as image1_scale1:
image1_scale1.load() image1_scale1.load()
image1_scale1_compare = Image.open(file1_compare).convert("RGB") with Image.open(file1_compare) as image1_scale1_compare:
image1_scale1_compare.load() image1_scale1_compare = image1_scale1_compare.convert("RGB")
self.assert_image_similar(image1_scale1, image1_scale1_compare, 5) image1_scale1_compare.load()
self.assert_image_similar(image1_scale1, image1_scale1_compare, 5)
# Non-Zero bounding box # Non-Zero bounding box
image2_scale1 = Image.open(file2) with Image.open(file2) as image2_scale1:
image2_scale1.load() image2_scale1.load()
image2_scale1_compare = Image.open(file2_compare).convert("RGB") with Image.open(file2_compare) as image2_scale1_compare:
image2_scale1_compare.load() image2_scale1_compare = image2_scale1_compare.convert("RGB")
self.assert_image_similar(image2_scale1, image2_scale1_compare, 10) image2_scale1_compare.load()
self.assert_image_similar(image2_scale1, image2_scale1_compare, 10)
@unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available")
def test_render_scale2(self): def test_render_scale2(self):
@ -141,57 +144,46 @@ class TestFileEps(PillowTestCase):
self.skipTest("zip/deflate support not available") self.skipTest("zip/deflate support not available")
# Zero bounding box # Zero bounding box
image1_scale2 = Image.open(file1) with Image.open(file1) as image1_scale2:
image1_scale2.load(scale=2) image1_scale2.load(scale=2)
image1_scale2_compare = Image.open(file1_compare_scale2).convert("RGB") with Image.open(file1_compare_scale2) as image1_scale2_compare:
image1_scale2_compare.load() image1_scale2_compare = image1_scale2_compare.convert("RGB")
self.assert_image_similar(image1_scale2, image1_scale2_compare, 5) image1_scale2_compare.load()
self.assert_image_similar(image1_scale2, image1_scale2_compare, 5)
# Non-Zero bounding box # Non-Zero bounding box
image2_scale2 = Image.open(file2) with Image.open(file2) as image2_scale2:
image2_scale2.load(scale=2) image2_scale2.load(scale=2)
image2_scale2_compare = Image.open(file2_compare_scale2).convert("RGB") with Image.open(file2_compare_scale2) as image2_scale2_compare:
image2_scale2_compare.load() image2_scale2_compare = image2_scale2_compare.convert("RGB")
self.assert_image_similar(image2_scale2, image2_scale2_compare, 10) image2_scale2_compare.load()
self.assert_image_similar(image2_scale2, image2_scale2_compare, 10)
@unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available")
def test_resize(self): def test_resize(self):
# Arrange files = [file1, file2, "Tests/images/illu10_preview.eps"]
image1 = Image.open(file1) for fn in files:
image2 = Image.open(file2) with Image.open(fn) as im:
image3 = Image.open("Tests/images/illu10_preview.eps") new_size = (100, 100)
new_size = (100, 100) im = im.resize(new_size)
self.assertEqual(im.size, new_size)
# Act
image1 = image1.resize(new_size)
image2 = image2.resize(new_size)
image3 = image3.resize(new_size)
# Assert
self.assertEqual(image1.size, new_size)
self.assertEqual(image2.size, new_size)
self.assertEqual(image3.size, new_size)
@unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available")
def test_thumbnail(self): def test_thumbnail(self):
# Issue #619 # Issue #619
# Arrange # Arrange
image1 = Image.open(file1) files = [file1, file2]
image2 = Image.open(file2) for fn in files:
new_size = (100, 100) with Image.open(file1) as im:
new_size = (100, 100)
# Act im.thumbnail(new_size)
image1.thumbnail(new_size) self.assertEqual(max(im.size), max(new_size))
image2.thumbnail(new_size)
# Assert
self.assertEqual(max(image1.size), max(new_size))
self.assertEqual(max(image2.size), max(new_size))
def test_read_binary_preview(self): def test_read_binary_preview(self):
# Issue 302 # Issue 302
# open image with binary preview # open image with binary preview
Image.open(file3) with Image.open(file3):
pass
def _test_readline(self, t, ending): def _test_readline(self, t, ending):
ending = "Failure with line ending: %s" % ( ending = "Failure with line ending: %s" % (
@ -239,16 +231,16 @@ class TestFileEps(PillowTestCase):
# Act / Assert # Act / Assert
for filename in FILES: for filename in FILES:
img = Image.open(filename) with Image.open(filename) as img:
self.assertEqual(img.mode, "RGB") self.assertEqual(img.mode, "RGB")
@unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available") @unittest.skipUnless(HAS_GHOSTSCRIPT, "Ghostscript not available")
def test_emptyline(self): def test_emptyline(self):
# Test file includes an empty line in the header data # Test file includes an empty line in the header data
emptyline_file = "Tests/images/zero_bb_emptyline.eps" emptyline_file = "Tests/images/zero_bb_emptyline.eps"
image = Image.open(emptyline_file) with Image.open(emptyline_file) as image:
image.load() image.load()
self.assertEqual(image.mode, "RGB") self.assertEqual(image.mode, "RGB")
self.assertEqual(image.size, (460, 352)) self.assertEqual(image.size, (460, 352))
self.assertEqual(image.format, "EPS") self.assertEqual(image.format, "EPS")

View File

@ -8,14 +8,14 @@ TEST_FILE = "Tests/images/hopper.fits"
class TestFileFitsStub(PillowTestCase): class TestFileFitsStub(PillowTestCase):
def test_open(self): def test_open(self):
# Act # Act
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
# Assert # Assert
self.assertEqual(im.format, "FITS") self.assertEqual(im.format, "FITS")
# Dummy data from the stub # Dummy data from the stub
self.assertEqual(im.mode, "F") self.assertEqual(im.mode, "F")
self.assertEqual(im.size, (1, 1)) self.assertEqual(im.size, (1, 1))
def test_invalid_file(self): def test_invalid_file(self):
# Arrange # Arrange
@ -28,19 +28,19 @@ class TestFileFitsStub(PillowTestCase):
def test_load(self): def test_load(self):
# Arrange # Arrange
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler # Act / Assert: stub cannot load without an implemented handler
self.assertRaises(IOError, im.load) self.assertRaises(IOError, im.load)
def test_save(self): def test_save(self):
# Arrange # Arrange
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
dummy_fp = None dummy_fp = None
dummy_filename = "dummy.filename" dummy_filename = "dummy.filename"
# Act / Assert: stub cannot save without an implemented handler # Act / Assert: stub cannot save without an implemented handler
self.assertRaises(IOError, im.save, dummy_filename) self.assertRaises(IOError, im.save, dummy_filename)
self.assertRaises( self.assertRaises(
IOError, FitsStubImagePlugin._save, im, dummy_fp, dummy_filename IOError, FitsStubImagePlugin._save, im, dummy_fp, dummy_filename
) )

View File

@ -1,6 +1,8 @@
import unittest
from PIL import FliImagePlugin, Image from PIL import FliImagePlugin, Image
from .helper import PillowTestCase from .helper import PillowTestCase, is_pypy
# created as an export of a palette image from Gimp2.6 # created as an export of a palette image from Gimp2.6
# save as...-> hopper.fli, default options. # save as...-> hopper.fli, default options.
@ -12,36 +14,52 @@ animated_test_file = "Tests/images/a.fli"
class TestFileFli(PillowTestCase): class TestFileFli(PillowTestCase):
def test_sanity(self): def test_sanity(self):
im = Image.open(static_test_file) with Image.open(static_test_file) as im:
im.load() im.load()
self.assertEqual(im.mode, "P") self.assertEqual(im.mode, "P")
self.assertEqual(im.size, (128, 128)) self.assertEqual(im.size, (128, 128))
self.assertEqual(im.format, "FLI") self.assertEqual(im.format, "FLI")
self.assertFalse(im.is_animated) self.assertFalse(im.is_animated)
im = Image.open(animated_test_file) with Image.open(animated_test_file) as im:
self.assertEqual(im.mode, "P") self.assertEqual(im.mode, "P")
self.assertEqual(im.size, (320, 200)) self.assertEqual(im.size, (320, 200))
self.assertEqual(im.format, "FLI") self.assertEqual(im.format, "FLI")
self.assertEqual(im.info["duration"], 71) self.assertEqual(im.info["duration"], 71)
self.assertTrue(im.is_animated) self.assertTrue(im.is_animated)
@unittest.skipIf(is_pypy(), "Requires CPython")
def test_unclosed_file(self): def test_unclosed_file(self):
def open(): def open():
im = Image.open(static_test_file) im = Image.open(static_test_file)
im.load() im.load()
self.assert_warning(ResourceWarning, open)
def test_closed_file(self):
def open():
im = Image.open(static_test_file)
im.load()
im.close()
self.assert_warning(None, open)
def test_context_manager(self):
def open():
with Image.open(static_test_file) as im:
im.load()
self.assert_warning(None, open) self.assert_warning(None, open)
def test_tell(self): def test_tell(self):
# Arrange # Arrange
im = Image.open(static_test_file) with Image.open(static_test_file) as im:
# Act # Act
frame = im.tell() frame = im.tell()
# Assert # Assert
self.assertEqual(frame, 0) self.assertEqual(frame, 0)
def test_invalid_file(self): def test_invalid_file(self):
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
@ -49,50 +67,50 @@ class TestFileFli(PillowTestCase):
self.assertRaises(SyntaxError, FliImagePlugin.FliImageFile, invalid_file) self.assertRaises(SyntaxError, FliImagePlugin.FliImageFile, invalid_file)
def test_n_frames(self): def test_n_frames(self):
im = Image.open(static_test_file) with Image.open(static_test_file) as im:
self.assertEqual(im.n_frames, 1) self.assertEqual(im.n_frames, 1)
self.assertFalse(im.is_animated) self.assertFalse(im.is_animated)
im = Image.open(animated_test_file) with Image.open(animated_test_file) as im:
self.assertEqual(im.n_frames, 384) self.assertEqual(im.n_frames, 384)
self.assertTrue(im.is_animated) self.assertTrue(im.is_animated)
def test_eoferror(self): def test_eoferror(self):
im = Image.open(animated_test_file) with Image.open(animated_test_file) as im:
n_frames = im.n_frames n_frames = im.n_frames
# Test seeking past the last frame # Test seeking past the last frame
self.assertRaises(EOFError, im.seek, n_frames) self.assertRaises(EOFError, im.seek, n_frames)
self.assertLess(im.tell(), n_frames) self.assertLess(im.tell(), n_frames)
# Test that seeking to the last frame does not raise an error # Test that seeking to the last frame does not raise an error
im.seek(n_frames - 1) im.seek(n_frames - 1)
def test_seek_tell(self): def test_seek_tell(self):
im = Image.open(animated_test_file) with Image.open(animated_test_file) as im:
layer_number = im.tell() layer_number = im.tell()
self.assertEqual(layer_number, 0) self.assertEqual(layer_number, 0)
im.seek(0) im.seek(0)
layer_number = im.tell() layer_number = im.tell()
self.assertEqual(layer_number, 0) self.assertEqual(layer_number, 0)
im.seek(1) im.seek(1)
layer_number = im.tell() layer_number = im.tell()
self.assertEqual(layer_number, 1) self.assertEqual(layer_number, 1)
im.seek(2) im.seek(2)
layer_number = im.tell() layer_number = im.tell()
self.assertEqual(layer_number, 2) self.assertEqual(layer_number, 2)
im.seek(1) im.seek(1)
layer_number = im.tell() layer_number = im.tell()
self.assertEqual(layer_number, 1) self.assertEqual(layer_number, 1)
def test_seek(self): def test_seek(self):
im = Image.open(animated_test_file) with Image.open(animated_test_file) as im:
im.seek(50) im.seek(50)
expected = Image.open("Tests/images/a_fli.png") with Image.open("Tests/images/a_fli.png") as expected:
self.assert_image_equal(im, expected) self.assert_image_equal(im, expected)

View File

@ -1,4 +1,6 @@
from .helper import PillowTestCase, unittest import unittest
from .helper import PillowTestCase
try: try:
from PIL import FpxImagePlugin from PIL import FpxImagePlugin

View File

@ -5,12 +5,11 @@ from .helper import PillowTestCase
class TestFileFtex(PillowTestCase): class TestFileFtex(PillowTestCase):
def test_load_raw(self): def test_load_raw(self):
im = Image.open("Tests/images/ftex_uncompressed.ftu") with Image.open("Tests/images/ftex_uncompressed.ftu") as im:
target = Image.open("Tests/images/ftex_uncompressed.png") with Image.open("Tests/images/ftex_uncompressed.png") as target:
self.assert_image_equal(im, target)
self.assert_image_equal(im, target)
def test_load_dxt1(self): def test_load_dxt1(self):
im = Image.open("Tests/images/ftex_dxt1.ftc") with Image.open("Tests/images/ftex_dxt1.ftc") as im:
target = Image.open("Tests/images/ftex_dxt1.png") with Image.open("Tests/images/ftex_dxt1.png") as target:
self.assert_image_similar(im, target.convert("RGBA"), 15) self.assert_image_similar(im, target.convert("RGBA"), 15)

View File

@ -10,8 +10,6 @@ class TestFileGbr(PillowTestCase):
self.assertRaises(SyntaxError, GbrImagePlugin.GbrImageFile, invalid_file) self.assertRaises(SyntaxError, GbrImagePlugin.GbrImageFile, invalid_file)
def test_gbr_file(self): def test_gbr_file(self):
im = Image.open("Tests/images/gbr.gbr") with Image.open("Tests/images/gbr.gbr") as im:
with Image.open("Tests/images/gbr.png") as target:
target = Image.open("Tests/images/gbr.png") self.assert_image_equal(target, im)
self.assert_image_equal(target, im)

View File

@ -1,4 +1,4 @@
from PIL import GdImageFile from PIL import GdImageFile, UnidentifiedImageError
from .helper import PillowTestCase from .helper import PillowTestCase
@ -7,9 +7,9 @@ TEST_GD_FILE = "Tests/images/hopper.gd"
class TestFileGd(PillowTestCase): class TestFileGd(PillowTestCase):
def test_sanity(self): def test_sanity(self):
im = GdImageFile.open(TEST_GD_FILE) with GdImageFile.open(TEST_GD_FILE) as im:
self.assertEqual(im.size, (128, 128)) self.assertEqual(im.size, (128, 128))
self.assertEqual(im.format, "GD") self.assertEqual(im.format, "GD")
def test_bad_mode(self): def test_bad_mode(self):
self.assertRaises(ValueError, GdImageFile.open, TEST_GD_FILE, "bad mode") self.assertRaises(ValueError, GdImageFile.open, TEST_GD_FILE, "bad mode")
@ -17,4 +17,4 @@ class TestFileGd(PillowTestCase):
def test_invalid_file(self): def test_invalid_file(self):
invalid_file = "Tests/images/flower.jpg" invalid_file = "Tests/images/flower.jpg"
self.assertRaises(IOError, GdImageFile.open, invalid_file) self.assertRaises(UnidentifiedImageError, GdImageFile.open, invalid_file)

View File

@ -1,8 +1,9 @@
import unittest
from io import BytesIO from io import BytesIO
from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette from PIL import GifImagePlugin, Image, ImageDraw, ImagePalette
from .helper import PillowTestCase, hopper, netpbm_available, unittest from .helper import PillowTestCase, hopper, is_pypy, netpbm_available
try: try:
from PIL import _webp from PIL import _webp
@ -26,18 +27,34 @@ class TestFileGif(PillowTestCase):
self.skipTest("gif support not available") # can this happen? self.skipTest("gif support not available") # can this happen?
def test_sanity(self): def test_sanity(self):
im = Image.open(TEST_GIF) with Image.open(TEST_GIF) as im:
im.load() im.load()
self.assertEqual(im.mode, "P") self.assertEqual(im.mode, "P")
self.assertEqual(im.size, (128, 128)) self.assertEqual(im.size, (128, 128))
self.assertEqual(im.format, "GIF") self.assertEqual(im.format, "GIF")
self.assertEqual(im.info["version"], b"GIF89a") self.assertEqual(im.info["version"], b"GIF89a")
@unittest.skipIf(is_pypy(), "Requires CPython")
def test_unclosed_file(self): def test_unclosed_file(self):
def open(): def open():
im = Image.open(TEST_GIF) im = Image.open(TEST_GIF)
im.load() im.load()
self.assert_warning(ResourceWarning, open)
def test_closed_file(self):
def open():
im = Image.open(TEST_GIF)
im.load()
im.close()
self.assert_warning(None, open)
def test_context_manager(self):
def open():
with Image.open(TEST_GIF) as im:
im.load()
self.assert_warning(None, open) self.assert_warning(None, open)
def test_invalid_file(self): def test_invalid_file(self):
@ -71,21 +88,20 @@ class TestFileGif(PillowTestCase):
def check(colors, size, expected_palette_length): def check(colors, size, expected_palette_length):
# make an image with empty colors in the start of the palette range # make an image with empty colors in the start of the palette range
im = Image.frombytes( im = Image.frombytes(
"P", "P", (colors, colors), bytes(range(256 - colors, 256)) * colors
(colors, colors),
bytes(bytearray(range(256 - colors, 256)) * colors),
) )
im = im.resize((size, size)) im = im.resize((size, size))
outfile = BytesIO() outfile = BytesIO()
im.save(outfile, "GIF") im.save(outfile, "GIF")
outfile.seek(0) outfile.seek(0)
reloaded = Image.open(outfile) with Image.open(outfile) as reloaded:
# check palette length
palette_length = max(
i + 1 for i, v in enumerate(reloaded.histogram()) if v
)
self.assertEqual(expected_palette_length, palette_length)
# check palette length self.assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
palette_length = max(i + 1 for i, v in enumerate(reloaded.histogram()) if v)
self.assertEqual(expected_palette_length, palette_length)
self.assert_image_equal(im.convert("RGB"), reloaded.convert("RGB"))
# These do optimize the palette # These do optimize the palette
check(128, 511, 128) check(128, 511, 128)
@ -103,7 +119,7 @@ class TestFileGif(PillowTestCase):
check(256, 511, 256) check(256, 511, 256)
def test_optimize_full_l(self): def test_optimize_full_l(self):
im = Image.frombytes("L", (16, 16), bytes(bytearray(range(256)))) im = Image.frombytes("L", (16, 16), bytes(range(256)))
test_file = BytesIO() test_file = BytesIO()
im.save(test_file, "GIF", optimize=True) im.save(test_file, "GIF", optimize=True)
self.assertEqual(im.mode, "L") self.assertEqual(im.mode, "L")
@ -112,67 +128,67 @@ class TestFileGif(PillowTestCase):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im = hopper() im = hopper()
im.save(out) im.save(out)
reread = Image.open(out) with Image.open(out) as reread:
self.assert_image_similar(reread.convert("RGB"), im, 50) self.assert_image_similar(reread.convert("RGB"), im, 50)
def test_roundtrip2(self): def test_roundtrip2(self):
# see https://github.com/python-pillow/Pillow/issues/403 # see https://github.com/python-pillow/Pillow/issues/403
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im = Image.open(TEST_GIF) with Image.open(TEST_GIF) as im:
im2 = im.copy() im2 = im.copy()
im2.save(out) im2.save(out)
reread = Image.open(out) with Image.open(out) as reread:
self.assert_image_similar(reread.convert("RGB"), hopper(), 50) self.assert_image_similar(reread.convert("RGB"), hopper(), 50)
def test_roundtrip_save_all(self): def test_roundtrip_save_all(self):
# Single frame image # Single frame image
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im = hopper() im = hopper()
im.save(out, save_all=True) im.save(out, save_all=True)
reread = Image.open(out) with Image.open(out) as reread:
self.assert_image_similar(reread.convert("RGB"), im, 50) self.assert_image_similar(reread.convert("RGB"), im, 50)
# Multiframe image # Multiframe image
im = Image.open("Tests/images/dispose_bgnd.gif") with Image.open("Tests/images/dispose_bgnd.gif") as im:
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im.save(out, save_all=True) im.save(out, save_all=True)
reread = Image.open(out) with Image.open(out) as reread:
self.assertEqual(reread.n_frames, 5) self.assertEqual(reread.n_frames, 5)
def test_headers_saving_for_animated_gifs(self): def test_headers_saving_for_animated_gifs(self):
important_headers = ["background", "version", "duration", "loop"] important_headers = ["background", "version", "duration", "loop"]
# Multiframe image # Multiframe image
im = Image.open("Tests/images/dispose_bgnd.gif") with Image.open("Tests/images/dispose_bgnd.gif") as im:
info = im.info.copy() info = im.info.copy()
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im.save(out, save_all=True) im.save(out, save_all=True)
reread = Image.open(out) with Image.open(out) as reread:
for header in important_headers: for header in important_headers:
self.assertEqual(info[header], reread.info[header]) self.assertEqual(info[header], reread.info[header])
def test_palette_handling(self): def test_palette_handling(self):
# see https://github.com/python-pillow/Pillow/issues/513 # see https://github.com/python-pillow/Pillow/issues/513
im = Image.open(TEST_GIF) with Image.open(TEST_GIF) as im:
im = im.convert("RGB") im = im.convert("RGB")
im = im.resize((100, 100), Image.LANCZOS) im = im.resize((100, 100), Image.LANCZOS)
im2 = im.convert("P", palette=Image.ADAPTIVE, colors=256) im2 = im.convert("P", palette=Image.ADAPTIVE, colors=256)
f = self.tempfile("temp.gif") f = self.tempfile("temp.gif")
im2.save(f, optimize=True) im2.save(f, optimize=True)
reloaded = Image.open(f) with Image.open(f) as reloaded:
self.assert_image_similar(im, reloaded.convert("RGB"), 10) self.assert_image_similar(im, reloaded.convert("RGB"), 10)
def test_palette_434(self): def test_palette_434(self):
# see https://github.com/python-pillow/Pillow/issues/434 # see https://github.com/python-pillow/Pillow/issues/434
@ -185,108 +201,115 @@ class TestFileGif(PillowTestCase):
return reloaded return reloaded
orig = "Tests/images/test.colors.gif" orig = "Tests/images/test.colors.gif"
im = Image.open(orig) with Image.open(orig) as im:
self.assert_image_similar(im, roundtrip(im), 1) with roundtrip(im) as reloaded:
self.assert_image_similar(im, roundtrip(im, optimize=True), 1) self.assert_image_similar(im, reloaded, 1)
with roundtrip(im, optimize=True) as reloaded:
self.assert_image_similar(im, reloaded, 1)
im = im.convert("RGB") im = im.convert("RGB")
# check automatic P conversion # check automatic P conversion
reloaded = roundtrip(im).convert("RGB") with roundtrip(im) as reloaded:
self.assert_image_equal(im, reloaded) reloaded = reloaded.convert("RGB")
self.assert_image_equal(im, reloaded)
@unittest.skipUnless(netpbm_available(), "netpbm not available") @unittest.skipUnless(netpbm_available(), "netpbm not available")
def test_save_netpbm_bmp_mode(self): def test_save_netpbm_bmp_mode(self):
img = Image.open(TEST_GIF).convert("RGB") with Image.open(TEST_GIF) as img:
img = img.convert("RGB")
tempfile = self.tempfile("temp.gif") tempfile = self.tempfile("temp.gif")
GifImagePlugin._save_netpbm(img, 0, tempfile) GifImagePlugin._save_netpbm(img, 0, tempfile)
self.assert_image_similar(img, Image.open(tempfile).convert("RGB"), 0) with Image.open(tempfile) as reloaded:
self.assert_image_similar(img, reloaded.convert("RGB"), 0)
@unittest.skipUnless(netpbm_available(), "netpbm not available") @unittest.skipUnless(netpbm_available(), "netpbm not available")
def test_save_netpbm_l_mode(self): def test_save_netpbm_l_mode(self):
img = Image.open(TEST_GIF).convert("L") with Image.open(TEST_GIF) as img:
img = img.convert("L")
tempfile = self.tempfile("temp.gif") tempfile = self.tempfile("temp.gif")
GifImagePlugin._save_netpbm(img, 0, tempfile) GifImagePlugin._save_netpbm(img, 0, tempfile)
self.assert_image_similar(img, Image.open(tempfile).convert("L"), 0) with Image.open(tempfile) as reloaded:
self.assert_image_similar(img, reloaded.convert("L"), 0)
def test_seek(self): def test_seek(self):
img = Image.open("Tests/images/dispose_none.gif") with Image.open("Tests/images/dispose_none.gif") as img:
framecount = 0 framecount = 0
try: try:
while True: while True:
framecount += 1 framecount += 1
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
except EOFError: except EOFError:
self.assertEqual(framecount, 5) self.assertEqual(framecount, 5)
def test_seek_info(self): def test_seek_info(self):
im = Image.open("Tests/images/iss634.gif") with Image.open("Tests/images/iss634.gif") as im:
info = im.info.copy() info = im.info.copy()
im.seek(1) im.seek(1)
im.seek(0) im.seek(0)
self.assertEqual(im.info, info) self.assertEqual(im.info, info)
def test_seek_rewind(self): def test_seek_rewind(self):
im = Image.open("Tests/images/iss634.gif") with Image.open("Tests/images/iss634.gif") as im:
im.seek(2) im.seek(2)
im.seek(1) im.seek(1)
expected = Image.open("Tests/images/iss634.gif") with Image.open("Tests/images/iss634.gif") as expected:
expected.seek(1) expected.seek(1)
self.assert_image_equal(im, expected) self.assert_image_equal(im, expected)
def test_n_frames(self): def test_n_frames(self):
for path, n_frames in [[TEST_GIF, 1], ["Tests/images/iss634.gif", 42]]: for path, n_frames in [[TEST_GIF, 1], ["Tests/images/iss634.gif", 42]]:
# Test is_animated before n_frames # Test is_animated before n_frames
im = Image.open(path) with Image.open(path) as im:
self.assertEqual(im.is_animated, n_frames != 1) self.assertEqual(im.is_animated, n_frames != 1)
# Test is_animated after n_frames # Test is_animated after n_frames
im = Image.open(path) with Image.open(path) as im:
self.assertEqual(im.n_frames, n_frames) self.assertEqual(im.n_frames, n_frames)
self.assertEqual(im.is_animated, n_frames != 1) self.assertEqual(im.is_animated, n_frames != 1)
def test_eoferror(self): def test_eoferror(self):
im = Image.open(TEST_GIF) with Image.open(TEST_GIF) as im:
n_frames = im.n_frames n_frames = im.n_frames
# Test seeking past the last frame # Test seeking past the last frame
self.assertRaises(EOFError, im.seek, n_frames) self.assertRaises(EOFError, im.seek, n_frames)
self.assertLess(im.tell(), n_frames) self.assertLess(im.tell(), n_frames)
# Test that seeking to the last frame does not raise an error # Test that seeking to the last frame does not raise an error
im.seek(n_frames - 1) im.seek(n_frames - 1)
def test_dispose_none(self): def test_dispose_none(self):
img = Image.open("Tests/images/dispose_none.gif") with Image.open("Tests/images/dispose_none.gif") as img:
try: try:
while True: while True:
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
self.assertEqual(img.disposal_method, 1) self.assertEqual(img.disposal_method, 1)
except EOFError: except EOFError:
pass pass
def test_dispose_background(self): def test_dispose_background(self):
img = Image.open("Tests/images/dispose_bgnd.gif") with Image.open("Tests/images/dispose_bgnd.gif") as img:
try: try:
while True: while True:
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
self.assertEqual(img.disposal_method, 2) self.assertEqual(img.disposal_method, 2)
except EOFError: except EOFError:
pass pass
def test_dispose_previous(self): def test_dispose_previous(self):
img = Image.open("Tests/images/dispose_prev.gif") with Image.open("Tests/images/dispose_prev.gif") as img:
try: try:
while True: while True:
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
self.assertEqual(img.disposal_method, 3) self.assertEqual(img.disposal_method, 3)
except EOFError: except EOFError:
pass pass
def test_save_dispose(self): def test_save_dispose(self):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
@ -299,10 +322,10 @@ class TestFileGif(PillowTestCase):
im_list[0].save( im_list[0].save(
out, save_all=True, append_images=im_list[1:], disposal=method out, save_all=True, append_images=im_list[1:], disposal=method
) )
img = Image.open(out) with Image.open(out) as img:
for _ in range(2): for _ in range(2):
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
self.assertEqual(img.disposal_method, method) self.assertEqual(img.disposal_method, method)
# check per frame disposal # check per frame disposal
im_list[0].save( im_list[0].save(
@ -312,11 +335,11 @@ class TestFileGif(PillowTestCase):
disposal=tuple(range(len(im_list))), disposal=tuple(range(len(im_list))),
) )
img = Image.open(out) with Image.open(out) as img:
for i in range(2): for i in range(2):
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
self.assertEqual(img.disposal_method, i + 1) self.assertEqual(img.disposal_method, i + 1)
def test_dispose2_palette(self): def test_dispose2_palette(self):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
@ -336,17 +359,16 @@ class TestFileGif(PillowTestCase):
im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2) im_list[0].save(out, save_all=True, append_images=im_list[1:], disposal=2)
img = Image.open(out) with Image.open(out) as img:
for i, circle in enumerate(circles):
img.seek(i)
rgb_img = img.convert("RGB")
for i, circle in enumerate(circles): # Check top left pixel matches background
img.seek(i) self.assertEqual(rgb_img.getpixel((0, 0)), (255, 0, 0))
rgb_img = img.convert("RGB")
# Check top left pixel matches background # Center remains red every frame
self.assertEqual(rgb_img.getpixel((0, 0)), (255, 0, 0)) self.assertEqual(rgb_img.getpixel((50, 50)), circle)
# Center remains red every frame
self.assertEqual(rgb_img.getpixel((50, 50)), circle)
def test_dispose2_diff(self): def test_dispose2_diff(self):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
@ -375,20 +397,19 @@ class TestFileGif(PillowTestCase):
out, save_all=True, append_images=im_list[1:], disposal=2, transparency=0 out, save_all=True, append_images=im_list[1:], disposal=2, transparency=0
) )
img = Image.open(out) with Image.open(out) as img:
for i, colours in enumerate(circles):
img.seek(i)
rgb_img = img.convert("RGBA")
for i, colours in enumerate(circles): # Check left circle is correct colour
img.seek(i) self.assertEqual(rgb_img.getpixel((20, 50)), colours[0])
rgb_img = img.convert("RGBA")
# Check left circle is correct colour # Check right circle is correct colour
self.assertEqual(rgb_img.getpixel((20, 50)), colours[0]) self.assertEqual(rgb_img.getpixel((80, 50)), colours[1])
# Check right circle is correct colour # Check BG is correct colour
self.assertEqual(rgb_img.getpixel((80, 50)), colours[1]) self.assertEqual(rgb_img.getpixel((1, 1)), (255, 255, 255, 0))
# Check BG is correct colour
self.assertEqual(rgb_img.getpixel((1, 1)), (255, 255, 255, 0))
def test_dispose2_background(self): def test_dispose2_background(self):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
@ -411,17 +432,17 @@ class TestFileGif(PillowTestCase):
out, save_all=True, append_images=im_list[1:], disposal=[0, 2], background=1 out, save_all=True, append_images=im_list[1:], disposal=[0, 2], background=1
) )
im = Image.open(out) with Image.open(out) as im:
im.seek(1) im.seek(1)
self.assertEqual(im.getpixel((0, 0)), 0) self.assertEqual(im.getpixel((0, 0)), 0)
def test_iss634(self): def test_iss634(self):
img = Image.open("Tests/images/iss634.gif") with Image.open("Tests/images/iss634.gif") as img:
# seek to the second frame # seek to the second frame
img.seek(img.tell() + 1) img.seek(img.tell() + 1)
# all transparent pixels should be replaced with the color from the # all transparent pixels should be replaced with the color from the
# first frame # first frame
self.assertEqual(img.histogram()[img.info["transparency"]], 0) self.assertEqual(img.histogram()[img.info["transparency"]], 0)
def test_duration(self): def test_duration(self):
duration = 1000 duration = 1000
@ -433,8 +454,8 @@ class TestFileGif(PillowTestCase):
im.info["duration"] = 100 im.info["duration"] = 100
im.save(out, duration=duration) im.save(out, duration=duration)
reread = Image.open(out) with Image.open(out) as reread:
self.assertEqual(reread.info["duration"], duration) self.assertEqual(reread.info["duration"], duration)
def test_multiple_duration(self): def test_multiple_duration(self):
duration_list = [1000, 2000, 3000] duration_list = [1000, 2000, 3000]
@ -450,27 +471,27 @@ class TestFileGif(PillowTestCase):
im_list[0].save( im_list[0].save(
out, save_all=True, append_images=im_list[1:], duration=duration_list out, save_all=True, append_images=im_list[1:], duration=duration_list
) )
reread = Image.open(out) with Image.open(out) as reread:
for duration in duration_list: for duration in duration_list:
self.assertEqual(reread.info["duration"], duration) self.assertEqual(reread.info["duration"], duration)
try: try:
reread.seek(reread.tell() + 1) reread.seek(reread.tell() + 1)
except EOFError: except EOFError:
pass pass
# duration as tuple # duration as tuple
im_list[0].save( im_list[0].save(
out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list) out, save_all=True, append_images=im_list[1:], duration=tuple(duration_list)
) )
reread = Image.open(out) with Image.open(out) as reread:
for duration in duration_list: for duration in duration_list:
self.assertEqual(reread.info["duration"], duration) self.assertEqual(reread.info["duration"], duration)
try: try:
reread.seek(reread.tell() + 1) reread.seek(reread.tell() + 1)
except EOFError: except EOFError:
pass pass
def test_identical_frames(self): def test_identical_frames(self):
duration_list = [1000, 1500, 2000, 4000] duration_list = [1000, 1500, 2000, 4000]
@ -487,13 +508,13 @@ class TestFileGif(PillowTestCase):
im_list[0].save( im_list[0].save(
out, save_all=True, append_images=im_list[1:], duration=duration_list out, save_all=True, append_images=im_list[1:], duration=duration_list
) )
reread = Image.open(out) with Image.open(out) as reread:
# Assert that the first three frames were combined # Assert that the first three frames were combined
self.assertEqual(reread.n_frames, 2) self.assertEqual(reread.n_frames, 2)
# Assert that the new duration is the total of the identical frames # Assert that the new duration is the total of the identical frames
self.assertEqual(reread.info["duration"], 4500) self.assertEqual(reread.info["duration"], 4500)
def test_identical_frames_to_single_frame(self): def test_identical_frames_to_single_frame(self):
for duration in ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500): for duration in ([1000, 1500, 2000, 4000], (1000, 1500, 2000, 4000), 8500):
@ -507,13 +528,12 @@ class TestFileGif(PillowTestCase):
im_list[0].save( im_list[0].save(
out, save_all=True, append_images=im_list[1:], duration=duration out, save_all=True, append_images=im_list[1:], duration=duration
) )
reread = Image.open(out) with Image.open(out) as reread:
# Assert that all frames were combined
self.assertEqual(reread.n_frames, 1)
# Assert that all frames were combined # Assert that the new duration is the total of the identical frames
self.assertEqual(reread.n_frames, 1) self.assertEqual(reread.info["duration"], 8500)
# Assert that the new duration is the total of the identical frames
self.assertEqual(reread.info["duration"], 8500)
def test_number_of_loops(self): def test_number_of_loops(self):
number_of_loops = 2 number_of_loops = 2
@ -521,35 +541,37 @@ class TestFileGif(PillowTestCase):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im = Image.new("L", (100, 100), "#000") im = Image.new("L", (100, 100), "#000")
im.save(out, loop=number_of_loops) im.save(out, loop=number_of_loops)
reread = Image.open(out) with Image.open(out) as reread:
self.assertEqual(reread.info["loop"], number_of_loops) self.assertEqual(reread.info["loop"], number_of_loops)
def test_background(self): def test_background(self):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im = Image.new("L", (100, 100), "#000") im = Image.new("L", (100, 100), "#000")
im.info["background"] = 1 im.info["background"] = 1
im.save(out) im.save(out)
reread = Image.open(out) with Image.open(out) as reread:
self.assertEqual(reread.info["background"], im.info["background"]) self.assertEqual(reread.info["background"], im.info["background"])
if HAVE_WEBP and _webp.HAVE_WEBPANIM: if HAVE_WEBP and _webp.HAVE_WEBPANIM:
im = Image.open("Tests/images/hopper.webp") with Image.open("Tests/images/hopper.webp") as im:
self.assertIsInstance(im.info["background"], tuple) self.assertIsInstance(im.info["background"], tuple)
im.save(out) im.save(out)
def test_comment(self): def test_comment(self):
im = Image.open(TEST_GIF) with Image.open(TEST_GIF) as im:
self.assertEqual(im.info["comment"], b"File written by Adobe Photoshop\xa8 4.0") self.assertEqual(
im.info["comment"], b"File written by Adobe Photoshop\xa8 4.0"
)
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im = Image.new("L", (100, 100), "#000") im = Image.new("L", (100, 100), "#000")
im.info["comment"] = b"Test comment text" im.info["comment"] = b"Test comment text"
im.save(out) im.save(out)
reread = Image.open(out) with Image.open(out) as reread:
self.assertEqual(reread.info["comment"], im.info["comment"]) self.assertEqual(reread.info["comment"], im.info["comment"])
def test_comment_over_255(self): def test_comment_over_255(self):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
@ -559,22 +581,22 @@ class TestFileGif(PillowTestCase):
comment += comment comment += comment
im.info["comment"] = comment im.info["comment"] = comment
im.save(out) im.save(out)
reread = Image.open(out) with Image.open(out) as reread:
self.assertEqual(reread.info["comment"], comment) self.assertEqual(reread.info["comment"], comment)
def test_zero_comment_subblocks(self): def test_zero_comment_subblocks(self):
im = Image.open("Tests/images/hopper_zero_comment_subblocks.gif") with Image.open("Tests/images/hopper_zero_comment_subblocks.gif") as im:
expected = Image.open(TEST_GIF) with Image.open(TEST_GIF) as expected:
self.assert_image_equal(im, expected) self.assert_image_equal(im, expected)
def test_version(self): def test_version(self):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
def assertVersionAfterSave(im, version): def assertVersionAfterSave(im, version):
im.save(out) im.save(out)
reread = Image.open(out) with Image.open(out) as reread:
self.assertEqual(reread.info["version"], version) self.assertEqual(reread.info["version"], version)
# Test that GIF87a is used by default # Test that GIF87a is used by default
im = Image.new("L", (100, 100), "#000") im = Image.new("L", (100, 100), "#000")
@ -590,12 +612,12 @@ class TestFileGif(PillowTestCase):
assertVersionAfterSave(im, b"GIF89a") assertVersionAfterSave(im, b"GIF89a")
# Test that a GIF87a image is also saved in that format # Test that a GIF87a image is also saved in that format
im = Image.open("Tests/images/test.colors.gif") with Image.open("Tests/images/test.colors.gif") as im:
assertVersionAfterSave(im, b"GIF87a") assertVersionAfterSave(im, b"GIF87a")
# Test that a GIF89a image is also saved in that format # Test that a GIF89a image is also saved in that format
im.info["version"] = b"GIF89a" im.info["version"] = b"GIF89a"
assertVersionAfterSave(im, b"GIF87a") assertVersionAfterSave(im, b"GIF87a")
def test_append_images(self): def test_append_images(self):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
@ -605,26 +627,25 @@ class TestFileGif(PillowTestCase):
ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]] ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]]
im.copy().save(out, save_all=True, append_images=ims) im.copy().save(out, save_all=True, append_images=ims)
reread = Image.open(out) with Image.open(out) as reread:
self.assertEqual(reread.n_frames, 3) self.assertEqual(reread.n_frames, 3)
# Tests appending using a generator # Tests appending using a generator
def imGenerator(ims): def imGenerator(ims):
for im in ims: yield from ims
yield im
im.save(out, save_all=True, append_images=imGenerator(ims)) im.save(out, save_all=True, append_images=imGenerator(ims))
reread = Image.open(out) with Image.open(out) as reread:
self.assertEqual(reread.n_frames, 3) self.assertEqual(reread.n_frames, 3)
# Tests appending single and multiple frame images # Tests appending single and multiple frame images
im = Image.open("Tests/images/dispose_none.gif") with Image.open("Tests/images/dispose_none.gif") as im:
ims = [Image.open("Tests/images/dispose_prev.gif")] with Image.open("Tests/images/dispose_prev.gif") as im2:
im.save(out, save_all=True, append_images=ims) im.save(out, save_all=True, append_images=[im2])
reread = Image.open(out) with Image.open(out) as reread:
self.assertEqual(reread.n_frames, 10) self.assertEqual(reread.n_frames, 10)
def test_transparent_optimize(self): def test_transparent_optimize(self):
# from issue #2195, if the transparent color is incorrectly # from issue #2195, if the transparent color is incorrectly
@ -633,7 +654,7 @@ class TestFileGif(PillowTestCase):
# that's > 128 items where the transparent color is actually # that's > 128 items where the transparent color is actually
# the top palette entry to trigger the bug. # the top palette entry to trigger the bug.
data = bytes(bytearray(range(1, 254))) data = bytes(range(1, 254))
palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3) palette = ImagePalette.ImagePalette("RGB", list(range(256)) * 3)
im = Image.new("L", (253, 1)) im = Image.new("L", (253, 1))
@ -642,9 +663,9 @@ class TestFileGif(PillowTestCase):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im.save(out, transparency=253) im.save(out, transparency=253)
reloaded = Image.open(out) with Image.open(out) as reloaded:
self.assertEqual(reloaded.info["transparency"], 253) self.assertEqual(reloaded.info["transparency"], 253)
def test_rgb_transparency(self): def test_rgb_transparency(self):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
@ -654,8 +675,8 @@ class TestFileGif(PillowTestCase):
im.info["transparency"] = (255, 0, 0) im.info["transparency"] = (255, 0, 0)
self.assert_warning(UserWarning, im.save, out) self.assert_warning(UserWarning, im.save, out)
reloaded = Image.open(out) with Image.open(out) as reloaded:
self.assertNotIn("transparency", reloaded.info) self.assertNotIn("transparency", reloaded.info)
# Multiple frames # Multiple frames
im = Image.new("RGB", (1, 1)) im = Image.new("RGB", (1, 1))
@ -663,8 +684,8 @@ class TestFileGif(PillowTestCase):
ims = [Image.new("RGB", (1, 1))] ims = [Image.new("RGB", (1, 1))]
self.assert_warning(UserWarning, im.save, out, save_all=True, append_images=ims) self.assert_warning(UserWarning, im.save, out, save_all=True, append_images=ims)
reloaded = Image.open(out) with Image.open(out) as reloaded:
self.assertNotIn("transparency", reloaded.info) self.assertNotIn("transparency", reloaded.info)
def test_bbox(self): def test_bbox(self):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
@ -673,22 +694,22 @@ class TestFileGif(PillowTestCase):
ims = [Image.new("RGB", (100, 100), "#000")] ims = [Image.new("RGB", (100, 100), "#000")]
im.save(out, save_all=True, append_images=ims) im.save(out, save_all=True, append_images=ims)
reread = Image.open(out) with Image.open(out) as reread:
self.assertEqual(reread.n_frames, 2) self.assertEqual(reread.n_frames, 2)
def test_palette_save_L(self): def test_palette_save_L(self):
# generate an L mode image with a separate palette # generate an L mode image with a separate palette
im = hopper("P") im = hopper("P")
im_l = Image.frombytes("L", im.size, im.tobytes()) im_l = Image.frombytes("L", im.size, im.tobytes())
palette = bytes(bytearray(im.getpalette())) palette = bytes(im.getpalette())
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im_l.save(out, palette=palette) im_l.save(out, palette=palette)
reloaded = Image.open(out) with Image.open(out) as reloaded:
self.assert_image_equal(reloaded.convert("RGB"), im.convert("RGB")) self.assert_image_equal(reloaded.convert("RGB"), im.convert("RGB"))
def test_palette_save_P(self): def test_palette_save_P(self):
# pass in a different palette, then construct what the image # pass in a different palette, then construct what the image
@ -696,14 +717,14 @@ class TestFileGif(PillowTestCase):
# Forcing a non-straight grayscale palette. # Forcing a non-straight grayscale palette.
im = hopper("P") im = hopper("P")
palette = bytes(bytearray([255 - i // 3 for i in range(768)])) palette = bytes([255 - i // 3 for i in range(768)])
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im.save(out, palette=palette) im.save(out, palette=palette)
reloaded = Image.open(out) with Image.open(out) as reloaded:
im.putpalette(palette) im.putpalette(palette)
self.assert_image_equal(reloaded, im) self.assert_image_equal(reloaded, im)
def test_palette_save_ImagePalette(self): def test_palette_save_ImagePalette(self):
# pass in a different palette, as an ImagePalette.ImagePalette # pass in a different palette, as an ImagePalette.ImagePalette
@ -715,9 +736,9 @@ class TestFileGif(PillowTestCase):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im.save(out, palette=palette) im.save(out, palette=palette)
reloaded = Image.open(out) with Image.open(out) as reloaded:
im.putpalette(palette) im.putpalette(palette)
self.assert_image_equal(reloaded, im) self.assert_image_equal(reloaded, im)
def test_save_I(self): def test_save_I(self):
# Test saving something that would trigger the auto-convert to 'L' # Test saving something that would trigger the auto-convert to 'L'
@ -727,17 +748,17 @@ class TestFileGif(PillowTestCase):
out = self.tempfile("temp.gif") out = self.tempfile("temp.gif")
im.save(out) im.save(out)
reloaded = Image.open(out) with Image.open(out) as reloaded:
self.assert_image_equal(reloaded.convert("L"), im.convert("L")) self.assert_image_equal(reloaded.convert("L"), im.convert("L"))
def test_getdata(self): def test_getdata(self):
# test getheader/getdata against legacy values # test getheader/getdata against legacy values
# Create a 'P' image with holes in the palette # Create a 'P' image with holes in the palette
im = Image._wedge().resize((16, 16)) im = Image._wedge().resize((16, 16), Image.NEAREST)
im.putpalette(ImagePalette.ImagePalette("RGB")) im.putpalette(ImagePalette.ImagePalette("RGB"))
im.info = {"background": 0} im.info = {"background": 0}
passed_palette = bytes(bytearray([255 - i // 3 for i in range(768)])) passed_palette = bytes([255 - i // 3 for i in range(768)])
GifImagePlugin._FORCE_OPTIMIZE = True GifImagePlugin._FORCE_OPTIMIZE = True
try: try:
@ -759,14 +780,13 @@ class TestFileGif(PillowTestCase):
def test_lzw_bits(self): def test_lzw_bits(self):
# see https://github.com/python-pillow/Pillow/issues/2811 # see https://github.com/python-pillow/Pillow/issues/2811
im = Image.open("Tests/images/issue_2811.gif") with Image.open("Tests/images/issue_2811.gif") as im:
self.assertEqual(im.tile[0][3][0], 11) # LZW bits
self.assertEqual(im.tile[0][3][0], 11) # LZW bits # codec error prepatch
# codec error prepatch im.load()
im.load()
def test_extents(self): def test_extents(self):
im = Image.open("Tests/images/test_extents.gif") with Image.open("Tests/images/test_extents.gif") as im:
self.assertEqual(im.size, (100, 100)) self.assertEqual(im.size, (100, 100))
im.seek(1) im.seek(1)
self.assertEqual(im.size, (150, 150)) self.assertEqual(im.size, (150, 150))

View File

@ -8,14 +8,14 @@ TEST_FILE = "Tests/images/WAlaska.wind.7days.grb"
class TestFileGribStub(PillowTestCase): class TestFileGribStub(PillowTestCase):
def test_open(self): def test_open(self):
# Act # Act
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
# Assert # Assert
self.assertEqual(im.format, "GRIB") self.assertEqual(im.format, "GRIB")
# Dummy data from the stub # Dummy data from the stub
self.assertEqual(im.mode, "F") self.assertEqual(im.mode, "F")
self.assertEqual(im.size, (1, 1)) self.assertEqual(im.size, (1, 1))
def test_invalid_file(self): def test_invalid_file(self):
# Arrange # Arrange
@ -28,10 +28,10 @@ class TestFileGribStub(PillowTestCase):
def test_load(self): def test_load(self):
# Arrange # Arrange
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler # Act / Assert: stub cannot load without an implemented handler
self.assertRaises(IOError, im.load) self.assertRaises(IOError, im.load)
def test_save(self): def test_save(self):
# Arrange # Arrange

View File

@ -8,14 +8,14 @@ TEST_FILE = "Tests/images/hdf5.h5"
class TestFileHdf5Stub(PillowTestCase): class TestFileHdf5Stub(PillowTestCase):
def test_open(self): def test_open(self):
# Act # Act
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
# Assert # Assert
self.assertEqual(im.format, "HDF5") self.assertEqual(im.format, "HDF5")
# Dummy data from the stub # Dummy data from the stub
self.assertEqual(im.mode, "F") self.assertEqual(im.mode, "F")
self.assertEqual(im.size, (1, 1)) self.assertEqual(im.size, (1, 1))
def test_invalid_file(self): def test_invalid_file(self):
# Arrange # Arrange
@ -28,19 +28,19 @@ class TestFileHdf5Stub(PillowTestCase):
def test_load(self): def test_load(self):
# Arrange # Arrange
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler # Act / Assert: stub cannot load without an implemented handler
self.assertRaises(IOError, im.load) self.assertRaises(IOError, im.load)
def test_save(self): def test_save(self):
# Arrange # Arrange
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
dummy_fp = None dummy_fp = None
dummy_filename = "dummy.filename" dummy_filename = "dummy.filename"
# Act / Assert: stub cannot save without an implemented handler # Act / Assert: stub cannot save without an implemented handler
self.assertRaises(IOError, im.save, dummy_filename) self.assertRaises(IOError, im.save, dummy_filename)
self.assertRaises( self.assertRaises(
IOError, Hdf5StubImagePlugin._save, im, dummy_fp, dummy_filename IOError, Hdf5StubImagePlugin._save, im, dummy_fp, dummy_filename
) )

View File

@ -1,9 +1,10 @@
import io import io
import sys import sys
import unittest
from PIL import IcnsImagePlugin, Image from PIL import IcnsImagePlugin, Image
from .helper import PillowTestCase, unittest from .helper import PillowTestCase
# sample icon file # sample icon file
TEST_FILE = "Tests/images/pillow.icns" TEST_FILE = "Tests/images/pillow.icns"
@ -15,72 +16,71 @@ class TestFileIcns(PillowTestCase):
def test_sanity(self): def test_sanity(self):
# Loading this icon by default should result in the largest size # Loading this icon by default should result in the largest size
# (512x512@2x) being loaded # (512x512@2x) being loaded
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
# Assert that there is no unclosed file warning # Assert that there is no unclosed file warning
self.assert_warning(None, im.load) self.assert_warning(None, im.load)
self.assertEqual(im.mode, "RGBA") self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (1024, 1024)) self.assertEqual(im.size, (1024, 1024))
self.assertEqual(im.format, "ICNS") self.assertEqual(im.format, "ICNS")
@unittest.skipIf(sys.platform != "darwin", "requires macOS") @unittest.skipIf(sys.platform != "darwin", "requires macOS")
def test_save(self): def test_save(self):
im = Image.open(TEST_FILE)
temp_file = self.tempfile("temp.icns") temp_file = self.tempfile("temp.icns")
im.save(temp_file)
reread = Image.open(temp_file) with Image.open(TEST_FILE) as im:
im.save(temp_file)
self.assertEqual(reread.mode, "RGBA") with Image.open(temp_file) as reread:
self.assertEqual(reread.size, (1024, 1024)) self.assertEqual(reread.mode, "RGBA")
self.assertEqual(reread.format, "ICNS") self.assertEqual(reread.size, (1024, 1024))
self.assertEqual(reread.format, "ICNS")
@unittest.skipIf(sys.platform != "darwin", "requires macOS") @unittest.skipIf(sys.platform != "darwin", "requires macOS")
def test_save_append_images(self): def test_save_append_images(self):
im = Image.open(TEST_FILE)
temp_file = self.tempfile("temp.icns") temp_file = self.tempfile("temp.icns")
provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128)) provided_im = Image.new("RGBA", (32, 32), (255, 0, 0, 128))
im.save(temp_file, append_images=[provided_im])
reread = Image.open(temp_file) with Image.open(TEST_FILE) as im:
self.assert_image_similar(reread, im, 1) im.save(temp_file, append_images=[provided_im])
reread = Image.open(temp_file) with Image.open(temp_file) as reread:
reread.size = (16, 16, 2) self.assert_image_similar(reread, im, 1)
reread.load()
self.assert_image_equal(reread, provided_im) with Image.open(temp_file) as reread:
reread.size = (16, 16, 2)
reread.load()
self.assert_image_equal(reread, provided_im)
def test_sizes(self): def test_sizes(self):
# Check that we can load all of the sizes, and that the final pixel # Check that we can load all of the sizes, and that the final pixel
# dimensions are as expected # dimensions are as expected
im = Image.open(TEST_FILE) with Image.open(TEST_FILE) as im:
for w, h, r in im.info["sizes"]: for w, h, r in im.info["sizes"]:
wr = w * r wr = w * r
hr = h * r hr = h * r
im.size = (w, h, r) im.size = (w, h, r)
im.load() im.load()
self.assertEqual(im.mode, "RGBA") self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (wr, hr)) self.assertEqual(im.size, (wr, hr))
# Check that we cannot load an incorrect size # Check that we cannot load an incorrect size
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
im.size = (1, 1) im.size = (1, 1)
def test_older_icon(self): def test_older_icon(self):
# This icon was made with Icon Composer rather than iconutil; it still # This icon was made with Icon Composer rather than iconutil; it still
# uses PNG rather than JP2, however (since it was made on 10.9). # uses PNG rather than JP2, however (since it was made on 10.9).
im = Image.open("Tests/images/pillow2.icns") with Image.open("Tests/images/pillow2.icns") as im:
for w, h, r in im.info["sizes"]: for w, h, r in im.info["sizes"]:
wr = w * r wr = w * r
hr = h * r hr = h * r
im2 = Image.open("Tests/images/pillow2.icns") with Image.open("Tests/images/pillow2.icns") as im2:
im2.size = (w, h, r) im2.size = (w, h, r)
im2.load() im2.load()
self.assertEqual(im2.mode, "RGBA") self.assertEqual(im2.mode, "RGBA")
self.assertEqual(im2.size, (wr, hr)) self.assertEqual(im2.size, (wr, hr))
def test_jp2_icon(self): def test_jp2_icon(self):
# This icon was made by using Uli Kusterer's oldiconutil to replace # This icon was made by using Uli Kusterer's oldiconutil to replace
@ -93,15 +93,15 @@ class TestFileIcns(PillowTestCase):
if not enable_jpeg2k: if not enable_jpeg2k:
return return
im = Image.open("Tests/images/pillow3.icns") with Image.open("Tests/images/pillow3.icns") as im:
for w, h, r in im.info["sizes"]: for w, h, r in im.info["sizes"]:
wr = w * r wr = w * r
hr = h * r hr = h * r
im2 = Image.open("Tests/images/pillow3.icns") with Image.open("Tests/images/pillow3.icns") as im2:
im2.size = (w, h, r) im2.size = (w, h, r)
im2.load() im2.load()
self.assertEqual(im2.mode, "RGBA") self.assertEqual(im2.mode, "RGBA")
self.assertEqual(im2.size, (wr, hr)) self.assertEqual(im2.size, (wr, hr))
def test_getimage(self): def test_getimage(self):
with open(TEST_FILE, "rb") as fp: with open(TEST_FILE, "rb") as fp:

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