Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added function to generate random palette based on harmonic color theory #3729

Merged
merged 11 commits into from
Feb 6, 2024
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
blazoncek marked this conversation as resolved.
Show resolved Hide resolved
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"
DedeHai marked this conversation as resolved.
Show resolved Hide resolved
#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
Loading