Estou usando o PIL para converter uma imagem PNG transparente carregada com o Django em um arquivo JPG. A saída parece quebrada.
Arquivo fonte
Código
Image.open(object.logo.path).save('/tmp/output.jpg', 'JPEG')
ou
Image.open(object.logo.path).convert('RGB').save('/tmp/output.png')
Resultado
De ambas as formas, a imagem resultante tem a seguinte aparência:
Existe uma maneira de corrigir isso? Eu gostaria de ter um fundo branco onde o fundo transparente costumava ser.
Solução
Graças às ótimas respostas, eu vim com a seguinte coleção de funções:
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
atuação
A função simples de não composição alpha_to_color
é a solução mais rápida, mas deixa para trás bordas feias porque não lida com áreas semitransparentes.
Tanto o PIL puro quanto as soluções de composição numpy fornecem ótimos resultados, mas alpha_composite_with_color
são muito mais rápidos (8,93 ms) do que pure_pil_alpha_to_color
(79,6 ms).Se numpy estiver disponível em seu sistema, esse é o caminho a percorrer. (Atualização: a nova versão PIL pura é a mais rápida de todas as soluções 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()
pode ser removidopure_pil_alpha_to_color_v2
sem alterar o resultado. (Depois de alterar as instâncias subsequentes deim
paraimage
, é claro.)Respostas:
Esta é uma versão muito mais simples - não tenho certeza de como é o desempenho. Muito baseado em algum trecho de django que encontrei ao construir
RGBA -> JPG + BG
suporte para miniaturas sorl.Resultado a 80%
Resultado @ 50%
fonte
background = Image.new("RGB", png.size, (255, 255, 255))
.paste
fazer uma mistura adequada.load
método é necessário para osplit
método. E é incrível ouvir que é realmente rápido / e / simples!tuple index out of range
. Corrigi isso seguindo outra pergunta ( stackoverflow.com/questions/1962795/… ). Tive que converter o PNG para RGBA primeiro e depois cortá-lo:alpha = img.split()[-1]
depois usei isso na máscara de fundo.Com a utilização
Image.alpha_composite
, a solução de Yuji 'Tomita' Tomita torna-se mais simples. Este código pode evitar umtuple index out of range
erro se png não tiver canal alfa.fonte
.convert("RGB")
antes de salvá-loAs partes transparentes geralmente possuem valor RGBA (0,0,0,0). Como o JPG não tem transparência, o valor jpeg é definido como (0,0,0), que é preto.
Ao redor do ícone circular, há pixels com valores RGB diferentes de zero onde A = 0. Portanto, eles parecem transparentes no PNG, mas com cores engraçadas no JPG.
Você pode definir todos os pixels onde A == 0 para ter R = G = B = 255 usando numpy como este:
Observe que o logotipo também tem alguns pixels semitransparentes usados para suavizar as bordas ao redor das palavras e do ícone. Salvar em jpeg ignora a semitransparência, fazendo com que o jpeg resultante pareça bastante irregular.
Um resultado de melhor qualidade poderia ser obtido usando o
convert
comando imagemagick :Para fazer uma mistura de qualidade melhor usando numpy, você pode usar a composição alfa :
fonte
Aqui está uma solução em PIL puro.
fonte
Não está quebrado. Está fazendo exatamente o que você mandou; esses pixels são pretos com total transparência. Você precisará iterar em todos os pixels e converter aqueles com transparência total em branco.
fonte
fonte
importar imagem
def fig2img (fig): "" "@brief Converter uma figura Matplotlib em uma imagem PIL no formato RGBA e retorná-la @param fig uma figura matplotlib @return uma imagem Python Imaging Library (PIL)" "" # coloque o pixmap da figura em uma matriz numpy buf = fig2data (fig) w, h, d = buf.shape return Image.frombytes ("RGBA", (w, h), buf.tostring ())
def fig2data (fig): "" "@brief Converte uma figura Matplotlib em um array 4D numpy com canais RGBA e retorna @param fig uma figura matplotlib @return um array 3D numpy de valores RGBA" "" # desenha o renderizador fig. canvas.draw ()
def rgba2rgb (img, c = (0, 0, 0), path = 'foo.jpg', is_already_saved = False, if_load = True): se não is_already_saved: background = Image.new ("RGB", img.size, c) background.paste (img, mask = img.split () [3]) # 3 é o canal alfa
fonte