From bca8166b68b2fa06c7be30ff7595206ac4d45dac Mon Sep 17 00:00:00 2001 From: rob tillaart Date: Tue, 26 Jul 2022 13:16:03 +0200 Subject: [PATCH] 0.3.0 GAMMA --- libraries/GAMMA/README.md | 22 ++++---- .../GammaErrorAnalysis/GammaErrorAnalysis.ino | 24 +++++---- .../error_analysis_0.2.2.txt | 19 +++++++ .../error_analysis_0.3.0.txt | 19 +++++++ .../GammaPerformance/performance_0.3.0.txt | 34 +++++++++++++ libraries/GAMMA/gamma.cpp | 51 +++++++++++++++---- libraries/GAMMA/gamma.h | 19 ++++--- libraries/GAMMA/keywords.txt | 2 + libraries/GAMMA/library.json | 2 +- libraries/GAMMA/library.properties | 2 +- libraries/GAMMA/test/unit_test_001.cpp | 42 +++++++++++---- 11 files changed, 187 insertions(+), 49 deletions(-) create mode 100644 libraries/GAMMA/examples/GammaErrorAnalysis/error_analysis_0.2.2.txt create mode 100644 libraries/GAMMA/examples/GammaErrorAnalysis/error_analysis_0.3.0.txt create mode 100644 libraries/GAMMA/examples/GammaPerformance/performance_0.3.0.txt diff --git a/libraries/GAMMA/README.md b/libraries/GAMMA/README.md index 7c678182..f9cadcd7 100644 --- a/libraries/GAMMA/README.md +++ b/libraries/GAMMA/README.md @@ -29,7 +29,7 @@ In short, choose the size that fits your application. The library has a **setGamma(float gamma)** function that allows an application to change the gamma value runtime. -This allows adjustments that a fixed table does not have. +This allows adjustments that are not possible with a fixed table. The class provides **dump()** to create a table e.g. to place in PROGMEM. Since 0.2.2 the library also has **dumpArray()** to generate a C-style array. @@ -46,18 +46,22 @@ array as parameter. The default for size = 32 as this is a good balance between and size of the internal array. The size parameter must be in {2, 4, 8, 16, 32, 64, 128, 256 }. - **~GAMMA()** destructor. -- **void begin()** The internal array is allocated and initialized with a gamma == 2.8. +- **bool begin()** The internal array is allocated and initialized with a gamma == 2.8. This is an often used value to adjust light to human eye responses. Note that **begin()** must be called before any other function. +Returns false if allocation fails. - **void setGamma(float gamma)** calculates and fills the array with new values. This can be done runtime so runtime adjustment of gamma mapping is possible. This calculation are relative expensive and takes quite some time (depending on size). If the array already is calculated for gamma, the calculation will be skipped. The parameter **gamma** must be > 0. The value 1 gives an 1:1 mapping. +Returns false if gamma <= 0 or if no table is allocated. - **float getGamma()** returns the set gamma value. -- **uint8_t operator \[\]** allows the GAMMA object to be accessed as an array. +- **uint8_t operator \[uint8_t index\]** allows the GAMMA object to be accessed as an array. like ```x = G[40];``` Makes it easy to switch with a real array. -The value returned is in the range 0 .. 255, so the user may need to scale it e.g. to 0.0 - 1.0 +The value returned is in the range 0 .. 255, so the user may need to scale it e.g. to 0.0 - 1.0. +Note: if internal table not allocated the function returns 0. +As this is a legitimate value the user should take care. ### Development functions @@ -66,9 +70,11 @@ The value returned is in the range 0 .. 255, so the user may need to scale it e. This is always a power of 2. - **uint16_t distinct()** returns the number of distinct values in the table. Especially with larger internal tables there will be duplicate numbers in the table. -- **void dump(Stream \*str = &Serial)** dumps the internal table to a stream, default Serial. +- **bool dump(Stream \*str = &Serial)** dumps the internal table to a stream, default Serial. Useful to create an array in RAM, PROGMEM, EEPROM, in a file or wherever. +Returns false if no table is allocated. - **void dumpArray(Stream \*str = &Serial)** dumps the internal table to a stream, default Serial, as a C-style array. See example. +Returns false if no table is allocated. ## Operation @@ -83,10 +89,8 @@ See example. - look for optimizations - getter \[\] - setGamma -> pow() is expensive -- improvements (0.3.0) - - return bool => begin() + setGamma() + dump()? - - check \_table != NULL in functions - - add gamma<=0 check in setGamma() - uint16 version? + - GAMMA16, GAMMA32, + - GAMMA_RGB ? - diff --git a/libraries/GAMMA/examples/GammaErrorAnalysis/GammaErrorAnalysis.ino b/libraries/GAMMA/examples/GammaErrorAnalysis/GammaErrorAnalysis.ino index 1e9494fa..c4933adb 100644 --- a/libraries/GAMMA/examples/GammaErrorAnalysis/GammaErrorAnalysis.ino +++ b/libraries/GAMMA/examples/GammaErrorAnalysis/GammaErrorAnalysis.ino @@ -19,6 +19,7 @@ GAMMA gt8(2); uint32_t start, d1; volatile int x; +int total = 0; void setup() { @@ -39,21 +40,22 @@ void setup() Serial.println("\nError Analysis 256 elements = reference\n"); Serial.println("Size\tErrors\tMaximum"); - test_error(gt1); - test_error(gt2); - test_error(gt3); - test_error(gt4); - test_error(gt5); - test_error(gt6); - test_error(gt7); - test_error(gt8); - Serial.println(); + total += test_error(gt1); + total += test_error(gt2); + total += test_error(gt3); + total += test_error(gt4); + total += test_error(gt5); + total += test_error(gt6); + total += test_error(gt7); +// total += test_error(gt8); + Serial.print("TOT\t"); + Serial.println(total); Serial.println("\ndone...\n"); } -void test_error(GAMMA gt) +int test_error(GAMMA gt) { int count = 0; int maxdiff = 0; @@ -70,6 +72,7 @@ void test_error(GAMMA gt) Serial.print(count); Serial.print('\t'); Serial.println(maxdiff); + return count; } @@ -79,4 +82,3 @@ void loop() // -- END OF FILE -- - diff --git a/libraries/GAMMA/examples/GammaErrorAnalysis/error_analysis_0.2.2.txt b/libraries/GAMMA/examples/GammaErrorAnalysis/error_analysis_0.2.2.txt new file mode 100644 index 00000000..bac10f17 --- /dev/null +++ b/libraries/GAMMA/examples/GammaErrorAnalysis/error_analysis_0.2.2.txt @@ -0,0 +1,19 @@ +Arduino UNO +IDE 1.8.19 + +GammaErrorAnalysis.ino +GAMMA_LIB_VERSION: 0.2.2 + +Error Analysis 256 elements = reference + +Size Errors Maximum +257 0 0 +129 29 1 +65 38 2 +33 46 2 +17 58 2 +9 177 2 +5 233 8 +TOT 581 + +done... \ No newline at end of file diff --git a/libraries/GAMMA/examples/GammaErrorAnalysis/error_analysis_0.3.0.txt b/libraries/GAMMA/examples/GammaErrorAnalysis/error_analysis_0.3.0.txt new file mode 100644 index 00000000..aedc2cd5 --- /dev/null +++ b/libraries/GAMMA/examples/GammaErrorAnalysis/error_analysis_0.3.0.txt @@ -0,0 +1,19 @@ +Arduino UNO +IDE 1.8.19 + +GammaErrorAnalysis.ino +GAMMA_LIB_VERSION: 0.3.0 + +Error Analysis 256 elements = reference + +Size Errors Maximum +257 0 0 +129 29 1 +65 34 2 +33 32 2 +17 46 2 +9 159 2 +5 235 8 +TOT 535 + +done... \ No newline at end of file diff --git a/libraries/GAMMA/examples/GammaPerformance/performance_0.3.0.txt b/libraries/GAMMA/examples/GammaPerformance/performance_0.3.0.txt new file mode 100644 index 00000000..9b2464f6 --- /dev/null +++ b/libraries/GAMMA/examples/GammaPerformance/performance_0.3.0.txt @@ -0,0 +1,34 @@ +Arduino UNO +IDE 1.8.19 + +GammaPerformance.ino +GAMMA_LIB_VERSION: 0.3.0 + +timing in microseconds + +SETGAMMA +SIZE TIME TIME per element +257 85872 334.13 +129 42696 330.98 +65 21172 325.72 +33 10420 315.76 +17 5052 297.18 + +SETGAMMA II +SIZE TIME TIME per element +257 8 0.03 +129 8 0.06 +65 8 0.12 +33 8 0.24 +17 8 0.47 + +GET[] +SIZE TIME TIME per element +257 460 1.80 +129 972 3.80 +65 1212 4.73 +33 1428 5.58 +17 1616 6.31 + + +done... diff --git a/libraries/GAMMA/gamma.cpp b/libraries/GAMMA/gamma.cpp index 2b109c9c..cc3d1160 100644 --- a/libraries/GAMMA/gamma.cpp +++ b/libraries/GAMMA/gamma.cpp @@ -1,7 +1,7 @@ // // FILE: gamma.cpp // AUTHOR: Rob Tillaart -// VERSION: 0.2.2 +// VERSION: 0.3.0 // DATE: 2020-08-08 // PURPOSE: Arduino Library to efficiently hold a gamma lookup table @@ -16,6 +16,13 @@ // add Stream parameter to dump() // add dumpArray(Stream) // fix distinct() +// +// 0.3.0 2022-07-26 change return type begin() + setGamma() +// add test gamma <=0 in setGamma() +// add _table == NULL tests +// fixed type of index in [] operator. +// adjust rounding in setGamma() to minimize errors. +// update build-CI #include "gamma.h" @@ -45,21 +52,25 @@ GAMMA::~GAMMA() }; -void GAMMA::begin() +bool GAMMA::begin() { if (_table == NULL) { _table = (uint8_t *)malloc(_size + 1); } + if (_table == NULL) return false; setGamma(2.8); + return true; }; -void GAMMA::setGamma(float gamma) +bool GAMMA::setGamma(float gamma) { + if (_table == NULL) return false; + if (gamma <= 0) return false; if (_gamma != gamma) { - yield(); // keep ESP happy + yield(); // try to keep ESP happy _gamma = gamma; // marginally faster // uint16_t iv = _interval; @@ -70,12 +81,15 @@ void GAMMA::setGamma(float gamma) // _table[i] = exp(x * _gamma) * 255 + 0.5; // } // REFERENCE - for (uint16_t i = 0; i < _size; i++) + // rounding factor 0.444 optimized with error example. + for (uint16_t i = 1; i < _size; i++) { - _table[i] = pow(i * _interval * (1.0/ 255.0), _gamma) * 255 + 0.5; + _table[i] = pow(i * _interval * (1.0/ 255.0), _gamma) * 255 + 0.444; } - _table[_size] = 255; // anchor for interpolation.. + _table[0] = 0; + _table[_size] = 255; // anchor for interpolation. } + return true; }; @@ -87,13 +101,17 @@ float GAMMA::getGamma() uint8_t GAMMA::operator[] (uint8_t index) { + // 0.3.0 _table test slows performance ~0.4 us. + if (_table == NULL) return 0; if (_interval == 1) return _table[index]; // else interpolate uint8_t i = index >> _shift; uint8_t m = index & _mask; // exact element shortcut if ( m == 0 ) return _table[i]; - // interpolation + // interpolation + // delta must be uint16_t to prevent overflow. (small tables) + // delta * m can be > 8 bit. uint16_t delta = _table[i+1] - _table[i]; delta = (delta * m + _interval/2) >> _shift; // == /_interval; return _table[i] + delta; @@ -120,17 +138,20 @@ uint16_t GAMMA::distinct() }; -void GAMMA::dump(Stream *str) +bool GAMMA::dump(Stream *str) { + if (_table == NULL) return false; for (uint16_t i = 0; i <= _size; i++) { str->println(_table[i]); } + return true; }; -void GAMMA::dumpArray(Stream *str) +bool GAMMA::dumpArray(Stream *str) { + if (_table == NULL) return false; str->println(); str->print("uint8_t gamma["); str->print(_size + 1); @@ -143,8 +164,18 @@ void GAMMA::dumpArray(Stream *str) if (i < _size) str->print(", "); } str->print("\n };\n\n"); + return true; }; +// performance investigation +// https://stackoverflow.com/questions/43429238/using-boost-cpp-int-for-functions-like-pow-and-rand +inline float GAMMA::fastPow(float a, float b) +{ + // reference + return pow(a, b); +} + + // -- END OF FILE -- diff --git a/libraries/GAMMA/gamma.h b/libraries/GAMMA/gamma.h index 638f5c90..b4513dd6 100644 --- a/libraries/GAMMA/gamma.h +++ b/libraries/GAMMA/gamma.h @@ -2,14 +2,14 @@ // // FILE: gamma.h // AUTHOR: Rob Tillaart -// VERSION: 0.2.2 +// VERSION: 0.3.0 // DATE: 2020-08-08 // PURPOSE: Arduino Library to efficiently hold a gamma lookup table #include "Arduino.h" -#define GAMMA_LIB_VERSION (F("0.2.2")) +#define GAMMA_LIB_VERSION (F("0.3.0")) #define GAMMA_DEFAULT_SIZE 32 #define GAMMA_MAX_SIZE 256 @@ -25,12 +25,15 @@ public: // allocates memory // sets default gamma = 2.8 - void begin(); + // Returns false if allocation fails + bool begin(); // CORE - void setGamma(float gamma); + // Returns false if gamma <= 0 + bool setGamma(float gamma); float getGamma(); // access values with index operator + // index = 0 .. size uint8_t operator[] (uint8_t index); // META INFO @@ -38,8 +41,8 @@ public: uint16_t distinct(); // DEBUG - void dump(Stream *str = &Serial); - void dumpArray(Stream *str = &Serial); + bool dump(Stream *str = &Serial); + bool dumpArray(Stream *str = &Serial); private: @@ -47,8 +50,10 @@ private: uint8_t _mask = 0; uint16_t _size = 0; uint8_t _interval = 0; - float _gamma = 0; + float _gamma = 1.0; // 1.0 == no gamma, linear. uint8_t * _table = NULL; + + float fastPow(float a, float b); }; diff --git a/libraries/GAMMA/keywords.txt b/libraries/GAMMA/keywords.txt index a039c333..0cb9f0f2 100644 --- a/libraries/GAMMA/keywords.txt +++ b/libraries/GAMMA/keywords.txt @@ -15,4 +15,6 @@ dumpArray KEYWORD2 # Constants (LITERAL1) GAMMA_LIB_VERSION LITERAL1 +GAMMA_DEFAULT_SIZE LITERAL1 +GAMMA_MAX_SIZE LITERAL1 diff --git a/libraries/GAMMA/library.json b/libraries/GAMMA/library.json index 6a1bd0a1..3ed5defb 100644 --- a/libraries/GAMMA/library.json +++ b/libraries/GAMMA/library.json @@ -15,7 +15,7 @@ "type": "git", "url": "https://github.com/RobTillaart/GAMMA.git" }, - "version": "0.2.2", + "version": "0.3.0", "license": "MIT", "frameworks": "arduino", "platforms": "*", diff --git a/libraries/GAMMA/library.properties b/libraries/GAMMA/library.properties index 94b71c1e..7d62e55d 100644 --- a/libraries/GAMMA/library.properties +++ b/libraries/GAMMA/library.properties @@ -1,5 +1,5 @@ name=GAMMA -version=0.2.2 +version=0.3.0 author=Rob Tillaart maintainer=Rob Tillaart sentence=Arduino Library for the GAMMA function diff --git a/libraries/GAMMA/test/unit_test_001.cpp b/libraries/GAMMA/test/unit_test_001.cpp index f382fee9..9e6a5fe7 100644 --- a/libraries/GAMMA/test/unit_test_001.cpp +++ b/libraries/GAMMA/test/unit_test_001.cpp @@ -49,43 +49,43 @@ unittest(test_constants) unittest(test_constructor) { GAMMA gt0; // uses default 32 size - gt0.begin(); + assertTrue(gt0.begin()); assertEqual(33, gt0.size()); assertEqualFloat(2.8, gt0.getGamma(), 0.0001); assertEqual(29, gt0.distinct()); GAMMA gt1(256); - gt1.begin(); + assertTrue(gt1.begin()); assertEqual(257, gt1.size()); assertEqualFloat(2.8, gt1.getGamma(), 0.0001); assertEqual(163, gt1.distinct()); GAMMA gt2(128); - gt2.begin(); + assertTrue(gt2.begin()); assertEqual(129, gt2.size()); assertEqualFloat(2.8, gt2.getGamma(), 0.0001); - assertEqual(98, gt2.distinct()); + assertEqual(97, gt2.distinct()); GAMMA gt3(64); - gt3.begin(); + assertTrue(gt3.begin()); assertEqual(65, gt3.size()); assertEqualFloat(2.8, gt3.getGamma(), 0.0001); assertEqual(54, gt3.distinct()); GAMMA gt4(32); // default - gt4.begin(); + assertTrue(gt4.begin()); assertEqual(33, gt4.size()); assertEqualFloat(2.8, gt4.getGamma(), 0.0001); assertEqual(29, gt4.distinct()); GAMMA gt5(16); - gt5.begin(); + assertTrue(gt5.begin()); assertEqual(17, gt5.size()); assertEqualFloat(2.8, gt5.getGamma(), 0.0001); assertEqual(16, gt5.distinct()); GAMMA gt6(8); - gt6.begin(); + assertTrue(gt6.begin()); assertEqual(9, gt6.size()); assertEqualFloat(2.8, gt6.getGamma(), 0.0001); assertEqual(9, gt6.distinct()); @@ -96,15 +96,37 @@ unittest(test_get_set) { GAMMA gt; // uses default 32 size - gt.begin(); + assertTrue(gt.begin()); for (int i = 1; i < 20; i++) { - gt.setGamma(i * 0.1); + assertTrue(gt.setGamma(i * 0.1)); assertEqualFloat(i * 0.1, gt.getGamma(), 0.001); } } +unittest(test_gamma_fail) +{ + GAMMA gt; // uses default 32 size + + // do not call begin() to force allocate error + assertFalse(gt.setGamma(3.14)); + assertEqualFloat(1.0, gt.getGamma(), 0.001); + + assertEqualFloat(0.0, gt[63], 0.0001); + assertFalse(gt.dump()); + assertFalse(gt.dumpArray()); + + // allocate + assertTrue(gt.begin()); + // still fails. + assertFalse(gt.setGamma(-2.0)); + // this works now + assertTrue(gt.setGamma(3.14)); + assertEqualFloat(3.14, gt.getGamma(), 0.001); +} + + unittest_main() // --------