Browse Source

EXRLoader: impl line-order & mixed channel support (#27982)

* EXRLoader: impl line-order & mixed channel support

* EXRLoader: update data-window condition
Guilherme Avila 1 year ago
parent
commit
4365ebc659
1 changed files with 89 additions and 19 deletions
  1. 89 19
      examples/jsm/loaders/EXRLoader.js

+ 89 - 19
examples/jsm/loaders/EXRLoader.js

@@ -1886,10 +1886,10 @@ class EXRLoader extends DataTextureLoader {
 
 		function parseBox2i( dataView, offset ) {
 
-			const xMin = parseUint32( dataView, offset );
-			const yMin = parseUint32( dataView, offset );
-			const xMax = parseUint32( dataView, offset );
-			const yMax = parseUint32( dataView, offset );
+			const xMin = parseInt32( dataView, offset );
+			const yMin = parseInt32( dataView, offset );
+			const xMax = parseInt32( dataView, offset );
+			const yMax = parseInt32( dataView, offset );
 
 			return { xMin: xMin, yMin: yMin, xMax: xMax, yMax: yMax };
 
@@ -1898,7 +1898,8 @@ class EXRLoader extends DataTextureLoader {
 		function parseLineOrder( dataView, offset ) {
 
 			const lineOrders = [
-				'INCREASING_Y'
+				'INCREASING_Y',
+				'DECREASING_Y'
 			];
 
 			const lineOrder = parseUint8( dataView, offset );
@@ -2066,10 +2067,12 @@ class EXRLoader extends DataTextureLoader {
 				width: EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1,
 				height: EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1,
 				channels: EXRHeader.channels.length,
+				channelLineOffsets: {},
+				scanOrder: null,
 				bytesPerLine: null,
 				lines: null,
 				inputSize: null,
-				type: EXRHeader.channels[ 0 ].pixelType,
+				type: null,
 				uncompress: null,
 				getter: null,
 				format: null,
@@ -2125,6 +2128,43 @@ class EXRLoader extends DataTextureLoader {
 
 			EXRDecoder.scanlineBlockSize = EXRDecoder.lines;
 
+			const channels = {};
+			for ( const channel of EXRHeader.channels ) {
+
+				switch ( channel.name ) {
+
+					case 'Y':
+					case 'R':
+					case 'G':
+					case 'B':
+					case 'A':
+						channels[ channel.name ] = true;
+						EXRDecoder.type = channel.pixelType;
+
+				}
+
+			}
+
+			// RGB images will be converted to RGBA format, preventing software emulation in select devices.
+			let fillAlpha = false;
+
+			if ( channels.R && channels.G && channels.B ) {
+
+				fillAlpha = ! channels.A;
+				EXRDecoder.outputChannels = 4;
+				EXRDecoder.decodeChannels = { R: 0, G: 1, B: 2, A: 3 };
+
+			} else if ( channels.Y ) {
+
+				EXRDecoder.outputChannels = 1;
+				EXRDecoder.decodeChannels = { Y: 0 };
+
+			} else {
+
+				throw new Error( 'EXRLoader.parse: file contains unsupported data channels.' );
+
+			}
+
 			if ( EXRDecoder.type == 1 ) {
 
 				// half
@@ -2164,15 +2204,13 @@ class EXRLoader extends DataTextureLoader {
 
 			}
 
-			EXRDecoder.blockCount = ( EXRHeader.dataWindow.yMax + 1 ) / EXRDecoder.scanlineBlockSize;
+			EXRDecoder.blockCount = EXRDecoder.height / EXRDecoder.scanlineBlockSize;
 
 			for ( let 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;
 
 			switch ( outputType ) {
@@ -2181,7 +2219,7 @@ class EXRLoader extends DataTextureLoader {
 					EXRDecoder.byteArray = new Float32Array( size );
 
 					// Fill initially with 1s for the alpha value if the texture is not RGBA, RGB values will be overwritten
-					if ( EXRDecoder.channels < EXRDecoder.outputChannels )
+					if ( fillAlpha )
 						EXRDecoder.byteArray.fill( 1, 0, size );
 
 					break;
@@ -2189,7 +2227,7 @@ class EXRLoader extends DataTextureLoader {
 				case HalfFloatType:
 					EXRDecoder.byteArray = new Uint16Array( size );
 
-					if ( EXRDecoder.channels < EXRDecoder.outputChannels )
+					if ( fillAlpha )
 						EXRDecoder.byteArray.fill( 0x3C00, 0, size ); // Uint16Array holds half float data, 0x3C00 is 1
 
 					break;
@@ -2200,7 +2238,31 @@ class EXRLoader extends DataTextureLoader {
 
 			}
 
-			EXRDecoder.bytesPerLine = EXRDecoder.width * EXRDecoder.inputSize * EXRDecoder.channels;
+			let byteOffset = 0;
+			for ( const channel of EXRHeader.channels ) {
+
+				if ( EXRDecoder.decodeChannels[ channel.name ] !== undefined ) {
+
+					EXRDecoder.channelLineOffsets[ channel.name ] = byteOffset * EXRDecoder.width;
+
+				}
+
+				byteOffset += channel.pixelType * 2;
+
+			}
+
+			EXRDecoder.bytesPerLine = EXRDecoder.width * byteOffset;
+			EXRDecoder.outLineWidth = EXRDecoder.width * EXRDecoder.outputChannels;
+
+			if ( EXRHeader.lineOrder === 'INCREASING_Y' ) {
+
+				EXRDecoder.scanOrder = ( y ) => y;
+
+			} else {
+
+				EXRDecoder.scanOrder = ( y ) => EXRDecoder.height - 1 - y;
+
+			}
 
 			if ( EXRDecoder.outputChannels == 4 ) {
 
@@ -2231,11 +2293,10 @@ class EXRLoader extends DataTextureLoader {
 		const EXRDecoder = setupDecoder( EXRHeader, bufferDataView, uInt8Array, offset, this.type );
 
 		const tmpOffset = { value: 0 };
-		const channelOffsets = { R: 0, G: 1, B: 2, A: 3, Y: 0 };
 
 		for ( let scanlineBlockIdx = 0; scanlineBlockIdx < EXRDecoder.height / EXRDecoder.scanlineBlockSize; scanlineBlockIdx ++ ) {
 
-			const line = parseUint32( bufferDataView, offset ); // line_no
+			const line = parseInt32( bufferDataView, offset ) - EXRHeader.dataWindow.yMin; // line_no
 			EXRDecoder.size = parseUint32( bufferDataView, offset ); // data_len
 			EXRDecoder.lines = ( ( line + EXRDecoder.scanlineBlockSize > EXRDecoder.height ) ? ( EXRDecoder.height - line ) : EXRDecoder.scanlineBlockSize );
 
@@ -2246,17 +2307,26 @@ class EXRLoader extends DataTextureLoader {
 
 			for ( let line_y = 0; line_y < EXRDecoder.scanlineBlockSize; line_y ++ ) {
 
-				const true_y = line_y + scanlineBlockIdx * EXRDecoder.scanlineBlockSize;
-				if ( true_y >= EXRDecoder.height ) break;
+				const scan_y = scanlineBlockIdx * EXRDecoder.scanlineBlockSize;
+				const true_y = line_y + EXRDecoder.scanOrder( scan_y );
+				if ( true_y >= EXRDecoder.height ) continue;
+
+				const lineOffset = line_y * EXRDecoder.bytesPerLine;
+				const outLineOffset = ( EXRDecoder.height - 1 - true_y ) * EXRDecoder.outLineWidth;
 
 				for ( let channelID = 0; channelID < EXRDecoder.channels; channelID ++ ) {
 
-					const cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ];
+					const name = EXRHeader.channels[ channelID ].name;
+					const lOff = EXRDecoder.channelLineOffsets[ name ];
+					const cOff = EXRDecoder.decodeChannels[ name ];
+
+					if ( cOff === undefined ) continue;
+
+					tmpOffset.value = lineOffset + lOff;
 
 					for ( let x = 0; x < EXRDecoder.width; x ++ ) {
 
-						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;
+						const outIndex = outLineOffset + x * EXRDecoder.outputChannels + cOff;
 						EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset );
 
 					}