使用PIL将RGBA PNG转换为RGB。

时间:2021-01-20 00:25:18

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

我正在使用PIL将一个透明的PNG图像转换成JPG文件上传的Django。输出看起来坏了。

Source file

使用PIL将RGBA PNG转换为RGB。

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:

这两种方法,得到的图像都是这样的:

使用PIL将RGBA PNG转换为RGB。

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://*.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://*.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://*.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://*.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.

简单的非合成的alpha_to_color函数是最快的解决方案,但是由于它没有处理半透明区域,所以留下了丑陋的边界。

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.)

纯粹的PIL和numpy合成的解决方案都给出了很好的结果,但是alpha_composite_with_color比pure_pil_alpha_to_color (79.6 msec)要快得多(8.93 msec)。如果您的系统上有numpy,那就是方法。(更新:新的纯PIL版本是所有提到的解决方案中最快的。)

$ 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

6 个解决方案

#1


86  

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.

这里有一个简单得多的版本——不确定它的性能如何。基于我在构建RGBA -> JPG + BG支持sorl缩略图时发现的一些django片段。

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%

结果@80%

使用PIL将RGBA PNG转换为RGB。

Result @ 50%
使用PIL将RGBA PNG转换为RGB。

结果@ 50%

#2


20  

By using Image.alpha_composite, the solution by Yuji 'Tomita' Tomita become simpler. This code can avoid a tuple index out of range error if png has no alpha channel.

通过使用图像。alpha_composite,由Yuji 'Tomita' Tomita的解决方案变得更简单。如果png没有alpha通道,这段代码可以避免一个tuple索引超出范围错误。

from PIL import Image

png = Image.open(img_path).convert('RGBA')
background = Image.new('RGBA', png.size, (255,255,255))

alpha_composite = Image.alpha_composite(background, png)
alpha_composite.save('foo.jpg', 'JPEG', quality=80)

#3


12  

The transparent parts mostly have RGBA value (0,0,0,0). Since the JPG has no transparency, the jpeg value is set to (0,0,0), which is black.

透明的部分主要有RGBA值(0,0,0,0)。由于JPG没有透明度,jpeg值被设置为(0,0,0),即黑色。

Around the circular icon, there are pixels with nonzero RGB values where A = 0. So they look transparent in the PNG, but funny-colored in the JPG.

在圆形图标周围,有非零RGB值的像素,其中A = 0。所以它们在PNG中看起来是透明的,但是在JPG中是很有趣的。

You can set all pixels where A == 0 to have R = G = B = 255 using numpy like this:

您可以设置所有像素,其中A == 0,让R = G = B = 255,使用numpy:

import Image
import numpy as np

FNAME = 'logo.png'
img = Image.open(FNAME).convert('RGBA')
x = np.array(img)
r, g, b, a = np.rollaxis(x, axis = -1)
r[a == 0] = 255
g[a == 0] = 255
b[a == 0] = 255
x = np.dstack([r, g, b, a])
img = Image.fromarray(x, 'RGBA')
img.save('/tmp/out.jpg')

使用PIL将RGBA PNG转换为RGB。


Note that the logo also has some semi-transparent pixels used to smooth the edges around the words and icon. Saving to jpeg ignores the semi-transparency, making the resultant jpeg look quite jagged.

注意,该徽标也有一些半透明像素用来平滑文字和图标的边缘。对jpeg的保存忽略了半透明性,使得最终的jpeg看起来相当参差。

A better quality result could be made using imagemagick's convert command:

使用imagemagick的convert命令可以获得更好的质量结果:

convert logo.png -background white -flatten /tmp/out.jpg

使用PIL将RGBA PNG转换为RGB。


To make a nicer quality blend using numpy, you could use alpha compositing:

为了更好地使用numpy,您可以使用alpha合成:

import Image
import numpy as np

def alpha_composite(src, dst):
    '''
    Return the alpha composite of src and dst.

    Parameters:
    src -- PIL RGBA Image object
    dst -- PIL RGBA Image object

    The algorithm comes from http://en.wikipedia.org/wiki/Alpha_compositing
    '''
    # http://*.com/a/3375291/190597
    # http://*.com/a/9166671/190597
    src = np.asarray(src)
    dst = np.asarray(dst)
    out = np.empty(src.shape, dtype = 'float')
    alpha = np.index_exp[:, :, 3:]
    rgb = np.index_exp[:, :, :3]
    src_a = src[alpha]/255.0
    dst_a = dst[alpha]/255.0
    out[alpha] = src_a+dst_a*(1-src_a)
    old_setting = np.seterr(invalid = 'ignore')
    out[rgb] = (src[rgb]*src_a + dst[rgb]*dst_a*(1-src_a))/out[alpha]
    np.seterr(**old_setting)    
    out[alpha] *= 255
    np.clip(out,0,255)
    # astype('uint8') maps np.nan (and np.inf) to 0
    out = out.astype('uint8')
    out = Image.fromarray(out, 'RGBA')
    return out            

FNAME = 'logo.png'
img = Image.open(FNAME).convert('RGBA')
white = Image.new('RGBA', size = img.size, color = (255, 255, 255, 255))
img = alpha_composite(img, white)
img.save('/tmp/out.jpg')

使用PIL将RGBA PNG转换为RGB。

#4


4  

Here's a solution in pure PIL.

这是纯PIL的一个解决方案。

def blend_value(under, over, a):
    return (over*a + under*(255-a)) / 255

def blend_rgba(under, over):
    return tuple([blend_value(under[i], over[i], over[3]) for i in (0,1,2)] + [255])

white = (255, 255, 255, 255)

im = Image.open(object.logo.path)
p = im.load()
for y in range(im.size[1]):
    for x in range(im.size[0]):
        p[x,y] = blend_rgba(white, p[x,y])
im.save('/tmp/output.png')

#5


1  

It's not broken. It's doing exactly what you told it to; those pixels are black with full transparency. You will need to iterate across all pixels and convert ones with full transparency to white.

它没有破。它完全按照你说的做;这些像素是黑色的,完全透明。您需要遍历所有的像素,并将完全透明的转换为白色。

#6


0  

import Image

导入图片

def fig2img ( fig ): """ @brief Convert a Matplotlib figure to a PIL Image in RGBA format and return it @param fig a matplotlib figure @return a Python Imaging Library ( PIL ) image """ # put the figure pixmap into a numpy array buf = fig2data ( fig ) w, h, d = buf.shape return Image.frombytes( "RGBA", ( w ,h ), buf.tostring( ) )

def fig2img(图):“”“@brief Matplotlib图转换为公益诉讼RGBA格式的图像并返回它@param无花果Matplotlib图@return Python成像库(公益诉讼)形象”“#把图象素映射成numpy数组缓冲区= fig2data(图)w h d =缓冲区。形状返回图像。frombytes("RGBA", (w,h), buf)。tostring())

def fig2data ( fig ): """ @brief Convert a Matplotlib figure to a 4D numpy array with RGBA channels and return it @param fig a matplotlib figure @return a numpy 3D array of RGBA values """ # draw the renderer fig.canvas.draw ( )

“@brief将一个Matplotlib图形转换为一个带有RGBA通道的4D numpy数组,并将其返回@param图一个Matplotlib图@返回一个RGBA值的numpy 3D数组”“”#绘制渲染器图。画()

# Get the RGBA buffer from the figure
w,h = fig.canvas.get_width_height()
buf = np.fromstring ( fig.canvas.tostring_argb(), dtype=np.uint8 )
buf.shape = ( w, h, 4 )

# canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode
buf = np.roll ( buf, 3, axis = 2 )
return buf

def rgba2rgb(img, c=(0, 0, 0), path='foo.jpg', is_already_saved=False, if_load=True): if not is_already_saved: background = Image.new("RGB", img.size, c) background.paste(img, mask=img.split()[3]) # 3 is the alpha channel

