FastLED 3.9.15
Loading...
Searching...
No Matches
gamma_lut.h
Go to the documentation of this file.
1#pragma once
2
3// Compile-time PROGMEM gamma LUT generator for C++11.
4//
5// Usage:
6// typedef fl::ProgmemLUT<fl::GammaEval<fl::gamma<fl::u8x24>(2.2f)>, 256> Gamma22;
7// uint8_t corrected = Gamma22::read(pixel);
8//
9// The gamma table is generated entirely at compile time and stored in
10// PROGMEM (flash) on AVR/ESP platforms. No runtime floating-point math
11// is involved in lookups.
12//
13// Why integer template parameters:
14// C++11 does not allow float or class-type non-type template parameters.
15// gamma<u8x24>(2.2f) converts the float to a constexpr uint32_t (the
16// raw fixed-point representation), which IS a valid NTTP in C++11.
17// This is why the helper returns an integer: it's the only way to
18// encode a floating-point gamma value in a template parameter in C++11.
19// This approach is portable across all embedded toolchains.
20
21#include "fl/stl/int.h"
23#include "fl/stl/type_traits.h"
24#include "fastled_progmem.h"
25#include "fl/stl/noexcept.h"
26
27namespace fl {
28
29// ---------- Public API ----------
30
31// Convert a float gamma value to a fixed-point raw integer suitable for
32// use as a template parameter. The Fixed type determines the precision.
33//
34// Example: gamma<u8x24>(2.2f) returns constexpr uint32_t
35template <typename Fixed>
36constexpr u32 gamma(float g) FL_NOEXCEPT {
37 return Fixed(g).raw();
38}
39
40// Forward declarations
41template <u32 GammaRaw> struct GammaEval;
42template <typename Fn, fl::size N> struct ProgmemLUT;
43
44// Convenience alias: 256-entry gamma table
45template <u32 G>
47
48// ---------- Constexpr pow implementation (C++11 compatible) ----------
49//
50// All functions use the single-return-statement form required by C++11
51// constexpr. Branching is done via ternary operators, and multi-step
52// computations are decomposed into chains of constexpr function calls.
53
54namespace detail {
55namespace gamma_constexpr {
56
57constexpr int FRAC = 24;
58constexpr u32 SCALE = (u32)1 << FRAC; // 16777216
59
60// ---- Highest-bit finder (constexpr recursive) ----
61
62constexpr int hb_step(u32 v, int r) FL_NOEXCEPT {
63 return (v & 0xFFFF0000u) ? hb_step(v >> 16, r + 16)
64 : (v & 0x0000FF00u) ? hb_step(v >> 8, r + 8)
65 : (v & 0x000000F0u) ? hb_step(v >> 4, r + 4)
66 : (v & 0x0000000Cu) ? hb_step(v >> 2, r + 2)
67 : (v & 0x00000002u) ? r + 1
68 : r;
69}
70
71constexpr int highest_bit(u32 v) FL_NOEXCEPT {
72 return v == 0 ? -1 : hb_step(v, 0);
73}
74
75// ---- Fixed-point log2 (signed result, negative for inputs < 1.0) ----
76// Uses 4-term minimax polynomial for log2(1+t), t in [0,1).
77// Minimax coefficients minimize max error over the full interval,
78// unlike Taylor which diverges badly near t=1.
79
80constexpr u32 log2_t(u32 val, int msb) FL_NOEXCEPT {
81 return msb >= FRAC
82 ? (val >> (msb - FRAC)) - SCALE
83 : (val << (FRAC - msb)) - SCALE;
84}
85
86// Horner steps for log2(1+t) polynomial, split into separate
87// constexpr functions for C++11 single-return-statement compliance.
89 return (static_cast<i64>(-1788416LL) * t) >> FRAC;
90}
92 return ((6098176LL + log2_h3(t)) * t) >> FRAC;
93}
95 return ((-11728384LL + log2_h2(t)) * t) >> FRAC;
96}
98 return ((24189248LL + log2_h1(t)) * t) >> FRAC;
99}
100
101constexpr i32 log2_with_msb(u32 val, int msb) FL_NOEXCEPT {
102 // Use multiplication instead of left-shift to avoid UB when (msb - FRAC)
103 // is negative (left-shifting a negative value is undefined in C++).
104 return static_cast<i32>(
105 static_cast<i64>(msb - FRAC) * static_cast<i64>(1LL << FRAC) +
106 log2_h0(static_cast<i64>(log2_t(val, msb)))
107 );
108}
109
110constexpr i32 log2_fp(u32 val) FL_NOEXCEPT {
111 return val == 0 ? static_cast<i32>(0x80000000)
112 : log2_with_msb(val, highest_bit(val));
113}
114
115// ---- Fixed-point exp2 (handles both positive and negative exponents) ----
116// Uses 4-term minimax polynomial for 2^t - 1, t in [0,1).
117// Minimax coefficients minimize max error over the full interval.
118
119constexpr u64 exp2_h3(u64 fr) FL_NOEXCEPT {
120 return (214016ULL * fr) >> FRAC;
121}
122constexpr u64 exp2_h2(u64 fr) FL_NOEXCEPT {
123 return ((895232ULL + exp2_h3(fr)) * fr) >> FRAC;
124}
125constexpr u64 exp2_h1(u64 fr) FL_NOEXCEPT {
126 return ((4038400ULL + exp2_h2(fr)) * fr) >> FRAC;
127}
128constexpr u64 exp2_h0(u64 fr) FL_NOEXCEPT {
129 return ((11629376ULL + exp2_h1(fr)) * fr) >> FRAC;
130}
132 return (1ULL << FRAC) + exp2_h0(fr);
133}
134
135// exp2 for non-negative 8.24 fixed-point input.
136constexpr u32 exp2_pos(u32 x) FL_NOEXCEPT {
137 return (x >> FRAC) >= 8 ? 0xFFFFFFFFu
138 : static_cast<u32>(
139 (static_cast<u64>(SCALE << (x >> FRAC)) *
140 exp2_frac(x & (SCALE - 1))) >> FRAC
141 );
142}
143
144// exp2 for negative input: 2^(-P) = 2^(1-f) >> (n+1)
145// where P = n + f, n = integer part, f = fractional part.
146constexpr u32 exp2_neg(u32 pos_val) FL_NOEXCEPT {
147 return ((pos_val >> FRAC) + 1) >= 32 ? 0
148 : static_cast<u32>(
149 exp2_pos(SCALE - (pos_val & (SCALE - 1)))
150 >> ((pos_val >> FRAC) + 1)
151 );
152}
153
154constexpr u32 exp2_fp(i32 x) FL_NOEXCEPT {
155 return x >= 0 ? exp2_pos(static_cast<u32>(x))
156 : exp2_neg(static_cast<u32>(-x));
157}
158
159// ---- Fixed-point pow: base^exp via exp2(exp * log2(base)) ----
160
161constexpr u32 pow_fp(u32 base_raw, u32 exp_raw) FL_NOEXCEPT {
162 return base_raw == 0 ? 0
163 : exp_raw == 0 ? SCALE
164 : base_raw == SCALE ? SCALE
165 : exp2_fp(static_cast<i32>(
166 (static_cast<i64>(exp_raw) *
167 static_cast<i64>(log2_fp(base_raw))) >> FRAC
168 ));
169}
170
171// ---- 8-bit gamma correction for a single pixel value ----
172// Computes: clamp(round(pow(x/255, gamma) * 255), 0, 255)
173
174constexpr u8 eval(u8 x, u32 gamma_raw) FL_NOEXCEPT {
175 return x == 0 ? static_cast<u8>(0)
176 : x == 255 ? static_cast<u8>(255)
177 : static_cast<u8>(
178 (pow_fp(
179 static_cast<u32>((static_cast<u64>(x) << FRAC) / 255),
180 gamma_raw
181 ) * 255ULL + (SCALE >> 1)) >> FRAC
182 );
183}
184
185// ---- 16-bit gamma correction for a single pixel value ----
186// Computes: clamp(round(pow(x/255, gamma) * 65535), 0, 65535)
187// Used by five-bit brightness and HD108 pipelines.
188
189constexpr u16 eval16(u8 x, u32 gamma_raw) FL_NOEXCEPT {
190 return x == 0 ? static_cast<u16>(0)
191 : x == 255 ? static_cast<u16>(65535)
192 : static_cast<u16>(
193 (pow_fp(
194 static_cast<u32>((static_cast<u64>(x) << FRAC) / 255),
195 gamma_raw
196 ) * 65535ULL + (SCALE >> 1)) >> FRAC
197 );
198}
199
200} // namespace gamma_constexpr
201} // namespace detail
202
203// ---------- GammaEval functors ----------
204// Compile-time gamma evaluation functors. GammaRaw is a fixed-point
205// raw value obtained from gamma<u8x24>(float_value).
206
207// 8-bit output: input u8 [0,255] → output u8 [0,255]
208template <u32 GammaRaw>
209struct GammaEval {
210 constexpr u8 operator()(u8 x) const FL_NOEXCEPT {
211 return detail::gamma_constexpr::eval(x, GammaRaw);
212 }
213};
214
215// 16-bit output: input u8 [0,255] → output u16 [0,65535]
216// Use this for five-bit brightness and HD108 pipelines.
217template <u32 GammaRaw>
219 constexpr u16 operator()(u8 x) const FL_NOEXCEPT {
220 return detail::gamma_constexpr::eval16(x, GammaRaw);
221 }
222};
223
224// ---------- ProgmemLUT generators ----------
225
226namespace detail {
227
228template <typename T, fl::size N>
229struct LutArray {
230 T values[N];
231};
232
233template <typename Fn, fl::size... Is>
234constexpr LutArray<u8, sizeof...(Is)>
236 return {{ Fn()(static_cast<u8>(Is))... }};
237}
238
239template <typename Fn, fl::size... Is>
240constexpr LutArray<u16, sizeof...(Is)>
242 return {{ Fn()(static_cast<u8>(Is))... }};
243}
244
245} // namespace detail
246
247// 8-bit PROGMEM lookup table (u8 input → u8 output).
248// Fn must be a functor with constexpr operator()(u8) returning u8.
249template <typename Fn, fl::size N>
251 static u8 read(u8 index) FL_NOEXCEPT {
252 return FL_PGM_READ_BYTE_NEAR(&kData.values[index]);
253 }
254
256};
257
258template <typename Fn, fl::size N>
262
263// 16-bit PROGMEM lookup table (u8 input → u16 output).
264// Fn must be a functor with constexpr operator()(u8) returning u16.
265// Used by five-bit brightness and HD108 gamma pipelines.
266template <typename Fn, fl::size N>
268 static u16 read(u8 index) FL_NOEXCEPT {
269 return FL_PGM_READ_WORD_NEAR(&kData.values[index]);
270 }
271
272 // Direct access to the raw PROGMEM array for hot-loop usage
273 // (avoids per-element function call overhead).
274 static const u16* data() FL_NOEXCEPT { return kData.values; }
275
277};
278
279template <typename Fn, fl::size N>
280FL_ALIGN_PROGMEM(64) const detail::LutArray<u16, N>
282 detail::make_lut_u16<Fn>(fl::make_index_sequence<N>{});
283
284// ---------- Convenience aliases ----------
285
286// 256-entry 8-bit gamma table
287template <u32 G>
288using GammaTable256 = ProgmemLUT<GammaEval<G>, 256>;
289
290// 256-entry 16-bit gamma table (for five-bit brightness, HD108)
291template <u32 G>
293
294// ---------- Pre-defined gamma tables ----------
295
296// 8-bit gamma tables (u8 input → u8 output).
297// Usage: uint8_t corrected = fl::Gamma22LUT::read(pixel);
300
301// 16-bit gamma tables (u8 input → u16 output).
302// Usage: uint16_t val = fl::Gamma22LUT16::read(pixel);
305
306// Explicit instantiation declaration for the most commonly used LUT.
307// Suppresses per-TU ~6KB _GLOBAL__sub_I_ init blocks across all modules.
308// Definition lives in gamma_lut.cpp.hpp (compiled once in fl.gfx+).
309// Other LUT variants are instantiated implicitly on demand.
310extern template struct ProgmemLUT16<GammaEval16<gamma<u8x24>(2.8f)>, 256>;
311
312} // namespace fl
#define FL_ALIGN_PROGMEM(N)
Force N-byte alignment for platforms with unaligned access or cache-line optimization.
#define FL_PGM_READ_BYTE_NEAR(x)
Read a byte (8-bit) from PROGMEM memory.
#define FL_PGM_READ_WORD_NEAR(x)
Read a word (16-bit) from PROGMEM memory.
#define FL_PROGMEM
PROGMEM keyword for storage.
Wrapper definitions to allow seamless use of PROGMEM in environments that have it.
constexpr u16 eval16(u8 x, u32 gamma_raw) FL_NOEXCEPT
Definition gamma_lut.h:189
constexpr u32 pow_fp(u32 base_raw, u32 exp_raw) FL_NOEXCEPT
Definition gamma_lut.h:161
constexpr i64 log2_h1(i64 t) FL_NOEXCEPT
Definition gamma_lut.h:94
constexpr i32 log2_fp(u32 val) FL_NOEXCEPT
Definition gamma_lut.h:110
constexpr u64 exp2_h3(u64 fr) FL_NOEXCEPT
Definition gamma_lut.h:119
constexpr u32 exp2_pos(u32 x) FL_NOEXCEPT
Definition gamma_lut.h:136
constexpr int highest_bit(u32 v) FL_NOEXCEPT
Definition gamma_lut.h:71
constexpr u64 exp2_h0(u64 fr) FL_NOEXCEPT
Definition gamma_lut.h:128
constexpr u32 exp2_fp(i32 x) FL_NOEXCEPT
Definition gamma_lut.h:154
constexpr u32 log2_t(u32 val, int msb) FL_NOEXCEPT
Definition gamma_lut.h:80
constexpr u64 exp2_frac(u64 fr) FL_NOEXCEPT
Definition gamma_lut.h:131
constexpr i64 log2_h3(i64 t) FL_NOEXCEPT
Definition gamma_lut.h:88
constexpr i64 log2_h0(i64 t) FL_NOEXCEPT
Definition gamma_lut.h:97
constexpr u64 exp2_h2(u64 fr) FL_NOEXCEPT
Definition gamma_lut.h:122
constexpr u64 exp2_h1(u64 fr) FL_NOEXCEPT
Definition gamma_lut.h:125
constexpr i32 log2_with_msb(u32 val, int msb) FL_NOEXCEPT
Definition gamma_lut.h:101
constexpr u8 eval(u8 x, u32 gamma_raw) FL_NOEXCEPT
Definition gamma_lut.h:174
constexpr u32 exp2_neg(u32 pos_val) FL_NOEXCEPT
Definition gamma_lut.h:146
constexpr int hb_step(u32 v, int r) FL_NOEXCEPT
Definition gamma_lut.h:62
constexpr i64 log2_h2(i64 t) FL_NOEXCEPT
Definition gamma_lut.h:91
constexpr LutArray< u16, sizeof...(Is)> make_lut_u16(fl::index_sequence< Is... >) FL_NOEXCEPT
Definition gamma_lut.h:241
constexpr LutArray< u8, sizeof...(Is)> make_lut_u8(fl::index_sequence< Is... >) FL_NOEXCEPT
Definition gamma_lut.h:235
Compile-time linker keep-alive hook for a single fl::Bus.
Definition bus_traits.h:48
typename make_index_sequence_impl< N >::type make_index_sequence
Definition s16x16x4.h:1168
unsigned char u8
Definition stdint.h:131
const detail::LutArray< u8, N > ProgmemLUT< Fn, N >::kData
Definition gamma_lut.h:260
ProgmemLUT< GammaEval< gamma< u8x24 >(2.8f)>, 256 > Gamma28LUT
Definition gamma_lut.h:299
ProgmemLUT16< GammaEval16< gamma< u8x24 >(2.2f)>, 256 > Gamma22LUT16
Definition gamma_lut.h:303
ProgmemLUT16< GammaEval16< G >, 256 > GammaTable16_256
Definition gamma_lut.h:292
fl::i64 i64
Definition s16x16x4.h:222
constexpr u32 gamma(float g) FL_NOEXCEPT
Definition gamma_lut.h:36
typename make_index_sequence_impl< N >::type make_index_sequence
ProgmemLUT< GammaEval< G >, 256 > GammaTable256
Definition gamma_lut.h:46
fl::u64 u64
Definition s16x16x4.h:221
ProgmemLUT16< GammaEval16< gamma< u8x24 >(2.8f)>, 256 > Gamma28LUT16
Definition gamma_lut.h:304
ProgmemLUT< GammaEval< gamma< u8x24 >(2.2f)>, 256 > Gamma22LUT
Definition gamma_lut.h:298
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT
constexpr u16 operator()(u8 x) const FL_NOEXCEPT
Definition gamma_lut.h:219
constexpr u8 operator()(u8 x) const FL_NOEXCEPT
Definition gamma_lut.h:210
static const detail::LutArray< u16, N > kData
Definition gamma_lut.h:276
static u16 read(u8 index) FL_NOEXCEPT
Definition gamma_lut.h:268
static const u16 * data() FL_NOEXCEPT
Definition gamma_lut.h:274
static u8 read(u8 index) FL_NOEXCEPT
Definition gamma_lut.h:251
static const detail::LutArray< u8, N > kData
Definition gamma_lut.h:255