Color palette support?

I just got 3 PixelBlaze V2s in and I’m really digging them. I’m really glad I stumbled across this system. On to the question:

It doesn’t look like PixelBlaze has a built-in function to support custom palettes. One of the user patterns “Pew-Pew-Pew!” has a method for doing this which I think I can copy/modify for different RGB values. However, I’m not a programmer so I have no idea how it actually works and adding/removing hues is outside my expertise.

1 - Is there a simple way to use palettes (ala FastLED style)?
2 - If not, are there plans to include palettes in future revisions?

1 Like

Hopefully there are some ideas here and mapping custom gradients. Recently, I’ve been disliking the default HSV hue spectrum because there is too much pink/purple and not enough red/yellow with my APA102 strip. With a saturation down to 0.9 for my current setup, there is almost no yellow coming through and the pink/purple band is very saturated and too wide of a band. Anyone notice this?

Hi John -

I’ve been thinking about this ever since you first posted.

First, I wanted to point out the fixH() utility I made a while back, which addresses the underrepresentation of red through yellow in the default HSV color space. Search the pattern library ⧉ for “Utility: Perceptual hue”.

Second, I started working on a palette implementation after your post 3 weeks ago. Here’s my progress. This is all just one basic pattern of t1 + index/pixelCount, but that pattern is being mapped through 15 different palettes.

I just posted the code at electromage.com/patterns ⧉ as “Utility: Palettes”

Let me know what you think. Is the code readable and the concept clear?

1 Like

Ooh, awesome! Initial read through + test on my APA102 strip looks good and the code is nicely documented. I will take some time to go through in detail and come back with any questions.

My first question is about the transition between colors. For example, how are you transitioning between say red and green without going through orange and yellow? Are you taking the “v” down to just black out hues?

Yeah, either value to zero (fade through black) or saturation to 1 (fade through white). Or both.

Also, in a subtractive model red and green would mix to brown/black instead of yellow - check out the Palette in there that’s labeled “The search for brown”

Here’s a quick math only hack I came up with. The idea is to stretch out the yellow area using a smooth function like sin(), taking only a quarter of the waveform. Notice that green is usually at 1/3, is now right in the middle. Both teals and purples are compressed to favor orange/yellows.

  h = h % 1 //make sure h is a value between 0 and 1, or this doesn't work
  h2 = h * sin(h * PI * .5)

Here’s the regular HSV next to this:
mod hsv

Here’s another variant that stretches out red quite a bit, compressing the distance between green and blue. This uses a full half wave, so it eases in and out and has a wide band of purples and orange/yellow.

h3 = wave(h/2 + .75)

mod2 hsv

1 Like

Ok, so I spent some time going through Jeff’s code and it is definitely working for me and is pretty easy to implement/change. I’m liking it! I also really like the “math-only” approach to tweaking the default hue spectrum. That’s a pretty slick method that you could apply all kinds of modulation to over time (say . . . time of day).

I had a massive face palm moment regarding my own application. I’ve been developing patterns for an office remodel by looking directly at the strip. My actual install will be bouncing the light off the wall. As soon as I turned the strip around, I realized there is no need to fade the S or V between color. A sharp transition is just fine since the the gradient is already blurred by bounced light.

Anyway, before that, I had really good success with using Arctan instead of linear interpolation. A curve like this, brings the colors closer together nicely. The 8 and 16 can be increased or decreased (proportionately to each other) to change the transition slope.

h = -.35*atan(8-16*x)+.5     // Where X is the index/count for the pixels between colors

arctan

1 Like

Just for future ref, here is what happens to the arctan transfer when values are tweaked

h = -.33*atan(50-100*x)+.5     // Where X is the index/count for the pixels between colors

arctan2

Doh. I can only upload one image per post. This is the last example on this subject, I swear.

h = -.65*atan(1-2*x)+.5     // Where X is the index/count for the pixels between colors

arctan3

@wizard Your second function is essentially the same as my second method in “Utility: perceptual hue”, but for others I wanted to point out here that in testing I found I needed to re-wrap the inputs if they were outside 0…1 (e.g. with the canonical t1 + index/pixelCount) or else it, of course, reverses.

function fixH2(pH) {
  pH = pH % 1
  return wave((pH-0.5)/2)
}

