OutlineMaskEffect = {
const { joinLayerBounds, getViewportBounds, makeViewport, getRenderBounds, arraysEqual } = omeUtil;
class OutlineMaskEffect {
constructor() {
this.id = 'outline-mask-effect';
this.props = null;
this.useInPicking = true;
this.order = 0;
this.channels = [];
}
preRender(device, preRenderOptions) {
const {layers, layerFilter, viewports, onViewportActive, views, isPicking} = preRenderOptions
let didRender = false;
if (!this.dummyMaskMap) {
this.dummyMaskMap = device.createTexture({
width: 1,
height: 1
});
}
if (isPicking) {
return {didRender};
}
const maskLayers = layers.filter(l => l.props.visible && l.props.operation.includes('mask'));
if (maskLayers.length === 0) {
this.masks = null;
this.channels.length = 0;
return {didRender};
}
this.masks = {};
if (!this.maskPass) {
this.maskPass = new OutlineMaskPass(device, {id: 'default-mask'});
this.maskMap = this.maskPass.maskMap;
}
const channelMap = this._sortMaskChannels(maskLayers);
const viewport = viewports[0];
const viewportChanged = !this.lastViewport || !this.lastViewport.equals(viewport);
if (viewport.resolution !== undefined) {
console.warn('MaskExtension is not supported in GlobeView')();
return {didRender};
}
for (const maskId in channelMap) {
const result = this._renderChannel(channelMap[maskId], {
layerFilter,
onViewportActive,
views,
viewport,
viewportChanged
});
didRender ||= result;
}
return {didRender};
}
_renderChannel(
channelInfo,
{
layerFilter,
onViewportActive,
views,
viewport,
viewportChanged
}
) {
let didRender = false;
const oldChannelInfo = this.channels[channelInfo.index];
if (!oldChannelInfo) {
return didRender;
}
const maskChanged =
channelInfo === oldChannelInfo ||
channelInfo.layers.length !== oldChannelInfo.layers.length ||
channelInfo.layers.some(
(layer, i) =>
layer !== oldChannelInfo.layers[i] ||
layer.props.transitions
) ||
channelInfo.layerBounds.some((b, i) => b !== oldChannelInfo.layerBounds[i]);
channelInfo.bounds = oldChannelInfo.bounds;
channelInfo.maskBounds = oldChannelInfo.maskBounds;
this.channels[channelInfo.index] = channelInfo;
if (maskChanged || viewportChanged) {
this.lastViewport = viewport;
const layerBounds = joinLayerBounds(channelInfo.layers, viewport);
channelInfo.bounds = layerBounds && getRenderBounds(layerBounds, viewport);
if (maskChanged || !arraysEqual(channelInfo.bounds, oldChannelInfo.bounds)) {
const {maskPass, maskMap} = this;
const maskViewport =
layerBounds &&
makeViewport({
bounds: channelInfo.bounds,
viewport,
width: maskMap.width,
height: maskMap.height,
border: 1
});
channelInfo.maskBounds = maskViewport ? maskViewport.getBounds() : [0, 0, 1, 1];
maskPass.render({
pass: 'mask',
channel: channelInfo.index,
layers: channelInfo.layers,
layerFilter,
viewports: maskViewport ? [maskViewport] : [],
onViewportActive,
views,
moduleParameters: {
devicePixelRatio: 1
}
});
didRender = true;
}
}
this.masks[channelInfo.id] = {
index: channelInfo.index,
bounds: channelInfo.maskBounds,
coordinateOrigin: channelInfo.coordinateOrigin,
coordinateSystem: channelInfo.coordinateSystem
};
return didRender;
}
_sortMaskChannels(maskLayers) {
const channelMap = {};
let channelCount = 0;
for (const layer of maskLayers) {
const {id} = layer.root;
let channelInfo = channelMap[id];
if (!channelInfo) {
if (++channelCount > 4) {
console.warn('Too many mask layers. The max supported is 4')();
continue;
}
channelInfo = {
id,
index: this.channels.findIndex(c => c?.id === id),
layers: [],
layerBounds: [],
coordinateOrigin: layer.root.props.coordinateOrigin,
coordinateSystem: layer.root.props.coordinateSystem
};
channelMap[id] = channelInfo;
}
channelInfo.layers.push(layer);
channelInfo.layerBounds.push(layer.getBounds());
}
for (let i = 0; i < 4; i++) {
const channelInfo = this.channels[i];
if (!channelInfo || !(channelInfo.id in channelMap)) {
this.channels[i] = null;
}
}
for (const maskId in channelMap) {
const channelInfo = channelMap[maskId];
if (channelInfo.index < 0) {
channelInfo.index = this.channels.findIndex(c => !c);
this.channels[channelInfo.index] = channelInfo;
}
}
return channelMap;
}
getModuleParameters({ maskMap, maskChannels }) {
return {
maskMap: this.masks ? this.maskMap : this.dummyMaskMap,
maskChannels: this.masks
};
}
cleanup() {
if (this.dummyMaskMap) {
this.dummyMaskMap.delete();
this.dummyMaskMap = undefined;
}
if (this.maskPass) {
this.maskPass.delete();
this.maskPass = undefined;
this.maskMap = undefined;
}
this.lastViewport = undefined;
this.masks = null;
this.channels.length = 0;
}
}
return OutlineMaskEffect;
}