@motionscript/browser 0.1.0-alpha.0 → 0.1.0-alpha.10

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.
Files changed (181) hide show
  1. package/CHANGELOG.md +187 -0
  2. package/NOTICE +49 -0
  3. package/README.md +96 -24
  4. package/dist/asset-bytes.d.ts +14 -5
  5. package/dist/asset-bytes.d.ts.map +1 -1
  6. package/dist/asset-bytes.js +23 -5
  7. package/dist/asset-bytes.js.map +1 -1
  8. package/dist/audio/bus-graph.d.ts +12 -3
  9. package/dist/audio/bus-graph.d.ts.map +1 -1
  10. package/dist/audio/bus-graph.js +35 -31
  11. package/dist/audio/bus-graph.js.map +1 -1
  12. package/dist/audio/effect-graph.d.ts +62 -0
  13. package/dist/audio/effect-graph.d.ts.map +1 -0
  14. package/dist/audio/effect-graph.js +234 -0
  15. package/dist/audio/effect-graph.js.map +1 -0
  16. package/dist/audio/index.d.ts.map +1 -1
  17. package/dist/audio/index.js +2 -3
  18. package/dist/audio/index.js.map +1 -1
  19. package/dist/audio/loop-region.d.ts +31 -0
  20. package/dist/audio/loop-region.d.ts.map +1 -0
  21. package/dist/audio/loop-region.js +43 -0
  22. package/dist/audio/loop-region.js.map +1 -0
  23. package/dist/audio/mixer.d.ts +4 -4
  24. package/dist/audio/mixer.d.ts.map +1 -1
  25. package/dist/audio/mixer.js +43 -44
  26. package/dist/audio/mixer.js.map +1 -1
  27. package/dist/audio/player.d.ts +15 -9
  28. package/dist/audio/player.d.ts.map +1 -1
  29. package/dist/audio/player.js +60 -69
  30. package/dist/audio/player.js.map +1 -1
  31. package/dist/browser/THIRD_PARTY_LICENSES.txt +784 -0
  32. package/dist/browser/audio.js +1 -1
  33. package/dist/browser/chunks/chunk-F3AZKPHU.js +2 -0
  34. package/dist/browser/chunks/chunk-F3AZKPHU.js.map +7 -0
  35. package/dist/browser/chunks/chunk-KLXMRKTL.js +2 -0
  36. package/dist/browser/chunks/chunk-L5UFQACH.js +163 -0
  37. package/dist/browser/chunks/chunk-L5UFQACH.js.map +7 -0
  38. package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js +14 -0
  39. package/dist/browser/chunks/mediabunny-mp3-encoder-ZCIHVVTJ.js.map +7 -0
  40. package/dist/browser/index.js +52 -1490
  41. package/dist/browser/index.js.map +4 -4
  42. package/dist/browser/manifest.json +21 -4
  43. package/dist/browser/ms_renderer_bg.wasm +0 -0
  44. package/dist/concat.d.ts +33 -7
  45. package/dist/concat.d.ts.map +1 -1
  46. package/dist/concat.js +117 -72
  47. package/dist/concat.js.map +1 -1
  48. package/dist/deliver.d.ts +10 -0
  49. package/dist/deliver.d.ts.map +1 -0
  50. package/dist/deliver.js +22 -0
  51. package/dist/deliver.js.map +1 -0
  52. package/dist/dynamic-nodes.d.ts.map +1 -1
  53. package/dist/dynamic-nodes.js +9 -12
  54. package/dist/dynamic-nodes.js.map +1 -1
  55. package/dist/engine.d.ts +43 -15
  56. package/dist/engine.d.ts.map +1 -1
  57. package/dist/engine.js +58 -36
  58. package/dist/engine.js.map +1 -1
  59. package/dist/export-audio.d.ts +36 -0
  60. package/dist/export-audio.d.ts.map +1 -0
  61. package/dist/export-audio.js +56 -0
  62. package/dist/export-audio.js.map +1 -0
  63. package/dist/exporter.d.ts +134 -15
  64. package/dist/exporter.d.ts.map +1 -1
  65. package/dist/exporter.js +135 -90
  66. package/dist/exporter.js.map +1 -1
  67. package/dist/formats.d.ts +36 -0
  68. package/dist/formats.d.ts.map +1 -0
  69. package/dist/formats.js +83 -0
  70. package/dist/formats.js.map +1 -0
  71. package/dist/frame-loop.d.ts +190 -0
  72. package/dist/frame-loop.d.ts.map +1 -0
  73. package/dist/frame-loop.js +304 -0
  74. package/dist/frame-loop.js.map +1 -0
  75. package/dist/index.d.ts +12 -10
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +18 -47
  78. package/dist/index.js.map +1 -1
  79. package/dist/mix-timeline-audio.d.ts +7 -7
  80. package/dist/mix-timeline-audio.d.ts.map +1 -1
  81. package/dist/mix-timeline-audio.js +11 -14
  82. package/dist/mix-timeline-audio.js.map +1 -1
  83. package/dist/parallel/export-parallel.d.ts +32 -0
  84. package/dist/parallel/export-parallel.d.ts.map +1 -0
  85. package/dist/parallel/export-parallel.js +114 -0
  86. package/dist/parallel/export-parallel.js.map +1 -0
  87. package/dist/parallel/governor.d.ts +57 -0
  88. package/dist/parallel/governor.d.ts.map +1 -0
  89. package/dist/parallel/governor.js +79 -0
  90. package/dist/parallel/governor.js.map +1 -0
  91. package/dist/parallel/lane.d.ts +45 -0
  92. package/dist/parallel/lane.d.ts.map +1 -0
  93. package/dist/parallel/lane.js +2 -0
  94. package/dist/parallel/lane.js.map +1 -0
  95. package/dist/parallel/lanes.d.ts +20 -0
  96. package/dist/parallel/lanes.d.ts.map +1 -0
  97. package/dist/parallel/lanes.js +152 -0
  98. package/dist/parallel/lanes.js.map +1 -0
  99. package/dist/parallel/plan.d.ts +23 -0
  100. package/dist/parallel/plan.d.ts.map +1 -0
  101. package/dist/parallel/plan.js +30 -0
  102. package/dist/parallel/plan.js.map +1 -0
  103. package/dist/parallel/run-lanes.d.ts +38 -0
  104. package/dist/parallel/run-lanes.d.ts.map +1 -0
  105. package/dist/parallel/run-lanes.js +164 -0
  106. package/dist/parallel/run-lanes.js.map +1 -0
  107. package/dist/parallel/session.d.ts +9 -0
  108. package/dist/parallel/session.d.ts.map +1 -0
  109. package/dist/parallel/session.js +79 -0
  110. package/dist/parallel/session.js.map +1 -0
  111. package/dist/renderer.d.ts +22 -0
  112. package/dist/renderer.d.ts.map +1 -0
  113. package/dist/renderer.js +23 -0
  114. package/dist/renderer.js.map +1 -0
  115. package/dist/screenshot.d.ts +6 -6
  116. package/dist/screenshot.d.ts.map +1 -1
  117. package/dist/screenshot.js +4 -5
  118. package/dist/screenshot.js.map +1 -1
  119. package/dist/still.d.ts +32 -24
  120. package/dist/still.d.ts.map +1 -1
  121. package/dist/still.js +54 -54
  122. package/dist/still.js.map +1 -1
  123. package/dist/ticker.d.ts +41 -9
  124. package/dist/ticker.d.ts.map +1 -1
  125. package/dist/ticker.js +113 -41
  126. package/dist/ticker.js.map +1 -1
  127. package/package.json +11 -10
  128. package/dist/audio/filter-graph.d.ts +0 -58
  129. package/dist/audio/filter-graph.d.ts.map +0 -1
  130. package/dist/audio/filter-graph.js +0 -235
  131. package/dist/audio/filter-graph.js.map +0 -1
  132. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js +0 -2
  133. package/dist/browser/chunks/GLTFLoader-PGXD4V5B.js.map +0 -7
  134. package/dist/browser/chunks/SkeletonUtils-XYDMTGTM.js +0 -2
  135. package/dist/browser/chunks/chunk-CTLBXZSU.js +0 -4119
  136. package/dist/browser/chunks/chunk-CTLBXZSU.js.map +0 -7
  137. package/dist/browser/chunks/chunk-FQ32PDIU.js +0 -2
  138. package/dist/browser/chunks/chunk-FQ32PDIU.js.map +0 -7
  139. package/dist/browser/chunks/chunk-FY3BTKY4.js +0 -2
  140. package/dist/browser/chunks/chunk-FY3BTKY4.js.map +0 -7
  141. package/dist/browser/chunks/chunk-NSFLRI4P.js +0 -2
  142. package/dist/browser/chunks/chunk-NSFLRI4P.js.map +0 -7
  143. package/dist/browser/chunks/three.module-Z4EOVUWF.js +0 -2
  144. package/dist/browser/chunks/three.module-Z4EOVUWF.js.map +0 -7
  145. package/dist/decode/image-levels.d.ts +0 -137
  146. package/dist/decode/image-levels.d.ts.map +0 -1
  147. package/dist/decode/image-levels.js +0 -477
  148. package/dist/decode/image-levels.js.map +0 -1
  149. package/dist/decode/levels.d.ts +0 -37
  150. package/dist/decode/levels.d.ts.map +0 -1
  151. package/dist/decode/levels.js +0 -68
  152. package/dist/decode/levels.js.map +0 -1
  153. package/dist/decode/video-levels.d.ts +0 -37
  154. package/dist/decode/video-levels.d.ts.map +0 -1
  155. package/dist/decode/video-levels.js +0 -95
  156. package/dist/decode/video-levels.js.map +0 -1
  157. package/dist/getter.d.ts +0 -10
  158. package/dist/getter.d.ts.map +0 -1
  159. package/dist/getter.js +0 -38
  160. package/dist/getter.js.map +0 -1
  161. package/dist/measure-context.d.ts +0 -12
  162. package/dist/measure-context.d.ts.map +0 -1
  163. package/dist/measure-context.js +0 -12
  164. package/dist/measure-context.js.map +0 -1
  165. package/dist/output.d.ts +0 -7
  166. package/dist/output.d.ts.map +0 -1
  167. package/dist/output.js +0 -6
  168. package/dist/output.js.map +0 -1
  169. package/dist/render-context.d.ts +0 -30
  170. package/dist/render-context.d.ts.map +0 -1
  171. package/dist/render-context.js +0 -55
  172. package/dist/render-context.js.map +0 -1
  173. package/dist/storage-adapter.d.ts +0 -367
  174. package/dist/storage-adapter.d.ts.map +0 -1
  175. package/dist/storage-adapter.js +0 -1278
  176. package/dist/storage-adapter.js.map +0 -1
  177. package/dist/three/renderer.d.ts +0 -48
  178. package/dist/three/renderer.d.ts.map +0 -1
  179. package/dist/three/renderer.js +0 -177
  180. package/dist/three/renderer.js.map +0 -1
  181. /package/dist/browser/chunks/{SkeletonUtils-XYDMTGTM.js.map → chunk-KLXMRKTL.js.map} +0 -0
