Challenge Idea: All The Colors (Rainbow Smoke)

Apologies if this isn’t the right place for this, or if it’s already been suggested/requested. I searched but didn’t find anything.

I’ve been trying to port “Rainbow Smoke” to Pixelblaze, unsuccessfully, and wondered if anyone else might like to make an attempt.

An output image example:
image

Video of a 32x32 RGB LED matrix (SmartMatrix) implementation:

It was originally a “code golf” challenge winner on stack exchange: popularity contest - Images with all colors - Code Golf Stack Exchange

The idea is to:

make images where each pixel is a unique color (no color is used twice and no color is missing).

The algorithm used by the winner:

put a random [color] in the middle, and then start putting random [colors] in a neighborhood that most resembles them.

My hope was to do it without mapping a virtual grid onto the actual pixels, but that may not be feasible. I’m also trying to get it to work for any arbitrary, potentially non-rectilinear 2D map (such as my Fibonacci layouts).

Obviously this would differ from the original idea, as we won’t have enough pixels to use “all colors”.

The original author’s site for the project: Rainbow Smoke - Home

My implementation of it in C/Arduino for the SmartMatrix Aurora project: aurora/PatternRainbowSmoke.h at master · pixelmatix/aurora · GitHub

2 Likes

That… is a very weird algorithm! Let me think about this for a couple of days.

Exhaustive neighborhood search and Pixelblaze are not things that occur together in nature, but there’s bound to be a sneaky way to get the same effect on non-rectangular displays.

1 Like

Thankfully the pattern doesn’t need to work quickly. I found it more interesting watching it work, filling in slowly, then pausing for a few seconds before clearing and starting over on a new random set.
I’m going to make another attempt tomorrow, after thinking about it for a while.
Can’t wait to see what you come up with!

Ok. First attempt:

This version does the color arranging incrementally, by random sampling and swapping. It makes nice patterns, and doesn’t use a lot of memory but is !!!extremely!!! slow. (Of course, it works for non-rectangular pixel arrangements. :slightly_smiling_face:)

Next up: A way to make it converge on a “good” color arrangement faster. Will post code when I get to a reasonable speed!

1 Like

Aesthetically, I like things that self-organize. I feel my CPU gets bored and annoyed if I make it do exhaustive searches. And watching the original algorithm, it did look a bit organic and live – like a finite state automaton, or a slime mold model.

So one of the first things I tried was building a variant of the “boids model” in which boids are very strongly attracted to others of similar color and do their best to avoid differently colored boids.

It actually works pretty well except that, sadly, it takes even more computation than the original exhaustive search approach. So much for aesthetics!

3 Likes

EDIT: Quick note - Fibonacci 256 layout seems to do best w/neighborhoodRadius of around 0.06.


Here’s a first cut at the basic algorithm. It is not fast, but will work on 2D displays of any shape or size

Things to note:

  • Memory is the limiting factor for pixel count. Neighborhood search runs incrementally, limiting the number of comparisons per frame so you can run more pixels without getting “execution steps exhausted”.
  • Uses random palettes of user specifiable size. (I chose to not do the “whole color gamut” thing, since I don’t have enough pixels to make that look good.)
  • Determines neighborhood based on radial distance. You may want to play with “neighborhoodRadius” to get best results for your display.
Source Code
// Rainbow Smoke (rainbowsmoke.hu/home) for Pixelblaze
// 2024 - ZRanger1

// Wow, what a lot of pixelCount-sized arrays!
var palette = array(pixelCount)
var pixels = array(pixelCount)
var coords = array(pixelCount)
var hasColor = array(pixelCount)

// may need to play with this to get a "good" neighborhood size for
// your display.
var neighborhoodRadius = 0.075;
var numColors = pixelCount;
var colorIndex;
var pixelIndex;

// limit the number of neighborhood comparisons per frame so we can run
// on displays with more pixels
// (128 == about 8 initial fps on 256 pixel display.  The algorithm speeds up considerably
// as it goes.)
var opsPerCycle = 128;  

// variables used to pass things to and from Pixelblaze mapPixels() functions
var tgtIndex;
var tgtColor;
var tgtX, tgtY;
var neighborCount;
var totalDist;

// trigger button to start new pattern
export function triggerNewCloud() {
  startPattern();
}


// generate a random palette, one color per pixel
function randomPalette() {
  var entriesPerColor = floor(pixelCount / numColors);
  var i = 0;
  
  for (var i = 0;i < pixelCount; i++) {
    palette[i] = array(3);
    
    if (0 == (i % entriesPerColor)) {
      r = random(1);
      g = random(1);
      b = random(1);
      r *= r;
      g *= g;
      b *= b;
    }
    
    palette[i][0] = r;
    palette[i][1] = g;
    palette[i][2] = b;
  }
}

