Java ImageIO 灰度 PNG 问题 [英] Java ImageIO Grayscale PNG Issue

查看:48
本文介绍了Java ImageIO 灰度 PNG 问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个我想尝试的灰度图像(实际上是lena").我得到了一个 512x512 PNG 文件,带有 216 种灰度灰色.

I have a grayscale image ("lena" actually) which I want to experiment with. I got it as a 512x512 PNG file with 216 shades of gray.

当我用 Java ImageIO 读取它时会发生什么,就像这样:

What happens is, when I read it with Java ImageIO, like that:

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

我得到一个只有 154 色的 BufferedImage!我才意识到这一点,导致我处理的图像看起来蜡黄,缺乏深黑色.

I get a BufferedImage with only 154 colours! I only realized this, cause my processed images which looked sallow, lacking deep black.

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

Even more irritating, when I use XnView convert the PNG to a GIF, which is a lossless procedure in this case, read the GIF with above code, I get all 216 colours in my BufferedImage.

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

Is there some kind of documentation or description, what happens to my PNG, when ImageIO reads it? Are there settings to fix that? I did these experiments on a fairly recent JDK1.8. It is just that my trust in Java PNG support is lost now and I will use coloured PNG later.

推荐答案

欢迎来到 Java 隐式色彩管理的伟大"世界!

Welcome to Java's "great" world of implicit color management!

对于 Java(至少是 ImageIO),内部的一切都是 sRGB 并且它隐含地进行颜色管理,这对于人们实际想要做的事情通常会适得其反.对于灰度图像,至少在大多数阅读器中使用 ImageIO 并且至少对于没有嵌入 ICC 配置文件的灰度图像(我还没有测试其他人),Java 会自动分配"一个 WhitePoint=D50,Gamma=1.0 的 ICC 配置文件.我也偶然发现了这个.

For Java (at least ImageIO) everything internally is sRGB and it implicitely does color management, which often is quite counter-productive for what one actually wants to do. For gray scale images, at least using ImageIO with most readers and at least for gray scale images without an embedded ICC profile (I haven't tested others yet), Java automatically "assigns" an ICC profile with WhitePoint=D50, Gamma=1.0. I stumbled across this as well.

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

And then, when you access pixels (I assume you use img.getRGB() or something similar?), you actually access sRGB values (Java's default color space on Windows).

结果是,当转换为 sRGB 时,它的伽马约为 2.2(sRGB 的伽马实际上有点复杂,但总体上接近 2.2),这有效地应用了 (1/Gamma)=2.2 的伽马校正对于图像,(a) 使图像看起来亮",(b) 由于从 256 到 256 个离散值的伽马校正,您还可以有效地消除一些灰色阴影.

The result is, when converting to sRGB, which has a gamma of ~2.2 (sRGB's gamma is actually a bit more complicated, but close to 2.2 overall), this affectively applies a gamma correction with (1/Gamma)=2.2 to the image, (a) making your image appear "light", and (b) due to the gamma correction from 256 to 256 discrete values, you also effectively loose some of your shades of gray.

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

You also can see the effect if you access your BufferedImage's data in different ways: a) access the profile:

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) 以不同的方式访问一些像素值......

b) access some pixel values in different ways ...

//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 (alpha,红色,绿色,蓝色).

If your pixel (0,0) is not by chance 100% black or 100% white, you will see that the sRGB value is "higher" than the gray value, for example gray = d1 -> sRGB = ffeaeaea (alpha, Red, Green, Blue).

在我看来,它不仅降低了您的灰度级,而且使您的图像更亮(与应用 1/gamma 值为 2.2 的 gamma 校正大致相同).如果 Java 用于没有嵌入 ICC 配置文件的灰色图像要么将灰色转换为 R=G=B=grayValue 的 sRGB,要么分配一个 ICC 灰色配置文件 WhitePoint=D50, Gamma=2.2(至少在 Windows 上),这将更合乎逻辑.由于 sRGB 不是 Gamma 2.2,后者仍然会让你失去一些灰色调.

From my point of view, it does not only reduce your gray levels, but also makes your image lighter (about the same as applying gamma correction with 1/gamma value of 2.2). It would be more logical if Java for gray images without embedded ICC Profile either translates gray to sRGB with R=G=B=grayValue or would assign an ICC Gray Profile WhitePoint=D50, Gamma=2.2 (at least on Windows). The latter still would make you loose a couple of gray tones due to sRGB not being exactly Gamma 2.2.

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

Regarding why it works with GIF: the GIF format has no concept of "gray scales" or ICC profiles, so your image is a 256 color palette image (the 256 colors happen to be 256 shades of gray). On opening a GIF, Java assumes the RGB values are sRGB.

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

Solution: Depending on what your actual use case is, the solution for you might be that you access the Raster data of each of your image's pixel (gray=raster.getSample(x,y,0)) and put it into an sRGB image setting R=G=B=gray. There might be an more elegant way, though.

关于您对 Java 或 PNG 的信任:由于它所做的隐式颜色转换,我在很多方面都在努力使用 java ImageIO.这个想法是内置颜色管理,而开发人员不需要太多关于颜色管理的知识.只要您仅使用 sRGB(并且您的输入也是 sRGB,或者没有颜色配置文件,因此可以合法地认为是 sRGB),这在一定程度上有效.如果您的输入图像中有其他颜色空间(例如 Adob​​eRGB),就会出现问题.灰色也是另一回事,尤其是 ImageIO 假定 Gamma=1.0 的(不寻常的)灰色配置文件.现在要了解ImageIO在做什么,你不仅需要知道你在色彩管理方面的基础知识,还需要弄清楚java在做什么.我在任何文档中都没有找到此信息!底线:ImageIO 做的事情肯定可以被认为是正确的.这通常不是您所期望的,如果这不是您想要做的,您可能会深入挖掘以找出原因或改变行为.

Regarding your trust in java or PNG: I'm struggling with java ImageIO in many ways due to the implicite color conversions it does. The idea is to have color management built in without the developers need much knowledge about color management. This works to some extend as long as you work with sRGB only (and your input is sRGB, too, or has no color profile and thus could legitimately considered to be sRGB). Trouble starts if you have other color spaces in your input images (for example AdobeRGB). Gray is another thing as well, especially the fact that ImageIO assumes an (unusual) Gray Profile with Gamma=1.0. Now to understand what ImageIO is doing, you don't only need to know your ABC in color management, but also need to figure out what java is doing. I didn't find this info in any documentation! Bottom line: ImageIO does things that certainly could be considered correct. It's just often not what you expect and you might to dig deeper to find out why or to change the behaviour if it isn't what you want to do.

这篇关于Java ImageIO 灰度 PNG 问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