JDK-8088135 : [Swing] NullPointerException when rendering SwingFXUtils.toFXImage() on 10,000x8,0000 image
  • Type: Bug
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: 8u20
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2014-02-26
  • Updated: 2018-09-05
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Relates :  
Description
Converted use SwingFXUtils.toFXImage() to convert BufferedImage then put result Image in ImageView. 10,000x8,000 fails on rendering, while 512x512 opens/renders fine. Debug text:

PCS Texture allocating buffer: com.sun.prism.sw.SWRTTexture@5261bdfd, 1924x1040
+ PR.resetClip
+ concat scale com.sun.prism.sw.SWGraphics@1c357047; sx: 1.0; sy: 1.0
+ PR.resetClip
+ PR.clear: Color[r=0.0, g=0.0, b=0.0, a=1.0]
PR.setColor: Color[r=0.0, g=0.0, b=0.0, a=1.0]
+ getTransformNoClone com.sun.prism.sw.SWGraphics@1c357047; tr: Affine2D[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
+ concatTransform com.sun.prism.sw.SWGraphics@1c357047; tr: Identity[]
+ getTransformNoClone com.sun.prism.sw.SWGraphics@1c357047; tr: Affine2D[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]
+ concatTransform com.sun.prism.sw.SWGraphics@1c357047; tr: Identity[]
java.lang.NullPointerException
	at com.sun.prism.sw.SWGraphics.drawTexture(SWGraphics.java:674)
	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.NGImageView.renderContent(NGImageView.java:123)
	at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)
	at com.sun.javafx.sg.prism.NGImageView.doRender(NGImageView.java:103)
	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.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:324)
	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)


Tracing code looks like NGImageView.java:121
>>>>121             Texture texture = factory.getCachedTexture(image, Texture.WrapMode.CLAMP_TO_EDGE);
is good candidate for returning null, which is passed to drawTexture() in line 123 ultimately throwing null exception in SWGraphics.java:674
>>>>674                     ", tex.w: " + tex.getPhysicalWidth() + ", tex.h: " + tex.getPhysicalHeight() +

Noticed as I write this that when I remove  -Dprism.debug=true from input dump becomes:
java.lang.NullPointerException
	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.NGImageView.renderContent(NGImageView.java:123)
	at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2043)
	at com.sun.javafx.sg.prism.NGImageView.doRender(NGImageView.java:103)
	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.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:324)
	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)

where exception is moved to SWGraphics.java:686:
>>>>686         int data[] = swTex.getDataNoClone();

In both cases first concrete use of texture.
Comments
Attaching a modified version of the submitted test case (JavaFXApplicationImageViewerBug.java): - Dynamically creates the test images on first run. - Uses FitWidth/Height to keep the entire image visible. - Keeps us from requesting too large a window and running into Glass errors. It runs fine on es2 (and probably d3d) with default settings on 8.0 due to our max single texture size, but it fails on SW due to trying to create a single texture of the entire size of the image. Running with "-Dprism.maxvram=500m" succeeds with both es2 and SW.
27-02-2014

Actually, es2 can manage to render the image even if you set a fitwidth/height to show the entire image scaled onto the screen because it only locks one texture at a time in the tiled view and so it may use up to 320mb of vram over the course of a single frame, but it only locks a single tile (of around 2k x 2k = 4mp = 32mb) at a time and it just ends up shuffling the image data through tiles and then reclaiming them later as it fills the texture memory - this may not be efficient, but it succeeds whereas the sw pipeline completely fails trying to do it all with a single huge texture that overflows the maxvram settting. Thus, adding a max tile size to the sw pipeline would make it succeed here, at the potential cost of performance. Note that my MBP puts up the scaled view of the huge image very quickly even with the default maxvram setting so while I wouldn't want to run sprites this large in a fast FPS game, displaying the image statically wouldn't necessarily bog down a more static application. That application would run better with a larger maxvram specified, though.
27-02-2014

(Long story short) Workaround on 8.0 is to run the app with "-Dprism.maxvram=500m"
27-02-2014

