/* * High dynamic range color library. * * Copyright <c> 2013 Micah Elizabeth Scott. <micah@scanlime.org> * * This is a high dynamic range (48-bit) color data type, * and a temporal dithering implementation that's compatible * with the OctoWS2811 LED driver. */ #pragma once #include <stdint.h> #include <algorithm> class OctoWS2811; /// Basic data type for a high-dynamic-range color. struct HColor { uint16_t r, g, b; }; /// Constructor for 16-bit colors static inline HColor HColor16(uint16_t r, uint16_t g, uint16_t b) { HColor c = { r, g, b }; return c; } /// Constructor for 8-bit colors static inline HColor HColor8(uint8_t r, uint8_t g, uint8_t b) { HColor c = { r | (unsigned(r) << 8), g | (unsigned(g) << 8), b | (unsigned(b) << 8), }; return c; } /// Constructor for 8-bit colors packed into a 24-bit word static inline HColor HColor8(uint32_t rgb) { return HColor8( (rgb & 0xFF0000) >> 16, (rgb & 0x00FF00) >> 8, rgb & 0x0000FF ); } /// Constructor for float colors, with clamping. static inline HColor HColorF(float r, float g, float b) { HColor c = { std::min<int>(0xffff, std::max<int>(0, r * 65535.0f + 0.5f)), std::min<int>(0xffff, std::max<int>(0, g * 65535.0f + 0.5f)), std::min<int>(0xffff, std::max<int>(0, b * 65535.0f + 0.5f)), }; return c; } /// Add two colors, with saturation static inline HColor operator + (HColor a, HColor b) { HColor c = { std::min<int>(0xffff, unsigned(a.r) + unsigned(b.r)), std::min<int>(0xffff, unsigned(a.g) + unsigned(b.g)), std::min<int>(0xffff, unsigned(a.b) + unsigned(b.b)), }; return c; } /** * Linear interpolation between two colors. "Alpha" is in 8-bit fixed point. * Returns c1 if alpha==0, or c2 if alpha==0x100. Values outside this range will extrapolate. */ static inline HColor lerp8(HColor c1, HColor c2, int alpha) { int invA = 0x100 - alpha; HColor c = { (c1.r * invA + c2.r * alpha) >> 8, (c1.g * invA + c2.g * alpha) >> 8, (c1.b * invA + c2.b * alpha) >> 8, }; return c; } /// Floating point linear interpolation, with clamping. static inline HColor lerp(HColor c1, HColor c2, float alpha) { float invA = 1.0f - alpha; HColor c = { std::min<int>(0xffff, std::max<int>(0, c1.r * invA + c2.r * alpha)), std::min<int>(0xffff, std::max<int>(0, c1.g * invA + c2.g * alpha)), std::min<int>(0xffff, std::max<int>(0, c1.b * invA + c2.b * alpha)), }; return c; } /// Data type for one display pixel struct HPixel { HColor color; int16_t residual[3]; /// Temporal dithering algorithm. Returns a 24-bit RGB color. uint32_t dither() { // Incorporate the residual from last frame int r16 = color.r + residual[0]; int g16 = color.g + residual[1]; int b16 = color.b + residual[2]; // Round to the nearest 8-bit value int r8 = std::min<int>(0xff, std::max<int>(0, (r16 + 0x80) >> 8)); int g8 = std::min<int>(0xff, std::max<int>(0, (g16 + 0x80) >> 8)); int b8 = std::min<int>(0xff, std::max<int>(0, (b16 + 0x80) >> 8)); // Compute the error, after expanding the 8-bit value back to 16-bit. residual[0] = r16 - (r8 * 257); residual[1] = g16 - (g8 * 257); residual[2] = b16 - (b8 * 257); return (r8 << 16) | (g8 << 8) | b8; } }; /// Data type for a framebuffer of pixels template <unsigned tCount> struct HPixelBuffer { HPixel pixels[tCount]; /// Update the entire frame void show(OctoWS2811 &leds) { for (unsigned i = 0; i < tCount; ++i) { leds.setPixel(i, pixels[i].dither()); } leds.show(); } };