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