/*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javafx.scene.effect;
import javafx.beans.
Observable;
import javafx.beans.property.
IntegerProperty;
import javafx.beans.property.
ObjectPropertyBase;
import javafx.beans.property.
SimpleIntegerProperty;
import javafx.scene.
Node;
import com.sun.javafx.effect.
EffectDirtyBits;
import com.sun.javafx.geom.
BaseBounds;
import com.sun.javafx.geom.
RectBounds;
import com.sun.javafx.geom.transform.
BaseTransform;
import com.sun.javafx.scene.
BoundsAccessor;
/**
* The abstract base class for all effect implementations.
* An effect is a graphical algorithm that produces an image, typically
* as a modification of a source image.
* An effect can be associated with a scene graph {@code Node} by setting
* the {@link javafx.scene.Node#effectProperty Node.effect} attribute.
* Some effects change the color properties of the source pixels
* (such as {@link ColorAdjust}),
* others combine multiple images together (such as {@link Blend}),
* while still others warp or move the pixels of the source image around
* (such as {@link DisplacementMap} or {@link PerspectiveTransform}).
* All effects have at least one input defined and the input can be set
* to another effect to chain the effects together and combine their
* results, or it can be left unspecified in which case the effect will
* operate on a graphical rendering of the node it is attached to.
* <p>
* Note: this is a conditional feature. See
* {@link javafx.application.ConditionalFeature#EFFECT ConditionalFeature.EFFECT}
* for more information.
* @since JavaFX 2.0
*/
public abstract class
Effect {
/**
* Creates a new Effect.
*/
protected
Effect() {
markDirty(
EffectDirtyBits.
EFFECT_DIRTY);
}
void
effectBoundsChanged() {
toggleDirty(
EffectDirtyBits.
BOUNDS_CHANGED);
}
private com.sun.scenario.effect.
Effect peer;
abstract com.sun.scenario.effect.
Effect impl_createImpl();
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public com.sun.scenario.effect.
Effect impl_getImpl() {
if (
peer == null) {
peer =
impl_createImpl();
}
return
peer;
}
// effect is marked dirty in the constructor, so we don't need to be lazy here
private
IntegerProperty effectDirty =
new
SimpleIntegerProperty(this, "effectDirty");
private void
setEffectDirty(int
value) {
impl_effectDirtyProperty().
set(
value);
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final
IntegerProperty impl_effectDirtyProperty() {
return
effectDirty;
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final boolean
impl_isEffectDirty() {
return
isEffectDirty(
EffectDirtyBits.
EFFECT_DIRTY);
}
/**
* Set the specified dirty bit
*/
final void
markDirty(
EffectDirtyBits dirtyBit) {
setEffectDirty(
effectDirty.
get() |
dirtyBit.
getMask());
}
/**
* Toggle the specified dirty bit
*/
private void
toggleDirty(
EffectDirtyBits dirtyBit) {
setEffectDirty(
effectDirty.
get() ^
dirtyBit.
getMask());
}
/**
* Test the specified dirty bit
*/
private boolean
isEffectDirty(
EffectDirtyBits dirtyBit) {
return ((
effectDirty.
get() &
dirtyBit.
getMask()) != 0);
}
/**
* Clear the specified dirty bit
*/
private void
clearEffectDirty(
EffectDirtyBits dirtyBit) {
setEffectDirty(
effectDirty.
get() & ~
dirtyBit.
getMask());
}
/**
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public final void
impl_sync() {
if (
isEffectDirty(
EffectDirtyBits.
EFFECT_DIRTY)) {
impl_update();
clearEffectDirty(
EffectDirtyBits.
EFFECT_DIRTY);
}
}
abstract void
impl_update();
abstract boolean
impl_checkChainContains(
Effect e);
boolean
impl_containsCycles(
Effect value) {
if (
value != null
&& (
value == this ||
value.
impl_checkChainContains(this))) {
return true;
}
return false;
}
class
EffectInputChangeListener extends
EffectChangeListener {
private int
oldBits;
public void
register(
Effect value) {
super.register(
value == null? null:
value.
impl_effectDirtyProperty());
if (
value != null) {
oldBits =
value.
impl_effectDirtyProperty().
get();
}
}
@
Override
public void
invalidated(
Observable valueModel) {
int
newBits = ((
IntegerProperty)
valueModel).
get();
int
dirtyBits =
newBits ^
oldBits;
oldBits =
newBits;
if (
EffectDirtyBits.
isSet(
dirtyBits,
EffectDirtyBits.
EFFECT_DIRTY)
&&
EffectDirtyBits.
isSet(
newBits,
EffectDirtyBits.
EFFECT_DIRTY)) {
markDirty(
EffectDirtyBits.
EFFECT_DIRTY);
}
if (
EffectDirtyBits.
isSet(
dirtyBits,
EffectDirtyBits.
BOUNDS_CHANGED)) {
toggleDirty(
EffectDirtyBits.
BOUNDS_CHANGED);
}
}
}
class
EffectInputProperty extends
ObjectPropertyBase<
Effect> {
private final
String propertyName;
private
Effect validInput = null;
private final
EffectInputChangeListener effectChangeListener =
new
EffectInputChangeListener();
public
EffectInputProperty(final
String propertyName) {
this.
propertyName =
propertyName;
}
@
Override
public void
invalidated() {
final
Effect newInput = super.get();
if (
impl_containsCycles(
newInput)) {
if (
isBound()) {
unbind();
set(
validInput);
throw new
IllegalArgumentException("Cycle in effect chain "
+ "detected, binding was set to incorrect value, "
+ "unbinding the input property");
} else {
set(
validInput);
throw new
IllegalArgumentException("Cycle in effect chain detected");
}
}
validInput =
newInput;
effectChangeListener.
register(
newInput);
markDirty(
EffectDirtyBits.
EFFECT_DIRTY);
// we toggle dirty flag for bounds on this effect to notify
// "consumers" of this effect that bounds have changed
//
// bounds of this effect might change
// even if bounds of chained effect are not dirty
effectBoundsChanged();
}
@
Override
public
Object getBean() {
return
Effect.this;
}
@
Override
public
String getName() {
return
propertyName;
}
}
/**
* Returns bounds of given node with applied effect.
*
* We *never* pass null in as a bounds. This method will
* NOT take a null bounds object. The returned value may be
* the same bounds object passed in, or it may be a new object.
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public abstract
BaseBounds impl_getBounds(
BaseBounds bounds,
BaseTransform tx,
Node node,
BoundsAccessor boundsAccessor);
/**
*
* @treatAsPrivate implementation detail
* @deprecated This is an internal API that is not intended for use and will be removed in the next version
*/
@
Deprecated
public abstract
Effect impl_copy();
static
BaseBounds transformBounds(
BaseTransform tx,
BaseBounds r) {
if (
tx == null ||
tx.
isIdentity()) {
return
r;
}
BaseBounds ret = new
RectBounds();
ret =
tx.
transform(
r,
ret);
return
ret;
}
// utility method used in calculation of bounds in BoxBlur and DropShadow effects
static int
getKernelSize(float
fsize, int
iterations) {
int
ksize = (int)
Math.
ceil(
fsize);
if (
ksize < 1)
ksize = 1;
ksize = (
ksize-1) *
iterations + 1;
ksize |= 1;
return
ksize / 2;
}
// utility method used for calculation of bounds in Shadow and DropShadow effects
static
BaseBounds getShadowBounds(
BaseBounds bounds,
BaseTransform tx,
float
width,
float
height,
BlurType blurType) {
int
hgrow = 0;
int
vgrow = 0;
switch (
blurType) {
case
GAUSSIAN:
float
hradius =
width < 1.0f ? 0.0f : ((
width - 1.0f) / 2.0f);
float
vradius =
height < 1.0f ? 0.0f : ((
height - 1.0f) / 2.0f);
hgrow = (int)
Math.
ceil(
hradius);
vgrow = (int)
Math.
ceil(
vradius);
break;
case
ONE_PASS_BOX:
hgrow =
getKernelSize(
Math.
round(
width/3.0f), 1);
vgrow =
getKernelSize(
Math.
round(
height/3.0f), 1);
break;
case
TWO_PASS_BOX:
hgrow =
getKernelSize(
Math.
round(
width/3.0f), 2);
vgrow =
getKernelSize(
Math.
round(
height/3.0f), 2);
break;
case
THREE_PASS_BOX:
hgrow =
getKernelSize(
Math.
round(
width/3.0f), 3);
vgrow =
getKernelSize(
Math.
round(
height/3.0f), 3);
break;
}
bounds =
bounds.
deriveWithPadding(
hgrow,
vgrow, 0);
return
transformBounds(
tx,
bounds);
}
// Returns input bounds for an effect. These are either bounds of input effect or
// geometric bounds of the node on which the effect calling this method is applied.
static
BaseBounds getInputBounds(
BaseBounds bounds,
BaseTransform tx,
Node node,
BoundsAccessor boundsAccessor,
Effect input) {
if (
input != null) {
bounds =
input.
impl_getBounds(
bounds,
tx,
node,
boundsAccessor);
} else {
bounds =
boundsAccessor.
getGeomBounds(
bounds,
tx,
node);
}
return
bounds;
}
}