JavaFX应用程序在绘制多个画布后无响应
我有一个应用程序,我想显示一个JavaFX应用程序在绘制多个画布后无响应,java,javafx,canvas,Java,Javafx,Canvas,我有一个应用程序,我想显示一个FlowPane,其中PDF页面缩略图被绘制到画布中。我正在使用和渲染页面。 我当前的实现创建了一系列画布作为页面数量,将它们添加到流程窗格,然后旋转一系列或异步任务,将页面内容绘制到画布中 我不确定异步绘图是否是推荐的方法,但我的想法是不要使用JavaFX线程进行PDF解析,以避免冻结应用程序。 现在,我的问题是,我可以从日志中看到,所有渲染任务都已完成,文档已关闭。UI显示一些呈现的页面,但在10秒钟内没有响应。在应用程序恢复之后,所有页面都被呈现,一切都很好地
FlowPane
,其中PDF页面缩略图被绘制到画布中。我正在使用和渲染页面。
我当前的实现创建了一系列画布作为页面数量,将它们添加到流程窗格
,然后旋转一系列或异步任务,将页面内容绘制到画布中
我不确定异步绘图是否是推荐的方法,但我的想法是不要使用JavaFX线程进行PDF解析,以避免冻结应用程序。
现在,我的问题是,我可以从日志中看到,所有渲染任务都已完成,文档已关闭。UI显示一些呈现的页面,但在10秒钟内没有响应。在应用程序恢复之后,所有页面都被呈现,一切都很好地工作
我试着描述一下,我认为这是相关的部分:
但我对引擎盖下发生的事情知之甚少,我无法找出我做错了什么。你对我的方法/代码有什么错误以及如何改进有什么想法或提示吗?理想情况下,我希望应用程序在页面缩略图填充时快速响应
我在Linux上有一台相当不错的机器,但我也在Windows上进行了测试,得到了相同的行为。我还试图用HBox
或VBox
替换FlowPane
,但仍然发生了同样的情况
下面是一些难看的代码来重现这种行为:
public class TestApp extends Application {
public static void main(String[] args) {
Application.launch(TestApp.class, args);
}
@Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
var flow = new FlowPane();
flow.setOnDragOver(e -> {
if (e.getDragboard().hasFiles()) {
e.acceptTransferModes(TransferMode.COPY);
}
e.consume();
});
flow.setOnDragDropped(e -> {
var tasks = new ArrayList<TestApp.RenderTask>();
try {
var document = PDDocument.load(e.getDragboard()
.getFiles().get(0));
var renderer = new PDFRenderer(document);
for (int i = 1; i <= document.getNumberOfPages(); i++) {
var page = document.getPage(i - 1);
var cropbox = page.getCropBox();
var thumbnailDimension = new Dimension2D(400,
400 * (cropbox.getHeight() / cropbox.getWidth()));
var thumbnail = new Canvas(thumbnailDimension.getWidth(), thumbnailDimension.getHeight());
var gs = thumbnail.getGraphicsContext2D();
var pop = gs.getFill();
gs.setFill(Color.WHITE);
gs.fillRect(0, 0, thumbnailDimension.getWidth(), thumbnailDimension.getHeight());
gs.setFill(pop);
tasks.add(new TestApp.RenderTask(renderer, thumbnail, i));
flow.getChildren().add(new Group(thumbnail));
}
var exec = Executors.newSingleThreadExecutor();
tasks.forEach(exec::submit);
exec.submit(()-> {
try {
document.close();
System.out.println("close");
} catch (IOException ioException) {
ioException.printStackTrace();
}
});
} catch (Exception ioException) {
ioException.printStackTrace();
}
e.setDropCompleted(true);
e.consume();
});
var scroll = new ScrollPane(flow);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
root.setCenter(scroll);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
private static record RenderTask(PDFRenderer renderer, Canvas canvas, int page) implements Runnable {
@Override
public void run() {
var gs = new FXGraphics2D(canvas.getGraphicsContext2D());
gs.setBackground(WHITE);
try {
renderer.renderPageToGraphics(page - 1, gs);
} catch (IOException e) {
e.printStackTrace();
}
gs.dispose();
}
}
}
公共类TestApp扩展应用程序{
公共静态void main(字符串[]args){
应用程序启动(TestApp.class,args);
}
@凌驾
公共无效开始(阶段primaryStage){
试一试{
BorderPane根=新的BorderPane();
场景=新场景(根,400400);
var flow=新的FlowPane();
流量设定值(e->{
如果(例如getDragboard().hasFiles()){
e、 acceptTransferModes(TransferMode.COPY);
}
e、 消费();
});
flow.setOnDragDropped(e->{
var tasks=new ArrayList();
试一试{
var document=PDDocument.load(例如getDragboard()
.getFiles().get(0));
var渲染器=新的PDF渲染器(文档);
对于(int i=1;i{
试一试{
document.close();
系统输出打印项次(“关闭”);
}捕获(IOException IOException){
ioException.printStackTrace();
}
});
}捕获(异常ioException){
ioException.printStackTrace();
}
e、 setDropCompleted(真);
e、 消费();
});
var scroll=新滚动窗格(流);
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
root.setCenter(滚动);
初级阶段。场景(场景);
primaryStage.show();
}捕获(例外e){
e、 printStackTrace();
}
}
私有静态记录RenderTask(PDFRenderer渲染器、画布画布、int页面)实现可运行{
@凌驾
公开募捐{
var gs=newFXGraphics2D(canvas.getGraphicsContext2D());
地面障碍(白色);
试一试{
renderPageToGraphics(第1页,gs);
}捕获(IOE异常){
e、 printStackTrace();
}
gs.dispose();
}
}
}
这是我用来测试它的310页PDF文件。
谢谢我终于得到了我想要的,应用程序响应了,缩略图已经准备好,并且内存使用有限 问题
我以为我是从FX线程中绘制画布,但是
Canvas
的工作原理是,你填充了一个绘制指令缓冲区,当画布成为场景的一部分时,它们就会被执行(我想)。发生的事情是,我用大量的绘图指令快速填充画布,将所有画布添加到FlowPane
中,应用程序花费大量时间实际执行310画布的绘图指令,并在约10秒钟内没有响应
在这里和Twitter上阅读了JavaFX社区的一些内容和建议后,我尝试切换到ImageView
实现,告诉PDFBox创建页面的BufferedImage
,并使用它创建ImageView
。它工作得很好,但内存使用率是Canvas impl的10倍:
两全其美在我的最终解决方案中,我将白色画布添加到FlowPane,创建页面的
BuffereImage
,并将其转换为FX线程的图像
,将图像绘制到画布
,然后丢弃图像
。
这是相同的310页PDF文件的内存使用情况:
这就是应用程序的响应性:
编辑(添加要复制的代码)这里是我使用的代码(使用SAMBox而不是PDFBox)
公共类TestAppCanvasImageMixed扩展应用程序{
公共静态void main(字符串[]args){
Application.launch(TestAppCanvas.class,args);
}
@凌驾
公共无效开始(阶段primaryStage){
试一试{
BorderPane根=新的BorderPane();
场景=新场景(根,400400);
var按钮=新按钮(“渲染”);
按钮。设置禁用(true);
setTop(新的HBox(按钮));
var flow=新的FlowPane();
流量设定值(e->{
如果(例如getDragboard().hasFiles()){
public class TestAppCanvasImageMixed extends Application {
public static void main(String[] args) {
Application.launch(TestAppCanvas.class, args);
}
@Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root, 400, 400);
var button = new Button("render");
button.setDisable(true);
root.setTop(new HBox(button));
var flow = new FlowPane();
flow.setOnDragOver(e -> {
if (e.getDragboard().hasFiles()) {
e.acceptTransferModes(TransferMode.COPY);
}
e.consume();
});
flow.setOnDragDropped(e -> {
ArrayList<TestAppCanvasImageMixed.RenderTaskToImage> tasks = new ArrayList<>();
try {
var document = PDDocument.load(e.getDragboard()
.getFiles().get(0));
var renderer = new PDFRenderer(document);
for (int i = 1; i <= document.getNumberOfPages(); i++) {
var page = document.getPage(i - 1);
var cropbox = page.getCropBox();
var thumbnailDimension = new Dimension2D(400,
400 * (cropbox.getHeight() / cropbox.getWidth()));
var thumbnail = new Canvas(thumbnailDimension.getWidth(), thumbnailDimension.getHeight());
var gs = thumbnail.getGraphicsContext2D();
var clip = new Rectangle(thumbnailDimension.getWidth(), thumbnailDimension.getHeight());
clip.setArcHeight(15);
clip.setArcWidth(15);
thumbnail.setClip(clip);
var pop = gs.getFill();
gs.setFill(WHITE);
gs.fillRect(0, 0, thumbnailDimension.getWidth(), thumbnailDimension.getHeight());
gs.setFill(pop);
var g = new Group();
tasks.add(new TestAppCanvasImageMixed.RenderTaskToImage(renderer, 400 / cropbox.getWidth(), i, thumbnail,
() -> g.getChildren().setAll(thumbnail)));
flow.getChildren().add(new Group());
}
button.setOnAction(a -> {
var exec = Executors.newFixedThreadPool(1);
tasks.forEach(exec::submit);
exec.submit(() -> {
try {
document.close();
System.out.println("close");
} catch (IOException ioException) {
ioException.printStackTrace();
}
});
});
button.setDisable(false);
} catch (Exception ioException) {
ioException.printStackTrace();
}
e.setDropCompleted(true);
e.consume();
});
var scroll = new ScrollPane(new StackPane(flow));
scroll.setFitToHeight(true);
scroll.setFitToWidth(true);
root.setCenter(scroll);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
}
private static record RenderTaskToImage(PDFRenderer renderer, float scale, int page, Canvas canvas, Runnable r) implements Runnable {
@Override
public void run() {
try {
var bufferedImage = renderer.renderImage(page - 1, scale, ImageType.ARGB, RenderDestination.VIEW);
var image = SwingFXUtils.toFXImage(bufferedImage, null);
var gc = canvas.getGraphicsContext2D();
gc.drawImage(image, 0, 0);
Platform.runLater(() -> r.run());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}