Super-fast scrolling of huge Bitmaps

AS3 Fast Bitmap Scroll Demo

Click To View Demo

I seem to get a lot of projects that require scrolling backgrounds. Ususally they are reasonably sized backgrounds, that tile or repeat. And there are a bunch of good solutions to smoothly scrolling tiled backgrounds. But recently I got a game assignment that was a new case. There was a HUGE non-repeating background image for a Where’s Waldo style hunt and find game.

This bitmap was 7163×2337, and due to the importance of detail we couldn’t make it any smaller than it already was.

So I had to tackle a technical solution so scroll a GIANT image smoothly in Flash. Now keep inmind you need to be using Flash Player 10.1 just to even use such huge images. But even so, all of my previous approaches failed. The image stuttered or tore as it moved, and the overall user experience was just plain ugly.

But then I had an idea: I imagined a camera panning across the huge image, just revealing a little of it at a time. I knew I could do this using BitmapData.draw(), and using the Matrix class to move it around.  I knew I could get good speeds with a 800×600 BitmapData.draw() and so I tried that. It worked better, but had the same issues as other solutions. I started messing with different speeds with the timer, and with the frame rate of the movie. But then I got a good tip from my friend MrGamesChef: try lowering the frame rate, and dropping the timer cycle as low as possible. Then use updateafterevent.

Well, folk. It works. And boy howdy does it. This awesome image in this example is 5147×3456 and it moves “like buttah”. Hopefully this will help out someone looking for a way to smoothly pan or scroll very large bitmaps in flash using AS3.

Click through for the code and zip of my sources :

The source for this is nice and simple. It uses two simple techniques. The first is BitmapData.draw(). Think of this as taking a snapshot in flash. It takes two parameters. The first parameter is the thing you want to draw. All displayObjects work: Bitmap, Sprite, MovieClip, Shape, even BitmapData.

The second parameter is the matrix. This is a matrix of the type flash.geom.Matrix. Every display object has a transform matrix, that describes its x, y, scale, and rotation as a matrix. (you can find it in displayobject.transform.transformmatrix()) In this example we only care about using the x and y, or as they are called in Matrix-speak, the tx and ty for translate-x and translate-y. In short, how far has the matrix shifted from the origin point at 0,0. An important point to remember is that tx and ty are relative to the object being drawn. And so the values will be the negative of the apparent direction of motion.

To make that a bit clearer, if you picture a camera above a sheet of paper, the matrix is shifting the paper, and the camera stays stationary. So to appear that the camera is pannign right over the paper, we shift the paper to the left. In the code,you’ll see this as using negative numbers to move the matrix.

Finally, here is the code example:

