Merge branch 'main' into fp

This commit is contained in:
Andrew Murray 2024-12-28 22:15:44 +11:00 committed by GitHub
commit b8f8799912
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 190 additions and 148 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 486 B

After

Width:  |  Height:  |  Size: 533 B

View File

@ -84,4 +84,4 @@ def test_handler(tmp_path: Path) -> None:
im.save(temp_file) im.save(temp_file)
assert handler.saved assert handler.saved
BufrStubImagePlugin._handler = None BufrStubImagePlugin.register_handler(None)

View File

@ -4,8 +4,6 @@ import pytest
from PIL import ContainerIO, Image from PIL import ContainerIO, Image
from .helper import hopper
TEST_FILE = "Tests/images/dummy.container" TEST_FILE = "Tests/images/dummy.container"
@ -15,15 +13,15 @@ def test_sanity() -> None:
def test_isatty() -> None: def test_isatty() -> None:
with hopper() as im: with open(TEST_FILE, "rb") as fh:
container = ContainerIO.ContainerIO(im, 0, 0) container = ContainerIO.ContainerIO(fh, 0, 0)
assert container.isatty() is False assert container.isatty() is False
def test_seekable() -> None: def test_seekable() -> None:
with hopper() as im: with open(TEST_FILE, "rb") as fh:
container = ContainerIO.ContainerIO(im, 0, 0) container = ContainerIO.ContainerIO(fh, 0, 0)
assert container.seekable() is True assert container.seekable() is True

View File

@ -84,4 +84,4 @@ def test_handler(tmp_path: Path) -> None:
im.save(temp_file) im.save(temp_file)
assert handler.saved assert handler.saved
GribStubImagePlugin._handler = None GribStubImagePlugin.register_handler(None)

View File

@ -86,4 +86,4 @@ def test_handler(tmp_path: Path) -> None:
im.save(temp_file) im.save(temp_file)
assert handler.saved assert handler.saved
Hdf5StubImagePlugin._handler = None Hdf5StubImagePlugin.register_handler(None)

View File

@ -1000,8 +1000,13 @@ class TestFileJpeg:
with Image.open(f) as reloaded: with Image.open(f) as reloaded:
assert reloaded.info["xmp"] == b"XMP test" assert reloaded.info["xmp"] == b"XMP test"
im.info["xmp"] = b"1" * 65504 # Check that XMP is not saved from image info
im.save(f) reloaded.save(f)
with Image.open(f) as reloaded:
assert "xmp" not in reloaded.info
im.save(f, xmp=b"1" * 65504)
with Image.open(f) as reloaded: with Image.open(f) as reloaded:
assert reloaded.info["xmp"] == b"1" * 65504 assert reloaded.info["xmp"] == b"1" * 65504

View File

@ -424,7 +424,8 @@ def test_pclr() -> None:
def test_comment() -> None: def test_comment() -> None:
with Image.open("Tests/images/comment.jp2") as im: for path in ("Tests/images/9bit.j2k", "Tests/images/comment.jp2"):
with Image.open(path) as im:
assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0" assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0"
# Test an image that is truncated partway through a codestream # Test an image that is truncated partway through a codestream

View File

@ -297,3 +297,15 @@ def test_save_all() -> None:
# Test that a single frame image will not be saved as an MPO # Test that a single frame image will not be saved as an MPO
jpg = roundtrip(im, save_all=True) jpg = roundtrip(im, save_all=True)
assert "mp" not in jpg.info assert "mp" not in jpg.info
def test_save_xmp() -> None:
im = Image.new("RGB", (1, 1))
im2 = Image.new("RGB", (1, 1), "#f00")
im2.encoderinfo = {"xmp": b"Second frame"}
im_reloaded = roundtrip(im, xmp=b"First frame", save_all=True, append_images=[im2])
assert im_reloaded.info["xmp"] == b"First frame"
im_reloaded.seek(1)
assert im_reloaded.info["xmp"] == b"Second frame"

View File

