diff --git a/CHANGELOG.md b/CHANGELOG.md index ba305fbb..f81e5cb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ### Builds after release 0.12.0 +#### Build 2111300 + +- Added CCT and white balance correction support (PR #2285) +- Unified UI slider style +- Added LED settings config template upload + #### Build 2111220 - Fixed preset cycle not working from preset called by UI diff --git a/platformio.ini b/platformio.ini index 9bc8bedf..d45f52be 100644 --- a/platformio.ini +++ b/platformio.ini @@ -435,6 +435,13 @@ board_build.ldscript = ${common.ldscript_2m512k} build_flags = ${common.build_flags_esp8266} -D LEDPIN=3 -D BTNPIN=2 -D IRPIN=5 -D WLED_MAX_BUTTONS=3 lib_deps = ${esp8266.lib_deps} +[env:athom7w] +board = esp_wroom_02 +platform = ${common.platform_wled_default} +board_build.ldscript = ${common.ldscript_2m512k} +build_flags = ${common.build_flags_esp8266} -D WLED_MAX_CCT_BLEND=0 -D BTNPIN=-1 -D IRPIN=-1 -D WLED_DISABLE_INFRARED +lib_deps = ${esp8266.lib_deps} + # ------------------------------------------------------------------------------ # travis test board configurations # ------------------------------------------------------------------------------ diff --git a/wled00/FX.h b/wled00/FX.h index 7b3bc69a..cfae0d6e 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -165,12 +165,12 @@ #define FX_MODE_FIRE_FLICKER 45 #define FX_MODE_GRADIENT 46 #define FX_MODE_LOADING 47 -#define FX_MODE_POLICE 48 +#define FX_MODE_POLICE 48 // candidate for removal (after below three) #define FX_MODE_POLICE_ALL 49 // candidate for removal #define FX_MODE_TWO_DOTS 50 -#define FX_MODE_TWO_AREAS 51 +#define FX_MODE_TWO_AREAS 51 // candidate for removal #define FX_MODE_RUNNING_DUAL 52 -#define FX_MODE_HALLOWEEN 53 +#define FX_MODE_HALLOWEEN 53 // candidate for removal #define FX_MODE_TRICOLOR_CHASE 54 #define FX_MODE_TRICOLOR_WIPE 55 #define FX_MODE_TRICOLOR_FADE 56 @@ -231,7 +231,7 @@ #define FX_MODE_CHUNCHUN 111 #define FX_MODE_DANCING_SHADOWS 112 #define FX_MODE_WASHING_MACHINE 113 -#define FX_MODE_CANDY_CANE 114 +#define FX_MODE_CANDY_CANE 114 // candidate for removal #define FX_MODE_BLENDS 115 #define FX_MODE_TV_SIMULATOR 116 #define FX_MODE_DYNAMIC_SMOOTH 117 @@ -247,36 +247,44 @@ class WS2812FX { // segment parameters public: - typedef struct Segment { // 30 (33 in memory?) bytes + typedef struct Segment { // 30 (32 in memory) bytes uint16_t start; uint16_t stop; //segment invalid if stop == 0 uint16_t offset; - uint8_t speed; - uint8_t intensity; - uint8_t palette; - uint8_t mode; - uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected - uint8_t grouping, spacing; - uint8_t opacity; + uint8_t speed; + uint8_t intensity; + uint8_t palette; + uint8_t mode; + uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected + uint8_t grouping, spacing; + uint8_t opacity; uint32_t colors[NUM_COLORS]; - uint8_t cct; //0==2000K, 255==10160K + uint8_t cct; //0==1900K, 255==10091K char *name; bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false; if (c == colors[slot]) return false; - ColorTransition::startTransition(opacity, colors[slot], instance->_transitionDur, segn, slot); + uint8_t b = (slot == 1) ? cct : opacity; + ColorTransition::startTransition(b, colors[slot], instance->_transitionDur, segn, slot); colors[slot] = c; return true; } + void setCCT(uint16_t k, uint8_t segn) { + if (segn >= MAX_NUM_SEGMENTS) return; + if (k > 255) { //kelvin value, convert to 0-255 + if (k < 1900) k = 1900; + if (k > 10091) k = 10091; + k = (k - 1900) >> 5; + } + if (cct == k) return; + ColorTransition::startTransition(cct, colors[1], instance->_transitionDur, segn, 1); + cct = k; + } void setOpacity(uint8_t o, uint8_t segn) { if (segn >= MAX_NUM_SEGMENTS) return; if (opacity == o) return; ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0); opacity = o; } - /*uint8_t actualOpacity() { //respects On/Off state - if (!getOption(SEG_OPTION_ON)) return 0; - return opacity; - }*/ void setOption(uint8_t n, bool val, uint8_t segn = 255) { bool prevOn = false; @@ -446,7 +454,7 @@ class WS2812FX { if (t.segment == s) //this is an active transition on the same segment+color { bool wasTurningOff = (oldBri == 0); - t.briOld = t.currentBri(wasTurningOff); + t.briOld = t.currentBri(wasTurningOff, slot); t.colorOld = t.currentColor(oldCol); } else { t.briOld = oldBri; @@ -478,11 +486,15 @@ class WS2812FX { uint32_t currentColor(uint32_t colorNew) { return instance->color_blend(colorOld, colorNew, progress(true), true); } - uint8_t currentBri(bool turningOff = false) { + uint8_t currentBri(bool turningOff = false, uint8_t slot = 0) { uint8_t segn = segment & 0x3F; if (segn >= MAX_NUM_SEGMENTS) return 0; uint8_t briNew = instance->_segments[segn].opacity; - if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0; + if (slot == 0) { + if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0; + } else { //transition slot 1 brightness for CCT transition + briNew = instance->_segments[segn].cct; + } uint32_t prog = progress() + 1; return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16; } @@ -653,6 +665,7 @@ class WS2812FX { applyToAllSelected = true, setEffectConfig(uint8_t m, uint8_t s, uint8_t i, uint8_t p), checkSegmentAlignment(void), + hasCCTBus(void), // return true if the strip is being sent pixel updates isUpdating(void); @@ -661,6 +674,7 @@ class WS2812FX { paletteFade = 0, paletteBlend = 0, milliampsPerLed = 55, + cctBlending = 0, getBrightness(void), getMode(void), getSpeed(void), diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 6c28b3e7..27a5c3bb 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -137,13 +137,16 @@ void WS2812FX::service() { if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen _virtualSegmentLength = SEGMENT.virtualLength(); _bri_t = SEGMENT.opacity; _colors_t[0] = SEGMENT.colors[0]; _colors_t[1] = SEGMENT.colors[1]; _colors_t[2] = SEGMENT.colors[2]; + uint8_t _cct_t = SEGMENT.cct; if (!IS_SEGMENT_ON) _bri_t = 0; for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) { if ((transitions[t].segment & 0x3F) != i) continue; uint8_t slot = transitions[t].segment >> 6; if (slot == 0) _bri_t = transitions[t].currentBri(); + if (slot == 1) _cct_t = transitions[t].currentBri(false, 1); _colors_t[slot] = transitions[t].currentColor(SEGMENT.colors[slot]); } + if (!cctFromRgb || correctWB) busses.setSegmentCCT(_cct_t, correctWB); for (uint8_t c = 0; c < 3; c++) _colors_t[c] = gamma32(_colors_t[c]); handle_palette(); delay = (this->*_mode[SEGMENT.mode])(); //effect function @@ -154,6 +157,7 @@ void WS2812FX::service() { } } _virtualSegmentLength = 0; + busses.setSegmentCCT(-1); if(doShow) { yield(); show(); @@ -189,21 +193,6 @@ void WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) uint16_t realIndex = realPixelIndex(i); uint16_t len = SEGMENT.length(); - // determine if we can do white balance and accurate W calc - // NOTE & TODO: does not work correctly with custom mapping if map spans different strips - int16_t cct = -1; - for (uint8_t b = 0; b < busses.getNumBusses(); b++) { - Bus *bus = busses.getBus(b); - if (bus == nullptr || !bus->containsPixel(realIndex)) continue; - //if (bus == nullptr || bus->getStart()getStart()+bus->getLength()>realIndex) continue; - uint8_t busType = bus->getType(); - if (allowCCT - || busType == TYPE_ANALOG_2CH - || busType == TYPE_ANALOG_5CH) { - if (cct<0) cct = SEGMENT.cct; - } - } - //color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments) if (_bri_t < 255) { r = scale8(r, _bri_t); @@ -563,6 +552,20 @@ uint16_t WS2812FX::getLengthPhysical(void) { return len; } +bool WS2812FX::hasCCTBus(void) { + if (cctFromRgb && !correctWB) return false; + for (uint8_t b = 0; b < busses.getNumBusses(); b++) { + Bus *bus = busses.getBus(b); + if (bus == nullptr || bus->getLength()==0) break; + switch (bus->getType()) { + case TYPE_ANALOG_5CH: + case TYPE_ANALOG_2CH: + return true; + } + } + return false; +} + void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing) { if (n >= MAX_NUM_SEGMENTS) return; Segment& seg = _segments[n]; @@ -617,7 +620,7 @@ void WS2812FX::resetSegments() { _segments[0].setOption(SEG_OPTION_SELECTED, 1); _segments[0].setOption(SEG_OPTION_ON, 1); _segments[0].opacity = 255; - _segments[0].cct = 128; + _segments[0].cct = 127; for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++) { @@ -625,7 +628,7 @@ void WS2812FX::resetSegments() { _segments[i].grouping = 1; _segments[i].setOption(SEG_OPTION_ON, 1); _segments[i].opacity = 255; - _segments[i].cct = 128; + _segments[i].cct = 127; _segments[i].speed = DEFAULT_SPEED; _segments[i].intensity = DEFAULT_INTENSITY; _segment_runtimes[i].reset(); @@ -1171,4 +1174,9 @@ uint32_t WS2812FX::gamma32(uint32_t color) return RGBW32(r, g, b, w); } -WS2812FX* WS2812FX::instance = nullptr; \ No newline at end of file +WS2812FX* WS2812FX::instance = nullptr; + +//Bus static member definition, would belong in bus_manager.cpp +int16_t Bus::_cct = -1; +uint8_t Bus::_cctBlend = 0; +uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL; \ No newline at end of file diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index 2e5b9b17..097ef2ac 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -73,17 +73,27 @@ void onAlexaChange(EspalexaDevice* dev) if (espalexaDevice->getColorMode() == EspalexaColorMode::ct) //shade of white { uint16_t ct = espalexaDevice->getCt(); - if (strip.isRgbw) - { + if (!ct) return; + uint16_t k = 1000000 / ct; //mireds to kelvin + + if (strip.hasCCTBus()) { + uint8_t segid = strip.getMainSegmentId(); + WS2812FX::Segment& seg = strip.getSegment(segid); + uint8_t cctPrev = seg.cct; + seg.setCCT(k, segid); + if (seg.cct != cctPrev) effectChanged = true; //send UDP + col[0]= 0; col[1]= 0; col[2]= 0; col[3]= 255; + } else if (strip.isRgbw) { switch (ct) { //these values empirically look good on RGBW case 199: col[0]=255; col[1]=255; col[2]=255; col[3]=255; break; case 234: col[0]=127; col[1]=127; col[2]=127; col[3]=255; break; case 284: col[0]= 0; col[1]= 0; col[2]= 0; col[3]=255; break; case 350: col[0]=130; col[1]= 90; col[2]= 0; col[3]=255; break; case 383: col[0]=255; col[1]=153; col[2]= 0; col[3]=255; break; + default : colorKtoRGB(k, col); } } else { - colorCTtoRGB(ct, col); + colorKtoRGB(k, col); } } else { uint32_t color = espalexaDevice->getRGB(); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index de507d5f..a9cb5d13 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -74,13 +74,12 @@ struct BusConfig { } }; -//parent class of BusDigital and BusPwm +//parent class of BusDigital, BusPwm, and BusNetwork class Bus { public: - Bus(uint8_t type, uint16_t start, uint8_t aw) { + Bus(uint8_t type, uint16_t start) { _type = type; _start = start; - _autoWhiteMode = isRgbw(_type) ? aw : RGBW_MODE_MANUAL_ONLY; }; virtual ~Bus() {} //throw the bus under the bus @@ -89,7 +88,6 @@ class Bus { virtual bool canShow() { return true; } virtual void setStatusPixel(uint32_t c) {} virtual void setPixelColor(uint16_t pix, uint32_t c) {} - virtual void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) {} virtual uint32_t getPixelColor(uint16_t pix) { return 0; } virtual void setBrightness(uint8_t b) {} virtual void cleanup() {} @@ -98,7 +96,6 @@ class Bus { virtual void setColorOrder() {} virtual uint8_t getColorOrder() { return COL_ORDER_RGB; } virtual uint8_t skippedLeds() { return 0; } - inline uint8_t getAutoWhiteMode() { return _autoWhiteMode; } inline uint16_t getStart() { return _start; } inline void setStart(uint16_t start) { _start = start; } inline uint8_t getType() { return _type; } @@ -112,6 +109,19 @@ class Bus { if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; return false; } + static void setCCT(uint16_t cct) { + _cct = cct; + } + static void setCCTBlend(uint8_t b) { + if (b > 100) b = 100; + _cctBlend = (b * 127) / 100; + //compile-time limiter for hardware that can't power both white channels at max + #ifdef WLED_MAX_CCT_BLEND + if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND; + #endif + } + inline static void setAutoWhiteMode(uint8_t m) { if (m < 4) _autoWhiteMode = m; } + inline static uint8_t getAutoWhiteMode() { return _autoWhiteMode; } bool reversed = false; @@ -122,17 +132,20 @@ class Bus { uint16_t _len = 1; bool _valid = false; bool _needsRefresh = false; - uint8_t _autoWhiteMode = 0; + static uint8_t _autoWhiteMode; + static int16_t _cct; + static uint8_t _cctBlend; uint32_t autoWhiteCalc(uint32_t c) { if (_autoWhiteMode == RGBW_MODE_MANUAL_ONLY) return c; + uint8_t w = W(c); + //ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0) + if (w > 0 && _autoWhiteMode == RGBW_MODE_DUAL) return c; uint8_t r = R(c); uint8_t g = G(c); uint8_t b = B(c); - uint8_t w = W(c); - // ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0) - if (!(w > 0 && _autoWhiteMode == RGBW_MODE_DUAL)) w = r < g ? (r < b ? r : b) : (g < b ? g : b); - if (_autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } // subtract w in ACCURATE mode + w = r < g ? (r < b ? r : b) : (g < b ? g : b); + if (_autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode return RGBW32(r, g, b, w); } }; @@ -181,15 +194,18 @@ class BusDigital : public Bus { PolyBus::setBrightness(_busPtr, _iType, b); } - void setStatusPixel(uint32_t c) { + //If LEDs are skipped, it is possible to use the first as a status LED. + //TODO only show if no new show due in the next 50ms + void setStatusPixel(uint32_t c) { if (_skip && canShow()) { - for (uint8_t i=0; i<_skip; i--) PolyBus::setPixelColor(_busPtr, _iType, i, c, _colorOrder); + PolyBus::setPixelColor(_busPtr, _iType, 0, c, _colorOrder); PolyBus::show(_busPtr, _iType); } } void setPixelColor(uint16_t pix, uint32_t c) { - if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); + if (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814) c = autoWhiteCalc(c); + if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT if (reversed) pix = _len - pix -1; else pix += _skip; PolyBus::setPixelColor(_busPtr, _iType, pix, c, _colorOrder); @@ -323,20 +339,49 @@ class BusPwm : public Bus { void setPixelColor(uint16_t pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel - if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); + if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c); + if (_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { + c = colorBalanceFromKelvin(_cct, c); //color correction from CCT + } uint8_t r = R(c); uint8_t g = G(c); uint8_t b = B(c); uint8_t w = W(c); + uint8_t cct = 0; //0 - full warm white, 255 - full cold white + if (_cct > -1) { + if (_cct >= 1900) cct = (_cct - 1900) >> 5; + else if (_cct < 256) cct = _cct; + } else { + cct = (approximateKelvinFromRGB(c) - 1900) >> 5; + } + + //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) + uint8_t ww, cw; + if (cct < _cctBlend) ww = 255; + else ww = ((255-cct) * 255) / (255 - _cctBlend); + + if ((255-cct) < _cctBlend) cw = 255; + else cw = (cct * 255) / (255 - _cctBlend); + + ww = (w * ww) / 255; //brightness scaling + cw = (w * cw) / 255; switch (_type) { - case TYPE_ANALOG_1CH: //one channel (white), use highest RGBW value - _data[0] = max(r, max(g, max(b, w))); break; + case TYPE_ANALOG_1CH: //one channel (white), relies on auto white calculation + _data[0] = w; + break; case TYPE_ANALOG_2CH: //warm white + cold white + _data[1] = cw; + _data[0] = ww; + break; + case TYPE_ANALOG_5CH: //RGB + warm white + cold white + _data[4] = cw; + w = ww; + case TYPE_ANALOG_4CH: //RGBW + _data[3] = w; case TYPE_ANALOG_3CH: //standard dumb RGB - case TYPE_ANALOG_4CH: //standard dumb RGBW - case TYPE_ANALOG_5CH: //we'll want the white handling from 2CH here + RGB - _data[0] = r; _data[1] = g; _data[2] = b; _data[3] = w; _data[4] = w; break; + _data[0] = r; _data[1] = g; _data[2] = b; + break; } } @@ -440,7 +485,8 @@ class BusNetwork : public Bus { void setPixelColor(uint16_t pix, uint32_t c) { if (!_valid || pix >= _len) return; - if (getAutoWhiteMode() != RGBW_MODE_MANUAL_ONLY) c = autoWhiteCalc(c); + if (_rgbw) c = autoWhiteCalc(c); + if (_cct >= 1900) c = colorBalanceFromKelvin(_cct, c); //color correction from CCT uint16_t offset = pix * _UDPchannels; _data[offset] = R(c); _data[offset+1] = G(c); @@ -448,11 +494,6 @@ class BusNetwork : public Bus { if (_rgbw) _data[offset+3] = W(c); } - void setPixelColor(uint16_t pix, uint32_t c, uint8_t cct) { - c = colorBalanceFromKelvin(2000+(cct<<5), c); // color correction from CCT - setPixelColor(pix, c); - } - uint32_t getPixelColor(uint16_t pix) { if (!_valid || pix >= _len) return 0; uint16_t offset = pix * _UDPchannels; @@ -567,9 +608,11 @@ class BusManager { } } - void setStatusPixel(uint32_t c) { - for (uint8_t i = 0; i < numBusses; i++) busses[i]->setStatusPixel(c); - } + void setStatusPixel(uint32_t c) { + for (uint8_t i = 0; i < numBusses; i++) { + busses[i]->setStatusPixel(c); + } + } void setPixelColor(uint16_t pix, uint32_t c, int16_t cct=-1) { for (uint8_t i = 0; i < numBusses; i++) { @@ -587,6 +630,15 @@ class BusManager { } } + void setSegmentCCT(int16_t cct, bool allowWBCorrection = false) { + if (cct > 255) cct = 255; + if (cct >= 0) { + //if white balance correction allowed, save as kelvin value instead of 0-255 + if (allowWBCorrection) cct = 1900 + (cct << 5); + } else cct = -1; + Bus::setCCT(cct); + } + uint32_t getPixelColor(uint16_t pix) { for (uint8_t i = 0; i < numBusses; i++) { Bus* b = busses[i]; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index b4aff9ff..033d2429 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -83,8 +83,11 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); - uint8_t rgbwMode = hw_led[F("rgbwm")] | RGBW_MODE_DUAL; // use global setting (legacy) - CJSON(allowCCT, hw_led["cct"]); + Bus::setAutoWhiteMode(hw_led[F("rgbwm")] | Bus::getAutoWhiteMode()); + CJSON(correctWB, hw_led["cct"]); + CJSON(cctFromRgb, hw_led[F("cr")]); + CJSON(strip.cctBlending, hw_led[F("cb")]); + Bus::setCCTBlend(strip.cctBlending); JsonArray ins = hw_led["ins"]; @@ -405,6 +408,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (i > 14) break; CJSON(DMXFixtureMap[i],dmx_fixmap[i]); } + + CJSON(e131ProxyUniverse, dmx[F("e131proxy")]); #endif DEBUG_PRINTLN(F("Starting usermod config.")); @@ -536,7 +541,9 @@ void serializeConfig() { hw_led[F("total")] = strip.getLengthTotal(); //no longer read, but provided for compatibility on downgrade hw_led[F("maxpwr")] = strip.ablMilliampsMax; hw_led[F("ledma")] = strip.milliampsPerLed; - hw_led["cct"] = allowCCT; + hw_led["cct"] = correctWB; + hw_led[F("cr")] = cctFromRgb; + hw_led[F("cb")] = strip.cctBlending; JsonArray hw_led_ins = hw_led.createNestedArray("ins"); @@ -754,8 +761,11 @@ void serializeConfig() { dmx[F("start-led")] = DMXStartLED; JsonArray dmx_fixmap = dmx.createNestedArray(F("fixmap")); - for (byte i = 0; i < 15; i++) + for (byte i = 0; i < 15; i++) { dmx_fixmap.add(DMXFixtureMap[i]); + } + + dmx[F("e131proxy")] = e131ProxyUniverse; #endif JsonObject usermods_settings = doc.createNestedObject("um"); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 0377a198..5fea9cb5 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -73,7 +73,7 @@ void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc g = round(288.1221695283 * pow((temp - 60), -0.0755148492)); b = 255; } - //g += 15; //mod by Aircoookie, a bit less accurate but visibly less pinkish + //g += 12; //mod by Aircoookie, a bit less accurate but visibly less pinkish rgb[0] = (uint8_t) constrain(r, 0, 255); rgb[1] = (uint8_t) constrain(g, 0, 255); rgb[2] = (uint8_t) constrain(b, 0, 255); @@ -245,23 +245,58 @@ void colorRGBtoRGBW(byte* rgb) //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_M } */ -// adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance) -void colorBalanceFromKelvin(uint16_t kelvin, byte *rgb) -{ - byte rgbw[4] = {0,0,0,0}; - colorKtoRGB(kelvin, rgbw); // convert Kelvin to RGB - rgb[0] = ((uint16_t) rgbw[0] * rgb[0]) / 255; // correct R - rgb[1] = ((uint16_t) rgbw[1] * rgb[1]) / 255; // correct G - rgb[2] = ((uint16_t) rgbw[2] * rgb[2]) / 255; // correct B -} +byte correctionRGB[4] = {0,0,0,0}; +uint16_t lastKelvin = 0; +// adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance) uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb) { - byte rgbw[4] = {0,0,0,0}; - colorKtoRGB(kelvin, rgbw); // convert Kelvin to RGB - rgbw[0] = ((uint16_t) rgbw[0] * R(rgb)) / 255; // correct R - rgbw[1] = ((uint16_t) rgbw[1] * G(rgb)) / 255; // correct G - rgbw[2] = ((uint16_t) rgbw[2] * B(rgb)) / 255; // correct B - rgbw[3] = W(rgb); + //remember so that slow colorKtoRGB() doesn't have to run for every setPixelColor() + if (lastKelvin != kelvin) colorKtoRGB(kelvin, correctionRGB); // convert Kelvin to RGB + lastKelvin = kelvin; + byte rgbw[4]; + rgbw[0] = ((uint16_t) correctionRGB[0] * R(rgb)) /255; // correct R + rgbw[1] = ((uint16_t) correctionRGB[1] * G(rgb)) /255; // correct G + rgbw[2] = ((uint16_t) correctionRGB[2] * B(rgb)) /255; // correct B + rgbw[3] = W(rgb); return colorFromRgbw(rgbw); } + +//approximates a Kelvin color temperature from an RGB color. +//this does no check for the "whiteness" of the color, +//so should be used combined with a saturation check (as done by auto-white) +//values from http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html (10deg) +//equation spreadsheet at https://bit.ly/30RkHaN +//accuracy +-50K from 1900K up to 8000K +//minimum returned: 1900K, maximum returned: 10091K (range of 8192) +uint16_t approximateKelvinFromRGB(uint32_t rgb) { + //if not either red or blue is 255, color is dimmed. Scale up + uint8_t r = R(rgb), b = B(rgb); + if (r == b) return 6550; //red == blue at about 6600K (also can't go further if both R and B are 0) + + if (r > b) { + //scale blue up as if red was at 255 + uint16_t scale = 0xFFFF / r; //get scale factor (range 257-65535) + b = ((uint16_t)b * scale) >> 8; + //For all temps K<6600 R is bigger than B (for full bri colors R=255) + //-> Use 9 linear approximations for blackbody radiation blue values from 2000-6600K (blue is always 0 below 2000K) + if (b < 33) return 1900 + b *6; + if (b < 72) return 2100 + (b-33) *10; + if (b < 101) return 2492 + (b-72) *14; + if (b < 132) return 2900 + (b-101) *16; + if (b < 159) return 3398 + (b-132) *19; + if (b < 186) return 3906 + (b-159) *22; + if (b < 210) return 4500 + (b-186) *25; + if (b < 230) return 5100 + (b-210) *30; + return 5700 + (b-230) *34; + } else { + //scale red up as if blue was at 255 + uint16_t scale = 0xFFFF / b; //get scale factor (range 257-65535) + r = ((uint16_t)r * scale) >> 8; + //For all temps K>6600 B is bigger than R (for full bri colors B=255) + //-> Use 2 linear approximations for blackbody radiation red values from 6600-10091K (blue is always 0 below 2000K) + if (r > 225) return 6600 + (254-r) *50; + uint16_t k = 8080 + (225-r) *86; + return (k > 10091) ? 10091 : k; + } +} diff --git a/wled00/const.h b/wled00/const.h index d7be4a03..d338e930 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -124,7 +124,7 @@ // - 0b010 (dec. 32-47) analog (PWM) // - 0b011 (dec. 48-63) digital (data + clock / SPI) // - 0b100 (dec. 64-79) unused/reserved -// - 0b101 (dec. 80-95) digital (data + clock / SPI) +// - 0b101 (dec. 80-95) virtual network busses // - 0b110 (dec. 96-111) unused/reserved // - 0b111 (dec. 112-127) unused/reserved //bit 7 is reserved and set to 0 diff --git a/wled00/data/index.css b/wled00/data/index.css index 15fc95b3..73ade1e0 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -513,12 +513,24 @@ img { .sliderdisplay { content:''; position: absolute; + /* top: 13px; left: 8px; right: 8px; height: 4px; + */ + top: 12.5px; bottom: 12.5px; + left: 13px; right: 13px; background: var(--c-4); border-radius: 16px; pointer-events: none; z-index: -1; + --bg: var(--c-f); +} + +#rwrap .sliderdisplay { --bg: #f00; } +#gwrap .sliderdisplay { --bg: #0f0; } +#bwrap .sliderdisplay { --bg: #00f; } +#wbal .sliderdisplay, #kwrap .sliderdisplay { + background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #cbdbff); } #rwrap .sliderdisplay, #gwrap .sliderdisplay, @@ -530,12 +542,15 @@ img { left: 0; right: 0; /*border: 1px solid var(--c-b);*/ } +/* #rwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #f00); } #gwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #0f0); } #bwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #00f); } +*/ #wwrap .sliderdisplay { background: linear-gradient(90deg, #000 0%, #fff); } +/* #wbal .sliderdisplay { background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #d4e0ff); } - +*/ .sliderbubble { width: 24px; position: relative; @@ -568,6 +583,7 @@ input[type=range] { background-color: transparent; cursor: pointer; } +/* #rwrap input[type=range], #gwrap input[type=range], #bwrap input[type=range], @@ -576,6 +592,7 @@ input[type=range] { width: 252px; margin: 0; } +*/ input[type=range]:focus { outline: none; } diff --git a/wled00/data/index.htm b/wled00/data/index.htm index cbaa72e5..1b1068ca 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -46,22 +46,35 @@
+
+
+ +
+

