import { LayerAnnotation } from './LayerAnnotation.js' import { Layer } from './Layer.js' import { Raster } from './Raster.js' import { Shader } from './Shader.js' import { LayoutTileImages } from './LayoutTileImages.js'; /** * @typedef {Object} LayerAnnotationImageOptions * @property {string} [url] - URL to the annotations JSON file * @property {string} [path] - Base path for annotation image files * @property {string} [format='vec4'] - Raster format for image data * @extends LayerAnnotationOptions */ /** * LayerAnnotationImage extends LayerAnnotation to provide support for image-based annotations. * Each annotation corresponds to a single tile in the layer, with customizable visibility * and shader-based rendering. * * Features: * - Image-based annotation rendering * - Per-annotation visibility control * - Custom shader support for image processing * - Automatic texture management * - WebGL/WebGL2 compatibility * - Multi-format raster support * * The class handles: * - Image loading and caching * - Texture creation and binding * - Shader setup and compilation * - Tile visibility management * - WebGL state management * * @extends LayerAnnotation * * @example * ```javascript * // Create image annotation layer * const imageAnnoLayer = new OpenLIME.LayerAnnotationImage({ * url: 'annotations.json', * path: './annotation-images/', * format: 'vec4' * }); * * // Configure visibility * imageAnnoLayer.setAllTilesVisible(true); * imageAnnoLayer.setTileVisible(0, false); // Hide first annotation * * // Add to viewer * viewer.addLayer('imageAnnotations', imageAnnoLayer); * ``` */ class LayerAnnotationImage extends LayerAnnotation { /** * Creates a new LayerAnnotationImage instance * @param {LayerAnnotationImageOptions} options - Configuration options * @throws {Error} If path is not specified (warns in console) */ constructor(options) { const url = options.url; if (options.path == null) { console.log("WARNING MISSING ANNOTATION PATH, SET TO ./annot/"); } super(options); const rasterFormat = this.format != null ? this.format : 'vec4'; let initCallback = () => { // Set Annotation Urls path if (options.path) { this.layout.path = options.path; } else if (url != null) { // Extract path from annotation.json path this.layout.setPathFromUrl(path); } for (let a of this.annotations) { let raster = new Raster({ format: rasterFormat }); this.rasters.push(raster); } console.log("Set " + this.annotations.length + " annotations into layout"); this.setupShader(rasterFormat); this.layout.setTileDescriptors(this.annotations); } this.addEvent('loaded', initCallback); } /** * Gets the number of annotations in the layer * @returns {number} Number of annotations */ length() { return this.annotations.length; } /** * Sets visibility for a specific annotation/tile * @param {number} index - Index of the annotation * @param {boolean} visible - Whether the annotation should be visible */ setTileVisible(index, visible) { this.layout.setTileVisible(index, visible); //this.annotations[index].needsUpdate = true; //this.emit('update'); } /** * Sets visibility for all annotations/tiles * @param {boolean} visible - Whether all annotations should be visible */ setAllTilesVisible(visible) { this.layout.setAllTilesVisible(visible); // for(let a of this.annotations) { // a.needsUpdate = true; // } //this.emit('update'); } /** * Renders a specific tile/annotation * @param {Object} tile - Tile object containing texture information * @param {number} index - Index of the tile * @throws {Error} If tile is missing textures * @private */ drawTile(tile, index) { if (tile.missing != 0) throw "Attempt to draw tile still missing textures" const idx = tile.index; //coords and texture buffers updated once for all tiles from main draw() call //bind texture of this tile only (each tile corresponds to an image) let gl = this.gl; let id = this.shader.samplers[idx].id; gl.uniform1i(this.shader.samplers[idx].location, idx); gl.activeTexture(gl.TEXTURE0 + idx); gl.bindTexture(gl.TEXTURE_2D, tile.tex[id]); const byteOffset = this.getTileByteOffset(index); gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, byteOffset); } /** * Configures the shader for rendering annotations * @param {string} rasterFormat - Format of the raster data ('vec4', etc) * @private */ setupShader(rasterFormat) { let samplers = []; let N = this.rasters.length; for (let i = 0; i < N; ++i) { samplers.push({ id: i, name: 'kd', type: rasterFormat }); } let shader = new Shader({ 'label': 'Rgb', 'samplers': samplers //[{ id:0, name:'kd', type: rasterFormat }] }); shader.fragShaderSrc = function (gl) { let gl2 = !(gl instanceof WebGLRenderingContext); let str = ` uniform sampler2D kd; ${gl2 ? 'in' : 'varying'} vec2 v_texcoord; vec4 data() { return texture${gl2 ? '' : '2D'}(kd, v_texcoord); } `; return str; }; this.shaders = { 'standard': shader }; this.setShader('standard'); } } Layer.prototype.types['annotation_image'] = (options) => { return new LayerAnnotationImage(options); } export { LayerAnnotationImage }