// Stores the index of the palette color for
// each pixel and marks the pixel as available.
function initPixelBuffer() {
  for (i = 0; i < pixelCount; i++){
    pixels[i] = i;
    hasColor[i] = 0;
  }
}

// get and save x/y coordinates for each pixel index so we can use them
// in beforeRender
function saveXYCoords(index,x,y,z) {
  coords[index] = array(2);
  coords[index][0] = x;
  coords[index][1] = y;  
}

// shuffle color array
// (standard Fisher-Yates-Knuth shuffle)
function shuffle() {
  for (var i = pixelCount - 1; i > 0; i--) {
    var r = floor(random(i+1));
    var tmp  = palette[i];
    palette[i] = palette[r];
    palette[r] = tmp;
  }
}

// 50% true, 50% false
function coinFlip() {
  return random(1) >= 0.5;
}

function startPattern() {
  initPixelBuffer();
  randomPalette();   
  shuffle();  
  
  colorIndex = 0;
  pixelIndex = random(pixelCount);
  searchComplete = 1;
  minDist = 9999;
  bestPoint = -1; 
  neighborhoodCenter = 0;  
}

// initialization
mapPixels(saveXYCoords);
startPattern();

// calculate total color distance between specified color and the neighborhood surrounding the
// pixel at tgtIndex.
function colorDistance(index,x,y,z) {
  // is this pixel within our defined neighborhood, with a color to compare?
  if ((index == tgtIndex) || (!hasColor[index]) || hypot(tgtX- x,tgtY - y) > neighborhoodRadius) return;
  neighborCount++;

  c1 = palette[pixels[index]];
  totalDist += hypot3(c1[0] - tgtColor[0],c1[1] - tgtColor[1],c1[2] - tgtColor[2])
}

// returns average color distance between tgtColor and the neighborhood around the 
// specified pixel index.
function radialColorDistance(index)  {
  neighborCount = 0;
  totalDist = 0;
  tgtIndex = index;
  tgtX = coords[tgtIndex][0]; tgtY = coords[tgtIndex][1]; 
  mapPixels(colorDistance);
  
  // return avg distance, or very large distance if no neighbors
  return (neighborCount > 0) ? totalDist / neighborCount : 9999;
}

// point search runs incrementally, at "opsPerCycle" neighborhood comparisons per frame.
// (somewhere between 50 and 100 is a reasonable setting for most displays) 
// This slows things down early in the pattern generation cycle, but it allows us to 
// run on displays with any number of pixels (limited by available memory, of course!)
var searchComplete;
var minDist;
var bestPoint;
var neighborhoodCenter;
function getAvailablePoint(color) {
  // find the next available point
  
  tgtColor = color;

  for (i = 0; i < opsPerCycle; i++) {
    if (neighborhoodCenter < pixelCount) {
      // find the neighborhood with the closest color match
      if (!hasColor[neighborhoodCenter]) {
        cd = radialColorDistance(neighborhoodCenter);
        if (cd < minDist) {
          bestPoint = neighborhoodCenter;
          minDist = cd;
        }
      }
      neighborhoodCenter++;
    }
    else {
      searchComplete = 1;
      break;
    }
  }
  
  // return point index, or -1 if no points available.
  return bestPoint;
}

export function beforeRender(delta) {
  
  if (colorIndex < pixelCount) {
   k = (colorIndex == 0) ? random(pixelCount) : getAvailablePoint(palette[colorIndex]);
   if (!searchComplete) return;
   
   if (k >= 0) {
       pixelIndex = k;
       pixels[pixelIndex] = colorIndex;
       hasColor[pixelIndex] = 1;
       colorIndex++;
    }
    searchComplete = 0;
    bestPoint = -1;
    neighborhoodCenter = 0;
    minDist = 9999;    
  }
}

export function render(index) {
  c = palette[pixels[index]];
  if (hasColor[index]) rgb(c[0],c[1],c[2]);  
}

The whole, slightly slow (around a minute to complete) thing in action:

2 Likes

Wow, excellent work! Here it is running on a Fibonacci256:

3 Likes

Super pretty!! Just updated my post w/note on neighborhood size for fibonacci 256, too!

(EDIT: Actually, kind of hard to tell. Default neighborhood size looks pretty good too.)

1 Like

UPDATE: added a timer to auto redraw after a while (controls). Added a rainbow palette and stacked the deck.


This looks super cool! I had some ideas for this but its really cool to see a working version already! I’m totally borrowing on your excellent work, @zranger1!

This variant uses palettes. Though not quite an exact match with the every color initial idea, I think it’s a really nice look, especially with limited pixels. Also happens to simplify color distance math. The target color can be a random number without needing to remember a large array of colors.