+
+
+
+ +
+
+
+

RGB color

- +

- +

- +

diff --git a/wled00/data/index.js b/wled00/data/index.js index 73ac81d4..d4ed33fe 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -42,25 +42,12 @@ var hol = [ var cpick = new iro.ColorPicker("#picker", { width: 260, wheelLightness: false, - wheelAngle: 90, - layout: [ - { - component: iro.ui.Wheel, - options: {} - }, - { - component: iro.ui.Slider, - options: { sliderType: 'value' } - }/*, - { - component: iro.ui.Slider, - options: { - sliderType: 'kelvin', - minTemperature: 2000, - maxTemperature: 10160 - } - }*/ - ] + wheelAngle: 270, + wheelDirection: "clockwise", + layout: [{ + component: iro.ui.Wheel, + options: {} + }] }); function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();} @@ -78,6 +65,8 @@ function applyCfg() var ccfg = cfg.comp.colors; gId('hexw').style.display = ccfg.hex ? "block":"none"; gId('picker').style.display = ccfg.picker ? "block":"none"; + gId('vwrap').style.display = ccfg.picker ? "block":"none"; + gId('kwrap').style.display = ccfg.picker ? "block":"none"; gId('rgbwrap').style.display = ccfg.rgb ? "block":"none"; gId('qcs-w').style.display = ccfg.quick ? "block":"none"; var l = cfg.comp.labels; @@ -204,8 +193,7 @@ function onLoad() if (window.location.protocol == "file:") { loc = true; locip = localStorage.getItem('locIp'); - if (!locip) - { + if (!locip) { locip = prompt("File Mode. Please enter WLED IP!"); localStorage.setItem('locIp', locip); } @@ -247,6 +235,7 @@ function onLoad() cpick.on("input:end", function() { setColor(1); }); + cpick.on("color:change", updatePSliders); pmtLS = localStorage.getItem('wledPmt'); // Load initial data @@ -992,6 +981,7 @@ function loadNodes() }); } +//update the 'sliderdisplay' background div of a slider for a visual indication of slider position function updateTrail(e) { if (e==null) return; @@ -1005,12 +995,14 @@ function updateTrail(e) if (b) b.innerHTML = e.value; } +//rangetouch slider function function toggleBubble(e) { var b = e.target.parentNode.parentNode.getElementsByTagName('output')[0]; b.classList.toggle('sliderbubbleshow'); } +//updates segment length upon input of segment values function updateLen(s) { if (!gId(`seg${s}s`)) return; @@ -1036,6 +1028,7 @@ function updateLen(s) gId(`seg${s}len`).innerHTML = out; } +//updates background color of currently selected preset function updatePA() { var ps = gEBCN("pres"); @@ -1081,10 +1074,10 @@ function updateUI() gId('wwrap').style.display = (isRgbw) ? "block":"none"; gId("wbal").style.display = (cct) ? "block":"none"; + gId('kwrap').style.display = (lastinfo.leds.cct) ? "none":"block"; updatePA(); - updateHex(); - updateRgb(); + updatePSliders(); } function updateSelectedPalette() @@ -1997,8 +1990,7 @@ function selectSlot(b) cd[csel].classList.add('xxs-w'); cpick.color.set(cd[csel].style.backgroundColor); gId('sliderW').value = whites[csel]; - updateHex(); - updateRgb(); + updatePSliders(); } var lasth = 0; @@ -2016,21 +2008,32 @@ function pC(col) setColor(0); } -function updateRgb() -{ +function updatePSliders() { + //update RGB sliders var col = cpick.color.rgb; gId('sliderR').value = col.r; gId('sliderG').value = col.g; gId('sliderB').value = col.b; -} -function updateHex() -{ - var str = cpick.color.hexString; - str = str.substring(1); + //update hex field + var str = cpick.color.hexString.substring(1); var w = whites[csel]; if (w > 0) str += w.toString(16); gId('hexc').value = str; + gId('hexcnf').style.backgroundColor = "var(--c-3)"; + + //update value slider + var v = gId('sliderV'); + v.value = cpick.color.value; + //background color as if color had full value + var hsv = {"h":cpick.color.hue,"s":cpick.color.saturation,"v":100}; + var c = iro.Color.hsvToRgb(hsv); + var cs = 'rgb('+c.r+','+c.g+','+c.b+')'; + v.parentNode.getElementsByClassName('sliderdisplay')[0].style.setProperty('--bg',cs); + updateTrail(v); + + //update Kelvin slider + gId('sliderK').value = cpick.color.kelvin; } function hexEnter() @@ -2051,16 +2054,25 @@ function fromHex() setColor(2); } +function fromV() +{ + cpick.color.setChannel('hsv', 'v', d.getElementById('sliderV').value); +} + +function fromK() +{ + cpick.color.set({ kelvin: d.getElementById('sliderK').value }); +} + function fromRgb() { var r = gId('sliderR').value; var g = gId('sliderG').value; var b = gId('sliderB').value; cpick.color.set(`rgb(${r},${g},${b})`); - setColor(0); } -// sets color from picker: 0=all, 1=leaving picker/HSV, 2=ignore white channel +//sr 0: from RGB sliders, 1: from picker, 2: from hex function setColor(sr) { var cd = gId('csl').children; // color slots @@ -2074,8 +2086,12 @@ function setColor(sr) } else if (csel == 2) { obj = {"seg": {"col": [[],[],[col.r, col.g, col.b, whites[csel]]]}}; } - updateHex(); - updateRgb(); + requestJson(obj); +} + +function setBalance(b) +{ + var obj = {"seg": {"cct": parseInt(b)}}; requestJson(obj); } diff --git a/wled00/data/iro.js b/wled00/data/iro.js index f459e417..3d63d041 100644 --- a/wled00/data/iro.js +++ b/wled00/data/iro.js @@ -1,7 +1,7 @@ /*! - * iro.js v5.3.1 - * 2016-2020 James Daniel + * iro.js v5.5.2 + * 2016-2021 James Daniel * Licensed under MPL 2.0 * github.com/jaames/iro.js */ -!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(t=t||self).iro=n()}(this,function(){"use strict";var k,s,n,i,o,m={},M=[],r=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|^--/i;function j(t,n){for(var i in n)t[i]=n[i];return t}function b(t){var n=t.parentNode;n&&n.removeChild(t)}function d(t,n,i){var r,e,u,o,l=arguments;if(n=j({},n),3=r/i?u=n:e=n}return n},function(t,n,i){n&&g(t.prototype,n),i&&g(t,i)}(l,[{key:"hsv",get:function(){var t=this.$;return{h:t.h,s:t.s,v:t.v}},set:function(t){var n=this.$;if(t=p({},n,t),this.onChange){var i={h:!1,v:!1,s:!1,a:!1};for(var r in n)i[r]=t[r]!=n[r];this.$=t,(i.h||i.s||i.v||i.a)&&this.onChange(this,i)}else this.$=t}},{key:"hsva",get:function(){return p({},this.$)},set:function(t){this.hsv=t}},{key:"hue",get:function(){return this.$.h},set:function(t){this.hsv={h:t}}},{key:"saturation",get:function(){return this.$.s},set:function(t){this.hsv={s:t}}},{key:"value",get:function(){return this.$.v},set:function(t){this.hsv={v:t}}},{key:"alpha",get:function(){return this.$.a},set:function(t){this.hsv=p({},this.hsv,{a:t})}},{key:"kelvin",get:function(){return l.rgbToKelvin(this.rgb)},set:function(t){this.rgb=l.kelvinToRgb(t)}},{key:"red",get:function(){return this.rgb.r},set:function(t){this.rgb=p({},this.rgb,{r:t})}},{key:"green",get:function(){return this.rgb.g},set:function(t){this.rgb=p({},this.rgb,{g:t})}},{key:"blue",get:function(){return this.rgb.b},set:function(t){this.rgb=p({},this.rgb,{b:t})}},{key:"rgb",get:function(){var t=l.hsvToRgb(this.$),n=t.r,i=t.g,r=t.b;return{r:U(n),g:U(i),b:U(r)}},set:function(t){this.hsv=p({},l.rgbToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"rgba",get:function(){return p({},this.rgb,{a:this.alpha})},set:function(t){this.rgb=t}},{key:"hsl",get:function(){var t=l.hsvToHsl(this.$),n=t.h,i=t.s,r=t.l;return{h:U(n),s:U(i),l:U(r)}},set:function(t){this.hsv=p({},l.hslToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"hsla",get:function(){return p({},this.hsl,{a:this.alpha})},set:function(t){this.hsl=t}},{key:"rgbString",get:function(){var t=this.rgb;return"rgb("+t.r+", "+t.g+", "+t.b+")"},set:function(t){var n,i,r,e,u=1;if((n=P.exec(t))?(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255)):(n=z.exec(t))&&(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255),u=K(n[4],1)),!n)throw new Error("Invalid rgb string");this.rgb={r:i,g:r,b:e,a:u}}},{key:"rgbaString",get:function(){var t=this.rgba;return"rgba("+t.r+", "+t.g+", "+t.b+", "+t.a+")"},set:function(t){this.rgbString=t}},{key:"hexString",get:function(){var t=this.rgb;return"#"+V(t.r)+V(t.g)+V(t.b)},set:function(t){var n,i,r,e,u=255;if((n=C.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3])):(n=D.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3]),u=17*Q(n[4])):(n=F.exec(t))?(i=Q(n[1]),r=Q(n[2]),e=Q(n[3])):(n=G.exec(t))&&(i=Q(n[1]),r=Q(n[2]),e=Q(n[3]),u=Q(n[4])),!n)throw new Error("Invalid hex string");this.rgb={r:i,g:r,b:e,a:u/255}}},{key:"hex8String",get:function(){var t=this.rgba;return"#"+V(t.r)+V(t.g)+V(t.b)+V(q(255*t.a))},set:function(t){this.hexString=t}},{key:"hslString",get:function(){var t=this.hsl;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%)"},set:function(t){var n,i,r,e,u=1;if((n=H.exec(t))?(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100)):(n=$.exec(t))&&(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100),u=K(n[4],1)),!n)throw new Error("Invalid hsl string");this.hsl={h:i,s:r,l:e,a:u}}},{key:"hslaString",get:function(){var t=this.hsla;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%, "+t.a+")"},set:function(t){this.hslString=t}}]),l}();function Z(t){var n,i=t.width,r=t.sliderSize,e=t.borderWidth,u=t.handleRadius,o=t.padding,l=t.sliderShape,s="horizontal"===t.layoutDirection;return r=null!=(n=r)?n:2*o+2*u+2*e,"circle"===l?{handleStart:t.padding+t.handleRadius,handleRange:i-2*o-2*u-2*e,width:i,height:i,cx:i/2,cy:i/2,radius:i/2-e/2}:{handleStart:r/2,handleRange:i-r,radius:r/2,x:0,y:0,width:s?r:i,height:s?i:r}}function tt(t,n){var i=Z(t),r=i.width,e=i.height,u=i.handleRange,o=i.handleStart,l="horizontal"===t.layoutDirection,s=l?r/2:e/2,c=o+function(t,n){var i=n.hsva,r=n.rgb;switch(t.sliderType){case"red":return r.r/2.55;case"green":return r.g/2.55;case"blue":return r.b/2.55;case"alpha":return 100*i.a;case"kelvin":var e=t.minTemperature,u=t.maxTemperature-e,o=(n.kelvin-e)/u*100;return Math.max(0,Math.min(o,100));case"hue":return i.h/=3.6;case"saturation":return i.s;case"value":default:return i.v}}(t,n)/100*u;return l&&(c=-1*c+u+2*o),{x:l?s:c,y:l?c:s}}function nt(t){var n=t.width/2;return{width:t.width,radius:n-t.borderWidth,cx:n,cy:n}}function it(t,n,i){var r=t.wheelAngle,e=t.wheelDirection;return((n=!i&&"clockwise"===e||i&&"anticlockwise"===e?(i?180:360)-(r-n):r+n)%360+360)%360}function rt(t,n,i){var r=nt(t),e=r.cx,u=r.cy,o=t.width/2-t.padding-t.handleRadius-t.borderWidth;n=e-n,i=u-i;var l=it(t,Math.atan2(-i,-n)*(180/Math.PI)),s=Math.min(Math.sqrt(n*n+i*i),o);return{h:Math.round(l),s:Math.round(100/o*s)}}function et(t){var n=t.width,i=t.boxHeight;return{width:n,height:null!=i?i:n,radius:t.padding+t.handleRadius}}function ut(t,n,i){var r=et(t),e=r.width,u=r.height,o=r.radius,l=(n-o)/(e-2*o)*100,s=(i-o)/(u-2*o)*100;return{s:Math.max(0,Math.min(l,100)),v:Math.max(0,Math.min(100-s,100))}}function ot(t){X=X||document.getElementsByTagName("base");var n=window.navigator.userAgent,i=/^((?!chrome|android).)*safari/i.test(n),r=/iPhone|iPod|iPad/i.test(n),e=window.location;return(i||r)&&0=r/i?u=n:e=n}return n},function(t,n,i){n&&g(t.prototype,n),i&&g(t,i)}(l,[{key:"hsv",get:function(){var t=this.$;return{h:t.h,s:t.s,v:t.v}},set:function(t){var n=this.$;if(t=b({},n,t),this.onChange){var i={h:!1,v:!1,s:!1,a:!1};for(var r in n)i[r]=t[r]!=n[r];this.$=t,(i.h||i.s||i.v||i.a)&&this.onChange(this,i)}else this.$=t}},{key:"hsva",get:function(){return b({},this.$)},set:function(t){this.hsv=t}},{key:"hue",get:function(){return this.$.h},set:function(t){this.hsv={h:t}}},{key:"saturation",get:function(){return this.$.s},set:function(t){this.hsv={s:t}}},{key:"value",get:function(){return this.$.v},set:function(t){this.hsv={v:t}}},{key:"alpha",get:function(){return this.$.a},set:function(t){this.hsv=b({},this.hsv,{a:t})}},{key:"kelvin",get:function(){return l.rgbToKelvin(this.rgb)},set:function(t){this.rgb=l.kelvinToRgb(t)}},{key:"red",get:function(){return this.rgb.r},set:function(t){this.rgb=b({},this.rgb,{r:t})}},{key:"green",get:function(){return this.rgb.g},set:function(t){this.rgb=b({},this.rgb,{g:t})}},{key:"blue",get:function(){return this.rgb.b},set:function(t){this.rgb=b({},this.rgb,{b:t})}},{key:"rgb",get:function(){var t=l.hsvToRgb(this.$),n=t.r,i=t.g,r=t.b;return{r:G(n),g:G(i),b:G(r)}},set:function(t){this.hsv=b({},l.rgbToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"rgba",get:function(){return b({},this.rgb,{a:this.alpha})},set:function(t){this.rgb=t}},{key:"hsl",get:function(){var t=l.hsvToHsl(this.$),n=t.h,i=t.s,r=t.l;return{h:G(n),s:G(i),l:G(r)}},set:function(t){this.hsv=b({},l.hslToHsv(t),{a:void 0===t.a?1:t.a})}},{key:"hsla",get:function(){return b({},this.hsl,{a:this.alpha})},set:function(t){this.hsl=t}},{key:"rgbString",get:function(){var t=this.rgb;return"rgb("+t.r+", "+t.g+", "+t.b+")"},set:function(t){var n,i,r,e,u=1;if((n=_.exec(t))?(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255)):(n=H.exec(t))&&(i=K(n[1],255),r=K(n[2],255),e=K(n[3],255),u=K(n[4],1)),!n)throw new Error("Invalid rgb string");this.rgb={r:i,g:r,b:e,a:u}}},{key:"rgbaString",get:function(){var t=this.rgba;return"rgba("+t.r+", "+t.g+", "+t.b+", "+t.a+")"},set:function(t){this.rgbString=t}},{key:"hexString",get:function(){var t=this.rgb;return"#"+U(t.r)+U(t.g)+U(t.b)},set:function(t){var n,i,r,e,u=255;if((n=D.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3])):(n=F.exec(t))?(i=17*Q(n[1]),r=17*Q(n[2]),e=17*Q(n[3]),u=17*Q(n[4])):(n=L.exec(t))?(i=Q(n[1]),r=Q(n[2]),e=Q(n[3])):(n=B.exec(t))&&(i=Q(n[1]),r=Q(n[2]),e=Q(n[3]),u=Q(n[4])),!n)throw new Error("Invalid hex string");this.rgb={r:i,g:r,b:e,a:u/255}}},{key:"hex8String",get:function(){var t=this.rgba;return"#"+U(t.r)+U(t.g)+U(t.b)+U(Z(255*t.a))},set:function(t){this.hexString=t}},{key:"hslString",get:function(){var t=this.hsl;return"hsl("+t.h+", "+t.s+"%, "+t.l+"%)"},set:function(t){var n,i,r,e,u=1;if((n=P.exec(t))?(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100)):(n=$.exec(t))&&(i=K(n[1],360),r=K(n[2],100),e=K(n[3],100),u=K(n[4],1)),!n)throw new Error("Invalid hsl string");this.hsl={h:i,s:r,l:e,a:u}}},{key:"hslaString",get:function(){var t=this.hsla;return"hsla("+t.h+", "+t.s+"%, "+t.l+"%, "+t.a+")"},set:function(t){this.hslString=t}}]),l}();function X(t){var n,i=t.width,r=t.sliderSize,e=t.borderWidth,u=t.handleRadius,o=t.padding,l=t.sliderShape,s="horizontal"===t.layoutDirection;return r=null!=(n=r)?n:2*o+2*u,"circle"===l?{handleStart:t.padding+t.handleRadius,handleRange:i-2*o-2*u,width:i,height:i,cx:i/2,cy:i/2,radius:i/2-e/2}:{handleStart:r/2,handleRange:i-r,radius:r/2,x:0,y:0,width:s?r:i,height:s?i:r}}function Y(t,n){var i=X(t),r=i.width,e=i.height,u=i.handleRange,o=i.handleStart,l="horizontal"===t.layoutDirection,s=l?r/2:e/2,c=o+function(t,n){var i=n.hsva,r=n.rgb;switch(t.sliderType){case"red":return r.r/2.55;case"green":return r.g/2.55;case"blue":return r.b/2.55;case"alpha":return 100*i.a;case"kelvin":var e=t.minTemperature,u=t.maxTemperature-e,o=(n.kelvin-e)/u*100;return Math.max(0,Math.min(o,100));case"hue":return i.h/=3.6;case"saturation":return i.s;case"value":default:return i.v}}(t,n)/100*u;return l&&(c=-1*c+u+2*o),{x:l?s:c,y:l?c:s}}var tt,nt=2*Math.PI,it=function(t,n){return(t%n+n)%n},rt=function(t,n){return Math.sqrt(t*t+n*n)};function et(t){return t.width/2-t.padding-t.handleRadius-t.borderWidth}function ut(t){var n=t.width/2;return{width:t.width,radius:n-t.borderWidth,cx:n,cy:n}}function ot(t,n,i){var r=t.wheelAngle,e=t.wheelDirection;return i&&"clockwise"===e?n=r+n:"clockwise"===e?n=360-r+n:i&&"anticlockwise"===e?n=r+180-n:"anticlockwise"===e&&(n=r-n),it(n,360)}function lt(t,n,i){var r=ut(t),e=r.cx,u=r.cy,o=et(t);n=e-n,i=u-i;var l=ot(t,Math.atan2(-i,-n)*(360/nt)),s=Math.min(rt(n,i),o);return{h:Math.round(l),s:Math.round(100/o*s)}}function st(t){var n=t.width,i=t.boxHeight;return{width:n,height:null!=i?i:n,radius:t.padding+t.handleRadius}}function ct(t,n,i){var r=st(t),e=r.width,u=r.height,o=r.radius,l=(n-o)/(e-2*o)*100,s=(i-o)/(u-2*o)*100;return{s:Math.max(0,Math.min(l,100)),v:Math.max(0,Math.min(100-s,100))}}function at(t,n,i,r){for(var e=0;e - - +
diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index c5eac450..2ff3f5d3 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -306,10 +306,11 @@ ${i+1}: - + - + + @@ -334,8 +335,7 @@ ${i+1}:

Reversed:

Skip 1st LED:
-

Off Refresh:  
-

Auto-calculate white channel from RGB:
 
+

Off Refresh:
`; f.insertAdjacentHTML("beforeend", cn); } @@ -402,9 +402,9 @@ ${i+1}: } if (!o.files) { - alert("This browser doesn't seem to support the `files` property of file inputs."); + alert("This browser doesn't support the `files` property of file inputs."); } else if (!o.files[0]) { - alert("Please select a JSON file before clicking 'Apply'"); + alert("Please select a JSON file first!"); } else { f = o.files[0]; fr = new FileReader(); @@ -538,7 +538,7 @@ ${i+1}:

Use Gamma correction for color: (strongly recommended)
Use Gamma correction for brightness: (not recommended)

- Brightness factor: % + Brightness factor: %%

Transitions

Crossfade:
Transition Time: ms
@@ -553,6 +553,19 @@ ${i+1}: +

White management

+ White Balance correction:
+ + Auto-calculate white channel from RGB:
+ +
+ Calculate CCT from RGB:
+ CCT additive blending: %%

Advanced

Palette blending: