Bitmap Filters Chapter 9: Drawing with Pixels 239 9 g.beginFill(0xFFFF00, 1); 10 g.drawRoundRect(0, 0, 200, 50, 20); 11 g.endFill(); 12 13 sp.filters = [ds]; 14 addChild(sp); Because we went the simple route of using a sprite for our interactive element (rather than building a multistate button with the SimpleButton class, as seen in the applied example at the end of Chapter 8), we need to set the buttonMode property of the sprite to true in line 15. This won’t create the up, over, and down states of a button symbol, but it will provide visual feedback by chang- ing the cursor to the hand cursor when over the sprite. The listeners in lines 16 through 18 trigger functions based on mouse behav- ior. The mouse down listener function, onDown() (lines 20 to 22) removes the drop shadow effect from the sprite by clearing the filters array. Both the mouse up and mouse out listeners point to the onUp() function in lines 23 to 25, which repopulates the filters array with the drop shadow. This restores the elevated “up” appearance to the sprite. 15 sp.buttonMode = true; 16 sp.addEventListener(MouseEvent.MOUSE_DOWN, onDown, false, 0, true); 17 sp.addEventListener(MouseEvent.MOUSE_UP, onUp, false, 0, true); 18 sp.addEventListener(MouseEvent.MOUSE_OUT, onUp, false, 0, true); 19 20 function onDown(evt:MouseEvent):void { 21 sp.filters = []; 22 } 23 function onUp(evt:MouseEvent):void { 24 sp.filters = [ds]; 25 } Another way to handle this task would be to leave the ds filter active, but change some of its properties. For example, rather than eliminating the shadow, you could reduce its distance value when the button is pressed. When the shadow appears closer to the object, the object’s virtual elevation appears to be reduced. Using the BlurFilter to create an airbrush With just a couple of lines of additional code, you can turn the brush from the drawing tool developed previously in this chapter into an airbrush. The following ActionScript excerpt shows new code in bold, and can be found in the paint_tool_erase_blur.fla source file. The first new line (30) creates an instance of the BlurFilter that blurs 40 pixels in the x and y direction. The second new line (39) applies the filter to the current tool. Figure 9-10 shows the result of softening the brush and eraser with these modifications. 26 canvas.addEventListener(MouseEvent.MOUSE_DOWN, 27 onDown, false, 0, true); 28 canvas.addEventListener(MouseEvent.MOUSE_UP, onUp, 29 false, 0, true); 30 canvas.addEventListener(Event.ENTER_FRAME, onLoop, 31 false, 0, true); N O T E For information about creating arrays with bracket syntax ([]), see Chapter 2. N O T E Filters can be used in creative ways. If you wanted to simulate casting a shadow from a moving light source, you could vary the distance, angle, and alpha values of the DropShadowFilter. See the “Animating Filters” post at the companion website, http://www. LearningActionScript3.com, for more information. Download from Wow! eBook <www.wowebook.com> Part II: Graphics and Interaction 240 Bitmap Filters 32 33 var blur:BlurFilter = new BlurFilter(40, 40); 34 35 function onDown(evt:MouseEvent):void { 36 mouseIsDown = true; 37 if (evt.shiftKey) { 38 tool = eraser; 39 } else { 40 tool = brush; 41 } 42 tool.filters = [blur]; 43 } Advanced Filters A number of more advanced ActionScript filters allow you to mimic some of the special effects features in pixel-editing applications like Photoshop. We’ll focus on the convolution, displacement map, and Perlin noise filters in this section, and then group a trio of color filters together in the following section. Convolution filter Convolution filtering is typically a part of many visual effects in most, if not all, pixel-editing applications. Photoshop offers direct access to a convolution filter (renamed to Custom some years ago, and found in the Filters →Other menu), but usually the filter works quietly behind the scenes. Put simply, a convolution filter calculates pixel color values by combining color values from adjacent pixels. Combining these colors in different ways (using different values in the matrix) produces a wide variety of image effects. These effects include, but are not limited to, blurring, sharpening, embossing, edge detection, and brightness. Using the filter effectively requires at least a working knowledge of matrices so, if you haven’t read Chapter 8, do so now. Although still a matrix, visualized as a grid of numbers, the ConvolutionFilter doesn’t use the same matrix for- mat discussed in Chapter 8. Instead, you can define any number of rows and columns in a convolution matrix, and the structure of the matrix determines how each pixel is affected. Unless you plan to delve deeply into writing your own filters, you probably don’t need to learn the algorithms behind how a convolution matrix works. In most circumstances, you’ll use an existing matrix for a specific effect and use experimentation to determine a satisfactory setting. To give your experimenting some focus, let’s look at three parts of the matrix: the center grid element, the grid symmetry, and the sum of all grid elements. Consider a 3 × 3 matrix. The center value in the matrix represents the current pixel (all pixels in an image are analyzed), while the remaining elements are the eight adjacent pixels. The numbers in each matrix position determine how the color values of that pixel affect the current pixel. The basic idea is that each of the nine pixels is given a weight, or importance, that affects how they are altered. Figure 9-10. A Blur filter applied to the drawing tool in the ongoing paint application Download from Wow! eBook <www.wowebook.com> Bitmap Filters Chapter 9: Drawing with Pixels 241 Blur Brightness Edges Emboss Original Sharpen Figure 9-11. Example convolution filter effects A convolution matrix of all zeros will turn an image black because no color values are used for any pixel, including the current pixel in the center of the grid. Using a 1 in the center of an all-zero grid won’t change the image because the current pixel is unchanged (default value of 1), and no color values from surrounding pixels are used. Placing a 2 in the center of an all-zero grid will brighten the image because no colors from surrounding pixels are used, but the weight of the color values of the current pixel are increased. The ConvolutionFilter constructor appearing on line 8, 14, and 20 in the following code example requires two parameters, the number of rows and the number of columns. A third, optional parameter is the matrix used to affect the image. If no matrix is furnished a default (no change) matrix is used. Applying a default matrix allows you to remove any changes made by prior convolution filters, as seen in the event listener that follows. This code is found in the convolution_filter_basics.fla source file. 1 var black:ConvolutionFilter; 2 var noChange:ConvolutionFilter; 3 var brightness:ConvolutionFilter; 4 5 var blackArr:Array = [0, 0, 0, 6 0, 0, 0, 7 0, 0, 0]; 8 black = new ConvolutionFilter(3, 3, blackArr); 9 mc0.filters = [black]; 10 Download from Wow! eBook <www.wowebook.com> Part II: Graphics and Interaction 242 Bitmap Filters 11 var noChangeArr:Array = [0, 0, 0, 12 0, 1, 0, 13 0, 0, 0]; 14 noChange = new ConvolutionFilter(3, 3, noChangeArr); 15 mc1.filters = [noChange]; 16 17 var brightnessArr:Array = [0, 0, 0, 18 0, 2, 0, 19 0, 0, 0]; 20 brightness = new ConvolutionFilter(3, 3, brightnessArr); 21 mc2.filters = [brightness]; 22 23 stage.addEventListener(MouseEvent.CLICK, onClick, 24 false, 0, true); 25 26 function onClick(evt:MouseEvent):void { 27 for (var i:int = 0; i < 3; i++) { 28 var mc:MovieClip = MovieClip(getChildAt(i)); 29 mc.filters = [noChange]; 30 } 31 } Now let’s focus on the symmetry of the surrounding grid elements. The remainder of the code in this section is found in the convolution_filter_more. fla source file, and demonstrates centralizing the filter creation into a func- tion called convFilter() to reduce repeating code. The function appears at the end of the discussion so you can focus on the matrices we’re discussing. The embossUp example uses reduced values for the three pixels to the upper left of the current pixel, and increased values for the three pixels to the lower right of the current pixel. The result is a traditional embossing effect (see Figure 9-11). By contrast, the embossDown example reverses this effect, seemingly stamping into the image. 32 var embossUp:Array = [-1, -1, 0, 33 -1, 1, 1, 34 0, 1, 1]; 35 convFilter(mc0, embossUp); 36 37 var embossDown:Array = [1, 1, 0, 38 1, 1, -1, 39 0, -1, -1]; 40 convFilter(mc1, embossDown); As our third area of interest, we want to focus on the fact that overall image brightness is affected by the sum of all elements in the matrix. In each of the prior examples, all the matrix elements add up to 1, except brighter (which adds up to 2) and black (which adds up to 0). The following example is a matrix that uses the left, top, right, and bottom adjacent pixel color values to affect the current pixel. The result is a blurring effect. However, a dramatic brightening of the image occurs because the sum of the matrix elements is 5, not 1. The affected image is five times brighter. If this is not desired, you can compensate by using an optional fourth param- eter of the ConvolutionFilter class, called a divisor. The sum of the matrix will be divided by this value and the result will affect the brightness. If the Download from Wow! eBook <www.wowebook.com> Bitmap Filters Chapter 9: Drawing with Pixels 243 result is 1, the brightening effect of the matrix will be eliminated. The first filter instance here uses only the first three parameters without compensating for brightness. The second instance adds the divisor as the fourth parameter, bringing the brightness back to the original state, leaving only the blur effect. 41 var blurBright:Array = [0, 1, 0, 42 1, 1, 1, 43 0, 1, 0]; 44 convFilter(mc2, blurBright); 45 46 var blurOnly:Array = [0, 1, 0, 47 1, 1, 1, 48 0, 1, 0]; 49 convFilter(mc3, blurOnly, 5); As a summary of what we’ve learned, we’ll look at how sharpen and find edges filters differ. The sharpen instance that follows uses negative values for the left, top, right, and bottom pixels, which is the opposite of blur and causes the pixels to pop. The sum of the matrix is 1, meaning there is no increase or decrease in brightness. The edges instance uses the same values for the surrounding pixels, but the sum of the array is 0. This has a sharpening effect but reduces the brightness, leaving only the emphasized edges visible. 50 var sharpen:Array = [ 0, -1, 0, 51 -1, 5, -1, 52 0, -1, 0]; 53 convFilter(mc4, sharpen); 54 55 var edges:Array = [ 0, -1, 0, 56 -1, 4, -1, 57 0, -1, 0]; 58 convFilter(mc5, edges); The function that applies these matrices differs from the prior source file in only one major respect: It provides for the use of the fourth optional param- eter, divisor, to compensate for accumulated brightness. 59 function convFilter(dispObj:DisplayObject, matrix:Array, 60 divisor:int=1):void { 61 var conv:ConvolutionFilter = 62 new ConvolutionFilter(3, 3, matrix, divisor); 63 dispObj.filters = [conv]; 64 } Perlin noise and displacement map Two other very useful and entertaining effects supported by ActionScript are the Perlin noise generator and the displacement map filter. Perlin noise is widely used for generating naturalistic animated effects like fog, clouds, smoke, water, and fire, as well as textures like wood, stone, and terrain. Displacement maps are used to translate (or displace) pixels to add extra dimension to surfaces. They are commonly used to add realism to textures (such as a pitted or grooved surface) as well as distort images to appear as if seen through a refracting material like glass or water. N O T E Ken Perlin developed the Perlin noise algorithm while creating the special effects for the 1982 film Tron. At the time, the extensive use of effects in that film may have been cost-prohibitive using traditional multi-exposure film compositing techniques. Perlin noise was used to manipulate the near-constant computer-generated glows and shad- ings, among other effects. Mr. Perlin won an Academy Award for Technical Achievement in 1997 for his contribu- tions to the industry. Download from Wow! eBook <www.wowebook.com> Part II: Graphics and Interaction 244 Bitmap Filters The following exercise, found in the perlin_displacement.f la source file, will cre- ate an animated Perlin noise texture that will then be used as the source for a displacement map. In our example, the Perlin noise will include random areas of blue, as shown in Figure 9-12. These blue areas will cause the displacement in a photo of a reef aquarium, and the combined effect will cause soft corals in the scene to undulate as if experiencing the effect of water currents. The source material we’ll use is a picture of a reef aquarium, as seen in Figure 9-13. The sea life are the sole elements in a foreground image, and will be affected by the filters so that they will appear to be moving in the water cur- rent. The rock is in a separate background and will not be affected. Perlin noise The first step in building our aquarium simulation is to create a BitmapData object to contain the Perlin noise. Our image will cover the stage, so we’ll pass the stage width and height into the object to size it appropriately (line 1). Lines 3 and 4 create a bitmap using the bitmap data, and then add that bitmap to the display list. However, the lines are commented out because we do not want to see the Perlin noise in the final product. We need access only to the bitmap data to drive the displacement map. However, it’s often help- ful to see the Perlin noise as you work so you can experiment with various settings. By uncommenting these lines, you can adjust the Perlin noise values until you’re satisfied with the effect, and then comment out these lines again when moving on to the displacement filter. 1 var bmpData:BitmapData = new BitmapData(stage.stageWidth, 2 stage.stageHeight); 3 //var bmp:Bitmap = new Bitmap(bmpData); 4 //addChild(bmp); 5 //comment out lines 3 and 4 to see Perlin noise The Perlin noise generator has a number of settings that will produce dra- matically different results when adjusted. As we discuss these settings, we’ll reference natural phenomena, like water and smoke. We’ll first discuss the settings of the filter and then simply pass these settings into the perlin- Noise() method later on in lines 30 through 32. Lines 7 and 8 set the scale of the texture in the x and y directions. Think of this as influencing the number of waves you can see at one time in water. A very large scale might result in the look of a swelling sea, and a small scale might look like a babbling brook. Line 7 determines the number of octaves in the texture, which are discreet layers of noise that function independently of each other. A single-octave noise will not be as complex as a multi-octave noise and, during animation, you can move a single-octave noise in only one direction at a time. You can create basic animations with single octaves, like the look of running water or, in our case, underwater current. But the ability to move each octave in a different direction makes multi-octave noise better suited for effects like col- liding waves moving in multiple directions, or fire, or smoke. Figure 9-12. Perlin noise texture Figure 9-13. Elements of the Perlin noise and displacement map filter exercise Download from Wow! eBook <www.wowebook.com> Bitmap Filters Chapter 9: Drawing with Pixels 245 Line 10 creates a random seed to influence the starting point for the creation of the texture. A random seed allows you to randomize the effect but also call back that same result by using the same seed at a later time. In our case, we only care about the randomization, so we’ll use a random number, between 0 and 100, for the seed as well. 6 //perlin noise settings 7 var baseX:Number = 50; 8 var baseY:Number = 50; 9 var numOctaves:Number = 1; 10 var randomSeed:Number = Math.random() * 100; 11 var stitch:Boolean = true; 12 var fractalNoise:Boolean = true; 13 var channelOptions:Number = BitmapDataChannel.BLUE; 14 var grayScale:Boolean = false; 15 var offsets:Array = new Array(new Point()); Line 11 determines whether the edges of the area defined when creating the noise pattern are stitched together in an attempt to create a seamless tile. When creating static textures, this “stitching” is not usually needed, but it’s recommended when animating the effect. Whether fractal noise or turbulence techniques are used when generating the effect is determined by line 12. Fractal noise (used when the fractalNoise property is true) generates a smoother effect; turbulence (used when the fractalNoise property is false) produces more distinct transitions between levels of detail. For example, fractal noise might be used to create a terrain map of rolling hills or an oceanscape, and turbulence might be better suited to a terrain of mountains or crevices in a rock. Line 13 chooses which channels of bitmap data are used when generating the texture: red, green, blue, and/or alpha. These can be indicated by con- stants from the BitmapDataChannel class or with integers. You can also use a special operator called the bitwise OR operator ( |) to combine channels to create multicolor effects or combine color with alpha. For example, combin- ing alpha with noise can create fog or smoke with transparent areas through which a background can be seen. In this exercise, because we are generating a pattern only to provide data for a displacement map, we need only one channel. (Blue was chosen arbitrarily.) However, experimenting with the Perlin noise settings can make it difficult to visualize the texture’s effect. To improve these efforts a bit, you can add alpha data to the mix, so you can see the underlying image through the pat- tern. Figure 9-14 shows the visible noise texture and the reef beneath it. In our finished example, the anemones will be displaced to a greater degree where blue is more visible. To see the background image as you experiment with the noise settings, you just have to add an alpha channel to the channelOptions property. To do this, replace line 13 with this: 13 var channelOptions:Number = BitmapDataChannel.BLUE | BitmapDataChannel.ALPHA ; N O T E Perlin noise layers are called octaves because, like musical octaves, each one doubles the frequency of the previous octave, increasing detail within the tex- ture. It’s also important to note that the processor power required to generate noise patterns increases with the num- ber of octaves used. Figure 9-14. Perlin noise detail without alpha data Download from Wow! eBook <www.wowebook.com> Part II: Graphics and Interaction 246 Bitmap Filters The grayscale parameter in line 14 desaturates the texture so it generates only grays. In our exercise, the texture won’t be visible, so this isn’t relevant, but it’s ideal when visible fog or smoke is required. Finally, line 15 uses an array of offset points, one for each octave, to control the location of the noise pattern generated. We need only one octave in this example, so this is a single-item array. Because the texture will not be visible, its starting point is arbitrary, so we’ll use a default point of (0, 0). During animation, we’ll alter the position of this point to move the pattern. You’ve now set all the values required to create a Perlin noise texture. If you want to see the noise before moving on to the next section, look at the per- lin_noise_only.fla source file. Later, we’ll animate these values by changing the offset values upon every enter frame event. First, however, we need to set up the displacement map settings. Displacement map The displacement map filter is a bit simpler. Lines 18 and 19 of the script that follows determine which color channel will affect the distortion in each direction. We used the blue channel when creating our Perlin noise texture, so we’ll use the same channel here. Next, lines 20 and 21 set the scale of displacement in the x and y directions. Think of these values as the size of the waves when looking through water, or the degree of refraction when looking through glass, in each direction. 16 //displacement map settings 17 var displaceMap:DisplacementMapFilter; 18 var componentX:uint = BitmapDataChannel.BLUE; 19 var componentY:uint = BitmapDataChannel.BLUE; 20 var xScale:Number = 10; 21 var yScale:Number = 10; 22 displaceMap = new DisplacementMapFilter(bmpData, new Point(), 23 componentX, componentY, xScale, yScale, 24 DisplacementMapFilterMode.CLAMP); Finally, lines 22 through 23 determine how the edges of the displacement map will behave. When set to clamp, any displacement will be confined by the edges of the source data. If wrap, is used, the distortion will wrap around from edge to edge. The wrap option is great for tiled patterns but not useful for affecting a realistic image of a recognizable object. You don’t want to see the top of a person’s head appearing beneath their feet as a displacement wraps from top to bottom edge. Now that our settings are complete, we create the DisplacementMapFilter in lines 22 through 24. The source for the displacement data is the same BitmapData object that is being affected by the Perlin noise pattern, so the degree of displacement will be determined by that bitmap data, passed into the class in the first parameter. The second parameter is the map point—the location at which the upper-left corner of the displacement map filter will be applied. This is useful for filtering only a portion of the image. We want Download from Wow! eBook <www.wowebook.com> Color Effects Chapter 9: Drawing with Pixels 247 to filter the entire image, however, so we’ll pass in a default point to begin filtering at (0, 0). The remainder of the parameters correspond directly to the settings previously created. Animating the effect To animate the Perlin noise, and therefore the displacement map effect, we start with a listener that triggers the onLoop() function upon every enter frame event. The first thing the function does is update the offset point for the Perlin noise octave, seen in lines 28 and 29. This example sets the offset point of the octave, not the location of a display object (see the adjacent note for more information). Lines 28 and 29 move the first octave (the only octave used in our example) up and to the right, 2 pixels in each direction. With each change to the offset point, the perlinNoise() method is called (line 30), applying all the previously set parameters along with the offset update. Finally, with the call of the Perlin noise method, the DisplacementMap filter source data is updated, so the DisplacementMap filter must be reapplied to the display object in line 33. 25 //enter frame update of both filters 26 addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true); 27 function onLoop(evt:Event):void { 28 offsets[0].x -= 2; 29 offsets[0].y += 2; 30 bmpData.perlinNoise(baseX, baseY, numOctaves, randomSeed, 31 stitch, fractalNoise, channelOptions, 32 grayScale, offsets); 33 tank_mc.filters = [displaceMap]; 34 } Color Effects You can apply color effects using ActionScript in multiple ways, and we’ll look at three. The first is relatively straightforward: Alter the emphasis of individual color channels (red, green, blue, and alpha) in an image using the ColorTransform class. The second is a more powerful technique that uses the ColorMatrixFilter to apply a detailed matrix to simultaneously change all color and alpha channels. The last method discussed is the simplest, using the Color class to apply a tint to a display object. The ColorTransform Class Although we’ll focus exclusively on color, you can use the ColorTransform class to adjust the alpha channel as well as the individual color channels of a display object or BitmapData object. In the examples that follow, we’ll be using the class to invert an image (create a color negative) and apply a simple saturation effect. N O T E Animating an octave with the offset property is not the same as moving a display object. Instead, think of adjust- ing the offset position of an octave as adjusting that octave’s registration point. If you move a movie clip five pixels in the x and y directions, it will move down and to the right. However, if you adjust the movie clip’s registration point, down and to the right, the clip won’t move on stage, but its contents will move up and to the left. Download from Wow! eBook <www.wowebook.com> Part II: Graphics and Interaction 248 Color Effects The class offers two ways to change color. First, you can multiply a color chan- nel to increase or decrease its effect. For example, you can double the weight of the color by using a multiplier of 2, and you can reduce the weight of the color by half by using 0.5 as a multiplier. Second, you can offset a color channel from –255 to 255. For example, assuming a default multiplier of 1 (no change from the multiplier), an offset value of 255 would maximize the red channel, 0 would apply no change, and –255 would remove all red from the image. The following code, found in the color_transform.fla source file, manipulates three movie clips instantiated as mc0, mc1, and mc2. Lines 1 through 10 show the default ColorTransform instance, which makes no change to the source material. Also, by using this default configuration (with a multiplier of 1 and an offset of 0 on each channel), you can effectively reset any prior color transformation. Despite the example’s focus on color, we’ve included alpha multiplier and offset values to show the complete syntax of a reset. Note that the ColorTransform class is not a filter, so it’s not applied to the fil- ters property of a display object. Instead, the color transformation is applied to the colorTransform property of a display object’s transform object (line 10). Similar to the filtering process, however, every time a change is made to the color transformation, it must be reapplied to the colorTransform property. Lines 12 through 19 provide for an increase in saturation. The offset values of all colors are unchanged, but the color multipliers increase the color for each channel. To emphasize the image, the values used increase red more than green and blue. This effect can be seen in the “Saturation” example in Figure 9-15. You could also partially desaturate an image using the same technique but applying a multiplier value of less than 1 to each color channel. Finally, lines 21 through 28 invert all color in the image. The multiplier for all color channels is set to –1, which effectively turns the image black, and then the offset values are set to full to revert back to color. This effect can be seen in the “Invert” example from Figure 9-15. 1 var noChange:ColorTransform = new ColorTransform(); 2 noChange.redOffset = 0; 3 noChange.greenOffset = 0; 4 noChange.blueOffset = 0; 5 noChange.alphaOffset = 0; 6 noChange.redMultiplier = 1; 7 noChange.greenMultiplier = 1; 8 noChange.blueMultiplier = 1; 9 noChange.alphaMultiplier = 1; 10 mc0.transform.colorTransform = noChange; 11 12 var saturation:ColorTransform = new ColorTransform(); 13 saturation.redOffset = 0; 14 saturation.greenOffset = 0; 15 saturation.blueOffset = 0; 16 saturation.redMultiplier = 1.3; 17 saturation.greenMultiplier = 1.1; 18 saturation.blueMultiplier = 1.1; 19 mc1.transform.colorTransform = saturation; 20 Original Saturation Invert Figure 9-15. ColorTransform filter effects Download from Wow! eBook <www.wowebook.com> . Bitmap Filters Chapter 9: Drawing with Pixels 239 9 g.beginFill(0xFFFF 00, 1); 10 g.drawRoundRect (0, 0, 200 , 50, 20) ; 11 g.endFill(); 12 13 sp.filters = [ds]; 14 addChild(sp); Because. embossDown example reverses this effect, seemingly stamping into the image. 32 var embossUp:Array = [-1 , -1 , 0, 33 -1 , 1, 1, 34 0, 1, 1]; 35 convFilter(mc0, embossUp); 36 37 var embossDown:Array. <www.wowebook.com> Part II: Graphics and Interaction 242 Bitmap Filters 11 var noChangeArr:Array = [0, 0, 0, 12 0, 1, 0, 13 0, 0, 0] ; 14 noChange = new ConvolutionFilter (3, 3, noChangeArr); 15