How to rotate a non-square image in Java?
java
Solution
When you rotate an image the width and height also change and your code doesn't take this into account.
Here is some old code I have lying around that should work better:
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class Rotation
{
BufferedImage image;
JLabel label;
public Rotation(BufferedImage image)
{
this.image = image;
}
private BufferedImage getImage(double theta)
{
// Determine the size of the rotated image
double cos = Math.abs(Math.cos(theta));
double sin = Math.abs(Math.sin(theta));
double width = image.getWidth();
double height = image.getHeight();
int w = (int)(width * cos + height * sin);
int h = (int)(width * sin + height * cos);
// Rotate and paint the original image onto a BufferedImage
BufferedImage out = new BufferedImage(w, h, image.getType());
Graphics2D g2 = out.createGraphics();
g2.setPaint(UIManager.getColor("Panel.background"));
g2.fillRect(0,0,w,h);
double x = w/2;
double y = h/2;
AffineTransform at = AffineTransform.getRotateInstance(theta, x, y);
x = (w - width)/2;
y = (h - height)/2;
at.translate(x, y);
g2.drawRenderedImage(image, at);
g2.dispose();
return out;
}
private JLabel getLabel()
{
ImageIcon icon = new ImageIcon(image);
label = new JLabel(icon);
label.setHorizontalAlignment(JLabel.CENTER);
return label;
}
private JSlider getSlider()
{
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = slider.getValue();
BufferedImage bi = getImage(Math.toRadians(value));
label.setIcon(new ImageIcon(bi));
}
});
return slider;
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
String path = "mong.jpg";
ClassLoader cl = Rotation.class.getClassLoader();
BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
Rotation r = new Rotation(bi);
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new JScrollPane(r.getLabel()));
f.getContentPane().add(r.getSlider(), "South");
f.pack();
f.setLocation(200,200);
f.setVisible(true);
}
catch(IOException e)
{
System.out.println(e);
}
}
});
}
}
Edit:
Another option is to create an Icon, then you can use the Rotated Icon. Then you can rotate and paint the icon in your painting code. Something like:
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class Rotation3 extends JPanel
{
private Icon icon;
private Icon rotated;
private int degrees;
public Rotation3(BufferedImage image)
{
icon = new ImageIcon( image );
setDegrees( 0 );
setPreferredSize( new Dimension(600, 600) );
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
double radians = Math.toRadians( degrees );
rotated = new RotatedIcon(icon, degrees);
// translate x/y so Icon is rotated around a specific point (300, 300)
int x = 300 - (rotated.getIconWidth() / 2);
int y = 300 - (rotated.getIconHeight() / 2);
rotated.paintIcon(this, g, x, y);
g.setColor(Color.RED);
g.fillOval(295, 295, 10, 10);
}
public void setDegrees(int degrees)
{
this.degrees = degrees;
double radians = Math.toRadians( degrees );
rotated = new RotatedIcon(icon, degrees);
repaint();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
String path = "dukewavered.gif";
ClassLoader cl = Rotation3.class.getClassLoader();
BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
final Rotation3 r = new Rotation3(bi);
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = slider.getValue();
r.setDegrees( value );
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(r));
f.add(slider, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
catch(IOException e)
{
System.out.println(e);
}
}
});
}
}
Edit 2:
Even easier than I thought here is an example:
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class Rotation2 extends JPanel
{
BufferedImage image;
int degrees;
int point = 250;
public Rotation2(BufferedImage image)
{
this.image = image;
setDegrees( 0 );
setPreferredSize( new Dimension(600, 600) );
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
double radians = Math.toRadians( degrees );
g2.translate(point, point);
g2.rotate(radians);
g2.translate(-image.getWidth(this) / 2, -image.getHeight(this) / 2);
g2.drawImage(image, 0, 0, null);
g2.dispose();
g.setColor(Color.RED);
g.fillOval(point - 5, point - 5, 10, 10);
}
public void setDegrees(int degrees)
{
this.degrees = degrees;
repaint();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
// String path = "mong.jpg";
String path = "dukewavered.gif";
ClassLoader cl = Rotation2.class.getClassLoader();
BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
final Rotation2 r = new Rotation2(bi);
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = slider.getValue();
r.setDegrees( value );
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(r));
f.add(slider, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
catch(IOException e)
{
System.out.println(e);
}
}
});
}
}
The rotation code was taken from: How to rotate an image gradually in Swing?
Problem
I recently saw this question on how to rotate an image in Java. I copy/pasted it directly from that answer. On implementation, it seems to only rotate images that are squares(that is have the same size, width and height). When I try to use it for a non-square image, it seems to cut off that part that would make it be a rectangle, if that makes sense. Like this How can I fix/work around this? Edit: The code I'm using. Also, I won't have a scroll bar as this will be a "game", and also won't be in full screen all of the time. ``` public class Player extends Entity { //Entity has basic values such as (float) x & y values, along with some getters and setters double theta; Reticle reticle; //draws a reticle where the cursor was(basically just replaces java.awt.Cursor due to something not neccessary for me to get into) Sprite currentImage; //basically just a BufferedImage that you can apply aspect ratios to //constructor @Override public void tick() { //(this line) gets the Reticle from the main-method class and set it to this reticle object reticleX = reticle.getX(); //basically gets the mouse coordinates reticleY = reticle.getY(); x += dX; //delta or change in X y += dY //delta or change in Y checkCollision(); //bounds checking //getCentralizedX()/Y() gets the center of the currentImage theta = getAngle(getCentralizedX(), getCentralizedY(), reticleX, reticleY); currentImage = Images.rotateSprite(currentImage, theta); } @Override public void render(Graphics g) { currentImage.render(g, x, y); g.drawLine((int) getCentralizedX(), (int) getCentralizedY(), (int) reticleX, (int) reticleY); } public double getAngle(float startX, float startY, float goalX, float goalY) { double angle = Math.atan2(goalY - startY, goalX - startX); //if(angle < 0) { //removed this as this was the thing messing up the rotation //angle += 360; //} } ``` If the angle of the soldier is from 90 < angle < 270, then it is (basically), however, if its its 90 > angle > 270, then it gets a little wonky. Here are some pictures. It is not the angle of the aim-line(the blue line) that is wrong. Removed all of the images as removing the `if(angle < 0)` inside of `getAngle()` fixed the rotation bug. Now the only problem is that it doesn't rotate in place. EDIT 2: My SSCCE, which uses the same method as my game, but freaks out for some reason. ``` public class RotateEx extends Canvas implements Runnable { Player player; public RotateEx(BufferedImage image) { player = new Player(image, 50, 50); setPreferredSize(new Dimension(600, 600)); } public void setDegrees(int degrees) { player.theta = Math.toRadians(degrees); } public BufferedImage rotateImage(BufferedImage original, double theta) { double cos = Math.abs(Math.cos(theta)); double sin = Math.abs(Math.sin(theta)); double width = original.getWidth(); double height = original.getHeight(); int w = (int) (width * cos + height * sin); int h = (int) (width * sin + height * cos); BufferedImage out = new BufferedImage(w, h, original.getType()); Graphics2D g2 = out.createGraphics(); double x = w / 2; //the middle of the two new values double y = h / 2; AffineTransform at = AffineTransform.getRotateInstance(theta, x, y); x = (w - width) / 2; y = (h - height) / 2; at.translate(x, y); g2.drawRenderedImage(original, at); g2.dispose(); return out; } public void tick() { player.tick(); } public void render() { BufferStrategy bs = this.getBufferStrategy(); if(bs == null) { createBufferStrategy(4); return; } Graphics g = bs.getDrawGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, getWidth(), getHeight()); player.render(g); g.dispose(); bs.show(); } public static void main(String args[]) throws IOException, InterruptedException { String loc = "FILELOCATION"; //of course this would be a valid image file BufferedImage image = ImageIO.read(new File(loc)); final RotateEx ex = new RotateEx(image); final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0); slider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { int value = slider.getValue(); ex.setDegrees(value); } }); JFrame f = new JFrame(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(ex); f.add(slider, BorderLayout.SOUTH); f.pack(); f.setLocationRelativeTo(null); f.setVisible(true); new Thread(ex).start(); } @Override public void run() { long lastTime = System.nanoTime(); final double numTicks = 60.0; double n = 1000000000 / numTicks; double delta = 0; int frames = 0; int ticks = 0; long timer = System.currentTimeMillis(); while (true) { long currentTime = System.nanoTime(); delta += (currentTime - lastTime) / n; lastTime = currentTime; render(); tick(); frames++; if (delta >= 1) { ticks++; delta--; } } } class Player { public float x, y; int width, height; public double theta; //how much to rotate, in radians BufferedImage currentImage; //this image would change, according to the animation and what frame its on public Player(BufferedImage image, float x, float y) { this.x = x; this.y = y; width = image.getWidth(); height = image.getHeight(); currentImage = image; } public void tick() { currentImage = rotateImage(currentImage, theta); } public void render(Graphics g) { g.drawImage(currentImage, (int) x, (int) y, null); } } ``` }