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);
}
});
}
}
Related
I have used several java.awt.Rectangle, java.awt.Polygon, and java.awt.geom.Ellipse2D shapes together and I want to rotate them with eachother and I also want to have them keep their location on the JFrame. When I use g2D.rotate(Math.toRadians(rotation)), the shapes move on the JFrame and they are no longer close together. How can I make it so that all of the shapes keep their position relative to eachother and their position on the JFrame? Thank you in advance.
If you want to be able to rotate the shapes together and keep their position on the JFrame I would recommend that you use the form of g2D.rotate that uses x and y coordinates to rotate with. You should make a standard x and y coordinate to draw each shapes position from so that when you use these standard x and y coordinates to rotate from all of the shapes will rotate together. It would probably look something like this:
// imports
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.Container;
import java.awt.Rectangle;
import java.awt.geom.Ellipse2D;
import java.awt.Polygon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JComponent;
public class GraphicsRotate extends JComponent implements ActionListener {
// JFrame and Container
JFrame frame = new JFrame("Graphics Rotate");
Container container = frame.getContentPane();
public int standardX = 100;
public int standardY = 100;
public int rotation = 0;
public static void main(String[] args) {
GraphicsRotate graphicsRotate = new GraphicsRotate();
graphicsRotate.setup();
}
public void setup() {
container.setBackground(Color.BLACK);
container.add(this);
KeyListener kl = new KeyListener() {
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
if (code == KeyEvent.VK_LEFT) {
rotation -= 10;
repaint();
}
if (code == KeyEvent.VK_RIGHT) {
rotation += 10;
repaint();
}
}
public void keyReleased(KeyEvent e) {
}
public void keyTyped(KeyEvent e) {
}
};
frame.setSize(500, 500);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addKeyListener(kl);
frame.setFocusable(true);
frame.requestFocus();
frame.setVisible(true);
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g.setColor(Color.GREEN);
g2.rotate(Math.toRadians(rotation), standardX, standardY);
Rectangle rect = new Rectangle(standardX + 10, standardY + 10, 5,
5);
Ellipse2D ellipse = new Ellipse2D.Double(standardX + 13, standardY
+ 5, 10, 10);
g2.fill(ellipse);
g2.fill(rect);
}
#Override
public void actionPerformed(ActionEvent e) {
}
}
This could be implemented on a much larger scale, and if you wanted the shapes to rotate around the center of the larger shape that you have made with them, then you could do the necessary calculations to figure out the center and do something like g2.rotate(Math.toRadians(rotation), standardX + middleX, standardY + middleY).
I hope that this answered your question.
It's really important to remember, transformations are compounding. So you can't simply apply a new rotation to the Graphics context for each object, instead, you need to reset the state between transformations
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public final class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private List<Shape> shapes = new ArrayList<>(4);
private double angle = 0;
public TestPane() {
shapes.add(new Rectangle2D.Double(50, 50, 100, 100));
shapes.add(new Rectangle2D.Double(250, 50, 100, 100));
shapes.add(new Rectangle2D.Double(50, 250, 100, 100));
shapes.add(new Rectangle2D.Double(250, 250, 100, 100));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
angle += 1;
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Shape shape : shapes) {
Graphics2D g2d = (Graphics2D) g.create();
Rectangle bounds = shape.getBounds();
int midx = bounds.x + (bounds.width / 2);
int midy = bounds.y + (bounds.height / 2);
g2d.setTransform(AffineTransform.getRotateInstance(Math.toRadians(angle), midx, midy));
g2d.draw(shape);
g2d.dispose();
}
}
}
}
You could also transform the shape directly, for example...
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
for (Shape shape : shapes) {
Rectangle bounds = shape.getBounds();
int midx = bounds.x + (bounds.width / 2);
int midy = bounds.y + (bounds.height / 2);
Path2D.Double rotatedShape = new Path2D.Double(shape, AffineTransform.getRotateInstance(Math.toRadians(angle), midx, midy));
g2d.draw(rotatedShape);
}
g2d.dispose();
}
So I am using a JFrame object to open up a window and add a bunch of graphics first I am adding an image then I am trying to add some lines, But it seems like the line start from the Y center of the previous image I would like for it to start at the top of the page
here is my code for the JFrame:
JFrame f = new JFrame();
JLabel trebeclef = new JLabel(new ImageIcon(getClass().getClassLoader().getResource("Some image")));
Draw d = new Draw();
f.add(trebeclef);
f.add(d);
f.setSize(1000,1000);
f.getContentPane().setBackground(Color.white);
f.setLayout(new FlowLayout(FlowLayout.LEFT,0,0));
f.pack();
f.setVisible(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
here is the code for the Draw class
public void paint(Graphics g2) {
super.paintComponent(g2);
Graphics2D g = (Graphics2D) g2;
g.setStroke(new BasicStroke(2));
g.drawLine(0, 0, 100, 0);
}
it results in this
Any help appreciated, thanks
Trying to layout components like this isn't the easiest thing to to, especially when you consider the complexities of JLabel and the possibilities of other layout constraints.
If you have the image and you're drawing the lines via custom painting, I would just custom paint the whole thing
Starting with...
We can produce something like...
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public final class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new ClefWithLinesPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class ClefWithLinesPane extends JPanel {
private BufferedImage trebbleClef;
public ClefWithLinesPane() throws IOException {
trebbleClef = ImageIO.read(getClass().getResource("/images/ClefLines.png"));
setBackground(Color.WHITE);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int x = 0;
int y = (getHeight() - trebbleClef.getHeight()) / 2;
g2d.drawImage(trebbleClef, x, y, this);
int[] lines = new int[] {
30, 60, 89, 120, 149
};
x = trebbleClef.getWidth();
g2d.setStroke(new BasicStroke(2));
for (int line = 0; line < lines.length; line++) {
y = lines[line];
g2d.drawLine(x, y, getWidth(), y);
}
g2d.dispose();
}
}
}
But... as you can see, the lines don't "quite" match up, now this is simply an issue with source image and you could spend some time cleaning it up, or, you could just dispense with the lines of the clef itself and do those you're self, for example...
Starting with...
We could produce something like...
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public final class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new ClefWithOutLinesPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class ClefWithOutLinesPane extends JPanel {
private BufferedImage trebbleClef;
public ClefWithOutLinesPane() throws IOException {
trebbleClef = ImageIO.read(getClass().getResource("/images/Clef.png"));
setBackground(Color.WHITE);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(800, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int minLineY = 30;
int maxLineY = 150;
int lineSpacing = (maxLineY - minLineY) / 4;
int x = 10;
g2d.setStroke(new BasicStroke(8));
g2d.drawLine(x, minLineY + 3, x, maxLineY - 3);
int y = (getHeight() - trebbleClef.getHeight()) / 2;
g2d.drawImage(trebbleClef, x + 10, y, this);
g2d.setStroke(new BasicStroke(2));
for (int line = 0; line < 5; line++) {
y = minLineY + (lineSpacing * line);
g2d.drawLine(x, y, getWidth(), y);
}
g2d.dispose();
}
}
}
Adds after setLayout. One often uses an overloaded add with an extra parameter for a layout's specific constraint.
By default the content pane the frame's adds are done upon, has a BorderLayout with center, left and so on.
The horizontal "line" in a flow is vertically centered. Setting a preferred height ensures a line component is filled up.
d.setPreferredSize(new Dimension(100, 1000));
Of course an other layout might serve better; depending on what you want - just experiment.
Can I edit Java2D codes in Design view like swing and awt components?
For example this line drawings:
import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tester extends JPanel {
public Tester() {
}
public void paint(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
float[] dash1 = { 2f, 0f, 2f };
g2d.drawLine(20, 40, 250, 40);
BasicStroke bs1 = new BasicStroke(1,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND,
1.0f,
dash1,
2f);
g2d.setStroke(bs1);
g2d.drawLine(20, 80, 250, 80);
}
public static void main(String[] args) {
Tester points = new Tester();
// Just frame settings
JFrame frame = new JFrame("Line");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(points);
frame.setSize(300, 250);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
The lines are not visible in the components or I can draw them only using code?
thanks
When run this code:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JButton;
import javax.swing.JLabel;
public class CustomButton extends JButton {
int width = 100;
int height = 50;
int radius = 10;
JLabel lab;
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.ORANGE);
g2.fillRoundRect(0, 0, width, height, radius, radius);
g2.dispose();
super.paintComponent(g2);
super.paintComponent(g);
}
}
And my other class:
package custom.frame;
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class CustomFrame {
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new FlowLayout());
f.setSize(500,500);
f.setLocationRelativeTo(null);
JPanel pane = new JPanel();
pane.setBounds(0,0,500,500);
CustomButton btn = new CustomButton();
pane.add(btn);
f.add(btn);
f.setVisible(true);
}
}
I get a get a regular rectangle with only 1 rounded side. Please see the image below.
Is this the expected function?
If not, how can I fix this.
Edit
I can get 2 rounded corners if I do this:
g2.fillRoundRect(0, 0, 50, 50, 7, 7);
The only thing I can really think of is that the window containing the rectangle is too small and is cutting off the other three corners, but that seems pretty unlikely.
I am working on an application that displays GPS tracks on a map. I want to draw the track as a colored path of arbitrary thickness. I found the GeneralPath class which seems to do just what I want. However, I want the colored path to also have a black border. I couldn't find anything about how to add a border to a path, so I came up with a quick hacky solution of drawing a thick black path first, and then drawing a thin colored path on top.
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 PathBorder {
private JFrame frame;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
PathBorder window = new PathBorder();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public PathBorder() {
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(5.5f));
g2d.setColor(Color.BLACK);
path = new GeneralPath();
path.moveTo(0, 0);
path.lineTo(100, 100);
path.lineTo(200, 100);
path.lineTo(100, 80);
g2d.draw(path);
g2d.setStroke(new BasicStroke(3));
g2d.setColor(Color.YELLOW);
g2d.draw(path);
}
};
frame.setBackground(Color.CYAN);
frame.add(panel);
}
}
Here is a pic (taken from the SSCCE) to highlight my problem. See the red circle below, notice how the outside border has a gap in it. I want that gap to be filled in so the border is continuous.
Here are some actual screenshots from my app of a real track:
If you look closely at the sharp hairpin turn in the lower right of the track, you'll see that the border gets lost briefly... the detailed pic below makes it more clear.
I am not sure exactly how to fix this, but I'm open to suggestions, either keeping with the GeneralPath strategy, or using a different idea entirely.
Experiment with the cap and join parameters for a better effect. E.G.
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 PathBorder {
private JFrame frame;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
PathBorder window = new PathBorder();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public PathBorder() {
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 450, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel() {
GeneralPath path;
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
BasicStroke s = new BasicStroke(
5.5f,
BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND);
g2d.setStroke(s);
g2d.setColor(Color.BLACK);
if (path==null) {
path = new GeneralPath();
path.moveTo(0, 0);
path.lineTo(100, 100);
path.lineTo(200, 100);
path.lineTo(100, 80);
}
g2d.draw(path);
g2d.setStroke(new BasicStroke(3));
g2d.setColor(Color.YELLOW);
g2d.draw(path);
}
};
frame.setBackground(Color.CYAN);
frame.add(panel);
}
}