Browse Source

Add gamma correction and brightness support

Use CIE L* (psychometric lightness) mapped from luminance
(assumes uniform luminance for R, G, and B channels)

Predefined colors are assumed to be LED values
S3
jrhoffa 2 years ago
parent
commit
eaab3ac0ac
11 changed files with 176 additions and 10 deletions
  1. +4
    -0
      files/gamma.json
  2. +1
    -0
      main/CMakeLists.txt
  3. +12
    -2
      main/device.cpp
  4. +2
    -0
      main/device.hpp
  5. +98
    -0
      main/gamma.cpp
  6. +14
    -0
      main/gamma.hpp
  7. +7
    -2
      main/leds.cpp
  8. +4
    -0
      main/leds.hpp
  9. +6
    -0
      main/main.cpp
  10. +26
    -5
      main/patterns/gradient.cpp
  11. +2
    -1
      main/presets.cpp

+ 4
- 0
files/gamma.json View File

@@ -0,0 +1,4 @@
{
"gamma": [0,0,0,0,0,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,5,5,5,5,5,6,6,6,6,6,7,7,7,7,8,8,8,8,9,9,9,10,10,10,10,11,11,11,12,12,12,13,13,13,14,14,15,15,15,16,16,17,17,17,18,18,19,19,20,20,21,21,22,22,23,23,24,24,25,25,26,26,27,28,28,29,29,30,31,31,32,32,33,34,34,35,36,37,37,38,39,39,40,41,42,43,43,44,45,46,47,47,48,49,50,51,52,53,54,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,70,71,72,73,74,75,76,77,79,80,81,82,83,85,86,87,88,90,91,92,94,95,96,98,99,100,102,103,105,106,108,109,110,112,113,115,116,118,120,121,123,124,126,128,129,131,132,134,136,138,139,141,143,145,146,148,150,152,154,155,157,159,161,163,165,167,169,171,173,175,177,179,181,183,185,187,189,191,193,196,198,200,202,204,207,209,211,214,216,218,220,223,225,228,230,232,235,237,240,242,245,247,250,252,255],
"inv_gamma": [0,9,18,26,33,39,44,48,52,56,60,63,66,69,72,74,77,79,81,84,86,88,90,92,94,96,97,99,101,103,104,106,107,109,110,112,113,115,116,117,119,120,121,123,124,125,126,128,129,130,131,132,133,134,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,151,152,153,154,155,156,157,158,159,159,160,161,162,163,163,164,165,166,167,167,168,169,170,171,171,172,173,174,174,175,176,176,177,178,179,179,180,181,181,182,183,183,184,185,185,186,187,187,188,189,189,190,191,191,192,192,193,194,194,195,196,196,197,197,198,198,199,200,200,201,201,202,203,203,204,204,205,205,206,206,207,208,208,209,209,210,210,211,211,212,212,213,213,214,215,215,216,216,217,217,218,218,219,219,220,220,221,221,222,222,223,223,224,224,225,225,225,226,226,227,227,228,228,229,229,230,230,231,231,232,232,232,233,233,234,234,235,235,236,236,236,237,237,238,238,239,239,240,240,240,241,241,242,242,242,243,243,244,244,245,245,245,246,246,247,247,247,248,248,249,249,249,250,250,251,251,251,252,252,253,253,253,254,254,255,255]
}

+ 1
- 0
main/CMakeLists.txt View File

@@ -9,6 +9,7 @@ idf_component_register(
"device.cpp" "device.cpp"
"utils.cpp" "utils.cpp"
"leds.cpp" "leds.cpp"
"gamma.cpp"
"presets.cpp" "presets.cpp"
"spi_leds.cpp" "spi_leds.cpp"
# "screen_leds.cpp" # "screen_leds.cpp"


+ 12
- 2
main/device.cpp View File

@@ -12,7 +12,7 @@ Device::Device(std::string _id) :
ready(false), ready(false),
mutex(xSemaphoreCreateMutex()), mutex(xSemaphoreCreateMutex()),
sem(xSemaphoreCreateBinary()), sem(xSemaphoreCreateBinary()),
power_on(false), strip_length(0),
power_on(false), strip_length(0), brightness(255),
client(start_mqtt_client(on_mqtt_connect, on_mqtt_message, this)) client(start_mqtt_client(on_mqtt_connect, on_mqtt_message, this))
{} {}


@@ -29,7 +29,11 @@ void Device::set_json_config(const cJSON *json) {
} }
const cJSON *length = cJSON_GetObjectItem(json, "length"); const cJSON *length = cJSON_GetObjectItem(json, "length");
if (length && cJSON_IsNumber(length)) { if (length && cJSON_IsNumber(length)) {
strip_length = length->valuedouble;
strip_length = length->valueint;
}
const cJSON *jbrightness = cJSON_GetObjectItem(json, "brightness");
if (jbrightness && cJSON_IsNumber(jbrightness)) {
brightness = jbrightness->valueint;
} }


