three
Version:
JavaScript 3D library
1,276 lines (793 loc) • 26.6 kB
JavaScript
import {
FloatType,
HalfFloatType,
RGBAFormat,
DataUtils,
} from 'three';
import * as fflate from '../libs/fflate.module.js';
const textEncoder = new TextEncoder();
const NO_COMPRESSION = 0;
const RLE_COMPRESSION = 1;
const ZIPS_COMPRESSION = 2;
const ZIP_COMPRESSION = 3;
const PIZ_COMPRESSION = 4;
const USHORT_RANGE = 1 << 16;
const BITMAP_SIZE = USHORT_RANGE >> 3;
const A_OFFSET = 1 << 15;
const M_OFFSET = 1 << 15;
const MOD_MASK = ( 1 << 16 ) - 1;
const HUF_ENCSIZE = ( 1 << 16 ) + 1;
const SHORT_ZEROCODE_RUN = 59;
const LONG_ZEROCODE_RUN = 63;
const SHORTEST_LONG_RUN = 2 + LONG_ZEROCODE_RUN - SHORT_ZEROCODE_RUN;
const LONGEST_LONG_RUN = 255 + SHORTEST_LONG_RUN;
/**
* An exporter for EXR.
*
* EXR ( Extended Dynamic Range) is an [open format specification](https://github.com/AcademySoftwareFoundation/openexr)
* for professional-grade image storage format of the motion picture industry. The purpose of
* format is to accurately and efficiently represent high-dynamic-range scene-linear image data
* and associated metadata. The library is widely used in host application software where accuracy
* is critical, such as photorealistic rendering, texture access, image compositing, deep compositing,
* and DI.
*
* ```js
* const exporter = new EXRExporter();
* const result = await exporter.parse( renderer, options );
* ```
*
* @three_import import { EXRExporter } from 'three/addons/exporters/EXRExporter.js';
*/
class EXRExporter {
/**
* This method has two variants.
*
* - When exporting a data texture, it receives two parameters. The texture and the exporter options.
* - When exporting a render target, it receives three parameters. The renderer, the
* render target and the exporter options.
*
* @async
* @param {(DataTexture|WebGPURenderer|WebGLRenderer)} arg1 - The data texture to export or a renderer.
* @param {(EXRExporter~Options|RenderTarget)} arg2 - The exporter options or a render target.
* @param {EXRExporter~Options} [arg3] - The exporter options.
* @return {Promise<Uint8Array>} A Promise that resolves with the exported EXR.
*/
async parse( arg1, arg2, arg3 ) {
if ( ! arg1 || ! ( arg1.isWebGLRenderer || arg1.isWebGPURenderer || arg1.isDataTexture ) ) {
throw Error( 'EXRExporter.parse: Unsupported first parameter, expected instance of WebGLRenderer, WebGPURenderer or DataTexture.' );
} else if ( arg1.isWebGLRenderer || arg1.isWebGPURenderer ) {
const renderer = arg1, renderTarget = arg2, options = arg3;
supportedRTT( renderTarget, options );
const info = buildInfoRTT( renderTarget, options ),
dataBuffer = await getPixelData( renderer, renderTarget, info ),
rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ),
chunks = compressData( rawContentBuffer, info );
return fillData( chunks, info );
} else if ( arg1.isDataTexture ) {
const texture = arg1, options = arg2;
supportedDT( texture );
const info = buildInfoDT( texture, options ),
dataBuffer = texture.image.data,
rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ),
chunks = compressData( rawContentBuffer, info );
return fillData( chunks, info );
}
}
}
function supportedRTT( renderTarget, options = {} ) {
if ( ! renderTarget || ! renderTarget.isRenderTarget ) {
throw Error( 'EXRExporter.parse: Unsupported second parameter, expected instance of WebGLRenderTarget.' );
}
const textureIndex = ( options.textureIndex !== undefined ) ? options.textureIndex : 0;
const texture = renderTarget.textures[ textureIndex ];
if ( texture === undefined ) {
throw Error( 'EXRExporter.parse: Invalid textureIndex, the render target has ' + renderTarget.textures.length + ' texture(s).' );
}
if ( renderTarget.isCubeRenderTarget || renderTarget.isWebGLCubeRenderTarget || renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) {
throw Error( 'EXRExporter.parse: Unsupported render target type, expected instance of WebGLRenderTarget.' );
}
if ( texture.type !== FloatType && texture.type !== HalfFloatType ) {
throw Error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture type.' );
}
if ( texture.format !== RGBAFormat ) {
throw Error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture format, expected RGBAFormat.' );
}
}
function supportedDT( texture ) {
if ( texture.type !== FloatType && texture.type !== HalfFloatType ) {
throw Error( 'EXRExporter.parse: Unsupported DataTexture texture type.' );
}
if ( texture.format !== RGBAFormat ) {
throw Error( 'EXRExporter.parse: Unsupported DataTexture texture format, expected RGBAFormat.' );
}
if ( ! texture.image.data ) {
throw Error( 'EXRExporter.parse: Invalid DataTexture image data.' );
}
if ( texture.type === FloatType && texture.image.data.constructor.name !== 'Float32Array' ) {
throw Error( 'EXRExporter.parse: DataTexture image data doesn\'t match type, expected \'Float32Array\'.' );
}
if ( texture.type === HalfFloatType && texture.image.data.constructor.name !== 'Uint16Array' ) {
throw Error( 'EXRExporter.parse: DataTexture image data doesn\'t match type, expected \'Uint16Array\'.' );
}
}
function buildInfoRTT( renderTarget, options = {} ) {
const compressionSizes = {
0: 1,
1: 1,
2: 1,
3: 16,
4: 32
};
const TEXTURE_INDEX = ( options.textureIndex !== undefined ) ? options.textureIndex : 0,
TEXTURE = renderTarget.textures[ TEXTURE_INDEX ],
WIDTH = renderTarget.width,
HEIGHT = renderTarget.height,
TYPE = TEXTURE.type,
FORMAT = TEXTURE.format,
COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION,
EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType,
OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1,
COMPRESSION_SIZE = compressionSizes[ COMPRESSION ],
NUM_CHANNELS = 4;
return {
width: WIDTH,
height: HEIGHT,
type: TYPE,
format: FORMAT,
compression: COMPRESSION,
blockLines: COMPRESSION_SIZE,
dataType: OUT_TYPE,
dataSize: 2 * OUT_TYPE,
numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ),
numInputChannels: 4,
numOutputChannels: NUM_CHANNELS,
textureIndex: TEXTURE_INDEX,
};
}
function buildInfoDT( texture, options = {} ) {
const compressionSizes = {
0: 1,
1: 1,
2: 1,
3: 16,
4: 32
};
const WIDTH = texture.image.width,
HEIGHT = texture.image.height,
TYPE = texture.type,
FORMAT = texture.format,
COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION,
EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType,
OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1,
COMPRESSION_SIZE = compressionSizes[ COMPRESSION ],
NUM_CHANNELS = 4;
return {
width: WIDTH,
height: HEIGHT,
type: TYPE,
format: FORMAT,
compression: COMPRESSION,
blockLines: COMPRESSION_SIZE,
dataType: OUT_TYPE,
dataSize: 2 * OUT_TYPE,
numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ),
numInputChannels: 4,
numOutputChannels: NUM_CHANNELS,
};
}
async function getPixelData( renderer, rtt, info ) {
let dataBuffer;
if ( renderer.isWebGLRenderer ) {
if ( info.type === FloatType ) {
dataBuffer = new Float32Array( info.width * info.height * info.numInputChannels );
} else {
dataBuffer = new Uint16Array( info.width * info.height * info.numInputChannels );
}
await renderer.readRenderTargetPixelsAsync( rtt, 0, 0, info.width, info.height, dataBuffer, undefined, info.textureIndex );
} else {
dataBuffer = await renderer.readRenderTargetPixelsAsync( rtt, 0, 0, info.width, info.height, info.textureIndex );
}
return dataBuffer;
}
function reorganizeDataBuffer( inBuffer, info ) {
const w = info.width,
h = info.height,
dec = { r: 0, g: 0, b: 0, a: 0 },
offset = { value: 0 },
cOffset = ( info.numOutputChannels == 4 ) ? 1 : 0,
getValue = ( info.type == FloatType ) ? getFloat32 : getFloat16,
setValue = ( info.dataType == 1 ) ? setFloat16 : setFloat32,
outBuffer = new Uint8Array( info.width * info.height * info.numOutputChannels * info.dataSize ),
dv = new DataView( outBuffer.buffer );
for ( let y = 0; y < h; ++ y ) {
for ( let x = 0; x < w; ++ x ) {
const i = y * w * 4 + x * 4;
const r = getValue( inBuffer, i );
const g = getValue( inBuffer, i + 1 );
const b = getValue( inBuffer, i + 2 );
const a = getValue( inBuffer, i + 3 );
const line = ( h - y - 1 ) * w * ( 3 + cOffset ) * info.dataSize;
decodeLinear( dec, r, g, b, a );
offset.value = line + x * info.dataSize;
setValue( dv, dec.a, offset );
offset.value = line + ( cOffset ) * w * info.dataSize + x * info.dataSize;
setValue( dv, dec.b, offset );
offset.value = line + ( 1 + cOffset ) * w * info.dataSize + x * info.dataSize;
setValue( dv, dec.g, offset );
offset.value = line + ( 2 + cOffset ) * w * info.dataSize + x * info.dataSize;
setValue( dv, dec.r, offset );
}
}
return outBuffer;
}
function compressData( inBuffer, info ) {
let compress,
tmpBuffer,
sum = 0;
const chunks = { data: new Array(), totalSize: 0 },
size = info.width * info.numOutputChannels * info.blockLines * info.dataSize;
switch ( info.compression ) {
case 0:
compress = compressNONE;
break;
case 1:
compress = compressRLE;
break;
case 2:
case 3:
compress = compressZIP;
break;
case 4:
compress = compressPIZ;
break;
}
if ( info.compression !== 0 ) {
tmpBuffer = new Uint8Array( size );
}
for ( let i = 0; i < info.numBlocks; ++ i ) {
const arr = inBuffer.subarray( size * i, size * ( i + 1 ) );
let block = compress( arr, tmpBuffer, info );
if ( block.length >= arr.length ) block = arr;
sum += block.length;
chunks.data.push( { dataChunk: block, size: block.length } );
}
chunks.totalSize = sum;
return chunks;
}
function compressNONE( data ) {
return data;
}
function reorderAndPredict( data, tmpBuffer ) {
tmpBuffer = tmpBuffer.subarray( 0, data.length );
//
// Reorder the pixel data.
//
let t1 = 0,
t2 = Math.floor( ( data.length + 1 ) / 2 ),
s = 0;
const stop = data.length - 1;
while ( true ) {
if ( s > stop ) break;
tmpBuffer[ t1 ++ ] = data[ s ++ ];
if ( s > stop ) break;
tmpBuffer[ t2 ++ ] = data[ s ++ ];
}
//
// Predictor.
//
let p = tmpBuffer[ 0 ];
for ( let t = 1; t < tmpBuffer.length; t ++ ) {
const d = tmpBuffer[ t ] - p + ( 128 + 256 );
p = tmpBuffer[ t ];
tmpBuffer[ t ] = d;
}
return tmpBuffer;
}
function compressRLE( data, tmpBuffer ) {
tmpBuffer = reorderAndPredict( data, tmpBuffer );
const out = new Uint8Array( tmpBuffer.length * 2 );
const end = tmpBuffer.length;
let runs = 0, rune = 1, o = 0;
while ( runs < end ) {
let count = 0;
while ( rune < end && tmpBuffer[ runs ] === tmpBuffer[ rune ] && count < 127 ) {
rune ++;
count ++;
}
if ( count >= 2 ) {
out[ o ++ ] = count;
out[ o ++ ] = tmpBuffer[ runs ];
runs = rune;
} else {
count ++;
while ( rune < end &&
( ( rune + 1 >= end || tmpBuffer[ rune ] !== tmpBuffer[ rune + 1 ] ) ||
( rune + 2 >= end || tmpBuffer[ rune + 1 ] !== tmpBuffer[ rune + 2 ] ) ) &&
count < 127 ) {
count ++;
rune ++;
}
out[ o ++ ] = - count;
while ( runs < rune ) out[ o ++ ] = tmpBuffer[ runs ++ ];
}
rune ++;
}
return out.slice( 0, o );
}
function compressZIP( data, tmpBuffer ) {
tmpBuffer = reorderAndPredict( data, tmpBuffer );
const deflate = fflate.zlibSync( tmpBuffer );
return deflate;
}
function compressPIZ( data, tmpBuffer, info ) {
const channels = info.numOutputChannels;
const wcount = info.dataSize / 2;
const nx = info.width;
const bpl = nx * info.dataSize;
const ny = data.length / ( bpl * channels );
const ndata = data.length / 2;
const buffer = new Uint16Array( ndata );
for ( let y = 0; y < ny; y ++ ) {
for ( let c = 0; c < channels; c ++ ) {
const src = ( y * channels + c ) * bpl;
const dst = ( c * ny + y ) * bpl / 2;
for ( let i = 0; i < bpl / 2; i ++ ) {
buffer[ dst + i ] = data[ src + 2 * i ] | ( data[ src + 2 * i + 1 ] << 8 );
}
}
}
const bitmap = new Uint8Array( BITMAP_SIZE );
for ( let i = 0; i < ndata; i ++ ) {
bitmap[ buffer[ i ] >> 3 ] |= 1 << ( buffer[ i ] & 7 );
}
bitmap[ 0 ] &= ~ 1;
let minNonZero = BITMAP_SIZE - 1;
let maxNonZero = 0;
for ( let i = 0; i < BITMAP_SIZE; i ++ ) {
if ( bitmap[ i ] ) {
if ( minNonZero > i ) minNonZero = i;
if ( maxNonZero < i ) maxNonZero = i;
}
}
const lut = new Uint16Array( USHORT_RANGE );
let k = 0;
for ( let i = 0; i < USHORT_RANGE; i ++ ) {
if ( i === 0 || ( bitmap[ i >> 3 ] & ( 1 << ( i & 7 ) ) ) ) lut[ i ] = k ++;
}
const maxValue = k - 1;
for ( let i = 0; i < ndata; i ++ ) {
buffer[ i ] = lut[ buffer[ i ] ];
}
for ( let c = 0; c < channels; c ++ ) {
for ( let j = 0; j < wcount; j ++ ) {
wav2Encode( buffer, c * nx * ny * wcount + j, nx, wcount, ny, wcount * nx, maxValue );
}
}
const bitmapLength = ( minNonZero <= maxNonZero ) ? maxNonZero - minNonZero + 1 : 0;
const huf = hufCompress( buffer, data.length );
if ( huf === null ) return data;
const out = new Uint8Array( 8 + bitmapLength + huf.length );
const dv = new DataView( out.buffer );
dv.setUint16( 0, minNonZero, true );
dv.setUint16( 2, maxNonZero, true );
out.set( bitmap.subarray( minNonZero, minNonZero + bitmapLength ), 4 );
dv.setUint32( 4 + bitmapLength, huf.length, true );
out.set( huf, 8 + bitmapLength );
return out;
}
const _wenc = { l: 0, h: 0 };
function wenc14( a, b, out ) {
const as = ( a << 16 ) >> 16;
const bs = ( b << 16 ) >> 16;
out.l = ( ( as + bs ) >> 1 ) & 0xffff;
out.h = ( as - bs ) & 0xffff;
}
function wenc16( a, b, out ) {
const ao = ( a + A_OFFSET ) & MOD_MASK;
let m = ( ao + b ) >> 1;
const d = ao - b;
if ( d < 0 ) m = ( m + M_OFFSET ) & MOD_MASK;
out.l = m;
out.h = d & MOD_MASK;
}
function wav2Encode( buffer, j, nx, ox, ny, oy, mx ) {
const wenc = ( mx < ( 1 << 14 ) ) ? wenc14 : wenc16;
const n = ( nx > ny ) ? ny : nx;
let p = 1;
let p2 = 2;
while ( p2 <= n ) {
let py = j;
const ey = j + oy * ( ny - p2 );
const oy1 = oy * p;
const oy2 = oy * p2;
const ox1 = ox * p;
const ox2 = ox * p2;
for ( ; py <= ey; py += oy2 ) {
let px = py;
const ex = py + ox * ( nx - p2 );
for ( ; px <= ex; px += ox2 ) {
const p01 = px + ox1;
const p10 = px + oy1;
const p11 = p10 + ox1;
wenc( buffer[ px ], buffer[ p01 ], _wenc );
const i00 = _wenc.l, i01 = _wenc.h;
wenc( buffer[ p10 ], buffer[ p11 ], _wenc );
const i10 = _wenc.l, i11 = _wenc.h;
wenc( i00, i10, _wenc );
buffer[ px ] = _wenc.l;
buffer[ p10 ] = _wenc.h;
wenc( i01, i11, _wenc );
buffer[ p01 ] = _wenc.l;
buffer[ p11 ] = _wenc.h;
}
if ( nx & p ) {
const p10 = px + oy1;
wenc( buffer[ px ], buffer[ p10 ], _wenc );
buffer[ px ] = _wenc.l;
buffer[ p10 ] = _wenc.h;
}
}
if ( ny & p ) {
let px = py;
const ex = py + ox * ( nx - p2 );
for ( ; px <= ex; px += ox2 ) {
const p01 = px + ox1;
wenc( buffer[ px ], buffer[ p01 ], _wenc );
buffer[ px ] = _wenc.l;
buffer[ p01 ] = _wenc.h;
}
}
p = p2;
p2 <<= 1;
}
}
function hufCompress( raw, maxLength ) {
const freq = new Float64Array( HUF_ENCSIZE );
for ( let i = 0; i < raw.length; i ++ ) freq[ raw[ i ] ] ++;
const table = hufBuildEncTable( freq );
const out = new Uint8Array( maxLength );
const writer = { out, offset: 20, c: 0, lc: 0, overflow: false };
hufPackEncTable( table, writer );
const tableLength = writer.offset - 20;
const dataStart = writer.offset;
writer.c = 0;
writer.lc = 0;
hufEncode( table, raw, writer );
const nBits = ( writer.offset - dataStart ) * 8 + writer.lc;
const length = dataStart + Math.ceil( nBits / 8 );
if ( writer.overflow || length >= maxLength ) return null;
const dv = new DataView( out.buffer );
dv.setUint32( 0, table.im, true );
dv.setUint32( 4, table.iM, true );
dv.setUint32( 8, tableLength, true );
dv.setUint32( 12, nBits, true );
dv.setUint32( 16, 0, true );
return out.subarray( 0, length );
}
function hufBuildEncTable( frq ) {
const hlink = new Uint32Array( HUF_ENCSIZE );
const heap = [];
let im = 0;
let iM = 0;
while ( ! frq[ im ] ) im ++;
for ( let i = im; i < HUF_ENCSIZE; i ++ ) {
hlink[ i ] = i;
if ( frq[ i ] ) {
heap.push( i );
iM = i;
}
}
iM ++;
frq[ iM ] = 1;
heap.push( iM );
let nf = heap.length;
const greater = ( a, b ) => frq[ a ] > frq[ b ] || ( frq[ a ] === frq[ b ] && a > b );
makeHeap( heap, nf, greater );
const lengths = new Uint8Array( HUF_ENCSIZE );
while ( nf > 1 ) {
const mm = heap[ 0 ];
popHeap( heap, nf, greater );
nf --;
const m = heap[ 0 ];
popHeap( heap, nf, greater );
frq[ m ] += frq[ mm ];
pushHeap( heap, nf, greater );
for ( let j = m; ; j = hlink[ j ] ) {
lengths[ j ] ++;
if ( hlink[ j ] === j ) {
hlink[ j ] = mm;
break;
}
}
for ( let j = mm; ; j = hlink[ j ] ) {
lengths[ j ] ++;
if ( hlink[ j ] === j ) break;
}
}
const n = new Float64Array( 59 );
for ( let i = 0; i < HUF_ENCSIZE; i ++ ) n[ lengths[ i ] ] ++;
let c = 0;
for ( let i = 58; i > 0; i -- ) {
const nc = Math.floor( ( c + n[ i ] ) / 2 );
n[ i ] = c;
c = nc;
}
const codes = new Float64Array( HUF_ENCSIZE );
for ( let i = 0; i < HUF_ENCSIZE; i ++ ) {
if ( lengths[ i ] > 0 ) codes[ i ] = n[ lengths[ i ] ] ++;
}
return { codes, lengths, im, iM };
}
function pushHeapHole( heap, holeIndex, topIndex, value, greater ) {
let parent = Math.floor( ( holeIndex - 1 ) / 2 );
while ( holeIndex > topIndex && greater( heap[ parent ], value ) ) {
heap[ holeIndex ] = heap[ parent ];
holeIndex = parent;
parent = Math.floor( ( holeIndex - 1 ) / 2 );
}
heap[ holeIndex ] = value;
}
function adjustHeap( heap, holeIndex, len, value, greater ) {
const topIndex = holeIndex;
let secondChild = holeIndex;
while ( secondChild < Math.floor( ( len - 1 ) / 2 ) ) {
secondChild = 2 * ( secondChild + 1 );
if ( greater( heap[ secondChild ], heap[ secondChild - 1 ] ) ) secondChild --;
heap[ holeIndex ] = heap[ secondChild ];
holeIndex = secondChild;
}
if ( ( len & 1 ) === 0 && secondChild === ( len - 2 ) / 2 ) {
secondChild = 2 * ( secondChild + 1 );
heap[ holeIndex ] = heap[ secondChild - 1 ];
holeIndex = secondChild - 1;
}
pushHeapHole( heap, holeIndex, topIndex, value, greater );
}
function makeHeap( heap, len, greater ) {
if ( len < 2 ) return;
for ( let parent = Math.floor( ( len - 2 ) / 2 ); parent >= 0; parent -- ) {
adjustHeap( heap, parent, len, heap[ parent ], greater );
}
}
function pushHeap( heap, len, greater ) {
pushHeapHole( heap, len - 1, 0, heap[ len - 1 ], greater );
}
function popHeap( heap, len, greater ) {
if ( len > 1 ) {
const last = len - 1;
const value = heap[ last ];
heap[ last ] = heap[ 0 ];
adjustHeap( heap, 0, last, value, greater );
}
}
function outputBits( writer, nBits, bits ) {
while ( nBits > 0 ) {
const k = Math.min( nBits, 16 );
nBits -= k;
writer.c = ( writer.c << k ) | ( Math.floor( bits / 2 ** nBits ) % ( 1 << k ) );
writer.lc += k;
while ( writer.lc >= 8 ) {
writer.lc -= 8;
if ( writer.offset < writer.out.length ) {
writer.out[ writer.offset ++ ] = writer.c >> writer.lc;
} else {
writer.overflow = true;
}
}
writer.c &= ( 1 << writer.lc ) - 1;
}
}
function hufPackEncTable( table, writer ) {
const { lengths, iM } = table;
for ( let im = table.im; im <= iM; im ++ ) {
const l = lengths[ im ];
if ( l === 0 ) {
let zerun = 1;
while ( im < iM && zerun < LONGEST_LONG_RUN ) {
if ( lengths[ im + 1 ] > 0 ) break;
im ++;
zerun ++;
}
if ( zerun >= 2 ) {
if ( zerun >= SHORTEST_LONG_RUN ) {
outputBits( writer, 6, LONG_ZEROCODE_RUN );
outputBits( writer, 8, zerun - SHORTEST_LONG_RUN );
} else {
outputBits( writer, 6, SHORT_ZEROCODE_RUN + zerun - 2 );
}
continue;
}
}
outputBits( writer, 6, l );
}
if ( writer.lc > 0 ) outputBits( writer, 8 - writer.lc, 0 );
}
function hufEncode( table, raw, writer ) {
const { codes, lengths, iM: rlc } = table;
const sendCode = ( s, runCount ) => {
if ( lengths[ s ] + lengths[ rlc ] + 8 < lengths[ s ] * runCount ) {
outputBits( writer, lengths[ s ], codes[ s ] );
outputBits( writer, lengths[ rlc ], codes[ rlc ] );
outputBits( writer, 8, runCount );
} else {
while ( runCount -- >= 0 ) outputBits( writer, lengths[ s ], codes[ s ] );
}
};
let s = raw[ 0 ];
let cs = 0;
for ( let i = 1; i < raw.length; i ++ ) {
const ns = raw[ i ];
if ( cs === 255 || s !== ns ) {
sendCode( s, cs );
cs = 0;
s = ns;
} else {
cs ++;
}
}
sendCode( s, cs );
if ( writer.lc > 0 ) {
if ( writer.offset < writer.out.length ) {
writer.out[ writer.offset ] = ( writer.c << ( 8 - writer.lc ) ) & 0xff;
} else {
writer.overflow = true;
}
}
}
function fillHeader( outBuffer, chunks, info ) {
const offset = { value: 0 };
const dv = new DataView( outBuffer.buffer );
setUint32( dv, 20000630, offset ); // magic
setUint32( dv, 2, offset ); // mask
// = HEADER =
setString( dv, 'compression', offset );
setString( dv, 'compression', offset );
setUint32( dv, 1, offset );
setUint8( dv, info.compression, offset );
setString( dv, 'screenWindowCenter', offset );
setString( dv, 'v2f', offset );
setUint32( dv, 8, offset );
setUint32( dv, 0, offset );
setUint32( dv, 0, offset );
setString( dv, 'screenWindowWidth', offset );
setString( dv, 'float', offset );
setUint32( dv, 4, offset );
setFloat32( dv, 1.0, offset );
setString( dv, 'pixelAspectRatio', offset );
setString( dv, 'float', offset );
setUint32( dv, 4, offset );
setFloat32( dv, 1.0, offset );
setString( dv, 'lineOrder', offset );
setString( dv, 'lineOrder', offset );
setUint32( dv, 1, offset );
setUint8( dv, 0, offset );
setString( dv, 'dataWindow', offset );
setString( dv, 'box2i', offset );
setUint32( dv, 16, offset );
setUint32( dv, 0, offset );
setUint32( dv, 0, offset );
setUint32( dv, info.width - 1, offset );
setUint32( dv, info.height - 1, offset );
setString( dv, 'displayWindow', offset );
setString( dv, 'box2i', offset );
setUint32( dv, 16, offset );
setUint32( dv, 0, offset );
setUint32( dv, 0, offset );
setUint32( dv, info.width - 1, offset );
setUint32( dv, info.height - 1, offset );
setString( dv, 'channels', offset );
setString( dv, 'chlist', offset );
setUint32( dv, info.numOutputChannels * 18 + 1, offset );
setString( dv, 'A', offset );
setUint32( dv, info.dataType, offset );
offset.value += 4;
setUint32( dv, 1, offset );
setUint32( dv, 1, offset );
setString( dv, 'B', offset );
setUint32( dv, info.dataType, offset );
offset.value += 4;
setUint32( dv, 1, offset );
setUint32( dv, 1, offset );
setString( dv, 'G', offset );
setUint32( dv, info.dataType, offset );
offset.value += 4;
setUint32( dv, 1, offset );
setUint32( dv, 1, offset );
setString( dv, 'R', offset );
setUint32( dv, info.dataType, offset );
offset.value += 4;
setUint32( dv, 1, offset );
setUint32( dv, 1, offset );
setUint8( dv, 0, offset );
// null-byte
setUint8( dv, 0, offset );
// = OFFSET TABLE =
let sum = offset.value + info.numBlocks * 8;
for ( let i = 0; i < chunks.data.length; ++ i ) {
setUint64( dv, sum, offset );
sum += chunks.data[ i ].size + 8;
}
}
function fillData( chunks, info ) {
const TableSize = info.numBlocks * 8,
HeaderSize = 259 + ( 18 * info.numOutputChannels ), // 259 + 18 * chlist
offset = { value: HeaderSize + TableSize },
outBuffer = new Uint8Array( HeaderSize + TableSize + chunks.totalSize + info.numBlocks * 8 ),
dv = new DataView( outBuffer.buffer );
fillHeader( outBuffer, chunks, info );
for ( let i = 0; i < chunks.data.length; ++ i ) {
const data = chunks.data[ i ].dataChunk;
const size = chunks.data[ i ].size;
setUint32( dv, i * info.blockLines, offset );
setUint32( dv, size, offset );
outBuffer.set( data, offset.value );
offset.value += size;
}
return outBuffer;
}
function decodeLinear( dec, r, g, b, a ) {
dec.r = r;
dec.g = g;
dec.b = b;
dec.a = a;
}
// function decodeSRGB( dec, r, g, b, a ) {
// dec.r = r > 0.04045 ? Math.pow( r * 0.9478672986 + 0.0521327014, 2.4 ) : r * 0.0773993808;
// dec.g = g > 0.04045 ? Math.pow( g * 0.9478672986 + 0.0521327014, 2.4 ) : g * 0.0773993808;
// dec.b = b > 0.04045 ? Math.pow( b * 0.9478672986 + 0.0521327014, 2.4 ) : b * 0.0773993808;
// dec.a = a;
// }
function setUint8( dv, value, offset ) {
dv.setUint8( offset.value, value );
offset.value += 1;
}
function setUint32( dv, value, offset ) {
dv.setUint32( offset.value, value, true );
offset.value += 4;
}
function setFloat16( dv, value, offset ) {
dv.setUint16( offset.value, DataUtils.toHalfFloat( value ), true );
offset.value += 2;
}
function setFloat32( dv, value, offset ) {
dv.setFloat32( offset.value, value, true );
offset.value += 4;
}
function setUint64( dv, value, offset ) {
dv.setBigUint64( offset.value, BigInt( value ), true );
offset.value += 8;
}
function setString( dv, string, offset ) {
const tmp = textEncoder.encode( string + '\0' );
for ( let i = 0; i < tmp.length; ++ i ) {
setUint8( dv, tmp[ i ], offset );
}
}
function decodeFloat16( binary ) {
const 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 getFloat16( arr, i ) {
return decodeFloat16( arr[ i ] );
}
function getFloat32( arr, i ) {
return arr[ i ];
}
/**
* Export options of `EXRExporter`.
*
* @typedef {Object} EXRExporter~Options
* @property {(HalfFloatType|FloatType)} [type=HalfFloatType] - Output data type.
* @property {(NO_COMPRESSION|RLE_COMPRESSION|ZIP_COMPRESSION|ZIPS_COMPRESSION|PIZ_COMPRESSION)} [compression=ZIP_COMPRESSION] - The compression algorithm.
* @property {number} [textureIndex=0] - When exporting a render target with multiple textures (MRT), the index of the texture to export.
**/
export { EXRExporter, NO_COMPRESSION, RLE_COMPRESSION, ZIP_COMPRESSION, ZIPS_COMPRESSION, PIZ_COMPRESSION };