Java ImageIO Grayscale PNG Issue - java

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.
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);
I get a BufferedImage with only 154 colours! I only realized this, cause my processed images which looked sallow, lacking deep black.
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.
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.

Welcome to Java's "great" world of implicit color management!
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.
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).
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.
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) 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)) );
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).
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.
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.
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.
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.

Somehow you have converted the image from a linear grayscale (gamma=1.0) to an sRGB grayscale (gamma=1/2.2). This can be demonstrated with GraphicsMagick. Start with Lenna.png downloaded from Wikipedia, then remove the sRGB chunk to create lena.png, then
gm convert lena.png -colorspace gray -depth 8 -strip lena-gray.png
lena-gray.png has 216 colors
gm convert lena-gray.png -gamma 2.2 -depth 8 -strip lena-gray-gm22.png
lena-gray-gm22.png has 154 colors and appears washed-out or faded.
I'm using a recent beta of graphicsmagick (version 1.4) with libpng-1.6.17.
To count the colors I used ImageMagick:
identify -verbose file.png | grep Colors
I used
pngcheck -v file.png
to verify that Lenna.png contains IHDR, sRGB, IDAT, and IEND chunks, while lena-gray.png and lena-gray-gm22.png contain only IHDR, IDAT, and IEND chunks.

Related

Should I convert BufferedImage.TYPE_4BYTE_ABGR to BufferedImage.TYPE_3BYTE_BGR?

I am working on image interpolation for which I am using bi-cubic interpolation to double the resolution of image in java using AffinedTransformOp.I used BufferedImage of TYPE_4BYTE_ABGR while doing up-scaling. When I tried to save back my upscale image using ImageIO.write then I found that openjdk does not support jpeg encoding for TYPE_4BYTE_ABGR so I converted this up-scaled image from TYPE_4BYTE_ABGR to TYPE_3BYTE_BGR. When I saved it in folder then found that the memory taken by this upscale image is way less(about half time) than the memory taken by original image.
So I assume that the original(input) image is represented by four channels ARGB while upscale(output) image is taking 3 channels RGB and that's why getting less memory.
Now my question is that should I use this conversion?
Is there some information that is getting lost?
Does quality of image remains same?
P.S: I've read from the documentation of ImageIO that when we convert ARGB to RGB than the alpha value gets premultiplied to RGB values and I think it should not affect the quality of the image.
I solved my problem and hope to share my answer. Actually the type of my original image was Grayscale and the color space of my original image was grey (meaning only one channel with 8 bits) with quality of 90.Problem arised when I used TYPE_4BYTE_ABGR for the upscaling instead of using TYPE_BYTE_GRAY. Secondly when you try to save this image in a file in jpeg format ImageIO.write uses compression of 75 by default so the image size will get small. You should use the compression factor which suits you or you should save it in PNG format. You can view information about your image by using identify -verbos image.jpg in linux and can see the color space, image type and quality etcYou can check this post to see how to set your compression quality manually in ImageIO.

How to decide which BufferedImage image type to use?

Java BufferedImage class has a long list of class variables known as the image type which can be used as an argument for the BufferedImage constructor.
However, Java docs did a minimal explanation what these image types are used for and how would it affect the BufferedImage to be created.
My question is:
How would an image type affect the BufferedImage to be created? Does it control the number of bits used to store various colors (Red,Green,Blue) and its transparency?
Which image type should we use if we just want to create
an opaque image
a transparent image
a translucent image
I read the description in the Java Doc many times, but just couldn't figure out how should we use it. For example, this one:
TYPE_INT_BGR
Represents an image with 8-bit RGB color components, corresponding to a Windows- or Solaris- style BGR color model, with the colors Blue, Green, and Red packed into integer pixels. There is no alpha. The image has a DirectColorModel. When data with non-opaque alpha is stored in an image of this type, the color data must be adjusted to a non-premultiplied form and the alpha discarded, as described in the AlphaComposite documentation.
Unless you have specific requirements (for example saving memory or saving computations or a specific native pixel format) just go with the default TYPE_INT_ARGB which has 8 bits per channel, 3 channels + alpha.
Skipping the alpha channel when working with 8 bits per channel won't affect the total memory occupied by the image since every pixel will be packed in an int in any case so 8 bits will be discarded.
Basically you have:
TYPE_INT_ARGB, 4 bytes per pixel with alpha channel
TYPE_INT_ARGB_PRE, 4 bytes per pixel, same as before but colors are already multiplied by the alpha of the pixel to save computations
TYPE_INT_RGB, 4 bytes per pixel without alpha channel
TYPE_USHORT_555_RGB and TYPE_USHORT_565_RGB, 2 bytes per pixel, much less colors, don't need to use it unless you have memory constraints
Then there are all the same kind of formats with swapped channels (eg. BGR instead that RGB). You should choose the one native of your platform so that less conversion should be done.

