diff --git a/examples/jsm/loaders/EXRLoader.js b/examples/jsm/loaders/EXRLoader.js index 86513e3d8e43e6..7d1b71dda8d5a8 100644 --- a/examples/jsm/loaders/EXRLoader.js +++ b/examples/jsm/loaders/EXRLoader.js @@ -6,10 +6,9 @@ import { LinearEncoding, LinearFilter, NearestFilter, + RedFormat, RGBAFormat, RGBEEncoding, - RGBEFormat, - RGBFormat, UnsignedByteType } from '../../../build/three.module.js'; import * as fflate from '../libs/fflate.module.js'; @@ -1312,8 +1311,7 @@ class EXRLoader extends DataTextureLoader { 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 outBuffer = new Uint16Array( info.width * info.scanlineBlockSize * ( info.channels * info.type ) ); var bitmap = new Uint8Array( BITMAP_SIZE ); // Setup channel info @@ -1333,6 +1331,7 @@ class EXRLoader extends DataTextureLoader { } // Read range compression data + var minNonZero = parseUint16( inDataView, inOffset ); var maxNonZero = parseUint16( inDataView, inOffset ); @@ -1489,7 +1488,7 @@ class EXRLoader extends DataTextureLoader { 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 ) ); + var outBuffer = new Uint8Array( info.width * info.lines * ( info.channels * info.type * INT16_SIZE ) ); // Read compression header information var dwaHeader = { @@ -1737,16 +1736,6 @@ class EXRLoader extends DataTextureLoader { } - 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 ); @@ -2037,270 +2026,268 @@ class EXRLoader extends DataTextureLoader { } - 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; + function parseHeader( dataView, buffer, offset ) { - while ( keepReading ) { + const EXRHeader = {}; - var attributeName = parseNullTerminatedString( buffer, offset ); + if ( dataView.getUint32( 0, true ) != 20000630 ) // magic + throw "THREE.EXRLoader: provided file doesn't appear to be in OpenEXR format."; - if ( attributeName == 0 ) { + EXRHeader.version = dataView.getUint8( 4, true ); - 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; - - } + const spec = dataView.getUint8( 5, true ); // fullMask + EXRHeader.spec = { + singleTile: !!(spec & 1), + longName: !!(spec & 2), + deepFormat: !!(spec & 4), + multiPart: !!(spec & 8), } - } + // start of header - // offsets - var dataWindowHeight = EXRHeader.dataWindow.yMax + 1; + offset.value = 8; // start at 8 - after pre-amble - var uncompress; - var scanlineBlockSize; + var keepReading = true; - switch ( EXRHeader.compression ) { + while ( keepReading ) { - case 'NO_COMPRESSION': + var attributeName = parseNullTerminatedString( buffer, offset ); - scanlineBlockSize = 1; - uncompress = uncompressRAW; - break; + if ( attributeName == 0 ) { - case 'RLE_COMPRESSION': + keepReading = false; - scanlineBlockSize = 1; - uncompress = uncompressRLE; - break; - - case 'ZIPS_COMPRESSION': - - scanlineBlockSize = 1; - uncompress = uncompressZIP; - break; - - case 'ZIP_COMPRESSION': - - scanlineBlockSize = 16; - uncompress = uncompressZIP; - break; + } else { - case 'PIZ_COMPRESSION': + var attributeType = parseNullTerminatedString( buffer, offset ); + var attributeSize = parseUint32( dataView, offset ); + var attributeValue = parseValue( dataView, buffer, offset, attributeType, attributeSize ); - scanlineBlockSize = 32; - uncompress = uncompressPIZ; - break; + if ( attributeValue === undefined ) { - case 'PXR24_COMPRESSION': + console.warn( `EXRLoader.parse: skipped unknown header attribute type \'${attributeType}\'.` ); - scanlineBlockSize = 16; - uncompress = uncompressPXR; - break; + } else { - case 'DWAA_COMPRESSION': + EXRHeader[ attributeName ] = attributeValue; - scanlineBlockSize = 32; - uncompress = uncompressDWA; - break; + } - case 'DWAB_COMPRESSION': + } - scanlineBlockSize = 256; - uncompress = uncompressDWA; - break; + } - default: + if ( spec != 0 ) { + console.error( "EXRHeader:", EXRHeader ); + throw "THREE.EXRLoader: provided file is currently unsupported." + } - throw 'EXRLoader.parse: ' + EXRHeader.compression + ' is unsupported'; + return EXRHeader; } - var size_t; - var getValue; - - // mixed pixelType not supported - var pixelType = EXRHeader.channels[ 0 ].pixelType; - - if ( pixelType === 1 ) { // half + function setupDecoder( EXRHeader, dataView, uInt8Array, offset, outputType ) { - switch ( this.type ) { + const EXRDecoder = { + size: 0, + viewer: dataView, + array: uInt8Array, + offset: offset, + width: EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1, + height: EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1, + channels: EXRHeader.channels.length, + bytesPerLine: null, + lines: null, + inputSize: null, + type: EXRHeader.channels[ 0 ].pixelType, + uncompress: null, + getter: null, + format: null, + encoding: null, + }; - case UnsignedByteType: - case FloatType: + switch ( EXRHeader.compression ) { - getValue = parseFloat16; - size_t = INT16_SIZE; + case 'NO_COMPRESSION': + EXRDecoder.lines = 1; + EXRDecoder.uncompress = uncompressRAW; break; - case HalfFloatType: - - getValue = parseUint16; - size_t = INT16_SIZE; + case 'RLE_COMPRESSION': + EXRDecoder.lines = 1; + EXRDecoder.uncompress = uncompressRLE; break; - } + case 'ZIPS_COMPRESSION': + EXRDecoder.lines = 1; + EXRDecoder.uncompress = uncompressZIP; + break; - } else if ( pixelType === 2 ) { // float + case 'ZIP_COMPRESSION': + EXRDecoder.lines = 16; + EXRDecoder.uncompress = uncompressZIP; + break; - switch ( this.type ) { + case 'PIZ_COMPRESSION': + EXRDecoder.lines = 32; + EXRDecoder.uncompress = uncompressPIZ; + break; - case UnsignedByteType: - case FloatType: + case 'PXR24_COMPRESSION': + EXRDecoder.lines = 16; + EXRDecoder.uncompress = uncompressPXR; + break; - getValue = parseFloat32; - size_t = FLOAT32_SIZE; + case 'DWAA_COMPRESSION': + EXRDecoder.lines = 32; + EXRDecoder.uncompress = uncompressDWA; break; - case HalfFloatType: + case 'DWAB_COMPRESSION': + EXRDecoder.lines = 256; + EXRDecoder.uncompress = uncompressDWA; + break; - getValue = decodeFloat32; - size_t = FLOAT32_SIZE; + default: + throw 'EXRLoader.parse: ' + EXRHeader.compression + ' is unsupported'; } - } else { - - throw 'EXRLoader.parse: unsupported pixelType ' + pixelType + ' for ' + EXRHeader.compression + '.'; - - } - - var numBlocks = dataWindowHeight / scanlineBlockSize; - - for ( var i = 0; i < numBlocks; i ++ ) { + EXRDecoder.scanlineBlockSize = EXRDecoder.lines; - parseUlong( bufferDataView, offset ); // scanlineOffset + if ( EXRDecoder.type == 1 ) { - } + // half + switch ( outputType ) { - // we should be passed the scanline offset table, start reading pixel data + case UnsignedByteType: + case FloatType: + EXRDecoder.getter = parseFloat16; + EXRDecoder.inputSize = INT16_SIZE; + break; - 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; + case HalfFloatType: + EXRDecoder.getter = parseUint16; + EXRDecoder.inputSize = INT16_SIZE; + break; - // 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: + } else if ( EXRDecoder.type == 2 ) { - var byteArray = new Float32Array( size ); + // float + switch ( outputType ) { - if ( EXRHeader.channels.length < numChannels ) { + case UnsignedByteType: + case FloatType: + EXRDecoder.getter = parseFloat32; + EXRDecoder.inputSize = FLOAT32_SIZE; + break; - byteArray.fill( 1, 0, size ); + case HalfFloatType: + EXRDecoder.getter = decodeFloat32; + EXRDecoder.inputSize = FLOAT32_SIZE; } - break; + } else { - case HalfFloatType: + throw 'EXRLoader.parse: unsupported pixelType ' + EXRDecoder.type + ' for ' + EXRHeader.compression + '.'; - var byteArray = new Uint16Array( size ); + } - if ( EXRHeader.channels.length < numChannels ) { + EXRDecoder.blockCount = ( EXRHeader.dataWindow.yMax + 1 ) / EXRDecoder.scanlineBlockSize; - byteArray.fill( 0x3C00, 0, size ); // Uint16Array holds half float data, 0x3C00 is 1 + for ( var i = 0; i < EXRDecoder.blockCount; i ++ ) + parseInt64( dataView, offset ) // scanlineOffset + + // we should be passed the scanline offset table, ready to start reading pixel data. - } + // RGB images will be converted to RGBA format, preventing software emulation in select devices. + EXRDecoder.outputChannels = ( ( EXRDecoder.channels == 3 ) ? 4 : EXRDecoder.channels ); + const size = EXRDecoder.width * EXRDecoder.height * EXRDecoder.outputChannels; - break; + switch ( outputType ) { - default: + case UnsignedByteType: + case FloatType: + EXRDecoder.byteArray = new Float32Array( size ); - console.error( 'THREE.EXRLoader: unsupported type: ', this.type ); - break; + // Fill initially with 1s for the alpha value if the texture is not RGBA, RGB values will be overwritten + if ( EXRDecoder.channels < EXRDecoder.outputChannels ) + EXRDecoder.byteArray.fill( 1, 0, size ); + + break; - } + case HalfFloatType: + EXRDecoder.byteArray = new Uint16Array( size ); + + if ( EXRDecoder.channels < EXRDecoder.outputChannels ) + EXRDecoder.byteArray.fill( 0x3C00, 0, size ); // Uint16Array holds half float data, 0x3C00 is 1 - var channelOffsets = { - R: 0, - G: 1, - B: 2, - A: 3 - }; + break; - var compressionInfo = { + default: + console.error( 'THREE.EXRLoader: unsupported type: ', outputType ); + break; - size: 0, - width: width, - lines: scanlineBlockSize, + } - offset: offset, - array: uInt8Array, - viewer: bufferDataView, + EXRDecoder.bytesPerLine = EXRDecoder.width * EXRDecoder.inputSize * EXRDecoder.channels; - type: pixelType, - channels: EXRHeader.channels.length, + if ( EXRDecoder.outputChannels == 4 ) { + EXRDecoder.format = RGBAFormat; + EXRDecoder.encoding = ( outputType == UnsignedByteType ) ? RGBEEncoding : LinearEncoding; + } else { + EXRDecoder.format = RedFormat; + EXRDecoder.encoding = LinearEncoding; + } - }; + return EXRDecoder; - var line; - var size; - var viewer; - var tmpOffset = { value: 0 }; + } - for ( var scanlineBlockIdx = 0; scanlineBlockIdx < height / scanlineBlockSize; scanlineBlockIdx ++ ) { + // start parsing file [START] - line = parseUint32( bufferDataView, offset ); // line_no - size = parseUint32( bufferDataView, offset ); // data_len + const bufferDataView = new DataView( buffer ); + const uInt8Array = new Uint8Array( buffer ); + const offset = { value: 0 }; - compressionInfo.lines = ( line + scanlineBlockSize > height ) ? height - line : scanlineBlockSize; - compressionInfo.offset = offset; - compressionInfo.size = size; + // get header information and validate format. + const EXRHeader = parseHeader( bufferDataView, buffer, offset ); - viewer = uncompress( compressionInfo ); + // get input compression information and prepare decoding. + const EXRDecoder = setupDecoder( EXRHeader, bufferDataView, uInt8Array, offset, this.type ); - offset.value += size; + const tmpOffset = { value: 0 }; + const channelOffsets = { R: 0, G: 1, B: 2, A: 3, Y: 0 }; - for ( var line_y = 0; line_y < scanlineBlockSize; line_y ++ ) { + for ( let scanlineBlockIdx = 0; scanlineBlockIdx < EXRDecoder.height / EXRDecoder.scanlineBlockSize; scanlineBlockIdx ++ ) { + + const line = parseUint32( bufferDataView, offset ); // line_no + EXRDecoder.size = parseUint32( bufferDataView, offset ); // data_len + EXRDecoder.lines = ( ( line + EXRDecoder.scanlineBlockSize > EXRDecoder.height ) ? (EXRDecoder.height-line) : EXRDecoder.scanlineBlockSize ); - var true_y = line_y + ( scanlineBlockIdx * scanlineBlockSize ); + const isCompressed = EXRDecoder.size < EXRDecoder.lines * EXRDecoder.bytesPerLine; + const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder ); - if ( true_y >= height ) break; + offset.value += EXRDecoder.size; - for ( var channelID = 0; channelID < EXRHeader.channels.length; channelID ++ ) { + for ( let line_y = 0; line_y < EXRDecoder.scanlineBlockSize; line_y ++ ) { - var cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ]; + const true_y = line_y + scanlineBlockIdx * EXRDecoder.scanlineBlockSize; + if ( true_y >= EXRDecoder.height ) break; - for ( var x = 0; x < width; x ++ ) { + for ( let channelID = 0; channelID < EXRDecoder.channels; channelID ++ ) { - var idx = ( line_y * ( EXRHeader.channels.length * width ) ) + ( channelID * width ) + x; - tmpOffset.value = idx * size_t; + const cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ]; - var val = getValue( viewer, tmpOffset ); + for ( let x = 0; x < EXRDecoder.width; x ++ ) { - byteArray[ ( ( ( height - 1 - true_y ) * ( width * numChannels ) ) + ( x * numChannels ) ) + cOff ] = val; + tmpOffset.value = ( line_y * ( EXRDecoder.channels * EXRDecoder.width ) + channelID * EXRDecoder.width + x ) * EXRDecoder.inputSize; + const outIndex = ( EXRDecoder.height - 1 - true_y ) * ( EXRDecoder.width * EXRDecoder.outputChannels ) + x * EXRDecoder.outputChannels + cOff; + EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset ); } @@ -2310,24 +2297,23 @@ class EXRLoader extends DataTextureLoader { } - if ( this.type === UnsignedByteType ) { + // convert to RGBE if user specifies Uint8 output on a RGB input texture + if ( EXRDecoder.encoding == RGBEEncoding ) { let v, i; - const size = byteArray.length; + const size = EXRDecoder.byteArray.length; const RGBEArray = new Uint8Array( size ); - for ( let h = 0; h < height; ++ h ) { + for ( let h = 0; h < EXRDecoder.height; ++ h ) { - for ( let w = 0; w < width; ++ w ) { + for ( let w = 0; w < EXRDecoder.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; + i = h * EXRDecoder.width * 4 + w * 4; + const red = EXRDecoder.byteArray[ i ]; + const green = EXRDecoder.byteArray[ i + 1 ]; + const blue = EXRDecoder.byteArray[ i + 2 ]; + v = red > green ? red : green; + v = blue > v ? blue : v; if ( v < 1e-32 ) { @@ -2337,7 +2323,6 @@ class EXRLoader extends DataTextureLoader { const res = frexp( v ); v = res[ 0 ] * 256 / v; - RGBEArray[ i ] = red * v; RGBEArray[ i + 1 ] = green * v; RGBEArray[ i + 2 ] = blue * v; @@ -2349,19 +2334,18 @@ class EXRLoader extends DataTextureLoader { } - byteArray = RGBEArray; + EXRDecoder.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 + width: EXRDecoder.width, + height: EXRDecoder.height, + data: EXRDecoder.byteArray, + format: EXRDecoder.format, + encoding: EXRDecoder.encoding, + type: this.type, }; } @@ -2377,28 +2361,11 @@ class EXRLoader extends DataTextureLoader { 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; - - } + texture.encoding = texData.encoding; + texture.minFilter = ( texture.encoding == RGBEEncoding ) ? NearestFilter : LinearFilter; + texture.magFilter = ( texture.encoding == RGBEEncoding ) ? NearestFilter : LinearFilter; + texture.generateMipmaps = false; + texture.flipY = false; if ( onLoad ) onLoad( texture, texData );