@ -772,22 +772,18 @@ class TestFilePng:
im.seek(1) im.seek(1)
@pytest.mark.parametrize("buffer", (True, False)) @pytest.mark.parametrize("buffer", (True, False))
def test_save_stdout(self, buffer: bool) -> None: def test_save_stdout(self, buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None:
old_stdout = sys.stdout
class MyStdOut: class MyStdOut:
buffer = BytesIO() buffer = BytesIO()
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO() mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
sys.stdout = mystdout monkeypatch.setattr(sys, "stdout", mystdout)
with Image.open(TEST_PNG_FILE) as im: with Image.open(TEST_PNG_FILE) as im:
im.save(sys.stdout, "PNG") im.save(sys.stdout, "PNG")
# Reset stdout
sys.stdout = old_stdout
if isinstance(mystdout, MyStdOut): if isinstance(mystdout, MyStdOut):
mystdout = mystdout.buffer mystdout = mystdout.buffer
with Image.open(mystdout) as reloaded: with Image.open(mystdout) as reloaded:

View File

@ -367,22 +367,18 @@ def test_mimetypes(tmp_path: Path) -> None:
@pytest.mark.parametrize("buffer", (True, False)) @pytest.mark.parametrize("buffer", (True, False))
def test_save_stdout(buffer: bool) -> None: def test_save_stdout(buffer: bool, monkeypatch: pytest.MonkeyPatch) -> None:
old_stdout = sys.stdout
class MyStdOut: class MyStdOut:
buffer = BytesIO() buffer = BytesIO()
mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO() mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO()
sys.stdout = mystdout monkeypatch.setattr(sys, "stdout", mystdout)
with Image.open(TEST_FILE) as im: with Image.open(TEST_FILE) as im:
im.save(sys.stdout, "PPM") im.save(sys.stdout, "PPM")
# Reset stdout
sys.stdout = old_stdout
if isinstance(mystdout, MyStdOut): if isinstance(mystdout, MyStdOut):
mystdout = mystdout.buffer mystdout = mystdout.buffer
with Image.open(mystdout) as reloaded: with Image.open(mystdout) as reloaded:

View File

@ -104,20 +104,20 @@ def test_transposed() -> None:
assert im.size == (590, 88) assert im.size == (590, 88)
def test_load_first_unless_jpeg() -> None: def test_load_first_unless_jpeg(monkeypatch: pytest.MonkeyPatch) -> None:
# Test that thumbnail() still uses draft() for JPEG # Test that thumbnail() still uses draft() for JPEG
with Image.open("Tests/images/hopper.jpg") as im: with Image.open("Tests/images/hopper.jpg") as im:
draft = im.draft original_draft = im.draft
def im_draft( def im_draft(
mode: str, size: tuple[int, int] mode: str | None, size: tuple[int, int] | None
) -> tuple[str, tuple[int, int, float, float]] | None: ) -> tuple[str, tuple[int, int, float, float]] | None:
result = draft(mode, size) result = original_draft(mode, size)
assert result is not None assert result is not None
return result return result
im.draft = im_draft monkeypatch.setattr(im, "draft", im_draft)
im.thumbnail((64, 64)) im.thumbnail((64, 64))

View File

@ -1674,6 +1674,9 @@ def test_continuous_horizontal_edges_polygon() -> None:
def test_discontiguous_corners_polygon() -> None: def test_discontiguous_corners_polygon() -> None:
img, draw = create_base_image_draw((84, 68)) img, draw = create_base_image_draw((84, 68))
draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK) draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK)
draw.polygon(
((82, 29), (82, 26), (82, 24), (67, 22), (52, 29), (52, 15), (67, 22)), BLACK
)
draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK) draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK)
draw.polygon( draw.polygon(
((38, 66), (5, 49), (77, 49), (47, 66), (82, 63), (82, 47), (1, 47), (1, 63)), ((38, 66), (5, 49), (77, 49), (47, 66), (82, 63), (82, 47), (1, 47), (1, 63)),

View File

@ -93,6 +93,19 @@ class TestImageFile:
assert p.image is not None assert p.image is not None
assert (48, 48) == p.image.size assert (48, 48) == p.image.size
@pytest.mark.filterwarnings("ignore:Corrupt EXIF data")
def test_incremental_tiff(self) -> None:
with ImageFile.Parser() as p:
with open("Tests/images/hopper.tif", "rb") as f:
p.feed(f.read(1024))
# Check that insufficient data was given in the first feed
assert not p.image
p.feed(f.read())
assert p.image is not None
assert (128, 128) == p.image.size
@skip_unless_feature("webp") @skip_unless_feature("webp")
def test_incremental_webp(self) -> None: def test_incremental_webp(self) -> None:
with ImageFile.Parser() as p: with ImageFile.Parser() as p:

View File

@ -74,6 +74,17 @@ def test_pickle_image(
helper_pickle_file(tmp_path, protocol, test_file, test_mode) helper_pickle_file(tmp_path, protocol, test_file, test_mode)
def test_pickle_jpeg() -> None:
# Arrange
with Image.open("Tests/images/hopper.jpg") as image:
# Act: roundtrip
unpickled_image = pickle.loads(pickle.dumps(image))
# Assert
assert len(unpickled_image.layer) == 3
assert unpickled_image.layers == 3
def test_pickle_la_mode_with_palette(tmp_path: Path) -> None: def test_pickle_la_mode_with_palette(tmp_path: Path) -> None:
# Arrange # Arrange
filename = str(tmp_path / "temp.pkl") filename = str(tmp_path / "temp.pkl")

View File

@ -52,6 +52,12 @@ zlib library, and what version of zlib-ng is being used::
Other Changes Other Changes
============= =============
Reading JPEG 2000 comments
^^^^^^^^^^^^^^^^^^^^^^^^^^
When opening a JPEG 2000 image, the comment may now be read into
:py:attr:`~PIL.Image.Image.info` for J2K images, not just JP2 images.
zlib-ng in wheels zlib-ng in wheels
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^

View File

@ -344,7 +344,7 @@ class pil_build_ext(build_ext):
for x in ("raqm", "fribidi") for x in ("raqm", "fribidi")
] ]
+ [ + [
("disable-platform-guessing", None, "Disable platform guessing on Linux"), ("disable-platform-guessing", None, "Disable platform guessing"),
("debug", None, "Debug logging"), ("debug", None, "Debug logging"),
] ]
+ [("add-imaging-libs=", None, "Add libs to _imaging build")] + [("add-imaging-libs=", None, "Add libs to _imaging build")]

View File

@ -274,7 +274,7 @@ class BlpImageFile(ImageFile.ImageFile):
raise BLPFormatError(msg) raise BLPFormatError(msg)
self._mode = "RGBA" if self._blp_alpha_depth else "RGB" self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))] self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, self.mode)]
class _BLPBaseDecoder(ImageFile.PyDecoder): class _BLPBaseDecoder(ImageFile.PyDecoder):

