【问题标题】:How can I create a feathered brush with JavaFX?如何使用 JavaFX 创建羽毛笔刷?
【发布时间】:2021-11-28 10:57:40
【问题描述】:

所以我正在尝试使用 JavaFX 在 Java 中创建一个非常基本的照片编辑器程序。到目前为止,我的画笔和橡皮擦通过以下方式工作得很好:

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();
    }
}

现在我想为画笔设置一个羽化/硬度值(就像在 photoshop 中一样),我可以在其中绘制一条看起来更柔和的线条,但我不确定如何使用 JavaFX 来实现它?里面有什么工具可以做这样的事情吗?

所以用一个视觉例子:左边的画笔是羽毛画笔,右边的不是(这就是我目前所拥有的)

【问题讨论】:

  • 我不知道这是什么意思。你能更好地解释你想要做什么吗?您可以编辑问题。
  • 在帖子中添加了一个示例
  • 所以你希望能够画出模糊的线,并且能够设置半径?
  • 将填充设置为radial gradient,并为纯色和透明色设置适当的色标。
  • 你也可以使用gc.setEffectGaussianBlur你可以设置半径来改变画笔的“硬”程度。

标签: java javafx brush feather


【解决方案1】:

简单的绘图应用程序。

它使用径向渐变渲染到画布上绘制的图像,但您可以直接将渐变绘制到画布上。渐变是一种绘画,因此您可以将其直接设置为图形上下文中setFill 的参数。

我的示例中的解决方案可能不会完全为您提供您正在寻找的解决方案,但也许您可以根据需要对其进行调整。

这是我为演示目的而组装的一个快速应用程序,如果需要功能更强大的绘图应用程序,它的结构可以更好。

创建“羽毛笔刷”的代码是基于渐变的:

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 diameterLabel = new Label();
        diameterLabel.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,
                diameterLabel,
                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)
        );
    }
}

【讨论】:

    猜你喜欢
    • 2018-08-22
    • 2016-09-22
    • 2013-02-10
    • 1970-01-01
    • 1970-01-01
    • 2017-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多