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 } 100 101 /** 102 * Creates a new Effect. 103 */ 104 protected Effect() { 105 markDirty(EffectDirtyBits.EFFECT_DIRTY); 106 } 107 108 void effectBoundsChanged() { 109 toggleDirty(EffectDirtyBits.BOUNDS_CHANGED); 110 } 111 112 private com.sun.scenario.effect.Effect peer; 113 abstract com.sun.scenario.effect.Effect createPeer(); 114 115 com.sun.scenario.effect.Effect getPeer() { 116 if (peer == null) { 117 peer = createPeer(); 118 } 119 return peer; 120 } 121 122 // effect is marked dirty in the constructor, so we don't need to be lazy here 123 private IntegerProperty effectDirty = 124 new SimpleIntegerProperty(this, "effectDirty"); 125 126 private void setEffectDirty(int value) { 127 effectDirtyProperty().set(value); 128 } 129 130 private final IntegerProperty effectDirtyProperty() { 131 return effectDirty; 132 } 133 134 private final boolean isEffectDirty() { 135 return isEffectDirty(EffectDirtyBits.EFFECT_DIRTY); 136 } 137 138 /** 139 * Set the specified dirty bit 140 */ 141 final void markDirty(EffectDirtyBits dirtyBit) { 142 setEffectDirty(effectDirty.get() | dirtyBit.getMask()); 143 } 144 145 /** 146 * Toggle the specified dirty bit 147 */ 148 private void toggleDirty(EffectDirtyBits dirtyBit) { 149 setEffectDirty(effectDirty.get() ^ dirtyBit.getMask()); 150 } 151 152 /** 153 * Test the specified dirty bit 154 */ 155 private boolean isEffectDirty(EffectDirtyBits dirtyBit) { 156 return ((effectDirty.get() & dirtyBit.getMask()) != 0); 157 } 158 159 /** 160 * Clear the specified dirty bit 161 */ 162 private void clearEffectDirty(EffectDirtyBits dirtyBit) { 163 setEffectDirty(effectDirty.get() & ~dirtyBit.getMask()); 164 } 165 166 final void sync() { 167 if (isEffectDirty(EffectDirtyBits.EFFECT_DIRTY)) { 168 update(); 169 clearEffectDirty(EffectDirtyBits.EFFECT_DIRTY); 170 } 171 } 172 173 abstract void update(); 174 175 abstract boolean checkChainContains(Effect e); 176 177 boolean containsCycles(Effect value) { 178 if (value != null 179 && (value == this || value.checkChainContains(this))) { 180 return true; 181 } 182 return false; 183 } 184 185 class EffectInputChangeListener extends EffectChangeListener { 186 private int oldBits; 187 188 public void register(Effect value) { 189 super.register(value == null? null: value.effectDirtyProperty()); 190 if (value != null) { 191 oldBits = value.effectDirtyProperty().get(); 192 } 193 } 194 195 @Override 196 public void invalidated(Observable valueModel) { 197 int newBits = ((IntegerProperty)valueModel).get(); 198 int dirtyBits = newBits ^ oldBits; 199 oldBits = newBits; 200 if (EffectDirtyBits.isSet(dirtyBits, EffectDirtyBits.EFFECT_DIRTY) 201 && EffectDirtyBits.isSet(newBits, EffectDirtyBits.EFFECT_DIRTY)) { 202 markDirty(EffectDirtyBits.EFFECT_DIRTY); 203 } 204 if (EffectDirtyBits.isSet(dirtyBits, EffectDirtyBits.BOUNDS_CHANGED)) { 205 toggleDirty(EffectDirtyBits.BOUNDS_CHANGED); 206 } 207 } 208 } 209 210 class EffectInputProperty extends ObjectPropertyBase<Effect> { 211 private final String propertyName; 212 213 private Effect validInput = null; 214 215 private final EffectInputChangeListener effectChangeListener = 216 new EffectInputChangeListener(); 217 218 public EffectInputProperty(final String propertyName) { 219 this.propertyName = propertyName; 220 } 221 222 @Override 223 public void invalidated() { 224 final Effect newInput = super.get(); 225 if (containsCycles(newInput)) { 226 if (isBound()) { 227 unbind(); 228 set(validInput); 229 throw new IllegalArgumentException("Cycle in effect chain " 230 + "detected, binding was set to incorrect value, " 231 + "unbinding the input property"); 232 } else { 233 set(validInput); 234 throw new IllegalArgumentException("Cycle in effect chain detected"); 235 } 236 } 237 validInput = newInput; 238 effectChangeListener.register(newInput); 239 markDirty(EffectDirtyBits.EFFECT_DIRTY); 240 241 // we toggle dirty flag for bounds on this effect to notify 242 // "consumers" of this effect that bounds have changed 243 // 244 // bounds of this effect might change 245 // even if bounds of chained effect are not dirty 246 effectBoundsChanged(); 247 } 248 249 @Override 250 public Object getBean() { 251 return Effect.this; 252 } 253 254 @Override 255 public String getName() { 256 return propertyName; 257 } 258 } 259 260 /** 261 * Returns bounds of given node with applied effect. 262 * 263 * We *never* pass null in as a bounds. This method will 264 * NOT take a null bounds object. The returned value may be 265 * the same bounds object passed in, or it may be a new object. 266 */ 267 abstract BaseBounds getBounds(BaseBounds bounds, 268 BaseTransform tx, 269 Node node, 270 BoundsAccessor boundsAccessor); 271 272 abstract Effect copy(); 273 274 static BaseBounds transformBounds(BaseTransform tx, BaseBounds r) { 275 if (tx == null || tx.isIdentity()) { 276 return r; 277 } 278 BaseBounds ret = new RectBounds(); 279 ret = tx.transform(r, ret); 280 return ret; 281 } 282 283 // utility method used in calculation of bounds in BoxBlur and DropShadow effects 284 static int getKernelSize(float fsize, int iterations) { 285 int ksize = (int) Math.ceil(fsize); 286 if (ksize < 1) ksize = 1; 287 ksize = (ksize-1) * iterations + 1; 288 ksize |= 1; 289 return ksize / 2; 290 } 291 292 // utility method used for calculation of bounds in Shadow and DropShadow effects 293 static BaseBounds getShadowBounds(BaseBounds bounds, 294 BaseTransform tx, 295 float width, 296 float height, 297 BlurType blurType) { 298 int hgrow = 0; 299 int vgrow = 0; 300 301 switch (blurType) { 302 case GAUSSIAN: 303 float hradius = width < 1.0f ? 0.0f : ((width - 1.0f) / 2.0f); 304 float vradius = height < 1.0f ? 0.0f : ((height - 1.0f) / 2.0f); 305 hgrow = (int) Math.ceil(hradius); 306 vgrow = (int) Math.ceil(vradius); 307 break; 308 case ONE_PASS_BOX: 309 hgrow = getKernelSize(Math.round(width/3.0f), 1); 310 vgrow = getKernelSize(Math.round(height/3.0f), 1); 311 break; 312 case TWO_PASS_BOX: 313 hgrow = getKernelSize(Math.round(width/3.0f), 2); 314 vgrow = getKernelSize(Math.round(height/3.0f), 2); 315 break; 316 case THREE_PASS_BOX: 317 hgrow = getKernelSize(Math.round(width/3.0f), 3); 318 vgrow = getKernelSize(Math.round(height/3.0f), 3); 319 break; 320 } 321 322 bounds = bounds.deriveWithPadding(hgrow, vgrow, 0); 323 324 return transformBounds(tx, bounds); 325 } 326 327 // Returns input bounds for an effect. These are either bounds of input effect or 328 // geometric bounds of the node on which the effect calling this method is applied. 329 static BaseBounds getInputBounds(BaseBounds bounds, 330 BaseTransform tx, 331 Node node, 332 BoundsAccessor boundsAccessor, 333 Effect input) { 334 if (input != null) { 335 bounds = input.getBounds(bounds, tx, node, boundsAccessor); 336 } else { 337 bounds = boundsAccessor.getGeomBounds(bounds, tx, node); 338 } 339 340 return bounds; 341 } 342 }