By Joe Ward
 
Created
15 October 2008
 

Requirements

 
Prerequisite knowledge

General experience building applications with Flash Professional or ActionScript is suggested. The examples in this article were built with Flash CS4. To examine the examples more closely, download the sample files and open the files in the Pixel Bender Toolkit and Flash CS4 or later.
 

 
User level

Intermediate
 

 
Sample files

Adobe Pixel Bender is a graphics processing engine supported by Adobe Flash Player 10, Adobe After Effects, and Adobe Photoshop. The language is based on fragment shader languages, such as OpenGL Shading Language (GLSL), used to optimize pixel drawing operations in 3D rendering. In Flash Player, you can use Pixel Bender programs to create filters, blends, area fills, and line fills.
 
Pixel Bender effects can be applied to any display object, including images, vector graphics, and even digital video. The execution speed is extremely fast; effects that would have taken seconds per frame to execute in ActionScript can now be achieved in real time:
 
This content requires Flash To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player. To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player.
Pixel Bender programs, called kernels, are written and compiled using the Adobe Pixel Bender Toolkit. The compiled bytecode produced by the toolkit can be loaded into a Shader object and used by your SWF content. The Pixel Bender Toolkit is installed automatically when you install Flash CS4 Professional.
 
Documentation for using Pixel Bender in Adobe Flash CS4 Professional is available in the Programming ActionScript 3.0 chapter: Working with Pixel Bender shaders and in the ActionScript Component and Language Reference in the Shader class section. This documentation contains a detailed description of the objects you can use with Pixel Bender in Flash CS4. Documentation on the Pixel Bender language is available from the Help menu of the Pixel Bender Toolkit.
 
If you want to see what's possible, as well as learn some new tricks, I recommend reviewing examples of Pixel Bender programs. In addition to the examples provided in this article, you can find a public repository of kernels hosted by Adobe at the Pixel Bender Exchange. The authors of these kernel programs have kindly agreed to share them with the wider Flash developer community. Perhaps your kernel will be the next one posted? There is also a forum for discussing Pixel Bender programming. Also visit the Pixel Bender Technology Center.
 

 
Pixel Bender language overview

Before jumping into kernel coding, let's take a brief look at the Pixel Bender language. You probably won't become an expert in the language just from reading this article, but the following discussion should help you understand programs written in the Pixel Bender language. For far more detail, refer to the Adobe Pixel Bender Language 1.0 Tutorial and Reference Guide, available from the Help menu of the Pixel Bender Toolkit application.
 
Pixel Bender uses procedural syntax similar to languages such as C, Java, and ActionScript. It includes built-in data types and functions targeted at image processing. If you are already familiar with ActionScript, then learning to write Pixel Bender kernels should be reasonably straightforward. The main differences in syntax between ActionScript and Pixel Bender language include:
 
  • Type declarations for variables go in front of the variable name, instead of after it. The var keyword is not used. Thus, instead of declaring an integer value with:
var foo:int;
you use the syntax:
 
int foo;
  • New objects cannot be created at runtime. Thus, the new keyword is not supported (or needed).
  • Pixel Bender uses the data type, float, instead of the data type, Number, for representing real numbers. The term float stands for floating-point (which just means that the decimal point can "float" to any position in the number).
  • It is important to remember that when you type a floating point number in a Pixel Bender program, you must include a decimal point. Otherwise, the Pixel Bender compiler will treat the number as an integer rather than a float value. Unlike ActionScript, Pixel Bender does not do any implicit data conversion. So if, for example, you type 1 instead of 1.0, you will get an abstruse error message from the Pixel Bender Toolkit.
  • Pixel Bender includes built-in vector data types. A vector type can be recognized by the number appended to the base type name. For example, a float3 type is a vector containing three elements of type float.
  • The following statement illustrates a typical use of a vector data type in Pixel Bender:
float4 rgbaPixel = float4( 1.0, 0.3, 0.2, 0.8 );
  • The statement above declares a new vector variable named rgbaPixel and assigns it a color value. The expression, float4( 1.0, 0.3, 0.2, 0.8 ), defines a literal float4 vector constant, which is assigned to the rgbaPixel variable.
  • Pixel Bender supports vector swizzling.
    The members of a vector can be accessed using dot notation and one of three sets of elements names: r,g,b,a; x,y,z,w; or s,t,p,q. Swizzling lets you rearrange the elements simply by reordering the element names. For example, the following statement swaps the red and green color channels of a pixel vector when assigning the pixel value to another variable:
     
pixel4 mixedUp = rgbaPixel.grba;
You can also repeat channels:
 
