如何在Python/Pillow中减少PNG图像的调色板,使其仅包含实际使用的颜色?

9

在处理具有透明度的先前优化过的索引颜色PNG图像(这里提供了一些背景信息,因为此问题涉及相同的图像文件)后,使用以下代码,PLTE块似乎会扩展更多颜色而不是实际使用的颜色。

我目前的代码:

#!/usr/bin/env/python3
import os

from PIL import Image


source_file = os.path.expanduser("~/Desktop/prob.png")
dest_file = os.path.expanduser("~/Desktop/processed_img.png")

img = Image.open(source_file)

# Convert all colors in the palette to grayscale and save the new palette
pal = img.getpalette()
for i in range(len(pal) // 3):
    # Using ITU-R 601-2 luma transform
    g = (pal[3*i] * 299 + pal[3*i+1] * 587 + pal[3*i+2] * 114) // 1000
    pal[3*i: 3*i+3] = [g, g, g]

img.putpalette(pal)

try:
    img.save(dest_file, optimize=True, format="PNG")
except IOError:
    ImageFile.MAXBLOCK = img.size[0] * img.size[1]
    img.save(dest_file, optimize=True, format="PNG")

使用 pngcheck 工具,我得到了原始文件的小型16色调色板:

$ pngcheck -tc7pv ~/Desktop/prob.png
File: /Users/victor/Desktop/prob.png (12562 bytes)
  chunk IHDR at offset 0x0000c, length 13
    825 x 825 image, 8-bit palette, non-interlaced
  chunk PLTE at offset 0x00025, length 48: 16 palette entries
     0:  (  0,  0,  0) = (0x00,0x00,0x00)
     1:  (230,230,230) = (0xe6,0xe6,0xe6)
     2:  (215,215,215) = (0xd7,0xd7,0xd7)
     3:  (199,199,199) = (0xc7,0xc7,0xc7)
     4:  (175,175,175) = (0xaf,0xaf,0xaf)
     5:  (143,143,143) = (0x8f,0x8f,0x8f)
     6:  (111,111,111) = (0x6f,0x6f,0x6f)
     7:  ( 79, 79, 79) = (0x4f,0x4f,0x4f)
     8:  ( 22, 22, 22) = (0x16,0x16,0x16)
     9:  (  0,  0,  0) = (0x00,0x00,0x00)
    10:  ( 47, 47, 47) = (0x2f,0x2f,0x2f)
    11:  (254,254,254) = (0xfe,0xfe,0xfe)
    12:  (115, 89,  0) = (0x73,0x59,0x00)
    13:  (225,176,  0) = (0xe1,0xb0,0x00)
    14:  (255,211,  0) = (0xff,0xd3,0x00)
    15:  (254,204,  0) = (0xfe,0xcc,0x00)
  chunk tRNS at offset 0x00061, length 1: 1 transparency entry
    0:    0 = 0x00
  chunk IDAT at offset 0x0006e, length 12432
    zlib: deflated, 32K window, maximum compression
  chunk IEND at offset 0x0310a, length 0
No errors detected in /Users/victor/Desktop/prob.png (5 chunks, 98.2% compression).

在使用上述代码示例处理图像后,pngcheck会显示一个更大的PLTE块,其中包含许多(可能未使用)颜色值:

$ pngcheck -tc7pv ~/Desktop/processed_img.png
File: /Users/victor/Desktop/processed_img.png (14680 bytes)
  chunk IHDR at offset 0x0000c, length 13
    825 x 825 image, 8-bit palette, non-interlaced
  chunk PLTE at offset 0x00025, length 768: 256 palette entries
      0:  (  0,  0,  0) = (0x00,0x00,0x00)
      1:  (230,230,230) = (0xe6,0xe6,0xe6)
      2:  (215,215,215) = (0xd7,0xd7,0xd7)
      3:  (199,199,199) = (0xc7,0xc7,0xc7)
      4:  (175,175,175) = (0xaf,0xaf,0xaf)
      5:  (143,143,143) = (0x8f,0x8f,0x8f)
      6:  (111,111,111) = (0x6f,0x6f,0x6f)
      7:  ( 79, 79, 79) = (0x4f,0x4f,0x4f)
      8:  ( 22, 22, 22) = (0x16,0x16,0x16)
      9:  (  0,  0,  0) = (0x00,0x00,0x00)
     10:  ( 47, 47, 47) = (0x2f,0x2f,0x2f)
     11:  (254,254,254) = (0xfe,0xfe,0xfe)
     12:  ( 86, 86, 86) = (0x56,0x56,0x56)
     13:  (170,170,170) = (0xaa,0xaa,0xaa)
     14:  (200,200,200) = (0xc8,0xc8,0xc8)
     15:  (195,195,195) = (0xc3,0xc3,0xc3)
     16:  ( 16, 16, 16) = (0x10,0x10,0x10)
     17:  ( 17, 17, 17) = (0x11,0x11,0x11)
     18:  ( 18, 18, 18) = (0x12,0x12,0x12)
     19:  ( 19, 19, 19) = (0x13,0x13,0x13)
     20:  ( 20, 20, 20) = (0x14,0x14,0x14)

(...)——并且它会一直列出所有值,直到255:
    254:  (254,254,254) = (0xfe,0xfe,0xfe)
    255:  (255,255,255) = (0xff,0xff,0xff)
  chunk tRNS at offset 0x00331, length 1: 1 transparency entry
    0:    0 = 0x00
  chunk IDAT at offset 0x0033e, length 13830
    zlib: deflated, 32K window, maximum compression
  chunk IEND at offset 0x03950, length 0
No errors detected in /Users/victor/Desktop/processed_img.png (5 chunks, 97.8% compression).

这种行为在Pillow上是否正常?有没有办法保存一个类似于原始文件的较短的PLTE块(我正在尝试优化文件大小)?

如果Pillow做不到,是否有其他简单的方法可以实现?最好使用纯Python,但也可以使用numpy或其他一些纯Python包,如PyPNGPurePNG,如果有帮助。


我猜在Python中读取生成的文件并拼接一个新的、更短的PLTE块不会太难... #UglySolutionsRUs :-) - Mark Setchell
我希望能有某种更或少准备就绪的解决方案,由某个对文件格式有更好了解的人构建。 - Victor Domingos
实际上,当使用较少的颜色并将其传递给像Image.convert()这样的方法时,我不会指望Pillow生成256色调色板。 - Victor Domingos
我用 Perl 写了一个非常基础的 PNG 块提取器,很容易转换成 Python。我认为你只需要复制除 PLTE 以外的所有内容,并生成带有校验和和长度的 PLTE 块即可... https://dev59.com/Jabja4cB1Zd3GeqPg33c#47109369 - Mark Setchell
1个回答

0
我使用Pillow 9.2.0在bee png上运行了您的代码,输出的png调色板中颜色数量与原始图像相同。此外,Image.convert()有参数palettecolors可用于控制输出颜色。例如,要保持与输入图像相同数量的颜色:
from PIL import Image

img = Image.open('bee.png')
num_pixels = img.size[0]*img.size[1]
num_colors = len(img.getcolors(num_pixels))
# Max palette size = 256
if num_colors > 256:
    num_colors = 256

img = img.convert(mode='P', palette=1, colors=num_colors)
img.save('bee2.png', optimize=True, format="PNG")

如果您有兴趣减小/管理图像大小,我建议使用Image.save()中的quality参数压缩图像,或者利用其他工具(如ExifTool)消除不必要的元数据(这些可能对bee png的影响有限)。

有关Pillow中图像模式的更多信息,请参阅Mark Setchell的答案:PIL中'P'和'L'模式图像之间的区别是什么?

使用Pillow进行文件大小减小的答案:如何使用PIL减小图像文件大小


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接