initial commit
This commit is contained in:
commit
1f7afd9fc4
10
README.md
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
||||
The Mandelbrot Set's Fractal Shape
|
||||
==================================
|
||||
|
||||
I was drifting to sleep when it just hit me, as it always does: "how are fractal shapes drawn anyway"?
|
||||
So I just looked it up and found out the wikipedia page on [The Mandelbrot set](https://en.wikipedia.org/wiki/Mandelbrot_set)
|
||||
|
||||
Here is a simple working rendering of the Mandelbrot set using HTML5 Canvas.
|
||||
I wanted to write the recursion as well (zooming in indefinitely), but was not in the mood really, maybe sometime later.
|
||||
|
||||
[GIF](mandelbrot.gif)
|
67
conversion.js
Normal file
67
conversion.js
Normal file
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Converts an HSL color value to RGB. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes h, s, and l are contained in the set [0, 1] and
|
||||
* returns r, g, and b in the set [0, 255].
|
||||
*
|
||||
* @param {number} h The hue
|
||||
* @param {number} s The saturation
|
||||
* @param {number} l The lightness
|
||||
* @return {Array} The RGB representation
|
||||
*/
|
||||
function hslToRgb(h, s, l){
|
||||
var r, g, b;
|
||||
|
||||
if(s == 0){
|
||||
r = g = b = l; // achromatic
|
||||
}else{
|
||||
var hue2rgb = function hue2rgb(p, q, t){
|
||||
if(t < 0) t += 1;
|
||||
if(t > 1) t -= 1;
|
||||
if(t < 1/6) return p + (q - p) * 6 * t;
|
||||
if(t < 1/2) return q;
|
||||
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
||||
return p;
|
||||
}
|
||||
|
||||
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
var p = 2 * l - q;
|
||||
r = hue2rgb(p, q, h + 1/3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1/3);
|
||||
}
|
||||
|
||||
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an RGB color value to HSL. Conversion formula
|
||||
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
|
||||
* Assumes r, g, and b are contained in the set [0, 255] and
|
||||
* returns h, s, and l in the set [0, 1].
|
||||
*
|
||||
* @param {number} r The red color value
|
||||
* @param {number} g The green color value
|
||||
* @param {number} b The blue color value
|
||||
* @return {Array} The HSL representation
|
||||
*/
|
||||
function rgbToHsl(r, g, b){
|
||||
r /= 255, g /= 255, b /= 255;
|
||||
var max = Math.max(r, g, b), min = Math.min(r, g, b);
|
||||
var h, s, l = (max + min) / 2;
|
||||
|
||||
if(max == min){
|
||||
h = s = 0; // achromatic
|
||||
}else{
|
||||
var d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch(max){
|
||||
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||
case g: h = (b - r) / d + 2; break;
|
||||
case b: h = (r - g) / d + 4; break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return [h, s, l];
|
||||
}
|
23
index.html
Normal file
23
index.html
Normal file
@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Mandelbrot Set</title>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id='board'></canvas>
|
||||
|
||||
<form>
|
||||
<label name='max-iterations'>Maximum Iterations</label>
|
||||
<input id='max-iterations' type='range' min='5' max='500' value='500'>
|
||||
|
||||
<label name='base-hue'>Base Hue</label>
|
||||
<input id='base-hue' type='range' min='0' max='255' value='0'>
|
||||
</form>
|
||||
|
||||
<script src='conversion.js'></script>
|
||||
<script src='params.js'></script>
|
||||
<script src='lib.js'></script>
|
||||
<script src='main.js'></script>
|
||||
</body>
|
||||
</html>
|
72
lib.js
Normal file
72
lib.js
Normal file
@ -0,0 +1,72 @@
|
||||
// The Canvas Board
|
||||
const board = document.getElementById('board');
|
||||
const ctx = board.getContext('2d');
|
||||
board.width = 900;
|
||||
board.height = 500;
|
||||
|
||||
|
||||
// Complex number representation
|
||||
class Complex {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
multiply(other) {
|
||||
return new Complex(this.x * other.x - this.y * other.y,
|
||||
this.x * other.y + this.y * other.x);
|
||||
}
|
||||
|
||||
add(other) {
|
||||
return new Complex(this.x + other.x, this.y + other.y);
|
||||
}
|
||||
|
||||
squaredAbsolute() {
|
||||
return this.x*this.x + this.y*this.y;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Take a number from one range to another
|
||||
const rangeTransform = (v, oldMin, oldMax, newMin, newMax) =>
|
||||
(((v - oldMin) * (newMax - newMin)) / (oldMax - oldMin)) + newMin
|
||||
|
||||
// Get index in ImageData from coordinates
|
||||
const coordinatesToDataIndex = (x, y) => 4 * (y * board.width + x);
|
||||
|
||||
// Put color in ImageData
|
||||
const paintPixel = (image, x, y, color) => {
|
||||
const i = coordinatesToDataIndex(x, y);
|
||||
|
||||
image.data[i] = color.r;
|
||||
image.data[i + 1] = color.g;
|
||||
image.data[i + 2] = color.b;
|
||||
image.data[i + 3] = color.a;
|
||||
}
|
||||
|
||||
class Color {
|
||||
constructor(r, g, b, a) {
|
||||
this.r = r;
|
||||
this.g = g;
|
||||
this.b = b;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
static random() {
|
||||
return new Color(Math.random() * 255, Math.random() * 255, Math.random() * 255, Math.random() * 255);
|
||||
}
|
||||
|
||||
hue(value) {
|
||||
const hsl = rgbToHsl(this.r, this.g, this.b);
|
||||
hsl[0] = value % 1;
|
||||
const rgb = hslToRgb.apply(null, hsl);
|
||||
|
||||
this.r = rgb[0];
|
||||
this.g = rgb[1];
|
||||
this.b = rgb[2];
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new Color(this.r, this.g, this.b, this.a);
|
||||
}
|
||||
}
|
51
main.js
Normal file
51
main.js
Normal file
@ -0,0 +1,51 @@
|
||||
// The mandelbrot set definition
|
||||
const set = (c) =>
|
||||
(z) => z.multiply(z).add(c);
|
||||
|
||||
|
||||
const BASE_COLOR = new Color(60, 60, 200, 255);
|
||||
|
||||
(function render() {
|
||||
requestAnimationFrame(() => {
|
||||
const image = ctx.createImageData(board.width, board.height);
|
||||
|
||||
for (let i = 0; i < board.width; i++) {
|
||||
for (let j = 0; j < board.height; j++) {
|
||||
const scaledX = rangeTransform(i, 0, board.width, -2.5, 1);
|
||||
const scaledY = rangeTransform(j, 0, board.height, -1, 1);
|
||||
|
||||
// constant: x_0 + iy_0
|
||||
const c = new Complex(scaledX, scaledY);
|
||||
|
||||
// The mandelbrot set, with the constant value fixed
|
||||
const f = set(c);
|
||||
let value = new Complex(0, 0);
|
||||
|
||||
let step = 0;
|
||||
while (value.squaredAbsolute() < 4 && step < maxIterations) {
|
||||
value = f(value);
|
||||
step++;
|
||||
}
|
||||
|
||||
let color = BASE_COLOR.clone();
|
||||
color.hue((baseHue / 255) + step*step / maxIterations);
|
||||
|
||||
paintPixel(image, i, j, color);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.putImageData(image, 0, 0);
|
||||
|
||||
render();
|
||||
});
|
||||
}());
|
||||
|
||||
maxIterations = 0;
|
||||
const mxi = setInterval(() => {
|
||||
maxIterations = Math.min(maxIterations + 10, 300);
|
||||
document.getElementById('max-iterations').value = maxIterations;
|
||||
|
||||
if (maxIterations === 300) {
|
||||
clearInterval(mxi);
|
||||
}
|
||||
}, 200);
|
BIN
mandelbrot.gif
Normal file
BIN
mandelbrot.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 MiB |
9
params.js
Normal file
9
params.js
Normal file
@ -0,0 +1,9 @@
|
||||
let maxIterations = parseInt(document.getElementById('max-iterations').value, 10);
|
||||
let baseHue = parseInt(document.getElementById('base-hue').value, 10);
|
||||
|
||||
document.getElementById('max-iterations').addEventListener('input', (e) => {
|
||||
maxIterations = parseInt(e.target.value, 10);
|
||||
})
|
||||
document.getElementById('base-hue').addEventListener('input', (e) => {
|
||||
baseHue = parseInt(e.target.value, 10);
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user