def rgba2rgb(img, c=(0,0,0), path='foo.jpg', is_already_save =False, if_load=True):如果不是is_already_saved: background = Image。新(RGB,img。大小,c)背景。粘贴(img, mask=img.split()[3]) # 3是alpha通道。

    background.save(path, 'JPEG', quality=100)   
    is_already_saved = True
if if_load:
    if is_already_saved:
        im = Image.open(path)
        return np.array(im)
    else:
        raise ValueError('No image to load.')

#1


86  

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.

这里有一个简单得多的版本——不确定它的性能如何。基于我在构建RGBA -> JPG + BG支持sorl缩略图时发现的一些django片段。

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%

结果@80%

使用PIL将RGBA PNG转换为RGB。

Result @ 50%
使用PIL将RGBA PNG转换为RGB。

结果@ 50%

#2


20  

By using Image.alpha_composite, the solution by Yuji 'Tomita' Tomita become simpler. This code can avoid a tuple index out of range error if png has no alpha channel.

通过使用图像。alpha_composite,由Yuji 'Tomita' Tomita的解决方案变得更简单。如果png没有alpha通道,这段代码可以避免一个tuple索引超出范围错误。

from PIL import Image

png = Image.open(img_path).convert('RGBA')
background = Image.new('RGBA', png.size, (255,255,255))

alpha_composite = Image.alpha_composite(background, png)
alpha_composite.save('foo.jpg', 'JPEG', quality=80)

#3


12  

The transparent parts mostly have RGBA value (0,0,0,0). Since the JPG has no transparency, the jpeg value is set to (0,0,0), which is black.

透明的部分主要有RGBA值(0,0,0,0)。由于JPG没有透明度,jpeg值被设置为(0,0,0),即黑色。

Around the circular icon, there are pixels with nonzero RGB values where A = 0. So they look transparent in the PNG, but funny-colored in the JPG.

在圆形图标周围,有非零RGB值的像素,其中A = 0。所以它们在PNG中看起来是透明的,但是在JPG中是很有趣的。

You can set all pixels where A == 0 to have R = G = B = 255 using numpy like this:

您可以设置所有像素,其中A == 0,让R = G = B = 255,使用numpy:

import Image
import numpy as np

FNAME = 'logo.png'
img = Image.open(FNAME).convert('RGBA')
x = np.array(img)
r, g, b, a = np.rollaxis(x, axis = -1)
r[a == 0] = 255
g[a == 0] = 255
b[a == 0] = 255
x = np.dstack([r, g, b, a])
img = Image.fromarray(x, 'RGBA')
img.save('/tmp/out.jpg')

使用PIL将RGBA PNG转换为RGB。


Note that the logo also has some semi-transparent pixels used to smooth the edges around the words and icon. Saving to jpeg ignores the semi-transparency, making the resultant jpeg look quite jagged.

注意,该徽标也有一些半透明像素用来平滑文字和图标的边缘。对jpeg的保存忽略了半透明性,使得最终的jpeg看起来相当参差。

A better quality result could be made using imagemagick's convert command:

使用imagemagick的convert命令可以获得更好的质量结果:

convert logo.png -background white -flatten /tmp/out.jpg

使用PIL将RGBA PNG转换为RGB。


To make a nicer quality blend using numpy, you could use alpha compositing:

为了更好地使用numpy,您可以使用alpha合成:

import Image
import numpy as np

def alpha_composite(src, dst):
    '''
    Return the alpha composite of src and dst.

    Parameters:
    src -- PIL RGBA Image object
    dst -- PIL RGBA Image object

    The algorithm comes from http://en.wikipedia.org/wiki/Alpha_compositing
    '''
    # http://*.com/a/3375291/190597
    # http://*.com/a/9166671/190597
    src = np.asarray(src)
    dst = np.asarray(dst)
    out = np.empty(src.shape, dtype = 'float')
    alpha = np.index_exp[:, :, 3:]
    rgb = np.index_exp[:, :, :3]
    src_a = src[alpha]/255.0
    dst_a = dst[alpha]/255.0
    out[alpha] = src_a+dst_a*(1-src_a)
    old_setting = np.seterr(invalid = 'ignore')
    out[rgb] = (src[rgb]*src_a + dst[rgb]*dst_a*(1-src_a))/out[alpha]
    np.seterr(**old_setting)    
    out[alpha] *= 255
    np.clip(out,0,255)
    # astype('uint8') maps np.nan (and np.inf) to 0
    out = out.astype('uint8')
    out = Image.fromarray(out, 'RGBA')
    return out            

