static const char *TAG = "gradient"; #include #include "presets.hpp" #include "utils.hpp" struct Gradient { Color at(Fixed x) const; static Gradient* make(int n) { Gradient *gradient = (Gradient*)malloc( sizeof(Gradient) + n * sizeof(*colors) ); gradient->n_colors = n; return gradient; } unsigned int n_colors; Color colors[]; }; class GradientPattern : public Pattern { public: GradientPattern( Gradient *_gradient, int _cycle_length=20, int _cycle_time_ms=-1, bool _reverse=false, bool _march=false ) : cycle_length(_cycle_length), cycle_time_ms( _cycle_time_ms < 0 ? _cycle_length * 500 : _cycle_time_ms ), reverse(_reverse), march(_march), gradient(_gradient) {} ~GradientPattern() { free(gradient); } State* start(int) const { return new State(); } void step(Color[], int, State*) const; private: struct State : Pattern::State { State() : last_us(0), offset(0) {} int64_t last_us; Fixed offset; }; int cycle_length; int cycle_time_ms; bool reverse; bool march; Gradient *gradient; }; static inline uint8_t mix(int l, int r, Fixed x) { return ((l << shift) + (x * (r - l))) >> shift; } Color Gradient::at(Fixed x) const { int i_left = (x * n_colors) >> shift; int i_right = i_left < n_colors - 1 ? i_left + 1 : 0; Fixed ratio = (x * n_colors) - (i_left << shift); //ESP_LOGI(TAG, "at(%d) = %d between %d and %d", x, ratio, i_left, i_right); Color cl = colors[i_left]; Color cr = colors[i_right]; Color color = { mix(cl.r, cr.r, ratio), mix(cl.g, cr.g, ratio), mix(cl.b, cr.b, ratio) }; /*ESP_LOGI(TAG, "(%d, %d, %d) %f (%d, %d, %d) -> (%d, %d, %d)", cl.r, cl.g, cl.b, ratio / (float)factor, cr.r, cr.g, cr.b, color.r, color.g, color.b );*/ return color; } void GradientPattern::step(Color pixels[], int len, Pattern::State *_state) const { GradientPattern::State *state = (GradientPattern::State*)_state; int64_t now_us = time_us(); int64_t duration_us = state->last_us == 0 ? 0 : now_us - state->last_us; state->last_us = now_us; //ESP_LOGI(TAG, "duration %d", duration_us); /* int period_us = 1000000 / 60; int n_skipped = (duration_us - period_us / 2) / period_us; if (n_skipped) { ESP_LOGW(TAG, "Skipped %d frames", n_skipped); } */ // Don't make a major animation jump if it's been terribly long if (duration_us > 100000) duration_us = 100000; int offset_delta = (duration_us << shift) / (cycle_time_ms * 1000); if (reverse) state->offset += offset_delta; else state->offset -= offset_delta; Fixed off = march ? (((int)state->offset * cycle_length) & ~((1 << shift) - 1)) / cycle_length : state->offset; //ESP_LOGI(TAG, "cycle time %d, delta %d, offset %d, off %d", cycle_time_ms, offset_delta, offset, off); for (int i = 0; i < len; ++i) { pixels[i] = gradient->at(off + ((i << shift) / cycle_length)); } } Pattern* json_gradient_pattern(cJSON *pattern, const Presets::ColorMap &colors) { if (pattern->type != cJSON_Object) { ESP_LOGE(TAG, "Not an object: %s", pattern->string); return NULL; } cJSON *jcolors = cJSON_GetObjectItem(pattern, "colors"); if (!jcolors) { ESP_LOGE(TAG, "No colors in %s", pattern->string); return NULL; } if (jcolors->type != cJSON_Array) { ESP_LOGE(TAG, "Not an array: %s", jcolors->string); return NULL; } Gradient *gradient = Gradient::make(cJSON_GetArraySize(jcolors)); int i = 0; cJSON *jcolor; cJSON_ArrayForEach(jcolor, jcolors) { Color color = Presets::json_color(jcolor, colors); gradient->colors[i++] = color; } return new GradientPattern(gradient); }