VolatileImages render incorrectly

Jessica Finley jfinley at tech4learning.com
Mon Jun 24 11:09:49 PDT 2013


Hiya folks,

I've run into a couple of problems with OpenJDK on Mac.  Firstly, and what started me down this path, is the performance of rendering BufferedImages seems to have really tanked.  It's as if they are not supported by hardware anymore (from vague references I've seen around the web, I understand that BufferedImages are supposed to be hardware accelerated.. and based on the performance on Windows (java 7) and Mac Java 6, I can believe that).  In OpenJDK 7 and 8, rendering lots of BufferedImages causes performance to truly and horribly suffer on certain Macs (the Macs with the low end graphics cards really seem to show this, but it's noticeable on all Macs I've tested on).

In an effort to speed up our software, I started using VolatileImages instead of BufferedImages in certain places and it has brought performance up to awesome.  However, I noticed the problem that I describe in Sun Bug Id: 9004409.  When I paint VolatileImage.getSnapshot() to either the screen graphics or to a BufferedImage, the transparent pixels are rendered incorrectly - they have black values instead of transparent values, as if the transparent pixels were first rendered over a solid black background and then the result is what is copied out to the BufferedImage.

Now, if I render the VolatileImage directly to the screen graphics, I don't see the problem, but any way I can figure out how to render the VolatileImage to or as a BufferedImage results in incorrect transparent pixels.

Below is a sample program that demonstrates the problem and compares it to BufferedImages which always render correctly.  

This problem only occurs on Mac Java 7 and 8… all works as expected on Windows Java 7 and Mac Java 6.

Any thoughts on this?  Is there a work around anyone knows of?  Also, am I correct in my conjecture that BufferedImages are supposed to be hardware accelerated, but they are not in OpenJDK?

Thanks for your time,
Jess

----------------------------------------

import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

/**
 * Shows the difference between the painting of a translucent BufferedImage and a translucent
 * VolatileImage.
 * 
 * 
 * On Java 6, this works fine.  On Java 7, the VolatileImages are not painted correctly, when painted
 * onto a BufferedImage prior to being painted to screen graphics.
 * 
 * @author jfinley
 *
 */
public class PaintTest {

	public PaintTest() {
		
		JPanel bufferedImagePanel = new JPanel() {
			protected void paintComponent(java.awt.Graphics g) {
				super.paintComponent(g);
				BufferedImage image = getBufferedImage();
				g.drawImage(image, 0, 0, null);
			};
		};

		JPanel bufferedImagePanel2 = new JPanel() {
			protected void paintComponent(java.awt.Graphics g) {
				super.paintComponent(g);
				BufferedImage bImage = getBufferedImage();
				
				BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
				Graphics2D g2 = image.createGraphics();
				
				//not really necessary, but in there for completeness
				g2.setComposite(AlphaComposite.Clear);
				g2.setColor(new Color(255, 255, 255, 0));
				g2.fillRect(0,0, image.getWidth(), image.getHeight());
				
				g2.setComposite(AlphaComposite.SrcOver);
				g2.drawImage(bImage,  0, 0, null);
				g2.dispose();
				
				g.drawImage(image, 0, 0, null);
			};
		};
		
		JPanel volatileImagePanel = new JPanel() {
			protected void paintComponent(java.awt.Graphics g) {
				super.paintComponent(g);
				VolatileImage vImage = getVolatileImage();
				
				/**
				 * If we draw the VolatileImage directly to g, it works fine... but
				 * if we draw VolatileImage.getSnapshot(), or otherwise convert the VolatileImage
				 * to a buffered image, we get the black halo funk of death.
				 */
				g.drawImage(vImage, 0, 0, null); //works
				//g.drawImage(vImage.getSnapshot(), 0, 0, null); //black halo funk of death
			};
		};
		
		JPanel volatileImagePanel2 = new JPanel() {
			protected void paintComponent(java.awt.Graphics g) {
				super.paintComponent(g);
				VolatileImage vImage = getVolatileImage();
				
				/**
				 * If we draw the VolatileImage directly to g, it works fine... but
				 * if we draw VolatileImage.getSnapshot(), or otherwise convert the VolatileImage
				 * to a buffered image, we get the black halo funk of death.
				 */
				//g.drawImage(vImage, 0, 0, null); //works
				g.drawImage(vImage.getSnapshot(), 0, 0, null); //black halo funk of death
			};
		};
		
		JPanel volatileImagePanel3 = new JPanel() {
			protected void paintComponent(java.awt.Graphics g) {
				super.paintComponent(g);
				VolatileImage vImage = getVolatileImage();
				
				/**
				 * If we draw the VolatileImage directly to g, it works fine... but
				 * if we draw VolatileImage.getSnapshot(), or otherwise convert the VolatileImage
				 * to a buffered image, we get the black halo funk of death.
				 */
				//g.drawImage(vImage, 0, 0, null); //works
				//g.drawImage(vImage.getSnapshot(), 0, 0, null); //black halo funk of death
				
				BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
				Graphics2D g2 = image.createGraphics();
				
				//not really necessary, but in there for completeness
				g2.setComposite(AlphaComposite.Clear);
				g2.setColor(new Color(255, 255, 255, 0));
				g2.fillRect(0,0, image.getWidth(), image.getHeight());
				
				g2.setComposite(AlphaComposite.SrcOver);
				g2.drawImage(vImage,  0, 0, null);
				g2.dispose();
				
				g.drawImage(image, 0, 0, null);
			};
		};

		final JPanel mainPanel = new JPanel(new GridBagLayout());
		GridBagConstraints gbc = new GridBagConstraints(0, 1, 1, 1, 0, 0, 
				GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0);

		mainPanel.add(new JLabel("Direct to g"), gbc);
		gbc.gridy++;
		mainPanel.add(new JLabel("BufferImage Proxy"), gbc);
		gbc.gridy++;
		mainPanel.add(new JLabel("BufferImage Proxy 2"), gbc);
		
		gbc.gridx = 1; gbc.gridy = 0;
		mainPanel.add(new JLabel("BufferedImages"), gbc);
		gbc.gridx++;
		mainPanel.add(new JLabel("VolatileImages"), gbc);

		gbc.gridx = 1; gbc.gridy = 1; gbc.weightx = 1; gbc.weighty = 1;
		mainPanel.add(bufferedImagePanel, gbc);
		gbc.gridy++;
		mainPanel.add(bufferedImagePanel2, gbc);
		gbc.gridx = 2; gbc.gridy = 1;
		mainPanel.add(volatileImagePanel, gbc);
		gbc.gridy++;
		mainPanel.add(volatileImagePanel2, gbc);
		gbc.gridy++;
		mainPanel.add(volatileImagePanel3, gbc);

		
		JButton repaintButton = new JButton("Repaint");
		repaintButton.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e) {
				mainPanel.repaint();
			}
		});
		
		gbc.gridx = 1; gbc.gridy++; gbc.gridwidth = 2; gbc.weighty = 0;
		mainPanel.add(repaintButton, gbc);


		JFrame mainFrame = new JFrame("O.M.G.");
		mainFrame.getContentPane().setLayout(new BorderLayout());
		mainFrame.getContentPane().add(mainPanel, BorderLayout.CENTER);
		mainFrame.setPreferredSize(new Dimension(400, 400));
		mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		mainFrame.pack();
		mainFrame.setVisible(true);
		
	}
	
	private BufferedImage getBufferedImage() {
		BufferedImage image = new BufferedImage(100, 100, BufferedImage.TRANSLUCENT);
		Graphics2D g = image.createGraphics();
		
		//not really necessary, in here for completeness
		g.setComposite(AlphaComposite.Clear);
		g.setColor(new Color(255, 255, 255, 0));
		g.fillRect(0,0, image.getWidth(), image.getHeight());
		
		g.setComposite(AlphaComposite.SrcOver);
		g.setColor(new Color(255, 0, 0, 128));
		g.fillRect(10, 10, image.getWidth()-20, image.getHeight()-20);

		g.dispose();
		return image;
	}

	private VolatileImage getVolatileImage() {
		GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
		VolatileImage vImage = gc.createCompatibleVolatileImage(100, 100, Transparency.TRANSLUCENT);
		Graphics2D g = vImage.createGraphics();
		
		//necessary, as VolatileImages start out filled opaque white
		g.setComposite(AlphaComposite.Clear);
		g.setColor(new Color(255, 255, 255, 0));
		g.fillRect(0,0, vImage.getWidth(), vImage.getHeight());
		
		g.setComposite(AlphaComposite.SrcOver);
		g.setColor(new Color(255, 0, 0, 128));
		g.fillRect(10, 10, vImage.getWidth()-20, vImage.getHeight()-20);

		g.dispose();
		return vImage;
	}

	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable(){
			public void run() {
				new PaintTest();
			}
		});
	}
}


More information about the macosx-port-dev mailing list