Flex Effects with Pixel Bender filters, part 1.

This is the first of a two part series on using Pixel Bender filters in Flex effects. I've recently started to spend a bit of time with Pixel Bender and have found it to be a good tool for creating interesting effects and transitions in Flex. Even if you don't write any yourself, there are plenty available to use at the Pixel Bender Exchange

In this first part I'll cover an example of making an Effect and EffectInstance that extend the Flex framework and also use a Pixel Bender filter to achieve the effect. In the second part I'll cover writing a Pixel Bender filter to use with a custom effect.

I won't go over embedding and instantiating a filter as a Shader; this is well documented here. There is also a good example of animating a Pixel Bender filter here.

The Pixel Bender filter I'm going to use for this example is a dissolve effect from the BlackBookSafe sample project on Adobe Developer Connection. This filter creates a patterned dissolve effect that can be manipulated via one parameter, 'transition', which goes from 1, invisible, to 0, completely normal. Values in between giving the dissolve effect.

To turn this into an effect I have made two new classes that extend TweenEffect and TweenEffectInstance. Doing this means I take advantage of all the Flex effects functionality, and get to use the effect as a simple MXML component. The first new class, DissolveEffect, is very simple. It has two properties which it will pass onto effect instances and overrides two methods: getAffectedProperties() and initInstance(). Scroll to the bottom to see this class in its entirety.

The second class is DissolveInstance (not to be confused with mx.effects.effectClasses.DissolveInstance). This class has the Pixel Bender filter (the pbj file) embedded into it, as well as a Shader and ShaderFilter. Beyond this it is much like the FadeInstance class in that it does a small amount of validation on its properties and then uses its superclass' tween to create the desired effect. In this case the tween's value updates the transition value on the Pixel Bender filter (or more precisely the transition.value[0]), and updates the target's filters on each tween update.

 
public class DissolveEffect extends TweenEffect 
{ 
    private static var AFFECTED_PROPERTIES:Array = [ "filters" ];
 
    [Inspectable(category="General", defaultValue="undefined")] 
    public var transitionFrom:Number; 
 
    [Inspectable(category="General", defaultValue="undefined")] 
    public var transitionTo:Number; 
 
    public function DissolveEffect(target:Object=null) 
    { 
        super(target); 		 	instanceClass = DissolveInstance; 
    } 


    override public function getAffectedProperties():Array 
    { 
        return AFFECTED_PROPERTIES; 
    } 


    override protected function initInstance(instance:IEffectInstance):void 
    { 
        super.initInstance(instance); 
        var dissolveInstance:DissolveInstance = DissolveInstance(instance);
        dissolveInstance.transitionFrom = transitionFrom; 
        dissolveInstance.transitionTo = transitionTo; 
    } 
} 

 
public class DissolveInstance extends TweenEffectInstance 	
{ 
 
    [Embed(source="assets/disolve.pbj", mimeType="application/octet-stream")] 
    private var dissolveKernel:Class; 
 
    private var dissolveShader:Shader; 
    private var shaderFilter:ShaderFilter; 
 
    public var transitionFrom:Number; 
    public var transitionTo:Number; 
 
    public function DissolveInstance(target:Object) 
    { 
        super(target); 
        dissolveShader = new Shader( new dissolveKernel() ); 
        shaderFilter = new ShaderFilter( dissolveShader ); 
    } 
 
    override public function initEffect(event:Event):void 
    { 
        super.initEffect(event); 
 
        switch (event.type) 
        {	
            case "childrenCreationComplete": 
            case FlexEvent.CREATION_COMPLETE: 
            case FlexEvent.SHOW: 
            case Event.ADDED: 
            { 
                if (isNaN(transitionFrom)) 
                    transitionFrom = 1; 
                
                if (isNaN(transitionTo)) 
                    transitionTo = 0; 


                break;
            } 
            case FlexEvent.HIDE: 
            case Event.REMOVED: 
            { 
                if (isNaN(transitionFrom))
                    transitionFrom = 0; 
 
                if (isNaN(transitionTo)) 
                    transitionTo = 1; 
                break; 
            } 
        } 
    } 
 
    override public function play():void 
    { 
        super.play(); 
 
        var values:PropertyChanges = propertyChanges; 
 
        if (isNaN(transitionFrom) && isNaN(transitionTo)) 
        {	
            if (values && values.end["transition"] !== undefined) 
            { 
                transitionFrom = 1; 
                transitionTo = values.end["transition"]; 
            } 
            else 
            { 
                transitionFrom = 1; 
                transitionTo = 0; 
            } 
        } 
        else if (isNaN(transitionFrom)) 
        { 
            transitionFrom = (transitionTo == 0) ? 1 : 0; 
        } 
        else if (isNaN(transitionTo)) 
        { 
            if (values && values.end["transition"] !== undefined) 
            { 
                transitionTo = values.end["transition"]; 
            } 
            else
            { 
                transitionTo = (transitionFrom == 0) ? 1 : 0;
            } 
        }	
 
        tween = createTween(this, transitionFrom, transitionTo, duration);
        dissolveShader.data.transition.value[0] = transitionFrom;
 
        if(target.filters) 
        { 
            setTargetFilters( false, true ); 
        } 
        else 
            target.filters = [ shaderFilter ]; 
    }


    override public function onTweenUpdate(value:Object):void 
    { 
        dissolveShader.data.transition.value[0] = value; 
        setTargetFilters( true, true ); 
    } 
 
    override public function onTweenEnd(value:Object):void 
    { 
        super.onTweenEnd(value);	
        setTargetFilters( true, false ); 
    }


    private function setTargetFilters( splice:Boolean, push:Boolean ):void 
    { 
        var arr:Array = target.filters; 
        if( splice ) 
            arr.splice( target.filters.indexOf( shaderFilter ), 1 ); 
 
        if( push ) 
            arr.push( shaderFilter ); 	target.filters = arr; 
    } 
}