Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Tests/test_file_dcx.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,37 @@ def test_seek_too_far() -> None:
im.seek(frame)


def test_seek_frame_with_different_mode() -> None:
# A later frame may use a different mode and pixel size than the first.
# The backing image must not be reused across such a change.
def pcx(bits: int, planes: int, w: int, h: int, body: bytes) -> bytes:
stride = (w * bits + 7) // 8
stride += stride % 2
header = bytes([10, 5, 1, bits])
header += o16(0) + o16(0) + o16(w - 1) + o16(h - 1) + o16(72) + o16(72)
header += bytes(48) + b"\0" + bytes([planes]) + o16(stride)
return header + bytes(128 - len(header)) + body

w = h = 128
first_frame = pcx(8, 1, w, h, b"\x41" * (w * h))
second_frame = pcx(8, 3, w, h, b"\x41" * (3 * w * h))
b = BytesIO(
o32(DcxImagePlugin.MAGIC)
+ o32(16)
+ o32(len(first_frame) + 16)
+ o32(0)
+ first_frame
+ second_frame
)
with Image.open(b) as im:
im.load()
assert im.mode == "L"
im.seek(1)
im.load()
assert im.mode == "RGB"
assert im.getpixel((0, 0)) == (0x41, 0x41, 0x41)


def test_seek_decompression_bomb() -> None:
with open("Tests/images/pil184.pcx", "rb") as fp:
first_frame = fp.read()
Expand Down
16 changes: 16 additions & 0 deletions Tests/test_file_mpo.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,22 @@ def test_seek(test_file: str) -> None:
assert im.tell() == 0


def test_seek_frame_with_different_mode() -> None:
# A frame may use a different mode and pixel size than the first,
# so the backing image must not be reused across the change.
out = BytesIO()
Image.new("L", (128, 128)).save(
out, "MPO", save_all=True, append_images=[Image.new("RGB", (128, 128))]
)
out.seek(0)
with Image.open(out) as im:
im.load()
assert im.mode == "L"
im.seek(1)
im.load()
assert im.mode == "RGB"


def test_seek_decompression_bomb() -> None:
with open("Tests/images/frozenpond.mpo", "rb") as fp:
data = fp.read()
Expand Down
18 changes: 18 additions & 0 deletions Tests/test_file_psd.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ def test_seek_tell() -> None:
assert layer_number == 2


def test_seek_layer_with_different_mode() -> None:
# A layer may use a different mode and pixel size than the composite image,
# so the backing image must not be reused across the change.
with open(test_file, "rb") as fp:
data = bytearray(fp.read())
# Patch the header to report a single grayscale channel for the composite,
# while the RGB/RGBA layer records are left untouched.
data[12:14] = b"\x00\x01" # channels
data[24:26] = b"\x00\x01" # grayscale mode

with Image.open(io.BytesIO(data)) as im:
im.load()
assert im.mode == "L"
im.seek(im.n_frames)
im.load()
assert im.mode == "RGBA"


def test_seek_back_to_initial_frame() -> None:
# Seeking away from the initial frame and then back should produce the same image
with Image.open(test_file) as im:
Expand Down
4 changes: 4 additions & 0 deletions src/PIL/DcxImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ def seek(self, frame: int) -> None:
self.fp.seek(self._offset[frame])
PcxImageFile._open(self)
Image._decompression_bomb_check(self.size)
if self._im is not None and (
self.im.size != self.size or self.im.mode != self.mode
):
self._im = None

def tell(self) -> int:
return self.frame
Expand Down
4 changes: 4 additions & 0 deletions src/PIL/MicImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ def seek(self, frame: int) -> None:

TiffImagePlugin.TiffImageFile._open(self)
Image._decompression_bomb_check(self.size)
if self._im is not None and (
self.im.size != self.size or self.im.mode != self.mode
):
self._im = None

self.frame = frame

Expand Down
4 changes: 4 additions & 0 deletions src/PIL/MpoImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ def seek(self, frame: int) -> None:
self.tile = [
ImageFile._Tile("jpeg", (0, 0, *self.size), self.offset, self.tile[0][-1])
]
if self._im is not None and (
self.im.size != self.size or self.im.mode != self.mode
):
self._im = None
self.__frame = frame

def tell(self) -> int:
Expand Down
4 changes: 4 additions & 0 deletions src/PIL/PsdImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ def seek(self, layer: int) -> None:
_, self._mode, _, self.tile = self.layers[layer - 1]
self.frame = layer
self.fp = self._fp
if self._im is not None and (
self.im.size != self.size or self.im.mode != self.mode
):
self._im = None

def tell(self) -> int:
# return layer number (0=image, 1..max=layers)
Expand Down