Używam PIL do konwersji przezroczystego obrazu PNG przesłanego za pomocą Django do pliku JPG. Wynik wygląda na uszkodzony.
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 obu przypadkach wynikowy obraz wygląda następująco:
Czy jest sposób, aby to naprawić? Chciałbym mieć białe tło tam, gdzie kiedyś było przezroczyste.
Rozwiązanie
Dzięki świetnym odpowiedziom opracowałem następujący zbiór 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
Występ
Prosta alpha_to_color
funkcja niekomponująca jest najszybszym rozwiązaniem, ale pozostawia brzydkie granice, ponieważ nie obsługuje obszarów półprzezroczystych.
Zarówno czysty PIL, jak i rozwiązanie numpy compositing dają świetne wyniki, ale alpha_composite_with_color
są znacznie szybsze (8,93 ms) niż pure_pil_alpha_to_color
(79,6 ms).Jeśli numpy jest dostępny w twoim systemie, to jest droga. (Aktualizacja: nowa czysta wersja PIL jest najszybszym 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
im = image.copy()
aby uzyskać nieco większą prędkość, można ją usunąćpure_pil_alpha_to_color_v2
bez zmiany wyniku. ( Oczywiście po zmianie kolejnych instancjiim
naimage
.)Odpowiedzi:
Oto wersja, która jest znacznie prostsza - nie jestem pewien, jak wydajna jest. W dużej mierze oparte na fragmencie django, który znalazłem podczas tworzenia
RGBA -> JPG + BG
obsługi miniatur 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 przy 80%
Wynik @ 50%
źródło
background = Image.new("RGB", png.size, (255, 255, 255))
.paste
zrobić odpowiednią mieszankę.load
metoda jest wymagana dla tejsplit
metody. I wspaniale jest słyszeć, że jest naprawdę szybki / i / prosty!tuple index out of range
. Naprawiłem to, wykonując inne pytanie ( stackoverflow.com/questions/1962795/ ... ). Musiałem najpierw przekonwertować PNG na RGBA, a następnie go pokroić:alpha = img.split()[-1]
następnie użyć tego na masce tła.Używając
Image.alpha_composite
, rozwiązanie autorstwa Yuji „Tomita” Tomita stało się prostsze. Ten kod pozwala uniknąćtuple index out of range
błędu, 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)
źródło
.convert("RGB")
przed zapisaniemCzęści przezroczyste mają przeważnie wartość RGBA (0,0,0,0). Ponieważ JPG nie ma przezroczystości, wartość jpeg jest ustawiona na (0,0,0), czyli czarny.
Wokół okrągłej ikony znajdują się piksele o niezerowych wartościach RGB, gdzie A = 0. Wyglądają więc na przezroczyste w PNG, ale mają zabawne kolory w JPG.
Możesz ustawić wszystkie piksele, w których A == 0, aby 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')
Zwróć uwagę, że logo zawiera również półprzezroczyste piksele używane do wygładzania krawędzi wokół słów i ikony. Zapisywanie do formatu jpeg ignoruje półprzezroczystość, przez co wynikowy plik jpeg wygląda na dość postrzępiony.
Lepszą jakość można uzyskać za pomocą
convert
polecenia imagemagick :Aby uzyskać ładniejszą mieszankę jakości za pomocą numpy, możesz użyć kompozycji alfa :
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')
źródło
Oto rozwiązanie w czystym 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')
źródło
To nie jest zepsute. Robi dokładnie to, co mu kazałeś; te piksele są czarne z pełną przezroczystością. Będziesz musiał powtórzyć wszystkie piksele i przekonwertować te z pełną przezroczystością na białe.
źródło
import numpy as np import PIL def convert_image(image_file): image = Image.open(image_file) # this could be a 4D array PNG (RGBA) original_width, original_height = image.size np_image = np.array(image) new_image = np.zeros((np_image.shape[0], np_image.shape[1], 3)) # create 3D array for each_channel in range(3): new_image[:,:,each_channel] = np_image[:,:,each_channel] # only copy first 3 channels. # flushing np_image = [] return new_image
źródło
importuj obraz
def fig2img (fig): "" "@brief Konwertuj figurę Matplotlib do obrazu PIL w formacie RGBA i zwróć ją @param fig a matplotlib figure @return a Python Imaging Library (PIL) obraz" "" # umieść obrazek w a numpy tablica buf = fig2data (fig) w, h, d = buf.shape return Image.frombytes ("RGBA", (w, h), buf.tostring ())
def fig2data (fig): "" "@brief Konwertuj figurę Matplotlib na tablicę numpy 4D z kanałami RGBA i zwracaj ją @param fig a matplotlib figure @return tablicę numpy 3D wartości RGBA" "" # narysuj renderer rys. canvas.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.new ("RGB", img.size, c) background.paste (img, mask = img.split () [3]) # 3 to kanał 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.')
źródło