From 434ba3ffc7ac76b727fa0225bb27396c905fad80 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 28 Aug 2024 22:25:28 +0200 Subject: [PATCH 01/10] Added FX to copy a segment in 1D or 2D - copies the source segment - brightness of segment is relative to source segment - optionally shifts the color hue - invert, transpose, mirror work - if source or targets do not match in size, smallest size is copied - unused pixels fade to black (allows overlapping segments) - if invalid source ID is set, segment just fades to black - added a rgb2hsv conversion function as the fastled variant is inaccurate and buggy note: 1D to 2D and vice versa is not supported --- wled00/FX.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++- wled00/FX.h | 4 ++-- wled00/colors.cpp | 26 +++++++++++++++++++++ wled00/fcn_declare.h | 1 + 4 files changed, 83 insertions(+), 3 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 807594e430..ca5a40f721 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -82,6 +82,7 @@ static um_data_t* getAudioData() { return um_data; } + // effect functions /* @@ -93,7 +94,58 @@ uint16_t mode_static(void) { } static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; - +/* + * Copy selected segment + */ +uint16_t mode_copy_segment(void) { + + uint32_t sourceid = SEGMENT.custom1; + SEGMENT.fadeToBlackBy(16); // fades out unused pixels, still allows overlay (also fades out if invalid ID is set) + if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) return FRAMETIME; // invalid source + CRGB sourcecolor; + if (strip._segments[sourceid].isActive()) { + // note: copying 1D to 2D as well as 2D to 1D is not supported + if(SEGMENT.is2D() && strip._segments[sourceid].is2D()) { // 2D setup + uint32_t cx, cy; // sizes to copy + cx = std::min(strip._segments[sourceid].virtualWidth(), SEGMENT.virtualWidth()); // get smaller width + cy = std::min(strip._segments[sourceid].virtualHeight(), SEGMENT.virtualHeight()); // get smaller height + for (unsigned x = 0; x < cx; x++) { + for (unsigned y = 0; y < cy; y++) { + sourcecolor = strip._segments[sourceid].getPixelColorXY(x, y); + if(SEGMENT.custom2 > 0) // color shifting enabled + { + CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV + pxHSV.h += SEGMENT.custom2; // shift hue + hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB + + } + SEGMENT.setPixelColorXY(x, y, sourcecolor); + //SEGMENT.setPixelColorXY(x, y, strip._segments[sourceid].getPixelColorXY(x, y)); //use this for no colorshift option + } + + } + } + else if(!SEGMENT.is2D() && !strip._segments[sourceid].is2D()) { // 1D strip + uint32_t cl; // length to copy + cl = std::min(strip._segments[sourceid].virtualLength(), SEGMENT.virtualLength()); // get smaller length + for (unsigned i = 0; i < cl; i++) { + sourcecolor = strip._segments[sourceid].getPixelColor(i); + if(SEGMENT.custom2 > 0) // color shifting enabled + { + CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV + pxHSV.h += SEGMENT.custom2; // shift hue + sourcecolor = (CRGB)pxHSV; // convert back to RGB + } + SEGMENT.setPixelColor(i, sourcecolor); + // SEGMENT.setPixelColor(i, strip._segments[sourceid].getPixelColor(i)); //use this for no colorshift option + } + } + } + return FRAMETIME; +} +//static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,ID;;;12;c1=0,c2=0"; +static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,ID,Color shift;;;12;c1=0,c2=0"; + /* * Blink/strobe function * Alternate between color1 and color2 @@ -7835,6 +7887,7 @@ void WS2812FX::setupEffectData() { _modeData.push_back(_data_RESERVED); } // now replace all pre-allocated effects + addEffect(FX_MODE_COPY, &mode_copy_segment, _data_FX_MODE_COPY); // --- 1D non-audio effects --- addEffect(FX_MODE_BLINK, &mode_blink, _data_FX_MODE_BLINK); addEffect(FX_MODE_BREATH, &mode_breath, _data_FX_MODE_BREATH); diff --git a/wled00/FX.h b/wled00/FX.h index b1b1fbcb78..243f51c5a9 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -317,8 +317,8 @@ #define FX_MODE_WAVESINS 184 #define FX_MODE_ROCKTAVES 185 #define FX_MODE_2DAKEMI 186 - -#define MODE_COUNT 187 +#define FX_MODE_COPY 187 +#define MODE_COUNT 188 typedef enum mapping1D2D { M12_Pixels = 0, diff --git a/wled00/colors.cpp b/wled00/colors.cpp index ebea7ea055..903b79a419 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -225,6 +225,32 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb } } +CHSV rgb2hsv(const CRGB& rgb) // convert rgb to hsv, more accurate and faster than fastled version +{ + int32_t r = rgb.r; + int32_t g = rgb.g; + int32_t b = rgb.b; + CHSV hsv = CHSV(0, 0, 0); + int32_t minval, maxval, delta; + minval = min(r, g); + minval = min(minval, b); + maxval = max(r, g); + maxval = max(maxval, b); + if (maxval == 0) return hsv; // black + hsv.v = maxval; + delta = maxval - minval; + hsv.s = (255 * delta) / maxval; + if (hsv.s == 0) return hsv; // gray value + int32_t h; //calculate hue + if (maxval == r) + h = (43 * (g - b)) / delta; + else if (maxval == g) h = 85 + (43 * (b - r)) / delta; + else h = 171 + (43 * (r - g)) / delta; + if(h < 0) h += 256; + hsv.h = h; + return hsv; +} + //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc { diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index d95b8ef8e4..ead60d89d7 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -85,6 +85,7 @@ 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 +CHSV rgb2hsv(const CRGB& rgb); //rgb to hsv void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO From 24df6bf5949ba3a70ea8888a16333171aa4d4a11 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 4 Sep 2024 21:44:52 +0200 Subject: [PATCH 02/10] Added copying source settings target segment now also copies the source settings (mirror, grouping, spacing, transpose) so the target looks exactly like the source. inverting can still be set independently. note: the bug in line 214 of FX_2Dfcn.cpp missing `start` needs to be fixed for it to work properly in 2D. --- wled00/FX.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index ca5a40f721..985835175e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -98,14 +98,20 @@ static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; * Copy selected segment */ uint16_t mode_copy_segment(void) { - - uint32_t sourceid = SEGMENT.custom1; + uint32_t sourceid = SEGMENT.custom3; SEGMENT.fadeToBlackBy(16); // fades out unused pixels, still allows overlay (also fades out if invalid ID is set) if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) return FRAMETIME; // invalid source CRGB sourcecolor; + //copy source segment settings (reverse can be set independently) + SEGMENT.mirror = strip._segments[sourceid].mirror; + SEGMENT.grouping = strip._segments[sourceid].grouping; + SEGMENT.spacing = strip._segments[sourceid].spacing; if (strip._segments[sourceid].isActive()) { // note: copying 1D to 2D as well as 2D to 1D is not supported if(SEGMENT.is2D() && strip._segments[sourceid].is2D()) { // 2D setup + //copy 2D segment settings + SEGMENT.mirror_y = strip._segments[sourceid].mirror_y; + SEGMENT.transpose = strip._segments[sourceid].transpose; uint32_t cx, cy; // sizes to copy cx = std::min(strip._segments[sourceid].virtualWidth(), SEGMENT.virtualWidth()); // get smaller width cy = std::min(strip._segments[sourceid].virtualHeight(), SEGMENT.virtualHeight()); // get smaller height @@ -116,13 +122,10 @@ uint16_t mode_copy_segment(void) { { CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV pxHSV.h += SEGMENT.custom2; // shift hue - hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB - + hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB } - SEGMENT.setPixelColorXY(x, y, sourcecolor); - //SEGMENT.setPixelColorXY(x, y, strip._segments[sourceid].getPixelColorXY(x, y)); //use this for no colorshift option + SEGMENT.setPixelColorXY(x, y, sourcecolor); } - } } else if(!SEGMENT.is2D() && !strip._segments[sourceid].is2D()) { // 1D strip @@ -143,8 +146,7 @@ uint16_t mode_copy_segment(void) { } return FRAMETIME; } -//static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,ID;;;12;c1=0,c2=0"; -static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,ID,Color shift;;;12;c1=0,c2=0"; +static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,,Color shift,ID;;;12;c2=0,c3=0"; /* * Blink/strobe function From 696f74b43a43fe68640ca53b66741244da485578 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 7 Sep 2024 11:05:09 +0200 Subject: [PATCH 03/10] Update integrating new ideas as discussed - added color adjust function with support for hue, lightness and brightness - colors can now be adjusted in hue, saturation and brightness - added `getRenderedPixelXY()` function (credit @willmmiles) - source is now accurately copied, even 1D->2D is possible - fix for segment offset not being zero in 2D in `getRenderedPixelXY()` - added checkmark to copy source grouping/spacing (may remove again) - tested many different scenarios, everything seems to work --- wled00/FX.cpp | 88 ++++++++++++++++++++++---------------------- wled00/colors.cpp | 15 ++++++++ wled00/fcn_declare.h | 1 + 3 files changed, 61 insertions(+), 43 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 985835175e..a17214a7d7 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -94,60 +94,62 @@ uint16_t mode_static(void) { } static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; + /* - * Copy selected segment + * Copy selected segment and perform (optional) color adjustments */ +// TODO: what is the correct place to put getRenderedPixelXY()? +static CRGB getRenderedPixelXY(Segment& seg, unsigned x, unsigned y = 0) { + // We read pixels back following mirror/reverse/transpose but ignoring grouping + // For every group-length pixels, add spacing + x *= seg.groupLength(); // expand to physical pixels + y *= seg.groupLength(); // expand to physical pixels + if (x >= seg.width() || y >= seg.height()) return 0; // fill out of range pixels with black + uint32_t offset = seg.is2D() ? 0 : seg.offset; // dirty fix, offset in 2D segments should be zero but is not + return strip.getPixelColorXY(seg.start + offset + x, seg.startY + y); +} + uint16_t mode_copy_segment(void) { uint32_t sourceid = SEGMENT.custom3; - SEGMENT.fadeToBlackBy(16); // fades out unused pixels, still allows overlay (also fades out if invalid ID is set) - if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) return FRAMETIME; // invalid source - CRGB sourcecolor; - //copy source segment settings (reverse can be set independently) - SEGMENT.mirror = strip._segments[sourceid].mirror; - SEGMENT.grouping = strip._segments[sourceid].grouping; - SEGMENT.spacing = strip._segments[sourceid].spacing; - if (strip._segments[sourceid].isActive()) { - // note: copying 1D to 2D as well as 2D to 1D is not supported - if(SEGMENT.is2D() && strip._segments[sourceid].is2D()) { // 2D setup - //copy 2D segment settings - SEGMENT.mirror_y = strip._segments[sourceid].mirror_y; - SEGMENT.transpose = strip._segments[sourceid].transpose; - uint32_t cx, cy; // sizes to copy - cx = std::min(strip._segments[sourceid].virtualWidth(), SEGMENT.virtualWidth()); // get smaller width - cy = std::min(strip._segments[sourceid].virtualHeight(), SEGMENT.virtualHeight()); // get smaller height - for (unsigned x = 0; x < cx; x++) { - for (unsigned y = 0; y < cy; y++) { - sourcecolor = strip._segments[sourceid].getPixelColorXY(x, y); - if(SEGMENT.custom2 > 0) // color shifting enabled - { - CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV - pxHSV.h += SEGMENT.custom2; // shift hue - hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB - } - SEGMENT.setPixelColorXY(x, y, sourcecolor); - } - } - } - else if(!SEGMENT.is2D() && !strip._segments[sourceid].is2D()) { // 1D strip + if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) { // invalid source + SEGMENT.fadeToBlackBy(15); // fade out, clears pixels and allows overlapping segments + return FRAMETIME; + } + uint32_t spacing, grouping; + if(SEGMENT.check2) { // copy source segment spacing & grouping (preliminary for testing, may remove again) + spacing = SEGMENT.spacing; + grouping = SEGMENT.grouping; + SEGMENT.spacing = strip._segments[sourceid].spacing; + SEGMENT.grouping = strip._segments[sourceid].grouping; + } + if (strip._segments[sourceid].isActive()) { + if(!strip._segments[sourceid].is2D() || !SEGMENT.is2D()) { // 1D source or 1D target; source can be expanded into 2D uint32_t cl; // length to copy - cl = std::min(strip._segments[sourceid].virtualLength(), SEGMENT.virtualLength()); // get smaller length - for (unsigned i = 0; i < cl; i++) { - sourcecolor = strip._segments[sourceid].getPixelColor(i); - if(SEGMENT.custom2 > 0) // color shifting enabled - { - CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV - pxHSV.h += SEGMENT.custom2; // shift hue - sourcecolor = (CRGB)pxHSV; // convert back to RGB - } - SEGMENT.setPixelColor(i, sourcecolor); - // SEGMENT.setPixelColor(i, strip._segments[sourceid].getPixelColor(i)); //use this for no colorshift option + for (unsigned i = 0; i < SEGMENT.virtualLength(); i++) { + CRGB sourcecolor = getRenderedPixelXY(strip._segments[sourceid], i); + adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // color adjustment + SEGMENT.setPixelColor(i, sourcecolor); + } + } else { // 2D source, note: 2D to 1D just copies the first row (y=0, x=0 to x=source.width) + for (unsigned y = 0; y < SEGMENT.virtualHeight(); y++) { + for (unsigned x = 0; x < SEGMENT.virtualWidth(); x++) { + CRGB sourcecolor = getRenderedPixelXY(strip._segments[sourceid], x, y); + adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // color adjustment + SEGMENT.setPixelColorXY(x, y, sourcecolor); + } } } } + //restore settings -> if settings change during rendiring, this will reset them... not ideal but cannot be avoided + if(SEGMENT.check2) { + SEGMENT.spacing = spacing; + SEGMENT.grouping = grouping; + } return FRAMETIME; } -static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,,,Color shift,ID;;;12;c2=0,c3=0"; +static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,,Copy Grouping & Spacing;;;12;ix=0,c1=0,c2=0,c3=0,o2=0"; + /* * Blink/strobe function * Alternate between color1 and color2 diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 903b79a419..d42b7e68d7 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -88,6 +88,21 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) return scaledcolor; } +/* + * color adjustment in HSV color space (converts RGB to HSV and back), color conversions are not 100% accurate! + shifts hue, increase brightness, decreases saturation (if not black) + note: inputs are 32bit to speed up the function, useful input value ranges are 0-255 + */ +void adjust_color(CRGB& sourcecolor, uint32_t hueShift, int32_t lighten, uint32_t brighten) { + if(hueShift + lighten + brighten == 0) return; // no change + CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV + if(pxHSV.v == 0) return; // do not change black pixels + pxHSV.h += hueShift; // shift hue + pxHSV.s = max((int32_t)0, (int32_t)pxHSV.s - lighten); // desaturate + pxHSV.v = min((uint32_t)255, (uint32_t)pxHSV.v + brighten); // increase brightness + hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB +} + void setRandomColor(byte* rgb) { lastRandomIndex = get_random_wheel_index(lastRandomIndex); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index ead60d89d7..c1f102ccdc 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -81,6 +81,7 @@ 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); +void adjust_color(CRGB& sourcecolor, uint32_t hueShift, int32_t lighten, uint32_t brighten); 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]))); } From 8aa82abc7d0fd556eed709b1a58c01d4234462b1 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 8 Sep 2024 08:02:28 +0200 Subject: [PATCH 04/10] fixes and optimizations - removed 'copy grouping/spacing' option - added 'Switch axis' instead (in 2D -> 1D copy this chooses x or y axis to be copied) - optimized for code size and speed by not using CRGB (where possible) and not returning struct in `rgb2hsv()` --- wled00/FX.cpp | 41 +++++++++++++++-------------------------- wled00/colors.cpp | 34 ++++++++++++++++++---------------- wled00/fcn_declare.h | 4 ++-- 3 files changed, 35 insertions(+), 44 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a17214a7d7..895826cfce 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -99,56 +99,45 @@ static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; * Copy selected segment and perform (optional) color adjustments */ // TODO: what is the correct place to put getRenderedPixelXY()? -static CRGB getRenderedPixelXY(Segment& seg, unsigned x, unsigned y = 0) { +static uint32_t getRenderedPixelXY(Segment& seg, unsigned x, unsigned y = 0) { // We read pixels back following mirror/reverse/transpose but ignoring grouping // For every group-length pixels, add spacing x *= seg.groupLength(); // expand to physical pixels y *= seg.groupLength(); // expand to physical pixels if (x >= seg.width() || y >= seg.height()) return 0; // fill out of range pixels with black - uint32_t offset = seg.is2D() ? 0 : seg.offset; // dirty fix, offset in 2D segments should be zero but is not + #warning this check can be removed once 2D offset is fixed + uint32_t offset = seg.is2D() ? 0 : seg.offset; // dirty fix, offset in 2D segments should be zero but is not TODO: fix the offset return strip.getPixelColorXY(seg.start + offset + x, seg.startY + y); } uint16_t mode_copy_segment(void) { uint32_t sourceid = SEGMENT.custom3; if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) { // invalid source - SEGMENT.fadeToBlackBy(15); // fade out, clears pixels and allows overlapping segments + SEGMENT.fadeToBlackBy(5); // fade out, clears pixels and allows overlapping segments return FRAMETIME; } - uint32_t spacing, grouping; - if(SEGMENT.check2) { // copy source segment spacing & grouping (preliminary for testing, may remove again) - spacing = SEGMENT.spacing; - grouping = SEGMENT.grouping; - SEGMENT.spacing = strip._segments[sourceid].spacing; - SEGMENT.grouping = strip._segments[sourceid].grouping; - } if (strip._segments[sourceid].isActive()) { - if(!strip._segments[sourceid].is2D() || !SEGMENT.is2D()) { // 1D source or 1D target; source can be expanded into 2D + uint32_t sourcecolor; + if(!strip._segments[sourceid].is2D()) { // 1D source, source can be expanded into 2D uint32_t cl; // length to copy for (unsigned i = 0; i < SEGMENT.virtualLength(); i++) { - CRGB sourcecolor = getRenderedPixelXY(strip._segments[sourceid], i); - adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // color adjustment - SEGMENT.setPixelColor(i, sourcecolor); + sourcecolor = getRenderedPixelXY(strip._segments[sourceid], i); + SEGMENT.setPixelColor(i, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); } - } else { // 2D source, note: 2D to 1D just copies the first row (y=0, x=0 to x=source.width) + } else { // 2D source, note: 2D to 1D just copies the first row (or first column if 'Switch axis' is checked in FX) for (unsigned y = 0; y < SEGMENT.virtualHeight(); y++) { - for (unsigned x = 0; x < SEGMENT.virtualWidth(); x++) { - CRGB sourcecolor = getRenderedPixelXY(strip._segments[sourceid], x, y); - adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); // color adjustment - SEGMENT.setPixelColorXY(x, y, sourcecolor); + for (unsigned x = 0; x < SEGMENT.virtualWidth(); x++) { + if(SEGMENT.check2) sourcecolor = getRenderedPixelXY(strip._segments[sourceid], y, x); // flip axis (for 2D -> 1D, in 2D Segments this does the same as 'Transpose') + else sourcecolor = getRenderedPixelXY(strip._segments[sourceid], x, y); + SEGMENT.setPixelColorXY(x, y, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); } } } } - //restore settings -> if settings change during rendiring, this will reset them... not ideal but cannot be avoided - if(SEGMENT.check2) { - SEGMENT.spacing = spacing; - SEGMENT.grouping = grouping; - } return FRAMETIME; } -static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,,Copy Grouping & Spacing;;;12;ix=0,c1=0,c2=0,c3=0,o2=0"; - +static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,,Switch axis(2D);;;1;ix=0,c1=0,c2=0,c3=0,o2=0"; + /* * Blink/strobe function diff --git a/wled00/colors.cpp b/wled00/colors.cpp index d42b7e68d7..59456d7821 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -93,14 +93,17 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) shifts hue, increase brightness, decreases saturation (if not black) note: inputs are 32bit to speed up the function, useful input value ranges are 0-255 */ -void adjust_color(CRGB& sourcecolor, uint32_t hueShift, int32_t lighten, uint32_t brighten) { - if(hueShift + lighten + brighten == 0) return; // no change - CHSV pxHSV = rgb2hsv(sourcecolor); //convert to HSV - if(pxHSV.v == 0) return; // do not change black pixels - pxHSV.h += hueShift; // shift hue - pxHSV.s = max((int32_t)0, (int32_t)pxHSV.s - lighten); // desaturate - pxHSV.v = min((uint32_t)255, (uint32_t)pxHSV.v + brighten); // increase brightness - hsv2rgb_spectrum(pxHSV, sourcecolor); // convert back to RGB +uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) { + if(hueShift + lighten + brighten == 0) return rgb; // no change + CHSV hsv; + rgb2hsv(rgb, hsv); //convert to HSV + if(hsv.v == 0) return rgb; // do not change black pixels + hsv.h += hueShift; // shift hue + hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate + hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness + CRGB adjusted; + hsv2rgb_spectrum(hsv, adjusted); // convert back to RGB + return RGBW32(adjusted.r,adjusted.g,adjusted.b,0); } void setRandomColor(byte* rgb) @@ -240,22 +243,22 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb } } -CHSV rgb2hsv(const CRGB& rgb) // convert rgb to hsv, more accurate and faster than fastled version +void rgb2hsv(const uint32_t rgb, CHSV& hsv) // convert rgb to hsv, more accurate and faster than fastled version { - int32_t r = rgb.r; - int32_t g = rgb.g; - int32_t b = rgb.b; - CHSV hsv = CHSV(0, 0, 0); + int32_t r = (rgb>>16)&0xFF; + int32_t g = (rgb>>8)&0xFF; + int32_t b = rgb&0xFF; + hsv = CHSV(0, 0, 0); int32_t minval, maxval, delta; minval = min(r, g); minval = min(minval, b); maxval = max(r, g); maxval = max(maxval, b); - if (maxval == 0) return hsv; // black + if (maxval == 0) return; // black hsv.v = maxval; delta = maxval - minval; hsv.s = (255 * delta) / maxval; - if (hsv.s == 0) return hsv; // gray value + if (hsv.s == 0) return; // gray value int32_t h; //calculate hue if (maxval == r) h = (43 * (g - b)) / delta; @@ -263,7 +266,6 @@ CHSV rgb2hsv(const CRGB& rgb) // convert rgb to hsv, more accurate and faster th else h = 171 + (43 * (r - g)) / delta; if(h < 0) h += 256; hsv.h = h; - return hsv; } //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index c1f102ccdc..80e65fd257 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -81,12 +81,12 @@ 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); -void adjust_color(CRGB& sourcecolor, uint32_t hueShift, int32_t lighten, uint32_t brighten); +uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten); 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 -CHSV rgb2hsv(const CRGB& rgb); //rgb to hsv +void rgb2hsv(const uint32_t rgb, CHSV& hsv); //rgb to hsv void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO From dd8353a2f09d85b134f2d85b8b7dc6911f39da88 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Mon, 9 Sep 2024 08:27:06 +0200 Subject: [PATCH 05/10] moved getRenderedPixelXY() into SEGMENT class, plus little improvement --- wled00/FX.cpp | 21 ++++----------------- wled00/FX.h | 1 + wled00/FX_fcn.cpp | 12 ++++++++++++ wled00/colors.cpp | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 895826cfce..2d39fe42b3 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -94,22 +94,9 @@ uint16_t mode_static(void) { } static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; - /* - * Copy selected segment and perform (optional) color adjustments + * Copy a segment and perform (optional) color adjustments */ -// TODO: what is the correct place to put getRenderedPixelXY()? -static uint32_t getRenderedPixelXY(Segment& seg, unsigned x, unsigned y = 0) { - // We read pixels back following mirror/reverse/transpose but ignoring grouping - // For every group-length pixels, add spacing - x *= seg.groupLength(); // expand to physical pixels - y *= seg.groupLength(); // expand to physical pixels - if (x >= seg.width() || y >= seg.height()) return 0; // fill out of range pixels with black - #warning this check can be removed once 2D offset is fixed - uint32_t offset = seg.is2D() ? 0 : seg.offset; // dirty fix, offset in 2D segments should be zero but is not TODO: fix the offset - return strip.getPixelColorXY(seg.start + offset + x, seg.startY + y); -} - uint16_t mode_copy_segment(void) { uint32_t sourceid = SEGMENT.custom3; if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) { // invalid source @@ -121,14 +108,14 @@ uint16_t mode_copy_segment(void) { if(!strip._segments[sourceid].is2D()) { // 1D source, source can be expanded into 2D uint32_t cl; // length to copy for (unsigned i = 0; i < SEGMENT.virtualLength(); i++) { - sourcecolor = getRenderedPixelXY(strip._segments[sourceid], i); + sourcecolor = SEGMENT.getRenderedPixelXY(strip._segments[sourceid], i); SEGMENT.setPixelColor(i, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); } } else { // 2D source, note: 2D to 1D just copies the first row (or first column if 'Switch axis' is checked in FX) for (unsigned y = 0; y < SEGMENT.virtualHeight(); y++) { for (unsigned x = 0; x < SEGMENT.virtualWidth(); x++) { - if(SEGMENT.check2) sourcecolor = getRenderedPixelXY(strip._segments[sourceid], y, x); // flip axis (for 2D -> 1D, in 2D Segments this does the same as 'Transpose') - else sourcecolor = getRenderedPixelXY(strip._segments[sourceid], x, y); + if(SEGMENT.check2) sourcecolor = SEGMENT.getRenderedPixelXY(strip._segments[sourceid], y, x); // flip axis (for 2D -> 1D, in 2D Segments this does the same as 'Transpose') + else sourcecolor = SEGMENT.getRenderedPixelXY(strip._segments[sourceid], x, y); SEGMENT.setPixelColorXY(x, y, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); } } diff --git a/wled00/FX.h b/wled00/FX.h index 243f51c5a9..32ab2286bb 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -588,6 +588,7 @@ typedef struct Segment { inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } #endif uint32_t getPixelColor(int i) const; + uint32_t getRenderedPixelXY(Segment& seg, unsigned x, unsigned y = 0); // 1D support functions (some implement 2D as well) void blur(uint8_t, bool smear = false); void fill(uint32_t c); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7a6fa40b52..f9a4253834 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -967,6 +967,18 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const return strip.getPixelColor(i); } +/* + * Read rendered pixel back (following mirror/reverse/transpose but ignoring grouping) + */ +uint32_t Segment::getRenderedPixelXY(Segment& seg, unsigned x, unsigned y) { + // For every group-length pixels, add spacing + x *= seg.groupLength(); // expand to physical pixels + y *= seg.groupLength(); // expand to physical pixels + if (x >= seg.width() || y >= seg.height()) return 0; // fill out of range pixels with black + uint32_t offset = seg.is2D() ? 0 : seg.offset; //offset in 2D segments is undefined, set to zero + return strip.getPixelColorXY(seg.start + offset + x, seg.startY + y); +} + uint8_t Segment::differs(Segment& b) const { uint8_t d = 0; if (start != b.start) d |= SEG_DIFFERS_BOUNDS; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 59456d7821..824879660a 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -94,7 +94,7 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) note: inputs are 32bit to speed up the function, useful input value ranges are 0-255 */ uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) { - if(hueShift + lighten + brighten == 0) return rgb; // no change + if(rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change CHSV hsv; rgb2hsv(rgb, hsv); //convert to HSV if(hsv.v == 0) return rgb; // do not change black pixels From df33a36fe4436814dcac668a558a0e96b249fafd Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Sep 2024 16:29:33 +0200 Subject: [PATCH 06/10] rgb2hsv now returns the CHSV value instead of using a reference added alias function to accept CRGB value. this is more versatile for future uses. also removed some whitespaces --- wled00/colors.cpp | 25 ++++++++++++------------- wled00/fcn_declare.h | 3 ++- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 3dfbe01ee9..0bfdaff141 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -91,19 +91,18 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) return scaledcolor; } -/* +/* * color adjustment in HSV color space (converts RGB to HSV and back), color conversions are not 100% accurate! shifts hue, increase brightness, decreases saturation (if not black) note: inputs are 32bit to speed up the function, useful input value ranges are 0-255 */ uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) { if(rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change - CHSV hsv; - rgb2hsv(rgb, hsv); //convert to HSV + CHSV hsv = rgb2hsv(rgb); //convert to HSV if(hsv.v == 0) return rgb; // do not change black pixels hsv.h += hueShift; // shift hue hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate - hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness + hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness CRGB adjusted; hsv2rgb_spectrum(hsv, adjusted); // convert back to RGB return RGBW32(adjusted.r,adjusted.g,adjusted.b,0); @@ -123,7 +122,7 @@ 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] = rgb2hsv(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 @@ -246,27 +245,27 @@ void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb } } -void rgb2hsv(const uint32_t rgb, CHSV& hsv) // convert rgb to hsv, more accurate and faster than fastled version +CHSV rgb2hsv(const uint32_t rgb) // convert rgb to hsv, more accurate and faster than fastled version { + CHSV hsv = CHSV(0, 0, 0); int32_t r = (rgb>>16)&0xFF; int32_t g = (rgb>>8)&0xFF; int32_t b = rgb&0xFF; - hsv = CHSV(0, 0, 0); int32_t minval, maxval, delta; minval = min(r, g); - minval = min(minval, b); + minval = min(minval, b); maxval = max(r, g); maxval = max(maxval, b); if (maxval == 0) return; // black hsv.v = maxval; delta = maxval - minval; hsv.s = (255 * delta) / maxval; - if (hsv.s == 0) return; // gray value + if (hsv.s == 0) return; // gray value int32_t h; //calculate hue - if (maxval == r) - h = (43 * (g - b)) / delta; - else if (maxval == g) h = 85 + (43 * (b - r)) / delta; - else h = 171 + (43 * (r - g)) / delta; + if (maxval == r) + h = (43 * (g - b)) / delta; + else if (maxval == g) h = 85 + (43 * (b - r)) / delta; + else h = 171 + (43 * (r - g)) / delta; if(h < 0) h += 256; hsv.h = h; } diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 55e19346b3..a7ee4e08b4 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -86,7 +86,8 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); 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 rgb2hsv(const uint32_t rgb, CHSV& hsv); //rgb to hsv +CHSV rgb2hsv(const uint32_t rgb); // rgb to hsv +inline CHSV rgb2hsv(const CRGB c) { return rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b))))); } // CRGB to hsv void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO From 7db1adccb7a8faf3e08d11adc9f03d25166c1e0b Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Sep 2024 16:53:22 +0200 Subject: [PATCH 07/10] added forgotten returnvalue --- wled00/colors.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 0bfdaff141..8b04d4c07a 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -256,11 +256,11 @@ CHSV rgb2hsv(const uint32_t rgb) // convert rgb to hsv, more accurate and faster minval = min(minval, b); maxval = max(r, g); maxval = max(maxval, b); - if (maxval == 0) return; // black + if (maxval == 0) return hsv; // black hsv.v = maxval; delta = maxval - minval; hsv.s = (255 * delta) / maxval; - if (hsv.s == 0) return; // gray value + if (hsv.s == 0) return hsv; // gray value int32_t h; //calculate hue if (maxval == r) h = (43 * (g - b)) / delta; @@ -268,6 +268,7 @@ CHSV rgb2hsv(const uint32_t rgb) // convert rgb to hsv, more accurate and faster else h = 171 + (43 * (r - g)) / delta; if(h < 0) h += 256; hsv.h = h; + return hsv; } //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) From 24a424a35063fb7ee0e15732ec4960c61603b1f5 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 24 Sep 2024 20:49:33 +0200 Subject: [PATCH 08/10] Moved getRenderedPixelXY to WS2812FX class and changed segment access --- wled00/FX.cpp | 23 ++++++++++++----------- wled00/FX.h | 4 ++-- wled00/FX_fcn.cpp | 24 ++++++++++++------------ 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 0dd3fbe49d..6700c02cdb 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -99,25 +99,26 @@ static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; */ uint16_t mode_copy_segment(void) { uint32_t sourceid = SEGMENT.custom3; - if (sourceid >= strip._segments.size() || sourceid == strip.getCurrSegmentId()) { // invalid source + if (sourceid >= strip.getSegmentsNum() || sourceid == strip.getCurrSegmentId()) { // invalid source SEGMENT.fadeToBlackBy(5); // fade out, clears pixels and allows overlapping segments - return FRAMETIME; + return FRAMETIME; } - if (strip._segments[sourceid].isActive()) { + Segment sourcesegment = strip.getSegment(sourceid); + if (sourcesegment.isActive()) { uint32_t sourcecolor; - if(!strip._segments[sourceid].is2D()) { // 1D source, source can be expanded into 2D + if(!sourcesegment.is2D()) { // 1D source, source can be expanded into 2D uint32_t cl; // length to copy - for (unsigned i = 0; i < SEGMENT.virtualLength(); i++) { - sourcecolor = SEGMENT.getRenderedPixelXY(strip._segments[sourceid], i); + for (unsigned i = 0; i < SEGMENT.virtualLength(); i++) { + sourcecolor = strip.getRenderedPixelXY(sourceid, i); SEGMENT.setPixelColor(i, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); } - } else { // 2D source, note: 2D to 1D just copies the first row (or first column if 'Switch axis' is checked in FX) + } else { // 2D source, note: 2D to 1D just copies the first row (or first column if 'Switch axis' is checked in FX) for (unsigned y = 0; y < SEGMENT.virtualHeight(); y++) { - for (unsigned x = 0; x < SEGMENT.virtualWidth(); x++) { - if(SEGMENT.check2) sourcecolor = SEGMENT.getRenderedPixelXY(strip._segments[sourceid], y, x); // flip axis (for 2D -> 1D, in 2D Segments this does the same as 'Transpose') - else sourcecolor = SEGMENT.getRenderedPixelXY(strip._segments[sourceid], x, y); + for (unsigned x = 0; x < SEGMENT.virtualWidth(); x++) { + if(SEGMENT.check2) sourcecolor = strip.getRenderedPixelXY(sourceid, y, x); // flip axis (for 2D -> 1D, in 2D Segments this does the same as 'Transpose') + else sourcecolor = strip.getRenderedPixelXY(sourceid, x, y); SEGMENT.setPixelColorXY(x, y, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); - } + } } } } diff --git a/wled00/FX.h b/wled00/FX.h index f9b8a6f8e0..c96b1342de 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -588,7 +588,6 @@ typedef struct Segment { inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } #endif [[gnu::hot]] uint32_t getPixelColor(int i) const; - uint32_t getRenderedPixelXY(Segment& seg, unsigned x, unsigned y = 0); // 1D support functions (some implement 2D as well) void blur(uint8_t, bool smear = false); void fill(uint32_t c); @@ -848,7 +847,8 @@ class WS2812FX { // 96 bytes uint32_t now, timebase, - getPixelColor(uint16_t) const; + getPixelColor(uint16_t) const, + getRenderedPixelXY(uint8_t segid, unsigned x, unsigned y = 0) const; inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index b87703f4c3..74e57101c0 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -970,18 +970,6 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const return strip.getPixelColor(i); } -/* - * Read rendered pixel back (following mirror/reverse/transpose but ignoring grouping) - */ -uint32_t Segment::getRenderedPixelXY(Segment& seg, unsigned x, unsigned y) { - // For every group-length pixels, add spacing - x *= seg.groupLength(); // expand to physical pixels - y *= seg.groupLength(); // expand to physical pixels - if (x >= seg.width() || y >= seg.height()) return 0; // fill out of range pixels with black - uint32_t offset = seg.is2D() ? 0 : seg.offset; //offset in 2D segments is undefined, set to zero - return strip.getPixelColorXY(seg.start + offset + x, seg.startY + y); -} - uint8_t Segment::differs(Segment& b) const { uint8_t d = 0; if (start != b.start) d |= SEG_DIFFERS_BOUNDS; @@ -1434,6 +1422,18 @@ uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) const { return BusManager::getPixelColor(i); } +/* + * Read rendered pixel back (following mirror/reverse/transpose but ignoring grouping) + */ +uint32_t WS2812FX::getRenderedPixelXY(uint8_t segid, unsigned x, unsigned y) const { + // For every group-length pixels, add spacing + x *= _segments[segid].groupLength(); // expand to physical pixels + y *= _segments[segid].groupLength(); // expand to physical pixels + if (x >= _segments[segid].width() || y >= _segments[segid].height()) return 0; // fill out of range pixels with black + uint32_t offset = _segments[segid].is2D() ? 0 : _segments[segid].offset; //offset in 2D segments is undefined, set to zero + return strip.getPixelColorXY(_segments[segid].start + offset + x, _segments[segid].startY + y); +} + void WS2812FX::show() { // avoid race condition, capture _callback value show_callback callback = _callback; From a5807b51cbdd9f1c86e5a4e0aef9804ac3f501b7 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 25 Sep 2024 19:36:20 +0200 Subject: [PATCH 09/10] Added HSV2RGB and RGB2HSV functions for higher accuracy conversions - Bonus: saves over 1.2kB of flash -also added a struct to handle HSV with 16bit hue better (including some conversions, can be extended easily) - the functions are optimized for speed and flash use. They are faster and more accurate than what fastled offers (and use much less flash). - replaced colorHStoRGB() with a call to the new hsv2rgb() function, saving even more flash - the 16bit hue calculations result in an almost perfect conversion from RGB to HSV and back, the maximum error was 1/255 in the cases I tested. --- wled00/colors.cpp | 84 +++++++++++++++++++++++++------------------- wled00/fcn_declare.h | 30 ++++++++++++++-- wled00/ir.cpp | 4 +-- 3 files changed, 77 insertions(+), 41 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 8b04d4c07a..f43a0f9fb3 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -98,14 +98,14 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) */ uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) { if(rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change - CHSV hsv = rgb2hsv(rgb); //convert to HSV - if(hsv.v == 0) return rgb; // do not change black pixels - hsv.h += hueShift; // shift hue + CHSV32 hsv; + rgb2hsv(rgb, hsv); //convert to HSV + hsv.h += (hueShift << 8); // shift hue (hue is 16 bits) hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness - CRGB adjusted; - hsv2rgb_spectrum(hsv, adjusted); // convert back to RGB - return RGBW32(adjusted.r,adjusted.g,adjusted.b,0); + uint32_t rgb_adjusted; + hsv2rgb(hsv, rgb_adjusted); // convert back to RGB TODO: make this into 16 bit conversion + return rgb_adjusted; } void setRandomColor(byte* rgb) @@ -223,31 +223,40 @@ CRGBPalette16 generateRandomPalette() //generate fully random palette CHSV(random8(), random8(160, 255), random8(128, 255))); } -void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb +void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0) { - float h = ((float)hue)/10922.5f; // hue*6/65535 - float s = ((float)sat)/255.0f; - int i = int(h); - float f = h - i; - int p = int(255.0f * (1.0f-s)); - int q = int(255.0f * (1.0f-s*f)); - int t = int(255.0f * (1.0f-s*(1.0f-f))); - p = constrain(p, 0, 255); - q = constrain(q, 0, 255); - t = constrain(t, 0, 255); - switch (i%6) { - case 0: rgb[0]=255,rgb[1]=t, rgb[2]=p; break; - case 1: rgb[0]=q, rgb[1]=255,rgb[2]=p; break; - case 2: rgb[0]=p, rgb[1]=255,rgb[2]=t; break; - case 3: rgb[0]=p, rgb[1]=q, rgb[2]=255;break; - case 4: rgb[0]=t, rgb[1]=p, rgb[2]=255;break; - case 5: rgb[0]=255,rgb[1]=p, rgb[2]=q; break; + unsigned int remainder, region, p, q, t; + unsigned int h = hsv.h; + unsigned int s = hsv.s; + unsigned int v = hsv.v; + if (s == 0) { + rgb = v << 16 | v << 8 | v; + return; + } + region = h / 10923; // 65536 / 6 = 10923 + remainder = (h - (region * 10923)) * 6; + p = (v * (256 - s)) >> 8; + q = (v * (255 - ((s * remainder) >> 16))) >> 8; + t = (v * (255 - ((s * (65535 - remainder)) >> 16))) >> 8; + switch (region) { + case 0: + rgb = v << 16 | t << 8 | p; break; + case 1: + rgb = q << 16 | v << 8 | p; break; + case 2: + rgb = p << 16 | v << 8 | t; break; + case 3: + rgb = p << 16 | q << 8 | v; break; + case 4: + rgb = t << 16 | p << 8 | v; break; + default: + rgb = v << 16 | p << 8 | q; break; } } -CHSV rgb2hsv(const uint32_t rgb) // convert rgb to hsv, more accurate and faster than fastled version +void rgb2hsv(const uint32_t rgb, CHSV32& hsv) // convert RGB to HSV (16bit hue), much more accurate and faster than fastled version { - CHSV hsv = CHSV(0, 0, 0); + hsv.raw = 0; int32_t r = (rgb>>16)&0xFF; int32_t g = (rgb>>8)&0xFF; int32_t b = rgb&0xFF; @@ -256,19 +265,22 @@ CHSV rgb2hsv(const uint32_t rgb) // convert rgb to hsv, more accurate and faster minval = min(minval, b); maxval = max(r, g); maxval = max(maxval, b); - if (maxval == 0) return hsv; // black + if (maxval == 0) return; // black hsv.v = maxval; delta = maxval - minval; hsv.s = (255 * delta) / maxval; - if (hsv.s == 0) return hsv; // gray value - int32_t h; //calculate hue - if (maxval == r) - h = (43 * (g - b)) / delta; - else if (maxval == g) h = 85 + (43 * (b - r)) / delta; - else h = 171 + (43 * (r - g)) / delta; - if(h < 0) h += 256; - hsv.h = h; - return hsv; + if (hsv.s == 0) return; // gray value + if (maxval == r) hsv.h = (10923 * (g - b)) / delta; + else if (maxval == g) hsv.h = 21845 + (10923 * (b - r)) / delta; + else hsv.h = 43690 + (10923 * (r - g)) / delta; +} + +void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) { //hue, sat to rgb + uint32_t crgb; + hsv2rgb(CHSV32(hue, sat, 255), crgb); + rgb[0] = byte((crgb) >> 16); + rgb[1] = byte((crgb) >> 8); + rgb[2] = byte(crgb); } //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index a7ee4e08b4..914433f946 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -66,6 +66,29 @@ typedef struct WiFiConfig { } wifi_config; //colors.cpp +struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions + union { + struct { + uint16_t h; // hue + uint8_t s; // saturation + uint8_t v; // value + }; + uint32_t raw; // 32bit access + }; + inline CHSV32() __attribute__((always_inline)) = default; // default constructor + + /// Allow construction from hue, saturation, and value + /// @param ih input hue + /// @param is input saturation + /// @param iv input value + inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v + : h(ih), s(is), v(iv) {} + inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v + : h((uint16_t)ih << 8), s(is), v(iv) {} + inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV + : h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {} + inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV +}; // similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) class NeoGammaWLEDMethod { public: @@ -85,9 +108,10 @@ uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); 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 -CHSV rgb2hsv(const uint32_t rgb); // rgb to hsv -inline CHSV rgb2hsv(const CRGB c) { return rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b))))); } // CRGB to hsv +void hsv2rgb(const CHSV32& hsv, uint32_t& rgb); +void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); +void rgb2hsv(const uint32_t rgb, CHSV32& hsv); +inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO diff --git a/wled00/ir.cpp b/wled00/ir.cpp index e4541cd909..f094d3b874 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -129,7 +129,7 @@ static void changeEffectSpeed(int8_t amount) } else { // if Effect == "solid Color", change the hue of the primary color Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); CRGB fastled_col = CRGB(sseg.colors[0]); - CHSV prim_hsv = rgb2hsv_approximate(fastled_col); + CHSV prim_hsv = rgb2hsv(fastled_col); int16_t new_val = (int16_t)prim_hsv.h + amount; if (new_val > 255) new_val -= 255; // roll-over if bigger than 255 if (new_val < 0) new_val += 255; // roll-over if smaller than 0 @@ -173,7 +173,7 @@ static void changeEffectIntensity(int8_t amount) } else { // if Effect == "solid Color", change the saturation of the primary color Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); CRGB fastled_col = CRGB(sseg.colors[0]); - CHSV prim_hsv = rgb2hsv_approximate(fastled_col); + CHSV prim_hsv = rgb2hsv(fastled_col); int16_t new_val = (int16_t) prim_hsv.s + amount; prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255 hsv2rgb_rainbow(prim_hsv, fastled_col); From f2c24cafbab10e50bedf4d67292e558e30022e21 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 17 Jan 2025 19:09:09 +0100 Subject: [PATCH 10/10] added overlay option, renamed slider --- wled00/FX.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index bb41e69568..3c1fc0c9a2 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -138,21 +138,31 @@ uint16_t mode_copy_segment(void) { uint32_t cl; // length to copy for (unsigned i = 0; i < SEGMENT.virtualLength(); i++) { sourcecolor = strip.getRenderedPixelXY(sourceid, i); - SEGMENT.setPixelColor(i, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); + uint32_t color = adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); + if(SEGMENT.check3) // overlay + SEGMENT.addPixelColor(i, color); + else + SEGMENT.setPixelColor(i, color); } } else { // 2D source, note: 2D to 1D just copies the first row (or first column if 'Switch axis' is checked in FX) for (unsigned y = 0; y < SEGMENT.virtualHeight(); y++) { for (unsigned x = 0; x < SEGMENT.virtualWidth(); x++) { - if(SEGMENT.check2) sourcecolor = strip.getRenderedPixelXY(sourceid, y, x); // flip axis (for 2D -> 1D, in 2D Segments this does the same as 'Transpose') - else sourcecolor = strip.getRenderedPixelXY(sourceid, x, y); - SEGMENT.setPixelColorXY(x, y, adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2)); + if(SEGMENT.check2) + sourcecolor = strip.getRenderedPixelXY(sourceid, y, x); // flip reading axis (for 2D -> 1D, in 2D Segments this does the same as 'Transpose') + else + sourcecolor = strip.getRenderedPixelXY(sourceid, x, y); + uint32_t color = adjust_color(sourcecolor, SEGMENT.intensity, SEGMENT.custom1, SEGMENT.custom2); + if(SEGMENT.check3) // overlay + SEGMENT.addPixelColorXY(x, y, color); + else + SEGMENT.setPixelColorXY(x, y, color); } } } } return FRAMETIME; } -static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,,Switch axis(2D);;;1;ix=0,c1=0,c2=0,c3=0,o2=0"; +static const char _data_FX_MODE_COPY[] PROGMEM = "Copy Segment@,Color shift,Lighten,Brighten,ID,,Axis(2D),Overlay;;;12;ix=0,c1=0,c2=0,c3=0"; /*