@field/cascade 0.2.22 → 0.3.0
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/AGENTS.md +47 -7
- package/ARCHITECTURE.md +30 -2
- package/CHANGELOG.md +89 -0
- package/DESIGN.md +10 -5
- package/README.md +103 -7
- package/dist/assets/CodeEditor-BADFbTJS.js +623 -0
- package/dist/assets/CodePanel-faK6PRfz.js +2 -0
- package/dist/assets/CustomNodeDialog-DhlKuviy.js +1 -0
- package/dist/assets/{ExportDialog-DWN4nhXI.js → ExportDialog-BJu2Bpvv.js} +2 -2
- package/dist/assets/ProjectSettings-D6a6kjqR.js +2 -0
- package/dist/assets/SettingsDialog-DJZdIvLC.js +1 -0
- package/dist/assets/VersionHistoryDialog-C1X3HOhc.js +3 -0
- package/dist/assets/{core-Bji4aZUU.js → core-CiuiuWZi.js} +29 -38
- package/dist/assets/{cssMode-B6Z_Q1Kh.js → cssMode-D-NwZSMJ.js} +1 -1
- package/dist/assets/{editor.api-jMoW_43X.js → editor.api-DT3WEvBd.js} +1 -1
- package/dist/assets/{freemarker2-DvvPV6m5.js → freemarker2-eKNYQaiz.js} +1 -1
- package/dist/assets/geo-DUvVbLnx.js +1 -0
- package/dist/assets/{handlebars-B8rfuSIa.js → handlebars-C2w1ggK2.js} +1 -1
- package/dist/assets/{html-DyHm6JCp.js → html-DQb41jyo.js} +1 -1
- package/dist/assets/{htmlMode-E9XLWE5u.js → htmlMode-IhNlh6Lv.js} +1 -1
- package/dist/assets/{image-BUcQC7PG.js → image-DMCXBY-_.js} +26 -40
- package/dist/assets/index-BDV5xGBZ.css +1 -0
- package/dist/assets/index-HTCN44kj.js +212 -0
- package/dist/assets/index-client-BoJgsN9M.js +1 -0
- package/dist/assets/{javascript-C_bM5PCN.js → javascript-BIT6jMLC.js} +1 -1
- package/dist/assets/{jsonMode-BdyVS7G4.js → jsonMode-CNfyB1ro.js} +1 -1
- package/dist/assets/{legacy-DIu3XRwl.js → legacy-CAj2_7QE.js} +2 -2
- package/dist/assets/{liquid-CDkn3spA.js → liquid-CmSmJmOG.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-AB5DMAsr.js → lspLanguageFeatures-BqMm3uMP.js} +1 -1
- package/dist/assets/{mdx-BvH_NjDN.js → mdx-6r6YgOFw.js} +1 -1
- package/dist/assets/{nodeTypeUtils-CweA3NHd.js → nodeTypeUtils-BBA1JqHx.js} +1 -1
- package/dist/assets/{python-B7uii4fi.js → python-x1H0TQuI.js} +1 -1
- package/dist/assets/{razor-Z48fWdJc.js → razor-Dzl1vZFm.js} +1 -1
- package/dist/assets/{tsMode-BDgacVG4.js → tsMode-n6DMMe3a.js} +1 -1
- package/dist/assets/{typescript-Derb6Oqa.js → typescript-ziEz65xN.js} +1 -1
- package/dist/assets/{workers-BtoMX_pm.js → workers-D-qUdp-r.js} +1 -1
- package/dist/assets/{xml-B1CaFSrG.js → xml-DMZjt5lE.js} +1 -1
- package/dist/assets/{yaml-HmIeA0KH.js → yaml-DFe5qZSO.js} +1 -1
- package/dist/cli/index.js +16194 -10195
- package/dist/cli/runtime/io.ts +93 -7
- package/dist/contracts/animation.d.ts +40 -0
- package/dist/contracts/animation.js +14 -0
- package/dist/contracts/definition.d.ts +39 -1
- package/dist/contracts/document.d.ts +15 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/validate.d.ts +10 -1
- package/dist/contracts/validate.js +9 -1
- package/dist/graphs/default.cascade +1 -1
- package/dist/index.html +5 -5
- package/dist/io.d.ts +196 -0
- package/dist/io.js +303 -0
- package/dist/net.d.ts +1 -0
- package/dist/net.js +28 -0
- package/dist/runtime/animation/channel.d.ts +108 -0
- package/dist/runtime/animation/channel.js +204 -0
- package/dist/runtime/animation/frameRange.d.ts +78 -0
- package/dist/runtime/animation/frameRange.js +76 -0
- package/dist/runtime/animation/index.d.ts +10 -0
- package/dist/runtime/animation/index.js +8 -0
- package/dist/runtime/builtins/core/random.d.ts +1 -1
- package/dist/runtime/builtins/core/remap.d.ts +1 -1
- package/dist/runtime/builtins/geo/copyToPoints.d.ts +1 -1
- package/dist/runtime/builtins/geo/copyToPoints.js +1 -1
- package/dist/runtime/builtins/geo/generators.d.ts +1 -1
- package/dist/runtime/builtins/geo/merge.d.ts +1 -1
- package/dist/runtime/builtins/geo/svgExport.d.ts +1 -1
- package/dist/runtime/builtins/geo/svgExport.js +1 -1
- package/dist/runtime/builtins/geo/transform.d.ts +1 -1
- package/dist/runtime/builtins/geo/transform.js +1 -1
- package/dist/runtime/definition/architecture.d.ts +1 -1
- package/dist/runtime/definition/extract.d.ts +1 -1
- package/dist/runtime/definition/extract.js +1 -1
- package/dist/runtime/error.d.ts +1 -1
- package/dist/runtime/expressions/evaluator.d.ts +53 -0
- package/dist/runtime/expressions/evaluator.js +193 -0
- package/dist/runtime/expressions/index.d.ts +15 -0
- package/dist/runtime/expressions/index.js +12 -0
- package/dist/runtime/expressions/math.d.ts +75 -0
- package/dist/runtime/expressions/math.js +140 -0
- package/dist/runtime/expressions/preprocess.d.ts +33 -0
- package/dist/runtime/expressions/preprocess.js +102 -0
- package/dist/runtime/expressions/scope.d.ts +27 -0
- package/dist/runtime/expressions/scope.js +10 -0
- package/dist/runtime/expressions/time.d.ts +54 -0
- package/dist/runtime/expressions/time.js +71 -0
- package/dist/runtime/expressions/types.d.ts +66 -0
- package/dist/runtime/expressions/types.js +9 -0
- package/dist/runtime/geometry/attributes.d.ts +2 -2
- package/dist/runtime/geometry/attributes.js +1 -1
- package/dist/runtime/geometry/builder.d.ts +1 -1
- package/dist/runtime/geometry/builder.js +1 -1
- package/dist/runtime/geometry/copy.d.ts +1 -1
- package/dist/runtime/geometry/copy.js +1 -1
- package/dist/runtime/geometry/generate.d.ts +1 -1
- package/dist/runtime/geometry/groups.d.ts +1 -1
- package/dist/runtime/geometry/groups.js +1 -1
- package/dist/runtime/geometry/matrix.d.ts +1 -1
- package/dist/runtime/geometry/merge.d.ts +1 -1
- package/dist/runtime/geometry/merge.js +1 -1
- package/dist/runtime/geometry/primitives.d.ts +1 -1
- package/dist/runtime/geometry/primitives.js +1 -1
- package/dist/runtime/geometry/promote.d.ts +1 -1
- package/dist/runtime/geometry/promote.js +1 -1
- package/dist/runtime/geometry/resample.d.ts +1 -1
- package/dist/runtime/geometry/resample.js +1 -1
- package/dist/runtime/geometry/svg.d.ts +1 -1
- package/dist/runtime/geometry/svg.js +1 -1
- package/dist/runtime/geometry/transform.d.ts +1 -1
- package/dist/runtime/geometry/transform.js +1 -1
- package/dist/runtime/index.d.ts +4 -1
- package/dist/runtime/index.js +4 -1
- package/dist/runtime/params/animator.d.ts +74 -0
- package/dist/runtime/params/animator.js +137 -0
- package/dist/runtime/params/index.d.ts +11 -0
- package/dist/runtime/params/index.js +9 -0
- package/dist/runtime/params/resolve.d.ts +73 -0
- package/dist/runtime/params/resolve.js +62 -0
- package/dist/runtime/runtime.d.ts +22 -0
- package/dist/runtime/runtime.js +242 -14
- package/dist/runtime/types.d.ts +30 -4
- package/dist/shell.d.ts +3 -3
- package/doc/NODE_REFERENCE.md +395 -0
- package/doc/PROJECT_AUTHORING.md +148 -6
- package/package.json +26 -3
- package/dist/assets/CodeEditor-DoaNEUaM.js +0 -641
- package/dist/assets/CodePanel-B0_2XJ-t.js +0 -2
- package/dist/assets/CustomNodeDialog-026lzecD.js +0 -1
- package/dist/assets/ProjectSettings-BzT164Yx.js +0 -2
- package/dist/assets/SettingsDialog-DmHb1V0r.js +0 -1
- package/dist/assets/VersionHistoryDialog-hH6VsXLX.js +0 -3
- package/dist/assets/geo-CXmCtzMM.js +0 -2
- package/dist/assets/index-BXYXSdgh.js +0 -224
- package/dist/assets/index-CrNke7SW.css +0 -1
- package/dist/assets/index-client-DN_FvjN8.js +0 -1
- package/dist/assets/quill-DjZ69wL_.js +0 -121
- package/dist/cli/index.js.map +0 -7
- package/dist/contracts/capabilities.d.ts.map +0 -1
- package/dist/contracts/capabilities.js.map +0 -1
- package/dist/contracts/color.d.ts.map +0 -1
- package/dist/contracts/color.js.map +0 -1
- package/dist/contracts/definition.d.ts.map +0 -1
- package/dist/contracts/definition.js.map +0 -1
- package/dist/contracts/document.d.ts.map +0 -1
- package/dist/contracts/document.js.map +0 -1
- package/dist/contracts/geometry.d.ts.map +0 -1
- package/dist/contracts/geometry.js.map +0 -1
- package/dist/contracts/index.d.ts.map +0 -1
- package/dist/contracts/index.js.map +0 -1
- package/dist/contracts/schema.d.ts.map +0 -1
- package/dist/contracts/schema.js.map +0 -1
- package/dist/contracts/validate.d.ts.map +0 -1
- package/dist/contracts/validate.js.map +0 -1
- package/dist/contracts/values.d.ts.map +0 -1
- package/dist/contracts/values.js.map +0 -1
- package/dist/graphs/default-orig.cascade +0 -128
- package/dist/runtime/browser.d.ts.map +0 -1
- package/dist/runtime/browser.js.map +0 -1
- package/dist/runtime/builtins/core/index.d.ts.map +0 -1
- package/dist/runtime/builtins/core/index.js.map +0 -1
- package/dist/runtime/builtins/core/random.d.ts.map +0 -1
- package/dist/runtime/builtins/core/random.js.map +0 -1
- package/dist/runtime/builtins/core/remap.d.ts.map +0 -1
- package/dist/runtime/builtins/core/remap.js.map +0 -1
- package/dist/runtime/builtins/core/routing.d.ts.map +0 -1
- package/dist/runtime/builtins/core/routing.js.map +0 -1
- package/dist/runtime/builtins/core/structural.d.ts.map +0 -1
- package/dist/runtime/builtins/core/structural.js.map +0 -1
- package/dist/runtime/builtins/geo/copyToPoints.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/copyToPoints.js.map +0 -1
- package/dist/runtime/builtins/geo/definitions.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/definitions.js.map +0 -1
- package/dist/runtime/builtins/geo/generators.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/generators.js.map +0 -1
- package/dist/runtime/builtins/geo/index.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/index.js.map +0 -1
- package/dist/runtime/builtins/geo/merge.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/merge.js.map +0 -1
- package/dist/runtime/builtins/geo/namespace.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/namespace.js.map +0 -1
- package/dist/runtime/builtins/geo/svgExport.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/svgExport.js.map +0 -1
- package/dist/runtime/builtins/geo/transform.d.ts.map +0 -1
- package/dist/runtime/builtins/geo/transform.js.map +0 -1
- package/dist/runtime/builtins/index.d.ts.map +0 -1
- package/dist/runtime/builtins/index.js.map +0 -1
- package/dist/runtime/definition/architecture.d.ts.map +0 -1
- package/dist/runtime/definition/architecture.js.map +0 -1
- package/dist/runtime/definition/extract.d.ts.map +0 -1
- package/dist/runtime/definition/extract.js.map +0 -1
- package/dist/runtime/error.d.ts.map +0 -1
- package/dist/runtime/error.js.map +0 -1
- package/dist/runtime/geometry/attributes.d.ts.map +0 -1
- package/dist/runtime/geometry/attributes.js.map +0 -1
- package/dist/runtime/geometry/builder.d.ts.map +0 -1
- package/dist/runtime/geometry/builder.js.map +0 -1
- package/dist/runtime/geometry/copy.d.ts.map +0 -1
- package/dist/runtime/geometry/copy.js.map +0 -1
- package/dist/runtime/geometry/generate.d.ts.map +0 -1
- package/dist/runtime/geometry/generate.js.map +0 -1
- package/dist/runtime/geometry/groups.d.ts.map +0 -1
- package/dist/runtime/geometry/groups.js.map +0 -1
- package/dist/runtime/geometry/index.d.ts.map +0 -1
- package/dist/runtime/geometry/index.js.map +0 -1
- package/dist/runtime/geometry/matrix.d.ts.map +0 -1
- package/dist/runtime/geometry/matrix.js.map +0 -1
- package/dist/runtime/geometry/merge.d.ts.map +0 -1
- package/dist/runtime/geometry/merge.js.map +0 -1
- package/dist/runtime/geometry/primitives.d.ts.map +0 -1
- package/dist/runtime/geometry/primitives.js.map +0 -1
- package/dist/runtime/geometry/promote.d.ts.map +0 -1
- package/dist/runtime/geometry/promote.js.map +0 -1
- package/dist/runtime/geometry/resample.d.ts.map +0 -1
- package/dist/runtime/geometry/resample.js.map +0 -1
- package/dist/runtime/geometry/svg.d.ts.map +0 -1
- package/dist/runtime/geometry/svg.js.map +0 -1
- package/dist/runtime/geometry/transform.d.ts.map +0 -1
- package/dist/runtime/geometry/transform.js.map +0 -1
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/node.d.ts.map +0 -1
- package/dist/runtime/node.js.map +0 -1
- package/dist/runtime/runtime.d.ts.map +0 -1
- package/dist/runtime/runtime.js.map +0 -1
- package/dist/runtime/types.d.ts.map +0 -1
- package/dist/runtime/types.js.map +0 -1
- package/dist/shell.js.map +0 -7
- package/dist/stage.js.map +0 -7
package/dist/cli/runtime/io.ts
CHANGED
|
@@ -43,6 +43,63 @@ export function mediaUrl(path: string, options: MediaOptions = {}): string {
|
|
|
43
43
|
return `/api/media/${encoded}${query ? `?${query}` : ''}`;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* The transport a host installs so `cascade/io` can reach project files.
|
|
48
|
+
*
|
|
49
|
+
* Same shape of decision as `cascade/stage`: the page posts to the server,
|
|
50
|
+
* because that is the only door a browser has, and a headless host installs a
|
|
51
|
+
* local transport before it runs the graph. Without one, every read here is a
|
|
52
|
+
* `fetch('/api/media/...')` against a relative URL with no origin to resolve it
|
|
53
|
+
* — which is exactly how a Canvas 2D node came to render in Studio and fail
|
|
54
|
+
* under `cascade run`.
|
|
55
|
+
*
|
|
56
|
+
* Paths stay project-relative on both sides. The bridge, not the node, decides
|
|
57
|
+
* what that resolves to.
|
|
58
|
+
*/
|
|
59
|
+
export interface IoBridge {
|
|
60
|
+
/** Read a project file's bytes. */
|
|
61
|
+
read(path: string, options?: MediaOptions): Promise<Uint8Array>;
|
|
62
|
+
/** Write bytes into the project cache. Returns the stored project-relative path. */
|
|
63
|
+
write(path: string, data: Uint8Array): Promise<string>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const IO_BRIDGE_KEY = '__cascadeIoBridge';
|
|
67
|
+
|
|
68
|
+
/** Install the local transport. Returns a disposer, so a test can take it down. */
|
|
69
|
+
export function installIoBridge(bridge: IoBridge | null): () => void {
|
|
70
|
+
const globals = globalThis as Record<string, unknown>;
|
|
71
|
+
const previous = globals[IO_BRIDGE_KEY];
|
|
72
|
+
globals[IO_BRIDGE_KEY] = bridge ?? undefined;
|
|
73
|
+
return () => {
|
|
74
|
+
globals[IO_BRIDGE_KEY] = previous;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function localIo(): IoBridge | null {
|
|
79
|
+
const bridge = (globalThis as Record<string, unknown>)[IO_BRIDGE_KEY] as IoBridge | undefined;
|
|
80
|
+
return bridge && typeof bridge.read === 'function' && typeof bridge.write === 'function' ? bridge : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A project file's bytes, over whichever transport this host has. Every read in
|
|
85
|
+
* this module goes through here so the two hosts differ in one place only.
|
|
86
|
+
*/
|
|
87
|
+
export async function loadBytes(path: string, options: MediaOptions = { raw: true }): Promise<ArrayBuffer> {
|
|
88
|
+
const bridge = localIo();
|
|
89
|
+
if (bridge) {
|
|
90
|
+
const bytes = await bridge.read(path, options);
|
|
91
|
+
// A Buffer is usually a view into a larger pool, and `loadFloats` builds a
|
|
92
|
+
// typed-array VIEW onto whatever comes back — so hand on the exact bytes.
|
|
93
|
+
const exact = bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength
|
|
94
|
+
? bytes.buffer
|
|
95
|
+
: bytes.slice().buffer;
|
|
96
|
+
return exact as ArrayBuffer;
|
|
97
|
+
}
|
|
98
|
+
const response = await fetch(mediaUrl(path, options));
|
|
99
|
+
if (!response.ok) throw new Error(`cascade/io: cannot load ${path} (${response.status})`);
|
|
100
|
+
return await response.arrayBuffer();
|
|
101
|
+
}
|
|
102
|
+
|
|
46
103
|
/**
|
|
47
104
|
* Load a project image as an <img>. Defaults to `raw` — a node is processing
|
|
48
105
|
* these pixels, not showing them, so a re-encoded copy would be the wrong thing
|
|
@@ -50,14 +107,21 @@ export function mediaUrl(path: string, options: MediaOptions = {}): string {
|
|
|
50
107
|
*/
|
|
51
108
|
export async function loadImage(path: string, options: MediaOptions = { raw: true }): Promise<HTMLImageElement> {
|
|
52
109
|
const image = new Image();
|
|
53
|
-
|
|
54
|
-
|
|
110
|
+
const bridge = localIo();
|
|
111
|
+
if (bridge) {
|
|
112
|
+
// No origin to resolve a URL against; hand the decoder the bytes.
|
|
113
|
+
(image as unknown as { src: unknown }).src = new Uint8Array(await loadBytes(path, options));
|
|
114
|
+
} else {
|
|
115
|
+
image.crossOrigin = 'anonymous';
|
|
116
|
+
image.src = mediaUrl(path, options);
|
|
117
|
+
}
|
|
55
118
|
await image.decode();
|
|
56
119
|
return image;
|
|
57
120
|
}
|
|
58
121
|
|
|
59
122
|
/** Same, as an ImageBitmap — the right input for `texImage2D`. */
|
|
60
123
|
export async function loadBitmap(path: string, options: MediaOptions = { raw: true }): Promise<ImageBitmap> {
|
|
124
|
+
if (localIo()) return createImageBitmap(new Blob([await loadBytes(path, options)]));
|
|
61
125
|
const response = await fetch(mediaUrl(path, options));
|
|
62
126
|
if (!response.ok) throw new Error(`cascade/io: cannot load ${path} (${response.status})`);
|
|
63
127
|
return createImageBitmap(await response.blob());
|
|
@@ -80,6 +144,17 @@ export interface SaveOptions {
|
|
|
80
144
|
quality?: number;
|
|
81
145
|
}
|
|
82
146
|
|
|
147
|
+
/**
|
|
148
|
+
* Anything that can produce encoded pixels. The two browser canvases are the
|
|
149
|
+
* usual case; a headless host's canvas offers Skia's own encoder instead, and
|
|
150
|
+
* `saveImage` takes whichever it is handed rather than making a node care.
|
|
151
|
+
*/
|
|
152
|
+
export interface ImageSource {
|
|
153
|
+
convertToBlob?(options?: { type?: string; quality?: number }): Promise<Blob>;
|
|
154
|
+
encode?(format: string, quality?: number): Promise<Uint8Array>;
|
|
155
|
+
toBuffer?(mime: string): Uint8Array;
|
|
156
|
+
}
|
|
157
|
+
|
|
83
158
|
/**
|
|
84
159
|
* Write pixels into the project and get back the path to hand downstream.
|
|
85
160
|
* Confined to the cache directory, like every other node output.
|
|
@@ -88,7 +163,7 @@ export interface SaveOptions {
|
|
|
88
163
|
* node.out('path').setValue(out);
|
|
89
164
|
*/
|
|
90
165
|
export async function saveImage(
|
|
91
|
-
source: HTMLCanvasElement | OffscreenCanvas | Blob,
|
|
166
|
+
source: HTMLCanvasElement | OffscreenCanvas | Blob | ImageSource,
|
|
92
167
|
path: string,
|
|
93
168
|
options: SaveOptions = {}
|
|
94
169
|
): Promise<string> {
|
|
@@ -98,8 +173,14 @@ export async function saveImage(
|
|
|
98
173
|
|
|
99
174
|
if (source instanceof Blob) {
|
|
100
175
|
blob = source;
|
|
101
|
-
} else if (
|
|
176
|
+
} else if (typeof (source as OffscreenCanvas).convertToBlob === 'function') {
|
|
102
177
|
blob = await (source as OffscreenCanvas).convertToBlob({ type, quality: options.quality });
|
|
178
|
+
} else if (typeof (source as ImageSource).encode === 'function') {
|
|
179
|
+
// A headless canvas that was handed over unwrapped — Skia's own encoder,
|
|
180
|
+
// rather than a browser API it does not have.
|
|
181
|
+
blob = new Blob([(await (source as ImageSource).encode!(format, options.quality)) as BlobPart], { type });
|
|
182
|
+
} else if (typeof (source as ImageSource).toBuffer === 'function') {
|
|
183
|
+
blob = new Blob([(source as ImageSource).toBuffer!(type) as BlobPart], { type });
|
|
103
184
|
} else {
|
|
104
185
|
blob = await new Promise<Blob>((resolve, reject) => {
|
|
105
186
|
(source as HTMLCanvasElement).toBlob(
|
|
@@ -116,6 +197,13 @@ export async function saveImage(
|
|
|
116
197
|
/** Write any bytes into the project cache and return the path. */
|
|
117
198
|
export async function saveBytes(data: Blob | ArrayBuffer | Uint8Array, path: string): Promise<string> {
|
|
118
199
|
const clean = path.replace(/^\.?\//, '');
|
|
200
|
+
const bridge = localIo();
|
|
201
|
+
if (bridge) {
|
|
202
|
+
const bytes = data instanceof Uint8Array
|
|
203
|
+
? data
|
|
204
|
+
: new Uint8Array(data instanceof Blob ? await data.arrayBuffer() : data);
|
|
205
|
+
return await bridge.write(clean, bytes);
|
|
206
|
+
}
|
|
119
207
|
const response = await fetch(`/api/media/${clean.split('/').map(encodeURIComponent).join('/')}`, {
|
|
120
208
|
method: 'PUT',
|
|
121
209
|
headers: { 'Content-Type': 'application/octet-stream' },
|
|
@@ -204,9 +292,7 @@ function parseNpyHeader(buffer: ArrayBuffer): NpyHeader {
|
|
|
204
292
|
* still loads — it just costs a pass.
|
|
205
293
|
*/
|
|
206
294
|
export async function loadFloats(path: string): Promise<FloatImage> {
|
|
207
|
-
const
|
|
208
|
-
if (!response.ok) throw new Error(`cascade/io: cannot load ${path} (${response.status})`);
|
|
209
|
-
const buffer = await response.arrayBuffer();
|
|
295
|
+
const buffer = await loadBytes(path, { raw: true });
|
|
210
296
|
const header = parseNpyHeader(buffer);
|
|
211
297
|
|
|
212
298
|
if (header.fortran) throw new Error('cascade/io: Fortran-ordered .npy is not supported');
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation as it appears in a document.
|
|
3
|
+
*
|
|
4
|
+
* A parameter can be a literal, an expression, or a keyframe channel. All
|
|
5
|
+
* three serialise in one place — the node's `props` map — because a channel
|
|
6
|
+
* that lived in a timeline object would need a registry of targets, and that
|
|
7
|
+
* registry goes stale the moment a node is renamed.
|
|
8
|
+
*
|
|
9
|
+
* A prop with neither an expression nor a channel is still written as a bare
|
|
10
|
+
* value, which is what every existing file on disk contains. The object form
|
|
11
|
+
* appears only when there is something to say beyond the value.
|
|
12
|
+
*/
|
|
13
|
+
export type CascadeInterpolation = "constant" | "linear" | "smooth";
|
|
14
|
+
/** One key. `frame` may be fractional. Interpolation governs the segment that
|
|
15
|
+
* LEAVES this key, and is omitted when it is the default (`smooth`). */
|
|
16
|
+
export interface CascadeSerializedKeyframe {
|
|
17
|
+
readonly frame: number;
|
|
18
|
+
readonly value: number;
|
|
19
|
+
readonly interpolation?: CascadeInterpolation;
|
|
20
|
+
}
|
|
21
|
+
/** Keys are written in frame order and hold at most one key per frame. A
|
|
22
|
+
* reader must tolerate neither being true and normalise. */
|
|
23
|
+
export interface CascadeSerializedChannel {
|
|
24
|
+
readonly keys: readonly CascadeSerializedKeyframe[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The object form of a prop. Written when the parameter carries an expression,
|
|
28
|
+
* a channel, or both.
|
|
29
|
+
*
|
|
30
|
+
* Both is legal, and the resolution order is fixed: **the channel wins and the
|
|
31
|
+
* expression is kept but inert.** Discarding an author's expression because
|
|
32
|
+
* they keyed the parameter is the worse failure of the two — the expression is
|
|
33
|
+
* still there to go back to, and deleting the channel restores it.
|
|
34
|
+
*/
|
|
35
|
+
export interface CascadeSerializedProp {
|
|
36
|
+
readonly value: unknown;
|
|
37
|
+
readonly expression?: string;
|
|
38
|
+
readonly channel?: CascadeSerializedChannel;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=animation.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation as it appears in a document.
|
|
3
|
+
*
|
|
4
|
+
* A parameter can be a literal, an expression, or a keyframe channel. All
|
|
5
|
+
* three serialise in one place — the node's `props` map — because a channel
|
|
6
|
+
* that lived in a timeline object would need a registry of targets, and that
|
|
7
|
+
* registry goes stale the moment a node is renamed.
|
|
8
|
+
*
|
|
9
|
+
* A prop with neither an expression nor a channel is still written as a bare
|
|
10
|
+
* value, which is what every existing file on disk contains. The object form
|
|
11
|
+
* appears only when there is something to say beyond the value.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=animation.js.map
|
|
@@ -4,7 +4,30 @@ export type RuntimeEnvironment = "portable" | "browser" | "server";
|
|
|
4
4
|
export type CapabilityForEnvironment<E extends RuntimeEnvironment> = E extends "portable" ? "assets" | "media" : E extends "browser" ? "assets" | "media" | "webgl" : "files" | "assets" | "media" | "python" | "shell";
|
|
5
5
|
export type DataTypeForEnvironment<E extends RuntimeEnvironment> = E extends "browser" ? CascadeType : Exclude<CascadeType, "texture">;
|
|
6
6
|
type ControlFor<T extends CascadeType> = T extends "float" ? "number" | "slider" | "range" : T extends "int" ? "int" | "slider" | "range" : T extends "bool" ? "boolean" : T extends "string" ? "text" | "textarea" | "select" : T extends "vec2" | "vec3" | "vec4" | "vec2i" | "vec3i" | "vec4i" ? "vector" : T extends "mat2" | "mat3" | "mat4" ? "matrix" : T extends "color" ? "color" : T extends "image" ? "image" : T extends "asset" ? "asset" | "select" : T extends "texture" ? never : "select";
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* `min`, `max` and `step`, for the types whose components are numbers.
|
|
9
|
+
*
|
|
10
|
+
* The vectors are included, and were not: they typed all three as `never`, so
|
|
11
|
+
* the six vector types could not carry a range at all. Nothing exercised it
|
|
12
|
+
* because no builtin vec prop has one — but Marcus's standing rule is that
|
|
13
|
+
* anything with an x and a y is one `vec2` rather than two floats, and two
|
|
14
|
+
* floats *can* carry a range while the `vec2` they became could not. So
|
|
15
|
+
* following the convention cost the Inspector its clamp and its drag
|
|
16
|
+
* granularity every time: `field-io-gradient-logo.offset` lost −1..1 step
|
|
17
|
+
* 0.005, `offset.offset` lost −2..2, `ripple.centre` lost −1..2, and every
|
|
18
|
+
* remaining vector candidate across the sketches carries a range too.
|
|
19
|
+
*
|
|
20
|
+
* One range for all components, not one per component. A per-component tuple
|
|
21
|
+
* is a real design question — an `x` and a `y` with different limits is a
|
|
22
|
+
* legitimate thing to want — but it is a second shape for this field and every
|
|
23
|
+
* reader would have to handle both, so it waits for a case that needs it
|
|
24
|
+
* rather than being guessed at now.
|
|
25
|
+
*
|
|
26
|
+
* Matrices stay excluded. Their control is a grid of components with an
|
|
27
|
+
* identity button, and a range across a transform's cells is not a thing
|
|
28
|
+
* anybody has asked for.
|
|
29
|
+
*/
|
|
30
|
+
type NumericMetadata<T extends CascadeType> = T extends "float" | "int" | "vec2" | "vec3" | "vec4" | "vec2i" | "vec3i" | "vec4i" ? {
|
|
8
31
|
readonly min?: number;
|
|
9
32
|
readonly max?: number;
|
|
10
33
|
readonly step?: number;
|
|
@@ -100,6 +123,21 @@ type OutputValue<O> = O extends TriggerOutputDefinition ? {
|
|
|
100
123
|
} : never;
|
|
101
124
|
export type DeclaredCapabilities<D extends NodeDefinition> = Readonly<Pick<CapabilityMap, D["capabilities"] extends readonly NodeCapabilityName[] ? D["capabilities"][number] : never>>;
|
|
102
125
|
export interface NodeExecutionContext<D extends NodeDefinition> {
|
|
126
|
+
/**
|
|
127
|
+
* This node instance's id, stable for the life of the graph.
|
|
128
|
+
*
|
|
129
|
+
* It exists for one reason: naming a scratch file so that two instances of
|
|
130
|
+
* the same module do not overwrite each other. `field-logo` runs one module
|
|
131
|
+
* three times at three sizes, and `cloud-plots` runs `height-field` twice
|
|
132
|
+
* with opposite mode toggles — a literal path silently loses one of each
|
|
133
|
+
* pair. Pass it to `cachePath(context.nodeId, '.png')` and nothing else.
|
|
134
|
+
*
|
|
135
|
+
* An id is deliberately all a node gets. It names a namespace, not a
|
|
136
|
+
* position: there is no way to reach a parent, a sibling or the graph
|
|
137
|
+
* through it, which is what keeps a definition-v1 node a pure function of
|
|
138
|
+
* its own inputs. Do not parse it.
|
|
139
|
+
*/
|
|
140
|
+
readonly nodeId: string;
|
|
103
141
|
readonly inputs: Readonly<{
|
|
104
142
|
[K in keyof Entries<D, "inputs">]: InputValue<Entries<D, "inputs">[K]>;
|
|
105
143
|
}>;
|
|
@@ -10,6 +10,21 @@ export interface CascadeDocumentNode {
|
|
|
10
10
|
value?: unknown;
|
|
11
11
|
}>[] | Readonly<Record<string, unknown>>;
|
|
12
12
|
readonly props?: Readonly<Record<string, unknown>>;
|
|
13
|
+
/**
|
|
14
|
+
* The dynamic engine's stored parameter values, and dead weight here.
|
|
15
|
+
*
|
|
16
|
+
* The deterministic runtime reads `props` and nothing else, so a document
|
|
17
|
+
* that still carries `params` loses every value in it and reports nothing —
|
|
18
|
+
* a converted sketch silently reverts to its defaults and draws something
|
|
19
|
+
* different. Declared so that the runtime can recognise the key and warn
|
|
20
|
+
* about it, which is the only reason it appears in a v1 type.
|
|
21
|
+
*
|
|
22
|
+
* @deprecated Migrate to `props`.
|
|
23
|
+
*/
|
|
24
|
+
readonly params?: readonly Readonly<{
|
|
25
|
+
name: string;
|
|
26
|
+
value?: unknown;
|
|
27
|
+
}>[] | Readonly<Record<string, unknown>>;
|
|
13
28
|
}
|
|
14
29
|
export interface CascadeDocumentAnnotation {
|
|
15
30
|
readonly id: string;
|
package/dist/contracts/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NodeCapabilityName } from "./capabilities.js";
|
|
2
|
+
import type { NodeDefinition, RuntimeEnvironment } from "./definition.js";
|
|
2
3
|
export interface Diagnostic {
|
|
3
4
|
readonly phase: string;
|
|
4
5
|
readonly code: string;
|
|
@@ -8,6 +9,14 @@ export interface Diagnostic {
|
|
|
8
9
|
readonly line?: number;
|
|
9
10
|
readonly column?: number;
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* The capabilities a definition may *declare* for each environment. Declaring
|
|
14
|
+
* one is not the same as a host installing it — `MediaCapability` is declared
|
|
15
|
+
* here and implemented by nobody — so anything deciding whether a graph can
|
|
16
|
+
* actually run must ask the host, and use this only to classify which host a
|
|
17
|
+
* capability belongs to.
|
|
18
|
+
*/
|
|
19
|
+
export declare const CAPABILITIES_BY_ENVIRONMENT: Record<RuntimeEnvironment, readonly NodeCapabilityName[]>;
|
|
11
20
|
export declare function validateNodeDefinition(value: unknown): readonly Diagnostic[];
|
|
12
21
|
export declare function assertNodeDefinition(value: unknown): asserts value is NodeDefinition;
|
|
13
22
|
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -5,11 +5,19 @@ const NAMESPACED = /^[a-z][a-z0-9-]*(?:\.[A-Za-z][A-Za-z0-9_]*)+$/;
|
|
|
5
5
|
const RESERVED = new Set(["__proto__", "prototype", "constructor"]);
|
|
6
6
|
const CORE = new Set(CORE_TYPES);
|
|
7
7
|
const CONTROLS = new Set(["number", "slider", "range", "int", "boolean", "text", "textarea", "select", "vector", "matrix", "color", "image", "asset"]);
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The capabilities a definition may *declare* for each environment. Declaring
|
|
10
|
+
* one is not the same as a host installing it — `MediaCapability` is declared
|
|
11
|
+
* here and implemented by nobody — so anything deciding whether a graph can
|
|
12
|
+
* actually run must ask the host, and use this only to classify which host a
|
|
13
|
+
* capability belongs to.
|
|
14
|
+
*/
|
|
15
|
+
export const CAPABILITIES_BY_ENVIRONMENT = {
|
|
9
16
|
portable: ["assets", "media"],
|
|
10
17
|
browser: ["assets", "media", "webgl"],
|
|
11
18
|
server: ["files", "assets", "media", "python", "shell"],
|
|
12
19
|
};
|
|
20
|
+
const ALLOWED = CAPABILITIES_BY_ENVIRONMENT;
|
|
13
21
|
const fields = (names) => new Set(names.split(" "));
|
|
14
22
|
const TOP_FIELDS = fields("apiVersion runsOn container capabilities label description icon inputs outputs props");
|
|
15
23
|
const DATA_FIELDS = fields("kind type default description variadic min max step accept control options");
|
package/dist/index.html
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Cascade</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="modulepreload" crossorigin href="/assets/legacy-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/index-client-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/nodeTypeUtils-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-HTCN44kj.js"></script>
|
|
8
|
+
<link rel="modulepreload" crossorigin href="/assets/legacy-CAj2_7QE.js">
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/index-client-BoJgsN9M.js">
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/nodeTypeUtils-BBA1JqHx.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BDV5xGBZ.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="app"></div>
|
package/dist/io.d.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cascade/io` — the runtime a BROWSER-side node imports.
|
|
3
|
+
*
|
|
4
|
+
* The interop rests on one rule: an image port carries a project-relative PATH,
|
|
5
|
+
* never pixels. A Python node writes a file and emits its path; a WebGL node
|
|
6
|
+
* reads that path as a texture, renders, writes its result back, and emits a
|
|
7
|
+
* path of the same kind. Neither knows or cares which side produced its input,
|
|
8
|
+
* which is what makes the two interchangeable — and it lets the heavy stages
|
|
9
|
+
* stay in Python while the interactive ones run in the page.
|
|
10
|
+
*
|
|
11
|
+
* The counterpart on the server side is the project's own exec bridge
|
|
12
|
+
* (`runStage`), which is per-project because the stages are.
|
|
13
|
+
*
|
|
14
|
+
* This file is compiled into any node module that imports 'cascade/io'; it is
|
|
15
|
+
* not published to npm and has no dependencies.
|
|
16
|
+
*/
|
|
17
|
+
export interface MediaOptions {
|
|
18
|
+
/** Ask for a resized copy. Never upscales past the file's own resolution. */
|
|
19
|
+
width?: number;
|
|
20
|
+
/** 'webp' | 'png' | 'jpeg' | 'avif'. Defaults to WebP, which is what you want
|
|
21
|
+
* for anything being looked at rather than processed. */
|
|
22
|
+
format?: string;
|
|
23
|
+
/** The original bytes, unresized and unconverted. Use this when the pixels
|
|
24
|
+
* matter — reading a render back for further processing, not previewing it. */
|
|
25
|
+
raw?: boolean;
|
|
26
|
+
quality?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Build the URL that serves a project file to the page. */
|
|
29
|
+
export declare function mediaUrl(path: string, options?: MediaOptions): string;
|
|
30
|
+
/**
|
|
31
|
+
* The transport a host installs so `cascade/io` can reach project files.
|
|
32
|
+
*
|
|
33
|
+
* Same shape of decision as `cascade/stage`: the page posts to the server,
|
|
34
|
+
* because that is the only door a browser has, and a headless host installs a
|
|
35
|
+
* local transport before it runs the graph. Without one, every read here is a
|
|
36
|
+
* `fetch('/api/media/...')` against a relative URL with no origin to resolve it
|
|
37
|
+
* — which is exactly how a Canvas 2D node came to render in Studio and fail
|
|
38
|
+
* under `cascade run`.
|
|
39
|
+
*
|
|
40
|
+
* Paths stay project-relative on both sides. The bridge, not the node, decides
|
|
41
|
+
* what that resolves to.
|
|
42
|
+
*/
|
|
43
|
+
export interface IoBridge {
|
|
44
|
+
/** Read a project file's bytes. */
|
|
45
|
+
read(path: string, options?: MediaOptions): Promise<Uint8Array>;
|
|
46
|
+
/** Write bytes into the project cache. Returns the stored project-relative path. */
|
|
47
|
+
write(path: string, data: Uint8Array): Promise<string>;
|
|
48
|
+
}
|
|
49
|
+
/** Install the local transport. Returns a disposer, so a test can take it down. */
|
|
50
|
+
export declare function installIoBridge(bridge: IoBridge | null): () => void;
|
|
51
|
+
/**
|
|
52
|
+
* A project file's bytes, over whichever transport this host has. Every read in
|
|
53
|
+
* this module goes through here so the two hosts differ in one place only.
|
|
54
|
+
*/
|
|
55
|
+
export declare function loadBytes(path: string, options?: MediaOptions): Promise<ArrayBuffer>;
|
|
56
|
+
/**
|
|
57
|
+
* Load a project image as an <img>. Defaults to `raw` — a node is processing
|
|
58
|
+
* these pixels, not showing them, so a re-encoded copy would be the wrong thing
|
|
59
|
+
* to hand it. Pass a width explicitly to work at a smaller size on purpose.
|
|
60
|
+
*/
|
|
61
|
+
export declare function loadImage(path: string, options?: MediaOptions): Promise<HTMLImageElement>;
|
|
62
|
+
/** Same, as an ImageBitmap — the right input for `texImage2D`. */
|
|
63
|
+
export declare function loadBitmap(path: string, options?: MediaOptions): Promise<ImageBitmap>;
|
|
64
|
+
/**
|
|
65
|
+
* A .npy field (a mask, a signal, a height or density field) rendered to
|
|
66
|
+
* greyscale, ready to upload as a texture. The numeric array itself stays on
|
|
67
|
+
* the server side for now; this is the visual form of it.
|
|
68
|
+
*/
|
|
69
|
+
export declare function loadFieldBitmap(path: string, options?: MediaOptions): Promise<ImageBitmap>;
|
|
70
|
+
export interface SaveOptions {
|
|
71
|
+
/** 'png' (default), 'webp' or 'jpeg'. PNG is the default because the usual
|
|
72
|
+
* consumer of a node's output is another node, and a lossy intermediate
|
|
73
|
+
* compounds down a pipeline. Choose webp when the output is a preview. */
|
|
74
|
+
format?: 'png' | 'webp' | 'jpeg';
|
|
75
|
+
quality?: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Anything that can produce encoded pixels. The two browser canvases are the
|
|
79
|
+
* usual case; a headless host's canvas offers Skia's own encoder instead, and
|
|
80
|
+
* `saveImage` takes whichever it is handed rather than making a node care.
|
|
81
|
+
*/
|
|
82
|
+
export interface ImageSource {
|
|
83
|
+
convertToBlob?(options?: {
|
|
84
|
+
type?: string;
|
|
85
|
+
quality?: number;
|
|
86
|
+
}): Promise<Blob>;
|
|
87
|
+
encode?(format: string, quality?: number): Promise<Uint8Array>;
|
|
88
|
+
toBuffer?(mime: string): Uint8Array;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Write pixels into the project and get back the path to hand downstream.
|
|
92
|
+
* Confined to the cache directory, like every other node output.
|
|
93
|
+
*
|
|
94
|
+
* const out = await saveImage(canvas, cachePath(node.id, '.png'));
|
|
95
|
+
* node.out('path').setValue(out);
|
|
96
|
+
*/
|
|
97
|
+
export declare function saveImage(source: HTMLCanvasElement | OffscreenCanvas | Blob | ImageSource, path: string, options?: SaveOptions): Promise<string>;
|
|
98
|
+
/** Write any bytes into the project cache and return the path. */
|
|
99
|
+
export declare function saveBytes(data: Blob | ArrayBuffer | Uint8Array, path: string): Promise<string>;
|
|
100
|
+
/**
|
|
101
|
+
* The output path convention, shared with the Python side: scoped by node id so
|
|
102
|
+
* one node's scratch file never lands on a sibling's. The server puts the cache
|
|
103
|
+
* key into the filename when it writes, so different inputs keep separate files.
|
|
104
|
+
*/
|
|
105
|
+
export declare function cachePath(nodeId: string, suffix: string): string;
|
|
106
|
+
/**
|
|
107
|
+
* Float pixel data, shared between Python and JavaScript with no conversion.
|
|
108
|
+
*
|
|
109
|
+
* This works because a `.npy` file holding little-endian float32 in C order IS
|
|
110
|
+
* a Float32Array with a header in front of it. numpy pads that header so the
|
|
111
|
+
* data begins on a 64-byte boundary — measured on this project's own files, it
|
|
112
|
+
* starts at byte 128 every time — which means a typed array can be constructed
|
|
113
|
+
* as a VIEW onto the fetched buffer rather than a copy of it. A 10 MB density
|
|
114
|
+
* field costs one fetch and no marshalling.
|
|
115
|
+
*
|
|
116
|
+
* From there it goes straight where you need it: into `texImage2D` as an R32F
|
|
117
|
+
* or RGBA32F texture, into a compute pass, or through `toImageData` for
|
|
118
|
+
* Canvas2D. And `saveFloats` writes the same layout back, so `np.load` on the
|
|
119
|
+
* Python side reads it as an ordinary array.
|
|
120
|
+
*/
|
|
121
|
+
export interface FloatImage {
|
|
122
|
+
data: Float32Array;
|
|
123
|
+
/** [width, height] */
|
|
124
|
+
size: [number, number];
|
|
125
|
+
/** 1 for a mask or signal, 3 for RGB, 4 for RGBA. */
|
|
126
|
+
channels: number;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Read a float image as a zero-copy Float32Array view. Non-float and
|
|
130
|
+
* non-native-endian arrays are converted rather than refused, so a uint8 mask
|
|
131
|
+
* still loads — it just costs a pass.
|
|
132
|
+
*/
|
|
133
|
+
export declare function loadFloats(path: string): Promise<FloatImage>;
|
|
134
|
+
/** Write float pixels back into the project. Python reads the result with a
|
|
135
|
+
* plain `np.load`. Returns the stored path. */
|
|
136
|
+
export declare function saveFloats(image: FloatImage, path: string): Promise<string>;
|
|
137
|
+
/**
|
|
138
|
+
* Float pixels -> ImageData for Canvas2D. Values are assumed to be 0..1 unless
|
|
139
|
+
* a range is given; pass the field's actual range to see it the way the viewer
|
|
140
|
+
* does rather than clipped.
|
|
141
|
+
*/
|
|
142
|
+
export declare function toImageData(image: FloatImage, range?: [number, number]): ImageData;
|
|
143
|
+
/**
|
|
144
|
+
* Upload float pixels as a GPU texture, no conversion on the way. Needs the
|
|
145
|
+
* EXT_color_buffer_float extension to render INTO one; sampling works without.
|
|
146
|
+
*/
|
|
147
|
+
export declare function uploadFloatTexture(gl: WebGL2RenderingContext, image: FloatImage): WebGLTexture;
|
|
148
|
+
/**
|
|
149
|
+
* A live GPU texture, passed between browser nodes.
|
|
150
|
+
*
|
|
151
|
+
* Separate from `image` on purpose, and the distinction is about cost rather
|
|
152
|
+
* than taste. An `image` is file-backed: durable, portable, readable by Python,
|
|
153
|
+
* and it costs a write and a read every time it is handed on. A `texture` never
|
|
154
|
+
* leaves the page — a chain of WebGL nodes passes one along with no round trip
|
|
155
|
+
* at all, which is what makes an interactive shader chain feel immediate.
|
|
156
|
+
*
|
|
157
|
+
* Because it is session-bound it cannot be cached, cannot be inspected after a
|
|
158
|
+
* reload, and cannot be read by a Python stage. So the conversions are explicit:
|
|
159
|
+
* `toImage` is a GPU readback and a file write, and it should be visible in the
|
|
160
|
+
* graph rather than happening silently on a connection.
|
|
161
|
+
*/
|
|
162
|
+
export interface TextureRef {
|
|
163
|
+
gl: WebGL2RenderingContext;
|
|
164
|
+
texture: WebGLTexture;
|
|
165
|
+
/** [width, height] */
|
|
166
|
+
size: [number, number];
|
|
167
|
+
channels: number;
|
|
168
|
+
/** True when the texture holds float data rather than 8-bit. */
|
|
169
|
+
float: boolean;
|
|
170
|
+
}
|
|
171
|
+
/** Upload an image file to the GPU. The integer path for `uploadFloatTexture`. */
|
|
172
|
+
export declare function textureFromImage(gl: WebGL2RenderingContext, image: string | {
|
|
173
|
+
path: string;
|
|
174
|
+
}, options?: MediaOptions): Promise<TextureRef>;
|
|
175
|
+
/** Allocate an empty texture to render into. */
|
|
176
|
+
export declare function createTexture(gl: WebGL2RenderingContext, size: [number, number], options?: {
|
|
177
|
+
float?: boolean;
|
|
178
|
+
channels?: number;
|
|
179
|
+
}): TextureRef;
|
|
180
|
+
/**
|
|
181
|
+
* Read a texture back off the GPU and write it into the project as an image.
|
|
182
|
+
* The one place a texture becomes durable — deliberately a named step, because
|
|
183
|
+
* a readback stalls the pipeline and a write costs a file.
|
|
184
|
+
*/
|
|
185
|
+
export declare function textureToImage(ref: TextureRef, path: string, options?: {
|
|
186
|
+
format?: 'png' | 'webp';
|
|
187
|
+
space?: 'srgb' | 'linear';
|
|
188
|
+
}): Promise<ImageRef>;
|
|
189
|
+
/** The value on an `image` port. Mirrors Cascade's core ImageRef. */
|
|
190
|
+
export interface ImageRef {
|
|
191
|
+
path: string;
|
|
192
|
+
size: [number, number];
|
|
193
|
+
channels: 'r' | 'a' | 'rg' | 'rgb' | 'rgba';
|
|
194
|
+
depth: 'u8' | 'u16' | 'f16' | 'f32';
|
|
195
|
+
space: 'srgb' | 'linear';
|
|
196
|
+
}
|