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
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:
这两种方法,得到的图像都是这样的:
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%
Result @ 50%
结果@ 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')
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
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')
#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%
Result @ 50%
结果@ 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')
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
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')
#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.')