Wie kann man Rotationen, Skalierungen, etc vornehmen?

Mit der Klasse AffineTransform lassen sich graphische Transformationen wie Rotationen, Scherungen, Translationen (=Verschiebungen) und Skalierungen vornehmen.

Unter einer affinen Transformation versteht man die geometrische Transformation eines Koordinatensystems in ein anderes. Es werden hier zwei Beispiele vorgestellt, die auf unterschiedliche Weise diese Problematik beleuchten.
Script 1 lädt zum Experimentieren ein. Es modifiziert einfache geometrische Grundformen (ein Rechteck und eine Linie) durch einfaches Ein- und Auskommentieren kleiner, nummerierter Scriptbereiche.
Script 2 stellt eine kleine Applikation dar, die einige Möglichkeiten der Klasse AffineTransform anhand eines Bildes aufzeigt und die Ergebnisse in einer GUI (Graphical User Interphase = Bildschirmfenster) präsentiert

Das erste Beispiel besteht aus zwei Klassen. Die Hauptklasse mit der main-Methode ist von JFrame abgeleitet und stellt den größten Teil des GUI in einem BorderLayout dar. Ins Zentrum wird ein Objekt der zweiten Klasse als Zeichenfläche geladen, darunter befindet sich ein JSlider, der, je nach Auswahl, eine schrittweise Transformation ermöglicht. Der Slider ist bei einem ChangeListener angemeldet, der seinen eingestellten Wert an eine Variable übergibt, die das gezeichnete Objekt modifiziert und anschließend das JPanel neu zeichnet.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class AffineTransformation extends JFrame implements ChangeListener {
    
    AffineTransformationPanel panel;
    
    public AffineTransformation(){
        panel = new AffineTransformationPanel();
        setLayout(new BorderLayout());
        add(panel, BorderLayout.CENTER);
        
        JSlider slider = new JSlider(0, 10, 0);
        slider.addChangeListener(this);
        add(slider, BorderLayout.SOUTH);

        this.setSize(300, 300);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }

    public static void main(String[] args) {
        new AffineTransformation();
    }

    public void stateChanged(ChangeEvent e) {
        panel.setNum((double)((JSlider)e.getSource()).getValue()/5);
        panel.repaint();
    }
}

class AffineTransformationPanel extends JPanel {
    
    double num;
    
    public void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
        g2d.setColor(Color.BLACK);
        int x = 15;
        int y = 10;
        int h = 100;
        int b = 200;
        Rectangle2D.Double rect = new Rectangle2D.Double(x, y, b, h);
        g2d.setColor(Color.RED);
        g2d.draw(rect);
        
        AffineTransform at = new AffineTransform();
        // (1)Scherung und zusätzliche Translation
        //at.setToShear(num, num/5);
        //at.translate(num*30, num*40);
        
        // (2)Translation (Verschiebung) und zusätzliche Scherung
        //at.setToTranslation(num*30, num*40);
        //at.shear(num, num/5);
        
        // (3)Skalierung
        //at.setToScale(num, num);
        
        // (4)Rotation um den Punkt x/y
        //at.setToRotation(num, x, y);
        
        // (5)Rotation um den Punkt 0/0
        //at.setToRotation(num);
        
        Shape s = at.createTransformedShape(rect);
        g2d.setColor(Color.BLACK);
        g2d.draw(s);
        
        // Verbindung der affinen Transformation mit dem gesamten Grafics-Kontext
        // bewirkt eine Transformation aller nachfolgender Objekte
        g2d.transform(at);
        Line2D.Double line = new Line2D.Double(100, 0, 100, 300);
        g2d.draw(line);
    }
    
    void setNum(double num){
        this.num = num;
    }
}

Diese Variable ist das einzige Feld, das die Klasse AffineTransformationPanel definiert. In der von JPanel abgeleiteten Klasse sind zudem noch zwei Methoden deklariert. Eine Getter-Methode für die Variable und die überschriebene Methode paintComponent(), die die Zeichenarbeit übernimmt.
Hier wird zunächst das als Parameter übergebene Graphics-Objekt in ein Objekt des Typs Graphics2D gecastet und mit diesem ein weißes gefülltes Rechteck in der Größe des Panels gezeichnet, um eine weiße Grundlage zu erhalten. In der Folge werden vier Variablen, die den linken oberen Initialisierungspunkt und die Breite und Höhe eines Rechtecks angeben deklariert und mit den entsprechenden Werten initialisiert. Mit diesen Werten wird ein Objekt der Klasse Rectangle2D.Double erzeugt. Die Wandlung der int-Werte in die notwendigen double-Werte erfolgt selbstständig. Das Objekt stellt ein als Umriss gezeichnetes Rechteck dar, das auf dem Graphics2D-Objekt durch die Mehtode draw() erzeugt wird. Es dient als Gegenstand der folgenden Manipulationen.

