2021-01-02 18:26:24 +00:00
|
|
|
/*
|
|
|
|
xdrv_04_light_utils.ino - Converter functions for lights
|
|
|
|
|
|
|
|
Copyright (C) 2020 Theo Arends
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// This file is compiled even if `USE_LIGHT` is not defined and provides
|
|
|
|
// general purpose converter fucntions
|
|
|
|
|
|
|
|
|
|
|
|
// New version of Gamma correction compute
|
|
|
|
// Instead of a table, we do a multi-linear approximation, which is close enough
|
|
|
|
// At low levels, the slope is a bit higher than actual gamma, to make changes smoother
|
|
|
|
// Internal resolution is 10 bits.
|
|
|
|
|
|
|
|
typedef struct gamma_table_t {
|
|
|
|
uint16_t to_src;
|
|
|
|
uint16_t to_gamma;
|
|
|
|
} gamma_table_t;
|
|
|
|
|
2022-11-24 08:37:07 +00:00
|
|
|
const gamma_table_t ac_dimmer_table[] = { // don't put in PROGMEM for performance reasons
|
2022-11-27 16:14:51 +00:00
|
|
|
{ 0, 0 },
|
|
|
|
{ 10, 64 },
|
2022-11-30 08:15:34 +00:00
|
|
|
{ 50, 175 },
|
|
|
|
{ 100, 235 },
|
|
|
|
{ 500, 485 },
|
|
|
|
{ 900, 704 },
|
|
|
|
{ 950, 748 },
|
|
|
|
{ 990, 850 },
|
2023-04-25 12:46:19 +01:00
|
|
|
{ 1023, 1023 },
|
2022-11-24 08:37:07 +00:00
|
|
|
{ 0xFFFF, 0xFFFF } // fail-safe if out of range
|
|
|
|
};
|
|
|
|
|
2021-01-02 18:26:24 +00:00
|
|
|
const gamma_table_t gamma_table[] = { // don't put in PROGMEM for performance reasons
|
|
|
|
{ 1, 1 },
|
|
|
|
{ 4, 1 },
|
|
|
|
{ 209, 13 },
|
|
|
|
{ 312, 41 },
|
|
|
|
{ 457, 106 },
|
|
|
|
{ 626, 261 },
|
|
|
|
{ 762, 450 },
|
|
|
|
{ 895, 703 },
|
|
|
|
{ 1023, 1023 },
|
|
|
|
{ 0xFFFF, 0xFFFF } // fail-safe if out of range
|
|
|
|
};
|
|
|
|
|
|
|
|
// simplified Gamma table for Fade, cheating a little at low brightness
|
|
|
|
const gamma_table_t gamma_table_fast[] = {
|
|
|
|
{ 384, 192 },
|
|
|
|
{ 768, 576 },
|
|
|
|
{ 1023, 1023 },
|
|
|
|
{ 0xFFFF, 0xFFFF } // fail-safe if out of range
|
|
|
|
};
|
|
|
|
|
|
|
|
// For reference, below are the computed gamma tables, via ledGamma()
|
|
|
|
// for 8 bits output:
|
|
|
|
// 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
|
|
// 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
|
|
|
|
// 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3,
|
|
|
|
// 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6,
|
|
|
|
// 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 11,
|
|
|
|
// 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18,
|
|
|
|
// 18, 19, 19, 20, 20, 21, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25,
|
|
|
|
// 25, 26, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 37, 38,
|
|
|
|
// 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, 51, 52, 53,
|
|
|
|
// 54, 55, 56, 57, 58, 59, 60, 61, 61, 62, 63, 64, 65, 67, 68, 69,
|
|
|
|
// 71, 72, 73, 75, 76, 78, 79, 80, 82, 83, 85, 86, 87, 89, 90, 91,
|
|
|
|
// 93, 94, 95, 97, 98,100,101,102,104,105,107,108,109,111,112,114,
|
|
|
|
// 116,118,120,122,124,125,127,129,131,133,135,137,139,141,143,144,
|
|
|
|
// 146,148,150,152,154,156,158,160,162,164,166,168,170,171,173,175,
|
|
|
|
// 178,180,183,185,188,190,193,195,198,200,203,205,208,210,213,215,
|
|
|
|
// 218,220,223,225,228,230,233,235,238,240,243,245,248,250,253,255
|
|
|
|
//
|
|
|
|
// and for 10 bits output:
|
|
|
|
// 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4,
|
|
|
|
// 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8,
|
|
|
|
// 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12,
|
|
|
|
// 12, 12, 13, 13, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25,
|
|
|
|
// 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43,
|
|
|
|
// 45, 47, 49, 50, 52, 54, 56, 58, 59, 61, 63, 65, 67, 68, 70, 72,
|
|
|
|
// 74, 76, 77, 79, 81, 83, 84, 86, 88, 90, 92, 93, 95, 97, 99, 101,
|
|
|
|
// 102, 104, 106, 110, 113, 117, 121, 124, 128, 132, 135, 139, 143, 146, 150, 154,
|
|
|
|
// 158, 162, 166, 169, 173, 177, 180, 184, 188, 191, 195, 199, 202, 206, 210, 213,
|
|
|
|
// 217, 221, 224, 228, 232, 235, 239, 243, 246, 250, 254, 257, 261, 267, 272, 278,
|
|
|
|
// 283, 289, 294, 300, 305, 311, 317, 322, 328, 333, 339, 344, 350, 356, 361, 367,
|
|
|
|
// 372, 378, 383, 389, 394, 400, 406, 411, 417, 422, 428, 433, 439, 444, 450, 458,
|
|
|
|
// 465, 473, 480, 488, 496, 503, 511, 518, 526, 534, 541, 549, 557, 564, 572, 579,
|
|
|
|
// 587, 595, 602, 610, 617, 627, 635, 642, 650, 657, 665, 673, 680, 688, 695, 703,
|
|
|
|
// 713, 723, 733, 743, 753, 763, 773, 783, 793, 803, 813, 823, 833, 843, 853, 863,
|
|
|
|
// 873, 883, 893, 903, 913, 923, 933, 943, 953, 963, 973, 983, 993,1003,1013,1023
|
|
|
|
//
|
|
|
|
// Output for Dimmer 0..100 values
|
|
|
|
// 0, 1, 2, 3, 3, 4, 4, 5, 5, 6, 7, 7, 8, 8, 9,
|
|
|
|
// 10, 10, 11, 12, 12, 13, 15, 17, 21, 23, 26, 28, 31, 34, 37,
|
|
|
|
// 40, 43, 49, 52, 58, 61, 67, 70, 76, 79, 84, 90, 93, 99,102,
|
|
|
|
// 110,117,128,135,146,158,166,177,184,195,202,213,221,232,239,
|
|
|
|
// 250,261,272,289,300,317,328,344,356,372,389,400,417,428,444,
|
|
|
|
// 458,480,496,518,534,557,579,595,617,635,657,673,695,713,743,
|
|
|
|
// 773,793,823,843,873,893,923,943,973,993,1023
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Color converters
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
// new version with only integer computing
|
|
|
|
// brightness is not needed, it is controlled via Dimmer
|
|
|
|
void RgbToHsb(uint8_t ir, uint8_t ig, uint8_t ib, uint16_t *r_hue, uint8_t *r_sat, uint8_t *r_bri) {
|
|
|
|
uint32_t r = ir;
|
|
|
|
uint32_t g = ig;
|
|
|
|
uint32_t b = ib;
|
|
|
|
uint32_t max = (r > g && r > b) ? r : (g > b) ? g : b; // 0..255
|
|
|
|
uint32_t min = (r < g && r < b) ? r : (g < b) ? g : b; // 0..255
|
|
|
|
uint32_t d = max - min; // 0..255
|
|
|
|
|
|
|
|
uint16_t hue = 0; // hue value in degrees ranges from 0 to 359
|
|
|
|
uint8_t sat = 0; // 0..255
|
|
|
|
uint8_t bri = max; // 0..255
|
|
|
|
|
|
|
|
if (d != 0) {
|
|
|
|
sat = changeUIntScale(d, 0, max, 0, 255);
|
|
|
|
if (r == max) {
|
|
|
|
hue = (g > b) ? changeUIntScale(g-b,0,d,0,60) : 360 - changeUIntScale(b-g,0,d,0,60);
|
|
|
|
} else if (g == max) {
|
|
|
|
hue = (b > r) ? 120 + changeUIntScale(b-r,0,d,0,60) : 120 - changeUIntScale(r-b,0,d,0,60);
|
|
|
|
} else {
|
|
|
|
hue = (r > g) ? 240 + changeUIntScale(r-g,0,d,0,60) : 240 - changeUIntScale(g-r,0,d,0,60);
|
|
|
|
}
|
|
|
|
hue = hue % 360; // 0..359
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r_hue) *r_hue = hue;
|
|
|
|
if (r_sat) *r_sat = sat;
|
|
|
|
if (r_bri) *r_bri = bri;
|
2021-01-23 15:26:23 +00:00
|
|
|
//AddLog(LOG_LEVEL_DEBUG_MORE, "RgbToHsb rgb (%d %d %d) hsb (%d %d %d)", r, g, b, hue, sat, bri);
|
2021-01-02 18:26:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void HsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b) {
|
|
|
|
uint32_t r = 255; // default to white
|
|
|
|
uint32_t g = 255;
|
|
|
|
uint32_t b = 255;
|
|
|
|
// we take brightness at 100%, brightness should be set separately
|
|
|
|
hue = hue % 360; // normalize to 0..359
|
|
|
|
|
|
|
|
if (sat > 0) {
|
|
|
|
uint32_t i = hue / 60; // quadrant 0..5
|
|
|
|
uint32_t f = hue % 60; // 0..59
|
|
|
|
uint32_t q = 255 - changeUIntScale(f, 0, 60, 0, sat); // 0..59
|
|
|
|
uint32_t p = 255 - sat;
|
|
|
|
uint32_t t = 255 - changeUIntScale(60 - f, 0, 60, 0, sat);
|
|
|
|
|
|
|
|
switch (i) {
|
|
|
|
case 0:
|
|
|
|
//r = 255;
|
|
|
|
g = t;
|
|
|
|
b = p;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
r = q;
|
|
|
|
//g = 255;
|
|
|
|
b = p;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
r = p;
|
|
|
|
//g = 255;
|
|
|
|
b = t;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
r = p;
|
|
|
|
g = q;
|
|
|
|
//b = 255;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
r = t;
|
|
|
|
g = p;
|
|
|
|
//b = 255;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
//r = 255;
|
|
|
|
g = p;
|
|
|
|
b = q;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (r_r) *r_r = r;
|
|
|
|
if (r_g) *r_g = g;
|
|
|
|
if (r_b) *r_b = b;
|
|
|
|
}
|
|
|
|
|
2022-02-08 15:39:59 +00:00
|
|
|
#ifdef ESP8266
|
|
|
|
#define POW FastPrecisePowf
|
|
|
|
#else
|
|
|
|
#define POW powf
|
|
|
|
#endif
|
2021-01-02 18:26:24 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// Matrix 3x3 multiplied to a 3 vector, result in a 3 vector
|
|
|
|
//
|
|
|
|
void mat3x3(const float *mat33, const float *vec3, float *res3) {
|
|
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
|
|
const float * v = vec3;
|
|
|
|
*res3 = 0.0f;
|
|
|
|
for (uint32_t j = 0; j < 3; j++) {
|
|
|
|
*res3 += *mat33++ * *v++;
|
|
|
|
}
|
|
|
|
res3++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RgbToXy(uint8_t i_r, uint8_t i_g, uint8_t i_b, float *r_x, float *r_y) {
|
|
|
|
float x = 0.31271f; // default medium white
|
|
|
|
float y = 0.32902f;
|
|
|
|
|
|
|
|
if (i_r + i_b + i_g > 0) {
|
|
|
|
float rgb[3] = { (float)i_r, (float)i_g, (float)i_b };
|
|
|
|
// https://gist.github.com/popcorn245/30afa0f98eea1c2fd34d
|
|
|
|
// Gamma correction
|
|
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
|
|
rgb[i] = rgb[i] / 255.0f;
|
|
|
|
rgb[i] = (rgb[i] > 0.04045f) ? POW((rgb[i] + 0.055f) / (1.0f + 0.055f), 2.4f) : (rgb[i] / 12.92f);
|
|
|
|
}
|
|
|
|
|
|
|
|
// conversion to X, Y, Z
|
|
|
|
// Y is also the Luminance
|
|
|
|
float XYZ[3];
|
|
|
|
static const float XYZ_factors[] = { 0.649926f, 0.103455f, 0.197109f,
|
|
|
|
0.234327f, 0.743075f, 0.022598f,
|
|
|
|
0.000000f, 0.053077f, 1.035763f };
|
|
|
|
mat3x3(XYZ_factors, rgb, XYZ);
|
|
|
|
|
|
|
|
float XYZ_sum = XYZ[0] + XYZ[1] + XYZ[2];
|
2022-02-08 15:39:59 +00:00
|
|
|
// AddLog(LOG_LEVEL_DEBUG, ">>>: RgbToXy X=%5_f Y=%5_f Z=%5_f TOTAL=%5_f", &XYZ[0], &XYZ[1], &XYZ[2], &XYZ_sum);
|
2021-01-02 18:26:24 +00:00
|
|
|
x = XYZ[0] / XYZ_sum;
|
|
|
|
y = XYZ[1] / XYZ_sum;
|
|
|
|
// we keep the raw gamut, one nice thing could be to convert to a narrower gamut
|
|
|
|
}
|
|
|
|
if (r_x) *r_x = x;
|
|
|
|
if (r_y) *r_y = y;
|
|
|
|
}
|
|
|
|
|
|
|
|
void XyToRgb(float x, float y, uint8_t *rr, uint8_t *rg, uint8_t *rb)
|
|
|
|
{
|
|
|
|
float XYZ[3], rgb[3];
|
|
|
|
x = (x > 0.99f ? 0.99f : (x < 0.01f ? 0.01f : x));
|
|
|
|
y = (y > 0.99f ? 0.99f : (y < 0.01f ? 0.01f : y));
|
|
|
|
float z = 1.0f - x - y;
|
2022-02-08 15:39:59 +00:00
|
|
|
// AddLog(LOG_LEVEL_DEBUG, ">>>: XyToRgb x=%5_f y=%5_f z=%5_f", &x, &y, &z);
|
2021-01-02 18:26:24 +00:00
|
|
|
XYZ[0] = x / y;
|
|
|
|
XYZ[1] = 1.0f;
|
|
|
|
XYZ[2] = z / y;
|
|
|
|
|
2022-02-08 15:39:59 +00:00
|
|
|
// AddLog(LOG_LEVEL_DEBUG, ">>>: XyToRgb X=%5_f Y=%5_f Z=%5_f", &XYZ[0], &XYZ[1], &XYZ[2]);
|
|
|
|
static const float rgb_factors[] = { 1.612f, -0.203f, -0.302f,
|
|
|
|
-0.509f, 1.412f, 0.066f,
|
|
|
|
0.026f, -0.072f, 0.962f };
|
2021-01-02 18:26:24 +00:00
|
|
|
mat3x3(rgb_factors, XYZ, rgb);
|
2022-02-08 15:39:59 +00:00
|
|
|
// AddLog(LOG_LEVEL_DEBUG, ">>>: XyToRgb rr=%5_f gg=%5_f bb=%5_f", &rgb[0], &rgb[1], &rgb[2]);
|
2021-01-02 18:26:24 +00:00
|
|
|
float max = (rgb[0] > rgb[1] && rgb[0] > rgb[2]) ? rgb[0] : (rgb[1] > rgb[2]) ? rgb[1] : rgb[2];
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
|
|
rgb[i] = rgb[i] / max; // normalize to max == 1.0
|
|
|
|
rgb[i] = (rgb[i] <= 0.0031308f) ? 12.92f * rgb[i] : 1.055f * POW(rgb[i], (1.0f / 2.4f)) - 0.055f; // gamma
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t irgb[3];
|
|
|
|
for (uint32_t i = 0; i < 3; i++) {
|
|
|
|
irgb[i] = rgb[i] * 255.0f + 0.5f;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rr) { *rr = (irgb[0] > 255 ? 255: (irgb[0] < 0 ? 0 : irgb[0])); }
|
|
|
|
if (rg) { *rg = (irgb[1] > 255 ? 255: (irgb[1] < 0 ? 0 : irgb[1])); }
|
|
|
|
if (rb) { *rb = (irgb[2] > 255 ? 255: (irgb[2] < 0 ? 0 : irgb[2])); }
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Change scales from 8 bits to 10 bits and vice versa
|
|
|
|
\*********************************************************************************************/
|
|
|
|
// 8 to 10 to 8 is guaranteed to give the same result
|
|
|
|
uint16_t change8to10(uint8_t v) {
|
|
|
|
return changeUIntScale(v, 0, 255, 0, 1023);
|
|
|
|
}
|
|
|
|
// change from 10 bits to 8 bits, but any non-zero input will be non-zero
|
|
|
|
uint8_t change10to8(uint16_t v) {
|
|
|
|
return (0 == v) ? 0 : changeUIntScale(v, 4, 1023, 1, 255);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Gamma correction
|
|
|
|
\*********************************************************************************************/
|
|
|
|
// Calculate the gamma corrected value for LEDS
|
|
|
|
uint16_t ledGamma_internal(uint16_t v, const struct gamma_table_t *gt_ptr) {
|
|
|
|
uint16_t from_src = 0;
|
|
|
|
uint16_t from_gamma = 0;
|
|
|
|
|
|
|
|
for (const gamma_table_t *gt = gt_ptr; ; gt++) {
|
|
|
|
uint16_t to_src = gt->to_src;
|
|
|
|
uint16_t to_gamma = gt->to_gamma;
|
|
|
|
if (v <= to_src) {
|
|
|
|
return changeUIntScale(v, from_src, to_src, from_gamma, to_gamma);
|
|
|
|
}
|
|
|
|
from_src = to_src;
|
|
|
|
from_gamma = to_gamma;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Calculate the reverse gamma value for LEDS
|
|
|
|
uint16_t ledGammaReverse_internal(uint16_t vg, const struct gamma_table_t *gt_ptr) {
|
|
|
|
uint16_t from_src = 0;
|
|
|
|
uint16_t from_gamma = 0;
|
|
|
|
|
|
|
|
for (const gamma_table_t *gt = gt_ptr; ; gt++) {
|
|
|
|
uint16_t to_src = gt->to_src;
|
|
|
|
uint16_t to_gamma = gt->to_gamma;
|
|
|
|
if (vg <= to_gamma) {
|
|
|
|
return changeUIntScale(vg, from_gamma, to_gamma, from_src, to_src);
|
|
|
|
}
|
|
|
|
from_src = to_src;
|
|
|
|
from_gamma = to_gamma;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-24 08:37:07 +00:00
|
|
|
// 10 bits power select to 10 bits timing based on sinus curve
|
|
|
|
uint16_t ac_zero_cross_power(uint16_t v) {
|
2022-11-27 16:14:51 +00:00
|
|
|
return ledGamma_internal(v, ac_dimmer_table);
|
2022-11-24 08:37:07 +00:00
|
|
|
}
|
|
|
|
|
2021-01-02 18:26:24 +00:00
|
|
|
// 10 bits in, 10 bits out
|
|
|
|
uint16_t ledGamma10_10(uint16_t v) {
|
|
|
|
return ledGamma_internal(v, gamma_table);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 10 bits resolution, 8 bits in
|
|
|
|
uint16_t ledGamma10(uint8_t v) {
|
|
|
|
return ledGamma10_10(change8to10(v));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Legacy function
|
|
|
|
uint8_t ledGamma(uint8_t v) {
|
|
|
|
return change10to8(ledGamma10(v));
|
|
|
|
}
|
|
|
|
|
2021-04-04 18:36:25 +01:00
|
|
|
// Reverse 10 bits
|
|
|
|
uint16_t ledGammaReverse(uint16_t vg) {
|
|
|
|
return ledGammaReverse_internal(vg, gamma_table);
|
|
|
|
}
|
|
|
|
|
2021-01-02 18:26:24 +00:00
|
|
|
// Fast versions for Fading
|
|
|
|
uint16_t ledGammaFast(uint16_t v) {
|
|
|
|
return ledGamma_internal(v, gamma_table_fast);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t leddGammaReverseFast(uint16_t vg) {
|
|
|
|
return ledGammaReverse_internal(vg, gamma_table_fast);
|
2022-11-24 08:37:07 +00:00
|
|
|
}
|