postcss-color-hct
Version:
PostCSS plugin to transform hct() function to more compatible CSS (rgb() or rgba()).
627 lines (626 loc) • 21.8 kB
JavaScript
import * as postcss from "postcss";
import * as reduceFunctionCall from "reduce-function-call";
/**
* @license
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function signum(num) {
if (num < 0) {
return -1;
} else if (num === 0) {
return 0;
} else {
return 1;
}
}
function lerp(start, stop, amount) {
return (1 - amount) * start + amount * stop;
}
function clampInt(min, max, input) {
if (input < min) {
return min;
} else if (input > max) {
return max;
}
return input;
}
function clampDouble(min, max, input) {
if (input < min) {
return min;
} else if (input > max) {
return max;
}
return input;
}
function sanitizeDegreesDouble(degrees) {
degrees = degrees % 360;
if (degrees < 0) {
degrees = degrees + 360;
}
return degrees;
}
function matrixMultiply(row, matrix) {
const a = row[0] * matrix[0][0] + row[1] * matrix[0][1] + row[2] * matrix[0][2];
const b = row[0] * matrix[1][0] + row[1] * matrix[1][1] + row[2] * matrix[1][2];
const c = row[0] * matrix[2][0] + row[1] * matrix[2][1] + row[2] * matrix[2][2];
return [a, b, c];
}
/**
* @license
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const SRGB_TO_XYZ = [
[0.41233895, 0.35762064, 0.18051042],
[0.2126, 0.7152, 0.0722],
[0.01932141, 0.11916382, 0.95034478]
];
const XYZ_TO_SRGB = [
[
3.2413774792388685,
-1.5376652402851851,
-0.49885366846268053
],
[
-0.9691452513005321,
1.8758853451067872,
0.04156585616912061
],
[
0.05562093689691305,
-0.20395524564742123,
1.0571799111220335
]
];
const WHITE_POINT_D65 = [95.047, 100, 108.883];
function argbFromRgb(red, green, blue) {
return (255 << 24 | (red & 255) << 16 | (green & 255) << 8 | blue & 255) >>> 0;
}
function redFromArgb(argb) {
return argb >> 16 & 255;
}
function greenFromArgb(argb) {
return argb >> 8 & 255;
}
function blueFromArgb(argb) {
return argb & 255;
}
function argbFromXyz(x, y, z) {
const matrix = XYZ_TO_SRGB;
const linearR = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2] * z;
const linearG = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2] * z;
const linearB = matrix[2][0] * x + matrix[2][1] * y + matrix[2][2] * z;
const r = delinearized(linearR);
const g = delinearized(linearG);
const b = delinearized(linearB);
return argbFromRgb(r, g, b);
}
function xyzFromArgb(argb) {
const r = linearized(redFromArgb(argb));
const g = linearized(greenFromArgb(argb));
const b = linearized(blueFromArgb(argb));
return matrixMultiply([r, g, b], SRGB_TO_XYZ);
}
function argbFromLstar(lstar) {
const fy = (lstar + 16) / 116;
const fz = fy;
const fx = fy;
const kappa = 24389 / 27;
const epsilon = 216 / 24389;
const lExceedsEpsilonKappa = lstar > 8;
const y = lExceedsEpsilonKappa ? fy * fy * fy : lstar / kappa;
const cubeExceedEpsilon = fy * fy * fy > epsilon;
const x = cubeExceedEpsilon ? fx * fx * fx : lstar / kappa;
const z = cubeExceedEpsilon ? fz * fz * fz : lstar / kappa;
const whitePoint = WHITE_POINT_D65;
return argbFromXyz(x * whitePoint[0], y * whitePoint[1], z * whitePoint[2]);
}
function lstarFromArgb(argb) {
const y = xyzFromArgb(argb)[1] / 100;
const e = 216 / 24389;
if (y <= e) {
return 24389 / 27 * y;
} else {
const yIntermediate = Math.pow(y, 1 / 3);
return 116 * yIntermediate - 16;
}
}
function yFromLstar(lstar) {
const ke = 8;
if (lstar > ke) {
return Math.pow((lstar + 16) / 116, 3) * 100;
} else {
return lstar / (24389 / 27) * 100;
}
}
function linearized(rgbComponent) {
const normalized = rgbComponent / 255;
if (normalized <= 0.040449936) {
return normalized / 12.92 * 100;
} else {
return Math.pow((normalized + 0.055) / 1.055, 2.4) * 100;
}
}
function delinearized(rgbComponent) {
const normalized = rgbComponent / 100;
let delinearized2 = 0;
if (normalized <= 31308e-7) {
delinearized2 = normalized * 12.92;
} else {
delinearized2 = 1.055 * Math.pow(normalized, 1 / 2.4) - 0.055;
}
return clampInt(0, 255, Math.round(delinearized2 * 255));
}
function whitePointD65() {
return WHITE_POINT_D65;
}
/**
* @license
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class ViewingConditions {
constructor(n, aw, nbb, ncb, c, nc, rgbD, fl, fLRoot, z) {
this.n = n;
this.aw = aw;
this.nbb = nbb;
this.ncb = ncb;
this.c = c;
this.nc = nc;
this.rgbD = rgbD;
this.fl = fl;
this.fLRoot = fLRoot;
this.z = z;
}
static make(whitePoint = whitePointD65(), adaptingLuminance = 200 / Math.PI * yFromLstar(50) / 100, backgroundLstar = 50, surround = 2, discountingIlluminant = false) {
const xyz = whitePoint;
const rW = xyz[0] * 0.401288 + xyz[1] * 0.650173 + xyz[2] * -0.051461;
const gW = xyz[0] * -0.250268 + xyz[1] * 1.204414 + xyz[2] * 0.045854;
const bW = xyz[0] * -2079e-6 + xyz[1] * 0.048952 + xyz[2] * 0.953127;
const f = 0.8 + surround / 10;
const c = f >= 0.9 ? lerp(0.59, 0.69, (f - 0.9) * 10) : lerp(0.525, 0.59, (f - 0.8) * 10);
let d = discountingIlluminant ? 1 : f * (1 - 1 / 3.6 * Math.exp((-adaptingLuminance - 42) / 92));
d = d > 1 ? 1 : d < 0 ? 0 : d;
const nc = f;
const rgbD = [
d * (100 / rW) + 1 - d,
d * (100 / gW) + 1 - d,
d * (100 / bW) + 1 - d
];
const k = 1 / (5 * adaptingLuminance + 1);
const k4 = k * k * k * k;
const k4F = 1 - k4;
const fl = k4 * adaptingLuminance + 0.1 * k4F * k4F * Math.cbrt(5 * adaptingLuminance);
const n = yFromLstar(backgroundLstar) / whitePoint[1];
const z = 1.48 + Math.sqrt(n);
const nbb = 0.725 / Math.pow(n, 0.2);
const ncb = nbb;
const rgbAFactors = [
Math.pow(fl * rgbD[0] * rW / 100, 0.42),
Math.pow(fl * rgbD[1] * gW / 100, 0.42),
Math.pow(fl * rgbD[2] * bW / 100, 0.42)
];
const rgbA = [
400 * rgbAFactors[0] / (rgbAFactors[0] + 27.13),
400 * rgbAFactors[1] / (rgbAFactors[1] + 27.13),
400 * rgbAFactors[2] / (rgbAFactors[2] + 27.13)
];
const aw = (2 * rgbA[0] + rgbA[1] + 0.05 * rgbA[2]) * nbb;
return new ViewingConditions(n, aw, nbb, ncb, c, nc, rgbD, fl, Math.pow(fl, 0.25), z);
}
}
ViewingConditions.DEFAULT = ViewingConditions.make();
/**
* @license
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class Cam16 {
constructor(hue, chroma, j, q, m, s, jstar, astar, bstar) {
this.hue = hue;
this.chroma = chroma;
this.j = j;
this.q = q;
this.m = m;
this.s = s;
this.jstar = jstar;
this.astar = astar;
this.bstar = bstar;
}
distance(other) {
const dJ = this.jstar - other.jstar;
const dA = this.astar - other.astar;
const dB = this.bstar - other.bstar;
const dEPrime = Math.sqrt(dJ * dJ + dA * dA + dB * dB);
const dE = 1.41 * Math.pow(dEPrime, 0.63);
return dE;
}
static fromInt(argb) {
return Cam16.fromIntInViewingConditions(argb, ViewingConditions.DEFAULT);
}
static fromIntInViewingConditions(argb, viewingConditions) {
const red = (argb & 16711680) >> 16;
const green = (argb & 65280) >> 8;
const blue = argb & 255;
const redL = linearized(red);
const greenL = linearized(green);
const blueL = linearized(blue);
const x = 0.41233895 * redL + 0.35762064 * greenL + 0.18051042 * blueL;
const y = 0.2126 * redL + 0.7152 * greenL + 0.0722 * blueL;
const z = 0.01932141 * redL + 0.11916382 * greenL + 0.95034478 * blueL;
const rC = 0.401288 * x + 0.650173 * y - 0.051461 * z;
const gC = -0.250268 * x + 1.204414 * y + 0.045854 * z;
const bC = -2079e-6 * x + 0.048952 * y + 0.953127 * z;
const rD = viewingConditions.rgbD[0] * rC;
const gD = viewingConditions.rgbD[1] * gC;
const bD = viewingConditions.rgbD[2] * bC;
const rAF = Math.pow(viewingConditions.fl * Math.abs(rD) / 100, 0.42);
const gAF = Math.pow(viewingConditions.fl * Math.abs(gD) / 100, 0.42);
const bAF = Math.pow(viewingConditions.fl * Math.abs(bD) / 100, 0.42);
const rA = signum(rD) * 400 * rAF / (rAF + 27.13);
const gA = signum(gD) * 400 * gAF / (gAF + 27.13);
const bA = signum(bD) * 400 * bAF / (bAF + 27.13);
const a = (11 * rA + -12 * gA + bA) / 11;
const b = (rA + gA - 2 * bA) / 9;
const u = (20 * rA + 20 * gA + 21 * bA) / 20;
const p2 = (40 * rA + 20 * gA + bA) / 20;
const atan2 = Math.atan2(b, a);
const atanDegrees = atan2 * 180 / Math.PI;
const hue = atanDegrees < 0 ? atanDegrees + 360 : atanDegrees >= 360 ? atanDegrees - 360 : atanDegrees;
const hueRadians = hue * Math.PI / 180;
const ac = p2 * viewingConditions.nbb;
const j = 100 * Math.pow(ac / viewingConditions.aw, viewingConditions.c * viewingConditions.z);
const q = 4 / viewingConditions.c * Math.sqrt(j / 100) * (viewingConditions.aw + 4) * viewingConditions.fLRoot;
const huePrime = hue < 20.14 ? hue + 360 : hue;
const eHue = 0.25 * (Math.cos(huePrime * Math.PI / 180 + 2) + 3.8);
const p1 = 5e4 / 13 * eHue * viewingConditions.nc * viewingConditions.ncb;
const t = p1 * Math.sqrt(a * a + b * b) / (u + 0.305);
const alpha = Math.pow(t, 0.9) * Math.pow(1.64 - Math.pow(0.29, viewingConditions.n), 0.73);
const c = alpha * Math.sqrt(j / 100);
const m = c * viewingConditions.fLRoot;
const s = 50 * Math.sqrt(alpha * viewingConditions.c / (viewingConditions.aw + 4));
const jstar = (1 + 100 * 7e-3) * j / (1 + 7e-3 * j);
const mstar = 1 / 0.0228 * Math.log(1 + 0.0228 * m);
const astar = mstar * Math.cos(hueRadians);
const bstar = mstar * Math.sin(hueRadians);
return new Cam16(hue, c, j, q, m, s, jstar, astar, bstar);
}
static fromJch(j, c, h) {
return Cam16.fromJchInViewingConditions(j, c, h, ViewingConditions.DEFAULT);
}
static fromJchInViewingConditions(j, c, h, viewingConditions) {
const q = 4 / viewingConditions.c * Math.sqrt(j / 100) * (viewingConditions.aw + 4) * viewingConditions.fLRoot;
const m = c * viewingConditions.fLRoot;
const alpha = c / Math.sqrt(j / 100);
const s = 50 * Math.sqrt(alpha * viewingConditions.c / (viewingConditions.aw + 4));
const hueRadians = h * Math.PI / 180;
const jstar = (1 + 100 * 7e-3) * j / (1 + 7e-3 * j);
const mstar = 1 / 0.0228 * Math.log(1 + 0.0228 * m);
const astar = mstar * Math.cos(hueRadians);
const bstar = mstar * Math.sin(hueRadians);
return new Cam16(h, c, j, q, m, s, jstar, astar, bstar);
}
static fromUcs(jstar, astar, bstar) {
return Cam16.fromUcsInViewingConditions(jstar, astar, bstar, ViewingConditions.DEFAULT);
}
static fromUcsInViewingConditions(jstar, astar, bstar, viewingConditions) {
const a = astar;
const b = bstar;
const m = Math.sqrt(a * a + b * b);
const M = (Math.exp(m * 0.0228) - 1) / 0.0228;
const c = M / viewingConditions.fLRoot;
let h = Math.atan2(b, a) * (180 / Math.PI);
if (h < 0) {
h += 360;
}
const j = jstar / (1 - (jstar - 100) * 7e-3);
return Cam16.fromJchInViewingConditions(j, c, h, viewingConditions);
}
toInt() {
return this.viewed(ViewingConditions.DEFAULT);
}
viewed(viewingConditions) {
const alpha = this.chroma === 0 || this.j === 0 ? 0 : this.chroma / Math.sqrt(this.j / 100);
const t = Math.pow(alpha / Math.pow(1.64 - Math.pow(0.29, viewingConditions.n), 0.73), 1 / 0.9);
const hRad = this.hue * Math.PI / 180;
const eHue = 0.25 * (Math.cos(hRad + 2) + 3.8);
const ac = viewingConditions.aw * Math.pow(this.j / 100, 1 / viewingConditions.c / viewingConditions.z);
const p1 = eHue * (5e4 / 13) * viewingConditions.nc * viewingConditions.ncb;
const p2 = ac / viewingConditions.nbb;
const hSin = Math.sin(hRad);
const hCos = Math.cos(hRad);
const gamma = 23 * (p2 + 0.305) * t / (23 * p1 + 11 * t * hCos + 108 * t * hSin);
const a = gamma * hCos;
const b = gamma * hSin;
const rA = (460 * p2 + 451 * a + 288 * b) / 1403;
const gA = (460 * p2 - 891 * a - 261 * b) / 1403;
const bA = (460 * p2 - 220 * a - 6300 * b) / 1403;
const rCBase = Math.max(0, 27.13 * Math.abs(rA) / (400 - Math.abs(rA)));
const rC = signum(rA) * (100 / viewingConditions.fl) * Math.pow(rCBase, 1 / 0.42);
const gCBase = Math.max(0, 27.13 * Math.abs(gA) / (400 - Math.abs(gA)));
const gC = signum(gA) * (100 / viewingConditions.fl) * Math.pow(gCBase, 1 / 0.42);
const bCBase = Math.max(0, 27.13 * Math.abs(bA) / (400 - Math.abs(bA)));
const bC = signum(bA) * (100 / viewingConditions.fl) * Math.pow(bCBase, 1 / 0.42);
const rF = rC / viewingConditions.rgbD[0];
const gF = gC / viewingConditions.rgbD[1];
const bF = bC / viewingConditions.rgbD[2];
const x = 1.86206786 * rF - 1.01125463 * gF + 0.14918677 * bF;
const y = 0.38752654 * rF + 0.62144744 * gF - 897398e-8 * bF;
const z = -0.0158415 * rF - 0.03412294 * gF + 1.04996444 * bF;
const argb = argbFromXyz(x, y, z);
return argb;
}
}
/**
* @license
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class Hct {
constructor(internalHue, internalChroma, internalTone) {
this.internalHue = internalHue;
this.internalChroma = internalChroma;
this.internalTone = internalTone;
this.setInternalState(this.toInt());
}
static from(hue, chroma, tone) {
return new Hct(hue, chroma, tone);
}
static fromInt(argb) {
const cam = Cam16.fromInt(argb);
const tone = lstarFromArgb(argb);
return new Hct(cam.hue, cam.chroma, tone);
}
toInt() {
return getInt(this.internalHue, this.internalChroma, this.internalTone);
}
get hue() {
return this.internalHue;
}
set hue(newHue) {
this.setInternalState(getInt(sanitizeDegreesDouble(newHue), this.internalChroma, this.internalTone));
}
get chroma() {
return this.internalChroma;
}
set chroma(newChroma) {
this.setInternalState(getInt(this.internalHue, newChroma, this.internalTone));
}
get tone() {
return this.internalTone;
}
set tone(newTone) {
this.setInternalState(getInt(this.internalHue, this.internalChroma, newTone));
}
setInternalState(argb) {
const cam = Cam16.fromInt(argb);
const tone = lstarFromArgb(argb);
this.internalHue = cam.hue;
this.internalChroma = cam.chroma;
this.internalTone = tone;
}
}
const CHROMA_SEARCH_ENDPOINT = 0.4;
const DE_MAX = 1;
const DL_MAX = 0.2;
const LIGHTNESS_SEARCH_ENDPOINT = 0.01;
function getInt(hue, chroma, tone) {
return getIntInViewingConditions(sanitizeDegreesDouble(hue), chroma, clampDouble(0, 100, tone), ViewingConditions.DEFAULT);
}
function getIntInViewingConditions(hue, chroma, tone, viewingConditions) {
if (chroma < 1 || Math.round(tone) <= 0 || Math.round(tone) >= 100) {
return argbFromLstar(tone);
}
hue = sanitizeDegreesDouble(hue);
let high = chroma;
let mid = chroma;
let low = 0;
let isFirstLoop = true;
let answer = null;
while (Math.abs(low - high) >= CHROMA_SEARCH_ENDPOINT) {
const possibleAnswer = findCamByJ(hue, mid, tone);
if (isFirstLoop) {
if (possibleAnswer != null) {
return possibleAnswer.viewed(viewingConditions);
} else {
isFirstLoop = false;
mid = low + (high - low) / 2;
continue;
}
}
if (possibleAnswer === null) {
high = mid;
} else {
answer = possibleAnswer;
low = mid;
}
mid = low + (high - low) / 2;
}
if (answer === null) {
return argbFromLstar(tone);
}
return answer.viewed(viewingConditions);
}
function findCamByJ(hue, chroma, tone) {
let low = 0;
let high = 100;
let mid = 0;
let bestdL = 1e3;
let bestdE = 1e3;
let bestCam = null;
while (Math.abs(low - high) > LIGHTNESS_SEARCH_ENDPOINT) {
mid = low + (high - low) / 2;
const camBeforeClip = Cam16.fromJch(mid, chroma, hue);
const clipped = camBeforeClip.toInt();
const clippedLstar = lstarFromArgb(clipped);
const dL = Math.abs(tone - clippedLstar);
if (dL < DL_MAX) {
const camClipped = Cam16.fromInt(clipped);
const dE = camClipped.distance(Cam16.fromJch(camClipped.j, camClipped.chroma, hue));
if (dE <= DE_MAX && dE <= bestdE) {
bestdL = dL;
bestdE = dE;
bestCam = camClipped;
}
}
if (bestdL === 0 && bestdE === 0) {
break;
}
if (clippedLstar < tone) {
low = mid;
} else {
high = mid;
}
}
return bestCam;
}
/**
* @license
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const hexFromArgb = (argb) => {
const r = redFromArgb(argb);
const g = greenFromArgb(argb);
const b = blueFromArgb(argb);
const outParts = [r.toString(16), g.toString(16), b.toString(16)];
for (const [i, part] of outParts.entries()) {
if (part.length === 1) {
outParts[i] = "0" + part;
}
}
return "#" + outParts.join("");
};
function getColor(values) {
if (values === void 0 || values.length === 0) {
throw "HCT values are undefined";
}
if (values.some(isNaN)) {
throw "HCT values are not numbers";
}
const [h, c, t] = values;
if (isNaN(+h) || isNaN(+c) || isNaN(+t)) {
throw 'Unable to parse HCT color: "' + values.join(",") + '"';
}
const hex = hctToHex(h, c, t);
const opacity = values.length > 3 ? values[3] : void 0;
return rgbToString(hexToRgb(hex), opacity);
}
function hctToHex(h, c, t) {
const color = Hct.from(Number(h), Number(c), Number(t));
const hex = hexFromArgb(color.toInt());
return hex;
}
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
const { r, g, b } = {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
};
return [r, g, b];
}
function rgbToString(rgb, opacity) {
const [r, g, b] = rgb;
if (opacity) {
return `rgb(${r} ${g} ${b} / ${opacity})`;
} else {
return `rgb(${r} ${g} ${b})`;
}
}
function justFloat(n) {
return parseFloat(n);
}
function colorValuesDefined(hct) {
return !hct.some(isNaN);
}
function transformDecl(decl) {
const value = decl.value;
function reduceHcl(body) {
const hctaValues = body.split(",").map(justFloat);
if (!colorValuesDefined(hctaValues)) {
throw decl.error('Unable to parse color: "' + value + '"');
}
try {
return getColor(hctaValues);
} catch (e) {
throw decl.error(e);
}
}
decl.value = reduceFunctionCall(value, "hct", reduceHcl);
}
function colorHcl(css) {
css.walkDecls(transformDecl);
}
function colorHclPlugin() {
return colorHcl;
}
module.exports = postcss.plugin("postcss-color-hct", colorHclPlugin);
export { colorHclPlugin };