OverviewFilesCommitsIssuesDocsReleasesPeers

cthuloid / webui / static / color.js

66 lines · javascript ref:
// Colour maths for the page's Blender-style picker. A plain ES module so the
// browser loads it and brittle-bare tests it (test/color.test.js).
//
// All values are 0..1. A slot is { r, g, b, level }: the colour at full drive,
// normalised so its brightest channel is 1, and `level` the brightness. That
// is exactly HSV with V = level, which is why the picker's value bar and the
// row's brightness slider are the same control.

export function hsvToRgb (h, s, v) {
  h = ((h % 1) + 1) % 1
  const i = Math.floor(h * 6)
  const f = h * 6 - i
  const p = v * (1 - s)
  const q = v * (1 - f * s)
  const t = v * (1 - (1 - f) * s)
  switch (i % 6) {
    case 0: return { r: v, g: t, b: p }
    case 1: return { r: q, g: v, b: p }
    case 2: return { r: p, g: v, b: t }
    case 3: return { r: p, g: q, b: v }
    case 4: return { r: t, g: p, b: v }
    default: return { r: v, g: p, b: q }
  }
}

export function rgbToHsv (r, g, b) {
  const max = Math.max(r, g, b)
  const min = Math.min(r, g, b)
  const d = max - min
  let h = 0
  if (d > 0) {
    if (max === r) h = ((g - b) / d) % 6
    else if (max === g) h = (b - r) / d + 2
    else h = (r - g) / d + 4
    h /= 6
    if (h < 0) h += 1
  }
  return { h, s: max > 0 ? d / max : 0, v: max }
}

// Hue and saturation come from the slot's colour, V from its level -- so a
// slot dimmed to 0 still knows its colour. `fallbackHue` is for a colourless
// (grey or black) slot, where hue is undefined.
export function slotToHsv (slot, fallbackHue = 0) {
  const { h, s } = rgbToHsv(slot.r, slot.g, slot.b)
  return { h: s > 0 ? h : fallbackHue, s, v: slot.level }
}

export function hsvToSlot (h, s, v) {
  const c = hsvToRgb(h, s, 1)
  return { r: c.r, g: c.g, b: c.b, level: v }
}

const hex2 = (x) => Math.round(Math.max(0, Math.min(1, x)) * 255).toString(16).padStart(2, '0')

export function toHex (c) {
  return '#' + hex2(c.r) + hex2(c.g) + hex2(c.b)
}

export function fromHex (str) {
  const m = /^#?([0-9a-f]{6})$/i.exec(String(str).trim())
  if (!m) return null
  const n = parseInt(m[1], 16)
  return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 }
}