@@ -0,0 +1,23 @@
1
+ import { createWgpuRenderer, isWgpuSupported } from "@motionscript/renderer";
2
+ import { loadAssetBytes } from "./asset-bytes";
3
+ /**
4
+ * This browser cannot run the WebGPU renderer: no `navigator.gpu`, or no
5
+ * adapter behind it. Every entry that draws (an engine, a still, an export,
6
+ * a timeline's audio mix) rejects with it before drawing anything.
7
+ */
8
+ export class WebGpuUnavailableError extends Error {
9
+ constructor() {
10
+ super("Motion Script draws with WebGPU, and this browser has no WebGPU adapter.");
11
+ this.name = "WebGpuUnavailableError";
12
+ }
13
+ }
14
+ /**
15
+ * The renderer every entry draws with, or {@link WebGpuUnavailableError}.
16
+ * `gpu: false` is for an entry that only measures text, which needs no device.
17
+ */
18
+ export async function loadRenderer(options = {}, { gpu = true } = {}) {
19
+ if (gpu && !(await isWgpuSupported()))
20
+ throw new WebGpuUnavailableError();
21
+ return createWgpuRenderer({ wasmUrl: options.rendererWasmUrl, loadBytes: loadAssetBytes });
22
+ }
23
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAqB,MAAM,wBAAwB,CAAC;AAChG,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAQ/C;;;;GAIG;AACH,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAC7C;QACI,KAAK,CAAC,0EAA0E,CAAC,CAAC;QAClF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACzC,CAAC;CACJ;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAA8B,EAAE,EAAE,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,EAAE;IACpF,IAAI,GAAG,IAAI,CAAC,CAAC,MAAM,eAAe,EAAE,CAAC;QAAE,MAAM,IAAI,sBAAsB,EAAE,CAAC;IAC1E,OAAO,kBAAkB,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;AAC/F,CAAC","sourcesContent":["import { createWgpuRenderer, isWgpuSupported, type WgpuRenderer } from \"@motionscript/renderer\";\nimport { loadAssetBytes } from \"./asset-bytes\";\n\n/** Where the renderer's WebAssembly is served, for every entry that draws. */\nexport interface RendererWasmOption {\n /** URL of `ms_renderer_bg.wasm`; defaults to beside `@motionscript/renderer`'s module, which a bundler resolves. */\n rendererWasmUrl?: string | URL;\n}\n\n/**\n * This browser cannot run the WebGPU renderer: no `navigator.gpu`, or no\n * adapter behind it. Every entry that draws (an engine, a still, an export,\n * a timeline's audio mix) rejects with it before drawing anything.\n */\nexport class WebGpuUnavailableError extends Error {\n constructor() {\n super(\"Motion Script draws with WebGPU, and this browser has no WebGPU adapter.\");\n this.name = \"WebGpuUnavailableError\";\n }\n}\n\n/**\n * The renderer every entry draws with, or {@link WebGpuUnavailableError}.\n * `gpu: false` is for an entry that only measures text, which needs no device.\n */\nexport async function loadRenderer(options: RendererWasmOption = {}, { gpu = true } = {}): Promise<WgpuRenderer> {\n if (gpu && !(await isWgpuSupported())) throw new WebGpuUnavailableError();\n return createWgpuRenderer({ wasmUrl: options.rendererWasmUrl, loadBytes: loadAssetBytes });\n}\n"]}
@@ -1,10 +1,11 @@
1
- import { type AssetManifest, type ProjectDocument, type Registry, type SceneDocument, type Size2D, type Theme } from "@motionscript/core";
2
- import type { FrameSpec, ScreenshotFormat } from "@motionscript/skia-render/export";
1
+ import { type AssetManifest, type FrameSpec, type ProjectDocument, type Registry, type VisualDocument, type Size2D, type Theme } from "@motionscript/core";
2
+ import type { ScreenshotFormat } from "./frame-loop";
3
+ import type { RendererWasmOption } from "./renderer";
3
4
  import { type FrameRef } from "./still";
4
5
  export type { ScreenshotFormat, FrameSpec };
