I was pondering for a moment how the distortion map was used in my previous post to create a paint spreading effect. I realized it probably has been done before, but I've never seen it done quite this way. In Photoshop there's a very handy tool for smearing bitmap content and I find it to be very useful there. I've recreated the effect here with a smooth edged brush. The dog you see is Sadie, my great dane, and subject of some recent photography by my wife.
Without further ado, here's the example, followed by the source code. The trickiest part was to make the effect "add" to the smear, rather than just to re-apply a new smear factor. I accomplished this by using the SUBTRACT and ADD blend modes.
This movie requires Flash Player 9
package com.agstrout.smear {
import flash.display.MovieClip;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.filters.DisplacementMapFilter;
import flash.display.Loader;
import flash.events.Event;
import flash.net.URLRequest;
import flash.geom.Rectangle;
import flash.display.BitmapDataChannel;
import flash.geom.Point;
import flash.display.Graphics;
import flash.events.MouseEvent;
import flash.display.BlendMode; /**
* com.agstrout.smear.Smear
* @author Arne G Strout
* Created : May 15, 2009
* Re-creates the Smear tool effect from Photoshop using DistortionMapFilter
*/
public class Smear extends MovieClip {
public var hit:MovieClip; // Click button
public var holder:Bitmap; // Result Bitmap Holder
public var bmp:BitmapData; // The source bitmap
public var output:BitmapData; // The result bitmap
public var distort:BitmapData; // The distortion map
public var filter:DisplacementMapFilter; // The distortion map filter
public var loader: Loader; // loader to load the image to be modified
private var _lx:Number; // The last mouse X position
private var _ly:Number; // The last mouse Y position
private static const MY_URL : String = "http://www.agstrout.com/images/image.jpg"; // Image to load
private var paint : MovieClip; // Paint layer
/**
* Smear Constructor
*/
public function Smear() {
paint=new MovieClip(); // creates the painting layer
loader=new Loader(); // creates the loader
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,_onImageLoaded); // loader listener
loader.load(new URLRequest(MY_URL)); // Load the constant URL
}
/**
* Image Loaded handler
*/
private function _onImageLoaded(event : Event) : void {
// Create the output area and source
holder=new Bitmap();
bmp=Bitmap(loader.content).bitmapData; // Use the loaded Bitmap (JPG, PNG, or GIF) for the source BitmapData
var r:Rectangle=bmp.rect;// the size of the source bitmap
distort=new BitmapData(r.width,r.height,false,_argb(256,128,128,128)); // Make the distortion map the same size with 128 in the X/Y (R/B) registers on all pixels
output=bmp.clone(); // Copy the source for the output
holder.bitmapData=output; // assign the output bitmapdata to the holder
addChild(holder); // Add the holder to the stage
// Filter
// Use RED / BLUE registers for X/Y displacement, and 100 amplification for a nice effect, clamp so the edges are solid.
filter=new DisplacementMapFilter(distort,null,BitmapDataChannel.RED,BitmapDataChannel.BLUE,100,100,"clamp");
// Create the click button
hit=new MovieClip();
var g:Graphics=hit.graphics;
g.beginFill(0x000000,0); // transparent square
g.drawRect(0, 0, r.width, r.height); // same size as the source bitmapdata
g.endFill();
addChild(hit);
hit.addEventListener(MouseEvent.MOUSE_DOWN,_onMouseDown); // PRESS
stage.addEventListener(MouseEvent.MOUSE_UP,_onMouseUp); // RELEASE
_update(); // UPDATE
}
// ON MOUSE DOWN, set the lx/ly
private function _onMouseDown(event : MouseEvent) : void {
stage.addEventListener(MouseEvent.MOUSE_MOVE,_onMouseMove);
_lx=mouseX;
_ly=mouseY;
}
//
private function _onMouseMove(event : MouseEvent) : void {
var dx:Number=mouseX-_lx; // diff x
var dy:Number=mouseY-_ly; // diff y
var col:Number;
var mc:MovieClip=paint;
var g:Graphics=mc.graphics;
g.clear();
var i:int;
// red
for(i=0;i<128;i+=4){
col=_argb(256-i*2,Math.floor(Math.abs(dx)),0,0);
g.lineStyle(64*(1-i/128),col,1,false,"normal");
g.moveTo(_lx,_ly);
g.lineTo(mouseX,mouseY);
}
if(dx>0)distort.draw(mc,null,null,BlendMode.SUBTRACT);
if(dx<0)distort.draw(mc,null,null,BlendMode.ADD);
// blue
g.clear();
for(i=0;i<128;i+=4){
col=_argb(256-i*2,0,0,Math.floor(Math.abs(dy)));
g.lineStyle(64*(1-i/128),col,1,false,"normal");
g.moveTo(_lx,_ly);
g.lineTo(mouseX,mouseY);
}
if(dy>0)distort.draw(mc,null,null,BlendMode.SUBTRACT);
if(dy<0)distort.draw(mc,null,null,BlendMode.ADD);
_lx=mouseX;
_ly=mouseY;
_update();
}
private function _onMouseUp(event : MouseEvent) : void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE,_onMouseMove);
}
private function _update() : void {
output.applyFilter(bmp, bmp.rect,new Point(0,0), filter);
}
private function _argb(a:uint,r:uint,g:uint,b:uint):uint{
return (a<<24 | r<<16 | g<<8 | b); // Create a color value from a,r,g,b data
}
}
}