162 lines
6.8 KiB
C++
162 lines
6.8 KiB
C++
// 实测:vtkImagePlaneWidget 的纹理平面能否把 LUT 下溢透明(alpha=0)的 texel 渲染成透明。
|
||
// 构造一张图:左半真值(映射红)、右半哨兵 -1(下溢→透明)。纹理平面背后放一块不透明【黄】板,
|
||
// 背景【绿】。套用 buildLut(transparentBelowRange=true) 后离屏渲染、回读像素分类:
|
||
// 右半空白处出现【黄】 → texel alpha 透明【成功】(黄板透过来)
|
||
// 出现【蓝】 → 值被钳到最低色(透明没生效)
|
||
// 出现【黑】 → alpha 被丢
|
||
// 纯实测,不靠文档措辞猜。
|
||
#include <cstdio>
|
||
|
||
#include <vtkActor.h>
|
||
#include <vtkFloatArray.h>
|
||
#include <vtkImageData.h>
|
||
#include <vtkImageMapToColors.h>
|
||
#include <vtkImagePlaneWidget.h>
|
||
#include <vtkLookupTable.h>
|
||
#include <vtkNew.h>
|
||
#include <vtkPNGWriter.h>
|
||
#include <vtkPlaneSource.h>
|
||
#include <vtkPointData.h>
|
||
#include <vtkPolyDataMapper.h>
|
||
#include <vtkProperty.h>
|
||
#include <vtkRenderWindow.h>
|
||
#include <vtkRenderWindowInteractor.h>
|
||
#include <vtkRenderer.h>
|
||
#include <vtkTrivialProducer.h>
|
||
#include <vtkUnsignedCharArray.h>
|
||
#include <vtkWindowToImageFilter.h>
|
||
#include <vtkCamera.h>
|
||
|
||
#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<vtkImageData> img;
|
||
img->SetDimensions(nx, ny, nz);
|
||
img->SetOrigin(0, 0, 0);
|
||
img->SetSpacing(1, 1, 1);
|
||
vtkNew<vtkFloatArray> sc;
|
||
sc->SetName("v");
|
||
sc->SetNumberOfTuples(static_cast<vtkIdType>(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<vtkIdType>(k) * ny + j) * nx + i;
|
||
const float v = (i < gradCols)
|
||
? static_cast<float>(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<vtkRenderer> ren;
|
||
ren->SetBackground(0.15, 0.15, 0.15); // 深灰 = 背景
|
||
vtkNew<vtkRenderWindow> rw;
|
||
rw->SetOffScreenRendering(1);
|
||
rw->AddRenderer(ren);
|
||
rw->SetSize(400, 400);
|
||
vtkNew<vtkRenderWindowInteractor> iren;
|
||
iren->SetRenderWindow(rw);
|
||
|
||
// 4) 纹理平面【背后】放一块不透明黄板(z=0.5, 在切片 z=1 之下=远离上方相机)。
|
||
vtkNew<vtkPlaneSource> yp;
|
||
yp->SetOrigin(0, 0, 0.5);
|
||
yp->SetPoint1(nx - 1, 0, 0.5);
|
||
yp->SetPoint2(0, ny - 1, 0.5);
|
||
vtkNew<vtkPolyDataMapper> ym;
|
||
ym->SetInputConnection(yp->GetOutputPort());
|
||
vtkNew<vtkActor> 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<vtkTrivialProducer> prod;
|
||
prod->SetOutput(img);
|
||
vtkNew<vtkImagePlaneWidget> 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<vtkWindowToImageFilter> w2i;
|
||
w2i->SetInput(rw);
|
||
w2i->Update();
|
||
vtkImageData* out = w2i->GetOutput();
|
||
{
|
||
vtkNew<vtkPNGWriter> 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<unsigned char*>(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<unsigned char*>(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;
|
||
}
|