5
- export type ScreenshotParams = {
6
+ export type ScreenshotParams = RendererWasmOption & {
6
7
  /** A project, or a lone still or composition as the one track of one. */
7
- document: ProjectDocument | SceneDocument;
8
+ document: ProjectDocument | VisualDocument;
8
9
  /** The vocabulary its clips compile against. Defaults to what the library ships. */
9
10
  registry?: Registry;
10
11
  /** Win over the document's own; 1920×1080 at 60 fps when neither says. */
@@ -14,7 +15,6 @@ export type ScreenshotParams = {
14
15
  manifest?: AssetManifest;
15
16
  /** Theme for a document that carries none of its own. */
16
17
  theme?: Theme;
17
- wasmUrl?: string;
18
18
  /** Which frame to capture. Defaults to the timeline's first. */
19
19
  frame?: FrameRef;
20
20
  /** Encoding format (default "png"). */
@@ -38,7 +38,7 @@ export type ScreenshotResult = {
38
38
  *
39
39
  * For anything that captures **repeatedly** — a thumbnail builder, a preview that
40
40
  * repaints as the user edits — hold a {@link StillRenderer} instead. This function
41
- * builds and tears down a WebGL surface per call, which is the right trade for one
41
+ * builds and tears down a WebGPU surface per call, which is the right trade for one
42
42
  * capture and the wrong one for a hundred.
43
43
  */
44
44
  export declare function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,aAAa,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EACvG,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEpF,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG;IAC3B,yEAAyE;IACzE,QAAQ,EAAE,eAAe,GAAG,aAAa,CAAC;IAC1C,oFAAoF;IACpF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,uCAAuC;IACvC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,KAAK,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmB1F"}
1
+ {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,KAAK,KAAK,EACxH,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7D,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,CAAC;AAE5C,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG;IAChD,yEAAyE;IACzE,QAAQ,EAAE,eAAe,GAAG,cAAc,CAAC;IAC3C,oFAAoF;IACpF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,yDAAyD;IACzD,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,gEAAgE;IAChE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,uCAAuC;IACvC,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,2BAA2B;IAC3B,KAAK,EAAE,UAAU,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmB1F"}
@@ -1,5 +1,4 @@
1
- import { resolveProjectDocument, } from "@motionscript/core";
2
- import { DEFAULT_OUTPUT } from "./output";
1
+ import { DEFAULT_OUTPUT, resolveProjectDocument, } from "@motionscript/core";
3
2
  import { createStillRenderer } from "./still";
4
3
  /**
5
4
  * Renders a single frame of a document to an image.
@@ -10,16 +9,16 @@ import { createStillRenderer } from "./still";
10
9
  *
11
10
  * For anything that captures **repeatedly** — a thumbnail builder, a preview that
12
11
  * repaints as the user edits — hold a {@link StillRenderer} instead. This function
13
- * builds and tears down a WebGL surface per call, which is the right trade for one
12
+ * builds and tears down a WebGPU surface per call, which is the right trade for one
14
13
  * capture and the wrong one for a hundred.
15
14
  */
16
15
  export async function exportScreenshot(params) {
17
- const { registry, scale, manifest, theme, wasmUrl, frame, format = "png", quality = 0.92, } = params;
16
+ const { registry, scale, manifest, theme, rendererWasmUrl, frame, format = "png", quality = 0.92, } = params;
18
17
  const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);
19
18
  if (project.tracks.length === 0)
20
19
  throw new Error("The project has no tracks to screenshot.");
21
20
  const renderer = await createStillRenderer({
22
- viewport: project.viewport, fps: project.fps, scale, manifest, theme, wasmUrl, registry,
21
+ viewport: project.viewport, fps: project.fps, scale, manifest, theme, rendererWasmUrl, registry,
23
22
  });
24
23
  try {
25
24
  const drawn = await renderer.render(project, { frame });
@@ -1 +1 @@
1
- {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,sBAAsB,GAEzB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAiC7D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAwB;IAC3D,MAAM,EACF,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EACzC,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,OAAO,GAAG,IAAI,GACxC,GAAG,MAAM,CAAC;IAEX,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAE7F,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC;QACvC,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ;KAC1F,CAAC,CAAC;IAEH,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACxD,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC5E,CAAC;YAAS,CAAC;QACP,QAAQ,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;AACL,CAAC","sourcesContent":["import {\n resolveProjectDocument,\n type AssetManifest, type ProjectDocument, type Registry, type SceneDocument, type Size2D, type Theme,\n} from \"@motionscript/core\";\nimport type { FrameSpec, ScreenshotFormat } from \"@motionscript/skia-render/export\";\nimport { DEFAULT_OUTPUT } from \"./output\";\nimport { createStillRenderer, type FrameRef } from \"./still\";\n\nexport type { ScreenshotFormat, FrameSpec };\n\nexport type ScreenshotParams = {\n /** A project, or a lone still or composition as the one track of one. */\n document: ProjectDocument | SceneDocument;\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\n registry?: Registry;\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\n viewport?: Size2D;\n fps?: number;\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n wasmUrl?: string;\n /** Which frame to capture. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Encoding format (default \"png\"). */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1] (ignored for png; default 0.92). */\n quality?: number;\n};\n\nexport type ScreenshotResult = {\n /** The project frame that was captured, after clamping. */\n frame: number;\n totalFrames: number;\n /** Encoded image bytes. */\n bytes: Uint8Array;\n};\n\n/**\n * Renders a single frame of a document to an image.\n *\n * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.\n * It therefore takes the same path as a live preview, and as the video export at\n * that frame — there is one renderer, not three.\n *\n * For anything that captures **repeatedly** — a thumbnail builder, a preview that\n * repaints as the user edits — hold a {@link StillRenderer} instead. This function\n * builds and tears down a WebGL surface per call, which is the right trade for one\n * capture and the wrong one for a hundred.\n */\nexport async function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult> {\n const {\n registry, scale, manifest, theme, wasmUrl,\n frame, format = \"png\", quality = 0.92,\n } = params;\n\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) throw new Error(\"The project has no tracks to screenshot.\");\n\n const renderer = await createStillRenderer({\n viewport: project.viewport, fps: project.fps, scale, manifest, theme, wasmUrl, registry,\n });\n\n try {\n const drawn = await renderer.render(project, { frame });\n return { ...drawn, bytes: await renderer.toBytes({ format, quality }) };\n } finally {\n renderer.dispose();\n }\n}\n"]}
1
+ {"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../src/screenshot.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,cAAc,EAAE,sBAAsB,GAEzC,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,mBAAmB,EAAiB,MAAM,SAAS,CAAC;AAgC7D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAwB;IAC3D,MAAM,EACF,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EACjD,KAAK,EAAE,MAAM,GAAG,KAAK,EAAE,OAAO,GAAG,IAAI,GACxC,GAAG,MAAM,CAAC;IAEX,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAChF,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAE7F,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC;QACvC,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ;KAClG,CAAC,CAAC;IAEH,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACxD,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC5E,CAAC;YAAS,CAAC;QACP,QAAQ,CAAC,OAAO,EAAE,CAAC;IACvB,CAAC;AACL,CAAC","sourcesContent":["import {\n DEFAULT_OUTPUT, resolveProjectDocument,\n type AssetManifest, type FrameSpec, type ProjectDocument, type Registry, type VisualDocument, type Size2D, type Theme,\n} from \"@motionscript/core\";\nimport type { ScreenshotFormat } from \"./frame-loop\";\nimport type { RendererWasmOption } from \"./renderer\";\nimport { createStillRenderer, type FrameRef } from \"./still\";\n\nexport type { ScreenshotFormat, FrameSpec };\n\nexport type ScreenshotParams = RendererWasmOption & {\n /** A project, or a lone still or composition as the one track of one. */\n document: ProjectDocument | VisualDocument;\n /** The vocabulary its clips compile against. Defaults to what the library ships. */\n registry?: Registry;\n /** Win over the document's own; 1920×1080 at 60 fps when neither says. */\n viewport?: Size2D;\n fps?: number;\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n /** Which frame to capture. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Encoding format (default \"png\"). */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1] (ignored for png; default 0.92). */\n quality?: number;\n};\n\nexport type ScreenshotResult = {\n /** The project frame that was captured, after clamping. */\n frame: number;\n totalFrames: number;\n /** Encoded image bytes. */\n bytes: Uint8Array;\n};\n\n/**\n * Renders a single frame of a document to an image.\n *\n * A one-shot wrapper over {@link StillRenderer}: create, render, encode, dispose.\n * It therefore takes the same path as a live preview, and as the video export at\n * that frame — there is one renderer, not three.\n *\n * For anything that captures **repeatedly** — a thumbnail builder, a preview that\n * repaints as the user edits — hold a {@link StillRenderer} instead. This function\n * builds and tears down a WebGPU surface per call, which is the right trade for one\n * capture and the wrong one for a hundred.\n */\nexport async function exportScreenshot(params: ScreenshotParams): Promise<ScreenshotResult> {\n const {\n registry, scale, manifest, theme, rendererWasmUrl,\n frame, format = \"png\", quality = 0.92,\n } = params;\n\n const project = resolveProjectDocument(params.document, params, DEFAULT_OUTPUT);\n if (project.tracks.length === 0) throw new Error(\"The project has no tracks to screenshot.\");\n\n const renderer = await createStillRenderer({\n viewport: project.viewport, fps: project.fps, scale, manifest, theme, rendererWasmUrl, registry,\n });\n\n try {\n const drawn = await renderer.render(project, { frame });\n return { ...drawn, bytes: await renderer.toBytes({ format, quality }) };\n } finally {\n renderer.dispose();\n }\n}\n"]}
package/dist/still.d.ts CHANGED
@@ -1,14 +1,15 @@
1
- import { Registry, type AssetManifest, type ProjectDocument, type SceneDocument, type Size2D, type Theme } from "@motionscript/core";
2
- import { type DrawnFrame, type FrameSpec, type ScreenshotFormat } from "@motionscript/skia-render/export";
3
- import { getCanvasKit } from "./getter";
1
+ import { Registry, type AssetManifest, type FrameSpec, type RenderIssue, type ProjectDocument, type VisualDocument, type Size2D, type Theme } from "@motionscript/core";
2
+ import { type DrawnFrame, type ScreenshotFormat } from "./frame-loop";
3
+ import type { BrowserRenderer } from "./engine";
4
+ import { type RendererWasmOption } from "./renderer";
4
5
  /** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */
5
- export type StillSource = ProjectDocument | SceneDocument;
6
+ export type StillSource = ProjectDocument | VisualDocument;
6
7
  /**
7
8
  * Which frame to draw, in whatever form is most convenient — a global frame
8
9
  * index, the timeline's first or last frame, or a `FrameSpec`.
9
10
  */
10
11
  export type FrameRef = FrameSpec | number | "first" | "last";
11
- export interface StillRendererOptions {
12
+ export interface StillRendererOptions extends RendererWasmOption {
12
13
  /**
13
14
  * The canvas to draw into. Omit and the renderer creates its own hidden one
14
15
  * and removes it on {@link StillRenderer.dispose} — which is what a one-shot
@@ -24,7 +25,6 @@ export interface StillRendererOptions {
24
25
  manifest?: AssetManifest;
25
26
  /** Theme for a document that carries none of its own. */
26
27
  theme?: Theme;
27
- wasmUrl?: string;
28
28
  /**
29
29
  * The vocabulary a document is compiled against.
30
30
  *
@@ -41,11 +41,14 @@ export interface RenderStillOptions {
41
41
  /** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */
42
42
  time?: number;
43
43
  /**
44
- * Draw at this pixel ratio instead of the renderer's. **Resizes the canvas and
45
- * rebuilds the Skia surface**, so keep it off the per-edit path — render the
46
- * preview at its normal scale and pass this only when capturing at a higher one.
44
+ * Draw this call at this pixel ratio instead of the renderer's. **Resizes the
45
+ * canvas and rebuilds the surface**, and the next call without it rebuilds
46
+ * them back, so keep it off the per-edit path — render the preview at its
47
+ * normal scale and pass this only when capturing at a higher one.
47
48
  */
48
49
  scale?: number;
50
+ /** What the renderer drew but not as the document says; a lost GPU device throws `RenderBackendError` instead. */
51
+ onIssue?: (issue: RenderIssue) => void;
49
52
  }
50
53
  export interface EncodeStillOptions {
51
54
  /** Defaults to `"png"`. */
@@ -54,12 +57,12 @@ export interface EncodeStillOptions {
54
57
  quality?: number;
55
58
  }
56
59
  /** Normalize the many ways a caller can name a frame into the engine's own form. */
57
- export declare function toFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec;
60
+ export declare function stillFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec;
58
61
  /**
59
62
  * A long-lived single-frame renderer.
60
63
  *
61
64
  * Where `exportScreenshot` is one frame from a standing start, this keeps the
62
- * expensive things — the CanvasKit module, the WebGL Skia surface, and the
65
+ * expensive things — the renderer module, its GPU surface, and the
63
66
  * storage adapter's decoded-asset cache — alive across calls, so re-rendering
64
67
  * after an edit costs discovering one frame and a draw rather than a new GL
65
68
  * context. That is
@@ -84,24 +87,33 @@ export declare function toFrameSpec(options: RenderStillOptions | undefined, fps
84
87
  * different themes still each draw correctly; but anything else that calls
85
88
  * `setTheme` between a render and a read of the surface will have been
86
89
  * overwritten by the next render.
90
+ *
91
+ * **One surface, one caller at a time.** A render and the encode of its result
92
+ * are two calls, and the next render draws over the surface between them, so
93
+ * callers that share a renderer without knowing of each other must be queued —
94
+ * as `session.image()` queues its calls.
87
95
  */
88
96
  export declare class StillRenderer {
89
97
  readonly canvas: HTMLCanvasElement;
90
98
  private renderContext;
91
99
  private storageAdapter;
92
100
  private catalog;
93
- private readonly canvasKit;
101
+ private readonly backend;
94
102
  /** True when we made the canvas and must clean it up. */
95
103
  private readonly ownsCanvas;
96
104
  private viewportValue;
97
105
  private readonly fpsValue;
98
- private scaleValue;
106
+ private readonly scaleValue;
107
+ /** What the canvas is sized at now, which a call's own `scale` changes. */
108
+ private surfaceScale;
99
109
  private manifest;
100
110
  private theme?;
101
111
  private readonly registry;
102
112
  private disposed;
103
- /** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */
104
- constructor(canvasKit: Awaited<ReturnType<typeof getCanvasKit>>, options: StillRendererOptions);
113
+ /** False until a frame has been drawn onto the current surface; a remount clears it. */
114
+ private drawn;
115
+ /** @internal Use {@link createStillRenderer}, which loads the renderer first. */
116
+ constructor(backend: BrowserRenderer, options: StillRendererOptions);
105
117
  get viewport(): Size2D;
106
118
  get fps(): number;
107
119
  get scale(): number;
@@ -132,20 +144,16 @@ export declare class StillRenderer {
132
144
  toDataURL(options?: EncodeStillOptions): string;
133
145
  dispose(): void;
134
146
  /**
135
- * Read the surface back into a 2D canvas, which is what actually encodes: this
136
- * CanvasKit build ships no wasm image encoders, so the browser's own codecs are
137
- * the only ones available.
147
+ * Read the surface back into a 2D canvas, which is what actually encodes: the
148
+ * renderers ship no image encoders, so the browser's own codecs are the only
149
+ * ones available.
138
150
  */
139
151
  private snapshotToCanvas;
140
152
  private sizeCanvas;
141
- /** Resize the backing store and build a fresh Skia surface over it. */
153
+ /** Resize the backing store and mount a fresh surface over it. */
142
154
  private remount;
143
155
  private assertLive;
144
156
  }
145
- /**
146
- * Create a {@link StillRenderer}, loading CanvasKit if it isn't already.
147
- *
148
- * `getCanvasKit` memoizes, so a second renderer in the same page is cheap.
149
- */
157
+ /** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */
150
158
  export declare function createStillRenderer(options?: StillRendererOptions): Promise<StillRenderer>;
151
159
  //# sourceMappingURL=still.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"still.d.ts","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"still.d.ts","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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"}
package/dist/still.js CHANGED
@@ -1,15 +1,11 @@
1
- import { ManifestAssetCatalog, Registry, resolveProjectDocument, setTheme, } from "@motionscript/core";
2
- import { drawFrameAt, } from "@motionscript/skia-render/export";
3
- import { WebRenderContext } from "./render-context";
4
- import { WebStorageAdapter } from "./storage-adapter";
5
- import { getCanvasKit } from "./getter";
6
- import { DEFAULT_OUTPUT } from "./output";
1
+ import { DEFAULT_OUTPUT, ManifestAssetCatalog, frameSpecAtTime, Registry, resolveProjectDocument, } from "@motionscript/core";
2
+ import { drawFrameAt, } from "./frame-loop";
3
+ import { loadRenderer } from "./renderer";
7
4
  const EMPTY_MANIFEST = { image: {}, video: {}, audio: {}, font: {} };
8
5
  /** Normalize the many ways a caller can name a frame into the engine's own form. */
9
- export function toFrameSpec(options, fps) {
10
- if (options?.time !== undefined) {
11
- return { kind: "frame", frame: Math.round(options.time * fps) };
12
- }
6
+ export function stillFrameSpec(options, fps) {
7
+ if (options?.time !== undefined)
8
+ return frameSpecAtTime(options.time, fps);
13
9
  const frame = options?.frame;
14
10
  if (frame === undefined || frame === "first")
15
11
  return { kind: "first" };
@@ -26,7 +22,7 @@ function mimeOf(format) {
26
22
  * A long-lived single-frame renderer.
27
23
  *
28
24
  * Where `exportScreenshot` is one frame from a standing start, this keeps the
29
- * expensive things — the CanvasKit module, the WebGL Skia surface, and the
25
+ * expensive things — the renderer module, its GPU surface, and the
30
26
  * storage adapter's decoded-asset cache — alive across calls, so re-rendering
31
27
  * after an edit costs discovering one frame and a draw rather than a new GL
32
28
  * context. That is
@@ -51,28 +47,38 @@ function mimeOf(format) {
51
47
  * different themes still each draw correctly; but anything else that calls
52
48
  * `setTheme` between a render and a read of the surface will have been
53
49
  * overwritten by the next render.
50
+ *
51
+ * **One surface, one caller at a time.** A render and the encode of its result
52
+ * are two calls, and the next render draws over the surface between them, so
53
+ * callers that share a renderer without knowing of each other must be queued —
54
+ * as `session.image()` queues its calls.
54
55
  */
55
56
  export class StillRenderer {
56
57
  canvas;
57
58
  renderContext;
58
59
  storageAdapter;
59
60
  catalog;
60
- canvasKit;
61
+ backend;
61
62
  /** True when we made the canvas and must clean it up. */
62
63
  ownsCanvas;
63
64
  viewportValue;
64
65
  fpsValue;
65
66
  scaleValue;
67
+ /** What the canvas is sized at now, which a call's own `scale` changes. */
68
+ surfaceScale;
66
69
  manifest;
67
70
  theme;
68
71
  registry;
69
72
  disposed = false;
70
- /** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */
71
- constructor(canvasKit, options) {
72
- this.canvasKit = canvasKit;
73
+ /** False until a frame has been drawn onto the current surface; a remount clears it. */
74
+ drawn = false;
75
+ /** @internal Use {@link createStillRenderer}, which loads the renderer first. */
76
+ constructor(backend, options) {
77
+ this.backend = backend;
73
78
  this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;
74
79
  this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;
75
80
  this.scaleValue = options.scale ?? 1;
81
+ this.surfaceScale = this.scaleValue;
76
82
  this.manifest = options.manifest ?? EMPTY_MANIFEST;
77
83
  this.theme = options.theme;
78
84
  this.registry = options.registry ?? new Registry();
@@ -84,8 +90,10 @@ export class StillRenderer {
84
90
  }
85
91
  this.sizeCanvas();
86
92
  this.catalog = new ManifestAssetCatalog(this.manifest);
87
- this.storageAdapter = new WebStorageAdapter(canvasKit, this.catalog, this.viewportValue, this.fpsValue);
88
- this.renderContext = new WebRenderContext(canvasKit, this.storageAdapter);
93
+ this.storageAdapter = this.backend.createStorageAdapter({
94
+ catalog: this.catalog, viewport: this.viewportValue, fps: this.fpsValue,
95
+ });
96
+ this.renderContext = this.backend.createRenderContext(this.storageAdapter);
89
97
  this.renderContext.mount(this.canvas);
90
98
  }
91
99
  get viewport() { return this.viewportValue; }
@@ -98,24 +106,23 @@ export class StillRenderer {
98
106
  async render(source, options) {
99
107
  this.assertLive();
100
108
  const scale = options?.scale ?? this.scaleValue;
101
- if (scale !== this.scaleValue) {
102
- this.scaleValue = scale;
103
- this.remount();
104
- }
109
+ if (scale !== this.surfaceScale)
110
+ this.remount(scale);
111
+ await this.renderContext.whenMounted?.();
105
112
  const project = resolveProjectDocument(source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue });
106
- // Re-applied per render, not once at construction: it is a global
107
- // registry, so a second renderer (or the player) may have replaced it
108
- // since. Cheap — a map swap.
109
- setTheme(project.theme ?? this.theme);
110
- return drawFrameAt({
113
+ const frame = await drawFrameAt({
111
114
  project,
112
115
  registry: this.registry,
116
+ theme: this.theme,
113
117
  scale,
114
118
  renderContext: this.renderContext,
115
119
  storageAdapter: this.storageAdapter,
116
120
  assetCatalog: this.catalog,
117
- frame: toFrameSpec(options, project.fps),
121
+ frame: stillFrameSpec(options, project.fps),
122
+ onIssue: options?.onIssue,
118
123
  });
124
+ this.drawn = true;
125
+ return frame;
119
126
  }
120
127
  /** Theme for a document that carries none of its own. */
121
128
  setTheme(theme) { this.theme = theme; }
@@ -144,9 +151,11 @@ export class StillRenderer {
144
151
  // Disposing the context disposes the adapter it holds, so this releases
145
152
  // the old surface, its handlers and every decode in one call.
146
153
  this.renderContext.dispose();
147
- this.storageAdapter = new WebStorageAdapter(this.canvasKit, this.catalog, viewport, this.fpsValue);
148
- this.renderContext = new WebRenderContext(this.canvasKit, this.storageAdapter);
149
- this.remount();
154
+ this.storageAdapter = this.backend.createStorageAdapter({
155
+ catalog: this.catalog, viewport, fps: this.fpsValue,
156
+ });
157
+ this.renderContext = this.backend.createRenderContext(this.storageAdapter);
158
+ this.remount(this.scaleValue);
150
159
  }
151
160
  /** Encode the frame currently on the surface. */
152
161
  async toBytes(options) {
@@ -177,31 +186,27 @@ export class StillRenderer {
177
186
  this.canvas.remove();
178
187
  }
179
188
  /**
180
- * Read the surface back into a 2D canvas, which is what actually encodes: this
181
- * CanvasKit build ships no wasm image encoders, so the browser's own codecs are
182
- * the only ones available.
189
+ * Read the surface back into a 2D canvas, which is what actually encodes: the
190
+ * renderers ship no image encoders, so the browser's own codecs are the only
191
+ * ones available.
183
192
  */
184
193
  snapshotToCanvas() {
185
194
  this.assertLive();
186
- const snapshot = this.renderContext.snapshotPixels();
187
- if (!snapshot)
195
+ if (!this.drawn)
188
196
  throw new Error("Nothing has been rendered yet.");
189
- const canvas = document.createElement("canvas");
190
- canvas.width = snapshot.width;
191
- canvas.height = snapshot.height;
192
- const ctx = canvas.getContext("2d");
193
- if (!ctx)
194
- throw new Error("Still encode: could not get a 2d context.");
195
- // `pixels` is unpremultiplied RGBA8888, which is exactly ImageData's layout.
196
- ctx.putImageData(new ImageData(new Uint8ClampedArray(snapshot.pixels), snapshot.width, snapshot.height), 0, 0);
197
+ const canvas = this.renderContext.snapshotCanvas?.();
198
+ if (!canvas)
199
+ throw new Error("Still encode: could not read the surface into a 2d canvas.");
197
200
  return canvas;
198
201
  }
199
202
  sizeCanvas() {
200
- this.canvas.width = this.viewportValue.width * this.scaleValue;
201
- this.canvas.height = this.viewportValue.height * this.scaleValue;
203
+ this.canvas.width = this.viewportValue.width * this.surfaceScale;
204
+ this.canvas.height = this.viewportValue.height * this.surfaceScale;
202
205
  }
203
- /** Resize the backing store and build a fresh Skia surface over it. */
204
- remount() {
206
+ /** Resize the backing store and mount a fresh surface over it. */
207
+ remount(scale) {
208
+ this.surfaceScale = scale;
209
+ this.drawn = false;
205
210
  this.sizeCanvas();
206
211
  this.renderContext.mount(this.canvas);
207
212
  }
@@ -210,13 +215,8 @@ export class StillRenderer {
210
215
  throw new Error("This StillRenderer has been disposed.");
211
216
  }
212
217
  }
213
- /**
214
- * Create a {@link StillRenderer}, loading CanvasKit if it isn't already.
215
- *
216
- * `getCanvasKit` memoizes, so a second renderer in the same page is cheap.
217
- */
218
+ /** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */
218
219
  export async function createStillRenderer(options = {}) {
219
- const canvasKit = await getCanvasKit(options.wasmUrl);
220
- return new StillRenderer(canvasKit, options);
220
+ return new StillRenderer(await loadRenderer(options), options);
221
221
  }
222
222
  //# sourceMappingURL=still.js.map
package/dist/still.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"still.js","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AssetCatalog, ManifestAssetCatalog,\n Registry,\n resolveProjectDocument,\n setTheme,\n type AssetManifest,\n type ProjectDocument,\n type SceneDocument,\n type Size2D,\n type Theme,\n} from \"@motionscript/core\";\nimport {\n drawFrameAt,\n type DrawnFrame,\n type FrameSpec,\n type ScreenshotFormat,\n} from \"@motionscript/skia-render/export\";\nimport { WebRenderContext } from \"./render-context\";\nimport { WebStorageAdapter } from \"./storage-adapter\";\nimport { getCanvasKit } from \"./getter\";\nimport { DEFAULT_OUTPUT } from \"./output\";\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\n\n/** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */\nexport type StillSource = ProjectDocument | SceneDocument;\n\n/**\n * Which frame to draw, in whatever form is most convenient — a global frame\n * index, the timeline's first or last frame, or a `FrameSpec`.\n */\nexport type FrameRef = FrameSpec | number | \"first\" | \"last\";\n\nexport interface StillRendererOptions {\n /**\n * The canvas to draw into. Omit and the renderer creates its own hidden one\n * and removes it on {@link StillRenderer.dispose} — which is what a one-shot\n * capture wants, while a live preview passes the canvas it already shows.\n */\n canvas?: HTMLCanvasElement;\n /** Output resolution in pixels, whatever the document asks for. Defaults to 1920×1080. */\n viewport?: Size2D;\n /** Frame rate for a document that sets none. Defaults to 60. */\n fps?: number;\n /** Device pixel ratio the surface is sized at. Defaults to 1. */\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n wasmUrl?: string;\n /**\n * The vocabulary a document is compiled against.\n *\n * Defaults to a fresh one carrying only what the library ships — a renderer\n * owns its registry the way an engine does, so a host with custom node types\n * passes the same one it gave its engine rather than relying on whatever a\n * module happened to register.\n */\n registry?: Registry;\n}\n\nexport interface RenderStillOptions {\n /** Which frame to draw. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */\n time?: number;\n /**\n * Draw at this pixel ratio instead of the renderer's. **Resizes the canvas and\n * rebuilds the Skia surface**, so keep it off the per-edit path — render the\n * preview at its normal scale and pass this only when capturing at a higher one.\n */\n scale?: number;\n}\n\nexport interface EncodeStillOptions {\n /** Defaults to `\"png\"`. */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1]; ignored for png. Defaults to 0.92. */\n quality?: number;\n}\n\n/** Normalize the many ways a caller can name a frame into the engine's own form. */\nexport function toFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec {\n if (options?.time !== undefined) {\n return { kind: \"frame\", frame: Math.round(options.time * fps) };\n }\n const frame = options?.frame;\n if (frame === undefined || frame === \"first\") return { kind: \"first\" };\n if (frame === \"last\") return { kind: \"last\" };\n if (typeof frame === \"number\") return { kind: \"frame\", frame };\n return frame;\n}\n\nfunction mimeOf(format: ScreenshotFormat): string {\n return format === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\n}\n\n/**\n * A long-lived single-frame renderer.\n *\n * Where `exportScreenshot` is one frame from a standing start, this keeps the\n * expensive things — the CanvasKit module, the WebGL Skia surface, and the\n * storage adapter's decoded-asset cache — alive across calls, so re-rendering\n * after an edit costs discovering one frame and a draw rather than a new GL\n * context. That is\n * the difference between a one-off capture and a builder that repaints while the\n * user types.\n *\n * It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is\n * the pass the video exporter runs per frame. A preview and its exported image\n * cannot drift apart, because there is only one path.\n *\n * ```ts\n * const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });\n * await renderer.render({ kind: \"still\", nodes: [\n * { id: \"bg\", type: \"rect\", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: \"bg\" } },\n * ] });\n * const blob = await renderer.toBlob({ format: \"png\" });\n * renderer.dispose();\n * ```\n *\n * **The theme is process-global in core.** {@link render} re-applies the\n * document's (or this renderer's) before every draw, so two renderers with\n * different themes still each draw correctly; but anything else that calls\n * `setTheme` between a render and a read of the surface will have been\n * overwritten by the next render.\n */\nexport class StillRenderer {\n readonly canvas: HTMLCanvasElement;\n\n private renderContext: WebRenderContext;\n private storageAdapter: WebStorageAdapter;\n private catalog: AssetCatalog;\n\n private readonly canvasKit: Awaited<ReturnType<typeof getCanvasKit>>;\n /** True when we made the canvas and must clean it up. */\n private readonly ownsCanvas: boolean;\n\n private viewportValue: Size2D;\n private readonly fpsValue: number;\n private scaleValue: number;\n private manifest: AssetManifest;\n private theme?: Theme;\n private readonly registry: Registry;\n private disposed = false;\n\n /** @internal Use {@link createStillRenderer}, which loads CanvasKit first. */\n constructor(\n canvasKit: Awaited<ReturnType<typeof getCanvasKit>>,\n options: StillRendererOptions,\n ) {\n this.canvasKit = canvasKit;\n this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;\n this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;\n this.scaleValue = options.scale ?? 1;\n this.manifest = options.manifest ?? EMPTY_MANIFEST;\n this.theme = options.theme;\n this.registry = options.registry ?? new Registry();\n\n this.ownsCanvas = options.canvas === undefined;\n this.canvas = options.canvas ?? document.createElement(\"canvas\");\n if (this.ownsCanvas) {\n this.canvas.style.display = \"none\";\n document.body.appendChild(this.canvas);\n }\n this.sizeCanvas();\n\n this.catalog = new ManifestAssetCatalog(this.manifest);\n this.storageAdapter = new WebStorageAdapter(\n canvasKit, this.catalog, this.viewportValue, this.fpsValue,\n );\n this.renderContext = new WebRenderContext(canvasKit, this.storageAdapter);\n this.renderContext.mount(this.canvas);\n }\n\n get viewport(): Size2D { return this.viewportValue; }\n get fps(): number { return this.fpsValue; }\n get scale(): number { return this.scaleValue; }\n\n /**\n * Draw one frame of `source`, at this renderer's viewport — the surface is\n * the output, so it wins over any size the document asks for.\n */\n async render(source: StillSource, options?: RenderStillOptions): Promise<DrawnFrame> {\n this.assertLive();\n const scale = options?.scale ?? this.scaleValue;\n if (scale !== this.scaleValue) {\n this.scaleValue = scale;\n this.remount();\n }\n\n const project = resolveProjectDocument(\n source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue },\n );\n // Re-applied per render, not once at construction: it is a global\n // registry, so a second renderer (or the player) may have replaced it\n // since. Cheap — a map swap.\n setTheme(project.theme ?? this.theme);\n\n return drawFrameAt({\n project,\n registry: this.registry,\n scale,\n renderContext: this.renderContext,\n storageAdapter: this.storageAdapter,\n assetCatalog: this.catalog,\n frame: toFrameSpec(options, project.fps!),\n });\n }\n\n /** Theme for a document that carries none of its own. */\n setTheme(theme?: Theme): void { this.theme = theme; }\n\n /** Swap the asset manifest, keeping every decode already cached. */\n setManifest(manifest: AssetManifest): void {\n this.assertLive();\n this.manifest = manifest;\n this.catalog = new ManifestAssetCatalog(manifest);\n this.storageAdapter.setCatalog(this.catalog);\n }\n\n /**\n * Change the output resolution.\n *\n * A full reset, unlike {@link setManifest}: the viewport clamps the resolution\n * images are decoded *at*, so cached decodes made for the old one would be\n * soft at the new one, and the render context's handlers captured the adapter\n * when they were built — so both are rebuilt. Call this when the output format\n * changes, not per edit.\n */\n setViewport(viewport: Size2D): void {\n this.assertLive();\n if (viewport.width === this.viewportValue.width && viewport.height === this.viewportValue.height) {\n return;\n }\n this.viewportValue = viewport;\n // Disposing the context disposes the adapter it holds, so this releases\n // the old surface, its handlers and every decode in one call.\n this.renderContext.dispose();\n this.storageAdapter = new WebStorageAdapter(\n this.canvasKit, this.catalog, viewport, this.fpsValue,\n );\n this.renderContext = new WebRenderContext(this.canvasKit, this.storageAdapter);\n this.remount();\n }\n\n /** Encode the frame currently on the surface. */\n async toBytes(options?: EncodeStillOptions): Promise<Uint8Array> {\n const blob = await this.toBlob(options);\n return new Uint8Array(await blob.arrayBuffer());\n }\n\n /** Encode the frame currently on the surface. */\n toBlob(options?: EncodeStillOptions): Promise<Blob> {\n const canvas = this.snapshotToCanvas();\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => blob\n ? resolve(blob)\n : reject(new Error(\"Still encode produced no data.\")),\n mimeOf(format),\n quality,\n );\n });\n }\n\n /** Encode the frame currently on the surface. */\n toDataURL(options?: EncodeStillOptions): string {\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return this.snapshotToCanvas().toDataURL(mimeOf(format), quality);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.renderContext.dispose();\n if (this.ownsCanvas) this.canvas.remove();\n }\n\n /**\n * Read the surface back into a 2D canvas, which is what actually encodes: this\n * CanvasKit build ships no wasm image encoders, so the browser's own codecs are\n * the only ones available.\n */\n private snapshotToCanvas(): HTMLCanvasElement {\n this.assertLive();\n const snapshot = this.renderContext.snapshotPixels();\n if (!snapshot) throw new Error(\"Nothing has been rendered yet.\");\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = snapshot.width;\n canvas.height = snapshot.height;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) throw new Error(\"Still encode: could not get a 2d context.\");\n // `pixels` is unpremultiplied RGBA8888, which is exactly ImageData's layout.\n ctx.putImageData(\n new ImageData(new Uint8ClampedArray(snapshot.pixels), snapshot.width, snapshot.height),\n 0, 0,\n );\n return canvas;\n }\n\n private sizeCanvas(): void {\n this.canvas.width = this.viewportValue.width * this.scaleValue;\n this.canvas.height = this.viewportValue.height * this.scaleValue;\n }\n\n /** Resize the backing store and build a fresh Skia surface over it. */\n private remount(): void {\n this.sizeCanvas();\n this.renderContext.mount(this.canvas);\n }\n\n private assertLive(): void {\n if (this.disposed) throw new Error(\"This StillRenderer has been disposed.\");\n }\n}\n\n/**\n * Create a {@link StillRenderer}, loading CanvasKit if it isn't already.\n *\n * `getCanvasKit` memoizes, so a second renderer in the same page is cheap.\n */\nexport async function createStillRenderer(\n options: StillRendererOptions = {},\n): Promise<StillRenderer> {\n const canvasKit = await getCanvasKit(options.wasmUrl);\n return new StillRenderer(canvasKit, options);\n}\n"]}
1
+ {"version":3,"file":"still.js","sourceRoot":"","sources":["../src/still.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AssetCatalog, DEFAULT_OUTPUT, ManifestAssetCatalog,\n frameSpecAtTime,\n Registry,\n resolveProjectDocument,\n type AssetManifest,\n type FrameSpec,\n type RenderIssue,\n type ProjectDocument,\n type VisualDocument,\n type Size2D,\n type StorageAdapter,\n type Theme,\n} from \"@motionscript/core\";\nimport {\n drawFrameAt,\n type DrawnFrame,\n type ScreenshotFormat,\n} from \"./frame-loop\";\nimport type { BrowserRenderContext, BrowserRenderer } from \"./engine\";\nimport { loadRenderer, type RendererWasmOption } from \"./renderer\";\n\nconst EMPTY_MANIFEST: AssetManifest = { image: {}, video: {}, audio: {}, font: {} };\n\n/** Anything a still can be rendered from: a project, or a lone still or composition as the one track of one. */\nexport type StillSource = ProjectDocument | VisualDocument;\n\n/**\n * Which frame to draw, in whatever form is most convenient — a global frame\n * index, the timeline's first or last frame, or a `FrameSpec`.\n */\nexport type FrameRef = FrameSpec | number | \"first\" | \"last\";\n\nexport interface StillRendererOptions extends RendererWasmOption {\n /**\n * The canvas to draw into. Omit and the renderer creates its own hidden one\n * and removes it on {@link StillRenderer.dispose} — which is what a one-shot\n * capture wants, while a live preview passes the canvas it already shows.\n */\n canvas?: HTMLCanvasElement;\n /** Output resolution in pixels, whatever the document asks for. Defaults to 1920×1080. */\n viewport?: Size2D;\n /** Frame rate for a document that sets none. Defaults to 60. */\n fps?: number;\n /** Device pixel ratio the surface is sized at. Defaults to 1. */\n scale?: number;\n manifest?: AssetManifest;\n /** Theme for a document that carries none of its own. */\n theme?: Theme;\n /**\n * The vocabulary a document is compiled against.\n *\n * Defaults to a fresh one carrying only what the library ships — a renderer\n * owns its registry the way an engine does, so a host with custom node types\n * passes the same one it gave its engine rather than relying on whatever a\n * module happened to register.\n */\n registry?: Registry;\n}\n\nexport interface RenderStillOptions {\n /** Which frame to draw. Defaults to the timeline's first. */\n frame?: FrameRef;\n /** Seconds into the timeline, converted with the document's fps. Wins over `frame`. */\n time?: number;\n /**\n * Draw this call at this pixel ratio instead of the renderer's. **Resizes the\n * canvas and rebuilds the surface**, and the next call without it rebuilds\n * them back, so keep it off the per-edit path — render the preview at its\n * normal scale and pass this only when capturing at a higher one.\n */\n scale?: number;\n /** What the renderer drew but not as the document says; a lost GPU device throws `RenderBackendError` instead. */\n onIssue?: (issue: RenderIssue) => void;\n}\n\nexport interface EncodeStillOptions {\n /** Defaults to `\"png\"`. */\n format?: ScreenshotFormat;\n /** JPEG quality in [0,1]; ignored for png. Defaults to 0.92. */\n quality?: number;\n}\n\n/** Normalize the many ways a caller can name a frame into the engine's own form. */\nexport function stillFrameSpec(options: RenderStillOptions | undefined, fps: number): FrameSpec {\n if (options?.time !== undefined) return frameSpecAtTime(options.time, fps);\n const frame = options?.frame;\n if (frame === undefined || frame === \"first\") return { kind: \"first\" };\n if (frame === \"last\") return { kind: \"last\" };\n if (typeof frame === \"number\") return { kind: \"frame\", frame };\n return frame;\n}\n\nfunction mimeOf(format: ScreenshotFormat): string {\n return format === \"jpeg\" ? \"image/jpeg\" : \"image/png\";\n}\n\n/**\n * A long-lived single-frame renderer.\n *\n * Where `exportScreenshot` is one frame from a standing start, this keeps the\n * expensive things — the renderer module, its GPU surface, and the\n * storage adapter's decoded-asset cache — alive across calls, so re-rendering\n * after an edit costs discovering one frame and a draw rather than a new GL\n * context. That is\n * the difference between a one-off capture and a builder that repaints while the\n * user types.\n *\n * It is also the *same* draw: {@link render} goes through `drawFrameAt`, which is\n * the pass the video exporter runs per frame. A preview and its exported image\n * cannot drift apart, because there is only one path.\n *\n * ```ts\n * const renderer = await createStillRenderer({ canvas, viewport, manifest, theme });\n * await renderer.render({ kind: \"still\", nodes: [\n * { id: \"bg\", type: \"rect\", parentId: null, layerOrder: 0, props: { width: 1920, height: 1080, fill: \"bg\" } },\n * ] });\n * const blob = await renderer.toBlob({ format: \"png\" });\n * renderer.dispose();\n * ```\n *\n * **The theme is process-global in core.** {@link render} re-applies the\n * document's (or this renderer's) before every draw, so two renderers with\n * different themes still each draw correctly; but anything else that calls\n * `setTheme` between a render and a read of the surface will have been\n * overwritten by the next render.\n *\n * **One surface, one caller at a time.** A render and the encode of its result\n * are two calls, and the next render draws over the surface between them, so\n * callers that share a renderer without knowing of each other must be queued —\n * as `session.image()` queues its calls.\n */\nexport class StillRenderer {\n readonly canvas: HTMLCanvasElement;\n\n private renderContext: BrowserRenderContext;\n private storageAdapter: StorageAdapter;\n private catalog: AssetCatalog;\n\n private readonly backend: BrowserRenderer;\n /** True when we made the canvas and must clean it up. */\n private readonly ownsCanvas: boolean;\n\n private viewportValue: Size2D;\n private readonly fpsValue: number;\n private readonly scaleValue: number;\n /** What the canvas is sized at now, which a call's own `scale` changes. */\n private surfaceScale: number;\n private manifest: AssetManifest;\n private theme?: Theme;\n private readonly registry: Registry;\n private disposed = false;\n /** False until a frame has been drawn onto the current surface; a remount clears it. */\n private drawn = false;\n\n /** @internal Use {@link createStillRenderer}, which loads the renderer first. */\n constructor(\n backend: BrowserRenderer,\n options: StillRendererOptions,\n ) {\n this.backend = backend;\n this.viewportValue = options.viewport ?? DEFAULT_OUTPUT.viewport;\n this.fpsValue = options.fps ?? DEFAULT_OUTPUT.fps;\n this.scaleValue = options.scale ?? 1;\n this.surfaceScale = this.scaleValue;\n this.manifest = options.manifest ?? EMPTY_MANIFEST;\n this.theme = options.theme;\n this.registry = options.registry ?? new Registry();\n\n this.ownsCanvas = options.canvas === undefined;\n this.canvas = options.canvas ?? document.createElement(\"canvas\");\n if (this.ownsCanvas) {\n this.canvas.style.display = \"none\";\n document.body.appendChild(this.canvas);\n }\n this.sizeCanvas();\n\n this.catalog = new ManifestAssetCatalog(this.manifest);\n this.storageAdapter = this.backend.createStorageAdapter({\n catalog: this.catalog, viewport: this.viewportValue, fps: this.fpsValue,\n });\n this.renderContext = this.backend.createRenderContext(this.storageAdapter);\n this.renderContext.mount(this.canvas);\n }\n\n get viewport(): Size2D { return this.viewportValue; }\n get fps(): number { return this.fpsValue; }\n get scale(): number { return this.scaleValue; }\n\n /**\n * Draw one frame of `source`, at this renderer's viewport — the surface is\n * the output, so it wins over any size the document asks for.\n */\n async render(source: StillSource, options?: RenderStillOptions): Promise<DrawnFrame> {\n this.assertLive();\n const scale = options?.scale ?? this.scaleValue;\n if (scale !== this.surfaceScale) this.remount(scale);\n await this.renderContext.whenMounted?.();\n\n const project = resolveProjectDocument(\n source, { viewport: this.viewportValue }, { viewport: this.viewportValue, fps: this.fpsValue },\n );\n\n const frame = await drawFrameAt({\n project,\n registry: this.registry,\n theme: this.theme,\n scale,\n renderContext: this.renderContext,\n storageAdapter: this.storageAdapter,\n assetCatalog: this.catalog,\n frame: stillFrameSpec(options, project.fps!),\n onIssue: options?.onIssue,\n });\n this.drawn = true;\n return frame;\n }\n\n /** Theme for a document that carries none of its own. */\n setTheme(theme?: Theme): void { this.theme = theme; }\n\n /** Swap the asset manifest, keeping every decode already cached. */\n setManifest(manifest: AssetManifest): void {\n this.assertLive();\n this.manifest = manifest;\n this.catalog = new ManifestAssetCatalog(manifest);\n this.storageAdapter.setCatalog(this.catalog);\n }\n\n /**\n * Change the output resolution.\n *\n * A full reset, unlike {@link setManifest}: the viewport clamps the resolution\n * images are decoded *at*, so cached decodes made for the old one would be\n * soft at the new one, and the render context's handlers captured the adapter\n * when they were built — so both are rebuilt. Call this when the output format\n * changes, not per edit.\n */\n setViewport(viewport: Size2D): void {\n this.assertLive();\n if (viewport.width === this.viewportValue.width && viewport.height === this.viewportValue.height) {\n return;\n }\n this.viewportValue = viewport;\n // Disposing the context disposes the adapter it holds, so this releases\n // the old surface, its handlers and every decode in one call.\n this.renderContext.dispose();\n this.storageAdapter = this.backend.createStorageAdapter({\n catalog: this.catalog, viewport, fps: this.fpsValue,\n });\n this.renderContext = this.backend.createRenderContext(this.storageAdapter);\n this.remount(this.scaleValue);\n }\n\n /** Encode the frame currently on the surface. */\n async toBytes(options?: EncodeStillOptions): Promise<Uint8Array> {\n const blob = await this.toBlob(options);\n return new Uint8Array(await blob.arrayBuffer());\n }\n\n /** Encode the frame currently on the surface. */\n toBlob(options?: EncodeStillOptions): Promise<Blob> {\n const canvas = this.snapshotToCanvas();\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => blob\n ? resolve(blob)\n : reject(new Error(\"Still encode produced no data.\")),\n mimeOf(format),\n quality,\n );\n });\n }\n\n /** Encode the frame currently on the surface. */\n toDataURL(options?: EncodeStillOptions): string {\n const { format = \"png\", quality = 0.92 } = options ?? {};\n return this.snapshotToCanvas().toDataURL(mimeOf(format), quality);\n }\n\n dispose(): void {\n if (this.disposed) return;\n this.disposed = true;\n this.renderContext.dispose();\n if (this.ownsCanvas) this.canvas.remove();\n }\n\n /**\n * Read the surface back into a 2D canvas, which is what actually encodes: the\n * renderers ship no image encoders, so the browser's own codecs are the only\n * ones available.\n */\n private snapshotToCanvas(): HTMLCanvasElement {\n this.assertLive();\n if (!this.drawn) throw new Error(\"Nothing has been rendered yet.\");\n const canvas = this.renderContext.snapshotCanvas?.();\n if (!canvas) throw new Error(\"Still encode: could not read the surface into a 2d canvas.\");\n return canvas;\n }\n\n private sizeCanvas(): void {\n this.canvas.width = this.viewportValue.width * this.surfaceScale;\n this.canvas.height = this.viewportValue.height * this.surfaceScale;\n }\n\n /** Resize the backing store and mount a fresh surface over it. */\n private remount(scale: number): void {\n this.surfaceScale = scale;\n this.drawn = false;\n this.sizeCanvas();\n this.renderContext.mount(this.canvas);\n }\n\n private assertLive(): void {\n if (this.disposed) throw new Error(\"This StillRenderer has been disposed.\");\n }\n}\n\n/** Create a {@link StillRenderer}; rejects with `WebGpuUnavailableError` without WebGPU. */\nexport async function createStillRenderer(\n options: StillRendererOptions = {},\n): Promise<StillRenderer> {\n return new StillRenderer(await loadRenderer(options), options);\n}\n"]}