Email Updates RSS Subscribe
Line

This blog is created and maintained by the technical team at Hook in an effort to preserve and share the insights and experience gained during the research and testing phases of our development process. Often, much of this information is lost or hidden once a project is completed. These articles aim to revisit, expand and/or review the concepts that seem worth exploring further. The site also serves as a platform for releasing tools developed internally to help streamline ad development.

Launch
Line

Hook is a digital production company that develops interactive content for industry leading agencies and their brands. For more information visit www.byhook.com.

Line

Simulated Motion Blur in Flash

Line
Posted on February 11th, 2010 by Jake
Line

Different Approaches to Simulate Motion Blur in Flash

A better title for this set of experiments would be “The art of making things smudgy in Flash”. Since that is basically what we are doing here. The retina works a bit like the film in a traditional camera. Light energy comes in, stimulates part of the nerve, which in turn sends a single to the brain to deal with that stimulation. The thing is, that once the light stops pouring into the eye and over the retina, the nerves stimulation gradually fades away, and doesn’t just shut off when the light stops. The same thing happens to film. You expose the film for a certain amount of time, and the part that is exposed, can’t be un-exposed. So if the shutter is open, and an object passes in front of the camera more and more parts of the film are being exposed. So the image “smears” across the film, much like in these images:

(I wanted more views, so I figured a cat image would help)

This is all well and good unless you are in the digital visual arts world. If you don’t attempt to reproduce some sort of motion blur, the image looks too “Framey”, like a stop motion film. And yes, “Framey” is a highly regarded technical term. Its sort of like when progressive scan TVs came out. The images on each frame were suddenly much sharper than we were used to. Much of the natural blur from the interleaving was gone. Same goes for lcd/plasma monitors. The pixels are instant on and instant off, there is very little ghosting. The older CRTs would have quite a bit of ghosting, which gave the images a good bit of natural motion blur. (think of the trails behind each star in a slowly moving star field, like the old windows screen saver)

Below is the culmination of what we learned during these tests. Drop the frame rate down to 2 fps to really see whats going on. If you disable blurring at a higher frame rate (like 30fps) and then re-enable it, you can really see the difference. It feels quite a bit better with the blurring enabled. Go ahead, give it a shot:


Get Adobe Flash player




The major problem with all digital media is that there isn’t anything between the bits. For any digital media that changes over time, there is a very discrete rate of change. 30 frames per second, 100 cycles per second (hertz), and (much to your ISP’s chagrin) 1.2MB downloaded from your favorite torrent site every second, are all examples of discrete intervals. There is frame 1 and then frame 2 of a video, no frame 1.5. Natural life is purely analogue, which means there is an infinite amount of detail between seconds. So in order to mimic that feel of analogue, we need to attempt to conjure up what happens between frames, and show that on each of the “real” frames. (Beat that, stupid nature!)

First things first, research
If you can remember (and how could you not) the PS2 game “Shadow of the Colossus”, then you’ve already experienced an attempt to replicate many of the different phenomena that happens in nature, in an interactive digital medium. They managed to get tone mapping (faked HDR lighting), realtime shadows and motion blur working in harmony to bring a smooth analog feel to the game. It also just so happens there was a technical paper written about how they pulled off those effects in the game. This is a truly fantastic read, and a translated version can be found here:
http://www.bruno-urbain.com/resources/making_of_sotc.pdf

They pulled off motion blur by combining two different methods. A camera blur, for everything but the character, and actual geometry generation from interpolated joint rotations. Since we are only dealing with 2D today, we can ignore the second method. However the second method is still a sweet way to do really nice inter-character motion blur in realtime.

The way the camera blur works, is by shifting the camera left and right a couple of units every frame, taking a snap shot of those camera positions, and combining those images together with alpha blending. Essentially this is frame accumulation. This concept is the basis for two of the three blurring methods used in the example above.

Just to do a sanity check we popped open Adobe After Effects and applied the CCForce Motion Blur filter to a test animation. Surprisingly their solution isn’t much more dramatic than the SOTC solution. Determine where thing would be between frames and draw faded out version of the image on the actual frame.

Up until now, this post has been a lot of reading, and not a lot of images. So here is an early example of each of the three types of blurring we created. Linear Interpolation, Curved Interpolation (both based on the frame accumulation concept), and standard flash Directional Blurring with a twist, literally. Drag the mouse around at different speeds and with different frame rates set to see the different effects. For clarity, the initial effect is the Linear Interpolation.


Get Adobe Flash player




Alpha Blending and Location Interpolation
Before we got into the actual blurring techniques in depth, we had to solve one problem first. Fast image duplication and alpha blending, like so:




As you could probably tell from our previous posts, we have become quite the fans of Flash’s BitmapData class. So as you have probably guessed, we are doing to use the draw() and copyPixels() functions fairly extensively to achieve these effects. Yeah Code Time!

