#include "Afterglow.h" #include "Heatmap_Utils.h" #include "../render/Blend2D_Cache.h" #include #include namespace renderive { namespace detail { namespace { struct Afterglow_Runtime_Base {}; struct Afterglow_Runtime { std::deque> history; }; using Afterglow_Runtime_State = Double_State_Strategy; } struct Afterglow_Control::Impl { Impl(std::shared_ptr frequency, std::shared_ptr power) : frequency_axis(std::move(frequency)), power_axis(std::move(power)) {} std::shared_ptr frequency_axis; std::shared_ptr power_axis; Afterglow_Runtime_State runtime; }; Afterglow_Control::Afterglow_Control(Plot_Core& plot, const Afterglow_Properties& properties, std::shared_ptr frequency_axis, std::shared_ptr power_axis) : Plottable_State(plot, properties), impl_(std::make_unique(std::move(frequency_axis), std::move(power_axis))) {} Afterglow_Control::~Afterglow_Control() = default; std::size_t Afterglow_Control::history_count() const { return impl_->runtime.read([](const Afterglow_Runtime& runtime) { return runtime.history.size(); }); } std::size_t Afterglow_Control::latest_spectrum_point_count() const { return impl_->runtime.read([](const Afterglow_Runtime& runtime) { return runtime.history.empty() ? 0 : runtime.history.back().size(); }); } std::size_t Afterglow_Control::rendered_cell_count() const { const auto state = properties(); const auto runtime = impl_->runtime.read([](const Afterglow_Runtime& value) { return value; }); if(runtime.history.empty()) return 0; const int width = std::min(state.frequency_point_size.get(), static_cast(runtime.history.back().size())); const int height = state.power_point_size.get() > 0 ? state.power_point_size.get() : std::max(1, static_cast(impl_->power_axis->pixel_length())); return width > 0 && height > 0 ? static_cast(width) * static_cast(height) : 0; } void Afterglow_Control::append_spectrum(std::span values) { if(get<&Afterglow_Properties::frequency_point_size>() <= 0) set<&Afterglow_Properties::frequency_point_size>(static_cast(values.size())); impl_->runtime.update([values](Afterglow_Runtime& runtime) { runtime.history.emplace_back(values.begin(), values.end()); while(runtime.history.size() > 64) runtime.history.pop_front(); }); changed(); } void Afterglow_Control::append_spectrum(std::pmr::vector&& values) { append_spectrum(std::span(values.data(), values.size())); } void Afterglow_Control::publish() { publish_properties(); impl_->runtime.publish(); } void Afterglow_Control::paint(Painter& painter) { const auto state = render_properties(); const auto runtime = impl_->runtime.render_use_state(); if(runtime.history.empty()) return; const int width = std::min(state.frequency_point_size.get(), static_cast(runtime.history.back().size())); const int height = state.power_point_size.get() > 0 ? state.power_point_size.get() : std::max(1, static_cast(impl_->power_axis->pixel_length())); if(width <= 0 || height <= 0) return; std::vector intensity(static_cast(width) * height); double weight = 1.0; const double decay = 1.0 - state.attenuation_rate.get(); for(auto iterator = runtime.history.rbegin(); iterator != runtime.history.rend(); ++iterator) { const int count = std::min(width, static_cast(iterator->size())); for(int x = 0; x < count; ++x) { const double normalized = normalized_value((*iterator)[static_cast(x)], state.power_range); const int y = std::clamp(height - 1 - static_cast(normalized * (height - 1)), 0, height - 1); intensity[static_cast(y) * width + x] += weight; if(state.interpolate && y + 1 < height) intensity[static_cast(y + 1) * width + x] += weight * 0.35; } weight *= decay; if(weight < 0.01) break; } const double maximum = std::max(1.0, *std::max_element(intensity.begin(), intensity.end())); std::vector pixels(intensity.size()); for(std::size_t index = 0; index < pixels.size(); ++index) pixels[index] = state.color_map.at_normalized(intensity[index] / maximum); painter.heatmap(mapped_rect(impl_->frequency_axis->transform(), impl_->power_axis->transform(), state.frequency_range, state.power_range), width, height, pixels, Image_Interpolation_Mode::Bilinear); } } }