It picks a random palette (borrowing a bunch from the Lux patterns), and you can click a button to randomize it.

I wanted to speed up finding closer color matches, so I added a step that calculates a map of neighbors. I noticed that the neighbor search was favoring lower pixel indexes when the distance was otherwise equal, on regular grids things tended to grow up and left (which is cool and maybe plant-like). I wanted to pick a random one if all else was equal to make it more like a slime mold that doesn’t favor a direction.

I add a neighbor mapping stage that finds the nearest numNeighbors neighbors, randomizing it a bit if numNeighbors is low compared to the minimum neighborhoodRadius (which can now be fairly large without impacting speed).

It does the mapping in the render call while showing the min neighbor radius around the pixel being mapped, along with any pixels that were at one point considered a close neighbor - that converges to a tight random circle limited by the numNeighbors setting. I borrow the pixels array to use as list of random pixels to map instead of iterating. It’s fast enough to be mostly limited by the LED data rate, but still takes a while for large pixel counts. I’m thinking about doing a few at a time via mapPixels instead, and display something more appealing during the mapping phase.

For drawing, it uses a similar trick as the closest neighbor mapping to find a number of color matches, and will randomize them if equal.

You can toss in multiple random seed pixels if you like, and it will re-seed if it can’t find any neighbors. That helps with some cases that can happen if you set numNeighbors low (the neighbor graph can create orphans) or have separate islands of pixels in your map further away than the neighborhoodRadius.

Pattern Source Code
// Slime mold palette
// 2024 - wizard
// Based heavily on:
// Rainbow Smoke (rainbowsmoke.hu/home) for Pixelblaze
// 2024 - ZRanger1
// and borrowing palettes from the Lux Lavalier

var numNeighbors = 8 //this is a key setting, smaller values create tighter patterns
var initialSeeds = 1
var neighborhoodRadius = 0.2; //big enough for an 8x8 grid to find adjacent pixels

// limit the number of neighborhood comparisons per frame so we can run
// on displays with more pixels
var opsPerCycle = 1024;
var matches = array(8) //consider at most this many neighborhoods while drawing

//store 2 indexes per element to save space
var neighborhoodMap = array(ceil(pixelCount * numNeighbors / 2))
var pixels = array(pixelCount) //keep track of what color has been chosen

var neighbors = array(numNeighbors) //used during neighborhood mapping for a pixel, later copied to neighborhoodMap


var lastMatch = matches.length-1
var lastNeighbor = numNeighbors - 1
var emptySlot = 0x7FFF + (0x7FFF>>16) //magic number for empty neighbors / matches
export var mapIndex, tgtIndex, tgtX, tgtY //which pixel is being mapped
var needsMapping = true
export var tgtColor, neighborCount, totalDist, searchComplete, neighborhoodCenter, pixelsDrawn
var redrawTimer
var redrawSeconds = 30
var autoRedraw = true

export function triggerRemap() {
  neighborhoodMap.mutate(() => emptySlot)
  //borrow the pixel array during mapping stage to store a list of indexes to map
  pixels.mutate((v, i) => i)
  pixels.sortBy(() => random(1) - .5) //shuffle
  needsMapping = true
  mapIndex = -1
}

triggerRemap()

function restartSearch() {
  searchComplete = false
  neighborhoodCenter = 0;
  tgtColor = random(1)
  matches.mutate(() => emptySlot)
}

//start with a bunch of seeds if you like
export function inputNumberNumSeeds(v) {
  initialSeeds = max(v,1)
}

//you can add seeds any time too
export function triggerSeedPixel() {
  if (pixelsDrawn >= pixelCount)
    return
  //try hard, but not forever, to find a free pixel to seed
  for (var i = 0; i < 100; i++) {
    var index = random(pixelCount)
    if (pixels[index] == -1) {
      pixels[index] = random(1)
      pixelsDrawn++
      return
    }
  }
}

//start a new drawing, clear the display and pick a new palette
export function triggerDraw() {
  if (needsMapping)
    return
  triggerRandomPalette()
  pixels.mutate(() => -1) //erase the pixels
  pixelsDrawn = 0
  //seed the first pixel(s)

  //TODO might look cool to always start at an edge or something

  for (i = 0; i < initialSeeds; i++)
    triggerSeedPixel()
  restartSearch()
  redrawTimer = 0
}

export function toggleAutoRedraw(v) {
  autoRedraw = v
}

export function inputNumberRedrawSeconds(v) {
  redrawSeconds = v
}

function loadTargetCoords(index, x, y) {
  if (index == tgtIndex) {
    tgtX = x
    tgtY = y
  }
}

