When I created a particle test I just cached images based on rotation (like 35 rotations), color tint, and alpha and created a wrapper so that they were created automatically. Worked well. Yes there should be some kind of tint operation, but when dealing with software rendering your best bet much like in flash is to cache everything.
Particle Example I made for fun
<!DOCTYPE HTML>
<html lang="en">
<head>
<title>Particle Test</title>
<script language="javascript" src="../Vector.js"></script>
<script type="text/javascript">
function Particle(x, y)
{
this.position = new Vector(x, y);
this.velocity = new Vector(0.0, 0.0);
this.force = new Vector(0.0, 0.0);
this.mass = 1;
this.alpha = 0;
}
// Canvas
var canvas = null;
var context2D = null;
// Blue Particle Texture
var blueParticleTexture = new Image();
var blueParticleTextureLoaded = false;
var blueParticleTextureAlpha = new Array();
var mousePosition = new Vector();
var mouseDownPosition = new Vector();
// Particles
var particles = new Array();
var center = new Vector(250, 250);
var imageData;
function Initialize()
{
canvas = document.getElementById('canvas');
context2D = canvas.getContext('2d');
for (var createEntity = 0; createEntity < 150; ++createEntity)
{
var randomAngle = Math.random() * Math.PI * 2;
var particle = new Particle(Math.cos(randomAngle) * 250 + 250, Math.sin(randomAngle) * 250 + 250);
particle.velocity = center.Subtract(particle.position).Normal().Normalize().Multiply(Math.random() * 5 + 2);
particle.mass = Math.random() * 3 + 0.5;
particles.push(particle);
}
blueParticleTexture.onload = function()
{
context2D.drawImage(blueParticleTexture, 0, 0);
imageData = context2D.getImageData(0, 0, 5, 5);
var imageDataPixels = imageData.data;
for (var i = 0; i <= 255; ++i)
{
var newImageData = context2D.createImageData(5, 5);
var pixels = newImageData.data;
for (var j = 0, n = pixels.length; j < n; j += 4)
{
pixels[j] = imageDataPixels[j];
pixels[j + 1] = imageDataPixels[j + 1];
pixels[j + 2] = imageDataPixels[j + 2];
pixels[j + 3] = Math.floor(imageDataPixels[j + 3] * i / 255);
}
blueParticleTextureAlpha.push(newImageData);
}
blueParticleTextureLoaded = true;
}
blueParticleTexture.src = 'blueparticle.png';
setInterval(Update, 50);
}
function Update()
{
// Clear the screen
context2D.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < particles.length; ++i)
{
var particle = particles[i];
var v = center.Subtract(particle.position).Normalize().Multiply(0.5);
particle.force = v;
particle.velocity.ThisAdd(particle.force.Divide(particle.mass));
particle.velocity.ThisMultiply(0.98);
particle.position.ThisAdd(particle.velocity);
particle.force = new Vector();
//if (particle.alpha + 5 < 255) particle.alpha += 5;
if (particle.position.Subtract(center).LengthSquared() < 20 * 20)
{
var randomAngle = Math.random() * Math.PI * 2;
particle.position = new Vector(Math.cos(randomAngle) * 250 + 250, Math.sin(randomAngle) * 250 + 250);
particle.velocity = center.Subtract(particle.position).Normal().Normalize().Multiply(Math.random() * 5 + 2);
//particle.alpha = 0;
}
}
if (blueParticleTextureLoaded)
{
for (var i = 0; i < particles.length; ++i)
{
var particle = particles[i];
var intensity = Math.min(1, Math.max(0, 1 - Math.abs(particle.position.Subtract(center).Length() - 125) / 125));
context2D.putImageData(blueParticleTextureAlpha[Math.floor(intensity * 255)], particle.position.X - 2.5, particle.position.Y - 2.5, 0, 0, blueParticleTexture.width, blueParticleTexture.height);
//context2D.drawImage(blueParticleTexture, particle.position.X - 2.5, particle.position.Y - 2.5);
}
}
}
</script>
<body onload="Initialize()" style="background-color:black">
<canvas id="canvas" width="500" height="500" style="border:2px solid gray;"/>
<h1>Canvas is not supported in this browser.</h1>
</canvas>
<p>No directions</p>
</body>
</html>
where vector.js is just a naive vector object:
// Vector class
// TODO: EXamples
// v0 = v1 * 100 + v3 * 200;
// v0 = v1.MultiplY(100).Add(v2.MultiplY(200));
// TODO: In the future maYbe implement:
// VectorEval("%1 = %2 * %3 + %4 * %5", v0, v1, 100, v2, 200);
function Vector(X, Y)
{
/*
this.__defineGetter__("X", function() { return this.X; });
this.__defineSetter__("X", function(value) { this.X = value });
this.__defineGetter__("Y", function() { return this.Y; });
this.__defineSetter__("Y", function(value) { this.Y = value });
*/
this.Add = function(v)
{
return new Vector(this.X + v.X, this.Y + v.Y);
}
this.Subtract = function(v)
{
return new Vector(this.X - v.X, this.Y - v.Y);
}
this.Multiply = function(s)
{
return new Vector(this.X * s, this.Y * s);
}
this.Divide = function(s)
{
return new Vector(this.X / s, this.Y / s);
}
this.ThisAdd = function(v)
{
this.X += v.X;
this.Y += v.Y;
return this;
}
this.ThisSubtract = function(v)
{
this.X -= v.X;
this.Y -= v.Y;
return this;
}
this.ThisMultiply = function(s)
{
this.X *= s;
this.Y *= s;
return this;
}
this.ThisDivide = function(s)
{
this.X /= s;
this.Y /= s;
return this;
}
this.Length = function()
{
return Math.sqrt(this.X * this.X + this.Y * this.Y);
}
this.LengthSquared = function()
{
return this.X * this.X + this.Y * this.Y;
}
this.Normal = function()
{
return new Vector(-this.Y, this.X);
}
this.ThisNormal = function()
{
var X = this.X;
this.X = -this.Y
this.Y = X;
return this;
}
this.Normalize = function()
{
var length = this.Length();
if(length != 0)
{
return new Vector(this.X / length, this.Y / length);
}
}
this.ThisNormalize = function()
{
var length = this.Length();
if (length != 0)
{
this.X /= length;
this.Y /= length;
}
return this;
}
this.Negate = function()
{
return new Vector(-this.X, -this.Y);
}
this.ThisNegate = function()
{
this.X = -this.X;
this.Y = -this.Y;
return this;
}
this.Compare = function(v)
{
return Math.abs(this.X - v.X) < 0.0001 && Math.abs(this.Y - v.Y) < 0.0001;
}
this.Dot = function(v)
{
return this.X * v.X + this.Y * v.Y;
}
this.Cross = function(v)
{
return this.X * v.Y - this.Y * v.X;
}
this.Projection = function(v)
{
return this.MultiplY(v, (this.X * v.X + this.Y * v.Y) / (v.X * v.X + v.Y * v.Y));
}
this.ThisProjection = function(v)
{
var temp = (this.X * v.X + this.Y * v.Y) / (v.X * v.X + v.Y * v.Y);
this.X = v.X * temp;
this.Y = v.Y * temp;
return this;
}
// If X and Y aren't supplied, default them to zero
if (X == undefined) this.X = 0; else this.X = X;
if (Y == undefined) this.Y = 0; else this.Y = Y;
}
/*
Object.definePropertY(Vector, "X", {get : function(){ return X; },
set : function(value){ X = value; },
enumerable : true,
configurable : true});
Object.definePropertY(Vector, "Y", {get : function(){ return X; },
set : function(value){ X = value; },
enumerable : true,
configurable : true});
*/