【问题标题】:How to make a Javafx Image Crop App如何制作 Javafx 图像裁剪应用程序
【发布时间】:2024-05-03 19:25:05
【问题描述】:

我正在为我的同事构建一个应用程序来裁剪图像。 我使用 FXML 和 Scene Builder 来构建 GUI。用户单击按钮以从他的计算机中选择图像。然后,图像显示在 GUI 中。用户可以在窗格中缩放和移动。最后,单击一个按钮将编辑后的图像保存到他的计算机。

但是,我真的不知道我应该使用什么库来构建应用程序。
这是我第一次处理图形。我不知道如何读取图像、裁剪图像和写入图像。 Javafx Canvas 用于窗格?

除了 Java 文档之外还有什么好的资源可以阅读以了解我该怎么做?

【问题讨论】:

    标签: java image canvas graphics javafx


    【解决方案1】:

    您的问题在 * 上无法回答。我建议你先阅读official Oracle documentation about JavaFX

    不过,既然是个有趣的话题,那就用代码来回答吧。

    您需要考虑几件事情:

    • 使用 ImageView 作为容器
    • 使用 ScrollPane 以防图像变大
    • 提供选择机制
    • 裁剪图像本身
    • 将图像保存到文件中,提供文件选择器对话框

    差不多就是这样。在下面的示例中,使用鼠标左键进行选择,使用鼠标右键进行裁剪上下文菜单,然后在选择边界处拍摄 ImageView 节点的快照,然后将图像保存到文件中。

    import java.awt.Graphics2D;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    import javafx.application.Application;
    import javafx.embed.swing.SwingFXUtils;
    import javafx.event.ActionEvent;
    import javafx.event.EventHandler;
    import javafx.geometry.Bounds;
    import javafx.geometry.Rectangle2D;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.SnapshotParameters;
    import javafx.scene.control.ContextMenu;
    import javafx.scene.control.MenuItem;
    import javafx.scene.control.ScrollPane;
    import javafx.scene.image.Image;
    import javafx.scene.image.ImageView;
    import javafx.scene.image.WritableImage;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.shape.StrokeLineCap;
    import javafx.stage.FileChooser;
    import javafx.stage.Stage;
    
    import javax.imageio.ImageIO;
    
    
    /**
     * Load image, provide rectangle for rubberband selection. Press right mouse button for "crop" context menu which then crops the image at the selection rectangle and saves it as jpg.
     */
    public class ImageCropWithRubberBand extends Application {
    
        RubberBandSelection rubberBandSelection;
        ImageView imageView;
    
        Stage primaryStage;
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
    
            this.primaryStage = primaryStage;
    
            primaryStage.setTitle("Image Crop");
    
            BorderPane root = new BorderPane();
    
            // container for image layers
            ScrollPane scrollPane = new ScrollPane();
    
            // image layer: a group of images
            Group imageLayer = new Group(); 
    
            // load the image
    //      Image image = new Image( getClass().getResource( "cat.jpg").toExternalForm());
            Image image = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Gatto_europeo4.jpg/1024px-Gatto_europeo4.jpg");
    
            // the container for the image as a javafx node
            imageView = new ImageView( image);
    
            // add image to layer
            imageLayer.getChildren().add( imageView);
    
            // use scrollpane for image view in case the image is large
            scrollPane.setContent(imageLayer);
    
            // put scrollpane in scene
            root.setCenter(scrollPane);
    
            // rubberband selection
            rubberBandSelection = new RubberBandSelection(imageLayer);
    
            // create context menu and menu items
            ContextMenu contextMenu = new ContextMenu();
    
            MenuItem cropMenuItem = new MenuItem("Crop");
            cropMenuItem.setOnAction(new EventHandler<ActionEvent>() {
                public void handle(ActionEvent e) {
    
                    // get bounds for image crop
                    Bounds selectionBounds = rubberBandSelection.getBounds();
    
                    // show bounds info
                    System.out.println( "Selected area: " + selectionBounds);
    
                    // crop the image
                    crop( selectionBounds);
    
                }
            });
            contextMenu.getItems().add( cropMenuItem);
    
            // set context menu on image layer
            imageLayer.setOnMousePressed(new EventHandler<MouseEvent>() {
                @Override
                public void handle(MouseEvent event) {
                    if (event.isSecondaryButtonDown()) {
                        contextMenu.show(imageLayer, event.getScreenX(), event.getScreenY());
                    }
                }
            });
    
            primaryStage.setScene(new Scene(root, 1024, 768));
            primaryStage.show();
        }
    
        private void crop( Bounds bounds) {
    
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("Save Image");
    
            File file = fileChooser.showSaveDialog( primaryStage);
            if (file == null)
                return;
    
            int width = (int) bounds.getWidth();
            int height = (int) bounds.getHeight();
    
            SnapshotParameters parameters = new SnapshotParameters();
            parameters.setFill(Color.TRANSPARENT);
            parameters.setViewport(new Rectangle2D( bounds.getMinX(), bounds.getMinY(), width, height));
    
            WritableImage wi = new WritableImage( width, height);
            imageView.snapshot(parameters, wi);
    
            // save image 
            // !!! has bug because of transparency (use approach below) !!!
            // --------------------------------
    //        try {
    //          ImageIO.write(SwingFXUtils.fromFXImage( wi, null), "jpg", file);
    //      } catch (IOException e) {
    //          e.printStackTrace();
    //      }
    
    
            // save image (without alpha)
            // --------------------------------
            BufferedImage bufImageARGB = SwingFXUtils.fromFXImage(wi, null);
            BufferedImage bufImageRGB = new BufferedImage(bufImageARGB.getWidth(), bufImageARGB.getHeight(), BufferedImage.OPAQUE);
    
            Graphics2D graphics = bufImageRGB.createGraphics();
            graphics.drawImage(bufImageARGB, 0, 0, null);
    
            try {
    
                ImageIO.write(bufImageRGB, "jpg", file); 
    
                System.out.println( "Image saved to " + file.getAbsolutePath());
    
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            graphics.dispose();
    
        }
    
        /**
         * Drag rectangle with mouse cursor in order to get selection bounds
         */
        public static class RubberBandSelection {
    
            final DragContext dragContext = new DragContext();
            Rectangle rect = new Rectangle();
    
            Group group;
    
    
            public Bounds getBounds() {
                return rect.getBoundsInParent();
            }
    
            public RubberBandSelection( Group group) {
    
                this.group = group;
    
                rect = new Rectangle( 0,0,0,0);
                rect.setStroke(Color.BLUE);
                rect.setStrokeWidth(1);
                rect.setStrokeLineCap(StrokeLineCap.ROUND);
                rect.setFill(Color.LIGHTBLUE.deriveColor(0, 1.2, 1, 0.6));
    
                group.addEventHandler(MouseEvent.MOUSE_PRESSED, onMousePressedEventHandler);
                group.addEventHandler(MouseEvent.MOUSE_DRAGGED, onMouseDraggedEventHandler);
                group.addEventHandler(MouseEvent.MOUSE_RELEASED, onMouseReleasedEventHandler);
    
            }
    
            EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
    
                @Override
                public void handle(MouseEvent event) {
    
                    if( event.isSecondaryButtonDown())
                        return;
    
                    // remove old rect
                    rect.setX(0);
                    rect.setY(0);
                    rect.setWidth(0);
                    rect.setHeight(0);
    
                    group.getChildren().remove( rect);
    
    
                    // prepare new drag operation
                    dragContext.mouseAnchorX = event.getX();
                    dragContext.mouseAnchorY = event.getY();
    
                    rect.setX(dragContext.mouseAnchorX);
                    rect.setY(dragContext.mouseAnchorY);
                    rect.setWidth(0);
                    rect.setHeight(0);
    
                    group.getChildren().add( rect);
    
                }
            };
    
            EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
    
                @Override
                public void handle(MouseEvent event) {
    
                    if( event.isSecondaryButtonDown())
                        return;
    
                    double offsetX = event.getX() - dragContext.mouseAnchorX;
                    double offsetY = event.getY() - dragContext.mouseAnchorY;
    
                    if( offsetX > 0)
                        rect.setWidth( offsetX);
                    else {
                        rect.setX(event.getX());
                        rect.setWidth(dragContext.mouseAnchorX - rect.getX());
                    }
    
                    if( offsetY > 0) {
                        rect.setHeight( offsetY);
                    } else {
                        rect.setY(event.getY());
                        rect.setHeight(dragContext.mouseAnchorY - rect.getY());
                    }
                }
            };
    
    
            EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
    
                @Override
                public void handle(MouseEvent event) {
    
                    if( event.isSecondaryButtonDown())
                        return;
    
                    // remove rectangle
                    // note: we want to keep the ruuberband selection for the cropping => code is just commented out
                    /*
                    rect.setX(0);
                    rect.setY(0);
                    rect.setWidth(0);
                    rect.setHeight(0);
    
                    group.getChildren().remove( rect);
                    */
    
                }
            };
            private static final class DragContext {
    
                public double mouseAnchorX;
                public double mouseAnchorY;
    
    
            }
        }
    }
    

    截图:

    裁剪后的图像:

    【讨论】:

    • 对新手的好解释
    最近更新 更多