简体   繁体   中英

How can I create a feathered brush with JavaFX?

So I'm trying to create a very basic photo editor program in Java, using JavaFX. I got a brush and eraser working pretty well so far the following way:

package application;

import java.io.File;

import javax.imageio.ImageIO;

import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;

public class EditorController {
    
    private boolean eraser = false;
    
    @FXML
    private Canvas canvas;
    @FXML
    private ColorPicker colorPicker;
    @FXML
    private TextField brushSize;
    @FXML
    private TextField selectedTool;

    private Point2D last = null;
    
    public void initialize() {
        GraphicsContext gc = canvas.getGraphicsContext2D();
        
        canvas.setOnMouseReleased(e -> {last = null;});
        canvas.setOnMouseClicked(e -> {
            if (!eraser) {
                double size = Double.parseDouble(brushSize.getText());
                float mouseX = (float) e.getX();
                float mouseY = (float) e.getY();
                gc.fillOval(mouseX-(size/2), mouseY-(size/2), size, size);
            }
            });
        canvas.setOnMouseDragged(e -> {
            System.out.println(eraser);
            double size = Double.parseDouble(brushSize.getText());
            gc.setLineCap(StrokeLineCap.ROUND);
            gc.setLineWidth(size);
            float mouseX = (float) e.getX();
            float mouseY = (float) e.getY();
            if (last != null && !eraser) {
                gc.strokeLine(last.getX(), last.getY(), mouseX, mouseY);
            } else if (eraser) {
                gc.clearRect(mouseX, mouseY, size, size);
            }
            last = new Point2D(mouseX, mouseY);
        });
    }
    
    public void onSave() {
        try {
            Image snapshot = canvas.snapshot(null, null);
            
            ImageIO.write(SwingFXUtils.fromFXImage(snapshot, null), "png", new File("paint.png"));
        } catch (Exception e) {
            System.out.println("Failed to save image: " + e);
        }
    }
    
    public void onLoad() { 
        // not implemented yet
    }
    
    // not implemented yet
    public void onUndo() { }
    public void onRedo() { }
    public void onSmaller() { }
    public void onBigger() { }
    public void onResetView() { }
    public void onFitView() { }
    public void onFillView() { }
    public void onNewLayer() { }
    public void onDeleteLayer() { }
    public void onDuplicateLayer() { }
    public void onGroupLayers() { }
    public void onMergeLayers() { }
    public void onAddMask() { }
    public void onBrush() { eraser = false; selectedTool.setText("Brush"); }
    public void onEraser() { eraser = true; selectedTool.setText("Eraser"); }
    
    public void onExit() {
        Platform.exit();
    }
}

Now I want to have a feather/hardness value for the brush (like in photoshop) where I can draw a softer-looking line, but I'm not sure how to achieve it with JavaFX? Are there any tools within it for things like this?

So with a visual example: the brush on the left would be a feathered brush, the one on the right isn't (and that's what I have currently)

示例图片在这里

Simple drawing app.

It uses a radial gradient rendered to an image that is draw to the canvas, but you could just draw the gradient directly onto the canvas. A gradient is a paint so you can set it directly as an argument to setFill on a graphics context.

The solution in my example probably won't exactly give you the solution you are looking for, but perhaps you could tweak it for what you need.

It was a quick app I put together for demo purposes, it could be structured better if a more functional drawing app was required.

The code which creates the "feathered brush" is based on a gradient:

private RadialGradient createSoftBrushGradient(Color primaryColor) {
    return new RadialGradient(
            0, 0,
            .5, .5,
            .5,
            true,
            CycleMethod.NO_CYCLE,
            new Stop(0, primaryColor),
            new Stop(1, Color.TRANSPARENT)
    );
}

图片

