// 实测:vtkImagePlaneWidget 的纹理平面能否把 LUT 下溢透明(alpha=0)的 texel 渲染成透明。 // 构造一张图:左半真值(映射红)、右半哨兵 -1(下溢→透明)。纹理平面背后放一块不透明【黄】板, // 背景【绿】。套用 buildLut(transparentBelowRange=true) 后离屏渲染、回读像素分类: // 右半空白处出现【黄】 → texel alpha 透明【成功】(黄板透过来) // 出现【蓝】 → 值被钳到最低色(透明没生效) // 出现【黑】 → alpha 被丢 // 纯实测,不靠文档措辞猜。 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ColorLutBuilder.hpp" #include "model/ColorScale.hpp" int main() { using namespace geopro; // 1) 构造体: 40x40x3, 左半 i<20 → 200(真值,映射红); 右半 → -1(哨兵,下溢透明)。 const int nx = 40, ny = 40, nz = 3; vtkNew img; img->SetDimensions(nx, ny, nz); img->SetOrigin(0, 0, 0); img->SetSpacing(1, 1, 1); vtkNew sc; sc->SetName("v"); sc->SetNumberOfTuples(static_cast(nx) * ny * nz); // 全值域核查:左侧 [0..nx-9] 列填 0→200 渐变(真数据),右侧 8 列填 -1(哨兵/白化)。 // 验证 (a) 颜色映射跨整个 [vmin,vmax] 是否正确;(b) 空值是否透明。 const int blankCols = 8; const int gradCols = nx - blankCols; for (int k = 0; k < nz; ++k) for (int j = 0; j < ny; ++j) for (int i = 0; i < nx; ++i) { const vtkIdType id = (static_cast(k) * ny + j) * nx + i; const float v = (i < gradCols) ? static_cast(200.0 * i / (gradCols - 1)) // 0→200 渐变 : -1.0F; // 哨兵 sc->SetValue(id, v); } img->GetPointData()->SetScalars(sc); // 2) 三色阶(全不透明): 0→蓝, 100→黄, 200→红。渐变应呈 蓝→黄→红;-1 应透明(露品红背板)。 core::ColorScale cs; cs.addStop(0.0, core::Rgba{0, 0, 255, 255}); cs.addStop(100.0, core::Rgba{255, 255, 0, 255}); cs.addStop(200.0, core::Rgba{255, 0, 0, 255}); auto lut = render::buildLut(cs, 0.0, 200.0, 256, /*transparentBelowRange=*/true); // 3) 渲染器 + 离屏窗口 + 交互器(widget 必需)。背景绿。 vtkNew ren; ren->SetBackground(0.15, 0.15, 0.15); // 深灰 = 背景 vtkNew rw; rw->SetOffScreenRendering(1); rw->AddRenderer(ren); rw->SetSize(400, 400); vtkNew iren; iren->SetRenderWindow(rw); // 4) 纹理平面【背后】放一块不透明黄板(z=0.5, 在切片 z=1 之下=远离上方相机)。 vtkNew yp; yp->SetOrigin(0, 0, 0.5); yp->SetPoint1(nx - 1, 0, 0.5); yp->SetPoint2(0, ny - 1, 0.5); vtkNew ym; ym->SetInputConnection(yp->GetOutputPort()); vtkNew ya; ya->SetMapper(ym); ya->GetProperty()->SetColor(1.0, 0.0, 1.0); // 品红 = 背板(空值透明处会露出它) ya->GetProperty()->LightingOff(); ren->AddActor(ya); // 5) 真 vtkImagePlaneWidget: Z 法向切片 1(z=1), 套用户 LUT, 最近邻避免边界混色。 vtkNew prod; prod->SetOutput(img); vtkNew w; w->SetInteractor(iren); w->SetInputConnection(prod->GetOutputPort()); w->SetPlaneOrientationToZAxes(); w->SetSliceIndex(1); w->SetResliceInterpolateToNearestNeighbour(); w->TextureInterpolateOff(); w->SetLookupTable(lut); // 钉死 window/level 到 [vmin,vmax]=[0,200],阻止 widget 按输入标量范围自动拉伸。 w->SetWindowLevel(200.0, 100.0); w->On(); // 6) 顶视相机(沿 -Z 俯视), 平行投影框住平面。 ren->GetActiveCamera()->SetPosition(nx / 2.0, ny / 2.0, 100.0); ren->GetActiveCamera()->SetFocalPoint(nx / 2.0, ny / 2.0, 1.0); ren->GetActiveCamera()->SetViewUp(0, 1, 0); ren->GetActiveCamera()->ParallelProjectionOn(); ren->ResetCamera(); rw->Render(); // 7) 回读像素, 分类计数。 vtkNew w2i; w2i->SetInput(rw); w2i->Update(); vtkImageData* out = w2i->GetOutput(); { vtkNew pw; pw->SetFileName("D:/dev/spike_data/slice_alpha_probe.png"); pw->SetInputConnection(w2i->GetOutputPort()); pw->Write(); } int dims[3]; out->GetDimensions(dims); long red = 0, yellow = 0, green = 0, blue = 0, black = 0, other = 0; for (int y = 0; y < dims[1]; ++y) for (int x = 0; x < dims[0]; ++x) { const auto* p = static_cast(out->GetScalarPointer(x, y, 0)); const int r = p[0], g = p[1], b = p[2]; if (r > 150 && g < 100 && b < 100) ++red; else if (r > 150 && g > 150 && b < 100) ++yellow; else if (r < 100 && g > 150 && b < 100) ++green; else if (b > 150 && r < 100 && g < 100) ++blue; else if (r < 60 && g < 60 && b < 60) ++black; else ++other; } std::printf("PIXELS red(data)=%ld yellow(behind-shows=TRANSPARENT)=%ld " "green(bg)=%ld blue(clamp-lowest)=%ld black(alpha-dropped)=%ld other=%ld\n", red, yellow, green, blue, black, other); // 8) 直接判读 colormap 对下溢值输出的 RGBA(隔离 LUT 正确性 vs 纹理渲染)。 if (auto* cm = w->GetColorMap()) { cm->Update(); if (auto* co = cm->GetOutput()) { int cd[3]; co->GetDimensions(cd); const int comps = co->GetNumberOfScalarComponents(); // 右半某点(i=30,j=20,值=150) colormap 应产出 alpha=0。 const auto* px = static_cast(co->GetScalarPointer(30, 20, 0)); std::printf("COLORMAP comps=%d rightHalfTexel.RGBA=", comps); for (int c = 0; c < comps; ++c) std::printf("%d ", px[c]); std::printf("(期望 alpha=0 => LUT 正确产出透明)\n"); } } std::printf("VERDICT: 若 yellow 远多于 blue/black => widget 纹理【支持】texel alpha 透明; " "若 blue 多 => 钳最低色; 若 black 多 => 丢 alpha\n"); return 0; }