Skip to content

Commit c6bb49d

Browse files
authored
Use DecompressionStream for decompressing .spz files (#181)
1 parent 2defaf3 commit c6bb49d

3 files changed

Lines changed: 48 additions & 41 deletions

File tree

src/spz.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,28 @@ export class SpzReader {
1818
fileBytes: Uint8Array;
1919
reader: GunzipReader;
2020

21-
version: number;
22-
numSplats: number;
23-
shDegree: number;
24-
fractionalBits: number;
25-
flags: number;
26-
flagAntiAlias: boolean;
27-
reserved: number;
28-
parsed: boolean;
21+
version = -1;
22+
numSplats = 0;
23+
shDegree = 0;
24+
fractionalBits = 0;
25+
flags = 0;
26+
flagAntiAlias = false;
27+
reserved = 0;
28+
headerParsed = false;
29+
parsed = false;
2930

3031
constructor({ fileBytes }: { fileBytes: Uint8Array | ArrayBuffer }) {
3132
this.fileBytes =
3233
fileBytes instanceof ArrayBuffer ? new Uint8Array(fileBytes) : fileBytes;
3334
this.reader = new GunzipReader({ fileBytes: this.fileBytes });
35+
}
36+
37+
async parseHeader() {
38+
if (this.headerParsed) {
39+
throw new Error("SPZ file header already parsed");
40+
}
3441

35-
const header = new DataView(this.reader.read(16).buffer);
42+
const header = new DataView((await this.reader.read(16)).buffer);
3643
if (header.getUint32(0, true) !== 0x5053474e) {
3744
throw new Error("Invalid SPZ file");
3845
}
@@ -47,10 +54,11 @@ export class SpzReader {
4754
this.flags = header.getUint8(14);
4855
this.flagAntiAlias = (this.flags & 0x01) !== 0;
4956
this.reserved = header.getUint8(15);
57+
this.headerParsed = true;
5058
this.parsed = false;
5159
}
5260

53-
parseSplats(
61+
async parseSplats(
5462
centerCallback?: (index: number, x: number, y: number, z: number) => void,
5563
alphaCallback?: (index: number, alpha: number) => void,
5664
rgbCallback?: (index: number, r: number, g: number, b: number) => void,
@@ -74,14 +82,17 @@ export class SpzReader {
7482
sh3?: Float32Array,
7583
) => void,
7684
) {
85+
if (!this.headerParsed) {
86+
throw new Error("SPZ file header must be parsed first");
87+
}
7788
if (this.parsed) {
7889
throw new Error("SPZ file already parsed");
7990
}
8091
this.parsed = true;
8192

8293
if (this.version === 1) {
8394
// float16 centers
84-
const centerBytes = this.reader.read(this.numSplats * 3 * 2);
95+
const centerBytes = await this.reader.read(this.numSplats * 3 * 2);
8596
const centerUint16 = new Uint16Array(centerBytes.buffer);
8697
for (let i = 0; i < this.numSplats; i++) {
8798
const i3 = i * 3;
@@ -93,7 +104,7 @@ export class SpzReader {
93104
} else if (this.version === 2 || this.version === 3) {
94105
// 24-bit fixed-point centers
95106
const fixed = 1 << this.fractionalBits;
96-
const centerBytes = this.reader.read(this.numSplats * 3 * 3);
107+
const centerBytes = await this.reader.read(this.numSplats * 3 * 3);
97108
for (let i = 0; i < this.numSplats; i++) {
98109
const i9 = i * 9;
99110
const x =
@@ -121,13 +132,13 @@ export class SpzReader {
121132
}
122133

123134
{
124-
const bytes = this.reader.read(this.numSplats);
135+
const bytes = await this.reader.read(this.numSplats);
125136
for (let i = 0; i < this.numSplats; i++) {
126137
alphaCallback?.(i, bytes[i] / 255);
127138
}
128139
}
129140
{
130-
const rgbBytes = this.reader.read(this.numSplats * 3);
141+
const rgbBytes = await this.reader.read(this.numSplats * 3);
131142
const scale = SH_C0 / 0.15;
132143
for (let i = 0; i < this.numSplats; i++) {
133144
const i3 = i * 3;
@@ -138,7 +149,7 @@ export class SpzReader {
138149
}
139150
}
140151
{
141-
const scalesBytes = this.reader.read(this.numSplats * 3);
152+
const scalesBytes = await this.reader.read(this.numSplats * 3);
142153
for (let i = 0; i < this.numSplats; i++) {
143154
const i3 = i * 3;
144155
const scaleX = Math.exp(scalesBytes[i3] / 16 - 10);
@@ -160,7 +171,7 @@ export class SpzReader {
160171
// v^2 + v^2 = 1
161172
// v = 1 / sqrt(2);
162173
const maxValue = 1 / Math.sqrt(2); // 0.7071
163-
const quatBytes = this.reader.read(this.numSplats * 4);
174+
const quatBytes = await this.reader.read(this.numSplats * 4);
164175
for (let i = 0; i < this.numSplats; i++) {
165176
const i3 = i * 4;
166177
const quaternion = [0, 0, 0, 0];
@@ -211,7 +222,7 @@ export class SpzReader {
211222
);
212223
}
213224
} else {
214-
const quatBytes = this.reader.read(this.numSplats * 3);
225+
const quatBytes = await this.reader.read(this.numSplats * 3);
215226
for (let i = 0; i < this.numSplats; i++) {
216227
const i3 = i * 3;
217228
const quatX = quatBytes[i3] / 127.5 - 1;
@@ -228,7 +239,7 @@ export class SpzReader {
228239
const sh1 = new Float32Array(3 * 3);
229240
const sh2 = this.shDegree >= 2 ? new Float32Array(5 * 3) : undefined;
230241
const sh3 = this.shDegree >= 3 ? new Float32Array(7 * 3) : undefined;
231-
const shBytes = this.reader.read(
242+
const shBytes = await this.reader.read(
232243
this.numSplats * SH_DEGREE_TO_VECS[this.shDegree] * 3,
233244
);
234245

@@ -592,6 +603,7 @@ export async function transcodeSpz(input: TranscodeSpzInput) {
592603
}
593604
case SplatFileType.SPZ: {
594605
const spz = new SpzReader({ fileBytes: input.fileBytes });
606+
await spz.parseHeader();
595607
const mapping = new Int32Array(spz.numSplats);
596608
mapping.fill(-1);
597609
const centers = new Float32Array(spz.numSplats * 3);

src/utils.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,39 +1344,33 @@ export class GunzipReader {
13441344
fileBytes: Uint8Array;
13451345
chunkBytes: number;
13461346

1347-
offset: number;
13481347
chunks: Uint8Array[];
13491348
totalBytes: number;
1350-
gunzip: Gunzip;
1349+
reader: ReadableStreamDefaultReader;
13511350

13521351
constructor({
13531352
fileBytes,
13541353
chunkBytes = 64 * 1024,
13551354
}: { fileBytes: Uint8Array; chunkBytes?: number }) {
13561355
this.fileBytes = fileBytes;
13571356
this.chunkBytes = chunkBytes;
1358-
this.offset = 0;
13591357
this.chunks = [];
13601358
this.totalBytes = 0;
13611359

1362-
this.gunzip = new Gunzip((chunk, _final) => {
1363-
this.chunks.push(chunk);
1364-
this.totalBytes += chunk.length;
1365-
});
1360+
const ds = new DecompressionStream("gzip");
1361+
const decompressionStream = new Blob([fileBytes]).stream().pipeThrough(ds);
1362+
this.reader = decompressionStream.getReader();
13661363
}
13671364

1368-
read(numBytes: number): Uint8Array {
1369-
while (this.totalBytes < numBytes && this.offset < this.fileBytes.length) {
1370-
const end = Math.min(
1371-
this.offset + this.chunkBytes,
1372-
this.fileBytes.length,
1373-
);
1374-
this.gunzip.push(this.fileBytes.subarray(this.offset, end), false);
1375-
this.offset = end;
1376-
}
1365+
async read(numBytes: number): Promise<Uint8Array> {
1366+
while (this.totalBytes < numBytes) {
1367+
const { value: chunk, done: readerDone } = await this.reader.read();
1368+
if (readerDone) {
1369+
break;
1370+
}
13771371

1378-
if (this.totalBytes < numBytes && this.offset >= this.fileBytes.length) {
1379-
this.gunzip.push(new Uint8Array(0), true);
1372+
this.chunks.push(chunk);
1373+
this.totalBytes += chunk.length;
13801374
}
13811375

13821376
if (this.totalBytes < numBytes) {

src/worker.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ async function onMessage(event: MessageEvent) {
6161
fileBytes: Uint8Array;
6262
splatEncoding: SplatEncoding;
6363
};
64-
const decoded = unpackSpz(fileBytes, splatEncoding);
64+
const decoded = await unpackSpz(fileBytes, splatEncoding);
6565
result = {
6666
id,
6767
numSplats: decoded.numSplats,
@@ -379,21 +379,22 @@ async function unpackPly({
379379
return { packedArray, numSplats, extra };
380380
}
381381

382-
function unpackSpz(
382+
async function unpackSpz(
383383
fileBytes: Uint8Array,
384384
splatEncoding: SplatEncoding,
385-
): {
385+
): Promise<{
386386
packedArray: Uint32Array;
387387
numSplats: number;
388388
extra: Record<string, unknown>;
389-
} {
389+
}> {
390390
const spz = new SpzReader({ fileBytes });
391+
await spz.parseHeader();
391392
const numSplats = spz.numSplats;
392393
const maxSplats = computeMaxSplats(numSplats);
393394
const packedArray = new Uint32Array(maxSplats * 4);
394395
const extra: Record<string, unknown> = {};
395396

396-
spz.parseSplats(
397+
await spz.parseSplats(
397398
(index, x, y, z) => {
398399
setPackedSplatCenter(packedArray, index, x, y, z);
399400
},

0 commit comments

Comments
 (0)