Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit TIFF strip size when saving with LibTIFF #5514

Merged
merged 5 commits into from Jun 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 10 additions & 1 deletion Tests/test_file_libtiff.py
Expand Up @@ -9,7 +9,7 @@
import pytest

from PIL import Image, ImageFilter, TiffImagePlugin, TiffTags, features
from PIL.TiffImagePlugin import SUBIFD
from PIL.TiffImagePlugin import STRIPOFFSETS, SUBIFD

from .helper import (
assert_image_equal,
Expand Down Expand Up @@ -967,3 +967,12 @@ def test_realloc_overflow(self):
# Assert that the error code is IMAGING_CODEC_MEMORY
assert str(e.value) == "-9"
TiffImagePlugin.READ_LIBTIFF = False

def test_save_multistrip(self, tmp_path):
im = hopper("RGB").resize((256, 256))
out = str(tmp_path / "temp.tif")
im.save(out, compression="tiff_adobe_deflate")

with Image.open(out) as im:
# Assert that there are multiple strips
assert len(im.tag_v2[STRIPOFFSETS]) > 1
18 changes: 14 additions & 4 deletions src/PIL/TiffImagePlugin.py
Expand Up @@ -1558,12 +1558,22 @@ def _save(im, fp, filename):
ifd[COLORMAP] = tuple(v * 256 for v in lut)
# data orientation
stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)
ifd[ROWSPERSTRIP] = im.size[1]
strip_byte_counts = stride * im.size[1]
# aim for 64 KB strips when using libtiff writer
if libtiff:
rows_per_strip = min((2 ** 16 + stride - 1) // stride, im.size[1])
else:
rows_per_strip = im.size[1]
strip_byte_counts = stride * rows_per_strip
strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip
ifd[ROWSPERSTRIP] = rows_per_strip
if strip_byte_counts >= 2 ** 16:
ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
ifd[STRIPBYTECOUNTS] = strip_byte_counts
ifd[STRIPOFFSETS] = 0 # this is adjusted by IFD writer
ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),
)
ifd[STRIPOFFSETS] = tuple(
range(0, strip_byte_counts * strips_per_image, strip_byte_counts)
) # this is adjusted by IFD writer
# no compression by default:
ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)

Expand Down