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

Ensure TIFF RowsPerStrip is multiple of 8 for JPEG compression #5588

Merged
merged 2 commits into from Jul 18, 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
5 changes: 3 additions & 2 deletions Tests/test_file_libtiff.py
Expand Up @@ -968,10 +968,11 @@ def test_realloc_overflow(self):
assert str(e.value) == "-9"
TiffImagePlugin.READ_LIBTIFF = False

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

with Image.open(out) as im:
# Assert that there are multiple strips
Expand Down
3 changes: 3 additions & 0 deletions src/PIL/TiffImagePlugin.py
Expand Up @@ -1577,6 +1577,9 @@ def _save(im, fp, filename):
# aim for 64 KB strips when using libtiff writer
if libtiff:
rows_per_strip = min((2 ** 16 + stride - 1) // stride, im.size[1])
# JPEG encoder expects multiple of 8 rows
if compression == "jpeg":
rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1])
rows_per_strip = ((rows_per_strip + 7) // 8) * 8

Because rows_per_strip has already been set to be im.size[1] at minimum, and the calculation here only ever rounds up, the min part is unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it is needed, precisely because it rounds up. This is to support the rare case of small images where im.size[1]<8 and you end up as a single strip. Libtiff/jpeg codec have no problem handling the single/last strip that is not factor of 8, as it is padded internally.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, thanks

Copy link
Member

@radarhere radarhere Jul 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1])
rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, rows_per_strip)

What about this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't work for the corner case where you have u multi-strip w/ e.g. rows_per_strip=2 and im.size[1]=6. For JPEG, you want to end up as single strip of 6 rows.

else:
rows_per_strip = im.size[1]
strip_byte_counts = stride * rows_per_strip
Expand Down