diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index dfd33078847..0f5960de291 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -1,6 +1,7 @@ from __future__ import annotations import filecmp +import io import warnings from pathlib import Path @@ -8,7 +9,7 @@ from PIL import Image, ImImagePlugin -from .helper import assert_image_equal_tofile, hopper, is_pypy +from .helper import assert_image_equal, assert_image_equal_tofile, hopper, is_pypy # sample im TEST_IM = "Tests/images/hopper.im" @@ -83,6 +84,41 @@ def test_eoferror() -> None: im.seek(n_frames - 1) +@pytest.mark.parametrize( + "image_type, rawmode, mode", + ( + ("L 16", "I;16", "I;16"), + ("L 32S", "I;32S", "I"), + ("L 32F", "F;32F", "F"), + ("YCC", "YCbCr;L", "YCbCr"), + ), +) +def test_seek_non_8bit(image_type: str, rawmode: str, mode: str) -> None: + # The frame stride must be derived from the actual bytes per pixel, not + # from the length of the mode name (which only matches for 8-bit bands). + w, h = 4, 4 + frames = [] + for base in (0, 1000): + frame = Image.new(mode, (w, h)) + for y in range(h): + for x in range(w): + frame.putpixel((x, y), base + y * w + x) + frames.append(frame) + + header = ( + f"Image type: {image_type} image\r\n" + f"Image size (x*y): {w}*{h}\r\n" + f"File size (no of images): {len(frames)}\r\n" + ).encode("ascii") + header += b"\x00" * (511 - len(header)) + b"\x1a" + data = b"".join(frame.tobytes("raw", rawmode, 0, -1) for frame in frames) + + with Image.open(io.BytesIO(header + data)) as im: + for index, frame in enumerate(frames): + im.seek(index) + assert_image_equal(im, frame) + + @pytest.mark.parametrize("mode", ("RGB", "P", "PA")) def test_roundtrip(mode: str, tmp_path: Path) -> None: out = tmp_path / "temp.im" diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index ef54f16e97e..ae6cf2956d0 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -30,7 +30,7 @@ import re from typing import IO, Any -from . import Image, ImageFile, ImagePalette +from . import Image, ImageFile, ImageMode, ImagePalette from ._util import DeferredError # -------------------------------------------------------------------- @@ -300,7 +300,8 @@ def seek(self, frame: int) -> None: if self.mode == "1": bits = 1 else: - bits = 8 * len(self.mode) + mode = ImageMode.getmode(self.mode) + bits = 8 * len(mode.bands) * int(mode.typestr[-1]) size = ((self.size[0] * bits + 7) // 8) * self.size[1] offs = self.__offset + frame * size