//store a neighbor into the map, with 2 neighbors per array element
function setNeighborhoodMap(pixelIndex, neighborIndex, value) {
  var offset = floor((pixelIndex * numNeighbors + neighborIndex) / 2)
  var oldValue = neighborhoodMap[offset];
  if (neighborIndex & 1)
    neighborhoodMap[offset] = trunc(oldValue) + (value >> 16)
  else
    neighborhoodMap[offset] = frac(oldValue) + trunc(value)
}


// returns average color distance between tgtColor and the neighborhood around the
// specified pixel index, or -1 if the neighborhood is empty
function radialColorDistance(index) {
  neighborCount = 0;
  totalDist = 0;
  tgtIndex = index;

  //look through the neighbors for this index
  var offset = floor((index * numNeighbors) / 2)
  var value;
  for (var i = 0; i < numNeighbors; i++) {
    value = neighborhoodMap[offset];
    if (i & 1) {
      value = frac(value) << 16
      offset++
    } else {
      value = trunc(value)
    }
    if (value < pixelCount) { //filter out empty entries (0x7FFF)
      var color = pixels[value]
      if (color >= 0) {
        totalDist += abs(color - tgtColor)
        neighborCount++;
      }
    }
  }

  // return avg distance, or very large distance if no neighbors
  return (neighborCount > 0) ? totalDist / neighborCount : -1;
}

// find the next available point with a neighborhood with the closest color match
function getAvailablePoint() {
  var cd

  // point search can run incrementally, at "opsPerCycle" neighborhood comparisons per frame.
  for (i = 0; i < opsPerCycle; i++) {
    if (neighborhoodCenter < pixelCount) {
      //only consider pixels that don't yet have a color
      if (pixels[neighborhoodCenter] == -1) {
        cd = radialColorDistance(neighborhoodCenter)
        if (cd >= 0) { //skip empty neighborhood
          //store match distance in integer part and index in fraction to compact and keep data pair together during sort
          //add some random bits in between to prevent the index order from impacting sort for equal distances
          cd = (round(cd << 8) << 6) + (random(100) & 0x3f) + (neighborhoodCenter >> 16)
          
          //see if we should replace the worst match
          if (cd < matches[lastMatch]) {
            matches[lastMatch] = cd
            //sort matches from close to far
            matches.sort(); //very fast, basically a call to std::sort
          }
        }
      }
      neighborhoodCenter++;
    } else {
      searchComplete = true;
      break;
    }
  }

  // return point index from the best match, or -1 if no points available.
  if (matches[0] == emptySlot)
    return -1;
  return frac(matches[0]) << 16
}


export var render2D //dynamically switched at runtime

export function beforeRender(deltaMs) {
  var k
  if (needsMapping) {
    render2D = renderMapNeighborhood
    //save previous neighbors
    if (mapIndex != -1) {
      for (var i = 0; i < numNeighbors; i++) {
        var offset = floor(i / 2)
        setNeighborhoodMap(tgtIndex, i, frac(neighbors[i]) << 16)
      }
    }

    if (++mapIndex < pixelCount) {
      //instead of going through linearly, we have borrowed the pixels array to do a random scan
      tgtIndex = pixels[mapIndex]
      mapPixels(loadTargetCoords)
      neighbors.mutate(() => emptySlot)
    } else {
      needsMapping = false
      triggerDraw()
    }
  } else {
    render2D = renderDrawColors

    if (pixelsDrawn < pixelCount) {
      k = getAvailablePoint();
      if (!searchComplete) return;

      if (k >= 0) {
        pixels[k] = tgtColor;
        pixelsDrawn++;
      } else {
        //special case, search was complete but didn't find anything, yet we still have pixels to draw
        //maybe an island of pixels with no neighbors
        //try to make a new seed
        triggerSeedPixel()
      }
      restartSearch()
    } else {
      if (autoRedraw) {
        redrawTimer += deltaMs / 1000
        if (redrawTimer >= redrawSeconds) {
          triggerDraw();
        }
      }
    }
  }
}

function renderDrawColors(index, x, y) {
  var color = pixels[index]
  if (color > 0) {
    paint(color)
  }
}

function renderMapNeighborhood(index, x, y) {
  if (index == tgtIndex) {
    rgb(0, 0, 1)
    return;
  }

  var tgtDist = hypot(tgtX - x, tgtY - y)
  //show a sort of progress bar by filling the screen with gray
  hsv(0, 0, .1 * (y < mapIndex / pixelCount))

  //don't consider pixels that are too far to be neighbors
  if (tgtDist < neighborhoodRadius) {
    //try to store this dist to the local neighbors
    //boot the highest distance one if this is closer
    //easy if neighbors are sorted by distance, only need to compare one value, store, and re-sort

    //store as distance in integer part and index in fraction to compact and keep data pair together during sort
    //add some random bits in between to prevent the index order from impacting sort for equal distances
    tgtDist = (round(tgtDist << 8) << 6) + (random(100) & 0x3f) + (index >> 16)

    //see if we should replace the furthest neighbor
    if (tgtDist < neighbors[lastNeighbor]) {
      neighbors[lastNeighbor] = tgtDist
      //sort neighbors from close to far
      neighbors.sort(); //very fast, basically a call to std::sort
      rgb(.5, .5, 0) //was closer than existing neighbors but might still get kicked out
    } else {
      rgb(.3, .0, .0) //was checked, but never closer than existing neighbors
    }
  }
}

