dicom-microscopy-viewer-changed
Version:
Interactive web-based viewer for DICOM Microscopy Images
385 lines (359 loc) • 12.4 kB
JavaScript
/**
* Utilities for parsing literal style objects
* @module ol/webgl/styleparser
*/
import {ShaderBuilder} from './ShaderBuilder.js';
import {
ValueTypes,
expressionToGlsl,
getStringNumberEquivalent,
uniformNameForVariable,
} from '../style/expressions.js';
import {asArray} from '../color.js';
/**
* @param {import('../style/literal.js').SymbolType} type Symbol type
* @param {string} sizeExpressionGlsl Size expression
* @return {string} The GLSL opacity function
*/
export function getSymbolOpacityGlslFunction(type, sizeExpressionGlsl) {
switch (type) {
case 'square':
case 'image':
return '1.0';
// taken from https://thebookofshaders.com/07/
case 'circle':
return `(1.0-smoothstep(1.-4./${sizeExpressionGlsl},1.,dot(v_quadCoord-.5,v_quadCoord-.5)*4.))`;
case 'triangle':
const st = '(v_quadCoord*2.-1.)';
const a = `(atan(${st}.x,${st}.y))`;
return `(1.0-smoothstep(.5-3./${sizeExpressionGlsl},.5,cos(floor(.5+${a}/2.094395102)*2.094395102-${a})*length(${st})))`;
default:
throw new Error(`Unexpected symbol type: ${type}`);
}
}
/**
* Packs all components of a color into a two-floats array
* @param {import("../color.js").Color|string} color Color as array of numbers or string
* @return {Array<number>} Vec2 array containing the color in compressed form
*/
export function packColor(color) {
const array = asArray(color);
const r = array[0] * 256;
const g = array[1];
const b = array[2] * 256;
const a = Math.round(array[3] * 255);
return [r + g, b + a];
}
const UNPACK_COLOR_FN = `vec4 unpackColor(vec2 packedColor) {
return fract(packedColor[1] / 256.0) * vec4(
fract(floor(packedColor[0] / 256.0) / 256.0),
fract(packedColor[0] / 256.0),
fract(floor(packedColor[1] / 256.0) / 256.0),
1.0
);
}`;
/**
* @param {ValueTypes} type Value type
* @return {1|2|3|4} The amount of components for this value
*/
function getGlslSizeFromType(type) {
if (type === ValueTypes.COLOR) {
return 2;
}
if (type === ValueTypes.NUMBER_ARRAY) {
return 4;
}
return 1;
}
/**
* @param {ValueTypes} type Value type
* @return {'float'|'vec2'|'vec3'|'vec4'} The corresponding GLSL type for this value
*/
function getGlslTypeFromType(type) {
const size = getGlslSizeFromType(type);
if (size > 1) {
return /** @type {'vec2'|'vec3'|'vec4'} */ (`vec${size}`);
}
return 'float';
}
/**
* @param {import("../style/literal").LiteralStyle} style Style
* @param {ShaderBuilder} builder Shader builder
* @param {Object<string,import("../webgl/Helper").UniformValue>} uniforms Uniforms
* @param {import("../style/expressions.js").ParsingContext} vertContext Vertex shader parsing context
* @param {import("../style/expressions.js").ParsingContext} fragContext Fragment shader parsing context
*/
function parseSymbolProperties(
style,
builder,
uniforms,
vertContext,
fragContext
) {
if (!('symbol' in style)) {
return;
}
const symbolStyle = style.symbol;
if ('color' in symbolStyle) {
const color = symbolStyle.color;
const opacity = symbolStyle.opacity !== undefined ? symbolStyle.opacity : 1;
const parsedColor = expressionToGlsl(fragContext, color, ValueTypes.COLOR);
const parsedOpacity = expressionToGlsl(
fragContext,
opacity,
ValueTypes.NUMBER
);
builder.setSymbolColorExpression(
`vec4(${parsedColor}.rgb, ${parsedColor}.a * ${parsedOpacity})`
);
}
if (symbolStyle.symbolType === 'image' && symbolStyle.src) {
const texture = new Image();
texture.crossOrigin =
symbolStyle.crossOrigin === undefined
? 'anonymous'
: symbolStyle.crossOrigin;
texture.src = symbolStyle.src;
builder
.addUniform('sampler2D u_texture')
.setSymbolColorExpression(
`${builder.getSymbolColorExpression()} * texture2D(u_texture, v_texCoord)`
);
uniforms['u_texture'] = texture;
} else if ('symbolType' in symbolStyle) {
let visibleSize = builder.getSymbolSizeExpression();
if ('size' in symbolStyle) {
visibleSize = expressionToGlsl(
fragContext,
symbolStyle.size,
ValueTypes.NUMBER_ARRAY | ValueTypes.NUMBER
);
}
const symbolOpacity = getSymbolOpacityGlslFunction(
symbolStyle.symbolType,
`vec2(${visibleSize}).x`
);
builder.setSymbolColorExpression(
`${builder.getSymbolColorExpression()} * vec4(1.0, 1.0, 1.0, ${symbolOpacity})`
);
}
if ('size' in symbolStyle) {
const size = symbolStyle.size;
const parsedSize = expressionToGlsl(
vertContext,
size,
ValueTypes.NUMBER_ARRAY | ValueTypes.NUMBER
);
builder.setSymbolSizeExpression(`vec2(${parsedSize})`);
}
if ('textureCoord' in symbolStyle) {
builder.setTextureCoordinateExpression(
expressionToGlsl(
vertContext,
symbolStyle.textureCoord,
ValueTypes.NUMBER_ARRAY
)
);
}
if ('offset' in symbolStyle) {
builder.setSymbolOffsetExpression(
expressionToGlsl(vertContext, symbolStyle.offset, ValueTypes.NUMBER_ARRAY)
);
}
if ('rotation' in symbolStyle) {
builder.setSymbolRotationExpression(
expressionToGlsl(vertContext, symbolStyle.rotation, ValueTypes.NUMBER)
);
}
if ('rotateWithView' in symbolStyle) {
builder.setSymbolRotateWithView(!!symbolStyle.rotateWithView);
}
}
/**
* @param {import("../style/literal").LiteralStyle} style Style
* @param {ShaderBuilder} builder Shader Builder
* @param {Object<string,import("../webgl/Helper").UniformValue>} uniforms Uniforms
* @param {import("../style/expressions.js").ParsingContext} vertContext Vertex shader parsing context
* @param {import("../style/expressions.js").ParsingContext} fragContext Fragment shader parsing context
*/
function parseStrokeProperties(
style,
builder,
uniforms,
vertContext,
fragContext
) {
if ('stroke-color' in style) {
builder.setStrokeColorExpression(
expressionToGlsl(fragContext, style['stroke-color'], ValueTypes.COLOR)
);
}
if ('stroke-width' in style) {
builder.setStrokeWidthExpression(
expressionToGlsl(vertContext, style['stroke-width'], ValueTypes.NUMBER)
);
}
}
/**
* @param {import("../style/literal").LiteralStyle} style Style
* @param {ShaderBuilder} builder Shader Builder
* @param {Object<string,import("../webgl/Helper").UniformValue>} uniforms Uniforms
* @param {import("../style/expressions.js").ParsingContext} vertContext Vertex shader parsing context
* @param {import("../style/expressions.js").ParsingContext} fragContext Fragment shader parsing context
*/
function parseFillProperties(
style,
builder,
uniforms,
vertContext,
fragContext
) {
if ('fill-color' in style) {
builder.setFillColorExpression(
expressionToGlsl(fragContext, style['fill-color'], ValueTypes.COLOR)
);
}
}
/**
* @typedef {Object} StyleParseResult
* @property {ShaderBuilder} builder Shader builder pre-configured according to a given style
* @property {import("../render/webgl/VectorStyleRenderer.js").UniformDefinitions} uniforms Uniform definitions
* @property {import("../render/webgl/VectorStyleRenderer.js").AttributeDefinitions} attributes Attribute definitions
*/
/**
* Parses a {@link import("../style/literal").LiteralStyle} object and returns a {@link ShaderBuilder}
* object that has been configured according to the given style, as well as `attributes` and `uniforms`
* arrays to be fed to the `WebGLPointsRenderer` class.
*
* Also returns `uniforms` and `attributes` properties as expected by the
* {@link module:ol/renderer/webgl/PointsLayer~WebGLPointsLayerRenderer}.
*
* @param {import("../style/literal").LiteralStyle} style Literal style.
* @return {StyleParseResult} Result containing shader params, attributes and uniforms.
*/
export function parseLiteralStyle(style) {
/**
* @type {import("../style/expressions.js").ParsingContext}
*/
const vertContext = {
inFragmentShader: false,
variables: [],
attributes: [],
stringLiteralsMap: {},
functions: {},
style: style,
};
/**
* @type {import("../style/expressions.js").ParsingContext}
*/
const fragContext = {
inFragmentShader: true,
variables: vertContext.variables,
attributes: [],
stringLiteralsMap: vertContext.stringLiteralsMap,
functions: {},
style: style,
};
const builder = new ShaderBuilder();
/** @type {Object<string,import("../webgl/Helper").UniformValue>} */
const uniforms = {};
parseSymbolProperties(style, builder, uniforms, vertContext, fragContext);
parseStrokeProperties(style, builder, uniforms, vertContext, fragContext);
parseFillProperties(style, builder, uniforms, vertContext, fragContext);
if (style.filter) {
const parsedFilter = expressionToGlsl(
fragContext,
style.filter,
ValueTypes.BOOLEAN
);
builder.setFragmentDiscardExpression(`!${parsedFilter}`);
}
// define one uniform per variable
fragContext.variables.forEach(function (variable) {
const uniformName = uniformNameForVariable(variable.name);
builder.addUniform(`${getGlslTypeFromType(variable.type)} ${uniformName}`);
let callback;
if (variable.type === ValueTypes.STRING) {
callback = () =>
getStringNumberEquivalent(
vertContext,
/** @type {string} */ (style.variables[variable.name])
);
} else if (variable.type === ValueTypes.COLOR) {
callback = () =>
packColor([
...asArray(
/** @type {string|Array<number>} */ (
style.variables[variable.name]
) || '#eee'
),
]);
} else if (variable.type === ValueTypes.BOOLEAN) {
callback = () =>
/** @type {boolean} */ (style.variables[variable.name]) ? 1.0 : 0.0;
} else {
callback = () => /** @type {number} */ (style.variables[variable.name]);
}
uniforms[uniformName] = callback;
});
// for each feature attribute used in the fragment shader, define a varying that will be used to pass data
// from the vertex to the fragment shader, as well as an attribute in the vertex shader (if not already present)
fragContext.attributes.forEach(function (attribute) {
if (!vertContext.attributes.find((a) => a.name === attribute.name)) {
vertContext.attributes.push(attribute);
}
let type = getGlslTypeFromType(attribute.type);
let expression = `a_${attribute.name}`;
if (attribute.type === ValueTypes.COLOR) {
type = 'vec4';
expression = `unpackColor(${expression})`;
builder.addVertexShaderFunction(UNPACK_COLOR_FN);
}
builder.addVarying(`v_${attribute.name}`, type, expression);
});
// for each feature attribute used in the vertex shader, define an attribute in the vertex shader.
vertContext.attributes.forEach(function (attribute) {
builder.addAttribute(
`${getGlslTypeFromType(attribute.type)} a_${attribute.name}`
);
});
const attributes = vertContext.attributes.map(function (attribute) {
let callback;
if (attribute.callback) {
callback = attribute.callback;
} else if (attribute.type === ValueTypes.STRING) {
callback = (feature) =>
getStringNumberEquivalent(vertContext, feature.get(attribute.name));
} else if (attribute.type === ValueTypes.COLOR) {
callback = (feature) =>
packColor([...asArray(feature.get(attribute.name) || '#eee')]);
} else if (attribute.type === ValueTypes.BOOLEAN) {
callback = (feature) => (feature.get(attribute.name) ? 1.0 : 0.0);
} else {
callback = (feature) => feature.get(attribute.name);
}
return {
name: attribute.name,
size: getGlslSizeFromType(attribute.type),
callback,
};
});
// add functions that were collected in the parsing contexts
for (const functionName in vertContext.functions) {
builder.addVertexShaderFunction(vertContext.functions[functionName]);
}
for (const functionName in fragContext.functions) {
builder.addFragmentShaderFunction(fragContext.functions[functionName]);
}
return {
builder: builder,
attributes: attributes.reduce(
(prev, curr) => ({
...prev,
[curr.name]: {callback: curr.callback, size: curr.size},
}),
{}
),
uniforms: uniforms,
};
}