Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions python/tests/skeleton_options_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,20 @@ def test_skeleton_options(webdriver):
@pytest.mark.parametrize("layout", ["xy", "3d"])
@pytest.mark.parametrize("primitive", ["edge", "node"])
@pytest.mark.parametrize(
"shader_alpha,layer_alpha", [(1, 1), (0.5, 1), (1, 0.5), (0.5, 0.5)]
"shader_output,shader_alpha",
[
("rgba", 1),
("rgba", 0.5),
("rgb", 1),
("default", 1),
("segment-rgb", 1),
("segment-rgba", 1),
],
)
def test_skeleton_rgba_opacity(webdriver, layout, primitive, shader_alpha, layer_alpha):
@pytest.mark.parametrize("layer_alpha", [1, 0.5])
def test_skeleton_opacity(
webdriver, layout, primitive, shader_output, shader_alpha, layer_alpha
):
if primitive == "edge":
source = SkeletonSource(
vertex_positions=[[-100, 0, 0], [100, 0, 0]],
Expand All @@ -117,20 +128,35 @@ def test_skeleton_rgba_opacity(webdriver, layout, primitive, shader_alpha, layer
source=source,
segments=[1],
object_alpha=layer_alpha,
segment_colors={1: "#ff8040"},
),
)
rendering = s.layers[0].skeleton_rendering
rendering.mode2d = "lines"
rendering.mode3d = "lines"
rendering.line_width2d = 16
rendering.line_width3d = 16
rendering.shader = f"""
if shader_output == "rgba":
rendering.shader = f"""
void main() {{
emitRGBA(vec4(1.0, 0.5, 0.25, {shader_alpha:.1f}));
}}
"""
elif shader_output == "rgb":
rendering.shader = "void main() { emitRGB(vec3(1.0, 0.5, 0.25)); }"
elif shader_output == "default":
rendering.shader = "void main() { emitDefault(); }"
elif shader_output == "segment-rgb":
rendering.shader = "void main() { emitRGB(segmentColor().rgb); }"
else:
rendering.shader = "void main() { emitRGBA(segmentColor()); }"

pixels = webdriver.viewer.screenshot(size=[32, 32]).screenshot.image_pixels
expected_rgb = np.array([1.0, 0.5, 0.25]) * shader_alpha * layer_alpha * 255
color = (
np.array([255, 128, 64])
if shader_output in ("default", "segment-rgb", "segment-rgba")
else np.array([255, 127.5, 63.75])
)
expected_rgb = color * shader_alpha * layer_alpha
expected = np.tile(np.append(expected_rgb, 255), (4, 4, 1))
np.testing.assert_allclose(pixels[14:18, 14:18], expected, atol=2, rtol=0)
54 changes: 30 additions & 24 deletions src/skeleton/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ import { TrackableBoolean } from "#src/trackable_boolean.js";
import { TrackableValue, WatchableValue } from "#src/trackable_value.js";
import { DataType } from "#src/util/data_type.js";
import { RefCounted } from "#src/util/disposable.js";
import type { vec3 } from "#src/util/geom.js";
import type { vec4 } from "#src/util/geom.js";
import { mat4 } from "#src/util/geom.js";
import { verifyFinitePositiveFloat } from "#src/util/json.js";
import { NullarySignal } from "#src/util/signal.js";
Expand Down Expand Up @@ -127,9 +127,19 @@ class RenderHelper extends RefCounted {

defineCommonShader(builder: ShaderBuilder) {
defineVertexId(builder);
// Skeleton shaders use straight-alpha colors until the final emission.
builder.addUniform("highp vec4", "uColor");
builder.addUniform("highp mat4", "uProjection");
builder.addUniform("highp uint", "uPickID");
builder.addFragmentCode(`
// Layer opacity is applied by emitRGBA.
vec4 segmentColor() {
return vec4(uColor.rgb, 1.0);
}
vec4 premultiplyAlpha(vec4 color) {
return ${this.targetIsSliceView ? "color" : "vec4(color.rgb * color.a, color.a)"};
}
`);
}

edgeShaderGetter;
Expand Down Expand Up @@ -179,19 +189,15 @@ highp uint vertexIndex = aVertexIndex.x * (1u - lineEndpointIndex) + aVertexInde
`;

builder.addFragmentCode(`
vec4 segmentColor() {
return uColor;
}
void emitRGBA(vec4 color) {
color.a *= uColor.a;
color.rgb *= ${this.getPremultiplyAlphaFactor()};
emit(vec4(color.rgb, color.a * getLineAlpha() * ${this.getCrossSectionFadeFactor()}), uPickID);
color.a *= uColor.a * getLineAlpha() * ${this.getCrossSectionFadeFactor()};
emit(premultiplyAlpha(color), uPickID);
}
void emitRGB(vec3 color) {
emit(vec4(color * uColor.a, uColor.a * getLineAlpha() * ${this.getCrossSectionFadeFactor()}), uPickID);
emitRGBA(vec4(color, 1.0));
}
void emitDefault() {
emit(vec4(uColor.rgb, uColor.a * getLineAlpha() * ${this.getCrossSectionFadeFactor()}), uPickID);
emitRGB(uColor.rgb);
}
`);
builder.addFragmentCode(glsl_COLORMAPS);
Expand Down Expand Up @@ -250,22 +256,17 @@ emitCircle(uProjection * vec4(vertexPosition, 1.0), uNodeDiameter, 0.0);
`;

builder.addFragmentCode(`
vec4 segmentColor() {
return uColor;
}
void emitRGBA(vec4 color) {
color.a *= uColor.a;
color.rgb *= ${this.getPremultiplyAlphaFactor()};
vec4 borderColor = color;
vec4 circleColor = getCircleColor(color, borderColor);
emit(circleColor, uPickID);
emit(premultiplyAlpha(circleColor), uPickID);
}
void emitRGB(vec3 color) {
emitRGBA(vec4(color, 1.0));
}
void emitDefault() {
vec4 circleColor = getCircleColor(uColor, uColor);
emit(circleColor, uPickID);
emitRGB(uColor.rgb);
}
`);
builder.addFragmentCode(glsl_COLORMAPS);
Expand Down Expand Up @@ -319,10 +320,6 @@ void emitDefault() {
});
}

getPremultiplyAlphaFactor() {
return this.targetIsSliceView ? "1.0" : "color.a";
}

getCrossSectionFadeFactor() {
if (this.targetIsSliceView) {
return "(clamp(1.0 - 2.0 * abs(0.5 - gl_FragCoord.z), 0.0, 1.0))";
Expand All @@ -342,8 +339,17 @@ void emitDefault() {
this.vertexIdHelper.enable();
}

setColor(gl: GL, shader: ShaderProgram, color: vec3) {
gl.uniform4fv(shader.uniform("uColor"), color);
setColor(gl: GL, shader: ShaderProgram, color: vec4) {
// The shared segmentation color is already premultiplied by layer alpha.
const alpha = color[3];
const scale = alpha > 0 ? 1 / alpha : 0;
gl.uniform4f(
shader.uniform("uColor"),
color[0] * scale,
color[1] * scale,
color[2] * scale,
alpha,
);
}

setPickID(gl: GL, shader: ShaderProgram, pickID: number) {
Expand Down Expand Up @@ -641,9 +647,9 @@ export class SkeletonLayer extends RefCounted {
}
if (color !== undefined) {
edgeShader.bind();
renderHelper.setColor(gl, edgeShader, <vec3>(<Float32Array>color));
renderHelper.setColor(gl, edgeShader, color);
nodeShader.bind();
renderHelper.setColor(gl, nodeShader, <vec3>(<Float32Array>color));
renderHelper.setColor(gl, nodeShader, color);
}
if (pickIndex !== undefined) {
edgeShader.bind();
Expand Down
Loading