fix(render): convert Trace::width through the UI scale, and fold the controller's tolerance conversion into Style::device()
This commit is contained in:
1 parent
7500fc90d2
commit
69495a3799
7 files changed
+158
-24
No files matched your search
@@ -2418,6 +2418,20 @@ PLOT_TEST(legend_does_not_cover_the_plot_when_placed_bottom_left) {
|
||||
|
||||
namespace {
|
||||
|
||||
/// True for a pixel holding the axis line's chalk grey at a usable coverage: the line
|
||||
/// is `axis_line` (0x8A94A6) over the frame's dark background, so a third of the
|
||||
/// line's own level per channel is a pixel the stroke lifted off the background --
|
||||
/// `text_ink`'s coverage test, with a tone test on top that no blue trace and no dark
|
||||
/// grid line satisfies. The tick labels are the same chalk family, which is why the
|
||||
/// scans below keep clear of the label strip's own columns.
|
||||
bool axis_line_pixel(const plot::u8 px[4], plot::Color c) {
|
||||
const auto lifted = [](plot::u8 got, plot::f32 want) {
|
||||
return static_cast<plot::f32>(got) >= want * 255.0f * 0.33f;
|
||||
};
|
||||
const bool grey = static_cast<int>(px[2]) - static_cast<int>(px[0]) < 40;
|
||||
return lifted(px[0], c.r) && lifted(px[1], c.g) && lifted(px[2], c.b) && grey;
|
||||
}
|
||||
|
||||
/// The chrome one render reports, in **device** pixels. Both runs of the two-scale
|
||||
/// tests below fill this from a frame they really drew, so a comparison of the two
|
||||
/// figures is a comparison of two frames and not of two derivations of one rule.
|
||||
@@ -2663,3 +2677,113 @@ PLOT_TEST(dense_threshold_stays_a_device_ratio) {
|
||||
static_cast<double>(kThreshold));
|
||||
ctx.destroy();
|
||||
}
|
||||
|
||||
/// A trace's stroke is a chrome size like the axis lines', so the *weight* it is drawn
|
||||
/// with -- and with it the trace/axis hierarchy a reader sees -- must not depend on the
|
||||
/// display. `Trace::width` reaches the same `flush_lines()` the axis lines do, so it has
|
||||
/// to go through the same `Style::device()` conversion; left raw, a 1.5 px trace is 1.5
|
||||
/// *device* px against a 3 device px axis line at scale 2.
|
||||
///
|
||||
/// The measurement is the ink a fixed-size **device** rectangle holds over a flat trace
|
||||
/// (so the count is the stroke's width times a constant length, and cleaning up the
|
||||
/// anti-aliasing costs a fraction of a pixel rather than the whole figure), plus the
|
||||
/// same count over the y axis line. The assertion is the ratio between them: it is 1 at
|
||||
/// both scales when both strokes are converted, and it halves at scale 2 when the trace
|
||||
/// alone is not.
|
||||
PLOT_TEST(trace_stroke_weight_is_logical) {
|
||||
constexpr plot::u32 kBigW = 2u * kW;
|
||||
constexpr plot::u32 kBigH = 2u * kH;
|
||||
/// The width both strokes are given, in logical pixels: equal on purpose, so the
|
||||
/// trace/axis ratio the case pins is 1 rather than some arbitrary figure.
|
||||
constexpr plot::f32 kWidth = 3.0f;
|
||||
plottest::HeadlessContext ctx(kBigW, kBigH);
|
||||
plot::Status st;
|
||||
PLOT_CHECK(ctx.create(&st));
|
||||
PLOT_CHECK(plot::gl::load(nullptr));
|
||||
|
||||
plot::Renderer r;
|
||||
PLOT_CHECK(r.init(&st));
|
||||
r.style().line_width_px = kWidth; // the axis lines and cursor markers
|
||||
|
||||
// A flat trace, so every column of the scan crosses it exactly once and the ink
|
||||
// count is its width times the scan's length. y = 0.5 on a [0,1] axis is not a tick,
|
||||
// so no grid line runs along it.
|
||||
std::vector<plot::f32> flat(64, 0.5f);
|
||||
plot::data::ArraySeries s(std::move(flat), 0.0, 1.0);
|
||||
|
||||
/// The device ink of both strokes at one scale: pixels of trace colour in a
|
||||
/// 200x8 px box over the trace, and axis-line pixels in a 6x200 px box over the y
|
||||
/// axis line. Both boxes are the same *device* size at every scale, which is what
|
||||
/// makes the count proportional to the stroke's width alone.
|
||||
struct StrokeInk {
|
||||
plot::u32 trace = 0u;
|
||||
plot::u32 axis = 0u;
|
||||
};
|
||||
const auto ink_at = [&](plot::f32 scale) {
|
||||
const plot::u32 w = static_cast<plot::u32>(scale * static_cast<plot::f32>(kW));
|
||||
const plot::u32 h = static_cast<plot::u32>(scale * static_cast<plot::f32>(kH));
|
||||
plot::Plot p;
|
||||
p.set_viewport({0.0f, 0.0f, static_cast<plot::f32>(w), static_cast<plot::f32>(h)});
|
||||
const plot::u32 ax = p.add_x_axis({"t", {0.0, 10.0}, false, plot::AxisPlace::Bottom, false});
|
||||
const plot::u32 ay = p.add_y_axis({"v", {0.0, 1.0}, false, plot::AxisPlace::Left, false});
|
||||
p.add_trace({&s, plot::Color::hex(0x40C0FF), kWidth, ax, ay});
|
||||
|
||||
r.style().ui_scale = scale;
|
||||
r.begin_frame(kBigW, kBigH, {0.0f, 0.0f, static_cast<plot::f32>(w), static_cast<plot::f32>(h)});
|
||||
r.draw(p);
|
||||
r.end_frame();
|
||||
PLOT_CHECK(!r.last_frame_had_gl_error());
|
||||
|
||||
const plot::PlotLayout& lay = p.layout();
|
||||
// Generic over the predicate: the two strokes are recognised by different rules
|
||||
// (the trace by blue dominance, the axis line by its chalk grey).
|
||||
const auto count = [&](plot::u32 x0, plot::u32 y0, plot::u32 bw, plot::u32 bh, auto pred) {
|
||||
plot::u32 hits = 0;
|
||||
for (plot::u32 y = y0; y < y0 + bh; ++y) {
|
||||
for (plot::u32 x = x0; x < x0 + bw; ++x) {
|
||||
plot::u8 px[4] = {};
|
||||
PLOT_CHECK(ctx.read_pixel(x, kBigH - 1u - y, px));
|
||||
if (pred(px)) ++hits;
|
||||
}
|
||||
}
|
||||
return hits;
|
||||
};
|
||||
|
||||
StrokeInk ink;
|
||||
// The trace: a fixed device box inside the axes, centred on the stroke's row.
|
||||
const plot::u32 ty = static_cast<plot::u32>(p.y_transform(ay).to_screen_y(0.5));
|
||||
const plot::u32 tx0 = static_cast<plot::u32>(lay.axes.x) + 40u;
|
||||
ink.trace = count(tx0, ty - 4u, 200u, 8u,
|
||||
[](const plot::u8* px) { return trace_pixel(px); });
|
||||
// The axis line: two columns left of it, so the tick labels' own ink (which ends
|
||||
// `grid_label_pad` short of the line) is outside the scan at both scales.
|
||||
const plot::Color line = r.style().axis_line;
|
||||
ink.axis = count(static_cast<plot::u32>(lay.axes.x) - 2u,
|
||||
static_cast<plot::u32>(lay.axes.y) + 40u, 6u, 200u,
|
||||
[line](const plot::u8* px) { return axis_line_pixel(px, line); });
|
||||
return ink;
|
||||
};
|
||||
|
||||
const StrokeInk one = ink_at(1.0f);
|
||||
const StrokeInk two = ink_at(2.0f);
|
||||
const plot::f32 trace_ratio =
|
||||
static_cast<plot::f32>(two.trace) / static_cast<plot::f32>(one.trace);
|
||||
const plot::f32 axis_ratio =
|
||||
static_cast<plot::f32>(two.axis) / static_cast<plot::f32>(one.axis);
|
||||
std::printf("[trace] 1x trace %u axis %u; 2x trace %u axis %u; ratios %.3f / %.3f ->"
|
||||
" weight ratio %.3f\n",
|
||||
one.trace, one.axis, two.trace, two.axis, static_cast<double>(trace_ratio),
|
||||
static_cast<double>(axis_ratio),
|
||||
static_cast<double>(trace_ratio / axis_ratio));
|
||||
|
||||
// Not vacuous: the trace really is in the box (and the axis line in its own).
|
||||
PLOT_CHECK(one.trace > 100u);
|
||||
PLOT_CHECK(one.axis > 100u);
|
||||
// The same logical width at twice the scale is twice the device ink -- for the trace
|
||||
// exactly as for the axis line it is drawn against. A trace left in device pixels
|
||||
// fails this: its ink count is the same at both scales.
|
||||
PLOT_CHECK_NEAR(trace_ratio, 2.0f, 0.35f);
|
||||
PLOT_CHECK_NEAR(axis_ratio, 2.0f, 0.35f);
|
||||
PLOT_CHECK_NEAR(trace_ratio / axis_ratio, 1.0f, 0.15f);
|
||||
ctx.destroy();
|
||||
}
|
||||
Reference in new issue
Block a user