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 }