View File

@ -561,9 +561,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
+ struct.pack("<4I", *rgba_mask) # dwRGBABitMask + struct.pack("<4I", *rgba_mask) # dwRGBABitMask
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
) )
ImageFile._save( ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
)
def _accept(prefix: bytes) -> bool: def _accept(prefix: bytes) -> bool:

View File

@ -456,7 +456,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes, eps: int = 1) -
if hasattr(fp, "flush"): if hasattr(fp, "flush"):
fp.flush() fp.flush()
ImageFile._save(im, fp, [ImageFile._Tile("eps", (0, 0) + im.size, 0, None)]) ImageFile._save(im, fp, [ImageFile._Tile("eps", (0, 0) + im.size)])
fp.write(b"\n%%%%EndBinary\n") fp.write(b"\n%%%%EndBinary\n")
fp.write(b"grestore end\n") fp.write(b"grestore end\n")

View File

@ -161,7 +161,7 @@ class FliImageFile(ImageFile.ImageFile):
framesize = i32(s) framesize = i32(s)
self.decodermaxblock = framesize self.decodermaxblock = framesize
self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset, None)] self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset)]
self.__offset += framesize self.__offset += framesize

View File

@ -171,7 +171,7 @@ class FpxImageFile(ImageFile.ImageFile):
"raw", "raw",
(x, y, x1, y1), (x, y, x1, y1),
i32(s, i) + 28, i32(s, i) + 28,
(self.rawmode,), self.rawmode,
) )
) )

