Merge pull request #3729 from DedeHai/harmonic-random-palette-generator
added function to generate random palette based on harmonic color theory
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
| @@ -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; | ||||
| @@ -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;} | ||||
| @@ -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); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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"]); | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -848,6 +848,7 @@ Swap: <select id="xw${i}" name="XW${i}"> | ||||
| 			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> | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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(); | ||||
|   | ||||
| @@ -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" | ||||
| @@ -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); | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Blaž Kristan
					Blaž Kristan