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.
Related
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 need to:
1.) move the origin and also rotate the coordinate plane so that x-values progress rightward and y-values progress upward from the new origin(which needs to be the bottom left corner of the inner, blue rectangle in the code below). This will enable me to plot points at x,y coordinate pairs in the code below.
2.) plot rotated labels for the tic marks on the y-axis of the data plot.
The code below sets up this problem. It works, except for two problems:
1.) the data points are being plotted with the upper left hand corner as the origin and y-values descending downward
2.) the labels for the tic marks on the y-axis are not being drawn on the screen
Can anyone show me how to fix the code below so that it fixes these two problems and does what the first paragraph above describes?
The code is in the following two java files:
DataGUI.java
import java.awt.*;
import java.util.ArrayList;
import javax.swing.*;
class DataGUI extends JFrame{
DataGUI() {
super("X,Y Plot");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setPreferredSize(new Dimension(800, 400));
this.pack();
this.setSize(new Dimension(800, 600));
this.setLocationRelativeTo(null);
setLayout(new GridLayout());
ArrayList<Double> myDiffs = new ArrayList<Double>();
myDiffs.add(25.0);
myDiffs.add(9.0);
myDiffs.add(7.0);
myDiffs.add(16.0);
myDiffs.add(15.0);
myDiffs.add(6.0);
myDiffs.add(2.0);
myDiffs.add(8.0);
myDiffs.add(2.0);
myDiffs.add(27.0);
myDiffs.add(14.0);
myDiffs.add(12.0);
myDiffs.add(19.0);
myDiffs.add(10.0);
myDiffs.add(11.0);
myDiffs.add(8.0);
myDiffs.add(19.0);
myDiffs.add(2.0);
myDiffs.add(16.0);
myDiffs.add(5.0);
myDiffs.add(18.0);
myDiffs.add(23.0);
myDiffs.add(9.0);
myDiffs.add(4.0);
myDiffs.add(8.0);
myDiffs.add(9.0);
myDiffs.add(3.0);
myDiffs.add(3.0);
myDiffs.add(9.0);
myDiffs.add(13.0);
myDiffs.add(17.0);
myDiffs.add(7.0);
myDiffs.add(0.0);
myDiffs.add(2.0);
myDiffs.add(3.0);
myDiffs.add(33.0);
myDiffs.add(23.0);
myDiffs.add(26.0);
myDiffs.add(12.0);
myDiffs.add(12.0);
myDiffs.add(19.0);
myDiffs.add(14.0);
myDiffs.add(9.0);
myDiffs.add(26.0);
myDiffs.add(24.0);
myDiffs.add(13.0);
myDiffs.add(19.0);
myDiffs.add(2.0);
myDiffs.add(7.0);
myDiffs.add(28.0);
myDiffs.add(15.0);
myDiffs.add(2.0);
myDiffs.add(5.0);
myDiffs.add(17.0);
myDiffs.add(2.0);
myDiffs.add(16.0);
myDiffs.add(19.0);
myDiffs.add(2.0);
myDiffs.add(31.0);
DataPanel myPP = new DataPanel(myDiffs,this.getHeight(),this.getWidth());
this.add(myPP);
this.setVisible(true);// Display the panel.
}
public static void main(String[] args){
DataGUI myDataGUI = new DataGUI();
myDataGUI.setVisible(true);
}
}
DataPanel.java (Note: I edited the code below to include trashgod's suggestions, but it still does not work.)
import java.awt.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.*;
class DataPanel extends JPanel {
Insets ins; // holds the panel's insets
ArrayList<Double> myDiffs;
double maxDiff = Double.NEGATIVE_INFINITY;
double minDiff = Double.POSITIVE_INFINITY;
double maxPlot;
DataPanel(ArrayList<Double> Diffs, int h, int w){
setOpaque(true);// Ensure that panel is opaque.
setPreferredSize(new Dimension(w, h));
setMinimumSize(new Dimension(w, h));
setMaximumSize(new Dimension(w, h));
myDiffs = Diffs;
repaint();
this.setVisible(true);
}
protected void paintComponent(Graphics g){// Override paintComponent() method.
super.paintComponent(g);
//get data about plotting environment and about text
int height = getHeight();
int width = getWidth();
ins = getInsets();
Graphics2D g2d = (Graphics2D)g;
FontMetrics fontMetrics = g2d.getFontMetrics();
String xString = ("x-axis label");
int xStrWidth = fontMetrics.stringWidth(xString);
int xStrHeight = fontMetrics.getHeight();
String yString = "y-axis label";
int yStrWidth = fontMetrics.stringWidth(yString);
int yStrHeight = fontMetrics.getHeight();
String titleString ="Title of Graphic";
int titleStrWidth = fontMetrics.stringWidth(titleString);
int titleStrHeight = fontMetrics.getHeight();
int leftMargin = ins.left;
//set parameters for inner rectangle
int hPad=10;
int vPad = 6;
int testLeftStartPlotWindow = ins.left+5+(3*yStrHeight);
int testInnerWidth = width-testLeftStartPlotWindow-ins.right-hPad;
getMaxMinDiffs();
getMaxPlotVal();
double increment = 5.0;
int numTicks = (int)(maxPlot/increment);//will use numTicks for: remainder, leftStartPlotWindow, innerRectangle+labels+tickmarks
int remainder = testInnerWidth%numTicks;
int leftStartPlotWindow = testLeftStartPlotWindow-remainder;
System.out.println("remainder is: "+remainder);
int bottomPad = (3*xStrHeight)-vPad;
int blueTop = ins.bottom+(vPad/2)+titleStrHeight;
int blueHeight = height-bottomPad-blueTop;
int blueWidth = blueHeight;
int blueBottom = blueHeight+blueTop;
//plot outer rectangle
g.setColor(Color.red);
int redWidth = width-leftMargin-1;
g.drawRect(leftMargin, ins.bottom, redWidth, height-ins.bottom-1);
//write top label
g.setColor(Color.black);
g.drawString(titleString, leftStartPlotWindow+((blueWidth/2)-(titleStrWidth/2)), titleStrHeight);
// fill, then plot, inner rectangle
g.setColor(Color.white);
g.fillRect(leftStartPlotWindow, blueTop, blueWidth, blueHeight);
g.setColor(Color.blue);
g.drawRect(leftStartPlotWindow, blueTop, blueWidth, blueHeight);
//scale the diffs to fit window
double Scalar = blueWidth/maxPlot;
ArrayList<Double> scaledDiffs = new ArrayList<Double>();
for(int e = 0;e<myDiffs.size();e++){scaledDiffs.add(myDiffs.get(e)*Scalar);}
//plot the scaled Diffs
AffineTransform at = g2d.getTransform();//save the graphics context's transform
g2d.translate(leftStartPlotWindow, blueTop);//translate origin to bottom-left corner of blue rectangle
g2d.scale(1, -1);//invert the y-axis
for(int w = 0;w<scaledDiffs.size();w++){
if(w>0){
double prior = scaledDiffs.get(w-1);
int priorInt = (int)prior;
double current = scaledDiffs.get(w);
int currentInt = (int)current;
g2d.drawOval(priorInt, currentInt, 4, 4);
}
}
g2d.setTransform(at);//restore the transform for conventional rendering
//write x-axis label
g.setColor(Color.red);
g.drawString(xString, leftStartPlotWindow+((blueWidth/2)-(xStrWidth/2)), height-ins.bottom-vPad);
//write y-axis label
g2d.rotate(Math.toRadians(-90), 0, 0);//rotate text 90 degrees counter-clockwise
g.drawString(yString, -(height/2)-(yStrWidth/2), yStrHeight);
g2d.rotate(Math.toRadians(+90), 0, 0);//rotate text 90 degrees clockwise
// draw tick marks on x-axis
NumberFormat formatter = new DecimalFormat("#0.0");
double k = (double)blueWidth/(double)numTicks;
double iteration = 0;
for(int h=0;h<=numTicks;h++){
int xval = (int)(h*k);
g.setColor(Color.red);
g.drawLine(leftStartPlotWindow+xval, blueBottom+2, leftStartPlotWindow+xval, blueBottom+(xStrHeight/2));//draw tick marks
g.drawString(formatter.format(iteration),leftStartPlotWindow+xval-(fontMetrics.stringWidth(Double.toString(iteration))/2),blueBottom+(xStrHeight/2)+13);
iteration+=increment;
}
// draw tick marks on y-axis
iteration = 0;
for(int h=0;h<=numTicks;h++){
int yval = (int)(h*k);
g.setColor(Color.red);
g.drawLine(leftStartPlotWindow-2, blueBottom-yval, leftStartPlotWindow-(yStrHeight/2), blueBottom-yval);//draw tick marks
g2d.rotate(Math.toRadians(-90), 0, 0);//rotate text 90 degrees counter-clockwise
g.drawString(formatter.format(iteration),leftStartPlotWindow-2,blueBottom-(fontMetrics.stringWidth(Double.toString(iteration))/2));
g2d.rotate(Math.toRadians(+90), 0, 0);//rotate text 90 degrees clockwise
iteration+=increment;
}
}
void getMaxMinDiffs(){// get max and min of Diffs
for(int u = 0;u<myDiffs.size();u++){
if(myDiffs.get(u)>maxDiff){maxDiff = myDiffs.get(u);}
if(myDiffs.get(u)<minDiff){minDiff = myDiffs.get(u);}
}
}
void getMaxPlotVal(){
maxPlot = maxDiff;
maxPlot += 1;//make sure maxPlot is bigger than the max data value
while(maxPlot%5!=0){maxPlot+=1;}//make sure maxPlot is a multiple of 5
}
}
Also, as always, links to articles or tutorials on the topic are much appreciated.
One approach is shown in SineTest. In outline,
Save the graphics context's transform.
Graphics2D g2d = (Graphics2D) g;
AffineTransform at = g2d.getTransform();
Translate the origin to the center.
g2d.translate(w / 2, h / 2);
Invert the y-axis.
g2d.scale(1, -1);
Render using cartesian coordinates.
Restore the transform for conventional rendering.
g2d.setTransform(at);
Apologies for somewhat incomplete answer, but this may get your gears turning. Java draws things the way you described them: It considers the top left corner of the screen to be 0, 0 and draws x increasing to the right and y increasing downwards. If you make the line that states
g2d.drawOval(priorInt, currentInt, 4, 4);
into
g2d.drawOval(blueWidth - priorInt, blueHeight - currentInt, 4, 4);
it should yield the correct results for your first issue. I need a bit more info on the second problem to help you with that one though. Are they just off the screen or are the getting drawn over by something else? Try flipping +s and -s around to see if you can get the correct result if that is the case.
I was wondering if I could find some help on this problem. I was asked to use an image ("corn.jpg"), and flip it entirely upside down. I know I need to write a program which will switch pixels from the top left corner with the bottom left, and so on, but I wasn't able to get my program to work properly before time ran out. Could anyone provide a few tips or suggestions to solve this problem? I'd like to be able to write my code out myself, so suggestions only please. Please note that my knowledge of APImage and Pixel is very limited. I am programming in Java.
Here is what I managed to get done.
import images.APImage;
import images.Pixel;
public class Test2
{
public static void main(String [] args)
{
APImage image = new APImage("corn.jpg");
int width = image.getImageWidth();
int height = image.getImageHeight();
int middle = height / 2;
//need to switch pixels in bottom half with the pixels in the top half
//top half of image
for(int y = 0; y < middle; y++)
{
for (int x = 0; x < width; x++)
{
//bottom half of image
for (int h = height; h > middle; h++)
{
for(int w = 0; w < width; w++)
{
Pixel bottomHalf = image.getPixel(h, w);
Pixel topHalf = image.getPixel(x, y);
//set bottom half pixels to corresponding top ones?
bottomHalf.setRed(topHalf.getRed());
bottomHalf.setGreen(topHalf.getGreen());
bottomHalf.setBlue(topHalf.getBlue());
//set top half pixels to corresponding bottom ones?
topHalf.setRed(bottomHalf.getRed());
topHalf.setGreen(bottomHalf.getGreen());
topHalf.setBlue(bottomHalf.getBlue());
}
}
}
}
image.draw();
}
}
Thank you for your help!
See Transforming Shapes, Text, and Images.
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class FlipVertical {
public static BufferedImage getFlippedImage(BufferedImage bi) {
BufferedImage flipped = new BufferedImage(
bi.getWidth(),
bi.getHeight(),
bi.getType());
AffineTransform tran = AffineTransform.getTranslateInstance(0, bi.getHeight());
AffineTransform flip = AffineTransform.getScaleInstance(1d, -1d);
tran.concatenate(flip);
Graphics2D g = flipped.createGraphics();
g.setTransform(tran);
g.drawImage(bi, 0, 0, null);
g.dispose();
return flipped;
}
FlipVertical(BufferedImage bi) {
JPanel gui = new JPanel(new GridLayout(1,2,2,2));
gui.add(new JLabel(new ImageIcon(bi)));
gui.add(new JLabel(new ImageIcon(getFlippedImage(bi))));
JOptionPane.showMessageDialog(null, gui);
}
public static void main(String[] args) throws AWTException {
final Robot robot = new Robot();
Runnable r = new Runnable() {
#Override
public void run() {
final BufferedImage bi = robot.createScreenCapture(
new Rectangle(0, 660, 200, 100));
new FlipVertical(bi);
}
};
SwingUtilities.invokeLater(r);
}
}
Whenever you're swapping variables, if your language doesn't allow for simultaneous assignment (and Java doesn't), you need to use a temporary variable.
Consider this:
a = 1;
b = 2;
a = b; // a is now 2, just like b
b = a; // b now uselessly becomes 2 again
Rather than that, do this:
t = a; // t is now 1
a = b; // a is now 2
b = t; // b is now 1
EDIT: And also what #vandale says in comments :P
If you are able to use the Graphics class, the following may be of use:
http://www.javaworld.com/javatips/jw-javatip32.html
And the Graphics class documentation:
http://docs.oracle.com/javase/7/docs/api/java/awt/Graphics.html
Instead of using
Pixel bottomHalf = image.getPixel(h, w);
Pixel topHalf = image.getPixel(x, y);
//set bottom half pixels to corresponding top ones?
bottomHalf.setRed(topHalf.getRed());
bottomHalf.setGreen(topHalf.getGreen());
bottomHalf.setBlue(topHalf.getBlue());
//set top half pixels to corresponding bottom ones?
topHalf.setRed(bottomHalf.getRed());
topHalf.setGreen(bottomHalf.getGreen());
topHalf.setBlue(bottomHalf.getBlue());
You should have stored the bottomHalf's RGB into a temporary Pixel and used that to set topHalf after replacing bottomHalf's values (if you follow). You could have also really used something like this.... assuming your pixel operates on integer rgb values (which would have improved your main method).
private static final Pixel updateRGB(Pixel in, int red, int green, int blue) {
in.setRed(red); in.setGreen(green); in.setBlue(blue);
}
You want to flip the image upside down, not swap the top and bottom half.
The loop could look like this.
int topRow = 0;
int bottomRow = height-1;
while(topRow < bottomRow) {
for(int x = 0; x < width; x++) {
Pixel t = image.getPixel(x, topRow);
image.setPixel(x, topRow, image.getPixel(x, bottomRow));
image.setPixel(x, bottomRow, t);
}
topRow++;
bottomRow--;
}
Trying to figure out the best way to do this (And without crossing any specifics DO NOTs that I don't know about).
I'm working on visually displaying a graph (Various nodes, with edges connecting them) with circles and lines to represent such. Each node will be added during runtime and I can't hardcode this. From what I understand, all painting needs to be done in the paint(Graphics g) method - which isn't that helpful, since I can't be change the parameters and it seems this is only called during the initial creation?
Right now I was thinking about having it call various other methods, passing the Graphics object, and depending on other variables - I'll decide whether that's what I even want to call (Since the paint() method is the only one I can call).
Am I going about this completely wrong? Never bothered with this before.
To give you a better idea of what I want to end up with: I want to be able to pass the coordinates of the shape I want to add for the node, and then add it to whatever I have on the graph so far. And then same with the edges, I want to be able to pass the beginning and end point of the line to repaint on top of whatever is existing at that time.
Not exactly what I want right now - but you'll get the idea from what I patched together so far:
import java.awt.*;
import javax.swing.*;
public class MyCanvas extends Canvas
{
public MyCanvas()
{
}
public void paint(Graphics graphics)
{
// Keep this until I figured out if it's painted on load or not.
graphics.drawLine(10, 20, 350, 380);
}
public static void main(String[] args)
{
MyCanvas canvas = new MyCanvas();
JFrame frame = new JFrame();
int vertexes = 0;
// Change this next part later to be dynamic.
vertexes = 10;
int canvasSize = vertexes * vertexes;
frame.setSize(canvasSize, canvasSize);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(canvas);
frame.setVisible(true);
}
public void drawNode(int x, int y, Graphics g)
{
// Treat each location as a 10x10 block. If position 1,1 then go to (5,5) - If position 3,5 then go to (25, 45) eg: (x*10)-5, (y*10)-5
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
g.setColor(Color.white);
g.fillOval(xLoc, yLoc, 8, 8);
g.drawOval(xLoc, yLoc, 8, 8);
}
public void drawArc(int x, int y, int xx, int yy, Graphics g)
{
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
int xxLoc = (xx*10) - 5;
int yyLoc = (yy*10) - 5;
g.drawLine(xLoc, yLoc, xxLoc, yyLoc);
}
}
Edit: (Response for Andrew)
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class MyCanvas extends JPanel
{
public MyCanvas() {
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
public static void main(String[] args)
{
int vertexes = 0;
// Change this next part later to be dynamic.
vertexes = 10;
int canvasSize = vertexes * vertexes;
JFrame frame = new JFrame();
JLabel label = new JLabel();
BufferedImage bImage = new BufferedImage(canvasSize, canvasSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bImage.createGraphics();
g2d.drawLine(50, 50, 300, 300);
ImageIcon iIcon = new ImageIcon(bImage);
label.setIcon(iIcon);
frame.add(label);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
g2d = drawNode(1,1,g2d);
label.repaint();
}
public static Graphics2D drawNode(int x, int y,Graphics2D g2d)
{
// Treat each location as a 10x10 block. If position 1,1 then go to (5,5) - If position 3,5 then go to (25, 45) eg: (x*10)-5, (y*10)-5
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
g2d.setColor(Color.white);
g2d.fillOval(xLoc, yLoc, 8, 8);
g2d.drawOval(xLoc, yLoc, 8, 8);
return g2d;
}
public static void drawArc(int x, int y, int xx, int yy)
{
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
int xxLoc = (xx*10) - 5;
int yyLoc = (yy*10) - 5;
// g.drawLine(xLoc, yLoc, xxLoc, yyLoc);
}
}
There are various strategies you might pursue for this.
If the objects are never removed from the drawing once done, use a BufferedImage, put it in a (ImageIcon in a) JLabel. When it comes time to update:
Get the graphics instance of the image and draw the new element.
Dispose of the graphics object.
Call repaint() on the label.
Keep a list of the drawn elements. In the paint method, paint them all. When a new element is added, call repaint() on the rendering component.
Here is an example of the 1st technique:
import java.awt.image.BufferedImage;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Random;
public class MyCanvas
{
JLabel view;
BufferedImage surface;
Random random = new Random();
public MyCanvas()
{
surface = new BufferedImage(600,400,BufferedImage.TYPE_INT_RGB);
view = new JLabel(new ImageIcon(surface));
Graphics g = surface.getGraphics();
g.setColor(Color.ORANGE);
g.fillRect(0,0,600,400);
g.setColor(Color.BLACK);
// Keep this until I figured out if it's painted on load or not.
g.drawLine(10, 20, 350, 380);
g.dispose();
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
addNewElement();
}
};
Timer timer = new Timer(200, listener);
timer.start();
}
public void addNewElement() {
boolean drawArc = random.nextBoolean();
int x = random.nextInt(60);
int y = random.nextInt(40);
Graphics g = surface.getGraphics();
if (drawArc) {
g.setColor(Color.BLUE);
int xx = random.nextInt(60);
int yy = random.nextInt(40);
drawArc(x,y,xx,yy,g);
} else {
drawNode(x,y,g);
}
g.dispose();
view.repaint();
}
public static void main(String[] args)
{
MyCanvas canvas = new MyCanvas();
JFrame frame = new JFrame();
int vertexes = 0;
// Change this next part later to be dynamic.
vertexes = 10;
int canvasSize = vertexes * vertexes;
frame.setSize(canvasSize, canvasSize);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(canvas.view);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public void drawNode(int x, int y, Graphics g)
{
// Treat each location as a 10x10 block. If position 1,1 then go to (5,5) - If position 3,5 then go to (25, 45) eg: (x*10)-5, (y*10)-5
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
g.setColor(Color.white);
g.fillOval(xLoc, yLoc, 8, 8);
g.drawOval(xLoc, yLoc, 8, 8);
}
public void drawArc(int x, int y, int xx, int yy, Graphics g)
{
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
int xxLoc = (xx*10) - 5;
int yyLoc = (yy*10) - 5;
g.drawLine(xLoc, yLoc, xxLoc, yyLoc);
}
}
Further tip
You might notice that the lines look quite 'jagged' & ugly. Both the BufferedImage or a JComponent has access to the more useful Graphics2D object (for the JComponent it is necessary to cast it in paintComponent()). A Graphics2D instance accepts rendering hints that can be used to smooth (dither) the elements drawn.