changeset 1108:5843e77aab79

RT-21570: Need to update snapshot feature to use new Image Ops API
author kcr
date Tue, 22 May 2012 10:31:07 -0700
parents 49481ac33509
children 969b2929178a
files javafx-ui-common/src/com/sun/javafx/tk/Toolkit.java javafx-ui-common/src/javafx/scene/Node.java javafx-ui-common/src/javafx/scene/Scene.java javafx-ui-common/src/javafx/scene/SnapshotResult.java javafx-ui-common/src/javafx/scene/image/Image.java javafx-ui-common/src/javafx/scene/image/WritableImage.java
diffstat 6 files changed, 129 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/javafx-ui-common/src/com/sun/javafx/tk/Toolkit.java	Tue May 22 10:00:37 2012 -0700
+++ b/javafx-ui-common/src/com/sun/javafx/tk/Toolkit.java	Tue May 22 10:31:07 2012 -0700
@@ -44,6 +44,7 @@
 import javafx.scene.Scene;
 import javafx.scene.effect.BlurType;
 import javafx.scene.image.Image;
+import javafx.scene.image.WritableImage;
 import javafx.scene.input.DragEvent;
 import javafx.scene.input.Dragboard;
 import javafx.scene.input.InputMethodEvent;
@@ -922,4 +923,19 @@
         sceneAccessor = accessor;
     }
 
+    public interface WritableImageAccessor {
+        public void loadTkImage(WritableImage wimg, Object loader);
+        public Object getTkImageLoader(WritableImage wimg);
+    }
+
+    private static WritableImageAccessor writableImageAccessor = null;
+
+    public static void setWritableImageAccessor(WritableImageAccessor accessor) {
+        writableImageAccessor = accessor;
+    }
+
+    public static WritableImageAccessor getWritableImageAccessor() {
+        return writableImageAccessor;
+    }
+
 }
--- a/javafx-ui-common/src/javafx/scene/Node.java	Tue May 22 10:00:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/Node.java	Tue May 22 10:31:07 2012 -0700
@@ -99,7 +99,7 @@
 import javafx.beans.property.*;
 import javafx.beans.value.WritableValue;
 import javafx.geometry.Rectangle2D;
-import javafx.scene.image.Image;
+import javafx.scene.image.WritableImage;
 import javafx.scene.input.SwipeEvent;
 import javafx.scene.input.TouchEvent;
 import javafx.scene.text.Font;
@@ -1628,7 +1628,7 @@
         Scene.impl_setAllowPGAccess(false);
     }
 
