Merge remote-tracking branch 'pillow/main' into improved_dds
# Conflicts: # Tests/test_file_dds.py # src/PIL/DdsImagePlugin.py
|
@ -20,13 +20,12 @@ environment:
|
|||
|
||||
install:
|
||||
- '%PYTHON%\%EXECUTABLE% --version'
|
||||
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
|
||||
- '%PYTHON%\%EXECUTABLE% -m pip install --upgrade pip'
|
||||
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
|
||||
- 7z x pillow-depends.zip -oc:\
|
||||
- 7z x pillow-test-images.zip -oc:\
|
||||
- mv c:\pillow-depends-main c:\pillow-depends
|
||||
- xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images
|
||||
- 7z x ..\pillow-depends\nasm-2.16.01-win64.zip -oc:\
|
||||
- curl -fsSL -o nasm-win64.zip https://raw.githubusercontent.com/python-pillow/pillow-depends/main/nasm-2.16.01-win64.zip
|
||||
- 7z x nasm-win64.zip -oc:\
|
||||
- choco install ghostscript --version=10.0.0.20230317
|
||||
- path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH%
|
||||
- cd c:\pillow\winbuild\
|
||||
|
@ -37,10 +36,9 @@ install:
|
|||
- path C:\pillow\winbuild\build\bin;%PATH%
|
||||
|
||||
build_script:
|
||||
- ps: |
|
||||
c:\pillow\winbuild\build\build_pillow.cmd install
|
||||
$host.SetShouldExit(0)
|
||||
- cd c:\pillow
|
||||
- winbuild\build\build_env.cmd
|
||||
- '%PYTHON%\%EXECUTABLE% -m pip install -v -C raqm=vendor -C fribidi=vendor .'
|
||||
- '%PYTHON%\%EXECUTABLE% selftest.py --installed'
|
||||
|
||||
test_script:
|
||||
|
@ -62,18 +60,15 @@ cache:
|
|||
- '%LOCALAPPDATA%\pip\Cache'
|
||||
|
||||
artifacts:
|
||||
- path: pillow\dist\*.egg
|
||||
- path: pillow\*.egg
|
||||
name: egg
|
||||
- path: pillow\dist\*.wheel
|
||||
- path: pillow\*.whl
|
||||
name: wheel
|
||||
|
||||
before_deploy:
|
||||
- cd c:\pillow
|
||||
- '%PYTHON%\%EXECUTABLE% -m pip install wheel'
|
||||
- cd c:\pillow\winbuild\
|
||||
- c:\pillow\winbuild\build\build_pillow.cmd bdist_wheel
|
||||
- cd c:\pillow
|
||||
- ps: Get-ChildItem .\dist\*.* | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
|
||||
- '%PYTHON%\%EXECUTABLE% -m pip wheel -v -C raqm=vendor -C fribidi=vendor .'
|
||||
- ps: Get-ChildItem .\*.whl | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
|
||||
|
||||
deploy:
|
||||
provider: S3
|
||||
|
|
|
@ -23,7 +23,7 @@ if [[ $(uname) != CYGWIN* ]]; then
|
|||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
||||
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
||||
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
||||
sway wl-clipboard
|
||||
sway wl-clipboard libopenblas-dev
|
||||
fi
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
|
@ -38,11 +38,10 @@ python3 -m pip install -U pytest-timeout
|
|||
python3 -m pip install pyroma
|
||||
|
||||
if [[ $(uname) != CYGWIN* ]]; then
|
||||
# TODO Remove condition when NumPy supports 3.12
|
||||
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
|
||||
python3 -m pip install numpy
|
||||
|
||||
# PyQt6 doesn't support PyPy3
|
||||
if [[ "$GHA_PYTHON_VERSION" != "3.12-dev" && $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||
if [[ $GHA_PYTHON_VERSION == 3.* ]]; then
|
||||
sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0
|
||||
python3 -m pip install pyqt6
|
||||
fi
|
||||
|
|
1
.github/CONTRIBUTING.md
vendored
|
@ -19,6 +19,7 @@ Please send a pull request to the `main` branch. Please include [documentation](
|
|||
- Follow PEP 8.
|
||||
- When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor.
|
||||
- Include [release notes](https://github.com/python-pillow/Pillow/tree/main/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests.
|
||||
- Do not add to the [changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) for proposed changes, as that is updated after changes are merged.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
|
|
2
.github/mergify.yml
vendored
|
@ -7,7 +7,7 @@ pull_request_rules:
|
|||
- status-success=Test Successful
|
||||
- status-success=Docker Test Successful
|
||||
- status-success=Windows Test Successful
|
||||
- status-success=MinGW Test Successful
|
||||
- status-success=MinGW
|
||||
- status-success=Cygwin Test Successful
|
||||
- status-success=continuous-integration/appveyor/pr
|
||||
actions:
|
||||
|
|
2
.github/workflows/docs.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
|||
name: Docs
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
|
|
2
.github/workflows/lint.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
|||
name: Lint
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: pre-commit cache
|
||||
uses: actions/cache@v3
|
||||
|
|
4
.github/workflows/macos-install.sh
vendored
|
@ -3,6 +3,7 @@
|
|||
set -e
|
||||
|
||||
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
|
||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||
|
||||
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
||||
python3 -m pip install coverage
|
||||
|
@ -13,8 +14,7 @@ python3 -m pip install -U pytest-cov
|
|||
python3 -m pip install -U pytest-timeout
|
||||
python3 -m pip install pyroma
|
||||
|
||||
# TODO Remove condition when NumPy supports 3.12
|
||||
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
|
||||
python3 -m pip install numpy
|
||||
|
||||
# extra test images
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
|
34
.github/workflows/test-cygwin.yml
vendored
|
@ -4,11 +4,19 @@ on:
|
|||
push:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
@ -36,7 +44,7 @@ jobs:
|
|||
git config --global core.autocrlf input
|
||||
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Cygwin
|
||||
uses: cygwin/cygwin-install-action@v4
|
||||
|
@ -76,17 +84,23 @@ jobs:
|
|||
with:
|
||||
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
|
||||
|
||||
- name: Select Python version
|
||||
run: |
|
||||
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
|
||||
|
||||
- name: Get latest NumPy version
|
||||
id: latest-numpy
|
||||
shell: bash.exe -eo pipefail -o igncr "{0}"
|
||||
run: |
|
||||
python3 -m pip list --outdated | grep numpy | sed -r 's/ +/ /g' | cut -d ' ' -f 3 | sed 's/^/version=/' >> $GITHUB_OUTPUT
|
||||
|
||||
- name: pip cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
||||
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-${{ hashFiles('.ci/install.sh') }}
|
||||
key: ${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-${{ hashFiles('.ci/install.sh') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
|
||||
|
||||
- name: Select Python version
|
||||
run: |
|
||||
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
|
||||
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-
|
||||
|
||||
- name: Build system information
|
||||
run: |
|
||||
|
@ -96,15 +110,15 @@ jobs:
|
|||
run: |
|
||||
bash.exe .ci/install.sh
|
||||
|
||||
- name: Install a different NumPy
|
||||
- name: Upgrade NumPy
|
||||
shell: dash.exe -l "{0}"
|
||||
run: |
|
||||
python3 -m pip install -U numpy
|
||||
python3 -m pip install -U "numpy<1.26"
|
||||
|
||||
- name: Build
|
||||
shell: bash.exe -eo pipefail -o igncr "{0}"
|
||||
run: |
|
||||
SETUPTOOLS_USE_DISTUTILS=stdlib .ci/build.sh
|
||||
.ci/build.sh
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
|
|
13
.github/workflows/test-docker.yml
vendored
|
@ -4,11 +4,19 @@ on:
|
|||
push:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
@ -38,8 +46,9 @@ jobs:
|
|||
centos-7-amd64,
|
||||
centos-stream-8-amd64,
|
||||
centos-stream-9-amd64,
|
||||
debian-11-bullseye-x86,
|
||||
debian-11-bullseye-amd64,
|
||||
debian-12-bookworm-x86,
|
||||
debian-12-bookworm-amd64,
|
||||
fedora-37-amd64,
|
||||
fedora-38-amd64,
|
||||
gentoo,
|
||||
|
@ -58,7 +67,7 @@ jobs:
|
|||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
|
75
.github/workflows/test-mingw.yml
vendored
|
@ -4,11 +4,19 @@ on:
|
|||
push:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
@ -21,31 +29,20 @@ concurrency:
|
|||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
mingw: ["MINGW32", "MINGW64"]
|
||||
include:
|
||||
- mingw: "MINGW32"
|
||||
name: "MSYS2 MinGW 32-bit"
|
||||
package: "mingw-w64-i686"
|
||||
- mingw: "MINGW64"
|
||||
name: "MSYS2 MinGW 64-bit"
|
||||
package: "mingw-w64-x86_64"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash.exe --login -eo pipefail "{0}"
|
||||
env:
|
||||
MSYSTEM: ${{ matrix.mingw }}
|
||||
MSYSTEM: MINGW64
|
||||
CHERE_INVOKING: 1
|
||||
|
||||
timeout-minutes: 30
|
||||
name: ${{ matrix.name }}
|
||||
name: "MinGW"
|
||||
|
||||
steps:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up shell
|
||||
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
||||
|
@ -54,33 +51,29 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: |
|
||||
pacman -S --noconfirm \
|
||||
${{ matrix.package }}-freetype \
|
||||
${{ matrix.package }}-gcc \
|
||||
${{ matrix.package }}-ghostscript \
|
||||
${{ matrix.package }}-lcms2 \
|
||||
${{ matrix.package }}-libimagequant \
|
||||
${{ matrix.package }}-libjpeg-turbo \
|
||||
${{ matrix.package }}-libraqm \
|
||||
${{ matrix.package }}-libtiff \
|
||||
${{ matrix.package }}-libwebp \
|
||||
${{ matrix.package }}-openjpeg2 \
|
||||
${{ matrix.package }}-python3-cffi \
|
||||
${{ matrix.package }}-python3-numpy \
|
||||
${{ matrix.package }}-python3-olefile \
|
||||
${{ matrix.package }}-python3-pip \
|
||||
${{ matrix.package }}-python3-setuptools
|
||||
|
||||
if [ ${{ matrix.package }} == "mingw-w64-x86_64" ]; then
|
||||
pacman -S --noconfirm \
|
||||
${{ matrix.package }}-python-pyqt6
|
||||
fi
|
||||
mingw-w64-x86_64-freetype \
|
||||
mingw-w64-x86_64-gcc \
|
||||
mingw-w64-x86_64-ghostscript \
|
||||
mingw-w64-x86_64-lcms2 \
|
||||
mingw-w64-x86_64-libimagequant \
|
||||
mingw-w64-x86_64-libjpeg-turbo \
|
||||
mingw-w64-x86_64-libraqm \
|
||||
mingw-w64-x86_64-libtiff \
|
||||
mingw-w64-x86_64-libwebp \
|
||||
mingw-w64-x86_64-openjpeg2 \
|
||||
mingw-w64-x86_64-python3-cffi \
|
||||
mingw-w64-x86_64-python3-numpy \
|
||||
mingw-w64-x86_64-python3-olefile \
|
||||
mingw-w64-x86_64-python3-pip \
|
||||
mingw-w64-x86_64-python3-setuptools \
|
||||
mingw-w64-x86_64-python-pyqt6
|
||||
|
||||
python3 -m pip install pyroma pytest pytest-cov pytest-timeout
|
||||
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
||||
- name: Build Pillow
|
||||
run: SETUPTOOLS_USE_DISTUTILS="stdlib" CFLAGS="-coverage" python3 -m pip install --global-option="build_ext" .
|
||||
run: SETUPTOOLS_USE_DISTUTILS="stdlib" CFLAGS="-coverage" python3 -m pip install .
|
||||
|
||||
- name: Test Pillow
|
||||
run: |
|
||||
|
@ -93,14 +86,4 @@ jobs:
|
|||
with:
|
||||
file: ./coverage.xml
|
||||
flags: GHA_Windows
|
||||
name: ${{ matrix.name }}
|
||||
|
||||
success:
|
||||
permissions:
|
||||
contents: none
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
name: MinGW Test Successful
|
||||
steps:
|
||||
- name: Success
|
||||
run: echo MinGW Test Successful
|
||||
name: "MSYS2 MinGW"
|
||||
|
|
2
.github/workflows/test-valgrind.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
|||
name: ${{ matrix.docker }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
|
49
.github/workflows/test-windows.yml
vendored
|
@ -4,11 +4,19 @@ on:
|
|||
push:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
@ -24,31 +32,24 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12-dev"]
|
||||
architecture: ["x86", "x64"]
|
||||
include:
|
||||
# PyPy 7.3.4+ only ships 64-bit binaries for Windows
|
||||
- python-version: "pypy3.8"
|
||||
architecture: "x64"
|
||||
- python-version: "pypy3.9"
|
||||
architecture: "x64"
|
||||
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||
|
||||
timeout-minutes: 30
|
||||
|
||||
name: Python ${{ matrix.python-version }} ${{ matrix.architecture }}
|
||||
name: Python ${{ matrix.python-version }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Pillow
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout cached dependencies
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: python-pillow/pillow-depends
|
||||
path: winbuild\depends
|
||||
|
||||
- name: Checkout extra test images
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: python-pillow/test-images
|
||||
path: Tests\test-images
|
||||
|
@ -58,15 +59,14 @@ jobs:
|
|||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
architecture: ${{ matrix.architecture }}
|
||||
cache: pip
|
||||
cache-dependency-path: ".github/workflows/test-windows.yml"
|
||||
|
||||
- name: Print build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: python3 -m pip install setuptools wheel pytest pytest-cov pytest-timeout defusedxml
|
||||
run: python3 -m pip install setuptools wheel pytest pytest-cov pytest-timeout defusedxml
|
||||
- name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml
|
||||
run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml
|
||||
|
||||
- name: Install dependencies
|
||||
id: install
|
||||
|
@ -97,7 +97,7 @@ jobs:
|
|||
- name: Prepare build
|
||||
if: steps.build-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
& python.exe winbuild\build_prepare.py -v --python $env:pythonLocation
|
||||
& python.exe winbuild\build_prepare.py -v
|
||||
shell: pwsh
|
||||
|
||||
- name: Build dependencies / libjpeg-turbo
|
||||
|
@ -165,9 +165,9 @@ jobs:
|
|||
|
||||
- name: Build Pillow
|
||||
run: |
|
||||
$FLAGS=""
|
||||
if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS="--disable-imagequant" }
|
||||
& winbuild\build\build_pillow.cmd $FLAGS install
|
||||
$FLAGS="-C raqm=vendor -C fribidi=vendor"
|
||||
if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS+=" -C imagequant=disable" }
|
||||
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
|
||||
& $env:pythonLocation\python.exe selftest.py --installed
|
||||
shell: pwsh
|
||||
|
||||
|
@ -206,14 +206,14 @@ jobs:
|
|||
with:
|
||||
file: ./coverage.xml
|
||||
flags: GHA_Windows
|
||||
name: ${{ runner.os }} Python ${{ matrix.python-version }} ${{ matrix.architecture }}
|
||||
name: ${{ runner.os }} Python ${{ matrix.python-version }}
|
||||
|
||||
- name: Build wheel
|
||||
id: wheel
|
||||
if: "github.event_name != 'pull_request'"
|
||||
run: |
|
||||
mkdir fribidi\${{ matrix.architecture }}
|
||||
copy winbuild\build\bin\fribidi* fribidi\${{ matrix.architecture }}
|
||||
mkdir fribidi
|
||||
copy winbuild\build\bin\fribidi* fribidi
|
||||
setlocal EnableDelayedExpansion
|
||||
for %%f in (winbuild\build\license\*) do (
|
||||
set x=%%~nf
|
||||
|
@ -231,7 +231,8 @@ jobs:
|
|||
)
|
||||
)
|
||||
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo dist=dist-%%a >> %GITHUB_OUTPUT%
|
||||
winbuild\\build\\build_pillow.cmd --disable-imagequant bdist_wheel
|
||||
call winbuild\\build\\build_env.cmd
|
||||
%pythonLocation%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor -C imagequant=disable .
|
||||
shell: cmd
|
||||
|
||||
- name: Upload wheel
|
||||
|
@ -239,7 +240,7 @@ jobs:
|
|||
if: "github.event_name != 'pull_request'"
|
||||
with:
|
||||
name: ${{ steps.wheel.outputs.dist }}
|
||||
path: dist\*.whl
|
||||
path: "*.whl"
|
||||
|
||||
- name: Upload fribidi.dll
|
||||
if: "github.event_name != 'pull_request' && matrix.python-version == 3.11"
|
||||
|
|
14
.github/workflows/test.yml
vendored
|
@ -4,11 +4,19 @@ on:
|
|||
push:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- ".github/workflows/docs.yml"
|
||||
- ".github/workflows/wheels*"
|
||||
- ".gitmodules"
|
||||
- ".travis.yml"
|
||||
- "docs/**"
|
||||
- "wheels/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
@ -29,9 +37,9 @@ jobs:
|
|||
"ubuntu-latest",
|
||||
]
|
||||
python-version: [
|
||||
"pypy3.10",
|
||||
"pypy3.9",
|
||||
"pypy3.8",
|
||||
"3.12-dev",
|
||||
"3.12",
|
||||
"3.11",
|
||||
"3.10",
|
||||
"3.9",
|
||||
|
@ -48,7 +56,7 @@ jobs:
|
|||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
|
|
40
.github/workflows/wheels-build.sh
vendored
Executable file
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
# webp, zstd, xz, libtiff, libxcb cause a conflict with building webp, libtiff, libxcb
|
||||
# libxdmcp causes an issue on macOS < 11
|
||||
# curl from brew requires zstd, use system curl
|
||||
# if php is installed, brew tries to reinstall these after installing openblas
|
||||
# remove lcms2 and libpng to fix building openjpeg on arm64
|
||||
brew remove --ignore-dependencies webp zstd xz libpng libtiff libxcb libxdmcp curl php lcms2 ghostscript
|
||||
|
||||
brew install pkg-config
|
||||
|
||||
if [[ "$PLAT" == "arm64" ]]; then
|
||||
export MACOSX_DEPLOYMENT_TARGET="11.0"
|
||||
else
|
||||
export MACOSX_DEPLOYMENT_TARGET="10.10"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$MB_PYTHON_VERSION" == pypy3* ]]; then
|
||||
MB_PYTHON_OSX_VER="10.9"
|
||||
fi
|
||||
|
||||
echo "::group::Install a virtualenv"
|
||||
source wheels/multibuild/common_utils.sh
|
||||
source wheels/multibuild/travis_steps.sh
|
||||
python3 -m pip install virtualenv
|
||||
before_install
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Build wheel"
|
||||
build_wheel
|
||||
ls -l "${GITHUB_WORKSPACE}/${WHEEL_SDIR}/"
|
||||
echo "::endgroup::"
|
||||
|
||||
if [[ $MACOSX_DEPLOYMENT_TARGET != "11.0" ]]; then
|
||||
echo "::group::Test wheel"
|
||||
install_run
|
||||
echo "::endgroup::"
|
||||
fi
|
69
.github/workflows/wheels-linux.yml
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
name: Build Linux wheels
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
artifacts-name:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
CONFIG_PATH: "wheels/config.sh"
|
||||
REPO_DIR: "."
|
||||
TEST_DEPENDS: "pytest pytest-timeout"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.python }} ${{ matrix.mb-ml-libc }}${{ matrix.mb-ml-ver }}
|
||||
runs-on: "ubuntu-latest"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python: [
|
||||
"pypy3.9-7.3.13",
|
||||
"pypy3.10-7.3.13",
|
||||
"3.8",
|
||||
"3.9",
|
||||
"3.10",
|
||||
"3.11",
|
||||
"3.12",
|
||||
]
|
||||
mb-ml-libc: [ "manylinux" ]
|
||||
mb-ml-ver: [ 2014, "_2_28" ]
|
||||
include:
|
||||
- python: "3.8"
|
||||
mb-ml-libc: "musllinux"
|
||||
mb-ml-ver: "_1_1"
|
||||
- python: "3.9"
|
||||
mb-ml-libc: "musllinux"
|
||||
mb-ml-ver: "_1_1"
|
||||
- python: "3.10"
|
||||
mb-ml-libc: "musllinux"
|
||||
mb-ml-ver: "_1_1"
|
||||
- python: "3.11"
|
||||
mb-ml-libc: "musllinux"
|
||||
mb-ml-ver: "_1_1"
|
||||
- python: "3.12"
|
||||
mb-ml-libc: "musllinux"
|
||||
mb-ml-ver: "_1_1"
|
||||
env:
|
||||
MB_PYTHON_VERSION: ${{ matrix.python }}
|
||||
MB_ML_LIBC: ${{ matrix.mb-ml-libc }}
|
||||
MB_ML_VER: ${{ matrix.mb-ml-ver }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Build Wheel
|
||||
run: .github/workflows/wheels-build.sh
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ inputs.artifacts-name }}
|
||||
path: wheelhouse/*.whl
|
||||
# Uncomment to get SSH access for testing
|
||||
# - name: Setup tmate session
|
||||
# if: failure()
|
||||
# uses: mxschmitt/action-tmate@v3
|
57
.github/workflows/wheels-macos.yml
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
name: Build macOS wheels
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
artifacts-name:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
CONFIG_PATH: "wheels/config.sh"
|
||||
REPO_DIR: "."
|
||||
TEST_DEPENDS: "pytest pytest-timeout"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.python }} ${{ matrix.platform }}
|
||||
runs-on: "macos-latest"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python: [
|
||||
"pypy3.9-7.3.13",
|
||||
"pypy3.10-7.3.13",
|
||||
"3.8",
|
||||
"3.9",
|
||||
"3.10",
|
||||
"3.11",
|
||||
"3.12",
|
||||
]
|
||||
platform: [ "x86_64", "arm64" ]
|
||||
exclude:
|
||||
- python: "pypy3.9-7.3.13"
|
||||
platform: "arm64"
|
||||
- python: "pypy3.10-7.3.13"
|
||||
platform: "arm64"
|
||||
env:
|
||||
PLAT: ${{ matrix.platform }}
|
||||
MB_PYTHON_VERSION: ${{ matrix.python }}
|
||||
TRAVIS_OS_NAME: "osx"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Build Wheel
|
||||
run: .github/workflows/wheels-build.sh
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ inputs.artifacts-name }}
|
||||
path: wheelhouse/*.whl
|
||||
# Uncomment to get SSH access for testing
|
||||
# - name: Setup tmate session
|
||||
# if: failure()
|
||||
# uses: mxschmitt/action-tmate@v3
|
42
.github/workflows/wheels.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: Wheels
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/wheels*.yml"
|
||||
- "wheels/*"
|
||||
tags:
|
||||
- "*"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/wheels*.yml"
|
||||
- "wheels/*"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
macos:
|
||||
uses: ./.github/workflows/wheels-macos.yml
|
||||
with:
|
||||
artifacts-name: "wheels"
|
||||
|
||||
linux:
|
||||
uses: ./.github/workflows/wheels-linux.yml
|
||||
with:
|
||||
artifacts-name: "wheels"
|
||||
|
||||
success:
|
||||
permissions:
|
||||
contents: none
|
||||
needs: [macos, linux]
|
||||
runs-on: ubuntu-latest
|
||||
name: Wheels Successful
|
||||
steps:
|
||||
- name: Success
|
||||
run: echo Wheels Successful
|
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "multibuild"]
|
||||
path = wheels/multibuild
|
||||
url = https://github.com/multi-build/multibuild.git
|
|
@ -1,6 +1,12 @@
|
|||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.3.0
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.13.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 23.9.1
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--target-version=py38]
|
||||
|
@ -18,22 +24,22 @@ repos:
|
|||
files: ^src/
|
||||
|
||||
- repo: https://github.com/asottile/yesqa
|
||||
rev: v1.4.0
|
||||
rev: v1.5.0
|
||||
hooks:
|
||||
- id: yesqa
|
||||
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||
rev: v1.5.1
|
||||
rev: v1.5.4
|
||||
hooks:
|
||||
- id: remove-tabs
|
||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
rev: 6.1.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
additional_dependencies:
|
||||
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat]
|
||||
[flake8-2020, flake8-errmsg, flake8-implicit-str-concat, flake8-logging]
|
||||
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.10.0
|
||||
|
@ -44,17 +50,33 @@ repos:
|
|||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-merge-conflict
|
||||
- id: check-json
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
- id: end-of-file-fixer
|
||||
exclude: ^Tests/images/
|
||||
- id: trailing-whitespace
|
||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||
|
||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||
rev: v0.6.7
|
||||
rev: v0.6.8
|
||||
hooks:
|
||||
- id: sphinx-lint
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: 1.2.0
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.14
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
- repo: https://github.com/tox-dev/tox-ini-fmt
|
||||
rev: 1.3.0
|
||||
rev: 1.3.1
|
||||
hooks:
|
||||
- id: tox-ini-fmt
|
||||
|
||||
|
|
135
.travis.yml
Normal file
|
@ -0,0 +1,135 @@
|
|||
if: tag IS present OR type = api
|
||||
|
||||
env:
|
||||
global:
|
||||
- CONFIG_PATH=wheels/config.sh
|
||||
- REPO_DIR=.
|
||||
- PLAT=aarch64
|
||||
- TEST_DEPENDS=pytest-timeout
|
||||
|
||||
language: python
|
||||
# Default Python version is usually 3.6
|
||||
python: "3.11"
|
||||
dist: focal
|
||||
services: docker
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- name: "3.8 Focal manylinux2014 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER=2014
|
||||
- MB_PYTHON_VERSION=3.8
|
||||
- name: "3.8 Focal manylinux_2_28 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER="_2_28"
|
||||
- MB_PYTHON_VERSION=3.8
|
||||
- name: "3.8 musllinux_1_1 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER="_1_1"
|
||||
- MB_ML_LIBC="musllinux"
|
||||
- MB_PYTHON_VERSION=3.8
|
||||
- name: "3.9 Focal manylinux2014 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER=2014
|
||||
- MB_PYTHON_VERSION=3.9
|
||||
- name: "3.9 Focal manylinux_2_28 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER="_2_28"
|
||||
- MB_PYTHON_VERSION=3.9
|
||||
- name: "3.9 musllinux_1_1 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER="_1_1"
|
||||
- MB_ML_LIBC="musllinux"
|
||||
- MB_PYTHON_VERSION=3.9
|
||||
- name: "3.10 Focal manylinux2014 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER=2014
|
||||
- MB_PYTHON_VERSION=3.10
|
||||
- name: "3.10 Focal manylinux_2_28 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER="_2_28"
|
||||
- MB_PYTHON_VERSION=3.10
|
||||
- name: "3.10 musllinux_1_1 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER="_1_1"
|
||||
- MB_ML_LIBC="musllinux"
|
||||
- MB_PYTHON_VERSION=3.10
|
||||
- name: "3.11 Focal manylinux_2_28 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER=2014
|
||||
- MB_PYTHON_VERSION=3.11
|
||||
- name: "3.11 Focal manylinux_2_28 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER="_2_28"
|
||||
- MB_PYTHON_VERSION=3.11
|
||||
- name: "3.11 musllinux_1_1 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER="_1_1"
|
||||
- MB_ML_LIBC="musllinux"
|
||||
- MB_PYTHON_VERSION=3.11
|
||||
- name: "3.12 Focal manylinux_2_28 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER=2014
|
||||
- MB_PYTHON_VERSION=3.12
|
||||
- name: "3.12 Focal manylinux_2_28 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER="_2_28"
|
||||
- MB_PYTHON_VERSION=3.12
|
||||
- name: "3.12 musllinux_1_1 aarch64"
|
||||
os: linux
|
||||
arch: arm64
|
||||
env:
|
||||
- MB_ML_VER="_1_1"
|
||||
- MB_ML_LIBC="musllinux"
|
||||
- MB_PYTHON_VERSION=3.12
|
||||
|
||||
before_install:
|
||||
- source wheels/multibuild/common_utils.sh
|
||||
- source wheels/multibuild/travis_steps.sh
|
||||
- before_install
|
||||
|
||||
install:
|
||||
- build_multilinux aarch64 build_wheel
|
||||
- ls -l "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/"
|
||||
|
||||
script:
|
||||
- install_run
|
||||
|
||||
# Upload wheels to GitHub Releases
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key: $GITHUB_RELEASE_TOKEN
|
||||
file_glob: true
|
||||
file: "${TRAVIS_BUILD_DIR}/${WHEEL_SDIR}/*.whl"
|
||||
on:
|
||||
repo: python-pillow/Pillow
|
||||
tags: true
|
||||
skip_cleanup: true
|
171
CHANGES.rst
|
@ -2,9 +2,147 @@
|
|||
Changelog (Pillow)
|
||||
==================
|
||||
|
||||
10.0.0 (unreleased)
|
||||
10.1.0 (unreleased)
|
||||
-------------------
|
||||
|
||||
- Support BGR;15, BGR;16 and BGR;24 access, unpacking and putdata #7303
|
||||
[radarhere]
|
||||
|
||||
- Added CMYK to RGB unpacker #7310
|
||||
[radarhere]
|
||||
|
||||
- Improved flexibility of XMP parsing #7274
|
||||
[radarhere]
|
||||
|
||||
- Support reading 8-bit YCbCr TIFF images #7415
|
||||
[radarhere]
|
||||
|
||||
- Allow saving I;16B images as PNG #7302
|
||||
[radarhere]
|
||||
|
||||
- Corrected drawing I;16 points and writing I;16 text #7257
|
||||
[radarhere]
|
||||
|
||||
- Set blue channel to 128 for BC5S #7413
|
||||
[radarhere]
|
||||
|
||||
- Increase flexibility when reading IPTC fields #7319
|
||||
[radarhere]
|
||||
|
||||
- Set C palette to be empty by default #7289
|
||||
[radarhere]
|
||||
|
||||
- Added gs_binary to control Ghostscript use on all platforms #7392
|
||||
[radarhere]
|
||||
|
||||
- Read bounding box information from the trailer of EPS files if specified #7382
|
||||
[nopperl, radarhere]
|
||||
|
||||
- Added reading 8-bit color DDS images #7426
|
||||
[radarhere]
|
||||
|
||||
- Added has_transparency_data #7420
|
||||
[radarhere, hugovk]
|
||||
|
||||
- Fixed bug when reading BC5S DDS images #7401
|
||||
[radarhere]
|
||||
|
||||
- Prevent TIFF orientation from being applied more than once #7383
|
||||
[radarhere]
|
||||
|
||||
- Use previous pixel alpha for QOI_OP_RGB #7357
|
||||
[radarhere]
|
||||
|
||||
- Added BC5U reading #7358
|
||||
[radarhere]
|
||||
|
||||
- Allow getpixel() to accept a list #7355
|
||||
[radarhere, homm]
|
||||
|
||||
- Allow GaussianBlur and BoxBlur to accept a sequence of x and y radii #7336
|
||||
[radarhere]
|
||||
|
||||
- Expand JPEG buffer size when saving optimized or progressive #7345
|
||||
[radarhere]
|
||||
|
||||
- Added session type check for Linux in ImageGrab.grabclipboard() #7332
|
||||
[TheNooB2706, radarhere, hugovk]
|
||||
|
||||
- Allow "loop=None" when saving GIF images #7329
|
||||
[radarhere]
|
||||
|
||||
- Fixed transparency when saving P mode images to PDF #7323
|
||||
[radarhere]
|
||||
|
||||
- Added saving LA images as PDFs #7299
|
||||
[radarhere]
|
||||
|
||||
- Set SMaskInData to 1 for PDFs with alpha #7316, #7317
|
||||
[radarhere]
|
||||
|
||||
- Changed Image mode property to be read-only by default #7307
|
||||
[radarhere]
|
||||
|
||||
- Silence exceptions in _repr_jpeg_ and _repr_png_ #7266
|
||||
[mtreinish, radarhere]
|
||||
|
||||
- Do not use transparency when saving GIF if it has been removed when normalizing mode #7284
|
||||
[radarhere]
|
||||
|
||||
- Fix missing symbols when libtiff depends on libjpeg #7270
|
||||
[heitbaum]
|
||||
|
||||
10.0.1 (2023-09-15)
|
||||
-------------------
|
||||
|
||||
- Updated libwebp to 1.3.2 #7395
|
||||
[radarhere]
|
||||
|
||||
- Updated zlib to 1.3 #7344
|
||||
[radarhere]
|
||||
|
||||
10.0.0 (2023-07-01)
|
||||
-------------------
|
||||
|
||||
- Fixed deallocating mask images #7246
|
||||
[radarhere]
|
||||
|
||||
- Added ImageFont.MAX_STRING_LENGTH #7244
|
||||
[radarhere, hugovk]
|
||||
|
||||
- Fix Windows build with pyproject.toml #7230
|
||||
[hugovk, nulano, radarhere]
|
||||
|
||||
- Do not close provided file handles with libtiff #7199
|
||||
[radarhere]
|
||||
|
||||
- Convert to HSV if mode is HSV in getcolor() #7226
|
||||
[radarhere]
|
||||
|
||||
- Added alpha_only argument to getbbox() #7123
|
||||
[radarhere. hugovk]
|
||||
|
||||
- Prioritise speed in _repr_png_ #7242
|
||||
[radarhere]
|
||||
|
||||
- Do not use CFFI access by default on PyPy #7236
|
||||
[radarhere]
|
||||
|
||||
- Limit size even if one dimension is zero in decompression bomb check #7235
|
||||
[radarhere]
|
||||
|
||||
- Use --config-settings instead of deprecated --global-option #7171
|
||||
[radarhere]
|
||||
|
||||
- Better C integer definitions #6645
|
||||
[Yay295, hugovk]
|
||||
|
||||
- Fixed finding dependencies on Cygwin #7175
|
||||
[radarhere]
|
||||
|
||||
- Changed grabclipboard() to use PNG instead of JPG compression on macOS #7219
|
||||
[abey79, radarhere]
|
||||
|
||||
- Added in_place argument to ImageOps.exif_transpose() #7092
|
||||
[radarhere]
|
||||
|
||||
|
@ -5690,8 +5828,8 @@ http://svn.effbot.org/public/pil/
|
|||
a polyline, independent of line angle.
|
||||
|
||||
- Fixed bearing calculation and clipping in the ImageFont truetype
|
||||
renderer; this could lead to clipped text, or crashes in the low-
|
||||
level _imagingft module. (based on input from Adam Twardoch and
|
||||
renderer; this could lead to clipped text, or crashes in the low-level
|
||||
_imagingft module. (based on input from Adam Twardoch and
|
||||
others).
|
||||
|
||||
- Added ImageQt wrapper module, for converting PIL Image objects to
|
||||
|
@ -5772,8 +5910,7 @@ http://svn.effbot.org/public/pil/
|
|||
1.1.5c2 and 1.1.5 final
|
||||
-----------------------
|
||||
|
||||
- Added experimental PERSPECTIVE transform method (from Jeff Breiden-
|
||||
bach).
|
||||
- Added experimental PERSPECTIVE transform method (from Jeff Breidenbach).
|
||||
|
||||
1.1.5c1
|
||||
-------
|
||||
|
@ -5845,8 +5982,8 @@ http://svn.effbot.org/public/pil/
|
|||
|
||||
- Fixed BILINEAR/BICUBIC/ANTIALIAS filtering for mode "LA".
|
||||
|
||||
- Added "getcolors()" method. This is similar to the existing histo-
|
||||
gram method, but looks at color values instead of individual layers,
|
||||
- Added "getcolors()" method. This is similar to the existing histogram
|
||||
method, but looks at color values instead of individual layers,
|
||||
and returns an unsorted list of (count, color) tuples.
|
||||
|
||||
By default, the method returns None if finds more than 256 colors.
|
||||
|
@ -6062,8 +6199,8 @@ http://svn.effbot.org/public/pil/
|
|||
|
||||
- Added limited support for "bitfield compression" in BMP files
|
||||
and DIB buffers, for 15-bit, 16-bit, and 32-bit images. This
|
||||
also fixes a problem with ImageGrab module when copying screen-
|
||||
dumps from the clipboard on 15/16/32-bit displays.
|
||||
also fixes a problem with ImageGrab module when copying screendumps
|
||||
from the clipboard on 15/16/32-bit displays.
|
||||
|
||||
- Added experimental WAL (Quake 2 textures) loader. To use this
|
||||
loader, import WalImageFile and call the "open" method in that
|
||||
|
@ -6174,8 +6311,8 @@ http://svn.effbot.org/public/pil/
|
|||
1.1.3 final
|
||||
-----------
|
||||
|
||||
- Made setup.py look for old versions of zlib. For some back-
|
||||
ground, see: https://zlib.net/advisory-2002-03-11.txt
|
||||
- Made setup.py look for old versions of zlib. For some background,
|
||||
see: https://zlib.net/advisory-2002-03-11.txt
|
||||
|
||||
1.1.3c2
|
||||
-------
|
||||
|
@ -6366,8 +6503,8 @@ http://svn.effbot.org/public/pil/
|
|||
supports all major PIL image modes (including F and I).
|
||||
|
||||
- The ImageFile module now includes a Parser class, which can
|
||||
be used to incrementally decode an image file (while down-
|
||||
loading it from the net, for example). See the handbook for
|
||||
be used to incrementally decode an image file (while downloading
|
||||
it from the net, for example). See the handbook for
|
||||
details.
|
||||
|
||||
- "show" now converts non-standard modes to "L" or "RGB" (as
|
||||
|
@ -6505,8 +6642,8 @@ http://svn.effbot.org/public/pil/
|
|||
|
||||
- The Image "transform" method now supports Image.QUAD transforms.
|
||||
The data argument is an 8-tuple giving the upper left, lower
|
||||
left, lower right, and upper right corner of the source quadri-
|
||||
lateral. Also added Image.MESH transform which takes a list
|
||||
left, lower right, and upper right corner of the source quadrilateral.
|
||||
Also added Image.MESH transform which takes a list
|
||||
of quadrilaterals.
|
||||
|
||||
- The Image "resize", "rotate", and "transform" methods now support
|
||||
|
@ -6731,8 +6868,8 @@ The test suite includes 400 individual tests.
|
|||
neither "short", "int" nor "long" are 32-bit wide.
|
||||
|
||||
- Added file= and data= keyword arguments to PhotoImage and BitmapImage.
|
||||
This allows you to use them as drop-in replacements for the corre-
|
||||
sponding Tkinter classes.
|
||||
This allows you to use them as drop-in replacements for the corresponding
|
||||
Tkinter classes.
|
||||
|
||||
- Removed bogus references to the crack coder (ImagingCrack).
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ graft src
|
|||
graft depends
|
||||
graft winbuild
|
||||
graft docs
|
||||
graft _custom_build
|
||||
|
||||
# build/src control detritus
|
||||
exclude .appveyor.yml
|
||||
|
@ -28,3 +29,4 @@ global-exclude .git*
|
|||
global-exclude *.pyc
|
||||
global-exclude *.so
|
||||
prune .ci
|
||||
prune wheels
|
||||
|
|
9
Makefile
|
@ -46,7 +46,6 @@ help:
|
|||
@echo " docserve run an HTTP server on the docs directory"
|
||||
@echo " html make HTML docs"
|
||||
@echo " htmlview open the index page built by the html target in your browser"
|
||||
@echo " inplace make inplace extension"
|
||||
@echo " install make and install"
|
||||
@echo " install-coverage make and install with C coverage"
|
||||
@echo " lint run the lint checks"
|
||||
|
@ -54,10 +53,6 @@ help:
|
|||
@echo " release-test run code and package tests before release"
|
||||
@echo " test run tests on installed Pillow"
|
||||
|
||||
.PHONY: inplace
|
||||
inplace: clean
|
||||
python3 -m pip install -e --global-option="build_ext" --global-option="--inplace" .
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
python3 -m pip -v install .
|
||||
|
@ -65,7 +60,7 @@ install:
|
|||
|
||||
.PHONY: install-coverage
|
||||
install-coverage:
|
||||
CFLAGS="-coverage -Werror=implicit-function-declaration" python3 -m pip -v install --global-option="build_ext" .
|
||||
CFLAGS="-coverage -Werror=implicit-function-declaration" python3 -m pip -v install .
|
||||
python3 selftest.py
|
||||
|
||||
.PHONY: debug
|
||||
|
@ -74,7 +69,7 @@ debug:
|
|||
# for our stuff, kills optimization, and redirects to dev null so we
|
||||
# see any build failures.
|
||||
make clean > /dev/null
|
||||
CFLAGS='-g -O0' python3 -m pip -v install --global-option="build_ext" . > /dev/null
|
||||
CFLAGS='-g -O0' python3 -m pip -v install . > /dev/null
|
||||
|
||||
.PHONY: release-test
|
||||
release-test:
|
||||
|
|
14
README.md
|
@ -45,12 +45,12 @@ As of 2019, Pillow development is
|
|||
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
|
||||
alt="AppVeyor CI build status (Windows)"
|
||||
src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
|
||||
<a href="https://github.com/python-pillow/pillow-wheels/actions"><img
|
||||
alt="GitHub Actions wheels build status (Wheels)"
|
||||
src="https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg"></a>
|
||||
<a href="https://app.travis-ci.com/github/python-pillow/pillow-wheels"><img
|
||||
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
|
||||
alt="GitHub Actions build status (Wheels)"
|
||||
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
|
||||
<a href="https://app.travis-ci.com/github/python-pillow/Pillow"><img
|
||||
alt="Travis CI wheels build status (aarch64)"
|
||||
src="https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels"></a>
|
||||
src="https://img.shields.io/travis/com/python-pillow/Pillow/main.svg?label=aarch64%20wheels"></a>
|
||||
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
|
||||
alt="Code coverage"
|
||||
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
||||
|
@ -74,9 +74,9 @@ As of 2019, Pillow development is
|
|||
<a href="https://pypi.org/project/Pillow/"><img
|
||||
alt="Number of PyPI downloads"
|
||||
src="https://img.shields.io/pypi/dm/pillow.svg"></a>
|
||||
<a href="https://bestpractices.coreinfrastructure.org/projects/6331"><img
|
||||
<a href="https://www.bestpractices.dev/projects/6331"><img
|
||||
alt="OpenSSF Best Practices"
|
||||
src="https://bestpractices.coreinfrastructure.org/projects/6331/badge"></a>
|
||||
src="https://www.bestpractices.dev/projects/6331/badge"></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
17
RELEASING.md
|
@ -10,7 +10,7 @@ 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 `main` branch.
|
||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch.
|
||||
* [ ] Check that all of the wheel builds [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels) pass the tests in Travis CI and GitHub Actions.
|
||||
* [ ] Check that all of the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) and [Travis CI](https://app.travis-ci.com/github/python-pillow/pillow) jobs by manually triggering them.
|
||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||
* [ ] Update `CHANGES.rst`.
|
||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||
|
@ -99,17 +99,14 @@ Released as needed privately to individual vendors for critical security-related
|
|||
## Binary Distributions
|
||||
|
||||
### macOS and Linux
|
||||
* [ ] Use the [Pillow Wheel Builder](https://github.com/python-pillow/pillow-wheels):
|
||||
* [ ] Download wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
|
||||
```bash
|
||||
git clone https://github.com/python-pillow/pillow-wheels
|
||||
cd pillow-wheels
|
||||
./update-pillow-tag.sh [[release tag]]
|
||||
```
|
||||
* [ ] Download wheels from the [Pillow Wheel Builder release](https://github.com/python-pillow/pillow-wheels/releases)
|
||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli) from the main repo:
|
||||
```bash
|
||||
gh release download --dir dist --pattern "*.whl" --repo python-pillow/pillow-wheels
|
||||
gh run download --dir dist
|
||||
# select dist-x.y.z
|
||||
```
|
||||
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
|
||||
and copy into `dist`.
|
||||
|
||||
### Windows
|
||||
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
|
||||
|
|
|
@ -27,17 +27,11 @@ def timer(func, label, *args):
|
|||
for x in range(iterations):
|
||||
func(*args)
|
||||
if time.time() - starttime > 10:
|
||||
print(
|
||||
"{}: breaking at {} iterations, {:.6f} per iteration".format(
|
||||
label, x + 1, (time.time() - starttime) / (x + 1.0)
|
||||
)
|
||||
)
|
||||
break
|
||||
if x == iterations - 1:
|
||||
endtime = time.time()
|
||||
print(
|
||||
"{}: {:.4f} s {:.6f} per iteration".format(
|
||||
label, endtime - starttime, (endtime - starttime) / (x + 1.0)
|
||||
"{}: completed {} iterations in {:.4f}s, {:.6f}s per iteration".format(
|
||||
label, x + 1, endtime - starttime, (endtime - starttime) / (x + 1.0)
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -45,7 +39,7 @@ def timer(func, label, *args):
|
|||
def test_direct():
|
||||
im = hopper()
|
||||
im.load()
|
||||
# im = Image.new( "RGB", (2000, 2000), (1, 3, 2))
|
||||
# im = Image.new("RGB", (2000, 2000), (1, 3, 2))
|
||||
caccess = im.im.pixel_access(False)
|
||||
access = PyAccess.new(im, False)
|
||||
|
||||
|
|
0
Tests/check_j2k_leaks.py
Executable file → Normal file
BIN
Tests/fonts/oom-4da0210eb7081b0bf15bf16cc4c52ce02c1e1bbc.ttf
Normal file
|
@ -91,7 +91,7 @@ def assert_image_equal(a, b, msg=None):
|
|||
if HAS_UPLOADER:
|
||||
try:
|
||||
url = test_image_results.upload(a, b)
|
||||
logger.error(f"Url for test images: {url}")
|
||||
logger.error("URL for test images: %s", url)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
@ -126,7 +126,7 @@ def assert_image_similar(a, b, epsilon, msg=None):
|
|||
if HAS_UPLOADER:
|
||||
try:
|
||||
url = test_image_results.upload(a, b)
|
||||
logger.error(f"Url for test images: {url}")
|
||||
logger.exception("URL for test images: %s", url)
|
||||
except Exception:
|
||||
pass
|
||||
raise e
|
||||
|
|
|
@ -22,4 +22,3 @@ and that the name of ICC shall not be used in advertising or publicity
|
|||
pertaining to distribution of the software without specific, written
|
||||
prior permission. ICC makes no representations about the suitability
|
||||
of this software for any purpose.
|
||||
|
||||
|
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 95 KiB |
BIN
Tests/images/bc5u.dds
Normal file
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 180 B |
0
Tests/images/negative_size.ppm
Executable file → Normal file
BIN
Tests/images/palette.dds
Normal file
BIN
Tests/images/xmp_no_prefix.jpg
Normal file
After Width: | Height: | Size: 788 B |
BIN
Tests/images/xmp_padded.jpg
Normal file
After Width: | Height: | Size: 778 B |
BIN
Tests/images/zero_bb_eof_before_boundingbox.eps
Normal file
BIN
Tests/images/zero_bb_trailer.eps
Normal file
BIN
Tests/images/zero_width.gif
Normal file
After Width: | Height: | Size: 44 B |
|
@ -20,7 +20,7 @@ python3 setup.py build --build-base=/tmp/build install
|
|||
# Build fuzzers in $OUT.
|
||||
for fuzzer in $(find $SRC -name 'fuzz_*.py'); do
|
||||
compile_python_fuzzer $fuzzer \
|
||||
--add-binary /usr/local/lib/libjpeg.so.62.3.0:. \
|
||||
--add-binary /usr/local/lib/libjpeg.so.62.4.0:. \
|
||||
--add-binary /usr/local/lib/libfreetype.so.6:. \
|
||||
--add-binary /usr/local/lib/liblcms2.so.2:. \
|
||||
--add-binary /usr/local/lib/libopenjp2.so.7:. \
|
||||
|
|
|
@ -6,6 +6,7 @@ import packaging
|
|||
import pytest
|
||||
|
||||
from PIL import Image, features
|
||||
from Tests.helper import skip_unless_feature
|
||||
|
||||
if sys.platform.startswith("win32"):
|
||||
pytest.skip("Fuzzer is linux only", allow_module_level=True)
|
||||
|
@ -48,6 +49,7 @@ def test_fuzz_images(path):
|
|||
fuzzers.disable_decompressionbomb_error()
|
||||
|
||||
|
||||
@skip_unless_feature("freetype2")
|
||||
@pytest.mark.parametrize(
|
||||
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
|
||||
)
|
||||
|
|
|
@ -22,7 +22,7 @@ def test_imageops_box_blur():
|
|||
|
||||
|
||||
def box_blur(image, radius=1, n=1):
|
||||
return image._new(image.im.box_blur(radius, n))
|
||||
return image._new(image.im.box_blur((radius, radius), n))
|
||||
|
||||
|
||||
def assert_image(im, data, delta=0):
|
||||
|
|
|
@ -64,6 +64,15 @@ class TestDecompressionBomb:
|
|||
with pytest.raises(Image.DecompressionBombError):
|
||||
im.seek(1)
|
||||
|
||||
def test_exception_gif_zero_width(self):
|
||||
# Set limit to trigger exception on the test file
|
||||
Image.MAX_IMAGE_PIXELS = 4 * 64 * 128
|
||||
assert Image.MAX_IMAGE_PIXELS == 4 * 64 * 128
|
||||
|
||||
with pytest.raises(Image.DecompressionBombError):
|
||||
with Image.open("Tests/images/zero_width.gif"):
|
||||
pass
|
||||
|
||||
def test_exception_bmp(self):
|
||||
with pytest.raises(Image.DecompressionBombError):
|
||||
with Image.open("Tests/images/bmp/b/reallybig.bmp"):
|
||||
|
|
|
@ -374,6 +374,20 @@ def test_apng_save(tmp_path):
|
|||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||
|
||||
|
||||
def test_apng_save_alpha(tmp_path):
|
||||
test_file = str(tmp_path / "temp.png")
|
||||
|
||||
im = Image.new("RGBA", (1, 1), (255, 0, 0, 255))
|
||||
im2 = Image.new("RGBA", (1, 1), (255, 0, 0, 127))
|
||||
im.save(test_file, save_all=True, append_images=[im2])
|
||||
|
||||
with Image.open(test_file) as reloaded:
|
||||
assert reloaded.getpixel((0, 0)) == (255, 0, 0, 255)
|
||||
|
||||
reloaded.seek(1)
|
||||
assert reloaded.getpixel((0, 0)) == (255, 0, 0, 127)
|
||||
|
||||
|
||||
def test_apng_save_split_fdat(tmp_path):
|
||||
# test to make sure we do not generate sequence errors when writing
|
||||
# frames with image data spanning multiple fdAT chunks (in this case
|
||||
|
|
|
@ -16,6 +16,7 @@ TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds"
|
|||
TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
|
||||
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
|
||||
TEST_FILE_BC5S = "Tests/images/bc5s.dds"
|
||||
TEST_FILE_BC5U = "Tests/images/bc5u.dds"
|
||||
TEST_FILE_BC6H = "Tests/images/bc6h.dds"
|
||||
TEST_FILE_BC6HS = "Tests/images/bc6h_sf.dds"
|
||||
TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
|
||||
|
@ -81,10 +82,18 @@ def test_sanity_ati1():
|
|||
assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
|
||||
|
||||
|
||||
def test_sanity_ati2():
|
||||
"""Check ATI2 images can be opened"""
|
||||
@pytest.mark.parametrize(
|
||||
"image_path",
|
||||
(
|
||||
TEST_FILE_ATI2,
|
||||
# hexeditted to use BC5U FourCC
|
||||
TEST_FILE_BC5U,
|
||||
),
|
||||
)
|
||||
def test_sanity_ati2_bc5u(image_path):
|
||||
"""Check ATI2 and BC5U images can be opened"""
|
||||
|
||||
with Image.open(TEST_FILE_ATI2) as im:
|
||||
with Image.open(image_path) as im:
|
||||
im.load()
|
||||
|
||||
assert im.format == "DDS"
|
||||
|
@ -289,6 +298,11 @@ def test_dxt5_colorblock_alpha_issue_4142():
|
|||
assert px[2] != 0
|
||||
|
||||
|
||||
def test_palette():
|
||||
with Image.open("Tests/images/palette.dds") as im:
|
||||
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_file",
|
||||
(
|
||||
|
|
|
@ -8,6 +8,7 @@ from .helper import (
|
|||
assert_image_similar,
|
||||
assert_image_similar_tofile,
|
||||
hopper,
|
||||
is_win32,
|
||||
mark_if_feature_version,
|
||||
skip_unless_feature,
|
||||
)
|
||||
|
@ -98,6 +99,20 @@ def test_load():
|
|||
assert im.load()[0, 0] == (255, 255, 255)
|
||||
|
||||
|
||||
def test_binary():
|
||||
if HAS_GHOSTSCRIPT:
|
||||
assert EpsImagePlugin.gs_binary is not None
|
||||
else:
|
||||
assert EpsImagePlugin.gs_binary is False
|
||||
|
||||
if not is_win32():
|
||||
assert EpsImagePlugin.gs_windows_binary is None
|
||||
elif not HAS_GHOSTSCRIPT:
|
||||
assert EpsImagePlugin.gs_windows_binary is False
|
||||
else:
|
||||
assert EpsImagePlugin.gs_windows_binary is not None
|
||||
|
||||
|
||||
def test_invalid_file():
|
||||
invalid_file = "Tests/images/flower.jpg"
|
||||
with pytest.raises(SyntaxError):
|
||||
|
@ -404,3 +419,18 @@ def test_timeout(test_file):
|
|||
with pytest.raises(Image.UnidentifiedImageError):
|
||||
with Image.open(f):
|
||||
pass
|
||||
|
||||
|
||||
def test_bounding_box_in_trailer():
|
||||
# Check bounding boxes are parsed in the same way
|
||||
# when specified in the header and the trailer
|
||||
with Image.open("Tests/images/zero_bb_trailer.eps") as trailer_image, Image.open(
|
||||
FILE1
|
||||
) as header_image:
|
||||
assert trailer_image.size == header_image.size
|
||||
|
||||
|
||||
def test_eof_before_bounding_box():
|
||||
with pytest.raises(OSError):
|
||||
with Image.open("Tests/images/zero_bb_eof_before_boundingbox.eps"):
|
||||
pass
|
||||
|
|
|
@ -205,14 +205,14 @@ def test_optimize_full_l():
|
|||
|
||||
|
||||
def test_optimize_if_palette_can_be_reduced_by_half():
|
||||
with Image.open("Tests/images/test.colors.gif") as im:
|
||||
# Reduce dimensions because original is too big for _get_optimize()
|
||||
im = im.resize((591, 443))
|
||||
im_rgb = im.convert("RGB")
|
||||
im = Image.new("P", (8, 1))
|
||||
im.palette = ImagePalette.raw("RGB", bytes((0, 0, 0) * 150))
|
||||
for i in range(8):
|
||||
im.putpixel((i, 0), (i + 1, 0, 0))
|
||||
|
||||
for optimize, colors in ((False, 256), (True, 8)):
|
||||
out = BytesIO()
|
||||
im_rgb.save(out, "GIF", optimize=optimize)
|
||||
im.save(out, "GIF", optimize=optimize)
|
||||
with Image.open(out) as reloaded:
|
||||
assert len(reloaded.palette.palette) // 3 == colors
|
||||
|
||||
|
@ -875,6 +875,14 @@ def test_identical_frames_to_single_frame(duration, tmp_path):
|
|||
assert reread.info["duration"] == 8500
|
||||
|
||||
|
||||
def test_loop_none(tmp_path):
|
||||
out = str(tmp_path / "temp.gif")
|
||||
im = Image.new("L", (100, 100), "#000")
|
||||
im.save(out, loop=None)
|
||||
with Image.open(out) as reread:
|
||||
assert "loop" not in reread.info
|
||||
|
||||
|
||||
def test_number_of_loops(tmp_path):
|
||||
number_of_loops = 2
|
||||
|
||||
|
@ -1086,6 +1094,21 @@ def test_transparent_optimize(tmp_path):
|
|||
assert reloaded.info["transparency"] == reloaded.getpixel((252, 0))
|
||||
|
||||
|
||||
def test_removed_transparency(tmp_path):
|
||||
out = str(tmp_path / "temp.gif")
|
||||
im = Image.new("RGB", (256, 1))
|
||||
|
||||
for x in range(256):
|
||||
im.putpixel((x, 0), (x, 0, 0))
|
||||
|
||||
im.info["transparency"] = (255, 255, 255)
|
||||
with pytest.warns(UserWarning):
|
||||
im.save(out)
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert "transparency" not in reloaded.info
|
||||
|
||||
|
||||
def test_rgb_transparency(tmp_path):
|
||||
out = str(tmp_path / "temp.gif")
|
||||
|
||||
|
@ -1130,6 +1153,18 @@ def test_bbox(tmp_path):
|
|||
assert reread.n_frames == 2
|
||||
|
||||
|
||||
def test_bbox_alpha(tmp_path):
|
||||
out = str(tmp_path / "temp.gif")
|
||||
|
||||
im = Image.new("RGBA", (1, 2), (255, 0, 0, 255))
|
||||
im.putpixel((0, 1), (255, 0, 0, 0))
|
||||
im2 = Image.new("RGBA", (1, 2), (255, 0, 0, 0))
|
||||
im.save(out, save_all=True, append_images=[im2])
|
||||
|
||||
with Image.open(out) as reread:
|
||||
assert reread.n_frames == 2
|
||||
|
||||
|
||||
def test_palette_save_L(tmp_path):
|
||||
# Generate an L mode image with a separate palette
|
||||
|
||||
|
@ -1145,18 +1180,17 @@ def test_palette_save_L(tmp_path):
|
|||
|
||||
|
||||
def test_palette_save_P(tmp_path):
|
||||
# Pass in a different palette, then construct what the image would look like.
|
||||
# Forcing a non-straight grayscale palette.
|
||||
|
||||
im = hopper("P")
|
||||
palette = bytes(255 - i // 3 for i in range(768))
|
||||
im = Image.new("P", (1, 2))
|
||||
im.putpixel((0, 1), 1)
|
||||
|
||||
out = str(tmp_path / "temp.gif")
|
||||
im.save(out, palette=palette)
|
||||
im.save(out, palette=bytes((1, 2, 3, 4, 5, 6)))
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
im.putpalette(palette)
|
||||
assert_image_equal(reloaded, im)
|
||||
reloaded_rgb = reloaded.convert("RGB")
|
||||
|
||||
assert reloaded_rgb.getpixel((0, 0)) == (1, 2, 3)
|
||||
assert reloaded_rgb.getpixel((0, 1)) == (4, 5, 6)
|
||||
|
||||
|
||||
def test_palette_save_duplicate_entries(tmp_path):
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import sys
|
||||
from io import StringIO
|
||||
from io import BytesIO, StringIO
|
||||
|
||||
from PIL import Image, IptcImagePlugin
|
||||
|
||||
|
@ -30,6 +30,36 @@ def test_getiptcinfo_jpg_found():
|
|||
assert iptc[(2, 101)] == b"Hungary"
|
||||
|
||||
|
||||
def test_getiptcinfo_fotostation():
|
||||
# Arrange
|
||||
with open(TEST_FILE, "rb") as fp:
|
||||
data = bytearray(fp.read())
|
||||
data[86] = 240
|
||||
f = BytesIO(data)
|
||||
with Image.open(f) as im:
|
||||
# Act
|
||||
iptc = IptcImagePlugin.getiptcinfo(im)
|
||||
|
||||
# Assert
|
||||
for tag in iptc.keys():
|
||||
if tag[0] == 240:
|
||||
return
|
||||
assert False, "FotoStation tag not found"
|
||||
|
||||
|
||||
def test_getiptcinfo_zero_padding():
|
||||
# Arrange
|
||||
with Image.open(TEST_FILE) as im:
|
||||
im.info["photoshop"][0x0404] += b"\x00\x00\x00"
|
||||
|
||||
# Act
|
||||
iptc = IptcImagePlugin.getiptcinfo(im)
|
||||
|
||||
# Assert
|
||||
assert isinstance(iptc, dict)
|
||||
assert len(iptc) == 3
|
||||
|
||||
|
||||
def test_getiptcinfo_tiff_none():
|
||||
# Arrange
|
||||
with Image.open("Tests/images/hopper.tif") as im:
|
||||
|
|
|
@ -214,13 +214,20 @@ class TestFileJpeg:
|
|||
# Should not raise OSError for image with icc larger than image size.
|
||||
im.save(
|
||||
f,
|
||||
format="JPEG",
|
||||
progressive=True,
|
||||
quality=95,
|
||||
icc_profile=icc_profile,
|
||||
optimize=True,
|
||||
)
|
||||
|
||||
with Image.open("Tests/images/flower2.jpg") as im:
|
||||
f = str(tmp_path / "temp2.jpg")
|
||||
im.save(f, progressive=True, quality=94, icc_profile=b" " * 53955)
|
||||
|
||||
with Image.open("Tests/images/flower2.jpg") as im:
|
||||
f = str(tmp_path / "temp3.jpg")
|
||||
im.save(f, progressive=True, quality=94, exif=b" " * 43668)
|
||||
|
||||
def test_optimize(self):
|
||||
im1 = self.roundtrip(hopper())
|
||||
im2 = self.roundtrip(hopper(), optimize=0)
|
||||
|
@ -875,7 +882,10 @@ class TestFileJpeg:
|
|||
def test_getxmp(self):
|
||||
with Image.open("Tests/images/xmp_test.jpg") as im:
|
||||
if ElementTree is None:
|
||||
with pytest.warns(UserWarning):
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="XMP data cannot be read without defusedxml dependency",
|
||||
):
|
||||
assert im.getxmp() == {}
|
||||
else:
|
||||
xmp = im.getxmp()
|
||||
|
@ -898,6 +908,28 @@ class TestFileJpeg:
|
|||
with Image.open("Tests/images/hopper.jpg") as im:
|
||||
assert im.getxmp() == {}
|
||||
|
||||
def test_getxmp_no_prefix(self):
|
||||
with Image.open("Tests/images/xmp_no_prefix.jpg") as im:
|
||||
if ElementTree is None:
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="XMP data cannot be read without defusedxml dependency",
|
||||
):
|
||||
assert im.getxmp() == {}
|
||||
else:
|
||||
assert im.getxmp() == {"xmpmeta": {"key": "value"}}
|
||||
|
||||
def test_getxmp_padded(self):
|
||||
with Image.open("Tests/images/xmp_padded.jpg") as im:
|
||||
if ElementTree is None:
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="XMP data cannot be read without defusedxml dependency",
|
||||
):
|
||||
assert im.getxmp() == {}
|
||||
else:
|
||||
assert im.getxmp() == {"xmpmeta": None}
|
||||
|
||||
@pytest.mark.timeout(timeout=1)
|
||||
def test_eof(self):
|
||||
# Even though this decoder never says that it is finished
|
||||
|
@ -929,11 +961,10 @@ class TestFileJpeg:
|
|||
assert repr_jpeg.format == "JPEG"
|
||||
assert_image_similar(im, repr_jpeg, 17)
|
||||
|
||||
def test_repr_jpeg_error(self):
|
||||
def test_repr_jpeg_error_returns_none(self):
|
||||
im = hopper("F")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
im._repr_jpeg_()
|
||||
assert im._repr_jpeg_() is None
|
||||
|
||||
|
||||
@pytest.mark.skipif(not is_win32(), reason="Windows only")
|
||||
|
|
|
@ -274,17 +274,15 @@ def test_sgnd(tmp_path):
|
|||
assert reloaded_signed.getpixel((0, 0)) == 128
|
||||
|
||||
|
||||
def test_rgba():
|
||||
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
|
||||
def test_rgba(ext):
|
||||
# Arrange
|
||||
with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
|
||||
with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2:
|
||||
with Image.open("Tests/images/rgb_trns_ycbc" + ext) as im:
|
||||
# Act
|
||||
j2k.load()
|
||||
jp2.load()
|
||||
im.load()
|
||||
|
||||
# Assert
|
||||
assert j2k.mode == "RGBA"
|
||||
assert jp2.mode == "RGBA"
|
||||
assert im.mode == "RGBA"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
|
||||
|
|
|
@ -8,7 +8,7 @@ from collections import namedtuple
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features
|
||||
from PIL import Image, ImageFilter, ImageOps, TiffImagePlugin, TiffTags, features
|
||||
from PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
|
||||
|
||||
from .helper import (
|
||||
|
@ -1035,7 +1035,18 @@ class TestFileLibTiff(LibTiffTestCase):
|
|||
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
|
||||
for i in range(2, 9):
|
||||
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
|
||||
assert 274 in im.tag_v2
|
||||
|
||||
im.load()
|
||||
assert 274 not in im.tag_v2
|
||||
|
||||
assert_image_similar(base_im, im, 0.7)
|
||||
|
||||
def test_exif_transpose(self):
|
||||
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
|
||||
for i in range(2, 9):
|
||||
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
|
||||
im = ImageOps.exif_transpose(im)
|
||||
|
||||
assert_image_similar(base_im, im, 0.7)
|
||||
|
||||
|
|
|
@ -43,8 +43,25 @@ def test_save(tmp_path, mode):
|
|||
|
||||
|
||||
@skip_unless_feature("jpg_2000")
|
||||
def test_save_rgba(tmp_path):
|
||||
helper_save_as_pdf(tmp_path, "RGBA")
|
||||
@pytest.mark.parametrize("mode", ("LA", "RGBA"))
|
||||
def test_save_alpha(tmp_path, mode):
|
||||
helper_save_as_pdf(tmp_path, mode)
|
||||
|
||||
|
||||
def test_p_alpha(tmp_path):
|
||||
# Arrange
|
||||
outfile = str(tmp_path / "temp.pdf")
|
||||
with Image.open("Tests/images/pil123p.png") as im:
|
||||
assert im.mode == "P"
|
||||
assert isinstance(im.info["transparency"], bytes)
|
||||
|
||||
# Act
|
||||
im.save(outfile)
|
||||
|
||||
# Assert
|
||||
with open(outfile, "rb") as fp:
|
||||
contents = fp.read()
|
||||
assert b"\n/SMask " in contents
|
||||
|
||||
|
||||
def test_monochrome(tmp_path):
|
||||
|
@ -57,8 +74,8 @@ def test_monochrome(tmp_path):
|
|||
|
||||
|
||||
def test_unsupported_mode(tmp_path):
|
||||
im = hopper("LA")
|
||||
outfile = str(tmp_path / "temp_LA.pdf")
|
||||
im = hopper("PA")
|
||||
outfile = str(tmp_path / "temp_PA.pdf")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
im.save(outfile)
|
||||
|
|
|
@ -79,7 +79,7 @@ class TestFilePng:
|
|||
|
||||
def test_sanity(self, tmp_path):
|
||||
# internal version number
|
||||
assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib"))
|
||||
assert re.search(r"\d+(\.\d+){1,3}$", features.version_codec("zlib"))
|
||||
|
||||
test_file = str(tmp_path / "temp.png")
|
||||
|
||||
|
@ -92,11 +92,11 @@ class TestFilePng:
|
|||
assert im.format == "PNG"
|
||||
assert im.get_format_mimetype() == "image/png"
|
||||
|
||||
for mode in ["1", "L", "P", "RGB", "I", "I;16"]:
|
||||
for mode in ["1", "L", "P", "RGB", "I", "I;16", "I;16B"]:
|
||||
im = hopper(mode)
|
||||
im.save(test_file)
|
||||
with Image.open(test_file) as reloaded:
|
||||
if mode == "I;16":
|
||||
if mode in ("I;16", "I;16B"):
|
||||
reloaded = reloaded.convert(mode)
|
||||
assert_image_equal(reloaded, im)
|
||||
|
||||
|
@ -532,11 +532,10 @@ class TestFilePng:
|
|||
assert repr_png.format == "PNG"
|
||||
assert_image_equal(im, repr_png)
|
||||
|
||||
def test_repr_png_error(self):
|
||||
def test_repr_png_error_returns_none(self):
|
||||
im = hopper("F")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
im._repr_png_()
|
||||
assert im._repr_png_() is None
|
||||
|
||||
def test_chunk_order(self, tmp_path):
|
||||
with Image.open("Tests/images/icc_profile.png") as im:
|
||||
|
@ -666,7 +665,10 @@ class TestFilePng:
|
|||
def test_getxmp(self):
|
||||
with Image.open("Tests/images/color_snakes.png") as im:
|
||||
if ElementTree is None:
|
||||
with pytest.warns(UserWarning):
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="XMP data cannot be read without defusedxml dependency",
|
||||
):
|
||||
assert im.getxmp() == {}
|
||||
else:
|
||||
xmp = im.getxmp()
|
||||
|
|
|
@ -2,7 +2,7 @@ import pytest
|
|||
|
||||
from PIL import Image, QoiImagePlugin
|
||||
|
||||
from .helper import assert_image_equal_tofile, assert_image_similar_tofile
|
||||
from .helper import assert_image_equal_tofile
|
||||
|
||||
|
||||
def test_sanity():
|
||||
|
@ -18,7 +18,7 @@ def test_sanity():
|
|||
assert im.size == (162, 150)
|
||||
assert im.format == "QOI"
|
||||
|
||||
assert_image_similar_tofile(im, "Tests/images/pil123rgba.png", 0.03)
|
||||
assert_image_equal_tofile(im, "Tests/images/pil123rgba.png")
|
||||
|
||||
|
||||
def test_invalid_file():
|
||||
|
|
|
@ -734,7 +734,10 @@ class TestFileTiff:
|
|||
def test_getxmp(self):
|
||||
with Image.open("Tests/images/lab.tif") as im:
|
||||
if ElementTree is None:
|
||||
with pytest.warns(UserWarning):
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="XMP data cannot be read without defusedxml dependency",
|
||||
):
|
||||
assert im.getxmp() == {}
|
||||
else:
|
||||
xmp = im.getxmp()
|
||||
|
|
|
@ -235,3 +235,13 @@ class TestFileWebp:
|
|||
with Image.open(out_webp) as reloaded:
|
||||
reloaded.load()
|
||||
assert reloaded.info["duration"] == 1000
|
||||
|
||||
def test_roundtrip_rgba_palette(self, tmp_path):
|
||||
temp_file = str(tmp_path / "temp.webp")
|
||||
im = Image.new("RGBA", (1, 1)).convert("P")
|
||||
assert im.mode == "P"
|
||||
assert im.palette.mode == "RGBA"
|
||||
im.save(temp_file)
|
||||
|
||||
with Image.open(temp_file) as im:
|
||||
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
|
||||
|
|
|
@ -118,7 +118,10 @@ def test_getxmp():
|
|||
|
||||
with Image.open("Tests/images/flower2.webp") as im:
|
||||
if ElementTree is None:
|
||||
with pytest.warns(UserWarning):
|
||||
with pytest.warns(
|
||||
UserWarning,
|
||||
match="XMP data cannot be read without defusedxml dependency",
|
||||
):
|
||||
assert im.getxmp() == {}
|
||||
else:
|
||||
assert (
|
||||
|
|
|
@ -135,6 +135,12 @@ class TestImage:
|
|||
with pytest.raises(AttributeError):
|
||||
im.size = (3, 4)
|
||||
|
||||
def test_set_mode(self):
|
||||
im = Image.new("RGB", (1, 1))
|
||||
|
||||
with pytest.raises(AttributeError):
|
||||
im.mode = "P"
|
||||
|
||||
def test_invalid_image(self):
|
||||
im = io.BytesIO(b"")
|
||||
with pytest.raises(UnidentifiedImageError):
|
||||
|
@ -632,8 +638,8 @@ class TestImage:
|
|||
im.remap_palette(None)
|
||||
|
||||
def test_remap_palette_transparency(self):
|
||||
im = Image.new("P", (1, 2))
|
||||
im.putpixel((0, 1), 1)
|
||||
im = Image.new("P", (1, 2), (0, 0, 0))
|
||||
im.putpixel((0, 1), (255, 0, 0))
|
||||
im.info["transparency"] = 0
|
||||
|
||||
im_remapped = im.remap_palette([1, 0])
|
||||
|
@ -655,15 +661,15 @@ class TestImage:
|
|||
blank_p.palette = None
|
||||
blank_pa.palette = None
|
||||
|
||||
def _make_new(base_image, im, palette_result=None):
|
||||
new_im = base_image._new(im)
|
||||
assert new_im.mode == im.mode
|
||||
assert new_im.size == im.size
|
||||
assert new_im.info == base_image.info
|
||||
def _make_new(base_image, image, palette_result=None):
|
||||
new_image = base_image._new(image.im)
|
||||
assert new_image.mode == image.mode
|
||||
assert new_image.size == image.size
|
||||
assert new_image.info == base_image.info
|
||||
if palette_result is not None:
|
||||
assert new_im.palette.tobytes() == palette_result.tobytes()
|
||||
assert new_image.palette.tobytes() == palette_result.tobytes()
|
||||
else:
|
||||
assert new_im.palette is None
|
||||
assert new_image.palette is None
|
||||
|
||||
_make_new(im, im_p, ImagePalette.ImagePalette(list(range(256)) * 3))
|
||||
_make_new(im_p, im, None)
|
||||
|
@ -900,6 +906,31 @@ class TestImage:
|
|||
im = Image.new("RGB", size)
|
||||
assert im.tobytes() == b""
|
||||
|
||||
def test_has_transparency_data(self):
|
||||
for mode in ("1", "L", "P", "RGB"):
|
||||
im = Image.new(mode, (1, 1))
|
||||
assert not im.has_transparency_data
|
||||
|
||||
for mode in ("LA", "La", "PA", "RGBA", "RGBa"):
|
||||
im = Image.new(mode, (1, 1))
|
||||
assert im.has_transparency_data
|
||||
|
||||
# P mode with "transparency" info
|
||||
with Image.open("Tests/images/first_frame_transparency.gif") as im:
|
||||
assert "transparency" in im.info
|
||||
assert im.has_transparency_data
|
||||
|
||||
# RGB mode with "transparency" info
|
||||
with Image.open("Tests/images/rgb_trns.png") as im:
|
||||
assert "transparency" in im.info
|
||||
assert im.has_transparency_data
|
||||
|
||||
# P mode with RGBA palette
|
||||
im = Image.new("RGBA", (1, 1)).convert("P")
|
||||
assert im.mode == "P"
|
||||
assert im.palette.mode == "RGBA"
|
||||
assert im.has_transparency_data
|
||||
|
||||
def test_apply_transparency(self):
|
||||
im = Image.new("P", (1, 1))
|
||||
im.putpalette((0, 0, 0, 1, 1, 1))
|
||||
|
|
|
@ -130,9 +130,16 @@ class TestImageGetPixel(AccessTest):
|
|||
bands = Image.getmodebands(mode)
|
||||
if bands == 1:
|
||||
return 1
|
||||
if mode in ("BGR;15", "BGR;16"):
|
||||
# These modes have less than 8 bits per band
|
||||
# So (1, 2, 3) cannot be roundtripped
|
||||
return (16, 32, 49)
|
||||
return tuple(range(1, bands + 1))
|
||||
|
||||
def check(self, mode, expected_color=None):
|
||||
if self._need_cffi_access and mode.startswith("BGR;"):
|
||||
pytest.skip("Support not added to deprecated module for BGR;* modes")
|
||||
|
||||
if not expected_color:
|
||||
expected_color = self.color(mode)
|
||||
|
||||
|
@ -203,6 +210,9 @@ class TestImageGetPixel(AccessTest):
|
|||
"F",
|
||||
"P",
|
||||
"PA",
|
||||
"BGR;15",
|
||||
"BGR;16",
|
||||
"BGR;24",
|
||||
"RGB",
|
||||
"RGBA",
|
||||
"RGBX",
|
||||
|
@ -213,6 +223,10 @@ class TestImageGetPixel(AccessTest):
|
|||
def test_basic(self, mode):
|
||||
self.check(mode)
|
||||
|
||||
def test_list(self):
|
||||
im = hopper()
|
||||
assert im.getpixel([0, 0]) == (20, 20, 70)
|
||||
|
||||
@pytest.mark.parametrize("mode", ("I;16", "I;16B"))
|
||||
@pytest.mark.parametrize(
|
||||
"expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)
|
||||
|
@ -232,11 +246,13 @@ class TestImageGetPixel(AccessTest):
|
|||
assert im.convert("RGBA").getpixel((0, 0)) == (255, 0, 0, alpha)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||
class TestCffiPutPixel(TestImagePutPixel):
|
||||
_need_cffi_access = True
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
@pytest.mark.skipif(cffi is None, reason="No CFFI")
|
||||
class TestCffiGetPixel(TestImageGetPixel):
|
||||
_need_cffi_access = True
|
||||
|
@ -252,6 +268,7 @@ class TestCffi(AccessTest):
|
|||
Using private interfaces, forcing a capi access and
|
||||
a pyaccess for the same image"""
|
||||
caccess = im.im.pixel_access(False)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, False)
|
||||
|
||||
w, h = im.size
|
||||
|
@ -264,16 +281,12 @@ class TestCffi(AccessTest):
|
|||
access[(access.xsize + 1, access.ysize + 1)]
|
||||
|
||||
def test_get_vs_c(self):
|
||||
with pytest.warns(DeprecationWarning):
|
||||
rgb = hopper("RGB")
|
||||
rgb.load()
|
||||
self._test_get_access(rgb)
|
||||
self._test_get_access(hopper("RGBA"))
|
||||
self._test_get_access(hopper("L"))
|
||||
self._test_get_access(hopper("LA"))
|
||||
self._test_get_access(hopper("1"))
|
||||
self._test_get_access(hopper("P"))
|
||||
# self._test_get_access(hopper('PA')) # PA -- how do I make a PA image?
|
||||
self._test_get_access(hopper("F"))
|
||||
for mode in ("RGBA", "L", "LA", "1", "P", "F"):
|
||||
self._test_get_access(hopper(mode))
|
||||
|
||||
for mode in ("I;16", "I;16L", "I;16B", "I;16N", "I"):
|
||||
im = Image.new(mode, (10, 10), 40000)
|
||||
|
@ -292,6 +305,7 @@ class TestCffi(AccessTest):
|
|||
Using private interfaces, forcing a capi access and
|
||||
a pyaccess for the same image"""
|
||||
caccess = im.im.pixel_access(False)
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, False)
|
||||
|
||||
w, h = im.size
|
||||
|
@ -301,12 +315,14 @@ class TestCffi(AccessTest):
|
|||
assert color == caccess[(x, y)]
|
||||
|
||||
# Attempt to set the value on a read-only image
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, True)
|
||||
with pytest.raises(ValueError):
|
||||
access[(0, 0)] = color
|
||||
|
||||
def test_set_vs_c(self):
|
||||
rgb = hopper("RGB")
|
||||
with pytest.warns(DeprecationWarning):
|
||||
rgb.load()
|
||||
self._test_set_access(rgb, (255, 128, 0))
|
||||
self._test_set_access(hopper("RGBA"), (255, 192, 128, 0))
|
||||
|
@ -326,6 +342,7 @@ class TestCffi(AccessTest):
|
|||
# im = Image.new('I;32B', (10, 10), 2**10)
|
||||
# self._test_set_access(im, 2**13-1)
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
|
||||
def test_not_implemented(self):
|
||||
assert PyAccess.new(hopper("BGR;15")) is None
|
||||
|
||||
|
@ -335,6 +352,7 @@ class TestCffi(AccessTest):
|
|||
|
||||
for _ in range(10):
|
||||
# Do not save references to the image, only to the access object
|
||||
with pytest.warns(DeprecationWarning):
|
||||
px = Image.new("L", (size, 1), 0).load()
|
||||
for i in range(size):
|
||||
# pixels can contain garbage if image is released
|
||||
|
@ -344,6 +362,7 @@ class TestCffi(AccessTest):
|
|||
def test_p_putpixel_rgb_rgba(self, mode):
|
||||
for color in ((255, 0, 0), (255, 0, 0, 127 if mode == "PA" else 255)):
|
||||
im = Image.new(mode, (1, 1))
|
||||
with pytest.warns(DeprecationWarning):
|
||||
access = PyAccess.new(im, False)
|
||||
access.putpixel((0, 0), color)
|
||||
|
||||
|
|
|
@ -117,11 +117,11 @@ def test_trns_p(tmp_path):
|
|||
f = str(tmp_path / "temp.png")
|
||||
|
||||
im_l = im.convert("L")
|
||||
assert im_l.info["transparency"] == 1 # undone
|
||||
assert im_l.info["transparency"] == 0
|
||||
im_l.save(f)
|
||||
|
||||
im_rgb = im.convert("RGB")
|
||||
assert im_rgb.info["transparency"] == (0, 1, 2) # undone
|
||||
assert im_rgb.info["transparency"] == (0, 0, 0)
|
||||
im_rgb.save(f)
|
||||
|
||||
|
||||
|
|
|
@ -23,9 +23,12 @@ from .helper import assert_image_equal, hopper
|
|||
ImageFilter.MinFilter,
|
||||
ImageFilter.ModeFilter,
|
||||
ImageFilter.GaussianBlur,
|
||||
ImageFilter.GaussianBlur(0),
|
||||
ImageFilter.GaussianBlur(5),
|
||||
ImageFilter.GaussianBlur((2, 5)),
|
||||
ImageFilter.BoxBlur(0),
|
||||
ImageFilter.BoxBlur(5),
|
||||
ImageFilter.BoxBlur((2, 5)),
|
||||
ImageFilter.UnsharpMask,
|
||||
ImageFilter.UnsharpMask(10),
|
||||
),
|
||||
|
@ -185,12 +188,21 @@ def test_consistency_5x5(mode):
|
|||
assert_image_equal(source.filter(kernel), reference)
|
||||
|
||||
|
||||
def test_invalid_box_blur_filter():
|
||||
@pytest.mark.parametrize(
|
||||
"radius",
|
||||
(
|
||||
-2,
|
||||
(-2, -2),
|
||||
(-2, 2),
|
||||
(2, -2),
|
||||
),
|
||||
)
|
||||
def test_invalid_box_blur_filter(radius):
|
||||
with pytest.raises(ValueError):
|
||||
ImageFilter.BoxBlur(-2)
|
||||
ImageFilter.BoxBlur(radius)
|
||||
|
||||
im = hopper()
|
||||
box_blur_filter = ImageFilter.BoxBlur(2)
|
||||
box_blur_filter.radius = -2
|
||||
box_blur_filter.radius = radius
|
||||
with pytest.raises(ValueError):
|
||||
im.filter(box_blur_filter)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
|
||||
from .helper import hopper
|
||||
|
@ -38,3 +40,16 @@ def test_bbox():
|
|||
for color in ((0, 0), (127, 0), (255, 0)):
|
||||
im = Image.new(mode, (100, 100), color)
|
||||
check(im, (255, 255))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("RGBA", "RGBa", "La", "LA", "PA"))
|
||||
def test_bbox_alpha_only_false(mode):
|
||||
im = Image.new(mode, (100, 100))
|
||||
assert im.getbbox(alpha_only=False) is None
|
||||
|
||||
fill_color = [1] * Image.getmodebands(mode)
|
||||
fill_color[-1] = 0
|
||||
im.paste(tuple(fill_color), (25, 25, 75, 75))
|
||||
assert im.getbbox(alpha_only=False) == (25, 25, 75, 75)
|
||||
|
||||
assert im.getbbox() is None
|
||||
|
|
|
@ -76,6 +76,15 @@ def test_mode_F():
|
|||
assert list(im.getdata()) == target
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24"))
|
||||
def test_mode_BGR(mode):
|
||||
data = [(16, 32, 49), (32, 32, 98)]
|
||||
im = Image.new(mode, (1, 2))
|
||||
im.putdata(data)
|
||||
|
||||
assert list(im.getdata()) == data
|
||||
|
||||
|
||||
def test_array_B():
|
||||
# shouldn't segfault
|
||||
# see https://github.com/python-pillow/Pillow/issues/1008
|
||||
|
|
|
@ -84,3 +84,14 @@ def test_rgba_palette(mode, palette):
|
|||
im.putpalette(palette, mode)
|
||||
assert im.getpalette() == [1, 2, 3]
|
||||
assert im.palette.colors == {(1, 2, 3, 4): 0}
|
||||
|
||||
|
||||
def test_empty_palette():
|
||||
im = Image.new("P", (1, 1))
|
||||
assert im.getpalette() == []
|
||||
|
||||
|
||||
def test_undefined_palette_index():
|
||||
im = Image.new("P", (1, 1), 3)
|
||||
im.putpalette((1, 2, 3))
|
||||
assert im.convert("RGB").getpixel((0, 0)) == (0, 0, 0)
|
||||
|
|
|
@ -193,6 +193,10 @@ def test_rounding_errors():
|
|||
Image.new("LA", (1, 1), "white")
|
||||
|
||||
|
||||
def test_color_hsv():
|
||||
assert (170, 255, 255) == ImageColor.getcolor("hsv(240, 100%, 100%)", "HSV")
|
||||
|
||||
|
||||
def test_color_too_long():
|
||||
# Arrange
|
||||
color_too_long = "hsl(" + "1" * 40 + "," + "1" * 40 + "%," + "1" * 40 + "%)"
|
||||
|
|
|
@ -586,6 +586,18 @@ def test_point(points):
|
|||
assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png")
|
||||
|
||||
|
||||
def test_point_I16():
|
||||
# Arrange
|
||||
im = Image.new("I;16", (1, 1))
|
||||
draw = ImageDraw.Draw(im)
|
||||
|
||||
# Act
|
||||
draw.point((0, 0), fill=0x1234)
|
||||
|
||||
# Assert
|
||||
assert im.getpixel((0, 0)) == 0x1234
|
||||
|
||||
|
||||
@pytest.mark.parametrize("points", POINTS)
|
||||
def test_polygon(points):
|
||||
# Arrange
|
||||
|
@ -732,7 +744,7 @@ def test_rectangle_I16(bbox):
|
|||
draw = ImageDraw.Draw(im)
|
||||
|
||||
# Act
|
||||
draw.rectangle(bbox, fill="black", outline="green")
|
||||
draw.rectangle(bbox, outline=0xFFFF)
|
||||
|
||||
# Assert
|
||||
assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
|
||||
|
@ -1326,6 +1338,7 @@ def test_stroke_multiline():
|
|||
assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
|
||||
|
||||
|
||||
@skip_unless_feature("freetype2")
|
||||
def test_setting_default_font():
|
||||
# Arrange
|
||||
im = Image.new("RGB", (100, 250))
|
||||
|
|
|
@ -136,7 +136,7 @@ class TestImageFile:
|
|||
|
||||
class DummyImageFile(ImageFile.ImageFile):
|
||||
def _open(self):
|
||||
self.mode = "RGB"
|
||||
self._mode = "RGB"
|
||||
self._size = (1, 1)
|
||||
|
||||
im = DummyImageFile(buf)
|
||||
|
@ -217,7 +217,7 @@ xoff, yoff, xsize, ysize = 10, 20, 100, 100
|
|||
class MockImageFile(ImageFile.ImageFile):
|
||||
def _open(self):
|
||||
self.rawmode = "RGBA"
|
||||
self.mode = "RGBA"
|
||||
self._mode = "RGBA"
|
||||
self._size = (200, 200)
|
||||
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
|
||||
|
||||
|
|
|
@ -141,7 +141,9 @@ def test_I16(font):
|
|||
draw = ImageDraw.Draw(im)
|
||||
|
||||
txt = "Hello World!"
|
||||
draw.text((10, 10), txt, font=font)
|
||||
draw.text((10, 10), txt, fill=0xFFFE, font=font)
|
||||
|
||||
assert im.getpixel((12, 14)) == 0xFFFE
|
||||
|
||||
target = "Tests/images/transparent_background_text_L.png"
|
||||
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
||||
|
@ -1038,10 +1040,30 @@ def test_render_mono_size():
|
|||
assert_image_equal_tofile(im, "Tests/images/text_mono.gif")
|
||||
|
||||
|
||||
def test_too_many_characters(font):
|
||||
with pytest.raises(ValueError):
|
||||
font.getlength("A" * 1_000_001)
|
||||
with pytest.raises(ValueError):
|
||||
font.getbbox("A" * 1_000_001)
|
||||
with pytest.raises(ValueError):
|
||||
font.getmask2("A" * 1_000_001)
|
||||
|
||||
transposed_font = ImageFont.TransposedFont(font)
|
||||
with pytest.raises(ValueError):
|
||||
transposed_font.getlength("A" * 1_000_001)
|
||||
|
||||
default_font = ImageFont.load_default()
|
||||
with pytest.raises(ValueError):
|
||||
default_font.getlength("A" * 1_000_001)
|
||||
with pytest.raises(ValueError):
|
||||
default_font.getbbox("A" * 1_000_001)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_file",
|
||||
[
|
||||
"Tests/fonts/oom-e8e927ba6c0d38274a37c1567560eb33baf74627.ttf",
|
||||
"Tests/fonts/oom-4da0210eb7081b0bf15bf16cc4c52ce02c1e1bbc.ttf",
|
||||
],
|
||||
)
|
||||
def test_oom(test_file):
|
||||
|
|
|
@ -340,6 +340,17 @@ class TestLibUnpack:
|
|||
self.assert_unpack("RGB", "G;16N", 2, (0, 1, 0), (0, 3, 0), (0, 5, 0))
|
||||
self.assert_unpack("RGB", "B;16N", 2, (0, 0, 1), (0, 0, 3), (0, 0, 5))
|
||||
|
||||
self.assert_unpack(
|
||||
"RGB", "CMYK", 4, (250, 249, 248), (242, 241, 240), (234, 233, 233)
|
||||
)
|
||||
|
||||
def test_BGR(self):
|
||||
self.assert_unpack("BGR;15", "BGR;15", 3, (8, 131, 0), (24, 0, 8), (41, 131, 8))
|
||||
self.assert_unpack(
|
||||
"BGR;16", "BGR;16", 3, (8, 64, 0), (24, 129, 0), (41, 194, 0)
|
||||
)
|
||||
self.assert_unpack("BGR;24", "BGR;24", 3, (1, 2, 3), (4, 5, 6), (7, 8, 9))
|
||||
|
||||
def test_RGBA(self):
|
||||
self.assert_unpack("RGBA", "LA", 2, (1, 1, 1, 2), (3, 3, 3, 4), (5, 5, 5, 6))
|
||||
self.assert_unpack(
|
||||
|
|
|
@ -75,13 +75,13 @@ def test_pickle_la_mode_with_palette(tmp_path):
|
|||
|
||||
# Act / Assert
|
||||
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
|
||||
im.mode = "LA"
|
||||
im._mode = "LA"
|
||||
with open(filename, "wb") as f:
|
||||
pickle.dump(im, f, protocol)
|
||||
with open(filename, "rb") as f:
|
||||
loaded_im = pickle.load(f)
|
||||
|
||||
im.mode = "PA"
|
||||
im._mode = "PA"
|
||||
assert im == loaded_im
|
||||
|
||||
|
||||
|
@ -112,6 +112,7 @@ def helper_assert_pickled_font_images(font1, font2):
|
|||
assert_image_equal(im1, im2)
|
||||
|
||||
|
||||
@skip_unless_feature("freetype2")
|
||||
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
|
||||
def test_pickle_font_string(protocol):
|
||||
# Arrange
|
||||
|
@ -125,6 +126,7 @@ def test_pickle_font_string(protocol):
|
|||
helper_assert_pickled_font_images(font, unpickled_font)
|
||||
|
||||
|
||||
@skip_unless_feature("freetype2")
|
||||
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
|
||||
def test_pickle_font_file(tmp_path, protocol):
|
||||
# Arrange
|
||||
|
|
56
_custom_build/backend.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
import sys
|
||||
|
||||
from setuptools.build_meta import * # noqa: F401, F403
|
||||
from setuptools.build_meta import build_wheel
|
||||
|
||||
backend_class = build_wheel.__self__.__class__
|
||||
|
||||
|
||||
class _CustomBuildMetaBackend(backend_class):
|
||||
def run_setup(self, setup_script="setup.py"):
|
||||
if self.config_settings:
|
||||
|
||||
def config_has(key, value):
|
||||
settings = self.config_settings.get(key)
|
||||
if settings:
|
||||
if not isinstance(settings, list):
|
||||
settings = [settings]
|
||||
return value in settings
|
||||
|
||||
flags = []
|
||||
for dependency in (
|
||||
"zlib",
|
||||
"jpeg",
|
||||
"tiff",
|
||||
"freetype",
|
||||
"raqm",
|
||||
"lcms",
|
||||
"webp",
|
||||
"webpmux",
|
||||
"jpeg2000",
|
||||
"imagequant",
|
||||
"xcb",
|
||||
):
|
||||
if config_has(dependency, "enable"):
|
||||
flags.append("--enable-" + dependency)
|
||||
elif config_has(dependency, "disable"):
|
||||
flags.append("--disable-" + dependency)
|
||||
for dependency in ("raqm", "fribidi"):
|
||||
if config_has(dependency, "vendor"):
|
||||
flags.append("--vendor-" + dependency)
|
||||
if self.config_settings.get("platform-guessing") == "disable":
|
||||
flags.append("--disable-platform-guessing")
|
||||
if self.config_settings.get("debug") == "true":
|
||||
flags.append("--debug")
|
||||
if flags:
|
||||
sys.argv = sys.argv[:1] + ["build_ext"] + flags + sys.argv[1:]
|
||||
return super().run_setup(setup_script)
|
||||
|
||||
def build_wheel(
|
||||
self, wheel_directory, config_settings=None, metadata_directory=None
|
||||
):
|
||||
self.config_settings = config_settings
|
||||
return super().build_wheel(wheel_directory, config_settings, metadata_directory)
|
||||
|
||||
|
||||
build_wheel = _CustomBuildMetaBackend().build_wheel
|
|
@ -12,7 +12,7 @@ coverage:
|
|||
status:
|
||||
project:
|
||||
default:
|
||||
threshold: 0.01%
|
||||
threshold: 0.1%
|
||||
|
||||
# Matches 'omit:' in .coveragerc
|
||||
ignore:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
# install libimagequant
|
||||
|
||||
archive=libimagequant-4.2.0
|
||||
archive=libimagequant-4.2.1
|
||||
|
||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||
|
||||
|
|
|
@ -11,4 +11,3 @@ pushd $archive
|
|||
meson build --prefix=/usr && sudo ninja -C build install
|
||||
|
||||
popd
|
||||
|
||||
|
|
|
@ -15,4 +15,3 @@ make && sudo make install
|
|||
cd ..
|
||||
|
||||
popd
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
# install webp
|
||||
|
||||
archive=libwebp-1.3.0
|
||||
archive=libwebp-1.3.2
|
||||
|
||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||
|
||||
|
|
|
@ -2,4 +2,3 @@
|
|||
|
||||
pkg install -y python ndk-sysroot clang make \
|
||||
libjpeg-turbo
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ The fork author's goal is to foster and support active development of PIL throug
|
|||
|
||||
.. _GitHub Actions: https://github.com/python-pillow/Pillow/actions
|
||||
.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow
|
||||
.. _Travis CI: https://app.travis-ci.com/github/python-pillow/pillow-wheels
|
||||
.. _Travis CI: https://app.travis-ci.com/github/python-pillow/Pillow
|
||||
.. _GitHub: https://github.com/python-pillow/Pillow
|
||||
.. _Python Package Index: https://pypi.org/project/Pillow/
|
||||
|
||||
|
|
|
@ -318,7 +318,7 @@ def setup(app):
|
|||
|
||||
|
||||
linkcheck_allowed_redirects = {
|
||||
r"https://bestpractices.coreinfrastructure.org/projects/6331": r"https://bestpractices.coreinfrastructure.org/en/.*", # noqa: E501
|
||||
r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*", # noqa: E501
|
||||
r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", # noqa: E501
|
||||
r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", # noqa: E501
|
||||
r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", # noqa: E501
|
||||
|
|
|
@ -22,6 +22,18 @@ be removed in Pillow 11 (2024-10-15). This class was only made as a helper to
|
|||
be used internally, so there is no replacement. If you need this functionality
|
||||
though, it is a very short class that can easily be recreated in your own code.
|
||||
|
||||
PyAccess and Image.USE_CFFI_ACCESS
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 10.0.0
|
||||
|
||||
Since Pillow's C API is now faster than PyAccess on PyPy,
|
||||
:py:mod:`~PIL.PyAccess` has been deprecated and will be removed in Pillow
|
||||
11.0.0 (2024-10-15). Pillow's C API will now be used by default on PyPy instead.
|
||||
|
||||
``Image.USE_CFFI_ACCESS``, for switching from the C API to PyAccess, is
|
||||
similarly deprecated.
|
||||
|
||||
Removed features
|
||||
----------------
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
|
||||
flags, height, width = struct.unpack("<3I", header.read(12))
|
||||
self._size = (width, height)
|
||||
self.mode = "RGBA"
|
||||
self._mode = "RGBA"
|
||||
|
||||
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
||||
struct.unpack("<11I", header.read(44)) # reserved
|
||||
|
|
|
@ -63,8 +63,35 @@ DDS
|
|||
^^^
|
||||
|
||||
DDS is a popular container texture format used in video games and natively supported
|
||||
by DirectX. Uncompressed RGB and RGBA can be read, and (since 8.3.0) written. DXT1,
|
||||
DXT3 (since 3.4.0) and DXT5 pixel formats can be read, only in ``RGBA`` mode.
|
||||
by DirectX.
|
||||
|
||||
DXT1 and DXT5 pixel formats can be read, only in ``RGBA`` mode.
|
||||
|
||||
.. versionadded:: 3.4.0
|
||||
DXT3 images can be read in ``RGB`` mode and DX10 images can be read in
|
||||
``RGB`` and ``RGBA`` mode.
|
||||
|
||||
.. versionadded:: 6.0.0
|
||||
Uncompressed ``RGBA`` images can be read.
|
||||
|
||||
|
||||
.. versionadded:: 8.3.0
|
||||
BC5S images can be opened in ``RGB`` mode, and uncompressed ``RGB`` images
|
||||
can be read. Uncompressed data can also be saved to image files.
|
||||
|
||||
|
||||
.. versionadded:: 9.3.0
|
||||
ATI1 images can be opened in ``L`` mode and ATI2 images can be opened in
|
||||
``RGB`` mode.
|
||||
|
||||
.. versionadded:: 9.4.0
|
||||
Uncompressed ``L`` ("luminance") and ``LA`` images can be opened and saved.
|
||||
|
||||
|
||||
.. versionadded:: 10.1.0
|
||||
BC5U can be read in ``RGB`` mode, and 8-bit color indexed images can be read
|
||||
in ``P`` mode.
|
||||
|
||||
|
||||
DIB
|
||||
^^^
|
||||
|
@ -88,8 +115,13 @@ in ``L``, ``RGB`` and ``CMYK`` modes.
|
|||
Loading
|
||||
~~~~~~~
|
||||
|
||||
To use Ghostscript, Pillow searches for the "gs" executable. On Windows, it
|
||||
also searches for "gswin32c" and "gswin64c". To customise this behaviour,
|
||||
``EpsImagePlugin.gs_binary = "gswin64"`` will set the name of the executable to
|
||||
use. ``EpsImagePlugin.gs_binary = False`` will prevent Ghostscript use.
|
||||
|
||||
If Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.load`
|
||||
method with the following parameters to affect how Ghostscript renders the EPS
|
||||
method with the following parameters to affect how Ghostscript renders the EPS.
|
||||
|
||||
**scale**
|
||||
Affects the scale of the resultant rasterized image. If the EPS suggests
|
||||
|
@ -253,7 +285,7 @@ their :py:attr:`~PIL.Image.Image.info` values.
|
|||
|
||||
**loop**
|
||||
Integer number of times the GIF should loop. 0 means that it will loop
|
||||
forever. By default, the image will not loop.
|
||||
forever. If omitted or ``None``, the image will not loop.
|
||||
|
||||
**comment**
|
||||
A comment about the image.
|
||||
|
@ -861,6 +893,10 @@ PPM
|
|||
Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I`` or
|
||||
``RGB`` data.
|
||||
|
||||
"Raw" (P4 to P6) formats can be read, and are used when writing.
|
||||
|
||||
Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well.
|
||||
|
||||
SGI
|
||||
^^^
|
||||
|
||||
|
@ -1404,7 +1440,7 @@ the open function in the :py:mod:`~PIL.WalImageFile` module to read files in
|
|||
this format.
|
||||
|
||||
By default, a Quake2 standard palette is attached to the texture. To override
|
||||
the palette, use the putpalette method.
|
||||
the palette, use the :py:func:`PIL.Image.Image.putpalette()` method.
|
||||
|
||||
WMF, EMF
|
||||
^^^^^^^^
|
||||
|
@ -1482,7 +1518,7 @@ files. Different encoding methods are used, depending on the image mode.
|
|||
unavailable
|
||||
* L, RGB and CMYK mode images use JPEG encoding
|
||||
* P mode images use HEX encoding
|
||||
* RGBA mode images use JPEG2000 encoding
|
||||
* LA and RGBA mode images use JPEG2000 encoding
|
||||
|
||||
.. _pdf-saving:
|
||||
|
||||
|
|
|
@ -72,11 +72,11 @@ true color.
|
|||
# mode setting
|
||||
bits = int(header[3])
|
||||
if bits == 1:
|
||||
self.mode = "1"
|
||||
self._mode = "1"
|
||||
elif bits == 8:
|
||||
self.mode = "L"
|
||||
self._mode = "L"
|
||||
elif bits == 24:
|
||||
self.mode = "RGB"
|
||||
self._mode = "RGB"
|
||||
else:
|
||||
msg = "unknown number of bits"
|
||||
raise SyntaxError(msg)
|
||||
|
|
|
@ -37,12 +37,12 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
|
|||
:target: https://ci.appveyor.com/project/python-pillow/Pillow
|
||||
:alt: AppVeyor CI build status (Windows)
|
||||
|
||||
.. image:: https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg
|
||||
:target: https://github.com/python-pillow/pillow-wheels/actions
|
||||
:alt: GitHub Actions wheels build status (Wheels)
|
||||
.. image:: https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg
|
||||
:target: https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml
|
||||
:alt: GitHub Actions build status (Wheels)
|
||||
|
||||
.. image:: https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels
|
||||
:target: https://app.travis-ci.com/github/python-pillow/pillow-wheels
|
||||
.. image:: https://img.shields.io/travis/com/python-pillow/Pillow/main.svg?label=aarch64%20wheels
|
||||
:target: https://app.travis-ci.com/github/python-pillow/Pillow
|
||||
:alt: Travis CI wheels build status (aarch64)
|
||||
|
||||
.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg
|
||||
|
@ -69,8 +69,8 @@ Pillow for enterprise is available via the Tidelift Subscription. `Learn more <h
|
|||
:target: https://pypi.org/project/Pillow/
|
||||
:alt: Number of PyPI downloads
|
||||
|
||||
.. image:: https://bestpractices.coreinfrastructure.org/projects/6331/badge
|
||||
:target: https://bestpractices.coreinfrastructure.org/projects/6331
|
||||
.. image:: https://www.bestpractices.dev/projects/6331/badge
|
||||
:target: https://www.bestpractices.dev/projects/6331
|
||||
:alt: OpenSSF Best Practices
|
||||
|
||||
.. image:: https://badges.gitter.im/python-pillow/Pillow.svg
|
||||
|
|
|
@ -82,11 +82,12 @@ Install Pillow with :command:`pip`::
|
|||
|
||||
.. tab:: Windows
|
||||
|
||||
.. warning:: Pillow > 9.5.0 no longer includes 32-bit wheels.
|
||||
|
||||
We provide Pillow binaries for Windows compiled for the matrix of
|
||||
supported Pythons in both 32 and 64-bit versions in the wheel format.
|
||||
These binaries include support for all optional libraries except
|
||||
libimagequant and libxcb. Raqm support requires
|
||||
FriBiDi to be installed separately::
|
||||
supported Pythons in 64-bit versions in the wheel format. These binaries include
|
||||
support for all optional libraries except libimagequant and libxcb. Raqm support
|
||||
requires FriBiDi to be installed separately::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
python3 -m pip install --upgrade Pillow
|
||||
|
@ -155,7 +156,7 @@ Many of Pillow's features require external libraries:
|
|||
|
||||
* **libtiff** provides compressed TIFF functionality
|
||||
|
||||
* Pillow has been tested with libtiff versions **3.x** and **4.0-4.5**
|
||||
* Pillow has been tested with libtiff versions **3.x** and **4.0-4.6.0**
|
||||
|
||||
* **libfreetype** provides type related services
|
||||
|
||||
|
@ -181,7 +182,7 @@ Many of Pillow's features require external libraries:
|
|||
|
||||
* **libimagequant** provides improved color quantization
|
||||
|
||||
* Pillow has been tested with libimagequant **2.6-4.2**
|
||||
* Pillow has been tested with libimagequant **2.6-4.2.1**
|
||||
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||
the Pillow license, therefore we will not be distributing binaries
|
||||
with libimagequant support enabled.
|
||||
|
@ -312,6 +313,11 @@ Many of Pillow's features require external libraries:
|
|||
mingw-w64-x86_64-libimagequant \
|
||||
mingw-w64-x86_64-libraqm
|
||||
|
||||
https://www.msys2.org/docs/python/ states that setuptools >= 60 does not work with
|
||||
MSYS2. To workaround this, before installing Pillow you must run::
|
||||
|
||||
export SETUPTOOLS_USE_DISTUTILS=stdlib
|
||||
|
||||
.. tab:: FreeBSD
|
||||
|
||||
.. Note:: Only FreeBSD 10 and 11 tested
|
||||
|
@ -380,40 +386,40 @@ Build Options
|
|||
using a setting of 1. By default, it uses 4 CPUs, or if 4 are not
|
||||
available, as many as are present.
|
||||
|
||||
* Build flags: ``--disable-zlib``, ``--disable-jpeg``,
|
||||
``--disable-tiff``, ``--disable-freetype``, ``--disable-raqm``,
|
||||
``--disable-lcms``, ``--disable-webp``, ``--disable-webpmux``,
|
||||
``--disable-jpeg2000``, ``--disable-imagequant``, ``--disable-xcb``.
|
||||
* Config settings: ``-C zlib=disable``, ``-C jpeg=disable``,
|
||||
``-C tiff=disable``, ``-C freetype=disable``, ``-C raqm=disable``,
|
||||
``-C lcms=disable``, ``-C webp=disable``, ``-C webpmux=disable``,
|
||||
``-C jpeg2000=disable``, ``-C imagequant=disable``, ``-C xcb=disable``.
|
||||
Disable building the corresponding feature even if the development
|
||||
libraries are present on the building machine.
|
||||
|
||||
* Build flags: ``--enable-zlib``, ``--enable-jpeg``,
|
||||
``--enable-tiff``, ``--enable-freetype``, ``--enable-raqm``,
|
||||
``--enable-lcms``, ``--enable-webp``, ``--enable-webpmux``,
|
||||
``--enable-jpeg2000``, ``--enable-imagequant``, ``--enable-xcb``.
|
||||
* Config settings: ``-C zlib=enable``, ``-C jpeg=enable``,
|
||||
``-C tiff=enable``, ``-C freetype=enable``, ``-C raqm=enable``,
|
||||
``-C lcms=enable``, ``-C webp=enable``, ``-C webpmux=enable``,
|
||||
``-C jpeg2000=enable``, ``-C imagequant=enable``, ``-C xcb=enable``.
|
||||
Require that the corresponding feature is built. The build will raise
|
||||
an exception if the libraries are not found. Webpmux (WebP metadata)
|
||||
relies on WebP support. Tcl and Tk also must be used together.
|
||||
|
||||
* Build flags: ``--vendor-raqm``, ``--vendor-fribidi``.
|
||||
* Config settings: ``-C raqm=vendor``, ``-C fribidi=vendor``.
|
||||
These flags are used to compile a modified version of libraqm and
|
||||
a shim that dynamically loads libfribidi at runtime. These are
|
||||
used to compile the standard Pillow wheels. Compiling libraqm requires
|
||||
a C99-compliant compiler.
|
||||
|
||||
* Build flag: ``--disable-platform-guessing``. Skips all of the
|
||||
* Build flag: ``-C platform-guessing=disable``. Skips all of the
|
||||
platform dependent guessing of include and library directories for
|
||||
automated build systems that configure the proper paths in the
|
||||
environment variables (e.g. Buildroot).
|
||||
|
||||
* Build flag: ``--debug``. Adds a debugging flag to the include and
|
||||
* Build flag: ``-C debug=true``. Adds a debugging flag to the include and
|
||||
library search process to dump all paths searched for and found to
|
||||
stdout.
|
||||
|
||||
|
||||
Sample usage::
|
||||
|
||||
python3 -m pip install --upgrade Pillow --global-option="build_ext" --global-option="--enable-[feature]"
|
||||
python3 -m pip install --upgrade Pillow -C [feature]=enable
|
||||
|
||||
Platform Support
|
||||
----------------
|
||||
|
@ -446,9 +452,9 @@ These platforms are built and tested for every change.
|
|||
+----------------------------------+----------------------------+---------------------+
|
||||
| CentOS Stream 9 | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Debian 11 Bullseye | 3.9 | x86 |
|
||||
| Debian 11 Bullseye | 3.9 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Debian 12 Bookworm | 3.11 | x86 |
|
||||
| Debian 12 Bookworm | 3.11 | x86, x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Fedora 37 | 3.11 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
|
@ -469,10 +475,12 @@ These platforms are built and tested for every change.
|
|||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2016 | 3.8 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86, x86-64 |
|
||||
| Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 |
|
||||
| | 3.12, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.9 (MinGW) | x86, x86-64 |
|
||||
| | 3.11 | x86 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.9 (MinGW) | x86-64 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.8, 3.9 (Cygwin) | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
|
@ -492,9 +500,13 @@ These platforms have been reported to work at the versions mentioned.
|
|||
| Operating system | | Tested Python | | Latest tested | | Tested |
|
||||
| | | versions | | Pillow version | | processors |
|
||||
+==================================+===========================+==================+==============+
|
||||
| macOS 13 Ventura | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.5.0 |arm |
|
||||
| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
|
||||
+----------------------------------+---------------------------+------------------+--------------+
|
||||
| macOS 12 Big Sur | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
|
||||
| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
|
||||
| +---------------------------+------------------+ |
|
||||
| | 3.7 | 9.5.0 | |
|
||||
+----------------------------------+---------------------------+------------------+--------------+
|
||||
| macOS 12 Monterey | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.3.0 |arm |
|
||||
+----------------------------------+---------------------------+------------------+--------------+
|
||||
| macOS 11 Big Sur | 3.7, 3.8, 3.9, 3.10 | 8.4.0 |arm |
|
||||
| +---------------------------+------------------+--------------+
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
Python,3.11,3.10,3.9,3.8,3.7,3.6,3.5
|
||||
Pillow >= 10,Yes,Yes,Yes,Yes,,,
|
||||
Pillow 9.3 - 9.5,Yes,Yes,Yes,Yes,Yes,,
|
||||
Pillow 9.0 - 9.2,,Yes,Yes,Yes,Yes,,
|
||||
Pillow 8.3.2 - 8.4,,Yes,Yes,Yes,Yes,Yes,
|
||||
Pillow 8.0 - 8.3.1,,,Yes,Yes,Yes,Yes,
|
||||
Pillow 7.0 - 7.2,,,,Yes,Yes,Yes,Yes
|
||||
Python,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5
|
||||
Pillow >= 10.1,Yes,Yes,Yes,Yes,Yes,,,
|
||||
Pillow 10.0,,Yes,Yes,Yes,Yes,,,
|
||||
Pillow 9.3 - 9.5,,Yes,Yes,Yes,Yes,Yes,,
|
||||
Pillow 9.0 - 9.2,,,Yes,Yes,Yes,Yes,,
|
||||
Pillow 8.3.2 - 8.4,,,Yes,Yes,Yes,Yes,Yes,
|
||||
Pillow 8.0 - 8.3.1,,,,Yes,Yes,Yes,Yes,
|
||||
Pillow 7.0 - 7.2,,,,,Yes,Yes,Yes,Yes
|
||||
|
|
|
|
@ -93,10 +93,14 @@ Generating images
|
|||
Registering plugins
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. autofunction:: preinit
|
||||
.. autofunction:: init
|
||||
|
||||
.. note::
|
||||
|
||||
These functions are for use by plugin authors. Application authors can
|
||||
ignore them.
|
||||
These functions are for use by plugin authors. They are called when a
|
||||
plugin is loaded as part of :py:meth:`~preinit()` or :py:meth:`~init()`.
|
||||
Application authors can ignore them.
|
||||
|
||||
.. autofunction:: register_open
|
||||
.. autofunction:: register_mime
|
||||
|
@ -347,6 +351,8 @@ Instances of the :py:class:`Image` class have the following attributes:
|
|||
|
||||
.. seealso:: :attr:`~Image.is_animated`, :func:`~Image.seek` and :func:`~Image.tell`
|
||||
|
||||
.. autoattribute:: PIL.Image.Image.has_transparency_data
|
||||
|
||||
Classes
|
||||
-------
|
||||
|
||||
|
|
|
@ -538,7 +538,7 @@ Methods
|
|||
It should be a `BCP 47 language code`_.
|
||||
Requires libraqm.
|
||||
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
||||
:return: Width for horizontal, height for vertical text.
|
||||
:return: Either width for horizontal text, or height for vertical text.
|
||||
|
||||
.. py:method:: ImageDraw.textbbox(xy, text, font=None, anchor=None, spacing=4, align="left", direction=None, features=None, language=None, stroke_width=0, embedded_color=False)
|
||||
|
||||
|
|
|
@ -18,6 +18,15 @@ 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.
|
||||
|
||||
.. warning::
|
||||
To protect against potential DOS attacks when using arbitrary strings as
|
||||
text input, Pillow will raise a ``ValueError`` if the number of characters
|
||||
is over a certain limit, :py:data:`MAX_STRING_LENGTH`.
|
||||
|
||||
This threshold can be changed by setting
|
||||
:py:data:`MAX_STRING_LENGTH`. It can be disabled by setting
|
||||
``ImageFont.MAX_STRING_LENGTH = None``.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
|
@ -73,3 +82,12 @@ Constants
|
|||
|
||||
Requires Raqm, you can check support using
|
||||
:py:func:`PIL.features.check_feature` with ``feature="raqm"``.
|
||||
|
||||
Constants
|
||||
---------
|
||||
|
||||
.. data:: MAX_STRING_LENGTH
|
||||
|
||||
Set to 1,000,000, to protect against potential DOS attacks. Pillow will
|
||||
raise a ``ValueError`` if the number of characters is over this limit. The
|
||||
check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``.
|
||||
|
|
|
@ -127,10 +127,15 @@ This undocumented method has been removed.
|
|||
Deprecations
|
||||
============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
PyAccess and Image.USE_CFFI_ACCESS
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
Since Pillow's C API is now faster than PyAccess on PyPy,
|
||||
:py:mod:`~PIL.PyAccess` has been deprecated and will be removed in Pillow
|
||||
11.0.0 (2024-10-15). Pillow's C API will now be used by default on PyPy instead.
|
||||
|
||||
``Image.USE_CFFI_ACCESS``, for switching from the C API to PyAccess, is
|
||||
similarly deprecated.
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
@ -144,22 +149,47 @@ An optional line ``width`` parameter has been added to
|
|||
API Additions
|
||||
=============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
Added ``alpha_only`` argument to ``getbbox()``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
:py:meth:`~PIL.Image.Image.getbbox` now accepts a keyword argument of
|
||||
``alpha_only``. This is an optional flag, defaulting to ``True``. If ``True``
|
||||
and the image has an alpha channel, trim transparent pixels. Otherwise, trim
|
||||
pixels when all channels are zero.
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
Limit size even if one dimension is zero
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
When performing decompression bomb checks, Pillow did not reject images with
|
||||
excessive width and zero height, or zero width and excessive height. That has
|
||||
now been fixed.
|
||||
|
||||
This effectively dates to the PIL fork, since problem images would still have
|
||||
been processed before Pillow started checking for decompression bombs.
|
||||
|
||||
Added ImageFont.MAX_STRING_LENGTH
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To protect against potential DOS attacks when using arbitrary strings as text
|
||||
input, Pillow will now raise a ``ValueError`` if the number of characters
|
||||
passed into ImageFont methods is over a certain limit,
|
||||
:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`.
|
||||
|
||||
This threshold can be changed by setting
|
||||
:py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. It can be disabled by setting
|
||||
``ImageFont.MAX_STRING_LENGTH = None``.
|
||||
|
||||
Other Changes
|
||||
=============
|
||||
|
||||
32-bit wheels
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
32-bit wheels are no longer provided.
|
||||
|
||||
Support display_jpeg() in IPython
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -176,4 +206,4 @@ Support reading signed 8-bit TIFF images
|
|||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TIFF images with signed integer data, 8 bits per sample and a photometric
|
||||
interpretaton of BlackIsZero can now be read.
|
||||
interpretation of BlackIsZero can now be read.
|
||||
|
|
14
docs/releasenotes/10.0.1.rst
Normal file
|
@ -0,0 +1,14 @@
|
|||
10.0.1
|
||||
------
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
This release addresses :cve:`2023-4863`, by providing an updated install script and
|
||||
updated wheels to include libwebp 1.3.2, preventing a potential heap buffer overflow
|
||||
in WebP.
|
||||
|
||||
Updated tests to pass with latest zlib version
|
||||
==============================================
|
||||
|
||||
The release of zlib 1.3 caused one of the tests in the Pillow test suite to fail.
|
76
docs/releasenotes/10.1.0.rst
Normal file
|
@ -0,0 +1,76 @@
|
|||
10.1.0
|
||||
------
|
||||
|
||||
Backwards Incompatible Changes
|
||||
==============================
|
||||
|
||||
Setting image mode
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you attempt to set the mode of an image directly, e.g.
|
||||
``im.mode = "RGBA"``, you will now receive an ``AttributeError``. This is
|
||||
not about removing existing functionality, but instead about raising an
|
||||
explicit error to prevent later consequences. The ``convert`` method is the
|
||||
correct way to change an image's mode.
|
||||
|
||||
API Changes
|
||||
===========
|
||||
|
||||
Accept a list in getpixel()
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:py:meth:`~PIL.Image.Image.getpixel` now accepts a list of coordinates, as well
|
||||
as a tuple. ::
|
||||
|
||||
from PIL import Image
|
||||
im = Image.new("RGB", (1, 1))
|
||||
im.getpixel((0, 0))
|
||||
im.getpixel([0, 0])
|
||||
|
||||
BoxBlur and GaussianBlur allow for different x and y radii
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:py:class:`~PIL.ImageFilter.BoxBlur` and
|
||||
:py:class:`~PIL.ImageFilter.GaussianBlur` now allow a sequence of x and y radii
|
||||
to be specified, rather than a single number for both dimensions. ::
|
||||
|
||||
from PIL import ImageFilter
|
||||
ImageFilter.BoxBlur((2, 5))
|
||||
ImageFilter.GaussianBlur((2, 5))
|
||||
|
||||
API Additions
|
||||
=============
|
||||
|
||||
EpsImagePlugin.gs_binary
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``EpsImagePlugin.gs_windows_binary`` stores the name of the Ghostscript
|
||||
executable on Windows. ``EpsImagePlugin.gs_binary`` has now been added for all
|
||||
platforms, and can be used to customise the name of the executable, or disable
|
||||
use entirely through ``EpsImagePlugin.gs_binary = False``.
|
||||
|
||||
has_transparency_data
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Images now have :py:attr:`~PIL.Image.Image.has_transparency_data` to indicate
|
||||
whether the image has transparency data, whether in the form of an alpha
|
||||
channel, a palette with an alpha channel, or a "transparency" key in the
|
||||
:py:attr:`~PIL.Image.Image.info` dictionary.
|
||||
|
||||
Even if this attribute is true, the image might still appear solid, if all of
|
||||
the values shown within are opaque.
|
||||
|
||||
Other Changes
|
||||
=============
|
||||
|
||||
Added support for DDS BC5U and 8-bit color indexed images
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Support has been added to read BC5U DDS files as RGB images, and
|
||||
PALETTEINDEXED8 DDS files as P mode images.
|
||||
|
||||
Support reading signed 8-bit YCbCr TIFF images
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TIFF images with unsigned integer data, 8 bits per sample and a photometric
|
||||
interpretation of YCbCr can now be read.
|
|
@ -49,4 +49,3 @@ The external dependencies on libjpeg and zlib are now required by default.
|
|||
If the headers or libraries are not found, then installation will abort
|
||||
with an error. This behaviour can be disabled with the ``--disable-libjpeg``
|
||||
and ``--disable-zlib`` flags.
|
||||
|
||||
|
|
|
@ -34,7 +34,3 @@ image size can lead to a smaller allocation than expected, leading to
|
|||
arbitrary writes.
|
||||
|
||||
This issue was found by Cris Neckar at Divergent Security.
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -20,5 +20,3 @@ CPython 3.6.1 to not work on installations of C-Python 3.6.0. This fix
|
|||
undefines PySlice_GetIndicesEx if it exists to restore compatibility
|
||||
with both 3.6.0 and 3.6.1. See https://bugs.python.org/issue29943 for
|
||||
more details.
|
||||
|
||||
|
||||
|
|
|
@ -8,4 +8,3 @@ Fixed Windows PyPy Build
|
|||
|
||||
A change in the 4.2.0 cycle broke the Windows PyPy build. This has
|
||||
been fixed, and PyPy is now part of the Windows CI matrix.
|
||||
|
||||
|
|
|
@ -175,6 +175,3 @@ Dark theme for docs
|
|||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The https://pillow.readthedocs.io documentation will use a dark theme if the user has requested the system use one. Uses the ``prefers-color-scheme`` CSS media query.
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ expected to be backported to earlier versions.
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
10.1.0
|
||||
10.0.1
|
||||
10.0.0
|
||||
9.5.0
|
||||
9.4.0
|
||||
|
|
8
pyproject.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[build-system]
|
||||
build-backend = "backend"
|
||||
requires = [
|
||||
"setuptools>=67.8",
|
||||
]
|
||||
backend-path = [
|
||||
"_custom_build",
|
||||
]
|