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); | |