Affine Transformations

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);
        }
    }
}

AffineTransformOp Filter

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);
        }
    }
}

Using The Mouse to Rotate an Image

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)
        {
        }
    }
}

Using Renering Hints

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);
        }
    }
}

Mirror Images

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);
        }
    }
}
 
<div align="center"><a href="../versionC/index.html" title="DKIT Lecture notes homepage for Derek O&#39; Reilly, Dundalk Institute of Technology (DKIT), Dundalk, County Louth, Ireland. Copyright Derek O&#39; Reilly, DKIT." target="_parent" style='font-size:0;color:white;background-color:white'>&nbsp;</a></div>