Estoy usando PIL para convertir una imagen PNG transparente cargada con Django en un archivo JPG. La salida parece rota.
Archivo fuente
Código
Image.open(object.logo.path).save('/tmp/output.jpg', 'JPEG')
o
Image.open(object.logo.path).convert('RGB').save('/tmp/output.png')
Resultado
En ambos sentidos, la imagen resultante se ve así:
¿Hay alguna forma de solucionar este problema? Me gustaría tener un fondo blanco donde solía estar el fondo transparente.
Solución
Gracias a las excelentes respuestas, se me ocurrió la siguiente colección de funciones:
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
Actuación
La función simple de no composición alpha_to_color
es la solución más rápida, pero deja atrás los bordes feos porque no maneja áreas semitransparentes.
Tanto el PIL puro como las soluciones de composición numpy dan excelentes resultados, pero alpha_composite_with_color
es mucho más rápido (8,93 mseg) que pure_pil_alpha_to_color
(79,6 mseg).Si numpy está disponible en su sistema, ese es el camino a seguir. (Actualización: la nueva versión pura de PIL es la más rápida de todas las soluciones mencionadas).
$ 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
im = image.copy()
se puede eliminarpure_pil_alpha_to_color_v2
sin cambiar el resultado. (Después de cambiar las instancias posteriores deim
aimage
, por supuesto.)Respuestas:
Aquí hay una versión que es mucho más simple, no estoy seguro de su rendimiento. Basada en gran medida en un fragmento de django que encontré mientras
RGBA -> JPG + BG
creaba soporte para miniaturas sorl.Resultado @ 80%
Resultado @ 50%
fuente
background = Image.new("RGB", png.size, (255, 255, 255))
.paste
una mezcla adecuada.load
método es necesario para elsplit
método. ¡Y es increíble escuchar que es realmente rápido y simple!tuple index out of range
. Solucioné esto siguiendo otra pregunta ( stackoverflow.com/questions/1962795/… ). Primero tuve que convertir el PNG a RGBA y luego cortarlo:alpha = img.split()[-1]
luego usarlo en la máscara de fondo.Al usar
Image.alpha_composite
, la solución de Yuji 'Tomita' Tomita se vuelve más simple. Este código puede evitar untuple index out of range
error si png no tiene canal alfa.fuente
.convert("RGB")
antes de guardarlaLa mayoría de las partes transparentes tienen un valor RGBA (0,0,0,0). Dado que el JPG no tiene transparencia, el valor de jpeg se establece en (0,0,0), que es negro.
Alrededor del icono circular, hay píxeles con valores RGB distintos de cero donde A = 0. Por lo tanto, se ven transparentes en PNG, pero de colores divertidos en JPG.
Puede configurar todos los píxeles donde A == 0 para tener R = G = B = 255 usando numpy como este:
Tenga en cuenta que el logotipo también tiene algunos píxeles semitransparentes que se utilizan para suavizar los bordes alrededor de las palabras y el icono. Guardar en jpeg ignora la semitransparencia, lo que hace que el jpeg resultante parezca bastante irregular.
Se podría obtener un resultado de mejor calidad usando el
convert
comando de imagemagick :Para hacer una mezcla de mejor calidad usando numpy, puede usar composición alfa :
fuente
Aquí hay una solución en PIL puro.
fuente
No esta roto Está haciendo exactamente lo que le dijiste; esos píxeles son negros con transparencia total. Deberá iterar en todos los píxeles y convertir los que tengan transparencia total en blanco.
fuente
fuente
importar imagen
def fig2img (fig): "" "@brief Convierta una figura Matplotlib en una imagen PIL en formato RGBA y devuélvala @param fig una figura matplotlib @return una imagen Python Imaging Library (PIL)" "" # coloque el mapa de píxeles de la figura en una matriz numpy buf = fig2data (fig) w, h, d = buf.shape return Image.frombytes ("RGBA", (w, h), buf.tostring ())
def fig2data (fig): "" "@brief Convierta una figura Matplotlib en una matriz 4D numpy con canales RGBA y devuélvala @param fig una figura matplotlib @return una matriz 3D numpy de valores RGBA" "" # dibuje el renderizador fig. canvas.draw ()
def rgba2rgb (img, c = (0, 0, 0), ruta = 'foo.jpg', is_already_saved = False, if_load = True): si no is_already_saved: background = Image.new ("RGB", img.size, c) background.paste (img, mask = img.split () [3]) # 3 es el canal alfa
fuente