-    private Object doSnapshot(SnapshotParameters params, Object platformImage) {
+    private WritableImage doSnapshot(SnapshotParameters params, WritableImage img) {
         if (getScene() != null) {
             getScene().doCSSLayoutSyncForSnapshot(this);
         } else {
@@ -1662,9 +1662,9 @@
             w = tempBounds.getWidth();
             h = tempBounds.getHeight();
         }
-        Object result = Scene.doSnapshot(getScene(), x, y, w, h,
+        WritableImage result = Scene.doSnapshot(getScene(), x, y, w, h,
                 this, transform, params.isDepthBuffer(),
-                params.getFill(), params.getCamera(), platformImage);
+                params.getFill(), params.getCamera(), img);
 
         return result;
     }
@@ -1680,21 +1680,19 @@
      * then the Scene's attributes will be used if this node is part of a scene,
      * or default attributes will be used if this node is not part of a scene.
      *
-     * @param image the image that will be used to hold the rendered node.
-     * It may be null in which case a new Image will be constructed.
+     * @param image the writable image that will be used to hold the rendered node.
+     * It may be null in which case a new WritableImage will be constructed.
      * If the image is non-null, the node will be rendered into the
      * existing image.
-     * If the image is larger than the bounds of the node, the area outside
-     * the bounds will be filled with the fill color specified in the
-     * snapshot parameters. If the image is smaller than the bounds,
-     * the rendered image will be clipped.
+     * In this case, the width and height of the image determine the area
+     * that is rendered instead of the width and height of the Node's bounds.
      *
      * @throws IllegalStateException if this method is called on a thread
      *     other than the JavaFX Application Thread.
      *
      * @return the rendered image
      */
-    public Image snapshot(SnapshotParameters params, Image image) {
+    public WritableImage snapshot(SnapshotParameters params, WritableImage image) {
         Toolkit.getToolkit().checkFxUserThread();
 
         if (params == null) {
@@ -1707,16 +1705,7 @@
             }
         }
 
-        // TODO: Ignore image for now. In order to support it, we either need
-        // ImageOps support or we need a private method to mutate the existing
-        // platform image in an image object.
-        if (image != null) {
-            System.err.println("WARNING: Scene.snapshot: image currently ignored");
-        }
-
-        Object platformImage = doSnapshot(params, null);
-        Image theImage = Image.impl_fromPlatformImage(platformImage);
-        return theImage;
+        return doSnapshot(params, image);
     }
 
     /**
@@ -1741,21 +1730,19 @@
      * then the Scene's attributes will be used if this node is part of a scene,
      * or default attributes will be used if this node is not part of a scene.
      *
-     * @param image the image that will be used to hold the rendered node.
-     * It may be null in which case a new Image will be constructed.
+     * @param image the writable image that will be used to hold the rendered node.
+     * It may be null in which case a new WritableImage will be constructed.
      * If the image is non-null, the node will be rendered into the
      * existing image.
-     * If the image is larger than the bounds of the node, the area outside
-     * the bounds will be filled with the fill color specified in the
-     * snapshot parameters. If the image is smaller than the bounds,
-     * the rendered image will be clipped.
+     * In this case, the width and height of the image determine the area
+     * that is rendered instead of the width and height of the Node's bounds.
      *
      * @throws IllegalStateException if this method is called on a thread
      *     other than the JavaFX Application Thread.
      *
      */
     public void snapshot(Callback<SnapshotResult, Void> callback,
-            SnapshotParameters params, Image image) {
+            SnapshotParameters params, WritableImage image) {
 
         Toolkit.getToolkit().checkFxUserThread();
 
@@ -1771,24 +1758,17 @@
             params = params.copy();
         }
 
-        // TODO: Ignore image for now. In order to support it, we either need
-        // ImageOps support or we need a private method to mutate the existing
-        // platform image in an image object.
-        if (image != null) {
-            System.err.println("WARNING: Scene.snapshot: image currently ignored");
-        }
-
         final SnapshotParameters theParams = params;
         final Callback<SnapshotResult, Void> theCallback = callback;
+        final WritableImage theImage = image;
 
         // Create a deferred runnable that will be run from a pulse listener
         // that is called after all of the scenes have been synced but before
         // any of them have been rendered.
         final Runnable snapshotRunnable = new Runnable() {
             @Override public void run() {
-                Object platformImage = doSnapshot(theParams, null);
-                Image theImage = Image.impl_fromPlatformImage(platformImage);
-                SnapshotResult result = new SnapshotResult(theImage, Node.this, theParams);
+                WritableImage img = doSnapshot(theParams, theImage);
+                SnapshotResult result = new SnapshotResult(img, Node.this, theParams);
 //                System.err.println("Calling snapshot callback");
                 try {
                     Void v = theCallback.call(result);
--- a/javafx-ui-common/src/javafx/scene/Scene.java	Tue May 22 10:00:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/Scene.java	Tue May 22 10:31:07 2012 -0700
@@ -110,7 +110,7 @@
 import javafx.beans.property.ReadOnlyDoubleWrapper;
 import javafx.beans.property.ReadOnlyObjectProperty;
 import javafx.beans.property.ReadOnlyObjectWrapper;
-import javafx.scene.image.Image;
+import javafx.scene.image.WritableImage;
 import javafx.scene.input.GestureEvent;
 import javafx.scene.input.MouseDragEvent;
 import javafx.scene.input.RotateEvent;
@@ -1017,7 +1017,7 @@
     }
 
     /**
-     * This method is superseded by {@link #snapshot(javafx.scene.image.Image)}
+     * This method is superseded by {@link #snapshot(javafx.scene.image.WritableImage)}
      *
      * WARNING: This method is not part of the public API and is
      * subject to change!  It is intended for use by the designer tool only.
@@ -1030,8 +1030,7 @@
     // to new 2.2 public API before we remove this.
     @Deprecated
     public Object renderToImage(Object platformImage) {
-        Toolkit.getToolkit().checkFxUserThread();
-        return doSnapshot(platformImage, 1.0f);
+        return renderToImage(platformImage, 1.0f);
     }
 
     /**
@@ -1052,7 +1051,11 @@
     @Deprecated
     public Object renderToImage(Object platformImage, float scale) {
         Toolkit.getToolkit().checkFxUserThread();
-        return doSnapshot(platformImage, scale);
+        // NOTE: that we no longer use the passed in platform image. Since this
+        // API is deprecated and will be removed in 3.0 this is not a concern.
+        // Also, we used to return a TK image loader and now we return
+        // the actual TK PlatformImage.
+        return doSnapshot(null, scale).impl_getPlatformImage();
     }
 
     private void doLayoutPassWithoutPulse(int maxAttempts) {
@@ -1089,20 +1092,32 @@
 
     // Shared method for Scene.snapshot and Node.snapshot. It is static because
     // we might be doing a Node snapshot with a null scene
-    // TODO: modify to take an Image rather than platformImage
-    static Object doSnapshot(Scene scene,
+    static WritableImage doSnapshot(Scene scene,
             double x, double y, double w, double h,
             Node root, BaseTransform transform, boolean depthBuffer,
-            Paint fill, Camera camera, Object platformImage) {
+            Paint fill, Camera camera, WritableImage wimg) {
 
         Toolkit tk = Toolkit.getToolkit();
         Toolkit.ImageRenderingContext context = new Toolkit.ImageRenderingContext();
 
+        int xMin = (int)Math.floor(x);
+        int yMin = (int)Math.floor(y);
+        int xMax = (int)Math.ceil(x + w);
+        int yMax = (int)Math.ceil(y + h);
+        int width = xMax - xMin;
+        int height = yMax - yMin;
+        if (wimg == null) {
+            wimg = new WritableImage(width, height);
+        } else {
+            width = (int)wimg.getWidth();
+            height = (int)wimg.getHeight();
+        }
+
         impl_setAllowPGAccess(true);
-        context.x = (int)Math.floor(x);
-        context.y = (int)Math.floor(y);
-        context.width = (int)Math.ceil(w);
-        context.height = (int)Math.ceil(h);
+        context.x = xMin;
+        context.y = xMin;
+        context.width = width;
+        context.height = height;
         context.transform = transform;
         context.depthBuffer = depthBuffer;
         context.root = root.impl_getPGNode();
@@ -1113,9 +1128,12 @@
         } else {
             context.camera = null;
         }
-        context.platformImage = platformImage;
+
+        Toolkit.WritableImageAccessor accessor = Toolkit.getWritableImageAccessor();
+        context.platformImage = accessor.getTkImageLoader(wimg);
         impl_setAllowPGAccess(false);
-        Object result = tk.renderToImage(context);
+        Object tkImage = tk.renderToImage(context);
+        accessor.loadTkImage(wimg, tkImage);
 
         // if this scene belongs to some stage
         // we need to mark the entire scene as dirty
@@ -1124,14 +1142,13 @@
             scene.setNeedsRepaint();
         }
 
-        return result;
+        return wimg;
     }
 
     /**
      * Implementation method for snapshot
      */
-    // TODO: modify to take an Image rather than platformImage
-    private Object doSnapshot(Object platformImage, float scale) {
+    private WritableImage doSnapshot(WritableImage img, float scale) {
         // TODO: no need to do CSS, layout or sync in the deferred case,
         // if this scene is attached to a visible stage
         doCSSLayoutSyncForSnapshot(getRoot());
@@ -1149,7 +1166,7 @@
 
         return doSnapshot(this, 0, 0, w, h,
                 getRoot(), transform, isDepthBuffer(),
-                getFill(), getCamera(), platformImage);
+                getFill(), getCamera(), img);
     }
 
     // Pulse listener used to run all deferred (async) snapshot requests
@@ -1203,32 +1220,21 @@
      * CSS and layout processing will be done for the scene prior to
      * rendering it.
      *
-     * @param image the image that will be used to hold the rendered scene.
-     * It may be null in which case a new Image will be constructed.
+     * @param image the writable image that will be used to hold the rendered scene.
+     * It may be null in which case a new WritableImage will be constructed.
      * If the image is non-null, the scene will be rendered into the
      * existing image.
-     * If the image is larger than the scene, the area outside the bounds
-     * of the scene will be filled with the Scene fill color. If the image is
-     * smaller than the scene, the rendered image will be clipped.
+     * In this case, the width and height of the image determine the area
+     * that is rendered instead of the width and height of the scene.
      *
      * @throws IllegalStateException if this method is called on a thread
      *     other than the JavaFX Application Thread.
      *
      * @return the rendered image
      */
-    public Image snapshot(Image image) {
+    public WritableImage snapshot(WritableImage image) {
         Toolkit.getToolkit().checkFxUserThread();
-
-        // TODO: Ignore image for now. In order to support it, we either need
-        // ImageOps support or we need a private method to mutate the existing
-        // platform image in an image object.
-        if (image != null) {
-            System.err.println("WARNING: Scene.snapshot: image currently ignored");
-        }
-
-        Object platformImage = doSnapshot(null, 1.0f);
-        Image theImage = Image.impl_fromPlatformImage(platformImage);
-        return theImage;
+        return  doSnapshot(image, 1.0f);
     }
 
     /**
@@ -1246,37 +1252,30 @@
      * the callback will contain the rendered image and the source scene
      * that was rendered.
      *
-     * @param image the image that will be used to hold the rendered scene.
-     * It may be null in which case a new Image will be constructed.
+     * @param image the writable image that will be used to hold the rendered scene.
+     * It may be null in which case a new WritableImage will be constructed.
      * If the image is non-null, the scene will be rendered into the
      * existing image.
-     * If the image is larger than the scene, the area outside the bounds
-     * of the scene will be filled with the Scene fill color. If the image is
-     * smaller than the scene, the rendered image will be clipped.
+     * In this case, the width and height of the image determine the area
+     * that is rendered instead of the width and height of the scene.
      *
      * @throws IllegalStateException if this method is called on a thread
      *     other than the JavaFX Application Thread.
      */
-    public void snapshot(Callback<SnapshotResult, Void> callback, Image image) {
+    public void snapshot(Callback<SnapshotResult, Void> callback, WritableImage image) {
         Toolkit.getToolkit().checkFxUserThread();
 
-        // TODO: Ignore image for now. In order to support it, we either need
-        // ImageOps support or we need a private method to mutate the existing
-        // platform image in an image object.
-        if (image != null) {
-            System.err.println("WARNING: Scene.snapshot: image currently ignored");
-        }
+        final Callback<SnapshotResult, Void> theCallback = callback;
+        final WritableImage theImage = image;
 
         // Create a deferred runnable that will be run from a pulse listener
         // that is called after all of the scenes have been synced but before
         // any of them have been rendered.
-        final Callback<SnapshotResult, Void> theCallback = callback;
         final Runnable snapshotRunnable = new Runnable() {
             @Override public void run() {
-                Object platformImage = doSnapshot(null, 1.0f);
-                Image theImage = Image.impl_fromPlatformImage(platformImage);
+                WritableImage img = doSnapshot(theImage, 1.0f);
 //                System.err.println("Calling snapshot callback");
-                SnapshotResult result = new SnapshotResult(theImage, Scene.this, null);
+                SnapshotResult result = new SnapshotResult(img, Scene.this, null);
                 try {
                     Void v = theCallback.call(result);
                 } catch (Throwable th) {
--- a/javafx-ui-common/src/javafx/scene/SnapshotResult.java	Tue May 22 10:00:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/SnapshotResult.java	Tue May 22 10:31:07 2012 -0700
@@ -24,18 +24,18 @@
  */
 package javafx.scene;
 
-import javafx.scene.image.Image;
+import javafx.scene.image.WritableImage;
 
 /**
  * This class holds the result of a snapshot operation.
  */
 public class SnapshotResult {
-    private Image image;
+    private WritableImage image;
     private Object source;
     private SnapshotParameters params;
 
     // Package scope constructor
-    SnapshotResult(Image image, Object source, SnapshotParameters params) {
+    SnapshotResult(WritableImage image, Object source, SnapshotParameters params) {
         this.image = image;
         this.source = source;
         this.params = params;
@@ -46,7 +46,7 @@
      *
      * @return the generated image
      */
-    public Image getImage() {
+    public WritableImage getImage() {
         return image;
     }
 
--- a/javafx-ui-common/src/javafx/scene/image/Image.java	Tue May 22 10:00:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/image/Image.java	Tue May 22 10:31:07 2012 -0700
@@ -867,6 +867,10 @@
         heightPropertyImpl().store(height);
     }
 
+    void setPlatformImage(PlatformImage newPlatformImage) {
+        platformImage.set(newPlatformImage);
+    }
+
     private static final int MAX_RUNNING_TASKS = 4;
     private static int runningTasks = 0;
     private static final Queue<ImageTask> pendingTasks =
--- a/javafx-ui-common/src/javafx/scene/image/WritableImage.java	Tue May 22 10:00:37 2012 -0700
+++ b/javafx-ui-common/src/javafx/scene/image/WritableImage.java	Tue May 22 10:31:07 2012 -0700
@@ -25,7 +25,9 @@
 
 package javafx.scene.image;
 
+import com.sun.javafx.tk.ImageLoader;
 import com.sun.javafx.tk.PlatformImage;
+import com.sun.javafx.tk.Toolkit;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.IntBuffer;
@@ -39,6 +41,21 @@
  * images read from a file or URL.
  */
 public class WritableImage extends Image {
+
+    static {
+        Toolkit.setWritableImageAccessor(new Toolkit.WritableImageAccessor() {
+            @Override public void loadTkImage(WritableImage wimg, Object loader) {
+                wimg.loadTkImage(loader);
+            }
+
+            @Override public Object getTkImageLoader(WritableImage wimg) {
+                return wimg.getTkImageLoader();
+            }
+        });
+    }
+
+    private ImageLoader tkImageLoader;
+
     /**
      * Construct an empty image of the specified dimensions.
      * The image will initially be filled with transparent pixels.
@@ -212,4 +229,24 @@
         }
         return writer;
     }
+
+    private void loadTkImage(Object loader) {
+        if (!(loader instanceof ImageLoader)) {
+            throw new IllegalArgumentException("Unrecognized image loader: "
+                    + loader);
+        }
+        ImageLoader tkLoader = (ImageLoader)loader;
+        if (tkLoader.getWidth() != (int)this.getWidth()
+                || tkLoader.getHeight() != (int)this.getHeight())
+        {
+            throw new IllegalArgumentException("Size of loader does not match size of image");
+        }
+
+        super.setPlatformImage(tkLoader.getFrame(0));
+        this.tkImageLoader = tkLoader;
+    }
+
+    private Object getTkImageLoader() {
+        return tkImageLoader;
+    }
 }