private function drawInbetweens():void
{//drawInbetweens
	_sourceBD = new Pony(1, 1);
 
	var colorMat:Array = [
		1, 0, 0, 0, 0,					//RED
		0, 1, 0, 0, 0,					//GREEN
		0, 0, 1, 0, 0,					//BLUE
		0, 0, 0, (1/_numSamples), 0,	//ALPHA
	];
 
	var filt:ColorMatrixFilter = new ColorMatrixFilter(colorMat);
	_sourceBD.applyFilter(_sourceBD, _sourceBD.rect, new Point(), filt);
 
	for (var i:int = 0; i < _numSamples; i++)
	{//draw all
		_canvas.copyPixels(_sourceBD, _sourceBD.rect, new Point(i * _offset, 0), null, null, true);
	}//draw all
}//drawInbetweens

In this tiny morsel we are are creating a new copy of the source bitmap, and storing that into _sourceBD. Next we create an array that will be fed into the ColorMatrix filter. If you notice, in the section of the matrix that controls the alpha level, we are simply making each inbetween equal parts of 100%. So if there are five samples, each sample will be set to 20% alpha. Next we create the filter, and apply it to the _sourceBD bitmap data object. Lastly we walk through a for loop that stamps down one copy of the alpha adjusted bitmap per sample. You will also notice that the last parameter of the copyPixels() call is set to true. This is the flag for enabling alpha merging, which is the key to getting the overlapping images to blend and not cover each other up. First problem solved, on to the next.

Linear Blurring
Now that we have some alpha blended goodness, we can finally get on with doing some motion tests. The first method we came up with was your standard linear interpolation of the positions between visible frames. Also known as LERP… good ol’ LERPage. (for even more fun, check out SLERP :) )
The result with a simple LERP was this:




For both of the location interpolation methods we came up with, we need the same pieces of information. We need to know about the past locations of the object so that we can build the current frame. For this simple LERP version, all we need to know is where the object was one frame previous, and where it will be on the current frame. With these two points of data we can calculate the change in X and the change in Y, divide those differences by the number of samples we want to plop down, and draw our samples with those offsets. Now that I read that back it sounds way more complicated than it is. Insert your preferred triangle rise over run example here, or check out my nifty hand drawn one here:




If our object was at 0,0 on the previous frame and is now at 5,-5, then we know that it moved 5 pixels to the right(run) and 5 pixels up(rise). If we have 5 samples that we want to stamp down, then we divide the change in X (run) by the number of samples (5), and the change in Y by the number of samples (5). That means that for each sample we want to move it 1 pixel to the right and 1 pixel up and then draw it to the screen.

On every update call for an object doing a linear blur the following chunks of code are called. I have skipped some of the code because it won’t make sense yet. We also decided to try to make this class easy to implement for designers, but I’ll get to that mess later.

//Skip first frame
while(_posList.length < 3)
{//save pos and skip
	//Fill pos list
	_posList.push(new Point(x, y));
}//save pos and skip
 
var prevPoint:Point = _posList[_posList.length - 1];
_curPoint = new Point(x, y);

First we build a list of previous points where the object has been. We will need at least three later, but the linear blur only requires the current location and the previous location.

//Get source image
var currentBounds:Rectangle = this.getBounds(parent);
var bWidth:Number = Math.abs(currentBounds.right - currentBounds.left);
var bHeight:Number = Math.abs(currentBounds.bottom - currentBounds.top);
_sourceBD = new BitmapData(bWidth, bHeight, true, 0x0000FF00);	//GREEN
_sourceBD.draw(this);

Next we generate a BitmapData object from our source MovieClip. For this we create a new BitmapData object, and simply call draw() on the source MovieClip. This will render the clip to bitmap data and store it in the _sourceBD object. A quick tip that we have found to be useful when dealing with lots of little bitmap canvases, is to fill the new BitmapData object with a color. A different color for each canvas if you can. That way when you are trying to figure out why parts of your images are being cropped off, you can see the bounds of the BitmapData object itself. Once the debugging is done, you simply set the alpha bit on the fill color to 0 and recompile. As you can see in this example, we chose green. To make the green show up as the background of the bitmap, the color is changed to 0xFF00FF00, which is 255 Alpha (full alpha), 0 Red, 255 green, and 0 blue. Anyone who has done any sort of placement work with divs or css will be all too familiar with this technique.

As stated before, this class needed to be easy to implement for anyone, so this entire blur scheme needed to be self contained within the MovieClip the class was applied to. This is why we are getting the bounds of the parent object. But again, like I said, we will get to that later, as it was a bit of a mind bender.