Are you generating those gradient images by just screenshotting the preview, or using a CodePen or something?

And John, those arctans are slick too. Nice to be able to tune. Thanks.

@jeff,
So it is! How did I miss that? I had the first from an experiment some months ago and reinvented it, but clearly I borrowed the second from you or we think alike :grinning:

I screenshoted the preview (the one near save)

While all of the above code is awesome, it’s not really a direct replacement for FastLED palettes

And

It might be nice to have a simple (cut and paste) way to use those. @jeff 's code example is probably Overkill but a good start. I’m picturing a function that essentially given a palette array (or a default), and would otherwise replace making an RGB or hsv call (likely calling it itself)

I see where this would help with translating FastLED patterns. Since I plan on that, if it doesn’t exist yet, it will soon. (I’m planning on lighting up my dark winter with lots of LED coding)

In the name of coordinating efforts in the best interest of PB, just know that I think Ben is considering internalizing palette support on the roadmap!

2 Likes

I was searching for exactly this, and got to this thread via a Google query – I’d also love a re-implementation of FastLED’s color palette functions!

2 Likes

Replying to myself here – for anyone else looking – the Pattern “Utility: Palettes” within https://electromage.com/patterns/ (BTW: If there’s a deep link option to a given pattern, I don’t see it?) does basically exactly what you’d want if you’re used to FastLED palettes.

3 Likes

I came across this, as part of the amazing stuff from Inigo:

His version of a simple palette method, using cosines.

And found in the comments on the shadertoy demo
is this:

http://dev.thi.ng/gradients/

Another super powerful way to define a palette, and more so, it’s in the 0…1 format we want for PB. By default it uses RGB, (but I’m curious, and will play with it in HSV).
I’ll write up a demo pattern for this shortly.

The randomize button shows me, that basically if you just generate a random set of values between -PI and PI, you end up with a decent palette almost every single time (and the rest are mostly too small a variety of color, like light greenblue to darker greenblue, or one slightly varied shade of orange). That’s phenomenal.

3 Likes

Seems to work, and it will only get easier with array initializers on the way…

//  Define palette.
var paramA = array(3); var paramB = array(3); var paramC = array(3); var paramD = array(3);
//  rainbow1:     [[0.500 0.500 0.500] [0.500 0.500 0.500] [1.000 1.000 1.000] [0.000 0.333 0.667]]
paramA[0] = 0.500; paramA[1] = 0.500; paramA[2] = 0.500;
paramB[0] = 0.500; paramB[1] = 0.500; paramB[2] = 0.500;
paramC[0] = 1.000; paramC[1] = 1.000; paramC[2] = 1.000;
paramD[0] = 0.000; paramD[1] = 0.333; paramD[2] = 0.667;

//  Calculate the palette color at a particular point and return its RGB components.
function paletteColorAt(t, coeffA, coeffB, coeffC, coeffD, retVal) {
  for (i=0;i<retVal.length;i++) retVal[i] = coeffA[i] + coeffB[i] * cos(PI2 * (coeffC[i] * t + coeffD[i]));
}

var paletteRGB = array(3);
export function render(index) {
  paletteColorAt(index/pixelCount, paramA, paramB, paramC, paramD, paletteRGB);
  rgb(paletteRGB[0], paletteRGB[1], paletteRGB[2]);
}

The cpt library is another good source of palettes; you can use PaletteKnife for FastLED to extract the coefficients and then render them like so:

//  Define palette as a variable number of bands, each containing a startIndex, R, G, and B component.
function newPaletteRGB(numBands) { ary = array(numBands); ary.mutate((v) => array(4)); return ary; }
function setPaletteBandRGB(ary, index, offset, R, G, B) { ary[index][0] = offset; ary[index][1] = R >> 8; ary[index][2] = G >> 8; ary[index][3] = B >> 8; }
function LERP(percent, low, high) { return low + percent * (high - low); }
export var saveV;
function rgbFromFastLedPalette(v, palette, retVal) {
  v = clamp(v << 8, 0, 255); 
  for (idx=0;idx<palette.length;idx++) {
    if (v <= palette[idx][0]) {
      //  We're in this band, so LERP to find the appropriate shade.
      if (v == 0) { retVal[0] = palette[idx][1]; retVal[1] = palette[idx][2]; retVal[2] = palette[idx][3]; }
      else {
        scale = (v - palette[idx-1][0]) / (palette[idx][0] - palette[idx-1][0]);
        retVal[0] = LERP(scale, palette[idx-1][1], palette[idx][1]); retVal[1] = LERP(scale, palette[idx-1][2], palette[idx][2]); retVal[2] = LERP(scale, palette[idx-1][3], palette[idx][3]);
      }
      break;
    }
  }
}