FNAME = 'logo.png'
img = Image.open(FNAME).convert('RGBA')
white = Image.new('RGBA', size = img.size, color = (255, 255, 255, 255))
img = alpha_composite(img, white)
img.save('/tmp/out.jpg')

使用PIL将RGBA PNG转换为RGB。

#4


4  

Here's a solution in pure PIL.

这是纯PIL的一个解决方案。

def blend_value(under, over, a):
    return (over*a + under*(255-a)) / 255

def blend_rgba(under, over):
    return tuple([blend_value(under[i], over[i], over[3]) for i in (0,1,2)] + [255])

white = (255, 255, 255, 255)

im = Image.open(object.logo.path)
p = im.load()
for y in range(im.size[1]):
    for x in range(im.size[0]):
        p[x,y] = blend_rgba(white, p[x,y])
im.save('/tmp/output.png')

#5


1  

It's not broken. It's doing exactly what you told it to; those pixels are black with full transparency. You will need to iterate across all pixels and convert ones with full transparency to white.

它没有破。它完全按照你说的做;这些像素是黑色的,完全透明。您需要遍历所有的像素,并将完全透明的转换为白色。

#6


0  

import Image

导入图片

def fig2img ( fig ): """ @brief Convert a Matplotlib figure to a PIL Image in RGBA format and return it @param fig a matplotlib figure @return a Python Imaging Library ( PIL ) image """ # put the figure pixmap into a numpy array buf = fig2data ( fig ) w, h, d = buf.shape return Image.frombytes( "RGBA", ( w ,h ), buf.tostring( ) )

def fig2img(图):“”“@brief Matplotlib图转换为公益诉讼RGBA格式的图像并返回它@param无花果Matplotlib图@return Python成像库(公益诉讼)形象”“#把图象素映射成numpy数组缓冲区= fig2data(图)w h d =缓冲区。形状返回图像。frombytes("RGBA", (w,h), buf)。tostring())

def fig2data ( fig ): """ @brief Convert a Matplotlib figure to a 4D numpy array with RGBA channels and return it @param fig a matplotlib figure @return a numpy 3D array of RGBA values """ # draw the renderer fig.canvas.draw ( )

“@brief将一个Matplotlib图形转换为一个带有RGBA通道的4D numpy数组,并将其返回@param图一个Matplotlib图@返回一个RGBA值的numpy 3D数组”“”#绘制渲染器图。画()

# Get the RGBA buffer from the figure
w,h = fig.canvas.get_width_height()
buf = np.fromstring ( fig.canvas.tostring_argb(), dtype=np.uint8 )
buf.shape = ( w, h, 4 )

# canvas.tostring_argb give pixmap in ARGB mode. Roll the ALPHA channel to have it in RGBA mode
buf = np.roll ( buf, 3, axis = 2 )
return buf

def rgba2rgb(img, c=(0, 0, 0), path='foo.jpg', is_already_saved=False, if_load=True): if not is_already_saved: background = Image.new("RGB", img.size, c) background.paste(img, mask=img.split()[3]) # 3 is the alpha channel

def rgba2rgb(img, c=(0,0,0), path='foo.jpg', is_already_save =False, if_load=True):如果不是is_already_saved: background = Image。新(RGB,img。大小,c)背景。粘贴(img, mask=img.split()[3]) # 3是alpha通道。

    background.save(path, 'JPEG', quality=100)   
    is_already_saved = True
if if_load:
    if is_already_saved:
        im = Image.open(path)
        return np.array(im)
    else:
        raise ValueError('No image to load.')