Java2D paintComponent optimization question
Hi All,
Any help with this would be greatly appreciated. I'm attempting to set up a Panel with a series of small images which is fairly dynamic. I'm using paintComponent for the panel to make it dynamic on resize and on the images so that I can modify the images and make some semi-transparent, etc... I'm finding issues when I make the window small and scroll around the content. There's a lot of artifacts hanging around and many of the images don't show up at all. It's essentially really flaky. Simplified sample code below, does anyone have advice on fixing?
Thanks!
Mike
CentralPane.java:
packagedefault;
import javax.swing.*;
import java.awt.*;
import java.util.*;
publicclass CentralPaneextends JPanel{
private Vector images;
private Toolkit kit;
public CentralPane()
{
kit = Toolkit.getDefaultToolkit();
images =new Vector();
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
images.add("c:\\sf.jpg");
}
privatevoid drawImages()
{
int startLeft = 15;
int imgSpacing = 33;
int rowSpacing = 41;
int heightSpacing = 139;
int imgWidth = 71;
int imgHeight = 101;
int imgOffset = 16;
int startTop = heightSpacing - (imgHeight+imgOffset);
int imgsPerRow = (int)(((double)(this.getWidth()-(startLeft*2)))/((double)(imgSpacing+imgWidth)));
if (imgsPerRow < 1)
imgsPerRow = 1;
int numRows = (int)((double)images.size()/(double)imgsPerRow);
int panelHeight = heightSpacing*numRows;
int imgsLastRow = images.size() % imgsPerRow;
this.setPreferredSize(new Dimension(this.getWidth(), panelHeight));
int panelWidth = this.getWidth();
if (imgsLastRow > 0 )
numRows++;
for (int i=0;i<numRows;i++)
{
int imgsInRow = (i==numRows-1)?imgsLastRow:imgsPerRow;
for (int j=0;j<imgsInRow;j++)
{
ImagePane myImage =new ImagePane((String)images.get((i*imgsPerRow)+j), i,j);
myImage.setBounds(startLeft+(j*(imgSpacing+imgWidth)), startTop+(i*heightSpacing), imgWidth, imgHeight);
this.add(myImage);
}
}
}
publicstaticvoid main (String arg[])
{
CentralPane pane =new CentralPane();
JScrollPane jScrollPane1 =new javax.swing.JScrollPane();
jScrollPane1.setViewportView(pane);
JFrame frame =new JFrame();
frame.setContentPane(jScrollPane1);
frame.pack();
frame.setBounds(10,10, 600,600);
frame.setVisible(true);
}
@Override
protectedvoid paintComponent(Graphics g){
System.out.println("Repainting Central Pane");
super.paintComponent(g);
this.regenerateImages();
}
privatevoid regenerateImages()
{
this.removeAll();
this.drawImages();
}
}
ImagePane.java :
packagedefault;
import java.awt.AlphaComposite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
publicclass ImagePaneextends JPanel{
private Image myImage;
privateint row;
privateint col;
public ImagePane (String filename,int r,int c)
{
row = r;
col = c;
myImage =new ImageIcon(filename).getImage().getScaledInstance(71, 101, Image.SCALE_DEFAULT);
// Every second one should be semi-transparent
if (c % 2 == 0)
{
BufferedImage bi =new BufferedImage(71, 101,
BufferedImage.TYPE_INT_ARGB);// ARGB to support transparency if in original image
Graphics2D g2 = bi.createGraphics();
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 0.5f));
g2.drawImage(myImage, 0, 0,null);
myImage =new ImageIcon(bi).getImage();
}
}
@Override
protectedvoid paintComponent(Graphics g){
// TODO Auto-generated method stub
System.out.println("Repainting Image Pane - R:"+row+" C:"+col);
g.drawImage(myImage, 0, 0,this);
}
}
>
# 1
This concerns basic design. You can do this one of two general ways: draw the images in
paintComponent or use components. But not both.
You are removing, resizing and adding components in your paintComponent code. This won't
work well. Also, getScaledInstance is extremely slow compared to BufferedImage scaling.
The image returned from the method loads asynchronously, as does those returned from
getImage in the beginning.
To do this in your drawing code, get rid of the image component, load your images, and
draw them in paintComponent. You can create and use an array of Rectangles to locate the
images. Relocate and resize the rectangles for componentResized events, not for every trip
through paintComponent.
For the component approach do not override either paintComponent or paint in the container
that will host the image components. You can resize and relocate the image components for
componentResized events.
Using newer BufferedImage methods will speed things up a lot.
Images with non-opaque color run slower than opaque images.
Here's an example of using drawing code for this:
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.*;
import javax.imageio.ImageIO;
import javax.swing.*;
public class FasterImages extends JPanel {
BufferedImage[] images;
BufferedImage[] scaled;
Rectangle[] rects;
AlphaComposite ac;
public FasterImages(BufferedImage[] images) {
this.images = images;
rects = new Rectangle[images.length];
for(int j = 0; j < rects.length; j++)
rects[j] = new Rectangle();
int rule = AlphaComposite.SRC_OVER;
float alpha = 0.5f;
ac = AlphaComposite.getInstance(rule, alpha);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
if(scaled == null)
initScaledImages();
Composite plain = g2.getComposite();
for(int j = 0; j < scaled.length; j++) {
if(j % 2 == 0)
g2.setComposite(plain);
else
g2.setComposite(ac);
g2.drawImage(scaled[j], rects[j].x, rects[j].y, this);
}
}
private void initScaledImages() {
scaled = new BufferedImage[images.length];
int w = ((JViewport)getParent()).getExtentSize().width;
int startLeft = 15;
int imgSpacing = 33;
int rowSpacing = 41;
int heightSpacing = 139;
int imgWidth = 71;
int imgHeight = 101;
int imgOffset = 16;
int startTop = heightSpacing - (imgHeight+imgOffset);
int imgsPerRow = (w-(startLeft*2))/(imgSpacing+imgWidth);
if (imgsPerRow < 1)
imgsPerRow = 1;
int numRows = images.length / imgsPerRow;
int imgsLastRow = images.length % imgsPerRow;
if (imgsLastRow > 0 )
numRows++;
int panelHeight = heightSpacing * numRows;
setPreferredSize(new Dimension(w, panelHeight));
//int panelWidth = this.getWidth();
for (int j = 0, count = 0; j < numRows; j++) {
int imgsInRow = (j == numRows-1) ? imgsLastRow : imgsPerRow;
for (int k = 0; k < imgsInRow; k++) {
scaled[count] = scale(images[count], imgWidth, imgHeight);
rects[count++].setFrame(startLeft+(k*(imgSpacing+imgWidth)),
startTop+(j*heightSpacing),
imgWidth, imgHeight);
}
}
revalidate();
}
private BufferedImage scale(BufferedImage src, int w, int h) {
BufferedImage scaled = new BufferedImage(w, h, src.getType());
Graphics2D g2 = scaled.createGraphics();
g2.setBackground(getBackground());
g2.clearRect(0,0,w,h);
double xScale = (double)w / src.getWidth();
double yScale = (double)h / src.getHeight();
double scale = Math.min(xScale, yScale);
double x = (w - scale*src.getWidth())/2;
double y = (h - scale*src.getHeight())/2;
AffineTransform at = AffineTransform.getTranslateInstance(x, y);
at.scale(scale, scale);
g2.drawRenderedImage(src, at);
g2.dispose();
return scaled;
}
public static void main(String[] args) throws IOException {
String[] ids = {
"--", "-c", "-cg--", "-c-h-", "-c--t", "--g--", "h-", "-t"
};
BufferedImage[] images = new BufferedImage[ids.length];
for(int j = 0; j < images.length; j++)
images[j] = ImageIO.read(new File("images/geek" + ids[j] + ".gif"));
final FasterImages test = new FasterImages(images);
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new JScrollPane(test));
f.setSize(400,400);
f.setLocation(200,200);
f.setVisible(true);
EventQueue.invokeLater(new Runnable() {
public void run() {
test.addComponentListener(test.resizer);
}
});
}
private ComponentListener resizer = new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
scaled = null;
repaint();
}
};
}