// Gradient palette "quagga_gp" originally from http://soliton.vm.bytemark.co.uk/pub/cpt-city/rc/tn/quagga.png.index.html; converted for FastLED with gammas (2.6, 2.2, 2.5)
var paletteQuagga = newPaletteRGB(6);
setPaletteBandRGB(paletteQuagga, 0,   0,   1,   9,  84);
setPaletteBandRGB(paletteQuagga, 1,  40,  42,  24,  72);
setPaletteBandRGB(paletteQuagga, 2,  84,   6,  58,   2);
setPaletteBandRGB(paletteQuagga, 3, 168,  88, 169,  24);
setPaletteBandRGB(paletteQuagga, 4, 211,  42,  24,  72);
setPaletteBandRGB(paletteQuagga, 5, 255,   1,   9,  84);

//  Pattern
var pixelRGB = array(3);
export function render(index) {
  rgbFromFastLedPalette(index/pixelCount, paletteQuagga, pixelRGB);
    rgb(pixelRGB[0], pixelRGB[1], pixelRGB[2]);
}

This too will become simpler with v3.19+…happy days!

1 Like

Nice. Using array iterative functions, instead of the for loop, and moving the RGB call into the function, you can make it even shorter and cleaner. (I’ll do it with 3.19b shortly.)

Additionally:
It seems a huge waste to define 4 values of 256 (position, R,G,B) as a 4 value array, given that PB stores numbers in 16.16 and can compact those 4 numbers into 1 32 bit value, since the function could extract the 4 8bit values easily enough.
Plus in code, you could also write it as 2 0x hex numbers (since we can’t do hex decimals/fractionals directly), which means it’s still slightly human readable, if you know hex color codes. Seems better than the binary option with 32 1s and 0s. Decimal is still more human readable, though, so likely I’ll figure out a clean way to populate the single array.

Yes, for HDR LEDs, you’d want more bits… but for ‘normal’ usage, reducing the size would mean you could store more palettes in far less space (using less variables too)

So I’ll implement this as tightly as I can, in the hopes that @wizard will just adopt the code/method and add a basic palette selection into firmware. (Assuming people want this, of course)

@wizard, any chance of adding lerp() as built in to 3.19?

Is it just me, or do you wish instead of random seeming names, pallettes were sorted/ordered by some sort of general scheme?

I don’t mind seeing Lava, but Red Lava would at least sort with other reds. Red Orangey is fine too. Even Red Yellow…

Bluegreens… I’m fine with Blue Ocean, Blue Clouds, Blue Atlantica, etc.

Redgreens… Etc

Rainbow

Browns, Yellows…

At least then I’d have an idea without having to look up a palette color scheme or “try it out blindly” (over and over and over…)
If I’m wanting a pallette for a Fire effect, looking at a handful of Red palettes is easy, not scrolling thru dozens to try to pick out the ones that might work.

In that vein, when I crib these (and I will), I’m gonna rename them appropriately.

1 Like

If the palette bands are expressed as RGB values, they’re already sortable…

Off the top of my head, if sorting was done based on the component values of the bands presented in descending order (dropping any zeroed components) then similar color transitions would sort out typographically to be near one another:

Bands Description
bFF->b80g40 BlueOnly to BlueGreen
bFF->g80b40 BlueOnly to SeaGreen
bFF->gFF BlueOnly to GreenOnly
b80g40->gFF BlueGreen to GreenOnly
g80b40->gFF SeaGreen to GreenOnly
gFF->bFF GreenOnly to BlueOnly
rFF->r80b80 RedOnly to Purple
rFF->r80b80->bFF RedOnly to Purple to BlueOnly
2 Likes