89 lines
4.6 KiB
C++
89 lines
4.6 KiB
C++
#include "Afterglow.h"
|
|
#include "Heatmap_Utils.h"
|
|
#include "../render/Blend2D_Cache.h"
|
|
#include <algorithm>
|
|
#include <deque>
|
|
namespace renderive {
|
|
namespace detail {
|
|
namespace {
|
|
struct Afterglow_Runtime_Base {};
|
|
struct Afterglow_Runtime {
|
|
std::deque<std::vector<double>> history;
|
|
};
|
|
using Afterglow_Runtime_State = Double_State_Strategy<Afterglow_Runtime_Base, Afterglow_Runtime>;
|
|
}
|
|
struct Afterglow_Control::Impl {
|
|
Impl(std::shared_ptr<Frequency_Axis> frequency, std::shared_ptr<Axis> power) : frequency_axis(std::move(frequency)), power_axis(std::move(power)) {}
|
|
std::shared_ptr<Frequency_Axis> frequency_axis;
|
|
std::shared_ptr<Axis> power_axis;
|
|
Afterglow_Runtime_State runtime;
|
|
};
|
|
Afterglow_Control::Afterglow_Control(Plot_Core& plot, const Afterglow_Properties& properties, std::shared_ptr<Frequency_Axis> frequency_axis, std::shared_ptr<Axis> power_axis)
|
|
: Plottable_State(plot, properties), impl_(std::make_unique<Impl>(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<int>(runtime.history.back().size()));
|
|
const int height = state.power_point_size.get() > 0 ? state.power_point_size.get() : std::max(1, static_cast<int>(impl_->power_axis->pixel_length()));
|
|
return width > 0 && height > 0 ? static_cast<std::size_t>(width) * static_cast<std::size_t>(height) : 0;
|
|
}
|
|
void Afterglow_Control::append_spectrum(std::span<const double> values) {
|
|
if(get<&Afterglow_Properties::frequency_point_size>() <= 0)
|
|
set<&Afterglow_Properties::frequency_point_size>(static_cast<int>(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<double>&& values) {
|
|
append_spectrum(std::span<const double>(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<int>(runtime.history.back().size()));
|
|
const int height = state.power_point_size.get() > 0 ? state.power_point_size.get() : std::max(1, static_cast<int>(impl_->power_axis->pixel_length()));
|
|
if(width <= 0 || height <= 0)
|
|
return;
|
|
std::vector<double> intensity(static_cast<std::size_t>(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<int>(iterator->size()));
|
|
for(int x = 0; x < count; ++x) {
|
|
const double normalized = normalized_value((*iterator)[static_cast<std::size_t>(x)], state.power_range);
|
|
const int y = std::clamp(height - 1 - static_cast<int>(normalized * (height - 1)), 0, height - 1);
|
|
intensity[static_cast<std::size_t>(y) * width + x] += weight;
|
|
if(state.interpolate && y + 1 < height)
|
|
intensity[static_cast<std::size_t>(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<Pixel> 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);
|
|
}
|
|
}
|
|
}
|