Search code examples
javaimagepngjavax.imageio

Is there a way to get Java to read a PNG as TYPE_INT_RGB / TYPE_INT_ARGB?


When I read a PNG image in Java using javax.imageio.ImageIO.read(), the resulting BufferedImage is of TYPE_3BYTE_BGR or TYPE_4BYTE_ABGR depending on transparency.

I'm processing very large images (64+ megapixels), and need them in TYPE_INT_RGB / TYPE_INT_ARGB format, which requires an expensive and very-large-chunk-of-memory hogging repainting of the image onto a new image in the correct format, which is causing OOMs.

It would be much better if I could somehow persuade ImageIO to read the image in the desired format from the get-go - is there any way of doing that? Thanks!


Solution

  • Yes, it is possible to read into a predefined type of BufferedImage, given that the type is supported by and compatible with the reader plugin. Most often the TYPE_#BYTE_* types are compatible with the TYPE_INT_* types, and this is the case for the standard PNGImageReader.

    To make it work, you need access to the ImageReader directly, and use the read method that takes an ImageReadParam to control the type of image. It's possible to read into a pre-allocated image by using the ImageReadParam.setDestination(..) method, or to just specify the type of image and let the reader plugin allocate it for you by using ImageReadParam.setDestinationType(..) like I will show below.

    Here's a short stand-alone code sample that shows how to read into a specific image type:

    public static void main(String[] args) throws IOException {
        File input = new File(args[0]);
    
        try (ImageInputStream stream = ImageIO.createImageInputStream(input)) {
            // Find a suitable reader
            Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
            if (!readers.hasNext()) {
                throw new IIOException("No reader for " + input);
            }
    
            ImageReader reader = readers.next();
            try {
                reader.setInput(stream);
    
                // Query the reader for types and select the best match
                ImageTypeSpecifier intPackedType = getIntPackedType(reader);
                System.out.println("intPackedType = " + intPackedType);
    
                // Pass the type to the reader using read param
                ImageReadParam param = reader.getDefaultReadParam();
                param.setDestinationType(intPackedType);
    
                // Finally read the image
                BufferedImage image = reader.read(0, param);
                System.out.println("image = " + image);
            }
            finally {
                reader.dispose();
            }
        }
    }
    
    private static ImageTypeSpecifier getIntPackedType(ImageReader reader) throws IOException {
        Iterator<ImageTypeSpecifier> types = reader.getImageTypes(0);
    
        while (types.hasNext()) {
            ImageTypeSpecifier spec = types.next();
    
            switch (spec.getBufferedImageType()) {
                case BufferedImage.TYPE_INT_RGB:
                case BufferedImage.TYPE_INT_ARGB:
                    return spec;
                default:
                    // continue searching
            }
        }
    
        return null;
    }
    

    Sample output from one of my runs using a PNG as input:

    intPackedType = javax.imageio.ImageTypeSpecifier$Packed@707084ba
    image = BufferedImage@45ff54e6: type = 2 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=ff000000 IntegerInterleavedRaster: width = 100 height = 100 #Bands = 4 xOff = 0 yOff = 0 dataOffset[0] 0
    

    Where type = 2 means BufferedImage.TYPE_INT_ARGB.