Java ImageIO灰度PNG问题

时间:2015-07-09 08:53:51

标签: java png javax.imageio grayscale

我有一个灰度图像(实际上是“lena”),我想试验一下。我得到它是一个 512x512 PNG文件,有216种阴影的灰色。

当我用Java ImageIO阅读它时,会发生什么:

    String name = args[0];
    File fi = new File(name);
    BufferedImage img = ImageIO.read(fi);

我得到一个带仅154种颜色的BufferedImage!我只是意识到这一点,导致我处理过的图像显得粗糙,缺乏深黑色。

当我使用XnView将PNG转换为GIF时更加烦人,在这种情况下这是一个无损程序,用上面的代码读取GIF,我在BufferedImage中得到所有216种颜色。

是否有某种文档或描述,当我的PNG读取它时会发生什么?有设置来解决这个问题吗?我在最近的JDK1.8上做过这些实验。只是我对Java PNG支持的信任现在已经丢失,我稍后会使用彩色PNG。

2 个答案:

答案 0 :(得分:7)

欢迎来到Java隐藏色彩管理的“伟大”世界!

对于Java(至少是ImageIO),内部的所有内容都是sRGB,并且它隐含地进行颜色管理,这通常会对实际想要做的事情产生相反的效果。 对于灰度图像,至少使用大多数读取器的ImageIO,至少对于没有嵌入式ICC配置文件的灰度图像(我尚未测试其他图像),Java会自动“分配”具有WhitePoint = D50,Gamma = 1.0的ICC配置文件。我偶然发现了这一点。

然后,当你访问像素(我假设你使用img.getRGB()或类似的东西?)时,你实际上访问sRGB值(Windows上的Java默认颜色空间)。

结果是,当转换为sRGB时,其伽马值为~2.2(sRGB的伽玛实际上有点复杂,但总体上接近2.2),这有效地应用了伽马校正(1 / Gamma)= 2.2对于图像,(a)使图像显得“亮”,(b)由于256到256个离散值的伽马校正,你还可以有效地消除一些灰色阴影。

如果以不同方式访问BufferedImage的数据,也可以看到效果: a)访问个人资料:

ColorSpace colorSpace = img.getColorModel().getColorSpace();
if ( colorSpace instanceof ICC_ColorSpace ) {
    ICC_Profile profile = ((ICC_ColorSpace)colorSpace).getProfile();
    if ( profile instanceof ICC_ProfileGray ) {
        float gamma = ((ICC_ProfileGray)profile).getGamma();
        system.out.println("Gray Profile Gamma: "+gamma); // 1.0 !
    }
}

b)以不同方式访问某些像素值...

//access sRGB values (Colors translated from img's ICC profile to sRGB)
System.out.println( "pixel 0,0 value (sRGB): " + Integer.toHexString(img.getRGB(0,0)) ); // getRGB() actually means "getSRGB()"
//access raw raster data, this will give you the uncorrected gray value
//as it is in the image file
Raster raster = image.getRaster();
System.out.println( "pixel 0,0 value (RAW gray value): " + Integer.toHexString(raster.getSample(0,0,0)) );

如果您的像素(0,0)不是偶然的100%黑色或100%白色,您将看到sRGB值比灰度值“更高”,例如gray = d1 - > sRGB = ffeaeaea(阿尔法,红色,绿色,蓝色)。

从我的观点来看,它不仅会降低您的灰度级,还会使您的图像更亮(与使用1 /伽玛值为2.2的伽马校正相同)。如果没有嵌入式ICC配置文件的灰色图像的Java将灰色转换为具有R = G = B = grayValue的sRGB,或者将分配ICC灰度配置文件WhitePoint = D50,Gamma = 2.2(至少在Windows上),则更合乎逻辑。由于sRGB不完全是Gamma 2.2,后者仍会让你失去一些灰色调。

关于它为什么适用于GIF:GIF格式没有“灰度”或ICC配置文件的概念,因此您的图像是256色调色板图像(256色恰好是256色灰度)。在打开GIF时,Java假定RGB值是sRGB。

<强>解决方案: 根据您的实际使用情况,您的解决方案可能是您访问每个图像的像素(灰色= raster.getSample(x,y,0))的栅格数据并将其放入sRGB图像设置R = G = B =灰色。但是,可能会有更优雅的方式。

关于您对java或PNG的信任: 由于它具有隐含的颜色转换,我在很多方面都在努力研究java ImageIO。我们的想法是内置色彩管理,而开发人员不需要太多关于色彩管理的知识。只要您只使用sRGB(并且您的输入也是sRGB,或者没有颜色配置文件,因此可以合法地认为是sRGB),这在一定程度上有效。如果输入图像中有其他颜色空间(例如AdobeRGB),则会启动麻烦。灰色也是另一回事,尤其是ImageIO假设Gamma = 1.0的(异常)灰度轮廓。现在要了解ImageIO正在做什么,您不仅需要知道您在颜色管理中的ABC,还需要弄清楚java正在做什么。我没有在任何文档中找到此信息!结论:ImageIO确实可以认为是正确的。它往往不是你所期望的,你可能会深入挖掘,找出原因或改变行为,如果它不是你想要做的。

答案 1 :(得分:1)

不知何故,您已将图像从线性灰度(gamma = 1.0)转换为sRGB灰度(gamma = 1 / 2.2)。这可以通过GraphicsMagick来演示。从维基百科下载的Lenna.png开始,然后删除sRGB块以创建lena.png,然后

gm convert lena.png -colorspace gray -depth 8 -strip lena-gray.png

lena-gray.png有216种颜色

gm convert lena-gray.png -gamma 2.2 -depth 8 -strip lena-gray-gm22.png

lena-grey-gm22.png有154种颜色,看起来褪色或褪色。

我最近使用了libmang-1.6.17的graphicsmagick(版本1.4)测试版。

要计算我使用ImageMagick的颜色:

identify -verbose file.png | grep Colors

我用过

pngcheck -v file.png

验证Lenna.png是否包含IHDR,sRGB,IDAT和IEND块,而lena-gray.png和lena-grey-gm22.png仅包含IHDR,IDAT和IEND块。