Skip to content

Commit

Permalink
Merge pull request #3729 from DedeHai/harmonic-random-palette-generator
Browse files Browse the repository at this point in the history
added function to generate random palette based on harmonic color theory
  • Loading branch information
blazoncek committed Feb 6, 2024
2 parents 8ab621b + 41e51bb commit 0ab2d18
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 29 deletions.
3 changes: 2 additions & 1 deletion wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,8 @@ typedef struct Segment {
// perhaps this should be per segment, not static
static CRGBPalette16 _randomPalette; // actual random palette
static CRGBPalette16 _newRandomPalette; // target random palette
static unsigned long _lastPaletteChange; // last random palette change time in millis()
static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000
static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF
#ifndef WLED_DISABLE_MODE_BLEND
static bool _modeBlend; // mode/effect blending semaphore
#endif
Expand Down
42 changes: 22 additions & 20 deletions wled00/FX_fcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for t
uint16_t Segment::maxWidth = DEFAULT_LED_COUNT;
uint16_t Segment::maxHeight = 1;

CRGBPalette16 Segment::_randomPalette = CRGBPalette16(DEFAULT_COLOR);
CRGBPalette16 Segment::_newRandomPalette = CRGBPalette16(DEFAULT_COLOR);
unsigned long Segment::_lastPaletteChange = 0; // perhaps it should be per segment
CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR);
uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment
uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only)

#ifndef WLED_DISABLE_MODE_BLEND
bool Segment::_modeBlend = false;
Expand Down Expand Up @@ -220,20 +221,9 @@ CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint
switch (pal) {
case 0: //default palette. Exceptions for specific effects above
targetPalette = PartyColors_p; break;
case 1: {//periodically replace palette with a random one
unsigned long timeSinceLastChange = millis() - _lastPaletteChange;
if (timeSinceLastChange > randomPaletteChangeTime * 1000U) {
_randomPalette = _newRandomPalette;
_newRandomPalette = CRGBPalette16(
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)));
_lastPaletteChange = millis();
handleRandomPalette(); // do a 1st pass of blend
}
targetPalette = _randomPalette;
break;}
case 1: //randomly generated palette
targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette()
break;
case 2: {//primary color only
CRGB prim = gamma32(colors[0]);
targetPalette = CRGBPalette16(prim); break;}
Expand Down Expand Up @@ -462,10 +452,22 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u
return targetPalette;
}

// relies on WS2812FX::service() to call it max every 8ms or more (MIN_SHOW_DELAY)
// relies on WS2812FX::service() to call it for each frame
void Segment::handleRandomPalette() {
// just do a blend; if the palettes are identical it will just compare 48 bytes (same as _randomPalette == _newRandomPalette)
// this will slowly blend _newRandomPalette into _randomPalette every 15ms or 8ms (depending on MIN_SHOW_DELAY)
// is it time to generate a new palette?
if ((millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) {
_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
_lastPaletteChange = millis()/1000U;
_lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately
}

// if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls)
if (strip.paletteFade) {
// assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less)
// in reality there need to be 255 blends to fully blend two entirely different palettes
if ((millis() & 0xFFFF) - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update
_lastPaletteBlend = millis();
}
nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48);
}

Expand Down
2 changes: 2 additions & 0 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
strip.setTransition(fadeTransition ? transitionDelayDefault : 0);
CJSON(strip.paletteFade, light_tr["pal"]);
CJSON(randomPaletteChangeTime, light_tr[F("rpc")]);
CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]);

JsonObject light_nl = light["nl"];
CJSON(nightlightMode, light_nl["mode"]);
Expand Down Expand Up @@ -872,6 +873,7 @@ void serializeConfig() {
light_tr["dur"] = transitionDelayDefault / 100;
light_tr["pal"] = strip.paletteFade;
light_tr[F("rpc")] = randomPaletteChangeTime;
light_tr[F("hrp")] = useHarmonicRandomPalette;

JsonObject light_nl = light.createNestedObject("nl");
light_nl["mode"] = nightlightMode;
Expand Down
109 changes: 109 additions & 0 deletions wled00/colors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,115 @@ void setRandomColor(byte* rgb)
colorHStoRGB(lastRandomIndex*256,255,rgb);
}