package 
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.TimerEvent;
	import flash.geom.Matrix;
	import flash.utils.Timer;
 
	/**
	 * ...
	 * @author Zach
	 */
	[Frame(factoryClass="Preloader")]
	public class FastBitmapScroll extends Sprite 
	{
		// This image used in this example and where to find it and buy a poster of it.
		[Embed(source = 'map_of_humanity_large.jpg')]
		private var mapClass:Class
		private var linkToMapOfHumanity:String = "http://www.jtillustration.com/maps.html";
 
 
		// Speed at which to scroll the canvas;
		private var speedX:Number = 0;
		private var speedY:Number = 0;
 
		// midpoint of the screen. Sacved as variable to improve speed;
		private var midX:Number = 400;
		private var midY:Number = 300;
 
		//
 
		//The bitmapdata visible to user;
		private var canvas:BitmapData;
 
		//The giant source bitmapdata;
		private var map:Bitmap;
 
		//The timer that drives it all;
		private var updateTimer:Timer;
		private var matrix:Matrix;
 
		// values to prevent the matrix from moving outside the bitmap;
		private var xLimit:Number;
		private var yLimit:Number;
 
		public function FastBitmapScroll():void 
		{
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
 
		private function init(e:Event = null):void 
		{
			removeEventListener(Event.ADDED_TO_STAGE, init);
			// entry point
 
			//create bitmaps
			canvas =  new BitmapData(800, 600, false);
			map = new mapClass();
 
			var canvasBitmap:Bitmap = new Bitmap(canvas);
			addChild(canvasBitmap);
			matrix = new Matrix();
			// create limits
			xLimit = stage.stageWidth - map.width;
			yLimit = stage.stageHeight - map.height;
 
			// set matrix transfor to middle of map;
			matrix.tx = 400 - (map.width * 0.5);
			matrix.ty = 300 - (map.height * 0.5);
 
			//create and start the timer loop;
			updateTimer = new Timer(3, 0); // *magic sauce #1
			updateTimer.addEventListener(TimerEvent.TIMER, update);
			updateTimer.start();
		}
 
		private function update(e:TimerEvent):void 
		{
			speedX = (midX - mouseX) * 0.01;
			speedY = (midY - mouseY) * 0.01;
			/*
			* Note: I cube my speeds. The reason is:
			* A. To make the speeds slower towards the center
			* B. Cubing rather than squaring preserves the sign.
			* (-1 * -1 * -1) still equals -1, where as (-1 * -1) becomes 1.
			*/
			matrix.translate(speedX * speedX * speedX, speedY * speedY * speedY);
			// reset matrix transforms if matrix is out of bounds.
			if (matrix.tx > 0) { 
				matrix.tx = 0;
			} else if ( matrix.tx < xLimit) {
				matrix.tx = xLimit;
			}
			if (matrix.ty > 0) { 
				matrix.ty = 0;
			} else if ( matrix.ty < yLimit) {
				matrix.ty = yLimit;
			}
			canvas.draw(map, matrix);
			e.updateAfterEvent();// *magic sauce #2
		}
 
	}
 
}

And here is the zip file of the whole project.

About The Plastic Sturgeon

This is my site. It gets pretty biographical on the About page.
This entry was posted in Tutorials and tagged , , , , , . Bookmark the permalink.

