diff --git a/src/render/ContourBands.cpp b/src/render/ContourBands.cpp index 8840013..817535e 100644 --- a/src/render/ContourBands.cpp +++ b/src/render/ContourBands.cpp @@ -21,6 +21,55 @@ using geopro::core::Vec2; using geopro::core::Rgba; namespace { + +// 双线性上采样:nx×ny → ((nx-1)*k+1)×((ny-1)*k+1)。NaN 单元参与的目标点置 NaN。 +Grid upsampleBilinear(const Grid& g, int k) { + if (k <= 1) return g; + const int nx = g.nx(), ny = g.ny(); + const int Nx = (nx - 1) * k + 1, Ny = (ny - 1) * k + 1; + Grid out(Nx, Ny); + out.x.resize(Nx); out.y.resize(Ny); + for (int I = 0; I < Nx; ++I) { double fi = double(I) / k; int i0 = std::min(int(fi), nx - 2); + double t = fi - i0; out.x[I] = g.x[i0] * (1 - t) + g.x[i0 + 1] * t; } + for (int J = 0; J < Ny; ++J) { double fj = double(J) / k; int j0 = std::min(int(fj), ny - 2); + double t = fj - j0; out.y[J] = g.y[j0] * (1 - t) + g.y[j0 + 1] * t; } + for (int J = 0; J < Ny; ++J) { + double fj = double(J) / k; int j0 = std::min(int(fj), ny - 2); double tj = fj - j0; + for (int I = 0; I < Nx; ++I) { + double fi = double(I) / k; int i0 = std::min(int(fi), nx - 2); double ti = fi - i0; + double v00 = g.valueAt(i0, j0), v10 = g.valueAt(i0 + 1, j0); + double v01 = g.valueAt(i0, j0 + 1), v11 = g.valueAt(i0 + 1, j0 + 1); + if (std::isnan(v00) || std::isnan(v10) || std::isnan(v01) || std::isnan(v11)) + out.valueAt(I, J) = std::nan(""); + else + out.valueAt(I, J) = (v00 * (1 - ti) + v10 * ti) * (1 - tj) + + (v01 * (1 - ti) + v11 * ti) * tj; + } + } + out.vmin = g.vmin; out.vmax = g.vmax; + return out; +} + +// 3x3 盒式平滑(NaN 跳过);strength 0..1 线性混合原值。 +Grid smoothGrid(const Grid& g, double strength) { + if (strength <= 0) return g; + const int nx = g.nx(), ny = g.ny(); + Grid out = g; + for (int j = 0; j < ny; ++j) + for (int i = 0; i < nx; ++i) { + double v = g.valueAt(i, j); + if (std::isnan(v)) continue; + double sum = 0; int n = 0; + for (int dj = -1; dj <= 1; ++dj) for (int di = -1; di <= 1; ++di) { + int ii = i + di, jj = j + dj; + if (ii < 0 || ii >= nx || jj < 0 || jj >= ny) continue; + double w = g.valueAt(ii, jj); if (std::isnan(w)) continue; sum += w; ++n; + } + if (n) out.valueAt(i, j) = v * (1 - strength) + (sum / n) * strength; + } + return out; +} + // 用 Grid 构 vtkStructuredGrid(点 (x[i], y[j], 0);NaN 值置 0 但记录于掩膜—— // 首版裁剪在 Task 1.4 接入,这里先全量)。 vtkSmartPointer toStructuredGrid(const Grid& g) { @@ -43,7 +92,7 @@ vtkSmartPointer toStructuredGrid(const Grid& g) { } } // namespace -ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const ContourOptions&) { +ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const ContourOptions& opt) { ContourBandsResult out; const int nx = g.nx(), ny = g.ny(); if (nx < 2 || ny < 2 || g.x.size() < 2 || g.y.size() < 2) return out; @@ -51,7 +100,8 @@ ContourBandsResult buildContourBands(const Grid& g, const ColorScale& cs, const const std::vector stops = cs.stopValues(); if (stops.size() < 2) return out; - auto sg = toStructuredGrid(g); + Grid work = smoothGrid(upsampleBilinear(g, std::max(1, opt.upsample)), opt.smooth); + auto sg = toStructuredGrid(work); vtkNew surf; surf->SetInputData(sg); vtkNew banded; banded->SetInputConnection(surf->GetOutputPort()); diff --git a/tests/render/test_contour_bands.cpp b/tests/render/test_contour_bands.cpp index c844cc9..7994241 100644 --- a/tests/render/test_contour_bands.cpp +++ b/tests/render/test_contour_bands.cpp @@ -3,6 +3,22 @@ using namespace geopro::core; using namespace geopro::render; +// 上采样 2x:色带边界更密 → 多边形数应多于不上采样。 +TEST(ContourBands, UpsampleIncreasesDetail) { + Grid g(3, 3); + g.x = {0, 1, 2}; g.y = {0, 1, 2}; + for (int j = 0; j < 3; ++j) + for (int i = 0; i < 3; ++i) g.valueAt(i, j) = static_cast(i * i + j); + g.vmin = 0; g.vmax = 6; + ColorScale cs; + cs.addStop(0, Rgba{0,0,255,255}); cs.addStop(3, Rgba{0,255,0,255}); cs.addStop(6, Rgba{255,0,0,255}); + ContourOptions a; a.upsample = 1; a.smooth = 0; a.simplifyTol = 0; + ContourOptions b; b.upsample = 2; b.smooth = 0; b.simplifyTol = 0; + auto ra = buildContourBands(g, cs, a); + auto rb = buildContourBands(g, cs, b); + EXPECT_GT(rb.bands.size(), ra.bands.size()); +} + // 2x2 平滑梯度网格 + 2 段色阶 → 至少 1 个色带多边形,多边形顶点 >=3。 TEST(ContourBands, ProducesNonEmptyBands) { Grid g(3, 3);