skip to Main Content

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.

2

Answers


  1. 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(); 
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search