mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 18:06:18 +03:00
Merge branch 'main' into dds_rgb
This commit is contained in:
commit
8b44116773
48
.github/workflows/test-windows.yml
vendored
48
.github/workflows/test-windows.yml
vendored
|
@ -72,10 +72,10 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
id: install
|
id: install
|
||||||
run: |
|
run: |
|
||||||
7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
choco install nasm --no-progress
|
||||||
echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH
|
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
choco install ghostscript --version=10.0.0.20230317
|
choco install ghostscript --version=10.0.0.20230317 --no-progress
|
||||||
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
|
echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
# Install extra test images
|
# Install extra test images
|
||||||
|
@ -167,7 +167,6 @@ jobs:
|
||||||
- name: Build Pillow
|
- name: Build Pillow
|
||||||
run: |
|
run: |
|
||||||
$FLAGS="-C raqm=vendor -C fribidi=vendor"
|
$FLAGS="-C raqm=vendor -C fribidi=vendor"
|
||||||
if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS+=" -C imagequant=disable" }
|
|
||||||
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
|
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
|
||||||
& $env:pythonLocation\python.exe selftest.py --installed
|
& $env:pythonLocation\python.exe selftest.py --installed
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
@ -209,47 +208,6 @@ jobs:
|
||||||
flags: GHA_Windows
|
flags: GHA_Windows
|
||||||
name: ${{ runner.os }} Python ${{ matrix.python-version }}
|
name: ${{ runner.os }} Python ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Build wheel
|
|
||||||
id: wheel
|
|
||||||
if: "github.event_name != 'pull_request'"
|
|
||||||
run: |
|
|
||||||
mkdir fribidi
|
|
||||||
copy winbuild\build\bin\fribidi* fribidi
|
|
||||||
setlocal EnableDelayedExpansion
|
|
||||||
for %%f in (winbuild\build\license\*) do (
|
|
||||||
set x=%%~nf
|
|
||||||
rem Skip FriBiDi license, it is not included in the wheel.
|
|
||||||
set fribidi=!x:~0,7!
|
|
||||||
if NOT !fribidi!==fribidi (
|
|
||||||
rem Skip imagequant license, it is not included in the wheel.
|
|
||||||
set libimagequant=!x:~0,13!
|
|
||||||
if NOT !libimagequant!==libimagequant (
|
|
||||||
echo. >> LICENSE
|
|
||||||
echo ===== %%~nf ===== >> LICENSE
|
|
||||||
echo. >> LICENSE
|
|
||||||
type %%f >> LICENSE
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo dist=dist-%%a >> %GITHUB_OUTPUT%
|
|
||||||
call winbuild\\build\\build_env.cmd
|
|
||||||
%pythonLocation%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor -C imagequant=disable .
|
|
||||||
shell: cmd
|
|
||||||
|
|
||||||
- name: Upload wheel
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
if: "github.event_name != 'pull_request'"
|
|
||||||
with:
|
|
||||||
name: ${{ steps.wheel.outputs.dist }}
|
|
||||||
path: "*.whl"
|
|
||||||
|
|
||||||
- name: Upload fribidi.dll
|
|
||||||
if: "github.event_name != 'pull_request' && matrix.python-version == 3.11"
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: fribidi
|
|
||||||
path: fribidi\*
|
|
||||||
|
|
||||||
success:
|
success:
|
||||||
permissions:
|
permissions:
|
||||||
contents: none
|
contents: none
|
||||||
|
|
4
.github/workflows/wheels-dependencies.sh
vendored
4
.github/workflows/wheels-dependencies.sh
vendored
|
@ -16,13 +16,13 @@ ARCHIVE_SDIR=pillow-depends-main
|
||||||
|
|
||||||
# Package versions for fresh source builds
|
# Package versions for fresh source builds
|
||||||
FREETYPE_VERSION=2.13.2
|
FREETYPE_VERSION=2.13.2
|
||||||
HARFBUZZ_VERSION=8.2.1
|
HARFBUZZ_VERSION=8.3.0
|
||||||
LIBPNG_VERSION=1.6.40
|
LIBPNG_VERSION=1.6.40
|
||||||
JPEGTURBO_VERSION=3.0.1
|
JPEGTURBO_VERSION=3.0.1
|
||||||
OPENJPEG_VERSION=2.5.0
|
OPENJPEG_VERSION=2.5.0
|
||||||
XZ_VERSION=5.4.5
|
XZ_VERSION=5.4.5
|
||||||
TIFF_VERSION=4.6.0
|
TIFF_VERSION=4.6.0
|
||||||
LCMS2_VERSION=2.15
|
LCMS2_VERSION=2.16
|
||||||
if [[ -n "$IS_MACOS" ]]; then
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
GIFLIB_VERSION=5.1.4
|
GIFLIB_VERSION=5.1.4
|
||||||
else
|
else
|
||||||
|
|
22
.github/workflows/wheels-test.ps1
vendored
Normal file
22
.github/workflows/wheels-test.ps1
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
param ([string]$venv, [string]$pillow="C:\pillow")
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
Set-PSDebug -Trace 1
|
||||||
|
if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") {
|
||||||
|
# unlike CPython, PyPy requires Visual C++ Redistributable to be installed
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||||
|
Invoke-WebRequest -Uri 'https://aka.ms/vs/15/release/vc_redist.x64.exe' -OutFile 'vc_redist.x64.exe'
|
||||||
|
C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null
|
||||||
|
}
|
||||||
|
$env:path += ";$pillow\winbuild\build\bin\"
|
||||||
|
& "$venv\Scripts\activate.ps1"
|
||||||
|
& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f
|
||||||
|
cd $pillow
|
||||||
|
& python -VV
|
||||||
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
|
& python selftest.py
|
||||||
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
|
& python -m pytest -vx Tests\check_wheel.py
|
||||||
|
if (!$?) { exit $LASTEXITCODE }
|
||||||
|
& python -m pytest -vx Tests
|
||||||
|
if (!$?) { exit $LASTEXITCODE }
|
22
.github/workflows/wheels-test.sh
vendored
22
.github/workflows/wheels-test.sh
vendored
|
@ -1,10 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
EXP_CODECS="jpg jpg_2000 libtiff zlib"
|
|
||||||
EXP_MODULES="freetype2 littlecms2 pil tkinter webp"
|
|
||||||
EXP_FEATURES="fribidi harfbuzz libjpeg_turbo raqm transp_webp webp_anim webp_mux xcb"
|
|
||||||
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
brew install fribidi
|
brew install fribidi
|
||||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||||
|
@ -25,21 +21,5 @@ fi
|
||||||
|
|
||||||
# Runs tests
|
# Runs tests
|
||||||
python3 selftest.py
|
python3 selftest.py
|
||||||
|
python3 -m pytest Tests/check_wheel.py
|
||||||
python3 -m pytest
|
python3 -m pytest
|
||||||
|
|
||||||
# Test against expected codecs, modules and features
|
|
||||||
codecs=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_codecs())))')
|
|
||||||
if [ "$codecs" != "$EXP_CODECS" ]; then
|
|
||||||
echo "Codecs should be: '$EXP_CODECS'; but are '$codecs'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
modules=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_modules())))')
|
|
||||||
if [ "$modules" != "$EXP_MODULES" ]; then
|
|
||||||
echo "Modules should be: '$EXP_MODULES'; but are '$modules'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
features=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_features())))')
|
|
||||||
if [ "$features" != "$EXP_FEATURES" ]; then
|
|
||||||
echo "Features should be: '$EXP_FEATURES'; but are '$features'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
109
.github/workflows/wheels.yml
vendored
109
.github/workflows/wheels.yml
vendored
|
@ -3,14 +3,20 @@ name: Wheels
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/wheels*.yml"
|
- ".ci/requirements-cibw.txt"
|
||||||
|
- ".github/workflows/wheel*"
|
||||||
- "wheels/*"
|
- "wheels/*"
|
||||||
|
- "winbuild/build_prepare.py"
|
||||||
|
- "winbuild/fribidi.cmake"
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- "*"
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- ".github/workflows/wheels*.yml"
|
- ".ci/requirements-cibw.txt"
|
||||||
|
- ".github/workflows/wheel*"
|
||||||
- "wheels/*"
|
- "wheels/*"
|
||||||
|
- "winbuild/build_prepare.py"
|
||||||
|
- "winbuild/fribidi.cmake"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -63,7 +69,6 @@ jobs:
|
||||||
env:
|
env:
|
||||||
CIBW_ARCHS: ${{ matrix.archs }}
|
CIBW_ARCHS: ${{ matrix.archs }}
|
||||||
CIBW_BUILD: ${{ matrix.build }}
|
CIBW_BUILD: ${{ matrix.build }}
|
||||||
CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor
|
|
||||||
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: pp38-*
|
CIBW_SKIP: pp38-*
|
||||||
|
@ -75,6 +80,102 @@ jobs:
|
||||||
name: dist
|
name: dist
|
||||||
path: ./wheelhouse/*.whl
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
|
windows:
|
||||||
|
name: Windows ${{ matrix.arch }}
|
||||||
|
runs-on: windows-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- arch: x86
|
||||||
|
cibw_arch: x86
|
||||||
|
- arch: x64
|
||||||
|
cibw_arch: AMD64
|
||||||
|
- arch: ARM64
|
||||||
|
cibw_arch: ARM64
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Checkout extra test images
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: python-pillow/test-images
|
||||||
|
path: Tests\test-images
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- name: Prepare for build
|
||||||
|
run: |
|
||||||
|
choco install nasm --no-progress
|
||||||
|
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||||
|
|
||||||
|
# Install extra test images
|
||||||
|
xcopy /S /Y Tests\test-images\* Tests\images
|
||||||
|
|
||||||
|
& python.exe -m pip install -r .ci/requirements-cibw.txt
|
||||||
|
|
||||||
|
# Cannot cross-compile FriBiDi (only used for tests)
|
||||||
|
$FLAGS = ("--no-imagequant", "--architecture=${{ matrix.arch }}")
|
||||||
|
if ('${{ matrix.arch }}' -eq 'ARM64') { $FLAGS += "--no-fribidi" }
|
||||||
|
& python.exe winbuild\build_prepare.py -v @FLAGS
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
run: |
|
||||||
|
setlocal EnableDelayedExpansion
|
||||||
|
for %%f in (winbuild\build\license\*) do (
|
||||||
|
set x=%%~nf
|
||||||
|
rem Skip FriBiDi license, it is not included in the wheel.
|
||||||
|
set fribidi=!x:~0,7!
|
||||||
|
if NOT !fribidi!==fribidi (
|
||||||
|
rem Skip imagequant license, it is not included in the wheel.
|
||||||
|
set libimagequant=!x:~0,13!
|
||||||
|
if NOT !libimagequant!==libimagequant (
|
||||||
|
echo. >> LICENSE
|
||||||
|
echo ===== %%~nf ===== >> LICENSE
|
||||||
|
echo. >> LICENSE
|
||||||
|
type %%f >> LICENSE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
call winbuild\\build\\build_env.cmd
|
||||||
|
%pythonLocation%\python.exe -m cibuildwheel . --output-dir wheelhouse
|
||||||
|
env:
|
||||||
|
CIBW_ARCHS: ${{ matrix.cibw_arch }}
|
||||||
|
CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd"
|
||||||
|
CIBW_CACHE_PATH: "C:\\cibw"
|
||||||
|
CIBW_TEST_SKIP: "*-win_arm64"
|
||||||
|
CIBW_TEST_COMMAND: 'docker run --rm
|
||||||
|
-v {project}:C:\pillow
|
||||||
|
-v C:\cibw:C:\cibw
|
||||||
|
-v %CD%\..\venv-test:%CD%\..\venv-test
|
||||||
|
-e CI -e GITHUB_ACTIONS
|
||||||
|
mcr.microsoft.com/windows/servercore:ltsc2022
|
||||||
|
powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test'
|
||||||
|
shell: cmd
|
||||||
|
|
||||||
|
- name: Upload wheels
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
|
||||||
|
- name: Prepare to upload FriBiDi
|
||||||
|
if: "matrix.arch != 'ARM64'"
|
||||||
|
run: |
|
||||||
|
mkdir fribidi\${{ matrix.arch }}
|
||||||
|
copy winbuild\build\bin\fribidi* fribidi\${{ matrix.arch }}
|
||||||
|
shell: cmd
|
||||||
|
|
||||||
|
- name: Upload fribidi.dll
|
||||||
|
if: "matrix.arch != 'ARM64'"
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: fribidi
|
||||||
|
path: fribidi\*
|
||||||
|
|
||||||
sdist:
|
sdist:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
@ -97,7 +198,7 @@ jobs:
|
||||||
success:
|
success:
|
||||||
permissions:
|
permissions:
|
||||||
contents: none
|
contents: none
|
||||||
needs: [build, sdist]
|
needs: [build, windows, sdist]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Wheels Successful
|
name: Wheels Successful
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.1.4
|
rev: v0.1.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
rev: 23.10.1
|
rev: 23.11.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
|
@ -42,12 +42,12 @@ repos:
|
||||||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||||
|
|
||||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||||
rev: v0.8.1
|
rev: v0.9.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: sphinx-lint
|
- id: sphinx-lint
|
||||||
|
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
rev: 1.4.1
|
rev: 1.5.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ if: tag IS present OR type = api
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- CIBW_ARCHS=aarch64
|
- CIBW_ARCHS=aarch64
|
||||||
- CIBW_CONFIG_SETTINGS="raqm=enable raqm=vendor fribidi=vendor"
|
|
||||||
- CIBW_SKIP=pp38-*
|
- CIBW_SKIP=pp38-*
|
||||||
|
|
||||||
language: python
|
language: python
|
||||||
|
|
20
CHANGES.rst
20
CHANGES.rst
|
@ -5,7 +5,25 @@ Changelog (Pillow)
|
||||||
10.2.0 (unreleased)
|
10.2.0 (unreleased)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
- Raise ValueError when TrueType font size is not greater than zero #7584
|
- Optimize ImageStat.Stat.extrema #7593
|
||||||
|
[florath, radarhere]
|
||||||
|
|
||||||
|
- Handle pathlib.Path in FreeTypeFont #7578
|
||||||
|
[radarhere, hugovk, nulano]
|
||||||
|
|
||||||
|
- Added support for reading DX10 BC4 DDS images #7603
|
||||||
|
[sambvfx, radarhere]
|
||||||
|
|
||||||
|
- Optimized ImageStat.Stat.count #7599
|
||||||
|
[florath]
|
||||||
|
|
||||||
|
- Correct PDF palette size when saving #7555
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Fixed closing file pointer with olefile 0.47 #7594
|
||||||
|
[radarhere]
|
||||||
|
|
||||||
|
- Raise ValueError when TrueType font size is not greater than zero #7584, #7587
|
||||||
[akx, radarhere]
|
[akx, radarhere]
|
||||||
|
|
||||||
- If absent, do not try to close fp when closing image #7557
|
- If absent, do not try to close fp when closing image #7557
|
||||||
|
|
|
@ -94,7 +94,6 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
|
|
||||||
## Source and Binary Distributions
|
## Source and Binary Distributions
|
||||||
|
|
||||||
### macOS and Linux
|
|
||||||
* [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
* [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml)
|
||||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
|
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
|
||||||
```bash
|
```bash
|
||||||
|
@ -104,14 +103,6 @@ Released as needed privately to individual vendors for critical security-related
|
||||||
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
|
* [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases)
|
||||||
and copy into `dist`.
|
and copy into `dist`.
|
||||||
|
|
||||||
### Windows
|
|
||||||
* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml)
|
|
||||||
and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli):
|
|
||||||
```bash
|
|
||||||
gh run download --dir dist
|
|
||||||
# select dist-x.y.z
|
|
||||||
```
|
|
||||||
|
|
||||||
## Publicize Release
|
## Publicize Release
|
||||||
|
|
||||||
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
|
* [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010
|
||||||
|
|
41
Tests/check_wheel.py
Normal file
41
Tests/check_wheel.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PIL import features
|
||||||
|
|
||||||
|
|
||||||
|
def test_wheel_modules():
|
||||||
|
expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"}
|
||||||
|
|
||||||
|
# tkinter is not available in cibuildwheel installed CPython on Windows
|
||||||
|
try:
|
||||||
|
import tkinter
|
||||||
|
|
||||||
|
assert tkinter
|
||||||
|
except ImportError:
|
||||||
|
expected_modules.remove("tkinter")
|
||||||
|
|
||||||
|
assert set(features.get_supported_modules()) == expected_modules
|
||||||
|
|
||||||
|
|
||||||
|
def test_wheel_codecs():
|
||||||
|
expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"}
|
||||||
|
|
||||||
|
assert set(features.get_supported_codecs()) == expected_codecs
|
||||||
|
|
||||||
|
|
||||||
|
def test_wheel_features():
|
||||||
|
expected_features = {
|
||||||
|
"webp_anim",
|
||||||
|
"webp_mux",
|
||||||
|
"transp_webp",
|
||||||
|
"raqm",
|
||||||
|
"fribidi",
|
||||||
|
"harfbuzz",
|
||||||
|
"libjpeg_turbo",
|
||||||
|
"xcb",
|
||||||
|
}
|
||||||
|
|
||||||
|
if sys.platform == "win32":
|
||||||
|
expected_features.remove("xcb")
|
||||||
|
|
||||||
|
assert set(features.get_supported_features()) == expected_features
|
|
@ -5,6 +5,7 @@ Helper functions.
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -258,11 +259,21 @@ def hopper(mode=None, cache={}):
|
||||||
|
|
||||||
|
|
||||||
def djpeg_available():
|
def djpeg_available():
|
||||||
return bool(shutil.which("djpeg"))
|
if shutil.which("djpeg"):
|
||||||
|
try:
|
||||||
|
subprocess.check_call(["djpeg", "-version"])
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError: # pragma: no cover
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def cjpeg_available():
|
def cjpeg_available():
|
||||||
return bool(shutil.which("cjpeg"))
|
if shutil.which("cjpeg"):
|
||||||
|
try:
|
||||||
|
subprocess.check_call(["cjpeg", "-version"])
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError: # pragma: no cover
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def netpbm_available():
|
def netpbm_available():
|
||||||
|
|
BIN
Tests/images/bc1.dds
Executable file
BIN
Tests/images/bc1.dds
Executable file
Binary file not shown.
BIN
Tests/images/bc1_typeless.dds
Executable file
BIN
Tests/images/bc1_typeless.dds
Executable file
Binary file not shown.
BIN
Tests/images/bc4_typeless.dds
Normal file
BIN
Tests/images/bc4_typeless.dds
Normal file
Binary file not shown.
BIN
Tests/images/bc4_unorm.dds
Normal file
BIN
Tests/images/bc4_unorm.dds
Normal file
Binary file not shown.
BIN
Tests/images/bc4_unorm.png
Normal file
BIN
Tests/images/bc4_unorm.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 982 B |
BIN
Tests/images/bc4u.dds
Normal file
BIN
Tests/images/bc4u.dds
Normal file
Binary file not shown.
Binary file not shown.
BIN
Tests/images/unsupported_bitcount.dds
Normal file
BIN
Tests/images/unsupported_bitcount.dds
Normal file
Binary file not shown.
|
@ -356,9 +356,7 @@ def test_apng_save(tmp_path):
|
||||||
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
assert im.getpixel((64, 32)) == (0, 255, 0, 255)
|
||||||
|
|
||||||
with Image.open("Tests/images/apng/single_frame_default.png") as im:
|
with Image.open("Tests/images/apng/single_frame_default.png") as im:
|
||||||
frames = []
|
frames = [frame_im.copy() for frame_im in ImageSequence.Iterator(im)]
|
||||||
for frame_im in ImageSequence.Iterator(im):
|
|
||||||
frames.append(frame_im.copy())
|
|
||||||
frames[0].save(
|
frames[0].save(
|
||||||
test_file, save_all=True, default_image=True, append_images=frames[1:]
|
test_file, save_all=True, default_image=True, append_images=frames[1:]
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,9 +12,14 @@ TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
|
||||||
TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds"
|
TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds"
|
||||||
TEST_FILE_ATI1 = "Tests/images/ati1.dds"
|
TEST_FILE_ATI1 = "Tests/images/ati1.dds"
|
||||||
TEST_FILE_ATI2 = "Tests/images/ati2.dds"
|
TEST_FILE_ATI2 = "Tests/images/ati2.dds"
|
||||||
|
TEST_FILE_DX10_BC4_TYPELESS = "Tests/images/bc4_typeless.dds"
|
||||||
|
TEST_FILE_DX10_BC4_UNORM = "Tests/images/bc4_unorm.dds"
|
||||||
TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds"
|
TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds"
|
||||||
TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
|
TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
|
||||||
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
|
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
|
||||||
|
TEST_FILE_DX10_BC1 = "Tests/images/bc1.dds"
|
||||||
|
TEST_FILE_DX10_BC1_TYPELESS = "Tests/images/bc1_typeless.dds"
|
||||||
|
TEST_FILE_BC4U = "Tests/images/bc4u.dds"
|
||||||
TEST_FILE_BC5S = "Tests/images/bc5s.dds"
|
TEST_FILE_BC5S = "Tests/images/bc5s.dds"
|
||||||
TEST_FILE_BC5U = "Tests/images/bc5u.dds"
|
TEST_FILE_BC5U = "Tests/images/bc5u.dds"
|
||||||
TEST_FILE_BC6H = "Tests/images/bc6h.dds"
|
TEST_FILE_BC6H = "Tests/images/bc6h.dds"
|
||||||
|
@ -30,11 +35,20 @@ TEST_FILE_UNCOMPRESSED_BGR15 = "Tests/images/bgr15.dds"
|
||||||
TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
|
TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
|
||||||
|
|
||||||
|
|
||||||
def test_sanity_dxt1():
|
@pytest.mark.parametrize(
|
||||||
"""Check DXT1 images can be opened"""
|
"image_path",
|
||||||
|
(
|
||||||
|
TEST_FILE_DXT1,
|
||||||
|
# hexeditted to use DX10 FourCC
|
||||||
|
TEST_FILE_DX10_BC1,
|
||||||
|
TEST_FILE_DX10_BC1_TYPELESS,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_sanity_dxt1_bc1(image_path):
|
||||||
|
"""Check DXT1 and BC1 images can be opened"""
|
||||||
with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target:
|
with Image.open(TEST_FILE_DXT1.replace(".dds", ".png")) as target:
|
||||||
target = target.convert("RGBA")
|
target = target.convert("RGBA")
|
||||||
with Image.open(TEST_FILE_DXT1) as im:
|
with Image.open(image_path) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert im.format == "DDS"
|
assert im.format == "DDS"
|
||||||
|
@ -70,10 +84,18 @@ def test_sanity_dxt5():
|
||||||
assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png"))
|
assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png"))
|
||||||
|
|
||||||
|
|
||||||
def test_sanity_ati1():
|
@pytest.mark.parametrize(
|
||||||
"""Check ATI1 images can be opened"""
|
"image_path",
|
||||||
|
(
|
||||||
|
TEST_FILE_ATI1,
|
||||||
|
# hexeditted to use BC4U FourCC
|
||||||
|
TEST_FILE_BC4U,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_sanity_ati1_bc4u(image_path):
|
||||||
|
"""Check ATI1 and BC4U images can be opened"""
|
||||||
|
|
||||||
with Image.open(TEST_FILE_ATI1) as im:
|
with Image.open(image_path) as im:
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
assert im.format == "DDS"
|
assert im.format == "DDS"
|
||||||
|
@ -83,6 +105,27 @@ def test_sanity_ati1():
|
||||||
assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
|
assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png"))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"image_path",
|
||||||
|
(
|
||||||
|
TEST_FILE_DX10_BC4_UNORM,
|
||||||
|
# hexeditted to be typeless
|
||||||
|
TEST_FILE_DX10_BC4_TYPELESS,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_dx10_bc4(image_path):
|
||||||
|
"""Check DX10 BC4 images can be opened"""
|
||||||
|
|
||||||
|
with Image.open(image_path) as im:
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
assert im.format == "DDS"
|
||||||
|
assert im.mode == "L"
|
||||||
|
assert im.size == (64, 64)
|
||||||
|
|
||||||
|
assert_image_equal_tofile(im, TEST_FILE_DX10_BC4_UNORM.replace(".dds", ".png"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"image_path",
|
"image_path",
|
||||||
(
|
(
|
||||||
|
@ -200,12 +243,6 @@ def test_dx10_r8g8b8a8_unorm_srgb():
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_unimplemented_dxgi_format():
|
|
||||||
with pytest.raises(NotImplementedError):
|
|
||||||
with Image.open("Tests/images/unimplemented_dxgi_format.dds"):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("mode", "size", "test_file"),
|
("mode", "size", "test_file"),
|
||||||
[
|
[
|
||||||
|
@ -305,9 +342,22 @@ def test_palette():
|
||||||
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
|
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
|
||||||
|
|
||||||
|
|
||||||
def test_unimplemented_pixel_format():
|
def test_unsupported_bitcount():
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
with Image.open("Tests/images/unsupported_bitcount.dds"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"test_file",
|
||||||
|
(
|
||||||
|
"Tests/images/unimplemented_dxgi_format.dds",
|
||||||
|
"Tests/images/unimplemented_pfflags.dds",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_not_implemented(test_file):
|
||||||
with pytest.raises(NotImplementedError):
|
with pytest.raises(NotImplementedError):
|
||||||
with Image.open("Tests/images/unimplemented_pixel_format.dds"):
|
with Image.open(test_file):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ def test_quantize_no_dither():
|
||||||
|
|
||||||
def test_quantize_no_dither2():
|
def test_quantize_no_dither2():
|
||||||
im = Image.new("RGB", (9, 1))
|
im = Image.new("RGB", (9, 1))
|
||||||
im.putdata(list((p,) * 3 for p in range(0, 36, 4)))
|
im.putdata([(p,) * 3 for p in range(0, 36, 4)])
|
||||||
|
|
||||||
palette = Image.new("P", (1, 1))
|
palette = Image.new("P", (1, 1))
|
||||||
data = (0, 0, 0, 32, 32, 32)
|
data = (0, 0, 0, 32, 32, 32)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from packaging.version import parse as parse_version
|
from packaging.version import parse as parse_version
|
||||||
|
@ -76,8 +77,9 @@ def _render(font, layout_engine):
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
def test_font_with_name(layout_engine):
|
@pytest.mark.parametrize("font", (FONT_PATH, Path(FONT_PATH)))
|
||||||
_render(FONT_PATH, layout_engine)
|
def test_font_with_name(layout_engine, font):
|
||||||
|
_render(font, layout_engine)
|
||||||
|
|
||||||
|
|
||||||
def test_font_with_filelike(layout_engine):
|
def test_font_with_filelike(layout_engine):
|
||||||
|
|
|
@ -11,6 +11,10 @@ from .helper import assert_image_equal_tofile, skip_unless_feature
|
||||||
|
|
||||||
|
|
||||||
class TestImageGrab:
|
class TestImageGrab:
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
os.environ.get("USERNAME") == "ContainerAdministrator",
|
||||||
|
reason="can't grab screen when running in Docker",
|
||||||
|
)
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS"
|
sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS"
|
||||||
)
|
)
|
||||||
|
|
|
@ -521,6 +521,19 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options:
|
||||||
|
|
||||||
.. versionadded:: 2.5.0
|
.. versionadded:: 2.5.0
|
||||||
|
|
||||||
|
**streamtype**
|
||||||
|
Allows storing images without quantization and Huffman tables, or with
|
||||||
|
these tables but without image data. This is useful for container formats
|
||||||
|
or network protocols that handle tables separately and share them between
|
||||||
|
images.
|
||||||
|
|
||||||
|
* ``0`` (default): interchange datastream, with tables and image data
|
||||||
|
* ``1``: abbreviated table specification (tables-only) datastream
|
||||||
|
|
||||||
|
.. versionadded:: 10.2.0
|
||||||
|
|
||||||
|
* ``2``: abbreviated image (image-only) datastream
|
||||||
|
|
||||||
**comment**
|
**comment**
|
||||||
A comment about the image.
|
A comment about the image.
|
||||||
|
|
||||||
|
|
|
@ -95,11 +95,10 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images::
|
||||||
|
|
||||||
.. tab:: Windows
|
.. tab:: Windows
|
||||||
|
|
||||||
.. warning:: Pillow > 9.5.0 no longer includes 32-bit wheels.
|
We provide Pillow binaries for Windows compiled for the matrix of supported
|
||||||
|
Pythons in the wheel format. These include x86, x86-64 and arm64 versions
|
||||||
We provide Pillow binaries for Windows compiled for the matrix of
|
(with the exception of Python 3.8 on arm64). These binaries include support
|
||||||
supported Pythons in 64-bit versions in the wheel format. These binaries include
|
for all optional libraries except libimagequant and libxcb. Raqm support
|
||||||
support for all optional libraries except libimagequant and libxcb. Raqm support
|
|
||||||
requires FriBiDi to be installed separately::
|
requires FriBiDi to be installed separately::
|
||||||
|
|
||||||
python3 -m pip install --upgrade pip
|
python3 -m pip install --upgrade pip
|
||||||
|
@ -176,7 +175,7 @@ Many of Pillow's features require external libraries:
|
||||||
* **littlecms** provides color management
|
* **littlecms** provides color management
|
||||||
|
|
||||||
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
|
* Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and
|
||||||
above uses liblcms2. Tested with **1.19** and **2.7-2.15**.
|
above uses liblcms2. Tested with **1.19** and **2.7-2.16**.
|
||||||
|
|
||||||
* **libwebp** provides the WebP format.
|
* **libwebp** provides the WebP format.
|
||||||
|
|
||||||
|
|
|
@ -70,21 +70,20 @@ Methods
|
||||||
Constants
|
Constants
|
||||||
---------
|
---------
|
||||||
|
|
||||||
.. data:: PIL.ImageFont.Layout.BASIC
|
.. class:: Layout
|
||||||
|
|
||||||
Use basic text layout for TrueType font.
|
.. py:attribute:: BASIC
|
||||||
Advanced features such as text direction are not supported.
|
|
||||||
|
|
||||||
.. data:: PIL.ImageFont.Layout.RAQM
|
Use basic text layout for TrueType font.
|
||||||
|
Advanced features such as text direction are not supported.
|
||||||
|
|
||||||
Use Raqm text layout for TrueType font.
|
.. py:attribute:: RAQM
|
||||||
Advanced features are supported.
|
|
||||||
|
|
||||||
Requires Raqm, you can check support using
|
Use Raqm text layout for TrueType font.
|
||||||
:py:func:`PIL.features.check_feature` with ``feature="raqm"``.
|
Advanced features are supported.
|
||||||
|
|
||||||
Constants
|
Requires Raqm, you can check support using
|
||||||
---------
|
:py:func:`PIL.features.check_feature` with ``feature="raqm"``.
|
||||||
|
|
||||||
.. data:: MAX_STRING_LENGTH
|
.. data:: MAX_STRING_LENGTH
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,14 @@ Plugin reference
|
||||||
:undoc-members:
|
:undoc-members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
:mod:`~PIL.DdsImagePlugin` Module
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
.. automodule:: PIL.DdsImagePlugin
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
:mod:`~PIL.EpsImagePlugin` Module
|
:mod:`~PIL.EpsImagePlugin` Module
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
|
56
docs/releasenotes/10.2.0.rst
Normal file
56
docs/releasenotes/10.2.0.rst
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
10.2.0
|
||||||
|
------
|
||||||
|
|
||||||
|
Backwards Incompatible Changes
|
||||||
|
==============================
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
============
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
API Changes
|
||||||
|
===========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
API Additions
|
||||||
|
=============
|
||||||
|
|
||||||
|
Added DdsImagePlugin enums
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
:py:class:`~PIL.DdsImagePlugin.DDSD`, :py:class:`~PIL.DdsImagePlugin.DDSCAPS`,
|
||||||
|
:py:class:`~PIL.DdsImagePlugin.DDSCAPS2`, :py:class:`~PIL.DdsImagePlugin.DDPF`,
|
||||||
|
:py:class:`~PIL.DdsImagePlugin.DXGI_FORMAT` and :py:class:`~PIL.DdsImagePlugin.D3DFMT`
|
||||||
|
enums have been added to :py:class:`PIL.DdsImagePlugin`.
|
||||||
|
|
||||||
|
Security
|
||||||
|
========
|
||||||
|
|
||||||
|
TODO
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
Other Changes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Added DDS BC4U and DX10 BC1 and BC4 reading
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Support has been added to read the BC4U format of DDS images.
|
||||||
|
|
||||||
|
Support has also been added to read DX10 BC1 and BC4, whether UNORM or
|
||||||
|
TYPELESS.
|
|
@ -14,6 +14,7 @@ expected to be backported to earlier versions.
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
10.2.0
|
||||||
10.1.0
|
10.1.0
|
||||||
10.0.1
|
10.0.1
|
||||||
10.0.0
|
10.0.0
|
||||||
|
|
|
@ -88,12 +88,15 @@ version = {attr = "PIL.__version__"}
|
||||||
|
|
||||||
[tool.cibuildwheel]
|
[tool.cibuildwheel]
|
||||||
before-all = ".github/workflows/wheels-dependencies.sh"
|
before-all = ".github/workflows/wheels-dependencies.sh"
|
||||||
|
build-verbosity = 1
|
||||||
|
config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable"
|
||||||
test-command = "cd {project} && .github/workflows/wheels-test.sh"
|
test-command = "cd {project} && .github/workflows/wheels-test.sh"
|
||||||
test-extras = "tests"
|
test-extras = "tests"
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
select = [
|
select = [
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
"E", # pycodestyle errors
|
"E", # pycodestyle errors
|
||||||
"EM", # flake8-errmsg
|
"EM", # flake8-errmsg
|
||||||
"F", # pyflakes errors
|
"F", # pyflakes errors
|
||||||
|
|
22
setup.py
22
setup.py
|
@ -440,17 +440,17 @@ class pil_build_ext(build_ext):
|
||||||
|
|
||||||
#
|
#
|
||||||
# add configured kits
|
# add configured kits
|
||||||
for root_name, lib_name in dict(
|
for root_name, lib_name in {
|
||||||
JPEG_ROOT="libjpeg",
|
"JPEG_ROOT": "libjpeg",
|
||||||
JPEG2K_ROOT="libopenjp2",
|
"JPEG2K_ROOT": "libopenjp2",
|
||||||
TIFF_ROOT=("libtiff-5", "libtiff-4"),
|
"TIFF_ROOT": ("libtiff-5", "libtiff-4"),
|
||||||
ZLIB_ROOT="zlib",
|
"ZLIB_ROOT": "zlib",
|
||||||
FREETYPE_ROOT="freetype2",
|
"FREETYPE_ROOT": "freetype2",
|
||||||
HARFBUZZ_ROOT="harfbuzz",
|
"HARFBUZZ_ROOT": "harfbuzz",
|
||||||
FRIBIDI_ROOT="fribidi",
|
"FRIBIDI_ROOT": "fribidi",
|
||||||
LCMS_ROOT="lcms2",
|
"LCMS_ROOT": "lcms2",
|
||||||
IMAGEQUANT_ROOT="libimagequant",
|
"IMAGEQUANT_ROOT": "libimagequant",
|
||||||
).items():
|
}.items():
|
||||||
root = globals()[root_name]
|
root = globals()[root_name]
|
||||||
|
|
||||||
if root is None and root_name in os.environ:
|
if root is None and root_name in os.environ:
|
||||||
|
|
|
@ -396,7 +396,7 @@ def _save(im, fp, filename, bitmap_header=True):
|
||||||
dpi = info.get("dpi", (96, 96))
|
dpi = info.get("dpi", (96, 96))
|
||||||
|
|
||||||
# 1 meter == 39.3701 inches
|
# 1 meter == 39.3701 inches
|
||||||
ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi))
|
ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi)
|
||||||
|
|
||||||
stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
|
stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3)
|
||||||
header = 40 # or 64 for OS/2 version 2
|
header = 40 # or 64 for OS/2 version 2
|
||||||
|
|
|
@ -64,8 +64,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
||||||
d, e, o, a = self.tile[0]
|
d, e, o, a = self.tile[0]
|
||||||
self.tile[0] = d, (0, 0) + self.size, o, a
|
self.tile[0] = d, (0, 0) + self.size, o, a
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
|
|
@ -3,110 +3,322 @@ A Pillow loader for .dds files (S3TC-compressed aka DXTC)
|
||||||
Jerome Leclanche <jerome@leclan.ch>
|
Jerome Leclanche <jerome@leclan.ch>
|
||||||
|
|
||||||
Documentation:
|
Documentation:
|
||||||
https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
|
https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
|
||||||
|
|
||||||
The contents of this file are hereby released in the public domain (CC0)
|
The contents of this file are hereby released in the public domain (CC0)
|
||||||
Full text of the CC0 license:
|
Full text of the CC0 license:
|
||||||
https://creativecommons.org/publicdomain/zero/1.0/
|
https://creativecommons.org/publicdomain/zero/1.0/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import io
|
||||||
import struct
|
import struct
|
||||||
from io import BytesIO
|
import sys
|
||||||
|
from enum import IntEnum, IntFlag
|
||||||
|
|
||||||
from . import Image, ImageFile, ImagePalette
|
from . import Image, ImageFile, ImagePalette
|
||||||
from ._binary import o8
|
from ._binary import o8
|
||||||
|
from ._binary import i32le as i32
|
||||||
from ._binary import o32le as o32
|
from ._binary import o32le as o32
|
||||||
|
|
||||||
# Magic ("DDS ")
|
# Magic ("DDS ")
|
||||||
DDS_MAGIC = 0x20534444
|
DDS_MAGIC = 0x20534444
|
||||||
|
|
||||||
|
|
||||||
# DDS flags
|
# DDS flags
|
||||||
DDSD_CAPS = 0x1
|
class DDSD(IntFlag):
|
||||||
DDSD_HEIGHT = 0x2
|
CAPS = 0x1
|
||||||
DDSD_WIDTH = 0x4
|
HEIGHT = 0x2
|
||||||
DDSD_PITCH = 0x8
|
WIDTH = 0x4
|
||||||
DDSD_PIXELFORMAT = 0x1000
|
PITCH = 0x8
|
||||||
DDSD_MIPMAPCOUNT = 0x20000
|
PIXELFORMAT = 0x1000
|
||||||
DDSD_LINEARSIZE = 0x80000
|
MIPMAPCOUNT = 0x20000
|
||||||
DDSD_DEPTH = 0x800000
|
LINEARSIZE = 0x80000
|
||||||
|
DEPTH = 0x800000
|
||||||
|
|
||||||
|
|
||||||
# DDS caps
|
# DDS caps
|
||||||
DDSCAPS_COMPLEX = 0x8
|
class DDSCAPS(IntFlag):
|
||||||
DDSCAPS_TEXTURE = 0x1000
|
COMPLEX = 0x8
|
||||||
DDSCAPS_MIPMAP = 0x400000
|
TEXTURE = 0x1000
|
||||||
|
MIPMAP = 0x400000
|
||||||
|
|
||||||
|
|
||||||
|
class DDSCAPS2(IntFlag):
|
||||||
|
CUBEMAP = 0x200
|
||||||
|
CUBEMAP_POSITIVEX = 0x400
|
||||||
|
CUBEMAP_NEGATIVEX = 0x800
|
||||||
|
CUBEMAP_POSITIVEY = 0x1000
|
||||||
|
CUBEMAP_NEGATIVEY = 0x2000
|
||||||
|
CUBEMAP_POSITIVEZ = 0x4000
|
||||||
|
CUBEMAP_NEGATIVEZ = 0x8000
|
||||||
|
VOLUME = 0x200000
|
||||||
|
|
||||||
DDSCAPS2_CUBEMAP = 0x200
|
|
||||||
DDSCAPS2_CUBEMAP_POSITIVEX = 0x400
|
|
||||||
DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800
|
|
||||||
DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000
|
|
||||||
DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000
|
|
||||||
DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000
|
|
||||||
DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000
|
|
||||||
DDSCAPS2_VOLUME = 0x200000
|
|
||||||
|
|
||||||
# Pixel Format
|
# Pixel Format
|
||||||
DDPF_ALPHAPIXELS = 0x1
|
class DDPF(IntFlag):
|
||||||
DDPF_ALPHA = 0x2
|
ALPHAPIXELS = 0x1
|
||||||
DDPF_FOURCC = 0x4
|
ALPHA = 0x2
|
||||||
DDPF_PALETTEINDEXED8 = 0x20
|
FOURCC = 0x4
|
||||||
DDPF_RGB = 0x40
|
PALETTEINDEXED8 = 0x20
|
||||||
DDPF_LUMINANCE = 0x20000
|
RGB = 0x40
|
||||||
|
LUMINANCE = 0x20000
|
||||||
|
|
||||||
# dds.h
|
|
||||||
|
|
||||||
DDS_FOURCC = DDPF_FOURCC
|
|
||||||
DDS_RGB = DDPF_RGB
|
|
||||||
DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS
|
|
||||||
DDS_LUMINANCE = DDPF_LUMINANCE
|
|
||||||
DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS
|
|
||||||
DDS_ALPHA = DDPF_ALPHA
|
|
||||||
DDS_PAL8 = DDPF_PALETTEINDEXED8
|
|
||||||
|
|
||||||
DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
|
|
||||||
DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT
|
|
||||||
DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH
|
|
||||||
DDS_HEADER_FLAGS_PITCH = DDSD_PITCH
|
|
||||||
DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE
|
|
||||||
|
|
||||||
DDS_HEIGHT = DDSD_HEIGHT
|
|
||||||
DDS_WIDTH = DDSD_WIDTH
|
|
||||||
|
|
||||||
DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE
|
|
||||||
DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
|
|
||||||
DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX
|
|
||||||
|
|
||||||
DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX
|
|
||||||
DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX
|
|
||||||
DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY
|
|
||||||
DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY
|
|
||||||
DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ
|
|
||||||
DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ
|
|
||||||
|
|
||||||
|
|
||||||
# DXT1
|
|
||||||
DXT1_FOURCC = 0x31545844
|
|
||||||
|
|
||||||
# DXT3
|
|
||||||
DXT3_FOURCC = 0x33545844
|
|
||||||
|
|
||||||
# DXT5
|
|
||||||
DXT5_FOURCC = 0x35545844
|
|
||||||
|
|
||||||
|
|
||||||
# dxgiformat.h
|
# dxgiformat.h
|
||||||
|
class DXGI_FORMAT(IntEnum):
|
||||||
|
UNKNOWN = 0
|
||||||
|
R32G32B32A32_TYPELESS = 1
|
||||||
|
R32G32B32A32_FLOAT = 2
|
||||||
|
R32G32B32A32_UINT = 3
|
||||||
|
R32G32B32A32_SINT = 4
|
||||||
|
R32G32B32_TYPELESS = 5
|
||||||
|
R32G32B32_FLOAT = 6
|
||||||
|
R32G32B32_UINT = 7
|
||||||
|
R32G32B32_SINT = 8
|
||||||
|
R16G16B16A16_TYPELESS = 9
|
||||||
|
R16G16B16A16_FLOAT = 10
|
||||||
|
R16G16B16A16_UNORM = 11
|
||||||
|
R16G16B16A16_UINT = 12
|
||||||
|
R16G16B16A16_SNORM = 13
|
||||||
|
R16G16B16A16_SINT = 14
|
||||||
|
R32G32_TYPELESS = 15
|
||||||
|
R32G32_FLOAT = 16
|
||||||
|
R32G32_UINT = 17
|
||||||
|
R32G32_SINT = 18
|
||||||
|
R32G8X24_TYPELESS = 19
|
||||||
|
D32_FLOAT_S8X24_UINT = 20
|
||||||
|
R32_FLOAT_X8X24_TYPELESS = 21
|
||||||
|
X32_TYPELESS_G8X24_UINT = 22
|
||||||
|
R10G10B10A2_TYPELESS = 23
|
||||||
|
R10G10B10A2_UNORM = 24
|
||||||
|
R10G10B10A2_UINT = 25
|
||||||
|
R11G11B10_FLOAT = 26
|
||||||
|
R8G8B8A8_TYPELESS = 27
|
||||||
|
R8G8B8A8_UNORM = 28
|
||||||
|
R8G8B8A8_UNORM_SRGB = 29
|
||||||
|
R8G8B8A8_UINT = 30
|
||||||
|
R8G8B8A8_SNORM = 31
|
||||||
|
R8G8B8A8_SINT = 32
|
||||||
|
R16G16_TYPELESS = 33
|
||||||
|
R16G16_FLOAT = 34
|
||||||
|
R16G16_UNORM = 35
|
||||||
|
R16G16_UINT = 36
|
||||||
|
R16G16_SNORM = 37
|
||||||
|
R16G16_SINT = 38
|
||||||
|
R32_TYPELESS = 39
|
||||||
|
D32_FLOAT = 40
|
||||||
|
R32_FLOAT = 41
|
||||||
|
R32_UINT = 42
|
||||||
|
R32_SINT = 43
|
||||||
|
R24G8_TYPELESS = 44
|
||||||
|
D24_UNORM_S8_UINT = 45
|
||||||
|
R24_UNORM_X8_TYPELESS = 46
|
||||||
|
X24_TYPELESS_G8_UINT = 47
|
||||||
|
R8G8_TYPELESS = 48
|
||||||
|
R8G8_UNORM = 49
|
||||||
|
R8G8_UINT = 50
|
||||||
|
R8G8_SNORM = 51
|
||||||
|
R8G8_SINT = 52
|
||||||
|
R16_TYPELESS = 53
|
||||||
|
R16_FLOAT = 54
|
||||||
|
D16_UNORM = 55
|
||||||
|
R16_UNORM = 56
|
||||||
|
R16_UINT = 57
|
||||||
|
R16_SNORM = 58
|
||||||
|
R16_SINT = 59
|
||||||
|
R8_TYPELESS = 60
|
||||||
|
R8_UNORM = 61
|
||||||
|
R8_UINT = 62
|
||||||
|
R8_SNORM = 63
|
||||||
|
R8_SINT = 64
|
||||||
|
A8_UNORM = 65
|
||||||
|
R1_UNORM = 66
|
||||||
|
R9G9B9E5_SHAREDEXP = 67
|
||||||
|
R8G8_B8G8_UNORM = 68
|
||||||
|
G8R8_G8B8_UNORM = 69
|
||||||
|
BC1_TYPELESS = 70
|
||||||
|
BC1_UNORM = 71
|
||||||
|
BC1_UNORM_SRGB = 72
|
||||||
|
BC2_TYPELESS = 73
|
||||||
|
BC2_UNORM = 74
|
||||||
|
BC2_UNORM_SRGB = 75
|
||||||
|
BC3_TYPELESS = 76
|
||||||
|
BC3_UNORM = 77
|
||||||
|
BC3_UNORM_SRGB = 78
|
||||||
|
BC4_TYPELESS = 79
|
||||||
|
BC4_UNORM = 80
|
||||||
|
BC4_SNORM = 81
|
||||||
|
BC5_TYPELESS = 82
|
||||||
|
BC5_UNORM = 83
|
||||||
|
BC5_SNORM = 84
|
||||||
|
B5G6R5_UNORM = 85
|
||||||
|
B5G5R5A1_UNORM = 86
|
||||||
|
B8G8R8A8_UNORM = 87
|
||||||
|
B8G8R8X8_UNORM = 88
|
||||||
|
R10G10B10_XR_BIAS_A2_UNORM = 89
|
||||||
|
B8G8R8A8_TYPELESS = 90
|
||||||
|
B8G8R8A8_UNORM_SRGB = 91
|
||||||
|
B8G8R8X8_TYPELESS = 92
|
||||||
|
B8G8R8X8_UNORM_SRGB = 93
|
||||||
|
BC6H_TYPELESS = 94
|
||||||
|
BC6H_UF16 = 95
|
||||||
|
BC6H_SF16 = 96
|
||||||
|
BC7_TYPELESS = 97
|
||||||
|
BC7_UNORM = 98
|
||||||
|
BC7_UNORM_SRGB = 99
|
||||||
|
AYUV = 100
|
||||||
|
Y410 = 101
|
||||||
|
Y416 = 102
|
||||||
|
NV12 = 103
|
||||||
|
P010 = 104
|
||||||
|
P016 = 105
|
||||||
|
OPAQUE_420 = 106
|
||||||
|
YUY2 = 107
|
||||||
|
Y210 = 108
|
||||||
|
Y216 = 109
|
||||||
|
NV11 = 110
|
||||||
|
AI44 = 111
|
||||||
|
IA44 = 112
|
||||||
|
P8 = 113
|
||||||
|
A8P8 = 114
|
||||||
|
B4G4R4A4_UNORM = 115
|
||||||
|
P208 = 130
|
||||||
|
V208 = 131
|
||||||
|
V408 = 132
|
||||||
|
SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189
|
||||||
|
SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190
|
||||||
|
|
||||||
DXGI_FORMAT_R8G8B8A8_TYPELESS = 27
|
|
||||||
DXGI_FORMAT_R8G8B8A8_UNORM = 28
|
class D3DFMT(IntEnum):
|
||||||
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
|
UNKNOWN = 0
|
||||||
DXGI_FORMAT_BC5_TYPELESS = 82
|
R8G8B8 = 20
|
||||||
DXGI_FORMAT_BC5_UNORM = 83
|
A8R8G8B8 = 21
|
||||||
DXGI_FORMAT_BC5_SNORM = 84
|
X8R8G8B8 = 22
|
||||||
DXGI_FORMAT_BC6H_UF16 = 95
|
R5G6B5 = 23
|
||||||
DXGI_FORMAT_BC6H_SF16 = 96
|
X1R5G5B5 = 24
|
||||||
DXGI_FORMAT_BC7_TYPELESS = 97
|
A1R5G5B5 = 25
|
||||||
DXGI_FORMAT_BC7_UNORM = 98
|
A4R4G4B4 = 26
|
||||||
DXGI_FORMAT_BC7_UNORM_SRGB = 99
|
R3G3B2 = 27
|
||||||
|
A8 = 28
|
||||||
|
A8R3G3B2 = 29
|
||||||
|
X4R4G4B4 = 30
|
||||||
|
A2B10G10R10 = 31
|
||||||
|
A8B8G8R8 = 32
|
||||||
|
X8B8G8R8 = 33
|
||||||
|
G16R16 = 34
|
||||||
|
A2R10G10B10 = 35
|
||||||
|
A16B16G16R16 = 36
|
||||||
|
A8P8 = 40
|
||||||
|
P8 = 41
|
||||||
|
L8 = 50
|
||||||
|
A8L8 = 51
|
||||||
|
A4L4 = 52
|
||||||
|
V8U8 = 60
|
||||||
|
L6V5U5 = 61
|
||||||
|
X8L8V8U8 = 62
|
||||||
|
Q8W8V8U8 = 63
|
||||||
|
V16U16 = 64
|
||||||
|
A2W10V10U10 = 67
|
||||||
|
D16_LOCKABLE = 70
|
||||||
|
D32 = 71
|
||||||
|
D15S1 = 73
|
||||||
|
D24S8 = 75
|
||||||
|
D24X8 = 77
|
||||||
|
D24X4S4 = 79
|
||||||
|
D16 = 80
|
||||||
|
D32F_LOCKABLE = 82
|
||||||
|
D24FS8 = 83
|
||||||
|
D32_LOCKABLE = 84
|
||||||
|
S8_LOCKABLE = 85
|
||||||
|
L16 = 81
|
||||||
|
VERTEXDATA = 100
|
||||||
|
INDEX16 = 101
|
||||||
|
INDEX32 = 102
|
||||||
|
Q16W16V16U16 = 110
|
||||||
|
R16F = 111
|
||||||
|
G16R16F = 112
|
||||||
|
A16B16G16R16F = 113
|
||||||
|
R32F = 114
|
||||||
|
G32R32F = 115
|
||||||
|
A32B32G32R32F = 116
|
||||||
|
CxV8U8 = 117
|
||||||
|
A1 = 118
|
||||||
|
A2B10G10R10_XR_BIAS = 119
|
||||||
|
BINARYBUFFER = 199
|
||||||
|
|
||||||
|
UYVY = i32(b"UYVY")
|
||||||
|
R8G8_B8G8 = i32(b"RGBG")
|
||||||
|
YUY2 = i32(b"YUY2")
|
||||||
|
G8R8_G8B8 = i32(b"GRGB")
|
||||||
|
DXT1 = i32(b"DXT1")
|
||||||
|
DXT2 = i32(b"DXT2")
|
||||||
|
DXT3 = i32(b"DXT3")
|
||||||
|
DXT4 = i32(b"DXT4")
|
||||||
|
DXT5 = i32(b"DXT5")
|
||||||
|
DX10 = i32(b"DX10")
|
||||||
|
BC4S = i32(b"BC4S")
|
||||||
|
BC4U = i32(b"BC4U")
|
||||||
|
BC5S = i32(b"BC5S")
|
||||||
|
BC5U = i32(b"BC5U")
|
||||||
|
ATI1 = i32(b"ATI1")
|
||||||
|
ATI2 = i32(b"ATI2")
|
||||||
|
MULTI2_ARGB8 = i32(b"MET1")
|
||||||
|
|
||||||
|
|
||||||
|
# Backward compatibility layer
|
||||||
|
module = sys.modules[__name__]
|
||||||
|
for item in DDSD:
|
||||||
|
setattr(module, "DDSD_" + item.name, item.value)
|
||||||
|
for item in DDSCAPS:
|
||||||
|
setattr(module, "DDSCAPS_" + item.name, item.value)
|
||||||
|
for item in DDSCAPS2:
|
||||||
|
setattr(module, "DDSCAPS2_" + item.name, item.value)
|
||||||
|
for item in DDPF:
|
||||||
|
setattr(module, "DDPF_" + item.name, item.value)
|
||||||
|
|
||||||
|
DDS_FOURCC = DDPF.FOURCC
|
||||||
|
DDS_RGB = DDPF.RGB
|
||||||
|
DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS
|
||||||
|
DDS_LUMINANCE = DDPF.LUMINANCE
|
||||||
|
DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS
|
||||||
|
DDS_ALPHA = DDPF.ALPHA
|
||||||
|
DDS_PAL8 = DDPF.PALETTEINDEXED8
|
||||||
|
|
||||||
|
DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
|
||||||
|
DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT
|
||||||
|
DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH
|
||||||
|
DDS_HEADER_FLAGS_PITCH = DDSD.PITCH
|
||||||
|
DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE
|
||||||
|
|
||||||
|
DDS_HEIGHT = DDSD.HEIGHT
|
||||||
|
DDS_WIDTH = DDSD.WIDTH
|
||||||
|
|
||||||
|
DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE
|
||||||
|
DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP
|
||||||
|
DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX
|
||||||
|
|
||||||
|
DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX
|
||||||
|
DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX
|
||||||
|
DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY
|
||||||
|
DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY
|
||||||
|
DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ
|
||||||
|
DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ
|
||||||
|
|
||||||
|
DXT1_FOURCC = D3DFMT.DXT1
|
||||||
|
DXT3_FOURCC = D3DFMT.DXT3
|
||||||
|
DXT5_FOURCC = D3DFMT.DXT5
|
||||||
|
|
||||||
|
DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS
|
||||||
|
DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM
|
||||||
|
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB
|
||||||
|
DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS
|
||||||
|
DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM
|
||||||
|
DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM
|
||||||
|
DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16
|
||||||
|
DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16
|
||||||
|
DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS
|
||||||
|
DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM
|
||||||
|
DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB
|
||||||
|
|
||||||
|
|
||||||
class DdsImageFile(ImageFile.ImageFile):
|
class DdsImageFile(ImageFile.ImageFile):
|
||||||
|
@ -125,112 +337,134 @@ class DdsImageFile(ImageFile.ImageFile):
|
||||||
if len(header_bytes) != 120:
|
if len(header_bytes) != 120:
|
||||||
msg = f"Incomplete header: {len(header_bytes)} bytes"
|
msg = f"Incomplete header: {len(header_bytes)} bytes"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
header = BytesIO(header_bytes)
|
header = io.BytesIO(header_bytes)
|
||||||
|
|
||||||
flags, height, width = struct.unpack("<3I", header.read(12))
|
flags, height, width = struct.unpack("<3I", header.read(12))
|
||||||
self._size = (width, height)
|
self._size = (width, height)
|
||||||
self._mode = "RGBA"
|
extents = (0, 0) + self.size
|
||||||
|
|
||||||
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
||||||
struct.unpack("<11I", header.read(44)) # reserved
|
struct.unpack("<11I", header.read(44)) # reserved
|
||||||
|
|
||||||
# pixel format
|
# pixel format
|
||||||
pfsize, pfflags = struct.unpack("<2I", header.read(8))
|
pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16))
|
||||||
fourcc = header.read(4)
|
n = 0
|
||||||
(bitcount,) = struct.unpack("<I", header.read(4))
|
rawmode = None
|
||||||
if pfflags & DDPF_LUMINANCE:
|
if pfflags & DDPF.RGB:
|
||||||
# Texture contains uncompressed L or LA data
|
|
||||||
if pfflags & DDPF_ALPHAPIXELS:
|
|
||||||
self._mode = "LA"
|
|
||||||
else:
|
|
||||||
self._mode = "L"
|
|
||||||
|
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
|
||||||
elif pfflags & DDPF_RGB:
|
|
||||||
# Texture contains uncompressed RGB data
|
# Texture contains uncompressed RGB data
|
||||||
if pfflags & DDPF_ALPHAPIXELS:
|
if pfflags & DDPF.ALPHAPIXELS:
|
||||||
|
self._mode = "RGBA"
|
||||||
mask_count = 4
|
mask_count = 4
|
||||||
else:
|
else:
|
||||||
self._mode = "RGB"
|
self._mode = "RGB"
|
||||||
mask_count = 3
|
mask_count = 3
|
||||||
|
|
||||||
masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
|
masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
|
||||||
self.tile = [("dds_rgb", (0, 0) + self.size, 0, (bitcount, masks))]
|
self.tile = [("dds_rgb", extents, 0, (bitcount, masks))]
|
||||||
elif pfflags & DDPF_PALETTEINDEXED8:
|
return
|
||||||
|
elif pfflags & DDPF.LUMINANCE:
|
||||||
|
if bitcount == 8:
|
||||||
|
self._mode = "L"
|
||||||
|
elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS:
|
||||||
|
self._mode = "LA"
|
||||||
|
else:
|
||||||
|
msg = f"Unsupported bitcount {bitcount} for {pfflags}"
|
||||||
|
raise OSError(msg)
|
||||||
|
elif pfflags & DDPF.PALETTEINDEXED8:
|
||||||
self._mode = "P"
|
self._mode = "P"
|
||||||
self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
|
self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, "L")]
|
elif pfflags & DDPF.FOURCC:
|
||||||
else:
|
offset = header_size + 4
|
||||||
data_start = header_size + 4
|
if fourcc == D3DFMT.DXT1:
|
||||||
n = 0
|
self._mode = "RGBA"
|
||||||
if fourcc == b"DXT1":
|
|
||||||
self.pixel_format = "DXT1"
|
self.pixel_format = "DXT1"
|
||||||
n = 1
|
n = 1
|
||||||
elif fourcc == b"DXT3":
|
elif fourcc == D3DFMT.DXT3:
|
||||||
|
self._mode = "RGBA"
|
||||||
self.pixel_format = "DXT3"
|
self.pixel_format = "DXT3"
|
||||||
n = 2
|
n = 2
|
||||||
elif fourcc == b"DXT5":
|
elif fourcc == D3DFMT.DXT5:
|
||||||
|
self._mode = "RGBA"
|
||||||
self.pixel_format = "DXT5"
|
self.pixel_format = "DXT5"
|
||||||
n = 3
|
n = 3
|
||||||
elif fourcc == b"ATI1":
|
elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1):
|
||||||
|
self._mode = "L"
|
||||||
self.pixel_format = "BC4"
|
self.pixel_format = "BC4"
|
||||||
n = 4
|
n = 4
|
||||||
self._mode = "L"
|
elif fourcc == D3DFMT.BC5S:
|
||||||
elif fourcc in (b"ATI2", b"BC5U"):
|
|
||||||
self.pixel_format = "BC5"
|
|
||||||
n = 5
|
|
||||||
self._mode = "RGB"
|
self._mode = "RGB"
|
||||||
elif fourcc == b"BC5S":
|
|
||||||
self.pixel_format = "BC5S"
|
self.pixel_format = "BC5S"
|
||||||
n = 5
|
n = 5
|
||||||
|
elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2):
|
||||||
self._mode = "RGB"
|
self._mode = "RGB"
|
||||||
elif fourcc == b"DX10":
|
self.pixel_format = "BC5"
|
||||||
data_start += 20
|
n = 5
|
||||||
|
elif fourcc == D3DFMT.DX10:
|
||||||
|
offset += 20
|
||||||
# ignoring flags which pertain to volume textures and cubemaps
|
# ignoring flags which pertain to volume textures and cubemaps
|
||||||
(dxgi_format,) = struct.unpack("<I", self.fp.read(4))
|
(dxgi_format,) = struct.unpack("<I", self.fp.read(4))
|
||||||
self.fp.read(16)
|
self.fp.read(16)
|
||||||
if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM):
|
if dxgi_format in (
|
||||||
|
DXGI_FORMAT.BC1_UNORM,
|
||||||
|
DXGI_FORMAT.BC1_TYPELESS,
|
||||||
|
):
|
||||||
|
self._mode = "RGBA"
|
||||||
|
self.pixel_format = "BC1"
|
||||||
|
n = 1
|
||||||
|
elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM):
|
||||||
|
self._mode = "L"
|
||||||
|
self.pixel_format = "BC4"
|
||||||
|
n = 4
|
||||||
|
elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM):
|
||||||
|
self._mode = "RGB"
|
||||||
self.pixel_format = "BC5"
|
self.pixel_format = "BC5"
|
||||||
n = 5
|
n = 5
|
||||||
|
elif dxgi_format == DXGI_FORMAT.BC5_SNORM:
|
||||||
self._mode = "RGB"
|
self._mode = "RGB"
|
||||||
elif dxgi_format == DXGI_FORMAT_BC5_SNORM:
|
|
||||||
self.pixel_format = "BC5S"
|
self.pixel_format = "BC5S"
|
||||||
n = 5
|
n = 5
|
||||||
|
elif dxgi_format == DXGI_FORMAT.BC6H_UF16:
|
||||||
self._mode = "RGB"
|
self._mode = "RGB"
|
||||||
elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
|
|
||||||
self.pixel_format = "BC6H"
|
self.pixel_format = "BC6H"
|
||||||
n = 6
|
n = 6
|
||||||
|
elif dxgi_format == DXGI_FORMAT.BC6H_SF16:
|
||||||
self._mode = "RGB"
|
self._mode = "RGB"
|
||||||
elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
|
|
||||||
self.pixel_format = "BC6HS"
|
self.pixel_format = "BC6HS"
|
||||||
n = 6
|
n = 6
|
||||||
self._mode = "RGB"
|
|
||||||
elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
|
|
||||||
self.pixel_format = "BC7"
|
|
||||||
n = 7
|
|
||||||
elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
|
|
||||||
self.pixel_format = "BC7"
|
|
||||||
self.info["gamma"] = 1 / 2.2
|
|
||||||
n = 7
|
|
||||||
elif dxgi_format in (
|
elif dxgi_format in (
|
||||||
DXGI_FORMAT_R8G8B8A8_TYPELESS,
|
DXGI_FORMAT.BC7_TYPELESS,
|
||||||
DXGI_FORMAT_R8G8B8A8_UNORM,
|
DXGI_FORMAT.BC7_UNORM,
|
||||||
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
|
DXGI_FORMAT.BC7_UNORM_SRGB,
|
||||||
):
|
):
|
||||||
self.tile = [("raw", (0, 0) + self.size, 0, ("RGBA", 0, 1))]
|
self._mode = "RGBA"
|
||||||
if dxgi_format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
self.pixel_format = "BC7"
|
||||||
|
n = 7
|
||||||
|
if dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB:
|
||||||
|
self.info["gamma"] = 1 / 2.2
|
||||||
|
elif dxgi_format in (
|
||||||
|
DXGI_FORMAT.R8G8B8A8_TYPELESS,
|
||||||
|
DXGI_FORMAT.R8G8B8A8_UNORM,
|
||||||
|
DXGI_FORMAT.R8G8B8A8_UNORM_SRGB,
|
||||||
|
):
|
||||||
|
self._mode = "RGBA"
|
||||||
|
if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB:
|
||||||
self.info["gamma"] = 1 / 2.2
|
self.info["gamma"] = 1 / 2.2
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
msg = f"Unimplemented DXGI format {dxgi_format}"
|
msg = f"Unimplemented DXGI format {dxgi_format}"
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
else:
|
else:
|
||||||
msg = f"Unimplemented pixel format {repr(fourcc)}"
|
msg = f"Unimplemented pixel format {repr(fourcc)}"
|
||||||
raise NotImplementedError(msg)
|
raise NotImplementedError(msg)
|
||||||
|
else:
|
||||||
|
msg = f"Unknown pixel format flags {pfflags}"
|
||||||
|
raise NotImplementedError(msg)
|
||||||
|
|
||||||
|
if n:
|
||||||
self.tile = [
|
self.tile = [
|
||||||
("bcn", (0, 0) + self.size, data_start, (n, self.pixel_format))
|
ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format))
|
||||||
]
|
]
|
||||||
|
else:
|
||||||
|
self.tile = [ImageFile._Tile("raw", extents, 0, rawmode or self.mode)]
|
||||||
|
|
||||||
def load_seek(self, pos):
|
def load_seek(self, pos):
|
||||||
pass
|
pass
|
||||||
|
@ -274,48 +508,51 @@ def _save(im, fp, filename):
|
||||||
msg = f"cannot write mode {im.mode} as DDS"
|
msg = f"cannot write mode {im.mode} as DDS"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
|
||||||
rawmode = im.mode
|
alpha = im.mode[-1] == "A"
|
||||||
masks = [0xFF0000, 0xFF00, 0xFF]
|
if im.mode[0] == "L":
|
||||||
if im.mode in ("L", "LA"):
|
pixel_flags = DDPF.LUMINANCE
|
||||||
pixel_flags = DDPF_LUMINANCE
|
rawmode = im.mode
|
||||||
|
if alpha:
|
||||||
|
rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF]
|
||||||
|
else:
|
||||||
|
rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000]
|
||||||
else:
|
else:
|
||||||
pixel_flags = DDPF_RGB
|
pixel_flags = DDPF.RGB
|
||||||
rawmode = rawmode[::-1]
|
rawmode = im.mode[::-1]
|
||||||
if im.mode in ("LA", "RGBA"):
|
rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF]
|
||||||
pixel_flags |= DDPF_ALPHAPIXELS
|
|
||||||
masks.append(0xFF000000)
|
|
||||||
|
|
||||||
bitcount = len(masks) * 8
|
if alpha:
|
||||||
while len(masks) < 4:
|
r, g, b, a = im.split()
|
||||||
masks.append(0)
|
im = Image.merge("RGBA", (a, r, g, b))
|
||||||
|
if alpha:
|
||||||
|
pixel_flags |= DDPF.ALPHAPIXELS
|
||||||
|
rgba_mask.append(0xFF000000 if alpha else 0)
|
||||||
|
|
||||||
|
flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT
|
||||||
|
bitcount = len(im.getbands()) * 8
|
||||||
|
pitch = (im.width * bitcount + 7) // 8
|
||||||
|
|
||||||
fp.write(
|
fp.write(
|
||||||
o32(DDS_MAGIC)
|
o32(DDS_MAGIC)
|
||||||
+ o32(124) # header size
|
+ struct.pack(
|
||||||
+ o32(
|
"<7I",
|
||||||
DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PITCH | DDSD_PIXELFORMAT
|
124, # header size
|
||||||
) # flags
|
flags, # flags
|
||||||
+ o32(im.height)
|
im.height,
|
||||||
+ o32(im.width)
|
im.width,
|
||||||
+ o32((im.width * bitcount + 7) // 8) # pitch
|
pitch,
|
||||||
+ o32(0) # depth
|
0, # depth
|
||||||
+ o32(0) # mipmaps
|
0, # mipmaps
|
||||||
+ o32(0) * 11 # reserved
|
)
|
||||||
+ o32(32) # pfsize
|
+ struct.pack("11I", *((0,) * 11)) # reserved
|
||||||
+ o32(pixel_flags) # pfflags
|
# pfsize, pfflags, fourcc, bitcount
|
||||||
+ o32(0) # fourcc
|
+ struct.pack("<4I", 32, pixel_flags, 0, bitcount)
|
||||||
+ o32(bitcount) # bitcount
|
+ struct.pack("<4I", *rgba_mask) # dwRGBABitMask
|
||||||
+ b"".join(o32(mask) for mask in masks) # rgbabitmask
|
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
|
||||||
+ o32(DDSCAPS_TEXTURE) # dwCaps
|
)
|
||||||
+ o32(0) # dwCaps2
|
ImageFile._save(
|
||||||
+ o32(0) # dwCaps3
|
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
|
||||||
+ o32(0) # dwCaps4
|
|
||||||
+ o32(0) # dwReserved2
|
|
||||||
)
|
)
|
||||||
if im.mode == "RGBA":
|
|
||||||
r, g, b, a = im.split()
|
|
||||||
im = Image.merge("RGBA", (a, r, g, b))
|
|
||||||
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
|
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix):
|
def _accept(prefix):
|
||||||
|
|
|
@ -97,16 +97,15 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
s = prop[0x2000002 | id]
|
s = prop[0x2000002 | id]
|
||||||
|
|
||||||
colors = []
|
|
||||||
bands = i32(s, 4)
|
bands = i32(s, 4)
|
||||||
if bands > 4:
|
if bands > 4:
|
||||||
msg = "Invalid number of bands"
|
msg = "Invalid number of bands"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
for i in range(bands):
|
|
||||||
# note: for now, we ignore the "uncalibrated" flag
|
|
||||||
colors.append(i32(s, 8 + i * 4) & 0x7FFFFFFF)
|
|
||||||
|
|
||||||
self._mode, self.rawmode = MODES[tuple(colors)]
|
# note: for now, we ignore the "uncalibrated" flag
|
||||||
|
colors = tuple(i32(s, 8 + i * 4) & 0x7FFFFFFF for i in range(bands))
|
||||||
|
|
||||||
|
self._mode, self.rawmode = MODES[colors]
|
||||||
|
|
||||||
# load JPEG tables, if any
|
# load JPEG tables, if any
|
||||||
self.jpeg = {}
|
self.jpeg = {}
|
||||||
|
@ -227,6 +226,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
break # isn't really required
|
break # isn't really required
|
||||||
|
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
|
self._fp = self.fp
|
||||||
self.fp = None
|
self.fp = None
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
|
|
|
@ -40,7 +40,7 @@ from enum import IntEnum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import defusedxml.ElementTree as ElementTree
|
from defusedxml import ElementTree
|
||||||
except ImportError:
|
except ImportError:
|
||||||
ElementTree = None
|
ElementTree = None
|
||||||
|
|
||||||
|
@ -1160,7 +1160,7 @@ class Image:
|
||||||
if palette.mode != "P":
|
if palette.mode != "P":
|
||||||
msg = "bad mode for palette image"
|
msg = "bad mode for palette image"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
if self.mode != "RGB" and self.mode != "L":
|
if self.mode not in {"RGB", "L"}:
|
||||||
msg = "only RGB or L mode images can be quantized to a palette"
|
msg = "only RGB or L mode images can be quantized to a palette"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
im = self.im.convert("P", dither, palette.im)
|
im = self.im.convert("P", dither, palette.im)
|
||||||
|
@ -1288,9 +1288,9 @@ class Image:
|
||||||
if self.im.bands == 1 or multiband:
|
if self.im.bands == 1 or multiband:
|
||||||
return self._new(filter.filter(self.im))
|
return self._new(filter.filter(self.im))
|
||||||
|
|
||||||
ims = []
|
ims = [
|
||||||
for c in range(self.im.bands):
|
self._new(filter.filter(self.im.getband(c))) for c in range(self.im.bands)
|
||||||
ims.append(self._new(filter.filter(self.im.getband(c))))
|
]
|
||||||
return merge(self.mode, ims)
|
return merge(self.mode, ims)
|
||||||
|
|
||||||
def getbands(self):
|
def getbands(self):
|
||||||
|
@ -1339,10 +1339,7 @@ class Image:
|
||||||
self.load()
|
self.load()
|
||||||
if self.mode in ("1", "L", "P"):
|
if self.mode in ("1", "L", "P"):
|
||||||
h = self.im.histogram()
|
h = self.im.histogram()
|
||||||
out = []
|
out = [(h[i], i) for i in range(256) if h[i]]
|
||||||
for i in range(256):
|
|
||||||
if h[i]:
|
|
||||||
out.append((h[i], i))
|
|
||||||
if len(out) > maxcolors:
|
if len(out) > maxcolors:
|
||||||
return None
|
return None
|
||||||
return out
|
return out
|
||||||
|
@ -1383,10 +1380,7 @@ class Image:
|
||||||
|
|
||||||
self.load()
|
self.load()
|
||||||
if self.im.bands > 1:
|
if self.im.bands > 1:
|
||||||
extrema = []
|
return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
|
||||||
for i in range(self.im.bands):
|
|
||||||
extrema.append(self.im.getband(i).getextrema())
|
|
||||||
return tuple(extrema)
|
|
||||||
return self.im.getextrema()
|
return self.im.getextrema()
|
||||||
|
|
||||||
def _getxmp(self, xmp_tags):
|
def _getxmp(self, xmp_tags):
|
||||||
|
|
|
@ -787,11 +787,8 @@ def getProfileInfo(profile):
|
||||||
# info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
|
# info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint
|
||||||
description = profile.profile.profile_description
|
description = profile.profile.profile_description
|
||||||
cpright = profile.profile.copyright
|
cpright = profile.profile.copyright
|
||||||
arr = []
|
elements = [element for element in (description, cpright) if element]
|
||||||
for elt in (description, cpright):
|
return "\r\n\r\n".join(elements) + "\r\n\r\n"
|
||||||
if elt:
|
|
||||||
arr.append(elt)
|
|
||||||
return "\r\n\r\n".join(arr) + "\r\n\r\n"
|
|
||||||
|
|
||||||
except (AttributeError, OSError, TypeError, ValueError) as v:
|
except (AttributeError, OSError, TypeError, ValueError) as v:
|
||||||
raise PyCMSError(v) from v
|
raise PyCMSError(v) from v
|
||||||
|
|
|
@ -921,7 +921,7 @@ def floodfill(image, xy, value, border=None, thresh=0):
|
||||||
if border is None:
|
if border is None:
|
||||||
fill = _color_diff(p, background) <= thresh
|
fill = _color_diff(p, background) <= thresh
|
||||||
else:
|
else:
|
||||||
fill = p != value and p != border
|
fill = p not in (value, border)
|
||||||
if fill:
|
if fill:
|
||||||
pixel[s, t] = value
|
pixel[s, t] = value
|
||||||
new_edge.add((s, t))
|
new_edge.add((s, t))
|
||||||
|
|
|
@ -26,11 +26,13 @@
|
||||||
#
|
#
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
import struct
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
from typing import NamedTuple
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._util import is_path
|
from ._util import is_path
|
||||||
|
@ -77,6 +79,13 @@ def _tilesort(t):
|
||||||
return t[2]
|
return t[2]
|
||||||
|
|
||||||
|
|
||||||
|
class _Tile(NamedTuple):
|
||||||
|
encoder_name: str
|
||||||
|
extents: tuple[int, int, int, int]
|
||||||
|
offset: int
|
||||||
|
args: tuple | str | None
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
# ImageFile base class
|
# ImageFile base class
|
||||||
|
@ -520,13 +529,13 @@ def _save(im, fp, tile, bufsize=0):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
|
|
||||||
def _encode_tile(im, fp, tile, bufsize, fh, exc=None):
|
def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None):
|
||||||
for e, b, o, a in tile:
|
for encoder_name, extents, offset, args in tile:
|
||||||
if o > 0:
|
if offset > 0:
|
||||||
fp.seek(o)
|
fp.seek(offset)
|
||||||
encoder = Image._getencoder(im.mode, e, a, im.encoderconfig)
|
encoder = Image._getencoder(im.mode, encoder_name, args, im.encoderconfig)
|
||||||
try:
|
try:
|
||||||
encoder.setimage(im.im, b)
|
encoder.setimage(im.im, extents)
|
||||||
if encoder.pushes_fd:
|
if encoder.pushes_fd:
|
||||||
encoder.setfd(fp)
|
encoder.setfd(fp)
|
||||||
errcode = encoder.encode_to_pyfd()[1]
|
errcode = encoder.encode_to_pyfd()[1]
|
||||||
|
|
|
@ -25,12 +25,16 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import IO
|
||||||
|
|
||||||
from . import Image
|
from . import Image
|
||||||
from ._util import is_directory, is_path
|
from ._util import is_directory, is_path
|
||||||
|
@ -185,9 +189,20 @@ class ImageFont:
|
||||||
class FreeTypeFont:
|
class FreeTypeFont:
|
||||||
"""FreeType font wrapper (requires _imagingft service)"""
|
"""FreeType font wrapper (requires _imagingft service)"""
|
||||||
|
|
||||||
def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
font: bytes | str | Path | IO | None = None,
|
||||||
|
size: float = 10,
|
||||||
|
index: int = 0,
|
||||||
|
encoding: str = "",
|
||||||
|
layout_engine: Layout | None = None,
|
||||||
|
) -> None:
|
||||||
# FIXME: use service provider instead
|
# FIXME: use service provider instead
|
||||||
|
|
||||||
|
if size <= 0:
|
||||||
|
msg = "font size must be greater than 0"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
self.path = font
|
self.path = font
|
||||||
self.size = size
|
self.size = size
|
||||||
self.index = index
|
self.index = index
|
||||||
|
@ -213,6 +228,8 @@ class FreeTypeFont:
|
||||||
)
|
)
|
||||||
|
|
||||||
if is_path(font):
|
if is_path(font):
|
||||||
|
if isinstance(font, Path):
|
||||||
|
font = str(font)
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
font_bytes_path = font if isinstance(font, bytes) else font.encode()
|
font_bytes_path = font if isinstance(font, bytes) else font.encode()
|
||||||
try:
|
try:
|
||||||
|
@ -775,7 +792,7 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
||||||
This specifies the character set to use. It does not alter the
|
This specifies the character set to use. It does not alter the
|
||||||
encoding of any text provided in subsequent operations.
|
encoding of any text provided in subsequent operations.
|
||||||
:param layout_engine: Which layout engine to use, if available:
|
:param layout_engine: Which layout engine to use, if available:
|
||||||
:data:`.ImageFont.Layout.BASIC` or :data:`.ImageFont.Layout.RAQM`.
|
:attr:`.ImageFont.Layout.BASIC` or :attr:`.ImageFont.Layout.RAQM`.
|
||||||
If it is available, Raqm layout will be used by default.
|
If it is available, Raqm layout will be used by default.
|
||||||
Otherwise, basic layout will be used.
|
Otherwise, basic layout will be used.
|
||||||
|
|
||||||
|
@ -791,10 +808,6 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None):
|
||||||
:exception ValueError: If the font size is not greater than zero.
|
:exception ValueError: If the font size is not greater than zero.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if size <= 0:
|
|
||||||
msg = "font size must be greater than 0"
|
|
||||||
raise ValueError(msg)
|
|
||||||
|
|
||||||
def freetype(font):
|
def freetype(font):
|
||||||
return FreeTypeFont(font, size, index, encoding, layout_engine)
|
return FreeTypeFont(font, size, index, encoding, layout_engine)
|
||||||
|
|
||||||
|
|
|
@ -557,9 +557,7 @@ def invert(image):
|
||||||
:param image: The image to invert.
|
:param image: The image to invert.
|
||||||
:return: An image.
|
:return: An image.
|
||||||
"""
|
"""
|
||||||
lut = []
|
lut = list(range(255, -1, -1))
|
||||||
for i in range(256):
|
|
||||||
lut.append(255 - i)
|
|
||||||
return image.point(lut) if image.mode == "1" else _lut(image, lut)
|
return image.point(lut) if image.mode == "1" else _lut(image, lut)
|
||||||
|
|
||||||
|
|
||||||
|
@ -581,10 +579,8 @@ def posterize(image, bits):
|
||||||
:param bits: The number of bits to keep for each channel (1-8).
|
:param bits: The number of bits to keep for each channel (1-8).
|
||||||
:return: An image.
|
:return: An image.
|
||||||
"""
|
"""
|
||||||
lut = []
|
|
||||||
mask = ~(2 ** (8 - bits) - 1)
|
mask = ~(2 ** (8 - bits) - 1)
|
||||||
for i in range(256):
|
lut = [i & mask for i in range(256)]
|
||||||
lut.append(i & mask)
|
|
||||||
return _lut(image, lut)
|
return _lut(image, lut)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -200,21 +200,15 @@ def raw(rawmode, data):
|
||||||
|
|
||||||
|
|
||||||
def make_linear_lut(black, white):
|
def make_linear_lut(black, white):
|
||||||
lut = []
|
|
||||||
if black == 0:
|
if black == 0:
|
||||||
for i in range(256):
|
return [white * i // 255 for i in range(256)]
|
||||||
lut.append(white * i // 255)
|
|
||||||
else:
|
msg = "unavailable when black is non-zero"
|
||||||
msg = "unavailable when black is non-zero"
|
raise NotImplementedError(msg) # FIXME
|
||||||
raise NotImplementedError(msg) # FIXME
|
|
||||||
return lut
|
|
||||||
|
|
||||||
|
|
||||||
def make_gamma_lut(exp):
|
def make_gamma_lut(exp):
|
||||||
lut = []
|
return [int(((i / 255.0) ** exp) * 255.0 + 0.5) for i in range(256)]
|
||||||
for i in range(256):
|
|
||||||
lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
|
|
||||||
return lut
|
|
||||||
|
|
||||||
|
|
||||||
def negative(mode="RGB"):
|
def negative(mode="RGB"):
|
||||||
|
@ -226,9 +220,7 @@ def negative(mode="RGB"):
|
||||||
def random(mode="RGB"):
|
def random(mode="RGB"):
|
||||||
from random import randint
|
from random import randint
|
||||||
|
|
||||||
palette = []
|
palette = [randint(0, 255) for _ in range(256 * len(mode))]
|
||||||
for i in range(256 * len(mode)):
|
|
||||||
palette.append(randint(0, 255))
|
|
||||||
return ImagePalette(mode, palette)
|
return ImagePalette(mode, palette)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -103,12 +103,10 @@ def align8to32(bytes, width, mode):
|
||||||
if not extra_padding:
|
if not extra_padding:
|
||||||
return bytes
|
return bytes
|
||||||
|
|
||||||
new_data = []
|
new_data = [
|
||||||
for i in range(len(bytes) // bytes_per_line):
|
bytes[i * bytes_per_line : (i + 1) * bytes_per_line] + b"\x00" * extra_padding
|
||||||
new_data.append(
|
for i in range(len(bytes) // bytes_per_line)
|
||||||
bytes[i * bytes_per_line : (i + 1) * bytes_per_line]
|
]
|
||||||
+ b"\x00" * extra_padding
|
|
||||||
)
|
|
||||||
|
|
||||||
return b"".join(new_data)
|
return b"".join(new_data)
|
||||||
|
|
||||||
|
@ -131,15 +129,11 @@ def _toqclass_helper(im):
|
||||||
format = qt_format.Format_Mono
|
format = qt_format.Format_Mono
|
||||||
elif im.mode == "L":
|
elif im.mode == "L":
|
||||||
format = qt_format.Format_Indexed8
|
format = qt_format.Format_Indexed8
|
||||||
colortable = []
|
colortable = [rgb(i, i, i) for i in range(256)]
|
||||||
for i in range(256):
|
|
||||||
colortable.append(rgb(i, i, i))
|
|
||||||
elif im.mode == "P":
|
elif im.mode == "P":
|
||||||
format = qt_format.Format_Indexed8
|
format = qt_format.Format_Indexed8
|
||||||
colortable = []
|
|
||||||
palette = im.getpalette()
|
palette = im.getpalette()
|
||||||
for i in range(0, len(palette), 3):
|
colortable = [rgb(*palette[i : i + 3]) for i in range(0, len(palette), 3)]
|
||||||
colortable.append(rgb(*palette[i : i + 3]))
|
|
||||||
elif im.mode == "RGB":
|
elif im.mode == "RGB":
|
||||||
# Populate the 4th channel with 255
|
# Populate the 4th channel with 255
|
||||||
im = im.convert("RGBA")
|
im = im.convert("RGBA")
|
||||||
|
|
|
@ -21,9 +21,7 @@
|
||||||
# See the README file for information on usage and redistribution.
|
# See the README file for information on usage and redistribution.
|
||||||
#
|
#
|
||||||
|
|
||||||
import functools
|
|
||||||
import math
|
import math
|
||||||
import operator
|
|
||||||
|
|
||||||
|
|
||||||
class Stat:
|
class Stat:
|
||||||
|
@ -53,26 +51,22 @@ class Stat:
|
||||||
"""Get min/max values for each band in the image"""
|
"""Get min/max values for each band in the image"""
|
||||||
|
|
||||||
def minmax(histogram):
|
def minmax(histogram):
|
||||||
n = 255
|
res_min, res_max = 255, 0
|
||||||
x = 0
|
|
||||||
for i in range(256):
|
for i in range(256):
|
||||||
if histogram[i]:
|
if histogram[i]:
|
||||||
n = min(n, i)
|
res_min = i
|
||||||
x = max(x, i)
|
break
|
||||||
return n, x # returns (255, 0) if there's no data in the histogram
|
for i in range(255, -1, -1):
|
||||||
|
if histogram[i]:
|
||||||
|
res_max = i
|
||||||
|
break
|
||||||
|
return res_min, res_max
|
||||||
|
|
||||||
v = []
|
return [minmax(self.h[i:]) for i in range(0, len(self.h), 256)]
|
||||||
for i in range(0, len(self.h), 256):
|
|
||||||
v.append(minmax(self.h[i:]))
|
|
||||||
return v
|
|
||||||
|
|
||||||
def _getcount(self):
|
def _getcount(self):
|
||||||
"""Get total number of pixels in each layer"""
|
"""Get total number of pixels in each layer"""
|
||||||
|
return [sum(self.h[i : i + 256]) for i in range(0, len(self.h), 256)]
|
||||||
v = []
|
|
||||||
for i in range(0, len(self.h), 256):
|
|
||||||
v.append(functools.reduce(operator.add, self.h[i : i + 256]))
|
|
||||||
return v
|
|
||||||
|
|
||||||
def _getsum(self):
|
def _getsum(self):
|
||||||
"""Get sum of all pixels in each layer"""
|
"""Get sum of all pixels in each layer"""
|
||||||
|
@ -98,11 +92,7 @@ class Stat:
|
||||||
|
|
||||||
def _getmean(self):
|
def _getmean(self):
|
||||||
"""Get average pixel level for each layer"""
|
"""Get average pixel level for each layer"""
|
||||||
|
return [self.sum[i] / self.count[i] for i in self.bands]
|
||||||
v = []
|
|
||||||
for i in self.bands:
|
|
||||||
v.append(self.sum[i] / self.count[i])
|
|
||||||
return v
|
|
||||||
|
|
||||||
def _getmedian(self):
|
def _getmedian(self):
|
||||||
"""Get median pixel level for each layer"""
|
"""Get median pixel level for each layer"""
|
||||||
|
@ -121,28 +111,18 @@ class Stat:
|
||||||
|
|
||||||
def _getrms(self):
|
def _getrms(self):
|
||||||
"""Get RMS for each layer"""
|
"""Get RMS for each layer"""
|
||||||
|
return [math.sqrt(self.sum2[i] / self.count[i]) for i in self.bands]
|
||||||
v = []
|
|
||||||
for i in self.bands:
|
|
||||||
v.append(math.sqrt(self.sum2[i] / self.count[i]))
|
|
||||||
return v
|
|
||||||
|
|
||||||
def _getvar(self):
|
def _getvar(self):
|
||||||
"""Get variance for each layer"""
|
"""Get variance for each layer"""
|
||||||
|
return [
|
||||||
v = []
|
(self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i]
|
||||||
for i in self.bands:
|
for i in self.bands
|
||||||
n = self.count[i]
|
]
|
||||||
v.append((self.sum2[i] - (self.sum[i] ** 2.0) / n) / n)
|
|
||||||
return v
|
|
||||||
|
|
||||||
def _getstddev(self):
|
def _getstddev(self):
|
||||||
"""Get standard deviation for each layer"""
|
"""Get standard deviation for each layer"""
|
||||||
|
return [math.sqrt(self.var[i]) for i in self.bands]
|
||||||
v = []
|
|
||||||
for i in self.bands:
|
|
||||||
v.append(math.sqrt(self.var[i]))
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
Global = Stat # compatibility
|
Global = Stat # compatibility
|
||||||
|
|
|
@ -334,10 +334,7 @@ def _save(im, fp, filename):
|
||||||
if quality_layers is not None and not (
|
if quality_layers is not None and not (
|
||||||
isinstance(quality_layers, (list, tuple))
|
isinstance(quality_layers, (list, tuple))
|
||||||
and all(
|
and all(
|
||||||
[
|
isinstance(quality_layer, (int, float)) for quality_layer in quality_layers
|
||||||
isinstance(quality_layer, (int, float))
|
|
||||||
for quality_layer in quality_layers
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
msg = "quality_layers must be a sequence of numbers"
|
msg = "quality_layers must be a sequence of numbers"
|
||||||
|
|
|
@ -233,9 +233,7 @@ def SOF(self, marker):
|
||||||
# fixup icc profile
|
# fixup icc profile
|
||||||
self.icclist.sort() # sort by sequence number
|
self.icclist.sort() # sort by sequence number
|
||||||
if self.icclist[0][13] == len(self.icclist):
|
if self.icclist[0][13] == len(self.icclist):
|
||||||
profile = []
|
profile = [p[14:] for p in self.icclist]
|
||||||
for p in self.icclist:
|
|
||||||
profile.append(p[14:])
|
|
||||||
icc_profile = b"".join(profile)
|
icc_profile = b"".join(profile)
|
||||||
else:
|
else:
|
||||||
icc_profile = None # wrong number of fragments
|
icc_profile = None # wrong number of fragments
|
||||||
|
@ -397,7 +395,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
# self.__offset = self.fp.tell()
|
# self.__offset = self.fp.tell()
|
||||||
break
|
break
|
||||||
s = self.fp.read(1)
|
s = self.fp.read(1)
|
||||||
elif i == 0 or i == 0xFFFF:
|
elif i in {0, 0xFFFF}:
|
||||||
# padded marker or junk; move on
|
# padded marker or junk; move on
|
||||||
s = b"\xff"
|
s = b"\xff"
|
||||||
elif i == 0xFF00: # Skip extraneous data (escaped 0xFF)
|
elif i == 0xFF00: # Skip extraneous data (escaped 0xFF)
|
||||||
|
|
|
@ -51,10 +51,11 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
# find ACI subfiles with Image members (maybe not the
|
# find ACI subfiles with Image members (maybe not the
|
||||||
# best way to identify MIC files, but what the... ;-)
|
# best way to identify MIC files, but what the... ;-)
|
||||||
|
|
||||||
self.images = []
|
self.images = [
|
||||||
for path in self.ole.listdir():
|
path
|
||||||
if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image":
|
for path in self.ole.listdir()
|
||||||
self.images.append(path)
|
if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image"
|
||||||
|
]
|
||||||
|
|
||||||
# if we didn't find any images, this is probably not
|
# if we didn't find any images, this is probably not
|
||||||
# an MIC file.
|
# an MIC file.
|
||||||
|
@ -66,6 +67,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
self._n_frames = len(self.images)
|
self._n_frames = len(self.images)
|
||||||
self.is_animated = self._n_frames > 1
|
self.is_animated = self._n_frames > 1
|
||||||
|
|
||||||
|
self.__fp = self.fp
|
||||||
self.seek(0)
|
self.seek(0)
|
||||||
|
|
||||||
def seek(self, frame):
|
def seek(self, frame):
|
||||||
|
@ -87,10 +89,12 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
||||||
return self.frame
|
return self.frame
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
self.__fp.close()
|
||||||
self.ole.close()
|
self.ole.close()
|
||||||
super().close()
|
super().close()
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
|
self.__fp.close()
|
||||||
self.ole.close()
|
self.ole.close()
|
||||||
super().__exit__()
|
super().__exit__()
|
||||||
|
|
||||||
|
|
|
@ -129,9 +129,8 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
nprops = i32(fp.read(4))
|
nprops = i32(fp.read(4))
|
||||||
|
|
||||||
# read property description
|
# read property description
|
||||||
p = []
|
p = [(i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))) for _ in range(nprops)]
|
||||||
for i in range(nprops):
|
|
||||||
p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))))
|
|
||||||
if nprops & 3:
|
if nprops & 3:
|
||||||
fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad
|
fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad
|
||||||
|
|
||||||
|
@ -186,8 +185,6 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
#
|
#
|
||||||
# bitmap data
|
# bitmap data
|
||||||
|
|
||||||
bitmaps = []
|
|
||||||
|
|
||||||
fp, format, i16, i32 = self._getformat(PCF_BITMAPS)
|
fp, format, i16, i32 = self._getformat(PCF_BITMAPS)
|
||||||
|
|
||||||
nbitmaps = i32(fp.read(4))
|
nbitmaps = i32(fp.read(4))
|
||||||
|
@ -196,13 +193,9 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
msg = "Wrong number of bitmaps"
|
msg = "Wrong number of bitmaps"
|
||||||
raise OSError(msg)
|
raise OSError(msg)
|
||||||
|
|
||||||
offsets = []
|
offsets = [i32(fp.read(4)) for _ in range(nbitmaps)]
|
||||||
for i in range(nbitmaps):
|
|
||||||
offsets.append(i32(fp.read(4)))
|
|
||||||
|
|
||||||
bitmap_sizes = []
|
bitmap_sizes = [i32(fp.read(4)) for _ in range(4)]
|
||||||
for i in range(4):
|
|
||||||
bitmap_sizes.append(i32(fp.read(4)))
|
|
||||||
|
|
||||||
# byteorder = format & 4 # non-zero => MSB
|
# byteorder = format & 4 # non-zero => MSB
|
||||||
bitorder = format & 8 # non-zero => MSB
|
bitorder = format & 8 # non-zero => MSB
|
||||||
|
@ -218,6 +211,7 @@ class PcfFontFile(FontFile.FontFile):
|
||||||
if bitorder:
|
if bitorder:
|
||||||
mode = "1"
|
mode = "1"
|
||||||
|
|
||||||
|
bitmaps = []
|
||||||
for i in range(nbitmaps):
|
for i in range(nbitmaps):
|
||||||
xsize, ysize = metrics[i][:2]
|
xsize, ysize = metrics[i][:2]
|
||||||
b, e = offsets[i : i + 2]
|
b, e = offsets[i : i + 2]
|
||||||
|
|
|
@ -96,7 +96,7 @@ def _write_image(im, filename, existing_pdf, image_refs):
|
||||||
dict_obj["ColorSpace"] = [
|
dict_obj["ColorSpace"] = [
|
||||||
PdfParser.PdfName("Indexed"),
|
PdfParser.PdfName("Indexed"),
|
||||||
PdfParser.PdfName("DeviceRGB"),
|
PdfParser.PdfName("DeviceRGB"),
|
||||||
255,
|
len(palette) // 3 - 1,
|
||||||
PdfParser.PdfBinary(palette),
|
PdfParser.PdfBinary(palette),
|
||||||
]
|
]
|
||||||
procset = "ImageI" # indexed color
|
procset = "ImageI" # indexed color
|
||||||
|
|
|
@ -123,7 +123,7 @@ class SgiImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
|
|
||||||
def _save(im, fp, filename):
|
def _save(im, fp, filename):
|
||||||
if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "L":
|
if im.mode not in {"RGB", "RGBA", "L"}:
|
||||||
msg = "Unsupported SGI image mode"
|
msg = "Unsupported SGI image mode"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ def _save(im, fp, filename):
|
||||||
# Z Dimension: Number of channels
|
# Z Dimension: Number of channels
|
||||||
z = len(im.mode)
|
z = len(im.mode)
|
||||||
|
|
||||||
if dim == 1 or dim == 2:
|
if dim in {1, 2}:
|
||||||
z = 1
|
z = 1
|
||||||
|
|
||||||
# assert we've got the right number of bands.
|
# assert we've got the right number of bands.
|
||||||
|
|
|
@ -238,9 +238,7 @@ def makeSpiderHeader(im):
|
||||||
if nvalues < 23:
|
if nvalues < 23:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
hdr = []
|
hdr = [0.0] * nvalues
|
||||||
for i in range(nvalues):
|
|
||||||
hdr.append(0.0)
|
|
||||||
|
|
||||||
# NB these are Fortran indices
|
# NB these are Fortran indices
|
||||||
hdr[1] = 1.0 # nslice (=1 for an image)
|
hdr[1] = 1.0 # nslice (=1 for an image)
|
||||||
|
|
|
@ -427,7 +427,7 @@ def _populate():
|
||||||
|
|
||||||
TAGS_V2[k] = TagInfo(k, *v)
|
TAGS_V2[k] = TagInfo(k, *v)
|
||||||
|
|
||||||
for group, tags in TAGS_V2_GROUPS.items():
|
for tags in TAGS_V2_GROUPS.values():
|
||||||
for k, v in tags.items():
|
for k, v in tags.items():
|
||||||
tags[k] = TagInfo(k, *v)
|
tags[k] = TagInfo(k, *v)
|
||||||
|
|
||||||
|
|
|
@ -279,10 +279,10 @@ DEPS = {
|
||||||
"libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],
|
"libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],
|
||||||
},
|
},
|
||||||
"lcms2": {
|
"lcms2": {
|
||||||
"url": SF_PROJECTS + "/lcms/files/lcms/2.15/lcms2-2.15.tar.gz/download",
|
"url": SF_PROJECTS + "/lcms/files/lcms/2.16/lcms2-2.16.tar.gz/download",
|
||||||
"filename": "lcms2-2.15.tar.gz",
|
"filename": "lcms2-2.16.tar.gz",
|
||||||
"dir": "lcms2-2.15",
|
"dir": "lcms2-2.16",
|
||||||
"license": "COPYING",
|
"license": "LICENSE",
|
||||||
"patch": {
|
"patch": {
|
||||||
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
|
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
|
||||||
# default is /MD for x86 and /MT for x64, we need /MD always
|
# default is /MD for x86 and /MT for x64, we need /MD always
|
||||||
|
@ -345,9 +345,9 @@ DEPS = {
|
||||||
"libs": [r"imagequant.lib"],
|
"libs": [r"imagequant.lib"],
|
||||||
},
|
},
|
||||||
"harfbuzz": {
|
"harfbuzz": {
|
||||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/8.2.1.zip",
|
"url": "https://github.com/harfbuzz/harfbuzz/archive/8.3.0.zip",
|
||||||
"filename": "harfbuzz-8.2.1.zip",
|
"filename": "harfbuzz-8.3.0.zip",
|
||||||
"dir": "harfbuzz-8.2.1",
|
"dir": "harfbuzz-8.3.0",
|
||||||
"license": "COPYING",
|
"license": "COPYING",
|
||||||
"build": [
|
"build": [
|
||||||
*cmds_cmake(
|
*cmds_cmake(
|
||||||
|
@ -482,7 +482,7 @@ def extract_dep(url: str, filename: str) -> None:
|
||||||
msg = "Attempted Path Traversal in Zip File"
|
msg = "Attempted Path Traversal in Zip File"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
zf.extractall(sources_dir)
|
zf.extractall(sources_dir)
|
||||||
elif filename.endswith(".tar.gz") or filename.endswith(".tgz"):
|
elif filename.endswith((".tar.gz", ".tgz")):
|
||||||
with tarfile.open(file, "r:gz") as tgz:
|
with tarfile.open(file, "r:gz") as tgz:
|
||||||
for member in tgz.getnames():
|
for member in tgz.getnames():
|
||||||
member_abspath = os.path.abspath(os.path.join(sources_dir, member))
|
member_abspath = os.path.abspath(os.path.join(sources_dir, member))
|
||||||
|
@ -586,14 +586,19 @@ def build_dep(name: str) -> str:
|
||||||
|
|
||||||
def build_dep_all() -> None:
|
def build_dep_all() -> None:
|
||||||
lines = [r'call "{build_dir}\build_env.cmd"']
|
lines = [r'call "{build_dir}\build_env.cmd"']
|
||||||
|
gha_groups = "GITHUB_ACTIONS" in os.environ
|
||||||
for dep_name in DEPS:
|
for dep_name in DEPS:
|
||||||
print()
|
print()
|
||||||
if dep_name in disabled:
|
if dep_name in disabled:
|
||||||
print(f"Skipping disabled dependency {dep_name}")
|
print(f"Skipping disabled dependency {dep_name}")
|
||||||
continue
|
continue
|
||||||
script = build_dep(dep_name)
|
script = build_dep(dep_name)
|
||||||
|
if gha_groups:
|
||||||
|
lines.append(f"@echo ::group::Running {script}")
|
||||||
lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"')
|
lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"')
|
||||||
lines.append("if errorlevel 1 echo Build failed! && exit /B 1")
|
lines.append("if errorlevel 1 echo Build failed! && exit /B 1")
|
||||||
|
if gha_groups:
|
||||||
|
lines.append("@echo ::endgroup::")
|
||||||
print()
|
print()
|
||||||
lines.append("@echo All Pillow dependencies built successfully!")
|
lines.append("@echo All Pillow dependencies built successfully!")
|
||||||
write_script("build_dep_all.cmd", lines)
|
write_script("build_dep_all.cmd", lines)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user