-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
What did you do?
I'm using Pillow indirectly through another framework that, when encountering a multi-frame image, iterates over the frames in the image, seeks to each one, and creates a copy of each for further processing using img.copy()
.
I found that an exception is thrown when processing certain jpeg images. Specifically, when a photo has been exported from the macOS Photos app and the output width was limited to something smaller than the original image.
When exporting images as JPEG from the Photos app, it produces files that include two frames:
- Frame 0: Mode = RGB
- Frame 1: Mode = L
For the example image, the original size of the image was 3024x4032.
When exporting and keeping the original size, the two frames are:
- Frame 0: Mode = RGB, Size = 3024x4032
- Frame 1: Mode = L, Size = 1512x2016
For the example image, I exported it with a custom width of 1024, and the two frames in the resulting image are:
- Frame 0: Mode = RGB, Size = 1024x1366
- Frame 1: Mode = L, Size = 1512x2016
It seems that frame 1 being larger than frame 0 is a problem.
What did you expect to happen?
No exception to be thrown when iterating over the individual frames, seeking to them, and calling img.copy()
What actually happened?
An exception is thrown when calling im.copy()
after seeking to the second frame *if the second frame's dimensions are larger than the first frame's.
What are your OS, Python and Pillow versions?
- OS: macOS 15.6.1
- Python: 3.12.11 - 3.13.7 (all I've tried)
- Pillow: 11.3+
Reproduction
- Copy the example image at the bottom into
Tests/images
asjpeg_tile_error.jpeg
- Add and run the following test case.
- An exception is thrown when calling
im.copy()
on the second frame.
TEST_FILE = "Tests/images/jpeg_tile_error.jpeg"
@skip_unless_feature("jpg")
class TestFileJpegTileError:
def test_copy(self) -> None:
# Test img.copy()
with Image.open(TEST_FILE) as im:
assert isinstance(im, JpegImagePlugin.JpegImageFile)
if hasattr(im, "n_frames") and im.n_frames > 1:
for i in range(im.n_frames):
im.seek(i)
print(f"Frame {i}, size: {im.size}, mode: {im.mode}, format: {im.format}, get_format_mimetype: {im.get_format_mimetype()}")
im_copy = im.copy()