// From ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb

// Gradient palette "ib_jul01_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/xmas/tn/ib_jul01.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var ib_jul01 = [
  0.0, 0.761, 0.004, 0.004,
  0.369, 0.004, 0.114, 0.071,
  0.518, 0.224, 0.514, 0.11,
  1.0, 0.443, 0.004, 0.004,
];

// Gradient palette "es_vintage_57_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_57.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var es_vintage_57 = [
  0.0, 0.008, 0.004, 0.004,
  0.208, 0.071, 0.004, 0.0,
  0.408, 0.271, 0.114, 0.004,
  0.6, 0.655, 0.529, 0.039,
  1.0, 0.18, 0.22, 0.016,
];

// Gradient palette "es_vintage_01_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_01.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var es_vintage_01 = [
  0.0, 0.016, 0.004, 0.004,
  0.2, 0.063, 0.0, 0.004,
  0.298, 0.38, 0.408, 0.012,
  0.396, 1.0, 0.514, 0.075,
  0.498, 0.263, 0.035, 0.016,
  0.6, 0.063, 0.0, 0.004,
  0.898, 0.016, 0.004, 0.004,
  1.0, 0.016, 0.004, 0.004,
];

// Gradient palette "es_rivendell_15_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/rivendell/tn/es_rivendell_15.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var es_rivendell_15 = [
  0.0, 0.004, 0.055, 0.02,
  0.396, 0.063, 0.141, 0.055,
  0.647, 0.22, 0.267, 0.118,
  0.949, 0.588, 0.612, 0.388,
  1.0, 0.588, 0.612, 0.388,
];

// Gradient palette "rgi_15_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/rgi/tn/rgi_15.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var rgi_15 = [
  0.0, 0.016, 0.004, 0.122,
  0.122, 0.216, 0.004, 0.063,
  0.247, 0.773, 0.012, 0.027,
  0.373, 0.231, 0.008, 0.067,
  0.498, 0.024, 0.008, 0.133,
  0.624, 0.153, 0.024, 0.129,
  0.749, 0.439, 0.051, 0.125,
  0.875, 0.22, 0.035, 0.137,
  1.0, 0.086, 0.024, 0.149,
];

// Gradient palette "retro2_16_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/retro2/tn/retro2_16.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var retro2_16 = [
  0.0, 0.737, 0.529, 0.004,
  1.0, 0.18, 0.027, 0.004,
];

// Gradient palette "Analogous_1_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/red/tn/Analogous_1.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var Analogous_1 = [
  0.0, 0.012, 0.0, 1.0,
  0.247, 0.09, 0.0, 1.0,
  0.498, 0.263, 0.0, 1.0,
  0.749, 0.557, 0.0, 0.176,
  1.0, 1.0, 0.0, 0.0,
];

// Gradient palette "es_pinksplash_08_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_08.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var es_pinksplash_08 = [
  0.0, 0.494, 0.043, 1.0,
  0.498, 0.773, 0.004, 0.086,
  0.686, 0.824, 0.616, 0.675,
  0.867, 0.616, 0.012, 0.439,
  1.0, 0.616, 0.012, 0.439,
];

// Gradient palette "es_pinksplash_07_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_07.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var es_pinksplash_07 = [
  0.0, 0.898, 0.004, 0.004,
  0.239, 0.949, 0.016, 0.247,
  0.396, 1.0, 0.047, 1.0,
  0.498, 0.976, 0.318, 0.988,
  0.6, 1.0, 0.043, 0.922,
  0.757, 0.957, 0.02, 0.267,
  1.0, 0.91, 0.004, 0.02,
];

// Gradient palette "Coral_reef_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/other/tn/Coral_reef.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var Coral_reef = [
  0.0, 0.157, 0.78, 0.773,
  0.196, 0.039, 0.596, 0.608,
  0.376, 0.004, 0.435, 0.471,
  0.376, 0.169, 0.498, 0.635,
  0.545, 0.039, 0.286, 0.435,
  1.0, 0.004, 0.133, 0.278,
];

// Gradient palette "es_ocean_breeze_068_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_068.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var es_ocean_breeze_068 = [
  0.0, 0.392, 0.612, 0.6,
  0.2, 0.004, 0.388, 0.537,
  0.396, 0.004, 0.267, 0.329,
  0.408, 0.137, 0.557, 0.659,
  0.698, 0.0, 0.247, 0.459,
  1.0, 0.004, 0.039, 0.039,
];

