Filters in Starling

Just in case you happened to miss it, the most recent version of the Starling Framework (the one which you can pull from Github, that is) now supports custom filters. You can check out the official announcement here.

Using filters in Starling couldn’t be easier – you just assign a filter to the filter property of any Starling DisplayObject, like so: myStarlingMovieclip.filter = myStarlingFilter;

Creating your own filters is also pretty easy – assuming, that is, you have a moderate handle on AGAL and can write a decent fragment shader. To do so, just extend the FragmentFilter class and in the createPrograms method drop in your own fragment shader. In the activate method, you can upload whatever constants the shader requires to the Context3D instance (which is accessible as the variable ‘context’).

I’ve written two myself, so far: a PixelateFilter (which was improved by Daniel Sperl of the Starling / Gamua team), and a NewsprintFilter which produces a kind of black and white halftone effect (the controls of this filter are kinda touchy and can produce some very bizarre and unexpected results. The default settings make a nice effect, in my opinion, and you could just leave them as is if you’d like. Of course, you’re welcome to tinker, but expect some oddness). The full code for the filters is below, but first is a quick ‘explorer’ demo that lets you test 5 different filters applied to an Image instance in Starling (my two, plus three included in the Starling download):

[kml_flashembed publishmethod=”static” fversion=”11.3.0″ movie=”http://blog.onebyonedesign.com/wp-content/uploads/2012/09/sfilters.swf” width=”512″ height=”420″ targetclass=”flashmovie” wmode=”direct”]

Get Adobe Flash player

[/kml_flashembed]

I’ve used the default filter package for the classes below, so they can be easily plunked down with the other Starling filters, but you’re welcome to change it to something else so as not to muddy up the core framework.

the PixelateFilter:

/**
 *	Copyright (c) 2012 Devon O. Wolfgang
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */

package starling.filters
{
    import flash.display3D.Context3D;
    import flash.display3D.Context3DProgramType;
    import flash.display3D.Program3D;
    
    import starling.textures.Texture;
    
    public class PixelateFilter extends FragmentFilter
    {
        private var mQuantifiers:Vector. = new [1, 1, 1, 1];
        private var mPixelSize:int;
        private var mShaderProgram:Program3D;
        
        /**
         *
         * @param       pixelSize               size of pixel effect
         */
        public function PixelateFilter(pixelSize:int)
        {
            mPixelSize = pixelSize;
        }
        
        public override function dispose():void
        {
            if (mShaderProgram) mShaderProgram.dispose();
            super.dispose();
        }
        
        protected override function createPrograms():void
        {
            var fragmentProgramCode:String =
                "div ft0, v0, fc0                                               \n" +
                "frc ft1, ft0                                                   \n" +
                "sub ft0, ft0, ft1                                              \n" +
                "mul ft0, ft0, fc0                                              \n" +
                "tex oc, ft0, fs0<2d, clamp, nearest>"
            
            mShaderProgram = assembleAgal(fragmentProgramCode);
        }
        
        protected override function activate(pass:int, context:Context3D, texture:Texture):void
        {
            // already set by super class:
            //
            // vertex constants 0-3: mvpMatrix (3D)
            // vertex attribute 0:   vertex position (FLOAT_2)
            // vertex attribute 1:   texture coordinates (FLOAT_2)
            // texture 0:            input texture
            
			// thank you, Daniel Sperl! ( http://forum.starling-framework.org/topic/new-feature-filters/page/2 )
            mQuantifiers[0] = mPixelSize / texture.width;
            mQuantifiers[1] = mPixelSize / texture.height;
            
            context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, mQuantifiers, 1);
            context.setProgram(mShaderProgram);
        }
        
        public function get pixelSize():int { return mPixelSize; }
        public function set pixelSize(value:int):void { mPixelSize = value; }
    }
}

and the NewsprintFilter:

/**
 *	Copyright (c) 2012 Devon O. Wolfgang
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */

