@field/cascade 0.10.0 → 0.12.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 +25 -5
- package/ARCHITECTURE.md +5 -4
- package/CHANGELOG.md +382 -0
- package/README.md +1 -2
- package/dist/assets/AgentPanel-1JugFjE4.css +1 -0
- package/dist/assets/AgentPanel-BDL5MddA.js +5 -0
- package/dist/assets/{CodeEditor-DSZBpzdy.js → CodeEditor-C-xYG-Wn.js} +120 -120
- package/dist/assets/CodePanel-CtXd50m6.js +2 -0
- package/dist/assets/CookInfoPanel-B3NMhKJF.css +1 -0
- package/dist/assets/CookInfoPanel-DG4_TRsX.js +2 -0
- package/dist/assets/CustomNodeDialog-CHfZUs-r.js +1 -0
- package/dist/assets/DefinitionNode-CaRTXQ_b.js +1 -0
- package/dist/assets/DefinitionPanel-C3CxZY9r.js +1 -0
- package/dist/assets/DefinitionPanel-VNLnAiQD.css +1 -0
- package/dist/assets/OpenGraphDialog-BLdr6k_q.js +1 -0
- package/dist/assets/ProjectSettings-ZnvmIAir.js +2 -0
- package/dist/assets/{SettingsDialog-p25h3tj0.js → SettingsDialog-CBX3Wa87.js} +1 -1
- package/dist/assets/TimelinePanel-CDeR0arY.css +1 -0
- package/dist/assets/TimelinePanel-CWgnFH1e.js +1 -0
- package/dist/assets/{VersionHistoryDialog-DzFLUotB.js → VersionHistoryDialog-BOSoW1R-.js} +1 -1
- package/dist/assets/assert-BO2_erPD.js +1 -0
- package/dist/assets/assert-hvykGCnP.js +1 -0
- package/dist/assets/builder-BmBKcDix.js +1 -0
- package/dist/assets/camera-DFF7YA3F.js +1 -0
- package/dist/assets/checks-DAvyO9Gf.js +1 -0
- package/dist/assets/contactSheet-N1NcaWxw.js +2 -0
- package/dist/assets/{core-BICrEt0y.js → core-DB-MkGjj.js} +3 -4
- package/dist/assets/coreTypes-DtnDATZ7.js +1 -0
- package/dist/assets/{cssMode-D7TXw57I.js → cssMode-DPVckSOc.js} +1 -1
- package/dist/assets/definitions-B227e7f_.js +1 -0
- package/dist/assets/definitions-D0WOUJd7.js +1 -0
- package/dist/assets/{editor.api-DvkCO3ko.js → editor.api-Dkutg_Z1.js} +1 -1
- package/dist/assets/expressions-X-Z-GrgW.js +5 -0
- package/dist/assets/file-DAL6IfjV.js +1 -0
- package/dist/assets/filters-D3k1LRMa.js +1 -0
- package/dist/assets/{freemarker2-BA2lxsF1.js → freemarker2-CLiLKk3B.js} +1 -1
- package/dist/assets/generators-BhDxRAFC.js +1 -0
- package/dist/assets/geo-BsMImJIJ.js +1 -0
- package/dist/assets/geo-C4YngDob.js +2 -0
- package/dist/assets/{handlebars-BcDiyPd8.js → handlebars-CcD3aMeL.js} +1 -1
- package/dist/assets/{html-C7O4TyhX.js → html-DvZWGfqb.js} +1 -1
- package/dist/assets/{htmlMode-WaDGF3Mj.js → htmlMode-DUD3VaoN.js} +1 -1
- package/dist/assets/image-0SfH-snh.js +1 -0
- package/dist/assets/image-Ck-RdGU4.js +2 -0
- package/dist/assets/index-BWnrmIB9.js +137 -0
- package/dist/assets/index-DIaxEONI.css +1 -0
- package/dist/assets/io-BGmt2g0D.js +1 -0
- package/dist/assets/{javascript-CpIEK7tz.js → javascript-Cap2ahgh.js} +1 -1
- package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-B0uLjp9C.js} +1 -1
- package/dist/assets/{liquid-CN6FFHZE.js → liquid-CnxwhfLn.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-Dn9hlKX6.js} +1 -1
- package/dist/assets/math-GeULS-sk.js +1 -0
- package/dist/assets/{mdx-DE89pafV.js → mdx-BV3jbrVa.js} +1 -1
- package/dist/assets/pop-KMF_qloi.js +1 -0
- package/dist/assets/preload-helper-Czpn1I53.js +1 -0
- package/dist/assets/{python-ByTllTB9.js → python-CGyFk4XK.js} +1 -1
- package/dist/assets/{razor-USYlRRCE.js → razor-CQfzX3-N.js} +1 -1
- package/dist/assets/remap-5SjXQU-q.js +1 -0
- package/dist/assets/renderMarkdown-ork0Oir7.js +67 -0
- package/dist/assets/studioImages-CAyH-0Pl.js +1 -0
- package/dist/assets/text-ZpX7qNkx.js +2 -0
- package/dist/assets/time-P_OiBhcf.js +1 -0
- package/dist/assets/{tsMode-BXpN8iUz.js → tsMode-BOeXzO9H.js} +1 -1
- package/dist/assets/{typescript-DInBjGQQ.js → typescript-J1SnE_Tu.js} +1 -1
- package/dist/assets/{workers-C7Ye5B7D.js → workers-CYwwTiVF.js} +1 -1
- package/dist/assets/{xml-U0Uocqyo.js → xml-DnPRNzK9.js} +1 -1
- package/dist/assets/{yaml-CBw_PVYf.js → yaml-BmmYBNa9.js} +1 -1
- package/dist/cli/DefinitionNode-P7AA4TAU.js +15 -0
- package/dist/cli/Graph-OB6EWSGL.js +2975 -0
- package/dist/cli/assert-GROGQNCQ.js +25 -0
- package/dist/cli/checks-3WB44XBL.js +189 -0
- package/dist/cli/chunk-2JEU3MGB.js +1125 -0
- package/dist/cli/chunk-2KXB47FP.js +115 -0
- package/dist/cli/chunk-3RXL5GHB.js +816 -0
- package/dist/cli/chunk-53LGFPWX.js +138 -0
- package/dist/cli/chunk-5ORUQ6ES.js +267 -0
- package/dist/cli/chunk-73S4JZXK.js +336 -0
- package/dist/cli/chunk-7QE2ZNSN.js +121 -0
- package/dist/cli/chunk-BP7UKF36.js +376 -0
- package/dist/cli/chunk-BPODRH52.js +201 -0
- package/dist/cli/chunk-BZ63XQ3I.js +1301 -0
- package/dist/cli/chunk-CKS2FQWR.js +89 -0
- package/dist/cli/chunk-CO22XHNG.js +593 -0
- package/dist/cli/chunk-CTPLRSWF.js +2197 -0
- package/dist/cli/chunk-DEQSEFFH.js +153 -0
- package/dist/cli/chunk-EU4ZFZ7Y.js +122 -0
- package/dist/cli/chunk-EZRWJ4G2.js +104 -0
- package/dist/cli/chunk-FWMSGKEH.js +71 -0
- package/dist/cli/chunk-G23A2BZZ.js +705 -0
- package/dist/cli/chunk-GRIN2DDP.js +1465 -0
- package/dist/cli/chunk-HFHWYL55.js +139 -0
- package/dist/cli/chunk-HSNOBIVT.js +263 -0
- package/dist/cli/chunk-ICH7N5PN.js +26 -0
- package/dist/cli/chunk-J7VK7MNF.js +418 -0
- package/dist/cli/chunk-JVOCK5W4.js +83 -0
- package/dist/cli/chunk-KBGUO27Q.js +15 -0
- package/dist/cli/chunk-KH4MUBKF.js +1551 -0
- package/dist/cli/chunk-L6B6FUQP.js +330 -0
- package/dist/cli/chunk-LTXWBXOM.js +438 -0
- package/dist/cli/chunk-MI5JLQYA.js +220 -0
- package/dist/cli/chunk-MP653A5O.js +3766 -0
- package/dist/cli/chunk-NJFMDXHR.js +320 -0
- package/dist/cli/chunk-OM6BEME6.js +267 -0
- package/dist/cli/chunk-OWWBYLHC.js +136 -0
- package/dist/cli/chunk-QQPA2ZHH.js +273 -0
- package/dist/cli/chunk-RUU3YWXU.js +99 -0
- package/dist/cli/chunk-RXDIMXN5.js +98 -0
- package/dist/cli/chunk-ST6LROUG.js +267 -0
- package/dist/cli/chunk-TJW67QSI.js +225 -0
- package/dist/cli/chunk-WD62JIP6.js +118 -0
- package/dist/cli/chunk-WGKPJZDY.js +364 -0
- package/dist/cli/chunk-X4WKAOQP.js +634 -0
- package/dist/cli/chunk-X566FWBV.js +156 -0
- package/dist/cli/chunk-YH637PDL.js +317 -0
- package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
- package/dist/cli/chunk-ZSCUZE6I.js +112 -0
- package/dist/cli/cliCommands-JJN5Z3ZC.js +3470 -0
- package/dist/cli/commands-PHYFYA3F.js +1374 -0
- package/dist/cli/contactSheet-2PQVRUFW.js +11 -0
- package/dist/cli/definitions-BPOPW52L.js +32 -0
- package/dist/cli/doctorCommand-ORWQRNYP.js +106 -0
- package/dist/cli/edit-S5KEQ5RA.js +28 -0
- package/dist/cli/editorCommands-NFR32JOY.js +41 -0
- package/dist/cli/encode-ICPEPMH5.js +17 -0
- package/dist/cli/file-3HTQHPYQ.js +41 -0
- package/dist/cli/filters-6ODFWYK4.js +379 -0
- package/dist/cli/frames-LSAUBZ7P.js +16 -0
- package/dist/cli/generators-YU7GB4MD.js +210 -0
- package/dist/cli/geo-6ZPGKZRU.js +43 -0
- package/dist/cli/graphCommands-EOUOBOIJ.js +184 -0
- package/dist/cli/graphFile-YTGJB5F5.js +292 -0
- package/dist/cli/headlessCanvas-ENRHC7GS.js +16 -0
- package/dist/cli/helpCommand-PULH6ZYB.js +157 -0
- package/dist/cli/image-TXELZZK5.js +55 -0
- package/dist/cli/index.js +100 -30756
- package/dist/cli/math-RSYRQ3C6.js +16 -0
- package/dist/cli/mcp-JVZFQOEX.js +164 -0
- package/dist/cli/parity-X6DEH2DV.js +334 -0
- package/dist/cli/pop-B7SIIYKN.js +39 -0
- package/dist/cli/project-VYGIA7JX.js +12 -0
- package/dist/cli/projectDefinition-F3D7UIHJ.js +27 -0
- package/dist/cli/projectRuntime-2Q4JKEH3.js +55 -0
- package/dist/cli/registerStandardNodes-BXJCB6E4.js +744 -0
- package/dist/cli/render-PT4MWI6U.js +438 -0
- package/dist/cli/runCommand-HNPQVZMS.js +198 -0
- package/dist/cli/runner-5XPXVW7J.js +43 -0
- package/dist/cli/runtime/image.ts +302 -0
- package/dist/cli/runtime/io.ts +78 -0
- package/dist/cli/runtime/stage.ts +4 -1
- package/dist/cli/share-MHF6TNRK.js +166 -0
- package/dist/cli/stageCache-TY2BFZJR.js +15 -0
- package/dist/cli/stageRunner-KY2EJP7X.js +10 -0
- package/dist/cli/text-MB3EKR4H.js +126 -0
- package/dist/cli/verbs-PI2JH5FI.js +19 -0
- package/dist/cli/webBuild-WPPMGGW7.js +44 -0
- package/dist/contracts/capabilities.d.ts +18 -1
- package/dist/contracts/color.d.ts +8 -0
- package/dist/contracts/color.js +23 -0
- package/dist/contracts/definition.d.ts +10 -0
- package/dist/contracts/documentFormat.d.ts +18 -0
- package/dist/contracts/documentFormat.js +195 -0
- package/dist/contracts/editor.d.ts +17 -0
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/schema.d.ts +1 -1
- package/dist/contracts/schema.js +1 -0
- package/dist/contracts/validate.js +11 -1
- package/dist/contracts/values.d.ts +24 -0
- package/dist/image.d.ts +156 -0
- package/dist/image.js +161 -0
- package/dist/index.html +8 -2
- package/dist/io.d.ts +29 -0
- package/dist/io.js +40 -0
- package/dist/player/app.js +3 -2
- package/dist/player/editor.js +35 -9
- package/dist/player/editorEngine.js +3 -2
- package/dist/runtime/builtins/assert/checks.d.ts +18 -0
- package/dist/runtime/builtins/assert/checks.js +207 -0
- package/dist/runtime/builtins/assert/definitions.d.ts +130 -0
- package/dist/runtime/builtins/assert/definitions.js +54 -0
- package/dist/runtime/builtins/assert/index.d.ts +152 -0
- package/dist/runtime/builtins/assert/index.js +36 -0
- package/dist/runtime/builtins/image/contactSheet.d.ts +64 -0
- package/dist/runtime/builtins/image/contactSheet.js +150 -0
- package/dist/runtime/builtins/image/definitions.d.ts +950 -0
- package/dist/runtime/builtins/image/definitions.js +343 -0
- package/dist/runtime/builtins/image/file.d.ts +8 -0
- package/dist/runtime/builtins/image/file.js +37 -0
- package/dist/runtime/builtins/image/filters.d.ts +8 -0
- package/dist/runtime/builtins/image/filters.js +394 -0
- package/dist/runtime/builtins/image/generators.d.ts +7 -0
- package/dist/runtime/builtins/image/generators.js +144 -0
- package/dist/runtime/builtins/image/index.d.ts +952 -0
- package/dist/runtime/builtins/image/index.js +44 -0
- package/dist/runtime/builtins/image/io.d.ts +100 -0
- package/dist/runtime/builtins/image/io.js +142 -0
- package/dist/runtime/builtins/image/planar.d.ts +55 -0
- package/dist/runtime/builtins/image/planar.js +274 -0
- package/dist/runtime/builtins/image/simplex.d.ts +22 -0
- package/dist/runtime/builtins/image/simplex.js +92 -0
- package/dist/runtime/builtins/image/text.d.ts +4 -0
- package/dist/runtime/builtins/image/text.js +141 -0
- package/dist/runtime/builtins/index.d.ts +3 -0
- package/dist/runtime/builtins/index.js +7 -0
- package/dist/runtime/gpu.d.ts +25 -0
- package/dist/runtime/gpu.js +40 -0
- package/dist/runtime/runtime.d.ts +2 -0
- package/dist/runtime/runtime.js +268 -44
- package/dist/runtime/types.d.ts +30 -0
- package/dist/stage.js +4 -1
- package/doc/CLI_REFERENCE.md +91 -2
- package/doc/EDITOR_EXPORT.md +37 -2
- package/doc/MCP.md +18 -1
- package/doc/NODE_REFERENCE.md +424 -10
- package/doc/PROJECT_AUTHORING.md +222 -7
- package/package.json +19 -4
- package/dist/assets/CodePanel-sY6sZa7B.js +0 -2
- package/dist/assets/CustomNodeDialog-BRKnqZhp.js +0 -1
- package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
- package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
- package/dist/assets/OpenGraphDialog-ZsgL-iwr.js +0 -1
- package/dist/assets/ProjectSettings-Cw17nNe3.js +0 -2
- package/dist/assets/definitions-DUww2Rt-.js +0 -1
- package/dist/assets/geo-C2RHOVHC.js +0 -1
- package/dist/assets/image-DR4oeyJB.js +0 -2062
- package/dist/assets/index-7vae6YFC.css +0 -1
- package/dist/assets/index-DyvDC6pS.js +0 -219
- package/dist/assets/math-DaDEgWKt.js +0 -1
- package/dist/assets/pop-pLH7L0PW.js +0 -1
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/** The bodies of `cascade.assert.*`; the declarations are in `index.ts`. */
|
|
2
|
+
class AssertionFailed extends Error {
|
|
3
|
+
constructor(nodeId, moduleId, detail, message) {
|
|
4
|
+
const extra = typeof message === "string" && message.trim() ? ` ${message.trim()}` : "";
|
|
5
|
+
super(`Assertion failed in "${nodeId}" (${moduleId}): ${detail}.${extra}`);
|
|
6
|
+
this.name = "AssertionFailed";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function executeRange({ nodeId, inputs, props, outputs }) {
|
|
10
|
+
const value = inputs.value;
|
|
11
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
12
|
+
throw new AssertionFailed(nodeId, "cascade.assert.Range", `value ${String(value)} is not a finite number`, props.message);
|
|
13
|
+
}
|
|
14
|
+
if (value < props.min || value > props.max) {
|
|
15
|
+
throw new AssertionFailed(nodeId, "cascade.assert.Range", `value ${value} is outside [${props.min}, ${props.max}]`, props.message);
|
|
16
|
+
}
|
|
17
|
+
outputs.value.set(value);
|
|
18
|
+
}
|
|
19
|
+
export function executeEquals({ nodeId, inputs, props, outputs }) {
|
|
20
|
+
if (!deepEqual(inputs.value, props.expected, props.tolerance)) {
|
|
21
|
+
throw new AssertionFailed(nodeId, "cascade.assert.Equals", `got ${preview(inputs.value)}, expected ${preview(props.expected)}`, props.message);
|
|
22
|
+
}
|
|
23
|
+
outputs.value.set(inputs.value);
|
|
24
|
+
}
|
|
25
|
+
export function executeTrue({ nodeId, inputs, props, outputs }) {
|
|
26
|
+
if (inputs.value !== true)
|
|
27
|
+
throw new AssertionFailed(nodeId, "cascade.assert.True", `value is ${preview(inputs.value)}`, props.message);
|
|
28
|
+
outputs.value.set(true);
|
|
29
|
+
}
|
|
30
|
+
export async function executeNotBlank({ nodeId, inputs, props, outputs, capabilities, signal }) {
|
|
31
|
+
const image = inputs.image;
|
|
32
|
+
const ref = typeof image === "string" ? { path: image } : image;
|
|
33
|
+
if (!ref || typeof ref.path !== "string" || !ref.path) {
|
|
34
|
+
throw new AssertionFailed(nodeId, "cascade.assert.NotBlank", "no image arrived", props.message);
|
|
35
|
+
}
|
|
36
|
+
const path = ref.path;
|
|
37
|
+
const kept = capabilities.assets.imagePixels?.({ path });
|
|
38
|
+
const pixels = kept
|
|
39
|
+
? { width: kept.width, height: kept.height, channels: 4, data: kept.data }
|
|
40
|
+
: await decodeImage(await capabilities.assets.read({ path }, { signal }));
|
|
41
|
+
if (!pixels)
|
|
42
|
+
throw new AssertionFailed(nodeId, "cascade.assert.NotBlank", `${path} is not a PNG or PPM this node can read`, props.message);
|
|
43
|
+
const verdict = blankness(pixels, props.tolerance);
|
|
44
|
+
if (verdict)
|
|
45
|
+
throw new AssertionFailed(nodeId, "cascade.assert.NotBlank", `${path} (${pixels.width}x${pixels.height}) is ${verdict}`, props.message);
|
|
46
|
+
outputs.image.set(inputs.image);
|
|
47
|
+
}
|
|
48
|
+
function preview(value) {
|
|
49
|
+
const text = JSON.stringify(value) ?? String(value);
|
|
50
|
+
return text.length > 120 ? `${text.slice(0, 117)}...` : text;
|
|
51
|
+
}
|
|
52
|
+
function deepEqual(a, b, tolerance) {
|
|
53
|
+
if (typeof a === "number" && typeof b === "number")
|
|
54
|
+
return a === b || Math.abs(a - b) <= tolerance;
|
|
55
|
+
if (Array.isArray(a) && Array.isArray(b))
|
|
56
|
+
return a.length === b.length && a.every((item, index) => deepEqual(item, b[index], tolerance));
|
|
57
|
+
if (a && b && typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
|
|
58
|
+
const left = a;
|
|
59
|
+
const right = b;
|
|
60
|
+
const keys = Object.keys(left);
|
|
61
|
+
if (keys.length !== Object.keys(right).length)
|
|
62
|
+
return false;
|
|
63
|
+
return keys.every((key) => key in right && deepEqual(left[key], right[key], tolerance));
|
|
64
|
+
}
|
|
65
|
+
return a === b || (a == null && b == null);
|
|
66
|
+
}
|
|
67
|
+
/** 'one flat colour', 'fully transparent', or null when the image has content. */
|
|
68
|
+
export function blankness(pixels, tolerance = 0) {
|
|
69
|
+
const { channels, data } = pixels;
|
|
70
|
+
const alpha = channels === 2 || channels === 4 ? channels - 1 : -1;
|
|
71
|
+
const colour = alpha === -1 ? channels : channels - 1;
|
|
72
|
+
const min = new Array(channels).fill(255);
|
|
73
|
+
const max = new Array(channels).fill(0);
|
|
74
|
+
let opaque = alpha === -1;
|
|
75
|
+
for (let index = 0; index < data.length; index += channels) {
|
|
76
|
+
if (alpha !== -1 && data[index + alpha] > tolerance)
|
|
77
|
+
opaque = true;
|
|
78
|
+
for (let channel = 0; channel < channels; channel += 1) {
|
|
79
|
+
const value = data[index + channel];
|
|
80
|
+
if (value < min[channel])
|
|
81
|
+
min[channel] = value;
|
|
82
|
+
if (value > max[channel])
|
|
83
|
+
max[channel] = value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (!opaque)
|
|
87
|
+
return "fully transparent";
|
|
88
|
+
const spread = Math.max(...max.map((value, channel) => value - min[channel]));
|
|
89
|
+
if (spread <= tolerance)
|
|
90
|
+
return colour === 1 ? `one flat grey (${min[0]})` : `one flat colour (rgb ${min.slice(0, 3).join(", ")})`;
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
/** 8-bit non-interlaced PNG (grey, grey+alpha, RGB, RGBA) or binary PPM/PGM. */
|
|
94
|
+
export async function decodeImage(bytes) {
|
|
95
|
+
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)
|
|
96
|
+
return decodePng(bytes);
|
|
97
|
+
if (bytes[0] === 0x50 && (bytes[1] === 0x36 || bytes[1] === 0x35))
|
|
98
|
+
return decodePnm(bytes);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function decodePnm(bytes) {
|
|
102
|
+
const fields = [];
|
|
103
|
+
let index = 2;
|
|
104
|
+
while (fields.length < 3 && index < bytes.length) {
|
|
105
|
+
while (index < bytes.length && /\s/.test(String.fromCharCode(bytes[index])))
|
|
106
|
+
index += 1;
|
|
107
|
+
if (bytes[index] === 0x23) {
|
|
108
|
+
while (index < bytes.length && bytes[index] !== 0x0a)
|
|
109
|
+
index += 1;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
let field = "";
|
|
113
|
+
while (index < bytes.length && !/\s/.test(String.fromCharCode(bytes[index])))
|
|
114
|
+
field += String.fromCharCode(bytes[index++]);
|
|
115
|
+
fields.push(field);
|
|
116
|
+
}
|
|
117
|
+
const [width, height, maxValue] = fields.map(Number);
|
|
118
|
+
if (!width || !height || maxValue !== 255)
|
|
119
|
+
return null;
|
|
120
|
+
const channels = bytes[1] === 0x36 ? 3 : 1;
|
|
121
|
+
return { width, height, channels, data: bytes.subarray(index + 1, index + 1 + width * height * channels) };
|
|
122
|
+
}
|
|
123
|
+
/** zlib inflate with the realm's own `DecompressionStream`, in a page, a worker or Node. */
|
|
124
|
+
async function inflate(data) {
|
|
125
|
+
const globals = globalThis;
|
|
126
|
+
if (!globals.DecompressionStream || !globals.Blob || !globals.Response) {
|
|
127
|
+
throw new Error("cascade.assert.NotBlank: this host has no DecompressionStream to read a PNG with");
|
|
128
|
+
}
|
|
129
|
+
const stream = new globals.Blob([data]).stream().pipeThrough(new globals.DecompressionStream("deflate"));
|
|
130
|
+
return new Uint8Array(await new globals.Response(stream).arrayBuffer());
|
|
131
|
+
}
|
|
132
|
+
async function decodePng(bytes) {
|
|
133
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
134
|
+
let offset = 8;
|
|
135
|
+
let width = 0;
|
|
136
|
+
let height = 0;
|
|
137
|
+
let channels = 0;
|
|
138
|
+
const idat = [];
|
|
139
|
+
while (offset + 8 <= bytes.length) {
|
|
140
|
+
const length = view.getUint32(offset);
|
|
141
|
+
const type = String.fromCharCode(...bytes.subarray(offset + 4, offset + 8));
|
|
142
|
+
const body = bytes.subarray(offset + 8, offset + 8 + length);
|
|
143
|
+
if (type === "IHDR") {
|
|
144
|
+
width = view.getUint32(offset + 8);
|
|
145
|
+
height = view.getUint32(offset + 12);
|
|
146
|
+
const depth = body[8];
|
|
147
|
+
const colourType = body[9];
|
|
148
|
+
const interlace = body[12];
|
|
149
|
+
channels = { 0: 1, 4: 2, 2: 3, 6: 4 }[colourType] ?? 0;
|
|
150
|
+
if (depth !== 8 || !channels || interlace !== 0)
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
else if (type === "IDAT")
|
|
154
|
+
idat.push(body);
|
|
155
|
+
else if (type === "IEND")
|
|
156
|
+
break;
|
|
157
|
+
offset += 12 + length;
|
|
158
|
+
}
|
|
159
|
+
if (!width || !height || !idat.length)
|
|
160
|
+
return null;
|
|
161
|
+
const joined = new Uint8Array(idat.reduce((sum, part) => sum + part.length, 0));
|
|
162
|
+
let at = 0;
|
|
163
|
+
for (const part of idat) {
|
|
164
|
+
joined.set(part, at);
|
|
165
|
+
at += part.length;
|
|
166
|
+
}
|
|
167
|
+
const raw = await inflate(joined);
|
|
168
|
+
const stride = width * channels;
|
|
169
|
+
const data = new Uint8Array(stride * height);
|
|
170
|
+
for (let row = 0; row < height; row += 1) {
|
|
171
|
+
const filter = raw[row * (stride + 1)];
|
|
172
|
+
const line = raw.subarray(row * (stride + 1) + 1, (row + 1) * (stride + 1));
|
|
173
|
+
const out = data.subarray(row * stride, (row + 1) * stride);
|
|
174
|
+
const previous = row > 0 ? data.subarray((row - 1) * stride, row * stride) : new Uint8Array(stride);
|
|
175
|
+
for (let x = 0; x < stride; x += 1) {
|
|
176
|
+
const left = x >= channels ? out[x - channels] : 0;
|
|
177
|
+
const up = previous[x];
|
|
178
|
+
const upLeft = x >= channels ? previous[x - channels] : 0;
|
|
179
|
+
const value = line[x];
|
|
180
|
+
switch (filter) {
|
|
181
|
+
case 0:
|
|
182
|
+
out[x] = value;
|
|
183
|
+
break;
|
|
184
|
+
case 1:
|
|
185
|
+
out[x] = (value + left) & 255;
|
|
186
|
+
break;
|
|
187
|
+
case 2:
|
|
188
|
+
out[x] = (value + up) & 255;
|
|
189
|
+
break;
|
|
190
|
+
case 3:
|
|
191
|
+
out[x] = (value + ((left + up) >> 1)) & 255;
|
|
192
|
+
break;
|
|
193
|
+
case 4: {
|
|
194
|
+
const estimate = left + up - upLeft;
|
|
195
|
+
const a = Math.abs(estimate - left);
|
|
196
|
+
const b = Math.abs(estimate - up);
|
|
197
|
+
const c = Math.abs(estimate - upLeft);
|
|
198
|
+
out[x] = (value + (a <= b && a <= c ? left : b <= c ? up : upLeft)) & 255;
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
default: return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { width, height, channels, data };
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=checks.js.map
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/** The declarations of `cascade.assert.*`; see `index.ts`. */
|
|
2
|
+
export declare const rangeDefinition: {
|
|
3
|
+
readonly apiVersion: 1;
|
|
4
|
+
readonly runsOn: "portable";
|
|
5
|
+
readonly label: "Assert Range";
|
|
6
|
+
readonly description: "Fails the run unless value is a finite number in [min, max]; passes value through";
|
|
7
|
+
readonly icon: "ShieldCheck";
|
|
8
|
+
readonly inputs: {
|
|
9
|
+
readonly value: {
|
|
10
|
+
readonly kind: "data";
|
|
11
|
+
readonly type: "float";
|
|
12
|
+
readonly default: 0;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
readonly outputs: {
|
|
16
|
+
readonly value: {
|
|
17
|
+
readonly kind: "data";
|
|
18
|
+
readonly type: "float";
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
readonly props: {
|
|
22
|
+
readonly min: {
|
|
23
|
+
readonly type: "float";
|
|
24
|
+
readonly default: 0;
|
|
25
|
+
};
|
|
26
|
+
readonly max: {
|
|
27
|
+
readonly type: "float";
|
|
28
|
+
readonly default: 1;
|
|
29
|
+
};
|
|
30
|
+
readonly message: {
|
|
31
|
+
readonly type: "string";
|
|
32
|
+
readonly default: "";
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export declare const equalsDefinition: {
|
|
37
|
+
readonly apiVersion: 1;
|
|
38
|
+
readonly runsOn: "portable";
|
|
39
|
+
readonly label: "Assert Equals";
|
|
40
|
+
readonly description: "Fails the run unless value equals expected (JSON-equal; numbers within tolerance); passes value through";
|
|
41
|
+
readonly icon: "ShieldCheck";
|
|
42
|
+
readonly inputs: {
|
|
43
|
+
readonly value: {
|
|
44
|
+
readonly kind: "data";
|
|
45
|
+
readonly type: "any";
|
|
46
|
+
readonly default: null;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
readonly outputs: {
|
|
50
|
+
readonly value: {
|
|
51
|
+
readonly kind: "data";
|
|
52
|
+
readonly type: "any";
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
readonly props: {
|
|
56
|
+
readonly expected: {
|
|
57
|
+
readonly type: "any";
|
|
58
|
+
readonly default: null;
|
|
59
|
+
};
|
|
60
|
+
readonly tolerance: {
|
|
61
|
+
readonly type: "float";
|
|
62
|
+
readonly default: 0;
|
|
63
|
+
readonly min: 0;
|
|
64
|
+
};
|
|
65
|
+
readonly message: {
|
|
66
|
+
readonly type: "string";
|
|
67
|
+
readonly default: "";
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
export declare const trueDefinition: {
|
|
72
|
+
readonly apiVersion: 1;
|
|
73
|
+
readonly runsOn: "portable";
|
|
74
|
+
readonly label: "Assert True";
|
|
75
|
+
readonly description: "Fails the run unless value is true (wire a condition, or give the prop an expression); passes value through";
|
|
76
|
+
readonly icon: "ShieldCheck";
|
|
77
|
+
readonly inputs: {
|
|
78
|
+
readonly value: {
|
|
79
|
+
readonly kind: "data";
|
|
80
|
+
readonly type: "bool";
|
|
81
|
+
readonly default: false;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
readonly outputs: {
|
|
85
|
+
readonly value: {
|
|
86
|
+
readonly kind: "data";
|
|
87
|
+
readonly type: "bool";
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
readonly props: {
|
|
91
|
+
readonly message: {
|
|
92
|
+
readonly type: "string";
|
|
93
|
+
readonly default: "";
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
export declare const notBlankDefinition: {
|
|
98
|
+
readonly apiVersion: 1;
|
|
99
|
+
readonly runsOn: "portable";
|
|
100
|
+
readonly label: "Assert Not Blank";
|
|
101
|
+
readonly description: "Fails the run when an image (PNG or PPM) is one flat colour or fully transparent; passes the image through";
|
|
102
|
+
readonly icon: "ShieldCheck";
|
|
103
|
+
readonly capabilities: readonly ["assets"];
|
|
104
|
+
readonly inputs: {
|
|
105
|
+
readonly image: {
|
|
106
|
+
readonly kind: "data";
|
|
107
|
+
readonly type: "image";
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
readonly outputs: {
|
|
111
|
+
readonly image: {
|
|
112
|
+
readonly kind: "data";
|
|
113
|
+
readonly type: "image";
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
readonly props: {
|
|
117
|
+
readonly tolerance: {
|
|
118
|
+
readonly type: "int";
|
|
119
|
+
readonly default: 0;
|
|
120
|
+
readonly min: 0;
|
|
121
|
+
readonly max: 255;
|
|
122
|
+
readonly step: 1;
|
|
123
|
+
};
|
|
124
|
+
readonly message: {
|
|
125
|
+
readonly type: "string";
|
|
126
|
+
readonly default: "";
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=definitions.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** The declarations of `cascade.assert.*`; see `index.ts`. */
|
|
2
|
+
export const rangeDefinition = {
|
|
3
|
+
apiVersion: 1,
|
|
4
|
+
runsOn: "portable",
|
|
5
|
+
label: "Assert Range",
|
|
6
|
+
description: "Fails the run unless value is a finite number in [min, max]; passes value through",
|
|
7
|
+
icon: "ShieldCheck",
|
|
8
|
+
inputs: { value: { kind: "data", type: "float", default: 0 } },
|
|
9
|
+
outputs: { value: { kind: "data", type: "float" } },
|
|
10
|
+
props: {
|
|
11
|
+
min: { type: "float", default: 0 },
|
|
12
|
+
max: { type: "float", default: 1 },
|
|
13
|
+
message: { type: "string", default: "" },
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
export const equalsDefinition = {
|
|
17
|
+
apiVersion: 1,
|
|
18
|
+
runsOn: "portable",
|
|
19
|
+
label: "Assert Equals",
|
|
20
|
+
description: "Fails the run unless value equals expected (JSON-equal; numbers within tolerance); passes value through",
|
|
21
|
+
icon: "ShieldCheck",
|
|
22
|
+
inputs: { value: { kind: "data", type: "any", default: null } },
|
|
23
|
+
outputs: { value: { kind: "data", type: "any" } },
|
|
24
|
+
props: {
|
|
25
|
+
expected: { type: "any", default: null },
|
|
26
|
+
tolerance: { type: "float", default: 0, min: 0 },
|
|
27
|
+
message: { type: "string", default: "" },
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
export const trueDefinition = {
|
|
31
|
+
apiVersion: 1,
|
|
32
|
+
runsOn: "portable",
|
|
33
|
+
label: "Assert True",
|
|
34
|
+
description: "Fails the run unless value is true (wire a condition, or give the prop an expression); passes value through",
|
|
35
|
+
icon: "ShieldCheck",
|
|
36
|
+
inputs: { value: { kind: "data", type: "bool", default: false } },
|
|
37
|
+
outputs: { value: { kind: "data", type: "bool" } },
|
|
38
|
+
props: { message: { type: "string", default: "" } },
|
|
39
|
+
};
|
|
40
|
+
export const notBlankDefinition = {
|
|
41
|
+
apiVersion: 1,
|
|
42
|
+
runsOn: "portable",
|
|
43
|
+
label: "Assert Not Blank",
|
|
44
|
+
description: "Fails the run when an image (PNG or PPM) is one flat colour or fully transparent; passes the image through",
|
|
45
|
+
icon: "ShieldCheck",
|
|
46
|
+
capabilities: ["assets"],
|
|
47
|
+
inputs: { image: { kind: "data", type: "image" } },
|
|
48
|
+
outputs: { image: { kind: "data", type: "image" } },
|
|
49
|
+
props: {
|
|
50
|
+
tolerance: { type: "int", default: 0, min: 0, max: 255, step: 1 },
|
|
51
|
+
message: { type: "string", default: "" },
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=definitions.js.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { DefinitionNodeRegistration } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* `cascade.assert.*`: nodes that fail a run.
|
|
4
|
+
*
|
|
5
|
+
* An agent checking its own render used to do it outside the graph (a QR code
|
|
6
|
+
* scanned by a separate script, a blank frame noticed by eye). An assertion
|
|
7
|
+
* node puts the check in the graph: when it does not hold, the node throws,
|
|
8
|
+
* the run fails with a non-zero exit, and `run --json` carries the message,
|
|
9
|
+
* `{ ok: false, error: { message: "Assertion failed in \"…\" …" } }`.
|
|
10
|
+
*
|
|
11
|
+
* Each passes its input through, so an assertion can sit inline and is cooked
|
|
12
|
+
* whenever what is downstream of it is. An unconsumed `NotBlank` is not saved
|
|
13
|
+
* as an image output (`src/cli/imageOutputs.ts` skips this namespace).
|
|
14
|
+
*
|
|
15
|
+
* Portable since 2026-11-07, so Studio registers them too: `NotBlank` takes the
|
|
16
|
+
* host's in-memory pixels when it has them and otherwise decodes the PNG (or
|
|
17
|
+
* PPM) with the realm's `DecompressionStream` rather than `node:zlib`. The
|
|
18
|
+
* declarations are in `definitions.ts` and the bodies in `checks.ts`, behind a
|
|
19
|
+
* dynamic import, because this list is in Studio's first-paint bundle.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ASSERT_NAMESPACE = "cascade.assert.";
|
|
22
|
+
export * from "./definitions.js";
|
|
23
|
+
export declare const rangeRegistration: DefinitionNodeRegistration<{
|
|
24
|
+
readonly apiVersion: 1;
|
|
25
|
+
readonly runsOn: "portable";
|
|
26
|
+
readonly label: "Assert Range";
|
|
27
|
+
readonly description: "Fails the run unless value is a finite number in [min, max]; passes value through";
|
|
28
|
+
readonly icon: "ShieldCheck";
|
|
29
|
+
readonly inputs: {
|
|
30
|
+
readonly value: {
|
|
31
|
+
readonly kind: "data";
|
|
32
|
+
readonly type: "float";
|
|
33
|
+
readonly default: 0;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
readonly outputs: {
|
|
37
|
+
readonly value: {
|
|
38
|
+
readonly kind: "data";
|
|
39
|
+
readonly type: "float";
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
readonly props: {
|
|
43
|
+
readonly min: {
|
|
44
|
+
readonly type: "float";
|
|
45
|
+
readonly default: 0;
|
|
46
|
+
};
|
|
47
|
+
readonly max: {
|
|
48
|
+
readonly type: "float";
|
|
49
|
+
readonly default: 1;
|
|
50
|
+
};
|
|
51
|
+
readonly message: {
|
|
52
|
+
readonly type: "string";
|
|
53
|
+
readonly default: "";
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
}>;
|
|
57
|
+
export declare const equalsRegistration: DefinitionNodeRegistration<{
|
|
58
|
+
readonly apiVersion: 1;
|
|
59
|
+
readonly runsOn: "portable";
|
|
60
|
+
readonly label: "Assert Equals";
|
|
61
|
+
readonly description: "Fails the run unless value equals expected (JSON-equal; numbers within tolerance); passes value through";
|
|
62
|
+
readonly icon: "ShieldCheck";
|
|
63
|
+
readonly inputs: {
|
|
64
|
+
readonly value: {
|
|
65
|
+
readonly kind: "data";
|
|
66
|
+
readonly type: "any";
|
|
67
|
+
readonly default: null;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
readonly outputs: {
|
|
71
|
+
readonly value: {
|
|
72
|
+
readonly kind: "data";
|
|
73
|
+
readonly type: "any";
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
readonly props: {
|
|
77
|
+
readonly expected: {
|
|
78
|
+
readonly type: "any";
|
|
79
|
+
readonly default: null;
|
|
80
|
+
};
|
|
81
|
+
readonly tolerance: {
|
|
82
|
+
readonly type: "float";
|
|
83
|
+
readonly default: 0;
|
|
84
|
+
readonly min: 0;
|
|
85
|
+
};
|
|
86
|
+
readonly message: {
|
|
87
|
+
readonly type: "string";
|
|
88
|
+
readonly default: "";
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
}>;
|
|
92
|
+
export declare const trueRegistration: DefinitionNodeRegistration<{
|
|
93
|
+
readonly apiVersion: 1;
|
|
94
|
+
readonly runsOn: "portable";
|
|
95
|
+
readonly label: "Assert True";
|
|
96
|
+
readonly description: "Fails the run unless value is true (wire a condition, or give the prop an expression); passes value through";
|
|
97
|
+
readonly icon: "ShieldCheck";
|
|
98
|
+
readonly inputs: {
|
|
99
|
+
readonly value: {
|
|
100
|
+
readonly kind: "data";
|
|
101
|
+
readonly type: "bool";
|
|
102
|
+
readonly default: false;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
readonly outputs: {
|
|
106
|
+
readonly value: {
|
|
107
|
+
readonly kind: "data";
|
|
108
|
+
readonly type: "bool";
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
readonly props: {
|
|
112
|
+
readonly message: {
|
|
113
|
+
readonly type: "string";
|
|
114
|
+
readonly default: "";
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
}>;
|
|
118
|
+
export declare const notBlankRegistration: DefinitionNodeRegistration<{
|
|
119
|
+
readonly apiVersion: 1;
|
|
120
|
+
readonly runsOn: "portable";
|
|
121
|
+
readonly label: "Assert Not Blank";
|
|
122
|
+
readonly description: "Fails the run when an image (PNG or PPM) is one flat colour or fully transparent; passes the image through";
|
|
123
|
+
readonly icon: "ShieldCheck";
|
|
124
|
+
readonly capabilities: readonly ["assets"];
|
|
125
|
+
readonly inputs: {
|
|
126
|
+
readonly image: {
|
|
127
|
+
readonly kind: "data";
|
|
128
|
+
readonly type: "image";
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
readonly outputs: {
|
|
132
|
+
readonly image: {
|
|
133
|
+
readonly kind: "data";
|
|
134
|
+
readonly type: "image";
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
readonly props: {
|
|
138
|
+
readonly tolerance: {
|
|
139
|
+
readonly type: "int";
|
|
140
|
+
readonly default: 0;
|
|
141
|
+
readonly min: 0;
|
|
142
|
+
readonly max: 255;
|
|
143
|
+
readonly step: 1;
|
|
144
|
+
};
|
|
145
|
+
readonly message: {
|
|
146
|
+
readonly type: "string";
|
|
147
|
+
readonly default: "";
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
}>;
|
|
151
|
+
export declare const assertNodeRegistrations: readonly DefinitionNodeRegistration[];
|
|
152
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { equalsDefinition, notBlankDefinition, rangeDefinition, trueDefinition } from "./definitions.js";
|
|
2
|
+
/**
|
|
3
|
+
* `cascade.assert.*`: nodes that fail a run.
|
|
4
|
+
*
|
|
5
|
+
* An agent checking its own render used to do it outside the graph (a QR code
|
|
6
|
+
* scanned by a separate script, a blank frame noticed by eye). An assertion
|
|
7
|
+
* node puts the check in the graph: when it does not hold, the node throws,
|
|
8
|
+
* the run fails with a non-zero exit, and `run --json` carries the message,
|
|
9
|
+
* `{ ok: false, error: { message: "Assertion failed in \"…\" …" } }`.
|
|
10
|
+
*
|
|
11
|
+
* Each passes its input through, so an assertion can sit inline and is cooked
|
|
12
|
+
* whenever what is downstream of it is. An unconsumed `NotBlank` is not saved
|
|
13
|
+
* as an image output (`src/cli/imageOutputs.ts` skips this namespace).
|
|
14
|
+
*
|
|
15
|
+
* Portable since 2026-11-07, so Studio registers them too: `NotBlank` takes the
|
|
16
|
+
* host's in-memory pixels when it has them and otherwise decodes the PNG (or
|
|
17
|
+
* PPM) with the realm's `DecompressionStream` rather than `node:zlib`. The
|
|
18
|
+
* declarations are in `definitions.ts` and the bodies in `checks.ts`, behind a
|
|
19
|
+
* dynamic import, because this list is in Studio's first-paint bundle.
|
|
20
|
+
*/
|
|
21
|
+
export const ASSERT_NAMESPACE = "cascade.assert.";
|
|
22
|
+
export * from "./definitions.js";
|
|
23
|
+
function lazy(name, definition, load) {
|
|
24
|
+
return { kind: "definition-v1", moduleId: `${ASSERT_NAMESPACE}${name}`, definition, loadExecute: load };
|
|
25
|
+
}
|
|
26
|
+
export const rangeRegistration = lazy("Range", rangeDefinition, async () => (await import("./checks.js")).executeRange);
|
|
27
|
+
export const equalsRegistration = lazy("Equals", equalsDefinition, async () => (await import("./checks.js")).executeEquals);
|
|
28
|
+
export const trueRegistration = lazy("True", trueDefinition, async () => (await import("./checks.js")).executeTrue);
|
|
29
|
+
export const notBlankRegistration = lazy("NotBlank", notBlankDefinition, async () => (await import("./checks.js")).executeNotBlank);
|
|
30
|
+
export const assertNodeRegistrations = Object.freeze([
|
|
31
|
+
equalsRegistration,
|
|
32
|
+
notBlankRegistration,
|
|
33
|
+
rangeRegistration,
|
|
34
|
+
trueRegistration,
|
|
35
|
+
]);
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { NodeExecutionContext } from "@field/cascade/contracts";
|
|
2
|
+
import type { contactSheetDefinition } from "./definitions.js";
|
|
3
|
+
/**
|
|
4
|
+
* `cascade.image.ContactSheet` — several images as one labelled grid.
|
|
5
|
+
*
|
|
6
|
+
* The selection loop is a ladder of three to five options and a pick, and the
|
|
7
|
+
* pick is made by looking at them side by side. So the sheet is a node rather
|
|
8
|
+
* than a script beside the graph: a graph can end in one, and `cascade ladder`
|
|
9
|
+
* lays out its variants with the same layout and drawing code below.
|
|
10
|
+
*
|
|
11
|
+
* `portable` with `assets`, like `cascade.geo.Render`: it reads each image
|
|
12
|
+
* through the asset capability, decodes it with `createImageBitmap` and draws
|
|
13
|
+
* on an `OffscreenCanvas`, which both hosts supply (the headless one through
|
|
14
|
+
* @napi-rs/canvas). Its declaration is in `definitions.ts` with the rest of
|
|
15
|
+
* the library, so Studio's first paint carries the literal and not this body.
|
|
16
|
+
*/
|
|
17
|
+
export interface ContactSheetOptions {
|
|
18
|
+
readonly columns?: number;
|
|
19
|
+
readonly gap?: number;
|
|
20
|
+
readonly cellWidth?: number;
|
|
21
|
+
readonly labels?: readonly string[];
|
|
22
|
+
readonly labelSize?: number;
|
|
23
|
+
/** RGBA, 0 to 1. */
|
|
24
|
+
readonly background?: readonly number[];
|
|
25
|
+
readonly labelColor?: readonly number[];
|
|
26
|
+
}
|
|
27
|
+
export interface SheetCell {
|
|
28
|
+
readonly x: number;
|
|
29
|
+
readonly y: number;
|
|
30
|
+
readonly width: number;
|
|
31
|
+
readonly height: number;
|
|
32
|
+
/** Top-left of the label line. */
|
|
33
|
+
readonly labelX: number;
|
|
34
|
+
readonly labelY: number;
|
|
35
|
+
}
|
|
36
|
+
export interface SheetLayout {
|
|
37
|
+
readonly width: number;
|
|
38
|
+
readonly height: number;
|
|
39
|
+
readonly columns: number;
|
|
40
|
+
readonly rows: number;
|
|
41
|
+
readonly cell: readonly [number, number];
|
|
42
|
+
readonly labelHeight: number;
|
|
43
|
+
readonly cells: readonly SheetCell[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Where each image goes. By default every cell is as wide as the widest image
|
|
47
|
+
* and as tall as the tallest, and images keep their size. With `cellWidth`,
|
|
48
|
+
* cells are that wide and as tall as the tallest image needs at that width,
|
|
49
|
+
* and each image is scaled to fit. Either way an image is centred in its cell
|
|
50
|
+
* and never cropped, in whole pixels, so a sheet is the same bytes every run.
|
|
51
|
+
*/
|
|
52
|
+
export declare function contactSheetLayout(sizes: readonly (readonly [number, number])[], options?: {
|
|
53
|
+
columns?: number;
|
|
54
|
+
gap?: number;
|
|
55
|
+
cellWidth?: number;
|
|
56
|
+
labelHeight?: number;
|
|
57
|
+
}): SheetLayout;
|
|
58
|
+
/** Encoded images (PNG, JPEG, WebP) in, one PNG sheet out. */
|
|
59
|
+
export declare function composeContactSheet(images: readonly Uint8Array[], options?: ContactSheetOptions): Promise<{
|
|
60
|
+
bytes: Uint8Array;
|
|
61
|
+
layout: SheetLayout;
|
|
62
|
+
}>;
|
|
63
|
+
export declare function executeContactSheet(context: NodeExecutionContext<typeof contactSheetDefinition>): Promise<void>;
|
|
64
|
+
//# sourceMappingURL=contactSheet.d.ts.map
|