Merge branch 'main' into wrap
This commit is contained in:
commit
a69b4ec228
@ -53,7 +53,7 @@ pushd depends && ./install_imagequant.sh && popd
|
||||
pushd depends && sudo ./install_raqm.sh && popd
|
||||
|
||||
# libavif
|
||||
pushd depends && sudo ./install_libavif.sh && popd
|
||||
pushd depends && ./install_libavif.sh && popd
|
||||
|
||||
# extra test images
|
||||
pushd depends && ./install_extra_test_images.sh && popd
|
||||
|
||||
3
.github/workflows/cifuzz.yml
vendored
3
.github/workflows/cifuzz.yml
vendored
@ -24,6 +24,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
Fuzzing:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
16
.github/workflows/docs.yml
vendored
16
.github/workflows/docs.yml
vendored
@ -48,6 +48,13 @@ jobs:
|
||||
- name: Build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: Cache libavif
|
||||
uses: actions/cache@v5
|
||||
id: cache-libavif
|
||||
with:
|
||||
path: ~/cache-libavif
|
||||
key: ${{ runner.os }}-libavif-${{ hashFiles('depends/install_libavif.sh', 'depends/libavif-svt4.patch') }}
|
||||
|
||||
- name: Cache libimagequant
|
||||
uses: actions/cache@v5
|
||||
id: cache-libimagequant
|
||||
@ -55,12 +62,21 @@ jobs:
|
||||
path: ~/cache-libimagequant
|
||||
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
|
||||
|
||||
- name: Cache libwebp
|
||||
uses: actions/cache@v5
|
||||
id: cache-libwebp
|
||||
with:
|
||||
path: ~/cache-libwebp
|
||||
key: ${{ runner.os }}-libwebp-${{ hashFiles('depends/install_webp.sh') }}
|
||||
|
||||
- name: Install Linux dependencies
|
||||
run: |
|
||||
.ci/install.sh
|
||||
env:
|
||||
GHA_PYTHON_VERSION: "3.x"
|
||||
GHA_LIBAVIF_CACHE_HIT: ${{ steps.cache-libavif.outputs.cache-hit }}
|
||||
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||
GHA_LIBWEBP_CACHE_HIT: ${{ steps.cache-libwebp.outputs.cache-hit }}
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
|
||||
3
.github/workflows/release-drafter.yml
vendored
3
.github/workflows/release-drafter.yml
vendored
@ -14,6 +14,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
update_release_draft:
|
||||
permissions:
|
||||
|
||||
3
.github/workflows/stale.yml
vendored
3
.github/workflows/stale.yml
vendored
@ -12,6 +12,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
if: github.repository_owner == 'python-pillow'
|
||||
|
||||
5
.github/workflows/test-docker.yml
vendored
5
.github/workflows/test-docker.yml
vendored
@ -26,6 +26,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@ -83,7 +86,7 @@ jobs:
|
||||
|
||||
- name: Docker pull
|
||||
run: |
|
||||
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
||||
docker pull ${{ matrix.qemu-arch && format('--platform=linux/{0}', matrix.qemu-arch)}} pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
||||
|
||||
- name: Docker build
|
||||
run: |
|
||||
|
||||
1
.github/workflows/test-mingw.yml
vendored
1
.github/workflows/test-mingw.yml
vendored
@ -28,6 +28,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
COVERAGE_CORE: sysmon
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
3
.github/workflows/test-valgrind-memory.yml
vendored
3
.github/workflows/test-valgrind-memory.yml
vendored
@ -26,6 +26,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
|
||||
3
.github/workflows/test-valgrind.yml
vendored
3
.github/workflows/test-valgrind.yml
vendored
@ -24,6 +24,9 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
|
||||
5
.github/workflows/test-windows.yml
vendored
5
.github/workflows/test-windows.yml
vendored
@ -28,6 +28,7 @@ concurrency:
|
||||
|
||||
env:
|
||||
COVERAGE_CORE: sysmon
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -98,8 +99,8 @@ jobs:
|
||||
choco install nasm --no-progress
|
||||
echo "C:\Program Files\NASM" >> $env:GITHUB_PATH
|
||||
|
||||
choco install ghostscript --version=10.6.0 --no-progress
|
||||
echo "C:\Program Files\gs\gs10.06.0\bin" >> $env:GITHUB_PATH
|
||||
choco install ghostscript --version=10.7.0 --no-progress
|
||||
echo "C:\Program Files\gs\gs10.07.0\bin" >> $env:GITHUB_PATH
|
||||
|
||||
# Install extra test images
|
||||
xcopy /S /Y Tests\test-images\* Tests\images
|
||||
|
||||
20
.github/workflows/test.yml
vendored
20
.github/workflows/test.yml
vendored
@ -61,7 +61,7 @@ jobs:
|
||||
- { python-version: "3.14t", disable-gil: true }
|
||||
- { python-version: "3.13t", disable-gil: true }
|
||||
# Intel
|
||||
- { os: "macos-15-intel", python-version: "3.10" }
|
||||
- { os: "macos-26-intel", python-version: "3.10" }
|
||||
exclude:
|
||||
- { os: "macos-latest", python-version: "3.10" }
|
||||
|
||||
@ -91,6 +91,14 @@ jobs:
|
||||
- name: Build system information
|
||||
run: python3 .github/workflows/system-info.py
|
||||
|
||||
- name: Cache libavif
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
uses: actions/cache@v5
|
||||
id: cache-libavif
|
||||
with:
|
||||
path: ~/cache-libavif
|
||||
key: ${{ runner.os }}-libavif-${{ hashFiles('depends/install_libavif.sh', 'depends/libavif-svt4.patch') }}
|
||||
|
||||
- name: Cache libimagequant
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
uses: actions/cache@v5
|
||||
@ -99,13 +107,23 @@ jobs:
|
||||
path: ~/cache-libimagequant
|
||||
key: ${{ runner.os }}-libimagequant-${{ hashFiles('depends/install_imagequant.sh') }}
|
||||
|
||||
- name: Cache libwebp
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
uses: actions/cache@v5
|
||||
id: cache-libwebp
|
||||
with:
|
||||
path: ~/cache-libwebp
|
||||
key: ${{ runner.os }}-libwebp-${{ hashFiles('depends/install_webp.sh') }}
|
||||
|
||||
- name: Install Linux dependencies
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: |
|
||||
.ci/install.sh
|
||||
env:
|
||||
GHA_PYTHON_VERSION: ${{ matrix.python-version }}
|
||||
GHA_LIBAVIF_CACHE_HIT: ${{ steps.cache-libavif.outputs.cache-hit }}
|
||||
GHA_LIBIMAGEQUANT_CACHE_HIT: ${{ steps.cache-libimagequant.outputs.cache-hit }}
|
||||
GHA_LIBWEBP_CACHE_HIT: ${{ steps.cache-libwebp.outputs.cache-hit }}
|
||||
|
||||
- name: Install macOS dependencies
|
||||
if: startsWith(matrix.os, 'macOS')
|
||||
|
||||
16
.github/workflows/wheels-dependencies.sh
vendored
16
.github/workflows/wheels-dependencies.sh
vendored
@ -90,13 +90,9 @@ fi
|
||||
ARCHIVE_SDIR=pillow-depends-main
|
||||
|
||||
# Package versions for fresh source builds.
|
||||
if [[ -n "$IOS_SDK" ]]; then
|
||||
FREETYPE_VERSION=2.13.3
|
||||
else
|
||||
FREETYPE_VERSION=2.14.1
|
||||
fi
|
||||
HARFBUZZ_VERSION=12.3.2
|
||||
LIBPNG_VERSION=1.6.54
|
||||
FREETYPE_VERSION=2.14.3
|
||||
HARFBUZZ_VERSION=13.2.1
|
||||
LIBPNG_VERSION=1.6.56
|
||||
JPEGTURBO_VERSION=3.1.3
|
||||
OPENJPEG_VERSION=2.5.4
|
||||
XZ_VERSION=5.8.2
|
||||
@ -108,7 +104,7 @@ LIBWEBP_VERSION=1.6.0
|
||||
BZIP2_VERSION=1.0.8
|
||||
LIBXCB_VERSION=1.17.0
|
||||
BROTLI_VERSION=1.2.0
|
||||
LIBAVIF_VERSION=1.3.0
|
||||
LIBAVIF_VERSION=1.4.1
|
||||
|
||||
function build_pkg_config {
|
||||
if [ -e pkg-config-stamp ]; then return; fi
|
||||
@ -310,10 +306,6 @@ function build {
|
||||
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
# Custom freetype build
|
||||
if [[ -z "$IOS_SDK" ]]; then
|
||||
build_simple sed 4.9 https://mirrors.middlendian.com/gnu/sed
|
||||
fi
|
||||
|
||||
build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no
|
||||
else
|
||||
build_freetype
|
||||
|
||||
12
.github/workflows/wheels.yml
vendored
12
.github/workflows/wheels.yml
vendored
@ -53,19 +53,19 @@ jobs:
|
||||
include:
|
||||
- name: "macOS 10.10 x86_64"
|
||||
platform: macos
|
||||
os: macos-15-intel
|
||||
os: macos-26-intel
|
||||
cibw_arch: x86_64
|
||||
build: "cp3{10,11}*"
|
||||
macosx_deployment_target: "10.10"
|
||||
- name: "macOS 10.13 x86_64"
|
||||
platform: macos
|
||||
os: macos-15-intel
|
||||
os: macos-26-intel
|
||||
cibw_arch: x86_64
|
||||
build: "cp3{12,13}*"
|
||||
macosx_deployment_target: "10.13"
|
||||
- name: "macOS 10.15 x86_64"
|
||||
platform: macos
|
||||
os: macos-15-intel
|
||||
os: macos-26-intel
|
||||
cibw_arch: x86_64
|
||||
build: "{cp314,pp3}*"
|
||||
macosx_deployment_target: "10.15"
|
||||
@ -250,7 +250,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Count dists
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
@ -269,7 +269,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: Upload wheels to scientific-python-nightly-wheels
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: dist-!(sdist)*
|
||||
path: dist
|
||||
@ -291,7 +291,7 @@ jobs:
|
||||
permissions:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v7
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: dist-*
|
||||
path: dist
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.14
|
||||
rev: v0.15.4
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
args: [--exit-non-zero-on-fix]
|
||||
@ -11,7 +11,7 @@ repos:
|
||||
- id: black
|
||||
|
||||
- repo: https://github.com/PyCQA/bandit
|
||||
rev: 1.9.3
|
||||
rev: 1.9.4
|
||||
hooks:
|
||||
- id: bandit
|
||||
args: [--severity-level=high]
|
||||
@ -24,7 +24,7 @@ repos:
|
||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v21.1.8
|
||||
rev: v22.1.0
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types: [c]
|
||||
@ -38,6 +38,7 @@ repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v6.0.0
|
||||
hooks:
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-shebang-scripts-are-executable
|
||||
- id: check-merge-conflict
|
||||
@ -51,7 +52,7 @@ repos:
|
||||
exclude: ^\.github/.*TEMPLATE|^Tests/(fonts|images)/
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema
|
||||
rev: 0.36.1
|
||||
rev: 0.37.0
|
||||
hooks:
|
||||
- id: check-github-workflows
|
||||
- id: check-readthedocs
|
||||
@ -68,12 +69,12 @@ repos:
|
||||
- id: sphinx-lint
|
||||
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: v2.12.1
|
||||
rev: v2.16.2
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.24.1
|
||||
rev: v0.25
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
additional_dependencies: [trove-classifiers>=2024.10.12]
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
STARTFONT
|
||||
FONT ÿ
|
||||
SIZE 10
|
||||
FONTBOUNDINGBOX
|
||||
CHARS
|
||||
FONTBOUNDINGBOX 1 1 0 0
|
||||
CHARS 1
|
||||
STARTCHAR
|
||||
ENCODING
|
||||
ENCODING 65
|
||||
BBX 2 5
|
||||
ENDCHAR
|
||||
ENDFONT
|
||||
|
||||
BIN
Tests/images/pal8rletrns.png
Normal file
BIN
Tests/images/pal8rletrns.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@ -56,7 +56,7 @@ def test_questionable() -> None:
|
||||
im.load()
|
||||
if os.path.basename(f) not in supported:
|
||||
print(f"Please add {f} to the partially supported bmp specs.")
|
||||
except Exception: # as msg:
|
||||
except Exception: # noqa: PERF203
|
||||
if os.path.basename(f) in supported:
|
||||
raise
|
||||
|
||||
@ -106,7 +106,7 @@ def test_good() -> None:
|
||||
|
||||
assert_image_similar(im_converted, compare_converted, 5)
|
||||
|
||||
except Exception as msg:
|
||||
except Exception as msg: # noqa: PERF203
|
||||
# there are three here that are unsupported:
|
||||
unsupported = (
|
||||
os.path.join(base, "g", "rgb32bf.bmp"),
|
||||
|
||||
@ -145,14 +145,14 @@ class TestFileAvif:
|
||||
|
||||
# avifdec hopper.avif avif/hopper_avif_write.png
|
||||
assert_image_similar_tofile(
|
||||
reloaded, "Tests/images/avif/hopper_avif_write.png", 6.02
|
||||
reloaded, "Tests/images/avif/hopper_avif_write.png", 6.88
|
||||
)
|
||||
|
||||
# This test asserts that the images are similar. If the average pixel
|
||||
# difference between the two images is less than the epsilon value,
|
||||
# then we're going to accept that it's a reasonable lossy version of
|
||||
# the image.
|
||||
assert_image_similar(reloaded, im, 8.62)
|
||||
assert_image_similar(reloaded, im, 9.28)
|
||||
|
||||
def test_AvifEncoder_with_invalid_args(self) -> None:
|
||||
"""
|
||||
@ -461,12 +461,9 @@ class TestFileAvif:
|
||||
@pytest.mark.parametrize(
|
||||
"advanced",
|
||||
[
|
||||
{
|
||||
"aq-mode": "1",
|
||||
"enable-chroma-deltaq": "1",
|
||||
},
|
||||
(("aq-mode", "1"), ("enable-chroma-deltaq", "1")),
|
||||
[("aq-mode", "1"), ("enable-chroma-deltaq", "1")],
|
||||
{"tune": "psnr"},
|
||||
(("tune", "psnr"),),
|
||||
[("tune", "psnr")],
|
||||
],
|
||||
)
|
||||
def test_encoder_advanced_codec_options(
|
||||
|
||||
@ -221,6 +221,11 @@ def test_rle8_eof(file_name: str, length: int) -> None:
|
||||
im.load()
|
||||
|
||||
|
||||
def test_rle_delta() -> None:
|
||||
with Image.open("Tests/images/bmp/q/pal8rletrns.bmp") as im:
|
||||
assert_image_equal_tofile(im, "Tests/images/pal8rletrns.png")
|
||||
|
||||
|
||||
def test_unsupported_bmp_bitfields_layout() -> None:
|
||||
fp = io.BytesIO(
|
||||
o32(40) # header size
|
||||
|
||||
@ -179,9 +179,7 @@ def test_iter(bytesmode: bool) -> None:
|
||||
container = ContainerIO.ContainerIO(fh, 0, 120)
|
||||
|
||||
# Act
|
||||
data = []
|
||||
for line in container:
|
||||
data.append(line)
|
||||
data = list(container)
|
||||
|
||||
# Assert
|
||||
if bytesmode:
|
||||
|
||||
@ -310,6 +310,14 @@ def test_roundtrip_save_all_1(tmp_path: Path) -> None:
|
||||
assert reloaded.getpixel((0, 0)) == 255
|
||||
|
||||
|
||||
@pytest.mark.parametrize("size", ((0, 1), (1, 0), (0, 0)))
|
||||
def test_save_zero(size: tuple[int, int]) -> None:
|
||||
b = BytesIO()
|
||||
im = Image.new("RGB", size)
|
||||
with pytest.raises(ValueError, match="cannot write empty image"):
|
||||
im.save(b, "GIF")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path, mode",
|
||||
(
|
||||
|
||||
@ -85,7 +85,7 @@ class TestFileJpeg:
|
||||
def test_zero(self, size: tuple[int, int], tmp_path: Path) -> None:
|
||||
f = tmp_path / "temp.jpg"
|
||||
im = Image.new("RGB", size)
|
||||
with pytest.raises(ValueError):
|
||||
with pytest.raises(ValueError, match="cannot write empty image"):
|
||||
im.save(f)
|
||||
|
||||
def test_app(self) -> None:
|
||||
|
||||
@ -148,6 +148,22 @@ def test_prog_res_rt(card: ImageFile.ImageFile) -> None:
|
||||
assert_image_equal(im, card)
|
||||
|
||||
|
||||
def test_unknown_progression(tmp_path: Path) -> None:
|
||||
outfile = tmp_path / "temp.jp2"
|
||||
|
||||
im = Image.new("1", (1, 1))
|
||||
with pytest.raises(ValueError, match="unknown progression"):
|
||||
im.save(outfile, progression="invalid")
|
||||
|
||||
|
||||
def test_unknown_cinema_mode(tmp_path: Path) -> None:
|
||||
outfile = tmp_path / "temp.jp2"
|
||||
|
||||
im = Image.new("1", (1, 1))
|
||||
with pytest.raises(ValueError, match="unknown cinema mode"):
|
||||
im.save(outfile, cinema_mode="invalid")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("num_resolutions", range(2, 6))
|
||||
def test_default_num_resolutions(
|
||||
card: ImageFile.ImageFile, num_resolutions: int
|
||||
@ -440,11 +456,19 @@ def test_pclr() -> None:
|
||||
assert len(im.palette.colors) == 256
|
||||
assert im.palette.colors[(255, 255, 255)] == 0
|
||||
|
||||
for enumcs in (0, 15, 17):
|
||||
with open(f"{EXTRA_DIR}/issue104_jpxstream.jp2", "rb") as fp:
|
||||
data = bytearray(fp.read())
|
||||
data[114:115] = bytes([enumcs])
|
||||
with Image.open(BytesIO(data)) as im:
|
||||
assert im.mode == "L"
|
||||
|
||||
with Image.open(
|
||||
f"{EXTRA_DIR}/147af3f1083de4393666b7d99b01b58b_signal_sigsegv_130c531_6155_5136.jp2"
|
||||
) as im:
|
||||
assert im.mode == "P"
|
||||
assert im.palette is not None
|
||||
assert im.palette.mode == "CMYK"
|
||||
assert len(im.palette.colors) == 139
|
||||
assert im.palette.colors[(0, 0, 0, 0)] == 0
|
||||
|
||||
|
||||
@ -224,10 +224,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||
with Image.open("Tests/images/hopper_g4.tif") as im:
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
for tag in im.tag_v2:
|
||||
try:
|
||||
del core_items[tag]
|
||||
except KeyError:
|
||||
pass
|
||||
core_items.pop(tag, None)
|
||||
del core_items[320] # colormap is special, tested below
|
||||
|
||||
# Type codes:
|
||||
@ -1244,7 +1241,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||
def test_save_zero(self, compression: str | None, tmp_path: Path) -> None:
|
||||
im = Image.new("RGB", (0, 0))
|
||||
out = tmp_path / "temp.tif"
|
||||
with pytest.raises(SystemError):
|
||||
with pytest.raises(ValueError, match="cannot write empty image"):
|
||||
im.save(out, compression=compression)
|
||||
|
||||
def test_save_many_compressed(self, tmp_path: Path) -> None:
|
||||
|
||||
@ -6,7 +6,14 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageFile, JpegImagePlugin, MpoImagePlugin
|
||||
from PIL import (
|
||||
Image,
|
||||
ImageFile,
|
||||
JpegImagePlugin,
|
||||
MpoImagePlugin,
|
||||
TiffImagePlugin,
|
||||
_binary,
|
||||
)
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
@ -145,6 +152,32 @@ def test_parallax() -> None:
|
||||
assert exif.get_ifd(0x927C)[0xB211] == -3.125
|
||||
|
||||
|
||||
def test_truncated_makernote() -> None:
|
||||
def check(ifd: TiffImagePlugin.ImageFileDirectory_v2) -> None:
|
||||
fp = BytesIO()
|
||||
ifd.save(fp)
|
||||
|
||||
e = Image.Exif()
|
||||
e.load(fp.getvalue())
|
||||
assert e.get_ifd(37500) == {}
|
||||
|
||||
# Nintendo
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
ifd[271] = "Nintendo"
|
||||
ifd[34665] = {37500: b" "}
|
||||
check(ifd)
|
||||
|
||||
# Fujifilm
|
||||
for data in (
|
||||
b"FUJIFILM",
|
||||
b"FUJIFILM" + _binary.o32le(50),
|
||||
b"FUJIFILM" + _binary.o32le(0),
|
||||
):
|
||||
ifd = TiffImagePlugin.ImageFileDirectory_v2()
|
||||
ifd[34665] = {37500: data}
|
||||
check(ifd)
|
||||
|
||||
|
||||
def test_reload_exif_after_seek() -> None:
|
||||
with Image.open("Tests/images/sugarshack.mpo") as im:
|
||||
exif = im.getexif()
|
||||
|
||||
@ -37,6 +37,14 @@ def test_sanity(tmp_path: Path) -> None:
|
||||
im.save(f)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("size", ((0, 1), (1, 0), (0, 0)))
|
||||
def test_save_zero(size: tuple[int, int]) -> None:
|
||||
b = io.BytesIO()
|
||||
im = Image.new("1", size)
|
||||
with pytest.raises(ValueError):
|
||||
im.save(b, "PCX")
|
||||
|
||||
|
||||
def test_p_4_planes() -> None:
|
||||
with Image.open("Tests/images/p_4_planes.pcx") as im:
|
||||
assert im.getpixel((0, 0)) == 3
|
||||
|
||||
@ -707,6 +707,16 @@ class TestFilePng:
|
||||
assert reloaded.png.im_palette is not None
|
||||
assert len(reloaded.png.im_palette[1]) == 3
|
||||
|
||||
def test_plte_cmyk(self, tmp_path: Path) -> None:
|
||||
im = Image.new("P", (1, 1))
|
||||
im.putpalette((0, 100, 150, 200), "CMYK")
|
||||
|
||||
out = tmp_path / "temp.png"
|
||||
im.save(out)
|
||||
|
||||
with Image.open(out) as reloaded:
|
||||
assert reloaded.convert("CMYK").getpixel((0, 0)) == (200, 222, 232, 0)
|
||||
|
||||
def test_getxmp(self) -> None:
|
||||
with Image.open("Tests/images/color_snakes.png") as im:
|
||||
if ElementTree is None:
|
||||
|
||||
@ -68,6 +68,14 @@ def test_save(tmp_path: Path) -> None:
|
||||
assert im2.format == "SPIDER"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("size", ((0, 1), (1, 0), (0, 0)))
|
||||
def test_save_zero(size: tuple[int, int]) -> None:
|
||||
b = BytesIO()
|
||||
im = Image.new("1", size)
|
||||
with pytest.raises(ValueError, match="cannot write empty image"):
|
||||
im.save(b, "SPIDER")
|
||||
|
||||
|
||||
def test_tempfile() -> None:
|
||||
# Arrange
|
||||
im = hopper()
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
from PIL import Image, UnidentifiedImageError, _binary
|
||||
|
||||
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
|
||||
|
||||
@ -13,8 +14,6 @@ _TGA_DIR = os.path.join("Tests", "images", "tga")
|
||||
_TGA_DIR_COMMON = os.path.join(_TGA_DIR, "common")
|
||||
|
||||
|
||||
_ORIGINS = ("tl", "bl")
|
||||
|
||||
_ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
|
||||
|
||||
|
||||
@ -29,7 +28,7 @@ _ORIGIN_TO_ORIENTATION = {"tl": 1, "bl": -1}
|
||||
("200x32", "RGBA"),
|
||||
),
|
||||
)
|
||||
@pytest.mark.parametrize("origin", _ORIGINS)
|
||||
@pytest.mark.parametrize("origin", _ORIGIN_TO_ORIENTATION)
|
||||
@pytest.mark.parametrize("rle", (True, False))
|
||||
def test_sanity(
|
||||
size_mode: tuple[str, str], origin: str, rle: str, tmp_path: Path
|
||||
@ -94,6 +93,25 @@ def test_rgba_16() -> None:
|
||||
assert im.getpixel((1, 0)) == (0, 255, 82, 0)
|
||||
|
||||
|
||||
def test_v2_no_alpha() -> None:
|
||||
test_file = "Tests/images/tga/common/200x32_rgba_tl_rle.tga"
|
||||
with open(test_file, "rb") as fp:
|
||||
data = fp.read()
|
||||
data += (
|
||||
b"\x00" * 495
|
||||
+ _binary.o32le(len(data))
|
||||
+ _binary.o32le(0)
|
||||
+ b"TRUEVISION-XFILE.\x00"
|
||||
)
|
||||
with Image.open(BytesIO(data)) as im:
|
||||
with Image.open(test_file) as im2:
|
||||
r, g, b = im2.split()[:3]
|
||||
a = Image.new("L", im2.size, 255)
|
||||
expected = Image.merge("RGBA", (r, g, b, a))
|
||||
|
||||
assert_image_equal(im, expected)
|
||||
|
||||
|
||||
def test_id_field() -> None:
|
||||
# tga file with id field
|
||||
test_file = "Tests/images/tga_id_field.tga"
|
||||
|
||||
@ -16,6 +16,7 @@ from PIL import (
|
||||
TiffImagePlugin,
|
||||
TiffTags,
|
||||
UnidentifiedImageError,
|
||||
_binary,
|
||||
)
|
||||
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION
|
||||
|
||||
@ -941,6 +942,15 @@ class TestFileTiff:
|
||||
4001,
|
||||
]
|
||||
|
||||
def test_truncated_photoshop_blocks(self) -> None:
|
||||
with Image.open("Tests/images/hopper.tif") as im:
|
||||
assert isinstance(im, TiffImagePlugin.TiffImageFile)
|
||||
im.tag_v2[34377] = b"8BIM"
|
||||
assert im.get_photoshop_blocks() == {}
|
||||
|
||||
im.tag_v2[34377] = b"8BIM" + _binary.o16be(0) + _binary.o8(2) + b" " * 5
|
||||
assert im.get_photoshop_blocks() == {}
|
||||
|
||||
def test_tiff_chunks(self, tmp_path: Path) -> None:
|
||||
tmpfile = tmp_path / "temp.tif"
|
||||
|
||||
|
||||
@ -1,11 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
from .helper import skip_unless_feature
|
||||
|
||||
|
||||
class TestFontCrash:
|
||||
def _fuzz_font(self, font: ImageFont.FreeTypeFont) -> None:
|
||||
@ -18,8 +14,6 @@ class TestFontCrash:
|
||||
draw.multiline_textbbox((10, 10), "ABC\nAaaa", font, stroke_width=2)
|
||||
draw.text((10, 10), "Test Text", font=font, fill="#000")
|
||||
|
||||
@skip_unless_feature("freetype2")
|
||||
def test_segfault(self) -> None:
|
||||
with pytest.raises(OSError):
|
||||
font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784")
|
||||
self._fuzz_font(font)
|
||||
font = ImageFont.truetype("Tests/fonts/fuzz_font-5203009437302784")
|
||||
self._fuzz_font(font)
|
||||
|
||||
@ -75,6 +75,16 @@ def test_draw(request: pytest.FixtureRequest, tmp_path: Path) -> None:
|
||||
assert_image_equal_tofile(im, "Tests/images/test_draw_pbm_target.png")
|
||||
|
||||
|
||||
def test_to_imagefont() -> None:
|
||||
with open(fontname, "rb") as test_file:
|
||||
pcffont = PcfFontFile.PcfFontFile(test_file)
|
||||
imagefont = pcffont.to_imagefont()
|
||||
im = Image.new("L", (130, 30), "white")
|
||||
draw = ImageDraw.Draw(im)
|
||||
draw.text((0, 0), message, "black", font=imagefont)
|
||||
assert_image_equal_tofile(im, "Tests/images/test_draw_pbm_target.png")
|
||||
|
||||
|
||||
def test_textsize(request: pytest.FixtureRequest, tmp_path: Path) -> None:
|
||||
tempname = save_font(request, tmp_path)
|
||||
font = ImageFont.load(tempname)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@ -7,6 +8,15 @@ import pytest
|
||||
from PIL import FontFile, Image
|
||||
|
||||
|
||||
def test_puti16() -> None:
|
||||
fp = BytesIO()
|
||||
FontFile.puti16(fp, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
|
||||
assert fp.getvalue() == (
|
||||
b"\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04"
|
||||
b"\x00\x05\x00\x06\x00\x07\x00\x08\x00\t"
|
||||
)
|
||||
|
||||
|
||||
def test_compile() -> None:
|
||||
font = FontFile.FontFile()
|
||||
font.glyph[0] = ((0, 0), (0, 0, 0, 0), (0, 0, 0, 1), Image.new("L", (0, 0)))
|
||||
@ -24,5 +34,11 @@ def test_save(tmp_path: Path) -> None:
|
||||
tempname = str(tmp_path / "temp.pil")
|
||||
|
||||
font = FontFile.FontFile()
|
||||
with pytest.raises(ValueError):
|
||||
with pytest.raises(ValueError, match="No bitmap created"):
|
||||
font.save(tempname)
|
||||
|
||||
|
||||
def test_to_imagefont() -> None:
|
||||
font = FontFile.FontFile()
|
||||
with pytest.raises(ValueError, match="No bitmap created"):
|
||||
font.to_imagefont()
|
||||
|
||||
@ -91,6 +91,21 @@ def test_rgba_palette(mode: str, palette: tuple[int, ...]) -> None:
|
||||
assert im.palette.colors == {(1, 2, 3, 4): 0}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"mode, palette",
|
||||
(
|
||||
("CMYK", (1, 2, 3, 4)),
|
||||
("CMYKX", (1, 2, 3, 4, 0)),
|
||||
),
|
||||
)
|
||||
def test_cmyk_palette(mode: str, palette: tuple[int, ...]) -> None:
|
||||
im = Image.new("P", (1, 1))
|
||||
im.putpalette(palette, mode)
|
||||
assert im.getpalette() == [250, 249, 248]
|
||||
assert im.palette is not None
|
||||
assert im.palette.colors == {(1, 2, 3, 4): 0}
|
||||
|
||||
|
||||
def test_empty_palette() -> None:
|
||||
im = Image.new("P", (1, 1))
|
||||
assert im.getpalette() == []
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
import io
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@ -23,6 +23,13 @@ def test_reload() -> None:
|
||||
assert_image_equal(im.convert("RGB"), original.convert("RGB"))
|
||||
|
||||
|
||||
def test_save_fp() -> None:
|
||||
palette = ImagePalette.ImagePalette()
|
||||
with io.StringIO() as fp:
|
||||
palette.save(fp)
|
||||
assert not fp.closed
|
||||
|
||||
|
||||
def test_getcolor() -> None:
|
||||
palette = ImagePalette.ImagePalette()
|
||||
assert len(palette.palette) == 0
|
||||
@ -204,7 +211,7 @@ def test_2bit_palette(tmp_path: Path) -> None:
|
||||
|
||||
|
||||
def test_getpalette() -> None:
|
||||
b = BytesIO(b"0 1\n1 2 3 4")
|
||||
b = io.BytesIO(b"0 1\n1 2 3 4")
|
||||
p = PaletteFile.PaletteFile(b)
|
||||
|
||||
palette, rawmode = p.getpalette()
|
||||
@ -216,6 +223,6 @@ def test_invalid_palette() -> None:
|
||||
with pytest.raises(OSError):
|
||||
ImagePalette.load("Tests/images/hopper.jpg")
|
||||
|
||||
b = BytesIO(b"1" * 101)
|
||||
b = io.BytesIO(b"1" * 101)
|
||||
with pytest.raises(SyntaxError, match="bad palette file"):
|
||||
PaletteFile.PaletteFile(b)
|
||||
|
||||
@ -5,7 +5,10 @@ archive=$1
|
||||
url=$2
|
||||
|
||||
if [ ! -f $archive.tar.gz ]; then
|
||||
wget --no-verbose -O $archive.tar.gz $url
|
||||
wget -O $archive.tar.gz $url \
|
||||
--no-verbose \
|
||||
--retry-connrefused \
|
||||
--retry-on-http-error=429,503,504
|
||||
fi
|
||||
|
||||
rmdir $archive
|
||||
|
||||
@ -1,68 +1,86 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
version=1.3.0
|
||||
version=1.4.1
|
||||
|
||||
./download-and-extract.sh libavif-$version https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$version.tar.gz
|
||||
if [[ "$GHA_LIBAVIF_CACHE_HIT" == "true" ]]; then
|
||||
|
||||
pushd libavif-$version
|
||||
LIBDIR=/usr/lib/x86_64-linux-gnu
|
||||
|
||||
# Apply patch for SVT-AV1 4.0 compatibility
|
||||
# Pending release of https://github.com/AOMediaCodec/libavif/pull/2971
|
||||
patch -p1 < ../libavif-svt4.patch
|
||||
# Copy cached files into place
|
||||
sudo cp ~/cache-libavif/lib/* $LIBDIR/
|
||||
sudo cp -r ~/cache-libavif/include/avif /usr/include/
|
||||
|
||||
if [ $(uname) == "Darwin" ] && [ -x "$(command -v brew)" ]; then
|
||||
PREFIX=$(brew --prefix)
|
||||
else
|
||||
PREFIX=/usr
|
||||
|
||||
./download-and-extract.sh libavif-$version https://github.com/AOMediaCodec/libavif/archive/refs/tags/v$version.tar.gz
|
||||
|
||||
pushd libavif-$version
|
||||
|
||||
if [ $(uname) == "Darwin" ] && [ -x "$(command -v brew)" ]; then
|
||||
PREFIX=$(brew --prefix)
|
||||
else
|
||||
PREFIX=/usr
|
||||
fi
|
||||
|
||||
PKGCONFIG=${PKGCONFIG:-pkg-config}
|
||||
|
||||
LIBAVIF_CMAKE_FLAGS=()
|
||||
HAS_DECODER=0
|
||||
HAS_ENCODER=0
|
||||
|
||||
if $PKGCONFIG --exists aom; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_AOM=SYSTEM)
|
||||
HAS_ENCODER=1
|
||||
HAS_DECODER=1
|
||||
fi
|
||||
|
||||
if $PKGCONFIG --exists dav1d; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_DAV1D=SYSTEM)
|
||||
HAS_DECODER=1
|
||||
fi
|
||||
|
||||
if $PKGCONFIG --exists libgav1; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_LIBGAV1=SYSTEM)
|
||||
HAS_DECODER=1
|
||||
fi
|
||||
|
||||
if $PKGCONFIG --exists rav1e; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_RAV1E=SYSTEM)
|
||||
HAS_ENCODER=1
|
||||
fi
|
||||
|
||||
if $PKGCONFIG --exists SvtAv1Enc; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_SVT=SYSTEM)
|
||||
HAS_ENCODER=1
|
||||
fi
|
||||
|
||||
if [ "$HAS_ENCODER" != 1 ] || [ "$HAS_DECODER" != 1 ]; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_AOM=LOCAL)
|
||||
fi
|
||||
|
||||
cmake \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
-DCMAKE_INSTALL_NAME_DIR=$PREFIX/lib \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_MACOSX_RPATH=OFF \
|
||||
-DAVIF_LIBSHARPYUV=LOCAL \
|
||||
-DAVIF_LIBYUV=LOCAL \
|
||||
"${LIBAVIF_CMAKE_FLAGS[@]}" \
|
||||
.
|
||||
|
||||
sudo make install
|
||||
|
||||
if [ -n "$GITHUB_ACTIONS" ] && [ "$(uname)" != "Darwin" ]; then
|
||||
# Copy to cache
|
||||
LIBDIR=/usr/lib/x86_64-linux-gnu
|
||||
rm -rf ~/cache-libavif
|
||||
mkdir -p ~/cache-libavif/lib
|
||||
mkdir -p ~/cache-libavif/include
|
||||
cp $LIBDIR/libavif.so* ~/cache-libavif/lib/
|
||||
cp -r /usr/include/avif ~/cache-libavif/include/
|
||||
fi
|
||||
|
||||
popd
|
||||
|
||||
fi
|
||||
|
||||
PKGCONFIG=${PKGCONFIG:-pkg-config}
|
||||
|
||||
LIBAVIF_CMAKE_FLAGS=()
|
||||
HAS_DECODER=0
|
||||
HAS_ENCODER=0
|
||||
|
||||
if $PKGCONFIG --exists aom; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_AOM=SYSTEM)
|
||||
HAS_ENCODER=1
|
||||
HAS_DECODER=1
|
||||
fi
|
||||
|
||||
if $PKGCONFIG --exists dav1d; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_DAV1D=SYSTEM)
|
||||
HAS_DECODER=1
|
||||
fi
|
||||
|
||||
if $PKGCONFIG --exists libgav1; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_LIBGAV1=SYSTEM)
|
||||
HAS_DECODER=1
|
||||
fi
|
||||
|
||||
if $PKGCONFIG --exists rav1e; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_RAV1E=SYSTEM)
|
||||
HAS_ENCODER=1
|
||||
fi
|
||||
|
||||
if $PKGCONFIG --exists SvtAv1Enc; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_SVT=SYSTEM)
|
||||
HAS_ENCODER=1
|
||||
fi
|
||||
|
||||
if [ "$HAS_ENCODER" != 1 ] || [ "$HAS_DECODER" != 1 ]; then
|
||||
LIBAVIF_CMAKE_FLAGS+=(-DAVIF_CODEC_AOM=LOCAL)
|
||||
fi
|
||||
|
||||
cmake \
|
||||
-DCMAKE_INSTALL_PREFIX=$PREFIX \
|
||||
-DCMAKE_INSTALL_NAME_DIR=$PREFIX/lib \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_MACOSX_RPATH=OFF \
|
||||
-DAVIF_LIBSHARPYUV=LOCAL \
|
||||
-DAVIF_LIBYUV=LOCAL \
|
||||
"${LIBAVIF_CMAKE_FLAGS[@]}" \
|
||||
.
|
||||
|
||||
make install
|
||||
|
||||
popd
|
||||
|
||||
@ -3,10 +3,30 @@
|
||||
|
||||
archive=libwebp-1.6.0
|
||||
|
||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||
if [[ "$GHA_LIBWEBP_CACHE_HIT" == "true" ]]; then
|
||||
|
||||
pushd $archive
|
||||
# Copy cached files into place
|
||||
sudo cp ~/cache-libwebp/lib/* /usr/lib/
|
||||
sudo cp -r ~/cache-libwebp/include/webp /usr/include/
|
||||
|
||||
./configure --prefix=/usr --enable-libwebpmux --enable-libwebpdemux && make -j4 && sudo make -j4 install
|
||||
else
|
||||
|
||||
popd
|
||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||
|
||||
pushd $archive
|
||||
|
||||
./configure --prefix=/usr --enable-libwebpmux --enable-libwebpdemux && make -j4 && sudo make -j4 install
|
||||
|
||||
if [ -n "$GITHUB_ACTIONS" ]; then
|
||||
# Copy to cache
|
||||
rm -rf ~/cache-libwebp
|
||||
mkdir -p ~/cache-libwebp/lib
|
||||
mkdir -p ~/cache-libwebp/include
|
||||
cp /usr/lib/libwebp*.so* /usr/lib/libwebp*.a ~/cache-libwebp/lib/
|
||||
cp /usr/lib/libsharpyuv.so* /usr/lib/libsharpyuv.a ~/cache-libwebp/lib/
|
||||
cp -r /usr/include/webp ~/cache-libwebp/include/
|
||||
fi
|
||||
|
||||
popd
|
||||
|
||||
fi
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
--- a/src/codec_svt.c
|
||||
+++ b/src/codec_svt.c
|
||||
@@ -162,7 +162,11 @@ static avifResult svtCodecEncodeImage(avifEncoder * encoder,
|
||||
#else
|
||||
svt_config->logical_processors = encoder->maxThreads;
|
||||
#endif
|
||||
+#if SVT_AV1_CHECK_VERSION(4, 0, 0)
|
||||
+ svt_config->aq_mode = 2;
|
||||
+#else
|
||||
svt_config->enable_adaptive_quantization = 2;
|
||||
+#endif
|
||||
// disable 2-pass
|
||||
#if SVT_AV1_CHECK_VERSION(0, 9, 0)
|
||||
svt_config->rc_stats_buffer = (SvtAv1FixedBuf) { NULL, 0 };
|
||||
@ -7,6 +7,7 @@ itself.
|
||||
|
||||
Here is a list of PyPI projects that offer additional plugins:
|
||||
|
||||
* :pypi:`amigainfo`: Adds support for Amiga Workbench .info icon files.
|
||||
* :pypi:`DjvuRleImagePlugin`: Plugin for the DjVu RLE image format as defined in the DjVuLibre docs.
|
||||
* :pypi:`heif-image-plugin`: Simple HEIF/HEIC images plugin, based on the pyheif library.
|
||||
* :pypi:`jxlpy`: Introduces reading and writing support for JPEG XL.
|
||||
|
||||
@ -39,15 +39,15 @@ These platforms are built and tested for every change.
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Gentoo | 3.12 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 15 Sequoia | 3.10 | x86-64 |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.11, 3.12, 3.13, 3.14, | arm64 |
|
||||
| | PyPy3 | |
|
||||
| macOS 15 Sequoia | 3.11, 3.12, 3.13, 3.14, | arm64 |
|
||||
| | 3.15, PyPy3 | |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| macOS 26 Tahoe | 3.10 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Ubuntu Linux 24.04 LTS (Noble) | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
||||
| | 3.14, PyPy3 | |
|
||||
| | 3.14, 3.15, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.12 | arm64v8, ppc64le, |
|
||||
| | | s390x |
|
||||
@ -55,7 +55,7 @@ These platforms are built and tested for every change.
|
||||
| Windows Server 2022 | 3.10 | x86 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Windows Server 2025 | 3.11, 3.12, 3.13, 3.14, | x86-64 |
|
||||
| | PyPy3 | |
|
||||
| | 3.15, PyPy3 | |
|
||||
| +----------------------------+---------------------+
|
||||
| | 3.13 (MinGW) | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
|
||||
@ -8,10 +8,14 @@ The :py:mod:`~PIL.ImageFont` module defines a class with the same name. Instance
|
||||
this class store bitmap fonts, and are used with the
|
||||
:py:meth:`PIL.ImageDraw.ImageDraw.text` method.
|
||||
|
||||
PIL uses its own font file format to store bitmap fonts, limited to 256 characters. You can use
|
||||
`pilfont.py <https://github.com/python-pillow/pillow-scripts/blob/main/Scripts/pilfont.py>`_
|
||||
from :pypi:`pillow-scripts` to convert BDF and
|
||||
PCF font descriptors (X window font formats) to this format.
|
||||
Pillow uses its own font file format to store bitmap fonts, limited to 256 characters. You
|
||||
can use :py:meth:`~PIL.FontFile.FontFile.to_imagefont` to convert BDF and PCF font
|
||||
descriptors (X Window font formats) to this format::
|
||||
|
||||
from PIL import PcfFontFile
|
||||
with open("Tests/fonts/10x20-ISO8859-1.pcf", "rb") as fp:
|
||||
font = PcfFontFile.PcfFontFile(fp)
|
||||
imagefont = font.to_imagefont()
|
||||
|
||||
Starting with version 1.1.4, PIL can be configured to support TrueType and
|
||||
OpenType fonts (as well as other font formats supported by the FreeType
|
||||
|
||||
@ -14,33 +14,34 @@ TODO
|
||||
|
||||
TODO
|
||||
|
||||
Backwards incompatible changes
|
||||
==============================
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Deprecations
|
||||
============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
API changes
|
||||
===========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
Error when encoding an empty image
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
Attempting to encode an image with zero width or height would previously raise
|
||||
a :py:exc:`SystemError`. That has now been changed to a :py:exc:`ValueError`.
|
||||
|
||||
This does not add any new errors. SGI, ICNS and ICO formats are still able to
|
||||
save (0, 0) images.
|
||||
|
||||
API additions
|
||||
=============
|
||||
|
||||
FontFile.to_imagefont()
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:py:class:`~PIL.FontFile.FontFile` instances can now be directly converted to
|
||||
:py:class:`~PIL.ImageFont.ImageFont` instances::
|
||||
|
||||
>>> from PIL import PcfFontFile
|
||||
>>> with open("Tests/fonts/10x20-ISO8859-1.pcf", "rb") as fp:
|
||||
... pcffont = PcfFontFile.PcfFontFile(fp)
|
||||
... pcffont.to_imagefont()
|
||||
...
|
||||
<PIL.ImageFont.ImageFont object at 0x10457bb80>
|
||||
|
||||
ImageText.Text.wrap
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -69,7 +70,8 @@ or scaling, optionally with a font size limit::
|
||||
Other changes
|
||||
=============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
Support reading JPEG2000 images with CMYK palettes
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
JPEG2000 images with CMYK palettes can now be read. This is the first integration of
|
||||
CMYK palettes into Pillow.
|
||||
|
||||
@ -62,7 +62,6 @@ optional-dependencies.test-arrow = [
|
||||
"nanoarrow",
|
||||
"pyarrow",
|
||||
]
|
||||
|
||||
optional-dependencies.tests = [
|
||||
"check-manifest",
|
||||
"coverage>=7.4.2",
|
||||
@ -77,16 +76,15 @@ optional-dependencies.tests = [
|
||||
"pytest-xdist",
|
||||
"trove-classifiers>=2024.10.12",
|
||||
]
|
||||
|
||||
optional-dependencies.xmp = [
|
||||
"defusedxml",
|
||||
]
|
||||
urls."Release notes" = "https://pillow.readthedocs.io/en/stable/releasenotes/index.html"
|
||||
urls.Changelog = "https://github.com/python-pillow/Pillow/releases"
|
||||
urls.Documentation = "https://pillow.readthedocs.io"
|
||||
urls.Funding = "https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi"
|
||||
urls.Homepage = "https://python-pillow.github.io"
|
||||
urls.Mastodon = "https://fosstodon.org/@pillow"
|
||||
urls."Release notes" = "https://pillow.readthedocs.io/en/stable/releasenotes/index.html"
|
||||
urls.Source = "https://github.com/python-pillow/Pillow"
|
||||
|
||||
[tool.setuptools]
|
||||
@ -95,70 +93,50 @@ packages = [
|
||||
]
|
||||
include-package-data = true
|
||||
package-dir = { "" = "src" }
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = { attr = "PIL.__version__" }
|
||||
dynamic.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"
|
||||
test-requires = [
|
||||
"numpy",
|
||||
]
|
||||
xbuild-tools = [ ]
|
||||
|
||||
[tool.cibuildwheel.ios]
|
||||
xbuild-tools = []
|
||||
# Disable platform guessing on iOS, and disable raqm (since there won't be a
|
||||
# vendor version, and we can't distribute it due to licensing)
|
||||
config-settings = "raqm=disable imagequant=disable platform-guessing=disable"
|
||||
|
||||
ios.config-settings = "raqm=disable imagequant=disable platform-guessing=disable"
|
||||
# iOS needs to be given a specific pytest invocation and list of test sources.
|
||||
test-sources = [
|
||||
ios.test-sources = [
|
||||
"checks",
|
||||
"Tests",
|
||||
"selftest.py",
|
||||
]
|
||||
test-command = [
|
||||
ios.test-command = [
|
||||
"python -m selftest",
|
||||
"python -m pytest -vv -x -W always checks/check_wheel.py Tests",
|
||||
]
|
||||
|
||||
# There's no numpy wheel for iOS (yet...)
|
||||
test-requires = [ ]
|
||||
|
||||
[tool.cibuildwheel.macos]
|
||||
ios.test-requires = []
|
||||
# Disable platform guessing on macOS to avoid picking up Homebrew etc.
|
||||
config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable platform-guessing=disable"
|
||||
|
||||
[tool.cibuildwheel.macos.environment]
|
||||
macos.config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable platform-guessing=disable"
|
||||
# Isolate macOS build environment from Homebrew etc.
|
||||
PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
|
||||
|
||||
[[tool.cibuildwheel.overrides]]
|
||||
# iOS environment is isolated by cibuildwheel, but needs the dependencies
|
||||
select = "*_iphoneos"
|
||||
environment.PATH = "$(pwd)/build/deps/iphoneos/bin:$PATH"
|
||||
|
||||
[[tool.cibuildwheel.overrides]]
|
||||
# iOS simulator environment is isolated by cibuildwheel, but needs the dependencies
|
||||
select = "*_iphonesimulator"
|
||||
environment.PATH = "$(pwd)/build/deps/iphonesimulator/bin:$PATH"
|
||||
|
||||
[[tool.cibuildwheel.overrides]]
|
||||
select = "*-win32"
|
||||
test-requires = [ ]
|
||||
macos.environment.PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
|
||||
overrides = [
|
||||
# iOS environment is isolated by cibuildwheel, but needs the dependencies
|
||||
{ select = "*_iphoneos", environment.PATH = "$(pwd)/build/deps/iphoneos/bin:$PATH" },
|
||||
# iOS simulator environment is isolated by cibuildwheel, but needs the dependencies
|
||||
{ select = "*_iphonesimulator", environment.PATH = "$(pwd)/build/deps/iphonesimulator/bin:$PATH" },
|
||||
{ select = "*-win32", test-requires = [] },
|
||||
]
|
||||
|
||||
[tool.black]
|
||||
exclude = "wheels/multibuild"
|
||||
|
||||
[tool.ruff]
|
||||
exclude = [ "wheels/multibuild" ]
|
||||
|
||||
fix = true
|
||||
lint.select = [
|
||||
"C4", # flake8-comprehensions
|
||||
@ -168,6 +146,7 @@ lint.select = [
|
||||
"I", # isort
|
||||
"ISC", # flake8-implicit-str-concat
|
||||
"LOG", # flake8-logging
|
||||
"PERF", # perflint
|
||||
"PGH", # pygrep-hooks
|
||||
"PIE", # flake8-pie
|
||||
"PT", # flake8-pytest-style
|
||||
@ -207,8 +186,8 @@ lint.isort.required-imports = [
|
||||
[tool.pyproject-fmt]
|
||||
max_supported_python = "3.14"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "-ra --color=auto"
|
||||
[tool.pytest]
|
||||
addopts = [ "-ra", "--color=auto" ]
|
||||
testpaths = [
|
||||
"Tests",
|
||||
]
|
||||
|
||||
10
setup.py
10
setup.py
@ -302,7 +302,7 @@ def _pkg_config(name: str) -> tuple[list[str], list[str]] | None:
|
||||
subprocess.check_output(command_cflags).decode("utf8").strip(),
|
||||
)[::2][1:]
|
||||
return libs, cflags
|
||||
except Exception:
|
||||
except Exception: # noqa: PERF203
|
||||
pass
|
||||
return None
|
||||
|
||||
@ -1078,10 +1078,10 @@ libraries: list[tuple[str, _BuildInfo]] = [
|
||||
]
|
||||
|
||||
files: list[str | os.PathLike[str]] = ["src/_imaging.c"]
|
||||
for src_file in _IMAGING:
|
||||
files.append("src/" + src_file + ".c")
|
||||
for src_file in _LIB_IMAGING:
|
||||
files.append(os.path.join("src/libImaging", src_file + ".c"))
|
||||
files.extend("src/" + src_file + ".c" for src_file in _IMAGING)
|
||||
files.extend(
|
||||
os.path.join("src/libImaging", src_file + ".c") for src_file in _LIB_IMAGING
|
||||
)
|
||||
ext_modules = [
|
||||
Extension("PIL._imaging", files),
|
||||
Extension("PIL._imagingft", ["src/_imagingft.c"]),
|
||||
|
||||
@ -369,7 +369,7 @@ class BmpRleDecoder(ImageFile.PyDecoder):
|
||||
bytes_read = self.fd.read(2)
|
||||
if len(bytes_read) < 2:
|
||||
break
|
||||
right, up = self.fd.read(2)
|
||||
right, up = bytes_read
|
||||
data += b"\x00" * (right + up * self.state.xsize)
|
||||
x = len(data) % self.state.xsize
|
||||
else:
|
||||
|
||||
@ -289,6 +289,7 @@ class Base(IntEnum):
|
||||
OpcodeList2 = 0xC741
|
||||
OpcodeList3 = 0xC74E
|
||||
NoiseProfile = 0xC761
|
||||
FrameRate = 0xC764
|
||||
|
||||
|
||||
"""Maps EXIF tags to tag names."""
|
||||
|
||||
@ -18,7 +18,7 @@ from __future__ import annotations
|
||||
import os
|
||||
from typing import BinaryIO
|
||||
|
||||
from . import Image, _binary
|
||||
from . import Image, ImageFont, _binary
|
||||
|
||||
WIDTH = 800
|
||||
|
||||
@ -110,6 +110,22 @@ class FontFile:
|
||||
self.bitmap.paste(im.crop(src), s)
|
||||
self.metrics[i] = d, dst, s
|
||||
|
||||
def _encode_metrics(self) -> bytes:
|
||||
values: list[int] = []
|
||||
for i in range(256):
|
||||
m = self.metrics[i]
|
||||
if m:
|
||||
values.extend(m[0] + m[1] + m[2])
|
||||
else:
|
||||
values.extend((0,) * 10)
|
||||
|
||||
data = bytearray()
|
||||
for v in values:
|
||||
if v < 0:
|
||||
v += 65536
|
||||
data += _binary.o16be(v)
|
||||
return bytes(data)
|
||||
|
||||
def save(self, filename: str) -> None:
|
||||
"""Save font"""
|
||||
|
||||
@ -126,9 +142,18 @@ class FontFile:
|
||||
fp.write(b"PILfont\n")
|
||||
fp.write(f";;;;;;{self.ysize};\n".encode("ascii")) # HACK!!!
|
||||
fp.write(b"DATA\n")
|
||||
for id in range(256):
|
||||
m = self.metrics[id]
|
||||
if not m:
|
||||
puti16(fp, (0,) * 10)
|
||||
else:
|
||||
puti16(fp, m[0] + m[1] + m[2])
|
||||
fp.write(self._encode_metrics())
|
||||
|
||||
def to_imagefont(self) -> ImageFont.ImageFont:
|
||||
"""Convert to ImageFont"""
|
||||
|
||||
self.compile()
|
||||
|
||||
# font data
|
||||
if not self.bitmap:
|
||||
msg = "No bitmap created"
|
||||
raise ValueError(msg)
|
||||
|
||||
imagefont = ImageFont.ImageFont()
|
||||
imagefont._load(self.bitmap, self._encode_metrics())
|
||||
return imagefont
|
||||
|
||||
@ -164,13 +164,13 @@ class GifImageFile(ImageFile.ImageFile):
|
||||
self._seek(0)
|
||||
|
||||
last_frame = self.__frame
|
||||
for f in range(self.__frame + 1, frame + 1):
|
||||
try:
|
||||
try:
|
||||
for f in range(self.__frame + 1, frame + 1):
|
||||
self._seek(f)
|
||||
except EOFError as e:
|
||||
self.seek(last_frame)
|
||||
msg = "no more images in GIF file"
|
||||
raise EOFError(msg) from e
|
||||
except EOFError as e:
|
||||
self.seek(last_frame)
|
||||
msg = "no more images in GIF file"
|
||||
raise EOFError(msg) from e
|
||||
|
||||
def _seek(self, frame: int, update_image: bool = True) -> None:
|
||||
if isinstance(self._fp, DeferredError):
|
||||
@ -937,7 +937,13 @@ def _get_optimize(im: Image.Image, info: dict[str, Any]) -> list[int] | None:
|
||||
:param info: encoderinfo
|
||||
:returns: list of indexes of palette entries in use, or None
|
||||
"""
|
||||
if im.mode in ("P", "L") and info and info.get("optimize"):
|
||||
if (
|
||||
im.mode in ("P", "L")
|
||||
and info
|
||||
and info.get("optimize")
|
||||
and im.width != 0
|
||||
and im.height != 0
|
||||
):
|
||||
# Potentially expensive operation.
|
||||
|
||||
# The palette saves 3 bytes per color not used, but palette
|
||||
|
||||
@ -80,8 +80,7 @@ def read_32(
|
||||
if byte_int & 0x80:
|
||||
blocksize = byte_int - 125
|
||||
byte = fobj.read(1)
|
||||
for i in range(blocksize):
|
||||
data.append(byte)
|
||||
data.extend([byte] * blocksize)
|
||||
else:
|
||||
blocksize = byte_int + 1
|
||||
data.append(fobj.read(blocksize))
|
||||
|
||||
148
src/PIL/Image.py
148
src/PIL/Image.py
@ -488,7 +488,7 @@ def init() -> bool:
|
||||
try:
|
||||
logger.debug("Importing %s", plugin)
|
||||
__import__(f"{__spec__.parent}.{plugin}", globals(), locals(), [])
|
||||
except ImportError as e:
|
||||
except ImportError as e: # noqa: PERF203
|
||||
logger.debug("Image: failed to import %s: %s", plugin, e)
|
||||
|
||||
if OPEN or SAVE:
|
||||
@ -885,7 +885,7 @@ class Image:
|
||||
|
||||
# unpack data
|
||||
e = _getencoder(self.mode, encoder_name, encoder_args)
|
||||
e.setimage(self.im)
|
||||
e.setimage(self.im, (0, 0) + self.size)
|
||||
|
||||
from . import ImageFile
|
||||
|
||||
@ -956,7 +956,7 @@ class Image:
|
||||
|
||||
# unpack data
|
||||
d = _getdecoder(self.mode, decoder_name, decoder_args)
|
||||
d.setimage(self.im)
|
||||
d.setimage(self.im, (0, 0) + self.size)
|
||||
s = d.decode(data)
|
||||
|
||||
if s[0] >= 0:
|
||||
@ -2145,8 +2145,8 @@ class Image:
|
||||
Alternatively, an 8-bit string may be used instead of an integer sequence.
|
||||
|
||||
:param data: A palette sequence (either a list or a string).
|
||||
:param rawmode: The raw mode of the palette. Either "RGB", "RGBA", or a mode
|
||||
that can be transformed to "RGB" or "RGBA" (e.g. "R", "BGR;15", "RGBA;L").
|
||||
:param rawmode: The raw mode of the palette. Either "RGB", "RGBA", "CMYK", or a
|
||||
mode that can be transformed to one of those modes (e.g. "R", "RGBA;L").
|
||||
"""
|
||||
from . import ImagePalette
|
||||
|
||||
@ -2165,7 +2165,12 @@ class Image:
|
||||
palette = ImagePalette.raw(rawmode, data)
|
||||
self._mode = "PA" if "A" in self.mode else "P"
|
||||
self.palette = palette
|
||||
self.palette.mode = "RGBA" if "A" in rawmode else "RGB"
|
||||
if rawmode.startswith("CMYK"):
|
||||
self.palette.mode = "CMYK"
|
||||
elif "A" in rawmode:
|
||||
self.palette.mode = "RGBA"
|
||||
else:
|
||||
self.palette.mode = "RGB"
|
||||
self.load() # install new palette
|
||||
|
||||
def putpixel(
|
||||
@ -4229,80 +4234,83 @@ class Exif(_ExifBase):
|
||||
if tag == ExifTags.IFD.MakerNote:
|
||||
from .TiffImagePlugin import ImageFileDirectory_v2
|
||||
|
||||
if tag_data.startswith(b"FUJIFILM"):
|
||||
ifd_offset = i32le(tag_data, 8)
|
||||
ifd_data = tag_data[ifd_offset:]
|
||||
try:
|
||||
if tag_data.startswith(b"FUJIFILM"):
|
||||
ifd_offset = i32le(tag_data, 8)
|
||||
ifd_data = tag_data[ifd_offset:]
|
||||
|
||||
makernote = {}
|
||||
for i in range(struct.unpack("<H", ifd_data[:2])[0]):
|
||||
ifd_tag, typ, count, data = struct.unpack(
|
||||
"<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
||||
)
|
||||
try:
|
||||
(
|
||||
unit_size,
|
||||
handler,
|
||||
) = ImageFileDirectory_v2._load_dispatch[typ]
|
||||
except KeyError:
|
||||
continue
|
||||
size = count * unit_size
|
||||
if size > 4:
|
||||
(offset,) = struct.unpack("<L", data)
|
||||
data = ifd_data[offset - 12 : offset + size - 12]
|
||||
else:
|
||||
data = data[:size]
|
||||
|
||||
if len(data) != size:
|
||||
warnings.warn(
|
||||
"Possibly corrupt EXIF MakerNote data. "
|
||||
f"Expecting to read {size} bytes but only got "
|
||||
f"{len(data)}. Skipping tag {ifd_tag}"
|
||||
makernote = {}
|
||||
for i in range(struct.unpack("<H", ifd_data[:2])[0]):
|
||||
ifd_tag, typ, count, data = struct.unpack(
|
||||
"<HHL4s", ifd_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
||||
)
|
||||
continue
|
||||
try:
|
||||
(
|
||||
unit_size,
|
||||
handler,
|
||||
) = ImageFileDirectory_v2._load_dispatch[typ]
|
||||
except KeyError:
|
||||
continue
|
||||
size = count * unit_size
|
||||
if size > 4:
|
||||
(offset,) = struct.unpack("<L", data)
|
||||
data = ifd_data[offset - 12 : offset + size - 12]
|
||||
else:
|
||||
data = data[:size]
|
||||
|
||||
if not data:
|
||||
continue
|
||||
if len(data) != size:
|
||||
warnings.warn(
|
||||
"Possibly corrupt EXIF MakerNote data. "
|
||||
f"Expecting to read {size} bytes but only got "
|
||||
f"{len(data)}. Skipping tag {ifd_tag}"
|
||||
)
|
||||
continue
|
||||
|
||||
makernote[ifd_tag] = handler(
|
||||
ImageFileDirectory_v2(), data, False
|
||||
)
|
||||
self._ifds[tag] = dict(self._fixup_dict(makernote))
|
||||
elif self.get(0x010F) == "Nintendo":
|
||||
makernote = {}
|
||||
for i in range(struct.unpack(">H", tag_data[:2])[0]):
|
||||
ifd_tag, typ, count, data = struct.unpack(
|
||||
">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
||||
)
|
||||
if ifd_tag == 0x1101:
|
||||
# CameraInfo
|
||||
(offset,) = struct.unpack(">L", data)
|
||||
self.fp.seek(offset)
|
||||
if not data:
|
||||
continue
|
||||
|
||||
camerainfo: dict[str, int | bytes] = {
|
||||
"ModelID": self.fp.read(4)
|
||||
}
|
||||
makernote[ifd_tag] = handler(
|
||||
ImageFileDirectory_v2(), data, False
|
||||
)
|
||||
self._ifds[tag] = dict(self._fixup_dict(makernote))
|
||||
elif self.get(0x010F) == "Nintendo":
|
||||
makernote = {}
|
||||
for i in range(struct.unpack(">H", tag_data[:2])[0]):
|
||||
ifd_tag, typ, count, data = struct.unpack(
|
||||
">HHL4s", tag_data[i * 12 + 2 : (i + 1) * 12 + 2]
|
||||
)
|
||||
if ifd_tag == 0x1101:
|
||||
# CameraInfo
|
||||
(offset,) = struct.unpack(">L", data)
|
||||
self.fp.seek(offset)
|
||||
|
||||
self.fp.read(4)
|
||||
# Seconds since 2000
|
||||
camerainfo["TimeStamp"] = i32le(self.fp.read(12))
|
||||
camerainfo: dict[str, int | bytes] = {
|
||||
"ModelID": self.fp.read(4)
|
||||
}
|
||||
|
||||
self.fp.read(4)
|
||||
camerainfo["InternalSerialNumber"] = self.fp.read(4)
|
||||
self.fp.read(4)
|
||||
# Seconds since 2000
|
||||
camerainfo["TimeStamp"] = i32le(self.fp.read(12))
|
||||
|
||||
self.fp.read(12)
|
||||
parallax = self.fp.read(4)
|
||||
handler = ImageFileDirectory_v2._load_dispatch[
|
||||
TiffTags.FLOAT
|
||||
][1]
|
||||
camerainfo["Parallax"] = handler(
|
||||
ImageFileDirectory_v2(), parallax, False
|
||||
)[0]
|
||||
self.fp.read(4)
|
||||
camerainfo["InternalSerialNumber"] = self.fp.read(4)
|
||||
|
||||
self.fp.read(4)
|
||||
camerainfo["Category"] = self.fp.read(2)
|
||||
self.fp.read(12)
|
||||
parallax = self.fp.read(4)
|
||||
handler = ImageFileDirectory_v2._load_dispatch[
|
||||
TiffTags.FLOAT
|
||||
][1]
|
||||
camerainfo["Parallax"] = handler(
|
||||
ImageFileDirectory_v2(), parallax, False
|
||||
)[0]
|
||||
|
||||
makernote = {0x1101: camerainfo}
|
||||
self._ifds[tag] = makernote
|
||||
self.fp.read(4)
|
||||
camerainfo["Category"] = self.fp.read(2)
|
||||
|
||||
makernote = {0x1101: camerainfo}
|
||||
self._ifds[tag] = makernote
|
||||
except struct.error:
|
||||
pass
|
||||
else:
|
||||
# Interop
|
||||
ifd = self._get_ifd_dict(tag_data, tag)
|
||||
|
||||
@ -215,8 +215,10 @@ class ImageFile(Image.Image):
|
||||
if subifd_offsets:
|
||||
if not isinstance(subifd_offsets, tuple):
|
||||
subifd_offsets = (subifd_offsets,)
|
||||
for subifd_offset in subifd_offsets:
|
||||
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
|
||||
ifds = [
|
||||
(exif._get_ifd_dict(subifd_offset), subifd_offset)
|
||||
for subifd_offset in subifd_offsets
|
||||
]
|
||||
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
|
||||
if ifd1 and ifd1.get(ExifTags.Base.JpegIFOffset):
|
||||
assert exif._info is not None
|
||||
@ -579,10 +581,7 @@ class Parser:
|
||||
pass # not enough data
|
||||
else:
|
||||
flag = hasattr(im, "load_seek") or hasattr(im, "load_read")
|
||||
if flag or len(im.tile) != 1:
|
||||
# custom load code, or multiple tiles
|
||||
self.decode = None
|
||||
else:
|
||||
if not flag and len(im.tile) == 1:
|
||||
# initialize decoder
|
||||
im.load_prepare()
|
||||
d, e, o, a = im.tile[0]
|
||||
|
||||
@ -149,6 +149,9 @@ class ImageFont:
|
||||
# read PILfont metrics
|
||||
data = file.read(256 * 20)
|
||||
|
||||
self._load(image, data)
|
||||
|
||||
def _load(self, image: Image.Image, data: bytes) -> None:
|
||||
image.load()
|
||||
|
||||
self.font = Image.core.font(image.im, data)
|
||||
@ -927,7 +930,7 @@ def load_path(filename: str | bytes) -> ImageFont:
|
||||
for directory in sys.path:
|
||||
try:
|
||||
return load(os.path.join(directory, filename))
|
||||
except OSError:
|
||||
except OSError: # noqa: PERF203
|
||||
pass
|
||||
msg = f'cannot find font file "{filename}" in sys.path'
|
||||
if os.path.exists(filename):
|
||||
|
||||
@ -191,19 +191,22 @@ class ImagePalette:
|
||||
if self.rawmode:
|
||||
msg = "palette contains raw palette data"
|
||||
raise ValueError(msg)
|
||||
open_fp = False
|
||||
if isinstance(fp, str):
|
||||
fp = open(fp, "w")
|
||||
fp.write("# Palette\n")
|
||||
fp.write(f"# Mode: {self.mode}\n")
|
||||
for i in range(256):
|
||||
fp.write(f"{i}")
|
||||
for j in range(i * len(self.mode), (i + 1) * len(self.mode)):
|
||||
try:
|
||||
fp.write(f" {self.palette[j]}")
|
||||
except IndexError:
|
||||
fp.write(" 0")
|
||||
fp.write("\n")
|
||||
fp.close()
|
||||
open_fp = True
|
||||
try:
|
||||
fp.write("# Palette\n")
|
||||
fp.write(f"# Mode: {self.mode}\n")
|
||||
palette_len = len(self.palette)
|
||||
for i in range(256):
|
||||
fp.write(f"{i}")
|
||||
for j in range(i * len(self.mode), (i + 1) * len(self.mode)):
|
||||
fp.write(f" {self.palette[j] if j < palette_len else 0}")
|
||||
fp.write("\n")
|
||||
finally:
|
||||
if open_fp:
|
||||
fp.close()
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
@ -185,13 +185,9 @@ def getiptcinfo(
|
||||
|
||||
data = None
|
||||
|
||||
info: dict[tuple[int, int], bytes | list[bytes]] = {}
|
||||
if isinstance(im, IptcImageFile):
|
||||
# return info dictionary right away
|
||||
for k, v in im.info.items():
|
||||
if isinstance(k, tuple):
|
||||
info[k] = v
|
||||
return info
|
||||
return {k: v for k, v in im.info.items() if isinstance(k, tuple)}
|
||||
|
||||
elif isinstance(im, JpegImagePlugin.JpegImageFile):
|
||||
# extract the IPTC/NAA resource
|
||||
@ -227,7 +223,4 @@ def getiptcinfo(
|
||||
except (IndexError, KeyError):
|
||||
pass # expected failure
|
||||
|
||||
for k, v in iptc_im.info.items():
|
||||
if isinstance(k, tuple):
|
||||
info[k] = v
|
||||
return info
|
||||
return {k: v for k, v in iptc_im.info.items() if isinstance(k, tuple)}
|
||||
|
||||
@ -176,6 +176,7 @@ def _parse_jp2_header(
|
||||
nc = None
|
||||
dpi = None # 2-tuple of DPI info, or None
|
||||
palette = None
|
||||
colr = None
|
||||
|
||||
while header.has_next_box():
|
||||
tbox = header.next_box_type()
|
||||
@ -196,11 +197,18 @@ def _parse_jp2_header(
|
||||
mode = "RGB"
|
||||
elif nc == 4:
|
||||
mode = "RGBA"
|
||||
elif tbox == b"colr" and nc == 4:
|
||||
elif tbox == b"colr":
|
||||
meth, _, _, enumcs = header.read_fields(">BBBI")
|
||||
if meth == 1 and enumcs == 12:
|
||||
mode = "CMYK"
|
||||
elif tbox == b"pclr" and mode in ("L", "LA"):
|
||||
if meth == 1:
|
||||
if enumcs in (0, 15):
|
||||
colr = "1"
|
||||
elif enumcs == 12:
|
||||
colr = "CMYK"
|
||||
if nc == 4:
|
||||
mode = "CMYK"
|
||||
elif enumcs == 17:
|
||||
colr = "L"
|
||||
elif tbox == b"pclr" and mode in ("L", "LA") and colr not in ("1", "L"):
|
||||
ne, npc = header.read_fields(">HB")
|
||||
assert isinstance(ne, int)
|
||||
assert isinstance(npc, int)
|
||||
@ -210,7 +218,11 @@ def _parse_jp2_header(
|
||||
if bitdepth > max_bitdepth:
|
||||
max_bitdepth = bitdepth
|
||||
if max_bitdepth <= 8:
|
||||
palette = ImagePalette.ImagePalette("RGBA" if npc == 4 else "RGB")
|
||||
if npc == 4:
|
||||
palette_mode = "CMYK" if colr == "CMYK" else "RGBA"
|
||||
else:
|
||||
palette_mode = "RGB"
|
||||
palette = ImagePalette.ImagePalette(palette_mode)
|
||||
for i in range(ne):
|
||||
color: list[int] = []
|
||||
for value in header.read_fields(">" + ("B" * npc)):
|
||||
|
||||
@ -127,8 +127,8 @@ def APP(self: JpegImageFile, marker: int) -> None:
|
||||
# parse the image resource block
|
||||
offset = 14
|
||||
photoshop = self.info.setdefault("photoshop", {})
|
||||
while s[offset : offset + 4] == b"8BIM":
|
||||
try:
|
||||
try:
|
||||
while s[offset : offset + 4] == b"8BIM":
|
||||
offset += 4
|
||||
# resource code
|
||||
code = i16(s, offset)
|
||||
@ -153,8 +153,8 @@ def APP(self: JpegImageFile, marker: int) -> None:
|
||||
photoshop[code] = data
|
||||
offset += size
|
||||
offset += offset & 1 # align
|
||||
except struct.error:
|
||||
break # insufficient data
|
||||
except struct.error:
|
||||
pass # insufficient data
|
||||
|
||||
elif marker == 0xFFEE and s.startswith(b"Adobe"):
|
||||
self.info["adobe"] = i16(s, 5)
|
||||
@ -661,10 +661,6 @@ def get_sampling(im: Image.Image) -> int:
|
||||
|
||||
|
||||
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||
if im.width == 0 or im.height == 0:
|
||||
msg = "cannot write empty image as JPEG"
|
||||
raise ValueError(msg)
|
||||
|
||||
try:
|
||||
rawmode = RAWMODE[im.mode]
|
||||
except KeyError as e:
|
||||
@ -742,17 +738,15 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||
if not (0 < len(qtables) < 5):
|
||||
msg = "None or too many quantization tables"
|
||||
raise ValueError(msg)
|
||||
for idx, table in enumerate(qtables):
|
||||
try:
|
||||
try:
|
||||
for idx, table in enumerate(qtables):
|
||||
if len(table) != 64:
|
||||
msg = "Invalid quantization table"
|
||||
raise TypeError(msg)
|
||||
table_array = array.array("H", table)
|
||||
except TypeError as e:
|
||||
msg = "Invalid quantization table"
|
||||
raise ValueError(msg) from e
|
||||
else:
|
||||
qtables[idx] = list(table_array)
|
||||
qtables[idx] = list(array.array("H", table))
|
||||
except TypeError as e:
|
||||
msg = "Invalid quantization table"
|
||||
raise ValueError(msg) from e
|
||||
return qtables
|
||||
|
||||
if qtables == "keep":
|
||||
|
||||
@ -59,11 +59,10 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||
+ b"MPF\0"
|
||||
+ b" " * ifd_length
|
||||
)
|
||||
exif = im_frame.encoderinfo.get("exif")
|
||||
if isinstance(exif, Image.Exif):
|
||||
exif = exif.tobytes()
|
||||
im_frame.encoderinfo["exif"] = exif
|
||||
if exif:
|
||||
if exif := im_frame.encoderinfo.get("exif"):
|
||||
if isinstance(exif, Image.Exif):
|
||||
exif = exif.tobytes()
|
||||
im_frame.encoderinfo["exif"] = exif
|
||||
mpf_offset += 4 + len(exif)
|
||||
|
||||
JpegImagePlugin._save(im_frame, fp, filename)
|
||||
|
||||
@ -251,7 +251,7 @@ class PcfFontFile(FontFile.FontFile):
|
||||
]
|
||||
if encoding_offset != 0xFFFF:
|
||||
encoding[i] = encoding_offset
|
||||
except UnicodeDecodeError:
|
||||
except UnicodeDecodeError: # noqa: PERF203
|
||||
# character is not supported in selected encoding
|
||||
pass
|
||||
|
||||
|
||||
@ -146,6 +146,10 @@ SAVE = {
|
||||
|
||||
|
||||
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||
if im.width == 0 or im.height == 0:
|
||||
msg = "Cannot write empty image as PCX"
|
||||
raise ValueError(msg)
|
||||
|
||||
try:
|
||||
version, bits, planes, rawmode = SAVE[im.mode]
|
||||
except KeyError as e:
|
||||
|
||||
@ -866,13 +866,13 @@ class PngImageFile(ImageFile.ImageFile):
|
||||
self._seek(0, True)
|
||||
|
||||
last_frame = self.__frame
|
||||
for f in range(self.__frame + 1, frame + 1):
|
||||
try:
|
||||
try:
|
||||
for f in range(self.__frame + 1, frame + 1):
|
||||
self._seek(f)
|
||||
except EOFError as e:
|
||||
self.seek(last_frame)
|
||||
msg = "no more images in APNG file"
|
||||
raise EOFError(msg) from e
|
||||
except EOFError as e:
|
||||
self.seek(last_frame)
|
||||
msg = "no more images in APNG file"
|
||||
raise EOFError(msg) from e
|
||||
|
||||
def _seek(self, frame: int, rewind: bool = False) -> None:
|
||||
assert self.png is not None
|
||||
@ -1353,6 +1353,9 @@ def _save(
|
||||
mode = im.mode
|
||||
|
||||
outmode = mode
|
||||
palette = []
|
||||
if im.palette:
|
||||
palette = im.getpalette() or []
|
||||
if mode == "P":
|
||||
#
|
||||
# attempt to minimize storage requirements for palette images
|
||||
@ -1362,7 +1365,7 @@ def _save(
|
||||
else:
|
||||
# check palette contents
|
||||
if im.palette:
|
||||
colors = max(min(len(im.palette.getdata()[1]) // 3, 256), 1)
|
||||
colors = max(min(len(palette) // 3, 256), 1)
|
||||
else:
|
||||
colors = 256
|
||||
|
||||
@ -1403,8 +1406,7 @@ def _save(
|
||||
|
||||
chunks = [b"cHRM", b"cICP", b"gAMA", b"sBIT", b"sRGB", b"tIME"]
|
||||
|
||||
icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile"))
|
||||
if icc:
|
||||
if icc := im.encoderinfo.get("icc_profile", im.info.get("icc_profile")):
|
||||
# ICC profile
|
||||
# according to PNG spec, the iCCP chunk contains:
|
||||
# Profile name 1-79 bytes (character string)
|
||||
@ -1419,8 +1421,7 @@ def _save(
|
||||
# Disallow sRGB chunks when an iCCP-chunk has been emitted.
|
||||
chunks.remove(b"sRGB")
|
||||
|
||||
info = im.encoderinfo.get("pnginfo")
|
||||
if info:
|
||||
if info := im.encoderinfo.get("pnginfo"):
|
||||
chunks_multiple_allowed = [b"sPLT", b"iTXt", b"tEXt", b"zTXt"]
|
||||
for info_chunk in info.chunks:
|
||||
cid, data = info_chunk[:2]
|
||||
@ -1437,7 +1438,7 @@ def _save(
|
||||
|
||||
if im.mode == "P":
|
||||
palette_byte_number = colors * 3
|
||||
palette_bytes = im.im.getpalette("RGB")[:palette_byte_number]
|
||||
palette_bytes = bytes(palette[:palette_byte_number])
|
||||
while len(palette_bytes) < palette_byte_number:
|
||||
palette_bytes += b"\0"
|
||||
chunk(fp, b"PLTE", palette_bytes)
|
||||
@ -1472,8 +1473,7 @@ def _save(
|
||||
alpha_bytes = colors
|
||||
chunk(fp, b"tRNS", alpha[:alpha_bytes])
|
||||
|
||||
dpi = im.encoderinfo.get("dpi")
|
||||
if dpi:
|
||||
if dpi := im.encoderinfo.get("dpi"):
|
||||
chunk(
|
||||
fp,
|
||||
b"pHYs",
|
||||
@ -1490,8 +1490,7 @@ def _save(
|
||||
chunks.remove(cid)
|
||||
chunk(fp, cid, data)
|
||||
|
||||
exif = im.encoderinfo.get("exif")
|
||||
if exif:
|
||||
if exif := im.encoderinfo.get("exif"):
|
||||
if isinstance(exif, Image.Exif):
|
||||
exif = exif.tobytes(8)
|
||||
if exif.startswith(b"Exif\x00\x00"):
|
||||
|
||||
@ -244,7 +244,7 @@ def loadImageSeries(filelist: list[str] | None = None) -> list[Image.Image] | No
|
||||
|
||||
def makeSpiderHeader(im: Image.Image) -> list[bytes]:
|
||||
nsam, nrow = im.size
|
||||
lenbyt = nsam * 4 # There are labrec records in the header
|
||||
lenbyt = max(1, nsam) * 4 # There are labrec records in the header
|
||||
labrec = int(1024 / lenbyt)
|
||||
if 1024 % lenbyt != 0:
|
||||
labrec += 1
|
||||
|
||||
@ -17,11 +17,13 @@
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import warnings
|
||||
from typing import IO
|
||||
|
||||
from . import Image, ImageFile, ImagePalette
|
||||
from ._binary import i16le as i16
|
||||
from ._binary import i32le as i32
|
||||
from ._binary import o8
|
||||
from ._binary import o16le as o16
|
||||
|
||||
@ -157,6 +159,20 @@ class TgaImageFile(ImageFile.ImageFile):
|
||||
pass # cannot decode
|
||||
|
||||
def load_end(self) -> None:
|
||||
if self.mode == "RGBA":
|
||||
assert self.fp is not None
|
||||
self.fp.seek(-26, os.SEEK_END)
|
||||
footer = self.fp.read(26)
|
||||
if footer.endswith(b"TRUEVISION-XFILE.\x00"):
|
||||
# version 2
|
||||
extension_offset = i32(footer)
|
||||
if extension_offset:
|
||||
self.fp.seek(extension_offset + 494)
|
||||
attributes_type = self.fp.read(1)
|
||||
if attributes_type == b"\x00":
|
||||
# No alpha
|
||||
self.im.fillband(3, 255)
|
||||
|
||||
if self._flip_horizontally:
|
||||
self.im = self.im.transpose(Image.Transpose.FLIP_LEFT_RIGHT)
|
||||
|
||||
|
||||
@ -1287,10 +1287,13 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||
blocks = {}
|
||||
val = self.tag_v2.get(ExifTags.Base.ImageResources)
|
||||
if val:
|
||||
while val.startswith(b"8BIM"):
|
||||
while val.startswith(b"8BIM") and len(val) >= 12:
|
||||
id = i16(val[4:6])
|
||||
n = math.ceil((val[6] + 1) / 2) * 2
|
||||
size = i32(val[6 + n : 10 + n])
|
||||
try:
|
||||
size = i32(val[6 + n : 10 + n])
|
||||
except struct.error:
|
||||
break
|
||||
data = val[10 + n : 10 + n + size]
|
||||
blocks[id] = {"data": data}
|
||||
|
||||
|
||||
@ -124,8 +124,10 @@ PyImagingPhotoPut(
|
||||
if (im->mode == IMAGING_MODE_1 || im->mode == IMAGING_MODE_L) {
|
||||
block.pixelSize = 1;
|
||||
block.offset[0] = block.offset[1] = block.offset[2] = block.offset[3] = 0;
|
||||
} else if (im->mode == IMAGING_MODE_RGB || im->mode == IMAGING_MODE_RGBA ||
|
||||
im->mode == IMAGING_MODE_RGBX || im->mode == IMAGING_MODE_RGBa) {
|
||||
} else if (
|
||||
im->mode == IMAGING_MODE_RGB || im->mode == IMAGING_MODE_RGBA ||
|
||||
im->mode == IMAGING_MODE_RGBX || im->mode == IMAGING_MODE_RGBa
|
||||
) {
|
||||
block.pixelSize = 4;
|
||||
block.offset[0] = 0;
|
||||
block.offset[1] = 1;
|
||||
|
||||
26
src/_avif.c
26
src/_avif.c
@ -425,7 +425,7 @@ end:
|
||||
return (PyObject *)self;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
void
|
||||
_encoder_dealloc(AvifEncoderObject *self) {
|
||||
if (self->encoder) {
|
||||
avifEncoderDestroy(self->encoder);
|
||||
@ -433,7 +433,7 @@ _encoder_dealloc(AvifEncoderObject *self) {
|
||||
if (self->image) {
|
||||
avifImageDestroy(self->image);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
@ -485,7 +485,7 @@ _encoder_add(AvifEncoderObject *self, PyObject *args) {
|
||||
frame = image;
|
||||
} else {
|
||||
frame = avifImageCreateEmpty();
|
||||
if (image == NULL) {
|
||||
if (frame == NULL) {
|
||||
PyErr_SetString(PyExc_ValueError, "Image creation failed");
|
||||
return NULL;
|
||||
}
|
||||
@ -687,13 +687,13 @@ AvifDecoderNew(PyObject *self_, PyObject *args) {
|
||||
return (PyObject *)self;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
void
|
||||
_decoder_dealloc(AvifDecoderObject *self) {
|
||||
if (self->decoder) {
|
||||
avifDecoderDestroy(self->decoder);
|
||||
}
|
||||
PyBuffer_Release(&self->buffer);
|
||||
Py_RETURN_NONE;
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
@ -708,15 +708,27 @@ _decoder_get_info(AvifDecoderObject *self) {
|
||||
|
||||
if (image->xmp.size) {
|
||||
xmp = PyBytes_FromStringAndSize((const char *)image->xmp.data, image->xmp.size);
|
||||
if (!xmp) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (image->exif.size) {
|
||||
exif =
|
||||
PyBytes_FromStringAndSize((const char *)image->exif.data, image->exif.size);
|
||||
if (!exif) {
|
||||
Py_XDECREF(xmp);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (image->icc.size) {
|
||||
icc = PyBytes_FromStringAndSize((const char *)image->icc.data, image->icc.size);
|
||||
if (!icc) {
|
||||
Py_XDECREF(xmp);
|
||||
Py_XDECREF(exif);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
ret = Py_BuildValue(
|
||||
@ -799,6 +811,7 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) {
|
||||
|
||||
if (rgb.height > PY_SSIZE_T_MAX / rgb.rowBytes) {
|
||||
PyErr_SetString(PyExc_MemoryError, "Integer overflow in pixel size");
|
||||
avifRGBImageFreePixels(&rgb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -806,6 +819,9 @@ _decoder_get_frame(AvifDecoderObject *self, PyObject *args) {
|
||||
|
||||
bytes = PyBytes_FromStringAndSize((char *)rgb.pixels, size);
|
||||
avifRGBImageFreePixels(&rgb);
|
||||
if (!bytes) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = Py_BuildValue(
|
||||
"SKKK",
|
||||
|
||||
@ -254,6 +254,9 @@ void
|
||||
ReleaseArrowSchemaPyCapsule(PyObject *capsule) {
|
||||
struct ArrowSchema *schema =
|
||||
(struct ArrowSchema *)PyCapsule_GetPointer(capsule, "arrow_schema");
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
if (schema->release != NULL) {
|
||||
schema->release(schema);
|
||||
}
|
||||
@ -276,6 +279,9 @@ void
|
||||
ReleaseArrowArrayPyCapsule(PyObject *capsule) {
|
||||
struct ArrowArray *array =
|
||||
(struct ArrowArray *)PyCapsule_GetPointer(capsule, "arrow_array");
|
||||
if (!array) {
|
||||
return;
|
||||
}
|
||||
if (array->release != NULL) {
|
||||
array->release(array);
|
||||
}
|
||||
@ -1216,7 +1222,9 @@ _getxy(PyObject *xy, int *x, int *y) {
|
||||
PyObject *int_value = PyObject_CallMethod(value, "__int__", NULL);
|
||||
if (int_value != NULL && PyLong_Check(int_value)) {
|
||||
*x = PyLong_AS_LONG(int_value);
|
||||
Py_DECREF(int_value);
|
||||
} else {
|
||||
Py_XDECREF(int_value);
|
||||
goto badval;
|
||||
}
|
||||
}
|
||||
@ -1230,7 +1238,9 @@ _getxy(PyObject *xy, int *x, int *y) {
|
||||
PyObject *int_value = PyObject_CallMethod(value, "__int__", NULL);
|
||||
if (int_value != NULL && PyLong_Check(int_value)) {
|
||||
*y = PyLong_AS_LONG(int_value);
|
||||
Py_DECREF(int_value);
|
||||
} else {
|
||||
Py_XDECREF(int_value);
|
||||
goto badval;
|
||||
}
|
||||
}
|
||||
@ -2469,6 +2479,9 @@ _split(ImagingObject *self) {
|
||||
}
|
||||
|
||||
list = PyTuple_New(self->image->bands);
|
||||
if (!list) {
|
||||
return NULL;
|
||||
}
|
||||
for (i = 0; i < self->image->bands; i++) {
|
||||
imaging_object = PyImagingNew(bands[i]);
|
||||
if (!imaging_object) {
|
||||
@ -3765,6 +3778,9 @@ _ptr_destructor(PyObject *capsule) {
|
||||
static PyObject *
|
||||
_getattr_ptr(ImagingObject *self, void *closure) {
|
||||
PyObject *capsule = PyCapsule_New(self->image, IMAGING_MAGIC, _ptr_destructor);
|
||||
if (!capsule) {
|
||||
return NULL;
|
||||
}
|
||||
Py_INCREF(self);
|
||||
PyCapsule_SetContext(capsule, self);
|
||||
return capsule;
|
||||
@ -4320,8 +4336,9 @@ setup_module(PyObject *m) {
|
||||
#else
|
||||
have_libjpegturbo = Py_False;
|
||||
#endif
|
||||
Py_INCREF(have_libjpegturbo);
|
||||
PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", have_libjpegturbo);
|
||||
if (PyModule_AddObjectRef(m, "HAVE_LIBJPEGTURBO", have_libjpegturbo) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
PyObject *have_mozjpeg;
|
||||
#ifdef JPEG_C_PARAM_SUPPORTED
|
||||
@ -4329,8 +4346,9 @@ setup_module(PyObject *m) {
|
||||
#else
|
||||
have_mozjpeg = Py_False;
|
||||
#endif
|
||||
Py_INCREF(have_mozjpeg);
|
||||
PyModule_AddObject(m, "HAVE_MOZJPEG", have_mozjpeg);
|
||||
if (PyModule_AddObjectRef(m, "HAVE_MOZJPEG", have_mozjpeg) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
PyObject *have_libimagequant;
|
||||
#ifdef HAVE_LIBIMAGEQUANT
|
||||
@ -4344,8 +4362,9 @@ setup_module(PyObject *m) {
|
||||
#else
|
||||
have_libimagequant = Py_False;
|
||||
#endif
|
||||
Py_INCREF(have_libimagequant);
|
||||
PyModule_AddObject(m, "HAVE_LIBIMAGEQUANT", have_libimagequant);
|
||||
if (PyModule_AddObjectRef(m, "HAVE_LIBIMAGEQUANT", have_libimagequant) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBZ
|
||||
/* zip encoding strategies */
|
||||
@ -4373,8 +4392,9 @@ setup_module(PyObject *m) {
|
||||
#else
|
||||
have_zlibng = Py_False;
|
||||
#endif
|
||||
Py_INCREF(have_zlibng);
|
||||
PyModule_AddObject(m, "HAVE_ZLIBNG", have_zlibng);
|
||||
if (PyModule_AddObjectRef(m, "HAVE_ZLIBNG", have_zlibng) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LIBTIFF
|
||||
{
|
||||
@ -4391,8 +4411,9 @@ setup_module(PyObject *m) {
|
||||
#else
|
||||
have_xcb = Py_False;
|
||||
#endif
|
||||
Py_INCREF(have_xcb);
|
||||
PyModule_AddObject(m, "HAVE_XCB", have_xcb);
|
||||
if (PyModule_AddObjectRef(m, "HAVE_XCB", have_xcb) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
PyObject *pillow_version = PyUnicode_FromString(version);
|
||||
PyDict_SetItemString(
|
||||
|
||||
@ -558,7 +558,13 @@ cms_transform_apply(CmsTransformObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!im) {
|
||||
return NULL;
|
||||
}
|
||||
imOut = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
|
||||
if (!imOut) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return Py_BuildValue("i", pyCMSdoTransform(im, imOut, self->transform));
|
||||
}
|
||||
@ -1447,14 +1453,14 @@ setup_module(PyObject *m) {
|
||||
int vn;
|
||||
|
||||
/* Ready object types */
|
||||
PyType_Ready(&CmsProfile_Type);
|
||||
PyType_Ready(&CmsTransform_Type);
|
||||
if (PyType_Ready(&CmsProfile_Type) < 0 || PyType_Ready(&CmsTransform_Type) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_INCREF(&CmsProfile_Type);
|
||||
PyModule_AddObject(m, "CmsProfile", (PyObject *)&CmsProfile_Type);
|
||||
|
||||
Py_INCREF(&CmsTransform_Type);
|
||||
PyModule_AddObject(m, "CmsTransform", (PyObject *)&CmsTransform_Type);
|
||||
if (PyModule_AddObjectRef(m, "CmsProfile", (PyObject *)&CmsProfile_Type) < 0 ||
|
||||
PyModule_AddObjectRef(m, "CmsTransform", (PyObject *)&CmsTransform_Type) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
d = PyModule_GetDict(m);
|
||||
|
||||
|
||||
282
src/_imagingft.c
282
src/_imagingft.c
@ -941,8 +941,18 @@ font_render(FontObject *self, PyObject *args) {
|
||||
return NULL;
|
||||
}
|
||||
PyObject *imagePtr = PyObject_GetAttrString(image, "ptr");
|
||||
if (!imagePtr) {
|
||||
PyMem_Del(glyph_info);
|
||||
Py_DECREF(image);
|
||||
return NULL;
|
||||
}
|
||||
im = (Imaging)PyCapsule_GetPointer(imagePtr, IMAGING_MAGIC);
|
||||
Py_XDECREF(imagePtr);
|
||||
Py_DECREF(imagePtr);
|
||||
if (!im) {
|
||||
PyMem_Del(glyph_info);
|
||||
Py_DECREF(image);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
x_offset = round(x_offset - stroke_width);
|
||||
y_offset = round(y_offset - stroke_width);
|
||||
@ -1042,130 +1052,99 @@ font_render(FontObject *self, PyObject *args) {
|
||||
yy = -(py + glyph_slot->bitmap_top);
|
||||
}
|
||||
|
||||
// Null buffer, is dereferenced in FT_Bitmap_Convert
|
||||
if (!bitmap.buffer && bitmap.rows) {
|
||||
PyErr_SetString(PyExc_OSError, "Bitmap missing for glyph");
|
||||
goto glyph_error;
|
||||
}
|
||||
|
||||
/* convert non-8bpp bitmaps */
|
||||
switch (bitmap.pixel_mode) {
|
||||
case FT_PIXEL_MODE_MONO:
|
||||
convert_scale = 255;
|
||||
break;
|
||||
case FT_PIXEL_MODE_GRAY2:
|
||||
convert_scale = 255 / 3;
|
||||
break;
|
||||
case FT_PIXEL_MODE_GRAY4:
|
||||
convert_scale = 255 / 15;
|
||||
break;
|
||||
default:
|
||||
convert_scale = 1;
|
||||
}
|
||||
switch (bitmap.pixel_mode) {
|
||||
case FT_PIXEL_MODE_MONO:
|
||||
case FT_PIXEL_MODE_GRAY2:
|
||||
case FT_PIXEL_MODE_GRAY4:
|
||||
if (!bitmap_converted_ready) {
|
||||
FT_Bitmap_Init(&bitmap_converted);
|
||||
bitmap_converted_ready = 1;
|
||||
}
|
||||
error = FT_Bitmap_Convert(library, &bitmap, &bitmap_converted, 1);
|
||||
if (error) {
|
||||
geterror(error);
|
||||
goto glyph_error;
|
||||
}
|
||||
bitmap = bitmap_converted;
|
||||
/* bitmap is now FT_PIXEL_MODE_GRAY, fall through */
|
||||
case FT_PIXEL_MODE_GRAY:
|
||||
break;
|
||||
case FT_PIXEL_MODE_BGRA:
|
||||
if (color) {
|
||||
if (bitmap.buffer) {
|
||||
/* convert non-8bpp bitmaps */
|
||||
switch (bitmap.pixel_mode) {
|
||||
case FT_PIXEL_MODE_MONO:
|
||||
convert_scale = 255;
|
||||
break;
|
||||
}
|
||||
/* we didn't ask for color, fall through to default */
|
||||
default:
|
||||
PyErr_SetString(PyExc_OSError, "unsupported bitmap pixel mode");
|
||||
goto glyph_error;
|
||||
}
|
||||
|
||||
/* clip glyph bitmap width to target image bounds */
|
||||
x0 = 0;
|
||||
x1 = bitmap.width;
|
||||
if (xx < 0) {
|
||||
x0 = -xx;
|
||||
}
|
||||
if (xx + x1 > im->xsize) {
|
||||
x1 = im->xsize - xx;
|
||||
}
|
||||
|
||||
source = (unsigned char *)bitmap.buffer;
|
||||
for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++, yy++) {
|
||||
/* clip glyph bitmap height to target image bounds */
|
||||
if (yy >= 0 && yy < im->ysize) {
|
||||
/* blend this glyph into the buffer */
|
||||
int k;
|
||||
unsigned char *target;
|
||||
unsigned int tmp;
|
||||
if (color) {
|
||||
/* target[RGB] returns the color, target[A] returns the mask */
|
||||
/* target bands get split again in ImageDraw.text */
|
||||
target = (unsigned char *)im->image[yy] + xx * 4;
|
||||
} else {
|
||||
target = im->image8[yy] + xx;
|
||||
}
|
||||
if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
|
||||
/* paste color glyph */
|
||||
for (k = x0; k < x1; k++) {
|
||||
unsigned int src_alpha = source[k * 4 + 3];
|
||||
|
||||
/* paste only if source has data */
|
||||
if (src_alpha > 0) {
|
||||
/* unpremultiply BGRa */
|
||||
int src_red =
|
||||
CLIP8((255 * (int)source[k * 4 + 2]) / src_alpha);
|
||||
int src_green =
|
||||
CLIP8((255 * (int)source[k * 4 + 1]) / src_alpha);
|
||||
int src_blue =
|
||||
CLIP8((255 * (int)source[k * 4 + 0]) / src_alpha);
|
||||
|
||||
/* blend required if target has data */
|
||||
if (target[k * 4 + 3] > 0) {
|
||||
/* blend RGBA colors */
|
||||
target[k * 4 + 0] =
|
||||
BLEND(src_alpha, target[k * 4 + 0], src_red, tmp);
|
||||
target[k * 4 + 1] =
|
||||
BLEND(src_alpha, target[k * 4 + 1], src_green, tmp);
|
||||
target[k * 4 + 2] =
|
||||
BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp);
|
||||
target[k * 4 + 3] = CLIP8(
|
||||
src_alpha +
|
||||
MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp)
|
||||
);
|
||||
} else {
|
||||
/* paste unpremultiplied RGBA values */
|
||||
target[k * 4 + 0] = src_red;
|
||||
target[k * 4 + 1] = src_green;
|
||||
target[k * 4 + 2] = src_blue;
|
||||
target[k * 4 + 3] = src_alpha;
|
||||
}
|
||||
}
|
||||
case FT_PIXEL_MODE_GRAY2:
|
||||
convert_scale = 255 / 3;
|
||||
break;
|
||||
case FT_PIXEL_MODE_GRAY4:
|
||||
convert_scale = 255 / 15;
|
||||
break;
|
||||
default:
|
||||
convert_scale = 1;
|
||||
}
|
||||
switch (bitmap.pixel_mode) {
|
||||
case FT_PIXEL_MODE_MONO:
|
||||
case FT_PIXEL_MODE_GRAY2:
|
||||
case FT_PIXEL_MODE_GRAY4:
|
||||
if (!bitmap_converted_ready) {
|
||||
FT_Bitmap_Init(&bitmap_converted);
|
||||
bitmap_converted_ready = 1;
|
||||
}
|
||||
} else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
|
||||
error = FT_Bitmap_Convert(library, &bitmap, &bitmap_converted, 1);
|
||||
if (error) {
|
||||
geterror(error);
|
||||
goto glyph_error;
|
||||
}
|
||||
bitmap = bitmap_converted;
|
||||
/* bitmap is now FT_PIXEL_MODE_GRAY, fall through */
|
||||
case FT_PIXEL_MODE_GRAY:
|
||||
break;
|
||||
case FT_PIXEL_MODE_BGRA:
|
||||
if (color) {
|
||||
unsigned char *ink = (unsigned char *)&foreground_ink;
|
||||
break;
|
||||
}
|
||||
/* we didn't ask for color, fall through to default */
|
||||
default:
|
||||
PyErr_SetString(PyExc_OSError, "unsupported bitmap pixel mode");
|
||||
goto glyph_error;
|
||||
}
|
||||
|
||||
/* clip glyph bitmap width to target image bounds */
|
||||
x0 = 0;
|
||||
x1 = bitmap.width;
|
||||
if (xx < 0) {
|
||||
x0 = -xx;
|
||||
}
|
||||
if (xx + x1 > im->xsize) {
|
||||
x1 = im->xsize - xx;
|
||||
}
|
||||
|
||||
source = (unsigned char *)bitmap.buffer;
|
||||
for (bitmap_y = 0; bitmap_y < bitmap.rows; bitmap_y++, yy++) {
|
||||
/* clip glyph bitmap height to target image bounds */
|
||||
if (yy >= 0 && yy < im->ysize) {
|
||||
/* blend this glyph into the buffer */
|
||||
int k;
|
||||
unsigned char *target;
|
||||
unsigned int tmp;
|
||||
if (color) {
|
||||
/* target[RGB] returns the color, target[A] returns the mask */
|
||||
/* target bands get split again in ImageDraw.text */
|
||||
target = (unsigned char *)im->image[yy] + xx * 4;
|
||||
} else {
|
||||
target = im->image8[yy] + xx;
|
||||
}
|
||||
if (color && bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
|
||||
/* paste color glyph */
|
||||
for (k = x0; k < x1; k++) {
|
||||
unsigned int src_alpha = source[k] * convert_scale;
|
||||
unsigned int src_alpha = source[k * 4 + 3];
|
||||
|
||||
/* paste only if source has data */
|
||||
if (src_alpha > 0) {
|
||||
/* unpremultiply BGRa */
|
||||
int src_red =
|
||||
CLIP8((255 * (int)source[k * 4 + 2]) / src_alpha);
|
||||
int src_green =
|
||||
CLIP8((255 * (int)source[k * 4 + 1]) / src_alpha);
|
||||
int src_blue =
|
||||
CLIP8((255 * (int)source[k * 4 + 0]) / src_alpha);
|
||||
|
||||
/* blend required if target has data */
|
||||
if (target[k * 4 + 3] > 0) {
|
||||
/* blend RGBA colors */
|
||||
target[k * 4 + 0] = BLEND(
|
||||
src_alpha, target[k * 4 + 0], ink[0], tmp
|
||||
src_alpha, target[k * 4 + 0], src_red, tmp
|
||||
);
|
||||
target[k * 4 + 1] = BLEND(
|
||||
src_alpha, target[k * 4 + 1], ink[1], tmp
|
||||
src_alpha, target[k * 4 + 1], src_green, tmp
|
||||
);
|
||||
target[k * 4 + 2] = BLEND(
|
||||
src_alpha, target[k * 4 + 2], ink[2], tmp
|
||||
src_alpha, target[k * 4 + 2], src_blue, tmp
|
||||
);
|
||||
target[k * 4 + 3] = CLIP8(
|
||||
src_alpha +
|
||||
@ -1174,35 +1153,68 @@ font_render(FontObject *self, PyObject *args) {
|
||||
)
|
||||
);
|
||||
} else {
|
||||
target[k * 4 + 0] = ink[0];
|
||||
target[k * 4 + 1] = ink[1];
|
||||
target[k * 4 + 2] = ink[2];
|
||||
/* paste unpremultiplied RGBA values */
|
||||
target[k * 4 + 0] = src_red;
|
||||
target[k * 4 + 1] = src_green;
|
||||
target[k * 4 + 2] = src_blue;
|
||||
target[k * 4 + 3] = src_alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (k = x0; k < x1; k++) {
|
||||
unsigned int src_alpha = source[k] * convert_scale;
|
||||
if (src_alpha > 0) {
|
||||
target[k] =
|
||||
target[k] > 0
|
||||
? CLIP8(
|
||||
src_alpha +
|
||||
MULDIV255(
|
||||
target[k], (255 - src_alpha), tmp
|
||||
} else if (bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
|
||||
if (color) {
|
||||
unsigned char *ink = (unsigned char *)&foreground_ink;
|
||||
for (k = x0; k < x1; k++) {
|
||||
unsigned int src_alpha = source[k] * convert_scale;
|
||||
if (src_alpha > 0) {
|
||||
if (target[k * 4 + 3] > 0) {
|
||||
target[k * 4 + 0] = BLEND(
|
||||
src_alpha, target[k * 4 + 0], ink[0], tmp
|
||||
);
|
||||
target[k * 4 + 1] = BLEND(
|
||||
src_alpha, target[k * 4 + 1], ink[1], tmp
|
||||
);
|
||||
target[k * 4 + 2] = BLEND(
|
||||
src_alpha, target[k * 4 + 2], ink[2], tmp
|
||||
);
|
||||
target[k * 4 + 3] = CLIP8(
|
||||
src_alpha + MULDIV255(
|
||||
target[k * 4 + 3],
|
||||
(255 - src_alpha),
|
||||
tmp
|
||||
)
|
||||
);
|
||||
} else {
|
||||
target[k * 4 + 0] = ink[0];
|
||||
target[k * 4 + 1] = ink[1];
|
||||
target[k * 4 + 2] = ink[2];
|
||||
target[k * 4 + 3] = src_alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (k = x0; k < x1; k++) {
|
||||
unsigned int src_alpha = source[k] * convert_scale;
|
||||
if (src_alpha > 0) {
|
||||
target[k] =
|
||||
target[k] > 0
|
||||
? CLIP8(
|
||||
src_alpha +
|
||||
MULDIV255(
|
||||
target[k], (255 - src_alpha), tmp
|
||||
)
|
||||
)
|
||||
)
|
||||
: src_alpha;
|
||||
: src_alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PyErr_SetString(PyExc_OSError, "unsupported bitmap pixel mode");
|
||||
goto glyph_error;
|
||||
}
|
||||
} else {
|
||||
PyErr_SetString(PyExc_OSError, "unsupported bitmap pixel mode");
|
||||
goto glyph_error;
|
||||
}
|
||||
source += bitmap.pitch;
|
||||
}
|
||||
source += bitmap.pitch;
|
||||
}
|
||||
x += glyph_info[i].x_advance;
|
||||
y += glyph_info[i].y_advance;
|
||||
@ -1543,7 +1555,9 @@ setup_module(PyObject *m) {
|
||||
d = PyModule_GetDict(m);
|
||||
|
||||
/* Ready object type */
|
||||
PyType_Ready(&Font_Type);
|
||||
if (PyType_Ready(&Font_Type) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (FT_Init_FreeType(&library)) {
|
||||
return 0; /* leave it uninitialized */
|
||||
|
||||
@ -187,8 +187,17 @@ _unop(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
unop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_UNOP_MAGIC);
|
||||
if (!unop) {
|
||||
return NULL;
|
||||
}
|
||||
out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!out) {
|
||||
return NULL;
|
||||
}
|
||||
im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
|
||||
if (!im1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
unop(out, im1);
|
||||
|
||||
@ -219,9 +228,21 @@ _binop(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
binop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_BINOP_MAGIC);
|
||||
if (!binop) {
|
||||
return NULL;
|
||||
}
|
||||
out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!out) {
|
||||
return NULL;
|
||||
}
|
||||
im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
|
||||
if (!im1) {
|
||||
return NULL;
|
||||
}
|
||||
im2 = (Imaging)PyCapsule_GetPointer(i2, IMAGING_MAGIC);
|
||||
if (!im2) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
binop(out, im1, im2);
|
||||
|
||||
|
||||
@ -53,7 +53,13 @@ apply(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!imgin) {
|
||||
return NULL;
|
||||
}
|
||||
imgout = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC);
|
||||
if (!imgout) {
|
||||
return NULL;
|
||||
}
|
||||
width = imgin->xsize;
|
||||
height = imgin->ysize;
|
||||
|
||||
@ -143,6 +149,9 @@ match(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!imgin) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Unsupported image type");
|
||||
@ -185,6 +194,10 @@ match(PyObject *self, PyObject *args) {
|
||||
(b6 << 6) | (b7 << 7) | (b8 << 8));
|
||||
if (lut[lut_idx]) {
|
||||
PyObject *coordObj = Py_BuildValue("(nn)", col_idx, row_idx);
|
||||
if (!coordObj) {
|
||||
Py_DECREF(ret);
|
||||
return NULL;
|
||||
}
|
||||
PyList_Append(ret, coordObj);
|
||||
Py_XDECREF(coordObj);
|
||||
}
|
||||
@ -216,6 +229,9 @@ get_on_pixels(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
img = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!img) {
|
||||
return NULL;
|
||||
}
|
||||
rows = img->image8;
|
||||
width = img->xsize;
|
||||
height = img->ysize;
|
||||
@ -230,6 +246,10 @@ get_on_pixels(PyObject *self, PyObject *args) {
|
||||
for (col_idx = 0; col_idx < width; col_idx++) {
|
||||
if (row[col_idx]) {
|
||||
PyObject *coordObj = Py_BuildValue("(nn)", col_idx, row_idx);
|
||||
if (!coordObj) {
|
||||
Py_DECREF(ret);
|
||||
return NULL;
|
||||
}
|
||||
PyList_Append(ret, coordObj);
|
||||
Py_XDECREF(coordObj);
|
||||
}
|
||||
|
||||
11
src/_webp.c
11
src/_webp.c
@ -219,6 +219,7 @@ _anim_encoder_dealloc(PyObject *self) {
|
||||
WebPAnimEncoderObject *encp = (WebPAnimEncoderObject *)self;
|
||||
WebPPictureFree(&(encp->frame));
|
||||
WebPAnimEncoderDelete(encp->enc);
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
@ -261,6 +262,9 @@ _anim_encoder_add(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!im) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Setup config for this frame
|
||||
if (!WebPConfigInit(&config)) {
|
||||
@ -441,6 +445,7 @@ _anim_decoder_dealloc(PyObject *self) {
|
||||
WebPAnimDecoderObject *decp = (WebPAnimDecoderObject *)self;
|
||||
WebPDataClear(&(decp->data));
|
||||
WebPAnimDecoderDelete(decp->dec);
|
||||
Py_TYPE(self)->tp_free(self);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
@ -503,6 +508,9 @@ _anim_decoder_get_next(PyObject *self) {
|
||||
bytes = PyBytes_FromStringAndSize(
|
||||
(char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height
|
||||
);
|
||||
if (!bytes) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = Py_BuildValue("Si", bytes, timestamp);
|
||||
|
||||
@ -605,6 +613,9 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC);
|
||||
if (!im) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Setup config for this frame
|
||||
if (!WebPConfigInit(&config)) {
|
||||
|
||||
16
src/decode.c
16
src/decode.c
@ -163,7 +163,7 @@ _setimage(ImagingDecoderObject *decoder, PyObject *args) {
|
||||
x0 = y0 = x1 = y1 = 0;
|
||||
|
||||
/* FIXME: should publish the ImagingType descriptor */
|
||||
if (!PyArg_ParseTuple(args, "O|(iiii)", &op, &x0, &y0, &x1, &y1)) {
|
||||
if (!PyArg_ParseTuple(args, "O(iiii)", &op, &x0, &y0, &x1, &y1)) {
|
||||
return NULL;
|
||||
}
|
||||
im = PyImaging_AsImaging(op);
|
||||
@ -176,15 +176,10 @@ _setimage(ImagingDecoderObject *decoder, PyObject *args) {
|
||||
state = &decoder->state;
|
||||
|
||||
/* Setup decoding tile extent */
|
||||
if (x0 == 0 && x1 == 0) {
|
||||
state->xsize = im->xsize;
|
||||
state->ysize = im->ysize;
|
||||
} else {
|
||||
state->xoff = x0;
|
||||
state->yoff = y0;
|
||||
state->xsize = x1 - x0;
|
||||
state->ysize = y1 - y0;
|
||||
}
|
||||
state->xoff = x0;
|
||||
state->yoff = y0;
|
||||
state->xsize = x1 - x0;
|
||||
state->ysize = y1 - y0;
|
||||
|
||||
if (state->xoff < 0 || state->xsize <= 0 ||
|
||||
state->xsize + state->xoff > (int)im->xsize || state->yoff < 0 ||
|
||||
@ -910,6 +905,7 @@ PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) {
|
||||
} else if (strcmp(format, "jp2") == 0) {
|
||||
codec_format = OPJ_CODEC_JP2;
|
||||
} else {
|
||||
PyErr_SetString(PyExc_ValueError, "unknown codec format");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@ -480,6 +480,9 @@ PyImaging_GrabClipboardWin32(PyObject *self, PyObject *args) {
|
||||
GlobalUnlock(handle);
|
||||
CloseClipboard();
|
||||
|
||||
if (!result) {
|
||||
return NULL;
|
||||
}
|
||||
return Py_BuildValue("zN", format_names[format], result);
|
||||
}
|
||||
|
||||
|
||||
33
src/encode.c
33
src/encode.c
@ -232,27 +232,26 @@ _setimage(ImagingEncoderObject *encoder, PyObject *args) {
|
||||
x0 = y0 = x1 = y1 = 0;
|
||||
|
||||
/* FIXME: should publish the ImagingType descriptor */
|
||||
if (!PyArg_ParseTuple(args, "O|(nnnn)", &op, &x0, &y0, &x1, &y1)) {
|
||||
if (!PyArg_ParseTuple(args, "O(nnnn)", &op, &x0, &y0, &x1, &y1)) {
|
||||
return NULL;
|
||||
}
|
||||
im = PyImaging_AsImaging(op);
|
||||
if (!im) {
|
||||
return NULL;
|
||||
}
|
||||
if (im->xsize == 0 || im->ysize == 0) {
|
||||
PyErr_SetString(PyExc_ValueError, "cannot write empty image");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
encoder->im = im;
|
||||
|
||||
state = &encoder->state;
|
||||
|
||||
if (x0 == 0 && x1 == 0) {
|
||||
state->xsize = im->xsize;
|
||||
state->ysize = im->ysize;
|
||||
} else {
|
||||
state->xoff = x0;
|
||||
state->yoff = y0;
|
||||
state->xsize = x1 - x0;
|
||||
state->ysize = y1 - y0;
|
||||
}
|
||||
state->xoff = x0;
|
||||
state->yoff = y0;
|
||||
state->xsize = x1 - x0;
|
||||
state->ysize = y1 - y0;
|
||||
|
||||
if (state->xoff < 0 || state->xsize <= 0 ||
|
||||
state->xsize + state->xoff > im->xsize || state->yoff < 0 ||
|
||||
@ -728,6 +727,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||
const RawModeID rawmode = findRawModeID(rawmode_name);
|
||||
|
||||
if (get_packer(encoder, mode, rawmode) < 0) {
|
||||
Py_DECREF(encoder);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -743,6 +743,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||
for (pos = 0; pos < tags_size; pos++) {
|
||||
item = PyList_GetItemRef(tags, pos);
|
||||
if (item == NULL) {
|
||||
Py_DECREF(encoder);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -767,6 +768,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||
if (!is_core_tag) {
|
||||
PyObject *tag_type;
|
||||
if (PyDict_GetItemRef(types, key, &tag_type) < 0) {
|
||||
Py_DECREF(encoder);
|
||||
return NULL; // Exception has been already set
|
||||
}
|
||||
if (tag_type) {
|
||||
@ -838,6 +840,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||
if (key_int == TIFFTAG_COLORMAP) {
|
||||
int stride = 256;
|
||||
if (len != 768) {
|
||||
Py_DECREF(encoder);
|
||||
PyErr_SetString(
|
||||
PyExc_ValueError, "Requiring 768 items for Colormap"
|
||||
);
|
||||
@ -999,8 +1002,9 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, PyBytes_AsString(value)
|
||||
);
|
||||
} else if (type == TIFF_DOUBLE || type == TIFF_SRATIONAL ||
|
||||
type == TIFF_RATIONAL) {
|
||||
} else if (
|
||||
type == TIFF_DOUBLE || type == TIFF_SRATIONAL || type == TIFF_RATIONAL
|
||||
) {
|
||||
status = ImagingLibTiffSetField(
|
||||
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
|
||||
);
|
||||
@ -1343,11 +1347,10 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
|
||||
|
||||
if (strcmp(format, "j2k") == 0) {
|
||||
codec_format = OPJ_CODEC_J2K;
|
||||
} else if (strcmp(format, "jpt") == 0) {
|
||||
codec_format = OPJ_CODEC_JPT;
|
||||
} else if (strcmp(format, "jp2") == 0) {
|
||||
codec_format = OPJ_CODEC_JP2;
|
||||
} else {
|
||||
PyErr_SetString(PyExc_ValueError, "unknown codec format");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -1362,6 +1365,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
|
||||
} else if (strcmp(progression, "CPRL") == 0) {
|
||||
prog_order = OPJ_CPRL;
|
||||
} else {
|
||||
PyErr_SetString(PyExc_ValueError, "unknown progression");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -1374,6 +1378,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) {
|
||||
} else if (strcmp(cinema_mode, "cinema4k-24") == 0) {
|
||||
cine_mode = OPJ_CINEMA4K_24;
|
||||
} else {
|
||||
PyErr_SetString(PyExc_ValueError, "unknown cinema mode");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
@ -170,16 +170,17 @@ export_named_type(struct ArrowSchema *schema, char *format, const char *name) {
|
||||
strncpy(formatp, format, format_len);
|
||||
strncpy(namep, name, name_len);
|
||||
|
||||
*schema = (struct ArrowSchema){// Type description
|
||||
.format = formatp,
|
||||
.name = namep,
|
||||
.metadata = NULL,
|
||||
.flags = 0,
|
||||
.n_children = 0,
|
||||
.children = NULL,
|
||||
.dictionary = NULL,
|
||||
// Bookkeeping
|
||||
.release = &ReleaseExportedSchema
|
||||
*schema = (struct ArrowSchema){
|
||||
// Type description
|
||||
.format = formatp,
|
||||
.name = namep,
|
||||
.metadata = NULL,
|
||||
.flags = 0,
|
||||
.n_children = 0,
|
||||
.children = NULL,
|
||||
.dictionary = NULL,
|
||||
// Bookkeeping
|
||||
.release = &ReleaseExportedSchema
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
@ -287,17 +288,18 @@ export_single_channel_array(Imaging im, struct ArrowArray *array) {
|
||||
im->refcount++;
|
||||
MUTEX_UNLOCK(&im->mutex);
|
||||
// Initialize primitive fields
|
||||
*array = (struct ArrowArray){// Data description
|
||||
.length = length,
|
||||
.offset = 0,
|
||||
.null_count = 0,
|
||||
.n_buffers = 2,
|
||||
.n_children = 0,
|
||||
.children = NULL,
|
||||
.dictionary = NULL,
|
||||
// Bookkeeping
|
||||
.release = &release_const_array,
|
||||
.private_data = im
|
||||
*array = (struct ArrowArray){
|
||||
// Data description
|
||||
.length = length,
|
||||
.offset = 0,
|
||||
.null_count = 0,
|
||||
.n_buffers = 2,
|
||||
.n_children = 0,
|
||||
.children = NULL,
|
||||
.dictionary = NULL,
|
||||
// Bookkeeping
|
||||
.release = &release_const_array,
|
||||
.private_data = im
|
||||
};
|
||||
|
||||
// Allocate list of buffers
|
||||
@ -332,17 +334,18 @@ export_fixed_pixel_array(Imaging im, struct ArrowArray *array) {
|
||||
// Initialize primitive fields
|
||||
// Fixed length arrays are 1 buffer of validity, and the length in pixels.
|
||||
// Data is in a child array.
|
||||
*array = (struct ArrowArray){// Data description
|
||||
.length = length,
|
||||
.offset = 0,
|
||||
.null_count = 0,
|
||||
.n_buffers = 1,
|
||||
.n_children = 1,
|
||||
.children = NULL,
|
||||
.dictionary = NULL,
|
||||
// Bookkeeping
|
||||
.release = &release_const_array,
|
||||
.private_data = im
|
||||
*array = (struct ArrowArray){
|
||||
// Data description
|
||||
.length = length,
|
||||
.offset = 0,
|
||||
.null_count = 0,
|
||||
.n_buffers = 1,
|
||||
.n_children = 1,
|
||||
.children = NULL,
|
||||
.dictionary = NULL,
|
||||
// Bookkeeping
|
||||
.release = &release_const_array,
|
||||
.private_data = im
|
||||
};
|
||||
|
||||
// Allocate list of buffers
|
||||
@ -367,17 +370,18 @@ export_fixed_pixel_array(Imaging im, struct ArrowArray *array) {
|
||||
MUTEX_LOCK(&im->mutex);
|
||||
im->refcount++;
|
||||
MUTEX_UNLOCK(&im->mutex);
|
||||
*array->children[0] = (struct ArrowArray){// Data description
|
||||
.length = length * 4,
|
||||
.offset = 0,
|
||||
.null_count = 0,
|
||||
.n_buffers = 2,
|
||||
.n_children = 0,
|
||||
.children = NULL,
|
||||
.dictionary = NULL,
|
||||
// Bookkeeping
|
||||
.release = &release_const_array,
|
||||
.private_data = im
|
||||
*array->children[0] = (struct ArrowArray){
|
||||
// Data description
|
||||
.length = length * 4,
|
||||
.offset = 0,
|
||||
.null_count = 0,
|
||||
.n_buffers = 2,
|
||||
.n_children = 0,
|
||||
.children = NULL,
|
||||
.dictionary = NULL,
|
||||
// Bookkeeping
|
||||
.release = &release_const_array,
|
||||
.private_data = im
|
||||
};
|
||||
|
||||
array->children[0]->buffers =
|
||||
|
||||
@ -1695,16 +1695,20 @@ ImagingConvertTransparent(Imaging imIn, const ModeID mode, int r, int g, int b)
|
||||
if (mode == IMAGING_MODE_RGBa) {
|
||||
premultiplied = 1;
|
||||
}
|
||||
} else if (imIn->mode == IMAGING_MODE_RGB &&
|
||||
(mode == IMAGING_MODE_LA || mode == IMAGING_MODE_La)) {
|
||||
} else if (
|
||||
imIn->mode == IMAGING_MODE_RGB &&
|
||||
(mode == IMAGING_MODE_LA || mode == IMAGING_MODE_La)
|
||||
) {
|
||||
convert = rgb2la;
|
||||
source_transparency = 1;
|
||||
if (mode == IMAGING_MODE_La) {
|
||||
premultiplied = 1;
|
||||
}
|
||||
} else if ((imIn->mode == IMAGING_MODE_1 || imIn->mode == IMAGING_MODE_I ||
|
||||
imIn->mode == IMAGING_MODE_I_16 || imIn->mode == IMAGING_MODE_L) &&
|
||||
(mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_LA)) {
|
||||
} else if (
|
||||
(imIn->mode == IMAGING_MODE_1 || imIn->mode == IMAGING_MODE_I ||
|
||||
imIn->mode == IMAGING_MODE_I_16 || imIn->mode == IMAGING_MODE_L) &&
|
||||
(mode == IMAGING_MODE_RGBA || mode == IMAGING_MODE_LA)
|
||||
) {
|
||||
if (imIn->mode == IMAGING_MODE_1) {
|
||||
convert = bit2rgb;
|
||||
} else if (imIn->mode == IMAGING_MODE_I) {
|
||||
|
||||
@ -537,8 +537,9 @@ polygon_generic(
|
||||
// Needed to draw consistent polygons
|
||||
xx[j] = xx[j - 1];
|
||||
j++;
|
||||
} else if ((ymin == current->ymin || ymin == current->ymax) &&
|
||||
current->dx != 0) {
|
||||
} else if (
|
||||
(ymin == current->ymin || ymin == current->ymax) && current->dx != 0
|
||||
) {
|
||||
// Connect discontiguous corners
|
||||
for (k = 0; k < i; k++) {
|
||||
Edge *other_edge = edge_table[k];
|
||||
@ -570,8 +571,10 @@ polygon_generic(
|
||||
adjacent_line_x, adjacent_line_x_other_edge
|
||||
)) +
|
||||
1;
|
||||
} else if (xx[j - 1] < adjacent_line_x - 1 &&
|
||||
xx[j - 1] < adjacent_line_x_other_edge - 1) {
|
||||
} else if (
|
||||
xx[j - 1] < adjacent_line_x - 1 &&
|
||||
xx[j - 1] < adjacent_line_x_other_edge - 1
|
||||
) {
|
||||
xx[j - 1] =
|
||||
roundf(fmin(
|
||||
adjacent_line_x, adjacent_line_x_other_edge
|
||||
|
||||
@ -89,10 +89,12 @@ ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) {
|
||||
INT32 mask = 0xffffffff;
|
||||
if (im->bands == 3) {
|
||||
((UINT8 *)&mask)[3] = 0;
|
||||
} else if (alpha_only &&
|
||||
(im->mode == IMAGING_MODE_RGBa || im->mode == IMAGING_MODE_RGBA ||
|
||||
im->mode == IMAGING_MODE_La || im->mode == IMAGING_MODE_LA ||
|
||||
im->mode == IMAGING_MODE_PA)) {
|
||||
} else if (
|
||||
alpha_only &&
|
||||
(im->mode == IMAGING_MODE_RGBa || im->mode == IMAGING_MODE_RGBA ||
|
||||
im->mode == IMAGING_MODE_La || im->mode == IMAGING_MODE_LA ||
|
||||
im->mode == IMAGING_MODE_PA)
|
||||
) {
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
mask = 0x000000ff;
|
||||
#else
|
||||
|
||||
@ -601,6 +601,7 @@ j2ku_sycca_rgba(
|
||||
static const struct j2k_decode_unpacker j2k_unpackers[] = {
|
||||
{IMAGING_MODE_L, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_l},
|
||||
{IMAGING_MODE_P, OPJ_CLRSPC_SRGB, 1, 0, j2ku_gray_l},
|
||||
{IMAGING_MODE_P, OPJ_CLRSPC_CMYK, 1, 0, j2ku_gray_l},
|
||||
{IMAGING_MODE_PA, OPJ_CLRSPC_SRGB, 2, 0, j2ku_graya_la},
|
||||
{IMAGING_MODE_I_16, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
|
||||
{IMAGING_MODE_I_16B, OPJ_CLRSPC_GRAY, 1, 0, j2ku_gray_i},
|
||||
|
||||
@ -330,8 +330,10 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
|
||||
components = 4;
|
||||
color_space = OPJ_CLRSPC_SRGB;
|
||||
pack = j2k_pack_rgba;
|
||||
#if ((OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR == 5 && OPJ_VERSION_BUILD >= 3) || \
|
||||
(OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR > 5) || OPJ_VERSION_MAJOR > 2)
|
||||
#if ( \
|
||||
(OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR == 5 && OPJ_VERSION_BUILD >= 3) || \
|
||||
(OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR > 5) || OPJ_VERSION_MAJOR > 2 \
|
||||
)
|
||||
} else if (im->mode == IMAGING_MODE_CMYK) {
|
||||
components = 4;
|
||||
color_space = OPJ_CLRSPC_CMYK;
|
||||
|
||||
@ -206,8 +206,10 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by
|
||||
context->cinfo.out_color_space = JCS_EXT_RGBX;
|
||||
}
|
||||
#endif
|
||||
else if (context->rawmode == IMAGING_RAWMODE_CMYK ||
|
||||
context->rawmode == IMAGING_RAWMODE_CMYK_I) {
|
||||
else if (
|
||||
context->rawmode == IMAGING_RAWMODE_CMYK ||
|
||||
context->rawmode == IMAGING_RAWMODE_CMYK_I
|
||||
) {
|
||||
context->cinfo.out_color_space = JCS_CMYK;
|
||||
} else if (context->rawmode == IMAGING_RAWMODE_YCbCr) {
|
||||
context->cinfo.out_color_space = JCS_YCbCr;
|
||||
|
||||
@ -46,8 +46,9 @@ ImagingConvertMatrix(Imaging im, const ModeID mode, float m[]) {
|
||||
}
|
||||
}
|
||||
ImagingSectionLeave(&cookie);
|
||||
} else if (mode == IMAGING_MODE_HSV || mode == IMAGING_MODE_LAB ||
|
||||
mode == IMAGING_MODE_RGB) {
|
||||
} else if (
|
||||
mode == IMAGING_MODE_HSV || mode == IMAGING_MODE_LAB || mode == IMAGING_MODE_RGB
|
||||
) {
|
||||
imOut = ImagingNewDirty(mode, im->xsize, im->ysize);
|
||||
if (!imOut) {
|
||||
return NULL;
|
||||
|
||||
@ -325,6 +325,19 @@ ImagingPackXBGR(UINT8 *out, const UINT8 *in, int pixels) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ImagingPackCMYK2RGB(UINT8 *out, const UINT8 *in, int xsize) {
|
||||
int x, nk, tmp;
|
||||
for (x = 0; x < xsize; x++) {
|
||||
nk = 255 - in[3];
|
||||
out[0] = CLIP8(nk - MULDIV255(in[0], nk, tmp));
|
||||
out[1] = CLIP8(nk - MULDIV255(in[1], nk, tmp));
|
||||
out[2] = CLIP8(nk - MULDIV255(in[2], nk, tmp));
|
||||
out += 3;
|
||||
in += 4;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ImagingPackBGRA(UINT8 *out, const UINT8 *in, int pixels) {
|
||||
int i;
|
||||
@ -605,6 +618,7 @@ static struct {
|
||||
{IMAGING_MODE_CMYK, IMAGING_RAWMODE_M, 8, band1},
|
||||
{IMAGING_MODE_CMYK, IMAGING_RAWMODE_Y, 8, band2},
|
||||
{IMAGING_MODE_CMYK, IMAGING_RAWMODE_K, 8, band3},
|
||||
{IMAGING_MODE_CMYK, IMAGING_RAWMODE_RGB, 24, ImagingPackCMYK2RGB},
|
||||
|
||||
/* video (YCbCr) */
|
||||
{IMAGING_MODE_YCbCr, IMAGING_RAWMODE_YCbCr, 24, ImagingPackRGB},
|
||||
|
||||
@ -27,7 +27,8 @@ ImagingPaletteNew(const ModeID mode) {
|
||||
int i;
|
||||
ImagingPalette palette;
|
||||
|
||||
if (mode != IMAGING_MODE_RGB && mode != IMAGING_MODE_RGBA) {
|
||||
if (mode != IMAGING_MODE_RGB && mode != IMAGING_MODE_RGBA &&
|
||||
mode != IMAGING_MODE_CMYK) {
|
||||
return (ImagingPalette)ImagingError_ModeError();
|
||||
}
|
||||
|
||||
|
||||
@ -352,16 +352,16 @@ ImagingPaste(
|
||||
|
||||
static inline void
|
||||
fill(
|
||||
Imaging imOut, const void *ink_, int dx, int dy, int xsize, int ysize, int pixelsize
|
||||
Imaging imOut, const UINT8 *ink, int dx, int dy, int xsize, int ysize, int pixelsize
|
||||
) {
|
||||
/* fill opaque region */
|
||||
|
||||
int x, y;
|
||||
int x, y, i;
|
||||
UINT8 ink8 = 0;
|
||||
INT32 ink32 = 0L;
|
||||
|
||||
memcpy(&ink32, ink_, pixelsize);
|
||||
memcpy(&ink8, ink_, sizeof(ink8));
|
||||
memcpy(&ink32, ink, pixelsize);
|
||||
memcpy(&ink8, ink, sizeof(ink8));
|
||||
|
||||
if (imOut->image8 || ink32 == 0L) {
|
||||
dx *= pixelsize;
|
||||
@ -371,12 +371,24 @@ fill(
|
||||
}
|
||||
|
||||
} else {
|
||||
#if defined _WIN32 && !defined _WIN64
|
||||
dx *= pixelsize;
|
||||
for (y = 0; y < ysize; y++) {
|
||||
UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx;
|
||||
for (x = 0; x < xsize; x++) {
|
||||
for (i = 0; i < pixelsize; i++) {
|
||||
*out++ = ink[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (y = 0; y < ysize; y++) {
|
||||
INT32 *out = imOut->image32[y + dy] + dx;
|
||||
for (x = 0; x < xsize; x++) {
|
||||
out[x] = ink32;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -175,8 +175,15 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
|
||||
|
||||
/* Get all data from File descriptor */
|
||||
c = (SGISTATE *)state->context;
|
||||
_imaging_seek_pyFd(state->fd, 0L, SEEK_END);
|
||||
if (_imaging_seek_pyFd(state->fd, 0L, SEEK_END) == -1) {
|
||||
state->errcode = IMAGING_CODEC_UNKNOWN;
|
||||
return -1;
|
||||
}
|
||||
c->bufsize = _imaging_tell_pyFd(state->fd);
|
||||
if (c->bufsize == -1) {
|
||||
state->errcode = IMAGING_CODEC_UNKNOWN;
|
||||
return -1;
|
||||
}
|
||||
c->bufsize -= SGI_HEADER_SIZE;
|
||||
|
||||
c->tablen = im->bands * im->ysize;
|
||||
@ -194,8 +201,8 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t
|
||||
state->errcode = IMAGING_CODEC_MEMORY;
|
||||
return -1;
|
||||
}
|
||||
_imaging_seek_pyFd(state->fd, SGI_HEADER_SIZE, SEEK_SET);
|
||||
if (_imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize) != c->bufsize) {
|
||||
if (_imaging_seek_pyFd(state->fd, SGI_HEADER_SIZE, SEEK_SET) == -1 ||
|
||||
_imaging_read_pyFd(state->fd, (char *)ptr, c->bufsize) != c->bufsize) {
|
||||
free(ptr);
|
||||
state->errcode = IMAGING_CODEC_UNKNOWN;
|
||||
return -1;
|
||||
|
||||
@ -677,9 +677,15 @@ ImagingNewArrow(
|
||||
Imaging im;
|
||||
struct ArrowSchema *schema =
|
||||
(struct ArrowSchema *)PyCapsule_GetPointer(schema_capsule, "arrow_schema");
|
||||
if (!schema) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct ArrowArray *external_array =
|
||||
(struct ArrowArray *)PyCapsule_GetPointer(array_capsule, "arrow_array");
|
||||
if (!external_array) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (xsize < 0 || ysize < 0) {
|
||||
return (Imaging)ImagingError_ValueError("bad image size");
|
||||
|
||||
@ -12,6 +12,9 @@ _imaging_read_pyFd(PyObject *fd, char *dest, Py_ssize_t bytes) {
|
||||
int bytes_result;
|
||||
|
||||
result = PyObject_CallMethod(fd, "read", "n", bytes);
|
||||
if (result == NULL) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
bytes_result = PyBytes_AsStringAndSize(result, &buffer, &length);
|
||||
if (bytes_result == -1) {
|
||||
@ -28,7 +31,7 @@ _imaging_read_pyFd(PyObject *fd, char *dest, Py_ssize_t bytes) {
|
||||
return length;
|
||||
|
||||
err:
|
||||
Py_DECREF(result);
|
||||
Py_XDECREF(result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -38,9 +41,16 @@ _imaging_write_pyFd(PyObject *fd, char *src, Py_ssize_t bytes) {
|
||||
PyObject *byteObj;
|
||||
|
||||
byteObj = PyBytes_FromStringAndSize(src, bytes);
|
||||
if (!byteObj) {
|
||||
return -1;
|
||||
}
|
||||
result = PyObject_CallMethod(fd, "write", "O", byteObj);
|
||||
|
||||
Py_DECREF(byteObj);
|
||||
if (result == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_DECREF(result);
|
||||
|
||||
return bytes;
|
||||
@ -51,6 +61,9 @@ _imaging_seek_pyFd(PyObject *fd, Py_ssize_t offset, int whence) {
|
||||
PyObject *result;
|
||||
|
||||
result = PyObject_CallMethod(fd, "seek", "ni", offset, whence);
|
||||
if (result == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_DECREF(result);
|
||||
return 0;
|
||||
@ -62,6 +75,9 @@ _imaging_tell_pyFd(PyObject *fd) {
|
||||
Py_ssize_t location;
|
||||
|
||||
result = PyObject_CallMethod(fd, "tell", NULL);
|
||||
if (result == NULL) {
|
||||
return -1;
|
||||
}
|
||||
location = PyLong_AsSsize_t(result);
|
||||
|
||||
Py_DECREF(result);
|
||||
|
||||
@ -114,14 +114,14 @@ ARCHITECTURES = {
|
||||
|
||||
V = {
|
||||
"BROTLI": "1.2.0",
|
||||
"FREETYPE": "2.14.1",
|
||||
"FREETYPE": "2.14.3",
|
||||
"FRIBIDI": "1.0.16",
|
||||
"HARFBUZZ": "12.3.2",
|
||||
"HARFBUZZ": "13.2.1",
|
||||
"JPEGTURBO": "3.1.3",
|
||||
"LCMS2": "2.18",
|
||||
"LIBAVIF": "1.3.0",
|
||||
"LIBAVIF": "1.4.1",
|
||||
"LIBIMAGEQUANT": "4.4.1",
|
||||
"LIBPNG": "1.6.54",
|
||||
"LIBPNG": "1.6.56",
|
||||
"LIBWEBP": "1.6.0",
|
||||
"OPENJPEG": "2.5.4",
|
||||
"TIFF": "4.7.1",
|
||||
@ -542,14 +542,11 @@ def write_script(
|
||||
|
||||
|
||||
def get_footer(dep: dict[str, Any]) -> list[str]:
|
||||
lines = []
|
||||
for out in dep.get("headers", []):
|
||||
lines.append(cmd_copy(out, "{inc_dir}"))
|
||||
for out in dep.get("libs", []):
|
||||
lines.append(cmd_copy(out, "{lib_dir}"))
|
||||
for out in dep.get("bins", []):
|
||||
lines.append(cmd_copy(out, "{bin_dir}"))
|
||||
return lines
|
||||
return (
|
||||
[cmd_copy(out, "{inc_dir}") for out in dep.get("headers", [])]
|
||||
+ [cmd_copy(out, "{lib_dir}") for out in dep.get("libs", [])]
|
||||
+ [cmd_copy(out, "{bin_dir}") for out in dep.get("bins", [])]
|
||||
)
|
||||
|
||||
|
||||
def build_env(prefs: dict[str, str], verbose: bool) -> None:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user