Skip to content

Commit 06b05f9

Browse files
committed
feat(ImageMapper): array-based outline opacity
Label outlines now have finer-grained opacity controls. Each label can now specify its own outline opacity via setLabelOutlineOpacity(array).
1 parent a85aea0 commit 06b05f9

File tree

3 files changed

+138
-28
lines changed

3 files changed

+138
-28
lines changed

Examples/Rendering/ImageLabelOutline/index.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,11 @@ function createLabelmap(backgroundImageData) {
7676
labelMap.actor.getProperty().setUseLabelOutline(true);
7777
// Label outline thickness is for first segment -> 2 (positioned at array index 0), second segment -> 4
7878
// (positioned at array index 4)
79-
labelMap.actor.getProperty().setLabelOutlineThickness([2, 1, 1, 1, 4]);
80-
labelMap.actor.getProperty().setLabelOutlineOpacity(1.0);
79+
labelMap.actor.getProperty().setLabelOutlineThickness([4, 1, 1, 1, 8]);
80+
// Sets the outline opacity similarly to the outline thickness
81+
labelMap.actor.getProperty().setLabelOutlineOpacity([0.5, 1, 1, 1, 0.8]);
82+
// or you can set a global outline opacity
83+
// labelMap.actor.getProperty().setLabelOutlineOpacity(1.0);
8184

8285
// This is very important to make sure the labelmap is rendered
8386
// correctly

Sources/Rendering/Core/ImageProperty/index.d.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,21 @@ export interface vtkImageProperty extends vtkObject {
106106
getUseLabelOutline(): boolean;
107107

108108
/**
109-
* Set the 0 to 1 opacity of the label outline.
110-
* @param {Number} opacity
109+
* Set opacity of the label outline.
110+
*
111+
* Opacity must be between 0 and 1.
112+
* If the given opacity is a number, the opacity will apply to all outline segments.
113+
* If the given opacity is an array of numbers, each opacity value will apply to the
114+
* label equal to the opacity value's index + 1. (This is the same behavior as setLabelOutlineThickness).
115+
*
116+
* @param {Number | Number[]} opacity
111117
*/
112-
setLabelOutlineOpacity(opacity: number): boolean;
118+
setLabelOutlineOpacity(opacity: number | number[]): boolean;
113119

114120
/**
115121
* Get the 0 to 1 opacity of the label outline.
116122
*/
117-
getLabelOutlineOpacity(): number;
123+
getLabelOutlineOpacity(): number | number[];
118124

119125
/**
120126
* gets the label outline thickness

Sources/Rendering/OpenGL/ImageMapper/index.js

Lines changed: 123 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ function vtkOpenGLImageMapper(publicAPI, model) {
6161
model._colorTransferFunc,
6262
model._pwFunc,
6363
model._labelOutlineThicknessArray,
64+
model._labelOutlineOpacity,
6465
].forEach((coreObject) =>
6566
renderWindow.unregisterGraphicsResourceUser(coreObject, publicAPI)
6667
);
@@ -189,10 +190,16 @@ function vtkOpenGLImageMapper(publicAPI, model) {
189190
'uniform sampler2D texture1;',
190191
'uniform sampler2D colorTexture1;',
191192
'uniform sampler2D pwfTexture1;',
192-
'uniform sampler2D labelOutlineTexture1;',
193193
'uniform float opacity;',
194-
'uniform float outlineOpacity;',
195194
];
195+
if (actor.getProperty().getUseLabelOutline()) {
196+
tcoordDec = tcoordDec.concat([
197+
// outline thickness
198+
'uniform sampler2D labelOutlineTexture1;',
199+
// outline opacity
200+
'uniform sampler2D labelOutlineOpacityTexture1;',
201+
]);
202+
}
196203
if (iComps) {
197204
for (let comp = 1; comp < tNumComp; comp++) {
198205
tcoordDec = tcoordDec.concat([
@@ -263,7 +270,6 @@ function vtkOpenGLImageMapper(publicAPI, model) {
263270
FSSource,
264271
'//VTK::LabelOutline::Dec',
265272
[
266-
'uniform int outlineThickness;',
267273
'uniform float vpWidth;',
268274
'uniform float vpHeight;',
269275
'uniform float vpOffsetX;',
@@ -372,6 +378,7 @@ function vtkOpenGLImageMapper(publicAPI, model) {
372378
int segmentIndex = int(centerValue * 255.0);
373379
float textureCoordinate = float(segmentIndex - 1) / 1024.0;
374380
float textureValue = texture2D(labelOutlineTexture1, vec2(textureCoordinate, 0.5)).r;
381+
float outlineOpacity = texture2D(labelOutlineOpacityTexture1, vec2(textureCoordinate, 0.5)).r;
375382
int actualThickness = int(textureValue * 255.0);
376383
377384
if (segmentIndex == 0){
@@ -547,6 +554,10 @@ function vtkOpenGLImageMapper(publicAPI, model) {
547554
needRebuild ||
548555
model.lastHaveSeenDepthRequest !== model.haveSeenDepthRequest ||
549556
cellBO.getProgram()?.getHandle() === 0 ||
557+
cellBO.getShaderSourceTime().getMTime() < model.renderable.getMTime() ||
558+
cellBO.getShaderSourceTime().getMTime() < model.currentInput.getMTime() ||
559+
cellBO.getShaderSourceTime().getMTime() <
560+
actor.getProperty().getMTime() ||
550561
model.lastTextureComponents !== tNumComp ||
551562
model.lastIndependentComponents !== iComp
552563
) {
@@ -721,11 +732,19 @@ function vtkOpenGLImageMapper(publicAPI, model) {
721732
const texOpacityUnit = model.pwfTexture.getTextureUnit();
722733
cellBO.getProgram().setUniformi('pwfTexture1', texOpacityUnit);
723734

724-
const outlineThicknessUnit =
725-
model.labelOutlineThicknessTexture.getTextureUnit();
726-
cellBO
727-
.getProgram()
728-
.setUniformi('labelOutlineTexture1', outlineThicknessUnit);
735+
if (actor.getProperty().getUseLabelOutline()) {
736+
const outlineThicknessUnit =
737+
model.labelOutlineThicknessTexture.getTextureUnit();
738+
cellBO
739+
.getProgram()
740+
.setUniformi('labelOutlineTexture1', outlineThicknessUnit);
741+
742+
const texOutlineOpacityUnit =
743+
model.labelOutlineOpacityTexture.getTextureUnit();
744+
cellBO
745+
.getProgram()
746+
.setUniformi('labelOutlineOpacityTexture1', texOutlineOpacityUnit);
747+
}
729748

730749
if (model.renderable.getNumberOfClippingPlanes()) {
731750
// add all the clipping planes
@@ -768,14 +787,6 @@ function vtkOpenGLImageMapper(publicAPI, model) {
768787
cellBO.getProgram().setUniformi('numClipPlanes', numClipPlanes);
769788
cellBO.getProgram().setUniform4fv('clipPlanes', planeEquations);
770789
}
771-
772-
// outline thickness and opacity
773-
const vtkImageLabelOutline = actor.getProperty().getUseLabelOutline();
774-
775-
if (vtkImageLabelOutline === true) {
776-
const outlineOpacity = actor.getProperty().getLabelOutlineOpacity();
777-
cellBO.getProgram().setUniformf('outlineOpacity', outlineOpacity);
778-
}
779790
};
780791

781792
publicAPI.setCameraShaderParameters = (cellBO, ren, actor) => {
@@ -862,7 +873,10 @@ function vtkOpenGLImageMapper(publicAPI, model) {
862873
// activate the texture
863874
model.openGLTexture.activate();
864875
model.colorTexture.activate();
865-
model.labelOutlineThicknessTexture.activate();
876+
if (actor.getProperty().getUseLabelOutline()) {
877+
model.labelOutlineThicknessTexture.activate();
878+
model.labelOutlineOpacityTexture.activate();
879+
}
866880
model.pwfTexture.activate();
867881

868882
// draw polygons
@@ -875,7 +889,10 @@ function vtkOpenGLImageMapper(publicAPI, model) {
875889

876890
model.openGLTexture.deactivate();
877891
model.colorTexture.deactivate();
878-
model.labelOutlineThicknessTexture.deactivate();
892+
if (actor.getProperty().getUseLabelOutline()) {
893+
model.labelOutlineThicknessTexture.deactivate();
894+
model.labelOutlineOpacityTexture.deactivate();
895+
}
879896
model.pwfTexture.deactivate();
880897
};
881898

@@ -917,7 +934,9 @@ function vtkOpenGLImageMapper(publicAPI, model) {
917934
model.VBOBuildTime.getMTime() < model.currentInput.getMTime() ||
918935
!model.openGLTexture?.getHandle() ||
919936
!model.colorTexture?.getHandle() ||
920-
!model.labelOutlineThicknessTexture?.getHandle() ||
937+
(actor.getProperty().getUseLabelOutline() &&
938+
(!model.labelOutlineThicknessTexture?.getHandle() ||
939+
!model.labelOutlineOpacityTexture?.getHandle())) ||
921940
!model.pwfTexture?.getHandle();
922941

923942
publicAPI.buildBufferObjects = (ren, actor) => {
@@ -1150,8 +1169,11 @@ function vtkOpenGLImageMapper(publicAPI, model) {
11501169
model.pwfTexture = pwfTex.oglObject;
11511170
}
11521171

1153-
// Build outline thickness buffer
1154-
publicAPI.updatelabelOutlineThicknessTexture(actor);
1172+
if (actor.getProperty().getUseLabelOutline()) {
1173+
// Build outline thickness + opacity buffers
1174+
publicAPI.updatelabelOutlineThicknessTexture(actor);
1175+
publicAPI.updateLabelOutlineOpacityTexture(actor);
1176+
}
11551177

11561178
// Find what IJK axis and what direction to slice along
11571179
const { ijkMode } = model.renderable.getClosestIJKAxis();
@@ -1393,6 +1415,85 @@ function vtkOpenGLImageMapper(publicAPI, model) {
13931415
}
13941416
};
13951417

1418+
publicAPI.updateLabelOutlineOpacityTexture = (image) => {
1419+
let labelOutlineOpacity = image.getProperty().getLabelOutlineOpacity();
1420+
1421+
// when the labelOutlineOpacity is a number, we use _cachedLabelOutlineOpacityObj
1422+
// as a stable object reference for `[labelOutlineOpacity]`.
1423+
if (typeof labelOutlineOpacity === 'number') {
1424+
if (model._cachedLabelOutlineOpacityObj?.[0] === labelOutlineOpacity) {
1425+
labelOutlineOpacity = model._cachedLabelOutlineOpacityObj;
1426+
} else {
1427+
labelOutlineOpacity = [labelOutlineOpacity];
1428+
}
1429+
model._cachedLabelOutlineOpacityObj = labelOutlineOpacity;
1430+
}
1431+
1432+
const lTex =
1433+
model._openGLRenderWindow.getGraphicsResourceForObject(
1434+
labelOutlineOpacity
1435+
);
1436+
1437+
const toString = `${labelOutlineOpacity.join('-')}`;
1438+
const reBuildL = !lTex?.oglObject?.getHandle() || lTex?.hash !== toString;
1439+
1440+
if (reBuildL) {
1441+
let lWidth = model.renderable.getLabelOutlineTextureWidth();
1442+
if (lWidth <= 0) {
1443+
lWidth = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
1444+
}
1445+
const lHeight = 1;
1446+
const lSize = lWidth * lHeight;
1447+
const lTable = new Float32Array(lSize);
1448+
1449+
for (let i = 0; i < lWidth; ++i) {
1450+
// Retrieve the opacity value for the current segment index.
1451+
// If the value is undefined, use the first element's value as a default, otherwise use the value (even if 0)
1452+
lTable[i] = labelOutlineOpacity[i] ?? labelOutlineOpacity[0];
1453+
}
1454+
model.labelOutlineOpacityTexture = vtkOpenGLTexture.newInstance({
1455+
resizable: false,
1456+
});
1457+
model.labelOutlineOpacityTexture.setOpenGLRenderWindow(
1458+
model._openGLRenderWindow
1459+
);
1460+
1461+
model.labelOutlineOpacityTexture.resetFormatAndType();
1462+
model.labelOutlineOpacityTexture.setMinificationFilter(Filter.NEAREST);
1463+
model.labelOutlineOpacityTexture.setMagnificationFilter(Filter.NEAREST);
1464+
1465+
// Create a 2D texture (acting as 1D) from the raw data
1466+
model.labelOutlineOpacityTexture.create2DFromRaw({
1467+
width: lWidth,
1468+
height: lHeight,
1469+
numComps: 1,
1470+
dataType: VtkDataTypes.FLOAT,
1471+
data: lTable,
1472+
});
1473+
1474+
if (labelOutlineOpacity) {
1475+
model._openGLRenderWindow.setGraphicsResourceForObject(
1476+
labelOutlineOpacity,
1477+
model.labelOutlineOpacityTexture,
1478+
toString
1479+
);
1480+
if (labelOutlineOpacity !== model._labelOutlineOpacity) {
1481+
model._openGLRenderWindow.registerGraphicsResourceUser(
1482+
labelOutlineOpacity,
1483+
publicAPI
1484+
);
1485+
model._openGLRenderWindow.unregisterGraphicsResourceUser(
1486+
model._labelOutlineOpacity,
1487+
publicAPI
1488+
);
1489+
}
1490+
model._labelOutlineOpacity = labelOutlineOpacity;
1491+
}
1492+
} else {
1493+
model.labelOutlineOpacityTexture = lTex.oglObject;
1494+
}
1495+
};
1496+
13961497
publicAPI.updatelabelOutlineThicknessTexture = (image) => {
13971498
const labelOutlineThicknessArray = image
13981499
.getProperty()
@@ -1509,7 +1610,7 @@ const DEFAULT_VALUES = {
15091610
colorTexture: null,
15101611
pwfTexture: null,
15111612
labelOutlineThicknessTexture: null,
1512-
labelOutlineThicknessTextureString: null,
1613+
labelOutlineOpacityTexture: null,
15131614
lastHaveSeenDepthRequest: false,
15141615
haveSeenDepthRequest: false,
15151616
lastTextureComponents: 0,

0 commit comments

Comments
 (0)