initial commit

This commit is contained in:
Mahdi Dibaiee 2018-07-14 19:48:43 +04:30
commit 1f7afd9fc4
7 changed files with 232 additions and 0 deletions

10
README.md Normal file
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

9
params.js Normal file
View 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);
})