FastLED 3.9.15
Loading...
Searching...
No Matches
colorutils.cpp
Go to the documentation of this file.
1#define FASTLED_INTERNAL
2#define __PROG_TYPES_COMPAT__
3
7
8#include "fl/int.h"
9#include <math.h>
10#include "fl/stdint.h"
11
12#include "FastLED.h"
13#include "fl/assert.h"
14#include "fl/colorutils.h"
15#include "fl/unused.h"
16#include "fl/xymap.h"
17
18namespace fl {
19
20CRGB &nblend(CRGB &existing, const CRGB &overlay, fract8 amountOfOverlay) {
21 if (amountOfOverlay == 0) {
22 return existing;
23 }
24
25 if (amountOfOverlay == 255) {
26 existing = overlay;
27 return existing;
28 }
29
30#if 0
31 // Old blend method which unfortunately had some rounding errors
32 fract8 amountOfKeep = 255 - amountOfOverlay;
33
34 existing.red = scale8_LEAVING_R1_DIRTY( existing.red, amountOfKeep)
35 + scale8_LEAVING_R1_DIRTY( overlay.red, amountOfOverlay);
36 existing.green = scale8_LEAVING_R1_DIRTY( existing.green, amountOfKeep)
37 + scale8_LEAVING_R1_DIRTY( overlay.green, amountOfOverlay);
38 existing.blue = scale8_LEAVING_R1_DIRTY( existing.blue, amountOfKeep)
39 + scale8_LEAVING_R1_DIRTY( overlay.blue, amountOfOverlay);
40
41 cleanup_R1();
42#else
43 // Corrected blend method, with no loss-of-precision rounding errors
44 existing.red = blend8(existing.red, overlay.red, amountOfOverlay);
45 existing.green = blend8(existing.green, overlay.green, amountOfOverlay);
46 existing.blue = blend8(existing.blue, overlay.blue, amountOfOverlay);
47#endif
48
49 return existing;
50}
51
52void nblend(CRGB *existing, const CRGB *overlay, fl::u16 count,
53 fract8 amountOfOverlay) {
54 for (fl::u16 i = count; i; --i) {
55 nblend(*existing, *overlay, amountOfOverlay);
56 ++existing;
57 ++overlay;
58 }
59}
60
61CRGB blend(const CRGB &p1, const CRGB &p2, fract8 amountOfP2) {
62 CRGB nu(p1);
63 nblend(nu, p2, amountOfP2);
64 return nu;
65}
66
67CRGB *blend(const CRGB *src1, const CRGB *src2, CRGB *dest, fl::u16 count,
68 fract8 amountOfsrc2) {
69 for (fl::u16 i = 0; i < count; ++i) {
70 dest[i] = blend(src1[i], src2[i], amountOfsrc2);
71 }
72 return dest;
73}
74
75CHSV &nblend(CHSV &existing, const CHSV &overlay, fract8 amountOfOverlay,
76 TGradientDirectionCode directionCode) {
77 if (amountOfOverlay == 0) {
78 return existing;
79 }
80
81 if (amountOfOverlay == 255) {
82 existing = overlay;
83 return existing;
84 }
85
86 fract8 amountOfKeep = 255 - amountOfOverlay;
87
88 fl::u8 huedelta8 = overlay.hue - existing.hue;
89
90 if (directionCode == SHORTEST_HUES) {
91 directionCode = FORWARD_HUES;
92 if (huedelta8 > 127) {
93 directionCode = BACKWARD_HUES;
94 }
95 }
96
97 if (directionCode == LONGEST_HUES) {
98 directionCode = FORWARD_HUES;
99 if (huedelta8 < 128) {
100 directionCode = BACKWARD_HUES;
101 }
102 }
103
104 if (directionCode == FORWARD_HUES) {
105 existing.hue = existing.hue + scale8(huedelta8, amountOfOverlay);
106 } else /* directionCode == BACKWARD_HUES */
107 {
108 huedelta8 = -huedelta8;
109 existing.hue = existing.hue - scale8(huedelta8, amountOfOverlay);
110 }
111
112 existing.sat = scale8_LEAVING_R1_DIRTY(existing.sat, amountOfKeep) +
113 scale8_LEAVING_R1_DIRTY(overlay.sat, amountOfOverlay);
114 existing.val = scale8_LEAVING_R1_DIRTY(existing.val, amountOfKeep) +
115 scale8_LEAVING_R1_DIRTY(overlay.val, amountOfOverlay);
116
117 cleanup_R1();
118
119 return existing;
120}
121
122void nblend(CHSV *existing, const CHSV *overlay, fl::u16 count,
123 fract8 amountOfOverlay, TGradientDirectionCode directionCode) {
124 if (existing == overlay)
125 return;
126 for (fl::u16 i = count; i; --i) {
127 nblend(*existing, *overlay, amountOfOverlay, directionCode);
128 ++existing;
129 ++overlay;
130 }
131}
132
133CHSV blend(const CHSV &p1, const CHSV &p2, fract8 amountOfP2,
134 TGradientDirectionCode directionCode) {
135 CHSV nu(p1);
136 nblend(nu, p2, amountOfP2, directionCode);
137 return nu;
138}
139
140CHSV *blend(const CHSV *src1, const CHSV *src2, CHSV *dest, fl::u16 count,
141 fract8 amountOfsrc2, TGradientDirectionCode directionCode) {
142 for (fl::u16 i = 0; i < count; ++i) {
143 dest[i] = blend(src1[i], src2[i], amountOfsrc2, directionCode);
144 }
145 return dest;
146}
147
148void nscale8_video(CRGB *leds, fl::u16 num_leds, fl::u8 scale) {
149 for (fl::u16 i = 0; i < num_leds; ++i) {
150 leds[i].nscale8_video(scale);
151 }
152}
153
154void fade_video(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) {
155 nscale8_video(leds, num_leds, 255 - fadeBy);
156}
157
158void fadeLightBy(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) {
159 nscale8_video(leds, num_leds, 255 - fadeBy);
160}
161
162void fadeToBlackBy(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) {
163 nscale8(leds, num_leds, 255 - fadeBy);
164}
165
166void fade_raw(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) {
167 nscale8(leds, num_leds, 255 - fadeBy);
168}
169
170void nscale8(CRGB *leds, fl::u16 num_leds, fl::u8 scale) {
171 for (fl::u16 i = 0; i < num_leds; ++i) {
172 leds[i].nscale8(scale);
173 }
174}
175
176void fadeUsingColor(CRGB *leds, fl::u16 numLeds, const CRGB &colormask) {
177 fl::u8 fr, fg, fb;
178 fr = colormask.r;
179 fg = colormask.g;
180 fb = colormask.b;
181
182 for (fl::u16 i = 0; i < numLeds; ++i) {
183 leds[i].r = scale8_LEAVING_R1_DIRTY(leds[i].r, fr);
184 leds[i].g = scale8_LEAVING_R1_DIRTY(leds[i].g, fg);
185 leds[i].b = scale8(leds[i].b, fb);
186 }
187}
188
189// CRGB HeatColor( fl::u8 temperature)
190//
191// Approximates a 'black body radiation' spectrum for
192// a given 'heat' level. This is useful for animations of 'fire'.
193// Heat is specified as an arbitrary scale from 0 (cool) to 255 (hot).
194// This is NOT a chromatically correct 'black body radiation'
195// spectrum, but it's surprisingly close, and it's fast and small.
196//
197// On AVR/Arduino, this typically takes around 70 bytes of program memory,
198// versus 768 bytes for a full 256-entry RGB lookup table.
199
200CRGB HeatColor(fl::u8 temperature) {
201 CRGB heatcolor;
202
203 // Scale 'heat' down from 0-255 to 0-191,
204 // which can then be easily divided into three
205 // equal 'thirds' of 64 units each.
206 fl::u8 t192 = scale8_video(temperature, 191);
207
208 // calculate a value that ramps up from
209 // zero to 255 in each 'third' of the scale.
210 fl::u8 heatramp = t192 & 0x3F; // 0..63
211 heatramp <<= 2; // scale up to 0..252
212
213 // now figure out which third of the spectrum we're in:
214 if (t192 & 0x80) {
215 // we're in the hottest third
216 heatcolor.r = 255; // full red
217 heatcolor.g = 255; // full green
218 heatcolor.b = heatramp; // ramp up blue
219
220 } else if (t192 & 0x40) {
221 // we're in the middle third
222 heatcolor.r = 255; // full red
223 heatcolor.g = heatramp; // ramp up green
224 heatcolor.b = 0; // no blue
225
226 } else {
227 // we're in the coolest third
228 heatcolor.r = heatramp; // ramp up red
229 heatcolor.g = 0; // no green
230 heatcolor.b = 0; // no blue
231 }
232
233 return heatcolor;
234}
235
240inline fl::u8 lsrX4(fl::u8 dividend) __attribute__((always_inline));
241inline fl::u8 lsrX4(fl::u8 dividend) {
242#if defined(__AVR__)
243 dividend /= 2;
244 dividend /= 2;
245 dividend /= 2;
246 dividend /= 2;
247#else
248 dividend >>= 4;
249#endif
250 return dividend;
251}
252
253CRGB ColorFromPaletteExtended(const CRGBPalette32 &pal, fl::u16 index,
254 fl::u8 brightness, TBlendType blendType) {
255 // Extract the five most significant bits of the index as a palette index.
256 fl::u8 index_5bit = (index >> 11);
257 // Calculate the 8-bit offset from the palette index.
258 fl::u8 offset = (fl::u8)(index >> 3);
259 // Get the palette entry from the 5-bit index
260 const CRGB *entry = &(pal[0]) + index_5bit;
261 fl::u8 red1 = entry->red;
262 fl::u8 green1 = entry->green;
263 fl::u8 blue1 = entry->blue;
264
265 fl::u8 blend = offset && (blendType != NOBLEND);
266 if (blend) {
267 if (index_5bit == 31) {
268 entry = &(pal[0]);
269 } else {
270 entry++;
271 }
272
273 // Calculate the scaling factor and scaled values for the lower palette
274 // value.
275 fl::u8 f1 = 255 - offset;
276 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
277 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
278 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
279
280 // Calculate the scaled values for the neighbouring palette value.
281 fl::u8 red2 = entry->red;
282 fl::u8 green2 = entry->green;
283 fl::u8 blue2 = entry->blue;
284 red2 = scale8_LEAVING_R1_DIRTY(red2, offset);
285 green2 = scale8_LEAVING_R1_DIRTY(green2, offset);
286 blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset);
287 cleanup_R1();
288
289 // These sums can't overflow, so no qadd8 needed.
290 red1 += red2;
291 green1 += green2;
292 blue1 += blue2;
293 }
294 if (brightness != 255) {
295 nscale8x3_video(red1, green1, blue1, brightness);
296 }
297 return CRGB(red1, green1, blue1);
298}
299
300CRGB ColorFromPalette(const CRGBPalette16 &pal, fl::u8 index,
301 fl::u8 brightness, TBlendType blendType) {
302 if (blendType == LINEARBLEND_NOWRAP) {
303 index = map8(index, 0, 239); // Blend range is affected by lo4 blend of
304 // values, remap to avoid wrapping
305 }
306
307 // hi4 = index >> 4;
308 fl::u8 hi4 = lsrX4(index);
309 fl::u8 lo4 = index & 0x0F;
310
311 // const CRGB* entry = &(pal[0]) + hi4;
312 // since hi4 is always 0..15, hi4 * sizeof(CRGB) can be a single-byte value,
313 // instead of the two byte 'int' that avr-gcc defaults to.
314 // So, we multiply hi4 X sizeof(CRGB), giving hi4XsizeofCRGB;
315 fl::u8 hi4XsizeofCRGB = hi4 * sizeof(CRGB);
316 // We then add that to a base array pointer.
317 const CRGB *entry = (CRGB *)((fl::u8 *)(&(pal[0])) + hi4XsizeofCRGB);
318
319 fl::u8 blend = lo4 && (blendType != NOBLEND);
320
321 fl::u8 red1 = entry->red;
322 fl::u8 green1 = entry->green;
323 fl::u8 blue1 = entry->blue;
324
325 if (blend) {
326
327 if (hi4 == 15) {
328 entry = &(pal[0]);
329 } else {
330 ++entry;
331 }
332
333 fl::u8 f2 = lo4 << 4;
334 fl::u8 f1 = 255 - f2;
335
336 // rgb1.nscale8(f1);
337 fl::u8 red2 = entry->red;
338 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
339 red2 = scale8_LEAVING_R1_DIRTY(red2, f2);
340 red1 += red2;
341
342 fl::u8 green2 = entry->green;
343 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
344 green2 = scale8_LEAVING_R1_DIRTY(green2, f2);
345 green1 += green2;
346
347 fl::u8 blue2 = entry->blue;
348 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
349 blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2);
350 blue1 += blue2;
351
352 cleanup_R1();
353 }
354
355 if (brightness != 255) {
356 if (brightness) {
357 ++brightness; // adjust for rounding
358 // Now, since brightness is nonzero, we don't need the full
359 // scale8_video logic; we can just to scale8 and then add one
360 // (unless scale8 fixed) to all nonzero inputs.
361 if (red1) {
363#if !(FASTLED_SCALE8_FIXED == 1)
364 ++red1;
365#endif
366 }
367 if (green1) {
368 green1 = scale8_LEAVING_R1_DIRTY(green1, brightness);
369#if !(FASTLED_SCALE8_FIXED == 1)
370 ++green1;
371#endif
372 }
373 if (blue1) {
374 blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness);
375#if !(FASTLED_SCALE8_FIXED == 1)
376 ++blue1;
377#endif
378 }
379 cleanup_R1();
380 } else {
381 red1 = 0;
382 green1 = 0;
383 blue1 = 0;
384 }
385 }
386
387 return CRGB(red1, green1, blue1);
388}
389
390CRGB ColorFromPaletteExtended(const CRGBPalette16 &pal, fl::u16 index,
391 fl::u8 brightness, TBlendType blendType) {
392 // Extract the four most significant bits of the index as a palette index.
393 fl::u8 index_4bit = index >> 12;
394 // Calculate the 8-bit offset from the palette index.
395 fl::u8 offset = (fl::u8)(index >> 4);
396 // Get the palette entry from the 4-bit index
397 const CRGB *entry = &(pal[0]) + index_4bit;
398 fl::u8 red1 = entry->red;
399 fl::u8 green1 = entry->green;
400 fl::u8 blue1 = entry->blue;
401
402 fl::u8 blend = offset && (blendType != NOBLEND);
403 if (blend) {
404 if (index_4bit == 15) {
405 entry = &(pal[0]);
406 } else {
407 entry++;
408 }
409
410 // Calculate the scaling factor and scaled values for the lower palette
411 // value.
412 fl::u8 f1 = 255 - offset;
413 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
414 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
415 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
416
417 // Calculate the scaled values for the neighbouring palette value.
418 fl::u8 red2 = entry->red;
419 fl::u8 green2 = entry->green;
420 fl::u8 blue2 = entry->blue;
421 red2 = scale8_LEAVING_R1_DIRTY(red2, offset);
422 green2 = scale8_LEAVING_R1_DIRTY(green2, offset);
423 blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset);
424 cleanup_R1();
425
426 // These sums can't overflow, so no qadd8 needed.
427 red1 += red2;
428 green1 += green2;
429 blue1 += blue2;
430 }
431 if (brightness != 255) {
432 // nscale8x3_video(red1, green1, blue1, brightness);
433 nscale8x3(red1, green1, blue1, brightness);
434 }
435 return CRGB(red1, green1, blue1);
436}
437
439 fl::u8 brightness, TBlendType blendType) {
440 if (blendType == LINEARBLEND_NOWRAP) {
441 index = map8(index, 0, 239); // Blend range is affected by lo4 blend of
442 // values, remap to avoid wrapping
443 }
444
445 // hi4 = index >> 4;
446 fl::u8 hi4 = lsrX4(index);
447 fl::u8 lo4 = index & 0x0F;
448
449 CRGB entry(FL_PGM_READ_DWORD_NEAR(&(pal[0]) + hi4));
450
451 fl::u8 red1 = entry.red;
452 fl::u8 green1 = entry.green;
453 fl::u8 blue1 = entry.blue;
454
455 fl::u8 blend = lo4 && (blendType != NOBLEND);
456
457 if (blend) {
458
459 if (hi4 == 15) {
460 entry = FL_PGM_READ_DWORD_NEAR(&(pal[0]));
461 } else {
462 entry = FL_PGM_READ_DWORD_NEAR(&(pal[1]) + hi4);
463 }
464
465 fl::u8 f2 = lo4 << 4;
466 fl::u8 f1 = 255 - f2;
467
468 fl::u8 red2 = entry.red;
469 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
470 red2 = scale8_LEAVING_R1_DIRTY(red2, f2);
471 red1 += red2;
472
473 fl::u8 green2 = entry.green;
474 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
475 green2 = scale8_LEAVING_R1_DIRTY(green2, f2);
476 green1 += green2;
477
478 fl::u8 blue2 = entry.blue;
479 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
480 blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2);
481 blue1 += blue2;
482
483 cleanup_R1();
484 }
485
486 if (brightness != 255) {
487 if (brightness) {
488 ++brightness; // adjust for rounding
489 // Now, since brightness is nonzero, we don't need the full
490 // scale8_video logic; we can just to scale8 and then add one
491 // (unless scale8 fixed) to all nonzero inputs.
492 if (red1) {
494#if !(FASTLED_SCALE8_FIXED == 1)
495 ++red1;
496#endif
497 }
498 if (green1) {
499 green1 = scale8_LEAVING_R1_DIRTY(green1, brightness);
500#if !(FASTLED_SCALE8_FIXED == 1)
501 ++green1;
502#endif
503 }
504 if (blue1) {
505 blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness);
506#if !(FASTLED_SCALE8_FIXED == 1)
507 ++blue1;
508#endif
509 }
510 cleanup_R1();
511 } else {
512 red1 = 0;
513 green1 = 0;
514 blue1 = 0;
515 }
516 }
517
518 return CRGB(red1, green1, blue1);
519}
520
521CRGB ColorFromPalette(const CRGBPalette32 &pal, fl::u8 index,
522 fl::u8 brightness, TBlendType blendType) {
523 if (blendType == LINEARBLEND_NOWRAP) {
524 index = map8(index, 0, 247); // Blend range is affected by lo3 blend of
525 // values, remap to avoid wrapping
526 }
527
528 fl::u8 hi5 = index;
529#if defined(__AVR__)
530 hi5 /= 2;
531 hi5 /= 2;
532 hi5 /= 2;
533#else
534 hi5 >>= 3;
535#endif
536 fl::u8 lo3 = index & 0x07;
537
538 // const CRGB* entry = &(pal[0]) + hi5;
539 // since hi5 is always 0..31, hi4 * sizeof(CRGB) can be a single-byte value,
540 // instead of the two byte 'int' that avr-gcc defaults to.
541 // So, we multiply hi5 X sizeof(CRGB), giving hi5XsizeofCRGB;
542 fl::u8 hi5XsizeofCRGB = hi5 * sizeof(CRGB);
543 // We then add that to a base array pointer.
544 const CRGB *entry = (CRGB *)((fl::u8 *)(&(pal[0])) + hi5XsizeofCRGB);
545
546 fl::u8 red1 = entry->red;
547 fl::u8 green1 = entry->green;
548 fl::u8 blue1 = entry->blue;
549
550 fl::u8 blend = lo3 && (blendType != NOBLEND);
551
552 if (blend) {
553
554 if (hi5 == 31) {
555 entry = &(pal[0]);
556 } else {
557 ++entry;
558 }
559
560 fl::u8 f2 = lo3 << 5;
561 fl::u8 f1 = 255 - f2;
562
563 fl::u8 red2 = entry->red;
564 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
565 red2 = scale8_LEAVING_R1_DIRTY(red2, f2);
566 red1 += red2;
567
568 fl::u8 green2 = entry->green;
569 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
570 green2 = scale8_LEAVING_R1_DIRTY(green2, f2);
571 green1 += green2;
572
573 fl::u8 blue2 = entry->blue;
574 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
575 blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2);
576 blue1 += blue2;
577
578 cleanup_R1();
579 }
580
581 if (brightness != 255) {
582 if (brightness) {
583 ++brightness; // adjust for rounding
584 // Now, since brightness is nonzero, we don't need the full
585 // scale8_video logic; we can just to scale8 and then add one
586 // (unless scale8 fixed) to all nonzero inputs.
587 if (red1) {
589#if !(FASTLED_SCALE8_FIXED == 1)
590 ++red1;
591#endif
592 }
593 if (green1) {
594 green1 = scale8_LEAVING_R1_DIRTY(green1, brightness);
595#if !(FASTLED_SCALE8_FIXED == 1)
596 ++green1;
597#endif
598 }
599 if (blue1) {
600 blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness);
601#if !(FASTLED_SCALE8_FIXED == 1)
602 ++blue1;
603#endif
604 }
605 cleanup_R1();
606 } else {
607 red1 = 0;
608 green1 = 0;
609 blue1 = 0;
610 }
611 }
612
613 return CRGB(red1, green1, blue1);
614}
615
617 fl::u8 brightness, TBlendType blendType) {
618 if (blendType == LINEARBLEND_NOWRAP) {
619 index = map8(index, 0, 247); // Blend range is affected by lo3 blend of
620 // values, remap to avoid wrapping
621 }
622
623 fl::u8 hi5 = index;
624#if defined(__AVR__)
625 hi5 /= 2;
626 hi5 /= 2;
627 hi5 /= 2;
628#else
629 hi5 >>= 3;
630#endif
631 fl::u8 lo3 = index & 0x07;
632
633 CRGB entry(FL_PGM_READ_DWORD_NEAR(&(pal[0]) + hi5));
634
635 fl::u8 red1 = entry.red;
636 fl::u8 green1 = entry.green;
637 fl::u8 blue1 = entry.blue;
638
639 fl::u8 blend = lo3 && (blendType != NOBLEND);
640
641 if (blend) {
642
643 if (hi5 == 31) {
644 entry = FL_PGM_READ_DWORD_NEAR(&(pal[0]));
645 } else {
646 entry = FL_PGM_READ_DWORD_NEAR(&(pal[1]) + hi5);
647 }
648
649 fl::u8 f2 = lo3 << 5;
650 fl::u8 f1 = 255 - f2;
651
652 fl::u8 red2 = entry.red;
653 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
654 red2 = scale8_LEAVING_R1_DIRTY(red2, f2);
655 red1 += red2;
656
657 fl::u8 green2 = entry.green;
658 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
659 green2 = scale8_LEAVING_R1_DIRTY(green2, f2);
660 green1 += green2;
661
662 fl::u8 blue2 = entry.blue;
663 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
664 blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2);
665 blue1 += blue2;
666
667 cleanup_R1();
668 }
669
670 if (brightness != 255) {
671 if (brightness) {
672 ++brightness; // adjust for rounding
673 // Now, since brightness is nonzero, we don't need the full
674 // scale8_video logic; we can just to scale8 and then add one
675 // (unless scale8 fixed) to all nonzero inputs.
676 if (red1) {
678#if !(FASTLED_SCALE8_FIXED == 1)
679 ++red1;
680#endif
681 }
682 if (green1) {
683 green1 = scale8_LEAVING_R1_DIRTY(green1, brightness);
684#if !(FASTLED_SCALE8_FIXED == 1)
685 ++green1;
686#endif
687 }
688 if (blue1) {
689 blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness);
690#if !(FASTLED_SCALE8_FIXED == 1)
691 ++blue1;
692#endif
693 }
694 cleanup_R1();
695 } else {
696 red1 = 0;
697 green1 = 0;
698 blue1 = 0;
699 }
700 }
701
702 return CRGB(red1, green1, blue1);
703}
704
705CRGB ColorFromPalette(const CRGBPalette256 &pal, fl::u8 index,
706 fl::u8 brightness, TBlendType) {
707 const CRGB *entry = &(pal[0]) + index;
708
709 fl::u8 red = entry->red;
710 fl::u8 green = entry->green;
711 fl::u8 blue = entry->blue;
712
713 if (brightness != 255) {
714 ++brightness; // adjust for rounding
718 cleanup_R1();
719 }
720
721 return CRGB(red, green, blue);
722}
723
724CRGB ColorFromPaletteExtended(const CRGBPalette256 &pal, fl::u16 index,
725 fl::u8 brightness, TBlendType blendType) {
726 // Extract the eight most significant bits of the index as a palette index.
727 fl::u8 index_8bit = index >> 8;
728 // Calculate the 8-bit offset from the palette index.
729 fl::u8 offset = index & 0xff;
730 // Get the palette entry from the 8-bit index
731 const CRGB *entry = &(pal[0]) + index_8bit;
732 fl::u8 red1 = entry->red;
733 fl::u8 green1 = entry->green;
734 fl::u8 blue1 = entry->blue;
735
736 fl::u8 blend = offset && (blendType != NOBLEND);
737 if (blend) {
738 if (index_8bit == 255) {
739 entry = &(pal[0]);
740 } else {
741 entry++;
742 }
743
744 // Calculate the scaling factor and scaled values for the lower palette
745 // value.
746 fl::u8 f1 = 255 - offset;
747 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
748 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
749 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
750
751 // Calculate the scaled values for the neighbouring palette value.
752 fl::u8 red2 = entry->red;
753 fl::u8 green2 = entry->green;
754 fl::u8 blue2 = entry->blue;
755 red2 = scale8_LEAVING_R1_DIRTY(red2, offset);
756 green2 = scale8_LEAVING_R1_DIRTY(green2, offset);
757 blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset);
758 cleanup_R1();
759
760 // These sums can't overflow, so no qadd8 needed.
761 red1 += red2;
762 green1 += green2;
763 blue1 += blue2;
764 }
765 if (brightness != 255) {
766 // nscale8x3_video(red1, green1, blue1, brightness);
767 nscale8x3(red1, green1, blue1, brightness);
768 }
769 return CRGB(red1, green1, blue1);
770}
771
772CHSV ColorFromPalette(const CHSVPalette16 &pal, fl::u8 index,
773 fl::u8 brightness, TBlendType blendType) {
774 if (blendType == LINEARBLEND_NOWRAP) {
775 index = map8(index, 0, 239); // Blend range is affected by lo4 blend of
776 // values, remap to avoid wrapping
777 }
778
779 // hi4 = index >> 4;
780 fl::u8 hi4 = lsrX4(index);
781 fl::u8 lo4 = index & 0x0F;
782
783 // CRGB rgb1 = pal[ hi4];
784 const CHSV *entry = &(pal[0]) + hi4;
785
786 fl::u8 hue1 = entry->hue;
787 fl::u8 sat1 = entry->sat;
788 fl::u8 val1 = entry->val;
789
790 fl::u8 blend = lo4 && (blendType != NOBLEND);
791
792 if (blend) {
793
794 if (hi4 == 15) {
795 entry = &(pal[0]);
796 } else {
797 ++entry;
798 }
799
800 fl::u8 f2 = lo4 << 4;
801 fl::u8 f1 = 255 - f2;
802
803 fl::u8 hue2 = entry->hue;
804 fl::u8 sat2 = entry->sat;
805 fl::u8 val2 = entry->val;
806
807 // Now some special casing for blending to or from
808 // either black or white. Black and white don't have
809 // proper 'hue' of their own, so when ramping from
810 // something else to/from black/white, we set the 'hue'
811 // of the black/white color to be the same as the hue
812 // of the other color, so that you get the expected
813 // brightness or saturation ramp, with hue staying
814 // constant:
815
816 // If we are starting from white (sat=0)
817 // or black (val=0), adopt the target hue.
818 if (sat1 == 0 || val1 == 0) {
819 hue1 = hue2;
820 }
821
822 // If we are ending at white (sat=0)
823 // or black (val=0), adopt the starting hue.
824 if (sat2 == 0 || val2 == 0) {
825 hue2 = hue1;
826 }
827
828 sat1 = scale8_LEAVING_R1_DIRTY(sat1, f1);
829 val1 = scale8_LEAVING_R1_DIRTY(val1, f1);
830
831 sat2 = scale8_LEAVING_R1_DIRTY(sat2, f2);
832 val2 = scale8_LEAVING_R1_DIRTY(val2, f2);
833
834 // cleanup_R1();
835
836 // These sums can't overflow, so no qadd8 needed.
837 sat1 += sat2;
838 val1 += val2;
839
840 fl::u8 deltaHue = (fl::u8)(hue2 - hue1);
841 if (deltaHue & 0x80) {
842 // go backwards
843 hue1 -= scale8(256 - deltaHue, f2);
844 } else {
845 // go forwards
846 hue1 += scale8(deltaHue, f2);
847 }
848
849 cleanup_R1();
850 }
851
852 if (brightness != 255) {
853 val1 = scale8_video(val1, brightness);
854 }
855
856 return CHSV(hue1, sat1, val1);
857}
858
859CHSV ColorFromPalette(const CHSVPalette32 &pal, fl::u8 index,
860 fl::u8 brightness, TBlendType blendType) {
861 if (blendType == LINEARBLEND_NOWRAP) {
862 index = map8(index, 0, 247); // Blend range is affected by lo3 blend of
863 // values, remap to avoid wrapping
864 }
865
866 fl::u8 hi5 = index;
867#if defined(__AVR__)
868 hi5 /= 2;
869 hi5 /= 2;
870 hi5 /= 2;
871#else
872 hi5 >>= 3;
873#endif
874 fl::u8 lo3 = index & 0x07;
875
876 fl::u8 hi5XsizeofCHSV = hi5 * sizeof(CHSV);
877 const CHSV *entry = (CHSV *)((fl::u8 *)(&(pal[0])) + hi5XsizeofCHSV);
878
879 fl::u8 hue1 = entry->hue;
880 fl::u8 sat1 = entry->sat;
881 fl::u8 val1 = entry->val;
882
883 fl::u8 blend = lo3 && (blendType != NOBLEND);
884
885 if (blend) {
886
887 if (hi5 == 31) {
888 entry = &(pal[0]);
889 } else {
890 ++entry;
891 }
892
893 fl::u8 f2 = lo3 << 5;
894 fl::u8 f1 = 255 - f2;
895
896 fl::u8 hue2 = entry->hue;
897 fl::u8 sat2 = entry->sat;
898 fl::u8 val2 = entry->val;
899
900 // Now some special casing for blending to or from
901 // either black or white. Black and white don't have
902 // proper 'hue' of their own, so when ramping from
903 // something else to/from black/white, we set the 'hue'
904 // of the black/white color to be the same as the hue
905 // of the other color, so that you get the expected
906 // brightness or saturation ramp, with hue staying
907 // constant:
908
909 // If we are starting from white (sat=0)
910 // or black (val=0), adopt the target hue.
911 if (sat1 == 0 || val1 == 0) {
912 hue1 = hue2;
913 }
914
915 // If we are ending at white (sat=0)
916 // or black (val=0), adopt the starting hue.
917 if (sat2 == 0 || val2 == 0) {
918 hue2 = hue1;
919 }
920
921 sat1 = scale8_LEAVING_R1_DIRTY(sat1, f1);
922 val1 = scale8_LEAVING_R1_DIRTY(val1, f1);
923
924 sat2 = scale8_LEAVING_R1_DIRTY(sat2, f2);
925 val2 = scale8_LEAVING_R1_DIRTY(val2, f2);
926
927 // cleanup_R1();
928
929 // These sums can't overflow, so no qadd8 needed.
930 sat1 += sat2;
931 val1 += val2;
932
933 fl::u8 deltaHue = (fl::u8)(hue2 - hue1);
934 if (deltaHue & 0x80) {
935 // go backwards
936 hue1 -= scale8(256 - deltaHue, f2);
937 } else {
938 // go forwards
939 hue1 += scale8(deltaHue, f2);
940 }
941
942 cleanup_R1();
943 }
944
945 if (brightness != 255) {
946 val1 = scale8_video(val1, brightness);
947 }
948
949 return CHSV(hue1, sat1, val1);
950}
951
952CHSV ColorFromPalette(const CHSVPalette256 &pal, fl::u8 index,
953 fl::u8 brightness, TBlendType) {
954 CHSV hsv = *(&(pal[0]) + index);
955
956 if (brightness != 255) {
957 hsv.value = scale8_video(hsv.value, brightness);
958 }
959
960 return hsv;
961}
962
963void UpscalePalette(const class CRGBPalette16 &srcpal16,
964 class CRGBPalette256 &destpal256) {
965 for (int i = 0; i < 256; ++i) {
966 destpal256[(fl::u8)(i)] = ColorFromPalette(srcpal16, i);
967 }
968}
969
970void UpscalePalette(const class CHSVPalette16 &srcpal16,
971 class CHSVPalette256 &destpal256) {
972 for (int i = 0; i < 256; ++i) {
973 destpal256[(fl::u8)(i)] = ColorFromPalette(srcpal16, i);
974 }
975}
976
977void UpscalePalette(const class CRGBPalette16 &srcpal16,
978 class CRGBPalette32 &destpal32) {
979 for (fl::u8 i = 0; i < 16; ++i) {
980 fl::u8 j = i * 2;
981 destpal32[j + 0] = srcpal16[i];
982 destpal32[j + 1] = srcpal16[i];
983 }
984}
985
986void UpscalePalette(const class CHSVPalette16 &srcpal16,
987 class CHSVPalette32 &destpal32) {
988 for (fl::u8 i = 0; i < 16; ++i) {
989 fl::u8 j = i * 2;
990 destpal32[j + 0] = srcpal16[i];
991 destpal32[j + 1] = srcpal16[i];
992 }
993}
994
995void UpscalePalette(const class CRGBPalette32 &srcpal32,
996 class CRGBPalette256 &destpal256) {
997 for (int i = 0; i < 256; ++i) {
998 destpal256[(fl::u8)(i)] = ColorFromPalette(srcpal32, i);
999 }
1000}
1001
1002void UpscalePalette(const class CHSVPalette32 &srcpal32,
1003 class CHSVPalette256 &destpal256) {
1004 for (int i = 0; i < 256; ++i) {
1005 destpal256[(fl::u8)(i)] = ColorFromPalette(srcpal32, i);
1006 }
1007}
1008
1009#if 0
1010// replaced by PartyColors_p
1011void SetupPartyColors(CRGBPalette16& pal)
1012{
1013 fill_gradient( pal, 0, CHSV( HUE_PURPLE,255,255), 7, CHSV(HUE_YELLOW - 18,255,255), FORWARD_HUES);
1014 fill_gradient( pal, 8, CHSV( HUE_ORANGE,255,255), 15, CHSV(HUE_BLUE + 18,255,255), BACKWARD_HUES);
1015}
1016#endif
1017
1018void nblendPaletteTowardPalette(CRGBPalette16 &current, CRGBPalette16 &target,
1019 fl::u8 maxChanges) {
1020 fl::u8 *p1;
1021 fl::u8 *p2;
1022 fl::u8 changes = 0;
1023
1024 p1 = (fl::u8 *)current.entries;
1025 p2 = (fl::u8 *)target.entries;
1026
1027 const fl::u8 totalChannels = sizeof(CRGBPalette16);
1028 for (fl::u8 i = 0; i < totalChannels; ++i) {
1029 // if the values are equal, no changes are needed
1030 if (p1[i] == p2[i]) {
1031 continue;
1032 }
1033
1034 // if the current value is less than the target, increase it by one
1035 if (p1[i] < p2[i]) {
1036 ++p1[i];
1037 ++changes;
1038 }
1039
1040 // if the current value is greater than the target,
1041 // increase it by one (or two if it's still greater).
1042 if (p1[i] > p2[i]) {
1043 --p1[i];
1044 ++changes;
1045 if (p1[i] > p2[i]) {
1046 --p1[i];
1047 }
1048 }
1049
1050 // if we've hit the maximum number of changes, exit
1051 if (changes >= maxChanges) {
1052 break;
1053 }
1054 }
1055}
1056
1058 float orig;
1059 float adj;
1060 orig = (float)(brightness) / (255.0);
1061 adj = pow(orig, gamma) * (255.0);
1062 fl::u8 result = (fl::u8)(adj);
1063 if ((brightness > 0) && (result == 0)) {
1064 result = 1; // never gamma-adjust a positive number down to zero
1065 }
1066 return result;
1067}
1068
1069CRGB applyGamma_video(const CRGB &orig, float gamma) {
1070 CRGB adj;
1071 adj.r = applyGamma_video(orig.r, gamma);
1072 adj.g = applyGamma_video(orig.g, gamma);
1073 adj.b = applyGamma_video(orig.b, gamma);
1074 return adj;
1075}
1076
1077CRGB applyGamma_video(const CRGB &orig, float gammaR, float gammaG,
1078 float gammaB) {
1079 CRGB adj;
1080 adj.r = applyGamma_video(orig.r, gammaR);
1081 adj.g = applyGamma_video(orig.g, gammaG);
1082 adj.b = applyGamma_video(orig.b, gammaB);
1083 return adj;
1084}
1085
1086CRGB &napplyGamma_video(CRGB &rgb, float gamma) {
1087 rgb = applyGamma_video(rgb, gamma);
1088 return rgb;
1089}
1090
1091CRGB &napplyGamma_video(CRGB &rgb, float gammaR, float gammaG, float gammaB) {
1092 rgb = applyGamma_video(rgb, gammaR, gammaG, gammaB);
1093 return rgb;
1094}
1095
1096void napplyGamma_video(CRGB *rgbarray, fl::u16 count, float gamma) {
1097 for (fl::u16 i = 0; i < count; ++i) {
1098 rgbarray[i] = applyGamma_video(rgbarray[i], gamma);
1099 }
1100}
1101
1102void napplyGamma_video(CRGB *rgbarray, fl::u16 count, float gammaR,
1103 float gammaG, float gammaB) {
1104 for (fl::u16 i = 0; i < count; ++i) {
1105 rgbarray[i] = applyGamma_video(rgbarray[i], gammaR, gammaG, gammaB);
1106 }
1107}
1108
1109} // namespace fl
CRGB leds[NUM_LEDS]
central include file for FastLED, defines the CFastLED class/object
uint16_t scale
Definition Noise.ino:74
UISlider brightness("Brightness", 128, 0, 255, 1)
@ HUE_BLUE
Blue (225°)
Definition hsv.h:102
@ HUE_YELLOW
Yellow (90°)
Definition hsv.h:99
@ HUE_ORANGE
Orange (45°)
Definition hsv.h:98
@ HUE_PURPLE
Purple (270°)
Definition hsv.h:103
Result type for promise operations.
@ FORWARD_HUES
Hue always goes clockwise around the color wheel.
@ BACKWARD_HUES
Hue always goes counter-clockwise around the color wheel.
fl::u32 TProgmemRGBPalette32[32]
CRGBPalette32 entries stored in PROGMEM memory.
fl::u32 TProgmemRGBPalette16[16]
CRGBPalette16 entries stored in PROGMEM memory.
UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
#define FL_PGM_READ_DWORD_NEAR(x)
Read a double word (32-bit) from PROGMEM memory.
Utility functions for color fill, palettes, blending, and more.
void fill_gradient(T *targetArray, u16 startpos, CHSV startcolor, u16 endpos, CHSV endcolor, TGradientDirectionCode directionCode=SHORTEST_HUES)
Fill a range of LEDs with a smooth HSV gradient between two HSV colors.
Definition fill.h:87
LIB8STATIC uint8_t map8(uint8_t in, uint8_t rangeStart, uint8_t rangeEnd)
Map from one full-range 8-bit value into a narrower range of 8-bit values, possibly a range of hues.
Definition lib8tion.h:397
LIB8STATIC uint8_t blend8(uint8_t a, uint8_t b, uint8_t amountOfB)
Blend a variable proportion (0-255) of one byte to another.
Definition math8.h:683
LIB8STATIC_ALWAYS_INLINE void cleanup_R1()
Clean up the r1 register after a series of *LEAVING_R1_DIRTY calls.
Definition scale8.h:343
LIB8STATIC_ALWAYS_INLINE uint8_t scale8_LEAVING_R1_DIRTY(uint8_t i, fract8 scale)
This version of scale8() does not clean up the R1 register on AVR.
Definition scale8.h:180
LIB8STATIC_ALWAYS_INLINE uint8_t scale8_video_LEAVING_R1_DIRTY(uint8_t i, fract8 scale)
This version of scale8_video() does not clean up the R1 register on AVR.
Definition scale8.h:272
LIB8STATIC void nscale8x3(uint8_t &r, uint8_t &g, uint8_t &b, fract8 scale)
Scale three one-byte values by a fourth one, which is treated as the numerator of a fraction whose de...
Definition scale8.h:367
LIB8STATIC_ALWAYS_INLINE uint8_t scale8_video(uint8_t i, fract8 scale)
The "video" version of scale8() guarantees that the output will be only be zero if one or both of the...
Definition scale8.h:127
LIB8STATIC void nscale8x3_video(uint8_t &r, uint8_t &g, uint8_t &b, fract8 scale)
Scale three one-byte values by a fourth one, which is treated as the numerator of a fraction whose de...
Definition scale8.h:401
LIB8STATIC_ALWAYS_INLINE uint8_t scale8(uint8_t i, fract8 scale)
Scale one byte by a second one, which is treated as the numerator of a fraction whose denominator is ...
Definition scale8.h:44
unsigned char u8
Definition int.h:17
TGradientDirectionCode
Hue direction for calculating fill gradients.
@ SHORTEST_HUES
Hue goes whichever way is shortest.
@ LONGEST_HUES
Hue goes whichever way is longest.
@ FORWARD_HUES
Hue always goes clockwise around the color wheel.
@ BACKWARD_HUES
Hue always goes counter-clockwise around the color wheel.
void fadeUsingColor(CRGB *leds, fl::u16 numLeds, const CRGB &colormask)
void UpscalePalette(const class CRGBPalette16 &srcpal16, class CRGBPalette256 &destpal256)
void fadeToBlackBy(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy)
CRGB ColorFromPalette(const CRGBPalette16 &pal, fl::u8 index, fl::u8 brightness, TBlendType blendType)
u8 fract8
Fixed-Point Fractional Types.
Definition int.h:49
void nscale8(CRGB *leds, fl::u16 num_leds, fl::u8 scale)
void fade_raw(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy)
void fade_video(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy)
CRGB ColorFromPaletteExtended(const CRGBPalette32 &pal, fl::u16 index, fl::u8 brightness, TBlendType blendType)
fl::u8 applyGamma_video(fl::u8 brightness, float gamma)
CRGB blend(const CRGB &p1, const CRGB &p2, fract8 amountOfP2)
CRGB & nblend(CRGB &existing, const CRGB &overlay, fract8 amountOfOverlay)
CRGB HeatColor(fl::u8 temperature)
fl::u8 lsrX4(fl::u8 dividend)
Helper function to divide a number by 16, aka four logical shift right (LSR)'s.
void fadeLightBy(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy)
CRGB & napplyGamma_video(CRGB &rgb, float gamma)
void nblendPaletteTowardPalette(CRGBPalette16 &current, CRGBPalette16 &target, fl::u8 maxChanges)
void nscale8_video(CRGB *leds, fl::u16 num_leds, fl::u8 scale)
IMPORTANT!
Definition crgb.h:20
Representation of an RGB pixel (Red, Green, Blue)
Definition crgb.h:86
Representation of an HSV pixel (hue, saturation, value (aka brightness)).
Definition hsv.h:15