Konwertuj RGBA PNG NA RGB za pomocą PIL

Używam PIL do konwersji przezroczystego obrazu PNG przesłanego za pomocą Django do pliku JPG. Wyjście wygląda na zepsute.

Plik źródłowy

przezroczysty plik źródłowy

Kod

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

Lub

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

Wynik

W obie strony, wynikowy obraz wygląda tak:

plik wynikowy

Czy można to jakoś naprawić? Chciałbym mieć białe tło tam, gdzie kiedyś było przezroczyste tło.

Rozwiązanie

Dzięki wspaniałym odpowiedziom, przybyłem up z następującym zbiorem funkcji:

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

Wydajność

Prosta funkcja non-compositing alpha_to_color jest najszybszym rozwiązaniem, ale pozostawia brzydkie granice, ponieważ nie obsługuje półprzezroczystych obszarów.

Zarówno czysty pil, jak i rozwiązania numpy compositing dają świetne wyniki, ale alpha_composite_with_color jest znacznie szybszy (8,93 msec) niż pure_pil_alpha_to_color (79,6 msec). jeśli numpy jest dostępny w Twoim systemie, to jest droga do zrobienia. (Aktualizacja: Nowa wersja pure PIL jest najszybsze ze wszystkich wymienionych rozwiązań.)

$ 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
Author: Danilo Bargen, 2012-02-06

6 answers

Oto wersja, która jest znacznie prostsza - Nie wiem, jak wydajna jest. Mocno oparty na jakimś fragmencie django, który znalazłem podczas budowania RGBA -> JPG + BG wsparcia dla miniaturek 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)

Wynik @80%

Tutaj wpisz opis obrazka

Wynik @ 50%
Tutaj wpisz opis obrazka

 90
Author: Yuji 'Tomita' Tomita,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-27 18:20:25

Za pomocą Image.alpha_composite, rozwiązanie autorstwa Yuji 'Tomita' Tomita staje się prostsze. Ten kod może uniknąć błędu tuple index out of range, jeśli png nie ma kanału alfa.

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)
 22
Author: shuuji3,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-03 19:08:07

Przezroczyste części najczęściej mają wartość RGBA (0,0,0,0). Ponieważ JPG nie ma przezroczystości, wartość jpeg jest ustawiona na (0,0,0), która jest czarna.

Wokół okrągłej ikony znajdują się piksele o niezerowych wartościach RGB, gdzie A = 0. Więc wyglądają przezroczyste w PNG, ale zabawne-kolorowe w JPG.

Możesz ustawić wszystkie piksele, gdzie A == 0, aby miały R = G = B = 255, używając numpy w następujący sposób:

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

Tutaj wpisz opis obrazka


Zauważ, że logo ma również półprzezroczyste piksele używane do wygładzania krawędzi wokół słów i ikony. Zapisywanie w FORMACIE jpeg ignoruje półprzezroczystość, dzięki czemu powstały plik JPEG wygląda na postrzępiony.

Lepszą jakość można uzyskać za pomocą polecenia imagemagick convert:

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

Tutaj wpisz opis obrazka


Aby uzyskać lepszą jakość mieszanki za pomocą numpy, możesz użyć Alpha compositing :

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

Tutaj wpisz opis obrazka

 12
Author: unutbu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-07 12:22:28

Oto rozwiązanie w czystej 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')
 4
Author: Mark Ransom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-06 22:17:43

Nie jest złamana. Robi dokładnie to, co mu kazałeś; te piksele są czarne z pełną przezroczystością. Będziesz musiał powtarzać wszystkie piksele i konwertować te z pełną przezroczystością na białe.

 1
Author: Ignacio Vazquez-Abrams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-06 20:02:35

Importuj Obraz

Def fig2img (fig ): """ @brief przekonwertuj figurę Matplotlib na obrazek PIL w formacie RGBA i zwróć go @param fig a matplotlib figure @ return a Python Imaging Library (PIL ) image """ # umieść obrazek w tablicy numpy buf = fig2data ( fig ) w, h , d = buf.kształt return Image.frombytes( "RGBA", (w ,h ), buf.tostring ())

Def fig2data (fig ): """ @ brief Konwertuj figurę Matplotlib na tablicę numpy 4D z kanałami RGBA i zwróć go @param fig a matplotlib figure @ return a numpy 3D array of rgba values """ # draw the renderer rys.płótno.draw ()

# 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.Nowa ("RGB", img.Rozmiar, c) tło.wklej(img, Maska=img.split()[3]) # 3 jest kanałem alfa

    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
Author: Thomas Chaton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-10 11:37:46