【问题标题】:How to crop and resize JavaFX Image?如何裁剪和调整 JavaFX 图像的大小?
【发布时间】:2014-12-11 11:52:17
【问题描述】:

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

这是我到目前为止所做的:

  • 将文件中的全尺寸图像加载到 Image 对象中。
  • 计算要显示图像的哪个部分并计算比例 参数以将其正确放入 800x600 画布中。

所以基本上我想使用GraphicsContext.drawImage(...) - 将给定图像的当前源矩形绘制到画布的给定目标矩形。

对于这种方法,我正确计算了所有参数。问题是有时 Image 大于 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)

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

【问题讨论】:

    标签: image canvas javafx-2 javafx-8


    【解决方案1】:

    您可以通过多种方式执行此操作,具体取决于您在流程中的各个阶段获得的信息。

    如果您在加载之前知道文件中图像的大小,从而可以计算比例因子,那么您实际上可以在加载时对其进行缩放:

    double requiredWidth = ... ;
    double requiredHeight = ... ;
    String imageURL = ... ;
    
    Image image = new Image(imageURL, requiredWidth, requiredHeight, false, true);
    

    最后两个参数是preserveRatiosmooth。后者将强制使用更慢但质量更好的重新缩放算法。

    现在您可以将其裁剪为新的WritableImage,如post you linked

    double x = ... ;
    double y = ... ;
    double width = ...;
    double height = ... ;
    WritableImage croppedImage = new WritableImage(image.getPixelReader(), x, y, width, height);
    

    其中xywidthheight 定义了裁剪区域(在缩放坐标中)。

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

    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);
    

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

    【讨论】:

    • 感谢您的回答。可悲的是我不能让它工作。如果我使用您的第二个建议,那么在加载单个 50 mb 图像时,即使堆空间设置为 512m,我也会收到“java.lang.OutOfMemoryError:Java 堆空间”异常。只有当我能够先裁剪图像然后调整其大小时,您的第一个建议才有效。据我所知,这是不可能的。我不知道这在java中会很难做到。我不知道如何解决这个问题。
    • 适合高效编程访问的图像的内存表示将比磁盘表示使用更多的内存。您的图像是 1 亿像素。我不知道 JavaFX 如何在内部表示它们,但我猜每个像素至少需要一个 int,因此您正在查看约 400MB 的单个图像,当然您的其余代码也需要一些堆空间。你不能分配更多的内存吗?我不会在小于 2GB 的堆空间的情况下尝试这个。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-15
    • 1970-01-01
    • 2011-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多