| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "wled.h" | ||
| 2 | |||
| 3 | /* | ||
| 4 | * Color conversion & utility methods | ||
| 5 | */ | ||
| 6 | |||
| 7 | /* | ||
| 8 | * color blend function, based on FastLED blend function | ||
| 9 | * the calculation for each color is: result = (A*(amountOfA) + A + B*(amountOfB) + B) / 256 with amountOfA = 255 - amountOfB | ||
| 10 | */ | ||
| 11 | 224567 | uint32_t color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { | |
| 12 | // min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance | ||
| 13 | 224567 | const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated (poorman's SIMD; https://github.com/wled/WLED/pull/4568#discussion_r1986587221) | |
| 14 | 224567 | uint32_t rb1 = color1 & TWO_CHANNEL_MASK; // extract R & B channels from color1 | |
| 15 | 224567 | uint32_t wg1 = (color1 >> 8) & TWO_CHANNEL_MASK; // extract W & G channels from color1 (shifted for multiplication later) | |
| 16 | 224567 | uint32_t rb2 = color2 & TWO_CHANNEL_MASK; // extract R & B channels from color2 | |
| 17 | 224567 | uint32_t wg2 = (color2 >> 8) & TWO_CHANNEL_MASK; // extract W & G channels from color2 (shifted for multiplication later) | |
| 18 | 224567 | uint32_t rb3 = ((((rb1 << 8) | rb2) + (rb2 * blend) - (rb1 * blend)) >> 8) & TWO_CHANNEL_MASK; // blend red and blue | |
| 19 | 224567 | uint32_t wg3 = ((((wg1 << 8) | wg2) + (wg2 * blend) - (wg1 * blend))) & ~TWO_CHANNEL_MASK; // negated mask for white and green | |
| 20 | 224567 | return rb3 | wg3; | |
| 21 | } | ||
| 22 | |||
| 23 | /* | ||
| 24 | * color add function that preserves ratio | ||
| 25 | * original idea: https://github.com/wled-dev/WLED/pull/2465 by https://github.com/Proto-molecule | ||
| 26 | * speed optimisations by @dedehai | ||
| 27 | */ | ||
| 28 | 318865 | uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR) | |
| 29 | { | ||
| 30 |
2/2✓ Branch 0 taken 91062 times.
✓ Branch 1 taken 227803 times.
|
318865 | if (c1 == BLACK) return c2; |
| 31 |
2/2✓ Branch 0 taken 38616 times.
✓ Branch 1 taken 189187 times.
|
227803 | if (c2 == BLACK) return c1; |
| 32 | 189187 | const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated | |
| 33 | 189187 | uint32_t rb = ( c1 & TWO_CHANNEL_MASK) + ( c2 & TWO_CHANNEL_MASK); // mask and add two colors at once | |
| 34 | 189187 | uint32_t wg = ((c1>>8) & TWO_CHANNEL_MASK) + ((c2>>8) & TWO_CHANNEL_MASK); | |
| 35 | 189187 | uint32_t r = rb >> 16; // extract single color values | |
| 36 | 189187 | uint32_t b = rb & 0xFFFF; | |
| 37 | 189187 | uint32_t w = wg >> 16; | |
| 38 | 189187 | uint32_t g = wg & 0xFFFF; | |
| 39 | |||
| 40 |
2/2✓ Branch 0 taken 38810 times.
✓ Branch 1 taken 150377 times.
|
189187 | if (preserveCR) { // preserve color ratios |
| 41 | 38810 | uint32_t max = std::max(r,g); // check for overflow note | |
| 42 | 38810 | max = std::max(max,b); | |
| 43 | 38810 | max = std::max(max,w); | |
| 44 | //unsigned max = r; // check for overflow note | ||
| 45 | //max = g > max ? g : max; | ||
| 46 | //max = b > max ? b : max; | ||
| 47 | //max = w > max ? w : max; | ||
| 48 |
2/2✓ Branch 0 taken 27229 times.
✓ Branch 1 taken 11581 times.
|
38810 | if (max > 255) { |
| 49 | 27229 | const uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead | |
| 50 | 27229 | rb = ((rb * scale) >> 8) & TWO_CHANNEL_MASK; | |
| 51 | 27229 | wg = (wg * scale) & ~TWO_CHANNEL_MASK; | |
| 52 | 11581 | } else wg <<= 8; //shift white and green back to correct position | |
| 53 | 38810 | return rb | wg; | |
| 54 | } else { | ||
| 55 | 150377 | r = r > 255 ? 255 : r; | |
| 56 | 150377 | g = g > 255 ? 255 : g; | |
| 57 | 150377 | b = b > 255 ? 255 : b; | |
| 58 | 150377 | w = w > 255 ? 255 : w; | |
| 59 | 150377 | return RGBW32(r,g,b,w); | |
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | /* | ||
| 64 | * fades color toward black | ||
| 65 | * if using "video" method the resulting color will never become black unless it is already black | ||
| 66 | */ | ||
| 67 | |||
| 68 | 2212039 | uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) | |
| 69 | { | ||
| 70 |
2/2✓ Branch 0 taken 1603180 times.
✓ Branch 1 taken 608859 times.
|
2212039 | if (amount == 255) return c1; |
| 71 |
4/4✓ Branch 0 taken 422749 times.
✓ Branch 1 taken 186110 times.
✓ Branch 2 taken 34983 times.
✓ Branch 3 taken 387766 times.
|
608859 | if (c1 == BLACK || amount == 0) return BLACK; |
| 72 | uint32_t scaledcolor; // color order is: W R G B from MSB to LSB | ||
| 73 | 387766 | uint32_t scale = amount; // 32bit for faster calculation | |
| 74 | 387766 | uint32_t addRemains = 0; | |
| 75 |
2/2✓ Branch 0 taken 355483 times.
✓ Branch 1 taken 32283 times.
|
387766 | if (!video) scale++; // add one for correct scaling using bitshifts |
| 76 | else { // video scaling: make sure colors do not dim to zero if they started non-zero | ||
| 77 |
2/2✓ Branch 0 taken 32282 times.
✓ Branch 1 taken 1 times.
|
32283 | addRemains = R(c1) ? 0x00010000 : 0; |
| 78 |
2/2✓ Branch 0 taken 31615 times.
✓ Branch 1 taken 668 times.
|
32283 | addRemains |= G(c1) ? 0x00000100 : 0; |
| 79 |
2/2✓ Branch 0 taken 2778 times.
✓ Branch 1 taken 29505 times.
|
32283 | addRemains |= B(c1) ? 0x00000001 : 0; |
| 80 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 32283 times.
|
32283 | addRemains |= W(c1) ? 0x01000000 : 0; |
| 81 | } | ||
| 82 | 387766 | const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; | |
| 83 | 387766 | uint32_t rb = (((c1 & TWO_CHANNEL_MASK) * scale) >> 8) & TWO_CHANNEL_MASK; // scale red and blue | |
| 84 | 387766 | uint32_t wg = (((c1 >> 8) & TWO_CHANNEL_MASK) * scale) & ~TWO_CHANNEL_MASK; // scale white and green | |
| 85 | 387766 | scaledcolor = (rb | wg) + addRemains; | |
| 86 | 387766 | return scaledcolor; | |
| 87 | } | ||
| 88 | |||
| 89 | // 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes) | ||
| 90 | 519983 | uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) | |
| 91 | { | ||
| 92 |
2/2✓ Branch 0 taken 240813 times.
✓ Branch 1 taken 279170 times.
|
519983 | if (blendType == LINEARBLEND_NOWRAP) { |
| 93 | 240813 | index = (index * 0xF0) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping | |
| 94 | } | ||
| 95 | 519983 | unsigned hi4 = byte(index) >> 4; | |
| 96 | 519983 | unsigned lo4 = (index & 0x0F); | |
| 97 | 519983 | const CRGB* entry = (CRGB*)&(pal[0]) + hi4; | |
| 98 | 519983 | unsigned red1 = entry->r; | |
| 99 | 519983 | unsigned green1 = entry->g; | |
| 100 | 519983 | unsigned blue1 = entry->b; | |
| 101 |
4/4✓ Branch 0 taken 479901 times.
✓ Branch 1 taken 40082 times.
✓ Branch 2 taken 469875 times.
✓ Branch 3 taken 10026 times.
|
519983 | if (lo4 && blendType != NOBLEND) { |
| 102 |
2/2✓ Branch 0 taken 14805 times.
✓ Branch 1 taken 455070 times.
|
484680 | if (hi4 == 15) entry = &(pal[0]); |
| 103 | 455070 | else ++entry; | |
| 104 | 469875 | unsigned f2 = (lo4 << 4); | |
| 105 | 469875 | unsigned f1 = 256 - f2; | |
| 106 | 469875 | red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; // note: using color_blend() is 20% slower | |
| 107 | 469875 | green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; | |
| 108 | 469875 | blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; | |
| 109 | } | ||
| 110 |
2/2✓ Branch 0 taken 98826 times.
✓ Branch 1 taken 421157 times.
|
519983 | if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted |
| 111 | // actually color_fade(c1, brightness) | ||
| 112 | 98826 | uint32_t scale = brightness + 1; // adjust for rounding (bitshift) | |
| 113 | 98826 | red1 = (red1 * scale) >> 8; // note: using color_fade() is 30% slower | |
| 114 | 98826 | green1 = (green1 * scale) >> 8; | |
| 115 | 98826 | blue1 = (blue1 * scale) >> 8; | |
| 116 | } | ||
| 117 | 519983 | return RGBW32(red1,green1,blue1,0); | |
| 118 | } | ||
| 119 | |||
| 120 | ✗ | void setRandomColor(byte* rgb) | |
| 121 | { | ||
| 122 | ✗ | lastRandomIndex = get_random_wheel_index(lastRandomIndex); | |
| 123 | ✗ | colorHStoRGB(lastRandomIndex*256,255,rgb); | |
| 124 | ✗ | } | |
| 125 | |||
| 126 | /* | ||
| 127 | * generates a random palette based on harmonic color theory | ||
| 128 | * takes a base palette as the input, it will choose one color of the base palette and keep it | ||
| 129 | */ | ||
| 130 | 457 | CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette) | |
| 131 | { | ||
| 132 | 457 | CHSV palettecolors[4]; // array of colors for the new palette | |
| 133 | 457 | uint8_t keepcolorposition = hw_random8(4); // color position of current random palette to keep | |
| 134 |
1/2✓ Branch 0 taken 457 times.
✗ Branch 1 not taken.
|
457 | palettecolors[keepcolorposition] = rgb2hsv(basepalette.entries[keepcolorposition*5]); // read one of the base colors of the current palette |
| 135 | 457 | palettecolors[keepcolorposition].hue += hw_random8(10)-5; // +/- 5 randomness of base color | |
| 136 | // generate 4 saturation and brightness value numbers | ||
| 137 | // only one saturation is allowed to be below 200 creating mostly vibrant colors | ||
| 138 | // only one brightness value number is allowed below 200, creating mostly bright palettes | ||
| 139 | |||
| 140 |
2/2✓ Branch 0 taken 1371 times.
✓ Branch 1 taken 457 times.
|
1828 | for (int i = 0; i < 3; i++) { // generate three high values |
| 141 | 1371 | palettecolors[i].saturation = hw_random8(200,255); | |
| 142 | 1371 | palettecolors[i].value = hw_random8(220,255); | |
| 143 | } | ||
| 144 | // allow one to be lower | ||
| 145 | 457 | palettecolors[3].saturation = hw_random8(20,255); | |
| 146 | 457 | palettecolors[3].value = hw_random8(80,255); | |
| 147 | |||
| 148 | // shuffle the arrays | ||
| 149 |
2/2✓ Branch 0 taken 1371 times.
✓ Branch 1 taken 457 times.
|
1828 | for (int i = 3; i > 0; i--) { |
| 150 | 1371 | std::swap(palettecolors[i].saturation, palettecolors[hw_random8(i + 1)].saturation); | |
| 151 | 1371 | std::swap(palettecolors[i].value, palettecolors[hw_random8(i + 1)].value); | |
| 152 | } | ||
| 153 | |||
| 154 | // now generate three new hues based off of the hue of the chosen current color | ||
| 155 | 457 | uint8_t basehue = palettecolors[keepcolorposition].hue; | |
| 156 | uint8_t harmonics[3]; // hues that are harmonic but still a little random | ||
| 157 | 457 | uint8_t type = hw_random8(5); // choose a harmony type | |
| 158 | |||
| 159 |
5/6✓ Branch 0 taken 98 times.
✓ Branch 1 taken 55 times.
✓ Branch 2 taken 63 times.
✓ Branch 3 taken 115 times.
✓ Branch 4 taken 126 times.
✗ Branch 5 not taken.
|
457 | switch (type) { |
| 160 | 98 | case 0: // analogous | |
| 161 | 98 | harmonics[0] = basehue + hw_random8(30, 50); | |
| 162 | 98 | harmonics[1] = basehue + hw_random8(10, 30); | |
| 163 | 98 | harmonics[2] = basehue - hw_random8(10, 30); | |
| 164 | 98 | break; | |
| 165 | |||
| 166 | 55 | case 1: // triadic | |
| 167 | 55 | harmonics[0] = basehue + 113 + hw_random8(15); | |
| 168 | 55 | harmonics[1] = basehue + 233 + hw_random8(15); | |
| 169 | 55 | harmonics[2] = basehue - 7 + hw_random8(15); | |
| 170 | 55 | break; | |
| 171 | |||
| 172 | 63 | case 2: // split-complementary | |
| 173 | 63 | harmonics[0] = basehue + 145 + hw_random8(10); | |
| 174 | 63 | harmonics[1] = basehue + 205 + hw_random8(10); | |
| 175 | 63 | harmonics[2] = basehue - 5 + hw_random8(10); | |
| 176 | 63 | break; | |
| 177 | |||
| 178 | 115 | case 3: // square | |
| 179 | 115 | harmonics[0] = basehue + 85 + hw_random8(10); | |
| 180 | 115 | harmonics[1] = basehue + 175 + hw_random8(10); | |
| 181 | 115 | harmonics[2] = basehue + 265 + hw_random8(10); | |
| 182 | 115 | break; | |
| 183 | |||
| 184 | 126 | case 4: // tetradic | |
| 185 | 126 | harmonics[0] = basehue + 80 + hw_random8(20); | |
| 186 | 126 | harmonics[1] = basehue + 170 + hw_random8(20); | |
| 187 | 126 | harmonics[2] = basehue - 15 + hw_random8(30); | |
| 188 | 126 | break; | |
| 189 | } | ||
| 190 | |||
| 191 |
2/2✓ Branch 0 taken 175 times.
✓ Branch 1 taken 282 times.
|
457 | if (hw_random8() < 128) { |
| 192 | // 50:50 chance of shuffling hues or keep the color order | ||
| 193 |
2/2✓ Branch 0 taken 350 times.
✓ Branch 1 taken 175 times.
|
525 | for (int i = 2; i > 0; i--) { |
| 194 | 350 | std::swap(harmonics[i], harmonics[hw_random8(i + 1)]); | |
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | // now set the hues | ||
| 199 | 457 | int j = 0; | |
| 200 |
2/2✓ Branch 0 taken 1828 times.
✓ Branch 1 taken 457 times.
|
2285 | for (int i = 0; i < 4; i++) { |
| 201 |
2/2✓ Branch 0 taken 457 times.
✓ Branch 1 taken 1371 times.
|
1828 | if (i==keepcolorposition) continue; // skip the base color |
| 202 | 1371 | palettecolors[i].hue = harmonics[j]; | |
| 203 | 1371 | j++; | |
| 204 | } | ||
| 205 | |||
| 206 | 457 | bool makepastelpalette = false; | |
| 207 |
2/2✓ Branch 0 taken 43 times.
✓ Branch 1 taken 414 times.
|
457 | if (hw_random8() < 25) { // ~10% chance of desaturated 'pastel' colors |
| 208 | 43 | makepastelpalette = true; | |
| 209 | } | ||
| 210 | |||
| 211 | // apply saturation & gamma correction | ||
| 212 | 457 | CRGB RGBpalettecolors[4]; | |
| 213 |
2/2✓ Branch 0 taken 1828 times.
✓ Branch 1 taken 457 times.
|
2285 | for (int i = 0; i < 4; i++) { |
| 214 |
4/4✓ Branch 0 taken 172 times.
✓ Branch 1 taken 1656 times.
✓ Branch 2 taken 136 times.
✓ Branch 3 taken 36 times.
|
1828 | if (makepastelpalette && palettecolors[i].saturation > 180) { |
| 215 | 136 | palettecolors[i].saturation -= 160; //desaturate all four colors | |
| 216 | } | ||
| 217 |
1/2✓ Branch 0 taken 1828 times.
✗ Branch 1 not taken.
|
1828 | RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB |
| 218 |
1/2✓ Branch 0 taken 1828 times.
✗ Branch 1 not taken.
|
1828 | RGBpalettecolors[i] = gamma32(((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU); //strip alpha from CRGB |
| 219 | } | ||
| 220 | |||
| 221 | return CRGBPalette16(RGBpalettecolors[0], | ||
| 222 | RGBpalettecolors[1], | ||
| 223 | RGBpalettecolors[2], | ||
| 224 |
1/2✓ Branch 0 taken 457 times.
✗ Branch 1 not taken.
|
914 | RGBpalettecolors[3]); |
| 225 | } | ||
| 226 | |||
| 227 | 4692 | CRGBPalette16 generateRandomPalette() // generate fully random palette | |
| 228 | { | ||
| 229 | 4692 | return CRGBPalette16(CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)), | |
| 230 | 4692 | CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)), | |
| 231 | 4692 | CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255)), | |
| 232 |
1/2✓ Branch 0 taken 4692 times.
✗ Branch 1 not taken.
|
13684 | CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255))); |
| 233 | } | ||
| 234 | |||
| 235 | 486 | void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0) | |
| 236 | { | ||
| 237 | unsigned int remainder, region, p, q, t; | ||
| 238 | 486 | unsigned int h = hsv.h; | |
| 239 | 486 | unsigned int s = hsv.s; | |
| 240 | 486 | unsigned int v = hsv.v; | |
| 241 |
2/2✓ Branch 0 taken 49 times.
✓ Branch 1 taken 437 times.
|
486 | if (s == 0) { |
| 242 | 49 | rgb = v << 16 | v << 8 | v; | |
| 243 | 49 | return; | |
| 244 | } | ||
| 245 | 437 | region = h / 10923; // 65536 / 6 = 10923 | |
| 246 | 437 | remainder = (h - (region * 10923)) * 6; | |
| 247 | 437 | p = (v * (255 - s)) >> 8; | |
| 248 | 437 | q = (v * (255 - ((s * remainder) >> 16))) >> 8; | |
| 249 | 437 | t = (v * (255 - ((s * (65535 - remainder)) >> 16))) >> 8; | |
| 250 |
6/6✓ Branch 0 taken 257 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 40 times.
✓ Branch 4 taken 52 times.
✓ Branch 5 taken 58 times.
|
437 | switch (region) { |
| 251 | 257 | case 0: | |
| 252 | 257 | rgb = v << 16 | t << 8 | p; break; | |
| 253 | 8 | case 1: | |
| 254 | 8 | rgb = q << 16 | v << 8 | p; break; | |
| 255 | 22 | case 2: | |
| 256 | 22 | rgb = p << 16 | v << 8 | t; break; | |
| 257 | 40 | case 3: | |
| 258 | 40 | rgb = p << 16 | q << 8 | v; break; | |
| 259 | 52 | case 4: | |
| 260 | 52 | rgb = t << 16 | p << 8 | v; break; | |
| 261 | 58 | default: | |
| 262 | 58 | rgb = v << 16 | p << 8 | q; break; | |
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | // shift hue and change saturation/value of a colour in place, used by "Copy Segment" | ||
| 267 | // note: the W channel is carried through untouched, the HSV round trip only covers RGB | ||
| 268 | ✗ | void adjust_color(uint32_t& rgb, int32_t hueShift, int32_t satChange, int32_t valueChange) { | |
| 269 | ✗ | if ((rgb & 0x00FFFFFF) == 0 && valueChange <= 0) return; // black and no value change -> stays black | |
| 270 | ✗ | const uint32_t w = rgb & 0xFF000000; | |
| 271 | CHSV32 hsv; | ||
| 272 | ✗ | rgb2hsv(rgb, hsv); | |
| 273 | ✗ | hsv.h += (hueShift << 8); // hue is 16 bits, shift is 8 | |
| 274 | ✗ | int32_t s = (int32_t)hsv.s + satChange; | |
| 275 | ✗ | int32_t v = (int32_t)hsv.v + valueChange; | |
| 276 | ✗ | hsv.s = s < 0 ? 0 : (s > 255 ? 255 : s); | |
| 277 | ✗ | hsv.v = v < 0 ? 0 : (v > 255 ? 255 : v); | |
| 278 | uint32_t out; | ||
| 279 | ✗ | hsv2rgb(hsv, out); | |
| 280 | ✗ | rgb = (out & 0x00FFFFFF) | w; | |
| 281 | } | ||
| 282 | |||
| 283 | 943 | void rgb2hsv(const uint32_t rgb, CHSV32& hsv) // convert RGB to HSV (16bit hue), much more accurate and faster than fastled version | |
| 284 | { | ||
| 285 | 943 | hsv.raw = 0; | |
| 286 | 943 | int32_t r = (rgb>>16)&0xFF; | |
| 287 | 943 | int32_t g = (rgb>>8)&0xFF; | |
| 288 | 943 | int32_t b = rgb&0xFF; | |
| 289 | int32_t minval, maxval, delta; | ||
| 290 | 943 | minval = min(r, g); | |
| 291 | 943 | minval = min(minval, b); | |
| 292 | 943 | maxval = max(r, g); | |
| 293 | 943 | maxval = max(maxval, b); | |
| 294 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 943 times.
|
943 | if (maxval == 0) return; // black |
| 295 | 943 | hsv.v = maxval; | |
| 296 | 943 | delta = maxval - minval; | |
| 297 | 943 | hsv.s = (255 * delta) / maxval; | |
| 298 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 943 times.
|
943 | if (hsv.s == 0) return; // gray value |
| 299 |
2/2✓ Branch 0 taken 648 times.
✓ Branch 1 taken 295 times.
|
943 | if (maxval == r) hsv.h = (10923 * (g - b)) / delta; |
| 300 |
2/2✓ Branch 0 taken 85 times.
✓ Branch 1 taken 210 times.
|
295 | else if (maxval == g) hsv.h = 21845 + (10923 * (b - r)) / delta; |
| 301 | 210 | else hsv.h = 43690 + (10923 * (r - g)) / delta; | |
| 302 | } | ||
| 303 | |||
| 304 | ✗ | void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) { //hue, sat to rgb | |
| 305 | uint32_t crgb; | ||
| 306 | ✗ | hsv2rgb(CHSV32(hue, sat, 255), crgb); | |
| 307 | ✗ | rgb[0] = byte((crgb) >> 16); | |
| 308 | ✗ | rgb[1] = byte((crgb) >> 8); | |
| 309 | ✗ | rgb[2] = byte(crgb); | |
| 310 | ✗ | } | |
| 311 | |||
| 312 | //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) | ||
| 313 | ✗ | void colorKtoRGB(uint16_t kelvin, byte* rgb) //white spectrum to rgb, calc | |
| 314 | { | ||
| 315 | ✗ | int r = 0, g = 0, b = 0; | |
| 316 | ✗ | float temp = kelvin / 100.0f; | |
| 317 | ✗ | if (temp <= 66.0f) { | |
| 318 | ✗ | r = 255; | |
| 319 | ✗ | g = roundf(99.4708025861f * logf(temp) - 161.1195681661f); | |
| 320 | ✗ | if (temp <= 19.0f) { | |
| 321 | ✗ | b = 0; | |
| 322 | } else { | ||
| 323 | ✗ | b = roundf(138.5177312231f * logf((temp - 10.0f)) - 305.0447927307f); | |
| 324 | } | ||
| 325 | } else { | ||
| 326 | ✗ | r = roundf(329.698727446f * powf((temp - 60.0f), -0.1332047592f)); | |
| 327 | ✗ | g = roundf(288.1221695283f * powf((temp - 60.0f), -0.0755148492f)); | |
| 328 | ✗ | b = 255; | |
| 329 | } | ||
| 330 | //g += 12; //mod by Aircoookie, a bit less accurate but visibly less pinkish | ||
| 331 | ✗ | rgb[0] = (uint8_t) constrain(r, 0, 255); | |
| 332 | ✗ | rgb[1] = (uint8_t) constrain(g, 0, 255); | |
| 333 | ✗ | rgb[2] = (uint8_t) constrain(b, 0, 255); | |
| 334 | ✗ | rgb[3] = 0; | |
| 335 | ✗ | } | |
| 336 | |||
| 337 | ✗ | void colorCTtoRGB(uint16_t mired, byte* rgb) //white spectrum to rgb, bins | |
| 338 | { | ||
| 339 | //this is only an approximation using WS2812B with gamma correction enabled | ||
| 340 | ✗ | if (mired > 475) { | |
| 341 | ✗ | rgb[0]=255;rgb[1]=199;rgb[2]=92;//500 | |
| 342 | ✗ | } else if (mired > 425) { | |
| 343 | ✗ | rgb[0]=255;rgb[1]=213;rgb[2]=118;//450 | |
| 344 | ✗ | } else if (mired > 375) { | |
| 345 | ✗ | rgb[0]=255;rgb[1]=216;rgb[2]=118;//400 | |
| 346 | ✗ | } else if (mired > 325) { | |
| 347 | ✗ | rgb[0]=255;rgb[1]=234;rgb[2]=140;//350 | |
| 348 | ✗ | } else if (mired > 275) { | |
| 349 | ✗ | rgb[0]=255;rgb[1]=243;rgb[2]=160;//300 | |
| 350 | ✗ | } else if (mired > 225) { | |
| 351 | ✗ | rgb[0]=250;rgb[1]=255;rgb[2]=188;//250 | |
| 352 | ✗ | } else if (mired > 175) { | |
| 353 | ✗ | rgb[0]=247;rgb[1]=255;rgb[2]=215;//200 | |
| 354 | } else { | ||
| 355 | ✗ | rgb[0]=237;rgb[1]=255;rgb[2]=239;//150 | |
| 356 | } | ||
| 357 | ✗ | } | |
| 358 | |||
| 359 | #ifndef WLED_DISABLE_HUESYNC | ||
| 360 | ✗ | void colorXYtoRGB(float x, float y, byte* rgb) //coordinates to rgb (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy) | |
| 361 | { | ||
| 362 | ✗ | float z = 1.0f - x - y; | |
| 363 | ✗ | float X = (1.0f / y) * x; | |
| 364 | ✗ | float Z = (1.0f / y) * z; | |
| 365 | ✗ | float r = (int)255*(X * 1.656492f - 0.354851f - Z * 0.255038f); | |
| 366 | ✗ | float g = (int)255*(-X * 0.707196f + 1.655397f + Z * 0.036152f); | |
| 367 | ✗ | float b = (int)255*(X * 0.051713f - 0.121364f + Z * 1.011530f); | |
| 368 | ✗ | if (r > b && r > g && r > 1.0f) { | |
| 369 | // red is too big | ||
| 370 | ✗ | g = g / r; | |
| 371 | ✗ | b = b / r; | |
| 372 | ✗ | r = 1.0f; | |
| 373 | ✗ | } else if (g > b && g > r && g > 1.0f) { | |
| 374 | // green is too big | ||
| 375 | ✗ | r = r / g; | |
| 376 | ✗ | b = b / g; | |
| 377 | ✗ | g = 1.0f; | |
| 378 | ✗ | } else if (b > r && b > g && b > 1.0f) { | |
| 379 | // blue is too big | ||
| 380 | ✗ | r = r / b; | |
| 381 | ✗ | g = g / b; | |
| 382 | ✗ | b = 1.0f; | |
| 383 | } | ||
| 384 | // Apply gamma correction | ||
| 385 | ✗ | r = r <= 0.0031308f ? 12.92f * r : (1.0f + 0.055f) * powf(r, (1.0f / 2.4f)) - 0.055f; | |
| 386 | ✗ | g = g <= 0.0031308f ? 12.92f * g : (1.0f + 0.055f) * powf(g, (1.0f / 2.4f)) - 0.055f; | |
| 387 | ✗ | b = b <= 0.0031308f ? 12.92f * b : (1.0f + 0.055f) * powf(b, (1.0f / 2.4f)) - 0.055f; | |
| 388 | |||
| 389 | ✗ | if (r > b && r > g) { | |
| 390 | // red is biggest | ||
| 391 | ✗ | if (r > 1.0f) { | |
| 392 | ✗ | g = g / r; | |
| 393 | ✗ | b = b / r; | |
| 394 | ✗ | r = 1.0f; | |
| 395 | } | ||
| 396 | ✗ | } else if (g > b && g > r) { | |
| 397 | // green is biggest | ||
| 398 | ✗ | if (g > 1.0f) { | |
| 399 | ✗ | r = r / g; | |
| 400 | ✗ | b = b / g; | |
| 401 | ✗ | g = 1.0f; | |
| 402 | } | ||
| 403 | ✗ | } else if (b > r && b > g) { | |
| 404 | // blue is biggest | ||
| 405 | ✗ | if (b > 1.0f) { | |
| 406 | ✗ | r = r / b; | |
| 407 | ✗ | g = g / b; | |
| 408 | ✗ | b = 1.0f; | |
| 409 | } | ||
| 410 | } | ||
| 411 | ✗ | rgb[0] = byte(255.0f*r); | |
| 412 | ✗ | rgb[1] = byte(255.0f*g); | |
| 413 | ✗ | rgb[2] = byte(255.0f*b); | |
| 414 | ✗ | } | |
| 415 | |||
| 416 | ✗ | void colorRGBtoXY(const byte* rgb, float* xy) //rgb to coordinates (https://www.developers.meethue.com/documentation/color-conversions-rgb-xy) | |
| 417 | { | ||
| 418 | ✗ | float X = rgb[0] * 0.664511f + rgb[1] * 0.154324f + rgb[2] * 0.162028f; | |
| 419 | ✗ | float Y = rgb[0] * 0.283881f + rgb[1] * 0.668433f + rgb[2] * 0.047685f; | |
| 420 | ✗ | float Z = rgb[0] * 0.000088f + rgb[1] * 0.072310f + rgb[2] * 0.986039f; | |
| 421 | ✗ | xy[0] = X / (X + Y + Z); | |
| 422 | ✗ | xy[1] = Y / (X + Y + Z); | |
| 423 | ✗ | } | |
| 424 | #endif // WLED_DISABLE_HUESYNC | ||
| 425 | |||
| 426 | //RRGGBB / WWRRGGBB order for hex | ||
| 427 | ✗ | void colorFromDecOrHexString(byte* rgb, const char* in) | |
| 428 | { | ||
| 429 | ✗ | if (in[0] == 0) return; | |
| 430 | ✗ | char first = in[0]; | |
| 431 | ✗ | uint32_t c = 0; | |
| 432 | |||
| 433 | ✗ | if (first == '#' || first == 'h' || first == 'H') //is HEX encoded | |
| 434 | { | ||
| 435 | ✗ | c = strtoul(in +1, NULL, 16); | |
| 436 | } else | ||
| 437 | { | ||
| 438 | ✗ | c = strtoul(in, NULL, 10); | |
| 439 | } | ||
| 440 | |||
| 441 | ✗ | rgb[0] = R(c); | |
| 442 | ✗ | rgb[1] = G(c); | |
| 443 | ✗ | rgb[2] = B(c); | |
| 444 | ✗ | rgb[3] = W(c); | |
| 445 | } | ||
| 446 | |||
| 447 | //contrary to the colorFromDecOrHexString() function, this uses the more standard RRGGBB / RRGGBBWW order | ||
| 448 | ✗ | bool colorFromHexString(byte* rgb, const char* in) { | |
| 449 | ✗ | if (in == nullptr) return false; | |
| 450 | ✗ | size_t inputSize = strnlen(in, 9); | |
| 451 | ✗ | if (inputSize != 6 && inputSize != 8) return false; | |
| 452 | |||
| 453 | ✗ | uint32_t c = strtoul(in, NULL, 16); | |
| 454 | |||
| 455 | ✗ | if (inputSize == 6) { | |
| 456 | ✗ | rgb[0] = (c >> 16); | |
| 457 | ✗ | rgb[1] = (c >> 8); | |
| 458 | ✗ | rgb[2] = c ; | |
| 459 | } else { | ||
| 460 | ✗ | rgb[0] = (c >> 24); | |
| 461 | ✗ | rgb[1] = (c >> 16); | |
| 462 | ✗ | rgb[2] = (c >> 8); | |
| 463 | ✗ | rgb[3] = c ; | |
| 464 | } | ||
| 465 | ✗ | return true; | |
| 466 | } | ||
| 467 | |||
| 468 | static inline float minf(float v, float w) | ||
| 469 | { | ||
| 470 | if (w > v) return v; | ||
| 471 | return w; | ||
| 472 | } | ||
| 473 | |||
| 474 | static inline float maxf(float v, float w) | ||
| 475 | { | ||
| 476 | if (w > v) return w; | ||
| 477 | return v; | ||
| 478 | } | ||
| 479 | |||
| 480 | // adjust RGB values based on color temperature in K (range [2800-10200]) (https://en.wikipedia.org/wiki/Color_balance) | ||
| 481 | // called from bus manager when color correction is enabled! | ||
| 482 | ✗ | uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb) | |
| 483 | { | ||
| 484 | //remember so that slow colorKtoRGB() doesn't have to run for every setPixelColor() | ||
| 485 | static byte correctionRGB[4] = {0,0,0,0}; | ||
| 486 | static uint16_t lastKelvin = 0; | ||
| 487 | ✗ | if (lastKelvin != kelvin) colorKtoRGB(kelvin, correctionRGB); // convert Kelvin to RGB | |
| 488 | ✗ | lastKelvin = kelvin; | |
| 489 | byte rgbw[4]; | ||
| 490 | ✗ | rgbw[0] = ((uint16_t) correctionRGB[0] * R(rgb)) /255; // correct R | |
| 491 | ✗ | rgbw[1] = ((uint16_t) correctionRGB[1] * G(rgb)) /255; // correct G | |
| 492 | ✗ | rgbw[2] = ((uint16_t) correctionRGB[2] * B(rgb)) /255; // correct B | |
| 493 | ✗ | rgbw[3] = W(rgb); | |
| 494 | ✗ | return RGBW32(rgbw[0],rgbw[1],rgbw[2],rgbw[3]); | |
| 495 | } | ||
| 496 | |||
| 497 | //approximates a Kelvin color temperature from an RGB color. | ||
| 498 | //this does no check for the "whiteness" of the color, | ||
| 499 | //so should be used combined with a saturation check (as done by auto-white) | ||
| 500 | //values from http://www.vendian.org/mncharity/dir3/blackbody/UnstableURLs/bbr_color.html (10deg) | ||
| 501 | //equation spreadsheet at https://bit.ly/30RkHaN | ||
| 502 | //accuracy +-50K from 1900K up to 8000K | ||
| 503 | //minimum returned: 1900K, maximum returned: 10091K (range of 8192) | ||
| 504 | ✗ | uint16_t approximateKelvinFromRGB(uint32_t rgb) { | |
| 505 | //if not either red or blue is 255, color is dimmed. Scale up | ||
| 506 | ✗ | uint8_t r = R(rgb), b = B(rgb); | |
| 507 | ✗ | if (r == b) return 6550; //red == blue at about 6600K (also can't go further if both R and B are 0) | |
| 508 | |||
| 509 | ✗ | if (r > b) { | |
| 510 | //scale blue up as if red was at 255 | ||
| 511 | ✗ | uint16_t scale = 0xFFFF / r; //get scale factor (range 257-65535) | |
| 512 | ✗ | b = ((uint16_t)b * scale) >> 8; | |
| 513 | //For all temps K<6600 R is bigger than B (for full bri colors R=255) | ||
| 514 | //-> Use 9 linear approximations for blackbody radiation blue values from 2000-6600K (blue is always 0 below 2000K) | ||
| 515 | ✗ | if (b < 33) return 1900 + b *6; | |
| 516 | ✗ | if (b < 72) return 2100 + (b-33) *10; | |
| 517 | ✗ | if (b < 101) return 2492 + (b-72) *14; | |
| 518 | ✗ | if (b < 132) return 2900 + (b-101) *16; | |
| 519 | ✗ | if (b < 159) return 3398 + (b-132) *19; | |
| 520 | ✗ | if (b < 186) return 3906 + (b-159) *22; | |
| 521 | ✗ | if (b < 210) return 4500 + (b-186) *25; | |
| 522 | ✗ | if (b < 230) return 5100 + (b-210) *30; | |
| 523 | ✗ | return 5700 + (b-230) *34; | |
| 524 | } else { | ||
| 525 | //scale red up as if blue was at 255 | ||
| 526 | ✗ | uint16_t scale = 0xFFFF / b; //get scale factor (range 257-65535) | |
| 527 | ✗ | r = ((uint16_t)r * scale) >> 8; | |
| 528 | //For all temps K>6600 B is bigger than R (for full bri colors B=255) | ||
| 529 | //-> Use 2 linear approximations for blackbody radiation red values from 6600-10091K (blue is always 0 below 2000K) | ||
| 530 | ✗ | if (r > 225) return 6600 + (254-r) *50; | |
| 531 | ✗ | uint16_t k = 8080 + (225-r) *86; | |
| 532 | ✗ | return (k > 10091) ? 10091 : k; | |
| 533 | } | ||
| 534 | } | ||
| 535 | |||
| 536 | // gamma lookup table used for color correction (filled on 1st use (cfg.cpp & set.cpp)) | ||
| 537 | uint8_t NeoGammaWLEDMethod::gammaT[256]; | ||
| 538 | |||
| 539 | // re-calculates & fills gamma table | ||
| 540 | ✗ | void NeoGammaWLEDMethod::calcGammaTable(float gamma) | |
| 541 | { | ||
| 542 | ✗ | for (size_t i = 0; i < 256; i++) { | |
| 543 | ✗ | gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f); | |
| 544 | } | ||
| 545 | ✗ | } | |
| 546 | |||
| 547 | ✗ | uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value) | |
| 548 | { | ||
| 549 | ✗ | if (!gammaCorrectCol) return value; | |
| 550 | return gammaT[value]; | ||
| 551 | } | ||
| 552 | |||
| 553 | // used for color gamma correction | ||
| 554 | 28882 | uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct32(uint32_t color) | |
| 555 | { | ||
| 556 | 28882 | if (!gammaCorrectCol) return color; | |
| 557 | uint8_t w = W(color); | ||
| 558 | uint8_t r = R(color); | ||
| 559 | uint8_t g = G(color); | ||
| 560 | uint8_t b = B(color); | ||
| 561 | w = gammaT[w]; | ||
| 562 | r = gammaT[r]; | ||
| 563 | g = gammaT[g]; | ||
| 564 | b = gammaT[b]; | ||
| 565 | return RGBW32(r, g, b, w); | ||
| 566 | } | ||
| 567 |