Merge remote-tracking branch 'upstream/master' into ellipse
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
2
.github/workflows/lint.yml
vendored
|
@ -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
|
||||
|
|
7
.github/workflows/test-docker.yml
vendored
|
@ -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
|
||||
|
|
390
.github/workflows/test-windows.yml
vendored
|
@ -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 }}
|
||||
|
|
8
.github/workflows/test.yml
vendored
|
@ -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 }}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
106
CHANGES.rst
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
2
Makefile
|
@ -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 .
|
||||
|
|
14
RELEASING.md
|
@ -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.:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
BIN
Tests/images/icc-after-SOF.jpg
Normal file
After Width: | Height: | Size: 212 B |
BIN
Tests/images/imagedraw_polygon_1px_high.png
Normal file
After Width: | Height: | Size: 73 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/variation_adobe_older_harfbuzz.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/variation_adobe_older_harfbuzz_axes.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/variation_adobe_older_harfbuzz_name.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Tests/images/xmp_tags_orientation.png
Normal file
After Width: | Height: | Size: 171 KiB |
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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",))
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -289,4 +289,4 @@ texinfo_documents = [
|
|||
|
||||
|
||||
def setup(app):
|
||||
app.add_javascript("js/script.js")
|
||||
app.add_js_file("js/script.js")
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 doesn’t 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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 |
|
||||
+----------------------------------+------------------------------+--------------------------------+-----------------------+
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
-------
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
60
docs/reference/features.rst
Normal 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
|
|
@ -30,6 +30,7 @@ Reference
|
|||
PSDraw
|
||||
PixelAccess
|
||||
PyAccess
|
||||
features
|
||||
../PIL
|
||||
plugins
|
||||
internal_design
|
||||
|
|
|
@ -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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
@ -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
|
||||
=============
|
||||
|
|
16
docs/releasenotes/7.1.2.rst
Normal 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.
|
29
docs/releasenotes/7.2.0.rst
Normal 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.
|
|
@ -6,6 +6,8 @@ Release Notes
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
7.2.0
|
||||
7.1.2
|
||||
7.1.1
|
||||
7.1.0
|
||||
7.0.0
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -10,3 +10,4 @@ multi_line_output = 3
|
|||
|
||||
[tool:pytest]
|
||||
addopts = -rs
|
||||
testpaths = Tests
|
||||
|
|
10
setup.py
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 == "<":
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|