mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-07-18 04:02:25 +03:00
Merge branch 'main' into cibuildwheel_delocate
This commit is contained in:
commit
67ffa32ef0
2
.github/workflows/test-cygwin.yml
vendored
2
.github/workflows/test-cygwin.yml
vendored
|
@ -52,7 +52,7 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install Cygwin
|
- name: Install Cygwin
|
||||||
uses: cygwin/cygwin-install-action@v4
|
uses: cygwin/cygwin-install-action@v5
|
||||||
with:
|
with:
|
||||||
packages: >
|
packages: >
|
||||||
gcc-g++
|
gcc-g++
|
||||||
|
|
2
.github/workflows/wheels-dependencies.sh
vendored
2
.github/workflows/wheels-dependencies.sh
vendored
|
@ -39,7 +39,7 @@ ARCHIVE_SDIR=pillow-depends-main
|
||||||
# Package versions for fresh source builds
|
# Package versions for fresh source builds
|
||||||
FREETYPE_VERSION=2.13.3
|
FREETYPE_VERSION=2.13.3
|
||||||
HARFBUZZ_VERSION=10.1.0
|
HARFBUZZ_VERSION=10.1.0
|
||||||
LIBPNG_VERSION=1.6.44
|
LIBPNG_VERSION=1.6.45
|
||||||
JPEGTURBO_VERSION=3.1.0
|
JPEGTURBO_VERSION=3.1.0
|
||||||
OPENJPEG_VERSION=2.5.3
|
OPENJPEG_VERSION=2.5.3
|
||||||
XZ_VERSION=5.6.3
|
XZ_VERSION=5.6.3
|
||||||
|
|
2
.github/workflows/wheels.yml
vendored
2
.github/workflows/wheels.yml
vendored
|
@ -13,6 +13,7 @@ on:
|
||||||
paths:
|
paths:
|
||||||
- ".ci/requirements-cibw.txt"
|
- ".ci/requirements-cibw.txt"
|
||||||
- ".github/workflows/wheel*"
|
- ".github/workflows/wheel*"
|
||||||
|
- "pyproject.toml"
|
||||||
- "setup.py"
|
- "setup.py"
|
||||||
- "wheels/*"
|
- "wheels/*"
|
||||||
- "winbuild/build_prepare.py"
|
- "winbuild/build_prepare.py"
|
||||||
|
@ -23,6 +24,7 @@ on:
|
||||||
paths:
|
paths:
|
||||||
- ".ci/requirements-cibw.txt"
|
- ".ci/requirements-cibw.txt"
|
||||||
- ".github/workflows/wheel*"
|
- ".github/workflows/wheel*"
|
||||||
|
- "pyproject.toml"
|
||||||
- "setup.py"
|
- "setup.py"
|
||||||
- "wheels/*"
|
- "wheels/*"
|
||||||
- "winbuild/build_prepare.py"
|
- "winbuild/build_prepare.py"
|
||||||
|
|
|
@ -253,8 +253,7 @@ def test_truncated_mask() -> None:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with Image.open(io.BytesIO(data)) as im:
|
with Image.open(io.BytesIO(data)) as im:
|
||||||
with Image.open("Tests/images/hopper_mask.png") as expected:
|
assert im.mode == "1"
|
||||||
assert im.mode == "1"
|
|
||||||
|
|
||||||
# 32 bpp
|
# 32 bpp
|
||||||
output = io.BytesIO()
|
output = io.BytesIO()
|
||||||
|
|
|
@ -181,7 +181,7 @@ 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):
|
def test_dpi_jfif_cm(self) -> None:
|
||||||
with Image.open("Tests/images/jfif_unit_cm.jpg") as im:
|
with Image.open("Tests/images/jfif_unit_cm.jpg") as im:
|
||||||
assert im.info["dpi"] == (2.54, 5.08)
|
assert im.info["dpi"] == (2.54, 5.08)
|
||||||
|
|
||||||
|
@ -281,7 +281,10 @@ class TestFileJpeg:
|
||||||
assert not im2.info.get("progressive")
|
assert not im2.info.get("progressive")
|
||||||
assert im3.info.get("progressive")
|
assert im3.info.get("progressive")
|
||||||
|
|
||||||
assert_image_equal(im1, im3)
|
if features.check_feature("mozjpeg"):
|
||||||
|
assert_image_similar(im1, im3, 9.39)
|
||||||
|
else:
|
||||||
|
assert_image_equal(im1, im3)
|
||||||
assert im1_bytes >= im3_bytes
|
assert im1_bytes >= im3_bytes
|
||||||
|
|
||||||
def test_progressive_large_buffer(self, tmp_path: Path) -> None:
|
def test_progressive_large_buffer(self, tmp_path: Path) -> None:
|
||||||
|
@ -423,8 +426,12 @@ class TestFileJpeg:
|
||||||
|
|
||||||
im2 = self.roundtrip(hopper(), progressive=1)
|
im2 = self.roundtrip(hopper(), progressive=1)
|
||||||
im3 = self.roundtrip(hopper(), progression=1) # compatibility
|
im3 = self.roundtrip(hopper(), progression=1) # compatibility
|
||||||
assert_image_equal(im1, im2)
|
if features.check_feature("mozjpeg"):
|
||||||
assert_image_equal(im1, im3)
|
assert_image_similar(im1, im2, 9.39)
|
||||||
|
assert_image_similar(im1, im3, 9.39)
|
||||||
|
else:
|
||||||
|
assert_image_equal(im1, im2)
|
||||||
|
assert_image_equal(im1, im3)
|
||||||
assert im2.info.get("progressive")
|
assert im2.info.get("progressive")
|
||||||
assert im2.info.get("progression")
|
assert im2.info.get("progression")
|
||||||
assert im3.info.get("progressive")
|
assert im3.info.get("progressive")
|
||||||
|
@ -1030,7 +1037,7 @@ class TestFileJpeg:
|
||||||
|
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
im.tile = [
|
im.tile = [
|
||||||
("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)),
|
ImageFile._Tile("INFINITE", (0, 0, 128, 128), 0, ("RGB", 0, 1)),
|
||||||
]
|
]
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
im.load()
|
im.load()
|
||||||
|
|
|
@ -1146,7 +1146,7 @@ class TestFileLibTiff(LibTiffTestCase):
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
# Assert that the error code is IMAGING_CODEC_MEMORY
|
# Assert that the error code is IMAGING_CODEC_MEMORY
|
||||||
assert str(e.value) == "-9"
|
assert str(e.value) == "decoder error -9"
|
||||||
|
|
||||||
@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg"))
|
@pytest.mark.parametrize("compression", ("tiff_adobe_deflate", "jpeg"))
|
||||||
def test_save_multistrip(self, compression: str, tmp_path: Path) -> None:
|
def test_save_multistrip(self, compression: str, tmp_path: Path) -> None:
|
||||||
|
|
|
@ -618,7 +618,7 @@ class TestFilePng:
|
||||||
with Image.open("Tests/images/truncated_image.png") as im:
|
with Image.open("Tests/images/truncated_image.png") as im:
|
||||||
# The file is truncated
|
# The file is truncated
|
||||||
with pytest.raises(OSError):
|
with pytest.raises(OSError):
|
||||||
im.text()
|
im.text
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
ImageFile.LOAD_TRUNCATED_IMAGES = True
|
||||||
assert isinstance(im.text, dict)
|
assert isinstance(im.text, dict)
|
||||||
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
ImageFile.LOAD_TRUNCATED_IMAGES = False
|
||||||
|
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image, ImageSequence, SpiderImagePlugin
|
from PIL import Image, SpiderImagePlugin
|
||||||
|
|
||||||
from .helper import assert_image_equal, hopper, is_pypy
|
from .helper import assert_image_equal, hopper, is_pypy
|
||||||
|
|
||||||
|
@ -153,8 +153,8 @@ def test_nonstack_file() -> None:
|
||||||
|
|
||||||
def test_nonstack_dos() -> None:
|
def test_nonstack_dos() -> None:
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
for i, frame in enumerate(ImageSequence.Iterator(im)):
|
with pytest.raises(EOFError):
|
||||||
assert i <= 1, "Non-stack DOS file test failed"
|
im.seek(0)
|
||||||
|
|
||||||
|
|
||||||
# for issue #4093
|
# for issue #4093
|
||||||
|
|
|
@ -117,10 +117,16 @@ class TestFileTiff:
|
||||||
|
|
||||||
def test_bigtiff_save(self, tmp_path: Path) -> None:
|
def test_bigtiff_save(self, tmp_path: Path) -> None:
|
||||||
outfile = str(tmp_path / "temp.tif")
|
outfile = str(tmp_path / "temp.tif")
|
||||||
hopper().save(outfile, big_tiff=True)
|
im = hopper()
|
||||||
|
im.save(outfile, big_tiff=True)
|
||||||
|
|
||||||
with Image.open(outfile) as im:
|
with Image.open(outfile) as reloaded:
|
||||||
assert im.tag_v2._bigtiff is True
|
assert reloaded.tag_v2._bigtiff is True
|
||||||
|
|
||||||
|
im.save(outfile, save_all=True, append_images=[im], big_tiff=True)
|
||||||
|
|
||||||
|
with Image.open(outfile) as reloaded:
|
||||||
|
assert reloaded.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"):
|
||||||
|
@ -740,7 +746,7 @@ class TestFileTiff:
|
||||||
assert reread.n_frames == 3
|
assert reread.n_frames == 3
|
||||||
|
|
||||||
def test_fixoffsets(self) -> None:
|
def test_fixoffsets(self) -> None:
|
||||||
b = BytesIO(b"II\x2a\x00\x00\x00\x00\x00")
|
b = BytesIO(b"II\x2A\x00\x00\x00\x00\x00")
|
||||||
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
b.seek(0)
|
b.seek(0)
|
||||||
a.fixOffsets(1, isShort=True)
|
a.fixOffsets(1, isShort=True)
|
||||||
|
@ -753,6 +759,37 @@ class TestFileTiff:
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
a.fixOffsets(1)
|
a.fixOffsets(1)
|
||||||
|
|
||||||
|
b = BytesIO(b"II\x2A\x00\x00\x00\x00\x00")
|
||||||
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
|
a.offsetOfNewPage = 2**16
|
||||||
|
|
||||||
|
b.seek(0)
|
||||||
|
a.fixOffsets(1, isShort=True)
|
||||||
|
|
||||||
|
b = BytesIO(b"II\x2B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||||
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
|
a.offsetOfNewPage = 2**32
|
||||||
|
|
||||||
|
b.seek(0)
|
||||||
|
a.fixOffsets(1, isShort=True)
|
||||||
|
|
||||||
|
b.seek(0)
|
||||||
|
a.fixOffsets(1, isLong=True)
|
||||||
|
|
||||||
|
def test_appending_tiff_writer_writelong(self) -> None:
|
||||||
|
data = b"II\x2A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||||
|
b = BytesIO(data)
|
||||||
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
|
a.writeLong(2**32 - 1)
|
||||||
|
assert b.getvalue() == data + b"\xff\xff\xff\xff"
|
||||||
|
|
||||||
|
def test_appending_tiff_writer_rewritelastshorttolong(self) -> None:
|
||||||
|
data = b"II\x2A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||||
|
b = BytesIO(data)
|
||||||
|
with TiffImagePlugin.AppendingTiffWriter(b) as a:
|
||||||
|
a.rewriteLastShortToLong(2**32 - 1)
|
||||||
|
assert b.getvalue() == data[:-2] + b"\xff\xff\xff\xff"
|
||||||
|
|
||||||
def test_saving_icc_profile(self, tmp_path: Path) -> None:
|
def test_saving_icc_profile(self, tmp_path: Path) -> None:
|
||||||
# Tests saving TIFF with icc_profile set.
|
# Tests saving TIFF with icc_profile set.
|
||||||
# At the time of writing this will only work for non-compressed tiffs
|
# At the time of writing this will only work for non-compressed tiffs
|
||||||
|
|
|
@ -374,14 +374,10 @@ class BLP1Decoder(_BLPBaseDecoder):
|
||||||
image = JpegImageFile(BytesIO(data))
|
image = JpegImageFile(BytesIO(data))
|
||||||
Image._decompression_bomb_check(image.size)
|
Image._decompression_bomb_check(image.size)
|
||||||
if image.mode == "CMYK":
|
if image.mode == "CMYK":
|
||||||
decoder_name, extents, offset, args = image.tile[0]
|
args = image.tile[0].args
|
||||||
assert isinstance(args, tuple)
|
assert isinstance(args, tuple)
|
||||||
image.tile = [
|
image.tile = [image.tile[0]._replace(args=(args[0], "CMYK"))]
|
||||||
ImageFile._Tile(decoder_name, extents, offset, (args[0], "CMYK"))
|
self.set_as_raw(image.convert("RGB").tobytes(), "BGR")
|
||||||
]
|
|
||||||
r, g, b = image.convert("RGB").split()
|
|
||||||
reversed_image = Image.merge("RGB", (b, g, r))
|
|
||||||
self.set_as_raw(reversed_image.tobytes())
|
|
||||||
|
|
||||||
|
|
||||||
class BLP2Decoder(_BLPBaseDecoder):
|
class BLP2Decoder(_BLPBaseDecoder):
|
||||||
|
|
|
@ -949,7 +949,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
warnings.warn(str(msg))
|
warnings.warn(str(msg))
|
||||||
return
|
return
|
||||||
|
|
||||||
def _get_ifh(self):
|
def _get_ifh(self) -> bytes:
|
||||||
ifh = self._prefix + self._pack("H", 43 if self._bigtiff else 42)
|
ifh = self._prefix + self._pack("H", 43 if self._bigtiff else 42)
|
||||||
if self._bigtiff:
|
if self._bigtiff:
|
||||||
ifh += self._pack("HH", 8, 0)
|
ifh += self._pack("HH", 8, 0)
|
||||||
|
@ -962,13 +962,16 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
result = self._pack("Q" if self._bigtiff else "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 += len(result) + len(self._tags_v2) * (20 if self._bigtiff else 12) + 4
|
|
||||||
|
fmt = "Q" if self._bigtiff else "L"
|
||||||
|
fmt_size = 8 if self._bigtiff else 4
|
||||||
|
offset += (
|
||||||
|
len(result) + len(self._tags_v2) * (20 if self._bigtiff else 12) + fmt_size
|
||||||
|
)
|
||||||
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)
|
||||||
|
@ -1024,7 +1027,7 @@ class ImageFileDirectory_v2(_IFDv2Base):
|
||||||
)
|
)
|
||||||
|
|
||||||
# -- overwrite here for multi-page --
|
# -- overwrite here for multi-page --
|
||||||
result += b"\0\0\0\0" # end of entries
|
result += self._pack(fmt, 0) # end of entries
|
||||||
|
|
||||||
# pass 3: write auxiliary data to file
|
# pass 3: write auxiliary data to file
|
||||||
for tag, typ, count, value, data in entries:
|
for tag, typ, count, value, data in entries:
|
||||||
|
@ -1406,7 +1409,8 @@ class TiffImageFile(ImageFile.ImageFile):
|
||||||
self.fp = None # might be shared
|
self.fp = None # might be shared
|
||||||
|
|
||||||
if err < 0:
|
if err < 0:
|
||||||
raise OSError(err)
|
msg = f"decoder error {err}"
|
||||||
|
raise OSError(msg)
|
||||||
|
|
||||||
return Image.Image.load(self)
|
return Image.Image.load(self)
|
||||||
|
|
||||||
|
@ -2043,20 +2047,21 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
self.offsetOfNewPage = 0
|
self.offsetOfNewPage = 0
|
||||||
|
|
||||||
self.IIMM = iimm = self.f.read(4)
|
self.IIMM = iimm = self.f.read(4)
|
||||||
|
self._bigtiff = b"\x2B" in iimm
|
||||||
if not iimm:
|
if not iimm:
|
||||||
# empty file - first page
|
# empty file - first page
|
||||||
self.isFirst = True
|
self.isFirst = True
|
||||||
return
|
return
|
||||||
|
|
||||||
self.isFirst = False
|
self.isFirst = False
|
||||||
if iimm == b"II\x2a\x00":
|
if iimm not in PREFIXES:
|
||||||
self.setEndian("<")
|
|
||||||
elif iimm == b"MM\x00\x2a":
|
|
||||||
self.setEndian(">")
|
|
||||||
else:
|
|
||||||
msg = "Invalid TIFF file header"
|
msg = "Invalid TIFF file header"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
self.setEndian("<" if iimm.startswith(II) else ">")
|
||||||
|
|
||||||
|
if self._bigtiff:
|
||||||
|
self.f.seek(4, os.SEEK_CUR)
|
||||||
self.skipIFDs()
|
self.skipIFDs()
|
||||||
self.goToEnd()
|
self.goToEnd()
|
||||||
|
|
||||||
|
@ -2076,11 +2081,13 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
msg = "IIMM of new page doesn't match IIMM of first page"
|
msg = "IIMM of new page doesn't match IIMM of first page"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
ifd_offset = self.readLong()
|
if self._bigtiff:
|
||||||
|
self.f.seek(4, os.SEEK_CUR)
|
||||||
|
ifd_offset = self._read(8 if self._bigtiff else 4)
|
||||||
ifd_offset += self.offsetOfNewPage
|
ifd_offset += self.offsetOfNewPage
|
||||||
assert self.whereToWriteNewIFDOffset is not None
|
assert self.whereToWriteNewIFDOffset is not None
|
||||||
self.f.seek(self.whereToWriteNewIFDOffset)
|
self.f.seek(self.whereToWriteNewIFDOffset)
|
||||||
self.writeLong(ifd_offset)
|
self._write(ifd_offset, 8 if self._bigtiff else 4)
|
||||||
self.f.seek(ifd_offset)
|
self.f.seek(ifd_offset)
|
||||||
self.fixIFD()
|
self.fixIFD()
|
||||||
|
|
||||||
|
@ -2126,18 +2133,20 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
self.endian = endian
|
self.endian = endian
|
||||||
self.longFmt = f"{self.endian}L"
|
self.longFmt = f"{self.endian}L"
|
||||||
self.shortFmt = f"{self.endian}H"
|
self.shortFmt = f"{self.endian}H"
|
||||||
self.tagFormat = f"{self.endian}HHL"
|
self.tagFormat = f"{self.endian}HH" + ("Q" if self._bigtiff else "L")
|
||||||
|
|
||||||
def skipIFDs(self) -> None:
|
def skipIFDs(self) -> None:
|
||||||
while True:
|
while True:
|
||||||
ifd_offset = self.readLong()
|
ifd_offset = self._read(8 if self._bigtiff else 4)
|
||||||
if ifd_offset == 0:
|
if ifd_offset == 0:
|
||||||
self.whereToWriteNewIFDOffset = self.f.tell() - 4
|
self.whereToWriteNewIFDOffset = self.f.tell() - (
|
||||||
|
8 if self._bigtiff else 4
|
||||||
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
self.f.seek(ifd_offset)
|
self.f.seek(ifd_offset)
|
||||||
num_tags = self.readShort()
|
num_tags = self._read(8 if self._bigtiff else 2)
|
||||||
self.f.seek(num_tags * 12, os.SEEK_CUR)
|
self.f.seek(num_tags * (20 if self._bigtiff else 12), os.SEEK_CUR)
|
||||||
|
|
||||||
def write(self, data: Buffer, /) -> int:
|
def write(self, data: Buffer, /) -> int:
|
||||||
return self.f.write(data)
|
return self.f.write(data)
|
||||||
|
@ -2167,17 +2176,19 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
msg = f"wrote only {bytes_written} bytes but wanted {expected}"
|
msg = f"wrote only {bytes_written} bytes but wanted {expected}"
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
def rewriteLastShortToLong(self, value: int) -> None:
|
def _rewriteLast(
|
||||||
self.f.seek(-2, os.SEEK_CUR)
|
self, value: int, field_size: int, new_field_size: int = 0
|
||||||
bytes_written = self.f.write(struct.pack(self.longFmt, value))
|
) -> None:
|
||||||
self._verify_bytes_written(bytes_written, 4)
|
|
||||||
|
|
||||||
def _rewriteLast(self, value: int, field_size: int) -> None:
|
|
||||||
self.f.seek(-field_size, os.SEEK_CUR)
|
self.f.seek(-field_size, os.SEEK_CUR)
|
||||||
|
if not new_field_size:
|
||||||
|
new_field_size = field_size
|
||||||
bytes_written = self.f.write(
|
bytes_written = self.f.write(
|
||||||
struct.pack(self.endian + self._fmt(field_size), value)
|
struct.pack(self.endian + self._fmt(new_field_size), value)
|
||||||
)
|
)
|
||||||
self._verify_bytes_written(bytes_written, field_size)
|
self._verify_bytes_written(bytes_written, new_field_size)
|
||||||
|
|
||||||
|
def rewriteLastShortToLong(self, value: int) -> None:
|
||||||
|
self._rewriteLast(value, 2, 4)
|
||||||
|
|
||||||
def rewriteLastShort(self, value: int) -> None:
|
def rewriteLastShort(self, value: int) -> None:
|
||||||
return self._rewriteLast(value, 2)
|
return self._rewriteLast(value, 2)
|
||||||
|
@ -2185,13 +2196,17 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
def rewriteLastLong(self, value: int) -> None:
|
def rewriteLastLong(self, value: int) -> None:
|
||||||
return self._rewriteLast(value, 4)
|
return self._rewriteLast(value, 4)
|
||||||
|
|
||||||
|
def _write(self, value: int, field_size: int) -> None:
|
||||||
|
bytes_written = self.f.write(
|
||||||
|
struct.pack(self.endian + self._fmt(field_size), value)
|
||||||
|
)
|
||||||
|
self._verify_bytes_written(bytes_written, field_size)
|
||||||
|
|
||||||
def writeShort(self, value: int) -> None:
|
def writeShort(self, value: int) -> None:
|
||||||
bytes_written = self.f.write(struct.pack(self.shortFmt, value))
|
self._write(value, 2)
|
||||||
self._verify_bytes_written(bytes_written, 2)
|
|
||||||
|
|
||||||
def writeLong(self, value: int) -> None:
|
def writeLong(self, value: int) -> None:
|
||||||
bytes_written = self.f.write(struct.pack(self.longFmt, value))
|
self._write(value, 4)
|
||||||
self._verify_bytes_written(bytes_written, 4)
|
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
self.finalize()
|
self.finalize()
|
||||||
|
@ -2199,24 +2214,27 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
self.f.close()
|
self.f.close()
|
||||||
|
|
||||||
def fixIFD(self) -> None:
|
def fixIFD(self) -> None:
|
||||||
num_tags = self.readShort()
|
num_tags = self._read(8 if self._bigtiff else 2)
|
||||||
|
|
||||||
for i in range(num_tags):
|
for i in range(num_tags):
|
||||||
tag, field_type, count = struct.unpack(self.tagFormat, self.f.read(8))
|
tag, field_type, count = struct.unpack(
|
||||||
|
self.tagFormat, self.f.read(12 if self._bigtiff else 8)
|
||||||
|
)
|
||||||
|
|
||||||
field_size = self.fieldSizes[field_type]
|
field_size = self.fieldSizes[field_type]
|
||||||
total_size = field_size * count
|
total_size = field_size * count
|
||||||
is_local = total_size <= 4
|
fmt_size = 8 if self._bigtiff else 4
|
||||||
|
is_local = total_size <= fmt_size
|
||||||
if not is_local:
|
if not is_local:
|
||||||
offset = self.readLong() + self.offsetOfNewPage
|
offset = self._read(fmt_size) + self.offsetOfNewPage
|
||||||
self.rewriteLastLong(offset)
|
self._rewriteLast(offset, fmt_size)
|
||||||
|
|
||||||
if tag in self.Tags:
|
if tag in self.Tags:
|
||||||
cur_pos = self.f.tell()
|
cur_pos = self.f.tell()
|
||||||
|
|
||||||
if is_local:
|
if is_local:
|
||||||
self._fixOffsets(count, field_size)
|
self._fixOffsets(count, field_size)
|
||||||
self.f.seek(cur_pos + 4)
|
self.f.seek(cur_pos + fmt_size)
|
||||||
else:
|
else:
|
||||||
self.f.seek(offset)
|
self.f.seek(offset)
|
||||||
self._fixOffsets(count, field_size)
|
self._fixOffsets(count, field_size)
|
||||||
|
@ -2224,24 +2242,33 @@ class AppendingTiffWriter(io.BytesIO):
|
||||||
|
|
||||||
elif is_local:
|
elif is_local:
|
||||||
# skip the locally stored value that is not an offset
|
# skip the locally stored value that is not an offset
|
||||||
self.f.seek(4, os.SEEK_CUR)
|
self.f.seek(fmt_size, os.SEEK_CUR)
|
||||||
|
|
||||||
def _fixOffsets(self, count: int, field_size: int) -> None:
|
def _fixOffsets(self, count: int, field_size: int) -> None:
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
offset = self._read(field_size)
|
offset = self._read(field_size)
|
||||||
offset += self.offsetOfNewPage
|
offset += self.offsetOfNewPage
|
||||||
if field_size == 2 and offset >= 65536:
|
|
||||||
# offset is now too large - we must convert shorts to longs
|
new_field_size = 0
|
||||||
|
if self._bigtiff and field_size in (2, 4) and offset >= 2**32:
|
||||||
|
# offset is now too large - we must convert long to long8
|
||||||
|
new_field_size = 8
|
||||||
|
elif field_size == 2 and offset >= 2**16:
|
||||||
|
# offset is now too large - we must convert short to long
|
||||||
|
new_field_size = 4
|
||||||
|
if new_field_size:
|
||||||
if count != 1:
|
if count != 1:
|
||||||
msg = "not implemented"
|
msg = "not implemented"
|
||||||
raise RuntimeError(msg) # XXX TODO
|
raise RuntimeError(msg) # XXX TODO
|
||||||
|
|
||||||
# simple case - the offset is just one and therefore it is
|
# simple case - the offset is just one and therefore it is
|
||||||
# local (not referenced with another offset)
|
# local (not referenced with another offset)
|
||||||
self.rewriteLastShortToLong(offset)
|
self._rewriteLast(offset, field_size, new_field_size)
|
||||||
self.f.seek(-10, os.SEEK_CUR)
|
# Move back past the new offset, past 'count', and before 'field_type'
|
||||||
self.writeShort(TiffTags.LONG) # rewrite the type to LONG
|
rewind = -new_field_size - 4 - 2
|
||||||
self.f.seek(8, os.SEEK_CUR)
|
self.f.seek(rewind, os.SEEK_CUR)
|
||||||
|
self.writeShort(new_field_size) # rewrite the type
|
||||||
|
self.f.seek(2 - rewind, os.SEEK_CUR)
|
||||||
else:
|
else:
|
||||||
self._rewriteLast(offset, field_size)
|
self._rewriteLast(offset, field_size)
|
||||||
|
|
||||||
|
|
|
@ -127,6 +127,7 @@ features: dict[str, tuple[str, str | bool, str | None]] = {
|
||||||
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
|
"fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
|
||||||
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
|
"harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
|
||||||
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
|
"libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
|
||||||
|
"mozjpeg": ("PIL._imaging", "HAVE_MOZJPEG", "libjpeg_turbo_version"),
|
||||||
"zlib_ng": ("PIL._imaging", "HAVE_ZLIBNG", "zlib_ng_version"),
|
"zlib_ng": ("PIL._imaging", "HAVE_ZLIBNG", "zlib_ng_version"),
|
||||||
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
|
"libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
|
||||||
"xcb": ("PIL._imaging", "HAVE_XCB", None),
|
"xcb": ("PIL._imaging", "HAVE_XCB", None),
|
||||||
|
@ -300,7 +301,8 @@ def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None:
|
||||||
if name == "jpg":
|
if name == "jpg":
|
||||||
libjpeg_turbo_version = version_feature("libjpeg_turbo")
|
libjpeg_turbo_version = version_feature("libjpeg_turbo")
|
||||||
if libjpeg_turbo_version is not None:
|
if libjpeg_turbo_version is not None:
|
||||||
v = "libjpeg-turbo " + libjpeg_turbo_version
|
v = "mozjpeg" if check_feature("mozjpeg") else "libjpeg-turbo"
|
||||||
|
v += " " + libjpeg_turbo_version
|
||||||
if v is None:
|
if v is None:
|
||||||
v = version(name)
|
v = version(name)
|
||||||
if v is not None:
|
if v is not None:
|
||||||
|
|
|
@ -76,6 +76,13 @@
|
||||||
|
|
||||||
#ifdef HAVE_LIBJPEG
|
#ifdef HAVE_LIBJPEG
|
||||||
#include "jconfig.h"
|
#include "jconfig.h"
|
||||||
|
#ifdef LIBJPEG_TURBO_VERSION
|
||||||
|
#define JCONFIG_INCLUDED
|
||||||
|
#ifdef __CYGWIN__
|
||||||
|
#define _BASETSD_H
|
||||||
|
#endif
|
||||||
|
#include "jpeglib.h"
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_LIBZ
|
#ifdef HAVE_LIBZ
|
||||||
|
@ -4367,6 +4374,15 @@ setup_module(PyObject *m) {
|
||||||
Py_INCREF(have_libjpegturbo);
|
Py_INCREF(have_libjpegturbo);
|
||||||
PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", have_libjpegturbo);
|
PyModule_AddObject(m, "HAVE_LIBJPEGTURBO", have_libjpegturbo);
|
||||||
|
|
||||||
|
PyObject *have_mozjpeg;
|
||||||
|
#ifdef JPEG_C_PARAM_SUPPORTED
|
||||||
|
have_mozjpeg = Py_True;
|
||||||
|
#else
|
||||||
|
have_mozjpeg = Py_False;
|
||||||
|
#endif
|
||||||
|
Py_INCREF(have_mozjpeg);
|
||||||
|
PyModule_AddObject(m, "HAVE_MOZJPEG", have_mozjpeg);
|
||||||
|
|
||||||
PyObject *have_libimagequant;
|
PyObject *have_libimagequant;
|
||||||
#ifdef HAVE_LIBIMAGEQUANT
|
#ifdef HAVE_LIBIMAGEQUANT
|
||||||
have_libimagequant = Py_True;
|
have_libimagequant = Py_True;
|
||||||
|
|
|
@ -339,29 +339,23 @@ text_layout_raqm(
|
||||||
len = PySequence_Fast_GET_SIZE(seq);
|
len = PySequence_Fast_GET_SIZE(seq);
|
||||||
for (j = 0; j < len; j++) {
|
for (j = 0; j < len; j++) {
|
||||||
PyObject *item = PySequence_Fast_GET_ITEM(seq, j);
|
PyObject *item = PySequence_Fast_GET_ITEM(seq, j);
|
||||||
char *feature = NULL;
|
|
||||||
Py_ssize_t size = 0;
|
|
||||||
PyObject *bytes;
|
|
||||||
|
|
||||||
if (!PyUnicode_Check(item)) {
|
if (!PyUnicode_Check(item)) {
|
||||||
Py_DECREF(seq);
|
Py_DECREF(seq);
|
||||||
PyErr_SetString(PyExc_TypeError, "expected a string");
|
PyErr_SetString(PyExc_TypeError, "expected a string");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
bytes = PyUnicode_AsUTF8String(item);
|
|
||||||
if (bytes == NULL) {
|
Py_ssize_t size;
|
||||||
|
const char *feature = PyUnicode_AsUTF8AndSize(item, &size);
|
||||||
|
if (feature == NULL) {
|
||||||
Py_DECREF(seq);
|
Py_DECREF(seq);
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
feature = PyBytes_AS_STRING(bytes);
|
|
||||||
size = PyBytes_GET_SIZE(bytes);
|
|
||||||
if (!raqm_add_font_feature(rq, feature, size)) {
|
if (!raqm_add_font_feature(rq, feature, size)) {
|
||||||
Py_DECREF(seq);
|
Py_DECREF(seq);
|
||||||
Py_DECREF(bytes);
|
|
||||||
PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed");
|
PyErr_SetString(PyExc_ValueError, "raqm_add_font_feature() failed");
|
||||||
goto failed;
|
goto failed;
|
||||||
}
|
}
|
||||||
Py_DECREF(bytes);
|
|
||||||
}
|
}
|
||||||
Py_DECREF(seq);
|
Py_DECREF(seq);
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,16 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compressor configuration */
|
/* Compressor configuration */
|
||||||
|
#ifdef JPEG_C_PARAM_SUPPORTED
|
||||||
|
/* MozJPEG */
|
||||||
|
if (!context->progressive) {
|
||||||
|
/* Do not use MozJPEG progressive default */
|
||||||
|
jpeg_c_set_int_param(
|
||||||
|
&context->cinfo, JINT_COMPRESS_PROFILE, JCP_FASTEST
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
jpeg_set_defaults(&context->cinfo);
|
jpeg_set_defaults(&context->cinfo);
|
||||||
|
|
||||||
/* Prevent RGB -> YCbCr conversion */
|
/* Prevent RGB -> YCbCr conversion */
|
||||||
|
|
|
@ -1664,6 +1664,7 @@ static struct {
|
||||||
{"RGBA", "RGBaXX", 48, unpackRGBaskip2},
|
{"RGBA", "RGBaXX", 48, unpackRGBaskip2},
|
||||||
{"RGBA", "RGBa;16L", 64, unpackRGBa16L},
|
{"RGBA", "RGBa;16L", 64, unpackRGBa16L},
|
||||||
{"RGBA", "RGBa;16B", 64, unpackRGBa16B},
|
{"RGBA", "RGBa;16B", 64, unpackRGBa16B},
|
||||||
|
{"RGBA", "BGR", 24, ImagingUnpackBGR},
|
||||||
{"RGBA", "BGRa", 32, unpackBGRa},
|
{"RGBA", "BGRa", 32, unpackBGRa},
|
||||||
{"RGBA", "RGBA;I", 32, unpackRGBAI},
|
{"RGBA", "RGBA;I", 32, unpackRGBAI},
|
||||||
{"RGBA", "RGBA;L", 32, unpackRGBAL},
|
{"RGBA", "RGBA;L", 32, unpackRGBAL},
|
||||||
|
|
|
@ -116,14 +116,13 @@ V = {
|
||||||
"HARFBUZZ": "10.1.0",
|
"HARFBUZZ": "10.1.0",
|
||||||
"JPEGTURBO": "3.1.0",
|
"JPEGTURBO": "3.1.0",
|
||||||
"LCMS2": "2.16",
|
"LCMS2": "2.16",
|
||||||
"LIBPNG": "1.6.44",
|
"LIBPNG": "1.6.45",
|
||||||
"LIBWEBP": "1.5.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.3",
|
"ZLIBNG": "2.2.3",
|
||||||
}
|
}
|
||||||
V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "")
|
|
||||||
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
|
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
|
||||||
|
|
||||||
|
|
||||||
|
@ -241,8 +240,8 @@ DEPS: dict[str, dict[str, Any]] = {
|
||||||
},
|
},
|
||||||
"libpng": {
|
"libpng": {
|
||||||
"url": f"{SF_PROJECTS}/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/"
|
"url": f"{SF_PROJECTS}/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/"
|
||||||
f"lpng{V['LIBPNG_DOTLESS']}.zip/download",
|
f"FILENAME/download",
|
||||||
"filename": f"lpng{V['LIBPNG_DOTLESS']}.zip",
|
"filename": f"libpng-{V['LIBPNG']}.tar.gz",
|
||||||
"license": "LICENSE",
|
"license": "LICENSE",
|
||||||
"build": [
|
"build": [
|
||||||
*cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"),
|
*cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user