From a6ad9ab2aa6663afff66861c429d87852f3406c3 Mon Sep 17 00:00:00 2001 From: Maxime de Roucy Date: Fri, 9 Mar 2018 01:36:22 +0100 Subject: [PATCH] major rewrite: allow the user to set his own heat_color Change the algorithm so the hotest key is always at the hotest color. Add comments. Other changes. --- README.md | 38 ++++++-- src/Kaleidoscope/Heatmap.cpp | 162 ++++++++++++++++++++++++++--------- src/Kaleidoscope/Heatmap.h | 11 ++- 3 files changed, 159 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 43a45d3e..4598c0bc 100644 --- a/README.md +++ b/README.md @@ -26,18 +26,27 @@ include the header, and make sure the plugin is in use: #include #include +static const cRGB heat_colors[] PROGMEM = { + { 0, 0, 0}, // black + {255, 25, 25}, // blue + { 25, 255, 25}, // green + { 25, 25, 255} // red +}; + void setup() { Kaleidoscope.use(&HeatmapEffect); Kaleidoscope.setup (); + + HeatmapEffect.heat_colors = heat_colors; + HeatmapEffect.heat_colors_length = 4; } ``` -This sets up the heatmap to update every 500 cycles, which is about 2.5 seconds, -and is the default. It also registers a new LED effect, which means that if you -have not set up any other effects, then Heatmap will likely be the default. You -may not want that, so setting up at least one other LED effect, such as `LEDOff` -is highly recommended. +This sets up the heatmap to update every second (by default). It also registers +a new LED effect, which means that if you have not set up any other effects, +then Heatmap will likely be the default. You may not want that, so setting up +at least one other LED effect, such as `LEDOff` is highly recommended. ## Plugin methods @@ -57,7 +66,24 @@ and properties: > often. Doing it too rarely, on the other hand, make it much less useful. One > has to strike a reasonable balance. > -> Defaults to *500*. +> Defaults to *1000*. + +### `.heat_colors` + +> A cRGB array describing the gradian of colors that will be used, from colder +> to hoter keys. +> E.g. with `heat_colors = {{100, 0, 0}, {0, 100, 0}, {0, 0, 100}}`, a key +> with a temperature of 0.8 (0=coldest, 1=hotest), will end up with a color +> `{0, 40, 60}`. +> +> Defaults to `{{0, 0, 0}, {25, 255, 25}, {25, 255, 255}, {25, 25, 255}}` +> (black, green, yellow, red) + +### `.heat_colors_length` + +> Length of the `heat_colors` array. +> +> Defaults to *4* ## Dependencies diff --git a/src/Kaleidoscope/Heatmap.cpp b/src/Kaleidoscope/Heatmap.cpp index 78d1a50f..23727206 100644 --- a/src/Kaleidoscope/Heatmap.cpp +++ b/src/Kaleidoscope/Heatmap.cpp @@ -21,49 +21,94 @@ namespace kaleidoscope { -uint8_t Heatmap::heatmap_[ROWS][COLS]; -uint16_t Heatmap::total_keys_; -uint8_t Heatmap::highest_count_; -uint16_t Heatmap::update_delay = 500; -uint32_t Heatmap::end_time_; - -const float Heatmap::heat_colors_[][3] = {{0.0, 0.0, 0.0}, {0.1, 1, 0.1}, {1, 1, 0.1}, {1, 0.1, 0.1}}; - -void Heatmap::shiftStats(void) { - highest_count_ = total_keys_ = 0; - for (uint8_t r = 0; r < ROWS; r++) { - for (uint8_t c = 0; c < COLS; c++) { - heatmap_[r][c] = heatmap_[r][c] >> 1; - total_keys_ += heatmap_[r][c]; - if (heatmap_[r][c] > highest_count_) - highest_count_ = heatmap_[r][c]; - } - } -} +// store the number of times each key has been strock +uint16_t Heatmap::heatmap_[ROWS][COLS]; +// max of heatmap_ (we divide by it so we start at 1) +uint16_t Heatmap::highest_ = 1; +// next heatmap computation time +uint32_t Heatmap::next_heatmap_comp_time_ = 0; +// in the cRGB struct the order is blue, green, red (It should be called cBGR…) +// default heat_colors black green yellow red +static const cRGB heat_colors_default_[] PROGMEM = {{0, 0, 0}, {25, 255, 25}, {25, 255, 255}, {25, 25, 255}}; + +// colors from cold to hot +const cRGB *Heatmap::heat_colors = heat_colors_default_; +uint8_t Heatmap::heat_colors_length = 4; +// number of millisecond to wait between each heatmap computation +uint16_t Heatmap::update_delay = 1000; cRGB Heatmap::computeColor(float v) { + // compute the color corresponding to a value between 0 and 1 + + /* + * for exemple, if: + * v=0.8 + * heat_colors_lenth=4 (hcl) + * the red components of heat_colors are: 0, 25, 25, 255 (rhc) + * the red component returned by computeColor will be: 117 + * + * 255 | / + * | / + * | / + * 117 | - - - - - - -/ + * | / + * 25 | ______/ | + * | __/ + * | _/ | + * |/_________________ + * 0 1 2 ↑ 3 + * ↑ 2.4 ↑ + * idx1 | idx2 + * <—–> + * fb + * + * in this exemple, I call red heat_colors: rhc + * idx1 = floor(v×(hcl-1)) = floor(0.8×3) = floor(2.4) = 2 + * idx2 = idx1 + 1 = 3 + * fb = v×(hcl-1)-idx1 = 0.8×3 - 2 = 0.4 + * red = (rhc[idx2]-rhc[idx1])×fb + rhc[idx1] = (255-25)×(2.4-2) + 25 = 117 + */ + float fb = 0; uint8_t idx1, idx2; if (v <= 0) { + // if v = 0, don't bother computing fb and use heat_colors[0] idx1 = idx2 = 0; } else if (v >= 1) { - idx1 = idx2 = 3; + // if v = 1, + // don't bother computing fb and use heat_colors[heat_colors_length-1] + idx1 = idx2 = heat_colors_length - 1; } else { - float val = v * 3; - idx1 = static_cast(floor(val)); + float val = v * (heat_colors_length - 1); + // static_cast from float to int just drop the decimal part of the number + // static_cast(5.9) → 5 + idx1 = static_cast(val); idx2 = idx1 + 1; fb = val - static_cast(idx1); } - uint8_t r = static_cast(((heat_colors_[idx2][0] - heat_colors_[idx1][0]) * fb + heat_colors_[idx1][0]) * 255); - uint8_t g = static_cast(((heat_colors_[idx2][1] - heat_colors_[idx1][1]) * fb + heat_colors_[idx1][1]) * 255); - uint8_t b = static_cast(((heat_colors_[idx2][2] - heat_colors_[idx1][2]) * fb + heat_colors_[idx1][2]) * 255); + uint8_t r = static_cast((pgm_read_byte(&(heat_colors[idx2].r)) - pgm_read_byte(&(heat_colors[idx1].r))) * fb + pgm_read_byte(&(heat_colors[idx1].r))); + uint8_t g = static_cast((pgm_read_byte(&(heat_colors[idx2].g)) - pgm_read_byte(&(heat_colors[idx1].g))) * fb + pgm_read_byte(&(heat_colors[idx1].g))); + uint8_t b = static_cast((pgm_read_byte(&(heat_colors[idx2].b)) - pgm_read_byte(&(heat_colors[idx1].b))) * fb + pgm_read_byte(&(heat_colors[idx1].b))); return {b, g, r}; } -Heatmap::Heatmap(void) { +void Heatmap::shiftStats(void) { + // this method is called when: + // 1. a value in heatmap_ reach INT8_MAX + // 2. highest_ reach heat_colors_length*512 (see Heatmap::loopHook) + + // we divide every heatmap element by 2 + for (uint8_t r = 0; r < ROWS; r++) { + for (uint8_t c = 0; c < COLS; c++) { + heatmap_[r][c] = heatmap_[r][c] >> 1; + } + } + + // and also divide highest_ accordingly + highest_ = highest_ >> 1; } void Heatmap::setup(void) { @@ -72,44 +117,81 @@ void Heatmap::setup(void) { } Key Heatmap::eventHook(Key mapped_key, byte row, byte col, uint8_t key_state) { - // if it is a synthetic key, skip it. + // this methode is called frequently by Kaleidoscope + // even if the module isn't activated + + // if it is a synthetic key, skip it if (key_state & INJECTED) return mapped_key; - // if the key is not toggled on, return. + // if the key is not toggled on, skip it if (!keyToggledOn(key_state)) return mapped_key; - total_keys_++; + // increment the heatmap_ value related to the key heatmap_[row][col]++; - if (heatmap_[row][col] > highest_count_) - highest_count_ = heatmap_[row][col]; + + // check highest_ + if (highest_ < heatmap_[row][col]) { + highest_ = heatmap_[row][col]; + + // if highest_ (and so heatmap_ value related to the key) + // is close to overflow: call shiftStats + // NOTE: this is barely impossible since shiftStats should be + // called much sooner by Heatmap::loopHook + if (highest_ == INT16_MAX) + shiftStats(); + } return mapped_key; } -void -Heatmap::loopHook(bool is_post_clear) { - if (highest_count_ > 191 || total_keys_ > 16000) +void Heatmap::loopHook(bool is_post_clear) { + // this methode is called frequently by Kaleidoscope + // even if the module isn't activated + + // call shiftStats (divide every heatmap value by 2) + // if highest_ reach heat_colors_length*512. + // So after the shift, highest_ will be heat_colors_length*256. We + // didn't lose any precision in our heatmap since between each color we have a + // maximum precision of 256 ; said differently, there is 256 state (at max) + // between heat_colors[x] and heat_colors[x+1]. + if (highest_ > (static_cast(heat_colors_length) << 9)) shiftStats(); } -void -Heatmap::update(void) { - if (end_time_ && (millis() < end_time_)) +void Heatmap::update(void) { + // this methode is called frequently by the LEDControl::loopHook + + // do nothing if we didn't reach next_heatmap_comp_time_ yet + if (next_heatmap_comp_time_ && (millis() < next_heatmap_comp_time_)) return; + // do the heatmap computing + // (we reach next_heatmap_comp_time_ or next_heatmap_comp_time_ was never scheduled) - end_time_ = millis() + update_delay; + // schedule the next heatmap computing + next_heatmap_comp_time_ = millis() + update_delay; + // for each key for (uint8_t r = 0; r < ROWS; r++) { for (uint8_t c = 0; c < COLS; c++) { - uint8_t cap = max(total_keys_, 1); - float v = static_cast(heatmap_[r][c]) / cap; + // how much the key was pressed compared to the others (between 0 and 1) + // (total_keys_ can't be equal to 0) + float v = static_cast(heatmap_[r][c]) / highest_; + // we could have used an interger instead of a float, but then we would + // have had to change some multiplication in division. + // / on uint is slower than * on float, so I stay with the float + // https://forum.arduino.cc/index.php?topic=92684.msg2733723#msg2733723 + + // set the LED color accordingly ::LEDControl.setCrgbAt(r, c, computeColor(v)); } } } +Heatmap::Heatmap(void) { +} + } kaleidoscope::Heatmap HeatmapEffect; diff --git a/src/Kaleidoscope/Heatmap.h b/src/Kaleidoscope/Heatmap.h index 76f0c3ee..fba0a51e 100644 --- a/src/Kaleidoscope/Heatmap.h +++ b/src/Kaleidoscope/Heatmap.h @@ -27,18 +27,17 @@ class Heatmap : public LEDMode { Heatmap(void); static uint16_t update_delay; + static const cRGB *heat_colors; + static uint8_t heat_colors_length; protected: void setup(void) final; void update(void) final; private: - static uint8_t heatmap_[ROWS][COLS]; - static uint16_t total_keys_; - static uint8_t highest_count_; - static uint32_t end_time_; - - static const float heat_colors_[][3]; + static uint16_t heatmap_[ROWS][COLS]; + static uint16_t highest_; + static uint32_t next_heatmap_comp_time_; static void shiftStats(void); static cRGB computeColor(float v);