[canvaskit] Fix JSON loading.

This commit is contained in:
Mario Zechner 2024-07-09 14:22:11 +02:00
parent 1b1e358f85
commit e9bcaa7a9b
5 changed files with 253 additions and 155 deletions

View File

@ -34,7 +34,6 @@
canvasElement.width = canvasElement.clientWidth * dpr;
canvasElement.height = canvasElement.clientHeight * dpr;
// Initialize CanvasKit and create a surface from the Canvas element to draw to
const ck = await CanvasKitInit();
const surface = ck.MakeCanvasSurface('foo');

View File

@ -1,30 +1,68 @@
export * from "@esotericsoftware/spine-core";
import { AnimationState, AnimationStateData, AtlasAttachmentLoader, BlendMode, ClippingAttachment, Color, MeshAttachment, NumberArrayLike, Physics, RegionAttachment, Skeleton, SkeletonBinary, SkeletonClipping, SkeletonData, SkeletonJson, Texture, TextureAtlas, TextureFilter, TextureWrap, Utils } from "@esotericsoftware/spine-core";
import { Canvas, Surface, CanvasKit, Image, Paint, Shader, BlendMode as CanvasKitBlendMode } from "canvaskit-wasm";
import {
AnimationState,
AnimationStateData,
AtlasAttachmentLoader,
BlendMode,
ClippingAttachment,
Color,
MeshAttachment,
NumberArrayLike,
Physics,
RegionAttachment,
Skeleton,
SkeletonBinary,
SkeletonClipping,
SkeletonData,
SkeletonJson,
Texture,
TextureAtlas,
TextureFilter,
TextureWrap,
Utils,
} from "@esotericsoftware/spine-core";
import {
Canvas,
Surface,
CanvasKit,
Image,
Paint,
Shader,
BlendMode as CanvasKitBlendMode,
} from "canvaskit-wasm";
Skeleton.yDown = true;
type CanvasKitImage = { shaders: Shader[], paintPerBlendMode: Map<BlendMode, Paint>, image: Image };
type CanvasKitImage = {
shaders: Shader[];
paintPerBlendMode: Map<BlendMode, Paint>;
image: Image;
};
// CanvasKit blend modes for premultiplied alpha
function toCkBlendMode (ck: CanvasKit, blendMode: BlendMode) {
switch (blendMode) {
case BlendMode.Normal: return ck.BlendMode.SrcOver;
case BlendMode.Additive: return ck.BlendMode.Plus;
case BlendMode.Multiply: return ck.BlendMode.SrcOver;
case BlendMode.Screen: return ck.BlendMode.Screen;
default: return ck.BlendMode.SrcOver;
case BlendMode.Normal:
return ck.BlendMode.SrcOver;
case BlendMode.Additive:
return ck.BlendMode.Plus;
case BlendMode.Multiply:
return ck.BlendMode.SrcOver;
case BlendMode.Screen:
return ck.BlendMode.Screen;
default:
return ck.BlendMode.SrcOver;
}
}
function bufferToUtf8String (buffer: any) {
if (typeof Buffer !== 'undefined') {
return buffer.toString('utf-8');
} else if (typeof TextDecoder !== 'undefined') {
return new TextDecoder('utf-8').decode(buffer);
if (typeof Buffer !== "undefined") {
return buffer.toString("utf-8");
} else if (typeof TextDecoder !== "undefined") {
return new TextDecoder("utf-8").decode(buffer);
} else {
throw new Error('Unsupported environment');
throw new Error("Unsupported environment");
}
}
@ -33,11 +71,9 @@ class CanvasKitTexture extends Texture {
return this._image;
}
setFilters(minFilter: TextureFilter, magFilter: TextureFilter): void {
}
setFilters (minFilter: TextureFilter, magFilter: TextureFilter): void { }
setWraps(uWrap: TextureWrap, vWrap: TextureWrap): void {
}
setWraps (uWrap: TextureWrap, vWrap: TextureWrap): void { }
dispose (): void {
const data: CanvasKitImage = this._image;
@ -51,16 +87,30 @@ class CanvasKitTexture extends Texture {
this._image = null;
}
static async fromFile(ck: CanvasKit, path: string, readFile: (path: string) => Promise<any>): Promise<CanvasKitTexture> {
static async fromFile (
ck: CanvasKit,
path: string,
readFile: (path: string) => Promise<any>
): Promise<CanvasKitTexture> {
const imgData = await readFile(path);
if (!imgData) throw new Error(`Could not load image ${path}`);
const image = ck.MakeImageFromEncoded(imgData);
if (!image) throw new Error(`Could not load image ${path}`);
const paintPerBlendMode = new Map<BlendMode, Paint>();
const shaders: Shader[] = [];
for (const blendMode of [BlendMode.Normal, BlendMode.Additive, BlendMode.Multiply, BlendMode.Screen]) {
for (const blendMode of [
BlendMode.Normal,
BlendMode.Additive,
BlendMode.Multiply,
BlendMode.Screen,
]) {
const paint = new ck.Paint();
const shader = image.makeShaderOptions(ck.TileMode.Clamp, ck.TileMode.Clamp, ck.FilterMode.Linear, ck.MipmapMode.Linear);
const shader = image.makeShaderOptions(
ck.TileMode.Clamp,
ck.TileMode.Clamp,
ck.FilterMode.Linear,
ck.MipmapMode.Linear
);
paint.setShader(shader);
paint.setBlendMode(toCkBlendMode(ck, blendMode));
paintPerBlendMode.set(blendMode, paint);
@ -74,12 +124,21 @@ class CanvasKitTexture extends Texture {
* Loads a {@link TextureAtlas} and its atlas page images from the given file path using the `readFile(path: string): Promise<Buffer>` function.
* Throws an `Error` if the file or one of the atlas page images could not be loaded.
*/
export async function loadTextureAtlas(ck: CanvasKit, atlasFile: string, readFile: (path: string) => Promise<Buffer>): Promise<TextureAtlas> {
export async function loadTextureAtlas (
ck: CanvasKit,
atlasFile: string,
readFile: (path: string) => Promise<Buffer>
): Promise<TextureAtlas> {
const atlas = new TextureAtlas(bufferToUtf8String(await readFile(atlasFile)));
const slashIndex = atlasFile.lastIndexOf("/");
const parentDir = slashIndex >= 0 ? atlasFile.substring(0, slashIndex + 1) + "/" : "";
const parentDir =
slashIndex >= 0 ? atlasFile.substring(0, slashIndex + 1) + "/" : "";
for (const page of atlas.pages) {
const texture = await CanvasKitTexture.fromFile(ck, parentDir + page.name, readFile);
const texture = await CanvasKitTexture.fromFile(
ck,
parentDir + page.name,
readFile
);
page.setTexture(texture);
}
return atlas;
@ -89,9 +148,17 @@ export async function loadTextureAtlas(ck: CanvasKit, atlasFile: string, readFil
* Loads a {@link SkeletonData} from the given file path (`.json` or `.skel`) using the `readFile(path: string): Promise<Buffer>` function.
* Attachments will be looked up in the provided atlas.
*/
export async function loadSkeletonData(skeletonFile: string, atlas: TextureAtlas, readFile: (path: string) => Promise<Buffer>): Promise<SkeletonData> {
export async function loadSkeletonData (
skeletonFile: string,
atlas: TextureAtlas,
readFile: (path: string) => Promise<Buffer>,
scale = 1
): Promise<SkeletonData> {
const attachmentLoader = new AtlasAttachmentLoader(atlas);
const loader = skeletonFile.endsWith(".json") ? new SkeletonJson(attachmentLoader) : new SkeletonBinary(attachmentLoader);
const loader = skeletonFile.endsWith(".json")
? new SkeletonJson(attachmentLoader)
: new SkeletonBinary(attachmentLoader);
loader.scale = scale;
let data = await readFile(skeletonFile);
if (skeletonFile.endsWith(".json")) {
data = bufferToUtf8String(data);
@ -113,7 +180,9 @@ export class SkeletonDrawable {
*/
constructor (skeletonData: SkeletonData) {
this.skeleton = new Skeleton(skeletonData);
this.animationState = new AnimationState(new AnimationStateData(skeletonData));
this.animationState = new AnimationState(
new AnimationStateData(skeletonData)
);
}
/**
@ -186,9 +255,19 @@ export class SkeletonRenderer {
attachmentColor = region.color;
} else if (attachment instanceof MeshAttachment) {
let mesh = attachment as MeshAttachment;
positions = positions.length < mesh.worldVerticesLength ? Utils.newFloatArray(mesh.worldVerticesLength) : positions;
positions =
positions.length < mesh.worldVerticesLength
? Utils.newFloatArray(mesh.worldVerticesLength)
: positions;
numVertices = mesh.worldVerticesLength >> 1;
mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, positions, 0, 2);
mesh.computeWorldVertices(
slot,
0,
mesh.worldVerticesLength,
positions,
0,
2
);
triangles = mesh.triangles;
texture = mesh.region?.texture as CanvasKitTexture;
uvs = mesh.uvs as Float32Array;
@ -204,7 +283,12 @@ export class SkeletonRenderer {
if (texture) {
if (clipper.isClipping()) {
clipper.clipTrianglesUnpacked(positions, triangles, triangles.length, uvs);
clipper.clipTrianglesUnpacked(
positions,
triangles,
triangles.length,
uvs
);
positions = clipper.clippedVertices;
uvs = clipper.clippedUVs;
triangles = clipper.clippedTriangles;
@ -217,7 +301,8 @@ export class SkeletonRenderer {
finalColor.b = skeletonColor.b * slotColor.b * attachmentColor.b;
finalColor.a = skeletonColor.a * slotColor.a * attachmentColor.a;
if (colors.length / 4 < numVertices) colors = Utils.newFloatArray(numVertices * 4);
if (colors.length / 4 < numVertices)
colors = Utils.newFloatArray(numVertices * 4);
for (let i = 0, n = numVertices * 4; i < n; i += 4) {
colors[i] = finalColor.r;
colors[i + 1] = finalColor.g;
@ -225,7 +310,10 @@ export class SkeletonRenderer {
colors[i + 3] = finalColor.a;
}
const scaledUvs = this.scratchUVs.length < uvs.length ? Utils.newFloatArray(uvs.length) : this.scratchUVs;
const scaledUvs =
this.scratchUVs.length < uvs.length
? Utils.newFloatArray(uvs.length)
: this.scratchUVs;
const width = texture.getImage().image.width();
const height = texture.getImage().image.height();
for (let i = 0; i < uvs.length; i += 2) {
@ -234,8 +322,19 @@ export class SkeletonRenderer {
}
const blendMode = slot.data.blendMode;
const vertices = this.ck.MakeVertices(this.ck.VertexMode.Triangles, positions, scaledUvs, colors, triangles, false);
canvas.drawVertices(vertices, this.ck.BlendMode.Modulate, texture.getImage().paintPerBlendMode.get(blendMode)!);
const vertices = this.ck.MakeVertices(
this.ck.VertexMode.Triangles,
positions,
scaledUvs,
colors,
triangles,
false
);
canvas.drawVertices(
vertices,
this.ck.BlendMode.Modulate,
texture.getImage().paintPerBlendMode.get(blendMode)!
);
vertices.delete();
}

View File

@ -64,7 +64,7 @@ export class SkeletonBinary {
this.attachmentLoader = attachmentLoader;
}
readSkeletonData (binary: Uint8Array | ArrayBuffer): SkeletonData {
readSkeletonData (binary: Uint8Array | ArrayBuffer): SkeletonData {
let scale = this.scale;
let skeletonData = new SkeletonData();