Wie kann man die Darstellung eines Bildes mit JavaFX anpassen?

Die Klasse ImageView erlaubt das Skalieren, Drehen und Beschneiden der Darstellung einer Bilddatei.

Das Beispiel zeigt eine einfache JavaFX-Anwendung, in der ein Bild angezeigt wird, dessen Darstellung nach Klick auf entsprechende Buttons skaliert, gedreht und beschnitten werden kann.

In start() wird als erstes ein Bild geladen und durch Übergabe an den Konstruktor auf eine ImageView gesetzt. Dieser wird dann ein Schatteneffekt in Form eines DropShadow hinzugefügt. Der Konstruktor bekommt zwei Parameter übergeben: die Breite des Schattenverlaufs und die Farbe.

Es folgt die Deklaration vierer Button-Objekte, die jeweils bei EventHandlern angemeldet werden. In deren Methoden handle() findet der Aufruf jeweils einer Methode für das Rotieren, Skalieren, Beschneiden der Bilddarstellung und das Zurücksetzen der Manipulationen statt.
Mit einem Abstand von fünf Pixeln werden die Buttons in eine HBox gesetzt und in dieser durch setAlignment(Pos.CENTER) zentriert.

Das Layout wird durch ein GridPane gemanaged. Durch die Übergabe entsprechender Parameter an add() werden die ImageView auf die oberste Zelle und die HBox darunter gesetzt.
Die Formatierung der Rasterzellen findet auf zweierlei Weisen statt. Zunächst wird die ImageView durch die statischen Methoden GridPane.setHalignment() und GridPane.setValignment() horizontal und vertikal zentriert. Dann werden die Zeilen durch die Übergabe von Constraints hinsichtlich ihrer Höhe festgelegt. Hierbei wird der ersten Zeile, derjenigen mit der ImageView, eine flexible Höhe zugewiesen um sicherzustellen, dass die Fläche immer den Großteil der Scene einnimmt und die Buttons sich am unteren Fensterrand befinden.
Vorsicht übrigens bei gleichzeitiger Verwendung von absoluten und relativen Angaben innerhalb eines Constraintsystems. Dies ist nicht unproblematisch und kann zu unvorhersehbaren Ergebnissen führen.

Auf ähnliche Weise wird die Breite der ImageView-Darstellung gewährleistet. Da im Fenster eine GridPane mit einer einzigen Spalte dargestellt ist, kann deren Breite durch eine Zuweisung von 100% Spaltenbreite auf diejenige der Fensterbreite festgelegt werden.

