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 }