With 8.0 we now curb our VRAM usage to a maximum of 256 megabytes. Previously we never kept track and would only run into problems when the system started acting up. If you are running on a system that has lots of VRAM then the program would still work fine, but if you ever ran your app on a smaller system then any number of problems could occur so we imposed a "voluntary" max similar to the way that the java runtime imposes a reasonable "voluntary" max on the heap size, which can be overridden on the command line. A 10kx8k image has 80m pixels, at 32-bits those take 4 bytes each so the vram usage was 320mb which is larger than the default max of 256mb. Raising it up with -Dprism.maxvram=500m allows the test case to run just fine with a 10kx8k image (both es2 and sw pipelines). Note that the test case as written gets Glass errors on Mac due to the stage being too large. I had to add primaryStage.setMaxWidth/Height() calls to limit the stage size to 1000x1000 for it to even run. At that point, the es2 pipeline actually runs just fine with the default maxvram because the maximum texture size on OpenGL causes us to render it with tiles and we only create textures for the visible tiles and so we use a limited amount of vram. In the case of the sw pipeline, though, we try to create the texture as one large tile and it overflows our max vram setting and dies immediately. If the ImageView had a fitwidth/height set that was all visible, then both would get the error since we'd still try to render the entire image on both pipelines. So, there are a couple of issues here: - we now limit maxvram when we did not before (use -Dprism.maxvram and -Dprism.targetvram to control) - sw pipeline does not tile images into manageable textures so very large images can overflow the vram even if we only render a small portion of the image. - the error we throw when we max out the VRAM is not very helpful
27-02-2014

