Based on https://gist.github.com/randompast/266276b1001ab6d3f42703ae4a335333
and using the CIE/RGB library from https://github.com/usolved/cie-rgb-converter
| license: mit |
Based on https://gist.github.com/randompast/266276b1001ab6d3f42703ae4a335333
and using the CIE/RGB library from https://github.com/usolved/cie-rgb-converter
| /* | |
| With these functions you can convert the CIE color space to the RGB color space and vice versa. | |
| The developer documentation for Philips Hue provides the formulas used in the code below: | |
| https://developers.meethue.com/documentation/color-conversions-rgb-xy | |
| I've used the formulas and Objective-C example code and transfered it to JavaScript. | |
| Examples: | |
| var rgb = cie_to_rgb(0.6611, 0.2936) | |
| var cie = rgb_to_cie(255, 39, 60) | |
| ------------------------------------------------------------------------------------ | |
| The MIT License (MIT) | |
| Copyright (c) 2017 www.usolved.net | |
| Published under https://github.com/usolved/cie-rgb-converter | |
| Permission is hereby granted, free of charge, to any person obtaining a copy | |
| of this software and associated documentation files (the "Software"), to deal | |
| in the Software without restriction, including without limitation the rights | |
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| copies of the Software, and to permit persons to whom the Software is | |
| furnished to do so, subject to the following conditions: | |
| The above copyright notice and this permission notice shall be included in | |
| all copies or substantial portions of the Software. | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| THE SOFTWARE. | |
| */ | |
| /** | |
| * Converts CIE color space to RGB color space | |
| * @param {Number} x | |
| * @param {Number} y | |
| * @param {Number} brightness - Ranges from 1 to 254 | |
| * @return {Array} Array that contains the color values for red, green and blue | |
| */ | |
| function cie_to_rgb(x, y, brightness) | |
| { | |
| //Set to maximum brightness if no custom value was given (Not the slick ECMAScript 6 way for compatibility reasons) | |
| if (brightness === undefined) { | |
| brightness = 254; | |
| } | |
| var z = 1.0 - x - y; | |
| var Y = (brightness / 254).toFixed(2); | |
| var X = (Y / y) * x; | |
| var Z = (Y / y) * z; | |
| //Convert to RGB using Wide RGB D65 conversion | |
| var red = X * 1.656492 - Y * 0.354851 - Z * 0.255038; | |
| var green = -X * 0.707196 + Y * 1.655397 + Z * 0.036152; | |
| var blue = X * 0.051713 - Y * 0.121364 + Z * 1.011530; | |
| //If red, green or blue is larger than 1.0 set it back to the maximum of 1.0 | |
| if (red > blue && red > green && red > 1.0) { | |
| green = green / red; | |
| blue = blue / red; | |
| red = 1.0; | |
| } | |
| else if (green > blue && green > red && green > 1.0) { | |
| red = red / green; | |
| blue = blue / green; | |
| green = 1.0; | |
| } | |
| else if (blue > red && blue > green && blue > 1.0) { | |
| red = red / blue; | |
| green = green / blue; | |
| blue = 1.0; | |
| } | |
| //Reverse gamma correction | |
| red = red <= 0.0031308 ? 12.92 * red : (1.0 + 0.055) * Math.pow(red, (1.0 / 2.4)) - 0.055; | |
| green = green <= 0.0031308 ? 12.92 * green : (1.0 + 0.055) * Math.pow(green, (1.0 / 2.4)) - 0.055; | |
| blue = blue <= 0.0031308 ? 12.92 * blue : (1.0 + 0.055) * Math.pow(blue, (1.0 / 2.4)) - 0.055; | |
| //Convert normalized decimal to decimal | |
| red = Math.round(red * 255); | |
| green = Math.round(green * 255); | |
| blue = Math.round(blue * 255); | |
| if (isNaN(red)) | |
| red = 0; | |
| if (isNaN(green)) | |
| green = 0; | |
| if (isNaN(blue)) | |
| blue = 0; | |
| return [red, green, blue]; | |
| } | |
| /** | |
| * Converts RGB color space to CIE color space | |
| * @param {Number} red | |
| * @param {Number} green | |
| * @param {Number} blue | |
| * @return {Array} Array that contains the CIE color values for x and y | |
| */ | |
| function rgb_to_cie(red, green, blue) | |
| { | |
| //Apply a gamma correction to the RGB values, which makes the color more vivid and more the like the color displayed on the screen of your device | |
| var red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92); | |
| var green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92); | |
| var blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92); | |
| //RGB values to XYZ using the Wide RGB D65 conversion formula | |
| var X = red * 0.664511 + green * 0.154324 + blue * 0.162028; | |
| var Y = red * 0.283881 + green * 0.668433 + blue * 0.047685; | |
| var Z = red * 0.000088 + green * 0.072310 + blue * 0.986039; | |
| //Calculate the xy values from the XYZ values | |
| var x = (X / (X + Y + Z)).toFixed(4); | |
| var y = (Y / (X + Y + Z)).toFixed(4); | |
| if (isNaN(x)) | |
| x = 0; | |
| if (isNaN(y)) | |
| y = 0; | |
| return [x, y]; | |
| } |
| <!DOCTYPE html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>Mandelbrotch</title> | |
| <style> | |
| canvas { width: 1024px; height: 1024px; } | |
| body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
| </style> | |
| </head> | |
| <body> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.3/dat.gui.min.js"></script> | |
| <script src="cie_rgb_converter.js"></script> | |
| <script src="randompast.js"></script> | |
| </body> |
| //Inspiration via Mathologer: https://www.youtube.com/watch?v=leFep9yt3JY&t=12m53s | |
| //Excitement via Two Minute Papers: https://www.youtube.com/watch?v=HvHZXPd0Bjs&list=PLujxSBD-JXgnqDD1n-V30pKtp6Q886x7e&index=93 | |
| const CANVAS_SIZE=1024 | |
| var canvas = document.body.appendChild(document.createElement('canvas')) | |
| canvas.width = CANVAS_SIZE | |
| canvas.height = CANVAS_SIZE | |
| var ctx = canvas.getContext('2d') | |
| //x^2 + c | |
| var f = function(z, c){ return [ z[0]*z[0] - z[1]*z[1] + c[0], 2*z[0]*z[1] + c[1] ] } | |
| //|z|_2 | |
| var h = function(z){return Math.sqrt(z[0]*z[0] + z[1]*z[1])} | |
| //iteration > 2 ? -> iteration | |
| var g = function(c, n){ | |
| var z = f([0,0], c) | |
| for(var i = 0; i < n; i++){ | |
| z = f(z, c) | |
| if(h(z) > 2) return i | |
| } | |
| return i | |
| } | |
| function clamp(value, begin, end) { | |
| if (value < begin) value = begin; | |
| if (value > end) value = end; | |
| return value; | |
| } | |
| function rescale(value, from_begin, from_end, to_begin, to_end) { | |
| let t = (value - from_begin) / (from_end - from_begin); | |
| return to_begin + (to_end - to_begin) * t; | |
| } | |
| let colorparams = { | |
| radius_center: 1/3, | |
| radius_fade: 10, | |
| bri_scale: 200, | |
| angle_start: 3, | |
| angle_scale: 6, | |
| cie_x: 1/3, | |
| cie_y: 1/3, | |
| } | |
| function color(count, limit){ | |
| let P = colorparams; | |
| let r = P.radius_center / ((P.radius_fade+count)/P.radius_fade); | |
| let bri = count == limit? 0 : Math.min(255, count/limit*P.bri_scale); | |
| let angle = P.angle_start + P.angle_scale * count / limit; | |
| let x = P.cie_x + r * Math.cos(angle), y = P.cie_y + r * Math.sin(angle); | |
| let [R, G, B] = cie_to_rgb(x, y, bri); | |
| R = clamp(R, 0, 255); | |
| G = clamp(G, 0, 255); | |
| B = clamp(B, 0, 255); | |
| return [R, G, B]; | |
| } | |
| function calculate(limit) { | |
| let counts = []; | |
| for (let y = 0; y < CANVAS_SIZE; y++) { | |
| counts[y] = []; | |
| for (let x = 0; x < CANVAS_SIZE; x++) { | |
| let zx = rescale(x, 0, CANVAS_SIZE, -2, +2); | |
| let zy = rescale(y, 0, CANVAS_SIZE, -2, +2); | |
| counts[y][x] = g([zx, zy], limit); | |
| } | |
| } | |
| return counts; | |
| } | |
| function draw(counts, limit) { | |
| console.log("REDRAW"); | |
| const imageData = ctx.getImageData(0, 0, CANVAS_SIZE, CANVAS_SIZE); | |
| const pixels = imageData.data; | |
| let colormap = []; | |
| for (let i = 0; i <= limit; i++) { | |
| colormap[i] = color(i, limit); | |
| } | |
| for (let x = 0; x < CANVAS_SIZE; x++) { | |
| for (let y = 0; y < CANVAS_SIZE; y++) { | |
| let index = 4 * (y * CANVAS_SIZE + x); | |
| let RGB = colormap[counts[y][x]]; | |
| for (let i = 0; i < 3; i++) { | |
| pixels[index+i] = RGB[i]; | |
| } | |
| pixels[index+3] = 255; | |
| } | |
| } | |
| ctx.putImageData(imageData, 0, 0); | |
| } | |
| const LIMIT = 25; | |
| var counts = calculate(LIMIT); | |
| function redraw() { draw(counts, LIMIT); } | |
| redraw(); | |
| var gui = new dat.GUI(); | |
| function w(name, min, max) { gui.add(colorparams, name, min, max).onChange(redraw); } | |
| w('radius_center', 0, 1); | |
| w('radius_fade', 0, 50); | |
| w('bri_scale', 0, 1000); | |
| w('angle_start', 0, 6.14); | |
| w('angle_scale', 0, 30); | |
| w('cie_x', 0, 1); | |
| w('cie_y', 0, 1); | |