optional additive blending for the spectrogram
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
namespace baudmine {
|
||||
|
||||
namespace {
|
||||
constexpr float kLinearEpsilon = kLinearEpsilon; // threshold for log10 of linear power
|
||||
constexpr float kLinearEpsilon = 1e-20f; // threshold for log10 of linear power
|
||||
constexpr float kLogGuard = 1e-30f; // guard against log10(0) in compressed scale
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -467,6 +467,9 @@ void Application::render() {
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
ImGui::MenuItem("Grid", nullptr, &specDisplay_.showGrid);
|
||||
ImGui::MenuItem("Fill Spectrum", nullptr, &specDisplay_.fillSpectrum);
|
||||
ImGui::MenuItem("Additive Blend", nullptr, &specDisplay_.additiveBlend);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("Mix multi-channel spectrum colors additively");
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("VSync", nullptr, &vsync_)) {
|
||||
SDL_GL_SetSwapInterval(vsync_ ? 1 : 0);
|
||||
@@ -669,6 +672,7 @@ void Application::loadConfig() {
|
||||
showSidebar_ = config_.getBool("show_sidebar", showSidebar_);
|
||||
specDisplay_.peakHoldEnable = config_.getBool("peak_hold", specDisplay_.peakHoldEnable);
|
||||
specDisplay_.peakHoldDecay = config_.getFloat("peak_hold_decay", specDisplay_.peakHoldDecay);
|
||||
specDisplay_.additiveBlend = config_.getBool("additive_blend", specDisplay_.additiveBlend);
|
||||
cursors_.snapToPeaks = config_.getBool("snap_to_peaks", cursors_.snapToPeaks);
|
||||
measurements_.traceMinFreq = config_.getFloat("trace_min_freq", measurements_.traceMinFreq);
|
||||
measurements_.traceMaxFreq = config_.getFloat("trace_max_freq", measurements_.traceMaxFreq);
|
||||
@@ -732,6 +736,7 @@ void Application::saveConfig() const {
|
||||
cfg.setBool("show_sidebar", showSidebar_);
|
||||
cfg.setBool("peak_hold", specDisplay_.peakHoldEnable);
|
||||
cfg.setFloat("peak_hold_decay", specDisplay_.peakHoldDecay);
|
||||
cfg.setBool("additive_blend", specDisplay_.additiveBlend);
|
||||
cfg.setBool("snap_to_peaks", cursors_.snapToPeaks);
|
||||
cfg.setFloat("trace_min_freq", measurements_.traceMinFreq);
|
||||
cfg.setFloat("trace_max_freq", measurements_.traceMaxFreq);
|
||||
|
||||
@@ -2,8 +2,22 @@
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <GLES2/gl2.h>
|
||||
#else
|
||||
#include <GL/gl.h>
|
||||
#endif
|
||||
|
||||
namespace baudmine {
|
||||
|
||||
// ImGui draw-list callbacks to switch GL blend mode for additive color mixing.
|
||||
static void setAdditiveBlend(const ImDrawList*, const ImDrawCmd*) {
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
||||
}
|
||||
static void restoreDefaultBlend(const ImDrawList*, const ImDrawCmd*) {
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
static float freqToLogFrac(double freq, double minFreq, double maxFreq) {
|
||||
if (freq <= 0 || minFreq <= 0) return 0.0f;
|
||||
double logMin = std::log10(minFreq);
|
||||
@@ -152,52 +166,75 @@ void SpectrumDisplay::draw(const std::vector<std::vector<float>>& spectra,
|
||||
}
|
||||
}
|
||||
|
||||
// Draw each channel's spectrum.
|
||||
std::vector<ImVec2> points;
|
||||
// Build polylines for all channels.
|
||||
int nCh = static_cast<int>(spectra.size());
|
||||
std::vector<std::vector<ImVec2>> allPoints(nCh);
|
||||
for (int ch = 0; ch < nCh; ++ch) {
|
||||
if (spectra[ch].empty()) continue;
|
||||
const ChannelStyle& st = (ch < static_cast<int>(styles.size()))
|
||||
? styles[ch]
|
||||
: styles.back();
|
||||
|
||||
buildPolyline(spectra[ch], minDB, maxDB,
|
||||
isIQ, freqScale, posX, posY, sizeX, sizeY,
|
||||
viewLo, viewHi, points);
|
||||
viewLo, viewHi, allPoints[ch]);
|
||||
}
|
||||
|
||||
// Fill
|
||||
if (fillSpectrum && points.size() >= 2) {
|
||||
for (size_t i = 0; i + 1 < points.size(); ++i) {
|
||||
ImVec2 tl = points[i];
|
||||
ImVec2 tr = points[i + 1];
|
||||
ImVec2 bl = {tl.x, posY + sizeY};
|
||||
ImVec2 br = {tr.x, posY + sizeY};
|
||||
dl->AddQuadFilled(tl, tr, br, bl, st.fillColor);
|
||||
// Draw spectra: all fills first, then all lines on top.
|
||||
// Multi-channel uses additive GL blending so colors mix (green + purple = white).
|
||||
{
|
||||
bool additive = additiveBlend && nCh > 1;
|
||||
// Use lower alpha under additive blend to avoid oversaturation.
|
||||
constexpr ImU8 kLineAlpha = 180;
|
||||
constexpr ImU8 kFillAlpha = 30;
|
||||
|
||||
if (additive)
|
||||
dl->AddCallback(setAdditiveBlend, nullptr);
|
||||
|
||||
// Pass 1: fills (drawn first so lines sit on top of all fills).
|
||||
if (fillSpectrum) {
|
||||
for (int ch = 0; ch < nCh; ++ch) {
|
||||
const auto& pts = allPoints[ch];
|
||||
if (pts.size() < 2) continue;
|
||||
const ChannelStyle& st = (ch < static_cast<int>(styles.size()))
|
||||
? styles[ch] : styles.back();
|
||||
ImU32 col = additive
|
||||
? ((st.fillColor & 0x00FFFFFF) | (static_cast<ImU32>(kFillAlpha) << 24))
|
||||
: st.fillColor;
|
||||
for (size_t i = 0; i + 1 < pts.size(); ++i) {
|
||||
dl->AddQuadFilled(pts[i], pts[i + 1],
|
||||
{pts[i + 1].x, posY + sizeY},
|
||||
{pts[i].x, posY + sizeY}, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Line
|
||||
if (points.size() >= 2)
|
||||
dl->AddPolyline(points.data(), static_cast<int>(points.size()),
|
||||
st.lineColor, ImDrawFlags_None, 1.5f);
|
||||
// Pass 2: lines.
|
||||
for (int ch = 0; ch < nCh; ++ch) {
|
||||
const auto& pts = allPoints[ch];
|
||||
if (pts.size() < 2) continue;
|
||||
const ChannelStyle& st = (ch < static_cast<int>(styles.size()))
|
||||
? styles[ch] : styles.back();
|
||||
ImU32 col = additive
|
||||
? ((st.lineColor & 0x00FFFFFF) | (static_cast<ImU32>(kLineAlpha) << 24))
|
||||
: st.lineColor;
|
||||
dl->AddPolyline(pts.data(), static_cast<int>(pts.size()),
|
||||
col, ImDrawFlags_None, 1.5f);
|
||||
}
|
||||
|
||||
if (additive)
|
||||
dl->AddCallback(restoreDefaultBlend, nullptr);
|
||||
}
|
||||
|
||||
// Peak hold traces (drawn as dashed-style thin lines above the live spectrum).
|
||||
// Peak hold traces.
|
||||
if (peakHoldEnable && !peakHold_.empty()) {
|
||||
std::vector<ImVec2> phPoints;
|
||||
for (int ch = 0; ch < nCh && ch < static_cast<int>(peakHold_.size()); ++ch) {
|
||||
if (peakHold_[ch].empty()) continue;
|
||||
const ChannelStyle& st = (ch < static_cast<int>(styles.size()))
|
||||
? styles[ch] : styles.back();
|
||||
|
||||
// Use the same line color but dimmer.
|
||||
ImU32 col = (st.lineColor & 0x00FFFFFF) | 0x90000000;
|
||||
|
||||
buildPolyline(peakHold_[ch], minDB, maxDB,
|
||||
isIQ, freqScale, posX, posY, sizeX, sizeY,
|
||||
viewLo, viewHi, points);
|
||||
|
||||
if (points.size() >= 2)
|
||||
dl->AddPolyline(points.data(), static_cast<int>(points.size()),
|
||||
viewLo, viewHi, phPoints);
|
||||
if (phPoints.size() >= 2)
|
||||
dl->AddPolyline(phPoints.data(), static_cast<int>(phPoints.size()),
|
||||
col, ImDrawFlags_None, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
|
||||
bool showGrid = true;
|
||||
bool fillSpectrum = false;
|
||||
bool additiveBlend = true; // additive color mixing for multi-channel
|
||||
bool peakHoldEnable = false;
|
||||
float peakHoldDecay = 20.0f; // dB/second decay rate
|
||||
|
||||
|
||||
@@ -18,15 +18,16 @@ struct UIState {
|
||||
int waterfallChannel = 0;
|
||||
bool waterfallMultiCh = true;
|
||||
std::array<bool, kMaxChannels> channelEnabled = {true,true,true,true,true,true,true,true};
|
||||
// Complementary pairs: colors at indices 0+1, 2+3, 4+5, 6+7 sum to white.
|
||||
std::array<ImVec4, kMaxChannels> channelColors = {{
|
||||
{0.20f, 0.90f, 0.30f, 1.0f}, // green
|
||||
{0.70f, 0.30f, 1.00f, 1.0f}, // purple
|
||||
{0.20f, 0.90f, 0.20f, 1.0f}, // green
|
||||
{0.80f, 0.10f, 0.80f, 1.0f}, // purple
|
||||
{1.00f, 0.55f, 0.00f, 1.0f}, // orange
|
||||
{0.00f, 0.75f, 1.00f, 1.0f}, // cyan
|
||||
{0.00f, 0.45f, 1.00f, 1.0f}, // cyan
|
||||
{1.00f, 0.25f, 0.25f, 1.0f}, // red
|
||||
{1.00f, 1.00f, 0.30f, 1.0f}, // yellow
|
||||
{0.50f, 0.80f, 0.50f, 1.0f}, // light green
|
||||
{0.80f, 0.50f, 0.80f, 1.0f}, // pink
|
||||
{0.00f, 0.75f, 0.75f, 1.0f}, // teal
|
||||
{1.00f, 1.00f, 0.20f, 1.0f}, // yellow
|
||||
{0.00f, 0.00f, 0.80f, 1.0f}, // blue
|
||||
}};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user