# Optimizing image generation in Perlin noise generator

I've created a simple Perlin noise generator in JavaScript and am trying to find ways to optimize it. It lives inside a webpage and generates an image using nothing more than random number generator and a lot of arrays and algebra.

The end result is kind of slow. Not unusable, but a lot slower than a Java applet would have been. I'm wondering if I'm making some naive mistakes that could really speed things up or if JavaScript is not really up to this sort of task.

The code is on jsbin

function Random()
{
this.m = 2147483647; //2^31 - 1
this.a = 16807; //7^5; primitive root of m
this.q = 127773; // m / a
this.r = 2836; // m % a
this.seed = 1;

this.setSeed = function(seed)
{
if (seed <= 0)
{
seed = -(seed % (this.m - 1)) + 1;
}
if (seed > this.m - 1)
{
seed = this.m - 1;
}
this.seed = seed;
};

this.nextLong = function()
{
var res = this.a * (this.seed % this.q) - this.r * Math.floor(this.seed / this.q);
if (res <= 0)
{
res += this.m;
}
this.seed = res;
return res;
};

this.next = function()
{
var res = this.nextLong();
return res / this.m;
};
}

function Color(r, g, b, a)
{
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}

function PerlinSampler2D(width, height, randseed)
{
this.width = width;
this.height = height;
this.randseed = randseed;
this.gradients = new Array(width * height * 2);

var rand = new Random();
rand.setSeed(randseed);
for (var i = 0; i < this.gradients.length; i += 2)
{
var x, y;
var angle = rand.next() * Math.PI * 2;
x = Math.sin(angle);
y = Math.cos(angle);

}

this.dot = function(cellX, cellY, vx, vy)
{
var offset = (cellX + cellY * this.width) * 2;
var wy = this.gradients[offset + 1];
return wx * vx + wy * vy;
};

this.lerp = function(a, b, t)
{
return a + t * (b - a);
};

this.sCurve = function(t)
{
return t * t * (3 - 2 * t);
};

this.getValue = function(x, y)
{
var xCell = Math.floor(x);
var yCell = Math.floor(y);
var xFrac = x - xCell;
var yFrac = y - yCell;

var x0 = xCell;
var y0 = yCell;
var x1 = xCell === this.width - 1 ? 0 : xCell + 1;
var y1 = yCell === this.height - 1 ? 0 : yCell + 1;

var v00 = this.dot(x0, y0, xFrac, yFrac);
var v10 = this.dot(x1, y0, xFrac - 1, yFrac);
var v01 = this.dot(x0, y1, xFrac, yFrac - 1);
var v11 = this.dot(x1, y1, xFrac - 1, yFrac - 1);

var vx0 = this.lerp(v00, v10, this.sCurve(xFrac));
var vx1 = this.lerp(v01, v11, this.sCurve(xFrac));

return this.lerp(vx0, vx1, this.sCurve(yFrac));
};
}

function Image(width, height)
{
this.width = width;
this.height = height;

this.data = new Array(width * height * 4);

this.setColor = function(x, y, color)
{
var offset = ((y * this.width) + x) * 4;

this.data[offset] = color.r;
this.data[offset + 1] = color.g;
this.data[offset + 2] = color.b;
this.data[offset + 3] = color.a;
};

this.setRgba = function(x, y, r, g, b, a)
{
var offset = ((y * this.width) + x) * 4;

this.data[offset] = r;
this.data[offset + 1] = g;
this.data[offset + 2] = b;
this.data[offset + 3] = a;
};

this.createPerlinNoise = function(period, randseed)
{
var sampler = new PerlinSampler2D(Math.ceil(this.width / period), Math.ceil(this.height / period), randseed);

for (var j = 0; j < this.height; ++j)
{
for (var i = 0; i < this.width; ++i)
{
var val = sampler.getValue(i / period, j / period);
var b = (val + 1) / 2 * 256;

this.setRgba(i, j,
b, b, b, 0xff);
}
}
};

this.createTurbulence = function(period, levels, randseed)
{
var layers = new Array(this.width * this.height);

for (var j = 0; j < this.height; ++j)
{
for (var i = 0; i < this.width; ++i)
{
layers[i + j * this.width] = 0;
}
}

var localPeriod = period;
var atten = 1;
for (var k = 0; k < levels; ++k)
{
var sampler = new PerlinSampler2D(Math.ceil(this.width / localPeriod), Math.ceil(this.height / localPeriod), randseed);

for (var j = 0; j < this.height; ++j)
{
for (var i = 0; i < this.width; ++i)
{
var val = sampler.getValue(i / localPeriod, j / localPeriod);
layers[i + j * this.width] += val * atten;
}
}
localPeriod /= 2;
atten /= 2;
}

for (var j = 0; j < this.height; ++j)
{
for (var i = 0; i < this.width; ++i)
{
var b = (layers[i + j * this.width] + 1) / 2 * 256;

this.setRgba(i, j,
b, b, b, 0xff);
}
}
};

this.toImageContext = function(ctx)
{
var imgData = ctx.createImageData(this.width, this.height);

for (var i = 0, len = width * height * 4; i < len; i++)
{
imgData.data[i] = this.data[i];
}

return imgData;
};
}

• Welcome to Code Review! That's an interesting question, I hope you get some good reviews! – Phrancis Jan 29 '15 at 17:26
• "The end result is kind of slow" – how have you measured this? – Pavlo Jan 29 '15 at 20:12
• I have run the program using Firefox and noticed that it takes a little less than a second to redraw the screen after I initiate a redraw event. – kitfox Jan 29 '15 at 21:32