mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-26 01:46: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
|
||||
id: install
|
||||
run: |
|
||||
7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\"
|
||||
echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH
|
||||
choco install nasm --no-progress
|
||||
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
|
||||
|
||||
# Install extra test images
|
||||
|
@ -167,7 +167,6 @@ jobs:
|
|||
- name: Build Pillow
|
||||
run: |
|
||||
$FLAGS="-C raqm=vendor -C fribidi=vendor"
|
||||
if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS+=" -C imagequant=disable" }
|
||||
cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ."
|
||||
& $env:pythonLocation\python.exe selftest.py --installed
|
||||
shell: pwsh
|
||||
|
@ -209,47 +208,6 @@ jobs:
|
|||
flags: GHA_Windows
|
||||
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:
|
||||
permissions:
|
||||
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
|
||||
FREETYPE_VERSION=2.13.2
|
||||
HARFBUZZ_VERSION=8.2.1
|
||||
HARFBUZZ_VERSION=8.3.0
|
||||
LIBPNG_VERSION=1.6.40
|
||||
JPEGTURBO_VERSION=3.0.1
|
||||
OPENJPEG_VERSION=2.5.0
|
||||
XZ_VERSION=5.4.5
|
||||
TIFF_VERSION=4.6.0
|
||||
LCMS2_VERSION=2.15
|
||||
LCMS2_VERSION=2.16
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
GIFLIB_VERSION=5.1.4
|
||||
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
|
||||
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
|
||||
brew install fribidi
|
||||
export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig"
|
||||
|
@ -25,21 +21,5 @@ fi
|
|||
|
||||
# Runs tests
|
||||
python3 selftest.py
|
||||
python3 -m pytest Tests/check_wheel.py
|
||||
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:
|
||||
push:
|
||||
paths:
|
||||
- ".github/workflows/wheels*.yml"
|
||||
- ".ci/requirements-cibw.txt"
|
||||
- ".github/workflows/wheel*"
|
||||
- "wheels/*"
|
||||
- "winbuild/build_prepare.py"
|
||||
- "winbuild/fribidi.cmake"
|
||||
tags:
|
||||
- "*"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/wheels*.yml"
|
||||
- ".ci/requirements-cibw.txt"
|
||||
- ".github/workflows/wheel*"
|
||||
- "wheels/*"
|
||||
- "winbuild/build_prepare.py"
|
||||
- "winbuild/fribidi.cmake"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
@ -63,7 +69,6 @@ jobs:
|
|||
env:
|
||||
CIBW_ARCHS: ${{ matrix.archs }}
|
||||
CIBW_BUILD: ${{ matrix.build }}
|
||||
CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor
|
||||
CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }}
|
||||
CIBW_SKIP: pp38-*
|
||||
|
@ -75,6 +80,102 @@ jobs:
|
|||
name: dist
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
@ -97,7 +198,7 @@ jobs:
|
|||
success:
|
||||
permissions:
|
||||
contents: none
|
||||
needs: [build, sdist]
|
||||
needs: [build, windows, sdist]
|
||||
runs-on: ubuntu-latest
|
||||
name: Wheels Successful
|
||||
steps:
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.1.4
|
||||
rev: v0.1.6
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 23.10.1
|
||||
rev: 23.11.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
|
@ -42,12 +42,12 @@ repos:
|
|||
exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||
|
||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||
rev: v0.8.1
|
||||
rev: v0.9.0
|
||||
hooks:
|
||||
- id: sphinx-lint
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: 1.4.1
|
||||
rev: 1.5.3
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ if: tag IS present OR type = api
|
|||
env:
|
||||
global:
|
||||
- CIBW_ARCHS=aarch64
|
||||
- CIBW_CONFIG_SETTINGS="raqm=enable raqm=vendor fribidi=vendor"
|
||||
- CIBW_SKIP=pp38-*
|
||||
|
||||
language: python
|
||||
|
|
20
CHANGES.rst
20
CHANGES.rst
|
@ -5,7 +5,25 @@ Changelog (Pillow)
|
|||
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]
|
||||
|
||||
- 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
|
||||
|
||||
### macOS and Linux
|
||||
* [ ] 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):
|
||||
```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)
|
||||
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
|
||||
|
||||
* [ ] 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 os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import tempfile
|
||||
|
@ -258,11 +259,21 @@ def hopper(mode=None, cache={}):
|
|||
|
||||
|
||||
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():
|
||||
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():
|
||||
|
|
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)
|
||||
|
||||
with Image.open("Tests/images/apng/single_frame_default.png") as im:
|
||||
frames = []
|
||||
for frame_im in ImageSequence.Iterator(im):
|
||||
frames.append(frame_im.copy())
|
||||
frames = [frame_im.copy() for frame_im in ImageSequence.Iterator(im)]
|
||||
frames[0].save(
|
||||
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_ATI1 = "Tests/images/ati1.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_UNORM = "Tests/images/bc5_unorm.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_BC5U = "Tests/images/bc5u.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"
|
||||
|
||||
|
||||
def test_sanity_dxt1():
|
||||
"""Check DXT1 images can be opened"""
|
||||
@pytest.mark.parametrize(
|
||||
"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:
|
||||
target = target.convert("RGBA")
|
||||
with Image.open(TEST_FILE_DXT1) as im:
|
||||
with Image.open(image_path) as im:
|
||||
im.load()
|
||||
|
||||
assert im.format == "DDS"
|
||||
|
@ -70,10 +84,18 @@ def test_sanity_dxt5():
|
|||
assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png"))
|
||||
|
||||
|
||||
def test_sanity_ati1():
|
||||
"""Check ATI1 images can be opened"""
|
||||
@pytest.mark.parametrize(
|
||||
"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()
|
||||
|
||||
assert im.format == "DDS"
|
||||
|
@ -83,6 +105,27 @@ def test_sanity_ati1():
|
|||
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(
|
||||
"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(
|
||||
("mode", "size", "test_file"),
|
||||
[
|
||||
|
@ -305,9 +342,22 @@ def test_palette():
|
|||
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 Image.open("Tests/images/unimplemented_pixel_format.dds"):
|
||||
with Image.open(test_file):
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ def test_quantize_no_dither():
|
|||
|
||||
def test_quantize_no_dither2():
|
||||
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))
|
||||
data = (0, 0, 0, 32, 32, 32)
|
||||
|
|
|
@ -4,6 +4,7 @@ import re
|
|||
import shutil
|
||||
import sys
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from packaging.version import parse as parse_version
|
||||
|
@ -76,8 +77,9 @@ def _render(font, layout_engine):
|
|||
return img
|
||||
|
||||
|
||||
def test_font_with_name(layout_engine):
|
||||
_render(FONT_PATH, layout_engine)
|
||||
@pytest.mark.parametrize("font", (FONT_PATH, Path(FONT_PATH)))
|
||||
def test_font_with_name(layout_engine, font):
|
||||
_render(font, 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:
|
||||
@pytest.mark.skipif(
|
||||
os.environ.get("USERNAME") == "ContainerAdministrator",
|
||||
reason="can't grab screen when running in Docker",
|
||||
)
|
||||
@pytest.mark.skipif(
|
||||
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
|
||||
|
||||
**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**
|
||||
A comment about the image.
|
||||
|
||||
|
|
|
@ -95,11 +95,10 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images::
|
|||
|
||||
.. 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 64-bit versions in the wheel format. These binaries include
|
||||
support for all optional libraries except libimagequant and libxcb. Raqm support
|
||||
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
|
||||
(with the exception of Python 3.8 on arm64). These binaries include support
|
||||
for all optional libraries except libimagequant and libxcb. Raqm support
|
||||
requires FriBiDi to be installed separately::
|
||||
|
||||
python3 -m pip install --upgrade pip
|
||||
|
@ -176,7 +175,7 @@ Many of Pillow's features require external libraries:
|
|||
* **littlecms** provides color management
|
||||
|
||||
* 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.
|
||||
|
||||
|
|
|
@ -70,12 +70,14 @@ Methods
|
|||
Constants
|
||||
---------
|
||||
|
||||
.. data:: PIL.ImageFont.Layout.BASIC
|
||||
.. class:: Layout
|
||||
|
||||
.. py:attribute:: BASIC
|
||||
|
||||
Use basic text layout for TrueType font.
|
||||
Advanced features such as text direction are not supported.
|
||||
|
||||
.. data:: PIL.ImageFont.Layout.RAQM
|
||||
.. py:attribute:: RAQM
|
||||
|
||||
Use Raqm text layout for TrueType font.
|
||||
Advanced features are supported.
|
||||
|
@ -83,9 +85,6 @@ Constants
|
|||
Requires Raqm, you can check support using
|
||||
:py:func:`PIL.features.check_feature` with ``feature="raqm"``.
|
||||
|
||||
Constants
|
||||
---------
|
||||
|
||||
.. data:: MAX_STRING_LENGTH
|
||||
|
||||
Set to 1,000,000, to protect against potential DOS attacks. Pillow will
|
||||
|
|
|
@ -33,6 +33,14 @@ Plugin reference
|
|||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
:mod:`~PIL.DdsImagePlugin` Module
|
||||
---------------------------------
|
||||
|
||||
.. automodule:: PIL.DdsImagePlugin
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
: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::
|
||||
:maxdepth: 2
|
||||
|
||||
10.2.0
|
||||
10.1.0
|
||||
10.0.1
|
||||
10.0.0
|
||||
|
|
|
@ -88,12 +88,15 @@ version = {attr = "PIL.__version__"}
|
|||
|
||||
[tool.cibuildwheel]
|
||||
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-extras = "tests"
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
select = [
|
||||
"C4", # flake8-comprehensions
|
||||
"E", # pycodestyle errors
|
||||
"EM", # flake8-errmsg
|
||||
"F", # pyflakes errors
|
||||
|
|
22
setup.py
22
setup.py
|
@ -440,17 +440,17 @@ class pil_build_ext(build_ext):
|
|||
|
||||
#
|
||||
# add configured kits
|
||||
for root_name, lib_name in dict(
|
||||
JPEG_ROOT="libjpeg",
|
||||
JPEG2K_ROOT="libopenjp2",
|
||||
TIFF_ROOT=("libtiff-5", "libtiff-4"),
|
||||
ZLIB_ROOT="zlib",
|
||||
FREETYPE_ROOT="freetype2",
|
||||
HARFBUZZ_ROOT="harfbuzz",
|
||||
FRIBIDI_ROOT="fribidi",
|
||||
LCMS_ROOT="lcms2",
|
||||
IMAGEQUANT_ROOT="libimagequant",
|
||||
).items():
|
||||
for root_name, lib_name in {
|
||||
"JPEG_ROOT": "libjpeg",
|
||||
"JPEG2K_ROOT": "libopenjp2",
|
||||
"TIFF_ROOT": ("libtiff-5", "libtiff-4"),
|
||||
"ZLIB_ROOT": "zlib",
|
||||
"FREETYPE_ROOT": "freetype2",
|
||||
"HARFBUZZ_ROOT": "harfbuzz",
|
||||
"FRIBIDI_ROOT": "fribidi",
|
||||
"LCMS_ROOT": "lcms2",
|
||||
"IMAGEQUANT_ROOT": "libimagequant",
|
||||
}.items():
|
||||
root = globals()[root_name]
|
||||
|
||||
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))
|
||||
|
||||
# 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)
|
||||
header = 40 # or 64 for OS/2 version 2
|
||||
|
|
|
@ -64,8 +64,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile):
|
|||
d, e, o, a = self.tile[0]
|
||||
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>
|
||||
|
||||
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)
|
||||
Full text of the CC0 license:
|
||||
https://creativecommons.org/publicdomain/zero/1.0/
|
||||
https://creativecommons.org/publicdomain/zero/1.0/
|
||||
"""
|
||||
|
||||
import io
|
||||
import struct
|
||||
from io import BytesIO
|
||||
import sys
|
||||
from enum import IntEnum, IntFlag
|
||||
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import o8
|
||||
from ._binary import i32le as i32
|
||||
from ._binary import o32le as o32
|
||||
|
||||
# Magic ("DDS ")
|
||||
DDS_MAGIC = 0x20534444
|
||||
|
||||
|
||||
# DDS flags
|
||||
DDSD_CAPS = 0x1
|
||||
DDSD_HEIGHT = 0x2
|
||||
DDSD_WIDTH = 0x4
|
||||
DDSD_PITCH = 0x8
|
||||
DDSD_PIXELFORMAT = 0x1000
|
||||
DDSD_MIPMAPCOUNT = 0x20000
|
||||
DDSD_LINEARSIZE = 0x80000
|
||||
DDSD_DEPTH = 0x800000
|
||||
class DDSD(IntFlag):
|
||||
CAPS = 0x1
|
||||
HEIGHT = 0x2
|
||||
WIDTH = 0x4
|
||||
PITCH = 0x8
|
||||
PIXELFORMAT = 0x1000
|
||||
MIPMAPCOUNT = 0x20000
|
||||
LINEARSIZE = 0x80000
|
||||
DEPTH = 0x800000
|
||||
|
||||
|
||||
# DDS caps
|
||||
DDSCAPS_COMPLEX = 0x8
|
||||
DDSCAPS_TEXTURE = 0x1000
|
||||
DDSCAPS_MIPMAP = 0x400000
|
||||
class DDSCAPS(IntFlag):
|
||||
COMPLEX = 0x8
|
||||
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
|
||||
DDPF_ALPHAPIXELS = 0x1
|
||||
DDPF_ALPHA = 0x2
|
||||
DDPF_FOURCC = 0x4
|
||||
DDPF_PALETTEINDEXED8 = 0x20
|
||||
DDPF_RGB = 0x40
|
||||
DDPF_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
|
||||
class DDPF(IntFlag):
|
||||
ALPHAPIXELS = 0x1
|
||||
ALPHA = 0x2
|
||||
FOURCC = 0x4
|
||||
PALETTEINDEXED8 = 0x20
|
||||
RGB = 0x40
|
||||
LUMINANCE = 0x20000
|
||||
|
||||
|
||||
# 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
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
|
||||
DXGI_FORMAT_BC5_TYPELESS = 82
|
||||
DXGI_FORMAT_BC5_UNORM = 83
|
||||
DXGI_FORMAT_BC5_SNORM = 84
|
||||
DXGI_FORMAT_BC6H_UF16 = 95
|
||||
DXGI_FORMAT_BC6H_SF16 = 96
|
||||
DXGI_FORMAT_BC7_TYPELESS = 97
|
||||
DXGI_FORMAT_BC7_UNORM = 98
|
||||
DXGI_FORMAT_BC7_UNORM_SRGB = 99
|
||||
|
||||
class D3DFMT(IntEnum):
|
||||
UNKNOWN = 0
|
||||
R8G8B8 = 20
|
||||
A8R8G8B8 = 21
|
||||
X8R8G8B8 = 22
|
||||
R5G6B5 = 23
|
||||
X1R5G5B5 = 24
|
||||
A1R5G5B5 = 25
|
||||
A4R4G4B4 = 26
|
||||
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):
|
||||
|
@ -125,112 +337,134 @@ class DdsImageFile(ImageFile.ImageFile):
|
|||
if len(header_bytes) != 120:
|
||||
msg = f"Incomplete header: {len(header_bytes)} bytes"
|
||||
raise OSError(msg)
|
||||
header = BytesIO(header_bytes)
|
||||
header = io.BytesIO(header_bytes)
|
||||
|
||||
flags, height, width = struct.unpack("<3I", header.read(12))
|
||||
self._size = (width, height)
|
||||
self._mode = "RGBA"
|
||||
extents = (0, 0) + self.size
|
||||
|
||||
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
|
||||
struct.unpack("<11I", header.read(44)) # reserved
|
||||
|
||||
# pixel format
|
||||
pfsize, pfflags = struct.unpack("<2I", header.read(8))
|
||||
fourcc = header.read(4)
|
||||
(bitcount,) = struct.unpack("<I", header.read(4))
|
||||
if pfflags & DDPF_LUMINANCE:
|
||||
# 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:
|
||||
pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16))
|
||||
n = 0
|
||||
rawmode = None
|
||||
if pfflags & DDPF.RGB:
|
||||
# Texture contains uncompressed RGB data
|
||||
if pfflags & DDPF_ALPHAPIXELS:
|
||||
if pfflags & DDPF.ALPHAPIXELS:
|
||||
self._mode = "RGBA"
|
||||
mask_count = 4
|
||||
else:
|
||||
self._mode = "RGB"
|
||||
mask_count = 3
|
||||
|
||||
masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
|
||||
self.tile = [("dds_rgb", (0, 0) + self.size, 0, (bitcount, masks))]
|
||||
elif pfflags & DDPF_PALETTEINDEXED8:
|
||||
self.tile = [("dds_rgb", extents, 0, (bitcount, masks))]
|
||||
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.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
|
||||
self.tile = [("raw", (0, 0) + self.size, 0, "L")]
|
||||
else:
|
||||
data_start = header_size + 4
|
||||
n = 0
|
||||
if fourcc == b"DXT1":
|
||||
elif pfflags & DDPF.FOURCC:
|
||||
offset = header_size + 4
|
||||
if fourcc == D3DFMT.DXT1:
|
||||
self._mode = "RGBA"
|
||||
self.pixel_format = "DXT1"
|
||||
n = 1
|
||||
elif fourcc == b"DXT3":
|
||||
elif fourcc == D3DFMT.DXT3:
|
||||
self._mode = "RGBA"
|
||||
self.pixel_format = "DXT3"
|
||||
n = 2
|
||||
elif fourcc == b"DXT5":
|
||||
elif fourcc == D3DFMT.DXT5:
|
||||
self._mode = "RGBA"
|
||||
self.pixel_format = "DXT5"
|
||||
n = 3
|
||||
elif fourcc == b"ATI1":
|
||||
elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1):
|
||||
self._mode = "L"
|
||||
self.pixel_format = "BC4"
|
||||
n = 4
|
||||
self._mode = "L"
|
||||
elif fourcc in (b"ATI2", b"BC5U"):
|
||||
self.pixel_format = "BC5"
|
||||
n = 5
|
||||
elif fourcc == D3DFMT.BC5S:
|
||||
self._mode = "RGB"
|
||||
elif fourcc == b"BC5S":
|
||||
self.pixel_format = "BC5S"
|
||||
n = 5
|
||||
elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2):
|
||||
self._mode = "RGB"
|
||||
elif fourcc == b"DX10":
|
||||
data_start += 20
|
||||
self.pixel_format = "BC5"
|
||||
n = 5
|
||||
elif fourcc == D3DFMT.DX10:
|
||||
offset += 20
|
||||
# ignoring flags which pertain to volume textures and cubemaps
|
||||
(dxgi_format,) = struct.unpack("<I", self.fp.read(4))
|
||||
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"
|
||||
n = 5
|
||||
elif dxgi_format == DXGI_FORMAT.BC5_SNORM:
|
||||
self._mode = "RGB"
|
||||
elif dxgi_format == DXGI_FORMAT_BC5_SNORM:
|
||||
self.pixel_format = "BC5S"
|
||||
n = 5
|
||||
elif dxgi_format == DXGI_FORMAT.BC6H_UF16:
|
||||
self._mode = "RGB"
|
||||
elif dxgi_format == DXGI_FORMAT_BC6H_UF16:
|
||||
self.pixel_format = "BC6H"
|
||||
n = 6
|
||||
elif dxgi_format == DXGI_FORMAT.BC6H_SF16:
|
||||
self._mode = "RGB"
|
||||
elif dxgi_format == DXGI_FORMAT_BC6H_SF16:
|
||||
self.pixel_format = "BC6HS"
|
||||
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 (
|
||||
DXGI_FORMAT_R8G8B8A8_TYPELESS,
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM,
|
||||
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
|
||||
DXGI_FORMAT.BC7_TYPELESS,
|
||||
DXGI_FORMAT.BC7_UNORM,
|
||||
DXGI_FORMAT.BC7_UNORM_SRGB,
|
||||
):
|
||||
self.tile = [("raw", (0, 0) + self.size, 0, ("RGBA", 0, 1))]
|
||||
if dxgi_format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
||||
self._mode = "RGBA"
|
||||
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
|
||||
return
|
||||
else:
|
||||
msg = f"Unimplemented DXGI format {dxgi_format}"
|
||||
raise NotImplementedError(msg)
|
||||
else:
|
||||
msg = f"Unimplemented pixel format {repr(fourcc)}"
|
||||
raise NotImplementedError(msg)
|
||||
else:
|
||||
msg = f"Unknown pixel format flags {pfflags}"
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
if n:
|
||||
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):
|
||||
pass
|
||||
|
@ -274,48 +508,51 @@ def _save(im, fp, filename):
|
|||
msg = f"cannot write mode {im.mode} as DDS"
|
||||
raise OSError(msg)
|
||||
|
||||
alpha = im.mode[-1] == "A"
|
||||
if im.mode[0] == "L":
|
||||
pixel_flags = DDPF.LUMINANCE
|
||||
rawmode = im.mode
|
||||
masks = [0xFF0000, 0xFF00, 0xFF]
|
||||
if im.mode in ("L", "LA"):
|
||||
pixel_flags = DDPF_LUMINANCE
|
||||
if alpha:
|
||||
rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF]
|
||||
else:
|
||||
pixel_flags = DDPF_RGB
|
||||
rawmode = rawmode[::-1]
|
||||
if im.mode in ("LA", "RGBA"):
|
||||
pixel_flags |= DDPF_ALPHAPIXELS
|
||||
masks.append(0xFF000000)
|
||||
rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000]
|
||||
else:
|
||||
pixel_flags = DDPF.RGB
|
||||
rawmode = im.mode[::-1]
|
||||
rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF]
|
||||
|
||||
bitcount = len(masks) * 8
|
||||
while len(masks) < 4:
|
||||
masks.append(0)
|
||||
if alpha:
|
||||
r, g, b, a = im.split()
|
||||
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(
|
||||
o32(DDS_MAGIC)
|
||||
+ o32(124) # header size
|
||||
+ o32(
|
||||
DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PITCH | DDSD_PIXELFORMAT
|
||||
) # flags
|
||||
+ o32(im.height)
|
||||
+ o32(im.width)
|
||||
+ o32((im.width * bitcount + 7) // 8) # pitch
|
||||
+ o32(0) # depth
|
||||
+ o32(0) # mipmaps
|
||||
+ o32(0) * 11 # reserved
|
||||
+ o32(32) # pfsize
|
||||
+ o32(pixel_flags) # pfflags
|
||||
+ o32(0) # fourcc
|
||||
+ o32(bitcount) # bitcount
|
||||
+ b"".join(o32(mask) for mask in masks) # rgbabitmask
|
||||
+ o32(DDSCAPS_TEXTURE) # dwCaps
|
||||
+ o32(0) # dwCaps2
|
||||
+ o32(0) # dwCaps3
|
||||
+ o32(0) # dwCaps4
|
||||
+ o32(0) # dwReserved2
|
||||
+ struct.pack(
|
||||
"<7I",
|
||||
124, # header size
|
||||
flags, # flags
|
||||
im.height,
|
||||
im.width,
|
||||
pitch,
|
||||
0, # depth
|
||||
0, # mipmaps
|
||||
)
|
||||
+ struct.pack("11I", *((0,) * 11)) # reserved
|
||||
# pfsize, pfflags, fourcc, bitcount
|
||||
+ struct.pack("<4I", 32, pixel_flags, 0, bitcount)
|
||||
+ struct.pack("<4I", *rgba_mask) # dwRGBABitMask
|
||||
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
|
||||
)
|
||||
ImageFile._save(
|
||||
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
|
||||
)
|
||||
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):
|
||||
|
|
|
@ -97,16 +97,15 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
|
||||
s = prop[0x2000002 | id]
|
||||
|
||||
colors = []
|
||||
bands = i32(s, 4)
|
||||
if bands > 4:
|
||||
msg = "Invalid number of bands"
|
||||
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
|
||||
self.jpeg = {}
|
||||
|
@ -227,6 +226,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
break # isn't really required
|
||||
|
||||
self.stream = stream
|
||||
self._fp = self.fp
|
||||
self.fp = None
|
||||
|
||||
def load(self):
|
||||
|
|
|
@ -40,7 +40,7 @@ from enum import IntEnum
|
|||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import defusedxml.ElementTree as ElementTree
|
||||
from defusedxml import ElementTree
|
||||
except ImportError:
|
||||
ElementTree = None
|
||||
|
||||
|
@ -1160,7 +1160,7 @@ class Image:
|
|||
if palette.mode != "P":
|
||||
msg = "bad mode for palette image"
|
||||
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"
|
||||
raise ValueError(msg)
|
||||
im = self.im.convert("P", dither, palette.im)
|
||||
|
@ -1288,9 +1288,9 @@ class Image:
|
|||
if self.im.bands == 1 or multiband:
|
||||
return self._new(filter.filter(self.im))
|
||||
|
||||
ims = []
|
||||
for c in range(self.im.bands):
|
||||
ims.append(self._new(filter.filter(self.im.getband(c))))
|
||||
ims = [
|
||||
self._new(filter.filter(self.im.getband(c))) for c in range(self.im.bands)
|
||||
]
|
||||
return merge(self.mode, ims)
|
||||
|
||||
def getbands(self):
|
||||
|
@ -1339,10 +1339,7 @@ class Image:
|
|||
self.load()
|
||||
if self.mode in ("1", "L", "P"):
|
||||
h = self.im.histogram()
|
||||
out = []
|
||||
for i in range(256):
|
||||
if h[i]:
|
||||
out.append((h[i], i))
|
||||
out = [(h[i], i) for i in range(256) if h[i]]
|
||||
if len(out) > maxcolors:
|
||||
return None
|
||||
return out
|
||||
|
@ -1383,10 +1380,7 @@ class Image:
|
|||
|
||||
self.load()
|
||||
if self.im.bands > 1:
|
||||
extrema = []
|
||||
for i in range(self.im.bands):
|
||||
extrema.append(self.im.getband(i).getextrema())
|
||||
return tuple(extrema)
|
||||
return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
|
||||
return self.im.getextrema()
|
||||
|
||||
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
|
||||
description = profile.profile.profile_description
|
||||
cpright = profile.profile.copyright
|
||||
arr = []
|
||||
for elt in (description, cpright):
|
||||
if elt:
|
||||
arr.append(elt)
|
||||
return "\r\n\r\n".join(arr) + "\r\n\r\n"
|
||||
elements = [element for element in (description, cpright) if element]
|
||||
return "\r\n\r\n".join(elements) + "\r\n\r\n"
|
||||
|
||||
except (AttributeError, OSError, TypeError, ValueError) as v:
|
||||
raise PyCMSError(v) from v
|
||||
|
|
|
@ -921,7 +921,7 @@ def floodfill(image, xy, value, border=None, thresh=0):
|
|||
if border is None:
|
||||
fill = _color_diff(p, background) <= thresh
|
||||
else:
|
||||
fill = p != value and p != border
|
||||
fill = p not in (value, border)
|
||||
if fill:
|
||||
pixel[s, t] = value
|
||||
new_edge.add((s, t))
|
||||
|
|
|
@ -26,11 +26,13 @@
|
|||
#
|
||||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import itertools
|
||||
import struct
|
||||
import sys
|
||||
from typing import NamedTuple
|
||||
|
||||
from . import Image
|
||||
from ._util import is_path
|
||||
|
@ -77,6 +79,13 @@ def _tilesort(t):
|
|||
return t[2]
|
||||
|
||||
|
||||
class _Tile(NamedTuple):
|
||||
encoder_name: str
|
||||
extents: tuple[int, int, int, int]
|
||||
offset: int
|
||||
args: tuple | str | None
|
||||
|
||||
|
||||
#
|
||||
# --------------------------------------------------------------------
|
||||
# ImageFile base class
|
||||
|
@ -520,13 +529,13 @@ def _save(im, fp, tile, bufsize=0):
|
|||
fp.flush()
|
||||
|
||||
|
||||
def _encode_tile(im, fp, tile, bufsize, fh, exc=None):
|
||||
for e, b, o, a in tile:
|
||||
if o > 0:
|
||||
fp.seek(o)
|
||||
encoder = Image._getencoder(im.mode, e, a, im.encoderconfig)
|
||||
def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None):
|
||||
for encoder_name, extents, offset, args in tile:
|
||||
if offset > 0:
|
||||
fp.seek(offset)
|
||||
encoder = Image._getencoder(im.mode, encoder_name, args, im.encoderconfig)
|
||||
try:
|
||||
encoder.setimage(im.im, b)
|
||||
encoder.setimage(im.im, extents)
|
||||
if encoder.pushes_fd:
|
||||
encoder.setfd(fp)
|
||||
errcode = encoder.encode_to_pyfd()[1]
|
||||
|
|
|
@ -25,12 +25,16 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from enum import IntEnum
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import IO
|
||||
|
||||
from . import Image
|
||||
from ._util import is_directory, is_path
|
||||
|
@ -185,9 +189,20 @@ class ImageFont:
|
|||
class FreeTypeFont:
|
||||
"""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
|
||||
|
||||
if size <= 0:
|
||||
msg = "font size must be greater than 0"
|
||||
raise ValueError(msg)
|
||||
|
||||
self.path = font
|
||||
self.size = size
|
||||
self.index = index
|
||||
|
@ -213,6 +228,8 @@ class FreeTypeFont:
|
|||
)
|
||||
|
||||
if is_path(font):
|
||||
if isinstance(font, Path):
|
||||
font = str(font)
|
||||
if sys.platform == "win32":
|
||||
font_bytes_path = font if isinstance(font, bytes) else font.encode()
|
||||
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
|
||||
encoding of any text provided in subsequent operations.
|
||||
: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.
|
||||
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.
|
||||
"""
|
||||
|
||||
if size <= 0:
|
||||
msg = "font size must be greater than 0"
|
||||
raise ValueError(msg)
|
||||
|
||||
def freetype(font):
|
||||
return FreeTypeFont(font, size, index, encoding, layout_engine)
|
||||
|
||||
|
|
|
@ -557,9 +557,7 @@ def invert(image):
|
|||
:param image: The image to invert.
|
||||
:return: An image.
|
||||
"""
|
||||
lut = []
|
||||
for i in range(256):
|
||||
lut.append(255 - i)
|
||||
lut = list(range(255, -1, -1))
|
||||
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).
|
||||
:return: An image.
|
||||
"""
|
||||
lut = []
|
||||
mask = ~(2 ** (8 - bits) - 1)
|
||||
for i in range(256):
|
||||
lut.append(i & mask)
|
||||
lut = [i & mask for i in range(256)]
|
||||
return _lut(image, lut)
|
||||
|
||||
|
||||
|
|
|
@ -200,21 +200,15 @@ def raw(rawmode, data):
|
|||
|
||||
|
||||
def make_linear_lut(black, white):
|
||||
lut = []
|
||||
if black == 0:
|
||||
for i in range(256):
|
||||
lut.append(white * i // 255)
|
||||
else:
|
||||
return [white * i // 255 for i in range(256)]
|
||||
|
||||
msg = "unavailable when black is non-zero"
|
||||
raise NotImplementedError(msg) # FIXME
|
||||
return lut
|
||||
|
||||
|
||||
def make_gamma_lut(exp):
|
||||
lut = []
|
||||
for i in range(256):
|
||||
lut.append(int(((i / 255.0) ** exp) * 255.0 + 0.5))
|
||||
return lut
|
||||
return [int(((i / 255.0) ** exp) * 255.0 + 0.5) for i in range(256)]
|
||||
|
||||
|
||||
def negative(mode="RGB"):
|
||||
|
@ -226,9 +220,7 @@ def negative(mode="RGB"):
|
|||
def random(mode="RGB"):
|
||||
from random import randint
|
||||
|
||||
palette = []
|
||||
for i in range(256 * len(mode)):
|
||||
palette.append(randint(0, 255))
|
||||
palette = [randint(0, 255) for _ in range(256 * len(mode))]
|
||||
return ImagePalette(mode, palette)
|
||||
|
||||
|
||||
|
|
|
@ -103,12 +103,10 @@ def align8to32(bytes, width, mode):
|
|||
if not extra_padding:
|
||||
return bytes
|
||||
|
||||
new_data = []
|
||||
for i in range(len(bytes) // bytes_per_line):
|
||||
new_data.append(
|
||||
bytes[i * bytes_per_line : (i + 1) * bytes_per_line]
|
||||
+ b"\x00" * extra_padding
|
||||
)
|
||||
new_data = [
|
||||
bytes[i * bytes_per_line : (i + 1) * bytes_per_line] + b"\x00" * extra_padding
|
||||
for i in range(len(bytes) // bytes_per_line)
|
||||
]
|
||||
|
||||
return b"".join(new_data)
|
||||
|
||||
|
@ -131,15 +129,11 @@ def _toqclass_helper(im):
|
|||
format = qt_format.Format_Mono
|
||||
elif im.mode == "L":
|
||||
format = qt_format.Format_Indexed8
|
||||
colortable = []
|
||||
for i in range(256):
|
||||
colortable.append(rgb(i, i, i))
|
||||
colortable = [rgb(i, i, i) for i in range(256)]
|
||||
elif im.mode == "P":
|
||||
format = qt_format.Format_Indexed8
|
||||
colortable = []
|
||||
palette = im.getpalette()
|
||||
for i in range(0, len(palette), 3):
|
||||
colortable.append(rgb(*palette[i : i + 3]))
|
||||
colortable = [rgb(*palette[i : i + 3]) for i in range(0, len(palette), 3)]
|
||||
elif im.mode == "RGB":
|
||||
# Populate the 4th channel with 255
|
||||
im = im.convert("RGBA")
|
||||
|
|
|
@ -21,9 +21,7 @@
|
|||
# See the README file for information on usage and redistribution.
|
||||
#
|
||||
|
||||
import functools
|
||||
import math
|
||||
import operator
|
||||
|
||||
|
||||
class Stat:
|
||||
|
@ -53,26 +51,22 @@ class Stat:
|
|||
"""Get min/max values for each band in the image"""
|
||||
|
||||
def minmax(histogram):
|
||||
n = 255
|
||||
x = 0
|
||||
res_min, res_max = 255, 0
|
||||
for i in range(256):
|
||||
if histogram[i]:
|
||||
n = min(n, i)
|
||||
x = max(x, i)
|
||||
return n, x # returns (255, 0) if there's no data in the histogram
|
||||
res_min = i
|
||||
break
|
||||
for i in range(255, -1, -1):
|
||||
if histogram[i]:
|
||||
res_max = i
|
||||
break
|
||||
return res_min, res_max
|
||||
|
||||
v = []
|
||||
for i in range(0, len(self.h), 256):
|
||||
v.append(minmax(self.h[i:]))
|
||||
return v
|
||||
return [minmax(self.h[i:]) for i in range(0, len(self.h), 256)]
|
||||
|
||||
def _getcount(self):
|
||||
"""Get total number of pixels in each layer"""
|
||||
|
||||
v = []
|
||||
for i in range(0, len(self.h), 256):
|
||||
v.append(functools.reduce(operator.add, self.h[i : i + 256]))
|
||||
return v
|
||||
return [sum(self.h[i : i + 256]) for i in range(0, len(self.h), 256)]
|
||||
|
||||
def _getsum(self):
|
||||
"""Get sum of all pixels in each layer"""
|
||||
|
@ -98,11 +92,7 @@ class Stat:
|
|||
|
||||
def _getmean(self):
|
||||
"""Get average pixel level for each layer"""
|
||||
|
||||
v = []
|
||||
for i in self.bands:
|
||||
v.append(self.sum[i] / self.count[i])
|
||||
return v
|
||||
return [self.sum[i] / self.count[i] for i in self.bands]
|
||||
|
||||
def _getmedian(self):
|
||||
"""Get median pixel level for each layer"""
|
||||
|
@ -121,28 +111,18 @@ class Stat:
|
|||
|
||||
def _getrms(self):
|
||||
"""Get RMS for each layer"""
|
||||
|
||||
v = []
|
||||
for i in self.bands:
|
||||
v.append(math.sqrt(self.sum2[i] / self.count[i]))
|
||||
return v
|
||||
return [math.sqrt(self.sum2[i] / self.count[i]) for i in self.bands]
|
||||
|
||||
def _getvar(self):
|
||||
"""Get variance for each layer"""
|
||||
|
||||
v = []
|
||||
for i in self.bands:
|
||||
n = self.count[i]
|
||||
v.append((self.sum2[i] - (self.sum[i] ** 2.0) / n) / n)
|
||||
return v
|
||||
return [
|
||||
(self.sum2[i] - (self.sum[i] ** 2.0) / self.count[i]) / self.count[i]
|
||||
for i in self.bands
|
||||
]
|
||||
|
||||
def _getstddev(self):
|
||||
"""Get standard deviation for each layer"""
|
||||
|
||||
v = []
|
||||
for i in self.bands:
|
||||
v.append(math.sqrt(self.var[i]))
|
||||
return v
|
||||
return [math.sqrt(self.var[i]) for i in self.bands]
|
||||
|
||||
|
||||
Global = Stat # compatibility
|
||||
|
|
|
@ -334,10 +334,7 @@ def _save(im, fp, filename):
|
|||
if quality_layers is not None and not (
|
||||
isinstance(quality_layers, (list, tuple))
|
||||
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"
|
||||
|
|
|
@ -233,9 +233,7 @@ def SOF(self, marker):
|
|||
# fixup icc profile
|
||||
self.icclist.sort() # sort by sequence number
|
||||
if self.icclist[0][13] == len(self.icclist):
|
||||
profile = []
|
||||
for p in self.icclist:
|
||||
profile.append(p[14:])
|
||||
profile = [p[14:] for p in self.icclist]
|
||||
icc_profile = b"".join(profile)
|
||||
else:
|
||||
icc_profile = None # wrong number of fragments
|
||||
|
@ -397,7 +395,7 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
# self.__offset = self.fp.tell()
|
||||
break
|
||||
s = self.fp.read(1)
|
||||
elif i == 0 or i == 0xFFFF:
|
||||
elif i in {0, 0xFFFF}:
|
||||
# padded marker or junk; move on
|
||||
s = b"\xff"
|
||||
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
|
||||
# best way to identify MIC files, but what the... ;-)
|
||||
|
||||
self.images = []
|
||||
for path in self.ole.listdir():
|
||||
if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image":
|
||||
self.images.append(path)
|
||||
self.images = [
|
||||
path
|
||||
for path in self.ole.listdir()
|
||||
if path[1:] and path[0][-4:] == ".ACI" and path[1] == "Image"
|
||||
]
|
||||
|
||||
# if we didn't find any images, this is probably not
|
||||
# an MIC file.
|
||||
|
@ -66,6 +67,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
self._n_frames = len(self.images)
|
||||
self.is_animated = self._n_frames > 1
|
||||
|
||||
self.__fp = self.fp
|
||||
self.seek(0)
|
||||
|
||||
def seek(self, frame):
|
||||
|
@ -87,10 +89,12 @@ class MicImageFile(TiffImagePlugin.TiffImageFile):
|
|||
return self.frame
|
||||
|
||||
def close(self):
|
||||
self.__fp.close()
|
||||
self.ole.close()
|
||||
super().close()
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.__fp.close()
|
||||
self.ole.close()
|
||||
super().__exit__()
|
||||
|
||||
|
|
|
@ -129,9 +129,8 @@ class PcfFontFile(FontFile.FontFile):
|
|||
nprops = i32(fp.read(4))
|
||||
|
||||
# read property description
|
||||
p = []
|
||||
for i in range(nprops):
|
||||
p.append((i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))))
|
||||
p = [(i32(fp.read(4)), i8(fp.read(1)), i32(fp.read(4))) for _ in range(nprops)]
|
||||
|
||||
if nprops & 3:
|
||||
fp.seek(4 - (nprops & 3), io.SEEK_CUR) # pad
|
||||
|
||||
|
@ -186,8 +185,6 @@ class PcfFontFile(FontFile.FontFile):
|
|||
#
|
||||
# bitmap data
|
||||
|
||||
bitmaps = []
|
||||
|
||||
fp, format, i16, i32 = self._getformat(PCF_BITMAPS)
|
||||
|
||||
nbitmaps = i32(fp.read(4))
|
||||
|
@ -196,13 +193,9 @@ class PcfFontFile(FontFile.FontFile):
|
|||
msg = "Wrong number of bitmaps"
|
||||
raise OSError(msg)
|
||||
|
||||
offsets = []
|
||||
for i in range(nbitmaps):
|
||||
offsets.append(i32(fp.read(4)))
|
||||
offsets = [i32(fp.read(4)) for _ in range(nbitmaps)]
|
||||
|
||||
bitmap_sizes = []
|
||||
for i in range(4):
|
||||
bitmap_sizes.append(i32(fp.read(4)))
|
||||
bitmap_sizes = [i32(fp.read(4)) for _ in range(4)]
|
||||
|
||||
# byteorder = format & 4 # non-zero => MSB
|
||||
bitorder = format & 8 # non-zero => MSB
|
||||
|
@ -218,6 +211,7 @@ class PcfFontFile(FontFile.FontFile):
|
|||
if bitorder:
|
||||
mode = "1"
|
||||
|
||||
bitmaps = []
|
||||
for i in range(nbitmaps):
|
||||
xsize, ysize = metrics[i][:2]
|
||||
b, e = offsets[i : i + 2]
|
||||
|
|
|
@ -96,7 +96,7 @@ def _write_image(im, filename, existing_pdf, image_refs):
|
|||
dict_obj["ColorSpace"] = [
|
||||
PdfParser.PdfName("Indexed"),
|
||||
PdfParser.PdfName("DeviceRGB"),
|
||||
255,
|
||||
len(palette) // 3 - 1,
|
||||
PdfParser.PdfBinary(palette),
|
||||
]
|
||||
procset = "ImageI" # indexed color
|
||||
|
|
|
@ -123,7 +123,7 @@ class SgiImageFile(ImageFile.ImageFile):
|
|||
|
||||
|
||||
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"
|
||||
raise ValueError(msg)
|
||||
|
||||
|
@ -155,7 +155,7 @@ def _save(im, fp, filename):
|
|||
# Z Dimension: Number of channels
|
||||
z = len(im.mode)
|
||||
|
||||
if dim == 1 or dim == 2:
|
||||
if dim in {1, 2}:
|
||||
z = 1
|
||||
|
||||
# assert we've got the right number of bands.
|
||||
|
|
|
@ -238,9 +238,7 @@ def makeSpiderHeader(im):
|
|||
if nvalues < 23:
|
||||
return []
|
||||
|
||||
hdr = []
|
||||
for i in range(nvalues):
|
||||
hdr.append(0.0)
|
||||
hdr = [0.0] * nvalues
|
||||
|
||||
# NB these are Fortran indices
|
||||
hdr[1] = 1.0 # nslice (=1 for an image)
|
||||
|
|
|
@ -427,7 +427,7 @@ def _populate():
|
|||
|
||||
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():
|
||||
tags[k] = TagInfo(k, *v)
|
||||
|
||||
|
|
|
@ -279,10 +279,10 @@ DEPS = {
|
|||
"libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"],
|
||||
},
|
||||
"lcms2": {
|
||||
"url": SF_PROJECTS + "/lcms/files/lcms/2.15/lcms2-2.15.tar.gz/download",
|
||||
"filename": "lcms2-2.15.tar.gz",
|
||||
"dir": "lcms2-2.15",
|
||||
"license": "COPYING",
|
||||
"url": SF_PROJECTS + "/lcms/files/lcms/2.16/lcms2-2.16.tar.gz/download",
|
||||
"filename": "lcms2-2.16.tar.gz",
|
||||
"dir": "lcms2-2.16",
|
||||
"license": "LICENSE",
|
||||
"patch": {
|
||||
r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
|
||||
# default is /MD for x86 and /MT for x64, we need /MD always
|
||||
|
@ -345,9 +345,9 @@ DEPS = {
|
|||
"libs": [r"imagequant.lib"],
|
||||
},
|
||||
"harfbuzz": {
|
||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/8.2.1.zip",
|
||||
"filename": "harfbuzz-8.2.1.zip",
|
||||
"dir": "harfbuzz-8.2.1",
|
||||
"url": "https://github.com/harfbuzz/harfbuzz/archive/8.3.0.zip",
|
||||
"filename": "harfbuzz-8.3.0.zip",
|
||||
"dir": "harfbuzz-8.3.0",
|
||||
"license": "COPYING",
|
||||
"build": [
|
||||
*cmds_cmake(
|
||||
|
@ -482,7 +482,7 @@ def extract_dep(url: str, filename: str) -> None:
|
|||
msg = "Attempted Path Traversal in Zip File"
|
||||
raise RuntimeError(msg)
|
||||
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:
|
||||
for member in tgz.getnames():
|
||||
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:
|
||||
lines = [r'call "{build_dir}\build_env.cmd"']
|
||||
gha_groups = "GITHUB_ACTIONS" in os.environ
|
||||
for dep_name in DEPS:
|
||||
print()
|
||||
if dep_name in disabled:
|
||||
print(f"Skipping disabled dependency {dep_name}")
|
||||
continue
|
||||
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("if errorlevel 1 echo Build failed! && exit /B 1")
|
||||
if gha_groups:
|
||||
lines.append("@echo ::endgroup::")
|
||||
print()
|
||||
lines.append("@echo All Pillow dependencies built successfully!")
|
||||
write_script("build_dep_all.cmd", lines)
|
||||
|
|
Loading…
Reference in New Issue
Block a user