Wie kann man Rotationen, Skalierungen, etc 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.
Durch einfaches Ein- und Auskommentieren kleiner, nummerierter
Scriptbereiche können einfache geometrische Grundformen
(ein Rechteck und eine Linie) modifiziert werden.
Script 2 stellt eine kleine Applikation dar,
die einige Möglichkeiten der Klasse AffineTransform
anhand eines Bildes aufzeigt und die Ergebnisse in einem GUI (Graphical User Interface =
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, von JPanel
abgeleiteten Klasse, AffineTransformationPanel
,
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 des
Panel-Objektes übergibt. Sie dient als
Stellgröße für die Modifikation des gezeichneten
Objektes. Dies wird bei Aktivierung des Sliders entsprechend
modifiziert. Anschließend wird das JPanel
neu
gezeichnet.
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 implements ChangeListener {
AffineTransformationPanel panel;
public AffineTransformation() {
init();
}
private void init() {
JFrame frame = new JFrame("Affine Transformatiion");
panel = new AffineTransformationPanel();
frame.setLayout(new BorderLayout());
frame.add(panel, BorderLayout.CENTER);
JSlider slider = new JSlider(0, 10, 0);
slider.addChangeListener(this);
frame.add(slider, BorderLayout.SOUTH);
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> 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 num
ist die einzige, die 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
Grundfläche zu erhalten. In der Folge werden vier Variablen
deklariert, die den linken oberen Initialisierungspunkt und die
Breite und Höhe eines Rechtecks angeben. Sie werden mit den
entsprechenden Werten initialisiert und mit diesen Werten 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 Auswirkungen zu erkunden. Allen gemeinsam ist die Tatsache,
dass 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:
- Bedingt durch die zugrunde liegende Matrix-Berechnung findet ohne explizite Angabe eines Koordinatenzentrums bei der Scheerung und Skalierung immer auch eine Translation (=Verschiebung) statt.
- Die Reihenfolge der Transformation ist bei mehreren hintereinander geschalteten Operationen nicht gleichgültig. Durch entsprechendes Auskommentieren und Verschieben der Abschnitte kann das Verhalten leicht überprüft werden.
- Die angesprochenen Methoden
setToXXX()
zeichnen noch kein transformiertes Objekt, sondern führen nur die Transformation selbst aus.
Die Zeichnung selbst wird nach Anpassung der Vordergrundfarbe
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.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.SwingUtilities;
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;
public class ImageDemo implements ChangeListener, ActionListener {
private final String bildPfad = "img/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;
public ImageDemo() {
initGUI();
}
private void initGUI() {
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);
JFrame frame = new JFrame("Affine Transformation");
frame.add(centerPanel, BorderLayout.CENTER);
frame.add(rechtsPanel, BorderLayout.EAST);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.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;
}
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;
}
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;
}
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;
}
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;
}
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[]) {
SwingUtilities.invokeLater(() -> new ImageDemo());
}
}
Wenn Ihnen javabeginners.de gefällt, freue ich mich über eine Spende an diese gemeinnützigen Organisationen.