@pixerate/editor 0.189.0 → 0.191.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 (41) hide show
  1. package/dist/{chunk-ED4L4UD6.cjs → chunk-2UR4OX3B.cjs} +1002 -787
  2. package/dist/{chunk-M7XIC2FC.js → chunk-RQ2EM5TY.js} +333 -0
  3. package/dist/{chunk-ZEHTUR7Z.js → chunk-TXYXF32N.js} +968 -753
  4. package/dist/{chunk-5UEFNX24.cjs → chunk-ZQGZWFPU.cjs} +344 -11
  5. package/dist/image-editor/controller.d.cts +73 -1
  6. package/dist/image-editor/controller.d.ts +73 -1
  7. package/dist/image-editor/free-transform.d.cts +3 -6
  8. package/dist/image-editor/free-transform.d.ts +3 -6
  9. package/dist/image-editor/index.cjs +1382 -904
  10. package/dist/image-editor/index.d.cts +1 -0
  11. package/dist/image-editor/index.d.ts +1 -0
  12. package/dist/image-editor/index.js +485 -7
  13. package/dist/image-editor/layer-tools.d.cts +1 -1
  14. package/dist/image-editor/layer-tools.d.ts +1 -1
  15. package/dist/image-editor/layers/compositor.d.cts +3 -0
  16. package/dist/image-editor/layers/compositor.d.ts +3 -0
  17. package/dist/image-editor/layers/model.d.cts +23 -0
  18. package/dist/image-editor/layers/model.d.ts +23 -0
  19. package/dist/image-editor/layers/panel.d.cts +11 -1
  20. package/dist/image-editor/layers/panel.d.ts +11 -1
  21. package/dist/image-editor/layers/serialize.d.cts +11 -0
  22. package/dist/image-editor/layers/serialize.d.ts +11 -0
  23. package/dist/image-editor/layers/shape.d.cts +5 -0
  24. package/dist/image-editor/layers/shape.d.ts +5 -0
  25. package/dist/image-editor/layers/vector-mask.d.cts +51 -0
  26. package/dist/image-editor/layers/vector-mask.d.ts +51 -0
  27. package/dist/image-editor/psd/import.d.cts +0 -7
  28. package/dist/image-editor/psd/import.d.ts +0 -7
  29. package/dist/image-editor/selection/index.d.cts +1 -0
  30. package/dist/image-editor/selection/index.d.ts +1 -0
  31. package/dist/image-editor/selection/path.d.cts +37 -0
  32. package/dist/image-editor/selection/path.d.ts +37 -0
  33. package/dist/paint-editor/index.cjs +6 -6
  34. package/dist/paint-editor/index.js +1 -1
  35. package/dist/vector/index.cjs +21 -303
  36. package/dist/vector/index.js +15 -297
  37. package/dist/vector/trace.d.cts +8 -1
  38. package/dist/vector/trace.d.ts +8 -1
  39. package/package.json +1 -1
  40. package/skills/pixerate-image-editor/SKILL.md +8 -0
  41. package/skills/pixerate-image-selection-masking/SKILL.md +10 -2
