Skip to content

Commit

Permalink
Cherry picked 13f2c5a
Browse files Browse the repository at this point in the history
  • Loading branch information
Frederick Price committed Apr 22, 2023
1 parent 17e624e commit 812b469
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 40 deletions.
Binary file not shown.
69 changes: 33 additions & 36 deletions Tests/test_file_tiff.py
Expand Up @@ -2,7 +2,9 @@
import sys
from io import BytesIO

from PIL import Image, TiffImagePlugin
import pytest

from PIL import Image, ImageFile, TiffImagePlugin, UnidentifiedImageError
from PIL._util import py3
from PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION

Expand All @@ -13,7 +15,6 @@

class TestFileTiff(PillowTestCase):
def test_sanity(self):

filename = self.tempfile("temp.tif")

hopper("RGB").save(filename)
Expand Down Expand Up @@ -64,16 +65,14 @@ def test_wrong_bits_per_sample(self):

self.assertEqual(im.mode, "RGBA")
self.assertEqual(im.size, (52, 53))
self.assertEqual(
im.tile, [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))])
self.assertEqual(im.tile, [("raw", (0, 0, 52, 53), 160, ("RGBA", 0, 1))])
im.load()

def test_set_legacy_api(self):
ifd = TiffImagePlugin.ImageFileDirectory_v2()
with self.assertRaises(Exception) as e:
ifd.legacy_api = None
self.assertEqual(str(e.exception),
"Not allowing setting of legacy api")
self.assertEqual(str(e.exception), "Not allowing setting of legacy api")

def test_size(self):
filename = "Tests/images/pil168.tif"
Expand All @@ -93,10 +92,8 @@ def test_xyres_tiff(self):
self.assertIsInstance(im.tag[Y_RESOLUTION][0], tuple)

# v2 api
self.assertIsInstance(
im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)
self.assertIsInstance(
im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)
self.assertIsInstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)
self.assertIsInstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)

self.assertEqual(im.info["dpi"], (72.0, 72.0))

Expand All @@ -105,10 +102,8 @@ def test_xyres_fallback_tiff(self):
im = Image.open(filename)

# v2 api
self.assertIsInstance(
im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)
self.assertIsInstance(
im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)
self.assertIsInstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)
self.assertIsInstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)
self.assertRaises(KeyError, lambda: im.tag_v2[RESOLUTION_UNIT])

# Legacy.
Expand Down Expand Up @@ -163,12 +158,10 @@ def test_save_setting_missing_resolution(self):
def test_invalid_file(self):
invalid_file = "Tests/images/flower.jpg"

self.assertRaises(
SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file)
self.assertRaises(SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file)

TiffImagePlugin.PREFIXES.append(b"\xff\xd8\xff\xe0")
self.assertRaises(
SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file)
self.assertRaises(SyntaxError, TiffImagePlugin.TiffImageFile, invalid_file)
TiffImagePlugin.PREFIXES.pop()

def test_bad_exif(self):
Expand Down Expand Up @@ -223,8 +216,8 @@ def test_16bit_s(self):
self.assertEqual(im.getpixel((0, 1)), 0)

def test_12bit_rawmode(self):
""" Are we generating the same interpretation
of the image as Imagemagick is? """
"""Are we generating the same interpretation
of the image as Imagemagick is?"""

im = Image.open("Tests/images/12bit.cropped.tif")

Expand All @@ -243,8 +236,7 @@ def test_32bit_float(self):
im.load()

self.assertEqual(im.getpixel((0, 0)), -0.4526388943195343)
self.assertEqual(
im.getextrema(), (-3.140936851501465, 3.140684127807617))
self.assertEqual(im.getextrema(), (-3.140936851501465, 3.140684127807617))

