Java Forum / GUI / May 2005
Displaying rotated ImageIcon on JScrollPane with menu bar
Jim Gibson - 25 May 2005 02:32 GMT I am writing a large java application (not applet) that needs to display a map image and overlay it with moving object icons. I also want users to be able to zoom in and see detail and scroll around the map, so I am drawing on a JPanel inside a JScrollPane inside a JFrame. I also need to be able to rotate the icons and have a menu bar.
The map consists of approx. 3900 filled polygons. Because of this large number, and because the map does not change, I am creating a BufferedImage, drawing the polygons on that image, then using Graphics2D.drawImage() to draw the map image on the JPanel. To zoom in, I will eventually generate a larger image and redraw the map. For testing, I am making the initail panel just about large enough to show the whole map, then resizing the frame to make it smaller. This part is working OK.
After drawing the map image in the JPanel, I draw an ImageIcon at each position where there should be an object, using a rotational Affine Transformation to rotate the graphics around the icon position (actually the center of the icon and not the origin at the upper left corner). This part is a little flakey.
The problem is that the icons are not always drawn in the correct position. They are sometimes displaced upwards by what looks like the height of the menu bar. This happens when the icons are drawn initially and whenever I manually try to resize the window. However, whenever the window is repainted, the icons are drawn at the correct positions.
The program at the end of this message illustrates the problem. For a map, the program draws two rectangles, one blue and one green. A single object at the middle of the rectangles is drawn rotated right by 30 deg. The window is updated after 20 seconds and every 5 seconds thereafter.
There are several problems with this program's behaviour:
1. The aforementioned behaviour of the icons. Under Linux Red Hat 9, Java 1.5, the icon is misplaced until the first repaint. Under Mac OS X, Java 1.4.2, the icon starts out misplaced, but is quickly redrawn in the correct location. Under both systems, resizing the screen moves the icon until the next repaint is done. This behaviour goes away if either the menu bar does not exist or if the icon is not rotated, and this is why I say the displacement is about equal to the height of the menu bar. It looks like the icon is drawn at a position relative to the upper left corner of the menu bar and not of the panel.
2. The redrawing of the screen when a resizing is done can be very, very slow, sometimes taking 10 seconds. The screen seems to be redrawn at every 1 or 2 pixel locations, instead of just the final screen size.
3. The background of the icon is not transparent as I think it should be.
4. I am required to adjust the drawn position of the icon by the value of the scrollbars. In other words, it looks like the graphics context of the icon is the viewport of the scroll pane, and not the underlying panel, even though I am drawing the icon as part of paintComponent of the JPanel and using the Graphics context provided. This is not necessary when drawing the line and the label string.
Can anyone see what I am doing wrong or explain this behaviour? If not, is there a work-around? Can anyone suggest a better approach that might work better?
Thank you for reading this lengthy post.
// ScrollMap.java
import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.image.*; import javax.swing.*;
public class ScrollMap extends JFrame { JScrollPane scrollpane; MapPanel map; ImageIcon dot; BufferedImage img; Container mainw;
public ScrollMap() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(820,820); mainw = getContentPane(); map = new MapPanel(); map.setBackground(Color.gray); scrollpane = new JScrollPane( map, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS ); mainw.add(scrollpane, BorderLayout.CENTER); JMenuBar menu = new JMenuBar(); JMenu file = new JMenu("File"); menu.add(file); JMenuItem item = new JMenuItem("Exit"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0);} }); file.add(item); setJMenuBar(menu); // comment out and it works setVisible(true); }
public void initialize() {
// create map image int w = map.getWidth(); int h = map.getHeight(); map.setPreferredSize(new Dimension(w,h)); img = new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB); System.out.println("image size ("+w+","+h+") created"); Graphics2D g1 = (Graphics2D)img.getGraphics(); g1.setBackground(new Color(64,64,64)); g1.clearRect(0,0,w,h); Rectangle2D.Float rec = new Rectangle2D.Float(200,200,400,200); g1.setPaint(new Color(0,0,128)); g1.fill(rec); rec.setRect(200,400,400,200); g1.setPaint(new Color(0,128,0)); g1.fill(rec);
// create object icon as skinny oval BufferedImage img2 = new BufferedImage(20,40,BufferedImage.TYPE_INT_RGB); Graphics2D g2 = (Graphics2D)img2.getGraphics(); g2.setBackground(new Color(0,0,0,0)); g2.clearRect(0,0,20,40); Ellipse2D.Float oval = new Ellipse2D.Float(4,0,12,40); g2.setPaint(Color.yellow); g2.fill(oval); dot = new ImageIcon(img2); }
public void repaint() { mainw.repaint();}
public static void main(String[] args) { ScrollMap sm = new ScrollMap(); sm.initialize(); int delay = 20000; while(true) { try{ Thread.sleep(delay); System.out.println("repainting screen"); sm.repaint(); delay = 5000; }catch( InterruptedException e ){} } }
class MapPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g;
// get scrollbar positions int x = scrollpane.getHorizontalScrollBar().getValue(); int y = scrollpane.getVerticalScrollBar().getValue();
// draw map image g2.drawImage(img,0,0,null);
// draw rotated object at (400,400) but displaced by scroll values int px = 400 - x; int py = 400 - y; AffineTransform save = g2.getTransform(); AffineTransform rotate = new AffineTransform(); rotate.setToRotation(0.523599,px,py); g2.setTransform(rotate); // comment out and it works dot.paintIcon(this,g2,px-10,py-20); g2.setTransform(save);
// draw label for object at (400,400) with no displacement Line2D.Float leader = new Line2D.Float(400,400,440,440); g2.setPaint(Color.white); g2.draw(leader); g2.drawString("Object",442,446); } } }
 Signature Jim Gibson
Thomas Weidenfeller - 25 May 2005 09:02 GMT > 1. The aforementioned behaviour of the icons. Under Linux Red Hat 9, > Java 1.5, the icon is misplaced until the first repaint. Under Mac OS [quoted text clipped - 5 lines] > bar. It looks like the icon is drawn at a position relative to the > upper left corner of the menu bar and not of the panel. Your math is wrong. More precisely, you make an assumption about the Graphics g you get in your paint method which is not true.
> 2. The redrawing of the screen when a resizing is done can be very, > very slow, sometimes taking 10 seconds. The screen seems to be redrawn > at every 1 or 2 pixel locations, instead of just the final screen size. Difficult to say what the real reason is without some debugging session. But you have messed up the repaint() mechanism for no apparent reason. This might at least contribute to the problem. You could also try to observer the clipping and do some clipping yourself (only repaint the parts of the image which really need to be re-painted).
> 3. The background of the icon is not transparent as I think it should > be. First mistake: The usage of the Icon API for drawing them. Just keep them as images.
Second mistake: Not using an AlphaComposite to specify, well, alpha composition :-)
> // create object icon as skinny oval > BufferedImage img2 = [quoted text clipped - 7 lines] > dot = new ImageIcon(img2); > } Just keep the image, there is no need to build an Icon out of it.
> public void repaint() { mainw.repaint();} Here you mess up the repaint mechanism.
> AffineTransform save = g2.getTransform(); > AffineTransform rotate = new AffineTransform(); > rotate.setToRotation(0.523599,px,py); Here you assume that you got a Graphics with an affine transform with an origin of (0,0). This is usually not the case at all for lightweight components. All of a container's contents is usually painted by using one and the same Graphics object (of one drawing buffer), where only the AT is adjusted before it is handed down to each component in the container in turn.
This is for example the reason why one is supposed to restore the Graphics object to its original settings before leaving a paint[Component]() method. If you don't do it, the next component in the container might be drawn with settings it shouldn't be drawn with.
setToRotation() ignores the current origin. As the "set" in the name implies, it sets new values. It doesn't take the existing ones into account.
> g2.setTransform(rotate); // comment out and it works Instead of setting a new AT, you need to concatenate with the old AT
> dot.paintIcon(this,g2,px-10,py-20); > g2.setTransform(save); [quoted text clipped - 5 lines] > g2.drawString("Object",442,446); > } You don't reset the Graphics to its original state. E.g. the paint color is left changed. This often does not affect other components, since they usually also set their color, but better save than sorry. If you have many changes, consider working on a copy of the original Graphics object, and not touching the original object at all (create()/dispose()).
/Thomas
 Signature The comp.lang.java.gui FAQ: ftp://ftp.cs.uu.nl/pub/NEWS.ANSWERS/computer-lang/java/gui/faq
