Reading Grayscale PNG image files without distortion - java

I need to read and process a large number of PNG files that are grayscale. By that I mean that if they are opened in either Photoshop or GIMP, the image mode is Grayscale - not an RGB image with grayscale values.
ImageIO does not seem to achieve this. It appears to treat all image files as sRGB. This mangles grayscale values. I need to read and process these PNG files where (in my code) each pixel has exactly the same value as if I had opened the grayscale file in Photoshop or GIMP. Does anybody know of some open source software that can achieve this, please? Or better how to achieve this using ImageIO.
Additional Information:
I am using getRGB() on a BufferedImage. The underlying pixel in the image file is 0x86. I understand that this does not necessarily correspond to an ARGB pixel containing 0xFF868686, as this depends upon luminance/gamma. However, in the absence of a getter with a gamma type argument, I would have expected the default mapping to be to ARGB=0xFF868686. If I use GIMP or Photoshop to convert a grayscale image containing a pixel with the value of 0x86 to RGB then the pixel becomes 0xFF868686. This is the obvious default.
However, ImageIO seems to use a weird gamma (whether you like it or not) with grayscale image files that makes the grayscale pixels very, very light after mapping to ARGB. In this case, 0x86 maps to 0xFFC0C0C0. This is not only very light, it can also result in considerable data loss as many grayscale values can be mapped to fewer ARGB values. The only time that this distortion will not result in data loss is for very dark grayscale images. An appropriate Gamma is context dependent, different physical media will distort luminance differently. However, in the absence of a context, the mapping: 0x86 --> 0xFF868686 makes most sense - witness the choices made for GIMP and Photoshop.
Leaving the getRGB() issue to one side, having loaded the grayscale image (using ImageIO.read( imageFile )), the getType() method of BufferedImage returns Type=0 (Custom) and not Type=10 (TYPE_BYTE_GRAY) as I would have expected.
In short, ImageIO does not seem to provide a nice and simple high level way of reading and manipulating existing grayscale images. I had hoped not to have to mess around under the covers with Rasters, ICC, sampling etc. Nor do I want to have to physically convert all the grayscale image files to RGB. All I wanted was an API load() method for BufferedImage that works just like open file does in GIMP or Photoshop. I have not been able to achieve this. I am hoping that this is my ignorance and not a limitation of Java ImageIO.
Possible Solution:
After digging around I have the following to offer as a possible technique for accessing the underlying grayscale values:
final File imageFile = new File( "test.png" );
final BufferedImage image = ImageIO.read( imageFile );
// --- Confirm that image has ColorSpace Type is GRAY and PixelSize==16
final Raster raster = image.getData();
// --- Confirm that: raster.getTransferType() == DataBuffer.TYPE_BYTE
for( int x=0, xLimit=image.getWidth(); x < xLimit; x++ ) {
for( int y=0, yLimit=image.getHeight(); y < yLimit; y++ ) {
final Object dataObject = raster.getDataElements( x, y, null );
// --- Confirm that dataObject is instance of byte[]
final byte[] pixelData = (byte[]) dataObject;
// --- Confirm that: pixelData.length == 2
final int grayscalePixelValue = pixelData[0] & 0xFF;
final int grayscalePixelAlpha = pixelData[1] & 0xFF;
// --- Do something with the grayscale pixel data
}
}
The javadoc is not great, so I cannot guarantee that this is correct, but it seems to work for me.

In case you want to try a third party (mine) lib: https://github.com/leonbloy/pngj/
If you are certain that the image is plain grayscale (8 bits, no alpha, no palette, no profile), it's quite simple:
PngReaderByte pngr = new PngReaderByte(new File(filename)); //
if (pngr.imgInfo.channels!=1 || pngr.imgInfo.bitDepth != 8 || pngr.imgInfo.indexed)
throw new RuntimeException("This method is for gray images");
for (int row = 0; row < pngr.imgInfo.rows; row++) {
ImageLineByte line = pngr.readRowByte();
byte [] buf = line.getScanlineByte();
// do what you want
}
pngr.end();