View File

@ -96,7 +96,7 @@ class FtexImageFile(ImageFile.ImageFile):
self._mode = "RGBA" self._mode = "RGBA"
self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))] self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))]
elif format == Format.UNCOMPRESSED: elif format == Format.UNCOMPRESSED:
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))] self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, "RGB")]
else: else:
msg = f"Invalid texture compression format: {repr(format)}" msg = f"Invalid texture compression format: {repr(format)}"
raise ValueError(msg) raise ValueError(msg)

View File

@ -76,7 +76,7 @@ class GdImageFile(ImageFile.ImageFile):
"raw", "raw",
(0, 0) + self.size, (0, 0) + self.size,
7 + true_color_offset + 4 + 256 * 4, 7 + true_color_offset + 4 + 256 * 4,
("L", 0, 1), "L",
) )
] ]

View File

@ -754,7 +754,7 @@ class Image:
def __setstate__(self, state: list[Any]) -> None: def __setstate__(self, state: list[Any]) -> None:
Image.__init__(self) Image.__init__(self)
info, mode, size, palette, data = state info, mode, size, palette, data = state[:5]
self.info = info self.info = info
self._mode = mode self._mode = mode
self._size = size self._size = size
@ -2556,7 +2556,7 @@ class Image:
self._ensure_mutable() self._ensure_mutable()
save_all = params.pop("save_all", False) save_all = params.pop("save_all", False)
self.encoderinfo = params self.encoderinfo = {**getattr(self, "encoderinfo", {}), **params}
self.encoderconfig: tuple[Any, ...] = () self.encoderconfig: tuple[Any, ...] = ()
preinit() preinit()
@ -2603,6 +2603,11 @@ class Image:
except PermissionError: except PermissionError:
pass pass
raise raise
finally:
try:
del self.encoderinfo
except AttributeError:
pass
if open_fp: if open_fp:
fp.close() fp.close()

View File

@ -98,8 +98,8 @@ def _tilesort(t: _Tile) -> int:
class _Tile(NamedTuple): class _Tile(NamedTuple):
codec_name: str codec_name: str
extents: tuple[int, int, int, int] | None extents: tuple[int, int, int, int] | None
offset: int offset: int = 0
args: tuple[Any, ...] | str | None args: tuple[Any, ...] | str | None = None
# #

View File

@ -104,28 +104,17 @@ def grab(
def grabclipboard() -> Image.Image | list[str] | None: def grabclipboard() -> Image.Image | list[str] | None:
if sys.platform == "darwin": if sys.platform == "darwin":
fh, filepath = tempfile.mkstemp(".png") p = subprocess.run(
os.close(fh) ["osascript", "-e", "get the clipboard as «class PNGf»"],
commands = [ capture_output=True,
'set theFile to (open for access POSIX file "' )
+ filepath if p.returncode != 0:
+ '" with write permission)', return None
"try",
" write (the clipboard as «class PNGf») to theFile",
"end try",
"close access theFile",
]
script = ["osascript"]
for command in commands:
script += ["-e", command]
subprocess.call(script)
im = None import binascii
if os.stat(filepath).st_size != 0:
im = Image.open(filepath) data = io.BytesIO(binascii.unhexlify(p.stdout[11:-3]))
im.load() return Image.open(data)
os.unlink(filepath)
return im
elif sys.platform == "win32": elif sys.platform == "win32":
fmt, data = Image.core.grabclipboard_win32() fmt, data = Image.core.grabclipboard_win32()
if fmt == "file": # CF_HDROP if fmt == "file": # CF_HDROP

View File

@ -698,10 +698,11 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image
8: Image.Transpose.ROTATE_90, 8: Image.Transpose.ROTATE_90,
}.get(orientation) }.get(orientation)
if method is not None: if method is not None:
transposed_image = image.transpose(method)
if in_place: if in_place:
image.im = transposed_image.im image.im = image.im.transpose(method)
image._size = transposed_image._size image._size = image.im.size
else:
transposed_image = image.transpose(method)
exif_image = image if in_place else transposed_image exif_image = image if in_place else transposed_image
exif = exif_image.getexif() exif = exif_image.getexif()

