HueFree - Library for Color Vision Simulations

source

colorMethods.js

/*
HueFree - Color transformations for color blind visions and more.
Copyright (C) 2023, 2024  Piyush Katyal

This file is part of HueFree Library.

HueFree is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

HueFree is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with HueFree.  If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * Converts a CSS color string to an array of RGB values.
 * Supports rgb(x,x,x) and rgba(x,x,x,x) formats. If rgb(x,x,x,x) is provided then input is percieved as rgba type.
 * Similarly if rgba(x,x,x) is provided then input is percieved as rgb type.
 * 
 * @param {string} color - The CSS color string to convert, e.g., 'rgb(255, 23, 45)'.
 * @returns {number[]|undefined} - An array containing the RGB components [red, green, blue],
 *                                 or undefined if the input string is invalid, e.g., [255, 23, 45].
 */

function stringToRgb(color) {
    let rgbStart;
    let rgbRegex = /rgba?\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)(?:\s*,\s*(-?\d+(?:\.\d+)?))?\s*\)/;
    let startIndex = color.search(rgbRegex);
    if (startIndex == -1)
        return;
    if (color[startIndex + 3] == 'a')
        rgbStart = startIndex + 5;
    else if (color[startIndex + 3] == '(')
        rgbStart = startIndex + 4;
    else
        return;
    let sRGB = color.substring(rgbStart, color.length - 1);
    sRGB = sRGB.split(/\s*,\s*/);
    for (let i = 0; i < sRGB.length; ++i)
        sRGB[i] = parseFloat(sRGB[i]);
    return sRGB;
}

/**
 * Converts an array of RGB or RGBA values to a CSS color string.
 * If invalid format is provided then the array is returned without any modifications.
 * 
 * @param {number[]} color - An array containing the RGB components [red, green, blue] or [red, green, blue, alpha].
 * @returns {string} - The CSS color string in the format 'rgb(r, g, b)' or 'rgba(r, g, b, a)'.
 */

function rgbToString(color) {
    let rgbString = "rgb(";
    if (color.length === 4) {
        rgbString = "rgba(";
        rgbString += color[0] + ", " + color[1] + ", " + color[2] + ", " + color[3] + ")";
    }
    else if(color.length === 3)
        rgbString += color[0] + ", " + color[1] + ", " + color[2] + ")";
    else
        return color;
    return rgbString;
}

/**
 * Linearizes an sRGB color array by applying a gamma correction formula.
 *
 * @param {string|number[]} color - The CSS color string or an array containing the RGB components [red, green, blue, alpha].
 * @returns {number[]|undefined} - An array containing the linearized RGB components [red, green, blue, alpha],
 *                                 or undefined if the input is invalid.
 */

function linearize(color) {
    let sRGB = color;
    if (typeof (sRGB) === "string")
        sRGB = stringToRgb(sRGB);
    if (!sRGB)
        return;
    // Linearize the sRGB color values
    let rgbLinear = sRGB;
    const boundLimit = 0.04045;
    const maxRGBValue = 255;
    const gamma = 2.4;
    const rgbLength = 3;    // Constant length due to rgba format
    for (let i = 0; i < rgbLength; ++i) {
        let v = sRGB[i] / maxRGBValue;
        if (v <= boundLimit) {
            const fact = 12.92;
            rgbLinear[i] = v / fact;
        }
        else {
            const fact = 0.055;
            rgbLinear[i] = Math.pow(((v + fact) / (1 + fact)), gamma);
        }
    }
    return rgbLinear;
}

/**
 * De-linearizes an RGB color array from linear to gamma-corrected sRGB values.
 *
 * @param {string|number[]} color - The CSS color string or an array containing the linearized RGB components [red, green, blue, alpha].
 * @returns {number[]|undefined} - An array containing the de-linearized sRGB components [red, green, blue, alpha],
 *                                 or undefined if the input is invalid.
 */

function deLinearize(color) {
    let rgbLinear = color;
    if (typeof (rgbLinear) === "string")
        rgbLinear = stringToRgb(rgbLinear);
    if (!rgbLinear)
        return;
    // De-Linearize the color to sRGB
    let sRGB = rgbLinear;
    const gamma = 2.4;
    const boundLimit = 0.0031308;
    const maxRGBValue = 255;
    const rgbLength = 3;        // Constant length due to rgba format
    for (let i = 0; i < rgbLength; ++i) {
        let v = rgbLinear[i];
        if (v <= boundLimit) {
            const fact = 12.92;
            sRGB[i] = maxRGBValue * fact * v;
        }
        else {
            const fact = 0.055;
            sRGB[i] = maxRGBValue * ((1 + fact) * (Math.pow(v, 1 / gamma)) - fact);
        }
    }
    return sRGB;
}

/**
 * Applies a color transformation matrix to an RGB color array.
 *
 * @param {number[][]} transMat - The transformation matrix, a 3x3 array of numbers.
 * @param {number[]} rgbMat - An array containing the RGB components [red, green, blue, alpha].
 * @returns {number[]} An array containing the transformed RGB components.
 */

function colorTransform(transMat, rgbMat) {
    let transColor = [...rgbMat];
    for (let i = 0; i < transMat.length; ++i) {
        let color = transMat[i][0] * rgbMat[0] + transMat[i][1] * rgbMat[1] + transMat[i][2] * rgbMat[2];
        transColor[i] = color;
    }
    return transColor;
}

module.exports = {
    stringToRgb,
    rgbToString,
    linearize,
    deLinearize,
    colorTransform
};