@monogrid/gainmap-js
Version:
A Javascript (TypeScript) Port of Adobe Gainmap Technology for storing HDR Images using an SDR Image + a gain map
302 lines (298 loc) • 12.6 kB
JavaScript
/**
* @monogrid/gainmap-js v3.1.0
* With ❤️, by MONOGRID <rnd@monogrid.com>
*/
import { RGBAFormat, LinearFilter, ClampToEdgeWrapping, Scene, OrthographicCamera, HalfFloatType, FloatType, Mesh, PlaneGeometry, WebGLRenderTarget, UVMapping, WebGLRenderer, DataTexture, LinearSRGBColorSpace, ShaderMaterial, Texture, IntType, ShortType, ByteType, UnsignedIntType, UnsignedByteType, MeshBasicMaterial } from 'three';
const getBufferForType = (type, width, height) => {
let out;
switch (type) {
case UnsignedByteType:
out = new Uint8ClampedArray(width * height * 4);
break;
case HalfFloatType:
out = new Uint16Array(width * height * 4);
break;
case UnsignedIntType:
out = new Uint32Array(width * height * 4);
break;
case ByteType:
out = new Int8Array(width * height * 4);
break;
case ShortType:
out = new Int16Array(width * height * 4);
break;
case IntType:
out = new Int32Array(width * height * 4);
break;
case FloatType:
out = new Float32Array(width * height * 4);
break;
default:
throw new Error('Unsupported data type');
}
return out;
};
let _canReadPixelsResult;
/**
* Test if this browser implementation can correctly read pixels from the specified
* Render target type.
*
* Runs only once
*
* @param type
* @param renderer
* @param camera
* @param renderTargetOptions
* @returns
*/
const canReadPixels = (type, renderer, camera, renderTargetOptions) => {
if (_canReadPixelsResult !== undefined)
return _canReadPixelsResult;
const testRT = new WebGLRenderTarget(1, 1, renderTargetOptions);
renderer.setRenderTarget(testRT);
const mesh = new Mesh(new PlaneGeometry(), new MeshBasicMaterial({ color: 0xffffff }));
renderer.render(mesh, camera);
renderer.setRenderTarget(null);
const out = getBufferForType(type, testRT.width, testRT.height);
renderer.readRenderTargetPixels(testRT, 0, 0, testRT.width, testRT.height, out);
testRT.dispose();
mesh.geometry.dispose();
mesh.material.dispose();
_canReadPixelsResult = out[0] !== 0;
return _canReadPixelsResult;
};
/**
* Utility class used for rendering a texture with a material
*
* @category Core
* @group Core
*/
class QuadRenderer {
/**
* Constructs a new QuadRenderer
*
* @param options Parameters for this QuadRenderer
*/
constructor(options) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
this._rendererIsDisposable = false;
this._supportsReadPixels = true;
/**
* Renders the input texture using the specified material
*/
this.render = () => {
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);
};
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: ((_a = options.renderTargetOptions) === null || _a === void 0 ? void 0 : _a.anisotropy) !== undefined ? (_b = options.renderTargetOptions) === null || _b === void 0 ? void 0 : _b.anisotropy : 1,
generateMipmaps: ((_c = options.renderTargetOptions) === null || _c === void 0 ? void 0 : _c.generateMipmaps) !== undefined ? (_d = options.renderTargetOptions) === null || _d === void 0 ? void 0 : _d.generateMipmaps : false,
magFilter: ((_e = options.renderTargetOptions) === null || _e === void 0 ? void 0 : _e.magFilter) !== undefined ? (_f = options.renderTargetOptions) === null || _f === void 0 ? void 0 : _f.magFilter : LinearFilter,
minFilter: ((_g = options.renderTargetOptions) === null || _g === void 0 ? void 0 : _g.minFilter) !== undefined ? (_h = options.renderTargetOptions) === null || _h === void 0 ? void 0 : _h.minFilter : LinearFilter,
samples: ((_j = options.renderTargetOptions) === null || _j === void 0 ? void 0 : _j.samples) !== undefined ? (_k = options.renderTargetOptions) === null || _k === void 0 ? void 0 : _k.samples : undefined,
wrapS: ((_l = options.renderTargetOptions) === null || _l === void 0 ? void 0 : _l.wrapS) !== undefined ? (_m = options.renderTargetOptions) === null || _m === void 0 ? void 0 : _m.wrapS : ClampToEdgeWrapping,
wrapT: ((_o = options.renderTargetOptions) === null || _o === void 0 ? void 0 : _o.wrapT) !== undefined ? (_p = options.renderTargetOptions) === null || _p === void 0 ? void 0 : _p.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();
if (!canReadPixels(this._type, this._renderer, this._camera, rtOptions)) {
let alternativeType;
switch (this._type) {
case HalfFloatType:
alternativeType = this._renderer.extensions.has('EXT_color_buffer_float') ? FloatType : undefined;
break;
}
if (alternativeType !== undefined) {
console.warn(`This browser does not support reading pixels from ${this._type} RenderTargets, switching to ${FloatType}`);
this._type = alternativeType;
}
else {
this._supportsReadPixels = false;
console.warn('This browser dos not support toArray or toDataTexture, calls to those methods will result in an error thrown');
}
}
this._quad = new Mesh(new PlaneGeometry(), this._material);
this._quad.geometry.computeBoundingBox();
this._scene.add(this._quad);
this._renderTarget = new WebGLRenderTarget(this.width, this.height, rtOptions);
this._renderTarget.texture.mapping = ((_q = options.renderTargetOptions) === null || _q === void 0 ? void 0 : _q.mapping) !== undefined ? (_r = options.renderTargetOptions) === null || _r === void 0 ? void 0 : _r.mapping : UVMapping;
}
/**
* Instantiates a temporary renderer
*
* @returns
*/
static instantiateRenderer() {
const renderer = new WebGLRenderer();
renderer.setSize(128, 128);
// renderer.outputColorSpace = SRGBColorSpace
// renderer.toneMapping = LinearToneMapping
// renderer.debug.checkShaderErrors = false
// this._rendererIsDisposable = true
return renderer;
}
/**
* 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
*/
toArray() {
if (!this._supportsReadPixels)
throw new Error('Can\'t read pixels in this browser');
const out = getBufferForType(this._type, this._width, this._height);
this._renderer.readRenderTargetPixels(this._renderTarget, 0, 0, this._width, this._height, out);
return out;
}
/**
* Performs a readPixel operation in the renderTarget
* and returns a DataTexture containing the read data
*
* @param options options
* @returns
*/
toDataTexture(options) {
const returnValue = new DataTexture(
// fixed values
this.toArray(), this.width, this.height, RGBAFormat, this._type,
// user values
(options === null || options === void 0 ? void 0 : options.mapping) || UVMapping, (options === null || options === void 0 ? void 0 : options.wrapS) || ClampToEdgeWrapping, (options === null || options === void 0 ? void 0 : options.wrapT) || ClampToEdgeWrapping, (options === null || options === void 0 ? void 0 : options.magFilter) || LinearFilter, (options === null || options === void 0 ? void 0 : options.minFilter) || LinearFilter, (options === null || options === void 0 ? void 0 : options.anisotropy) || 1,
// fixed value
LinearSRGBColorSpace);
// set this afterwards, we can't set it in constructor
returnValue.generateMipmaps = (options === null || options === void 0 ? void 0 : options.generateMipmaps) !== undefined ? options === null || options === void 0 ? void 0 : 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();
this._renderer.forceContextLoss();
}
}
/**
* 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) {
this.disposeOnDemandRenderer();
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();
}
/**
* 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 `WebGLRenderTarget` used.
*/
get renderTarget() { return this._renderTarget; }
set renderTarget(value) {
this._renderTarget = value;
this._width = value.width;
this._height = value.height;
// this._type = value.texture.type
}
/**
* The `Material` used.
*/
get material() { return this._material; }
/**
*
*/
get type() { return this._type; }
get colorSpace() { return this._colorSpace; }
}
export { QuadRenderer as Q };