Hi,A bug was filed in the IcedTea bugzilla which pointed out a few issues in java2d [1].
I am attaching example code and it's output to demonstrate one particular issue. The attached program draws 5 horizontal lines with different dash patterns. Using a BasicStroke with CAP_ROUND (or CAP_SQUARE) and a very small value for length of the dash, no dashes are drawn at the start of the line.
This seems to be the code responsible (Dasher.java line 221):
// ysplit = (int)(dysplit*65536.0);
// } else {
t = ((long)d << 16)/l;
xsplit = x0 + (int)(t*(x1 - x0) >> 16);
ysplit = y0 + (int)(t*(y1 - y0) >> 16);
'd' is the length of the dash, and 'l' is the distance between the
current location and the end location. Notice that the value of l
varies: as the current coordinate gets closer to the end of the line, l
becomes smaller. So if the value of d is very small, ((long)d << 16)/l
changes from 0 to a positive number. Hence dashes of length 0 are drawn
in the beginning and dashes of a non-zero length are drawn at the end of
the line.
The attached patch uses the total length of the line to calculate t, so it wont change for the same value of d. However, this patch causes other issues. The dash output now depends on the total length of the line. For example, changing the size of the window in the test case changes the value of the total length of the line. If total length becomes very large, t becomes 0, and the dashes on the line disappear. If the window is made small, the dashes appear on the line again.
Thanks, Omair [1] http://icedtea.classpath.org/bugzilla/show_bug.cgi?id=197
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
public class BasicStrokeDashTest extends JFrame {
private static final long serialVersionUID = -1280835182820012533L;
static final int WIDTH = 900;
static final int HEIGHT = 400;
static final int GAP = 40;
public BasicStrokeDashTest() {
super();
setSize(WIDTH, HEIGHT);
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
@Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
clearBackground(g2);
drawLines(g2);
}
private void clearBackground(Graphics2D g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.BLACK);
}
private void drawLines(Graphics2D g) {
int lines = 5;
int spacing = getHeight() / (lines + 1);
drawHorizontalLineWithDashPattern(g, spacing * 1, new float[] { 0.1f,
2.0f });
drawHorizontalLineWithDashPattern(g, spacing * 2, new float[] { 0.05f,
2.0f });
drawHorizontalLineWithDashPattern(g, spacing * 3, new float[] { 0.01f,
2.0f });
drawHorizontalLineWithDashPattern(g, spacing * 4, new float[] { 0.005f,
2.0f });
drawHorizontalLineWithDashPattern(g, spacing * 5, new float[] { 0.001f,
2.0f });
}
private void drawHorizontalLineWithDashPattern(Graphics2D g, int y,
float[] dashPattern) {
float strokeWidth = 1.0f;
// draw an indicator to show that a line will be drawn here
BasicStroke indicatorStroke = new BasicStroke(strokeWidth,
BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL);
g.setStroke(indicatorStroke);
g.drawLine(GAP / 3, y, (GAP * 2) / 3, y);
// now draw the actual line
BasicStroke stroke = new BasicStroke(strokeWidth,
BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL, strokeWidth,
dashPattern, 0.0f);
g.setStroke(stroke);
g.setColor(getForeground());
g.drawLine(GAP, y, getWidth() - GAP, y);
}
public static void main(String[] args) {
new BasicStrokeDashTest();
}
}
<<inline: dash-stroke-bug.png>>
--- Dasher.java.orig 2009-04-17 16:43:43.000000000 -0400
+++ Dasher.java 2009-04-30 16:31:17.000000000 -0400
@@ -181,6 +181,21 @@
}
public void lineTo(int x1, int y1) {
+ System.out.println("LineTo: x:" + x1/65536 + " y:" + y1/65536);
+ int totalLengthX = x1 - x0;
+ int totalLengthY = y1 - y0;
+ int totalLength;
+ if (symmetric) {
+ totalLength = (int)((PiscesMath.hypot(totalLengthX, totalLengthY)*65536L)/ldet);
+ } else{
+ long lengthA = ((long)totalLengthY*m00 - (long)totalLengthX*m10)/ldet;
+ long lengthB = ((long)totalLengthY*m01 - (long)totalLengthX*m11)/ldet;
+ totalLength = (int)PiscesMath.hypot(lengthA, lengthB);
+ }
+
+ int startX = x0;
+ int startY = y0;
+
while (true) {
int d = dash[idx] - phase;
int lx = x1 - x0;
@@ -220,9 +235,12 @@
// xsplit = (int)(dxsplit*65536.0);
// ysplit = (int)(dysplit*65536.0);
// } else {
- t = ((long)d << 16)/l;
- xsplit = x0 + (int)(t*(x1 - x0) >> 16);
- ysplit = y0 + (int)(t*(y1 - y0) >> 16);
+ t = ((long)d << 16)/totalLength;
+ int deltaX = (int)(t*(x1 - startX) >> 16);
+ xsplit = x0 + deltaX;
+ int deltaY = (int)(t*(y1 - startY) >> 16);
+ ysplit = y0 + deltaY;
+ // System.out.println("t:" + t + " deltaX:" + deltaX + " deltaY:" + deltaY);
// }
goTo(xsplit, ysplit);
