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 / August 2004

Tip: Looking for answers? Try searching our database.

Rendering performance while dragging

Thread view: 
sh_java_int - 27 Aug 2004 01:31 GMT
Hi all,

I wrote the following code which implements a custom panel with
rectangles drawn on it, which can be moved or stretched using the
mouse. To move a rectangle, click inside the rectangle and drag. To
stretch a rectangle, drag its left or right edge. The code works, but
I'm not very satisfied with the performance while dragging. If some of
you experts could improve the performance I'd be very interested in
learning the techniques.

The problem is clearly that while dragging, a number of repaints get
issued and each repaint paints everything which takes a while. One
solution is clearly to reduce the time of the repaint, but I'm more
interested in solutions which, while dragging, repaint in a special
way.

I've tried several such solutions, none has been quite satisfactory;
either it doesn't improve performance, or the dragged rectangle leaves
a trail while dragging.

Here's the code. Thanks a lot in advance for your help.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;

public class DragPerformance extends JPanel {

   int minArc = 10;
   int maxArc = 20;

   Vector rectList = new Vector();
   RectData draggedEvent;

   public static void main(String[] args) {
       DragPerformance dp = new DragPerformance();
       dp.setPreferredSize(new Dimension(600, 600));
       int num = 60;
       int offset = 600/num;
       for (int i = 0; i < 600; i+= offset) {
           dp.add("This is rectangle " + i, i, i, 100, 15);
       }
       JFrame fr = new JFrame();
       fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       fr.getContentPane().add(dp);
       fr.pack();
       fr.show();
   }

   public DragPerformance() {
       addMouseListener(new DragMouseListener(this));
   }

   public void add(String name, int x, int y, int w, int h) {
       RectData rd = new RectData();
       rd.x = x; rd.y = y; rd.w = w; rd.h = h;
       rd.name = name;
       rd.editable = true;
       rectList.add(rd);
   }

   public void paintComponent(Graphics g) {
       paintPanelBackground(g);
       paintEvents(g);
   }

   public void paintEvents(Graphics g) {
       int sz = rectList.size();
       for (int i = 0; i < sz; i++) {
           RectData rd = (RectData) rectList.get(i);
           if (rd == draggedEvent) continue;
           renderEvent(g, rd, false);            
       }
       if (draggedEvent != null) {
           renderEvent(g, draggedEvent, true);
       }
   }

   AlphaComposite ac =
   AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);

   protected void renderEvent(Graphics g,
                              RectData ed, boolean isDragged) {
       Composite comp = null;
       
       if (isDragged) {
           Graphics2D g2 = (Graphics2D) g;
           comp = g2.getComposite();
           g2.setComposite(ac);
       }
       
       int arc = ed.w/2;
       if (arc < minArc) arc = minArc;
       if (arc > maxArc) arc = maxArc;

       renderBackground(g, ed, isDragged, arc);
       g.setColor(Color.black);

       renderText(g, ed);
       
       if (isDragged) {
           ((Graphics2D)g).setComposite(comp);
       }
       
   }

   Color BACKGROUND = new Color(200, 200, 50);

   private void renderBackground(Graphics g, RectData ed, boolean
isDragged,
                                 int arc) {
       Color col = BACKGROUND;

       GradientPaint gp = createGradient(ed.x, ed.y, ed.w, ed.h,
                                         col, col.brighter());

       
       Graphics2D g2d = (Graphics2D)g;
       
       Paint p = g2d.getPaint();
       g2d.setPaint(gp);
       g2d.fillRoundRect(ed.x, ed.y, ed.w, ed.h, arc, arc);
       g2d.setPaint(p);

       Color borderColor = col;
       g.setColor(borderColor);
       g.drawRoundRect(ed.x, ed.y, ed.w, ed.h, arc, arc);
   }

   private GradientPaint createGradient(int x, int y, int w, int h,
                                               Color startCol, Color
endCol) {

       Color start = startCol;
       Color end = endCol;
       int endx = x+w;
       int endy = y;
       GradientPaint gp = new GradientPaint(x,y, start,
                                            endx, endy, end);

       return gp;
   }

   static int YOFF = 15;
   static int XOFF = 5;

   protected void renderText(Graphics g,
                             RectData ed) {
       g.setFont(getFont());
       renderMultiLineTextInBox(g, ed.name, ed.x+4, ed.y, ed.w-8,
ed.h);

   }

   static JTextArea _ta = new JTextArea();
   static Dimension _tadim = new Dimension();
   synchronized public static void renderMultiLineTextInBox(Graphics
g,
                                                       String text,
                                                       int x, int y,
                                                       int w, int h)
{
       if (w <= 0 || h <= 0) return;
       _ta.setLineWrap(true);
       _ta.setWrapStyleWord(true);
       _ta.setOpaque(false);
       _ta.setFont(g.getFont());
       _ta.setText(text);
       _tadim.setSize(w, h);
       _ta.setSize(_tadim);
       Graphics ga = g.create(x,y,w,h);
       _ta.getUI().paint(ga, _ta);
       Dimension d = _ta.getPreferredSize();
       if (d.height > h + 10) {
           ga.setColor(Color.red);
           ga.fillRect(w-4,h-4,4,4);
       }

   }

   public void paintPanelBackground(Graphics g) {
       
       Dimension d = getSize();
       g.setColor(Color.white);
       g.fillRect(0, 0, d.width, d.height);
       g.setColor(Color.gray);

       int xinc = d.width/10;
       int yinc = d.height/10;
       for (int x = 0; x < d.width; x+= xinc) {
           g.drawLine(x, 0, x, d.height);
       }
       for (int y = 0; y < d.width; y+= yinc) {
           g.drawLine(0, y, d.width, y);
       }
   }

   RectData getEventContaining(int x, int y) {
       int lx = x;
       int ly = y;
       int sz = rectList.size();
       RectData event = null;
       for (int i = 0; i < sz; i++) {
           RectData ed = (RectData) rectList.get(i);
           if (ed.contains(lx,ly)) {
               event = ed;
           }
       }
       return event;
   }

}

