test(render): a figure draws each row's own horizontal rulers
The figure-mode cursor test covered the *vertical* marker, which is one time shared by every row. A horizontal ruler is the other half: its y is on that row's own axis, so two rows' rulers are two different values, and every row's has to be drawn in its own band. A host that puts two rulers on every plot (`udpscope`'s cursors pane) needs exactly this, and had no test for it. It asserts the strokes only -- the ruler's value label goes through the text path, which this case is not about, so it uses the harness's no-atlas backend.
This commit is contained in:
1 parent
133cd680ce
commit
523446d08f
1 file changed
+77
@@ -5971,3 +5971,80 @@ PLOT_TEST(release_gl_shuts_down_and_the_renderer_can_reinit) {
|
||||
PLOT_CHECK(!r.last_frame_had_gl_error());
|
||||
ctx.destroy();
|
||||
}
|
||||
|
||||
// The other orientation, in a figure: a horizontal ruler is per row (its `y` is on that
|
||||
// row's y axis, so two rows' rulers are two different values), and every row's has to be
|
||||
// drawn in its own band. The vertical case above covers the shared-time half; this is the
|
||||
// half where each row owns its instrument, which is what an app's "two rulers per plot"
|
||||
// needs -- and which the app found undrawn.
|
||||
PLOT_TEST(figure_rows_draw_their_own_horizontal_rulers) {
|
||||
plottest::HeadlessContext ctx(kW, kH);
|
||||
plot::Status st;
|
||||
PLOT_CHECK(ctx.create(&st));
|
||||
PLOT_CHECK(plot::gl::load(nullptr));
|
||||
plot::Renderer r;
|
||||
PLOT_CHECK(r.init(&st));
|
||||
|
||||
const std::vector<plot::f32> samples = wave(101u, 0.0f);
|
||||
plot::data::ArraySeries series(samples, 0.0, 1.0);
|
||||
|
||||
// A strongly red ruler and a strongly green one, so a pixel that is red- or
|
||||
// green-dominant inside a row's axes can only be that row's ink: the trace is blue and
|
||||
// the chrome grey (all three channels equal).
|
||||
const plot::Color red = plot::Color::hex(0xFF2020);
|
||||
const plot::Color green = plot::Color::hex(0x20FF20);
|
||||
const plot::f32 y_scales[3] = {671.0e6f, 90.0f, 2.51f};
|
||||
|
||||
// The harness's no-atlas backend: a ruler always writes its value as text, and this case
|
||||
// is about the *stroke*, so the text is asked for and staged as nothing. (`OnePxPerString
|
||||
// Backend::glyph()` is null, which is what a real host without an atlas looks like.)
|
||||
OnePxPerStringBackend no_text;
|
||||
|
||||
plot::Figure figure(3u);
|
||||
figure.set_viewport({0, 0, static_cast<plot::f32>(kW), static_cast<plot::f32>(kH)});
|
||||
for (plot::u32 row = 0; row < 3u; ++row) {
|
||||
plot::Plot& p = figure.plot(row);
|
||||
const plot::u32 ax = p.add_x_axis({"t", {0.0, 100.0}, false, plot::AxisPlace::Bottom, false});
|
||||
const plot::u32 ay =
|
||||
p.add_y_axis({"v", {0.0, y_scales[row]}, false, plot::AxisPlace::Left, false});
|
||||
p.add_trace({&series, kRowBlue, 1.5f, ax, ay});
|
||||
p.set_text_backend(&no_text);
|
||||
// A quarter and three quarters up this row's own range: inside its axes whatever the
|
||||
// scale is, which is the point of a ruler being per row.
|
||||
p.add_h_ruler(0.25 * y_scales[row], red);
|
||||
p.add_h_ruler(0.75 * y_scales[row], green);
|
||||
}
|
||||
|
||||
r.begin_frame(kW, kH, {0, 0, static_cast<plot::f32>(kW), static_cast<plot::f32>(kH)});
|
||||
r.draw(figure);
|
||||
r.end_frame();
|
||||
PLOT_CHECK(!r.last_frame_had_gl_error());
|
||||
dump_if_requested(r, "figure_h_rulers.ppm");
|
||||
|
||||
const std::vector<plot::u8> frame = read_frame(ctx);
|
||||
// Rows of the frame that hold a fully covered red or green line, and which row's band
|
||||
// each falls in. The predicate demands full coverage exactly as the vertical case does.
|
||||
const auto lines_in = [&](const plot::Rect& area, bool want_red) {
|
||||
plot::u32 found = 0;
|
||||
for (plot::u32 y = static_cast<plot::u32>(area.y);
|
||||
y < static_cast<plot::u32>(area.bottom()); ++y) {
|
||||
plot::u32 run = 0;
|
||||
for (plot::u32 x = static_cast<plot::u32>(area.x);
|
||||
x < static_cast<plot::u32>(area.right()); ++x) {
|
||||
const plot::u8* px = &frame[(plot::usize(y) * ctx.width + x) * 4u];
|
||||
const bool ok = want_red ? (px[0] >= 200 && px[1] <= 80 && px[2] <= 80)
|
||||
: (px[1] >= 200 && px[0] <= 80 && px[2] <= 80);
|
||||
if (ok) ++run;
|
||||
}
|
||||
if (run > (area.w * 3u) / 4u) ++found;
|
||||
}
|
||||
return found;
|
||||
};
|
||||
const plot::FigureLayout layout = figure.layout();
|
||||
for (plot::u32 row = 0; row < 3u; ++row) {
|
||||
const plot::Rect& axes = figure.plot(row).layout().axes;
|
||||
PLOT_CHECK(lines_in(axes, true) >= 1u); // this row's own A ruler
|
||||
PLOT_CHECK(lines_in(axes, false) >= 1u); // and its B ruler
|
||||
}
|
||||
(void)layout;
|
||||
}
|
||||
Reference in new issue
Block a user