/*
* generates a random palette based on harmonic color theory
* takes a base palette as the input, it will choose one color of the base palette and keep it
*/
CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette)
{
CHSV palettecolors[4]; //array of colors for the new palette
uint8_t keepcolorposition = random8(4); //color position of current random palette to keep
palettecolors[keepcolorposition] = rgb2hsv_approximate(basepalette.entries[keepcolorposition*5]); //read one of the base colors of the current palette
palettecolors[keepcolorposition].hue += random8(10)-5; // +/- 5 randomness of base color
//generate 4 saturation and brightness value numbers
//only one saturation is allowed to be below 200 creating mostly vibrant colors
//only one brightness value number is allowed below 200, creating mostly bright palettes

for (int i = 0; i < 3; i++) { //generate three high values
palettecolors[i].saturation = random8(200,255);
palettecolors[i].value = random8(220,255);
}
//allow one to be lower
palettecolors[3].saturation = random8(20,255);
palettecolors[3].value = random8(80,255);

//shuffle the arrays
for (int i = 3; i > 0; i--) {
std::swap(palettecolors[i].saturation, palettecolors[random8(i + 1)].saturation);
std::swap(palettecolors[i].value, palettecolors[random8(i + 1)].value);
}

//now generate three new hues based off of the hue of the chosen current color
uint8_t basehue = palettecolors[keepcolorposition].hue;
uint8_t harmonics[3]; //hues that are harmonic but still a little random
uint8_t type = random8(5); //choose a harmony type

switch (type) {
case 0: // analogous
harmonics[0] = basehue + random8(30, 50);
harmonics[1] = basehue + random8(10, 30);
harmonics[2] = basehue - random8(10, 30);
break;

case 1: // triadic
harmonics[0] = basehue + 113 + random8(15);
harmonics[1] = basehue + 233 + random8(15);
harmonics[2] = basehue -7 + random8(15);
break;

case 2: // split-complementary
harmonics[0] = basehue + 145 + random8(10);
harmonics[1] = basehue + 205 + random8(10);
harmonics[2] = basehue - 5 + random8(10);
break;

case 3: // square
harmonics[0] = basehue + 85 + random8(10);
harmonics[1] = basehue + 175 + random8(10);
harmonics[2] = basehue + 265 + random8(10);
break;

case 4: // tetradic
harmonics[0] = basehue + 80 + random8(20);
harmonics[1] = basehue + 170 + random8(20);
harmonics[2] = basehue + random8(30)-15;
break;
}

if (random8() < 128) {
//50:50 chance of shuffling hues or keep the color order
for (int i = 2; i > 0; i--) {
std::swap(harmonics[i], harmonics[random8(i + 1)]);
}
}

//now set the hues
int j = 0;
for (int i = 0; i < 4; i++) {
if (i==keepcolorposition) continue; //skip the base color
palettecolors[i].hue = harmonics[j];
j++;
}

bool makepastelpalette = false;
if (random8() < 25) { //~10% chance of desaturated 'pastel' colors
makepastelpalette = true;
}

//apply saturation & gamma correction
CRGB RGBpalettecolors[4];
for (int i = 0; i < 4; i++) {
if (makepastelpalette && palettecolors[i].saturation > 180) {
palettecolors[i].saturation -= 160; //desaturate all four colors
}
RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB
RGBpalettecolors[i] = gamma32(((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU); //strip alpha from CRGB
}

return CRGBPalette16(RGBpalettecolors[0],
RGBpalettecolors[1],
RGBpalettecolors[2],
RGBpalettecolors[3]);
}

CRGBPalette16 generateRandomPalette(void) //generate fully random palette
{
return CRGBPalette16(CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)),
CHSV(random8(), random8(160, 255), random8(128, 255)));
}

void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb
{
float h = ((float)hue)/65535.0f;
Expand Down
1 change: 1 addition & 0 deletions wled00/data/settings_leds.htm
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,7 @@ <h3>Transitions</h3>
Palette transitions: <input type="checkbox" name="PF"><br>
</span>
<i>Random Cycle</i> Palette Time: <input name="TP" type="number" class="m" min="1" max="255"> s<br>
Use harmonic <i>Random Cycle</i> Palette: <input type="checkbox" name="TH"><br>
<h3>Timed light</h3>
Default Duration: <input name="TL" type="number" class="m" min="1" max="255" required> min<br>
Default Target brightness: <input name="TB" type="number" class="m" min="0" max="255" required><br>
Expand Down
2 changes: 2 additions & 0 deletions wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class NeoGammaWLEDMethod {
uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false);
uint32_t color_add(uint32_t,uint32_t, bool fast=false);
uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false);
CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette);
CRGBPalette16 generateRandomPalette(void);
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb
void colorKtoRGB(uint16_t kelvin, byte* rgb);
Expand Down
1 change: 1 addition & 0 deletions wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
strip.paletteFade = request->hasArg(F("PF"));
t = request->arg(F("TP")).toInt();
randomPaletteChangeTime = MIN(255,MAX(1,t));
useHarmonicRandomPalette = request->hasArg(F("TH"));

nightlightTargetBri = request->arg(F("TB")).toInt();
t = request->arg(F("TL")).toInt();
Expand Down
20 changes: 12 additions & 8 deletions wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument<PSRAM_Allocator>;
#define PSRAMDynamicJsonDocument DynamicJsonDocument
#endif

#define FASTLED_INTERNAL //remove annoying pragma messages
#define USE_GET_MILLISECOND_TIMER
#include "FastLED.h"
#include "const.h"
#include "fcn_declare.h"
#include "NodeStruct.h"
Expand Down Expand Up @@ -539,15 +542,16 @@ WLED_GLOBAL bool wasConnected _INIT(false);
WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same

// transitions
WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color
WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending
WLED_GLOBAL bool transitionActive _INIT(false);
WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration
WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json)
WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color
WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending
WLED_GLOBAL bool transitionActive _INIT(false);
WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration
WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json)
WLED_GLOBAL unsigned long transitionStartTime;
WLED_GLOBAL float tperLast _INIT(0.0f); // crossfade transition progress, 0.0f - 1.0f
WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt")
WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s)
WLED_GLOBAL float tperLast _INIT(0.0f); // crossfade transition progress, 0.0f - 1.0f
WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt")
WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s)
WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random

// nightlight
WLED_GLOBAL bool nightlightActive _INIT(false);
Expand Down
1 change: 1 addition & 0 deletions wled00/xml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('v',SET_F("TD"),transitionDelayDefault);
sappend('c',SET_F("PF"),strip.paletteFade);
sappend('v',SET_F("TP"),randomPaletteChangeTime);
sappend('c',SET_F("TH"),useHarmonicRandomPalette);
sappend('v',SET_F("BF"),briMultiplier);
sappend('v',SET_F("TB"),nightlightTargetBri);
sappend('v',SET_F("TL"),nightlightDelayMinsDefault);
Expand Down

0 comments on commit 0ab2d18

Please sign in to comment.