2012-10-23 121 views
6

假设你想缩放一个透明图像,但还不知道背景的颜色,你将其复合到以后。不幸的是,PIL似乎包含了完全透明像素的颜色值,导致了不好的结果。有没有办法告诉PIL调整大小忽略完全透明的像素?PIL保存透明度和颜色的缩放图像?

import PIL.Image 

filename = "trans.png" # http://qrc-designer.com/stuff/trans.png 
size = (25,25) 

im = PIL.Image.open(filename) 
print im.mode # RGBA 

im = im.resize(size, PIL.Image.LINEAR) # the same with CUBIC, ANTIALIAS, transform 
# im.show() # does not use alpha 
im.save("resizelinear_"+filename) 


# PIL scaled image has dark border 

original image with (0,0,0,0) black but fully transparent background output image with black halo proper output scaled with gimp

与(0,0,0,0)(黑色但完全透明)背景(左)

输出图像与黑晕(中)的原始图像

正确的输出与gimp比例缩放(右)

编辑:它看起来像实现我正在寻找的,我将不得不修改resize函数本身的采样,使它会忽略具有完全透明度的像素。

edit2:我发现了一个非常丑陋的解决方案。它将全透明像素的颜色值设置为周围非完全透明像素的平均值,以便在调整大小时最大程度地降低完全透明像素颜色的影响。它的速度很慢,但如果没有其他解决方案,我会发布它。通过使用扩张操作来处理必要的像素可以使其更快。

EDIT3:预乘alpha是要走的路 - 看到马克的回答

+0

是你的文件时加载到图片什么模式?也许alpha通道已经被删除了? – Ber

+0

它说RGBA和缩放的图像在边界以外的GIMP中也看起来很好。 – Gonzo

+0

您是否尝试将缩放后的图像混合在其他物体上,如白色背景?缩放是否可以,那么黑色的边缘会消失。 – Ber

回答

4

看来,PIL不调整,这是必要的,以获得正确的结果,之前做的α预乘。幸运的是,用蛮力来做这件事很容易。然后,您必须做相反的调整后的结果。

def premultiply(im): 
    pixels = im.load() 
    for y in range(im.size[1]): 
     for x in range(im.size[0]): 
      r, g, b, a = pixels[x, y] 
      if a != 255: 
       r = r * a // 255 
       g = g * a // 255 
       b = b * a // 255 
       pixels[x, y] = (r, g, b, a) 

def unmultiply(im): 
    pixels = im.load() 
    for y in range(im.size[1]): 
     for x in range(im.size[0]): 
      r, g, b, a = pixels[x, y] 
      if a != 255 and a != 0: 
       r = 255 if r >= a else 255 * r // a 
       g = 255 if g >= a else 255 * g // a 
       b = 255 if b >= a else 255 * b // a 
       pixels[x, y] = (r, g, b, a) 

结果:result of premultiply, resize, unmultiply

+0

比我的解决方案好得多。我不知道信息的损失有多糟糕。应该可以用numpy数组很好地加速这些。 – Gonzo

+0

预乘alpha损失:http://www.quasimondo.com/archives/000665.php结论:最好只在最终输出时使用 – Gonzo

+1

@Phelix如果你想降低一些透明度,精度的损失只是一个问题原因。大多数人不这样做。 –

2

您可以重新取样每个单独的频段:通过避免像这样高透明度值

im.load() 
bands = im.split() 
bands = [b.resize(size, Image.LINEAR) for b in bands] 
im = Image.merge('RGBA', bands) 

编辑

也许(需要numpy)

import numpy as np 

# ... 

im.load() 
bands = list(im.split()) 
a = np.asarray(bands[-1]) 
a.flags.writeable = True 
a[a != 0] = 1 
bands[-1] = Image.fromarray(a) 
bands = [b.resize(size, Image.LINEAR) for b in bands] 
a = np.asarray(bands[-1]) 
a.flags.writeable = True 
a[a != 0] = 255 
bands[-1] = Image.fromarray(a) 
im = Image.merge('RGBA', bands) 
+0

仍然是同样的问题。 – Gonzo

+0

第二个建议删除所有透明度。但我想保持光滑的边界。 – Gonzo

+0

我在第二个版本中看到了一些透明效果,尽管它被制作成二进制蒙版。 –

0

也许你可以用你想要的颜色填充整个图像,并且只在alpha通道中创建形状?

+0

如果我理解正确,这会在边界上放宽抗锯齿。我想成为粘贴到背景图片上的图片,以保持平滑过渡。 – Gonzo

+0

AA可以在alpha通道中完成(除非它不在那里) – Ber

+0

这就是问题所在,我想尽可能地保留原始图像的抗锯齿效果 – Gonzo

0

抱歉回答自己,但这是我知道的唯一的工作解决方案。它将全透明像素的颜色值设置为周围非完全透明像素的平均值,以便在调整大小时最大程度地降低完全透明像素颜色的影响。有些特殊情况下不能达到正确的结果。

这是非常丑陋和缓慢。如果你能想出更好的东西,我会很乐意接受你的答案。

# might be possible to speed this up by only processing necessary pixels 
# using scipy dilate, numpy where 

import PIL.Image 

filename = "trans.png" # http://qrc-designer.com/stuff/trans.png 
size = (25,25) 

import numpy as np 

im = PIL.Image.open(filename) 

npImRgba = np.asarray(im, dtype=np.uint8) 
npImRgba2 = np.asarray(im, dtype=np.uint8) 
npImRgba2.flags.writeable = True 
lenY = npImRgba.shape[0] 
lenX = npImRgba.shape[1] 
for y in range(npImRgba.shape[0]): 
    for x in range(npImRgba.shape[1]): 
     if npImRgba[y, x, 3] != 0: # only change completely transparent pixels 
      continue   
     colSum = np.zeros((3), dtype=np.uint16) 
     i = 0 
     for oy in [-1, 0, 1]: 
      for ox in [-1, 0, 1]: 
       if not oy and not ox: 
        continue 
       iy = y + oy 
       if iy < 0: 
        continue 
       if iy >= lenY: 
        continue 
       ix = x + ox 
       if ix < 0: 
        continue 
       if ix >= lenX: 
        continue 
       col = npImRgba[iy, ix] 
       if not col[3]: 
        continue 
       colSum += col[:3] 
       i += 1 
     npImRgba2[y, x, :3] = colSum/i 

im = PIL.Image.fromarray(npImRgba2) 
im = im.transform(size, PIL.Image.EXTENT, (0,0) + im.size, PIL.Image.LINEAR) 
im.save("slime_"+filename) 

结果: enter image description here