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