function ParticleShape(imageSource, particleCount, colors, hiddenCanvasID, x, y) {
	
	// states for the particles
	var FLOATING = 0;
	var SPELLING = 1;
	var EXPLODING = 2;
	
	var circleArray = new Array();
	var context = $('#main_canvas')[0].getContext("2d");
	var textureContext = $(hiddenCanvasID)[0].getContext("2d");
	var width = $("#main_canvas").width();
	var height = $("#main_canvas").height();
	var textureWidth = $(hiddenCanvasID).width();
	var textureHeight = $(hiddenCanvasID).height();
	var currentState = FLOATING;
	var image;
	var colors = colors;
	var particleCount = particleCount;
	var colorChangeSpeed = 5;
	var drawImage = false;
	var hideParticles = false;
	var particleAlpha = 1.0;
	var imageAlpha = 0.0;
	var nGeneralWindX=Math.sin(Math.random()*360);
	var nGeneralWindY=Math.cos(Math.random()*360);
	
	this.x = x;
	this.y = y;
	this.width = textureWidth;
	this.height = textureHeight;
	this.currentSpeed = 0.01;
	
	
	var init = function() {

		// load the image
		image = new Image();
		$(image).load(function(){});
		image.src=imageSource;
		
		// create all the particles placing them at random places, random velocities 
		// and random colors (from the colors specified)
		for(i=0; i < particleCount; i++) {
			circleArray[i] = new Circle(Math.floor(Math.random()*width), Math.floor(Math.random()*height),(Math.random() * 3) + 2);
			circleArray[i].color = colors[Math.floor(Math.random() * colors.length)];
		}
	}
	
	
	
	// draws and updates where the circles are supposed to be visually
	this.draw = function() {

		context.globalAlpha = 1.0;
		
		// draw the image if we want
		if (drawImage) {
			if (imageAlpha < 1.0)
				imageAlpha += 0.02;
			context.globalAlpha = imageAlpha;
			context.drawImage(image,this.x,this.y);
		}
		
		// hide the particles if we want
		if (hideParticles) {
			if (particleAlpha > 0.02)
				particleAlpha -= 0.02;
			else
				particleAlpha = 0.0;
		}



		for (var i = 0; i < circleArray.length; i++) {
			var C=circleArray[i];	
		
			// move them around
			if (C.destX>-1 && C.destX != -2) {	// to the point
				
				C.x += (C.destX - C.x) * this.currentSpeed;
				C.y += (C.destY - C.y) * this.currentSpeed;

			}
			else{ // floating around
				
				C.x += C.vx / 5 + nGeneralWindX;
				C.y += C.vy / 5 + nGeneralWindY;

				if (C.destX == -2) {
					C.vx *= .99;
					C.vy *= .99;
				}
			}

			// fades the color to the destination color
			if (C.colorFade) {
				if (C.color == C.destColor) C.colorFade = false;

				var destRGB = hex2rgb(C.destColor);
				var myRGB = hex2rgb(C.color);

				if (myRGB[0] < destRGB[0]) 
					myRGB[0] = myRGB[0] + colorChangeSpeed;
				else if (myRGB[0] > destRGB[0]) 
					myRGB[0] = myRGB[0] - colorChangeSpeed;

				if (myRGB[1] < destRGB[1])
					myRGB[1] = myRGB[1] + colorChangeSpeed;
				else if (myRGB[1] > destRGB[1])
					myRGB[1] = myRGB[1] - colorChangeSpeed;

				if (myRGB[2] < destRGB[2]) 
					myRGB[2] = myRGB[2] + colorChangeSpeed;
				else if (myRGB[2] > destRGB[2])
					myRGB[2] = myRGB[2] - colorChangeSpeed;

				if (myRGB[0] > 255) myRGB[0] = 255;
				if (myRGB[1] > 255) myRGB[1] = 255;
				if (myRGB[2] > 255) myRGB[2] = 255;

				if (myRGB[0] < 0) myRGB[0] = 0;
				if (myRGB[1] < 0) myRGB[1] = 0;
				if (myRGB[2] < 0) myRGB[2] = 0;

				C.color = "#" + toHex(myRGB[0]) + toHex(myRGB[1]) + toHex(myRGB[2]);
			}

			// decay the wind
			nGeneralWindX*=.9999;
			nGeneralWindY*=.9999;

			// keep the circle within the canvas space
			if (C.x <= 0 + C.r) { C.x = C.r; C.vx =- C.vx; }
			if (C.y <= 0 + C.r) { C.y = C.r; C.vy =- C.vy; };
			if (C.x >= width - C.r) { C.x = width - C.r; C.vx =- C.vx; }
			if (C.y >= height - C.r) { C.y = height - C.r; C.vy =- C.vy * .45; }

			// draw the circle
			if (C.destX > -1 && hideParticles) {
				context.globalAlpha = particleAlpha;
			}
			
			context.beginPath();
			context.fillStyle = C.color;
			context.arc(C.x, C.y, C.r, 0, Math.PI*2, true);
			context.closePath();
			context.fill();
			
			context.globalAlpha = 1.0;
		}	
		
		if (currentState == SPELLING)
			this.currentSpeed *= 1.05;
		else
			this.currentSpeed = 0.01;
	}
	
	
	
	// groups the particles together to spell out word
	this.spell = function() {
		
		currentState = SPELLING;
		var nIndex=0;

		textureContext.fillRect(0,0,textureWidth,textureHeight);
		textureContext.drawImage(image,0,0);
		
		var imageData = textureContext.getImageData(0,0,textureWidth,textureHeight);
		var sqWidth, sqHeight;
		if ($.browser.mozilla) {
			sqWidth = 10;
			sqHeight = 10;
		}
		else {
			sqWidth = 7;
			sqHeight = 7;
		}
		
		/*
		Sample pixels according to sq width intervals. Pixels have four components rgba.
		Transparent pixels rgba(0,0,0,255) should be represented by a circle
		*/

		for(i=0;i<imageData.height;i=i+sqHeight){
			for(j=0;j<imageData.width;j=j+sqWidth) {
				var nAvg=0;
				
				var index=(i*4)*imageData.width+(j*4);
			    var red=imageData.data[index]; 
			    var green=imageData.data[index+1]; 
			    var blue=imageData.data[index+2]; 
			    var alpha=imageData.data[index+3];
			    nAvg = red + green + blue;

				if(red>0&&green>0&&blue>0&&nIndex<circleArray.length){
					circleArray[nIndex].vx = Math.random()-.5*5;
					circleArray[nIndex].vy = Math.random()-.5*5;
					circleArray[nIndex].destX = j + this.x; //x destination 
					circleArray[nIndex].destY = i + this.y; //y destination
					nIndex++
				}
			}
		}

		for(i=nIndex+1;i<circleArray.length;i++) {
			//I think this flags unused pixels 
			circleArray[i].destX=-1;
			circleArray[i].destY=-1;
			circleArray[nIndex].vx = Math.random()-.5*5;
			circleArray[nIndex].vy = Math.random()-.5*5;
		}

	}


	this.explode = function() {
		
		currentState = EXPLODING;

		for (var i = 0; i < circleArray.length; i++) {
			var C = circleArray[i];	

			// only particles that are spelling out the letter
			if (C.destX > -1) {
				
				// our goal here is to find the distance of the circle from the center of the texture
				// then to calculate a random velocity (shooting it out in different directions)
				// and multiply it by a ratio of how far it is from the center
				// basically, it explodes everything.
				var x1 = textureWidth/2 + x;
				var y1 = textureHeight/2 + y;
				var xdiff = C.x - x1;
				var ydiff = C.y - y1;
				var distanceFromCenter = Math.sqrt(xdiff*xdiff + ydiff*ydiff);
				var maxDistance = Math.sqrt(-x1 * -x1 + -y1 * -y1);
				var distanceRatio = ((distanceFromCenter - maxDistance) / maxDistance) * 10;
				C.vx = ((Math.random() * 20) - 10) * distanceRatio;
				C.vy = ((Math.random() * 20) - 10) * distanceRatio;
				C.destX = -2;
				C.destY = -2;
			}
		}
	}
	
	
	this.makeBlack = function() {
		
		for (var i = 0; i < circleArray.length; i++) {
			var C = circleArray[i];
			// only fade the ones that are forming the shape
			if (C.destX>-1 && C.destX != -2) {
				C.destColor = "#38404D";
				C.colorFade = true;
			}
		}
	}
	
	this.makeColor = function() {
		
		for (var i = 0; i < circleArray.length; i++) {
			var C = circleArray[i];
			C.destColor = colors[Math.floor(Math.random() * colors.length)];
			C.colorFade = true;
		}
	}

	this.showImage = function() {
		currentState = FLOATING;
		drawImage = true;
		imageAlpha = 0.0;
	}

	this.hideImage = function() {
		drawImage = false;
		imageAlpha = 0.0;
	}

	this.hideParticles = function() {
		currentState = FLOATING;
		hideParticles = true;
		particleAlpha = 1.0;
	}

	this.showParticles = function() {
		hideParticles = false;
		particleAlpha = 1.0;
	}
	
	this.destroyParticles = function() {
		circleArray = [];
	}
	
	function hexToR(h) {return parseInt((cutHex(h)).substring(0,2),16)}
	function hexToG(h) {return parseInt((cutHex(h)).substring(2,4),16)}
	function hexToB(h) {return parseInt((cutHex(h)).substring(4,6),16)}
	function cutHex(h) {return (h.charAt(0)=="#") ? h.substring(1,7):h}

	var hex2rgb = function(hex) {
		var r = hexToR(hex);
		var g = hexToG(hex);
		var b = hexToB(hex);
		return [r, g, b];
	}

	var toHex = function(N) {
		 if (N==null) return "00";
		 N=parseInt(N); if (N==0 || isNaN(N)) return "00";
		 N=Math.max(0,N); N=Math.min(N,255); N=Math.round(N);
		 return "0123456789ABCDEF".charAt((N-N%16)/16)
		      + "0123456789ABCDEF".charAt(N%16);
	}
	
	init();
}
