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.