Hierzu wird als erstes ein Objekt der Klasse AffineTransform erzeugt. Auf ihm werden die geometrischen Transformationen vorgenommen. Einige sind in den folgenden fünf Abschnitten dargestellt und können einzeln auskommentiert werden, um ihre Auswirkung zu erkunden. Allen gemeinsam ist die Tatsache, das auf dem AffineTransform-Objekt eine Methode setToXXX() ausgeführt wird, wobei XXX angibt, welche Transformation ausgeführt wird. Die vom JSlider belegte Variable num wird hierbei als Parameter übergeben. Eine zusätzliche Transformation kann wie in (1) und (2) gezeigt über die Methoden shear(), translate(), rotate() und scale() durchgeführt werden. Wichtig zu wissen sind drei Dinge:

Die Zeichnung selber wird nach Anpassung der Vordergrundfarbe im Beispiel durch die Methode draw() vorgenommen, die auf dem Graphics2D-Objekt ausgeführt wird.
Möchte man nicht für jedes zu transformierende Objekt eine andere AffineTransform-Methode aufrufen, sondern verschiedene Objekte auf gleiche Weise manipulieren, so lässt sich das AffineTransform-Objekt auch direkt dem Graphik-Kontext mit der Methode transform() übergeben. Als Beispiel dient die im letzten Abschnitt der Methode erzeugte Linie.

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * Die Klasse demonstriert verschiedene grafische Transformationen
 * anhand eines Bildes (Pfad anpassen!)
 */
public class ImageDemo extends JFrame implements ChangeListener, ActionListener {

    private final String bildPfad = "test.jpg";

    private BufferedImage[] img;

    private JRadioButton scaleRB, rotateRB, transRB, scherRB;

    private JLabel origLabel, transLabel;

    private JTextField xScaleField, yScaleField,
            xPosRotateField, yPosRotateField, xTransField, yTransField,
            xScherField, yScherField;

    private JButton button;
    
    JSlider rotateSlider;

    private CardLayout cl;

    private JPanel cardPanel;

    private Box scalePanel, rotatePanel, transPanel, scherPanel;

    private static final long serialVersionUID = 1L;

    public ImageDemo() {

        JPanel rechtsPanel = initRB();

        img = getImages();

        if (img == null) {
            System.out.println("Image-Import fehlgeschlagen!");
            System.exit(1);
        }

        ImageIcon origIcon = new ImageIcon(img[0]);
        origLabel = new JLabel(origIcon);
        origLabel.setBorder(createBorder("Original"));

        ImageIcon transIcon = new ImageIcon();
        transLabel = new JLabel(transIcon);
        transLabel.setBorder(createBorder("Transformation"));

        JPanel centerPanel = new JPanel();
        centerPanel.setLayout(new GridLayout(2, 1));
        centerPanel.setBackground(Color.DARK_GRAY);
        centerPanel.add(origLabel);
        centerPanel.add(transLabel);

        getContentPane().add(centerPanel, BorderLayout.CENTER);
        getContentPane().add(rechtsPanel, BorderLayout.EAST);
        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
        setSize(dim.width, dim.height);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    }

    private Border createBorder(String title) {
        return new TitledBorder(new LineBorder(Color.WHITE), title,
                TitledBorder.ABOVE_BOTTOM, TitledBorder.LEADING, new Font(
                        "Verdana", Font.PLAIN, 12), Color.WHITE);
    }

