mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-05-08 01:43:42 +03:00
Merge branch 'main' into libavif-plugin
This commit is contained in:
commit
be0283061a
|
@ -21,7 +21,7 @@ set -e
|
||||||
|
|
||||||
if [[ $(uname) != CYGWIN* ]]; then
|
if [[ $(uname) != CYGWIN* ]]; then
|
||||||
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
|
||||||
ghostscript libjpeg-turbo-progs libopenjp2-7-dev\
|
ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
|
||||||
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
|
||||||
sway wl-clipboard libopenblas-dev nasm
|
sway wl-clipboard libopenblas-dev nasm
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
mypy==1.13.0
|
mypy==1.14.1
|
||||||
IceSpringPySideStubs-PyQt6
|
IceSpringPySideStubs-PyQt6
|
||||||
IceSpringPySideStubs-PySide6
|
IceSpringPySideStubs-PySide6
|
||||||
ipython
|
ipython
|
||||||
|
|
1
.github/workflows/test-docker.yml
vendored
1
.github/workflows/test-docker.yml
vendored
|
@ -44,6 +44,7 @@ jobs:
|
||||||
amazon-2023-amd64,
|
amazon-2023-amd64,
|
||||||
arch,
|
arch,
|
||||||
centos-stream-9-amd64,
|
centos-stream-9-amd64,
|
||||||
|
centos-stream-10-amd64,
|
||||||
debian-12-bookworm-x86,
|
debian-12-bookworm-x86,
|
||||||
debian-12-bookworm-amd64,
|
debian-12-bookworm-amd64,
|
||||||
fedora-40-amd64,
|
fedora-40-amd64,
|
||||||
|
|
18
.github/workflows/wheels-dependencies.sh
vendored
18
.github/workflows/wheels-dependencies.sh
vendored
|
@ -37,7 +37,7 @@ fi
|
||||||
ARCHIVE_SDIR=pillow-depends-main
|
ARCHIVE_SDIR=pillow-depends-main
|
||||||
|
|
||||||
# Package versions for fresh source builds
|
# Package versions for fresh source builds
|
||||||
FREETYPE_VERSION=2.13.2
|
FREETYPE_VERSION=2.13.3
|
||||||
HARFBUZZ_VERSION=10.1.0
|
HARFBUZZ_VERSION=10.1.0
|
||||||
LIBPNG_VERSION=1.6.44
|
LIBPNG_VERSION=1.6.44
|
||||||
JPEGTURBO_VERSION=3.1.0
|
JPEGTURBO_VERSION=3.1.0
|
||||||
|
@ -45,13 +45,9 @@ OPENJPEG_VERSION=2.5.3
|
||||||
XZ_VERSION=5.6.3
|
XZ_VERSION=5.6.3
|
||||||
TIFF_VERSION=4.6.0
|
TIFF_VERSION=4.6.0
|
||||||
LCMS2_VERSION=2.16
|
LCMS2_VERSION=2.16
|
||||||
if [[ -n "$IS_MACOS" ]]; then
|
|
||||||
GIFLIB_VERSION=5.2.2
|
GIFLIB_VERSION=5.2.2
|
||||||
else
|
ZLIB_NG_VERSION=2.2.3
|
||||||
GIFLIB_VERSION=5.2.1
|
LIBWEBP_VERSION=1.5.0
|
||||||
fi
|
|
||||||
ZLIB_NG_VERSION=2.2.2
|
|
||||||
LIBWEBP_VERSION=1.4.0
|
|
||||||
BZIP2_VERSION=1.0.8
|
BZIP2_VERSION=1.0.8
|
||||||
LIBXCB_VERSION=1.17.0
|
LIBXCB_VERSION=1.17.0
|
||||||
BROTLI_VERSION=1.1.0
|
BROTLI_VERSION=1.1.0
|
||||||
|
@ -210,6 +206,14 @@ function build {
|
||||||
CFLAGS="$CFLAGS -O3 -DNDEBUG"
|
CFLAGS="$CFLAGS -O3 -DNDEBUG"
|
||||||
if [[ -n "$IS_MACOS" ]]; then
|
if [[ -n "$IS_MACOS" ]]; then
|
||||||
CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names"
|
CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names"
|
||||||
|
# For giflib 5.2.2
|
||||||
|
elif [ -n "$IS_ALPINE" ]; then
|
||||||
|
apk add imagemagick
|
||||||
|
else
|
||||||
|
if [[ "$MB_ML_VER" == "_2_28" ]]; then
|
||||||
|
yum install -y epel-release
|
||||||
|
fi
|
||||||
|
yum install -y ImageMagick
|
||||||
fi
|
fi
|
||||||
build_libwebp
|
build_libwebp
|
||||||
CFLAGS=$ORIGINAL_CFLAGS
|
CFLAGS=$ORIGINAL_CFLAGS
|
||||||
|
|
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
|
@ -263,8 +263,6 @@ jobs:
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.x"
|
python-version: "3.x"
|
||||||
cache: pip
|
|
||||||
cache-dependency-path: "Makefile"
|
|
||||||
|
|
||||||
- run: make sdist
|
- run: make sdist
|
||||||
|
|
||||||
|
|
|
@ -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.8.1
|
rev: v0.8.4
|
||||||
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: v19.1.4
|
rev: v19.1.5
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
types: [c]
|
types: [c]
|
||||||
|
@ -56,6 +56,11 @@ repos:
|
||||||
- id: check-readthedocs
|
- id: check-readthedocs
|
||||||
- id: check-renovate
|
- id: check-renovate
|
||||||
|
|
||||||
|
- repo: https://github.com/woodruffw/zizmor-pre-commit
|
||||||
|
rev: v0.10.0
|
||||||
|
hooks:
|
||||||
|
- id: zizmor
|
||||||
|
|
||||||
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
- repo: https://github.com/sphinx-contrib/sphinx-lint
|
||||||
rev: v1.0.0
|
rev: v1.0.0
|
||||||
hooks:
|
hooks:
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 533 B |
BIN
Tests/images/jfif_unit_cm.jpg
Normal file
BIN
Tests/images/jfif_unit_cm.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 391 B |
|
@ -388,10 +388,12 @@ class TestColorLut3DFilter:
|
||||||
|
|
||||||
table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16)
|
table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16)
|
||||||
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
||||||
|
assert isinstance(lut.table, numpy.ndarray)
|
||||||
assert lut.table.shape == (table.size,)
|
assert lut.table.shape == (table.size,)
|
||||||
|
|
||||||
table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
|
table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
|
||||||
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
||||||
|
assert isinstance(lut.table, numpy.ndarray)
|
||||||
assert lut.table.shape == (table.size,)
|
assert lut.table.shape == (table.size,)
|
||||||
|
|
||||||
# Check application
|
# Check application
|
||||||
|
|
|
@ -4,7 +4,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import BlpImagePlugin, Image
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -19,6 +19,7 @@ def test_load_blp1() -> None:
|
||||||
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
|
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
|
||||||
|
|
||||||
with Image.open("Tests/images/blp/blp1_jpeg2.blp") as im:
|
with Image.open("Tests/images/blp/blp1_jpeg2.blp") as im:
|
||||||
|
assert im.mode == "RGBA"
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,6 +38,13 @@ def test_load_blp2_dxt1a() -> None:
|
||||||
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
|
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_file() -> None:
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
with pytest.raises(BlpImagePlugin.BLPFormatError):
|
||||||
|
BlpImagePlugin.BlpImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
def test_save(tmp_path: Path) -> None:
|
def test_save(tmp_path: Path) -> None:
|
||||||
f = str(tmp_path / "temp.blp")
|
f = str(tmp_path / "temp.blp")
|
||||||
|
|
||||||
|
|
|
@ -83,4 +83,4 @@ def test_handler(tmp_path: Path) -> None:
|
||||||
im.save(temp_file)
|
im.save(temp_file)
|
||||||
assert handler.saved
|
assert handler.saved
|
||||||
|
|
||||||
BufrStubImagePlugin._handler = None
|
BufrStubImagePlugin.register_handler(None)
|
||||||
|
|
|
@ -4,8 +4,6 @@ import pytest
|
||||||
|
|
||||||
from PIL import ContainerIO, Image
|
from PIL import ContainerIO, Image
|
||||||
|
|
||||||
from .helper import hopper
|
|
||||||
|
|
||||||
TEST_FILE = "Tests/images/dummy.container"
|
TEST_FILE = "Tests/images/dummy.container"
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,15 +13,15 @@ def test_sanity() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_isatty() -> None:
|
def test_isatty() -> None:
|
||||||
with hopper() as im:
|
with open(TEST_FILE, "rb") as fh:
|
||||||
container = ContainerIO.ContainerIO(im, 0, 0)
|
container = ContainerIO.ContainerIO(fh, 0, 0)
|
||||||
|
|
||||||
assert container.isatty() is False
|
assert container.isatty() is False
|
||||||
|
|
||||||
|
|
||||||
def test_seekable() -> None:
|
def test_seekable() -> None:
|
||||||
with hopper() as im:
|
with open(TEST_FILE, "rb") as fh:
|
||||||
container = ContainerIO.ContainerIO(im, 0, 0)
|
container = ContainerIO.ContainerIO(fh, 0, 0)
|
||||||
|
|
||||||
assert container.seekable() is True
|
assert container.seekable() is True
|
||||||
|
|
||||||
|
|
|
@ -83,4 +83,4 @@ def test_handler(tmp_path: Path) -> None:
|
||||||
im.save(temp_file)
|
im.save(temp_file)
|
||||||
assert handler.saved
|
assert handler.saved
|
||||||
|
|
||||||
GribStubImagePlugin._handler = None
|
GribStubImagePlugin.register_handler(None)
|
||||||
|
|
|
@ -85,4 +85,4 @@ def test_handler(tmp_path: Path) -> None:
|
||||||
im.save(temp_file)
|
im.save(temp_file)
|
||||||
assert handler.saved
|
assert handler.saved
|
||||||
|
|
||||||
Hdf5StubImagePlugin._handler = None
|
Hdf5StubImagePlugin.register_handler(None)
|
||||||
|
|
|
@ -181,6 +181,10 @@ class TestFileJpeg:
|
||||||
assert test(100, 200) == (100, 200)
|
assert test(100, 200) == (100, 200)
|
||||||
assert test(0) is None # square pixels
|
assert test(0) is None # square pixels
|
||||||
|
|
||||||
|
def test_dpi_jfif_cm(self):
|
||||||
|
with Image.open("Tests/images/jfif_unit_cm.jpg") as im:
|
||||||
|
assert im.info["dpi"] == (2.54, 5.08)
|
||||||
|
|
||||||
@mark_if_feature_version(
|
@mark_if_feature_version(
|
||||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||||
)
|
)
|
||||||
|
@ -349,7 +353,6 @@ class TestFileJpeg:
|
||||||
assert exif.get_ifd(0x8825) == {}
|
assert exif.get_ifd(0x8825) == {}
|
||||||
|
|
||||||
transposed = ImageOps.exif_transpose(im)
|
transposed = ImageOps.exif_transpose(im)
|
||||||
assert transposed is not None
|
|
||||||
exif = transposed.getexif()
|
exif = transposed.getexif()
|
||||||
assert exif.get_ifd(0x8825) == {}
|
assert exif.get_ifd(0x8825) == {}
|
||||||
|
|
||||||
|
@ -1000,8 +1003,13 @@ class TestFileJpeg:
|
||||||
with Image.open(f) as reloaded:
|
with Image.open(f) as reloaded:
|
||||||
assert reloaded.info["xmp"] == b"XMP test"
|
assert reloaded.info["xmp"] == b"XMP test"
|
||||||
|
|
||||||
im.info["xmp"] = b"1" * 65504
|
# Check that XMP is not saved from image info
|
||||||
im.save(f)
|
reloaded.save(f)
|
||||||
|
|
||||||
|
with Image.open(f) as reloaded:
|
||||||
|
assert "xmp" not in reloaded.info
|
||||||
|
|
||||||
|
im.save(f, xmp=b"1" * 65504)
|
||||||
with Image.open(f) as reloaded:
|
with Image.open(f) as reloaded:
|
||||||
assert reloaded.info["xmp"] == b"1" * 65504
|
assert reloaded.info["xmp"] == b"1" * 65504
|
||||||
|
|
||||||
|
|
|
@ -325,6 +325,18 @@ def test_cmyk() -> None:
|
||||||
assert im.getpixel((0, 0)) == (185, 134, 0, 0)
|
assert im.getpixel((0, 0)) == (185, 134, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
not os.path.exists(EXTRA_DIR), reason="Extra image files not installed"
|
||||||
|
)
|
||||||
|
@skip_unless_feature_version("jpg_2000", "2.5.3")
|
||||||
|
def test_cmyk_save() -> None:
|
||||||
|
with Image.open(f"{EXTRA_DIR}/issue205.jp2") as jp2:
|
||||||
|
assert jp2.mode == "CMYK"
|
||||||
|
|
||||||
|
im = roundtrip(jp2)
|
||||||
|
assert_image_equal(im, jp2)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
|
@pytest.mark.parametrize("ext", (".j2k", ".jp2"))
|
||||||
def test_16bit_monochrome_has_correct_mode(ext: str) -> None:
|
def test_16bit_monochrome_has_correct_mode(ext: str) -> None:
|
||||||
with Image.open("Tests/images/16bit.cropped" + ext) as im:
|
with Image.open("Tests/images/16bit.cropped" + ext) as im:
|
||||||
|
@ -424,7 +436,8 @@ def test_pclr() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_comment() -> None:
|
def test_comment() -> None:
|
||||||
with Image.open("Tests/images/comment.jp2") as im:
|
for path in ("Tests/images/9bit.j2k", "Tests/images/comment.jp2"):
|
||||||
|
with Image.open(path) as im:
|
||||||
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
|
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
|
||||||
|
|
||||||
# Test an image that is truncated partway through a codestream
|
# Test an image that is truncated partway through a codestream
|
||||||
|
|
|
@ -297,3 +297,15 @@ def test_save_all() -> None:
|
||||||
# Test that a single frame image will not be saved as an MPO
|
# Test that a single frame image will not be saved as an MPO
|
||||||
jpg = roundtrip(im, save_all=True)
|
jpg = roundtrip(im, save_all=True)
|
||||||
assert "mp" not in jpg.info
|
assert "mp" not in jpg.info
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_xmp() -> None:
|
||||||
|
im = Image.new("RGB", (1, 1))
|
||||||
|
im2 = Image.new("RGB", (1, 1), "#f00")
|
||||||
|
im2.encoderinfo = {"xmp": b"Second frame"}
|
||||||
|
im_reloaded = roundtrip(im, xmp=b"First frame", save_all=True, append_images=[im2])
|
||||||
|
|
||||||
|
assert im_reloaded.info["xmp"] == b"First frame"
|
||||||
|
|
||||||
|
im_reloaded.seek(1)
|
||||||
|
assert im_reloaded.info["xmp"] == b"Second frame"
|
||||||
|
|
|
@ -772,22 +772,18 @@ class TestFilePng:
|
||||||
im.seek(1)
|
im.seek(1)
|
||||||
|
|
||||||
@pytest.mark.parametrize("buffer", (True, False))
|
@pytest.mark.parametrize("buffer", (True, False))
|
||||||
def test_save_stdout(self, buffer: bool) -> None:
|
def test_save_stdout(self, buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
old_stdout = sys.stdout
|
|
||||||
|
|
||||||
class MyStdOut:
|
class MyStdOut:
|
||||||
buffer = BytesIO()
|
buffer = BytesIO()
|
||||||
|
|
||||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||||
|
|
||||||
sys.stdout = mystdout
|
monkeypatch.setattr(sys, "stdout", mystdout)
|
||||||
|
|
||||||
with Image.open(TEST_PNG_FILE) as im:
|
with Image.open(TEST_PNG_FILE) as im:
|
||||||
im.save(sys.stdout, "PNG")
|
im.save(sys.stdout, "PNG")
|
||||||
|
|
||||||
# Reset stdout
|
|
||||||
sys.stdout = old_stdout
|
|
||||||
|
|
||||||
if isinstance(mystdout, MyStdOut):
|
if isinstance(mystdout, MyStdOut):
|
||||||
mystdout = mystdout.buffer
|
mystdout = mystdout.buffer
|
||||||
with Image.open(mystdout) as reloaded:
|
with Image.open(mystdout) as reloaded:
|
||||||
|
|
|
@ -367,22 +367,18 @@ def test_mimetypes(tmp_path: Path) -> None:
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("buffer", (True, False))
|
@pytest.mark.parametrize("buffer", (True, False))
|
||||||
def test_save_stdout(buffer: bool) -> None:
|
def test_save_stdout(buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
old_stdout = sys.stdout
|
|
||||||
|
|
||||||
class MyStdOut:
|
class MyStdOut:
|
||||||
buffer = BytesIO()
|
buffer = BytesIO()
|
||||||
|
|
||||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||||
|
|
||||||
sys.stdout = mystdout
|
monkeypatch.setattr(sys, "stdout", mystdout)
|
||||||
|
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
im.save(sys.stdout, "PPM")
|
im.save(sys.stdout, "PPM")
|
||||||
|
|
||||||
# Reset stdout
|
|
||||||
sys.stdout = old_stdout
|
|
||||||
|
|
||||||
if isinstance(mystdout, MyStdOut):
|
if isinstance(mystdout, MyStdOut):
|
||||||
mystdout = mystdout.buffer
|
mystdout = mystdout.buffer
|
||||||
with Image.open(mystdout) as reloaded:
|
with Image.open(mystdout) as reloaded:
|
||||||
|
|
|
@ -115,6 +115,13 @@ class TestFileTiff:
|
||||||
outfile = str(tmp_path / "temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
|
im.save(outfile, save_all=True, append_images=[im], tiffinfo=im.tag_v2)
|
||||||
|
|
||||||
|
def test_bigtiff_save(self, tmp_path: Path) -> None:
|
||||||
|
outfile = str(tmp_path / "temp.tif")
|
||||||
|
hopper().save(outfile, big_tiff=True)
|
||||||
|
|
||||||
|
with Image.open(outfile) as im:
|
||||||
|
assert im.tag_v2._bigtiff is True
|
||||||
|
|
||||||
def test_seek_too_large(self) -> None:
|
def test_seek_too_large(self) -> None:
|
||||||
with pytest.raises(ValueError, match="Unable to seek to frame"):
|
with pytest.raises(ValueError, match="Unable to seek to frame"):
|
||||||
Image.open("Tests/images/seek_too_large.tif")
|
Image.open("Tests/images/seek_too_large.tif")
|
||||||
|
|
|
@ -35,6 +35,13 @@ def test_load() -> None:
|
||||||
assert im.load()[0, 0] == (255, 255, 255)
|
assert im.load()[0, 0] == (255, 255, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_zero_inch() -> None:
|
||||||
|
b = BytesIO(b"\xd7\xcd\xc6\x9a\x00\x00" + b"\x00" * 10)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
with Image.open(b):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_register_handler(tmp_path: Path) -> None:
|
def test_register_handler(tmp_path: Path) -> None:
|
||||||
class TestHandler(ImageFile.StubHandler):
|
class TestHandler(ImageFile.StubHandler):
|
||||||
methodCalled = False
|
methodCalled = False
|
||||||
|
|
|
@ -793,6 +793,10 @@ class TestImage:
|
||||||
ifd[36864] = b"0220"
|
ifd[36864] = b"0220"
|
||||||
assert exif.get_ifd(0x8769) == {36864: b"0220"}
|
assert exif.get_ifd(0x8769) == {36864: b"0220"}
|
||||||
|
|
||||||
|
reloaded_exif = Image.Exif()
|
||||||
|
reloaded_exif.load(exif.tobytes())
|
||||||
|
assert reloaded_exif.get_ifd(0x8769) == {36864: b"0220"}
|
||||||
|
|
||||||
@mark_if_feature_version(
|
@mark_if_feature_version(
|
||||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||||
)
|
)
|
||||||
|
|
|
@ -104,20 +104,20 @@ def test_transposed() -> None:
|
||||||
assert im.size == (590, 88)
|
assert im.size == (590, 88)
|
||||||
|
|
||||||
|
|
||||||
def test_load_first_unless_jpeg() -> None:
|
def test_load_first_unless_jpeg(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
# Test that thumbnail() still uses draft() for JPEG
|
# Test that thumbnail() still uses draft() for JPEG
|
||||||
with Image.open("Tests/images/hopper.jpg") as im:
|
with Image.open("Tests/images/hopper.jpg") as im:
|
||||||
draft = im.draft
|
original_draft = im.draft
|
||||||
|
|
||||||
def im_draft(
|
def im_draft(
|
||||||
mode: str, size: tuple[int, int]
|
mode: str | None, size: tuple[int, int] | None
|
||||||
) -> tuple[str, tuple[int, int, float, float]] | None:
|
) -> tuple[str, tuple[int, int, float, float]] | None:
|
||||||
result = draft(mode, size)
|
result = original_draft(mode, size)
|
||||||
assert result is not None
|
assert result is not None
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
im.draft = im_draft
|
monkeypatch.setattr(im, "draft", im_draft)
|
||||||
|
|
||||||
im.thumbnail((64, 64))
|
im.thumbnail((64, 64))
|
||||||
|
|
||||||
|
|
|
@ -1674,6 +1674,9 @@ def test_continuous_horizontal_edges_polygon() -> None:
|
||||||
def test_discontiguous_corners_polygon() -> None:
|
def test_discontiguous_corners_polygon() -> None:
|
||||||
img, draw = create_base_image_draw((84, 68))
|
img, draw = create_base_image_draw((84, 68))
|
||||||
draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK)
|
draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK)
|
||||||
|
draw.polygon(
|
||||||
|
((82, 29), (82, 26), (82, 24), (67, 22), (52, 29), (52, 15), (67, 22)), BLACK
|
||||||
|
)
|
||||||
draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK)
|
draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK)
|
||||||
draw.polygon(
|
draw.polygon(
|
||||||
((38, 66), (5, 49), (77, 49), (47, 66), (82, 63), (82, 47), (1, 47), (1, 63)),
|
((38, 66), (5, 49), (77, 49), (47, 66), (82, 63), (82, 47), (1, 47), (1, 63)),
|
||||||
|
|
|
@ -93,6 +93,19 @@ class TestImageFile:
|
||||||
assert p.image is not None
|
assert p.image is not None
|
||||||
assert (48, 48) == p.image.size
|
assert (48, 48) == p.image.size
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings("ignore:Corrupt EXIF data")
|
||||||
|
def test_incremental_tiff(self) -> None:
|
||||||
|
with ImageFile.Parser() as p:
|
||||||
|
with open("Tests/images/hopper.tif", "rb") as f:
|
||||||
|
p.feed(f.read(1024))
|
||||||
|
|
||||||
|
# Check that insufficient data was given in the first feed
|
||||||
|
assert not p.image
|
||||||
|
|
||||||
|
p.feed(f.read())
|
||||||
|
assert p.image is not None
|
||||||
|
assert (128, 128) == p.image.size
|
||||||
|
|
||||||
@skip_unless_feature("webp")
|
@skip_unless_feature("webp")
|
||||||
def test_incremental_webp(self) -> None:
|
def test_incremental_webp(self) -> None:
|
||||||
with ImageFile.Parser() as p:
|
with ImageFile.Parser() as p:
|
||||||
|
|
|
@ -405,7 +405,6 @@ def test_exif_transpose() -> None:
|
||||||
else:
|
else:
|
||||||
original_exif = im.info["exif"]
|
original_exif = im.info["exif"]
|
||||||
transposed_im = ImageOps.exif_transpose(im)
|
transposed_im = ImageOps.exif_transpose(im)
|
||||||
assert transposed_im is not None
|
|
||||||
assert_image_similar(base_im, transposed_im, 17)
|
assert_image_similar(base_im, transposed_im, 17)
|
||||||
if orientation_im is base_im:
|
if orientation_im is base_im:
|
||||||
assert "exif" not in im.info
|
assert "exif" not in im.info
|
||||||
|
@ -417,7 +416,6 @@ def test_exif_transpose() -> None:
|
||||||
|
|
||||||
# Repeat the operation to test that it does not keep transposing
|
# Repeat the operation to test that it does not keep transposing
|
||||||
transposed_im2 = ImageOps.exif_transpose(transposed_im)
|
transposed_im2 = ImageOps.exif_transpose(transposed_im)
|
||||||
assert transposed_im2 is not None
|
|
||||||
assert_image_equal(transposed_im2, transposed_im)
|
assert_image_equal(transposed_im2, transposed_im)
|
||||||
|
|
||||||
check(base_im)
|
check(base_im)
|
||||||
|
@ -433,7 +431,6 @@ def test_exif_transpose() -> None:
|
||||||
assert im.getexif()[0x0112] == 3
|
assert im.getexif()[0x0112] == 3
|
||||||
|
|
||||||
transposed_im = ImageOps.exif_transpose(im)
|
transposed_im = ImageOps.exif_transpose(im)
|
||||||
assert transposed_im is not None
|
|
||||||
assert 0x0112 not in transposed_im.getexif()
|
assert 0x0112 not in transposed_im.getexif()
|
||||||
|
|
||||||
transposed_im._reload_exif()
|
transposed_im._reload_exif()
|
||||||
|
@ -446,14 +443,12 @@ def test_exif_transpose() -> None:
|
||||||
assert im.getexif()[0x0112] == 3
|
assert im.getexif()[0x0112] == 3
|
||||||
|
|
||||||
transposed_im = ImageOps.exif_transpose(im)
|
transposed_im = ImageOps.exif_transpose(im)
|
||||||
assert transposed_im is not None
|
|
||||||
assert 0x0112 not in transposed_im.getexif()
|
assert 0x0112 not in transposed_im.getexif()
|
||||||
|
|
||||||
# Orientation set directly on Image.Exif
|
# Orientation set directly on Image.Exif
|
||||||
im = hopper()
|
im = hopper()
|
||||||
im.getexif()[0x0112] = 3
|
im.getexif()[0x0112] = 3
|
||||||
transposed_im = ImageOps.exif_transpose(im)
|
transposed_im = ImageOps.exif_transpose(im)
|
||||||
assert transposed_im is not None
|
|
||||||
assert 0x0112 not in transposed_im.getexif()
|
assert 0x0112 not in transposed_im.getexif()
|
||||||
|
|
||||||
|
|
||||||
|
@ -464,7 +459,6 @@ def test_exif_transpose_xml_without_xmp() -> None:
|
||||||
|
|
||||||
del im.info["xmp"]
|
del im.info["xmp"]
|
||||||
transposed_im = ImageOps.exif_transpose(im)
|
transposed_im = ImageOps.exif_transpose(im)
|
||||||
assert transposed_im is not None
|
|
||||||
assert 0x0112 not in transposed_im.getexif()
|
assert 0x0112 not in transposed_im.getexif()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,17 @@ def test_pickle_image(
|
||||||
helper_pickle_file(tmp_path, protocol, test_file, test_mode)
|
helper_pickle_file(tmp_path, protocol, test_file, test_mode)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pickle_jpeg() -> None:
|
||||||
|
# Arrange
|
||||||
|
with Image.open("Tests/images/hopper.jpg") as image:
|
||||||
|
# Act: roundtrip
|
||||||
|
unpickled_image = pickle.loads(pickle.dumps(image))
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
assert len(unpickled_image.layer) == 3
|
||||||
|
assert unpickled_image.layers == 3
|
||||||
|
|
||||||
|
|
||||||
def test_pickle_la_mode_with_palette(tmp_path: Path) -> None:
|
def test_pickle_la_mode_with_palette(tmp_path: Path) -> None:
|
||||||
# Arrange
|
# Arrange
|
||||||
filename = str(tmp_path / "temp.pkl")
|
filename = str(tmp_path / "temp.pkl")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# install webp
|
# install webp
|
||||||
|
|
||||||
archive=libwebp-1.4.0
|
archive=libwebp-1.5.0
|
||||||
|
|
||||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -175,6 +175,14 @@ deprecated and will be removed in Pillow 12 (2025-10-15). They were used for obt
|
||||||
raw pointers to ``ImagingCore`` internals. To interact with C code, you can use
|
raw pointers to ``ImagingCore`` internals. To interact with C code, you can use
|
||||||
``Image.Image.getim()``, which returns a ``Capsule`` object.
|
``Image.Image.getim()``, which returns a ``Capsule`` object.
|
||||||
|
|
||||||
|
ExifTags.IFD.Makernote
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. deprecated:: 11.1.0
|
||||||
|
|
||||||
|
``ExifTags.IFD.Makernote`` has been deprecated. Instead, use
|
||||||
|
``ExifTags.IFD.MakerNote``.
|
||||||
|
|
||||||
Removed features
|
Removed features
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
@ -572,10 +572,19 @@ JPEG 2000
|
||||||
Pillow reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB``,
|
Pillow reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB``,
|
||||||
``RGBA``, or ``YCbCr`` data. When reading, ``YCbCr`` data is converted to
|
``RGBA``, or ``YCbCr`` data. When reading, ``YCbCr`` data is converted to
|
||||||
``RGB`` or ``RGBA`` depending on whether or not there is an alpha channel.
|
``RGB`` or ``RGBA`` depending on whether or not there is an alpha channel.
|
||||||
Beginning with version 8.3.0, Pillow can read (but not write) ``RGB``,
|
|
||||||
``RGBA``, and ``YCbCr`` images with subsampled components. Pillow supports
|
.. versionadded:: 8.3.0
|
||||||
JPEG 2000 raw codestreams (``.j2k`` files), as well as boxed JPEG 2000 files
|
Pillow can read (but not write) ``RGB``, ``RGBA``, and ``YCbCr`` images with
|
||||||
(``.jp2`` or ``.jpx`` files).
|
subsampled components.
|
||||||
|
|
||||||
|
.. versionadded:: 10.4.0
|
||||||
|
Pillow can read ``CMYK`` images with OpenJPEG 2.5.1 and later.
|
||||||
|
|
||||||
|
.. versionadded:: 11.1.0
|
||||||
|
Pillow can write ``CMYK`` images with OpenJPEG 2.5.3 and later.
|
||||||
|
|
||||||
|
Pillow supports JPEG 2000 raw codestreams (``.j2k`` files), as well as boxed
|
||||||
|
JPEG 2000 files (``.jp2`` or ``.jpx`` files).
|
||||||
|
|
||||||
When loading, if you set the ``mode`` on the image prior to the
|
When loading, if you set the ``mode`` on the image prior to the
|
||||||
:py:meth:`~PIL.Image.Image.load` method being invoked, you can ask Pillow to
|
:py:meth:`~PIL.Image.Image.load` method being invoked, you can ask Pillow to
|
||||||
|
@ -1199,6 +1208,11 @@ The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum
|
||||||
|
|
||||||
.. versionadded:: 8.4.0
|
.. versionadded:: 8.4.0
|
||||||
|
|
||||||
|
**big_tiff**
|
||||||
|
If true, the image will be saved as a BigTIFF.
|
||||||
|
|
||||||
|
.. versionadded:: 11.1.0
|
||||||
|
|
||||||
**compression**
|
**compression**
|
||||||
A string containing the desired compression method for the
|
A string containing the desired compression method for the
|
||||||
file. (valid only with libtiff installed) Valid compression
|
file. (valid only with libtiff installed) Valid compression
|
||||||
|
|
|
@ -27,6 +27,8 @@ These platforms are built and tested for every change.
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| CentOS Stream 9 | 3.9 | x86-64 |
|
| CentOS Stream 9 | 3.9 | x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
|
| CentOS Stream 10 | 3.12 | x86-64 |
|
||||||
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Debian 12 Bookworm | 3.11 | x86, x86-64 |
|
| Debian 12 Bookworm | 3.11 | x86, x86-64 |
|
||||||
+----------------------------------+----------------------------+---------------------+
|
+----------------------------------+----------------------------+---------------------+
|
||||||
| Fedora 40 | 3.12 | x86-64 |
|
| Fedora 40 | 3.12 | x86-64 |
|
||||||
|
@ -75,7 +77,7 @@ These platforms have been reported to work at the versions mentioned.
|
||||||
| Operating system | | Tested Python | | Latest tested | | Tested |
|
| Operating system | | Tested Python | | Latest tested | | Tested |
|
||||||
| | | versions | | Pillow version | | processors |
|
| | | versions | | Pillow version | | processors |
|
||||||
+==================================+============================+==================+==============+
|
+==================================+============================+==================+==============+
|
||||||
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.0.0 |arm |
|
| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.1.0 |arm |
|
||||||
| +----------------------------+------------------+ |
|
| +----------------------------+------------------+ |
|
||||||
| | 3.8 | 10.4.0 | |
|
| | 3.8 | 10.4.0 | |
|
||||||
+----------------------------------+----------------------------+------------------+--------------+
|
+----------------------------------+----------------------------+------------------+--------------+
|
||||||
|
|
|
@ -1,40 +1,38 @@
|
||||||
11.1.0
|
11.1.0
|
||||||
------
|
------
|
||||||
|
|
||||||
Security
|
|
||||||
========
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
:cve:`YYYY-XXXXX`: TODO
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
Backwards Incompatible Changes
|
|
||||||
==============================
|
|
||||||
|
|
||||||
TODO
|
|
||||||
^^^^
|
|
||||||
|
|
||||||
Deprecations
|
Deprecations
|
||||||
============
|
============
|
||||||
|
|
||||||
TODO
|
ExifTags.IFD.Makernote
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
``ExifTags.IFD.Makernote`` has been deprecated. Instead, use
|
||||||
|
``ExifTags.IFD.MakerNote``.
|
||||||
|
|
||||||
API Changes
|
API Changes
|
||||||
===========
|
===========
|
||||||
|
|
||||||
TODO
|
Writing XMP bytes to JPEG and MPO
|
||||||
^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
TODO
|
Pillow 11.0.0 added writing XMP data to JPEG and MPO images::
|
||||||
|
|
||||||
|
im.info["xmp"] = b"test"
|
||||||
|
im.save("out.jpg")
|
||||||
|
|
||||||
|
However, this meant that XMP data was automatically kept from an opened image,
|
||||||
|
which is inconsistent with the rest of Pillow's behaviour. This functionality
|
||||||
|
has been removed. To write XMP data, the ``xmp`` argument can still be used for
|
||||||
|
JPEG files::
|
||||||
|
|
||||||
|
im.save("out.jpg", xmp=b"test")
|
||||||
|
|
||||||
|
To save XMP data to the second frame of an MPO image, ``encoderinfo`` can now
|
||||||
|
be used::
|
||||||
|
|
||||||
|
second_im.encoderinfo = {"xmp": b"test"}
|
||||||
|
im.save("out.mpo", save_all=True, append_images=[second_im])
|
||||||
|
|
||||||
API Additions
|
API Additions
|
||||||
=============
|
=============
|
||||||
|
@ -49,9 +47,32 @@ zlib library, and what version of zlib-ng is being used::
|
||||||
features.check_feature("zlib_ng") # True or False
|
features.check_feature("zlib_ng") # True or False
|
||||||
features.version_feature("zlib_ng") # "2.2.2" for example, or None
|
features.version_feature("zlib_ng") # "2.2.2" for example, or None
|
||||||
|
|
||||||
|
Saving TIFF as BigTIFF
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
TIFF images can now be saved as BigTIFF using a ``big_tiff`` argument::
|
||||||
|
|
||||||
|
im.save("out.tiff", big_tiff=True)
|
||||||
|
|
||||||
Other Changes
|
Other Changes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
Reading JPEG 2000 comments
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When opening a JPEG 2000 image, the comment may now be read into
|
||||||
|
:py:attr:`~PIL.Image.Image.info` for J2K images, not just JP2 images.
|
||||||
|
|
||||||
|
Saving JPEG 2000 CMYK images
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
With OpenJPEG 2.5.3 or later, Pillow can now save CMYK images as JPEG 2000 files.
|
||||||
|
|
||||||
|
Minimum C version
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
C99 is now the minimum version of C required to compile Pillow from source.
|
||||||
|
|
||||||
zlib-ng in wheels
|
zlib-ng in wheels
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -345,7 +345,7 @@ class pil_build_ext(build_ext):
|
||||||
for x in ("raqm", "fribidi")
|
for x in ("raqm", "fribidi")
|
||||||
]
|
]
|
||||||
+ [
|
+ [
|
||||||
("disable-platform-guessing", None, "Disable platform guessing on Linux"),
|
("disable-platform-guessing", None, "Disable platform guessing"),
|
||||||
("debug", None, "Debug logging"),
|
("debug", None, "Debug logging"),
|
||||||
]
|
]
|
||||||
+ [("add-imaging-libs=", None, "Add libs to _imaging build")]
|
+ [("add-imaging-libs=", None, "Add libs to _imaging build")]
|
||||||
|
|
|
@ -259,21 +259,36 @@ class BlpImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
def _open(self) -> None:
|
def _open(self) -> None:
|
||||||
self.magic = self.fp.read(4)
|
self.magic = self.fp.read(4)
|
||||||
|
if not _accept(self.magic):
|
||||||
self.fp.seek(5, os.SEEK_CUR)
|
|
||||||
(self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
|
|
||||||
|
|
||||||
self.fp.seek(2, os.SEEK_CUR)
|
|
||||||
self._size = struct.unpack("<II", self.fp.read(8))
|
|
||||||
|
|
||||||
if self.magic in (b"BLP1", b"BLP2"):
|
|
||||||
decoder = self.magic.decode()
|
|
||||||
else:
|
|
||||||
msg = f"Bad BLP magic {repr(self.magic)}"
|
msg = f"Bad BLP magic {repr(self.magic)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
|
|
||||||
self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
compression = struct.unpack("<i", self.fp.read(4))[0]
|
||||||
self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
if self.magic == b"BLP1":
|
||||||
|
alpha = struct.unpack("<I", self.fp.read(4))[0] != 0
|
||||||
|
else:
|
||||||
|
encoding = struct.unpack("<b", self.fp.read(1))[0]
|
||||||
|
alpha = struct.unpack("<b", self.fp.read(1))[0] != 0
|
||||||
|
alpha_encoding = struct.unpack("<b", self.fp.read(1))[0]
|
||||||
|
self.fp.seek(1, os.SEEK_CUR) # mips
|
||||||
|
|
||||||
|
self._size = struct.unpack("<II", self.fp.read(8))
|
||||||
|
|
||||||
|
args: tuple[int, int, bool] | tuple[int, int, bool, int]
|
||||||
|
if self.magic == b"BLP1":
|
||||||
|
encoding = struct.unpack("<i", self.fp.read(4))[0]
|
||||||
|
self.fp.seek(4, os.SEEK_CUR) # subtype
|
||||||
|
|
||||||
|
args = (compression, encoding, alpha)
|
||||||
|
offset = 28
|
||||||
|
else:
|
||||||
|
args = (compression, encoding, alpha, alpha_encoding)
|
||||||
|
offset = 20
|
||||||
|
|
||||||
|
decoder = self.magic.decode()
|
||||||
|
|
||||||
|
self._mode = "RGBA" if alpha else "RGB"
|
||||||
|
self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, offset, args)]
|
||||||
|
|
||||||
|
|
||||||
class _BLPBaseDecoder(ImageFile.PyDecoder):
|
class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
|
@ -281,7 +296,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
|
|
||||||
def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
|
def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
|
||||||
try:
|
try:
|
||||||
self._read_blp_header()
|
self._read_header()
|
||||||
self._load()
|
self._load()
|
||||||
except struct.error as e:
|
except struct.error as e:
|
||||||
msg = "Truncated BLP file"
|
msg = "Truncated BLP file"
|
||||||
|
@ -292,25 +307,9 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
def _load(self) -> None:
|
def _load(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _read_blp_header(self) -> None:
|
def _read_header(self) -> None:
|
||||||
assert self.fd is not None
|
self._offsets = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||||
self.fd.seek(4)
|
self._lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||||
(self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
|
|
||||||
|
|
||||||
(self._blp_encoding,) = struct.unpack("<b", self._safe_read(1))
|
|
||||||
(self._blp_alpha_depth,) = struct.unpack("<b", self._safe_read(1))
|
|
||||||
(self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_read(1))
|
|
||||||
self.fd.seek(1, os.SEEK_CUR) # mips
|
|
||||||
|
|
||||||
self.size = struct.unpack("<II", self._safe_read(8))
|
|
||||||
|
|
||||||
if isinstance(self, BLP1Decoder):
|
|
||||||
# Only present for BLP1
|
|
||||||
(self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
|
|
||||||
self.fd.seek(4, os.SEEK_CUR) # subtype
|
|
||||||
|
|
||||||
self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
|
|
||||||
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
|
||||||
|
|
||||||
def _safe_read(self, length: int) -> bytes:
|
def _safe_read(self, length: int) -> bytes:
|
||||||
assert self.fd is not None
|
assert self.fd is not None
|
||||||
|
@ -326,9 +325,11 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
ret.append((b, g, r, a))
|
ret.append((b, g, r, a))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _read_bgra(self, palette: list[tuple[int, int, int, int]]) -> bytearray:
|
def _read_bgra(
|
||||||
|
self, palette: list[tuple[int, int, int, int]], alpha: bool
|
||||||
|
) -> bytearray:
|
||||||
data = bytearray()
|
data = bytearray()
|
||||||
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
|
_data = BytesIO(self._safe_read(self._lengths[0]))
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
(offset,) = struct.unpack("<B", _data.read(1))
|
(offset,) = struct.unpack("<B", _data.read(1))
|
||||||
|
@ -336,7 +337,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
break
|
break
|
||||||
b, g, r, a = palette[offset]
|
b, g, r, a = palette[offset]
|
||||||
d: tuple[int, ...] = (r, g, b)
|
d: tuple[int, ...] = (r, g, b)
|
||||||
if self._blp_alpha_depth:
|
if alpha:
|
||||||
d += (a,)
|
d += (a,)
|
||||||
data.extend(d)
|
data.extend(d)
|
||||||
return data
|
return data
|
||||||
|
@ -344,19 +345,21 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
|
|
||||||
class BLP1Decoder(_BLPBaseDecoder):
|
class BLP1Decoder(_BLPBaseDecoder):
|
||||||
def _load(self) -> None:
|
def _load(self) -> None:
|
||||||
if self._blp_compression == Format.JPEG:
|
self._compression, self._encoding, alpha = self.args
|
||||||
|
|
||||||
|
if self._compression == Format.JPEG:
|
||||||
self._decode_jpeg_stream()
|
self._decode_jpeg_stream()
|
||||||
|
|
||||||
elif self._blp_compression == 1:
|
elif self._compression == 1:
|
||||||
if self._blp_encoding in (4, 5):
|
if self._encoding in (4, 5):
|
||||||
palette = self._read_palette()
|
palette = self._read_palette()
|
||||||
data = self._read_bgra(palette)
|
data = self._read_bgra(palette, alpha)
|
||||||
self.set_as_raw(data)
|
self.set_as_raw(data)
|
||||||
else:
|
else:
|
||||||
msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}"
|
msg = f"Unsupported BLP encoding {repr(self._encoding)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
else:
|
else:
|
||||||
msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
|
msg = f"Unsupported BLP compression {repr(self._encoding)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
|
|
||||||
def _decode_jpeg_stream(self) -> None:
|
def _decode_jpeg_stream(self) -> None:
|
||||||
|
@ -365,8 +368,8 @@ class BLP1Decoder(_BLPBaseDecoder):
|
||||||
(jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
|
(jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
|
||||||
jpeg_header = self._safe_read(jpeg_header_size)
|
jpeg_header = self._safe_read(jpeg_header_size)
|
||||||
assert self.fd is not None
|
assert self.fd is not None
|
||||||
self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
|
self._safe_read(self._offsets[0] - self.fd.tell()) # What IS this?
|
||||||
data = self._safe_read(self._blp_lengths[0])
|
data = self._safe_read(self._lengths[0])
|
||||||
data = jpeg_header + data
|
data = jpeg_header + data
|
||||||
image = JpegImageFile(BytesIO(data))
|
image = JpegImageFile(BytesIO(data))
|
||||||
Image._decompression_bomb_check(image.size)
|
Image._decompression_bomb_check(image.size)
|
||||||
|
@ -383,47 +386,47 @@ class BLP1Decoder(_BLPBaseDecoder):
|
||||||
|
|
||||||
class BLP2Decoder(_BLPBaseDecoder):
|
class BLP2Decoder(_BLPBaseDecoder):
|
||||||
def _load(self) -> None:
|
def _load(self) -> None:
|
||||||
|
self._compression, self._encoding, alpha, self._alpha_encoding = self.args
|
||||||
|
|
||||||
palette = self._read_palette()
|
palette = self._read_palette()
|
||||||
|
|
||||||
assert self.fd is not None
|
assert self.fd is not None
|
||||||
self.fd.seek(self._blp_offsets[0])
|
self.fd.seek(self._offsets[0])
|
||||||
|
|
||||||
if self._blp_compression == 1:
|
if self._compression == 1:
|
||||||
# Uncompressed or DirectX compression
|
# Uncompressed or DirectX compression
|
||||||
|
|
||||||
if self._blp_encoding == Encoding.UNCOMPRESSED:
|
if self._encoding == Encoding.UNCOMPRESSED:
|
||||||
data = self._read_bgra(palette)
|
data = self._read_bgra(palette, alpha)
|
||||||
|
|
||||||
elif self._blp_encoding == Encoding.DXT:
|
elif self._encoding == Encoding.DXT:
|
||||||
data = bytearray()
|
data = bytearray()
|
||||||
if self._blp_alpha_encoding == AlphaEncoding.DXT1:
|
if self._alpha_encoding == AlphaEncoding.DXT1:
|
||||||
linesize = (self.size[0] + 3) // 4 * 8
|
linesize = (self.state.xsize + 3) // 4 * 8
|
||||||
for yb in range((self.size[1] + 3) // 4):
|
for yb in range((self.state.ysize + 3) // 4):
|
||||||
for d in decode_dxt1(
|
for d in decode_dxt1(self._safe_read(linesize), alpha):
|
||||||
self._safe_read(linesize), alpha=bool(self._blp_alpha_depth)
|
|
||||||
):
|
|
||||||
data += d
|
data += d
|
||||||
|
|
||||||
elif self._blp_alpha_encoding == AlphaEncoding.DXT3:
|
elif self._alpha_encoding == AlphaEncoding.DXT3:
|
||||||
linesize = (self.size[0] + 3) // 4 * 16
|
linesize = (self.state.xsize + 3) // 4 * 16
|
||||||
for yb in range((self.size[1] + 3) // 4):
|
for yb in range((self.state.ysize + 3) // 4):
|
||||||
for d in decode_dxt3(self._safe_read(linesize)):
|
for d in decode_dxt3(self._safe_read(linesize)):
|
||||||
data += d
|
data += d
|
||||||
|
|
||||||
elif self._blp_alpha_encoding == AlphaEncoding.DXT5:
|
elif self._alpha_encoding == AlphaEncoding.DXT5:
|
||||||
linesize = (self.size[0] + 3) // 4 * 16
|
linesize = (self.state.xsize + 3) // 4 * 16
|
||||||
for yb in range((self.size[1] + 3) // 4):
|
for yb in range((self.state.ysize + 3) // 4):
|
||||||
for d in decode_dxt5(self._safe_read(linesize)):
|
for d in decode_dxt5(self._safe_read(linesize)):
|
||||||
data += d
|
data += d
|
||||||
else:
|
else:
|
||||||
msg = f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
|
msg = f"Unsupported alpha encoding {repr(self._alpha_encoding)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
else:
|
else:
|
||||||
msg = f"Unknown BLP encoding {repr(self._blp_encoding)}"
|
msg = f"Unknown BLP encoding {repr(self._encoding)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg = f"Unknown BLP compression {repr(self._blp_compression)}"
|
msg = f"Unknown BLP compression {repr(self._compression)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
|
|
||||||
self.set_as_raw(data)
|
self.set_as_raw(data)
|
||||||
|
@ -472,8 +475,13 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
||||||
assert im.palette is not None
|
assert im.palette is not None
|
||||||
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
|
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
|
||||||
|
|
||||||
|
alpha_depth = 1 if im.palette.mode == "RGBA" else 0
|
||||||
|
if magic == b"BLP1":
|
||||||
|
fp.write(struct.pack("<L", alpha_depth))
|
||||||
|
else:
|
||||||
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
|
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
|
||||||
fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
|
fp.write(struct.pack("<b", alpha_depth))
|
||||||
fp.write(struct.pack("<b", 0)) # alpha encoding
|
fp.write(struct.pack("<b", 0)) # alpha encoding
|
||||||
fp.write(struct.pack("<b", 0)) # mips
|
fp.write(struct.pack("<b", 0)) # mips
|
||||||
fp.write(struct.pack("<II", *im.size))
|
fp.write(struct.pack("<II", *im.size))
|
||||||
|
|
|
@ -560,9 +560,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
+ struct.pack("<4I", *rgba_mask) # dwRGBABitMask
|
+ struct.pack("<4I", *rgba_mask) # dwRGBABitMask
|
||||||
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
|
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
|
||||||
)
|
)
|
||||||
ImageFile._save(
|
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
|
||||||
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _accept(prefix: bytes) -> bool:
|
def _accept(prefix: bytes) -> bool:
|
||||||
|
|
|
@ -454,7 +454,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes, eps: int = 1) -
|
||||||
if hasattr(fp, "flush"):
|
if hasattr(fp, "flush"):
|
||||||
fp.flush()
|
fp.flush()
|
||||||
|
|
||||||
ImageFile._save(im, fp, [ImageFile._Tile("eps", (0, 0) + im.size, 0, None)])
|
ImageFile._save(im, fp, [ImageFile._Tile("eps", (0, 0) + im.size)])
|
||||||
|
|
||||||
fp.write(b"\n%%%%EndBinary\n")
|
fp.write(b"\n%%%%EndBinary\n")
|
||||||
fp.write(b"grestore end\n")
|
fp.write(b"grestore end\n")
|
||||||
|
|
|
@ -303,38 +303,38 @@ TAGS = {
|
||||||
|
|
||||||
|
|
||||||
class GPS(IntEnum):
|
class GPS(IntEnum):
|
||||||
GPSVersionID = 0
|
GPSVersionID = 0x00
|
||||||
GPSLatitudeRef = 1
|
GPSLatitudeRef = 0x01
|
||||||
GPSLatitude = 2
|
GPSLatitude = 0x02
|
||||||
GPSLongitudeRef = 3
|
GPSLongitudeRef = 0x03
|
||||||
GPSLongitude = 4
|
GPSLongitude = 0x04
|
||||||
GPSAltitudeRef = 5
|
GPSAltitudeRef = 0x05
|
||||||
GPSAltitude = 6
|
GPSAltitude = 0x06
|
||||||
GPSTimeStamp = 7
|
GPSTimeStamp = 0x07
|
||||||
GPSSatellites = 8
|
GPSSatellites = 0x08
|
||||||
GPSStatus = 9
|
GPSStatus = 0x09
|
||||||
GPSMeasureMode = 10
|
GPSMeasureMode = 0x0A
|
||||||
GPSDOP = 11
|
GPSDOP = 0x0B
|
||||||
GPSSpeedRef = 12
|
GPSSpeedRef = 0x0C
|
||||||
GPSSpeed = 13
|
GPSSpeed = 0x0D
|
||||||
GPSTrackRef = 14
|
GPSTrackRef = 0x0E
|
||||||
GPSTrack = 15
|
GPSTrack = 0x0F
|
||||||
GPSImgDirectionRef = 16
|
GPSImgDirectionRef = 0x10
|
||||||
GPSImgDirection = 17
|
GPSImgDirection = 0x11
|
||||||
GPSMapDatum = 18
|
GPSMapDatum = 0x12
|
||||||
GPSDestLatitudeRef = 19
|
GPSDestLatitudeRef = 0x13
|
||||||
GPSDestLatitude = 20
|
GPSDestLatitude = 0x14
|
||||||
GPSDestLongitudeRef = 21
|
GPSDestLongitudeRef = 0x15
|
||||||
GPSDestLongitude = 22
|
GPSDestLongitude = 0x16
|
||||||
GPSDestBearingRef = 23
|
GPSDestBearingRef = 0x17
|
||||||
GPSDestBearing = 24
|
GPSDestBearing = 0x18
|
||||||
GPSDestDistanceRef = 25
|
GPSDestDistanceRef = 0x19
|
||||||
GPSDestDistance = 26
|
GPSDestDistance = 0x1A
|
||||||
GPSProcessingMethod = 27
|
GPSProcessingMethod = 0x1B
|
||||||
GPSAreaInformation = 28
|
GPSAreaInformation = 0x1C
|
||||||
GPSDateStamp = 29
|
GPSDateStamp = 0x1D
|
||||||
GPSDifferential = 30
|
GPSDifferential = 0x1E
|
||||||
GPSHPositioningError = 31
|
GPSHPositioningError = 0x1F
|
||||||
|
|
||||||
|
|
||||||
"""Maps EXIF GPS tags to tag names."""
|
"""Maps EXIF GPS tags to tag names."""
|
||||||
|
@ -342,40 +342,41 @@ GPSTAGS = {i.value: i.name for i in GPS}
|
||||||
|
|
||||||
|
|
||||||
class Interop(IntEnum):
|
class Interop(IntEnum):
|
||||||
InteropIndex = 1
|
InteropIndex = 0x0001
|
||||||
InteropVersion = 2
|
InteropVersion = 0x0002
|
||||||
RelatedImageFileFormat = 4096
|
RelatedImageFileFormat = 0x1000
|
||||||
RelatedImageWidth = 4097
|
RelatedImageWidth = 0x1001
|
||||||
RelatedImageHeight = 4098
|
RelatedImageHeight = 0x1002
|
||||||
|
|
||||||
|
|
||||||
class IFD(IntEnum):
|
class IFD(IntEnum):
|
||||||
Exif = 34665
|
Exif = 0x8769
|
||||||
GPSInfo = 34853
|
GPSInfo = 0x8825
|
||||||
Makernote = 37500
|
MakerNote = 0x927C
|
||||||
Interop = 40965
|
Makernote = 0x927C # Deprecated
|
||||||
|
Interop = 0xA005
|
||||||
IFD1 = -1
|
IFD1 = -1
|
||||||
|
|
||||||
|
|
||||||
class LightSource(IntEnum):
|
class LightSource(IntEnum):
|
||||||
Unknown = 0
|
Unknown = 0x00
|
||||||
Daylight = 1
|
Daylight = 0x01
|
||||||
Fluorescent = 2
|
Fluorescent = 0x02
|
||||||
Tungsten = 3
|
Tungsten = 0x03
|
||||||
Flash = 4
|
Flash = 0x04
|
||||||
Fine = 9
|
Fine = 0x09
|
||||||
Cloudy = 10
|
Cloudy = 0x0A
|
||||||
Shade = 11
|
Shade = 0x0B
|
||||||
DaylightFluorescent = 12
|
DaylightFluorescent = 0x0C
|
||||||
DayWhiteFluorescent = 13
|
DayWhiteFluorescent = 0x0D
|
||||||
CoolWhiteFluorescent = 14
|
CoolWhiteFluorescent = 0x0E
|
||||||
WhiteFluorescent = 15
|
WhiteFluorescent = 0x0F
|
||||||
StandardLightA = 17
|
StandardLightA = 0x11
|
||||||
StandardLightB = 18
|
StandardLightB = 0x12
|
||||||
StandardLightC = 19
|
StandardLightC = 0x13
|
||||||
D55 = 20
|
D55 = 0x14
|
||||||
D65 = 21
|
D65 = 0x15
|
||||||
D75 = 22
|
D75 = 0x16
|
||||||
D50 = 23
|
D50 = 0x17
|
||||||
ISO = 24
|
ISO = 0x18
|
||||||
Other = 255
|
Other = 0xFF
|
||||||
|
|
|
@ -159,7 +159,7 @@ class FliImageFile(ImageFile.ImageFile):
|
||||||
framesize = i32(s)
|
framesize = i32(s)
|
||||||
|
|
||||||
self.decodermaxblock = framesize
|
self.decodermaxblock = framesize
|
||||||
self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset, None)]
|
self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset)]
|
||||||
|
|
||||||
self.__offset += framesize
|
self.__offset += framesize
|
||||||
|
|
||||||
|
|
|
@ -170,7 +170,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
||||||
"raw",
|
"raw",
|
||||||
(x, y, x1, y1),
|
(x, y, x1, y1),
|
||||||
i32(s, i) + 28,
|
i32(s, i) + 28,
|
||||||
(self.rawmode,),
|
self.rawmode,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ class FtexImageFile(ImageFile.ImageFile):
|
||||||
self._mode = "RGBA"
|
self._mode = "RGBA"
|
||||||
self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))]
|
self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))]
|
||||||
elif format == Format.UNCOMPRESSED:
|
elif format == Format.UNCOMPRESSED:
|
||||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
|
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, "RGB")]
|
||||||
else:
|
else:
|
||||||
msg = f"Invalid texture compression format: {repr(format)}"
|
msg = f"Invalid texture compression format: {repr(format)}"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
|
@ -76,7 +76,7 @@ class GdImageFile(ImageFile.ImageFile):
|
||||||
"raw",
|
"raw",
|
||||||
(0, 0) + self.size,
|
(0, 0) + self.size,
|
||||||
7 + true_color_offset + 4 + 256 * 4,
|
7 + true_color_offset + 4 + 256 * 4,
|
||||||
("L", 0, 1),
|
"L",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -754,7 +754,7 @@ class Image:
|
||||||
|
|
||||||
def __setstate__(self, state: list[Any]) -> None:
|
def __setstate__(self, state: list[Any]) -> None:
|
||||||
Image.__init__(self)
|
Image.__init__(self)
|
||||||
info, mode, size, palette, data = state
|
info, mode, size, palette, data = state[:5]
|
||||||
self.info = info
|
self.info = info
|
||||||
self._mode = mode
|
self._mode = mode
|
||||||
self._size = size
|
self._size = size
|
||||||
|
@ -1567,7 +1567,7 @@ class Image:
|
||||||
for subifd_offset in subifd_offsets:
|
for subifd_offset in subifd_offsets:
|
||||||
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
|
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
|
||||||
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
|
ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
|
||||||
if ifd1 and ifd1.get(513):
|
if ifd1 and ifd1.get(ExifTags.Base.JpegIFOffset):
|
||||||
assert exif._info is not None
|
assert exif._info is not None
|
||||||
ifds.append((ifd1, exif._info.next))
|
ifds.append((ifd1, exif._info.next))
|
||||||
|
|
||||||
|
@ -1579,11 +1579,11 @@ class Image:
|
||||||
|
|
||||||
fp = self.fp
|
fp = self.fp
|
||||||
if ifd is not None:
|
if ifd is not None:
|
||||||
thumbnail_offset = ifd.get(513)
|
thumbnail_offset = ifd.get(ExifTags.Base.JpegIFOffset)
|
||||||
if thumbnail_offset is not None:
|
if thumbnail_offset is not None:
|
||||||
thumbnail_offset += getattr(self, "_exif_offset", 0)
|
thumbnail_offset += getattr(self, "_exif_offset", 0)
|
||||||
self.fp.seek(thumbnail_offset)
|
self.fp.seek(thumbnail_offset)
|
||||||
data = self.fp.read(ifd.get(514))
|
data = self.fp.read(ifd.get(ExifTags.Base.JpegIFByteCount))
|
||||||
fp = io.BytesIO(data)
|
fp = io.BytesIO(data)
|
||||||
|
|
||||||
with open(fp) as im:
|
with open(fp) as im:
|
||||||
|
@ -2558,7 +2558,7 @@ class Image:
|
||||||
self._ensure_mutable()
|
self._ensure_mutable()
|
||||||
|
|
||||||
save_all = params.pop("save_all", False)
|
save_all = params.pop("save_all", False)
|
||||||
self.encoderinfo = params
|
self.encoderinfo = {**getattr(self, "encoderinfo", {}), **params}
|
||||||
self.encoderconfig: tuple[Any, ...] = ()
|
self.encoderconfig: tuple[Any, ...] = ()
|
||||||
|
|
||||||
preinit()
|
preinit()
|
||||||
|
@ -2605,6 +2605,11 @@ class Image:
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
pass
|
pass
|
||||||
raise
|
raise
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
del self.encoderinfo
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
if open_fp:
|
if open_fp:
|
||||||
fp.close()
|
fp.close()
|
||||||
|
|
||||||
|
@ -3886,7 +3891,7 @@ class Exif(_ExifBase):
|
||||||
gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo)
|
gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo)
|
||||||
print(gps_ifd)
|
print(gps_ifd)
|
||||||
|
|
||||||
Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.Makernote``,
|
Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.MakerNote``,
|
||||||
``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``.
|
``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``.
|
||||||
|
|
||||||
:py:mod:`~PIL.ExifTags` also has enum classes to provide names for data::
|
:py:mod:`~PIL.ExifTags` also has enum classes to provide names for data::
|
||||||
|
@ -4020,6 +4025,9 @@ class Exif(_ExifBase):
|
||||||
|
|
||||||
head = self._get_head()
|
head = self._get_head()
|
||||||
ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
|
ifd = TiffImagePlugin.ImageFileDirectory_v2(ifh=head)
|
||||||
|
for tag, ifd_dict in self._ifds.items():
|
||||||
|
if tag not in self:
|
||||||
|
ifd[tag] = ifd_dict
|
||||||
for tag, value in self.items():
|
for tag, value in self.items():
|
||||||
if tag in [
|
if tag in [
|
||||||
ExifTags.IFD.Exif,
|
ExifTags.IFD.Exif,
|
||||||
|
@ -4049,11 +4057,11 @@ class Exif(_ExifBase):
|
||||||
ifd = self._get_ifd_dict(offset, tag)
|
ifd = self._get_ifd_dict(offset, tag)
|
||||||
if ifd is not None:
|
if ifd is not None:
|
||||||
self._ifds[tag] = ifd
|
self._ifds[tag] = ifd
|
||||||
elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.Makernote]:
|
elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.MakerNote]:
|
||||||
if ExifTags.IFD.Exif not in self._ifds:
|
if ExifTags.IFD.Exif not in self._ifds:
|
||||||
self.get_ifd(ExifTags.IFD.Exif)
|
self.get_ifd(ExifTags.IFD.Exif)
|
||||||
tag_data = self._ifds[ExifTags.IFD.Exif][tag]
|
tag_data = self._ifds[ExifTags.IFD.Exif][tag]
|
||||||
if tag == ExifTags.IFD.Makernote:
|
if tag == ExifTags.IFD.MakerNote:
|
||||||
from .TiffImagePlugin import ImageFileDirectory_v2
|
from .TiffImagePlugin import ImageFileDirectory_v2
|
||||||
|
|
||||||
if tag_data[:8] == b"FUJIFILM":
|
if tag_data[:8] == b"FUJIFILM":
|
||||||
|
@ -4140,7 +4148,7 @@ class Exif(_ExifBase):
|
||||||
ifd = {
|
ifd = {
|
||||||
k: v
|
k: v
|
||||||
for (k, v) in ifd.items()
|
for (k, v) in ifd.items()
|
||||||
if k not in (ExifTags.IFD.Interop, ExifTags.IFD.Makernote)
|
if k not in (ExifTags.IFD.Interop, ExifTags.IFD.MakerNote)
|
||||||
}
|
}
|
||||||
return ifd
|
return ifd
|
||||||
|
|
||||||
|
|
|
@ -98,8 +98,8 @@ def _tilesort(t: _Tile) -> int:
|
||||||
class _Tile(NamedTuple):
|
class _Tile(NamedTuple):
|
||||||
codec_name: str
|
codec_name: str
|
||||||
extents: tuple[int, int, int, int] | None
|
extents: tuple[int, int, int, int] | None
|
||||||
offset: int
|
offset: int = 0
|
||||||
args: tuple[Any, ...] | str | None
|
args: tuple[Any, ...] | str | None = None
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -553,7 +553,7 @@ class Color3DLUT(MultibandFilter):
|
||||||
ch_out = channels or ch_in
|
ch_out = channels or ch_in
|
||||||
size_1d, size_2d, size_3d = self.size
|
size_1d, size_2d, size_3d = self.size
|
||||||
|
|
||||||
table = [0] * (size_1d * size_2d * size_3d * ch_out)
|
table: list[float] = [0] * (size_1d * size_2d * size_3d * ch_out)
|
||||||
idx_in = 0
|
idx_in = 0
|
||||||
idx_out = 0
|
idx_out = 0
|
||||||
for b in range(size_3d):
|
for b in range(size_3d):
|
||||||
|
|
|
@ -104,28 +104,17 @@ def grab(
|
||||||
|
|
||||||
def grabclipboard() -> Image.Image | list[str] | None:
|
def grabclipboard() -> Image.Image | list[str] | None:
|
||||||
if sys.platform == "darwin":
|
if sys.platform == "darwin":
|
||||||
fh, filepath = tempfile.mkstemp(".png")
|
p = subprocess.run(
|
||||||
os.close(fh)
|
["osascript", "-e", "get the clipboard as «class PNGf»"],
|
||||||
commands = [
|
capture_output=True,
|
||||||
'set theFile to (open for access POSIX file "'
|
)
|
||||||
+ filepath
|
if p.returncode != 0:
|
||||||
+ '" with write permission)',
|
return None
|
||||||
"try",
|
|
||||||
" write (the clipboard as «class PNGf») to theFile",
|
|
||||||
"end try",
|
|
||||||
"close access theFile",
|
|
||||||
]
|
|
||||||
script = ["osascript"]
|
|
||||||
for command in commands:
|
|
||||||
script += ["-e", command]
|
|
||||||
subprocess.call(script)
|
|
||||||
|
|
||||||
im = None
|
import binascii
|
||||||
if os.stat(filepath).st_size != 0:
|
|
||||||
im = Image.open(filepath)
|
data = io.BytesIO(binascii.unhexlify(p.stdout[11:-3]))
|
||||||
im.load()
|
return Image.open(data)
|
||||||
os.unlink(filepath)
|
|
||||||
return im
|
|
||||||
elif sys.platform == "win32":
|
elif sys.platform == "win32":
|
||||||
fmt, data = Image.core.grabclipboard_win32()
|
fmt, data = Image.core.grabclipboard_win32()
|
||||||
if fmt == "file": # CF_HDROP
|
if fmt == "file": # CF_HDROP
|
||||||
|
|
|
@ -22,7 +22,7 @@ import functools
|
||||||
import operator
|
import operator
|
||||||
import re
|
import re
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from typing import Protocol, cast
|
from typing import Literal, Protocol, cast, overload
|
||||||
|
|
||||||
from . import ExifTags, Image, ImagePalette
|
from . import ExifTags, Image, ImagePalette
|
||||||
|
|
||||||
|
@ -673,6 +673,16 @@ def solarize(image: Image.Image, threshold: int = 128) -> Image.Image:
|
||||||
return _lut(image, lut)
|
return _lut(image, lut)
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def exif_transpose(image: Image.Image, *, in_place: Literal[True]) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def exif_transpose(
|
||||||
|
image: Image.Image, *, in_place: Literal[False] = False
|
||||||
|
) -> Image.Image: ...
|
||||||
|
|
||||||
|
|
||||||
def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image | None:
|
def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image | None:
|
||||||
"""
|
"""
|
||||||
If an image has an EXIF Orientation tag, other than 1, transpose the image
|
If an image has an EXIF Orientation tag, other than 1, transpose the image
|
||||||
|
@ -698,10 +708,11 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image
|
||||||
8: Image.Transpose.ROTATE_90,
|
8: Image.Transpose.ROTATE_90,
|
||||||
}.get(orientation)
|
}.get(orientation)
|
||||||
if method is not None:
|
if method is not None:
|
||||||
transposed_image = image.transpose(method)
|
|
||||||
if in_place:
|
if in_place:
|
||||||
image.im = transposed_image.im
|
image.im = image.im.transpose(method)
|
||||||
image._size = transposed_image._size
|
image._size = image.im.size
|
||||||
|
else:
|
||||||
|
transposed_image = image.transpose(method)
|
||||||
exif_image = image if in_place else transposed_image
|
exif_image = image if in_place else transposed_image
|
||||||
|
|
||||||
exif = exif_image.getexif()
|
exif = exif_image.getexif()
|
||||||
|
|
|
@ -62,7 +62,7 @@ class ImtImageFile(ImageFile.ImageFile):
|
||||||
"raw",
|
"raw",
|
||||||
(0, 0) + self.size,
|
(0, 0) + self.size,
|
||||||
self.fp.tell() - len(buffer),
|
self.fp.tell() - len(buffer),
|
||||||
(self.mode, 0, 1),
|
self.mode,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -252,6 +252,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
if sig == b"\xff\x4f\xff\x51":
|
if sig == b"\xff\x4f\xff\x51":
|
||||||
self.codec = "j2k"
|
self.codec = "j2k"
|
||||||
self._size, self._mode = _parse_codestream(self.fp)
|
self._size, self._mode = _parse_codestream(self.fp)
|
||||||
|
self._parse_comment()
|
||||||
else:
|
else:
|
||||||
sig = sig + self.fp.read(8)
|
sig = sig + self.fp.read(8)
|
||||||
|
|
||||||
|
@ -262,6 +263,9 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
if dpi is not None:
|
if dpi is not None:
|
||||||
self.info["dpi"] = dpi
|
self.info["dpi"] = dpi
|
||||||
if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"):
|
if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"):
|
||||||
|
hdr = self.fp.read(2)
|
||||||
|
length = _binary.i16be(hdr)
|
||||||
|
self.fp.seek(length - 2, os.SEEK_CUR)
|
||||||
self._parse_comment()
|
self._parse_comment()
|
||||||
else:
|
else:
|
||||||
msg = "not a JPEG 2000 file"
|
msg = "not a JPEG 2000 file"
|
||||||
|
@ -296,10 +300,6 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
||||||
]
|
]
|
||||||
|
|
||||||
def _parse_comment(self) -> None:
|
def _parse_comment(self) -> None:
|
||||||
hdr = self.fp.read(2)
|
|
||||||
length = _binary.i16be(hdr)
|
|
||||||
self.fp.seek(length - 2, os.SEEK_CUR)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
marker = self.fp.read(2)
|
marker = self.fp.read(2)
|
||||||
if not marker:
|
if not marker:
|
||||||
|
|
|
@ -90,6 +90,9 @@ def APP(self: JpegImageFile, marker: int) -> None:
|
||||||
else:
|
else:
|
||||||
if jfif_unit == 1:
|
if jfif_unit == 1:
|
||||||
self.info["dpi"] = jfif_density
|
self.info["dpi"] = jfif_density
|
||||||
|
elif jfif_unit == 2: # cm
|
||||||
|
# 1 dpcm = 2.54 dpi
|
||||||
|
self.info["dpi"] = tuple(d * 2.54 for d in jfif_density)
|
||||||
self.info["jfif_unit"] = jfif_unit
|
self.info["jfif_unit"] = jfif_unit
|
||||||
self.info["jfif_density"] = jfif_density
|
self.info["jfif_density"] = jfif_density
|
||||||
elif marker == 0xFFE1 and s[:6] == b"Exif\0\0":
|
elif marker == 0xFFE1 and s[:6] == b"Exif\0\0":
|
||||||
|
@ -395,6 +398,13 @@ class JpegImageFile(ImageFile.ImageFile):
|
||||||
return getattr(self, "_" + name)
|
return getattr(self, "_" + name)
|
||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
|
|
||||||
|
def __getstate__(self) -> list[Any]:
|
||||||
|
return super().__getstate__() + [self.layers, self.layer]
|
||||||
|
|
||||||
|
def __setstate__(self, state: list[Any]) -> None:
|
||||||
|
super().__setstate__(state)
|
||||||
|
self.layers, self.layer = state[5:]
|
||||||
|
|
||||||
def load_read(self, read_bytes: int) -> bytes:
|
def load_read(self, read_bytes: int) -> bytes:
|
||||||
"""
|
"""
|
||||||
internal: read more image data
|
internal: read more image data
|
||||||
|
@ -751,7 +761,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
extra = info.get("extra", b"")
|
extra = info.get("extra", b"")
|
||||||
|
|
||||||
MAX_BYTES_IN_MARKER = 65533
|
MAX_BYTES_IN_MARKER = 65533
|
||||||
xmp = info.get("xmp", im.info.get("xmp"))
|
xmp = info.get("xmp")
|
||||||
if xmp:
|
if xmp:
|
||||||
overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
|
overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
|
||||||
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
|
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
|
||||||
|
|
|
@ -70,9 +70,9 @@ class MspImageFile(ImageFile.ImageFile):
|
||||||
self._size = i16(s, 4), i16(s, 6)
|
self._size = i16(s, 4), i16(s, 6)
|
||||||
|
|
||||||
if s[:4] == b"DanM":
|
if s[:4] == b"DanM":
|
||||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, ("1", 0, 1))]
|
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, "1")]
|
||||||
else:
|
else:
|
||||||
self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32, None)]
|
self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32)]
|
||||||
|
|
||||||
|
|
||||||
class MspDecoder(ImageFile.PyDecoder):
|
class MspDecoder(ImageFile.PyDecoder):
|
||||||
|
@ -188,7 +188,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
fp.write(o16(h))
|
fp.write(o16(h))
|
||||||
|
|
||||||
# image body
|
# image body
|
||||||
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 32, ("1", 0, 1))])
|
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 32, "1")])
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -47,7 +47,7 @@ class PcdImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
self._mode = "RGB"
|
self._mode = "RGB"
|
||||||
self._size = 768, 512 # FIXME: not correct for rotated images!
|
self._size = 768, 512 # FIXME: not correct for rotated images!
|
||||||
self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048, None)]
|
self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
|
||||||
|
|
||||||
def load_end(self) -> None:
|
def load_end(self) -> None:
|
||||||
if self.tile_post_rotate:
|
if self.tile_post_rotate:
|
||||||
|
|
|
@ -61,9 +61,7 @@ class PixarImageFile(ImageFile.ImageFile):
|
||||||
# FIXME: to be continued...
|
# FIXME: to be continued...
|
||||||
|
|
||||||
# create tile descriptor (assuming "dumped")
|
# create tile descriptor (assuming "dumped")
|
||||||
self.tile = [
|
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 1024, self.mode)]
|
||||||
ImageFile._Tile("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -32,7 +32,7 @@ class QoiImageFile(ImageFile.ImageFile):
|
||||||
self._mode = "RGB" if channels == 3 else "RGBA"
|
self._mode = "RGB" if channels == 3 else "RGBA"
|
||||||
|
|
||||||
self.fp.seek(1, os.SEEK_CUR) # colorspace
|
self.fp.seek(1, os.SEEK_CUR) # colorspace
|
||||||
self.tile = [ImageFile._Tile("qoi", (0, 0) + self._size, self.fp.tell(), None)]
|
self.tile = [ImageFile._Tile("qoi", (0, 0) + self._size, self.fp.tell())]
|
||||||
|
|
||||||
|
|
||||||
class QoiDecoder(ImageFile.PyDecoder):
|
class QoiDecoder(ImageFile.PyDecoder):
|
||||||
|
|
|
@ -154,9 +154,7 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
self.rawmode = "F;32F"
|
self.rawmode = "F;32F"
|
||||||
self._mode = "F"
|
self._mode = "F"
|
||||||
|
|
||||||
self.tile = [
|
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, offset, self.rawmode)]
|
||||||
ImageFile._Tile("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))
|
|
||||||
]
|
|
||||||
self._fp = self.fp # FIXME: hack
|
self._fp = self.fp # FIXME: hack
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -211,26 +209,27 @@ class SpiderImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
|
|
||||||
# given a list of filenames, return a list of images
|
# given a list of filenames, return a list of images
|
||||||
def loadImageSeries(filelist: list[str] | None = None) -> list[SpiderImageFile] | None:
|
def loadImageSeries(filelist: list[str] | None = None) -> list[Image.Image] | None:
|
||||||
"""create a list of :py:class:`~PIL.Image.Image` objects for use in a montage"""
|
"""create a list of :py:class:`~PIL.Image.Image` objects for use in a montage"""
|
||||||
if filelist is None or len(filelist) < 1:
|
if filelist is None or len(filelist) < 1:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
imglist = []
|
byte_imgs = []
|
||||||
for img in filelist:
|
for img in filelist:
|
||||||
if not os.path.exists(img):
|
if not os.path.exists(img):
|
||||||
print(f"unable to find {img}")
|
print(f"unable to find {img}")
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
with Image.open(img) as im:
|
with Image.open(img) as im:
|
||||||
im = im.convert2byte()
|
assert isinstance(im, SpiderImageFile)
|
||||||
|
byte_im = im.convert2byte()
|
||||||
except Exception:
|
except Exception:
|
||||||
if not isSpiderImage(img):
|
if not isSpiderImage(img):
|
||||||
print(f"{img} is not a Spider image file")
|
print(f"{img} is not a Spider image file")
|
||||||
continue
|
continue
|
||||||
im.info["filename"] = img
|
byte_im.info["filename"] = img
|
||||||
imglist.append(im)
|
byte_imgs.append(byte_im)
|
||||||
return imglist
|
return byte_imgs
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -280,9 +279,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
fp.writelines(hdr)
|
fp.writelines(hdr)
|
||||||
|
|
||||||
rawmode = "F;32NF" # 32-bit native floating point
|
rawmode = "F;32NF" # 32-bit native floating point
|
||||||
ImageFile._save(
|
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
|
||||||
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _save_spider(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
def _save_spider(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
|
@ -582,7 +582,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ifh: bytes = b"II\052\0\0\0\0\0",
|
ifh: bytes = b"II\x2A\x00\x00\x00\x00\x00",
|
||||||
prefix: bytes | None = None,
|
prefix: bytes | None = None,
|
||||||
group: int | None = None,
|
group: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -949,16 +949,26 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
warnings.warn(str(msg))
|
warnings.warn(str(msg))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _get_ifh(self):
|
||||||
|
ifh = self._prefix + self._pack("H", 43 if self._bigtiff else 42)
|
||||||
|
if self._bigtiff:
|
||||||
|
ifh += self._pack("HH", 8, 0)
|
||||||
|
ifh += self._pack("Q", 16) if self._bigtiff else self._pack("L", 8)
|
||||||
|
|
||||||
|
return ifh
|
||||||
|
|
||||||
def tobytes(self, offset: int = 0) -> bytes:
|
def tobytes(self, offset: int = 0) -> bytes:
|
||||||
# FIXME What about tagdata?
|
# FIXME What about tagdata?
|
||||||
result = self._pack("H", len(self._tags_v2))
|
result = self._pack("Q" if self._bigtiff else "H", len(self._tags_v2))
|
||||||
|
|
||||||
entries: list[tuple[int, int, int, bytes, bytes]] = []
|
entries: list[tuple[int, int, int, bytes, bytes]] = []
|
||||||
offset = offset + len(result) + len(self._tags_v2) * 12 + 4
|
offset += len(result) + len(self._tags_v2) * (20 if self._bigtiff else 12) + 4
|
||||||
stripoffsets = None
|
stripoffsets = None
|
||||||
|
|
||||||
# pass 1: convert tags to binary format
|
# pass 1: convert tags to binary format
|
||||||
# always write tags in ascending order
|
# always write tags in ascending order
|
||||||
|
fmt = "Q" if self._bigtiff else "L"
|
||||||
|
fmt_size = 8 if self._bigtiff else 4
|
||||||
for tag, value in sorted(self._tags_v2.items()):
|
for tag, value in sorted(self._tags_v2.items()):
|
||||||
if tag == STRIPOFFSETS:
|
if tag == STRIPOFFSETS:
|
||||||
stripoffsets = len(entries)
|
stripoffsets = len(entries)
|
||||||
|
@ -966,11 +976,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value))
|
logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value))
|
||||||
is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
|
is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
|
||||||
if is_ifd:
|
if is_ifd:
|
||||||
if self._endian == "<":
|
ifd = ImageFileDirectory_v2(self._get_ifh(), group=tag)
|
||||||
ifh = b"II\x2A\x00\x08\x00\x00\x00"
|
|
||||||
else:
|
|
||||||
ifh = b"MM\x00\x2A\x00\x00\x00\x08"
|
|
||||||
ifd = ImageFileDirectory_v2(ifh, group=tag)
|
|
||||||
values = self._tags_v2[tag]
|
values = self._tags_v2[tag]
|
||||||
for ifd_tag, ifd_value in values.items():
|
for ifd_tag, ifd_value in values.items():
|
||||||
ifd[ifd_tag] = ifd_value
|
ifd[ifd_tag] = ifd_value
|
||||||
|
@ -993,10 +999,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
else:
|
else:
|
||||||
count = len(values)
|
count = len(values)
|
||||||
# figure out if data fits into the entry
|
# figure out if data fits into the entry
|
||||||
if len(data) <= 4:
|
if len(data) <= fmt_size:
|
||||||
entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
|
entries.append((tag, typ, count, data.ljust(fmt_size, b"\0"), b""))
|
||||||
else:
|
else:
|
||||||
entries.append((tag, typ, count, self._pack("L", offset), data))
|
entries.append((tag, typ, count, self._pack(fmt, offset), data))
|
||||||
offset += (len(data) + 1) // 2 * 2 # pad to word
|
offset += (len(data) + 1) // 2 * 2 # pad to word
|
||||||
|
|
||||||
# update strip offset data to point beyond auxiliary data
|
# update strip offset data to point beyond auxiliary data
|
||||||
|
@ -1007,13 +1013,15 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
values = [val + offset for val in handler(self, data, self.legacy_api)]
|
values = [val + offset for val in handler(self, data, self.legacy_api)]
|
||||||
data = self._write_dispatch[typ](self, *values)
|
data = self._write_dispatch[typ](self, *values)
|
||||||
else:
|
else:
|
||||||
value = self._pack("L", self._unpack("L", value)[0] + offset)
|
value = self._pack(fmt, self._unpack(fmt, value)[0] + offset)
|
||||||
entries[stripoffsets] = tag, typ, count, value, data
|
entries[stripoffsets] = tag, typ, count, value, data
|
||||||
|
|
||||||
# pass 2: write entries to file
|
# pass 2: write entries to file
|
||||||
for tag, typ, count, value, data in entries:
|
for tag, typ, count, value, data in entries:
|
||||||
logger.debug("%s %s %s %s %s", tag, typ, count, repr(value), repr(data))
|
logger.debug("%s %s %s %s %s", tag, typ, count, repr(value), repr(data))
|
||||||
result += self._pack("HHL4s", tag, typ, count, value)
|
result += self._pack(
|
||||||
|
"HHQ8s" if self._bigtiff else "HHL4s", tag, typ, count, value
|
||||||
|
)
|
||||||
|
|
||||||
# -- overwrite here for multi-page --
|
# -- overwrite here for multi-page --
|
||||||
result += b"\0\0\0\0" # end of entries
|
result += b"\0\0\0\0" # end of entries
|
||||||
|
@ -1028,8 +1036,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
|
|
||||||
def save(self, fp: IO[bytes]) -> int:
|
def save(self, fp: IO[bytes]) -> int:
|
||||||
if fp.tell() == 0: # skip TIFF header on subsequent pages
|
if fp.tell() == 0: # skip TIFF header on subsequent pages
|
||||||
# tiff header -- PIL always starts the first IFD at offset 8
|
fp.write(self._get_ifh())
|
||||||
fp.write(self._prefix + self._pack("HL", 42, 8))
|
|
||||||
|
|
||||||
offset = fp.tell()
|
offset = fp.tell()
|
||||||
result = self.tobytes(offset)
|
result = self.tobytes(offset)
|
||||||
|
@ -1432,8 +1439,12 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING))
|
logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING))
|
||||||
|
|
||||||
# size
|
# size
|
||||||
xsize = self.tag_v2.get(IMAGEWIDTH)
|
try:
|
||||||
ysize = self.tag_v2.get(IMAGELENGTH)
|
xsize = self.tag_v2[IMAGEWIDTH]
|
||||||
|
ysize = self.tag_v2[IMAGELENGTH]
|
||||||
|
except KeyError as e:
|
||||||
|
msg = "Missing dimensions"
|
||||||
|
raise TypeError(msg) from e
|
||||||
if not isinstance(xsize, int) or not isinstance(ysize, int):
|
if not isinstance(xsize, int) or not isinstance(ysize, int):
|
||||||
msg = "Invalid dimensions"
|
msg = "Invalid dimensions"
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
@ -1555,17 +1566,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
# fillorder==2 modes have a corresponding
|
# fillorder==2 modes have a corresponding
|
||||||
# fillorder=1 mode
|
# fillorder=1 mode
|
||||||
self._mode, rawmode = OPEN_INFO[key]
|
self._mode, rawmode = OPEN_INFO[key]
|
||||||
# libtiff always returns the bytes in native order.
|
|
||||||
# we're expecting image byte order. So, if the rawmode
|
|
||||||
# contains I;16, we need to convert from native to image
|
|
||||||
# byte order.
|
|
||||||
if rawmode == "I;16":
|
|
||||||
rawmode = "I;16N"
|
|
||||||
if ";16B" in rawmode:
|
|
||||||
rawmode = rawmode.replace(";16B", ";16N")
|
|
||||||
if ";16L" in rawmode:
|
|
||||||
rawmode = rawmode.replace(";16L", ";16N")
|
|
||||||
|
|
||||||
# YCbCr images with new jpeg compression with pixels in one plane
|
# YCbCr images with new jpeg compression with pixels in one plane
|
||||||
# unpacked straight into RGB values
|
# unpacked straight into RGB values
|
||||||
if (
|
if (
|
||||||
|
@ -1574,6 +1574,14 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
and self._planar_configuration == 1
|
and self._planar_configuration == 1
|
||||||
):
|
):
|
||||||
rawmode = "RGB"
|
rawmode = "RGB"
|
||||||
|
# libtiff always returns the bytes in native order.
|
||||||
|
# we're expecting image byte order. So, if the rawmode
|
||||||
|
# contains I;16, we need to convert from native to image
|
||||||
|
# byte order.
|
||||||
|
elif rawmode == "I;16":
|
||||||
|
rawmode = "I;16N"
|
||||||
|
elif rawmode.endswith(";16B") or rawmode.endswith(";16L"):
|
||||||
|
rawmode = rawmode[:-1] + "N"
|
||||||
|
|
||||||
# Offset in the tile tuple is 0, we go from 0,0 to
|
# Offset in the tile tuple is 0, we go from 0,0 to
|
||||||
# w,h, and we only do this once -- eds
|
# w,h, and we only do this once -- eds
|
||||||
|
@ -1679,10 +1687,13 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
msg = f"cannot write mode {im.mode} as TIFF"
|
msg = f"cannot write mode {im.mode} as TIFF"
|
||||||
raise OSError(msg) from e
|
raise OSError(msg) from e
|
||||||
|
|
||||||
ifd = ImageFileDirectory_v2(prefix=prefix)
|
|
||||||
|
|
||||||
encoderinfo = im.encoderinfo
|
encoderinfo = im.encoderinfo
|
||||||
encoderconfig = im.encoderconfig
|
encoderconfig = im.encoderconfig
|
||||||
|
|
||||||
|
ifd = ImageFileDirectory_v2(prefix=prefix)
|
||||||
|
if encoderinfo.get("big_tiff"):
|
||||||
|
ifd._bigtiff = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
compression = encoderinfo["compression"]
|
compression = encoderinfo["compression"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1914,7 +1925,9 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
if not getattr(Image.core, "libtiff_support_custom_tags", False):
|
if not getattr(Image.core, "libtiff_support_custom_tags", False):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if tag in ifd.tagtype:
|
if tag in TiffTags.TAGS_V2_GROUPS:
|
||||||
|
types[tag] = TiffTags.LONG8
|
||||||
|
elif tag in ifd.tagtype:
|
||||||
types[tag] = ifd.tagtype[tag]
|
types[tag] = ifd.tagtype[tag]
|
||||||
elif not (isinstance(value, (int, float, str, bytes))):
|
elif not (isinstance(value, (int, float, str, bytes))):
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -92,6 +92,9 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
||||||
|
|
||||||
# get units per inch
|
# get units per inch
|
||||||
self._inch = word(s, 14)
|
self._inch = word(s, 14)
|
||||||
|
if self._inch == 0:
|
||||||
|
msg = "Invalid inch"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
# get bounding box
|
# get bounding box
|
||||||
x0 = short(s, 6)
|
x0 = short(s, 6)
|
||||||
|
|
|
@ -74,9 +74,7 @@ class XVThumbImageFile(ImageFile.ImageFile):
|
||||||
self.palette = ImagePalette.raw("RGB", PALETTE)
|
self.palette = ImagePalette.raw("RGB", PALETTE)
|
||||||
|
|
||||||
self.tile = [
|
self.tile = [
|
||||||
ImageFile._Tile(
|
ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), self.mode)
|
||||||
"raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1)
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ class XbmImageFile(ImageFile.ImageFile):
|
||||||
self._mode = "1"
|
self._mode = "1"
|
||||||
self._size = xsize, ysize
|
self._size = xsize, ysize
|
||||||
|
|
||||||
self.tile = [ImageFile._Tile("xbm", (0, 0) + self.size, m.end(), None)]
|
self.tile = [ImageFile._Tile("xbm", (0, 0) + self.size, m.end())]
|
||||||
|
|
||||||
|
|
||||||
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
@ -85,7 +85,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
||||||
fp.write(b"static char im_bits[] = {\n")
|
fp.write(b"static char im_bits[] = {\n")
|
||||||
|
|
||||||
ImageFile._save(im, fp, [ImageFile._Tile("xbm", (0, 0) + im.size, 0, None)])
|
ImageFile._save(im, fp, [ImageFile._Tile("xbm", (0, 0) + im.size)])
|
||||||
|
|
||||||
fp.write(b"};\n")
|
fp.write(b"};\n")
|
||||||
|
|
||||||
|
|
|
@ -101,9 +101,7 @@ class XpmImageFile(ImageFile.ImageFile):
|
||||||
self._mode = "P"
|
self._mode = "P"
|
||||||
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
||||||
|
|
||||||
self.tile = [
|
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), "P")]
|
||||||
ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))
|
|
||||||
]
|
|
||||||
|
|
||||||
def load_read(self, read_bytes: int) -> bytes:
|
def load_read(self, read_bytes: int) -> bytes:
|
||||||
#
|
#
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Master version for Pillow
|
# Master version for Pillow
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
__version__ = "11.1.0.dev0"
|
__version__ = "11.2.0.dev0"
|
||||||
|
|
|
@ -346,7 +346,7 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
|
||||||
// transform color channels only
|
// transform color channels only
|
||||||
for (i = 0; i < im->ysize; i++) {
|
for (i = 0; i < im->ysize; i++) {
|
||||||
|
@ -362,7 +362,7 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
|
||||||
// enough available on all platforms, so we polyfill it here for now.
|
// enough available on all platforms, so we polyfill it here for now.
|
||||||
pyCMScopyAux(hTransform, imOut, im);
|
pyCMScopyAux(hTransform, imOut, im);
|
||||||
|
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -378,7 +378,7 @@ _buildTransform(
|
||||||
) {
|
) {
|
||||||
cmsHTRANSFORM hTransform;
|
cmsHTRANSFORM hTransform;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
|
||||||
/* create the transform */
|
/* create the transform */
|
||||||
hTransform = cmsCreateTransform(
|
hTransform = cmsCreateTransform(
|
||||||
|
@ -412,7 +412,7 @@ _buildProofTransform(
|
||||||
) {
|
) {
|
||||||
cmsHTRANSFORM hTransform;
|
cmsHTRANSFORM hTransform;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
|
||||||
/* create the transform */
|
/* create the transform */
|
||||||
hTransform = cmsCreateProofingTransform(
|
hTransform = cmsCreateProofingTransform(
|
||||||
|
|
|
@ -690,9 +690,10 @@ PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
|
||||||
SetWindowLongPtr(wnd, 0, (LONG_PTR)callback);
|
SetWindowLongPtr(wnd, 0, (LONG_PTR)callback);
|
||||||
SetWindowLongPtr(wnd, sizeof(callback), (LONG_PTR)PyThreadState_Get());
|
SetWindowLongPtr(wnd, sizeof(callback), (LONG_PTR)PyThreadState_Get());
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS ShowWindow(wnd, SW_SHOWNORMAL);
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
ShowWindow(wnd, SW_SHOWNORMAL);
|
||||||
SetForegroundWindow(wnd); /* to make sure it's visible */
|
SetForegroundWindow(wnd); /* to make sure it's visible */
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS;
|
||||||
|
|
||||||
return Py_BuildValue(F_HANDLE, wnd);
|
return Py_BuildValue(F_HANDLE, wnd);
|
||||||
}
|
}
|
||||||
|
@ -701,11 +702,12 @@ PyObject *
|
||||||
PyImaging_EventLoopWin32(PyObject *self, PyObject *args) {
|
PyImaging_EventLoopWin32(PyObject *self, PyObject *args) {
|
||||||
MSG msg;
|
MSG msg;
|
||||||
|
|
||||||
Py_BEGIN_ALLOW_THREADS while (mainloop && GetMessage(&msg, NULL, 0, 0)) {
|
Py_BEGIN_ALLOW_THREADS;
|
||||||
|
while (mainloop && GetMessage(&msg, NULL, 0, 0)) {
|
||||||
TranslateMessage(&msg);
|
TranslateMessage(&msg);
|
||||||
DispatchMessage(&msg);
|
DispatchMessage(&msg);
|
||||||
}
|
}
|
||||||
Py_END_ALLOW_THREADS
|
Py_END_ALLOW_THREADS;
|
||||||
|
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
|
|
|
@ -736,7 +736,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
}
|
}
|
||||||
if (tag_type) {
|
if (tag_type) {
|
||||||
int type_int = PyLong_AsLong(tag_type);
|
int type_int = PyLong_AsLong(tag_type);
|
||||||
if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) {
|
if (type_int >= TIFF_BYTE && type_int <= TIFF_LONG8) {
|
||||||
type = (TIFFDataType)type_int;
|
type = (TIFFDataType)type_int;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -929,7 +929,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
);
|
);
|
||||||
} else if (type == TIFF_LONG) {
|
} else if (type == TIFF_LONG) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value)
|
&encoder->state, (ttag_t)key_int, (UINT32)PyLong_AsLong(value)
|
||||||
);
|
);
|
||||||
} else if (type == TIFF_SSHORT) {
|
} else if (type == TIFF_SSHORT) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
|
@ -959,6 +959,10 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
||||||
status = ImagingLibTiffSetField(
|
status = ImagingLibTiffSetField(
|
||||||
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
|
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
|
||||||
);
|
);
|
||||||
|
} else if (type == TIFF_LONG8) {
|
||||||
|
status = ImagingLibTiffSetField(
|
||||||
|
&encoder->state, (ttag_t)key_int, (uint64_t)PyLong_AsLongLong(value)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
TRACE(
|
TRACE(
|
||||||
("Unhandled type for key %d : %s \n",
|
("Unhandled type for key %d : %s \n",
|
||||||
|
|
|
@ -501,7 +501,8 @@ polygon_generic(
|
||||||
// Needed to draw consistent polygons
|
// Needed to draw consistent polygons
|
||||||
xx[j] = xx[j - 1];
|
xx[j] = xx[j - 1];
|
||||||
j++;
|
j++;
|
||||||
} else if (current->dx != 0 && roundf(xx[j - 1]) == xx[j - 1]) {
|
} else if (current->dx != 0 && j % 2 == 1 &&
|
||||||
|
roundf(xx[j - 1]) == xx[j - 1]) {
|
||||||
// Connect discontiguous corners
|
// Connect discontiguous corners
|
||||||
for (k = 0; k < i; k++) {
|
for (k = 0; k < i; k++) {
|
||||||
Edge *other_edge = edge_table[k];
|
Edge *other_edge = edge_table[k];
|
||||||
|
@ -510,10 +511,8 @@ polygon_generic(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Check if the two edges join to make a corner
|
// Check if the two edges join to make a corner
|
||||||
if (((ymin == current->ymin && ymin == other_edge->ymin) ||
|
if (xx[j - 1] ==
|
||||||
(ymin == current->ymax && ymin == other_edge->ymax)) &&
|
(ymin - other_edge->y0) * other_edge->dx + other_edge->x0) {
|
||||||
xx[j - 1] == (ymin - other_edge->y0) * other_edge->dx +
|
|
||||||
other_edge->x0) {
|
|
||||||
// Determine points from the edges on the next row
|
// Determine points from the edges on the next row
|
||||||
// Or if this is the last row, check the previous row
|
// Or if this is the last row, check the previous row
|
||||||
int offset = ymin == ymax ? -1 : 1;
|
int offset = ymin == ymax ? -1 : 1;
|
||||||
|
|
|
@ -44,8 +44,6 @@
|
||||||
defines their own types with the same names, so we need to be able to undef
|
defines their own types with the same names, so we need to be able to undef
|
||||||
ours before including the JPEG code. */
|
ours before including the JPEG code. */
|
||||||
|
|
||||||
#if __STDC_VERSION__ >= 199901L /* C99+ */
|
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define INT8 int8_t
|
#define INT8 int8_t
|
||||||
|
@ -55,34 +53,6 @@
|
||||||
#define INT32 int32_t
|
#define INT32 int32_t
|
||||||
#define UINT32 uint32_t
|
#define UINT32 uint32_t
|
||||||
|
|
||||||
#else /* < C99 */
|
|
||||||
|
|
||||||
#define INT8 signed char
|
|
||||||
|
|
||||||
#if SIZEOF_SHORT == 2
|
|
||||||
#define INT16 short
|
|
||||||
#elif SIZEOF_INT == 2
|
|
||||||
#define INT16 int
|
|
||||||
#else
|
|
||||||
#error Cannot find required 16-bit integer type
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if SIZEOF_SHORT == 4
|
|
||||||
#define INT32 short
|
|
||||||
#elif SIZEOF_INT == 4
|
|
||||||
#define INT32 int
|
|
||||||
#elif SIZEOF_LONG == 4
|
|
||||||
#define INT32 long
|
|
||||||
#else
|
|
||||||
#error Cannot find required 32-bit integer type
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define UINT8 unsigned char
|
|
||||||
#define UINT16 unsigned INT16
|
|
||||||
#define UINT32 unsigned INT32
|
|
||||||
|
|
||||||
#endif /* < C99 */
|
|
||||||
|
|
||||||
#endif /* not WIN */
|
#endif /* not WIN */
|
||||||
|
|
||||||
/* assume IEEE; tweak if necessary (patches are welcome) */
|
/* assume IEEE; tweak if necessary (patches are welcome) */
|
||||||
|
|
|
@ -640,7 +640,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
|
||||||
opj_dparameters_t params;
|
opj_dparameters_t params;
|
||||||
OPJ_COLOR_SPACE color_space;
|
OPJ_COLOR_SPACE color_space;
|
||||||
j2k_unpacker_t unpack = NULL;
|
j2k_unpacker_t unpack = NULL;
|
||||||
size_t buffer_size = 0, tile_bytes = 0;
|
size_t tile_bytes = 0;
|
||||||
unsigned n, tile_height, tile_width;
|
unsigned n, tile_height, tile_width;
|
||||||
int subsampling;
|
int subsampling;
|
||||||
int total_component_width = 0;
|
int total_component_width = 0;
|
||||||
|
@ -870,7 +870,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
|
||||||
tile_info.data_size = tile_bytes;
|
tile_info.data_size = tile_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer_size < tile_info.data_size) {
|
if (tile_info.data_size > 0) {
|
||||||
/* malloc check ok, overflow and tile size sanity check above */
|
/* malloc check ok, overflow and tile size sanity check above */
|
||||||
UINT8 *new = realloc(state->buffer, tile_info.data_size);
|
UINT8 *new = realloc(state->buffer, tile_info.data_size);
|
||||||
if (!new) {
|
if (!new) {
|
||||||
|
@ -883,7 +883,6 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
|
||||||
to valgrind errors. */
|
to valgrind errors. */
|
||||||
memset(new, 0, tile_info.data_size);
|
memset(new, 0, tile_info.data_size);
|
||||||
state->buffer = new;
|
state->buffer = new;
|
||||||
buffer_size = tile_info.data_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!opj_decode_tile_data(
|
if (!opj_decode_tile_data(
|
||||||
|
|
|
@ -330,6 +330,13 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
|
||||||
components = 4;
|
components = 4;
|
||||||
color_space = OPJ_CLRSPC_SRGB;
|
color_space = OPJ_CLRSPC_SRGB;
|
||||||
pack = j2k_pack_rgba;
|
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)
|
||||||
|
} else if (strcmp(im->mode, "CMYK") == 0) {
|
||||||
|
components = 4;
|
||||||
|
color_space = OPJ_CLRSPC_CMYK;
|
||||||
|
pack = j2k_pack_rgba;
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
state->errcode = IMAGING_CODEC_BROKEN;
|
state->errcode = IMAGING_CODEC_BROKEN;
|
||||||
state->state = J2K_STATE_FAILED;
|
state->state = J2K_STATE_FAILED;
|
||||||
|
|
|
@ -1695,6 +1695,7 @@ static struct {
|
||||||
|
|
||||||
#ifdef WORDS_BIGENDIAN
|
#ifdef WORDS_BIGENDIAN
|
||||||
{"RGB", "RGB;16N", 48, unpackRGB16B},
|
{"RGB", "RGB;16N", 48, unpackRGB16B},
|
||||||
|
{"RGB", "RGBX;16N", 64, unpackRGBA16B},
|
||||||
{"RGBA", "RGBa;16N", 64, unpackRGBa16B},
|
{"RGBA", "RGBa;16N", 64, unpackRGBa16B},
|
||||||
{"RGBA", "RGBA;16N", 64, unpackRGBA16B},
|
{"RGBA", "RGBA;16N", 64, unpackRGBA16B},
|
||||||
{"RGBX", "RGBX;16N", 64, unpackRGBA16B},
|
{"RGBX", "RGBX;16N", 64, unpackRGBA16B},
|
||||||
|
@ -1708,6 +1709,7 @@ static struct {
|
||||||
{"RGBA", "A;16N", 16, band316B},
|
{"RGBA", "A;16N", 16, band316B},
|
||||||
#else
|
#else
|
||||||
{"RGB", "RGB;16N", 48, unpackRGB16L},
|
{"RGB", "RGB;16N", 48, unpackRGB16L},
|
||||||
|
{"RGB", "RGBX;16N", 64, unpackRGBA16L},
|
||||||
{"RGBA", "RGBa;16N", 64, unpackRGBa16L},
|
{"RGBA", "RGBa;16N", 64, unpackRGBa16L},
|
||||||
{"RGBA", "RGBA;16N", 64, unpackRGBA16L},
|
{"RGBA", "RGBA;16N", 64, unpackRGBA16L},
|
||||||
{"RGBX", "RGBX;16N", 64, unpackRGBA16L},
|
{"RGBX", "RGBX;16N", 64, unpackRGBA16L},
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 74a9795bc64ff786b7e7d33bdec2843cf17e512e
|
Subproject commit 42d761728d141d8462cd9943f4329f12fe62b155
|
|
@ -117,11 +117,11 @@ V = {
|
||||||
"JPEGTURBO": "3.1.0",
|
"JPEGTURBO": "3.1.0",
|
||||||
"LCMS2": "2.16",
|
"LCMS2": "2.16",
|
||||||
"LIBPNG": "1.6.44",
|
"LIBPNG": "1.6.44",
|
||||||
"LIBWEBP": "1.4.0",
|
"LIBWEBP": "1.5.0",
|
||||||
"OPENJPEG": "2.5.3",
|
"OPENJPEG": "2.5.3",
|
||||||
"TIFF": "4.6.0",
|
"TIFF": "4.6.0",
|
||||||
"XZ": "5.6.3",
|
"XZ": "5.6.3",
|
||||||
"ZLIBNG": "2.2.2",
|
"ZLIBNG": "2.2.3",
|
||||||
"LIBAVIF": "1.1.1",
|
"LIBAVIF": "1.1.1",
|
||||||
}
|
}
|
||||||
V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "")
|
V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "")
|
||||||
|
@ -529,6 +529,9 @@ def extract_dep(url: str, filename: str, prefs: dict[str, str]) -> None:
|
||||||
if sources_dir_abs != member_prefix:
|
if sources_dir_abs != member_prefix:
|
||||||
msg = "Attempted Path Traversal in Tar File"
|
msg = "Attempted Path Traversal in Tar File"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
if sys.version_info >= (3, 12):
|
||||||
|
tgz.extractall(sources_dir, filter="data")
|
||||||
|
else:
|
||||||
tgz.extractall(sources_dir)
|
tgz.extractall(sources_dir)
|
||||||
else:
|
else:
|
||||||
msg = "Unknown archive type: " + filename
|
msg = "Unknown archive type: " + filename
|
||||||
|
|
Loading…
Reference in New Issue
Block a user