@pixerate/editor 0.119.0 → 0.121.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.
Files changed (74) hide show
  1. package/dist/animation-editor/index.cjs +54 -54
  2. package/dist/animation-editor/index.d.cts +3 -3
  3. package/dist/animation-editor/index.d.ts +3 -3
  4. package/dist/animation-editor/index.js +2 -2
  5. package/dist/{chunk-UMVW2Q66.js → chunk-4I54JSJ6.js} +2 -2
  6. package/dist/{chunk-N3467VD6.js → chunk-7QN76ESJ.js} +1 -179
  7. package/dist/chunk-7QN76ESJ.js.map +1 -0
  8. package/dist/{chunk-EJVDBITD.cjs → chunk-DCLJNVZS.cjs} +2 -180
  9. package/dist/chunk-DCLJNVZS.cjs.map +1 -0
  10. package/dist/{chunk-KGNUFO4T.cjs → chunk-DFLJHLKA.cjs} +36 -36
  11. package/dist/{chunk-KGNUFO4T.cjs.map → chunk-DFLJHLKA.cjs.map} +1 -1
  12. package/dist/{chunk-KZLQY4CD.js → chunk-E6N5KLMA.js} +12 -10
  13. package/dist/{chunk-KZLQY4CD.js.map → chunk-E6N5KLMA.js.map} +1 -1
  14. package/dist/{chunk-4S2AXNQF.cjs → chunk-GLOOH65Y.cjs} +236 -234
  15. package/dist/chunk-GLOOH65Y.cjs.map +1 -0
  16. package/dist/chunk-GX332W2C.js +181 -0
  17. package/dist/chunk-GX332W2C.js.map +1 -0
  18. package/dist/{chunk-TS5IEUW6.cjs → chunk-K7V27T72.cjs} +178 -3
  19. package/dist/chunk-K7V27T72.cjs.map +1 -0
  20. package/dist/{chunk-WMRIG4XQ.cjs → chunk-LL3RAN55.cjs} +17 -17
  21. package/dist/{chunk-WMRIG4XQ.cjs.map → chunk-LL3RAN55.cjs.map} +1 -1
  22. package/dist/chunk-MUUXNRU4.cjs +181 -0
  23. package/dist/chunk-MUUXNRU4.cjs.map +1 -0
  24. package/dist/{chunk-LI3KTY7K.cjs → chunk-VYW7JBTM.cjs} +72 -5
  25. package/dist/chunk-VYW7JBTM.cjs.map +1 -0
  26. package/dist/{chunk-TDQDMBUR.js → chunk-WAI6ATHC.js} +2 -2
  27. package/dist/{chunk-CSHEI3YT.js → chunk-WORWH5K5.js} +71 -4
  28. package/dist/chunk-WORWH5K5.js.map +1 -0
  29. package/dist/{chunk-YHW26LS2.js → chunk-ZZPGTNXJ.js} +177 -2
  30. package/dist/chunk-ZZPGTNXJ.js.map +1 -0
  31. package/dist/{export-Ds8Ccw0K.d.ts → export-D2D8hb_u.d.ts} +1 -1
  32. package/dist/{export-B_jO2PrH.d.cts → export-fP2hZYjm.d.cts} +1 -1
  33. package/dist/image-editor/index.cjs +789 -769
  34. package/dist/image-editor/index.cjs.map +1 -1
  35. package/dist/image-editor/index.d.cts +4 -4
  36. package/dist/image-editor/index.d.ts +4 -4
  37. package/dist/image-editor/index.js +32 -12
  38. package/dist/image-editor/index.js.map +1 -1
  39. package/dist/paint/index.cjs +22 -2
  40. package/dist/paint/index.cjs.map +1 -1
  41. package/dist/paint/index.d.cts +159 -1
  42. package/dist/paint/index.d.ts +159 -1
  43. package/dist/paint/index.js +21 -1
  44. package/dist/paint-editor/index.cjs +96 -95
  45. package/dist/paint-editor/index.cjs.map +1 -1
  46. package/dist/paint-editor/index.d.cts +2 -2
  47. package/dist/paint-editor/index.d.ts +2 -2
  48. package/dist/paint-editor/index.js +13 -12
  49. package/dist/paint-editor/index.js.map +1 -1
  50. package/dist/raster/index.cjs +5 -4
  51. package/dist/raster/index.cjs.map +1 -1
  52. package/dist/raster/index.js +11 -10
  53. package/dist/{svg-hK0WQc7-.d.cts → svg-S9zzvqaZ.d.cts} +1 -1
  54. package/dist/{svg-CTxrB5xO.d.ts → svg-leZpRsJB.d.ts} +1 -1
  55. package/dist/{types-TrSSh_RB.d.ts → types-BgwxFWvn.d.ts} +23 -9
  56. package/dist/{types-Dq-8qgoo.d.cts → types-DdRTDnza.d.cts} +23 -9
  57. package/dist/vector/index.cjs +887 -883
  58. package/dist/vector/index.cjs.map +1 -1
  59. package/dist/vector/index.d.cts +12 -5
  60. package/dist/vector/index.d.ts +12 -5
  61. package/dist/vector/index.js +14 -10
  62. package/dist/vector/index.js.map +1 -1
  63. package/package.json +1 -1
  64. package/skills/pixerate-vector/SKILL.md +1 -1
  65. package/skills/pixerate-vector/references/commands.md +1 -0
  66. package/dist/chunk-4S2AXNQF.cjs.map +0 -1
  67. package/dist/chunk-CSHEI3YT.js.map +0 -1
  68. package/dist/chunk-EJVDBITD.cjs.map +0 -1
  69. package/dist/chunk-LI3KTY7K.cjs.map +0 -1
  70. package/dist/chunk-N3467VD6.js.map +0 -1
  71. package/dist/chunk-TS5IEUW6.cjs.map +0 -1
  72. package/dist/chunk-YHW26LS2.js.map +0 -1
  73. /package/dist/{chunk-UMVW2Q66.js.map → chunk-4I54JSJ6.js.map} +0 -0
  74. /package/dist/{chunk-TDQDMBUR.js.map → chunk-WAI6ATHC.js.map} +0 -0
