#pragma once #include #include #include #include #include "../Axis/Frequency_Axis.h" #include "../Axis/Frequency_Axis_p.h" #include "../architecture/Bounded_Input_Buffer.h" #include "../base/Memory.h" #include "../base/global.h" #include "../primitive/Raster_Image_p.h" #include "Afterglow.h" namespace renderive { struct Afterglow_Render_State : Render_State, Afterglow_Prop {}; struct Afterglow_Input_Data : Input_Data { static constexpr std::size_t Max_Pending_Frame_Count = 4; Bounded_Vector_Double_Input_Buffer pending_spectra; void clear() override { pending_spectra.clear(); } }; struct Afterglow_Private : Typed_Render_Data { Raster_Image_Buffer image; std::weak_ptr color_bar; bool image_dirty = false; int current_history_index = 0; int cached_frequency_bin_count{}; int cached_power_bin_count{}; int cached_cell_count{}; std::uint64_t cache_color_map_hash{}; std::pmr::vector current_intensity_grid{memory_resource(Memory_Domain::Afterglow)}; std::pmr::vector previous_intensity_grid{memory_resource(Memory_Domain::Afterglow)}; std::pmr::vector merged_intensity_grid{memory_resource(Memory_Domain::Afterglow)}; void update_cache_size(int frequency_point_size, int power_point_size) { cached_frequency_bin_count = frequency_point_size; cached_power_bin_count = power_point_size; cached_cell_count = frequency_point_size * power_point_size; current_intensity_grid.resize(cached_cell_count); std::fill(current_intensity_grid.begin(), current_intensity_grid.end(), 0.0); previous_intensity_grid.resize(cached_cell_count); std::fill(previous_intensity_grid.begin(), previous_intensity_grid.end(), 0.0); merged_intensity_grid.resize(cached_cell_count); std::fill(merged_intensity_grid.begin(), merged_intensity_grid.end(), 0.0); current_history_index = 0; } void push_data(std::span power_range_data, const Afterglow_Render_State* s, const Color_Scale_Snapshot& color_scale); void draw_image(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Afterglow_Render_State* s, const Color_Scale_Snapshot& color_scale) { auto frequency_axis = s->frequency_axis.lock(); auto power_axis = s->power_axis.lock(); if (!frequency_axis || !power_axis || image.is_null()) return; if (s->frequency_range.length() == 0.0 || color_scale.range.length() == 0.0) return; Axis_Mapping_2D mapping = Axis_Render_Access::mapping(frequency_axis.get(), power_axis.get(), snapshot); Axis_Basis_2D basis = Axis_Basis_2D::from_ranges(mapping, s->frequency_range, color_scale.range); canvas.save(); canvas.transform( basis.domain_vector.x / static_cast(image.width()), basis.domain_vector.y / static_cast(image.width()), basis.value_vector.x / static_cast(image.height()), basis.value_vector.y / static_cast(image.height()), basis.origin.x, basis.origin.y); image.draw(canvas, RectF{0.0, 0.0, static_cast(image.width()), static_cast(image.height())}); canvas.restore(); } void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override { Afterglow_Render_State* s = render_state(frame_view); if (!s) return; auto color_scale = s->color_scale.snapshot(); draw_image(canvas, snapshot, s, *color_scale); } void prepare_data(const Render_Frame_Snapshot&, const Renderable_Frame_View& frame_view) override { Afterglow_Render_State* s = render_state(frame_view); Afterglow_Input_Data* input = render_input_data(frame_view); if (!s || !input) return; if (s->frequency_bin_count <= 0 || s->power_bin_count <= 0) return; auto color_scale = s->color_scale.snapshot(); if (cached_frequency_bin_count != s->frequency_bin_count || cached_power_bin_count != s->power_bin_count) { update_cache_size(s->frequency_bin_count, s->power_bin_count); } bool image_size_changed = image.width() != s->frequency_bin_count || image.height() != s->power_bin_count; std::uint64_t color_map_hash = color_scale->color_map.hash(); if (cache_color_map_hash != color_map_hash) { cache_color_map_hash = color_map_hash; image_dirty = true; } if (image_size_changed) { image.resize(s->frequency_bin_count, s->power_bin_count); image.fill(Color::transparent()); image_dirty = true; } input->pending_spectra.read_all([this, s, color_scale](const auto& data) { if (data.size() != s->frequency_bin_count) { return; } push_data(data, s, *color_scale); }); if (image_dirty) { image.write_normalized_grid(previous_intensity_grid.data(), s->frequency_bin_count, s->power_bin_count, color_scale->color_map); image_dirty = false; } frame_view.consume_input(); } }; template void Afterglow::Builder_T::init(std::shared_ptr parent, std::shared_ptr frequency_axis, std::shared_ptr power_axis) { ASSERT(parent && parent->attached_to_plot(), "Afterglow build error! parent invalid"); ASSERT(frequency_axis && power_axis, "Afterglow build error! axis == nullptr"); ASSERT(frequency_axis->attached_to_plot(), "Afterglow build error! frequency_axis->plot == nullptr"); ASSERT(frequency_axis->shares_plot_with(*power_axis), "Afterglow build error! frequency_axis->plot != power_axis->plot"); ASSERT(parent->shares_plot_with(*frequency_axis), "Afterglow build error! parent->plot != frequency_axis->plot"); ASSERT(frequency_axis->orientation() != power_axis->orientation(), "Afterglow build error! axes must be orthogonal"); this->parent = parent; static_cast(this)->frequency_axis = frequency_axis; static_cast(this)->power_axis = power_axis; } template void Afterglow::Builder_T::set(Afterglow* ret) { ret->init(parent); std::shared_ptr owner = ret->shared_from_this(); Afterglow_Private* pd = ret->d(); Render_Edit_Lease edit(pd); Afterglow_Render_State* sc = edit.operator->(); PROP_RT(std::weak_ptr, frequency_axis) PROP_RT(std::weak_ptr, power_axis) PROP_RT(Range, frequency_range) PROP_RT(int, frequency_bin_count) PROP_RT(int, power_bin_count) PROP_RT(bool, interpolate_power_bins) PROP_RT(double, decay_rate) PROP_RT(int, initial_intensity_threshold) sc->color_scale = static_cast(this)->color_scale.clone(); sc->frequency_bin_count = std::max(1, sc->frequency_bin_count); sc->power_bin_count = std::max(1, sc->power_bin_count); sc->decay_rate = std::clamp(sc->decay_rate, 0.0, 1.0); sc->initial_intensity_threshold = std::max(1, sc->initial_intensity_threshold); if (static_cast(this)->color_bar_enabled) { auto bar = Color_Bar::Builder(owner).set_color_scale(sc->color_scale).set_name("Afterglow").build(); pd->color_bar = bar; } } } // namespace renderive