mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-28 17:10:02 +03:00
Merge branch 'main' into fix-qtables-and-quality-scaling
This commit is contained in:
commit
c5deb5ae6f
|
@ -66,7 +66,7 @@ if [[ $(uname) != CYGWIN* ]]; then
|
||||||
pushd depends && ./install_raqm.sh && popd
|
pushd depends && ./install_raqm.sh && popd
|
||||||
|
|
||||||
# libavif
|
# libavif
|
||||||
pushd depends && CMAKE_POLICY_VERSION_MINIMUM=3.5 ./install_libavif.sh && popd
|
pushd depends && ./install_libavif.sh && popd
|
||||||
|
|
||||||
# extra test images
|
# extra test images
|
||||||
pushd depends && ./install_extra_test_images.sh && popd
|
pushd depends && ./install_extra_test_images.sh && popd
|
||||||
|
|
|
@ -4,6 +4,7 @@ IceSpringPySideStubs-PySide6
|
||||||
ipython
|
ipython
|
||||||
numpy
|
numpy
|
||||||
packaging
|
packaging
|
||||||
|
pyarrow-stubs
|
||||||
pytest
|
pytest
|
||||||
sphinx
|
sphinx
|
||||||
types-atheris
|
types-atheris
|
||||||
|
|
60
.github/workflows/test-valgrind-memory.yml
vendored
Normal file
60
.github/workflows/test-valgrind-memory.yml
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
name: Test Valgrind Memory Leaks
|
||||||
|
|
||||||
|
# like the Docker tests, but running valgrind only on *.c/*.h changes.
|
||||||
|
|
||||||
|
# this is very expensive. Only run on the pull request.
|
||||||
|
on:
|
||||||
|
# push:
|
||||||
|
# branches:
|
||||||
|
# - "**"
|
||||||
|
# paths:
|
||||||
|
# - ".github/workflows/test-valgrind.yml"
|
||||||
|
# - "**.c"
|
||||||
|
# - "**.h"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- ".github/workflows/test-valgrind.yml"
|
||||||
|
- "**.c"
|
||||||
|
- "**.h"
|
||||||
|
- "depends/docker-test-valgrind-memory.sh"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
docker: [
|
||||||
|
ubuntu-22.04-jammy-amd64-valgrind,
|
||||||
|
]
|
||||||
|
dockerTag: [main]
|
||||||
|
|
||||||
|
name: ${{ matrix.docker }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Build system information
|
||||||
|
run: python3 .github/workflows/system-info.py
|
||||||
|
|
||||||
|
- name: Docker pull
|
||||||
|
run: |
|
||||||
|
docker pull pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }}
|
||||||
|
|
||||||
|
- name: Build and Run Valgrind
|
||||||
|
run: |
|
||||||
|
# The Pillow user in the docker container is UID 1001
|
||||||
|
sudo chown -R 1001 $GITHUB_WORKSPACE
|
||||||
|
docker run --name pillow_container -e "PILLOW_VALGRIND_TEST=true" -v $GITHUB_WORKSPACE:/Pillow pythonpillow/${{ matrix.docker }}:${{ matrix.dockerTag }} /Pillow/depends/docker-test-valgrind-memory.sh
|
||||||
|
sudo chown -R runner $GITHUB_WORKSPACE
|
5
.github/workflows/test-windows.yml
vendored
5
.github/workflows/test-windows.yml
vendored
|
@ -31,16 +31,15 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: windows-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
python-version: ["pypy3.11", "pypy3.10", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
||||||
architecture: ["x64"]
|
architecture: ["x64"]
|
||||||
os: ["windows-latest"]
|
|
||||||
include:
|
include:
|
||||||
# Test the oldest Python on 32-bit
|
# Test the oldest Python on 32-bit
|
||||||
- { python-version: "3.9", architecture: "x86", os: "windows-2019" }
|
- { python-version: "3.9", architecture: "x86" }
|
||||||
|
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: v0.11.8
|
rev: v0.11.12
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--exit-non-zero-on-fix]
|
args: [--exit-non-zero-on-fix]
|
||||||
|
@ -24,7 +24,7 @@ repos:
|
||||||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: v20.1.3
|
rev: v20.1.5
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
types: [c]
|
types: [c]
|
||||||
|
@ -58,7 +58,7 @@ repos:
|
||||||
- id: check-renovate
|
- id: check-renovate
|
||||||
|
|
||||||
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||||
rev: v1.6.0
|
rev: v1.9.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: zizmor
|
- id: zizmor
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ repos:
|
||||||
- id: sphinx-lint
|
- id: sphinx-lint
|
||||||
|
|
||||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||||
rev: v2.5.1
|
rev: v2.6.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
|
|
||||||
|
|
16
Makefile
16
Makefile
|
@ -97,13 +97,27 @@ test:
|
||||||
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
|
python3 -c "import pytest" > /dev/null 2>&1 || python3 -m pip install pytest
|
||||||
python3 -m pytest -qq
|
python3 -m pytest -qq
|
||||||
|
|
||||||
|
.PHONY: test-p
|
||||||
|
test-p:
|
||||||
|
python3 -c "import xdist" > /dev/null 2>&1 || python3 -m pip install pytest-xdist
|
||||||
|
python3 -m pytest -qq -n auto
|
||||||
|
|
||||||
|
|
||||||
.PHONY: valgrind
|
.PHONY: valgrind
|
||||||
valgrind:
|
valgrind:
|
||||||
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
|
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
|
||||||
PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
|
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp --leak-check=no \
|
||||||
--log-file=/tmp/valgrind-output \
|
--log-file=/tmp/valgrind-output \
|
||||||
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
python3 -m pytest --no-memcheck -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
||||||
|
|
||||||
|
.PHONY: valgrind-leak
|
||||||
|
valgrind-leak:
|
||||||
|
python3 -c "import pytest_valgrind" > /dev/null 2>&1 || python3 -m pip install pytest-valgrind
|
||||||
|
PILLOW_VALGRIND_TEST=true PYTHONMALLOC=malloc valgrind --suppressions=Tests/oss-fuzz/python.supp \
|
||||||
|
--leak-check=full --show-leak-kinds=definite --errors-for-leak-kinds=definite \
|
||||||
|
--log-file=/tmp/valgrind-output \
|
||||||
|
python3 -m pytest -vv --valgrind --valgrind-log=/tmp/valgrind-output
|
||||||
|
|
||||||
.PHONY: readme
|
.PHONY: readme
|
||||||
readme:
|
readme:
|
||||||
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2
|
python3 -c "import markdown2" > /dev/null 2>&1 || python3 -m pip install markdown2
|
||||||
|
|
|
@ -161,6 +161,12 @@ def assert_tuple_approx_equal(
|
||||||
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
|
pytest.fail(msg + ": " + repr(actuals) + " != " + repr(targets))
|
||||||
|
|
||||||
|
|
||||||
|
def timeout_unless_slower_valgrind(timeout: float) -> pytest.MarkDecorator:
|
||||||
|
if "PILLOW_VALGRIND_TEST" in os.environ:
|
||||||
|
return pytest.mark.pil_noop_mark()
|
||||||
|
return pytest.mark.timeout(timeout)
|
||||||
|
|
||||||
|
|
||||||
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
def skip_unless_feature(feature: str) -> pytest.MarkDecorator:
|
||||||
reason = f"{feature} not available"
|
reason = f"{feature} not available"
|
||||||
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
return pytest.mark.skipif(not features.check(feature), reason=reason)
|
||||||
|
|
|
@ -14,3 +14,23 @@
|
||||||
fun:_TIFFReadEncodedTileAndAllocBuffer
|
fun:_TIFFReadEncodedTileAndAllocBuffer
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
<python_alloc_possible_leak>
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: all
|
||||||
|
fun:malloc
|
||||||
|
fun:_PyMem_RawMalloc
|
||||||
|
fun:PyObject_Malloc
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
<python_realloc_possible_leak>
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: all
|
||||||
|
fun:malloc
|
||||||
|
fun:_PyMem_RawRealloc
|
||||||
|
fun:PyMem_Realloc
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
|
@ -47,7 +47,6 @@ def test_unknown_version() -> None:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
|
def test_old_version(deprecated: str, plural: bool, expected: str) -> None:
|
||||||
expected = r""
|
|
||||||
with pytest.raises(RuntimeError, match=expected):
|
with pytest.raises(RuntimeError, match=expected):
|
||||||
_deprecate.deprecate(deprecated, 1, plural=plural)
|
_deprecate.deprecate(deprecated, 1, plural=plural)
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ from .helper import (
|
||||||
is_win32,
|
is_win32,
|
||||||
mark_if_feature_version,
|
mark_if_feature_version,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
)
|
)
|
||||||
|
|
||||||
HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript()
|
HAS_GHOSTSCRIPT = EpsImagePlugin.has_ghostscript()
|
||||||
|
@ -398,7 +399,7 @@ def test_emptyline() -> None:
|
||||||
assert image.format == "EPS"
|
assert image.format == "EPS"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.timeout(timeout=5)
|
@timeout_unless_slower_valgrind(5)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"test_file",
|
"test_file",
|
||||||
["Tests/images/eps/timeout-d675703545fee17acab56e5fec644c19979175de.eps"],
|
["Tests/images/eps/timeout-d675703545fee17acab56e5fec644c19979175de.eps"],
|
||||||
|
|
|
@ -7,7 +7,12 @@ import pytest
|
||||||
|
|
||||||
from PIL import FliImagePlugin, Image, ImageFile
|
from PIL import FliImagePlugin, Image, ImageFile
|
||||||
|
|
||||||
from .helper import assert_image_equal, assert_image_equal_tofile, is_pypy
|
from .helper import (
|
||||||
|
assert_image_equal,
|
||||||
|
assert_image_equal_tofile,
|
||||||
|
is_pypy,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
|
)
|
||||||
|
|
||||||
# created as an export of a palette image from Gimp2.6
|
# created as an export of a palette image from Gimp2.6
|
||||||
# save as...-> hopper.fli, default options.
|
# save as...-> hopper.fli, default options.
|
||||||
|
@ -189,7 +194,7 @@ def test_seek() -> None:
|
||||||
"Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli",
|
"Tests/images/timeout-bff0a9dc7243a8e6ede2408d2ffa6a9964698b87.fli",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.timeout(timeout=3)
|
@timeout_unless_slower_valgrind(3)
|
||||||
def test_timeouts(test_file: str) -> None:
|
def test_timeouts(test_file: str) -> None:
|
||||||
with open(test_file, "rb") as f:
|
with open(test_file, "rb") as f:
|
||||||
with Image.open(f) as im:
|
with Image.open(f) as im:
|
||||||
|
|
|
@ -32,6 +32,7 @@ from .helper import (
|
||||||
is_win32,
|
is_win32,
|
||||||
mark_if_feature_version,
|
mark_if_feature_version,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
)
|
)
|
||||||
|
|
||||||
ElementTree: ModuleType | None
|
ElementTree: ModuleType | None
|
||||||
|
@ -1045,7 +1046,7 @@ class TestFileJpeg:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
im.save(f, xmp=b"1" * 65505)
|
im.save(f, xmp=b"1" * 65505)
|
||||||
|
|
||||||
@pytest.mark.timeout(timeout=1)
|
@timeout_unless_slower_valgrind(1)
|
||||||
def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_eof(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
# Even though this decoder never says that it is finished
|
# Even though this decoder never says that it is finished
|
||||||
# the image should still end when there is no new data
|
# the image should still end when there is no new data
|
||||||
|
|
|
@ -13,7 +13,12 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, PdfParser, features
|
from PIL import Image, PdfParser, features
|
||||||
|
|
||||||
from .helper import hopper, mark_if_feature_version, skip_unless_feature
|
from .helper import (
|
||||||
|
hopper,
|
||||||
|
mark_if_feature_version,
|
||||||
|
skip_unless_feature,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str:
|
def helper_save_as_pdf(tmp_path: Path, mode: str, **kwargs: Any) -> str:
|
||||||
|
@ -339,8 +344,7 @@ def test_pdf_append_to_bytesio() -> None:
|
||||||
assert len(f.getvalue()) > initial_size
|
assert len(f.getvalue()) > initial_size
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.timeout(1)
|
@timeout_unless_slower_valgrind(1)
|
||||||
@pytest.mark.skipif("PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower")
|
|
||||||
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
|
@pytest.mark.parametrize("newline", (b"\r", b"\n"))
|
||||||
def test_redos(newline: bytes) -> None:
|
def test_redos(newline: bytes) -> None:
|
||||||
malicious = b" trailer<<>>" + newline * 3456
|
malicious = b" trailer<<>>" + newline * 3456
|
||||||
|
|
|
@ -26,6 +26,7 @@ from .helper import (
|
||||||
hopper,
|
hopper,
|
||||||
is_pypy,
|
is_pypy,
|
||||||
is_win32,
|
is_win32,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
)
|
)
|
||||||
|
|
||||||
ElementTree: ModuleType | None
|
ElementTree: ModuleType | None
|
||||||
|
@ -988,7 +989,7 @@ class TestFileTiff:
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
@pytest.mark.timeout(6)
|
@timeout_unless_slower_valgrind(6)
|
||||||
@pytest.mark.filterwarnings("ignore:Truncated File Read")
|
@pytest.mark.filterwarnings("ignore:Truncated File Read")
|
||||||
def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
def test_timeout(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
with Image.open("Tests/images/timeout-6646305047838720") as im:
|
with Image.open("Tests/images/timeout-6646305047838720") as im:
|
||||||
|
@ -1001,7 +1002,7 @@ class TestFileTiff:
|
||||||
"Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif",
|
"Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.timeout(2)
|
@timeout_unless_slower_valgrind(2)
|
||||||
def test_oom(self, test_file: str) -> None:
|
def test_oom(self, test_file: str) -> None:
|
||||||
with pytest.raises(UnidentifiedImageError):
|
with pytest.raises(UnidentifiedImageError):
|
||||||
with pytest.warns(UserWarning):
|
with pytest.warns(UserWarning):
|
||||||
|
|
|
@ -34,6 +34,7 @@ from .helper import (
|
||||||
is_win32,
|
is_win32,
|
||||||
mark_if_feature_version,
|
mark_if_feature_version,
|
||||||
skip_unless_feature,
|
skip_unless_feature,
|
||||||
|
timeout_unless_slower_valgrind,
|
||||||
)
|
)
|
||||||
|
|
||||||
ElementTree: ModuleType | None
|
ElementTree: ModuleType | None
|
||||||
|
@ -572,10 +573,7 @@ class TestImage:
|
||||||
i = Image.new("RGB", [1, 1])
|
i = Image.new("RGB", [1, 1])
|
||||||
assert isinstance(i.size, tuple)
|
assert isinstance(i.size, tuple)
|
||||||
|
|
||||||
@pytest.mark.timeout(0.75)
|
@timeout_unless_slower_valgrind(0.75)
|
||||||
@pytest.mark.skipif(
|
|
||||||
"PILLOW_VALGRIND_TEST" in os.environ, reason="Valgrind is slower"
|
|
||||||
)
|
|
||||||
@pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
|
@pytest.mark.parametrize("size", ((0, 100000000), (100000000, 0)))
|
||||||
def test_empty_image(self, size: tuple[int, int]) -> None:
|
def test_empty_image(self, size: tuple[int, int]) -> None:
|
||||||
Image.new("RGB", size)
|
Image.new("RGB", size)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageDraw, ImageFont, _util, features
|
from PIL import Image, ImageDraw, ImageFont, _util, features
|
||||||
|
|
||||||
from .helper import assert_image_equal_tofile
|
from .helper import assert_image_equal_tofile, timeout_unless_slower_valgrind
|
||||||
|
|
||||||
fonts = [ImageFont.load_default_imagefont()]
|
fonts = [ImageFont.load_default_imagefont()]
|
||||||
if not features.check_module("freetype2"):
|
if not features.check_module("freetype2"):
|
||||||
|
@ -72,7 +72,7 @@ def test_decompression_bomb() -> None:
|
||||||
font.getmask("A" * 1_000_000)
|
font.getmask("A" * 1_000_000)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.timeout(4)
|
@timeout_unless_slower_valgrind(4)
|
||||||
def test_oom() -> None:
|
def test_oom() -> None:
|
||||||
glyph = struct.pack(
|
glyph = struct.pack(
|
||||||
">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767
|
">hhhhhhhhhh", 1, 0, -32767, -32767, 32767, 32767, -32767, -32767, 32767, 32767
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any # undone
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -10,30 +10,73 @@ from .helper import (
|
||||||
assert_deep_equal,
|
assert_deep_equal,
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
hopper,
|
hopper,
|
||||||
|
is_big_endian,
|
||||||
)
|
)
|
||||||
|
|
||||||
pyarrow = pytest.importorskip("pyarrow", reason="PyArrow not installed")
|
TYPE_CHECKING = False
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import pyarrow
|
||||||
|
else:
|
||||||
|
pyarrow = pytest.importorskip("pyarrow", reason="PyArrow not installed")
|
||||||
|
|
||||||
TEST_IMAGE_SIZE = (10, 10)
|
TEST_IMAGE_SIZE = (10, 10)
|
||||||
|
|
||||||
|
|
||||||
def _test_img_equals_pyarray(
|
def _test_img_equals_pyarray(
|
||||||
img: Image.Image, arr: Any, mask: list[int] | None
|
img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
|
||||||
) -> None:
|
) -> None:
|
||||||
assert img.height * img.width == len(arr)
|
assert img.height * img.width * elts_per_pixel == len(arr)
|
||||||
px = img.load()
|
px = img.load()
|
||||||
assert px is not None
|
assert px is not None
|
||||||
|
if elts_per_pixel > 1 and mask is None:
|
||||||
|
# have to do element-wise comparison when we're comparing
|
||||||
|
# flattened r,g,b,a to a pixel.
|
||||||
|
mask = list(range(elts_per_pixel))
|
||||||
for x in range(0, img.size[0], int(img.size[0] / 10)):
|
for x in range(0, img.size[0], int(img.size[0] / 10)):
|
||||||
for y in range(0, img.size[1], int(img.size[1] / 10)):
|
for y in range(0, img.size[1], int(img.size[1] / 10)):
|
||||||
if mask:
|
if mask:
|
||||||
for ix, elt in enumerate(mask):
|
|
||||||
pixel = px[x, y]
|
pixel = px[x, y]
|
||||||
assert isinstance(pixel, tuple)
|
assert isinstance(pixel, tuple)
|
||||||
|
for ix, elt in enumerate(mask):
|
||||||
|
if elts_per_pixel == 1:
|
||||||
assert pixel[ix] == arr[y * img.width + x].as_py()[elt]
|
assert pixel[ix] == arr[y * img.width + x].as_py()[elt]
|
||||||
|
else:
|
||||||
|
assert (
|
||||||
|
pixel[ix]
|
||||||
|
== arr[(y * img.width + x) * elts_per_pixel + elt].as_py()
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert_deep_equal(px[x, y], arr[y * img.width + x].as_py())
|
assert_deep_equal(px[x, y], arr[y * img.width + x].as_py())
|
||||||
|
|
||||||
|
|
||||||
|
def _test_img_equals_int32_pyarray(
|
||||||
|
img: Image.Image, arr: Any, mask: list[int] | None, elts_per_pixel: int = 1
|
||||||
|
) -> None:
|
||||||
|
assert img.height * img.width * elts_per_pixel == len(arr)
|
||||||
|
px = img.load()
|
||||||
|
assert px is not None
|
||||||
|
if mask is None:
|
||||||
|
# have to do element-wise comparison when we're comparing
|
||||||
|
# flattened rgba in an uint32 to a pixel.
|
||||||
|
mask = list(range(elts_per_pixel))
|
||||||
|
for x in range(0, img.size[0], int(img.size[0] / 10)):
|
||||||
|
for y in range(0, img.size[1], int(img.size[1] / 10)):
|
||||||
|
pixel = px[x, y]
|
||||||
|
assert isinstance(pixel, tuple)
|
||||||
|
arr_pixel_int = arr[y * img.width + x].as_py()
|
||||||
|
arr_pixel_tuple = (
|
||||||
|
arr_pixel_int % 256,
|
||||||
|
(arr_pixel_int // 256) % 256,
|
||||||
|
(arr_pixel_int // 256**2) % 256,
|
||||||
|
(arr_pixel_int // 256**3),
|
||||||
|
)
|
||||||
|
if is_big_endian():
|
||||||
|
arr_pixel_tuple = arr_pixel_tuple[::-1]
|
||||||
|
|
||||||
|
for ix, elt in enumerate(mask):
|
||||||
|
assert pixel[ix] == arr_pixel_tuple[elt]
|
||||||
|
|
||||||
|
|
||||||
# really hard to get a non-nullable list type
|
# really hard to get a non-nullable list type
|
||||||
fl_uint8_4_type = pyarrow.field(
|
fl_uint8_4_type = pyarrow.field(
|
||||||
"_", pyarrow.list_(pyarrow.field("_", pyarrow.uint8()).with_nullable(False), 4)
|
"_", pyarrow.list_(pyarrow.field("_", pyarrow.uint8()).with_nullable(False), 4)
|
||||||
|
@ -55,14 +98,14 @@ fl_uint8_4_type = pyarrow.field(
|
||||||
("HSV", fl_uint8_4_type, [0, 1, 2]),
|
("HSV", fl_uint8_4_type, [0, 1, 2]),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
def test_to_array(mode: str, dtype: Any, mask: list[int] | None) -> None:
|
def test_to_array(mode: str, dtype: pyarrow.DataType, mask: list[int] | None) -> None:
|
||||||
img = hopper(mode)
|
img = hopper(mode)
|
||||||
|
|
||||||
# Resize to non-square
|
# Resize to non-square
|
||||||
img = img.crop((3, 0, 124, 127))
|
img = img.crop((3, 0, 124, 127))
|
||||||
assert img.size == (121, 127)
|
assert img.size == (121, 127)
|
||||||
|
|
||||||
arr = pyarrow.array(img)
|
arr = pyarrow.array(img) # type: ignore[call-overload]
|
||||||
_test_img_equals_pyarray(img, arr, mask)
|
_test_img_equals_pyarray(img, arr, mask)
|
||||||
assert arr.type == dtype
|
assert arr.type == dtype
|
||||||
|
|
||||||
|
@ -79,8 +122,8 @@ def test_lifetime() -> None:
|
||||||
|
|
||||||
img = hopper("L")
|
img = hopper("L")
|
||||||
|
|
||||||
arr_1 = pyarrow.array(img)
|
arr_1 = pyarrow.array(img) # type: ignore[call-overload]
|
||||||
arr_2 = pyarrow.array(img)
|
arr_2 = pyarrow.array(img) # type: ignore[call-overload]
|
||||||
|
|
||||||
del img
|
del img
|
||||||
|
|
||||||
|
@ -97,8 +140,8 @@ def test_lifetime2() -> None:
|
||||||
|
|
||||||
img = hopper("L")
|
img = hopper("L")
|
||||||
|
|
||||||
arr_1 = pyarrow.array(img)
|
arr_1 = pyarrow.array(img) # type: ignore[call-overload]
|
||||||
arr_2 = pyarrow.array(img)
|
arr_2 = pyarrow.array(img) # type: ignore[call-overload]
|
||||||
|
|
||||||
assert arr_1.sum().as_py() > 0
|
assert arr_1.sum().as_py() > 0
|
||||||
del arr_1
|
del arr_1
|
||||||
|
@ -110,3 +153,94 @@ def test_lifetime2() -> None:
|
||||||
px = img2.load()
|
px = img2.load()
|
||||||
assert px # make mypy happy
|
assert px # make mypy happy
|
||||||
assert isinstance(px[0, 0], int)
|
assert isinstance(px[0, 0], int)
|
||||||
|
|
||||||
|
|
||||||
|
class DataShape(NamedTuple):
|
||||||
|
dtype: pyarrow.DataType
|
||||||
|
# Strictly speaking, elt should be a pixel or pixel component, so
|
||||||
|
# list[uint8][4], float, int, uint32, uint8, etc. But more
|
||||||
|
# correctly, it should be exactly the dtype from the line above.
|
||||||
|
elt: Any
|
||||||
|
elts_per_pixel: int
|
||||||
|
|
||||||
|
|
||||||
|
UINT_ARR = DataShape(
|
||||||
|
dtype=fl_uint8_4_type,
|
||||||
|
elt=[1, 2, 3, 4], # array of 4 uint8 per pixel
|
||||||
|
elts_per_pixel=1, # only one array per pixel
|
||||||
|
)
|
||||||
|
|
||||||
|
UINT = DataShape(
|
||||||
|
dtype=pyarrow.uint8(),
|
||||||
|
elt=3, # one uint8,
|
||||||
|
elts_per_pixel=4, # but repeated 4x per pixel
|
||||||
|
)
|
||||||
|
|
||||||
|
UINT32 = DataShape(
|
||||||
|
dtype=pyarrow.uint32(),
|
||||||
|
elt=0xABCDEF45, # one packed int, doesn't fit in a int32 > 0x80000000
|
||||||
|
elts_per_pixel=1, # one per pixel
|
||||||
|
)
|
||||||
|
|
||||||
|
INT32 = DataShape(
|
||||||
|
dtype=pyarrow.uint32(),
|
||||||
|
elt=0x12CDEF45, # one packed int
|
||||||
|
elts_per_pixel=1, # one per pixel
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mode, data_tp, mask",
|
||||||
|
(
|
||||||
|
("L", DataShape(pyarrow.uint8(), 3, 1), None),
|
||||||
|
("I", DataShape(pyarrow.int32(), 1 << 24, 1), None),
|
||||||
|
("F", DataShape(pyarrow.float32(), 3.14159, 1), None),
|
||||||
|
("LA", UINT_ARR, [0, 3]),
|
||||||
|
("LA", UINT, [0, 3]),
|
||||||
|
("RGB", UINT_ARR, [0, 1, 2]),
|
||||||
|
("RGBA", UINT_ARR, None),
|
||||||
|
("CMYK", UINT_ARR, None),
|
||||||
|
("YCbCr", UINT_ARR, [0, 1, 2]),
|
||||||
|
("HSV", UINT_ARR, [0, 1, 2]),
|
||||||
|
("RGB", UINT, [0, 1, 2]),
|
||||||
|
("RGBA", UINT, None),
|
||||||
|
("CMYK", UINT, None),
|
||||||
|
("YCbCr", UINT, [0, 1, 2]),
|
||||||
|
("HSV", UINT, [0, 1, 2]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_fromarray(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
|
||||||
|
(dtype, elt, elts_per_pixel) = data_tp
|
||||||
|
|
||||||
|
ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
|
||||||
|
arr = pyarrow.array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
|
||||||
|
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
|
||||||
|
|
||||||
|
_test_img_equals_pyarray(img, arr, mask, elts_per_pixel)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mode, data_tp, mask",
|
||||||
|
(
|
||||||
|
("LA", UINT32, [0, 3]),
|
||||||
|
("RGB", UINT32, [0, 1, 2]),
|
||||||
|
("RGBA", UINT32, None),
|
||||||
|
("CMYK", UINT32, None),
|
||||||
|
("YCbCr", UINT32, [0, 1, 2]),
|
||||||
|
("HSV", UINT32, [0, 1, 2]),
|
||||||
|
("LA", INT32, [0, 3]),
|
||||||
|
("RGB", INT32, [0, 1, 2]),
|
||||||
|
("RGBA", INT32, None),
|
||||||
|
("CMYK", INT32, None),
|
||||||
|
("YCbCr", INT32, [0, 1, 2]),
|
||||||
|
("HSV", INT32, [0, 1, 2]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_from_int32array(mode: str, data_tp: DataShape, mask: list[int] | None) -> None:
|
||||||
|
(dtype, elt, elts_per_pixel) = data_tp
|
||||||
|
|
||||||
|
ct_pixels = TEST_IMAGE_SIZE[0] * TEST_IMAGE_SIZE[1]
|
||||||
|
arr = pyarrow.array([elt] * (ct_pixels * elts_per_pixel), type=dtype)
|
||||||
|
img = Image.fromarrow(arr, mode, TEST_IMAGE_SIZE)
|
||||||
|
|
||||||
|
_test_img_equals_int32_pyarray(img, arr, mask, elts_per_pixel)
|
||||||
|
|
11
depends/docker-test-valgrind-memory.sh
Executable file
11
depends/docker-test-valgrind-memory.sh
Executable file
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
## Run this as the test script in the Docker valgrind image.
|
||||||
|
## Note -- can be included directly into the Docker image,
|
||||||
|
## but requires the current python.supp.
|
||||||
|
|
||||||
|
source /vpy3/bin/activate
|
||||||
|
cd /Pillow
|
||||||
|
make clean
|
||||||
|
make install
|
||||||
|
make valgrind-leak
|
|
@ -194,9 +194,9 @@ Many of Pillow's features require external libraries:
|
||||||
|
|
||||||
pacman -S \
|
pacman -S \
|
||||||
mingw-w64-x86_64-gcc \
|
mingw-w64-x86_64-gcc \
|
||||||
mingw-w64-x86_64-python3 \
|
mingw-w64-x86_64-python \
|
||||||
mingw-w64-x86_64-python3-pip \
|
mingw-w64-x86_64-python-pip \
|
||||||
mingw-w64-x86_64-python3-setuptools
|
mingw-w64-x86_64-python-setuptools
|
||||||
|
|
||||||
Prerequisites are installed on **MSYS2 MinGW 64-bit** with::
|
Prerequisites are installed on **MSYS2 MinGW 64-bit** with::
|
||||||
|
|
||||||
|
|
|
@ -42,15 +42,17 @@ These platforms are built and tested for every change.
|
||||||
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
|
| macOS 14 Sonoma | 3.10, 3.11, 3.12, 3.13, | arm64 |
|
||||||
| | PyPy3 | |
|
| | PyPy3 | |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Ubuntu Linux 22.04 LTS (Jammy) | 3.9, 3.10, 3.11, | x86-64 |
|
| Ubuntu Linux 22.04 LTS (Jammy) | 3.10 | x86-64 |
|
||||||
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
| Ubuntu Linux 24.04 LTS (Noble) | 3.9, 3.10, 3.11, | x86-64 |
|
||||||
| | 3.12, 3.13, PyPy3 | |
|
| | 3.12, 3.13, PyPy3 | |
|
||||||
|
| +----------------------------+---------------------+
|
||||||
|
| | 3.12 | arm64v8, ppc64le, |
|
||||||
|
| | | s390x |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Ubuntu Linux 24.04 LTS (Noble) | 3.12 | x86-64, arm64v8, |
|
| Windows Server 2022 | 3.9 | x86 |
|
||||||
| | | ppc64le, s390x |
|
| +----------------------------+---------------------+
|
||||||
+----------------------------------+----------------------------+---------------------+
|
| | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
||||||
| Windows Server 2019 | 3.9 | x86 |
|
|
||||||
+----------------------------------+----------------------------+---------------------+
|
|
||||||
| Windows Server 2022 | 3.10, 3.11, 3.12, 3.13, | x86-64 |
|
|
||||||
| | PyPy3 | |
|
| | PyPy3 | |
|
||||||
| +----------------------------+---------------------+
|
| +----------------------------+---------------------+
|
||||||
| | 3.12 (MinGW) | x86-64 |
|
| | 3.12 (MinGW) | x86-64 |
|
||||||
|
|
|
@ -70,6 +70,7 @@ optional-dependencies.tests = [
|
||||||
"pytest",
|
"pytest",
|
||||||
"pytest-cov",
|
"pytest-cov",
|
||||||
"pytest-timeout",
|
"pytest-timeout",
|
||||||
|
"pytest-xdist",
|
||||||
"trove-classifiers>=2024.10.12",
|
"trove-classifiers>=2024.10.12",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
A Pillow loader for .dds files (S3TC-compressed aka DXTC)
|
A Pillow plugin for .dds files (S3TC-compressed aka DXTC)
|
||||||
Jerome Leclanche <jerome@leclan.ch>
|
Jerome Leclanche <jerome@leclan.ch>
|
||||||
|
|
||||||
Documentation:
|
Documentation:
|
||||||
|
|
|
@ -767,18 +767,20 @@ class Image:
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
This method returns the raw image data from the internal
|
This method returns raw image data derived from Pillow's internal
|
||||||
storage. For compressed image data (e.g. PNG, JPEG) use
|
storage. For compressed image data (e.g. PNG, JPEG) use
|
||||||
:meth:`~.save`, with a BytesIO parameter for in-memory
|
:meth:`~.save`, with a BytesIO parameter for in-memory data.
|
||||||
data.
|
|
||||||
|
|
||||||
:param encoder_name: What encoder to use. The default is to
|
:param encoder_name: What encoder to use.
|
||||||
use the standard "raw" encoder.
|
|
||||||
|
|
||||||
A list of C encoders can be seen under
|
The default is to use the standard "raw" encoder.
|
||||||
codecs section of the function array in
|
To see how this packs pixel data into the returned
|
||||||
:file:`_imaging.c`. Python encoders are
|
bytes, see :file:`libImaging/Pack.c`.
|
||||||
registered within the relevant plugins.
|
|
||||||
|
A list of C encoders can be seen under codecs
|
||||||
|
section of the function array in
|
||||||
|
:file:`_imaging.c`. Python encoders are registered
|
||||||
|
within the relevant plugins.
|
||||||
:param args: Extra arguments to the encoder.
|
:param args: Extra arguments to the encoder.
|
||||||
:returns: A :py:class:`bytes` object.
|
:returns: A :py:class:`bytes` object.
|
||||||
"""
|
"""
|
||||||
|
@ -800,7 +802,9 @@ class Image:
|
||||||
e = _getencoder(self.mode, encoder_name, encoder_args)
|
e = _getencoder(self.mode, encoder_name, encoder_args)
|
||||||
e.setimage(self.im)
|
e.setimage(self.im)
|
||||||
|
|
||||||
bufsize = max(65536, self.size[0] * 4) # see RawEncode.c
|
from . import ImageFile
|
||||||
|
|
||||||
|
bufsize = max(ImageFile.MAXBLOCK, self.size[0] * 4) # see RawEncode.c
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
while True:
|
while True:
|
||||||
|
|
|
@ -33,11 +33,7 @@ class BitStream:
|
||||||
|
|
||||||
def peek(self, bits: int) -> int:
|
def peek(self, bits: int) -> int:
|
||||||
while self.bits < bits:
|
while self.bits < bits:
|
||||||
c = self.next()
|
self.bitbuffer = (self.bitbuffer << 8) + self.next()
|
||||||
if c < 0:
|
|
||||||
self.bits = 0
|
|
||||||
continue
|
|
||||||
self.bitbuffer = (self.bitbuffer << 8) + c
|
|
||||||
self.bits += 8
|
self.bits += 8
|
||||||
return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1
|
return self.bitbuffer >> (self.bits - bits) & (1 << bits) - 1
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
||||||
def _open(self) -> None:
|
def _open(self) -> None:
|
||||||
# check placable header
|
# check placable header
|
||||||
s = self.fp.read(80)
|
s = self.fp.read(44)
|
||||||
|
|
||||||
if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"):
|
if s.startswith(b"\xd7\xcd\xc6\x9a\x00\x00"):
|
||||||
# placeable windows metafile
|
# placeable windows metafile
|
||||||
|
|
|
@ -308,8 +308,8 @@ _new_arrow(PyObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImagingBorrowArrow is responsible for retaining the array_capsule
|
// ImagingBorrowArrow is responsible for retaining the array_capsule
|
||||||
ret =
|
ret = PyImagingNew(
|
||||||
PyImagingNew(ImagingNewArrow(mode, xsize, ysize, schema_capsule, array_capsule)
|
ImagingNewArrow(mode, xsize, ysize, schema_capsule, array_capsule)
|
||||||
);
|
);
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
return ImagingError_ValueError("Invalid Arrow array mode or size mismatch");
|
return ImagingError_ValueError("Invalid Arrow array mode or size mismatch");
|
||||||
|
@ -1665,7 +1665,8 @@ _putdata(ImagingObject *self, PyObject *args) {
|
||||||
int bigendian = 0;
|
int bigendian = 0;
|
||||||
if (image->type == IMAGING_TYPE_SPECIAL) {
|
if (image->type == IMAGING_TYPE_SPECIAL) {
|
||||||
// I;16*
|
// I;16*
|
||||||
if (strcmp(image->mode, "I;16B") == 0
|
if (
|
||||||
|
strcmp(image->mode, "I;16B") == 0
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(image->mode, "I;16N") == 0
|
|| strcmp(image->mode, "I;16N") == 0
|
||||||
#endif
|
#endif
|
||||||
|
@ -2226,6 +2227,7 @@ _unsharp_mask(ImagingObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ImagingUnsharpMask(imOut, imIn, radius, percent, threshold)) {
|
if (!ImagingUnsharpMask(imOut, imIn, radius, percent, threshold)) {
|
||||||
|
ImagingDelete(imOut);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -275,6 +275,7 @@ text_layout_raqm(
|
||||||
if (!text || !size) {
|
if (!text || !size) {
|
||||||
/* return 0 and clean up, no glyphs==no size,
|
/* return 0 and clean up, no glyphs==no size,
|
||||||
and raqm fails with empty strings */
|
and raqm fails with empty strings */
|
||||||
|
PyMem_Free(text);
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
set_text = raqm_set_text(rq, text, size);
|
set_text = raqm_set_text(rq, text, size);
|
||||||
|
@ -425,6 +426,7 @@ text_layout_fallback(
|
||||||
"setting text direction, language or font features is not supported "
|
"setting text direction, language or font features is not supported "
|
||||||
"without libraqm"
|
"without libraqm"
|
||||||
);
|
);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PyUnicode_Check(string)) {
|
if (PyUnicode_Check(string)) {
|
||||||
|
|
|
@ -641,6 +641,10 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
||||||
ImagingSectionLeave(&cookie);
|
ImagingSectionLeave(&cookie);
|
||||||
|
|
||||||
WebPPictureFree(&pic);
|
WebPPictureFree(&pic);
|
||||||
|
|
||||||
|
output = writer.mem;
|
||||||
|
ret_size = writer.size;
|
||||||
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
int error_code = (&pic)->error_code;
|
int error_code = (&pic)->error_code;
|
||||||
char message[50] = "";
|
char message[50] = "";
|
||||||
|
@ -652,10 +656,9 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
PyErr_Format(PyExc_ValueError, "encoding error %d%s", error_code, message);
|
PyErr_Format(PyExc_ValueError, "encoding error %d%s", error_code, message);
|
||||||
|
free(output);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
output = writer.mem;
|
|
||||||
ret_size = writer.size;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
/* I want to truncate the *_size items that get passed into WebP
|
/* I want to truncate the *_size items that get passed into WebP
|
||||||
|
|
|
@ -327,11 +327,11 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) {
|
||||||
// added in Windows 10 (1607)
|
// added in Windows 10 (1607)
|
||||||
// loaded dynamically to avoid link errors
|
// loaded dynamically to avoid link errors
|
||||||
user32 = LoadLibraryA("User32.dll");
|
user32 = LoadLibraryA("User32.dll");
|
||||||
SetThreadDpiAwarenessContext_function = (Func_SetThreadDpiAwarenessContext
|
SetThreadDpiAwarenessContext_function = (Func_SetThreadDpiAwarenessContext)
|
||||||
)GetProcAddress(user32, "SetThreadDpiAwarenessContext");
|
GetProcAddress(user32, "SetThreadDpiAwarenessContext");
|
||||||
if (SetThreadDpiAwarenessContext_function != NULL) {
|
if (SetThreadDpiAwarenessContext_function != NULL) {
|
||||||
GetWindowDpiAwarenessContext_function = (Func_GetWindowDpiAwarenessContext
|
GetWindowDpiAwarenessContext_function = (Func_GetWindowDpiAwarenessContext)
|
||||||
)GetProcAddress(user32, "GetWindowDpiAwarenessContext");
|
GetProcAddress(user32, "GetWindowDpiAwarenessContext");
|
||||||
if (screens == -1 && GetWindowDpiAwarenessContext_function != NULL) {
|
if (screens == -1 && GetWindowDpiAwarenessContext_function != NULL) {
|
||||||
dpiAwareness = GetWindowDpiAwarenessContext_function(wnd);
|
dpiAwareness = GetWindowDpiAwarenessContext_function(wnd);
|
||||||
}
|
}
|
||||||
|
|
|
@ -703,6 +703,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
encoder->cleanup = ImagingLibTiffEncodeCleanup;
|
||||||
|
|
||||||
num_core_tags = sizeof(core_tags) / sizeof(int);
|
num_core_tags = sizeof(core_tags) / sizeof(int);
|
||||||
for (pos = 0; pos < tags_size; pos++) {
|
for (pos = 0; pos < tags_size; pos++) {
|
||||||
item = PyList_GetItemRef(tags, pos);
|
item = PyList_GetItemRef(tags, pos);
|
||||||
|
|
|
@ -36,7 +36,10 @@ ReleaseExportedSchema(struct ArrowSchema *array) {
|
||||||
child->release(child);
|
child->release(child);
|
||||||
child->release = NULL;
|
child->release = NULL;
|
||||||
}
|
}
|
||||||
// UNDONE -- should I be releasing the children?
|
free(array->children[i]);
|
||||||
|
}
|
||||||
|
if (array->children) {
|
||||||
|
free(array->children);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release dictionary
|
// Release dictionary
|
||||||
|
@ -117,6 +120,7 @@ export_imaging_schema(Imaging im, struct ArrowSchema *schema) {
|
||||||
retval = export_named_type(schema->children[0], im->arrow_band_format, "pixel");
|
retval = export_named_type(schema->children[0], im->arrow_band_format, "pixel");
|
||||||
if (retval != 0) {
|
if (retval != 0) {
|
||||||
free(schema->children[0]);
|
free(schema->children[0]);
|
||||||
|
free(schema->children);
|
||||||
schema->release(schema);
|
schema->release(schema);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
@ -127,9 +131,7 @@ static void
|
||||||
release_const_array(struct ArrowArray *array) {
|
release_const_array(struct ArrowArray *array) {
|
||||||
Imaging im = (Imaging)array->private_data;
|
Imaging im = (Imaging)array->private_data;
|
||||||
|
|
||||||
if (array->n_children == 0) {
|
|
||||||
ImagingDelete(im);
|
ImagingDelete(im);
|
||||||
}
|
|
||||||
|
|
||||||
// Free the buffers and the buffers array
|
// Free the buffers and the buffers array
|
||||||
if (array->buffers) {
|
if (array->buffers) {
|
||||||
|
|
|
@ -118,8 +118,9 @@ ImagingFillRadialGradient(const char *mode) {
|
||||||
|
|
||||||
for (y = 0; y < 256; y++) {
|
for (y = 0; y < 256; y++) {
|
||||||
for (x = 0; x < 256; x++) {
|
for (x = 0; x < 256; x++) {
|
||||||
d = (int
|
d = (int)sqrt(
|
||||||
)sqrt((double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0);
|
(double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0
|
||||||
|
);
|
||||||
if (d >= 255) {
|
if (d >= 255) {
|
||||||
d = 255;
|
d = 255;
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,7 +155,8 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) {
|
||||||
} else {
|
} else {
|
||||||
int bigendian = 0;
|
int bigendian = 0;
|
||||||
if (im->type == IMAGING_TYPE_SPECIAL) {
|
if (im->type == IMAGING_TYPE_SPECIAL) {
|
||||||
if (strcmp(im->mode, "I;16B") == 0
|
if (
|
||||||
|
strcmp(im->mode, "I;16B") == 0
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(im->mode, "I;16N") == 0
|
|| strcmp(im->mode, "I;16N") == 0
|
||||||
#endif
|
#endif
|
||||||
|
@ -308,7 +309,8 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) {
|
||||||
} else {
|
} else {
|
||||||
int bigendian = 0;
|
int bigendian = 0;
|
||||||
if (im->type == IMAGING_TYPE_SPECIAL) {
|
if (im->type == IMAGING_TYPE_SPECIAL) {
|
||||||
if (strcmp(im->mode, "I;16B") == 0
|
if (
|
||||||
|
strcmp(im->mode, "I;16B") == 0
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(im->mode, "I;16N") == 0
|
|| strcmp(im->mode, "I;16N") == 0
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -207,8 +207,8 @@ j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) {
|
||||||
|
|
||||||
if (params->cp_cinema == OPJ_CINEMA4K_24) {
|
if (params->cp_cinema == OPJ_CINEMA4K_24) {
|
||||||
float max_rate =
|
float max_rate =
|
||||||
((float)(components * im->xsize * im->ysize * 8) / (CINEMA_24_CS_LENGTH * 8)
|
((float)(components * im->xsize * im->ysize * 8) /
|
||||||
);
|
(CINEMA_24_CS_LENGTH * 8));
|
||||||
|
|
||||||
params->POC[0].tile = 1;
|
params->POC[0].tile = 1;
|
||||||
params->POC[0].resno0 = 0;
|
params->POC[0].resno0 = 0;
|
||||||
|
@ -243,8 +243,8 @@ j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) {
|
||||||
params->max_comp_size = COMP_24_CS_MAX_LENGTH;
|
params->max_comp_size = COMP_24_CS_MAX_LENGTH;
|
||||||
} else {
|
} else {
|
||||||
float max_rate =
|
float max_rate =
|
||||||
((float)(components * im->xsize * im->ysize * 8) / (CINEMA_48_CS_LENGTH * 8)
|
((float)(components * im->xsize * im->ysize * 8) /
|
||||||
);
|
(CINEMA_48_CS_LENGTH * 8));
|
||||||
|
|
||||||
for (n = 0; n < params->tcp_numlayers; ++n) {
|
for (n = 0; n < params->tcp_numlayers; ++n) {
|
||||||
rate = 0;
|
rate = 0;
|
||||||
|
|
|
@ -131,6 +131,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
state->errcode = IMAGING_CODEC_CONFIG;
|
state->errcode = IMAGING_CODEC_CONFIG;
|
||||||
|
jpeg_destroy_compress(&context->cinfo);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +162,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
/* Would subsample the green and blue
|
/* Would subsample the green and blue
|
||||||
channels, which doesn't make sense */
|
channels, which doesn't make sense */
|
||||||
state->errcode = IMAGING_CODEC_CONFIG;
|
state->errcode = IMAGING_CODEC_CONFIG;
|
||||||
|
jpeg_destroy_compress(&context->cinfo);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
jpeg_set_colorspace(&context->cinfo, JCS_RGB);
|
jpeg_set_colorspace(&context->cinfo, JCS_RGB);
|
||||||
|
|
|
@ -197,8 +197,9 @@ ImagingPoint(Imaging imIn, const char *mode, const void *table) {
|
||||||
return imOut;
|
return imOut;
|
||||||
|
|
||||||
mode_mismatch:
|
mode_mismatch:
|
||||||
return (Imaging
|
return (Imaging)ImagingError_ValueError(
|
||||||
)ImagingError_ValueError("point operation not supported for this mode");
|
"point operation not supported for this mode"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Imaging
|
Imaging
|
||||||
|
|
|
@ -470,7 +470,8 @@ ImagingResampleHorizontal_16bpc(
|
||||||
double *k;
|
double *k;
|
||||||
|
|
||||||
int bigendian = 0;
|
int bigendian = 0;
|
||||||
if (strcmp(imIn->mode, "I;16N") == 0
|
if (
|
||||||
|
strcmp(imIn->mode, "I;16N") == 0
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(imIn->mode, "I;16B") == 0
|
|| strcmp(imIn->mode, "I;16B") == 0
|
||||||
#endif
|
#endif
|
||||||
|
@ -509,7 +510,8 @@ ImagingResampleVertical_16bpc(
|
||||||
double *k;
|
double *k;
|
||||||
|
|
||||||
int bigendian = 0;
|
int bigendian = 0;
|
||||||
if (strcmp(imIn->mode, "I;16N") == 0
|
if (
|
||||||
|
strcmp(imIn->mode, "I;16N") == 0
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
|| strcmp(imIn->mode, "I;16B") == 0
|
|| strcmp(imIn->mode, "I;16B") == 0
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -602,8 +602,9 @@ ImagingBorrowArrow(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!borrowed_buffer) {
|
if (!borrowed_buffer) {
|
||||||
return (Imaging
|
return (Imaging)ImagingError_ValueError(
|
||||||
)ImagingError_ValueError("Arrow Array, exactly 2 buffers required");
|
"Arrow Array, exactly 2 buffers required"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (y = i = 0; y < im->ysize; y++) {
|
for (y = i = 0; y < im->ysize; y++) {
|
||||||
|
@ -723,6 +724,8 @@ ImagingNewArrow(
|
||||||
int64_t pixels = (int64_t)xsize * (int64_t)ysize;
|
int64_t pixels = (int64_t)xsize * (int64_t)ysize;
|
||||||
|
|
||||||
// fmt:off // don't reformat this
|
// fmt:off // don't reformat this
|
||||||
|
// stored as a single array, one element per pixel, either single band
|
||||||
|
// or multiband, where each pixel is an I32.
|
||||||
if (((strcmp(schema->format, "I") == 0 // int32
|
if (((strcmp(schema->format, "I") == 0 // int32
|
||||||
&& im->pixelsize == 4 // 4xchar* storage
|
&& im->pixelsize == 4 // 4xchar* storage
|
||||||
&& im->bands >= 2) // INT32 into any INT32 Storage mode
|
&& im->bands >= 2) // INT32 into any INT32 Storage mode
|
||||||
|
@ -735,6 +738,7 @@ ImagingNewArrow(
|
||||||
return im;
|
return im;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Stored as [[r,g,b,a],...]
|
||||||
if (strcmp(schema->format, "+w:4") == 0 // 4 up array
|
if (strcmp(schema->format, "+w:4") == 0 // 4 up array
|
||||||
&& im->pixelsize == 4 // storage as 32 bpc
|
&& im->pixelsize == 4 // storage as 32 bpc
|
||||||
&& schema->n_children > 0 // make sure schema is well formed.
|
&& schema->n_children > 0 // make sure schema is well formed.
|
||||||
|
@ -750,6 +754,17 @@ ImagingNewArrow(
|
||||||
return im;
|
return im;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Stored as [r,g,b,a,r,g,b,a,...]
|
||||||
|
if (strcmp(schema->format, "C") == 0 // uint8
|
||||||
|
&& im->pixelsize == 4 // storage as 32 bpc
|
||||||
|
&& schema->n_children == 0 // make sure schema is well formed.
|
||||||
|
&& strcmp(im->arrow_band_format, "C") == 0 // expected format
|
||||||
|
&& 4 * pixels == external_array->length) { // expected length
|
||||||
|
// single flat array, interleaved storage.
|
||||||
|
if (ImagingBorrowArrow(im, external_array, 1, array_capsule)) {
|
||||||
|
return im;
|
||||||
|
}
|
||||||
|
}
|
||||||
// fmt: on
|
// fmt: on
|
||||||
ImagingDelete(im);
|
ImagingDelete(im);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -557,7 +557,8 @@ _decodeStrip(
|
||||||
(tdata_t)state->buffer,
|
(tdata_t)state->buffer,
|
||||||
strip_size
|
strip_size
|
||||||
) == -1) {
|
) == -1) {
|
||||||
TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))
|
TRACE(
|
||||||
|
("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))
|
||||||
);
|
);
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -929,6 +930,27 @@ ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagingLibTiffEncodeCleanup(ImagingCodecState state) {
|
||||||
|
TIFFSTATE *clientstate = (TIFFSTATE *)state->context;
|
||||||
|
TIFF *tiff = clientstate->tiff;
|
||||||
|
|
||||||
|
if (!tiff) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// TIFFClose in libtiff calls tif_closeproc and TIFFCleanup
|
||||||
|
if (clientstate->fp) {
|
||||||
|
// Python will manage the closing of the file rather than libtiff
|
||||||
|
// So only call TIFFCleanup
|
||||||
|
TIFFCleanup(tiff);
|
||||||
|
} else {
|
||||||
|
// When tif_closeproc refers to our custom _tiffCloseProc though,
|
||||||
|
// that is fine, as it does not close the file
|
||||||
|
TIFFClose(tiff);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes) {
|
ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes) {
|
||||||
/* One shot encoder. Encode everything to the tiff in the clientstate.
|
/* One shot encoder. Encode everything to the tiff in the clientstate.
|
||||||
|
@ -1010,16 +1032,6 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
|
||||||
TRACE(("Encode Error, row %d\n", state->y));
|
TRACE(("Encode Error, row %d\n", state->y));
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
|
|
||||||
// TIFFClose in libtiff calls tif_closeproc and TIFFCleanup
|
|
||||||
if (clientstate->fp) {
|
|
||||||
// Python will manage the closing of the file rather than libtiff
|
|
||||||
// So only call TIFFCleanup
|
|
||||||
TIFFCleanup(tiff);
|
|
||||||
} else {
|
|
||||||
// When tif_closeproc refers to our custom _tiffCloseProc though,
|
|
||||||
// that is fine, as it does not close the file
|
|
||||||
TIFFClose(tiff);
|
|
||||||
}
|
|
||||||
if (!clientstate->fp) {
|
if (!clientstate->fp) {
|
||||||
free(clientstate->data);
|
free(clientstate->data);
|
||||||
}
|
}
|
||||||
|
@ -1036,22 +1048,11 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt
|
||||||
TRACE(("Error flushing the tiff"));
|
TRACE(("Error flushing the tiff"));
|
||||||
// likely reason is memory.
|
// likely reason is memory.
|
||||||
state->errcode = IMAGING_CODEC_MEMORY;
|
state->errcode = IMAGING_CODEC_MEMORY;
|
||||||
if (clientstate->fp) {
|
|
||||||
TIFFCleanup(tiff);
|
|
||||||
} else {
|
|
||||||
TIFFClose(tiff);
|
|
||||||
}
|
|
||||||
if (!clientstate->fp) {
|
if (!clientstate->fp) {
|
||||||
free(clientstate->data);
|
free(clientstate->data);
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
TRACE(("Closing \n"));
|
|
||||||
if (clientstate->fp) {
|
|
||||||
TIFFCleanup(tiff);
|
|
||||||
} else {
|
|
||||||
TIFFClose(tiff);
|
|
||||||
}
|
|
||||||
// reset the clientstate metadata to use it to read out the buffer.
|
// reset the clientstate metadata to use it to read out the buffer.
|
||||||
clientstate->loc = 0;
|
clientstate->loc = 0;
|
||||||
clientstate->size = clientstate->eof; // redundant?
|
clientstate->size = clientstate->eof; // redundant?
|
||||||
|
|
|
@ -40,6 +40,8 @@ ImagingLibTiffInit(ImagingCodecState state, int fp, uint32_t offset);
|
||||||
extern int
|
extern int
|
||||||
ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp);
|
ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp);
|
||||||
extern int
|
extern int
|
||||||
|
ImagingLibTiffEncodeCleanup(ImagingCodecState state);
|
||||||
|
extern int
|
||||||
ImagingLibTiffMergeFieldInfo(
|
ImagingLibTiffMergeFieldInfo(
|
||||||
ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length
|
ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length
|
||||||
);
|
);
|
||||||
|
|
|
@ -11,8 +11,7 @@ For more extensive info, see the [Windows build instructions](build.rst).
|
||||||
* Requires Microsoft Visual Studio 2017 or newer with C++ component.
|
* Requires Microsoft Visual Studio 2017 or newer with C++ component.
|
||||||
* Requires NASM for libjpeg-turbo, a required dependency when using this script.
|
* Requires NASM for libjpeg-turbo, a required dependency when using this script.
|
||||||
* Requires CMake 3.15 or newer (available as Visual Studio component).
|
* Requires CMake 3.15 or newer (available as Visual Studio component).
|
||||||
* Tested on Windows Server 2022 with Visual Studio 2022 Enterprise and Windows Server
|
* Tested on Windows Server 2022 with Visual Studio 2022 Enterprise (GitHub Actions).
|
||||||
2019 with Visual Studio 2019 Enterprise (GitHub Actions).
|
|
||||||
|
|
||||||
Here's an example script to build on Windows:
|
Here's an example script to build on Windows:
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user