changeset 42217:53ac12e1344a

6334602: Animated GIFs created from opaque PNG image frames appear transparent when loaded with Toolkit APIs Reviewed-by: serb, prr
author jdv
date Tue, 15 Nov 2016 12:52:24 +0530
parents 621af0ebf6c4
children 35e0972b2533 7ae63c40a6ec
files jdk/src/java.desktop/share/classes/sun/awt/image/GifImageDecoder.java jdk/test/java/awt/image/OpaquePNGToGIFTest.java jdk/test/java/awt/image/opaque_input.png
diffstat 3 files changed, 423 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/jdk/src/java.desktop/share/classes/sun/awt/image/GifImageDecoder.java	Mon Nov 14 09:59:36 2016 -0800
+++ b/jdk/src/java.desktop/share/classes/sun/awt/image/GifImageDecoder.java	Tue Nov 15 12:52:24 2016 +0530
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -31,7 +31,6 @@
  */
 package sun.awt.image;
 
-import java.util.Vector;
 import java.util.Hashtable;
 import java.io.InputStream;
 import java.io.IOException;
@@ -620,7 +619,6 @@
 
 class GifFrame {
     private static final boolean verbose = false;
-    private static IndexColorModel trans_model;
 
     static final int DISPOSAL_NONE      = 0x00;
     static final int DISPOSAL_SAVE      = 0x01;
@@ -718,12 +716,6 @@
             case DISPOSAL_BGCOLOR:
                 byte tpix;
                 if (model.getTransparentPixel() < 0) {
-                    model = trans_model;
-                    if (model == null) {
-                        model = new IndexColorModel(8, 1,
-                                                    new byte[4], 0, true);
-                        trans_model = model;
-                    }
                     tpix = 0;
                 } else {
                     tpix = (byte) model.getTransparentPixel();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/image/OpaquePNGToGIFTest.java	Tue Nov 15 12:52:24 2016 +0530
@@ -0,0 +1,422 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+ /*
+ * @test
+ * @bug     6334602
+ * @summary Test verifies that when we create GIF image from Opaque PNG images
+ *          it should not be transparent.
+ * @modules java.desktop/com.sun.imageio.plugins.gif
+ * @run     main/manual OpaquePNGToGIFTest
+ */
+
+import java.awt.image.BufferedImage;
+import com.sun.imageio.plugins.gif.GIFImageMetadata;
+import java.awt.Canvas;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Image;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.ImageWriteParam;
+import javax.imageio.ImageWriter;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageOutputStream;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+
+public final class OpaquePNGToGIFTest extends Frame {
+
+    Image img = null;
+    private Dimension prefImgSize = new Dimension(100, 100);
+    private Color canvasColor = new Color(0, 255, 0);
+    File outputFile = null;
+    ImageCanvas imageCanvas = new ImageCanvas();
+    FileOutputStream fos;
+    private static GridBagLayout layout;
+    private static JPanel mainControlPanel;
+    private static JPanel resultButtonPanel;
+    private static JPanel canvasPanel;
+    private static JLabel instructionText;
+    private static JButton startTestButton;
+    private static JButton passButton;
+    private static JButton failButton;
+    private static JDialog dialog;
+    private static Frame instructionFrame;
+    private static Frame imageFrame;
+    Toolkit tk;
+    ImageWriter writer = null;
+    boolean testPassed, testGeneratedInterrupt, startButtonClicked;
+    private static Thread mainThread;
+
+    public OpaquePNGToGIFTest() throws Exception {
+        SwingUtilities.invokeAndWait(() -> {
+            try {
+                startTest();
+            } catch (Exception ex) {
+                Logger.getLogger(OpaquePNGToGIFTest.class.getName()).
+                        log(Level.SEVERE, null, ex);
+            }
+        });
+        mainThread = Thread.currentThread();
+        try {
+            Thread.sleep(60000);
+        } catch (InterruptedException e) {
+            if (!testPassed && testGeneratedInterrupt) {
+                throw new RuntimeException("Test failed or didnt run"
+                        + " properly");
+            }
+        }
+        if (!testGeneratedInterrupt) {
+            if (img != null) {
+                img.flush();
+            }
+            instructionFrame.dispose();
+            if (startButtonClicked) {
+                imageFrame.dispose();
+            }
+            fos.close();
+            Files.delete(outputFile.toPath());
+            throw new RuntimeException("user has not executed the test");
+        }
+    }
+
+    public void startTest() throws Exception {
+        instructionFrame = new Frame();
+        dialog = new JDialog(instructionFrame);
+        dialog.setTitle("Instruction Dialog");
+        layout = new GridBagLayout();
+        mainControlPanel = new JPanel(layout);
+        resultButtonPanel = new JPanel(layout);
+        canvasPanel = new JPanel();
+        GridBagConstraints gbc = new GridBagConstraints();
+        String instructions
+                = "<html>    INSTRUCTIONS:<br><br>"
+                + "After clicking on Start Test button you will see Red<br> "
+                + " circle drawn with light blue background, if the circle<br>"
+                + " color changes from Red to Green then press button Fail,<br>"
+                + " if it stays Red then press button Pass.<br><br></html>";
+        instructionText = new JLabel();
+        instructionText.setText(instructions);
+
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        mainControlPanel.add(instructionText, gbc);
+        startTestButton = new JButton("Start Test");
+        startTestButton.setActionCommand("Start Test");
+        startTestButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                try {
+                    startButtonClicked = true;
+                    imageFrame = new Frame();
+
+                    Iterator it = ImageIO.getImageWritersByFormatName("GIF");
+                    while (it.hasNext()) {
+                        writer = (ImageWriter) it.next();
+                        break;
+                    }
+                    // read input opaque PNG image.
+                    String fileName = "opaque_input.png";
+                    String sep = System.getProperty("file.separator");
+                    String dir = System.getProperty("test.src", ".");
+                    System.out.println(dir + "     " + sep);
+                    String filePath = dir + sep + fileName;
+                    File inputFile = new File(filePath);
+                    ImageInputStream iis = ImageIO.
+                            createImageInputStream(inputFile);
+                    ImageReader reader = null;
+                    Iterator readerIter = ImageIO.getImageReaders(iis);
+                    while (readerIter.hasNext()) {
+                        reader = (ImageReader) readerIter.next();
+                        break;
+                    }
+
+                    reader.setInput(iis);
+                    IIOMetadata imgData = reader.getImageMetadata(0);
+                    BufferedImage bi = reader.read(0);
+
+                    //create temporary GIF imageas output
+                    File directory = new File(dir + sep);
+                    outputFile = File.createTempFile("output",
+                            ".gif", directory);
+                    createAnimatedImage(bi, imgData,
+                            writer, outputFile);
+                    writer.dispose();
+                    iis.close();
+                    if (outputFile == null) {
+                        throw new RuntimeException("Unable to create output GIF"
+                                + " file");
+                    }
+                    // extract GIF image using Toolkit and show it on a Panel.
+                    tk = Toolkit.getDefaultToolkit();
+                    img = tk.getImage(dir + sep + outputFile.getName());
+                    directory.delete();
+                    imageCanvas.setBackground(canvasColor);
+                    imageCanvas.setImage(img);
+                    imageCanvas.setPreferredSize(prefImgSize);
+                    canvasPanel.doLayout();
+
+                    canvasPanel.add(imageCanvas);
+                    imageFrame.add("Center", canvasPanel);
+                    imageFrame.setSize(200, 200);
+                    imageFrame.setVisible(true);
+                    imageFrame.addWindowListener(new WindowAdapter() {
+                        @Override
+                        public void windowClosing(WindowEvent e) {
+                            try {
+                                img.flush();
+                                instructionFrame.dispose();
+                                imageFrame.dispose();
+                                fail();
+                            } finally {
+                                try {
+                                    fos.close();
+                                    Files.delete(outputFile.toPath());
+                                } catch (IOException ex) {
+                                    Logger.getLogger(OpaquePNGToGIFTest.class.
+                                            getName()).log(Level.SEVERE, null,
+                                                    ex);
+                                }
+                            }
+                        }
+                    });
+                } catch (IOException ex) {
+                    Logger.getLogger(OpaquePNGToGIFTest.class.getName()).
+                            log(Level.SEVERE, null, ex);
+                }
+            }
+        });
+        passButton = new JButton("Pass");
+        passButton.setActionCommand("Pass");
+        passButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                try {
+                    if (img != null) {
+                        img.flush();
+                    }
+                    instructionFrame.dispose();
+                    if (!startButtonClicked) {
+                        fail();
+                    } else {
+                        imageFrame.dispose();
+                        pass();
+                    }
+                } finally {
+                    try {
+                        fos.close();
+                        Files.delete(outputFile.toPath());
+                    } catch (IOException ex) {
+                        Logger.getLogger(OpaquePNGToGIFTest.class.
+                                getName()).log(Level.SEVERE, null, ex);
+                    }
+                }
+            }
+        });
+        failButton = new JButton("Fail");
+        failButton.setActionCommand("Fail");
+        failButton.addActionListener(new ActionListener() {
+            @Override
+            public void actionPerformed(ActionEvent e) {
+                try {
+                    if (img != null) {
+                        img.flush();
+                    }
+                    instructionFrame.dispose();
+                    if (!startButtonClicked) {
+                        fail();
+                    } else {
+                        imageFrame.dispose();
+                        fail();
+                    }
+                } finally {
+                    try {
+                        fos.close();
+                        Files.delete(outputFile.toPath());
+                    } catch (IOException ex) {
+                        Logger.getLogger(OpaquePNGToGIFTest.class.
+                                getName()).log(Level.SEVERE, null, ex);
+                    }
+                }
+            }
+        });
+        gbc.gridx = 1;
+        gbc.gridy = 0;
+        resultButtonPanel.add(startTestButton, gbc);
+        gbc.gridx = 2;
+        gbc.gridy = 0;
+        resultButtonPanel.add(passButton, gbc);
+        gbc.gridx = 3;
+        gbc.gridy = 0;
+        resultButtonPanel.add(failButton, gbc);
+
+        gbc.gridx = 0;
+        gbc.gridy = 1;
+        mainControlPanel.add(resultButtonPanel, gbc);
+
+        dialog.add(mainControlPanel);
+        dialog.setSize(400, 200);
+        dialog.setLocationRelativeTo(null);
+        dialog.setVisible(true);
+
+        dialog.addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(WindowEvent e) {
+                try {
+                    if (img != null) {
+                        img.flush();
+                    }
+                    instructionFrame.dispose();
+                    if (!startButtonClicked) {
+                        fail();
+                    } else {
+                        imageFrame.dispose();
+                        fail();
+                    }
+                } finally {
+                    try {
+                        fos.close();
+                        Files.delete(outputFile.toPath());
+                    } catch (IOException ex) {
+                        Logger.getLogger(OpaquePNGToGIFTest.class.
+                                getName()).log(Level.SEVERE, null, ex);
+                    }
+                }
+            }
+        });
+    }
+
+    public synchronized void pass() {
+        testPassed = true;
+        testGeneratedInterrupt = true;
+        mainThread.interrupt();
+    }
+
+    public synchronized void fail() {
+        testPassed = false;
+        testGeneratedInterrupt = true;
+        mainThread.interrupt();
+    }
+
+    public void createAnimatedImage(BufferedImage bi, IIOMetadata metadata,
+            ImageWriter writer, File outputFile) {
+        try {
+
+            fos = new FileOutputStream(outputFile);
+            ImageOutputStream ios = ImageIO.createImageOutputStream(fos);
+            System.out.println(ios);
+            writer.setOutput(ios);
+
+            ImageWriteParam param = writer.getDefaultWriteParam();
+            IIOMetadata streamData = writer.getDefaultStreamMetadata(param);
+
+            writer.prepareWriteSequence(streamData);
+            ImageTypeSpecifier specify = new ImageTypeSpecifier(bi);
+            IIOMetadata imgData = writer.convertImageMetadata(
+                    (IIOMetadata) metadata, specify, param);
+            GIFImageMetadata gifData = setAnimationProperties(imgData);
+            IIOImage iim = new IIOImage(bi, null, gifData);
+            param.setProgressiveMode(param.MODE_DISABLED);
+            writer.writeToSequence(iim, param);
+            writer.endWriteSequence();
+            ios.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public GIFImageMetadata setAnimationProperties(IIOMetadata data) {
+        ArrayList appIDs = new ArrayList();
+        appIDs.add(new String("NETSCAPE").getBytes());
+        ArrayList authCodes = new ArrayList();
+        authCodes.add(new String("2.0").getBytes());
+        ArrayList appData = new ArrayList();
+        byte[] authData = {1, 0, 0};
+        appData.add(authData);
+
+        GIFImageMetadata gifData = (GIFImageMetadata) data;
+        gifData.delayTime = 200;
+        // If we set disposalMethod to 2 then only the issue is reproduced.
+        gifData.disposalMethod = 2;
+        gifData.userInputFlag = false;
+
+        gifData.applicationIDs = appIDs;
+        gifData.authenticationCodes = authCodes;
+        gifData.applicationData = appData;
+
+        return gifData;
+    }
+
+    public static void main(String args[]) throws Exception {
+        OpaquePNGToGIFTest test = new OpaquePNGToGIFTest();
+    }
+
+    class ImageCanvas extends Canvas {
+
+        Image im = null;
+
+        public void setImage(Image img) {
+            im = img;
+        }
+
+        public void clearImage() {
+            im = null;
+            repaint();
+        }
+
+        public void paint(Graphics g) {
+            Graphics2D g2d = (Graphics2D) g;
+
+            if (im != null) {
+                g2d.drawImage(im, 1, 1, getWidth(), getHeight(), this);
+            }
+        }
+    }
+}
+
Binary file jdk/test/java/awt/image/opaque_input.png has changed