Rotating Image with AffineTransform
The major problem (that I can see) is the translation of the Graphics
context which is offset the position that the rotation will take place.
I "think" rotation by default occurs at the top/left corner of the Graphics
context (where it's 0x0 position is, which you've translated to something else), this could be causing the image to be rotated out of frame (or viewable area)
You should provide a "anchor" point where the rotation takes place, typically, the centre is my personal preference.
The following example simply has a master image (due to size constraints I had to scale it, but you may not need this). I then use this to generate a "rotated" instance which is sized to allow the image to fit within in. This is a lot of fun with trig - I stole the code from somewhere, so credit to that developer.
The example allows you to click any where and it will change the rotation pivot, so you can see what's going on. The default position is the centre of the pane...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class SampleRotation {
public static void main(String[] args) {
new SampleRotation();
}
public SampleRotation() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
final RotationPane rotationPane = new RotationPane();
final JSlider slider = new JSlider(0, 100);
slider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
double angle = 720d * (slider.getValue() / 100d);
rotationPane.setAngle(angle);
}
});
slider.setValue(0);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(rotationPane);
frame.add(slider, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class RotationPane extends JPanel {
private BufferedImage img;
private BufferedImage rotated;
private double angle;
private Point clickPoint;
public RotationPane() {
try {
img = ImageIO.read(new File("/Users/swhitehead/Dropbox/MegaTokyo/issue459.jpg"));
BufferedImage scaled = new BufferedImage(img.getWidth() / 2, img.getHeight() / 2, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = scaled.createGraphics();
g2d.setTransform(AffineTransform.getScaleInstance(0.5d, 0.5d));
g2d.drawImage(img, 0, 0, this);
g2d.dispose();
img = scaled;
setAngle(0d);
} catch (IOException ex) {
ex.printStackTrace();
}
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
clickPoint = e.getPoint();
repaint();
}
});
}
public void setAngle(double angle) {
this.angle = angle;
double rads = Math.toRadians(getAngle());
double sin = Math.abs(Math.sin(rads)), cos = Math.abs(Math.cos(rads));
int w = img.getWidth();
int h = img.getHeight();
int newWidth = (int) Math.floor(w * cos + h * sin);
int newHeight = (int) Math.floor(h * cos + w * sin);
rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = rotated.createGraphics();
AffineTransform at = new AffineTransform();
at.translate((newWidth - w) / 2, (newHeight - h) / 2);
int x = clickPoint == null ? w / 2 : clickPoint.x;
int y = clickPoint == null ? h / 2 : clickPoint.y;
at.rotate(rads, x, y);
g2d.setTransform(at);
g2d.drawImage(img, 0, 0, this);
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, newWidth - 1, newHeight - 1);
g2d.dispose();
repaint();
}
public double getAngle() {
return angle;
}
@Override
public Dimension getPreferredSize() {
return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(this), img.getHeight(this));
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (rotated != null) {
Graphics2D g2d = (Graphics2D) g.create();
int x = (getWidth() - rotated.getWidth()) / 2;
int y = (getHeight() - rotated.getHeight()) / 2;
g2d.drawImage(rotated, x, y, this);
g2d.setColor(Color.RED);
x = clickPoint == null ? getWidth() / 2 : clickPoint.x;
y = clickPoint == null ? getHeight()/ 2 : clickPoint.y;
x -= 5;
y -= 5;
g2d.drawOval(x, y, 10, 10);
g2d.dispose();
}
}
}
}
Java Graphics2D AffineTransform Image Rotation
I've transformed your code using the recommendation of MadProgrammer:
- Don't override
paint
, overridepaintComponent
. - Call
super.paint
before performing any custom painting, - Never call
finalize
on anything and especially not on objects you didn't create yourself. - Use a Swing Timer
Note the following
A qualified this is used to access the ImageRotationView instance from the ActionListener inner class.
AffineTransform.getRotateInstance
returns a transform that rotates coordinates around an anchor point.- Speed can be optimized but it works correctly like this.
- This class works as a standalone application
- A file named
dice.png
should be present in the base directory.
.
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.Timer;
public class ImageRotationFrame {
public static void main(String[] args) {
new ImageRotationFrame();
}
public ImageRotationFrame() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame("Testing");
frame.setSize(400, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new ImageRotationComponent());
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private class ImageRotationComponent extends JComponent {
Image arrow;
double angle = 0.0;
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
angle += 0.4;
AffineTransform trans = AffineTransform.getRotateInstance(angle, getWidth() / 2, getHeight() / 2);
((Graphics2D) g).drawImage(arrow, trans, this);
}
public ImageRotationComponent() {
try {
arrow = ImageIO.read(new File("dice.png"));
} catch (IOException e) {
e.printStackTrace();
}
int delay = 500; //milliseconds
ActionListener taskPerformer = new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
ImageRotationComponent.this.repaint();
}
};
new Timer(delay, taskPerformer).start();
}
}
}
Rotating Image with AffineTransform Produces Visually Correct Pixels, but getRGB Doesn't Match
You are looking at the wrong pixel.
Assume your image is 1x1
pixel in size. You're originally looking at pixel (0, 0)
. Then with your code, in the destination image, you're looking at pixel (width - 0, height - 1)
, which turns out to be pixel (1, 1)
. When clearly you still need to be looking at (0, 0)
as the image doesn't have a pixel at (1, 1)
.
In your case, your transformed coordinate isn't outside your image, but it points to the wrong pixel.
To fix your code, change the lines that calculate your expX
and expY
values, and subtract 1 from each:
int expX = destImage.getWidth() - origX - 1;
int expY = destImage.getHeight() - origY - 1;
That returns the correct pixel values when I run your code (on my own image, as you haven't provided the image that you used).
There is another issue in the code that will only surface with an odd-numbered width or height: you're using integer arithmetic to determine the center of rotation, which rounds down when it shouldn't. Change the lines that determine the center to (to use floating-point numbers, so that you can get a fractional number as a result) :
double centerX = origImage.getWidth() / 2.0;
double centerY = origImage.getHeight() / 2.0;
AffineTransform.rotate() - how do I xlate, rotate, and scale at the same time?
Okay, this is a little slight of hand. The example code will only work for 90 degree increments (it was only designed this way), to do smaller increments you to use some trig to calculate the image width and height (there's a answer somewhere for that to ;))
public class ImagePane extends JPanel {
private BufferedImage masterImage;
private BufferedImage renderedImage;
public ImagePane(BufferedImage image) {
masterImage = image;
applyRotation(0);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(renderedImage.getWidth(), renderedImage.getHeight());
}
@Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
protected int getVirtualAngle(int angle) {
float fRotations = (float) angle / 360f;
int rotations = (int) (fRotations - (fRotations / 1000));
int virtual = angle - (rotations * 360);
if (virtual < 0) {
virtual = 360 + virtual;
}
return virtual;
}
public void applyRotation(int angle) {
// This will only work for angles of 90 degrees...
// Normalize the angle to make sure it's only between 0-360 degrees
int virtualAngle = getVirtualAngle(angle);
Dimension size = new Dimension(masterImage.getWidth(), masterImage.getHeight());
int masterWidth = masterImage.getWidth();
int masterHeight = masterImage.getHeight();
double x = 0; //masterWidth / 2.0;
double y = 0; //masterHeight / 2.0;
switch (virtualAngle) {
case 0:
break;
case 180:
break;
case 90:
case 270:
size = new Dimension(masterImage.getHeight(), masterImage.getWidth());
x = (masterHeight - masterWidth) / 2.0;
y = (masterWidth - masterHeight) / 2.0;
break;
}
renderedImage = new BufferedImage(size.width, size.height, masterImage.getTransparency());
Graphics2D g2d = renderedImage.createGraphics();
AffineTransform at = AffineTransform.getTranslateInstance(x, y);
at.rotate(Math.toRadians(virtualAngle), masterWidth / 2.0, masterHeight / 2.0);
g2d.drawImage(masterImage, at, null);
g2d.dispose();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
int width = getWidth() - 1;
int height = getHeight() - 1;
int x = (width - renderedImage.getWidth()) / 2;
int y = (height - renderedImage.getHeight()) / 2;
g2d.drawImage(renderedImage, x, y, this);
}
}
Now, you could simply "flip" the image vertically, if that works better for you
public class FlipPane extends JPanel {
private BufferedImage masterImage;
private BufferedImage renderedImage;
public FlipPane(BufferedImage image) {
masterImage = image;
flipMaster();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(renderedImage.getWidth(), renderedImage.getHeight());
}
@Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
protected void flipMaster() {
renderedImage = new BufferedImage(masterImage.getWidth(), masterImage.getHeight(), masterImage.getTransparency());
Graphics2D g2d = renderedImage.createGraphics();
g2d.setTransform(AffineTransform.getScaleInstance(1, -1));
g2d.drawImage(masterImage, 0, -masterImage.getHeight(), this);
g2d.dispose();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
int width = getWidth() - 1;
int height = getHeight() - 1;
int x = (width - renderedImage.getWidth()) / 2;
int y = (height - renderedImage.getHeight()) / 2;
g2d.drawImage(renderedImage, x, y, this);
}
}
This basically results in:
Original | 180 degree rotation | Vertical inversion...
Now, if you change the flipMaster
method to read:
g2d.setTransform(AffineTransform.getScaleInstance(-1, -1));
g2d.drawImage(masterImage, -masterImage.getWidth(), -masterImage.getHeight(), this);
You'll get the same effect as the 180 rotation ;)
I'm rotating image in java but want to save rotated image
Let's start with, this isn't rotating the image, it's rotating the Graphics
context which is used to display the image, it doesn't affect the original image at all
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);
g2d.setColor(Color.BLACK);
int x = (getWidth() - image.getWidth()) / 2;
int y = (getHeight() - image.getHeight()) / 2;
AffineTransform at = new AffineTransform();
at.setToRotation(getAngle(), x + (image.getWidth() / 2), y + (image.getHeight() / 2));
at.translate(x, y);
g2d.setTransform(at);
g2d.drawImage(image, 0, 0, this);
g2d.dispose();
}
And then this...
try {
File imagefile = new File("C:/pics/1206.jpg");
image = ImageIO.read(imagefile);
ImageIO.write(image, "jpg",new File("C:/pics"));
ImageIO.write(image, "bmp",new File("C:/pics"));
ImageIO.write(image, "gif",new File("C:/picsf"));
ImageIO.write(image, "png",new File("C:/pics"));
} catch (IOException ex) {
ex.printStackTrace();
}
which just saves the original image to a number of different formats ... to the same file , but doesn't even react to any changes to the angle
Instead, you need to use something which generates a new image from the original, rotated by the amount you want...
public BufferedImage rotateImageByDegrees(BufferedImage img, double degrees) {
double rads = Math.toRadians(degrees);
double sin = Math.abs(Math.sin(rads)), cos = Math.abs(Math.cos(rads));
int w = img.getWidth();
int h = img.getHeight();
int newWidth = (int) Math.floor(w * cos + h * sin);
int newHeight = (int) Math.floor(h * cos + w * sin);
BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = rotated.createGraphics();
AffineTransform at = new AffineTransform();
at.translate((newWidth - w) / 2, (newHeight - h) / 2);
int x = clickPoint == null ? w / 2 : clickPoint.x;
int y = clickPoint == null ? h / 2 : clickPoint.y;
at.rotate(rads, x, y);
g2d.setTransform(at);
g2d.drawImage(img, 0, 0, this);
g2d.setColor(Color.RED);
g2d.drawRect(0, 0, newWidth - 1, newHeight - 1);
g2d.dispose();
return rotated;
}
Then you can save it...
File imagefile = new File("C:/pics/1206.jpg");
image = ImageIO.read(imagefile);
BufferedImage rotated = rotateImageByDegrees(image, 22.5);
ImageIO.write(rotated, "png", new File("RotatedBy225.png"));
So, the next time you use one of my previous examples and I tell you it's not doing what you think/want it to, I hope you will understand my meaning better and look more closly at the other examples we show you
Java image rotation with AffineTransform outputs black image, but works well when resized
Passing a new BufferedImage into the filter() method rather than letting it create its own works (not completely black).
Also the transform did not appear to work correctly, the image ended up being offset in the destination. I was able to fix it by manually applying the necessary translations, note these work in reverse order, and in the destination image the width = the old height, and height = the old width.
AffineTransform tx = new AffineTransform();
// last, width = height and height = width :)
tx.translate(originalImage.getHeight() / 2,originalImage.getWidth() / 2);
tx.rotate(Math.PI / 2);
// first - center image at the origin so rotate works OK
tx.translate(-originalImage.getWidth() / 2,-originalImage.getHeight() / 2);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
// new destination image where height = width and width = height.
BufferedImage newImage =new BufferedImage(originalImage.getHeight(), originalImage.getWidth(), originalImage.getType());
op.filter(originalImage, newImage);
The javadoc for filter() states that it will create a BufferedImage for you, I'm still unsure why this does not work, there must be an issue here.
If the destination image is null, a BufferedImage is created with the source ColorModel.
Related Topics
Jschexception: Algorithm Negotiation Fail
How to Insert Image into Jtable Cell
Manipulating and Comparing Floating Points in Java
Java, How to Add Library Files in Netbeans
How to Test Void Method with Junit Testing Tools
Instance Method Reference and Lambda Parameters
Compiler Error When Declaring a Variable Inside If Condition and No Curly Braces
Delete Item from Array and Shrink Array
Why Does Arraylist Have "Implements List"
CSV Parsing in Java - Working Example
JSONobject:Why JSONobject Changing the Order of Attributes
Validate Jaxbelement in JPA/Jax-Rs Web Service
How to Use @Id with String Type in JPA/Hibernate
Use Custom Fonts When Creating PDF Using Ireport
Get the Changed HTML Content After It's Updated by JavaScript? (Htmlunit)
Hibernate - @Elementcollection - Strange Delete/Insert Behavior