1 /*
   2  * Copyright (c) 2010, 2016, 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.ObjectProperty;
  31 import javafx.beans.property.ObjectPropertyBase;
  32 import javafx.scene.Node;
  33 
  34 import com.sun.javafx.util.Utils;
  35 import com.sun.javafx.effect.EffectDirtyBits;
  36 import com.sun.javafx.geom.BaseBounds;
  37 import com.sun.javafx.geom.RectBounds;
  38 import com.sun.javafx.geom.transform.BaseTransform;
  39 import com.sun.javafx.scene.BoundsAccessor;
  40 import com.sun.scenario.effect.Blend.Mode;
  41 
  42 
  43 /**
  44  * An effect that blends the two inputs together using one of the
  45  * pre-defined {@link BlendMode}s.
  46  *
  47  * <p>
  48  * Example:
  49  * <pre><code>
  50  * Blend blend = new Blend();
  51  * blend.setMode(BlendMode.COLOR_BURN);
  52  *
  53  * ColorInput colorInput = new ColorInput();
  54  * colorInput.setPaint(Color.STEELBLUE);
  55  * colorInput.setX(10);
  56  * colorInput.setY(10);
  57  * colorInput.setWidth(100);
  58  * colorInput.setHeight(180);
  59  *
  60  * blend.setTopInput(colorInput);
  61  *
  62  * Rectangle rect = new Rectangle();
  63  * rect.setWidth(220);
  64  * rect.setHeight(100);
  65  * Stop[] stops = new Stop[]{new Stop(0, Color.LIGHTSTEELBLUE), new Stop(1, Color.PALEGREEN)};
  66  * LinearGradient lg = new LinearGradient(0, 0, 0.25, 0.25, true, CycleMethod.REFLECT, stops);
  67  * rect.setFill(lg);
  68  *
  69  * Text text = new Text();
  70  * text.setX(15);
  71  * text.setY(65);
  72  * text.setFill(Color.PALEVIOLETRED);
  73  * text.setText("COLOR_BURN");
  74  * text.setFont(Font.font(null, FontWeight.BOLD, 30));
  75  *
  76  * Group g = new Group();
  77  * g.setEffect(blend);
  78  * g.getChildren().addAll(rect, text);
  79  * </pre></code>
  80  *
  81  * <p> The code above produces the following: </p>
  82  * <p> <img src="doc-files/blend.png"/> </p>
  83  * @since JavaFX 2.0
  84  */
  85 public class Blend extends Effect {
  86 
  87     static private Mode toPGMode(BlendMode mode) {
  88         if (mode == null) {
  89             return Mode.SRC_OVER; // Default value
  90         } else if (mode == BlendMode.SRC_OVER) {
  91             return Mode.SRC_OVER;
  92         } else if (mode == BlendMode.SRC_ATOP) {
  93             return Mode.SRC_ATOP;
  94         } else if (mode == BlendMode.ADD) {
  95             return Mode.ADD;
  96         } else if (mode == BlendMode.MULTIPLY) {
  97             return Mode.MULTIPLY;
  98         } else if (mode == BlendMode.SCREEN) {
  99             return Mode.SCREEN;
 100         } else if (mode == BlendMode.OVERLAY) {
 101             return Mode.OVERLAY;
 102         } else if (mode == BlendMode.DARKEN) {
 103             return Mode.DARKEN;
 104         } else if (mode == BlendMode.LIGHTEN) {
 105             return Mode.LIGHTEN;
 106         } else if (mode == BlendMode.COLOR_DODGE) {
 107             return Mode.COLOR_DODGE;
 108         } else if (mode == BlendMode.COLOR_BURN) {
 109             return Mode.COLOR_BURN;
 110         } else if (mode == BlendMode.HARD_LIGHT) {
 111             return Mode.HARD_LIGHT;
 112         } else if (mode == BlendMode.SOFT_LIGHT) {
 113             return Mode.SOFT_LIGHT;
 114         } else if (mode == BlendMode.DIFFERENCE) {
 115             return Mode.DIFFERENCE;
 116         } else if (mode == BlendMode.EXCLUSION) {
 117             return Mode.EXCLUSION;
 118         } else if (mode == BlendMode.RED) {
 119             return Mode.RED;
 120         } else if (mode == BlendMode.GREEN) {
 121             return Mode.GREEN;
 122         } else if (mode == BlendMode.BLUE) {
 123             return Mode.BLUE;
 124         } else {
 125             throw new java.lang.AssertionError("Unrecognized blend mode: {mode}");
 126         }
 127     }
 128 
 129     /**
 130      * Used by Group to convert the FX BlendMode enum value into a Decora value.
 131      */
 132     static Mode getToolkitMode(BlendMode mode) {
 133         return toPGMode(mode);
 134     }
 135 
 136     /**
 137      * Creates a new instance of Blend with default parameters.
 138      */
 139     public Blend() {}
 140 
 141     /**
 142      * Creates a new instance of Blend with the specified mode.
 143      * @param mode the {@code BlendMode} used to blend the two inputs together
 144      * @since JavaFX 2.1
 145      */
 146     public Blend(BlendMode mode) {
 147         setMode(mode);
 148     }
 149 
 150     /**
 151      * Creates a new instance of Blend with the specified mode and bottom
 152      * and top inputs.
 153      * @param mode the {@code BlendMode} used to blend the two inputs together
 154      * @param bottomInput the bottom input for this {@code Blend} operation
 155      * @param topInput the top input for this {@code Blend} operation
 156      * @since JavaFX 2.1
 157      */
 158     public Blend(BlendMode mode, Effect bottomInput, Effect topInput) {
 159         setMode(mode);
 160         setBottomInput(bottomInput);
 161         setTopInput(topInput);
 162     }
 163 
 164     @Override
 165     com.sun.scenario.effect.Blend createPeer() {
 166         return new com.sun.scenario.effect.Blend(
 167                         toPGMode(BlendMode.SRC_OVER),
 168                         com.sun.scenario.effect.Effect.DefaultInput,
 169                         com.sun.scenario.effect.Effect.DefaultInput);
 170     }
 171 
 172     /**
 173      * The {@code BlendMode} used to blend the two inputs together.
 174      * <pre>
 175      *       Min: n/a
 176      *       Max: n/a
 177      *   Default: BlendMode.SRC_OVER
 178      *  Identity: n/a
 179      * </pre>
 180      * @defaultValue SRC_OVER
 181      */
 182     private ObjectProperty<BlendMode> mode;
 183 
 184 
 185     public final void setMode(BlendMode value) {
 186         modeProperty().set(value);
 187     }
 188 
 189     public final BlendMode getMode() {
 190         return mode == null ? BlendMode.SRC_OVER : mode.get();
 191     }
 192 
 193     public final ObjectProperty<BlendMode> modeProperty() {
 194         if (mode == null) {
 195             mode = new ObjectPropertyBase<BlendMode>(BlendMode.SRC_OVER) {
 196 
 197                 @Override
 198                 public void invalidated() {
 199                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 200                 }
 201 
 202                 @Override
 203                 public Object getBean() {
 204                     return Blend.this;
 205                 }
 206 
 207                 @Override
 208                 public String getName() {
 209                     return "mode";
 210                 }
 211             };
 212         }
 213         return mode;
 214     }
 215 
 216     /**
 217      * The opacity value, which is modulated with the top input prior
 218      * to blending.
 219      * <pre>
 220      *       Min: 0.0
 221      *       Max: 1.0
 222      *   Default: 1.0
 223      *  Identity: 1.0
 224      * </pre>
 225      * @defaultValue 1.0
 226      */
 227     private DoubleProperty opacity;
 228 
 229 
 230     public final void setOpacity(double value) {
 231         opacityProperty().set(value);
 232     }
 233 
 234     public final double getOpacity() {
 235         return opacity == null ? 1 : opacity.get();
 236     }
 237 
 238     public final DoubleProperty opacityProperty() {
 239         if (opacity == null) {
 240             opacity = new DoublePropertyBase(1) {
 241 
 242                 @Override
 243                 public void invalidated() {
 244                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 245                 }
 246 
 247                 @Override
 248                 public Object getBean() {
 249                     return Blend.this;
 250                 }
 251 
 252                 @Override
 253                 public String getName() {
 254                     return "opacity";
 255                 }
 256             };
 257         }
 258         return opacity;
 259     }
 260 
 261     /**
 262      * The bottom input for this {@code Blend} operation.
 263      * If set to {@code null}, or left unspecified, a graphical image of
 264      * the {@code Node} to which the {@code Effect} is attached will be
 265      * used as the input.
 266      * @defaultValue null
 267      */
 268     private ObjectProperty<Effect> bottomInput;
 269 
 270 
 271     public final void setBottomInput(Effect value) {
 272         bottomInputProperty().set(value);
 273     }
 274 
 275     public final Effect getBottomInput() {
 276         return bottomInput == null ? null : bottomInput.get();
 277     }
 278 
 279     public final ObjectProperty<Effect> bottomInputProperty() {
 280         if (bottomInput == null) {
 281             bottomInput = new EffectInputProperty("bottomInput");
 282         }
 283         return bottomInput;
 284     }
 285 
 286     /**
 287      * The top input for this {@code Blend} operation.
 288      * If set to {@code null}, or left unspecified, a graphical image of
 289      * the {@code Node} to which the {@code Effect} is attached will be
 290      * used as the input.
 291      * @defaultValue null
 292      */
 293     private ObjectProperty<Effect> topInput;
 294 
 295 
 296     public final void setTopInput(Effect value) {
 297         topInputProperty().set(value);
 298     }
 299 
 300     public final Effect getTopInput() {
 301         return topInput == null ? null : topInput.get();
 302     }
 303 
 304     public final ObjectProperty<Effect> topInputProperty() {
 305         if (topInput == null) {
 306             topInput = new EffectInputProperty("topInput");
 307         }
 308         return topInput;
 309     }
 310 
 311     @Override
 312     boolean checkChainContains(Effect e) {
 313         Effect localTopInput = getTopInput();
 314         Effect localBottomInput = getBottomInput();
 315         if (localTopInput == e || localBottomInput == e)
 316             return true;
 317         if (localTopInput != null && localTopInput.checkChainContains(e))
 318             return true;
 319         if (localBottomInput != null && localBottomInput.checkChainContains(e))
 320             return true;
 321 
 322         return false;
 323     }
 324 
 325     @Override
 326     void update() {
 327         Effect localBottomInput = getBottomInput();
 328         Effect localTopInput = getTopInput();
 329 
 330         if (localTopInput != null) {
 331             localTopInput.sync();
 332         }
 333         if (localBottomInput != null) {
 334             localBottomInput.sync();
 335         }
 336 
 337         com.sun.scenario.effect.Blend peer =
 338                 (com.sun.scenario.effect.Blend) getPeer();
 339         peer.setTopInput(localTopInput == null ? null : localTopInput.getPeer());
 340         peer.setBottomInput(localBottomInput == null ? null : localBottomInput.getPeer());
 341         peer.setOpacity((float)Utils.clamp(0, getOpacity(), 1));
 342         peer.setMode(toPGMode(getMode()));
 343     }
 344 
 345     @Override
 346     BaseBounds getBounds(BaseBounds bounds,
 347                          BaseTransform tx,
 348                          Node node,
 349                          BoundsAccessor boundsAccessor) {
 350         BaseBounds topBounds = new RectBounds();
 351         BaseBounds bottomBounds = new RectBounds();
 352         bottomBounds = getInputBounds(bottomBounds, tx,
 353                                       node, boundsAccessor,
 354                                       getBottomInput());
 355         topBounds = getInputBounds(topBounds, tx,
 356                                    node, boundsAccessor,
 357                                    getTopInput());
 358         BaseBounds ret = topBounds.deriveWithUnion(bottomBounds);
 359         return ret;
 360     }
 361 
 362     @Override
 363     Effect copy() {
 364         return new Blend(this.getMode(), this.getBottomInput(), this.getTopInput());
 365     }
 366 }