如何在java中保存索引颜色PNG

如何在java中保存索引颜色PNG,java,graphics,png,bufferedimage,javax.imageio,Java,Graphics,Png,Bufferedimage,Javax.imageio,如何在java中将图像保存为java.awt.image.IndexColorModelPNG?我正在用ImageIO加载索引颜色png,用Catalino库操作它,不幸的是,它将颜色空间转换为java.awt.image.DirectColorModel 现在我想以与原始图像完全相同的格式保存结果。我尝试了以下代码片段 private static void testIndexedColor() throws IOException { FastBitmap input = n

如何在java中将图像保存为
java.awt.image.IndexColorModel
PNG?我正在用ImageIO加载索引颜色png,用Catalino库操作它,不幸的是,它将颜色空间转换为
java.awt.image.DirectColorModel

现在我想以与原始图像完全相同的格式保存结果。我尝试了以下代码片段

private static void testIndexedColor() throws IOException {
        FastBitmap input = new FastBitmap("test.png");
        BufferedImage bi = new BufferedImage(input.getWidth(), input.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
        bi.getGraphics().drawImage(input.toBufferedImage(), 0, 0, null);
        ImageIO.write(bi, "PNG", new File("test_result.png"));
}

但在结果中,白色背景中出现奇怪的浅灰色像素伪影,PPI降低。如何在没有质量损失和失真的情况下正确地转换回索引颜色模式?

假设我对Catalano框架的看法是正确的,您应该能够重新编写您的方法,如下所示:

private static void testIndexedColor() throws IOException {
    BufferedImage bi = ImageIO.read(new File("test.png"));
    FastBitmap input = new FastBitmap(bi);

    Graphics2D g = bi.createGraphics();
    try {
        g.drawImage(input.toBufferedImage(), 0, 0, null);
    }
    finally {
         g.dispose(); // Good practice ;-)
    }

    ImageIO.write(bi, "PNG", new File("test_result.png"));
}
至少你应该摆脱固定的调色板和工件

但是,这可能仍然会修改PPI(但不会影响像素)。甚至在某些情况下,图像也可能被写回非调色板PNG


更新:似乎
PNGImageWriter
(通过
PNGMetadata
)实际上将包含完美灰度的
IndexColorModel
重新写入默认灰度PNG。这通常是一个好主意,因为您可以通过不写入PLTE块来减小文件大小。您应该能够绕过这一点,通过将原始数据中的元数据与图像像素数据一起传递,以指示编写器保留
IndexColorModel
(即写入PLTE块):

这应该(作为奖励)也保持你的PPI不变


PS:对于生产代码,您还需要对上面的
读取器
编写器
进行
dispose()
,但为了保持重点,避免进一步讨论
try/finally
;-)

这里发生的是,您的
bi
buffereImage
是使用默认的
IndexColorModel
创建的(与256色“web安全”调色板的颜色相同)。您需要从
input
获取
IndexColorModel
或某种形式的调色板条目。如果不可能,最坏的情况是,您可以使用
ImageIO
再次读取输入文件以获得它(速度较慢),甚至可以使用某种形式的颜色减少(速度最慢)为修改后的图像重新创建“最佳”调色板。不确定PPI发生了什么(或者这是否重要)。PS:您所指的库是Catalano框架吗?同样值得一提的是,特定的图像是黑白的。当我在gimp模式下打开它时,它显示为索引颜色。但是,如果我用ImageIO将此图像加载到BuffereImage,并立即使用ImageIO将此相同的BuffereImage写入文件,则该模式现在是神秘的灰度模式。我甚至不确定灰度和索引颜色是否相互排斥,或者灰度图像是否可以使用调色板?为什么会发生这种转换?你介意分享PNG吗?在Java
IndexColorModel
中,始终使用sRGB颜色空间。但这并不能阻止颜色变成灰色。所以这实际上取决于你定义的“灰度”。当文件中没有与颜色数据匹配的
ColorModel
类时,或者如果这样的
ColorModel
不实用(速度慢),通常会发生转换(在Java ImageIO中,我不知道这个细节中的Gimp)。我的更新版本对您有帮助吗?我很高兴您添加了
dispose()
,但最终的尝试是没有必要的。
g.drawImage
几乎不可能抛出异常。@VGR在内存不足的情况下,这是必要的。如果您打算从OutOfMemoryErrors中“恢复”出来(实际上,您不能),那么您最好最后尝试一下每一行代码。当内存不足时,程序执行将有效地完成,唯一有用的操作是退出。@VGR首先有一个原因是
dispose()
方法存在。通常,在Java中,您会让GC处理这个问题。但由于可能涉及本机资源,因此需要显式的
dispose()
。这也是为什么您应该使用
try/finally
。这与恢复无关。它是关于释放您可能持有的任何资源(与stream的
close()
等相同)。我也不同意你其余的陈述,但我不会去那里…;-)我测试了这个,结果以灰度保存。我断言
buffereImage
ColorModel
IndexColorModel
read
drawImage
操作之后的一个实例。似乎是
write
方法转换了
ColorModel
。有可能绕过这个吗?
private static void testIndexedColor() throws IOException {
    File in = new File("test.png");
    File out new File("test_result.png");

    try (ImageInputStream input = ImageIO.createImageInputStream(in);
         ImageOutputStream output = ImageIO.createImageOutputStream(out)) {
        ImageReader reader = ImageIO.getImageReaders(input).next(); // Will fail if no reader
        reader.setInput(input);

        ImageWriter writer = ImageIO.getImageWriter(reader); // Will obtain a writer that understands the metadata from the reader
        writer.setOutput(output);  // Will fail if no writer

        // Now, the important part, we'll read the pixel AND metadata all in one go
        IIOImage image = reader.readAll(0, null); // PNGs only have a single image, so index 0 is safe

        // You can now access and modify the image data using:
        BufferedImage bi = (BufferedImage) image.getRenderedImage();
        FastBitmap fb = new FastBitmap(bi);

        // ...do stuff...

        Graphics2D g = bi.createGraphics();
        try {
            g.drawImage(fb.toBufferedImage(), 0, 0, null);
        }
        finally {
            g.dispose();
        }

        // Write pixel and metadata back
        writer.write(null, image, writer.getDefaultWriteParam());
    }
}