Java ImageIO灰度PNG问题

Java ImageIO灰度PNG问题,java,png,javax.imageio,grayscale,Java,Png,Javax.imageio,Grayscale,我有一个灰度图像(“lena”实际上),我想用它做实验。我得到的是一个带有216个灰色阴影的512x512 PNG文件 当我用Java ImageIO阅读它时,会发生这样的情况: String name = args[0]; File fi = new File(name); BufferedImage img = ImageIO.read(fi); 我得到的BuffereImage只有154种颜色!我只是意识到了这一点,因为我处理过的图像看起来发黄,缺乏深黑色 更令人

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

当我用Java ImageIO阅读它时,会发生这样的情况:

    String name = args[0];
    File fi = new File(name);
    BufferedImage img = ImageIO.read(fi);
我得到的BuffereImage只有154种颜色!我只是意识到了这一点,因为我处理过的图像看起来发黄,缺乏深黑色

更令人恼火的是,当我使用XnView将PNG转换为GIF时,这是一个无损的过程。在本例中,使用上述代码读取GIF,我的BuffereImage中获得了所有216种颜色


是否有某种文档或描述,当ImageIO读取PNG时,我的PNG会发生什么变化?有没有设置可以解决这个问题?我在最近的JDK1.8上做了这些实验。只是我对JavaPNG支持的信任现在已经失去,我将在以后使用彩色PNG

您已将图像从线性灰度(gamma=1.0)转换为sRGB灰度(gamma=1/2.2)。这可以用GraphicsMagick演示。从Wikipedia下载的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-gray-gm22.png有154种颜色,看起来褪色或褪色

我在libpng-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-gray-gm22.png仅包含IHDR、IDAT和IEND块。

欢迎来到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离散值,你还可以有效地放松一些灰色的阴影

如果您以不同的方式访问BuffereImage的数据,也可以看到效果: 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=FFAEAEA(alpha、红色、绿色、蓝色)

从我的观点来看,它不仅降低了灰度级,而且使图像更亮(与使用1/gamma值为2.2的gamma校正差不多)。如果Java用于没有嵌入ICC配置文件的灰度图像,或者将灰度转换为sRGB,R=G=B=grayValue,或者将ICC灰度配置文件WhitePoint=D50,Gamma=2.2(至少在Windows上),则更符合逻辑。后者仍然会使您失去一些灰色色调,因为sRGB不完全是Gamma 2.2

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

解决方案: 根据实际用例的不同,您的解决方案可能是访问每个图像像素的光栅数据(gray=graster.getSample(x,y,0))并将其放入sRGB图像设置R=G=B=gray。不过,也许还有一种更优雅的方式

关于您对java或PNG的信任:
由于java ImageIO的隐式颜色转换,我在很多方面都在与它作斗争。其想法是在开发人员不需要太多颜色管理知识的情况下内置颜色管理。只要您只使用sRGB(并且您的输入也是sRGB,或者没有颜色配置文件,因此可以合法地将其视为sRGB),这在一定程度上是可行的。如果输入图像中有其他颜色空间(例如AdobeRGB),则会出现问题。灰色也是另一回事,尤其是ImageIO假设伽马=1.0的(不寻常的)灰色轮廓。现在要理解ImageIO在做什么,您不仅需要了解颜色管理的基本知识,还需要了解java在做什么。我没有在任何文档中找到此信息!一句话:ImageIO做的事情肯定被认为是正确的。这往往不是你所期望的,你可能需要更深入地了解原因,或者如果不是你想做的,你可以改变行为。

如何计算颜色的数量(PNG和
缓冲图像
)?您的PNG是否有
iCCP
(ICC配置文件)块?一个
gAMA
(gamma)区块?有趣的是,数字154再次出现。但我什么都没换。我只是让ImageIO读取文件。我的意思是,我仍然可以从完全相同的PNG创建一个“完整”的GIF,但它不能“存活”ImageIO。但是,是的,我在想,如果ImageIO确实做了一些假设,比如gamma,但它不应该能够打开或关闭它。了解原始图像是否有颜色空间信息(iCCP、cHRM、gAMA或sRGB块)会很有帮助。您可以使用“pngcheck”来查找。这可能提供了一个线索,说明为什么ImageIO(或其PNG插件)在读取图像时将颜色空间从线性转换为sRGB。