import { Layer } from './Layer.js' import { Raster16Bit } from './Raster16Bit.js' import { ShaderHDR } from './ShaderHDR.js' /** * @typedef {Object} LayerHDROptions * @property {string} url - URL of the image to display (required) * @property {string|Layout} [layout='image'] - Layout format for image display * @property {string} [format='rgba16f'] - Image data format for WebGL processing * @property {boolean} [debug=false] - Enable debug output * @extends LayerOptions */ /** * LayerHDR provides advanced HDR image rendering capabilities in OpenLIME. * It is designed for high dynamic range (HDR) image processing and rendering, * leveraging WebGL shaders and tone mapping techniques. * * Features: * - HDR tone mapping with configurable white point * - WebGL-based rendering with 16-bit precision * - Automatic raster data management * - Shader-based processing for HDR compression * * Technical Details: * - Uses WebGL textures for HDR image data * - Supports 16-bit float formats (e.g., rgba16f) * - Integrates with OpenLIME layout system * - Provides multiple tone mapping options: Reinhard, ACES, and Exposure * * @extends Layer * * @example * ```javascript * const hdrLayer = new OpenLIME.LayerHDR({ * url: 'hdr-image.hdr', * format: 'rgba16f' * }); * viewer.addLayer('hdr', hdrLayer); * ``` */ class LayerHDR extends Layer { /** * Creates a new LayerHDR instance. * * @param {LayerHDROptions} options - Configuration options for the HDR layer */ constructor(options) { options = Object.assign({ format: 'rgba16f', autoWhitePoint: true, debug: false, mode: 'reinhard', }, options); super(options); if (Object.keys(this.rasters).length != 0) throw "Rasters options should be empty!"; if (this.url) this.layout.setUrls([this.url]); else if (this.layout.urls.length == 0) throw "Missing options.url parameter"; const rasterOptions = { format: this.format, isLinear: true, // HDR data is always in linear space debug: this.debug }; // Add custom data loader if provided if (this.dataLoader) { rasterOptions.dataLoader = this.dataLoader; rasterOptions.dataLoaderOptions = this.dataLoaderOptions || {}; } let raster = new Raster16Bit(rasterOptions); raster.addEvent('loaded', () => { if (this.autoWhitePoint) { const maxValue = raster.getStatInfo().maxValue ? raster.getStatInfo().maxValue : 1.0; this.setWhitePoint(maxValue); } this.emit('loaded'); }); this.rasters.push(raster); // Create the HDR shader with all tone mapping parameters let shader = new ShaderHDR({ label: 'HDR', format: this.format, mode: this.mode || 'reinhard', }); this.shaders = { 'hdr': shader }; this.setShader('hdr'); // Reinhard params this.addControl('whitePoint', [1.0]); this.addControl('shadowLift', [0.0]); // ACES params this.addControl('acesContrast', [1.2]); // Exposure params this.addControl('exposure', [1.0]); // Balanced params this.addControl('highlightCompression', [1.0]); } /** * Sets the white point for HDR tone mapping. * * @param {number} v - The new white point value * @param {number} [delayms=1] - Delay in milliseconds for the transition * @param {string} [easing='linear'] - Easing function for the transition */ setWhitePoint(v, delayms = 1, easing = 'linear') { this.setControl('whitePoint', [v], delayms, easing); } /** * Gets the current white point value. * * @returns {number} The current white point value */ getWhitePoint() { return this.controls['whitePoint'].current.value[0]; } /** * Sets the shadow lift value for HDR tone mapping. * * @param {number} v - The new shadow lift value * @param {number} [delayms=1] - Delay in milliseconds for the transition * @param {string} [easing='linear'] - Easing function for the transition */ setShadowLift(v, delayms = 1, easing = 'linear') { this.setControl('shadowLift', [v], delayms, easing); } /** * Gets the current shadow lift value. * * @returns {number} The current shadow lift value */ getShadowLift() { return this.controls['shadowLift'].current.value[0]; } /** * Sets the ACES contrast parameter for ACES tone mapping. * * @param {number} v - The new ACES contrast value * @param {number} [delayms=1] - Delay in milliseconds for the transition * @param {string} [easing='linear'] - Easing function for the transition */ setAcesContrast(v, delayms = 1, easing = 'linear') { this.setControl('acesContrast', [v], delayms, easing); } /** * Gets the current ACES contrast value. * * @returns {number} The current ACES contrast value */ getAcesContrast() { return this.controls['acesContrast'].current.value[0]; } /** * Sets the exposure value for exposure-based tone mapping. * * @param {number} v - The new exposure value * @param {number} [delayms=1] - Delay in milliseconds for the transition * @param {string} [easing='linear'] - Easing function for the transition */ setExposure(v, delayms = 1, easing = 'linear') { this.setControl('exposure', [v], delayms, easing); } /** * Gets the current exposure value. * * @returns {number} The current exposure value */ getExposure() { return this.controls['exposure'].current.value[0]; } /** * Sets the highlight compression value for HDR tone mapping. * * @param {number} v - The new highlight compression value * @param {number} [delayms=1] - Delay in milliseconds for the transition * @param {string} [easing='linear'] - Easing function for the transition */ setHighlightCompression(v, delayms = 1, easing = 'linear') { this.setControl('highlightCompression', [v], delayms, easing); } /** * Gets the current highlight compression value. * * @returns {number} The current highlight compression value */ getHighlightCompression() { return this.controls['highlightCompression'].current.value[0]; } /** * Retrieves statistical information about the raster data. * * @returns {Object} An object containing statistical information (e.g., maxValue, avgLuminance) */ getStatInfo() { return this.rasters[0].getStatInfo(); } /** * Interpolates control values and updates the shader with the current parameters. * * @returns {boolean} Whether the interpolation is complete */ interpolateControls() { const done = super.interpolateControls(); const whitePoint = this.getWhitePoint(); const shadowLift = this.getShadowLift(); const acesContrast = this.getAcesContrast(); const exposure = this.getExposure(); const highlightCompression = this.getHighlightCompression(); this.shader.setWhitePoint(whitePoint); this.shader.setShadowLift(shadowLift); this.shader.setAcesContrast(acesContrast); this.shader.setExposure(exposure); this.shader.setHighlightCompression(highlightCompression); return done; } } /** * Registers this layer type with the Layer factory. * * @type {Function} * @private */ Layer.prototype.types['hdr'] = (options) => { return new LayerHDR(options); } export { LayerHDR }