1 /* 2 * Copyright (c) 2008, 2014, 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.RectBounds; 33 import com.sun.javafx.geom.Rectangle; 34 import com.sun.javafx.geom.transform.BaseTransform; 35 import com.sun.scenario.effect.impl.state.RenderState; 36 37 /** 38 * An effect that renders a reflected version of the input below the 39 * actual input content. 40 */ 41 public class Reflection extends CoreEffect { 42 43 private float topOffset; 44 private float topOpacity; 45 private float bottomOpacity; 46 private float fraction; 47 48 /** 49 * Constructs a new {@code Reflection} effect with default values, 50 * using the default input for source data. 51 * This is a shorthand equivalent to: 52 * <pre> 53 * new Reflection(DefaultInput) 54 * </pre> 55 */ 56 public Reflection() { 57 this(DefaultInput); 58 } 59 60 /** 61 * Constructs a new {@code Reflection} effect with default values. 62 * 63 * @param input the single input {@code Effect} 64 */ 65 public Reflection(Effect input) { 66 super(input); 67 this.topOffset = 0f; 68 this.topOpacity = 0.5f; 69 this.bottomOpacity = 0f; 70 this.fraction = 0.75f; 71 updatePeerKey("Reflection"); 72 } 73 74 /** 75 * Returns the input for this {@code Effect}. 76 * 77 * @return the input for this {@code Effect} 78 */ 79 public final Effect getInput() { 80 return getInputs().get(0); 81 } 82 83 /** 84 * Sets the input for this {@code Effect} to a specific 85 * {@code Effect} or to the default input if {@code input} is 86 * {@code null}. 87 * 88 * @param input the input for this {@code Effect} 89 */ 90 public void setInput(Effect input) { 91 setInput(0, input); 92 } 93 94 /** 95 * Returns the top offset adjustment, which is the distance between the 96 * bottom of the input and the top of the reflection. 97 * 98 * @return the top offset adjustment 99 */ 100 public float getTopOffset() { 101 return topOffset; 102 } 103 104 /** 105 * Sets the top offset adjustment, which is the distance between the 106 * bottom of the input and the top of the reflection. 107 * <pre> 108 * Min: n/a 109 * Max: n/a 110 * Default: 0.0 111 * Identity: 0.0 112 * </pre> 113 * 114 * @param topOffset the top offset adjustment 115 */ 116 public void setTopOffset(float topOffset) { 117 float old = this.topOffset; 118 this.topOffset = topOffset; 119 } 120 121 /** 122 * Returns the top opacity value, which is the opacity of the reflection 123 * at its top extreme. 124 * 125 * @return the top opacity value 126 */ 127 public float getTopOpacity() { 128 return topOpacity; 129 } 130 131 /** 132 * Sets the top opacity value, which is the opacity of the reflection 133 * at its top extreme. 134 * <pre> 135 * Min: 0.0 136 * Max: 1.0 137 * Default: 0.5 138 * Identity: 1.0 139 * </pre> 140 * 141 * @param topOpacity the top opacity value 142 * @throws IllegalArgumentException if {@code topOpacity} is outside the 143 * allowable range 144 */ 145 public void setTopOpacity(float topOpacity) { 146 if (topOpacity < 0f || topOpacity > 1f) { 147 throw new IllegalArgumentException("Top opacity must be in the range [0,1]"); 148 } 149 float old = this.topOpacity; 150 this.topOpacity = topOpacity; 151 } 152 153 /** 154 * Returns the bottom opacity value, which is the opacity of the reflection 155 * at its bottom extreme. 156 * 157 * @return the bottom opacity value 158 */ 159 public float getBottomOpacity() { 160 return bottomOpacity; 161 } 162 163 /** 164 * Sets the bottom opacity value, which is the opacity of the reflection 165 * at its bottom extreme. 166 * <pre> 167 * Min: 0.0 168 * Max: 1.0 169 * Default: 0.0 170 * Identity: 1.0 171 * </pre> 172 * 173 * @param bottomOpacity the bottom opacity value 174 * @throws IllegalArgumentException if {@code bottomOpacity} is outside the 175 * allowable range 176 */ 177 public void setBottomOpacity(float bottomOpacity) { 178 if (bottomOpacity < 0f || bottomOpacity > 1f) { 179 throw new IllegalArgumentException("Bottom opacity must be in the range [0,1]"); 180 } 181 float old = this.bottomOpacity; 182 this.bottomOpacity = bottomOpacity; 183 } 184 185 /** 186 * Returns the fraction of the input that is visible in the reflection. 187 * 188 * @return the fraction value 189 */ 190 public float getFraction() { 191 return fraction; 192 } 193 194 /** 195 * Sets the fraction of the input that is visible in the reflection. 196 * For example, a value of 0.5 means that only the bottom half of the 197 * input will be visible in the reflection. 198 * <pre> 199 * Min: 0.0 200 * Max: 1.0 201 * Default: 0.75 202 * Identity: 1.0 203 * </pre> 204 * 205 * @param fraction the fraction of the input that is visible 206 * in the reflection 207 * @throws IllegalArgumentException if {@code fraction} is outside the 208 * allowable range 209 */ 210 public void setFraction(float fraction) { 211 if (fraction < 0f || fraction > 1f) { 212 throw new IllegalArgumentException("Fraction must be in the range [0,1]"); 213 } 214 float old = this.fraction; 215 this.fraction = fraction; 216 } 217 218 @Override 219 public BaseBounds getBounds(BaseTransform transform, 220 Effect defaultInput) 221 { 222 Effect input = getDefaultedInput(0, defaultInput); 223 BaseBounds r = input.getBounds(BaseTransform.IDENTITY_TRANSFORM, defaultInput); 224 r.roundOut(); // NOTE is this really necessary? 225 float x1 = (float) r.getMinX(); 226 float y1 = (float) (r.getMaxY() + topOffset); 227 float x2 = (float) r.getMaxX(); 228 float y2 = (float) (y1 + (fraction * r.getHeight())); 229 BaseBounds ret = new RectBounds(x1, y1, x2, y2); 230 ret = ret.deriveWithUnion(r); 231 return transformBounds(transform, ret); 232 } 233 234 @Override 235 public Point2D transform(Point2D p, Effect defaultInput) { 236 return getDefaultedInput(0, defaultInput).transform(p, defaultInput); 237 } 238 239 @Override 240 public Point2D untransform(Point2D p, Effect defaultInput) { 241 return getDefaultedInput(0, defaultInput).untransform(p, defaultInput); 242 } 243 244 @Override 245 public RenderState getRenderState(FilterContext fctx, 246 BaseTransform transform, 247 Rectangle outputClip, 248 Object renderHelper, 249 Effect defaultInput) 250 { 251 // RT-27405 252 // TODO: We could calculate which parts are needed based on the two 253 // ways that the input is rendered into this ouput rectangle. For now, 254 // we will just use the stock object that requests unclipped inputs. 255 return RenderState.UnclippedUserSpaceRenderState; 256 } 257 258 @Override 259 public boolean reducesOpaquePixels() { 260 final Effect input = getInput(); 261 return input != null && input.reducesOpaquePixels(); 262 } 263 264 @Override 265 public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) { 266 Effect di = getDefaultedInput(0, defaultInput); 267 DirtyRegionContainer drc = di.getDirtyRegions(defaultInput, regionPool); 268 269 BaseBounds contentBounds = di.getBounds(BaseTransform.IDENTITY_TRANSFORM, defaultInput); 270 float cbMaxY = contentBounds.getMaxY(); 271 float reflectedMaxYBase = (2 * cbMaxY) + getTopOffset(); 272 float reflecteCbMaxY = cbMaxY + getTopOffset() + (fraction * contentBounds.getHeight()); 273 DirtyRegionContainer newDRC = regionPool.checkOut(); 274 for (int i = 0; i < drc.size(); i++) { 275 BaseBounds regionBounds = drc.getDirtyRegion(i); 276 float reflectedRegionMinY = reflectedMaxYBase - regionBounds.getMaxY(); 277 float reflectedRegionMaxY = Math.min(reflecteCbMaxY, reflectedRegionMinY + regionBounds.getHeight()); 278 279 newDRC.addDirtyRegion(new RectBounds(regionBounds.getMinX(), 280 reflectedRegionMinY, 281 regionBounds.getMaxX(), 282 reflectedRegionMaxY)); 283 } 284 drc.merge(newDRC); 285 regionPool.checkIn(newDRC); 286 287 return drc; 288 } 289 290 }