publish_state_locked(); publish_state_locked();
@@ -55,6 +59,11 @@ void Device::advertise_locked() {
cJSON *effects = cJSON_AddArrayToObject(json, "effect_list"); cJSON *effects = cJSON_AddArrayToObject(json, "effect_list");
for (auto iter = Presets::map_begin(); iter != Presets::map_end(); ++iter) for (auto iter = Presets::map_begin(); iter != Presets::map_end(); ++iter)
cJSON_AddItemToArray(effects, cJSON_CreateString(iter->first.c_str())); cJSON_AddItemToArray(effects, cJSON_CreateString(iter->first.c_str()));
cJSON_AddBoolToObject(json, "brightness", true);
cJSON_AddStringToObject(json, "brightness_command_topic", (topic_prefix + "/cmd").c_str());
cJSON_AddStringToObject(json, "brightness_state_topic", (topic_prefix + "/state").c_str());
cJSON_AddStringToObject(json, "brightness_template", "{{ value_json.brightness }}");
cJSON_AddStringToObject(json, "brightness_value_template", "{\"brightness\": {{ value }} }");


char *config = cJSON_PrintUnformatted(json); char *config = cJSON_PrintUnformatted(json);


@@ -193,6 +202,7 @@ cJSON* Device::make_json_config_locked() const {
cJSON_AddStringToObject(json, "state", power_on ? "ON" : "OFF"); cJSON_AddStringToObject(json, "state", power_on ? "ON" : "OFF");
cJSON_AddStringToObject(json, "effect", effect.c_str()); cJSON_AddStringToObject(json, "effect", effect.c_str());
cJSON_AddNumberToObject(json, "length", strip_length); cJSON_AddNumberToObject(json, "length", strip_length);
cJSON_AddNumberToObject(json, "brightness", brightness);
return json; return json;
} }




+ 2
- 0
main/device.hpp View File

@@ -27,6 +27,7 @@ public:
const std::string& get_effect() const { return effect; } const std::string& get_effect() const { return effect; }
bool is_on() const { return power_on; } bool is_on() const { return power_on; }
int get_strip_length() const { return strip_length; } int get_strip_length() const { return strip_length; }
int get_brightness() const { return brightness; }


cJSON* make_json_config_locked() const; cJSON* make_json_config_locked() const;


@@ -43,6 +44,7 @@ private:
bool power_on; bool power_on;
std::string effect; std::string effect;
int strip_length; int strip_length;
int brightness;


esp_mqtt_client_handle_t client; esp_mqtt_client_handle_t client;




+ 98
- 0
main/gamma.cpp View File

@@ -0,0 +1,98 @@
static const char *TAG = "gamma";
#include <esp_log.h>

#include <cJSON.h>

#include "gamma.hpp"
#include "utils.hpp"


