Is it possible to create an outline around a PolyLine - java

This example draws a simple PolyLine.
Is it possible to draw an outline around this PolyLine in red.
Not a single large red square but one that outlines the original PolyLine by 3-5 points away from all areas.
Some calculations were attempted and work for a fixed value, but when the PolyLine values are random, the algorithm doesn't always work as the next section of the line could turn right instead of left or up instead of down.
You almost have to look 2-3 points ahead to know if you are going to have add or subtract.
Is there an easier way to do it?
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class PolyLine extends JPanel
{
public void paint(Graphics g) {
int[] xs = {25, 125, 85, 75, 25, 65, };
int[] ys = {50, 50, 100, 110, 150, 100};
BasicStroke traceStroke = new BasicStroke (1);
Graphics2D gc = (Graphics2D) g.create();
gc.setStroke(traceStroke);
gc.setColor(Color.BLUE);
gc.drawPolyline(xs, ys, 6);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.add(new PolyLine());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBounds(20,20, 1500,1500);
frame.setVisible(true);
}
}

First, a remark: It's usually preferable to have the geometric primitives as a Shape. The drawPolyline function (which uses these odd integer array coordinates) is somewhat out-dated. Creating the polyline as a Path2D is far more flexible.
For the task that you described, it will also be necessary to convert the polyline coordinates into a Path2D (if you switched to Path2D anyhow, you could omit this conversion step).
When you have the polyline as a Shape, the task is rather simple: You can create a stroked version of this shape, using a BasicStroke with the desired thickness and cap/join characteristics, by calling BasicStroke#createStrokedShape. This shape will basically be the shape of a "thick line". In order to avoid artifacts at the joins, you can create an Area from this Shape, and then draw this area.
So in the end, painting the actual outline is done with 2 lines of code, and the result is as follows:
But the MCVE here, for completeness:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ShapeOutlineTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new ShapeOutlineTestPanel());
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ShapeOutlineTestPanel extends JPanel
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
int[] xs = { 25, 125, 85, 75, 25, 65, };
int[] ys = { 50, 50, 100, 110, 150, 100 };
BasicStroke traceStroke = new BasicStroke(1);
g.setStroke(traceStroke);
g.setColor(Color.BLUE);
g.drawPolyline(xs, ys, 6);
Path2D path = new Path2D.Double();
for (int i = 0; i < xs.length; i++)
{
if (i == 0)
{
path.moveTo(xs[i], ys[i]);
}
else
{
path.lineTo(xs[i], ys[i]);
}
}
g.setColor(Color.RED);
BasicStroke stroke = new BasicStroke(
10.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
g.draw(new Area(stroke.createStrokedShape(path)));
}
}

Related

Ugly diagonal lines after affine transform

