SRGB-aware image resize in Pillow

情到浓时终转凉″ 提交于 2019-12-04 05:59:31

I ended up implementing sRGB-aware resize myself using the following routine. It takes an 8-bit RGB image and a target size and resampling filter.

from PIL import Image
import numpy as np

def SRGBResize(im, size, filter):
    # Convert to numpy array of float
    arr = np.array(im, dtype=np.float32) / 255.0
    # Convert sRGB -> linear
    arr = np.where(arr <= 0.04045, arr/12.92, ((arr+0.055)/1.055)**2.4)
    # Resize using PIL
    arrOut = np.zeros((size[1], size[0], arr.shape[2]))
    for i in range(arr.shape[2]):
        chan = Image.fromarray(arr[:,:,i])
        chan = chan.resize(size, filter)
        arrOut[:,:,i] = np.array(chan)
    # Convert linear -> sRGB
    arrOut = np.where(arrOut <= 0.0031308, 12.92*arrOut, 1.055*arrOut**(1.0/2.4) - 0.055)
    # Convert to 8-bit
    arrOut = np.uint8(np.rint(arrOut * 255.0))
    # Convert back to PIL
    return Image.fromarray(arrOut)

After a lot of reading and trial and error I have stumbled upon a good solution. It assumes an sRGB image, converts it to linear colour space to do the resizing, then converts back to sRGB.

There is a slight downside in that a colour depth of 8 bits per pixel is used even when the image is in it's linear form. This results in a loss of variance in darker regions. Reading from this issue post it seems there is no way to convert to a higher depth using Pillow unfortunately.

from PIL import Image
from PIL.ImageCms import profileToProfile

SRGB_PROFILE = 'sRGB.icc'
LINEARIZED_PROFILE = 'linearized-sRGB.icc'

im = Image.open(IN_PATH)
im = profileToProfile(im, SRGB_PROFILE, LINEARIZED_PROFILE)
im = im.resize((WIDTH, HEIGHT), Image.ANTIALIAS)
im = profileToProfile(im, LINEARIZED_PROFILE, SRGB_PROFILE)

im.save(OUT_PATH)

You'll need a linearised ICC colour profile as Pillow/lcms can't do it without. You can get one from this issue post and the author mentions in the file "no copyright, use freely". You'll also need an sRGB profile which should be easily obtainable from your OS or online.

Much of the processing time is taken up computing the transformations from sRGB and back again. If you are going to be doing a lot of these operations you can store these transformations to re-use them like so:

from PIL.ImageCms import buildTransform, applyTransform

SRGB_TO_LINEARIZED = buildTransform(SRGB_PROFILE, LINEARIZED_PROFILE, 'RGB', 'RGB')
LINEARIZED_TO_SRGB = buildTransform(LINEARIZED_PROFILE, SRGB_PROFILE, 'RGB', 'RGB')

im = applyTransform(im, SRGB_TO_LINEARIZED)
im = im.resize((WIDTH, HEIGHT), Image.ANTIALIAS)
im = applyTransform(im, LINEARIZED_TO_SRGB)

I hope this helps and I'd be interested to hear if anyone has any ideas on resolving the 8 bit colour space issue.

99% of image resize implementations will not get sRGB right (which, unfortunately, is 99.9% of image material), and those who do usually will do it right by default and give you the option to opt out of gamma de/encoding.

[opinionated mode on, read with care]

IOW, if there is no option you likely have to add the code yourself - or just use pamscale. If a library doesn't get sRGB right it will have other flaws anyway.

[opinionated mode off]

You could de/encode yourself as discussed in

http://www.imagemagick.org/discourse-server/viewtopic.php?t=15955

but a from quick glance it seems pillow is not capable of doing that trick.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!