Initial project import
This commit is contained in:
814
node_modules/@monogrid/gainmap-js/dist/decode.webgpu.js
generated
vendored
Normal file
814
node_modules/@monogrid/gainmap-js/dist/decode.webgpu.js
generated
vendored
Normal file
@@ -0,0 +1,814 @@
|
||||
/**
|
||||
* @monogrid/gainmap-js v3.4.0
|
||||
* With ❤️, by MONOGRID <gainmap@monogrid.com>
|
||||
*/
|
||||
|
||||
import { c as createDecodeFunction, L as LoaderBaseShared, e as extractGainmapFromJPEG, X as XMPMetadataNotFoundError, G as GainMapNotFoundError } from './Loader-DLI-_JDP.js';
|
||||
export { M as MPFExtractor, a as extractXMP } from './Loader-DLI-_JDP.js';
|
||||
import { ClampToEdgeWrapping, LinearFilter, Scene, OrthographicCamera, Mesh, PlaneGeometry, RenderTarget, RGBAFormat, UVMapping, WebGPURenderer, DataTexture, LinearSRGBColorSpace, ShaderMaterial, Texture, MeshBasicNodeMaterial, NoBlending, FileLoader } from 'three/webgpu';
|
||||
import 'three';
|
||||
import { vec3, texture, uniform, pow, sub, float, add, mul, exp2, max, min } from 'three/tsl';
|
||||
|
||||
/**
|
||||
* Utility class used for rendering a texture with a material (WebGPU version)
|
||||
*
|
||||
* @category Core
|
||||
* @group Core
|
||||
*/
|
||||
class QuadRenderer {
|
||||
_renderer;
|
||||
_rendererIsDisposable = false;
|
||||
_material;
|
||||
_scene;
|
||||
_camera;
|
||||
_quad;
|
||||
_renderTarget;
|
||||
_width;
|
||||
_height;
|
||||
_type;
|
||||
_colorSpace;
|
||||
_supportsReadPixels = true;
|
||||
/**
|
||||
* Constructs a new QuadRenderer
|
||||
*
|
||||
* @param options Parameters for this QuadRenderer
|
||||
*/
|
||||
constructor(options) {
|
||||
this._width = options.width;
|
||||
this._height = options.height;
|
||||
this._type = options.type;
|
||||
this._colorSpace = options.colorSpace;
|
||||
const rtOptions = {
|
||||
// fixed options
|
||||
format: RGBAFormat,
|
||||
depthBuffer: false,
|
||||
stencilBuffer: false,
|
||||
// user options
|
||||
type: this._type, // set in class property
|
||||
colorSpace: this._colorSpace, // set in class property
|
||||
anisotropy: options.renderTargetOptions?.anisotropy !== undefined ? options.renderTargetOptions?.anisotropy : 1,
|
||||
generateMipmaps: options.renderTargetOptions?.generateMipmaps !== undefined ? options.renderTargetOptions?.generateMipmaps : false,
|
||||
magFilter: options.renderTargetOptions?.magFilter !== undefined ? options.renderTargetOptions?.magFilter : LinearFilter,
|
||||
minFilter: options.renderTargetOptions?.minFilter !== undefined ? options.renderTargetOptions?.minFilter : LinearFilter,
|
||||
samples: options.renderTargetOptions?.samples !== undefined ? options.renderTargetOptions?.samples : undefined,
|
||||
wrapS: options.renderTargetOptions?.wrapS !== undefined ? options.renderTargetOptions?.wrapS : ClampToEdgeWrapping,
|
||||
wrapT: options.renderTargetOptions?.wrapT !== undefined ? options.renderTargetOptions?.wrapT : ClampToEdgeWrapping
|
||||
};
|
||||
this._material = options.material;
|
||||
if (options.renderer) {
|
||||
this._renderer = options.renderer;
|
||||
}
|
||||
else {
|
||||
this._renderer = QuadRenderer.instantiateRenderer();
|
||||
this._rendererIsDisposable = true;
|
||||
}
|
||||
this._scene = new Scene();
|
||||
this._camera = new OrthographicCamera();
|
||||
this._camera.position.set(0, 0, 10);
|
||||
this._camera.left = -0.5;
|
||||
this._camera.right = 0.5;
|
||||
this._camera.top = 0.5;
|
||||
this._camera.bottom = -0.5;
|
||||
this._camera.updateProjectionMatrix();
|
||||
this._quad = new Mesh(new PlaneGeometry(), this._material);
|
||||
this._quad.geometry.computeBoundingBox();
|
||||
this._scene.add(this._quad);
|
||||
this._renderTarget = new RenderTarget(this.width, this.height, rtOptions);
|
||||
this._renderTarget.texture.mapping = options.renderTargetOptions?.mapping !== undefined ? options.renderTargetOptions?.mapping : UVMapping;
|
||||
}
|
||||
/**
|
||||
* Instantiates a temporary renderer
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
static instantiateRenderer() {
|
||||
const renderer = new WebGPURenderer();
|
||||
renderer.setSize(128, 128);
|
||||
return renderer;
|
||||
}
|
||||
/**
|
||||
* Renders the input texture using the specified material
|
||||
*/
|
||||
render = async () => {
|
||||
if (!this._renderer.hasInitialized()) {
|
||||
await this._renderer.init();
|
||||
}
|
||||
this._renderer.setRenderTarget(this._renderTarget);
|
||||
try {
|
||||
this._renderer.render(this._scene, this._camera);
|
||||
}
|
||||
catch (e) {
|
||||
this._renderer.setRenderTarget(null);
|
||||
throw e;
|
||||
}
|
||||
this._renderer.setRenderTarget(null);
|
||||
};
|
||||
/**
|
||||
* Obtains a Buffer containing the rendered texture.
|
||||
*
|
||||
* @throws Error if the browser cannot read pixels from this RenderTarget type.
|
||||
* @returns a TypedArray containing RGBA values from this renderer
|
||||
*/
|
||||
async toArray() {
|
||||
if (!this._supportsReadPixels)
|
||||
throw new Error('Can\'t read pixels in this browser');
|
||||
const out = await this._renderer.readRenderTargetPixelsAsync(this._renderTarget, 0, 0, this._width, this._height);
|
||||
return out;
|
||||
}
|
||||
/**
|
||||
* Performs a readPixel operation in the renderTarget
|
||||
* and returns a DataTexture containing the read data
|
||||
*
|
||||
* @param options options
|
||||
* @returns
|
||||
*/
|
||||
async toDataTexture(options) {
|
||||
const returnValue = new DataTexture(
|
||||
// fixed values
|
||||
await this.toArray(), this.width, this.height, RGBAFormat, this._type,
|
||||
// user values
|
||||
options?.mapping || UVMapping, options?.wrapS || ClampToEdgeWrapping, options?.wrapT || ClampToEdgeWrapping, options?.magFilter || LinearFilter, options?.minFilter || LinearFilter, options?.anisotropy || 1,
|
||||
// fixed value
|
||||
LinearSRGBColorSpace);
|
||||
returnValue.flipY = options?.flipY !== undefined ? options?.flipY : true;
|
||||
// set this afterwards, we can't set it in constructor
|
||||
returnValue.generateMipmaps = options?.generateMipmaps !== undefined ? options?.generateMipmaps : false;
|
||||
return returnValue;
|
||||
}
|
||||
/**
|
||||
* If using a disposable renderer, it will dispose it.
|
||||
*/
|
||||
disposeOnDemandRenderer() {
|
||||
this._renderer.setRenderTarget(null);
|
||||
if (this._rendererIsDisposable) {
|
||||
this._renderer.dispose();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Will dispose of **all** assets used by this renderer.
|
||||
*
|
||||
*
|
||||
* @param disposeRenderTarget will dispose of the renderTarget which will not be usable later
|
||||
* set this to true if you passed the `renderTarget.texture` to a `PMREMGenerator`
|
||||
* or are otherwise done with it.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const loader = new HDRJPGLoader(renderer)
|
||||
* const result = await loader.loadAsync('gainmap.jpeg')
|
||||
* const mesh = new Mesh(geometry, new MeshBasicMaterial({ map: result.renderTarget.texture }) )
|
||||
* // DO NOT dispose the renderTarget here,
|
||||
* // it is used directly in the material
|
||||
* result.dispose()
|
||||
* ```
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const loader = new HDRJPGLoader(renderer)
|
||||
* const pmremGenerator = new PMREMGenerator( renderer );
|
||||
* const result = await loader.loadAsync('gainmap.jpeg')
|
||||
* const envMap = pmremGenerator.fromEquirectangular(result.renderTarget.texture)
|
||||
* const mesh = new Mesh(geometry, new MeshStandardMaterial({ envMap }) )
|
||||
* // renderTarget can be disposed here
|
||||
* // because it was used to generate a PMREM texture
|
||||
* result.dispose(true)
|
||||
* ```
|
||||
*/
|
||||
dispose(disposeRenderTarget) {
|
||||
if (disposeRenderTarget) {
|
||||
this.renderTarget.dispose();
|
||||
}
|
||||
// dispose shader material texture uniforms
|
||||
if (this.material instanceof ShaderMaterial) {
|
||||
Object.values(this.material.uniforms).forEach(v => {
|
||||
if (v.value instanceof Texture)
|
||||
v.value.dispose();
|
||||
});
|
||||
}
|
||||
// dispose other material properties
|
||||
Object.values(this.material).forEach(value => {
|
||||
if (value instanceof Texture)
|
||||
value.dispose();
|
||||
});
|
||||
this.material.dispose();
|
||||
this._quad.geometry.dispose();
|
||||
this.disposeOnDemandRenderer();
|
||||
}
|
||||
/**
|
||||
* Width of the texture
|
||||
*/
|
||||
get width() { return this._width; }
|
||||
set width(value) {
|
||||
this._width = value;
|
||||
this._renderTarget.setSize(this._width, this._height);
|
||||
}
|
||||
/**
|
||||
* Height of the texture
|
||||
*/
|
||||
get height() { return this._height; }
|
||||
set height(value) {
|
||||
this._height = value;
|
||||
this._renderTarget.setSize(this._width, this._height);
|
||||
}
|
||||
/**
|
||||
* The renderer used
|
||||
*/
|
||||
get renderer() { return this._renderer; }
|
||||
/**
|
||||
* The `RenderTarget` used.
|
||||
*/
|
||||
get renderTarget() { return this._renderTarget; }
|
||||
set renderTarget(value) {
|
||||
this._renderTarget = value;
|
||||
this._width = value.width;
|
||||
this._height = value.height;
|
||||
}
|
||||
/**
|
||||
* The `Material` used.
|
||||
*/
|
||||
get material() { return this._material; }
|
||||
/**
|
||||
*
|
||||
*/
|
||||
get type() { return this._type; }
|
||||
get colorSpace() { return this._colorSpace; }
|
||||
}
|
||||
|
||||
// min half float value
|
||||
const HALF_FLOAT_MIN = vec3(-65504, -65504, -65504);
|
||||
// max half float value
|
||||
const HALF_FLOAT_MAX = vec3(65504, 65504, 65504);
|
||||
/**
|
||||
* A Material which is able to decode the Gainmap into a full HDR Representation using TSL (Three.js Shading Language)
|
||||
*
|
||||
* @category Materials
|
||||
* @group Materials
|
||||
*/
|
||||
class GainMapDecoderMaterial extends MeshBasicNodeMaterial {
|
||||
_maxDisplayBoost;
|
||||
_hdrCapacityMin;
|
||||
_hdrCapacityMax;
|
||||
// Uniforms for TSL
|
||||
_gammaUniform;
|
||||
_offsetHdrUniform;
|
||||
_offsetSdrUniform;
|
||||
_gainMapMinUniform;
|
||||
_gainMapMaxUniform;
|
||||
_weightFactorUniform;
|
||||
_sdrTexture;
|
||||
_gainMapTexture;
|
||||
/**
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
constructor({ gamma, offsetHdr, offsetSdr, gainMapMin, gainMapMax, maxDisplayBoost, hdrCapacityMin, hdrCapacityMax, sdr, gainMap }) {
|
||||
super();
|
||||
this.name = 'GainMapDecoderMaterial';
|
||||
this.blending = NoBlending;
|
||||
this.depthTest = false;
|
||||
this.depthWrite = false;
|
||||
this._sdrTexture = texture(sdr);
|
||||
this._gainMapTexture = texture(gainMap);
|
||||
// Create uniform nodes
|
||||
this._gammaUniform = uniform(vec3(1.0 / gamma[0], 1.0 / gamma[1], 1.0 / gamma[2]));
|
||||
this._offsetHdrUniform = uniform(vec3(offsetHdr[0], offsetHdr[1], offsetHdr[2]));
|
||||
this._offsetSdrUniform = uniform(vec3(offsetSdr[0], offsetSdr[1], offsetSdr[2]));
|
||||
this._gainMapMinUniform = uniform(vec3(gainMapMin[0], gainMapMin[1], gainMapMin[2]));
|
||||
this._gainMapMaxUniform = uniform(vec3(gainMapMax[0], gainMapMax[1], gainMapMax[2]));
|
||||
const weightFactor = (Math.log2(maxDisplayBoost) - hdrCapacityMin) / (hdrCapacityMax - hdrCapacityMin);
|
||||
this._weightFactorUniform = uniform(weightFactor);
|
||||
this._maxDisplayBoost = maxDisplayBoost;
|
||||
this._hdrCapacityMin = hdrCapacityMin;
|
||||
this._hdrCapacityMax = hdrCapacityMax;
|
||||
// Build the TSL shader graph
|
||||
// Get RGB values
|
||||
const rgb = this._sdrTexture.rgb;
|
||||
const recovery = this._gainMapTexture.rgb;
|
||||
// Apply gamma correction
|
||||
const logRecovery = pow(recovery, this._gammaUniform);
|
||||
// Calculate log boost
|
||||
// logBoost = gainMapMin * (1.0 - logRecovery) + gainMapMax * logRecovery
|
||||
const oneMinusLogRecovery = sub(float(1.0), logRecovery);
|
||||
const logBoost = add(mul(this._gainMapMinUniform, oneMinusLogRecovery), mul(this._gainMapMaxUniform, logRecovery));
|
||||
// Calculate HDR color
|
||||
// hdrColor = (rgb + offsetSdr) * exp2(logBoost * weightFactor) - offsetHdr
|
||||
const hdrColor = sub(mul(add(rgb, this._offsetSdrUniform), exp2(mul(logBoost, this._weightFactorUniform))), this._offsetHdrUniform);
|
||||
// Clamp to half float range
|
||||
const clampedHdrColor = max(HALF_FLOAT_MIN, min(HALF_FLOAT_MAX, hdrColor));
|
||||
// Set the color output
|
||||
this.colorNode = clampedHdrColor;
|
||||
}
|
||||
get sdr() { return this._sdrTexture.value; }
|
||||
set sdr(value) { this._sdrTexture.value = value; }
|
||||
get gainMap() { return this._gainMapTexture.value; }
|
||||
set gainMap(value) { this._gainMapTexture.value = value; }
|
||||
/**
|
||||
* @see {@link GainMapMetadata.offsetHdr}
|
||||
*/
|
||||
get offsetHdr() {
|
||||
return [this._offsetHdrUniform.value.x, this._offsetHdrUniform.value.y, this._offsetHdrUniform.value.z];
|
||||
}
|
||||
set offsetHdr(value) {
|
||||
this._offsetHdrUniform.value.x = value[0];
|
||||
this._offsetHdrUniform.value.y = value[1];
|
||||
this._offsetHdrUniform.value.z = value[2];
|
||||
}
|
||||
/**
|
||||
* @see {@link GainMapMetadata.offsetSdr}
|
||||
*/
|
||||
get offsetSdr() {
|
||||
return [this._offsetSdrUniform.value.x, this._offsetSdrUniform.value.y, this._offsetSdrUniform.value.z];
|
||||
}
|
||||
set offsetSdr(value) {
|
||||
this._offsetSdrUniform.value.x = value[0];
|
||||
this._offsetSdrUniform.value.y = value[1];
|
||||
this._offsetSdrUniform.value.z = value[2];
|
||||
}
|
||||
/**
|
||||
* @see {@link GainMapMetadata.gainMapMin}
|
||||
*/
|
||||
get gainMapMin() {
|
||||
return [this._gainMapMinUniform.value.x, this._gainMapMinUniform.value.y, this._gainMapMinUniform.value.z];
|
||||
}
|
||||
set gainMapMin(value) {
|
||||
this._gainMapMinUniform.value.x = value[0];
|
||||
this._gainMapMinUniform.value.y = value[1];
|
||||
this._gainMapMinUniform.value.z = value[2];
|
||||
}
|
||||
/**
|
||||
* @see {@link GainMapMetadata.gainMapMax}
|
||||
*/
|
||||
get gainMapMax() {
|
||||
return [this._gainMapMaxUniform.value.x, this._gainMapMaxUniform.value.y, this._gainMapMaxUniform.value.z];
|
||||
}
|
||||
set gainMapMax(value) {
|
||||
this._gainMapMaxUniform.value.x = value[0];
|
||||
this._gainMapMaxUniform.value.y = value[1];
|
||||
this._gainMapMaxUniform.value.z = value[2];
|
||||
}
|
||||
/**
|
||||
* @see {@link GainMapMetadata.gamma}
|
||||
*/
|
||||
get gamma() {
|
||||
return [1 / this._gammaUniform.value.x, 1 / this._gammaUniform.value.y, 1 / this._gammaUniform.value.z];
|
||||
}
|
||||
set gamma(value) {
|
||||
this._gammaUniform.value.x = 1.0 / value[0];
|
||||
this._gammaUniform.value.y = 1.0 / value[1];
|
||||
this._gammaUniform.value.z = 1.0 / value[2];
|
||||
}
|
||||
/**
|
||||
* @see {@link GainMapMetadata.hdrCapacityMin}
|
||||
* @remarks Logarithmic space
|
||||
*/
|
||||
get hdrCapacityMin() { return this._hdrCapacityMin; }
|
||||
set hdrCapacityMin(value) {
|
||||
this._hdrCapacityMin = value;
|
||||
this.calculateWeight();
|
||||
}
|
||||
/**
|
||||
* @see {@link GainMapMetadata.hdrCapacityMax}
|
||||
* @remarks Logarithmic space
|
||||
*/
|
||||
get hdrCapacityMax() { return this._hdrCapacityMax; }
|
||||
set hdrCapacityMax(value) {
|
||||
this._hdrCapacityMax = value;
|
||||
this.calculateWeight();
|
||||
}
|
||||
/**
|
||||
* @see {@link GainmapDecodingParameters.maxDisplayBoost}
|
||||
* @remarks Non Logarithmic space
|
||||
*/
|
||||
get maxDisplayBoost() { return this._maxDisplayBoost; }
|
||||
set maxDisplayBoost(value) {
|
||||
this._maxDisplayBoost = Math.max(1, Math.min(65504, value));
|
||||
this.calculateWeight();
|
||||
}
|
||||
calculateWeight() {
|
||||
const val = (Math.log2(this._maxDisplayBoost) - this._hdrCapacityMin) / (this._hdrCapacityMax - this._hdrCapacityMin);
|
||||
this._weightFactorUniform.value = Math.max(0, Math.min(1, val));
|
||||
}
|
||||
}
|
||||
|
||||
const decodeImpl = createDecodeFunction({
|
||||
renderer: WebGPURenderer,
|
||||
createMaterial: (params) => new GainMapDecoderMaterial(params),
|
||||
createQuadRenderer: (params) => new QuadRenderer(params)
|
||||
});
|
||||
/**
|
||||
* Decodes a gain map using WebGPU RenderTarget
|
||||
*
|
||||
* @category Decoding Functions
|
||||
* @group Decoding Functions
|
||||
* @example
|
||||
* import { decode } from '@monogrid/gainmap-js/webgpu'
|
||||
* import {
|
||||
* Mesh,
|
||||
* MeshBasicMaterial,
|
||||
* PerspectiveCamera,
|
||||
* PlaneGeometry,
|
||||
* Scene,
|
||||
* TextureLoader,
|
||||
* WebGPURenderer
|
||||
* } from 'three/webgpu'
|
||||
*
|
||||
* const renderer = new WebGPURenderer()
|
||||
*
|
||||
* const textureLoader = new TextureLoader()
|
||||
*
|
||||
* // load SDR Representation
|
||||
* const sdr = await textureLoader.loadAsync('sdr.jpg')
|
||||
* // load Gain map recovery image
|
||||
* const gainMap = await textureLoader.loadAsync('gainmap.jpg')
|
||||
* // load metadata
|
||||
* const metadata = await (await fetch('metadata.json')).json()
|
||||
*
|
||||
* const result = await decode({
|
||||
* sdr,
|
||||
* gainMap,
|
||||
* // this allows to use `result.renderTarget.texture` directly
|
||||
* renderer,
|
||||
* // this will restore the full HDR range
|
||||
* maxDisplayBoost: Math.pow(2, metadata.hdrCapacityMax),
|
||||
* ...metadata
|
||||
* })
|
||||
*
|
||||
* const scene = new Scene()
|
||||
* // `result` can be used to populate a Texture
|
||||
* const mesh = new Mesh(
|
||||
* new PlaneGeometry(),
|
||||
* new MeshBasicMaterial({ map: result.renderTarget.texture })
|
||||
* )
|
||||
* scene.add(mesh)
|
||||
* renderer.render(scene, new PerspectiveCamera())
|
||||
*
|
||||
* // result must be manually disposed
|
||||
* // when you are done using it
|
||||
* result.dispose()
|
||||
*
|
||||
* @param params
|
||||
* @returns
|
||||
* @throws {Error} if the WebGPURenderer fails to render the gain map
|
||||
*/
|
||||
const decode = async (params) => {
|
||||
// Ensure renderer is defined for the base function
|
||||
if (!params.renderer) {
|
||||
throw new Error('Renderer is required for decode function');
|
||||
}
|
||||
const quadRenderer = decodeImpl({
|
||||
...params,
|
||||
renderer: params.renderer
|
||||
});
|
||||
try {
|
||||
await quadRenderer.render();
|
||||
}
|
||||
catch (e) {
|
||||
quadRenderer.disposeOnDemandRenderer();
|
||||
throw e;
|
||||
}
|
||||
return quadRenderer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for WebGPU loaders
|
||||
* @template TUrl - The type of URL used to load resources
|
||||
*/
|
||||
class LoaderBaseWebGPU extends LoaderBaseShared {
|
||||
constructor(renderer, manager) {
|
||||
super({
|
||||
renderer,
|
||||
createMaterial: (params) => new GainMapDecoderMaterial(params),
|
||||
createQuadRenderer: (params) => new QuadRenderer(params)
|
||||
}, manager);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param quadRenderer
|
||||
* @param metadata
|
||||
* @param sdrBuffer
|
||||
* @param gainMapBuffer
|
||||
*/
|
||||
async render(quadRenderer, metadata, sdrBuffer, gainMapBuffer) {
|
||||
// in WebGPU we apparently don't need flipY under any circumstance
|
||||
// except in QuadRenderer.toDataTexture() where we perform it in the texture itself
|
||||
const { sdrImage, gainMapImage, needsFlip } = await this.processImages(sdrBuffer, gainMapBuffer, 'from-image');
|
||||
const { gainMap, sdr } = this.createTextures(sdrImage, gainMapImage, needsFlip);
|
||||
this.updateQuadRenderer(quadRenderer, sdrImage, gainMap, sdr, metadata);
|
||||
await quadRenderer.render();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Three.js Loader for the gain map format (WebGPU version).
|
||||
*
|
||||
* @category Loaders
|
||||
* @group Loaders
|
||||
*
|
||||
* @example
|
||||
* import { GainMapLoader } from '@monogrid/gainmap-js/webgpu'
|
||||
* import {
|
||||
* EquirectangularReflectionMapping,
|
||||
* Mesh,
|
||||
* MeshBasicMaterial,
|
||||
* PerspectiveCamera,
|
||||
* PlaneGeometry,
|
||||
* Scene,
|
||||
* WebGPURenderer
|
||||
* } from 'three/webgpu'
|
||||
*
|
||||
* const renderer = new WebGPURenderer()
|
||||
* await renderer.init()
|
||||
*
|
||||
* const loader = new GainMapLoader(renderer)
|
||||
* .setRenderTargetOptions({ mapping: EquirectangularReflectionMapping })
|
||||
*
|
||||
* const result = await loader.loadAsync(['sdr.jpeg', 'gainmap.jpeg', 'metadata.json'])
|
||||
* // `result` can be used to populate a Texture
|
||||
*
|
||||
* const scene = new Scene()
|
||||
* const mesh = new Mesh(
|
||||
* new PlaneGeometry(),
|
||||
* new MeshBasicMaterial({ map: result.renderTarget.texture })
|
||||
* )
|
||||
* scene.add(mesh)
|
||||
* renderer.render(scene, new PerspectiveCamera())
|
||||
*
|
||||
* // Starting from three.js r159
|
||||
* // `result.renderTarget.texture` can
|
||||
* // also be used as Equirectangular scene background
|
||||
* //
|
||||
* // it was previously needed to convert it
|
||||
* // to a DataTexture with `result.toDataTexture()`
|
||||
* scene.background = result.renderTarget.texture
|
||||
*
|
||||
* // result must be manually disposed
|
||||
* // when you are done using it
|
||||
* result.dispose()
|
||||
*
|
||||
*/
|
||||
class GainMapLoader extends LoaderBaseWebGPU {
|
||||
/**
|
||||
* Loads a gainmap using separate data
|
||||
* * sdr image
|
||||
* * gain map image
|
||||
* * metadata json
|
||||
*
|
||||
* useful for webp gain maps
|
||||
*
|
||||
* @param urls An array in the form of [sdr.jpg, gainmap.jpg, metadata.json]
|
||||
* @param onLoad Load complete callback, will receive the result
|
||||
* @param onProgress Progress callback, will receive a `ProgressEvent`
|
||||
* @param onError Error callback
|
||||
* @returns
|
||||
*/
|
||||
load([sdrUrl, gainMapUrl, metadataUrl], onLoad, onProgress, onError) {
|
||||
const quadRenderer = this.prepareQuadRenderer();
|
||||
let sdr;
|
||||
let gainMap;
|
||||
let metadata;
|
||||
const loadCheck = async () => {
|
||||
if (sdr && gainMap && metadata) {
|
||||
// solves #16
|
||||
try {
|
||||
await this.render(quadRenderer, metadata, sdr, gainMap);
|
||||
}
|
||||
catch (error) {
|
||||
this.manager.itemError(sdrUrl);
|
||||
this.manager.itemError(gainMapUrl);
|
||||
this.manager.itemError(metadataUrl);
|
||||
if (typeof onError === 'function')
|
||||
onError(error);
|
||||
quadRenderer.disposeOnDemandRenderer();
|
||||
return;
|
||||
}
|
||||
if (typeof onLoad === 'function')
|
||||
onLoad(quadRenderer);
|
||||
this.manager.itemEnd(sdrUrl);
|
||||
this.manager.itemEnd(gainMapUrl);
|
||||
this.manager.itemEnd(metadataUrl);
|
||||
quadRenderer.disposeOnDemandRenderer();
|
||||
}
|
||||
};
|
||||
let sdrLengthComputable = true;
|
||||
let sdrTotal = 0;
|
||||
let sdrLoaded = 0;
|
||||
let gainMapLengthComputable = true;
|
||||
let gainMapTotal = 0;
|
||||
let gainMapLoaded = 0;
|
||||
let metadataLengthComputable = true;
|
||||
let metadataTotal = 0;
|
||||
let metadataLoaded = 0;
|
||||
const progressHandler = () => {
|
||||
if (typeof onProgress === 'function') {
|
||||
const total = sdrTotal + gainMapTotal + metadataTotal;
|
||||
const loaded = sdrLoaded + gainMapLoaded + metadataLoaded;
|
||||
const lengthComputable = sdrLengthComputable && gainMapLengthComputable && metadataLengthComputable;
|
||||
onProgress(new ProgressEvent('progress', { lengthComputable, loaded, total }));
|
||||
}
|
||||
};
|
||||
this.manager.itemStart(sdrUrl);
|
||||
this.manager.itemStart(gainMapUrl);
|
||||
this.manager.itemStart(metadataUrl);
|
||||
const sdrLoader = new FileLoader(this._internalLoadingManager);
|
||||
sdrLoader.setResponseType('arraybuffer');
|
||||
sdrLoader.setRequestHeader(this.requestHeader);
|
||||
sdrLoader.setPath(this.path);
|
||||
sdrLoader.setWithCredentials(this.withCredentials);
|
||||
sdrLoader.load(sdrUrl, async (buffer) => {
|
||||
/* istanbul ignore if
|
||||
this condition exists only because of three.js types + strict mode
|
||||
*/
|
||||
if (typeof buffer === 'string')
|
||||
throw new Error('Invalid sdr buffer');
|
||||
sdr = buffer;
|
||||
await loadCheck();
|
||||
}, (e) => {
|
||||
sdrLengthComputable = e.lengthComputable;
|
||||
sdrLoaded = e.loaded;
|
||||
sdrTotal = e.total;
|
||||
progressHandler();
|
||||
}, (error) => {
|
||||
this.manager.itemError(sdrUrl);
|
||||
if (typeof onError === 'function')
|
||||
onError(error);
|
||||
});
|
||||
const gainMapLoader = new FileLoader(this._internalLoadingManager);
|
||||
gainMapLoader.setResponseType('arraybuffer');
|
||||
gainMapLoader.setRequestHeader(this.requestHeader);
|
||||
gainMapLoader.setPath(this.path);
|
||||
gainMapLoader.setWithCredentials(this.withCredentials);
|
||||
gainMapLoader.load(gainMapUrl, async (buffer) => {
|
||||
/* istanbul ignore if
|
||||
this condition exists only because of three.js types + strict mode
|
||||
*/
|
||||
if (typeof buffer === 'string')
|
||||
throw new Error('Invalid gainmap buffer');
|
||||
gainMap = buffer;
|
||||
await loadCheck();
|
||||
}, (e) => {
|
||||
gainMapLengthComputable = e.lengthComputable;
|
||||
gainMapLoaded = e.loaded;
|
||||
gainMapTotal = e.total;
|
||||
progressHandler();
|
||||
}, (error) => {
|
||||
this.manager.itemError(gainMapUrl);
|
||||
if (typeof onError === 'function')
|
||||
onError(error);
|
||||
});
|
||||
const metadataLoader = new FileLoader(this._internalLoadingManager);
|
||||
// metadataLoader.setResponseType('json')
|
||||
metadataLoader.setRequestHeader(this.requestHeader);
|
||||
metadataLoader.setPath(this.path);
|
||||
metadataLoader.setWithCredentials(this.withCredentials);
|
||||
metadataLoader.load(metadataUrl, async (json) => {
|
||||
/* istanbul ignore if
|
||||
this condition exists only because of three.js types + strict mode
|
||||
*/
|
||||
if (typeof json !== 'string')
|
||||
throw new Error('Invalid metadata string');
|
||||
// TODO: implement check on JSON file and remove this eslint disable
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
metadata = JSON.parse(json);
|
||||
await loadCheck();
|
||||
}, (e) => {
|
||||
metadataLengthComputable = e.lengthComputable;
|
||||
metadataLoaded = e.loaded;
|
||||
metadataTotal = e.total;
|
||||
progressHandler();
|
||||
}, (error) => {
|
||||
this.manager.itemError(metadataUrl);
|
||||
if (typeof onError === 'function')
|
||||
onError(error);
|
||||
});
|
||||
return quadRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Three.js Loader for a JPEG with embedded gainmap metadata (WebGPU version).
|
||||
*
|
||||
* @category Loaders
|
||||
* @group Loaders
|
||||
*
|
||||
* @example
|
||||
* import { HDRJPGLoader } from '@monogrid/gainmap-js/webgpu'
|
||||
* import {
|
||||
* EquirectangularReflectionMapping,
|
||||
* Mesh,
|
||||
* MeshBasicMaterial,
|
||||
* PerspectiveCamera,
|
||||
* PlaneGeometry,
|
||||
* Scene,
|
||||
* WebGPURenderer
|
||||
* } from 'three/webgpu'
|
||||
*
|
||||
* const renderer = new WebGPURenderer()
|
||||
* await renderer.init()
|
||||
*
|
||||
* const loader = new HDRJPGLoader(renderer)
|
||||
* .setRenderTargetOptions({ mapping: EquirectangularReflectionMapping })
|
||||
*
|
||||
* const result = await loader.loadAsync('gainmap.jpeg')
|
||||
* // `result` can be used to populate a Texture
|
||||
*
|
||||
* const scene = new Scene()
|
||||
* const mesh = new Mesh(
|
||||
* new PlaneGeometry(),
|
||||
* new MeshBasicMaterial({ map: result.renderTarget.texture })
|
||||
* )
|
||||
* scene.add(mesh)
|
||||
* renderer.render(scene, new PerspectiveCamera())
|
||||
*
|
||||
* // Starting from three.js r159
|
||||
* // `result.renderTarget.texture` can
|
||||
* // also be used as Equirectangular scene background
|
||||
* //
|
||||
* // it was previously needed to convert it
|
||||
* // to a DataTexture with `result.toDataTexture()`
|
||||
* scene.background = result.renderTarget.texture
|
||||
*
|
||||
* // result must be manually disposed
|
||||
* // when you are done using it
|
||||
* result.dispose()
|
||||
*
|
||||
*/
|
||||
class HDRJPGLoader extends LoaderBaseWebGPU {
|
||||
/**
|
||||
* Loads a JPEG containing gain map metadata
|
||||
* Renders a normal SDR image if gainmap data is not found
|
||||
*
|
||||
* @param url Path to a JPEG file containing embedded gain map metadata
|
||||
* @param onLoad Load complete callback, will receive the result
|
||||
* @param onProgress Progress callback, will receive a `ProgressEvent`
|
||||
* @param onError Error callback
|
||||
* @returns
|
||||
*/
|
||||
load(url, onLoad, onProgress, onError) {
|
||||
const quadRenderer = this.prepareQuadRenderer();
|
||||
const loader = new FileLoader(this._internalLoadingManager);
|
||||
loader.setResponseType('arraybuffer');
|
||||
loader.setRequestHeader(this.requestHeader);
|
||||
loader.setPath(this.path);
|
||||
loader.setWithCredentials(this.withCredentials);
|
||||
this.manager.itemStart(url);
|
||||
loader.load(url, async (jpeg) => {
|
||||
/* istanbul ignore if
|
||||
this condition exists only because of three.js types + strict mode
|
||||
*/
|
||||
if (typeof jpeg === 'string')
|
||||
throw new Error('Invalid buffer, received [string], was expecting [ArrayBuffer]');
|
||||
const jpegBuffer = new Uint8Array(jpeg);
|
||||
let sdrJPEG;
|
||||
let gainMapJPEG;
|
||||
let metadata;
|
||||
try {
|
||||
const extractionResult = await extractGainmapFromJPEG(jpegBuffer);
|
||||
// gain map is successfully reconstructed
|
||||
sdrJPEG = extractionResult.sdr;
|
||||
gainMapJPEG = extractionResult.gainMap;
|
||||
metadata = extractionResult.metadata;
|
||||
}
|
||||
catch (e) {
|
||||
// render the SDR version if this is not a gainmap
|
||||
if (e instanceof XMPMetadataNotFoundError || e instanceof GainMapNotFoundError) {
|
||||
console.warn(`Failure to reconstruct an HDR image from ${url}: Gain map metadata not found in the file, HDRJPGLoader will render the SDR jpeg`);
|
||||
metadata = {
|
||||
gainMapMin: [0, 0, 0],
|
||||
gainMapMax: [1, 1, 1],
|
||||
gamma: [1, 1, 1],
|
||||
hdrCapacityMin: 0,
|
||||
hdrCapacityMax: 1,
|
||||
offsetHdr: [0, 0, 0],
|
||||
offsetSdr: [0, 0, 0]
|
||||
};
|
||||
sdrJPEG = jpegBuffer;
|
||||
}
|
||||
else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
// solves #16
|
||||
try {
|
||||
await this.render(quadRenderer, metadata, sdrJPEG.buffer, gainMapJPEG?.buffer);
|
||||
}
|
||||
catch (error) {
|
||||
this.manager.itemError(url);
|
||||
if (typeof onError === 'function')
|
||||
onError(error);
|
||||
quadRenderer.disposeOnDemandRenderer();
|
||||
return;
|
||||
}
|
||||
if (typeof onLoad === 'function')
|
||||
onLoad(quadRenderer);
|
||||
this.manager.itemEnd(url);
|
||||
quadRenderer.disposeOnDemandRenderer();
|
||||
}, onProgress, (error) => {
|
||||
this.manager.itemError(url);
|
||||
if (typeof onError === 'function')
|
||||
onError(error);
|
||||
});
|
||||
return quadRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
export { GainMapDecoderMaterial, GainMapLoader, GainMapNotFoundError, HDRJPGLoader, HDRJPGLoader as JPEGRLoader, LoaderBaseShared, QuadRenderer, XMPMetadataNotFoundError, createDecodeFunction, decode, extractGainmapFromJPEG };
|
||||
Reference in New Issue
Block a user