import { DataTextureLoader, DataUtils, FloatType, HalfFloatType, LinearEncoding, LinearFilter, NearestFilter, RGBAFormat, RGBEEncoding, RGBEFormat, RGBFormat, UnsignedByteType } from '../../../build/three.module.js'; import * as fflate from '../libs/fflate.module.js'; /** * OpenEXR loader currently supports uncompressed, ZIP(S), RLE, PIZ and DWA/B compression. * Supports reading as UnsignedByte, HalfFloat and Float type data texture. * * Referred to the original Industrial Light & Magic OpenEXR implementation and the TinyEXR / Syoyo Fujita * implementation, so I have preserved their copyright notices. */ // /* // Copyright (c) 2014 - 2017, Syoyo Fujita // All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of the Syoyo Fujita nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // */ // // TinyEXR contains some OpenEXR code, which is licensed under ------------ // /////////////////////////////////////////////////////////////////////////// // // // // Copyright (c) 2002, Industrial Light & Magic, a division of Lucas // // Digital Ltd. LLC // // // // All rights reserved. // // // // Redistribution and use in source and binary forms, with or without // // modification, are permitted provided that the following conditions are // // met: // // * Redistributions of source code must retain the above copyright // // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above // // copyright notice, this list of conditions and the following disclaimer // // in the documentation and/or other materials provided with the // // distribution. // // * Neither the name of Industrial Light & Magic nor the names of // // its contributors may be used to endorse or promote products derived // // from this software without specific prior written permission. // // // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // // /////////////////////////////////////////////////////////////////////////// // // End of OpenEXR license ------------------------------------------------- class EXRLoader extends DataTextureLoader { constructor( manager ) { super( manager ); this.type = FloatType; } parse( buffer ) { const USHORT_RANGE = ( 1 << 16 ); const BITMAP_SIZE = ( USHORT_RANGE >> 3 ); const HUF_ENCBITS = 16; // literal (value) bit length const HUF_DECBITS = 14; // decoding bit size (>= 8) const HUF_ENCSIZE = ( 1 << HUF_ENCBITS ) + 1; // encoding table size const HUF_DECSIZE = 1 << HUF_DECBITS; // decoding table size const HUF_DECMASK = HUF_DECSIZE - 1; const NBITS = 16; const A_OFFSET = 1 << ( NBITS - 1 ); const MOD_MASK = ( 1 << NBITS ) - 1; const SHORT_ZEROCODE_RUN = 59; const LONG_ZEROCODE_RUN = 63; const SHORTEST_LONG_RUN = 2 + LONG_ZEROCODE_RUN - SHORT_ZEROCODE_RUN; const ULONG_SIZE = 8; const FLOAT32_SIZE = 4; const INT32_SIZE = 4; const INT16_SIZE = 2; const INT8_SIZE = 1; const STATIC_HUFFMAN = 0; const DEFLATE = 1; const UNKNOWN = 0; const LOSSY_DCT = 1; const RLE = 2; const logBase = Math.pow( 2.7182818, 2.2 ); var tmpDataView = new DataView( new ArrayBuffer( 8 ) ); function frexp( value ) { if ( value === 0 ) return [ value, 0 ]; tmpDataView.setFloat64( 0, value ); var bits = ( tmpDataView.getUint32( 0 ) >>> 20 ) & 0x7FF; if ( bits === 0 ) { // denormal tmpDataView.setFloat64( 0, value * Math.pow( 2, 64 ) ); // exp + 64 bits = ( ( tmpDataView.getUint32( 0 ) >>> 20 ) & 0x7FF ) - 64; } var exponent = bits - 1022; var mantissa = ldexp( value, - exponent ); return [ mantissa, exponent ]; } function ldexp( mantissa, exponent ) { var steps = Math.min( 3, Math.ceil( Math.abs( exponent ) / 1023 ) ); var result = mantissa; for ( var i = 0; i < steps; i ++ ) result *= Math.pow( 2, Math.floor( ( exponent + i ) / steps ) ); return result; } function reverseLutFromBitmap( bitmap, lut ) { var k = 0; for ( var i = 0; i < USHORT_RANGE; ++ i ) { if ( ( i == 0 ) || ( bitmap[ i >> 3 ] & ( 1 << ( i & 7 ) ) ) ) { lut[ k ++ ] = i; } } var n = k - 1; while ( k < USHORT_RANGE ) lut[ k ++ ] = 0; return n; } function hufClearDecTable( hdec ) { for ( var i = 0; i < HUF_DECSIZE; i ++ ) { hdec[ i ] = {}; hdec[ i ].len = 0; hdec[ i ].lit = 0; hdec[ i ].p = null; } } const getBitsReturn = { l: 0, c: 0, lc: 0 }; function getBits( nBits, c, lc, uInt8Array, inOffset ) { while ( lc < nBits ) { c = ( c << 8 ) | parseUint8Array( uInt8Array, inOffset ); lc += 8; } lc -= nBits; getBitsReturn.l = ( c >> lc ) & ( ( 1 << nBits ) - 1 ); getBitsReturn.c = c; getBitsReturn.lc = lc; } const hufTableBuffer = new Array( 59 ); function hufCanonicalCodeTable( hcode ) { for ( var i = 0; i <= 58; ++ i ) hufTableBuffer[ i ] = 0; for ( var i = 0; i < HUF_ENCSIZE; ++ i ) hufTableBuffer[ hcode[ i ] ] += 1; var c = 0; for ( var i = 58; i > 0; -- i ) { var nc = ( ( c + hufTableBuffer[ i ] ) >> 1 ); hufTableBuffer[ i ] = c; c = nc; } for ( var i = 0; i < HUF_ENCSIZE; ++ i ) { var l = hcode[ i ]; if ( l > 0 ) hcode[ i ] = l | ( hufTableBuffer[ l ] ++ << 6 ); } } function hufUnpackEncTable( uInt8Array, inDataView, inOffset, ni, im, iM, hcode ) { var p = inOffset; var c = 0; var lc = 0; for ( ; im <= iM; im ++ ) { if ( p.value - inOffset.value > ni ) return false; getBits( 6, c, lc, uInt8Array, p ); var l = getBitsReturn.l; c = getBitsReturn.c; lc = getBitsReturn.lc; hcode[ im ] = l; if ( l == LONG_ZEROCODE_RUN ) { if ( p.value - inOffset.value > ni ) { throw 'Something wrong with hufUnpackEncTable'; } getBits( 8, c, lc, uInt8Array, p ); var zerun = getBitsReturn.l + SHORTEST_LONG_RUN; c = getBitsReturn.c; lc = getBitsReturn.lc; if ( im + zerun > iM + 1 ) { throw 'Something wrong with hufUnpackEncTable'; } while ( zerun -- ) hcode[ im ++ ] = 0; im --; } else if ( l >= SHORT_ZEROCODE_RUN ) { var zerun = l - SHORT_ZEROCODE_RUN + 2; if ( im + zerun > iM + 1 ) { throw 'Something wrong with hufUnpackEncTable'; } while ( zerun -- ) hcode[ im ++ ] = 0; im --; } } hufCanonicalCodeTable( hcode ); } function hufLength( code ) { return code & 63; } function hufCode( code ) { return code >> 6; } function hufBuildDecTable( hcode, im, iM, hdecod ) { for ( ; im <= iM; im ++ ) { var c = hufCode( hcode[ im ] ); var l = hufLength( hcode[ im ] ); if ( c >> l ) { throw 'Invalid table entry'; } if ( l > HUF_DECBITS ) { var pl = hdecod[ ( c >> ( l - HUF_DECBITS ) ) ]; if ( pl.len ) { throw 'Invalid table entry'; } pl.lit ++; if ( pl.p ) { var p = pl.p; pl.p = new Array( pl.lit ); for ( var i = 0; i < pl.lit - 1; ++ i ) { pl.p[ i ] = p[ i ]; } } else { pl.p = new Array( 1 ); } pl.p[ pl.lit - 1 ] = im; } else if ( l ) { var plOffset = 0; for ( var i = 1 << ( HUF_DECBITS - l ); i > 0; i -- ) { var pl = hdecod[ ( c << ( HUF_DECBITS - l ) ) + plOffset ]; if ( pl.len || pl.p ) { throw 'Invalid table entry'; } pl.len = l; pl.lit = im; plOffset ++; } } } return true; } const getCharReturn = { c: 0, lc: 0 }; function getChar( c, lc, uInt8Array, inOffset ) { c = ( c << 8 ) | parseUint8Array( uInt8Array, inOffset ); lc += 8; getCharReturn.c = c; getCharReturn.lc = lc; } const getCodeReturn = { c: 0, lc: 0 }; function getCode( po, rlc, c, lc, uInt8Array, inDataView, inOffset, outBuffer, outBufferOffset, outBufferEndOffset ) { if ( po == rlc ) { if ( lc < 8 ) { getChar( c, lc, uInt8Array, inOffset ); c = getCharReturn.c; lc = getCharReturn.lc; } lc -= 8; var cs = ( c >> lc ); var cs = new Uint8Array( [ cs ] )[ 0 ]; if ( outBufferOffset.value + cs > outBufferEndOffset ) { return false; } var s = outBuffer[ outBufferOffset.value - 1 ]; while ( cs -- > 0 ) { outBuffer[ outBufferOffset.value ++ ] = s; } } else if ( outBufferOffset.value < outBufferEndOffset ) { outBuffer[ outBufferOffset.value ++ ] = po; } else { return false; } getCodeReturn.c = c; getCodeReturn.lc = lc; } function UInt16( value ) { return ( value & 0xFFFF ); } function Int16( value ) { var ref = UInt16( value ); return ( ref > 0x7FFF ) ? ref - 0x10000 : ref; } const wdec14Return = { a: 0, b: 0 }; function wdec14( l, h ) { var ls = Int16( l ); var hs = Int16( h ); var hi = hs; var ai = ls + ( hi & 1 ) + ( hi >> 1 ); var as = ai; var bs = ai - hi; wdec14Return.a = as; wdec14Return.b = bs; } function wdec16( l, h ) { var m = UInt16( l ); var d = UInt16( h ); var bb = ( m - ( d >> 1 ) ) & MOD_MASK; var aa = ( d + bb - A_OFFSET ) & MOD_MASK; wdec14Return.a = aa; wdec14Return.b = bb; } function wav2Decode( buffer, j, nx, ox, ny, oy, mx ) { var w14 = mx < ( 1 << 14 ); var n = ( nx > ny ) ? ny : nx; var p = 1; var p2; while ( p <= n ) p <<= 1; p >>= 1; p2 = p; p >>= 1; while ( p >= 1 ) { var py = 0; var ey = py + oy * ( ny - p2 ); var oy1 = oy * p; var oy2 = oy * p2; var ox1 = ox * p; var ox2 = ox * p2; var i00, i01, i10, i11; for ( ; py <= ey; py += oy2 ) { var px = py; var ex = py + ox * ( nx - p2 ); for ( ; px <= ex; px += ox2 ) { var p01 = px + ox1; var p10 = px + oy1; var p11 = p10 + ox1; if ( w14 ) { wdec14( buffer[ px + j ], buffer[ p10 + j ] ); i00 = wdec14Return.a; i10 = wdec14Return.b; wdec14( buffer[ p01 + j ], buffer[ p11 + j ] ); i01 = wdec14Return.a; i11 = wdec14Return.b; wdec14( i00, i01 ); buffer[ px + j ] = wdec14Return.a; buffer[ p01 + j ] = wdec14Return.b; wdec14( i10, i11 ); buffer[ p10 + j ] = wdec14Return.a; buffer[ p11 + j ] = wdec14Return.b; } else { wdec16( buffer[ px + j ], buffer[ p10 + j ] ); i00 = wdec14Return.a; i10 = wdec14Return.b; wdec16( buffer[ p01 + j ], buffer[ p11 + j ] ); i01 = wdec14Return.a; i11 = wdec14Return.b; wdec16( i00, i01 ); buffer[ px + j ] = wdec14Return.a; buffer[ p01 + j ] = wdec14Return.b; wdec16( i10, i11 ); buffer[ p10 + j ] = wdec14Return.a; buffer[ p11 + j ] = wdec14Return.b; } } if ( nx & p ) { var p10 = px + oy1; if ( w14 ) wdec14( buffer[ px + j ], buffer[ p10 + j ] ); else wdec16( buffer[ px + j ], buffer[ p10 + j ] ); i00 = wdec14Return.a; buffer[ p10 + j ] = wdec14Return.b; buffer[ px + j ] = i00; } } if ( ny & p ) { var px = py; var ex = py + ox * ( nx - p2 ); for ( ; px <= ex; px += ox2 ) { var p01 = px + ox1; if ( w14 ) wdec14( buffer[ px + j ], buffer[ p01 + j ] ); else wdec16( buffer[ px + j ], buffer[ p01 + j ] ); i00 = wdec14Return.a; buffer[ p01 + j ] = wdec14Return.b; buffer[ px + j ] = i00; } } p2 = p; p >>= 1; } return py; } function hufDecode( encodingTable, decodingTable, uInt8Array, inDataView, inOffset, ni, rlc, no, outBuffer, outOffset ) { var c = 0; var lc = 0; var outBufferEndOffset = no; var inOffsetEnd = Math.trunc( inOffset.value + ( ni + 7 ) / 8 ); while ( inOffset.value < inOffsetEnd ) { getChar( c, lc, uInt8Array, inOffset ); c = getCharReturn.c; lc = getCharReturn.lc; while ( lc >= HUF_DECBITS ) { var index = ( c >> ( lc - HUF_DECBITS ) ) & HUF_DECMASK; var pl = decodingTable[ index ]; if ( pl.len ) { lc -= pl.len; getCode( pl.lit, rlc, c, lc, uInt8Array, inDataView, inOffset, outBuffer, outOffset, outBufferEndOffset ); c = getCodeReturn.c; lc = getCodeReturn.lc; } else { if ( ! pl.p ) { throw 'hufDecode issues'; } var j; for ( j = 0; j < pl.lit; j ++ ) { var l = hufLength( encodingTable[ pl.p[ j ] ] ); while ( lc < l && inOffset.value < inOffsetEnd ) { getChar( c, lc, uInt8Array, inOffset ); c = getCharReturn.c; lc = getCharReturn.lc; } if ( lc >= l ) { if ( hufCode( encodingTable[ pl.p[ j ] ] ) == ( ( c >> ( lc - l ) ) & ( ( 1 << l ) - 1 ) ) ) { lc -= l; getCode( pl.p[ j ], rlc, c, lc, uInt8Array, inDataView, inOffset, outBuffer, outOffset, outBufferEndOffset ); c = getCodeReturn.c; lc = getCodeReturn.lc; break; } } } if ( j == pl.lit ) { throw 'hufDecode issues'; } } } } var i = ( 8 - ni ) & 7; c >>= i; lc -= i; while ( lc > 0 ) { var pl = decodingTable[ ( c << ( HUF_DECBITS - lc ) ) & HUF_DECMASK ]; if ( pl.len ) { lc -= pl.len; getCode( pl.lit, rlc, c, lc, uInt8Array, inDataView, inOffset, outBuffer, outOffset, outBufferEndOffset ); c = getCodeReturn.c; lc = getCodeReturn.lc; } else { throw 'hufDecode issues'; } } return true; } function hufUncompress( uInt8Array, inDataView, inOffset, nCompressed, outBuffer, nRaw ) { var outOffset = { value: 0 }; var initialInOffset = inOffset.value; var im = parseUint32( inDataView, inOffset ); var iM = parseUint32( inDataView, inOffset ); inOffset.value += 4; var nBits = parseUint32( inDataView, inOffset ); inOffset.value += 4; if ( im < 0 || im >= HUF_ENCSIZE || iM < 0 || iM >= HUF_ENCSIZE ) { throw 'Something wrong with HUF_ENCSIZE'; } var freq = new Array( HUF_ENCSIZE ); var hdec = new Array( HUF_DECSIZE ); hufClearDecTable( hdec ); var ni = nCompressed - ( inOffset.value - initialInOffset ); hufUnpackEncTable( uInt8Array, inDataView, inOffset, ni, im, iM, freq ); if ( nBits > 8 * ( nCompressed - ( inOffset.value - initialInOffset ) ) ) { throw 'Something wrong with hufUncompress'; } hufBuildDecTable( freq, im, iM, hdec ); hufDecode( freq, hdec, uInt8Array, inDataView, inOffset, nBits, iM, nRaw, outBuffer, outOffset ); } function applyLut( lut, data, nData ) { for ( var i = 0; i < nData; ++ i ) { data[ i ] = lut[ data[ i ] ]; } } function predictor( source ) { for ( var t = 1; t < source.length; t ++ ) { var d = source[ t - 1 ] + source[ t ] - 128; source[ t ] = d; } } function interleaveScalar( source, out ) { var t1 = 0; var t2 = Math.floor( ( source.length + 1 ) / 2 ); var s = 0; var stop = source.length - 1; while ( true ) { if ( s > stop ) break; out[ s ++ ] = source[ t1 ++ ]; if ( s > stop ) break; out[ s ++ ] = source[ t2 ++ ]; } } function decodeRunLength( source ) { var size = source.byteLength; var out = new Array(); var p = 0; var reader = new DataView( source ); while ( size > 0 ) { var l = reader.getInt8( p ++ ); if ( l < 0 ) { var count = - l; size -= count + 1; for ( var i = 0; i < count; i ++ ) { out.push( reader.getUint8( p ++ ) ); } } else { var count = l; size -= 2; var value = reader.getUint8( p ++ ); for ( var i = 0; i < count + 1; i ++ ) { out.push( value ); } } } return out; } function lossyDctDecode( cscSet, rowPtrs, channelData, acBuffer, dcBuffer, outBuffer ) { var dataView = new DataView( outBuffer.buffer ); var width = channelData[ cscSet.idx[ 0 ] ].width; var height = channelData[ cscSet.idx[ 0 ] ].height; var numComp = 3; var numFullBlocksX = Math.floor( width / 8.0 ); var numBlocksX = Math.ceil( width / 8.0 ); var numBlocksY = Math.ceil( height / 8.0 ); var leftoverX = width - ( numBlocksX - 1 ) * 8; var leftoverY = height - ( numBlocksY - 1 ) * 8; var currAcComp = { value: 0 }; var currDcComp = new Array( numComp ); var dctData = new Array( numComp ); var halfZigBlock = new Array( numComp ); var rowBlock = new Array( numComp ); var rowOffsets = new Array( numComp ); for ( let comp = 0; comp < numComp; ++ comp ) { rowOffsets[ comp ] = rowPtrs[ cscSet.idx[ comp ] ]; currDcComp[ comp ] = ( comp < 1 ) ? 0 : currDcComp[ comp - 1 ] + numBlocksX * numBlocksY; dctData[ comp ] = new Float32Array( 64 ); halfZigBlock[ comp ] = new Uint16Array( 64 ); rowBlock[ comp ] = new Uint16Array( numBlocksX * 64 ); } for ( let blocky = 0; blocky < numBlocksY; ++ blocky ) { var maxY = 8; if ( blocky == numBlocksY - 1 ) maxY = leftoverY; var maxX = 8; for ( let blockx = 0; blockx < numBlocksX; ++ blockx ) { if ( blockx == numBlocksX - 1 ) maxX = leftoverX; for ( let comp = 0; comp < numComp; ++ comp ) { halfZigBlock[ comp ].fill( 0 ); // set block DC component halfZigBlock[ comp ][ 0 ] = dcBuffer[ currDcComp[ comp ] ++ ]; // set block AC components unRleAC( currAcComp, acBuffer, halfZigBlock[ comp ] ); // UnZigZag block to float unZigZag( halfZigBlock[ comp ], dctData[ comp ] ); // decode float dct dctInverse( dctData[ comp ] ); } if ( numComp == 3 ) { csc709Inverse( dctData ); } for ( let comp = 0; comp < numComp; ++ comp ) { convertToHalf( dctData[ comp ], rowBlock[ comp ], blockx * 64 ); } } // blockx let offset = 0; for ( let comp = 0; comp < numComp; ++ comp ) { const type = channelData[ cscSet.idx[ comp ] ].type; for ( let y = 8 * blocky; y < 8 * blocky + maxY; ++ y ) { offset = rowOffsets[ comp ][ y ]; for ( let blockx = 0; blockx < numFullBlocksX; ++ blockx ) { const src = blockx * 64 + ( ( y & 0x7 ) * 8 ); dataView.setUint16( offset + 0 * INT16_SIZE * type, rowBlock[ comp ][ src + 0 ], true ); dataView.setUint16( offset + 1 * INT16_SIZE * type, rowBlock[ comp ][ src + 1 ], true ); dataView.setUint16( offset + 2 * INT16_SIZE * type, rowBlock[ comp ][ src + 2 ], true ); dataView.setUint16( offset + 3 * INT16_SIZE * type, rowBlock[ comp ][ src + 3 ], true ); dataView.setUint16( offset + 4 * INT16_SIZE * type, rowBlock[ comp ][ src + 4 ], true ); dataView.setUint16( offset + 5 * INT16_SIZE * type, rowBlock[ comp ][ src + 5 ], true ); dataView.setUint16( offset + 6 * INT16_SIZE * type, rowBlock[ comp ][ src + 6 ], true ); dataView.setUint16( offset + 7 * INT16_SIZE * type, rowBlock[ comp ][ src + 7 ], true ); offset += 8 * INT16_SIZE * type; } } // handle partial X blocks if ( numFullBlocksX != numBlocksX ) { for ( let y = 8 * blocky; y < 8 * blocky + maxY; ++ y ) { const offset = rowOffsets[ comp ][ y ] + 8 * numFullBlocksX * INT16_SIZE * type; const src = numFullBlocksX * 64 + ( ( y & 0x7 ) * 8 ); for ( let x = 0; x < maxX; ++ x ) { dataView.setUint16( offset + x * INT16_SIZE * type, rowBlock[ comp ][ src + x ], true ); } } } } // comp } // blocky var halfRow = new Uint16Array( width ); var dataView = new DataView( outBuffer.buffer ); // convert channels back to float, if needed for ( var comp = 0; comp < numComp; ++ comp ) { channelData[ cscSet.idx[ comp ] ].decoded = true; var type = channelData[ cscSet.idx[ comp ] ].type; if ( channelData[ comp ].type != 2 ) continue; for ( var y = 0; y < height; ++ y ) { const offset = rowOffsets[ comp ][ y ]; for ( var x = 0; x < width; ++ x ) { halfRow[ x ] = dataView.getUint16( offset + x * INT16_SIZE * type, true ); } for ( var x = 0; x < width; ++ x ) { dataView.setFloat32( offset + x * INT16_SIZE * type, decodeFloat16( halfRow[ x ] ), true ); } } } } function unRleAC( currAcComp, acBuffer, halfZigBlock ) { var acValue; var dctComp = 1; while ( dctComp < 64 ) { acValue = acBuffer[ currAcComp.value ]; if ( acValue == 0xff00 ) { dctComp = 64; } else if ( acValue >> 8 == 0xff ) { dctComp += acValue & 0xff; } else { halfZigBlock[ dctComp ] = acValue; dctComp ++; } currAcComp.value ++; } } function unZigZag( src, dst ) { dst[ 0 ] = decodeFloat16( src[ 0 ] ); dst[ 1 ] = decodeFloat16( src[ 1 ] ); dst[ 2 ] = decodeFloat16( src[ 5 ] ); dst[ 3 ] = decodeFloat16( src[ 6 ] ); dst[ 4 ] = decodeFloat16( src[ 14 ] ); dst[ 5 ] = decodeFloat16( src[ 15 ] ); dst[ 6 ] = decodeFloat16( src[ 27 ] ); dst[ 7 ] = decodeFloat16( src[ 28 ] ); dst[ 8 ] = decodeFloat16( src[ 2 ] ); dst[ 9 ] = decodeFloat16( src[ 4 ] ); dst[ 10 ] = decodeFloat16( src[ 7 ] ); dst[ 11 ] = decodeFloat16( src[ 13 ] ); dst[ 12 ] = decodeFloat16( src[ 16 ] ); dst[ 13 ] = decodeFloat16( src[ 26 ] ); dst[ 14 ] = decodeFloat16( src[ 29 ] ); dst[ 15 ] = decodeFloat16( src[ 42 ] ); dst[ 16 ] = decodeFloat16( src[ 3 ] ); dst[ 17 ] = decodeFloat16( src[ 8 ] ); dst[ 18 ] = decodeFloat16( src[ 12 ] ); dst[ 19 ] = decodeFloat16( src[ 17 ] ); dst[ 20 ] = decodeFloat16( src[ 25 ] ); dst[ 21 ] = decodeFloat16( src[ 30 ] ); dst[ 22 ] = decodeFloat16( src[ 41 ] ); dst[ 23 ] = decodeFloat16( src[ 43 ] ); dst[ 24 ] = decodeFloat16( src[ 9 ] ); dst[ 25 ] = decodeFloat16( src[ 11 ] ); dst[ 26 ] = decodeFloat16( src[ 18 ] ); dst[ 27 ] = decodeFloat16( src[ 24 ] ); dst[ 28 ] = decodeFloat16( src[ 31 ] ); dst[ 29 ] = decodeFloat16( src[ 40 ] ); dst[ 30 ] = decodeFloat16( src[ 44 ] ); dst[ 31 ] = decodeFloat16( src[ 53 ] ); dst[ 32 ] = decodeFloat16( src[ 10 ] ); dst[ 33 ] = decodeFloat16( src[ 19 ] ); dst[ 34 ] = decodeFloat16( src[ 23 ] ); dst[ 35 ] = decodeFloat16( src[ 32 ] ); dst[ 36 ] = decodeFloat16( src[ 39 ] ); dst[ 37 ] = decodeFloat16( src[ 45 ] ); dst[ 38 ] = decodeFloat16( src[ 52 ] ); dst[ 39 ] = decodeFloat16( src[ 54 ] ); dst[ 40 ] = decodeFloat16( src[ 20 ] ); dst[ 41 ] = decodeFloat16( src[ 22 ] ); dst[ 42 ] = decodeFloat16( src[ 33 ] ); dst[ 43 ] = decodeFloat16( src[ 38 ] ); dst[ 44 ] = decodeFloat16( src[ 46 ] ); dst[ 45 ] = decodeFloat16( src[ 51 ] ); dst[ 46 ] = decodeFloat16( src[ 55 ] ); dst[ 47 ] = decodeFloat16( src[ 60 ] ); dst[ 48 ] = decodeFloat16( src[ 21 ] ); dst[ 49 ] = decodeFloat16( src[ 34 ] ); dst[ 50 ] = decodeFloat16( src[ 37 ] ); dst[ 51 ] = decodeFloat16( src[ 47 ] ); dst[ 52 ] = decodeFloat16( src[ 50 ] ); dst[ 53 ] = decodeFloat16( src[ 56 ] ); dst[ 54 ] = decodeFloat16( src[ 59 ] ); dst[ 55 ] = decodeFloat16( src[ 61 ] ); dst[ 56 ] = decodeFloat16( src[ 35 ] ); dst[ 57 ] = decodeFloat16( src[ 36 ] ); dst[ 58 ] = decodeFloat16( src[ 48 ] ); dst[ 59 ] = decodeFloat16( src[ 49 ] ); dst[ 60 ] = decodeFloat16( src[ 57 ] ); dst[ 61 ] = decodeFloat16( src[ 58 ] ); dst[ 62 ] = decodeFloat16( src[ 62 ] ); dst[ 63 ] = decodeFloat16( src[ 63 ] ); } function dctInverse( data ) { const a = 0.5 * Math.cos( 3.14159 / 4.0 ); const b = 0.5 * Math.cos( 3.14159 / 16.0 ); const c = 0.5 * Math.cos( 3.14159 / 8.0 ); const d = 0.5 * Math.cos( 3.0 * 3.14159 / 16.0 ); const e = 0.5 * Math.cos( 5.0 * 3.14159 / 16.0 ); const f = 0.5 * Math.cos( 3.0 * 3.14159 / 8.0 ); const g = 0.5 * Math.cos( 7.0 * 3.14159 / 16.0 ); var alpha = new Array( 4 ); var beta = new Array( 4 ); var theta = new Array( 4 ); var gamma = new Array( 4 ); for ( var row = 0; row < 8; ++ row ) { var rowPtr = row * 8; alpha[ 0 ] = c * data[ rowPtr + 2 ]; alpha[ 1 ] = f * data[ rowPtr + 2 ]; alpha[ 2 ] = c * data[ rowPtr + 6 ]; alpha[ 3 ] = f * data[ rowPtr + 6 ]; beta[ 0 ] = b * data[ rowPtr + 1 ] + d * data[ rowPtr + 3 ] + e * data[ rowPtr + 5 ] + g * data[ rowPtr + 7 ]; beta[ 1 ] = d * data[ rowPtr + 1 ] - g * data[ rowPtr + 3 ] - b * data[ rowPtr + 5 ] - e * data[ rowPtr + 7 ]; beta[ 2 ] = e * data[ rowPtr + 1 ] - b * data[ rowPtr + 3 ] + g * data[ rowPtr + 5 ] + d * data[ rowPtr + 7 ]; beta[ 3 ] = g * data[ rowPtr + 1 ] - e * data[ rowPtr + 3 ] + d * data[ rowPtr + 5 ] - b * data[ rowPtr + 7 ]; theta[ 0 ] = a * ( data[ rowPtr + 0 ] + data[ rowPtr + 4 ] ); theta[ 3 ] = a * ( data[ rowPtr + 0 ] - data[ rowPtr + 4 ] ); theta[ 1 ] = alpha[ 0 ] + alpha[ 3 ]; theta[ 2 ] = alpha[ 1 ] - alpha[ 2 ]; gamma[ 0 ] = theta[ 0 ] + theta[ 1 ]; gamma[ 1 ] = theta[ 3 ] + theta[ 2 ]; gamma[ 2 ] = theta[ 3 ] - theta[ 2 ]; gamma[ 3 ] = theta[ 0 ] - theta[ 1 ]; data[ rowPtr + 0 ] = gamma[ 0 ] + beta[ 0 ]; data[ rowPtr + 1 ] = gamma[ 1 ] + beta[ 1 ]; data[ rowPtr + 2 ] = gamma[ 2 ] + beta[ 2 ]; data[ rowPtr + 3 ] = gamma[ 3 ] + beta[ 3 ]; data[ rowPtr + 4 ] = gamma[ 3 ] - beta[ 3 ]; data[ rowPtr + 5 ] = gamma[ 2 ] - beta[ 2 ]; data[ rowPtr + 6 ] = gamma[ 1 ] - beta[ 1 ]; data[ rowPtr + 7 ] = gamma[ 0 ] - beta[ 0 ]; } for ( var column = 0; column < 8; ++ column ) { alpha[ 0 ] = c * data[ 16 + column ]; alpha[ 1 ] = f * data[ 16 + column ]; alpha[ 2 ] = c * data[ 48 + column ]; alpha[ 3 ] = f * data[ 48 + column ]; beta[ 0 ] = b * data[ 8 + column ] + d * data[ 24 + column ] + e * data[ 40 + column ] + g * data[ 56 + column ]; beta[ 1 ] = d * data[ 8 + column ] - g * data[ 24 + column ] - b * data[ 40 + column ] - e * data[ 56 + column ]; beta[ 2 ] = e * data[ 8 + column ] - b * data[ 24 + column ] + g * data[ 40 + column ] + d * data[ 56 + column ]; beta[ 3 ] = g * data[ 8 + column ] - e * data[ 24 + column ] + d * data[ 40 + column ] - b * data[ 56 + column ]; theta[ 0 ] = a * ( data[ column ] + data[ 32 + column ] ); theta[ 3 ] = a * ( data[ column ] - data[ 32 + column ] ); theta[ 1 ] = alpha[ 0 ] + alpha[ 3 ]; theta[ 2 ] = alpha[ 1 ] - alpha[ 2 ]; gamma[ 0 ] = theta[ 0 ] + theta[ 1 ]; gamma[ 1 ] = theta[ 3 ] + theta[ 2 ]; gamma[ 2 ] = theta[ 3 ] - theta[ 2 ]; gamma[ 3 ] = theta[ 0 ] - theta[ 1 ]; data[ 0 + column ] = gamma[ 0 ] + beta[ 0 ]; data[ 8 + column ] = gamma[ 1 ] + beta[ 1 ]; data[ 16 + column ] = gamma[ 2 ] + beta[ 2 ]; data[ 24 + column ] = gamma[ 3 ] + beta[ 3 ]; data[ 32 + column ] = gamma[ 3 ] - beta[ 3 ]; data[ 40 + column ] = gamma[ 2 ] - beta[ 2 ]; data[ 48 + column ] = gamma[ 1 ] - beta[ 1 ]; data[ 56 + column ] = gamma[ 0 ] - beta[ 0 ]; } } function csc709Inverse( data ) { for ( var i = 0; i < 64; ++ i ) { var y = data[ 0 ][ i ]; var cb = data[ 1 ][ i ]; var cr = data[ 2 ][ i ]; data[ 0 ][ i ] = y + 1.5747 * cr; data[ 1 ][ i ] = y - 0.1873 * cb - 0.4682 * cr; data[ 2 ][ i ] = y + 1.8556 * cb; } } function convertToHalf( src, dst, idx ) { for ( var i = 0; i < 64; ++ i ) { dst[ idx + i ] = DataUtils.toHalfFloat( toLinear( src[ i ] ) ); } } function toLinear( float ) { if ( float <= 1 ) { return Math.sign( float ) * Math.pow( Math.abs( float ), 2.2 ); } else { return Math.sign( float ) * Math.pow( logBase, Math.abs( float ) - 1.0 ); } } function uncompressRAW( info ) { return new DataView( info.array.buffer, info.offset.value, info.size ); } function uncompressRLE( info ) { var compressed = info.viewer.buffer.slice( info.offset.value, info.offset.value + info.size ); var rawBuffer = new Uint8Array( decodeRunLength( compressed ) ); var tmpBuffer = new Uint8Array( rawBuffer.length ); predictor( rawBuffer ); // revert predictor interleaveScalar( rawBuffer, tmpBuffer ); // interleave pixels return new DataView( tmpBuffer.buffer ); } function uncompressZIP( info ) { var compressed = info.array.slice( info.offset.value, info.offset.value + info.size ); if ( typeof fflate === 'undefined' ) { console.error( 'THREE.EXRLoader: External library fflate.min.js required.' ); } var rawBuffer = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef var tmpBuffer = new Uint8Array( rawBuffer.length ); predictor( rawBuffer ); // revert predictor interleaveScalar( rawBuffer, tmpBuffer ); // interleave pixels return new DataView( tmpBuffer.buffer ); } function uncompressPIZ( info ) { var inDataView = info.viewer; var inOffset = { value: info.offset.value }; var tmpBufSize = info.width * scanlineBlockSize * ( EXRHeader.channels.length * info.type ); var outBuffer = new Uint16Array( tmpBufSize ); var bitmap = new Uint8Array( BITMAP_SIZE ); // Setup channel info var outBufferEnd = 0; var pizChannelData = new Array( info.channels ); for ( var i = 0; i < info.channels; i ++ ) { pizChannelData[ i ] = {}; pizChannelData[ i ][ 'start' ] = outBufferEnd; pizChannelData[ i ][ 'end' ] = pizChannelData[ i ][ 'start' ]; pizChannelData[ i ][ 'nx' ] = info.width; pizChannelData[ i ][ 'ny' ] = info.lines; pizChannelData[ i ][ 'size' ] = info.type; outBufferEnd += pizChannelData[ i ].nx * pizChannelData[ i ].ny * pizChannelData[ i ].size; } // Read range compression data var minNonZero = parseUint16( inDataView, inOffset ); var maxNonZero = parseUint16( inDataView, inOffset ); if ( maxNonZero >= BITMAP_SIZE ) { throw 'Something is wrong with PIZ_COMPRESSION BITMAP_SIZE'; } if ( minNonZero <= maxNonZero ) { for ( var i = 0; i < maxNonZero - minNonZero + 1; i ++ ) { bitmap[ i + minNonZero ] = parseUint8( inDataView, inOffset ); } } // Reverse LUT var lut = new Uint16Array( USHORT_RANGE ); var maxValue = reverseLutFromBitmap( bitmap, lut ); var length = parseUint32( inDataView, inOffset ); // Huffman decoding hufUncompress( info.array, inDataView, inOffset, length, outBuffer, outBufferEnd ); // Wavelet decoding for ( var i = 0; i < info.channels; ++ i ) { var cd = pizChannelData[ i ]; for ( var j = 0; j < pizChannelData[ i ].size; ++ j ) { wav2Decode( outBuffer, cd.start + j, cd.nx, cd.size, cd.ny, cd.nx * cd.size, maxValue ); } } // Expand the pixel data to their original range applyLut( lut, outBuffer, outBufferEnd ); // Rearrange the pixel data into the format expected by the caller. var tmpOffset = 0; var tmpBuffer = new Uint8Array( outBuffer.buffer.byteLength ); for ( var y = 0; y < info.lines; y ++ ) { for ( var c = 0; c < info.channels; c ++ ) { var cd = pizChannelData[ c ]; var n = cd.nx * cd.size; var cp = new Uint8Array( outBuffer.buffer, cd.end * INT16_SIZE, n * INT16_SIZE ); tmpBuffer.set( cp, tmpOffset ); tmpOffset += n * INT16_SIZE; cd.end += n; } } return new DataView( tmpBuffer.buffer ); } function uncompressPXR( info ) { var compressed = info.array.slice( info.offset.value, info.offset.value + info.size ); if ( typeof fflate === 'undefined' ) { console.error( 'THREE.EXRLoader: External library fflate.min.js required.' ); } var rawBuffer = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef const sz = info.lines * info.channels * info.width; const tmpBuffer = ( info.type == 1 ) ? new Uint16Array( sz ) : new Uint32Array( sz ); let tmpBufferEnd = 0; let writePtr = 0; const ptr = new Array( 4 ); for ( let y = 0; y < info.lines; y ++ ) { for ( let c = 0; c < info.channels; c ++ ) { let pixel = 0; switch ( info.type ) { case 1: ptr[ 0 ] = tmpBufferEnd; ptr[ 1 ] = ptr[ 0 ] + info.width; tmpBufferEnd = ptr[ 1 ] + info.width; for ( let j = 0; j < info.width; ++ j ) { const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 8 ) | rawBuffer[ ptr[ 1 ] ++ ]; pixel += diff; tmpBuffer[ writePtr ] = pixel; writePtr ++; } break; case 2: ptr[ 0 ] = tmpBufferEnd; ptr[ 1 ] = ptr[ 0 ] + info.width; ptr[ 2 ] = ptr[ 1 ] + info.width; tmpBufferEnd = ptr[ 2 ] + info.width; for ( let j = 0; j < info.width; ++ j ) { const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 24 ) | ( rawBuffer[ ptr[ 1 ] ++ ] << 16 ) | ( rawBuffer[ ptr[ 2 ] ++ ] << 8 ); pixel += diff; tmpBuffer[ writePtr ] = pixel; writePtr ++; } break; } } } return new DataView( tmpBuffer.buffer ); } function uncompressDWA( info ) { var inDataView = info.viewer; var inOffset = { value: info.offset.value }; var outBuffer = new Uint8Array( info.width * info.lines * ( EXRHeader.channels.length * info.type * INT16_SIZE ) ); // Read compression header information var dwaHeader = { version: parseInt64( inDataView, inOffset ), unknownUncompressedSize: parseInt64( inDataView, inOffset ), unknownCompressedSize: parseInt64( inDataView, inOffset ), acCompressedSize: parseInt64( inDataView, inOffset ), dcCompressedSize: parseInt64( inDataView, inOffset ), rleCompressedSize: parseInt64( inDataView, inOffset ), rleUncompressedSize: parseInt64( inDataView, inOffset ), rleRawSize: parseInt64( inDataView, inOffset ), totalAcUncompressedCount: parseInt64( inDataView, inOffset ), totalDcUncompressedCount: parseInt64( inDataView, inOffset ), acCompression: parseInt64( inDataView, inOffset ) }; if ( dwaHeader.version < 2 ) throw 'EXRLoader.parse: ' + EXRHeader.compression + ' version ' + dwaHeader.version + ' is unsupported'; // Read channel ruleset information var channelRules = new Array(); var ruleSize = parseUint16( inDataView, inOffset ) - INT16_SIZE; while ( ruleSize > 0 ) { var name = parseNullTerminatedString( inDataView.buffer, inOffset ); var value = parseUint8( inDataView, inOffset ); var compression = ( value >> 2 ) & 3; var csc = ( value >> 4 ) - 1; var index = new Int8Array( [ csc ] )[ 0 ]; var type = parseUint8( inDataView, inOffset ); channelRules.push( { name: name, index: index, type: type, compression: compression, } ); ruleSize -= name.length + 3; } // Classify channels var channels = EXRHeader.channels; var channelData = new Array( info.channels ); for ( var i = 0; i < info.channels; ++ i ) { var cd = channelData[ i ] = {}; var channel = channels[ i ]; cd.name = channel.name; cd.compression = UNKNOWN; cd.decoded = false; cd.type = channel.pixelType; cd.pLinear = channel.pLinear; cd.width = info.width; cd.height = info.lines; } var cscSet = { idx: new Array( 3 ) }; for ( var offset = 0; offset < info.channels; ++ offset ) { var cd = channelData[ offset ]; for ( var i = 0; i < channelRules.length; ++ i ) { var rule = channelRules[ i ]; if ( cd.name == rule.name ) { cd.compression = rule.compression; if ( rule.index >= 0 ) { cscSet.idx[ rule.index ] = offset; } cd.offset = offset; } } } // Read DCT - AC component data if ( dwaHeader.acCompressedSize > 0 ) { switch ( dwaHeader.acCompression ) { case STATIC_HUFFMAN: var acBuffer = new Uint16Array( dwaHeader.totalAcUncompressedCount ); hufUncompress( info.array, inDataView, inOffset, dwaHeader.acCompressedSize, acBuffer, dwaHeader.totalAcUncompressedCount ); break; case DEFLATE: var compressed = info.array.slice( inOffset.value, inOffset.value + dwaHeader.totalAcUncompressedCount ); var data = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef var acBuffer = new Uint16Array( data.buffer ); inOffset.value += dwaHeader.totalAcUncompressedCount; break; } } // Read DCT - DC component data if ( dwaHeader.dcCompressedSize > 0 ) { var zlibInfo = { array: info.array, offset: inOffset, size: dwaHeader.dcCompressedSize }; var dcBuffer = new Uint16Array( uncompressZIP( zlibInfo ).buffer ); inOffset.value += dwaHeader.dcCompressedSize; } // Read RLE compressed data if ( dwaHeader.rleRawSize > 0 ) { var compressed = info.array.slice( inOffset.value, inOffset.value + dwaHeader.rleCompressedSize ); var data = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef var rleBuffer = decodeRunLength( data.buffer ); inOffset.value += dwaHeader.rleCompressedSize; } // Prepare outbuffer data offset var outBufferEnd = 0; var rowOffsets = new Array( channelData.length ); for ( var i = 0; i < rowOffsets.length; ++ i ) { rowOffsets[ i ] = new Array(); } for ( var y = 0; y < info.lines; ++ y ) { for ( var chan = 0; chan < channelData.length; ++ chan ) { rowOffsets[ chan ].push( outBufferEnd ); outBufferEnd += channelData[ chan ].width * info.type * INT16_SIZE; } } // Lossy DCT decode RGB channels lossyDctDecode( cscSet, rowOffsets, channelData, acBuffer, dcBuffer, outBuffer ); // Decode other channels for ( var i = 0; i < channelData.length; ++ i ) { var cd = channelData[ i ]; if ( cd.decoded ) continue; switch ( cd.compression ) { case RLE: var row = 0; var rleOffset = 0; for ( var y = 0; y < info.lines; ++ y ) { var rowOffsetBytes = rowOffsets[ i ][ row ]; for ( var x = 0; x < cd.width; ++ x ) { for ( var byte = 0; byte < INT16_SIZE * cd.type; ++ byte ) { outBuffer[ rowOffsetBytes ++ ] = rleBuffer[ rleOffset + byte * cd.width * cd.height ]; } rleOffset ++; } row ++; } break; case LOSSY_DCT: // skip default: throw 'EXRLoader.parse: unsupported channel compression'; } } return new DataView( outBuffer.buffer ); } function parseNullTerminatedString( buffer, offset ) { var uintBuffer = new Uint8Array( buffer ); var endOffset = 0; while ( uintBuffer[ offset.value + endOffset ] != 0 ) { endOffset += 1; } var stringValue = new TextDecoder().decode( uintBuffer.slice( offset.value, offset.value + endOffset ) ); offset.value = offset.value + endOffset + 1; return stringValue; } function parseFixedLengthString( buffer, offset, size ) { var stringValue = new TextDecoder().decode( new Uint8Array( buffer ).slice( offset.value, offset.value + size ) ); offset.value = offset.value + size; return stringValue; } function parseUlong( dataView, offset ) { var uLong = dataView.getUint32( 0, true ); offset.value = offset.value + ULONG_SIZE; return uLong; } function parseRational( dataView, offset ) { var x = parseInt32( dataView, offset ); var y = parseUint32( dataView, offset ); return [ x, y ]; } function parseTimecode( dataView, offset ) { var x = parseUint32( dataView, offset ); var y = parseUint32( dataView, offset ); return [ x, y ]; } function parseInt32( dataView, offset ) { var Int32 = dataView.getInt32( offset.value, true ); offset.value = offset.value + INT32_SIZE; return Int32; } function parseUint32( dataView, offset ) { var Uint32 = dataView.getUint32( offset.value, true ); offset.value = offset.value + INT32_SIZE; return Uint32; } function parseUint8Array( uInt8Array, offset ) { var Uint8 = uInt8Array[ offset.value ]; offset.value = offset.value + INT8_SIZE; return Uint8; } function parseUint8( dataView, offset ) { var Uint8 = dataView.getUint8( offset.value ); offset.value = offset.value + INT8_SIZE; return Uint8; } function parseInt64( dataView, offset ) { var int = Number( dataView.getBigInt64( offset.value, true ) ); offset.value += ULONG_SIZE; return int; } function parseFloat32( dataView, offset ) { var float = dataView.getFloat32( offset.value, true ); offset.value += FLOAT32_SIZE; return float; } function decodeFloat32( dataView, offset ) { return DataUtils.toHalfFloat( parseFloat32( dataView, offset ) ); } // https://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript function decodeFloat16( binary ) { var exponent = ( binary & 0x7C00 ) >> 10, fraction = binary & 0x03FF; return ( binary >> 15 ? - 1 : 1 ) * ( exponent ? ( exponent === 0x1F ? fraction ? NaN : Infinity : Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 ) ) : 6.103515625e-5 * ( fraction / 0x400 ) ); } function parseUint16( dataView, offset ) { var Uint16 = dataView.getUint16( offset.value, true ); offset.value += INT16_SIZE; return Uint16; } function parseFloat16( buffer, offset ) { return decodeFloat16( parseUint16( buffer, offset ) ); } function parseChlist( dataView, buffer, offset, size ) { var startOffset = offset.value; var channels = []; while ( offset.value < ( startOffset + size - 1 ) ) { var name = parseNullTerminatedString( buffer, offset ); var pixelType = parseInt32( dataView, offset ); var pLinear = parseUint8( dataView, offset ); offset.value += 3; // reserved, three chars var xSampling = parseInt32( dataView, offset ); var ySampling = parseInt32( dataView, offset ); channels.push( { name: name, pixelType: pixelType, pLinear: pLinear, xSampling: xSampling, ySampling: ySampling } ); } offset.value += 1; return channels; } function parseChromaticities( dataView, offset ) { var redX = parseFloat32( dataView, offset ); var redY = parseFloat32( dataView, offset ); var greenX = parseFloat32( dataView, offset ); var greenY = parseFloat32( dataView, offset ); var blueX = parseFloat32( dataView, offset ); var blueY = parseFloat32( dataView, offset ); var whiteX = parseFloat32( dataView, offset ); var whiteY = parseFloat32( dataView, offset ); return { redX: redX, redY: redY, greenX: greenX, greenY: greenY, blueX: blueX, blueY: blueY, whiteX: whiteX, whiteY: whiteY }; } function parseCompression( dataView, offset ) { var compressionCodes = [ 'NO_COMPRESSION', 'RLE_COMPRESSION', 'ZIPS_COMPRESSION', 'ZIP_COMPRESSION', 'PIZ_COMPRESSION', 'PXR24_COMPRESSION', 'B44_COMPRESSION', 'B44A_COMPRESSION', 'DWAA_COMPRESSION', 'DWAB_COMPRESSION' ]; var compression = parseUint8( dataView, offset ); return compressionCodes[ compression ]; } function parseBox2i( dataView, offset ) { var xMin = parseUint32( dataView, offset ); var yMin = parseUint32( dataView, offset ); var xMax = parseUint32( dataView, offset ); var yMax = parseUint32( dataView, offset ); return { xMin: xMin, yMin: yMin, xMax: xMax, yMax: yMax }; } function parseLineOrder( dataView, offset ) { var lineOrders = [ 'INCREASING_Y' ]; var lineOrder = parseUint8( dataView, offset ); return lineOrders[ lineOrder ]; } function parseV2f( dataView, offset ) { var x = parseFloat32( dataView, offset ); var y = parseFloat32( dataView, offset ); return [ x, y ]; } function parseV3f( dataView, offset ) { var x = parseFloat32( dataView, offset ); var y = parseFloat32( dataView, offset ); var z = parseFloat32( dataView, offset ); return [ x, y, z ]; } function parseValue( dataView, buffer, offset, type, size ) { if ( type === 'string' || type === 'stringvector' || type === 'iccProfile' ) { return parseFixedLengthString( buffer, offset, size ); } else if ( type === 'chlist' ) { return parseChlist( dataView, buffer, offset, size ); } else if ( type === 'chromaticities' ) { return parseChromaticities( dataView, offset ); } else if ( type === 'compression' ) { return parseCompression( dataView, offset ); } else if ( type === 'box2i' ) { return parseBox2i( dataView, offset ); } else if ( type === 'lineOrder' ) { return parseLineOrder( dataView, offset ); } else if ( type === 'float' ) { return parseFloat32( dataView, offset ); } else if ( type === 'v2f' ) { return parseV2f( dataView, offset ); } else if ( type === 'v3f' ) { return parseV3f( dataView, offset ); } else if ( type === 'int' ) { return parseInt32( dataView, offset ); } else if ( type === 'rational' ) { return parseRational( dataView, offset ); } else if ( type === 'timecode' ) { return parseTimecode( dataView, offset ); } else if ( type === 'preview' ) { offset.value += size; return 'skipped'; } else { offset.value += size; return undefined; } } var bufferDataView = new DataView( buffer ); var uInt8Array = new Uint8Array( buffer ); var EXRHeader = {}; bufferDataView.getUint32( 0, true ); // magic bufferDataView.getUint8( 4, true ); // versionByteZero bufferDataView.getUint8( 5, true ); // fullMask // start of header var offset = { value: 8 }; // start at 8, after magic stuff var keepReading = true; while ( keepReading ) { var attributeName = parseNullTerminatedString( buffer, offset ); if ( attributeName == 0 ) { keepReading = false; } else { var attributeType = parseNullTerminatedString( buffer, offset ); var attributeSize = parseUint32( bufferDataView, offset ); var attributeValue = parseValue( bufferDataView, buffer, offset, attributeType, attributeSize ); if ( attributeValue === undefined ) { console.warn( `EXRLoader.parse: skipped unknown header attribute type \'${ attributeType }\'.` ); } else { EXRHeader[ attributeName ] = attributeValue; } } } // offsets var dataWindowHeight = EXRHeader.dataWindow.yMax + 1; var uncompress; var scanlineBlockSize; switch ( EXRHeader.compression ) { case 'NO_COMPRESSION': scanlineBlockSize = 1; uncompress = uncompressRAW; break; case 'RLE_COMPRESSION': scanlineBlockSize = 1; uncompress = uncompressRLE; break; case 'ZIPS_COMPRESSION': scanlineBlockSize = 1; uncompress = uncompressZIP; break; case 'ZIP_COMPRESSION': scanlineBlockSize = 16; uncompress = uncompressZIP; break; case 'PIZ_COMPRESSION': scanlineBlockSize = 32; uncompress = uncompressPIZ; break; case 'PXR24_COMPRESSION': scanlineBlockSize = 16; uncompress = uncompressPXR; break; case 'DWAA_COMPRESSION': scanlineBlockSize = 32; uncompress = uncompressDWA; break; case 'DWAB_COMPRESSION': scanlineBlockSize = 256; uncompress = uncompressDWA; break; default: throw 'EXRLoader.parse: ' + EXRHeader.compression + ' is unsupported'; } var size_t; var getValue; // mixed pixelType not supported var pixelType = EXRHeader.channels[ 0 ].pixelType; if ( pixelType === 1 ) { // half switch ( this.type ) { case UnsignedByteType: case FloatType: getValue = parseFloat16; size_t = INT16_SIZE; break; case HalfFloatType: getValue = parseUint16; size_t = INT16_SIZE; break; } } else if ( pixelType === 2 ) { // float switch ( this.type ) { case UnsignedByteType: case FloatType: getValue = parseFloat32; size_t = FLOAT32_SIZE; break; case HalfFloatType: getValue = decodeFloat32; size_t = FLOAT32_SIZE; } } else { throw 'EXRLoader.parse: unsupported pixelType ' + pixelType + ' for ' + EXRHeader.compression + '.'; } var numBlocks = dataWindowHeight / scanlineBlockSize; for ( var i = 0; i < numBlocks; i ++ ) { parseUlong( bufferDataView, offset ); // scanlineOffset } // we should be passed the scanline offset table, start reading pixel data var width = EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1; var height = EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1; // Firefox only supports RGBA (half) float textures // var numChannels = EXRHeader.channels.length; var numChannels = 4; var size = width * height * numChannels; // Fill initially with 1s for the alpha value if the texture is not RGBA, RGB values will be overwritten switch ( this.type ) { case UnsignedByteType: case FloatType: var byteArray = new Float32Array( size ); if ( EXRHeader.channels.length < numChannels ) { byteArray.fill( 1, 0, size ); } break; case HalfFloatType: var byteArray = new Uint16Array( size ); if ( EXRHeader.channels.length < numChannels ) { byteArray.fill( 0x3C00, 0, size ); // Uint16Array holds half float data, 0x3C00 is 1 } break; default: console.error( 'THREE.EXRLoader: unsupported type: ', this.type ); break; } var channelOffsets = { R: 0, G: 1, B: 2, A: 3 }; var compressionInfo = { size: 0, width: width, lines: scanlineBlockSize, offset: offset, array: uInt8Array, viewer: bufferDataView, type: pixelType, channels: EXRHeader.channels.length, }; var line; var size; var viewer; var tmpOffset = { value: 0 }; for ( var scanlineBlockIdx = 0; scanlineBlockIdx < height / scanlineBlockSize; scanlineBlockIdx ++ ) { line = parseUint32( bufferDataView, offset ); // line_no size = parseUint32( bufferDataView, offset ); // data_len compressionInfo.lines = ( line + scanlineBlockSize > height ) ? height - line : scanlineBlockSize; compressionInfo.offset = offset; compressionInfo.size = size; viewer = uncompress( compressionInfo ); offset.value += size; for ( var line_y = 0; line_y < scanlineBlockSize; line_y ++ ) { var true_y = line_y + ( scanlineBlockIdx * scanlineBlockSize ); if ( true_y >= height ) break; for ( var channelID = 0; channelID < EXRHeader.channels.length; channelID ++ ) { var cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ]; for ( var x = 0; x < width; x ++ ) { var idx = ( line_y * ( EXRHeader.channels.length * width ) ) + ( channelID * width ) + x; tmpOffset.value = idx * size_t; var val = getValue( viewer, tmpOffset ); byteArray[ ( ( ( height - 1 - true_y ) * ( width * numChannels ) ) + ( x * numChannels ) ) + cOff ] = val; } } } } if ( this.type === UnsignedByteType ) { let v, i; const size = byteArray.length; const RGBEArray = new Uint8Array( size ); for ( let h = 0; h < height; ++ h ) { for ( let w = 0; w < width; ++ w ) { i = h * width * 4 + w * 4; const red = byteArray[ i ]; const green = byteArray[ i + 1 ]; const blue = byteArray[ i + 2 ]; v = ( red > green ) ? red : green; v = ( blue > v ) ? blue : v; if ( v < 1e-32 ) { RGBEArray[ i ] = RGBEArray[ i + 1 ] = RGBEArray[ i + 2 ] = RGBEArray[ i + 3 ] = 0; } else { const res = frexp( v ); v = res[ 0 ] * 256 / v; RGBEArray[ i ] = red * v; RGBEArray[ i + 1 ] = green * v; RGBEArray[ i + 2 ] = blue * v; RGBEArray[ i + 3 ] = res[ 1 ] + 128; } } } byteArray = RGBEArray; } const format = ( this.type === UnsignedByteType ) ? RGBEFormat : ( numChannels === 4 ) ? RGBAFormat : RGBFormat; return { header: EXRHeader, width: width, height: height, data: byteArray, format: format, type: this.type }; } setDataType( value ) { this.type = value; return this; } load( url, onLoad, onProgress, onError ) { function onLoadCallback( texture, texData ) { switch ( texture.type ) { case UnsignedByteType: texture.encoding = RGBEEncoding; texture.minFilter = NearestFilter; texture.magFilter = NearestFilter; texture.generateMipmaps = false; texture.flipY = false; break; case FloatType: case HalfFloatType: texture.encoding = LinearEncoding; texture.minFilter = LinearFilter; texture.magFilter = LinearFilter; texture.generateMipmaps = false; texture.flipY = false; break; } if ( onLoad ) onLoad( texture, texData ); } return super.load( url, onLoadCallback, onProgress, onError ); } } export { EXRLoader };