// Gradient palette "es_ocean_breeze_036_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_036.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var es_ocean_breeze_036 = [
  0.0, 0.004, 0.024, 0.027,
  0.349, 0.004, 0.388, 0.435,
  0.6, 0.565, 0.82, 1.0,
  1.0, 0.0, 0.286, 0.322,
];

// Gradient palette "departure_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/mjf/tn/departure.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var departure = [
  0.0, 0.031, 0.012, 0.0,
  0.165, 0.09, 0.027, 0.0,
  0.247, 0.294, 0.149, 0.024,
  0.329, 0.663, 0.388, 0.149,
  0.416, 0.835, 0.663, 0.467,
  0.455, 1.0, 1.0, 1.0,
  0.541, 0.529, 1.0, 0.541,
  0.58, 0.086, 1.0, 0.094,
  0.667, 0.0, 1.0, 0.0,
  0.749, 0.0, 0.533, 0.0,
  0.831, 0.0, 0.216, 0.0,
  1.0, 0.0, 0.216, 0.0,
];

// Gradient palette "es_landscape_64_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_64.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var es_landscape_64 = [
  0.0, 0.0, 0.0, 0.0,
  0.145, 0.008, 0.098, 0.004,
  0.298, 0.059, 0.451, 0.02,
  0.498, 0.31, 0.835, 0.004,
  0.502, 0.494, 0.827, 0.184,
  0.51, 0.737, 0.82, 0.969,
  0.6, 0.565, 0.714, 0.804,
  0.8, 0.231, 0.459, 0.98,
  1.0, 0.004, 0.145, 0.753,
];

// Gradient palette "es_landscape_33_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_33.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var es_landscape_33 = [
  0.0, 0.004, 0.02, 0.0,
  0.075, 0.125, 0.09, 0.004,
  0.149, 0.631, 0.216, 0.004,
  0.247, 0.898, 0.565, 0.004,
  0.259, 0.153, 0.557, 0.29,
  1.0, 0.004, 0.016, 0.004,
];

// Gradient palette "rainbowsherbet_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/icecream/tn/rainbowsherbet.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var rainbowsherbet = [
  0.0, 1.0, 0.129, 0.016,
  0.169, 1.0, 0.267, 0.098,
  0.337, 1.0, 0.027, 0.098,
  0.498, 1.0, 0.322, 0.404,
  0.667, 1.0, 1.0, 0.949,
  0.82, 0.165, 1.0, 0.086,
  1.0, 0.341, 1.0, 0.255,
];

// Gradient palette "gr65_hult_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr65_hult.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var gr65_hult = [
  0.0, 0.969, 0.69, 0.969,
  0.188, 1.0, 0.533, 1.0,
  0.349, 0.863, 0.114, 0.886,
  0.627, 0.027, 0.322, 0.698,
  0.847, 0.004, 0.486, 0.427,
  1.0, 0.004, 0.486, 0.427,
];

// Gradient palette "gr64_hult_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr64_hult.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var gr64_hult = [
  0.0, 0.004, 0.486, 0.427,
  0.259, 0.004, 0.365, 0.31,
  0.408, 0.204, 0.255, 0.004,
  0.51, 0.451, 0.498, 0.004,
  0.588, 0.204, 0.255, 0.004,
  0.788, 0.004, 0.337, 0.282,
  0.937, 0.0, 0.216, 0.176,
  1.0, 0.0, 0.216, 0.176,
];

// Gradient palette "GMT_drywet_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gmt/tn/GMT_drywet.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var GMT_drywet = [
  0.0, 0.184, 0.118, 0.008,
  0.165, 0.835, 0.576, 0.094,
  0.329, 0.404, 0.859, 0.204,
  0.498, 0.012, 0.859, 0.812,
  0.667, 0.004, 0.188, 0.839,
  0.831, 0.004, 0.004, 0.435,
  1.0, 0.004, 0.027, 0.129,
];

// Gradient palette "ib15_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/general/tn/ib15.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var ib15 = [
  0.0, 0.443, 0.357, 0.576,
  0.282, 0.616, 0.345, 0.306,
  0.349, 0.816, 0.333, 0.129,
  0.42, 1.0, 0.114, 0.043,
  0.553, 0.537, 0.122, 0.153,
  1.0, 0.231, 0.129, 0.349,
];

// Gradient palette "Fuschia_7_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/fuschia/tn/Fuschia-7.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var Fuschia_7 = [
  0.0, 0.169, 0.012, 0.6,
  0.247, 0.392, 0.016, 0.404,
  0.498, 0.737, 0.02, 0.259,
  0.749, 0.631, 0.043, 0.451,
  1.0, 0.529, 0.078, 0.714,
];

