JDK-4756347 : 1.4.1 Graphics.draw[Oval/Polygon] scatters boundary line
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 1.4.1
  • Priority: P4
  • Status: Closed
  • Resolution: Cannot Reproduce
  • OS: windows_2000
  • CPU: x86
  • Submitted: 2002-10-01
  • Updated: 2004-08-10
  • Resolved: 2004-08-10
Description

Name: gm110360			Date: 10/01/2002


FULL PRODUCT VERSION :
java version "1.4.1"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1-b21)
Java HotSpot(TM) Client VM (build 1.4.1-b21, mixed mode)

FULL OPERATING SYSTEM VERSION :
Microsoft Windows 2000 [Version 5.00.2195]

EXTRA RELEVANT SYSTEM CONFIGURATION :
Sony PCG-883L Notebook with ATI Technologies Mobilt Radeon
display adaptor.

A DESCRIPTION OF THE PROBLEM :
Graphics.drawOval() or Graphics.drawPolygon() are generating
laterally scattered segments of the locus of the oval or
polygon.

REGRESSION.  Last worked in version 1.3.1

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1.To see bug Compile BugPanel.java as-is
2.Run: java BugPanel
3.Click on panel(option - and drag it around)
4.Recompile after changing drawOval/fillOval to
drawRect/fillRect and rerun to see proper behaviour.
5.Same happens with drawPolygon/fillPolygon, but not
demonstated here.

EXPECTED VERSUS ACTUAL BEHAVIOR :
Expect a single normal (smooth) curve forming an oval.
Instead, one gets broken segments covering about  one third
of the curvve all around, and the remaining segments are drawn
at an x-axis offset to + and - directions. Seems a period of
3 of the x-width. So we get three renderings of the oval all
with brolen curve segments

What should happen is a single smooth oval drawing

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JFrame;
import javax.swing.UIManager;

public class BugPanel extends JPanel implements MouseListener{

  private transient Dimension viewSize;
  private transient int viewHeight, viewWidth;
  private final static int VIEW_MARGIN = 10; // percentage of view dimensions
  private transient double x_origin, y_origin;
  private transient double x_width, y_height;
  private transient double x_delta, y_delta;
  private transient double min_xdel, min_ydel;
  private final static int XPAD = 5;
  private final static int YPAD = 5;
  protected transient Vector locations;
  Location grab;

    // HERE'S WHERE fillOval/drawOval are called.
    // Replace with fillRect/drawRect to see normal behaviour
  private void drawLocation(Graphics g, Location location) {
    Dimension d = getLocationSize(location);
    int w = d.width;
    int h = d.height;
    FontMetrics fm = g.getFontMetrics();
	if(location==grab) g.setColor(Color.lightGray);
	else g.setColor(Color.white);
//    g.fillRect(location.getX() - w/2, location.getY() - h/2, w, h);
    g.fillOval(location.getX() - w/2, location.getY() - h/2, w, h);
	g.setColor(Color.black);
	g.drawString(location.getName(), location.getX() - w/2 + XPAD, location.getY()
+ YPAD);
	g.setColor(Color.red);
//    g.drawRect(location.getX() - w/2, location.getY() - h/2, w, h);
    g.drawOval(location.getX() - w/2, location.getY() - h/2, w, h);
  }


  public BugPanel() {
    locations = new Vector();
	addMouseListener(this);
	MouseMotionAdapter mml = new MouseMotionAdapter(){
		//
//
//// Begin mouse motion listener
  public void mouseDragged(MouseEvent me) {
	// if grabbed set where
	if(grab!=null){
		grab.setX(me.getX()+(int)min_xdel);
		grab.setY(me.getY()+(int)min_ydel);
	}
	repaint();
  }

  public void mouseMoved(MouseEvent me) {
	  // No Mouse Button Down, so do nothing
  }
// End Mouse Motion Listener
//
//
  };
	addMouseMotionListener(mml);
	ComponentAdapter componentListener = new ComponentAdapter(){
		public void componentResized(ComponentEvent e) {
			viewHeight = (int)getHeight();
			viewWidth = (int)getWidth();
            }  };
	addComponentListener(componentListener);
  }

//
//
//// Begin mouse listener