View File

@ -62,7 +62,7 @@ class ImtImageFile(ImageFile.ImageFile):
"raw", "raw",
(0, 0) + self.size, (0, 0) + self.size,
self.fp.tell() - len(buffer), self.fp.tell() - len(buffer),
(self.mode, 0, 1), self.mode,
) )
] ]

View File

@ -253,6 +253,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
if sig == b"\xff\x4f\xff\x51": if sig == b"\xff\x4f\xff\x51":
self.codec = "j2k" self.codec = "j2k"
self._size, self._mode = _parse_codestream(self.fp) self._size, self._mode = _parse_codestream(self.fp)
self._parse_comment()
else: else:
sig = sig + self.fp.read(8) sig = sig + self.fp.read(8)
@ -263,6 +264,9 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
if dpi is not None: if dpi is not None:
self.info["dpi"] = dpi self.info["dpi"] = dpi
if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"): if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"):
hdr = self.fp.read(2)
length = _binary.i16be(hdr)
self.fp.seek(length - 2, os.SEEK_CUR)
self._parse_comment() self._parse_comment()
else: else:
msg = "not a JPEG 2000 file" msg = "not a JPEG 2000 file"
@ -298,10 +302,6 @@ class Jpeg2KImageFile(ImageFile.ImageFile):
def _parse_comment(self) -> None: def _parse_comment(self) -> None:
assert self.fp is not None assert self.fp is not None
hdr = self.fp.read(2)
length = _binary.i16be(hdr)
self.fp.seek(length - 2, os.SEEK_CUR)
while True: while True:
marker = self.fp.read(2) marker = self.fp.read(2)
if not marker: if not marker:

View File

@ -401,6 +401,13 @@ class JpegImageFile(ImageFile.ImageFile):
return getattr(self, "_" + name) return getattr(self, "_" + name)
raise AttributeError(name) raise AttributeError(name)
def __getstate__(self) -> list[Any]:
return super().__getstate__() + [self.layers, self.layer]
def __setstate__(self, state: list[Any]) -> None:
super().__setstate__(state)
self.layers, self.layer = state[5:]
def load_read(self, read_bytes: int) -> bytes: def load_read(self, read_bytes: int) -> bytes:
""" """
internal: read more image data internal: read more image data
@ -758,7 +765,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
extra = info.get("extra", b"") extra = info.get("extra", b"")
MAX_BYTES_IN_MARKER = 65533 MAX_BYTES_IN_MARKER = 65533
xmp = info.get("xmp", im.info.get("xmp")) xmp = info.get("xmp")
if xmp: if xmp:
overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00" overhead_len = 29 # b"http://ns.adobe.com/xap/1.0/\x00"
max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len

View File

@ -70,9 +70,9 @@ class MspImageFile(ImageFile.ImageFile):
self._size = i16(s, 4), i16(s, 6) self._size = i16(s, 4), i16(s, 6)
if s[:4] == b"DanM": if s[:4] == b"DanM":
self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, ("1", 0, 1))] self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, "1")]
else: else:
self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32, None)] self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32)]
class MspDecoder(ImageFile.PyDecoder): class MspDecoder(ImageFile.PyDecoder):
@ -188,7 +188,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
fp.write(o16(h)) fp.write(o16(h))
# image body # image body
ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 32, ("1", 0, 1))]) ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 32, "1")])
# #

View File

@ -47,7 +47,7 @@ class PcdImageFile(ImageFile.ImageFile):
self._mode = "RGB" self._mode = "RGB"
self._size = 768, 512 # FIXME: not correct for rotated images! self._size = 768, 512 # FIXME: not correct for rotated images!
self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048, None)] self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
def load_end(self) -> None: def load_end(self) -> None:
if self.tile_post_rotate: if self.tile_post_rotate:

View File

@ -61,9 +61,7 @@ class PixarImageFile(ImageFile.ImageFile):
# FIXME: to be continued... # FIXME: to be continued...
# create tile descriptor (assuming "dumped") # create tile descriptor (assuming "dumped")
self.tile = [ self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 1024, self.mode)]
ImageFile._Tile("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))
]
# #

