Следующий код реализует рисование кистью на изображении с использованием холста. Идея состоит в том, чтобы разместить объект Canvas поверх ImageView, нарисовать на нем и, наконец, визуализировать StackPane (изображение + холст) с помощью SnapshotParameters и WritableImage.
Код
Метод saveSnapshot(StackPane stack, int width, int height) просто создает WritableImage с заданной шириной и высотой (соответствующими размерам исходного изображения) и принимает снимок StackPane на нем.
Это полный код:
Код: Выделить всё
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.embed.swing.SwingFXUtils;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Objects;
public class DrawOverImageCanvas extends Application {
private double lastX;
private double lastY;
@Override
public void start(Stage stage) {
Image image = new Image(Objects.requireNonNull(getClass().getResourceAsStream("yourPath")));
int height = (int) image.getHeight();
int width = (int) image.getWidth();
System.out.println("width: " + width + " height: " + height);
if (image.isError())
System.err.println("Could not load image: " + image.getException());
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
imageView.setFitWidth(500);
Canvas canvas = new Canvas();
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setLineWidth(3);
gc.setStroke(Color.RED);
canvas.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
lastX = e.getX();
lastY = e.getY();
}
});
canvas.setOnMouseDragged(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
double x = e.getX();
double y = e.getY();
gc.strokeLine(lastX, lastY, x, y);
lastX = x;
lastY = y;
}
});
StackPane stack = new StackPane(imageView, canvas);
stack.setAlignment(Pos.CENTER);
stack.setStyle("-fx-background-color: transparent;");
DoubleBinding imageWidth = Bindings.createDoubleBinding(
() -> imageView.getLayoutBounds().getWidth(),
imageView.layoutBoundsProperty()
);
DoubleBinding imageHeight = Bindings.createDoubleBinding(
() -> imageView.getLayoutBounds().getHeight(),
imageView.layoutBoundsProperty()
);
stack.minWidthProperty().bind(imageWidth);
stack.prefWidthProperty().bind(imageWidth);
stack.maxWidthProperty().bind(imageWidth);
stack.minHeightProperty().bind(imageHeight);
stack.prefHeightProperty().bind(imageHeight);
stack.maxHeightProperty().bind(imageHeight);
canvas.widthProperty().bind(imageWidth);
canvas.heightProperty().bind(imageHeight);
Button saveButton = new Button("Save PNG");
saveButton.setOnAction(e -> saveSnapshot(stack, width, height));
Button clearButton = new Button("Clear Drawing");
clearButton.setOnAction(e -> {
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
});
VBox root = new VBox(10, stack, new VBox(5, saveButton, clearButton));
root.setAlignment(Pos.CENTER);
root.setStyle("-fx-padding: 10; -fx-background-color: #333333;");
Scene scene = new Scene(root);
stage.setTitle("Draw over Image with Canvas");
stage.setScene(scene);
stage.sizeToScene();
stage.show();
}
private void saveSnapshot(StackPane stack, int width, int height) {
SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.TRANSPARENT);
WritableImage writableImage = new WritableImage(width, height);
stack.snapshot(params, writableImage);
BufferedImage bufferedImage = SwingFXUtils.fromFXImage(writableImage, null);
File outFile = new File("500.png");
try {
ImageIO.write(bufferedImage, "png", outFile);
System.out.println("Saved to: " + outFile.getAbsolutePath());
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
Проблема заключается в том, что когда для FitWidth и FitHeight изображения установлены значения, меньшие, чем их фактическая ширина и высота, снимок захватывает всю StackPane, используя значения FitWidth и FitHeight, а не фактическую ширину и высоту изображения, поскольку размер StackPane привязан к ImageView.
Получающееся изображение снимка сохранит свой первоначальный размер (поскольку размер WritableImage соответствует размеру изображения), но внутри него будет отображаться StackPane, поскольку FitWidth и FitHeight были меньше фактической высоты и ширины изображения.
Снимок экрана ниже взят из проводника моего компьютера и показывает разные результаты, полученные с использованием различных значений Fit Width из От 200 до 500 по сравнению с исходным изображением в его фактическом размере. Все изображения имеют одинаковые размеры, но визуализированные StackPane различаются в зависимости от значения, используемого для setFitWidth.

Есть ли способ, чтобы визуализированная StackPane занимала всю высоту и ширину исходного изображения?
Подробнее здесь: https://stackoverflow.com/questions/798 ... ered-image
Мобильная версия