Repository: python-pillow/Pillow

Issue:
Tif reading X_RESOLUTION, Y_RESOLUTION seems buggy
in PIL/TiffImagePlugin.py starting at line 723, getscalar() is returning a tuple by default, then manipulating the returned value as if it is a tuple. What happens when the TIF file actually contains a valid scalar X_RESOLUTION and Y_RESOLUTION? At the least the code should check that they are indeed tuples and not scalars, or am I crazy :)?



Discussion:
I'm not familiar with that code, but you are welcome to submit a pull request for review. Sounds like a reasonable fix.

IIRC, It's a rational number in the spec, but I'm not sure if there are alternates/exceptions where an integer is a valid entry.  Do you have a test image that is reported valid in other software that's being interpreted incorrectly here?


Resolving patch:
diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py
--- a/PIL/TiffImagePlugin.py
+++ b/PIL/TiffImagePlugin.py
@@ -615,7 +615,7 @@ def _load_libtiff(self):
             return pixel
 
         self.load_prepare()
-        
+
         if not len(self.tile) == 1:
             raise IOError("Not exactly one tile")
 
@@ -635,7 +635,7 @@ def _load_libtiff(self):
             #
             # Rearranging for supporting byteio items, since they have a fileno
             # that returns an IOError if there's no underlying fp. Easier to deal
-            # with here by reordering. 
+            # with here by reordering.
             if Image.DEBUG:
                 print ("have getvalue. just sending in a string from getvalue")
             n,e = d.decode(self.fp.getvalue())
@@ -649,10 +649,10 @@ def _load_libtiff(self):
             # we have something else.
             if Image.DEBUG:
                 print ("don't have fileno or getvalue. just reading")
-            # UNDONE -- so much for that buffer size thing. 
+            # UNDONE -- so much for that buffer size thing.
             n, e = d.decode(self.fp.read())
 
-        
+
         self.tile = []
         self.readonly = 0
         self.fp = None # might be shared
@@ -723,6 +723,10 @@ def _setup(self):
         xres = getscalar(X_RESOLUTION, (1, 1))
         yres = getscalar(Y_RESOLUTION, (1, 1))
 
+        if xres and not isinstance(xres, tuple):
+            xres = (xres, 1.)
+        if yres and not isinstance(yres, tuple):
+            yres = (yres, 1.)
         if xres and yres:
             xres = xres[0] / (xres[1] or 1)
             yres = yres[0] / (yres[1] or 1)
@@ -750,19 +754,19 @@ def _setup(self):
                 # Decoder expects entire file as one tile.
                 # There's a buffer size limit in load (64k)
                 # so large g4 images will fail if we use that
-                # function. 
+                # function.
                 #
                 # Setup the one tile for the whole image, then
                 # replace the existing load function with our
                 # _load_libtiff function.
-                
+
                 self.load = self._load_libtiff
-                
+
                 # To be nice on memory footprint, if there's a
                 # file descriptor, use that instead of reading
                 # into a string in python.
 
-                # libtiff closes the file descriptor, so pass in a dup. 
+                # libtiff closes the file descriptor, so pass in a dup.
                 try:
                     fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno())
                 except IOError:
@@ -990,7 +994,7 @@ def _save(im, fp, filename):
                     if type(v) == str:
                         atts[k] = v
                         continue
-                    
+
         except:
             # if we don't have an ifd here, just punt.
             pass
@@ -1007,7 +1011,7 @@ def _save(im, fp, filename):
                 break
         if s < 0:
             raise IOError("encoder error %d when writing image file" % s)
-        
+
     else:
         offset = ifd.save(fp)
 


Test patch:
diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py
--- a/Tests/test_file_tiff.py
+++ b/Tests/test_file_tiff.py
@@ -60,4 +60,14 @@ def test_gimp_tiff():
             ])
     assert_no_exception(lambda: im.load())
 
-
+def test_xyres_tiff():
+    from PIL.TiffImagePlugin import X_RESOLUTION, Y_RESOLUTION
+    file = "Tests/images/pil168.tif"
+    im = Image.open(file)
+    assert isinstance(im.tag.tags[X_RESOLUTION][0], tuple)
+    assert isinstance(im.tag.tags[Y_RESOLUTION][0], tuple)
+    #Try to read a file where X,Y_RESOLUTION are ints
+    im.tag.tags[X_RESOLUTION] = (72,)
+    im.tag.tags[Y_RESOLUTION] = (72,)
+    im._setup()
+    assert_equal(im.info['dpi'], (72., 72.))