JavaFX中的画笔描边 [英] Paintbrush stroke in JavaFX

查看:175
本文介绍了JavaFX中的画笔描边的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在JavaFX中编写一个绘画应用程序。我想要一个类似真正的画笔的画笔,但我不知道如何启动算法。下面的代码显示了我当前的画笔描边,尽管它是一个有用的描边,但它不是一个真正的画笔:

  import javafx.application。应用; 
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import javafx.stage.Stage;

import static javafx.scene.input.MouseEvent。*;


public class BrushTester extends Application {

private static final Color color = Color.CHOCOLATE;
private static final double START_OPACITY = 0.3;
private static final double OPACITY_MODIFIER = 0.002;

private double currentOpacity = START_OPACITY;
private double strokeWidth = 15;

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

@Override
public void start(Stage primaryStage)throws Exception {
Canvas canvas = new Canvas(600d,600d);
GraphicsContext gc = canvas.getGraphicsContext2D();

canvas.addEventHandler(MOUSE_DRAGGED,e - > BrushTester.this.handleMouseDragged(gc,e));
canvas.addEventHandler(MOUSE_PRESSED,e - > handleMousePressed(gc,e));
canvas.addEventHandler(MOUSE_RELEASED,e - > handleMouseReleased(gc,e));

Group root = new Group();
root.getChildren()。add(canvas);
primaryStage.setScene(new Scene(root,Color.DARKGRAY));
primaryStage.show();

$ b $ private void configureGraphicsContext(GraphicsContext gc){
gc.setStroke(new Color(color.getRed(),color.getGreen(),color.getBlue(), currentOpacity));
gc.setLineCap(StrokeLineCap.ROUND);
gc.setLineJoin(StrokeLineJoin.ROUND);
gc.setLineWidth(strokeWidth);


public void handleMousePressed(GraphicsContext gc,MouseEvent e){
configureGraphicsContext(gc);
gc.beginPath();
gc.moveTo(e.getX(),e.​​getY());
gc.stroke();
}

public void handleMouseReleased(GraphicsContext gc,MouseEvent e){
currentOpacity = START_OPACITY;
gc.closePath();
}

public void handleMouseDragged(GraphicsContext gc,MouseEvent e){
currentOpacity = Math.max(0,currentOpacity - OPACITY_MODIFIER);
configureGraphicsContext(gc);
gc.lineTo(e.getX(),e.​​getY());
gc.stroke();
}
}

任何人如何更接近真正的东西?

解决方案

这一切都取决于你想要达到的目标。我个人会使用


  • 一个AnimationTimer

  • 可自定义的画笔(即图像)中风,因此您可以指定大小和硬度

  • 线条绘制算法(如Bresenham)将前一个鼠标位置与当前位置连接,以获得点之间的完整线条



  • $ b

    一个简单的绘图算法的简单例子:

      import java.util.Random; 

    import javafx.animation.AnimationTimer;
    import javafx.application.Application;
    导入javafx.geometry.Point2D;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.SnapshotParameters;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.image.Image;
    import javafx.scene.image.WritableImage;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.CycleMethod;
    import javafx.scene.paint.RadialGradient;
    import javafx.scene.paint.Stop;
    import javafx.scene.shape.Circle;
    import javafx.stage.Stage;

    public class Main扩展应用程序{

    private static double SCENE_WIDTH = 1280;
    private static double SCENE_HEIGHT = 720;

    static Random = new Random();

    画布画布;
    GraphicsContext graphicsContext;

    AnimationTimer循环;

    Point2D mouseLocation = new Point2D(0,0);
    boolean mousePressed = false;
    Point2D prevMouseLocation = new Point2D(0,0);

    场景场景;

    Image brush = createBrush(30.0,Color.CHOCOLATE);
    double brushWidthHalf = brush.getWidth()/ 2.0;
    double brushHeightHalf = brush.getHeight()/ 2.0;



    @Override
    public void start(Stage primaryStage){

    BorderPane root = new BorderPane();

    canvas = new Canvas(SCENE_WIDTH,SCENE_HEIGHT);

    graphicsContext = canvas.getGraphicsContext2D();

    Pane layerPane = new Pane();

    layerPane.getChildren()。addAll(canvas);

    root.setCenter(layerPane);

    scene = new Scene(root,SCENE_WIDTH,SCENE_HEIGHT);

    primaryStage.setScene(scene);
    primaryStage.show();

    addListeners();

    startAnimation();



    $ b private void startAnimation(){

    loop = new AnimationTimer(){

    @Override
    public void handle(long now){

    if(mousePressed){

    //试试这个
    // graphicsContext.drawImage(brush ,mouseLocation.getX() - brushWidthHalf,mouseLocation.getY() - brushHeightHalf);

    //然后这个
    bresenhamLine(prevMouseLocation.getX(),prevMouseLocation.getY(),mouseLocation.getX(),mouseLocation.getY());



    prevMouseLocation = new Point2D(mouseLocation.getX(),mouseLocation.getY());

    }
    };

    loop.start();

    }

    // https://de.wikipedia.org/wiki/Bresenham-Algorithmus
    private void bresenhamLine(double x0,double y0,double x1 ,double y1)
    {
    double dx = Math.abs(x1-x0),sx = x0 double dy = -Math.abs(y1-y0),sy = y0 double err = dx + dy,e2; / *错误值e_xy * /

    while(true){
    graphicsContext.drawImage(brush,x0 - brushWidthHalf,y0 - brushHeightHalf);
    if(x0 == x1&& y0 == y1)break;
    e2 = 2. * err;如果(e2> dy){err + = dy; x0 + = sx; } / * e_xy + e_x> 0 * /
    if(e2 }
    }


    private void addListeners(){

    scene.addEventFilter(MouseEvent.ANY,e - > ; {

    mouseLocation = new Point2D(e.getX(),e.​​getY());

    mousePressed = e.isPrimaryButtonDown();

    });





    public static Image createImage(Node node){

    WritableImage wi;

    SnapshotParameters parameters = new SnapshotParameters();
    parameters.setFill(Color.TRANSPARENT);

    int imageWidth =(int)node.getBoundsInLocal()。getWidth();
    int imageHeight =(int)node.getBoundsInLocal()。getHeight();

    wi = new WritableImage(imageWidth,imageHeight);
    node.snapshot(parameters,wi);

    返回wi;

    }


    public static Image createBrush(double radius,Color color){

    //用给定颜色创建渐变图像
    Circle brush =新的Circle(半径);

    RadialGradient gradient1 = new RadialGradient(0,0,0,0,radius,false,CycleMethod.NO_CYCLE,new Stop(0,color.deriveColor(1,1,1,0.3)),new Stop(1,color.deriveColor(1,1,1,0)));

    brush.setFill(gradient1);

    //创建图像
    return createImage(brush);



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

    当然你可以用e扩展它。 G。


    • 多层

    • 图层和图像上下文级别的JavaFX混合模式
    • 来模拟强制我会使用一个绘制延迟(例如200毫秒)和一个缓冲区的鼠标位置,并让不透明度取决于鼠标是否仍然按下

    • 使用贝塞尔曲线平滑线条

    • ...






    刷子变化时的示例当您开始绘画时:

      import java.util.Random; 

    import javafx.animation.AnimationTimer;
    import javafx.application.Application;
    导入javafx.geometry.Point2D;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.SnapshotParameters;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.image.Image;
    import javafx.scene.image.WritableImage;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.CycleMethod;
    import javafx.scene.paint.RadialGradient;
    import javafx.scene.paint.Stop;
    import javafx.scene.shape.Circle;
    import javafx.stage.Stage;

    public class Main扩展应用程序{

    private static double SCENE_WIDTH = 1280;
    private static double SCENE_HEIGHT = 720;

    static Random = new Random();

    画布画布;
    GraphicsContext graphicsContext;

    AnimationTimer循环;

    Point2D mouseLocation = new Point2D(0,0);
    boolean mousePressed = false;
    Point2D prevMouseLocation = new Point2D(0,0);

    场景场景;

    double brushMaxSize = 30;
    Image brush = createBrush(brushMaxSize,Color.CHOCOLATE);
    double brushWidthHalf = brush.getWidth()/ 2.0;
    double brushHeightHalf = brush.getHeight()/ 2.0;

    双重压力= 0;
    double pressureDelay = 0.04;

    private Image [] brushVariations = new Image [256];

    @Override
    public void start(Stage primaryStage){

    BorderPane root = new BorderPane();

    canvas = new Canvas(SCENE_WIDTH,SCENE_HEIGHT);

    for(int i = 0; i< brushVariations.length; i ++){

    double size =(brushMaxSize - 1)/(double)brushVariations.length *(双)i + 1;

    brushVariations [i] = createBrush(size,Color.CHOCOLATE);
    }

    graphicsContext = canvas.getGraphicsContext2D();

    Pane layerPane = new Pane();

    layerPane.getChildren()。addAll(canvas);

    root.setCenter(layerPane);

    scene = new Scene(root,SCENE_WIDTH,SCENE_HEIGHT);

    primaryStage.setScene(scene);
    primaryStage.show();

    addListeners();

    startAnimation();



    $ b private void startAnimation(){

    loop = new AnimationTimer(){

    @Override
    public void handle(long now){

    if(mousePressed){

    //试试这个
    // graphicsContext.drawImage(brush ,mouseLocation.getX() - brushWidthHalf,mouseLocation.getY() - brushHeightHalf);

    //然后这个
    bresenhamLine(prevMouseLocation.getX(),prevMouseLocation.getY(),mouseLocation.getX(),mouseLocation.getY());

    压力+ =压力延迟;
    if(pressure> 1){
    pressure = 1;
    }

    }其他{

    压力= 0;



    prevMouseLocation = new Point2D(mouseLocation.getX(),mouseLocation.getY());

    }
    };

    loop.start();

    }

    // https://de.wikipedia.org/wiki/Bresenham-Algorithmus
    private void bresenhamLine(double x0,double y0,double x1 ,double y1)
    {
    double dx = Math.abs(x1-x0),sx = x0 double dy = -Math.abs(y1-y0),sy = y0 double err = dx + dy,e2; / *错误值e_xy * /

    while(true){

    int variation =(int)(pressure *(brushVariations.length - 1));
    图像brushVariation = brushVariations [变体];

    graphicsContext.setGlobalAlpha(pressure);
    graphicsContext.drawImage(brushVariation,x0 - brushWidthHalf,y0 - brushHeightHalf);

    if(x0 == x1&& y0 == y1)break;
    e2 = 2. * err;如果(e2> dy){err + = dy; x0 + = sx; } / * e_xy + e_x> 0 * /
    if(e2 }
    }


    private void addListeners(){

    scene.addEventFilter(MouseEvent.ANY,e - > ; {

    mouseLocation = new Point2D(e.getX(),e.​​getY());

    mousePressed = e.isPrimaryButtonDown();

    });





    public static Image createImage(Node node){

    WritableImage wi;

    SnapshotParameters parameters = new SnapshotParameters();
    parameters.setFill(Color.TRANSPARENT);

    int imageWidth =(int)node.getBoundsInLocal()。getWidth();
    int imageHeight =(int)node.getBoundsInLocal()。getHeight();

    wi = new WritableImage(imageWidth,imageHeight);
    node.snapshot(parameters,wi);

    返回wi;

    }


    public static Image createBrush(double radius,Color color){

    //用给定颜色创建渐变图像
    Circle brush =新的Circle(半径);

    RadialGradient gradient1 = new RadialGradient(0,0,0,0,radius,false,CycleMethod.NO_CYCLE,new Stop(0,color.deriveColor(1,1,1,0.3)),new Stop(1,color.deriveColor(1,1,1,0)));

    brush.setFill(gradient1);

    //创建图像
    return createImage(brush);



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


    $ / code $ / pre

    $ hr

    限制刷子长度的变化示例

      import javafx.animation.AnimationTimer; 
    import javafx.application.Application;
    导入javafx.geometry.Point2D;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.SnapshotParameters;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.control.ColorPicker;
    import javafx.scene.image.Image;
    import javafx.scene.image.WritableImage;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.CycleMethod;
    import javafx.scene.paint.RadialGradient;
    import javafx.scene.paint.Stop;
    import javafx.scene.shape.Circle;
    import javafx.stage.Stage;

    public class Main扩展应用程序{

    private static double SCENE_WIDTH = 1280;
    private static double SCENE_HEIGHT = 720;

    画布画布;
    GraphicsContext graphicsContext;

    AnimationTimer循环;

    Point2D mouseLocation = new Point2D(0,0);
    boolean mousePressed = false;
    Point2D prevMouseLocation = new Point2D(0,0);

    场景场景;

    double brushMaxSize = 30;

    双重压力= 0;
    double pressureDelay = 0.04;
    double pressureDirection = 1;

    double strokeTimeMax = 1;
    double strokeTime = 0;
    double strokeTimeDelay = 0.07;

    private Image [] brushVariations = new Image [256];

    ColorPicker colorPicker = new ColorPicker();

    @Override
    public void start(Stage primaryStage){

    BorderPane root = new BorderPane();

    canvas = new Canvas(SCENE_WIDTH,SCENE_HEIGHT);

    graphicsContext = canvas.getGraphicsContext2D();
    graphicsContext.setFill(Color.WHITE);
    graphicsContext.fillRect(0,0,SCENE_WIDTH,SCENE_HEIGHT);

    Pane layerPane = new Pane();

    layerPane.getChildren()。addAll(canvas);

    colorPicker.setValue(Color.CHOCOLATE);
    colorPicker.setOnAction(e - > {
    createBrushVariations();
    });

    root.setCenter(layerPane);
    root.setTop(colorPicker);

    scene = new Scene(root,SCENE_WIDTH,SCENE_HEIGHT,Color.WHITE);

    primaryStage.setScene(scene);
    primaryStage.show();

    createBrushVariations();

    addListeners();

    startAnimation();



    private void createBrushVariations(){

    for(int i = 0; i< brushVariations.length; i ++){

    double size =(brushMaxSize - 1)/(double)brushVariations.length *(double)i + 1;

    brushVariations [i] = createBrush(size,colorPicker.getValue());



    $ b private void startAnimation(){

    loop = new AnimationTimer(){

    @Override
    public void handle(long now){

    if(mousePressed){

    //试试这个
    // graphicsContext.drawImage( brush,mouseLocation.getX() -
    // brushWidthHalf,mouseLocation.getY() - brushHeightHalf);

    //然后这个
    bresenhamLine(prevMouseLocation.getX(),prevMouseLocation.getY(),mouseLocation.getX(),mouseLocation.getY());

    //增加或减少
    strokeTime + = strokeTimeDelay * pressureDirection;

    //反向
    if(strokeTime> strokeTimeMax){
    pressureDirection = -1;
    }

    // while still
    if(strokeTime> 0){

    pressure + = pressureDelay * pressureDirection;

    //压力钳位值为[0,1]
    if(压力> 1){
    压力= 1;
    } else if(pressure <0){
    pressure = 0;
    }

    }其他{

    压力= 0;

    }

    }其他{

    压力= 0;
    pressureDirection = 1;
    strokeTime = 0;



    prevMouseLocation = new Point2D(mouseLocation.getX(),mouseLocation.getY());

    }
    };

    loop.start();

    }

    // https://de.wikipedia.org/wiki/Bresenham-Algorithmus
    private void bresenhamLine(double x0,double y0,double x1 ,double y1){
    double dx = Math.abs(x1-x0),sx = x0 < x1? 1.:-1。
    double dy = -Math.abs(y1-y0),sy = y0 < y1? 1.:-1。
    double err = dx + dy,e2; / *错误值e_xy * /

    while(true){

    int variation =(int)(pressure *(brushVariations.length - 1));
    图像brushVariation = brushVariations [变体];

    graphicsContext.setGlobalAlpha(pressure);
    graphicsContext.drawImage(brushVariation,x0 - brushVariation.getWidth()/ 2.0,y0 - brushVariation.getHeight()/ 2.0);

    if(x0 == x1&& y0 == y1)
    break;
    e2 = 2. * err;
    if(e2> dy){
    err + = dy;
    x0 + = sx;
    } / * e_xy + e_x> 0 * /
    if(e2 err + = dx;
    y0 + = sy;
    } / * e_xy + e_y< 0 * /
    }
    }

    private void addListeners(){

    canvas.addEventFilter(MouseEvent.ANY,e - > {

    mouseLocation = new Point2D(e.getX(),e.​​getY());

    mousePressed = e.isPrimaryButtonDown();

    }) ;


    $ b public static Image createImage(Node node){

    WritableImage wi;

    SnapshotParameters parameters = new SnapshotParameters();
    parameters.setFill(Color.TRANSPARENT);

    int imageWidth =(int)node.getBoundsInLocal()。getWidth();
    int imageHeight =(int)node.getBoundsInLocal()。getHeight();

    wi = new WritableImage(imageWidth,imageHeight);
    node.snapshot(parameters,wi);

    返回wi;


    $ b public static Image createBrush(double radius,Color color){

    //用给定颜色创建渐变图像
    Circle brush = new Circle(radius);

    RadialGradient gradient1 = new RadialGradient(0,0,0,0,radius,false,CycleMethod.NO_CYCLE,new Stop(0,color.deriveColor(1,1,1,0.3)),new Stop(1,color.deriveColor(1,1,1,0)));

    brush.setFill(gradient1);

    //创建图像
    return createImage(brush);



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




    $ b

    这就是它的样子:





    或者使用不同的颜色,我在最后添加了一个颜色选择器例如:


    I'm trying to write a painting application in JavaFX. I want a brush resembling a real paintbrush, but I'm not sure how to start the algorithm. The code below shows my current paintbrush stroke, although it's a useful stroke, it's not really a paintbrush:

    import javafx.application.Application;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.StrokeLineCap;
    import javafx.scene.shape.StrokeLineJoin;
    import javafx.stage.Stage;
    
    import static javafx.scene.input.MouseEvent.*;
    
    
    public class BrushTester extends Application {
    
        private static final Color color = Color.CHOCOLATE;
        private static final double START_OPACITY = 0.3;
        private static final double OPACITY_MODIFIER = 0.002;
    
        private double currentOpacity = START_OPACITY;
        private double strokeWidth = 15;
    
        public static void main(String[] args) {
            Application.launch(BrushTester.class);
        }
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            Canvas canvas = new Canvas(600d, 600d);
            GraphicsContext gc = canvas.getGraphicsContext2D();
    
            canvas.addEventHandler(MOUSE_DRAGGED, e -> BrushTester.this.handleMouseDragged(gc, e));
            canvas.addEventHandler(MOUSE_PRESSED, e -> handleMousePressed(gc, e));
            canvas.addEventHandler(MOUSE_RELEASED, e -> handleMouseReleased(gc, e));
    
            Group root = new Group();
            root.getChildren().add(canvas);
            primaryStage.setScene(new Scene(root, Color.DARKGRAY));
            primaryStage.show();
        }
    
        private void configureGraphicsContext(GraphicsContext gc) {
            gc.setStroke(new Color(color.getRed(), color.getGreen(), color.getBlue(), currentOpacity));
            gc.setLineCap(StrokeLineCap.ROUND);
            gc.setLineJoin(StrokeLineJoin.ROUND);
            gc.setLineWidth(strokeWidth);
        }
    
        public void handleMousePressed(GraphicsContext gc, MouseEvent e) {
            configureGraphicsContext(gc);
            gc.beginPath();
            gc.moveTo(e.getX(), e.getY());
            gc.stroke();
        }
    
        public void handleMouseReleased(GraphicsContext gc, MouseEvent e) {
            currentOpacity = START_OPACITY;
            gc.closePath();
        }
    
        public void handleMouseDragged(GraphicsContext gc, MouseEvent e) {
            currentOpacity = Math.max(0, currentOpacity - OPACITY_MODIFIER);
            configureGraphicsContext(gc);
            gc.lineTo(e.getX(), e.getY());
            gc.stroke();
        }
    }
    

    Anyone with some tips on how to get closer to the real thing?

    解决方案

    It all depends on what you're trying to achieve. Personally I would use

    • an AnimationTimer
    • a customizable Brush (i. e. an Image) instead of a stroke, so you can specify size and hardness
    • a line drawing algorithm (like Bresenham) to connect the previous mouse location with the current one to get a full line between points

    A quick example with a simple drawing algorithm:

    import java.util.Random;
    
    import javafx.animation.AnimationTimer;
    import javafx.application.Application;
    import javafx.geometry.Point2D;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.SnapshotParameters;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.image.Image;
    import javafx.scene.image.WritableImage;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.CycleMethod;
    import javafx.scene.paint.RadialGradient;
    import javafx.scene.paint.Stop;
    import javafx.scene.shape.Circle;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
        private static double SCENE_WIDTH = 1280;
        private static double SCENE_HEIGHT = 720;
    
        static Random random = new Random();
    
        Canvas canvas;
        GraphicsContext graphicsContext;
    
        AnimationTimer loop;
    
        Point2D mouseLocation = new Point2D( 0, 0);
        boolean mousePressed = false;
        Point2D prevMouseLocation = new Point2D( 0, 0);
    
        Scene scene;
    
        Image brush = createBrush( 30.0, Color.CHOCOLATE);
        double brushWidthHalf = brush.getWidth() / 2.0;
        double brushHeightHalf = brush.getHeight() / 2.0;
    
    
    
        @Override
        public void start(Stage primaryStage) {
    
            BorderPane root = new BorderPane();
    
            canvas = new Canvas( SCENE_WIDTH, SCENE_HEIGHT);
    
            graphicsContext = canvas.getGraphicsContext2D();
    
            Pane layerPane = new Pane();
    
            layerPane.getChildren().addAll(canvas);
    
            root.setCenter(layerPane);
    
            scene = new Scene(root, SCENE_WIDTH, SCENE_HEIGHT);
    
            primaryStage.setScene(scene);
            primaryStage.show();
    
            addListeners();
    
            startAnimation();
    
    
        }
    
        private void startAnimation() {
    
            loop = new AnimationTimer() {
    
                @Override
                public void handle(long now) {
    
                    if( mousePressed) {
    
                        // try this
                        // graphicsContext.drawImage( brush, mouseLocation.getX() - brushWidthHalf, mouseLocation.getY() - brushHeightHalf);
    
                        // then this
                        bresenhamLine( prevMouseLocation.getX(), prevMouseLocation.getY(), mouseLocation.getX(), mouseLocation.getY());
    
                    }
    
                    prevMouseLocation = new Point2D( mouseLocation.getX(), mouseLocation.getY());
    
                }
            };
    
            loop.start();
    
        }
    
        // https://de.wikipedia.org/wiki/Bresenham-Algorithmus
        private void bresenhamLine(double x0, double y0, double x1, double y1)
        {
          double dx =  Math.abs(x1-x0), sx = x0<x1 ? 1. : -1.;
          double dy = -Math.abs(y1-y0), sy = y0<y1 ? 1. : -1.;
          double err = dx+dy, e2; /* error value e_xy */
    
          while( true){
            graphicsContext.drawImage( brush, x0 - brushWidthHalf, y0 - brushHeightHalf);
            if (x0==x1 && y0==y1) break;
            e2 = 2.*err;
            if (e2 > dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
            if (e2 < dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
          }
        }
    
    
        private void addListeners() {
    
            scene.addEventFilter(MouseEvent.ANY, e -> {
    
                mouseLocation = new Point2D(e.getX(), e.getY());
    
                mousePressed = e.isPrimaryButtonDown();
    
            });
    
    
        }
    
    
        public static Image createImage(Node node) {
    
            WritableImage wi;
    
            SnapshotParameters parameters = new SnapshotParameters();
            parameters.setFill(Color.TRANSPARENT);
    
            int imageWidth = (int) node.getBoundsInLocal().getWidth();
            int imageHeight = (int) node.getBoundsInLocal().getHeight();
    
            wi = new WritableImage(imageWidth, imageHeight);
            node.snapshot(parameters, wi);
    
            return wi;
    
        }
    
    
        public static Image createBrush( double radius, Color color) {
    
            // create gradient image with given color
            Circle brush = new Circle(radius);
    
            RadialGradient gradient1 = new RadialGradient(0, 0, 0, 0, radius, false, CycleMethod.NO_CYCLE, new Stop(0, color.deriveColor(1, 1, 1, 0.3)), new Stop(1, color.deriveColor(1, 1, 1, 0)));
    
            brush.setFill(gradient1);
    
            // create image
            return createImage(brush);
    
        }
    
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    Of course you can extend this with e. g.

    • multiple layers
    • JavaFX's blend modes on layer and graphicscontext level
    • to simulate force I'd use a paint delay (eg 200 ms) and a buffer for the mouse locations and let the opacity depend on whether the mouse is still pressed or not
    • smooth the lines by using bezier curves
    • ...

    Example with Brush variations when you start painting:

    import java.util.Random;
    
    import javafx.animation.AnimationTimer;
    import javafx.application.Application;
    import javafx.geometry.Point2D;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.SnapshotParameters;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.image.Image;
    import javafx.scene.image.WritableImage;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.CycleMethod;
    import javafx.scene.paint.RadialGradient;
    import javafx.scene.paint.Stop;
    import javafx.scene.shape.Circle;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
        private static double SCENE_WIDTH = 1280;
        private static double SCENE_HEIGHT = 720;
    
        static Random random = new Random();
    
        Canvas canvas;
        GraphicsContext graphicsContext;
    
        AnimationTimer loop;
    
        Point2D mouseLocation = new Point2D( 0, 0);
        boolean mousePressed = false;
        Point2D prevMouseLocation = new Point2D( 0, 0);
    
        Scene scene;
    
        double brushMaxSize = 30;
        Image brush = createBrush( brushMaxSize, Color.CHOCOLATE);
        double brushWidthHalf = brush.getWidth() / 2.0;
        double brushHeightHalf = brush.getHeight() / 2.0;
    
        double pressure = 0;
        double pressureDelay = 0.04;
    
        private Image[] brushVariations = new Image[256];
    
        @Override
        public void start(Stage primaryStage) {
    
            BorderPane root = new BorderPane();
    
            canvas = new Canvas( SCENE_WIDTH, SCENE_HEIGHT);
    
            for( int i=0; i < brushVariations.length; i++) {
    
                double size = (brushMaxSize - 1) / (double) brushVariations.length  * (double) i + 1;
    
                brushVariations[i] = createBrush( size, Color.CHOCOLATE);
            }
    
            graphicsContext = canvas.getGraphicsContext2D();
    
            Pane layerPane = new Pane();
    
            layerPane.getChildren().addAll(canvas);
    
            root.setCenter(layerPane);
    
            scene = new Scene(root, SCENE_WIDTH, SCENE_HEIGHT);
    
            primaryStage.setScene(scene);
            primaryStage.show();
    
            addListeners();
    
            startAnimation();
    
    
        }
    
        private void startAnimation() {
    
            loop = new AnimationTimer() {
    
                @Override
                public void handle(long now) {
    
                    if( mousePressed) {
    
                        // try this
                        // graphicsContext.drawImage( brush, mouseLocation.getX() - brushWidthHalf, mouseLocation.getY() - brushHeightHalf);
    
                        // then this
                        bresenhamLine( prevMouseLocation.getX(), prevMouseLocation.getY(), mouseLocation.getX(), mouseLocation.getY());
    
                        pressure += pressureDelay;
                        if( pressure > 1) {
                            pressure = 1;
                        }
    
                    } else {
    
                        pressure = 0;
    
                    }
    
                    prevMouseLocation = new Point2D( mouseLocation.getX(), mouseLocation.getY());
    
                }
            };
    
            loop.start();
    
        }
    
        // https://de.wikipedia.org/wiki/Bresenham-Algorithmus
        private void bresenhamLine(double x0, double y0, double x1, double y1)
        {
          double dx =  Math.abs(x1-x0), sx = x0<x1 ? 1. : -1.;
          double dy = -Math.abs(y1-y0), sy = y0<y1 ? 1. : -1.;
          double err = dx+dy, e2; /* error value e_xy */
    
          while( true){
    
            int variation = (int) (pressure * (brushVariations.length - 1));
            Image brushVariation = brushVariations[ variation ];
    
            graphicsContext.setGlobalAlpha(pressure);
            graphicsContext.drawImage( brushVariation, x0 - brushWidthHalf, y0 - brushHeightHalf);
    
            if (x0==x1 && y0==y1) break;
            e2 = 2.*err;
            if (e2 > dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
            if (e2 < dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
          }
        }
    
    
        private void addListeners() {
    
            scene.addEventFilter(MouseEvent.ANY, e -> {
    
                mouseLocation = new Point2D(e.getX(), e.getY());
    
                mousePressed = e.isPrimaryButtonDown();
    
            });
    
    
        }
    
    
        public static Image createImage(Node node) {
    
            WritableImage wi;
    
            SnapshotParameters parameters = new SnapshotParameters();
            parameters.setFill(Color.TRANSPARENT);
    
            int imageWidth = (int) node.getBoundsInLocal().getWidth();
            int imageHeight = (int) node.getBoundsInLocal().getHeight();
    
            wi = new WritableImage(imageWidth, imageHeight);
            node.snapshot(parameters, wi);
    
            return wi;
    
        }
    
    
        public static Image createBrush( double radius, Color color) {
    
            // create gradient image with given color
            Circle brush = new Circle(radius);
    
            RadialGradient gradient1 = new RadialGradient(0, 0, 0, 0, radius, false, CycleMethod.NO_CYCLE, new Stop(0, color.deriveColor(1, 1, 1, 0.3)), new Stop(1, color.deriveColor(1, 1, 1, 0)));
    
            brush.setFill(gradient1);
    
            // create image
            return createImage(brush);
    
        }
    
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    


    Example with variation for limiting the brush length

    import javafx.animation.AnimationTimer;
    import javafx.application.Application;
    import javafx.geometry.Point2D;
    import javafx.scene.Node;
    import javafx.scene.Scene;
    import javafx.scene.SnapshotParameters;
    import javafx.scene.canvas.Canvas;
    import javafx.scene.canvas.GraphicsContext;
    import javafx.scene.control.ColorPicker;
    import javafx.scene.image.Image;
    import javafx.scene.image.WritableImage;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.paint.CycleMethod;
    import javafx.scene.paint.RadialGradient;
    import javafx.scene.paint.Stop;
    import javafx.scene.shape.Circle;
    import javafx.stage.Stage;
    
    public class Main extends Application {
    
        private static double SCENE_WIDTH = 1280;
        private static double SCENE_HEIGHT = 720;
    
        Canvas canvas;
        GraphicsContext graphicsContext;
    
        AnimationTimer loop;
    
        Point2D mouseLocation = new Point2D(0, 0);
        boolean mousePressed = false;
        Point2D prevMouseLocation = new Point2D(0, 0);
    
        Scene scene;
    
        double brushMaxSize = 30;
    
        double pressure = 0;
        double pressureDelay = 0.04;
        double pressureDirection = 1;
    
        double strokeTimeMax = 1;
        double strokeTime = 0;
        double strokeTimeDelay = 0.07;
    
        private Image[] brushVariations = new Image[256];
    
        ColorPicker colorPicker = new ColorPicker();
    
        @Override
        public void start(Stage primaryStage) {
    
            BorderPane root = new BorderPane();
    
            canvas = new Canvas(SCENE_WIDTH, SCENE_HEIGHT);
    
            graphicsContext = canvas.getGraphicsContext2D();
            graphicsContext.setFill(Color.WHITE);
            graphicsContext.fillRect(0, 0, SCENE_WIDTH, SCENE_HEIGHT);
    
            Pane layerPane = new Pane();
    
            layerPane.getChildren().addAll(canvas);
    
            colorPicker.setValue(Color.CHOCOLATE);
            colorPicker.setOnAction(e -> {
                createBrushVariations();
            });
    
            root.setCenter(layerPane);
            root.setTop(colorPicker);
    
            scene = new Scene(root, SCENE_WIDTH, SCENE_HEIGHT, Color.WHITE);
    
            primaryStage.setScene(scene);
            primaryStage.show();
    
            createBrushVariations();
    
            addListeners();
    
            startAnimation();
    
        }
    
        private void createBrushVariations() {
    
            for (int i = 0; i < brushVariations.length; i++) {
    
                double size = (brushMaxSize - 1) / (double) brushVariations.length * (double) i + 1;
    
                brushVariations[i] = createBrush(size, colorPicker.getValue());
            }
    
        }
    
        private void startAnimation() {
    
            loop = new AnimationTimer() {
    
                @Override
                public void handle(long now) {
    
                    if (mousePressed) {
    
                        // try this
                        // graphicsContext.drawImage( brush, mouseLocation.getX() -
                        // brushWidthHalf, mouseLocation.getY() - brushHeightHalf);
    
                        // then this
                        bresenhamLine(prevMouseLocation.getX(), prevMouseLocation.getY(), mouseLocation.getX(), mouseLocation.getY());
    
                        // increasing or decreasing
                        strokeTime += strokeTimeDelay * pressureDirection;
    
                        // invert direction
                        if (strokeTime > strokeTimeMax) {
                            pressureDirection = -1;
                        }
    
                        // while still
                        if (strokeTime > 0) {
    
                            pressure += pressureDelay * pressureDirection;
    
                            // clamp value of pressure to be [0,1]
                            if (pressure > 1) {
                                pressure = 1;
                            } else if (pressure < 0) {
                                pressure = 0;
                            }
    
                        } else {
    
                            pressure = 0;
    
                        }
    
                    } else {
    
                        pressure = 0;
                        pressureDirection = 1;
                        strokeTime = 0;
    
                    }
    
                    prevMouseLocation = new Point2D(mouseLocation.getX(), mouseLocation.getY());
    
                }
            };
    
            loop.start();
    
        }
    
        // https://de.wikipedia.org/wiki/Bresenham-Algorithmus
        private void bresenhamLine(double x0, double y0, double x1, double y1) {
            double dx = Math.abs(x1 - x0), sx = x0 < x1 ? 1. : -1.;
            double dy = -Math.abs(y1 - y0), sy = y0 < y1 ? 1. : -1.;
            double err = dx + dy, e2; /* error value e_xy */
    
            while (true) {
    
                int variation = (int) (pressure * (brushVariations.length - 1));
                Image brushVariation = brushVariations[variation];
    
                graphicsContext.setGlobalAlpha(pressure);
                graphicsContext.drawImage(brushVariation, x0 - brushVariation.getWidth() / 2.0, y0 - brushVariation.getHeight() / 2.0);
    
                if (x0 == x1 && y0 == y1)
                    break;
                e2 = 2. * err;
                if (e2 > dy) {
                    err += dy;
                    x0 += sx;
                } /* e_xy+e_x > 0 */
                if (e2 < dx) {
                    err += dx;
                    y0 += sy;
                } /* e_xy+e_y < 0 */
            }
        }
    
        private void addListeners() {
    
            canvas.addEventFilter(MouseEvent.ANY, e -> {
    
                mouseLocation = new Point2D(e.getX(), e.getY());
    
                mousePressed = e.isPrimaryButtonDown();
    
            });
    
        }
    
        public static Image createImage(Node node) {
    
            WritableImage wi;
    
            SnapshotParameters parameters = new SnapshotParameters();
            parameters.setFill(Color.TRANSPARENT);
    
            int imageWidth = (int) node.getBoundsInLocal().getWidth();
            int imageHeight = (int) node.getBoundsInLocal().getHeight();
    
            wi = new WritableImage(imageWidth, imageHeight);
            node.snapshot(parameters, wi);
    
            return wi;
    
        }
    
        public static Image createBrush(double radius, Color color) {
    
            // create gradient image with given color
            Circle brush = new Circle(radius);
    
            RadialGradient gradient1 = new RadialGradient(0, 0, 0, 0, radius, false, CycleMethod.NO_CYCLE, new Stop(0, color.deriveColor(1, 1, 1, 0.3)), new Stop(1, color.deriveColor(1, 1, 1, 0)));
    
            brush.setFill(gradient1);
    
            // create image
            return createImage(brush);
    
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    This is how it looks like:

    or using different colors, I added a color picker in the last example:

    这篇关于JavaFX中的画笔描边的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