This question is a followup to one I asked some time ago: Drawing a bordered path with sharp corners in Java
After experimentation, I've discovered some behavior which may be intended, or may be a bug, but either way is not desired by me. SSCCE:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.GeneralPath;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class CornerTest {
private JFrame frame;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
CornerTest window = new CornerTest();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public CornerTest() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel() {
protected void paintComponent(Graphics g) {
GeneralPath path;
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(15.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.setColor(Color.BLACK);
path = new GeneralPath();
path.moveTo(100, 100);
path.lineTo(200, 100);
path.lineTo(100, 50);
g2d.draw(path);
}
};
frame.setBackground(Color.CYAN);
frame.add(panel);
}
}
By taking the line path.lineTo(100, 50);
and playing with the second argument, we can change the angle of the path drawn. See the image below for various examples.
Basically, I am drawing a GeneralPath
with the angle slightly decreasing in each image. Eventually (in the bottom image) the angle reaches zero. In this case, the rounded "join" no longer is painted. This sort of makes sense, but it sort of doesn't. I'm not sure if it is intended. Interestingly, if you change the angle to slightly greater than 0 (i.e. by changing the line referenced above to path.lineTo(100, 99.999);
the rounded corner is drawn again.
In my application it is possible for paths to double back on themselves (i.e. create zero degree angles), and in this case it is much more appealing aesthetically to have the join round drawn in such a case. Is there any way I can hack the Java source to make this work?
I'm drawing very long paths with like 500+ points, it will add a lot of overhead to my paint method to check each "sub-path"
Don't build the GeneralPath in the paint method. You could use a wrapper class that will filter each point as it is added. The code here will compare the slope of the line every time a point is about to be added. When the slopes are the same a moveTo
is generated for the last point before the lineTo
is added. This will cause the round stroke to be generated:
import java.awt.*;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.*;
import java.util.List;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class CornerTest {
private JFrame frame;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
CornerTest window = new CornerTest();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public CornerTest() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 450);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final GeneralPath gp = new GeneralPath();
MyGeneralPath path = new MyGeneralPath(gp);
path.moveTo(100, 100);
path.lineTo(200, 100);
path.lineTo(100, 50);
// path.lineTo(50, 100);
path.flush();
JPanel panel = new JPanel() {
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(15.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2d.setColor(Color.BLACK);
g2d.draw(gp);
}
};
frame.setBackground(Color.CYAN);
frame.add(panel);
}
static class MyGeneralPath
{
private GeneralPath path;
private List<Point2D.Double> points = new ArrayList<Point2D.Double>();
public MyGeneralPath(GeneralPath path)
{
this.path = path;
}
public void moveTo(double x, double y)
{
flush();
points.add( new Point2D.Double(x, y) );
}
public void lineTo(double x, double y)
{
Point2D.Double point = new Point2D.Double(x, y);
checkSlope(point);
points.add( point );
}
private void checkSlope(Point2D.Double p3)
{
int size = points.size();
if (size < 2 )
return;
Point2D.Double p2 = points.get(size - 1);
Point2D.Double p1 = points.get(size - 2);
double slope1 = (p2.getY() - p1.getY()) / (p2.getX() - p1.getX());
double slope2 = (p3.getY() - p2.getY()) / (p3.getX() - p2.getX());
if (slope1 == slope2)
moveTo(p2.getX(), p2.getY());
}
public void flush()
{
int size = points.size();
if (size == 0)
return;
Point2D.Double point = points.get(0);
path.moveTo(point.getX(), point.getY());
for (int i = 1; i < size; i++)
{
point = points.get(i);
path.lineTo(point.getX(), point.getY());
}
points.clear();
}
}
}