Java's ImageIO is known to be broken on images with a grayscale palette.
Java ImageIO Grayscale PNG Issue
javax.imageio.ImageIO reading incorrect RGB values on grayscale images
My batch jpg resizer works with color images, but grayscale ones become washed out
Wrong brightness converting image to grayscale in Java
Oracle: JDK-5051418 : Grayscale TYPE_CUSTOM BufferedImages are rendered lighter than TYPE_BYTE_GRAY
Oracle: JDK-6467250 : BufferedImage getRGB(x,y) problem

Related

Store information in a PNG file without distorting the image?

I want to write informations (former: int/String) into a PNG-file without making the changes visible to the human eye.
In order to achieve this i extract the file in the form of a Byte array:
byte[] imageAsBytes = fileInputStream.readAllBytes();
In the next step i jump the header (8 Bytes, already tried up to 300Bytes). I already have my information processed and can extract it bitwise.
I replace the last Bit from the picture`s Bytes with the Bitwise extracted information.
This works well with the .bmp file format but applying this technique to PNG-files distorts the image.
My guess is that the PNG-file contains additional information after the header.
So is there any information stored after the header and if so what marks this "information-part" as one?
In order to read and decode a .png file we can use BufferedImage as it follows:
File imageFile = new File("myimage.png");
BufferedImage image = ImageIO.read(imageFile);
No we can manipulate its pixels using getRGB and setRGB methods:
int pixel = image.getRGB(x, y);
// do some byte manipulation on the pixel
// ....
image.setRGB(x, y, pixel);
After this the BufferedImage should be piped to an output stream to be able to save the new image.
I think the best way will be not look at file bytes, but open it with some png decoder then change pixels at image itslef then save it again using png. When You will play with file bytes itself for sure You will broke the format of png, You didnt in bmp becasue bmp its jsut pixel by pixel saved drectly to file wihout any packaging or encoding

rgb pixel values got changed in image after writing image from array of values

Hi my code is like this
BufferedImage img1=new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB);
for(int r=0; r<w; r++)
{
for(int c=0; c<h; c++)
{
img1.setRGB(0, 0, w, h, data,0,w);
}
}
ImageIO.write(img1,"jpg", new File("abc.jpg"));
I want to create image of width w and height h..I am having my pixel values in an int array called data(consists of combined rgb values)..ImageIO.write method creates the image but with different pixel values..please help me out..i tried very hardly..but still not getting..
The reason why is because you're using JPEG compression. JPEG compression is a lossy compression algorithm, so what you have stored in memory will not be the same as you have written to file. Lossy compression allows for high compression ratios, and to mimic or closely resemble what you visually see in the image, but the actual contents will not be the same. They will certainly be similar though.
The only way you can write to an image file and keep the image pixels the same is to use a lossless compression algorithm. Try using PNG instead.

Reading pixel aspect ratio of TIF image in Java

I am reading in and processing TIF images using ImageIO and JAI. The results are all working perfectly, except that a number of the TIF images do not have square pixels. The aspect ratio of the pixels is being lost during the processing so the resulting image looks stretched.
I found this question which reads out the resolution in C#: Change tiff pixel aspect ratio to square but I cannot find any equivalent in java.
Does anyone know how either to read the horizontal and vertical resolution (not size) of a BufferedImage and/or TIF Image in Java or cause JAI to scale the image as it loads it so that the resulting pixels are square?
After an hour of Googling and trying things I think I have found a solution.
IIOMetadata iiom = ir.getImageMetadata(i);
TIFFDirectory dir = TIFFDirectory.createFromMetadata(iiom);
TIFFField fieldXRes = dir.getTIFFField(BaselineTIFFTagSet.TAG_X_RESOLUTION);
TIFFField fieldYRes = dir.getTIFFField(BaselineTIFFTagSet.TAG_Y_RESOLUTION);
int xRes = fieldXRes.getAsInt(0);
int yRes = fieldYRes.getAsInt(0);
As an alternative, you can also get the same values from the Standard Metadata, if you don't want to rely on the JAI API (or TIFF format specifics at all).
The Dimension element has the child elements HorizontalPixelSize and VerticalPixelSize which should be equivalent to the values you got from the TIFF tags above, as well as a PixelAspectRatio you could use directly.

How to convert an indexed image to a RGB image in Java?

I have a color indexed TIFF image (8-bits) and I want to convert it to a RGB 24-bits image (not indexed). What would be the way to do that?
I'm using JMagick. In a weird way, it works fine for indexed 8-bits images that are grayscale when i use:
image.transformRgbImage(info.getColorspace());
even if the image, though not indexed any more, is still 8-bits after that, which is lucky as it is grayscale and should actually be 8-bits. The weird stuff is that the transformRgbImage() performs that although I'd rather expect it to convert the image to a 24-bits one. Anyway...
The same way doesn't work for a color indexed 8-bits image. I just don't know how to use the JMagick API to achieve that goal. I tried setting:
image.setDepth(24);
or:
info.setDepth(24);
but both result in an EXCEPTION_ACCESS_VIOLATION. When I set:
info.setDepth(32);
no exception is raised, 1) but the image is 32-bits, which shouldn't be, and 2) it is all black (1 unique color). Why does the setDepth(24) raises such an exception?? How should I do?
Thanks in advance for your help.
I dont know about jmagick, but generally once you created an image object its properties are fixed (size and color model).
You don't change an images properties, you create a new image with the desired target properties and paint your original image into the new image. In plain core java you would simply do it like this:
public BufferedImage toRGB(Image i) {
BufferedImage rgb = new BufferedImage(i.getWidth(null), i.getHeight(null), BufferedImage.TYPE_INT_RGB);
rgb.createGraphics().drawImage(i, 0, 0, null);
return rgb;
}

Programmatically Reducing JPEG file size

Apologies for any ignorance, but I have never worked with jpeg images (let alone any types of images) in Java before.
Supposing I want to send a jpeg image from a web service to a client. Is there any way that I can reduce the jpeg file size by manipulating the colour profile of the image in some way?
I have already been able to reduce the image size by scaling it using a neat tool for BufferedImages called imgscalr. See here.
I would also like a jpeg that has less colours than a high quality jpeg image. For example, I would like to be able to use 8bit colour in my jpeg instead of say 16bit colour.
What exactly would I need to change if I have a BufferedImage from Java's 2D package?
Another way to reduce image size is to change compression level. You can do that using ImageWriter.
ImageWriter writer = null;
Iterator<ImageWriter> iwi = ImageIO.getImageWritersByFormatName("jpg");
if (!iwi.hasNext())
return;
writer = (ImageWriter) iwi.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT) ;
iwp.setCompressionQuality(compressionQuality);
writer.setOutput(...);
writer.write(null, image, iwp);
The easiest way to do this is to decompress the byte stream into a Java Image, optionally resize it (which makes it smaller) and then regenerate a JPEG image from this with the desired quality setting.
This new image is then what is sent to the client.
Have a look at the ImageIO class. As for reducing file size: since the image would already be a JPEG the only things you could do is reduce the quality or the image size.
Another thing to keep in mind: if the image is a CMYK jpeg it might be bigger. Unfortunately ImageIO can't handle those, but you can try JAI ImageIO to convert from CMYK to RGB (which should be much smaller).
Two of the possible solutions are downscaling the image, here's how you'd do it:
BufferedImage original = //your image here
scaled = original.getScaledInstance(finalWidth, finalHeight, Image.SCALE_SMOOTH); // scale the image to a smaller one
BufferedImage result = new BufferedImage(finalWidth, finalHeight, original.getType());
Graphics2D g = result.createGraphics();
g.drawImage(scaled, 0, 0, null); //draw the smaller image
g.dispose();
Obviously, you have to calculate the scaled width and height so the image stays by the same aspect ratio.
Once you have drawn it smaller, you can now turn this image into a JPEG file:
BufferedImage image = // this is the final scaled down image
JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(output);
JPEGEncodeParam jpegEncodeParam = jpegEncoder.getDefaultJPEGEncodeParam(image);
jpegEncodeParam.setDensityUnit(JPEGEncodeParam.DENSITY_UNIT_DOTS_INCH);
jpegEncodeParam.setXDensity(92);
jpegEncodeParam.setYDensity(92);
jpegEncodeParam.setQuality( 0.8F , false);
jpegEncoder.encode(image, jpegEncodeParam);
These classes are from the JAI package (more exactly com.sun.image.codec.jpeg) and the JVM might complain that they should not be used directly, but you can ignore that.
You can possibly download JAI from here, if it does not work I have github mirrors setup for the two libraries, JAI core and JAI ImageIO.

Categories

Resources