Python 3.5 + Pillow 是否不支持 RGBA PNG?

Are RGBA PNGs unsupported in Python 3.5 + Pillow?

* 编辑 * 不,我不是要转换 .BMP 只需简单地加载 PNG 并将其呈现在 tk Canvas 中(透明位实际上是透明的。)

目标: 加载具有透明度的 PNG 文件并使用 Python3 在 tkinter Canvas 中渲染它们(并使用 Pillow 支持 PIL)

问题: 当加载为 RGBA 时,它们无法在 canvas 中呈现。将它们转换为 RGB,它们会渲染,但当然没有透明度。

环境: Mac OSX, Python 3.5 安装并验证 Pillow 在 3.5 路径中

备注: CuriosityRover.png 确实是一个 RGBA,打印语句确认,并使用显示灰色背景的 finder 和 Photoshop 保存透明网页进行验证。

我试过像这样只提取 alpha 层:

 alpha = img.convert('RGBA').split()[-1]

调用 alpha.show() 确实在预览 alpha 层中显示了我,但它无法在 canvas 小部件中呈现。

我还尝试使用 ImageOps.invert(alpha) 反转 alpha 层 (通过在应用反转后调用 .show() 进行验证,我在预览中看到反向 alpha 层。)

我试过像这样创建矩形透明区域: transparent_area = (0, 0, 300, 300) 掩码 = Image.new('L', curosity.size, 颜色=255) 绘制= ImageDraw.Draw(面具) draw.rectangle(transparent_area, 填充=0) img.putalpha(面具)

并且 可以创建一个透明的矩形区域,所以看起来 canvas.create_image 可以很好地处理透明度,但不知何故我无法创建透明度数据正确的 PhotoImage。

我一天中打开了很多堆栈溢出选项卡,我很尴尬,我无法解决这个问题。

这似乎是世界上最简单的事情。我做错了什么?

import tkinter as tk
from PIL import Image, ImageTk

img = Image.open("./Assets/CuriosityRover.png")
img2 = Image.open("./Assets/CuriosityRover.png").convert('RGB')

img.show()  # Shows the image just fine in preview
print(img.format, img.size, img.mode)

root = tk.Tk()
photo = ImageTk.PhotoImage(img)  # img fails render, img2 works but no alpha

canvas = tk.Canvas(root, width=600, height=600, bg="black")

canvas.create_image((300, 300), image=photo)
canvas.grid(row=0, column=0)

root.mainloop()

哇哦!我解决了这个问题。

TL;DR - 是的 RGBA* PNGS 在 Pillow/Tkinker 中 不受支持 - 但下面有一个变通方法并强制 Alpha 通道的值仅为 0 或 255

*通道不能有除 0 或 255 以外的任何值,如果有则不会绘制整个图像(即使是 alpha 通道中具有 255 的像素。)

更长的版本: 它正在窃听我的废话。原来我的问题是 photoshop 保存了 8 位信息的 Alpha 通道,所以我的测试图像在我肉眼看不到的范围内有微妙的透明度。当我查看图像时,它们看起来不是不透明就是透明。

但是通过比较矩形透明度测试用例成功后的实际字节数,我可以看到 Image 只需要 0 表示透明或 255 表示不透明(因为 Tkinter 正在动态布局 GUI,它不需要知道在部分透明的情况下下面要混合的像素的颜色是什么。)

所以,为了解决这个问题,我现在 运行 我的图像通过我在下面创建的这个辅助函数来翻转进入 alpha 通道的 8 位信息,并强制它们为 0 或 255。我有点武断地选择了在50及以下的情况下我认为它是透明的。

希望其他人在不得不从头开始解决这个问题之前看到这一点。

# Fixes the issue when trying to render RBGAs that have 8bits of information in the alpha channel
# Turns your image into 8 bits on RGB and then 1 bit on the A channel
# This will render correctly
# See the example below for how to use

from PIL import Image


def flattenAlpha(img):
    alpha = img.split()[-1]  # Pull off the alpha layer
    ab = alpha.tobytes()  # Original 8-bit alpha

    checked = []  # Create a new array to store the cleaned up alpha layer bytes

    # Walk through all pixels and set them either to 0 for transparent or 255 for opaque fancy pants
    transparent = 50  # change to suit your tolerance for what is and is not transparent

    p = 0
    for pixel in range(0, len(ab)):
        if ab[pixel] < transparent:
            checked.append(0)  # Transparent
        else:
            checked.append(255)  # Opaque
        p += 1

    mask = Image.frombytes('L', img.size, bytes(checked))

    img.putalpha(mask)

    return img

# Run this as a test case.
# Assumes that you have a PNG named "CuriosityRover.png"
# that is an RGBA with varying levels of Alpha in the
# subdirectory assets from your working directory

if __name__ == "__main__":
    from PIL import ImageTk
    import tkinter as tk

    img = Image.open("./Assets/CuriosityRover.png")

    img = flattenAlpha(img)
    root = tk.Tk()

    photo = ImageTk.PhotoImage(img)
    canvas = tk.Canvas(root, width=600, height=600, bg="red")

    canvas.create_image((300, 300), image=photo)
    canvas.grid(row=0, column=0)

    root.mainloop()