View File

@ -33,7 +33,7 @@ class QoiImageFile(ImageFile.ImageFile):
self._mode = "RGB" if channels == 3 else "RGBA" self._mode = "RGB" if channels == 3 else "RGBA"
self.fp.seek(1, os.SEEK_CUR) # colorspace self.fp.seek(1, os.SEEK_CUR) # colorspace
self.tile = [ImageFile._Tile("qoi", (0, 0) + self._size, self.fp.tell(), None)] self.tile = [ImageFile._Tile("qoi", (0, 0) + self._size, self.fp.tell())]
class QoiDecoder(ImageFile.PyDecoder): class QoiDecoder(ImageFile.PyDecoder):

View File

@ -155,9 +155,7 @@ class SpiderImageFile(ImageFile.ImageFile):
self.rawmode = "F;32F" self.rawmode = "F;32F"
self._mode = "F" self._mode = "F"
self.tile = [ self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, offset, self.rawmode)]
ImageFile._Tile("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))
]
self._fp = self.fp # FIXME: hack self._fp = self.fp # FIXME: hack
@property @property
@ -212,26 +210,27 @@ class SpiderImageFile(ImageFile.ImageFile):
# given a list of filenames, return a list of images # given a list of filenames, return a list of images
def loadImageSeries(filelist: list[str] | None = None) -> list[SpiderImageFile] | None: def loadImageSeries(filelist: list[str] | None = None) -> list[Image.Image] | None:
"""create a list of :py:class:`~PIL.Image.Image` objects for use in a montage""" """create a list of :py:class:`~PIL.Image.Image` objects for use in a montage"""
if filelist is None or len(filelist) < 1: if filelist is None or len(filelist) < 1:
return None return None
imglist = [] byte_imgs = []
for img in filelist: for img in filelist:
if not os.path.exists(img): if not os.path.exists(img):
print(f"unable to find {img}") print(f"unable to find {img}")
continue continue
try: try:
with Image.open(img) as im: with Image.open(img) as im:
im = im.convert2byte() assert isinstance(im, SpiderImageFile)
byte_im = im.convert2byte()
except Exception: except Exception:
if not isSpiderImage(img): if not isSpiderImage(img):
print(f"{img} is not a Spider image file") print(f"{img} is not a Spider image file")
continue continue
im.info["filename"] = img byte_im.info["filename"] = img
imglist.append(im) byte_imgs.append(byte_im)
return imglist return byte_imgs
# -------------------------------------------------------------------- # --------------------------------------------------------------------
@ -281,9 +280,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
fp.writelines(hdr) fp.writelines(hdr)
rawmode = "F;32NF" # 32-bit native floating point rawmode = "F;32NF" # 32-bit native floating point
ImageFile._save( ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
)
def _save_spider(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: def _save_spider(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:

View File

@ -1434,8 +1434,12 @@ class TiffImageFile(ImageFile.ImageFile):
logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING)) logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING))
# size # size
xsize = self.tag_v2.get(IMAGEWIDTH) try:
ysize = self.tag_v2.get(IMAGELENGTH) xsize = self.tag_v2[IMAGEWIDTH]
ysize = self.tag_v2[IMAGELENGTH]
except KeyError as e:
msg = "Missing dimensions"
raise TypeError(msg) from e
if not isinstance(xsize, int) or not isinstance(ysize, int): if not isinstance(xsize, int) or not isinstance(ysize, int):
msg = "Invalid dimensions" msg = "Invalid dimensions"
raise ValueError(msg) raise ValueError(msg)
@ -1916,7 +1920,9 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
if not getattr(Image.core, "libtiff_support_custom_tags", False): if not getattr(Image.core, "libtiff_support_custom_tags", False):
continue continue
if tag in ifd.tagtype: if tag in TiffTags.TAGS_V2_GROUPS:
types[tag] = TiffTags.LONG8
elif tag in ifd.tagtype:
types[tag] = ifd.tagtype[tag] types[tag] = ifd.tagtype[tag]
elif not (isinstance(value, (int, float, str, bytes))): elif not (isinstance(value, (int, float, str, bytes))):
continue continue