namespace Gamma {

static uint8_t gamma[256];
static uint8_t inv_gamma[256];


static void default_table(uint8_t *table) {
for (int val = 0; val < 256; ++val) *table++ = val;
}


static void json_table(cJSON *jarray, uint8_t *table) {
if (jarray->type != cJSON_Array) {
ESP_LOGE(TAG, "Not an array: %s", jarray->string);
}

int count = cJSON_GetArraySize(jarray);
if (count != 256) {
ESP_LOGE(TAG, "Wrong size for %s: %d", jarray->string, count);
}

uint8_t *ptr = table;
cJSON *jnumber;
cJSON_ArrayForEach(jnumber, jarray) {
if (jnumber->type != cJSON_Number) {
ESP_LOGE(TAG, "Not a number: %s", jnumber->string);
default_table(table);
return;
}
*ptr++ = (uint8_t)jnumber->valueint;
}
}

static inline cJSON* load_json(const char *path) {
return cJSON_Parse(read_file(path).c_str());
}

void reload() {
default_table(gamma);
default_table(inv_gamma);

cJSON *json = load_json("/spiffs/gamma.json");
if (!json) {
ESP_LOGE(TAG, "No gamma values!");
return;
}

cJSON *jgamma = cJSON_GetObjectItem(json, "gamma");
if (!jgamma) {
ESP_LOGE(TAG, "No gamma values!");
cJSON_Delete(json);
return;
}

cJSON *jinv_gamma = cJSON_GetObjectItem(json, "inv_gamma");
if (!jinv_gamma) {
ESP_LOGE(TAG, "No inverse gamma values!");
cJSON_Delete(json);
return;
}

json_table(jgamma, gamma);
json_table(jinv_gamma, inv_gamma);

cJSON_Delete(json);
}

/*
static inline uint8_t scale(int val, int num, int dem) {
if (dem == 0) return 0;
int val = (val * num) / dem;
return (val >= 256 ? 255 : val);
}
*/

static inline Color correct_single(Color c, int b) {
return {gamma[(b * c.r) / 255], gamma[(b * c.g) / 255], gamma[(b * c.b) / 255]};
}

void correct(Color *dst, const Color *src, int n, int b) {
while(n-- > 0) *dst++ = correct_single(*src++, b);
}

Color wrong_single(Color c) {
return {inv_gamma[c.r], inv_gamma[c.g], inv_gamma[c.b]};
}


} // Gamma

+ 14
- 0
main/gamma.hpp View File

@@ -0,0 +1,14 @@
#pragma once

#include "leds.hpp"


namespace Gamma {

void reload();

void correct(Color *dst, const Color *src, int len, int brightness = 255);

Color wrong_single(Color);

} // Gamma

+ 7
- 2
main/leds.cpp View File

@@ -3,13 +3,15 @@ static const char *TAG = "leds";


#include <cstring> #include <cstring>


#include "gamma.hpp"
#include "leds.hpp" #include "leds.hpp"
#include "utils.hpp" #include "utils.hpp"




LEDStrip::LEDStrip(int _length) : LEDStrip::LEDStrip(int _length) :
length(_length), pattern(NULL),
length(_length), brightness(255), pattern(NULL),
pixels(new Color[_length]), pixels(new Color[_length]),
colors(new Color[_length]),
state(NULL) state(NULL)
{} {}


@@ -23,13 +25,16 @@ void LEDStrip::setLength(int _length) {
clear(); clear();
length_changing(_length); length_changing(_length);
delete[] pixels; delete[] pixels;
delete[] colors;
length = _length; length = _length;
pixels = new Color[_length]; pixels = new Color[_length];
colors = new Color[_length];
update_state(); update_state();
} }


void LEDStrip::step() { void LEDStrip::step() {
if (pattern) pattern->step(pixels, length, state);
if (pattern) pattern->step(colors, length, state);
Gamma::correct(pixels, colors, length, brightness);
} }


