diff --git a/examples/NeoPixelGamma/NeoPixelGamma.ino b/examples/gamma/NeoPixelGamma/NeoPixelGamma.ino similarity index 100% rename from examples/NeoPixelGamma/NeoPixelGamma.ino rename to examples/gamma/NeoPixelGamma/NeoPixelGamma.ino diff --git a/examples/gamma/NeoPixelGammaDynamic/NeoPixelGammaDynamic.ino b/examples/gamma/NeoPixelGammaDynamic/NeoPixelGammaDynamic.ino new file mode 100644 index 0000000..52e0d56 --- /dev/null +++ b/examples/gamma/NeoPixelGammaDynamic/NeoPixelGammaDynamic.ino @@ -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 +#include + +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 strip(PixelCount, PixelPin); + +NeoGamma colorGamma; + +void DrawPixels(bool corrected, HslColor startColor, HslColor stopColor) +{ + for (uint16_t index = 0; index < strip.PixelCount() - 1; index++) + { + float progress = index / static_cast(strip.PixelCount() - 2); + RgbColor color = HslColor::LinearBlend(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); +} \ No newline at end of file diff --git a/src/internal/NeoColors.h b/src/internal/NeoColors.h index 70c4ae6..72ccba4 100644 --- a/src/internal/NeoColors.h +++ b/src/internal/NeoColors.h @@ -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" \ No newline at end of file +#include "colors/NeoGammaInvertMethod.h" diff --git a/src/internal/colors/NeoGammaDynamicTableMethod.cpp b/src/internal/colors/NeoGammaDynamicTableMethod.cpp new file mode 100644 index 0000000..383b39f --- /dev/null +++ b/src/internal/colors/NeoGammaDynamicTableMethod.cpp @@ -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 +. +-------------------------------------------------------------------------*/ + +#include +#include "../NeoUtil.h" +#include "NeoGammaDynamicTableMethod.h" + + +uint8_t NeoGammaDynamicTableMethod::_table[256] = { 0 }; +NeoGammaDynamicTableMethod::NeoGamma16LowHint* NeoGammaDynamicTableMethod::_hints = nullptr; +uint8_t NeoGammaDynamicTableMethod::_hintsCount = 0; \ No newline at end of file diff --git a/src/internal/colors/NeoGammaDynamicTableMethod.h b/src/internal/colors/NeoGammaDynamicTableMethod.h new file mode 100644 index 0000000..d5db04a --- /dev/null +++ b/src/internal/colors/NeoGammaDynamicTableMethod.h @@ -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 +. +-------------------------------------------------------------------------*/ +#pragma once + +#if defined(NEOPIXEBUS_NO_STL) + +typedef float(*GammaCalcFunction)(float unitValue); + +#else + +#undef max +#undef min +#include +typedef std::function 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(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(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; +};