View File

@ -74,9 +74,7 @@ class XVThumbImageFile(ImageFile.ImageFile):
self.palette = ImagePalette.raw("RGB", PALETTE) self.palette = ImagePalette.raw("RGB", PALETTE)
self.tile = [ self.tile = [
ImageFile._Tile( ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), self.mode)
"raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1)
)
] ]

View File

@ -67,7 +67,7 @@ class XbmImageFile(ImageFile.ImageFile):
self._mode = "1" self._mode = "1"
self._size = xsize, ysize self._size = xsize, ysize
self.tile = [ImageFile._Tile("xbm", (0, 0) + self.size, m.end(), None)] self.tile = [ImageFile._Tile("xbm", (0, 0) + self.size, m.end())]
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
@ -85,7 +85,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
fp.write(b"static char im_bits[] = {\n") fp.write(b"static char im_bits[] = {\n")
ImageFile._save(im, fp, [ImageFile._Tile("xbm", (0, 0) + im.size, 0, None)]) ImageFile._save(im, fp, [ImageFile._Tile("xbm", (0, 0) + im.size)])
fp.write(b"};\n") fp.write(b"};\n")

View File

@ -102,9 +102,7 @@ class XpmImageFile(ImageFile.ImageFile):
self._mode = "P" self._mode = "P"
self.palette = ImagePalette.raw("RGB", b"".join(palette)) self.palette = ImagePalette.raw("RGB", b"".join(palette))
self.tile = [ self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), "P")]
ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))
]
def load_read(self, read_bytes: int) -> bytes: def load_read(self, read_bytes: int) -> bytes:
# #

View File

