@field/cascade 0.2.22 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +47 -7
- package/ARCHITECTURE.md +30 -2
- package/CHANGELOG.md +107 -0
- package/DESIGN.md +10 -5
- package/README.md +103 -7
- package/dist/assets/CodeEditor-D22fVi-4.js +623 -0
- package/dist/assets/CodePanel-DPNEYfcR.js +2 -0
- package/dist/assets/CustomNodeDialog-DeHCAvgb.js +1 -0
- package/dist/assets/{ExportDialog-DWN4nhXI.js → ExportDialog-BJu2Bpvv.js} +2 -2
- package/dist/assets/ProjectSettings-DWpVtWEx.js +2 -0
- package/dist/assets/SettingsDialog-za00IS3L.js +1 -0
- package/dist/assets/VersionHistoryDialog-DTMGhI0_.js +3 -0
- package/dist/assets/{core-Bji4aZUU.js → core-JsyKMO1k.js} +29 -38
- package/dist/assets/{cssMode-B6Z_Q1Kh.js → cssMode-emdRheFK.js} +1 -1
- package/dist/assets/{editor.api-jMoW_43X.js → editor.api-xHX-fMSo.js} +1 -1
- package/dist/assets/{freemarker2-DvvPV6m5.js → freemarker2-C3M8_Gs3.js} +1 -1
- package/dist/assets/geo-BAR6lQp5.js +1 -0
- package/dist/assets/{handlebars-B8rfuSIa.js → handlebars-CBMurtFk.js} +1 -1
- package/dist/assets/{html-DyHm6JCp.js → html-xbLQwknw.js} +1 -1
- package/dist/assets/{htmlMode-E9XLWE5u.js → htmlMode-2wKdDTon.js} +1 -1
- package/dist/assets/{image-BUcQC7PG.js → image-i1j6nWKe.js} +26 -40
- package/dist/assets/index-BDV5xGBZ.css +1 -0
- package/dist/assets/index-CMcH8AgP.js +212 -0
- package/dist/assets/index-client-BoJgsN9M.js +1 -0
- package/dist/assets/{javascript-C_bM5PCN.js → javascript-DJaOo_2I.js} +1 -1
- package/dist/assets/{jsonMode-BdyVS7G4.js → jsonMode-Bheas8_0.js} +1 -1
- package/dist/assets/{legacy-DIu3XRwl.js → legacy-CAj2_7QE.js} +2 -2
- package/dist/assets/{liquid-CDkn3spA.js → liquid-BCj_BGbM.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-AB5DMAsr.js → lspLanguageFeatures-BfIsx_xz.js} +1 -1
- package/dist/assets/{mdx-BvH_NjDN.js → mdx-DwiQDiVO.js} +1 -1
- package/dist/assets/{nodeTypeUtils-CweA3NHd.js → nodeTypeUtils-BBA1JqHx.js} +1 -1
- package/dist/assets/{python-B7uii4fi.js → python-BjrB5nqj.js} +1 -1
- package/dist/assets/{razor-Z48fWdJc.js → razor-CfoBmGDs.js} +1 -1
- package/dist/assets/{tsMode-BDgacVG4.js → tsMode-BcEoMSxp.js} +1 -1
- package/dist/assets/{typescript-Derb6Oqa.js → typescript-DIg7TEmQ.js} +1 -1
- package/dist/assets/{workers-BtoMX_pm.js → workers-vXYuwtPw.js} +1 -1
- package/dist/assets/{xml-B1CaFSrG.js → xml-DexLQIQy.js} +1 -1
- package/dist/assets/{yaml-HmIeA0KH.js → yaml-DUnD_1dE.js} +1 -1
- package/dist/cli/index.js +16467 -10200
- package/dist/cli/runtime/gpu.ts +76 -0
- 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/capabilities.d.ts +69 -6
- package/dist/contracts/definition.d.ts +60 -2
- 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/schema.js +8 -5
- package/dist/contracts/validate.d.ts +10 -1
- package/dist/contracts/validate.js +20 -3
- package/dist/gpu.d.ts +71 -0
- package/dist/gpu.js +53 -0
- 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 +42 -0
- package/dist/runtime/runtime.js +351 -29
- package/dist/runtime/types.d.ts +32 -6
- package/dist/shell.d.ts +3 -3
- package/doc/NODE_REFERENCE.md +395 -0
- package/doc/PROJECT_AUTHORING.md +148 -6
- package/package.json +30 -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/gpu.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cascade/gpu` — WebGPU's flag namespaces as plain values.
|
|
3
|
+
*
|
|
4
|
+
* Small, and it was load-bearing. TypeScript's DOM library declares the WebGPU
|
|
5
|
+
* *interfaces* but not the constant objects, so the only way to reach
|
|
6
|
+
* `GPUTextureUsage.RENDER_ATTACHMENT` was off `globalThis` — which
|
|
7
|
+
* `architecture/ambient-state` correctly forbids inside a node module, because
|
|
8
|
+
* an ambient read makes a node's inputs invisible to the static reader that
|
|
9
|
+
* lets `cascade check` say anything without cooking.
|
|
10
|
+
*
|
|
11
|
+
* So a sketch had to choose between a rule and a working shader, and it chose
|
|
12
|
+
* a project library file to hide the read in. Both stay honest now: the rule
|
|
13
|
+
* keeps its teeth and a node imports these instead. That is the whole reason
|
|
14
|
+
* this file exists, and it is why it had to land with the capability rather
|
|
15
|
+
* than after it — a rule whose only workaround is removed at the same moment
|
|
16
|
+
* is a rule; one enforced a release earlier is a nuisance.
|
|
17
|
+
*
|
|
18
|
+
* The values are fixed by the WebGPU specification, not by an implementation,
|
|
19
|
+
* which is what makes writing them down safe rather than a guess. They are
|
|
20
|
+
* checked against a real browser's own globals in `tests/gpu-usage-flags.test.ts`
|
|
21
|
+
* — the numbers below were verified against Chrome rather than recalled.
|
|
22
|
+
*/
|
|
23
|
+
/** `GPUBufferUsage`. */
|
|
24
|
+
export declare const BufferUsage: {
|
|
25
|
+
readonly MAP_READ: 1;
|
|
26
|
+
readonly MAP_WRITE: 2;
|
|
27
|
+
readonly COPY_SRC: 4;
|
|
28
|
+
readonly COPY_DST: 8;
|
|
29
|
+
readonly INDEX: 16;
|
|
30
|
+
readonly VERTEX: 32;
|
|
31
|
+
readonly UNIFORM: 64;
|
|
32
|
+
readonly STORAGE: 128;
|
|
33
|
+
readonly INDIRECT: 256;
|
|
34
|
+
readonly QUERY_RESOLVE: 512;
|
|
35
|
+
};
|
|
36
|
+
/** `GPUTextureUsage`. */
|
|
37
|
+
export declare const TextureUsage: {
|
|
38
|
+
readonly COPY_SRC: 1;
|
|
39
|
+
readonly COPY_DST: 2;
|
|
40
|
+
readonly TEXTURE_BINDING: 4;
|
|
41
|
+
readonly STORAGE_BINDING: 8;
|
|
42
|
+
readonly RENDER_ATTACHMENT: 16;
|
|
43
|
+
/**
|
|
44
|
+
* Chrome 147 has this and it is newer than the rest — verified present
|
|
45
|
+
* 2026-09-08 alongside the others, but do not assume every implementation
|
|
46
|
+
* carries it. Included rather than omitted deliberately: leaving it out
|
|
47
|
+
* would send a node that wants it back to `globalThis`, which is the exact
|
|
48
|
+
* ambient read this file exists to remove, so an honest constant with a
|
|
49
|
+
* caveat beats an omission that forces a rule violation.
|
|
50
|
+
*/
|
|
51
|
+
readonly TRANSIENT_ATTACHMENT: 32;
|
|
52
|
+
};
|
|
53
|
+
/** `GPUShaderStage`, for a bind group layout entry's `visibility`. */
|
|
54
|
+
export declare const ShaderStage: {
|
|
55
|
+
readonly VERTEX: 1;
|
|
56
|
+
readonly FRAGMENT: 2;
|
|
57
|
+
readonly COMPUTE: 4;
|
|
58
|
+
};
|
|
59
|
+
/** `GPUMapMode`, for `buffer.mapAsync`. */
|
|
60
|
+
export declare const MapMode: {
|
|
61
|
+
readonly READ: 1;
|
|
62
|
+
readonly WRITE: 2;
|
|
63
|
+
};
|
|
64
|
+
/** `GPUColorWrite`, for a colour target's `writeMask`. */
|
|
65
|
+
export declare const ColorWrite: {
|
|
66
|
+
readonly RED: 1;
|
|
67
|
+
readonly GREEN: 2;
|
|
68
|
+
readonly BLUE: 4;
|
|
69
|
+
readonly ALPHA: 8;
|
|
70
|
+
readonly ALL: 15;
|
|
71
|
+
};
|
package/dist/gpu.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// server/src/runtime/gpu.ts
|
|
2
|
+
var BufferUsage = {
|
|
3
|
+
MAP_READ: 1,
|
|
4
|
+
MAP_WRITE: 2,
|
|
5
|
+
COPY_SRC: 4,
|
|
6
|
+
COPY_DST: 8,
|
|
7
|
+
INDEX: 16,
|
|
8
|
+
VERTEX: 32,
|
|
9
|
+
UNIFORM: 64,
|
|
10
|
+
STORAGE: 128,
|
|
11
|
+
INDIRECT: 256,
|
|
12
|
+
QUERY_RESOLVE: 512
|
|
13
|
+
};
|
|
14
|
+
var TextureUsage = {
|
|
15
|
+
COPY_SRC: 1,
|
|
16
|
+
COPY_DST: 2,
|
|
17
|
+
TEXTURE_BINDING: 4,
|
|
18
|
+
STORAGE_BINDING: 8,
|
|
19
|
+
RENDER_ATTACHMENT: 16,
|
|
20
|
+
/**
|
|
21
|
+
* Chrome 147 has this and it is newer than the rest — verified present
|
|
22
|
+
* 2026-09-08 alongside the others, but do not assume every implementation
|
|
23
|
+
* carries it. Included rather than omitted deliberately: leaving it out
|
|
24
|
+
* would send a node that wants it back to `globalThis`, which is the exact
|
|
25
|
+
* ambient read this file exists to remove, so an honest constant with a
|
|
26
|
+
* caveat beats an omission that forces a rule violation.
|
|
27
|
+
*/
|
|
28
|
+
TRANSIENT_ATTACHMENT: 32
|
|
29
|
+
};
|
|
30
|
+
var ShaderStage = {
|
|
31
|
+
VERTEX: 1,
|
|
32
|
+
FRAGMENT: 2,
|
|
33
|
+
COMPUTE: 4
|
|
34
|
+
};
|
|
35
|
+
var MapMode = {
|
|
36
|
+
READ: 1,
|
|
37
|
+
WRITE: 2
|
|
38
|
+
};
|
|
39
|
+
var ColorWrite = {
|
|
40
|
+
RED: 1,
|
|
41
|
+
GREEN: 2,
|
|
42
|
+
BLUE: 4,
|
|
43
|
+
ALPHA: 8,
|
|
44
|
+
ALL: 15
|
|
45
|
+
};
|
|
46
|
+
export {
|
|
47
|
+
BufferUsage,
|
|
48
|
+
ColorWrite,
|
|
49
|
+
MapMode,
|
|
50
|
+
ShaderStage,
|
|
51
|
+
TextureUsage
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=gpu.js.map
|
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-CMcH8AgP.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
|
+
}
|
package/dist/io.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// server/src/runtime/io.ts
|
|
2
|
+
function mediaUrl(path, options = {}) {
|
|
3
|
+
if (/^(https?:|data:|blob:)/.test(path)) return path;
|
|
4
|
+
const clean = path.replace(/^\.?\//, "");
|
|
5
|
+
const encoded = clean.split("/").map(encodeURIComponent).join("/");
|
|
6
|
+
const params = new URLSearchParams();
|
|
7
|
+
if (options.raw) params.set("raw", "1");
|
|
8
|
+
else {
|
|
9
|
+
if (options.width) params.set("w", String(options.width));
|
|
10
|
+
if (options.format) params.set("fmt", options.format);
|
|
11
|
+
if (options.quality) params.set("q", String(options.quality));
|
|
12
|
+
}
|
|
13
|
+
const query = params.toString();
|
|
14
|
+
return `/api/media/${encoded}${query ? `?${query}` : ""}`;
|
|
15
|
+
}
|
|
16
|
+
var IO_BRIDGE_KEY = "__cascadeIoBridge";
|
|
17
|
+
function installIoBridge(bridge) {
|
|
18
|
+
const globals = globalThis;
|
|
19
|
+
const previous = globals[IO_BRIDGE_KEY];
|
|
20
|
+
globals[IO_BRIDGE_KEY] = bridge ?? void 0;
|
|
21
|
+
return () => {
|
|
22
|
+
globals[IO_BRIDGE_KEY] = previous;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function localIo() {
|
|
26
|
+
const bridge = globalThis[IO_BRIDGE_KEY];
|
|
27
|
+
return bridge && typeof bridge.read === "function" && typeof bridge.write === "function" ? bridge : null;
|
|
28
|
+
}
|
|
29
|
+
async function loadBytes(path, options = { raw: true }) {
|
|
30
|
+
const bridge = localIo();
|
|
31
|
+
if (bridge) {
|
|
32
|
+
const bytes = await bridge.read(path, options);
|
|
33
|
+
const exact = bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength ? bytes.buffer : bytes.slice().buffer;
|
|
34
|
+
return exact;
|
|
35
|
+
}
|
|
36
|
+
const response = await fetch(mediaUrl(path, options));
|
|
37
|
+
if (!response.ok) throw new Error(`cascade/io: cannot load ${path} (${response.status})`);
|
|
38
|
+
return await response.arrayBuffer();
|
|
39
|
+
}
|
|
40
|
+
async function loadImage(path, options = { raw: true }) {
|
|
41
|
+
const image = new Image();
|
|
42
|
+
const bridge = localIo();
|
|
43
|
+
if (bridge) {
|
|
44
|
+
image.src = new Uint8Array(await loadBytes(path, options));
|
|
45
|
+
} else {
|
|
46
|
+
image.crossOrigin = "anonymous";
|
|
47
|
+
image.src = mediaUrl(path, options);
|
|
48
|
+
}
|
|
49
|
+
await image.decode();
|
|
50
|
+
return image;
|
|
51
|
+
}
|
|
52
|
+
async function loadBitmap(path, options = { raw: true }) {
|
|
53
|
+
if (localIo()) return createImageBitmap(new Blob([await loadBytes(path, options)]));
|
|
54
|
+
const response = await fetch(mediaUrl(path, options));
|
|
55
|
+
if (!response.ok) throw new Error(`cascade/io: cannot load ${path} (${response.status})`);
|
|
56
|
+
return createImageBitmap(await response.blob());
|
|
57
|
+
}
|
|
58
|
+
async function loadFieldBitmap(path, options = {}) {
|
|
59
|
+
return loadBitmap(path, { ...options, raw: false });
|
|
60
|
+
}
|
|
61
|
+
async function saveImage(source, path, options = {}) {
|
|
62
|
+
const format = options.format ?? "png";
|
|
63
|
+
const type = `image/${format}`;
|
|
64
|
+
let blob;
|
|
65
|
+
if (source instanceof Blob) {
|
|
66
|
+
blob = source;
|
|
67
|
+
} else if (typeof source.convertToBlob === "function") {
|
|
68
|
+
blob = await source.convertToBlob({ type, quality: options.quality });
|
|
69
|
+
} else if (typeof source.encode === "function") {
|
|
70
|
+
blob = new Blob([await source.encode(format, options.quality)], { type });
|
|
71
|
+
} else if (typeof source.toBuffer === "function") {
|
|
72
|
+
blob = new Blob([source.toBuffer(type)], { type });
|
|
73
|
+
} else {
|
|
74
|
+
blob = await new Promise((resolve, reject) => {
|
|
75
|
+
source.toBlob(
|
|
76
|
+
(result) => result ? resolve(result) : reject(new Error("cascade/io: canvas produced no data")),
|
|
77
|
+
type,
|
|
78
|
+
options.quality
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return saveBytes(blob, path);
|
|
83
|
+
}
|
|
84
|
+
async function saveBytes(data, path) {
|
|
85
|
+
const clean = path.replace(/^\.?\//, "");
|
|
86
|
+
const bridge = localIo();
|
|
87
|
+
if (bridge) {
|
|
88
|
+
const bytes = data instanceof Uint8Array ? data : new Uint8Array(data instanceof Blob ? await data.arrayBuffer() : data);
|
|
89
|
+
return await bridge.write(clean, bytes);
|
|
90
|
+
}
|
|
91
|
+
const response = await fetch(`/api/media/${clean.split("/").map(encodeURIComponent).join("/")}`, {
|
|
92
|
+
method: "PUT",
|
|
93
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
94
|
+
body: data
|
|
95
|
+
});
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw new Error(`cascade/io: cannot write ${path} \u2014 ${await response.text()}`);
|
|
98
|
+
}
|
|
99
|
+
const result = await response.json();
|
|
100
|
+
return result.path;
|
|
101
|
+
}
|
|
102
|
+
function cachePath(nodeId, suffix) {
|
|
103
|
+
const safe = nodeId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
104
|
+
return `.cascade-cache/${safe}${suffix}`;
|
|
105
|
+
}
|
|
106
|
+
function parseNpyHeader(buffer) {
|
|
107
|
+
const bytes = new Uint8Array(buffer);
|
|
108
|
+
const magic = String.fromCharCode(...bytes.subarray(0, 6));
|
|
109
|
+
if (magic !== "\x93NUMPY") throw new Error("cascade/io: not a .npy file");
|
|
110
|
+
const view = new DataView(buffer);
|
|
111
|
+
const major = bytes[6];
|
|
112
|
+
const headerLength = major >= 2 ? view.getUint32(8, true) : view.getUint16(8, true);
|
|
113
|
+
const headerStart = major >= 2 ? 12 : 10;
|
|
114
|
+
const header = String.fromCharCode(...bytes.subarray(headerStart, headerStart + headerLength));
|
|
115
|
+
const dtype = header.match(/'descr':\s*'([^']+)'/)?.[1];
|
|
116
|
+
if (!dtype) throw new Error("cascade/io: .npy header has no dtype");
|
|
117
|
+
const shape = (header.match(/'shape':\s*\(([^)]*)\)/)?.[1] ?? "").split(",").map((part) => part.trim()).filter(Boolean).map(Number);
|
|
118
|
+
return {
|
|
119
|
+
dtype,
|
|
120
|
+
shape,
|
|
121
|
+
dataOffset: headerStart + headerLength,
|
|
122
|
+
fortran: /'fortran_order':\s*True/.test(header)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function loadFloats(path) {
|
|
126
|
+
const buffer = await loadBytes(path, { raw: true });
|
|
127
|
+
const header = parseNpyHeader(buffer);
|
|
128
|
+
if (header.fortran) throw new Error("cascade/io: Fortran-ordered .npy is not supported");
|
|
129
|
+
if (header.shape.length < 2) throw new Error(`cascade/io: expected an image-shaped array, got ${header.shape.length}D`);
|
|
130
|
+
const [height, width] = header.shape;
|
|
131
|
+
const channels = header.shape.length > 2 ? header.shape.slice(2).reduce((a, b) => a * b, 1) : 1;
|
|
132
|
+
const count = width * height * channels;
|
|
133
|
+
let data;
|
|
134
|
+
if (header.dtype === "<f4" || header.dtype === "=f4") {
|
|
135
|
+
data = new Float32Array(buffer, header.dataOffset, count);
|
|
136
|
+
} else {
|
|
137
|
+
const source = readAs(buffer, header.dataOffset, header.dtype, count);
|
|
138
|
+
data = source instanceof Float32Array ? source : Float32Array.from(source);
|
|
139
|
+
}
|
|
140
|
+
return { data, size: [width, height], channels };
|
|
141
|
+
}
|
|
142
|
+
function readAs(buffer, offset, dtype, count) {
|
|
143
|
+
switch (dtype) {
|
|
144
|
+
case "<f8":
|
|
145
|
+
case "=f8":
|
|
146
|
+
return new Float64Array(buffer, offset, count);
|
|
147
|
+
case "|u1":
|
|
148
|
+
case "<u1":
|
|
149
|
+
case "=u1":
|
|
150
|
+
case "|b1":
|
|
151
|
+
return new Uint8Array(buffer, offset, count);
|
|
152
|
+
case "|i1":
|
|
153
|
+
return new Int8Array(buffer, offset, count);
|
|
154
|
+
case "<i2":
|
|
155
|
+
return new Int16Array(buffer, offset, count);
|
|
156
|
+
case "<u2":
|
|
157
|
+
return new Uint16Array(buffer, offset, count);
|
|
158
|
+
case "<i4":
|
|
159
|
+
return new Int32Array(buffer, offset, count);
|
|
160
|
+
case "<u4":
|
|
161
|
+
return new Uint32Array(buffer, offset, count);
|
|
162
|
+
default:
|
|
163
|
+
throw new Error(`cascade/io: .npy dtype ${dtype} is not supported`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function encodeNpy(data, shape) {
|
|
167
|
+
const dict = `{'descr': '<f4', 'fortran_order': False, 'shape': (${shape.join(", ")}${shape.length === 1 ? "," : ""}), }`;
|
|
168
|
+
const unpadded = 10 + dict.length + 1;
|
|
169
|
+
const padding = (64 - unpadded % 64) % 64;
|
|
170
|
+
const header = dict + " ".repeat(padding) + "\n";
|
|
171
|
+
const out = new Uint8Array(10 + header.length + data.byteLength);
|
|
172
|
+
const view = new DataView(out.buffer);
|
|
173
|
+
out.set([147, 78, 85, 77, 80, 89, 1, 0], 0);
|
|
174
|
+
view.setUint16(8, header.length, true);
|
|
175
|
+
for (let i = 0; i < header.length; i++) out[10 + i] = header.charCodeAt(i);
|
|
176
|
+
out.set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), 10 + header.length);
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
async function saveFloats(image, path) {
|
|
180
|
+
const [width, height] = image.size;
|
|
181
|
+
const shape = image.channels > 1 ? [height, width, image.channels] : [height, width];
|
|
182
|
+
return saveBytes(encodeNpy(image.data, shape), path);
|
|
183
|
+
}
|
|
184
|
+
function toImageData(image, range = [0, 1]) {
|
|
185
|
+
const [width, height] = image.size;
|
|
186
|
+
const [low, high] = range;
|
|
187
|
+
const span = high - low || 1;
|
|
188
|
+
const out = new Uint8ClampedArray(width * height * 4);
|
|
189
|
+
for (let i = 0; i < width * height; i++) {
|
|
190
|
+
const base = i * image.channels;
|
|
191
|
+
const r = (image.data[base] - low) / span * 255;
|
|
192
|
+
const g = image.channels > 1 ? (image.data[base + 1] - low) / span * 255 : r;
|
|
193
|
+
const b = image.channels > 2 ? (image.data[base + 2] - low) / span * 255 : r;
|
|
194
|
+
const a = image.channels > 3 ? image.data[base + 3] * 255 : 255;
|
|
195
|
+
out[i * 4] = r;
|
|
196
|
+
out[i * 4 + 1] = g;
|
|
197
|
+
out[i * 4 + 2] = b;
|
|
198
|
+
out[i * 4 + 3] = a;
|
|
199
|
+
}
|
|
200
|
+
return new ImageData(out, width, height);
|
|
201
|
+
}
|
|
202
|
+
function uploadFloatTexture(gl, image) {
|
|
203
|
+
const [width, height] = image.size;
|
|
204
|
+
const formats = {
|
|
205
|
+
1: [gl.R32F, gl.RED],
|
|
206
|
+
2: [gl.RG32F, gl.RG],
|
|
207
|
+
3: [gl.RGB32F, gl.RGB],
|
|
208
|
+
4: [gl.RGBA32F, gl.RGBA]
|
|
209
|
+
};
|
|
210
|
+
const pair = formats[image.channels];
|
|
211
|
+
if (!pair) throw new Error(`cascade/io: ${image.channels} channels has no texture format`);
|
|
212
|
+
const [internalFormat, format] = pair;
|
|
213
|
+
const texture = gl.createTexture();
|
|
214
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
215
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
216
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
217
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
218
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
219
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
220
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width, height, 0, format, gl.FLOAT, image.data);
|
|
221
|
+
return texture;
|
|
222
|
+
}
|
|
223
|
+
async function textureFromImage(gl, image, options = { raw: true }) {
|
|
224
|
+
const path = typeof image === "string" ? image : image.path;
|
|
225
|
+
if (path.endsWith(".npy")) {
|
|
226
|
+
const floats = await loadFloats(path);
|
|
227
|
+
return {
|
|
228
|
+
gl,
|
|
229
|
+
texture: uploadFloatTexture(gl, floats),
|
|
230
|
+
size: floats.size,
|
|
231
|
+
channels: floats.channels,
|
|
232
|
+
float: true
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const bitmap = await loadBitmap(path, options);
|
|
236
|
+
const texture = gl.createTexture();
|
|
237
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
238
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
239
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
240
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
241
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
242
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
|
|
243
|
+
const size = [bitmap.width, bitmap.height];
|
|
244
|
+
bitmap.close();
|
|
245
|
+
return { gl, texture, size, channels: 4, float: false };
|
|
246
|
+
}
|
|
247
|
+
function createTexture(gl, size, options = {}) {
|
|
248
|
+
const [width, height] = size;
|
|
249
|
+
const float = options.float ?? false;
|
|
250
|
+
const channels = options.channels ?? 4;
|
|
251
|
+
const texture = gl.createTexture();
|
|
252
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
253
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
254
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
255
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
256
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
257
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, float ? gl.RGBA32F : gl.RGBA8, width, height);
|
|
258
|
+
return { gl, texture, size, channels, float };
|
|
259
|
+
}
|
|
260
|
+
async function textureToImage(ref, path, options = {}) {
|
|
261
|
+
const { gl, size } = ref;
|
|
262
|
+
const [width, height] = size;
|
|
263
|
+
const framebuffer = gl.createFramebuffer();
|
|
264
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
265
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, ref.texture, 0);
|
|
266
|
+
try {
|
|
267
|
+
if (ref.float) {
|
|
268
|
+
const pixels2 = new Float32Array(width * height * 4);
|
|
269
|
+
gl.readPixels(0, 0, width, height, gl.RGBA, gl.FLOAT, pixels2);
|
|
270
|
+
const stored2 = await saveFloats({ data: pixels2, size, channels: 4 }, path.replace(/\.[^.]+$/, "") + ".npy");
|
|
271
|
+
return { path: stored2, size, channels: "rgba", depth: "f32", space: options.space ?? "linear" };
|
|
272
|
+
}
|
|
273
|
+
const pixels = new Uint8ClampedArray(width * height * 4);
|
|
274
|
+
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
|
275
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
276
|
+
canvas.getContext("2d").putImageData(new ImageData(pixels, width, height), 0, 0);
|
|
277
|
+
const format = options.format ?? "png";
|
|
278
|
+
const stored = await saveImage(canvas, path, { format });
|
|
279
|
+
return { path: stored, size, channels: "rgba", depth: "u8", space: options.space ?? "srgb" };
|
|
280
|
+
} finally {
|
|
281
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
282
|
+
gl.deleteFramebuffer(framebuffer);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
export {
|
|
286
|
+
cachePath,
|
|
287
|
+
createTexture,
|
|
288
|
+
installIoBridge,
|
|
289
|
+
loadBitmap,
|
|
290
|
+
loadBytes,
|
|
291
|
+
loadFieldBitmap,
|
|
292
|
+
loadFloats,
|
|
293
|
+
loadImage,
|
|
294
|
+
mediaUrl,
|
|
295
|
+
saveBytes,
|
|
296
|
+
saveFloats,
|
|
297
|
+
saveImage,
|
|
298
|
+
textureFromImage,
|
|
299
|
+
textureToImage,
|
|
300
|
+
toImageData,
|
|
301
|
+
uploadFloatTexture
|
|
302
|
+
};
|
|
303
|
+
//# sourceMappingURL=io.js.map
|
package/dist/net.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function authorizedFetch(credential: string, url: string, init?: RequestInit): Promise<Response>;
|
package/dist/net.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// server/src/runtime/net.ts
|
|
2
|
+
var capability = null;
|
|
3
|
+
async function authorizedFetch(credential, url, init = {}) {
|
|
4
|
+
capability ??= fetch("/api/net/capability").then(async (response2) => {
|
|
5
|
+
if (!response2.ok) throw new Error(`Authorized fetch capability failed: HTTP ${response2.status}`);
|
|
6
|
+
return (await response2.json()).capability;
|
|
7
|
+
});
|
|
8
|
+
const headers = Object.fromEntries(new Headers(init.headers).entries());
|
|
9
|
+
const response = await fetch("/api/net", {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: { "Content-Type": "application/json", "X-Cascade-Net-Capability": await capability },
|
|
12
|
+
body: JSON.stringify({
|
|
13
|
+
credential,
|
|
14
|
+
url,
|
|
15
|
+
method: init.method ?? "GET",
|
|
16
|
+
headers,
|
|
17
|
+
...typeof init.body === "string" ? { body: init.body } : {}
|
|
18
|
+
}),
|
|
19
|
+
signal: init.signal
|
|
20
|
+
});
|
|
21
|
+
const result = await response.json();
|
|
22
|
+
if (!response.ok || !result.ok) throw new Error(result.error ?? `Authorized fetch failed: HTTP ${response.status}`);
|
|
23
|
+
return new Response(result.body, { status: result.status, statusText: result.statusText, headers: result.headers });
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
authorizedFetch
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=net.js.map
|