def test_unknown_pixel_mode(self):
self.assertRaises(
Expand Down Expand Up @@ -454,8 +446,7 @@ def test_gray_semibyte_per_pixel(self):
self.assert_image_equal(im, im2)

def test_with_underscores(self):
kwargs = {"resolution_unit": "inch",
"x_resolution": 72, "y_resolution": 36}
kwargs = {"resolution_unit": "inch", "x_resolution": 72, "y_resolution": 36}
filename = self.tempfile("temp.tif")
hopper("RGB").save(filename, **kwargs)
im = Image.open(filename)
Expand Down Expand Up @@ -486,25 +477,22 @@ def test_strip_raw(self):
infile = "Tests/images/tiff_strip_raw.tif"
im = Image.open(infile)

self.assert_image_equal_tofile(
im, "Tests/images/tiff_adobe_deflate.png")
self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")

def test_strip_planar_raw(self):
# gdal_translate -of GTiff -co INTERLEAVE=BAND \
# tiff_strip_raw.tif tiff_strip_planar_raw.tiff
infile = "Tests/images/tiff_strip_planar_raw.tif"
im = Image.open(infile)

self.assert_image_equal_tofile(
im, "Tests/images/tiff_adobe_deflate.png")
self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")

def test_strip_planar_raw_with_overviews(self):
# gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16
infile = "Tests/images/tiff_strip_planar_raw_with_overviews.tif"
im = Image.open(infile)

self.assert_image_equal_tofile(
im, "Tests/images/tiff_adobe_deflate.png")
self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")

def test_tiled_planar_raw(self):
# gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \
Expand All @@ -513,8 +501,7 @@ def test_tiled_planar_raw(self):
infile = "Tests/images/tiff_tiled_planar_raw.tif"
im = Image.open(infile)

self.assert_image_equal_tofile(
im, "Tests/images/tiff_adobe_deflate.png")
self.assert_image_equal_tofile(im, "Tests/images/tiff_adobe_deflate.png")

def test_palette(self):
for mode in ["P", "PA"]:
Expand All @@ -541,8 +528,7 @@ def test_tiff_save_all(self):
# Test appending images
mp = io.BytesIO()
im = Image.new("RGB", (100, 100), "#f00")
ims = [Image.new("RGB", (100, 100), color)
for color in ["#0f0", "#00f"]]
ims = [Image.new("RGB", (100, 100), color) for color in ["#0f0", "#00f"]]
im.copy().save(mp, format="TIFF", save_all=True, append_images=ims)

mp.seek(0, os.SEEK_SET)
Expand All @@ -555,8 +541,7 @@ def imGenerator(ims):
yield im

mp = io.BytesIO()
im.save(mp, format="TIFF", save_all=True,
append_images=imGenerator(ims))
im.save(mp, format="TIFF", save_all=True, append_images=imGenerator(ims))

mp.seek(0, os.SEEK_SET)
reread = Image.open(mp)
Expand Down Expand Up @@ -616,6 +601,18 @@ def test_fd_leak(self):
tmpfile = self.tempfile("temp.tif")
import os

@pytest.mark.parametrize(
"test_file",
[
"Tests/images/oom-225817ca0f8c663be7ab4b9e717b02c661e66834.tif",
],
)
@pytest.mark.timeout(2)
def test_oom(self, test_file):
with pytest.raises(UnidentifiedImageError):
with Image.open(test_file) as im:
im.load()

# this is an mmaped file.
with Image.open("Tests/images/uint16_1_4660.tif") as im:
im.save(tmpfile)
Expand Down
25 changes: 21 additions & 4 deletions src/PIL/TiffImagePlugin.py
Expand Up @@ -261,6 +261,8 @@
(MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
}

MAX_SAMPLESPERPIXEL = max(len(key_tp[4]) for key_tp in OPEN_INFO.keys())

PREFIXES = [
b"MM\x00\x2A", # Valid TIFF header with big-endian byte order
b"II\x2A\x00", # Valid TIFF header with little-endian byte order
Expand Down Expand Up @@ -1264,10 +1266,25 @@ def _setup(self):
else:
bps_count = 1
bps_count += len(extra_tuple)
# Some files have only one value in bps_tuple,
# while should have more. Fix it
if bps_count > len(bps_tuple) and len(bps_tuple) == 1:
bps_tuple = bps_tuple * bps_count
bps_actual_count = len(bps_tuple)
samples_per_pixel = self.tag_v2.get(
SAMPLESPERPIXEL,
3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1,
)

if samples_per_pixel > MAX_SAMPLESPERPIXEL:
# DOS check, samples_per_pixel can be a Long, and we extend the tuple below
logger.error("More samples per pixel than can be decoded: %s", samples_per_pixel)
raise SyntaxError("Invalid value for samples per pixel")

if samples_per_pixel < bps_actual_count:
# If a file has more values in bps_tuple than expected,
# remove the excess.
bps_tuple = bps_tuple[:samples_per_pixel]
elif samples_per_pixel > bps_actual_count and bps_actual_count == 1:
# If a file has only one value in bps_tuple, when it should have more,
# presume it is the same number of bits for all of the samples.
bps_tuple = bps_tuple * samples_per_pixel

# mode: check photometric interpretation and bits per pixel
key = (
Expand Down

0 comments on commit 812b469

Please sign in to comment.