Die Erzeugung einer Scene, deren Setzen auf die Stage, das Anpassen der Fenstergröße an die Scene und das Anzeigen der Application schließen den Quelltext ab.

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.geometry.VPos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.RowConstraints;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class BildManipulieren extends Application {
    @Override
    public void start(Stage primaryStage) {
        
        Image image = new Image("img/wien.jpg");
        ImageView imgView = new ImageView(image);
        imgView.setEffect(new DropShadow(20, Color.BLACK));
        
        Button rotButt = new Button("drehen");
        rotButt.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                imgView.setRotate(imgView.getRotate() + 90);
            }
        });

        Button scaleButt = new Button("skalieren");
        scaleButt.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                scale(imgView, 200);
            }
        });
        
        Button clipButt = new Button("beschneiden");
        clipButt.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                clip(imgView);
            }
        });
        
        Button resetButt = new Button("reset");
        resetButt.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                reset(imgView);
            }
        });
        HBox buttBox = new HBox(5);
        buttBox.getChildren().addAll(rotButt, scaleButt, clipButt, resetButt);
        buttBox.setPadding(new Insets(.5));
        buttBox.setAlignment(Pos.CENTER);
        
        GridPane grid = new GridPane();
        grid.add(imgView, 0, 0);
        grid.add(buttBox, 0, 1);
        grid.setGridLinesVisible(false);

        GridPane.setHalignment(imgView, HPos.CENTER);
        GridPane.setValignment(imgView, VPos.CENTER);

        RowConstraints row1 = new RowConstraints();
        row1.setVgrow(Priority.ALWAYS);
        grid.getRowConstraints().add(row1);

        ColumnConstraints col1 = new ColumnConstraints();
        col1.setPercentWidth(100);
        grid.getColumnConstraints().add(col1);

        Scene scene = new Scene(grid, Color.RED);
        primaryStage.setTitle("ImageView Beispiel");
        primaryStage.setScene(scene);
        primaryStage.sizeToScene();
        primaryStage.show();
    }

    private void scale(ImageView view, double trans) {
        Image img = view.getImage();
        if (img == null)
            return;
        double imgW = view.getFitWidth() > 0 ? view.getFitWidth() : img.getWidth();
        double imgH = view.getFitHeight() > 0 ? view.getFitHeight() : img.getHeight();
        double deltaX = img.getWidth() / trans;
        double deltaY = img.getHeight() / trans;
        double delta = deltaX >= deltaY ? deltaX : deltaY;
        double newW = imgW / delta;
        double newH = imgH / delta;
        double oldW = view.getFitWidth();
        double oldH = view.getFitHeight();
        view.setFitWidth(newW);
        view.setFitHeight(newH);
        view.setPreserveRatio(true);
        view.setX((oldW - newW) / 2);
        view.setY((oldH - newH) / -2);
    }
    
    private void clip(ImageView view) {
        Rectangle2D rect = new Rectangle2D(50, 50, 200, 200);
        view.setViewport(rect);
    }
    
    private void reset(ImageView view) {
        Image img = view.getImage();
        view.setRotate(0);
        view.setFitWidth(img.getWidth());
        view.setFitHeight(img.getHeight());
        view.setPreserveRatio(true);
        view.setViewport(null);
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}

Werfen wir einen Blick auf die drei Methoden zur Manipulation der Anzeige in der Reihenfolge ihrer Deklaration.

Rotation

Zur Rotation der ImageView genügt es, die Methode rotate() der ImageView aufzurufen. Ihr wird der aktuelle Rotationsgrad plus 90 Grad als Parameter übergeben, sodass bei jedem Klick eine Viertelumdrehung in Uhrzeigerrichtung erfolgt. Die Bilddarstellung bleibt dabei unskaliert, sodass ggf. nicht die vollständige Abbildung erfolgt.

Skalierung

Die Methode scale() dient zum Skalieren der ImageView. Sie bekommt die ImageView selbst und einen Skalierungsfaktor als Parameter übergeben. Von dieser wird das geladene Image-Objekt ermittelt. Ist keines vorhanden, so terminiert die Methode ergebnislos. Es gilt zu beachten, dass das Image selbst nicht skaliert wird, sondern mit seinen Kantenlängen lediglich die Berechnungsgrundlage darstellt.
Ist ein Bild geladen, so werden Breite und Höhe der Darstellung ermittelt und zwar so, dass bei bereits erfolgter Skalierung die Werte der skalierten Darstellung, ansonsten die Werte des Ausgangsbildes verwendet werden.
Der Quotient der Kantenlängen des Ausgangsbildes und des Skalierungsfaktors wird errechnet und der größere von beiden in der Variablen delta abgelegt. Bildbreite und -höhe werden durch delta dividiert, um die Zielgröße des Bildes zu erreichen. Durch die Methode setPreserveRatio() wird erreicht, dass die korrekte Proportionierung des Bildes erhalten bleibt.
In den letzten beiden Zeilen der Methode wird die Mittelposition der ImageView auf der Scene errechnet und diese entsprechend gesetzt.

Beschneidung

Die Methode clip() ist recht einfach gestaltet. Hier wird ein Rechteck in Form eines Rectangle-Objektes in der gewünschten Ausschnittgröße erstellt und durch setViewport() auf die ImageView gesetzt.

Zurücksetzen

Zum Zurücksetzen der Ansicht auf den Ausgangszustand wird der Rotationswinkel auf 0 gesetzt, die ImageView wieder auf die Kantenlängen des Ausgangsbildes gebracht und die Viewporteinschränkung entfernt.

Bilddarstellung anpassen - Ausgangszustand

Bilddarstellung anpassen - Nach Skalierung, Rotation und Beschnitt