		public void mouseClicked(MouseEvent me) {
                        Object src = me.getSource();
			if(locations.size() == 0){ // Create just one to demonstrate problem
                            System.out.println("Show scatter with Oval!");
				Location location = new Location("You may Drag this 'icon'
around",me.getX(),me.getY());
				locations.add(location);
			}
//			me.consume();
		}

		public void mousePressed(MouseEvent me) {
			// grab and drag nearest location
				double nearest = Double.MAX_VALUE;
				double proximity = Double.MAX_VALUE;
				min_xdel = min_ydel = 0.0;
				int x = me.getX();
				int y = me.getY();
				Enumeration e = locations.elements();
				e = locations.elements();
				while(e.hasMoreElements()) {
  					Location l = (Location)e.nextElement();
  					Dimension d = getLocationSize(l);
					x_delta = l.getX() - x;
					y_delta = l.getY() - y;
					proximity = (x_delta) * (x_delta) + (y_delta) * (y_delta);
					double snap = (d.width/2) * (d.width/2) + (d.height/2) * (d.height/2);
					// do not snap if you have not touched anything first
					// or if you have backed off from a previous snap
					// unless an alternative improves the last snap
					if ((nearest==Double.MAX_VALUE&&proximity<=snap)
						|| (nearest!=Double.MAX_VALUE&&proximity < nearest)) {
								grab = l;
					nearest = proximity;
					min_xdel = x_delta;
					min_ydel = y_delta;
  					}
				}
				if(grab!=null){
					grab.setX(x+(int)min_xdel);
					grab.setY(y+(int)min_ydel);
					}
		 repaint();
//			me.consume();
		}

		public void mouseReleased(MouseEvent me) {
        if(grab!=null){
			grab.setX(me.getX()+(int)min_xdel);
			grab.setY( me.getY()+(int)min_ydel);
            grab=null;
		 }
		 repaint();
//		 me.consume();
		}

    public void mouseEntered(MouseEvent me) {
    }

    public void mouseExited(MouseEvent me) {
    }

// End Mouse Listener
//
//


  public void initSize(Dimension size) {
	setPreferredSize(size);
	  viewSize = getPreferredSize();
	  viewHeight = (int)viewSize.getHeight();
	  viewWidth = (int)viewSize.getWidth();
	y_origin = viewHeight/VIEW_MARGIN;
	x_origin = viewWidth/VIEW_MARGIN;
	x_width = (double) viewWidth;
	y_height = (double) viewHeight;
  }

  public Dimension getLocationSize(Location location) {
    Graphics g = getGraphics();
    FontMetrics fm = g.getFontMetrics();
    int w = fm.stringWidth(location.getName()) + 2 * XPAD;
    int h = fm.getHeight() + 2 * YPAD;
	location.setW(w);
	location.setH(h);
    return new Dimension(w, h);
  }

    public void paint(Graphics g) {
/* Graphics.clearRect() was not needed pre JSDK1.3.x */
/* comment out for the effect that appeared in 1.3.x */
 g.clearRect(0,0,viewWidth,viewHeight);
    Enumeration l = locations.elements();
    while(l.hasMoreElements()){
      drawLocation(g, (Location)l.nextElement());
    }
	g.setColor(Color.black);
  }

//
//
//// Specify Location (made inner to fit into demo file)
public class Location {
  private String name;
  private int x, y, w, h;

  public Location(String name, int x, int y) {
    this.name = name;
    this.x = x;
    this.y = y;
  }
  public String getName() { return name;  }


  public int getX() { return x;  }

  public int getY() { return y;  }

  public int getW() { return w;  }

  public int getH() { return h;  }

  public void setX(int x) { this.x = x;  }

  public void setY(int y) { this.y = y;  }

  public void setW(int w) { this.w = w;  }

  public void setH(int h) { this.h = h;  }

  public void setName(String name) { this.name = name;  }

  }
// End Location
//
//
  public static void main(String[] args){
		try { // Toggle L&F between 'System' with 'CrossPlatform'
	    UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
	} catch (Exception exc) {
	    System.err.println("Error loading L&F: " + exc);
	}
		JFrame frame = new JFrame("DrawBugDemo: Click in pane for Demonstration");
                frame.addWindowListener(
                  new WindowAdapter() {
                    public void windowClosing(WindowEvent e) {
                        System.out.println("Also happens with drawPolygon!");
                        System.out.println("But not with with drawRect!");
                        System.out.println("Ciao!");
                        System.exit(0);}
                  }
                 );
		frame.setSize(new Dimension(420,300)); // Use Size of reference Frame
		frame.setLocation(20,30);
		BugPanel topPanel = new BugPanel();
		topPanel.setPreferredSize(new Dimension(420,140));
                frame.getContentPane().add(topPanel);
		frame.addMouseListener(topPanel); // This does not help symptom */
                frame.setVisible( true);
  }
}

---------- END SOURCE ----------

CUSTOMER WORKAROUND :
None found
(Review ID: 165096) 
======================================================================

Comments
WORK AROUND Because this appears to be a bug in the graphics hw or driver for that particular video card, disabling ddraw (which punts to GDI for these cases) is a successful workaround for the problem: java -Dsun.java2d.ddoffscreen=false (ddoffscreen disables our use of offscreen surfaces, so the back buffer is a BufferedImage instead) java -Dsun.java2d.noddraw=true (noddraw disables ddraw completely, including copies to the screen).
13-08-2004

EVALUATION Graphics.draw[Oval/Polygon] is owned by 2D. ###@###.### 2002-10-01 Not reproducible on my current desktop (Matrox g400); seems like a very dcard/driver-version (and possibly resolution) dependent bug. Drawing ovals through Java2D simply ends up in GDI calls, so it seems like the GDI driver for that graphics configuration is simply doing weird things with the oval rendering. I've contacted the user to get specifics on their video setup to see if I can get a similar testing configuration here to reproduce the bug. ###@###.### 2002-11-07 Verified with the user that the problem only occurs when we render using ddraw (actually our GDI punt) to the back buffer; our software rendering routines (to a BufferedImage back buffer) work fine. Pretty clear that this is a driver or hardware bug since we simply call GDI and let it handle the primitive completely. I'm leaving the bug open for now pending more information on driver versions (and hopefully the ability to reproduce the bug in-house), but I don't see what we can do here besides recommend a driver upgrade (which will hopefully fix the problem, assuming ATI has fixed whatever the bug was). ###@###.### 2002-11-15 Just tested this with a later Radeon Mobility card (9700M) and cannot see the problem. This has to be a card/driver-specific problem that we cannot affect; hopefully the bug is fixed in later drivers if it was a driver problem. It certainly appears fixed in later hardware, such as the 9700M. Closing the bug as Not Reproducible. ###@###.### 2004-08-10
10-08-2004