I'm working on figures drawing on low-end device. I noticed that after scale affine transformation many of thin lines (e.g. 1px width) become ugly. Especially in case of diagonal lines (e.g. a line that's sloped by 45 degrees).
I prepared demo program with the most evident examples (I included boilerplate GUI code so you can run the example):
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
public class DiagonalLinesDemo extends JPanel
{
public void paintComponent(Graphics g)
{
Graphics2D graphics = (Graphics2D) g;
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
drawRemark(graphics, 1F, 20);
drawDiagonalLine(graphics, 1F, 0);
drawRemark(graphics, 1.1070F, 80);
drawDiagonalLine(graphics, 1.1070F, 60);
drawRemark(graphics, 1.0162F, 160);
drawDiagonalLine(graphics, 1.0162F, 140);
drawRemark(graphics, 1.3300F, 250);
drawDiagonalLine(graphics, 1.3300F, 160);
drawRemark(graphics, 1.5555F, 330);
drawDiagonalLine(graphics, 1.5555F, 180);
}
private static void drawDiagonalLine(Graphics2D graphics, float scale, int anchor)
{
GeneralPath path = new GeneralPath(Path2D.WIND_EVEN_ODD);
path.moveTo(anchor + 10, 100);
path.lineTo(anchor + 60, 50);
AffineTransform upScaleTransform = new AffineTransform(
scale,
0F,
0F,
scale,
0F,
0F);
graphics.setTransform(upScaleTransform);
graphics.setColor(Color.BLACK);
graphics.draw(path);
}
private static void drawRemark(Graphics2D graphics, float scale, int anchor)
{
graphics.setTransform(new AffineTransform());
graphics.setColor(Color.BLUE);
String remark = String.format("%1.4f", scale);
graphics.drawString(remark, anchor, 50);
}
public static void main(String args[])
{
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("Diagonal Lines Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBackground(Color.white);
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
DiagonalLinesDemo panel = new DiagonalLinesDemo();
frame.add(panel);
frame.setVisible(true);
}
private static final int PANEL_WIDTH = 400;
private static final int PANEL_HEIGHT = 200;
private static final int FRAME_WIDTH = PANEL_WIDTH + 50;
private static final int FRAME_HEIGHT = PANEL_HEIGHT + 50;
}
The result is:
As you can see from the screenshot, after transformation (scale values are remarked above the lines for clarity) ugly fractures appeared. I know that fractures are unavoidable due to absence of antialiasing (I cannot enable antialiasing due to customer's limitations). But I expect more-or-less pretty result, like it's depicted below (I highlighted desired shapes in red):
I tried to adjust winding rule, rendering hints. But without luck. Also, I'm aware of interpolation. But that approach is too resource-consuming (as I said earlier, customer's hardware resources are very limited). Are there any other options?

Draw a Rectangle Using Mouse Click

I'm having issues getting a rectangle drawn when the mouse is clicked on screen in my JFrame. I've tried a few different methods and the closest I've gotten is just getting the coordinates appearing. Any drawing seems to be ignored for some reason.
package pathfinder;
import java.awt.BorderLayout;
import java.awt.Canvas;
import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseMotionAdapter;
public class forLoopDesign extends JPanel{
//offsets for hard-coded path
int hOffset = 40;
int vOffset = 40;
//check if adjacent block is wall
boolean wall = false;
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g) {
//draw start and end points
g.setColor(Color.magenta);
g.fillRect(0, 0, 40, 40);
g.setColor(Color.white);
g.drawString("Start", 7, 24);
g.setColor(Color.red);
g.fillRect(720, 720, 40, 40);
g.setColor(Color.white);
g.drawString("Finish", 724, 744);
//draw grid
g.setColor(Color.black);
for(int i=0; i<760; i+=40){
for(int j=0; j<800; j+= 40){
g.drawRect(i, j, 40, 40);
}
}
//draw hard-coded path
g.setColor(Color.cyan);
for(int i=0; i< 17; i++){
g.fillRect(hOffset + 1, vOffset + 1, 39, 39);
hOffset += 40;
vOffset += 40;
}
//check for mouse click, print coordinates
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
System.out.println("(x, y) of mouse click= (" + x + ", " + y + ")");
g.setColor(Color.orange);
g.fillRect(200,200,40,40);
}
});
}
//draw wall at mouse click location
public void highlightSquare(int x, int y, Graphics g){
wall = true;
System.out.println("wall value=" + wall);
g.setColor(Color.black);
g.fillRect(x, y, 40, 40);
}
//initialize jframe properties
public static void main(String[] args) {
JFrame f = new JFrame();
f.getContentPane().add(new forLoopDesign(), BorderLayout.CENTER);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(767, 790);
f.setVisible(true);
f.setResizable(false);
f.setLocationRelativeTo(null);
}}
My end goal is to be able to draw rectangles on the screen to create a maze like screen and have the program navigate its way through it (cyan squares). This is an ambitious project and I realize I've probably made many mistakes so far so feel free to add any other criticisms you might have. Thanks in advance
All custom painting needs to be done in the paintComponent(..) method.
So in your mouseClick logic you need to save the point where you clicked in an ArrayList and invoke repaint(). Then the paintComponent(...) method needs to iterate through the ArrayList to paint the rectangle at the given point.
See the DrawOnComponent example from Custom Painting Approaches for a working example of this approach.

Set gradient direction in swing

I have been trying to draw lines in swing and get some laser-like effects using gradients. I want to apply the gradient on the line's width (ex: a red line core fading to orange on the edges). The problem is when I draw at an angle, I want to somehow apply the same angle to the gradient.
public void paintComponent(Graphics g) {
Graphics2D en = (Graphics2D) g;
GradientPaint gp = new GradientPaint(25, 25, Color.red, 15, 25,
Color.orange, true);
en.setPaint(gp);
en.setStroke(new BasicStroke(4.0F));
en.drawLine(10, 10, 800, 600);
}
One easy trick to get away with this, is to call setTransform() on the Graphics2D. This will do for you the rotation of both the line and the gradient at once. If you don't want to do this, then you will have to rotate the gradient itself (so basically pt1 and pt2 of the Gradient manually, ie, you need to compute them according to the rotation you have applied to your line).
Here is a small example illustrating my first idea. Just slide the ticker at the bottom to watch the line (and the gradient) rotate around the center of the panel:
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class TestPanel extends JPanel {
private double angle = 0;
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D en = (Graphics2D) g;
AffineTransform tf = AffineTransform.getTranslateInstance(-getWidth() / 2, -getHeight() / 2);
tf.preConcatenate(AffineTransform.getRotateInstance(Math.toRadians(angle)));
tf.preConcatenate(AffineTransform.getTranslateInstance(getWidth() / 2, getHeight() / 2));
en.setTransform(tf);
GradientPaint gp = new GradientPaint(25, 25, Color.red, 15, 25, Color.orange, true);
en.setPaint(gp);
en.setStroke(new BasicStroke(4.0F));
en.drawLine(400, 400, 600, 600);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 800);
}
public void setAngle(double angle) {
this.angle = angle;
repaint();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
final TestPanel panel = new TestPanel();
final JSlider slider = new JSlider(0, 360);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
panel.setAngle(slider.getValue());
}
});
slider.setValue(0);
frame.add(panel);
frame.add(slider, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}

