/*
 * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.scenario.effect.impl.prism;

import com.sun.glass.ui.Screen;
import com.sun.javafx.geom.PickRay;
import com.sun.javafx.geom.Point2D;
import com.sun.javafx.geom.Rectangle;
import com.sun.javafx.geom.Vec3d;
import com.sun.javafx.geom.transform.Affine2D;
import com.sun.javafx.geom.transform.Affine3D;
import com.sun.javafx.geom.transform.BaseTransform;
import com.sun.javafx.geom.transform.NoninvertibleTransformException;
import com.sun.javafx.sg.prism.NGCamera;
import com.sun.javafx.sg.prism.NGPerspectiveCamera;
import com.sun.prism.Graphics;
import com.sun.prism.RenderTarget;
import com.sun.prism.ResourceFactory;
import com.sun.prism.Texture;
import com.sun.scenario.effect.Effect;
import com.sun.scenario.effect.FilterContext;
import com.sun.scenario.effect.ImageData;
import com.sun.scenario.effect.impl.EffectPeer;
import com.sun.scenario.effect.impl.ImagePool;

public class PrEffectHelper {

    
Applies the given filter effect to the series of inputs and then renders the result to the provided Graphics at the specified location. This method is similar to the following pseudo-code:
    g.drawTexture(effect.filter(g.getTransform()), x, y);
except that it is likely to be more efficient (and correct).
Params:
  • effect – the effect to be rendered
  • g – the Graphics to which the Effect will be rendered
  • x – the x location of the filtered result
  • y – the y location of the filtered result
  • defaultInput – the default input Effect to be used if any of the inputs for any of the effects in the chain are unspecified (i.e. null).
/** * Applies the given filter effect to the series of inputs and then renders * the result to the provided {@code Graphics} at the specified * location. * This method is similar to the following pseudo-code: * <pre> * g.drawTexture(effect.filter(g.getTransform()), x, y); * </pre> * except that it is likely to be more efficient (and correct). * * @param effect the effect to be rendered * @param g the {@code Graphics} to which the {@code Effect} will be * rendered * @param x the x location of the filtered result * @param y the y location of the filtered result * @param defaultInput the default input {@code Effect} to be used if * any of the inputs for any of the effects in the * chain are unspecified (i.e. {@code null}). */
public static void render(Effect effect, Graphics g, float x, float y, Effect defaultInput) { BaseTransform transform; Rectangle rclip = getGraphicsClipNoClone(g); BaseTransform origtx = g.getTransformNoClone().copy(); BaseTransform rendertx; if (origtx.is2D()) { // process the effect using the current 2D transform, and then // render the resulting image in device space (i.e., with identity) if (x != 0f || y != 0f || !origtx.isIdentity()) { transform = new Affine2D(origtx); ((Affine2D) transform).translate(x, y); } else { transform = BaseTransform.IDENTITY_TRANSFORM; } g.setTransform(null); rendertx = null; } else { // process the effect with an identity (2D) transform, and then // render the resulting image using the current (3D) modelview // and/or projection transform // RT-27555 // TODO: this will not work if the effect is applied to a Group // that has children with 3D transforms (relative to the Group), // but at least it's good enough for simple effects applied to // leaf nodes (e.g. applying a Reflection to a leaf ImageView node) double scalex = Math.hypot(origtx.getMxx(), origtx.getMyx()); double scaley = Math.hypot(origtx.getMxy(), origtx.getMyy()); double scale = Math.max(scalex, scaley); if (scale <= 1.0) { transform = BaseTransform.IDENTITY_TRANSFORM; rendertx = origtx; } else { transform = BaseTransform.getScaleInstance(scale, scale); rendertx = new Affine3D(origtx); scale = 1.0 / scale; ((Affine3D) rendertx).scale(scale, scale); } NGCamera cam = g.getCameraNoClone(); BaseTransform inv; try { inv = rendertx.createInverse(); } catch (NoninvertibleTransformException e) { return; } PickRay ray = new PickRay(); Vec3d tmpvec = new Vec3d(); // See FilterEffect.untransformClip for a description of // why we round in by half a pixel here. float x1 = rclip.x + 0.5f; float y1 = rclip.y + 0.5f; float x2 = rclip.x + rclip.width - 0.5f; float y2 = rclip.y + rclip.height - 0.5f; double rtw = g.getRenderTarget().getContentWidth(); double rth = g.getRenderTarget().getContentHeight(); Point2D cul = project(x1, y1, rtw, rth, cam, inv, ray, tmpvec, null); Point2D cur = project(x2, y1, rtw, rth, cam, inv, ray, tmpvec, null); Point2D cll = project(x1, y2, rtw, rth, cam, inv, ray, tmpvec, null); Point2D clr = project(x2, y2, rtw, rth, cam, inv, ray, tmpvec, null); rclip = clipbounds(cul, cur, cll, clr); } Screen screen = g.getAssociatedScreen(); FilterContext fctx; // RT-27555 if (screen == null) { ResourceFactory factory = g.getResourceFactory(); fctx = PrFilterContext.getPrinterContext(factory); } else { fctx = PrFilterContext.getInstance(screen); } // TODO: Pass the camera down so that nodes can render with it // for proper perspective below this level. PrRenderInfo prinfo; if (rendertx != null) { // Whatever results are produced will have to be post-transformed // so attempts at direct rendering would use the wrong transform. prinfo = null; } else if (g.isDepthBuffer() && g.isDepthTest()) { // Some of the multi-step operations may produce both flat image // results that would not track the actual Z depth of any direct // Node rendering so we must disable direct rendering to avoid // depth buffer conflicts. prinfo = null; } else { // If none of the above conditions apply, then the PrRenderInfo // can represent all information necessary to directly render // any ImageData or Node to the destination. prinfo = new PrRenderInfo(g); } boolean valid; ImagePool.numEffects++; do { ImageData res = effect.filter(fctx, transform, rclip, prinfo, defaultInput); if (res == null) return; if (valid = res.validate(fctx)) { Rectangle r = res.getUntransformedBounds(); // the actual image may be much larger than the region // of interest ("r"), so to improve performance we render // only that subregion here Texture tex = ((PrTexture)res.getUntransformedImage()).getTextureObject(); g.setTransform(rendertx); g.transform(res.getTransform()); g.drawTexture(tex, r.x, r.y, r.width, r.height); } res.unref(); } while (!valid); g.setTransform(origtx); } static Point2D project(float x, float y, double vw, double vh, NGCamera cam, BaseTransform inv, PickRay tmpray, Vec3d tmpvec, Point2D ret) { // Calculations in cam.computePickRay are done relative to the // view w,h in the camera which may not match our actual view // dimensions so we scale them to that rectangle, compute the // pick rays, then scale the back to the actual device space before // intersecting with our chosen rendering plane. double xscale = cam.getViewWidth() / vw; double yscale = cam.getViewHeight() / vh; x *= xscale; y *= yscale; tmpray = cam.computePickRay(x, y, tmpray); unscale(tmpray.getOriginNoClone(), xscale, yscale); unscale(tmpray.getDirectionNoClone(), xscale, yscale); return tmpray.projectToZeroPlane(inv, cam instanceof NGPerspectiveCamera, tmpvec, ret); } private static void unscale(Vec3d v, double sx, double sy) { v.x /= sx; v.y /= sy; } static Rectangle clipbounds(Point2D cul, Point2D cur, Point2D cll, Point2D clr) { // Note that 3D perspective transforms frequently deal with infinite // values as a plane is rotated towards an end-on view from the eye. // The standard ways of getting the bounds of 4 float points tend to // ignore overflow, but we would frequently see trouble as objects are // flipped over if we didn't have the tests for integer overflow near // the bottom of this method. When those conditions occur it usually // means we can see down an arbitrary distance (perhaps to the horizon) // on the plane of the node being rendered so we need to render it // with no clip to make sure we get all the data for the effect. if (cul != null && cur != null && cll != null && clr != null) { double x1, y1, x2, y2; if (cul.x < cur.x) { x1 = cul.x; x2 = cur.x; } else { x1 = cur.x; x2 = cul.x; } if (cul.y < cur.y) { y1 = cul.y; y2 = cur.y; } else { y1 = cur.y; y2 = cul.y; } if (cll.x < clr.x) { x1 = Math.min(x1, cll.x); x2 = Math.max(x2, clr.x); } else { x1 = Math.min(x1, clr.x); x2 = Math.max(x2, cll.x); } if (cll.y < clr.y) { y1 = Math.min(y1, cll.y); y2 = Math.max(y2, clr.y); } else { y1 = Math.min(y1, clr.y); y2 = Math.max(y2, cll.y); } // See FilterEffect.untransformClip for a description of // why we round out by half a pixel here. x1 = Math.floor(x1-0.5f); y1 = Math.floor(y1-0.5f); x2 = Math.ceil(x2+0.5f)-x1; y2 = Math.ceil(y2+0.5f)-y1; int x = (int) x1; int y = (int) y1; int w = (int) x2; int h = (int) y2; if (x == x1 && y == y1 && w == x2 && h == y2) { // Return a valid rectangle only if we do not overflow, // otherwise let the method return a null below for // unclipped operation. return new Rectangle(x, y, w, h); } } return null; } public static Rectangle getGraphicsClipNoClone(Graphics g) { Rectangle rclip = g.getClipRectNoClone(); if (rclip == null) { RenderTarget rt = g.getRenderTarget(); rclip = new Rectangle(rt.getContentWidth(), rt.getContentHeight()); } return rclip; } public static void renderImageData(Graphics gdst, ImageData srcData, Rectangle dstBounds) { int w = dstBounds.width; int h = dstBounds.height; PrDrawable src = (PrDrawable) srcData.getUntransformedImage(); BaseTransform srcTx = srcData.getTransform(); Rectangle srcBounds = srcData.getUntransformedBounds(); float dx1 = 0f; float dy1 = 0f; float dx2 = dx1 + w; float dy2 = dy1 + h; if (srcTx.isTranslateOrIdentity()) { float tx = (float) srcTx.getMxt(); float ty = (float) srcTx.getMyt(); float sx1 = dstBounds.x - (srcBounds.x + tx); float sy1 = dstBounds.y - (srcBounds.y + ty); float sx2 = sx1 + w; float sy2 = sy1 + h; gdst.drawTexture(src.getTextureObject(), dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2); } else { float[] srcRect = new float[8]; int srcCoords = EffectPeer.getTextureCoordinates(srcRect, srcBounds.x, srcBounds.y, src.getPhysicalWidth(), src.getPhysicalHeight(), dstBounds, srcTx); if (srcCoords < 8) { gdst.drawTextureRaw(src.getTextureObject(), dx1, dy1, dx2, dy2, srcRect[0], srcRect[1], srcRect[2], srcRect[3]); } else { gdst.drawMappedTextureRaw(src.getTextureObject(), dx1, dy1, dx2, dy2, srcRect[0], srcRect[1], srcRect[4], srcRect[5], srcRect[6], srcRect[7], srcRect[2], srcRect[3]); } } } }