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.javafx.geom.Point2D; 29 import com.sun.javafx.geom.BaseBounds; 30 import com.sun.javafx.geom.DirtyRegionContainer; 31 import com.sun.javafx.geom.DirtyRegionPool; 32 import com.sun.javafx.geom.Rectangle; 33 import com.sun.javafx.geom.RectBounds; 34 import com.sun.javafx.geom.transform.BaseTransform; 35 import com.sun.scenario.effect.impl.state.RenderState; 36 37 /** 38 * An effect that shifts each pixel according to an (x,y) distance from 39 * the (red,green) channels of a map image, respectively. 40 */ 41 public class DisplacementMap extends CoreEffect { 42 43 private FloatMap mapData; 44 private float scaleX = 1f; 45 private float scaleY = 1f; 46 private float offsetX = 0f; 47 private float offsetY = 0f; 48 private boolean wrap; 49 50 /** 51 * Constructs a new {@code DisplacementMap} effect, 52 * using the default input for source data. 53 * This is a shorthand equivalent to: 54 * <pre> 55 * new DisplacementMap(mapData, DefaultInput) 56 * </pre> 57 * 58 * @throws IllegalArgumentException if {@code mapData} is null 59 */ 60 public DisplacementMap(FloatMap mapData) { 61 this(mapData, DefaultInput); 62 } 63 64 /** 65 * Constructs a new {@code DisplacementMap} effect using the specified 66 * input {@code Effect} for source data, or the default input if 67 * {@code contentInput} is {@code null}. 68 * 69 * @param mapData the map data 70 * @param contentInput the content input {@code Effect} 71 * @throws IllegalArgumentException if {@code mapData} is null 72 */ 73 public DisplacementMap(FloatMap mapData, Effect contentInput) { 74 super(contentInput); 75 setMapData(mapData); 76 updatePeerKey("DisplacementMap"); 77 } 78 79 /** 80 * Returns the map data for this {@code Effect}. 81 * 82 * @return the map data for this {@code Effect} 83 */ 84 public final FloatMap getMapData() { 85 return mapData; 86 } 87 88 /** 89 * Sets the map data for this {@code Effect}. 90 * 91 * @param mapData the map data for this {@code Effect} 92 * @throws IllegalArgumentException if {@code mapData} is null 93 */ 94 public void setMapData(FloatMap mapData) { 95 if (mapData == null) { 96 throw new IllegalArgumentException("Map data must be non-null"); 97 } 98 FloatMap old = this.mapData; 99 this.mapData = mapData; 100 } 101 102 /** 103 * Returns the content input for this {@code Effect}. 104 * 105 * @return the content input for this {@code Effect} 106 */ 107 public final Effect getContentInput() { 108 return getInputs().get(0); 109 } 110 111 /** 112 * Sets the content input for this {@code Effect} to a specific 113 * {@code Effect} or to the default input if {@code input} is 114 * {@code null}. 115 * 116 * @param contentInput the content input for this {@code Effect} 117 */ 118 public void setContentInput(Effect contentInput) { 119 setInput(0, contentInput); 120 } 121 122 /** 123 * Returns the x scale factor. 124 * 125 * @return the x scale factor 126 */ 127 public float getScaleX() { 128 return scaleX; 129 } 130 131 /** 132 * Sets the x scale factor. 133 * <pre> 134 * Min: n/a 135 * Max: n/a 136 * Default: 1.0 137 * Identity: 1.0 138 * </pre> 139 * 140 * @param scaleX the x scale factor 141 */ 142 public void setScaleX(float scaleX) { 143 float old = this.scaleX; 144 this.scaleX = scaleX; 145 } 146 147 /** 148 * Returns the y scale factor. 149 * 150 * @return the y scale factor 151 */ 152 public float getScaleY() { 153 return scaleY; 154 } 155 156 /** 157 * Sets the y scale factor. 158 * <pre> 159 * Min: n/a 160 * Max: n/a 161 * Default: 1.0 162 * Identity: 1.0 163 * </pre> 164 * 165 * @param scaleY the y scale factor 166 */ 167 public void setScaleY(float scaleY) { 168 float old = this.scaleY; 169 this.scaleY = scaleY; 170 } 171 172 /** 173 * Returns the x offset factor. 174 * 175 * @return the x offset factor 176 */ 177 public float getOffsetX() { 178 return offsetX; 179 } 180 181 /** 182 * Sets the x offset factor. 183 * <pre> 184 * Min: n/a 185 * Max: n/a 186 * Default: -0.5 187 * Identity: 0.0 188 * </pre> 189 * 190 * @param offsetX the x offset factor 191 */ 192 public void setOffsetX(float offsetX) { 193 float old = this.offsetX; 194 this.offsetX = offsetX; 195 } 196 197 /** 198 * Returns the y offset factor. 199 * 200 * @return the y offset factor 201 */ 202 public float getOffsetY() { 203 return offsetY; 204 } 205 206 /** 207 * Sets the y offset factor. 208 * <pre> 209 * Min: n/a 210 * Max: n/a 211 * Default: -0.5 212 * Identity: 0.0 213 * </pre> 214 * 215 * @param offsetY the y offset factor 216 */ 217 public void setOffsetY(float offsetY) { 218 float old = this.offsetY; 219 this.offsetY = offsetY; 220 } 221 222 /** 223 * Returns whether values taken from outside the edges of the map 224 * "wrap around" or not. 225 * 226 * @return true if wrapping is enabled, false otherwise 227 */ 228 public boolean getWrap() { 229 return this.wrap; 230 } 231 232 /** 233 * Sets whether values taken from outside the edges of the map 234 * "wrap around" or not. 235 * <pre> 236 * Min: n/a 237 * Max: n/a 238 * Default: false 239 * Identity: n/a 240 * </pre> 241 * 242 * @param wrap true if wrapping is enabled, false otherwise 243 */ 244 public void setWrap(boolean wrap) { 245 boolean old = this.wrap; 246 this.wrap = wrap; 247 } 248 249 /** 250 * Transform the specified point {@code p} from the coordinate space 251 * of the primary content input to the coordinate space of the effect 252 * output. 253 * In essence, this method asks the question "Which output coordinate 254 * is most affected by the data at the specified coordinate in the 255 * primary source input?" 256 * <p> 257 * Since the displacement map represents a mapping from output pixels 258 * to relative source pixels, there may be multiple answers or no 259 * answer for any given input coordinate so this method returns 260 * {@code (NaN, NaN)}. 261 * 262 * @param p the point in the coordinate space of the primary content 263 * input to be transformed 264 * @param defaultInput the default input {@code Effect} to be used in 265 * all cases where a filter has a null input 266 * @return the undefined point {@code (NaN, NaN)} 267 */ 268 @Override 269 public Point2D transform(Point2D p, Effect defaultInput) { 270 return new Point2D(Float.NaN, Float.NaN); 271 } 272 273 /** 274 * Transform the specified point {@code p} from the coordinate space 275 * of the output of the effect into the coordinate space of the 276 * primary content input. 277 * In essence, this method asks the question "Which source coordinate 278 * contributes most to the definition of the output at the specified 279 * coordinate?" 280 * <p> 281 * This method returns the floating point coordinate where the 282 * backwards mapping displacement map tells it to get the data for 283 * the specified coordinate. 284 * 285 * @param p the point in the coordinate space of the result output 286 * to be transformed 287 * @param defaultInput the default input {@code Effect} to be used in 288 * all cases where a filter has a null input 289 * @return the untransformed point in the coordinate space of the 290 * primary content input 291 */ 292 @Override 293 public Point2D untransform(Point2D p, Effect defaultInput) { 294 BaseBounds r = getBounds(BaseTransform.IDENTITY_TRANSFORM, defaultInput); 295 float rw = (float) r.getWidth(); 296 float rh = (float) r.getHeight(); 297 float x = (float) ((p.x - r.getMinX()) / rw); 298 float y = (float) ((p.y - r.getMinY()) / rh); 299 // If the coordinates are outside of the effect there is no 300 // displacement effect occuring so we do not transform the point. 301 if (x >= 0 && y >= 0 && x < 1 && y < 1) { 302 int mx = (int) (x * mapData.getWidth()); 303 int my = (int) (y * mapData.getHeight()); 304 float dx = mapData.getSample(mx, my, 0); 305 float dy = mapData.getSample(mx, my, 1); 306 x += scaleX * (dx + offsetX); 307 y += scaleY * (dy + offsetY); 308 if (wrap) { 309 x -= Math.floor(x); 310 y -= Math.floor(y); 311 } 312 p = new Point2D(x * rw + r.getMinX(), 313 y * rh + r.getMinY()); 314 } 315 return getDefaultedInput(0, defaultInput).untransform(p, defaultInput); 316 } 317 318 @Override 319 public ImageData filterImageDatas(FilterContext fctx, 320 BaseTransform transform, 321 Rectangle outputClip, 322 RenderState rstate, 323 ImageData... inputs) 324 { 325 // NOTE: The floatmap is mapped to the entire output bounds so 326 // we need to use unclipped math in the peers to get the 327 // texture mapping right. 328 return super.filterImageDatas(fctx, transform, null, rstate, inputs); 329 } 330 @Override 331 public RenderState getRenderState(FilterContext fctx, 332 BaseTransform transform, 333 Rectangle outputClip, 334 Object renderHelper, 335 Effect defaultInput) 336 { 337 // NOTE: We could scan the map and calculate all possible input points 338 // that might contribute to the clipped output? Until then, any 339 // pixel in the output could come from any pixel in the input so 340 // we will need the full input bounds to proceed... 341 return RenderState.UnclippedUserSpaceRenderState; 342 } 343 344 @Override 345 public boolean reducesOpaquePixels() { 346 return true; 347 } 348 349 @Override 350 public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) { 351 DirtyRegionContainer drc = regionPool.checkOut(); 352 drc.deriveWithNewRegion((RectBounds) getBounds(BaseTransform.IDENTITY_TRANSFORM, defaultInput)); 353 354 return drc; 355 } 356 }