# Code for Task #5: Rainbow Codenections!

**URL:** <https://forum.electromage.com/t/code-for-task-5-rainbow-codenections/1042>\
**Category:** Pixelblaze Academy of Wizardry and Enchantment\
**Created:** [February 26, 2021, 9:00pm UTC](https://forum.electromage.com/t/code-for-task-5-rainbow-codenections/1042 "2021-02-26T21:00:15Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![Scruffynerf](https://forum.electromage.com/letter_avatar_proxy/v4/letter/s/34f0e0/32.png) [@Scruffynerf](https://forum.electromage.com/u/Scruffynerf)\
**Post date:** [February 26, 2021, 9:00pm UTC](https://forum.electromage.com/t/code-for-task-5-rainbow-codenections/1042/1 "2021-02-26T21:00:15Z")

</div>

Post your answers to [Task #5](https://forum.electromage.com/t/task-5-end-of-the-line/1040) here, so we avoid spoilers for others.

---

<div class="post-metadata">

**Author:** ![zranger1](https://forum.electromage.com/user_avatar/forum.electromage.com/zranger1/32/301_2.png) [@zranger1](https://forum.electromage.com/u/zranger1)\
**Post date:** [March 6, 2021, 9:02pm UTC](https://forum.electromage.com/t/code-for-task-5-rainbow-codenections/1042/2 "2021-03-06T21:02:04Z")

</div>

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:

> [@2d rainbow patterns?](https://forum.electromage.com/t/2d-rainbow-patterns/922):
>
> Hey Guys, First I want to give major props to all that can figure out the coding. I’ve spent hours trying to grasp it and the only thing I accomplish is a migraine. I tried to edit some of the existing patterns and just not coming close. I’ve used editors like LEDEDIT software in the past and they all have these rainbow presets that start in the middle of the matrix expanding outwards in a really pretty rainbow hue. I’m looking for the same patterns, no black or white. just a pretty run thro…

Without further ado, the code:

```auto
// 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);
}

```

---

<div class="post-metadata">

**Author:** ![jeff](https://forum.electromage.com/user_avatar/forum.electromage.com/jeff/32/3473_2.png) [@jeff](https://forum.electromage.com/u/jeff)\
**Post date:** [March 9, 2021, 9:52am UTC](https://forum.electromage.com/t/code-for-task-5-rainbow-codenections/1042/3 "2021-03-09T09:52:19Z")

</div>

[/uploads/default/original/1X/a08d9fc7a476c220ddf1628960c735d664ea0c45.mp4](https://forum.electromage.com/uploads/default/original/1X/a08d9fc7a476c220ddf1628960c735d664ea0c45.mp4)

Here’s what I know:

- I ported [HSLuv](https://www.hsluv.org/) 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](https://forum.electromage.com/t/color-palette-support/192/6) we already have

🤔

> **Click to show the the code 😬**
>
> ```javascript
> /*
> !! 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()
> 
> ```
