colorjs.io
Version:
Color space agnostic color manipulation library
201 lines (169 loc) • 4.57 kB
JavaScript
import Color from "../../color.js";
const supportsP3 = self.CSS && CSS.supports("color", "color(display-p3 0 1 0)");
function alpha_to_string(a = 100) {
return (a < 100? ` / ${a}%` : "");
}
function LCH_to_r2020_string(l, c, h, a = 100) {
return "color(rec2020 " + LCH_to_r2020([+l, +c, +h]).map(x => {
x = Math.round(x * 10000)/10000;
return x;
}).join(" ") + alpha_to_string(a) + ")";
}
function LCH_to_P3_string(l, c, h, a = 100, forceInGamut = false) {
if (forceInGamut) {
[l, c, h] = force_into_gamut(l, c, h, isLCH_within_P3);
}
return "color(display-p3 " + LCH_to_P3([+l, +c, +h]).map(x => {
x = Math.round(x * 10000)/10000;
return x;
}).join(" ") + alpha_to_string(a) + ")";
}
function LCH_to_sRGB_string(l, c, h, a = 100, forceInGamut = false) {
if (forceInGamut) {
[l, c, h] = force_into_gamut(l, c, h, isLCH_within_sRGB);
}
return "rgb(" + LCH_to_sRGB([+l, +c, +h]).map(x => {
return Math.round(x * 10000)/100 + "%";
}).join(" ") + alpha_to_string(a) + ")";
}
function force_into_gamut(l, c, h, isLCH_within) {
// Moves an lch color into the sRGB gamut
// by holding the l and h steady,
// and adjusting the c via binary-search
// until the color is on the sRGB boundary.
if (isLCH_within(l, c, h)) {
return [l, c, h];
}
let hiC = c;
let loC = 0;
const ε = .0001;
c /= 2;
// .0001 chosen fairly arbitrarily as "close enough"
while (hiC - loC > ε) {
if (isLCH_within(l, c, h)) {
loC = c;
}
else {
hiC = c;
}
c = (hiC + loC)/2;
}
return [l, c, h];
}
function isLCH_within_sRGB(l, c, h) {
var rgb = LCH_to_sRGB([+l, +c, +h]);
const ε = .000005;
return rgb.reduce((a, b) => a && b >= (0 - ε) && b <= (1 + ε), true);
}
function isLCH_within_P3(l, c, h) {
var rgb = LCH_to_P3([+l, +c, +h]);
const ε = .000005;
return rgb.reduce((a, b) => a && b >= (0 - ε) && b <= (1 + ε), true);
}
function isLCH_within_r2020(l, c, h) {
var rgb = LCH_to_r2020([+l, +c, +h]);
const ε = .000005;
return rgb.reduce((a, b) => a && b >= (0 - ε) && b <= (1 + ε), true);
}
// Generate gradient stops for the sliders
// (we need to use more to emulate proper interpolation)
// function slider_stops(range, l, c, h, a, index) {
// return range.map(x => {
// args = [l, c, h, a, true];
// args[index] = x;
// var LCH_to_string = supportsP3? LCH_to_P3_string : LCH_to_sRGB_string;
// return LCH_to_string(...args);
// }).join(", ");
// }
function slider_stops(color, component, range) {
let min = color.clone();
let max = color.clone();
min[component] = range[0];
max[component] = range[1];
return Color.steps(min, max, {steps: 10 }).map(c => c.toString({fallback: true}));
}
function CSS_color_to_LCH(str) {
str = str || prompt("Enter any sRGB color format your browser recognizes, or a color(display-p3) color");
if (!str) {
return;
}
const prefixP3 = "color(display-p3 ";
if (str.trim().indexOf(prefixP3) === 0) {
var params = str.slice(prefixP3.length).match(/-?[\d.]+/g).map(x => +x);
console.log(params);
var lch = P3_to_LCH(params.slice(0, 3));
}
else {
// Assume RGBA for now, normalize via computed style
var dummy = document.createElement("_");
document.body.appendChild(dummy);
dummy.style.color = str;
var computedStr = getComputedStyle(dummy).color;
var params = computedStr.match(/-?[\d.]+/g).map(x => +x);
params = params.map((x, i) => i < 3? x/255 : x);
var lch = sRGB_to_LCH(params.slice(0, 3));
}
return {
lightness: lch[0],
chroma: lch[1],
hue: lch[2],
alpha: (params[3] || 1) * 100
};
}
// Produce a default (not very good) name
function LCH_name(l, c, h) {
h = h % 360;
var ret = [];
var baseColor;
if (l < 35) {
ret.push("Dark");
}
else if (l > 70) {
ret.push("Light");
}
if (c > 10) {
if (c < 35) {
ret.push("Muted");
}
else if (c > 70) {
if (l > 60 ) {
ret.push("Bright");
}
}
// Chromatic
for (let [hue, baseColor] of Object.entries({
20: "Pink",
40: "Red",
60: "Orange",
100: "Yellow",
150: "Green",
210: "Cyan",
260: "Blue",
320: "Purple",
360: "Pink"
})) {
if (h <= hue) {
ret.push(baseColor);
break;
}
}
}
else {
if (c > 1) {
ret.unshift(h < 120 || h > 300? "Warm": "Cool");
}
ret.push("Gray");
}
ret = ret.join(" ");
if (/Yellow$/.test(ret) && l < 40) {
// Dark Yellow is an oxymoron
ret = "Brown";
}
return ret;
}
// Select text in readonly input fields when you focus them
document.addEventListener("click", evt => {
if (evt.target.matches("input[readonly]")) {
evt.target.select();
}
});