FastLED 3.10.6
Loading...
Searching...
No Matches
color_managed_source.cpp.hpp
Go to the documentation of this file.
1// ok no header - implementation for fl/channels/color_managed_source.h
2
5
6namespace fl {
7
8namespace {
9
10// One threshold per frame of the eight-frame cycle, in 1/256 of a code, in
11// bit-reversed order (0, 4, 2, 6, 1, 5, 3, 7) so that however many frames of
12// the eight carry the extra code, they are spread across the cycle rather than
13// bunched. Each sits in the middle of its 1/8 band, so a fraction f of a code
14// is rounded up on ceil((f - 16) / 32) of the eight frames: the cycle's mean
15// is within 1/16 of a code of the exact value. Eight frames is the cycle
16// BINARY_DITHER already runs, so this adds no new cadence (see
17// docs/color-ws2812-shaping-paths.md).
18constexpr u8 kTemporalDitherThresholds[8] = {16, 144, 80, 208, 48, 176, 112, 240};
19
20} // namespace
21
28
32
36
40 : ColorManagedPixelSource(controller, order, pipeline,
42 // No shared frame counter on TINY (the colour
43 // pipeline is compiled out there anyway).
44 0
45#else
47#endif
48 ) {}
49
51 u8* b2_out) FL_NO_EXCEPT {
52 const u8* raw = mController.mData;
53 i32 drives[3];
54 processPixelQ16(mPipeline, raw[0], raw[1], raw[2], drives);
55 u8 channels[3];
57 // The phase is the frame's, offset by the pixel's position, so
58 // any eight neighbours cover the whole cycle in every frame: a
59 // uniform strip's total light does not pulse. All three channels
60 // share it, so a neutral pixel is neutral in every frame.
61 const int position = mController.mLen - mController.mLenRemaining;
62 const u8 phase = static_cast<u8>((mDitherPhase + (position & 7)) & 7);
63 const u8 threshold = kTemporalDitherThresholds[phase];
64 for (int i = 0; i < 3; ++i) {
65 channels[i] = quantizeDithered(drives[i], threshold);
66 }
67 } else {
68 for (int i = 0; i < 3; ++i) {
69 channels[i] = quantize(drives[i]);
70 }
71 }
72 *b0_out = channels[mSlot0];
73 *b1_out = channels[mSlot1];
74 *b2_out = channels[mSlot2];
75}
76
77#if !FL_PLATFORM_HAS_TINY_MEMORY
78void ColorManagedPixelSource::loadAndScaleRGB16(u16* b0_out, u16* b1_out,
79 u16* b2_out) FL_NO_EXCEPT {
80 const u8* raw = mController.mData;
81 i32 drives[3];
82 processPixelQ16(mPipeline, raw[0], raw[1], raw[2], drives);
83 u16 channels[3];
84 for (int i = 0; i < 3; ++i) {
85 channels[i] = quantize16(drives[i]);
86 }
87 *b0_out = channels[mSlot0];
88 *b1_out = channels[mSlot1];
89 *b2_out = channels[mSlot2];
90}
91#endif
92
94 u8* b1_out, u8* b2_out,
95 u8* b3_out) FL_NO_EXCEPT {
96 if (!mPipeline.wide || mPipeline.wide->topology !=
98 mController.loadAndScaleRGBW(rgbw, b0_out, b1_out, b2_out, b3_out);
99 return;
100 }
101 const u8* raw = mController.mData;
102 i32 drives[5];
103 processPixelWideQ16(mPipeline, raw[0], raw[1], raw[2], drives);
104 u8 codes[4];
105 if (temporalDitherEnabled()) {
106 const int position = mController.mLen - mController.mLenRemaining;
107 const u8 phase = static_cast<u8>((mDitherPhase + (position & 7)) & 7);
108 const u8 threshold = kTemporalDitherThresholds[phase];
109 for (int i = 0; i < 4; ++i)
110 codes[i] = quantizeDithered(drives[i], threshold);
111 } else {
112 for (int i = 0; i < 4; ++i) codes[i] = quantize(drives[i]);
113 }
114 rgbw_partial_reorder(rgbw.w_placement, codes[mSlot0], codes[mSlot1],
115 codes[mSlot2], codes[3], b0_out, b1_out,
116 b2_out, b3_out);
117}
118
120 u8* b1_out, u8* b2_out,
121 u8* b3_out,
122 u8* b4_out) FL_NO_EXCEPT {
123 if (!mPipeline.wide || mPipeline.wide->topology !=
125 mController.loadAndScaleRGBWW(rgbww, b0_out, b1_out, b2_out,
126 b3_out, b4_out);
127 return;
128 }
129 const u8* raw = mController.mData;
130 i32 drives[5];
131 processPixelWideQ16(mPipeline, raw[0], raw[1], raw[2], drives);
132 u8 codes[5];
133 if (temporalDitherEnabled()) {
134 const int position = mController.mLen - mController.mLenRemaining;
135 const u8 phase = static_cast<u8>((mDitherPhase + (position & 7)) & 7);
136 const u8 threshold = kTemporalDitherThresholds[phase];
137 for (int i = 0; i < 5; ++i)
138 codes[i] = quantizeDithered(drives[i], threshold);
139 } else {
140 for (int i = 0; i < 5; ++i) codes[i] = quantize(drives[i]);
141 }
142 rgbww_partial_reorder(rgbww.w_placement, codes[mSlot0], codes[mSlot1],
143 codes[mSlot2], codes[3], codes[4], b0_out,
144 b1_out, b2_out, b3_out, b4_out);
145}
146
147#if FASTLED_HD_COLOR_MIXING
148// Guarded to match the declaration. Without this the definition survives on a
149// build with HD mixing off -- AVR, among others -- where the member does not
150// exist, and the compile fails there and nowhere else.
151void ColorManagedPixelSource::loadRGBScaleAndBrightness(
152 u8* c0, u8* c1, u8* c2, u8* brightness) FL_NO_EXCEPT {
153 // Full scale: the drives already carry brightness as C4's flux scalar, so
154 // the controller's brightness here would dim the strip a second time
155 // through the 5-bit field. B1's conservative treatment -- SK9822, and any
156 // chip without current-vs-chromaticity data -- holds the field fixed.
157 *c0 = 255;
158 *c1 = 255;
159 *c2 = 255;
160 *brightness = 255;
161}
162#endif
163
165 return mController.ditherActive();
166}
167
169 u8 threshold) FL_NO_EXCEPT {
170 if (drive <= 0) {
171 return 0;
172 }
173 if (drive >= 65536) {
174 return 255;
175 }
176 // drive * 255 < 2^24: the exact code in 16.16, split into its integer
177 // part and its whole 16-bit fraction. Compared at full width, so the
178 // cycle's mean is within 1/16 of a code exactly; truncating the
179 // fraction to eight bits first would add up to 1/256 on top.
180 const u32 exact = static_cast<u32>(drive) * 255u;
181 const u32 base = exact >> 16;
182 const u32 fraction = exact & 0xFFFFu;
183 const u32 code =
184 base + (fraction > (static_cast<u32>(threshold) << 8) ? 1u : 0u);
185 return static_cast<u8>(code > 255u ? 255u : code);
186}
187
189 if (drive <= 0) {
190 return 0;
191 }
192 if (drive >= 65536) {
193 return 255;
194 }
195 return static_cast<u8>((static_cast<i32>(drive) * 255 + 32768) >> 16);
196}
197
198#if !FL_PLATFORM_HAS_TINY_MEMORY
200 if (drive <= 0) {
201 return 0;
202 }
203 if (drive >= 65536) {
204 return 65535;
205 }
206 // 64-bit intermediate on purpose: drive reaches 65535 and 65535*65535
207 // leaves i32. The 8-bit form above multiplies by 255 and stays inside
208 // it, which is why it does not need this.
209 return static_cast<u16>(
210 (static_cast<i64>(drive) * 65535 + 32768) >> 16);
211}
212#endif
213} // namespace fl
fl::UISlider brightness("Brightness", BRIGHTNESS, 0, 255)
CLEDController * controller
Rgbw rgbw
void loadAndScaleRGB(u8 *b0_out, u8 *b1_out, u8 *b2_out) FL_NO_EXCEPT
The colour-managed path.
void loadAndScaleRGBW(const Rgbw &rgbw, u8 *b0_out, u8 *b1_out, u8 *b2_out, u8 *b3_out) FL_NO_EXCEPT
Physical RGBW solve when a matching profile is bound; otherwise the legacy RGB-to-RGBW conversion.
StreamingPipelineQ16 & mPipeline
bool temporalDitherEnabled() const FL_NO_EXCEPT
True when the channel asked for dither this frame.
ColorManagedPixelSource(PixelController< RGB > &controller, EOrder order, ColorPipelineFrameRef pipeline, u8 dither_phase) FL_NO_EXCEPT
dither_phase is where this frame sits in the temporal-dither cycle.
void setFlux(u8 brightness) FL_NO_EXCEPT
Frame-local brightness is applied to the source's owned pipeline on shared tiers; only managed iterat...
static u8 quantize(i32 drive) FL_NO_EXCEPT
s16.16 drive to an 8-bit code, rounding to nearest.
static u16 quantize16(i32 drive) FL_NO_EXCEPT
quantize onto the 16-bit wire.
void loadAndScaleRGB16(u16 *b0_out, u16 *b1_out, u16 *b2_out) FL_NO_EXCEPT
The same pixel, quantized once to 16 bits instead of to 8 (P8, #4042).
PixelController< RGB > & mController
void loadAndScaleRGBWW(Rgbww rgbww, u8 *b0_out, u8 *b1_out, u8 *b2_out, u8 *b3_out, u8 *b4_out) FL_NO_EXCEPT
Physical two-white solve when a matching RGBWW profile is bound; otherwise the legacy conversion.
static u8 quantizeDithered(i32 drive, u8 threshold) FL_NO_EXCEPT
quantize with the pipeline's temporal dither (P8, C5): floor(x) or floor(x) + 1 of the exact code x,...
static FluxScalar fromBrightness(u8 brightness) FL_NO_EXCEPT
setBrightness(b) as the linear flux scalar b/255.
A linear flux scalar in s16.16.
Definition flux_scalar.h:21
Shared temporal-dither phase tracking.
u8 ditherFrame() FL_NO_EXCEPT
Return the temporal-dither phase shared by all controllers in this frame.
unsigned char u8
Definition stdint.h:131
void rgbww_partial_reorder(EOrderWW ww_placement, u8 b0, u8 b1, u8 b2, u8 ww, u8 wc, u8 *out_b0, u8 *out_b1, u8 *out_b2, u8 *out_b3, u8 *out_b4) FL_NO_EXCEPT
Dispatch RGB->RGBWW for a given mode.
void rgbw_partial_reorder(EOrderW w_placement, u8 b0, u8 b1, u8 b2, u8 w, u8 *out_b0, u8 *out_b1, u8 *out_b2, u8 *out_b3)
Definition rgbw.cpp.hpp:678
void setPipelineFluxQ16(StreamingPipelineQ16 *pipeline, FluxScalar flux) FL_NO_EXCEPT
Set the composed brightness-and-power scalar for the frames that follow.
InputGamut g FL_NO_EXCEPT
Definition rgbw.h:121
EOrder
RGB color channel orderings, used when instantiating controllers to determine what order the controll...
Definition eorder.h:13
void processPixelWideQ16(const StreamingPipelineQ16 &pipeline, u8 r, u8 g, u8 b, i32(&drives)[5]) FL_NO_EXCEPT
Like processPixelQ16, but solves the physical 4/5-emitter hull.
StreamingPipelineQ16 & ColorPipelineFrameRef
void processPixelQ16(const StreamingPipelineQ16 &pipeline, u8 r, u8 g, u8 b, i32(&drives)[3]) FL_NO_EXCEPT
One pixel: an RGB8 code triple to three physical emitter drives in s16.16.
Base definition for an LED controller.
Definition crgb.hpp:179
#define RGB_BYTE2(RO)
Gets the color channel for byte 2.
#define RGB_BYTE1(RO)
Gets the color channel for byte 1.
#define RGB_BYTE0(RO)
Gets the color channel for byte 0.
#define FL_PLATFORM_HAS_TINY_MEMORY
fl::i64 i64
Definition stdint.h:221
Pixel controller class.
Per-strip RGBWW configuration.
Definition rgbww.h:60