Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / GUI / May 2005

Tip: Looking for answers? Try searching our database.

Displaying rotated ImageIcon on JScrollPane with menu bar

Thread view: 
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 Magazines

Get 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 ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.