Saturon LogoSaturon

🚧 This documentation covers a pre-1.0 release. Expect breaking changes.

Recipes

Register Plugins

A plugin recipe to extend Saturon's Color class with a suite of quick manipulation methods.

declare module "saturon" {
    interface Color<M extends ColorModel = ColorModel> {
        /**
         * Applies a linear multiplier to lightness (HSL).
         * 0 => black, 1 => no change, >1 => brighter.
         */
        brightness(multiplier: number): Color<"hsl">;

        /**
         * Adjusts contrast in RGB.
         * 0 => mid-gray, 1 => no change, >1 => more contrast.
         */
        contrast(value: number): Color<"rgb">;

        /**
         * Converts toward grayscale (HSL desaturation).
         * 0 => unchanged, 1 => fully grayscale.
         */
        grayscale(value: number): Color<"hsl">;

        /**
         * Rotates hue in degrees (HSL).
         * 0 => unchanged.
         */
        hueRotate(angle: number): Color<"hsl">;

        /**
         * Inverts RGB channels with a linear blend.
         * 0 => unchanged, 1 => fully inverted.
         */
        invert(value: number): Color<"rgb">;

        /**
         * Adjusts alpha (opacity).
         * 0 => fully transparent, 1 => unchanged.
         */
        opacity(value: number): Color<"rgb">;

        /**
         * Multiplies saturation (HSL).
         * 0 => completely unsaturated, 1 => unchanged, >1 => more saturated.
         */
        saturate(value: number): Color<"hsl">;

        /**
         * Applies a sepia-like effect (approximated in HSL).
         * 0 => unchanged, 1 => fully sepia.
         */
        sepia(value: number): Color<"hsl">;
    }
}

function clamp(value: number, min: number, max: number) {
    return Math.min(Math.max(value, min), max);
}

/**
 * Plugin: colorManipulationPlugin
 * Adds brightness, contrast, grayscale, hueRotate, invert, opacity, saturate, sepia
 */
export function colorManipulationPlugin(ColorClass: typeof Color) {
    ColorClass.prototype.brightness = function <M extends ColorModel>(this: Color<M>, multiplier: number) {
        return this.in("hsl").with({
            l: (l: number) => clamp(l * multiplier, 0, 100),
        });
    };

    ColorClass.prototype.contrast = function <M extends ColorModel>(this: Color<M>, value: number) {
        const mid = 128;
        return this.in("rgb").with({
            r: (r: number) => clamp((r - mid) * value + mid, 0, 255),
            g: (g: number) => clamp((g - mid) * value + mid, 0, 255),
            b: (b: number) => clamp((b - mid) * value + mid, 0, 255),
        });
    };

    ColorClass.prototype.grayscale = function <M extends ColorModel>(this: Color<M>, value: number) {
        return this.in("hsl").with({
            s: (s: number) => clamp(s * (1 - value), 0, 100),
        });
    };

    ColorClass.prototype.hueRotate = function <M extends ColorModel>(this: Color<M>, angle: number) {
        return this.in("hsl").with({
            h: (h: number) => {
                const next = (h + angle) % 360;
                return next < 0 ? next + 360 : next;
            },
        });
    };

    ColorClass.prototype.invert = function <M extends ColorModel>(this: Color<M>, value: number) {
        return this.in("rgb").with({
            r: (r: number) => clamp(r * (1 - value) + (255 - r) * value, 0, 255),
            g: (g: number) => clamp(g * (1 - value) + (255 - g) * value, 0, 255),
            b: (b: number) => clamp(b * (1 - value) + (255 - b) * value, 0, 255),
        });
    };

    ColorClass.prototype.opacity = function <M extends ColorModel>(this: Color<M>, value: number) {
        return this.in("rgb").with({
            alpha: (a: number = 1) => clamp(a * value, 0, 1),
        });
    };

    ColorClass.prototype.saturate = function <M extends ColorModel>(this: Color<M>, value: number) {
        return this.in("hsl").with({
            s: (s: number) => clamp(s * value, 0, 100),
        });
    };

    ColorClass.prototype.sepia = function <M extends ColorModel>(this: Color<M>, value: number) {
        const targetHue = 30;
        const targetSat = 30;
        return this.in("hsl").with({
            h: (h: number) => {
                const delta = ((targetHue - h + 540) % 360) - 180;
                return (h + delta * value + 360) % 360;
            },
            s: (s: number) => clamp(s * (1 - value) + targetSat * value, 0, 100),
            l: (l: number) => clamp(l * (1 - 0.15 * value) + l * (0.15 * value), 0, 100),
        });
    };
}

Example Usage

import { colorManipulationPlugin } from "../plugins.js";

use(colorManipulationPlugin);

const c = Color.from("hsl(50 50 50)");

// brightness (HSL lightness multiplier)
c.brightness(0.5).toString(); // darker
c.brightness(1.2).toString(); // brighter

// contrast (RGB)
Color.from("rgb(100 150 200)").contrast(0.5).toString();
Color.from("rgb(100 150 200)").contrast(1.2).toString();

// grayscale (HSL desaturation)
Color.from("hsl(200 80 50)").grayscale(1).toString(); // fully gray

// hueRotate
Color.from("hsl(10 60 50)").hueRotate(180).toString();

// invert
Color.from("rgb(10 20 30)").invert(1).toString();

// opacity
Color.from("rgb(10 20 30)").opacity(0.5).toString();

// saturate
Color.from("hsl(200 40 50)").saturate(0.5).toString();

// sepia
Color.from("hsl(200 60 40)").sepia(1).toString();

On this page