Repository: python-pillow/Pillow

Issue:
ICC Profile not saved for TIF files
When loading an image from an array(image opened with cv2, skimage, etc.) and then saving it in .tif format, the icc_profile is not saved. For example:

```python
import cv2
from PIL import Image, ImageCms

profile = ImageCms.getOpenProfile("AdobeRGB.icm")

img_cv2 = cv2.imread("original.png")
img_pil = Image.fromarray(cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB))
img_pil.save("result_cv2_to_pil_tif.tif", icc_profile=profile.tobytes())
```

However, when doing the same for a ".png" file the icc_profile is saved

```python
img_pil.save("result_cv2_to_pil_tif.tif", icc_profile=profile.tobytes())
```

I solved it adding the icc_profile to the info attribute prior saving the image:

```python
img_pil.info['icc_profile'] = profile.tobytes()
img_pil.save("result_cv2_to_pil_tif.tif")
```

It seems a bug in the **save** method when the file format is .tif, as it not considers adding the icc_profile to the output image if it was not previously in the info attribute.

I haven't tested it with other file formats.


Discussion:
Hi Crubert-digit, 
I justed wanted to add that I'm having the same problem. For me the ".tobytes()" give me an error as well "AttributeError: 'bytes' object has no attribute 'tobytes'". If I save the images without going to bytes it works for saving it, but the colors of the image do not change (for .tif format). 

I checked my images color spaces before and after the convert (.jpg and .tif). Both of them shows after the convert that there is a Adobe RGB profile, but only the .jpg shows the correct colors. 

Does anyone know how to fix this problem? 
Hi GvdBend, 

Are you opening the profile file with ImageCms.getOpenProfile("AdobeRGB.icm")?? It seems you're trying to convert to Bytes an object which is already Bytes. I use the tobytes() method to convert the profile file to the icc_profile bytes format after opening it with ImageCms.

For the TIF files try assigning the profile on the info attribute prior saving it (I'm assuming the profile variable already has the icc profile loaded and it is a Bytes object):

image.info['icc_profile'] = profile



Hi Crubert-digit, 

Sorry, I'm quite new to Python and I indeed didn't use the "ImageCms.getOpenProfile("AdobeRGB.icm")". Now when I try to use your code it gives me the following error : 'PIL.ImageCms.PyCMSError: cannot open profile file'. My understanding is that I haven't created the AdobeRGB profile yet, as it does not standard exist in Pillow? 

I've checked the website and tried to read more in to the getOpenProfile and buildTransformFromOpenProfiles, but can't really figure out what to do. Could you please explain to me what you did. Thanks in advance! 
Hi GvdBend,

ImageCms.getOpenProfile("AdobeRGB.icm") is opening a file in my path called 'AdobeRGB.icm' which contains the icc profile, so actually the method is:
ImageCms.getOpenProfile(file_path)


Hi Crubert-digit, 

Thanks to you I made it work, thank you! 
> However, when doing the same for a ".png" file the icc_profile is saved
> 
> ```python
> img_pil.save("result_cv2_to_pil_tif.tif", icc_profile=profile.tobytes())
> ```

This is confusing - I presume you meant to write "result_cv2_to_pil_tif.png" in your code example.

Resolving patch:
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -1481,8 +1481,9 @@ def _save(im, fp, filename):
 
     # preserve ICC profile (should also work when saving other formats
     # which support profiles as TIFF) -- 2008-06-06 Florian Hoech
-    if "icc_profile" in im.info:
-        ifd[ICCPROFILE] = im.info["icc_profile"]
+    icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile"))
+    if icc:
+        ifd[ICCPROFILE] = icc
 
     for key, name in [
         (IMAGEDESCRIPTION, "description"),


Test patch:
diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py
--- a/Tests/test_file_png.py
+++ b/Tests/test_file_png.py
@@ -517,6 +517,8 @@ def test_save_icc_profile(self):
 
     def test_discard_icc_profile(self):
         with Image.open("Tests/images/icc_profile.png") as im:
+            assert "icc_profile" in im.info
+
             im = roundtrip(im, icc_profile=None)
         assert "icc_profile" not in im.info
 
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
@@ -568,6 +568,28 @@ def test_saving_icc_profile(self, tmp_path):
         with Image.open(tmpfile) as reloaded:
             assert b"Dummy value" == reloaded.info["icc_profile"]
 
+    def test_save_icc_profile(self, tmp_path):
+        im = hopper()
+        assert "icc_profile" not in im.info
+
+        outfile = str(tmp_path / "temp.tif")
+        icc_profile = b"Dummy value"
+        im.save(outfile, icc_profile=icc_profile)
+
+        with Image.open(outfile) as reloaded:
+            assert reloaded.info["icc_profile"] == icc_profile
+
+    def test_discard_icc_profile(self, tmp_path):
+        outfile = str(tmp_path / "temp.tif")
+
+        with Image.open("Tests/images/icc_profile.png") as im:
+            assert "icc_profile" in im.info
+
+            im.save(outfile, icc_profile=None)
+
+        with Image.open(outfile) as reloaded:
+            assert "icc_profile" not in reloaded.info
+
     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")