import javafx.application.Application;
import javafx.beans.property.*;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.*;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class SprayPaint extends Application {
    private final IntegerProperty brushDiameter = new SimpleIntegerProperty();
    private final ObjectProperty<Image> brushImage = new SimpleObjectProperty<>();
    private final ToggleGroup brushHardnessSelection = new ToggleGroup();
    private final RadioButton hardBrushSelection = new RadioButton();
    private final RadioButton softBrushSelection = new RadioButton();
    private final Circle hardBrush = new Circle();
    private final Circle softBrush = new Circle();
    private final SnapshotParameters snapshotParams = new SnapshotParameters();
    private final Canvas canvas = new Canvas(600, 450);

    @Override
    public void start(Stage stage) {
        snapshotParams.setFill(Color.TRANSPARENT);

        Pane controls = createControls();

        StackPane canvasHolder = new StackPane(canvas);
        canvasHolder.setStyle("-fx-border-color: gray;");
        canvasHolder.setMaxSize(Pane.USE_PREF_SIZE, Pane.USE_PREF_SIZE);

        VBox layout = new VBox(
                10,
                controls,
                canvasHolder
        );
        layout.setPadding(new Insets(10));

        stage.setScene(new Scene(layout));
        stage.show();

        enableDrawing(canvas);
    }

    private void enableDrawing(Canvas canvas) {
        EventHandler<MouseEvent> drawHandler = event -> {
            Image brush = snapshotBrushImage();

            GraphicsContext gc = canvas.getGraphicsContext2D();
            gc.drawImage(
                    brush,
                    event.getX() - brushDiameter.doubleValue() / 2,
                    event.getY() - brushDiameter.doubleValue() / 2
            );
        };

        canvas.setOnMousePressed(drawHandler);
        canvas.setOnMouseDragged(drawHandler);
    }

    private Image snapshotBrushImage() {
        if (brushImage.get() == null) {
            if (hardBrushSelection == brushHardnessSelection.getSelectedToggle()) {
                brushImage.set(snapshot(hardBrush));
            } else { // soft brush selected
                brushImage.set(snapshot(softBrush));
            }
        }

        return brushImage.get();
    }

    private Image snapshot(Circle brushNode) {
        return brushNode.snapshot(snapshotParams, null);
    }

    private Pane createControls() {
        hardBrush.radiusProperty().bind(
                brushDiameter.divide(2.0)
        );
        softBrush.radiusProperty().bind(
                brushDiameter.divide(2.0)
        );

        hardBrushSelection.getStyleClass().addAll("toggle-button", "left-pill");
        hardBrushSelection.getStyleClass().remove( "radio-button");
        StackPane hardBrushGraphic = new StackPane(hardBrush);
        hardBrushGraphic.setMinSize(40, 40);
        hardBrushSelection.setGraphic(hardBrushGraphic);
        hardBrushSelection.setToggleGroup(brushHardnessSelection);

        softBrushSelection.getStyleClass().addAll( "toggle-button", "right-pill");
        softBrushSelection.getStyleClass().remove( "radio-button");
        StackPane softBrushGraphic = new StackPane(softBrush);
        softBrushGraphic.setMinSize(40, 40);
        softBrushSelection.setGraphic(softBrushGraphic);
        softBrushSelection.setToggleGroup(brushHardnessSelection);

        hardBrushSelection.setSelected(true);

        HBox brushSelectionPanel = new HBox(hardBrushSelection, softBrushSelection);

        Slider brushDiameterSlider = new Slider(8, 40, 20);
        brushDiameterSlider.setMajorTickUnit(4);
        brushDiameterSlider.setMinorTickCount(0);
        brushDiameterSlider.setShowTickMarks(true);

        brushDiameter.setValue((int) Math.round(brushDiameterSlider.getValue()));
        brushDiameterSlider.valueProperty().addListener((observable, oldValue, newValue) ->
                brushDiameter.setValue((int) Math.round(newValue.doubleValue()))
        );

        Label fontSizeLabel = new Label();
        fontSizeLabel.textProperty().bind(
                brushDiameter.asString()
        );

        ColorPicker colorPicker = new ColorPicker();
        hardBrush.fillProperty().bind(
                colorPicker.valueProperty()
        );
        colorPicker.valueProperty().addListener((observable, oldColor, newColor) ->
                softBrush.setFill(
                        createSoftBrushGradient(newColor)
                )
        );
        colorPicker.setValue(Color.NAVY);

        brushDiameter.addListener((observable, oldValue, newValue) ->
                brushImage.set(null)
        );
        colorPicker.valueProperty().addListener((observable, oldValue, newValue) ->
                brushImage.set(null)
        );
        brushHardnessSelection.selectedToggleProperty().addListener((observable, oldValue, newValue) ->
                brushImage.set(null)
        );

        Button clear = new Button("Clear");
        clear.setOnAction(e ->
                canvas.getGraphicsContext2D().clearRect(
                        0, 0, canvas.getWidth(), canvas.getHeight()
                )
        );

        HBox controlPanel = new HBox(
                10,
                colorPicker,
                brushSelectionPanel,
                new Label("Diameter: "),
                brushDiameterSlider,
                fontSizeLabel,
                clear
        );

        controlPanel.setMinWidth(450);
        controlPanel.setMinHeight(Pane.USE_PREF_SIZE);

        return controlPanel;
    }

    private RadialGradient createSoftBrushGradient(Color primaryColor) {
        return new RadialGradient(
                0, 0,
                .5, .5,
                .5,
                true,
                CycleMethod.NO_CYCLE,
                new Stop(0, primaryColor),
                new Stop(1, Color.TRANSPARENT)
        );
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM