mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-03-13 01:05:48 +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
|
||||
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\
|
||||
sway wl-clipboard libopenblas-dev nasm
|
||||
fi
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
mypy==1.13.0
|
||||
mypy==1.14.1
|
||||
IceSpringPySideStubs-PyQt6
|
||||
IceSpringPySideStubs-PySide6
|
||||
ipython
|
||||
|
|
1
.github/workflows/test-docker.yml
vendored
1
.github/workflows/test-docker.yml
vendored
|
@ -44,6 +44,7 @@ jobs:
|
|||
amazon-2023-amd64,
|
||||
arch,
|
||||
centos-stream-9-amd64,
|
||||
centos-stream-10-amd64,
|
||||
debian-12-bookworm-x86,
|
||||
debian-12-bookworm-amd64,
|
||||
fedora-40-amd64,
|
||||
|
|
20
.github/workflows/wheels-dependencies.sh
vendored
20
.github/workflows/wheels-dependencies.sh
vendored
|
@ -37,7 +37,7 @@ fi
|
|||
ARCHIVE_SDIR=pillow-depends-main
|
||||
|
||||
# Package versions for fresh source builds
|
||||
FREETYPE_VERSION=2.13.2
|
||||
FREETYPE_VERSION=2.13.3
|
||||
HARFBUZZ_VERSION=10.1.0
|
||||
LIBPNG_VERSION=1.6.44
|
||||
JPEGTURBO_VERSION=3.1.0
|
||||
|
@ -45,13 +45,9 @@ OPENJPEG_VERSION=2.5.3
|
|||
XZ_VERSION=5.6.3
|
||||
TIFF_VERSION=4.6.0
|
||||
LCMS2_VERSION=2.16
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
GIFLIB_VERSION=5.2.2
|
||||
else
|
||||
GIFLIB_VERSION=5.2.1
|
||||
fi
|
||||
ZLIB_NG_VERSION=2.2.2
|
||||
LIBWEBP_VERSION=1.4.0
|
||||
GIFLIB_VERSION=5.2.2
|
||||
ZLIB_NG_VERSION=2.2.3
|
||||
LIBWEBP_VERSION=1.5.0
|
||||
BZIP2_VERSION=1.0.8
|
||||
LIBXCB_VERSION=1.17.0
|
||||
BROTLI_VERSION=1.1.0
|
||||
|
@ -210,6 +206,14 @@ function build {
|
|||
CFLAGS="$CFLAGS -O3 -DNDEBUG"
|
||||
if [[ -n "$IS_MACOS" ]]; then
|
||||
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
|
||||
build_libwebp
|
||||
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
|
||||
with:
|
||||
python-version: "3.x"
|
||||
cache: pip
|
||||
cache-dependency-path: "Makefile"
|
||||
|
||||
- run: make sdist
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.8.1
|
||||
rev: v0.8.4
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--exit-non-zero-on-fix]
|
||||
|
@ -24,7 +24,7 @@ repos:
|
|||
exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: v19.1.4
|
||||
rev: v19.1.5
|
||||
hooks:
|
||||
- id: clang-format
|
||||
types: [c]
|
||||
|
@ -56,6 +56,11 @@ repos:
|
|||
- id: check-readthedocs
|
||||
- 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
|
||||
rev: v1.0.0
|
||||
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)
|
||||
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
||||
assert isinstance(lut.table, numpy.ndarray)
|
||||
assert lut.table.shape == (table.size,)
|
||||
|
||||
table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
|
||||
lut = ImageFilter.Color3DLUT((5, 6, 7), table)
|
||||
assert isinstance(lut.table, numpy.ndarray)
|
||||
assert lut.table.shape == (table.size,)
|
||||
|
||||
# Check application
|
||||
|
|
|
@ -4,7 +4,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from PIL import Image
|
||||
from PIL import BlpImagePlugin, Image
|
||||
|
||||
from .helper import (
|
||||
assert_image_equal,
|
||||
|
@ -19,6 +19,7 @@ def test_load_blp1() -> None:
|
|||
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
|
||||
|
||||
with Image.open("Tests/images/blp/blp1_jpeg2.blp") as im:
|
||||
assert im.mode == "RGBA"
|
||||
im.load()
|
||||
|
||||
|
||||
|
@ -37,6 +38,13 @@ def test_load_blp2_dxt1a() -> None:
|
|||
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:
|
||||
f = str(tmp_path / "temp.blp")
|
||||
|
||||
|
|
|
@ -83,4 +83,4 @@ def test_handler(tmp_path: Path) -> None:
|
|||
im.save(temp_file)
|
||||
assert handler.saved
|
||||
|
||||
BufrStubImagePlugin._handler = None
|
||||
BufrStubImagePlugin.register_handler(None)
|
||||
|
|
|
@ -4,8 +4,6 @@ import pytest
|
|||
|
||||
from PIL import ContainerIO, Image
|
||||
|
||||
from .helper import hopper
|
||||
|
||||
TEST_FILE = "Tests/images/dummy.container"
|
||||
|
||||
|
||||
|
@ -15,15 +13,15 @@ def test_sanity() -> None:
|
|||
|
||||
|
||||
def test_isatty() -> None:
|
||||
with hopper() as im:
|
||||
container = ContainerIO.ContainerIO(im, 0, 0)
|
||||
with open(TEST_FILE, "rb") as fh:
|
||||
container = ContainerIO.ContainerIO(fh, 0, 0)
|
||||
|
||||
assert container.isatty() is False
|
||||
|
||||
|
||||
def test_seekable() -> None:
|
||||
with hopper() as im:
|
||||
container = ContainerIO.ContainerIO(im, 0, 0)
|
||||
with open(TEST_FILE, "rb") as fh:
|
||||
container = ContainerIO.ContainerIO(fh, 0, 0)
|
||||
|
||||
assert container.seekable() is True
|
||||
|
||||
|
|
|
@ -83,4 +83,4 @@ def test_handler(tmp_path: Path) -> None:
|
|||
im.save(temp_file)
|
||||
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)
|
||||
assert handler.saved
|
||||
|
||||
Hdf5StubImagePlugin._handler = None
|
||||
Hdf5StubImagePlugin.register_handler(None)
|
||||
|
|
|
@ -181,6 +181,10 @@ class TestFileJpeg:
|
|||
assert test(100, 200) == (100, 200)
|
||||
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(
|
||||
pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing"
|
||||
)
|
||||
|
@ -349,7 +353,6 @@ class TestFileJpeg:
|
|||
assert exif.get_ifd(0x8825) == {}
|
||||
|
||||
transposed = ImageOps.exif_transpose(im)
|
||||
assert transposed is not None
|
||||
exif = transposed.getexif()
|
||||
assert exif.get_ifd(0x8825) == {}
|
||||
|
||||
|
@ -1000,8 +1003,13 @@ class TestFileJpeg:
|
|||
with Image.open(f) as reloaded:
|
||||
assert reloaded.info["xmp"] == b"XMP test"
|
||||
|
||||
im.info["xmp"] = b"1" * 65504
|
||||
im.save(f)
|
||||
# Check that XMP is not saved from image info
|
||||
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:
|
||||
assert reloaded.info["xmp"] == b"1" * 65504
|
||||
|
||||
|
|
|
@ -325,6 +325,18 @@ def test_cmyk() -> None:
|
|||
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"))
|
||||
def test_16bit_monochrome_has_correct_mode(ext: str) -> None:
|
||||
with Image.open("Tests/images/16bit.cropped" + ext) as im:
|
||||
|
@ -424,8 +436,9 @@ def test_pclr() -> None:
|
|||
|
||||
|
||||
def test_comment() -> None:
|
||||
with Image.open("Tests/images/comment.jp2") as im:
|
||||
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
|
||||
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"
|
||||
|
||||
# Test an image that is truncated partway through a codestream
|
||||
with open("Tests/images/comment.jp2", "rb") as fp:
|
||||
|
|
|
@ -297,3 +297,15 @@ def test_save_all() -> None:
|
|||
# Test that a single frame image will not be saved as an MPO
|
||||
jpg = roundtrip(im, save_all=True)
|
||||
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)
|
||||
|
||||
@pytest.mark.parametrize("buffer", (True, False))
|
||||
def test_save_stdout(self, buffer: bool) -> None:
|
||||
old_stdout = sys.stdout
|
||||
def test_save_stdout(self, buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
class MyStdOut:
|
||||
buffer = 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:
|
||||
im.save(sys.stdout, "PNG")
|
||||
|
||||
# Reset stdout
|
||||
sys.stdout = old_stdout
|
||||
|
||||
if isinstance(mystdout, MyStdOut):
|
||||
mystdout = mystdout.buffer
|
||||
with Image.open(mystdout) as reloaded:
|
||||
|
|
|
@ -367,22 +367,18 @@ def test_mimetypes(tmp_path: Path) -> None:
|
|||
|
||||
|
||||
@pytest.mark.parametrize("buffer", (True, False))
|
||||
def test_save_stdout(buffer: bool) -> None:
|
||||
old_stdout = sys.stdout
|
||||
def test_save_stdout(buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
||||
class MyStdOut:
|
||||
buffer = BytesIO()
|
||||
|
||||
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
|
||||
|
||||
sys.stdout = mystdout
|
||||
monkeypatch.setattr(sys, "stdout", mystdout)
|
||||
|
||||
with Image.open(TEST_FILE) as im:
|
||||
im.save(sys.stdout, "PPM")
|
||||
|
||||
# Reset stdout
|
||||
sys.stdout = old_stdout
|
||||
|
||||
if isinstance(mystdout, MyStdOut):
|
||||
mystdout = mystdout.buffer
|
||||
with Image.open(mystdout) as reloaded:
|
||||
|
|
|
@ -115,6 +115,13 @@ class TestFileTiff:
|
|||
outfile = str(tmp_path / "temp.tif")
|
||||
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:
|
||||
with pytest.raises(ValueError, match="Unable to seek to frame"):
|
||||
Image.open("Tests/images/seek_too_large.tif")
|
||||
|
|
|
@ -35,6 +35,13 @@ def test_load() -> None:
|
|||
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:
|
||||
class TestHandler(ImageFile.StubHandler):
|
||||
methodCalled = False
|
||||
|
|
|
@ -793,6 +793,10 @@ class TestImage:
|
|||
ifd[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(
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
with Image.open("Tests/images/hopper.jpg") as im:
|
||||
draft = im.draft
|
||||
original_draft = 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:
|
||||
result = draft(mode, size)
|
||||
result = original_draft(mode, size)
|
||||
assert result is not None
|
||||
|
||||
return result
|
||||
|
||||
im.draft = im_draft
|
||||
monkeypatch.setattr(im, "draft", im_draft)
|
||||
|
||||
im.thumbnail((64, 64))
|
||||
|
||||
|
|
|
@ -1674,6 +1674,9 @@ def test_continuous_horizontal_edges_polygon() -> None:
|
|||
def test_discontiguous_corners_polygon() -> None:
|
||||
img, draw = create_base_image_draw((84, 68))
|
||||
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(
|
||||
((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 (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")
|
||||
def test_incremental_webp(self) -> None:
|
||||
with ImageFile.Parser() as p:
|
||||
|
|
|
@ -405,7 +405,6 @@ def test_exif_transpose() -> None:
|
|||
else:
|
||||
original_exif = im.info["exif"]
|
||||
transposed_im = ImageOps.exif_transpose(im)
|
||||
assert transposed_im is not None
|
||||
assert_image_similar(base_im, transposed_im, 17)
|
||||
if orientation_im is base_im:
|
||||
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
|
||||
transposed_im2 = ImageOps.exif_transpose(transposed_im)
|
||||
assert transposed_im2 is not None
|
||||
assert_image_equal(transposed_im2, transposed_im)
|
||||
|
||||
check(base_im)
|
||||
|
@ -433,7 +431,6 @@ def test_exif_transpose() -> None:
|
|||
assert im.getexif()[0x0112] == 3
|
||||
|
||||
transposed_im = ImageOps.exif_transpose(im)
|
||||
assert transposed_im is not None
|
||||
assert 0x0112 not in transposed_im.getexif()
|
||||
|
||||
transposed_im._reload_exif()
|
||||
|
@ -446,14 +443,12 @@ def test_exif_transpose() -> None:
|
|||
assert im.getexif()[0x0112] == 3
|
||||
|
||||
transposed_im = ImageOps.exif_transpose(im)
|
||||
assert transposed_im is not None
|
||||
assert 0x0112 not in transposed_im.getexif()
|
||||
|
||||
# Orientation set directly on Image.Exif
|
||||
im = hopper()
|
||||
im.getexif()[0x0112] = 3
|
||||
transposed_im = ImageOps.exif_transpose(im)
|
||||
assert transposed_im is not None
|
||||
assert 0x0112 not in transposed_im.getexif()
|
||||
|
||||
|
||||
|
@ -464,7 +459,6 @@ def test_exif_transpose_xml_without_xmp() -> None:
|
|||
|
||||
del im.info["xmp"]
|
||||
transposed_im = ImageOps.exif_transpose(im)
|
||||
assert transposed_im is not None
|
||||
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)
|
||||
|
||||
|
||||
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:
|
||||
# Arrange
|
||||
filename = str(tmp_path / "temp.pkl")
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
# 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
|
||||
|
||||
|
|
|
@ -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
|
||||
``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
|
||||
----------------
|
||||
|
||||
|
|
|
@ -572,10 +572,19 @@ JPEG 2000
|
|||
Pillow reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB``,
|
||||
``RGBA``, or ``YCbCr`` data. When reading, ``YCbCr`` data is converted to
|
||||
``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
|
||||
JPEG 2000 raw codestreams (``.j2k`` files), as well as boxed JPEG 2000 files
|
||||
(``.jp2`` or ``.jpx`` files).
|
||||
|
||||
.. versionadded:: 8.3.0
|
||||
Pillow can read (but not write) ``RGB``, ``RGBA``, and ``YCbCr`` images with
|
||||
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
|
||||
: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
|
||||
|
||||
**big_tiff**
|
||||
If true, the image will be saved as a BigTIFF.
|
||||
|
||||
.. versionadded:: 11.1.0
|
||||
|
||||
**compression**
|
||||
A string containing the desired compression method for the
|
||||
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 10 | 3.12 | x86-64 |
|
||||
+----------------------------------+----------------------------+---------------------+
|
||||
| Debian 12 Bookworm | 3.11 | x86, 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 |
|
||||
| | | 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 | |
|
||||
+----------------------------------+----------------------------+------------------+--------------+
|
||||
|
|
|
@ -1,40 +1,38 @@
|
|||
11.1.0
|
||||
------
|
||||
|
||||
Security
|
||||
========
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
TODO
|
||||
|
||||
:cve:`YYYY-XXXXX`: TODO
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
|
||||
Backwards Incompatible Changes
|
||||
==============================
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
|
||||
Deprecations
|
||||
============
|
||||
|
||||
TODO
|
||||
^^^^
|
||||
ExifTags.IFD.Makernote
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
TODO
|
||||
``ExifTags.IFD.Makernote`` has been deprecated. Instead, use
|
||||
``ExifTags.IFD.MakerNote``.
|
||||
|
||||
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
|
||||
=============
|
||||
|
@ -49,9 +47,32 @@ zlib library, and what version of zlib-ng is being used::
|
|||
features.check_feature("zlib_ng") # True or False
|
||||
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
|
||||
=============
|
||||
|
||||
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
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
2
setup.py
2
setup.py
|
@ -345,7 +345,7 @@ class pil_build_ext(build_ext):
|
|||
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"),
|
||||
]
|
||||
+ [("add-imaging-libs=", None, "Add libs to _imaging build")]
|
||||
|
|
|
@ -259,21 +259,36 @@ class BlpImageFile(ImageFile.ImageFile):
|
|||
|
||||
def _open(self) -> None:
|
||||
self.magic = self.fp.read(4)
|
||||
|
||||
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:
|
||||
if not _accept(self.magic):
|
||||
msg = f"Bad BLP magic {repr(self.magic)}"
|
||||
raise BLPFormatError(msg)
|
||||
|
||||
self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
||||
self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
|
||||
compression = struct.unpack("<i", self.fp.read(4))[0]
|
||||
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):
|
||||
|
@ -281,7 +296,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
|||
|
||||
def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
|
||||
try:
|
||||
self._read_blp_header()
|
||||
self._read_header()
|
||||
self._load()
|
||||
except struct.error as e:
|
||||
msg = "Truncated BLP file"
|
||||
|
@ -292,25 +307,9 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
|||
def _load(self) -> None:
|
||||
pass
|
||||
|
||||
def _read_blp_header(self) -> None:
|
||||
assert self.fd is not None
|
||||
self.fd.seek(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 _read_header(self) -> None:
|
||||
self._offsets = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||
self._lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||
|
||||
def _safe_read(self, length: int) -> bytes:
|
||||
assert self.fd is not None
|
||||
|
@ -326,9 +325,11 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
|||
ret.append((b, g, r, a))
|
||||
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 = BytesIO(self._safe_read(self._blp_lengths[0]))
|
||||
_data = BytesIO(self._safe_read(self._lengths[0]))
|
||||
while True:
|
||||
try:
|
||||
(offset,) = struct.unpack("<B", _data.read(1))
|
||||
|
@ -336,7 +337,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
|||
break
|
||||
b, g, r, a = palette[offset]
|
||||
d: tuple[int, ...] = (r, g, b)
|
||||
if self._blp_alpha_depth:
|
||||
if alpha:
|
||||
d += (a,)
|
||||
data.extend(d)
|
||||
return data
|
||||
|
@ -344,19 +345,21 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
|||
|
||||
class BLP1Decoder(_BLPBaseDecoder):
|
||||
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()
|
||||
|
||||
elif self._blp_compression == 1:
|
||||
if self._blp_encoding in (4, 5):
|
||||
elif self._compression == 1:
|
||||
if self._encoding in (4, 5):
|
||||
palette = self._read_palette()
|
||||
data = self._read_bgra(palette)
|
||||
data = self._read_bgra(palette, alpha)
|
||||
self.set_as_raw(data)
|
||||
else:
|
||||
msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}"
|
||||
msg = f"Unsupported BLP encoding {repr(self._encoding)}"
|
||||
raise BLPFormatError(msg)
|
||||
else:
|
||||
msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
|
||||
msg = f"Unsupported BLP compression {repr(self._encoding)}"
|
||||
raise BLPFormatError(msg)
|
||||
|
||||
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 = self._safe_read(jpeg_header_size)
|
||||
assert self.fd is not None
|
||||
self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
|
||||
data = self._safe_read(self._blp_lengths[0])
|
||||
self._safe_read(self._offsets[0] - self.fd.tell()) # What IS this?
|
||||
data = self._safe_read(self._lengths[0])
|
||||
data = jpeg_header + data
|
||||
image = JpegImageFile(BytesIO(data))
|
||||
Image._decompression_bomb_check(image.size)
|
||||
|
@ -383,47 +386,47 @@ class BLP1Decoder(_BLPBaseDecoder):
|
|||
|
||||
class BLP2Decoder(_BLPBaseDecoder):
|
||||
def _load(self) -> None:
|
||||
self._compression, self._encoding, alpha, self._alpha_encoding = self.args
|
||||
|
||||
palette = self._read_palette()
|
||||
|
||||
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
|
||||
|
||||
if self._blp_encoding == Encoding.UNCOMPRESSED:
|
||||
data = self._read_bgra(palette)
|
||||
if self._encoding == Encoding.UNCOMPRESSED:
|
||||
data = self._read_bgra(palette, alpha)
|
||||
|
||||
elif self._blp_encoding == Encoding.DXT:
|
||||
elif self._encoding == Encoding.DXT:
|
||||
data = bytearray()
|
||||
if self._blp_alpha_encoding == AlphaEncoding.DXT1:
|
||||
linesize = (self.size[0] + 3) // 4 * 8
|
||||
for yb in range((self.size[1] + 3) // 4):
|
||||
for d in decode_dxt1(
|
||||
self._safe_read(linesize), alpha=bool(self._blp_alpha_depth)
|
||||
):
|
||||
if self._alpha_encoding == AlphaEncoding.DXT1:
|
||||
linesize = (self.state.xsize + 3) // 4 * 8
|
||||
for yb in range((self.state.ysize + 3) // 4):
|
||||
for d in decode_dxt1(self._safe_read(linesize), alpha):
|
||||
data += d
|
||||
|
||||
elif self._blp_alpha_encoding == AlphaEncoding.DXT3:
|
||||
linesize = (self.size[0] + 3) // 4 * 16
|
||||
for yb in range((self.size[1] + 3) // 4):
|
||||
elif self._alpha_encoding == AlphaEncoding.DXT3:
|
||||
linesize = (self.state.xsize + 3) // 4 * 16
|
||||
for yb in range((self.state.ysize + 3) // 4):
|
||||
for d in decode_dxt3(self._safe_read(linesize)):
|
||||
data += d
|
||||
|
||||
elif self._blp_alpha_encoding == AlphaEncoding.DXT5:
|
||||
linesize = (self.size[0] + 3) // 4 * 16
|
||||
for yb in range((self.size[1] + 3) // 4):
|
||||
elif self._alpha_encoding == AlphaEncoding.DXT5:
|
||||
linesize = (self.state.xsize + 3) // 4 * 16
|
||||
for yb in range((self.state.ysize + 3) // 4):
|
||||
for d in decode_dxt5(self._safe_read(linesize)):
|
||||
data += d
|
||||
else:
|
||||
msg = f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
|
||||
msg = f"Unsupported alpha encoding {repr(self._alpha_encoding)}"
|
||||
raise BLPFormatError(msg)
|
||||
else:
|
||||
msg = f"Unknown BLP encoding {repr(self._blp_encoding)}"
|
||||
msg = f"Unknown BLP encoding {repr(self._encoding)}"
|
||||
raise BLPFormatError(msg)
|
||||
|
||||
else:
|
||||
msg = f"Unknown BLP compression {repr(self._blp_compression)}"
|
||||
msg = f"Unknown BLP compression {repr(self._compression)}"
|
||||
raise BLPFormatError(msg)
|
||||
|
||||
self.set_as_raw(data)
|
||||
|
@ -472,10 +475,15 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
|
||||
assert im.palette is not None
|
||||
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
|
||||
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", 0)) # alpha encoding
|
||||
fp.write(struct.pack("<b", 0)) # mips
|
||||
|
||||
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", alpha_depth))
|
||||
fp.write(struct.pack("<b", 0)) # alpha encoding
|
||||
fp.write(struct.pack("<b", 0)) # mips
|
||||
fp.write(struct.pack("<II", *im.size))
|
||||
if magic == b"BLP1":
|
||||
fp.write(struct.pack("<i", 5))
|
||||
|
|
|
@ -560,9 +560,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
+ struct.pack("<4I", *rgba_mask) # dwRGBABitMask
|
||||
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
|
||||
)
|
||||
ImageFile._save(
|
||||
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
|
||||
)
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
|
||||
|
||||
|
||||
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"):
|
||||
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"grestore end\n")
|
||||
|
|
|
@ -303,38 +303,38 @@ TAGS = {
|
|||
|
||||
|
||||
class GPS(IntEnum):
|
||||
GPSVersionID = 0
|
||||
GPSLatitudeRef = 1
|
||||
GPSLatitude = 2
|
||||
GPSLongitudeRef = 3
|
||||
GPSLongitude = 4
|
||||
GPSAltitudeRef = 5
|
||||
GPSAltitude = 6
|
||||
GPSTimeStamp = 7
|
||||
GPSSatellites = 8
|
||||
GPSStatus = 9
|
||||
GPSMeasureMode = 10
|
||||
GPSDOP = 11
|
||||
GPSSpeedRef = 12
|
||||
GPSSpeed = 13
|
||||
GPSTrackRef = 14
|
||||
GPSTrack = 15
|
||||
GPSImgDirectionRef = 16
|
||||
GPSImgDirection = 17
|
||||
GPSMapDatum = 18
|
||||
GPSDestLatitudeRef = 19
|
||||
GPSDestLatitude = 20
|
||||
GPSDestLongitudeRef = 21
|
||||
GPSDestLongitude = 22
|
||||
GPSDestBearingRef = 23
|
||||
GPSDestBearing = 24
|
||||
GPSDestDistanceRef = 25
|
||||
GPSDestDistance = 26
|
||||
GPSProcessingMethod = 27
|
||||
GPSAreaInformation = 28
|
||||
GPSDateStamp = 29
|
||||
GPSDifferential = 30
|
||||
GPSHPositioningError = 31
|
||||
GPSVersionID = 0x00
|
||||
GPSLatitudeRef = 0x01
|
||||
GPSLatitude = 0x02
|
||||
GPSLongitudeRef = 0x03
|
||||
GPSLongitude = 0x04
|
||||
GPSAltitudeRef = 0x05
|
||||
GPSAltitude = 0x06
|
||||
GPSTimeStamp = 0x07
|
||||
GPSSatellites = 0x08
|
||||
GPSStatus = 0x09
|
||||
GPSMeasureMode = 0x0A
|
||||
GPSDOP = 0x0B
|
||||
GPSSpeedRef = 0x0C
|
||||
GPSSpeed = 0x0D
|
||||
GPSTrackRef = 0x0E
|
||||
GPSTrack = 0x0F
|
||||
GPSImgDirectionRef = 0x10
|
||||
GPSImgDirection = 0x11
|
||||
GPSMapDatum = 0x12
|
||||
GPSDestLatitudeRef = 0x13
|
||||
GPSDestLatitude = 0x14
|
||||
GPSDestLongitudeRef = 0x15
|
||||
GPSDestLongitude = 0x16
|
||||
GPSDestBearingRef = 0x17
|
||||
GPSDestBearing = 0x18
|
||||
GPSDestDistanceRef = 0x19
|
||||
GPSDestDistance = 0x1A
|
||||
GPSProcessingMethod = 0x1B
|
||||
GPSAreaInformation = 0x1C
|
||||
GPSDateStamp = 0x1D
|
||||
GPSDifferential = 0x1E
|
||||
GPSHPositioningError = 0x1F
|
||||
|
||||
|
||||
"""Maps EXIF GPS tags to tag names."""
|
||||
|
@ -342,40 +342,41 @@ GPSTAGS = {i.value: i.name for i in GPS}
|
|||
|
||||
|
||||
class Interop(IntEnum):
|
||||
InteropIndex = 1
|
||||
InteropVersion = 2
|
||||
RelatedImageFileFormat = 4096
|
||||
RelatedImageWidth = 4097
|
||||
RelatedImageHeight = 4098
|
||||
InteropIndex = 0x0001
|
||||
InteropVersion = 0x0002
|
||||
RelatedImageFileFormat = 0x1000
|
||||
RelatedImageWidth = 0x1001
|
||||
RelatedImageHeight = 0x1002
|
||||
|
||||
|
||||
class IFD(IntEnum):
|
||||
Exif = 34665
|
||||
GPSInfo = 34853
|
||||
Makernote = 37500
|
||||
Interop = 40965
|
||||
Exif = 0x8769
|
||||
GPSInfo = 0x8825
|
||||
MakerNote = 0x927C
|
||||
Makernote = 0x927C # Deprecated
|
||||
Interop = 0xA005
|
||||
IFD1 = -1
|
||||
|
||||
|
||||
class LightSource(IntEnum):
|
||||
Unknown = 0
|
||||
Daylight = 1
|
||||
Fluorescent = 2
|
||||
Tungsten = 3
|
||||
Flash = 4
|
||||
Fine = 9
|
||||
Cloudy = 10
|
||||
Shade = 11
|
||||
DaylightFluorescent = 12
|
||||
DayWhiteFluorescent = 13
|
||||
CoolWhiteFluorescent = 14
|
||||
WhiteFluorescent = 15
|
||||
StandardLightA = 17
|
||||
StandardLightB = 18
|
||||
StandardLightC = 19
|
||||
D55 = 20
|
||||
D65 = 21
|
||||
D75 = 22
|
||||
D50 = 23
|
||||
ISO = 24
|
||||
Other = 255
|
||||
Unknown = 0x00
|
||||
Daylight = 0x01
|
||||
Fluorescent = 0x02
|
||||
Tungsten = 0x03
|
||||
Flash = 0x04
|
||||
Fine = 0x09
|
||||
Cloudy = 0x0A
|
||||
Shade = 0x0B
|
||||
DaylightFluorescent = 0x0C
|
||||
DayWhiteFluorescent = 0x0D
|
||||
CoolWhiteFluorescent = 0x0E
|
||||
WhiteFluorescent = 0x0F
|
||||
StandardLightA = 0x11
|
||||
StandardLightB = 0x12
|
||||
StandardLightC = 0x13
|
||||
D55 = 0x14
|
||||
D65 = 0x15
|
||||
D75 = 0x16
|
||||
D50 = 0x17
|
||||
ISO = 0x18
|
||||
Other = 0xFF
|
||||
|
|
|
@ -159,7 +159,7 @@ class FliImageFile(ImageFile.ImageFile):
|
|||
framesize = i32(s)
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@ class FpxImageFile(ImageFile.ImageFile):
|
|||
"raw",
|
||||
(x, y, x1, y1),
|
||||
i32(s, i) + 28,
|
||||
(self.rawmode,),
|
||||
self.rawmode,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ class FtexImageFile(ImageFile.ImageFile):
|
|||
self._mode = "RGBA"
|
||||
self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))]
|
||||
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:
|
||||
msg = f"Invalid texture compression format: {repr(format)}"
|
||||
raise ValueError(msg)
|
||||
|
|
|
@ -76,7 +76,7 @@ class GdImageFile(ImageFile.ImageFile):
|
|||
"raw",
|
||||
(0, 0) + self.size,
|
||||
7 + true_color_offset + 4 + 256 * 4,
|
||||
("L", 0, 1),
|
||||
"L",
|
||||
)
|
||||
]
|
||||
|
||||
|
|
|
@ -754,7 +754,7 @@ class Image:
|
|||
|
||||
def __setstate__(self, state: list[Any]) -> None:
|
||||
Image.__init__(self)
|
||||
info, mode, size, palette, data = state
|
||||
info, mode, size, palette, data = state[:5]
|
||||
self.info = info
|
||||
self._mode = mode
|
||||
self._size = size
|
||||
|
@ -1567,7 +1567,7 @@ class Image:
|
|||
for subifd_offset in subifd_offsets:
|
||||
ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
|
||||
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
|
||||
ifds.append((ifd1, exif._info.next))
|
||||
|
||||
|
@ -1579,11 +1579,11 @@ class Image:
|
|||
|
||||
fp = self.fp
|
||||
if ifd is not None:
|
||||
thumbnail_offset = ifd.get(513)
|
||||
thumbnail_offset = ifd.get(ExifTags.Base.JpegIFOffset)
|
||||
if thumbnail_offset is not None:
|
||||
thumbnail_offset += getattr(self, "_exif_offset", 0)
|
||||
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)
|
||||
|
||||
with open(fp) as im:
|
||||
|
@ -2558,7 +2558,7 @@ class Image:
|
|||
self._ensure_mutable()
|
||||
|
||||
save_all = params.pop("save_all", False)
|
||||
self.encoderinfo = params
|
||||
self.encoderinfo = {**getattr(self, "encoderinfo", {}), **params}
|
||||
self.encoderconfig: tuple[Any, ...] = ()
|
||||
|
||||
preinit()
|
||||
|
@ -2605,6 +2605,11 @@ class Image:
|
|||
except PermissionError:
|
||||
pass
|
||||
raise
|
||||
finally:
|
||||
try:
|
||||
del self.encoderinfo
|
||||
except AttributeError:
|
||||
pass
|
||||
if open_fp:
|
||||
fp.close()
|
||||
|
||||
|
@ -3886,7 +3891,7 @@ class Exif(_ExifBase):
|
|||
gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo)
|
||||
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``.
|
||||
|
||||
:py:mod:`~PIL.ExifTags` also has enum classes to provide names for data::
|
||||
|
@ -4020,6 +4025,9 @@ class Exif(_ExifBase):
|
|||
|
||||
head = self._get_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():
|
||||
if tag in [
|
||||
ExifTags.IFD.Exif,
|
||||
|
@ -4049,11 +4057,11 @@ class Exif(_ExifBase):
|
|||
ifd = self._get_ifd_dict(offset, tag)
|
||||
if ifd is not None:
|
||||
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:
|
||||
self.get_ifd(ExifTags.IFD.Exif)
|
||||
tag_data = self._ifds[ExifTags.IFD.Exif][tag]
|
||||
if tag == ExifTags.IFD.Makernote:
|
||||
if tag == ExifTags.IFD.MakerNote:
|
||||
from .TiffImagePlugin import ImageFileDirectory_v2
|
||||
|
||||
if tag_data[:8] == b"FUJIFILM":
|
||||
|
@ -4140,7 +4148,7 @@ class Exif(_ExifBase):
|
|||
ifd = {
|
||||
k: v
|
||||
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
|
||||
|
||||
|
|
|
@ -98,8 +98,8 @@ def _tilesort(t: _Tile) -> int:
|
|||
class _Tile(NamedTuple):
|
||||
codec_name: str
|
||||
extents: tuple[int, int, int, int] | None
|
||||
offset: int
|
||||
args: tuple[Any, ...] | str | None
|
||||
offset: int = 0
|
||||
args: tuple[Any, ...] | str | None = None
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -553,7 +553,7 @@ class Color3DLUT(MultibandFilter):
|
|||
ch_out = channels or ch_in
|
||||
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_out = 0
|
||||
for b in range(size_3d):
|
||||
|
|
|
@ -104,28 +104,17 @@ def grab(
|
|||
|
||||
def grabclipboard() -> Image.Image | list[str] | None:
|
||||
if sys.platform == "darwin":
|
||||
fh, filepath = tempfile.mkstemp(".png")
|
||||
os.close(fh)
|
||||
commands = [
|
||||
'set theFile to (open for access POSIX file "'
|
||||
+ filepath
|
||||
+ '" with write permission)',
|
||||
"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)
|
||||
p = subprocess.run(
|
||||
["osascript", "-e", "get the clipboard as «class PNGf»"],
|
||||
capture_output=True,
|
||||
)
|
||||
if p.returncode != 0:
|
||||
return None
|
||||
|
||||
im = None
|
||||
if os.stat(filepath).st_size != 0:
|
||||
im = Image.open(filepath)
|
||||
im.load()
|
||||
os.unlink(filepath)
|
||||
return im
|
||||
import binascii
|
||||
|
||||
data = io.BytesIO(binascii.unhexlify(p.stdout[11:-3]))
|
||||
return Image.open(data)
|
||||
elif sys.platform == "win32":
|
||||
fmt, data = Image.core.grabclipboard_win32()
|
||||
if fmt == "file": # CF_HDROP
|
||||
|
|
|
@ -22,7 +22,7 @@ import functools
|
|||
import operator
|
||||
import re
|
||||
from collections.abc import Sequence
|
||||
from typing import Protocol, cast
|
||||
from typing import Literal, Protocol, cast, overload
|
||||
|
||||
from . import ExifTags, Image, ImagePalette
|
||||
|
||||
|
@ -673,6 +673,16 @@ def solarize(image: Image.Image, threshold: int = 128) -> Image.Image:
|
|||
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:
|
||||
"""
|
||||
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,
|
||||
}.get(orientation)
|
||||
if method is not None:
|
||||
transposed_image = image.transpose(method)
|
||||
if in_place:
|
||||
image.im = transposed_image.im
|
||||
image._size = transposed_image._size
|
||||
image.im = image.im.transpose(method)
|
||||
image._size = image.im.size
|
||||
else:
|
||||
transposed_image = image.transpose(method)
|
||||
exif_image = image if in_place else transposed_image
|
||||
|
||||
exif = exif_image.getexif()
|
||||
|
|
|
@ -62,7 +62,7 @@ class ImtImageFile(ImageFile.ImageFile):
|
|||
"raw",
|
||||
(0, 0) + self.size,
|
||||
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":
|
||||
self.codec = "j2k"
|
||||
self._size, self._mode = _parse_codestream(self.fp)
|
||||
self._parse_comment()
|
||||
else:
|
||||
sig = sig + self.fp.read(8)
|
||||
|
||||
|
@ -262,6 +263,9 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
|||
if dpi is not None:
|
||||
self.info["dpi"] = dpi
|
||||
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()
|
||||
else:
|
||||
msg = "not a JPEG 2000 file"
|
||||
|
@ -296,10 +300,6 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
|
|||
]
|
||||
|
||||
def _parse_comment(self) -> None:
|
||||
hdr = self.fp.read(2)
|
||||
length = _binary.i16be(hdr)
|
||||
self.fp.seek(length - 2, os.SEEK_CUR)
|
||||
|
||||
while True:
|
||||
marker = self.fp.read(2)
|
||||
if not marker:
|
||||
|
|
|
@ -90,6 +90,9 @@ def APP(self: JpegImageFile, marker: int) -> None:
|
|||
else:
|
||||
if jfif_unit == 1:
|
||||
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_density"] = jfif_density
|
||||
elif marker == 0xFFE1 and s[:6] == b"Exif\0\0":
|
||||
|
@ -395,6 +398,13 @@ class JpegImageFile(ImageFile.ImageFile):
|
|||
return getattr(self, "_" + 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:
|
||||
"""
|
||||
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"")
|
||||
|
||||
MAX_BYTES_IN_MARKER = 65533
|
||||
xmp = info.get("xmp", im.info.get("xmp"))
|
||||
xmp = info.get("xmp")
|
||||
if xmp:
|
||||
overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
|
||||
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)
|
||||
|
||||
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:
|
||||
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):
|
||||
|
@ -188,7 +188,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
fp.write(o16(h))
|
||||
|
||||
# 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._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:
|
||||
if self.tile_post_rotate:
|
||||
|
|
|
@ -61,9 +61,7 @@ class PixarImageFile(ImageFile.ImageFile):
|
|||
# FIXME: to be continued...
|
||||
|
||||
# create tile descriptor (assuming "dumped")
|
||||
self.tile = [
|
||||
ImageFile._Tile("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))
|
||||
]
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 1024, self.mode)]
|
||||
|
||||
|
||||
#
|
||||
|
|
|
@ -32,7 +32,7 @@ class QoiImageFile(ImageFile.ImageFile):
|
|||
self._mode = "RGB" if channels == 3 else "RGBA"
|
||||
|
||||
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):
|
||||
|
|
|
@ -154,9 +154,7 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
self.rawmode = "F;32F"
|
||||
self._mode = "F"
|
||||
|
||||
self.tile = [
|
||||
ImageFile._Tile("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))
|
||||
]
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, offset, self.rawmode)]
|
||||
self._fp = self.fp # FIXME: hack
|
||||
|
||||
@property
|
||||
|
@ -211,26 +209,27 @@ class SpiderImageFile(ImageFile.ImageFile):
|
|||
|
||||
|
||||
# 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"""
|
||||
if filelist is None or len(filelist) < 1:
|
||||
return None
|
||||
|
||||
imglist = []
|
||||
byte_imgs = []
|
||||
for img in filelist:
|
||||
if not os.path.exists(img):
|
||||
print(f"unable to find {img}")
|
||||
continue
|
||||
try:
|
||||
with Image.open(img) as im:
|
||||
im = im.convert2byte()
|
||||
assert isinstance(im, SpiderImageFile)
|
||||
byte_im = im.convert2byte()
|
||||
except Exception:
|
||||
if not isSpiderImage(img):
|
||||
print(f"{img} is not a Spider image file")
|
||||
continue
|
||||
im.info["filename"] = img
|
||||
imglist.append(im)
|
||||
return imglist
|
||||
byte_im.info["filename"] = img
|
||||
byte_imgs.append(byte_im)
|
||||
return byte_imgs
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
|
@ -280,9 +279,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
fp.writelines(hdr)
|
||||
|
||||
rawmode = "F;32NF" # 32-bit native floating point
|
||||
ImageFile._save(
|
||||
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
|
||||
)
|
||||
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
|
||||
|
||||
|
||||
def _save_spider(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||
|
|
|
@ -582,7 +582,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
|
||||
def __init__(
|
||||
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,
|
||||
group: int | None = None,
|
||||
) -> None:
|
||||
|
@ -949,16 +949,26 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
warnings.warn(str(msg))
|
||||
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:
|
||||
# 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]] = []
|
||||
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
|
||||
|
||||
# pass 1: convert tags to binary format
|
||||
# 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()):
|
||||
if tag == STRIPOFFSETS:
|
||||
stripoffsets = len(entries)
|
||||
|
@ -966,11 +976,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
logger.debug("Tag %s, Type: %s, Value: %s", tag, typ, repr(value))
|
||||
is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
|
||||
if is_ifd:
|
||||
if self._endian == "<":
|
||||
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)
|
||||
ifd = ImageFileDirectory_v2(self._get_ifh(), group=tag)
|
||||
values = self._tags_v2[tag]
|
||||
for ifd_tag, ifd_value in values.items():
|
||||
ifd[ifd_tag] = ifd_value
|
||||
|
@ -993,10 +999,10 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
else:
|
||||
count = len(values)
|
||||
# figure out if data fits into the entry
|
||||
if len(data) <= 4:
|
||||
entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
|
||||
if len(data) <= fmt_size:
|
||||
entries.append((tag, typ, count, data.ljust(fmt_size, b"\0"), b""))
|
||||
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
|
||||
|
||||
# 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)]
|
||||
data = self._write_dispatch[typ](self, *values)
|
||||
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
|
||||
|
||||
# pass 2: write entries to file
|
||||
for tag, typ, count, value, data in entries:
|
||||
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 --
|
||||
result += b"\0\0\0\0" # end of entries
|
||||
|
@ -1028,8 +1036,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
|||
|
||||
def save(self, fp: IO[bytes]) -> int:
|
||||
if fp.tell() == 0: # skip TIFF header on subsequent pages
|
||||
# tiff header -- PIL always starts the first IFD at offset 8
|
||||
fp.write(self._prefix + self._pack("HL", 42, 8))
|
||||
fp.write(self._get_ifh())
|
||||
|
||||
offset = fp.tell()
|
||||
result = self.tobytes(offset)
|
||||
|
@ -1432,8 +1439,12 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING))
|
||||
|
||||
# size
|
||||
xsize = self.tag_v2.get(IMAGEWIDTH)
|
||||
ysize = self.tag_v2.get(IMAGELENGTH)
|
||||
try:
|
||||
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):
|
||||
msg = "Invalid dimensions"
|
||||
raise ValueError(msg)
|
||||
|
@ -1555,17 +1566,6 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
# fillorder==2 modes have a corresponding
|
||||
# fillorder=1 mode
|
||||
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
|
||||
# unpacked straight into RGB values
|
||||
if (
|
||||
|
@ -1574,6 +1574,14 @@ class TiffImageFile(ImageFile.ImageFile):
|
|||
and self._planar_configuration == 1
|
||||
):
|
||||
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
|
||||
# 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"
|
||||
raise OSError(msg) from e
|
||||
|
||||
ifd = ImageFileDirectory_v2(prefix=prefix)
|
||||
|
||||
encoderinfo = im.encoderinfo
|
||||
encoderconfig = im.encoderconfig
|
||||
|
||||
ifd = ImageFileDirectory_v2(prefix=prefix)
|
||||
if encoderinfo.get("big_tiff"):
|
||||
ifd._bigtiff = True
|
||||
|
||||
try:
|
||||
compression = encoderinfo["compression"]
|
||||
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):
|
||||
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]
|
||||
elif not (isinstance(value, (int, float, str, bytes))):
|
||||
continue
|
||||
|
|
|
@ -92,6 +92,9 @@ class WmfStubImageFile(ImageFile.StubImageFile):
|
|||
|
||||
# get units per inch
|
||||
self._inch = word(s, 14)
|
||||
if self._inch == 0:
|
||||
msg = "Invalid inch"
|
||||
raise ValueError(msg)
|
||||
|
||||
# get bounding box
|
||||
x0 = short(s, 6)
|
||||
|
|
|
@ -74,9 +74,7 @@ class XVThumbImageFile(ImageFile.ImageFile):
|
|||
self.palette = ImagePalette.raw("RGB", PALETTE)
|
||||
|
||||
self.tile = [
|
||||
ImageFile._Tile(
|
||||
"raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1)
|
||||
)
|
||||
ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), self.mode)
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ class XbmImageFile(ImageFile.ImageFile):
|
|||
self._mode = "1"
|
||||
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:
|
||||
|
@ -85,7 +85,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
|||
|
||||
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")
|
||||
|
||||
|
|
|
@ -101,9 +101,7 @@ class XpmImageFile(ImageFile.ImageFile):
|
|||
self._mode = "P"
|
||||
self.palette = ImagePalette.raw("RGB", b"".join(palette))
|
||||
|
||||
self.tile = [
|
||||
ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))
|
||||
]
|
||||
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), "P")]
|
||||
|
||||
def load_read(self, read_bytes: int) -> bytes:
|
||||
#
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Master version for Pillow
|
||||
from __future__ import annotations
|
||||
|
||||
__version__ = "11.1.0.dev0"
|
||||
__version__ = "11.2.0.dev0"
|
||||
|
|
|
@ -346,10 +346,10 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
|
||||
// transform color channels only
|
||||
for (i = 0; i < im->ysize; i++) {
|
||||
// transform color channels only
|
||||
for (i = 0; i < im->ysize; i++) {
|
||||
cmsDoTransform(hTransform, im->image[i], imOut->image[i], im->xsize);
|
||||
}
|
||||
|
||||
|
@ -362,9 +362,9 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
|
|||
// enough available on all platforms, so we polyfill it here for now.
|
||||
pyCMScopyAux(hTransform, imOut, im);
|
||||
|
||||
Py_END_ALLOW_THREADS
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static cmsHTRANSFORM
|
||||
|
@ -378,17 +378,17 @@ _buildTransform(
|
|||
) {
|
||||
cmsHTRANSFORM hTransform;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
|
||||
/* create the transform */
|
||||
hTransform = cmsCreateTransform(
|
||||
hInputProfile,
|
||||
findLCMStype(sInMode),
|
||||
hOutputProfile,
|
||||
findLCMStype(sOutMode),
|
||||
iRenderingIntent,
|
||||
cmsFLAGS
|
||||
);
|
||||
/* create the transform */
|
||||
hTransform = cmsCreateTransform(
|
||||
hInputProfile,
|
||||
findLCMStype(sInMode),
|
||||
hOutputProfile,
|
||||
findLCMStype(sOutMode),
|
||||
iRenderingIntent,
|
||||
cmsFLAGS
|
||||
);
|
||||
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
|
@ -412,19 +412,19 @@ _buildProofTransform(
|
|||
) {
|
||||
cmsHTRANSFORM hTransform;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
|
||||
/* create the transform */
|
||||
hTransform = cmsCreateProofingTransform(
|
||||
hInputProfile,
|
||||
findLCMStype(sInMode),
|
||||
hOutputProfile,
|
||||
findLCMStype(sOutMode),
|
||||
hProofProfile,
|
||||
iRenderingIntent,
|
||||
iProofIntent,
|
||||
cmsFLAGS
|
||||
);
|
||||
/* create the transform */
|
||||
hTransform = cmsCreateProofingTransform(
|
||||
hInputProfile,
|
||||
findLCMStype(sInMode),
|
||||
hOutputProfile,
|
||||
findLCMStype(sOutMode),
|
||||
hProofProfile,
|
||||
iRenderingIntent,
|
||||
iProofIntent,
|
||||
cmsFLAGS
|
||||
);
|
||||
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
|
|
|
@ -690,24 +690,26 @@ PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
|
|||
SetWindowLongPtr(wnd, 0, (LONG_PTR)callback);
|
||||
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 */
|
||||
Py_END_ALLOW_THREADS
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
return Py_BuildValue(F_HANDLE, wnd);
|
||||
return Py_BuildValue(F_HANDLE, wnd);
|
||||
}
|
||||
|
||||
PyObject *
|
||||
PyImaging_EventLoopWin32(PyObject *self, PyObject *args) {
|
||||
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);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
Py_END_ALLOW_THREADS
|
||||
Py_END_ALLOW_THREADS;
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
|
|
|
@ -736,7 +736,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
}
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
|
@ -929,7 +929,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
);
|
||||
} else if (type == TIFF_LONG) {
|
||||
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) {
|
||||
status = ImagingLibTiffSetField(
|
||||
|
@ -959,6 +959,10 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
|
|||
status = ImagingLibTiffSetField(
|
||||
&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 {
|
||||
TRACE(
|
||||
("Unhandled type for key %d : %s \n",
|
||||
|
|
|
@ -501,7 +501,8 @@ polygon_generic(
|
|||
// Needed to draw consistent polygons
|
||||
xx[j] = xx[j - 1];
|
||||
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
|
||||
for (k = 0; k < i; k++) {
|
||||
Edge *other_edge = edge_table[k];
|
||||
|
@ -510,10 +511,8 @@ polygon_generic(
|
|||
continue;
|
||||
}
|
||||
// Check if the two edges join to make a corner
|
||||
if (((ymin == current->ymin && ymin == other_edge->ymin) ||
|
||||
(ymin == current->ymax && ymin == other_edge->ymax)) &&
|
||||
xx[j - 1] == (ymin - other_edge->y0) * other_edge->dx +
|
||||
other_edge->x0) {
|
||||
if (xx[j - 1] ==
|
||||
(ymin - other_edge->y0) * other_edge->dx + other_edge->x0) {
|
||||
// Determine points from the edges on the next row
|
||||
// Or if this is the last row, check the previous row
|
||||
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
|
||||
ours before including the JPEG code. */
|
||||
|
||||
#if __STDC_VERSION__ >= 199901L /* C99+ */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define INT8 int8_t
|
||||
|
@ -55,34 +53,6 @@
|
|||
#define INT32 int32_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 */
|
||||
|
||||
/* assume IEEE; tweak if necessary (patches are welcome) */
|
||||
|
|
|
@ -640,7 +640,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
|
|||
opj_dparameters_t params;
|
||||
OPJ_COLOR_SPACE color_space;
|
||||
j2k_unpacker_t unpack = NULL;
|
||||
size_t buffer_size = 0, tile_bytes = 0;
|
||||
size_t tile_bytes = 0;
|
||||
unsigned n, tile_height, tile_width;
|
||||
int subsampling;
|
||||
int total_component_width = 0;
|
||||
|
@ -870,7 +870,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
|
|||
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 */
|
||||
UINT8 *new = realloc(state->buffer, tile_info.data_size);
|
||||
if (!new) {
|
||||
|
@ -883,7 +883,6 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
|
|||
to valgrind errors. */
|
||||
memset(new, 0, tile_info.data_size);
|
||||
state->buffer = new;
|
||||
buffer_size = tile_info.data_size;
|
||||
}
|
||||
|
||||
if (!opj_decode_tile_data(
|
||||
|
|
|
@ -330,6 +330,13 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) {
|
|||
components = 4;
|
||||
color_space = OPJ_CLRSPC_SRGB;
|
||||
pack = j2k_pack_rgba;
|
||||
#if ((OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR == 5 && OPJ_VERSION_BUILD >= 3) || \
|
||||
(OPJ_VERSION_MAJOR == 2 && OPJ_VERSION_MINOR > 5) || OPJ_VERSION_MAJOR > 2)
|
||||
} else if (strcmp(im->mode, "CMYK") == 0) {
|
||||
components = 4;
|
||||
color_space = OPJ_CLRSPC_CMYK;
|
||||
pack = j2k_pack_rgba;
|
||||
#endif
|
||||
} else {
|
||||
state->errcode = IMAGING_CODEC_BROKEN;
|
||||
state->state = J2K_STATE_FAILED;
|
||||
|
|
|
@ -1695,6 +1695,7 @@ static struct {
|
|||
|
||||
#ifdef WORDS_BIGENDIAN
|
||||
{"RGB", "RGB;16N", 48, unpackRGB16B},
|
||||
{"RGB", "RGBX;16N", 64, unpackRGBA16B},
|
||||
{"RGBA", "RGBa;16N", 64, unpackRGBa16B},
|
||||
{"RGBA", "RGBA;16N", 64, unpackRGBA16B},
|
||||
{"RGBX", "RGBX;16N", 64, unpackRGBA16B},
|
||||
|
@ -1708,6 +1709,7 @@ static struct {
|
|||
{"RGBA", "A;16N", 16, band316B},
|
||||
#else
|
||||
{"RGB", "RGB;16N", 48, unpackRGB16L},
|
||||
{"RGB", "RGBX;16N", 64, unpackRGBA16L},
|
||||
{"RGBA", "RGBa;16N", 64, unpackRGBa16L},
|
||||
{"RGBA", "RGBA;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",
|
||||
"LCMS2": "2.16",
|
||||
"LIBPNG": "1.6.44",
|
||||
"LIBWEBP": "1.4.0",
|
||||
"LIBWEBP": "1.5.0",
|
||||
"OPENJPEG": "2.5.3",
|
||||
"TIFF": "4.6.0",
|
||||
"XZ": "5.6.3",
|
||||
"ZLIBNG": "2.2.2",
|
||||
"ZLIBNG": "2.2.3",
|
||||
"LIBAVIF": "1.1.1",
|
||||
}
|
||||
V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "")
|
||||
|
@ -529,7 +529,10 @@ def extract_dep(url: str, filename: str, prefs: dict[str, str]) -> None:
|
|||
if sources_dir_abs != member_prefix:
|
||||
msg = "Attempted Path Traversal in Tar File"
|
||||
raise RuntimeError(msg)
|
||||
tgz.extractall(sources_dir)
|
||||
if sys.version_info >= (3, 12):
|
||||
tgz.extractall(sources_dir, filter="data")
|
||||
else:
|
||||
tgz.extractall(sources_dir)
|
||||
else:
|
||||
msg = "Unknown archive type: " + filename
|
||||
raise RuntimeError(msg)
|
||||
|
|
Loading…
Reference in New Issue
Block a user