FastLED 3.9.15
Loading...
Searching...
No Matches
colorutils.cpp.hpp
Go to the documentation of this file.
1#define FASTLED_INTERNAL
2#define __PROG_TYPES_COMPAT__
3
7
8#include "fl/stl/int.h"
9#include "platforms/is_platform.h"
10#include "fl/math/math.h"
11#include "fl/math/fixed_point.h" // for fl::s16x16 — used by applyGamma_video
12#include "fl/stl/stdint.h"
13
14#include "fl/system/fastled.h"
15#include "fl/stl/assert.h"
16#include "fl/gfx/colorutils.h"
18#include "fl/math/xymap.h"
19
20namespace fl {
21
22CRGB &nblend(CRGB &existing, const CRGB &overlay, fract8 amountOfOverlay) {
23 if (amountOfOverlay == 0) {
24 return existing;
25 }
26
27 if (amountOfOverlay == 255) {
28 existing = overlay;
29 return existing;
30 }
31
32#if 0
33 // Old blend method which unfortunately had some rounding errors
34 fract8 amountOfKeep = 255 - amountOfOverlay;
35
36 existing.red = scale8_LEAVING_R1_DIRTY( existing.red, amountOfKeep)
37 + scale8_LEAVING_R1_DIRTY( overlay.red, amountOfOverlay);
38 existing.green = scale8_LEAVING_R1_DIRTY( existing.green, amountOfKeep)
39 + scale8_LEAVING_R1_DIRTY( overlay.green, amountOfOverlay);
40 existing.blue = scale8_LEAVING_R1_DIRTY( existing.blue, amountOfKeep)
41 + scale8_LEAVING_R1_DIRTY( overlay.blue, amountOfOverlay);
42
43 cleanup_R1();
44#else
45 // Corrected blend method, with no loss-of-precision rounding errors
46 existing.red = blend8(existing.red, overlay.red, amountOfOverlay);
47 existing.green = blend8(existing.green, overlay.green, amountOfOverlay);
48 existing.blue = blend8(existing.blue, overlay.blue, amountOfOverlay);
49#endif
50
51 return existing;
52}
53
54void nblend(CRGB *existing, const CRGB *overlay, fl::u16 count,
55 fract8 amountOfOverlay) {
56 for (fl::u16 i = count; i; --i) {
57 nblend(*existing, *overlay, amountOfOverlay);
58 ++existing;
59 ++overlay;
60 }
61}
62
63CRGB blend(const CRGB &p1, const CRGB &p2, fract8 amountOfP2) {
64 CRGB nu(p1);
65 nblend(nu, p2, amountOfP2);
66 return nu;
67}
68
69CRGB *blend(const CRGB *src1, const CRGB *src2, CRGB *dest, fl::u16 count,
70 fract8 amountOfsrc2) {
71 for (fl::u16 i = 0; i < count; ++i) {
72 dest[i] = blend(src1[i], src2[i], amountOfsrc2);
73 }
74 return dest;
75}
76
77CHSV &nblend(CHSV &existing, const CHSV &overlay, fract8 amountOfOverlay,
78 TGradientDirectionCode directionCode) {
79 if (amountOfOverlay == 0) {
80 return existing;
81 }
82
83 if (amountOfOverlay == 255) {
84 existing = overlay;
85 return existing;
86 }
87
88 fract8 amountOfKeep = 255 - amountOfOverlay;
89
90 fl::u8 huedelta8 = overlay.hue - existing.hue;
91
92 if (directionCode == SHORTEST_HUES) {
93 directionCode = FORWARD_HUES;
94 if (huedelta8 > 127) {
95 directionCode = BACKWARD_HUES;
96 }
97 }
98
99 if (directionCode == LONGEST_HUES) {
100 directionCode = FORWARD_HUES;
101 if (huedelta8 < 128) {
102 directionCode = BACKWARD_HUES;
103 }
104 }
105
106 if (directionCode == FORWARD_HUES) {
107 existing.hue = existing.hue + scale8(huedelta8, amountOfOverlay);
108 } else /* directionCode == BACKWARD_HUES */
109 {
110 huedelta8 = -huedelta8;
111 existing.hue = existing.hue - scale8(huedelta8, amountOfOverlay);
112 }
113
114 existing.sat = scale8_LEAVING_R1_DIRTY(existing.sat, amountOfKeep) +
115 scale8_LEAVING_R1_DIRTY(overlay.sat, amountOfOverlay);
116 existing.val = scale8_LEAVING_R1_DIRTY(existing.val, amountOfKeep) +
117 scale8_LEAVING_R1_DIRTY(overlay.val, amountOfOverlay);
118
119 cleanup_R1();
120
121 return existing;
122}
123
124void nblend(CHSV *existing, const CHSV *overlay, fl::u16 count,
125 fract8 amountOfOverlay, TGradientDirectionCode directionCode) {
126 if (existing == overlay)
127 return;
128 for (fl::u16 i = count; i; --i) {
129 nblend(*existing, *overlay, amountOfOverlay, directionCode);
130 ++existing;
131 ++overlay;
132 }
133}
134
135CHSV blend(const CHSV &p1, const CHSV &p2, fract8 amountOfP2,
136 TGradientDirectionCode directionCode) {
137 CHSV nu(p1);
138 nblend(nu, p2, amountOfP2, directionCode);
139 return nu;
140}
141
142CHSV *blend(const CHSV *src1, const CHSV *src2, CHSV *dest, fl::u16 count,
143 fract8 amountOfsrc2, TGradientDirectionCode directionCode) {
144 for (fl::u16 i = 0; i < count; ++i) {
145 dest[i] = blend(src1[i], src2[i], amountOfsrc2, directionCode);
146 }
147 return dest;
148}
149
150void nscale8_video(CRGB *leds, fl::u16 num_leds, fl::u8 scale) {
151 for (fl::u16 i = 0; i < num_leds; ++i) {
152 leds[i].nscale8_video(scale);
153 }
154}
155
156void fade_video(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) {
157 nscale8_video(leds, num_leds, 255 - fadeBy);
158}
159
160void fadeLightBy(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) {
161 nscale8_video(leds, num_leds, 255 - fadeBy);
162}
163
164void fadeToBlackBy(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) {
165 nscale8(leds, num_leds, 255 - fadeBy);
166}
167
168void fade_raw(CRGB *leds, fl::u16 num_leds, fl::u8 fadeBy) {
169 nscale8(leds, num_leds, 255 - fadeBy);
170}
171
172void nscale8(CRGB *leds, fl::u16 num_leds, fl::u8 scale) {
173 for (fl::u16 i = 0; i < num_leds; ++i) {
174 leds[i].nscale8(scale);
175 }
176}
177
178void fadeUsingColor(CRGB *leds, fl::u16 numLeds, const CRGB &colormask) {
179 fl::u8 fr, fg, fb;
180 fr = colormask.r;
181 fg = colormask.g;
182 fb = colormask.b;
183
184 for (fl::u16 i = 0; i < numLeds; ++i) {
185 leds[i].r = scale8_LEAVING_R1_DIRTY(leds[i].r, fr);
186 leds[i].g = scale8_LEAVING_R1_DIRTY(leds[i].g, fg);
187 leds[i].b = scale8(leds[i].b, fb);
188 }
189}
190
191// CRGB HeatColor( fl::u8 temperature)
192//
193// Approximates a 'black body radiation' spectrum for
194// a given 'heat' level. This is useful for animations of 'fire'.
195// Heat is specified as an arbitrary scale from 0 (cool) to 255 (hot).
196// This is NOT a chromatically correct 'black body radiation'
197// spectrum, but it's surprisingly close, and it's fast and small.
198//
199// On AVR/Arduino, this typically takes around 70 bytes of program memory,
200// versus 768 bytes for a full 256-entry RGB lookup table.
201
202CRGB HeatColor(fl::u8 temperature) {
203 CRGB heatcolor;
204
205 // Scale 'heat' down from 0-255 to 0-191,
206 // which can then be easily divided into three
207 // equal 'thirds' of 64 units each.
208 fl::u8 t192 = scale8_video(temperature, 191);
209
210 // calculate a value that ramps up from
211 // zero to 255 in each 'third' of the scale.
212 fl::u8 heatramp = t192 & 0x3F; // 0..63
213 heatramp <<= 2; // scale up to 0..252
214
215 // now figure out which third of the spectrum we're in:
216 if (t192 & 0x80) {
217 // we're in the hottest third
218 heatcolor.r = 255; // full red
219 heatcolor.g = 255; // full green
220 heatcolor.b = heatramp; // ramp up blue
221
222 } else if (t192 & 0x40) {
223 // we're in the middle third
224 heatcolor.r = 255; // full red
225 heatcolor.g = heatramp; // ramp up green
226 heatcolor.b = 0; // no blue
227
228 } else {
229 // we're in the coolest third
230 heatcolor.r = heatramp; // ramp up red
231 heatcolor.g = 0; // no green
232 heatcolor.b = 0; // no blue
233 }
234
235 return heatcolor;
236}
237
242inline fl::u8 lsrX4(fl::u8 dividend) __attribute__((always_inline));
243inline fl::u8 lsrX4(fl::u8 dividend) {
244#if defined(FL_IS_AVR)
245 dividend /= 2;
246 dividend /= 2;
247 dividend /= 2;
248 dividend /= 2;
249#else
250 dividend >>= 4;
251#endif
252 return dividend;
253}
254
255namespace {
256
257template <fl::size Size> struct RuntimeRGBPaletteReader {
259
260 CRGB read(fl::u8 index) const { return entries[index]; }
261};
262
263template <fl::size Size> struct ProgmemRGBPaletteReader {
265
266 CRGB read(fl::u8 index) const {
267 return CRGB(FL_PGM_READ_DWORD_NEAR(entries.data() + index));
268 }
269};
270
271inline fl::u16 promote_channel_to_hd(fl::u8 channel) {
272 return static_cast<fl::u16>(channel) << 8;
273}
274
275inline fl::u16 lerp_channel_to_hd(fl::u8 a, fl::u8 b, fl::u16 frac,
276 fl::u32 segment_size) {
277 const fl::u32 a_raw = static_cast<fl::u32>(a) << 8;
278 const fl::u32 b_raw = static_cast<fl::u32>(b) << 8;
279 return static_cast<fl::u16>(
280 (a_raw * (segment_size - frac) + b_raw * frac) / segment_size);
281}
282
288
289inline fl::u16 scale_hd_channel(fl::u16 channel, fl::u8x8 scale) {
290 const fl::u32 scaled =
291 (static_cast<fl::u32>(channel) * scale.raw()) >> 8;
292 return scaled > 0xFFFFu ? fl::u16(0xFFFF) : static_cast<fl::u16>(scaled);
293}
294
296 if (brightness.raw() == 256) {
297 return;
298 }
302}
303
304template <fl::u8 PaletteBits, typename Reader>
305CRGB16 ColorFromPaletteHDImpl(const Reader &reader, fl::u16 index,
307 TBlendType blendType) {
308 const fl::u8 frac_bits = 16 - PaletteBits;
309 const fl::u8 palette_last = (fl::u8(1) << PaletteBits) - 1;
310 const fl::u32 segment_size = fl::u32(1) << frac_bits;
311 const fl::u8 entry_index = static_cast<fl::u8>(index >> frac_bits);
312 const fl::u16 frac =
313 static_cast<fl::u16>(index & (segment_size - fl::u32(1)));
314
315 const CRGB rgb1 = reader.read(entry_index);
316 CRGB16 out;
317 if (frac && blendType != NOBLEND) {
318 CRGB rgb2;
319 if (entry_index == palette_last) {
320 rgb2 = blendType == LINEARBLEND_NOWRAP ? rgb1 : reader.read(0);
321 } else {
322 rgb2 = reader.read(entry_index + 1);
323 }
325 rgb1.red, rgb2.red, frac, segment_size)),
327 rgb1.green, rgb2.green, frac, segment_size)),
329 rgb1.blue, rgb2.blue, frac, segment_size)));
330 } else {
331 out = promote_rgb_to_hd(rgb1);
332 }
333
335 return out;
336}
337
338} // namespace
339
340CRGB ColorFromPaletteExtended(const CRGBPalette32 &pal, fl::u16 index,
341 fl::u8 brightness, TBlendType blendType) {
342 // Extract the five most significant bits of the index as a palette index.
343 fl::u8 index_5bit = (index >> 11);
344 // Calculate the 8-bit offset from the palette index.
345 fl::u8 offset = (fl::u8)(index >> 3);
346 // Get the palette entry from the 5-bit index
347 const CRGB *entry = &(pal[0]) + index_5bit;
348 fl::u8 red1 = entry->red;
349 fl::u8 green1 = entry->green;
350 fl::u8 blue1 = entry->blue;
351
352 fl::u8 blend = offset && (blendType != NOBLEND);
353 if (blend) {
354 if (index_5bit == 31) {
355 entry = &(pal[0]);
356 } else {
357 entry++;
358 }
359
360 // Calculate the scaling factor and scaled values for the lower palette
361 // value.
362 fl::u8 f1 = 255 - offset;
363 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
364 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
365 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
366
367 // Calculate the scaled values for the neighbouring palette value.
368 fl::u8 red2 = entry->red;
369 fl::u8 green2 = entry->green;
370 fl::u8 blue2 = entry->blue;
371 red2 = scale8_LEAVING_R1_DIRTY(red2, offset);
372 green2 = scale8_LEAVING_R1_DIRTY(green2, offset);
373 blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset);
374 cleanup_R1();
375
376 // These sums can't overflow, so no qadd8 needed.
377 red1 += red2;
378 green1 += green2;
379 blue1 += blue2;
380 }
381 if (brightness != 255) {
382 // nscale8x3_video(red1, green1, blue1, brightness);
383 nscale8x3(red1, green1, blue1, brightness);
384 }
385 return CRGB(red1, green1, blue1);
386}
387
388CRGB16 ColorFromPaletteHD(const CRGBPalette32 &pal, fl::u16 index,
389 fl::u8x8 brightness, TBlendType blendType) {
390 return ColorFromPaletteHDImpl<5>(
391 RuntimeRGBPaletteReader<32>{fl::span<const CRGB, 32>(&pal.entries[0], 32)},
392 index, brightness, blendType);
393}
394
395CRGB ColorFromPalette(const CRGBPalette16 &pal, fl::u8 index,
396 fl::u8 brightness, TBlendType blendType) {
397 if (blendType == LINEARBLEND_NOWRAP) {
398 index = map8(index, 0, 239); // Blend range is affected by lo4 blend of
399 // values, remap to avoid wrapping
400 }
401
402 // hi4 = index >> 4;
403 fl::u8 hi4 = lsrX4(index);
404 fl::u8 lo4 = index & 0x0F;
405
406 // const CRGB* entry = &(pal[0]) + hi4;
407 // since hi4 is always 0..15, hi4 * sizeof(CRGB) can be a single-byte value,
408 // instead of the two byte 'int' that avr-gcc defaults to.
409 // So, we multiply hi4 X sizeof(CRGB), giving hi4XsizeofCRGB;
410 fl::u8 hi4XsizeofCRGB = hi4 * sizeof(CRGB);
411 // We then add that to a base array pointer.
412 const CRGB *entry = (CRGB *)((fl::u8 *)(&(pal[0])) + hi4XsizeofCRGB);
413
414 fl::u8 blend = lo4 && (blendType != NOBLEND);
415
416 fl::u8 red1 = entry->red;
417 fl::u8 green1 = entry->green;
418 fl::u8 blue1 = entry->blue;
419
420 if (blend) {
421
422 if (hi4 == 15) {
423 entry = &(pal[0]);
424 } else {
425 ++entry;
426 }
427
428 fl::u8 f2 = lo4 << 4;
429 fl::u8 f1 = 255 - f2;
430
431 // rgb1.nscale8(f1);
432 fl::u8 red2 = entry->red;
433 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
434 red2 = scale8_LEAVING_R1_DIRTY(red2, f2);
435 red1 += red2;
436
437 fl::u8 green2 = entry->green;
438 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
439 green2 = scale8_LEAVING_R1_DIRTY(green2, f2);
440 green1 += green2;
441
442 fl::u8 blue2 = entry->blue;
443 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
444 blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2);
445 blue1 += blue2;
446
447 cleanup_R1();
448 }
449
450 if (brightness != 255) {
451 if (brightness) {
452 ++brightness; // adjust for rounding
453 // Now, since brightness is nonzero, we don't need the full
454 // scale8_video logic; we can just to scale8 and then add one
455 // (unless scale8 fixed) to all nonzero inputs.
456 if (red1) {
457 red1 = scale8_LEAVING_R1_DIRTY(red1, brightness);
458#if !(FASTLED_SCALE8_FIXED == 1)
459 ++red1;
460#endif
461 }
462 if (green1) {
463 green1 = scale8_LEAVING_R1_DIRTY(green1, brightness);
464#if !(FASTLED_SCALE8_FIXED == 1)
465 ++green1;
466#endif
467 }
468 if (blue1) {
469 blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness);
470#if !(FASTLED_SCALE8_FIXED == 1)
471 ++blue1;
472#endif
473 }
474 cleanup_R1();
475 } else {
476 red1 = 0;
477 green1 = 0;
478 blue1 = 0;
479 }
480 }
481
482 return CRGB(red1, green1, blue1);
483}
484
485CRGB ColorFromPaletteExtended(const CRGBPalette16 &pal, fl::u16 index,
486 fl::u8 brightness, TBlendType blendType) {
487 // Extract the four most significant bits of the index as a palette index.
488 fl::u8 index_4bit = index >> 12;
489 // Calculate the 8-bit offset from the palette index.
490 fl::u8 offset = (fl::u8)(index >> 4);
491 // Get the palette entry from the 4-bit index
492 const CRGB *entry = &(pal[0]) + index_4bit;
493 fl::u8 red1 = entry->red;
494 fl::u8 green1 = entry->green;
495 fl::u8 blue1 = entry->blue;
496
497 fl::u8 blend = offset && (blendType != NOBLEND);
498 if (blend) {
499 if (index_4bit == 15) {
500 entry = &(pal[0]);
501 } else {
502 entry++;
503 }
504
505 // Calculate the scaling factor and scaled values for the lower palette
506 // value.
507 fl::u8 f1 = 255 - offset;
508 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
509 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
510 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
511
512 // Calculate the scaled values for the neighbouring palette value.
513 fl::u8 red2 = entry->red;
514 fl::u8 green2 = entry->green;
515 fl::u8 blue2 = entry->blue;
516 red2 = scale8_LEAVING_R1_DIRTY(red2, offset);
517 green2 = scale8_LEAVING_R1_DIRTY(green2, offset);
518 blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset);
519 cleanup_R1();
520
521 // These sums can't overflow, so no qadd8 needed.
522 red1 += red2;
523 green1 += green2;
524 blue1 += blue2;
525 }
526 if (brightness != 255) {
527 // nscale8x3_video(red1, green1, blue1, brightness);
528 nscale8x3(red1, green1, blue1, brightness);
529 }
530 return CRGB(red1, green1, blue1);
531}
532
533CRGB16 ColorFromPaletteHD(const CRGBPalette16 &pal, fl::u16 index,
534 fl::u8x8 brightness, TBlendType blendType) {
535 return ColorFromPaletteHDImpl<4>(
536 RuntimeRGBPaletteReader<16>{fl::span<const CRGB, 16>(&pal.entries[0], 16)},
537 index, brightness, blendType);
538}
539
541 fl::u8 brightness, TBlendType blendType) {
542 // Extract the four most significant bits of the index as a palette index.
543 fl::u8 index_4bit = index >> 12;
544 // Calculate the 8-bit offset from the palette index.
545 fl::u8 offset = (fl::u8)(index >> 4);
546 // Get the palette entry from the 4-bit index
547 CRGB entry(FL_PGM_READ_DWORD_NEAR(&(pal[0]) + index_4bit));
548 fl::u8 red1 = entry.red;
549 fl::u8 green1 = entry.green;
550 fl::u8 blue1 = entry.blue;
551
552 fl::u8 blend = offset && (blendType != NOBLEND);
553 if (blend) {
554 if (index_4bit == 15) {
555 entry = FL_PGM_READ_DWORD_NEAR(&(pal[0]));
556 } else {
557 entry = FL_PGM_READ_DWORD_NEAR(&(pal[1]) + index_4bit);
558 }
559
560 // Calculate the scaling factor and scaled values for the lower palette
561 // value.
562 fl::u8 f1 = 255 - offset;
563 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
564 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
565 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
566
567 // Calculate the scaled values for the neighbouring palette value.
568 fl::u8 red2 = entry.red;
569 fl::u8 green2 = entry.green;
570 fl::u8 blue2 = entry.blue;
571 red2 = scale8_LEAVING_R1_DIRTY(red2, offset);
572 green2 = scale8_LEAVING_R1_DIRTY(green2, offset);
573 blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset);
574 cleanup_R1();
575
576 // These sums can't overflow, so no qadd8 needed.
577 red1 += red2;
578 green1 += green2;
579 blue1 += blue2;
580 }
581 if (brightness != 255) {
582 // nscale8x3_video(red1, green1, blue1, brightness);
583 nscale8x3(red1, green1, blue1, brightness);
584 }
585 return CRGB(red1, green1, blue1);
586}
587
589 fl::u8x8 brightness, TBlendType blendType) {
590 return ColorFromPaletteHDImpl<4>(
591 ProgmemRGBPaletteReader<16>{fl::span<const fl::u32, 16>(&pal[0], 16)},
592 index, brightness, blendType);
593}
594
596 fl::u8 brightness, TBlendType blendType) {
597 // Extract the five most significant bits of the index as a palette index.
598 fl::u8 index_5bit = (index >> 11);
599 // Calculate the 8-bit offset from the palette index.
600 fl::u8 offset = (fl::u8)(index >> 3);
601 // Get the palette entry from the 5-bit index
602 CRGB entry(FL_PGM_READ_DWORD_NEAR(&(pal[0]) + index_5bit));
603 fl::u8 red1 = entry.red;
604 fl::u8 green1 = entry.green;
605 fl::u8 blue1 = entry.blue;
606
607 fl::u8 blend = offset && (blendType != NOBLEND);
608 if (blend) {
609 if (index_5bit == 31) {
610 entry = FL_PGM_READ_DWORD_NEAR(&(pal[0]));
611 } else {
612 entry = FL_PGM_READ_DWORD_NEAR(&(pal[1]) + index_5bit);
613 }
614
615 // Calculate the scaling factor and scaled values for the lower palette
616 // value.
617 fl::u8 f1 = 255 - offset;
618 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
619 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
620 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
621
622 // Calculate the scaled values for the neighbouring palette value.
623 fl::u8 red2 = entry.red;
624 fl::u8 green2 = entry.green;
625 fl::u8 blue2 = entry.blue;
626 red2 = scale8_LEAVING_R1_DIRTY(red2, offset);
627 green2 = scale8_LEAVING_R1_DIRTY(green2, offset);
628 blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset);
629 cleanup_R1();
630
631 // These sums can't overflow, so no qadd8 needed.
632 red1 += red2;
633 green1 += green2;
634 blue1 += blue2;
635 }
636 if (brightness != 255) {
637 // nscale8x3_video(red1, green1, blue1, brightness);
638 nscale8x3(red1, green1, blue1, brightness);
639 }
640 return CRGB(red1, green1, blue1);
641}
642
644 fl::u8x8 brightness, TBlendType blendType) {
645 return ColorFromPaletteHDImpl<5>(
646 ProgmemRGBPaletteReader<32>{fl::span<const fl::u32, 32>(&pal[0], 32)},
647 index, brightness, blendType);
648}
649
651 fl::u8 brightness, TBlendType blendType) {
652 if (blendType == LINEARBLEND_NOWRAP) {
653 index = map8(index, 0, 239); // Blend range is affected by lo4 blend of
654 // values, remap to avoid wrapping
655 }
656
657 // hi4 = index >> 4;
658 fl::u8 hi4 = lsrX4(index);
659 fl::u8 lo4 = index & 0x0F;
660
661 CRGB entry(FL_PGM_READ_DWORD_NEAR(&(pal[0]) + hi4));
662
663 fl::u8 red1 = entry.red;
664 fl::u8 green1 = entry.green;
665 fl::u8 blue1 = entry.blue;
666
667 fl::u8 blend = lo4 && (blendType != NOBLEND);
668
669 if (blend) {
670
671 if (hi4 == 15) {
672 entry = FL_PGM_READ_DWORD_NEAR(&(pal[0]));
673 } else {
674 entry = FL_PGM_READ_DWORD_NEAR(&(pal[1]) + hi4);
675 }
676
677 fl::u8 f2 = lo4 << 4;
678 fl::u8 f1 = 255 - f2;
679
680 fl::u8 red2 = entry.red;
681 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
682 red2 = scale8_LEAVING_R1_DIRTY(red2, f2);
683 red1 += red2;
684
685 fl::u8 green2 = entry.green;
686 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
687 green2 = scale8_LEAVING_R1_DIRTY(green2, f2);
688 green1 += green2;
689
690 fl::u8 blue2 = entry.blue;
691 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
692 blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2);
693 blue1 += blue2;
694
695 cleanup_R1();
696 }
697
698 if (brightness != 255) {
699 if (brightness) {
700 ++brightness; // adjust for rounding
701 // Now, since brightness is nonzero, we don't need the full
702 // scale8_video logic; we can just to scale8 and then add one
703 // (unless scale8 fixed) to all nonzero inputs.
704 if (red1) {
705 red1 = scale8_LEAVING_R1_DIRTY(red1, brightness);
706#if !(FASTLED_SCALE8_FIXED == 1)
707 ++red1;
708#endif
709 }
710 if (green1) {
711 green1 = scale8_LEAVING_R1_DIRTY(green1, brightness);
712#if !(FASTLED_SCALE8_FIXED == 1)
713 ++green1;
714#endif
715 }
716 if (blue1) {
717 blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness);
718#if !(FASTLED_SCALE8_FIXED == 1)
719 ++blue1;
720#endif
721 }
722 cleanup_R1();
723 } else {
724 red1 = 0;
725 green1 = 0;
726 blue1 = 0;
727 }
728 }
729
730 return CRGB(red1, green1, blue1);
731}
732
733CRGB ColorFromPalette(const CRGBPalette32 &pal, fl::u8 index,
734 fl::u8 brightness, TBlendType blendType) {
735 if (blendType == LINEARBLEND_NOWRAP) {
736 index = map8(index, 0, 247); // Blend range is affected by lo3 blend of
737 // values, remap to avoid wrapping
738 }
739
740 fl::u8 hi5 = index;
741#if defined(FL_IS_AVR)
742 hi5 /= 2;
743 hi5 /= 2;
744 hi5 /= 2;
745#else
746 hi5 >>= 3;
747#endif
748 fl::u8 lo3 = index & 0x07;
749
750 // const CRGB* entry = &(pal[0]) + hi5;
751 // since hi5 is always 0..31, hi4 * sizeof(CRGB) can be a single-byte value,
752 // instead of the two byte 'int' that avr-gcc defaults to.
753 // So, we multiply hi5 X sizeof(CRGB), giving hi5XsizeofCRGB;
754 fl::u8 hi5XsizeofCRGB = hi5 * sizeof(CRGB);
755 // We then add that to a base array pointer.
756 const CRGB *entry = (CRGB *)((fl::u8 *)(&(pal[0])) + hi5XsizeofCRGB);
757
758 fl::u8 red1 = entry->red;
759 fl::u8 green1 = entry->green;
760 fl::u8 blue1 = entry->blue;
761
762 fl::u8 blend = lo3 && (blendType != NOBLEND);
763
764 if (blend) {
765
766 if (hi5 == 31) {
767 entry = &(pal[0]);
768 } else {
769 ++entry;
770 }
771
772 fl::u8 f2 = lo3 << 5;
773 fl::u8 f1 = 255 - f2;
774
775 fl::u8 red2 = entry->red;
776 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
777 red2 = scale8_LEAVING_R1_DIRTY(red2, f2);
778 red1 += red2;
779
780 fl::u8 green2 = entry->green;
781 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
782 green2 = scale8_LEAVING_R1_DIRTY(green2, f2);
783 green1 += green2;
784
785 fl::u8 blue2 = entry->blue;
786 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
787 blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2);
788 blue1 += blue2;
789
790 cleanup_R1();
791 }
792
793 if (brightness != 255) {
794 if (brightness) {
795 ++brightness; // adjust for rounding
796 // Now, since brightness is nonzero, we don't need the full
797 // scale8_video logic; we can just to scale8 and then add one
798 // (unless scale8 fixed) to all nonzero inputs.
799 if (red1) {
800 red1 = scale8_LEAVING_R1_DIRTY(red1, brightness);
801#if !(FASTLED_SCALE8_FIXED == 1)
802 ++red1;
803#endif
804 }
805 if (green1) {
806 green1 = scale8_LEAVING_R1_DIRTY(green1, brightness);
807#if !(FASTLED_SCALE8_FIXED == 1)
808 ++green1;
809#endif
810 }
811 if (blue1) {
812 blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness);
813#if !(FASTLED_SCALE8_FIXED == 1)
814 ++blue1;
815#endif
816 }
817 cleanup_R1();
818 } else {
819 red1 = 0;
820 green1 = 0;
821 blue1 = 0;
822 }
823 }
824
825 return CRGB(red1, green1, blue1);
826}
827
829 fl::u8 brightness, TBlendType blendType) {
830 if (blendType == LINEARBLEND_NOWRAP) {
831 index = map8(index, 0, 247); // Blend range is affected by lo3 blend of
832 // values, remap to avoid wrapping
833 }
834
835 fl::u8 hi5 = index;
836#if defined(FL_IS_AVR)
837 hi5 /= 2;
838 hi5 /= 2;
839 hi5 /= 2;
840#else
841 hi5 >>= 3;
842#endif
843 fl::u8 lo3 = index & 0x07;
844
845 CRGB entry(FL_PGM_READ_DWORD_NEAR(&(pal[0]) + hi5));
846
847 fl::u8 red1 = entry.red;
848 fl::u8 green1 = entry.green;
849 fl::u8 blue1 = entry.blue;
850
851 fl::u8 blend = lo3 && (blendType != NOBLEND);
852
853 if (blend) {
854
855 if (hi5 == 31) {
856 entry = FL_PGM_READ_DWORD_NEAR(&(pal[0]));
857 } else {
858 entry = FL_PGM_READ_DWORD_NEAR(&(pal[1]) + hi5);
859 }
860
861 fl::u8 f2 = lo3 << 5;
862 fl::u8 f1 = 255 - f2;
863
864 fl::u8 red2 = entry.red;
865 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
866 red2 = scale8_LEAVING_R1_DIRTY(red2, f2);
867 red1 += red2;
868
869 fl::u8 green2 = entry.green;
870 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
871 green2 = scale8_LEAVING_R1_DIRTY(green2, f2);
872 green1 += green2;
873
874 fl::u8 blue2 = entry.blue;
875 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
876 blue2 = scale8_LEAVING_R1_DIRTY(blue2, f2);
877 blue1 += blue2;
878
879 cleanup_R1();
880 }
881
882 if (brightness != 255) {
883 if (brightness) {
884 ++brightness; // adjust for rounding
885 // Now, since brightness is nonzero, we don't need the full
886 // scale8_video logic; we can just to scale8 and then add one
887 // (unless scale8 fixed) to all nonzero inputs.
888 if (red1) {
889 red1 = scale8_LEAVING_R1_DIRTY(red1, brightness);
890#if !(FASTLED_SCALE8_FIXED == 1)
891 ++red1;
892#endif
893 }
894 if (green1) {
895 green1 = scale8_LEAVING_R1_DIRTY(green1, brightness);
896#if !(FASTLED_SCALE8_FIXED == 1)
897 ++green1;
898#endif
899 }
900 if (blue1) {
901 blue1 = scale8_LEAVING_R1_DIRTY(blue1, brightness);
902#if !(FASTLED_SCALE8_FIXED == 1)
903 ++blue1;
904#endif
905 }
906 cleanup_R1();
907 } else {
908 red1 = 0;
909 green1 = 0;
910 blue1 = 0;
911 }
912 }
913
914 return CRGB(red1, green1, blue1);
915}
916
917CRGB ColorFromPalette(const CRGBPalette256 &pal, fl::u8 index,
918 fl::u8 brightness, TBlendType) {
919 const CRGB *entry = &(pal[0]) + index;
920
921 fl::u8 red = entry->red;
922 fl::u8 green = entry->green;
923 fl::u8 blue = entry->blue;
924
925 if (brightness != 255) {
926 ++brightness; // adjust for rounding
927 red = scale8_video_LEAVING_R1_DIRTY(red, brightness);
928 green = scale8_video_LEAVING_R1_DIRTY(green, brightness);
929 blue = scale8_video_LEAVING_R1_DIRTY(blue, brightness);
930 cleanup_R1();
931 }
932
933 return CRGB(red, green, blue);
934}
935
936CRGB ColorFromPaletteExtended(const CRGBPalette256 &pal, fl::u16 index,
937 fl::u8 brightness, TBlendType blendType) {
938 // Extract the eight most significant bits of the index as a palette index.
939 fl::u8 index_8bit = index >> 8;
940 // Calculate the 8-bit offset from the palette index.
941 fl::u8 offset = index & 0xff;
942 // Get the palette entry from the 8-bit index
943 const CRGB *entry = &(pal[0]) + index_8bit;
944 fl::u8 red1 = entry->red;
945 fl::u8 green1 = entry->green;
946 fl::u8 blue1 = entry->blue;
947
948 fl::u8 blend = offset && (blendType != NOBLEND);
949 if (blend) {
950 if (index_8bit == 255) {
951 entry = &(pal[0]);
952 } else {
953 entry++;
954 }
955
956 // Calculate the scaling factor and scaled values for the lower palette
957 // value.
958 fl::u8 f1 = 255 - offset;
959 red1 = scale8_LEAVING_R1_DIRTY(red1, f1);
960 green1 = scale8_LEAVING_R1_DIRTY(green1, f1);
961 blue1 = scale8_LEAVING_R1_DIRTY(blue1, f1);
962
963 // Calculate the scaled values for the neighbouring palette value.
964 fl::u8 red2 = entry->red;
965 fl::u8 green2 = entry->green;
966 fl::u8 blue2 = entry->blue;
967 red2 = scale8_LEAVING_R1_DIRTY(red2, offset);
968 green2 = scale8_LEAVING_R1_DIRTY(green2, offset);
969 blue2 = scale8_LEAVING_R1_DIRTY(blue2, offset);
970 cleanup_R1();
971
972 // These sums can't overflow, so no qadd8 needed.
973 red1 += red2;
974 green1 += green2;
975 blue1 += blue2;
976 }
977 if (brightness != 255) {
978 // nscale8x3_video(red1, green1, blue1, brightness);
979 nscale8x3(red1, green1, blue1, brightness);
980 }
981 return CRGB(red1, green1, blue1);
982}
983
984CRGB16 ColorFromPaletteHD(const CRGBPalette256 &pal, fl::u16 index,
985 fl::u8x8 brightness, TBlendType blendType) {
986 return ColorFromPaletteHDImpl<8>(
987 RuntimeRGBPaletteReader<256>{fl::span<const CRGB, 256>(&pal.entries[0], 256)},
988 index, brightness, blendType);
989}
990
991CHSV ColorFromPalette(const CHSVPalette16 &pal, fl::u8 index,
992 fl::u8 brightness, TBlendType blendType) {
993 if (blendType == LINEARBLEND_NOWRAP) {
994 index = map8(index, 0, 239); // Blend range is affected by lo4 blend of
995 // values, remap to avoid wrapping
996 }
997
998 // hi4 = index >> 4;
999 fl::u8 hi4 = lsrX4(index);
1000 fl::u8 lo4 = index & 0x0F;
1001
1002 // CRGB rgb1 = pal[ hi4];
1003 const CHSV *entry = &(pal[0]) + hi4;
1004
1005 fl::u8 hue1 = entry->hue;
1006 fl::u8 sat1 = entry->sat;
1007 fl::u8 val1 = entry->val;
1008
1009 fl::u8 blend = lo4 && (blendType != NOBLEND);
1010
1011 if (blend) {
1012
1013 if (hi4 == 15) {
1014 entry = &(pal[0]);
1015 } else {
1016 ++entry;
1017 }
1018
1019 fl::u8 f2 = lo4 << 4;
1020 fl::u8 f1 = 255 - f2;
1021
1022 fl::u8 hue2 = entry->hue;
1023 fl::u8 sat2 = entry->sat;
1024 fl::u8 val2 = entry->val;
1025
1026 // Now some special casing for blending to or from
1027 // either black or white. Black and white don't have
1028 // proper 'hue' of their own, so when ramping from
1029 // something else to/from black/white, we set the 'hue'
1030 // of the black/white color to be the same as the hue
1031 // of the other color, so that you get the expected
1032 // brightness or saturation ramp, with hue staying
1033 // constant:
1034
1035 // If we are starting from white (sat=0)
1036 // or black (val=0), adopt the target hue.
1037 if (sat1 == 0 || val1 == 0) {
1038 hue1 = hue2;
1039 }
1040
1041 // If we are ending at white (sat=0)
1042 // or black (val=0), adopt the starting hue.
1043 if (sat2 == 0 || val2 == 0) {
1044 hue2 = hue1;
1045 }
1046
1047 sat1 = scale8_LEAVING_R1_DIRTY(sat1, f1);
1048 val1 = scale8_LEAVING_R1_DIRTY(val1, f1);
1049
1050 sat2 = scale8_LEAVING_R1_DIRTY(sat2, f2);
1051 val2 = scale8_LEAVING_R1_DIRTY(val2, f2);
1052
1053 // cleanup_R1();
1054
1055 // These sums can't overflow, so no qadd8 needed.
1056 sat1 += sat2;
1057 val1 += val2;
1058
1059 fl::u8 deltaHue = (fl::u8)(hue2 - hue1);
1060 if (deltaHue & 0x80) {
1061 // go backwards
1062 hue1 -= scale8(256 - deltaHue, f2);
1063 } else {
1064 // go forwards
1065 hue1 += scale8(deltaHue, f2);
1066 }
1067
1068 cleanup_R1();
1069 }
1070
1071 if (brightness != 255) {
1072 val1 = scale8_video(val1, brightness);
1073 }
1074
1075 return CHSV(hue1, sat1, val1);
1076}
1077
1078CHSV ColorFromPalette(const CHSVPalette32 &pal, fl::u8 index,
1079 fl::u8 brightness, TBlendType blendType) {
1080 if (blendType == LINEARBLEND_NOWRAP) {
1081 index = map8(index, 0, 247); // Blend range is affected by lo3 blend of
1082 // values, remap to avoid wrapping
1083 }
1084
1085 fl::u8 hi5 = index;
1086#if defined(FL_IS_AVR)
1087 hi5 /= 2;
1088 hi5 /= 2;
1089 hi5 /= 2;
1090#else
1091 hi5 >>= 3;
1092#endif
1093 fl::u8 lo3 = index & 0x07;
1094
1095 fl::u8 hi5XsizeofCHSV = hi5 * sizeof(CHSV);
1096 const CHSV *entry = (CHSV *)((fl::u8 *)(&(pal[0])) + hi5XsizeofCHSV);
1097
1098 fl::u8 hue1 = entry->hue;
1099 fl::u8 sat1 = entry->sat;
1100 fl::u8 val1 = entry->val;
1101
1102 fl::u8 blend = lo3 && (blendType != NOBLEND);
1103
1104 if (blend) {
1105
1106 if (hi5 == 31) {
1107 entry = &(pal[0]);
1108 } else {
1109 ++entry;
1110 }
1111
1112 fl::u8 f2 = lo3 << 5;
1113 fl::u8 f1 = 255 - f2;
1114
1115 fl::u8 hue2 = entry->hue;
1116 fl::u8 sat2 = entry->sat;
1117 fl::u8 val2 = entry->val;
1118
1119 // Now some special casing for blending to or from
1120 // either black or white. Black and white don't have
1121 // proper 'hue' of their own, so when ramping from
1122 // something else to/from black/white, we set the 'hue'
1123 // of the black/white color to be the same as the hue
1124 // of the other color, so that you get the expected
1125 // brightness or saturation ramp, with hue staying
1126 // constant:
1127
1128 // If we are starting from white (sat=0)
1129 // or black (val=0), adopt the target hue.
1130 if (sat1 == 0 || val1 == 0) {
1131 hue1 = hue2;
1132 }
1133
1134 // If we are ending at white (sat=0)
1135 // or black (val=0), adopt the starting hue.
1136 if (sat2 == 0 || val2 == 0) {
1137 hue2 = hue1;
1138 }
1139
1140 sat1 = scale8_LEAVING_R1_DIRTY(sat1, f1);
1141 val1 = scale8_LEAVING_R1_DIRTY(val1, f1);
1142
1143 sat2 = scale8_LEAVING_R1_DIRTY(sat2, f2);
1144 val2 = scale8_LEAVING_R1_DIRTY(val2, f2);
1145
1146 // cleanup_R1();
1147
1148 // These sums can't overflow, so no qadd8 needed.
1149 sat1 += sat2;
1150 val1 += val2;
1151
1152 fl::u8 deltaHue = (fl::u8)(hue2 - hue1);
1153 if (deltaHue & 0x80) {
1154 // go backwards
1155 hue1 -= scale8(256 - deltaHue, f2);
1156 } else {
1157 // go forwards
1158 hue1 += scale8(deltaHue, f2);
1159 }
1160
1161 cleanup_R1();
1162 }
1163
1164 if (brightness != 255) {
1165 val1 = scale8_video(val1, brightness);
1166 }
1167
1168 return CHSV(hue1, sat1, val1);
1169}
1170
1171CHSV ColorFromPalette(const CHSVPalette256 &pal, fl::u8 index,
1172 fl::u8 brightness, TBlendType) {
1173 CHSV hsv = *(&(pal[0]) + index);
1174
1175 if (brightness != 255) {
1176 hsv.value = scale8_video(hsv.value, brightness);
1177 }
1178
1179 return hsv;
1180}
1181
1182namespace detail {
1183
1184template <typename TSrcPalette, typename TDestPalette>
1185void UpscalePaletteRepeat(const TSrcPalette &srcpal,
1186 TDestPalette &destpal) {
1187 const fl::u16 src_size = sizeof(srcpal.entries) / sizeof(srcpal.entries[0]);
1188 const fl::u16 dest_size =
1189 sizeof(destpal.entries) / sizeof(destpal.entries[0]);
1190 const fl::u16 repeat = dest_size / src_size;
1191 for (fl::u16 i = 0; i < src_size; ++i) {
1192 const fl::u16 start = i * repeat;
1193 for (fl::u16 j = 0; j < repeat; ++j) {
1194 destpal[static_cast<fl::u8>(start + j)] = srcpal[static_cast<fl::u8>(i)];
1195 }
1196 }
1197}
1198
1199template <typename TSrcPalette, typename TDestPalette>
1200void UpscalePaletteInterpolated(const TSrcPalette &srcpal,
1201 TDestPalette &destpal) {
1202 const fl::u16 dest_size =
1203 sizeof(destpal.entries) / sizeof(destpal.entries[0]);
1204 for (fl::u16 i = 0; i < dest_size; ++i) {
1205 destpal[static_cast<fl::u8>(i)] =
1206 ColorFromPalette(srcpal, static_cast<fl::u8>(i));
1207 }
1208}
1209
1210} // namespace detail
1211
1212void UpscalePalette(const class CRGBPalette16 &srcpal16,
1213 class CRGBPalette256 &destpal256) {
1214 detail::UpscalePaletteInterpolated(srcpal16, destpal256);
1215}
1216
1217void UpscalePalette(const class CHSVPalette16 &srcpal16,
1218 class CHSVPalette256 &destpal256) {
1219 detail::UpscalePaletteInterpolated(srcpal16, destpal256);
1220}
1221
1222void UpscalePalette(const class CRGBPalette16 &srcpal16,
1223 class CRGBPalette32 &destpal32) {
1224 detail::UpscalePaletteRepeat(srcpal16, destpal32);
1225}
1226
1227void UpscalePalette(const class CHSVPalette16 &srcpal16,
1228 class CHSVPalette32 &destpal32) {
1229 detail::UpscalePaletteRepeat(srcpal16, destpal32);
1230}
1231
1232void UpscalePalette(const class CRGBPalette32 &srcpal32,
1233 class CRGBPalette256 &destpal256) {
1234 detail::UpscalePaletteInterpolated(srcpal32, destpal256);
1235}
1236
1237void UpscalePalette(const class CHSVPalette32 &srcpal32,
1238 class CHSVPalette256 &destpal256) {
1239 detail::UpscalePaletteInterpolated(srcpal32, destpal256);
1240}
1241
1242#if 0
1243// replaced by PartyColors_p
1244void SetupPartyColors(CRGBPalette16& pal)
1245{
1246 fill_gradient( pal, 0, CHSV( HUE_PURPLE,255,255), 7, CHSV(HUE_YELLOW - 18,255,255), FORWARD_HUES);
1247 fill_gradient( pal, 8, CHSV( HUE_ORANGE,255,255), 15, CHSV(HUE_BLUE + 18,255,255), BACKWARD_HUES);
1248}
1249#endif
1250
1251void nblendPaletteTowardPalette(CRGBPalette16 &current, CRGBPalette16 &target,
1252 fl::u8 maxChanges) {
1253 fl::u8 *p1;
1254 fl::u8 *p2;
1255 fl::u8 changes = 0;
1256
1257 p1 = (fl::u8 *)current.entries;
1258 p2 = (fl::u8 *)target.entries;
1259
1260 const fl::u8 totalChannels = sizeof(CRGBPalette16);
1261 for (fl::u8 i = 0; i < totalChannels; ++i) {
1262 // if the values are equal, no changes are needed
1263 if (p1[i] == p2[i]) {
1264 continue;
1265 }
1266
1267 // if the current value is less than the target, increase it by one
1268 if (p1[i] < p2[i]) {
1269 ++p1[i];
1270 ++changes;
1271 }
1272
1273 // if the current value is greater than the target,
1274 // increase it by one (or two if it's still greater).
1275 if (p1[i] > p2[i]) {
1276 --p1[i];
1277 ++changes;
1278 if (p1[i] > p2[i]) {
1279 --p1[i];
1280 }
1281 }
1282
1283 // if we've hit the maximum number of changes, exit
1284 if (changes >= maxChanges) {
1285 break;
1286 }
1287 }
1288}
1289
1291 // Fixed-point path (s16x16) avoids pulling `__ieee754_pow` (libm,
1292 // ~2.7 KB) into release builds — see #2886 / #2910. Matches the
1293 // gamma8 LUT generator's approach in src/fl/math/ease.cpp.hpp.
1294 if (brightness == 0) {
1295 return 0;
1296 }
1297 constexpr fl::s16x16 inv_255_fp(1.0f / 255.0f);
1298 const fl::s16x16 gamma_fp(gamma);
1299 const fl::s16x16 x = static_cast<i32>(brightness) * inv_255_fp; // [0, 1]
1300 const fl::s16x16 r = fl::s16x16::pow(x, gamma_fp); // (0, 1]
1301 // Scale to u8 [0, 255] with round-half-up:
1302 // result = ((u32)raw * 255 + 0x8000) >> 16
1303 const fl::u32 scaled =
1304 (static_cast<fl::u32>(r.raw()) * 255u + 0x8000u) >> 16;
1305 fl::u8 result = static_cast<fl::u8>(scaled > 255u ? 255u : scaled);
1306 if (result == 0) {
1307 result = 1; // never gamma-adjust a positive number down to zero
1308 }
1309 return result;
1310}
1311
1312CRGB applyGamma_video(const CRGB &orig, float gamma) {
1313 CRGB adj;
1314 adj.r = applyGamma_video(orig.r, gamma);
1315 adj.g = applyGamma_video(orig.g, gamma);
1316 adj.b = applyGamma_video(orig.b, gamma);
1317 return adj;
1318}
1319
1320CRGB applyGamma_video(const CRGB &orig, float gammaR, float gammaG,
1321 float gammaB) {
1322 CRGB adj;
1323 adj.r = applyGamma_video(orig.r, gammaR);
1324 adj.g = applyGamma_video(orig.g, gammaG);
1325 adj.b = applyGamma_video(orig.b, gammaB);
1326 return adj;
1327}
1328
1331 return rgb;
1332}
1333
1334CRGB &napplyGamma_video(CRGB &rgb, float gammaR, float gammaG, float gammaB) {
1335 rgb = applyGamma_video(rgb, gammaR, gammaG, gammaB);
1336 return rgb;
1337}
1338
1339void napplyGamma_video(CRGB *rgbarray, fl::u16 count, float gamma) {
1340 for (fl::u16 i = 0; i < count; ++i) {
1341 rgbarray[i] = applyGamma_video(rgbarray[i], gamma);
1342 }
1343}
1344
1345void napplyGamma_video(CRGB *rgbarray, fl::u16 count, float gammaR,
1346 float gammaG, float gammaB) {
1347 for (fl::u16 i = 0; i < count; ++i) {
1348 rgbarray[i] = applyGamma_video(rgbarray[i], gammaR, gammaG, gammaB);
1349 }
1350}
1351
1352} // namespace fl
fl::CRGB leds[NUM_LEDS]
fl::UISlider brightness("Brightness", BRIGHTNESS, 0, 255)
fl::UISlider scale("Scale", 4,.1, 4,.1)
@ HUE_BLUE
Blue (225°)
Definition hsv.h:103
@ HUE_YELLOW
Yellow (90°)
Definition hsv.h:100
@ HUE_ORANGE
Orange (45°)
Definition hsv.h:99
@ HUE_PURPLE
Purple (270°)
Definition hsv.h:104
constexpr i32 raw() const FL_NOEXCEPT
Definition s16x16.h:60
static FASTLED_FORCE_INLINE s16x16 pow(s16x16 base, s16x16 exp) FL_NOEXCEPT
Definition s16x16.h:235
static constexpr FASTLED_FORCE_INLINE u8x8 from_raw(u16 raw) FL_NOEXCEPT
Definition u8x8.h:53
@ 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.
fl::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.
Internal FastLED header for implementation files.
void fill_gradient(T *targetArray, u16 startpos, CHSV startcolor, u16 endpos, CHSV endcolor, TGradientDirectionCode directionCode=SHORTEST_HUES) FL_NOEXCEPT
Fill a range of LEDs with a smooth HSV gradient between two HSV colors.
Definition fill.h:105
fl::hsv8 CHSV
Definition chsv.h:11
fl::CRGB CRGB
Definition crgb.h:25
LIB8STATIC fl::u8 map8(fl::u8 in, fl::u8 rangeStart, fl::u8 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:467
void scale_rgb_hd(CRGB16 &rgb, fl::u8x8 brightness)
fl::u16 lerp_channel_to_hd(fl::u8 a, fl::u8 b, fl::u16 frac, fl::u32 segment_size)
fl::u16 scale_hd_channel(fl::u16 channel, fl::u8x8 scale)
CRGB16 ColorFromPaletteHDImpl(const Reader &reader, fl::u16 index, fl::u8x8 brightness, TBlendType blendType)
void UpscalePaletteInterpolated(const TSrcPalette &srcpal, TDestPalette &destpal)
void UpscalePaletteRepeat(const TSrcPalette &srcpal, TDestPalette &destpal)
unsigned char u8
Definition s16x16x4.h:132
u8 fract8
Fixed-Point Fractional Types.
Definition s16x16x4.h:161
unsigned char u8
Definition stdint.h:131
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.
fl::CRGB CRGB
Definition video.h:15
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)
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)
CRGB16 ColorFromPaletteHD(const CRGBPalette32 &pal, fl::u16 index, fl::u8x8 brightness, TBlendType blendType)
fl::u8 applyGamma_video(fl::u8 brightness, float gamma)
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
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.
constexpr u32 gamma(float g) FL_NOEXCEPT
Definition gamma_lut.h:36
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)
Base definition for an LED controller.
Definition crgb.hpp:179
float green
Definition core_types.h:45
float red
Definition core_types.h:44
float blue
Definition core_types.h:46
Representation of an 8-bit RGB pixel (Red, Green, Blue)
Definition crgb.h:38