pixel4 allRed = rgbaPixel.rrra;
and drop channels:
 
pixel2 redAndBlue = rgbaPixel.rb;
Note: The choice of which set of element names to use with a vector variable is up to you. For example, myVector.r is the same as myVector.x. A good practice is to use the rgba set for colors, and the xyzw or stpq sets for positions. You cannot mix names from more than one set in the same reference.
 
  • Pixel Bender also supports built-in matrix data types. A matrix data type is similar to a vector, but contains a two dimensional array of numbers. The float3x3 matrix type, for example, contains three vectors of three elements.

  • Pixel Bender in Flash Player does not support loops, custom functions (other than the evaluatePixel() function), or arrays.
Note: When developing Pixel Bender kernels for Flash Player, be sure to enable the Turn on Flash Player Warnings and Errors option (under the Build menu of the toolkit window). With this option enabled, the compiler will inform you immediately when you are using unsupported Pixel Bender language features. (Otherwise, you won't see the errors until you try to export the kernel for Flash Player.)
 
 
Kernel walkthrough
The typical Pixel bender kernel performs the following tasks:
 
  • Samples a pixel from the input image
  • Applies a calculation to the sampled pixel color
  • Assigns the modified value to the output pixel
The following simple Pixel Bender kernel does each of these tasks. The program defines a kernel named ChannelScrambler:
 
<languageVersion : 1.0;> kernel ChannelScramblerFilter < namespace : "com.adobe.example"; vendor : "Adobe Systems Inc."; version : 1; description : "Changes the color channel order from rgba to gbra."; > { input image4 inputImage; output pixel4 outPixel; void evaluatePixel() { pixel4 samplePixel = sampleNearest( inputImage, outCoord() ); outPixel = samplePixel.gbra; } }
The kernel declares an input image, named inputImage, and an output pixel, named outPixel. In the evaluatePixel() function, the pixel at the output coordinates currently being processed is accessed using two built-in functions, sampleNearest() and outCoord(). Next, the sampled pixel is assigned to the outPixel variable, using swizzling to reorder the color channels.
 
This kernel produces the following result when used as a filter in SWF files generated in Flash CS4:
 
This content requires Flash To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player. To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player.
The required elements of a Pixel Bender program include the languageVersion tag:
 
<languageVersion: 1.0;>
and the kernel name declaration:
 
kernel ChannelScrambler{...}
Inside the kernel, there must be a single output declaration, such as output pixel4 outPixel, and an evaluatePixel() function. A kernel may have any number of inputs, including no inputs at all. However, the way you use a kernel in Flash Player creates additional requirements on the number of inputs. A shader used as a blend requires two inputs, a shader used as a filter requires one input, and a shader used as a fill does not require any inputs at all.
 
A Pixel Bender kernel is run once for every pixel in the output image. No state information is saved between runs, so it isn't possible to collect an average pixel value by accumulating the pixels sampled in each run. Each run of the kernel must compute all the information it needs—although you can pre-compute information in ActionScript and pass it in as an input or parameter.
 
 
Sampling
To access the pixel values in an input image, you must use the sampling functions. Sampling is accomplished with the following built-in functions:
 
  • sampleNearest() returns a vector containing the channel values of the pixel closest to the specified coordinates. (There is also a sampleLinear() function that behaves slightly differently.)
  • outCoord() returns the coordinates of the current output pixel.
As Pixel Bender processes an image, it executes the kernel for every pixel in the output image. The outCoord() function returns the coordinates of the current pixel. The Pixel Bender coordinate system is similar to that used by Flash Player. The origin is registered at the top, left corner. Positive values increase to the right and down. Pixels are always square.
 
You are not limited to sampling the pixel directly at the outCoord() position. For example, you could use the following sampling statement to sample the pixel that is 10 pixels right and 5 pixels down from the current pixel:
 
pixel4 inputPixel = sampleNearest( inputImage, outCoord() + float2( 10.0, 5.0 ) );
The expression, outCoord() + float2( 10.0, 5.0 ), adds a two-element vector to the vector of coordinates produced by the outCoord() function. An equivalent way to code the same expression is: float2(outCoord().x + 10.0, outCoord().y + 5.0).
 
If the sample coordinates go beyond the bounds of the input image, then a color vector containing all zeros is returned. For example, if the input is of type image4, then sampling an exterior pixel will return a completely transparent black pixel. If the input is of type image3, then it will return a black pixel (without an alpha channel). The Pixel Bender coordinate space theoretically extends to infinity. There are, of course, practical limits on the range of coordinates that can be expressed, as well as limits on the usefulness of sampling data that does not exist.
 
 
Working with pixels
Once you have a vector representing a pixel, there are a number of ways to work with the color values. For example, if you have a pixel4 variable named pix, you can address the individual color channels, or combinations of channels in the following ways:
 
  • Red channel: pix.r or pix[0]
  • Green channel: pix.g or pix[1]
  • Blue channel: pix.b or pix[2]
  • Alpha channel: pix.a or pix[3]
  • Red and alpha channels: pix.ra
  • All the color channels with red and blue swapped and no alpha channel: pix.bgr
A single color channel is a 32-bit floating-point number, normally between 0.0 (black) and 1.0 (white). The output color values can be outside this range, but they won't change the rendered appearance. In other words, pixel3(-1.0, -1.0, -1.0) is just as black as pixel3(0.0, 0.0, 0.0) when rendered as a bitmap. (However, if you run multiple filters on the same image, the difference can be significant since the second filter will see (-1.0, -1.0, -1.0) not (0.0, 0.0, 0.0) when sampling that pixel.)
 
You can perform arithmetic on a pixel vector with either scalar or vector values. When using scalar values, the operation is applied to each channel. For example, the following operation will divide the value of each channel in half (including the alpha channel):
 
pixel4 pix = sampleNearest( inputImage, outCoord()); pix = pix / 2.0;
The same operation could also be written using vectors:
 
pix = pix * pixel4( 0.5, 0.5, 0.5, 0.5 );
Although Pixel Bender images have 32-bits per channel, graphics in Flash CS4 only have 8-bits per channel. When a kernel is run in Flash Player, the input image data is converted to 32-bits per channel and then converted back to 8-bits per channel when kernel execution is complete.
 
 
Defining inputs
Inputs are declared with the input keyword:
 
input image4 sourceImage;
Inputs can be declared using the data types: image1, image2, image3, or image4. The different inputs in a kernel can have different numbers of channels. The number of channels in the output image produced by the shader is determined by the data type of the output pixel, not by the data types of the inputs.
 
You can declare more inputs than are required for the way a kernel is used in Flash Player. However, Flash Player will only automatically assign image data to the required inputs. You must assign an image to the extra inputs before assigning the kernel as a blend, filter or fill. For example, if your filter kernel used an additional image to create a textured effect, you would have to assign the texture as an input to the kernel before assigning the ShaderFilter containing the kernel to the filter array of a display object (more on how to do that later in this article).
 
 
Defining parameters
In addition to input images, you can supply other values to a kernel as parameters. Parameters are declared with the parameter keyword and can be any data type, except image (or region—which you can't use in kernels written for Flash Player anyway). You can declare metadata for parameters to specify the default, minimum, and maximum values. You can also supply a description. The metadata is declared between angle braces (<>) and can be accessed in Flash Player. It is always a good idea to define a reasonable default value for a parameter.
 
The following parameter statement declares a float3 parameter with metadata:
 
parameter float3 weights < defaultValue : float3( 0.5, 0.5, 0.5 ); minValue : float3( 0.1, 0.1, 0.1 ); maxValue : float3( 0.9, 0.9, 0.9 ); description : "A three element vector of weight values." >;
To access parameter values in Flash Player, you use the data property of the ActionScript Shader object containing the kernel. The current, minimum and maximum values of the above parameter can be accessed in ActionScript with the following statements (assuming that myShader is a Shader object containing a kernel with this parameter):
 
var currentWeights:Array = myShader.data.weights.value; var minWeights:Array = myShader.data.weights.minimumValue; var maxWeights:Array = myShader.data.weights.maximumValue;
Since the parameter is a float3 vector type, the ActionScript arrays returned will contain three elements. If the parameter was a scalar type, such as float, then the arrays returned would contain a single element.
 

 
Exporting and loading a kernel

Use the Export Kernel Filter for Flash Player command from the Pixel Bender Toolkit to compile and export the kernel for use in Flash Player. Kernels are exported with a file extension .pbj (see Figure 1).
 
Exporting the kernal from the Pixel Bender Toolkit
Figure 1. Exporting the kernal from the Pixel Bender Toolkit
To load a Pixel Bender kernel in Flash Player, you must either embed or load the compiled kernel.
 
The Embed tag, newly supported in Flash CS4 Professional, instructs the ActionScript compiler to embed the Pixel Bender kernel when it creates the SWF file. The Embed tag is used with a variable definition of type, Class, as shown in the following example:
 
[Embed(source="channelscrambler.pbj", mimeType="application/octet-stream")] var ChannelScramblerKernel:Class;
To use the kernel, create an instance of the class, in this case, ChannelScramblerFilter. The following code uses an embedded kernel to create new Shader and ShaderFilter objects, which are applied it to a MovieClip instance on the Stage:
 
var camellia_mc:MovieClip; //Embed the PixelBender kernel in the output SWF [Embed(source="channelscrambler.pbj", mimeType="application/octet-stream")] var ChannelScramblerKernel:Class; var shader:Shader = new Shader(new ChannelScramblerKernel() ); var shaderFilter:ShaderFilter = new ShaderFilter( shader ); camellia_mc.filters = [ shaderFilter ];
Using the Embed tag is typically the simplest method of loading Pixel Bender kernels, but you can also load kernels at runtime. The following example uses the URLLoader class to load a kernel:
 
var camellia_mc:MovieClip; var urlRequest:URLRequest = new URLRequest( "channelscrambler.pbj" ); var urlLoader:URLLoader = new URLLoader(); urlLoader.dataFormat = URLLoaderDataFormat.BINARY; urlLoader.addEventListener( Event.COMPLETE, applyFilter ); urlLoader.load( urlRequest ); function applyFilter( event:Event ):void { trace("apply"); urlLoader.removeEventListener( Event.COMPLETE, applyFilter ); var shader:Shader = new Shader( event.target.data ); var shaderFilter:ShaderFilter = new ShaderFilter( shader ); camellia_mc.filters = [ shaderFilter ]; }
Note: When you use the Embed tag, Flash CS4 Professional uses the Flex.swc library from the Flex SDK. This SDK is installed with Flash CS4, typically in the Common/Configuration/ActionScript 3.0/libs/flex_sdk_3 subdirectory. The first time you test or publish a movie using the Embed tag, you will be asked to confirm the location of the Flex SDK. You can click OK to use the Flex SDK installed with Flash CS4, or you can change the path if you prefer to use a different version of Flex. The setting used for a project can be changed later on the Advanced ActionScript 3.0 Settings dialog box, under the Library path tab. (Access the Advanced ActionScript 3.0 Settings dialog box from the Flash tab of the Publish Settings by clicking the Settings button next to the Script drop-down menu.)
 

 
Using blends

A blend combines the colors in the display object to which the blend is applied with the colors below the object on the Stage. Flash Player supports several built-in blends, defined in the BlendMode class. As a learning exercise, we duplicate a couple of the built-in blends. Then we create a blend that can't be easily achieved using the built-in options.
 
To apply a blend to a display object, create a Shader object with the loaded kernel bytecode and assign it to the blendShader property of the display object. A blend kernel must have two inputs. The first input is the foreground display object (whose blendShader property is set). The second input is whatever is behind the foreground object. If you use additional inputs, perhaps for creating masks or textures, you must assign an image in the form of a BitmapData object to these inputs yourself before applying the blend.
 
 
Multiply
In a multiply blend, each color in the foreground object is multiplied by the color of the background object. This blend darkens the result, except in the scenario where one of the images is pure white:
 
This content requires Flash To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player. To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player.
The following kernel declares two inputs, named foreground and background, and an output, named result. In the evaluatePixel() function, the pixel at the current coordinate is sampled in each image using the sampleNearest() function. The pixels are then multiplied together.
 
<languageVersion : 1.0;> kernel MultiplyBlend < namespace : "com.adobe.example"; vendor : "Adobe Systems Inc."; version : 1; description : "A simple multiply blend"; > { input image4 foreground; input image4 background; output pixel4 result; void evaluatePixel() { pixel4 a = sampleNearest( foreground, outCoord() ); pixel4 b = sampleNearest( background, outCoord() ); result = a * b; } }
Note: When you multiply two vectors in the Pixel Bender language, each corresponding pair of component values are multiplied together. Thus, if a and b are float2 vectors, for example, the statement:
 
a * b
is equivalent to the two statements:
 
a.x * b.x a.y * b.y
The following ActionScript code is used to load and apply the blend:
 
var camellia_mc:MovieClip; //Embed the Pixel Bender kernel in the output SWF [Embed(source="multiplyblend.pbj", mimeType="application/octet-stream")] var MultiplyBlendKernel:Class; var shader:Shader = new Shader( new MultiplyBlendKernel() ); camellia_mc.blendShader = shader;
 
Screen
A screen blend inverts the colors, multiplies them together, and then inverts the result. This has the opposite effect as applying the multiply blend. The screen blend lightens the result, except in the scenario where one of the images is black:
 
This content requires Flash To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player. To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player.
<languageVersion : 1.0;> kernel ScreenBlend < namespace : "com.adobe.example"; vendor : "Adobe Systems Inc."; version : 1; description : "Screen blend"; > { input image4 foreground; input image4 background; output pixel4 result; void evaluatePixel() { pixel4 a = sampleNearest( background, outCoord() ); pixel4 b = sampleNearest( foreground, outCoord() ); result = 1.0 - (1.0 - a) * (1.0 - b); } }
As you can see, this kernel is almost identical to the multiply kernel. Only the mathematical operation used to produce the result has changed. This example uses the same ActionScript code, except it is loading and applying a different shader.
 
 
Hard light
A hard light blend is a combination of the multiply and screen blends. If the foreground pixel is lighter than 50% gray, then a screen blend is performed. Otherwise, a multiply blend is performed:
 
This content requires Flash To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player. To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player.
<languageVersion : 1.0;> kernel HardLightBlend < namespace : "com.adobe.example"; vendor : "Adobe Systems Inc."; version : 1; description : "Hard light blend"; > { input image4 foreground; input image4 background; output pixel4 result; void evaluatePixel() { pixel4 a = sampleNearest( background, outCoord() ); pixel4 b = sampleNearest( foreground, outCoord() ); float gray = (b.r + b.g + b.b)/3.0; if( gray < 0.5 ) { result = 2.0 * a * b; } else { result = 1.0 - 2.0 * (1.0 - a) * (1.0 - b); } } }
The hard light blend is a bit more complicated than the previous two. First, the gray level of the pixel in the foreground image is calculated by averaging the color channels. Then, an if statement is used to select either a multiply or a screen blend operation.
 
Again, this example uses the same ActionScript code, but it loads and applies a different shader.
 
 
Perlin grain
Now for something that is a bit more difficult to achieve with the built-in blends. The next filter uses a noise texture and sin() functions to generate a wood grain or marbling effect. The effect depends on the characteristics of the noise texture and generally looks best with Perlin-type noise:
 
This content requires Flash To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player. To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player.
The shader works by sampling the pixels values from the noise image. Instead of using the noise pixels directly in the image, the shader feeds the noise value into a series of sin() functions. The background is multiplied by the result. A turbulence parameter is used to control the curviness of the resulting effect:
 
<languageVersion: 1.0;> kernel GrainBlend < namespace : "com.adobe.example"; vendor : "Adobe Systems Inc."; version : 1; description : "Creates a wood grain or marbling effect"; > { input image4 background; input image4 noise; output pixel4 dst; parameter float turbulence < maxValue : 500.0; minValue : 0.0; defaultValue : 150.0; >; void evaluatePixel() { pixel4 a = sampleNearest(background, outCoord()); pixel4 b = sampleNearest(noise, outCoord()); float alpha = a.a; //save the original alpha if( (b.a > 0.0) && (a.a > 0.0)){ float seed = outCoord().x + (((b.r + b.g + b.b)/3.0) * turbulence); float grain = (0.7 * sin(seed) + 0.3 * sin(2.0 * seed + 0.3) + 0.2 * sin(3.0 * seed + 0.2)); dst = sampleNearest(background, outCoord()) * (grain + 0.5); dst.a = alpha; //restore the original alpha } else { //Just copy the background pixel outside the area of the noise image dst = sampleNearest(background, outCoord()); } } }
The ActionScript code used for this example loads and applies the shader in the same way as the previous examples. In addition, the example creates a slider to control the turbulence parameter:
 
import fl.controls.Slider; import fl.events.SliderEvent; var noise_mc:MovieClip; var turbulence:Slider; //Embed the Pixel Bender kernel in the output SWF [Embed(source="grainblend.pbj", mimeType="application/octet-stream")] var GrainBlendKernel:Class; //Create the Shader object var shader:Shader = new Shader( new GrainBlendKernel() ); //Set the slider values based on the parameter metadata turbulence.minimum = shader.data.turbulence.minValue; turbulence.maximum = shader.data.turbulence.maxValue; turbulence.value = shader.data.turbulence.defaultValue; turbulence.liveDragging = true; turbulence.addEventListener( SliderEvent.CHANGE, updateFilter ); //Apply the blend noise_mc.blendShader = shader; function updateFilter( event:SliderEvent ):void { shader.data.turbulence.value = [turbulence.value]; noise_mc.blendMode = BlendMode.NORMAL; noise_mc.blendShader = shader; }
The example uses a slider to control the shader turbulence parameter. The minimum, maximum and starting values of the slider are set according to the parameter metadata. Then, the updateFilter() method is used to change the parameter value whenever a change event is dispatched by the Slider object. Because a shader object is cloned when you set the blendShader property of a display object, you cannot simply change the parameter value of the original Shader object. You must also reassign the updated Shader object to the blendShader property.
 
For simplicity, this example uses a bitmap for the noise texture. You can also use the perlinNoise() function of the BitmapData class to create a suitable texture.
 

 
Using filters

Shaders used as filters are applied to a single image. In addition to creating a Shader object, as we did for blends, you must also create a ShaderFilter object, passing in the Shader containing the kernel:
 
var shader:Shader = new Shader( loadedBytes ); var shaderFilter:ShaderFilter = new ShaderFilter( shader );
The ShaderFilter object "wraps" the shader and allows you to use the shader like a built-in filter, by adding it to the filters array of a display object:
 
displayObject.filters = [ shaderFilter ];
The object to which a shader is applied is automatically set as the first input of the kernel. If a filter kernel takes additional images as inputs, these must be set before the filter is assigned to a display object.
 
We've already looked at a simple filter, ChannelScrambler, so let's go straight to a more complex example, the Gaussian blur.
 
 
Gaussian blur
A Gaussian blur is an example of a convolution filter. (Convolution is a fancy word for a filter that computes a weighted average of nearby pixels.) Although Flash CS4 Professional includes a built-in class for creating convolution filters of arbitrary size, programming a Gaussian blur in Pixel Bender is a good exercise that demonstrates several important aspects of Pixel Bender.
 
This content requires Flash To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player. To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player.
General-purpose convolution filters aren't easy to achieve in Pixel Bender because Flash Player doesn't support loops in kernel code. Instead of using a for loop, we have to write an individual program statement to sample each pixel in the neighborhood. This example kernel creates a Gaussian blur that can have a sampling radius between 1 and 6 (corresponding to convolution matrices ranging in size between 3 × 3 to 13 × 13).
 
The filter takes advantage of the fact that a Gaussian blur is separable, which simply means that you can perform the operation in two passes. One pass blurs the image horizontally, and the other pass blurs the image vertically. This saves several calculations, since for each final pixel, only about 4 times the radius pixels have to be sampled, and weighted and averaged, rather than 2 times the radius2 pixels. For example, at the largest radius supported by this filter, 26 input pixels are sampled for each final output pixel in both the vertical and horizontal passes combined. If the filter computed the blur in a single pass, 169 input pixels would have to be sampled for each output pixel.
 
To overcome the lack of a for loop, the kernel treats each integer radius value separately. For each allowed radius value, the two pixels located at that distance to either side of the current pixel are sampled. The Gaussian weights are the same for both pixels, so they are added together. Once all the necessary pixels are sampled, the weight and scale factors are applied.
 
The following kernel code is used for the horizontal pass (a similar kernel is used for the vertical pass):
 
<languageVersion: 1.0;> kernel HorizontalGaussianBlur < namespace : "com.adobe.example"; vendor : "Adobe Systems Inc."; version : 1; description : "The horizontal convolution of a Gaussian blur"; > { input image4 src; output float4 result; parameter int radius < minValue : 1; maxValue : 6; defaultValue : 6; >; void evaluatePixel() { pixel4 center, band1, band2, band3, band4, band5, band6; float2 pos = outCoord(); //Sample image in bands if( radius > 5 ) { band6 = sampleNearest(src, float2(pos.x - 6.0, pos.y)) + sampleNearest(src, float2(pos.x + 6.0, pos.y)); } if( radius > 4 ) { band5 = sampleNearest(src, float2(pos.x - 5.0, pos.y)) + sampleNearest(src, float2(pos.x + 5.0, pos.y)); } if( radius > 3 ) { band4 = sampleNearest(src, float2(pos.x - 4.0, pos.y)) + sampleNearest(src, float2(pos.x + 4.0, pos.y)); } if( radius > 2 ) { band3 = sampleNearest(src, float2(pos.x - 3.0, pos.y)) + sampleNearest(src, float2(pos.x + 3.0, pos.y)); } if( radius > 1 ) { band2 = sampleNearest(src, float2(pos.x - 2.0, pos.y)) + sampleNearest(src, float2(pos.x + 2.0, pos.y)); } band1 = sampleNearest(src, float2(pos.x - 1.0, pos.y)) + sampleNearest(src, float2(pos.x + 1.0, pos.y)); center = sampleNearest(src, pos); //Apply weights and compute resulting pixel if( radius == 6 ) { result = (band6 + (band5 * 12.0) + (band4 * 66.0) + (band3 * 220.0) + (band2 * 495.0) + (band1 * 792.0) + (center * 924.0))/4096.0; } if( radius == 5 ) { result = (band5 + (band4 * 10.0) + (band3 * 45.0) + (band2 * 120.0) + (band1 * 210.0) + (center * 252.0))/1024.0; } if( radius == 4 ) { result = (band4 + (band3 * 8.0) + (band2 * 28.0) + (band1 * 56.0) + (center * 70.0))/256.0; } if( radius == 3 ) { result = (band3 + (band2 * 6.0) + (band1 * 15.0) + (center * 20.0))/64.0; } if( radius == 2 ) { result = (band2 + (band1 * 4.0) + (center * 6.0))/16.0; } if( radius == 1 ) { result = (band1 + (center * 2.0))/4.0; } } }
Both kernels must be applied as filters for the complete effect. It does not matter which order the filters are applied in. The ActionScript code shown below uses a slider to control the radius parameter:
 
import fl.controls.Slider; import fl.events.SliderEvent; var camellia_mc:MovieClip; var radiusSlider:Slider; //Embed the Pixel Bender kernel in the output SWF [Embed(source="verticalgaussianblur.pbj", mimeType="application/octet-stream")] var VerticalBlurKernel:Class; [Embed(source="horizontalgaussianblur.pbj", mimeType="application/octet-stream")] var HorizontalBlurKernel:Class; //Create the shaders var vBlurShader:Shader = new Shader( new VerticalBlurKernel() ); var hBlurShader:Shader = new Shader( new HorizontalBlurKernel() ); //Create the filters var hBlurFilter:ShaderFilter = new ShaderFilter( hBlurShader ); var vBlurFilter:ShaderFilter = new ShaderFilter( vBlurShader ); //Initialize the slider using the radius parameter metadata radiusSlider.value = hBlurShader.data.radius.value; radiusSlider.minimum = hBlurShader.data.radius.minValue; radiusSlider.maximum = hBlurShader.data.radius.maxValue; radiusSlider.liveDragging = true; radiusSlider.addEventListener( SliderEvent.CHANGE, updateFilters ); //Apply the filters camellia_mc.filters = [ hBlurFilter, vBlurFilter ]; //Reapply the filters when the slider is changed function updateFilters( event:SliderEvent ):void { hBlurShader.data.radius.value = vBlurShader.data.radius.value = [radiusSlider.value]; camellia_mc.filters = [ hBlurFilter, vBlurFilter ]; }
The example sets the radius of each filter to the same value. As with blends, the shader filters must be reassigned to the display object after changing parameter values.
 

 
Using fills

To use a kernel as an area fill, create a Shader object containing the kernel bytecode and pass that to the beginShaderFill() function of the display object graphics property when drawing the object. Like bitmap fills, shader fills are registered to the origin of the display object. This registration can be adjusted by using a translation matrix.
 
Shaders used as area or line fills are not automatically assigned an input image. (If you need an image as part of the fill algorithm, you must explicitly assign an image to the input before calling the beginShaderFill() method.)
 
 
Checker fill
The next fill example creates a checker pattern. The size and color of the checker squares are controlled by kernel parameters:
 
This content requires Flash To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player. To view this content, JavaScript must be enabled, and you need the latest version of the Adobe Flash Player.
The checker algorithm uses the modulo function to compare the current pixel position to multiples of the checker size:
 
float vertical = mod(position.x, checkerSize * 2.0);
This modulo function returns the remainder produced by dividing the x coordinate by checkerSize multiplied by 2. If, for example, checkerSize is equal to 10, then we get the pattern 0-19, 0-19, 0-19,... as x increases across the image. So whenever the result is less than the checker size, the kernel draws color A, otherwise it draws color B. That creates stripes. To produce the checker pattern, we have to apply the technique in both the horizontal and vertical directions, like this:
 
float vertical = mod(position.x, checkerSize * 2.0); float horizontal = mod(position.y, checkerSize * 2.0);
The remaining question is how to combine these results. The logical XOR operator (^^), which only returns true when just one of the inputs is true, is perfect here:
 
( vertical < checkerSize ) ^^ ( horizontal < checkerSize )
The following example displays the full kernel code:
 
<languageVersion : 1.0;> kernel CheckerFill < namespace : "com.adobe.example"; vendor : "Adobe Systems Inc."; version : 1; description : "A checkered field generator"; > { output pixel4 dst; parameter float checkerSize < defaultValue : 10.0; minValue : 1.0; maxValue : 75.0; >; parameter pixel4 colorA < defaultValue : pixel4(0.0, 1.0, 1.0, 1.0); >; parameter pixel4 colorB < defaultValue : pixel4( 0.0, 0.0, 0.0, 1.0 ); >; void evaluatePixel() { float2 position = outCoord(); float vertical = mod(position.x, checkerSize * 2.0); float horizontal = mod(position.y, checkerSize * 2.0); dst = (( vertical < checkerSize ) ^^ ( horizontal < checkerSize )) ? colorA : colorB; } }
The ActionScript code used for this example is slightly more complex than the previous examples, both because more parameters are used for the kernel and because the parameters themselves are more complex.
 
In the example, the initialize() function is called when the CheckerFill kernel has finished loading. This function creates the Shader object and uses the metadata of the kernel parameters to set up the initial values for the controls. It then calls the drawShape() function, which draws a circle using a shader fill.
 
To update the fill, the drawShape() function is called again whenever a change event is dispatched by one of the controls. The function sets the kernel parameters based on the current control values, clears the current graphics, and redraws the shape:
 
import fl.controls.Slider; import fl.events.SliderEvent; import fl.controls.ColorPicker; import fl.events.ColorPickerEvent; var filledShape:Shape = new Shape(); var checkerSize:Slider; var colorA:ColorPicker; var colorB:ColorPicker; var areaShader:Shader; //Embed the Pixel Bender kernel in the output SWF [Embed(source="checkerfill.pbj", mimeType="application/octet-stream")] var CheckerFillKernel:Class; //Create the Shader object using the embedded kernel areaShader = new Shader( new CheckerFillKernel() ); //Set controls using shader parameter metadata checkerSize.minimum = areaShader.data.checkerSize.minValue; checkerSize.maximum = areaShader.data.checkerSize.maxValue; checkerSize.value = areaShader.data.checkerSize.value; checkerSize.liveDragging = true; checkerSize.addEventListener( SliderEvent.CHANGE, updateFilters ); colorA.selectedColor = vectorToColor( areaShader.data.colorA.value ); colorA.addEventListener( ColorPickerEvent.CHANGE, updateFilters ); colorB.selectedColor = vectorToColor( areaShader.data.colorB.value ); colorB.addEventListener( ColorPickerEvent.CHANGE, updateFilters ); filledShape.x = 150; drawShape(); addChild( filledShape ); function updateFilters( event:Event ):void { areaShader.data.checkerSize.value = [ checkerSize.value ]; areaShader.data.colorA.value = colorToVector( colorA.selectedColor | 0xff000000 ); areaShader.data.colorB.value = colorToVector( colorB.selectedColor | 0xff000000 ); drawShape(); } function drawShape():void { with( filledShape.graphics ) { clear(); beginShaderFill( areaShader ); drawCircle( 100, 75, 75 ); endFill(); } } function vectorToColor( pixelChannels:Array ):uint { return (pixelChannels[3] * 0xff << 24) | (pixelChannels[0] * 0xff << 16) | (pixelChannels[1] * 0xff << 8) | pixelChannels[2] * 0xff; } function colorToVector( color:uint ):Array { var result:Array = new Array(4); result[3] = ((color >> 24) & 0x000000ff)/0xff; result[0] = ((color >> 16) & 0x000000ff)/0xff; result[1] = ((color >> 8) & 0x000000ff)/0xff; result[2] = (color & 0x000000ff)/0xff; return result; }
In the earlier examples we incorporated a slider to set the kernel parameters. The color picker controls used in this example are not quite as straightforward. Parameter values are accessed in Flash Player as an array. In the case of a scalar parameter, such as checkerSize, the array holds a single value. For vector types such as colors, the array contains an element for each element of the vector. Since the colorA and colorB parameters are of type pixel4, there are four values in the array, one for each channel in the pixel. A color in ActionScript, on the other hand, is represented as a single 32-bit uint value containing all the channel information. In addition, the alpha channel is the first channel in a uint color, but it is the last channel in the Shader object parameter array. The example functions, vectorToColor() and colorToVector(), translate the colors between the two forms so that we can transfer color selections between the ColorPicker objects and the shader parameters.
 
The vectorToColor() function works by multiplying each color channel returned by the Pixel Bender kernel by 256 (hexadecimal value 0xff). The resulting value is then shifted to the corresponding argb position within the 32-bit uint value using the bitwise left shift operator (<<). Finally, the four channels are combined into a single uint with the bitwise OR operator ( | ) and returned.
 
The colorToVector() function performs the inverse operation. For each channel, the function does a bitwise right shift operation (>>), masks out the bits that belong to any other channel with the bitwise AND operator (&) and divides the result by 256 (0xff) to scale the value between 0 and 1. The results are assigned to the appropriate element of an array, in the order expected by Pixel Bender, and then returned.