Is there any benefit to calling drawPolyline() versus iterating through each line and calling drawLine() in the Graphics2D class?
For example:
graphics2d.drawPolyline(xPoints, yPoints, nPoints);
versus:
for (MyBean line : myBeans) {
graphics2d.drawLine(line.getX1Point(), line.getY1Point(), line.getX2Point(), line.getY2Point());
}
Is the first a convenience method for the second?
Fairly new to AWT. (I realize that the first one may be more concise.)
Edit: I call BufferedImage.createGraphics() for the implementation of Graphics2D.
There is an important difference: With drawPolyline you are drawing a single polyline. With drawLine you are drawing individual lines. So far, so obvious. But what does it mean?
The difference mainly shows up when assigning a "non-trivial" Stroke to the graphics object - usually, a certain BasicStroke. This receives several parameters in the constructor. The important one regarding your question is the join parameter. It can be JOIN_BEVEL, JOIN_METER and JOIN_ROUND. It determines how two connected lines are joined. And this, obviously, can only be applied when it is known that the lines are connected, which is only the case in the drawPolyline call. It simply can not be applied for individual drawLine calls.
The following is a screenshot showing this difference. It uses a stroke with a witdh of 15 and a join=BasicStroke.JOIN_ROUND. The left part is drawn with drawPolyline, and the right one is drawn as individual lines:
But you should not usually not use drawPolyline anyhow...
... because it is somehow out-dated and has several shortcomings. First of all, it is a hassle to create the arrays that are required for calling it. And importantly, it only accepts int[] arrays.
The whole Java 2D painting infrastructure was originally focussing on int coordinates, like in Graphics#drawLine(int,int,int,int). This has been generalized, and the Graphics2D methods allow a much greater flexibility here. So the usual way to draw a polyline nowadays would be to create a Shape object containing the polyline. In most cases, this will be a Path2D instance:
Path2D path = new Path2D.Double();
path.moveTo(x0,y0);
path.lineTo(x1,y1);
path.lineTo(x2,y2);
...
graphics2D.draw(path);
However, just for reference, here is the code that was used to create the above image:
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class DrawLineVsDrawPolyline
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
createAndShowGUI();
}
});
}
private static void createAndShowGUI()
{
JFrame f = new JFrame("");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new BorderLayout());
class Line
{
int x1, y1, x2, y2;
Line(int x1, int y1, int x2, int y2)
{
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
public int getX1Point()
{
return x1;
}
public int getY1Point()
{
return y1;
}
public int getX2Point()
{
return x2;
}
public int getY2Point()
{
return y2;
}
}
int xPoints[] = new int[] { 100, 150, 200 };
int yPoints[] = new int[] { 100, 250, 100 };
int nPoints = xPoints.length;
List<Line> lines = new ArrayList<Line>();
for (int i0=0; i0<nPoints-1; i0++)
{
int i1 = i0+1;
int x1 = xPoints[i0];
int y1 = yPoints[i0];
int x2 = xPoints[i1];
int y2 = yPoints[i1];
lines.add(new Line(x1,y1,x2,y2));
}
JPanel panel = new JPanel()
{
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setColor(Color.RED);
g.setStroke(new BasicStroke(20.0f,
BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND));
g.drawPolyline(xPoints, yPoints, nPoints);
g.translate(200, 0);
for (Line line : lines) {
g.drawLine(
line.getX1Point(), line.getY1Point(),
line.getX2Point(), line.getY2Point());
}
}
};
f.getContentPane().add(panel, BorderLayout.CENTER);
f.setSize(500,500);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
Bearing in mind that Graphics2D is an abstract class, so you don't know which concrete implementation you are working with, I would use whichever method makes your code cleaner.
I rarely use drawPolyline() because it's usually a pain to get the coordinates into arrays for pass to the method. But you should also consider creating a GeneralPath object which can be passed to the Graphics2D.draw(Shape) method. Drawing a path might be more efficient than drawing individual lines for some implementations of Graphics2D. Occasionally I've noticed also that the output looks slightly better with a path rather than individual lines, particularly if the line color is partially transparent (you can sometimes see darker spots where the end points of the individual lines overlap).
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)).
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 fill a triangle using horizontal lines and I can't figure out what's wrong with my current method. Before anyone says to just use fillPolygon, I can't use that. I need to fill it using lines.
It seems to work ok in some situations and completely break in others.
That's how it should look. But then I tried applying my method to a rotating 3D cube and...
I have no idea what's wrong. Also, the red borders are also one of my triangle methods. Those work perfectly and the filled triangles and the outlined triangles have the same vertices inputted.
public void filledTri(int x1,int y1,int x2,int y2,int x3,int y3){
int[] xs = {x1,x2,x3};
int[] ys = {y1,y2,y3};
//Sort vertices in vertical order so A/1 is highest and C/3 is lowest
int I,tempx,tempy;
for(int i=1;i<3;i++){
I = i-1;
tempx = xs[i];
tempy = ys[i];
while(I>=0&&tempy<ys[I]){
xs[I+1] = xs[I];
ys[I+1] = ys[I];
I--;
}
xs[I+1] = tempx;
ys[I+1] = tempy;
}
//Set left and right edges
linepts ab = new linepts(xs[0],ys[0],xs[1],ys[1]),
ac = new linepts(xs[0],ys[0],xs[2],ys[2]);
linepts[] lines = {ab.getEndX() < ac.getEndX() ? ab : ac,
ab.getEndX() > ac.getEndX() ? ab : ac,
new linepts(xs[1],ys[1],xs[2],ys[2])};
//Fill triangle
int startY = ys[0],endY = ys[2];
for(int y=startY;y<=endY;y++){
if(y>ys[1])
horizontalLine((int)Math.round(lines[2].getX(y)),
y,
(int)Math.round(lines[1].getX(y)));
else
horizontalLine((int)Math.round(lines[0].getX(y)),
y,
(int)Math.round(lines[1].getX(y)));
}
getX(int y) gets me the x coordinate where the line passes through the y value. If it's a horizontal line it just returns the line's start x
Point A is the highest on screen and the lowest value, B is the middle, and C is the lowest on screen and highest value
I'm using a buffered image on a jframe to draw it if that helps.
I've seen what you are doing in a Software Renderer tutorial. It is explained in this and this episodes.
What he does there is scanning the longest to get every pixel on that line, it stores the min X value and max X value, (given by the other 2 lines). He originally makes it for specific triangles, but then he upgrades the code to accept generic triangles.
Here's a nice diagram to explain that:
I assume what you're experiencing is because of projecting 3D triangles into 2D ones (clipping, triangles get infinite coordinates, or because you're program doesn't takes too well empty triangles.
One way is to draw the lines to an image, then use that image in a TexturePaint to fill a Shape (the triangle in this case).
It might look something like this: (if you use a single image containing one red line, put it over a random BG color, and use a smoothed 1.5 pixel stroke to draw the shape itself in blue).
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.util.*;
public class LinesFillShape {
private JComponent ui = null;
LinesFillShape() {
initUI();
}
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(new JLabel(new ImageIcon(getImage())));
}
private void drawPolygon(Graphics2D g, int sz, Random r) {
int[] xpoints = {
r.nextInt(sz), r.nextInt(sz), r.nextInt(sz)
};
int[] ypoints = {
r.nextInt(sz), r.nextInt(sz), r.nextInt(sz)
};
Polygon p = new Polygon(xpoints, ypoints, 3);
Color bg = new Color(r.nextInt(255),r.nextInt(255),r.nextInt(255));
g.setColor(bg);
g.fill(p);
g.setPaint(
new TexturePaint(getTexture(),
new Rectangle2D.Double(0, 0, 8, 8)));
g.fill(p);
g.setStroke(new BasicStroke(1.5f));
g.setColor(Color.BLUE);
g.draw(p);
}
private BufferedImage getImage() {
int sz = 600;
BufferedImage bi = new BufferedImage(sz, sz, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Random r = new Random();
drawPolygon(g, sz, r);
drawPolygon(g, sz, r);
drawPolygon(g, sz, r);
g.dispose();
return bi;
}
private BufferedImage getTexture() {
BufferedImage bi = new BufferedImage(8, 8, BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.getGraphics();
g.setColor(Color.RED);
// TODO: something more interesting here..
g.drawLine(0, 0, 0, 8);
g.dispose();
return bi;
}
public JComponent getUI() {
return ui;
}
public static void main(String[] args) {
Runnable r = () -> {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception useDefault) {
}
LinesFillShape o = new LinesFillShape();
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);
}
}
I have not scrutinized your code but I can tell you that you are not always joining intersections with the relevant sides.
You can work as follows:
For a given scanline (some Y),
compare the ordinates of the endpoints of the three sides in pairs (Y0-Y1, Y1-Y2, Y2-Y0),
there will be zero or two sides that straddle Y; use the condition (Yi > Y) != (Yi+1 > Y) (indexes modulo 3), and no other,
for the sides that straddle Y, compute the intersection point.
You will scan from min(Y0, Y1, Y2) to max(Y0, Y1, Y2) and each time join the two intersections.
So Im basically just trying to Draw a whole bunch of random triangles to the screen in a loop while changing the colors which seemed not very difficult but i cannot find where my problem lies... it wont loop it just displays one image here's what i have
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;
import javax.swing.JFrame;
public class ManyTriangles extends Canvas {
public void paint(Graphics g) {
Random r = new Random();
int x1 = r.nextInt(350);
int x2 = r.nextInt(400);
int x3 = r.nextInt(300);
int y1 = r.nextInt(800);
int y2 = r.nextInt(200);
int y3 = r.nextInt(600);
int[] xpts = { x1, x2, x3 };
int[] ypts = { y1, y2, y3 };
int randomColor = r.nextInt(3);
for (int x = 0; x <= 500; x++) {
if (randomColor == 3) {
g.setColor(Color.green);
} else if (randomColor == 2) {
g.setColor(Color.red);
} else if (randomColor == 1) {
g.setColor(Color.blue);
}
g.fillPolygon(xpts, ypts, 3);
}
}
public static void main(String[] args) {
ManyTriangles canvas = new ManyTriangles();
JFrame frame = new JFrame("Lots of Triangle's");
frame.setSize(1024, 768);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(canvas);
frame.setVisible(true);
}
}
Your xpts and yppts never change within the loop, so you are painting the same thing over and over again in different colors
You've broken the paint chain by not calling super.paint
You're mixing heavy and light weight components (Canvas on a JFrame), this is not really a good idea...
Instead...
Move the creation of the xpts and yppts into the loop
Call super.paint before doing any custom painting or event better
Use a JPanel instead of a Canvas and override it's paintComponent method instead, making sure you call super.paintComponent before doing any custom painting...
See Painting in AWT and Swing, Performing Custom Painting for more details
Other issues...
Because you're re-generating the output each time paint is called, your output could change at random times (as the repaint manager schedules new repaint requests). If you don't want this, generate the shapes in the constructor or other method, adding them to some kind of List or array and iterate over this within your paintComponent method...
I've been writing this little painting program thing, but whenever I release the mouse and move to another point on the screen, the line is drawn over there. I tried clearing the points when the mouse has been released, but that deletes everything on screen.
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
public class PaintingCanvas extends Canvas implements MouseMotionListener, MouseListener {
private ArrayList<Point> points = new ArrayList<Point>();
public PaintingCanvas(int width, int height) {
setBounds(0, 0, width, height);
addMouseMotionListener(this);
addMouseListener(this);
}
public void paint(Graphics g) {
for (int i = 0; i < points.size() - 2; i++) {
Point p1 = points.get(i);
Point p2 = points.get(i + 1);
g.drawLine(p1.x, p1.y, p2.x, p2.y);
}
}
#Override
public void mouseDragged(MouseEvent e) {
points.add(e.getPoint());
repaint();
}
}
I suggest:
Call the super.paint(g); method first thing in your paint method.
When the mouse is released, paint the image represented by the points ArrayList to a BufferedImage and then clear the points ArrayList, and then call repaint().
Draw the BufferedImage in the paint method. before drawing your lines (but check that it's not null first). You do this with the Graphics#drawImage(Image image, int x, int y, ...) method.
Better, re-write this to work in Swing by painting in a JPanel's paintComponent method.
Of course this is happening - once you start drawing again, the new points are added after the old ones. Once you paint after that, they are included. You'll need to seperate the different paths from each other.
Have you looked at the Path classes? If you are simply drawing discrete lines to a screen, the GeneralPath class might be a simple solution.
The Drawing Arbitrary Shapes tutorial explains how to use these.
Basically, every time the user pressed the mouse (on a mousePressed event), you would call the path's moveTo(x, y) method. For every segment (replacing what you currently do in the mouseDragged() method), you would call the path's lineTo(x, y) method.
No matter what, you -definitely- need to handle mousePressed or mouseReleased events, or both, as you are looking for some way to indicate the start of a new line/path, rather than using the old one.
private void jPanel1MouseDragged(java.awt.event.MouseEvent evt) {
points.add(evt.getPoint());
for (int i = 0; i < points.size() - 2; i++)
{
Point p1 = points.get(i);
Point p2 = points.get(i + 1);
jPanel1.getGraphics().drawLine(p1.x, p1.y, p2.x, p2.y);
}
}
private void jPanel1MousePressed(java.awt.event.MouseEvent evt) {
points.clear();
points.add(evt.getPoint());
}
I would make a 2d arraylist in which the first array is points added to until you lift up your finger then a new array is added for the second line etc...
Insert a garbage point -1,-1 in points inside mouseReleased method and check for it inside paint method and skip that point. Updated code:
#Override
public void mouseReleased(MouseEvent e) {
points.add(new Point(-1, -1));
}
#Override
public void paint(Graphics g) {
for (int i = 0; i < points.size() - 1; i++) {
Point p = points.get(i+1);
int x2 = p.x;
int y2 = p.y;
if ( x2 == -1 || y2 == -1) {
++i;
//continue // Considered bad practice, can play havoc with your system. (source http://xkcd.com/292/ ).
}
else {
p = points.get(i);
int x1 = p.x;
int y1 = p.y;
g.drawLine(x1, y1, x2, y2);
}
}
}
OP, how it feels to see this question after 3 years? :D