6334602: Animated GIFs created from opaque PNG image frames appear transparent when loaded with Toolkit APIs
Reviewed-by: serb, prr
--- 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