    private JPanel initRB() {
        scaleRB = new JRadioButton("Skalierung", true);
        scaleRB.addChangeListener(this);
        rotateRB = new JRadioButton("Rotation");
        rotateRB.addChangeListener(this);
        transRB = new JRadioButton("Translation");
        transRB.addChangeListener(this);
        scherRB = new JRadioButton("Scherung");
        scherRB.addChangeListener(this);
        ButtonGroup buttonGroup = new ButtonGroup();
        buttonGroup.add(scaleRB);
        buttonGroup.add(rotateRB);
        buttonGroup.add(transRB);
        buttonGroup.add(scherRB);
        JPanel rBPanel = new JPanel(new GridLayout(4, 1, 0, 5));
        rBPanel.add(scaleRB);
        rBPanel.add(rotateRB);
        rBPanel.add(transRB);
        rBPanel.add(scherRB);

        JLabel xScaleLabel, yScaleLabel, faktorRotateLabel, xPosRotateLabel, yPosRotateLabel, xTransLabel, yTransLabel, xScherLabel, yScherLabel;
        xScaleLabel = new JLabel("X-Wert:");
        yScaleLabel = new JLabel("Y-Wert:");
        faktorRotateLabel = new JLabel("Winkel");
        xPosRotateLabel = new JLabel("X-Wert:");
        yPosRotateLabel = new JLabel("Y-Wert:");
        xTransLabel = new JLabel("X-Wert:");
        yTransLabel = new JLabel("Y-Wert:");
        xScherLabel = new JLabel("X-Wert:");
        yScherLabel = new JLabel("Y-Wert:");

        xScaleField = new JTextField(4);
        xScaleField.setPreferredSize(new Dimension(100, 20));
        yScaleField = new JTextField(4);
        yScaleField.setPreferredSize(new Dimension(100, 20));
        // Bereich 0 - 2*PI
        // wird im Listener durch 100 dividiert
        rotateSlider = new JSlider(0, 628, 0);
        rotateSlider.setMajorTickSpacing(150);
        rotateSlider.setPaintTicks(true);
        xPosRotateField = new JTextField(4);
        yPosRotateField = new JTextField(4);
        xTransField = new JTextField(4);
        yTransField = new JTextField(4);
        xScherField = new JTextField(4);
        yScherField = new JTextField(4);

        Dimension fillDim = new Dimension(10, Short.MAX_VALUE);

        scalePanel = new Box(BoxLayout.Y_AXIS);
        scalePanel.add(xScaleLabel);
        scalePanel.add(xScaleField);
        scalePanel.add(yScaleLabel);
        scalePanel.add(yScaleField);
        scalePanel.add(new Box.Filler(fillDim, fillDim, fillDim));

        rotatePanel = new Box(BoxLayout.Y_AXIS);
        rotatePanel.add(faktorRotateLabel);
        rotatePanel.add(rotateSlider);
        rotatePanel.add(xPosRotateLabel);
        rotatePanel.add(xPosRotateField);
        rotatePanel.add(yPosRotateLabel);
        rotatePanel.add(yPosRotateField);
        rotatePanel.add(new Box.Filler(fillDim, fillDim, fillDim));

        transPanel = new Box(BoxLayout.Y_AXIS);
        transPanel.add(xTransLabel);
        transPanel.add(xTransField);
        transPanel.add(yTransLabel);
        transPanel.add(yTransField);
        transPanel.add(new Box.Filler(fillDim, fillDim, fillDim));

        scherPanel = new Box(BoxLayout.Y_AXIS);
        scherPanel.add(xScherLabel);
        scherPanel.add(xScherField);
        scherPanel.add(yScherLabel);
        scherPanel.add(yScherField);
        scherPanel.add(new Box.Filler(fillDim, fillDim, fillDim));

        cl = new CardLayout(0, 20);
        cardPanel = new JPanel(cl);
        cardPanel.add(scalePanel, "Skalierung");
        cardPanel.add(rotatePanel, "Rotation");
        cardPanel.add(transPanel, "Translation (Verschieben)");
        cardPanel.add(scherPanel, "Scherung");

        button = new JButton("Transformieren");
        button.addActionListener(this);

        JPanel mainPanel = new JPanel(new BorderLayout(5, 10));
        mainPanel.add(rBPanel, BorderLayout.NORTH);
        mainPanel.add(cardPanel, BorderLayout.CENTER);
        mainPanel.add(button, BorderLayout.SOUTH);
        return mainPanel;
    }

    /**
     * skaliert ein BufferedImage
     */
    private BufferedImage[] scaleImage(String xStr, String yStr) {
        BufferedImage[] iArr = getImages();
        if (iArr == null)
            return null;

        // 1.0 ist Originalgroesse
        double x = str2double(xStr);
        double y = str2double(yStr);
        if(x == 0 || y == 0) return null;
        AffineTransform trans = AffineTransform.getScaleInstance(x, y);
        AffineTransformOp op = new AffineTransformOp(trans,
                AffineTransformOp.TYPE_BILINEAR);
        op.filter(iArr[0], iArr[1]);
        return iArr;
    }

    /**
     * dreht ein BufferedImage
     */
    private BufferedImage[] rotateImage(String fStr, String xStr, String yStr) {
        BufferedImage[] iArr = getImages();
        if (iArr == null)
            return null;
        double x = str2double(xStr);
        double y = str2double(yStr);
        if(xStr.equals("-1")) x = iArr[0].getWidth()/2;
        if(yStr.equals("-1")) y = iArr[0].getHeight()/2;

        // um die Mitte rotieren
        // f = 6.284 - volle Drehung
        double f = str2double(fStr);
        AffineTransform trans = AffineTransform.getRotateInstance(f, x, y);
        AffineTransformOp op = new AffineTransformOp(trans,
                AffineTransformOp.TYPE_BILINEAR);
        op.filter(iArr[0], iArr[1]);
        return iArr;
    }

