@glissade/backend-canvas2d 0.18.0 → 0.19.0-pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +7 -0
- package/dist/index.js +9 -0
- package/dist/snapshot.d.ts +48 -0
- package/dist/snapshot.js +77 -0
- package/package.json +7 -3
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,13 @@ declare class Canvas2DBackend implements RenderBackend {
|
|
|
28
28
|
measureText(text: string, font: FontSpec): TextMetricsLite$1;
|
|
29
29
|
render(list: DisplayList): void;
|
|
30
30
|
readPixels(): Promise<Uint8ClampedArray>;
|
|
31
|
+
/**
|
|
32
|
+
* The backend's underlying canvas. Exposed so the tree-shakeable
|
|
33
|
+
* `@glissade/backend-canvas2d/snapshot` subpath (`snapshotCanvas`) can read it
|
|
34
|
+
* WITHOUT pulling the data-URL/Blob-encode code onto this base index — a
|
|
35
|
+
* trivial getter that adds no encode bytes here.
|
|
36
|
+
*/
|
|
37
|
+
get canvas(): AnyCanvas;
|
|
31
38
|
dispose(): void;
|
|
32
39
|
private context;
|
|
33
40
|
}
|
package/dist/index.js
CHANGED
|
@@ -72,6 +72,15 @@ var Canvas2DBackend = class {
|
|
|
72
72
|
async readPixels() {
|
|
73
73
|
return this.context(this.target).getImageData(0, 0, this.target.width, this.target.height).data;
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* The backend's underlying canvas. Exposed so the tree-shakeable
|
|
77
|
+
* `@glissade/backend-canvas2d/snapshot` subpath (`snapshotCanvas`) can read it
|
|
78
|
+
* WITHOUT pulling the data-URL/Blob-encode code onto this base index — a
|
|
79
|
+
* trivial getter that adds no encode bytes here.
|
|
80
|
+
*/
|
|
81
|
+
get canvas() {
|
|
82
|
+
return this.target;
|
|
83
|
+
}
|
|
75
84
|
dispose() {
|
|
76
85
|
this.raster.dispose();
|
|
77
86
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Canvas2DBackend } from "./index.js";
|
|
2
|
+
import { Scene } from "@glissade/scene";
|
|
3
|
+
import { Timeline } from "@glissade/core";
|
|
4
|
+
|
|
5
|
+
//#region src/snapshot.d.ts
|
|
6
|
+
|
|
7
|
+
type AnyCanvas = HTMLCanvasElement | OffscreenCanvas;
|
|
8
|
+
/**
|
|
9
|
+
* Capture a canvas (or a `Canvas2DBackend`'s current canvas) as a
|
|
10
|
+
* `data:image/png;base64,…` URL — the DX seam an AI consumer hit ("can't
|
|
11
|
+
* screenshot a live canvas"). Render a frame first (`backend.render(list)`),
|
|
12
|
+
* then `await snapshotCanvas(backend)`.
|
|
13
|
+
*
|
|
14
|
+
* Browser-only: relies on `OffscreenCanvas.convertToBlob` /
|
|
15
|
+
* `HTMLCanvasElement.toDataURL`. Async to match the underlying serialization
|
|
16
|
+
* (OffscreenCanvas is Blob-based, hence Promise-shaped).
|
|
17
|
+
*
|
|
18
|
+
* @param canvasOrBackend a `Canvas2DBackend`, an `HTMLCanvasElement`, or an `OffscreenCanvas`.
|
|
19
|
+
* @param type image MIME type (default `image/png`); `quality` for lossy types.
|
|
20
|
+
*/
|
|
21
|
+
declare function snapshotCanvas(canvasOrBackend: AnyCanvas | Canvas2DBackend, type?: string, quality?: number): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* One-shot DX convenience (0.19): `evaluate(scene, timeline, t)` →
|
|
24
|
+
* `Canvas2DBackend.render` → `snapshotCanvas()`, returning a
|
|
25
|
+
* `data:image/png;base64,…` URL for the frame. The "screenshot a frame" path a
|
|
26
|
+
* host/test reaches for when it can't drive a live `<canvas>` (the AI-consumer
|
|
27
|
+
* wall).
|
|
28
|
+
*
|
|
29
|
+
* Allocates an offscreen target sized to the scene (`OffscreenCanvas`, falling
|
|
30
|
+
* back to a detached `<canvas>`), so the caller needs no DOM canvas. Browser-only
|
|
31
|
+
* (same constraint as `snapshotCanvas()` — Skia/Node has its own encode path).
|
|
32
|
+
*
|
|
33
|
+
* Mirrors the `evaluate` overload pair: pass a timeline + time to sample an
|
|
34
|
+
* animated frame, or omit both for the controlled-drive form (host-owned
|
|
35
|
+
* playhead via `node.set(...)`, evaluated at the scene's current playhead).
|
|
36
|
+
*
|
|
37
|
+
* @param type image MIME type (default `image/png`); `quality` for lossy types.
|
|
38
|
+
*/
|
|
39
|
+
declare function renderToDataURL(scene: Scene, timeline: Timeline, t: number, opts?: {
|
|
40
|
+
type?: string;
|
|
41
|
+
quality?: number;
|
|
42
|
+
}): Promise<string>;
|
|
43
|
+
declare function renderToDataURL(scene: Scene, opts?: {
|
|
44
|
+
type?: string;
|
|
45
|
+
quality?: number;
|
|
46
|
+
}): Promise<string>;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { renderToDataURL, snapshotCanvas };
|
package/dist/snapshot.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Canvas2DBackend } from "./index.js";
|
|
2
|
+
import { evaluate } from "@glissade/scene";
|
|
3
|
+
//#region src/snapshot.ts
|
|
4
|
+
/**
|
|
5
|
+
* @glissade/backend-canvas2d/snapshot — the "screenshot a rendered frame as a
|
|
6
|
+
* data URL" DX seam (the AI-consumer wall: "can't screenshot a live canvas").
|
|
7
|
+
*
|
|
8
|
+
* This is DX / screenshot TOOLING, NOT a render path: a no-build playback embed
|
|
9
|
+
* never needs it. So it lives on this SEPARATE, tree-shakeable subpath — off the
|
|
10
|
+
* `@glissade/backend-canvas2d` base index (which is on the base embed budget),
|
|
11
|
+
* mirroring how `@glissade/scene/path` / `/layout` / `/describe` keep their
|
|
12
|
+
* byte-expensive code off the base scene index. The data-URL/Blob-encode bytes
|
|
13
|
+
* here must never enter the base embed bundle.
|
|
14
|
+
*
|
|
15
|
+
* Browser-only by design: relies on `OffscreenCanvas.convertToBlob` /
|
|
16
|
+
* `HTMLCanvasElement.toDataURL`. The Skia/Node twin has its own
|
|
17
|
+
* `encodePNG`/`toBuffer` path (@napi-rs/canvas). Importing this module in a
|
|
18
|
+
* headless Node env never throws — the browser-only constraint is enforced at
|
|
19
|
+
* call time, never at load.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Encode a `Blob` (from `OffscreenCanvas.convertToBlob`) as a `data:` URL.
|
|
23
|
+
* `arrayBuffer()` + `btoa()` (both Web standards present in browser, worker, and
|
|
24
|
+
* Node) — leaner than wiring a `FileReader` and with no Node `Buffer`
|
|
25
|
+
* dependency, so the bundle stays browser-clean.
|
|
26
|
+
*/
|
|
27
|
+
async function blobToDataURL(blob) {
|
|
28
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
29
|
+
let binary = "";
|
|
30
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
31
|
+
return `data:${blob.type || "image/png"};base64,${btoa(binary)}`;
|
|
32
|
+
}
|
|
33
|
+
/** Pull the underlying canvas out of a `Canvas2DBackend`, or pass a canvas through. */
|
|
34
|
+
function canvasOf(canvasOrBackend) {
|
|
35
|
+
return canvasOrBackend instanceof Canvas2DBackend ? canvasOrBackend.canvas : canvasOrBackend;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Capture a canvas (or a `Canvas2DBackend`'s current canvas) as a
|
|
39
|
+
* `data:image/png;base64,…` URL — the DX seam an AI consumer hit ("can't
|
|
40
|
+
* screenshot a live canvas"). Render a frame first (`backend.render(list)`),
|
|
41
|
+
* then `await snapshotCanvas(backend)`.
|
|
42
|
+
*
|
|
43
|
+
* Browser-only: relies on `OffscreenCanvas.convertToBlob` /
|
|
44
|
+
* `HTMLCanvasElement.toDataURL`. Async to match the underlying serialization
|
|
45
|
+
* (OffscreenCanvas is Blob-based, hence Promise-shaped).
|
|
46
|
+
*
|
|
47
|
+
* @param canvasOrBackend a `Canvas2DBackend`, an `HTMLCanvasElement`, or an `OffscreenCanvas`.
|
|
48
|
+
* @param type image MIME type (default `image/png`); `quality` for lossy types.
|
|
49
|
+
*/
|
|
50
|
+
async function snapshotCanvas(canvasOrBackend, type = "image/png", quality) {
|
|
51
|
+
const canvas = canvasOf(canvasOrBackend);
|
|
52
|
+
if (typeof OffscreenCanvas !== "undefined" && canvas instanceof OffscreenCanvas) return await blobToDataURL(await canvas.convertToBlob({
|
|
53
|
+
type,
|
|
54
|
+
...quality !== void 0 ? { quality } : {}
|
|
55
|
+
}));
|
|
56
|
+
const el = canvas;
|
|
57
|
+
if (typeof el.toDataURL !== "function") throw new Error("snapshotCanvas() is browser-only: canvas has no toDataURL/convertToBlob");
|
|
58
|
+
return quality !== void 0 ? el.toDataURL(type, quality) : el.toDataURL(type);
|
|
59
|
+
}
|
|
60
|
+
async function renderToDataURL(scene, timelineOrOpts, t, opts) {
|
|
61
|
+
const hasTimeline = timelineOrOpts !== void 0 && typeof timelineOrOpts.version === "number";
|
|
62
|
+
const timeline = hasTimeline ? timelineOrOpts : void 0;
|
|
63
|
+
const o = (hasTimeline ? opts : timelineOrOpts) ?? {};
|
|
64
|
+
const { w, h } = scene.size;
|
|
65
|
+
const backend = new Canvas2DBackend(typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(w, h) : Object.assign(document.createElement("canvas"), {
|
|
66
|
+
width: w,
|
|
67
|
+
height: h
|
|
68
|
+
}));
|
|
69
|
+
try {
|
|
70
|
+
backend.render(timeline !== void 0 ? evaluate(scene, timeline, t ?? 0) : evaluate(scene));
|
|
71
|
+
return await snapshotCanvas(backend, o.type, o.quality);
|
|
72
|
+
} finally {
|
|
73
|
+
backend.dispose();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { renderToDataURL, snapshotCanvas };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/backend-canvas2d",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0-pre.1",
|
|
4
4
|
"description": "glissade Canvas 2D render backend: DisplayList -> CanvasRenderingContext2D/OffscreenCanvas.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"engines": {
|
|
@@ -12,14 +12,18 @@
|
|
|
12
12
|
".": {
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./snapshot": {
|
|
17
|
+
"types": "./dist/snapshot.d.ts",
|
|
18
|
+
"default": "./dist/snapshot.js"
|
|
15
19
|
}
|
|
16
20
|
},
|
|
17
21
|
"files": [
|
|
18
22
|
"dist"
|
|
19
23
|
],
|
|
20
24
|
"dependencies": {
|
|
21
|
-
"@glissade/core": "0.
|
|
22
|
-
"@glissade/scene": "0.
|
|
25
|
+
"@glissade/core": "0.19.0-pre.1",
|
|
26
|
+
"@glissade/scene": "0.19.0-pre.1"
|
|
23
27
|
},
|
|
24
28
|
"repository": {
|
|
25
29
|
"type": "git",
|