Disable standard repainting of JToggleButton when It is selected

I want my JToggleButton not to repaint when It is selected. I indicate state changing by pair of words ("check/next").
Standard behavior is blue lighting but I want to disable it.
Perhaps you could show the words on ImageIcons. For example:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
public class ToggleFun {
private static final Color BACKGROUND_COLOR = new Color(200, 200, 255);
public static void main(String[] args) {
int biWidth = 60;
int biHeight = 30;
BufferedImage checkImg = new BufferedImage(biWidth, biHeight, BufferedImage.TYPE_INT_RGB);
BufferedImage nextImg = new BufferedImage(biWidth, biHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = checkImg.createGraphics();
g2.setColor(BACKGROUND_COLOR);
g2.fillRect(0, 0, biWidth, biHeight);
g2.setColor(Color.black);
g2.drawString("Check", 10, 20);
g2.dispose();
g2 = nextImg.createGraphics();
g2.setColor(BACKGROUND_COLOR);
g2.fillRect(0, 0, biWidth, biHeight);
g2.setColor(Color.black);
g2.drawString("Next", 15, 20);
g2.dispose();
ImageIcon checkIcon = new ImageIcon(checkImg);
ImageIcon nextIcon = new ImageIcon(nextImg);
JToggleButton toggleBtn = new JToggleButton(checkIcon);
toggleBtn.setSelectedIcon(nextIcon);
toggleBtn.setContentAreaFilled(false);
toggleBtn.setBorder(BorderFactory.createLineBorder(Color.black));
JPanel panel = new JPanel();
panel.add(toggleBtn);
JOptionPane.showMessageDialog(null, panel);
}
}
See: AbstractButton.setContentAreaFilled(false).
But note that users generally prefer a GUI element that follows the 'path of least surprise'. This type of rendering might be better described as going off on a bit of a crash-bang through the undergrowth beside that path.

Java JFrame size and centre

I'm working on a uni project which is to create a dice face using 2d graphics shapes. I have got that all done but I have a problem: I want my shape to change size when I adjust the window size, instead of just staying still also so that it stays in the middle.
I thought I could set the position so that is was center tried this but didn't work. I'm not sure but would I have to write co-ordinates so that its changes size with the window? Any help with both problems would be great.
GUI setup code
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class DiceSimulator {
public static void main(String[] args) {
JFrame frame = new JFrame("DiceSimulator");
frame.setVisible(true);
frame.setSize(400, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
draw object = new draw();
frame.add(object);
frame.setLocationRelativeTo(null);
object.drawing();
}
}
Painting code
import javax.swing.*;
import java.awt.*;
//import java.util.Random;
public class draw extends JComponent {
public void drawing() {
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
//Random1 random = new Random1();
Graphics2D g2 = (Graphics2D) g;
g.setColor(Color.BLACK);
Rectangle box = new Rectangle(115, 60, 150, 150);
g2.fill(box);
g.setColor(Color.WHITE);
g.fillOval(145, 75, 30, 30);
g.setColor(Color.WHITE);
g.fillOval(205, 75, 30, 30);
g.setColor(Color.WHITE);
g.fillOval(145, 115, 30, 30);
g.setColor(Color.WHITE);
g.fillOval(205, 115, 30, 30);
g.setColor(Color.WHITE);
g.fillOval(145, 155, 30, 30);
g.setColor(Color.WHITE);
g.fillOval(205, 155, 30, 30);
}
}
In the paintComponent() method you need to use
int width = getSize().width;
int height = getSize().height;
to get the current size of the component as it changes size when the frame changes size. Then based on this current size you can draw your components. This means that you can't hardcode the values in your drawing methods.
If you want to shift all the drawing coordinates with one command then you can use:
g.translate(5, 5);
at the top of the method. Then all hardcoded (x, y) values will be adjusted by 5 pixels each. This will allow you to change the centering of the drawing.

Categories