Merge remote-tracking branch 'upstream/master' into ellipse

This commit is contained in:
Stanislau Tsitsianok 2020-06-16 20:20:44 +03:00
commit 27109c9011
No known key found for this signature in database
GPG Key ID: 349CB26B2ED6E0C0
212 changed files with 6666 additions and 5502 deletions

View File

@ -1,4 +1,5 @@
version: '{build}'
image: Visual Studio 2017
clone_folder: c:\pillow
init:
- ECHO %PYTHON%
@ -6,69 +7,37 @@ init:
# Uncomment previous line to get RDP access during the build.
environment:
X64_EXT: -x64
EXECUTABLE: python.exe
PIP_DIR: Scripts
VENV: NO
TEST_OPTIONS:
DEPLOY: YES
matrix:
- PYTHON: C:/Python38
- PYTHON: C:/Python38-x64
- PYTHON: C:/Python35
ARCHITECTURE: x86
- PYTHON: C:/Python35-x64
- PYTHON: C:/msys64/mingw32
EXECUTABLE: bin/python3
PIP_DIR: bin
TEST_OPTIONS: --processes=0
DEPLOY: NO
- PYTHON: C:/vp/pypy3
EXECUTABLE: bin/pypy.exe
PIP_DIR: bin
VENV: YES
ARCHITECTURE: x64
install:
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/master.zip
- 7z x pillow-depends.zip -oc:\
- mv c:\pillow-depends-master c:\pillow-depends
- xcopy c:\pillow-depends\*.zip c:\pillow\winbuild\
- xcopy c:\pillow-depends\*.tar.gz c:\pillow\winbuild\
- xcopy /s c:\pillow-depends\test_images\* c:\pillow\tests\images
- cd c:\pillow\winbuild\
- ps: |
if ($env:PYTHON -eq "c:/vp/pypy3")
{
c:\pillow\winbuild\appveyor_install_pypy3.cmd
}
- ps: |
if ($env:PYTHON -eq "c:/msys64/mingw32")
{
c:\msys64\usr\bin\bash -l -c c:\\pillow\\winbuild\\appveyor_install_msys2_deps.sh
}
else
{
c:\python37\python.exe c:\pillow\winbuild\build_dep.py
c:\pillow\winbuild\build_deps.cmd
$host.SetShouldExit(0)
}
- 7z x ..\pillow-depends\nasm-2.14.02-win64.zip -oc:\
- curl -fsSL -o gs952.exe https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/gs952/gs952w32.exe
- gs952.exe /S
- path %path%;C:\Program Files (x86)\gs\gs9.52\bin
- path c:\nasm-2.14.02;C:\Program Files (x86)\gs\gs9.52\bin;%PATH%
- cd c:\pillow\winbuild\
- ps: |
c:\python37\python.exe c:\pillow\winbuild\build_prepare.py -v --depends=C:\pillow-depends\
c:\pillow\winbuild\build\build_dep_all.cmd
$host.SetShouldExit(0)
- path C:\pillow\winbuild\build\bin;%PATH%
build_script:
- ps: |
if ($env:PYTHON -eq "c:/msys64/mingw32")
{
c:\msys64\usr\bin\bash -l -c c:\\pillow\\winbuild\\appveyor_build_msys2.sh
Write-Host "through install"
c:\pillow\winbuild\build\build_pillow.cmd install
$host.SetShouldExit(0)
}
else
{
& $env:PYTHON/$env:EXECUTABLE c:\pillow\winbuild\build.py
$host.SetShouldExit(0)
}
- cd c:\pillow
- '%PYTHON%\%EXECUTABLE% selftest.py --installed'
@ -99,7 +68,7 @@ before_deploy:
- cd c:\pillow
- '%PYTHON%\%PIP_DIR%\pip.exe install wheel'
- cd c:\pillow\winbuild\
- '%PYTHON%\%EXECUTABLE% c:\pillow\winbuild\build.py --wheel'
- c:\pillow\winbuild\build\build_pillow.cmd bdist_wheel
- cd c:\pillow
- ps: Get-ChildItem .\dist\*.* | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }

View File

@ -1,8 +1,22 @@
#!/bin/bash
aptget_update()
{
if [ ! -z $1 ]; then
echo ""
echo "Retrying apt-get update..."
echo ""
fi
output=`sudo apt-get update 2>&1`
echo "$output"
if [[ $output == *[WE]:\ * ]]; then
return 1
fi
}
aptget_update || aptget_update retry || aptget_update retry
set -e
sudo apt-get update
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
cmake imagemagick libharfbuzz-dev libfribidi-dev
@ -20,7 +34,7 @@ if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then
# arm64, ppc64le, s390x CPUs:
# "ERROR: Could not find a version that satisfies the requirement pyqt5"
if [[ $TRAVIS_CPU_ARCH == "amd64" ]]; then
sudo apt-get -qq install pyqt5-dev-tools
sudo apt-get -qq install libxcb-xinerama0 pyqt5-dev-tools
pip install pyqt5
fi
fi

View File

@ -13,7 +13,7 @@ jobs:
name: Python ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: pip cache
uses: actions/cache@v1

View File

@ -14,6 +14,7 @@ jobs:
arch,
ubuntu-16.04-xenial-amd64,
ubuntu-18.04-bionic-amd64,
ubuntu-20.04-focal-amd64,
debian-9-stretch-x86,
debian-10-buster-x86,
centos-6-amd64,
@ -21,15 +22,15 @@ jobs:
centos-8-amd64,
amazon-1-amd64,
amazon-2-amd64,
fedora-30-amd64,
fedora-31-amd64,
fedora-32-amd64,
]
dockerTag: [master]
name: ${{ matrix.docker }}
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Build system information
run: python .github/workflows/system-info.py
@ -46,7 +47,6 @@ jobs:
sudo chown -R runner $GITHUB_WORKSPACE
- name: After success
if: success()
run: |
PATH="$PATH:~/.local/bin"
docker start pillow_container
@ -59,7 +59,6 @@ jobs:
MATRIX_DOCKER: ${{ matrix.docker }}
- name: Upload coverage
if: success()
uses: codecov/codecov-action@v1
with:
flags: GHA_Docker

View File

