diff --git a/ci/ci-compile b/ci/ci-compile index a7a21f2c..2bc3fd3d 100755 --- a/ci/ci-compile +++ b/ci/ci-compile @@ -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"} diff --git a/examples/Apa102HD/Apa102HD.ino b/examples/Apa102HD/Apa102HD.ino new file mode 100644 index 00000000..76138a67 --- /dev/null +++ b/examples/Apa102HD/Apa102HD.ino @@ -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 +#include +#include + +#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(leds_hd, NUM_LEDS); + FastLED.addLeds(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. +} + diff --git a/src/FastLED.h b/src/FastLED.h index 540cea32..807d3f1e 100644 --- a/src/FastLED.h +++ b/src/FastLED.h @@ -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 c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } case DOTSTAR: case APA102: { static APA102Controller c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } + case DOTSTARHD: + case APA102HD: { static APA102ControllerHD c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } case SK9822: { static SK9822Controller c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } + case SK9822HD: { static SK9822ControllerHD c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } } } @@ -293,7 +299,10 @@ public: case P9813: { static P9813Controller c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } case DOTSTAR: case APA102: { static APA102Controller c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } + case DOTSTARHD: + case APA102HD: { static APA102ControllerHD c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } case SK9822: { static SK9822Controller c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } + case SK9822HD: { static SK9822ControllerHD c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } } } @@ -308,7 +317,10 @@ public: case P9813: { static P9813Controller c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } case DOTSTAR: case APA102: { static APA102Controller c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } + case DOTSTARHD: + case APA102HD: { static APA102ControllerHD c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } case SK9822: { static SK9822Controller c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } + case SK9822HD: { static SK9822ControllerHD c; return addLeds(&c, data, nLedsOrOffset, nLedsIfOffset); } } } diff --git a/src/chipsets.h b/src/chipsets.h index 40bd988c..7097e5a0 100644 --- a/src/chipsets.h +++ b/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 +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 { typedef SPIOutput 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 { #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 & 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& 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(brightness); + } + // Legacy showPixels implementation. + inline void showPixelsDefault(PixelController & 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 & 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( + (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 -class SK9822Controller : public CPixelLEDController { - typedef SPIOutput 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 & 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 +> { }; diff --git a/src/five_bit_hd_gamma.cpp b/src/five_bit_hd_gamma.cpp new file mode 100644 index 00000000..f908ff09 --- /dev/null +++ b/src/five_bit_hd_gamma.cpp @@ -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 diff --git a/src/five_bit_hd_gamma.h b/src/five_bit_hd_gamma.h new file mode 100644 index 00000000..1c7cc66c --- /dev/null +++ b/src/five_bit_hd_gamma.h @@ -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_