Convert RGBA PNG to RGB with PIL
Question: I'm using PIL to convert a transparent PNG image uploaded with Django to a JPG
file. The output looks broken.

### Source file

![transparent source file](http://i.stack.imgur.com/I2uNe.png)

### Code

    
    
    Image.open(object.logo.path).save('/tmp/output.jpg', 'JPEG')
    

or

    
    
    Image.open(object.logo.path).convert('RGB').save('/tmp/output.png')
    

### Result

Both ways, the resulting image looks like this:

![resulting file](http://i.stack.imgur.com/rmlPz.png)

Is there a way to fix this? I'd like to have white background where the
transparent background used to be.

* * *

# Solution

Thanks to the great answers, I've come up with the following function
collection:

    
    
    import Image
    import numpy as np
    
    
    def alpha_to_color(image, color=(255, 255, 255)):
        """Set all fully transparent pixels of an RGBA image to the specified color.
        This is a very simple solution that might leave over some ugly edges, due
        to semi-transparent areas. You should use alpha_composite_with color instead.
    
        Source: http://stackoverflow.com/a/9166671/284318
    
        Keyword Arguments:
        image -- PIL RGBA Image object
        color -- Tuple r, g, b (default 255, 255, 255)
    
        """ 
        x = np.array(image)
        r, g, b, a = np.rollaxis(x, axis=-1)
        r[a == 0] = color[0]
        g[a == 0] = color[1]
        b[a == 0] = color[2] 
        x = np.dstack([r, g, b, a])
        return Image.fromarray(x, 'RGBA')
    
    
    def alpha_composite(front, back):
        """Alpha composite two RGBA images.
    
        Source: http://stackoverflow.com/a/9166671/284318
    
        Keyword Arguments:
        front -- PIL RGBA Image object
        back -- PIL RGBA Image object
    
        """
        front = np.asarray(front)
        back = np.asarray(back)
        result = np.empty(front.shape, dtype='float')
        alpha = np.index_exp[:, :, 3:]
        rgb = np.index_exp[:, :, :3]
        falpha = front[alpha] / 255.0
        balpha = back[alpha] / 255.0
        result[alpha] = falpha + balpha * (1 - falpha)
        old_setting = np.seterr(invalid='ignore')
        result[rgb] = (front[rgb] * falpha + back[rgb] * balpha * (1 - falpha)) / result[alpha]
        np.seterr(**old_setting)
        result[alpha] *= 255
        np.clip(result, 0, 255)
        # astype('uint8') maps np.nan and np.inf to 0
        result = result.astype('uint8')
        result = Image.fromarray(result, 'RGBA')
        return result
    
    
    def alpha_composite_with_color(image, color=(255, 255, 255)):
        """Alpha composite an RGBA image with a single color image of the
        specified color and the same size as the original image.
    
        Keyword Arguments:
        image -- PIL RGBA Image object
        color -- Tuple r, g, b (default 255, 255, 255)
    
        """
        back = Image.new('RGBA', size=image.size, color=color + (255,))
        return alpha_composite(image, back)
    
    
    def pure_pil_alpha_to_color_v1(image, color=(255, 255, 255)):
        """Alpha composite an RGBA Image with a specified color.
    
        NOTE: This version is much slower than the
        alpha_composite_with_color solution. Use it only if
        numpy is not available.
    
        Source: http://stackoverflow.com/a/9168169/284318
    
        Keyword Arguments:
        image -- PIL RGBA Image object
        color -- Tuple r, g, b (default 255, 255, 255)
    
        """ 
        def blend_value(back, front, a):
            return (front * a + back * (255 - a)) / 255
    
        def blend_rgba(back, front):
            result = [blend_value(back[i], front[i], front[3]) for i in (0, 1, 2)]
            return tuple(result + [255])
    
        im = image.copy()  # don't edit the reference directly
        p = im.load()  # load pixel array
        for y in range(im.size[1]):
            for x in range(im.size[0]):
                p[x, y] = blend_rgba(color + (255,), p[x, y])
    
        return im
    
    def pure_pil_alpha_to_color_v2(image, color=(255, 255, 255)):
        """Alpha composite an RGBA Image with a specified color.
    
        Simpler, faster version than the solutions above.
    
        Source: http://stackoverflow.com/a/9459208/284318
    
        Keyword Arguments:
        image -- PIL RGBA Image object
        color -- Tuple r, g, b (default 255, 255, 255)
    
        """
        image.load()  # needed for split()
        background = Image.new('RGB', image.size, color)
        background.paste(image, mask=image.split()[3])  # 3 is the alpha channel
        return background
    

## Performance

The simple non-compositing `alpha_to_color` function is the fastest solution,
but leaves behind ugly borders because it does not handle semi transparent
areas.

Both the pure PIL and the numpy compositing solutions give great results, but
`alpha_composite_with_color` is much faster (8.93 msec) than
`pure_pil_alpha_to_color` (79.6 msec). ~~If numpy is available on your system,
that's the way to go.~~ (Update: The new pure PIL version is the fastest of
all mentioned solutions.)

    
    
    $ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.alpha_to_color(i)"
    10 loops, best of 3: 4.67 msec per loop
    $ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.alpha_composite_with_color(i)"
    10 loops, best of 3: 8.93 msec per loop
    $ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.pure_pil_alpha_to_color(i)"
    10 loops, best of 3: 79.6 msec per loop
    $ python -m timeit "import Image; from apps.front import utils; i = Image.open(u'logo.png'); i2 = utils.pure_pil_alpha_to_color_v2(i)"
    10 loops, best of 3: 1.1 msec per loop
    


Answer: Here's a version that's much simpler - not sure how performant it is. Heavily
based on some django snippet I found while building `RGBA -> JPG + BG` support
for sorl thumbnails.

    
    
    from PIL import Image
    
    png = Image.open(object.logo.path)
    png.load() # required for png.split()
    
    background = Image.new("RGB", png.size, (255, 255, 255))
    background.paste(png, mask=png.split()[3]) # 3 is the alpha channel
    
    background.save('foo.jpg', 'JPEG', quality=80)
    

Result @80%

![enter image description here](http://i.stack.imgur.com/jaVG7.jpg)

Result @ 50%  
![enter image description here](http://i.stack.imgur.com/9IsAI.jpg)