// Gradient palette "es_emerald_dragon_08_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/emerald_dragon/tn/es_emerald_dragon_08.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var es_emerald_dragon_08 = [
  0.0, 0.38, 1.0, 0.004,
  0.396, 0.184, 0.522, 0.004,
  0.698, 0.051, 0.169, 0.004,
  1.0, 0.008, 0.039, 0.004,
];

// Gradient palette "lava_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/lava.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var lava = [
  0.0, 0.0, 0.0, 0.0,
  0.18, 0.071, 0.0, 0.0,
  0.376, 0.443, 0.0, 0.0,
  0.424, 0.557, 0.012, 0.004,
  0.467, 0.686, 0.067, 0.004,
  0.573, 0.835, 0.173, 0.008,
  0.682, 1.0, 0.322, 0.016,
  0.737, 1.0, 0.451, 0.016,
  0.792, 1.0, 0.612, 0.016,
  0.855, 1.0, 0.796, 0.016,
  0.918, 1.0, 1.0, 0.016,
  0.957, 1.0, 1.0, 0.278,
  1.0, 1.0, 1.0, 1.0,
];

// Gradient palette "fire_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/fire.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var fire = [
  0.0, 0.004, 0.004, 0.0,
  0.298, 0.125, 0.02, 0.0,
  0.573, 0.753, 0.094, 0.0,
  0.773, 0.863, 0.412, 0.02,
  0.941, 0.988, 1.0, 0.122,
  0.98, 0.988, 1.0, 0.435,
  1.0, 1.0, 1.0, 1.0,
];

// Gradient palette "Colorfull_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Colorfull.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var Colorfull = [
  0.0, 0.039, 0.333, 0.02,
  0.098, 0.114, 0.427, 0.071,
  0.235, 0.231, 0.541, 0.165,
  0.365, 0.325, 0.388, 0.204,
  0.416, 0.431, 0.259, 0.251,
  0.427, 0.482, 0.192, 0.255,
  0.443, 0.545, 0.137, 0.259,
  0.455, 0.753, 0.459, 0.384,
  0.486, 1.0, 1.0, 0.537,
  0.659, 0.392, 0.706, 0.608,
  1.0, 0.086, 0.475, 0.682,
];

// Gradient palette "Magenta_Evening_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Magenta_Evening.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var Magenta_Evening = [
  0.0, 0.278, 0.106, 0.153,
  0.122, 0.51, 0.043, 0.2,
  0.247, 0.835, 0.008, 0.251,
  0.275, 0.91, 0.004, 0.259,
  0.298, 0.988, 0.004, 0.271,
  0.424, 0.482, 0.008, 0.2,
  1.0, 0.18, 0.035, 0.137,
];

// Gradient palette "Pink_Purple_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Pink_Purple.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var Pink_Purple = [
  0.0, 0.075, 0.008, 0.153,
  0.098, 0.102, 0.016, 0.176,
  0.2, 0.129, 0.024, 0.204,
  0.298, 0.267, 0.243, 0.49,
  0.4, 0.463, 0.733, 0.941,
  0.427, 0.639, 0.843, 0.969,
  0.447, 0.851, 0.957, 1.0,
  0.478, 0.624, 0.584, 0.867,
  0.584, 0.443, 0.306, 0.737,
  0.718, 0.502, 0.224, 0.608,
  1.0, 0.573, 0.157, 0.482,
];

// Gradient palette "Sunset_Real_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Real.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var Sunset_Real = [
  0.0, 0.471, 0.0, 0.0,
  0.086, 0.702, 0.086, 0.0,
  0.2, 1.0, 0.408, 0.0,
  0.333, 0.655, 0.086, 0.071,
  0.529, 0.392, 0.0, 0.404,
  0.776, 0.063, 0.0, 0.51,
  1.0, 0.0, 0.0, 0.627,
];

// Gradient palette "es_autumn_19_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_19.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var es_autumn_19 = [
  0.0, 0.102, 0.004, 0.004,
  0.2, 0.263, 0.016, 0.004,
  0.329, 0.463, 0.055, 0.004,
  0.408, 0.537, 0.596, 0.204,
  0.439, 0.443, 0.255, 0.004,
  0.478, 0.522, 0.584, 0.231,
  0.486, 0.537, 0.596, 0.204,
  0.529, 0.443, 0.255, 0.004,
  0.557, 0.545, 0.604, 0.18,
  0.639, 0.443, 0.051, 0.004,
  0.8, 0.216, 0.012, 0.004,
  0.976, 0.067, 0.004, 0.004,
  1.0, 0.067, 0.004, 0.004,
];