var prevPoint:Point = _posList[_posList.length - 1];
var diffX:Number = _curPoint.x - prevPoint.x;
var diffY:Number = _curPoint.y - prevPoint.y;
var offsetX:Number = diffX / _numSamples;
var offsetY:Number = diffY / _numSamples;
 
_samplePosList = new Array();
var pt:Point = prevPoint.clone();
for (var i:int = 0; i < _numSamples; i++)
{//push sample positions
	_samplePosList.push(pt.clone());
	pt.x += offsetX;
	pt.y += offsetY;
}//push sample positions

Now that we have a source bitmap to use as a blur sample, we make a bunch of copies of it, and fade down the alpha on each of those samples. First as in the code above, we generate the inbetween locations between the previous frame and the current frame.

//Make a list of samples
_sampleList = new Array();
for (var i:int = 0; i < _samplePosList.length; i++)
{//makeSamples
	var bd:BitmapData;
	bd = new BitmapData(sourceBD.width, sourceBD.height, true, 0x00000000);
	bd.copyPixels(sourceBD, sourceBD.rect, _zeroPoint, null, null, true);
 
	//push into list
	_sampleList.push(bd);
}//makeSamples

Then we build a list of copies of the source, which we are calling samples.

//Alpha filter
for (var k:int = 0; k < _sampleList.length; k++)
{//filter whole list
	var alphaBD:BitmapData = new BitmapData(_sampleList[k].width, _sampleList[k].height, true, 0x00000000);
	alphaBD.copyPixels(_sampleList[k], _sampleList[k].rect, _zeroPoint, null, null, true);
	alphaBD.applyFilter(alphaBD, alphaBD.rect, _zeroPoint, _colorFilt);
	_sampleList[k] = alphaBD;
}//filter whole list

Next we use the same color matrix filter as we did at the beginning of this post, to bring down the alpha value of each of the samples.

//Final render to canvas
for (var i:int = 0; i < _numSamples; i++)
{//stamp
	_canvas.copyPixels(_sampleList[i], _sampleList[i].rect, _samplePosList[i], null, null, true);
}//stamp

Lastly, we copy the resultant samples to the screen object, which in this case is a BitmapData object called _canvas.

Linear Blur Curved Blur

Yeah! we finally have our first semblance of simulated motion blur in flash. However using the linear alone is not without visual issues. For instance, go back up to the the previous flash example, and click the Linear button, and the 18fps button. If you draw circles with your mouse at any sort of real speed, you will start to see the purely linear calculations of this blur. You get more of a diamond shape than a circle. The ultimate goal would be to get a real circle out of this, which of course involves curves. If you are the impatient type, click the Curve button and the 18fps button in that previous example and set the number of samples up pretty high, to something like 60. Now drag your mouse around in a circle. It becomes much less linear and actual curves start to emerge.

Curved Blurring
Blurring on a curve ended up being significantly more complex that we had originally thought. I have to admit, the first handful of attempts failed pretty miserably. You could say the curves threw us for a loop! (*ZING* here come the pun police)
Here is a bit of a collage of what didn’t work:




As you may or may not be able to tell, I was convinced that we could achieve this curve by using accumulated vectors. This meant that with three points of reference, I could create two displacement vectors, compare the angle between the two, LERP that angle between the current point and the previous point, and then divide the distance between the current point and the previous point by the number of samples and get an offset length. So basically what we would end up with is a set of vectors that we could just add together and put a sample at the head of each vector. At the time it sounded like a great plan, and believe me we tried to make it work. Ultimately that just wasn’t the solution. For your enjoyment here are a couple of tests swfs that made an attempt to use this ill-fated method.
(On the last two, you can drag one of the red handles around)

Get Adobe Flash player

Get Adobe Flash player

Get Adobe Flash player

With inner rage building, and an imminent Hulk Smash to the keyboard, we scrapped this method, and started fresh.

