GCC Code Coverage Report


Directory: ./
File: overlays/wled/colors.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 180 342 52.6%
Functions: 9 22 40.9%
Branches: 77 204 37.7%

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