package starling.filters
{
    import flash.display3D.Context3D;
    import flash.display3D.Context3DProgramType;
    import flash.display3D.Program3D;
    
    import starling.textures.Texture;

    public class NewsprintFilter extends FragmentFilter
    {
        private var mQuantifiers:Vector.	= new [1.0, 1.0, 1.0,  1.0];
	private var mConstants:Vector.		= new [4.0, 5.0, 10.0, 3.0];;
        private var mShaderProgram:Program3D;
		
	private var mSize:Number;
	private var mScale:Number;
	private var mAngle:Number;
        
        public function NewsprintFilter(size:Number = 120.0, scale:Number = 3.0, angleInRadians:Number = 0.0)
        {
	    mSize	= size;
	    mScale	= scale;
	    mAngle	= angleInRadians;
        }
        
        public override function dispose():void
        {
            if (mShaderProgram) mShaderProgram.dispose();
            super.dispose();
        }
        
        protected override function createPrograms():void
        {
            var fragmentProgramCode:String =
                "tex ft0, v0, fs0<2d, clamp, nearest, nomip> \n" +
		"mov ft1, fc0.w \n" +
		"add ft1.x, ft0.x, ft0.y \n" +
		"add ft1.x, ft1.x, ft0.z \n" +
		"div ft1.x, ft1.x, fc1.w \n" +
		"mul ft2, ft1.x, fc1.z \n" +
		"sub ft2, ft2, fc1.y \n" +
		"mov ft5, fc0.w \n" +
		"mov ft5.x, fc0.x \n" +
		"sin ft5.x, ft5.x \n" +
		"mov ft5.y, fc0.x \n" +
		"cos ft5.y, ft5.y \n" +
		"mul ft6, v0, fc0.z \n" +
		"sub ft6, ft6, fc0.w \n" +
		"mov ft7, fc0.w \n" +
		"mul ft7.z, ft5.y, ft6.x \n" +
		"mul ft7.w, ft5.x, ft6.y \n" +
		"sub ft7.x, ft7.z, ft7.w \n" +
		"mul ft7.x, ft7.x, fc0.y \n" +
		"mul ft7.z, ft5.x, ft6.x \n" +
		"mul ft7.w, ft5.y, ft6.y \n" +
		"add ft7.y, ft7.z, ft7.w \n" +
		"mul ft7.y, ft7.y, fc0.y \n" +
		"sin ft6.x, ft7.x \n" +
		"sin ft6.y, ft7.y \n" +
		"mul ft3, ft6.x, ft6.y \n" +
		"mul ft3, ft3, fc1.x \n" +
		"add ft2, ft2, ft3 \n" +
		"mov ft2.w, ft0.w \n" +
		"mov oc, ft2"
            
            mShaderProgram = assembleAgal(fragmentProgramCode);
        }
        
        protected override function activate(pass:int, context:Context3D, texture:Texture):void
        {
            // already set by super class:
            // 
            // vertex constants 0-3: mvpMatrix (3D)
            // vertex attribute 0:   vertex position (FLOAT_2)
            // vertex attribute 1:   texture coordinates (FLOAT_2)
            // texture 0:            input texture
            
	    mQuantifiers[0] = mAngle;
	    mQuantifiers[1] = mScale;
	    mQuantifiers[2] = mSize;
			
            context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, mQuantifiers, 1);
	    context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, mConstants,   1);
            context.setProgram(mShaderProgram);
        }
		
	public function get size():Number { return mSize; }
	public function set size(value:Number):void { mSize = value; }
		
	public function get angle():Number { return mAngle; }
	public function set angle(value:Number):void { mAngle = value; }
		
	public function get scale():Number { return mScale; }
	public function set scale(value:Number):void { mScale = value; }
    }
}

Enjoy…


And on a completely unrelated note, I happen to be on a quest for fulltime gainful employment at the moment – preferably in the Dublin Ireland area. So, should you or anyone you know be in need of, well, a guy who does the type of stuff I do, don’t hesitate to contact me. You can check out my online CV (or resume for those in the States) here. Don’t be shy. Pass it on. Thanks.

Date:
Category: