Repository: python-pillow/Pillow

Issue:
Tuple of integers in info['icc_profile']
Test image: http://ucarecdn.com/265ac6b0-2a1f-4128-b198-ee5159581771/

Pillow 3.2

``` python
>>> icc = Image.open('test.tiff').info['icc_profile']
>>> print repr(icc[:30])
(0, 8, 128, 112, 65, 68, 66, 69, 2, 16, 0, 0, 112, 114, 116, 114, 67, 77, 89, 75, 76, 97, 98, 32, 7, 208, 0, 7, 0, 26)
```

Pillow 2.9

``` python
>>> icc = Image.open('test.tiff').info['icc_profile']
>>> print repr(icc[:30])
'\x00\x08\x80pADBE\x02\x10\x00\x00prtrCMYKLab \x07\xd0\x00\x07\x00\x1a'
```



Discussion:
Currently, I use the following code to extract ICC profile in Pillow 3.2:

``` python
    def _extract_icc(self):
        icc = self.image.info.pop('icc_profile', None)
        if isinstance(icc, tuple):
            try:
                icc = "".join(icc)
            except TypeError:
                icc = ''.join(map(chr, icc))
        return icc
```

While for Pillow 2.9 the following was enough:

``` python
icc = self.image.info.pop('icc_profile', None)
```

Related to #1831?

Really I don't know. How I understand, #1831 fixes tuple of bytestrings instead of bytestring. In this case, Pillow returns a tuple of ints.


Resolving patch:
diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py
--- a/PIL/TiffImagePlugin.py
+++ b/PIL/TiffImagePlugin.py
@@ -544,8 +544,7 @@ def _setitem(self, tag, value, legacy_api):
                             self.tagtype[tag] = 2
 
         if self.tagtype[tag] == 7 and bytes is not str:
-            values = [value.encode("ascii", 'replace') if isinstance(value, str) else value
-                      for value in values]
+            values = [value.encode("ascii", 'replace') if isinstance(value, str) else value]
 
         values = tuple(info.cvt_enum(value) for value in values)
 
@@ -604,8 +603,7 @@ def _register_basic(idx_fmt_name):
 
     @_register_loader(1, 1)  # Basic type, except for the legacy API.
     def load_byte(self, data, legacy_api=True):
-        return (data if legacy_api else
-                tuple(map(ord, data) if bytes is str else data))
+        return data
 
     @_register_writer(1)  # Basic type, except for the legacy API.
     def write_byte(self, data):
diff --git a/PIL/TiffTags.py b/PIL/TiffTags.py
--- a/PIL/TiffTags.py
+++ b/PIL/TiffTags.py
@@ -48,11 +48,21 @@ def lookup(tag):
 #
 #  id: (Name, Type, Length, enum_values)
 #
+# The length here differs from the length in the tiff spec.  For
+# numbers, the tiff spec is for the number of fields returned. We
+# agree here.  For string-like types, the tiff spec uses the length of
+# field in bytes.  In Pillow, we are using the number of expected
+# fields, in general 1 for string-like types.
 
+
+BYTE = 1
 ASCII = 2
 SHORT = 3
 LONG = 4
 RATIONAL = 5
