I am new to Java and am learning new things everyday.
I am trying to learn how to take TWO DIFFERENT .png files and rotate them SEPARATELY using the paint component.
I am able to make both images paint and have separate movement, however the rotate function does NOT seem to recognize each 2DGraphic variable as independent.
Both images will be rotated to the last '.rotate' angles. I have looked up online but every tutorial referring to rotating a graphic are only dealing with one image. This works fine. I just can not get two images to rotate differently. I want P1GFX to be rotated separately from P2GFX.
Here is the code.
THIS CODE WORKS, however they are BOTH rotated to whatever the last .rotate specifies.
public void paintComponent(Graphics frogp1) {
Graphics2D P1GFX = (Graphics2D)frogp1;
Graphics2D P2GFX = (Graphics2D)frogp1;
P1GFX.rotate(90, 150 / 2, 150 / 2);
P2GFX.rotate(40, 50, 50);
P1GFX.drawImage(p1.getImage1(), p1x, p1y,this);
P2GFX.drawImage(p2.getImage2(), p2x, p2y, this);
}
So, I tried making multiple parameters in paintComponent! That should work right? NOPE!
THIS CODE Makes NO IMAGE appear at all! Nothing is drawn on the screen when there are more than one parameter inside paintComponent!
public void paintComponent(Graphics frogp1, Graphics frogp2) {
Graphics2D P1GFX = (Graphics2D)frogp1;
Graphics2D P2GFX = (Graphics2D)frogp2;
P1GFX.rotate(90, 150 / 2, 150 / 2);
P2GFX.rotate(40, 50, 50);
P1GFX.drawImage(p1.getImage1(), p1x, p1y,this);
P2GFX.drawImage(p2.getImage2(), p2x, p2y, this);
}
So I thought, hey! Maybe I need to make more than one paintComponent! Well, of course that is NOT possible without recreating my own instance of the repaint() method.
public void paintComponent1(Graphics frogp1) {
Graphics2D P1GFX = (Graphics2D)frogp1;
P1GFX.rotate(90, 150 / 2, 150 / 2);
P1GFX.drawImage(p1.getImage1(), p1x, p1y,this);
}
public void paintComponent2(Graphics frogp2) {
Graphics2D P2GFX = (Graphics2D)frogp2;
P2GFX.rotate(90, 150 / 2, 150 / 2);
P2GFX.drawImage(p2.getImage2(), p2x, p2y,this);
}
This makes repaint() do nothing, therefore nothing is drawn.
Please help me be able to rotate more than one image / Graphics2D variable !
Graphics2D P1GFX = (Graphics2D)frogp1;
Graphics2D P2GFX = (Graphics2D)frogp1;
Casting an Object means you are still using the same Object reference.
If you want two separate Graphics objects then you need to do:
Graphics2D p1gfx = (Graphics2D)frogp1.create();
Graphics2D p2gfx = (Graphics2D)frogp1.create();
then when you finish with the Graphics object you use:
p1gfx.dispose();
p2gfx.dispose();
And I change the variable names to follow Java naming conventions. Don't use all upper case characters for variable names.
You could just rotate, then unrotate and reposition:
public void paintComponent(Graphics graphics) {
Graphics2D g2d = (Graphics2D)graphics;
g2d.rotate(90, 150 / 2, 150 / 2);
g2d.drawImage(p1.getImage1(), p1x, p1y,this);
g2d.rotate(-90, 150 / 2, 150 / 2);
g2d.rotate(40, 50, 50);
g2d.drawImage(p2.getImage2(), p2x, p2y, this);
}
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 have started using AffineTransform to rotate text I'm drawing with Graphics2D and I noticed it would sometimes work fine and other times it wouldn't when I just realized that it works always as expected on my Windows 7 PC but never on my Windows 10 laptop.
I use Java 15.0.1 on both systems.
Here is a small test case to show you my point:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
public class AffineTransformTest extends JPanel {
private static final int SIZE = 40;
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
int centerX = getWidth()/2;
int centerY = getHeight()/2;
g2.setColor(Color.BLACK);
g2.drawRect(centerX - SIZE/2, centerY - SIZE/2, SIZE, SIZE);
AffineTransform at = g2.getTransform();
at.setToRotation(Math.toRadians(45),centerX, centerY);
g2.setTransform(at);
g2.setColor(Color.RED);
g2.drawRect(centerX - SIZE/2, centerY - SIZE/2, SIZE, SIZE);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
AffineTransformTest test = new AffineTransformTest();
frame.setContentPane(test);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300,300);
frame.setVisible(true);
}
}
The black rectangle is a regular one centered in the middle of the JPanel.
The red rectangle is drawn after a 45° rotation and is supposed to share the same center as the black one (as shown on the first picture).
My laptop however produces the result shown on the second picture.
Expected result - Windows 7 |
Incorrect result - Windows 10
How can this be?
This is likely because you are violating the contract of the paintComponent() method, where the Javadoc states
If you override this in a subclass you should not make permanent
changes to the passed in Graphics. For example, you should not alter
the clip Rectangle or modify the transform. If you need to do these
operations you may find it easier to create a new Graphics from the
passed in Graphics and manipulate it.
So maybe subtle differences between the Windows 7 and Windows 10 native implementations of the UI is why one breaks and the other doesn't.
To see if this is indeed the cause, try changing the line Graphics2D g2 = (Graphics2D)g; to Graphics2D g2 = (Graphics2D)g.create(); which will make a clone of the Graphics object and leave the original unchanged.
Edit to clarify the correct answer:
The issue was caused by the at.setToRotation call clearing the default scaling transform on the graphics context after drawing the black square and before drawing the rotated red square. The recommended solution is to call the rotate(theta,x,y) form of the method directly on the Graphics2D object which preserves the existing scale transform.
I use NetBeans and the last version of JDK. For some reason there is no Graphics.draw() method, though java.awt.geom.Line2D and java.awt.Graphics2D are imported. How do I draw a Line2D.Double element?
Shape pLine;
private void playerDraw(){
Graphics g2 = getGraphics();
pLine = new Line2D.Double(px, py, Math.cos(angle)*10+px,Math.sin(angle)*10+py);
g2.drawRect(px-5, py-5, 10, 10);
g2.draw(pLine); //this doesn't compile(cannot find symbol)
}
Your main problem is that you're using a Graphics object as if it were a Graphics2D object, but it's not and as the Graphics class Java API entry will show you, the Graphics class does not have a draw method, while Graphics2D does. I think that you're missing a key line, something like:
Graphics g = getGraphics();
Graphics2D g2 = (Graphics2D) g; // the missing line
But having said that, you are using Graphics incorrectly as you should avoid getting it by calling getGraphics() on a Swing component since this gives you an unstable short-lived Graphics object whose use risks causing short-lived images or NullPointerExceptions, but rather you should do your drawing within the JComponent's paintComponent(Graphics g) method.
You missed to declare pLine variable in your Class:
Eg.
public class Example
{
public Line2D.Double pLine;
private void playerDraw(){
Graphics g2 = getGraphics();
pLine = new Line2D.Double(px, py,Math.cos(angle)*10+px,Math.sin(angle)*10+py);
g2.drawRect(px-5, py-5, 10, 10);
g2.draw(pLine); //this doesn't compile(cannot find symbol)
}
}
First of all, yes, I know I'm a newbie, and this is my first attempt at making a custom Component.
Okay, so in my project, I'm trying to make a custom button that does three things:
Draw a background
Get the icon of a selected app
Draw the app's name in the button.
It can do all three of those, except that the button is tiny:
The icon is the Jar application and the name is "Test Application".
This is the paintComponent(Graphics) method in my class, AppButton:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D antiAlias = (Graphics2D) g;
antiAlias.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.blue);
//g.fillRoundRect(x, y, width, height, arcWidth, arcHeight);
g.fillRoundRect(this.getX(), this.getY(), this.getWidth(), this.getHeight() - 25, 20, 20);
g.setColor(Color.red);
FontMetrics metrics = g.getFontMetrics();
int widthOfAppName = metrics.stringWidth(this.appName);
g.drawString(this.appName, this.getWidth() / 2 - (widthOfAppName / 2), this.getHeight() - 10);
File refrenceFile = new File(this.appURL);
try {
if (refrenceFile.exists()) {
ShellFolder sf = ShellFolder.getShellFolder(refrenceFile);
this.appIcon = new ImageIcon(sf.getIcon(true));
g.drawImage(this.appIcon.getImage(), this.getWidth() / 2 - (this.appIcon.getIconWidth() / 2),
this.getHeight() / 2 - (this.appIcon.getIconHeight() / 2), JLaunch.theFrame);
//Draw the centered Image
} else {
ImageIcon noImageFound = getNoImageAvailable();
//g.drawImage(img, x, y, observer)
g.drawImage(noImageFound.getImage(), this.getWidth() / 2 - (noImageFound.getIconWidth() / 2),
this.getHeight() / 2 - (noImageFound.getIconHeight() / 2), JLaunch.theFrame);
//Draw the centered Image
}
} catch (Exception e) {
e.printStackTrace();
}
}
On a side note, if anyone has a good understanding about custom Swing Components, can you also please point me to a good tutorial or a way of learning it like you did?
Well, for one, JButton can draw images. But what you are probably looking for is JComponent.setPreferedSize() You can check out the documentation here: http://docs.oracle.com/javase/6/docs/api/javax/swing/JComponent.html#setPreferredSize(java.awt.Dimension).
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"));
}