// Gradient palette "BlacK_Blue_Magenta_White_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Blue_Magenta_White.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var BlacK_Blue_Magenta_White = [
  0.0, 0.0, 0.0, 0.0,
  0.165, 0.0, 0.0, 0.176,
  0.329, 0.0, 0.0, 1.0,
  0.498, 0.165, 0.0, 1.0,
  0.667, 1.0, 0.0, 1.0,
  0.831, 1.0, 0.216, 1.0,
  1.0, 1.0, 1.0, 1.0,
];

// Gradient palette "BlacK_Magenta_Red_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Magenta_Red.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var BlacK_Magenta_Red = [
  0.0, 0.0, 0.0, 0.0,
  0.247, 0.165, 0.0, 0.176,
  0.498, 1.0, 0.0, 1.0,
  0.749, 1.0, 0.0, 0.176,
  1.0, 1.0, 0.0, 0.0,
];

// Gradient palette "BlacK_Red_Magenta_Yellow_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Red_Magenta_Yellow.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var BlacK_Red_Magenta_Yellow = [
  0.0, 0.0, 0.0, 0.0,
  0.165, 0.165, 0.0, 0.0,
  0.329, 1.0, 0.0, 0.0,
  0.498, 1.0, 0.0, 0.176,
  0.667, 1.0, 0.0, 1.0,
  0.831, 1.0, 0.216, 0.176,
  1.0, 1.0, 1.0, 0.0,
];

// Gradient palette "Blue_Cyan_Yellow_gp", originally from
// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/Blue_Cyan_Yellow.png.index.html
// converted for FastLED with gammas (2.6, 2.2, 2.5)
var Blue_Cyan_Yellow = [
  0.0, 0.0, 0.0, 1.0,
  0.247, 0.0, 0.216, 1.0,
  0.498, 0.0, 1.0, 1.0,
  0.749, 0.165, 1.0, 0.176,
  1.0, 1.0, 1.0, 0.0,
];

var rainbow = [
0, .2, 0, 0,
.1, 1, 0, 0,
.3, 1, 0.5, 0,
.4, 0.5, 1, 0,
.5, 0, 1, 0, 
.55, 0, 1, .5,
.6, 0, .5, 1, 
.65, 0, 0, 1,
.7, .5, 0, 1,
.8, 1, 0, 1, 
.9, .2, .1, .5,
1, .2, 0, 0
]

// Single array of defined cpt-city color palettes.
// This will let us programmatically choose one based on
// a number, rather than having to activate each explicitly
// by name every time.
//
// This list of color palettes acts as a "playlist"; you can
// add or delete, or re-arrange as you wish.
// Count of how many cpt-city gradients are defined:
var allPalettes = [
  rainbow,
  rainbow,
  rainbow,
  rainbow,
  rainbow,
  ib_jul01,
  es_vintage_57,
  es_vintage_01,
  es_rivendell_15,
  rgi_15,
  retro2_16,
  Analogous_1,
  es_pinksplash_08,
  es_pinksplash_07,
  Coral_reef,
  es_ocean_breeze_068,
  es_ocean_breeze_036,
  departure,
  es_landscape_64,
  es_landscape_33,
  rainbowsherbet,
  gr65_hult,
  gr64_hult,
  GMT_drywet,
  ib15,
  Fuschia_7,
  es_emerald_dragon_08,
  lava,
  fire,
  Colorfull,
  Magenta_Evening,
  Pink_Purple,
  Sunset_Real,
  es_autumn_19,
  BlacK_Blue_Magenta_White,
  BlacK_Magenta_Red,
  BlacK_Red_Magenta_Yellow,
  Blue_Cyan_Yellow,
];

var currentPalette

export function triggerRandomPalette() {
  currentPalette = floor(random(allPalettes.length))
  setPalette(allPalettes[currentPalette]);
}

export function showNumberPalette() {
  return currentPalette
}

2 Likes

Love this! One of my favorite things about this forum has always been the way we play with and improve each other’s ideas. A few of the things I think are coolest about this:

  • use of paint and palettes greatly speeds up color comparison vs hypot3(), and cuts down color storage size.
  • saved neighborhood map! I thought about doing this, pretty much exactly this way with 2 neighbors per element, but was scared off by the potential storage cost. Glad to be wrong!
  • dynamic renderer swapping. Great visualization during the mapping stage!
1 Like

Wow, brilliant!

I’m busy with Mother’s Day activities today, but I’ll get a video of this running on the real thing tomorrow.

1 Like

Here it is running on a Fibonacci256 HDR, quite quickly! :star_struck:

2 Likes

Lux Lavalier & Heart pendants:

3 Likes

My shrine wall of Fibonacci & PixelBlaze:

5 Likes

Love the shrine wall!

1 Like