用PIL将RGBA PNG转换为RGB

我使用PIL将用Django上传的透明PNG图像转换为JPG文件。 输出看起来破碎。

源文件

透明的源文件

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

要么

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

结果

两种方式,最终的图像如下所示:

产生的文件

有没有办法来解决这个问题? 我想有透明背景曾经是白色的背景。


感谢伟大的答案,我想出了以下函数集合:

 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 

性能

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

纯PIL和numpy合成解决schemealpha_composite_with_color很好的效果,但alpha_composite_with_colorpure_pil_alpha_to_color (79.6 msec)要快得多(8.93毫秒)。 如果你的系统上有numpy的话,那就是要走了。 (更新:新的纯PIL版本是所有提到的解决scheme中最快的。)

 $ 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 

这是一个更简单的版本 – 不知道它是多么的高性能。 很重要的一些Django片段,我发现,同时build立RGBA -> JPG + BG支持sorl缩略图。

 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) 

结果@ 80%

在这里输入图像描述

结果@ 50%
在这里输入图像描述

通过使用Image.alpha_composite ,Yuji Tomita Tomita的解决scheme变得更简单。 如果png没有alpha通道,这个代码可以避免tuple index out of range错误。

 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) 

透明部分大多具有RGBA值(0,0,0,0)。 由于JPG没有透明度,所以jpeg值被设置为(0,0,0),这是黑色的。

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

你可以像这样使用numpy来设置所有像素,其中A == 0的R = G = B = 255。

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

在这里输入图像描述


请注意,徽标也有一些半透明的像素用来平滑文字和图标周围的边缘。 保存为jpeg会忽略半透明度,从而使得所得的jpeg看起来相当锯齿。

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

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

在这里输入图像描述


要使用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://stackoverflow.com/a/3375291/190597 # http://stackoverflow.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解决scheme。

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

它没有坏。 这正是你所说的; 那些像素是完全透明的黑色。 您需要遍历所有像素,并将具有完全透明度的像素转换为白色。