Copyright Derek O'Reilly, Dundalk Institute of Technology (DkIT), Dundalk, Co. Louth, Ireland.
Affine transformations allow us to translate, scale, rotate and shear content as it is being painted onto a component.
Rotation within paintComponent using an AffineTransform (Run Applet)
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class AffineTransformDemo extends JApplet
{
@Override
public void init()
{
this.setContentPane(new View());
}
public class View extends JPanel
{
@Override
public void paintComponent(Graphics g)
{
final Image image = new ImageIcon(getClass().getClassLoader().getResource("images/koala.jpg")).getImage();
final int startX = 0;
final int startY = 75;
final int width = 100;
final int height = 100;
final int step = 120;
final AffineTransform affineTransform = new AffineTransform();
super.paintComponent(g);
final Graphics2D g2 = (Graphics2D) g;
// painting done prior to application of AffineTransform will not be affected by transformation
g2.drawString("Some text", startX, startY - 10);
g2.drawImage(image, startX, startY, width, height, this);
// rotate about the centre of the image about the centre of rotation
affineTransform.rotate(Math.toRadians(45.0), (double) startX + (double) step + (width / 2.0), (double) startY + (height / 2.0));
// set the transformation
g2.setTransform(affineTransform);
// transformation will now be applied to g2d
g2.drawImage(image, startX + step, startY, width, height, this);
g2.setColor(Color.BLACK);
g2.drawString("Transformed text", startX + step, startY - 10);
// reset the transformation matrix
affineTransform.setToIdentity();
// rotate about the centre of the image about the centre of rotation
affineTransform.rotate(Math.toRadians(90.0), (double) startX + (double) step * 2.0 + (width / 2.0), (double) startY + (height / 2.0));
g2.setTransform(affineTransform);
g2.drawImage(image, startX + (2 * step), startY, width, height, this);
}
}
}
Instead of applying a transform to a graphic, it can be applied to an image as it is being drawn.
Rotation of an image prior to painting using an AffineTransform (Run Applet)
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class AffineTransformOfImageDemo extends JApplet
{
@Override
public void init()
{
this.setContentPane(new View());
}
public class View extends JPanel
{
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
final Graphics2D g2 = (Graphics2D) g;
final Image image = new ImageIcon(getClass().getClassLoader().getResource("images/koala.jpg")).getImage();
final AffineTransform affineTransform = new AffineTransform();
// transforms are applied in reverse order
affineTransform.translate(50, 10); // the transform that is listed first, is applied last
affineTransform.rotate(Math.toRadians(45.0));
affineTransform.scale(0.2, 0.05); // the transform that is listed last is applied first
g2.drawImage(image, affineTransform, this);
}
}
}
Using a AffineTransformOp is similar the various other BufferedImageOp classes. In all cases, we can apply a filter to a source to generate a destination. The other BufferedImageOp classes are explained in further detail in these other sections of the notes (ColorConvertOp, ConvolveOp, LookupOp & RescaleOp).
AffineTransformOp Example (Run Applet)
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.*;
public class AffineTransformOpDemo extends JApplet
{
@Override
public void init()
{
this.setContentPane(new View());
}
public class View extends JPanel
{
private final Image image = new ImageIcon(getClass().getClassLoader().getResource("images/koala.jpg")).getImage();
public View()
{
super();
}
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// place the original file image into buffered image
final BufferedImage srcBufferedImg = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
final Graphics2D SrcG = srcBufferedImg.createGraphics();
SrcG.drawImage(this.image, 0, 0, getWidth(), getHeight(), this);
final AffineTransform affineTransform = new AffineTransform();
// transforms are applied in reverse order
affineTransform.translate(100, 100); // the transform that is listed first, is applied last
affineTransform.rotate(Math.toRadians(-20.0));
affineTransform.scale(0.3, 0.3); // this transform that is listed last is applied first
// make a new transformation filter
AffineTransformOp affineTransformOp = new AffineTransformOp(affineTransform, null);
// apply the transformation filter
final BufferedImage destBufferedImg = affineTransformOp.filter(srcBufferedImg, null);
// draw the destination buffered image onto the applet's panel
g.drawImage(destBufferedImg, 0, 0, getWidth(), getHeight(), this);
}
}
}
The example below shows how we can use the mouse to rotate an image.
Rotation of an image prior to painting using an AffineTransform (Run Applet)
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JPanel;
public class AffineTransformOp_RealtimeRotate_Demo extends JApplet
{
@Override
public void init()
{
this.setContentPane(new View());
}
public class View extends JPanel implements MouseListener, MouseMotionListener
{
private double rotationDegrees = 0; // the number of degrees to rotate the image
private int mouseStartX; // used in MouseDragged() to find direction that mouse is moving
private int mouseStartY;
private final Image image = new ImageIcon(getClass().getClassLoader().getResource("images/koala.jpg")).getImage();
private final int imageWidth = 130; // the size of the displayed image
private final int imageHeight = 100;
private final int imageDisplayX = 50; // The location on the screen of the image
private final int imageDisplayY = 50;
public View()
{
super();
this.addMouseListener(this);
this.addMouseMotionListener(this);
}
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// place the original file image into buffered image
final BufferedImage srcBufferedImg = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
final Graphics2D SrcG = srcBufferedImg.createGraphics();
SrcG.drawImage(this.image, 0, 0, this.imageWidth, this.imageHeight, this);
final int centreX = this.imageWidth / 2;
final int centreY = this.imageHeight / 2;
final AffineTransform affineTransform = new AffineTransform();
// transforms are applied in reverse order
affineTransform.translate(this.imageDisplayX, this.imageDisplayY); // location on the display to paint the image
// To rotate about the centre of an image, we must:
// 1) translate the centre of the image to the origin
// 2) rotate
// 3) translate the centre of the image back to its original position
// Remember that the transforms are applied in reverse order
affineTransform.translate(centreX, centreY);
affineTransform.rotate(Math.toRadians(this.rotationDegrees));
affineTransform.translate(-centreX, -centreY);
// make a new AffineTransformOp filter
final AffineTransformOp affineTransformOp = new AffineTransformOp(affineTransform, null);
// apply the AffineTransformOp filter
final BufferedImage destBufferedImg = affineTransformOp.filter(srcBufferedImg, null);
// draw the destination buffered image onto the applet's panel
g.drawImage(destBufferedImg, 0, 0, destBufferedImg.getWidth(), destBufferedImg.getHeight(), this);
}
@Override
public void mouseDragged(MouseEvent e)
{
final int rotationStepSize = 10;
// Set rotation amount based on the direction that the mouse is moving and
// where the mouse is being dragged in relation to the centre of the image
if (Math.abs(e.getX() - this.mouseStartX) > Math.abs(e.getY() - this.mouseStartY))
{
// mouse is being moved primarily along the x-axis
final int centerY = this.imageDisplayY + (this.imageHeight / 2);
if ((e.getX() < this.mouseStartX)) // mouse is moving to the left
{
if (this.mouseStartY < centerY) // mouse is above the image
{
this.rotationDegrees -= rotationStepSize;
}
else // mouse is below the image
{
this.rotationDegrees += rotationStepSize;
}
}
else // mouse is moving to the right
{
if (this.mouseStartY < centerY) // mouse is to the left of the image
{
this.rotationDegrees += rotationStepSize;
}
else // mouse is to the right of the image
{
this.rotationDegrees -= rotationStepSize;
}
}
}
else
{
// mouse is being moved primarily along the y-axis
final int centerX = this.imageDisplayX + (this.imageWidth / 2);
if ((e.getY() < this.mouseStartY)) // mouse is moving to the left
{
if (this.mouseStartX < centerX) // mouse is above the image
{
this.rotationDegrees += rotationStepSize;
}
else // mouse is below the image
{
this.rotationDegrees -= rotationStepSize;
}
}
else // mouse is moving to the right
{
if (this.mouseStartX < centerX) // mouse is to the left of the image
{
this.rotationDegrees -= rotationStepSize;
}
else // mouse is to the right of the image
{
this.rotationDegrees += rotationStepSize;
}
}
}
this.mouseStartX = e.getX();
this.mouseStartY = e.getY();
this.repaint();
}
@Override
public void mouseMoved(MouseEvent e)
{
}
@Override
public void mouseClicked(MouseEvent e)
{
}
@Override
public void mousePressed(MouseEvent e)
{
this.mouseStartX = e.getX();
this.mouseStartY = e.getY();
}
@Override
public void mouseReleased(MouseEvent e)
{
}
@Override
public void mouseEntered(MouseEvent e)
{
}
@Override
public void mouseExited(MouseEvent e)
{
}
}
}
Rendering hints can be used to increase the quality of an affine transformation. Note that Rendering hints can also be used on the other BufferedImageOps - ColorConvertOp, ConvolveOp, LookupOp & RescaleOp.
AffineTransformOp_WithRenderingHint_Demo Example (Run Applet)
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.*;
public class AffineTransformOp_WithRenderingHint_Demo extends JApplet
{
@Override
public void init()
{
this.setContentPane(new View());
}
public class View extends JPanel implements ActionListener
{
private final Image image = new ImageIcon(getClass().getClassLoader().getResource("images/koala.jpg")).getImage();
private final JButton renderingHintButton = new JButton("Turn Hint On");
private RenderingHints renderingHints = null;
public View()
{
super();
this.add(this.renderingHintButton);
this.renderingHintButton.addActionListener(this);
}
@Override
public void actionPerformed(ActionEvent e)
{
if (this.renderingHintButton.getText().equals("Turn Hint On"))
{
renderingHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
this.renderingHintButton.setText("Turn Hint Off");
}
else
{
renderingHints = null;
this.renderingHintButton.setText("Turn Hint On");
}
this.repaint();
}
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// place the original file image into buffered image
final BufferedImage srcBufferedImg = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
final Graphics2D SrcG = srcBufferedImg.createGraphics();
SrcG.drawImage(this.image, 0, 0, getWidth(), getHeight(), this);
final AffineTransform affineTransform = new AffineTransform();
// transforms are applied in reverse order
affineTransform.translate(100, 100); // the transform that is listed first, is applied last
affineTransform.rotate(Math.toRadians(-20.0));
affineTransform.scale(0.3, 0.3); // this transform that is listed last is applied first
// make a new ColorConvert filter
final AffineTransformOp affineTransformOp = new AffineTransformOp(affineTransform, renderingHints);
// apply the ColorConvert filter
final BufferedImage destBufferedImg = affineTransformOp.filter(srcBufferedImg, null);
// draw the destination buffered image onto the applet's panel
g.drawImage(destBufferedImg, 0, 0, getWidth(), getHeight(), this);
}
}
}
We can use the AffineTransform to creat a mirrow image of an original image, as can be seen in the example below.
AffineTransform_MirrorImage_Demo Example (Run Applet)
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import javax.swing.*;
public class AffineTransform_MirrorImage_Demo extends JApplet
{
@Override
public void init()
{
this.setContentPane(new View());
}
public class View extends JPanel
{
private final Image image = new ImageIcon(getClass().getClassLoader().getResource("images/koala.jpg")).getImage();
public View()
{
super();
}
@Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// place the original file image into buffered image
final BufferedImage originalBufferedImg = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
final Graphics2D originalBufferedG = originalBufferedImg.createGraphics();
originalBufferedG.drawImage(this.image, 0, 0, getWidth(), getHeight(), this);
final AffineTransform affineTransform = new AffineTransform();
affineTransform.translate(0, getHeight()); // need to translate because we are scaling y by a factor of -1
affineTransform.scale(1.0, -1.0);
final AffineTransformOp affineTransformOp = new AffineTransformOp(affineTransform, null);
final BufferedImage invertedBufferedImg = affineTransformOp.filter(originalBufferedImg, null);
// draw the original image and the inverted image
g.drawImage(this.image, 0, 0, getWidth(), getHeight() / 2, this);
g.drawImage(invertedBufferedImg, 0, getHeight() / 2, getWidth(), getHeight() / 2, this);
}
}
}
Copyright Derek O' Reilly, Dundalk Institute of Technology (DkIT), Dundalk, Co. Louth, Ireland.