Lo and behold, it was Pierre Bézier to the rescue! I started reading the Wiki page on the Bezier curve (http://en.wikipedia.org/wiki/B%C3%A9zier_curve) only to have my eyes glass over from the many equations. Thankfully I kept reading because about three fourths of the way down, are the most awesome bezier curve creation animated .gifs I’ve ever seen. Go ahead, check it out, I’ll wait. So really what it comes down to, is that each point on the curve is a percentage (t) along the line Q (made from the Q0 and Q1 points). The Q0 and Q1 points are defined by going along the line segments defined by the control handles, P0, P1 and P2 (assuming you are looking at the Quadratic Curve examples on that page) Smashing all of that together will give you a point along a curve defined by that function. For us, this was great, we wanted to get points in a curve so we knew where to stamp down the inbetween samples.

However the big problem was, how do you determine where the control handles needed to be so that the curve would pass through the the current location, and the location on each of the two frames previous. We first came across Grant Skinners work with curve generation. (http://www.gskinner.com/blog/archives/2008/05/drawing_curved.html)
Implementing his method allowed us to use the list of previous locations as the control handles, and then we could generate a curve between those. To generate the curve, he suggested bisecting the line (created between the locations on the previous frames) and draw the curve with Flash’s built in methods by passing in the bisected locations and the frame locations. This was a cool idea, and fairly straightforward to implement. However, the accuracy of the curve just wasn’t there. We needed the curve to really go through the locations, and not use the locations as control points.

With some more RedBull and a private session with Google, we stumbled across Andy Woodruff’s Bezier classes. (http://www.cartogrammar.com/blog/continuous-curves-with-actionscript-3/)
This was just the solution we needed. The class was modified to remove much of the error checking, since we knew how many and what kind of values were going to be passed to it. It was also modified to return an array of evenly spaced points on that curve, that we could then use to place our stamps. The final loop was modified to look like this, in order to return the points we wanted:

// Loop through points to draw cubic Bézier curves through the penultimate point, or through the last point if the line is closed.
for (i = firstPt; i < lastPt - 1; i++)
{//all points
	// BezierSegment instance using the current point, its second control point, the next point's first control point, and the next point
	var bezier:BezierSegment = new BezierSegment(p[i],controlPts[i][1],controlPts[i+1][0],p[i+1]);
 
	//determine offset along bezier segment
	var offset:Number = 1 / numInterpPoints;
	for (var t = 0; t <= 1; t += offset)
	{
		var val = bezier.getValue(t);	// x,y on the curve for a given t
		resultList.push(val);
	}
}//all points

The final result ended up being a fairly subtle difference, but one that really helped to smooth out the sharp corners of the linear blur result. This solution is not without its own issues however. Just like we couldn’t use the Linear method alone, we won’t be able to use the Curve method by itself either. The curve method requires three unique points of information. Which means if the object is moving slowly enough, then at least two of the locations will be in the same place across update calls. So for slow moving objects you wouldn’t get a blur, and what fun is that? Additionally since it is a bezier curve we are working with, locations that are very linear or in tight groups tend to throw the curve off in rather unpleasant ways. Therefore we decided to dynamically change what type of blurring we are doing based on the behavior of the objects movement. We threw together a crude method for determining which blur function to use:

//Determine blur method
var isCurve:Boolean;
 
//See if we are curving
var aVect:Vector2D = new Vector2D(_posList[_posList.length - 2].x, _posList[_posList.length - 2].y);		//2 frames ago
var bVect:Vector2D = new Vector2D(_posList[_posList.length - 1].x, _posList[_posList.length - 1].y);		//1 frame ago
var cVect:Vector2D = new Vector2D(_curPoint.x, _curPoint.y);		//current frame
var diffVect1:Vector2D = bVect.minus(aVect);
var diffVect2:Vector2D = cVect.minus(bVect);
var curveAngle:Number = Math.abs(radToDeg(diffVect1.angle - diffVect2.angle));
 
isCurve = (curveAngle > _curveBlurThreshold)
 
if(!checkDupPos() && isCurve)
{//no dupes, use curve
	doCurveBlur();
}//no dupes, use curve
else
{//use linear
	doLinearBlur();
}//use linear

So from the tests so far, we know that using the Linear blur works really well if the current location and the previous locations are not drastically out of line, and the Curve blur works well when the direction changes quickly in a somewhat circular motion. We also know that we can’t use the Curve blur if we do not have three unique locations, and that it doesn’t work well with tight groups of locations. So we tried to dynamically decide which method to use based on the last three locations.

Once again we took advantage of Brad’s amazing Vector2D classes to make this all work. First we create three vectors, aVect, bVect, and cVect. These are essentially point locations from the past three frames. Knowing these three points we can subtract the vectors and get the angle at which the object was headed between those points. So diffVect1 and diffVect2 are the resultant displacement vectors, that contain the magnitude and angle information we need. Lastly we simply take the absolute value of the difference between the angles of the two displacement vectors. We can then compare that with a threshold. If its greater than that threshold, we will use the curve method, otherwise use the linear method. We also do a quick check to see if there are any duplicate locations in the last three frames. If there are, we simply use the linear method. This is still a fairly crude method for determining which method to use, so it can be improved, but it works well enough for now, as is.

Directional Smudging
This is starting to look pretty good. However there is still one issue that plagues both of these methods. The fact that we are using discreet “stamps” of the image, means that if the object moves fast enough you can plainly see the individual inbetween images. To help smear the image a bit we are going to use Flash’s BlurFilter class. This class allows you to blur a bitmap by some horizontal amount as well as by some vertical amount. Generally the amount of blur in the X and the amount of blur in the Y will be the same, but in our case, we are only going to blur in one direction, the X direction. The final result looks something like this:

This method of Directional Blurring has many issues when used by itself as the main blurring method. In the example above, click 18fps and the Directional button. Move the mouse slowly (in that test it wasn’t adjusted for frame rate) and you will get an idea of the effect. If you move the mouse more quickly or in a circle you can witness the breakdown of the effect. The blurring occurs from the center outwards, which means that the center is the most dense area, ultimately being the most noticeable. So if blurred across a large enough area, it just looks like a stretched image. Also the blur is basically a line that rotates around the center. Which means that longer blurs look more like lines pointing in towards the center of where the object is moving, and doesn’t really have any visual link to the previous positions. The one advantage it does have is that it smears an image very nicely if its a small enough blur. So what we ended up doing was blur each of the inbetweens in the direction of the next inbetween. This really smoothed out stamps into a clean smeary blend of blurry goodness.

The trick to this effect was getting around the fact that you can only blur horizontally and vertically, not on some diagonal. After a bit of thinking we came up with a way to do it. The solution was actually written on paper in one of the failed attempts notes pages above. The trick is to rotate the object in the opposite direction you want to blur it, blur that image horizontally, and then rotate the newly blurred image back to 0. This will put the blur in a diagonal pattern.




The image above illustrates the steps to use a horizontal blur in a diagonal fashion.

The code to make this happen looks like this:

private function fillSampleList(sourceBD:BitmapData, doDirBlur:Boolean):void
{//fillSampleList
	_sampleList = new Array();
 
	for (var i:int = 0; i < _samplePosList.length; i++)
	{//makeSamples
		var bd:BitmapData;
		var moveAngle:Number;
		var moveAngleRad:Number;
 
		if (doDirBlur)
		{//blur'em
			var vect1:Vector2D;
			var vect2:Vector2D;
			var finalVect:Vector2D;
 
			if (i > 0)
			{//use previous and current to get angle
				vect1 = new Vector2D(_samplePosList[i - 1].x, _samplePosList[i - 1].y);
				vect2 = new Vector2D(_samplePosList[i].x, _samplePosList[i].y);
			}//use previous and current to get angle
			else
			{//use next and current to get angle
				vect1 = new Vector2D(_samplePosList[i].x, _samplePosList[i].y);
				vect2 = new Vector2D(_samplePosList[i + 1].x, _samplePosList[i + 1].y);
			}//use next and current to get angle
 
			//get displacement vect
			finalVect = vect2.minus(vect1);
			moveAngle = radToDeg(finalVect.angle);
 
			//remap move angle to right two quadrans (since left two are visually the same)
			if (moveAngle < -90 || moveAngle > 90)
			{//remap angle
				if (moveAngle < -90)
				{//handle neg
					moveAngle = (180 - Math.abs(moveAngle));
				}//handle neg
				else
				{//handle pos
					moveAngle = moveAngle - 180;
				}//handle pos
			}//remap angle
 
			//Save remapped angle
			moveAngleRad = degToRad(moveAngle);
			var boxSize:Number = Math.sqrt(Math.pow(sourceBD.width, 2) + Math.pow(sourceBD.height, 2)) ;
			bd = new BitmapData(boxSize, boxSize, true, 0x00888888);
 
			//Center sourceBD into bigger bd (pre-rotation)
			var transMat:Matrix = new Matrix();
			var wDiff:Number = boxSize - sourceBD.width;
			var hDiff:Number = boxSize - sourceBD.height;
			transMat.translate((boxSize / 2)-sourceBD.width/2 - wDiff/2, (boxSize / 2)- sourceBD.height/2 - hDiff/2);
			bd.draw(sourceBD, transMat);
 
			//Handle edge case angles
			if (moveAngle == 90 || moveAngle == -90)
			{//
				moveAngle = 90;
			}//
 
			if (moveAngle == 180 || moveAngle == -180)
			{//
				moveAngle = 0;
			}//
 
			//Center bitmap across top left corner, rotate, and translate back to original location
			transMat.identity();
			transMat.translate( -bd.width / 2, -bd.height / 2);
			transMat.rotate(degToRad(-moveAngle));
			transMat.translate(bd.width / 2, bd.height / 2);
 
			//Draw with rotated matrix
			var tmpBD:BitmapData = bd.clone();
			bd.fillRect(bd.rect, 0x00FF0000);
			bd.draw(tmpBD, transMat);
 
			//Apply blur
			var blurFilt:BlurFilter;
			blurFilt = new BlurFilter(Math.min(10, finalVect.length * 1), 0, 3);
			var bda:BitmapData = new BitmapData(boxSize, boxSize, true, 0x0000FF00);
			bda.applyFilter(bd, bd.rect, _zeroPoint, blurFilt);
 
			//Set up matrix to rotate back
			transMat.identity();
			transMat.translate(-bda.width/2, -bda.height/2);
			transMat.rotate(degToRad(moveAngle));
			transMat.translate(bda.width / 2, bda.height / 2);
 
			//Draw back with un-rotation matrix
			var bdb:BitmapData = new BitmapData(bda.width, bda.height, true, 0x00FF0000);
			bdb.draw(bda, transMat, null, null, null, true);
 
			//set bd to result
			bd = bdb;
		}//blur'em
		else
		{//no blur
			bd = new BitmapData(sourceBD.width, sourceBD.height, true, 0x00000000);
			bd.copyPixels(sourceBD, sourceBD.rect, _zeroPoint, null, null, true);
		}//no blur
 
		//push into list
		_sampleList.push(bd);
 
	}//makeSamples
 
}//fillSampleList

Please be aware that this whole experiment was really just that. Much of the code is not optimized for speed, but instead written with clarity as the main focus. This function in particular can be made to run quite a bit faster. But enough with the lame excuses, lets see whats going on here.

The outer most loop walks us through each of the points in the sample position list. We first calculate a displacement vector between each of the points in the sample point list. This finalVector contains the angle and the distance to the next sample. The angle will be used to determine how much to rotate the bitmapData by in order to get the blur in the right direction, and magnitude component of the vector will be used to determine the strength of the blur. Once we have the angle, we need to push it into the positive space. Since blurring along 45 degrees and 135 degrees produces the same visual result, we just map the angle to the positive side of the circle.

With the angle taken care of we need to figure out how big of a canvas we need in order to draw the rotated source bitmap on to it without clipping. If you take a square and rotate it 45 degrees counter-clockwise, you will see that the corners of the square fall outside of the original square. This is due to the fact that the length of the distance between opposing corners is longer than any of the sides of the square. This means we need a bigger square to draw the rotated square into.

If we draw a circle around all four corners of the non-rotated square we can use that diameter as the minimum size of the new canvas to draw into. From the illustration above you can also see that the radius is half of the diagonal of the square. Which means that the length of the diagonal is the same as the diameter of the circle. This is great news, because we can fire up the old Pythagorean theorem to determine the length of the diagonal. So we take the square root of the sum of the width of the non-rotated square squared, and the height of the non-rotated square squared.

var boxSize:Number = Math.sqrt(Math.pow(sourceBD.width, 2) + Math.pow(sourceBD.height, 2)) ;

This will always get us a canvas bigger than we need, even if the source is a rectangle.

Next we draw down the pixels from the source to the new big canvas. We use a translation matrix in the draw call to center the source in the new canvas.

Now we modify the moveAngle if it is perpendicular or flat so that we always blur towards the positive.

Then we move into the key part of this whole deal, actually rotating the bitmap:

//Center bitmap across top left corner, rotate, and translate back to original location
transMat.identity();
transMat.translate( -bd.width / 2, -bd.height / 2);
transMat.rotate(degToRad(-moveAngle));
transMat.translate(bd.width / 2, bd.height / 2);
 
//Draw with rotated matrix
var tmpBD:BitmapData = bd.clone();
bd.fillRect(bd.rect, 0x00FF0000);
bd.draw(tmpBD, transMat);

We are going to use flash’s built in Matrix class to handle the rotation of the grid that is our source bitmap. When you do a matrix rotate, the cells are rotated around the upper left hand corner (0,0). For our purposes, this is no good. We want to rotate around the center. So what we have to do is move the center of the image up to the upper left hand corner of the matrix. Then do our rotate, then move it back down. Once the matrix is created, we simply apply it with the draw() command. Please note that when creating a matrix for transformation, you are really building a pattern of where a value should be moved to in the grid. So our bitmap is a grid of pixels, and when the matrix is applied, it goes though each of those pixels, and moves the color value of each pixel, to another spot in the grid, giving the illusion of rotation.

That completes step two of the image sequence above. Next we blur the rotated image.

//Apply blur
var blurFilt:BlurFilter;
blurFilt = new BlurFilter(Math.min(10, finalVect.length * 1), 0, 3);
var bda:BitmapData = new BitmapData(boxSize, boxSize, true, 0x0000FF00);
bda.applyFilter(bd, bd.rect, _zeroPoint, blurFilt);

For this we create a new blur filter, and pass in a strength value for X which is equal to the distance between inbetweens with a maximum of 10. Then we create a new bitmap that the blurred result will be draw onto, and finally apply the filter.

With step three completed we finally rotate the blurred image back to complete step four.

//Set up matrix to rotate back
transMat.identity();
transMat.translate(-bda.width/2, -bda.height/2);
transMat.rotate(degToRad(moveAngle));
transMat.translate(bda.width / 2, bda.height / 2);
 
//Draw back with un-rotation matrix
var bdb:BitmapData = new BitmapData(bda.width, bda.height, true, 0x00FF0000);
bdb.draw(bda, transMat, null, null, null, true);

For this we essentially do the opposite of step 2 and rotate the image back to 0 degrees.

Conclusion
With all three of these methods combined you end up with a pretty decent motion blur simulation.


That just about wraps it up for the actual blurring parts of the experiment. As you can see, not one of the blurring methods could really stand on their own, but with the power of each of them, you can get a Captain Planet moment, and everything comes together nicely. There is one more “bonus” issue that we needed to solve, and that is the issue of ease of use. We didn’t want the designers to have to do anything special to use the motion blur. So we made an attempt to make these blurring methods as easy as possible to use without the need for a developer to make it work. We got most of the way there, and that discussion is in the next section.

Implementation
This was by far the most complicated and frustrating part of the whole experiment. We needed to find a way to allow a designer to type the name of the BlurObject class into the properties panel of an object in the library and just have it work. Since most of the drawing is done with bitmap data, we needed to tackle the problem of figuring out how big to make the canvases, where to put those canvases, and how much to offset the inbetweens so that they line up with the source object, even though everything is contained within that source object. Trust me, more RedBull was required.

//Hide canvas
if (_canvasBitmap.parent == this)
{//remove
	removeChild(_canvasBitmap);
}//remove

The final drawing of the inbetweens was done onto an object called _canvasBitmap, which is a Bitmap Object with its BitmapData assigned to _canvas, which is a BitmapData object.

Since we are using the draw() command (as seen earlier) to retrieve the pixels that we need from the source MovieClip, we need to hide any of the previous inbetweens and canvases from any previous frames, because we don’t want that as part of the draw() render. In this case we simply remove the _canvasBitmap from the display list.

From there we determine what kind of blur we want, do, the blurring and handle the alpha as if our source object was sitting at 0,0. When that is all done, we are ready to do our final render to the canvas.

//Build new canvas
var canvasRect:Rectangle = calcCanvasRect(_samplePosList, _sampleList);
_canvas = new BitmapData(canvasRect.width, canvasRect.height, true, 0x00000000);
 
//Push samples into canvas coord. space
offsetPoints(_samplePosList, -canvasRect.left, -canvasRect.top);
 
//Re-align canvas
var canvasOffsetX:Number = -_samplePosList[_samplePosList.length - 1].x;
var canvasOffsetY:Number = -_samplePosList[_samplePosList.length - 1].y;
_canvasBitmap.x = canvasOffsetX;
_canvasBitmap.y = canvasOffsetY;
 
//Final render to canvas
for (var i:int = 0; i < _numSamples; i++)
{//stamp
	_canvas.copyPixels(_sampleList[i], _sampleList[i].rect, _samplePosList[i], null, null, true);
}//stamp
 
 
//Reset canvas bitmapdata source
_canvasBitmap.bitmapData.dispose();
_canvasBitmap.bitmapData = _canvas;
 
//Save old position and remove uneeded positions
_posList.push(new Point(x, y));
_posList.shift();
 
//Update the visuals
addChild(_canvasBitmap);

Line by line, it works like this:

  • Determine what size we need for the final render of all the samples/inbetweens. (I’ll get to that in a minute)
  • Build a new canvas to that size and fill it with empty pixels.
  • Take all of the points in the Sample Position List and offset them by subtracting the canvasRect X position and the canvasRect Y position. Essentially shifting all of the positions into the new canvas coordinate space.
  • Move the final canvas so that the final inbetween lines up with the current position of the source object.
  • Draw all of the samples on the new canvas.
  • Destroy the old canvas, and set the BitmapData property of the canvas bitmap to be the newly created canvas.
  • Drop the oldest of the previous positions stored, and push the current position. This saves the current position so that it can be used as the previous position in the next frame.
  • Add the canvas bitmap back onto the display list.

As for the two functions I glossed over, here they are:

To calculate the new canvas size.

private function calcCanvasRect(pointList:Array, sampleList:Array):Rectangle
{//calcCanvasRect
	var left:Number = pointList[0].x;
	var top:Number = pointList[0].y;
	var bottom:Number = top + sampleList[0].height;
	var right:Number = left + sampleList[0].width;
 
	for (var i:int = 1; i < pointList.length; i++)
	{//find bounds
		var curPoint:Point = pointList[i];
		var sizeRect:Rectangle = sampleList[i].rect;
 
		//sides
		if (curPoint.x < left) { left = curPoint.x; }
		if (curPoint.y < top) { top = curPoint.y; }
		if (curPoint.x + sizeRect.width > right) { right = curPoint.x + sizeRect.width; }
		if (curPoint.y + sizeRect.height > bottom) { bottom = curPoint.y + sizeRect.height; }
 
	}//find bounds
 
	return new Rectangle(left, top, (right-left), (bottom-top));
}//calcCanvasRect

To shift all of the positions in the position list:

private function offsetPoints(pointList:Array, xOffset:Number, yOffset:Number):void
{//offsetPoints
	for (var i:int = 0; i < pointList.length; i++)
	{//offset
		pointList[i].x += xOffset;
		pointList[i].y += yOffset;
	}//offset
}//offsetPoints

Limitations
Ok, so all of that work is finally done for a fairly subtle effect. Was it worth it? I sure hope so. I believe it helps soften the often overly sharp digital movements. Hopefully these concepts can be put to good use in the near future.

As awesome as I think this turned out, it is sadly not without limitations. A couple of these limitations can be overcome with a few minor improvements. The two limitations that should be able to be fixed in a short amount of time, are the rotation interpolation, and the scaling interpolation. Basically both of those can be achieved in the same manner as the linear blur. Simply break up the property across the number of samples and apply those offsets to each of the stamps before drawing them. So for rotation for instance, you would rotate each stamp a little more from the previous frame to the current frame. Same thing goes with scale. These features are not in this post because in order to do this, you need to have a reference to the source image. This would require the movieclip to be set up with set naming or a predefined construction. We tried to avoid that kind of thinking at all cost. So rotation and scaling are not supported in this one. But as I said, it wouldn’t be that difficult to add.

However there is still one limitation that will be very difficult to overcome. Actual frame image interpolation. This means that if you have an animated movieclip, you would need to be able to make new frames out of thin air that show the average changes from the previous frame to the next frame. I know some of this technology exists in some TV’s, Video Codecs, and Projectors, but I don’t believe any of it is artifact free. If you manage to get that working, please post about it someplace :)

The experiment finally comes to an end, and I think we learned quite a bit. Hopefully someone else out there learned from this as well. Good luck!

Line
15 Responses to “Simulated Motion Blur in Flash”
  1. hungconcon says:

    I hope that it will be packed into the library to facilitate sharing, I’ve seen what you research and it is a valuable experience your great, I appreciate this and would like to congratulate you .

  2. Great article!!! Thanks for sharing!

  3. [...] a cue from an totally unrelated article on simulating motion blur, There is a fairly simple technique for determining the maximum area which a rotating quadrilateral [...]

  4. Если сказать больше…

    беакрис рулит…

  5. Jake says:

    Hey Muhammad, thanks very much for the kind words!

  6. Muhammad Arif Butt says:

    Thanks for sharing your great stuff – good to read, and easy to understand.
    very in depth information.

    keep it up. you are a pro

  7. [...] This post was mentioned on Twitter by Burton Posey, Emmanuel Ulloa. Emmanuel Ulloa said: Currently reading http://labs.byhook.com/2010/02/11/simulated-motion-blur-in-flash/ Really good article on Flash Motion Blur [...]

  8. [...] http://labs.byhook.com/2010/02/11/simulated-motion-blur-in-flash/ 喜欢本文,那就收藏到: 两个在线生成 Loading 图片的网站推荐:Flex4视频教程《一周学习Flex4》中文字幕版+离线下载播放器 随机日志关于一些比较热门的flash、flex话题视频在Flash Builder 4 beta中使用Pixel Bender作为数值运算引擎(五)Adobe推出“Pixel Bender Technology Center”专题站Social Game的技术挑战 ——支持千万级DAU的Social Game技术构架(ppt) [...]

  9. Peter Dee says:

    I still get a little misty thinking about those colossi…

  10. Jake says:

    It looks like they are using the built in flash BlurFilter and using it in a similar fashion to the directional blur example above. (I don’t have the code, so I can’t say for sure, this is just a visual observation)
    It does work well with a relatively slow and relatively linear animation. So for things like a slide out panel or whatever I think it would look pretty good. I would be curious to see what happens if you hooked it up to mouse movement, where it can be very erratic and fast moving.
    As for CPU usage I have no doubt that just doing the directional blur would be faster than doing directional and the interpolated blur together.
    If you get to trying some of those things out please let us know the results!

  11. Fast Hosting says:

    Nice tutorial! It’s good to see all the processes that went into getting the motion blur working well. Have you seen the Greensock motion blur plugin? http://www.greensock.com/v11/

    I wonder how this compares. My initial impression is that maybe the Greensock plugin doesn’t take into account the curving trajectory, like this one does, but I’m not sure. I’d be curious to see someone do a comparison of the two. CPU usage would be another comparison factor.

    Thanks for the tutorial and code!

  12. Jake says:

    Thanks for the comments Zack! Please let us know if you getting it running in something, we would love to see it!

  13. Zack Jordan says:

    Seriously great article; very in-depth exploration of the subject. I’m definitely going to be playing around with this. It could be a great addition to some game engines if it were optimized enough.


Leave a Reply

*

Line
Line
Pony