Created
July 21, 2009 07:34
-
-
Save fedelebron/151184 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>Mandelbrot in JS + Canvas</title> | |
<meta charset="UTF-8"> | |
<style type="text/css"> | |
#centered { | |
margin-left: 35%; | |
margin-top: 10%; | |
width: 430px; | |
} | |
#canvas { | |
cursor: crosshair; | |
} | |
#cover { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
top: 0px; | |
left: 0px; | |
opacity: 0.5; | |
background-color: #000000; | |
z-index: 2; | |
cursor: wait; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="centered"> | |
<canvas id="canvas" width="400" height="300"></canvas> | |
</div> | |
<div id="cover"> </div> | |
<script> | |
var MandelbrotMaker = function(canvas, displacement, zoom) { | |
this.canvas = canvas === undefined ? document.getElementById('canvas') : canvas; | |
if(this.canvas === null) { | |
throw "Canvas not found."; | |
} | |
this.displacement = displacement === undefined ? [0, 0] : displacement; // 0 <= x, y <= 1 (try [-0.713, 0.278]) | |
this.stretch = zoom === undefined ? [1,1] : [zoom, zoom]; // 0 < x, y (try [1500, 1500]) | |
this.iterations = 100; // 0 < X | |
this.convergence_radius = 4; | |
this.escape_threshold = 20; | |
this.pallete_size = 2<<8; | |
this.starting_color = [157,21,21]; // leave null for random bicolor gradient | |
this.ending_color = [8,192,68]; | |
this.init(); | |
}; | |
MandelbrotMaker.prototype.init = function() { | |
this.convergence_radius_squared = this.convergence_radius*this.convergence_radius; | |
this.ctx = this.canvas.getContext('2d'); | |
this.width = this.canvas.width; | |
this.height = this.canvas.height; | |
// this serves as a buffer, we don't want to be repainting | |
// on each pixel calculation | |
this.canvasData = this.ctx.createImageData(this.width, this.height); | |
}; | |
MandelbrotMaker.prototype.escapeTime = function(c, d) { | |
var a = 0, b = 0, ta /* temp a*/, aa, bb, modulus; | |
for(var i = 0; i < this.iterations; i++) { | |
aa = a*a; | |
bb = b*b; | |
ta = aa - bb + c; | |
b = 2*a*b + d; | |
a = ta; | |
modulus = aa+bb; | |
if(modulus > this.convergence_radius_squared) { | |
return [i, modulus]; // point exploded after i iterations | |
} | |
} | |
return [Infinity, modulus]; // point never explodes; it's a member of the set | |
}; | |
MandelbrotMaker.prototype.generatePainter = function() { | |
var mrand = function(a, b) { | |
return (Math.random() * b + a) % b; | |
}; | |
var table = []; | |
if(this.starting_color === null && this.ending_color === null) { | |
var ll = [mrand(0, 255), mrand(0, 255), mrand(0,255)]; // pick two random colors | |
var ul = [mrand(0, 255), mrand(0, 255), mrand(0,255)]; // to start and end the degrade | |
} else { | |
var ll = this.starting_color; | |
var ul = this.ending_color; | |
} | |
// how much should each component increase with each iteration? | |
var increases = [(ul[0] - ll[0])/this.pallete_size, (ul[1] - ll[1])/this.pallete_size, (ul[2] - ll[2])/this.pallete_size]; | |
for(var i = 0; i < this.pallete_size; i++) { | |
table[i] = [Math.floor(ll[0] + i*increases[0]), Math.floor(ll[1] + i*increases[1]), Math.floor(ll[2]+i*increases[2])]; | |
} | |
var lnP = Math.log(2); | |
var tlnB = 2*Math.log(this.escape_threshold); | |
var getColorId = function(iterations, value) { | |
return iterations + Math.log(tlnB - Math.log(Math.log(value)))/lnP; | |
}; | |
var that = this; // hack: we lose "this" when we create the generator function; | |
// "that" now refers to the MandelbrotMaker object | |
return function(iterations, value) { | |
var index = Math.floor((getColorId(iterations, value))*(that.pallete_size/100))-2; | |
if(index < 0) { // gorgeous, -1%4 = -1, to JS. son of a... | |
index = 0; | |
} | |
if(table[index] === undefined) { | |
return [0,0,0]; | |
} | |
return table[index]; | |
}; | |
}; | |
MandelbrotMaker.prototype.setColor = function(pixel, color) { | |
this.canvasData.data[pixel+0] = color[0]; | |
this.canvasData.data[pixel+1] = color[1]; | |
this.canvasData.data[pixel+2] = color[2]; | |
this.canvasData.data[pixel+3] = 255; // RGBA, we want full opacity | |
}; | |
MandelbrotMaker.prototype.coverPage = function(callback) { | |
document.getElementById('cover').style.display = ''; | |
setTimeout(callback, 1); | |
}; | |
MandelbrotMaker.prototype.uncoverPage = function() { | |
document.getElementById('cover').style.display = 'none'; | |
document.getElementById('canvas').style.cursor = 'crosshair'; | |
}; | |
MandelbrotMaker.prototype.getDisplacement = function() { | |
return this.displacement; | |
}; | |
MandelbrotMaker.prototype.getZoom = function() { | |
return this.stretch[0]; | |
}; | |
MandelbrotMaker.prototype.paint = function() { | |
var that = this; | |
this.coverPage(function() { | |
var painter = that.generatePainter(); | |
var time, value, temp, color, pixel; | |
var black = [0,0,0]; | |
for(var x_0 = -(that.width/2); x_0 < that.width/2; x_0++) { | |
for(var y_0 = -(that.height/2); y_0 < that.height/2; y_0++) { | |
// time it takes for the function to escape the origin | |
temp = that.escapeTime(x_0/(100*that.stretch[0]) + that.displacement[0], y_0/(100*that.stretch[1]) + that.displacement[1]); | |
time = temp[0]; | |
value = temp[1]; | |
if(time === Infinity) { // point never escaped, it's a member of the set | |
color = black; | |
} else { | |
color = painter(time, value); // point exploded after time iterations, and its modulus was value | |
} | |
// translate the pixel's location to a point within the canvas | |
pixel = (that.width/2+x_0 + (that.height/2+y_0) * that.width) * 4; | |
that.setColor(pixel, color); | |
} | |
} | |
// put buffer back on canvas | |
that.ctx.putImageData(that.canvasData, 0, 0); | |
that.uncoverPage(); | |
}); | |
}; | |
document.addEventListener('DOMContentLoaded', function() { | |
var canvas = document.getElementById('canvas'); | |
var maker = new MandelbrotMaker(canvas, [0, 0], 1); | |
maker.paint(); | |
canvas.addEventListener('click', (function(maker){ | |
var m = maker; | |
return function(e) { | |
/*e.pageX, e.pageY give us the click coords relative to the page */ | |
var rect = document.getElementById('canvas').getBoundingClientRect(); | |
var coords = [e.pageX - rect.left, e.pageY - rect.top]; | |
var displacement = [(coords[0]-canvas.width/2)/rect.width, (coords[1]-canvas.height/2)/rect.height]; | |
var currentZoom = m.getZoom(); | |
var currentDisplacement = m.getDisplacement(); | |
m = new MandelbrotMaker(canvas, [currentDisplacement[0] + displacement[0]/(currentZoom/2), currentDisplacement[1] + displacement[1]/(currentZoom/2)], currentZoom*1.75); | |
m.paint(); | |
} | |
})(maker), false); | |
}, true); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment