问题描述
我有一个灰度图像(实际上是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,我得到全部216我的BufferedImage中的颜色。
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.
是否存在某种文档或描述,当我的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的ICC配置文件,Gamma = 1.0。我偶然发现了这一点。
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值(Java在Windows上的默认颜色空间)。
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,Red,Green,Blue)。
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 /伽玛值为2.2的伽马校正相同)。如果没有嵌入式ICC配置文件的灰色图像的Java将灰色转换为具有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.
解决方案:
根据您的实际用例,您的解决方案可能是您访问每个图像像素的栅格数据(灰色= 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),这在一定程度上有效。如果输入图像中有其他颜色空间(例如AdobeRGB),则会启动麻烦。灰色也是另一回事,特别是ImageIO采用Gamma = 1.0的(异常)灰度轮廓这一事实。现在要了解ImageIO正在做什么,您不仅需要知道您在颜色管理中的ABC,还需要弄清楚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问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!