zdymak 0.10.0 → 0.13.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/LICENSE +21 -21
- package/README.md +733 -288
- package/SKILL.md +183 -88
- package/bin/zdymak.mjs +3 -3
- package/package.json +66 -48
- package/src/canvas.mjs +116 -116
- package/src/capture/index.mjs +318 -285
- package/src/capture/web.mjs +137 -0
- package/src/cli.mjs +298 -215
- package/src/config.mjs +159 -121
- package/src/destinations.mjs +103 -0
- package/src/easings.mjs +74 -0
- package/src/effects.mjs +335 -0
- package/src/encode.mjs +70 -44
- package/src/fonts.mjs +42 -42
- package/src/frames.mjs +186 -168
- package/src/graphic.mjs +107 -77
- package/src/index.mjs +27 -12
- package/src/montage.mjs +428 -419
- package/src/png.mjs +50 -50
- package/src/premium.mjs +362 -361
- package/src/reel.mjs +366 -245
- package/src/screenshots.mjs +118 -66
- package/src/specs.mjs +123 -88
- package/src/statusbar.mjs +163 -0
- package/src/still.mjs +72 -62
- package/src/transitions.mjs +653 -0
- package/src/validate.mjs +98 -0
- package/src/video.mjs +243 -240
- package/types/index.d.ts +559 -0
package/src/canvas.mjs
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
/** Shared canvas primitives (Skia via @napi-rs/canvas) used by the reel renderer. */
|
|
2
|
-
|
|
3
|
-
export const font = (sizePx, weight = 'bold') => `${weight} ${sizePx}px Brand`;
|
|
4
|
-
|
|
5
|
-
/** #rrggbb + alpha → rgba() string. */
|
|
6
|
-
export function hexA(hex, a) {
|
|
7
|
-
const n = parseInt(String(hex).replace('#', ''), 16);
|
|
8
|
-
return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${a})`;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** #rrggbb → [r,g,b]. */
|
|
12
|
-
export function hexRgb(hex) {
|
|
13
|
-
const n = parseInt(String(hex).replace('#', ''), 16);
|
|
14
|
-
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** Numerically integrated damped spring 0→1 (semi-implicit Euler) → per-frame lookup. Over-damped configs
|
|
18
|
-
* glide and settle (camera dolly); lighter damping gives a small overshoot. */
|
|
19
|
-
export function springSeries(frames, fps, { stiffness = 55, damping = 24, mass = 1.5 } = {}) {
|
|
20
|
-
const dt = 1 / fps;
|
|
21
|
-
let x = 0;
|
|
22
|
-
let v = 0;
|
|
23
|
-
const out = new Array(frames);
|
|
24
|
-
for (let f = 0; f < frames; f++) {
|
|
25
|
-
const a = (stiffness * (1 - x) - damping * v) / mass;
|
|
26
|
-
v += a * dt;
|
|
27
|
-
x += v * dt;
|
|
28
|
-
out[f] = x;
|
|
29
|
-
}
|
|
30
|
-
return out;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Rounded-rectangle subpath (no fill/stroke). */
|
|
34
|
-
export function roundRectPath(ctx, x, y, w, h, r) {
|
|
35
|
-
const rr = Math.min(r, w / 2, h / 2);
|
|
36
|
-
ctx.beginPath();
|
|
37
|
-
ctx.moveTo(x + rr, y);
|
|
38
|
-
ctx.arcTo(x + w, y, x + w, y + h, rr);
|
|
39
|
-
ctx.arcTo(x + w, y + h, x, y + h, rr);
|
|
40
|
-
ctx.arcTo(x, y + h, x, y, rr);
|
|
41
|
-
ctx.arcTo(x, y, x + w, y, rr);
|
|
42
|
-
ctx.closePath();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Vertical linear gradient top→bottom filling the whole canvas. */
|
|
46
|
-
export function fillVerticalGradient(ctx, w, h, top, bottom) {
|
|
47
|
-
const g = ctx.createLinearGradient(0, 0, 0, h);
|
|
48
|
-
g.addColorStop(0, top);
|
|
49
|
-
g.addColorStop(1, bottom);
|
|
50
|
-
ctx.fillStyle = g;
|
|
51
|
-
ctx.fillRect(0, 0, w, h);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** Soft radial brand glow — "something is arriving" depth on a dark matte. */
|
|
55
|
-
export function radialGlow(ctx, cx, cy, radius, color, alpha) {
|
|
56
|
-
const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, radius);
|
|
57
|
-
g.addColorStop(0, hexA(color, alpha));
|
|
58
|
-
g.addColorStop(1, hexA(color, 0));
|
|
59
|
-
ctx.fillStyle = g;
|
|
60
|
-
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** Word-wrap `text` to `maxWidth` at the current font; returns line array. */
|
|
64
|
-
export function wrapLines(ctx, text, maxWidth) {
|
|
65
|
-
const words = String(text).split(/\s+/);
|
|
66
|
-
const lines = [];
|
|
67
|
-
let line = '';
|
|
68
|
-
for (const w of words) {
|
|
69
|
-
const test = line ? `${line} ${w}` : w;
|
|
70
|
-
if (ctx.measureText(test).width > maxWidth && line) {
|
|
71
|
-
lines.push(line);
|
|
72
|
-
line = w;
|
|
73
|
-
} else {
|
|
74
|
-
line = test;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (line) lines.push(line);
|
|
78
|
-
return lines;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Typeset a centred caption block (bold title + optional lighter subtitle). Returns height drawn. */
|
|
82
|
-
export function drawCaption(ctx, { title, subtitle, centerX, top, maxWidth, titleSize, subSize, titleColor, subColor }) {
|
|
83
|
-
ctx.textAlign = 'center';
|
|
84
|
-
ctx.textBaseline = 'top';
|
|
85
|
-
let y = top;
|
|
86
|
-
ctx.font = font(titleSize, 'bold');
|
|
87
|
-
ctx.fillStyle = titleColor;
|
|
88
|
-
const titleLH = Math.round(titleSize * 1.12);
|
|
89
|
-
for (const ln of wrapLines(ctx, title, maxWidth)) {
|
|
90
|
-
ctx.fillText(ln, centerX, y);
|
|
91
|
-
y += titleLH;
|
|
92
|
-
}
|
|
93
|
-
if (subtitle) {
|
|
94
|
-
y += Math.round(titleSize * 0.28);
|
|
95
|
-
ctx.font = font(subSize, 'regular');
|
|
96
|
-
ctx.fillStyle = subColor;
|
|
97
|
-
const subLH = Math.round(subSize * 1.3);
|
|
98
|
-
for (const ln of wrapLines(ctx, subtitle, maxWidth)) {
|
|
99
|
-
ctx.fillText(ln, centerX, y);
|
|
100
|
-
y += subLH;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return y - top;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Height a caption block WOULD occupy (mirror of drawCaption's layout), for vertical centring. */
|
|
107
|
-
export function measureCaption(ctx, { title, subtitle, maxWidth, titleSize, subSize }) {
|
|
108
|
-
ctx.font = font(titleSize, 'bold');
|
|
109
|
-
let h = wrapLines(ctx, title, maxWidth).length * Math.round(titleSize * 1.12);
|
|
110
|
-
if (subtitle) {
|
|
111
|
-
h += Math.round(titleSize * 0.28);
|
|
112
|
-
ctx.font = font(subSize, 'regular');
|
|
113
|
-
h += wrapLines(ctx, subtitle, maxWidth).length * Math.round(subSize * 1.3);
|
|
114
|
-
}
|
|
115
|
-
return h;
|
|
116
|
-
}
|
|
1
|
+
/** Shared canvas primitives (Skia via @napi-rs/canvas) used by the reel renderer. */
|
|
2
|
+
|
|
3
|
+
export const font = (sizePx, weight = 'bold') => `${weight} ${sizePx}px Brand`;
|
|
4
|
+
|
|
5
|
+
/** #rrggbb + alpha → rgba() string. */
|
|
6
|
+
export function hexA(hex, a) {
|
|
7
|
+
const n = parseInt(String(hex).replace('#', ''), 16);
|
|
8
|
+
return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${a})`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** #rrggbb → [r,g,b]. */
|
|
12
|
+
export function hexRgb(hex) {
|
|
13
|
+
const n = parseInt(String(hex).replace('#', ''), 16);
|
|
14
|
+
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Numerically integrated damped spring 0→1 (semi-implicit Euler) → per-frame lookup. Over-damped configs
|
|
18
|
+
* glide and settle (camera dolly); lighter damping gives a small overshoot. */
|
|
19
|
+
export function springSeries(frames, fps, { stiffness = 55, damping = 24, mass = 1.5 } = {}) {
|
|
20
|
+
const dt = 1 / fps;
|
|
21
|
+
let x = 0;
|
|
22
|
+
let v = 0;
|
|
23
|
+
const out = new Array(frames);
|
|
24
|
+
for (let f = 0; f < frames; f++) {
|
|
25
|
+
const a = (stiffness * (1 - x) - damping * v) / mass;
|
|
26
|
+
v += a * dt;
|
|
27
|
+
x += v * dt;
|
|
28
|
+
out[f] = x;
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Rounded-rectangle subpath (no fill/stroke). */
|
|
34
|
+
export function roundRectPath(ctx, x, y, w, h, r) {
|
|
35
|
+
const rr = Math.min(r, w / 2, h / 2);
|
|
36
|
+
ctx.beginPath();
|
|
37
|
+
ctx.moveTo(x + rr, y);
|
|
38
|
+
ctx.arcTo(x + w, y, x + w, y + h, rr);
|
|
39
|
+
ctx.arcTo(x + w, y + h, x, y + h, rr);
|
|
40
|
+
ctx.arcTo(x, y + h, x, y, rr);
|
|
41
|
+
ctx.arcTo(x, y, x + w, y, rr);
|
|
42
|
+
ctx.closePath();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Vertical linear gradient top→bottom filling the whole canvas. */
|
|
46
|
+
export function fillVerticalGradient(ctx, w, h, top, bottom) {
|
|
47
|
+
const g = ctx.createLinearGradient(0, 0, 0, h);
|
|
48
|
+
g.addColorStop(0, top);
|
|
49
|
+
g.addColorStop(1, bottom);
|
|
50
|
+
ctx.fillStyle = g;
|
|
51
|
+
ctx.fillRect(0, 0, w, h);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Soft radial brand glow — "something is arriving" depth on a dark matte. */
|
|
55
|
+
export function radialGlow(ctx, cx, cy, radius, color, alpha) {
|
|
56
|
+
const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, radius);
|
|
57
|
+
g.addColorStop(0, hexA(color, alpha));
|
|
58
|
+
g.addColorStop(1, hexA(color, 0));
|
|
59
|
+
ctx.fillStyle = g;
|
|
60
|
+
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Word-wrap `text` to `maxWidth` at the current font; returns line array. */
|
|
64
|
+
export function wrapLines(ctx, text, maxWidth) {
|
|
65
|
+
const words = String(text).split(/\s+/);
|
|
66
|
+
const lines = [];
|
|
67
|
+
let line = '';
|
|
68
|
+
for (const w of words) {
|
|
69
|
+
const test = line ? `${line} ${w}` : w;
|
|
70
|
+
if (ctx.measureText(test).width > maxWidth && line) {
|
|
71
|
+
lines.push(line);
|
|
72
|
+
line = w;
|
|
73
|
+
} else {
|
|
74
|
+
line = test;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (line) lines.push(line);
|
|
78
|
+
return lines;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Typeset a centred caption block (bold title + optional lighter subtitle). Returns height drawn. */
|
|
82
|
+
export function drawCaption(ctx, { title, subtitle, centerX, top, maxWidth, titleSize, subSize, titleColor, subColor }) {
|
|
83
|
+
ctx.textAlign = 'center';
|
|
84
|
+
ctx.textBaseline = 'top';
|
|
85
|
+
let y = top;
|
|
86
|
+
ctx.font = font(titleSize, 'bold');
|
|
87
|
+
ctx.fillStyle = titleColor;
|
|
88
|
+
const titleLH = Math.round(titleSize * 1.12);
|
|
89
|
+
for (const ln of wrapLines(ctx, title, maxWidth)) {
|
|
90
|
+
ctx.fillText(ln, centerX, y);
|
|
91
|
+
y += titleLH;
|
|
92
|
+
}
|
|
93
|
+
if (subtitle) {
|
|
94
|
+
y += Math.round(titleSize * 0.28);
|
|
95
|
+
ctx.font = font(subSize, 'regular');
|
|
96
|
+
ctx.fillStyle = subColor;
|
|
97
|
+
const subLH = Math.round(subSize * 1.3);
|
|
98
|
+
for (const ln of wrapLines(ctx, subtitle, maxWidth)) {
|
|
99
|
+
ctx.fillText(ln, centerX, y);
|
|
100
|
+
y += subLH;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return y - top;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Height a caption block WOULD occupy (mirror of drawCaption's layout), for vertical centring. */
|
|
107
|
+
export function measureCaption(ctx, { title, subtitle, maxWidth, titleSize, subSize }) {
|
|
108
|
+
ctx.font = font(titleSize, 'bold');
|
|
109
|
+
let h = wrapLines(ctx, title, maxWidth).length * Math.round(titleSize * 1.12);
|
|
110
|
+
if (subtitle) {
|
|
111
|
+
h += Math.round(titleSize * 0.28);
|
|
112
|
+
ctx.font = font(subSize, 'regular');
|
|
113
|
+
h += wrapLines(ctx, subtitle, maxWidth).length * Math.round(subSize * 1.3);
|
|
114
|
+
}
|
|
115
|
+
return h;
|
|
116
|
+
}
|