Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-4486696

JViewport does not scroll diagonally efficiently

XMLWordPrintable



      Name: boT120536 Date: 07/31/2001


      lowe% java -version
      java version "1.3.0"
      Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0)
      Java HotSpot(TM) Client VM (build 1.3.0, mixed mode)

      *AND*

      lowe% java -version
      java version "1.4.0-beta"
      Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-beta-b65)
      Java HotSpot(TM) Client VM (build 1.4.0-beta-b65, mixed mode)


      JViewport is terribly inefficient at scrolling diagonally.

      When scrolling using a scrollbar (horizontally or vertically only), it works
      fine - the screen contents are blitted as appropriate, and the small area of
      newly-exposed view component is repainted.

      However, when both horizontal and vertical scroll values are changed at the same
      time, JViewport behaves badly - it repaints the entire component.

      Our imaging application has a "mouse scroller" attached to it, so the user can
      click-and-drag the image around (as well as using scrollbars). Thus diagonal
      scrolls are very common. In addition, it is **VERY** expensive to repaint the
      screen. Depending on how much processing is being done to the image and how
      well the JAI cache performs, it can take large fractions of a second to repaint
      (NOTE: this bug has nothing to do with JAI per se).

      In addition, we have the display set up to paint asynchronously, so the user
      does not have to sit there and wait with an unresponsive GUI while tiles of the
      image are being fetched. This asynchronicity combined with the full-screen
      repaint on diagonal scrolls is highly objectionable. The scrolling is
      inefficient even without this asynchronicity but becomes unusable with it.

      It is not acceptable to do an X-only scroll followed by Y-only. Doing so causes
      an even more objectionable "stairstep" scroll.

      Attached is a demonstration program that shows the issue. The event listeners
      set up a mouse scroller; grab and drag the image to illustrate. The repaint
      method wastes time, then just repaints the damaged area with a new color (to
      visually show what has been repainted).

      When a diagonal scroll happens, the damaged area is not a rectangle; it is
      instead an L-shaped area, which is not supportable in a single paint call.
      However, the fix is easy. Instead of a single paint call, call paint twice...
      once for the X damage, and once for the Y damage. A single blit takes care of
      the rest of the screen.

      Based on the JDK 1.3 source, the change to JViewport to effect this is as
      follows. Only JViewport.windowBlitPaint(Graphics g) need be modified. Here is
      the modified routine:

          private boolean windowBlitPaint(Graphics g) {
              int width = getWidth();
              int height = getHeight();

              if ((width == 0) || (height == 0)) {
                  return false;
              }

              boolean retValue;
              RepaintManager rm = RepaintManager.currentManager(this);
              JComponent view = (JComponent) getView();

              if (lastPaintPosition == null ||
                  lastPaintPosition.equals(getViewLocation())) {
                  paintView(g);
                  retValue = false;
              } else {
                  // The image was scrolled. Manipulate the backing store and flush
                  // it to g.
                  Point blitFrom = new Point();
                  Point blitTo = new Point();
                  Dimension blitSize = new Dimension();
                  Rectangle blitPaintX = new Rectangle();
                  Rectangle blitPaintY = new Rectangle();

                  Point newLocation = getViewLocation();
                  int dx = newLocation.x - lastPaintPosition.x;
                  int dy = newLocation.y - lastPaintPosition.y;
                  boolean canBlit = computeBlit(dx, dy, blitFrom, blitTo, blitSize,
                                                blitPaintX, blitPaintY);
                  if (!canBlit) {
                      paintView(g);
                      retValue = false;
                  } else {
                      boolean isDBE = rm.isDoubleBufferingEnabled();
                      int bdx = blitTo.x - blitFrom.x;
                      int bdy = blitTo.y - blitFrom.y;

                      // Move the relevant part of the backing store.
                      blitWindowGraphics(blitFrom.x, blitFrom.y, blitSize.width,
                                         blitSize.height, bdx, bdy);

                      // Prepare the rest of the view; the part that has just been
                      // exposed.

                      Image off = rm.getOffscreenBuffer(this,getWidth(),getHeight());

                      if (!blitPaintX.isEmpty()) {
                          Rectangle r = view.getBounds().intersection(blitPaintX);
                          r.x -= view.getX();
                          r.y -= view.getY();
                          g.translate(+view.getX(), +view.getY());
                          g.setClip(r.x,r.y,r.width,r.height);
                          rm.setDoubleBufferingEnabled(false);
                          view.paint(g);
                          rm.setDoubleBufferingEnabled(isDBE);
                          g.translate(-view.getX(), -view.getY());
                      }
                      if (!blitPaintY.isEmpty()) {
                          Rectangle r = view.getBounds().intersection(blitPaintY);
                          r.x -= view.getX();
                          r.y -= view.getY();
                          g.translate(+view.getX(), +view.getY());
                          g.setClip(r.x,r.y,r.width,r.height);
                          rm.setDoubleBufferingEnabled(false);
                          view.paint(g);
                          rm.setDoubleBufferingEnabled(isDBE);
                          g.translate(-view.getX(), -view.getY());
                      }
                      retValue = true;
                  }
              }
              lastPaintPosition = getViewLocation();
              return retValue;
          }

      The only changes are inside the primary "else" clause. Comparison to the 1.3
      baseline source should be straightforward.

      Actually effecting this change is non-trivial because JViewport is not
      subclassable, but that's being submitted as a separate bug.

      The same diagonal scroll behavior is observed with both JDK 1.3 and 1.4 beta. A
      quick perusal of the 1.4 source shows that this bug has not been fixed.

      The referenced test code is below:

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

      public class JViewportScrollTest extends JPanel
                      implements MouseListener, MouseMotionListener
      {
          private int _mouseX, _mouseY;
          private static JViewport _vp; // ugly, but it's a test case
          private static final Color _colors[] = { Color.red, Color.green, Color.blue,
              Color.yellow, Color.magenta, Color.cyan };
          private int _colorIndex;
          private double _x;

          public static void main(String argv[])
          {
              // Create window with scroll pane and our component inside

              JFrame window = new JFrame("Scroll Test");
              JViewportScrollTest jvst = new JViewportScrollTest();
              JScrollPane jsp = new JScrollPane(jvst);
              jsp.setPreferredSize(new Dimension(300,300));

              // Set up a "mouse scroller" so you can click and drag with the mouse
              // to scroll diagonally

              _vp = jsp.getViewport();
              _vp.addMouseListener(jvst);
              _vp.addMouseMotionListener(jvst);

              window.getContentPane().add(jsp);
              window.pack();
              window.show();
          }

          public JViewportScrollTest()
          {
              super();
              setPreferredSize(new Dimension(1000,1000));
              _colorIndex = 0;
          }

          /** Paint the component. We put in an artificial delay to simulate
           * a slow paint operation. Finally, just paint the bounding box a
           * single color (but cycle through the colors)
           */
          public void paintComponent(Graphics g)
          {
              Rectangle damage = g.getClipBounds();
              System.out.println("Painting area " + damage);

              // Delay loop... imagine loading an image from disk and processing it...

              for (int i=0; i < 500000; i++)
                  _x += Math.sin((double)i);

              g.setColor(_colors[_colorIndex++]);
              if (_colorIndex >= _colors.length) _colorIndex = 0;

              g.fillRect(damage.x, damage.y, damage.width, damage.height);
          }


          /** Responds to the mousePressed event, which initiates a scroll. */

          public void mousePressed(MouseEvent me)
          {
              _mouseX = me.getX();
              _mouseY = me.getY();
          }

          /** Responds to the mouseDragged event, which actually does a scroll. */

          public void mouseDragged(MouseEvent me)
          {
              int x = me.getX();
              int y = me.getY();
              if (x == _mouseX && y == _mouseY)
                  return; // nothing to do

              int delta_x = _mouseX - x;
              int delta_y = _mouseY - y;

              Point p = _vp.getViewPosition();
              p.translate(delta_x, delta_y);

              // Make sure we didn't scroll out of bounds

              Dimension d = _vp.getViewSize();
              int w = _vp.getWidth();
              if (p.x + w > d.width) p.x = d.width - w;
              int h = _vp.getHeight();
              if (p.y + h > d.height) p.y = d.height - h;
              if (p.x < 0) p.x = 0;
              if (p.y < 0) p.y = 0;

      // ENABLE THIS FOR STANDARD, DIAGONAL SCROLLING
              _vp.setViewPosition(p);

      // ENABLE THIS FOR OBJECTIONABLE STAIRSTEP SCROLLING
      // _vp.setViewPosition(new Point(p.x, (int)_vp.getViewPosition().getY()));
      // _vp.setViewPosition(new Point((int)_vp.getViewPosition().getX(), p.y));

      // END OF OPTION

              _mouseX = x;
              _mouseY = y;
          }

          /** Not used */
          public void mouseMoved(MouseEvent me) { }
          public void mouseClicked(MouseEvent me) { }
          public void mouseReleased(MouseEvent me) { }
          public void mouseEntered(MouseEvent me) { }
          public void mouseExited(MouseEvent me) { }

      }
      (Review ID: 127335)
      ======================================================================

            Unassigned Unassigned
            bonealsunw Bret O'neal (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

              Created:
              Updated:
              Imported:
              Indexed: