-
Bug
-
Resolution: Unresolved
-
P4
-
None
-
1.3.1
-
generic
-
generic
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)
======================================================================
- relates to
-
JDK-4478765 EOU: Make subclassing JViewport easier
-
- Open
-