FastLED 3.10.8
Loading...
Searching...
No Matches
options.h
Go to the documentation of this file.
1#pragma once
2
3#include "led_sysdefs.h" // IWYU pragma: keep
4#include "pixeltypes.h" // IWYU pragma: keep
5#include "color.h"
6#include "dither_mode.h"
7#include "fl/channels/bus.h"
8#include "rgbw.h" // IWYU pragma: keep
9#include "fl/gfx/rgbww.h" // IWYU pragma: keep
10#include "fl/stl/optional.h"
11#include "fl/stl/variant.h"
14#include "fl/log/log.h"
15#include "fl/stl/compiler_control.h" // FL_DEPRECATED
16
17namespace fl {
18
19// Declared rather than included. Every path that binds a profile has to
20// install these -- there are three, and two of them do not go through
21// setColorProfile: Channel::create<Profile>,
22// ChannelOptions::withColorProfile<Profile> both set mStaticProfile directly.
23// Missing one leaves that channel silently on the legacy path, which is the
24// exact bug this whole change exists to fix.
25//
26// Declared rather than included. `pipeline_binding.h` pulls the whole gfx
27// pipeline in behind it, and reaching those headers from here puts them
28// ahead of whatever brings `EmitterProfile` into scope unqualified --
29// `device_solve.h`, `gamut_map.h` and `white_allocation.h` all stop
30// compiling. One declaration is all this file needs, and keeping it to one
31// also keeps `options.h` cheap for every translation unit that includes it.
33
34// Emits the ProfileClearedByLegacy warning event through the pipeline hooks,
35// so a sketch that never binds a profile does not link the event machinery.
37
38
39#ifndef FL_COLOR_PROFILE_RUNTIME
40#if defined(FL_IS_AVR)
41#define FL_COLOR_PROFILE_RUNTIME 0
42#else
43#define FL_COLOR_PROFILE_RUNTIME (!FL_PLATFORM_HAS_TINY_MEMORY)
44#endif
45#endif
46
47#if defined(FL_IS_AVR) && FL_COLOR_PROFILE_RUNTIME
48#error "Runtime color profiles are unsupported on AVR"
49#endif
50
59#ifndef FL_COLOR_PIPELINE_SHARED
60#define FL_COLOR_PIPELINE_SHARED (FL_COLOR_PROFILE_RUNTIME && FL_PLATFORM_HAS_LARGE_MEMORY)
61#endif
62
96 Bus mBus = Bus::AUTO; // Typed driver selection
99 fl::variant<fl::Empty, Rgbw, Rgbww> mWhiteCfg;
100 fl::optional<float> mGamma; // Gamma correction (nullopt = use default 2.8)
101#if FL_COLOR_PROFILE_RUNTIME
102 ColorProfileBinding mColorProfile;
103#endif
104 fl::u8 mBusWhich = 0; // Instance selector for portable buses
105
107#if FL_COLOR_PROFILE_RUNTIME
108 return setColorProfile(profile, SourceProfile::linearSrgb(), true);
109#else
110 FL_UNUSED(profile);
111 return false;
112#endif
113 }
114 bool setColorProfile(const EmitterProfile& profile,
115 SourceProfile source,
117#if FL_COLOR_PROFILE_RUNTIME
118 return setColorProfile(profile, source, false, gamut);
119#else
120 FL_UNUSED(profile); FL_UNUSED(source); FL_UNUSED(gamut);
121 return false;
122#endif
123 }
124 bool setColorProfile(const EmitterProfile& profile, SourceProfile source,
125 bool global_source, GamutPolicy gamut = GamutPolicy::ChromaCompress) FL_NO_EXCEPT {
126#if !FL_COLOR_PROFILE_RUNTIME
127 FL_UNUSED(profile); FL_UNUSED(source); FL_UNUSED(global_source); FL_UNUSED(gamut);
128 return false;
129#else
130 // Captured before mStaticProfile is cleared below, so the warning
131 // condition still sees whether a profile was bound on entry.
132 const bool had_profile = hasColorProfile();
133 mColorProfile.mStaticProfile = nullptr;
134 if (profile.native_code_depth == 0 || profile.native_code_depth > 16 ||
135 !validChromaticity(profile.xy_r) || !validChromaticity(profile.xy_g) ||
136 !validChromaticity(profile.xy_b) || !validPrimaries(source.primaries) ||
137 !finitePositive(profile.lum_r) || !finitePositive(profile.lum_g) || !finitePositive(profile.lum_b) ||
138 (profile.topology != colorimetric_response::EmitterTopology::RGB &&
141 (profile.topology != colorimetric_response::EmitterTopology::RGB &&
142 (!validChromaticity(profile.xy_white1) ||
143 !finitePositive(profile.lum_white1) ||
144 (profile.response_lut_size != 0 &&
145 profile.response_lut_white1 == nullptr))) ||
147 (!validChromaticity(profile.xy_white2) ||
148 !finitePositive(profile.lum_white2) ||
149 (profile.response_lut_size != 0 &&
150 profile.response_lut_white2 == nullptr))) ||
151 (profile.response_lut_size != 0 &&
152 (profile.response_lut_r == nullptr || profile.response_lut_g == nullptr || profile.response_lut_b == nullptr))) {
154 return false;
155 }
156 if (!validResponseLut(profile.response_lut_r, profile.response_lut_size) ||
157 !validResponseLut(profile.response_lut_g, profile.response_lut_size) ||
158 !validResponseLut(profile.response_lut_b, profile.response_lut_size)) {
160 return false;
161 }
162 if (profile.topology != colorimetric_response::EmitterTopology::RGB &&
163 !validResponseLut(profile.response_lut_white1,
164 profile.response_lut_size)) {
166 return false;
167 }
168 if (profile.topology == colorimetric_response::EmitterTopology::RGBWW &&
169 !validResponseLut(profile.response_lut_white2,
170 profile.response_lut_size)) {
172 return false;
173 }
174 // Warn only once validation has passed. Warning earlier consumed
175 // mWarnedLegacyCleared on a profile that was then rejected, and
176 // clearColorProfile() leaves mCorrection/mTemperature alone -- so the
177 // next *valid* profile cleared the caller's legacy settings silently.
178 // Every field cleared below whose loss is detectable has to be
179 // represented here, or the caller loses it in silence. This used to
180 // name mCorrection and mTemperature only, while the clear also resets
181 // mGamma -- so a caller who set just a gamma had it discarded with no
182 // warning, which is exactly what the note above is about (#4331).
183 //
184 // mDitherMode is deliberately absent, and cannot be added by
185 // comparison. Its default is BINARY_DITHER and the clear sets
186 // DISABLE_DITHER, so `mDitherMode != BINARY_DITHER` is true precisely
187 // when the caller already chose DISABLE_DITHER and nothing is being
188 // taken away, and false in the case that loses dithering. Detecting
189 // that needs a "the caller set this" flag, which mGamma gets for free
190 // by being an optional. Until then, binding a profile turns dithering
191 // off quietly (#4331).
192 const bool clearing_legacy =
195 mGamma.has_value();
196 if (!had_profile && clearing_legacy && !mWarnedLegacyCleared) {
197 FL_WARN("Color profile clears legacy correction/temperature/gamma");
200 mWarnedLegacyCleared = true;
201 }
202 // The one call that makes the colour pipeline reachable. Everything
203 // downstream goes through function pointers these install, so a
204 // program that never gets here never references the pipeline and the
205 // linker drops it -- about 3.5 KB of flash for sketches that do not
206 // ask for colour management.
208 mColorProfile.mStorage = fl::make_shared<ColorProfileStorage>(profile);
209 mColorProfile.mSource = source;
210 mColorProfile.mGamut = gamut;
211 mColorProfile.mRequested = true;
212 mColorProfile.mUseGlobalSourceDefault = global_source;
215 mGamma.reset();
217 return true;
218#endif
219 }
221#if FL_COLOR_PROFILE_RUNTIME
223 mColorProfile.mSource = source;
224 mColorProfile.mRequested = true;
225#else
226 FL_UNUSED(source);
227#endif
228 }
230#if FL_COLOR_PROFILE_RUNTIME
231 mColorProfile.mStorage.reset();
232 mColorProfile.mStaticProfile = nullptr;
233 mColorProfile.mRequested = false;
234 // Also drop the global-source opt-in. Without this, a profile bound
235 // by setColorProfile() leaves the flag set, and a following
236 // requestColorManagement(explicitSource) has its source silently
237 // replaced by defaultSourceProfile() in Channel::create().
238 // setColorProfile() re-establishes the flag itself when it succeeds.
239 mColorProfile.mUseGlobalSourceDefault = false;
240#endif
241 }
243#if FL_COLOR_PROFILE_RUNTIME
244 return mColorProfile.active();
245#else
246 return false;
247#endif
248 }
249
261 FL_DEPRECATED("Ask the channel: Channel::isColorManaged()")
262 bool isColorManaged() const FL_NO_EXCEPT { return false; }
264#if FL_COLOR_PROFILE_RUNTIME
265 return mColorProfile.profile();
266#else
267 return nullptr;
268#endif
269 }
272 bool hasTargetWhite() const FL_NO_EXCEPT;
273
274 template<const EmitterProfile& Profile>
276#ifdef FL_IS_AVR
277 FL_STATIC_ASSERT(&Profile == nullptr, "Color profiles require the Channels API on a supported non-AVR platform");
278#endif
279 ChannelOptions options;
280#if FL_COLOR_PROFILE_RUNTIME
282 options.mColorProfile.mStaticProfile = &Profile;
283 options.mColorProfile.mRequested = true;
284#endif
285 return options;
286 }
289
290private:
291 static bool validChromaticity(const float xy[2]) FL_NO_EXCEPT {
292 return xy[0] > 0.0f && xy[0] < 1.0f && xy[1] > 0.0f && xy[1] < 1.0f &&
293 xy[0] + xy[1] <= 1.0f;
294 }
296 return xy.x > 0.0f && xy.x < 1.0f && xy.y > 0.0f && xy.y < 1.0f &&
297 xy.x + xy.y <= 1.0f;
298 }
299 static bool validPrimaries(const RgbPrimaries& primaries) FL_NO_EXCEPT {
300 return validChromaticity(primaries.red) && validChromaticity(primaries.green) &&
301 validChromaticity(primaries.blue) && validChromaticity(primaries.white);
302 }
303 static bool validResponseLut(const u16* values, u16 size) FL_NO_EXCEPT {
304 if (size == 0) return true;
305 if (values == nullptr || size < 2 || values[0] != 0 ||
306 values[size - 1] != 65535) return false;
307 for (u16 i = 1; i < size; ++i) {
308 if (values[i] < values[i - 1]) return false;
309 }
310 return true;
311 }
312 static bool finitePositive(float value) FL_NO_EXCEPT {
313 return value > 0.0f && value < 3.4028234e38f;
314 }
316#if FL_COLOR_PROFILE_RUNTIME
317 if (hasColorProfile() && !mWarnedProfileCleared) {
318 FL_WARN("Legacy correction/temperature clears color profile");
320 mWarnedProfileCleared = true;
321 }
322#endif
323 }
324
325public:
326
330 if (auto* p = mWhiteCfg.ptr<Rgbw>()) return *p;
331 return RgbwInvalid::value();
332 }
333
336 if (auto* p = mWhiteCfg.ptr<Rgbww>()) return *p;
337 return RgbwwInvalid::value();
338 }
339
344 bool isRgbw() const FL_NO_EXCEPT {
345 auto* p = mWhiteCfg.ptr<Rgbw>();
346 return p != nullptr && p->active();
347 }
348
350 bool isRgbww() const FL_NO_EXCEPT {
351 auto* p = mWhiteCfg.ptr<Rgbww>();
352 return p != nullptr && p->active();
353 }
354
355private:
356#if FL_COLOR_PROFILE_RUNTIME
357 bool mWarnedProfileCleared = false;
358 bool mWarnedLegacyCleared = false;
359#endif
360};
361
362} // namespace fl
unsigned int xy(unsigned int x, unsigned int y)
Portable compile-time identifiers for LED channel buses.
#define FL_UNUSED(x)
#define BINARY_DITHER
Enable dithering using binary dithering (only option)
Definition dither_mode.h:16
#define DISABLE_DITHER
Disable dithering.
Definition dither_mode.h:14
Declares dithering options and types.
@ UncorrectedTemperature
Uncorrected temperature (0xFFFFFF)
Definition color.h:90
@ UncorrectedColor
Uncorrected color (0xFFFFFF)
Definition color.h:24
Determines which platform system definitions to include.
#define FL_WARN(...)
Definition log.h:437
Centralized logging categories for FastLED hardware interfaces and subsystems.
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
Definition shared_ptr.h:423
unsigned char u8
Definition stdint.h:131
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
constexpr int type_rank< T >::value
void installColorPipelineHooks() FL_NO_EXCEPT
Point the hooks at the real implementations.
Optional< T > optional
Definition optional.h:16
void notifyColorProfileClearedByLegacy() FL_NO_EXCEPT
GamutPolicy
Bus
Portable public bus selector.
Definition bus.h:27
@ AUTO
Definition bus.h:28
Base definition for an LED controller.
Definition crgb.hpp:179
Payload shared by every color-profile notification.
Includes defintions for RGB and HSV pixels.
5-channel RGB + warm-W + cool-W (RGBWW / RGBCCT) configuration types (issue #2558,...
#define FL_STATIC_ASSERT(...)
Representation of an 8-bit RGB pixel (Red, Green, Blue)
Definition crgb.h:38
static ChannelEvents & instance() FL_NO_EXCEPT
fl::function_list< void(const ColorProfileEvent &)> onColorProfileWarning
static bool validPrimaries(const RgbPrimaries &primaries) FL_NO_EXCEPT
Definition options.h:299
bool setColorProfile(const EmitterProfile &profile) FL_NO_EXCEPT
Definition options.h:106
void clearColorProfile() FL_NO_EXCEPT
Definition options.h:229
static bool validChromaticity(Chromaticity xy) FL_NO_EXCEPT
Definition options.h:295
fl::optional< float > mGamma
Definition options.h:100
Rgbww rgbww() const FL_NO_EXCEPT
Definition options.h:335
bool isRgbw() const FL_NO_EXCEPT
True if this channel emits 4-channel RGBW.
Definition options.h:344
void warnProfileCleared() FL_NO_EXCEPT
Definition options.h:315
static bool validResponseLut(const u16 *values, u16 size) FL_NO_EXCEPT
Definition options.h:303
fl::variant< fl::Empty, Rgbw, Rgbww > mWhiteCfg
White-channel selection (variant): Empty = plain RGB, Rgbw = 4-channel, Rgbww = 5-channel.
Definition options.h:99
bool hasColorProfile() const FL_NO_EXCEPT
Definition options.h:242
fl::u8 mDitherMode
Definition options.h:95
void setLegacyTemperature(CRGB temperature) FL_NO_EXCEPT
Definition options.h:288
static ChannelOptions withColorProfile() FL_NO_EXCEPT
Definition options.h:275
const EmitterProfile * emitterProfile() const FL_NO_EXCEPT
Definition options.h:263
void setLegacyCorrection(CRGB correction) FL_NO_EXCEPT
Definition options.h:287
static bool validChromaticity(const float xy[2]) FL_NO_EXCEPT
Definition options.h:291
bool hasTargetWhite() const FL_NO_EXCEPT
bool setColorProfile(const EmitterProfile &profile, SourceProfile source, bool global_source, GamutPolicy gamut=GamutPolicy::ChromaCompress) FL_NO_EXCEPT
Definition options.h:124
void requestColorManagement(SourceProfile source=SourceProfile::linearSrgb()) FL_NO_EXCEPT
Definition options.h:220
Rgbw rgbw() const FL_NO_EXCEPT
Definition options.h:329
bool setTargetWhite(Chromaticity white) FL_NO_EXCEPT
FL_DEPRECATED("Ask the channel: Channel::isColorManaged()") bool isColorManaged() const FL_NO_EXCEPT
Definition options.h:261
static bool finitePositive(float value) FL_NO_EXCEPT
Definition options.h:312
bool setColorProfile(const EmitterProfile &profile, SourceProfile source, GamutPolicy gamut=GamutPolicy::ChromaCompress) FL_NO_EXCEPT
Definition options.h:114
Chromaticity targetWhite() const FL_NO_EXCEPT
bool isRgbww() const FL_NO_EXCEPT
True if this channel emits 5-channel RGBWW.
Definition options.h:350
Optional channel configuration parameters All fields have sensible defaults and can be overridden as ...
Definition options.h:92
CIE 1931 xy chromaticity, independent of encoded storage and chipset.
const EmitterProfile * profile() const FL_NO_EXCEPT
const EmitterProfile * mStaticProfile
fl::shared_ptr< const ColorProfileStorage > mStorage
bool active() const FL_NO_EXCEPT
Immutable profile binding carried by a channel.
FASTLED_FORCE_INLINE bool active() const FL_NO_EXCEPT
Definition rgbw.h:258
static Rgbw value() FL_NO_EXCEPT
Definition rgbw.h:279
FASTLED_FORCE_INLINE bool active() const FL_NO_EXCEPT
Definition rgbww.h:100
static Rgbww value() FL_NO_EXCEPT
Definition rgbww.h:111
static constexpr SourceProfile linearSrgb() FL_NO_EXCEPT
A source declaration, not a container or an output-device selection.