1 /*
   2  * Copyright (c) 2010, 2015, 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 javafx.scene.effect;
  27 
  28 import javafx.beans.property.DoubleProperty;
  29 import javafx.beans.property.DoublePropertyBase;
  30 import javafx.beans.property.IntegerProperty;
  31 import javafx.beans.property.IntegerPropertyBase;
  32 import javafx.beans.property.ObjectProperty;
  33 import javafx.scene.Node;
  34 
  35 import com.sun.javafx.util.Utils;
  36 import com.sun.javafx.effect.EffectDirtyBits;
  37 import com.sun.javafx.geom.BaseBounds;
  38 import com.sun.javafx.geom.transform.BaseTransform;
  39 import com.sun.javafx.scene.BoundsAccessor;
  40 
  41 
  42 /**
  43  * A blur effect using a simple box filter kernel, with separately
  44  * configurable sizes in both dimensions, and an iteration parameter
  45  * that controls the quality of the resulting blur.
  46  *
  47  * <p>
  48  * Example:
  49  * <pre><code>
  50  * BoxBlur boxBlur = new BoxBlur();
  51  * boxBlur.setWidth(10);
  52  * boxBlur.setHeight(3);
  53  * boxBlur.setIterations(3);
  54  *
  55  * Text text = new Text();
  56  * text.setText("Blurry Text!");
  57  * text.setFill(Color.web("0x3b596d"));
  58  * text.setFont(Font.font(null, FontWeight.BOLD, 50));
  59  * text.setX(10);
  60  * text.setY(50);
  61  * text.setEffect(boxBlur);
  62  * </pre></code>
  63  * <p>
  64  * The code above produces the following:
  65  * </p>
  66  * <p>
  67  * <img src="doc-files/boxblur.png"/>
  68  * </p>
  69  * @since JavaFX 2.0
  70  */
  71 public class BoxBlur extends Effect {
  72 
  73     /**
  74      * Creates a new instance of BoxBlur with default parameters.
  75      */
  76     public BoxBlur() {}
  77 
  78     /**
  79      * Creates a new instance of BoxBlur with specified width, height and
  80      * iterations.
  81      * @param width the horizontal dimension of the blur effect
  82      * @param height the vertical dimension of the blur effect
  83      * @param iterations the number of times to iterate the blur effect to
  84      * improve its "quality" or "smoothness"
  85      */
  86     public BoxBlur(double width, double height, int iterations) {
  87         setWidth(width);
  88         setHeight(height);
  89         setIterations(iterations);
  90     }
  91 
  92     @Override
  93     com.sun.scenario.effect.BoxBlur impl_createImpl() {
  94         return new com.sun.scenario.effect.BoxBlur();
  95     };
  96     /**
  97      * The input for this {@code Effect}.
  98      * If set to {@code null}, or left unspecified, a graphical image of
  99      * the {@code Node} to which the {@code Effect} is attached will be
 100      * used as the input.
 101      * @defaultValue null
 102      */
 103     private ObjectProperty<Effect> input;
 104 
 105 
 106     public final void setInput(Effect value) {
 107         inputProperty().set(value);
 108     }
 109 
 110     public final Effect getInput() {
 111         return input == null ? null : input.get();
 112     }
 113 
 114     public final ObjectProperty<Effect> inputProperty() {
 115         if (input == null) {
 116             input = new EffectInputProperty("input");
 117         }
 118         return input;
 119     }
 120 
 121     @Override
 122     boolean impl_checkChainContains(Effect e) {
 123         Effect localInput = getInput();
 124         if (localInput == null)
 125             return false;
 126         if (localInput == e)
 127             return true;
 128         return localInput.impl_checkChainContains(e);
 129     }
 130 
 131     /**
 132      * The horizontal dimension of the blur effect.
 133      * The color information for a given pixel will be spread across
 134      * a Box of the indicated width centered over the pixel.
 135      * Values less than or equal to 1 will not spread the color data
 136      * beyond the pixel where it originated from and so will have
 137      * no effect.
 138      * <pre>
 139      *       Min:   0.0
 140      *       Max: 255.0
 141      *   Default:   5.0
 142      *  Identity:  &lt;1.0
 143      * </pre>
 144      * @defaultValue 5.0
 145      */
 146     private DoubleProperty width;
 147 
 148 
 149     public final void setWidth(double value) {
 150         widthProperty().set(value);
 151     }
 152 
 153     public final double getWidth() {
 154         return width == null ? 5 : width.get();
 155     }
 156 
 157     public final DoubleProperty widthProperty() {
 158         if (width == null) {
 159             width = new DoublePropertyBase(5) {
 160 
 161                 @Override
 162                 public void invalidated() {
 163                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 164                     effectBoundsChanged();
 165                 }
 166 
 167                 @Override
 168                 public Object getBean() {
 169                     return BoxBlur.this;
 170                 }
 171 
 172                 @Override
 173                 public String getName() {
 174                     return "width";
 175                 }
 176             };
 177         }
 178         return width;
 179     }
 180 
 181     /**
 182      * The vertical dimension of the blur effect.
 183      * The color information for a given pixel will be spread across
 184      * a Box of the indicated height centered over the pixel.
 185      * Values less than or equal to 1 will not spread the color data
 186      * beyond the pixel where it originated from and so will have
 187      * no effect.
 188      * <pre>
 189      *       Min:   0.0
 190      *       Max: 255.0
 191      *   Default:   5.0
 192      *  Identity:  &lt;1.0
 193      * </pre>
 194      * @defaultValue 5.0
 195      */
 196     private DoubleProperty height;
 197 
 198 
 199     public final void setHeight(double value) {
 200         heightProperty().set(value);
 201     }
 202 
 203     public final double getHeight() {
 204         return height == null ? 5 : height.get();
 205     }
 206 
 207     public final DoubleProperty heightProperty() {
 208         if (height == null) {
 209             height = new DoublePropertyBase(5) {
 210 
 211                 @Override
 212                 public void invalidated() {
 213                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 214                     effectBoundsChanged();
 215                 }
 216 
 217                 @Override
 218                 public Object getBean() {
 219                     return BoxBlur.this;
 220                 }
 221 
 222                 @Override
 223                 public String getName() {
 224                     return "height";
 225                 }
 226             };
 227         }
 228         return height;
 229     }
 230 
 231     /**
 232      * The number of times to iterate the blur effect to improve its
 233      * "quality" or "smoothness".
 234      * Iterating the effect 3 times approximates the quality of a
 235      * Gaussian Blur to within 3%.
 236      * <pre>
 237      *       Min:   0
 238      *       Max:   3
 239      *   Default:   1
 240      *  Identity:   0
 241      * </pre>
 242      * @defaultValue 1
 243      */
 244     private IntegerProperty iterations;
 245 
 246 
 247     public final void setIterations(int value) {
 248         iterationsProperty().set(value);
 249     }
 250 
 251     public final int getIterations() {
 252         return iterations == null ? 1 : iterations.get();
 253     }
 254 
 255     public final IntegerProperty iterationsProperty() {
 256         if (iterations == null) {
 257             iterations = new IntegerPropertyBase(1) {
 258 
 259                 @Override
 260                 public void invalidated() {
 261                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 262                     effectBoundsChanged();
 263                 }
 264 
 265                 @Override
 266                 public Object getBean() {
 267                     return BoxBlur.this;
 268                 }
 269 
 270                 @Override
 271                 public String getName() {
 272                     return "iterations";
 273                 }
 274             };
 275         }
 276         return iterations;
 277     }
 278 
 279     private int getClampedWidth() {
 280         return Utils.clamp(0, (int) getWidth(), 255);
 281     }
 282 
 283     private int getClampedHeight() {
 284         return Utils.clamp(0, (int) getHeight(), 255);
 285     }
 286 
 287     private int getClampedIterations() {
 288         return Utils.clamp(0, getIterations(), 3);
 289     }
 290 
 291     @Override
 292     void impl_update() {
 293         Effect localInput = getInput();
 294         if (localInput != null) {
 295             localInput.impl_sync();
 296         }
 297 
 298         com.sun.scenario.effect.BoxBlur peer =
 299                 (com.sun.scenario.effect.BoxBlur) impl_getImpl();
 300         peer.setInput(localInput == null ? null : localInput.impl_getImpl());
 301         peer.setHorizontalSize(getClampedWidth());
 302         peer.setVerticalSize(getClampedHeight());
 303         peer.setPasses(getClampedIterations());
 304     }
 305 
 306     /**
 307      * @treatAsPrivate implementation detail
 308      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 309      */
 310     @Deprecated
 311     @Override
 312     public BaseBounds impl_getBounds(BaseBounds bounds,
 313                                      BaseTransform tx,
 314                                      Node node,
 315                                      BoundsAccessor boundsAccessor) {
 316         bounds = getInputBounds(bounds,
 317                                 BaseTransform.IDENTITY_TRANSFORM,
 318                                 node, boundsAccessor,
 319                                 getInput());
 320 
 321         int localIterations = getClampedIterations();
 322 
 323         int hgrow = getKernelSize(getClampedWidth(), localIterations);
 324         int vgrow = getKernelSize(getClampedHeight(), localIterations);
 325 
 326         bounds = bounds.deriveWithPadding(hgrow, vgrow, 0);
 327 
 328         return transformBounds(tx, bounds);
 329     }
 330 
 331     /**
 332      * @treatAsPrivate implementation detail
 333      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 334      */
 335     @Deprecated
 336     @Override
 337     public Effect impl_copy() {
 338         BoxBlur bb = new BoxBlur(this.getWidth(), this.getHeight(), this.getIterations());
 339         bb.setInput(this.getInput());
 340         return bb;
 341     }
 342 }