20 Responses to Super-fast scrolling of huge Bitmaps

  1. Vikram says:

    Wow.. Magic sauce #2 rocks \m/
    Thanks for this lifesaver and also the zip file :)

  2. prognplay says:

    Wow !! Wonderfull thanks a lot for share !! very very impressive

  3. Darcey says:

    Could you do a version where the image is tiled/repeated?

  4. Digitalic says:

    Darcey, I don’t know if this will help, but Lee Brimelow has done a ‘Quick Tip’ which covers both creating seamless images in Photoshop and implementing the scrolling in AS3. Go to leebrimelow.com and look for “Creating Seamless Background Textures” in the Quick Tips section.

    I think that combining this with Plastic Surgeon’s tutorial should help you to achieve what you need.

  5. Lavon says:

    Why would you use draw? I thought copyPixels was faster?

  6. Copy pixels does not allow you to scroll by less than on pixel at a time. this makes the animation look choppy when scrolling a low speeds.In any case, the best method for this now, is to use Stage3D.

  7. Gabriele says:

    How would you position a player (bitmap) in a location relative to map coordinates ?
    I am looking into matrix transformations , as I kind of remember opengl dealing with matrices in my coder-youth.

  8. You can use the matrix tx and ty mode to know how far from the origin the background has moved, then add that to the x and y position of a sprite relative to the 0,0 corner of the bitmap being scrolled. They key is that matrix.tx and matrix.ty correspond to the x and y position of your scrolling BG.

  9. MacShrike says:

    o7, I salute you,

    Although im not using the matrix; just scrolling the background underneath the main screen (Might go there if I have time left). But I am using the timed afterupdate construction.
    Finally got rid of the slight jerk that the screen showed when some NPC had to calculate some extra.
    Perhaps I can now even increment the maximum NPCs on stage, doing that before would totally give ervey human beeing an epilleptic attack.
    Now all movement is consistant and smooth even using a 3072 X 1824 movieclip on a mobile device. pretty sweet.

    Bless you for sharing.

    Mac

  10. MacShrike says:

    Ah but I forgot to ask you this….

    Smooth scrolling is now only a question of tweaking the framerate v.s the timer.Great! But..

    Using a .jpg 3000X1800 that is compressed from 22Mb to about 900Kb takes about 15 seconds to decode and instantiate to a MC using new myMc() On a(my) mobile device.

    Is this the same for your method (canvas blitting)? and is it the turning the .jpg into a new mc what takes so much time?
    Or does embedding the jpg and setting it up as a bitmapcanvas take just as long?

    Sounds lazy because I could just try and see for myself but I’ll have to rewrite a lot of code just to test it.
    I was hoping you could answer this question out of experience and or expertise.

    Highest regards,
    Mac

  11. Arby says:

    Looks great, but there is nothing we can do about the horrible tearing, huh?

  12. Tearing is dependent on your system, It doesn’t happen on any of the ones I have access to. However, for anything requiring performance in flash, I now recommend using Starling or some other openGL solution that uses Stage3D. The speed is way better. I’ll try and post a how to for that soon.

  13. Kyle says:

    Little bit confused here. You say you wanna use draw instead of copyPixels because it allows for smoother scrolling. But… in your example when I try moving really slowly it never seems to move in increments of less than one pixel anyway. Is that some kind of mistake? Is bitmap smoothing turned off or something?

    Now you’re talking about Stage3D, but that worries me because the power of Stage3D is dependent on the power of a user’s GPU, right? It seems to me that one of the big appeals of Flash games is that you don’t need a gaming computer to run them. So when everyone’s making Flash games that use Stage3D people like me who have weak video cards will be screwed over.

  14. @Kyle – so you asked two questions: first about the 1-pixel increments. Flash itself only renders by the 1/2 pixle increment. So moving an object less than 0.5 pixels will not change the screen drawing of that object. So a low speeds, you still see some wiggle. But I promise you, if you switch to using copy pixels, you will notice that being forced to move in 1-pixel increments makes the scrolling appear not-smooth.

    As for Stage 3D: You’re partially right. Stage 3D is designed to use the GPU. But Flash’s implementation of 3D is based on a very conservative spec. You don’t need a gaming rig. It will run on mobile devices even. People with ANY video card will get excellent performance. And users with no video card (they still exist) will probably still get better performance from stage 3D than from the method I show above. The stage 3D has a software render fallback for those cases. Its much, much slower than GPU drawing, But so far I have not encountered any machines without GPU acceleration in my field testing. But if you think you may be targeting that edge case of hardware, you may need to consider possibly skipping graphics altogether and sticking to command-lines and text.

  15. nubz says:

    Nice indeed, this works perfectly!
    What would you suggest if this was used fullscreen with rescaling, like I’m doing with this and big map photos? I’m trying to make large photo also zoomable on mouse scroll, and this far I’ve tried to change canvasBitmap.scaleX & .scaleY on the fly and it rescales the bitmap for sure, but same time it rescales render area, which I would like to keep in size of fullscreen all the time.. I’ve tried everything “simple” but there must be something I’ve missed?

  16. Damondin says:

    thanks, this example was very useful

  17. Joel says:

    Incredible. I was trying for days to implement a system that subdivided the map into chunks and tried to work out which chunks it should load, but I could never get reliable chunks. I chucked your class into my code, changed a couple of lines and I was scrolling past thousands of tiles in no time. Thanks!

  18. Sumeet Basak says:

    i have read your blog many years back and it was great to learn from this to experiment on other things. today i use a part of this.
    i have a 6 bitmaps that are 2000 px in height each, these have to scroll on y axis in a loop on a android device. using greensock blitmask was optimised around 7%-9% of CPU but choppy. ofcourse the large bitmap had to be on stage as a movieclip and tween that to so that a blitmask can be created. the results were not pleasing as compared to the method you mentioned. using the matrix to redraw at regular intervals. the output is super slick, but the performance shot up to 21%. i am sure its because of the BitmapData.draw() function used at every interval.
    any ideas how i could optimise this down?

  19. Sumeet Basak says:

    by the way thanks for sharing this technique :)
    kudos.

  20. Paul says:

    Hi, interesting article.

    I’m running into a problem with a drawing app I’m developing, I need the image to follow the mouse but it has a little delay, I’m using Starling, and there is no updateAfterEvent.

    It seems like I would need to force a render when I’m moving the image through the stage.

    Do you know of any workaround?.

    Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>