/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package javafxapplicationimageviewerbug; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.stage.FileChooser; import javafx.stage.Stage; /** * * @author Jeff Hilton */ public class JavaFXApplicationImageViewerBug extends Application { @Override public void start(Stage primaryStage) { try { FileChooser chooser = new FileChooser(); File file = chooser.showOpenDialog(primaryStage); if (file != null) { Image image = new Image(new BufferedInputStream( new FileInputStream(file))); //__Add all controls to the main panel that will serve as the root. Group root = new Group(); root.getChildren().add(new ImageView(image)); Scene scene = new Scene(root); primaryStage.setTitle(file.toString()); primaryStage.setScene(scene); primaryStage.show(); } } catch(Exception e) { System.out.println("Exception:"+e.getLocalizedMessage()); } } /** * The main() method is ignored in correctly deployed JavaFX application. * main() serves only as fallback in case the application can not be * launched through deployment artifacts, e.g., in IDEs with limited FX * support. NetBeans ignores main(). * * @param args the command line arguments */ public static void main(String[] args) { launch(args); } }
26-02-2014

Post the new code please.
26-02-2014

I converted the tif images to png and the image reading code to: Image image = new Image(new BufferedInputStream( new FileInputStream(file))); and removed ImageJ calls (Removed the whole loadImage() method and ij includes as well as the jar dependency.) This works for the small image but gets stuck (stalls) on the above line of code for the big image. No exception it just never gets to the next line. Only tried JDK7. Any other suggestion?
26-02-2014

Interesting. This seems like a regression. Can you edit the code to get rid of ImageJ? We don't want that dependency.
26-02-2014

Steve, you misunderstand JDK8 is where the problem is, JDK7 worked. To verify I found JDK1.7.4_40 still on my machine but had to use Netbeans 7.3.1 for JDK7 test and NetBeans 8.0 Beta for JDK 8 test. The results confirm what I said. JDK 7 works JDK 8 does not. Same error at WGraphics.java:686 on JDK 8. (NOTE: you will need latest ImageJ ij.jar for this test to read in tif images.) Here is the simplified code: /* * To change this template, choose Tools | Templates * and open the template in the editor. */ package javafxapplicationimageviewerbug; import ij.ImagePlus; import ij.io.Opener; import ij.process.ColorProcessor; import ij.process.ImageProcessor; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import javafx.application.Application; import javafx.embed.swing.SwingFXUtils; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.image.WritableImage; import javafx.scene.paint.Color; import javafx.stage.FileChooser; import javafx.stage.Stage; /** * */ public class JavaFXApplicationImageViewerBug extends Application { @Override public void start(Stage primaryStage) { try { FileChooser chooser = new FileChooser(); File file = chooser.showOpenDialog(primaryStage); if (file != null) { Image image = loadImageFile(file); //__Add all controls to the main panel that will serve as the root. Group root = new Group(); root.getChildren().add(new ImageView(image)); Scene scene = new Scene(root); scene.setFill(Color.BLACK); //ScenicView.show(scene); primaryStage.setTitle(file.toString()); primaryStage.setScene(scene); primaryStage.show(); } } catch(Exception e) { System.out.println("Exception:"+e.getLocalizedMessage()); } } protected Image loadImageFile(File file) { Image returnValue = null; if (file.exists()) { try { BufferedImage bufferedImage = null; Opener tiffOpener = new Opener(); ImagePlus imagePlus = tiffOpener.openImage(file.getAbsoluteFile().getParent(), file.getName()); ImageProcessor imageProcessor = imagePlus.getChannelProcessor(); if (imageProcessor instanceof ColorProcessor) { ColorProcessor colorProcessor = (ColorProcessor)imageProcessor; /* byte[] channel = colorProcessor.getChannel(_channelToLoad); for (int i = 0; i < channel.length; i++) { channel[i] = (byte)(0x000000ff - channel[i]); } colorProcessor.setRGB(channel, channel, channel); bufferedImage = colorProcessor.getBufferedImage(); */ int channelToLoad = 1; colorProcessor.setWeightingFactors(channelToLoad==1?1.0:0.0,channelToLoad==2?1.0:0.0,channelToLoad==3?1.0:0.0); ImageProcessor byteProcessor = colorProcessor.convertToByte(false); byteProcessor.invertLut(); bufferedImage = byteProcessor.getBufferedImage(); } else { //bufferedImage = imageProcessor.getBufferedImage(); java.awt.Image awtImage = imagePlus.getImage(); bufferedImage = new BufferedImage(awtImage.getWidth(null), awtImage.getHeight(null), BufferedImage.TYPE_INT_ARGB); Graphics2D bGr = bufferedImage.createGraphics(); boolean isDone = bGr.drawImage(awtImage, 0, 0, null); bGr.dispose(); awtImage = null; bGr = null; } tiffOpener = null; imagePlus = null; imageProcessor = null; //WritableImage image = new WritableImage(bufferedImage.getWidth(), bufferedImage.getHeight()); //image = SwingFXUtils.toFXImage(bufferedImage, image); WritableImage image = SwingFXUtils.toFXImage(bufferedImage, null); bufferedImage = null; returnValue = image; } catch (Exception e) { returnValue = null; System.out.println("Exception:"+e.getLocalizedMessage()); } } else { System.out.println("File does not exist:"+file.getPath()); } return returnValue; } /** * The main() method is ignored in correctly deployed JavaFX application. * main() serves only as fallback in case the application can not be * launched through deployment artifacts, e.g., in IDEs with limited FX * support. NetBeans ignores main(). * * @param args the command line arguments */ public static void main(String[] args) { launch(args); } }
26-02-2014

Interesting also that you seem to be running the software graphics pipeline. Not sure this matters. This will happen if JFX detects that your graphics card is unsupported or is missing required features.
26-02-2014

I had assumed the problem was still there in 8. Can you get a jdk8 and try? Further, can you provide a small sample that shows the problem? Thanks. https://wiki.openjdk.java.net/display/OpenJFX/Submitting+a+Bug+Report
26-02-2014

We have other places where this is a problem as well (see RT-22073 which is a bug in snapshot of a large image).
26-02-2014

To be clear, it worked when I developed the program a few weeks ago.
26-02-2014

Note: I developed the program in JavaFx2.2 a 2-3 weeks ago. Switched to Java 8 and deinstalled the "Java 7/JavaFX2.2 SDK" to make room, so I am not sure the version.
26-02-2014

It is likely that this image is simply just too big.
26-02-2014