Merge pull request #1546 from zackees/apa102-hd-gamma
Implements "High Bit Depth Gamma Correction Algorithm for APA102/Dotstar LEDs" for APA102 and SK9822 chipsets
This commit is contained in:
commit
3a03742a09
@ -17,7 +17,7 @@
|
||||
set -eou pipefail
|
||||
|
||||
# List of examples that will be compiled by default
|
||||
EXAMPLES=${EXAMPLES:-"Blink ColorPalette ColorTemperature Cylon DemoReel100
|
||||
EXAMPLES=${EXAMPLES:-"Apa102HD Blink ColorPalette ColorTemperature Cylon DemoReel100
|
||||
Fire2012 FirstLight Multiple/MultipleStripsInOneArray
|
||||
Multiple/ArrayOfLedArrays Noise NoisePlayground NoisePlusPalette Pacifica
|
||||
Pride2015 RGBCalibrate RGBSetDemo TwinkleFox XYMatrix"}
|
||||
|
87
examples/Apa102HD/Apa102HD.ino
Normal file
87
examples/Apa102HD/Apa102HD.ino
Normal file
@ -0,0 +1,87 @@
|
||||
/// @file Apa102HD.ino
|
||||
/// @brief Example showing how to use the APA102HD gamma correction.
|
||||
///
|
||||
/// In this example we compare two strips of LEDs.
|
||||
/// One strip is in HD mode, the other is in software gamma mode.
|
||||
///
|
||||
/// Each strip is a linear ramp of brightnesses, from 0 to 255.
|
||||
/// Showcasing all the different brightnesses.
|
||||
///
|
||||
/// Why do we love gamma correction? Gamma correction more closely
|
||||
/// matches how humans see light. Led values are measured in fractions
|
||||
/// of max power output (1/255, 2/255, etc.), while humans see light
|
||||
/// in a logarithmic way. Gamma correction converts to this eye friendly
|
||||
/// curve. Gamma correction wants a LED with a high bit depth. The APA102
|
||||
/// gives us the standard 3 components (red, green, blue) with 8 bits each, it
|
||||
/// *also* has a 5 bit brightness component. This gives us a total of 13 bits,
|
||||
/// which allows us to achieve a higher dynamic range. This means deeper fades.
|
||||
///
|
||||
/// Example:
|
||||
/// CRGB leds[NUM_LEDS] = {0};
|
||||
/// void setup() {
|
||||
/// FastLED.addLeds<
|
||||
/// APA102HD, // <--- This selects HD mode.
|
||||
/// STRIP_0_DATA_PIN,
|
||||
/// STRIP_0_CLOCK_PIN,
|
||||
/// RGB
|
||||
/// >(leds, NUM_LEDS);
|
||||
/// }
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <FastLED.h>
|
||||
#include <lib8tion.h>
|
||||
|
||||
#define NUM_LEDS 20
|
||||
// uint8_t DATA_PIN, uint8_t CLOCK_PIN,
|
||||
#define STRIP_0_DATA_PIN 1
|
||||
#define STRIP_0_CLOCK_PIN 2
|
||||
#define STRIP_1_DATA_PIN 3
|
||||
#define STRIP_1_CLOCK_PIN 4
|
||||
|
||||
CRGB leds_hd[NUM_LEDS] = {0}; // HD mode implies gamma.
|
||||
CRGB leds[NUM_LEDS] = {0}; // Software gamma mode.
|
||||
|
||||
// This is the regular gamma correction function that we used to have
|
||||
// to do. It's used here to showcase the difference between APA102HD
|
||||
// mode which does the gamma correction for you.
|
||||
CRGB software_gamma(const CRGB& in) {
|
||||
CRGB out;
|
||||
// dim8_raw are the old gamma correction functions.
|
||||
out.r = dim8_raw(in.r);
|
||||
out.g = dim8_raw(in.g);
|
||||
out.b = dim8_raw(in.b);
|
||||
return out;
|
||||
}
|
||||
|
||||
void setup() {
|
||||
delay(500); // power-up safety delay
|
||||
// Two strips of LEDs, one in HD mode, one in software gamma mode.
|
||||
FastLED.addLeds<APA102HD, STRIP_0_DATA_PIN, STRIP_0_CLOCK_PIN, RGB>(leds_hd, NUM_LEDS);
|
||||
FastLED.addLeds<APA102, STRIP_1_DATA_PIN, STRIP_1_CLOCK_PIN, RGB>(leds, NUM_LEDS);
|
||||
}
|
||||
|
||||
uint8_t wrap_8bit(int i) {
|
||||
// Module % operator here wraps a large "i" so that it is
|
||||
// always in [0, 255] range when returned. For example, if
|
||||
// "i" is 256, then this will return 0. If "i" is 257
|
||||
// then this will return 1. No matter how big the "i" is, the
|
||||
// output range will always be [0, 255]
|
||||
return i % 256;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// Draw a a linear ramp of brightnesses to showcase the difference between
|
||||
// the HD and non-HD mode.
|
||||
for (int i = 0; i < NUM_LEDS; i++) {
|
||||
uint8_t brightness = map(i, 0, NUM_LEDS - 1, 0, 255);
|
||||
CRGB c(brightness, brightness, brightness); // Just make a shade of white.
|
||||
leds_hd[i] = c; // The APA102HD leds do their own gamma correction.
|
||||
CRGB c_gamma_corrected = software_gamma(c);
|
||||
leds[i] = c_gamma_corrected; // Set the software gamma corrected
|
||||
// values to the other strip.
|
||||
}
|
||||
FastLED.show(); // All leds are now written out.
|
||||
delay(8); // Wait 8 milliseconds until the next frame.
|
||||
}
|
||||
|
@ -91,7 +91,10 @@ enum ESPIChipsets {
|
||||
P9813, ///< P9813 LED chipset
|
||||
APA102, ///< APA102 LED chipset
|
||||
SK9822, ///< SK9822 LED chipset
|
||||
DOTSTAR ///< APA102 LED chipset alias
|
||||
SK9822HD, ///< SK9822 LED chipset with 5-bit gamma correction
|
||||
DOTSTAR, ///< APA102 LED chipset alias
|
||||
DOTSTARHD, ///< APA102HD LED chipset alias
|
||||
APA102HD, ///< APA102 LED chipset with 5-bit gamma correction
|
||||
};
|
||||
|
||||
/// Smart Matrix Library controller type
|
||||
@ -278,7 +281,10 @@ public:
|
||||
case P9813: { static P9813Controller<DATA_PIN, CLOCK_PIN, RGB_ORDER, SPI_DATA_RATE> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case DOTSTAR:
|
||||
case APA102: { static APA102Controller<DATA_PIN, CLOCK_PIN, RGB_ORDER, SPI_DATA_RATE> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case DOTSTARHD:
|
||||
case APA102HD: { static APA102ControllerHD<DATA_PIN, CLOCK_PIN, RGB_ORDER, SPI_DATA_RATE> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case SK9822: { static SK9822Controller<DATA_PIN, CLOCK_PIN, RGB_ORDER, SPI_DATA_RATE> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case SK9822HD: { static SK9822ControllerHD<DATA_PIN, CLOCK_PIN, RGB_ORDER, SPI_DATA_RATE> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,7 +299,10 @@ public:
|
||||
case P9813: { static P9813Controller<DATA_PIN, CLOCK_PIN> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case DOTSTAR:
|
||||
case APA102: { static APA102Controller<DATA_PIN, CLOCK_PIN> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case DOTSTARHD:
|
||||
case APA102HD: { static APA102ControllerHD<DATA_PIN, CLOCK_PIN> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case SK9822: { static SK9822Controller<DATA_PIN, CLOCK_PIN> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case SK9822HD: { static SK9822ControllerHD<DATA_PIN, CLOCK_PIN> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,7 +317,10 @@ public:
|
||||
case P9813: { static P9813Controller<DATA_PIN, CLOCK_PIN, RGB_ORDER> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case DOTSTAR:
|
||||
case APA102: { static APA102Controller<DATA_PIN, CLOCK_PIN, RGB_ORDER> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case DOTSTARHD:
|
||||
case APA102HD: { static APA102ControllerHD<DATA_PIN, CLOCK_PIN, RGB_ORDER> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case SK9822: { static SK9822Controller<DATA_PIN, CLOCK_PIN, RGB_ORDER> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
case SK9822HD: { static SK9822ControllerHD<DATA_PIN, CLOCK_PIN, RGB_ORDER> c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); }
|
||||
}
|
||||
}
|
||||
|
||||
|
216
src/chipsets.h
216
src/chipsets.h
@ -3,6 +3,7 @@
|
||||
|
||||
#include "FastLED.h"
|
||||
#include "pixeltypes.h"
|
||||
#include "five_bit_hd_gamma.h"
|
||||
|
||||
/// @file chipsets.h
|
||||
/// Contains the bulk of the definitions for the various LED chipsets supported.
|
||||
@ -214,13 +215,35 @@ protected:
|
||||
/// @tparam CLOCK_PIN the clock pin for these LEDs
|
||||
/// @tparam RGB_ORDER the RGB ordering for these LEDs
|
||||
/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(12)
|
||||
template <uint8_t DATA_PIN, uint8_t CLOCK_PIN, EOrder RGB_ORDER = RGB, uint32_t SPI_SPEED = DATA_RATE_MHZ(12)>
|
||||
template <
|
||||
uint8_t DATA_PIN, uint8_t CLOCK_PIN,
|
||||
EOrder RGB_ORDER = RGB,
|
||||
uint32_t SPI_SPEED = DATA_RATE_MHZ(12),
|
||||
FiveBitGammaCorrectionMode GAMMA_CORRECTION_MODE = kFiveBitGammaCorrectionMode_Null,
|
||||
uint32_t START_FRAME = 0x00000000,
|
||||
uint32_t END_FRAME = 0xFF000000
|
||||
>
|
||||
class APA102Controller : public CPixelLEDController<RGB_ORDER> {
|
||||
typedef SPIOutput<DATA_PIN, CLOCK_PIN, SPI_SPEED> SPI;
|
||||
SPI mSPI;
|
||||
|
||||
void startBoundary() { mSPI.writeWord(0); mSPI.writeWord(0); }
|
||||
void endBoundary(int nLeds) { int nDWords = (nLeds/32); do { mSPI.writeByte(0xFF); mSPI.writeByte(0x00); mSPI.writeByte(0x00); mSPI.writeByte(0x00); } while(nDWords--); }
|
||||
void startBoundary() {
|
||||
mSPI.writeWord(START_FRAME >> 16);
|
||||
mSPI.writeWord(START_FRAME & 0xFFFF);
|
||||
}
|
||||
void endBoundary(int nLeds) {
|
||||
int nDWords = (nLeds/32);
|
||||
const uint8_t b0 = uint8_t(END_FRAME >> 24 & 0x000000ff);
|
||||
const uint8_t b1 = uint8_t(END_FRAME >> 16 & 0x000000ff);
|
||||
const uint8_t b2 = uint8_t(END_FRAME >> 8 & 0x000000ff);
|
||||
const uint8_t b3 = uint8_t(END_FRAME >> 0 & 0x000000ff);
|
||||
do {
|
||||
mSPI.writeByte(b0);
|
||||
mSPI.writeByte(b1);
|
||||
mSPI.writeByte(b2);
|
||||
mSPI.writeByte(b3);
|
||||
} while(nDWords--);
|
||||
}
|
||||
|
||||
inline void writeLed(uint8_t brightness, uint8_t b0, uint8_t b1, uint8_t b2) __attribute__((always_inline)) {
|
||||
#ifdef FASTLED_SPI_BYTE_ONLY
|
||||
@ -237,6 +260,15 @@ class APA102Controller : public CPixelLEDController<RGB_ORDER> {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void write2Bytes(uint8_t b1, uint8_t b2) __attribute__((always_inline)) {
|
||||
#ifdef FASTLED_SPI_BYTE_ONLY
|
||||
mSPI.writeByte(b1);
|
||||
mSPI.writeByte(b2);
|
||||
#else
|
||||
mSPI.writeWord(uint16_t(b1) << 8 | b2);
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
APA102Controller() {}
|
||||
|
||||
@ -247,9 +279,26 @@ public:
|
||||
protected:
|
||||
/// @copydoc CPixelLEDController::showPixels()
|
||||
virtual void showPixels(PixelController<RGB_ORDER> & pixels) {
|
||||
mSPI.select();
|
||||
switch (GAMMA_CORRECTION_MODE) {
|
||||
case kFiveBitGammaCorrectionMode_Null: {
|
||||
showPixelsDefault(pixels);
|
||||
break;
|
||||
}
|
||||
case kFiveBitGammaCorrectionMode_BitShift: {
|
||||
showPixelsGammaBitShift(pixels);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t s0 = pixels.getScale0(), s1 = pixels.getScale1(), s2 = pixels.getScale2();
|
||||
private:
|
||||
|
||||
static inline void getGlobalBrightnessAndScalingFactors(
|
||||
PixelController<RGB_ORDER>& pixels,
|
||||
uint8_t* out_s0, uint8_t* out_s1, uint8_t* out_s2, uint8_t* out_brightness) {
|
||||
uint8_t s0 = pixels.getScale0();
|
||||
uint8_t s1 = pixels.getScale1();
|
||||
uint8_t s2 = pixels.getScale2();
|
||||
#if FASTLED_USE_GLOBAL_BRIGHTNESS == 1
|
||||
const uint16_t maxBrightness = 0x1F;
|
||||
uint16_t brightness = ((((uint16_t)max(max(s0, s1), s2) + 1) * maxBrightness - 1) >> 8) + 1;
|
||||
@ -259,10 +308,23 @@ protected:
|
||||
#else
|
||||
const uint8_t brightness = 0x1F;
|
||||
#endif
|
||||
*out_s0 = s0;
|
||||
*out_s1 = s1;
|
||||
*out_s2 = s2;
|
||||
*out_brightness = static_cast<uint8_t>(brightness);
|
||||
}
|
||||
|
||||
// Legacy showPixels implementation.
|
||||
inline void showPixelsDefault(PixelController<RGB_ORDER> & pixels) {
|
||||
mSPI.select();
|
||||
uint8_t s0, s1, s2, global_brightness;
|
||||
getGlobalBrightnessAndScalingFactors(pixels, &s0, &s1, &s2, &global_brightness);
|
||||
startBoundary();
|
||||
while (pixels.has(1)) {
|
||||
writeLed(brightness, pixels.loadAndScale0(0, s0), pixels.loadAndScale1(0, s1), pixels.loadAndScale2(0, s2));
|
||||
uint8_t r = pixels.loadAndScale0(0, s0);
|
||||
uint8_t g = pixels.loadAndScale1(0, s1);
|
||||
uint8_t b = pixels.loadAndScale2(0, s2);
|
||||
writeLed(global_brightness, r, g, b);
|
||||
pixels.stepDithering();
|
||||
pixels.advanceData();
|
||||
}
|
||||
@ -272,72 +334,96 @@ protected:
|
||||
mSPI.release();
|
||||
}
|
||||
|
||||
inline void showPixelsGammaBitShift(PixelController<RGB_ORDER> & pixels) {
|
||||
mSPI.select();
|
||||
uint8_t s0, s1, s2, global_brightness;
|
||||
getGlobalBrightnessAndScalingFactors(pixels, &s0, &s1, &s2, &global_brightness);
|
||||
startBoundary();
|
||||
while (pixels.has(1)) {
|
||||
uint8_t r = pixels.loadAndScale0(0, s0);
|
||||
uint8_t g = pixels.loadAndScale1(0, s1);
|
||||
uint8_t b = pixels.loadAndScale2(0, s2);
|
||||
uint8_t brightness = 0;
|
||||
five_bit_hd_gamma_bitshift(r, g, b, &r, &g, &b, &brightness);
|
||||
if (global_brightness >= 0x1F) {
|
||||
// 5-bit mix.
|
||||
brightness = static_cast<uint8_t>(
|
||||
(uint16_t(brightness) * global_brightness)
|
||||
/ 0x1F
|
||||
);
|
||||
}
|
||||
writeLed(brightness, r, g, b);
|
||||
pixels.stepDithering();
|
||||
pixels.advanceData();
|
||||
}
|
||||
endBoundary(pixels.size());
|
||||
|
||||
mSPI.waitFully();
|
||||
mSPI.release();
|
||||
}
|
||||
};
|
||||
|
||||
/// SK9822 controller class.
|
||||
template <
|
||||
uint8_t DATA_PIN,
|
||||
uint8_t CLOCK_PIN,
|
||||
EOrder RGB_ORDER = RGB,
|
||||
uint32_t SPI_SPEED = DATA_RATE_MHZ(24)
|
||||
>
|
||||
class APA102ControllerHD : public APA102Controller<
|
||||
DATA_PIN,
|
||||
CLOCK_PIN,
|
||||
RGB_ORDER,
|
||||
SPI_SPEED,
|
||||
kFiveBitGammaCorrectionMode_BitShift,
|
||||
uint32_t(0x00000000),
|
||||
uint32_t(0x00000000)> {
|
||||
public:
|
||||
APA102ControllerHD() = default;
|
||||
APA102ControllerHD(const APA102ControllerHD&) = delete;
|
||||
};
|
||||
|
||||
/// SK9822 controller class. It's exactly the same as the APA102Controller protocol but with a different END_FRAME and default SPI_SPEED.
|
||||
/// @tparam DATA_PIN the data pin for these LEDs
|
||||
/// @tparam CLOCK_PIN the clock pin for these LEDs
|
||||
/// @tparam RGB_ORDER the RGB ordering for these LEDs
|
||||
/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(24)
|
||||
template <uint8_t DATA_PIN, uint8_t CLOCK_PIN, EOrder RGB_ORDER = RGB, uint32_t SPI_SPEED = DATA_RATE_MHZ(24)>
|
||||
class SK9822Controller : public CPixelLEDController<RGB_ORDER> {
|
||||
typedef SPIOutput<DATA_PIN, CLOCK_PIN, SPI_SPEED> SPI;
|
||||
SPI mSPI;
|
||||
|
||||
void startBoundary() { mSPI.writeWord(0); mSPI.writeWord(0); }
|
||||
void endBoundary(int nLeds) { int nLongWords = (nLeds/32); do { mSPI.writeByte(0x00); mSPI.writeByte(0x00); mSPI.writeByte(0x00); mSPI.writeByte(0x00); } while(nLongWords--); }
|
||||
|
||||
inline void writeLed(uint8_t brightness, uint8_t b0, uint8_t b1, uint8_t b2) __attribute__((always_inline)) {
|
||||
#ifdef FASTLED_SPI_BYTE_ONLY
|
||||
mSPI.writeByte(0xE0 | brightness);
|
||||
mSPI.writeByte(b0);
|
||||
mSPI.writeByte(b1);
|
||||
mSPI.writeByte(b2);
|
||||
#else
|
||||
uint16_t b = 0xE000 | (brightness << 8) | (uint16_t)b0;
|
||||
mSPI.writeWord(b);
|
||||
uint16_t w = b1 << 8;
|
||||
w |= b2;
|
||||
mSPI.writeWord(w);
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
SK9822Controller() {}
|
||||
|
||||
virtual void init() {
|
||||
mSPI.init();
|
||||
}
|
||||
|
||||
protected:
|
||||
/// @copydoc CPixelLEDController::showPixels()
|
||||
virtual void showPixels(PixelController<RGB_ORDER> & pixels) {
|
||||
mSPI.select();
|
||||
|
||||
uint8_t s0 = pixels.getScale0(), s1 = pixels.getScale1(), s2 = pixels.getScale2();
|
||||
#if FASTLED_USE_GLOBAL_BRIGHTNESS == 1
|
||||
const uint16_t maxBrightness = 0x1F;
|
||||
uint16_t brightness = ((((uint16_t)max(max(s0, s1), s2) + 1) * maxBrightness - 1) >> 8) + 1;
|
||||
s0 = (maxBrightness * s0 + (brightness >> 1)) / brightness;
|
||||
s1 = (maxBrightness * s1 + (brightness >> 1)) / brightness;
|
||||
s2 = (maxBrightness * s2 + (brightness >> 1)) / brightness;
|
||||
#else
|
||||
const uint8_t brightness = 0x1F;
|
||||
#endif
|
||||
|
||||
startBoundary();
|
||||
while (pixels.has(1)) {
|
||||
writeLed(brightness, pixels.loadAndScale0(0, s0), pixels.loadAndScale1(0, s1), pixels.loadAndScale2(0, s2));
|
||||
pixels.stepDithering();
|
||||
pixels.advanceData();
|
||||
}
|
||||
|
||||
endBoundary(pixels.size());
|
||||
|
||||
mSPI.waitFully();
|
||||
mSPI.release();
|
||||
}
|
||||
template <
|
||||
uint8_t DATA_PIN,
|
||||
uint8_t CLOCK_PIN,
|
||||
EOrder RGB_ORDER = RGB,
|
||||
uint32_t SPI_SPEED = DATA_RATE_MHZ(24)
|
||||
>
|
||||
class SK9822Controller : public APA102Controller<
|
||||
DATA_PIN,
|
||||
CLOCK_PIN,
|
||||
RGB_ORDER,
|
||||
SPI_SPEED,
|
||||
kFiveBitGammaCorrectionMode_Null,
|
||||
0x00000000,
|
||||
0x00000000
|
||||
> {
|
||||
};
|
||||
|
||||
/// SK9822 controller class. It's exactly the same as the APA102Controller protocol but with a different END_FRAME and default SPI_SPEED.
|
||||
/// @tparam DATA_PIN the data pin for these LEDs
|
||||
/// @tparam CLOCK_PIN the clock pin for these LEDs
|
||||
/// @tparam RGB_ORDER the RGB ordering for these LEDs
|
||||
/// @tparam SPI_SPEED the clock divider used for these LEDs. Set using the ::DATA_RATE_MHZ / ::DATA_RATE_KHZ macros. Defaults to ::DATA_RATE_MHZ(24)
|
||||
template <
|
||||
uint8_t DATA_PIN,
|
||||
uint8_t CLOCK_PIN,
|
||||
EOrder RGB_ORDER = RGB,
|
||||
uint32_t SPI_SPEED = DATA_RATE_MHZ(24)
|
||||
>
|
||||
class SK9822ControllerHD : public APA102Controller<
|
||||
DATA_PIN,
|
||||
CLOCK_PIN,
|
||||
RGB_ORDER,
|
||||
SPI_SPEED,
|
||||
kFiveBitGammaCorrectionMode_BitShift,
|
||||
0x00000000,
|
||||
0x00000000
|
||||
> {
|
||||
};
|
||||
|
||||
|
||||
|
135
src/five_bit_hd_gamma.cpp
Normal file
135
src/five_bit_hd_gamma.cpp
Normal file
@ -0,0 +1,135 @@
|
||||
#include "FastLED.h"
|
||||
#include "five_bit_hd_gamma.h"
|
||||
|
||||
#ifndef FASTLED_FIVE_BIT_HD_GAMMA_LOW_END_LINEAR_RAMP
|
||||
#define FASTLED_FIVE_BIT_HD_GAMMA_LOW_END_LINEAR_RAMP 1
|
||||
#endif
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
|
||||
__attribute__((weak))
|
||||
void five_bit_hd_gamma_function(
|
||||
uint8_t r8, uint8_t g8, uint8_t b8,
|
||||
uint16_t* r16, uint16_t* g16, uint16_t* b16) {
|
||||
*r16 = uint16_t(r8) * r8;
|
||||
*g16 = uint16_t(g8) * g8;
|
||||
*b16 = uint16_t(b8) * b8;
|
||||
}
|
||||
|
||||
__attribute__((weak))
|
||||
void five_bit_hd_gamma_bitshift(
|
||||
uint8_t r8, uint8_t g8, uint8_t b8,
|
||||
uint8_t* out_r8,
|
||||
uint8_t* out_g8,
|
||||
uint8_t* out_b8,
|
||||
uint8_t* out_power_5bit) {
|
||||
|
||||
// Step 1: Gamma Correction
|
||||
uint16_t r16, g16, b16;
|
||||
five_bit_hd_gamma_function(r8, g8, b8, &r16, &g16, &b16);
|
||||
|
||||
// Step 2: Initialize 5-bit brightness.
|
||||
// Note: we only get 5 levels of brightness
|
||||
uint8_t v8 = 31;
|
||||
|
||||
uint16_t numerator = 1;
|
||||
uint16_t denominator = 1;
|
||||
const uint32_t r16_const = r16;
|
||||
const uint32_t g16_const = g16;
|
||||
const uint32_t b16_const = b16;
|
||||
|
||||
// Step 3: Bit Shifting Loop, can probably replaced with a
|
||||
// single pass bit-twiddling hack.
|
||||
do {
|
||||
// Note that to avoid slow divisions, we multiply the max_value
|
||||
// by the denominator.
|
||||
uint32_t max_value = 0xfffful * 15;
|
||||
if (r16_const * 31 > max_value) {
|
||||
break;
|
||||
}
|
||||
if (g16_const * 31 > max_value) {
|
||||
break;
|
||||
}
|
||||
if (b16_const * 31 > max_value) {
|
||||
break;
|
||||
}
|
||||
numerator = 31;
|
||||
denominator = 15;
|
||||
v8 = 15;
|
||||
|
||||
max_value = 0xfffful * 15 * 7;
|
||||
if (r16_const * 31 * 15 > max_value) {
|
||||
break;
|
||||
}
|
||||
if (g16_const * 31 * 15 > max_value) {
|
||||
break;
|
||||
}
|
||||
if (b16_const * 31 * 15 > max_value) {
|
||||
break;
|
||||
}
|
||||
numerator = 31 * 15;
|
||||
denominator = 15 * 7;
|
||||
v8 = 7;
|
||||
|
||||
max_value = 0xfffful * 15 * 7 * 3;
|
||||
if (r16_const * 31 * 15 * 7 > max_value) {
|
||||
break;
|
||||
}
|
||||
if (g16_const * 31 * 15 * 7 > max_value) {
|
||||
break;
|
||||
}
|
||||
if (b16_const * 31 * 15 * 7 > max_value) {
|
||||
break;
|
||||
}
|
||||
numerator = 31 * 15 * 7;
|
||||
denominator = 15 * 7 * 3;
|
||||
v8 = 3;
|
||||
|
||||
max_value = 0xfffful * 15 * 7 * 3;
|
||||
if (r16_const * 31 * 15 * 7 * 3 > max_value) {
|
||||
break;
|
||||
}
|
||||
if (g16_const * 31 * 15 * 7 * 3 > max_value) {
|
||||
break;
|
||||
}
|
||||
if (b16_const * 31 * 15 * 7 * 3 > max_value) {
|
||||
break;
|
||||
}
|
||||
numerator = 31 * 15 * 7 * 3;
|
||||
v8 = 1;
|
||||
} while(false);
|
||||
|
||||
r16 = uint16_t(r16_const * numerator / denominator);
|
||||
g16 = uint16_t(g16_const * numerator / denominator);
|
||||
b16 = uint16_t(b16_const * numerator / denominator);
|
||||
|
||||
// Step 4: Conversion Back to 8-bit.
|
||||
uint8_t r8_final = (r8 == 255 && uint8_t(r16 >> 8) >= 254) ? 255 : uint8_t(r16 >> 8);
|
||||
uint8_t g8_final = (g8 == 255 && uint8_t(g16 >> 8) >= 254) ? 255 : uint8_t(g16 >> 8);
|
||||
uint8_t b8_final = (b8 == 255 && uint8_t(b16 >> 8) >= 254) ? 255 : uint8_t(b16 >> 8);
|
||||
|
||||
#if FASTLED_FIVE_BIT_HD_GAMMA_LOW_END_LINEAR_RAMP == 1
|
||||
if (v8 == 1) {
|
||||
// Linear tuning for the lowest possible brightness. x=y until
|
||||
// the intersection point at 9.
|
||||
if (r8 < 9 && r16 > 0) {
|
||||
r8_final = r8;
|
||||
}
|
||||
if (g8 < 9 && g16 > 0) {
|
||||
g8_final = g8;
|
||||
}
|
||||
if (b8 < 9 && b16 > 0) {
|
||||
b8_final = b8;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Step 5: Output
|
||||
*out_r8 = r8_final;
|
||||
*out_g8 = g8_final;
|
||||
*out_b8 = b8_final;
|
||||
*out_power_5bit = v8;
|
||||
}
|
||||
|
||||
FASTLED_NAMESPACE_END
|
54
src/five_bit_hd_gamma.h
Normal file
54
src/five_bit_hd_gamma.h
Normal file
@ -0,0 +1,54 @@
|
||||
#ifndef _FIVE_BIT_HD_GAMMA_H_
|
||||
#define _FIVE_BIT_HD_GAMMA_H_
|
||||
|
||||
#include "FastLED.h"
|
||||
|
||||
FASTLED_NAMESPACE_BEGIN
|
||||
|
||||
enum FiveBitGammaCorrectionMode {
|
||||
kFiveBitGammaCorrectionMode_Null = 0,
|
||||
kFiveBitGammaCorrectionMode_BitShift = 1
|
||||
};
|
||||
|
||||
// Applies gamma correction for the RGBV(8, 8, 8, 5) color space, where
|
||||
// the last byte is the brightness byte at 5 bits.
|
||||
// To override this five_bit_hd_gamma_bitshift function just define
|
||||
// your own version anywhere in your project.
|
||||
// Example:
|
||||
// FASTLED_NAMESPACE_BEGIN
|
||||
// void five_bit_hd_gamma_bitshift(
|
||||
// uint8_t r8, uint8_t g8, uint8_t b8,
|
||||
// uint8_t* out_r8,
|
||||
// uint8_t* out_g8,
|
||||
// uint8_t* out_b8,
|
||||
// uint8_t* out_power_5bit) {
|
||||
// cout << "hello world\n";
|
||||
// }
|
||||
// FASTLED_NAMESPACE_END
|
||||
void five_bit_hd_gamma_bitshift(
|
||||
uint8_t r8, uint8_t g8, uint8_t b8,
|
||||
uint8_t* out_r8,
|
||||
uint8_t* out_g8,
|
||||
uint8_t* out_b8,
|
||||
uint8_t* out_power_5bit) __attribute__((weak));
|
||||
|
||||
// Simple gamma correction function that converts from
|
||||
// 8-bit color component and converts it to gamma corrected 16-bit
|
||||
// color component. Fast and no memory overhead!
|
||||
// To override this function just define your own version
|
||||
// anywhere in your project.
|
||||
// Example:
|
||||
// FASTLED_NAMESPACE_BEGIN
|
||||
// void five_bit_hd_gamma_function(
|
||||
// uint8_t r8, uint8_t g8, uint8_t b8,
|
||||
// uint16_t* r16, uint16_t* g16, uint16_t* b16) {
|
||||
// cout << "hello world\n";
|
||||
// }
|
||||
// FASTLED_NAMESPACE_END
|
||||
void five_bit_hd_gamma_function(
|
||||
uint8_t r8, uint8_t g8, uint8_t b8,
|
||||
uint16_t* r16, uint16_t* g16, uint16_t* b16) __attribute__((weak));
|
||||
|
||||
FASTLED_NAMESPACE_END
|
||||
|
||||
#endif // _FIVE_BIT_HD_GAMMA_H_
|
Loading…
Reference in New Issue
Block a user