Source: color.mjs

import { clamp, randomRange, randomRangeInt } from "./util.mjs";

/**
 * Utility class for manipulating RGBA colors.
 *
 * Color is a simple color class composed of 4 components:
 * - Red
 * - Green
 * - Blue
 * - Alpha (opacity)
 *
 * Each component is a public member, an integer in the range 0 to 255
 */
export class Color {
    /** The red value of the color */
    r;
    /** The green value of the color */
    g;
    /** The blue value of the color */
    b;
    /** The alpha value of the color */
    a;

    /**
     * Create a new color from R, G, B and A components
     * in the range of 0 to 255.
     * @param {number} r red value
     * @param {number} g green value
     * @param {number} b blue value
     * @param {number} a alpha (opacity) value
     */
    constructor(r, g, b, a = 255) {
        this.r = clamp(r, 0, 255);
        this.g = clamp(g, 0, 255);
        this.b = clamp(b, 0, 255);
        this.a = clamp(a, 0, 255);
    }

    /**
     * Creates a color from floats. Expects the floats
     * to be in the range from 0.0 to 1.0.
     * @param {number} r - red value
     * @param {number} g - green value
     * @param {number} b - blue value
     * @param {number} a - alpha value
     * @returns {Color} - the color
     */
    static fromFloats(r, g, b, a) {
        let red = (r || 0) * 255;
        let green = (g || 0) * 255;
        let blue = (b || 0) * 255;
        let alpha = (a || 1) * 255;
        return new Color(
            Math.round(red),
            Math.round(green),
            Math.round(blue),
            Math.round(alpha));
    }

    /**
     * Create a color from floats. Expects the floats
     * to be in the range of 0.0 to 1.0.
     * @param {number} r - the red value
     * @param {number} g - the green value
     * @param {number} b - the blue value
     * @param {number} a - the alpha value
     * @returns {Color}
     */
    static fromRgba(r, g, b, a) {
        return Color.fromFloats(r, g, b, a);
    }

    /**
     * Creates a color from integers. Expectes the
     * integers to be in the range from 0 to 255.
     * @param {number} r - red value
     * @param {number} g - green value
     * @param {number} b - blue value
     * @param {number} a - alpha value
     * @returns {Color} - the color
     */
    static fromBytes(r, g, b, a) {
        return new Color(r || 0, g || 0, b || 0, a || 255);
    }

    /**
     * Creates a color from integers. Expects the
     * integers to be in the range of 0 to 255.
     * @param {number} r - the red value
     * @param {number} g - the green value
     * @param {number} b - the blue value
     * @param {number} a - the alpha value
     * @returns {Color}
     */
    static fromRgba8(r, g, b, a) {
        return Color.fromBytes(r, g, b, a);
    }

    /**
     * Creates a color from a hex value
     * @param {number} hex - the hex color value in the format 0xrrggbbaa
     * @returns {Color} - the color
     */
    static fromHex(hex) {
        let r = (hex >> 24) & 0xff;
        let g = (hex >> 16) & 0xff;
        let b = (hex >> 8) & 0xff;
        let a = hex & 0xff;

        return new Color(r, g, b, a);
    }

    /**
     * Creates a color from a hex string value.
     * @param {string} hexStr - the hex color string in the format 0xrrggbbaa
     * @returns {Color}
     */
    static fromHexStr(hexStr) {
        return Color.fromHex(parseInt(hexStr, 16));
    }

    /**
     * Creates a color from an HSL color value.
     * Assumes h, s and l are in the range of 0 to 1
     * @param {number} h - hue
     * @param {number} s - saturation
     * @param {number} l - luminance
     * @returns {Color} - the color
     */
    static fromHsl(h, s, l) {
        let r, g, b;

        if (s == 0) {
            r = g = b = l;
        } else {
            const hue2rgb = (p, q, t) => {
                if (t < 0) t += 1;
                if (t > 1) t -= 1;
                if (t < 1 / 6) return p + (q - p) * 6 * t;
                if (t < 1 / 2) return q;
                if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
                return p;
            };

            let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
            let p = 2 * l - q;
            r = hue2rgb(p, q, h + 1 / 3);
            g = hue2rgb(p, q, h);
            b = hue2rgb(p, q, h - 1 / 3);
        }

        return Color.fromFloats(r, g, b, 1.0);
    }

    /**
     * Creates a color from an HSL color value.
     * Assumes h, s and l are in the range of 0 to 359 and 0 to 100.
     * @param {number} h - hue
     * @param {number} s - saturation
     * @param {number} l - luminance
     */
    static fromHsl8(h, s, l) {
        return Color.fromHsl(h / 359, s / 100, l / 100);
    }

    static fromHsb8(h, s, b) {
        let l = (2 - s) * b / 2;
        if (l != 0) {
            if (l == 0) {
                s = 0;
            } else if (l < 0.5) {
                s = s * b / (l * 2);
            } else {
                s = s * b / (2 - l * 2);
            }
        }

        return Color.fromHsl(h / 359, s / 100, l / 100);
    }

