JavaFX中的画笔描边 [英] Paintbrush stroke in 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();
}
}
任何人如何更接近真正的东西?
这一切都取决于你想要达到的目标。我个人会使用 一个简单的绘图算法的简单例子:
$ 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
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。
刷子变化时的示例当您开始绘画时:
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屋!