OverviewFilesCommitsIssuesDocsReleasesPeers

cthuloid / webui / test / color.test.js

58 lines · javascript ref:
// Colour maths behind the page's Blender-style picker (static/color.js).
import test from 'brittle'
import { hsvToRgb, rgbToHsv, slotToHsv, hsvToSlot, toHex, fromHex } from '../static/color.js'

const near = (t, a, b, msg) => t.ok(Math.abs(a - b) < 1e-6, msg + ' (' + a + ' vs ' + b + ')')

test('primaries convert both ways', (t) => {
  t.alike(hsvToRgb(0, 1, 1), { r: 1, g: 0, b: 0 })
  // 1/3 and 2/3 are not exact in binary: compare within float noise.
  for (const [h, want] of [[1 / 3, [0, 1, 0]], [2 / 3, [0, 0, 1]]]) {
    const c = hsvToRgb(h, 1, 1)
    near(t, c.r, want[0], 'r at h=' + h)
    near(t, c.g, want[1], 'g at h=' + h)
    near(t, c.b, want[2], 'b at h=' + h)
  }
  t.alike(hsvToRgb(0.5, 0, 0.25), { r: 0.25, g: 0.25, b: 0.25 })
  const h = rgbToHsv(0, 0, 1)
  near(t, h.h, 2 / 3, 'blue hue')
  t.is(h.s, 1)
  t.is(h.v, 1)
})

test('rgb -> hsv -> rgb round-trips', (t) => {
  for (const [r, g, b] of [[0.2, 0.7, 0.1], [1, 0.5, 0.25], [0.9, 0.9, 0.95], [0.01, 0, 0.6]]) {
    const { h, s, v } = rgbToHsv(r, g, b)
    const back = hsvToRgb(h, s, v)
    near(t, back.r, r, 'r')
    near(t, back.g, g, 'g')
    near(t, back.b, b, 'b')
  }
})

test('a slot maps to HSV with V = brightness', (t) => {
  const hsv = slotToHsv({ r: 0, g: 0.5, b: 1, level: 0.4 })
  near(t, hsv.v, 0.4, 'V is the level')
  near(t, hsv.s, 1, 'fully saturated')
  const slot = hsvToSlot(hsv.h, hsv.s, hsv.v)
  near(t, slot.level, 0.4, 'level back')
  near(t, slot.b, 1, 'colour normalised to full')
  near(t, slot.g, 0.5, 'g kept')
})

test('a dark slot keeps the hue it was given', (t) => {
  // V = 0 must not lose the colour: dimming to black and back returns to it.
  const hsv = slotToHsv({ r: 1, g: 0, b: 0, level: 0 }, 0.3)
  t.is(hsv.v, 0)
  near(t, hsv.h, 0, 'hue from the slot colour, not the fallback')
  const grey = slotToHsv({ r: 0, g: 0, b: 0, level: 0 }, 0.3)
  near(t, grey.h, 0.3, 'no colour at all: fallback hue')
})

test('hex conversion', (t) => {
  t.is(toHex({ r: 1, g: 0.5, b: 0 }), '#ff8000')
  t.alike(fromHex('#ff8000'), { r: 1, g: 128 / 255, b: 0 })
  t.alike(fromHex('ff8000'), { r: 1, g: 128 / 255, b: 0 }, 'no #')
  t.is(fromHex('#zzz'), null)
})