Python 使用PIL调整png图像的大小会丢失透明度

Python 使用PIL调整png图像的大小会丢失透明度,python,python-imaging-library,Python,Python Imaging Library,我使用此方法调整png图像的大小: 但该图像仍会失去透明度: import Image, numpy def resize(filename, img,height, width): if filename.endswith(".png"): img = img.convert('RGBA') premult = numpy.fromstring(img.tostring(), dtype=numpy.uint8) alphaLayer

我使用此方法调整png图像的大小:

但该图像仍会失去透明度:

import Image, numpy

def resize(filename, img,height, width):
    if filename.endswith(".png"):
        img = img.convert('RGBA')
        premult = numpy.fromstring(img.tostring(), dtype=numpy.uint8)
        alphaLayer = premult[3::4] / 255.0
        premult[::4] *= alphaLayer
        premult[1::4] *= alphaLayer
        premult[2::4] *= alphaLayer
        img = Image.fromstring("RGBA", img.size, premult.tostring())
    img = img.resize((height,width), Image.ANTIALIAS)
    return img


问题与链接的问题无关,相反,您必须修补PIL,使其正确读取
tRNS
PNG块。PIL对此区块采用单个值,但显示的此图像对调色板中的每个值都有透明度描述。处理完后,就可以简单地解决问题:将图像转换为
'LA'
模式并调整大小:

import sys
from PIL import Image

img = Image.open(sys.argv[1])
pal = img.getpalette()
width, height = img.size
actual_transp = img.info['actual_transparency'] # XXX This will fail.

result = Image.new('LA', img.size)

im = img.load()
res = result.load()
for x in range(width):
    for y in range(height):
        t = actual_transp[im[x, y]]
        color = pal[im[x, y]]
        res[x, y] = (color, t)

result.resize((64, 64), Image.ANTIALIAS).save(sys.argv[2])
所以我们从这个,到这个:


针对这种特定情况的PIL补丁实际上非常简单。打开你的
PIL/PngImagePlugin.py
,进入函数
chunk\u tRNS
,输入检查
im\u模式==“P”
if
语句,然后检查
i>=0
,然后添加行
self.im.info[“实际透明度”]=map(ord,s)

我已经解决了这个问题,不再需要来自mmgp的漂亮破解。 PIL现在将正确读取并应用透明度


这是一个更复杂的调整大小功能(保持透明度):它只允许使用新的宽度值、新的宽度和高度值或参考文件来获得新的大小。您也可以更改重采样方法:

## PngResizeTransparency.py
#
## Resize PNG image by keeping the transparency
## thanks to https://stackoverflow.com/users/1453719/nicolas-barbey
#
## Use:
##   - using a reference file to get new sizes:
#        PNG_ResizeKeepTransparency(SourceFile, ResizedFile, RefFile ='YourRefFile.png')
##   - using only the resized width:
#        PNG_ResizeKeepTransparency(SourceFile, ResizedFile, new_width)
##   - using resized width and hight:
#        PNG_ResizeKeepTransparency(SourceFile, ResizedFile, new_width, new_height)
##   - using resample mode: add param resample="NEAREST"/"BILINEAR"/"BICUBIC"/"ANTIALIAS"

from PIL import Image

def PNG_ResizeKeepTransparency(SourceFile, ResizedFile, new_width=0, new_height=0, resample="ANTIALIAS", RefFile =''):
    # needs PIL
    # Inputs:
    #   - SourceFile  = initial PNG file (including the path)
    #   - ResizedFile = resized PNG file (including the path)
    #   - new_width   = resized width in pixels; if you need % plz include it here: [your%] *initial width
    #   - new_height  = resized hight in pixels ; default = 0 = it will be calculated using new_width
    #   - resample = "NEAREST", "BILINEAR", "BICUBIC" and "ANTIALIAS"; default = "ANTIALIAS"
    #   - RefFile  = reference file to get the size for resize; default = ''

    img = Image.open(SourceFile) # open PNG image path and name
    img = img.convert("RGBA")    # convert to RGBA channels
    width, height = img.size     # get initial size

    # if there is a reference file to get the new size
    if RefFile != '':
        imgRef = Image.open(RefFile)
        new_width, new_height = imgRef.size
    else:
        # if we use only the new_width to resize in proportion the new_height
        # if you want % of resize please use it into new_width (?% * initial width)
        if new_height == 0:
            new_height = new_width*width/height

    # split image by channels (bands) and resize by channels
    img.load()
    bands = img.split()
    # resample mode
    if resample=="NEAREST":
        resample = Image.NEAREST
    else:
        if resample=="BILINEAR":
            resample = Image.BILINEAR
        else:
            if resample=="BICUBIC":
                resample = Image.BICUBIC
            else:
                if resample=="ANTIALIAS":
                    resample = Image.ANTIALIAS
    bands = [b.resize((new_width, new_height), resample) for b in bands]
    # merge the channels after individual resize
    img = Image.merge('RGBA', bands)
    # save the image
    img.save(ResizedFile)
    return

