mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-12 02:06:18 +03:00
Merge branch 'main' into jpeg_xmp
This commit is contained in:
commit
f24222a954
|
@ -21,13 +21,11 @@ environment:
|
||||||
install:
|
install:
|
||||||
- '%PYTHON%\%EXECUTABLE% --version'
|
- '%PYTHON%\%EXECUTABLE% --version'
|
||||||
- '%PYTHON%\%EXECUTABLE% -m pip install --upgrade pip'
|
- '%PYTHON%\%EXECUTABLE% -m pip install --upgrade pip'
|
||||||
- curl -fsSL -o pillow-depends.zip https://github.com/python-pillow/pillow-depends/archive/main.zip
|
|
||||||
- curl -fsSL -o pillow-test-images.zip https://github.com/python-pillow/test-images/archive/main.zip
|
- 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:\
|
- 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
|
- 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
|
- choco install ghostscript --version=10.0.0.20230317
|
||||||
- path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH%
|
- path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH%
|
||||||
- cd c:\pillow\winbuild\
|
- cd c:\pillow\winbuild\
|
||||||
|
|
|
@ -23,7 +23,7 @@ if [[ $(uname) != CYGWIN* ]]; then
|
||||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
||||||
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
ghostscript libffi-dev libjpeg-turbo-progs libopenjp2-7-dev\
|
||||||
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
||||||
sway wl-clipboard
|
sway wl-clipboard libopenblas-dev
|
||||||
fi
|
fi
|
||||||
|
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
|
@ -38,11 +38,10 @@ python3 -m pip install -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
if [[ $(uname) != CYGWIN* ]]; then
|
||||||
# TODO Remove condition when NumPy supports 3.12
|
python3 -m pip install numpy
|
||||||
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
|
|
||||||
|
|
||||||
# PyQt6 doesn't support PyPy3
|
# 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
|
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
|
python3 -m pip install pyqt6
|
||||||
fi
|
fi
|
||||||
|
|
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
||||||
name: Docs
|
name: Docs
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
|
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
||||||
name: Lint
|
name: Lint
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: pre-commit cache
|
- name: pre-commit cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
|
|
4
.github/workflows/macos-install.sh
vendored
4
.github/workflows/macos-install.sh
vendored
|
@ -3,6 +3,7 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm
|
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
|
PYTHONOPTIMIZE=0 python3 -m pip install cffi
|
||||||
python3 -m pip install coverage
|
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 -U pytest-timeout
|
||||||
python3 -m pip install pyroma
|
python3 -m pip install pyroma
|
||||||
|
|
||||||
# TODO Remove condition when NumPy supports 3.12
|
python3 -m pip install numpy
|
||||||
if ! [ "$GHA_PYTHON_VERSION" == "3.12-dev" ]; then python3 -m pip install numpy ; fi
|
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
2
.github/workflows/release-drafter.yml
vendored
2
.github/workflows/release-drafter.yml
vendored
|
@ -10,7 +10,7 @@ on:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
|
@ -8,7 +8,7 @@ on:
|
||||||
permissions:
|
permissions:
|
||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
|
32
.github/workflows/test-cygwin.yml
vendored
32
.github/workflows/test-cygwin.yml
vendored
|
@ -4,11 +4,19 @@ on:
|
||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -36,7 +44,7 @@ jobs:
|
||||||
git config --global core.autocrlf input
|
git config --global core.autocrlf input
|
||||||
|
|
||||||
- name: Checkout Pillow
|
- name: Checkout Pillow
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Cygwin
|
- name: Install Cygwin
|
||||||
uses: cygwin/cygwin-install-action@v4
|
uses: cygwin/cygwin-install-action@v4
|
||||||
|
@ -76,17 +84,23 @@ jobs:
|
||||||
with:
|
with:
|
||||||
dirs: 'C:\cygwin\bin;C:\cygwin\lib\lapack'
|
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
|
- name: pip cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: 'C:\cygwin\home\runneradmin\.cache\pip'
|
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: |
|
restore-keys: |
|
||||||
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-
|
${{ runner.os }}-cygwin-pip3.${{ matrix.python-minor-version }}-numpy${{ steps.latest-numpy.outputs.version }}-
|
||||||
|
|
||||||
- name: Select Python version
|
|
||||||
run: |
|
|
||||||
ln -sf c:/cygwin/bin/python3.${{ matrix.python-minor-version }} c:/cygwin/bin/python3
|
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: |
|
run: |
|
||||||
|
@ -96,10 +110,10 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
bash.exe .ci/install.sh
|
bash.exe .ci/install.sh
|
||||||
|
|
||||||
- name: Install a different NumPy
|
- name: Upgrade NumPy
|
||||||
shell: dash.exe -l "{0}"
|
shell: dash.exe -l "{0}"
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install -U numpy
|
python3 -m pip install -U "numpy<1.26"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: bash.exe -eo pipefail -o igncr "{0}"
|
shell: bash.exe -eo pipefail -o igncr "{0}"
|
||||||
|
|
10
.github/workflows/test-docker.yml
vendored
10
.github/workflows/test-docker.yml
vendored
|
@ -4,11 +4,19 @@ on:
|
||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -59,7 +67,7 @@ jobs:
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
10
.github/workflows/test-mingw.yml
vendored
10
.github/workflows/test-mingw.yml
vendored
|
@ -4,11 +4,19 @@ on:
|
||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -34,7 +42,7 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Pillow
|
- name: Checkout Pillow
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up shell
|
- name: Set up shell
|
||||||
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH
|
||||||
|
|
2
.github/workflows/test-valgrind.yml
vendored
2
.github/workflows/test-valgrind.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
||||||
name: ${{ matrix.docker }}
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Build system information
|
- name: Build system information
|
||||||
run: python3 .github/workflows/system-info.py
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
16
.github/workflows/test-windows.yml
vendored
16
.github/workflows/test-windows.yml
vendored
|
@ -4,11 +4,19 @@ on:
|
||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -24,7 +32,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12-dev"]
|
python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
@ -32,16 +40,16 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Pillow
|
- name: Checkout Pillow
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Checkout cached dependencies
|
- name: Checkout cached dependencies
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: python-pillow/pillow-depends
|
repository: python-pillow/pillow-depends
|
||||||
path: winbuild\depends
|
path: winbuild\depends
|
||||||
|
|
||||||
- name: Checkout extra test images
|
- name: Checkout extra test images
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: python-pillow/test-images
|
repository: python-pillow/test-images
|
||||||
path: Tests\test-images
|
path: Tests\test-images
|
||||||
|
|
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
|
@ -4,11 +4,19 @@ on:
|
||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- ".github/workflows/docs.yml"
|
- ".github/workflows/docs.yml"
|
||||||
|
- ".github/workflows/wheels*"
|
||||||
|
- ".gitmodules"
|
||||||
|
- ".travis.yml"
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
|
- "wheels/**"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -31,7 +39,7 @@ jobs:
|
||||||
python-version: [
|
python-version: [
|
||||||
"pypy3.10",
|
"pypy3.10",
|
||||||
"pypy3.9",
|
"pypy3.9",
|
||||||
"3.12-dev",
|
"3.12",
|
||||||
"3.11",
|
"3.11",
|
||||||
"3.10",
|
"3.10",
|
||||||
"3.9",
|
"3.9",
|
||||||
|
@ -48,7 +56,7 @@ jobs:
|
||||||
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
name: ${{ matrix.os }} Python ${{ matrix.python-version }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
|
|
40
.github/workflows/wheels-build.sh
vendored
Executable file
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
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
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
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
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:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: 23.3.0
|
rev: v3.13.0
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
args: [--py38-plus]
|
||||||
|
|
||||||
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
|
rev: 23.9.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--target-version=py38]
|
args: [--target-version=py38]
|
||||||
|
@ -23,17 +29,17 @@ repos:
|
||||||
- id: yesqa
|
- id: yesqa
|
||||||
|
|
||||||
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
- repo: https://github.com/Lucas-C/pre-commit-hooks
|
||||||
rev: v1.5.1
|
rev: v1.5.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: remove-tabs
|
- id: remove-tabs
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||||
|
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 6.0.0
|
rev: 6.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies:
|
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
|
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||||
rev: v1.10.0
|
rev: v1.10.0
|
||||||
|
@ -44,23 +50,28 @@ repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.4.0
|
rev: v4.4.0
|
||||||
hooks:
|
hooks:
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- id: check-json
|
- id: check-json
|
||||||
- id: check-toml
|
- id: check-toml
|
||||||
- id: check-yaml
|
- 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
|
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||||
rev: v0.6.7
|
rev: v0.6.8
|
||||||
hooks:
|
hooks:
|
||||||
- id: sphinx-lint
|
- id: sphinx-lint
|
||||||
|
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
rev: 0.12.1
|
rev: 1.2.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
|
|
||||||
- repo: https://github.com/abravalheri/validate-pyproject
|
- repo: https://github.com/abravalheri/validate-pyproject
|
||||||
rev: v0.13
|
rev: v0.14
|
||||||
hooks:
|
hooks:
|
||||||
- id: validate-pyproject
|
- id: validate-pyproject
|
||||||
|
|
||||||
|
|
135
.travis.yml
Normal file
135
.travis.yml
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
if: tag IS present
|
||||||
|
|
||||||
|
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
|
112
CHANGES.rst
112
CHANGES.rst
|
@ -5,9 +5,90 @@ Changelog (Pillow)
|
||||||
10.1.0 (unreleased)
|
10.1.0 (unreleased)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
- 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
|
- Fix missing symbols when libtiff depends on libjpeg #7270
|
||||||
[heitbaum]
|
[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)
|
10.0.0 (2023-07-01)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
@ -5735,8 +5816,8 @@ http://svn.effbot.org/public/pil/
|
||||||
a polyline, independent of line angle.
|
a polyline, independent of line angle.
|
||||||
|
|
||||||
- Fixed bearing calculation and clipping in the ImageFont truetype
|
- Fixed bearing calculation and clipping in the ImageFont truetype
|
||||||
renderer; this could lead to clipped text, or crashes in the low-
|
renderer; this could lead to clipped text, or crashes in the low-level
|
||||||
level _imagingft module. (based on input from Adam Twardoch and
|
_imagingft module. (based on input from Adam Twardoch and
|
||||||
others).
|
others).
|
||||||
|
|
||||||
- Added ImageQt wrapper module, for converting PIL Image objects to
|
- Added ImageQt wrapper module, for converting PIL Image objects to
|
||||||
|
@ -5817,8 +5898,7 @@ http://svn.effbot.org/public/pil/
|
||||||
1.1.5c2 and 1.1.5 final
|
1.1.5c2 and 1.1.5 final
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
- Added experimental PERSPECTIVE transform method (from Jeff Breiden-
|
- Added experimental PERSPECTIVE transform method (from Jeff Breidenbach).
|
||||||
bach).
|
|
||||||
|
|
||||||
1.1.5c1
|
1.1.5c1
|
||||||
-------
|
-------
|
||||||
|
@ -5890,8 +5970,8 @@ http://svn.effbot.org/public/pil/
|
||||||
|
|
||||||
- Fixed BILINEAR/BICUBIC/ANTIALIAS filtering for mode "LA".
|
- Fixed BILINEAR/BICUBIC/ANTIALIAS filtering for mode "LA".
|
||||||
|
|
||||||
- Added "getcolors()" method. This is similar to the existing histo-
|
- Added "getcolors()" method. This is similar to the existing histogram
|
||||||
gram method, but looks at color values instead of individual layers,
|
method, but looks at color values instead of individual layers,
|
||||||
and returns an unsorted list of (count, color) tuples.
|
and returns an unsorted list of (count, color) tuples.
|
||||||
|
|
||||||
By default, the method returns None if finds more than 256 colors.
|
By default, the method returns None if finds more than 256 colors.
|
||||||
|
@ -6107,8 +6187,8 @@ http://svn.effbot.org/public/pil/
|
||||||
|
|
||||||
- Added limited support for "bitfield compression" in BMP files
|
- Added limited support for "bitfield compression" in BMP files
|
||||||
and DIB buffers, for 15-bit, 16-bit, and 32-bit images. This
|
and DIB buffers, for 15-bit, 16-bit, and 32-bit images. This
|
||||||
also fixes a problem with ImageGrab module when copying screen-
|
also fixes a problem with ImageGrab module when copying screendumps
|
||||||
dumps from the clipboard on 15/16/32-bit displays.
|
from the clipboard on 15/16/32-bit displays.
|
||||||
|
|
||||||
- Added experimental WAL (Quake 2 textures) loader. To use this
|
- Added experimental WAL (Quake 2 textures) loader. To use this
|
||||||
loader, import WalImageFile and call the "open" method in that
|
loader, import WalImageFile and call the "open" method in that
|
||||||
|
@ -6219,8 +6299,8 @@ http://svn.effbot.org/public/pil/
|
||||||
1.1.3 final
|
1.1.3 final
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
- Made setup.py look for old versions of zlib. For some back-
|
- Made setup.py look for old versions of zlib. For some background,
|
||||||
ground, see: https://zlib.net/advisory-2002-03-11.txt
|
see: https://zlib.net/advisory-2002-03-11.txt
|
||||||
|
|
||||||
1.1.3c2
|
1.1.3c2
|
||||||
-------
|
-------
|
||||||
|
@ -6411,8 +6491,8 @@ http://svn.effbot.org/public/pil/
|
||||||
supports all major PIL image modes (including F and I).
|
supports all major PIL image modes (including F and I).
|
||||||
|
|
||||||
- The ImageFile module now includes a Parser class, which can
|
- The ImageFile module now includes a Parser class, which can
|
||||||
be used to incrementally decode an image file (while down-
|
be used to incrementally decode an image file (while downloading
|
||||||
loading it from the net, for example). See the handbook for
|
it from the net, for example). See the handbook for
|
||||||
details.
|
details.
|
||||||
|
|
||||||
- "show" now converts non-standard modes to "L" or "RGB" (as
|
- "show" now converts non-standard modes to "L" or "RGB" (as
|
||||||
|
@ -6550,8 +6630,8 @@ http://svn.effbot.org/public/pil/
|
||||||
|
|
||||||
- The Image "transform" method now supports Image.QUAD transforms.
|
- The Image "transform" method now supports Image.QUAD transforms.
|
||||||
The data argument is an 8-tuple giving the upper left, lower
|
The data argument is an 8-tuple giving the upper left, lower
|
||||||
left, lower right, and upper right corner of the source quadri-
|
left, lower right, and upper right corner of the source quadrilateral.
|
||||||
lateral. Also added Image.MESH transform which takes a list
|
Also added Image.MESH transform which takes a list
|
||||||
of quadrilaterals.
|
of quadrilaterals.
|
||||||
|
|
||||||
- The Image "resize", "rotate", and "transform" methods now support
|
- The Image "resize", "rotate", and "transform" methods now support
|
||||||
|
@ -6776,8 +6856,8 @@ The test suite includes 400 individual tests.
|
||||||
neither "short", "int" nor "long" are 32-bit wide.
|
neither "short", "int" nor "long" are 32-bit wide.
|
||||||
|
|
||||||
- Added file= and data= keyword arguments to PhotoImage and BitmapImage.
|
- Added file= and data= keyword arguments to PhotoImage and BitmapImage.
|
||||||
This allows you to use them as drop-in replacements for the corre-
|
This allows you to use them as drop-in replacements for the corresponding
|
||||||
sponding Tkinter classes.
|
Tkinter classes.
|
||||||
|
|
||||||
- Removed bogus references to the crack coder (ImagingCrack).
|
- Removed bogus references to the crack coder (ImagingCrack).
|
||||||
|
|
||||||
|
|
|
@ -29,3 +29,4 @@ global-exclude .git*
|
||||||
global-exclude *.pyc
|
global-exclude *.pyc
|
||||||
global-exclude *.so
|
global-exclude *.so
|
||||||
prune .ci
|
prune .ci
|
||||||
|
prune wheels
|
||||||
|
|
10
README.md
10
README.md
|
@ -45,12 +45,12 @@ As of 2019, Pillow development is
|
||||||
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
|
<a href="https://ci.appveyor.com/project/python-pillow/Pillow"><img
|
||||||
alt="AppVeyor CI build status (Windows)"
|
alt="AppVeyor CI build status (Windows)"
|
||||||
src="https://img.shields.io/appveyor/build/python-pillow/Pillow/main.svg?label=Windows%20build"></a>
|
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
|
<a href="https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml"><img
|
||||||
alt="GitHub Actions wheels build status (Wheels)"
|
alt="GitHub Actions build status (Wheels)"
|
||||||
src="https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg"></a>
|
src="https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg"></a>
|
||||||
<a href="https://app.travis-ci.com/github/python-pillow/pillow-wheels"><img
|
<a href="https://app.travis-ci.com/github/python-pillow/Pillow"><img
|
||||||
alt="Travis CI wheels build status (aarch64)"
|
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
|
<a href="https://app.codecov.io/gh/python-pillow/Pillow"><img
|
||||||
alt="Code coverage"
|
alt="Code coverage"
|
||||||
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
src="https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg"></a>
|
||||||
|
|
17
RELEASING.md
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
|
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||||
* [ ] Develop and prepare release in `main` branch.
|
* [ ] 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 [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`
|
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||||
* [ ] Update `CHANGES.rst`.
|
* [ ] Update `CHANGES.rst`.
|
||||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||||
|
@ -99,17 +99,14 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
## Binary Distributions
|
## Binary Distributions
|
||||||
|
|
||||||
### macOS and Linux
|
### 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
|
```bash
|
||||||
git clone https://github.com/python-pillow/pillow-wheels
|
gh run download --dir dist
|
||||||
cd pillow-wheels
|
# select dist-x.y.z
|
||||||
./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
|
|
||||||
```
|
```
|
||||||
|
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
|
||||||
|
and copy into `dist`.
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
|
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
|
||||||
|
|
0
Tests/check_j2k_leaks.py
Executable file → Normal file
0
Tests/check_j2k_leaks.py
Executable file → Normal file
|
@ -37,4 +37,4 @@ The Font Software may be sold as part of a larger software package but no copy o
|
||||||
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
|
|
||||||
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.
|
Except as contained in this notice, the name of Tavmjong Bah shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from Tavmjong Bah. For further information, contact: tavmjong @ free . fr.
|
||||||
|
|
|
@ -91,7 +91,7 @@ def assert_image_equal(a, b, msg=None):
|
||||||
if HAS_UPLOADER:
|
if HAS_UPLOADER:
|
||||||
try:
|
try:
|
||||||
url = test_image_results.upload(a, b)
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -126,7 +126,7 @@ def assert_image_similar(a, b, epsilon, msg=None):
|
||||||
if HAS_UPLOADER:
|
if HAS_UPLOADER:
|
||||||
try:
|
try:
|
||||||
url = test_image_results.upload(a, b)
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
raise e
|
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
|
pertaining to distribution of the software without specific, written
|
||||||
prior permission. ICC makes no representations about the suitability
|
prior permission. ICC makes no representations about the suitability
|
||||||
of this software for any purpose.
|
of this software for any purpose.
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 95 KiB |
BIN
Tests/images/bc5u.dds
Normal file
BIN
Tests/images/bc5u.dds
Normal file
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 180 B |
0
Tests/images/negative_size.ppm
Executable file → Normal file
0
Tests/images/negative_size.ppm
Executable file → Normal file
BIN
Tests/images/palette.dds
Normal file
BIN
Tests/images/palette.dds
Normal file
Binary file not shown.
BIN
Tests/images/zero_bb_eof_before_boundingbox.eps
Normal file
BIN
Tests/images/zero_bb_eof_before_boundingbox.eps
Normal file
Binary file not shown.
BIN
Tests/images/zero_bb_trailer.eps
Normal file
BIN
Tests/images/zero_bb_trailer.eps
Normal file
Binary file not shown.
|
@ -6,6 +6,7 @@ import packaging
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, features
|
from PIL import Image, features
|
||||||
|
from Tests.helper import skip_unless_feature
|
||||||
|
|
||||||
if sys.platform.startswith("win32"):
|
if sys.platform.startswith("win32"):
|
||||||
pytest.skip("Fuzzer is linux only", allow_module_level=True)
|
pytest.skip("Fuzzer is linux only", allow_module_level=True)
|
||||||
|
@ -48,6 +49,7 @@ def test_fuzz_images(path):
|
||||||
fuzzers.disable_decompressionbomb_error()
|
fuzzers.disable_decompressionbomb_error()
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature("freetype2")
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path", subprocess.check_output("find Tests/fonts -type f", shell=True).split(b"\n")
|
"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):
|
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):
|
def assert_image(im, data, delta=0):
|
||||||
|
|
|
@ -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_UNORM = "Tests/images/bc5_unorm.dds"
|
||||||
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
|
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
|
||||||
TEST_FILE_BC5S = "Tests/images/bc5s.dds"
|
TEST_FILE_BC5S = "Tests/images/bc5s.dds"
|
||||||
|
TEST_FILE_BC5U = "Tests/images/bc5u.dds"
|
||||||
TEST_FILE_BC6H = "Tests/images/bc6h.dds"
|
TEST_FILE_BC6H = "Tests/images/bc6h.dds"
|
||||||
TEST_FILE_BC6HS = "Tests/images/bc6h_sf.dds"
|
TEST_FILE_BC6HS = "Tests/images/bc6h_sf.dds"
|
||||||
TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
|
TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds"
|
||||||
|
@ -81,10 +82,18 @@ def test_sanity_ati1():
|
||||||
assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
|
assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
|
||||||
|
|
||||||
|
|
||||||
def test_sanity_ati2():
|
@pytest.mark.parametrize(
|
||||||
"""Check ATI2 images can be opened"""
|
"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()
|
im.load()
|
||||||
|
|
||||||
assert im.format == "DDS"
|
assert im.format == "DDS"
|
||||||
|
@ -289,6 +298,11 @@ def test_dxt5_colorblock_alpha_issue_4142():
|
||||||
assert px[2] != 0
|
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")
|
||||||
|
|
||||||
|
|
||||||
def test_unimplemented_pixel_format():
|
def test_unimplemented_pixel_format():
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
with Image.open("Tests/images/unimplemented_pixel_format.dds"):
|
with Image.open("Tests/images/unimplemented_pixel_format.dds"):
|
||||||
|
|
|
@ -8,6 +8,7 @@ from .helper import (
|
||||||
assert_image_similar,
|
assert_image_similar,
|
||||||
assert_image_similar_tofile,
|
assert_image_similar_tofile,
|
||||||
hopper,
|
hopper,
|
||||||
|
is_win32,
|
||||||
mark_if_feature_version,
|
mark_if_feature_version,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
)
|
)
|
||||||
|
@ -98,6 +99,20 @@ def test_load():
|
||||||
assert im.load()[0, 0] == (255, 255, 255)
|
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():
|
def test_invalid_file():
|
||||||
invalid_file = "Tests/images/flower.jpg"
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
with pytest.raises(SyntaxError):
|
with pytest.raises(SyntaxError):
|
||||||
|
@ -404,3 +419,18 @@ def test_timeout(test_file):
|
||||||
with pytest.raises(Image.UnidentifiedImageError):
|
with pytest.raises(Image.UnidentifiedImageError):
|
||||||
with Image.open(f):
|
with Image.open(f):
|
||||||
pass
|
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():
|
def test_optimize_if_palette_can_be_reduced_by_half():
|
||||||
with Image.open("Tests/images/test.colors.gif") as im:
|
im = Image.new("P", (8, 1))
|
||||||
# Reduce dimensions because original is too big for _get_optimize()
|
im.palette = ImagePalette.raw("RGB", bytes((0, 0, 0) * 150))
|
||||||
im = im.resize((591, 443))
|
for i in range(8):
|
||||||
im_rgb = im.convert("RGB")
|
im.putpixel((i, 0), (i + 1, 0, 0))
|
||||||
|
|
||||||
for optimize, colors in ((False, 256), (True, 8)):
|
for optimize, colors in ((False, 256), (True, 8)):
|
||||||
out = BytesIO()
|
out = BytesIO()
|
||||||
im_rgb.save(out, "GIF", optimize=optimize)
|
im.save(out, "GIF", optimize=optimize)
|
||||||
with Image.open(out) as reloaded:
|
with Image.open(out) as reloaded:
|
||||||
assert len(reloaded.palette.palette) // 3 == colors
|
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
|
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):
|
def test_number_of_loops(tmp_path):
|
||||||
number_of_loops = 2
|
number_of_loops = 2
|
||||||
|
|
||||||
|
@ -1086,6 +1094,21 @@ def test_transparent_optimize(tmp_path):
|
||||||
assert reloaded.info["transparency"] == reloaded.getpixel((252, 0))
|
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):
|
def test_rgb_transparency(tmp_path):
|
||||||
out = str(tmp_path / "temp.gif")
|
out = str(tmp_path / "temp.gif")
|
||||||
|
|
||||||
|
@ -1157,18 +1180,17 @@ def test_palette_save_L(tmp_path):
|
||||||
|
|
||||||
|
|
||||||
def test_palette_save_P(tmp_path):
|
def test_palette_save_P(tmp_path):
|
||||||
# Pass in a different palette, then construct what the image would look like.
|
im = Image.new("P", (1, 2))
|
||||||
# Forcing a non-straight grayscale palette.
|
im.putpixel((0, 1), 1)
|
||||||
|
|
||||||
im = hopper("P")
|
|
||||||
palette = bytes(255 - i // 3 for i in range(768))
|
|
||||||
|
|
||||||
out = str(tmp_path / "temp.gif")
|
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:
|
with Image.open(out) as reloaded:
|
||||||
im.putpalette(palette)
|
reloaded_rgb = reloaded.convert("RGB")
|
||||||
assert_image_equal(reloaded, im)
|
|
||||||
|
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):
|
def test_palette_save_duplicate_entries(tmp_path):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import sys
|
import sys
|
||||||
from io import StringIO
|
from io import BytesIO, StringIO
|
||||||
|
|
||||||
from PIL import Image, IptcImagePlugin
|
from PIL import Image, IptcImagePlugin
|
||||||
|
|
||||||
|
@ -30,6 +30,36 @@ def test_getiptcinfo_jpg_found():
|
||||||
assert iptc[(2, 101)] == b"Hungary"
|
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():
|
def test_getiptcinfo_tiff_none():
|
||||||
# Arrange
|
# Arrange
|
||||||
with Image.open("Tests/images/hopper.tif") as im:
|
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.
|
# Should not raise OSError for image with icc larger than image size.
|
||||||
im.save(
|
im.save(
|
||||||
f,
|
f,
|
||||||
format="JPEG",
|
|
||||||
progressive=True,
|
progressive=True,
|
||||||
quality=95,
|
quality=95,
|
||||||
icc_profile=icc_profile,
|
icc_profile=icc_profile,
|
||||||
optimize=True,
|
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):
|
def test_optimize(self):
|
||||||
im1 = self.roundtrip(hopper())
|
im1 = self.roundtrip(hopper())
|
||||||
im2 = self.roundtrip(hopper(), optimize=0)
|
im2 = self.roundtrip(hopper(), optimize=0)
|
||||||
|
@ -945,11 +952,10 @@ class TestFileJpeg:
|
||||||
assert repr_jpeg.format == "JPEG"
|
assert repr_jpeg.format == "JPEG"
|
||||||
assert_image_similar(im, repr_jpeg, 17)
|
assert_image_similar(im, repr_jpeg, 17)
|
||||||
|
|
||||||
def test_repr_jpeg_error(self):
|
def test_repr_jpeg_error_returns_none(self):
|
||||||
im = hopper("F")
|
im = hopper("F")
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
assert im._repr_jpeg_() is None
|
||||||
im._repr_jpeg_()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not is_win32(), reason="Windows only")
|
@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
|
assert reloaded_signed.getpixel((0, 0)) == 128
|
||||||
|
|
||||||
|
|
||||||
def test_rgba():
|
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
|
||||||
|
def test_rgba(ext):
|
||||||
# Arrange
|
# Arrange
|
||||||
with Image.open("Tests/images/rgb_trns_ycbc.j2k") as j2k:
|
with Image.open("Tests/images/rgb_trns_ycbc" + ext) as im:
|
||||||
with Image.open("Tests/images/rgb_trns_ycbc.jp2") as jp2:
|
# Act
|
||||||
# Act
|
im.load()
|
||||||
j2k.load()
|
|
||||||
jp2.load()
|
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert j2k.mode == "RGBA"
|
assert im.mode == "RGBA"
|
||||||
assert jp2.mode == "RGBA"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
|
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
|
||||||
|
|
|
@ -8,7 +8,7 @@ from collections import namedtuple
|
||||||
|
|
||||||
import pytest
|
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 PIL.TiffImagePlugin import SAMPLEFORMAT, STRIPOFFSETS, SUBIFD
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
|
@ -1035,7 +1035,18 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
|
with Image.open("Tests/images/g4_orientation_1.tif") as base_im:
|
||||||
for i in range(2, 9):
|
for i in range(2, 9):
|
||||||
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
|
with Image.open("Tests/images/g4_orientation_" + str(i) + ".tif") as im:
|
||||||
|
assert 274 in im.tag_v2
|
||||||
|
|
||||||
im.load()
|
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)
|
assert_image_similar(base_im, im, 0.7)
|
||||||
|
|
||||||
|
|
|
@ -43,8 +43,25 @@ def test_save(tmp_path, mode):
|
||||||
|
|
||||||
|
|
||||||
@skip_unless_feature("jpg_2000")
|
@skip_unless_feature("jpg_2000")
|
||||||
def test_save_rgba(tmp_path):
|
@pytest.mark.parametrize("mode", ("LA", "RGBA"))
|
||||||
helper_save_as_pdf(tmp_path, "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):
|
def test_monochrome(tmp_path):
|
||||||
|
@ -57,8 +74,8 @@ def test_monochrome(tmp_path):
|
||||||
|
|
||||||
|
|
||||||
def test_unsupported_mode(tmp_path):
|
def test_unsupported_mode(tmp_path):
|
||||||
im = hopper("LA")
|
im = hopper("PA")
|
||||||
outfile = str(tmp_path / "temp_LA.pdf")
|
outfile = str(tmp_path / "temp_PA.pdf")
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.save(outfile)
|
im.save(outfile)
|
||||||
|
|
|
@ -79,7 +79,7 @@ class TestFilePng:
|
||||||
|
|
||||||
def test_sanity(self, tmp_path):
|
def test_sanity(self, tmp_path):
|
||||||
# internal version number
|
# 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")
|
test_file = str(tmp_path / "temp.png")
|
||||||
|
|
||||||
|
@ -92,11 +92,11 @@ class TestFilePng:
|
||||||
assert im.format == "PNG"
|
assert im.format == "PNG"
|
||||||
assert im.get_format_mimetype() == "image/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 = hopper(mode)
|
||||||
im.save(test_file)
|
im.save(test_file)
|
||||||
with Image.open(test_file) as reloaded:
|
with Image.open(test_file) as reloaded:
|
||||||
if mode == "I;16":
|
if mode in ("I;16", "I;16B"):
|
||||||
reloaded = reloaded.convert(mode)
|
reloaded = reloaded.convert(mode)
|
||||||
assert_image_equal(reloaded, im)
|
assert_image_equal(reloaded, im)
|
||||||
|
|
||||||
|
@ -532,11 +532,10 @@ class TestFilePng:
|
||||||
assert repr_png.format == "PNG"
|
assert repr_png.format == "PNG"
|
||||||
assert_image_equal(im, repr_png)
|
assert_image_equal(im, repr_png)
|
||||||
|
|
||||||
def test_repr_png_error(self):
|
def test_repr_png_error_returns_none(self):
|
||||||
im = hopper("F")
|
im = hopper("F")
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
assert im._repr_png_() is None
|
||||||
im._repr_png_()
|
|
||||||
|
|
||||||
def test_chunk_order(self, tmp_path):
|
def test_chunk_order(self, tmp_path):
|
||||||
with Image.open("Tests/images/icc_profile.png") as im:
|
with Image.open("Tests/images/icc_profile.png") as im:
|
||||||
|
|
|
@ -2,7 +2,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, QoiImagePlugin
|
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():
|
def test_sanity():
|
||||||
|
@ -18,7 +18,7 @@ def test_sanity():
|
||||||
assert im.size == (162, 150)
|
assert im.size == (162, 150)
|
||||||
assert im.format == "QOI"
|
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():
|
def test_invalid_file():
|
||||||
|
|
|
@ -235,3 +235,13 @@ class TestFileWebp:
|
||||||
with Image.open(out_webp) as reloaded:
|
with Image.open(out_webp) as reloaded:
|
||||||
reloaded.load()
|
reloaded.load()
|
||||||
assert reloaded.info["duration"] == 1000
|
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)
|
||||||
|
|
|
@ -135,6 +135,12 @@ class TestImage:
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
im.size = (3, 4)
|
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):
|
def test_invalid_image(self):
|
||||||
im = io.BytesIO(b"")
|
im = io.BytesIO(b"")
|
||||||
with pytest.raises(UnidentifiedImageError):
|
with pytest.raises(UnidentifiedImageError):
|
||||||
|
@ -632,8 +638,8 @@ class TestImage:
|
||||||
im.remap_palette(None)
|
im.remap_palette(None)
|
||||||
|
|
||||||
def test_remap_palette_transparency(self):
|
def test_remap_palette_transparency(self):
|
||||||
im = Image.new("P", (1, 2))
|
im = Image.new("P", (1, 2), (0, 0, 0))
|
||||||
im.putpixel((0, 1), 1)
|
im.putpixel((0, 1), (255, 0, 0))
|
||||||
im.info["transparency"] = 0
|
im.info["transparency"] = 0
|
||||||
|
|
||||||
im_remapped = im.remap_palette([1, 0])
|
im_remapped = im.remap_palette([1, 0])
|
||||||
|
@ -655,15 +661,15 @@ class TestImage:
|
||||||
blank_p.palette = None
|
blank_p.palette = None
|
||||||
blank_pa.palette = None
|
blank_pa.palette = None
|
||||||
|
|
||||||
def _make_new(base_image, im, palette_result=None):
|
def _make_new(base_image, image, palette_result=None):
|
||||||
new_im = base_image._new(im)
|
new_image = base_image._new(image.im)
|
||||||
assert new_im.mode == im.mode
|
assert new_image.mode == image.mode
|
||||||
assert new_im.size == im.size
|
assert new_image.size == image.size
|
||||||
assert new_im.info == base_image.info
|
assert new_image.info == base_image.info
|
||||||
if palette_result is not None:
|
if palette_result is not None:
|
||||||
assert new_im.palette.tobytes() == palette_result.tobytes()
|
assert new_image.palette.tobytes() == palette_result.tobytes()
|
||||||
else:
|
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, im_p, ImagePalette.ImagePalette(list(range(256)) * 3))
|
||||||
_make_new(im_p, im, None)
|
_make_new(im_p, im, None)
|
||||||
|
@ -900,6 +906,31 @@ class TestImage:
|
||||||
im = Image.new("RGB", size)
|
im = Image.new("RGB", size)
|
||||||
assert im.tobytes() == b""
|
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):
|
def test_apply_transparency(self):
|
||||||
im = Image.new("P", (1, 1))
|
im = Image.new("P", (1, 1))
|
||||||
im.putpalette((0, 0, 0, 1, 1, 1))
|
im.putpalette((0, 0, 0, 1, 1, 1))
|
||||||
|
|
|
@ -213,6 +213,10 @@ class TestImageGetPixel(AccessTest):
|
||||||
def test_basic(self, mode):
|
def test_basic(self, mode):
|
||||||
self.check(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("mode", ("I;16", "I;16B"))
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)
|
"expected_color", (2**15 - 1, 2**15, 2**15 + 1, 2**16 - 1)
|
||||||
|
|
|
@ -117,11 +117,11 @@ def test_trns_p(tmp_path):
|
||||||
f = str(tmp_path / "temp.png")
|
f = str(tmp_path / "temp.png")
|
||||||
|
|
||||||
im_l = im.convert("L")
|
im_l = im.convert("L")
|
||||||
assert im_l.info["transparency"] == 1 # undone
|
assert im_l.info["transparency"] == 0
|
||||||
im_l.save(f)
|
im_l.save(f)
|
||||||
|
|
||||||
im_rgb = im.convert("RGB")
|
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)
|
im_rgb.save(f)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,12 @@ from .helper import assert_image_equal, hopper
|
||||||
ImageFilter.MinFilter,
|
ImageFilter.MinFilter,
|
||||||
ImageFilter.ModeFilter,
|
ImageFilter.ModeFilter,
|
||||||
ImageFilter.GaussianBlur,
|
ImageFilter.GaussianBlur,
|
||||||
|
ImageFilter.GaussianBlur(0),
|
||||||
ImageFilter.GaussianBlur(5),
|
ImageFilter.GaussianBlur(5),
|
||||||
|
ImageFilter.GaussianBlur((2, 5)),
|
||||||
ImageFilter.BoxBlur(0),
|
ImageFilter.BoxBlur(0),
|
||||||
ImageFilter.BoxBlur(5),
|
ImageFilter.BoxBlur(5),
|
||||||
|
ImageFilter.BoxBlur((2, 5)),
|
||||||
ImageFilter.UnsharpMask,
|
ImageFilter.UnsharpMask,
|
||||||
ImageFilter.UnsharpMask(10),
|
ImageFilter.UnsharpMask(10),
|
||||||
),
|
),
|
||||||
|
@ -185,12 +188,21 @@ def test_consistency_5x5(mode):
|
||||||
assert_image_equal(source.filter(kernel), reference)
|
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):
|
with pytest.raises(ValueError):
|
||||||
ImageFilter.BoxBlur(-2)
|
ImageFilter.BoxBlur(radius)
|
||||||
|
|
||||||
im = hopper()
|
im = hopper()
|
||||||
box_blur_filter = ImageFilter.BoxBlur(2)
|
box_blur_filter = ImageFilter.BoxBlur(2)
|
||||||
box_blur_filter.radius = -2
|
box_blur_filter.radius = radius
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.filter(box_blur_filter)
|
im.filter(box_blur_filter)
|
||||||
|
|
|
@ -84,3 +84,14 @@ def test_rgba_palette(mode, palette):
|
||||||
im.putpalette(palette, mode)
|
im.putpalette(palette, mode)
|
||||||
assert im.getpalette() == [1, 2, 3]
|
assert im.getpalette() == [1, 2, 3]
|
||||||
assert im.palette.colors == {(1, 2, 3, 4): 0}
|
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)
|
||||||
|
|
|
@ -586,6 +586,18 @@ def test_point(points):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_point.png")
|
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)
|
@pytest.mark.parametrize("points", POINTS)
|
||||||
def test_polygon(points):
|
def test_polygon(points):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -732,7 +744,7 @@ def test_rectangle_I16(bbox):
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.rectangle(bbox, fill="black", outline="green")
|
draw.rectangle(bbox, outline=0xFFFF)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert_image_equal_tofile(im.convert("I"), "Tests/images/imagedraw_rectangle_I.png")
|
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)
|
assert_image_similar_tofile(im, "Tests/images/imagedraw_stroke_multiline.png", 3.3)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature("freetype2")
|
||||||
def test_setting_default_font():
|
def test_setting_default_font():
|
||||||
# Arrange
|
# Arrange
|
||||||
im = Image.new("RGB", (100, 250))
|
im = Image.new("RGB", (100, 250))
|
||||||
|
|
|
@ -136,7 +136,7 @@ class TestImageFile:
|
||||||
|
|
||||||
class DummyImageFile(ImageFile.ImageFile):
|
class DummyImageFile(ImageFile.ImageFile):
|
||||||
def _open(self):
|
def _open(self):
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
self._size = (1, 1)
|
self._size = (1, 1)
|
||||||
|
|
||||||
im = DummyImageFile(buf)
|
im = DummyImageFile(buf)
|
||||||
|
@ -217,7 +217,7 @@ xoff, yoff, xsize, ysize = 10, 20, 100, 100
|
||||||
class MockImageFile(ImageFile.ImageFile):
|
class MockImageFile(ImageFile.ImageFile):
|
||||||
def _open(self):
|
def _open(self):
|
||||||
self.rawmode = "RGBA"
|
self.rawmode = "RGBA"
|
||||||
self.mode = "RGBA"
|
self._mode = "RGBA"
|
||||||
self._size = (200, 200)
|
self._size = (200, 200)
|
||||||
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
|
self.tile = [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 32, None)]
|
||||||
|
|
||||||
|
|
|
@ -141,7 +141,9 @@ def test_I16(font):
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
txt = "Hello World!"
|
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"
|
target = "Tests/images/transparent_background_text_L.png"
|
||||||
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
assert_image_similar_tofile(im.convert("L"), target, 0.01)
|
||||||
|
|
|
@ -75,13 +75,13 @@ def test_pickle_la_mode_with_palette(tmp_path):
|
||||||
|
|
||||||
# Act / Assert
|
# Act / Assert
|
||||||
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
|
for protocol in range(0, pickle.HIGHEST_PROTOCOL + 1):
|
||||||
im.mode = "LA"
|
im._mode = "LA"
|
||||||
with open(filename, "wb") as f:
|
with open(filename, "wb") as f:
|
||||||
pickle.dump(im, f, protocol)
|
pickle.dump(im, f, protocol)
|
||||||
with open(filename, "rb") as f:
|
with open(filename, "rb") as f:
|
||||||
loaded_im = pickle.load(f)
|
loaded_im = pickle.load(f)
|
||||||
|
|
||||||
im.mode = "PA"
|
im._mode = "PA"
|
||||||
assert im == loaded_im
|
assert im == loaded_im
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,6 +112,7 @@ def helper_assert_pickled_font_images(font1, font2):
|
||||||
assert_image_equal(im1, im2)
|
assert_image_equal(im1, im2)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature("freetype2")
|
||||||
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
|
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
|
||||||
def test_pickle_font_string(protocol):
|
def test_pickle_font_string(protocol):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
@ -125,6 +126,7 @@ def test_pickle_font_string(protocol):
|
||||||
helper_assert_pickled_font_images(font, unpickled_font)
|
helper_assert_pickled_font_images(font, unpickled_font)
|
||||||
|
|
||||||
|
|
||||||
|
@skip_unless_feature("freetype2")
|
||||||
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
|
@pytest.mark.parametrize("protocol", list(range(0, pickle.HIGHEST_PROTOCOL + 1)))
|
||||||
def test_pickle_font_file(tmp_path, protocol):
|
def test_pickle_font_file(tmp_path, protocol):
|
||||||
# Arrange
|
# Arrange
|
||||||
|
|
0
_custom_build/backend.py
Executable file → Normal file
0
_custom_build/backend.py
Executable file → Normal file
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install libimagequant
|
# 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
|
./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
|
meson build --prefix=/usr && sudo ninja -C build install
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
|
|
@ -15,4 +15,3 @@ make && sudo make install
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install webp
|
# install webp
|
||||||
|
|
||||||
archive=libwebp-1.3.1
|
archive=libwebp-1.3.2
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
./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 \
|
pkg install -y python ndk-sysroot clang make \
|
||||||
libjpeg-turbo
|
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
|
.. _GitHub Actions: https://github.com/python-pillow/Pillow/actions
|
||||||
.. _AppVeyor: https://ci.appveyor.com/project/Python-pillow/pillow
|
.. _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
|
.. _GitHub: https://github.com/python-pillow/Pillow
|
||||||
.. _Python Package Index: https://pypi.org/project/Pillow/
|
.. _Python Package Index: https://pypi.org/project/Pillow/
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,7 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
flags, height, width = struct.unpack("<3I", header.read(12))
|
flags, height, width = struct.unpack("<3I", header.read(12))
|
||||||
self._size = (width, height)
|
self._size = (width, height)
|
||||||
self.mode = "RGBA"
|
self._mode = "RGBA"
|
||||||
|
|
||||||
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
||||||
struct.unpack("<11I", header.read(44)) # reserved
|
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
|
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,
|
by DirectX.
|
||||||
DXT3 (since 3.4.0) and DXT5 pixel formats can be read, only in ``RGBA`` mode.
|
|
||||||
|
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
|
DIB
|
||||||
^^^
|
^^^
|
||||||
|
@ -88,8 +115,13 @@ in ``L``, ``RGB`` and ``CMYK`` modes.
|
||||||
Loading
|
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`
|
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**
|
**scale**
|
||||||
Affects the scale of the resultant rasterized image. If the EPS suggests
|
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**
|
**loop**
|
||||||
Integer number of times the GIF should loop. 0 means that it will 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**
|
**comment**
|
||||||
A comment about the image.
|
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
|
Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I`` or
|
||||||
``RGB`` data.
|
``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
|
SGI
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
@ -1482,7 +1518,7 @@ files. Different encoding methods are used, depending on the image mode.
|
||||||
unavailable
|
unavailable
|
||||||
* L, RGB and CMYK mode images use JPEG encoding
|
* L, RGB and CMYK mode images use JPEG encoding
|
||||||
* P mode images use HEX encoding
|
* P mode images use HEX encoding
|
||||||
* RGBA mode images use JPEG2000 encoding
|
* LA and RGBA mode images use JPEG2000 encoding
|
||||||
|
|
||||||
.. _pdf-saving:
|
.. _pdf-saving:
|
||||||
|
|
||||||
|
|
|
@ -72,11 +72,11 @@ true color.
|
||||||
# mode setting
|
# mode setting
|
||||||
bits = int(header[3])
|
bits = int(header[3])
|
||||||
if bits == 1:
|
if bits == 1:
|
||||||
self.mode = "1"
|
self._mode = "1"
|
||||||
elif bits == 8:
|
elif bits == 8:
|
||||||
self.mode = "L"
|
self._mode = "L"
|
||||||
elif bits == 24:
|
elif bits == 24:
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
else:
|
else:
|
||||||
msg = "unknown number of bits"
|
msg = "unknown number of bits"
|
||||||
raise SyntaxError(msg)
|
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
|
:target: https://ci.appveyor.com/project/python-pillow/Pillow
|
||||||
:alt: AppVeyor CI build status (Windows)
|
:alt: AppVeyor CI build status (Windows)
|
||||||
|
|
||||||
.. image:: https://github.com/python-pillow/pillow-wheels/workflows/Wheels/badge.svg
|
.. image:: https://github.com/python-pillow/Pillow/workflows/Wheels/badge.svg
|
||||||
:target: https://github.com/python-pillow/pillow-wheels/actions
|
:target: https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml
|
||||||
:alt: GitHub Actions wheels build status (Wheels)
|
:alt: GitHub Actions build status (Wheels)
|
||||||
|
|
||||||
.. image:: https://img.shields.io/travis/com/python-pillow/pillow-wheels/main.svg?label=aarch64%20wheels
|
.. 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-wheels
|
:target: https://app.travis-ci.com/github/python-pillow/Pillow
|
||||||
:alt: Travis CI wheels build status (aarch64)
|
:alt: Travis CI wheels build status (aarch64)
|
||||||
|
|
||||||
.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg
|
.. image:: https://codecov.io/gh/python-pillow/Pillow/branch/main/graph/badge.svg
|
||||||
|
|
|
@ -83,10 +83,9 @@ Install Pillow with :command:`pip`::
|
||||||
.. tab:: Windows
|
.. tab:: Windows
|
||||||
|
|
||||||
We provide Pillow binaries for Windows compiled for the matrix of
|
We provide Pillow binaries for Windows compiled for the matrix of
|
||||||
supported Pythons in both 32 and 64-bit versions in the wheel format.
|
supported Pythons in 64-bit versions in the wheel format. These binaries include
|
||||||
These binaries include support for all optional libraries except
|
support for all optional libraries except libimagequant and libxcb. Raqm support
|
||||||
libimagequant and libxcb. Raqm support requires
|
requires FriBiDi to be installed separately::
|
||||||
FriBiDi to be installed separately::
|
|
||||||
|
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
python3 -m pip install --upgrade Pillow
|
python3 -m pip install --upgrade Pillow
|
||||||
|
@ -181,7 +180,7 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
* **libimagequant** provides improved color quantization
|
* **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
|
* Libimagequant is licensed GPLv3, which is more restrictive than
|
||||||
the Pillow license, therefore we will not be distributing binaries
|
the Pillow license, therefore we will not be distributing binaries
|
||||||
with libimagequant support enabled.
|
with libimagequant support enabled.
|
||||||
|
@ -499,11 +498,13 @@ These platforms have been reported to work at the versions mentioned.
|
||||||
| Operating system | | Tested Python | | Latest tested | | Tested |
|
| Operating system | | Tested Python | | Latest tested | | Tested |
|
||||||
| | | versions | | Pillow version | | processors |
|
| | | versions | | Pillow version | | processors |
|
||||||
+==================================+===========================+==================+==============+
|
+==================================+===========================+==================+==============+
|
||||||
| macOS 13 Ventura | 3.8, 3.9, 3.10, 3.11 | 10.0.0 |arm |
|
| macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11 | 10.0.1 |arm |
|
||||||
| +---------------------------+------------------+ |
|
|
||||||
| | 3.7, 3.8, 3.9, 3.10, 3.11 | 9.5.0 | |
|
|
||||||
+----------------------------------+---------------------------+------------------+--------------+
|
+----------------------------------+---------------------------+------------------+--------------+
|
||||||
| 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 |
|
| 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
|
Python,3.12,3.11,3.10,3.9,3.8,3.7,3.6,3.5
|
||||||
Pillow >= 10,Yes,Yes,Yes,Yes,,,
|
Pillow >= 10.1,Yes,Yes,Yes,Yes,Yes,,,
|
||||||
Pillow 9.3 - 9.5,Yes,Yes,Yes,Yes,Yes,,
|
Pillow 10.0,,Yes,Yes,Yes,Yes,,,
|
||||||
Pillow 9.0 - 9.2,,Yes,Yes,Yes,Yes,,
|
Pillow 9.3 - 9.5,,Yes,Yes,Yes,Yes,Yes,,
|
||||||
Pillow 8.3.2 - 8.4,,Yes,Yes,Yes,Yes,Yes,
|
Pillow 9.0 - 9.2,,,Yes,Yes,Yes,Yes,,
|
||||||
Pillow 8.0 - 8.3.1,,,Yes,Yes,Yes,Yes,
|
Pillow 8.3.2 - 8.4,,,Yes,Yes,Yes,Yes,Yes,
|
||||||
Pillow 7.0 - 7.2,,,,Yes,Yes,Yes,Yes
|
Pillow 8.0 - 8.3.1,,,,Yes,Yes,Yes,Yes,
|
||||||
|
Pillow 7.0 - 7.2,,,,,Yes,Yes,Yes,Yes
|
||||||
|
|
|
|
@ -5,4 +5,4 @@ Pillow 5.2 - 5.4,,Yes,Yes,Yes,Yes,,,Yes,,,
|
||||||
Pillow 5.0 - 5.1,,,Yes,Yes,Yes,,,Yes,,,
|
Pillow 5.0 - 5.1,,,Yes,Yes,Yes,,,Yes,,,
|
||||||
Pillow 4,,,Yes,Yes,Yes,Yes,,Yes,,,
|
Pillow 4,,,Yes,Yes,Yes,Yes,,Yes,,,
|
||||||
Pillow 2 - 3,,,,Yes,Yes,Yes,Yes,Yes,Yes,,
|
Pillow 2 - 3,,,,Yes,Yes,Yes,Yes,Yes,Yes,,
|
||||||
Pillow < 2,,,,,,,,Yes,Yes,Yes,Yes
|
Pillow < 2,,,,,,,,Yes,Yes,Yes,Yes
|
||||||
|
|
|
|
@ -93,10 +93,14 @@ Generating images
|
||||||
Registering plugins
|
Registering plugins
|
||||||
^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. autofunction:: preinit
|
||||||
|
.. autofunction:: init
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
These functions are for use by plugin authors. Application authors can
|
These functions are for use by plugin authors. They are called when a
|
||||||
ignore them.
|
plugin is loaded as part of :py:meth:`~preinit()` or :py:meth:`~init()`.
|
||||||
|
Application authors can ignore them.
|
||||||
|
|
||||||
.. autofunction:: register_open
|
.. autofunction:: register_open
|
||||||
.. autofunction:: register_mime
|
.. 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`
|
.. seealso:: :attr:`~Image.is_animated`, :func:`~Image.seek` and :func:`~Image.tell`
|
||||||
|
|
||||||
|
.. autoattribute:: PIL.Image.Image.has_transparency_data
|
||||||
|
|
||||||
Classes
|
Classes
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -538,7 +538,7 @@ Methods
|
||||||
It should be a `BCP 47 language code`_.
|
It should be a `BCP 47 language code`_.
|
||||||
Requires libraqm.
|
Requires libraqm.
|
||||||
:param embedded_color: Whether to use font embedded color glyphs (COLR, CBDT, SBIX).
|
: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)
|
.. 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)
|
||||||
|
|
||||||
|
|
|
@ -206,4 +206,4 @@ Support reading signed 8-bit TIFF images
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TIFF images with signed integer data, 8 bits per sample and a photometric
|
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
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.
|
66
docs/releasenotes/10.1.0.rst
Normal file
66
docs/releasenotes/10.1.0.rst
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
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.
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
============
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
API Changes
|
||||||
|
===========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
API Additions
|
||||||
|
=============
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Added support for DDS 8-bit color indexed images
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Support has been added to read 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
|
If the headers or libraries are not found, then installation will abort
|
||||||
with an error. This behaviour can be disabled with the ``--disable-libjpeg``
|
with an error. This behaviour can be disabled with the ``--disable-libjpeg``
|
||||||
and ``--disable-zlib`` flags.
|
and ``--disable-zlib`` flags.
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,3 @@ image size can lead to a smaller allocation than expected, leading to
|
||||||
arbitrary writes.
|
arbitrary writes.
|
||||||
|
|
||||||
This issue was found by Cris Neckar at Divergent Security.
|
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
|
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
|
with both 3.6.0 and 3.6.1. See https://bugs.python.org/issue29943 for
|
||||||
more details.
|
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
|
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.
|
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.
|
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::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
10.1.0
|
||||||
|
10.0.1
|
||||||
10.0.0
|
10.0.0
|
||||||
9.5.0
|
9.5.0
|
||||||
9.4.0
|
9.4.0
|
||||||
|
|
|
@ -16,6 +16,7 @@ classifiers =
|
||||||
Programming Language :: Python :: 3.9
|
Programming Language :: Python :: 3.9
|
||||||
Programming Language :: Python :: 3.10
|
Programming Language :: Python :: 3.10
|
||||||
Programming Language :: Python :: 3.11
|
Programming Language :: Python :: 3.11
|
||||||
|
Programming Language :: Python :: 3.12
|
||||||
Programming Language :: Python :: Implementation :: CPython
|
Programming Language :: Python :: Implementation :: CPython
|
||||||
Programming Language :: Python :: Implementation :: PyPy
|
Programming Language :: Python :: Implementation :: PyPy
|
||||||
Topic :: Multimedia :: Graphics
|
Topic :: Multimedia :: Graphics
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -39,7 +39,7 @@ TIFF_ROOT = None
|
||||||
ZLIB_ROOT = None
|
ZLIB_ROOT = None
|
||||||
FUZZING_BUILD = "LIB_FUZZING_ENGINE" in os.environ
|
FUZZING_BUILD = "LIB_FUZZING_ENGINE" in os.environ
|
||||||
|
|
||||||
if sys.platform == "win32" and sys.version_info >= (3, 12):
|
if sys.platform == "win32" and sys.version_info >= (3, 13):
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
atexit.register(
|
atexit.register(
|
||||||
|
|
|
@ -68,11 +68,11 @@ def bdf_char(f):
|
||||||
# followed by the width in x (BBw), height in y (BBh),
|
# followed by the width in x (BBw), height in y (BBh),
|
||||||
# and x and y displacement (BBxoff0, BByoff0)
|
# and x and y displacement (BBxoff0, BByoff0)
|
||||||
# of the lower left corner from the origin of the character.
|
# of the lower left corner from the origin of the character.
|
||||||
width, height, x_disp, y_disp = [int(p) for p in props["BBX"].split()]
|
width, height, x_disp, y_disp = (int(p) for p in props["BBX"].split())
|
||||||
|
|
||||||
# The word DWIDTH
|
# The word DWIDTH
|
||||||
# followed by the width in x and y of the character in device pixels.
|
# followed by the width in x and y of the character in device pixels.
|
||||||
dwx, dwy = [int(p) for p in props["DWIDTH"].split()]
|
dwx, dwy = (int(p) for p in props["DWIDTH"].split())
|
||||||
|
|
||||||
bbox = (
|
bbox = (
|
||||||
(dwx, dwy),
|
(dwx, dwy),
|
||||||
|
|
|
@ -266,7 +266,7 @@ class BlpImageFile(ImageFile.ImageFile):
|
||||||
msg = f"Bad BLP magic {repr(self.magic)}"
|
msg = f"Bad BLP magic {repr(self.magic)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
|
|
||||||
self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
||||||
self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
||||||
|
|
||||||
|
|
||||||
|
@ -419,9 +419,11 @@ class BLPEncoder(ImageFile.PyEncoder):
|
||||||
def _write_palette(self):
|
def _write_palette(self):
|
||||||
data = b""
|
data = b""
|
||||||
palette = self.im.getpalette("RGBA", "RGBA")
|
palette = self.im.getpalette("RGBA", "RGBA")
|
||||||
for i in range(256):
|
for i in range(len(palette) // 4):
|
||||||
r, g, b, a = palette[i * 4 : (i + 1) * 4]
|
r, g, b, a = palette[i * 4 : (i + 1) * 4]
|
||||||
data += struct.pack("<4B", b, g, r, a)
|
data += struct.pack("<4B", b, g, r, a)
|
||||||
|
while len(data) < 256 * 4:
|
||||||
|
data += b"\x00" * 4
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def encode(self, bufsize):
|
def encode(self, bufsize):
|
||||||
|
@ -442,7 +444,7 @@ class BLPEncoder(ImageFile.PyEncoder):
|
||||||
return len(data), 0, data
|
return len(data), 0, data
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename, save_all=False):
|
def _save(im, fp, filename):
|
||||||
if im.mode != "P":
|
if im.mode != "P":
|
||||||
msg = "Unsupported BLP image mode"
|
msg = "Unsupported BLP image mode"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
|
@ -163,7 +163,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
offset += 4 * file_info["colors"]
|
offset += 4 * file_info["colors"]
|
||||||
|
|
||||||
# ---------------------- Check bit depth for unusual unsupported values
|
# ---------------------- Check bit depth for unusual unsupported values
|
||||||
self.mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
|
self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None))
|
||||||
if self.mode is None:
|
if self.mode is None:
|
||||||
msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
|
msg = f"Unsupported BMP pixel depth ({file_info['bits']})"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
@ -200,7 +200,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
|
and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]]
|
||||||
):
|
):
|
||||||
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
|
raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])]
|
||||||
self.mode = "RGBA" if "A" in raw_mode else self.mode
|
self._mode = "RGBA" if "A" in raw_mode else self.mode
|
||||||
elif (
|
elif (
|
||||||
file_info["bits"] in (24, 16)
|
file_info["bits"] in (24, 16)
|
||||||
and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
|
and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]]
|
||||||
|
@ -214,7 +214,7 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
elif file_info["compression"] == self.RAW:
|
elif file_info["compression"] == self.RAW:
|
||||||
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
|
if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset
|
||||||
raw_mode, self.mode = "BGRA", "RGBA"
|
raw_mode, self._mode = "BGRA", "RGBA"
|
||||||
elif file_info["compression"] in (self.RLE8, self.RLE4):
|
elif file_info["compression"] in (self.RLE8, self.RLE4):
|
||||||
decoder_name = "bmp_rle"
|
decoder_name = "bmp_rle"
|
||||||
else:
|
else:
|
||||||
|
@ -245,10 +245,10 @@ class BmpImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
# ------- If all colors are grey, white or black, ditch palette
|
# ------- If all colors are grey, white or black, ditch palette
|
||||||
if greyscale:
|
if greyscale:
|
||||||
self.mode = "1" if file_info["colors"] == 2 else "L"
|
self._mode = "1" if file_info["colors"] == 2 else "L"
|
||||||
raw_mode = self.mode
|
raw_mode = self.mode
|
||||||
else:
|
else:
|
||||||
self.mode = "P"
|
self._mode = "P"
|
||||||
self.palette = ImagePalette.raw(
|
self.palette = ImagePalette.raw(
|
||||||
"BGRX" if padding == 4 else "BGR", palette
|
"BGRX" if padding == 4 else "BGR", palette
|
||||||
)
|
)
|
||||||
|
|
|
@ -46,7 +46,7 @@ class BufrStubImageFile(ImageFile.StubImageFile):
|
||||||
self.fp.seek(offset)
|
self.fp.seek(offset)
|
||||||
|
|
||||||
# make something up
|
# make something up
|
||||||
self.mode = "F"
|
self._mode = "F"
|
||||||
self._size = 1, 1
|
self._size = 1, 1
|
||||||
|
|
||||||
loader = self._load()
|
loader = self._load()
|
||||||
|
|
|
@ -13,7 +13,7 @@ Full text of the CC0 license:
|
||||||
import struct
|
import struct
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import o32le as o32
|
from ._binary import o32le as o32
|
||||||
|
|
||||||
# Magic ("DDS ")
|
# Magic ("DDS ")
|
||||||
|
@ -128,7 +128,7 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
flags, height, width = struct.unpack("<3I", header.read(12))
|
flags, height, width = struct.unpack("<3I", header.read(12))
|
||||||
self._size = (width, height)
|
self._size = (width, height)
|
||||||
self.mode = "RGBA"
|
self._mode = "RGBA"
|
||||||
|
|
||||||
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
||||||
struct.unpack("<11I", header.read(44)) # reserved
|
struct.unpack("<11I", header.read(44)) # reserved
|
||||||
|
@ -141,9 +141,9 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
if pfflags & DDPF_LUMINANCE:
|
if pfflags & DDPF_LUMINANCE:
|
||||||
# Texture contains uncompressed L or LA data
|
# Texture contains uncompressed L or LA data
|
||||||
if pfflags & DDPF_ALPHAPIXELS:
|
if pfflags & DDPF_ALPHAPIXELS:
|
||||||
self.mode = "LA"
|
self._mode = "LA"
|
||||||
else:
|
else:
|
||||||
self.mode = "L"
|
self._mode = "L"
|
||||||
|
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
||||||
elif pfflags & DDPF_RGB:
|
elif pfflags & DDPF_RGB:
|
||||||
|
@ -153,10 +153,14 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
if pfflags & DDPF_ALPHAPIXELS:
|
if pfflags & DDPF_ALPHAPIXELS:
|
||||||
rawmode += masks[0xFF000000]
|
rawmode += masks[0xFF000000]
|
||||||
else:
|
else:
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
|
rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF]
|
||||||
|
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))]
|
self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))]
|
||||||
|
elif pfflags & DDPF_PALETTEINDEXED8:
|
||||||
|
self._mode = "P"
|
||||||
|
self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
|
||||||
|
self.tile = [("raw", (0, 0) + self.size, 0, "L")]
|
||||||
else:
|
else:
|
||||||
data_start = header_size + 4
|
data_start = header_size + 4
|
||||||
n = 0
|
n = 0
|
||||||
|
@ -172,15 +176,15 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
elif fourcc == b"ATI1":
|
elif fourcc == b"ATI1":
|
||||||
self.pixel_format = "BC4"
|
self.pixel_format = "BC4"
|
||||||
n = 4
|
n = 4
|
||||||
self.mode = "L"
|
self._mode = "L"
|
||||||
elif fourcc == b"ATI2":
|
elif fourcc in (b"ATI2", b"BC5U"):
|
||||||
self.pixel_format = "BC5"
|
self.pixel_format = "BC5"
|
||||||
n = 5
|
n = 5
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
elif fourcc == b"BC5S":
|
elif fourcc == b"BC5S":
|
||||||
self.pixel_format = "BC5S"
|
self.pixel_format = "BC5S"
|
||||||
n = 5
|
n = 5
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
elif fourcc == b"DX10":
|
elif fourcc == b"DX10":
|
||||||
data_start += 20
|
data_start += 20
|
||||||
# ignoring flags which pertain to volume textures and cubemaps
|
# ignoring flags which pertain to volume textures and cubemaps
|
||||||
|
@ -189,19 +193,19 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM):
|
if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM):
|
||||||
self.pixel_format = "BC5"
|
self.pixel_format = "BC5"
|
||||||
n = 5
|
n = 5
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
elif dxgi_format == DXGI_FORMAT_BC5_SNORM:
|
elif dxgi_format == DXGI_FORMAT_BC5_SNORM:
|
||||||
self.pixel_format = "BC5S"
|
self.pixel_format = "BC5S"
|
||||||
n = 5
|
n = 5
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
|
elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
|
||||||
self.pixel_format = "BC6H"
|
self.pixel_format = "BC6H"
|
||||||
n = 6
|
n = 6
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
|
elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
|
||||||
self.pixel_format = "BC6HS"
|
self.pixel_format = "BC6HS"
|
||||||
n = 6
|
n = 6
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
|
elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
|
||||||
self.pixel_format = "BC7"
|
self.pixel_format = "BC7"
|
||||||
n = 7
|
n = 7
|
||||||
|
|
|
@ -37,33 +37,39 @@ from ._deprecate import deprecate
|
||||||
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
split = re.compile(r"^%%([^:]*):[ \t]*(.*)[ \t]*$")
|
||||||
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
field = re.compile(r"^%[%!\w]([^:]*)[ \t]*$")
|
||||||
|
|
||||||
|
gs_binary = None
|
||||||
gs_windows_binary = None
|
gs_windows_binary = None
|
||||||
if sys.platform.startswith("win"):
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
for binary in ("gswin32c", "gswin64c", "gs"):
|
|
||||||
if shutil.which(binary) is not None:
|
|
||||||
gs_windows_binary = binary
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
gs_windows_binary = False
|
|
||||||
|
|
||||||
|
|
||||||
def has_ghostscript():
|
def has_ghostscript():
|
||||||
if gs_windows_binary:
|
global gs_binary, gs_windows_binary
|
||||||
return True
|
if gs_binary is None:
|
||||||
if not sys.platform.startswith("win"):
|
if sys.platform.startswith("win"):
|
||||||
try:
|
if gs_windows_binary is None:
|
||||||
subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL)
|
import shutil
|
||||||
return True
|
|
||||||
except OSError:
|
for binary in ("gswin32c", "gswin64c", "gs"):
|
||||||
# No Ghostscript
|
if shutil.which(binary) is not None:
|
||||||
pass
|
gs_windows_binary = binary
|
||||||
return False
|
break
|
||||||
|
else:
|
||||||
|
gs_windows_binary = False
|
||||||
|
gs_binary = gs_windows_binary
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
subprocess.check_call(["gs", "--version"], stdout=subprocess.DEVNULL)
|
||||||
|
gs_binary = "gs"
|
||||||
|
except OSError:
|
||||||
|
gs_binary = False
|
||||||
|
return gs_binary is not False
|
||||||
|
|
||||||
|
|
||||||
def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
||||||
"""Render an image using Ghostscript"""
|
"""Render an image using Ghostscript"""
|
||||||
|
global gs_binary
|
||||||
|
if not has_ghostscript():
|
||||||
|
msg = "Unable to locate Ghostscript on paths"
|
||||||
|
raise OSError(msg)
|
||||||
|
|
||||||
# Unpack decoder tile
|
# Unpack decoder tile
|
||||||
decoder, tile, offset, data = tile[0]
|
decoder, tile, offset, data = tile[0]
|
||||||
|
@ -113,7 +119,7 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
||||||
|
|
||||||
# Build Ghostscript command
|
# Build Ghostscript command
|
||||||
command = [
|
command = [
|
||||||
"gs",
|
gs_binary,
|
||||||
"-q", # quiet mode
|
"-q", # quiet mode
|
||||||
"-g%dx%d" % size, # set output geometry (pixels)
|
"-g%dx%d" % size, # set output geometry (pixels)
|
||||||
"-r%fx%f" % res, # set input DPI (dots per inch)
|
"-r%fx%f" % res, # set input DPI (dots per inch)
|
||||||
|
@ -132,19 +138,6 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False):
|
||||||
"showpage",
|
"showpage",
|
||||||
]
|
]
|
||||||
|
|
||||||
if gs_windows_binary is not None:
|
|
||||||
if not gs_windows_binary:
|
|
||||||
try:
|
|
||||||
os.unlink(outfile)
|
|
||||||
if infile_temp:
|
|
||||||
os.unlink(infile_temp)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
msg = "Unable to locate Ghostscript on paths"
|
|
||||||
raise OSError(msg)
|
|
||||||
command[0] = gs_windows_binary
|
|
||||||
|
|
||||||
# push data through Ghostscript
|
# push data through Ghostscript
|
||||||
try:
|
try:
|
||||||
startupinfo = None
|
startupinfo = None
|
||||||
|
@ -227,13 +220,15 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
# go to offset - start of "%!PS"
|
# go to offset - start of "%!PS"
|
||||||
self.fp.seek(offset)
|
self.fp.seek(offset)
|
||||||
|
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
self._size = None
|
self._size = None
|
||||||
|
|
||||||
byte_arr = bytearray(255)
|
byte_arr = bytearray(255)
|
||||||
bytes_mv = memoryview(byte_arr)
|
bytes_mv = memoryview(byte_arr)
|
||||||
bytes_read = 0
|
bytes_read = 0
|
||||||
reading_comments = True
|
reading_header_comments = True
|
||||||
|
reading_trailer_comments = False
|
||||||
|
trailer_reached = False
|
||||||
|
|
||||||
def check_required_header_comments():
|
def check_required_header_comments():
|
||||||
if "PS-Adobe" not in self.info:
|
if "PS-Adobe" not in self.info:
|
||||||
|
@ -243,6 +238,36 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
msg = 'EPS header missing "%%BoundingBox" comment'
|
msg = 'EPS header missing "%%BoundingBox" comment'
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
|
|
||||||
|
def _read_comment(s):
|
||||||
|
nonlocal reading_trailer_comments
|
||||||
|
try:
|
||||||
|
m = split.match(s)
|
||||||
|
except re.error as e:
|
||||||
|
msg = "not an EPS file"
|
||||||
|
raise SyntaxError(msg) from e
|
||||||
|
|
||||||
|
if m:
|
||||||
|
k, v = m.group(1, 2)
|
||||||
|
self.info[k] = v
|
||||||
|
if k == "BoundingBox":
|
||||||
|
if v == "(atend)":
|
||||||
|
reading_trailer_comments = True
|
||||||
|
elif not self._size or (
|
||||||
|
trailer_reached and reading_trailer_comments
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
# Note: The DSC spec says that BoundingBox
|
||||||
|
# fields should be integers, but some drivers
|
||||||
|
# put floating point values there anyway.
|
||||||
|
box = [int(float(i)) for i in v.split()]
|
||||||
|
self._size = box[2] - box[0], box[3] - box[1]
|
||||||
|
self.tile = [
|
||||||
|
("eps", (0, 0) + self.size, offset, (length, box))
|
||||||
|
]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return True
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
byte = self.fp.read(1)
|
byte = self.fp.read(1)
|
||||||
if byte == b"":
|
if byte == b"":
|
||||||
|
@ -265,9 +290,9 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
msg = "not an EPS file"
|
msg = "not an EPS file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
else:
|
else:
|
||||||
if reading_comments:
|
if reading_header_comments:
|
||||||
check_required_header_comments()
|
check_required_header_comments()
|
||||||
reading_comments = False
|
reading_header_comments = False
|
||||||
# reset bytes_read so we can keep reading
|
# reset bytes_read so we can keep reading
|
||||||
# data until the end of the line
|
# data until the end of the line
|
||||||
bytes_read = 0
|
bytes_read = 0
|
||||||
|
@ -275,7 +300,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
bytes_read += 1
|
bytes_read += 1
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if reading_comments:
|
if reading_header_comments:
|
||||||
# Load EPS header
|
# Load EPS header
|
||||||
|
|
||||||
# if this line doesn't start with a "%",
|
# if this line doesn't start with a "%",
|
||||||
|
@ -283,33 +308,11 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
# then we've reached the end of the header/comments
|
# then we've reached the end of the header/comments
|
||||||
if byte_arr[0] != ord("%") or bytes_mv[:13] == b"%%EndComments":
|
if byte_arr[0] != ord("%") or bytes_mv[:13] == b"%%EndComments":
|
||||||
check_required_header_comments()
|
check_required_header_comments()
|
||||||
reading_comments = False
|
reading_header_comments = False
|
||||||
continue
|
continue
|
||||||
|
|
||||||
s = str(bytes_mv[:bytes_read], "latin-1")
|
s = str(bytes_mv[:bytes_read], "latin-1")
|
||||||
|
if not _read_comment(s):
|
||||||
try:
|
|
||||||
m = split.match(s)
|
|
||||||
except re.error as e:
|
|
||||||
msg = "not an EPS file"
|
|
||||||
raise SyntaxError(msg) from e
|
|
||||||
|
|
||||||
if m:
|
|
||||||
k, v = m.group(1, 2)
|
|
||||||
self.info[k] = v
|
|
||||||
if k == "BoundingBox":
|
|
||||||
try:
|
|
||||||
# Note: The DSC spec says that BoundingBox
|
|
||||||
# fields should be integers, but some drivers
|
|
||||||
# put floating point values there anyway.
|
|
||||||
box = [int(float(i)) for i in v.split()]
|
|
||||||
self._size = box[2] - box[0], box[3] - box[1]
|
|
||||||
self.tile = [
|
|
||||||
("eps", (0, 0) + self.size, offset, (length, box))
|
|
||||||
]
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
m = field.match(s)
|
m = field.match(s)
|
||||||
if m:
|
if m:
|
||||||
k = m.group(1)
|
k = m.group(1)
|
||||||
|
@ -339,15 +342,15 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
# data start identifier (the image data follows after a single line
|
# data start identifier (the image data follows after a single line
|
||||||
# consisting only of this quoted value)
|
# consisting only of this quoted value)
|
||||||
image_data_values = byte_arr[11:bytes_read].split(None, 7)
|
image_data_values = byte_arr[11:bytes_read].split(None, 7)
|
||||||
columns, rows, bit_depth, mode_id = [
|
columns, rows, bit_depth, mode_id = (
|
||||||
int(value) for value in image_data_values[:4]
|
int(value) for value in image_data_values[:4]
|
||||||
]
|
)
|
||||||
|
|
||||||
if bit_depth == 1:
|
if bit_depth == 1:
|
||||||
self.mode = "1"
|
self._mode = "1"
|
||||||
elif bit_depth == 8:
|
elif bit_depth == 8:
|
||||||
try:
|
try:
|
||||||
self.mode = self.mode_map[mode_id]
|
self._mode = self.mode_map[mode_id]
|
||||||
except ValueError:
|
except ValueError:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -355,7 +358,18 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self._size = columns, rows
|
self._size = columns, rows
|
||||||
return
|
return
|
||||||
|
elif trailer_reached and reading_trailer_comments:
|
||||||
|
# Load EPS trailer
|
||||||
|
|
||||||
|
# if this line starts with "%%EOF",
|
||||||
|
# then we've reached the end of the file
|
||||||
|
if bytes_mv[:5] == b"%%EOF":
|
||||||
|
break
|
||||||
|
|
||||||
|
s = str(bytes_mv[:bytes_read], "latin-1")
|
||||||
|
_read_comment(s)
|
||||||
|
elif bytes_mv[:9] == b"%%Trailer":
|
||||||
|
trailer_reached = True
|
||||||
bytes_read = 0
|
bytes_read = 0
|
||||||
|
|
||||||
check_required_header_comments()
|
check_required_header_comments()
|
||||||
|
@ -391,7 +405,7 @@ class EpsImageFile(ImageFile.ImageFile):
|
||||||
# Load EPS via Ghostscript
|
# Load EPS via Ghostscript
|
||||||
if self.tile:
|
if self.tile:
|
||||||
self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
|
self.im = Ghostscript(self.tile, self.size, self.fp, scale, transparency)
|
||||||
self.mode = self.im.mode
|
self._mode = self.im.mode
|
||||||
self._size = self.im.size
|
self._size = self.im.size
|
||||||
self.tile = []
|
self.tile = []
|
||||||
return Image.Image.load(self)
|
return Image.Image.load(self)
|
||||||
|
|
|
@ -51,14 +51,14 @@ class FitsImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
number_of_bits = int(headers[b"BITPIX"])
|
number_of_bits = int(headers[b"BITPIX"])
|
||||||
if number_of_bits == 8:
|
if number_of_bits == 8:
|
||||||
self.mode = "L"
|
self._mode = "L"
|
||||||
elif number_of_bits == 16:
|
elif number_of_bits == 16:
|
||||||
self.mode = "I"
|
self._mode = "I"
|
||||||
# rawmode = "I;16S"
|
# rawmode = "I;16S"
|
||||||
elif number_of_bits == 32:
|
elif number_of_bits == 32:
|
||||||
self.mode = "I"
|
self._mode = "I"
|
||||||
elif number_of_bits in (-32, -64):
|
elif number_of_bits in (-32, -64):
|
||||||
self.mode = "F"
|
self._mode = "F"
|
||||||
# rawmode = "F" if number_of_bits == -32 else "F;64F"
|
# rawmode = "F" if number_of_bits == -32 else "F;64F"
|
||||||
|
|
||||||
offset = math.ceil(self.fp.tell() / 2880) * 2880
|
offset = math.ceil(self.fp.tell() / 2880) * 2880
|
||||||
|
|
|
@ -56,7 +56,7 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
self.is_animated = self.n_frames > 1
|
self.is_animated = self.n_frames > 1
|
||||||
|
|
||||||
# image characteristics
|
# image characteristics
|
||||||
self.mode = "P"
|
self._mode = "P"
|
||||||
self._size = i16(s, 8), i16(s, 10)
|
self._size = i16(s, 8), i16(s, 10)
|
||||||
|
|
||||||
# animation speed
|
# animation speed
|
||||||
|
|
|
@ -106,7 +106,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
# note: for now, we ignore the "uncalibrated" flag
|
# note: for now, we ignore the "uncalibrated" flag
|
||||||
colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
|
colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
|
||||||
|
|
||||||
self.mode, self.rawmode = MODES[tuple(colors)]
|
self._mode, self.rawmode = MODES[tuple(colors)]
|
||||||
|
|
||||||
# load JPEG tables, if any
|
# load JPEG tables, if any
|
||||||
self.jpeg = {}
|
self.jpeg = {}
|
||||||
|
|
|
@ -77,7 +77,7 @@ class FtexImageFile(ImageFile.ImageFile):
|
||||||
self._size = struct.unpack("<2i", self.fp.read(8))
|
self._size = struct.unpack("<2i", self.fp.read(8))
|
||||||
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
|
mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
|
||||||
|
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
|
|
||||||
# Only support single-format files.
|
# Only support single-format files.
|
||||||
# I don't know of any multi-format file.
|
# I don't know of any multi-format file.
|
||||||
|
@ -90,7 +90,7 @@ class FtexImageFile(ImageFile.ImageFile):
|
||||||
data = self.fp.read(mipmap_size)
|
data = self.fp.read(mipmap_size)
|
||||||
|
|
||||||
if format == Format.DXT1:
|
if format == Format.DXT1:
|
||||||
self.mode = "RGBA"
|
self._mode = "RGBA"
|
||||||
self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
|
self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
|
||||||
elif format == Format.UNCOMPRESSED:
|
elif format == Format.UNCOMPRESSED:
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
|
self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
|
||||||
|
|
|
@ -73,9 +73,9 @@ class GbrImageFile(ImageFile.ImageFile):
|
||||||
comment = self.fp.read(comment_length)[:-1]
|
comment = self.fp.read(comment_length)[:-1]
|
||||||
|
|
||||||
if color_depth == 1:
|
if color_depth == 1:
|
||||||
self.mode = "L"
|
self._mode = "L"
|
||||||
else:
|
else:
|
||||||
self.mode = "RGBA"
|
self._mode = "RGBA"
|
||||||
|
|
||||||
self._size = width, height
|
self._size = width, height
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ class GdImageFile(ImageFile.ImageFile):
|
||||||
msg = "Not a valid GD 2.x .gd file"
|
msg = "Not a valid GD 2.x .gd file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
|
|
||||||
self.mode = "L" # FIXME: "P"
|
self._mode = "L" # FIXME: "P"
|
||||||
self._size = i16(s, 2), i16(s, 4)
|
self._size = i16(s, 2), i16(s, 4)
|
||||||
|
|
||||||
true_color = s[6]
|
true_color = s[6]
|
||||||
|
|
|
@ -304,11 +304,11 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
if frame == 0:
|
if frame == 0:
|
||||||
if self._frame_palette:
|
if self._frame_palette:
|
||||||
if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
if LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
||||||
self.mode = "RGBA" if frame_transparency is not None else "RGB"
|
self._mode = "RGBA" if frame_transparency is not None else "RGB"
|
||||||
else:
|
else:
|
||||||
self.mode = "P"
|
self._mode = "P"
|
||||||
else:
|
else:
|
||||||
self.mode = "L"
|
self._mode = "L"
|
||||||
|
|
||||||
if not palette and self.global_palette:
|
if not palette and self.global_palette:
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
@ -325,10 +325,10 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
if "transparency" in self.info:
|
if "transparency" in self.info:
|
||||||
self.im.putpalettealpha(self.info["transparency"], 0)
|
self.im.putpalettealpha(self.info["transparency"], 0)
|
||||||
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
|
self.im = self.im.convert("RGBA", Image.Dither.FLOYDSTEINBERG)
|
||||||
self.mode = "RGBA"
|
self._mode = "RGBA"
|
||||||
del self.info["transparency"]
|
del self.info["transparency"]
|
||||||
else:
|
else:
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
|
self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG)
|
||||||
|
|
||||||
def _rgb(color):
|
def _rgb(color):
|
||||||
|
@ -424,7 +424,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
self.im.putpalette(*self._frame_palette.getdata())
|
self.im.putpalette(*self._frame_palette.getdata())
|
||||||
else:
|
else:
|
||||||
self.im = None
|
self.im = None
|
||||||
self.mode = temp_mode
|
self._mode = temp_mode
|
||||||
self._frame_palette = None
|
self._frame_palette = None
|
||||||
|
|
||||||
super().load_prepare()
|
super().load_prepare()
|
||||||
|
@ -434,9 +434,9 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS:
|
||||||
if self._frame_transparency is not None:
|
if self._frame_transparency is not None:
|
||||||
self.im.putpalettealpha(self._frame_transparency, 0)
|
self.im.putpalettealpha(self._frame_transparency, 0)
|
||||||
self.mode = "RGBA"
|
self._mode = "RGBA"
|
||||||
else:
|
else:
|
||||||
self.mode = "RGB"
|
self._mode = "RGB"
|
||||||
self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG)
|
self.im = self.im.convert(self.mode, Image.Dither.FLOYDSTEINBERG)
|
||||||
return
|
return
|
||||||
if not self._prev_im:
|
if not self._prev_im:
|
||||||
|
@ -449,7 +449,7 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
frame_im = self._crop(frame_im, self.dispose_extent)
|
frame_im = self._crop(frame_im, self.dispose_extent)
|
||||||
|
|
||||||
self.im = self._prev_im
|
self.im = self._prev_im
|
||||||
self.mode = self.im.mode
|
self._mode = self.im.mode
|
||||||
if frame_im.mode == "RGBA":
|
if frame_im.mode == "RGBA":
|
||||||
self.im.paste(frame_im, self.dispose_extent, frame_im)
|
self.im.paste(frame_im, self.dispose_extent, frame_im)
|
||||||
else:
|
else:
|
||||||
|
@ -683,11 +683,7 @@ def get_interlace(im):
|
||||||
def _write_local_header(fp, im, offset, flags):
|
def _write_local_header(fp, im, offset, flags):
|
||||||
transparent_color_exists = False
|
transparent_color_exists = False
|
||||||
try:
|
try:
|
||||||
if "transparency" in im.encoderinfo:
|
transparency = int(im.encoderinfo["transparency"])
|
||||||
transparency = im.encoderinfo["transparency"]
|
|
||||||
else:
|
|
||||||
transparency = im.info["transparency"]
|
|
||||||
transparency = int(transparency)
|
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -916,7 +912,7 @@ def _get_global_header(im, info):
|
||||||
info
|
info
|
||||||
and (
|
and (
|
||||||
"transparency" in info
|
"transparency" in info
|
||||||
or "loop" in info
|
or info.get("loop") is not None
|
||||||
or info.get("duration")
|
or info.get("duration")
|
||||||
or info.get("comment")
|
or info.get("comment")
|
||||||
)
|
)
|
||||||
|
@ -941,7 +937,7 @@ def _get_global_header(im, info):
|
||||||
# Global Color Table
|
# Global Color Table
|
||||||
_get_header_palette(palette_bytes),
|
_get_header_palette(palette_bytes),
|
||||||
]
|
]
|
||||||
if "loop" in info:
|
if info.get("loop") is not None:
|
||||||
header.append(
|
header.append(
|
||||||
b"!"
|
b"!"
|
||||||
+ o8(255) # extension intro
|
+ o8(255) # extension intro
|
||||||
|
|
|
@ -46,7 +46,7 @@ class GribStubImageFile(ImageFile.StubImageFile):
|
||||||
self.fp.seek(offset)
|
self.fp.seek(offset)
|
||||||
|
|
||||||
# make something up
|
# make something up
|
||||||
self.mode = "F"
|
self._mode = "F"
|
||||||
self._size = 1, 1
|
self._size = 1, 1
|
||||||
|
|
||||||
loader = self._load()
|
loader = self._load()
|
||||||
|
|
|
@ -46,7 +46,7 @@ class HDF5StubImageFile(ImageFile.StubImageFile):
|
||||||
self.fp.seek(offset)
|
self.fp.seek(offset)
|
||||||
|
|
||||||
# make something up
|
# make something up
|
||||||
self.mode = "F"
|
self._mode = "F"
|
||||||
self._size = 1, 1
|
self._size = 1, 1
|
||||||
|
|
||||||
loader = self._load()
|
loader = self._load()
|
||||||
|
|
|
@ -253,7 +253,7 @@ class IcnsImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
def _open(self):
|
def _open(self):
|
||||||
self.icns = IcnsFile(self.fp)
|
self.icns = IcnsFile(self.fp)
|
||||||
self.mode = "RGBA"
|
self._mode = "RGBA"
|
||||||
self.info["sizes"] = self.icns.itersizes()
|
self.info["sizes"] = self.icns.itersizes()
|
||||||
self.best_size = self.icns.bestsize()
|
self.best_size = self.icns.bestsize()
|
||||||
self.size = (
|
self.size = (
|
||||||
|
@ -305,7 +305,7 @@ class IcnsImageFile(ImageFile.ImageFile):
|
||||||
px = im.load()
|
px = im.load()
|
||||||
|
|
||||||
self.im = im.im
|
self.im = im.im
|
||||||
self.mode = im.mode
|
self._mode = im.mode
|
||||||
self.size = im.size
|
self.size = im.size
|
||||||
|
|
||||||
return px
|
return px
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user