JavaFX-撤消缩放画布上的绘图
我正在开发一个简单的图像编辑功能,作为一个更大的JavaFX应用程序的一部分,但是我在一起解决撤销/缩放和绘图需求时遇到了一些麻烦 我的要求如下: 用户应能够:JavaFX-撤消缩放画布上的绘图,java,javafx,canvas,undo,Java,Javafx,Canvas,Undo,我正在开发一个简单的图像编辑功能,作为一个更大的JavaFX应用程序的一部分,但是我在一起解决撤销/缩放和绘图需求时遇到了一些麻烦 我的要求如下: 用户应能够: 在图像上徒手画 放大和缩小图像 撤消更改 如果画布比窗口大,它应该有滚动条 我是如何实现这些要求的: 通过在画布上按下鼠标时开始一条线,拖动鼠标时抚摸它,松开按钮时关闭路径,可以完成绘图 缩放的工作原理是将画布缩放到更高或更低的值 Undo方法在按下鼠标(在进行任何更改之前)时拍摄画布当前状态的快照,并将其推送到图像堆栈中。当我需
- 在图像上徒手画
- 放大和缩小图像
- 撤消更改
- 如果画布比窗口大,它应该有滚动条
- 通过在画布上按下鼠标时开始一条线,拖动鼠标时抚摸它,松开按钮时关闭路径,可以完成绘图
- 缩放的工作原理是将画布缩放到更高或更低的值
- Undo方法在按下鼠标(在进行任何更改之前)时拍摄画布当前状态的快照,并将其推送到图像堆栈中。当我需要撤销一些更改时,我弹出堆栈的最后一个图像并在画布上绘制,用最后一个图像替换当前图像
- 要有滚动条,我只需将画布放在一个组和一个滚动窗格中
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.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Stack;
public class Main extends Application {
Stack<Image> undoStack;
Canvas canvas;
double canvasScale;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
canvasScale = 1.0;
undoStack = new Stack<>();
BorderPane borderPane = new BorderPane();
HBox hbox = new HBox(4);
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> undo());
Button btnIncreaseZoom = new Button("Increase Zoom");
btnIncreaseZoom.setOnAction(actionEvent -> increaseZoom());
Button btnDecreaseZoom = new Button("Decrease Zoom");
btnDecreaseZoom.setOnAction(actionEvent -> decreaseZoom());
hbox.getChildren().addAll(btnUndo, btnIncreaseZoom, btnDecreaseZoom);
ScrollPane scrollPane = new ScrollPane();
Group group = new Group();
canvas = new Canvas();
canvas.setWidth(400);
canvas.setHeight(300);
group.getChildren().add(canvas);
scrollPane.setContent(group);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setLineWidth(2.0);
gc.setStroke(Color.RED);
canvas.setOnMousePressed(mouseEvent -> {
pushUndo();
gc.beginPath();
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
});
canvas.setOnMouseDragged(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
});
canvas.setOnMouseReleased(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
gc.closePath();
});
borderPane.setTop(hbox);
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 800, 600);
stage.setScene(scene);
stage.show();
}
private void increaseZoom() {
canvasScale += 0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void decreaseZoom () {
canvasScale -= 0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void pushUndo() {
// Restore the canvas scale to 1 so I can get the original scale image
canvas.setScaleX(1);
canvas.setScaleY(1);
// Get the image with the snapshot method and store it on the undo stack
Image snapshot = canvas.snapshot(null, null);
undoStack.push(snapshot);
// Set the canvas scale to the value it was before the method
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void undo() {
if (!undoStack.empty()) {
Image undoImage = undoStack.pop();
canvas.getGraphicsContext2D().drawImage(undoImage, 0, 0);
}
}
}
- 不重新缩放以拍摄快照-不会导致不需要的线条,但我最终会在堆栈中使用不同的图像大小,如果在拍摄快照时图像较小(缩小),我现在的图像分辨率较低,无法在不降低质量的情况下进行缩放
- 调整逻辑并将pushUndo调用放到mousererelease事件中——它几乎成功了,但是当用户滚动到某个地方并在那里绘制时,重新缩放会导致图像滚动回左上角
- 试图搜索一种“克隆”或序列化画布并将对象状态存储在堆栈中的方法-没有找到任何我能够适应的方法,并且JavaFX不支持对象的序列化
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.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import java.util.Stack;
public class Main extends Application {
Stack<Image> undoStack;
Canvas canvas;
double canvasScale;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
canvasScale = 1.0;
undoStack = new Stack<>();
BorderPane borderPane = new BorderPane();
HBox hbox = new HBox(4);
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> undo());
Button btnIncreaseZoom = new Button("Increase Zoom");
btnIncreaseZoom.setOnAction(actionEvent -> increaseZoom());
Button btnDecreaseZoom = new Button("Decrease Zoom");
btnDecreaseZoom.setOnAction(actionEvent -> decreaseZoom());
hbox.getChildren().addAll(btnUndo, btnIncreaseZoom, btnDecreaseZoom);
ScrollPane scrollPane = new ScrollPane();
Group group = new Group();
canvas = new Canvas();
canvas.setWidth(400);
canvas.setHeight(300);
group.getChildren().add(canvas);
scrollPane.setContent(group);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setLineWidth(2.0);
gc.setStroke(Color.RED);
canvas.setOnMousePressed(mouseEvent -> {
pushUndo();
gc.beginPath();
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
});
canvas.setOnMouseDragged(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
});
canvas.setOnMouseReleased(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
gc.closePath();
});
borderPane.setTop(hbox);
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 800, 600);
stage.setScene(scene);
stage.show();
}
private void increaseZoom() {
canvasScale += 0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void decreaseZoom () {
canvasScale -= 0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void pushUndo() {
// Restore the canvas scale to 1 so I can get the original scale image
canvas.setScaleX(1);
canvas.setScaleY(1);
// Get the image with the snapshot method and store it on the undo stack
Image snapshot = canvas.snapshot(null, null);
undoStack.push(snapshot);
// Set the canvas scale to the value it was before the method
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvasScale);
}
private void undo() {
if (!undoStack.empty()) {
Image undoImage = undoStack.pop();
canvas.getGraphicsContext2D().drawImage(undoImage, 0, 0);
}
}
}
导入javafx.application.application;
导入javafx.scene.Group;
导入javafx.scene.scene;
导入javafx.scene.canvas.canvas;
导入javafx.scene.canvas.GraphicsContext;
导入javafx.scene.control.Button;
导入javafx.scene.control.ScrollPane;
导入javafx.scene.image.image;
导入javafx.scene.layout.BorderPane;
导入javafx.scene.layout.HBox;
导入javafx.scene.paint.Color;
导入javafx.stage.stage;
导入java.util.Stack;
公共类主扩展应用程序{
堆栈撤消堆栈;
帆布;
双拉票量表;
公共静态void main(字符串[]args){
发射(args);
}
@凌驾
公众假期开始(阶段){
拉票量表=1.0;
undoStack=新堆栈();
BorderPane BorderPane=新的BorderPane();
HBox HBox=新的HBox(4);
按钮btnUndo=新按钮(“撤消”);
setOnAction(actionEvent->undo());
按钮BTNinIncreaseZoom=新按钮(“增加缩放”);
btninIncreaseZoom.setOnAction(actionEvent->increaseZoom());
按钮BTN减少缩放=新按钮(“减少缩放”);
btnDecreseZoom.setOnAction(actionEvent->DecreseZoom());
hbox.getChildren().addAll(btnUndo、btninIncreaseZoom、btnDecreaseZoom);
ScrollPane ScrollPane=新的ScrollPane();
组=新组();
画布=新画布();
画布设置宽度(400);
画布设置高度(300);
group.getChildren().add(画布);
scrollPane.setContent(组);
GraphicsContext gc=canvas.getGraphicsContext2D();
gc.设定线宽(2.0);
gc.设定行程(颜色为红色);
canvas.setOnMousePressed(mouseEvent->{
pushUndo();
gc.beginPath();
gc.lineTo(mouseEvent.getX(),mouseEvent.getY());
});
canvas.setonmousedrable(mouseEvent->{
gc.lineTo(mouseEvent.getX(),mouseEvent.getY());
gc.stroke();
});
canvas.setOnMouseReleased(mouseEvent->{
gc.lineTo(mouseEvent.getX(),mouseEvent.getY());
gc.stroke();
gc.closePath();
});
borderPane.setTop(hbox);
边框窗格。设置中心(滚动窗格);
场景=新场景(边框窗格,800600);
舞台场景;
stage.show();
}
私有void increaseZoom(){
拉票比例+=0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvascale);
}
私有void递减缩放(){
拉票比例-=0.1;
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvascale);
}
私有void pushUndo(){
//将画布比例恢复为1,以便获得原始比例图像
画布。设置刻度(1);
帆布。定格尺(1);
//使用快照方法获取图像并将其存储在撤消堆栈上
Image snapshot=canvas.snapshot(null,null);
撤消堆栈推送(快照);
//将画布比例设置为方法之前的值
canvas.setScaleX(canvasScale);
canvas.setScaleY(canvascale);
}
私有void undo(){
如果(!撤消堆栈)
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import java.util.Stack;
public class ExtendedCanvas extends Canvas {
private final double ZOOM_SCALE = 0.1;
private final double MAX_ZOOM_SCALE = 3.0;
private final double MIN_ZOOM_SCALE = 0.2;
private double currentScale;
private final Stack<Image> undoStack;
private final Stack<Image> redoStack;
private final Canvas carbonCanvas;
private final GraphicsContext gc;
private final GraphicsContext carbonGc;
public ExtendedCanvas(double width, double height){
super(width, height);
carbonCanvas = new Canvas(width, height);
undoStack = new Stack<>();
redoStack = new Stack<>();
currentScale = 1.0;
gc = this.getGraphicsContext2D();
carbonGc = carbonCanvas.getGraphicsContext2D();
setEventHandlers();
}
private void setEventHandlers() {
this.setOnMousePressed(mouseEvent -> {
pushUndo();
gc.beginPath();
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.beginPath();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
});
this.setOnMouseDragged(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.stroke();
});
this.setOnMouseReleased(mouseEvent -> {
gc.lineTo(mouseEvent.getX(), mouseEvent.getY());
gc.stroke();
gc.closePath();
carbonGc.lineTo(mouseEvent.getX(), mouseEvent.getY());
carbonGc.stroke();
carbonGc.closePath();
});
}
public void zoomIn() {
if (currentScale < MAX_ZOOM_SCALE ) {
currentScale += ZOOM_SCALE;
setScale(currentScale);
}
}
public void zoomOut() {
if (currentScale > MIN_ZOOM_SCALE) {
currentScale -= ZOOM_SCALE;
setScale(currentScale);
}
}
public void zoomNormal() {
currentScale = 1.0;
setScale(currentScale);
}
private void setScale(double value) {
this.setScaleX(value);
this.setScaleY(value);
carbonCanvas.setScaleX(value);
carbonCanvas.setScaleY(value);
}
private void pushUndo() {
redoStack.clear();
undoStack.push(getSnapshot());
}
private Image getSnapshot(){
carbonCanvas.setScaleX(1);
carbonCanvas.setScaleY(1);
Image snapshot = carbonCanvas.snapshot(null, null);
carbonCanvas.setScaleX(currentScale);
carbonCanvas.setScaleY(currentScale);
return snapshot;
}
public void undo() {
if (hasUndo()) {
Image redo = getSnapshot();
redoStack.push(redo);
Image undoImage = undoStack.pop();
gc.drawImage(undoImage, 0, 0);
carbonGc.drawImage(undoImage, 0, 0);
}
}
public void redo() {
if (hasRedo()) {
Image undo = getSnapshot();
undoStack.push(undo);
Image redoImage = redoStack.pop();
gc.drawImage(redoImage, 0, 0);
carbonGc.drawImage(redoImage, 0, 0);
}
}
public boolean hasUndo() {
return !undoStack.isEmpty();
}
public boolean hasRedo() {
return !redoStack.isEmpty();
}
}
package com.felipepaschoal;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
public class Main extends Application {
ExtendedCanvas extendedCanvas;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
BorderPane borderPane = new BorderPane();
HBox hbox = new HBox(4);
Button btnUndo = new Button("Undo");
btnUndo.setOnAction(actionEvent -> extendedCanvas.undo());
Button btnRedo = new Button("Redo");
btnRedo.setOnAction(actionEvent -> extendedCanvas.redo());
Button btnDecreaseZoom = new Button("-");
btnDecreaseZoom.setOnAction(actionEvent -> extendedCanvas.zoomOut());
Button btnResetZoom = new Button("Reset");
btnResetZoom.setOnAction(event -> extendedCanvas.zoomNormal());
Button btnIncreaseZoom = new Button("+");
btnIncreaseZoom.setOnAction(actionEvent -> extendedCanvas.zoomIn());
hbox.getChildren().addAll(
btnUndo,
btnRedo,
btnDecreaseZoom,
btnResetZoom,
btnIncreaseZoom
);
ScrollPane scrollPane = new ScrollPane();
Group group = new Group();
extendedCanvas = new ExtendedCanvas(300,200);
group.getChildren().add(extendedCanvas);
scrollPane.setContent(group);
borderPane.setTop(hbox);
borderPane.setCenter(scrollPane);
Scene scene = new Scene(borderPane, 600, 400);
stage.setScene(scene);
stage.show();
}
}