#######################################################

if __name__ == "__main__":
    sFile = './autumn-png-leaf.png'
    # resize using new width value (new height is calculated by keeping image aspect)
    PNG_ResizeKeepTransparency(sFile, sFile[:-4]+'_resized.png', 400)
    # resize using a reference file to get the new image dimension 
    PNG_ResizeKeepTransparency(sFile, sFile[:-4]+'_resized.png', RefFile = 'autumn-png-leaf_starry-night-van-gogh_fchollet_10.png')

可能是@MarkRansom的副本,但他的图像最初不是在RGBA中,它是一个苍白的图像,如果转换为RGBA,会使一切都出错。@mmgp,我明白了,谢谢你清理了它。我没有我通常用来评估这个位置的图像的工具,但是我想在PIL中打开它会立即显示出来。我认为这个特定的PNG与PIL完全不兼容。它有一个带有透明度的调色板,当PIL读取文件时,透明度会被丢弃。@MarkRansom确实,我也无法用PIL正确读取它。这:,是我能得到的最好的了。
ImagePalette
类已经有了一个与之相关联的
模式,只要PIL使用一个合适的
'RGBA'
模式,一切都会好得多。您的解决方案适用于单色图像的这种特殊情况,但有许多其他有效图像可能会失败。@MarkRansom但此透明度数据在
PLTE
区块中不可用,PIL在该区块中为png图像构建调色板。因此,如果调色板仅构建为
RGBA
,则它必须正确读取
tRNS
块,并可能将其组合到调色板中。因此,包含的“补丁”仅用于正确读取
tRNS
块,任何模式
P
下的png图像都可以使用此补丁。你认为哪种png图像会失败?你的回答假设调色板是灰度调色板,但事实并非如此。转换为
'RGBA'
而不是
'LA'
可以解决这个问题,但结果将不再是8位。@MarkRansom但答案的这一部分与问题无关,图像可以包含一个
RGB
调色板,然后从
LA
切换到
RGBA
就可以正常工作。你是说最新的流血枕头会使用它吗?我不知道你这里所说的“黑客”是什么意思,因为你在代码中也在做self.im\u info[“transparency\u palete”]=s
。如何正确地阅读区块是一种黑客行为?不,正如你已经注意到的,我也在使用这个想法。但是在我的实现中,我没有复制图像并在其上循环。加载时,透明度将按预期方式应用。解决方案的最底层是“黑客”。你说流血是什么意思?我指的是流血版本。你修补了
chunk\tRNS
和我一样,但仅仅是5小时前,这只是巧合吗?我并不是说这在任何方面都是革命性的,但我们可以看到这是多么可悲:人A发布了一个答案,人B应用了先前答案中的补丁加上ε修改,人B不好地谈论人A,因为人A没有做ε修改。最后,我没有“正确地”修补PIL,因为它涉及修改
\u imaging.c
(因此涉及重新编译),因为我所做的只是显示PIL读取png块不正确,称之为“hack”,我并没有说任何我认为不好的话。黑客真是太棒了。我用了你对块的知识,这不是巧合,我不会说我发现了这个缺陷。zuroc在枕头上发布了这个相关问题。我刚刚看了一下,很好地看了一下您的解决方案,并将其直接实现到枕头中,在那里它将被重新编译。MarkRansom还提到了解决方案中的一个小缺陷,即强制将图像转换为灰度或rgb以应用透明度。内部错误修复没有这个问题。这正是上面提到的正确的解决方案标记。