class DragMouseListener implements MouseListener,
MouseMotionListener {
   static protected int DRAG_NONE= 0;
   static protected int DRAG_MOVE= 1;
   static protected int DRAG_START_TIME= 2;
   static protected int DRAG_END_TIME = 3;

   static private final Cursor defaultCursor =
   Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
   static private final Cursor leftDragCursor =
   Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR);
   static private final Cursor rightDragCursor =
   Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
   static private final Cursor upDragCursor =
   Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR);
   static private final Cursor downDragCursor =
   Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR);
   static private final Cursor wholeDragCursor =
   Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);

   protected Point lastPress;
   protected Point lastDragPosition;
   protected int eventDragOffsetX;
   protected int eventDragOffsetY;

   protected int dragState;

   DragPerformance view;

   public DragMouseListener(DragPerformance view) {
       this.view = view;
   }

   public void mouseClicked(MouseEvent e) {
   }
   
   public void mousePressed(MouseEvent e) {
       lastPress = e.getPoint();
       
   }
   
   public void mouseReleased(MouseEvent e) {
       if (dragState != DRAG_NONE) {
           view.repaint();
       }
       setDragState(DRAG_NONE);
       view.draggedEvent = null;
   }
   
   public void mouseEntered(MouseEvent e) {
       view.addMouseMotionListener(this);
   }

   public void mouseExited(MouseEvent e) {
       view.removeMouseMotionListener(this);
       cancelDrag();
   }

   private void cancelDrag() {
       setDragState(DRAG_NONE);
       if (view.draggedEvent != null) {
           view.draggedEvent.restoreGeometry();
       }
   }

   public void mouseDragged(MouseEvent e) {
       if (dragState == DRAG_NONE) {
           setDragState(setDragType());
           if (dragState == DRAG_MOVE) {
               eventDragOffsetX =
                   e.getX() - view.draggedEvent.x;
               eventDragOffsetY =
                   e.getY() - view.draggedEvent.y;
           }
       }
       if (dragState != DRAG_NONE) {
           updateViewForDrag(e);
       }
       lastDragPosition = e.getPoint();
   }

   private void updateViewForDrag(MouseEvent e) {
       int x = e.getX();
       int y = e.getY();
       if (dragState == DRAG_START_TIME) {
           setDraggedEventStart(x, y);
       } else if (dragState == DRAG_END_TIME) {
           setDraggedEventEnd(x, y);
       } else if (dragState == DRAG_MOVE) {
           setDraggedEventPosition(x, y);
       }
       view.repaint();
   }

   private void setDraggedEventPosition(int x, int y) {
       int px = x - eventDragOffsetX;
       int py = y - eventDragOffsetY;
       view.draggedEvent.x = px;
       view.draggedEvent.y = py;
   }
   
   private void setDraggedEventEnd(int x, int y) {
       int lx = x;
       if (lx < view.draggedEvent.x) {
           setDragState(DRAG_START_TIME);
           setDraggedEventStart(x, y);
       } else {
           view.draggedEvent.w = lx - view.draggedEvent.x;
       }
   }
   
   private void setDraggedEventStart(int x, int y) {
       int lx = x;
       if (lx > view.draggedEvent.x + view.draggedEvent.w) {
           setDragState(DRAG_END_TIME);
           setDraggedEventEnd(x, y);
       } else {
           RectData ed = view.draggedEvent;
           int end = ed.x + ed.w;
           view.draggedEvent.x = lx;
           view.draggedEvent.w = end - lx;
       }
   }
   
   protected int setDragType() {
       int x = lastPress.x;
       int y = lastPress.y;
       RectData ed = view.getEventContaining(x, y);
       if (ed != null && ed.isEditable()) {
           int lx = x;
           int ly = y;

           ed.saveGeometry();
           view.draggedEvent = ed;
           if (ed.nearStart(lx,ly)) {
               return DRAG_START_TIME;
           } else if (ed.nearEnd(lx,ly)) {
               return DRAG_END_TIME;
           } else {
               return DRAG_MOVE;
           }
       }
       return DRAG_NONE;        
   }

   public void mouseMoved(MouseEvent e) {
       int x = e.getX();
       int y = e.getY();
       RectData ed = view.getEventContaining(x, y);
       if (ed != null && ed.isEditable()) {
           int lx = x;
           int ly = y;

           if (ed.nearStart(lx,ly)) {
               setStartDragCursor();
           } else if (ed.nearEnd(lx, ly)) {
               setEndDragCursor();
           } else {
               setMoveCursor();
           }
       } else {
           setDefaultCursor();
       }
       
   }

   
   private void setStartDragCursor() {
       view.setCursor(leftDragCursor);
   }
   
   
   private void setEndDragCursor() {
       view.setCursor(rightDragCursor);
   }
   
   
   private void setMoveCursor() {
       view.setCursor(wholeDragCursor);
       
   }
   
   
   private void setDefaultCursor() {
       view.setCursor(defaultCursor);        
   }
   

   private void setDragState(int state) {
       dragState = state;
       if (state != DRAG_NONE) {
           //view.setInDrag(true);
       } else {
           //view.setInDrag(false);
       }
   }

}

