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.Observable; 29 import javafx.beans.property.IntegerProperty; 30 import javafx.beans.property.ObjectPropertyBase; 31 import javafx.beans.property.SimpleIntegerProperty; 32 import javafx.scene.Node; 33 34 import com.sun.javafx.effect.EffectDirtyBits; 35 import com.sun.javafx.geom.BaseBounds; 36 import com.sun.javafx.geom.RectBounds; 37 import com.sun.javafx.geom.transform.BaseTransform; 38 import com.sun.javafx.scene.BoundsAccessor; 39 import com.sun.scenario.effect.EffectHelper; 40 41 /** 42 * The abstract base class for all effect implementations. 43 * An effect is a graphical algorithm that produces an image, typically 44 * as a modification of a source image. 45 * An effect can be associated with a scene graph {@code Node} by setting 46 * the {@link javafx.scene.Node#effectProperty Node.effect} attribute. 47 * Some effects change the color properties of the source pixels 48 * (such as {@link ColorAdjust}), 49 * others combine multiple images together (such as {@link Blend}), 50 * while still others warp or move the pixels of the source image around 51 * (such as {@link DisplacementMap} or {@link PerspectiveTransform}). 52 * All effects have at least one input defined and the input can be set 53 * to another effect to chain the effects together and combine their 54 * results, or it can be left unspecified in which case the effect will 55 * operate on a graphical rendering of the node it is attached to. 56 * <p> 57 * Note: this is a conditional feature. See 58 * {@link javafx.application.ConditionalFeature#EFFECT ConditionalFeature.EFFECT} 59 * for more information. 60 * @since JavaFX 2.0 61 */ 62 public abstract class Effect { 63 static { 64 // This is used by classes in different packages to get access to 65 // private and package private methods. 66 EffectHelper.setEffectAccessor(new EffectHelper.EffectAccessor() { 67 68 @Override 69 public com.sun.scenario.effect.Effect getPeer(Effect effect) { 70 return effect.getPeer(); 71 } 72 73 @Override 74 public void sync(Effect effect) { 75 effect.sync(); 76 } 77 78 @Override 79 public IntegerProperty effectDirtyProperty(Effect effect) { 80 return effect.effectDirtyProperty(); 81 } 82 83 @Override 84 public boolean isEffectDirty(Effect effect) { 85 return effect.isEffectDirty(); 86 } 87 88 @Override 89 public BaseBounds getBounds(Effect effect, BaseBounds bounds, 90 BaseTransform tx, Node node, BoundsAccessor boundsAccessor) { 91 return effect.getBounds(bounds, tx, node, boundsAccessor); 92 } 93 94 @Override 95 public Effect copy(Effect effect) { 96 return effect.copy(); 97 } 98 99 @Override 100 public com.sun.scenario.effect.Blend.Mode getToolkitBlendMode(BlendMode mode) { 101 return Blend.getToolkitMode(mode); 102 } 103 }); 104 } 105 106 /** 107 * Creates a new Effect. 108 */ 109 protected Effect() { 110 markDirty(EffectDirtyBits.EFFECT_DIRTY); 111 } 112 113 void effectBoundsChanged() { 114 toggleDirty(EffectDirtyBits.BOUNDS_CHANGED); 115 } 116 117 private com.sun.scenario.effect.Effect peer; 118 abstract com.sun.scenario.effect.Effect createPeer(); 119 120 com.sun.scenario.effect.Effect getPeer() { 121 if (peer == null) { 122 peer = createPeer(); 123 } 124 return peer; 125 } 126 127 // effect is marked dirty in the constructor, so we don't need to be lazy here 128 private IntegerProperty effectDirty = 129 new SimpleIntegerProperty(this, "effectDirty"); 130 131 private void setEffectDirty(int value) { 132 effectDirtyProperty().set(value); 133 } 134 135 private final IntegerProperty effectDirtyProperty() { 136 return effectDirty; 137 } 138 139 private final boolean isEffectDirty() { 140 return isEffectDirty(EffectDirtyBits.EFFECT_DIRTY); 141 } 142 143 /** 144 * Set the specified dirty bit 145 */ 146 final void markDirty(EffectDirtyBits dirtyBit) { 147 setEffectDirty(effectDirty.get() | dirtyBit.getMask()); 148 } 149 150 /** 151 * Toggle the specified dirty bit 152 */ 153 private void toggleDirty(EffectDirtyBits dirtyBit) { 154 setEffectDirty(effectDirty.get() ^ dirtyBit.getMask()); 155 } 156 157 /** 158 * Test the specified dirty bit 159 */ 160 private boolean isEffectDirty(EffectDirtyBits dirtyBit) { 161 return ((effectDirty.get() & dirtyBit.getMask()) != 0); 162 } 163 164 /** 165 * Clear the specified dirty bit 166 */ 167 private void clearEffectDirty(EffectDirtyBits dirtyBit) { 168 setEffectDirty(effectDirty.get() & ~dirtyBit.getMask()); 169 } 170 171 final void sync() { 172 if (isEffectDirty(EffectDirtyBits.EFFECT_DIRTY)) { 173 update(); 174 clearEffectDirty(EffectDirtyBits.EFFECT_DIRTY); 175 } 176 } 177 178 abstract void update(); 179 180 abstract boolean checkChainContains(Effect e); 181 182 boolean containsCycles(Effect value) { 183 if (value != null 184 && (value == this || value.checkChainContains(this))) { 185 return true; 186 } 187 return false; 188 } 189 190 class EffectInputChangeListener extends EffectChangeListener { 191 private int oldBits; 192 193 public void register(Effect value) { 194 super.register(value == null? null: value.effectDirtyProperty()); 195 if (value != null) { 196 oldBits = value.effectDirtyProperty().get(); 197 } 198 } 199 200 @Override 201 public void invalidated(Observable valueModel) { 202 int newBits = ((IntegerProperty)valueModel).get(); 203 int dirtyBits = newBits ^ oldBits; 204 oldBits = newBits; 205 if (EffectDirtyBits.isSet(dirtyBits, EffectDirtyBits.EFFECT_DIRTY) 206 && EffectDirtyBits.isSet(newBits, EffectDirtyBits.EFFECT_DIRTY)) { 207 markDirty(EffectDirtyBits.EFFECT_DIRTY); 208 } 209 if (EffectDirtyBits.isSet(dirtyBits, EffectDirtyBits.BOUNDS_CHANGED)) { 210 toggleDirty(EffectDirtyBits.BOUNDS_CHANGED); 211 } 212 } 213 } 214 215 class EffectInputProperty extends ObjectPropertyBase<Effect> { 216 private final String propertyName; 217 218 private Effect validInput = null; 219 220 private final EffectInputChangeListener effectChangeListener = 221 new EffectInputChangeListener(); 222 223 public EffectInputProperty(final String propertyName) { 224 this.propertyName = propertyName; 225 } 226 227 @Override 228 public void invalidated() { 229 final Effect newInput = super.get(); 230 if (containsCycles(newInput)) { 231 if (isBound()) { 232 unbind(); 233 set(validInput); 234 throw new IllegalArgumentException("Cycle in effect chain " 235 + "detected, binding was set to incorrect value, " 236 + "unbinding the input property"); 237 } else { 238 set(validInput); 239 throw new IllegalArgumentException("Cycle in effect chain detected"); 240 } 241 } 242 validInput = newInput; 243 effectChangeListener.register(newInput); 244 markDirty(EffectDirtyBits.EFFECT_DIRTY); 245 246 // we toggle dirty flag for bounds on this effect to notify 247 // "consumers" of this effect that bounds have changed 248 // 249 // bounds of this effect might change 250 // even if bounds of chained effect are not dirty 251 effectBoundsChanged(); 252 } 253 254 @Override 255 public Object getBean() { 256 return Effect.this; 257 } 258 259 @Override 260 public String getName() { 261 return propertyName; 262 } 263 } 264 265 /** 266 * Returns bounds of given node with applied effect. 267 * 268 * We *never* pass null in as a bounds. This method will 269 * NOT take a null bounds object. The returned value may be 270 * the same bounds object passed in, or it may be a new object. 271 */ 272 abstract BaseBounds getBounds(BaseBounds bounds, 273 BaseTransform tx, 274 Node node, 275 BoundsAccessor boundsAccessor); 276 277 abstract Effect copy(); 278 279 static BaseBounds transformBounds(BaseTransform tx, BaseBounds r) { 280 if (tx == null || tx.isIdentity()) { 281 return r; 282 } 283 BaseBounds ret = new RectBounds(); 284 ret = tx.transform(r, ret); 285 return ret; 286 } 287 288 // utility method used in calculation of bounds in BoxBlur and DropShadow effects 289 static int getKernelSize(float fsize, int iterations) { 290 int ksize = (int) Math.ceil(fsize); 291 if (ksize < 1) ksize = 1; 292 ksize = (ksize-1) * iterations + 1; 293 ksize |= 1; 294 return ksize / 2; 295 } 296 297 // utility method used for calculation of bounds in Shadow and DropShadow effects 298 static BaseBounds getShadowBounds(BaseBounds bounds, 299 BaseTransform tx, 300 float width, 301 float height, 302 BlurType blurType) { 303 int hgrow = 0; 304 int vgrow = 0; 305 306 switch (blurType) { 307 case GAUSSIAN: 308 float hradius = width < 1.0f ? 0.0f : ((width - 1.0f) / 2.0f); 309 float vradius = height < 1.0f ? 0.0f : ((height - 1.0f) / 2.0f); 310 hgrow = (int) Math.ceil(hradius); 311 vgrow = (int) Math.ceil(vradius); 312 break; 313 case ONE_PASS_BOX: 314 hgrow = getKernelSize(Math.round(width/3.0f), 1); 315 vgrow = getKernelSize(Math.round(height/3.0f), 1); 316 break; 317 case TWO_PASS_BOX: 318 hgrow = getKernelSize(Math.round(width/3.0f), 2); 319 vgrow = getKernelSize(Math.round(height/3.0f), 2); 320 break; 321 case THREE_PASS_BOX: 322 hgrow = getKernelSize(Math.round(width/3.0f), 3); 323 vgrow = getKernelSize(Math.round(height/3.0f), 3); 324 break; 325 } 326 327 bounds = bounds.deriveWithPadding(hgrow, vgrow, 0); 328 329 return transformBounds(tx, bounds); 330 } 331 332 // Returns input bounds for an effect. These are either bounds of input effect or 333 // geometric bounds of the node on which the effect calling this method is applied. 334 static BaseBounds getInputBounds(BaseBounds bounds, 335 BaseTransform tx, 336 Node node, 337 BoundsAccessor boundsAccessor, 338 Effect input) { 339 if (input != null) { 340 bounds = input.getBounds(bounds, tx, node, boundsAccessor); 341 } else { 342 bounds = boundsAccessor.getGeomBounds(bounds, tx, node); 343 } 344 345 return bounds; 346 } 347 }