const SERPENTINE_WIRING = true; function clamp(value, min, max) { return Math.max(min, Math.min(max, value)); } function hexToRgb(hex) { const normalizedHex = hex.trim().toLowerCase(); const value = normalizedHex.startsWith('#') ? normalizedHex.slice(1) : normalizedHex; return { r: parseInt(value.slice(0, 2), 16), g: parseInt(value.slice(2, 4), 16), b: parseInt(value.slice(4, 6), 16), }; } function rgbToHex(rgb) { return toHex(rgb.r) + toHex(rgb.g) + toHex(rgb.b); } function toHex(value) { const boundedValue = clamp(Math.round(value), 0, 255); const hex = boundedValue.toString(16); return hex.length === 1 ? '0' + hex : hex; } function lerpRgb(lhs, rhs, t) { return { r: Math.round(lhs.r + (rhs.r - lhs.r) * t), g: Math.round(lhs.g + (rhs.g - lhs.g) * t), b: Math.round(lhs.b + (rhs.b - lhs.b) * t), }; } function samplePalette(paletteStops, value) { const clampedValue = clamp(value, 0, 1); for (let index = 0; index < paletteStops.length - 1; ++index) { const left = paletteStops[index]; const right = paletteStops[index + 1]; if (clampedValue <= right.stop) { const span = right.stop - left.stop || 1; const t = clamp((clampedValue - left.stop) / span, 0, 1); const interpolatedColor = lerpRgb(left.color, right.color, t); return rgbToHex(interpolatedColor); } } return rgbToHex(paletteStops[paletteStops.length - 1].color); } function toIndex(col, row, width, serpentine = SERPENTINE_WIRING) { if (!serpentine || row % 2 === 0) { return row * width + col; } return row * width + (width - 1 - col); } function createFrame(width, height, fill = '000000') { return new Array(width * height).fill(fill); } function frameToPayload(frame) { return 'RAW:' + frame.join(''); } function fadeFrame(frame, factor) { for (let index = 0; index < frame.length; ++index) { const hex = frame[index]; const r = parseInt(hex.slice(0, 2), 16) * factor; const g = parseInt(hex.slice(2, 4), 16) * factor; const b = parseInt(hex.slice(4, 6), 16) * factor; frame[index] = toHex(r) + toHex(g) + toHex(b); } } function addRgbToFrame(frame, index, rgb) { if (index < 0 || index >= frame.length) { return; } const current = hexToRgb(frame[index]); const updated = { r: clamp(current.r + rgb.r, 0, 255), g: clamp(current.g + rgb.g, 0, 255), b: clamp(current.b + rgb.b, 0, 255), }; frame[index] = rgbToHex(updated); } function addHexColor(frame, index, hexColor, intensity = 1) { if (intensity <= 0) { return; } const base = hexToRgb(hexColor); addRgbToFrame(frame, index, { r: base.r * intensity, g: base.g * intensity, b: base.b * intensity, }); } module.exports = { SERPENTINE_WIRING, clamp, hexToRgb, rgbToHex, lerpRgb, samplePalette, toIndex, createFrame, frameToPayload, fadeFrame, addRgbToFrame, addHexColor, };