From 7752fb51317ede0991446c2671298e35d4c930d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Komar=C4=8Devi=C4=87?= Date: Thu, 27 May 2021 15:40:04 +0200 Subject: [PATCH 1/5] Limit TIFF strip size when saving with libtiff --- src/PIL/TiffImagePlugin.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index fc59f14d39e..3bb36848707 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1558,12 +1558,18 @@ 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] + rows_per_strip = im.size[1] strip_byte_counts = stride * im.size[1] + # aim for 64 KB strips when using libtiff writer + while libtiff and strip_byte_counts > 2 ** 16 and rows_per_strip > 1: + rows_per_strip = (rows_per_strip + 1) // 2 + 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) From c949d54605c3c271bffb62c474b74614233f7d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Komar=C4=8Devi=C4=87?= Date: Wed, 2 Jun 2021 11:28:49 +0200 Subject: [PATCH 2/5] Fix lint errors --- src/PIL/TiffImagePlugin.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 3bb36848707..68e2759b00b 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1568,8 +1568,12 @@ def _save(im, fp, filename): ifd[ROWSPERSTRIP] = rows_per_strip if strip_byte_counts >= 2 ** 16: ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG - 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 + 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) From 1c4deefe11042f4a1a9c5270fc32d7e1fea652b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Komar=C4=8Devi=C4=87?= Date: Wed, 2 Jun 2021 12:06:27 +0200 Subject: [PATCH 3/5] Make strip calculation more readable --- src/PIL/TiffImagePlugin.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 68e2759b00b..12c829ea5d9 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1558,12 +1558,12 @@ 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) - rows_per_strip = im.size[1] - strip_byte_counts = stride * im.size[1] # aim for 64 KB strips when using libtiff writer - while libtiff and strip_byte_counts > 2 ** 16 and rows_per_strip > 1: - rows_per_strip = (rows_per_strip + 1) // 2 - strip_byte_counts = stride * rows_per_strip + if libtiff: + rows_per_strip = (2 ** 16 + stride - 1) // stride + 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: From 0ae2981957a9b2c5a7e9d6b085d1a911a763a52c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Komar=C4=8Devi=C4=87?= Date: Wed, 2 Jun 2021 12:31:58 +0200 Subject: [PATCH 4/5] Put upper limit on rows per strip --- src/PIL/TiffImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 12c829ea5d9..85c68be0957 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1560,7 +1560,7 @@ def _save(im, fp, filename): stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8) # aim for 64 KB strips when using libtiff writer if libtiff: - rows_per_strip = (2 ** 16 + stride - 1) // stride + 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 From 100299a838b3461a192461ef97b39f7bc9165084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Komar=C4=8Devi=C4=87?= <4973094+kmilos@users.noreply.github.com> Date: Thu, 3 Jun 2021 14:53:41 +0200 Subject: [PATCH 5/5] Add multiple strip saving test --- Tests/test_file_libtiff.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index b7ec00ef16a..e2f0df84a8c 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -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, @@ -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