void LEDStrip::clear() { void LEDStrip::clear() {


+ 4
- 0
main/leds.hpp View File

@@ -45,10 +45,14 @@ public:
void setLength(int length); void setLength(int length);
int getLength() const { return length; } int getLength() const { return length; }


void setBrightness(int _brightness) { brightness = _brightness; }

protected: protected:
int length; int length;
int brightness;
const Pattern *pattern; const Pattern *pattern;
Color *pixels; Color *pixels;
Color *colors;


private: private:
void update_state(); void update_state();


+ 6
- 0
main/main.cpp View File

@@ -13,6 +13,7 @@ static const char *TAG = "blinky";
#include "device.hpp" #include "device.hpp"
#include "utils.hpp" #include "utils.hpp"
#include "leds.hpp" #include "leds.hpp"
#include "gamma.hpp"
#include "presets.hpp" #include "presets.hpp"
#include "utils.hpp" #include "utils.hpp"
#include "spi_leds.hpp" #include "spi_leds.hpp"
@@ -111,6 +112,8 @@ extern "C" void app_main(void) {
int frequency = 30; int frequency = 30;


while (true) { while (true) {
Gamma::reload();

// Trash the old preset in case we can't find the set effect // Trash the old preset in case we can't find the set effect
LEDs->setPattern(NULL); LEDs->setPattern(NULL);
Presets::reload(); Presets::reload();
@@ -121,9 +124,12 @@ extern "C" void app_main(void) {
const std::string effect = device.get_effect(); const std::string effect = device.get_effect();
bool is_on = device.is_on(); bool is_on = device.is_on();
int length = device.get_strip_length(); int length = device.get_strip_length();
int brightness = device.get_brightness();
cJSON *json = device.make_json_config_locked(); cJSON *json = device.make_json_config_locked();
device.unlock(); device.unlock();


LEDs->setBrightness(brightness);

if (length != LEDs->getLength()) { if (length != LEDs->getLength()) {
LEDs->setLength(length); LEDs->setLength(length);
} }


+ 26
- 5
main/patterns/gradient.cpp View File

@@ -26,9 +26,9 @@ public:
Gradient *_gradient, Gradient *_gradient,
int _cycle_length=20, int _cycle_time_ms=-1, int _cycle_length=20, int _cycle_time_ms=-1,
bool _reverse=false, bool _march=false bool _reverse=false, bool _march=false
) : cycle_length(_cycle_length),
) : cycle_length(_cycle_length > 0 ? _cycle_length : _gradient->n_colors),
cycle_time_ms( _cycle_time_ms < 0 ? cycle_time_ms( _cycle_time_ms < 0 ?
_cycle_length * 500 : _cycle_time_ms
cycle_length * 500 : _cycle_time_ms
), ),
reverse(_reverse), march(_march), gradient(_gradient) reverse(_reverse), march(_march), gradient(_gradient)
{} {}
@@ -97,8 +97,7 @@ void GradientPattern::step(Color pixels[], int len, Pattern::State *_state) cons
// Don't make a major animation jump if it's been terribly long // Don't make a major animation jump if it's been terribly long
if (duration_us > 100000) duration_us = 100000; if (duration_us > 100000) duration_us = 100000;



int offset_delta = (duration_us << shift) / (cycle_time_ms * 1000);
int offset_delta = (cycle_time_ms <= 0 ? 0 : (duration_us << shift) / (cycle_time_ms * 1000));
if (reverse) state->offset += offset_delta; else state->offset -= offset_delta; if (reverse) state->offset += offset_delta; else state->offset -= offset_delta;
Fixed off = march ? Fixed off = march ?
(((int)state->offset * cycle_length) & ~((1 << shift) - 1)) / cycle_length : (((int)state->offset * cycle_length) & ~((1 << shift) - 1)) / cycle_length :
@@ -134,5 +133,27 @@ Pattern* json_gradient_pattern(cJSON *pattern, const Presets::ColorMap &colors)
gradient->colors[i++] = color; gradient->colors[i++] = color;
} }


return new GradientPattern(gradient);
int cycle_length = 20;
cJSON *jlength = cJSON_GetObjectItem(pattern, "length");
if (jlength) {
if (jlength->type != cJSON_Number) {
ESP_LOGE(TAG, "Not a number: %s", jlength->string);
} else {
cycle_length = jlength->valueint;
}
}

int cycle_time_ms = -1;
cJSON *jduration = cJSON_GetObjectItem(pattern, "duration");
if (jduration) {
if (jduration->type != cJSON_Number) {
ESP_LOGE(TAG, "Not a number: %s", jduration->string);
} else {
cycle_time_ms = jduration->valuedouble * 1000.;
}
}

// TODO: Reverse, March

return new GradientPattern(gradient, cycle_length, cycle_time_ms);
} }

+ 2
- 1
main/presets.cpp View File

@@ -3,6 +3,7 @@ static const char *TAG = "presets";


#include <cJSON.h> #include <cJSON.h>


#include "gamma.hpp"
#include "presets.hpp" #include "presets.hpp"
#include "utils.hpp" #include "utils.hpp"


@@ -115,7 +116,7 @@ void reload() {
cJSON *jcolor; cJSON *jcolor;
cJSON_ArrayForEach(jcolor, jcolors) { cJSON_ArrayForEach(jcolor, jcolors) {
Color color = json_color(jcolor, colors); Color color = json_color(jcolor, colors);
colors[jcolor->string] = color;
colors[jcolor->string] = Gamma::wrong_single(color);
} }
cJSON_Delete(jcolors); cJSON_Delete(jcolors);
} }


Loading…
Cancel
Save