I am trying to plot a graph using the java 2d graphics library and I thought I had it. I want to plot in the coordinate system where 0,0 is in the center of the panel on the left edge. I used the following code and it seemed to give me the result I needed.
private void doDraw(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
AffineTransform saveAT = g2d.getTransform();
// get the height of the panel
int height = getHeight();
// Find the middle of the panel
double yTrans = ((double)height)/2.0;
AffineTransform tform = AffineTransform.getTranslateInstance( 0.0, yTrans);
g2d.setTransform(tform);
//draw the line for the x-axis.
g2d.drawLine(0, 0, 100, 0);
//restore the old transform
g2d.setTransform(saveAT);
}
This plotted the origin centered in the window.
The problem shows itself when I added a menu. Then the origin was offset in the y direction about twice the size of the menu higher then it should be. Do I need to account for the size of the menu and other containers that I add to the panel?
private void doDraw(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
int height = getHeight();
double yTrans = ((double)height)/2.0;
AffineTransform tform = AffineTransform.getTranslateInstance( 0.0, yTrans);
g2d.transform(tform);
//draw the line for the x-axis.
g2d.drawLine(0, 0, 100, 0);
}
works, thank you for your help
You might try the approach outlined here. Override paintComponent() to obtain a graphics context relative to the enclosing panel, rather than the enclosing frame.
To center the origin at the left edge, use
g2d.translate(0, h / 2);
To get upright, cartesian coordinates, use
g2d.scale(1, -1);
Related
I am working on a project that has a GUI and tanks that move. While tanks move fine, I am not able to figure out how to move/rotate them individually. I also need to clean my code as I feel I have a lot of extra stuff going on.
Here is some code, and here's what I tried.
I have four classes. Missiles, Tanks, and Board. I am calling keylisteners in the Tank class. Should I do that in the doDrawing method? The doDrawing method is in the Board class.
private void doDrawing(Graphics g)
{
final double rads = Math.toRadians(120);
final double sin = Math.abs(Math.sin(rads));
final double cos = Math.abs(Math.cos(rads));
final int w = (int) Math.floor(tank1.getX() * cos + tank1.getX() * sin);
final int h = (int) Math.floor(tank1.getY() * cos + tank1.getY() * sin);
Graphics2D g2d = (Graphics2D) g;
g2d.translate(w, h);
g2d.rotate(rot, tank1.getX(), tank1.getY());
AffineTransform backup = g2d.getTransform();
AffineTransform trans = new AffineTransform();
g2d.setTransform(backup);
//g2d.drawImage(tank1.getImage(), tank1.getX(), tank1.getY(), this);
trans.setToIdentity();
trans.rotate(rot, h, w);
trans.translate(h, w);
trans.setTransform(backup);
g2d.drawImage(tank1.getImage(), tank1.getX(), tank1.getY(), this);
//g2d.drawImage(tank1.getImage(), tank1.getX(), tank1.getY(), this);
g2d.drawImage(tank2.getImage(), tank2.getX(), tank2.getY(), this);
List<Missile> missiles = tank1.getMissiles();
for (Missile missile : missiles)
{
//trans.rotate(Math.toRadians(rads), w/2, h/2);
g2d.drawImage(missile.getImage(), missile.getX(), missile.getY() - 7, this);
//g2d.rotate(rot, missile.getX(), missile.getY() - 7);
}
}
So, just looking at...
private void doDrawing(Graphics g)
{
//...
Graphics2D g2d = (Graphics2D) g;
g2d.translate(w, h);
g2d.rotate(rot, tank1.getX(), tank1.getY());
AffineTransform backup = g2d.getTransform();
AffineTransform trans = new AffineTransform();
g2d.setTransform(backup);
//g2d.drawImage(tank1.getImage(), tank1.getX(), tank1.getY(), this);
trans.setToIdentity();
trans.rotate(rot, h, w);
trans.translate(h,w);
trans.setTransform(backup);
g2d.drawImage(tank1.getImage(), tank1.getX(), tank1.getY(), this);
g2d.drawImage(tank2.getImage(), tank2.getX(), tank2.getY(), this);
//...
}
It's immediately obvious that you're applying a transformation to the Graphics context which is never undone, so everything painted after it will have the same transformation applied to it.
The first thing you need to do is break the paint logic down so that it can paint a single entity.
The second thing you need to do is create a copy of the Graphics context before you make any modifications to it, this will prevent the parent copy from carrying those modifications forward which will then be compounded with future modifications.
Maybe something like...
private void doDrawing(Graphics g) {
// Because I don't trust anybody
Graphics2D g2d = (Graphics2D) g.create();
drawTank(tank1, g2d);
g2d.dispose();
g2d = (Graphics2D) g.create();
drawTank(tank2, g2d);
g2d.dispose();
}
private void drawTank(Tank tank, Graphics2D g) {
final double rads = Math.toRadians(120);
final double sin = Math.abs(Math.sin(rads));
final double cos = Math.abs(Math.cos(rads));
final int w = (int) Math.floor(tank.getX() * cos + tank.getX() * sin);
final int h = (int) Math.floor(tank.getY() * cos + tank.getY() * sin);
Graphics2D g2d = (Graphics2D) g.create();
g2d.translate(w, h);
// `rot` should be derived from the tank ... no idea where this is
g2d.translate(tank.getX(), tank.getY());
// Origin has now been translated to the tanks position
g2d.rotate(rot, 0, 0);
g2d.drawImage(tank.getImage(), 0, 0, this);
// We no longer to need to keep these resources
g2d.dispose();
// Create a new "clean" copy
g2d = (Graphics2D) g.create();
// I don't know if it's needed, but I would, personally, calculate
// any required rotation again, but that's me
List<Missile> missiles = tank1.getMissiles();
for (Missile missile : missiles) {
g2d.drawImage(missile.getImage(), missile.getX(), missile.getY() - 7, this);
}
}
For example, see:
Java - rotate image in place
How to move paint graphics along slope?
Recommendations:
Keep logic separate from display using a model-view code structure
In keeping with that, create a Tank logical class, one that knows its own position and its own orientation (or angle), and save your java.util.List<Tank> in the program's model.
Change the Tank position and orientation within a listener.
Use the View classes (the GUI classes) only to a) draw the state of the class's model and b) get input from the user.
Side recommendation: If this is Swing, avoid KeyListeners and instead use Key Bindings
You can use separate affine transforms for each tank
If your tanks move/rotate synchronously I guess the reason is that they all receive the same key events and react the same way.
Put the keylistener (or even better KeyBindings as suggested in another answer) into your Board class. The listener will check which key is pressed/released and give the correct tank the command to move or turn or whatever. This way you can make one tank react to WASD, while the other reacts on up/down/left/right.
The methods in the tank class however do not react to key events but could be named like forward, backward, turnleft, turnright.
The doDrawing method should do exactly as it's name suggests: just perform the drawing. This can happen any amount of times to get good screen refresh rates or redraw the board after another window no longer hides your game.
I'm pretty new to Affine Transformations and am finding a few things quite frustrating..
I've been working on a personal project that draws a laser (think Star Wars) on the screen. I use the RadialGradientPaint class to draw the glow for the laser, and then use scale and rotate Affine Transformations to change the oval's shape and turn the laser in a specific direction.
When I try to rotate the graphic (without the scale), it appears to shrink it, and I'm not sure why this is. I've tried looking this up to no avail. Perhaps I misinterpreted the Java Doc on this class. Why is this?
I'm also curious as to how I can get the transformed object to move to the same location as an un-transformed graphic. Both graphics move to different places even with the same coordinates. I know this might be due to an Affine Transformation shifting the whole graph, and that I might have to translate the transformed graphic to get it to the same exact place as the un-transformed graphic, but I'm not sure how to approach this.
Here is the portion of my code that draws the laser:
public void render(Graphics g) {
// If the graphic is visible on the frame, draw it
if(onScreen()) {
Graphics2D g2d = (Graphics2D) g;
// Preserves the original presets of the program's graphics
AffineTransform ogTransform = g2d.getTransform();
Shape ogClip = g2d.getClip();
Point2D center = new Point2D.Float(x, y);
float[] dist = {0.3f, 0.9f};
Color[] colors = {new Color(1.0f, 1.0f, 1.0f, 0.9f), new Color(0.0f, 1.0f, 0.0f, 0.1f)};
g2d.setPaint(new RadialGradientPaint(center, radius, dist, colors));
AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
AffineTransform rotationTransform = AffineTransform.getRotateInstance(angle, x + width/2f, y + height/2f);
// Transforms the shape to look more like a laser
rotationTransform.concatenate(scaleTransform);
g2d.setTransform(rotationTransform);
// Draws a "cut-out" of the rectangle
g2d.setClip(new Ellipse2D.Double(x - radius, y - radius, radius * 2, radius * 2));
g2d.fillRect(x - radius, y - radius, radius * 2, radius * 2);
// Default transformation and clip are set
g2d.setTransform(ogTransform);
g2d.setClip(ogClip);
// Outlines where the transformed graphic should be
g.setColor(Color.orange);
g.drawRect(x - 10, y - 10, 20, 20);
}
}
Any help is appreciated!
I have a requirement to draw a png/jpg in a trapezium shape.
.For ex : height reducing gradually along the x-axis. I could not find an API to do such, I see there is an API AffineTransform which may do such sorts, but could not figure out if trapezium could be achieved. Thanks for your help in advance
I tried something of this sorts,
Image image = imageIcon.getImage();
Graphics2D g2 = (Graphics2D) g;
AffineTransform saveXform = g2.getTransform();
AffineTransform toCenterAt = new AffineTransform();
//toCenterAt.concatenate(toCenterAt);
toCenterAt.translate(-(image.getWidth(null)/2), -(image.getHeight(null)/2));
//toCenterAt.setToRotation(5,4,4,4);
toCenterAt.scale(0.1, 1);
g2.transform(toCenterAt);
g2.drawImage(image, 200,200, null);
g2.setTransform(saveXform);
I'm quite new to graphics in java and I'm trying to create a shape that clips to the bottom of another shape. Here is an example of what I'm trying to achieve:
Where the white line at the base of the shape is the sort of clipped within the round edges.
The current way I am doing this is like so:
g2.setColor(gray);
Shape shape = getShape(); //round rectangle
g2.fill(shape);
Rectangle rect = new Rectangle(shape.getBounds().x, shape.getBounds().y, width, height - 3);
Area area = new Area(shape);
area.subtract(new Area(rect));
g2.setColor(white);
g2.fill(area);
I'm still experimenting with the clip methods but I can't seem to get it right. Is this current method ok (performance wise, since the component repaints quite often) or is there a more efficient way?
I think your original idea about using the clip methods was the right way to do it. This works for me:
static void drawShapes(Graphics2D g, int width, int height,
Shape clipShape) {
g.setPaint(Color.BLACK);
g.fillRect(0, 0, width, height);
g.clip(clipShape);
int centerX = width / 2;
g.setPaint(new GradientPaint(
centerX, 0, Color.WHITE,
centerX, height, new Color(255, 204, 0)));
g.fillRect(0, 0, width, height);
g.setPaint(Color.WHITE);
int whiteRectHeight = height * 4 / 5;
g.fillRect(0, whiteRectHeight,
width, height - whiteRectHeight);
}
Is this current method ok (performance wise, since the component repaints quite often) ..
Subtracting shapes is how I'd go about it. The objects could be a few instances or (possibly) a single instance that is transformed as needed.
A text demo., using scaling & fading.
Here's one with simple lines (..and dots, ..and it is animated).
Of course, if the image is purely additive, use a BufferedImage as the canvas & display it in a JLabel/ImageIcon combo. As in both of those examples.
I am developing an application using Java2d. The weird thing I noticed is, the origin is at the top left corner and positive x goes right and positive y increases down.
Is there a way to move the origin bottom left?
Thank you.
You are going to need to do a Scale and a translate.
in your paintComponent method you could do this:
public void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.translate(0, -height);
g2d.scale(1.0, -1.0);
//draw your component with the new coordinates
//you may want to reset the transforms at the end to prevent
//other controls from making incorrect assumptions
g2d.scale(1.0, -1.0);
g2d.translate(0, height);
}
my Swing is a little rusty but this should accomplish the task.
We can use the following way to resolve easily the problem,
public void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
// Flip the sign of the coordinate system
g2d.translate(0.0, getHeight());
g2d.scale(1.0, -1.0);
......
}
Have you tried Graphics2D.translate()?
You're going to want to just get used to it. Like luke mentioned, you technically CAN apply a transform to the graphics instance, but that will end up affecting performance negatively.
Just doing a translate could move the position of 0,0 to the bottom left, but movement along the positive axes will still be right in the x direction and down in the y direction, so the only thing you would accomplish is drawing everything offscreen. You'd need to do a rotate to accomplish what you're asking, which would add the overhead of radian calculations to the transform matrix of the graphics instance. That is not a good tradeoff.
Just for later reference, I had to swap the order of the calls to scale and translate in my code. Maybe this will help someone in the future:
#Test
public void bottomLeftOriginTest() throws IOException {
int width = 256;
int height = 512;
BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
Graphics2D ig = bi.createGraphics();
// save the "old" transform
AffineTransform old = ig.getTransform();
// origin is top left:
// update graphics object with the inverted y-transform
if (true) { /* order ok */
ig.scale(1.0, -1.0);
ig.translate(0, -bi.getHeight());
} else {
ig.translate(0, -bi.getHeight());
ig.scale(1.0, -1.0);
}
int xPoints[] = new int[] { 0, width, width };
int yPoints[] = new int[] { 0, height, 0 };
int nPoints = xPoints.length;
ig.setColor(Color.BLUE);
ig.fillRect(0, 0, bi.getWidth(), bi.getHeight());
ig.setColor(Color.RED);
ig.fillPolygon(xPoints, yPoints, nPoints);
// restore the old transform
ig.setTransform(old);
// Export the result to a file
ImageIO.write(bi, "PNG", new File("origin.png"));
}