mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-10 16:22:22 +03:00
Merge branch 'main' into jxl-support2
This commit is contained in:
commit
ece406575c
|
@ -66,7 +66,7 @@ if [[ $(uname) != CYGWIN* ]]; then
|
||||||
pushd depends && ./install_raqm.sh && popd
|
pushd depends && ./install_raqm.sh && popd
|
||||||
|
|
||||||
# libavif
|
# libavif
|
||||||
pushd depends && CMAKE_POLICY_VERSION_MINIMUM=3.5 ./install_libavif.sh && popd
|
pushd depends && ./install_libavif.sh && popd
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
cibuildwheel==2.23.2
|
cibuildwheel==3.0.0
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
mypy==1.15.0
|
mypy==1.16.1
|
||||||
IceSpringPySideStubs-PyQt6
|
IceSpringPySideStubs-PyQt6
|
||||||
IceSpringPySideStubs-PySide6
|
IceSpringPySideStubs-PySide6
|
||||||
ipython
|
ipython
|
||||||
numpy
|
numpy
|
||||||
packaging
|
packaging
|
||||||
|
pyarrow-stubs
|
||||||
pytest
|
pytest
|
||||||
sphinx
|
sphinx
|
||||||
types-atheris
|
types-atheris
|
||||||
|
|
46
.github/ISSUE_TEMPLATE/RELEASE.md
vendored
Normal file
46
.github/ISSUE_TEMPLATE/RELEASE.md
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
name: "Maintainers only: Release"
|
||||||
|
about: For maintainers to schedule a quarterly release
|
||||||
|
labels: Release
|
||||||
|
---
|
||||||
|
|
||||||
|
## Main release
|
||||||
|
|
||||||
|
Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
||||||
|
|
||||||
|
* [ ] Open a release ticket e.g. https://github.com/python-pillow/Pillow/issues/3154
|
||||||
|
* [ ] Develop and prepare release in `main` branch.
|
||||||
|
* [ ] Add release notes e.g. https://github.com/python-pillow/Pillow/pull/8885
|
||||||
|
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in `main` branch.
|
||||||
|
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
|
||||||
|
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
||||||
|
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
||||||
|
* [ ] Create branch and tag for release e.g.:
|
||||||
|
```bash
|
||||||
|
git branch [[MAJOR.MINOR]].x
|
||||||
|
git tag [[MAJOR.MINOR]].0
|
||||||
|
git push --tags
|
||||||
|
```
|
||||||
|
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) has passed, including the "Upload release to PyPI" job. This will have been triggered by the new tag.
|
||||||
|
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases).
|
||||||
|
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
||||||
|
```bash
|
||||||
|
git push --all
|
||||||
|
```
|
||||||
|
|
||||||
|
## Publicize release
|
||||||
|
|
||||||
|
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
|
||||||
|
|
||||||
|
## Docker images
|
||||||
|
|
||||||
|
* [ ] Update Pillow in the Docker Images repository
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/python-pillow/docker-images
|
||||||
|
cd docker-images
|
||||||
|
./update-pillow-tag.sh [[release tag]]
|
||||||
|
```
|
60
.github/workflows/test-valgrind-memory.yml
vendored
Normal file
60
.github/workflows/test-valgrind-memory.yml
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
name: Test Valgrind Memory Leaks
|
||||||
|
|
||||||
|
# like the Docker tests, but running valgrind only on *.c/*.h changes.
|
||||||
|
|
||||||
|
# this is very expensive. Only run on the pull request.
|
||||||
|
on:
|
||||||
|
# push:
|
||||||
|
# branches:
|
||||||
|
# - "**"
|
||||||
|
# paths:
|
||||||
|
# - ".github/workflows/test-valgrind.yml"
|
||||||
|
# - "**.c"
|
||||||
|
# - "**.h"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/test-valgrind.yml"
|
||||||
|
- "**.c"
|
||||||
|
- "**.h"
|
||||||
|
- "depends/docker-test-valgrind-memory.sh"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
docker: [
|
||||||
|
ubuntu-22.04-jammy-amd64-valgrind,
|
||||||
|
]
|
||||||
|
dockerTag: [main]
|
||||||
|
|
||||||
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Build system information
|
||||||
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
|
- name: Docker pull
|
||||||
|
run: |
|
||||||
|
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
||||||
|
|
||||||
|
- name: Build and Run Valgrind
|
||||||
|
run: |
|
||||||
|
# The Pillow user in the docker container is UID 1001
|
||||||
|
sudo chown -R 1001 $GITHUB_WORKSPACE
|
||||||
|
docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} /Pillow/depends/docker-test-valgrind-memory.sh
|
||||||
|
sudo chown -R runner $GITHUB_WORKSPACE
|
13
.github/workflows/test-windows.yml
vendored
13
.github/workflows/test-windows.yml
vendored
|
@ -31,16 +31,15 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", ">=3.13.5", "3.14"]
|
||||||
architecture: ["x64"]
|
architecture: ["x64"]
|
||||||
os: ["windows-latest"]
|
|
||||||
include:
|
include:
|
||||||
# Test the oldest Python on 32-bit
|
# Test the oldest Python on 32-bit
|
||||||
- { python-version: "3.9", architecture: "x86", os: "windows-2019" }
|
- { python-version: "3.9", architecture: "x86" }
|
||||||
|
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
|
|
||||||
|
@ -84,7 +83,7 @@ jobs:
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
|
|
||||||
- name: Install CPython dependencies
|
- name: Install CPython dependencies
|
||||||
if: "!contains(matrix.python-version, 'pypy') && matrix.architecture != 'x86'"
|
if: "!contains(matrix.python-version, 'pypy') && !contains(matrix.python-version, '3.14') && matrix.architecture != 'x86'"
|
||||||
run: |
|
run: |
|
||||||
python3 -m pip install PyQt6
|
python3 -m pip install PyQt6
|
||||||
|
|
||||||
|
@ -98,8 +97,8 @@ jobs:
|
||||||
choco install nasm --no-progress
|
choco install nasm --no-progress
|
||||||
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
choco install ghostscript --version=10.5.0 --no-progress
|
choco install ghostscript --version=10.5.1 --no-progress
|
||||||
echo "C:\Program Files\gs\gs10.05.0\bin" >> $env:GITHUB_PATH
|
echo "C:\Program Files\gs\gs10.05.1\bin" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
# Install extra test images
|
# Install extra test images
|
||||||
xcopy /S /Y Tests\test-images\* Tests\images
|
xcopy /S /Y Tests\test-images\* Tests\images
|
||||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -43,6 +43,7 @@ jobs:
|
||||||
python-version: [
|
python-version: [
|
||||||
"pypy3.11",
|
"pypy3.11",
|
||||||
"pypy3.10",
|
"pypy3.10",
|
||||||
|
"3.14t",
|
||||||
"3.14",
|
"3.14",
|
||||||
"3.13t",
|
"3.13t",
|
||||||
"3.13",
|
"3.13",
|
||||||
|
@ -55,6 +56,7 @@ jobs:
|
||||||
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
- { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" }
|
||||||
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
|
- { python-version: "3.10", PYTHONOPTIMIZE: 2 }
|
||||||
# Free-threaded
|
# Free-threaded
|
||||||
|
- { python-version: "3.14t", disable-gil: true }
|
||||||
- { python-version: "3.13t", disable-gil: true }
|
- { python-version: "3.13t", disable-gil: true }
|
||||||
# M1 only available for 3.10+
|
# M1 only available for 3.10+
|
||||||
- { os: "macos-13", python-version: "3.9" }
|
- { os: "macos-13", python-version: "3.9" }
|
||||||
|
|
6
.github/workflows/wheels-dependencies.sh
vendored
6
.github/workflows/wheels-dependencies.sh
vendored
|
@ -38,9 +38,9 @@ ARCHIVE_SDIR=pillow-depends-main
|
||||||
|
|
||||||
# Package versions for fresh source builds
|
# Package versions for fresh source builds
|
||||||
FREETYPE_VERSION=2.13.3
|
FREETYPE_VERSION=2.13.3
|
||||||
HARFBUZZ_VERSION=11.1.0
|
HARFBUZZ_VERSION=11.2.1
|
||||||
LIBPNG_VERSION=1.6.47
|
LIBPNG_VERSION=1.6.49
|
||||||
JPEGTURBO_VERSION=3.1.0
|
JPEGTURBO_VERSION=3.1.1
|
||||||
OPENJPEG_VERSION=2.5.3
|
OPENJPEG_VERSION=2.5.3
|
||||||
XZ_VERSION=5.8.1
|
XZ_VERSION=5.8.1
|
||||||
TIFF_VERSION=4.7.0
|
TIFF_VERSION=4.7.0
|
||||||
|
|
16
.github/workflows/wheels-test.ps1
vendored
16
.github/workflows/wheels-test.ps1
vendored
|
@ -9,17 +9,21 @@ if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
|
||||||
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
|
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
|
||||||
}
|
}
|
||||||
$env:path += ";$pillow\winbuild\build\bin\"
|
$env:path += ";$pillow\winbuild\build\bin\"
|
||||||
& "$venv\Scripts\activate.ps1"
|
if (Test-Path $venv\Scripts\pypy.exe) {
|
||||||
|
$python = "pypy.exe"
|
||||||
|
} else {
|
||||||
|
$python = "python.exe"
|
||||||
|
}
|
||||||
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
|
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
|
||||||
if ("$venv" -like "*\cibw-run-*-win_amd64\*") {
|
if ("$venv" -like "*\cibw-run-*-win_amd64\*") {
|
||||||
& python -m pip install numpy
|
& $venv\Scripts\$python -m pip install numpy
|
||||||
}
|
}
|
||||||
cd $pillow
|
cd $pillow
|
||||||
& python -VV
|
& $venv\Scripts\$python -VV
|
||||||
if (!$?) { exit $LASTEXITCODE }
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
& python selftest.py
|
& $venv\Scripts\$python selftest.py
|
||||||
if (!$?) { exit $LASTEXITCODE }
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
& python -m pytest -vx Tests\check_wheel.py
|
& $venv\Scripts\$python -m pytest -vx Tests\check_wheel.py
|
||||||
if (!$?) { exit $LASTEXITCODE }
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
& python -m pytest -vx Tests
|
& $venv\Scripts\$python -m pytest -vx Tests
|
||||||
if (!$?) { exit $LASTEXITCODE }
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
|
|
4
.github/workflows/wheels.yml
vendored
4
.github/workflows/wheels.yml
vendored
|
@ -58,7 +58,7 @@ jobs:
|
||||||
- name: "macOS 10.13 x86_64"
|
- name: "macOS 10.13 x86_64"
|
||||||
os: macos-13
|
os: macos-13
|
||||||
cibw_arch: x86_64
|
cibw_arch: x86_64
|
||||||
build: "cp3{12,13}*"
|
build: "cp3{12,13,14}*"
|
||||||
macosx_deployment_target: "10.13"
|
macosx_deployment_target: "10.13"
|
||||||
- name: "macOS 10.15 x86_64"
|
- name: "macOS 10.15 x86_64"
|
||||||
os: macos-13
|
os: macos-13
|
||||||
|
@ -110,7 +110,6 @@ jobs:
|
||||||
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }}
|
CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.manylinux }}
|
||||||
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||||
CIBW_SKIP: pp39-*
|
|
||||||
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }}
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
|
@ -188,7 +187,6 @@ jobs:
|
||||||
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
||||||
CIBW_CACHE_PATH: "C:\\cibw"
|
CIBW_CACHE_PATH: "C:\\cibw"
|
||||||
CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
|
CIBW_ENABLE: cpython-prerelease cpython-freethreading pypy
|
||||||
CIBW_SKIP: pp39-*
|
|
||||||
CIBW_TEST_SKIP: "*-win_arm64"
|
CIBW_TEST_SKIP: "*-win_arm64"
|
||||||
CIBW_TEST_COMMAND: 'docker run --rm
|
CIBW_TEST_COMMAND: 'docker run --rm
|
||||||
-v {project}:C:\pillow
|
-v {project}:C:\pillow
|
||||||
|
|
7
.github/zizmor.yml
vendored
Normal file
7
.github/zizmor.yml
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Configuration for the zizmor static analysis tool, run via pre-commit in CI
|
||||||
|
# https://woodruffw.github.io/zizmor/configuration/
|
||||||
|
rules:
|
||||||
|
unpinned-uses:
|
||||||
|
config:
|
||||||
|
policies:
|
||||||
|
"*": ref-pin
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.11.4
|
rev: v0.11.12
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--exit-non-zero-on-fix]
|
args: [--exit-non-zero-on-fix]
|
||||||
|
@ -24,7 +24,7 @@ repos:
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: v20.1.0
|
rev: v20.1.5
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
types: [c]
|
types: [c]
|
||||||
|
@ -51,14 +51,14 @@ repos:
|
||||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||||
|
|
||||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||||
rev: 0.32.1
|
rev: 0.33.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-github-workflows
|
- id: check-github-workflows
|
||||||
- id: check-readthedocs
|
- id: check-readthedocs
|
||||||
- id: check-renovate
|
- id: check-renovate
|
||||||
|
|
||||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||||
rev: v1.5.2
|
rev: v1.9.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: zizmor
|
- id: zizmor
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ repos:
|
||||||
- id: sphinx-lint
|
- id: sphinx-lint
|
||||||
|
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
rev: v2.5.1
|
rev: v2.6.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
|
|
||||||
|
|
16
Makefile
16
Makefile
|
@ -97,13 +97,27 @@ test:
|
||||||
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
|
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
|
||||||
python3 -m pytest -qq
|
python3 -m pytest -qq
|
||||||
|
|
||||||
|
.PHONY: test-p
|
||||||
|
test-p:
|
||||||
|
python3 -c "import xdist" > /dev/null 2>&1 || python3 -m pip install pytest-xdist
|
||||||
|
python3 -m pytest -qq -n auto
|
||||||
|
|
||||||
|
|
||||||
.PHONY: valgrind
|
.PHONY: valgrind
|
||||||
valgrind:
|
valgrind:
|
||||||
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
|
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
|
||||||
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
|
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
|
||||||
--log-file=/tmp/valgrind-output \
|
--log-file=/tmp/valgrind-output \
|
||||||
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
||||||
|
|
||||||
|
.PHONY: valgrind-leak
|
||||||
|
valgrind-leak:
|
||||||
|
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
|
||||||
|
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp \
|
||||||
|
--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite \
|
||||||
|
--log-file=/tmp/valgrind-output \
|
||||||
|
python3 -m pytest -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
||||||
|
|
||||||
.PHONY: readme
|
.PHONY: readme
|
||||||
readme:
|
readme:
|
||||||
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2
|
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2
|
||||||
|
|
|
@ -95,7 +95,7 @@ This library provides extensive file format support, an efficient internal repre
|
||||||
|
|
||||||
The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool.
|
The core image library is designed for fast access to data stored in a few basic pixel formats. It should provide a solid foundation for a general image processing tool.
|
||||||
|
|
||||||
## More Information
|
## More information
|
||||||
|
|
||||||
- [Documentation](https://pillow.readthedocs.io/)
|
- [Documentation](https://pillow.readthedocs.io/)
|
||||||
- [Installation](https://pillow.readthedocs.io/en/latest/installation/basic-installation.html)
|
- [Installation](https://pillow.readthedocs.io/en/latest/installation/basic-installation.html)
|
||||||
|
@ -107,6 +107,6 @@ The core image library is designed for fast access to data stored in a few basic
|
||||||
- [Changelog](https://github.com/python-pillow/Pillow/releases)
|
- [Changelog](https://github.com/python-pillow/Pillow/releases)
|
||||||
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
|
- [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork)
|
||||||
|
|
||||||
## Report a Vulnerability
|
## Report a vulnerability
|
||||||
|
|
||||||
To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security).
|
To report a security vulnerability, please follow the procedure described in the [Tidelift security policy](https://tidelift.com/docs/security).
|
||||||
|
|
35
RELEASING.md
35
RELEASING.md
|
@ -1,34 +1,15 @@
|
||||||
# Release Checklist
|
# Release checklist
|
||||||
|
|
||||||
See https://pillow.readthedocs.io/en/stable/releasenotes/versioning.html for
|
See https://pillow.readthedocs.io/en/stable/releasenotes/versioning.html for
|
||||||
information about how the version numbers line up with releases.
|
information about how the version numbers line up with releases.
|
||||||
|
|
||||||
## Main Release
|
## Main release
|
||||||
|
|
||||||
Released quarterly on January 2nd, April 1st, July 1st and October 15th.
|
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
|
* [ ] Create a new issue and select the "Maintainers only: Release" template.
|
||||||
* [ ] Develop and prepare release in `main` branch.
|
|
||||||
* [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) to confirm passing tests in `main` branch.
|
## Point release
|
||||||
* [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them.
|
|
||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py`
|
|
||||||
* [ ] Run pre-release check via `make release-test` in a freshly cloned repo.
|
|
||||||
* [ ] Create branch and tag for release e.g.:
|
|
||||||
```bash
|
|
||||||
git branch 5.2.x
|
|
||||||
git tag 5.2.0
|
|
||||||
git push --tags
|
|
||||||
```
|
|
||||||
* [ ] Check the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
|
||||||
has passed, including the "Upload release to PyPI" job. This will have been triggered
|
|
||||||
by the new tag.
|
|
||||||
* [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases).
|
|
||||||
* [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/),
|
|
||||||
increment and append `.dev0` to version identifier in `src/PIL/_version.py` and then:
|
|
||||||
```bash
|
|
||||||
git push --all
|
|
||||||
```
|
|
||||||
## Point Release
|
|
||||||
|
|
||||||
Released as needed for security, installation or critical bug fixes.
|
Released as needed for security, installation or critical bug fixes.
|
||||||
|
|
||||||
|
@ -58,7 +39,7 @@ Released as needed for security, installation or critical bug fixes.
|
||||||
git push
|
git push
|
||||||
```
|
```
|
||||||
|
|
||||||
## Embargoed Release
|
## Embargoed release
|
||||||
|
|
||||||
Released as needed privately to individual vendors for critical security-related bug fixes.
|
Released as needed privately to individual vendors for critical security-related bug fixes.
|
||||||
|
|
||||||
|
@ -82,7 +63,7 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
git push origin 2.5.x
|
git push origin 2.5.x
|
||||||
```
|
```
|
||||||
|
|
||||||
## Publicize Release
|
## Publicize release
|
||||||
|
|
||||||
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
|
* [ ] Announce release availability via [Mastodon](https://fosstodon.org/@pillow) e.g. https://fosstodon.org/@pillow/110639450470725321
|
||||||
|
|
||||||
|
@ -90,7 +71,7 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
|
|
||||||
* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
|
* [ ] Make sure the [default version for Read the Docs](https://pillow.readthedocs.io/en/stable/) is up-to-date with the release changes
|
||||||
|
|
||||||
## Docker Images
|
## Docker images
|
||||||
|
|
||||||
* [ ] Update Pillow in the Docker Images repository
|
* [ ] Update Pillow in the Docker Images repository
|
||||||
```bash
|
```bash
|
||||||
|
|
|
@ -11,13 +11,14 @@ from .helper import is_pypy
|
||||||
def test_wheel_modules() -> None:
|
def test_wheel_modules() -> None:
|
||||||
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
|
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
|
||||||
|
|
||||||
# tkinter is not available in cibuildwheel installed CPython on Windows
|
if sys.platform == "win32":
|
||||||
try:
|
# tkinter is not available in cibuildwheel installed CPython on Windows
|
||||||
import tkinter
|
try:
|
||||||
|
import tkinter
|
||||||
|
|
||||||
assert tkinter
|
assert tkinter
|
||||||
except ImportError:
|
except ImportError:
|
||||||
expected_modules.remove("tkinter")
|
expected_modules.remove("tkinter")
|
||||||
|
|
||||||
assert set(features.get_supported_modules()) == expected_modules
|
assert set(features.get_supported_modules()) == expected_modules
|
||||||
|
|
||||||
|
|
|
@ -161,6 +161,12 @@ def assert_tuple_approx_equal(
|
||||||
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
|
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
|
||||||
|
|
||||||
|
|
||||||
|
def timeout_unless_slower_valgrind(timeout: float) -> pytest.MarkDecorator:
|
||||||
|
if "PILLOW_VALGRIND_TEST" in os.environ:
|
||||||
|
return pytest.mark.pil_noop_mark()
|
||||||
|
return pytest.mark.timeout(timeout)
|
||||||
|
|
||||||
|
|
||||||
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
||||||
reason = f"{feature} not available"
|
reason = f"{feature} not available"
|
||||||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||||
|
|
Binary file not shown.
BIN
Tests/images/op_index.qoi
Normal file
BIN
Tests/images/op_index.qoi
Normal file
Binary file not shown.
BIN
Tests/images/p_4_planes.pcx
Normal file
BIN
Tests/images/p_4_planes.pcx
Normal file
Binary file not shown.
|
@ -14,3 +14,23 @@
|
||||||
fun:_TIFFReadEncodedTileAndAllocBuffer
|
fun:_TIFFReadEncodedTileAndAllocBuffer
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
<python_alloc_possible_leak>
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: all
|
||||||
|
fun:malloc
|
||||||
|
fun:_PyMem_RawMalloc
|
||||||
|
fun:PyObject_Malloc
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
<python_realloc_possible_leak>
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: all
|
||||||
|
fun:malloc
|
||||||
|
fun:_PyMem_RawRealloc
|
||||||
|
fun:PyMem_Realloc
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
|
@ -47,7 +47,6 @@ def test_unknown_version() -> None:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
|
def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
|
||||||
expected = r""
|
|
||||||
with pytest.raises(RuntimeError, match=expected):
|
with pytest.raises(RuntimeError, match=expected):
|
||||||
_deprecate.deprecate(deprecated, 1, plural=plural)
|
_deprecate.deprecate(deprecated, 1, plural=plural)
|
||||||
|
|
||||||
|
|
|
@ -233,7 +233,7 @@ class TestFileAvif:
|
||||||
with Image.open(out_gif) as reread:
|
with Image.open(out_gif) as reread:
|
||||||
reread_value = reread.convert("RGB").getpixel((1, 1))
|
reread_value = reread.convert("RGB").getpixel((1, 1))
|
||||||
difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)])
|
difference = sum([abs(original_value[i] - reread_value[i]) for i in range(3)])
|
||||||
assert difference <= 3
|
assert difference <= 6
|
||||||
|
|
||||||
def test_save_single_frame(self, tmp_path: Path) -> None:
|
def test_save_single_frame(self, tmp_path: Path) -> None:
|
||||||
temp_file = tmp_path / "temp.avif"
|
temp_file = tmp_path / "temp.avif"
|
||||||
|
|
|
@ -7,9 +7,8 @@ import pytest
|
||||||
from PIL import BlpImagePlugin, Image
|
from PIL import BlpImagePlugin, Image
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
|
||||||
assert_image_equal_tofile,
|
assert_image_equal_tofile,
|
||||||
assert_image_similar,
|
assert_image_similar_tofile,
|
||||||
hopper,
|
hopper,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,18 +51,16 @@ def test_save(tmp_path: Path) -> None:
|
||||||
im = hopper("P")
|
im = hopper("P")
|
||||||
im.save(f, blp_version=version)
|
im.save(f, blp_version=version)
|
||||||
|
|
||||||
with Image.open(f) as reloaded:
|
assert_image_equal_tofile(im.convert("RGB"), f)
|
||||||
assert_image_equal(im.convert("RGB"), reloaded)
|
|
||||||
|
|
||||||
with Image.open("Tests/images/transparent.png") as im:
|
with Image.open("Tests/images/transparent.png") as im:
|
||||||
f = tmp_path / "temp.blp"
|
f = tmp_path / "temp.blp"
|
||||||
im.convert("P").save(f, blp_version=version)
|
im.convert("P").save(f, blp_version=version)
|
||||||
|
|
||||||
with Image.open(f) as reloaded:
|
assert_image_similar_tofile(im, f, 8)
|
||||||
assert_image_similar(im, reloaded, 8)
|
|
||||||
|
|
||||||
im = hopper()
|
im = hopper()
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError, match="Unsupported BLP image mode"):
|
||||||
im.save(f)
|
im.save(f)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ from .helper import (
|
||||||
is_win32,
|
is_win32,
|
||||||
mark_if_feature_version,
|
mark_if_feature_version,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
)
|
)
|
||||||
|
|
||||||
HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript()
|
HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript()
|
||||||
|
@ -398,7 +399,7 @@ def test_emptyline() -> None:
|
||||||
assert image.format == "EPS"
|
assert image.format == "EPS"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.timeout(timeout=5)
|
@timeout_unless_slower_valgrind(5)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_file",
|
"test_file",
|
||||||
["Tests/images/eps/timeout-d675703545fee17acab56e5fec644c19979175de.eps"],
|
["Tests/images/eps/timeout-d675703545fee17acab56e5fec644c19979175de.eps"],
|
||||||
|
|
|
@ -7,7 +7,12 @@ import pytest
|
||||||
|
|
||||||
from PIL import FliImagePlugin, Image, ImageFile
|
from PIL import FliImagePlugin, Image, ImageFile
|
||||||
|
|
||||||
from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy
|
from .helper import (
|
||||||
|
assert_image_equal,
|
||||||
|
assert_image_equal_tofile,
|
||||||
|
is_pypy,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
|
)
|
||||||
|
|
||||||
# created as an export of a palette image from Gimp2.6
|
# created as an export of a palette image from Gimp2.6
|
||||||
# save as...-> hopper.fli, default options.
|
# save as...-> hopper.fli, default options.
|
||||||
|
@ -189,7 +194,7 @@ def test_seek() -> None:
|
||||||
"Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli",
|
"Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.timeout(timeout=3)
|
@timeout_unless_slower_valgrind(3)
|
||||||
def test_timeouts(test_file: str) -> None:
|
def test_timeouts(test_file: str) -> None:
|
||||||
with open(test_file, "rb") as f:
|
with open(test_file, "rb") as f:
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
|
|
|
@ -32,6 +32,7 @@ from .helper import (
|
||||||
is_win32,
|
is_win32,
|
||||||
mark_if_feature_version,
|
mark_if_feature_version,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
)
|
)
|
||||||
|
|
||||||
ElementTree: ModuleType | None
|
ElementTree: ModuleType | None
|
||||||
|
@ -144,14 +145,16 @@ class TestFileJpeg:
|
||||||
assert k > 0.9
|
assert k > 0.9
|
||||||
# roundtrip, and check again
|
# roundtrip, and check again
|
||||||
im = self.roundtrip(im)
|
im = self.roundtrip(im)
|
||||||
c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))
|
cmyk = im.getpixel((0, 0))
|
||||||
|
assert isinstance(cmyk, tuple)
|
||||||
|
c, m, y, k = (x / 255.0 for x in cmyk)
|
||||||
assert c == 0.0
|
assert c == 0.0
|
||||||
assert m > 0.8
|
assert m > 0.8
|
||||||
assert y > 0.8
|
assert y > 0.8
|
||||||
assert k == 0.0
|
assert k == 0.0
|
||||||
c, m, y, k = (
|
cmyk = im.getpixel((im.size[0] - 1, im.size[1] - 1))
|
||||||
x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))
|
assert isinstance(cmyk, tuple)
|
||||||
)
|
k = cmyk[3] / 255.0
|
||||||
assert k > 0.9
|
assert k > 0.9
|
||||||
|
|
||||||
def test_rgb(self) -> None:
|
def test_rgb(self) -> None:
|
||||||
|
@ -1033,7 +1036,7 @@ class TestFileJpeg:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.save(f, xmp=b"1" * 65505)
|
im.save(f, xmp=b"1" * 65505)
|
||||||
|
|
||||||
@pytest.mark.timeout(timeout=1)
|
@timeout_unless_slower_valgrind(1)
|
||||||
def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
# Even though this decoder never says that it is finished
|
# Even though this decoder never says that it is finished
|
||||||
# the image should still end when there is no new data
|
# the image should still end when there is no new data
|
||||||
|
@ -1064,10 +1067,16 @@ class TestFileJpeg:
|
||||||
for marker in b"\xff\xd8", b"\xff\xd9":
|
for marker in b"\xff\xd8", b"\xff\xd9":
|
||||||
assert marker in data[1]
|
assert marker in data[1]
|
||||||
assert marker in data[2]
|
assert marker in data[2]
|
||||||
# DHT, DQT
|
|
||||||
for marker in b"\xff\xc4", b"\xff\xdb":
|
# DQT
|
||||||
|
markers = [b"\xff\xdb"]
|
||||||
|
if features.check_feature("libjpeg_turbo"):
|
||||||
|
# DHT
|
||||||
|
markers.append(b"\xff\xc4")
|
||||||
|
for marker in markers:
|
||||||
assert marker in data[1]
|
assert marker in data[1]
|
||||||
assert marker not in data[2]
|
assert marker not in data[2]
|
||||||
|
|
||||||
# SOF0, SOS, APP0 (JFIF header)
|
# SOF0, SOS, APP0 (JFIF header)
|
||||||
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
|
for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0":
|
||||||
assert marker not in data[1]
|
assert marker not in data[1]
|
||||||
|
|
|
@ -37,6 +37,11 @@ def test_sanity(tmp_path: Path) -> None:
|
||||||
im.save(f)
|
im.save(f)
|
||||||
|
|
||||||
|
|
||||||
|
def test_p_4_planes() -> None:
|
||||||
|
with Image.open("Tests/images/p_4_planes.pcx") as im:
|
||||||
|
assert im.getpixel((0, 0)) == 3
|
||||||
|
|
||||||
|
|
||||||
def test_bad_image_size() -> None:
|
def test_bad_image_size() -> None:
|
||||||
with open("Tests/images/pil184.pcx", "rb") as fp:
|
with open("Tests/images/pil184.pcx", "rb") as fp:
|
||||||
data = fp.read()
|
data = fp.read()
|
||||||
|
|
|
@ -13,7 +13,12 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, PdfParser, features
|
from PIL import Image, PdfParser, features
|
||||||
|
|
||||||
from .helper import hopper, mark_if_feature_version, skip_unless_feature
|
from .helper import (
|
||||||
|
hopper,
|
||||||
|
mark_if_feature_version,
|
||||||
|
skip_unless_feature,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str:
|
def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str:
|
||||||
|
@ -339,8 +344,7 @@ def test_pdf_append_to_bytesio() -> None:
|
||||||
assert len(f.getvalue()) > initial_size
|
assert len(f.getvalue()) > initial_size
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.timeout(1)
|
@timeout_unless_slower_valgrind(1)
|
||||||
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
|
|
||||||
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
|
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
|
||||||
def test_redos(newline: bytes) -> None:
|
def test_redos(newline: bytes) -> None:
|
||||||
malicious = b" trailer<<>>" + newline * 3456
|
malicious = b" trailer<<>>" + newline * 3456
|
||||||
|
|
|
@ -100,11 +100,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", "I;16B"]:
|
for mode in ["1", "L", "P", "RGB", "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 in ("I", "I;16B"):
|
if mode == "I;16B":
|
||||||
reloaded = reloaded.convert(mode)
|
reloaded = reloaded.convert(mode)
|
||||||
assert_image_equal(reloaded, im)
|
assert_image_equal(reloaded, im)
|
||||||
|
|
||||||
|
@ -801,6 +801,16 @@ class TestFilePng:
|
||||||
with Image.open("Tests/images/truncated_end_chunk.png") as im:
|
with Image.open("Tests/images/truncated_end_chunk.png") as im:
|
||||||
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
assert_image_equal_tofile(im, "Tests/images/hopper.png")
|
||||||
|
|
||||||
|
def test_deprecation(self, tmp_path: Path) -> None:
|
||||||
|
test_file = tmp_path / "out.png"
|
||||||
|
|
||||||
|
im = hopper("I")
|
||||||
|
with pytest.warns(DeprecationWarning):
|
||||||
|
im.save(test_file)
|
||||||
|
|
||||||
|
with Image.open(test_file) as reloaded:
|
||||||
|
assert_image_equal(im, reloaded.convert("I"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
|
@pytest.mark.skipif(is_win32(), reason="Requires Unix or macOS")
|
||||||
@skip_unless_feature("zlib")
|
@skip_unless_feature("zlib")
|
||||||
|
|
|
@ -288,14 +288,16 @@ def test_non_integer_token(tmp_path: Path) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_header_token_too_long(tmp_path: Path) -> None:
|
@pytest.mark.parametrize("data", (b"P3\x0cAAAAAAAAAA\xee", b"P6\n 01234567890"))
|
||||||
|
def test_header_token_too_long(tmp_path: Path, data: bytes) -> None:
|
||||||
path = tmp_path / "temp.ppm"
|
path = tmp_path / "temp.ppm"
|
||||||
with open(path, "wb") as f:
|
with open(path, "wb") as f:
|
||||||
f.write(b"P6\n 01234567890")
|
f.write(data)
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="Token too long in file header: 01234567890"):
|
with pytest.raises(ValueError) as e:
|
||||||
with Image.open(path):
|
with Image.open(path):
|
||||||
pass
|
pass
|
||||||
|
assert "Token too long in file header: " in repr(e)
|
||||||
|
|
||||||
|
|
||||||
def test_truncated_file(tmp_path: Path) -> None:
|
def test_truncated_file(tmp_path: Path) -> None:
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, QoiImagePlugin
|
from PIL import Image, QoiImagePlugin
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import assert_image_equal_tofile, hopper
|
||||||
|
|
||||||
|
|
||||||
def test_sanity() -> None:
|
def test_sanity() -> None:
|
||||||
|
@ -28,3 +30,28 @@ def test_invalid_file() -> None:
|
||||||
|
|
||||||
with pytest.raises(SyntaxError):
|
with pytest.raises(SyntaxError):
|
||||||
QoiImagePlugin.QoiImageFile(invalid_file)
|
QoiImagePlugin.QoiImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_op_index() -> None:
|
||||||
|
# QOI_OP_INDEX as the first chunk
|
||||||
|
with Image.open("Tests/images/op_index.qoi") as im:
|
||||||
|
assert im.getpixel((0, 0)) == (0, 0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_save(tmp_path: Path) -> None:
|
||||||
|
f = tmp_path / "temp.qoi"
|
||||||
|
|
||||||
|
im = hopper()
|
||||||
|
im.save(f, colorspace="sRGB")
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, f)
|
||||||
|
|
||||||
|
for path in ("Tests/images/default_font.png", "Tests/images/pil123rgba.png"):
|
||||||
|
with Image.open(path) as im:
|
||||||
|
im.save(f)
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, f)
|
||||||
|
|
||||||
|
im = hopper("P")
|
||||||
|
with pytest.raises(ValueError, match="Unsupported QOI image mode"):
|
||||||
|
im.save(f)
|
||||||
|
|
|
@ -14,6 +14,7 @@ from PIL import (
|
||||||
ImageFile,
|
ImageFile,
|
||||||
JpegImagePlugin,
|
JpegImagePlugin,
|
||||||
TiffImagePlugin,
|
TiffImagePlugin,
|
||||||
|
TiffTags,
|
||||||
UnidentifiedImageError,
|
UnidentifiedImageError,
|
||||||
)
|
)
|
||||||
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
|
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
|
||||||
|
@ -26,6 +27,7 @@ from .helper import (
|
||||||
hopper,
|
hopper,
|
||||||
is_pypy,
|
is_pypy,
|
||||||
is_win32,
|
is_win32,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
)
|
)
|
||||||
|
|
||||||
ElementTree: ModuleType | None
|
ElementTree: ModuleType | None
|
||||||
|
@ -47,25 +49,10 @@ class TestFileTiff:
|
||||||
assert im.size == (128, 128)
|
assert im.size == (128, 128)
|
||||||
assert im.format == "TIFF"
|
assert im.format == "TIFF"
|
||||||
|
|
||||||
hopper("1").save(filename)
|
for mode in ("1", "L", "P", "RGB", "I", "I;16", "I;16L"):
|
||||||
with Image.open(filename):
|
hopper(mode).save(filename)
|
||||||
pass
|
with Image.open(filename):
|
||||||
|
pass
|
||||||
hopper("L").save(filename)
|
|
||||||
with Image.open(filename):
|
|
||||||
pass
|
|
||||||
|
|
||||||
hopper("P").save(filename)
|
|
||||||
with Image.open(filename):
|
|
||||||
pass
|
|
||||||
|
|
||||||
hopper("RGB").save(filename)
|
|
||||||
with Image.open(filename):
|
|
||||||
pass
|
|
||||||
|
|
||||||
hopper("I").save(filename)
|
|
||||||
with Image.open(filename):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
|
@pytest.mark.skipif(is_pypy(), reason="Requires CPython")
|
||||||
def test_unclosed_file(self) -> None:
|
def test_unclosed_file(self) -> None:
|
||||||
|
@ -899,6 +886,29 @@ class TestFileTiff:
|
||||||
assert description[0]["format"] == "image/tiff"
|
assert description[0]["format"] == "image/tiff"
|
||||||
assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
|
assert description[3]["BitsPerSample"]["Seq"]["li"] == ["8", "8", "8"]
|
||||||
|
|
||||||
|
def test_getxmp_undefined(self, tmp_path: Path) -> None:
|
||||||
|
tmpfile = tmp_path / "temp.tif"
|
||||||
|
im = Image.new("L", (1, 1))
|
||||||
|
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||||
|
ifd.tagtype[700] = TiffTags.UNDEFINED
|
||||||
|
with Image.open("Tests/images/lab.tif") as im_xmp:
|
||||||
|
ifd[700] = im_xmp.info["xmp"]
|
||||||
|
im.save(tmpfile, tiffinfo=ifd)
|
||||||
|
|
||||||
|
with Image.open(tmpfile) as im_reloaded:
|
||||||
|
if ElementTree is None:
|
||||||
|
with pytest.warns(
|
||||||
|
UserWarning,
|
||||||
|
match="XMP data cannot be read without defusedxml dependency",
|
||||||
|
):
|
||||||
|
assert im_reloaded.getxmp() == {}
|
||||||
|
else:
|
||||||
|
assert "xmp" in im_reloaded.info
|
||||||
|
xmp = im_reloaded.getxmp()
|
||||||
|
|
||||||
|
description = xmp["xmpmeta"]["RDF"]["Description"]
|
||||||
|
assert description[0]["format"] == "image/tiff"
|
||||||
|
|
||||||
def test_get_photoshop_blocks(self) -> None:
|
def test_get_photoshop_blocks(self) -> None:
|
||||||
with Image.open("Tests/images/lab.tif") as im:
|
with Image.open("Tests/images/lab.tif") as im:
|
||||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||||
|
@ -988,7 +998,7 @@ class TestFileTiff:
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
@pytest.mark.timeout(6)
|
@timeout_unless_slower_valgrind(6)
|
||||||
@pytest.mark.filterwarnings("ignore:Truncated File Read")
|
@pytest.mark.filterwarnings("ignore:Truncated File Read")
|
||||||
def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
with Image.open("Tests/images/timeout-6646305047838720") as im:
|
with Image.open("Tests/images/timeout-6646305047838720") as im:
|
||||||
|
@ -1001,7 +1011,7 @@ class TestFileTiff:
|
||||||
"Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif",
|
"Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.timeout(2)
|
@timeout_unless_slower_valgrind(2)
|
||||||
def test_oom(self, test_file: str) -> None:
|
def test_oom(self, test_file: str) -> None:
|
||||||
with pytest.raises(UnidentifiedImageError):
|
with pytest.raises(UnidentifiedImageError):
|
||||||
with pytest.warns(UserWarning):
|
with pytest.warns(UserWarning):
|
||||||
|
|
|
@ -34,6 +34,7 @@ from .helper import (
|
||||||
is_win32,
|
is_win32,
|
||||||
mark_if_feature_version,
|
mark_if_feature_version,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
)
|
)
|
||||||
|
|
||||||
ElementTree: ModuleType | None
|
ElementTree: ModuleType | None
|
||||||
|
@ -572,10 +573,7 @@ class TestImage:
|
||||||
i = Image.new("RGB", [1, 1])
|
i = Image.new("RGB", [1, 1])
|
||||||
assert isinstance(i.size, tuple)
|
assert isinstance(i.size, tuple)
|
||||||
|
|
||||||
@pytest.mark.timeout(0.75)
|
@timeout_unless_slower_valgrind(0.75)
|
||||||
@pytest.mark.skipif(
|
|
||||||
"PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower"
|
|
||||||
)
|
|
||||||
@pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
|
@pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
|
||||||
def test_empty_image(self, size: tuple[int, int]) -> None:
|
def test_empty_image(self, size: tuple[int, int]) -> None:
|
||||||
Image.new("RGB", size)
|
Image.new("RGB", size)
|
||||||
|
@ -673,6 +671,7 @@ class TestImage:
|
||||||
im_remapped = im.remap_palette(list(range(256)))
|
im_remapped = im.remap_palette(list(range(256)))
|
||||||
assert_image_equal(im, im_remapped)
|
assert_image_equal(im, im_remapped)
|
||||||
assert im.palette is not None
|
assert im.palette is not None
|
||||||
|
assert im_remapped.palette is not None
|
||||||
assert im.palette.palette == im_remapped.palette.palette
|
assert im.palette.palette == im_remapped.palette.palette
|
||||||
|
|
||||||
# Test illegal image mode
|
# Test illegal image mode
|
||||||
|
@ -975,6 +974,11 @@ class TestImage:
|
||||||
assert tag not in exif.get_ifd(0x8769)
|
assert tag not in exif.get_ifd(0x8769)
|
||||||
assert exif.get_ifd(0xA005)
|
assert exif.get_ifd(0xA005)
|
||||||
|
|
||||||
|
def test_exif_from_xmp_bytes(self) -> None:
|
||||||
|
im = Image.new("RGB", (1, 1))
|
||||||
|
im.info["xmp"] = b'\xff tiff:Orientation="2"'
|
||||||
|
assert im.getexif()[274] == 2
|
||||||
|
|
||||||
def test_empty_xmp(self) -> None:
|
def test_empty_xmp(self) -> None:
|
||||||
with Image.open("Tests/images/hopper.gif") as im:
|
with Image.open("Tests/images/hopper.gif") as im:
|
||||||
if ElementTree is None:
|
if ElementTree is None:
|
||||||
|
@ -991,7 +995,7 @@ class TestImage:
|
||||||
im = Image.new("RGB", (1, 1))
|
im = Image.new("RGB", (1, 1))
|
||||||
im.info["xmp"] = (
|
im.info["xmp"] = (
|
||||||
b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
|
b'<?xpacket begin="\xef\xbb\xbf" id="W5M0MpCehiHzreSzNTczkc9d"?>\n'
|
||||||
b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00'
|
b'<x:xmpmeta xmlns:x="adobe:ns:meta/" />\n<?xpacket end="w"?>\x00\x00 '
|
||||||
)
|
)
|
||||||
if ElementTree is None:
|
if ElementTree is None:
|
||||||
with pytest.warns(
|
with pytest.warns(
|
||||||
|
|
|
@ -101,7 +101,8 @@ def test_fromarray_strides_without_tobytes() -> None:
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)})
|
wrapped = Wrapper({"shape": (1, 1), "strides": (1, 1)})
|
||||||
Image.fromarray(wrapped, "L")
|
with pytest.warns(DeprecationWarning):
|
||||||
|
Image.fromarray(wrapped, "L")
|
||||||
|
|
||||||
|
|
||||||
def test_fromarray_palette() -> None:
|
def test_fromarray_palette() -> None:
|
||||||
|
@ -110,7 +111,8 @@ def test_fromarray_palette() -> None:
|
||||||
a = numpy.array(i)
|
a = numpy.array(i)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
out = Image.fromarray(a, "P")
|
with pytest.warns(DeprecationWarning):
|
||||||
|
out = Image.fromarray(a, "P")
|
||||||
|
|
||||||
# Assert that the Python and C palettes match
|
# Assert that the Python and C palettes match
|
||||||
assert out.palette is not None
|
assert out.palette is not None
|
||||||
|
|
|
@ -462,7 +462,7 @@ class TestCoreResampleBox:
|
||||||
im.resize((32, 32), resample, (20, 20, 20, 100))
|
im.resize((32, 32), resample, (20, 20, 20, 100))
|
||||||
im.resize((32, 32), resample, (20, 20, 100, 20))
|
im.resize((32, 32), resample, (20, 20, 100, 20))
|
||||||
|
|
||||||
with pytest.raises(TypeError, match="must be sequence of length 4"):
|
with pytest.raises(TypeError, match="must be (sequence|tuple) of length 4"):
|
||||||
im.resize((32, 32), resample, (im.width, im.height)) # type: ignore[arg-type]
|
im.resize((32, 32), resample, (im.width, im.height)) # type: ignore[arg-type]
|
||||||
|
|
||||||
with pytest.raises(ValueError, match="can't be negative"):
|
with pytest.raises(ValueError, match="can't be negative"):
|
||||||
|
|
|
@ -783,9 +783,10 @@ def test_rectangle_I16(bbox: Coords) -> None:
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
draw.rectangle(bbox, outline=0xFFFF)
|
draw.rectangle(bbox, outline=0xCDEF)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
|
assert im.getpixel((X0, Y0)) == 0xCDEF
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff")
|
assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_I.tiff")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont, _util, features
|
from PIL import Image, ImageDraw, ImageFont, _util, features
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import assert_image_equal_tofile, timeout_unless_slower_valgrind
|
||||||
|
|
||||||
fonts = [ImageFont.load_default_imagefont()]
|
fonts = [ImageFont.load_default_imagefont()]
|
||||||
if not features.check_module("freetype2"):
|
if not features.check_module("freetype2"):
|
||||||
|
@ -72,7 +72,7 @@ def test_decompression_bomb() -> None:
|
||||||
font.getmask("A" * 1_000_000)
|
font.getmask("A" * 1_000_000)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.timeout(4)
|
@timeout_unless_slower_valgrind(4)
|
||||||
def test_oom() -> None:
|
def test_oom() -> None:
|
||||||
glyph = struct.pack(
|
glyph = struct.pack(
|
||||||
">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767
|
">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767
|
||||||
|
|
|
@ -162,3 +162,13 @@ def test_pickle_font_file(tmp_path: Path, protocol: int) -> None:
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
helper_assert_pickled_font_images(font, unpickled_font)
|
helper_assert_pickled_font_images(font, unpickled_font)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_earlier_data() -> None:
|
||||||
|
im = pickle.loads(
|
||||||
|
b"\x80\x04\x95@\x00\x00\x00\x00\x00\x00\x00\x8c\x12PIL.PngImagePlugin"
|
||||||
|
b"\x94\x8c\x0cPngImageFile\x94\x93\x94)\x81\x94]\x94(}\x94\x8c\x01L\x94K\x01"
|
||||||
|
b"K\x01\x86\x94NC\x01\x00\x94eb."
|
||||||
|
)
|
||||||
|
assert im.mode == "L"
|
||||||
|
assert im.size == (1, 1)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any # undone
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -10,30 +10,73 @@ from .helper import (
|
||||||
assert_deep_equal,
|
assert_deep_equal,
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
hopper,
|
hopper,
|
||||||
|
is_big_endian,
|
||||||
)
|
)
|
||||||
|
|
||||||
pyarrow = pytest.importorskip("pyarrow", reason="PyArrow not installed")
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import pyarrow
|
||||||
|
else:
|
||||||
|
pyarrow = pytest.importorskip("pyarrow", reason="PyArrow not installed")
|
||||||
|
|
||||||
TEST_IMAGE_SIZE = (10, 10)
|
TEST_IMAGE_SIZE = (10, 10)
|
||||||
|
|
||||||
|
|
||||||
def _test_img_equals_pyarray(
|
def _test_img_equals_pyarray(
|
||||||
img: Image.Image, arr: Any, mask: list[int] | None
|
img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
|
||||||
) -> None:
|
) -> None:
|
||||||
assert img.height * img.width == len(arr)
|
assert img.height * img.width * elts_per_pixel == len(arr)
|
||||||
px = img.load()
|
px = img.load()
|
||||||
assert px is not None
|
assert px is not None
|
||||||
|
if elts_per_pixel > 1 and mask is None:
|
||||||
|
# have to do element-wise comparison when we're comparing
|
||||||
|
# flattened r,g,b,a to a pixel.
|
||||||
|
mask = list(range(elts_per_pixel))
|
||||||
for x in range(0, img.size[0], int(img.size[0] / 10)):
|
for x in range(0, img.size[0], int(img.size[0] / 10)):
|
||||||
for y in range(0, img.size[1], int(img.size[1] / 10)):
|
for y in range(0, img.size[1], int(img.size[1] / 10)):
|
||||||
if mask:
|
if mask:
|
||||||
|
pixel = px[x, y]
|
||||||
|
assert isinstance(pixel, tuple)
|
||||||
for ix, elt in enumerate(mask):
|
for ix, elt in enumerate(mask):
|
||||||
pixel = px[x, y]
|
if elts_per_pixel == 1:
|
||||||
assert isinstance(pixel, tuple)
|
assert pixel[ix] == arr[y * img.width + x].as_py()[elt]
|
||||||
assert pixel[ix] == arr[y * img.width + x].as_py()[elt]
|
else:
|
||||||
|
assert (
|
||||||
|
pixel[ix]
|
||||||
|
== arr[(y * img.width + x) * elts_per_pixel + elt].as_py()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert_deep_equal(px[x, y], arr[y * img.width + x].as_py())
|
assert_deep_equal(px[x, y], arr[y * img.width + x].as_py())
|
||||||
|
|
||||||
|
|
||||||
|
def _test_img_equals_int32_pyarray(
|
||||||
|
img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
|
||||||
|
) -> None:
|
||||||
|
assert img.height * img.width * elts_per_pixel == len(arr)
|
||||||
|
px = img.load()
|
||||||
|
assert px is not None
|
||||||
|
if mask is None:
|
||||||
|
# have to do element-wise comparison when we're comparing
|
||||||
|
# flattened rgba in an uint32 to a pixel.
|
||||||
|
mask = list(range(elts_per_pixel))
|
||||||
|
for x in range(0, img.size[0], int(img.size[0] / 10)):
|
||||||
|
for y in range(0, img.size[1], int(img.size[1] / 10)):
|
||||||
|
pixel = px[x, y]
|
||||||
|
assert isinstance(pixel, tuple)
|
||||||
|
arr_pixel_int = arr[y * img.width + x].as_py()
|
||||||
|
arr_pixel_tuple = (
|
||||||
|
arr_pixel_int % 256,
|
||||||
|
(arr_pixel_int // 256) % 256,
|
||||||
|
(arr_pixel_int // 256**2) % 256,
|
||||||
|
(arr_pixel_int // 256**3),
|
||||||
|
)
|
||||||
|
if is_big_endian():
|
||||||
|
arr_pixel_tuple = arr_pixel_tuple[::-1]
|
||||||
|
|
||||||
|
for ix, elt in enumerate(mask):
|
||||||
|
assert pixel[ix] == arr_pixel_tuple[elt]
|
||||||
|
|
||||||
|
|
||||||
# really hard to get a non-nullable list type
|
# really hard to get a non-nullable list type
|
||||||
fl_uint8_4_type = pyarrow.field(
|
fl_uint8_4_type = pyarrow.field(
|
||||||
"_", pyarrow.list_(pyarrow.field("_", pyarrow.uint8()).with_nullable(False), 4)
|
"_", pyarrow.list_(pyarrow.field("_", pyarrow.uint8()).with_nullable(False), 4)
|
||||||
|
@ -55,14 +98,14 @@ fl_uint8_4_type = pyarrow.field(
|
||||||
("HSV", fl_uint8_4_type, [0, 1, 2]),
|
("HSV", fl_uint8_4_type, [0, 1, 2]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_to_array(mode: str, dtype: Any, mask: list[int] | None) -> None:
|
def test_to_array(mode: str, dtype: pyarrow.DataType, mask: list[int] | None) -> None:
|
||||||
img = hopper(mode)
|
img = hopper(mode)
|
||||||
|
|
||||||
# Resize to non-square
|
# Resize to non-square
|
||||||
img = img.crop((3, 0, 124, 127))
|
img = img.crop((3, 0, 124, 127))
|
||||||
assert img.size == (121, 127)
|
assert img.size == (121, 127)
|
||||||
|
|
||||||
arr = pyarrow.array(img)
|
arr = pyarrow.array(img) # type: ignore[call-overload]
|
||||||
_test_img_equals_pyarray(img, arr, mask)
|
_test_img_equals_pyarray(img, arr, mask)
|
||||||
assert arr.type == dtype
|
assert arr.type == dtype
|
||||||
|
|
||||||
|
@ -79,8 +122,8 @@ def test_lifetime() -> None:
|
||||||
|
|
||||||
img = hopper("L")
|
img = hopper("L")
|
||||||
|
|
||||||
arr_1 = pyarrow.array(img)
|
arr_1 = pyarrow.array(img) # type: ignore[call-overload]
|
||||||
arr_2 = pyarrow.array(img)
|
arr_2 = pyarrow.array(img) # type: ignore[call-overload]
|
||||||
|
|
||||||
del img
|
del img
|
||||||
|
|
||||||
|
@ -97,8 +140,8 @@ def test_lifetime2() -> None:
|
||||||
|
|
||||||
img = hopper("L")
|
img = hopper("L")
|
||||||
|
|
||||||
arr_1 = pyarrow.array(img)
|
arr_1 = pyarrow.array(img) # type: ignore[call-overload]
|
||||||
arr_2 = pyarrow.array(img)
|
arr_2 = pyarrow.array(img) # type: ignore[call-overload]
|
||||||
|
|
||||||
assert arr_1.sum().as_py() > 0
|
assert arr_1.sum().as_py() > 0
|
||||||
del arr_1
|
del arr_1
|
||||||
|
@ -110,3 +153,94 @@ def test_lifetime2() -> None:
|
||||||
px = img2.load()
|
px = img2.load()
|
||||||
assert px # make mypy happy
|
assert px # make mypy happy
|
||||||
assert isinstance(px[0, 0], int)
|
assert isinstance(px[0, 0], int)
|
||||||
|
|
||||||
|
|
||||||
|
class DataShape(NamedTuple):
|
||||||
|
dtype: pyarrow.DataType
|
||||||
|
# Strictly speaking, elt should be a pixel or pixel component, so
|
||||||
|
# list[uint8][4], float, int, uint32, uint8, etc. But more
|
||||||
|
# correctly, it should be exactly the dtype from the line above.
|
||||||
|
elt: Any
|
||||||
|
elts_per_pixel: int
|
||||||
|
|
||||||
|
|
||||||
|
UINT_ARR = DataShape(
|
||||||
|
dtype=fl_uint8_4_type,
|
||||||
|
elt=[1, 2, 3, 4], # array of 4 uint8 per pixel
|
||||||
|
elts_per_pixel=1, # only one array per pixel
|
||||||
|
)
|
||||||
|
|
||||||
|
UINT = DataShape(
|
||||||
|
dtype=pyarrow.uint8(),
|
||||||
|
elt=3, # one uint8,
|
||||||
|
elts_per_pixel=4, # but repeated 4x per pixel
|
||||||
|
)
|
||||||
|
|
||||||
|
UINT32 = DataShape(
|
||||||
|
dtype=pyarrow.uint32(),
|
||||||
|
elt=0xABCDEF45, # one packed int, doesn't fit in a int32 > 0x80000000
|
||||||
|
elts_per_pixel=1, # one per pixel
|
||||||
|
)
|
||||||
|
|
||||||
|
INT32 = DataShape(
|
||||||
|
dtype=pyarrow.uint32(),
|
||||||
|
elt=0x12CDEF45, # one packed int
|
||||||
|
elts_per_pixel=1, # one per pixel
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mode, data_tp, mask",
|
||||||
|
(
|
||||||
|
("L", DataShape(pyarrow.uint8(), 3, 1), None),
|
||||||
|
("I", DataShape(pyarrow.int32(), 1 << 24, 1), None),
|
||||||
|
("F", DataShape(pyarrow.float32(), 3.14159, 1), None),
|
||||||
|
("LA", UINT_ARR, [0, 3]),
|
||||||
|
("LA", UINT, [0, 3]),
|
||||||
|
("RGB", UINT_ARR, [0, 1, 2]),
|
||||||
|
("RGBA", UINT_ARR, None),
|
||||||
|
("CMYK", UINT_ARR, None),
|
||||||
|
("YCbCr", UINT_ARR, [0, 1, 2]),
|
||||||
|
("HSV", UINT_ARR, [0, 1, 2]),
|
||||||
|
("RGB", UINT, [0, 1, 2]),
|
||||||
|
("RGBA", UINT, None),
|
||||||
|
("CMYK", UINT, None),
|
||||||
|
("YCbCr", UINT, [0, 1, 2]),
|
||||||
|
("HSV", UINT, [0, 1, 2]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
|
||||||
|
(dtype, elt, elts_per_pixel) = data_tp
|
||||||
|
|
||||||
|
ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
|
||||||
|
arr = pyarrow.array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
|
||||||
|
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
|
||||||
|
|
||||||
|
_test_img_equals_pyarray(img, arr, mask, elts_per_pixel)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mode, data_tp, mask",
|
||||||
|
(
|
||||||
|
("LA", UINT32, [0, 3]),
|
||||||
|
("RGB", UINT32, [0, 1, 2]),
|
||||||
|
("RGBA", UINT32, None),
|
||||||
|
("CMYK", UINT32, None),
|
||||||
|
("YCbCr", UINT32, [0, 1, 2]),
|
||||||
|
("HSV", UINT32, [0, 1, 2]),
|
||||||
|
("LA", INT32, [0, 3]),
|
||||||
|
("RGB", INT32, [0, 1, 2]),
|
||||||
|
("RGBA", INT32, None),
|
||||||
|
("CMYK", INT32, None),
|
||||||
|
("YCbCr", INT32, [0, 1, 2]),
|
||||||
|
("HSV", INT32, [0, 1, 2]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
|
||||||
|
(dtype, elt, elts_per_pixel) = data_tp
|
||||||
|
|
||||||
|
ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
|
||||||
|
arr = pyarrow.array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
|
||||||
|
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
|
||||||
|
|
||||||
|
_test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel)
|
||||||
|
|
|
@ -52,3 +52,17 @@ def test_tiff_crashes(test_file: str) -> None:
|
||||||
pytest.skip("test image not found")
|
pytest.skip("test image not found")
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_tiff_mmap() -> None:
|
||||||
|
try:
|
||||||
|
with Image.open("Tests/images/crash_mmap.tif") as im:
|
||||||
|
im.seek(1)
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
im.seek(0)
|
||||||
|
im.load()
|
||||||
|
except FileNotFoundError:
|
||||||
|
if on_ci():
|
||||||
|
raise
|
||||||
|
pytest.skip("test image not found")
|
||||||
|
|
11
depends/docker-test-valgrind-memory.sh
Executable file
11
depends/docker-test-valgrind-memory.sh
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
## Run this as the test script in the Docker valgrind image.
|
||||||
|
## Note -- can be included directly into the Docker image,
|
||||||
|
## but requires the current python.supp.
|
||||||
|
|
||||||
|
source /vpy3/bin/activate
|
||||||
|
cd /Pillow
|
||||||
|
make clean
|
||||||
|
make install
|
||||||
|
make valgrind-leak
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
|
|
||||||
version=1.2.1
|
version=1.3.0
|
||||||
|
|
||||||
./download-and-extract.sh libavif-$version https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$version.tar.gz
|
./download-and-extract.sh libavif-$version https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$version.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,23 @@
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
PYTHON = python3
|
PYTHON = python3
|
||||||
SPHINXOPTS =
|
|
||||||
SPHINXBUILD = $(PYTHON) -m sphinx.cmd.build
|
SPHINXBUILD = $(PYTHON) -m sphinx.cmd.build
|
||||||
PAPER =
|
SPHINXOPTS = --fail-on-warning
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
BUILDER = html
|
||||||
|
JOBS = auto
|
||||||
|
PAPER =
|
||||||
|
|
||||||
# Internal variables.
|
# Internal variables.
|
||||||
PAPEROPT_a4 = --define latex_paper_size=a4
|
PAPEROPT_a4 = --define latex_paper_size=a4
|
||||||
PAPEROPT_letter = --define latex_paper_size=letter
|
PAPEROPT_letter = --define latex_paper_size=letter
|
||||||
ALLSPHINXOPTS = --doctree-dir $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
|
||||||
# the i18n builder cannot share the environment and doctrees with the others
|
ALLSPHINXOPTS = --builder $(BUILDER) \
|
||||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
--doctree-dir $(BUILDDIR)/doctrees \
|
||||||
|
--jobs $(JOBS) \
|
||||||
|
$(PAPEROPT_$(PAPER)) \
|
||||||
|
$(SPHINXOPTS) \
|
||||||
|
. $(BUILDDIR)/$(BUILDER)
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help:
|
help:
|
||||||
|
@ -36,31 +42,19 @@ install-sphinx:
|
||||||
.PHONY: html
|
.PHONY: html
|
||||||
html:
|
html:
|
||||||
$(MAKE) install-sphinx
|
$(MAKE) install-sphinx
|
||||||
$(SPHINXBUILD) --builder html --fail-on-warning --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
$(SPHINXBUILD) $(ALLSPHINXOPTS)
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
|
||||||
|
|
||||||
.PHONY: dirhtml
|
.PHONY: dirhtml
|
||||||
dirhtml:
|
dirhtml: BUILDER = dirhtml
|
||||||
$(MAKE) install-sphinx
|
dirhtml: html
|
||||||
$(SPHINXBUILD) --builder dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
|
||||||
|
|
||||||
.PHONY: singlehtml
|
.PHONY: singlehtml
|
||||||
singlehtml:
|
singlehtml: BUILDER = singlehtml
|
||||||
$(MAKE) install-sphinx
|
singlehtml: html
|
||||||
$(SPHINXBUILD) --builder singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
|
||||||
@echo
|
|
||||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
|
||||||
|
|
||||||
.PHONY: linkcheck
|
.PHONY: linkcheck
|
||||||
linkcheck:
|
linkcheck: BUILDER = linkcheck
|
||||||
$(MAKE) install-sphinx
|
linkcheck: html
|
||||||
$(SPHINXBUILD) --builder linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck -j auto
|
|
||||||
@echo
|
|
||||||
@echo "Link check complete; look for any errors in the above output " \
|
|
||||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
|
||||||
|
|
||||||
.PHONY: htmlview
|
.PHONY: htmlview
|
||||||
htmlview: html
|
htmlview: html
|
||||||
|
|
|
@ -193,6 +193,28 @@ Image.Image.get_child_images()
|
||||||
method uses an image's file pointer, and so child images could only be retrieved from
|
method uses an image's file pointer, and so child images could only be retrieved from
|
||||||
an :py:class:`PIL.ImageFile.ImageFile` instance.
|
an :py:class:`PIL.ImageFile.ImageFile` instance.
|
||||||
|
|
||||||
|
Image.fromarray mode parameter
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.3.0
|
||||||
|
|
||||||
|
The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
|
||||||
|
mode can be automatically determined from the object's shape and type instead.
|
||||||
|
|
||||||
|
Saving I mode images as PNG
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.3.0
|
||||||
|
|
||||||
|
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
|
||||||
|
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
|
||||||
|
changing the data, this is now deprecated. Instead, the image can be converted to
|
||||||
|
another mode before saving::
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
im = Image.new("I", (1, 1))
|
||||||
|
im.convert("I;16").save("out.png")
|
||||||
|
|
||||||
Removed features
|
Removed features
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -1082,6 +1082,26 @@ Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I
|
||||||
|
|
||||||
Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well.
|
Since Pillow 9.2.0, "plain" (P1 to P3) formats can be read as well.
|
||||||
|
|
||||||
|
QOI
|
||||||
|
^^^
|
||||||
|
|
||||||
|
.. versionadded:: 9.5.0
|
||||||
|
|
||||||
|
Pillow reads and writes images in Quite OK Image format using a Python codec. If you
|
||||||
|
wish to write code specifically for this format, :pypi:`qoi` is an alternative library
|
||||||
|
that uses C to decode the image and interfaces with NumPy.
|
||||||
|
|
||||||
|
.. _qoi-saving:
|
||||||
|
|
||||||
|
Saving
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
The :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:
|
||||||
|
|
||||||
|
**colorspace**
|
||||||
|
If set to "sRGB", the colorspace will be written as sRGB with linear alpha, instead
|
||||||
|
of all channels being linear.
|
||||||
|
|
||||||
SGI
|
SGI
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
@ -1578,15 +1598,6 @@ PSD
|
||||||
|
|
||||||
Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.
|
Pillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.
|
||||||
|
|
||||||
QOI
|
|
||||||
^^^
|
|
||||||
|
|
||||||
.. versionadded:: 9.5.0
|
|
||||||
|
|
||||||
Pillow reads images in Quite OK Image format using a Python decoder. If you wish to
|
|
||||||
write code specifically for this format, :pypi:`qoi` is an alternative library that
|
|
||||||
uses C to decode the image and interfaces with NumPy.
|
|
||||||
|
|
||||||
SUN
|
SUN
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
|
|
|
@ -194,9 +194,9 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
pacman -S \
|
pacman -S \
|
||||||
mingw-w64-x86_64-gcc \
|
mingw-w64-x86_64-gcc \
|
||||||
mingw-w64-x86_64-python3 \
|
mingw-w64-x86_64-python \
|
||||||
mingw-w64-x86_64-python3-pip \
|
mingw-w64-x86_64-python-pip \
|
||||||
mingw-w64-x86_64-python3-setuptools
|
mingw-w64-x86_64-python-setuptools
|
||||||
|
|
||||||
Prerequisites are installed on **MSYS2 MinGW 64-bit** with::
|
Prerequisites are installed on **MSYS2 MinGW 64-bit** with::
|
||||||
|
|
||||||
|
|
|
@ -40,18 +40,20 @@ These platforms are built and tested for every change.
|
||||||
| macOS 13 Ventura | 3.9 | x86-64 |
|
| macOS 13 Ventura | 3.9 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
|
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
|
||||||
| | PyPy3 | |
|
| | 3.14, PyPy3 | |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.9, 3.10, 3.11, | x86-64 |
|
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
||||||
| | 3.12, 3.13, PyPy3 | |
|
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Ubuntu Linux 24.04 LTS (Noble) | 3.12 | x86-64, arm64v8, |
|
| Ubuntu Linux 24.04 LTS (Noble) | 3.9, 3.10, 3.11, | x86-64 |
|
||||||
| | | ppc64le, s390x |
|
| | 3.12, 3.13, 3.14, PyPy3 | |
|
||||||
|
| +----------------------------+---------------------+
|
||||||
|
| | 3.12 | arm64v8, ppc64le, |
|
||||||
|
| | | s390x |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Windows Server 2019 | 3.9 | x86 |
|
| Windows Server 2022 | 3.9 | x86 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| Windows Server 2022 | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
| | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
||||||
| | PyPy3 | |
|
| | 3.14, PyPy3 | |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| | 3.12 (MinGW) | x86-64 |
|
| | 3.12 (MinGW) | x86-64 |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
|
|
|
@ -7,10 +7,8 @@ if "%SPHINXBUILD%" == "" (
|
||||||
)
|
)
|
||||||
set BUILDDIR=_build
|
set BUILDDIR=_build
|
||||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
|
||||||
if NOT "%PAPER%" == "" (
|
if NOT "%PAPER%" == "" (
|
||||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if "%1" == "" goto help
|
if "%1" == "" goto help
|
||||||
|
|
|
@ -18,6 +18,9 @@ OpenType fonts (as well as other font formats supported by the FreeType
|
||||||
library). For earlier versions, TrueType support is only available as part of
|
library). For earlier versions, TrueType support is only available as part of
|
||||||
the imToolkit package.
|
the imToolkit package.
|
||||||
|
|
||||||
|
When measuring text sizes, this module will not break at newline characters. For
|
||||||
|
multiline text, see the :py:mod:`~PIL.ImageDraw` module.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
To protect against potential DOS attacks when using arbitrary strings as
|
To protect against potential DOS attacks when using arbitrary strings as
|
||||||
text input, Pillow will raise a :py:exc:`ValueError` if the number of characters
|
text input, Pillow will raise a :py:exc:`ValueError` if the number of characters
|
||||||
|
|
89
docs/releasenotes/11.3.0.rst
Normal file
89
docs/releasenotes/11.3.0.rst
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
11.3.0
|
||||||
|
------
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
:cve:`YYYY-XXXXX`: TODO
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Backwards incompatible changes
|
||||||
|
==============================
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
============
|
||||||
|
|
||||||
|
Image.fromarray mode parameter
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The ``mode`` parameter in :py:meth:`~PIL.Image.fromarray()` has been deprecated. The
|
||||||
|
mode can be automatically determined from the object's shape and type instead.
|
||||||
|
|
||||||
|
Saving I mode images as PNG
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In order to fit the 32 bits of I mode images into PNG, when PNG images can only contain
|
||||||
|
at most 16 bits for a channel, Pillow has been clipping the values. Rather than quietly
|
||||||
|
changing the data, this is now deprecated. Instead, the image can be converted to
|
||||||
|
another mode before saving::
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
im = Image.new("I", (1, 1))
|
||||||
|
im.convert("I;16").save("out.png")
|
||||||
|
|
||||||
|
API changes
|
||||||
|
===========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
API additions
|
||||||
|
=============
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Other changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Added QOI saving
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Support has been added for saving QOI images. ``colorspace`` can be used to specify the
|
||||||
|
colorspace as sRGB with linear alpha, e.g. ``im.save("out.qoi", colorspace="sRGB")``.
|
||||||
|
By default, all channels will be linear.
|
||||||
|
|
||||||
|
Support using more screenshot utilities with ImageGrab on Linux
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
:py:meth:`~PIL.ImageGrab.grab` is now able to use GNOME Screenshot, grim or Spectacle
|
||||||
|
on Linux in order to take a snapshot of the screen.
|
||||||
|
|
||||||
|
Do not build against libavif < 1
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Pillow only supports libavif 1.0.0 or later. In order to prevent errors when building
|
||||||
|
from source, if a user happens to have an earlier libavif on their system, Pillow will
|
||||||
|
now ignore it.
|
||||||
|
|
||||||
|
Python 3.14 beta
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
To help other projects prepare for Python 3.14, wheels are now built for the
|
||||||
|
3.14 beta as a preview. This is not official support for Python 3.14, but rather
|
||||||
|
an opportunity for you to test how Pillow works with the beta and report any
|
||||||
|
problems.
|
|
@ -14,6 +14,7 @@ expected to be backported to earlier versions.
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
11.3.0
|
||||||
11.2.1
|
11.2.1
|
||||||
11.1.0
|
11.1.0
|
||||||
11.0.0
|
11.0.0
|
||||||
|
|
|
@ -70,6 +70,7 @@ optional-dependencies.tests = [
|
||||||
"pytest",
|
"pytest",
|
||||||
"pytest-cov",
|
"pytest-cov",
|
||||||
"pytest-timeout",
|
"pytest-timeout",
|
||||||
|
"pytest-xdist",
|
||||||
"trove-classifiers>=2024.10.12",
|
"trove-classifiers>=2024.10.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
42
setup.py
42
setup.py
|
@ -16,7 +16,6 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from collections.abc import Iterator
|
from collections.abc import Iterator
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from setuptools import Extension, setup
|
from setuptools import Extension, setup
|
||||||
from setuptools.command.build_ext import build_ext
|
from setuptools.command.build_ext import build_ext
|
||||||
|
@ -46,7 +45,7 @@ WEBP_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, 14):
|
if sys.platform == "win32" and sys.version_info >= (3, 15):
|
||||||
import atexit
|
import atexit
|
||||||
|
|
||||||
atexit.register(
|
atexit.register(
|
||||||
|
@ -148,7 +147,7 @@ class RequiredDependencyException(Exception):
|
||||||
PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version
|
PLATFORM_MINGW = os.name == "nt" and "GCC" in sys.version
|
||||||
|
|
||||||
|
|
||||||
def _dbg(s: str, tp: Any = None) -> None:
|
def _dbg(s: str, tp: str | tuple[str, ...] | None = None) -> None:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
if tp:
|
if tp:
|
||||||
print(s % tp)
|
print(s % tp)
|
||||||
|
@ -163,7 +162,7 @@ def _find_library_dirs_ldconfig() -> list[str]:
|
||||||
args: list[str]
|
args: list[str]
|
||||||
env: dict[str, str]
|
env: dict[str, str]
|
||||||
expr: str
|
expr: str
|
||||||
if sys.platform.startswith("linux") or sys.platform.startswith("gnu"):
|
if sys.platform.startswith(("linux", "gnu")):
|
||||||
if struct.calcsize("l") == 4:
|
if struct.calcsize("l") == 4:
|
||||||
machine = os.uname()[4] + "-32"
|
machine = os.uname()[4] + "-32"
|
||||||
else:
|
else:
|
||||||
|
@ -224,13 +223,14 @@ def _add_directory(
|
||||||
path.insert(where, subdir)
|
path.insert(where, subdir)
|
||||||
|
|
||||||
|
|
||||||
def _find_include_file(self: pil_build_ext, include: str) -> int:
|
def _find_include_file(self: pil_build_ext, include: str) -> str | None:
|
||||||
for directory in self.compiler.include_dirs:
|
for directory in self.compiler.include_dirs:
|
||||||
_dbg("Checking for include file %s in %s", (include, directory))
|
_dbg("Checking for include file %s in %s", (include, directory))
|
||||||
if os.path.isfile(os.path.join(directory, include)):
|
path = os.path.join(directory, include)
|
||||||
|
if os.path.isfile(path):
|
||||||
_dbg("Found %s", include)
|
_dbg("Found %s", include)
|
||||||
return 1
|
return path
|
||||||
return 0
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _find_library_file(self: pil_build_ext, library: str) -> str | None:
|
def _find_library_file(self: pil_build_ext, library: str) -> str | None:
|
||||||
|
@ -509,11 +509,11 @@ class pil_build_ext(build_ext):
|
||||||
|
|
||||||
if root is None and pkg_config:
|
if root is None and pkg_config:
|
||||||
if isinstance(lib_name, str):
|
if isinstance(lib_name, str):
|
||||||
_dbg(f"Looking for `{lib_name}` using pkg-config.")
|
_dbg("Looking for `%s` using pkg-config.", lib_name)
|
||||||
root = pkg_config(lib_name)
|
root = pkg_config(lib_name)
|
||||||
else:
|
else:
|
||||||
for lib_name2 in lib_name:
|
for lib_name2 in lib_name:
|
||||||
_dbg(f"Looking for `{lib_name2}` using pkg-config.")
|
_dbg("Looking for `%s` using pkg-config.", lib_name2)
|
||||||
root = pkg_config(lib_name2)
|
root = pkg_config(lib_name2)
|
||||||
if root:
|
if root:
|
||||||
break
|
break
|
||||||
|
@ -623,11 +623,7 @@ class pil_build_ext(build_ext):
|
||||||
|
|
||||||
for extension in self.extensions:
|
for extension in self.extensions:
|
||||||
extension.extra_compile_args = ["-Wno-nullability-completeness"]
|
extension.extra_compile_args = ["-Wno-nullability-completeness"]
|
||||||
elif (
|
elif sys.platform.startswith(("linux", "gnu", "freebsd")):
|
||||||
sys.platform.startswith("linux")
|
|
||||||
or sys.platform.startswith("gnu")
|
|
||||||
or sys.platform.startswith("freebsd")
|
|
||||||
):
|
|
||||||
for dirname in _find_library_dirs_ldconfig():
|
for dirname in _find_library_dirs_ldconfig():
|
||||||
_add_directory(library_dirs, dirname)
|
_add_directory(library_dirs, dirname)
|
||||||
if sys.platform.startswith("linux") and os.environ.get("ANDROID_ROOT"):
|
if sys.platform.startswith("linux") and os.environ.get("ANDROID_ROOT"):
|
||||||
|
@ -736,7 +732,7 @@ class pil_build_ext(build_ext):
|
||||||
best_path = os.path.join(directory, name)
|
best_path = os.path.join(directory, name)
|
||||||
_dbg(
|
_dbg(
|
||||||
"Best openjpeg version %s so far in %s",
|
"Best openjpeg version %s so far in %s",
|
||||||
(best_version, best_path),
|
(str(best_version), best_path),
|
||||||
)
|
)
|
||||||
|
|
||||||
if best_version and _find_library_file(self, "openjp2"):
|
if best_version and _find_library_file(self, "openjp2"):
|
||||||
|
@ -766,12 +762,12 @@ class pil_build_ext(build_ext):
|
||||||
if feature.want("tiff"):
|
if feature.want("tiff"):
|
||||||
_dbg("Looking for tiff")
|
_dbg("Looking for tiff")
|
||||||
if _find_include_file(self, "tiff.h"):
|
if _find_include_file(self, "tiff.h"):
|
||||||
if _find_library_file(self, "tiff"):
|
|
||||||
feature.set("tiff", "tiff")
|
|
||||||
if sys.platform in ["win32", "darwin"] and _find_library_file(
|
if sys.platform in ["win32", "darwin"] and _find_library_file(
|
||||||
self, "libtiff"
|
self, "libtiff"
|
||||||
):
|
):
|
||||||
feature.set("tiff", "libtiff")
|
feature.set("tiff", "libtiff")
|
||||||
|
elif _find_library_file(self, "tiff"):
|
||||||
|
feature.set("tiff", "tiff")
|
||||||
|
|
||||||
if feature.want("freetype"):
|
if feature.want("freetype"):
|
||||||
_dbg("Looking for freetype")
|
_dbg("Looking for freetype")
|
||||||
|
@ -861,9 +857,13 @@ class pil_build_ext(build_ext):
|
||||||
|
|
||||||
if feature.want("avif"):
|
if feature.want("avif"):
|
||||||
_dbg("Looking for avif")
|
_dbg("Looking for avif")
|
||||||
if _find_include_file(self, "avif/avif.h"):
|
if avif_h := _find_include_file(self, "avif/avif.h"):
|
||||||
if _find_library_file(self, "avif"):
|
with open(avif_h, "rb") as fp:
|
||||||
feature.set("avif", "avif")
|
major_version = int(
|
||||||
|
fp.read().split(b"#define AVIF_VERSION_MAJOR ")[1].split()[0]
|
||||||
|
)
|
||||||
|
if major_version >= 1 and _find_library_file(self, "avif"):
|
||||||
|
feature.set("avif", "avif")
|
||||||
|
|
||||||
for f in feature:
|
for f in feature:
|
||||||
if not feature.get(f) and feature.require(f):
|
if not feature.get(f) and feature.require(f):
|
||||||
|
|
|
@ -16,7 +16,6 @@ except ImportError:
|
||||||
# Decoder options as module globals, until there is a way to pass parameters
|
# Decoder options as module globals, until there is a way to pass parameters
|
||||||
# to Image.open (see https://github.com/python-pillow/Pillow/issues/569)
|
# to Image.open (see https://github.com/python-pillow/Pillow/issues/569)
|
||||||
DECODE_CODEC_CHOICE = "auto"
|
DECODE_CODEC_CHOICE = "auto"
|
||||||
# Decoding is only affected by this for libavif **0.8.4** or greater.
|
|
||||||
DEFAULT_MAX_THREADS = 0
|
DEFAULT_MAX_THREADS = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
A Pillow loader for .dds files (S3TC-compressed aka DXTC)
|
A Pillow plugin for .dds files (S3TC-compressed aka DXTC)
|
||||||
Jerome Leclanche <jerome@leclan.ch>
|
Jerome Leclanche <jerome@leclan.ch>
|
||||||
|
|
||||||
Documentation:
|
Documentation:
|
||||||
|
|
|
@ -31,7 +31,7 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
from typing import IO, Any, Literal, NamedTuple, Union
|
from typing import IO, Any, Literal, NamedTuple, Union, cast
|
||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
Image,
|
Image,
|
||||||
|
@ -350,12 +350,15 @@ class GifImageFile(ImageFile.ImageFile):
|
||||||
if self._frame_palette:
|
if self._frame_palette:
|
||||||
if color * 3 + 3 > len(self._frame_palette.palette):
|
if color * 3 + 3 > len(self._frame_palette.palette):
|
||||||
color = 0
|
color = 0
|
||||||
return tuple(self._frame_palette.palette[color * 3 : color * 3 + 3])
|
return cast(
|
||||||
|
tuple[int, int, int],
|
||||||
|
tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return (color, color, color)
|
return (color, color, color)
|
||||||
|
|
||||||
self.dispose = None
|
self.dispose = None
|
||||||
self.dispose_extent = frame_dispose_extent
|
self.dispose_extent: tuple[int, int, int, int] | None = frame_dispose_extent
|
||||||
if self.dispose_extent and self.disposal_method >= 2:
|
if self.dispose_extent and self.disposal_method >= 2:
|
||||||
try:
|
try:
|
||||||
if self.disposal_method == 2:
|
if self.disposal_method == 2:
|
||||||
|
|
|
@ -767,18 +767,20 @@ class Image:
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
This method returns the raw image data from the internal
|
This method returns raw image data derived from Pillow's internal
|
||||||
storage. For compressed image data (e.g. PNG, JPEG) use
|
storage. For compressed image data (e.g. PNG, JPEG) use
|
||||||
:meth:`~.save`, with a BytesIO parameter for in-memory
|
:meth:`~.save`, with a BytesIO parameter for in-memory data.
|
||||||
data.
|
|
||||||
|
|
||||||
:param encoder_name: What encoder to use. The default is to
|
:param encoder_name: What encoder to use.
|
||||||
use the standard "raw" encoder.
|
|
||||||
|
|
||||||
A list of C encoders can be seen under
|
The default is to use the standard "raw" encoder.
|
||||||
codecs section of the function array in
|
To see how this packs pixel data into the returned
|
||||||
:file:`_imaging.c`. Python encoders are
|
bytes, see :file:`libImaging/Pack.c`.
|
||||||
registered within the relevant plugins.
|
|
||||||
|
A list of C encoders can be seen under codecs
|
||||||
|
section of the function array in
|
||||||
|
:file:`_imaging.c`. Python encoders are registered
|
||||||
|
within the relevant plugins.
|
||||||
:param args: Extra arguments to the encoder.
|
:param args: Extra arguments to the encoder.
|
||||||
:returns: A :py:class:`bytes` object.
|
:returns: A :py:class:`bytes` object.
|
||||||
"""
|
"""
|
||||||
|
@ -800,7 +802,9 @@ class Image:
|
||||||
e = _getencoder(self.mode, encoder_name, encoder_args)
|
e = _getencoder(self.mode, encoder_name, encoder_args)
|
||||||
e.setimage(self.im)
|
e.setimage(self.im)
|
||||||
|
|
||||||
bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
|
from . import ImageFile
|
||||||
|
|
||||||
|
bufsize = max(ImageFile.MAXBLOCK, self.size[0] * 4) # see RawEncode.c
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
while True:
|
while True:
|
||||||
|
@ -1507,7 +1511,7 @@ class Image:
|
||||||
return {}
|
return {}
|
||||||
if "xmp" not in self.info:
|
if "xmp" not in self.info:
|
||||||
return {}
|
return {}
|
||||||
root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00"))
|
root = ElementTree.fromstring(self.info["xmp"].rstrip(b"\x00 "))
|
||||||
return {get_name(root.tag): get_value(root)}
|
return {get_name(root.tag): get_value(root)}
|
||||||
|
|
||||||
def getexif(self) -> Exif:
|
def getexif(self) -> Exif:
|
||||||
|
@ -1538,10 +1542,11 @@ class Image:
|
||||||
# XMP tags
|
# XMP tags
|
||||||
if ExifTags.Base.Orientation not in self._exif:
|
if ExifTags.Base.Orientation not in self._exif:
|
||||||
xmp_tags = self.info.get("XML:com.adobe.xmp")
|
xmp_tags = self.info.get("XML:com.adobe.xmp")
|
||||||
|
pattern: str | bytes = r'tiff:Orientation(="|>)([0-9])'
|
||||||
if not xmp_tags and (xmp_tags := self.info.get("xmp")):
|
if not xmp_tags and (xmp_tags := self.info.get("xmp")):
|
||||||
xmp_tags = xmp_tags.decode("utf-8")
|
pattern = rb'tiff:Orientation(="|>)([0-9])'
|
||||||
if xmp_tags:
|
if xmp_tags:
|
||||||
match = re.search(r'tiff:Orientation(="|>)([0-9])', xmp_tags)
|
match = re.search(pattern, xmp_tags)
|
||||||
if match:
|
if match:
|
||||||
self._exif[ExifTags.Base.Orientation] = int(match[2])
|
self._exif[ExifTags.Base.Orientation] = int(match[2])
|
||||||
|
|
||||||
|
@ -3267,7 +3272,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
|
||||||
|
|
||||||
:param obj: Object with array interface
|
:param obj: Object with array interface
|
||||||
:param mode: Optional mode to use when reading ``obj``. Will be determined from
|
:param mode: Optional mode to use when reading ``obj``. Will be determined from
|
||||||
type if ``None``.
|
type if ``None``. Deprecated.
|
||||||
|
|
||||||
This will not be used to convert the data after reading, but will be used to
|
This will not be used to convert the data after reading, but will be used to
|
||||||
change how the data is read::
|
change how the data is read::
|
||||||
|
@ -3302,6 +3307,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image:
|
||||||
msg = f"Cannot handle this data type: {typekey_shape}, {typestr}"
|
msg = f"Cannot handle this data type: {typekey_shape}, {typestr}"
|
||||||
raise TypeError(msg) from e
|
raise TypeError(msg) from e
|
||||||
else:
|
else:
|
||||||
|
deprecate("'mode' parameter", 13)
|
||||||
rawmode = mode
|
rawmode = mode
|
||||||
if mode in ["1", "L", "I", "P", "F"]:
|
if mode in ["1", "L", "I", "P", "F"]:
|
||||||
ndmax = 2
|
ndmax = 2
|
||||||
|
|
|
@ -248,6 +248,9 @@ class ImageCmsProfile:
|
||||||
low-level profile object
|
low-level profile object
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self.filename = None
|
||||||
|
self.product_name = None # profile.product_name
|
||||||
|
self.product_info = None # profile.product_info
|
||||||
|
|
||||||
if isinstance(profile, str):
|
if isinstance(profile, str):
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
|
@ -256,23 +259,18 @@ class ImageCmsProfile:
|
||||||
profile_bytes_path.decode("ascii")
|
profile_bytes_path.decode("ascii")
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
with open(profile, "rb") as f:
|
with open(profile, "rb") as f:
|
||||||
self._set(core.profile_frombytes(f.read()))
|
self.profile = core.profile_frombytes(f.read())
|
||||||
return
|
return
|
||||||
self._set(core.profile_open(profile), profile)
|
self.filename = profile
|
||||||
|
self.profile = core.profile_open(profile)
|
||||||
elif hasattr(profile, "read"):
|
elif hasattr(profile, "read"):
|
||||||
self._set(core.profile_frombytes(profile.read()))
|
self.profile = core.profile_frombytes(profile.read())
|
||||||
elif isinstance(profile, core.CmsProfile):
|
elif isinstance(profile, core.CmsProfile):
|
||||||
self._set(profile)
|
self.profile = profile
|
||||||
else:
|
else:
|
||||||
msg = "Invalid type for Profile" # type: ignore[unreachable]
|
msg = "Invalid type for Profile" # type: ignore[unreachable]
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
def _set(self, profile: core.CmsProfile, filename: str | None = None) -> None:
|
|
||||||
self.profile = profile
|
|
||||||
self.filename = filename
|
|
||||||
self.product_name = None # profile.product_name
|
|
||||||
self.product_info = None # profile.product_info
|
|
||||||
|
|
||||||
def tobytes(self) -> bytes:
|
def tobytes(self) -> bytes:
|
||||||
"""
|
"""
|
||||||
Returns the profile in a format suitable for embedding in
|
Returns the profile in a format suitable for embedding in
|
||||||
|
|
|
@ -365,22 +365,10 @@ class ImageDraw:
|
||||||
# use the fill as a mask
|
# use the fill as a mask
|
||||||
mask = Image.new("1", self.im.size)
|
mask = Image.new("1", self.im.size)
|
||||||
mask_ink = self._getink(1)[0]
|
mask_ink = self._getink(1)[0]
|
||||||
|
draw = Draw(mask)
|
||||||
fill_im = mask.copy()
|
|
||||||
draw = Draw(fill_im)
|
|
||||||
draw.draw.draw_polygon(xy, mask_ink, 1)
|
draw.draw.draw_polygon(xy, mask_ink, 1)
|
||||||
|
|
||||||
ink_im = mask.copy()
|
self.draw.draw_polygon(xy, ink, 0, width * 2 - 1, mask.im)
|
||||||
draw = Draw(ink_im)
|
|
||||||
width = width * 2 - 1
|
|
||||||
draw.draw.draw_polygon(xy, mask_ink, 0, width)
|
|
||||||
|
|
||||||
mask.paste(ink_im, mask=fill_im)
|
|
||||||
|
|
||||||
im = Image.new(self.mode, self.im.size)
|
|
||||||
draw = Draw(im)
|
|
||||||
draw.draw.draw_polygon(xy, ink, 0, width)
|
|
||||||
self.im.paste(im.im, (0, 0) + im.size, mask.im)
|
|
||||||
|
|
||||||
def regular_polygon(
|
def regular_polygon(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -257,7 +257,8 @@ class ImageFile(Image.Image):
|
||||||
|
|
||||||
def __setstate__(self, state: list[Any]) -> None:
|
def __setstate__(self, state: list[Any]) -> None:
|
||||||
self.tile = []
|
self.tile = []
|
||||||
self.filename = state[5]
|
if len(state) > 5:
|
||||||
|
self.filename = state[5]
|
||||||
super().__setstate__(state)
|
super().__setstate__(state)
|
||||||
|
|
||||||
def verify(self) -> None:
|
def verify(self) -> None:
|
||||||
|
|
|
@ -134,10 +134,10 @@ def grabclipboard() -> Image.Image | list[str] | None:
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
o = struct.unpack_from("I", data)[0]
|
o = struct.unpack_from("I", data)[0]
|
||||||
if data[16] != 0:
|
if data[16] == 0:
|
||||||
files = data[o:].decode("utf-16le").split("\0")
|
|
||||||
else:
|
|
||||||
files = data[o:].decode("mbcs").split("\0")
|
files = data[o:].decode("mbcs").split("\0")
|
||||||
|
else:
|
||||||
|
files = data[o:].decode("utf-16le").split("\0")
|
||||||
return files[: files.index("")]
|
return files[: files.index("")]
|
||||||
if isinstance(data, bytes):
|
if isinstance(data, bytes):
|
||||||
data = io.BytesIO(data)
|
data = io.BytesIO(data)
|
||||||
|
|
|
@ -175,7 +175,9 @@ class MacViewer(Viewer):
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
raise FileNotFoundError
|
raise FileNotFoundError
|
||||||
subprocess.call(["open", "-a", "Preview.app", path])
|
subprocess.call(["open", "-a", "Preview.app", path])
|
||||||
executable = sys.executable or shutil.which("python3")
|
|
||||||
|
pyinstaller = getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
|
||||||
|
executable = (not pyinstaller and sys.executable) or shutil.which("python3")
|
||||||
if executable:
|
if executable:
|
||||||
subprocess.Popen(
|
subprocess.Popen(
|
||||||
[
|
[
|
||||||
|
|
|
@ -762,8 +762,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
extra = info.get("extra", b"")
|
extra = info.get("extra", b"")
|
||||||
|
|
||||||
MAX_BYTES_IN_MARKER = 65533
|
MAX_BYTES_IN_MARKER = 65533
|
||||||
xmp = info.get("xmp")
|
if xmp := info.get("xmp"):
|
||||||
if xmp:
|
|
||||||
overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
|
overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
|
||||||
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
|
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
|
||||||
if len(xmp) > max_data_bytes_in_marker:
|
if len(xmp) > max_data_bytes_in_marker:
|
||||||
|
@ -772,8 +771,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
size = o16(2 + overhead_len + len(xmp))
|
size = o16(2 + overhead_len + len(xmp))
|
||||||
extra += b"\xff\xe1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp
|
extra += b"\xff\xe1" + size + b"http://ns.adobe.com/xap/1.0/\x00" + xmp
|
||||||
|
|
||||||
icc_profile = info.get("icc_profile")
|
if icc_profile := info.get("icc_profile"):
|
||||||
if icc_profile:
|
|
||||||
overhead_len = 14 # b"ICC_PROFILE\0" + o8(i) + o8(len(markers))
|
overhead_len = 14 # b"ICC_PROFILE\0" + o8(i) + o8(len(markers))
|
||||||
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
|
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
|
||||||
markers = []
|
markers = []
|
||||||
|
@ -831,7 +829,6 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
# in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
|
# in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
|
||||||
# channels*size, this is a value that's been used in a django patch.
|
# channels*size, this is a value that's been used in a django patch.
|
||||||
# https://github.com/matthewwithanm/django-imagekit/issues/50
|
# https://github.com/matthewwithanm/django-imagekit/issues/50
|
||||||
bufsize = 0
|
|
||||||
if optimize or progressive:
|
if optimize or progressive:
|
||||||
# CMYK can be bigger
|
# CMYK can be bigger
|
||||||
if im.mode == "CMYK":
|
if im.mode == "CMYK":
|
||||||
|
@ -848,7 +845,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
else:
|
else:
|
||||||
# The EXIF info needs to be written as one block, + APP1, + one spare byte.
|
# The EXIF info needs to be written as one block, + APP1, + one spare byte.
|
||||||
# Ensure that our buffer is big enough. Same with the icc_profile block.
|
# Ensure that our buffer is big enough. Same with the icc_profile block.
|
||||||
bufsize = max(bufsize, len(exif) + 5, len(extra) + 1)
|
bufsize = max(len(exif) + 5, len(extra) + 1)
|
||||||
|
|
||||||
ImageFile._save(
|
ImageFile._save(
|
||||||
im, fp, [ImageFile._Tile("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize
|
im, fp, [ImageFile._Tile("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize
|
||||||
|
|
|
@ -33,11 +33,7 @@ class BitStream:
|
||||||
|
|
||||||
def peek(self, bits: int) -> int:
|
def peek(self, bits: int) -> int:
|
||||||
while self.bits < bits:
|
while self.bits < bits:
|
||||||
c = self.next()
|
self.bitbuffer = (self.bitbuffer << 8) + self.next()
|
||||||
if c < 0:
|
|
||||||
self.bits = 0
|
|
||||||
continue
|
|
||||||
self.bitbuffer = (self.bitbuffer << 8) + c
|
|
||||||
self.bits += 8
|
self.bits += 8
|
||||||
return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1
|
return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ class PcxImageFile(ImageFile.ImageFile):
|
||||||
# header
|
# header
|
||||||
assert self.fp is not None
|
assert self.fp is not None
|
||||||
|
|
||||||
s = self.fp.read(128)
|
s = self.fp.read(68)
|
||||||
if not _accept(s):
|
if not _accept(s):
|
||||||
msg = "not a PCX file"
|
msg = "not a PCX file"
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
|
@ -66,6 +66,8 @@ class PcxImageFile(ImageFile.ImageFile):
|
||||||
raise SyntaxError(msg)
|
raise SyntaxError(msg)
|
||||||
logger.debug("BBox: %s %s %s %s", *bbox)
|
logger.debug("BBox: %s %s %s %s", *bbox)
|
||||||
|
|
||||||
|
offset = self.fp.tell() + 60
|
||||||
|
|
||||||
# format
|
# format
|
||||||
version = s[1]
|
version = s[1]
|
||||||
bits = s[3]
|
bits = s[3]
|
||||||
|
@ -102,7 +104,6 @@ class PcxImageFile(ImageFile.ImageFile):
|
||||||
break
|
break
|
||||||
if mode == "P":
|
if mode == "P":
|
||||||
self.palette = ImagePalette.raw("RGB", s[1:])
|
self.palette = ImagePalette.raw("RGB", s[1:])
|
||||||
self.fp.seek(128)
|
|
||||||
|
|
||||||
elif version == 5 and bits == 8 and planes == 3:
|
elif version == 5 and bits == 8 and planes == 3:
|
||||||
mode = "RGB"
|
mode = "RGB"
|
||||||
|
@ -128,9 +129,7 @@ class PcxImageFile(ImageFile.ImageFile):
|
||||||
bbox = (0, 0) + self.size
|
bbox = (0, 0) + self.size
|
||||||
logger.debug("size: %sx%s", *self.size)
|
logger.debug("size: %sx%s", *self.size)
|
||||||
|
|
||||||
self.tile = [
|
self.tile = [ImageFile._Tile("pcx", bbox, offset, (rawmode, planes * stride))]
|
||||||
ImageFile._Tile("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
|
@ -48,6 +48,7 @@ from ._binary import i32be as i32
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
from ._binary import o16be as o16
|
from ._binary import o16be as o16
|
||||||
from ._binary import o32be as o32
|
from ._binary import o32be as o32
|
||||||
|
from ._deprecate import deprecate
|
||||||
from ._util import DeferredError
|
from ._util import DeferredError
|
||||||
|
|
||||||
TYPE_CHECKING = False
|
TYPE_CHECKING = False
|
||||||
|
@ -1368,6 +1369,8 @@ def _save(
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
msg = f"cannot write mode {mode} as PNG"
|
msg = f"cannot write mode {mode} as PNG"
|
||||||
raise OSError(msg) from e
|
raise OSError(msg) from e
|
||||||
|
if outmode == "I":
|
||||||
|
deprecate("Saving I mode images as PNG", 13, stacklevel=4)
|
||||||
|
|
||||||
#
|
#
|
||||||
# write minimal PNG file
|
# write minimal PNG file
|
||||||
|
|
|
@ -94,8 +94,8 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
msg = "Reached EOF while reading header"
|
msg = "Reached EOF while reading header"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
elif len(token) > 10:
|
elif len(token) > 10:
|
||||||
msg = f"Token too long in file header: {token.decode()}"
|
msg_too_long = b"Token too long in file header: %s" % token
|
||||||
raise ValueError(msg)
|
raise ValueError(msg_too_long)
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def _open(self) -> None:
|
def _open(self) -> None:
|
||||||
|
|
|
@ -8,9 +8,12 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from typing import IO
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
from ._binary import i32be as i32
|
from ._binary import i32be as i32
|
||||||
|
from ._binary import o8
|
||||||
|
from ._binary import o32be as o32
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
|
@ -51,7 +54,7 @@ class QoiDecoder(ImageFile.PyDecoder):
|
||||||
assert self.fd is not None
|
assert self.fd is not None
|
||||||
|
|
||||||
self._previously_seen_pixels = {}
|
self._previously_seen_pixels = {}
|
||||||
self._add_to_previous_pixels(bytearray((0, 0, 0, 255)))
|
self._previous_pixel = bytearray((0, 0, 0, 255))
|
||||||
|
|
||||||
data = bytearray()
|
data = bytearray()
|
||||||
bands = Image.getmodebands(self.mode)
|
bands = Image.getmodebands(self.mode)
|
||||||
|
@ -110,6 +113,122 @@ class QoiDecoder(ImageFile.PyDecoder):
|
||||||
return -1, 0
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
|
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
if im.mode == "RGB":
|
||||||
|
channels = 3
|
||||||
|
elif im.mode == "RGBA":
|
||||||
|
channels = 4
|
||||||
|
else:
|
||||||
|
msg = "Unsupported QOI image mode"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
colorspace = 0 if im.encoderinfo.get("colorspace") == "sRGB" else 1
|
||||||
|
|
||||||
|
fp.write(b"qoif")
|
||||||
|
fp.write(o32(im.size[0]))
|
||||||
|
fp.write(o32(im.size[1]))
|
||||||
|
fp.write(o8(channels))
|
||||||
|
fp.write(o8(colorspace))
|
||||||
|
|
||||||
|
ImageFile._save(im, fp, [ImageFile._Tile("qoi", (0, 0) + im.size)])
|
||||||
|
|
||||||
|
|
||||||
|
class QoiEncoder(ImageFile.PyEncoder):
|
||||||
|
_pushes_fd = True
|
||||||
|
_previous_pixel: tuple[int, int, int, int] | None = None
|
||||||
|
_previously_seen_pixels: dict[int, tuple[int, int, int, int]] = {}
|
||||||
|
_run = 0
|
||||||
|
|
||||||
|
def _write_run(self) -> bytes:
|
||||||
|
data = o8(0b11000000 | (self._run - 1)) # QOI_OP_RUN
|
||||||
|
self._run = 0
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _delta(self, left: int, right: int) -> int:
|
||||||
|
result = (left - right) & 255
|
||||||
|
if result >= 128:
|
||||||
|
result -= 256
|
||||||
|
return result
|
||||||
|
|
||||||
|
def encode(self, bufsize: int) -> tuple[int, int, bytes]:
|
||||||
|
assert self.im is not None
|
||||||
|
|
||||||
|
self._previously_seen_pixels = {0: (0, 0, 0, 0)}
|
||||||
|
self._previous_pixel = (0, 0, 0, 255)
|
||||||
|
|
||||||
|
data = bytearray()
|
||||||
|
w, h = self.im.size
|
||||||
|
bands = Image.getmodebands(self.mode)
|
||||||
|
|
||||||
|
for y in range(h):
|
||||||
|
for x in range(w):
|
||||||
|
pixel = self.im.getpixel((x, y))
|
||||||
|
if bands == 3:
|
||||||
|
pixel = (*pixel, 255)
|
||||||
|
|
||||||
|
if pixel == self._previous_pixel:
|
||||||
|
self._run += 1
|
||||||
|
if self._run == 62:
|
||||||
|
data += self._write_run()
|
||||||
|
else:
|
||||||
|
if self._run:
|
||||||
|
data += self._write_run()
|
||||||
|
|
||||||
|
r, g, b, a = pixel
|
||||||
|
hash_value = (r * 3 + g * 5 + b * 7 + a * 11) % 64
|
||||||
|
if self._previously_seen_pixels.get(hash_value) == pixel:
|
||||||
|
data += o8(hash_value) # QOI_OP_INDEX
|
||||||
|
elif self._previous_pixel:
|
||||||
|
self._previously_seen_pixels[hash_value] = pixel
|
||||||
|
|
||||||
|
prev_r, prev_g, prev_b, prev_a = self._previous_pixel
|
||||||
|
if prev_a == a:
|
||||||
|
delta_r = self._delta(r, prev_r)
|
||||||
|
delta_g = self._delta(g, prev_g)
|
||||||
|
delta_b = self._delta(b, prev_b)
|
||||||
|
|
||||||
|
if (
|
||||||
|
-2 <= delta_r < 2
|
||||||
|
and -2 <= delta_g < 2
|
||||||
|
and -2 <= delta_b < 2
|
||||||
|
):
|
||||||
|
data += o8(
|
||||||
|
0b01000000
|
||||||
|
| (delta_r + 2) << 4
|
||||||
|
| (delta_g + 2) << 2
|
||||||
|
| (delta_b + 2)
|
||||||
|
) # QOI_OP_DIFF
|
||||||
|
else:
|
||||||
|
delta_gr = self._delta(delta_r, delta_g)
|
||||||
|
delta_gb = self._delta(delta_b, delta_g)
|
||||||
|
if (
|
||||||
|
-8 <= delta_gr < 8
|
||||||
|
and -32 <= delta_g < 32
|
||||||
|
and -8 <= delta_gb < 8
|
||||||
|
):
|
||||||
|
data += o8(
|
||||||
|
0b10000000 | (delta_g + 32)
|
||||||
|
) # QOI_OP_LUMA
|
||||||
|
data += o8((delta_gr + 8) << 4 | (delta_gb + 8))
|
||||||
|
else:
|
||||||
|
data += o8(0b11111110) # QOI_OP_RGB
|
||||||
|
data += bytes(pixel[:3])
|
||||||
|
else:
|
||||||
|
data += o8(0b11111111) # QOI_OP_RGBA
|
||||||
|
data += bytes(pixel)
|
||||||
|
|
||||||
|
self._previous_pixel = pixel
|
||||||
|
|
||||||
|
if self._run:
|
||||||
|
data += self._write_run()
|
||||||
|
data += bytes((0, 0, 0, 0, 0, 0, 0, 1)) # padding
|
||||||
|
|
||||||
|
return len(data), 0, data
|
||||||
|
|
||||||
|
|
||||||
Image.register_open(QoiImageFile.format, QoiImageFile, _accept)
|
Image.register_open(QoiImageFile.format, QoiImageFile, _accept)
|
||||||
Image.register_decoder("qoi", QoiDecoder)
|
Image.register_decoder("qoi", QoiDecoder)
|
||||||
Image.register_extension(QoiImageFile.format, ".qoi")
|
Image.register_extension(QoiImageFile.format, ".qoi")
|
||||||
|
|
||||||
|
Image.register_save(QoiImageFile.format, _save)
|
||||||
|
Image.register_encoder("qoi", QoiEncoder)
|
||||||
|
|
|
@ -1217,9 +1217,10 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
return
|
return
|
||||||
self._seek(frame)
|
self._seek(frame)
|
||||||
if self._im is not None and (
|
if self._im is not None and (
|
||||||
self.im.size != self._tile_size or self.im.mode != self.mode
|
self.im.size != self._tile_size
|
||||||
|
or self.im.mode != self.mode
|
||||||
|
or self.readonly
|
||||||
):
|
):
|
||||||
# The core image will no longer be used
|
|
||||||
self._im = None
|
self._im = None
|
||||||
|
|
||||||
def _seek(self, frame: int) -> None:
|
def _seek(self, frame: int) -> None:
|
||||||
|
@ -1259,7 +1260,10 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self.fp.seek(self._frame_pos[frame])
|
self.fp.seek(self._frame_pos[frame])
|
||||||
self.tag_v2.load(self.fp)
|
self.tag_v2.load(self.fp)
|
||||||
if XMP in self.tag_v2:
|
if XMP in self.tag_v2:
|
||||||
self.info["xmp"] = self.tag_v2[XMP]
|
xmp = self.tag_v2[XMP]
|
||||||
|
if isinstance(xmp, tuple) and len(xmp) == 1:
|
||||||
|
xmp = xmp[0]
|
||||||
|
self.info["xmp"] = xmp
|
||||||
elif "xmp" in self.info:
|
elif "xmp" in self.info:
|
||||||
del self.info["xmp"]
|
del self.info["xmp"]
|
||||||
self._reload_exif()
|
self._reload_exif()
|
||||||
|
@ -1676,7 +1680,7 @@ SAVE_INFO = {
|
||||||
"PA": ("PA", II, 3, 1, (8, 8), 2),
|
"PA": ("PA", II, 3, 1, (8, 8), 2),
|
||||||
"I": ("I;32S", II, 1, 2, (32,), None),
|
"I": ("I;32S", II, 1, 2, (32,), None),
|
||||||
"I;16": ("I;16", II, 1, 1, (16,), None),
|
"I;16": ("I;16", II, 1, 1, (16,), None),
|
||||||
"I;16S": ("I;16S", II, 1, 2, (16,), None),
|
"I;16L": ("I;16L", II, 1, 1, (16,), None),
|
||||||
"F": ("F;32F", II, 1, 3, (32,), None),
|
"F": ("F;32F", II, 1, 3, (32,), None),
|
||||||
"RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
|
"RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
|
||||||
"RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
|
"RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
|
||||||
|
@ -1684,10 +1688,7 @@ SAVE_INFO = {
|
||||||
"CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
|
"CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
|
||||||
"YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
|
"YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
|
||||||
"LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
|
"LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
|
||||||
"I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
|
|
||||||
"I;16B": ("I;16B", MM, 1, 1, (16,), None),
|
"I;16B": ("I;16B", MM, 1, 1, (16,), None),
|
||||||
"I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
|
|
||||||
"F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1963,7 +1964,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
# we're storing image byte order. So, if the rawmode
|
# we're storing image byte order. So, if the rawmode
|
||||||
# contains I;16, we need to convert from native to image
|
# contains I;16, we need to convert from native to image
|
||||||
# byte order.
|
# byte order.
|
||||||
if im.mode in ("I;16B", "I;16"):
|
if im.mode in ("I;16", "I;16B", "I;16L"):
|
||||||
rawmode = "I;16N"
|
rawmode = "I;16N"
|
||||||
|
|
||||||
# Pass tags as sorted list so that the tags are set in a fixed order.
|
# Pass tags as sorted list so that the tags are set in a fixed order.
|
||||||
|
|
|
@ -81,7 +81,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
||||||
def _open(self) -> None:
|
def _open(self) -> None:
|
||||||
# check placable header
|
# check placable header
|
||||||
s = self.fp.read(80)
|
s = self.fp.read(44)
|
||||||
|
|
||||||
if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"):
|
if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"):
|
||||||
# placeable windows metafile
|
# placeable windows metafile
|
||||||
|
|
|
@ -12,6 +12,7 @@ def deprecate(
|
||||||
*,
|
*,
|
||||||
action: str | None = None,
|
action: str | None = None,
|
||||||
plural: bool = False,
|
plural: bool = False,
|
||||||
|
stacklevel: int = 3,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Deprecations helper.
|
Deprecations helper.
|
||||||
|
@ -67,5 +68,5 @@ def deprecate(
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
f"{deprecated} {is_} deprecated and will be removed in {removed}{action}",
|
f"{deprecated} {is_} deprecated and will be removed in {removed}{action}",
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
stacklevel=3,
|
stacklevel=stacklevel,
|
||||||
)
|
)
|
||||||
|
|
24
src/_avif.c
24
src/_avif.c
|
@ -881,26 +881,22 @@ setup_module(PyObject *m) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyModuleDef_Slot slots[] = {
|
||||||
|
{Py_mod_exec, setup_module},
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
|
||||||
|
#endif
|
||||||
|
{0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
PyMODINIT_FUNC
|
||||||
PyInit__avif(void) {
|
PyInit__avif(void) {
|
||||||
PyObject *m;
|
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
.m_name = "_avif",
|
.m_name = "_avif",
|
||||||
.m_size = -1,
|
|
||||||
.m_methods = avifMethods,
|
.m_methods = avifMethods,
|
||||||
|
.m_slots = slots
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
return PyModuleDef_Init(&module_def);
|
||||||
if (setup_module(m) < 0) {
|
|
||||||
Py_DECREF(m);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef Py_GIL_DISABLED
|
|
||||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -308,9 +308,9 @@ _new_arrow(PyObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImagingBorrowArrow is responsible for retaining the array_capsule
|
// ImagingBorrowArrow is responsible for retaining the array_capsule
|
||||||
ret =
|
ret = PyImagingNew(
|
||||||
PyImagingNew(ImagingNewArrow(mode, xsize, ysize, schema_capsule, array_capsule)
|
ImagingNewArrow(mode, xsize, ysize, schema_capsule, array_capsule)
|
||||||
);
|
);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
return ImagingError_ValueError("Invalid Arrow array mode or size mismatch");
|
return ImagingError_ValueError("Invalid Arrow array mode or size mismatch");
|
||||||
}
|
}
|
||||||
|
@ -338,12 +338,6 @@ static const char *no_palette = "image has no palette";
|
||||||
static const char *readonly = "image is readonly";
|
static const char *readonly = "image is readonly";
|
||||||
/* static const char* no_content = "image has no content"; */
|
/* static const char* no_content = "image has no content"; */
|
||||||
|
|
||||||
void *
|
|
||||||
ImagingError_OSError(void) {
|
|
||||||
PyErr_SetString(PyExc_OSError, "error when accessing file");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *
|
void *
|
||||||
ImagingError_MemoryError(void) {
|
ImagingError_MemoryError(void) {
|
||||||
return PyErr_NoMemory();
|
return PyErr_NoMemory();
|
||||||
|
@ -369,11 +363,6 @@ ImagingError_ValueError(const char *message) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
ImagingError_Clear(void) {
|
|
||||||
PyErr_Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
/* HELPERS */
|
/* HELPERS */
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
|
@ -1665,7 +1654,8 @@ _putdata(ImagingObject *self, PyObject *args) {
|
||||||
int bigendian = 0;
|
int bigendian = 0;
|
||||||
if (image->type == IMAGING_TYPE_SPECIAL) {
|
if (image->type == IMAGING_TYPE_SPECIAL) {
|
||||||
// I;16*
|
// I;16*
|
||||||
if (strcmp(image->mode, "I;16B") == 0
|
if (
|
||||||
|
strcmp(image->mode, "I;16B") == 0
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(image->mode, "I;16N") == 0
|
|| strcmp(image->mode, "I;16N") == 0
|
||||||
#endif
|
#endif
|
||||||
|
@ -2226,6 +2216,7 @@ _unsharp_mask(ImagingObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ImagingUnsharpMask(imOut, imIn, radius, percent, threshold)) {
|
if (!ImagingUnsharpMask(imOut, imIn, radius, percent, threshold)) {
|
||||||
|
ImagingDelete(imOut);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3218,7 +3209,8 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) {
|
||||||
(int)p[3],
|
(int)p[3],
|
||||||
&ink,
|
&ink,
|
||||||
width,
|
width,
|
||||||
self->blend
|
self->blend,
|
||||||
|
NULL
|
||||||
) < 0) {
|
) < 0) {
|
||||||
free(xy);
|
free(xy);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -3356,7 +3348,10 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
|
||||||
int ink;
|
int ink;
|
||||||
int fill = 0;
|
int fill = 0;
|
||||||
int width = 0;
|
int width = 0;
|
||||||
if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) {
|
ImagingObject *maskp = NULL;
|
||||||
|
if (!PyArg_ParseTuple(
|
||||||
|
args, "Oi|iiO!", &data, &ink, &fill, &width, &Imaging_Type, &maskp
|
||||||
|
)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3386,8 +3381,16 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) {
|
||||||
|
|
||||||
free(xy);
|
free(xy);
|
||||||
|
|
||||||
if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, width, self->blend) <
|
if (ImagingDrawPolygon(
|
||||||
0) {
|
self->image->image,
|
||||||
|
n,
|
||||||
|
ixy,
|
||||||
|
&ink,
|
||||||
|
fill,
|
||||||
|
width,
|
||||||
|
self->blend,
|
||||||
|
maskp ? maskp->image : NULL
|
||||||
|
) < 0) {
|
||||||
free(ixy);
|
free(ixy);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
@ -4460,27 +4463,22 @@ setup_module(PyObject *m) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyModuleDef_Slot slots[] = {
|
||||||
|
{Py_mod_exec, setup_module},
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
|
||||||
|
#endif
|
||||||
|
{0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
PyMODINIT_FUNC
|
||||||
PyInit__imaging(void) {
|
PyInit__imaging(void) {
|
||||||
PyObject *m;
|
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
.m_name = "_imaging",
|
.m_name = "_imaging",
|
||||||
.m_size = -1,
|
|
||||||
.m_methods = functions,
|
.m_methods = functions,
|
||||||
|
.m_slots = slots
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
return PyModuleDef_Init(&module_def);
|
||||||
|
|
||||||
if (setup_module(m) < 0) {
|
|
||||||
Py_DECREF(m);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef Py_GIL_DISABLED
|
|
||||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1463,28 +1463,24 @@ setup_module(PyObject *m) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyModuleDef_Slot slots[] = {
|
||||||
|
{Py_mod_exec, setup_module},
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
|
||||||
|
#endif
|
||||||
|
{0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
PyMODINIT_FUNC
|
||||||
PyInit__imagingcms(void) {
|
PyInit__imagingcms(void) {
|
||||||
PyObject *m;
|
PyDateTime_IMPORT;
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
.m_name = "_imagingcms",
|
.m_name = "_imagingcms",
|
||||||
.m_size = -1,
|
|
||||||
.m_methods = pyCMSdll_methods,
|
.m_methods = pyCMSdll_methods,
|
||||||
|
.m_slots = slots
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
return PyModuleDef_Init(&module_def);
|
||||||
|
|
||||||
if (setup_module(m) < 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyDateTime_IMPORT;
|
|
||||||
|
|
||||||
#ifdef Py_GIL_DISABLED
|
|
||||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,6 +275,7 @@ text_layout_raqm(
|
||||||
if (!text || !size) {
|
if (!text || !size) {
|
||||||
/* return 0 and clean up, no glyphs==no size,
|
/* return 0 and clean up, no glyphs==no size,
|
||||||
and raqm fails with empty strings */
|
and raqm fails with empty strings */
|
||||||
|
PyMem_Free(text);
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
set_text = raqm_set_text(rq, text, size);
|
set_text = raqm_set_text(rq, text, size);
|
||||||
|
@ -425,6 +426,7 @@ text_layout_fallback(
|
||||||
"setting text direction, language or font features is not supported "
|
"setting text direction, language or font features is not supported "
|
||||||
"without libraqm"
|
"without libraqm"
|
||||||
);
|
);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PyUnicode_Check(string)) {
|
if (PyUnicode_Check(string)) {
|
||||||
|
@ -1599,26 +1601,22 @@ setup_module(PyObject *m) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyModuleDef_Slot slots[] = {
|
||||||
|
{Py_mod_exec, setup_module},
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
|
||||||
|
#endif
|
||||||
|
{0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
PyMODINIT_FUNC
|
||||||
PyInit__imagingft(void) {
|
PyInit__imagingft(void) {
|
||||||
PyObject *m;
|
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
.m_name = "_imagingft",
|
.m_name = "_imagingft",
|
||||||
.m_size = -1,
|
|
||||||
.m_methods = _functions,
|
.m_methods = _functions,
|
||||||
|
.m_slots = slots
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
return PyModuleDef_Init(&module_def);
|
||||||
|
|
||||||
if (setup_module(m) < 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef Py_GIL_DISABLED
|
|
||||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -302,26 +302,22 @@ setup_module(PyObject *m) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyModuleDef_Slot slots[] = {
|
||||||
|
{Py_mod_exec, setup_module},
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
|
||||||
|
#endif
|
||||||
|
{0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
PyMODINIT_FUNC
|
||||||
PyInit__imagingmath(void) {
|
PyInit__imagingmath(void) {
|
||||||
PyObject *m;
|
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
.m_name = "_imagingmath",
|
.m_name = "_imagingmath",
|
||||||
.m_size = -1,
|
|
||||||
.m_methods = _functions,
|
.m_methods = _functions,
|
||||||
|
.m_slots = slots
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
return PyModuleDef_Init(&module_def);
|
||||||
|
|
||||||
if (setup_module(m) < 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef Py_GIL_DISABLED
|
|
||||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,23 +246,22 @@ static PyMethodDef functions[] = {
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static PyModuleDef_Slot slots[] = {
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
|
||||||
|
#endif
|
||||||
|
{0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
PyMODINIT_FUNC
|
||||||
PyInit__imagingmorph(void) {
|
PyInit__imagingmorph(void) {
|
||||||
PyObject *m;
|
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
.m_name = "_imagingmorph",
|
.m_name = "_imagingmorph",
|
||||||
.m_doc = "A module for doing image morphology",
|
.m_doc = "A module for doing image morphology",
|
||||||
.m_size = -1,
|
|
||||||
.m_methods = functions,
|
.m_methods = functions,
|
||||||
|
.m_slots = slots
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
return PyModuleDef_Init(&module_def);
|
||||||
|
|
||||||
#ifdef Py_GIL_DISABLED
|
|
||||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,24 +46,22 @@ static PyMethodDef functions[] = {
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static PyModuleDef_Slot slots[] = {
|
||||||
|
{Py_mod_exec, load_tkinter_funcs},
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
|
||||||
|
#endif
|
||||||
|
{0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
PyMODINIT_FUNC
|
||||||
PyInit__imagingtk(void) {
|
PyInit__imagingtk(void) {
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
.m_name = "_imagingtk",
|
.m_name = "_imagingtk",
|
||||||
.m_size = -1,
|
|
||||||
.m_methods = functions,
|
.m_methods = functions,
|
||||||
|
.m_slots = slots
|
||||||
};
|
};
|
||||||
PyObject *m;
|
|
||||||
m = PyModule_Create(&module_def);
|
|
||||||
if (load_tkinter_funcs() != 0) {
|
|
||||||
Py_DECREF(m);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef Py_GIL_DISABLED
|
return PyModuleDef_Init(&module_def);
|
||||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
31
src/_webp.c
31
src/_webp.c
|
@ -641,6 +641,10 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
||||||
ImagingSectionLeave(&cookie);
|
ImagingSectionLeave(&cookie);
|
||||||
|
|
||||||
WebPPictureFree(&pic);
|
WebPPictureFree(&pic);
|
||||||
|
|
||||||
|
output = writer.mem;
|
||||||
|
ret_size = writer.size;
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
int error_code = (&pic)->error_code;
|
int error_code = (&pic)->error_code;
|
||||||
char message[50] = "";
|
char message[50] = "";
|
||||||
|
@ -652,10 +656,9 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
PyErr_Format(PyExc_ValueError, "encoding error %d%s", error_code, message);
|
PyErr_Format(PyExc_ValueError, "encoding error %d%s", error_code, message);
|
||||||
|
free(output);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
output = writer.mem;
|
|
||||||
ret_size = writer.size;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
/* I want to truncate the *_size items that get passed into WebP
|
/* I want to truncate the *_size items that get passed into WebP
|
||||||
|
@ -777,26 +780,22 @@ setup_module(PyObject *m) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyModuleDef_Slot slots[] = {
|
||||||
|
{Py_mod_exec, setup_module},
|
||||||
|
#ifdef Py_GIL_DISABLED
|
||||||
|
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
|
||||||
|
#endif
|
||||||
|
{0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
PyMODINIT_FUNC
|
||||||
PyInit__webp(void) {
|
PyInit__webp(void) {
|
||||||
PyObject *m;
|
|
||||||
|
|
||||||
static PyModuleDef module_def = {
|
static PyModuleDef module_def = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
.m_name = "_webp",
|
.m_name = "_webp",
|
||||||
.m_size = -1,
|
|
||||||
.m_methods = webpMethods,
|
.m_methods = webpMethods,
|
||||||
|
.m_slots = slots
|
||||||
};
|
};
|
||||||
|
|
||||||
m = PyModule_Create(&module_def);
|
return PyModuleDef_Init(&module_def);
|
||||||
if (setup_module(m) < 0) {
|
|
||||||
Py_DECREF(m);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef Py_GIL_DISABLED
|
|
||||||
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -327,11 +327,11 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
|
||||||
// added in Windows 10 (1607)
|
// added in Windows 10 (1607)
|
||||||
// loaded dynamically to avoid link errors
|
// loaded dynamically to avoid link errors
|
||||||
user32 = LoadLibraryA("User32.dll");
|
user32 = LoadLibraryA("User32.dll");
|
||||||
SetThreadDpiAwarenessContext_function = (Func_SetThreadDpiAwarenessContext
|
SetThreadDpiAwarenessContext_function = (Func_SetThreadDpiAwarenessContext)
|
||||||
)GetProcAddress(user32, "SetThreadDpiAwarenessContext");
|
GetProcAddress(user32, "SetThreadDpiAwarenessContext");
|
||||||
if (SetThreadDpiAwarenessContext_function != NULL) {
|
if (SetThreadDpiAwarenessContext_function != NULL) {
|
||||||
GetWindowDpiAwarenessContext_function = (Func_GetWindowDpiAwarenessContext
|
GetWindowDpiAwarenessContext_function = (Func_GetWindowDpiAwarenessContext)
|
||||||
)GetProcAddress(user32, "GetWindowDpiAwarenessContext");
|
GetProcAddress(user32, "GetWindowDpiAwarenessContext");
|
||||||
if (screens == -1 && GetWindowDpiAwarenessContext_function != NULL) {
|
if (screens == -1 && GetWindowDpiAwarenessContext_function != NULL) {
|
||||||
dpiAwareness = GetWindowDpiAwarenessContext_function(wnd);
|
dpiAwareness = GetWindowDpiAwarenessContext_function(wnd);
|
||||||
}
|
}
|
||||||
|
|
|
@ -703,6 +703,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encoder->cleanup = ImagingLibTiffEncodeCleanup;
|
||||||
|
|
||||||
num_core_tags = sizeof(core_tags) / sizeof(int);
|
num_core_tags = sizeof(core_tags) / sizeof(int);
|
||||||
for (pos = 0; pos < tags_size; pos++) {
|
for (pos = 0; pos < tags_size; pos++) {
|
||||||
item = PyList_GetItemRef(tags, pos);
|
item = PyList_GetItemRef(tags, pos);
|
||||||
|
|
|
@ -36,7 +36,10 @@ ReleaseExportedSchema(struct ArrowSchema *array) {
|
||||||
child->release(child);
|
child->release(child);
|
||||||
child->release = NULL;
|
child->release = NULL;
|
||||||
}
|
}
|
||||||
// UNDONE -- should I be releasing the children?
|
free(array->children[i]);
|
||||||
|
}
|
||||||
|
if (array->children) {
|
||||||
|
free(array->children);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release dictionary
|
// Release dictionary
|
||||||
|
@ -98,7 +101,7 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for now, single block images */
|
/* for now, single block images */
|
||||||
if (!(im->blocks_count == 0 || im->blocks_count == 1)) {
|
if (im->blocks_count > 1) {
|
||||||
return IMAGING_ARROW_MEMORY_LAYOUT;
|
return IMAGING_ARROW_MEMORY_LAYOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +120,7 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
|
||||||
retval = export_named_type(schema->children[0], im->arrow_band_format, "pixel");
|
retval = export_named_type(schema->children[0], im->arrow_band_format, "pixel");
|
||||||
if (retval != 0) {
|
if (retval != 0) {
|
||||||
free(schema->children[0]);
|
free(schema->children[0]);
|
||||||
|
free(schema->children);
|
||||||
schema->release(schema);
|
schema->release(schema);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
@ -127,9 +131,7 @@ static void
|
||||||
release_const_array(struct ArrowArray *array) {
|
release_const_array(struct ArrowArray *array) {
|
||||||
Imaging im = (Imaging)array->private_data;
|
Imaging im = (Imaging)array->private_data;
|
||||||
|
|
||||||
if (array->n_children == 0) {
|
ImagingDelete(im);
|
||||||
ImagingDelete(im);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free the buffers and the buffers array
|
// Free the buffers and the buffers array
|
||||||
if (array->buffers) {
|
if (array->buffers) {
|
||||||
|
@ -157,7 +159,7 @@ export_single_channel_array(Imaging im, struct ArrowArray *array) {
|
||||||
int length = im->xsize * im->ysize;
|
int length = im->xsize * im->ysize;
|
||||||
|
|
||||||
/* for now, single block images */
|
/* for now, single block images */
|
||||||
if (!(im->blocks_count == 0 || im->blocks_count == 1)) {
|
if (im->blocks_count > 1) {
|
||||||
return IMAGING_ARROW_MEMORY_LAYOUT;
|
return IMAGING_ARROW_MEMORY_LAYOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +202,7 @@ export_fixed_pixel_array(Imaging im, struct ArrowArray *array) {
|
||||||
int length = im->xsize * im->ysize;
|
int length = im->xsize * im->ysize;
|
||||||
|
|
||||||
/* for now, single block images */
|
/* for now, single block images */
|
||||||
if (!(im->blocks_count == 0 || im->blocks_count == 1)) {
|
if (im->blocks_count > 1) {
|
||||||
return IMAGING_ARROW_MEMORY_LAYOUT;
|
return IMAGING_ARROW_MEMORY_LAYOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ typedef struct {
|
||||||
} Edge;
|
} Edge;
|
||||||
|
|
||||||
/* Type used in "polygon*" functions */
|
/* Type used in "polygon*" functions */
|
||||||
typedef void (*hline_handler)(Imaging, int, int, int, int);
|
typedef void (*hline_handler)(Imaging, int, int, int, int, Imaging);
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
point8(Imaging im, int x, int y, int ink) {
|
point8(Imaging im, int x, int y, int ink) {
|
||||||
|
@ -103,9 +103,7 @@ point32rgba(Imaging im, int x, int y, int ink) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
hline8(Imaging im, int x0, int y0, int x1, int ink) {
|
hline8(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
|
||||||
int pixelwidth;
|
|
||||||
|
|
||||||
if (y0 >= 0 && y0 < im->ysize) {
|
if (y0 >= 0 && y0 < im->ysize) {
|
||||||
if (x0 < 0) {
|
if (x0 < 0) {
|
||||||
x0 = 0;
|
x0 = 0;
|
||||||
|
@ -118,16 +116,41 @@ hline8(Imaging im, int x0, int y0, int x1, int ink) {
|
||||||
x1 = im->xsize - 1;
|
x1 = im->xsize - 1;
|
||||||
}
|
}
|
||||||
if (x0 <= x1) {
|
if (x0 <= x1) {
|
||||||
pixelwidth = strncmp(im->mode, "I;16", 4) == 0 ? 2 : 1;
|
int bigendian = -1;
|
||||||
memset(
|
if (strncmp(im->mode, "I;16", 4) == 0) {
|
||||||
im->image8[y0] + x0 * pixelwidth, (UINT8)ink, (x1 - x0 + 1) * pixelwidth
|
bigendian =
|
||||||
);
|
(
|
||||||
|
#ifdef WORDS_BIGENDIAN
|
||||||
|
strcmp(im->mode, "I;16") == 0 || strcmp(im->mode, "I;16L") == 0
|
||||||
|
#else
|
||||||
|
strcmp(im->mode, "I;16B") == 0
|
||||||
|
#endif
|
||||||
|
)
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
if (mask == NULL && bigendian == -1) {
|
||||||
|
memset(im->image8[y0] + x0, (UINT8)ink, (x1 - x0 + 1));
|
||||||
|
} else {
|
||||||
|
UINT8 *p = im->image8[y0];
|
||||||
|
while (x0 <= x1) {
|
||||||
|
if (mask == NULL || mask->image8[y0][x0]) {
|
||||||
|
if (bigendian == -1) {
|
||||||
|
p[x0] = ink;
|
||||||
|
} else {
|
||||||
|
p[x0 * 2 + (bigendian ? 1 : 0)] = ink;
|
||||||
|
p[x0 * 2 + (bigendian ? 0 : 1)] = ink >> 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x0++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
hline32(Imaging im, int x0, int y0, int x1, int ink) {
|
hline32(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
|
||||||
INT32 *p;
|
INT32 *p;
|
||||||
|
|
||||||
if (y0 >= 0 && y0 < im->ysize) {
|
if (y0 >= 0 && y0 < im->ysize) {
|
||||||
|
@ -143,13 +166,16 @@ hline32(Imaging im, int x0, int y0, int x1, int ink) {
|
||||||
}
|
}
|
||||||
p = im->image32[y0];
|
p = im->image32[y0];
|
||||||
while (x0 <= x1) {
|
while (x0 <= x1) {
|
||||||
p[x0++] = ink;
|
if (mask == NULL || mask->image8[y0][x0]) {
|
||||||
|
p[x0] = ink;
|
||||||
|
}
|
||||||
|
x0++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
hline32rgba(Imaging im, int x0, int y0, int x1, int ink) {
|
hline32rgba(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) {
|
||||||
unsigned int tmp;
|
unsigned int tmp;
|
||||||
|
|
||||||
if (y0 >= 0 && y0 < im->ysize) {
|
if (y0 >= 0 && y0 < im->ysize) {
|
||||||
|
@ -167,9 +193,11 @@ hline32rgba(Imaging im, int x0, int y0, int x1, int ink) {
|
||||||
UINT8 *out = (UINT8 *)im->image[y0] + x0 * 4;
|
UINT8 *out = (UINT8 *)im->image[y0] + x0 * 4;
|
||||||
UINT8 *in = (UINT8 *)&ink;
|
UINT8 *in = (UINT8 *)&ink;
|
||||||
while (x0 <= x1) {
|
while (x0 <= x1) {
|
||||||
out[0] = BLEND(in[3], out[0], in[0], tmp);
|
if (mask == NULL || mask->image8[y0][x0]) {
|
||||||
out[1] = BLEND(in[3], out[1], in[1], tmp);
|
out[0] = BLEND(in[3], out[0], in[0], tmp);
|
||||||
out[2] = BLEND(in[3], out[2], in[2], tmp);
|
out[1] = BLEND(in[3], out[1], in[1], tmp);
|
||||||
|
out[2] = BLEND(in[3], out[2], in[2], tmp);
|
||||||
|
}
|
||||||
x0++;
|
x0++;
|
||||||
out += 4;
|
out += 4;
|
||||||
}
|
}
|
||||||
|
@ -407,7 +435,14 @@ x_cmp(const void *x0, const void *x1) {
|
||||||
|
|
||||||
static void
|
static void
|
||||||
draw_horizontal_lines(
|
draw_horizontal_lines(
|
||||||
Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hline_handler hline
|
Imaging im,
|
||||||
|
int n,
|
||||||
|
Edge *e,
|
||||||
|
int ink,
|
||||||
|
int *x_pos,
|
||||||
|
int y,
|
||||||
|
hline_handler hline,
|
||||||
|
Imaging mask
|
||||||
) {
|
) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
|
@ -429,7 +464,7 @@ draw_horizontal_lines(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(*hline)(im, xmin, e[i].ymin, xmax, ink);
|
(*hline)(im, xmin, e[i].ymin, xmax, ink, mask);
|
||||||
*x_pos = xmax + 1;
|
*x_pos = xmax + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,7 +475,7 @@ draw_horizontal_lines(
|
||||||
*/
|
*/
|
||||||
static inline int
|
static inline int
|
||||||
polygon_generic(
|
polygon_generic(
|
||||||
Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, int hasAlpha
|
Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, Imaging mask
|
||||||
) {
|
) {
|
||||||
Edge **edge_table;
|
Edge **edge_table;
|
||||||
float *xx;
|
float *xx;
|
||||||
|
@ -461,6 +496,7 @@ polygon_generic(
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int hasAlpha = hline == hline32rgba;
|
||||||
for (i = 0; i < n; i++) {
|
for (i = 0; i < n; i++) {
|
||||||
if (ymin > e[i].ymin) {
|
if (ymin > e[i].ymin) {
|
||||||
ymin = e[i].ymin;
|
ymin = e[i].ymin;
|
||||||
|
@ -470,7 +506,7 @@ polygon_generic(
|
||||||
}
|
}
|
||||||
if (e[i].ymin == e[i].ymax) {
|
if (e[i].ymin == e[i].ymax) {
|
||||||
if (hasAlpha != 1) {
|
if (hasAlpha != 1) {
|
||||||
(*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink);
|
(*hline)(im, e[i].xmin, e[i].ymin, e[i].xmax, ink, mask);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -558,7 +594,7 @@ polygon_generic(
|
||||||
// Line would be before the current position
|
// Line would be before the current position
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline);
|
draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline, mask);
|
||||||
if (x_end < x_pos) {
|
if (x_end < x_pos) {
|
||||||
// Line would be before the current position
|
// Line would be before the current position
|
||||||
continue;
|
continue;
|
||||||
|
@ -574,13 +610,13 @@ polygon_generic(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(*hline)(im, x_start, ymin, x_end, ink);
|
(*hline)(im, x_start, ymin, x_end, ink, mask);
|
||||||
x_pos = x_end + 1;
|
x_pos = x_end + 1;
|
||||||
}
|
}
|
||||||
draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline);
|
draw_horizontal_lines(im, n, e, ink, &x_pos, ymin, hline, mask);
|
||||||
} else {
|
} else {
|
||||||
for (i = 1; i < j; i += 2) {
|
for (i = 1; i < j; i += 2) {
|
||||||
(*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink);
|
(*hline)(im, ROUND_UP(xx[i - 1]), ymin, ROUND_DOWN(xx[i]), ink, mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -590,21 +626,6 @@ polygon_generic(
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int
|
|
||||||
polygon8(Imaging im, int n, Edge *e, int ink, int eofill) {
|
|
||||||
return polygon_generic(im, n, e, ink, eofill, hline8, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int
|
|
||||||
polygon32(Imaging im, int n, Edge *e, int ink, int eofill) {
|
|
||||||
return polygon_generic(im, n, e, ink, eofill, hline32, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int
|
|
||||||
polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) {
|
|
||||||
return polygon_generic(im, n, e, ink, eofill, hline32rgba, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
add_edge(Edge *e, int x0, int y0, int x1, int y1) {
|
add_edge(Edge *e, int x0, int y0, int x1, int y1) {
|
||||||
/* printf("edge %d %d %d %d\n", x0, y0, x1, y1); */
|
/* printf("edge %d %d %d %d\n", x0, y0, x1, y1); */
|
||||||
|
@ -639,14 +660,13 @@ add_edge(Edge *e, int x0, int y0, int x1, int y1) {
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void (*point)(Imaging im, int x, int y, int ink);
|
void (*point)(Imaging im, int x, int y, int ink);
|
||||||
void (*hline)(Imaging im, int x0, int y0, int x1, int ink);
|
void (*hline)(Imaging im, int x0, int y0, int x1, int ink, Imaging mask);
|
||||||
void (*line)(Imaging im, int x0, int y0, int x1, int y1, int ink);
|
void (*line)(Imaging im, int x0, int y0, int x1, int y1, int ink);
|
||||||
int (*polygon)(Imaging im, int n, Edge *e, int ink, int eofill);
|
|
||||||
} DRAW;
|
} DRAW;
|
||||||
|
|
||||||
DRAW draw8 = {point8, hline8, line8, polygon8};
|
DRAW draw8 = {point8, hline8, line8};
|
||||||
DRAW draw32 = {point32, hline32, line32, polygon32};
|
DRAW draw32 = {point32, hline32, line32};
|
||||||
DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba, polygon32rgba};
|
DRAW draw32rgba = {point32rgba, hline32rgba, line32rgba};
|
||||||
|
|
||||||
/* -------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------- */
|
||||||
/* Interface */
|
/* Interface */
|
||||||
|
@ -691,7 +711,15 @@ ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink_, in
|
||||||
|
|
||||||
int
|
int
|
||||||
ImagingDrawWideLine(
|
ImagingDrawWideLine(
|
||||||
Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int width, int op
|
Imaging im,
|
||||||
|
int x0,
|
||||||
|
int y0,
|
||||||
|
int x1,
|
||||||
|
int y1,
|
||||||
|
const void *ink_,
|
||||||
|
int width,
|
||||||
|
int op,
|
||||||
|
Imaging mask
|
||||||
) {
|
) {
|
||||||
DRAW *draw;
|
DRAW *draw;
|
||||||
INT32 ink;
|
INT32 ink;
|
||||||
|
@ -731,7 +759,7 @@ ImagingDrawWideLine(
|
||||||
add_edge(e + 2, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1]);
|
add_edge(e + 2, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1]);
|
||||||
add_edge(e + 3, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1]);
|
add_edge(e + 3, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1]);
|
||||||
|
|
||||||
draw->polygon(im, 4, e, ink, 0);
|
polygon_generic(im, 4, e, ink, 0, draw->hline, mask);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -774,7 +802,7 @@ ImagingDrawRectangle(
|
||||||
}
|
}
|
||||||
|
|
||||||
for (y = y0; y <= y1; y++) {
|
for (y = y0; y <= y1; y++) {
|
||||||
draw->hline(im, x0, y, x1, ink);
|
draw->hline(im, x0, y, x1, ink, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -783,8 +811,8 @@ ImagingDrawRectangle(
|
||||||
width = 1;
|
width = 1;
|
||||||
}
|
}
|
||||||
for (i = 0; i < width; i++) {
|
for (i = 0; i < width; i++) {
|
||||||
draw->hline(im, x0, y0 + i, x1, ink);
|
draw->hline(im, x0, y0 + i, x1, ink, NULL);
|
||||||
draw->hline(im, x0, y1 - i, x1, ink);
|
draw->hline(im, x0, y1 - i, x1, ink, NULL);
|
||||||
draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink);
|
draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink);
|
||||||
draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink);
|
draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink);
|
||||||
}
|
}
|
||||||
|
@ -795,7 +823,14 @@ ImagingDrawRectangle(
|
||||||
|
|
||||||
int
|
int
|
||||||
ImagingDrawPolygon(
|
ImagingDrawPolygon(
|
||||||
Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op
|
Imaging im,
|
||||||
|
int count,
|
||||||
|
int *xy,
|
||||||
|
const void *ink_,
|
||||||
|
int fill,
|
||||||
|
int width,
|
||||||
|
int op,
|
||||||
|
Imaging mask
|
||||||
) {
|
) {
|
||||||
int i, n, x0, y0, x1, y1;
|
int i, n, x0, y0, x1, y1;
|
||||||
DRAW *draw;
|
DRAW *draw;
|
||||||
|
@ -839,7 +874,7 @@ ImagingDrawPolygon(
|
||||||
if (xy[i * 2] != xy[0] || xy[i * 2 + 1] != xy[1]) {
|
if (xy[i * 2] != xy[0] || xy[i * 2 + 1] != xy[1]) {
|
||||||
add_edge(&e[n++], xy[i * 2], xy[i * 2 + 1], xy[0], xy[1]);
|
add_edge(&e[n++], xy[i * 2], xy[i * 2 + 1], xy[0], xy[1]);
|
||||||
}
|
}
|
||||||
draw->polygon(im, n, e, ink, 0);
|
polygon_generic(im, n, e, ink, 0, draw->hline, mask);
|
||||||
free(e);
|
free(e);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -861,11 +896,12 @@ ImagingDrawPolygon(
|
||||||
xy[i * 2 + 3],
|
xy[i * 2 + 3],
|
||||||
ink_,
|
ink_,
|
||||||
width,
|
width,
|
||||||
op
|
op,
|
||||||
|
mask
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ImagingDrawWideLine(
|
ImagingDrawWideLine(
|
||||||
im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op
|
im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op, mask
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1536,7 +1572,9 @@ ellipseNew(
|
||||||
ellipse_init(&st, a, b, width);
|
ellipse_init(&st, a, b, width);
|
||||||
int32_t X0, Y, X1;
|
int32_t X0, Y, X1;
|
||||||
while (ellipse_next(&st, &X0, &Y, &X1) != -1) {
|
while (ellipse_next(&st, &X0, &Y, &X1) != -1) {
|
||||||
draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink);
|
draw->hline(
|
||||||
|
im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink, NULL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1571,7 +1609,9 @@ clipEllipseNew(
|
||||||
int32_t X0, Y, X1;
|
int32_t X0, Y, X1;
|
||||||
int next_code;
|
int next_code;
|
||||||
while ((next_code = clip_ellipse_next(&st, &X0, &Y, &X1)) >= 0) {
|
while ((next_code = clip_ellipse_next(&st, &X0, &Y, &X1)) >= 0) {
|
||||||
draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink);
|
draw->hline(
|
||||||
|
im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink, NULL
|
||||||
|
);
|
||||||
}
|
}
|
||||||
clip_ellipse_free(&st);
|
clip_ellipse_free(&st);
|
||||||
return next_code == -1 ? 0 : -1;
|
return next_code == -1 ? 0 : -1;
|
||||||
|
@ -1989,7 +2029,7 @@ ImagingDrawOutline(
|
||||||
|
|
||||||
DRAWINIT();
|
DRAWINIT();
|
||||||
|
|
||||||
draw->polygon(im, outline->count, outline->edges, ink, 0);
|
polygon_generic(im, outline->count, outline->edges, ink, 0, draw->hline, NULL);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ ImagingSavePPM(Imaging im, const char *outfile) {
|
||||||
|
|
||||||
fp = fopen(outfile, "wb");
|
fp = fopen(outfile, "wb");
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
(void)ImagingError_OSError();
|
PyErr_SetString(PyExc_OSError, "error when accessing file");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,8 +118,9 @@ ImagingFillRadialGradient(const char *mode) {
|
||||||
|
|
||||||
for (y = 0; y < 256; y++) {
|
for (y = 0; y < 256; y++) {
|
||||||
for (x = 0; x < 256; x++) {
|
for (x = 0; x < 256; x++) {
|
||||||
d = (int
|
d = (int)sqrt(
|
||||||
)sqrt((double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0);
|
(double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0
|
||||||
|
);
|
||||||
if (d >= 255) {
|
if (d >= 255) {
|
||||||
d = 255;
|
d = 255;
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,7 +155,8 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
|
||||||
} else {
|
} else {
|
||||||
int bigendian = 0;
|
int bigendian = 0;
|
||||||
if (im->type == IMAGING_TYPE_SPECIAL) {
|
if (im->type == IMAGING_TYPE_SPECIAL) {
|
||||||
if (strcmp(im->mode, "I;16B") == 0
|
if (
|
||||||
|
strcmp(im->mode, "I;16B") == 0
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(im->mode, "I;16N") == 0
|
|| strcmp(im->mode, "I;16N") == 0
|
||||||
#endif
|
#endif
|
||||||
|
@ -308,7 +309,8 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
|
||||||
} else {
|
} else {
|
||||||
int bigendian = 0;
|
int bigendian = 0;
|
||||||
if (im->type == IMAGING_TYPE_SPECIAL) {
|
if (im->type == IMAGING_TYPE_SPECIAL) {
|
||||||
if (strcmp(im->mode, "I;16B") == 0
|
if (
|
||||||
|
strcmp(im->mode, "I;16B") == 0
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(im->mode, "I;16N") == 0
|
|| strcmp(im->mode, "I;16N") == 0
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -270,8 +270,6 @@ ImagingSectionLeave(ImagingSectionCookie *cookie);
|
||||||
/* Exceptions */
|
/* Exceptions */
|
||||||
/* ---------- */
|
/* ---------- */
|
||||||
|
|
||||||
extern void *
|
|
||||||
ImagingError_OSError(void);
|
|
||||||
extern void *
|
extern void *
|
||||||
ImagingError_MemoryError(void);
|
ImagingError_MemoryError(void);
|
||||||
extern void *
|
extern void *
|
||||||
|
@ -280,8 +278,6 @@ extern void *
|
||||||
ImagingError_Mismatch(void); /* maps to ValueError by default */
|
ImagingError_Mismatch(void); /* maps to ValueError by default */
|
||||||
extern void *
|
extern void *
|
||||||
ImagingError_ValueError(const char *message);
|
ImagingError_ValueError(const char *message);
|
||||||
extern void
|
|
||||||
ImagingError_Clear(void);
|
|
||||||
|
|
||||||
/* Transform callbacks */
|
/* Transform callbacks */
|
||||||
/* ------------------- */
|
/* ------------------- */
|
||||||
|
@ -510,7 +506,15 @@ extern int
|
||||||
ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink, int op);
|
ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink, int op);
|
||||||
extern int
|
extern int
|
||||||
ImagingDrawWideLine(
|
ImagingDrawWideLine(
|
||||||
Imaging im, int x0, int y0, int x1, int y1, const void *ink, int width, int op
|
Imaging im,
|
||||||
|
int x0,
|
||||||
|
int y0,
|
||||||
|
int x1,
|
||||||
|
int y1,
|
||||||
|
const void *ink,
|
||||||
|
int width,
|
||||||
|
int op,
|
||||||
|
Imaging mask
|
||||||
);
|
);
|
||||||
extern int
|
extern int
|
||||||
ImagingDrawPieslice(
|
ImagingDrawPieslice(
|
||||||
|
@ -530,7 +534,14 @@ extern int
|
||||||
ImagingDrawPoint(Imaging im, int x, int y, const void *ink, int op);
|
ImagingDrawPoint(Imaging im, int x, int y, const void *ink, int op);
|
||||||
extern int
|
extern int
|
||||||
ImagingDrawPolygon(
|
ImagingDrawPolygon(
|
||||||
Imaging im, int points, int *xy, const void *ink, int fill, int width, int op
|
Imaging im,
|
||||||
|
int points,
|
||||||
|
int *xy,
|
||||||
|
const void *ink,
|
||||||
|
int fill,
|
||||||
|
int width,
|
||||||
|
int op,
|
||||||
|
Imaging mask
|
||||||
);
|
);
|
||||||
extern int
|
extern int
|
||||||
ImagingDrawRectangle(
|
ImagingDrawRectangle(
|
||||||
|
|
|
@ -207,8 +207,8 @@ j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) {
|
||||||
|
|
||||||
if (params->cp_cinema == OPJ_CINEMA4K_24) {
|
if (params->cp_cinema == OPJ_CINEMA4K_24) {
|
||||||
float max_rate =
|
float max_rate =
|
||||||
((float)(components * im->xsize * im->ysize * 8) / (CINEMA_24_CS_LENGTH * 8)
|
((float)(components * im->xsize * im->ysize * 8) /
|
||||||
);
|
(CINEMA_24_CS_LENGTH * 8));
|
||||||
|
|
||||||
params->POC[0].tile = 1;
|
params->POC[0].tile = 1;
|
||||||
params->POC[0].resno0 = 0;
|
params->POC[0].resno0 = 0;
|
||||||
|
@ -243,8 +243,8 @@ j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) {
|
||||||
params->max_comp_size = COMP_24_CS_MAX_LENGTH;
|
params->max_comp_size = COMP_24_CS_MAX_LENGTH;
|
||||||
} else {
|
} else {
|
||||||
float max_rate =
|
float max_rate =
|
||||||
((float)(components * im->xsize * im->ysize * 8) / (CINEMA_48_CS_LENGTH * 8)
|
((float)(components * im->xsize * im->ysize * 8) /
|
||||||
);
|
(CINEMA_48_CS_LENGTH * 8));
|
||||||
|
|
||||||
for (n = 0; n < params->tcp_numlayers; ++n) {
|
for (n = 0; n < params->tcp_numlayers; ++n) {
|
||||||
rate = 0;
|
rate = 0;
|
||||||
|
|
|
@ -131,6 +131,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
state->errcode = IMAGING_CODEC_CONFIG;
|
state->errcode = IMAGING_CODEC_CONFIG;
|
||||||
|
jpeg_destroy_compress(&context->cinfo);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +162,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
/* Would subsample the green and blue
|
/* Would subsample the green and blue
|
||||||
channels, which doesn't make sense */
|
channels, which doesn't make sense */
|
||||||
state->errcode = IMAGING_CODEC_CONFIG;
|
state->errcode = IMAGING_CODEC_CONFIG;
|
||||||
|
jpeg_destroy_compress(&context->cinfo);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
jpeg_set_colorspace(&context->cinfo, JCS_RGB);
|
jpeg_set_colorspace(&context->cinfo, JCS_RGB);
|
||||||
|
|
|
@ -60,15 +60,25 @@ ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state->x >= state->bytes) {
|
if (state->x >= state->bytes) {
|
||||||
if (state->bytes % state->xsize && state->bytes > state->xsize) {
|
int bands;
|
||||||
int bands = state->bytes / state->xsize;
|
int xsize = 0;
|
||||||
int stride = state->bytes / bands;
|
int stride = 0;
|
||||||
|
if (state->bits == 2 || state->bits == 4) {
|
||||||
|
xsize = (state->xsize + 7) / 8;
|
||||||
|
bands = state->bits;
|
||||||
|
stride = state->bytes / state->bits;
|
||||||
|
} else {
|
||||||
|
xsize = state->xsize;
|
||||||
|
bands = state->bytes / state->xsize;
|
||||||
|
if (bands != 0) {
|
||||||
|
stride = state->bytes / bands;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stride > xsize) {
|
||||||
int i;
|
int i;
|
||||||
for (i = 1; i < bands; i++) { // note -- skipping first band
|
for (i = 1; i < bands; i++) { // note -- skipping first band
|
||||||
memmove(
|
memmove(
|
||||||
&state->buffer[i * state->xsize],
|
&state->buffer[i * xsize], &state->buffer[i * stride], xsize
|
||||||
&state->buffer[i * stride],
|
|
||||||
state->xsize
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,8 +197,9 @@ ImagingPoint(Imaging imIn, const char *mode, const void *table) {
|
||||||
return imOut;
|
return imOut;
|
||||||
|
|
||||||
mode_mismatch:
|
mode_mismatch:
|
||||||
return (Imaging
|
return (Imaging)ImagingError_ValueError(
|
||||||
)ImagingError_ValueError("point operation not supported for this mode");
|
"point operation not supported for this mode"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
|
|
|
@ -470,7 +470,8 @@ ImagingResampleHorizontal_16bpc(
|
||||||
double *k;
|
double *k;
|
||||||
|
|
||||||
int bigendian = 0;
|
int bigendian = 0;
|
||||||
if (strcmp(imIn->mode, "I;16N") == 0
|
if (
|
||||||
|
strcmp(imIn->mode, "I;16N") == 0
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(imIn->mode, "I;16B") == 0
|
|| strcmp(imIn->mode, "I;16B") == 0
|
||||||
#endif
|
#endif
|
||||||
|
@ -509,7 +510,8 @@ ImagingResampleVertical_16bpc(
|
||||||
double *k;
|
double *k;
|
||||||
|
|
||||||
int bigendian = 0;
|
int bigendian = 0;
|
||||||
if (strcmp(imIn->mode, "I;16N") == 0
|
if (
|
||||||
|
strcmp(imIn->mode, "I;16N") == 0
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(imIn->mode, "I;16B") == 0
|
|| strcmp(imIn->mode, "I;16B") == 0
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -602,8 +602,9 @@ ImagingBorrowArrow(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!borrowed_buffer) {
|
if (!borrowed_buffer) {
|
||||||
return (Imaging
|
return (Imaging)ImagingError_ValueError(
|
||||||
)ImagingError_ValueError("Arrow Array, exactly 2 buffers required");
|
"Arrow Array, exactly 2 buffers required"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (y = i = 0; y < im->ysize; y++) {
|
for (y = i = 0; y < im->ysize; y++) {
|
||||||
|
@ -644,7 +645,7 @@ ImagingNewInternal(const char *mode, int xsize, int ysize, int dirty) {
|
||||||
return im;
|
return im;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImagingError_Clear();
|
PyErr_Clear();
|
||||||
|
|
||||||
// Try to allocate the image once more with smallest possible block size
|
// Try to allocate the image once more with smallest possible block size
|
||||||
MUTEX_LOCK(&ImagingDefaultArena.mutex);
|
MUTEX_LOCK(&ImagingDefaultArena.mutex);
|
||||||
|
@ -723,6 +724,8 @@ ImagingNewArrow(
|
||||||
int64_t pixels = (int64_t)xsize * (int64_t)ysize;
|
int64_t pixels = (int64_t)xsize * (int64_t)ysize;
|
||||||
|
|
||||||
// fmt:off // don't reformat this
|
// fmt:off // don't reformat this
|
||||||
|
// stored as a single array, one element per pixel, either single band
|
||||||
|
// or multiband, where each pixel is an I32.
|
||||||
if (((strcmp(schema->format, "I") == 0 // int32
|
if (((strcmp(schema->format, "I") == 0 // int32
|
||||||
&& im->pixelsize == 4 // 4xchar* storage
|
&& im->pixelsize == 4 // 4xchar* storage
|
||||||
&& im->bands >= 2) // INT32 into any INT32 Storage mode
|
&& im->bands >= 2) // INT32 into any INT32 Storage mode
|
||||||
|
@ -735,6 +738,7 @@ ImagingNewArrow(
|
||||||
return im;
|
return im;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Stored as [[r,g,b,a],...]
|
||||||
if (strcmp(schema->format, "+w:4") == 0 // 4 up array
|
if (strcmp(schema->format, "+w:4") == 0 // 4 up array
|
||||||
&& im->pixelsize == 4 // storage as 32 bpc
|
&& im->pixelsize == 4 // storage as 32 bpc
|
||||||
&& schema->n_children > 0 // make sure schema is well formed.
|
&& schema->n_children > 0 // make sure schema is well formed.
|
||||||
|
@ -750,6 +754,17 @@ ImagingNewArrow(
|
||||||
return im;
|
return im;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Stored as [r,g,b,a,r,g,b,a,...]
|
||||||
|
if (strcmp(schema->format, "C") == 0 // uint8
|
||||||
|
&& im->pixelsize == 4 // storage as 32 bpc
|
||||||
|
&& schema->n_children == 0 // make sure schema is well formed.
|
||||||
|
&& strcmp(im->arrow_band_format, "C") == 0 // expected format
|
||||||
|
&& 4 * pixels == external_array->length) { // expected length
|
||||||
|
// single flat array, interleaved storage.
|
||||||
|
if (ImagingBorrowArrow(im, external_array, 1, array_capsule)) {
|
||||||
|
return im;
|
||||||
|
}
|
||||||
|
}
|
||||||
// fmt: on
|
// fmt: on
|
||||||
ImagingDelete(im);
|
ImagingDelete(im);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -557,7 +557,8 @@ _decodeStrip(
|
||||||
(tdata_t)state->buffer,
|
(tdata_t)state->buffer,
|
||||||
strip_size
|
strip_size
|
||||||
) == -1) {
|
) == -1) {
|
||||||
TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))
|
TRACE(
|
||||||
|
("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))
|
||||||
);
|
);
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -929,6 +930,27 @@ ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingLibTiffEncodeCleanup(ImagingCodecState state) {
|
||||||
|
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
|
||||||
|
TIFF *tiff = clientstate->tiff;
|
||||||
|
|
||||||
|
if (!tiff) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// TIFFClose in libtiff calls tif_closeproc and TIFFCleanup
|
||||||
|
if (clientstate->fp) {
|
||||||
|
// Python will manage the closing of the file rather than libtiff
|
||||||
|
// So only call TIFFCleanup
|
||||||
|
TIFFCleanup(tiff);
|
||||||
|
} else {
|
||||||
|
// When tif_closeproc refers to our custom _tiffCloseProc though,
|
||||||
|
// that is fine, as it does not close the file
|
||||||
|
TIFFClose(tiff);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes) {
|
ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes) {
|
||||||
/* One shot encoder. Encode everything to the tiff in the clientstate.
|
/* One shot encoder. Encode everything to the tiff in the clientstate.
|
||||||
|
@ -1010,17 +1032,10 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
|
||||||
TRACE(("Encode Error, row %d\n", state->y));
|
TRACE(("Encode Error, row %d\n", state->y));
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
|
||||||
// TIFFClose in libtiff calls tif_closeproc and TIFFCleanup
|
|
||||||
if (clientstate->fp) {
|
if (clientstate->fp) {
|
||||||
// Python will manage the closing of the file rather than libtiff
|
|
||||||
// So only call TIFFCleanup
|
|
||||||
TIFFCleanup(tiff);
|
TIFFCleanup(tiff);
|
||||||
|
clientstate->tiff = NULL;
|
||||||
} else {
|
} else {
|
||||||
// When tif_closeproc refers to our custom _tiffCloseProc though,
|
|
||||||
// that is fine, as it does not close the file
|
|
||||||
TIFFClose(tiff);
|
|
||||||
}
|
|
||||||
if (!clientstate->fp) {
|
|
||||||
free(clientstate->data);
|
free(clientstate->data);
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -1036,22 +1051,11 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
|
||||||
TRACE(("Error flushing the tiff"));
|
TRACE(("Error flushing the tiff"));
|
||||||
// likely reason is memory.
|
// likely reason is memory.
|
||||||
state->errcode = IMAGING_CODEC_MEMORY;
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
if (clientstate->fp) {
|
|
||||||
TIFFCleanup(tiff);
|
|
||||||
} else {
|
|
||||||
TIFFClose(tiff);
|
|
||||||
}
|
|
||||||
if (!clientstate->fp) {
|
if (!clientstate->fp) {
|
||||||
free(clientstate->data);
|
free(clientstate->data);
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
TRACE(("Closing \n"));
|
|
||||||
if (clientstate->fp) {
|
|
||||||
TIFFCleanup(tiff);
|
|
||||||
} else {
|
|
||||||
TIFFClose(tiff);
|
|
||||||
}
|
|
||||||
// reset the clientstate metadata to use it to read out the buffer.
|
// reset the clientstate metadata to use it to read out the buffer.
|
||||||
clientstate->loc = 0;
|
clientstate->loc = 0;
|
||||||
clientstate->size = clientstate->eof; // redundant?
|
clientstate->size = clientstate->eof; // redundant?
|
||||||
|
|
|
@ -40,6 +40,8 @@ ImagingLibTiffInit(ImagingCodecState state, int fp, uint32_t offset);
|
||||||
extern int
|
extern int
|
||||||
ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp);
|
ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp);
|
||||||
extern int
|
extern int
|
||||||
|
ImagingLibTiffEncodeCleanup(ImagingCodecState state);
|
||||||
|
extern int
|
||||||
ImagingLibTiffMergeFieldInfo(
|
ImagingLibTiffMergeFieldInfo(
|
||||||
ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length
|
ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length
|
||||||
);
|
);
|
||||||
|
|
|
@ -137,6 +137,7 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
im->read_only = view.readonly;
|
||||||
im->destroy = mapping_destroy_buffer;
|
im->destroy = mapping_destroy_buffer;
|
||||||
|
|
||||||
Py_INCREF(target);
|
Py_INCREF(target);
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -3,7 +3,7 @@ requires =
|
||||||
tox>=4.2
|
tox>=4.2
|
||||||
env_list =
|
env_list =
|
||||||
lint
|
lint
|
||||||
py{py3, 313, 312, 311, 310, 39}
|
py{py3, 314, 313, 312, 311, 310, 39}
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
deps =
|
deps =
|
||||||
|
|
|
@ -11,8 +11,7 @@ For more extensive info, see the [Windows build instructions](build.rst).
|
||||||
* Requires Microsoft Visual Studio 2017 or newer with C++ component.
|
* Requires Microsoft Visual Studio 2017 or newer with C++ component.
|
||||||
* Requires NASM for libjpeg-turbo, a required dependency when using this script.
|
* Requires NASM for libjpeg-turbo, a required dependency when using this script.
|
||||||
* Requires CMake 3.15 or newer (available as Visual Studio component).
|
* Requires CMake 3.15 or newer (available as Visual Studio component).
|
||||||
* Tested on Windows Server 2022 with Visual Studio 2022 Enterprise and Windows Server
|
* Tested on Windows Server 2022 with Visual Studio 2022 Enterprise (GitHub Actions).
|
||||||
2019 with Visual Studio 2019 Enterprise (GitHub Actions).
|
|
||||||
|
|
||||||
Here's an example script to build on Windows:
|
Here's an example script to build on Windows:
|
||||||
|
|
||||||
|
|
|
@ -113,12 +113,12 @@ V = {
|
||||||
"BROTLI": "1.1.0",
|
"BROTLI": "1.1.0",
|
||||||
"FREETYPE": "2.13.3",
|
"FREETYPE": "2.13.3",
|
||||||
"FRIBIDI": "1.0.16",
|
"FRIBIDI": "1.0.16",
|
||||||
"HARFBUZZ": "11.1.0",
|
"HARFBUZZ": "11.2.1",
|
||||||
"JPEGTURBO": "3.1.0",
|
"JPEGTURBO": "3.1.1",
|
||||||
"LCMS2": "2.17",
|
"LCMS2": "2.17",
|
||||||
"LIBAVIF": "1.2.1",
|
"LIBAVIF": "1.3.0",
|
||||||
"LIBIMAGEQUANT": "4.3.4",
|
"LIBIMAGEQUANT": "4.3.4",
|
||||||
"LIBPNG": "1.6.47",
|
"LIBPNG": "1.6.49",
|
||||||
"LIBWEBP": "1.5.0",
|
"LIBWEBP": "1.5.0",
|
||||||
"OPENJPEG": "2.5.3",
|
"OPENJPEG": "2.5.3",
|
||||||
"TIFF": "4.7.0",
|
"TIFF": "4.7.0",
|
||||||
|
@ -385,10 +385,11 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||||
"bins": [r"*.dll"],
|
"bins": [r"*.dll"],
|
||||||
},
|
},
|
||||||
"libavif": {
|
"libavif": {
|
||||||
"url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['LIBAVIF']}.zip",
|
"url": f"https://github.com/AOMediaCodec/libavif/archive/v{V['LIBAVIF']}.tar.gz",
|
||||||
"filename": f"libavif-{V['LIBAVIF']}.zip",
|
"filename": f"libavif-{V['LIBAVIF']}.tar.gz",
|
||||||
"license": "LICENSE",
|
"license": "LICENSE",
|
||||||
"build": [
|
"build": [
|
||||||
|
"rustup update",
|
||||||
f"{sys.executable} -m pip install meson",
|
f"{sys.executable} -m pip install meson",
|
||||||
*cmds_cmake(
|
*cmds_cmake(
|
||||||
"avif_static",
|
"avif_static",
|
||||||
|
@ -399,7 +400,6 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||||
"-DAVIF_CODEC_DAV1D=LOCAL",
|
"-DAVIF_CODEC_DAV1D=LOCAL",
|
||||||
"-DAVIF_CODEC_RAV1E=LOCAL",
|
"-DAVIF_CODEC_RAV1E=LOCAL",
|
||||||
"-DAVIF_CODEC_SVT=LOCAL",
|
"-DAVIF_CODEC_SVT=LOCAL",
|
||||||
"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
|
|
||||||
),
|
),
|
||||||
cmd_xcopy("include", "{inc_dir}"),
|
cmd_xcopy("include", "{inc_dir}"),
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user