Flash-like image manipulation with CreateJS

This is an iteresting post made by Roger Engelbert, the author of step by step creation of a 3D helicopter game using Away3D and one game, many frameworks.

Today he’s going to show you how to achieve Flash-like image manipulations with EaselJS, the same framework I used to make a javascript port of Circle Chain game.

« I‘ve decided to take a very simple application done in Flash and transform it in JavaScript for Canvas, using an open source framework, trying to use as many features typically associated with Flash as I could cram in a tiny bit of code, and see how the framework handled them (mainly: events, bitmap data manipulation, the display list and masks).

The application is the core logic inside an old project, FlashNose3D, which I used years ago to talk about PureMVC. Only here, I’m just using the animation bit.

The framework I picked was EaselJS, which is based on ActionScript.

Here is the test application in Flash, using circles:

And here are the classes:

Main.as

package  {
 
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
 
	public class Main extends Sprite {
 
		private var _slices:Array;
 
		function Main () {
			_slices = [];
 
			var startWidth:int = 400;
			var numSlices:int = 40;
			var fraction:int = startWidth/(numSlices - 1);
			var s:Slice;
			for (var i:int = 0; i < numSlices; i++) {
				s = new Slice ((startWidth - i * fraction) * 0.5, i);
				s.x = stage.stageWidth >> 1;
				s.y = stage.stageHeight >> 1;
				this.addChild(s);
				_slices.push(s);
			}
			addEventListener(Event.ENTER_FRAME, on_Loop);
		}
 
 
		private function on_Loop (event:Event):void {
			var len:int = _slices.length;
			var s:Slice;
			var previous:Slice;
			for (var i:int = len-1; i >= 0; i--) {
				s = _slices[i];
				if (i == len-1) {
					s.update(new Point(mouseX, mouseY));
				} else {
					previous = _slices[i + 1];
					s.update(new Point(previous.x, previous.y));
				}
			}		
		}
	}
}

Slice.as

package  {
 
	import flash.display.Sprite;
	import flash.geom.Point;
 
	public class Slice extends Sprite {
 
 
		private var _vx:Number = 0;
		private var _vy:Number = 0;
		private var _friction:Number = 0.2;
		private var _spring:Number = 2;
 
		function Slice (radius:int, index:int) {
 
			this.graphics.beginFill(index * 50);
			this.graphics.drawCircle(0, 0, radius);
			this.graphics.endFill();
		}
 
 
		public function update (target:Point) {
 
			var diffx:Number;
			var diffy:Number;
			var ax:Number;
			var ay:Number;
 
			diffx = target.x - x;
			diffy = target.y - y;
 
			ax = diffx * _spring;
			ay = diffy * _spring;
 
			_vx += ax;
			_vy += ay
 
			_vx *= _friction;
			_vy *= _friction;
 
			this.x += _vx;
			this.y += _vy;
 
		}
 
	}
 
}

Then the EaselJS version. Click anywhere on the image to start the animation (although the effect looks better if you click on: nose, or mouth, or eyes).

And the code:

Main.js

//declare our globals
var NUM_SLICES = 50;
var FRICTION = 0.2;
var SPRING = 2;
 
(function(){
 
	this.animate;
	this.slices;
	this.stage;
	this.image;
 
 
 	this.init = function() {
 
		this.animate = false;
		this.slices = [];
 
		this.image = new Image();
		//handler to preload our image
		this.image.onload = handleImageLoad;
		this.image.src = "doctor3.jpg";
	}
 
	this.handleImageLoad = function () {
 
		var canvas = document.getElementById("testCanvas");
		stage = new createjs.Stage(canvas);
 
 
		var ratio = image.width/(NUM_SLICES + 1);
		var s;
		for (var i = 0; i < NUM_SLICES; i++) {
 
			if (i == 0) {
				s = new Slice ( image, -1 );
			} else {
				s = new Slice ( image, (image.width - i * ratio) * 0.5 );				
 
			}
			s.x = canvas.width * 0.5;
			s.y = canvas.height * 0.5;
 
			stage.addChild(s);
 
 
			slices.push(s);
		}
 
 
		//add stage event: Mouse Click
		stage.onClick = function (event) {
 
			if (animate) return;
 
			var p = slices[0].globalToLocal(event.stageX, event.stageY);
			var len = slices.length;
 
			for (var i = 0; i < len; i++) {
				slices[i].setNose(p.x, p.y);			
			}
 
			animate = true;
		}
		//render stage
		stage.update();
 
		//register main loop event
		createjs.Ticker.setFPS(30);
		createjs.Ticker.addListener(window);
	}
 
 
	//the main loop
	this.tick = function () {
 
		if (animate) {
 
			var len = slices.length;
			var s;
			var previous;
 
			for (var i = len-1; i >= 0; i--) {
				s = slices[i];
				if (i == len-1) {
					s.update(stage.mouseX, stage.mouseY);
				} else {
					previous = slices[i + 1];
					s.update(previous.x, previous.y);
				}
			}
 
		}
		//render stage
		stage.update();
	}
 
 
})();
 
window.onload = init;

Slice.js

(function(window) {
 
 
	this.circleMask;
    this.vx;
    this.vy;
 
    function Slice(image, radius) {
 
        this.initialize();
 
        var canvas = document.getElementById("testCanvas");
        this.image = image;
 
        this.regX = image.width * 0.5;
        this.regY = image.height * 0.5;
 
        vx = 0;
        vy = 0;
 
        if (radius > 0) {
 
        	this.circleMask = new createjs.Shape();
	        this.circleMask.graphics.beginFill("0xFFF");
			this.circleMask.graphics.drawCircle(0,0,radius);
			this.circleMask.graphics.endFill(); 	
        }    
    }
 
    Slice.prototype = new createjs.Bitmap();
 
    Slice.prototype.circleMask;
    Slice.prototype.vx;
    Slice.prototype.vy;
 
    Slice.prototype.Bitmap_initialize = Slice.prototype.initialize;
 
    Slice.prototype.initialize = function() {
        this.Bitmap_initialize();
    }
 
 
    Slice.prototype.setNose = function (noseX, noseY) {
 
        this.regX = noseX;
        this.regY = noseY;
 
        if (this.circleMask) {
            this.mask = this.circleMask;
        }
    }
 
    Slice.prototype.update = function (targetX, targetY) {
 
        var diffx, diffy, ax, ay;
 
        diffx = targetX - this.x;
        diffy = targetY - this.y;
 
        ax = diffx * SPRING;
        ay = diffy * SPRING;
 
        vx += ax;
        vy += ay
 
        vx *= FRICTION;
        vy *= FRICTION;
 
        this.x += vx;
        this.y += vy;
 
        if (this.circleMask) {
            this.circleMask.x = this.x;
            this.circleMask.y = this.y;
        }
 
    }
 
    Slice.prototype.normalize = function (p) {
    	var length = Math.sqrt(p.x*p.x+p.y*p.y);
    	return 	new createjs.Point((p.x/length), (p.y/length));
	}
 
    window.Slice = Slice;
}(window));

Of course, the JS version uses an actual image, so the classes are a bit more extensive.

But you can see, among other things:

– how to write classes with EaselJS
– how you can extend a display object in EaselJS. In the code I extended the Bitmap class.
– how to draw vector shapes. I used a circle for the mask.
– how to mask a bitmap with a vector shape.
– how to add the main loop
– how to add mouse events on stage. The same logic would work for any display object.
– how to change the registration point of a display object with its properties regX and regY.

»

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers