Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Copy Segment FX #4124

Open
wants to merge 11 commits into
base: 0_15
Choose a base branch
from
34 changes: 34 additions & 0 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ static um_data_t* getAudioData() {
return um_data;
}


// effect functions

/*
Expand All @@ -93,6 +94,38 @@ uint16_t mode_static(void) {
}
static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid";

/*
* Copy a segment and perform (optional) color adjustments
*/
uint16_t mode_copy_segment(void) {
uint32_t sourceid = SEGMENT.custom3;
if (sourceid >= strip.getSegmentsNum() || sourceid == strip.getCurrSegmentId()) { // invalid source
SEGMENT.fadeToBlackBy(5); // fade out, clears pixels and allows overlapping segments
return FRAMETIME;
}
Segment sourcesegment = strip.getSegment(sourceid);
if (sourcesegment.isActive()) {
uint32_t sourcecolor;
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 = 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)
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));
}
}
}
}
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";


/*
* Blink/strobe function
Expand Down Expand Up @@ -7830,6 +7863,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);
Expand Down
7 changes: 4 additions & 3 deletions wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -847,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
Expand Down
12 changes: 12 additions & 0 deletions wled00/FX_fcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1422,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;
Expand Down
93 changes: 74 additions & 19 deletions wled00/colors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,23 @@ 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
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
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)
{
lastRandomIndex = get_random_wheel_index(lastRandomIndex);
Expand All @@ -105,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
Expand Down Expand Up @@ -206,28 +223,66 @@ 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;
}
}

void rgb2hsv(const uint32_t rgb, CHSV32& hsv) // convert RGB to HSV (16bit hue), much more accurate and faster than fastled version
{
hsv.raw = 0;
int32_t r = (rgb>>16)&0xFF;
int32_t g = (rgb>>8)&0xFF;
int32_t b = rgb&0xFF;
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; // black
hsv.v = maxval;
delta = maxval - minval;
hsv.s = (255 * delta) / maxval;
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)
void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc
{
Expand Down
29 changes: 28 additions & 1 deletion wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -81,10 +104,14 @@ class NeoGammaWLEDMethod {
[[gnu::hot]] uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false);
[[gnu::hot]] uint32_t color_add(uint32_t,uint32_t, bool fast=false);
[[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false);
uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten);
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 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
Expand Down
4 changes: 2 additions & 2 deletions wled00/ir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down