Modular light transformers (#2124)

This commit is contained in:
Oxan van Leeuwen
2021-08-11 16:51:35 +12:00
committed by GitHub
parent 11477dbc03
commit 46f17bea66
8 changed files with 195 additions and 150 deletions
+48 -43
View File
@@ -24,6 +24,10 @@ void AddressableLight::call_setup() {
#endif
}
std::unique_ptr<LightTransformer> AddressableLight::create_default_transition() {
return make_unique<AddressableLightTransformer>(*this);
}
Color esp_color_from_light_color_values(LightColorValues val) {
auto r = to_uint8_scale(val.get_color_brightness() * val.get_red());
auto g = to_uint8_scale(val.get_color_brightness() * val.get_green());
@@ -37,66 +41,67 @@ void AddressableLight::write_state(LightState *state) {
auto max_brightness = to_uint8_scale(val.get_brightness() * val.get_state());
this->correction_.set_local_brightness(max_brightness);
this->last_transition_progress_ = 0.0f;
this->accumulated_alpha_ = 0.0f;
if (this->is_effect_active())
return;
// don't use LightState helper, gamma correction+brightness is handled by ESPColorView
this->all() = esp_color_from_light_color_values(val);
}
if (state->transformer_ == nullptr || !state->transformer_->is_transition()) {
// no transformer active or non-transition one
this->all() = esp_color_from_light_color_values(val);
} else {
// transition transformer active, activate specialized transition for addressable effects
// instead of using a unified transition for all LEDs, we use the current state each LED as the
// start. Warning: ugly
void AddressableLightTransformer::start() {
auto end_values = this->target_values_;
this->target_color_ = esp_color_from_light_color_values(end_values);
// We can't use a direct lerp smoothing here though - that would require creating a copy of the original
// state of each LED at the start of the transition
// Instead, we "fake" the look of the LERP by using an exponential average over time and using
// dynamically-calculated alpha values to match the look of the
// our transition will handle brightness, disable brightness in correction.
this->light_.correction_.set_local_brightness(255);
this->target_color_ *= to_uint8_scale(end_values.get_brightness() * end_values.get_state());
}
float new_progress = state->transformer_->get_progress();
float prev_smoothed = LightTransitionTransformer::smoothed_progress(last_transition_progress_);
float new_smoothed = LightTransitionTransformer::smoothed_progress(new_progress);
this->last_transition_progress_ = new_progress;
optional<LightColorValues> AddressableLightTransformer::apply() {
// Don't try to transition over running effects, instead immediately use the target values. write_state() and the
// effects pick up the change from current_values.
if (this->light_.is_effect_active())
return this->target_values_;
auto end_values = state->transformer_->get_end_values();
Color target_color = esp_color_from_light_color_values(end_values);
// Use a specialized transition for addressable lights: instead of using a unified transition for
// all LEDs, we use the current state of each LED as the start.
// our transition will handle brightness, disable brightness in correction.
this->correction_.set_local_brightness(255);
target_color *= to_uint8_scale(end_values.get_brightness() * end_values.get_state());
// We can't use a direct lerp smoothing here though - that would require creating a copy of the original
// state of each LED at the start of the transition.
// Instead, we "fake" the look of the LERP by using an exponential average over time and using
// dynamically-calculated alpha values to match the look.
float denom = (1.0f - new_smoothed);
float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom;
float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_());
// We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length
// We solve this by accumulating the fractional part of the alpha over time.
float alpha255 = alpha * 255.0f;
float alpha255int = floorf(alpha255);
float alpha255remainder = alpha255 - alpha255int;
float denom = (1.0f - smoothed_progress);
float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom;
this->accumulated_alpha_ += alpha255remainder;
float alpha_add = floorf(this->accumulated_alpha_);
this->accumulated_alpha_ -= alpha_add;
// We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length
// We solve this by accumulating the fractional part of the alpha over time.
float alpha255 = alpha * 255.0f;
float alpha255int = floorf(alpha255);
float alpha255remainder = alpha255 - alpha255int;
alpha255 += alpha_add;
alpha255 = clamp(alpha255, 0.0f, 255.0f);
auto alpha8 = static_cast<uint8_t>(alpha255);
this->accumulated_alpha_ += alpha255remainder;
float alpha_add = floorf(this->accumulated_alpha_);
this->accumulated_alpha_ -= alpha_add;
if (alpha8 != 0) {
uint8_t inv_alpha8 = 255 - alpha8;
Color add = target_color * alpha8;
alpha255 += alpha_add;
alpha255 = clamp(alpha255, 0.0f, 255.0f);
auto alpha8 = static_cast<uint8_t>(alpha255);
for (auto led : *this)
led = add + led.get() * inv_alpha8;
}
if (alpha8 != 0) {
uint8_t inv_alpha8 = 255 - alpha8;
Color add = this->target_color_ * alpha8;
for (auto led : this->light_)
led.set(add + led.get() * inv_alpha8);
}
this->schedule_show();
this->last_transition_progress_ = smoothed_progress;
this->light_.schedule_show();
return {};
}
} // namespace light