EXRLoader.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /**
  2. * @author Richard M. / https://github.com/richardmonette
  3. */
  4. // https://github.com/mrdoob/three.js/issues/10652
  5. // https://en.wikipedia.org/wiki/OpenEXR
  6. THREE.EXRLoader = function ( manager ) {
  7. this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
  8. };
  9. THREE.EXRLoader.prototype = Object.create( THREE.DataTextureLoader.prototype );
  10. THREE.EXRLoader.prototype._parser = function ( buffer ) {
  11. var parseNullTerminatedString = function( buffer, offset ) {
  12. var uintBuffer = new Uint8Array( buffer );
  13. var endOffset = 0;
  14. while ( uintBuffer[ offset.value + endOffset ] != 0 ) {
  15. endOffset += 1;
  16. }
  17. var stringValue = new TextDecoder().decode(
  18. new Uint8Array( buffer ).slice( offset.value, offset.value + endOffset )
  19. );
  20. offset.value = offset.value + endOffset + 1;
  21. return stringValue;
  22. }
  23. var parseFixedLengthString = function( buffer, offset, size ) {
  24. var stringValue = new TextDecoder().decode(
  25. new Uint8Array( buffer ).slice( offset.value, offset.value + size )
  26. );
  27. offset.value = offset.value + size;
  28. return stringValue;
  29. }
  30. var parseUlong = function( buffer, offset ) {
  31. var uLong = new DataView( buffer.slice( offset.value, offset.value + 4 ) ).getUint32( 0, true );
  32. offset.value = offset.value + 8;
  33. return uLong;
  34. }
  35. var parseUint32 = function( buffer, offset ) {
  36. var Uint32 = new DataView( buffer.slice( offset.value, offset.value + 4 ) ).getUint32( 0, true );
  37. offset.value = offset.value + 4;
  38. return Uint32;
  39. }
  40. var parseUint8 = function( buffer, offset ) {
  41. var Uint8 = new DataView( buffer.slice( offset.value, offset.value + 1 ) ).getUint8( 0, true );
  42. offset.value = offset.value + 1;
  43. return Uint8;
  44. }
  45. var parseFloat32 = function( buffer, offset ) {
  46. var float = new DataView( buffer.slice( offset.value, offset.value + 4 ) ).getFloat32( 0, true );
  47. offset.value += 4;
  48. return float;
  49. }
  50. // https://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript
  51. var decodeFloat16 = function( binary ) {
  52. var exponent = ( binary & 0x7C00 ) >> 10,
  53. fraction = binary & 0x03FF;
  54. return ( binary >> 15 ? - 1 : 1 ) * (
  55. exponent ?
  56. (
  57. exponent === 0x1F ?
  58. fraction ? NaN : Infinity :
  59. Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 )
  60. ) :
  61. 6.103515625e-5 * ( fraction / 0x400 )
  62. );
  63. }
  64. var parseFloat16 = function( buffer, offset ) {
  65. var float = new DataView( buffer.slice( offset.value, offset.value + 2 ) ).getUint16( 0, true );
  66. offset.value += 2;
  67. return decodeFloat16( float );
  68. }
  69. var parseChlist = function( buffer, offset, size ) {
  70. var startOffset = offset.value;
  71. var channels = [];
  72. while ( offset.value < ( startOffset + size - 1 ) ) {
  73. var name = parseNullTerminatedString( buffer, offset );
  74. var pixelType = parseUint32( buffer, offset ); // TODO: Cast this to UINT, HALF or FLOAT
  75. var pLinear = parseUint8( buffer, offset );
  76. offset.value += 3; // reserved, three chars
  77. var xSampling = parseUint32( buffer, offset );
  78. var ySampling = parseUint32( buffer, offset );
  79. channels.push( {
  80. name: name,
  81. pixelType: pixelType,
  82. pLinear: pLinear,
  83. xSampling: xSampling,
  84. ySampling: ySampling
  85. } );
  86. }
  87. offset.value += 1;
  88. return channels;
  89. }
  90. var parseChromaticities = function( buffer, offset ) {
  91. var redX = parseFloat32( buffer, offset );
  92. var redY = parseFloat32( buffer, offset );
  93. var greenX = parseFloat32( buffer, offset );
  94. var greenY = parseFloat32( buffer, offset );
  95. var blueX = parseFloat32( buffer, offset );
  96. var blueY = parseFloat32( buffer, offset );
  97. var whiteX = parseFloat32( buffer, offset );
  98. var whiteY = parseFloat32( buffer, offset );
  99. return { redX: redX, redY: redY, greenX, greenY, blueX, blueY, whiteX, whiteY };
  100. }
  101. var parseCompression = function( buffer, offset ) {
  102. var compressionCodes = [
  103. 'NO_COMPRESSION',
  104. 'PIZ_COMPRESSION'
  105. ];
  106. var compression = parseUint8( buffer, offset );
  107. return compressionCodes[ compression ];
  108. }
  109. var parseBox2i = function( buffer, offset ) {
  110. var xMin = parseUint32( buffer, offset );
  111. var yMin = parseUint32( buffer, offset );
  112. var xMax = parseUint32( buffer, offset );
  113. var yMax = parseUint32( buffer, offset );
  114. return { xMin: xMin, yMin: yMin, xMax: xMax, yMax: yMax };
  115. }
  116. var parseLineOrder = function( buffer, offset ) {
  117. var lineOrders = [
  118. 'INCREASING_Y'
  119. ];
  120. var lineOrder = parseUint8( buffer, offset );
  121. return lineOrders[ lineOrder ];
  122. }
  123. var parseV2f = function( buffer, offset ) {
  124. var x = parseFloat32( buffer, offset );
  125. var y = parseFloat32( buffer, offset );
  126. return [ x, y ];
  127. }
  128. var parseValue = function( buffer, offset, type, size ) {
  129. if ( type == 'string' || type == 'iccProfile' ) {
  130. return parseFixedLengthString( buffer, offset, size );
  131. } else if ( type == 'chlist' ) {
  132. return parseChlist( buffer, offset, size );
  133. } else if ( type == 'chromaticities' ) {
  134. return parseChromaticities( buffer, offset );
  135. } else if ( type == 'compression' ) {
  136. return parseCompression( buffer, offset );
  137. } else if ( type == 'box2i' ) {
  138. return parseBox2i( buffer, offset );
  139. } else if ( type == 'lineOrder' ) {
  140. return parseLineOrder( buffer, offset );
  141. } else if ( type == 'float' ) {
  142. return parseFloat32( buffer, offset );
  143. } else if ( type == 'v2f' ) {
  144. return parseV2f( buffer, offset );
  145. } else {
  146. throw 'Cannot parse value for unsupported type: ' + type;
  147. }
  148. }
  149. var EXRHeader = {};
  150. var magic = new DataView( buffer ).getUint32( 0, true );
  151. var versionByteZero = new DataView( buffer ).getUint8( 4, true );
  152. var fullMask = new DataView( buffer ).getUint8( 5, true );
  153. // start of header
  154. var offset = { value: 8 }; // start at 8, after magic stuff
  155. var keepReading = true;
  156. while ( keepReading ) {
  157. var attributeName = parseNullTerminatedString( buffer, offset );
  158. if ( attributeName == 0 ) {
  159. keepReading = false;
  160. } else {
  161. var attributeType = parseNullTerminatedString( buffer, offset );
  162. var attributeSize = parseUint32( buffer, offset );
  163. var attributeValue = parseValue( buffer, offset, attributeType, attributeSize );
  164. EXRHeader[ attributeName ] = attributeValue;
  165. }
  166. }
  167. // offsets
  168. var dataWindowHeight = EXRHeader.dataWindow.yMax + 1;
  169. var scanlineBlockSize = 1; // 1 for no compression, 32 for PIZ
  170. var numBlocks = dataWindowHeight / scanlineBlockSize;
  171. for ( var i = 0; i < numBlocks; i ++ ) {
  172. var scanlineOffset = parseUlong( buffer, offset );
  173. }
  174. // we should be passed the scanline offset table, start reading pixel data
  175. var width = EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1;
  176. var height = EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1;
  177. var numChannels = EXRHeader.channels.length;
  178. var byteArray = new Float32Array( width * height * numChannels );
  179. var channelOffsets = {
  180. R: 0,
  181. G: 1,
  182. B: 2,
  183. A: 3
  184. };
  185. for ( var y = 0; y < height; y ++ ) {
  186. var y_scanline = parseUint32( buffer, offset );
  187. var dataSize = parseUint32( buffer, offset );
  188. for ( var channelID = 0; channelID < EXRHeader.channels.length; channelID ++ ) {
  189. if ( EXRHeader.channels[ channelID ].pixelType == 1 ) {
  190. // HALF
  191. for ( var x = 0; x < width; x ++ ) {
  192. var val = parseFloat16( buffer, offset );
  193. var cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ];
  194. byteArray[ ( ( ( width - y_scanline ) * ( height * numChannels ) ) + ( x * numChannels ) ) + cOff ] = val;
  195. }
  196. } else {
  197. throw 'Only supported pixel format is HALF';
  198. }
  199. }
  200. }
  201. return {
  202. header: EXRHeader,
  203. width: width,
  204. height: height,
  205. data: byteArray,
  206. format: THREE.RGBAFormat,
  207. type: THREE.FloatType
  208. };
  209. };