使用 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 函数是最快的解决方案,但留下了丑陋的边框,因为它不处理半透明地区。
纯 PIL 和 numpy 合成解决方案都给出了很好的结果,但 alpha_composite_with_color
比 pure_pil_alpha_to_color
(79.6 毫秒) 快得多 (8.93 毫秒)。 如果 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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
这是一个更简单的版本 - 不确定它的性能如何。很大程度上基于我在构建 RGBA -> 时发现的一些 django 片段JPG + BG 支持 sorl 缩略图。
结果@80%
结果@50%
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.Result @80%
Result @ 50%
通过使用
Image.alpha_composite
,Yuji 'Tomita' Tomita 的解决方案变得更简单。如果 png 没有 Alpha 通道,此代码可以避免元组索引超出范围错误。By using
Image.alpha_composite
, the solution by Yuji 'Tomita' Tomita become simpler. This code can avoid atuple index out of range
error if png has no alpha channel.透明部分大多具有 RGBA 值(0,0,0,0)。由于JPG没有透明度,因此jpeg值设置为(0,0,0),即黑色。
圆形图标周围有一些具有非零 RGB 值的像素,其中 A = 0。因此它们在 PNG 中看起来是透明的,但在 JPG 中看起来颜色很有趣。
您可以使用 numpy 将 A == 0 处的所有像素设置为 R = G = B = 255,如下所示:
请注意,徽标还有一些半透明像素,用于平滑文字和图标周围的边缘。保存为 jpeg 会忽略半透明,使生成的 jpeg 看起来相当锯齿状。
使用 imagemagick 的
convert
命令可以得到更好的质量结果:">
alpha 合成:
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.
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.
You can set all pixels where A == 0 to have R = G = B = 255 using numpy like this:
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.
A better quality result could be made using imagemagick's
convert
command:To make a nicer quality blend using numpy, you could use alpha compositing:
这是纯 PIL 的解决方案。
Here's a solution in pure PIL.
它没有坏。它完全按照你的指示去做;这些像素是黑色且完全透明。您将需要迭代所有像素并将完全透明的像素转换为白色。
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.
已经给出了最好的答案:
但在看到这些解决方案之前,我自己是这样做的。我分享它只是因为它是最容易理解其工作原理的。 (每个像素如何混合)
我们所做的就是在白色(alpha = 0)和现有像素之间进行插值。 (alpha=255)
这两种解决方案创建的图像与您使用“画图”重新保存图像时获得的图像完全相同。 (您可以使用
(data1==data2).all()
进行检查)lerp()
函数中需要round()
来准确地说。我将像素保存到浮点数组中,以查看与重新保存的图像中的像素相比得到的结果,并了解像素应如何舍入。 (即 180 vs 180.3 和 214 vs 213.7)四舍五入到最接近的值是有意义的。现在我只需要知道如何准确地调整 Paint 的大小。
The best answer has been given already:
But here's how i did it myself before seeing these solutions. I'm sharing it just since it's the simplest to understand how it works. (how each pixel is blended)
All we're doing is interpolating between white (alpha=0) and the existing pixel. (alpha=255)
Both solutions create the exact same image you'd get if you re-saved an image with Paint. (you can check with
(data1==data2).all()
) Theround()
is needed in thelerp()
function to be exact. I saved the pixels to a float array to see what we get compared to the pixels in a re-saved image and saw how pixels should be rounded. (i.e. 180 vs 180.3 and 214 vs 213.7) And it makes sense to round to be what it's nearest to.Now i just need to know how to resize exactly how Paint resizes.
基于上面的示例:
它接收 RGBA 图像并返回将 alpha 通道转换为白色的 RGB 图像。
Based on above examples:
It takes in an RGBA image and returns an RGB image with alpha channel converted to white color.