class RectData {
   
   static private int START_END_NEAR = 5;

   // temp
   String name;

   // Logical values of x and y
   public int x;
   public int y;
   public int h;
   public int w;

   private boolean geometrySaved;

   private int save_x;
   private int save_y;
   private int save_h;
   private int save_w;

   public boolean editable;
   
   public int z;
   
   
   public void restoreGeometry() {
       if (geometrySaved) {
           x = save_x;
           y = save_y;
           w = save_w;
           h = save_h;
           geometrySaved = false;
       }
   }

   public void saveGeometry() {
       save_x = x;
       save_y = y;
       save_w = w;
       save_h = h;
       geometrySaved = true;
   }

   public void setEditable(boolean b) {
       editable = b;
   }

   public boolean isEditable() {
       return editable;
   }

   public boolean nearEnd(int lx, int ly) {
       int gap = 100;
       gap = x+w - lx;
       return (START_END_NEAR > Math.abs(gap));

   }

   public boolean nearStart(int lx, int ly) {
       int gap = 100;
       gap = x - lx;
       return (START_END_NEAR > Math.abs(gap));
   }

   public boolean contains(int lx, int ly) {
       return (!(lx < x || lx > x + w ||
                 ly < y || ly > y + h));
   }

   public boolean intersects(RectData ed) {
       return ( !(x+w < ed.x || x > ed.x + ed.w ||
                  y+h < ed.y || y > ed.y + ed.h));
   }

}
Larry Barowski - 27 Aug 2004 09:11 GMT
> Hi all,
>
[quoted text clipped - 11 lines]
> interested in solutions which, while dragging, repaint in a special
> way.

Every time the rectangle changes, call repaint(int, int, int, int)
twice, once with the old bounds and once with the new bounds.

You can also use an Image as a buffer for everything that is not
being dragged. When the drag state changes (start drag, end
drag), paint everything but the active item to the buffer. For
paintComponent(), draw the buffer followed by the active item,
if any.
ak - 27 Aug 2004 12:40 GMT
> I wrote the following code which implements a custom panel with
> rectangles drawn on it, which can be moved or stretched using the
[quoted text clipped - 13 lines]
> either it doesn't improve performance, or the dragged rectangle leaves
> a trail while dragging.

your problem is that you draws every time all your rects.
instead you should determine "damaged" area and change 2 methods:

Rectangle damagedArea;

public void paintComponent(Graphics g) {
       if (draggedEvent != null) {
           g.setClip(damagedAred);
       }
       paintPanelBackground(g);
       paintEvents(g);
   g.setClip(null);
   }

   public void paintEvents(Graphics g) {
       int sz = rectList.size();
                   for (int i = 0; i < sz; i++) {
               RectData rd = (RectData) rectList.get(i);
               if (rd == draggedEvent) continue;
               if (draggedEvent == null || damagedArea.intersects(new
Rectangle(rd.x, rd.y, rd.w, rd.h))) {
                     renderEvent(g, rd, false);
           }
       }
       if (draggedEvent != null) {
           renderEvent(g, draggedEvent, true);
       }
   }

Signature

