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.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      * @treatAsPrivate implementation detail
 132      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 133      */
 134     @Deprecated
 135     public static Mode impl_getToolkitMode(BlendMode mode) {
 136         return toPGMode(mode);
 137     }
 138 
 139     /**
 140      * Creates a new instance of Blend with default parameters.
 141      */
 142     public Blend() {}
 143 
 144     /**
 145      * Creates a new instance of Blend with the specified mode.
 146      * @param mode the {@code BlendMode} used to blend the two inputs together
 147      * @since JavaFX 2.1
 148      */
 149     public Blend(BlendMode mode) {
 150         setMode(mode);
 151     }
 152 
 153     /**
 154      * Creates a new instance of Blend with the specified mode and bottom
 155      * and top inputs.
 156      * @param mode the {@code BlendMode} used to blend the two inputs together
 157      * @param bottomInput the bottom input for this {@code Blend} operation
 158      * @param topInput the top input for this {@code Blend} operation
 159      * @since JavaFX 2.1
 160      */
 161     public Blend(BlendMode mode, Effect bottomInput, Effect topInput) {
 162         setMode(mode);
 163         setBottomInput(bottomInput);
 164         setTopInput(topInput);
 165     }
 166 
 167     @Override
 168     com.sun.scenario.effect.Blend impl_createImpl() {
 169         return new com.sun.scenario.effect.Blend(
 170                         toPGMode(BlendMode.SRC_OVER),
 171                         com.sun.scenario.effect.Effect.DefaultInput,
 172                         com.sun.scenario.effect.Effect.DefaultInput);
 173     }
 174 
 175     /**
 176      * The {@code BlendMode} used to blend the two inputs together.
 177      * <pre>
 178      *       Min: n/a
 179      *       Max: n/a
 180      *   Default: BlendMode.SRC_OVER
 181      *  Identity: n/a
 182      * </pre>
 183      * @defaultValue SRC_OVER
 184      */
 185     private ObjectProperty<BlendMode> mode;
 186 
 187 
 188     public final void setMode(BlendMode value) {
 189         modeProperty().set(value);
 190     }
 191 
 192     public final BlendMode getMode() {
 193         return mode == null ? BlendMode.SRC_OVER : mode.get();
 194     }
 195 
 196     public final ObjectProperty<BlendMode> modeProperty() {
 197         if (mode == null) {
 198             mode = new ObjectPropertyBase<BlendMode>(BlendMode.SRC_OVER) {
 199 
 200                 @Override
 201                 public void invalidated() {
 202                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 203                 }
 204 
 205                 @Override
 206                 public Object getBean() {
 207                     return Blend.this;
 208                 }
 209 
 210                 @Override
 211                 public String getName() {
 212                     return "mode";
 213                 }
 214             };
 215         }
 216         return mode;
 217     }
 218 
 219     /**
 220      * The opacity value, which is modulated with the top input prior
 221      * to blending.
 222      * <pre>
 223      *       Min: 0.0
 224      *       Max: 1.0
 225      *   Default: 1.0
 226      *  Identity: 1.0
 227      * </pre>
 228      * @defaultValue 1.0
 229      */
 230     private DoubleProperty opacity;
 231 
 232 
 233     public final void setOpacity(double value) {
 234         opacityProperty().set(value);
 235     }
 236 
 237     public final double getOpacity() {
 238         return opacity == null ? 1 : opacity.get();
 239     }
 240 
 241     public final DoubleProperty opacityProperty() {
 242         if (opacity == null) {
 243             opacity = new DoublePropertyBase(1) {
 244 
 245                 @Override
 246                 public void invalidated() {
 247                     markDirty(EffectDirtyBits.EFFECT_DIRTY);
 248                 }
 249 
 250                 @Override
 251                 public Object getBean() {
 252                     return Blend.this;
 253                 }
 254 
 255                 @Override
 256                 public String getName() {
 257                     return "opacity";
 258                 }
 259             };
 260         }
 261         return opacity;
 262     }
 263 
 264     /**
 265      * The bottom input for this {@code Blend} operation.
 266      * If set to {@code null}, or left unspecified, a graphical image of
 267      * the {@code Node} to which the {@code Effect} is attached will be
 268      * used as the input.
 269      * @defaultValue null
 270      */
 271     private ObjectProperty<Effect> bottomInput;
 272 
 273 
 274     public final void setBottomInput(Effect value) {
 275         bottomInputProperty().set(value);
 276     }
 277 
 278     public final Effect getBottomInput() {
 279         return bottomInput == null ? null : bottomInput.get();
 280     }
 281 
 282     public final ObjectProperty<Effect> bottomInputProperty() {
 283         if (bottomInput == null) {
 284             bottomInput = new EffectInputProperty("bottomInput");
 285         }
 286         return bottomInput;
 287     }
 288 
 289     /**
 290      * The top input for this {@code Blend} operation.
 291      * If set to {@code null}, or left unspecified, a graphical image of
 292      * the {@code Node} to which the {@code Effect} is attached will be
 293      * used as the input.
 294      * @defaultValue null
 295      */
 296     private ObjectProperty<Effect> topInput;
 297 
 298 
 299     public final void setTopInput(Effect value) {
 300         topInputProperty().set(value);
 301     }
 302 
 303     public final Effect getTopInput() {
 304         return topInput == null ? null : topInput.get();
 305     }
 306 
 307     public final ObjectProperty<Effect> topInputProperty() {
 308         if (topInput == null) {
 309             topInput = new EffectInputProperty("topInput");
 310         }
 311         return topInput;
 312     }
 313 
 314     @Override
 315     boolean impl_checkChainContains(Effect e) {
 316         Effect localTopInput = getTopInput();
 317         Effect localBottomInput = getBottomInput();
 318         if (localTopInput == e || localBottomInput == e)
 319             return true;
 320         if (localTopInput != null && localTopInput.impl_checkChainContains(e))
 321             return true;
 322         if (localBottomInput != null && localBottomInput.impl_checkChainContains(e))
 323             return true;
 324 
 325         return false;
 326     }
 327 
 328     @Override
 329     void impl_update() {
 330         Effect localBottomInput = getBottomInput();
 331         Effect localTopInput = getTopInput();
 332 
 333         if (localTopInput != null) {
 334             localTopInput.impl_sync();
 335         }
 336         if (localBottomInput != null) {
 337             localBottomInput.impl_sync();
 338         }
 339 
 340         com.sun.scenario.effect.Blend peer =
 341                 (com.sun.scenario.effect.Blend) impl_getImpl();
 342         peer.setTopInput(localTopInput == null ? null : localTopInput.impl_getImpl());
 343         peer.setBottomInput(localBottomInput == null ? null : localBottomInput.impl_getImpl());
 344         peer.setOpacity((float)Utils.clamp(0, getOpacity(), 1));
 345         peer.setMode(toPGMode(getMode()));
 346     }
 347 
 348     /**
 349      * @treatAsPrivate implementation detail
 350      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 351      */
 352     @Deprecated
 353     @Override
 354     public BaseBounds impl_getBounds(BaseBounds bounds,
 355                                      BaseTransform tx,
 356                                      Node node,
 357                                      BoundsAccessor boundsAccessor) {
 358         BaseBounds topBounds = new RectBounds();
 359         BaseBounds bottomBounds = new RectBounds();
 360         bottomBounds = getInputBounds(bottomBounds, tx,
 361                                       node, boundsAccessor,
 362                                       getBottomInput());
 363         topBounds = getInputBounds(topBounds, tx,
 364                                    node, boundsAccessor,
 365                                    getTopInput());
 366         BaseBounds ret = topBounds.deriveWithUnion(bottomBounds);
 367         return ret;
 368     }
 369 
 370     /**
 371      * @treatAsPrivate implementation detail
 372      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 373      */
 374     @Deprecated
 375     @Override
 376     public Effect impl_copy() {
 377         return new Blend(this.getMode(), this.getBottomInput(), this.getTopInput());
 378     }
 379 }