import { Layer } from './Layer.js'; import { LayerImage } from './LayerImage.js' import { ShaderDstretch } from './ShaderDstretch.js'; import { Raster } from './Raster.js'; /** * @typedef {Object} LayerDStretchOptions * @property {string} url - URL to the image to be processed (required) * @property {number[]} [eulerRotation=[0,0,0]] - Initial Euler rotation angles * @property {number} [worldRotation=0] - Global rotation offset for canvas/layer * @extends LayerImageOptions */ /** * LayerDStretch implements the DStretch (Decorrelation Stretch) algorithm for image enhancement. * This technique is particularly useful in archaeology and rock art documentation for revealing * faint pictographs and petroglyphs. * * DStretch works by: * 1. Converting RGB colors to a decorrelated color space * 2. Equalizing and stretching the color distributions * 3. Converting back to RGB for display * * Features: * - Real-time image enhancement * - Interactive control of enhancement parameters * - Automatic color statistics computation * - Support for large images through tiling * - GPU-accelerated processing * - Light direction control * * Technical Implementation: * - Uses Principal Component Analysis (PCA) for color decorrelation * - Supports custom transformation matrices * - Implements dynamic sampling for color statistics * - Handles WebGL texture management * - Supports progressive loading * * @extends LayerImage * * @example * ```javascript * // Create DStretch layer * const dstretchLayer = new OpenLIME.Layer({ * type: 'dstretch', * url: 'image.jpg', * visible: true * }); * * // Add to viewer * viewer.addLayer('enhanced', dstretchLayer); * * // Adjust enhancement parameters * dstretchLayer.setLight([0.5, 0.3], 500); // Animate to new enhancement * ``` */ class LayerDstretch extends LayerImage { /** * Creates a new LayerDStretch instance * @param {LayerDStretchOptions} options - Configuration options * @throws {Error} If url option is not provided */ constructor(options) { super(options); if (!this.url) throw "Url option is required"; this.shaders['dstretch'] = new ShaderDstretch(); this.setShader('dstretch'); this.eulerRotation = [0, 0, 0]; this.worldRotation = 0; //if the canvas or ethe layer rotate, light direction neeeds to be rotated too. if (this.url) this.loadJson(); this.addControl('light', [0, 0]); } /** * Sets the enhancement parameters through a light-like interface * @param {number[]} value - Two values controlling the enhancement transformation * @param {number} [dt] - Animation duration in milliseconds * @fires Layer#update */ setLight(value, dt) { this.setControl('light', value, dt); this.eulerRotation[0] = Math.PI * this.getControl('light').current.value[0];//this.controls['light'].current[0]; this.eulerRotation[1] = Math.PI * this.getControl('light').current.value[1];//this.controls['light'].current[1]; this.shader.updateRotationMatrix(this.eulerRotation); this.emit('update'); } /** * Loads DStretch configuration and image data * Attempts to load a .dstretch companion file with pre-computed statistics * Falls back to runtime sampling if companion file is not found * @private */ loadJson() { (async () => { let json try { let dstretchUrl = this.url.substring(0, this.url.lastIndexOf(".")) + ".dstretch"; let response = await fetch(dstretchUrl); console.log(response.ok); json = await response.json(); } catch (error) { json = { transformation: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1], samples: [] }; this.rasters[0].loadTexture = this.loadAndSampleTexture.bind(this); } this.layout.setUrls([this.url]); this.shader.init(json); })(); } /** * Renders the enhanced image * @param {Transform} transform - Current view transform * @param {Object} viewport - Current viewport parameters * @returns {boolean} Whether the render completed successfully * @override * @private */ draw(transform, viewport) { this.shader.setMinMax(); return super.draw(transform, viewport); } /** * Loads texture and performs color sampling if needed * Samples the image in a grid pattern to compute color statistics * @param {WebGLRenderingContext} gl - WebGL context * @param {HTMLImageElement} img - Source image * @returns {WebGLTexture} Created texture * @private */ loadAndSampleTexture(gl, img) { this.rasters[0].width = img.width; this.rasters[0].height = img.height; let tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); //_MIPMAP_LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); // Sample the texture // Temporarily print the texture on a canvas let canvas = document.createElement("canvas"); let context = canvas.getContext("2d"); canvas.setAttribute("width", img.width); canvas.setAttribute("height", img.height); context.drawImage(img, 0, 0, img.width, img.height); // Get the data and sample the texture let imageData = context.getImageData(0, 0, img.width, img.height).data; let samples = []; let rowSkip = Math.floor(img.height / 32); let colSkip = Math.floor(img.width / 32); console.log(rowSkip, colSkip); for (let i = 0; i < imageData.length; i += 4) { let row = Math.floor((i / 4) / img.width); let col = Math.floor(i / 4) % img.width; if (row % rowSkip == 0 && col % colSkip == 0) samples.push([imageData[i], imageData[i + 1], imageData[i + 2]]); } this.shader.samples = samples; this.shader.setMinMax(); return tex; } } /** * Register this layer type with the Layer factory * @type {Function} * @private */ Layer.prototype.types['dstretch'] = (options) => { return new LayerDstretch(options); } export { LayerDstretch }