From 2ce8f5cc866b8e32955f951321acf192fee61e7d Mon Sep 17 00:00:00 2001
From: Micah Elizabeth Scott <micah@scanlime.org>
Date: Tue, 23 Jul 2013 21:52:22 -0700
Subject: [PATCH] Switch to 257-entry color LUTs

This makes the mapping from 16-bit color to LUT entry straightforward, while still fully defining the entire range from 0x0000 through 0xFFFF. This fixes an edge case where colors greater than 0xFF00 would wrap past the end of the R/G LUTs onto the next color channel.
---
 README.md              | 8 +++++---
 firmware/fadecandy.cpp | 9 ++++++---
 firmware/fc_defs.h     | 3 ++-
 firmware/fc_usb.cpp    | 5 +----
 firmware/fc_usb.h      | 4 ++--
 5 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/README.md b/README.md
index 1a1a360..26b5a1a 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ This firmware is based on Stoffregen's excellent [OctoWS2811](http://www.pjrc.co
 * A high performance USB protocol
 * Zero copy architecture with triple-buffering
 * Interpolation between keyframes
-* Gamma and color correction with per-channel 256-entry lookup tables
+* Gamma and color correction with per-channel lookup tables
 * Temporal dithering
 
 These features add up to give *very smooth* fades and high dynamic range. Ever notice that annoying stair-stepping effect when fading LEDs from off to dim? Fadecandy avoids that using a form of [delta-sigma modulation](http://en.wikipedia.org/wiki/Delta-sigma_modulation). It rapidly wiggles each pixel's value up or down by one 8-bit step, in order to achieve 16-bit resolution for fades.
@@ -21,7 +21,7 @@ Vitals
 * 512 pixels supported per Teensy board (8 strings, 64 pixels per string)
 * Very high hardware frame rate (395 FPS) to support temporal dithering
 * Full-speed (12 Mbps) USB
-* 768-entry 16-bit color lookup table, for gamma correction and color balance
+* 257x3-entry 16-bit color lookup table, for gamma correction and color balance
 
 Color Processing
 ----------------
@@ -39,6 +39,8 @@ Each pixel goes through the following processing steps in Fadecandy:
 * These 8-bit colors are converted to the format needed by OctoWS2811's DMA engine
 * In hardware, the converted colors are streamed out to eight LED strings in parallel
 
+The color lookup tables can be used to implement gamma correction, brightness and contrast, and white point correction. Each channel (RGB) has a 257 entry table. Each entry is a 16-bit intensity. Entry 0 corresponds to the 16-bit color 0x0000, entry 1 corresponds to 0x0100, etc. The 257th entry corresponds to 0x10000, which is just past the end of the 16-bit intensity space.
+
 Keyframe Interpolation
 ----------------------
 
@@ -142,7 +144,7 @@ Byte Offset   | Description
 62            | Pixel 20, Green
 63            | Pixel 20, Blue
 
-In a type 1 packet, the USB packet contains up to 31 lookup-table entries. The lookup table is structured as three arrays of 256 entries, starting with the entire red-channel LUT, then the green-channel LUT, then the blue-channel LUT. Each packet is structured as follows:
+In a type 1 packet, the USB packet contains up to 31 lookup-table entries. The lookup table is structured as three arrays of 257 entries, starting with the entire red-channel LUT, then the green-channel LUT, then the blue-channel LUT. Each packet is structured as follows:
 
 Byte Offset   | Description
 ------------- | ------------
diff --git a/firmware/fadecandy.cpp b/firmware/fadecandy.cpp
index caf4800..6e464b0 100644
--- a/firmware/fadecandy.cpp
+++ b/firmware/fadecandy.cpp
@@ -75,6 +75,9 @@ ALWAYS_INLINE static inline uint32_t lutInterpolate(const uint16_t *lut, uint32_
      * Using our color LUT for the indicated channel, convert the
      * 16-bit intensity "arg" in our input colorspace to a corresponding
      * 16-bit intensity in the device colorspace.
+     *
+     * Remember that our LUT is 257 entries long. The final entry corresponds to an
+     * input of 0x10000, which can't quite be reached.
      */
 
     unsigned index = arg >> 8;
@@ -102,9 +105,9 @@ static uint32_t updatePixel(uint32_t icPrev, uint32_t icNext,
     int iB = (pixelPrev[2] * icPrev + pixelNext[2] * icNext) >> 16;
 
     // Pass through our color LUT
-    iR = lutInterpolate(&lut[0 * 256], iR);
-    iG = lutInterpolate(&lut[1 * 256], iG);
-    iB = lutInterpolate(&lut[2 * 256], iB);
+    iR = lutInterpolate(&lut[0 * LUT_CH_SIZE], iR);
+    iG = lutInterpolate(&lut[1 * LUT_CH_SIZE], iG);
+    iB = lutInterpolate(&lut[2 * LUT_CH_SIZE], iB);
 
     // Incorporate the residual from last frame
     iR += pResidual[0];
diff --git a/firmware/fc_defs.h b/firmware/fc_defs.h
index ce272a2..b93bb5b 100644
--- a/firmware/fc_defs.h
+++ b/firmware/fc_defs.h
@@ -31,7 +31,8 @@
 #define LEDS_TOTAL              (LEDS_PER_STRIP * 8)
 #define CHANNELS_TOTAL          (LEDS_TOTAL * 3)
 
-#define LUT_SIZE				(256 * 3)
+#define LUT_CH_SIZE				257
+#define LUT_TOTAL_SIZE			(LUT_CH_SIZE * 3)
 
 // USB packet layout
 #define PIXELS_PER_PACKET       21
diff --git a/firmware/fc_usb.cpp b/firmware/fc_usb.cpp
index 53b0e01..7bbba7a 100644
--- a/firmware/fc_usb.cpp
+++ b/firmware/fc_usb.cpp
@@ -106,10 +106,7 @@ void fcBuffers::finalizeLUT()
      * so this isn't a performance bottleneck.
      */
 
-    for (unsigned i = 0; i < LUT_SIZE; ++i) {
+    for (unsigned i = 0; i < LUT_TOTAL_SIZE; ++i) {
         lutCurrent[i] = lutNew.entry(i);
     }
-
-    // Padding, so that it's okay to read past the end during interpolation
-    lutCurrent[LUT_SIZE] = lutCurrent[LUT_SIZE - 1];
 }
diff --git a/firmware/fc_usb.h b/firmware/fc_usb.h
index d1a019e..c38bc37 100644
--- a/firmware/fc_usb.h
+++ b/firmware/fc_usb.h
@@ -110,8 +110,8 @@ struct fcBuffers
 
     fcFramebuffer fb[3];        // Triple-buffered video frames
 
-    fcColorLUT lutNew;                   // Partial LUT, not yet finalized
-    uint16_t lutCurrent[LUT_SIZE + 1];   // Active LUT, linearized for efficiency, padded on the end.
+    fcColorLUT lutNew;                      // Partial LUT, not yet finalized
+    uint16_t lutCurrent[LUT_TOTAL_SIZE];    // Active LUT, linearized for efficiency
 
     uint8_t flags;              // Configuration flags
 
-- 
GitLab