@@ -0,0 +1,181 @@
1
+ // src/geom/homography.ts
2
+ var IDENTITY_HOMOGRAPHY = [1, 0, 0, 0, 1, 0, 0, 0, 1];
3
+ function solve8(rows) {
4
+ const m = rows.map((r) => r.slice());
5
+ for (let col = 0; col < 8; col++) {
6
+ let pivot = col;
7
+ for (let r = col + 1; r < 8; r++) if (Math.abs(m[r][col]) > Math.abs(m[pivot][col])) pivot = r;
8
+ if (!(Math.abs(m[pivot][col]) >= 1e-12)) return null;
9
+ [m[col], m[pivot]] = [m[pivot], m[col]];
10
+ const p = m[col];
11
+ for (let r = 0; r < 8; r++) {
12
+ if (r === col) continue;
13
+ const f = m[r][col] / p[col];
14
+ if (f !== 0) for (let k = 0; k < 9; k++) m[r][k] -= f * p[k];
15
+ }
16
+ }
17
+ const out = [];
18
+ for (let i = 0; i < 8; i++) {
19
+ const v = m[i][8] / m[i][i];
20
+ if (!Number.isFinite(v)) return null;
21
+ out.push(v);
22
+ }
23
+ return out;
24
+ }
25
+ function applyHomography(h, p) {
26
+ const w = h[6] * p.x + h[7] * p.y + h[8];
27
+ if (!Number.isFinite(w) || Math.abs(w) < 1e-12) return null;
28
+ const x = (h[0] * p.x + h[1] * p.y + h[2]) / w;
29
+ const y = (h[3] * p.x + h[4] * p.y + h[5]) / w;
30
+ return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
31
+ }
32
+ function homographyW(h, p) {
33
+ return h[6] * p.x + h[7] * p.y + h[8];
34
+ }
35
+ var finite = (p) => Number.isFinite(p.x) && Number.isFinite(p.y);
36
+ function homographyFromPoints(from, to) {
37
+ if (from.length !== 4 || to.length !== 4 || ![...from, ...to].every(finite)) return null;
38
+ const rows = [];
39
+ for (let i = 0; i < 4; i++) {
40
+ const { x, y } = from[i];
41
+ const { x: u, y: v } = to[i];
42
+ rows.push([x, y, 1, 0, 0, 0, -u * x, -u * y, u]);
43
+ rows.push([0, 0, 0, x, y, 1, -v * x, -v * y, v]);
44
+ }
45
+ const h = solve8(rows);
46
+ if (!h) return null;
47
+ const out = [h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], 1];
48
+ for (let i = 0; i < 4; i++) {
49
+ const r = applyHomography(out, from[i]);
50
+ const q = to[i];
51
+ const scale = 1 + Math.max(Math.abs(q.x), Math.abs(q.y));
52
+ if (!r || Math.abs(r.x - q.x) > 1e-6 * scale || Math.abs(r.y - q.y) > 1e-6 * scale) return null;
53
+ }
54
+ return out;
55
+ }
56
+ function homographyKeepsQuad(h, quad) {
57
+ const ws = quad.map((p) => h[6] * p.x + h[7] * p.y + h[8]);
58
+ return ws.every((w) => w > 1e-9) || ws.every((w) => w < -1e-9);
59
+ }
60
+ function orderQuad(points) {
61
+ if (points.length !== 4 || !points.every(finite)) return null;
62
+ const cx = points.reduce((s, p) => s + p.x, 0) / 4;
63
+ const cy = points.reduce((s, p) => s + p.y, 0) / 4;
64
+ const q = points.slice().sort((a, b) => Math.atan2(a.y - cy, a.x - cx) - Math.atan2(b.y - cy, b.x - cx));
65
+ let first = 0;
66
+ for (let i = 1; i < 4; i++) if (q[i].x + q[i].y < q[first].x + q[first].y) first = i;
67
+ const ordered = [...q.slice(first), ...q.slice(0, first)];
68
+ const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
69
+ const turns = ordered.map((_, i) => cross(ordered[i], ordered[(i + 1) % 4], ordered[(i + 2) % 4]));
70
+ const area = turns.reduce((s, t) => s + t, 0) / 2;
71
+ const xs = ordered.map((p) => p.x);
72
+ const ys = ordered.map((p) => p.y);
73
+ const size = Math.max(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys));
74
+ const convex = turns.every((t) => t > 1e-9 * size * size);
75
+ return convex && area > 1e-6 * size * size ? ordered : null;
76
+ }
77
+ function multiplyHomographies(a, b) {
78
+ const r = new Array(9);
79
+ for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) r[i * 3 + j] = b[i * 3] * a[j] + b[i * 3 + 1] * a[3 + j] + b[i * 3 + 2] * a[6 + j];
80
+ return r;
81
+ }
82
+ function invertHomography(m) {
83
+ const c = [
84
+ m[4] * m[8] - m[5] * m[7],
85
+ m[2] * m[7] - m[1] * m[8],
86
+ m[1] * m[5] - m[2] * m[4],
87
+ m[5] * m[6] - m[3] * m[8],
88
+ m[0] * m[8] - m[2] * m[6],
89
+ m[2] * m[3] - m[0] * m[5],
90
+ m[3] * m[7] - m[4] * m[6],
91
+ m[1] * m[6] - m[0] * m[7],
92
+ m[0] * m[4] - m[1] * m[3]
93
+ ];
94
+ const det = m[0] * c[0] + m[1] * c[3] + m[2] * c[6];
95
+ if (!(Math.abs(det) >= 1e-12) || !Number.isFinite(det)) return null;
96
+ return c.map((v) => v / det);
97
+ }
98
+ function homographyFromAffine(m) {
99
+ return [m[0], m[2], m[4], m[1], m[3], m[5], 0, 0, 1];
100
+ }
101
+ function affineFromHomography(h, eps = 1e-12) {
102
+ if (Math.abs(h[6]) > eps * Math.abs(h[8]) || Math.abs(h[7]) > eps * Math.abs(h[8]) || !h[8]) return null;
103
+ const w = h[8];
104
+ return [h[0] / w, h[3] / w, h[1] / w, h[4] / w, h[2] / w, h[5] / w];
105
+ }
106
+ function isIdentityHomography(h, eps = 1e-9) {
107
+ const w = h[8];
108
+ if (Math.abs(w) < 1e-12) return false;
109
+ const id = IDENTITY_HOMOGRAPHY;
110
+ for (let i = 0; i < 8; i++) if (Math.abs(h[i] / w - id[i]) > eps) return false;
111
+ return true;
112
+ }
113
+ function squareToQuad(q) {
114
+ const [p0, p1, p2, p3] = q;
115
+ const sx = p0.x - p1.x + p2.x - p3.x;
116
+ const sy = p0.y - p1.y + p2.y - p3.y;
117
+ if (Math.abs(sx) < 1e-12 && Math.abs(sy) < 1e-12) {
118
+ return [p1.x - p0.x, p3.x - p0.x, p0.x, p1.y - p0.y, p3.y - p0.y, p0.y, 0, 0, 1];
119
+ }
120
+ const dx1 = p1.x - p2.x;
121
+ const dx2 = p3.x - p2.x;
122
+ const dy1 = p1.y - p2.y;
123
+ const dy2 = p3.y - p2.y;
124
+ const den = dx1 * dy2 - dx2 * dy1;
125
+ if (Math.abs(den) < 1e-12) return null;
126
+ const g = (sx * dy2 - dx2 * sy) / den;
127
+ const h = (dx1 * sy - sx * dy1) / den;
128
+ return [p1.x - p0.x + g * p1.x, p3.x - p0.x + h * p3.x, p0.x, p1.y - p0.y + g * p1.y, p3.y - p0.y + h * p3.y, p0.y, g, h, 1];
129
+ }
130
+ function rectToQuad(r, quad) {
131
+ if (!(r.width > 0 && r.height > 0) || quad.length !== 4 || !quad.every(finite)) return null;
132
+ const toUnit = [1 / r.width, 0, -r.x / r.width, 0, 1 / r.height, -r.y / r.height, 0, 0, 1];
133
+ const sq = squareToQuad(quad);
134
+ if (!sq) return null;
135
+ const h = multiplyHomographies(toUnit, sq);
136
+ return invertHomography(h) ? h : null;
137
+ }
138
+ function boxQuad(r) {
139
+ return [
140
+ { x: r.x, y: r.y },
141
+ { x: r.x + r.width, y: r.y },
142
+ { x: r.x + r.width, y: r.y + r.height },
143
+ { x: r.x, y: r.y + r.height }
144
+ ];
145
+ }
146
+ function homographyFrontSide(h, r) {
147
+ const ahead = boxQuad(r).filter((p) => homographyW(h, p) > 0).length;
148
+ if (ahead >= 3) return 1;
149
+ if (ahead <= 1) return -1;
150
+ return homographyW(h, { x: r.x + r.width / 2, y: r.y + r.height / 2 }) >= 0 ? 1 : -1;
151
+ }
152
+ var OUTPUT_LIMIT = 1 << 20;
153
+ function cornerBounds(corners) {
154
+ const lim = OUTPUT_LIMIT;
155
+ const xs = corners.map((c) => c.x);
156
+ const ys = corners.map((c) => c.y);
157
+ const x0 = Math.floor(Math.max(-lim, Math.min(...xs))) - 1;
158
+ const y0 = Math.floor(Math.max(-lim, Math.min(...ys))) - 1;
159
+ const x1 = Math.ceil(Math.min(lim, Math.max(...xs))) + 1;
160
+ const y1 = Math.ceil(Math.min(lim, Math.max(...ys))) + 1;
161
+ return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
162
+ }
163
+
164
+ export {
165
+ solve8,
166
+ applyHomography,
167
+ homographyW,
168
+ homographyFromPoints,
169
+ homographyKeepsQuad,
170
+ orderQuad,
171
+ multiplyHomographies,
172
+ invertHomography,
173
+ homographyFromAffine,
174
+ affineFromHomography,
175
+ isIdentityHomography,
176
+ rectToQuad,
177
+ boxQuad,
178
+ homographyFrontSide,
179
+ cornerBounds
180
+ };
181
+ //# sourceMappingURL=chunk-GX332W2C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/geom/homography.ts"],"sourcesContent":["/**\n * Perspective (projective) maps between quadrilaterals: Free Distort and Perspective in both the\n * vector and the image editor. Pure maths; degenerate or non-finite input gives `null` instead of\n * NaNs.\n *\n * Ported from ArtCraft (MIT OR Apache-2.0, © ArtCraft Team and contributors; see\n * THIRD_PARTY_NOTICES.md): pdfcraft `crates/geom/src/perspective.rs` (`Homography::from_points`,\n * `Homography::apply`, `solve8`, `order_quad`) and photocraft `crates/algo/src/transform.rs`\n * (`Homography::square_to_quad`, `rect_to_quad`, `inverse`, `mul`, `local_scale`, `front_side`,\n * `warp_output_bounds`, `convex_quad`).\n */\nimport type { AffineMatrix, Box, Homography, Quad, Vec2 } from './types';\n\nexport type { Homography };\n\nexport const IDENTITY_HOMOGRAPHY: Homography = [1, 0, 0, 0, 1, 0, 0, 0, 1];\n\n/**\n * Gaussian elimination with partial pivoting on an 8×8 system (the 9th column of each row is the\n * right-hand side). Null when the system is singular.\n */\nexport function solve8(rows: readonly (readonly number[])[]): number[] | null {\n const m = rows.map((r) => r.slice());\n for (let col = 0; col < 8; col++) {\n let pivot = col;\n for (let r = col + 1; r < 8; r++) if (Math.abs(m[r][col]) > Math.abs(m[pivot][col])) pivot = r;\n if (!(Math.abs(m[pivot][col]) >= 1e-12)) return null;\n [m[col], m[pivot]] = [m[pivot], m[col]];\n const p = m[col];\n for (let r = 0; r < 8; r++) {\n if (r === col) continue;\n const f = m[r][col] / p[col];\n if (f !== 0) for (let k = 0; k < 9; k++) m[r][k] -= f * p[k];\n }\n }\n const out: number[] = [];\n for (let i = 0; i < 8; i++) {\n const v = m[i][8] / m[i][i];\n if (!Number.isFinite(v)) return null;\n out.push(v);\n }\n return out;\n}\n\n/** The image of `p`, or null where the map sends it to infinity. */\nexport function applyHomography(h: Homography, p: Vec2): Vec2 | null {\n const w = h[6] * p.x + h[7] * p.y + h[8];\n if (!Number.isFinite(w) || Math.abs(w) < 1e-12) return null;\n const x = (h[0] * p.x + h[1] * p.y + h[2]) / w;\n const y = (h[3] * p.x + h[4] * p.y + h[5]) / w;\n return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;\n}\n\n/** The homogeneous `w` of `p`: its sign is the side of the horizon `p` maps to (0 on it). */\nexport function homographyW(h: Homography, p: Vec2): number {\n return h[6] * p.x + h[7] * p.y + h[8];\n}\n\nconst finite = (p: Vec2) => Number.isFinite(p.x) && Number.isFinite(p.y);\n\n/**\n * The projective map that takes each of `from` to the point at the same index of `to`. Null when\n * three of either set are collinear or a coordinate is not finite.\n */\nexport function homographyFromPoints(from: readonly Vec2[], to: readonly Vec2[]): Homography | null {\n if (from.length !== 4 || to.length !== 4 || ![...from, ...to].every(finite)) return null;\n // x' = (h0 x + h1 y + h2) / (h6 x + h7 y + 1), y' likewise with h3..h5: eight unknowns, two\n // equations per point pair.\n const rows: number[][] = [];\n for (let i = 0; i < 4; i++) {\n const { x, y } = from[i];\n const { x: u, y: v } = to[i];\n rows.push([x, y, 1, 0, 0, 0, -u * x, -u * y, u]);\n rows.push([0, 0, 0, x, y, 1, -v * x, -v * y, v]);\n }\n const h = solve8(rows);\n if (!h) return null;\n const out: Homography = [h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], 1];\n // Collinear points can leave a solvable but singular system: check the map really takes the\n // four points where they should go.\n for (let i = 0; i < 4; i++) {\n const r = applyHomography(out, from[i]);\n const q = to[i];\n const scale = 1 + Math.max(Math.abs(q.x), Math.abs(q.y));\n if (!r || Math.abs(r.x - q.x) > 1e-6 * scale || Math.abs(r.y - q.y) > 1e-6 * scale) return null;\n }\n return out;\n}\n\n/**\n * Whether `h` maps the whole convex quad `quad` without sending any of it through infinity (the\n * denominator keeps one sign over it): what a usable distortion needs.\n */\nexport function homographyKeepsQuad(h: Homography, quad: readonly Vec2[]): boolean {\n const ws = quad.map((p) => h[6] * p.x + h[7] * p.y + h[8]);\n return ws.every((w) => w > 1e-9) || ws.every((w) => w < -1e-9);\n}\n\n/**\n * The four corners in reading order for a y-down space: top-left, top-right, bottom-right,\n * bottom-left, whatever order they come in. Null unless they form a convex quadrilateral with a\n * non-negligible area.\n */\nexport function orderQuad(points: readonly Vec2[]): Vec2[] | null {\n if (points.length !== 4 || !points.every(finite)) return null;\n const cx = points.reduce((s, p) => s + p.x, 0) / 4;\n const cy = points.reduce((s, p) => s + p.y, 0) / 4;\n // Ascending angle about the centre is clockwise on screen (y down).\n const q = points.slice().sort((a, b) => Math.atan2(a.y - cy, a.x - cx) - Math.atan2(b.y - cy, b.x - cx));\n let first = 0;\n for (let i = 1; i < 4; i++) if (q[i].x + q[i].y < q[first].x + q[first].y) first = i;\n const ordered = [...q.slice(first), ...q.slice(0, first)];\n const cross = (o: Vec2, a: Vec2, b: Vec2) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);\n const turns = ordered.map((_, i) => cross(ordered[i], ordered[(i + 1) % 4], ordered[(i + 2) % 4]));\n const area = turns.reduce((s, t) => s + t, 0) / 2;\n const xs = ordered.map((p) => p.x);\n const ys = ordered.map((p) => p.y);\n const size = Math.max(Math.max(...xs) - Math.min(...xs), Math.max(...ys) - Math.min(...ys));\n const convex = turns.every((t) => t > 1e-9 * size * size);\n return convex && area > 1e-6 * size * size ? ordered : null;\n}\n\n/** `a` then `b` (apply `a` first). */\nexport function multiplyHomographies(a: Homography, b: Homography): Homography {\n // Row-major column-vector maps: (b ∘ a) = B · A.\n const r = new Array<number>(9);\n for (let i = 0; i < 3; i++) for (let j = 0; j < 3; j++) r[i * 3 + j] = b[i * 3] * a[j] + b[i * 3 + 1] * a[3 + j] + b[i * 3 + 2] * a[6 + j];\n return r as unknown as Homography;\n}\n\n/** The inverse map, or null for a singular one. */\nexport function invertHomography(m: Homography): Homography | null {\n const c = [\n m[4] * m[8] - m[5] * m[7],\n m[2] * m[7] - m[1] * m[8],\n m[1] * m[5] - m[2] * m[4],\n m[5] * m[6] - m[3] * m[8],\n m[0] * m[8] - m[2] * m[6],\n m[2] * m[3] - m[0] * m[5],\n m[3] * m[7] - m[4] * m[6],\n m[1] * m[6] - m[0] * m[7],\n m[0] * m[4] - m[1] * m[3],\n ];\n const det = m[0] * c[0] + m[1] * c[3] + m[2] * c[6];\n if (!(Math.abs(det) >= 1e-12) || !Number.isFinite(det)) return null;\n return c.map((v) => v / det) as unknown as Homography;\n}\n\n/** An affine matrix (SVG order) as a homography. */\nexport function homographyFromAffine(m: AffineMatrix): Homography {\n return [m[0], m[2], m[4], m[1], m[3], m[5], 0, 0, 1];\n}\n\n/** The affine matrix of `h` when it is affine (no perspective part), else null. */\nexport function affineFromHomography(h: Homography, eps = 1e-12): AffineMatrix | null {\n if (Math.abs(h[6]) > eps * Math.abs(h[8]) || Math.abs(h[7]) > eps * Math.abs(h[8]) || !h[8]) return null;\n const w = h[8];\n return [h[0] / w, h[3] / w, h[1] / w, h[4] / w, h[2] / w, h[5] / w];\n}\n\nexport function isIdentityHomography(h: Homography, eps = 1e-9): boolean {\n const w = h[8];\n if (Math.abs(w) < 1e-12) return false;\n const id = IDENTITY_HOMOGRAPHY;\n for (let i = 0; i < 8; i++) if (Math.abs(h[i] / w - id[i]) > eps) return false;\n return true;\n}\n\n/** Unit square → quad (corners (0,0), (1,0), (1,1), (0,1) to TL, TR, BR, BL); null when degenerate. */\nfunction squareToQuad(q: readonly Vec2[]): Homography | null {\n const [p0, p1, p2, p3] = q;\n const sx = p0.x - p1.x + p2.x - p3.x;\n const sy = p0.y - p1.y + p2.y - p3.y;\n if (Math.abs(sx) < 1e-12 && Math.abs(sy) < 1e-12) {\n return [p1.x - p0.x, p3.x - p0.x, p0.x, p1.y - p0.y, p3.y - p0.y, p0.y, 0, 0, 1];\n }\n const dx1 = p1.x - p2.x;\n const dx2 = p3.x - p2.x;\n const dy1 = p1.y - p2.y;\n const dy2 = p3.y - p2.y;\n const den = dx1 * dy2 - dx2 * dy1;\n if (Math.abs(den) < 1e-12) return null;\n const g = (sx * dy2 - dx2 * sy) / den;\n const h = (dx1 * sy - sx * dy1) / den;\n return [p1.x - p0.x + g * p1.x, p3.x - p0.x + h * p3.x, p0.x, p1.y - p0.y + g * p1.y, p3.y - p0.y + h * p3.y, p0.y, g, h, 1];\n}\n\n/**\n * The map taking box `r`'s corners onto `quad` (TL, TR, BR, BL), exact for parallelograms (then\n * affine). Null for an empty box, a non-finite or degenerate quad, or a singular result.\n */\nexport function rectToQuad(r: Box, quad: readonly Vec2[]): Homography | null {\n if (!(r.width > 0 && r.height > 0) || quad.length !== 4 || !quad.every(finite)) return null;\n const toUnit: Homography = [1 / r.width, 0, -r.x / r.width, 0, 1 / r.height, -r.y / r.height, 0, 0, 1];\n const sq = squareToQuad(quad);\n if (!sq) return null;\n const h = multiplyHomographies(toUnit, sq);\n return invertHomography(h) ? h : null;\n}\n\n/** Box `r`'s corners as a quad (TL, TR, BR, BL). */\nexport function boxQuad(r: Box): Quad {\n return [\n { x: r.x, y: r.y },\n { x: r.x + r.width, y: r.y },\n { x: r.x + r.width, y: r.y + r.height },\n { x: r.x, y: r.y + r.height },\n ];\n}\n\n/** Approximate linear scale of the map near `p` (square root of the Jacobian determinant). */\nexport function homographyLocalScale(h: Homography, p: Vec2): number {\n const e = 0.5;\n const a = applyHomography(h, { x: p.x - e, y: p.y });\n const b = applyHomography(h, { x: p.x + e, y: p.y });\n const c = applyHomography(h, { x: p.x, y: p.y - e });\n const d = applyHomography(h, { x: p.x, y: p.y + e });\n if (!a || !b || !c || !d) return 0;\n const jx = { x: b.x - a.x, y: b.y - a.y };\n const jy = { x: d.x - c.x, y: d.y - c.y };\n return Math.sqrt(Math.abs(jx.x * jy.y - jx.y * jy.x));\n}\n\n/**\n * The side of the horizon (+1 or −1) most of box `r`'s corners map to (the centre's on a tie). A\n * Distort dragged past the horizon maps the rest of the source to the far side, where it would\n * show as a mirrored copy wrapped round infinity; resampling keeps to this side, as Photoshop does.\n */\nexport function homographyFrontSide(h: Homography, r: Box): 1 | -1 {\n const ahead = boxQuad(r).filter((p) => homographyW(h, p) > 0).length;\n if (ahead >= 3) return 1;\n if (ahead <= 1) return -1;\n return homographyW(h, { x: r.x + r.width / 2, y: r.y + r.height / 2 }) >= 0 ? 1 : -1;\n}\n\n/** Most pixels a side of a warped result may span (±2²⁰), as photocraft clamps. */\nconst OUTPUT_LIMIT = 1 << 20;\n\n/**\n * The integer box a warp of box `r` through `h` fills: the warped corners plus a pixel for filter\n * support, each side clamped to ±2²⁰. Null when a corner maps to infinity or `h` is singular.\n */\nexport function warpOutputBounds(r: Box, h: Homography): Box | null {\n if (!(r.width > 0 && r.height > 0) || !invertHomography(h)) return null;\n const corners = boxQuad(r).map((p) => applyHomography(h, p));\n if (corners.some((c) => !c)) return null;\n return cornerBounds(corners as Vec2[]);\n}\n\n/** The integer box around `corners`, grown by a pixel and clamped to ±2²⁰. */\nexport function cornerBounds(corners: readonly Vec2[]): Box {\n const lim = OUTPUT_LIMIT;\n const xs = corners.map((c) => c.x);\n const ys = corners.map((c) => c.y);\n const x0 = Math.floor(Math.max(-lim, Math.min(...xs))) - 1;\n const y0 = Math.floor(Math.max(-lim, Math.min(...ys))) - 1;\n const x1 = Math.ceil(Math.min(lim, Math.max(...xs))) + 1;\n const y1 = Math.ceil(Math.min(lim, Math.max(...ys))) + 1;\n return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };\n}\n\n/**\n * Whether `q` (TL, TR, BR, BL) is strictly convex with every corner turn above ~0.06°, wound\n * clockwise on screen (y down), finite and at least 1 px² in area.\n */\nexport function isConvexQuad(q: readonly Vec2[]): boolean {\n if (q.length !== 4 || !q.every(finite)) return false;\n let area2 = 0;\n for (let i = 0; i < 4; i++) {\n const a = q[i];\n const b = q[(i + 1) % 4];\n const c = q[(i + 2) % 4];\n const e1 = { x: b.x - a.x, y: b.y - a.y };\n const e2 = { x: c.x - b.x, y: c.y - b.y };\n const cross = e1.x * e2.y - e1.y * e2.x;\n if (!(cross > 1e-3 * Math.hypot(e1.x, e1.y) * Math.hypot(e2.x, e2.y))) return false;\n area2 += a.x * b.y - b.x * a.y;\n }\n return Number.isFinite(area2) && area2 >= 2;\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;
2
2
 
