FastLED 3.10.6
Loading...
Searching...
No Matches
binary_dither.h File Reference

Detailed Description

Temporal binary dithering, as one self-contained, testable unit (#4672).


TEMPORAL DITHERING OVERVIEW

Temporal dithering recovers fractional brightness precision lost to integer quantization by varying pixel values across frames. At refresh rates above ~50Hz, human vision integrates these variations, perceiving the true fractional brightness.

THE PROBLEM: Integer scaling causes color shifts at low brightness. For example: CRGB(100, 60, 20) at 20% brightness -> RGB(19, 11, 3) Each channel loses different fractional precision, distorting the color.

THE SOLUTION: Add frame-varying noise BEFORE scaling, causing different rounding outcomes: Frame 1: scale8(100+0, 51) = 19 Frame 2: scale8(100+3, 51) = 20 <- noise pushed over threshold Your eye averages these to perceive the correct fractional brightness.

THE ALGORITHM:

  1. Frame counter R cycles 0-7, creating an 8-frame pattern
  2. Bit-reverse R to Q (0->0, 1->128, 2->64...) to distribute pattern temporally
  3. Center pattern: Q += 16
  4. Scale per channel: e[i] = 256/brightness, d[i] = scale8(Q, e[i]) Lower brightness needs BIGGER dither to compensate for larger % error
  5. Toggle between pixels: d[i] = e[i] - d[i] (spatial distribution)
  6. Apply: pixel = scale8(qadd8(pixel, d[i]), brightness)

VIRTUAL BITS: 8-frame cycle at 400Hz = 50Hz complete cycle -> +3 "virtual" bits Result: 8-bit hardware provides 11-bit perceived precision (0-2047 levels)

DISABLE FOR:

  • Cameras/photography (captures individual frames, sees flicker)
  • Slow refresh <50Hz (visible flickering)
  • Video recording (frame rate mismatches create artifacts) Use: FastLED.setDither(DISABLE_DITHER) at runtime, or define NO_DITHERING=1 to compile the algorithm out (see `fl::Dither` below).

NOTE: This is NOT gamma correction. Dithering is pure temporal averaging to recover quantization precision.


The state is two caller-owned arrays, indexed by source channel:

  • d[3]: the offset added to the current pixel.
  • e[3]: the toggle range. Stored one less than the 256/scale + 1 it is computed from, so stepping is the single subtraction d = e - d. PixelController owns them as its public d/e members because hand-written drivers (AVR clockless asm, the M0 and Teensy structs) read them by name; this file owns everything that is done to them.

Two policies share one static interface:

  • fl::BinaryDither – the algorithm.
  • fl::NoDither – init clears, step does nothing, apply is the identity. Every call compiles to nothing (or to the zeroing that keeps the asm drivers defined). fl::Dither is the one selected by NO_DITHERING.

Definition in file binary_dither.h.

#include "fl/stl/int.h"
#include "fl/stl/compiler_control.h"
#include "fl/stl/noexcept.h"
#include "fl/math/math8.h"
#include "fl/math/scale8.h"
+ Include dependency graph for binary_dither.h:
+ This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Classes

struct  fl::BinaryDither
 Temporal binary dithering over caller-owned d[3]/e[3]. More...
 
struct  fl::NoDither
 The disabled policy: same interface, no work. More...
 

Namespaces

namespace  fl
 Base definition for an LED controller.
 

Macros

#define MAX_LIKELY_UPDATE_RATE_HZ   400
 Predicted max update rate, in Hertz.
 
#define MIN_ACCEPTABLE_DITHER_RATE_HZ   50
 Minimum acceptable dithering rate, in Hertz.
 
#define RECOMMENDED_VIRTUAL_BITS
 Set "virtual bits" of dithering to the highest level that is not likely to cause excessive flickering at low brightness levels + low update rates.
 
#define UPDATES_PER_FULL_DITHER_CYCLE   (MAX_LIKELY_UPDATE_RATE_HZ / MIN_ACCEPTABLE_DITHER_RATE_HZ)
 The number of updates in a single dither cycle.
 
#define VIRTUAL_BITS   RECOMMENDED_VIRTUAL_BITS
 Alias for RECOMMENDED_VIRTUAL_BITS.
 

Typedefs

typedef BinaryDither fl::Dither
 The policy this build uses.