Skip to content

Commit ef24ed0

Browse files
authored
Use OffscreenCanvas and WebGL2 context to decode webp images (#90)
1 parent ba8083e commit ef24ed0

File tree

3 files changed

+49
-20
lines changed

3 files changed

+49
-20
lines changed

package-lock.json

Lines changed: 0 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@
5858
"spark-internal-rs": "file:rust/spark-internal-rs/pkg"
5959
},
6060
"dependencies": {
61-
"@jsquash/webp": "^1.5.0",
6261
"fflate": "^0.8.2"
6362
},
6463
"keywords": ["3d", "three.js", "gsplats", "3dgs", "gaussian", "splats"]

src/pcsogs.ts

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { decode as decodeWebp } from "@jsquash/webp";
21
import type { PcSogsJson } from "./SplatLoader";
32
import {
43
computeMaxSplats,
@@ -149,9 +148,56 @@ export async function unpackPcSogs(
149148
return { packedArray, numSplats, extra };
150149
}
151150

151+
// WebGL context for reading raw pixel data of WebP images
152+
let offscreenGlContext: WebGL2RenderingContext | null = null;
153+
152154
async function decodeImage(fileBytes: ArrayBuffer) {
153-
const { data: rgba, width, height } = await decodeWebp(fileBytes);
154-
return { rgba, width, height };
155+
if (!offscreenGlContext) {
156+
const canvas = new OffscreenCanvas(1, 1);
157+
offscreenGlContext = canvas.getContext("webgl2");
158+
if (!offscreenGlContext) {
159+
throw new Error("Failed to create WebGL2 context");
160+
}
161+
}
162+
163+
const imageBlob = new Blob([fileBytes]);
164+
const bitmap = await createImageBitmap(imageBlob, {
165+
premultiplyAlpha: "none",
166+
});
167+
168+
const gl = offscreenGlContext;
169+
const texture = gl.createTexture();
170+
gl.bindTexture(gl.TEXTURE_2D, texture);
171+
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
172+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
173+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
174+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
175+
176+
const framebuffer = gl.createFramebuffer();
177+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
178+
gl.framebufferTexture2D(
179+
gl.FRAMEBUFFER,
180+
gl.COLOR_ATTACHMENT0,
181+
gl.TEXTURE_2D,
182+
texture,
183+
0,
184+
);
185+
186+
const data = new Uint8Array(bitmap.width * bitmap.height * 4);
187+
gl.readPixels(
188+
0,
189+
0,
190+
bitmap.width,
191+
bitmap.height,
192+
gl.RGBA,
193+
gl.UNSIGNED_BYTE,
194+
data,
195+
);
196+
197+
gl.deleteTexture(texture);
198+
gl.deleteFramebuffer(framebuffer);
199+
200+
return { rgba: data, width: bitmap.width, height: bitmap.height };
155201
}
156202

157203
async function decodeImageRgba(fileBytes: ArrayBuffer) {

0 commit comments

Comments
 (0)