3
3
 
4
4
 
@@ -18,8 +18,13 @@ var _chunkL3PXFMOFcjs = require('./chunk-L3PXFMOF.cjs');
18
18
 
19
19
 
20
20
 
21
+
22
+
21
23
  var _chunk7ISPWEJAcjs = require('./chunk-7ISPWEJA.cjs');
22
24
 
25
+
26
+ var _chunkDCLJNVZScjs = require('./chunk-DCLJNVZS.cjs');
27
+
23
28
  // src/paint/heal.ts
24
29
  var TOL = 2e-3;
25
30
  var FINE_ITERS = 30;
@@ -937,6 +942,176 @@ function paintPathStroke(surface, samples, options) {
937
942
  return { surface: new (0, _chunk7ISPWEJAcjs.TiledSurface)(4, tiles, surface.defaultValue), bounds, dabs };
938
943
  }
939
944
 
945
+ // src/paint/brushstroke.ts
946
+ var DEFAULT_MAX_PIXELS = 4096 * 4096;
947
+ var MIN_SCALE = 1 / 16;
948
+ var MAX_SCALE = 16;
949
+ var isProfilePath = (input) => !Array.isArray(input) && typeof input === "object" && input !== null && "points" in input;
950
+ function profileAt(profile, f) {
951
+ if (!profile || !profile.length) return void 0;
952
+ if (profile.length === 1) return profile[0];
953
+ const u = Math.min(1, Math.max(0, f)) * (profile.length - 1);
954
+ const i = Math.min(profile.length - 2, Math.floor(u));
955
+ return profile[i] + (profile[i + 1] - profile[i]) * (u - i);
956
+ }
957
+ function brushstrokeSamples(input) {
958
+ if (!isProfilePath(input)) return input.map((s) => ({ ...s }));
959
+ const pts = input.points;
960
+ const lengths = [0];
961
+ for (let i = 1; i < pts.length; i++) lengths.push(lengths[i - 1] + Math.hypot(pts[i].x - pts[i - 1].x, pts[i].y - pts[i - 1].y));
962
+ const total = _nullishCoalesce(lengths.at(-1), () => ( 0));
963
+ return pts.map((p, i) => {
964
+ const f = total > 0 ? lengths[i] / total : pts.length > 1 ? i / (pts.length - 1) : 0;
965
+ const s = { x: p.x, y: p.y };
966
+ const pressure = profileAt(input.pressure, f);
967
+ const tiltX = profileAt(input.tiltX, f);
968
+ const tiltY = profileAt(input.tiltY, f);
969
+ if (pressure !== void 0) s.pressure = pressure;
970
+ if (tiltX !== void 0) s.tiltX = tiltX;
971
+ if (tiltY !== void 0) s.tiltY = tiltY;
972
+ return s;
973
+ });
974
+ }
975
+ var REFUSED_ENGINES = {
976
+ wet: "is a wet brush, and wet paint needs an area to flow in (paint it on a wet layer in the paint editor)",
977
+ mixer: "is a Mixer Brush, which picks up paint from under the tip and has none on a standalone stroke",
978
+ blender: "is a blender, which only moves paint that is already there",
979
+ smudge: "is a smudge brush, which only moves paint that is already there",
980
+ eraser: "is an eraser, which has nothing to remove on a standalone stroke"
981
+ };
982
+ function brushstrokeStrokeOptions(brush) {
983
+ if (!brush || typeof brush !== "object") throw new Error("A brushstroke needs a brush: tip settings ({ size, hardness, \u2026 }) or a paint brush preset.");
984
+ if (!("tip" in brush) || !brush.tip) return { brush: _chunkL3PXFMOFcjs.normalizeBrush.call(void 0, brush) };
985
+ const engine = _nullishCoalesce(brush.engine, () => ( "dab"));
986
+ if (engine !== "dab") {
987
+ const label = _nullishCoalesce(_nullishCoalesce(brush.name, () => ( brush.id)), () => ( "This brush"));
988
+ const why = _nullishCoalesce(REFUSED_ENGINES[engine], () => ( `uses the '${engine}' engine, which renderBrushstroke does not draw`));
989
+ throw new Error(`Brushstrokes render dry media only: '${label}' ${why}. Pick a dry brush (a dab-engine preset such as a pencil, pastel, charcoal, marker, ink or airbrush) or plain tip settings.`);
990
+ }
991
+ const d = _nullishCoalesce(brush.dynamics, () => ( {}));
992
+ const tilt = { size: _nullishCoalesce(d.tiltSize, () => ( 0)), roundness: _nullishCoalesce(d.tiltRoundness, () => ( 0)) };
993
+ return {
994
+ brush: _chunkL3PXFMOFcjs.normalizeBrush.call(void 0, brush.tip),
995
+ followPath: brush.rotation === "stroke",
996
+ minSize: _nullishCoalesce(d.minSize, () => ( 0)),
997
+ ...tilt.size || tilt.roundness ? { tilt } : {}
998
+ };
999
+ }
1000
+ function isBrushstrokeBrush(brush) {
1001
+ return !("tip" in brush) || !brush.tip || (_nullishCoalesce(brush.engine, () => ( "dab"))) === "dab";
1002
+ }
1003
+ function brushstrokeScaleBucket(zoom, devicePixelRatio = 1) {
1004
+ const s = zoom * devicePixelRatio;
1005
+ if (!Number.isFinite(s) || s <= 0) return 1;
1006
+ const b = Math.pow(Math.SQRT2, Math.ceil(Math.log2(Math.min(MAX_SCALE, Math.max(MIN_SCALE, s))) * 2 - 1e-9));
1007
+ return Math.min(MAX_SCALE, Math.max(MIN_SCALE, Math.round(b * 1e9) / 1e9));
1008
+ }
1009
+ var finite = (v) => typeof v === "number" && Number.isFinite(v);
1010
+ function renderBrushstroke(input, brush, options = {}) {
1011
+ const strokeOptions = brushstrokeStrokeOptions(brush);
1012
+ const samples = brushstrokeSamples(input);
1013
+ for (const s of samples) if (!finite(s.x) || !finite(s.y)) throw new Error("Brushstroke samples need finite x and y.");
1014
+ const color = _nullishCoalesce(_nullishCoalesce(_optionalChain([options, 'access', _7 => _7.paint, 'optionalAccess', _8 => _8.color]), () => ( options.color)), () => ( [0, 0, 0, 255]));
1015
+ let scale = finite(options.scale) && options.scale > 0 ? Math.min(MAX_SCALE, Math.max(MIN_SCALE, options.scale)) : 1;
1016
+ if (!samples.length) return { image: { width: 0, height: 0, data: new Uint8ClampedArray(0) }, bounds: null, scale, dabs: 0 };
1017
+ const tip = strokeOptions.brush;
1018
+ const reach = tip.size * (1 + 1.5 * (_nullishCoalesce(_optionalChain([strokeOptions, 'access', _9 => _9.tilt, 'optionalAccess', _10 => _10.size]), () => ( 0)))) + 4;
1019
+ let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
1020
+ for (const s of samples) {
1021
+ x0 = Math.min(x0, s.x);
1022
+ y0 = Math.min(y0, s.y);
1023
+ x1 = Math.max(x1, s.x);
1024
+ y1 = Math.max(y1, s.y);
1025
+ }
1026
+ const maxPixels = finite(options.maxPixels) && options.maxPixels > 0 ? options.maxPixels : DEFAULT_MAX_PIXELS;
1027
+ const area = (x1 - x0 + reach) * (y1 - y0 + reach);
1028
+ if (area * scale * scale > maxPixels) scale = Math.max(MIN_SCALE, Math.sqrt(maxPixels / area));
1029
+ const scaled = samples.map((s) => ({ ...s, x: s.x * scale, y: s.y * scale }));
1030
+ const { surface, bounds, dabs } = paintPathStroke(_chunk7ISPWEJAcjs.emptySurface.call(void 0, 4), scaled, {
1031
+ ...strokeOptions,
1032
+ brush: { ...tip, size: tip.size * scale },
1033
+ closed: _nullishCoalesce(options.closed, () => ( strokeOptions.closed)),
1034
+ color
1035
+ });
1036
+ if (!bounds) return { image: { width: 0, height: 0, data: new Uint8ClampedArray(0) }, bounds: null, scale, dabs };
1037
+ const rect = bounds;
1038
+ const data = _chunk7ISPWEJAcjs.readRegion.call(void 0, surface, rect);
1039
+ return {
1040
+ image: { width: rect.width, height: rect.height, data },
1041
+ bounds: { x: rect.x / scale, y: rect.y / scale, width: rect.width / scale, height: rect.height / scale },
1042
+ scale,
1043
+ dabs
1044
+ };
1045
+ }
1046
+ function brushstrokeDataUrl(stroke) {
1047
+ const { width, height, data } = stroke.image;
1048
+ return width && height ? _chunkDCLJNVZScjs.pngDataUrl.call(void 0, width, height, data) : "";
1049
+ }
1050
+ function brushstrokeToCanvas(stroke, createCanvas) {
1051
+ const { width, height, data } = stroke.image;
1052
+ if (!width || !height) return null;
1053
+ const canvas = createCanvas(width, height);
1054
+ const ctx = canvas.getContext("2d");
1055
+ if (!ctx) return null;
1056
+ const img = ctx.createImageData(width, height);
1057
+ img.data.set(data);
1058
+ ctx.putImageData(img, 0, 0);
1059
+ return canvas;
1060
+ }
1061
+ function hashString(s, h = 2166136261) {
1062
+ for (let i = 0; i < s.length; i++) {
1063
+ h ^= s.charCodeAt(i);
1064
+ h = Math.imul(h, 16777619) >>> 0;
1065
+ }
1066
+ return h;
1067
+ }
1068
+ function brushstrokeKey(input, brush, options = {}) {
1069
+ const path = JSON.stringify(input);
1070
+ const { brush: tip, ...rest } = brushstrokeStrokeOptions(brush);
1071
+ const rest2 = JSON.stringify([tip, rest, _nullishCoalesce(_nullishCoalesce(_optionalChain([options, 'access', _11 => _11.paint, 'optionalAccess', _12 => _12.color]), () => ( options.color)), () => ( null)), _nullishCoalesce(options.closed, () => ( false)), _nullishCoalesce(options.maxPixels, () => ( null))]);
1072
+ const scale = finite(options.scale) ? options.scale : 1;
1073
+ return `${hashString(path).toString(16)}:${path.length}:${hashString(rest2).toString(16)}:${scale}`;
1074
+ }
1075
+ var BrushstrokeCache = (_class3 = class {
1076
+ constructor(capacity = 64) {;_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);
1077
+ this.capacity = capacity;
1078
+ }
1079
+
1080
+ __init8() {this.entries = /* @__PURE__ */ new Map()}
1081
+ /** Renders that missed the cache (for tests and stats). */
1082
+ __init9() {this.misses = 0}
1083
+ get size() {
1084
+ return this.entries.size;
1085
+ }
1086
+ /** The cached stroke for these arguments, rendering (and caching) it on a miss. */
1087
+ render(input, brush, options = {}) {
1088
+ const key = brushstrokeKey(input, brush, options);
1089
+ const hit = this.entries.get(key);
1090
+ if (hit) {
1091
+ this.entries.delete(key);
1092
+ this.entries.set(key, hit);
1093
+ return hit;
1094
+ }
1095
+ this.misses++;
1096
+ const out = renderBrushstroke(input, brush, options);
1097
+ this.entries.set(key, out);
1098
+ while (this.entries.size > Math.max(1, this.capacity)) this.entries.delete(this.entries.keys().next().value);
1099
+ return out;
1100
+ }
1101
+ clear() {
1102
+ this.entries.clear();
1103
+ }
1104
+ }, _class3);
1105
+
1106
+
1107
+
1108
+
1109
+
1110
+
1111
+
1112
+
1113
+
1114
+
940
1115
 