@ -4,7 +4,6 @@ on: [push, pull_request]
jobs:
build:
runs-on: windows-2019
strategy:
fail-fast: false
@ -27,14 +26,16 @@ jobs:
name: Python ${{ matrix.python-version }} ${{ matrix.architecture }}
steps:
- uses: actions/checkout@v1
- name: Checkout Pillow
uses: actions/checkout@v2
- uses: actions/checkout@v1
- name: Checkout cached dependencies
uses: actions/checkout@v2
with:
repository: python-pillow/pillow-depends
ref: master
path: winbuild\depends
- name: Cache
- name: Cache pip
uses: actions/cache@v1
with:
path: ~\AppData\Local\pip\Cache
@ -51,291 +52,68 @@ jobs:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }}
- name: Build system information
- name: Print 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
run: python -m pip install wheel pytest pytest-cov
- name: Fetch dependencies
- name: Prepare 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"
7z x winbuild\depends\nasm-2.14.02-win64.zip "-o$env:RUNNER_WORKSPACE\"
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"
winbuild\depends\gs950w32.exe /S
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
xcopy /s winbuild\depends\test_images\* Tests\images\
& python.exe winbuild\build_prepare.py -v --python=$env:pythonLocation
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-9d
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
run: "& winbuild\\build\\build_dep_libjpeg.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
run: "& winbuild\\build\\build_dep_zlib.cmd"
- name: Build dependencies / LibTiff
run: "& winbuild\\build\\build_dep_libtiff.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.1.0
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
run: "& winbuild\\build\\build_dep_libwebp.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
run: "& winbuild\\build\\build_dep_freetype.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
run: "& winbuild\\build\\build_dep_lcms2.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
run: "& winbuild\\build\\build_dep_openjpeg.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
if: "github.event_name != 'push'"
run: "& winbuild\\build\\build_dep_libimagequant.cmd"
# for Raqm
# Raqm dependencies
- 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.4
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
run: "& winbuild\\build\\build_dep_harfbuzz.cmd"
- 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.9
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
run: "& winbuild\\build\\build_dep_fribidi.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
run: "& winbuild\\build\\build_dep_libraqm.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
& winbuild\build\build_pillow.cmd install
& $env:pythonLocation\python.exe selftest.py --installed
shell: pwsh
# failing with PyPy3
- name: Enable heap verification
if: "!contains(matrix.python-version, 'pypy')"
run: |
c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\python.exe
shell: cmd
run: "& 'C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x86\\gflags.exe' /p /enable $env:pythonLocation\\python.exe"
- 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 -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests
path %GITHUB_WORKSPACE%\\winbuild\\build\\bin;%PATH%
python.exe -m pytest -vx -W always --cov PIL --cov Tests --cov-report term --cov-report xml Tests
shell: cmd
- name: Prepare to upload errors
@ -352,38 +130,100 @@ jobs:
path: Tests/errors
- name: After success
if: success()
run: |
.ci/after_success.sh
shell: pwsh
- name: Upload coverage
if: success()
uses: codecov/codecov-action@v1
with:
file: ./coverage.xml
flags: GHA_Windows
name: ${{ runner.os }} Python ${{ matrix.python-version }}
file: ./coverage.xml
flags: GHA_Windows
name: ${{ runner.os }} Python ${{ matrix.python-version }} ${{ matrix.architecture }}
- name: Build wheel
id: wheel
if: "github.event_name == 'push' && !contains(matrix.python-version, 'pypy')"
if: "github.event_name == 'push'"
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
winbuild\\build\\build_pillow.cmd bdist_wheel"
shell: cmd
- uses: actions/upload-artifact@v1
if: "github.event_name == 'push' && !contains(matrix.python-version, 'pypy')"
- uses: actions/upload-artifact@v2
if: "github.event_name == 'push'"
with:
name: ${{ steps.wheel.outputs.dist }}
path: dist
path: dist\*.whl
msys:
runs-on: windows-2019
strategy:
fail-fast: false
matrix:
mingw: ["MINGW32", "MINGW64"]
include:
- mingw: "MINGW32"
package: "mingw-w64-i686"
- mingw: "MINGW64"
package: "mingw-w64-x86_64"
defaults:
run:
shell: bash.exe --login -eo pipefail "{0}"
env:
MSYSTEM: ${{ matrix.mingw }}
CHERE_INVOKING: 1
timeout-minutes: 30
name: MSYS2 ${{ matrix.mingw }}
steps:
- uses: actions/checkout@v2
- name: Set up shell
run: echo ::add-path::C:\msys64\usr\bin\
shell: pwsh
- name: Install Dependencies
run: |
pacman -S --noconfirm \
${{ matrix.package }}-python3-pip \
${{ matrix.package }}-python3-setuptools \
${{ matrix.package }}-python3-pytest \
${{ matrix.package }}-python3-pytest-cov \
${{ matrix.package }}-python3-cffi \
${{ matrix.package }}-python3-olefile \
${{ matrix.package }}-python3-numpy \
${{ matrix.package }}-python3-pyqt5 \
${{ matrix.package }}-python3-numpy \
${{ matrix.package }}-freetype \
${{ matrix.package }}-lcms2 \
${{ matrix.package }}-libwebp \
${{ matrix.package }}-libjpeg-turbo \
${{ matrix.package }}-openjpeg2 \
${{ matrix.package }}-libimagequant \
${{ matrix.package }}-libraqm \
${{ matrix.package }}-ghostscript \
subversion
python3 -m pip install pyroma
pushd depends && ./install_extra_test_images.sh && popd
- name: Build Pillow
run: |
# libtiff is unable to open files
CFLAGS="-coverage" python3 setup.py build_ext --disable-tiff install
- name: Test Pillow
run: |
python3 selftest.py --installed
python3 -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests
- name: Upload coverage
run: |
python3 -m pip install codecov
bash <(curl -s https://codecov.io/bash) -F GHA_Windows
env:
CODECOV_NAME: MSYS2 ${{ matrix.mingw }}

View File

@ -95,13 +95,17 @@ jobs:
name: errors
path: Tests/errors
- name: Docs
if: startsWith(matrix.os, 'ubuntu') && matrix.python-version == 3.8
run: |
pip install sphinx-rtd-theme
make doccheck
- name: After success
if: success()
run: |
.ci/after_success.sh
- name: Upload coverage
if: success()
run: bash <(curl -s https://codecov.io/bash) -F ${{ matrix.codecov-flag }}
env:
CODECOV_NAME: ${{ matrix.os }} Python ${{ matrix.python-version }}

View File

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
rev: 19.10b0
rev: 6bedb5c58a7d8c25aa9509f8217bc24e9797e90d # frozen: 19.10b0
hooks:
- id: black
args: ["--target-version", "py35"]
@ -8,25 +8,36 @@ repos:
files: \.py$
types: []
- repo: https://github.com/timothycrosley/isort
rev: 7c29dd9d55161704cfc45998c6f5c2c43d39264b # frozen: 4.3.21
hooks:
- id: isort
- repo: https://github.com/asottile/yesqa
rev: b13a51aa54142c59219c764e9f9362c049b439ed # frozen: v1.2.0
hooks:
- id: yesqa
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: ffbd448645bad2e7ca13f96fca5830058d27ccd5 # frozen: v1.1.7
hooks:
- id: remove-tabs
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.opt$)
- repo: https://gitlab.com/pycqa/flake8
rev: 3.7.9
rev: 735cfe7e1c57a8e05f660ba75de72313005af54a # frozen: 3.8.2
hooks:
- id: flake8
additional_dependencies: [flake8-2020, flake8-implicit-str-concat]
- repo: https://github.com/timothycrosley/isort
rev: 4.3.21
hooks:
- id: isort
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.5.1
rev: 0d7d077d6ed5624854f93ac601739c1804ebeb98 # frozen: v1.5.1
hooks:
- id: python-check-blanket-noqa
- id: rst-backticks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.5.0
rev: ebc15addedad713c86ef18ae9632c88e187dd0af # frozen: v3.1.0
hooks:
- id: check-merge-conflict
- id: check-yaml

View File

@ -28,6 +28,9 @@ matrix:
- python: "pypy3"
name: "PyPy3 Xenial"
- python: "3.9-dev"
name: "3.9-dev Xenial"
services: xvfb
- python: "3.8"
name: "3.8 Xenial"
services: xvfb

View File

@ -2,6 +2,54 @@
Changelog (Pillow)
==================
7.2.0 (unreleased)
------------------
- Use ImageFileDirectory_v2 in Image.Exif #4637
[radarhere]
- Corrected reading EXIF metadata without prefix #4677
[radarhere]
- Fixed drawing a jointed line with a sequence of numeric values #4580
[radarhere]
- Added support for 1-D NumPy arrays #4608
[radarhere]
- Parse orientation from XMP tags #4560
[radarhere]
- Speed up text layout by not rendering glyphs #4652
[nulano]
- Fixed ZeroDivisionError in Image.thumbnail #4625
[radarhere]
- Replaced TiffImagePlugin DEBUG with logging #4550
[radarhere]
- Fix repeatedly loading .gbr #4620
[ElinksFr, radarhere]
- JPEG: Truncate icclist instead of setting to None #4613
[homm]
- Fixes default offset for Exif #4594
[rodrigob, radarhere]
- Fixed bug when unpickling TIFF images #4565
[radarhere]
- Fix pickling WebP #4561
[hugovk, radarhere]
7.1.2 (2020-04-25)
------------------
- Raise an EOFError when seeking too far in PNG #4528
[radarhere]
7.1.1 (2020-04-02)
------------------
@ -5295,23 +5343,23 @@ Pre-fork
+ Added keyword options to the "save" method. The following options
are currently supported:
format option description
Format Option Description
--------------------------------------------------------
JPEG optimize minimize output file at the
expense of compression speed.
JPEG optimize Minimize output file at the
expense of compression speed.
JPEG progressive enable progressive output. the
option value is ignored.
JPEG progressive Enable progressive output.
The option value is ignored.
JPEG quality set compression quality (1-100).
the default value is 75.
JPEG quality Set compression quality (1-100).
The default value is 75.
JPEG smooth smooth dithered images. value
is strength (1-100). default is
off (0).
JPEG smooth Smooth dithered images.
Value is strength (1-100).
Default is off (0).
PNG optimize minimize output file at the
expense of compression speed.
PNG optimize Minimize output file at the
expense of compression speed.
Expect more options in future releases. Also note that
file writers silently ignore unknown options.
@ -5332,31 +5380,31 @@ Pre-fork
+ Various improvements to the sample scripts:
"pilconvert" Carries out some extra tricks in order to make
the resulting file as small as possible.
the resulting file as small as possible.
"explode" (NEW) Split an image sequence into individual frames.
"explode" (NEW) Split an image sequence into individual frames.
"gifmaker" (NEW) Convert a sequence file into a GIF animation.
Note that the GIF encoder create "uncompressed" GIF
files, so animations created by this script are
rather large (typically 2-5 times the compressed
sizes).
"gifmaker" (NEW) Convert a sequence file into a GIF animation.
Note that the GIF encoder create "uncompressed" GIF
files, so animations created by this script are
rather large (typically 2-5 times the compressed
sizes).
"image2py" (NEW) Convert a single image to a python module. See
comments in this script for details.
"image2py" (NEW) Convert a single image to a python module. See
comments in this script for details.
"player" If multiple images are given on the command line,
they are interpreted as frames in a sequence. The
script assumes that they all have the same size.
Also note that this script now can play FLI/FLC
and GIF animations.
"player" If multiple images are given on the command line,
they are interpreted as frames in a sequence. The
script assumes that they all have the same size.
Also note that this script now can play FLI/FLC
and GIF animations.
This player can also execute embedded Python
animation applets (ARG format only).
"viewer" Transparent images ("P" with transparency property,
and "RGBA") are superimposed on the standard Tk back-
ground.
"viewer" Transparent images ("P" with transparency property,
and "RGBA") are superimposed on the standard Tk back-
ground.
+ Fixed colour argument to "new". For multilayer images, pass a
tuple: (Red, Green, Blue), (Red, Green, Blue, Alpha), or (Cyan,

View File

@ -21,10 +21,8 @@ exclude .appveyor.yml
exclude .coveragerc
exclude .editorconfig
exclude .readthedocs.yml
exclude azure-pipelines.yml
exclude codecov.yml
global-exclude .git*
global-exclude *.pyc
global-exclude *.so
prune .azure-pipelines
prune .ci

View File

@ -67,7 +67,7 @@ debug:
CFLAGS='-g -O0' python3 setup.py build_ext install > /dev/null
install-req:
pip install -r requirements.txt
python3 -m pip install -r requirements.txt
install-venv:
virtualenv .

View File

@ -6,7 +6,10 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th.
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
* [ ] Develop and prepare release in `master` branch.
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) and [AppVeyor CI](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `master` branch.
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions),
[Travis CI](https://travis-ci.org/github/python-pillow/Pillow) and
[AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm
passing tests in `master` branch.
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI.
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
* [ ] Update `CHANGES.rst`.
@ -38,12 +41,19 @@ Released as needed for security, installation or critical bug fixes.
git checkout -t remotes/origin/5.2.x
```
* [ ] Cherry pick individual commits from `master` branch to release branch e.g. `5.2.x`.
* [ ] Check [Travis CI](https://travis-ci.org/python-pillow/Pillow) to confirm passing tests in release branch e.g. `5.2.x`.
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions),
[Travis CI](https://travis-ci.org/github/python-pillow/Pillow) and
[AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm
passing tests in release branch e.g. `5.2.x`.
* [ ] In compliance with [PEP 440](https://www.python.org/dev/peps/pep-0440/), update version identifier in `src/PIL/_version.py`
* [ ] Run pre-release check via `make release-test`.
* [ ] Create tag for release e.g.:
```bash
git tag 5.2.1
git push
git push --tags
```
* [ ] Create source distributions e.g.:

View File

@ -5,5 +5,5 @@ from PIL import Image
def test_j2k_overflow(tmp_path):
im = Image.new("RGBA", (1024, 131584))
target = str(tmp_path / "temp.jpc")
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(target)

View File

@ -9,6 +9,6 @@ def test_libtiff_segfault():
libtiff >= 4.0.0
"""
with pytest.raises(IOError):
with pytest.raises(OSError):
with Image.open(TEST_FILE) as im:
im.load()

View File

@ -6,6 +6,7 @@ import logging
import os
import shutil
import sys
import sysconfig
import tempfile
from io import BytesIO
@ -272,12 +273,8 @@ def on_github_actions():
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
)
# GitHub Actions, Travis and AppVeyor have "CI"
return "CI" in os.environ
def is_big_endian():
@ -292,6 +289,10 @@ def is_pypy():
return hasattr(sys, "pypy_translation_info")
def is_mingw():
return sysconfig.get_platform() == "mingw"
if sys.platform == "win32":
IMCONVERT = os.environ.get("MAGICK_HOME", "")
if IMCONVERT:

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

View File

@ -32,7 +32,7 @@ def test_load():
with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler
with pytest.raises(IOError):
with pytest.raises(OSError):
im.load()
@ -42,5 +42,5 @@ def test_save(tmp_path):
tmpfile = str(tmp_path / "temp.bufr")
# Act / Assert: stub cannot save without an implemented handler
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(tmpfile)

View File

@ -138,7 +138,7 @@ def test_short_header():
def short_header():
Image.open(BytesIO(img_file[:119]))
with pytest.raises(IOError):
with pytest.raises(OSError):
short_header()
@ -152,7 +152,7 @@ def test_short_file():
with Image.open(BytesIO(img_file[:-100])) as im:
im.load()
with pytest.raises(IOError):
with pytest.raises(OSError):
short_file()

View File

@ -30,7 +30,7 @@ def test_load():
with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler
with pytest.raises(IOError):
with pytest.raises(OSError):
im.load()
@ -41,7 +41,7 @@ def test_save():
dummy_filename = "dummy.filename"
# Act / Assert: stub cannot save without an implemented handler
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(dummy_filename)
with pytest.raises(IOError):
with pytest.raises(OSError):
FitsStubImagePlugin._save(im, dummy_fp, dummy_filename)

View File

@ -19,5 +19,5 @@ def test_invalid_file():
def test_fpx_invalid_number_of_bands():
with pytest.raises(IOError, match="Invalid number of bands"):
with pytest.raises(OSError, match="Invalid number of bands"):
Image.open("Tests/images/input_bw_five_bands.fpx")

View File

@ -15,3 +15,11 @@ def test_gbr_file():
with Image.open("Tests/images/gbr.gbr") as im:
with Image.open("Tests/images/gbr.png") as target:
assert_image_equal(target, im)
def test_multiple_load_operations():
with Image.open("Tests/images/gbr.gbr") as im:
im.load()
im.load()
with Image.open("Tests/images/gbr.png") as target:
assert_image_equal(target, im)

View File

@ -32,7 +32,7 @@ def test_load():
with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler
with pytest.raises(IOError):
with pytest.raises(OSError):
im.load()
@ -42,5 +42,5 @@ def test_save(tmp_path):
tmpfile = str(tmp_path / "temp.grib")
# Act / Assert: stub cannot save without an implemented handler
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(tmpfile)

View File

@ -30,7 +30,7 @@ def test_load():
with Image.open(TEST_FILE) as im:
# Act / Assert: stub cannot load without an implemented handler
with pytest.raises(IOError):
with pytest.raises(OSError):
im.load()
@ -41,7 +41,7 @@ def test_save():
dummy_filename = "dummy.filename"
# Act / Assert: stub cannot save without an implemented handler
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(dummy_filename)
with pytest.raises(IOError):
with pytest.raises(OSError):
Hdf5StubImagePlugin._save(im, dummy_fp, dummy_filename)

View File

@ -147,7 +147,7 @@ class TestFileJpeg:
with Image.open("Tests/images/icc_profile_big.jpg") as im:
f = str(tmp_path / "temp.jpg")
icc_profile = im.info["icc_profile"]
# Should not raise IOError for image with icc larger than image size.
# Should not raise OSError for image with icc larger than image size.
im.save(
f,
format="JPEG",
@ -219,7 +219,7 @@ class TestFileJpeg:
gps_index = 34853
expected_exif_gps = {
0: b"\x00\x00\x00\x01",
2: (4294967295, 1),
2: 4294967295,
5: b"\x01",
30: 65535,
29: "1999:99:99 99:99:99",
@ -241,7 +241,7 @@ class TestFileJpeg:
36867: "2099:09:29 10:10:10",
34853: {
0: b"\x00\x00\x00\x01",
2: (4294967295, 1),
2: 4294967295,
5: b"\x01",
30: 65535,
29: "1999:99:99 99:99:99",
@ -253,11 +253,11 @@ class TestFileJpeg:
271: "Make",
272: "XXX-XXX",
305: "PIL",
42034: ((1, 1), (1, 1), (1, 1), (1, 1)),
42034: (1, 1, 1, 1),
42035: "LensMake",
34856: b"\xaa\xaa\xaa\xaa\xaa\xaa",
282: (4294967295, 1),
33434: (4294967295, 1),
282: 4294967295,
33434: 4294967295,
}
with Image.open("Tests/images/exif_gps.jpg") as im:
@ -379,14 +379,14 @@ class TestFileJpeg:
ImageFile.LOAD_TRUNCATED_IMAGES = False
assert im.getbbox() is not None
def test_truncated_jpeg_throws_IOError(self):
def test_truncated_jpeg_throws_oserror(self):
filename = "Tests/images/truncated_jpeg.jpg"
with Image.open(filename) as im:
with pytest.raises(IOError):
with pytest.raises(OSError):
im.load()
# Test that the error is raised if loaded a second time
with pytest.raises(IOError):
with pytest.raises(OSError):
im.load()
def test_qtables(self, tmp_path):
@ -500,7 +500,7 @@ class TestFileJpeg:
def test_load_djpeg(self):
with Image.open(TEST_FILE) as img:
img.load_djpeg()
assert_image_similar(img, Image.open(TEST_FILE), 0)
assert_image_similar(img, Image.open(TEST_FILE), 5)
@pytest.mark.skipif(not cjpeg_available(), reason="cjpeg not available")
def test_save_cjpeg(self, tmp_path):
@ -552,7 +552,7 @@ class TestFileJpeg:
out = BytesIO()
for mode in ["LA", "La", "RGBA", "RGBa", "P"]:
img = Image.new(mode, (20, 20))
with pytest.raises(IOError):
with pytest.raises(OSError):
img.save(out, "JPEG")
def test_save_tiff_with_dpi(self, tmp_path):
@ -647,6 +647,19 @@ class TestFileJpeg:
# OSError for unidentified image.
assert im.info.get("dpi") == (72, 72)
def test_exif_x_resolution(self, tmp_path):
with Image.open("Tests/images/flower.jpg") as im:
exif = im.getexif()
assert exif[282] == 180
out = str(tmp_path / "out.jpg")
with pytest.warns(None) as record:
im.save(out, exif=exif)
assert len(record) == 0
with Image.open(out) as reloaded:
assert reloaded.getexif()[282] == 180
def test_invalid_exif_x_resolution(self):
# When no x or y resolution is defined in EXIF
with Image.open("Tests/images/invalid-exif-without-x-resolution.jpg") as im:
@ -689,6 +702,10 @@ class TestFileJpeg:
apps_13_lengths = [len(v) for k, v in im.applist if k == "APP13"]
assert [65504, 24] == apps_13_lengths
def test_icc_after_SOF(self):
with Image.open("Tests/images/icc-after-SOF.jpg") as im:
assert im.info["icc_profile"] == b"profile"
@pytest.mark.skipif(not is_win32(), reason="Windows only")
@skip_unless_feature("jpg")
@ -702,7 +719,7 @@ class TestFileCloseW32:
im = Image.open(tmpfile)
fp = im.fp
assert not fp.closed
with pytest.raises(WindowsError):
with pytest.raises(OSError):
os.remove(tmpfile)
im.load()
assert fp.closed

View File

@ -218,7 +218,7 @@ def test_16bit_jp2_roundtrips():
def test_unbound_local():
# prepatch, a malformed jp2 file could cause an UnboundLocalError exception.
with pytest.raises(IOError):
with pytest.raises(OSError):
Image.open("Tests/images/unbound_variable.jp2")

View File

@ -479,11 +479,11 @@ class TestFileLibTiff(LibTiffTestCase):
im = hopper("RGB")
out = str(tmp_path / "temp.tif")
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(out, compression="tiff_ccitt")
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(out, compression="group3")
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(out, compression="group4")
def test_fp_leak(self):
@ -831,7 +831,7 @@ class TestFileLibTiff(LibTiffTestCase):
def test_realloc_overflow(self):
TiffImagePlugin.READ_LIBTIFF = True
with Image.open("Tests/images/tiff_overflow_rows_per_strip.tif") as im:
with pytest.raises(IOError) as e:
with pytest.raises(OSError) as e:
im.load()
# Assert that the error code is IMAGING_CODEC_MEMORY

View File

@ -86,5 +86,5 @@ def test_cannot_save_wrong_mode(tmp_path):
filename = str(tmp_path / "temp.msp")
# Act/Assert
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(filename)

View File

@ -72,19 +72,19 @@ def test_p_mode(tmp_path):
roundtrip(tmp_path, mode)
def test_l_ioerror(tmp_path):
def test_l_oserror(tmp_path):
# Arrange
mode = "L"
# Act / Assert
with pytest.raises(IOError):
with pytest.raises(OSError):
helper_save_as_palm(tmp_path, mode)
def test_rgb_ioerror(tmp_path):
def test_rgb_oserror(tmp_path):
# Arrange
mode = "RGB"
# Act / Assert
with pytest.raises(IOError):
with pytest.raises(OSError):
helper_save_as_palm(tmp_path, mode)

View File

@ -172,7 +172,7 @@ def test_pdf_open(tmp_path):
def test_pdf_append_fails_on_nonexistent_file():
im = hopper("RGB")
with tempfile.TemporaryDirectory() as temp_dir:
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(os.path.join(temp_dir, "nonexistent.pdf"), append=True)

View File

@ -105,7 +105,7 @@ class TestFilePng:
# file was checked into Subversion as a text file.
test_file = "Tests/images/broken.png"
with pytest.raises(IOError):
with pytest.raises(OSError):
Image.open(test_file)
def test_bad_text(self):
@ -334,7 +334,7 @@ class TestFilePng:
def test_verify_struct_error(self):
# Check open/load/verify exception (#1755)
# offsets to test, -10: breaks in i32() in read. (IOError)
# offsets to test, -10: breaks in i32() in read. (OSError)
# -13: breaks in crc, txt chunk.
# -14: malformed chunk
@ -344,7 +344,7 @@ class TestFilePng:
with Image.open(BytesIO(test_file)) as im:
assert im.fp is not None
with pytest.raises((IOError, SyntaxError)):
with pytest.raises((OSError, SyntaxError)):
im.verify()
def test_verify_ignores_crc_error(self):
@ -463,7 +463,7 @@ class TestFilePng:
data = b"\x89" + fd.read()
pngfile = BytesIO(data)
with pytest.raises(IOError):
with pytest.raises(OSError):
Image.open(pngfile)
def test_trns_rgb(self):
@ -575,13 +575,13 @@ class TestFilePng:
# Raises a SyntaxError in load_end
with Image.open("Tests/images/broken_data_stream.png") as im:
with pytest.raises(IOError):
with pytest.raises(OSError):
assert isinstance(im.text, dict)
# Raises a UnicodeDecodeError in load_end
with Image.open("Tests/images/truncated_image.png") as im:
# The file is truncated
with pytest.raises(IOError):
with pytest.raises(OSError):
im.text()
ImageFile.LOAD_TRUNCATED_IMAGES = True
assert isinstance(im.text, dict)
@ -591,18 +591,27 @@ class TestFilePng:
with Image.open("Tests/images/hopper_idat_after_image_end.png") as im:
assert im.text == {"TXT": "VALUE", "ZIP": "VALUE"}
@pytest.mark.parametrize(
"test_file",
[
"Tests/images/exif.png", # With an EXIF chunk
"Tests/images/exif_imagemagick.png", # With an ImageMagick zTXt chunk
],
)
def test_exif(self, test_file):
with Image.open(test_file) as im:
def test_exif(self):
# With an EXIF chunk
with Image.open("Tests/images/exif.png") as im:
exif = im._getexif()
assert exif[274] == 1
# With an ImageMagick zTXt chunk
with Image.open("Tests/images/exif_imagemagick.png") as im:
exif = im._getexif()
assert exif[274] == 1
# Assert that info still can be extracted
# when the image is no longer a PngImageFile instance
exif = im.copy().getexif()
assert exif[274] == 1
# With XMP tags
with Image.open("Tests/images/xmp_tags_orientation.png") as im:
exif = im.getexif()
assert exif[274] == 3
def test_exif_save(self, tmp_path):
with Image.open("Tests/images/exif.png") as im:
test_file = str(tmp_path / "temp.png")

View File

@ -64,7 +64,7 @@ def test_neg_ppm():
# has been removed. The default opener doesn't accept negative
# sizes.
with pytest.raises(IOError):
with pytest.raises(OSError):
Image.open("Tests/images/negative_size.ppm")

View File

@ -125,5 +125,5 @@ def test_combined_larger_than_size():
# If we instead take the 'size' of the extra data field as the source of truth,
# then the seek can't be negative
with pytest.raises(IOError):
with pytest.raises(OSError):
Image.open("Tests/images/combined_larger_than_size.psd")

View File

@ -134,7 +134,7 @@ def test_is_int_not_a_number():
def test_invalid_file():
invalid_file = "Tests/images/invalid.spider"
with pytest.raises(IOError):
with pytest.raises(OSError):
Image.open(invalid_file)

View File

@ -196,7 +196,7 @@ class TestFileTiff:
def test_save_unsupported_mode(self, tmp_path):
im = hopper("HSV")
outfile = str(tmp_path / "temp.tif")
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(outfile)
def test_little_endian(self):
@ -249,7 +249,7 @@ class TestFileTiff:
assert im.getextrema() == (-3.140936851501465, 3.140684127807617)
def test_unknown_pixel_mode(self):
with pytest.raises(IOError):
with pytest.raises(OSError):
Image.open("Tests/images/hopper_unknown_pixel_mode.tif")
def test_n_frames(self):
@ -614,7 +614,7 @@ class TestFileTiffW32:
im = Image.open(tmpfile)
fp = im.fp
assert not fp.closed
with pytest.raises(WindowsError):
with pytest.raises(OSError):
os.remove(tmpfile)
im.load()
assert fp.closed

View File

@ -22,7 +22,7 @@ class TestUnsupportedWebp:
WebPImagePlugin.SUPPORTED = False
file_path = "Tests/images/hopper.webp"
pytest.warns(UserWarning, lambda: pytest.raises(IOError, Image.open, file_path))
pytest.warns(UserWarning, lambda: pytest.raises(OSError, Image.open, file_path))
if HAVE_WEBP:
WebPImagePlugin.SUPPORTED = True

View File

@ -30,6 +30,15 @@ def test_read_exif_metadata():
assert exif_data == expected_exif
def test_read_exif_metadata_without_prefix():
with Image.open("Tests/images/flower2.webp") as im:
# Assert prefix is not present
assert im.info["exif"][:6] != b"Exif\x00\x00"
exif = im.getexif()
assert exif[305] == "Adobe Photoshop CS6 (Macintosh)"
def test_write_exif_metadata():
file_path = "Tests/images/flower.jpg"
test_buffer = BytesIO()

View File

@ -66,7 +66,7 @@ def test_load_set_dpi():
assert im.size == (164, 164)
with Image.open("Tests/images/drawing_wmf_ref_144.png") as expected:
assert_image_similar(im, expected, 2.0)
assert_image_similar(im, expected, 2.1)
def test_save(tmp_path):
@ -74,5 +74,5 @@ def test_save(tmp_path):
for ext in [".wmf", ".emf"]:
tmpfile = str(tmp_path / ("temp" + ext))
with pytest.raises(IOError):
with pytest.raises(OSError):
im.save(tmpfile)

View File

@ -68,8 +68,8 @@ def test_textsize(request, tmp_path):
(dx, dy) = font.getsize(chr(i))
assert dy == 20
assert dx in (0, 10)
for l in range(len(message)):
msg = message[: l + 1]
for i in range(len(message)):
msg = message[: i + 1]
assert font.getsize(msg) == (len(msg) * 10, 20)

View File

@ -103,8 +103,8 @@ def _test_textsize(request, tmp_path, encoding):
assert dy == 20
assert dx in (0, 10)
message = charsets[encoding]["message"].encode(encoding)
for l in range(len(message)):
msg = message[: l + 1]
for i in range(len(message)):
msg = message[: i + 1]
assert font.getsize(msg) == (len(msg) * 10, 20)

View File

@ -57,7 +57,7 @@ class TestImage:
assert str(e.value) == "unrecognized image mode"
def test_exception_inheritance(self):
assert issubclass(UnidentifiedImageError, IOError)
assert issubclass(UnidentifiedImageError, OSError)
def test_sanity(self):
@ -687,5 +687,5 @@ class TestRegistry:
assert enc.args == ("RGB", "args", "extra")
def test_encode_registry_fail(self):
with pytest.raises(IOError):
with pytest.raises(OSError):
Image._getencoder("RGB", "DoesNotExist", ("args",), extra=("extra",))

View File

@ -112,12 +112,12 @@ def test_kernel_not_enough_coefficients():
def test_consistency_3x3():
with Image.open("Tests/images/hopper.bmp") as source:
with Image.open("Tests/images/hopper_emboss.bmp") as reference:
kernel = ImageFilter.Kernel( # noqa: E127
kernel = ImageFilter.Kernel(
(3, 3),
# fmt: off
(-1, -1, 0,
-1, 0, 1,
0, 1, 1),
0, 1, 1),
# fmt: on
0.3,
)
@ -134,14 +134,14 @@ def test_consistency_3x3():
def test_consistency_5x5():
with Image.open("Tests/images/hopper.bmp") as source:
with Image.open("Tests/images/hopper_emboss_more.bmp") as reference:
kernel = ImageFilter.Kernel( # noqa: E127
kernel = ImageFilter.Kernel(
(5, 5),
# fmt: off
(-1, -1, -1, -1, 0,
-1, -1, -1, 0, 1,
-1, -1, 0, 1, 1,
-1, 0, 1, 1, 1,
0, 1, 1, 1, 1),
0, 1, 1, 1, 1),
# fmt: on
0.3,
)

View File

@ -174,8 +174,10 @@ def assert_compare_images(a, b, max_average_diff, max_diff=255):
average_diff = sum(i * num for i, num in enumerate(ch_hist)) / (
a.size[0] * a.size[1]
)
msg = "average pixel value difference {:.4f} > expected {:.4f} "
"for '{}' band".format(average_diff, max_average_diff, band)
msg = (
"average pixel value difference {:.4f} > expected {:.4f} "
"for '{}' band".format(average_diff, max_average_diff, band)
)
assert max_average_diff >= average_diff, msg
last_diff = [i for i, num in enumerate(ch_hist) if num > 0][-1]

View File

@ -63,6 +63,12 @@ def test_aspect():
assert im.size == (75, 23) # ratio is 3.260869565217
def test_division_by_zero():
im = Image.new("L", (200, 2))
im.thumbnail((75, 75))
assert im.size == (75, 1)
def test_float():
im = Image.new("L", (128, 128))
im.thumbnail((99.9, 99.9))

View File

@ -484,10 +484,10 @@ def assert_aux_channel_preserved(mode, transform_in_place, preserved_channel):
def create_test_image():
# set up test image with something interesting in the tested aux channel.
# fmt: off
nine_grid_deltas = [ # noqa: E131
nine_grid_deltas = [
(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 0), (0, 1),
(1, -1), (1, 0), (1, 1),
(0, -1), (0, 0), (0, 1),
(1, -1), (1, 0), (1, 1),
]
# fmt: on
chans = []

View File

@ -569,6 +569,20 @@ def test_polygon_kite():
assert_image_equal(im, Image.open(expected))
def test_polygon_1px_high():
# Test drawing a 1px high polygon
# Arrange
im = Image.new("RGB", (3, 3))
draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_polygon_1px_high.png"
# Act
draw.polygon([(0, 1), (0, 1), (2, 1), (2, 1)], "#f00")
# Assert
assert_image_equal(im, Image.open(expected))
def helper_rectangle(bbox):
# Arrange
im = Image.new("RGB", (W, H))
@ -911,28 +925,93 @@ def test_wide_line_dot():
assert_image_similar(im, Image.open(expected), 1)
def test_line_joint():
@pytest.mark.parametrize(
"xy",
[
[
(400, 280),
(380, 280),
(450, 280),
(440, 120),
(350, 200),
(310, 280),
(300, 280),
(250, 280),
(250, 200),
(150, 200),
(150, 260),
(50, 200),
(150, 50),
(250, 100),
],
(
400,
280,
380,
280,
450,
280,
440,
120,
350,
200,
310,
280,
300,
280,
250,
280,
250,
200,
150,
200,
150,
260,
50,
200,
150,
50,
250,
100,
),
[
400,
280,
380,
280,
450,
280,
440,
120,
350,
200,
310,
280,
300,
280,
250,
280,
250,
200,
150,
200,
150,
260,
50,
200,
150,
50,
250,
100,
],
],
)
def test_line_joint(xy):
im = Image.new("RGB", (500, 325))
draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_line_joint_curve.png"
# Act
xy = [
(400, 280),
(380, 280),
(450, 280),
(440, 120),
(350, 200),
(310, 280),
(300, 280),
(250, 280),
(250, 200),
(150, 200),
(150, 260),
(50, 200),
(150, 50),
(250, 100),
]
draw.line(xy, GRAY, 50, "curve")
# Assert

View File

@ -71,7 +71,7 @@ class TestImageFile:
im1, im2 = roundtrip("JPEG") # lossy compression
assert_image(im1, im2.mode, im2.size)
with pytest.raises(IOError):
with pytest.raises(OSError):
roundtrip("PDF")
def test_ico(self):
@ -95,7 +95,13 @@ class TestImageFile:
def test_raise_ioerror(self):
with pytest.raises(IOError):
ImageFile.raise_ioerror(1)
with pytest.warns(DeprecationWarning) as record:
ImageFile.raise_ioerror(1)
assert len(record) == 1
def test_raise_oserror(self):
with pytest.raises(OSError):
ImageFile.raise_oserror(1)
def test_raise_typeerror(self):
with pytest.raises(TypeError):
@ -107,17 +113,17 @@ class TestImageFile:
input = f.read()
p = ImageFile.Parser()
p.feed(input)
with pytest.raises(IOError):
with pytest.raises(OSError):
p.close()
@skip_unless_feature("zlib")
def test_truncated_with_errors(self):
with Image.open("Tests/images/truncated_image.png") as im:
with pytest.raises(IOError):
with pytest.raises(OSError):
im.load()
# Test that the error is raised if loaded a second time
with pytest.raises(IOError):
with pytest.raises(OSError):
im.load()
@skip_unless_feature("zlib")
@ -132,7 +138,7 @@ class TestImageFile:
@skip_unless_feature("zlib")
def test_broken_datastream_with_errors(self):
with Image.open("Tests/images/broken_data_stream.png") as im:
with pytest.raises(IOError):
with pytest.raises(OSError):
im.load()
@skip_unless_feature("zlib")
@ -255,9 +261,9 @@ class TestPyDecoder:
with Image.open(out) as reloaded:
reloaded_exif = reloaded.getexif()
assert reloaded_exif[258] == 8
assert 40960 not in exif
assert 40960 not in reloaded_exif
assert reloaded_exif[40963] == 455
assert exif[11] == "Pillow test"
assert reloaded_exif[11] == "Pillow test"
with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Big endian
exif = im.getexif()
@ -275,9 +281,9 @@ class TestPyDecoder:
with Image.open(out) as reloaded:
reloaded_exif = reloaded.getexif()
assert reloaded_exif[258] == 8
assert 40960 not in exif
assert 34665 not in reloaded_exif
assert reloaded_exif[40963] == 455
assert exif[305] == "Pillow test"
assert reloaded_exif[305] == "Pillow test"
@skip_unless_feature("webp")
@skip_unless_feature("webp_anim")
@ -296,7 +302,7 @@ class TestPyDecoder:
reloaded_exif = reloaded.getexif()
assert reloaded_exif[258] == 8
assert reloaded_exif[40963] == 455
assert exif[305] == "Pillow test"
assert reloaded_exif[305] == "Pillow test"
im.save(out, exif=exif)
check_exif()

View File

@ -393,14 +393,14 @@ class TestImageFont:
filename = "somefilenamethatdoesntexist.ttf"
# Act/Assert
with pytest.raises(IOError):
with pytest.raises(OSError):
ImageFont.load_path(filename)
with pytest.raises(IOError):
with pytest.raises(OSError):
ImageFont.truetype(filename)
def test_load_non_font_bytes(self):
with open("Tests/images/hopper.jpg", "rb") as f:
with pytest.raises(IOError):
with pytest.raises(OSError):
ImageFont.truetype(f)
def test_default_font(self):
@ -615,9 +615,9 @@ class TestImageFont:
font.get_variation_axes()
return
with pytest.raises(IOError):
with pytest.raises(OSError):
font.get_variation_names()
with pytest.raises(IOError):
with pytest.raises(OSError):
font.get_variation_axes()
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf")
@ -660,6 +660,22 @@ class TestImageFont:
{"name": b"Size", "minimum": 0, "maximum": 300, "default": 0}
]
def _check_text(self, font, path, epsilon):
im = Image.new("RGB", (100, 75), "white")
d = ImageDraw.Draw(im)
d.text((10, 10), "Text", font=font, fill="black")
try:
with Image.open(path) as expected:
assert_image_similar(im, expected, epsilon)
except AssertionError:
if "_adobe" in path:
path = path.replace("_adobe", "_adobe_older_harfbuzz")
with Image.open(path) as expected:
assert_image_similar(im, expected, epsilon)
else:
raise
def test_variation_set_by_name(self):
font = self.get_font()
@ -669,28 +685,20 @@ class TestImageFont:
font.set_variation_by_name("Bold")
return
with pytest.raises(IOError):
with pytest.raises(OSError):
font.set_variation_by_name("Bold")
def _check_text(font, path, epsilon):
im = Image.new("RGB", (100, 75), "white")
d = ImageDraw.Draw(im)
d.text((10, 10), "Text", font=font, fill="black")
with Image.open(path) as expected:
assert_image_similar(im, expected, epsilon)
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
_check_text(font, "Tests/images/variation_adobe.png", 11)
self._check_text(font, "Tests/images/variation_adobe.png", 11)
for name in ["Bold", b"Bold"]:
font.set_variation_by_name(name)
_check_text(font, "Tests/images/variation_adobe_name.png", 11)
self._check_text(font, "Tests/images/variation_adobe_name.png", 11)
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
_check_text(font, "Tests/images/variation_tiny.png", 40)
self._check_text(font, "Tests/images/variation_tiny.png", 40)
for name in ["200", b"200"]:
font.set_variation_by_name(name)
_check_text(font, "Tests/images/variation_tiny_name.png", 40)
self._check_text(font, "Tests/images/variation_tiny_name.png", 40)
def test_variation_set_by_axes(self):
font = self.get_font()
@ -701,24 +709,16 @@ class TestImageFont:
font.set_variation_by_axes([100])
return
with pytest.raises(IOError):
with pytest.raises(OSError):
font.set_variation_by_axes([500, 50])
def _check_text(font, path, epsilon):
im = Image.new("RGB", (100, 75), "white")
d = ImageDraw.Draw(im)
d.text((10, 10), "Text", font=font, fill="black")
with Image.open(path) as expected:
assert_image_similar(im, expected, epsilon)
font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36)
font.set_variation_by_axes([500, 50])
_check_text(font, "Tests/images/variation_adobe_axes.png", 5.1)
self._check_text(font, "Tests/images/variation_adobe_axes.png", 5.1)
font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36)
font.set_variation_by_axes([100])
_check_text(font, "Tests/images/variation_tiny_axes.png", 32.5)
self._check_text(font, "Tests/images/variation_tiny_axes.png", 32.5)
@skip_unless_feature("raqm")

View File

@ -60,7 +60,7 @@ def test_complex_unicode_text():
target = "Tests/images/test_complex_unicode_text2.png"
with Image.open(target) as target_img:
assert_image_similar(im, target_img, 2.3)
assert_image_similar(im, target_img, 2.33)
def test_text_direction_rtl():

View File

@ -31,23 +31,23 @@ class TestImageGrab:
im2 = ImageGrab.grab(xdisplay="")
assert_image(im2, im2.mode, im2.size)
except IOError as e:
except OSError as e:
pytest.skip(str(e))
@pytest.mark.skipif(Image.core.HAVE_XCB, reason="tests missing XCB")
def test_grab_no_xcb(self):
if sys.platform not in ("win32", "darwin"):
with pytest.raises(IOError) as e:
with pytest.raises(OSError) as e:
ImageGrab.grab()
assert str(e.value).startswith("Pillow was built without XCB support")
with pytest.raises(IOError) as e:
with pytest.raises(OSError) as e:
ImageGrab.grab(xdisplay="")
assert str(e.value).startswith("Pillow was built without XCB support")
@pytest.mark.skipif(not Image.core.HAVE_XCB, reason="requires XCB")
def test_grab_invalid_xdisplay(self):
with pytest.raises(IOError) as e:
with pytest.raises(OSError) as e:
ImageGrab.grab(xdisplay="error.test:0.0")
assert str(e.value).startswith("X connection failed")

View File

@ -145,5 +145,5 @@ def test_2bit_palette(tmp_path):
def test_invalid_palette():
with pytest.raises(IOError):
with pytest.raises(OSError):
ImagePalette.load("Tests/images/hopper.jpg")

View File

@ -1,7 +1,7 @@
import pytest
from PIL import Image, ImageShow
from .helper import hopper, is_win32, on_ci, on_github_actions
from .helper import hopper, is_win32, on_ci
def test_sanity():
@ -38,8 +38,7 @@ def test_viewer_show():
@pytest.mark.skipif(
not on_ci() or (is_win32() and on_github_actions()),
reason="Only run on CIs; hangs on Windows on GitHub Actions",
not on_ci() or is_win32(), reason="Only run on CIs; hangs on Windows CIs",
)
def test_show():
for mode in ("1", "I;16", "LA", "RGB", "RGBA"):

View File

@ -20,7 +20,7 @@ def test_overflow():
# This image hits the offset test.
with Image.open("Tests/images/l2rgb_read.bmp") as im:
with pytest.raises((ValueError, MemoryError, IOError)):
with pytest.raises((ValueError, MemoryError, OSError)):
im.load()
Image.MAX_IMAGE_PIXELS = max_pixels

View File

@ -89,6 +89,11 @@ def test_3d_array():
assert_image(Image.fromarray(a[:, :, 1]), "L", TEST_IMAGE_SIZE)
def test_1d_array():
a = numpy.ones(5, dtype=numpy.uint8)
assert_image(Image.fromarray(a), "L", (1, 5))
def _test_img_equals_nparray(img, np):
assert len(np.shape) >= 2
np_size = np.shape[1], np.shape[0]

View File

@ -1,11 +1,14 @@
import pickle
import pytest
from PIL import Image
from .helper import skip_unless_feature
def helper_pickle_file(tmp_path, pickle, protocol=0, mode=None):
def helper_pickle_file(tmp_path, pickle, protocol, test_file, mode):
# Arrange
with Image.open("Tests/images/hopper.jpg") as im:
with Image.open(test_file) as im:
filename = str(tmp_path / "temp.pkl")
if mode:
im = im.convert(mode)
@ -20,9 +23,7 @@ def helper_pickle_file(tmp_path, pickle, protocol=0, mode=None):
assert im == loaded_im
def helper_pickle_string(
pickle, protocol=0, test_file="Tests/images/hopper.jpg", mode=None
):
def helper_pickle_string(pickle, protocol, test_file, mode):
with Image.open(test_file) as im:
if mode:
im = im.convert(mode)
@ -35,41 +36,31 @@ def helper_pickle_string(
assert im == loaded_im
def test_pickle_image(tmp_path):
@pytest.mark.parametrize(
("test_file", "test_mode"),
[
("Tests/images/hopper.jpg", None),
("Tests/images/hopper.jpg", "L"),
("Tests/images/hopper.jpg", "PA"),
pytest.param(
"Tests/images/hopper.webp", None, marks=skip_unless_feature("webp")
),
("Tests/images/hopper.tif", None),
("Tests/images/test-card.png", None),
("Tests/images/zero_bb.png", None),
("Tests/images/zero_bb_scale2.png", None),
("Tests/images/non_zero_bb.png", None),
("Tests/images/non_zero_bb_scale2.png", None),
("Tests/images/p_trns_single.png", None),
("Tests/images/pil123p.png", None),
("Tests/images/itxt_chunks.png", None),
],
)
def test_pickle_image(tmp_path, test_file, test_mode):
# Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
helper_pickle_string(pickle, protocol)
helper_pickle_file(tmp_path, pickle, protocol)
def test_pickle_p_mode():
# Act / Assert
for test_file in [
"Tests/images/test-card.png",
"Tests/images/zero_bb.png",
"Tests/images/zero_bb_scale2.png",
"Tests/images/non_zero_bb.png",
"Tests/images/non_zero_bb_scale2.png",
"Tests/images/p_trns_single.png",
"Tests/images/pil123p.png",
"Tests/images/itxt_chunks.png",
]:
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
helper_pickle_string(pickle, protocol=protocol, test_file=test_file)
def test_pickle_pa_mode(tmp_path):
# Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
helper_pickle_string(pickle, protocol, mode="PA")
helper_pickle_file(tmp_path, pickle, protocol, mode="PA")
def test_pickle_l_mode(tmp_path):
# Act / Assert
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
helper_pickle_string(pickle, protocol, mode="L")
helper_pickle_file(tmp_path, pickle, protocol, mode="L")
helper_pickle_string(pickle, protocol, test_file, test_mode)
helper_pickle_file(tmp_path, pickle, protocol, test_file, test_mode)
def test_pickle_la_mode_with_palette(tmp_path):
@ -88,3 +79,15 @@ def test_pickle_la_mode_with_palette(tmp_path):
im.mode = "PA"
assert im == loaded_im
@skip_unless_feature("webp")
def test_pickle_tell():
# Arrange
image = Image.open("Tests/images/hopper.webp")
# Act: roundtrip
unpickled_image = pickle.loads(pickle.dumps(image))
# Assert
assert unpickled_image.tell() == 0

View File

@ -10,5 +10,5 @@ from PIL import Image
def test_crashes(test_file):
with open(test_file, "rb") as f:
im = Image.open(f)
with pytest.raises(IOError):
with pytest.raises(OSError):
im.load()

View File

@ -4,12 +4,12 @@
# Use SVN to just fetch a single Git subdirectory
svn_export()
{
if [ ! -z $1 ]; then
echo ""
echo "Retrying svn export..."
echo ""
fi
if [ ! -z $1 ]; then
echo ""
echo "Retrying svn export..."
echo ""
fi
svn export --force https://github.com/python-pillow/pillow-depends/trunk/test_images ../Tests/images
svn export --force https://github.com/python-pillow/pillow-depends/trunk/test_images ../Tests/images
}
svn_export || svn_export retry || svn_export retry || svn_export retry

View File

@ -289,4 +289,4 @@ texinfo_documents = [
def setup(app):
app.add_javascript("js/script.js")
app.add_js_file("js/script.js")

View File

@ -12,6 +12,15 @@ Deprecated features
Below are features which are considered deprecated. Where appropriate,
a ``DeprecationWarning`` is issued.
ImageFile.raise_ioerror
~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.2.0
``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror``
is now deprecated and will be removed in a future released. Use
``ImageFile.raise_oserror`` instead.
PILLOW_VERSION constant
~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1226,7 +1226,7 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
**append**
Set to True to append pages to an existing PDF file. If the file doesn't
exist, an :py:exc:`IOError` will be raised.
exist, an :py:exc:`OSError` will be raised.
.. versionadded:: 5.1.0

View File

@ -29,7 +29,7 @@ bands in the image, and also the pixel type and depth. Common modes are “L”
(luminance) for greyscale images, “RGB” for true color images, and “CMYK” for
pre-press images.
If the file cannot be opened, an :py:exc:`IOError` exception is raised.
If the file cannot be opened, an :py:exc:`OSError` exception is raised.
Once you have an instance of the :py:class:`~PIL.Image.Image` class, you can use
the methods defined by this class to process and manipulate the image. For
@ -76,7 +76,7 @@ Convert files to JPEG
try:
with Image.open(infile) as im:
im.save(outfile)
except IOError:
except OSError:
print("cannot convert", infile)
A second argument can be supplied to the :py:meth:`~PIL.Image.Image.save`
@ -100,7 +100,7 @@ Create JPEG thumbnails
with Image.open(infile) as im:
im.thumbnail(size)
im.save(outfile, "JPEG")
except IOError:
except OSError:
print("cannot create thumbnail for", infile)
It is important to note that the library doesnt decode or load the raster data
@ -125,7 +125,7 @@ Identify Image Files
try:
with Image.open(infile) as im:
print(infile, im.format, "%dx%d" % im.size, im.mode)
except IOError:
except OSError:
pass
Cutting, pasting, and merging images
@ -450,7 +450,7 @@ context manager::
...
If everything goes well, the result is an :py:class:`PIL.Image.Image` object.
Otherwise, an :exc:`IOError` exception is raised.
Otherwise, an :exc:`OSError` exception is raised.
You can use a file-like object instead of the filename. The object must
implement :py:meth:`~file.read`, :py:meth:`~file.seek` and

View File

@ -17,8 +17,8 @@ itself. Such plug-ins usually have names like
Pillow decodes files in 2 stages:
1. It loops over the available image plugins in the loaded order, and
calls the plugin's ``accept`` function with the first 16 bytes of
the file. If the ``accept`` function returns true, the plugin's
calls the plugin's ``_accept`` function with the first 16 bytes of
the file. If the ``_accept`` function returns true, the plugin's
``_open`` method is called to set up the image metadata and image
tiles. The ``_open`` method is not for decoding the actual image
data.
@ -53,6 +53,11 @@ true color.
from PIL import Image, ImageFile
def _accept(prefix):
return prefix[:4] == b"SPAM"
class SpamImageFile(ImageFile.ImageFile):
format = "SPAM"
@ -60,12 +65,7 @@ true color.
def _open(self):
# check header
header = self.fp.read(128)
if header[:4] != b"SPAM":
raise SyntaxError("not a SPAM file")
header = header.split()
header = self.fp.read(128).split()
# size in pixels (width, height)
self._size = int(header[1]), int(header[2])
@ -86,7 +86,7 @@ true color.
("raw", (0, 0) + self.size, 128, (self.mode, 0, 1))
]
Image.register_open(SpamImageFile.format, SpamImageFile)
Image.register_open(SpamImageFile.format, SpamImageFile, _accept)
Image.register_extension(SpamImageFile.format, ".spam")
Image.register_extension(SpamImageFile.format, ".spa") # dos version
@ -179,7 +179,7 @@ complete list, see the table in the :py:mod:`Unpack.c` module. The following
table describes some commonly used **raw modes**:
+-----------+-----------------------------------------------------------------+
| mode | description |
| mode | description |
+===========+=================================================================+
| ``1`` | 1-bit bilevel, stored with the leftmost pixel in the most |
| | significant bit. 0 means black, 1 means white. |
@ -223,7 +223,7 @@ You can use the ``raw`` decoder to read images where data is packed in any
standard machine data type, using one of the following raw modes:
============ =======================================
mode description
mode description
============ =======================================
``F`` 32-bit native floating point.
``F;8`` 8-bit unsigned integer.

View File

@ -292,9 +292,10 @@ or from within the uncompressed source directory::
Building on Windows
^^^^^^^^^^^^^^^^^^^
We don't recommend trying to build on Windows. It is a maze of twisty
passages, mostly dead ends. There are build scripts and notes for the
Windows build in the ``winbuild`` directory.
We recommend you use prebuilt wheels from PyPI.
If you wish to compile Pillow manually, you can use the build scripts
in the ``winbuild`` directory used for CI testing and development.
These scripts require Visual Studio 2017 or newer and NASM.
Building on FreeBSD
^^^^^^^^^^^^^^^^^^^
@ -328,7 +329,7 @@ In Fedora, the command is::
.. Note:: ``redhat-rpm-config`` is required on Fedora 23, but not earlier versions.
Prerequisites are installed on **Ubuntu 16.04 LTS** with::
Prerequisites for **Ubuntu 16.04 LTS - 20.04 LTS** are installed with::
sudo apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev \
libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk \
@ -396,17 +397,23 @@ These platforms are built and tested for every change.
+----------------------------------+--------------------------+-----------------------+
| Debian 10 Buster | 3.7 |x86 |
+----------------------------------+--------------------------+-----------------------+
| Fedora 30 | 3.7 |x86-64 |
+----------------------------------+--------------------------+-----------------------+
| Fedora 31 | 3.7 |x86-64 |
+----------------------------------+--------------------------+-----------------------+
| Fedora 32 | 3.8 |x86-64 |
+----------------------------------+--------------------------+-----------------------+
| macOS 10.15 Catalina | 3.5, 3.6, 3.7, 3.8, PyPy3|x86-64 |
+----------------------------------+--------------------------+-----------------------+
| Ubuntu Linux 16.04 LTS | 3.5, 3.6, 3.7, 3.8, PyPy3|x86-64 |
+----------------------------------+--------------------------+-----------------------+
| Windows Server 2012 R2 | 3.5, 3.8 |x86, x86-64 |
| Ubuntu Linux 18.04 LTS | 3.6 |x86-64 |
+----------------------------------+--------------------------+-----------------------+
| Ubuntu Linux 20.04 LTS | 3.8 |x86-64 |
+----------------------------------+--------------------------+-----------------------+
| Windows Server 2016 | 3.8 |x86 |
| +--------------------------+-----------------------+
| | PyPy3, 3.7/MinGW |x86 |
| | 3.5 |x86-64 |
| +--------------------------+-----------------------+
| | 3.7/MinGW |x86 |
+----------------------------------+--------------------------+-----------------------+
| Windows Server 2019 | 3.5, 3.6, 3.7, 3.8 |x86, x86-64 |
| +--------------------------+-----------------------+
@ -427,7 +434,7 @@ These platforms have been reported to work at the versions mentioned.
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|**Operating system** |**Tested Python versions** |**Latest tested Pillow version**|**Tested processors** |
+----------------------------------+------------------------------+--------------------------------+-----------------------+
| macOS 10.15 Catalina | 3.5, 3.6, 3.7, 3.8 | 7.0.0 |x86-64 |
| macOS 10.15 Catalina | 3.5, 3.6, 3.7, 3.8 | 7.1.2 |x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+
| macOS 10.14 Mojave | 2.7, 3.5, 3.6, 3.7 | 6.0.0 |x86-64 |
| +------------------------------+--------------------------------+ +
@ -474,11 +481,13 @@ These platforms have been reported to work at the versions mentioned.
+----------------------------------+------------------------------+--------------------------------+-----------------------+
| FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+
| Windows 10 | 3.7 | 7.1.0 |x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+
| Windows 8.1 Pro | 2.6, 2.7, 3.2, 3.3, 3.4 | 2.4.0 |x86,x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+
| Windows 8 Pro | 2.6, 2.7, 3.2, 3.3, 3.4a3 | 2.2.0 |x86,x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+
| Windows 7 Pro | 2.7, 3.2, 3.3 | 3.4.1 |x86-64 |
| Windows 7 Professional | 3.7 | 7.0.0 |x86,x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+
| Windows Server 2008 R2 Enterprise| 3.3 | |x86-64 |
+----------------------------------+------------------------------+--------------------------------+-----------------------+

View File

@ -58,8 +58,8 @@ Functions
``warnings.simplefilter('ignore', Image.DecompressionBombWarning)``. See also `the logging
documentation`_ to have warnings output to the logging facility instead of stderr.
.. _decompression bombs: https://en.wikipedia.org/wiki/Zip_bomb
.. _the logging documentation: https://docs.python.org/3/library/logging.html#integration-with-the-warnings-module
.. _decompression bombs: https://en.wikipedia.org/wiki/Zip_bomb
.. _the logging documentation: https://docs.python.org/3/library/logging.html#integration-with-the-warnings-module
Image processing
^^^^^^^^^^^^^^^^
@ -179,6 +179,7 @@ This helps to get the bounding box coordinates of the input image:
.. automethod:: PIL.Image.Image.getcolors
.. automethod:: PIL.Image.Image.getdata
.. automethod:: PIL.Image.Image.getextrema
.. automethod:: PIL.Image.Image.getexif
.. automethod:: PIL.Image.Image.getpalette
.. automethod:: PIL.Image.Image.getpixel
.. automethod:: PIL.Image.Image.histogram

View File

@ -413,10 +413,10 @@ can be easily displayed in a chromaticity diagram, for example).
with :py:attr:`.intent_supported`.
:param intent: One of ``ImageCms.INTENT_ABSOLUTE_COLORIMETRIC``,
``ImageCms.INTENT_PERCEPTUAL``,
``ImageCms.INTENT_RELATIVE_COLORIMETRIC``
and ``ImageCms.INTENT_SATURATION``.
``ImageCms.INTENT_PERCEPTUAL``,
``ImageCms.INTENT_RELATIVE_COLORIMETRIC``
and ``ImageCms.INTENT_SATURATION``.
:param direction: One of ``ImageCms.DIRECTION_INPUT``,
``ImageCms.DIRECTION_OUTPUT``
and ``ImageCms.DIRECTION_PROOF``
``ImageCms.DIRECTION_OUTPUT``
and ``ImageCms.DIRECTION_PROOF``
:return: Boolean if the intent and direction is supported.

View File

@ -81,13 +81,13 @@ Example: Draw Partial Opacity Text
from PIL import Image, ImageDraw, ImageFont
# get an image
base = Image.open('Pillow/Tests/images/hopper.png').convert('RGBA')
base = Image.open("Pillow/Tests/images/hopper.png").convert("RGBA")
# make a blank image for the text, initialized to transparent text color
txt = Image.new('RGBA', base.size, (255,255,255,0))
txt = Image.new("RGBA", base.size, (255,255,255,0))
# get a font
fnt = ImageFont.truetype('Pillow/Tests/fonts/FreeMono.ttf', 40)
fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 40)
# get a drawing context
d = ImageDraw.Draw(txt)
@ -100,6 +100,25 @@ Example: Draw Partial Opacity Text
out.show()
Example: Draw Multiline Text
----------------------------
.. code-block:: python
from PIL import Image, ImageDraw, ImageFont
# create an image
out = Image.new("RGB", (150, 100), (255, 255, 255))
# get a font
fnt = ImageFont.truetype("Pillow/Tests/fonts/FreeMono.ttf", 40)
# get a drawing context
d = ImageDraw.Draw(out)
# draw multiline text
d.multiline_text((10,10), "Hello\nWorld", font=fnt, fill=(0, 0, 0))
out.show()
Functions
@ -193,8 +212,7 @@ Methods
.. versionadded:: 1.1.5
.. note:: This option was broken until version 1.1.6.
:param joint: Joint type between a sequence of lines. It can be "curve",
for rounded edges, or None.
:param joint: Joint type between a sequence of lines. It can be ``"curve"``, for rounded edges, or ``None``.
.. versionadded:: 5.3.0
@ -261,15 +279,18 @@ Methods
:param xy: Top left corner of the text.
:param text: Text to be drawn. If it contains any newline characters,
the text is passed on to multiline_text()
the text is passed on to
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`.
:param fill: Color to use for the text.
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
:param spacing: If the text is passed on to multiline_text(),
:param spacing: If the text is passed on to
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,
the number of pixels between lines.
:param align: If the text is passed on to multiline_text(),
"left", "center" or "right".
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
:param align: If the text is passed on to
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_text`,
``"left"``, ``"center"`` or ``"right"``.
:param direction: Direction of the text. It can be ``"rtl"`` (right to
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
Requires libraqm.
.. versionadded:: 4.2.0
@ -277,12 +298,11 @@ Methods
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
for example ``"dlig"`` or ``"ss01"``, but can be also
used to turn off default font features, for
example ``"-liga"`` to disable ligatures or ``"-kern"``
to disable kerning. To get all supported
features, see
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
features, see `OpenType docs`_.
Requires libraqm.
.. versionadded:: 4.2.0
@ -291,8 +311,7 @@ Methods
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`
It should be a `BCP 47 language code`_.
Requires libraqm.
.. versionadded:: 6.0.0
@ -302,9 +321,9 @@ Methods
.. versionadded:: 6.2.0
:param stroke_fill: Color to use for the text stroke. If not given, will default to
the ``fill`` parameter.
the ``fill`` parameter.
.. versionadded:: 6.2.0
.. versionadded:: 6.2.0
.. py:method:: PIL.ImageDraw.ImageDraw.multiline_text(xy, text, fill=None, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None)
@ -315,9 +334,9 @@ Methods
:param fill: Color to use for the text.
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
:param spacing: The number of pixels between lines.
:param align: "left", "center" or "right".
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
:param align: ``"left"``, ``"center"`` or ``"right"``.
:param direction: Direction of the text. It can be ``"rtl"`` (right to
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
Requires libraqm.
.. versionadded:: 4.2.0
@ -325,12 +344,11 @@ Methods
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
for example ``"dlig"`` or ``"ss01"``, but can be also
used to turn off default font features, for
example ``"-liga"`` to disable ligatures or ``"-kern"``
to disable kerning. To get all supported
features, see
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
features, see `OpenType docs`_.
Requires libraqm.
.. versionadded:: 4.2.0
@ -339,8 +357,7 @@ Methods
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`
It should be a `BCP 47 language code`_.
Requires libraqm.
.. versionadded:: 6.0.0
@ -350,24 +367,24 @@ Methods
Return the size of the given string, in pixels.
:param text: Text to be measured. If it contains any newline characters,
the text is passed on to multiline_textsize()
the text is passed on to :py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textsize`.
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
:param spacing: If the text is passed on to multiline_textsize(),
:param spacing: If the text is passed on to
:py:meth:`~PIL.ImageDraw.ImageDraw.multiline_textsize`,
the number of pixels between lines.
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
:param direction: Direction of the text. It can be ``"rtl"`` (right to
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
Requires libraqm.
.. versionadded:: 4.2.0
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
for example ``"dlig"`` or ``"ss01"``, but can be also
used to turn off default font features, for
example ``"-liga"`` to disable ligatures or ``"-kern"``
to disable kerning. To get all supported
features, see
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
features, see `OpenType docs`_.
Requires libraqm.
.. versionadded:: 4.2.0
@ -375,8 +392,7 @@ Methods
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`
It should be a `BCP 47 language code`_.
Requires libraqm.
.. versionadded:: 6.0.0
@ -392,8 +408,8 @@ Methods
:param text: Text to be measured.
:param font: An :py:class:`~PIL.ImageFont.ImageFont` instance.
:param spacing: The number of pixels between lines.
:param direction: Direction of the text. It can be 'rtl' (right to
left), 'ltr' (left to right) or 'ttb' (top to bottom).
:param direction: Direction of the text. It can be ``"rtl"`` (right to
left), ``"ltr"`` (left to right) or ``"ttb"`` (top to bottom).
Requires libraqm.
.. versionadded:: 4.2.0
@ -401,12 +417,11 @@ Methods
:param features: A list of OpenType font features to be used during text
layout. This is usually used to turn on optional
font features that are not enabled by default,
for example 'dlig' or 'ss01', but can be also
used to turn off default font features for
example '-liga' to disable ligatures or '-kern'
for example ``"dlig"`` or ``"ss01"``, but can be also
used to turn off default font features, for
example ``"-liga"`` to disable ligatures or ``"-kern"``
to disable kerning. To get all supported
features, see
https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist
features, see `OpenType docs`_.
Requires libraqm.
.. versionadded:: 4.2.0
@ -415,8 +430,7 @@ Methods
different glyph shapes or ligatures. This parameter tells
the font which language the text is in, and to apply the
correct substitutions as appropriate, if available.
It should be a `BCP 47 language code
<https://www.w3.org/International/articles/language-tags/>`
It should be a `BCP 47 language code`_.
Requires libraqm.
.. versionadded:: 6.0.0
@ -453,3 +467,6 @@ Methods
tolerable difference of a pixel value from the 'background' in
order for it to be replaced. Useful for filling regions of non-
homogeneous, but similar, colors.
.. _BCP 47 language code: https://www.w3.org/International/articles/language-tags/
.. _OpenType docs: https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist

View File

@ -9,13 +9,14 @@ this class store bitmap fonts, and are used with the
:py:meth:`PIL.ImageDraw.Draw.text` method.
PIL uses its own font file format to store bitmap fonts. You can use the
:command:`pilfont` utility to convert BDF and PCF font descriptors (X window
font formats) to this format.
:command:`pilfont` utility from
`pillow-scripts <https://pypi.org/project/pillow-scripts/>`_
to convert BDF and PCF font descriptors (X window font formats) to this format.
Starting with version 1.1.4, PIL can be configured to support TrueType and
OpenType fonts (as well as other font formats supported by the FreeType
library). For earlier versions, TrueType support is only available as part of
the imToolkit package
the imToolkit package.
Example
-------

View File

@ -1,20 +1,18 @@
.. py:module:: PIL.ImageGrab
.. py:currentmodule:: PIL.ImageGrab
:py:mod:`ImageGrab` Module (macOS and Windows only)
===================================================
:py:mod:`ImageGrab` Module
==========================
The :py:mod:`ImageGrab` module can be used to copy the contents of the screen
or the clipboard to a PIL image memory.
.. note:: The current version works on macOS and Windows only.
.. versionadded:: 1.1.3
.. py:function:: PIL.ImageGrab.grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None)
.. py:function:: grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=None)
Take a snapshot of the screen. The pixels inside the bounding box are
returned as an "RGB" image on Windows or "RGBA" on macOS.
returned as an "RGBA" on macOS, or an "RGB" image otherwise.
If the bounding box is omitted, the entire screen is copied.
.. versionadded:: 1.1.3 (Windows), 3.0.0 (macOS), 7.1.0 (Linux (X11))
@ -28,15 +26,17 @@ or the clipboard to a PIL image memory.
.. versionadded:: 6.2.0
:param xdisplay: X11 Display address. Pass ``None`` to grab the default system screen.
Pass ``""`` to grab the default X11 screen on Windows or macOS.
:param xdisplay:
X11 Display address. Pass ``None`` to grab the default system screen. Pass ``""`` to grab the default X11 screen on Windows or macOS.
You can check X11 support using :py:func:`PIL.features.check_feature` with ``feature="xcb"``.
.. versionadded:: 7.1.0
:return: An image
.. py:function:: PIL.ImageGrab.grabclipboard()
.. py:function:: grabclipboard()
Take a snapshot of the clipboard image, if any.
Take a snapshot of the clipboard image, if any. Only macOS and Windows are currently supported.
.. versionadded:: 1.1.4 (Windows), 3.3.0 (macOS)

View File

@ -0,0 +1,60 @@
.. py:module:: PIL.features
.. py:currentmodule:: PIL.features
:py:mod:`features` Module
==========================
The :py:mod:`PIL.features` module can be used to detect which Pillow features are available on your system.
.. autofunction:: PIL.features.pilinfo
.. autofunction:: PIL.features.check
.. autofunction:: PIL.features.get_supported
Modules
-------
Support for the following modules can be checked:
* ``pil``: The Pillow core module, required for all functionality.
* ``tkinter``: Tkinter support.
* ``freetype2``: FreeType font support via :py:func:`PIL.ImageFont.truetype`.
* ``littlecms2``: LittleCMS 2 support via :py:mod:`PIL.ImageCms`.
* ``webp``: WebP image support.
.. autofunction:: PIL.features.check_module
.. autofunction:: PIL.features.get_supported_modules
Codecs
------
These are only checked during Pillow compilation.
If the required library was uninstalled from the system, the ``pil`` core module may fail to load instead.
Support for the following codecs can be checked:
* ``jpg``: (compile time) Libjpeg support, required for JPEG based image formats.
* ``jpg_2000``: (compile time) OpenJPEG support, required for JPEG 2000 image formats.
* ``zlib``: (compile time) Zlib support, required for zlib compressed formats, such as PNG.
* ``libtiff``: (compile time) LibTIFF support, required for TIFF based image formats.
.. autofunction:: PIL.features.check_codec
.. autofunction:: PIL.features.get_supported_codecs
Features
--------
Some of these are only checked during Pillow compilation.
If the required library was uninstalled from the system, the relevant module may fail to load instead.
Support for the following features can be checked:
* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg.
* ``transp_webp``: Support for transparency in WebP images.
* ``webp_mux``: (compile time) Support for EXIF data in WebP images.
* ``webp_anim``: (compile time) Support for animated WebP images.
* ``raqm``: Raqm library, required for ``ImageFont.LAYOUT_RAQM`` in :py:func:`PIL.ImageFont.truetype`.
* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`.
* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library.
.. autofunction:: PIL.features.check_feature
.. autofunction:: PIL.features.get_supported_features

View File

@ -30,6 +30,7 @@ Reference
PSDraw
PixelAccess
PyAccess
features
../PIL
plugins
internal_design

View File

@ -85,7 +85,7 @@ Custom unidentified image error
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Pillow will now throw a custom ``UnidentifiedImageError`` when an image cannot be
identified. For backwards compatibility, this will inherit from ``IOError``.
identified. For backwards compatibility, this will inherit from ``OSError``.
New argument ``reducing_gap`` for Image.resize() and Image.thumbnail() methods
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -69,6 +69,16 @@ Passing a different value on Windows or macOS will force taking a snapshot
using the selected X server; pass an empty string to use the default X server.
XCB support is not included in pre-compiled wheels for Windows and macOS.
Security
========
This release includes security fixes.
* CVE-2020-10177 Fix multiple OOB reads in FLI decoding
* CVE-2020-10378 Fix bounds overflow in PCX decoding
* CVE-2020-10379 Fix two buffer overflows in TIFF decoding
* CVE-2020-10994 Fix bounds overflow in JPEG 2000 decoding
* CVE-2020-11538 Fix buffer overflow in SGI-RLE decoding
Other Changes
=============

View File

@ -0,0 +1,16 @@
7.1.2
-----
Fix another regression seeking PNG files
========================================
This fixes a regression introduced in 7.1.0 when adding support for APNG files.
When calling ``seek(n)`` on a regular PNG where ``n > 0``, it failed to raise an
``EOFError`` as it should have done, resulting in:
.. code-block:: python
AttributeError: 'NoneType' object has no attribute 'read'
Pillow 7.1.2 now raises the correct exception.

View File

@ -0,0 +1,29 @@
7.2.0
-----
API Changes
===========
Replaced TiffImagePlugin DEBUG with logging
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
``TiffImagePlugin.DEBUG = True`` has been a way to print various debugging
information when interacting with TIFF images. This has now been removed
in favour of Python's ``logging`` module, already used in other places in the
Pillow source code.
Corrected default offset when writing EXIF data
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Previously, the default ``offset`` argument for
:py:meth:`~PIL.Image.Exif.tobytes` was 0, which did not include the magic
header. It is now 8.
Moved to ImageFileDirectory_v2 in Image.Exif
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Moved from the legacy :py:class:`PIL.TiffImagePlugin.ImageFileDirectory_v1` to
:py:class:`PIL.TiffImagePlugin.ImageFileDirectory_v2` in
:py:class:`PIL.Image.Exif`. This means that Exif RATIONALs and SIGNED_RATIONALs
are now read as :py:class:`PIL.TiffImagePlugin.IFDRational`, instead of as a
tuple with a numerator and a denominator.

View File

@ -6,6 +6,8 @@ Release Notes
.. toctree::
:maxdepth: 2
7.2.0
7.1.2
7.1.1
7.1.0
7.0.0

View File

@ -47,7 +47,7 @@ def testimage():
('PPM', 'RGB', (128, 128))
>>> try:
... _info(Image.open("Tests/images/hopper.jpg"))
... except IOError as v:
... except OSError as v:
... print(v)
('JPEG', 'RGB', (128, 128))

View File

@ -10,3 +10,4 @@ multi_line_output = 3
[tool:pytest]
addopts = -rs
testpaths = Tests

View File

@ -579,7 +579,7 @@ class pil_build_ext(build_ext):
try:
listdir = os.listdir(directory)
except Exception:
# WindowsError, FileNotFoundError
# OSError, FileNotFoundError
continue
for name in listdir:
if name.startswith("openjpeg-") and os.path.isfile(
@ -711,7 +711,7 @@ class pil_build_ext(build_ext):
if feature.jpeg2000:
libs.append(feature.jpeg2000)
defs.append(("HAVE_OPENJPEG", None))
if sys.platform == "win32":
if sys.platform == "win32" and not PLATFORM_MINGW:
defs.append(("OPJ_STATIC", None))
if feature.zlib:
libs.append(feature.zlib)
@ -730,7 +730,11 @@ class pil_build_ext(build_ext):
if struct.unpack("h", b"\0\1")[0] == 1:
defs.append(("WORDS_BIGENDIAN", None))
if sys.platform == "win32" and not (PLATFORM_PYPY or PLATFORM_MINGW):
if (
sys.platform == "win32"
and sys.version_info < (3, 9)
and not (PLATFORM_PYPY or PLATFORM_MINGW)
):
defs.append(("PILLOW_VERSION", '"\\"%s\\""' % PILLOW_VERSION))
else:
defs.append(("PILLOW_VERSION", '"%s"' % PILLOW_VERSION))

View File

@ -59,16 +59,10 @@ class DcxImageFile(PcxImageFile):
self.__fp = self.fp
self.frame = None
self.n_frames = len(self._offset)
self.is_animated = self.n_frames > 1
self.seek(0)
@property
def n_frames(self):
return len(self._offset)
@property
def is_animated(self):
return len(self._offset) > 1
def seek(self, frame):
if not self._seek_check(frame):
return

View File

@ -51,7 +51,8 @@ class FliImageFile(ImageFile.ImageFile):
raise SyntaxError("not an FLI/FLC file")
# frames
self.__framecount = i16(s[6:8])
self.n_frames = i16(s[6:8])
self.is_animated = self.n_frames > 1
# image characteristics
self.mode = "P"
@ -110,14 +111,6 @@ class FliImageFile(ImageFile.ImageFile):
palette[i] = (r, g, b)
i += 1
@property
def n_frames(self):
return self.__framecount
@property
def is_animated(self):
return self.__framecount > 1
def seek(self, frame):
if not self._seek_check(frame):
return

View File

@ -99,7 +99,7 @@ class FpxImageFile(ImageFile.ImageFile):
colors = []
bands = i32(s, 4)
if bands > 4:
raise IOError("Invalid number of bands")
raise OSError("Invalid number of bands")
for i in range(bands):
# note: for now, we ignore the "uncalibrated" flag
colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)

View File

@ -84,6 +84,10 @@ class GbrImageFile(ImageFile.ImageFile):
self._data_size = width * height * color_depth
def load(self):
if self.im:
# Already loaded
return
self.im = Image.core.new(self.mode, self.size)
self.frombytes(self.fp.read(self._data_size))

View File

@ -74,7 +74,7 @@ def open(fp, mode="r"):
:param mode: Optional mode. In this version, if the mode argument
is given, it must be "r".
:returns: An image instance.
:raises IOError: If the image could not be read.
:raises OSError: If the image could not be read.
"""
if mode != "r":
raise ValueError("bad mode")

View File

@ -255,7 +255,7 @@ class GifImageFile(ImageFile.ImageFile):
else:
pass
# raise IOError, "illegal GIF tag `%x`" % i8(s)
# raise OSError, "illegal GIF tag `%x`" % i8(s)
try:
if self.disposal_method < 2:

View File

@ -35,6 +35,7 @@ import struct
import sys
import tempfile
import warnings
import xml.etree.ElementTree
from collections.abc import Callable, MutableMapping
from pathlib import Path
@ -1050,10 +1051,12 @@ class Image:
of colors.
:param colors: The desired number of colors, <= 256
:param method: 0 = median cut
1 = maximum coverage
2 = fast octree
3 = libimagequant
:param method: ``Image.MEDIANCUT=0`` (median cut),
``Image.MAXCOVERAGE=1`` (maximum coverage),
``Image.FASTOCTREE=2`` (fast octree),
``Image.LIBIMAGEQUANT=3`` (libimagequant; check support using
:py:func:`PIL.features.check_feature`
with ``feature="libimagequant"``).
:param kmeans: Integer
:param palette: Quantize to the palette of given
:py:class:`PIL.Image.Image`.
@ -1300,7 +1303,28 @@ class Image:
def getexif(self):
if self._exif is None:
self._exif = Exif()
self._exif.load(self.info.get("exif"))
exif_info = self.info.get("exif")
if exif_info is None and "Raw profile type exif" in self.info:
exif_info = bytes.fromhex(
"".join(self.info["Raw profile type exif"].split("\n")[3:])
)
self._exif.load(exif_info)
# XMP tags
if 0x0112 not in self._exif:
xmp_tags = self.info.get("XML:com.adobe.xmp")
if xmp_tags:
root = xml.etree.ElementTree.fromstring(xmp_tags)
for elem in root.iter():
if elem.tag.endswith("}Description"):
orientation = elem.attrib.get(
"{http://ns.adobe.com/tiff/1.0/}Orientation"
)
if orientation:
self._exif[0x0112] = int(orientation)
break
return self._exif
def getim(self):
@ -2080,7 +2104,7 @@ class Image:
:returns: None
:exception ValueError: If the output format could not be determined
from the file name. Use the format option to solve this.
:exception IOError: If the file could not be written. The file
:exception OSError: If the file could not be written. The file
may have been created, and may contain partial data.
"""
@ -2141,7 +2165,7 @@ class Image:
"""
Seeks to the given frame in this sequence file. If you seek
beyond the end of the sequence, the method raises an
**EOFError** exception. When a sequence file is opened, the
``EOFError`` exception. When a sequence file is opened, the
library automatically seeks to frame 0.
See :py:meth:`~PIL.Image.Image.tell`.
@ -2247,6 +2271,7 @@ class Image:
:py:attr:`PIL.Image.BICUBIC`, or :py:attr:`PIL.Image.LANCZOS`.
If omitted, it defaults to :py:attr:`PIL.Image.BICUBIC`.
(was :py:attr:`PIL.Image.NEAREST` prior to version 2.5.0).
See: :ref:`concept-filters`.
:param reducing_gap: Apply optimization by resizing the image
in two steps. First, reducing the image by integer times
using :py:meth:`~PIL.Image.Image.reduce` or
@ -2276,7 +2301,9 @@ class Image:
if x / y >= aspect:
x = round_aspect(y * aspect, key=lambda n: abs(aspect - n / y))
else:
y = round_aspect(x / aspect, key=lambda n: abs(aspect - x / n))
y = round_aspect(
x / aspect, key=lambda n: 0 if n == 0 else abs(aspect - x / n)
)
size = (x, y)
box = None
@ -2324,7 +2351,7 @@ class Image:
It may also be an object with a :py:meth:`~method.getdata` method
that returns a tuple supplying new **method** and **data** values::
class Example(object):
class Example:
def getdata(self):
method = Image.EXTENT
data = (0, 0, 100, 100)
@ -2336,6 +2363,7 @@ class Image:
environment), or :py:attr:`PIL.Image.BICUBIC` (cubic spline
interpolation in a 4x4 environment). If omitted, or if the image
has mode "1" or "P", it is set to :py:attr:`PIL.Image.NEAREST`.
See: :ref:`concept-filters`.
:param fill: If **method** is an
:py:class:`~PIL.Image.ImageTransformHandler` object, this is one of
the arguments passed to it. Otherwise, it is unused.
@ -2725,7 +2753,7 @@ def fromarray(obj, mode=None):
if ndim > ndmax:
raise ValueError("Too many dimensions: %d > %d." % (ndim, ndmax))
size = shape[1], shape[0]
size = 1 if ndim == 1 else shape[1], shape[0]
if strides is not None:
if hasattr(obj, "tobytes"):
obj = obj.tobytes()
@ -3222,7 +3250,7 @@ class Exif(MutableMapping):
def _fixup(self, value):
try:
if len(value) == 1 and not isinstance(value, dict):
if len(value) == 1 and isinstance(value, tuple):
return value[0]
except Exception:
pass
@ -3243,7 +3271,7 @@ class Exif(MutableMapping):
else:
from . import TiffImagePlugin
info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
info = TiffImagePlugin.ImageFileDirectory_v2(self.head)
info.load(self.fp)
return self._fixup_dict(info)
@ -3263,12 +3291,14 @@ class Exif(MutableMapping):
if not data:
return
self.fp = io.BytesIO(data[6:])
if data.startswith(b"Exif\x00\x00"):
data = data[6:]
self.fp = io.BytesIO(data)
self.head = self.fp.read(8)
# process dictionary
from . import TiffImagePlugin
self._info = TiffImagePlugin.ImageFileDirectory_v1(self.head)
self._info = TiffImagePlugin.ImageFileDirectory_v2(self.head)
self.endian = self._info._endian
self.fp.seek(self._info.next)
self._info.load(self.fp)
@ -3279,7 +3309,7 @@ class Exif(MutableMapping):
self._data.update(ifd)
self._ifds[0x8769] = ifd
def tobytes(self, offset=0):
def tobytes(self, offset=8):
from . import TiffImagePlugin
if self.endian == "<":

View File

@ -54,7 +54,7 @@ def invert(image):
def lighter(image1, image2):
"""
Compares the two images, pixel by pixel, and returns a new image containing
the lighter values. At least one of the images must have mode "1".
the lighter values.
.. code-block:: python
@ -71,7 +71,7 @@ def lighter(image1, image2):
def darker(image1, image2):
"""
Compares the two images, pixel by pixel, and returns a new image containing
the darker values. At least one of the images must have mode "1".
the darker values.
.. code-block:: python
@ -88,7 +88,7 @@ def darker(image1, image2):
def difference(image1, image2):
"""
Returns the absolute value of the pixel-by-pixel difference between the two
images. At least one of the images must have mode "1".
images.
.. code-block:: python
@ -107,8 +107,7 @@ def multiply(image1, image2):
Superimposes two images on top of each other.
If you multiply an image with a solid black image, the result is black. If
you multiply with a solid white image, the image is unaffected. At least
one of the images must have mode "1".
you multiply with a solid white image, the image is unaffected.
.. code-block:: python
@ -124,8 +123,7 @@ def multiply(image1, image2):
def screen(image1, image2):
"""
Superimposes two inverted images on top of each other. At least one of the
images must have mode "1".
Superimposes two inverted images on top of each other.
.. code-block:: python
@ -179,7 +177,6 @@ def add(image1, image2, scale=1.0, offset=0):
"""
Adds two images, dividing the result by scale and adding the
offset. If omitted, scale defaults to 1.0, and offset to 0.0.
At least one of the images must have mode "1".
.. code-block:: python
@ -196,8 +193,7 @@ def add(image1, image2, scale=1.0, offset=0):
def subtract(image1, image2, scale=1.0, offset=0):
"""
Subtracts two images, dividing the result by scale and adding the offset.
If omitted, scale defaults to 1.0, and offset to 0.0. At least one of the
images must have mode "1".
If omitted, scale defaults to 1.0, and offset to 0.0.
.. code-block:: python
@ -212,8 +208,7 @@ def subtract(image1, image2, scale=1.0, offset=0):
def add_modulo(image1, image2):
"""Add two images, without clipping the result. At least one of the images
must have mode "1".
"""Add two images, without clipping the result.
.. code-block:: python
@ -228,8 +223,7 @@ def add_modulo(image1, image2):
def subtract_modulo(image1, image2):
"""Subtract two images, without clipping the result. At least one of the
images must have mode "1".
"""Subtract two images, without clipping the result.
.. code-block:: python
@ -244,8 +238,12 @@ def subtract_modulo(image1, image2):
def logical_and(image1, image2):
"""Logical AND between two images. At least one of the images must have
mode "1".
"""Logical AND between two images.
Both of the images must have mode "1". If you would like to perform a
logical AND on an image with a mode other than "1", try
:py:meth:`~PIL.ImageChops.multiply` instead, using a black-and-white mask
as the second image.
.. code-block:: python
@ -260,8 +258,9 @@ def logical_and(image1, image2):
def logical_or(image1, image2):
"""Logical OR between two images. At least one of the images must have
mode "1".
"""Logical OR between two images.
Both of the images must have mode "1".
.. code-block:: python
@ -276,8 +275,9 @@ def logical_or(image1, image2):
def logical_xor(image1, image2):
"""Logical XOR between two images. At least one of the images must have
mode "1".
"""Logical XOR between two images.
Both of the images must have mode "1".
.. code-block:: python

View File

@ -192,9 +192,9 @@ class ImageCmsTransform(Image.ImagePointHandler):
"""
Transform. This can be used with the procedural API, or with the standard
Image.point() method.
:py:func:`~PIL.Image.Image.point` method.
Will return the output profile in the output.info['icc_profile'].
Will return the output profile in the ``output.info['icc_profile']``.
"""
def __init__(
@ -251,7 +251,7 @@ class ImageCmsTransform(Image.ImagePointHandler):
def get_display_profile(handle=None):
""" (experimental) Fetches the profile for the current display device.
:returns: None if the profile is not known.
:returns: ``None`` if the profile is not known.
"""
if sys.platform != "win32":
@ -292,27 +292,27 @@ def profileToProfile(
):
"""
(pyCMS) Applies an ICC transformation to a given image, mapping from
inputProfile to outputProfile.
``inputProfile`` to ``outputProfile``.
If the input or output profiles specified are not valid filenames, a
PyCMSError will be raised. If inPlace is True and outputMode != im.mode,
a PyCMSError will be raised. If an error occurs during application of
the profiles, a PyCMSError will be raised. If outputMode is not a mode
supported by the outputProfile (or by pyCMS), a PyCMSError will be
raised.
``PyCMSError`` will be raised. If ``inPlace`` is ``True`` and
``outputMode != im.mode``, a ``PyCMSError`` will be raised. If an error
occurs during application of the profiles, a ``PyCMSError`` will be raised.
If ``outputMode`` is not a mode supported by the ``outputProfile`` (or by pyCMS),
a ``PyCMSError`` will be raised.
This function applies an ICC transformation to im from inputProfile's
color space to outputProfile's color space using the specified rendering
This function applies an ICC transformation to im from ``inputProfile``'s
color space to ``outputProfile``'s color space using the specified rendering
intent to decide how to handle out-of-gamut colors.
OutputMode can be used to specify that a color mode conversion is to
``outputMode`` can be used to specify that a color mode conversion is to
be done using these profiles, but the specified profiles must be able
to handle that mode. I.e., if converting im from RGB to CMYK using
profiles, the input profile must handle RGB data, and the output
profile must handle CMYK data.
:param im: An open PIL image object (i.e. Image.new(...) or
Image.open(...), etc.)
:param im: An open :py:class:`~PIL.Image.Image` object (i.e. Image.new(...)
or Image.open(...), etc.)
:param inputProfile: String, as a valid filename path to the ICC input
profile you wish to use for this image, or a profile object
:param outputProfile: String, as a valid filename path to the ICC output
@ -332,12 +332,12 @@ def profileToProfile(
MUST be the same mode as the input, or omitted completely. If
omitted, the outputMode will be the same as the mode of the input
image (im.mode)
:param inPlace: Boolean. If True, the original image is modified in-place,
and None is returned. If False (default), a new Image object is
returned with the transform applied.
:param inPlace: Boolean. If ``True``, the original image is modified in-place,
and ``None`` is returned. If ``False`` (default), a new
:py:class:`~PIL.Image.Image` object is returned with the transform applied.
:param flags: Integer (0-...) specifying additional flags
:returns: Either None or a new PIL image object, depending on value of
inPlace
:returns: Either None or a new :py:class:`~PIL.Image.Image` object, depending on
the value of ``inPlace``
:exception PyCMSError:
"""
@ -381,7 +381,7 @@ def getOpenProfile(profileFilename):
The PyCMSProfile object can be passed back into pyCMS for use in creating
transforms and such (as in ImageCms.buildTransformFromOpenProfiles()).
If profileFilename is not a valid filename for an ICC profile, a PyCMSError
If ``profileFilename`` is not a valid filename for an ICC profile, a ``PyCMSError``
will be raised.
:param profileFilename: String, as a valid filename path to the ICC profile
@ -405,21 +405,21 @@ def buildTransform(
flags=0,
):
"""
(pyCMS) Builds an ICC transform mapping from the inputProfile to the
outputProfile. Use applyTransform to apply the transform to a given
(pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
``outputProfile``. Use applyTransform to apply the transform to a given
image.
If the input or output profiles specified are not valid filenames, a
PyCMSError will be raised. If an error occurs during creation of the
transform, a PyCMSError will be raised.
``PyCMSError`` will be raised. If an error occurs during creation of the
transform, a ``PyCMSError`` will be raised.
If inMode or outMode are not a mode supported by the outputProfile (or
by pyCMS), a PyCMSError will be raised.
If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile``
(or by pyCMS), a ``PyCMSError`` will be raised.
This function builds and returns an ICC transform from the inputProfile
to the outputProfile using the renderingIntent to determine what to do
This function builds and returns an ICC transform from the ``inputProfile``
to the ``outputProfile`` using the ``renderingIntent`` to determine what to do
with out-of-gamut colors. It will ONLY work for converting images that
are in inMode to images that are in outMode color format (PIL mode,
are in ``inMode`` to images that are in ``outMode`` color format (PIL mode,
i.e. "RGB", "RGBA", "CMYK", etc.).
Building the transform is a fair part of the overhead in
@ -432,7 +432,7 @@ def buildTransform(
The reason pyCMS returns a class object rather than a handle directly
to the transform is that it needs to keep track of the PIL input/output
modes that the transform is meant for. These attributes are stored in
the "inMode" and "outMode" attributes of the object (which can be
the ``inMode`` and ``outMode`` attributes of the object (which can be
manually overridden if you really want to, but I don't know of any
time that would be of use, or would even work).
@ -488,25 +488,25 @@ def buildProofTransform(
flags=FLAGS["SOFTPROOFING"],
):
"""
(pyCMS) Builds an ICC transform mapping from the inputProfile to the
outputProfile, but tries to simulate the result that would be
obtained on the proofProfile device.
(pyCMS) Builds an ICC transform mapping from the ``inputProfile`` to the
``outputProfile``, but tries to simulate the result that would be
obtained on the ``proofProfile`` device.
If the input, output, or proof profiles specified are not valid
filenames, a PyCMSError will be raised.
filenames, a ``PyCMSError`` will be raised.
If an error occurs during creation of the transform, a PyCMSError will
be raised.
If an error occurs during creation of the transform, a ``PyCMSError``
will be raised.
If inMode or outMode are not a mode supported by the outputProfile
(or by pyCMS), a PyCMSError will be raised.
If ``inMode`` or ``outMode`` are not a mode supported by the ``outputProfile``
(or by pyCMS), a ``PyCMSError`` will be raised.
This function builds and returns an ICC transform from the inputProfile
to the outputProfile, but tries to simulate the result that would be
obtained on the proofProfile device using renderingIntent and
proofRenderingIntent to determine what to do with out-of-gamut
This function builds and returns an ICC transform from the ``inputProfile``
to the ``outputProfile``, but tries to simulate the result that would be
obtained on the ``proofProfile`` device using ``renderingIntent`` and
``proofRenderingIntent`` to determine what to do with out-of-gamut
colors. This is known as "soft-proofing". It will ONLY work for
converting images that are in inMode to images that are in outMode
converting images that are in ``inMode`` to images that are in outMode
color format (PIL mode, i.e. "RGB", "RGBA", "CMYK", etc.).
Usage of the resulting transform object is exactly the same as with
@ -514,7 +514,7 @@ def buildProofTransform(
Proof profiling is generally used when using an output device to get a
good idea of what the final printed/displayed image would look like on
the proofProfile device when it's quicker and easier to use the
the ``proofProfile`` device when it's quicker and easier to use the
output device for judging color. Generally, this means that the
output device is a monitor, or a dye-sub printer (etc.), and the simulated
device is something more expensive, complicated, or time consuming
@ -596,39 +596,40 @@ def applyTransform(im, transform, inPlace=False):
"""
(pyCMS) Applies a transform to a given image.
If im.mode != transform.inMode, a PyCMSError is raised.
If ``im.mode != transform.inMode``, a ``PyCMSError`` is raised.
If inPlace is True and transform.inMode != transform.outMode, a
PyCMSError is raised.
If ``inPlace`` is ``True`` and ``transform.inMode != transform.outMode``, a
``PyCMSError`` is raised.
If im.mode, transform.inMode, or transform.outMode is not supported by
pyCMSdll or the profiles you used for the transform, a PyCMSError is
raised.
If ``im.mode``, ``transform.inMode`` or ``transform.outMode`` is not
supported by pyCMSdll or the profiles you used for the transform, a
``PyCMSError`` is raised.
If an error occurs while the transform is being applied, a PyCMSError
If an error occurs while the transform is being applied, a ``PyCMSError``
is raised.
This function applies a pre-calculated transform (from
ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles())
to an image. The transform can be used for multiple images, saving
to an image. The transform can be used for multiple images, saving
considerable calculation time if doing the same conversion multiple times.
If you want to modify im in-place instead of receiving a new image as
the return value, set inPlace to True. This can only be done if
transform.inMode and transform.outMode are the same, because we can't
the return value, set ``inPlace`` to ``True``. This can only be done if
``transform.inMode`` and ``transform.outMode`` are the same, because we can't
change the mode in-place (the buffer sizes for some modes are
different). The default behavior is to return a new Image object of
the same dimensions in mode transform.outMode.
different). The default behavior is to return a new :py:class:`~PIL.Image.Image`
object of the same dimensions in mode ``transform.outMode``.
:param im: A PIL Image object, and im.mode must be the same as the inMode
supported by the transform.
:param im: An :py:class:`~PIL.Image.Image` object, and im.mode must be the same
as the ``inMode`` supported by the transform.
:param transform: A valid CmsTransform class object
:param inPlace: Bool. If True, im is modified in place and None is
returned, if False, a new Image object with the transform applied is
returned (and im is not changed). The default is False.
:returns: Either None, or a new PIL Image object, depending on the value of
inPlace. The profile will be returned in the image's
info['icc_profile'].
:param inPlace: Bool. If ``True``, ``im` is modified in place and ``None`` is
returned, if ``False``, a new :py:class:`~PIL.Image.Image` object with the
transform applied is returned (and ``im`` is not changed). The default is
``False``.
:returns: Either ``None``, or a new :py:class:`~PIL.Image.Image` object,
depending on the value of ``inPlace``. The profile will be returned in
the image's ``info['icc_profile']``.
:exception PyCMSError:
"""
@ -648,11 +649,12 @@ def createProfile(colorSpace, colorTemp=-1):
"""
(pyCMS) Creates a profile.
If colorSpace not in ["LAB", "XYZ", "sRGB"], a PyCMSError is raised
If colorSpace not in ``["LAB", "XYZ", "sRGB"]``, a ``PyCMSError`` is raised.
If using LAB and colorTemp != a positive integer, a PyCMSError is raised.
If using LAB and ``colorTemp`` is not a positive integer, a ``PyCMSError`` is
raised.
If an error occurs while creating the profile, a PyCMSError is raised.
If an error occurs while creating the profile, a ``PyCMSError`` is raised.
Use this function to create common profiles on-the-fly instead of
having to supply a profile on disk and knowing the path to it. It
@ -696,9 +698,9 @@ def getProfileName(profile):
(pyCMS) Gets the internal product name for the given profile.
If profile isn't a valid CmsProfile object or filename to a profile,
a PyCMSError is raised If an error occurs while trying to obtain the
name tag, a PyCMSError is raised.
If ``profile`` isn't a valid CmsProfile object or filename to a profile,
a ``PyCMSError`` is raised If an error occurs while trying to obtain the
name tag, a ``PyCMSError`` is raised.
Use this function to obtain the INTERNAL name of the profile (stored
in an ICC tag in the profile itself), usually the one used when the
@ -737,11 +739,11 @@ def getProfileInfo(profile):
"""
(pyCMS) Gets the internal product information for the given profile.
If profile isn't a valid CmsProfile object or filename to a profile,
a PyCMSError is raised.
If ``profile`` isn't a valid CmsProfile object or filename to a profile,
a ``PyCMSError`` is raised.
If an error occurs while trying to obtain the info tag, a PyCMSError
is raised
If an error occurs while trying to obtain the info tag, a ``PyCMSError``
is raised.
Use this function to obtain the information stored in the profile's
info tag. This often contains details about the profile, and how it
@ -777,11 +779,11 @@ def getProfileCopyright(profile):
"""
(pyCMS) Gets the copyright for the given profile.
If profile isn't a valid CmsProfile object or filename to a profile,
a PyCMSError is raised.
If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
``PyCMSError`` is raised.
If an error occurs while trying to obtain the copyright tag, a PyCMSError
is raised
If an error occurs while trying to obtain the copyright tag, a ``PyCMSError``
is raised.
Use this function to obtain the information stored in the profile's
copyright tag.
@ -805,11 +807,11 @@ def getProfileManufacturer(profile):
"""
(pyCMS) Gets the manufacturer for the given profile.
If profile isn't a valid CmsProfile object or filename to a profile,
a PyCMSError is raised.
If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
``PyCMSError`` is raised.
If an error occurs while trying to obtain the manufacturer tag, a
PyCMSError is raised
``PyCMSError`` is raised.
Use this function to obtain the information stored in the profile's
manufacturer tag.
@ -833,11 +835,11 @@ def getProfileModel(profile):
"""
(pyCMS) Gets the model for the given profile.
If profile isn't a valid CmsProfile object or filename to a profile,
a PyCMSError is raised.
If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
``PyCMSError`` is raised.
If an error occurs while trying to obtain the model tag, a PyCMSError
is raised
If an error occurs while trying to obtain the model tag, a ``PyCMSError``
is raised.
Use this function to obtain the information stored in the profile's
model tag.
@ -862,11 +864,11 @@ def getProfileDescription(profile):
"""
(pyCMS) Gets the description for the given profile.
If profile isn't a valid CmsProfile object or filename to a profile,
a PyCMSError is raised.
If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
``PyCMSError`` is raised.
If an error occurs while trying to obtain the description tag, a PyCMSError
is raised
If an error occurs while trying to obtain the description tag, a ``PyCMSError``
is raised.
Use this function to obtain the information stored in the profile's
description tag.
@ -891,11 +893,11 @@ def getDefaultIntent(profile):
"""
(pyCMS) Gets the default intent name for the given profile.
If profile isn't a valid CmsProfile object or filename to a profile,
a PyCMSError is raised.
If ``profile`` isn't a valid CmsProfile object or filename to a profile, a
``PyCMSError`` is raised.
If an error occurs while trying to obtain the default intent, a
PyCMSError is raised.
``PyCMSError`` is raised.
Use this function to determine the default (and usually best optimized)
rendering intent for this profile. Most profiles support multiple
@ -931,14 +933,14 @@ def isIntentSupported(profile, intent, direction):
(pyCMS) Checks if a given intent is supported.
Use this function to verify that you can use your desired
renderingIntent with profile, and that profile can be used for the
``intent`` with ``profile``, and that ``profile`` can be used for the
input/output/proof profile as you desire.
Some profiles are created specifically for one "direction", can cannot
be used for others. Some profiles can only be used for certain
rendering intents... so it's best to either verify this before trying
be used for others. Some profiles can only be used for certain
rendering intents, so it's best to either verify this before trying
to create a transform with them (using this function), or catch the
potential PyCMSError that will occur if they don't support the modes
potential ``PyCMSError`` that will occur if they don't support the modes
you select.
:param profile: EITHER a valid CmsProfile object, OR a string of the

View File

@ -156,6 +156,8 @@ class ImageDraw:
if ink is not None:
self.draw.draw_lines(xy, ink, width)
if joint == "curve" and width > 4:
if not isinstance(xy[0], (list, tuple)):
xy = [tuple(xy[i : i + 2]) for i in range(0, len(xy), 2)]
for i in range(1, len(xy) - 1):
point = xy[i]
angles = [

View File

@ -30,6 +30,7 @@
import io
import struct
import sys
import warnings
from . import Image
from ._util import isPath
@ -49,7 +50,12 @@ ERRORS = {
}
def raise_ioerror(error):
#
# --------------------------------------------------------------------
# Helpers
def raise_oserror(error):
try:
message = Image.core.getcodecstatus(error)
except AttributeError:
@ -59,9 +65,13 @@ def raise_ioerror(error):
raise OSError(message + " when reading image file")
#
# --------------------------------------------------------------------
# Helpers
def raise_ioerror(error):
warnings.warn(
"raise_ioerror is deprecated and will be removed in a future release. "
"Use raise_oserror instead.",
DeprecationWarning,
)
return raise_oserror(error)
def _tilesort(t):
@ -140,10 +150,10 @@ class ImageFile(Image.Image):
def load(self):
"""Load image data based on tile list"""
pixel = Image.Image.load(self)
if self.tile is None:
raise OSError("cannot load this image")
pixel = Image.Image.load(self)
if not self.tile:
return pixel
@ -267,7 +277,7 @@ class ImageFile(Image.Image):
if not self.map and not LOAD_TRUNCATED_IMAGES and err_code < 0:
# still raised if decoder fails to return anything
raise_ioerror(err_code)
raise_oserror(err_code)
return Image.Image.load(self)
@ -358,7 +368,7 @@ class Parser:
(Consumer) Feed data to the parser.
:param data: A string buffer.
:exception IOError: If the parser failed to parse the image file.
:exception OSError: If the parser failed to parse the image file.
"""
# collect data
@ -390,7 +400,7 @@ class Parser:
if e < 0:
# decoding error
self.image = None
raise_ioerror(e)
raise_oserror(e)
else:
# end of image
return
@ -444,7 +454,7 @@ class Parser:
(Consumer) Close the stream.
:returns: An image object.
:exception IOError: If the parser failed to parse the image file either
:exception OSError: If the parser failed to parse the image file either
because it cannot be identified or cannot be
decoded.
"""

View File

@ -499,7 +499,7 @@ class FreeTypeFont:
def get_variation_names(self):
"""
:returns: A list of the named styles in a variation font.
:exception IOError: If the font is not a variation font.
:exception OSError: If the font is not a variation font.
"""
try:
names = self.font.getvarnames()
@ -510,7 +510,7 @@ class FreeTypeFont:
def set_variation_by_name(self, name):
"""
:param name: The name of the style.
:exception IOError: If the font is not a variation font.
:exception OSError: If the font is not a variation font.
"""
names = self.get_variation_names()
if not isinstance(name, bytes):
@ -529,7 +529,7 @@ class FreeTypeFont:
def get_variation_axes(self):
"""
:returns: A list of the axes in a variation font.
:exception IOError: If the font is not a variation font.
:exception OSError: If the font is not a variation font.
"""
try:
axes = self.font.getvaraxes()
@ -542,7 +542,7 @@ class FreeTypeFont:
def set_variation_by_axes(self, axes):
"""
:param axes: A list of values for each axis.
:exception IOError: If the font is not a variation font.
:exception OSError: If the font is not a variation font.
"""
try:
self.font.setvaraxes(axes)
@ -586,7 +586,7 @@ def load(filename):
:param filename: Name of font file.
:return: A font object.
:exception IOError: If the file could not be read.
:exception OSError: If the file could not be read.
"""
f = ImageFont()
f._load_pilfont(filename)
@ -637,8 +637,13 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
encoding of any text provided in subsequent operations.
:param layout_engine: Which layout engine to use, if available:
`ImageFont.LAYOUT_BASIC` or `ImageFont.LAYOUT_RAQM`.
You can check support for Raqm layout using
:py:func:`PIL.features.check_feature` with ``feature="raqm"``.
.. versionadded:: 4.2.0
:return: A font object.
:exception IOError: If the file could not be read.
:exception OSError: If the file could not be read.
"""
def freetype(font):
@ -698,7 +703,7 @@ def load_path(filename):
:param filename: Name of font file.
:return: A font object.
:exception IOError: If the file could not be read.
:exception OSError: If the file could not be read.
"""
for directory in sys.path:
if isDirectory(directory):

View File

@ -2,7 +2,7 @@
# The Python Imaging Library
# $Id$
#
# screen grabber (macOS and Windows only)
# screen grabber
#
# History:
# 2001-04-26 fl created
@ -60,7 +60,7 @@ def grab(bbox=None, include_layered_windows=False, all_screens=False, xdisplay=N
return im
# use xdisplay=None for default display on non-win32/macOS systems
if not Image.core.HAVE_XCB:
raise IOError("Pillow was built without XCB support")
raise OSError("Pillow was built without XCB support")
size, data = Image.core.grabscreen_x11(xdisplay)
im = Image.frombytes("RGB", size, data, "raw", "BGRX", size[0] * 4, 1)
if bbox:

View File

@ -142,12 +142,7 @@ def _toqclass_helper(im):
data = im.tobytes("raw", "BGRX")
format = QImage.Format_RGB32
elif im.mode == "RGBA":
try:
data = im.tobytes("raw", "BGRA")
except SystemError:
# workaround for earlier versions
r, g, b, a = im.split()
im = Image.merge("RGBA", (b, g, r, a))
data = im.tobytes("raw", "BGRA")
format = QImage.Format_ARGB32
else:
raise ValueError("unsupported image mode %r" % im.mode)

View File

@ -173,7 +173,7 @@ class Dib:
Load display memory contents from byte data.
:param buffer: A buffer containing display data (usually
data returned from <b>tobytes</b>)
data returned from :py:func:`~PIL.ImageWin.Dib.tobytes`)
"""
return self.image.frombytes(buffer)

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