6 lines
19 KiB
JavaScript
6 lines
19 KiB
JavaScript
/**
|
|
* @monogrid/gainmap-js v3.4.0
|
|
* With ❤️, by MONOGRID <gainmap@monogrid.com>
|
|
*/
|
|
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("three")):"function"==typeof define&&define.amd?define(["exports","three"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self)["@monogrid/gainmap-js"]={},e.three)}(this,function(e,r){"use strict";const t=(e,t,a)=>{let i;switch(e){case r.UnsignedByteType:i=new Uint8ClampedArray(t*a*4);break;case r.HalfFloatType:i=new Uint16Array(t*a*4);break;case r.UnsignedIntType:i=new Uint32Array(t*a*4);break;case r.ByteType:i=new Int8Array(t*a*4);break;case r.ShortType:i=new Int16Array(t*a*4);break;case r.IntType:i=new Int32Array(t*a*4);break;case r.FloatType:i=new Float32Array(t*a*4);break;default:throw new Error("Unsupported data type")}return i};let a;class i{_renderer;_rendererIsDisposable=!1;_material;_scene;_camera;_quad;_renderTarget;_width;_height;_type;_colorSpace;_supportsReadPixels=!0;constructor(e){this._width=e.width,this._height=e.height,this._type=e.type,this._colorSpace=e.colorSpace;const n={format:r.RGBAFormat,depthBuffer:!1,stencilBuffer:!1,type:this._type,colorSpace:this._colorSpace,anisotropy:void 0!==e.renderTargetOptions?.anisotropy?e.renderTargetOptions?.anisotropy:1,generateMipmaps:void 0!==e.renderTargetOptions?.generateMipmaps&&e.renderTargetOptions?.generateMipmaps,magFilter:void 0!==e.renderTargetOptions?.magFilter?e.renderTargetOptions?.magFilter:r.LinearFilter,minFilter:void 0!==e.renderTargetOptions?.minFilter?e.renderTargetOptions?.minFilter:r.LinearFilter,samples:void 0!==e.renderTargetOptions?.samples?e.renderTargetOptions?.samples:void 0,wrapS:void 0!==e.renderTargetOptions?.wrapS?e.renderTargetOptions?.wrapS:r.ClampToEdgeWrapping,wrapT:void 0!==e.renderTargetOptions?.wrapT?e.renderTargetOptions?.wrapT:r.ClampToEdgeWrapping};if(this._material=e.material,e.renderer?this._renderer=e.renderer:(this._renderer=i.instantiateRenderer(),this._rendererIsDisposable=!0),this._scene=new r.Scene,this._camera=new r.OrthographicCamera,this._camera.position.set(0,0,10),this._camera.left=-.5,this._camera.right=.5,this._camera.top=.5,this._camera.bottom=-.5,this._camera.updateProjectionMatrix(),!((e,i,n,s)=>{if(void 0!==a)return a;const o=new r.WebGLRenderTarget(1,1,s);i.setRenderTarget(o);const d=new r.Mesh(new r.PlaneGeometry,new r.MeshBasicMaterial({color:16777215}));i.render(d,n),i.setRenderTarget(null);const p=t(e,o.width,o.height);return i.readRenderTargetPixels(o,0,0,o.width,o.height,p),o.dispose(),d.geometry.dispose(),d.material.dispose(),a=0!==p[0],a})(this._type,this._renderer,this._camera,n)){let e;if(this._type===r.HalfFloatType)e=this._renderer.extensions.has("EXT_color_buffer_float")?r.FloatType:void 0;void 0!==e?(console.warn(`This browser does not support reading pixels from ${this._type} RenderTargets, switching to ${r.FloatType}`),this._type=e):(this._supportsReadPixels=!1,console.warn("This browser dos not support toArray or toDataTexture, calls to those methods will result in an error thrown"))}this._quad=new r.Mesh(new r.PlaneGeometry,this._material),this._quad.geometry.computeBoundingBox(),this._scene.add(this._quad),this._renderTarget=new r.WebGLRenderTarget(this.width,this.height,n),this._renderTarget.texture.mapping=void 0!==e.renderTargetOptions?.mapping?e.renderTargetOptions?.mapping:r.UVMapping}static instantiateRenderer(){const e=new r.WebGLRenderer;return e.setSize(128,128),e}render=()=>{this._renderer.setRenderTarget(this._renderTarget);try{this._renderer.render(this._scene,this._camera)}catch(e){throw this._renderer.setRenderTarget(null),e}this._renderer.setRenderTarget(null)};toArray(){if(!this._supportsReadPixels)throw new Error("Can't read pixels in this browser");const e=t(this._type,this._width,this._height);return this._renderer.readRenderTargetPixels(this._renderTarget,0,0,this._width,this._height,e),e}toDataTexture(e){const t=new r.DataTexture(this.toArray(),this.width,this.height,r.RGBAFormat,this._type,e?.mapping||r.UVMapping,e?.wrapS||r.ClampToEdgeWrapping,e?.wrapT||r.ClampToEdgeWrapping,e?.magFilter||r.LinearFilter,e?.minFilter||r.LinearFilter,e?.anisotropy||1,r.LinearSRGBColorSpace);return t.generateMipmaps=void 0!==e?.generateMipmaps&&e?.generateMipmaps,t}disposeOnDemandRenderer(){this._renderer.setRenderTarget(null),this._rendererIsDisposable&&(this._renderer.dispose(),this._renderer.forceContextLoss())}dispose(e){this.disposeOnDemandRenderer(),e&&this.renderTarget.dispose(),this.material instanceof r.ShaderMaterial&&Object.values(this.material.uniforms).forEach(e=>{e.value instanceof r.Texture&&e.value.dispose()}),Object.values(this.material).forEach(e=>{e instanceof r.Texture&&e.dispose()}),this.material.dispose(),this._quad.geometry.dispose()}get width(){return this._width}set width(e){this._width=e,this._renderTarget.setSize(this._width,this._height)}get height(){return this._height}set height(e){this._height=e,this._renderTarget.setSize(this._width,this._height)}get renderer(){return this._renderer}get renderTarget(){return this._renderTarget}set renderTarget(e){this._renderTarget=e,this._width=e.width,this._height=e.height}get material(){return this._material}get type(){return this._type}get colorSpace(){return this._colorSpace}}function n(e){return t=>{const{sdr:a,gainMap:i,renderer:n}=t;a.colorSpace!==r.SRGBColorSpace&&(console.warn("SDR Colorspace needs to be *SRGBColorSpace*, setting it automatically"),a.colorSpace=r.SRGBColorSpace),a.needsUpdate=!0,i.colorSpace!==r.LinearSRGBColorSpace&&(console.warn("Gainmap Colorspace needs to be *LinearSRGBColorSpace*, setting it automatically"),i.colorSpace=r.LinearSRGBColorSpace),i.needsUpdate=!0;const s=e.createMaterial({...t,sdr:a,gainMap:i});return e.createQuadRenderer({width:a.image.width,height:a.image.height,type:r.HalfFloatType,colorSpace:r.LinearSRGBColorSpace,material:s,renderer:n,renderTargetOptions:t.renderTargetOptions})}}class s extends Error{}class o extends Error{}const d=(e,r,t)=>{const a=new RegExp(`${r}="([^"]*)"`,"i").exec(e);if(a)return a[1];const i=new RegExp(`<${r}[^>]*>([\\s\\S]*?)</${r}>`,"i").exec(e);if(i){const e=i[1].match(/<rdf:li>([^<]*)<\/rdf:li>/g);return e&&3===e.length?e.map(e=>e.replace(/<\/?rdf:li>/g,"")):i[1].trim()}if(void 0!==t)return t;throw new Error(`Can't find ${r} in gainmap metadata`)},p=e=>{let r;r="undefined"!=typeof TextDecoder?(new TextDecoder).decode(e):e.toString();let t=r.indexOf("<x:xmpmeta");for(;-1!==t;){const e=r.indexOf("x:xmpmeta>",t),a=r.slice(t,e+10);try{const e=d(a,"hdrgm:GainMapMin","0"),r=d(a,"hdrgm:GainMapMax"),t=d(a,"hdrgm:Gamma","1"),i=d(a,"hdrgm:OffsetSDR","0.015625"),n=d(a,"hdrgm:OffsetHDR","0.015625"),s=/hdrgm:HDRCapacityMin="([^"]*)"/.exec(a),o=s?s[1]:"0",p=/hdrgm:HDRCapacityMax="([^"]*)"/.exec(a);if(!p)throw new Error("Incomplete gainmap metadata");const h=p[1];return{gainMapMin:Array.isArray(e)?e.map(e=>parseFloat(e)):[parseFloat(e),parseFloat(e),parseFloat(e)],gainMapMax:Array.isArray(r)?r.map(e=>parseFloat(e)):[parseFloat(r),parseFloat(r),parseFloat(r)],gamma:Array.isArray(t)?t.map(e=>parseFloat(e)):[parseFloat(t),parseFloat(t),parseFloat(t)],offsetSdr:Array.isArray(i)?i.map(e=>parseFloat(e)):[parseFloat(i),parseFloat(i),parseFloat(i)],offsetHdr:Array.isArray(n)?n.map(e=>parseFloat(e)):[parseFloat(n),parseFloat(n),parseFloat(n)],hdrCapacityMin:parseFloat(o),hdrCapacityMax:parseFloat(h)}}catch(e){}t=r.indexOf("<x:xmpmeta",e)}};class h{options;constructor(e){this.options={debug:!(!e||void 0===e.debug)&&e.debug,extractFII:!e||void 0===e.extractFII||e.extractFII,extractNonFII:!e||void 0===e.extractNonFII||e.extractNonFII}}extract(e){return new Promise((r,t)=>{const a=this.options.debug,i=new DataView(e.buffer);if(65496!==i.getUint16(0))return void t(new Error("Not a valid jpeg"));const n=i.byteLength;let s,o=2,d=0;for(;o<n;){if(++d>250)return void t(new Error(`Found no marker after ${d} loops 😵`));if(255!==i.getUint8(o))return void t(new Error(`Not a valid marker at offset 0x${o.toString(16)}, found: 0x${i.getUint8(o).toString(16)}`));if(s=i.getUint8(o+1),a&&console.log(`Marker: ${s.toString(16)}`),226===s){a&&console.log("Found APP2 marker (0xffe2)");const e=o+4;if(1297106432===i.getUint32(e)){const a=e+4;let n;if(18761===i.getUint16(a))n=!1;else{if(19789!==i.getUint16(a))return void t(new Error("No valid endianness marker found in TIFF header"));n=!0}if(42!==i.getUint16(a+2,!n))return void t(new Error("Not valid TIFF data! (no 0x002A marker)"));const s=i.getUint32(a+4,!n);if(s<8)return void t(new Error("Not valid TIFF data! (First offset less than 8)"));const o=a+s,d=i.getUint16(o,!n),p=o+2;let h=0;for(let e=p;e<p+12*d;e+=12)45057===i.getUint16(e,!n)&&(h=i.getUint32(e+8,!n));const l=o+2+12*d+4,c=[];for(let e=l;e<l+16*h;e+=16){const r={MPType:i.getUint32(e,!n),size:i.getUint32(e+4,!n),dataOffset:i.getUint32(e+8,!n),dependantImages:i.getUint32(e+12,!n),start:-1,end:-1,isFII:!1};r.dataOffset?(r.start=a+r.dataOffset,r.isFII=!1):(r.start=0,r.isFII=!0),r.end=r.start+r.size,c.push(r)}if(this.options.extractNonFII&&c.length){const e=new Blob([i]),t=[];for(const r of c){if(r.isFII&&!this.options.extractFII)continue;const a=e.slice(r.start,r.end+1,"image/jpeg");t.push(a)}r(t)}}}o+=2+i.getUint16(o+2)}})}}const l=async e=>{const r=p(e);if(!r)throw new o("Gain map XMP metadata not found");const t=new h({extractFII:!0,extractNonFII:!0}),a=await t.extract(e);if(2!==a.length)throw new s("Gain map recovery image not found");return{sdr:new Uint8Array(await a[0].arrayBuffer()),gainMap:new Uint8Array(await a[1].arrayBuffer()),metadata:r}},c=e=>new Promise((r,t)=>{const a=document.createElement("img");a.onload=()=>{r(a)},a.onerror=e=>{t(e)},a.src=URL.createObjectURL(e)});class g extends r.Loader{_renderer;_renderTargetOptions;_internalLoadingManager;_config;constructor(e,t){super(t),this._config=e,e.renderer&&(this._renderer=e.renderer),this._internalLoadingManager=new r.LoadingManager}setRenderer(e){return this._renderer=e,this}setRenderTargetOptions(e){return this._renderTargetOptions=e,this}prepareQuadRenderer(){this._renderer||console.warn("WARNING: A Renderer was not passed to this Loader constructor or in setRenderer, the result of this Loader will need to be converted to a Data Texture with toDataTexture() before you can use it in your renderer.");const e=this._config.createMaterial({gainMapMax:[1,1,1],gainMapMin:[0,0,0],gamma:[1,1,1],offsetHdr:[1,1,1],offsetSdr:[1,1,1],hdrCapacityMax:1,hdrCapacityMin:0,maxDisplayBoost:1,gainMap:new r.Texture,sdr:new r.Texture});return this._config.createQuadRenderer({width:16,height:16,type:r.HalfFloatType,colorSpace:r.LinearSRGBColorSpace,material:e,renderer:this._renderer,renderTargetOptions:this._renderTargetOptions})}async processImages(e,r,t){const a=r?new Blob([r],{type:"image/jpeg"}):void 0,i=new Blob([e],{type:"image/jpeg"});let n,s,o=!1;if("undefined"==typeof createImageBitmap){const e=await Promise.all([a?c(a):Promise.resolve(void 0),c(i)]);s=e[0],n=e[1],o="flipY"===t}else{const e=await Promise.all([a?createImageBitmap(a,{imageOrientation:t||"flipY"}):Promise.resolve(void 0),createImageBitmap(i,{imageOrientation:t||"flipY"})]);s=e[0],n=e[1]}return{sdrImage:n,gainMapImage:s,needsFlip:o}}createTextures(e,t,a){const i=new r.Texture(t||new ImageData(2,2),r.UVMapping,r.ClampToEdgeWrapping,r.ClampToEdgeWrapping,r.LinearFilter,r.LinearMipMapLinearFilter,r.RGBAFormat,r.UnsignedByteType,1,r.LinearSRGBColorSpace);i.flipY=a,i.needsUpdate=!0;const n=new r.Texture(e,r.UVMapping,r.ClampToEdgeWrapping,r.ClampToEdgeWrapping,r.LinearFilter,r.LinearMipMapLinearFilter,r.RGBAFormat,r.UnsignedByteType,1,r.SRGBColorSpace);return n.flipY=a,n.needsUpdate=!0,{gainMap:i,sdr:n}}updateQuadRenderer(e,r,t,a,i){e.width=r.width,e.height=r.height,e.material.gainMap=t,e.material.sdr=a,e.material.gainMapMin=i.gainMapMin,e.material.gainMapMax=i.gainMapMax,e.material.offsetHdr=i.offsetHdr,e.material.offsetSdr=i.offsetSdr,e.material.gamma=i.gamma,e.material.hdrCapacityMin=i.hdrCapacityMin,e.material.hdrCapacityMax=i.hdrCapacityMax,e.material.maxDisplayBoost=Math.pow(2,i.hdrCapacityMax),e.material.needsUpdate=!0}}class m extends r.ShaderMaterial{_maxDisplayBoost;_hdrCapacityMin;_hdrCapacityMax;constructor({gamma:e,offsetHdr:t,offsetSdr:a,gainMapMin:i,gainMapMax:n,maxDisplayBoost:s,hdrCapacityMin:o,hdrCapacityMax:d,sdr:p,gainMap:h}){super({name:"GainMapDecoderMaterial",vertexShader:"\nvarying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n",fragmentShader:"\n// min half float value\n#define HALF_FLOAT_MIN vec3( -65504, -65504, -65504 )\n// max half float value\n#define HALF_FLOAT_MAX vec3( 65504, 65504, 65504 )\n\nuniform sampler2D sdr;\nuniform sampler2D gainMap;\nuniform vec3 gamma;\nuniform vec3 offsetHdr;\nuniform vec3 offsetSdr;\nuniform vec3 gainMapMin;\nuniform vec3 gainMapMax;\nuniform float weightFactor;\n\nvarying vec2 vUv;\n\nvoid main() {\n vec3 rgb = texture2D( sdr, vUv ).rgb;\n vec3 recovery = texture2D( gainMap, vUv ).rgb;\n vec3 logRecovery = pow( recovery, gamma );\n vec3 logBoost = gainMapMin * ( 1.0 - logRecovery ) + gainMapMax * logRecovery;\n vec3 hdrColor = (rgb + offsetSdr) * exp2( logBoost * weightFactor ) - offsetHdr;\n vec3 clampedHdrColor = max( HALF_FLOAT_MIN, min( HALF_FLOAT_MAX, hdrColor ));\n gl_FragColor = vec4( clampedHdrColor , 1.0 );\n}\n",uniforms:{sdr:{value:p},gainMap:{value:h},gamma:{value:new r.Vector3(1/e[0],1/e[1],1/e[2])},offsetHdr:{value:(new r.Vector3).fromArray(t)},offsetSdr:{value:(new r.Vector3).fromArray(a)},gainMapMin:{value:(new r.Vector3).fromArray(i)},gainMapMax:{value:(new r.Vector3).fromArray(n)},weightFactor:{value:(Math.log2(s)-o)/(d-o)}},blending:r.NoBlending,depthTest:!1,depthWrite:!1}),this._maxDisplayBoost=s,this._hdrCapacityMin=o,this._hdrCapacityMax=d,this.needsUpdate=!0,this.uniformsNeedUpdate=!0}get sdr(){return this.uniforms.sdr.value}set sdr(e){this.uniforms.sdr.value=e}get gainMap(){return this.uniforms.gainMap.value}set gainMap(e){this.uniforms.gainMap.value=e}get offsetHdr(){return this.uniforms.offsetHdr.value.toArray()}set offsetHdr(e){this.uniforms.offsetHdr.value.fromArray(e)}get offsetSdr(){return this.uniforms.offsetSdr.value.toArray()}set offsetSdr(e){this.uniforms.offsetSdr.value.fromArray(e)}get gainMapMin(){return this.uniforms.gainMapMin.value.toArray()}set gainMapMin(e){this.uniforms.gainMapMin.value.fromArray(e)}get gainMapMax(){return this.uniforms.gainMapMax.value.toArray()}set gainMapMax(e){this.uniforms.gainMapMax.value.fromArray(e)}get gamma(){const e=this.uniforms.gamma.value;return[1/e.x,1/e.y,1/e.z]}set gamma(e){const r=this.uniforms.gamma.value;r.x=1/e[0],r.y=1/e[1],r.z=1/e[2]}get hdrCapacityMin(){return this._hdrCapacityMin}set hdrCapacityMin(e){this._hdrCapacityMin=e,this.calculateWeight()}get hdrCapacityMax(){return this._hdrCapacityMax}set hdrCapacityMax(e){this._hdrCapacityMax=e,this.calculateWeight()}get maxDisplayBoost(){return this._maxDisplayBoost}set maxDisplayBoost(e){this._maxDisplayBoost=Math.max(1,Math.min(65504,e)),this.calculateWeight()}calculateWeight(){const e=(Math.log2(this._maxDisplayBoost)-this._hdrCapacityMin)/(this._hdrCapacityMax-this._hdrCapacityMin);this.uniforms.weightFactor.value=Math.max(0,Math.min(1,e))}}const f=n({renderer:r.WebGLRenderer,createMaterial:e=>new m(e),createQuadRenderer:e=>new i(e)});class u extends g{constructor(e,r){super({renderer:e,createMaterial:e=>new m(e),createQuadRenderer:e=>new i(e)},r)}async render(e,r,t,a){const{sdrImage:i,gainMapImage:n,needsFlip:s}=await this.processImages(t,a,"flipY"),{gainMap:o,sdr:d}=this.createTextures(i,n,s);this.updateQuadRenderer(e,i,o,d,r),e.render()}}class y extends u{load(e,t,a,i){const n=this.prepareQuadRenderer(),d=new r.FileLoader(this._internalLoadingManager);return d.setResponseType("arraybuffer"),d.setRequestHeader(this.requestHeader),d.setPath(this.path),d.setWithCredentials(this.withCredentials),this.manager.itemStart(e),d.load(e,async r=>{if("string"==typeof r)throw new Error("Invalid buffer, received [string], was expecting [ArrayBuffer]");const a=new Uint8Array(r);let d,p,h;try{const e=await l(a);d=e.sdr,p=e.gainMap,h=e.metadata}catch(r){if(!(r instanceof o||r instanceof s))throw r;console.warn(`Failure to reconstruct an HDR image from ${e}: Gain map metadata not found in the file, HDRJPGLoader will render the SDR jpeg`),h={gainMapMin:[0,0,0],gainMapMax:[1,1,1],gamma:[1,1,1],hdrCapacityMin:0,hdrCapacityMax:1,offsetHdr:[0,0,0],offsetSdr:[0,0,0]},d=a}try{await this.render(n,h,d.buffer,p?.buffer)}catch(r){return this.manager.itemError(e),"function"==typeof i&&i(r),void n.disposeOnDemandRenderer()}"function"==typeof t&&t(n),this.manager.itemEnd(e),n.disposeOnDemandRenderer()},a,r=>{this.manager.itemError(e),"function"==typeof i&&i(r)}),n}}e.GainMapDecoderMaterial=m,e.GainMapLoader=class extends u{load([e,t,a],i,n,s){const o=this.prepareQuadRenderer();let d,p,h;const l=async()=>{if(d&&p&&h){try{await this.render(o,h,d,p)}catch(r){return this.manager.itemError(e),this.manager.itemError(t),this.manager.itemError(a),"function"==typeof s&&s(r),void o.disposeOnDemandRenderer()}"function"==typeof i&&i(o),this.manager.itemEnd(e),this.manager.itemEnd(t),this.manager.itemEnd(a),o.disposeOnDemandRenderer()}};let c=!0,g=0,m=0,f=!0,u=0,y=0,M=!0,w=0,_=0;const x=()=>{if("function"==typeof n){n(new ProgressEvent("progress",{lengthComputable:c&&f&&M,loaded:m+y+_,total:g+u+w}))}};this.manager.itemStart(e),this.manager.itemStart(t),this.manager.itemStart(a);const v=new r.FileLoader(this._internalLoadingManager);v.setResponseType("arraybuffer"),v.setRequestHeader(this.requestHeader),v.setPath(this.path),v.setWithCredentials(this.withCredentials),v.load(e,async e=>{if("string"==typeof e)throw new Error("Invalid sdr buffer");d=e,await l()},e=>{c=e.lengthComputable,m=e.loaded,g=e.total,x()},r=>{this.manager.itemError(e),"function"==typeof s&&s(r)});const T=new r.FileLoader(this._internalLoadingManager);T.setResponseType("arraybuffer"),T.setRequestHeader(this.requestHeader),T.setPath(this.path),T.setWithCredentials(this.withCredentials),T.load(t,async e=>{if("string"==typeof e)throw new Error("Invalid gainmap buffer");p=e,await l()},e=>{f=e.lengthComputable,y=e.loaded,u=e.total,x()},e=>{this.manager.itemError(t),"function"==typeof s&&s(e)});const F=new r.FileLoader(this._internalLoadingManager);return F.setRequestHeader(this.requestHeader),F.setPath(this.path),F.setWithCredentials(this.withCredentials),F.load(a,async e=>{if("string"!=typeof e)throw new Error("Invalid metadata string");h=JSON.parse(e),await l()},e=>{M=e.lengthComputable,_=e.loaded,w=e.total,x()},e=>{this.manager.itemError(a),"function"==typeof s&&s(e)}),o}},e.GainMapNotFoundError=s,e.HDRJPGLoader=y,e.JPEGRLoader=y,e.LoaderBaseShared=g,e.MPFExtractor=h,e.QuadRenderer=i,e.XMPMetadataNotFoundError=o,e.createDecodeFunction=n,e.decode=e=>{if(!e.renderer)throw new Error("Renderer is required for decode function");const r=f({...e,renderer:e.renderer});try{r.render()}catch(e){throw r.disposeOnDemandRenderer(),e}return r},e.extractGainmapFromJPEG=l,e.extractXMP=p});
|