将8位(16色调色板)PNG转换为4位(16色调色板)的Java或Python方法?

4 投票
2 回答
6413 浏览
提问于 2025-04-17 02:58

我有一堆图片,数量太多,手动处理不现实。这些图片是16色的8位PNG格式,我需要把它们转换成16色的4位格式,而且它们都有相同的调色板。

我在谷歌上搜索合适的库,但对这个具体问题没有找到太多信息,所以我来这里希望能找到一些更有针对性的解决方案。

我尝试使用PIL,参考了我在这里找到的其他答案,但没有成功。

img = Image.open('DownArrow_focused.png')
img = img.point(lambda i: i * 16, "L")
img.save('DownArrow_focused.png', 'PNG')

但是这样做出来的是灰度图像,并不是我想要的。

PIL不行,我在尝试PyPNG。GIMP可以做到这一点,但我有几百张这样的图片需要批量处理。而且我需要批量转换这些图片,所以这不是一次性的工作。

如果有基于Java的解决方案也可以,基本上任何我能在Linux/OSX机器上通过命令行运行的都可以。

2 个回答

0

在Python脚本中调用Netpbm程序,可以使用以下命令:

$ pngtopnm test.png | pnmquant 16 | pnmtopng > test16.png

$ file test16.png
test16.png: PNG image data, 700 x 303, 4-bit colormap, non-interlaced

而GIMP显示test16.png的颜色空间为索引颜色(16种颜色),我想这就是你想要的结果。

这不是一个纯粹的Python解决方案,因为PIL也不是纯Python,它还依赖于一些共享库。我认为你无法避免依赖某些外部图像软件。

1

在PNG格式中,调色板总是以RGB8的方式存储,也就是每个颜色索引占用3个字节(每个字节代表一种颜色)。调色板的条目数量可以是任意的,最多可以有256个。如果你现在有一张8位的图片,使用了16种颜色的调色板(总共16个条目),那么你不需要改变调色板,只需要重新打包像素字节(每个字节包含两个索引)。如果是这样的话,我觉得你可以用PNGJ这个工具来实现,下面是一些代码(未经测试):

public static void reencode(String orig, String dest) {
    PngReader png1 = FileHelper.createPngReader(new File(orig));
    ImageInfo pnginfo1 = png1.imgInfo;
    ImageInfo pnginfo2 = new ImageInfo(pnginfo1.cols, pnginfo1.rows, 4, false,false,true);  
    PngWriter png2 = FileHelper.createPngWriter(new File(dest), pnginfo2, false);
    png2.copyChunksFirst(png1, ChunksToWrite.COPY_ALL);
    ImageLine l2 = new ImageLine(pnginfo2);
    for (int row = 0; row < pnginfo1.rows; row++) {
        ImageLine l1 = png1.readRow(row);
        l2.tf_pack(l1.scanline, false);
        l2.setRown(row);
        png2.writeRow(l2);
    }
    png1.end();
    png2.copyChunksLast(png1, ChunksToWrite.COPY_ALL);
    png2.end();
    System.out.println("Done");
}

另外,如果你现在的调色板有16种“使用过”的颜色(但它的长度更长,因为还包括了一些未使用的颜色),那么你就需要做一些工作,修改调色板的部分(不过这也是可以做到的)。

撰写回答