Jim Gibson - 25 May 2005 22:54 GMT > > [misplaced icon problem description snipped] > > Your math is wrong. More precisely, you make an assumption about the > Graphics g you get in your paint method which is not true. I am assuming that when I overload paintComponent the method receives a Graphics object for the component for which (0,0) is the upper-left corner and (width,height) is the lower-right corner. Is that not true?
> > 2. The redrawing of the screen when a resizing is done can be very, > > very slow, sometimes taking 10 seconds. The screen seems to be redrawn [quoted text clipped - 5 lines] > observer the clipping and do some clipping yourself (only repaint the > parts of the image which really need to be re-painted). Too much work. I will live with the slow redraws for now. My test program only has six objects, so I don't see how I could speed that up much. It seems that it is drawing the scroll bars that takes a long time.
> > 3. The background of the icon is not transparent as I think it should > > be. > > First mistake: The usage of the Icon API for drawing them. Just keep > them as images. Thanks for that tip. I don't know why I used ImageIcon. I picked it up from a book for a previous, simpler program, and I kept using it. Just using BufferedImage alone seems to have solved my problem.
> Second mistake: Not using an AlphaComposite to specify, well, alpha > composition :-) > > > // create object icon as skinny oval > > BufferedImage img2 = > > new BufferedImage(20,40,BufferedImage.TYPE_INT_RGB); Actually, the mistake I made was using TYPE_INT_RGB when I should have used TYPE_INT_ARGB. That solved the transparency problem.
> > Graphics2D g2 = (Graphics2D)img2.getGraphics(); > > g2.setBackground(new Color(0,0,0,0)); [quoted text clipped - 10 lines] > > Here you mess up the repaint mechanism. Sorry. That method was for a version of the program in which ScrollMap is not a subclass of JFrame but contains a JFrame as a member. Hence the above method. I forgot to take it out when I made ScrollMap extend JFrame.
> > AffineTransform save = g2.getTransform(); > > AffineTransform rotate = new AffineTransform(); [quoted text clipped - 6 lines] > AT is adjusted before it is handed down to each component in the > container in turn. Thanks for this tip. Extending the current transformation was the other key to solving my problem.
The corrected and modified program is shown below, now featuring a moving object updated at 1 Hz, complete with moving leader line and dynamic tag, just like the real maps.
Thanks again for your help. Your suggestions have saved my bacon.
// ScrollMap.java import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.awt.image.*; import javax.swing.*;
public class ScrollMap extends JFrame { JScrollPane scrollpane; MapPanel map; BufferedImage obj; BufferedImage img; Container mainw; static double x = 340; static double y = 400; static int a = -90;
public ScrollMap() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(820,820); mainw = getContentPane(); map = new MapPanel(); map.setBackground(Color.gray); scrollpane = new JScrollPane( map, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS ); mainw.add(scrollpane, BorderLayout.CENTER); JMenuBar menu = new JMenuBar(); JMenu file = new JMenu("File"); menu.add(file); JMenuItem item = new JMenuItem("Exit"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0);} }); file.add(item); setJMenuBar(menu); setVisible(true); }
public void initialize() {
// create map image int w = map.getWidth(); int h = map.getHeight(); map.setPreferredSize(new Dimension(w,h)); img = new BufferedImage(w,h,BufferedImage.TYPE_INT_RGB); System.out.println("image size ("+w+","+h+") created"); Graphics2D g1 = (Graphics2D)img.getGraphics(); g1.setBackground(new Color(64,64,64)); g1.clearRect(0,0,w,h); Rectangle2D.Float rec = new Rectangle2D.Float(200,200,400,200); g1.setPaint(new Color(0,0,128)); g1.fill(rec); rec.setRect(200,400,400,200); g1.setPaint(new Color(0,128,0)); g1.fill(rec);
// create object image obj = new BufferedImage(20,10,BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = (Graphics2D)obj.getGraphics(); g2.setBackground(new Color(0,0,0,0)); g2.clearRect(0,0,20,10); Ellipse2D.Float oval = new Ellipse2D.Float(0,2,20,6); g2.setPaint(Color.yellow); g2.fill(oval); }
public static void main(String[] args) { ScrollMap sm = new ScrollMap(); sm.initialize(); try{ while(true) { Thread.sleep(1000); double r = Math.toRadians(a); x += Math.cos(r); y += Math.sin(r); a = (a+1)%360; sm.repaint(); } }catch( InterruptedException e ){} }
class MapPanel extends JPanel { public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; g2.drawImage(img,0,0,null); int px = (int)x; int py = (int)y;
// fetch and copy the current transformation AffineTransform save = g2.getTransform(); AffineTransform rotate = new AffineTransform(save);
// draw object rotate.rotate(Math.toRadians(a),px,py); g2.setTransform(rotate); g2.drawImage(obj,px-10,py-5,null);
// restore old transformation g2.setTransform(save);
// draw label for object Line2D.Float leader = new Line2D.Float(px,py,px+40,py+40); g2.setPaint(Color.red); g2.draw(leader); g2.setPaint(Color.white); String tag = "(" + px + "," + py + "," + (a+90)%360 + ")"; g2.drawString("Object",px+42,py+40); g2.drawString(tag,px+42,py+54); } } }
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|