Flex Effects with Pixel Bender filters, part 2.

This is the second part of a series on using Pixel Bender to create effects in Flex. In this part I'll go through making a simple filter in Pixel Bender that can be used to animate an effect in a similar way to the last post.

You can get hold of a copy of the Pixel Bender toolkit here and also get hold of a lot of good reading material to get started using it.

The filter I've made basically alters the alpha value of pixels based on their y position in the image. Using a sine function this is done in horizontal stripes to create a shutter show/hide effect. Here the code for the filter:

<languageVersion : 1.0;>
kernel ShutterFade
<   namespace : "com.as3offcuts.filters";
    vendor : "as3offcuts";
    version : 1;
    description : "shutter fade";
>


{
    //input pixel
    input image4 src;
    //output pixel
    output pixel4 dst;


    //parameter that takes the image through the effect from fully visible (0) to invisible (1)
    parameter float transition
    <
        minValue:       0.0;
        maxValue:       1.0;
        defaultValue:   0.0;
    >;


    //parameter that governs the height of the 'shutters'. The higher the number, the bigger the shutter.
    parameter float shutterHeight
    <
        minValue:       1.0;
        maxValue:       10.0;
        defaultValue:   1.0;
    >;


    void 
    evaluatePixel() 
    {
        //sample the current pixel
        pixel4 pix = sampleNearest( src, outCoord() );
        //get a base value from a sin function of the pixel's y position divided by the shutterHeight parameter
        float sinHeight = sin( outCoord().y / shutterHeight );
        //assign the base value adjusted by the transition value to the current pixel's alpha
        pix.a = sinHeight - ((transition * 3.) -2.);
        //assign the transformed pixel to the output
        dst = pix;
    }
}

 

Shutterfade source

This filter calculates a value based on the sine of the pixel's y position divided by the shutterHeight parameter. This value is then either exaggerated or reduced based on the transition parameter before being assigned to the output pixel's alpha value. This gives a graduated opaque-transparent effect in horizontal stripes going down the image.

Using this in a custom Flex effect is just a case of exporting the filter as a .pbj file and plugging it into an effect class in the same way as in the last post, and manipulating the transition parameter to create the animation.

 

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; 
    } 
}