webp.ts
examples/sogs/webp.ts · raw · open the example · all examples · site home
Exports: PixelsdecodeBitmapreadPixelsdecodePixels
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
// SOG images: WebP bytes into ImageBitmaps (decoded off the main thread),
// and their exact RGBA bytes. A 2D canvas would premultiply alpha (SOG keeps
// data there: the quaternion mode, 252 to 255), so the bytes are read back
// through WebGL2, which uploads an ImageBitmap made without premultiplying
// or color conversion as it is.
export interface Pixels {
rgba: Uint8Array<ArrayBuffer>;
width: number;
height: number;
}
export function decodeBitmap(bytes: Uint8Array<ArrayBuffer>): Promise<ImageBitmap> {
return createImageBitmap(new Blob([bytes], { type: "image/webp" }), { premultiplyAlpha: "none", colorSpaceConversion: "none" });
}
let gl: WebGL2RenderingContext | undefined;
let texture: WebGLTexture | null = null;
let framebuffer: WebGLFramebuffer | null = null;
export function readPixels(bitmap: ImageBitmap): Pixels {
if (!gl) {
const context = new OffscreenCanvas(1, 1).getContext("webgl2");
if (!context) {
throw new Error("Reading SOG images needs WebGL2");
}
gl = context;
texture = gl.createTexture();
framebuffer = gl.createFramebuffer();
}
const { width, height } = bitmap;
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
const rgba = new Uint8Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, rgba);
// Drop the image's storage until the next one
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
return { rgba, width, height };
}
export async function decodePixels(bytes: Uint8Array<ArrayBuffer>): Promise<Pixels> {
const bitmap = await decodeBitmap(bytes);
try {
return readPixels(bitmap);
} finally {
bitmap.close();
}
}