GCC Code Coverage Report


Directory: ./
File: overlays/wled/lib8tion/scale8.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 21 21 100.0%
Functions: 3 3 100.0%
Branches: 12 36 33.3%

Line Branch Exec Source
1 #pragma once
2
3 #include "lib8static.h"
4 #include "../crgb.h"
5 #include "../fl/namespace.h"
6
7 FASTLED_NAMESPACE_BEGIN
8
9 /// @file scale8.h
10 /// Fast, efficient 8-bit scaling functions specifically
11 /// designed for high-performance LED programming.
12
13 /// @addtogroup lib8tion
14 /// @{
15
16 /// @defgroup Scaling Scaling Functions
17 /// Fast, efficient 8-bit scaling functions specifically
18 /// designed for high-performance LED programming.
19 ///
20 /// Because of the AVR(Arduino) and ARM assembly language
21 /// implementations provided, using these functions often
22 /// results in smaller and faster code than the equivalent
23 /// program using plain "C" arithmetic and logic.
24 /// @{
25
26 /// Scale one byte by a second one, which is treated as
27 /// the numerator of a fraction whose denominator is 256.
28 ///
29 /// In other words, it computes i * (scale / 256)
30 /// @param i input value to scale
31 /// @param scale scale factor, in n/256 units
32 /// @returns scaled value
33 /// @note Takes 4 clocks on AVR with MUL, 2 clocks on ARM
34 LIB8STATIC_ALWAYS_INLINE uint8_t scale8(uint8_t i, fract8 scale) {
35 #if SCALE8_C == 1
36 #if (FASTLED_SCALE8_FIXED == 1)
37 return (((uint16_t)i) * (1 + (uint16_t)(scale))) >> 8;
38 #else
39 745200 return ((uint16_t)i * (uint16_t)(scale)) >> 8;
40 #endif
41 #elif SCALE8_AVRASM == 1
42 #if defined(LIB8_ATTINY)
43 #if (FASTLED_SCALE8_FIXED == 1)
44 uint8_t work = i;
45 #else
46 uint8_t work = 0;
47 #endif
48 uint8_t cnt = 0x80;
49 asm volatile(
50 #if (FASTLED_SCALE8_FIXED == 1)
51 " inc %[scale] \n\t"
52 " breq DONE_%= \n\t"
53 " clr %[work] \n\t"
54 #endif
55 "LOOP_%=: \n\t"
56 /*" sbrc %[scale], 0 \n\t"
57 " add %[work], %[i] \n\t"
58 " ror %[work] \n\t"
59 " lsr %[scale] \n\t"
60 " clc \n\t"*/
61 " sbrc %[scale], 0 \n\t"
62 " add %[work], %[i] \n\t"
63 " ror %[work] \n\t"
64 " lsr %[scale] \n\t"
65 " lsr %[cnt] \n\t"
66 "brcc LOOP_%= \n\t"
67 "DONE_%=: \n\t"
68 : [work] "+r"(work), [cnt] "+r"(cnt)
69 : [scale] "r"(scale), [i] "r"(i)
70 :);
71 return work;
72 #else
73 asm volatile(
74 #if (FASTLED_SCALE8_FIXED == 1)
75 // Multiply 8-bit i * 8-bit scale, giving 16-bit r1,r0
76 "mul %0, %1 \n\t"
77 // Add i to r0, possibly setting the carry flag
78 "add r0, %0 \n\t"
79 // load the immediate 0 into i (note, this does _not_ touch any flags)
80 "ldi %0, 0x00 \n\t"
81 // walk and chew gum at the same time
82 "adc %0, r1 \n\t"
83 #else
84 /* Multiply 8-bit i * 8-bit scale, giving 16-bit r1,r0 */
85 "mul %0, %1 \n\t"
86 /* Move the high 8-bits of the product (r1) back to i */
87 "mov %0, r1 \n\t"
88 /* Restore r1 to "0"; it's expected to always be that */
89 #endif
90 "clr __zero_reg__ \n\t"
91
92 : "+d"(i) /* writes to i; r16-r31, restricted by ldi */
93 : "r"(scale) /* uses scale */
94 : "r0", "r1" /* clobbers r0, r1 */
95 );
96 /* Return the result */
97 return i;
98 #endif
99 #else
100 #error "No implementation for scale8 available."
101 #endif
102 }
103
104 constexpr uint8_t scale8_constexpr(uint8_t i, fract8 scale) {
105 return (((uint16_t)i) * (1 + (uint16_t)(scale))) >> 8;
106 }
107
108 /// The "video" version of scale8() guarantees that the output will
109 /// be only be zero if one or both of the inputs are zero.
110 /// If both inputs are non-zero, the output is guaranteed to be non-zero.
111 /// This makes for better "video"/LED dimming, at the cost of
112 /// several additional cycles.
113 /// @param i input value to scale
114 /// @param scale scale factor, in n/256 units
115 /// @returns scaled value
116 /// @see scale8()
117 LIB8STATIC_ALWAYS_INLINE uint8_t scale8_video(uint8_t i, fract8 scale) {
118 #if SCALE8_C == 1 || defined(LIB8_ATTINY)
119
5/16
✓ Branch 0 taken 94181 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 94181 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 1782 times.
✓ Branch 13 taken 4979 times.
✓ Branch 14 taken 1782 times.
✗ Branch 15 not taken.
100942 uint8_t j = (((int)i * (int)scale) >> 8) + ((i && scale) ? 1 : 0);
120 // uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
121 // uint8_t j = (i == 0) ? 0 : (((int)i * (int)(scale) ) >> 8) +
122 // nonzeroscale;
123 100942 return j;
124 #elif SCALE8_AVRASM == 1
125 uint8_t j = 0;
126 asm volatile(" tst %[i]\n\t"
127 " breq L_%=\n\t"
128 " mul %[i], %[scale]\n\t"
129 " mov %[j], r1\n\t"
130 " clr __zero_reg__\n\t"
131 " cpse %[scale], r1\n\t"
132 " subi %[j], 0xFF\n\t"
133 "L_%=: \n\t"
134 : [j] "+d"(j) // r16-r31, restricted by subi
135 : [i] "r"(i), [scale] "r"(scale)
136 : "r0", "r1");
137 return j;
138 // uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
139 // asm volatile(
140 // " tst %0 \n"
141 // " breq L_%= \n"
142 // " mul %0, %1 \n"
143 // " mov %0, r1 \n"
144 // " add %0, %2 \n"
145 // " clr __zero_reg__ \n"
146 // "L_%=: \n"
147 // : "+a" (i)
148 // : "a" (scale), "a" (nonzeroscale)
149 // : "r0", "r1");
150 // // Return the result
151 // return i;
152 #else
153 #error "No implementation for scale8_video available."
154 #endif
155 }
156
157 /// @defgroup ScalingDirty Scaling Functions that Leave R1 Dirty
158 /// These functions are more efficient for scaling multiple
159 /// bytes at once, but require calling cleanup_R1() afterwards.
160 /// @{
161
162 /// This version of scale8() does not clean up the R1 register on AVR.
163 /// If you are doing several "scale8()'s" in a row, use this, and
164 /// then explicitly call cleanup_R1().
165 /// @warning You **MUST** call cleanup_R1() after using this function!
166 /// @param i input value to scale
167 /// @param scale scale factor, in n/256 units
168 /// @returns scaled value
169 /// @see scale8()
170 LIB8STATIC_ALWAYS_INLINE uint8_t scale8_LEAVING_R1_DIRTY(uint8_t i,
171 fract8 scale) {
172 #if SCALE8_C == 1
173 #if (FASTLED_SCALE8_FIXED == 1)
174 return (((uint16_t)i) * ((uint16_t)(scale) + 1)) >> 8;
175 #else
176 160409 return ((int)i * (int)(scale)) >> 8;
177 #endif
178 #elif SCALE8_AVRASM == 1
179 asm volatile(
180 #if (FASTLED_SCALE8_FIXED == 1)
181 // Multiply 8-bit i * 8-bit scale, giving 16-bit r1,r0
182 "mul %0, %1 \n\t"
183 // Add i to r0, possibly setting the carry flag
184 "add r0, %0 \n\t"
185 // load the immediate 0 into i (note, this does _not_ touch any flags)
186 "ldi %0, 0x00 \n\t"
187 // walk and chew gum at the same time
188 "adc %0, r1 \n\t"
189 #else
190 /* Multiply 8-bit i * 8-bit scale, giving 16-bit r1,r0 */
191 "mul %0, %1 \n\t"
192 /* Move the high 8-bits of the product (r1) back to i */
193 "mov %0, r1 \n\t"
194 #endif
195 /* R1 IS LEFT DIRTY HERE; YOU MUST ZERO IT OUT YOURSELF */
196 /* "clr __zero_reg__ \n\t" */
197 : "+d"(i) /* writes to i; r16-r31, restricted by ldi */
198 : "r"(scale) /* uses scale */
199 : "r0", "r1" /* clobbers r0, r1 */
200 );
201 // Return the result
202 return i;
203 #else
204 #error "No implementation for scale8_LEAVING_R1_DIRTY available."
205 #endif
206 }
207
208 /// In place modifying version of scale8() that does not clean up the R1
209 /// register on AVR. If you are doing several "scale8()'s" in a row, use this,
210 /// and then explicitly call cleanup_R1().
211 /// @warning You **MUST** call cleanup_R1() after using this function!
212 /// @par
213 /// @warning This function always modifies its arguments in place!
214 /// @param i input value to scale
215 /// @param scale scale factor, in n/256 units
216 /// @see scale8()
217 LIB8STATIC_ALWAYS_INLINE void nscale8_LEAVING_R1_DIRTY(uint8_t &i,
218 fract8 scale) {
219 #if SCALE8_C == 1
220 #if (FASTLED_SCALE8_FIXED == 1)
221 i = (((uint16_t)i) * ((uint16_t)(scale) + 1)) >> 8;
222 #else
223 i = ((int)i * (int)(scale)) >> 8;
224 #endif
225 #elif SCALE8_AVRASM == 1
226 asm volatile(
227 #if (FASTLED_SCALE8_FIXED == 1)
228 // Multiply 8-bit i * 8-bit scale, giving 16-bit r1,r0
229 "mul %0, %1 \n\t"
230 // Add i to r0, possibly setting the carry flag
231 "add r0, %0 \n\t"
232 // load the immediate 0 into i (note, this does _not_ touch any flags)
233 "ldi %0, 0x00 \n\t"
234 // walk and chew gum at the same time
235 "adc %0, r1 \n\t"
236 #else
237 /* Multiply 8-bit i * 8-bit scale, giving 16-bit r1,r0 */
238 "mul %0, %1 \n\t"
239 /* Move the high 8-bits of the product (r1) back to i */
240 "mov %0, r1 \n\t"
241 #endif
242 /* R1 IS LEFT DIRTY HERE; YOU MUST ZERO IT OUT YOURSELF */
243 /* "clr __zero_reg__ \n\t" */
244
245 : "+d"(i) /* writes to i; r16-r31, restricted by ldi */
246 : "r"(scale) /* uses scale */
247 : "r0", "r1" /* clobbers r0, r1 */
248 );
249 #else
250 #error "No implementation for nscale8_LEAVING_R1_DIRTY available."
251 #endif
252 }
253
254 /// This version of scale8_video() does not clean up the R1 register on AVR.
255 /// If you are doing several "scale8_video()'s" in a row, use this, and
256 /// then explicitly call cleanup_R1().
257 /// @warning You **MUST** call cleanup_R1() after using this function!
258 /// @param i input value to scale
259 /// @param scale scale factor, in n/256 units
260 /// @returns scaled value
261 /// @see scale8_video()
262 LIB8STATIC_ALWAYS_INLINE uint8_t scale8_video_LEAVING_R1_DIRTY(uint8_t i,
263 fract8 scale) {
264 #if SCALE8_C == 1 || defined(LIB8_ATTINY)
265
3/12
✓ Branch 0 taken 104632 times.
✓ Branch 1 taken 2875 times.
✓ Branch 2 taken 104632 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
107507 uint8_t j = (((int)i * (int)scale) >> 8) + ((i && scale) ? 1 : 0);
266 // uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
267 // uint8_t j = (i == 0) ? 0 : (((int)i * (int)(scale) ) >> 8) +
268 // nonzeroscale;
269 107507 return j;
270 #elif SCALE8_AVRASM == 1
271 uint8_t j = 0;
272 asm volatile(" tst %[i]\n\t"
273 " breq L_%=\n\t"
274 " mul %[i], %[scale]\n\t"
275 " mov %[j], r1\n\t"
276 " breq L_%=\n\t"
277 " subi %[j], 0xFF\n\t"
278 "L_%=: \n\t"
279 : [j] "+d"(j) // r16-r31, restricted by subi
280 : [i] "r"(i), [scale] "r"(scale)
281 : "r0", "r1");
282 return j;
283 // uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
284 // asm volatile(
285 // " tst %0 \n"
286 // " breq L_%= \n"
287 // " mul %0, %1 \n"
288 // " mov %0, r1 \n"
289 // " add %0, %2 \n"
290 // " clr __zero_reg__ \n"
291 // "L_%=: \n"
292 // : "+a" (i)
293 // : "a" (scale), "a" (nonzeroscale)
294 // : "r0", "r1");
295 // // Return the result
296 // return i;
297 #else
298 #error "No implementation for scale8_video_LEAVING_R1_DIRTY available."
299 #endif
300 }
301
302 /// In place modifying version of scale8_video() that does not clean up the R1
303 /// register on AVR. If you are doing several "scale8_video()'s" in a row, use
304 /// this, and then explicitly call cleanup_R1().
305 /// @warning You **MUST** call cleanup_R1() after using this function!
306 /// @par
307 /// @warning This function always modifies its arguments in place!
308 /// @param i input value to scale
309 /// @param scale scale factor, in n/256 units
310 /// @see scale8_video()
311 LIB8STATIC_ALWAYS_INLINE void nscale8_video_LEAVING_R1_DIRTY(uint8_t &i,
312 fract8 scale) {
313 #if SCALE8_C == 1 || defined(LIB8_ATTINY)
314 i = (((int)i * (int)scale) >> 8) + ((i && scale) ? 1 : 0);
315 #elif SCALE8_AVRASM == 1
316 asm volatile(" tst %[i]\n\t"
317 " breq L_%=\n\t"
318 " mul %[i], %[scale]\n\t"
319 " mov %[i], r1\n\t"
320 " breq L_%=\n\t"
321 " subi %[i], 0xFF\n\t"
322 "L_%=: \n\t"
323 : [i] "+d"(i) // r16-r31, restricted by subi
324 : [scale] "r"(scale)
325 : "r0", "r1");
326 #else
327 #error "No implementation for scale8_video_LEAVING_R1_DIRTY available."
328 #endif
329 }
330
331 /// Clean up the r1 register after a series of *LEAVING_R1_DIRTY calls
332 /// @ingroup ScalingDirty
333 LIB8STATIC_ALWAYS_INLINE void cleanup_R1() {
334 #if CLEANUP_R1_AVRASM == 1
335 // Restore r1 to "0"; it's expected to always be that
336 asm volatile("clr __zero_reg__ \n\t" : : : "r1");
337 #endif
338 22625 }
339
340 constexpr CRGB nscale8x3_constexpr(uint8_t r, uint8_t g, uint8_t b, fract8 scale) {
341 return CRGB(((int)r * (int)(scale)) >> 8, ((int)g * (int)(scale)) >> 8,
342 ((int)b * (int)(scale)) >> 8);
343 }
344
345 /// @} ScalingDirty
346
347 /// Scale three one-byte values by a fourth one, which is treated as
348 /// the numerator of a fraction whose demominator is 256.
349 ///
350 /// In other words, it computes r,g,b * (scale / 256)
351 ///
352 /// @warning This function always modifies its arguments in place!
353 /// @param r first value to scale
354 /// @param g second value to scale
355 /// @param b third value to scale
356 /// @param scale scale factor, in n/256 units
357 27063 LIB8STATIC void nscale8x3(uint8_t &r, uint8_t &g, uint8_t &b, fract8 scale) {
358 #if SCALE8_C == 1
359 #if (FASTLED_SCALE8_FIXED == 1)
360 uint16_t scale_fixed = scale + 1;
361 r = (((uint16_t)r) * scale_fixed) >> 8;
362 g = (((uint16_t)g) * scale_fixed) >> 8;
363 b = (((uint16_t)b) * scale_fixed) >> 8;
364 #else
365 27063 r = ((int)r * (int)(scale)) >> 8;
366 27063 g = ((int)g * (int)(scale)) >> 8;
367 27063 b = ((int)b * (int)(scale)) >> 8;
368 #endif
369 #elif SCALE8_AVRASM == 1
370 r = scale8_LEAVING_R1_DIRTY(r, scale);
371 g = scale8_LEAVING_R1_DIRTY(g, scale);
372 b = scale8_LEAVING_R1_DIRTY(b, scale);
373 cleanup_R1();
374 #else
375 #error "No implementation for nscale8x3 available."
376 #endif
377 27063 }
378
379 /// Scale three one-byte values by a fourth one, which is treated as
380 /// the numerator of a fraction whose demominator is 256.
381 ///
382 /// In other words, it computes r,g,b * (scale / 256), ensuring
383 /// that non-zero values passed in remain non-zero, no matter how low the scale
384 /// argument.
385 ///
386 /// @warning This function always modifies its arguments in place!
387 /// @param r first value to scale
388 /// @param g second value to scale
389 /// @param b third value to scale
390 /// @param scale scale factor, in n/256 units
391 46 LIB8STATIC void nscale8x3_video(uint8_t &r, uint8_t &g, uint8_t &b,
392 fract8 scale) {
393 #if SCALE8_C == 1
394
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
395
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 r = (r == 0) ? 0 : (((int)r * (int)(scale)) >> 8) + nonzeroscale;
396
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 g = (g == 0) ? 0 : (((int)g * (int)(scale)) >> 8) + nonzeroscale;
397
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 b = (b == 0) ? 0 : (((int)b * (int)(scale)) >> 8) + nonzeroscale;
398 #elif SCALE8_AVRASM == 1
399 nscale8_video_LEAVING_R1_DIRTY(r, scale);
400 nscale8_video_LEAVING_R1_DIRTY(g, scale);
401 nscale8_video_LEAVING_R1_DIRTY(b, scale);
402 cleanup_R1();
403 #else
404 #error "No implementation for nscale8x3 available."
405 #endif
406 46 }
407
408 /// Scale two one-byte values by a third one, which is treated as
409 /// the numerator of a fraction whose demominator is 256.
410 ///
411 /// In other words, it computes i,j * (scale / 256).
412 ///
413 /// @warning This function always modifies its arguments in place!
414 /// @param i first value to scale
415 /// @param j second value to scale
416 /// @param scale scale factor, in n/256 units
417 LIB8STATIC void nscale8x2(uint8_t &i, uint8_t &j, fract8 scale) {
418 #if SCALE8_C == 1
419 #if FASTLED_SCALE8_FIXED == 1
420 uint16_t scale_fixed = scale + 1;
421 i = (((uint16_t)i) * scale_fixed) >> 8;
422 j = (((uint16_t)j) * scale_fixed) >> 8;
423 #else
424 i = ((uint16_t)i * (uint16_t)(scale)) >> 8;
425 j = ((uint16_t)j * (uint16_t)(scale)) >> 8;
426 #endif
427 #elif SCALE8_AVRASM == 1
428 i = scale8_LEAVING_R1_DIRTY(i, scale);
429 j = scale8_LEAVING_R1_DIRTY(j, scale);
430 cleanup_R1();
431 #else
432 #error "No implementation for nscale8x2 available."
433 #endif
434 }
435
436 /// Scale two one-byte values by a third one, which is treated as
437 /// the numerator of a fraction whose demominator is 256.
438 ///
439 /// In other words, it computes i,j * (scale / 256), ensuring
440 /// that non-zero values passed in remain non zero, no matter how low the scale
441 /// argument.
442 ///
443 /// @warning This function always modifies its arguments in place!
444 /// @param i first value to scale
445 /// @param j second value to scale
446 /// @param scale scale factor, in n/256 units
447 LIB8STATIC void nscale8x2_video(uint8_t &i, uint8_t &j, fract8 scale) {
448 #if SCALE8_C == 1
449 uint8_t nonzeroscale = (scale != 0) ? 1 : 0;
450 i = (i == 0) ? 0 : (((int)i * (int)(scale)) >> 8) + nonzeroscale;
451 j = (j == 0) ? 0 : (((int)j * (int)(scale)) >> 8) + nonzeroscale;
452 #elif SCALE8_AVRASM == 1
453 nscale8_video_LEAVING_R1_DIRTY(i, scale);
454 nscale8_video_LEAVING_R1_DIRTY(j, scale);
455 cleanup_R1();
456 #else
457 #error "No implementation for nscale8x2 available."
458 #endif
459 }
460
461 /// Scale a 16-bit unsigned value by an 8-bit value, which is treated
462 /// as the numerator of a fraction whose denominator is 256.
463 ///
464 /// In other words, it computes i * (scale / 256)
465 /// @param i input value to scale
466 /// @param scale scale factor, in n/256 units
467 /// @returns scaled value
468 LIB8STATIC_ALWAYS_INLINE uint16_t scale16by8(uint16_t i, fract8 scale) {
469 if (scale == 0) {
470 return 0; // Fixes non zero output when scale == 0 and
471 // FASTLED_SCALE8_FIXED==1
472 }
473 #if SCALE16BY8_C == 1
474 uint16_t result;
475 #if FASTLED_SCALE8_FIXED == 1
476 result = (((uint32_t)(i) * (1 + ((uint32_t)scale))) >> 8);
477 #else
478 result = (i * scale) / 256;
479 #endif
480 return result;
481 #elif SCALE16BY8_AVRASM == 1
482 #if FASTLED_SCALE8_FIXED == 1
483 uint16_t result = 0;
484 asm volatile(
485 // result.A = HighByte( (i.A x scale) + i.A )
486 " mul %A[i], %[scale] \n\t"
487 " add r0, %A[i] \n\t"
488 // " adc r1, [zero] \n\t"
489 // " mov %A[result], r1 \n\t"
490 " adc %A[result], r1 \n\t"
491
492 // result.A-B += i.B x scale
493 " mul %B[i], %[scale] \n\t"
494 " add %A[result], r0 \n\t"
495 " adc %B[result], r1 \n\t"
496
497 // cleanup r1
498 " clr __zero_reg__ \n\t"
499
500 // result.A-B += i.B
501 " add %A[result], %B[i] \n\t"
502 " adc %B[result], __zero_reg__ \n\t"
503
504 : [result] "+r"(result)
505 : [i] "r"(i), [scale] "r"(scale)
506 : "r0", "r1");
507 return result;
508 #else
509 uint16_t result = 0;
510 asm volatile(
511 // result.A = HighByte(i.A x j )
512 " mul %A[i], %[scale] \n\t"
513 " mov %A[result], r1 \n\t"
514 //" clr %B[result] \n\t"
515
516 // result.A-B += i.B x j
517 " mul %B[i], %[scale] \n\t"
518 " add %A[result], r0 \n\t"
519 " adc %B[result], r1 \n\t"
520
521 // cleanup r1
522 " clr __zero_reg__ \n\t"
523
524 : [result] "+r"(result)
525 : [i] "r"(i), [scale] "r"(scale)
526 : "r0", "r1");
527 return result;
528 #endif
529 #else
530 #error "No implementation for scale16by8 available."
531 #endif
532 }
533
534 /// Scale a 16-bit unsigned value by an 16-bit value, which is treated
535 /// as the numerator of a fraction whose denominator is 65536.
536 /// In other words, it computes i * (scale / 65536)
537 /// @param i input value to scale
538 /// @param scale scale factor, in n/65536 units
539 /// @returns scaled value
540 82662 LIB8STATIC uint16_t scale16(uint16_t i, fract16 scale) {
541 #if SCALE16_C == 1
542 uint16_t result;
543 #if FASTLED_SCALE8_FIXED == 1
544 result = ((uint32_t)(i) * (1 + (uint32_t)(scale))) / 65536;
545 #else
546 82662 result = ((uint32_t)(i) * (uint32_t)(scale)) / 65536;
547 #endif
548 82662 return result;
549 #elif SCALE16_AVRASM == 1
550 #if FASTLED_SCALE8_FIXED == 1
551 // implemented sort of like
552 // result = ((i * scale) + i ) / 65536
553 //
554 // why not like this, you may ask?
555 // result = (i * (scale+1)) / 65536
556 // the answer is that if scale is 65535, then scale+1
557 // will be zero, which is not what we want.
558 uint32_t result;
559 asm volatile(
560 // result.A-B = i.A x scale.A
561 " mul %A[i], %A[scale] \n\t"
562 // save results...
563 // basic idea:
564 //" mov %A[result], r0 \n\t"
565 //" mov %B[result], r1 \n\t"
566 // which can be written as...
567 " movw %A[result], r0 \n\t"
568 // Because we're going to add i.A-B to
569 // result.A-D, we DO need to keep both
570 // the r0 and r1 portions of the product
571 // UNlike in the 'unfixed scale8' version.
572 // So the movw here is needed.
573 : [result] "=r"(result)
574 : [i] "r"(i), [scale] "r"(scale)
575 : "r0", "r1");
576
577 asm volatile(
578 // result.C-D = i.B x scale.B
579 " mul %B[i], %B[scale] \n\t"
580 //" mov %C[result], r0 \n\t"
581 //" mov %D[result], r1 \n\t"
582 " movw %C[result], r0 \n\t"
583 : [result] "+r"(result)
584 : [i] "r"(i), [scale] "r"(scale)
585 : "r0", "r1");
586
587 const uint8_t zero = 0;
588 asm volatile(
589 // result.B-D += i.B x scale.A
590 " mul %B[i], %A[scale] \n\t"
591
592 " add %B[result], r0 \n\t"
593 " adc %C[result], r1 \n\t"
594 " adc %D[result], %[zero] \n\t"
595
596 // result.B-D += i.A x scale.B
597 " mul %A[i], %B[scale] \n\t"
598
599 " add %B[result], r0 \n\t"
600 " adc %C[result], r1 \n\t"
601 " adc %D[result], %[zero] \n\t"
602
603 // cleanup r1
604 " clr r1 \n\t"
605
606 : [result] "+r"(result)
607 : [i] "r"(i), [scale] "r"(scale), [zero] "r"(zero)
608 : "r0", "r1");
609
610 asm volatile(
611 // result.A-D += i.A-B
612 " add %A[result], %A[i] \n\t"
613 " adc %B[result], %B[i] \n\t"
614 " adc %C[result], %[zero] \n\t"
615 " adc %D[result], %[zero] \n\t"
616 : [result] "+r"(result)
617 : [i] "r"(i), [zero] "r"(zero));
618
619 result = result >> 16;
620 return result;
621 #else
622 uint32_t result;
623 asm volatile(
624 // result.A-B = i.A x scale.A
625 " mul %A[i], %A[scale] \n\t"
626 // save results...
627 // basic idea:
628 //" mov %A[result], r0 \n\t"
629 //" mov %B[result], r1 \n\t"
630 // which can be written as...
631 " movw %A[result], r0 \n\t"
632 // We actually don't need to do anything with r0,
633 // as result.A is never used again here, so we
634 // could just move the high byte, but movw is
635 // one clock cycle, just like mov, so might as
636 // well, in case we want to use this code for
637 // a generic 16x16 multiply somewhere.
638
639 : [result] "=r"(result)
640 : [i] "r"(i), [scale] "r"(scale)
641 : "r0", "r1");
642
643 asm volatile(
644 // result.C-D = i.B x scale.B
645 " mul %B[i], %B[scale] \n\t"
646 //" mov %C[result], r0 \n\t"
647 //" mov %D[result], r1 \n\t"
648 " movw %C[result], r0 \n\t"
649 : [result] "+r"(result)
650 : [i] "r"(i), [scale] "r"(scale)
651 : "r0", "r1");
652
653 const uint8_t zero = 0;
654 asm volatile(
655 // result.B-D += i.B x scale.A
656 " mul %B[i], %A[scale] \n\t"
657
658 " add %B[result], r0 \n\t"
659 " adc %C[result], r1 \n\t"
660 " adc %D[result], %[zero] \n\t"
661
662 // result.B-D += i.A x scale.B
663 " mul %A[i], %B[scale] \n\t"
664
665 " add %B[result], r0 \n\t"
666 " adc %C[result], r1 \n\t"
667 " adc %D[result], %[zero] \n\t"
668
669 // cleanup r1
670 " clr r1 \n\t"
671
672 : [result] "+r"(result)
673 : [i] "r"(i), [scale] "r"(scale), [zero] "r"(zero)
674 : "r0", "r1");
675
676 result = result >> 16;
677 return result;
678 #endif
679 #else
680 #error "No implementation for scale16 available."
681 #endif
682 }
683 /// @} Scaling
684
685 /// @defgroup Dimming Dimming and Brightening Functions
686 /// Functions to dim or brighten data.
687 ///
688 /// The eye does not respond in a linear way to light.
689 /// High speed PWM'd LEDs at 50% duty cycle appear far
690 /// brighter then the "half as bright" you might expect.
691 ///
692 /// If you want your midpoint brightness LEDs (128) to
693 /// appear half as bright as "full" brightness (255), you
694 /// have to apply a "dimming function".
695 ///
696 /// @note These are approximations of gamma correction with
697 /// a gamma value of 2.0.
698 /// @see @ref GammaFuncs
699 /// @{
700
701 /// Adjust a scaling value for dimming.
702 /// @see scale8()
703 LIB8STATIC uint8_t dim8_raw(uint8_t x) { return scale8(x, x); }
704
705 /// Adjust a scaling value for dimming for video (value will never go below 1)
706 /// @see scale8_video()
707 LIB8STATIC uint8_t dim8_video(uint8_t x) { return scale8_video(x, x); }
708
709 /// Linear version of the dimming function that halves for values < 128
710 LIB8STATIC uint8_t dim8_lin(uint8_t x) {
711 if (x & 0x80) {
712 x = scale8(x, x);
713 } else {
714 x += 1;
715 x /= 2;
716 }
717 return x;
718 }
719
720 /// Brighten a value (inverse of dim8_raw())
721 LIB8STATIC uint8_t brighten8_raw(uint8_t x) {
722 uint8_t ix = 255 - x;
723 return 255 - scale8(ix, ix);
724 }
725
726 /// Brighten a value (inverse of dim8_video())
727 LIB8STATIC uint8_t brighten8_video(uint8_t x) {
728 uint8_t ix = 255 - x;
729 return 255 - scale8_video(ix, ix);
730 }
731
732 /// Brighten a value (inverse of dim8_lin())
733 LIB8STATIC uint8_t brighten8_lin(uint8_t x) {
734 uint8_t ix = 255 - x;
735 if (ix & 0x80) {
736 ix = scale8(ix, ix);
737 } else {
738 ix += 1;
739 ix /= 2;
740 }
741 return 255 - ix;
742 }
743
744 /// @} Dimming
745 /// @} lib8tion
746
747 FASTLED_NAMESPACE_END
748