EXRExporter.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. ( function () {
  2. /**
  3. * @author sciecode / https://github.com/sciecode
  4. *
  5. * EXR format references:
  6. * https://www.openexr.com/documentation/openexrfilelayout.pdf
  7. */
  8. const textEncoder = new TextEncoder();
  9. const NO_COMPRESSION = 0;
  10. const ZIPS_COMPRESSION = 2;
  11. const ZIP_COMPRESSION = 3;
  12. class EXRExporter {
  13. parse( renderer, renderTarget, options ) {
  14. if ( ! supported( renderer, renderTarget ) ) return undefined;
  15. const info = buildInfo( renderTarget, options ),
  16. dataBuffer = getPixelData( renderer, renderTarget, info ),
  17. rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ),
  18. chunks = compressData( rawContentBuffer, info );
  19. return fillData( chunks, info );
  20. }
  21. }
  22. function supported( renderer, renderTarget ) {
  23. if ( ! renderer || ! renderer.isWebGLRenderer ) {
  24. console.error( 'EXRExporter.parse: Unsupported first parameter, expected instance of WebGLRenderer.' );
  25. return false;
  26. }
  27. if ( ! renderTarget || ! renderTarget.isWebGLRenderTarget ) {
  28. console.error( 'EXRExporter.parse: Unsupported second parameter, expected instance of WebGLRenderTarget.' );
  29. return false;
  30. }
  31. if ( renderTarget.texture.type !== THREE.FloatType && renderTarget.texture.type !== THREE.HalfFloatType ) {
  32. console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture type.' );
  33. return false;
  34. }
  35. if ( renderTarget.texture.format !== THREE.RGBAFormat ) {
  36. console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture format, expected THREE.RGBAFormat.' );
  37. return false;
  38. }
  39. return true;
  40. }
  41. function buildInfo( renderTarget, options = {} ) {
  42. const compressionSizes = {
  43. 0: 1,
  44. 2: 1,
  45. 3: 16
  46. };
  47. const WIDTH = renderTarget.width,
  48. HEIGHT = renderTarget.height,
  49. TYPE = renderTarget.texture.type,
  50. FORMAT = renderTarget.texture.format,
  51. ENCODING = renderTarget.texture.encoding,
  52. COMPRESSION = options.compression !== undefined ? options.compression : ZIP_COMPRESSION,
  53. EXPORTER_TYPE = options.type !== undefined ? options.type : THREE.HalfFloatType,
  54. OUT_TYPE = EXPORTER_TYPE === THREE.FloatType ? 2 : 1,
  55. COMPRESSION_SIZE = compressionSizes[ COMPRESSION ],
  56. NUM_CHANNELS = 4;
  57. return {
  58. width: WIDTH,
  59. height: HEIGHT,
  60. type: TYPE,
  61. format: FORMAT,
  62. encoding: ENCODING,
  63. compression: COMPRESSION,
  64. blockLines: COMPRESSION_SIZE,
  65. dataType: OUT_TYPE,
  66. dataSize: 2 * OUT_TYPE,
  67. numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ),
  68. numInputChannels: 4,
  69. numOutputChannels: NUM_CHANNELS
  70. };
  71. }
  72. function getPixelData( renderer, rtt, info ) {
  73. let dataBuffer;
  74. if ( info.type === THREE.FloatType ) {
  75. dataBuffer = new Float32Array( info.width * info.height * info.numInputChannels );
  76. } else {
  77. dataBuffer = new Uint16Array( info.width * info.height * info.numInputChannels );
  78. }
  79. renderer.readRenderTargetPixels( rtt, 0, 0, info.width, info.height, dataBuffer );
  80. return dataBuffer;
  81. }
  82. function reorganizeDataBuffer( inBuffer, info ) {
  83. const w = info.width,
  84. h = info.height,
  85. dec = {
  86. r: 0,
  87. g: 0,
  88. b: 0,
  89. a: 0
  90. },
  91. offset = {
  92. value: 0
  93. },
  94. cOffset = info.numOutputChannels == 4 ? 1 : 0,
  95. getValue = info.type == THREE.FloatType ? getFloat32 : getFloat16,
  96. setValue = info.dataType == 1 ? setFloat16 : setFloat32,
  97. outBuffer = new Uint8Array( info.width * info.height * info.numOutputChannels * info.dataSize ),
  98. dv = new DataView( outBuffer.buffer );
  99. for ( let y = 0; y < h; ++ y ) {
  100. for ( let x = 0; x < w; ++ x ) {
  101. const i = y * w * 4 + x * 4;
  102. const r = getValue( inBuffer, i );
  103. const g = getValue( inBuffer, i + 1 );
  104. const b = getValue( inBuffer, i + 2 );
  105. const a = getValue( inBuffer, i + 3 );
  106. const line = ( h - y - 1 ) * w * ( 3 + cOffset ) * info.dataSize;
  107. decodeLinear( dec, r, g, b, a );
  108. offset.value = line + x * info.dataSize;
  109. setValue( dv, dec.a, offset );
  110. offset.value = line + cOffset * w * info.dataSize + x * info.dataSize;
  111. setValue( dv, dec.b, offset );
  112. offset.value = line + ( 1 + cOffset ) * w * info.dataSize + x * info.dataSize;
  113. setValue( dv, dec.g, offset );
  114. offset.value = line + ( 2 + cOffset ) * w * info.dataSize + x * info.dataSize;
  115. setValue( dv, dec.r, offset );
  116. }
  117. }
  118. return outBuffer;
  119. }
  120. function compressData( inBuffer, info ) {
  121. let compress,
  122. tmpBuffer,
  123. sum = 0;
  124. const chunks = {
  125. data: new Array(),
  126. totalSize: 0
  127. },
  128. size = info.width * info.numOutputChannels * info.blockLines * info.dataSize;
  129. switch ( info.compression ) {
  130. case 0:
  131. compress = compressNONE;
  132. break;
  133. case 2:
  134. case 3:
  135. compress = compressZIP;
  136. break;
  137. }
  138. if ( info.compression !== 0 ) {
  139. tmpBuffer = new Uint8Array( size );
  140. }
  141. for ( let i = 0; i < info.numBlocks; ++ i ) {
  142. const arr = inBuffer.subarray( size * i, size * ( i + 1 ) );
  143. const block = compress( arr, tmpBuffer );
  144. sum += block.length;
  145. chunks.data.push( {
  146. dataChunk: block,
  147. size: block.length
  148. } );
  149. }
  150. chunks.totalSize = sum;
  151. return chunks;
  152. }
  153. function compressNONE( data ) {
  154. return data;
  155. }
  156. function compressZIP( data, tmpBuffer ) {
  157. //
  158. // Reorder the pixel data.
  159. //
  160. let t1 = 0,
  161. t2 = Math.floor( ( data.length + 1 ) / 2 ),
  162. s = 0;
  163. const stop = data.length - 1;
  164. while ( true ) {
  165. if ( s > stop ) break;
  166. tmpBuffer[ t1 ++ ] = data[ s ++ ];
  167. if ( s > stop ) break;
  168. tmpBuffer[ t2 ++ ] = data[ s ++ ];
  169. } //
  170. // Predictor.
  171. //
  172. let p = tmpBuffer[ 0 ];
  173. for ( let t = 1; t < tmpBuffer.length; t ++ ) {
  174. const d = tmpBuffer[ t ] - p + ( 128 + 256 );
  175. p = tmpBuffer[ t ];
  176. tmpBuffer[ t ] = d;
  177. }
  178. if ( typeof fflate === 'undefined' ) {
  179. console.error( 'THREE.EXRLoader: External \`fflate.module.js\` required' );
  180. }
  181. const deflate = fflate.zlibSync( tmpBuffer ); // eslint-disable-line no-undef
  182. return deflate;
  183. }
  184. function fillHeader( outBuffer, chunks, info ) {
  185. const offset = {
  186. value: 0
  187. };
  188. const dv = new DataView( outBuffer.buffer );
  189. setUint32( dv, 20000630, offset ); // magic
  190. setUint32( dv, 2, offset ); // mask
  191. // = HEADER =
  192. setString( dv, 'compression', offset );
  193. setString( dv, 'compression', offset );
  194. setUint32( dv, 1, offset );
  195. setUint8( dv, info.compression, offset );
  196. setString( dv, 'screenWindowCenter', offset );
  197. setString( dv, 'v2f', offset );
  198. setUint32( dv, 8, offset );
  199. setUint32( dv, 0, offset );
  200. setUint32( dv, 0, offset );
  201. setString( dv, 'screenWindowWidth', offset );
  202. setString( dv, 'float', offset );
  203. setUint32( dv, 4, offset );
  204. setFloat32( dv, 1.0, offset );
  205. setString( dv, 'pixelAspectRatio', offset );
  206. setString( dv, 'float', offset );
  207. setUint32( dv, 4, offset );
  208. setFloat32( dv, 1.0, offset );
  209. setString( dv, 'lineOrder', offset );
  210. setString( dv, 'lineOrder', offset );
  211. setUint32( dv, 1, offset );
  212. setUint8( dv, 0, offset );
  213. setString( dv, 'dataWindow', offset );
  214. setString( dv, 'box2i', offset );
  215. setUint32( dv, 16, offset );
  216. setUint32( dv, 0, offset );
  217. setUint32( dv, 0, offset );
  218. setUint32( dv, info.width - 1, offset );
  219. setUint32( dv, info.height - 1, offset );
  220. setString( dv, 'displayWindow', offset );
  221. setString( dv, 'box2i', offset );
  222. setUint32( dv, 16, offset );
  223. setUint32( dv, 0, offset );
  224. setUint32( dv, 0, offset );
  225. setUint32( dv, info.width - 1, offset );
  226. setUint32( dv, info.height - 1, offset );
  227. setString( dv, 'channels', offset );
  228. setString( dv, 'chlist', offset );
  229. setUint32( dv, info.numOutputChannels * 18 + 1, offset );
  230. setString( dv, 'A', offset );
  231. setUint32( dv, info.dataType, offset );
  232. offset.value += 4;
  233. setUint32( dv, 1, offset );
  234. setUint32( dv, 1, offset );
  235. setString( dv, 'B', offset );
  236. setUint32( dv, info.dataType, offset );
  237. offset.value += 4;
  238. setUint32( dv, 1, offset );
  239. setUint32( dv, 1, offset );
  240. setString( dv, 'G', offset );
  241. setUint32( dv, info.dataType, offset );
  242. offset.value += 4;
  243. setUint32( dv, 1, offset );
  244. setUint32( dv, 1, offset );
  245. setString( dv, 'R', offset );
  246. setUint32( dv, info.dataType, offset );
  247. offset.value += 4;
  248. setUint32( dv, 1, offset );
  249. setUint32( dv, 1, offset );
  250. setUint8( dv, 0, offset ); // null-byte
  251. setUint8( dv, 0, offset ); // = OFFSET TABLE =
  252. let sum = offset.value + info.numBlocks * 8;
  253. for ( let i = 0; i < chunks.data.length; ++ i ) {
  254. setUint64( dv, sum, offset );
  255. sum += chunks.data[ i ].size + 8;
  256. }
  257. }
  258. function fillData( chunks, info ) {
  259. const TableSize = info.numBlocks * 8,
  260. HeaderSize = 259 + 18 * info.numOutputChannels,
  261. // 259 + 18 * chlist
  262. offset = {
  263. value: HeaderSize + TableSize
  264. },
  265. outBuffer = new Uint8Array( HeaderSize + TableSize + chunks.totalSize + info.numBlocks * 8 ),
  266. dv = new DataView( outBuffer.buffer );
  267. fillHeader( outBuffer, chunks, info );
  268. for ( let i = 0; i < chunks.data.length; ++ i ) {
  269. const data = chunks.data[ i ].dataChunk;
  270. const size = chunks.data[ i ].size;
  271. setUint32( dv, i * info.blockLines, offset );
  272. setUint32( dv, size, offset );
  273. outBuffer.set( data, offset.value );
  274. offset.value += size;
  275. }
  276. return outBuffer;
  277. }
  278. function decodeLinear( dec, r, g, b, a ) {
  279. dec.r = r;
  280. dec.g = g;
  281. dec.b = b;
  282. dec.a = a;
  283. } // function decodeSRGB( dec, r, g, b, a ) {
  284. // dec.r = r > 0.04045 ? Math.pow( r * 0.9478672986 + 0.0521327014, 2.4 ) : r * 0.0773993808;
  285. // dec.g = g > 0.04045 ? Math.pow( g * 0.9478672986 + 0.0521327014, 2.4 ) : g * 0.0773993808;
  286. // dec.b = b > 0.04045 ? Math.pow( b * 0.9478672986 + 0.0521327014, 2.4 ) : b * 0.0773993808;
  287. // dec.a = a;
  288. // }
  289. function setUint8( dv, value, offset ) {
  290. dv.setUint8( offset.value, value );
  291. offset.value += 1;
  292. }
  293. function setUint32( dv, value, offset ) {
  294. dv.setUint32( offset.value, value, true );
  295. offset.value += 4;
  296. }
  297. function setFloat16( dv, value, offset ) {
  298. dv.setUint16( offset.value, THREE.DataUtils.toHalfFloat( value ), true );
  299. offset.value += 2;
  300. }
  301. function setFloat32( dv, value, offset ) {
  302. dv.setFloat32( offset.value, value, true );
  303. offset.value += 4;
  304. }
  305. function setUint64( dv, value, offset ) {
  306. dv.setBigUint64( offset.value, BigInt( value ), true );
  307. offset.value += 8;
  308. }
  309. function setString( dv, string, offset ) {
  310. const tmp = textEncoder.encode( string + '\0' );
  311. for ( let i = 0; i < tmp.length; ++ i ) {
  312. setUint8( dv, tmp[ i ], offset );
  313. }
  314. }
  315. function decodeFloat16( binary ) {
  316. const exponent = ( binary & 0x7C00 ) >> 10,
  317. fraction = binary & 0x03FF;
  318. return ( binary >> 15 ? - 1 : 1 ) * ( exponent ? exponent === 0x1F ? fraction ? NaN : Infinity : Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 ) : 6.103515625e-5 * ( fraction / 0x400 ) );
  319. }
  320. function getFloat16( arr, i ) {
  321. return decodeFloat16( arr[ i ] );
  322. }
  323. function getFloat32( arr, i ) {
  324. return arr[ i ];
  325. }
  326. THREE.EXRExporter = EXRExporter;
  327. THREE.NO_COMPRESSION = NO_COMPRESSION;
  328. THREE.ZIPS_COMPRESSION = ZIPS_COMPRESSION;
  329. THREE.ZIP_COMPRESSION = ZIP_COMPRESSION;
  330. } )();