Andrei Kouznetsov
http://uio.dev.java.net Unified I/O for Java
http://reader.imagero.com Java image reader

Larry Barowski - 27 Aug 2004 19:46 GMT
> your problem is that you draws every time all your rects.
> instead you should determine "damaged" area and change 2 methods:
[quoted text clipped - 4 lines]
>         if (draggedEvent != null) {
>             g.setClip(damagedAred);

This is not a good idea. For one thing, paint events
may be delayed. You may get two drag events
before there is one paint event. The damaged
area computed during the first drag will be lost.
Also, the real "damaged area" may include
more than just the rectangle. Suppose some small
window from another application pops up over
your application while you are dragging, then pops
back down on its own. The area it covered will not
be repainted using this method.

Better to limit the repaint area on the front end
using repaint(int, int, int, int). Note that calling
repaint(int, int, int, int) twice, once with the old
rectangle bounds and once with the new bounds,
will usually result in just one paint event with
the clip set to the smallest rectangle covering
the two. There is no need to do such calculations
yourself.
ak - 27 Aug 2004 21:47 GMT
> This is not a good idea. For one thing, paint events
> may be delayed. You may get two drag events
[quoted text clipped - 15 lines]
> the two. There is no need to do such calculations
> yourself.

thats right, however you can instead of view.repaint() call
view.paintImmediately(damagedArea);
also you can save your damaged areas in some list and check d.areas for
intersection while repainting.

May be it is better on first drag event paint component into offscreen
image, and then on further events
u have to draw this image and draggedEvent.

Signature

Andrei Kouznetsov
http://uio.dev.java.net Unified I/O for Java
http://reader.imagero.com Java image reader

sh_java_int - 27 Aug 2004 21:15 GMT
Thanks very much for your suggestions.

To complete the picture I just wanted to report on the effects of the
suggestions. There were two suggestions

1) While dragging, clip the Graphics object to the "damaged" area and
draw only the rectangles that intersect the damaged area.

Method 1 results in no perceptible improvement.

2) While dragging, draw everything except the dragged rectangle from a
stored Image object, and draw the dragged rectangle as usual.

Method 2 gives responsive dragging, with the caveat that there is a
longish hesitation period at the start of the drag. This is because
the background image is constructed at the start of the drag. Once the
background image has been constructed, dragging is fast and
responsive.

All in all, I think I'll go with method 2.
ak - 27 Aug 2004 21:42 GMT
> 2) While dragging, draw everything except the dragged rectangle from a
> stored Image object, and draw the dragged rectangle as usual.
[quoted text clipped - 4 lines]
> background image has been constructed, dragging is fast and
> responsive.

note, that you need to create background image only if size of view changes,
otherwise
reuse existing one.

Signature

Andrei Kouznetsov
http://uio.dev.java.net Unified I/O for Java
http://reader.imagero.com Java image reader

sh_java_int - 28 Aug 2004 06:54 GMT
> note, that you need to create background image only if size of view changes,
> otherwise
> reuse existing one.

For the record, this isn't quite right. The background image has to be
computed whenever the view size changes, but it also has to be
computed whenever the dragged object changes, which is just about
every drag.
ak - 28 Aug 2004 09:28 GMT
> > note, that you need to create background image only if size of view changes,
> > otherwise
[quoted text clipped - 4 lines]
> computed whenever the dragged object changes, which is just about
> every drag.

Offscreen image should be created only if size changes - better only is size
of view bigger then size of image,
and you should paint your view into it every time drag object changes.
Second one is not really expensive.

Signature

Andrei Kouznetsov
http://uio.dev.java.net Unified I/O for Java
http://reader.imagero.com Java image reader

Larry Barowski - 29 Aug 2004 05:52 GMT
> Thanks very much for your suggestions.
>
[quoted text clipped - 7 lines]
> background image has been constructed, dragging is fast and
> responsive.

I wouldn't expect much delay on modern
hardware. How many rectangles are you
dealing with?

Note that you only need to repaint the part of the
background image covered by the rectangle that
will be dragged. You can also save some time by
not drawing rectangles that don't intersect the
clipping bounds - the computations to do that
take no significant time compared to actually
drawing the rectangles.

Also, you may want to show the rectangle in a
different color, or something like that, when it
is being dragged. That will cause less frustration
for the user, since they will know when the
delay is over.
Jacob - 28 Aug 2004 14:05 GMT
> I wrote the following code which implements a custom panel with
> rectangles drawn on it, which can be moved or stretched using the
[quoted text clipped - 3 lines]
> you experts could improve the performance I'd be very interested in
> learning the techniques.

Unless this is an excercise you do in order to learn
Java 2D, I suggest you look at libraries that handle
concepts like vector graphics, damage, double buffering,
background image and interactions already.

There are several; I know http://geosoft.no/graphics/


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.