Skip to content

Commit

Permalink
Merge pull request #7650 from radarhere/tiff
Browse files Browse the repository at this point in the history
Allow uncompressed TIFF images to be saved in chunks
  • Loading branch information
radarhere committed Jan 1, 2024
2 parents 9e835ca + 99760f4 commit bd7e709
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 10 deletions.
25 changes: 23 additions & 2 deletions Tests/test_file_tiff.py
Expand Up @@ -484,13 +484,13 @@ def test_modify_exif(self, tmp_path):
outfile = str(tmp_path / "temp.tif")
with Image.open("Tests/images/ifd_tag_type.tiff") as im:
exif = im.getexif()
exif[256] = 100
exif[264] = 100

im.save(outfile, exif=exif)

with Image.open(outfile) as im:
exif = im.getexif()
assert exif[256] == 100
assert exif[264] == 100

def test_reload_exif_after_seek(self):
with Image.open("Tests/images/multipage.tiff") as im:
Expand Down Expand Up @@ -781,6 +781,27 @@ def test_get_photoshop_blocks(self):
4001,
]

def test_tiff_chunks(self, tmp_path):
tmpfile = str(tmp_path / "temp.tif")

im = hopper()
with open(tmpfile, "wb") as fp:
for y in range(0, 128, 32):
chunk = im.crop((0, y, 128, y + 32))
if y == 0:
chunk.save(
fp,
"TIFF",
tiffinfo={
TiffImagePlugin.IMAGEWIDTH: 128,
TiffImagePlugin.IMAGELENGTH: 128,
},
)
else:
fp.write(chunk.tobytes())

assert_image_equal_tofile(im, tmpfile)

def test_close_on_load_exclusive(self, tmp_path):
# similar to test_fd_leak, but runs on unixlike os
tmpfile = str(tmp_path / "temp.tif")
Expand Down
1 change: 1 addition & 0 deletions Tests/test_file_tiff_metadata.py
Expand Up @@ -164,6 +164,7 @@ def test_change_stripbytecounts_tag_type(tmp_path):

# Resize the image so that STRIPBYTECOUNTS will be larger than a SHORT
im = im.resize((500, 500))
info[TiffImagePlugin.IMAGEWIDTH] = im.width

# STRIPBYTECOUNTS can be a SHORT or a LONG
info.tagtype[TiffImagePlugin.STRIPBYTECOUNTS] = TiffTags.SHORT
Expand Down
15 changes: 7 additions & 8 deletions src/PIL/TiffImagePlugin.py
Expand Up @@ -1704,28 +1704,27 @@ def _save(im, fp, filename):
colormap += [0] * (256 - colors)
ifd[COLORMAP] = colormap
# data orientation
stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)
w, h = ifd[IMAGEWIDTH], ifd[IMAGELENGTH]
stride = len(bits) * ((w * bits[0] + 7) // 8)
if ROWSPERSTRIP not in ifd:
# aim for given strip size (64 KB by default) when using libtiff writer
if libtiff:
im_strip_size = encoderinfo.get("strip_size", STRIP_SIZE)
rows_per_strip = (
1 if stride == 0 else min(im_strip_size // stride, im.size[1])
)
rows_per_strip = 1 if stride == 0 else min(im_strip_size // stride, h)
# JPEG encoder expects multiple of 8 rows
if compression == "jpeg":
rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, im.size[1])
rows_per_strip = min(((rows_per_strip + 7) // 8) * 8, h)
else:
rows_per_strip = im.size[1]
rows_per_strip = h
if rows_per_strip == 0:
rows_per_strip = 1
ifd[ROWSPERSTRIP] = rows_per_strip
strip_byte_counts = 1 if stride == 0 else stride * ifd[ROWSPERSTRIP]
strips_per_image = (im.size[1] + ifd[ROWSPERSTRIP] - 1) // ifd[ROWSPERSTRIP]
strips_per_image = (h + ifd[ROWSPERSTRIP] - 1) // ifd[ROWSPERSTRIP]
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),
stride * h - strip_byte_counts * (strips_per_image - 1),
)
ifd[STRIPOFFSETS] = tuple(
range(0, strip_byte_counts * strips_per_image, strip_byte_counts)
Expand Down

0 comments on commit bd7e709

Please sign in to comment.