Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8229890

WritableImage update fails for empty region

    Details

      Description

      ADDITIONAL SYSTEM INFORMATION :
      java.runtime.version: 12.0.2+10
      javafx.runtime.version: 13-ea+12

      A DESCRIPTION OF THE PROBLEM :
      The newly introduced WritableImages, which are backed by a java.nio.Buffer,
      fail to update with an exception when the callback returns an empty Rectangle2D.

      java.lang.IllegalArgumentException: srcw (0) and srch (0) must be > 0
      at com.sun.prism.impl.BaseTexture.checkUpdateParams(BaseTexture.java:354)
      at com.sun.prism.es2.ES2Texture.update(ES2Texture.java:640)
      at com.sun.prism.impl.BaseResourceFactory.getCachedTexture(BaseResourceFactory.java:232)
      at com.sun.prism.impl.BaseResourceFactory.getCachedTexture(BaseResourceFactory.java:156)
      at com.sun.javafx.sg.prism.NGImageView.renderContent(NGImageView.java:121)
      at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
      at com.sun.javafx.sg.prism.NGImageView.doRender(NGImageView.java:103)
      at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
      at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270)
      at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:578)
      at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072)
      at com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964)
      at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:479)
      at com.sun.javafx.tk.quantum.ViewPainter.paintImpl(ViewPainter.java:321)
      at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:91)
      at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
      at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
      at com.sun.javafx.tk.RenderJob.run(RenderJob.java:58)
      at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
      at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
      at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:125)
      at java.base/java.lang.Thread.run(Thread.java:835)

      According to the documentation the update of the buffer should happen inside the callback
      of the updateBuffer method of the PixelBuffer. The returned Rectangle2D is supposed to
      indicate which part of the image has become dirty. In practice it may well turn out that
      no pixels have changed during the update and it is thus just natural to indicate this
      by returning an empty Rectangle2D but obviously the implementation does not handle this
      case appropriately. For performance reasons this case should actually be filtered out very
      early in the processing pipeline because in this case no further processing is required.

      I have attached a test program which links an AWT BufferedImage via an IntBuffer to a
      JavaFX WritableImage so that I can draw directly into the BufferedImage via its Graphics2D
      context and the result is then displayed via the linked JavaFX image.

      The program has three buttons. The first one indicates a full update,
      the second one a partial update and the third one an empty update. The third one
      crashes for the above mentioned reason which should not be the case.

      See also: https://github.com/javafxports/openjdk-jfx/issues/567

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Run the attached test program and click on the buttons from left to right.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      1. Full update, 2. partial update ,3. no update and no exception.
      ACTUAL -
      1. Full update, 2. partial update ,3. exception.

      ---------- BEGIN SOURCE ----------
      package de.mpmediasoft.nativefxeval;

      import java.awt.Graphics2D;
      import java.awt.image.BufferedImage;
      import java.awt.image.DataBuffer;
      import java.awt.image.DataBufferInt;
      import java.nio.IntBuffer;

      import javafx.geometry.Rectangle2D;
      import javafx.scene.image.Image;
      import javafx.scene.image.PixelBuffer;
      import javafx.scene.image.PixelFormat;
      import javafx.scene.image.WritableImage;
      import javafx.util.Callback;

      public class AWTImageCanvas {

      private BufferedImage bufImg;
      private Graphics2D g2d;
      private PixelBuffer<IntBuffer> pixelBuffer;
      private WritableImage img;
      private Callback<java.awt.Graphics2D, java.awt.geom.Rectangle2D> updateCallback;

      public AWTImageCanvas(int width, int height) {
      bufImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
      g2d = (Graphics2D) bufImg.getGraphics();

      DataBuffer db = bufImg.getRaster().getDataBuffer();
      DataBufferInt dbi = (DataBufferInt) db;
      int[] raw = dbi.getData();
      IntBuffer ib = IntBuffer.wrap(raw);
      assert raw.length == width * height;

      PixelFormat<IntBuffer> pixelFormat = PixelFormat.getIntArgbPreInstance();
      pixelBuffer = new PixelBuffer<>(bufImg.getWidth(), bufImg.getHeight(), ib, pixelFormat);
      img = new WritableImage(pixelBuffer);
      }

      public Image getImage() {return img;}

      public int getWidth() {return bufImg.getWidth();}

      public int getHeight() {return bufImg.getHeight();}

      public void update() {
      if (updateCallback != null) {
      pixelBuffer.updateBuffer(pb -> {
      final java.awt.geom.Rectangle2D r = updateCallback.call(g2d);
      return (r != null) ? (r.isEmpty() ? Rectangle2D.EMPTY : new Rectangle2D(r.getX(), r.getY(), r.getWidth(), r.getHeight())) : null;
      });
      }
      }

      public void setOnUpdate(Callback<java.awt.Graphics2D, java.awt.geom.Rectangle2D> updateCallback) {
      this.updateCallback = updateCallback;
      }

      }

      package de.mpmediasoft.nativefxeval;

      import java.awt.BasicStroke;
      import java.awt.Color;
      import java.awt.geom.Path2D;

      import javafx.application.Application;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.control.ToolBar;
      import javafx.scene.image.ImageView;
      import javafx.scene.layout.BorderPane;
      import javafx.stage.Stage;

      public class AWTImageCanvasDemo extends Application {

      private AWTImageCanvas canvas = new AWTImageCanvas(800, 600);

      @Override
      public void init() {
      System.out.println("java.runtime.version: " + System.getProperty("java.runtime.version", "(undefined)"));
      System.out.println("javafx.runtime.version: " + System.getProperty("javafx.runtime.version", "(undefined)"));
      }

      Color c1 = Color.red;
      Color c2 = Color.green;
      Color c3 = Color.blue;

      Color c;

      @Override
      public void start(Stage primaryStage) throws Exception {
      Button b1 = new Button("Full (RED)");
      b1.setOnAction(e -> {
      c = c1;
      canvas.update();
      });

      Button b2 = new Button("Partial (GREEN)");
      b2.setOnAction(e -> {
      c = c2;
      canvas.update();
      });

      Button b3 = new Button("Empty (BLUE)");
      b3.setOnAction(e -> {
      c = c3;
      canvas.update();
      });

      ToolBar toolbar = new ToolBar(b1, b2, b3);

      BorderPane root = new BorderPane();
      root.setTop(toolbar);

      root.setCenter(new ImageView(canvas.getImage()));
      Scene scene = new Scene(root);
      primaryStage.setScene(scene);
      primaryStage.show();

      canvas.setOnUpdate(g2d -> {
      // This is pure AWT.

      g2d.setBackground(Color.decode("#F0F0FF"));
      g2d.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());

      Path2D p = new Path2D.Double();
      p.moveTo(100, 100);
      p.lineTo(700, 300);
      p.lineTo(200, 500);
      p.closePath();

      g2d.setColor(c);
      g2d.fill(p);
      g2d.setColor(new Color(50, 100, 150));
      g2d.setStroke(new BasicStroke(10));
      g2d.draw(p);

      if (c == c1) {
      System.out.println("Full update.");
      return null; // Full
      } else if (c == c2) {
      System.out.println("Partial update.");
      return new java.awt.geom.Rectangle2D.Double(0, 0, canvas.getWidth() / 2, canvas.getHeight()); // Partial
      } else {
      System.out.println("Empty update.");
      return new java.awt.geom.Rectangle2D.Double(); // Empty
      // return new java.awt.geom.Rectangle2D.Double(0, 0, 1, 1); // Almost Empty
      }
      });
      }

      public static void main(String[] args) {
      launch(args);
      }

      }

      class AWTImageCanvasDemoLauncher {public static void main(String[] args) {AWTImageCanvasDemo.main(args);}}

      ---------- END SOURCE ----------

      FREQUENCY : always


        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                kcr Kevin Rushforth
                Reporter:
                webbuggrp Webbug Group
              • Votes:
                0 Vote for this issue
                Watchers:
                3 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: