1 /*
   2  * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.scenario.effect;
  27 
  28 import com.sun.scenario.effect.impl.state.PerspectiveTransformState;
  29 import com.sun.javafx.geom.Point2D;
  30 import com.sun.javafx.geom.BaseBounds;
  31 import com.sun.javafx.geom.DirtyRegionContainer;
  32 import com.sun.javafx.geom.DirtyRegionPool;
  33 import com.sun.javafx.geom.RectBounds;
  34 import com.sun.javafx.geom.Rectangle;
  35 import com.sun.javafx.geom.transform.BaseTransform;
  36 import com.sun.scenario.effect.impl.state.RenderState;
  37 
  38 public class PerspectiveTransform extends CoreEffect {
  39     private float tx[][] = new float[3][3];
  40     private float ulx, uly, urx, ury, lrx, lry, llx, lly;
  41     private float devcoords[] = new float[8];
  42     private final PerspectiveTransformState state = new PerspectiveTransformState();
  43 
  44     public PerspectiveTransform() {
  45         this(DefaultInput);
  46     }
  47 
  48     public PerspectiveTransform(Effect input) {
  49         super(input);
  50         setQuadMapping(0f, 0f, 100f, 0f, 100f, 100f, 0f, 100f);
  51         updatePeerKey("PerspectiveTransform");
  52     }
  53 
  54     @Override
  55     Object getState() {
  56         return state;
  57     }
  58 
  59     /**
  60      * Returns the input for this {@code Effect}.
  61      *
  62      * @return the input for this {@code Effect}
  63      */
  64     public final Effect getInput() {
  65         return getInputs().get(0);
  66     }
  67 
  68     /**
  69      * Sets the input for this {@code Effect} to a specific
  70      * {@code Effect} or to the default input if {@code input} is
  71      * {@code null}.
  72      *
  73      * @param input the input for this {@code Effect}
  74      */
  75     public void setInput(Effect input) {
  76         setInput(0, input);
  77     }
  78 
  79     /**
  80      * Sets the transform to map the unit square to the indicated
  81      * quadrilateral coordinates.
  82      * The resulting perspective transform will perform the following
  83      * coordinate mappings:
  84      * <pre>
  85      *     T(0, 0) = (ulx, uly)
  86      *     T(0, 1) = (urx, ury)
  87      *     T(1, 1) = (lrx, lry)
  88      *     T(1, 0) = (llx, lly)
  89      * </pre>
  90      * Note that the upper left corner of the unit square {@code (0, 0)}
  91      * is mapped to the coordinates specified by {@code (ulx, uly)} and
  92      * so on around the unit square in a clockwise direction.
  93      *
  94      * @param ulx The X coordinate to which {@code (0, 0)} is mapped.
  95      * @param uly The Y coordinate to which {@code (0, 0)} is mapped.
  96      * @param urx The X coordinate to which {@code (1, 0)} is mapped.
  97      * @param ury The Y coordinate to which {@code (1, 0)} is mapped.
  98      * @param lrx The X coordinate to which {@code (1, 1)} is mapped.
  99      * @param lry The Y coordinate to which {@code (1, 1)} is mapped.
 100      * @param llx The X coordinate to which {@code (0, 1)} is mapped.
 101      * @param lly The Y coordinate to which {@code (0, 1)} is mapped.
 102      */
 103     private void setUnitQuadMapping(float ulx, float uly,
 104                                     float urx, float ury,
 105                                     float lrx, float lry,
 106                                     float llx, float lly)
 107     {
 108         float dx3 = ulx - urx + lrx - llx;
 109         float dy3 = uly - ury + lry - lly;
 110 
 111         tx[2][2] = 1.0F;
 112 
 113         if ((dx3 == 0.0F) && (dy3 == 0.0F)) { // TODO: use tolerance (RT-27402)
 114             tx[0][0] = urx - ulx;
 115             tx[0][1] = lrx - urx;
 116             tx[0][2] = ulx;
 117             tx[1][0] = ury - uly;
 118             tx[1][1] = lry - ury;
 119             tx[1][2] = uly;
 120             tx[2][0] = 0.0F;
 121             tx[2][1] = 0.0F;
 122         } else {
 123             float dx1 = urx - lrx;
 124             float dy1 = ury - lry;
 125             float dx2 = llx - lrx;
 126             float dy2 = lly - lry;
 127 
 128             float invdet = 1.0F/(dx1*dy2 - dx2*dy1);
 129             tx[2][0] = (dx3*dy2 - dx2*dy3)*invdet;
 130             tx[2][1] = (dx1*dy3 - dx3*dy1)*invdet;
 131             tx[0][0] = urx - ulx + tx[2][0]*urx;
 132             tx[0][1] = llx - ulx + tx[2][1]*llx;
 133             tx[0][2] = ulx;
 134             tx[1][0] = ury - uly + tx[2][0]*ury;
 135             tx[1][1] = lly - uly + tx[2][1]*lly;
 136             tx[1][2] = uly;
 137         }
 138         state.updateTx(tx);
 139     }
 140 
 141     public final void setQuadMapping(float ulx, float uly,
 142                                      float urx, float ury,
 143                                      float lrx, float lry,
 144                                      float llx, float lly)
 145     {
 146         this.ulx = ulx;
 147         this.uly = uly;
 148         this.urx = urx;
 149         this.ury = ury;
 150         this.lrx = lrx;
 151         this.lry = lry;
 152         this.llx = llx;
 153         this.lly = lly;
 154     }
 155 
 156     @Override
 157     public RectBounds getBounds(BaseTransform transform,
 158                               Effect defaultInput)
 159     {
 160         setupDevCoords(transform);
 161 
 162         float minx, miny, maxx, maxy;
 163         minx = maxx = devcoords[0];
 164         miny = maxy = devcoords[1];
 165         for (int i = 2; i < devcoords.length; i += 2) {
 166             if (minx > devcoords[i]) minx = devcoords[i];
 167             else if (maxx < devcoords[i]) maxx = devcoords[i];
 168             if (miny > devcoords[i+1]) miny = devcoords[i+1];
 169             else if (maxy < devcoords[i+1]) maxy = devcoords[i+1];
 170         }
 171         return new RectBounds(minx, miny, maxx, maxy);
 172     }
 173 
 174     private void setupDevCoords(BaseTransform transform) {
 175         devcoords[0] = ulx;
 176         devcoords[1] = uly;
 177         devcoords[2] = urx;
 178         devcoords[3] = ury;
 179         devcoords[4] = lrx;
 180         devcoords[5] = lry;
 181         devcoords[6] = llx;
 182         devcoords[7] = lly;
 183         transform.transform(devcoords, 0, devcoords, 0, 4);
 184     }
 185 
 186     @Override
 187     public ImageData filter(FilterContext fctx,
 188                             BaseTransform transform,
 189                             Rectangle outputClip,
 190                             Object renderHelper,
 191                             Effect defaultInput)
 192     {
 193         setupTransforms(transform);
 194         RenderState rstate = getRenderState(fctx, transform, outputClip,
 195                                             renderHelper, defaultInput);
 196         Effect input = getDefaultedInput(0, defaultInput);
 197         Rectangle inputClip = rstate.getInputClip(0, outputClip);
 198         ImageData inputData =
 199             input.filter(fctx, BaseTransform.IDENTITY_TRANSFORM,
 200                          inputClip, null, defaultInput);
 201         if (!inputData.validate(fctx)) {
 202             inputData.unref();
 203             return new ImageData(fctx, null, inputData.getUntransformedBounds());
 204         }
 205         ImageData ret = filterImageDatas(fctx, transform, outputClip, rstate, inputData);
 206         inputData.unref();
 207         return ret;
 208     }
 209 
 210     @Override
 211     public Rectangle getResultBounds(BaseTransform transform,
 212                                      Rectangle outputClip,
 213                                      ImageData... inputDatas)
 214     {
 215         Rectangle ob = new Rectangle(getBounds(transform, null));
 216         ob.intersectWith(outputClip);
 217         return ob;
 218     }
 219 
 220     @Override
 221     public Point2D transform(Point2D p, Effect defaultInput) {
 222         setupTransforms(BaseTransform.IDENTITY_TRANSFORM);
 223         Effect input = getDefaultedInput(0, defaultInput);
 224         p = input.transform(p, defaultInput);
 225         BaseBounds b = input.getBounds(BaseTransform.IDENTITY_TRANSFORM, defaultInput);
 226         float sx = (float) ((p.x - b.getMinX()) / b.getWidth());
 227         float sy = (float) ((p.y - b.getMinY()) / b.getHeight());
 228         float dx = tx[0][0] * sx + tx[0][1] * sy + tx[0][2];
 229         float dy = tx[1][0] * sx + tx[1][1] * sy + tx[1][2];
 230         float dw = tx[2][0] * sx + tx[2][1] * sy + tx[2][2];
 231         p = new Point2D(dx / dw, dy / dw);
 232         return p;
 233     }
 234 
 235     @Override
 236     public Point2D untransform(Point2D p, Effect defaultInput) {
 237         setupTransforms(BaseTransform.IDENTITY_TRANSFORM);
 238         Effect input = getDefaultedInput(0, defaultInput);
 239         float dx = (float) p.x;
 240         float dy = (float) p.y;
 241         float itx[][] = state.getITX();
 242         float sx = itx[0][0] * dx + itx[0][1] * dy + itx[0][2];
 243         float sy = itx[1][0] * dx + itx[1][1] * dy + itx[1][2];
 244         float sw = itx[2][0] * dx + itx[2][1] * dy + itx[2][2];
 245         BaseBounds b = input.getBounds(BaseTransform.IDENTITY_TRANSFORM, defaultInput);
 246         p = new Point2D(b.getMinX() + (sx / sw) * b.getWidth(),
 247                         b.getMinY() + (sy / sw) * b.getHeight());
 248         p = getDefaultedInput(0, defaultInput).untransform(p, defaultInput);
 249         return p;
 250     }
 251 
 252     private void setupTransforms(BaseTransform transform) {
 253         setupDevCoords(transform);
 254         setUnitQuadMapping(devcoords[0], devcoords[1],
 255                            devcoords[2], devcoords[3],
 256                            devcoords[4], devcoords[5],
 257                            devcoords[6], devcoords[7]);
 258     }
 259 
 260     @Override
 261     public RenderState getRenderState(FilterContext fctx,
 262                                       BaseTransform transform,
 263                                       Rectangle outputClip,
 264                                       Object renderHelper,
 265                                       Effect defaultInput)
 266     {
 267         // RT-27402
 268         // TODO: We could inverse map the output bounds through the perspective
 269         // transform to see what portions of the input contribute to the result,
 270         // but until we implement such a process we will just use the stock
 271         // object that specifies no clipping of the inputs.
 272         return RenderState.UnclippedUserSpaceRenderState;
 273     }
 274 
 275     @Override
 276     public boolean reducesOpaquePixels() {
 277         return true;
 278     }
 279 
 280     @Override
 281     public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
 282         DirtyRegionContainer drc = regionPool.checkOut();
 283 
 284         //RT-28197 - Dirty regions could be computed in more efficient way
 285         drc.deriveWithNewRegion((RectBounds) getBounds(BaseTransform.IDENTITY_TRANSFORM, defaultInput));
 286         
 287         return drc;
 288     }
 289 }