Advance Blender 5.2 web parity through M12-03D
This commit is contained in:
87
web/app/src/assets/AssetPreviewDisplay.ts
Normal file
87
web/app/src/assets/AssetPreviewDisplay.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { planAssetPreviewDecode } from "../../../protocol/asset-preview-decode";
|
||||
import type { AssetPreviewIdentityIR } from "../../../protocol/asset-preview";
|
||||
|
||||
export type AssetPreviewDisplayBackend = "MAIN_THREAD_CANVAS_2D" | "OFFSCREEN_CANVAS_2D";
|
||||
|
||||
export interface AssetPreviewDisplayReceiptIR {
|
||||
schemaVersion: 1;
|
||||
status: "READY";
|
||||
backend: AssetPreviewDisplayBackend;
|
||||
identitySha256: string;
|
||||
contentSha256: string;
|
||||
width: number;
|
||||
height: number;
|
||||
pixelByteLength: number;
|
||||
pixelSha256: string;
|
||||
nonTransparentPixels: number;
|
||||
bitmapClosed: true;
|
||||
}
|
||||
|
||||
export interface AssetPreviewDisplayResultIR {
|
||||
receipt: AssetPreviewDisplayReceiptIR;
|
||||
pixels: Uint8Array;
|
||||
}
|
||||
|
||||
type PreviewCanvas = HTMLCanvasElement | OffscreenCanvas;
|
||||
|
||||
async function sha256Bytes(value: Uint8Array): Promise<string> {
|
||||
const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(value).buffer);
|
||||
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
||||
}
|
||||
|
||||
export async function displayAssetPreview(
|
||||
canvas: PreviewCanvas,
|
||||
backend: AssetPreviewDisplayBackend,
|
||||
identityValue: unknown,
|
||||
encodedBytes: ArrayBuffer,
|
||||
): Promise<AssetPreviewDisplayResultIR> {
|
||||
const plan = await planAssetPreviewDecode(identityValue, encodedBytes);
|
||||
if (typeof createImageBitmap !== "function") throw new Error("CAPABILITY_MISSING: createImageBitmap is unavailable");
|
||||
const identity = identityValue as AssetPreviewIdentityIR;
|
||||
const bitmap = await createImageBitmap(new Blob([encodedBytes], { type: plan.mimeType }), {
|
||||
colorSpaceConversion: "none",
|
||||
premultiplyAlpha: "none",
|
||||
imageOrientation: "none",
|
||||
});
|
||||
try {
|
||||
if (bitmap.width !== plan.width || bitmap.height !== plan.height) {
|
||||
throw new Error("ASSET_PREVIEW_IDENTITY_MISMATCH: decoded preview dimensions changed");
|
||||
}
|
||||
canvas.width = plan.width;
|
||||
canvas.height = plan.height;
|
||||
const context = canvas.getContext("2d", { alpha: true, willReadFrequently: true }) as
|
||||
| CanvasRenderingContext2D
|
||||
| OffscreenCanvasRenderingContext2D
|
||||
| null;
|
||||
if (!context) throw new Error("CAPABILITY_MISSING: preview Canvas2D context is unavailable");
|
||||
context.clearRect(0, 0, plan.width, plan.height);
|
||||
context.imageSmoothingEnabled = false;
|
||||
context.globalCompositeOperation = "copy";
|
||||
context.drawImage(bitmap, 0, 0, plan.width, plan.height);
|
||||
const pixels = new Uint8Array(context.getImageData(0, 0, plan.width, plan.height).data);
|
||||
let nonTransparentPixels = 0;
|
||||
for (let offset = 3; offset < pixels.byteLength; offset += 4) if (pixels[offset] !== 0) nonTransparentPixels++;
|
||||
const receipt: AssetPreviewDisplayReceiptIR = {
|
||||
schemaVersion: 1,
|
||||
status: "READY",
|
||||
backend,
|
||||
identitySha256: identity.identitySha256,
|
||||
contentSha256: identity.content.sha256,
|
||||
width: plan.width,
|
||||
height: plan.height,
|
||||
pixelByteLength: pixels.byteLength,
|
||||
pixelSha256: await sha256Bytes(pixels),
|
||||
nonTransparentPixels,
|
||||
bitmapClosed: true,
|
||||
};
|
||||
if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) {
|
||||
canvas.dataset.assetPreviewStatus = "ready";
|
||||
canvas.dataset.assetPreviewBackend = backend;
|
||||
canvas.dataset.assetPreviewPixelSha256 = receipt.pixelSha256;
|
||||
}
|
||||
return { receipt, pixels };
|
||||
}
|
||||
finally {
|
||||
bitmap.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user