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 }