    /**
     * Generate a random color
     * @returns {Color} the random color value.
     */
    static random() {
        const r = randomRangeInt(0, 255);
        const g = randomRangeInt(0, 255);
        const b = randomRangeInt(0, 255);

        return new Color(r, g, b);
    }

    /**
     * Returns the color as a CSS color string.
     * @returns {string} - the css rgba() color string
     */
    asCss() {
        return `rgb(${this.r},${this.g},${this.b},${this.a})`;
    }

    /**
     * Returns the color as a CSS color string.
     * @returns {string} - the css rgba() color string
     */
    get css() {
        return this.asCss();
    }

    /**
     * Returns the color components as an array.
     * @returns {Array} the array of the color components.
     */
    get array() {
        return [this.r, this.g, this.b, this.a];
    }

    /** 0, 0, 0 */
    static get Black() { return new Color(0, 0, 0); }
    /** 255, 255, 255 */
    static get White() { return new Color(255, 255, 255); }
    /** 255, 0, 0 */
    static get Red() { return new Color(255, 0, 0); }
    /** 0, 255, 0 */
    static get Green() { return new Color(0, 255, 0); }
    /** 0, 0, 255 */
    static get Blue() { return new Color(0, 0, 255); }
    /** 255, 255, 0 */
    static get Yellow() { return new Color(255, 255, 0); }
    /** 255, 0, 255 */
    static get Magenta() { return new Color(255, 0, 255); }
    /** 0, 255, 255 */
    static get Cyan() { return new Color(0, 255, 255); }
    /** 0, 0, 0, 0 */
    static get Transparent() { return new Color(0, 0, 0, 0); }

    /** 172, 51, 53 */
    static get PastellRed() { return new Color(172, 51, 53); }
    /** 96, 117, 76 */
    static get GrassGreen() { return new Color(96, 117, 76); }
    /** */
    static get MetallicGold() { return new Color(211, 175, 55); }
    /** */
    static get Pink() { return new Color(255, 192, 203); }
    /** */
    static get PinkPantone() { return new Color(215, 72, 148); }
    /** */
    static get Plum() { return new Color(142, 69, 133); }
    /** */
    static get DarkViolet() { return new Color(112, 41, 99); }
    /** */
    static get OrchidViolet() { return new Color(122, 55, 139); }
    /** */
    static get Burgundy() { return new Color(159, 29, 53); }
    /** */
    static get LightBlue() { return new Color(153, 170, 255); }
    /** */
    static get PastellViolet() { return new Color(170, 102, 170); }
    /** */
    static get PastellYellow() { return new Color(230, 192, 115); }
    /** */
    static get Orange() { return new Color(255, 143, 0); }
    /** */
    static get PastellLightViolet() { return new Color(245, 206, 239); }
    /** */
    static get PastellGreen() { return new Color(154, 188, 167); }
    /** */
    static get LightGray() { return new Color(240, 240, 240); }
    /** */
    static get CardboardBrown() { return new Color(183, 168, 150); }
    /** */
    static get DarkBlue() { return new Color(28, 37, 77); }

    /** */
    static get Amaranth() { return new Color(229, 43, 80); }
    /** */
    static get Amber() { return new Color(255, 191, 0); }
    /** */
    static get Amethyst() { return new Color(153, 102, 204); }
    /** */
    static get BabyBlue() { return new Color(137, 207, 240); }
    /** */
    static get BananaYellow() { return new Color(255, 225, 53); }
    /** */
    static get BitterLemon() { return new Color(202, 224, 13); }
    /** */
    static get BitterLime() { return new Color(191, 255, 0); }
    /** */
    static get Bittersweet() { return new Color(254, 111, 94); }
    /** */
    static get BlackCoffee() { return new Color(59, 47, 47); }
    /** */
    static get Camouflage() { return new Color(120, 134, 107); }
    /** */
    static get Carmine() { return new Color(150, 0, 24); }
    /** */
    static get Champagne() { return new Color(247, 231, 206); }
    /** */
    static get Charcoal() { return new Color(54, 69, 79); }
    /** */
    static get CherryBlossom() { return new Color(255, 183, 197); }
    /** */
    static get ChromeYellow() { return new Color(255, 167, 0); }
    /** */
    static get Cobalt() { return new Color(0, 71, 171); }
    /** */
    static get Copper() { return new Color(184, 115, 51); }
    /** */
    static get Coral() { return new Color(255, 127, 80); }
    /** */
    static get Cornflower() { return new Color(100, 149, 237); }
    /** */
    static get Crimson() { return new Color(220, 20, 60); }
    /** */
    static get Dandelion() { return new Color(240, 225, 48); }
    /** */
    static get Eggplant() { return new Color(97, 64, 81); }
    /** */
    static get Emerald() { return new Color(80, 200, 120); }
    /** */
    static get FrenchRose() { return new Color(246, 74, 138); }
    /** */
    static get FuchsiaRose() { return new Color(199, 67, 117); }
    /** */
    static get Fuchsia() { return new Color(204, 57, 123); }
    /** */
    static get Mango() { return new Color(253, 190, 2); }
    /** */
    static get Mint() { return new Color(62, 180, 137); }

    /** */
    static get OldPaper() { return new Color(255, 255, 248); }
}