+UNDEFINED = 7
+SIGNED_RATIONAL = 10
+DOUBLE = 12
 
 TAGS_V2 = {
 
@@ -128,8 +138,8 @@ def lookup(tag):
     338: ("ExtraSamples", SHORT, 0),
     339: ("SampleFormat", SHORT, 0),
 
-    340: ("SMinSampleValue", 12, 0),
-    341: ("SMaxSampleValue", 12, 0),
+    340: ("SMinSampleValue", DOUBLE, 0),
+    341: ("SMaxSampleValue", DOUBLE, 0),
     342: ("TransferRange", SHORT, 6),
 
     # obsolete JPEG tags
@@ -152,34 +162,34 @@ def lookup(tag):
 
     # FIXME add more tags here
     34665: ("ExifIFD", SHORT, 1),
-    34675: ('ICCProfile', 7, 0),
-    34853: ('GPSInfoIFD', 1, 1),
+    34675: ('ICCProfile', UNDEFINED, 1),
+    34853: ('GPSInfoIFD', BYTE, 1),
 
     # MPInfo
-    45056: ("MPFVersion", 7, 1),
+    45056: ("MPFVersion", UNDEFINED, 1),
     45057: ("NumberOfImages", LONG, 1),
-    45058: ("MPEntry", 7, 1),
-    45059: ("ImageUIDList", 7, 0),
+    45058: ("MPEntry", UNDEFINED, 1),
+    45059: ("ImageUIDList", UNDEFINED, 0), # UNDONE, check
     45060: ("TotalFrames", LONG, 1),
     45313: ("MPIndividualNum", LONG, 1),
     45569: ("PanOrientation", LONG, 1),
     45570: ("PanOverlap_H", RATIONAL, 1),
     45571: ("PanOverlap_V", RATIONAL, 1),
     45572: ("BaseViewpointNum", LONG, 1),
-    45573: ("ConvergenceAngle", 10, 1),
+    45573: ("ConvergenceAngle", SIGNED_RATIONAL, 1),
     45574: ("BaselineLength", RATIONAL, 1),
-    45575: ("VerticalDivergence", 10, 1),
-    45576: ("AxisDistance_X", 10, 1),
-    45577: ("AxisDistance_Y", 10, 1),
-    45578: ("AxisDistance_Z", 10, 1),
-    45579: ("YawAngle", 10, 1),
-    45580: ("PitchAngle", 10, 1),
-    45581: ("RollAngle", 10, 1),
+    45575: ("VerticalDivergence", SIGNED_RATIONAL, 1),
+    45576: ("AxisDistance_X", SIGNED_RATIONAL, 1),
+    45577: ("AxisDistance_Y", SIGNED_RATIONAL, 1),
+    45578: ("AxisDistance_Z", SIGNED_RATIONAL, 1),
+    45579: ("YawAngle", SIGNED_RATIONAL, 1),
+    45580: ("PitchAngle", SIGNED_RATIONAL, 1),
+    45581: ("RollAngle", SIGNED_RATIONAL, 1),
 
     50741: ("MakerNoteSafety", SHORT, 1, {"Unsafe": 0, "Safe": 1}),
     50780: ("BestQualityScale", RATIONAL, 1),
     50838: ("ImageJMetaDataByteCounts", LONG, 1),
-    50839: ("ImageJMetaData", 7, 1)
+    50839: ("ImageJMetaData", UNDEFINED, 1)
 }
 
 # Legacy Tags structure


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
@@ -287,7 +287,7 @@ def test_load_byte(self):
             ifd = TiffImagePlugin.ImageFileDirectory_v2()
             data = b"abc"
             ret = ifd.load_byte(data, legacy_api)
-            self.assertEqual(ret, b"abc" if legacy_api else (97, 98, 99))
+            self.assertEqual(ret, b"abc")
 
     def test_load_string(self):
         ifd = TiffImagePlugin.ImageFileDirectory_v2()
diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py
--- a/Tests/test_file_tiff_metadata.py
+++ b/Tests/test_file_tiff_metadata.py
@@ -175,7 +175,7 @@ def test_iccprofile(self):
 
         im.save(out)
         reloaded = Image.open(out)
-        self.assert_(type(im.info['icc_profile']) is not type(tuple))
+        self.assert_(type(im.info['icc_profile']) is not tuple)
         self.assertEqual(im.info['icc_profile'], reloaded.info['icc_profile'])
 
     def test_iccprofile_binary(self):
@@ -186,6 +186,16 @@ def test_iccprofile_binary(self):
         self.assertEqual(im.tag_v2.tagtype[34675], 1)
         self.assertTrue(im.info['icc_profile'])
 
+    def test_iccprofile_save_png(self):
+        im = Image.open('Tests/images/hopper.iccprofile.tif')
+        outfile = self.tempfile('temp.png')
+        im.save(outfile)
+
+    def test_iccprofile_binary_save_png(self):
+        im = Image.open('Tests/images/hopper.iccprofile_binary.tif')
+        outfile = self.tempfile('temp.png')
+        im.save(outfile)
+
     def test_exif_div_zero(self):
         im = hopper()
         info = TiffImagePlugin.ImageFileDirectory_v2()