commit 1f7afd9fc4a5ace19a4fdac7357533baa97646eb Author: Mahdi Dibaiee Date: Sat Jul 14 19:48:43 2018 +0430 initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..010adee --- /dev/null +++ b/README.md @@ -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) diff --git a/conversion.js b/conversion.js new file mode 100644 index 0000000..018f786 --- /dev/null +++ b/conversion.js @@ -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]; +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..817ebdb --- /dev/null +++ b/index.html @@ -0,0 +1,23 @@ + + + + + Mandelbrot Set + + + + +
+ + + + + +
+ + + + + + + diff --git a/lib.js b/lib.js new file mode 100644 index 0000000..3d25dd0 --- /dev/null +++ b/lib.js @@ -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); + } +} diff --git a/main.js b/main.js new file mode 100644 index 0000000..840cc86 --- /dev/null +++ b/main.js @@ -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); diff --git a/mandelbrot.gif b/mandelbrot.gif new file mode 100644 index 0000000..02e51ec Binary files /dev/null and b/mandelbrot.gif differ diff --git a/params.js b/params.js new file mode 100644 index 0000000..cbdf0bf --- /dev/null +++ b/params.js @@ -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); +})