forked from Makuna/NeoPixelBus
Dynamic gamma table (#665)
This commit is contained in:
103
examples/gamma/NeoPixelGammaDynamic/NeoPixelGammaDynamic.ino
Normal file
103
examples/gamma/NeoPixelGammaDynamic/NeoPixelGammaDynamic.ino
Normal file
@@ -0,0 +1,103 @@
|
||||
// NeoPixelGammaDynamic
|
||||
// This example will display a timed series of color gradiants with gamma correction
|
||||
// and then without.
|
||||
// If the last pixel is on, then the colors being shown are color corrected.
|
||||
// It will show Red grandiant, Green grandiant, Blue grandiant, a White grandiant, and
|
||||
// then repeat.
|
||||
//
|
||||
// This will demonstrate the use of the NeoGamma class with the
|
||||
// NeoGammaDynamicTableMethod being used for a custom gamma table
|
||||
//
|
||||
//
|
||||
|
||||
#include <NeoPixelBus.h>
|
||||
#include <NeoPixelAnimator.h>
|
||||
|
||||
const uint16_t PixelCount = 16; // make sure to set this to the number of pixels in your strip
|
||||
const uint8_t PixelPin = 2; // make sure to set this to the correct pin, ignored for Esp8266
|
||||
|
||||
// for esp8266 the pin is ignored unless it is the bitbang method
|
||||
NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod> strip(PixelCount, PixelPin);
|
||||
|
||||
NeoGamma<NeoGammaDynamicTableMethod> colorGamma;
|
||||
|
||||
void DrawPixels(bool corrected, HslColor startColor, HslColor stopColor)
|
||||
{
|
||||
for (uint16_t index = 0; index < strip.PixelCount() - 1; index++)
|
||||
{
|
||||
float progress = index / static_cast<float>(strip.PixelCount() - 2);
|
||||
RgbColor color = HslColor::LinearBlend<NeoHueBlendShortestDistance>(startColor, stopColor, progress);
|
||||
if (corrected)
|
||||
{
|
||||
color = colorGamma.Correct(color);
|
||||
}
|
||||
strip.SetPixelColor(index, color);
|
||||
}
|
||||
|
||||
// use the last pixel to indicate if we are showing corrected colors or not
|
||||
if (corrected)
|
||||
{
|
||||
strip.SetPixelColor(strip.PixelCount() - 1, RgbColor(64));
|
||||
}
|
||||
else
|
||||
{
|
||||
strip.SetPixelColor(strip.PixelCount() - 1, RgbColor(0));
|
||||
}
|
||||
|
||||
strip.Show();
|
||||
}
|
||||
|
||||
|
||||
float GammaCalc(float unitValue)
|
||||
{
|
||||
// we will use CieLab gamma equation for our custom table
|
||||
return NeoEase::GammaCieLab(unitValue);
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
// initialize the internal gamma table using our GammaCalc function
|
||||
// if you plan on using 16bit element colors, add a second optional arg as `true`
|
||||
NeoGammaDynamicTableMethod::Initialize(GammaCalc); // , true);
|
||||
|
||||
strip.Begin();
|
||||
strip.Show();
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
HslColor startColor;
|
||||
HslColor stopColor;
|
||||
|
||||
// red color
|
||||
startColor = HslColor(0.0f, 1.0f, 0.0f);
|
||||
stopColor = HslColor(0.0f, 1.0f, 0.5f);
|
||||
DrawPixels(true, startColor, stopColor);
|
||||
delay(5000);
|
||||
DrawPixels(false, startColor, stopColor);
|
||||
delay(5000);
|
||||
|
||||
// green color
|
||||
startColor = HslColor(0.33f, 1.0f, 0.0f);
|
||||
stopColor = HslColor(0.33f, 1.0f, 0.5f);
|
||||
DrawPixels(true, startColor, stopColor);
|
||||
delay(5000);
|
||||
DrawPixels(false, startColor, stopColor);
|
||||
delay(5000);
|
||||
|
||||
// blue color
|
||||
startColor = HslColor(0.66f, 1.0f, 0.0f);
|
||||
stopColor = HslColor(0.66f, 1.0f, 0.5f);
|
||||
DrawPixels(true, startColor, stopColor);
|
||||
delay(5000);
|
||||
DrawPixels(false, startColor, stopColor);
|
||||
delay(5000);
|
||||
|
||||
// white color
|
||||
startColor = HslColor(0.0f, 0.0f, 0.0f);
|
||||
stopColor = HslColor(0.0f, 0.0f, 0.5f);
|
||||
DrawPixels(true, startColor, stopColor);
|
||||
delay(5000);
|
||||
DrawPixels(false, startColor, stopColor);
|
||||
delay(5000);
|
||||
}
|
@@ -47,5 +47,6 @@ License along with NeoPixel. If not, see
|
||||
#include "colors/NeoGammaEquationMethod.h"
|
||||
#include "colors/NeoGammaCieLabEquationMethod.h"
|
||||
#include "colors/NeoGammaTableMethod.h"
|
||||
#include "colors/NeoGammaDynamicTableMethod.h"
|
||||
#include "colors/NeoGammaNullMethod.h"
|
||||
#include "colors/NeoGammaInvertMethod.h"
|
34
src/internal/colors/NeoGammaDynamicTableMethod.cpp
Normal file
34
src/internal/colors/NeoGammaDynamicTableMethod.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
NeoGamma classes are used to correct RGB colors for human eye gamma levels
|
||||
|
||||
Written by Michael C. Miller.
|
||||
|
||||
I invest time and resources providing this open source code,
|
||||
please support me by dontating (see https://github.com/Makuna/NeoPixelBus)
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
This file is part of the Makuna/NeoPixelBus library.
|
||||
|
||||
NeoPixelBus is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as
|
||||
published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
NeoPixelBus is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with NeoPixel. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "../NeoUtil.h"
|
||||
#include "NeoGammaDynamicTableMethod.h"
|
||||
|
||||
|
||||
uint8_t NeoGammaDynamicTableMethod::_table[256] = { 0 };
|
||||
NeoGammaDynamicTableMethod::NeoGamma16LowHint* NeoGammaDynamicTableMethod::_hints = nullptr;
|
||||
uint8_t NeoGammaDynamicTableMethod::_hintsCount = 0;
|
224
src/internal/colors/NeoGammaDynamicTableMethod.h
Normal file
224
src/internal/colors/NeoGammaDynamicTableMethod.h
Normal file
@@ -0,0 +1,224 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
NeoGammaDynamicTableMethod class is used to correct RGB colors for human eye gamma levels equally
|
||||
across all color channels
|
||||
|
||||
Written by Michael C. Miller.
|
||||
|
||||
I invest time and resources providing this open source code,
|
||||
please support me by dontating (see https://github.com/Makuna/NeoPixelBus)
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
This file is part of the Makuna/NeoPixelBus library.
|
||||
|
||||
NeoPixelBus is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as
|
||||
published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
NeoPixelBus is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with NeoPixel. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------*/
|
||||
#pragma once
|
||||
|
||||
#if defined(NEOPIXEBUS_NO_STL)
|
||||
|
||||
typedef float(*GammaCalcFunction)(float unitValue);
|
||||
|
||||
#else
|
||||
|
||||
#undef max
|
||||
#undef min
|
||||
#include <functional>
|
||||
typedef std::function<float(float unitValue)> GammaCalcFunction;
|
||||
|
||||
#endif
|
||||
|
||||
class NeoGammaDynamicTableMethod
|
||||
{
|
||||
protected:
|
||||
struct NeoGamma16LowHint
|
||||
{
|
||||
uint8_t pos;
|
||||
uint8_t count;
|
||||
};
|
||||
|
||||
public:
|
||||
static uint8_t Correct(uint8_t value)
|
||||
{
|
||||
return _table[value];
|
||||
}
|
||||
|
||||
static uint16_t Correct(uint16_t value)
|
||||
{
|
||||
// since a single monolithic table would be an unreasonable memory usage
|
||||
// this will use a hybrid of two tables, the base 255 table for the hibyte
|
||||
// and a smaller table with hints on how to use the table for certain values
|
||||
// and the left over values some simple calculations to approximate the final
|
||||
// 16 bit value as compared to the equation
|
||||
uint8_t hi = (value >> 8);
|
||||
uint16_t lo = (value & 0x00ff);
|
||||
uint8_t hiResult = _table[hi];
|
||||
uint16_t lowResult = 0;
|
||||
|
||||
if (hi < _hintsCount)
|
||||
{
|
||||
// use _hints table to calculate a reasonable lowbyte
|
||||
lowResult = (lo + _hints[hi].pos * 256) / _hints[hi].count;
|
||||
}
|
||||
else if (hi == 255)
|
||||
{
|
||||
// last entry is always linear
|
||||
lowResult = lo;
|
||||
}
|
||||
else
|
||||
{
|
||||
// check the _table for duplicate or jumps to adjust the range of lowbyte
|
||||
if (hiResult == _table[hi - 1])
|
||||
{
|
||||
// this result is an upper duplicate
|
||||
lowResult = (lo >> 1) | 0x80; // lo / 2 + 128
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t delta = _table[hi + 1] - hiResult;
|
||||
|
||||
if (delta == 0)
|
||||
{
|
||||
// this result is a lower duplicate
|
||||
lowResult = (lo >> 1); // lo / 2
|
||||
}
|
||||
else if (delta == 1)
|
||||
{
|
||||
// this result is incremental and thus linear
|
||||
lowResult = lo;
|
||||
}
|
||||
else
|
||||
{
|
||||
// this result jumps by more than one, so need to spread across
|
||||
lowResult = delta * lo;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return (static_cast<uint16_t>(hiResult) << 8) + lowResult;
|
||||
}
|
||||
|
||||
static void Initialize(GammaCalcFunction calc, bool optimize16Bit = false)
|
||||
{
|
||||
// first, iterate and fill 8 bit table
|
||||
for (uint16_t entry = 0; entry < 256; entry++)
|
||||
{
|
||||
_table[entry] = static_cast<uint8_t>(255.0f * calc(entry / 255.0f) + 0.5f);
|
||||
}
|
||||
|
||||
if (!optimize16Bit)
|
||||
{
|
||||
// no optimization, so no 16 bit
|
||||
_hints = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
NeoGamma16LowHint hints[256];
|
||||
|
||||
// now walk table creating an optimized hint table for 16bit
|
||||
// approximation
|
||||
// There is an assumption that lower values have series of duplicates and
|
||||
// upper values at worst have two values the same
|
||||
//
|
||||
uint16_t entryStart = 0;
|
||||
uint16_t entryEnd = 0;
|
||||
uint16_t entryLastTriplet = 0;
|
||||
|
||||
while (entryStart < 255)
|
||||
{
|
||||
uint8_t value = _table[entryStart];
|
||||
|
||||
while (value == _table[entryEnd] && entryEnd < 255)
|
||||
{
|
||||
entryEnd++;
|
||||
}
|
||||
|
||||
if (entryEnd == entryStart)
|
||||
{
|
||||
// no more duplicates, no need to continue
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t pos = 0;
|
||||
uint8_t count = entryEnd - entryStart;
|
||||
|
||||
if (count >= 3)
|
||||
{
|
||||
// remember the last triplet + series
|
||||
// as we don't need hints after this
|
||||
// there can be paired duplicates after and before this
|
||||
entryLastTriplet = entryEnd;
|
||||
}
|
||||
|
||||
// fill hints with known duplicate value
|
||||
while (entryStart != entryEnd)
|
||||
{
|
||||
hints[entryStart].count = count;
|
||||
hints[entryStart].pos = pos;
|
||||
entryStart++;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
// create static hint table and copy temp table to it
|
||||
_hintsCount = entryLastTriplet; // only need to last triplet
|
||||
_hints = new NeoGamma16LowHint[_hintsCount];
|
||||
memcpy(_hints, hints, sizeof(NeoGamma16LowHint) * _hintsCount);
|
||||
}
|
||||
}
|
||||
|
||||
// SerialDumpTables is used if you want to generate your own static gamma table class
|
||||
// rather than use this dynamically generated table. Just capture the serial output
|
||||
// and use as your initializers for your tables
|
||||
static void SerialDumpTables()
|
||||
{
|
||||
Serial.println();
|
||||
Serial.println("8 bit:");
|
||||
for (uint16_t entry = 0; entry < 256; entry++)
|
||||
{
|
||||
if (entry % 16 == 0)
|
||||
{
|
||||
Serial.println();
|
||||
}
|
||||
Serial.print(_table[entry]);
|
||||
Serial.print(", ");
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
Serial.println();
|
||||
Serial.print("16 bit: hintsCount = ");
|
||||
Serial.println(_hintsCount);
|
||||
if (_hints)
|
||||
{
|
||||
for (uint8_t hint = 0; hint < _hintsCount; hint++)
|
||||
{
|
||||
if (hint % 16 == 0)
|
||||
{
|
||||
Serial.println();
|
||||
}
|
||||
Serial.print("{");
|
||||
Serial.print(_hints[hint].pos);
|
||||
Serial.print(",");
|
||||
Serial.print(_hints[hint].count);
|
||||
Serial.print("}, ");
|
||||
}
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
private:
|
||||
static uint8_t _table[256];
|
||||
static NeoGamma16LowHint* _hints;
|
||||
static uint8_t _hintsCount;
|
||||
};
|
Reference in New Issue
Block a user