@@ -1,3 +1,331 @@
1
+ import {
2
+ fitCubics,
3
+ simplifyRing
4
+ } from "./chunk-PWBZH3FK.js";
5
+
6
+ // src/vector/trace.ts
7
+ var VECTOR_TRACE_MAX_PIXELS = 4e6;
8
+ function signedArea(p) {
9
+ let a = 0;
10
+ for (let i = 0; i < p.length; i++) {
11
+ const [x0, y0] = p[i];
12
+ const [x1, y1] = p[(i + 1) % p.length];
13
+ a += x0 * y1 - x1 * y0;
14
+ }
15
+ return a / 2;
16
+ }
17
+ function pointInPoly(p, poly) {
18
+ let inside = false;
19
+ for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
20
+ const [xi, yi] = poly[i];
21
+ const [xj, yj] = poly[j];
22
+ if (yi > p[1] !== yj > p[1] && p[0] < (xj - xi) * (p[1] - yi) / (yj - yi) + xi) inside = !inside;
23
+ }
24
+ return inside;
25
+ }
26
+ function vectorTraceContours(field, w, h, threshold) {
27
+ const gw = w + 2;
28
+ const gh = h + 2;
29
+ const val = (i, j) => i === 0 || j === 0 || i > w || j > h ? -Infinity : field[(j - 1) * w + (i - 1)];
30
+ const inside = (i, j) => val(i, j) >= threshold;
31
+ const pos = (i, j) => [i - 0.5, j - 0.5];
32
+ const cross = (a, b) => {
33
+ const va = val(a[0], a[1]);
34
+ const vb = val(b[0], b[1]);
35
+ let t;
36
+ if (Number.isFinite(va) && Number.isFinite(vb) && Math.abs(vb - va) > 1e-12) t = Math.min(1, Math.max(0, (threshold - va) / (vb - va)));
37
+ else if (Number.isFinite(va)) t = va >= threshold ? 0.5 : 0;
38
+ else t = vb >= threshold ? 0.5 : 1;
39
+ const pa = pos(a[0], a[1]);
40
+ const pb = pos(b[0], b[1]);
41
+ return [pa[0] + (pb[0] - pa[0]) * t, pa[1] + (pb[1] - pa[1]) * t];
42
+ };
43
+ const hid = (i, j) => 2 * (j * gw + i);
44
+ const vid = (i, j) => 2 * (j * gw + i) + 1;
45
+ const next = /* @__PURE__ */ new Map();
46
+ const point = /* @__PURE__ */ new Map();
47
+ for (let j = 0; j < gh - 1; j++) {
48
+ for (let i = 0; i < gw - 1; i++) {
49
+ const tl = inside(i, j);
50
+ const tr = inside(i + 1, j);
51
+ const br = inside(i + 1, j + 1);
52
+ const bl = inside(i, j + 1);
53
+ const code = +tl << 3 | +tr << 2 | +br << 1 | +bl;
54
+ if (code === 0 || code === 15) continue;
55
+ const top = hid(i, j);
56
+ const bottom = hid(i, j + 1);
57
+ const left = vid(i, j);
58
+ const right = vid(i + 1, j);
59
+ const seg = (a, b) => next.set(a, b);
60
+ const mean = () => (val(i, j) + val(i + 1, j) + val(i + 1, j + 1) + val(i, j + 1)) / 4;
61
+ switch (code) {
62
+ case 1:
63
+ seg(left, bottom);
64
+ break;
65
+ case 2:
66
+ seg(bottom, right);
67
+ break;
68
+ case 3:
69
+ seg(left, right);
70
+ break;
71
+ case 4:
72
+ seg(right, top);
73
+ break;
74
+ case 5:
75
+ if (mean() >= threshold) {
76
+ seg(left, top);
77
+ seg(right, bottom);
78
+ } else {
79
+ seg(left, bottom);
80
+ seg(right, top);
81
+ }
82
+ break;
83
+ case 6:
84
+ seg(bottom, top);
85
+ break;
86
+ case 7:
87
+ seg(left, top);
88
+ break;
89
+ case 8:
90
+ seg(top, left);
91
+ break;
92
+ case 9:
93
+ seg(top, bottom);
94
+ break;
95
+ case 10:
96
+ if (mean() >= threshold) {
97
+ seg(top, right);
98
+ seg(bottom, left);
99
+ } else {
100
+ seg(top, left);
101
+ seg(bottom, right);
102
+ }
103
+ break;
104
+ case 11:
105
+ seg(top, right);
106
+ break;
107
+ case 12:
108
+ seg(right, left);
109
+ break;
110
+ case 13:
111
+ seg(right, bottom);
112
+ break;
113
+ case 14:
114
+ seg(bottom, left);
115
+ break;
116
+ }
117
+ const edges = [
118
+ [top, [i, j], [i + 1, j]],
119
+ [bottom, [i, j + 1], [i + 1, j + 1]],
120
+ [left, [i, j], [i, j + 1]],
121
+ [right, [i + 1, j], [i + 1, j + 1]]
122
+ ];
123
+ for (const [e, a, b] of edges) if (inside(a[0], a[1]) !== inside(b[0], b[1]) && !point.has(e)) point.set(e, cross(a, b));
124
+ }
125
+ }
126
+ const out = [];
127
+ for (const start of [...next.keys()].sort((a, b) => a - b)) {
128
+ if (!next.has(start)) continue;
129
+ const poly = [];
130
+ let e = start;
131
+ for (; ; ) {
132
+ const n = next.get(e);
133
+ if (n === void 0) break;
134
+ next.delete(e);
135
+ const p = point.get(e);
136
+ if (p) poly.push(p);
137
+ e = n;
138
+ if (e === start) break;
139
+ }
140
+ if (poly.length >= 3) out.push(poly);
141
+ }
142
+ return out;
143
+ }
144
+ function squareContourCorners(ring) {
145
+ const n = ring.length;
146
+ if (n < 4) return ring;
147
+ const eq = (u, v) => Math.abs(u - v) < 1e-9;
148
+ const out = [];
149
+ for (let i = 0; i < n; i++) {
150
+ const a = ring[i];
151
+ const b = ring[(i + 1) % n];
152
+ const p = ring[(i - 1 + n) % n];
153
+ const q = ring[(i + 2) % n];
154
+ const diagonal = Math.abs(Math.abs(b[0] - a[0]) - 0.5) < 0.02 && Math.abs(Math.abs(b[1] - a[1]) - 0.5) < 0.02;
155
+ const wraps = i === n - 1;
156
+ let corner = null;
157
+ if (diagonal && (!wraps || out[0] === ring[0])) {
158
+ if (eq(p[1], a[1]) && !eq(p[0], a[0]) && eq(q[0], b[0]) && !eq(q[1], b[1])) corner = [b[0], a[1]];
159
+ else if (eq(p[0], a[0]) && !eq(p[1], a[1]) && eq(q[1], b[1]) && !eq(q[0], b[0])) corner = [a[0], b[1]];
160
+ }
161
+ if (!corner) {
162
+ out.push(a);
163
+ continue;
164
+ }
165
+ out.push(corner);
166
+ if (wraps) out.shift();
167
+ else i++;
168
+ }
169
+ if (out.length < 3) return ring;
170
+ const m = out.length;
171
+ for (let k = 0; k < m; k++) {
172
+ const p = out[(k - 1 + m) % m];
173
+ const a = out[k];
174
+ const q = out[(k + 1) % m];
175
+ if (Math.abs((a[0] - p[0]) * (q[1] - a[1]) - (a[1] - p[1]) * (q[0] - a[0])) > 1e-6) return k ? [...out.slice(k), ...out.slice(0, k)] : out;
176
+ }
177
+ return out;
178
+ }
179
+ function loopsOf(field, w, h, threshold, tolerance, minArea, square = false) {
180
+ const loops = [];
181
+ for (const contour of vectorTraceContours(field, w, h, threshold)) {
182
+ const raw = square ? squareContourCorners(contour) : contour;
183
+ const area = signedArea(raw);
184
+ if (Math.abs(area) < Math.max(0, minArea)) continue;
185
+ const simple = simplifyRing(raw, Math.max(0.01, tolerance));
186
+ if (simple.length < 3) continue;
187
+ loops.push({ raw, simple, area, depth: 0 });
188
+ }
189
+ for (const l of loops) l.depth = loops.filter((o) => o !== l && Math.abs(o.area) > Math.abs(l.area) && pointInPoly(l.simple[0], o.simple)).length;
190
+ return loops.sort((a, b) => a.depth - b.depth || Math.abs(b.area) - Math.abs(a.area));
191
+ }
192
+ function loopSubpath(l, tolerance, roundness) {
193
+ if (roundness <= 0) return { closed: true, anchors: l.simple.map(([x, y]) => ({ x, y })) };
194
+ const cornerAngleDeg = 25 + roundness * 125;
195
+ const cubics = fitCubics(l.raw, Math.max(0.05, tolerance), {
196
+ closed: true,
197
+ cornerAngleDeg
198
+ });
199
+ if (!cubics.length) return { closed: true, anchors: l.simple.map(([x, y]) => ({ x, y })) };
200
+ const anchors = cubics.map((c, k) => {
201
+ const prev = cubics[(k - 1 + cubics.length) % cubics.length];
202
+ return {
203
+ x: c.p0[0],
204
+ y: c.p0[1],
205
+ in: { x: prev.p2[0], y: prev.p2[1] },
206
+ out: { x: c.p1[0], y: c.p1[1] }
207
+ };
208
+ });
209
+ return { closed: true, anchors };
210
+ }
211
+ function traceField(field, w, h, threshold, o) {
212
+ const tolerance = o.tolerance ?? 1;
213
+ const roundness = Math.min(1, Math.max(0, o.cornerRoundness ?? 0.5));
214
+ return loopsOf(field, w, h, threshold, tolerance, o.minArea ?? 10, o.squareCorners === true).map((l) => loopSubpath(l, tolerance, roundness));
215
+ }
216
+ function quantizeVectorTraceColors(pixels, count) {
217
+ const n = pixels.width * pixels.height;
218
+ const d = pixels.data;
219
+ const opaque = [];
220
+ for (let i = 0; i < n; i++) if (d[i * 4 + 3] >= 128) opaque.push(i);
221
+ const boxes = opaque.length ? [opaque] : [];
222
+ const range = (box, c) => {
223
+ let lo = 255;
224
+ let hi = 0;
225
+ for (const i of box) {
226
+ const v = d[i * 4 + c];
227
+ if (v < lo) lo = v;
228
+ if (v > hi) hi = v;
229
+ }
230
+ return hi - lo;
231
+ };
232
+ while (boxes.length < count) {
233
+ let best = -1;
234
+ let bestRange = 0;
235
+ let bestC = 0;
236
+ boxes.forEach((b, k) => {
237
+ if (b.length < 2) return;
238
+ for (let c = 0; c < 3; c++) {
239
+ const r = range(b, c);
240
+ if (r > bestRange) {
241
+ bestRange = r;
242
+ best = k;
243
+ bestC = c;
244
+ }
245
+ }
246
+ });
247
+ if (best < 0) break;
248
+ const box = boxes[best].slice().sort((a, b) => d[a * 4 + bestC] - d[b * 4 + bestC]);
249
+ const key = (k) => d[box[k] * 4 + bestC];
250
+ const v = key(box.length >> 1);
251
+ let mid = box.findIndex((_, k) => key(k) >= v);
252
+ if (mid <= 0) mid = box.findIndex((_, k) => key(k) > v);
253
+ boxes.splice(best, 1, box.slice(0, mid), box.slice(mid));
254
+ }
255
+ const palette = boxes.map((b) => {
256
+ const s = [0, 0, 0];
257
+ for (const i of b) for (let c = 0; c < 3; c++) s[c] += d[i * 4 + c];
258
+ return [Math.round(s[0] / b.length), Math.round(s[1] / b.length), Math.round(s[2] / b.length)];
259
+ });
260
+ const index = new Int16Array(n).fill(-1);
261
+ const counts = palette.map(() => 0);
262
+ for (const i of opaque) {
263
+ let best = 0;
264
+ let bestD = Infinity;
265
+ palette.forEach((p, k) => {
266
+ const dd = (d[i * 4] - p[0]) ** 2 + (d[i * 4 + 1] - p[1]) ** 2 + (d[i * 4 + 2] - p[2]) ** 2;
267
+ if (dd < bestD) {
268
+ bestD = dd;
269
+ best = k;
270
+ }
271
+ });
272
+ index[i] = best;
273
+ counts[best]++;
274
+ }
275
+ return { palette, index, counts };
276
+ }
277
+ var hex = (c) => `#${c.map((v) => Math.round(v).toString(16).padStart(2, "0")).join("")}`;
278
+ function traceVectorImage(pixels, options = {}) {
279
+ const { width: w, height: h, data } = pixels;
280
+ if (!(w > 0 && h > 0) || data.length < w * h * 4) throw new Error("The image has no pixels (or fewer than width \xD7 height \xD7 4 values).");
281
+ if (w * h > VECTOR_TRACE_MAX_PIXELS) throw new Error(`The image has ${w * h} pixels; scale it down to at most ${VECTOR_TRACE_MAX_PIXELS} to trace it.`);
282
+ const n = w * h;
283
+ if ((options.mode ?? "threshold") === "threshold") {
284
+ const field = new Float32Array(n);
285
+ const byAlpha = options.channel === "alpha";
286
+ for (let i = 0; i < n; i++) {
287
+ const a = data[i * 4 + 3] / 255;
288
+ field[i] = byAlpha ? a : a * (1 - (0.2126 * data[i * 4] + 0.7152 * data[i * 4 + 1] + 0.0722 * data[i * 4 + 2]) / 255);
289
+ }
290
+ const subpaths = traceField(field, w, h, Math.min(1, Math.max(0, options.threshold ?? 0.5)), options);
291
+ return subpaths.length ? [
292
+ {
293
+ id: "trace-1",
294
+ type: "path",
295
+ name: "Trace",
296
+ fill: options.color ?? "#000000",
297
+ stroke: null,
298
+ fillRule: "evenodd",
299
+ subpaths
300
+ }
301
+ ] : [];
302
+ }
303
+ const count = Math.min(32, Math.max(2, Math.round(options.colors ?? 6)));
304
+ const q = quantizeVectorTraceColors(pixels, count);
305
+ const order = q.palette.map((_, k) => k).filter((k) => q.counts[k] > 0).sort((a, b) => q.counts[b] - q.counts[a]);
306
+ const rank = new Map(order.map((k, r) => [k, r]));
307
+ const out = [];
308
+ order.forEach((k, r) => {
309
+ const field = new Uint8Array(n);
310
+ for (let i = 0; i < n; i++) {
311
+ const ix = q.index[i];
312
+ field[i] = ix >= 0 && rank.get(ix) >= r ? 1 : 0;
313
+ }
314
+ const subpaths = traceField(field, w, h, 0.5, options);
315
+ if (subpaths.length)
316
+ out.push({
317
+ id: `trace-${out.length + 1}`,
318
+ type: "path",
319
+ name: hex(q.palette[k]),
320
+ fill: hex(q.palette[k]),
321
+ stroke: null,
322
+ fillRule: "evenodd",
323
+ subpaths
324
+ });
325
+ });
326
+ return out;
327
+ }
328
+
1
329
  // src/text/opentype.ts
2
330
  var fail = (message) => {
3
331
  throw new Error(message);
@@ -923,6 +1251,11 @@ async function unwoff(view) {
923
1251
  }
924
1252
 
925
1253
  export {
1254
+ VECTOR_TRACE_MAX_PIXELS,
1255
+ vectorTraceContours,
1256
+ traceField,
1257
+ quantizeVectorTraceColors,
1258
+ traceVectorImage,
926
1259
  parseOpenTypeFont,
927
1260
  loadOpenTypeFont
928
1261
  };