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 }