Last active
October 13, 2016 20:42
-
-
Save amoilanen/59290f4048b03c7d01b88526fecf8afb to your computer and use it in GitHub Desktop.
Mandelbrot set visualization using the escape algorithm
This file contains hidden or 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
<!-- Hosted https://output.jsbin.com/gowiruwiku/2 --> | |
<html> | |
<header> | |
<style type="text/css"> | |
body { | |
margin: 0; | |
padding: 0; | |
background-color: black; | |
} | |
</style> | |
<script type="text/javascript"> | |
(function(host) { | |
var FULLY_OPAQUE_ALHPA = 255; | |
var FONT_SIZE_PX = 18; | |
function Display(canvas, width, height) { | |
this.canvas = canvas; | |
this.width = width; | |
this.height = height; | |
this.context = null; | |
this.imageData = null; | |
} | |
Display.prototype.initialize = function() { | |
this.canvas.setAttribute('width', window.innerWidth); | |
this.canvas.setAttribute('height', window.innerHeight); | |
this.context = this.canvas.getContext('2d'); | |
this.context.font = FONT_SIZE_PX + 'px Arial'; | |
this.imageData = this.context.getImageData(0, 0, this.width, this.height); | |
}; | |
Display.prototype.drawPixel = function(x, y, color) { | |
var index = (x + y * this.width) * 4; | |
this.imageData.data[index + 0] = color; | |
this.imageData.data[index + 1] = color; | |
this.imageData.data[index + 2] = color; | |
this.imageData.data[index + 3] = FULLY_OPAQUE_ALHPA; | |
} | |
Display.prototype.showComputationBorder = function(y) { | |
this.context.fillRect(0, y, this.width, 1); | |
} | |
Display.prototype.repaint = function() { | |
this.context.putImageData(this.imageData, 0, 0); | |
} | |
Display.prototype.showProgress = function(progress, pixelsPerSecond) { | |
this.context.fillStyle = 'green'; | |
var progressInfo = progress.toFixed(2) + '%'; | |
var speedInfo = 'Speed ' + pixelsPerSecond + 'K pixels/second'; | |
this.context.fillText("Drawing Mandelbrot set... " + progressInfo + ' ' + speedInfo, 20, 20); | |
} | |
host.Display = Display; | |
})(this); | |
(function(host) { | |
var MAX_COLOR = 255; | |
var MAX_VALUE = 4.0; | |
var MAX_ITERATIONS = 30; | |
var COLOR_SCALE = Math.floor(MAX_COLOR / MAX_ITERATIONS); | |
function getEscapeIterationsNumber(pointX, pointY) { | |
var currentIteration = 0; | |
var x = 0; | |
var y = 0; | |
while ((currentIteration < MAX_ITERATIONS) && (x * x + y * y < MAX_VALUE)) { | |
const xOfSquare = x * x - y * y; | |
const yOfSquare = 2 * x * y; | |
x = xOfSquare + pointX; | |
y = yOfSquare + pointY; | |
currentIteration++; | |
} | |
return currentIteration; | |
} | |
function getColorForIteration(iterationNumber) { | |
return Math.min(iterationNumber * COLOR_SCALE, MAX_COLOR); | |
} | |
host.EscapeAlgorithm = { | |
getColor: function(x, y) { | |
return getColorForIteration(getEscapeIterationsNumber(x, y)); | |
} | |
}; | |
})(this); | |
(function(host) { | |
/* | |
* Optimization: canvas will be repainted only after each UPDATE_CANVAS_STEP lines are computed | |
* because repainting is quite slow. | |
*/ | |
const UPDATE_CANVAS_STEP = 50; | |
const SCALED_SIZE = 4; | |
function MandelbrotSetVisualization(display, width, height) { | |
this.display = display; | |
this.width = width; | |
this.height = height; | |
this.size = Math.min(width, height); | |
this.totalProgressPercent = 0; | |
this.progressPerLinePercent = (1 / height) * 100; | |
} | |
MandelbrotSetVisualization.prototype.computeAndDraw = function() { | |
var self = this; | |
this.startTime = new Date().getTime(); | |
var width = this.width; | |
var height = this.height; | |
this.totalProgress = 0; | |
var promises = []; | |
for (var y = 0; y < height; y++) { | |
promises.push(new Promise(function(resolve, reject) { | |
setTimeout(function(y) { | |
for (var x = 0; x < width; x++) { | |
const color = EscapeAlgorithm.getColor(self.scaleX(x), self.scaleY(y)); | |
self.display.drawPixel(x, y, color); | |
} | |
self.totalProgressPercent += self.progressPerLinePercent; | |
self.drawCurrentState(y); | |
resolve(); | |
}, 0, y); | |
})); | |
} | |
Promise.all(promises).then(function() { | |
self.display.repaint(); | |
}); | |
} | |
MandelbrotSetVisualization.prototype.drawCurrentState = function(y) { | |
if (y % UPDATE_CANVAS_STEP === 0) { | |
this.display.repaint(); | |
const elapsedTime = new Date().getTime() - this.startTime; | |
const thousandPixelsPerSecond = Math.floor((((y + 1) * this.width) / elapsedTime)); | |
this.display.showComputationBorder(y); | |
this.display.showProgress(this.totalProgressPercent, thousandPixelsPerSecond); | |
} | |
}; | |
MandelbrotSetVisualization.prototype.scaleX = function(x) { | |
return (SCALED_SIZE * x / this.size) - (SCALED_SIZE * this.width) / (2 * this.size); | |
} | |
MandelbrotSetVisualization.prototype.scaleY = function(y) { | |
return (SCALED_SIZE * y / this.size) - (SCALED_SIZE * this.height) / (2 * this.size); | |
} | |
host.MandelbrotSetVisualization = MandelbrotSetVisualization; | |
})(this); | |
function main() { | |
var width = window.innerWidth; | |
var height = window.innerHeight; | |
var canvas = document.querySelector('canvas'); | |
var display = new Display(canvas, width, height); | |
var setOfMandelbrot = new MandelbrotSetVisualization(display, width, height); | |
display.initialize(); | |
setOfMandelbrot.computeAndDraw(); | |
} | |
window.addEventListener('load', main); | |
</script> | |
<title> | |
Mandelbrot Set Visualization | |
</title> | |
</header> | |
<body> | |
<canvas></canvas> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment