Pillow\'s basic Image.resize function doesn\'t appear to have any options for SRGB-aware filtering. Is there a way to do SRGB-aware resizing in Pillow?
I could do it ma
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).clip(0.0, 1.0)
# 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.