FastLED 3.10.8
Loading...
Searching...
No Matches
five_bit_hd_gamma.cpp.hpp
Go to the documentation of this file.
2#include "fl/stl/align.h"
3#include "fl/math/ease.h"
4#include "fl/system/fastled.h"
5#include "fl/stl/noexcept.h"
6
8
9namespace fl {
10
11namespace {
12namespace five_bit_impl {
13
14// ix/31 * 255/65536 * 256 scaling factors, valid for indexes 1..31.
15// Uses flash storage on AVR/ESP, zero heap allocation on all platforms.
16FL_ALIGN_PROGMEM(4) static constexpr u32 BRIGHT_SCALE[32] FL_PROGMEM = {
17 0, 2023680, 1011840, 674560, 505920, 404736, 337280, 289097,
18 252960, 224853, 202368, 183971, 168640, 155668, 144549, 134912,
19 126480, 119040, 112427, 106509, 101184, 96366, 91985, 87986,
20 84320, 80947, 77834, 74951, 72274, 69782, 67456, 65280};
21
22// Inlined gamma lookup with alignment enforced by aligned_ptr type.
23// Avoids cross-TU function call overhead of gamma_2_8() in hot span loops.
27
28// Branchless scale16by8: (i * (1 + scale)) >> 8.
29// Eliminates the if(scale==0) branch from the standard scale16by8.
30// The caller is responsible for skipping the call when scale==0.
31FL_ALWAYS_INLINE u16 scale16by8_nozero(u16 i, u16 scale_plus_one) FL_NO_EXCEPT {
32 return static_cast<u16>((static_cast<u32>(i) * scale_plus_one) >> 8);
33}
34
35// Core per-pixel transform, fully inlined. All uniform values are
36// precomputed by the caller and passed in to avoid per-pixel branches.
38 u16 r16, u16 g16, u16 b16, u8 brightness,
39 // Precomputed: (1 + brightness), or 0 if brightness==0xff (skip scaling)
40 u16 bright_p1, bool apply_brightness,
41 CRGB *out, u8 *out_power_5bit) FL_NO_EXCEPT {
42
43 // All-zero fast path (rare but worth checking — writes are cheap).
44 if ((r16 | g16 | b16) == 0) {
45 *out = CRGB(0, 0, 0);
46 *out_power_5bit = (brightness <= 31) ? brightness : 31;
47 return;
48 }
49
50 // Apply brightness: branchless multiply, skip only if brightness==0xff.
51 if (apply_brightness) {
52 r16 = scale16by8_nozero(r16, bright_p1);
53 g16 = scale16by8_nozero(g16, bright_p1);
54 b16 = scale16by8_nozero(b16, bright_p1);
55 }
56
57 // max3 via branchless ternary (compiler emits cmov on x86, csel on ARM).
58 u16 scale = r16;
59 if (g16 > scale) scale = g16;
60 if (b16 > scale) scale = b16;
61
62 if (scale == 0) {
63 *out = CRGB(0, 0, 0);
64 *out_power_5bit = 0;
65 return;
66 }
67
68 // 5-bit quantized scale at or above maximum value.
69 scale = (scale + (2047 - (scale >> 5))) >> 11;
70
71 u32 scalef = FL_PGM_READ_DWORD_ALIGNED(&BRIGHT_SCALE[scale]);
72 u8 r8 = static_cast<u8>((r16 * scalef + 0x808000) >> 24);
73 u8 g8 = static_cast<u8>((g16 * scalef + 0x808000) >> 24);
74 u8 b8 = static_cast<u8>((b16 * scalef + 0x808000) >> 24);
75
76 *out = CRGB(r8, g8, b8);
77 *out_power_5bit = static_cast<u8>(scale);
78}
79
80} // namespace five_bit_impl
81} // anonymous namespace
82
85 fl::span<const CRGB> colors, CRGB colors_scale, u8 global_brightness,
86 fl::span<CRGB> out_colors, fl::span<u8> out_power_5bit) FL_NO_EXCEPT {
87
88 u16 n = static_cast<u16>(colors.size());
89 if (out_colors.size() < n) n = static_cast<u16>(out_colors.size());
90 if (out_power_5bit.size() < n) n = static_cast<u16>(out_power_5bit.size());
91
92 // Brightness==0: zero everything and return immediately.
93 if (global_brightness == 0) {
94 for (u16 i = 0; i < n; ++i) {
95 out_colors[i] = CRGB(0, 0, 0);
96 out_power_5bit[i] = 0;
97 }
98 return;
99 }
100
102
103 // Precompute color-scale multipliers once (avoid per-pixel branches).
104 const bool apply_r_scale = (colors_scale.r != 0xff);
105 const bool apply_g_scale = (colors_scale.g != 0xff);
106 const bool apply_b_scale = (colors_scale.b != 0xff);
107 const u16 rscale_p1 = 1u + static_cast<u16>(colors_scale.r);
108 const u16 gscale_p1 = 1u + static_cast<u16>(colors_scale.g);
109 const u16 bscale_p1 = 1u + static_cast<u16>(colors_scale.b);
110
111 // Precompute brightness multiplier once.
112 const bool apply_brightness = (global_brightness != 0xff);
113 const u16 bright_p1 = 1u + static_cast<u16>(global_brightness);
114
115 for (u16 i = 0; i < n; ++i) {
116 const CRGB &c = colors[i];
117
118 u16 r16 = five_bit_impl::gamma_lut_read(glut, c.r);
119 u16 g16 = five_bit_impl::gamma_lut_read(glut, c.g);
120 u16 b16 = five_bit_impl::gamma_lut_read(glut, c.b);
121
122 // Color-scale: branchless multiply, guarded by precomputed bools.
123 if (apply_r_scale) r16 = five_bit_impl::scale16by8_nozero(r16, rscale_p1);
124 if (apply_g_scale) g16 = five_bit_impl::scale16by8_nozero(g16, gscale_p1);
125 if (apply_b_scale) b16 = five_bit_impl::scale16by8_nozero(b16, bscale_p1);
126
127 five_bit_impl::five_bit_pixel(r16, g16, b16, global_brightness,
128 bright_p1, apply_brightness,
129 &out_colors[i], &out_power_5bit[i]);
130 }
131}
132
135 fl::span<const CRGB> colors, CRGB colors_scale, u8 global_brightness,
137
138 u16 n = static_cast<u16>(colors.size());
139 if (out.size() < n) n = static_cast<u16>(out.size());
140
141 // Brightness==0: zero everything and return immediately.
142 if (global_brightness == 0) {
143 for (u16 i = 0; i < n; ++i) {
144 out[i].color = CRGB(0, 0, 0);
145 out[i].brightness_5bit = 0;
146 }
147 return;
148 }
149
151
152 // Precompute color-scale multipliers once (avoid per-pixel branches).
153 const bool apply_r_scale = (colors_scale.r != 0xff);
154 const bool apply_g_scale = (colors_scale.g != 0xff);
155 const bool apply_b_scale = (colors_scale.b != 0xff);
156 const u16 rscale_p1 = 1u + static_cast<u16>(colors_scale.r);
157 const u16 gscale_p1 = 1u + static_cast<u16>(colors_scale.g);
158 const u16 bscale_p1 = 1u + static_cast<u16>(colors_scale.b);
159
160 // Precompute brightness multiplier once.
161 const bool apply_brightness = (global_brightness != 0xff);
162 const u16 bright_p1 = 1u + static_cast<u16>(global_brightness);
163
164 for (u16 i = 0; i < n; ++i) {
165 const CRGB &c = colors[i];
166
167 u16 r16 = five_bit_impl::gamma_lut_read(glut, c.r);
168 u16 g16 = five_bit_impl::gamma_lut_read(glut, c.g);
169 u16 b16 = five_bit_impl::gamma_lut_read(glut, c.b);
170
171 // Color-scale: branchless multiply, guarded by precomputed bools.
172 if (apply_r_scale) r16 = five_bit_impl::scale16by8_nozero(r16, rscale_p1);
173 if (apply_g_scale) g16 = five_bit_impl::scale16by8_nozero(g16, gscale_p1);
174 if (apply_b_scale) b16 = five_bit_impl::scale16by8_nozero(b16, bscale_p1);
175
176 five_bit_impl::five_bit_pixel(r16, g16, b16, global_brightness,
177 bright_p1, apply_brightness,
178 &out[i].color, &out[i].brightness_5bit);
179 }
180}
181
182namespace detail {
183
184void five_bit_hd_solve16(u16 r16, u16 g16, u16 b16, u8 min_field, CRGB* out,
185 u8* out_field) FL_NO_EXCEPT {
186 // Target light per channel, in code x field units with eight fraction
187 // bits: a drive of 65535 is 255 x 31. The legacy solve's field rule is
188 // not monotonic at 16-bit resolution -- a one-step rise can move it to a
189 // field whose rounding lands lower -- so this uses its own: the smallest
190 // field f with T < 255.5 f, and codes rounded at that field. Within a
191 // field, rounding is monotonic; across a transition the new field's light
192 // is at least T - (f + 1) / 2 > 255 f, which is at least the old field's
193 // maximum. So emitted light never falls as a drive rises.
194 if (min_field < 1) {
195 min_field = 1;
196 }
197 if (min_field > 31) {
198 min_field = 31;
199 }
200 const u16 drives[3] = {r16, g16, b16};
201 u32 target[3];
202 u32 brightest = 0;
203 for (int i = 0; i < 3; ++i) {
204 target[i] = static_cast<u32>(
205 (static_cast<u64>(drives[i]) * 7905u * 256u) / 65535u); // 7905 = 255 x 31; u64 throughout (AVR int is 16 bits)
206 if (target[i] > brightest) {
207 brightest = target[i];
208 }
209 }
210 // 255.5 in eight fraction bits is 65408.
211 u32 field = brightest / 65408u + 1u;
212 if (field < min_field) {
213 field = min_field;
214 }
215 if (field > 31u) {
216 field = 31u;
217 }
218 const u32 unit = field * 256u;
219 for (int i = 0; i < 3; ++i) {
220 const u32 code = (target[i] + unit / 2u) / unit;
221 out->raw[i] = static_cast<u8>(code > 255u ? 255u : code);
222 }
223 *out_field = static_cast<u8>(field);
224}
225
226} // namespace detail
227
228} // namespace fl
229
fl::UISlider brightness("Brightness", BRIGHTNESS, 0, 255)
fl::UISlider scale("Scale", 4,.1, 4,.1)
Alignment macros and utilities for FastLED.
#define FL_OPTIMIZATION_LEVEL_O3_BEGIN
#define FL_ALWAYS_INLINE
#define FL_OPTIMIZATION_LEVEL_O3_END
#define FL_OPTIMIZE_FUNCTION
#define constexpr
Declares that it is possible to evaluate a value at compile time, introduced in C++11.
Definition cpp_compat.h:15
#define FL_ALIGN_PROGMEM(N)
Force N-byte alignment for platforms with unaligned access or cache-line optimization.
#define FL_PGM_READ_DWORD_ALIGNED(addr)
#define FL_PGM_READ_WORD_ALIGNED(addr)
#define FL_PROGMEM
PROGMEM keyword for storage.
Declares functions for five-bit gamma correction.
Internal FastLED header for implementation files.
fl::CRGB CRGB
Definition crgb.h:25
FL_ALWAYS_INLINE u16 gamma_lut_read(aligned_ptr< const u16, 64 > lut, u8 idx) FL_NO_EXCEPT
FL_ALWAYS_INLINE u16 scale16by8_nozero(u16 i, u16 scale_plus_one) FL_NO_EXCEPT
FL_ALWAYS_INLINE void five_bit_pixel(u16 r16, u16 g16, u16 b16, u8 brightness, u16 bright_p1, bool apply_brightness, CRGB *out, u8 *out_power_5bit) FL_NO_EXCEPT
void five_bit_hd_solve16(u16 r16, u16 g16, u16 b16, u8 min_field, CRGB *out, u8 *out_field) FL_NO_EXCEPT
unsigned char u8
Definition stdint.h:131
fl::CRGB CRGB
Definition video.h:18
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
const u16 GAMMA_2_8_LUT[256]
FL_OPTIMIZE_FUNCTION void five_bit_hd_gamma_bitshift(fl::span< const CRGB > colors, CRGB colors_scale, u8 global_brightness, fl::span< CRGB > out_colors, fl::span< u8 > out_power_5bit) FL_NO_EXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
fl::u64 u64
Definition stdint.h:220
Representation of an 8-bit RGB pixel (Red, Green, Blue)
Definition crgb.h:38