JDK-6788458 : PNGImageReader ignores tRNS chunk while reading non-indexed RGB/Gray images
  • Type: Enhancement
  • Component: client-libs
  • Sub-Component: javax.imageio
  • Affected Version: 9,10,11
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2008-12-23
  • Updated: 2018-10-05
  • Resolved: 2018-04-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11
11 b13Fixed
Related Reports
Relates :  
Description
A DESCRIPTION OF THE REQUEST :
ImageIO.read() ignores the tRNS chunk in 8-bit PNGs which are not indexed. This means that loading a PNG of color type 2 which has a transparent color via ImageIO.read() will result in a BufferedImage which is opaque. According to the PNG standard, tRNS is an ancillary chunk, so a compliant PNG decoder is free to ignore it, which is why I'm not reporting this as an ImageIO bug.

JUSTIFICATION :
The PNG decoder used by Toolkit.createImage() does respect the tRNS chunk and produces an Image with the correct transparency, but it returns an Image which is not also a BufferedImage, which means we  then have to spend extra time converting the image. ImageIO returns BufferedImages, so it would be nice if this kind of image could be decoded directly.

Additionally,  ImageIO is the only PNG decoder I've been able to find which doesn't honor the tRNS chunk, which means that this behavior, while not strictly speaking incorrect, is annoying and unexpected.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The transparent pixels in the resulting image should be transparent.
ACTUAL -
The transparent pixels in the resulting image were opaque.

---------- BEGIN SOURCE ----------
import javax.imageio.*;
import javax.swing.*;
import java.io.File;
import java.awt.Toolkit;
import java.net.URL;
import java.awt.Image;
 
public class Test {
  public static void main(final String[] args) throws Exception {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        try {
          final Image img = ImageIO.read(new File(args[0]));
//          final Image img = Toolkit.getDefaultToolkit().createImage(new URL("file://" + args[0]));
 
          final JFrame frame = new JFrame();
          final JLabel label = new JLabel(new ImageIcon(img));
 
          frame.add(label);
          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          frame.pack();
          frame.setVisible(true);
        }
        catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
  }
}
---------- END SOURCE ----------

Comments
After taking review suggestions into consideration, added support for non-indexed grayscale images also(http://mail.openjdk.java.net/pipermail/2d-dev/2018-April/009114.html ).
05-04-2018

Was able to make changes to support 16 bit non-indexed RGB images with tRNS chunk along with 8 bit RGB images. Since non-indexed RGB PNG images can be of only two bitDepths 8 & 16. We will supporting all types of non-indexed RGB images having tRNS chunk.
27-03-2018

While writing we don't ignore tRNS chunk and PNGImageWriter properly adds tRNS chunk data.
27-03-2018

Made hardcoded changes in PNGImageReader like changing destination image type to 4BYTE_ABGR, increasing input bands and elements per row in PNGImageReaderdecodePass(). Also added logic to compare pixel value with tRNS_RGB values and store alpha channel with 0 or 255. With these initial hardcoded changes was able to replicate Toolkit.createImage() behavior in ImageIO.PNGImageReader also.
20-03-2018

Test case where PNGImageReader.read() is used to show how we ignore tRNS chunk in case of 8 bit RGB non-indexed PNG images: import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Iterator; import javax.imageio.*; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import javax.swing.*; public final class Read8BitPNGWithTRNSChunk { public static void main(String[] args) throws IOException { BufferedImage img = new BufferedImage(50, 50, BufferedImage.TYPE_3BYTE_BGR); Graphics2D g2D = img.createGraphics(); g2D.setColor(Color.WHITE); g2D.fillRect(0, 0, 50, 50); g2D.dispose(); //ImageIO.write(img, "png", new File("example.png")); Iterator<ImageWriter> iterWriter = ImageIO.getImageWritersBySuffix("png"); ImageWriter writer = iterWriter.next(); ImageWriteParam param = writer.getDefaultWriteParam(); ImageTypeSpecifier specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); IIOMetadata metadata = writer.getDefaultImageMetadata(specifier, param); IIOMetadataNode tRNS_rgb = new IIOMetadataNode("tRNS_RGB"); tRNS_rgb.setAttribute("red", "255"); tRNS_rgb.setAttribute("green", "255"); tRNS_rgb.setAttribute("blue", "255"); IIOMetadataNode tRNS = new IIOMetadataNode("tRNS"); tRNS.appendChild(tRNS_rgb); IIOMetadataNode root = new IIOMetadataNode("javax_imageio_png_1.0"); root.appendChild(tRNS); metadata.mergeTree("javax_imageio_png_1.0", root); File output = new File("output.png"); ImageOutputStream ios = ImageIO.createImageOutputStream(output); writer.setOutput(ios); writer.write(metadata, new IIOImage(img, null, metadata), param); ImageInputStream iis = ImageIO.createImageInputStream(output); Iterator<ImageReader> iterReader = ImageIO.getImageReadersBySuffix("png"); ImageReader reader = iterReader.next(); reader.setInput(iis, false, false); BufferedImage display_img = reader.read(0); //Image display_img = Toolkit.getDefaultToolkit().createImage("output.png"); JFrame frame = new JFrame(); frame.getContentPane().setLayout(new FlowLayout()); frame.getContentPane().add(new JLabel(new ImageIcon(display_img))); frame.pack(); frame.setVisible(true); } } If we see a white image in JFrame then it shows that tRNS RGB values are ignored(because tRNS RGB value is 255,255,255 and all the pixels in the image should be considered as transparent). If we open "output.png" which is created after running the test case using IrfanView it doesn't show white image but it shows an image with all the values as RGB(0,0,0). But the expectation is to show all the colors as ARGB(0, 255, 255, 255) which is how it is done in Toolkit.createImage().
19-03-2018

More analysis: Both in the case where we use ImageIO.read() or PNGImageReader.read() we face the problem. In case of ImageIO.read() since ignoreMetadata is true we dont even parse tRNS chunk. In case PNGImageReader.read() we can see that we hit pasrse_tRNS() but as mentioned previously we ignore the tRNS data.
19-03-2018

Initial analysis: 1) Toolkit.createImage() : In case of PNGImageDecoder, we use RGBdefault(DirectColorModel with pixelsize 32 & datatype INT) colormodel as destination when we try to decode png image of IHDR colortype RGB. So when we are decoding metadata we use data present in tRNS chunk and calculate transparent pixel information. While we are decoding image data, for each pixel we verify where R, G & B values match with R, G & B values present in tRNS chunk. If pixel information doesnt match tRNS chunk data then we store the pixel with full alpha 255, otherwise we dont set alpha value and the pixel will be treated as transparent. 2) ImageIO.PNGImageReader.readImage() : In case of PNGImageReader, the first available default imagetype for destination is 3BYTE_RGB(which we set in PNGImageReader.getImageTypes()) in case of IHDR colortype RGB. When we are decoding metadata we store tRNS color values for RGB color type but we never store it as a transparent pixel data and use it while we are storing the pixel information in destination image type. Also since the default imagetype for destination is 3BYTE_RGB we will not be able to use transparent pixel information as there is no space to store alpha channel. Possible solution can be to have 4BYTE_ARGB default destination image when we see that we have tRNS information for IHDR colortype RGB. And then in PNGImageReader.decodePass() we can store, normal pixel information with full alpha and those pixels which match transparent pixel without any alpha. In case where user explicitly provides destination imagetype containing alpha then we may need more changes in decodePass to place alpha information at proper place.
08-03-2018

Please re-open if - if fix is in progress or on the plan to fix soon - if this is a P3 (file as P3, not P4)
18-03-2014