Post your answers to Task #5 here, so we avoid spoilers for others.
To celebrate the coming matrix tasks, here’s a rainbow pattern that uses many sliders and works in 2D as well as on a strip. It’s based on code that @scruffynerf and I put together for this thread:
Without further ado, the code:
// Rainbow and radial rainbow for 1D and 2D displays
export var speed = 0.25
export var direction = 1
export var scale = 2.9
// UI sliders
// higher is faster
export function sliderSpeed(v) {
speed = 1-v;
}
// left = inward, right = outward
export function sliderDirection(v) {
direction = (v < 0.5) ? 1 : -1;
}
export function sliderBandwidth(v) {
scale = sqrt(0.5) * 5 * (1-v);
}
// pythagorean distance from center of display for 2D. Pixelblaze
// provides normalized x,y coords, so center is always going
// to be (0.5,0.5) regardless of real world display dimensions
function getRadius(x, y) {
x -= 0.5; y -= 0.5;
return sqrt(x*x + y*y);
}
// generate a timer - a sawtooth wave that we can
// use to animate color -- the direction flag makes
// it positive or negative, depending on the UI
// slider setting
export function beforeRender(delta) {
t1 = direction * time(0.08 * speed);
}
// use wave starting from center and our timer to color every pixel
export function render(index) {
hsv(t1+(wave(0.5*index/pixelCount)*scale), 1, 1);
}
// use radius and our timer to color every pixel
export function render2D(index, x, y) {
hsv(t1+(getRadius(x, y)*scale), 1, 1);
}
2 Likes
Here’s what I know:
- I ported HSLuv to PB to chase the perfect rainbow
- This was ridiculous scope creep, it took forever
- I learned stuff
- If you like unicorns and pastels, you’ll luv HSLuv
- It requires PB v3 and I appreciate the new array functions
Here’s what I don’t know:
- If it’s any better than the fixH hacks we already have
Click to show the the code 😬
/*
!! This pattern requires Pixelblaze v3
This is a port of HSLuv to Pixelblaze's 16.16 fixed point math.
HSLuv is a perceptally equidistant colorspace with fewer tradeoffs than CIELUV
https://www.hsluv.org/
If you've ever been in search of a more-perfect rainbow than hsv(h, 1, 1)
generates, this is for you.
For some background, see
https://forum.electromage.com/t/color-palette-support/192/6?u=jeff
The math is very intense. A simple rainbow generator goes from 430
to 15 FPS using this! Therefore, you may wish to precompute and cache
a lookup table of some sort. More reasonable approaches WRT performance
are shown in the thread above and the Perceptual Hue pattern.
HSLuv is released under an MIT licesne, as is this.
Jeff Vyduna
*/
var speed = .3, saturation = 1, luminosity = .5
export function sliderSpeed(v) { speed = .0001 + v }
export function sliderSaturation(v) { saturation = v }
export function sliderLuminosity(v) { luminosity = v }
var hsluv = array(3)
var rgbResult = array(3)
var reds = array(pixelCount)
var greens = array(pixelCount)
var blues = array(pixelCount)
var elapsed = 0 // ms
export function beforeRender(delta) {
elapsed += delta
// I can't get precompute() to work outside of beforeRender, like on
// slider change (execution steps exhausted)
if (elapsed > 100) {
precompute()
elapsed -= 100
}
t1 = time(.02 / speed)
}
export function render(index) {
// Live-computation version
// h = (t1 + index/pixelCount) % 1
// hsluv[0] = h * 360
// rgbResult = hsluvToRgb(hsluv)
// rgb(rgbResult[0], rgbResult[1], rgbResult[2])
animIndex = (index + t1 * pixelCount) % pixelCount
rgb(reds[animIndex], greens[animIndex], blues[animIndex])
}
// Begin port of https://github.com/hsluv/hsluv/blob/master/haxe/src/hsluv/Hsluv.hx
// Constants
var m = array(3); m.mutate(() => array(3))
m[0][0] = 3.240969941904521
m[0][1] = -1.537383177570093
m[0][2] = -.498610760293
m[1][0] = -.96924363628087
m[1][1] = 1.87596750150772
m[1][2] = .041555057407175
m[2][0] = .055630079696993
m[2][1] = -.20397695888897
m[2][2] = 1.056971514242878
var minv = array(3); minv.mutate(() => array(3))
minv[0][0] = .41239079926595
minv[0][1] = .35758433938387
minv[0][2] = .18048078840183
minv[1][0] = .21263900587151
minv[1][1] = .71516867876775
minv[1][2] = .072192315360733
minv[2][0] = .019330818715591
minv[2][1] = .11919477979462
minv[2][2] = .95053215224966
var refY = 1,
refU = .19783000664283,
refV = .46831999493879,
kappa = 903.2962962,
epsilon =.0088564516
// hexChars = "0123456789abcdef" // When PB supports chars, we can add HTML hex codes
/**
For a given lightness, return a list of 6 lines in slope-intercept
form that represent the bounds in CIELUV, stepping over which will
push a value out of the RGB gamut
float64 converted to 16.16 by dividing all constants by 10000
*/
var boundResult = array(6)
boundResult.mutate(() => array(2))
function getBounds(L) {
var sub1 = pow((L + 16) / 116, 3)
var sub2 = sub1 > epsilon ? sub1 : L / kappa
for(var c = 0; c < 3; c++) {
var m1 = m[c][0], m2 = m[c][1], m3 = m[c][2]
for(var t = 0; t < 2; t++){
var top1 = (28.4517 * m1 - 9.4839 * m3) * sub2
var top2 = (83.8422 * m3 + 76.9860 * m2 + 73.1718 * m1) * L * sub2 - 76.9860 * t * L
var bottom = (63.2260 * m3 - 12.6452 * m2) * sub2 + 12.6452 * t
boundResult[c * 2 + t][0] = top1 / bottom // slope
boundResult[c * 2 + t][1] = top2 / bottom // intercept
}
}
return boundResult
}
/**
For given lightness, returns the maximum chroma. Keeping the chroma value
below this number will ensure that for any hue, the color is within the RGB
gamut.
*/
function maxSafeChromaForL(L) {
var bounds = getBounds(L)
var minResult = 32767 // Math.POSITIVE_INFINITY
for(var bound = 0; bound < bounds.length; bound++) {
var slope = bounds[bound][0]
var intercept = bounds[bound][1]
var length = abs(slope) / sqrt(intercept * intercept + 1)
minResult = min(minResult, length)
}
return minResult
}
function maxChromaForLH(L, H) {
var hrad = H / 360 * PI2
var bounds = getBounds(L)
var minResult = 32767 // Math.POSITIVE_INFINITY
for(var bound = 0; bound < bounds.length; bound++) {
var slope = bounds[bound][0]
var intercept = bounds[bound][1]
var length = intercept / (sin(hrad) - slope * cos(hrad))
if (length >= 0) minResult = min(minResult, length)
}
return minResult
}
function dotProduct(a, b) {
var sum = 0
for(var i = 0; i < a.length; i++) sum += a[i] * b[i]
return sum
}
// Used for rgb conversions
function fromLinear(c) {
return c <= .0031308 ? 12.92 * c : 1.055 * pow(c, .4166666666666667) - .055
}
function toLinear(c){
return c > 0.04045 ? pow((c + .055) / 1.055, 2.4) : c / 12.92
}
/**
* XYZ coordinates are ranging in [0;1] and RGB coordinates in [0;1] range.
* xyz: An array containing the color's X,Y and Z values.
* Returns an array containing the resulting color's red, green and blue.
**/
var rgbResult = array(3)
function xyzToRgb(xyz){
xyz.mapTo(rgbResult, (v, i, arr) =>
fromLinear(dotProduct(m[i], arr))
)
return rgbResult
}
/**
* RGB coordinates are ranging in [0;1] and XYZ coordinates in [0;1].
* rgb: An array containing the color's R,G,B values.
* Returns an array containing the resulting color's XYZ coordinates.
**/
var xyzResult = array(3)
var rgbl = array(3)
function rgbToXyz(rgb) {
rgb.mapTo(rgbl, (v) => toLinear(v))
rgbl.mapTo(xyzResult, function(v, i, arr) {
dotProduct(minv[i], arr)
})
return xyzResult
}
/**
* XYZ coordinates are ranging in [0;1].
* xyz: An array containing the color's X,Y,Z values.
* Returns an array containing the resulting color's LUV coordinates.
**/
var luvResult = array(3)
function xyzToLuv(xyz){
var X = xyz[0], Y = xyz[1], Z = xyz[2]
// Warning: Did not handle div by 0: https://github.com/hsluv/hsluv/blob/master/haxe/src/hsluv/Hsluv.hx#L207
var divider = X + (15 * Y) + (3 * Z)
var varU = 4 * X / divider
var varV = 9 * Y / divider
if (Y <= epsilon) {
var L = (Y / refY) * kappa
} else {
var L = 116 * pow(Y / refY, 1/3) - 16
}
if (L == 0) {
luvResult[0] = luvResult[1] = luvResult[2] = 0
return luvResult
}
luvResult[0] = L
luvResult[1] = 13 * L * (varU - refU)
luvResult[2] = 13 * L * (varV - refV)
return luvResult
}
/**
* XYZ coordinates are ranging in [0;1].
* luv: An array containing the color's L,U,V values.
* Returns an array containing the resulting color's XYZ coordinates.
**/
var xyzResult = array(3)
function luvToXyz(luv){
var L = luv[0], U = luv[1], V = luv[2]
if (L == 0) {
xyzResult[0] = xyzResult[1] = xyzResult[2] = 0
return xyzResult
}
var varU = U / (13 * L) + refU
var varV = V / (13 * L) + refV
if (L <= 8) {
var Y = refY * L / kappa
} else {
var Y = refY * pow((L + 16) / 116, 3)
}
var X = xyzResult[0] = - (9 * Y * varU) / ((varU - 4) * varV - varU * varV)
xyzResult[1] = Y
xyzResult[2] = (9 * Y - (15 * varV * Y) - (varV * X)) / (3 * varV)
return xyzResult
}
/**
* luv: An array containing the color's L,U,V values.
* returns an array containing the resulting color's LCH coordinates.
**/
var lchResult = array(3)
function luvToLch(luv) {
var U = luv[1], V = luv[2]
var H, C = hypot(U, V)
// Greys: disambiguate hue
if (C < 0.0001) {
H = 0
} else {
var hrad = atan2(V, U)
H = hrad * 180 / PI
if (H < 0) H += 360
}
lchResult[0] = luv[0]
lchResult[1] = C
lchResult[2] = H
return lchResult
}
/**
* lch: An array containing the color's L,C,H values.
* Returns an array containing the resulting color's LUV coordinates.
**/
var luvResult = array(3)
function lchToLuv(lch){
var L = lch[0], C = lch[1], H = lch[2]
var hrad = H / 360 * PI2
luvResult[0] = L
luvResult[1] = cos(hrad) * C
luvResult[2] = sin(hrad) * C
return luvResult
}
/**
* HSLuv values are ranging in [0;360], [0;100] and [0;100].
* hsluv: An array containing the color's H,S,L values in HSLuv color space.
* Returns an array containing the resulting color's LCH coordinates.
**/
var lchResult = array(3)
function hsluvToLch(hsluv){
var H = hsluv[0], S = hsluv[1], L = hsluv[2]
lchResult[1] = 0
lchResult[2] = H
// White and black: disambiguate chroma
if (L > 99.9999) { lchResult[0] = 100; return lchResult }
if (L < 0.001) { lchResult[0] = 0; return lchResult }
lchResult[0] = L
lchResult[1] = maxChromaForLH(L, H) / 100 * S
return lchResult
}
/**
* HSLuv values are ranging in [0;360], [0;100] and [0;100].
* lch: An array containing the color's LCH values.
* Returns an array containing the resulting color's HSL coordinates in HSLuv color space.
**/
var hsluvResult = array(3)
function lchToHsluv(lch) {
var L = lch[0], C = lch[1], H = lch[2]
hsluvResult[0] = H
hsluvResult[1] = 0
if(L > 99.9999) { hsluvResult[2] = 100; return hsluvResult }
if(L < 0.0001) { hsluvResult[2] = 0; return hsluvResult }
hsluvResult[1] = C / maxChromaForLH(L, H) * 100
hsluvResult[2] = L
return hsluvResult
}
/**
* HSLuv values are in [0;360], [0;100] and [0;100].
* hpluv: An array containing the color's H,S,L values in HPLuv (pastel variant) color space.
* Returns an array containing the resulting color's LCH coordinates.
**/
var lchResult = array(3)
function hpluvToLch(hpluv) {
var H = hpluv[0], S = hpluv[1], L = hpluv[2]
lchResult[1] = 0
lchResult[2] = H
if (L > 99.99999) { lchResult[0] = 100; return lchResult }
if (L < .0001) { lchResult[0] = 0; return lchResult }
lchResult[0] = L
lchResult[1] = maxSafeChromaForL(L) / 100 * S
return lchResult
}
/**
* HSLuv values are ranging in [0;360], [0;100] and [0;100].
* lch: An array containing the color's LCH values.
* Returns an array containing the resulting color's HSL coordinates in HPLuv (pastel variant) color space.
**/
var hpluvResult = array(3)
function lchToHpluv(lch) {
var L = lch[0], C = lch[1], H = lch[2]
hpluvResult[0] = H
hpluvResult[1] = 0
hpluvResult[2] = L
if (L > 99.9999) { hpluvResult[2] = 100; return hpluvResult }
if (L < 0.0001) { hpluvResult[2] = 0; return hpluvResult }
hpluvResult[1] = C / maxSafeChromaForL(L) * 100
return hpluvResult
}
// function rgbToHex(a){ // Cannot implement on Pixelblaze yet }
// function hexToRgb(a){ // Cannot implement on Pixelblaze yet }
/**
* RGB values are ranging in [0;1].
* lch: An array containing the color's LCH values.
* Returns an array containing the resulting color's RGB coordinates.
**/
function lchToRgb(lch){
return xyzToRgb(luvToXyz(lchToLuv(lch)))
}
/**
* RGB values are ranging in [0;1].
* rgb: An array containing the color's RGB values.
* Returns an array containing the resulting color's LCH coordinates.
**/
function rgbToLch(rgb) {
return luvToLch(xyzToLuv(rgbToXyz(rgb)))
}
// RGB <--> HPLuv
/**
* HSLuv values are ranging in [0;360], [0;100] and [0;100] and RGB in [0;1].
* hsluv: An array containing the color's HSL values in HSLuv color space.
* Returns an array containing the resulting color's RGB coordinates.
**/
function hsluvToRgb(hsluv){ return lchToRgb(hsluvToLch(hsluv)) }
function rgbToHsluv(rgb){ return lchToHsluv(rgbToLch(rgb)) }
function hpluvToRgb(hpluv){ return lchToRgb(hpluvToLch(hpluv)) }
function rgbToHpluv(rgb){ return lchToHpluv(rgbToLch(rgb)) }
// End Port of HSLuv
// Precache expensice hsluv computations
function precompute() {
hsluv[1] = 100 * saturation
hsluv[2] = 100 * luminosity
for (i = 0; i < pixelCount; i++) {
hsluv[0] = i / pixelCount * 360
rgbResult = hsluvToRgb(hsluv)
reds[i] = rgbResult[0]
greens[i] = rgbResult[1]
blues[i] = rgbResult[2]
}
}
precompute()
2 Likes