SRGB-aware image resize in Pillow

后端 未结 3 1815
庸人自扰
庸人自扰 2021-02-15 14:06

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

相关标签:
3条回答
  • 2021-02-15 14:41

    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)
    
    0 讨论(0)
  • 2021-02-15 14:45

    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.

    0 讨论(0)
  • 2021-02-15 14:45

    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.

    0 讨论(0)
提交回复
热议问题