242 lines
11 KiB
C++
242 lines
11 KiB
C++
#include <render_2D/plottable/Afterglow.hpp>
|
|
#include <render_2D/plottable/Constellation_Diagram.hpp>
|
|
#include <render_2D/plottable/Frequency_Trace.hpp>
|
|
#include <render_2D/plottable/Spectrum.hpp>
|
|
#include <render_2D/plottable/Sweep_Spectrum.hpp>
|
|
#include <render_2D/plottable/Waterfall.hpp>
|
|
#include <render_2D/plottable/common/Raster_Plot.hpp>
|
|
#include <gtest/gtest.h>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
using namespace aethera;
|
|
using namespace aethera::render_2d;
|
|
|
|
template <typename Object, typename... Arguments>
|
|
std::unique_ptr<Object> build_object(Arguments&&... arguments) {
|
|
typename Object::template Builder<Object> builder(std::forward<Arguments>(arguments)...);
|
|
auto result = builder.build();
|
|
if (!result) std::terminate();
|
|
return std::move(result).value();
|
|
}
|
|
}
|
|
|
|
TEST(partition_configuration,
|
|
builder_and_runtime_share_the_authoritative_partition_properties) {
|
|
using Frequency = Frequency_Axis;
|
|
using Power = Numeric_Axis;
|
|
using Time = Time_Axis;
|
|
using Spectrum_Object = Spectrum;
|
|
using Trace_Object = Frequency_Trace;
|
|
using Sweep_Object = Sweep_Spectrum;
|
|
using Afterglow_Object = Afterglow;
|
|
using Constellation_Object = Constellation_Diagram;
|
|
using Waterfall_Object = Waterfall;
|
|
|
|
auto frequency = build_object<Frequency>();
|
|
auto power = build_object<Power>();
|
|
auto q_axis = build_object<Power>();
|
|
auto time = build_object<Time>();
|
|
auto spectrum = build_object<Spectrum_Object>(frequency.get(), power.get(),
|
|
6u);
|
|
auto trace = build_object<Trace_Object>(time.get(), power.get(), 4u);
|
|
auto sweep = build_object<Sweep_Object>(frequency.get(), power.get(), 4u);
|
|
auto afterglow = build_object<Afterglow_Object>(
|
|
frequency.get(), power.get(), Plot_Partition_Grid{2, 3});
|
|
auto constellation = build_object<Constellation_Object>(
|
|
power.get(), q_axis.get(), 7u);
|
|
auto waterfall = build_object<Waterfall_Object>(
|
|
frequency.get(), time.get(), Plot_Partition_Grid{2, 3});
|
|
|
|
EXPECT_EQ(spectrum->read_prop<Spectrum::Base_Tag>().partition_count, 6u);
|
|
EXPECT_EQ(constellation->read_prop<Constellation_Diagram::Base_Tag>()
|
|
.partition_count,
|
|
7u);
|
|
EXPECT_EQ(waterfall->read_prop<Waterfall::Base_Tag>().partition_grid,
|
|
(Plot_Partition_Grid{2, 3}));
|
|
EXPECT_FALSE(spectrum->read_prop<Renderable_2D::Base_Tag>().cache_enabled);
|
|
EXPECT_FALSE(trace->read_prop<Renderable_2D::Base_Tag>().cache_enabled);
|
|
EXPECT_FALSE(sweep->read_prop<Renderable_2D::Base_Tag>().cache_enabled);
|
|
EXPECT_FALSE(afterglow->read_prop<Renderable_2D::Base_Tag>().cache_enabled);
|
|
EXPECT_FALSE(waterfall->read_prop<Renderable_2D::Base_Tag>().cache_enabled);
|
|
EXPECT_FALSE(constellation->read_prop<Renderable_2D::Base_Tag>().cache_enabled);
|
|
|
|
spectrum->set<&Spectrum::Prop::partition_count>(3u);
|
|
constellation->set<&Constellation_Diagram::Prop::partition_count>(2u);
|
|
waterfall->set<&Waterfall::Prop::partition_grid>(
|
|
Plot_Partition_Grid{4, 5});
|
|
EXPECT_EQ(spectrum->read_prop<Spectrum::Base_Tag>().partition_count, 3u);
|
|
EXPECT_EQ(constellation->read_prop<Constellation_Diagram::Base_Tag>()
|
|
.partition_count,
|
|
2u);
|
|
EXPECT_EQ(waterfall->read_prop<Waterfall::Base_Tag>().partition_grid,
|
|
(Plot_Partition_Grid{4, 5}));
|
|
|
|
spectrum->set<&Spectrum::Prop::partition_count>(0u);
|
|
waterfall->set<&Waterfall::Prop::partition_grid>(
|
|
Plot_Partition_Grid{0, 2});
|
|
EXPECT_EQ(spectrum->read_prop<Spectrum::Base_Tag>().partition_count, 0u);
|
|
EXPECT_EQ(waterfall->read_prop<Waterfall::Base_Tag>().partition_grid,
|
|
(Plot_Partition_Grid{0, 2}));
|
|
}
|
|
|
|
TEST(partition_configuration,
|
|
raster_grid_matches_a_single_framebuffer_view_without_seams) {
|
|
const Size canvas{137, 93};
|
|
const aethera::render_2d::detail::Raster_Layout layout{
|
|
19, 13, Rect_F{3.25, 5.5, 128.5, 80.25}, false, false, true};
|
|
std::vector<Pixel> pixels(
|
|
static_cast<std::size_t>(layout.width) * layout.height);
|
|
for (int y = 0; y < layout.height; ++y) {
|
|
for (int x = 0; x < layout.width; ++x) {
|
|
const auto red = static_cast<Pixel>((x * 255) /
|
|
(layout.width - 1));
|
|
const auto green = static_cast<Pixel>((y * 255) /
|
|
(layout.height - 1));
|
|
pixels[static_cast<std::size_t>(y) * layout.width + x] =
|
|
0xff000000u | (red << 16u) | (green << 8u) |
|
|
static_cast<Pixel>((x + y) & 0xff);
|
|
}
|
|
}
|
|
|
|
Blend2D_Cache single;
|
|
single.ensure_size(canvas);
|
|
single.clear();
|
|
{
|
|
aethera::render_2d::detail::Painter painter(single, canvas);
|
|
aethera::render_2d::detail::paint_raster(
|
|
painter, layout, pixels, Image_Interpolation_Mode::bilinear);
|
|
}
|
|
|
|
Blend2D_Cache partitioned;
|
|
partitioned.ensure_size(canvas);
|
|
partitioned.clear();
|
|
constexpr Plot_Partition_Grid grid{4, 3};
|
|
for (Plot_Partition_Count index = 0;
|
|
index < aethera::render_2d::detail::raster_partition_count(grid);
|
|
++index) {
|
|
const Rect_F region =
|
|
aethera::render_2d::detail::raster_paint_region(
|
|
layout, index, grid);
|
|
aethera::render_2d::detail::Painter painter(
|
|
partitioned, canvas, region);
|
|
aethera::render_2d::detail::paint_raster(
|
|
painter, layout, pixels, Image_Interpolation_Mode::bilinear);
|
|
}
|
|
|
|
const Image_View expected = single.view();
|
|
const Image_View actual = partitioned.view();
|
|
ASSERT_EQ(actual.width, expected.width);
|
|
ASSERT_EQ(actual.height, expected.height);
|
|
for (int y = 0; y < expected.height; ++y) {
|
|
const auto* expected_row = reinterpret_cast<const Pixel*>(
|
|
expected.data + static_cast<std::ptrdiff_t>(y) * expected.stride);
|
|
const auto* actual_row = reinterpret_cast<const Pixel*>(
|
|
actual.data + static_cast<std::ptrdiff_t>(y) * actual.stride);
|
|
for (int x = 0; x < expected.width; ++x)
|
|
EXPECT_EQ(actual_row[x], expected_row[x])
|
|
<< "pixel mismatch at " << x << ", " << y;
|
|
}
|
|
}
|
|
|
|
TEST(partition_configuration,
|
|
bilinear_tiles_with_source_halo_match_one_complete_heatmap) {
|
|
const Size canvas{137, 93};
|
|
const aethera::render_2d::detail::Raster_Layout layout{
|
|
19, 13, Rect_F{3.25, 5.5, 128.5, 80.25}, false, false, true};
|
|
std::vector<Pixel> pixels(
|
|
static_cast<std::size_t>(layout.width) * layout.height);
|
|
for (int y = 0; y < layout.height; ++y)
|
|
for (int x = 0; x < layout.width; ++x)
|
|
pixels[static_cast<std::size_t>(y) * layout.width + x] =
|
|
0xff000000u | (static_cast<Pixel>(x * 13) << 16u) |
|
|
(static_cast<Pixel>(y * 17) << 8u);
|
|
|
|
Blend2D_Cache expected_cache;
|
|
expected_cache.ensure_size(canvas);
|
|
expected_cache.clear();
|
|
{
|
|
aethera::render_2d::detail::Painter painter(expected_cache, canvas);
|
|
aethera::render_2d::detail::paint_raster(
|
|
painter, layout, pixels, Image_Interpolation_Mode::bilinear);
|
|
}
|
|
|
|
Blend2D_Cache actual_cache;
|
|
actual_cache.ensure_size(canvas);
|
|
actual_cache.clear();
|
|
constexpr Plot_Partition_Grid grid{4, 3};
|
|
const Rect_F target = layout.target.normalized();
|
|
for (Plot_Partition_Count index = 0;
|
|
index < aethera::render_2d::detail::raster_partition_count(grid);
|
|
++index) {
|
|
const auto core = aethera::render_2d::detail::raster_partition(
|
|
layout, index, grid);
|
|
const aethera::render_2d::detail::Raster_Partition source{
|
|
std::max(0, core.first_x - 1),
|
|
std::min(layout.width, core.last_x + 1),
|
|
std::max(0, core.first_y - 1),
|
|
std::min(layout.height, core.last_y + 1)};
|
|
const int width = source.last_x - source.first_x;
|
|
const int height = source.last_y - source.first_y;
|
|
std::vector<Pixel> tile(static_cast<std::size_t>(width) * height);
|
|
for (int y = source.first_y; y < source.last_y; ++y)
|
|
std::copy_n(pixels.begin() + static_cast<std::ptrdiff_t>(y) *
|
|
layout.width + source.first_x,
|
|
width,
|
|
tile.begin() + static_cast<std::ptrdiff_t>(
|
|
y - source.first_y) * width);
|
|
const auto project_x = [&](int boundary) {
|
|
return target.x + target.width * boundary / layout.width;
|
|
};
|
|
const auto project_y = [&](int boundary) {
|
|
return target.y + target.height * boundary / layout.height;
|
|
};
|
|
const double left = project_x(source.first_x);
|
|
const double top = project_y(source.first_y);
|
|
aethera::render_2d::detail::Painter painter(
|
|
actual_cache, canvas,
|
|
aethera::render_2d::detail::raster_paint_region(layout, index, grid));
|
|
painter.heatmap(
|
|
{left, top, project_x(source.last_x) - left,
|
|
project_y(source.last_y) - top},
|
|
width, height, tile, Image_Interpolation_Mode::bilinear);
|
|
}
|
|
const Image_View expected = expected_cache.view();
|
|
const Image_View actual = actual_cache.view();
|
|
for (int y = 0; y < expected.height; ++y) {
|
|
const auto* expected_row = reinterpret_cast<const Pixel*>(
|
|
expected.data + static_cast<std::ptrdiff_t>(y) * expected.stride);
|
|
const auto* actual_row = reinterpret_cast<const Pixel*>(
|
|
actual.data + static_cast<std::ptrdiff_t>(y) * actual.stride);
|
|
for (int x = 0; x < expected.width; ++x)
|
|
EXPECT_EQ(actual_row[x], expected_row[x])
|
|
<< "tile seam mismatch at " << x << ", " << y;
|
|
}
|
|
}
|
|
|
|
TEST(partition_configuration,
|
|
raster_grid_covers_each_matrix_cell_exactly_once) {
|
|
const aethera::render_2d::detail::Raster_Layout layout{
|
|
7, 5, Rect_F{0.0, 0.0, 140.0, 100.0}, true, true, false};
|
|
const Plot_Partition_Grid grid{3, 2};
|
|
std::vector<unsigned> visits(
|
|
static_cast<std::size_t>(layout.width) * layout.height);
|
|
|
|
for (Plot_Partition_Count index = 0;
|
|
index < aethera::render_2d::detail::raster_partition_count(grid);
|
|
++index) {
|
|
const aethera::render_2d::detail::Raster_Partition partition =
|
|
aethera::render_2d::detail::raster_partition(
|
|
layout, index, grid);
|
|
EXPECT_FALSE(partition.empty());
|
|
EXPECT_FALSE(aethera::render_2d::detail::raster_paint_region(
|
|
layout, index, grid).empty());
|
|
for (int y = partition.first_y; y < partition.last_y; ++y)
|
|
for (int x = partition.first_x; x < partition.last_x; ++x)
|
|
++visits[static_cast<std::size_t>(y) * layout.width + x];
|
|
}
|
|
for (const unsigned count : visits) EXPECT_EQ(count, 1u);
|
|
}
|