Java: How to work with CMYK image?

I know that RGB is for monitors and CMYK is for printing, but I want to work with CMYK without any conversions. I want to upload a CMYK image (jpeg) and print it. But when I used
com.sun.image.codec.jpeg.JPEGImageDecoder decoder = JPEGCodec.createJPEGDecoder(is);
return decoder.decodeAsBufferedImage();
I got an inversed colors image. How can I get the same image in CMYK?
There reason your colors are inverted is that you have a special variant of a CMYK JPEG image, namely Adobe CYYK. The strange colors are due to an old Photoshop bug (CMYK values are inverted) that has now become a de-facto standard that's handled by most JPEG software (except Java).
A proper CMYK conversion (handling different variants, using proper color profile etc.) can be found in: https://stackoverflow.com/a/12132630/413337.

RGB accuracy problems with bufferedImage

We find that there always exists some RGB accuracy problems whlie getting RGB from a jpeg file using bufferedImage in Java(ImageIO.read(file))..
Does anyone know some alternatives solutions?
What do you mean by accuracy problems? Does the JPEG you save and the one you load with Java look different? How are the colors different?
I'm going to go on a limb here and assume that you are using a JPEG with a color profile, meaning that you are reading RGB values just right but, your original application takes also the color profile and uses it to map the RGB values extracted from your JPEG into the RGB values that get shown on your monitor. Have a look with a good image viewer, and see what the embedded profile in your image is (look for things like sRGB or AdobeRGB) and see if your problems are consistent with the different color profiles

How do I create an alphablending BufferedImage using J2ME (CDC/PP 1.1)

I have a BufferedImage created using
new BufferedImage(wid,hgt,BufferedImage.TYPE_INT_ARGB);
to which I assemble a wallpaper using multiple other images. It works fine in Jave SE, but when I tried to run the code on a J9 CDC/PP platform I discovered that the Personal Profile BufferedImage has no constructors!
Can anyone point me to how I can construct an alpha-channel supporting image using CDC 1.0 and Personal Profile 1.1?
Edit: For now I have created fallback code which handles NoSuchMethodError (et al.) and then simply creates an image with GraphicsConfiguration.createCompatibleImage(int,int). It might be that that creates an alpha-blending image, but it will be a few weeks before I can specifically test that due to other priorities (testing on handhelds is not my direct responsibility, so it's out of my hands).
If I find a better answer, I will post it as an answer to this; in the meantime, if someone else beats me to it, be assured I will accept your answer if it works, and the answer will be of interest to me for the foreseeable future (I expect to still need an answer in 2-5 years).
The Image class (javax.microedition.lcdui.Image
) contains a method getRGB(...) which parses the Image into an array of RGB+Alpha values for each pixel in the image. Once you have the image in that format, its easy to tweak the alpha values to increase their transparency before you layer the images. This is really the only dynamic way I've seen to edit the transparency of an image in J2ME.
to get the alpha (transparency) value out of the rgba array you have to use bit-shifting like this:
int origAlpha = (rgba[j] >> 24);
and then to change the alpha (transparency) value to something different (without changing the color at that pixel), you can use bitshifting to insert a different transparency level.
int newAlpha = 0x33; // or use whatever 0-255 value you want, with 255=opaque, 0=transparent
rgba[j] = (rgba[j] & 0x00ffffff);
rgba[j] = (rgba[j] | (newAlpha << 24));
Then there is a createImage(...) method in Image that takes a byte-array of image data as a parameter, that can be used to create a new image out of your modified pixel data array.
Also helpful, SonyEricsson's developer website also has a tutorial with sample code called "Fade in and out images in MIDP 2.0" which explains "how to change the alpha value of an image to make it appear blended" which is essentially alpha-blending.

Categories