Image 如何裁剪和调整JavaFX图像的大小?

Image 如何裁剪和调整JavaFX图像的大小?,image,canvas,javafx-2,javafx-8,Image,Canvas,Javafx 2,Javafx 8,我试图在JavaFX画布上显示非常大的图像。单个图像的分辨率为11980x8365。 每个图像都有一个对应的世界文件,我可以使用它来正确定位图像。 我的画布尺寸是800x600。有时我需要在画布上写下整个图像,有时只是其中的一部分 以下是我迄今为止所做的工作: 将文件中的全尺寸图像加载到图像对象中 计算要显示的图像的哪一部分并计算比例 参数,以便将其正确装入800x600画布 所以基本上我想使用-将给定图像的当前源矩形绘制到画布的给定目标矩形 对于这种方法,我正确计算了所有参数。问题是,有时

我试图在JavaFX画布上显示非常大的图像。单个图像的分辨率为11980x8365。 每个图像都有一个对应的世界文件,我可以使用它来正确定位图像。 我的画布尺寸是800x600。有时我需要在画布上写下整个图像,有时只是其中的一部分

以下是我迄今为止所做的工作:

  • 将文件中的全尺寸图像加载到图像对象中
  • 计算要显示的图像的哪一部分并计算比例 参数,以便将其正确装入800x600画布
所以基本上我想使用-将给定图像的当前源矩形绘制到画布的给定目标矩形

对于这种方法,我正确计算了所有参数。问题是,有时图像大于2048x2048,出于某种原因,JavaFX试图使用GPU将此图像直接绘制到画布上(如果我理解正确的话)。这就是我得到异常的时候:

java.lang.NullPointerException
    at com.sun.prism.sw.SWGraphics.drawTexture(SWGraphics.java:686) at com.sun.prism.sw.SWGraphics.drawTexture(SWGraphics.java:686)
    at com.sun.prism.sw.SWGraphics.drawTexture(SWGraphics.java:665)
    at com.sun.prism.sw.SWGraphics.drawTexture(SWGraphics.java:648)
    at com.sun.javafx.sg.prism.NGCanvas.handleRenderOp(NGCanvas.java:1228)
    at com.sun.javafx.sg.prism.NGCanvas.renderStream(NGCanvas.java:997)
    at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:578)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)
    at com.sun.javafx.sg.prism.NGNode.renderForClip(NGNode.java:2282)
    at com.sun.javafx.sg.prism.NGNode.renderRectClip(NGNode.java:2176)
    at com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2202)
    at com.sun.javafx.sg.prism.CacheFilter.impl_renderNodeToCache(CacheFilter.java:655)
    at com.sun.javafx.sg.prism.CacheFilter.render(CacheFilter.java:561)
    at com.sun.javafx.sg.prism.NGNode.renderCached(NGNode.java:2346)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2034)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)
    at com.sun.javafx.sg.prism.NGNode.renderForClip(NGNode.java:2282)
    at com.sun.javafx.sg.prism.NGNode.renderRectClip(NGNode.java:2176)
    at com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2202)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2037)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)
    at com.sun.javafx.sg.prism.NGNode.renderForClip(NGNode.java:2282)
    at com.sun.javafx.sg.prism.NGNode.renderRectClip(NGNode.java:2176)
    at com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2202)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2037)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)
    at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:225)
    at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:575)
    at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)
    at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1951)
    at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:469)
    at com.sun.javafx.tk.quantum.ViewPainter.paintImpl(ViewPainter.java:317)
    at com.sun.javafx.tk.quantum.UploadingPainter.run(UploadingPainter.java:132)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
    at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:129)
    at java.lang.Thread.run(Thread.java:744)

所以我接下来要尝试的是在将图像发送到画布之前,在一些临时对象中裁剪和缩放图像。我找不到任何地方可以这样做的例子。我发现的唯一一个例子是如何进行剪切,但我不知道如何在剪切后缩放并将其转换为图像。

根据过程中的不同点上可用的信息,可以采用多种方法

如果在加载之前知道文件上图像的大小,并因此可以计算比例因子,则可以在加载时实际缩放图像:

double requiredWidth = ... ;
double requiredHeight = ... ;
String imageURL = ... ;

Image image = new Image(imageURL, requiredWidth, requiredHeight, false, true);
最后两个参数是
preserveRatio
smooth
。后者将强制使用速度较慢但质量更好的重缩放算法

现在,您只需将其裁剪为新的
可写图像
,如下所示:

其中,
x
y
宽度
高度
定义了裁剪区域(在缩放坐标中)

然后,您可以将裁剪后的图像绘制到画布中:

graphicsContent.drawImage(croppedImage, canvasX, canvasY);
另一种方法是加载整个图像,然后使用
ImageView
创建其裁剪的缩放视图:

Image fullImage = new Image(imageURL);

// define crop in image coordinates:
Rectangle2D croppedPortion = new Rectangle2D(x, y, width, height);

// target width and height:
double scaledWidth = ... ;
double scaledHeight = ... ;

ImageView imageView = new ImageView(fullImage);
imageView.setViewport(croppedPortion);
imageView.setFitWidth(scaledWidth);
imageView.setFitHeight(scaledHeight);
imageView.setSmooth(true);
现在,您可以通过拍摄
ImageView
的快照,使用原始图像的裁剪版本创建新图像。为此,您需要将
ImageView
置于屏幕外场景中:

Pane pane = new Pane(imageView);
Scene offScreenScene = new Scene(pane);
WritableImage croppedImage = imageView.snapshot(null, null);

然后,您可以像以前一样将裁剪后的图像绘制到画布中。

谢谢您的回答。很遗憾,我不能让它工作。如果我使用您的第二个建议,则在加载单个50MB映像时,即使堆空间设置为512m,也会出现“java.lang.OutOfMemoryError:java堆空间”异常。你的第一个建议可能只有在我能够首先裁剪一个图像,然后调整它的大小时才有效。据我所知,这是不可能的。我不知道这在java中会如此困难。我不知道如何解决这个问题。适合高效编程访问的映像的内存表示将使用比磁盘表示多得多的内存。你的图像有1亿像素。我不知道JavaFX在内部是如何表示它们的,但我猜它至少需要每像素一个int,因此对于单个图像,您需要大约400MB的内存,当然,您的代码的其余部分也需要一些堆空间。你不能分配更多的内存吗?我不会在堆空间小于2GB的情况下尝试此操作。
Pane pane = new Pane(imageView);
Scene offScreenScene = new Scene(pane);
WritableImage croppedImage = imageView.snapshot(null, null);