仍有bug

This commit is contained in:
2026-08-17 15:24:28 +08:00
parent e793c6a278
commit 5e976b27dd
39 changed files with 1059 additions and 6658 deletions
+35 -340
View File
@@ -1,350 +1,45 @@
#include "Web_Plot_Session.h"
#include "Pixel_Frame.h"
#include "render_2D/export.h"
#include <renderive/error/Error_Policy.hpp>
#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <mutex>
#include <stdexcept>
#include "render_2D/scene/Render_Scene_2D.h"
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
namespace renderive::web {
namespace {
Color blend(Color first, Color second, double amount) {
const auto channel = [amount](std::uint8_t a, std::uint8_t b) {
return static_cast<std::uint8_t>(std::clamp(
std::lround(a + (b - a) * amount), 0L, 255L));
};
return {
channel(first.r, second.r), channel(first.g, second.g),
channel(first.b, second.b), channel(first.a, second.a)
};
}
Color_Map radio_color_map() {
constexpr Color stops[] = {
{3, 7, 18, 255}, {16, 52, 105, 255}, {23, 163, 184, 255},
{238, 210, 91, 255}, {239, 68, 68, 255}
};
std::vector<Pixel> colors;
colors.reserve(256);
for (int index = 0; index < 256; ++index) {
const double position = index / 255.0 * (std::size(stops) - 1);
const auto stop = static_cast<std::size_t>(std::floor(position));
const auto next = std::min(stop + 1, std::size(stops) - 1);
colors.push_back(premultiply(blend(stops[stop], stops[next], position - stop)));
}
return Color_Map(std::move(colors));
}
Time_Of_Day current_time_of_day() {
constexpr std::int64_t day_ms = 24LL * 60LL * 60LL * 1000LL;
const auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
return {now % day_ms};
}
double gaussian(double x, double center, double width) {
const double normalized = (x - center) / width;
return std::exp(-0.5 * normalized * normalized);
}
} // namespace
struct Web_Plot_Session::Impl {
Render_Scene_2D plot;
renderive_Owner<Frequency_Axis> spectrum_frequency_axis;
renderive_Owner<Axis> spectrum_power_axis;
renderive_Owner<Frequency_Axis> waterfall_frequency_axis;
renderive_Owner<Time_Axis> waterfall_time_axis;
renderive_Owner<Spectrum> spectrum;
renderive_Owner<Waterfall> waterfall;
renderive_Owner<Selection_Rectangle_Overlay> selection;
Demo_Mode mode = Demo_Mode::Fm;
double gain_db = 8.0;
std::uint64_t frame_index{};
std::mutex mutex;
Impl() {
plot.init();
plot.set_background_color({3, 7, 18, 255});
if (!plot.set_max_render_fps(30.0))
::renderive::error::unexpected<std::logic_error>(
"validated web plot frame rate was rejected");
plot.set_viewport_size({960, 600});
const auto root = plot.root_renderable();
constexpr Color axis_color{93, 116, 151, 255};
const Range initial_frequency{101'300'000.0, 103'700'000.0};
{
auto attach = plot.attach_builder();
const auto make_group = [&](std::string name) {
auto group = detail::make_renderable_group(true);
group->set_object_name(std::move(name));
attach.attach(group);
attach.add_display_parent(group, root);
attach.add_dependency_parent(group, root);
return group;
};
const auto data = make_group("Web_Data");
const auto axes = make_group("Web_Axes");
const auto overlay = make_group("Web_Overlay");
axes->set_cache_mode(Renderable_Cache_Mode::Local_Pixel);
spectrum_frequency_axis =
Frequency_Axis::Builder{&attach}
.set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal)
.set<&Frequency_Axis::Properties::coordinates>(initial_frequency)
.set<&Frequency_Axis::Properties::precision>(2)
.set<&Frequency_Axis::Properties::tick_length>(8)
.set<&Frequency_Axis::Properties::sub_tick_length>(4)
.set<&Frequency_Axis::Properties::color>(axis_color)
.set<&Frequency_Axis::Properties::wheel>(true)
.set<&Frequency_Axis::Properties::drag>(true)
.build(axes);
spectrum_power_axis =
Axis::Builder{&attach}
.set<&Axis::Properties::orientation>(Orientation::Vertical)
.set<&Axis::Properties::coordinates>(Range{-20.0, -120.0})
.set<&Axis::Properties::precision>(0)
.set<&Axis::Properties::tick_length>(-8)
.set<&Axis::Properties::sub_tick_length>(-4)
.set<&Axis::Properties::color>(axis_color)
.set<&Axis::Properties::unit_text>("dBm")
.build(axes);
waterfall_frequency_axis =
Frequency_Axis::Builder{&attach}
.set<&Frequency_Axis::Properties::orientation>(Orientation::Horizontal)
.set<&Frequency_Axis::Properties::coordinates>(initial_frequency)
.set<&Frequency_Axis::Properties::precision>(2)
.set<&Frequency_Axis::Properties::tick_length>(8)
.set<&Frequency_Axis::Properties::sub_tick_length>(4)
.set<&Frequency_Axis::Properties::color>(axis_color)
.set<&Frequency_Axis::Properties::wheel>(true)
.set<&Frequency_Axis::Properties::drag>(true)
.build(axes);
waterfall_time_axis =
Time_Axis::Builder{&attach}
.set<&Time_Axis::Properties::orientation>(Orientation::Vertical)
.set<&Time_Axis::Properties::visible_count>(72)
.set<&Time_Axis::Properties::tick_label_spacing_px>(34)
.set<&Time_Axis::Properties::format>("mm:ss")
.set<&Time_Axis::Properties::tick_length>(-8)
.set<&Time_Axis::Properties::sub_tick_length>(-4)
.set<&Time_Axis::Properties::color>(axis_color)
.build(axes);
spectrum = Spectrum::Builder{&attach}
.set<&Spectrum::Properties::frequency_range>(initial_frequency)
.set<&Spectrum::Properties::frequency_point_size>(768)
.set<&Spectrum::Properties::center_frequency>(102'500'000.0)
.set<&Spectrum::Properties::sweep_frequency_range>(Range{102'200'000.0, 102'800'000.0})
.set<&Spectrum::Properties::max_marker_visible>(true)
.set<&Spectrum::Properties::sweep_region_visible>(true)
.set<&Spectrum::Properties::interpolation_mode>(Line_Interpolation_Mode::Cubic_Value)
.build(data, spectrum_frequency_axis, spectrum_power_axis);
spectrum->set<&Spectrum::Properties::current_pen>(Pen{
Color{57, 224, 177, 255}, 2.0, Line_Style::Solid,
Line_Cap::Round, Line_Join::Round
});
spectrum->set<&Spectrum::Properties::current_brush>(Brush{Color{31, 174, 145, 32}, Brush_Style::Solid});
spectrum->set<&Spectrum::Properties::max_pen>(Pen{Color{250, 204, 21, 210}, 1.0});
spectrum->set<&Spectrum::Properties::middle_frequency_pen>(Pen{
Color{56, 189, 248, 220}, 1.0,
Line_Style::Dash
});
waterfall = Waterfall::Builder{&attach}
.set<&Waterfall::Properties::frequency_range>(initial_frequency)
.set<&Waterfall::Properties::power_range>(Range{-120.0, -20.0})
.set<&Waterfall::Properties::frequency_bin_count>(768)
.set<&Waterfall::Properties::interpolation_mode>(Image_Interpolation_Mode::Bilinear)
.set<&Waterfall::Properties::color_map>(radio_color_map())
.build(data, waterfall_frequency_axis, waterfall_time_axis);
selection = Selection_Rectangle_Overlay::Builder{&attach}
.set<&Selection_Rectangle_Overlay::Properties::selection_brush>(Brush{Color{56, 189, 248, 36}, Brush_Style::Solid})
.set<&Selection_Rectangle_Overlay::Properties::selection_border_pen>(Pen{Color{125, 211, 252, 230}, 1.0, Line_Style::Dash})
.build(overlay, spectrum_frequency_axis, spectrum_power_axis);
}
apply_layout(plot.viewport_size());
plot.activate_view();
update_model();
const auto initial_render = plot.render_frame(true);
if (!initial_render || *initial_render != Plot_Render_Status::rendered)
plot.request_redraw();
}
~Impl() {
plot.deactivate_view();
}
void apply_layout(Size viewport) {
const int left = viewport.width < 620 ? 54 : 72;
const int right = viewport.width < 620 ? 44 : 70;
const int top = 16;
const int bottom = viewport.height < 480 ? 24 : 32;
const int gap = viewport.height < 480 ? 36 : 48;
const int content_width = std::max(1, viewport.width - left - right);
const int available_height = std::max(2, viewport.height - top - bottom - gap);
const int spectrum_height = std::max(1, available_height * 43 / 100);
const int waterfall_y = top + spectrum_height + gap;
const int waterfall_height = std::max(1, viewport.height - waterfall_y - bottom);
spectrum_frequency_axis->update([&](Axis_Properties& state) {
state.x = left;
state.y = top + spectrum_height;
state.pixel_length = static_cast<std::size_t>(content_width);
});
spectrum_power_axis->update([&](Axis_Properties& state) {
state.x = left;
state.y = top;
state.pixel_length = static_cast<std::size_t>(spectrum_height);
});
waterfall_frequency_axis->update([&](Axis_Properties& state) {
state.x = left;
state.y = waterfall_y + waterfall_height;
state.pixel_length = static_cast<std::size_t>(content_width);
});
waterfall_time_axis->update([&](auto& state) {
state.x = left;
state.y = waterfall_y;
state.pixel_length = static_cast<std::size_t>(waterfall_height);
});
}
void update_model() {
constexpr int sample_count = 768;
const double time = static_cast<double>(frame_index) * 0.075;
std::vector<double> samples(sample_count);
for (int index = 0; index < sample_count; ++index) {
const double x = static_cast<double>(index) / (sample_count - 1);
const double noise = std::sin(index * 12.9898 + frame_index * 0.371) *
std::sin(index * 0.137 + frame_index * 0.071);
double signal{};
switch (mode) {
case Demo_Mode::Am:
signal = 66.0 * gaussian(x, 0.5, 0.008) +
39.0 * gaussian(x, 0.42, 0.018) +
39.0 * gaussian(x, 0.58, 0.018);
break;
case Demo_Mode::Fm: {
const double moving = 0.5 + std::sin(time) * 0.035;
signal = 58.0 * gaussian(x, moving, 0.055) +
25.0 * gaussian(x, moving - 0.11, 0.023) +
25.0 * gaussian(x, moving + 0.11, 0.023);
break;
}
case Demo_Mode::Usb:
signal = 61.0 * gaussian(x, 0.57, 0.045) +
28.0 * gaussian(x, 0.68, 0.025);
break;
case Demo_Mode::Lsb:
signal = 61.0 * gaussian(x, 0.43, 0.045) +
28.0 * gaussian(x, 0.32, 0.025);
break;
}
samples[index] = std::clamp(-110.0 + noise * 4.5 + signal + gain_db,
-120.0, -20.0);
}
spectrum->update_samples(samples);
if ((frame_index & 1U) == 0U)
waterfall->append_row(current_time_of_day(), samples);
++frame_index;
}
void set_center_frequency(double megahertz) {
const double center = megahertz * 1'000'000.0;
const double bandwidth = spectrum_frequency_axis->get<&Axis_Properties::coordinates>().size();
const Range range{center - bandwidth * 0.5, center + bandwidth * 0.5};
spectrum_frequency_axis->set<&Axis_Properties::coordinates>(range);
waterfall_frequency_axis->set<&Axis_Properties::coordinates>(range);
spectrum->set<&Spectrum::Properties::frequency_range>(range);
spectrum->set<&Spectrum::Properties::center_frequency>(center);
spectrum->set<&Spectrum::Properties::sweep_frequency_range>(Range{
center - bandwidth * 0.125,
center + bandwidth * 0.125
});
waterfall->set<&Waterfall::Properties::frequency_range>(range);
}
void set_bandwidth(double kilohertz) {
const double center = spectrum->get<&Spectrum::Properties::center_frequency>();
const double bandwidth = kilohertz * 1000.0;
const Range range{center - bandwidth * 0.5, center + bandwidth * 0.5};
spectrum_frequency_axis->set<&Axis_Properties::coordinates>(range);
waterfall_frequency_axis->set<&Axis_Properties::coordinates>(range);
spectrum->set<&Spectrum::Properties::frequency_range>(range);
spectrum->set<&Spectrum::Properties::sweep_frequency_range>(Range{
center - bandwidth * 0.125,
center + bandwidth * 0.125
});
waterfall->set<&Waterfall::Properties::frequency_range>(range);
}
std::optional<std::string> render_pixels() {
if (!plot.view_active())
return std::nullopt;
update_model();
const auto render = plot.render_frame(true);
if (!render || *render != Plot_Render_Status::rendered)
return std::nullopt;
std::string pixels;
const Color background = plot.background_color();
const std::uint64_t sequence = plot.frame_status().latest_sequence;
plot.with_frame([&pixels, background, sequence](Image_View image) {
pixels = encode_pixel_frame(image, sequence, background);
});
return pixels.empty() ? std::nullopt : std::optional<std::string>(std::move(pixels));
Size viewport{960, 600};
std::uint64_t sequence{};
std::unique_ptr<Render_Scene_2D> scene;
Impl() { rebuild(); }
void rebuild() {
Render_Scene_2D::State state;
state.viewport = viewport;
scene = Render_Scene_2D::Builder{state}.build(
std::make_shared<Low_Latency_Render_Scene_2D_Strategy>());
scene->activate_view();
}
std::optional<Web_Response> handle(const Web_Event& event) {
std::lock_guard lock(mutex);
return std::visit(
[this](const auto& value) -> std::optional<Web_Response> {
using T = std::decay_t<decltype(value)>;
if constexpr (std::is_same_v<T, Frame_Request>) {
auto pixels = render_pixels();
if (pixels)
return Web_Response{Web_Response_Type::Pixels, std::move(*pixels)};
}
else if constexpr (std::is_same_v<T, Viewport_Resize>) {
const Size previous = plot.viewport_size();
plot.set_viewport_size(value.size);
apply_layout(value.size);
Resize_Event resized;
resized.old_size = previous;
resized.new_size = value.size;
plot.dispatch_event(resized);
}
else if constexpr (std::is_same_v<T, Event>) {
if (value.type == Event_Type::Show)
plot.activate_view();
else if (value.type == Event_Type::Hide)
plot.deactivate_view();
plot.dispatch_event(value);
}
else if constexpr (Event_Object<T>) {
plot.dispatch_event(value);
}
else if constexpr (std::is_same_v<T, Set_Demo_Mode>) {
mode = value.mode;
plot.request_redraw();
}
else if constexpr (std::is_same_v<T, Set_Center_Frequency>) {
set_center_frequency(value.megahertz);
}
else if constexpr (std::is_same_v<T, Set_Bandwidth>) {
set_bandwidth(value.kilohertz);
}
else if constexpr (std::is_same_v<T, Set_Gain>) {
gain_db = value.decibels;
plot.request_redraw();
}
else if constexpr (std::is_same_v<T, Set_Max_Hold>) {
spectrum->set<&Spectrum::Properties::max_hold_visible>(value.enabled);
}
else if constexpr (std::is_same_v<T, Set_Smoothing>) {
spectrum->set<&Spectrum::Properties::interpolation_mode>(
value.enabled
? Line_Interpolation_Mode::Cubic_Value
: Line_Interpolation_Mode::Nearest_Sample);
waterfall->set<&Waterfall::Properties::interpolation_mode>(
value.enabled
? Image_Interpolation_Mode::Bilinear
: Image_Interpolation_Mode::Nearest);
}
else if constexpr (std::is_same_v<T, Clear_Selection>) {
selection->clear_selected_regions();
}
return std::nullopt;
},
event);
return std::visit([this](const auto& value) -> std::optional<Web_Response> {
using T = std::decay_t<decltype(value)>;
if constexpr (std::same_as<T, Frame_Request>) {
const auto result = scene->render_frame(true);
if (!result || scene->wait_for_render() != Scene_Render_Result::none)
return std::nullopt;
std::string pixels;
scene->with_frame([&](Image_View image) {
pixels = encode_pixel_frame(image, ++sequence);
});
if (pixels.empty()) return std::nullopt;
return Web_Response{Web_Response_Type::Pixels,
std::move(pixels)};
} else if constexpr (std::same_as<T, Viewport_Resize>) {
viewport = value.size;
rebuild();
} else if constexpr (std::derived_from<T, Event>) {
scene->dispatch_event(value);
}
return std::nullopt;
}, event);
}
};
Web_Plot_Session::Web_Plot_Session() : impl_(std::make_unique<Impl>()) {}