941
1116
 
942
1117
 
@@ -971,5 +1146,5 @@ function paintPathStroke(surface, samples, options) {
971
1146
 
972
1147
 
973
1148
 
974
- exports.solveMembrane = solveMembrane; exports.seamlessClone = seamlessClone; exports.membraneFill = membraneFill; exports.dilateMask = dilateMask; exports.maskBounds = maskBounds; exports.bestOffset = bestOffset; exports.TONE_RANGES = TONE_RANGES; exports.luma = luma; exports.dodgeBurnCurve = dodgeBurnCurve; exports.dodgeBurn = dodgeBurn; exports.saturationOf = saturationOf; exports.sponge = sponge; exports.perDabStrength = perDabStrength; exports.RETOUCH_SAMPLES = RETOUCH_SAMPLES; exports.SPOT_HEAL_TYPES = SPOT_HEAL_TYPES; exports.DEFAULT_RETOUCH = DEFAULT_RETOUCH; exports.normalizeRetouch = normalizeRetouch; exports.TilePixelSource = TilePixelSource; exports.RegionPixelSource = RegionPixelSource; exports.readSourceRegion = readSourceRegion; exports.applyRetouchToTile = applyRetouchToTile; exports.applyToneToDeepTile = applyToneToDeepTile; exports.toneOf = toneOf; exports.healPixels = healPixels; exports.spotHealPixels = spotHealPixels; exports.spotHealWindow = spotHealWindow; exports.RetouchDabStroke = RetouchDabStroke; exports.smudgeEffect = smudgeEffect; exports.spongeEffect = spongeEffect; exports.rgbaViewOfGrey = rgbaViewOfGrey; exports.pathStrokeDabs = pathStrokeDabs; exports.pathStrokeCoverage = pathStrokeCoverage; exports.paintPathStroke = paintPathStroke;
975
- //# sourceMappingURL=chunk-TS5IEUW6.cjs.map
1149
+ exports.solveMembrane = solveMembrane; exports.seamlessClone = seamlessClone; exports.membraneFill = membraneFill; exports.dilateMask = dilateMask; exports.maskBounds = maskBounds; exports.bestOffset = bestOffset; exports.TONE_RANGES = TONE_RANGES; exports.luma = luma; exports.dodgeBurnCurve = dodgeBurnCurve; exports.dodgeBurn = dodgeBurn; exports.saturationOf = saturationOf; exports.sponge = sponge; exports.perDabStrength = perDabStrength; exports.RETOUCH_SAMPLES = RETOUCH_SAMPLES; exports.SPOT_HEAL_TYPES = SPOT_HEAL_TYPES; exports.DEFAULT_RETOUCH = DEFAULT_RETOUCH; exports.normalizeRetouch = normalizeRetouch; exports.TilePixelSource = TilePixelSource; exports.RegionPixelSource = RegionPixelSource; exports.readSourceRegion = readSourceRegion; exports.applyRetouchToTile = applyRetouchToTile; exports.applyToneToDeepTile = applyToneToDeepTile; exports.toneOf = toneOf; exports.healPixels = healPixels; exports.spotHealPixels = spotHealPixels; exports.spotHealWindow = spotHealWindow; exports.RetouchDabStroke = RetouchDabStroke; exports.smudgeEffect = smudgeEffect; exports.spongeEffect = spongeEffect; exports.rgbaViewOfGrey = rgbaViewOfGrey; exports.pathStrokeDabs = pathStrokeDabs; exports.pathStrokeCoverage = pathStrokeCoverage; exports.paintPathStroke = paintPathStroke; exports.brushstrokeSamples = brushstrokeSamples; exports.brushstrokeStrokeOptions = brushstrokeStrokeOptions; exports.isBrushstrokeBrush = isBrushstrokeBrush; exports.brushstrokeScaleBucket = brushstrokeScaleBucket; exports.renderBrushstroke = renderBrushstroke; exports.brushstrokeDataUrl = brushstrokeDataUrl; exports.brushstrokeToCanvas = brushstrokeToCanvas; exports.brushstrokeKey = brushstrokeKey; exports.BrushstrokeCache = BrushstrokeCache;
1150
+ //# sourceMappingURL=chunk-K7V27T72.cjs.map