Search code examples
pythonpython-3.xpython-imaging-librarycolor-profile

Why does colour completely changes when opening and showing this image? How do I add an ICC profile to an image?


This is the original image:

Original image

This is the image after I opened and showed it with Pillow:

Image opened in Pillow

The original image is JPEG, but I've tried changing the format to TIFF, PSD, but nothing works. I've tried converting the image to RGB, RGBA, CMYK, and can't see any improvement.

If I simply take a screenshot of the image, and open it in Pillow, the colours are preserved.

Then I thought I might not need Pillow, and I can use another library, and tried OpenCV, but the same thing happened! Same results as the picture above.

As @HansHirse suggested in the comments in my previous question, the post made me realise that when I just saving the image, the colours are preserved. (Though just opening and saving without using the ICC profile produces the exact image anyway.)

from PIL import Image

i = Image.open("/Users/shashwataryal/Downloads/Tom Ford Rodeo.jpeg")

##Colours are preserved when I just save the image immediately after opening.
i.save("/Users/shashwataryal/Downloads/1.jpeg")

##however, if I create a new image and paste my image I have the same issue.
##The image is very saturated. (not dull like in the question @HansHirse suggested in my previous post)
layer = Image.new('RGB', i.size, "#faf8f8")
layer.show()
layer.paste(i)
layer.show()
layer.save('/Users/shashwataryal/Downloads/1.jpeg',icc_profile=i.info.get('icc_profile'))
layer.save('/Users/shashwataryal/Downloads/2.jpeg',icc_profile=i.info.get('icc_profile'))

I'm trying to add the image to a new blank Pillow image and maybe add other images besides it as well. I can't figure out how to add the ICC profile to the new image. Will it affect other images if I paste them onto the new image created in Pillow?

My previous question was marked as a duplicate of:

Color gets dull: opencv cv2.imread cv2.imwrite

While this question is similar, it just deals with saving the image right after opening, and I have no issues saving it immediately after opening. My issue is saving it after pasting it onto a big blank canvas.


Solution

  • You'll need Pillow's ImageCms module:

    The ImageCms module provides color profile management support [...]

    For the following demonstration code, I used sample images with different embedded ICC profiles from here.

    from matplotlib import pyplot as plt
    from PIL import Image, ImageCms
    from io import BytesIO
    
    # Infices for test image
    infices = ['sRGB', 'AdobeRGB', 'ColorMatch', 'ProPhoto', 'WideRGB']
    
    # Initialize output visualization
    plt.figure(1, figsize=(19, 9))
    
    # Iterate infices
    for i_infix, infix in enumerate(infices, start=1):
    
        # Read image
        image = Image.open('Momiji-{}-yes.jpg'.format(infix))
    
        # Extract original ICC profile
        orig_icc = ImageCms.ImageCmsProfile(BytesIO(image.info.get('icc_profile')))
        desc = ImageCms.getProfileDescription(orig_icc)
    
        # Plot image with original ICC profile
        plt.subplot(2, len(infices), i_infix), plt.imshow(image)
        plt.title('Original ICC profile: {}'.format(desc))
    
        # Create sRGB ICC profile and convert image to sRGB
        srgb_icc = ImageCms.createProfile('sRGB')
        image = ImageCms.profileToProfile(image, orig_icc, srgb_icc)
    
        # Plot converted image with sRGB ICC profile
        plt.subplot(2, len(infices), len(infices) + i_infix), plt.imshow(image)
        plt.title('sRGB ICC profile')
    
    plt.tight_layout(), plt.show()
    

    The code extracts the ICC profile of each sample image, and stores it as an CmsProfile object. Using ImageCms.createProfile, you can create an inbuilt sRGB ICC profile, which I used for rectifying all input images using ImageCms.profileToProfile:

    Output sRGB

    As you can see, the input images have quite noticable color differences while the output images are very similar (I haven't checked pixel-wise equality).

    If you want to keep the ICC profile of a specific input image, you can simply save that in the beginning, and rectify all your input images w.r.t. that ICC profile. For example, that'd be the output for converting all input images to the "ProPhoto RGB" ICC profile:

    ProPhoto RGB

    So, you need to open your image, extract the ICC profile, paste your image(s) to the new blank canvas, and convert the resulting image to the saved ICC profile. If you have images from different sources, thus maybe having different embedded ICC profiles, you'll need to decide what to do: (1) Rectify all input images like shown above, incorporating possible color differences or (2) Actually convert the colors of your input images, such that they have the same appearance, but using the ICC profile of the final result. Latter will be a topic on its own.

    ----------------------------------------
    System information
    ----------------------------------------
    Platform:      Windows-10-10.0.19041-SP0
    Python:        3.9.1
    PyCharm:       2021.1.1
    Matplotlib:    3.4.2
    Pillow:        8.2.0
    ----------------------------------------