What I'm basically trying to do is rotate a BufferedImage and then draw it based on it's new centre point.
So, originally I would've just done;
graphics.drawImage(image, x - (image.getWidth() / 2), y - (image.getHeight() / 2), this);
However, I'm unsure how to do it when I rotate the image using AffineTransform.
If I remember from basic Maths at school I'll need to use Cos for the X and Sin for the Y, however I've been googling for an algorithm but can't seem to come across anything.
I found out how to get the coordinates of the rotated point. If x1,y1 are the original coordinates and at the AffineTransform then you can get the coordinates of the rotated point in the original coordinate system with
Point2D point1origCoords = at.transform(new Point2D.Double(x1,y1),null);
The coordinates of the point in the new coordinate system after the affine transformation would be the same as the original coordinates. They stay the same because it's the coordinate system that gets transformed instead, so you need to apply the affine transformation to them.
Here is a demo of a rotation about the origin:
And the code used to generate it:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Triangle extends JPanel {
public void drawTriangle(Graphics2D g,
int x1, int y1, int x2, int y2, int x3, int y3,
int s, Color c, AffineTransform at) throws NoninvertibleTransformException{
g.setStroke(new BasicStroke(3.0f));
g.setPaint(c);
int d = 10;
g.drawLine(x1*s, y1*s, x2*s, y2*s);
g.drawLine(x2*s, y2*s, x3*s, y3*s);
g.drawLine(x3*s, y3*s, x1*s, y1*s);
Ellipse2D center_of_triangle = new Ellipse2D.Double(.33*(x1+x2+x3)*s, .33*(y1+y2+y3)*s,4,4);
g.draw(center_of_triangle);
float dash1[] = {2.0f};
BasicStroke dashed =
new BasicStroke(1.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f, dash1, 0.0f);
g.setStroke(dashed);
g.drawLine(0,0,x1*s,y1*s);
g.drawLine(0,0,x2*s,y2*s);
g.drawLine(0,0,x3*s,y3*s);
g.setColor(Color.black);
Point2D point1origCoords = at.transform(new Point2D.Double(x1,y1),null);
Point2D point2origCoords = at.transform(new Point2D.Double(x2,y2),null);
Point2D point3origCoords = at.transform(new Point2D.Double(x3,y3),null);
g.drawString("("+String.format("%.2f",point1origCoords.getX())+", "
+String.format("%.2f",point1origCoords.getY())+")",
x1*s,y1*s+2*d);
g.drawString("("+String.format("%.2f",point2origCoords.getX())+", "
+String.format("%.2f",point2origCoords.getY())+")", x2*s-4*d,y2*s-d);
g.drawString("("+String.format("%.2f",point3origCoords.getX())+", "
+String.format("%.2f",point3origCoords.getY())+")", x3*s+d,y3*s);
// center of the triangle
Point2D.Double center = new Point2D.Double(.33*(x1+x2+x3), .33*(y1+y2+y3));
Point2D centerorigCoords = at.transform(center,null);
g.drawString("("+String.format("%.2f",centerorigCoords.getX())+", "
+String.format("%.2f",centerorigCoords.getY())+")",
(int)Math.round(center.x*s),(int)Math.round(center.y*s)-d);
}
public void drawGrid(Graphics2D g, int s) {
int w = getWidth();
int h = getHeight();
float dash1[] = {2.0f};
BasicStroke dashed =
new BasicStroke(1.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
10.0f, dash1, 0.0f);
g.setStroke(dashed);
g.setColor(Color.gray);
int j = 0;
while( j <= h ) {
g.drawLine(0,h-j,w,h-j);
j+=s;
}
j = 0;
while( j <= w ) {
g.drawLine(j,0,j,h);
j+=s;
}
}
public void paintComponent(Graphics g){
final int s=20;
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.black);
Font big = new Font("Times New Roman", Font.BOLD,20);
g2.setFont(big);
drawGrid(g2,s);
AffineTransform at = new AffineTransform();
// no rotation
at.rotate(Math.toRadians(0));
g2.transform(at);
// rotated triangle
try {
drawTriangle(g2,2,26,3,12,8,21,s,Color.blue,at);
} catch (NoninvertibleTransformException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
at.rotate(Math.toRadians(-45));
g2.transform(at);
try {
drawTriangle(g2,2,26,3,12,8,21,s,Color.magenta,at);
} catch (NoninvertibleTransformException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args) {
JFrame frame = new JFrame("Window"); //frame is the window
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Triangle panel = new Triangle(); //panel is the graphics area where we can draw
frame.add(panel); //put the panel inside the window
frame.setSize(600,600); //set the window size to 600x600 pixels
frame.setVisible(true);
}
}
Related
OBS! Changed as part of the question has been answered.
My math has been fixed due to your help and input, the same with StackOverflowError but I still can get my head around how to make the circle move from one x,y point to another.
Currently I just repeat the drawing multiple places.
public class MyFrame extends JPanel {
int xc = 300, yc = 300, r = 100, diam = 50;
double inc = Math.PI / 360, theta = 0;
public void paintComponent(Graphics g) {
Timer timer = new Timer(0, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
theta = theta + inc;
repaint();
}
});
timer.setDelay(2);
timer.start();
}
#Override
public void paint(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); //smooth the border around the circle
g2d.rotate(theta, xc, yc);
g2d.setColor(Color.blue);
g2d.drawOval(xc + r - diam / 2, yc + r - diam / 2, diam, diam);
paintComponent(g);
}
}
This should help you get started. You can modify it as you see fit. It simply has an outer circle revolve around an inner red dot at the center of the panel.
First, rotate the graphics context, and not the circle location around the center. Thus, no trig is required.
Anti-aliasing simply fools the eye into thinking the graphics are smoother.
BasicStroke sets the thickness of the line
you need to put the panel in a frame.
and super.paintComponent(g) should be first statement in paintComponent to clear panel (and do other things).
the timer updates the angle by increment and invokes repaint. A larger increment will make a quicker but more "jerky" motion about the center. If you set the angle to Math.PI/4, then you need to increase the timer delay (try about 1000ms).
Check out the Java Tutorials for more on painting.
Anything else I omitted or forgot should be documented in the JavaDocs.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class DrawCircle extends JPanel {
int width = 500, height = 500;
final int xc = width/2, yc = height/2;
int r = 100; // radius from center of panel to center of outer circle
int diam = 50; // outer circle diamter
double inc = Math.PI/360;
double theta = 0;
JFrame f = new JFrame();
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new DrawCircle().start());
}
public void start() {
f.add(this);
f.pack();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.setVisible(true);
Timer timer = new Timer(0, (ae)-> { theta += inc; repaint();});
timer.setDelay(20);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
g2d.rotate(theta, xc, yc);
g2d.setColor(Color.red);
g2d.fillOval(xc-3, yc-3, 6, 6); // center of panel
g2d.setStroke(new BasicStroke(3));
g2d.setColor(Color.blue);
// g2d.drawLine(xc,yc, xc+r, yc); // tether between centers
g2d.drawOval(xc+r-diam/2, yc-diam/2, diam,diam);
}
}
Updated Answer
OK, there are two fundamental things you are doing wrong.
You are not adding super.paintComponent(g) as the first statement in your paintComponent method.
you are overridding paint(Graphics g) (whether you intend to or not) because it is also public and is inherited by JPanel from JComponent. Do not use paint() as it isn't necessary here (maybe in some applications but I have never had the need). Move all the code in there to paintComponent
You should also move the timer code outside of paintComponent. It only needs to be defined once and is run in the background. It will continue to call your ActionListener class until you stop it.
Now, after doing the above you might ask "why is only one circle showing up when I draw?" The obvious answer is that I only wanted to draw one. But why doesn't it get repeated each time?
Because super.paintComponent(g) clears the JPanel each time paintComponent is invoked just as it is supposed to. So if you want to draw multiple circles (or other things), you need to put them in a list and draw them from within paintComponent. Since all events including painting and your timer are run in series on a single thread (the Event Dispatch Thread) it is important to keep processing to a minimum. So when possible, most calculations should be done outside of that thread. EDT processing should be as simple and as quick as possible.
My first answer showed a circle orbiting a point. But perhaps that is not what you want. You may just want to position circles uniformly around the center from a fixed distance. I have provided two methods of doing that.
Using rotate as before. Imo, it is the simplest. The angle is fixed and each time rotate is called, it is additive. So just call that method nCircle times and draw the circle. And remember to compute the x and y coordinates to correct for the radius.
Using trig to calculate the location of the circles. This uses a list of angles based on the nCircles. For each iteration, x and y are computed based on the radius and current angle.
Both of these are shown in different colors to demonstrate their overlay.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class DrawCircle2 extends JPanel {
int width = 500, height = 500;
final int xc = width / 2, yc = height / 2;
int r = 100; // radius from center of panel to center of outer circle
int diam = 50; // outer circle diamter
int nCircles = 8; // number of circles
double theta = Math.PI*2/nCircles;
List<Point> coords1 = fillForTrig();
JFrame f = new JFrame();
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new DrawCircle2().start());
}
private List<Point> fillForTrig() {
List<Point> list = new ArrayList<>();
for (int i = 0; i < nCircles; i++) {
int x = xc+(int)(r*Math.sin(i*theta));
int y = yc+(int)(r*Math.cos(i*theta));
list.add(new Point(x,y));
}
return list;
}
public void start() {
f.add(this);
f.pack();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
drawUsingRotate(g2d);
// drawUsingTrig(g2d);
}
private void drawUsingRotate(Graphics2D g2d) {
g2d = (Graphics2D)g2d.create();
g2d.setColor(Color.RED);
//fixed positions for radius as context is rotated
int xx = 0;
int yy = r;
for (int i = 0; i < nCircles;i++) {
g2d.rotate(theta, xc, yc);
// xx-diam/2 just places the center of the orbiting circle at
// the proper radius from the center of the panel. Same for yy.
g2d.drawOval(xc + xx - diam / 2, yc + yy - diam / 2, diam,
diam);
}
g2d.dispose();
}
private void drawUsingTrig(Graphics2D g2d) {
g2d = (Graphics2D)g2d.create();
g2d.setColor(Color.BLUE);
for (Point p : coords1) {
int x = (int)p.getX();
int y = (int)p.getY();
g2d.drawOval(x-diam/2, y-diam/2, diam, diam);
}
g2d.dispose();
}
}
Math.sin and Math.cos methods expect a value in radians. You can convert degrees to radians by multiplying with Math.PI/180
Therefore, try changing Math.cos(i * 360 / n) and Math.sin(i * 360 / n) to Math.cos((i * 360 / n)*(Math.PI/180)) and Math.sin((i * 360 / n)*(Math.PI/180)).
I'm trying to draw a rotated shape at a given point. To give an example, in the following image, the red rectangle is a non-rotated rectangle drawn at a point and then the blue rectangle is rotated and drawn at the same position. The blue rectangle is the outcome I'm aiming for.
I've been experimenting and trying different methods. Currently, here is what I used for the image:
Point point = new Point(300, 300);
Dimension dim = new Dimension(200, 100);
double radians = Math.toRadians(30);
g.setColor(new java.awt.Color(1f, 0f, 0f, .5f));
g.fillRect(point.x, point.y, dim.width, dim.height);
translate(g, dim, radians);
g.rotate(radians, point.getX(), point.getY());
g.setColor(new java.awt.Color(0f, 0f, 1f, .5f));
g.fillRect(point.x, point.y, dim.width, dim.height);
private static void translate(Graphics2D g, Dimension dim, double radians) {
if (radians > Math.toRadians(360)) {
radians %= Math.toRadians(360);
}
int xOffsetX = 0;
int xOffsetY = 0;
int yOffsetX = 0;
int yOffsetY = 0;
if (radians > 0 && radians <= Math.toRadians(90)) {
xOffsetY -= dim.getHeight();
} else if (radians > Math.toRadians(90) && radians <= Math.toRadians(180)) {
xOffsetX -= dim.getWidth();
xOffsetY -= dim.getHeight();
yOffsetY -= dim.getHeight();
} else if (radians > Math.toRadians(180) && radians <= Math.toRadians(270)) {
xOffsetX -= dim.getWidth();
yOffsetX -= dim.getWidth();
yOffsetY -= dim.getHeight();
} else {
yOffsetX -= dim.getWidth();
}
int x = rotateX(xOffsetX, xOffsetY, radians);
int y = rotateY(yOffsetX, yOffsetY, radians);
g.translate(x, y);
}
private static int rotateX(int x, int y, double radians) {
if (x == 0 && y == 0) {
return 0;
}
return (int) Math.round(x * Math.cos(radians) - y * Math.sin(radians));
}
private static int rotateY(int x, int y, double radians) {
if (x == 0 && y == 0) {
return 0;
}
return (int) Math.round(x * Math.sin(radians) + y * Math.cos(radians));
}
This works for rectangles but doesn't work for other types of shapes. I'm trying to figure out if there is a way to accomplish this for every type of shape. Also note that the code is just for testing purposes and there are a lot of bad practices in it, like calling Math.toRadians so much.
Something like this?
It can be achieved using a rotate transform first, then using the bounds of the rotated shape as a basis, the translate transform can be used to shift it back to meet the top most y and leftmost x values of the original rectangle.
See the getImage() method for one implementation of that.
int a = angleModel.getNumber().intValue();
AffineTransform rotateTransform = AffineTransform.getRotateInstance((a*2*Math.PI)/360d);
// rotate the original shape with no regard to the final bounds
Shape rotatedShape = rotateTransform.createTransformedShape(rectangle);
// get the bounds of the rotated shape
Rectangle2D rotatedRect = rotatedShape.getBounds2D();
// calculate the x,y offset needed to shift it to top/left bounds of original rectangle
double xOff = rectangle.getX()-rotatedRect.getX();
double yOff = rectangle.getY()-rotatedRect.getY();
AffineTransform translateTransform = AffineTransform.getTranslateInstance(xOff, yOff);
// shift the new shape to the top left of original rectangle
Shape rotateAndTranslateShape = translateTransform.createTransformedShape(rotatedShape);
Here is the complete source code:
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.EmptyBorder;
public class TransformedShape {
private JComponent ui = null;
JLabel output = new JLabel();
JToolBar tools = new JToolBar("Tools");
ChangeListener changeListener = (ChangeEvent e) -> {
refresh();
};
int pad = 5;
Rectangle2D.Double rectangle = new Rectangle2D.Double(pad,pad,200,100);
SpinnerNumberModel angleModel = new SpinnerNumberModel(30, 0, 90, 1);
public TransformedShape() {
initUI();
}
private BufferedImage getImage() {
int a = angleModel.getNumber().intValue();
AffineTransform rotateTransform = AffineTransform.getRotateInstance((a*2*Math.PI)/360d);
Shape rotatedShape = rotateTransform.createTransformedShape(rectangle);
Rectangle2D rotatedRect = rotatedShape.getBounds2D();
double xOff = rectangle.getX()-rotatedRect.getX();
double yOff = rectangle.getY()-rotatedRect.getY();
AffineTransform translateTransform = AffineTransform.getTranslateInstance(xOff, yOff);
Shape rotateAndTranslateShape = translateTransform.createTransformedShape(rotatedShape);
Area combinedShape = new Area(rotateAndTranslateShape);
combinedShape.add(new Area(rectangle));
Rectangle2D r = combinedShape.getBounds2D();
BufferedImage bi = new BufferedImage((int)(r.getWidth()+(2*pad)), (int)(r.getHeight()+(2*pad)), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(new Color(255,0,0,127));
g.fill(rectangle);
g.setColor(new Color(0,0,255,127));
g.fill(rotateAndTranslateShape);
g.dispose();
return bi;
}
private void addModelToToolbar(String label, SpinnerNumberModel model) {
tools.add(new JLabel(label));
JSpinner spinner = new JSpinner(model);
spinner.addChangeListener(changeListener);
tools.add(spinner);
}
public final void initUI() {
if (ui!=null) return;
ui = new JPanel(new BorderLayout(4,4));
ui.setBorder(new EmptyBorder(4,4,4,4));
ui.add(output);
ui.add(tools,BorderLayout.PAGE_START);
addModelToToolbar("Angle", angleModel);
refresh();
}
private void refresh() {
output.setIcon(new ImageIcon(getImage()));
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = () -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
ex.printStackTrace();
}
TransformedShape o = new TransformedShape();
JFrame f = new JFrame(o.getClass().getSimpleName());
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setLocationByPlatform(true);
f.setContentPane(o.getUI());
f.pack();
f.setMinimumSize(f.getSize());
f.setVisible(true);
};
SwingUtilities.invokeLater(r);
}
}
You have a shape, any shape.
You have a point (px,py) and you want to rotate the shape around this point and angle ag measured counter-clokwise.
For each point of the shape the proccess has three steps:
Translate to (px,py)
Rotate
Translate back to (0,0)
The translation is fully simple
xNew = xOld - px
yNew = yOld - py
The rotation is a bit less simple
xRot = xNew * cos(ag) - yNew * sin(ag)
yRot = xNew * sin(ag) + yNew * cos(ag)
Finally the translation back:
xDef = xRot + px
yDef = yRot + py
A bit of explanation: Any transformation can be seen in two ways: 1) I move the shape 2) I move the axis-system. If you think about it, you'll find that the trasnsformation is relative: seen from the axis point of view or seen from the shape point of view.
So, you can say "I want coordinates in the translated system", or you can also say "I want the coordinates of the translated shape".
It doesn't matter what point of view you chose, the equations are the same.
I'm explaining this so much, just to achieve you realize which is the positive direction of the angle: clockwise or counter-clockwise.
here i'm trying to draw a circle using drawOval method and I want to move it on the screen with a specific velocity. but i have a problem with double variables for the velocity.for example when vx=0.25 and vy=0 the circle is just stuck on its place.
sorry for my bad English though.
here is the java code that i'm using
int x=0 , y=0;
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
move();
g.drawOval(x, y, 10, 10);
repaint();
}
public void move() {
x+=0.25;
y+=0.25;
}
You should not call move from the paintComponent method! You never know when this method will be called, and thus, you cannot control the movement speed properly.
You should not call repaint from the paintComponent method! Never. This will send the painting system into an endless cycle of repaint operations!
Regarding the question:
There is a method for drawing arbitrary shapes based on double coordinates. This is also covered and explained extensively in the 2D Graphics Tutorial. The key is to use the Shape interface. For your particular example, the relevant part of the code is this:
private double x = 0;
private double y = 0;
#Override
public void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
double radius = 5;
g.draw(new Ellipse2D.Double(
x - radius, y - radius, radius * 2, radius * 2));
}
That is, you create an Ellipse2D instance, and then just draw it.
Here is an MVCE, showing what you're probably trying to accomplish:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class PaintWithDouble
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
PaintWithDoublePanel p = new PaintWithDoublePanel();
f.getContentPane().add(p);
startMoveThread(p);
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static void startMoveThread(PaintWithDoublePanel p)
{
Thread t = new Thread(() -> {
while (true)
{
p.move();
p.repaint();
try
{
Thread.sleep(20);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
return;
}
}
});
t.setDaemon(true);
t.start();
}
}
class PaintWithDoublePanel extends JPanel
{
private double x = 0;
private double y = 0;
#Override
public void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
double radius = 5;
g.draw(new Ellipse2D.Double(
x - radius, y - radius, radius * 2, radius * 2));
g.drawString("At " + x + ", " + y, 10, 30);
}
public void move()
{
x += 0.05;
y += 0.05;
}
}
Edited in response to the comment (and to clarify some things that have been said in other answers) :
While it is technically correct to say that there are "only whole pixels", and there "is no pixel with coordinates (0.3, 1.8)", this does not mean that fractional coordinates will not affect the final appearance of the rendered output. Every topic becomes a science when you're studying it long enough. Particularly, a lot of research went into the question of how to improve the visual appearance of rendered output, going beyond what you can achieve with a trivial Bresenham or so. An entry point for further research could be the article about subpixel rendering.
In many cases, as usual, there are trade-offs between the appearance and the drawing performance. As for Java and its 2D drawing capabilities, these trade-offs are mostly controlled via the RenderingHints class. For example, there is the RenderingHints#VALUE_STROKE_PURE that enables subpixel rendering. The effect is shown in this screen capture:
The slider is used to change the y-offset of the rightmost point of a horizontal line by -3 to +3 pixels. In the upper left, you see a line, rendered as-it-is. In the middle, you see the line magnified by a factor of 8, to better show the effect: The pixels are filled with different opacities, depending on how much of the pixel is covered by an idealized, 1 pixel wide line.
While it's certainly the case that this is not relevant for most application cases, it might be worth noting here.
The following is an MCVE that was used for the screen capture:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
public class PaintWithDoubleMagnified
{
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().setLayout(new BorderLayout());
PaintWithDoubleMagnifiedPanel p = new PaintWithDoubleMagnifiedPanel();
f.getContentPane().add(p, BorderLayout.CENTER);
JSlider slider = new JSlider(0, 100, 50);
slider.addChangeListener(e -> {
int value = slider.getValue();
double relative = -0.5 + value / 100.0;
p.setY(relative * 6);
});
f.getContentPane().add(slider, BorderLayout.SOUTH);
f.setSize(500, 500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class PaintWithDoubleMagnifiedPanel extends JPanel
{
private double y = 0;
#Override
public void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D) gr;
g.drawString("At " + y, 10, 20);
paintLine(g);
BufferedImage image = paintIntoImage();
g.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
g.scale(8.0, 8.0);
g.drawImage(image, 0, 0, null);
}
public void setY(double y)
{
this.y = y;
repaint();
}
private void paintLine(Graphics2D g)
{
g.setColor(Color.BLACK);
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(
RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
Line2D line = new Line2D.Double(
10, 30, 50, 30 + y);
g.draw(line);
}
private BufferedImage paintIntoImage()
{
BufferedImage image = new BufferedImage(
100, 100, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
paintLine(g);
g.dispose();
return image;
}
}
First notice that rendering system is using int as arguments for each pixel.
So if p1 is near p2 on x axis then
p1(x,y) and p2(x+1,y)
eg:(0,0) and (1,0)
You do not have something in the middle like (0.5,1) since no pixels.
That why Graphics api is using int for (x,y) coordinates.
Graphics api
If you wanted to consider also double you have to adapt the default coordinates systems to fit your needs.(cannot render all double there in individual pixels, need to group them in categories)
Eg. say want to place x_points : 0, 0.5, 1
So 0->0, 0.5(double)->1(int) , 1->2
Other pixels could map as 0.2->1, 0.7->2 , -0.9->0
One rule map consider all double range (better say in (-0.5,1])
can be -0.5<d<=0 -> 0,0<d<=0.5 -> 1, 0.5<d<=1 -> 2 where d=input_x(double)
That means you adjust the coordinates systems to fit your needs
is there a method in java for drawing a circle with double variables for its center?
NO(using standard Graphics api). Have just what api is provided, but you could render what ever input you wanted (even based on double) by adjusting coordinates system.
class MyPaint extends JPanel
{
private double x = 0, y=0;
private int width = 30, height = 30;
//adjust coordinates system
//for x in [0,1] have [0,0.1,0.2,0.3 ..]
//from no pixel between (0,1) to 9 pixels (0,0.1, ..,1)
//0->0,0.1->1,0.2->2,0.9->9,1->10
//in that way you have full control of rendering
private double scale_x = 0.1;
//same on y as x
private double scale_y = 0.1;
//pixel scaled on x,y
//drawing with
private int xs,ys;
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
xs = (int) (x/scale_x);
ys = (int) (y/scale_y);
g.drawString("Draw At: " + xs + ", " + ys + " From:" + x+","+y, 10, 30);
g.drawOval(xs, ys, (int) (width/scale_x), (int) (height/scale_y));
}
public void move()
{
//adjustments is better to be >= then scale(x or y) seen as absolute value
//if need 0.01 to be display on individual pixel on x
//then modify scale_x = 0.01 (or even 0.001)
x+=0.1;
y+=0.5;
}
}
I'm trying to draw a circle with a random center inside a big bigger circular surface. (I'm actually trying to simulate a human and his eyesight inside a room!) I need to draw a random line (call it line1) passing through its center which will intersect with the surface. line1 does not necessarily pass the center of circular surface. I also need to draw two lines forming 60 degree, facing on one side of line1. Can anyone help me with that?
I created an example of what I need to draw.
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Point;
import java.util.Random;
import javax.swing.JFrame;
public class ShapeTest extends JFrame{
int width=500;
int height=500;
public ShapeTest(){
setSize(width,height);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String a[]){
new ShapeTest();
}
public void paint(Graphics g){
// Circular Surface
drawCircleByCenter(g, width/2, height/2, width/2);
Random r = new Random();
Point center = new Point();
center.x=r.nextInt(width/2);
center.y=r.nextInt(width/2);
drawCircleByCenter(g, center.x, center.y, width/15);
}
void drawCircleByCenter(Graphics g, int x, int y, int radius){
//g.setColor(Color.LIGHT_GRAY);
g.drawOval(x-radius, y-radius, 2*radius, 2*radius);
}
}
Start by changing your method to draw a circle based on its center and radius to a method which returns a Ellipse2D object representing the circle. This will allow us to do some clipping and other things with the shape besides just draw it.
Setting the clip to be the shape of your large circle prevents stray marks from being made where you don't want them (think "color inside the lines"). This is important because when we draw the circles and lines inside the big circle, some of them will be too big and would otherwise mark outside the bounds of the big circle.
Once we set the clip, we use the method Line2D getVector(Point2D, double, length) with an origin at the center of the large circle, a random angle and a random length (capped to keep the small blue circle inside the big circle). Think of this a random polar coordinate with the center of the large circle as the origin. The end point of this vector is used to mark the center of the small circle.
Using the center of the small circle as a starting point, we can generate two vectors in opposite directions (just negate the length of one to get it going the other direction) by using a random direction angle. We use a length equal to the diameter of the big circle to make certain that the lines will always go all the way up to the edge of the big circle (but not past, thanks to our clip).
We simply add 60 and 120 degrees to the angle of our blue dashed line and draw two green lines calculating the vectors the same way we did for the two blue dashed lines, except we don't need to create ones with negated lengths. We can also add a normal vector in for good measure simply by adding 90 degrees to the angle of the blue dashed line.
Lastly, we pick some random polar coordinates (just like we did for the small blue circle) to represent some people, and using the intersection of the people with the areas created by the various lines, we can see where they are at and draw them up with color coded values.
Now that we have all the people, we eliminate the clip and draw the big circle and voila!
Check out Draw a line at a specific angle in Java for details on how I calculated the vectors for the lines.
But enough talk, here's the code:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ShapeTest extends JFrame {
private static final long serialVersionUID = 1L;
private int width = 500;
private int height = 500;
private int padding = 50;
private BufferedImage graphicsContext;
private JPanel contentPanel = new JPanel();
private JLabel contextRender;
private Stroke dashedStroke = new BasicStroke(3.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 2f, new float[] {3f, 3f}, 0f);
private Stroke solidStroke = new BasicStroke(3.0f);
private RenderingHints antialiasing;
private Random random = new Random();
public static void main(String[] args) {
//you should always use the SwingUtilities.invodeLater() method
//to perform actions on swing elements to make certain everything
//is happening on the correct swing thread
Runnable swingStarter = new Runnable()
{
#Override
public void run(){
new ShapeTest();
}
};
SwingUtilities.invokeLater(swingStarter);
}
public ShapeTest(){
antialiasing = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphicsContext = new BufferedImage(width + (2 * padding), width + (2 * padding), BufferedImage.TYPE_INT_RGB);
contextRender = new JLabel(new ImageIcon(graphicsContext));
contentPanel.add(contextRender);
contentPanel.setSize(width + padding * 2, height + padding * 2);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
this.setContentPane(contentPanel);
//take advantage of auto-sizing the window based on the size of its contents
this.pack();
this.setLocationRelativeTo(null);
this.paint();
setVisible(true);
}
public void paint() {
Graphics2D g2d = graphicsContext.createGraphics();
g2d.setRenderingHints(antialiasing);
//Set up the font to print on the circles
Font font = g2d.getFont();
font = font.deriveFont(Font.BOLD, 14f);
g2d.setFont(font);
FontMetrics fontMetrics = g2d.getFontMetrics();
//clear the background
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, graphicsContext.getWidth(), graphicsContext.getHeight());
//set up the large circle
Point2D largeCircleCenter = new Point2D.Double((double)width / 2 + padding, (double)height / 2 + padding);
double largeCircleRadius = (double)width / 2;
Ellipse2D largeCircle = getCircleByCenter(largeCircleCenter, largeCircleRadius);
//here we build the small circle
Point2D smallCircleCenter = new Point2D.Double();
double smallCircleRadius = 15;
//we need to make certain it is confined inside the larger circle
//so we choose the following values carefully
//we want to go a random direction from the circle, so chose an
//angle randomly in any direction
double smallCenterVectorAngle = random.nextDouble() * 360.0d;
//and we want to be a random distance from the center of the large circle, but
//we limit the distance based on the radius of the small circle to prevent it
//from appearing outside the large circle
double smallCenterVectorLength = random.nextDouble() * (largeCircleRadius - smallCircleRadius);
Line2D vectorToSmallCenter = getVector(largeCircleCenter, smallCenterVectorAngle, smallCenterVectorLength);
//the resulting end point of the vector is a random distance from the center of the large circle
//in a random direction, and guaranteed to not place the small circle outside the large
smallCircleCenter.setLocation(vectorToSmallCenter.getP2());
Ellipse2D smallCircle = getCircleByCenter(smallCircleCenter, smallCircleRadius);
//before we draw any of the circles or lines, set the clip to the large circle
//to prevent drawing outside our boundaries
g2d.setClip(largeCircle);
//chose a random angle for the line through the center of the small circle
double angle = random.nextDouble() * 360.0d;
//we create two lines that start at the center and go out at the angle in
//opposite directions. We use 2*largeCircleRadius to make certain they
//will be large enough to fill the circle, and the clip we set prevent stray
//marks outside the big circle
Line2D centerLine1 = getVector(smallCircleCenter, angle, largeCircleRadius * 2);
Line2D centerLine2 = getVector(smallCircleCenter, angle, -largeCircleRadius * 2);
//now we just add 20 and 120 to our angle for the center-line, start at the center
//and again, use largeCircleRadius*2 to make certain the lines are big enough
Line2D sightVector1 = getVector(smallCircleCenter, angle + 60, largeCircleRadius * 2);
Line2D sightVector2 = getVector(smallCircleCenter, angle + 120, largeCircleRadius * 2);
Path2D visible = new Path2D.Double();
visible.moveTo(sightVector1.getX2(), sightVector1.getY2());
visible.lineTo(smallCircleCenter.getX(), smallCircleCenter.getY());
visible.lineTo(sightVector2.getX2(), sightVector2.getY2());
visible.closePath();
Path2D greenSide = new Path2D.Double();
greenSide.moveTo(centerLine1.getX2(), centerLine1.getY2());
greenSide.lineTo(smallCircleCenter.getX(), smallCircleCenter.getY());
greenSide.lineTo(centerLine2.getX2(), centerLine2.getY2());
greenSide.lineTo(sightVector1.getX2(), sightVector1.getY2());
greenSide.closePath();
int personCount = 5;
Area visibleArea = new Area(visible);
visibleArea.intersect(new Area(largeCircle));
Area greenSideArea = new Area(greenSide);
greenSideArea.intersect(new Area(largeCircle));
//we create a list of the people in the circle to
//prevent overlap
ArrayList<Shape> people = new ArrayList<Shape>();
people.add(smallCircle);
int i = 0;
personLoop: while (i < personCount){
double personCenterVectorAngle = random.nextDouble() * 360.0d;
double personCenterVectorLength = random.nextDouble() * (largeCircleRadius - smallCircleRadius);
Line2D vectorToPersonCenter = getVector(largeCircleCenter, personCenterVectorAngle, personCenterVectorLength);
Point2D personCircleCenter = vectorToPersonCenter.getP2();
Ellipse2D personCircle = getCircleByCenter(personCircleCenter, smallCircleRadius);
//this little loop lets us skip a person if they have overlap
//with another person, since people don't generally overlap
Area personArea = new Area(personCircle);
for (Shape person : people)
{
Area overlapArea = new Area(person);
overlapArea.intersect(personArea);
//this means that we have found a conflicting
//person, so should skip them
if (!overlapArea.isEmpty()){
continue personLoop;
}
}
people.add(personCircle);
personArea.intersect(visibleArea);
Area greenSideAreaTest = new Area(personCircle);
greenSideAreaTest.intersect(greenSideArea);
if (personArea.isEmpty()){
if (greenSideAreaTest.isEmpty()){
g2d.setColor(Color.orange);
System.out.println("Person " + i + " is behind the blue line");
}
else {
System.out.println("Person " + i + " is in front of the blue line");
g2d.setColor(Color.cyan);
}
}
else
{
System.out.println("Person " + i + " is between the green lines");
g2d.setColor(Color.magenta);
}
//alternatively to circles intersecting the area of interest, we can check whether the center
//is in the area of interest which may make more intuitive sense visually
// if (visibleArea.contains(personCircleCenter)){
// System.out.println("Person " + i + " is between the green lines");
// g2d.setColor(Color.magenta);
// }
// else {
// if (greenSideArea.contains(personCircleCenter)) {
// System.out.println("Person " + i + " is in front of the blue line");
// g2d.setColor(Color.cyan);
// }
// else{
// g2d.setColor(Color.orange);
// System.out.println("Person " + i + " is behind the blue line");
// }
// }
g2d.fill(personCircle);
g2d.setColor(Color.black);
String itemString = "" + i;
Rectangle2D itemStringBounds = fontMetrics.getStringBounds(itemString, g2d);
double textX = personCircleCenter.getX() - (itemStringBounds.getWidth() / 2);
double textY = personCircleCenter.getY() + (itemStringBounds.getHeight()/ 2);
g2d.drawString("" + i, (float)textX, (float)textY);
i++;
}
//fill the small circle with blue
g2d.setColor(Color.BLUE);
g2d.fill(smallCircle);
//draw the two center lines lines
g2d.setStroke(dashedStroke);
g2d.draw(centerLine1);
g2d.draw(centerLine2);
//create and draw the black offset vector
Line2D normalVector = getVector(smallCircleCenter, angle + 90, largeCircleRadius * 2);
g2d.setColor(Color.black);
g2d.draw(normalVector);
//draw the offset vectors
g2d.setColor(new Color(0, 200, 0));
g2d.draw(sightVector1);
g2d.draw(sightVector2);
//we save the big circle for last, to cover up any stray marks under the stroke
//of its perimeter. We also set the clip back to null to prevent the large circle
//itselft from accidentally getting clipped
g2d.setClip(null);
g2d.setStroke(solidStroke);
g2d.setColor(Color.BLACK);
g2d.draw(largeCircle);
g2d.dispose();
//force the container for the context to re-paint itself
contextRender.repaint();
}
private static Line2D getVector(Point2D start, double degrees, double length){
//we just multiply the unit vector in the direction we want by the length
//we want to get a vector of correct direction and magnitute
double endX = start.getX() + (length * Math.sin(Math.PI * degrees/ 180.0d));
double endY = start.getY() + (length * Math.cos(Math.PI * degrees/ 180.0d));
Point2D end = new Point2D.Double(endX, endY);
Line2D vector = new Line2D.Double(start, end);
return vector;
}
private static Ellipse2D getCircleByCenter(Point2D center, double radius)
{
Ellipse2D.Double myCircle = new Ellipse2D.Double(center.getX() - radius, center.getY() - radius, 2 * radius, 2 * radius);
return myCircle;
}
}
The logic of the geometry turned out to be more tricky than I'd presumed, but this is what I think you are after.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.swing.*;
class HumanEyesightLines {
int rad = 150;
int radSmall = 15;
int pad = 10;
JPanel gui = new JPanel(new BorderLayout());
BufferedImage img = new BufferedImage(
2 * (rad + pad),
2 * (rad + pad),
BufferedImage.TYPE_INT_RGB);
Timer timer;
JLabel imgDisplay;
Random rnd = new Random();
RenderingHints rh = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
HumanEyesightLines() {
imgDisplay = new JLabel(new ImageIcon(img));
gui.add(imgDisplay);
File f = new File(System.getProperty("user.home"));
final File f0 = new File("HumanEyesiteLines");
f0.mkdirs();
try {
Desktop.getDesktop().open(f0);
} catch (IOException ex) {
ex.printStackTrace();
}
ActionListener animationListener = new ActionListener() {
int ii = 0;
#Override
public void actionPerformed(ActionEvent e) {
paintImage();
ii++;
if (ii < 100) {
System.out.println(ii);
File f1 = new File(f0, "eg" + ii + ".png");
try {
ImageIO.write(img, "png", f1);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
};
timer = new Timer(100, animationListener);
paintImage();
}
float[] dash = {3f, 3f};
float phase = 0f;
private final void paintImage() {
Graphics2D g = img.createGraphics();
g.setRenderingHints(rh);
g.setStroke(new BasicStroke(2f));
// fill the BG
g.setColor(Color.WHITE);
g.fillRect(0, 0, 2 * (rad + pad), 2 * (rad + pad));
// draw the big circle
Point center = new Point(rad + pad, rad + pad);
Shape bigCircle = new Ellipse2D.Double(pad, pad, 2 * rad, 2 * rad);
g.setColor(Color.MAGENTA.darker());
g.fill(bigCircle);
// set the clip to that of the big circle
g.setClip(bigCircle);
// draw the small circle
int xOff = rnd.nextInt(rad) - rad / 2;
int yOff = rnd.nextInt(rad) - rad / 2;
int x = center.x - xOff;
int y = center.y - yOff;
Shape smallCircle = new Ellipse2D.Double(
x - radSmall, y - radSmall,
2 * radSmall, 2 * radSmall);
g.setColor(Color.YELLOW);
g.fill(smallCircle);
g.setColor(Color.ORANGE);
g.draw(smallCircle);
g.setStroke(new BasicStroke(
1.5f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND,
2f,
dash,
phase));
// I don't know what the rule is for where the blue line goes, so
// will use the top left corner of the image as a 2nd anchor point.
int x0 = 0;
int y0 = 0;
double grad = (double) (y - y0) / (double) (x - x0);
// now calculate the RHS point from y = mx + b
// where b = 0 and m is the gradient
int x1 = 2 * (pad + rad);
int y1 = (int) (grad * x1);
Line2D.Double line1 = new Line2D.Double(x0, y0, x1, y1);
g.setColor(Color.BLUE);
g.draw(line1);
//find the perpendicular gradient.
double perpGrad = -1d / grad;
double perpTheta = Math.atan(perpGrad);
// angle from perp
double diffTheta = Math.PI / 6d;
g.setColor(Color.GREEN);
double viewLine1Theta = perpTheta + diffTheta;
Line2D.Double viewLine1 = getLine(x, y, viewLine1Theta);
double viewLine2Theta = perpTheta - diffTheta;
Line2D.Double viewLine2 = getLine(x, y, viewLine2Theta);
g.draw(viewLine1);
g.draw(viewLine2);
g.setColor(Color.BLACK);
Line2D.Double viewPerp = getLine(x, y, perpTheta);
g.draw(viewPerp);
g.setColor(Color.RED);
g.draw(bigCircle);
g.dispose();
imgDisplay.repaint();
}
/**
* Returns a Line2D starting at the point x1,y1 at angle theta.
*/
private final Line2D.Double getLine(double x1, double y1, double theta) {
double m;
double b;
double x2;
double y2;
if (theta < (-Math.PI / 2d)) {
System.out.println("CHANGE IT! " + theta);
m = Math.tan(theta);
b = y1 - (m * x1);
x2 = 0;
y2 = (m * x2) + b;
} else {
m = Math.tan(theta);
b = y1 - (m * x1);
x2 = 2 * (rad + pad);
y2 = (m * x2) + b;
}
/*
* System.out.println("Perp theta: " + theta); System.out.println("Line
* grad: " + m); System.out.println("Line off: " + b);
* System.out.println("x1,y1: " + x1 + "," + y1);
* System.out.println("x2,y2: " + x2 + "," + y2);
*
*/
return new Line2D.Double(x1, y1, x2, y2);
}
public JComponent getGui() {
return gui;
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
HumanEyesightLines hel = new HumanEyesightLines();
hel.start();
JOptionPane.showMessageDialog(null, hel.getGui());
hel.stop();
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
I'm trying to make a custom swing control that is a meter.
Swing Meter http://dl.dropbox.com/u/2363305/Programming/Java/swing_meter.gif
The arrow will move up and down. Here is my current code, but I feel I've done it wrong.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LinearGradientPaint;
import java.awt.Polygon;
import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class meter extends JFrame {
Stroke drawingStroke = new BasicStroke(2);
Rectangle2D rect = new Rectangle2D.Double(105, 50, 40, 200);
Double meterPercent = new Double(0.57);
public meter() {
setTitle("Meter");
setLayout(null);
setSize(300, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
public void paint(Graphics g) {
// Paint Meter
Graphics2D g1 = (Graphics2D) g;
g1.setStroke(drawingStroke);
g1.draw(rect);
// Set Meter Colors
Point2D start = new Point2D.Float(0, 0);
Point2D end = new Point2D.Float(0, this.getHeight());
float[] dist = { 0.1f, 0.5f, 0.9f };
Color[] colors = { Color.green, Color.yellow, Color.red };
LinearGradientPaint p = new LinearGradientPaint(start, end, dist,
colors);
g1.setPaint(p);
g1.fill(rect);
// Make a triangle - Arrow on Meter
int[] x = new int[3];
int[] y = new int[3];
int n; // count of points
// Set Points for Arrow
Integer meterArrowHypotenuse = (int) rect.getX();
Integer meterArrowTip = (int) rect.getY()
+ (int) (rect.getHeight() * (1 - meterPercent));
x[0] = meterArrowHypotenuse - 25;
x[1] = meterArrowHypotenuse - 25;
x[2] = meterArrowHypotenuse - 5;
y[0] = meterArrowTip - 20; // Top Left
y[1] = meterArrowTip + 20; // Bottom Left
y[2] = meterArrowTip; // Tip of Arrow
n = 3; // Number of points, 3 because its a triangle
// Draw Arrow Border
Polygon myTriShadow = new Polygon(x, y, n); // a triangle
g1.setPaint(Color.black);
g1.fill(myTriShadow);
// Set Points for Arrow Board
x[0] = x[0] + 1;
x[1] = x[1] + 1;
x[2] = x[2] - 2;
y[0] = y[0] + 3;
y[1] = y[1] - 3;
y[2] = y[2];
Robot robot = new Robot();
Color colorMeter = robot.getPixelColor(x[2]+10, y[2]);
// Draw Arrow
Polygon myTri = new Polygon(x, y, n); // a triangle
Color colr = new Color(colorMeter.getRed(), colorMeter.getGreen(), colorMeter.getBlue());
g1.setPaint(colr);
g1.fill(myTri);
}
public static void main(String[] args) {
new meter();
}
}
Thanks for looking.
In addition to #Jonas' example, you might like to look at the article How to Write a Custom Swing Component.
Addendum: On reflection, it looks a little intimidating, but you can extend BasicSliderUI and reuse some of your code in paintThumb() and paintTrack().
JSlider slider = new JSlider();
slider.setUI(new MySliderUI(slider));
...
private static class MySliderUI extends BasicSliderUI {
public MySliderUI(JSlider b) {
super(b);
}
#Override
public void paintTrack(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Rectangle r = trackRect;
g2d.setPaint(new GradientPaint(
r.x, r.y, Color.red, r.x + r.width, r.y + r.height, Color.blue));
g.fillRect(r.x, r.y, r.width, r.height);
}
#Override
public void paintThumb(Graphics g) {
super.paintThumb(g); // replace with your fill()
}
}
You could use a JSlider and use setValue(int n) to set the value whenever you need. You can also change the default appearance, so you get an arrow and a gradient as you want.