@ -346,7 +346,7 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
return -1; return -1;
} }
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS;
// transform color channels only // transform color channels only
for (i = 0; i < im->ysize; i++) { for (i = 0; i < im->ysize; i++) {
@ -362,7 +362,7 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
// enough available on all platforms, so we polyfill it here for now. // enough available on all platforms, so we polyfill it here for now.
pyCMScopyAux(hTransform, imOut, im); pyCMScopyAux(hTransform, imOut, im);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS;
return 0; return 0;
} }
@ -378,7 +378,7 @@ _buildTransform(
) { ) {
cmsHTRANSFORM hTransform; cmsHTRANSFORM hTransform;
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS;
/* create the transform */ /* create the transform */
hTransform = cmsCreateTransform( hTransform = cmsCreateTransform(
@ -412,7 +412,7 @@ _buildProofTransform(
) { ) {
cmsHTRANSFORM hTransform; cmsHTRANSFORM hTransform;
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS;
/* create the transform */ /* create the transform */
hTransform = cmsCreateProofingTransform( hTransform = cmsCreateProofingTransform(

View File

@ -690,9 +690,10 @@ PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
SetWindowLongPtr(wnd, 0, (LONG_PTR)callback); SetWindowLongPtr(wnd, 0, (LONG_PTR)callback);
SetWindowLongPtr(wnd, sizeof(callback), (LONG_PTR)PyThreadState_Get()); SetWindowLongPtr(wnd, sizeof(callback), (LONG_PTR)PyThreadState_Get());
Py_BEGIN_ALLOW_THREADS ShowWindow(wnd, SW_SHOWNORMAL); Py_BEGIN_ALLOW_THREADS;
ShowWindow(wnd, SW_SHOWNORMAL);
SetForegroundWindow(wnd); /* to make sure it's visible */ SetForegroundWindow(wnd); /* to make sure it's visible */
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS;
return Py_BuildValue(F_HANDLE, wnd); return Py_BuildValue(F_HANDLE, wnd);
} }
@ -701,11 +702,12 @@ PyObject *
PyImaging_EventLoopWin32(PyObject *self, PyObject *args) { PyImaging_EventLoopWin32(PyObject *self, PyObject *args) {
MSG msg; MSG msg;
Py_BEGIN_ALLOW_THREADS while (mainloop && GetMessage(&msg, NULL, 0, 0)) { Py_BEGIN_ALLOW_THREADS;
while (mainloop && GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg); TranslateMessage(&msg);
DispatchMessage(&msg); DispatchMessage(&msg);
} }
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS;
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;

View File

@ -736,7 +736,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
} }
if (tag_type) { if (tag_type) {
int type_int = PyLong_AsLong(tag_type); int type_int = PyLong_AsLong(tag_type);
if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) { if (type_int >= TIFF_BYTE && type_int <= TIFF_LONG8) {
type = (TIFFDataType)type_int; type = (TIFFDataType)type_int;
} }
} }
@ -929,7 +929,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
); );
} else if (type == TIFF_LONG) { } else if (type == TIFF_LONG) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value) &encoder->state, (ttag_t)key_int, (UINT32)PyLong_AsLong(value)
); );
} else if (type == TIFF_SSHORT) { } else if (type == TIFF_SSHORT) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
@ -959,6 +959,10 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
status = ImagingLibTiffSetField( status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value) &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
); );
} else if (type == TIFF_LONG8) {
status = ImagingLibTiffSetField(
&encoder->state, (ttag_t)key_int, (uint64_t)PyLong_AsLongLong(value)
);
} else { } else {
TRACE( TRACE(
("Unhandled type for key %d : %s \n", ("Unhandled type for key %d : %s \n",

View File

@ -501,7 +501,8 @@ polygon_generic(
// Needed to draw consistent polygons // Needed to draw consistent polygons
xx[j] = xx[j - 1]; xx[j] = xx[j - 1];
j++; j++;
} else if (current->dx != 0 && roundf(xx[j - 1]) == xx[j - 1]) { } else if (current->dx != 0 && j % 2 == 1 &&
roundf(xx[j - 1]) == xx[j - 1]) {
// Connect discontiguous corners // Connect discontiguous corners
for (k = 0; k < i; k++) { for (k = 0; k < i; k++) {
Edge *other_edge = edge_table[k]; Edge *other_edge = edge_table[k];
@ -510,10 +511,8 @@ polygon_generic(
continue; continue;
} }
// Check if the two edges join to make a corner // Check if the two edges join to make a corner
if (((ymin == current->ymin && ymin == other_edge->ymin) || if (xx[j - 1] ==
(ymin == current->ymax && ymin == other_edge->ymax)) && (ymin - other_edge->y0) * other_edge->dx + other_edge->x0) {
xx[j - 1] == (ymin - other_edge->y0) * other_edge->dx +
other_edge->x0) {
// Determine points from the edges on the next row // Determine points from the edges on the next row
// Or if this is the last row, check the previous row // Or if this is the last row, check the previous row
int offset = ymin == ymax ? -1 : 1; int offset = ymin == ymax ? -1 : 1;

View File

@ -640,7 +640,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
opj_dparameters_t params; opj_dparameters_t params;
OPJ_COLOR_SPACE color_space; OPJ_COLOR_SPACE color_space;
j2k_unpacker_t unpack = NULL; j2k_unpacker_t unpack = NULL;
size_t buffer_size = 0, tile_bytes = 0; size_t tile_bytes = 0;
unsigned n, tile_height, tile_width; unsigned n, tile_height, tile_width;
int subsampling; int subsampling;
int total_component_width = 0; int total_component_width = 0;
@ -870,7 +870,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
tile_info.data_size = tile_bytes; tile_info.data_size = tile_bytes;
} }
if (buffer_size < tile_info.data_size) { if (tile_info.data_size > 0) {
/* malloc check ok, overflow and tile size sanity check above */ /* malloc check ok, overflow and tile size sanity check above */
UINT8 *new = realloc(state->buffer, tile_info.data_size); UINT8 *new = realloc(state->buffer, tile_info.data_size);
if (!new) { if (!new) {
@ -883,7 +883,6 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) {
to valgrind errors. */ to valgrind errors. */
memset(new, 0, tile_info.data_size); memset(new, 0, tile_info.data_size);
state->buffer = new; state->buffer = new;
buffer_size = tile_info.data_size;
} }
if (!opj_decode_tile_data( if (!opj_decode_tile_data(