0.3.0 GAMMA

This commit is contained in:
rob tillaart 2022-07-26 13:16:03 +02:00
parent 2510474149
commit bca8166b68
11 changed files with 187 additions and 49 deletions

View File

@ -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 ?
-

View File

@ -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 --

View File

@ -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...

View File

@ -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...

View File

@ -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...

View File

@ -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 --

View File

@ -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);
};

View File

@ -15,4 +15,6 @@ dumpArray KEYWORD2
# Constants (LITERAL1)
GAMMA_LIB_VERSION LITERAL1
GAMMA_DEFAULT_SIZE LITERAL1
GAMMA_MAX_SIZE LITERAL1

View File

@ -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": "*",

View File

@ -1,5 +1,5 @@
name=GAMMA
version=0.2.2
version=0.3.0
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Arduino Library for the GAMMA function

View File

@ -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()
// --------