    /**
     * transformiert den Ursprungspunkt eines BufferedImage
     */
    private BufferedImage[] transImage(String xStr, String yStr) {
        BufferedImage[] iArr = getImages();
        if (iArr == null)
            return null;

        // Translation
        double x = str2double(xStr);
        double y = str2double(yStr);
        AffineTransform trans = AffineTransform.getTranslateInstance(x, y);
        AffineTransformOp op = new AffineTransformOp(trans,
                AffineTransformOp.TYPE_BILINEAR);
        op.filter(iArr[0], iArr[1]);
        return iArr;
    }

    /**
     * schert ein BufferedImage
     */
    private BufferedImage[] scherImage(String xStr, String yStr) {
        BufferedImage[] iArr = getImages();
        if (iArr == null)
            return null;

        // scheren
        double x = str2double(xStr);
        double y = str2double(yStr);
        AffineTransform trans = AffineTransform.getShearInstance(x, y);
        AffineTransformOp op = new AffineTransformOp(trans,
                AffineTransformOp.TYPE_BILINEAR);
        op.filter(iArr[0], iArr[1]);
        return iArr;
    }

    /**
     * 
     * @param fileName
     *            String des Originalbildes
     * @return BufferedImage[] Array mit zwei BufferdImages: das erste ist das
     *         Original-Bild, das zweite ist das Zielbild für die
     *         Transformation
     */
    private BufferedImage[] getImages() {
        File file = new File(bildPfad);
        BufferedImage origImg = null;
        try {
            origImg = ImageIO.read(file);
        } catch (IOException ex) {
            System.err.println("Image-Datei kann nicht gelesen werden!");
            System.exit(1);
        }
        if (origImg != null) {
            BufferedImage destImg = new BufferedImage(origImg.getWidth(),
                    origImg.getHeight(), origImg.getType());
            BufferedImage[] i = { origImg, destImg };
            return i;
        }
        return null;
    }

    private void setImg(BufferedImage[] images) {
        if (images == null) {
            System.out.println("fehlgeschlaagen");
            return;
        }
        ImageIcon origIcon = new ImageIcon(images[0]);
        ImageIcon transIcon = new ImageIcon(images[1]);
        origLabel.setIcon(origIcon);
        transLabel.setIcon(transIcon);
    }

    private double str2double(String dStr) {
        if (dStr == null)
            return 1;
        dStr = dStr.replace(',', '.');
        try {
            return new Double(dStr).doubleValue();
        } catch (NumberFormatException nfe) {
            System.out.println("falsches Zahlformat");
        }
        return 0;
    }

    public void stateChanged(ChangeEvent e) {
        if (e.getSource() == scaleRB) {
            cl.show(cardPanel, "Skalierung");
        }
        if (e.getSource() == rotateRB) {
            cl.show(cardPanel, "Rotation");
        }
        if (e.getSource() == transRB) {
            cl.show(cardPanel, "Translation (Verschieben)");
        }
        if (e.getSource() == scherRB) {
            cl.show(cardPanel, "Scherung");
        }
    }

    public void actionPerformed(ActionEvent e) {
        if (scaleRB.isSelected()) {
            if(xScaleField.getText().equals("") || yScaleField.getText().equals("")) return;
            img = scaleImage(xScaleField.getText(), yScaleField.getText());
            setImg(img);
        }
        if (rotateRB.isSelected()) {
            String xFaktor = "-1", yFaktor = "-1";
            if(!xPosRotateField.getText().equals("") && !yPosRotateField.getText().equals("")){
                xFaktor = xPosRotateField.getText();
                yFaktor = yPosRotateField.getText();
            }
            double faktor = (double)rotateSlider.getValue()/100;
            img = rotateImage(new Double(faktor).toString(), xFaktor, yFaktor);
            setImg(img);
        }
        if (transRB.isSelected()) {
            if(xTransField.getText().equals("") || yTransField.getText().equals("")) return;
            img = transImage(xTransField.getText(), yTransField.getText());
            setImg(img);
        }
        if (scherRB.isSelected()) {
            if(xScherField.getText().equals("") || yScherField.getText().equals("")) return;
            img = scherImage(xScherField.getText(), yTransField.getText());
            setImg(img);
        }
    }

    public static void main(String args[]) {
        new ImageDemo();
    }
}

Affine Transformation