UNPKG

toosoon-utils

Version:
157 lines (156 loc) 7.21 kB
import { lerp, triLerp } from '../../maths'; import { hclToRgb, hsbToRgb, hslToRgb, normalizeColor, rgbToHcl, rgbToHsb, rgbToHsl } from '../../colors'; export const defaultSettings = { colorSpace: 'rgb' }; /** * Utility class for generating color scales and interpolating between colors * * @exports * @class ColorScale */ export default class ColorScale { /** * Array of colors composing the color scale */ colors = []; /** * @param {ColorRepresentation} input Input color representation * @param {ColorRepresentation} target Target color representation * @param {number} [length=5] Amount of colors composing the color scale * @param {ColorScaleSettings} [settings] Color scale generation settings */ constructor(input, target, length = 5, settings = { ...defaultSettings }) { this.colors = ColorScale.generate(input, target, length, settings); } /** * Static method for generating a color scale * * @param {ColorRepresentation} input Input color representation * @param {ColorRepresentation} target Target color representation * @param {number} length Amount of colors composing the color scale * @param {ColorScaleSettings} [settings] Color scale generation settings * @returns {Array<[number, number, number]>} Color scale colors */ static generate(input, target, length, settings = { ...defaultSettings }) { const colors = []; const inputColor = normalizeColor(input); const targetColor = normalizeColor(target); for (let i = 0; i < length; i++) { const value = i / Math.floor(length); colors.push(ColorScale.interpolate(inputColor, targetColor, value, settings)); } return colors; } /** * Static method for interpolating between colors * * @param {[number,number,number]} inputColor Input color * @param {[number,number,number]} targetColor Target color * @param {number} value Interpolation normalized value * @param {ColorScaleSettings} [settings] Color scale settings * @returns {[number,number,number]} Interpolated color */ static interpolate(inputColor, targetColor, value, settings = { ...defaultSettings }) { switch (settings.colorSpace) { case 'rgb': { const r = lerp(value, inputColor[0], targetColor[0]); const g = lerp(value, inputColor[1], targetColor[1]); const b = lerp(value, inputColor[2], targetColor[2]); return [r, g, b]; } case 'hsl': { const inputHsl = rgbToHsl(inputColor); const targetHsl = rgbToHsl(targetColor); const h1 = inputHsl[0]; const s1 = inputHsl[1]; const l1 = inputHsl[2]; const h2 = targetHsl[0] + (settings.hueOffset ?? 0); const s2 = targetHsl[1] + (settings.saturationOffset ?? 0); const l2 = targetHsl[2] + (settings.lightnessOffset ?? 0); const h = lerp(value, h1, h2); const s = lerp(value, s1, s2); const l = lerp(value, l1, l2); return hslToRgb([h, s, l]); } case 'hsb': { const inputHsb = rgbToHsb(inputColor); const targetHsb = rgbToHsb(targetColor); const h1 = inputHsb[0]; const s1 = inputHsb[1]; const b1 = inputHsb[2]; const h2 = targetHsb[0] + (settings.hueOffset ?? 0); const s2 = targetHsb[1] + (settings.saturationOffset ?? 0); const b2 = targetHsb[2] + (settings.brightnessOffset ?? 0); const h = lerp(value, h1, h2); const s = lerp(value, s1, s2); const b = lerp(value, b1, b2); return hsbToRgb([h, s, b]); } case 'hcl': const inputHcl = rgbToHcl(inputColor); const targetHcl = rgbToHcl(targetColor); const powerValue = Math.pow(value, settings.powerStrength ?? 1); const h1 = inputHcl[0]; const c1 = inputHcl[1]; const l1 = inputHcl[2]; const h2 = targetHcl[0] + (settings.hueOffset ?? 0); const c2 = targetHcl[1] + (settings.chromaOffset ?? 0); const l2 = targetHcl[2] + (settings.luminanceOffset ?? 0); let h, c, l; // HCL color palettes // -> https://colorspace.r-forge.r-project.org/articles/hcl_palettes.html switch (settings.mode) { /** * Qualitative * Designed for coding categorical information, * where no particular ordering of categories is available * and every color should receive the same perceptual weight. * * - Hue: Linear * - Chroma: Constant * - Luminance: Constant */ case 'qualitative': { h = lerp(value, h1, h2); c = c1; l = l1; } /** * Sequential * Designed for coding ordered/numeric information, * going from high to low (or vice versa). * * - Hue: Constant | Linear * - Chroma: Linear (+power) | Triangular (+power) * - Luminance: Linear (+power) */ case 'sequential': { h = lerp(value, h1, h2); c = settings.triangular ? triLerp(powerValue, c1, c2, settings.triangular) : lerp(powerValue, c1, c2); l = lerp(powerValue, l1, l2); } /** * Diverging * Designed for coding ordered/numeric information around a central neutral value, * where colors diverge from neutral to two extremes. * * - Hue: Constants (x2) * - Chroma: Linear (+power) | Triangular (+power) * - Luminance: Linear (+power) */ case 'diverging': { h = value < 0.5 ? h1 : value > 0.5 ? h2 : lerp(0.5, h1, h2); c = settings.triangular ? triLerp(powerValue, c1, c2, settings.triangular) : lerp(powerValue, c1, c2); l = lerp(powerValue, l1, l2); } default: { h = lerp(value, h1, h2); c = lerp(value, c1, c2); l = lerp(value, l1, l2); } } return hclToRgb([h, c, l]); } } }