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.BaseBounds;
  29 import com.sun.javafx.geom.DirtyRegionContainer;
  30 import com.sun.javafx.geom.DirtyRegionPool;
  31 import com.sun.javafx.geom.RectBounds;
  32 import com.sun.javafx.geom.Rectangle;
  33 import com.sun.javafx.geom.transform.BaseTransform;
  34 import com.sun.scenario.effect.impl.Renderer;
  35 import com.sun.scenario.effect.impl.state.GaussianShadowState;
  36 
  37 /**
  38  * A blurred shadow effect using a Gaussian convolution kernel, with a
  39  * configurable radius and shadow color.  Only the alpha channel of the
  40  * input is used to create the shadow effect.  The alpha value of each
  41  * pixel from the result of the blur operation is modulated with the
  42  * specified shadow color to produce the resulting image.
  43  */
  44 public class GaussianShadow extends AbstractShadow {
  45 
  46     private GaussianShadowState state = new GaussianShadowState();
  47 
  48     /**
  49      * Constructs a new {@code GaussianShadow} effect with the default radius
  50      * (10.0) and the default color ({@code Color4f.BLACK}), using the
  51      * default input for source data.
  52      * This is a shorthand equivalent to:
  53      * <pre>
  54      *     new GaussianShadow(10f, Color4f.BLACK, DefaultInput)
  55      * </pre>
  56      */
  57     public GaussianShadow() {
  58         this(10f);
  59     }
  60 
  61     /**
  62      * Constructs a new {@code GaussianShadow} effect with the given radius
  63      * and the default color ({@code Color4f.BLACK}), using the
  64      * default input for source data.
  65      * This is a shorthand equivalent to:
  66      * <pre>
  67      *     new GaussianShadow(radius, Color4f.BLACK, DefaultInput)
  68      * </pre>
  69      *
  70      * @param radius the radius of the Gaussian kernel
  71      * @throws IllegalArgumentException if {@code radius} is outside the
  72      * allowable range
  73      */
  74     public GaussianShadow(float radius) {
  75         this(radius, Color4f.BLACK);
  76     }
  77 
  78     /**
  79      * Constructs a new {@code GaussianShadow} effect with the given radius
  80      * and color, using the default input for source data.
  81      * This is a shorthand equivalent to:
  82      * <pre>
  83      *     new GaussianShadow(radius, color, DefaultInput)
  84      * </pre>
  85      *
  86      * @param radius the radius of the Gaussian kernel
  87      * @param color the shadow {@code Color4f}
  88      * @throws IllegalArgumentException if {@code radius} is outside the
  89      * allowable range
  90      */
  91     public GaussianShadow(float radius, Color4f color) {
  92         this(radius, color, DefaultInput);
  93     }
  94 
  95     /**
  96      * Constructs a new {@code GaussianShadow} effect with the given
  97      * radius and color.
  98      *
  99      * @param radius the radius of the Gaussian kernel
 100      * @param color the shadow {@code Color4f}
 101      * @param input the single input {@code Effect}
 102      * @throws IllegalArgumentException if {@code radius} is outside the
 103      * allowable range, or if {@code color} is null
 104      */
 105     public GaussianShadow(float radius, Color4f color, Effect input) {
 106         super(input);
 107         state.setRadius(radius);
 108         state.setShadowColor(color);
 109     }
 110 
 111 
 112     @Override
 113     Object getState() {
 114         return state;
 115     }
 116 
 117     @Override
 118     public AccelType getAccelType(FilterContext fctx) {
 119         return Renderer.getRenderer(fctx).getAccelType();
 120     }
 121 
 122     /**
 123      * Returns the input for this {@code Effect}.
 124      *
 125      * @return the input for this {@code Effect}
 126      */
 127     public final Effect getInput() {
 128         return getInputs().get(0);
 129     }
 130 
 131     /**
 132      * Sets the input for this {@code Effect} to a specific {@code Effect}
 133      * or to the default input if {@code input} is {@code null}.
 134      *
 135      * @param input the input for this {@code Effect}
 136      */
 137     public void setInput(Effect input) {
 138         setInput(0, input);
 139     }
 140 
 141     /**
 142      * Returns the radius of the Gaussian kernel.
 143      *
 144      * @return the radius of the Gaussian kernel
 145      */
 146     public float getRadius() {
 147         return state.getRadius();
 148     }
 149 
 150     /**
 151      * Sets the radius of the Gaussian kernel.
 152      * <pre>
 153      *       Min:   0.0
 154      *       Max: 127.0
 155      *   Default:  10.0
 156      *  Identity:   0.0
 157      * </pre>
 158      *
 159      * @param radius the radius of the Gaussian kernel
 160      * @throws IllegalArgumentException if {@code radius} is outside the
 161      * allowable range
 162      */
 163     public void setRadius(float radius) {
 164         float old = state.getRadius();
 165         state.setRadius(radius);
 166     }
 167 
 168     /**
 169      * Returns the horizontal radius of the Gaussian kernel.
 170      *
 171      * @return the horizontal radius of the Gaussian kernel
 172      */
 173     public float getHRadius() {
 174         return state.getHRadius();
 175     }
 176 
 177     /**
 178      * Sets the horizontal radius of the Gaussian kernel.
 179      * <pre>
 180      *       Min:   0.0
 181      *       Max: 127.0
 182      *   Default:  10.0
 183      *  Identity:   0.0
 184      * </pre>
 185      *
 186      * @param hradius the horizontal radius of the Gaussian kernel
 187      * @throws IllegalArgumentException if {@code radius} is outside the
 188      * allowable range
 189      */
 190     public void setHRadius(float hradius) {
 191         float old = state.getHRadius();
 192         state.setHRadius(hradius);
 193     }
 194 
 195     /**
 196      * Returns the vertical radius of the Gaussian kernel.
 197      *
 198      * @return the vertical radius of the Gaussian kernel
 199      */
 200     public float getVRadius() {
 201         return state.getVRadius();
 202     }
 203 
 204     /**
 205      * Sets the vertical radius of the Gaussian kernel.
 206      * <pre>
 207      *       Min:   0.0
 208      *       Max: 127.0
 209      *   Default:  10.0
 210      *  Identity:   0.0
 211      * </pre>
 212      *
 213      * @param vradius the vertical radius of the Gaussian kernel
 214      * @throws IllegalArgumentException if {@code radius} is outside the
 215      * allowable range
 216      */
 217     public void setVRadius(float vradius) {
 218         float old = state.getVRadius();
 219         state.setVRadius(vradius);
 220     }
 221 
 222     /**
 223      * Gets the spread of the shadow effect.
 224      *
 225      * @return the spread of the shadow effect
 226      */
 227     public float getSpread() {
 228         return state.getSpread();
 229     }
 230 
 231     /**
 232      * Sets the spread of the shadow effect.
 233      * The spread is the portion of the radius where the contribution of
 234      * the source material will be 100%.
 235      * The remaining portion of the radius will have a contribution
 236      * controlled by the Gaussian kernel.
 237      * A spread of {@code 0.0} will result in a pure Gaussian distribution
 238      * of the shadow.
 239      * A spread of {@code 1.0} will result in a solid growth outward of the
 240      * source material opacity to the limit of the radius with a very sharp
 241      * cutoff to transparency at the radius.
 242      * <pre>
 243      *       Min: 0.0
 244      *       Max: 1.0
 245      *   Default: 0.0
 246      *  Identity: 0.0
 247      * </pre>
 248      *
 249      * @param spread the spread of the shadow effect
 250      * @throws IllegalArgumentException if {@code spread} is outside the
 251      * allowable range
 252      */
 253     public void setSpread(float spread) {
 254         float old = state.getSpread();
 255         state.setSpread(spread);
 256     }
 257 
 258     /**
 259      * Returns the shadow color.
 260      *
 261      * @return the shadow color
 262      */
 263     public Color4f getColor() {
 264         return state.getShadowColor();
 265     }
 266 
 267     /**
 268      * Sets the shadow color.
 269      * <pre>
 270      *       Min: n/a
 271      *       Max: n/a
 272      *   Default: Color4f.BLACK
 273      *  Identity: n/a
 274      * </pre>
 275      *
 276      * @param color the shadow color
 277      * @throws IllegalArgumentException if {@code color} is null
 278      */
 279     public void setColor(Color4f color) {
 280         Color4f old = state.getShadowColor();
 281         state.setShadowColor(color);
 282     }
 283 
 284     public float getGaussianRadius() {
 285         return getRadius();
 286     }
 287 
 288     public float getGaussianWidth() {
 289         return getHRadius() * 2.0f + 1.0f;
 290     }
 291 
 292     public float getGaussianHeight() {
 293         return getVRadius() * 2.0f + 1.0f;
 294     }
 295 
 296     public void setGaussianRadius(float r) {
 297         setRadius(r);
 298     }
 299 
 300     public void setGaussianWidth(float w) {
 301         setHRadius(w < 1.0f ? 0.0f : ((w - 1.0f) / 2.0f));
 302     }
 303 
 304     public void setGaussianHeight(float h) {
 305         setVRadius(h < 1.0f ? 0.0f : ((h - 1.0f) / 2.0f));
 306     }
 307 
 308     public ShadowMode getMode() {
 309         return ShadowMode.GAUSSIAN;
 310     }
 311 
 312     public AbstractShadow implFor(ShadowMode mode) {
 313         int passes = 0;
 314         switch (mode) {
 315             case GAUSSIAN:
 316                 return this;
 317             case ONE_PASS_BOX:
 318                 passes = 1;
 319                 break;
 320             case TWO_PASS_BOX:
 321                 passes = 2;
 322                 break;
 323             case THREE_PASS_BOX:
 324                 passes = 3;
 325                 break;
 326         }
 327         BoxShadow box = new BoxShadow();
 328         box.setInput(getInput());
 329         box.setGaussianWidth(getGaussianWidth());
 330         box.setGaussianHeight(getGaussianHeight());
 331         box.setColor(getColor());
 332         box.setPasses(passes);
 333         box.setSpread(getSpread());
 334         return box;
 335     }
 336 
 337     @Override
 338     public BaseBounds getBounds(BaseTransform transform, Effect defaultInput) {
 339         BaseBounds r = super.getBounds(null, defaultInput);
 340         int hpad = state.getPad(0);
 341         int vpad = state.getPad(1);
 342         RectBounds ret = new RectBounds(r.getMinX(), r.getMinY(), r.getMaxX(), r.getMaxY());
 343         ret.grow(hpad, vpad);
 344         return transformBounds(transform, ret);
 345     }
 346 
 347     @Override
 348     public Rectangle getResultBounds(BaseTransform transform,
 349                                      Rectangle outputClip,
 350                                      ImageData... inputDatas)
 351     {
 352         Rectangle r = super.getResultBounds(transform, outputClip, inputDatas);
 353         int hpad = state.getPad(0);
 354         int vpad = state.getPad(1);
 355         Rectangle ret = new Rectangle(r);
 356         ret.grow(hpad, vpad);
 357         return ret;
 358     }
 359 
 360     @Override
 361     public ImageData filterImageDatas(FilterContext fctx,
 362                                       BaseTransform transform,
 363                                       Rectangle outputClip,
 364                                       ImageData... inputs)
 365     {
 366         return state.filterImageDatas(this, fctx, transform, outputClip, inputs);
 367     }
 368 
 369     @Override
 370     public boolean operatesInUserSpace() {
 371         return true;
 372     }
 373 
 374     @Override
 375     protected Rectangle getInputClip(int inputIndex,
 376                                      BaseTransform transform,
 377                                      Rectangle outputClip)
 378     {
 379         // A blur needs as much "fringe" data from its input as it creates
 380         // around its output so we use the same expansion as is used in the
 381         // result bounds.
 382         if (outputClip != null) {
 383             int hpad = state.getPad(0);
 384             int vpad = state.getPad(1);
 385             if ((hpad | vpad) != 0) {
 386                 outputClip = new Rectangle(outputClip);
 387                 outputClip.grow(hpad, vpad);
 388             }
 389         }
 390         return outputClip;
 391     }
 392 
 393     @Override
 394     public boolean reducesOpaquePixels() {
 395         return true;
 396     }
 397 
 398     @Override
 399     public DirtyRegionContainer getDirtyRegions(Effect defaultInput, DirtyRegionPool regionPool) {
 400         Effect di = getDefaultedInput(0, defaultInput);
 401         DirtyRegionContainer drc = di.getDirtyRegions(defaultInput, regionPool);
 402         
 403         drc.grow(state.getPad(0), state.getPad(1));
 404 
 405         return drc;
 406     }
 407 }