@pixerate/editor 0.209.0 → 0.211.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/dist/{chunk-QE7D7TZR.cjs → chunk-H65IBXFI.cjs} +74 -25
- package/dist/{chunk-X4LUGTQX.cjs → chunk-L52VMQZS.cjs} +21 -10
- package/dist/{chunk-DWYQPL2A.js → chunk-LQ5SALHK.js} +13 -2
- package/dist/chunk-MQDIP4CF.cjs +462 -0
- package/dist/{chunk-KFIOEOCQ.js → chunk-W3TOZTL7.js} +55 -6
- package/dist/chunk-XI2NV5U7.js +462 -0
- package/dist/{chunk-3EHPLDFQ.cjs → chunk-YF3YZZ6G.cjs} +16 -453
- package/dist/{chunk-USQCKRZG.js → chunk-ZGJGSCUW.js} +8 -445
- package/dist/image-editor/content-aware-fill-session.d.cts +109 -0
- package/dist/image-editor/content-aware-fill-session.d.ts +109 -0
- package/dist/image-editor/controller.d.cts +111 -1
- package/dist/image-editor/controller.d.ts +111 -1
- package/dist/image-editor/index.cjs +1879 -1052
- package/dist/image-editor/index.d.cts +2 -0
- package/dist/image-editor/index.d.ts +2 -0
- package/dist/image-editor/index.js +869 -42
- package/dist/image-editor/layers/serialize.d.cts +5 -8
- package/dist/image-editor/layers/serialize.d.ts +5 -8
- package/dist/image-editor/paths.d.cts +41 -0
- package/dist/image-editor/paths.d.ts +41 -0
- package/dist/image-editor/psd/export.d.cts +8 -1
- package/dist/image-editor/psd/export.d.ts +8 -1
- package/dist/image-editor/psd/import.d.cts +7 -0
- package/dist/image-editor/psd/import.d.ts +7 -0
- package/dist/image-editor/psd/paths.d.cts +11 -0
- package/dist/image-editor/psd/paths.d.ts +11 -0
- package/dist/image-editor/selection/gesture.d.cts +10 -2
- package/dist/image-editor/selection/gesture.d.ts +10 -2
- package/dist/image-editor/selection/select-and-mask.d.cts +5 -0
- package/dist/image-editor/selection/select-and-mask.d.ts +5 -0
- package/dist/image-editor/selection/worker.d.cts +10 -0
- package/dist/image-editor/selection/worker.d.ts +10 -0
- package/dist/image-editor/selection-worker.cjs +4 -3
- package/dist/image-editor/selection-worker.js +2 -1
- package/dist/image-editor/types.d.cts +12 -0
- package/dist/image-editor/types.d.ts +12 -0
- package/dist/paint/heal.d.cts +3 -1
- package/dist/paint/heal.d.ts +3 -1
- package/dist/paint/index.cjs +4 -2
- package/dist/paint/index.js +10 -8
- package/dist/paint-editor/index.cjs +6 -6
- package/dist/paint-editor/index.js +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +20 -22
- package/skills/pixerate-image-editor/references/layer-effects.md +24 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +2 -1
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// src/paint/heal.ts
|
|
2
|
+
var TOL = 2e-3;
|
|
3
|
+
var FINE_ITERS = 30;
|
|
4
|
+
var FINE_OMEGA = 1.7;
|
|
5
|
+
function solveMembrane(w, h, unknown, v) {
|
|
6
|
+
if (unknown.length !== w * h || v.length !== w * h) throw new RangeError(`solveMembrane: expected ${w * h} cells, got ${unknown.length} and ${v.length}.`);
|
|
7
|
+
const u = new Uint8Array(w * h);
|
|
8
|
+
for (let i = 0; i < u.length; i++) u[i] = unknown[i] ? 1 : 0;
|
|
9
|
+
solveLevel(w, h, u, v, 0);
|
|
10
|
+
}
|
|
11
|
+
function solveLevel(w, h, unknown, v, depth) {
|
|
12
|
+
let nUnknown = 0;
|
|
13
|
+
let knownSum = 0;
|
|
14
|
+
let nKnown = 0;
|
|
15
|
+
for (let i = 0; i < unknown.length; i++) {
|
|
16
|
+
if (unknown[i]) nUnknown++;
|
|
17
|
+
else {
|
|
18
|
+
knownSum += v[i];
|
|
19
|
+
nKnown++;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (nUnknown === 0 || nKnown === 0) return;
|
|
23
|
+
if (w >= 8 && h >= 8 && nUnknown > 64 && depth < 16) {
|
|
24
|
+
const cw = Math.ceil(w / 2);
|
|
25
|
+
const chh = Math.ceil(h / 2);
|
|
26
|
+
const cu = new Uint8Array(cw * chh).fill(1);
|
|
27
|
+
const cv = new Float32Array(cw * chh);
|
|
28
|
+
for (let cy = 0; cy < chh; cy++) {
|
|
29
|
+
for (let cx = 0; cx < cw; cx++) {
|
|
30
|
+
let s = 0;
|
|
31
|
+
let n = 0;
|
|
32
|
+
for (let k = 0; k < 4; k++) {
|
|
33
|
+
const x = cx * 2 + (k & 1);
|
|
34
|
+
const y = cy * 2 + (k >> 1);
|
|
35
|
+
if (x < w && y < h && !unknown[y * w + x]) {
|
|
36
|
+
s += v[y * w + x];
|
|
37
|
+
n++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (n > 0) {
|
|
41
|
+
cu[cy * cw + cx] = 0;
|
|
42
|
+
cv[cy * cw + cx] = s / n;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
solveLevel(cw, chh, cu, cv, depth + 1);
|
|
47
|
+
for (let y = 0; y < h; y++) {
|
|
48
|
+
const fy = Math.min(chh - 1, Math.max(0, (y + 0.5) / 2 - 0.5));
|
|
49
|
+
const y0 = Math.floor(fy);
|
|
50
|
+
const y1 = Math.min(chh - 1, y0 + 1);
|
|
51
|
+
const ty = fy - y0;
|
|
52
|
+
for (let x = 0; x < w; x++) {
|
|
53
|
+
if (!unknown[y * w + x]) continue;
|
|
54
|
+
const fx = Math.min(cw - 1, Math.max(0, (x + 0.5) / 2 - 0.5));
|
|
55
|
+
const x0 = Math.floor(fx);
|
|
56
|
+
const x1 = Math.min(cw - 1, x0 + 1);
|
|
57
|
+
const tx = fx - x0;
|
|
58
|
+
const a = cv[y0 * cw + x0] + (cv[y0 * cw + x1] - cv[y0 * cw + x0]) * tx;
|
|
59
|
+
const b = cv[y1 * cw + x0] + (cv[y1 * cw + x1] - cv[y1 * cw + x0]) * tx;
|
|
60
|
+
v[y * w + x] = a + (b - a) * ty;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
sor(w, h, unknown, v, FINE_ITERS, FINE_OMEGA);
|
|
64
|
+
} else {
|
|
65
|
+
const mean = knownSum / nKnown;
|
|
66
|
+
for (let i = 0; i < v.length; i++) if (unknown[i]) v[i] = mean;
|
|
67
|
+
sor(w, h, unknown, v, 2e3, 1.85);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function sor(w, h, unknown, v, maxIter, omega) {
|
|
71
|
+
let n = 0;
|
|
72
|
+
for (let i = 0; i < unknown.length; i++) if (unknown[i]) n++;
|
|
73
|
+
const cells = new Uint32Array(n);
|
|
74
|
+
const masks = new Uint8Array(n);
|
|
75
|
+
for (let i = 0, k = 0; i < unknown.length; i++) {
|
|
76
|
+
if (!unknown[i]) continue;
|
|
77
|
+
const x = i % w;
|
|
78
|
+
const y = (i - x) / w;
|
|
79
|
+
cells[k] = i;
|
|
80
|
+
masks[k++] = (x > 0 ? 1 : 0) | (x + 1 < w ? 2 : 0) | (y > 0 ? 4 : 0) | (y + 1 < h ? 8 : 0);
|
|
81
|
+
}
|
|
82
|
+
for (let it = 0; it < maxIter; it++) {
|
|
83
|
+
let maxD = 0;
|
|
84
|
+
for (let k = 0; k < n; k++) {
|
|
85
|
+
const i = cells[k];
|
|
86
|
+
const m = masks[k];
|
|
87
|
+
let s;
|
|
88
|
+
let c;
|
|
89
|
+
if (m === 15) {
|
|
90
|
+
s = v[i - 1] + v[i + 1] + v[i - w] + v[i + w];
|
|
91
|
+
c = 4;
|
|
92
|
+
} else {
|
|
93
|
+
s = 0;
|
|
94
|
+
c = 0;
|
|
95
|
+
if (m & 1) s += v[i - 1], c++;
|
|
96
|
+
if (m & 2) s += v[i + 1], c++;
|
|
97
|
+
if (m & 4) s += v[i - w], c++;
|
|
98
|
+
if (m & 8) s += v[i + w], c++;
|
|
99
|
+
if (c === 0) continue;
|
|
100
|
+
}
|
|
101
|
+
const d = omega * (s / c - v[i]);
|
|
102
|
+
v[i] += d;
|
|
103
|
+
const ad = d < 0 ? -d : d;
|
|
104
|
+
if (ad > maxD) maxD = ad;
|
|
105
|
+
}
|
|
106
|
+
if (maxD < TOL) break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function seamlessClone(w, h, ch, src, dst, mask) {
|
|
110
|
+
const n = w * h;
|
|
111
|
+
if (src.length !== n * ch || dst.length !== n * ch || mask.length !== n) throw new RangeError("seamlessClone: src, dst and mask must cover w \xD7 h pixels.");
|
|
112
|
+
const out = new Float32Array(n * ch);
|
|
113
|
+
const hv = new Float32Array(n);
|
|
114
|
+
for (let c = 0; c < ch; c++) {
|
|
115
|
+
for (let i = 0; i < n; i++) hv[i] = mask[i] ? 0 : dst[i * ch + c] - src[i * ch + c];
|
|
116
|
+
solveMembrane(w, h, mask, hv);
|
|
117
|
+
for (let i = 0; i < n; i++) out[i * ch + c] = mask[i] ? src[i * ch + c] + hv[i] : dst[i * ch + c];
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
function membraneFill(w, h, ch, img, hole) {
|
|
122
|
+
const n = w * h;
|
|
123
|
+
const out = new Float32Array(n * ch);
|
|
124
|
+
const v = new Float32Array(n);
|
|
125
|
+
for (let c = 0; c < ch; c++) {
|
|
126
|
+
for (let i = 0; i < n; i++) v[i] = hole[i] ? 0 : img[i * ch + c];
|
|
127
|
+
solveMembrane(w, h, hole, v);
|
|
128
|
+
for (let i = 0; i < n; i++) out[i * ch + c] = v[i];
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function dilateMask(w, h, mask, r) {
|
|
133
|
+
const tmp = new Uint8Array(w * h);
|
|
134
|
+
for (let y = 0; y < h; y++) {
|
|
135
|
+
for (let x = 0; x < w; x++) {
|
|
136
|
+
if (!mask[y * w + x]) continue;
|
|
137
|
+
for (let xx = Math.max(0, x - r); xx <= Math.min(w - 1, x + r); xx++) tmp[y * w + xx] = 1;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const out = new Uint8Array(w * h);
|
|
141
|
+
for (let y = 0; y < h; y++) {
|
|
142
|
+
for (let x = 0; x < w; x++) {
|
|
143
|
+
if (!tmp[y * w + x]) continue;
|
|
144
|
+
for (let yy = Math.max(0, y - r); yy <= Math.min(h - 1, y + r); yy++) out[yy * w + x] = 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
var MaskIntegral = class {
|
|
150
|
+
constructor(w, h, mask) {
|
|
151
|
+
this.w = w;
|
|
152
|
+
this.h = h;
|
|
153
|
+
const W = w + 1;
|
|
154
|
+
this.s = new Uint32Array(W * (h + 1));
|
|
155
|
+
for (let y = 0; y < h; y++) {
|
|
156
|
+
let row = 0;
|
|
157
|
+
for (let x = 0; x < w; x++) {
|
|
158
|
+
row += mask[y * w + x] ? 1 : 0;
|
|
159
|
+
this.s[(y + 1) * W + x + 1] = this.s[y * W + x + 1] + row;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
/** Masked cells in [x0, x1) × [y0, y1) (clipped to the grid). */
|
|
167
|
+
count(x0, y0, x1, y1) {
|
|
168
|
+
const W = this.w + 1;
|
|
169
|
+
const a = Math.max(0, Math.min(this.w, x0));
|
|
170
|
+
const b = Math.max(0, Math.min(this.h, y0));
|
|
171
|
+
const c = Math.max(0, Math.min(this.w, x1));
|
|
172
|
+
const d = Math.max(0, Math.min(this.h, y1));
|
|
173
|
+
if (c <= a || d <= b) return 0;
|
|
174
|
+
return this.s[d * W + c] - this.s[b * W + c] - this.s[d * W + a] + this.s[b * W + a];
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
function maskBounds(w, h, mask) {
|
|
178
|
+
let x0 = w;
|
|
179
|
+
let y0 = h;
|
|
180
|
+
let x1 = -1;
|
|
181
|
+
let y1 = -1;
|
|
182
|
+
for (let y = 0; y < h; y++) {
|
|
183
|
+
for (let x = 0; x < w; x++) {
|
|
184
|
+
if (!mask[y * w + x]) continue;
|
|
185
|
+
if (x < x0) x0 = x;
|
|
186
|
+
if (x > x1) x1 = x;
|
|
187
|
+
if (y < y0) y0 = y;
|
|
188
|
+
if (y > y1) y1 = y;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return x1 < 0 ? null : [x0, y0, x1 + 1, y1 + 1];
|
|
192
|
+
}
|
|
193
|
+
function bestOffset(w, h, ch, img, hole, ring, maxRadius) {
|
|
194
|
+
const bb = maskBounds(w, h, hole);
|
|
195
|
+
if (!bb) return null;
|
|
196
|
+
const extent = Math.max(w, h);
|
|
197
|
+
const R = Math.max(0, Math.min(extent, Math.floor(maxRadius)));
|
|
198
|
+
if (R === 0) return null;
|
|
199
|
+
const rg = Math.max(1, Math.min(extent, Math.floor(ring)));
|
|
200
|
+
const hs = new MaskIntegral(w, h, hole);
|
|
201
|
+
const x0 = bb[0] - rg;
|
|
202
|
+
const y0 = bb[1] - rg;
|
|
203
|
+
const x1 = bb[2] + rg;
|
|
204
|
+
const y1 = bb[3] + rg;
|
|
205
|
+
const pts = [];
|
|
206
|
+
for (let y = Math.max(0, y0); y < Math.min(h, y1); y++) {
|
|
207
|
+
for (let x = Math.max(0, x0); x < Math.min(w, x1); x++) {
|
|
208
|
+
if (hole[y * w + x]) continue;
|
|
209
|
+
if (hs.count(x - rg, y - rg, x + rg + 1, y + rg + 1) > 0) pts.push(x, y);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const stride = Math.max(1, Math.floor(pts.length / 2 / 2e3));
|
|
213
|
+
const sx = [];
|
|
214
|
+
const sy = [];
|
|
215
|
+
for (let i = 0; i < pts.length; i += 2 * stride) {
|
|
216
|
+
sx.push(pts[i]);
|
|
217
|
+
sy.push(pts[i + 1]);
|
|
218
|
+
}
|
|
219
|
+
const n = Math.max(1, sx.length);
|
|
220
|
+
const evalAt = (dx, dy, cutoff) => {
|
|
221
|
+
if (dx === 0 && dy === 0) return null;
|
|
222
|
+
const hx0 = x0 + dx;
|
|
223
|
+
const hy0 = y0 + dy;
|
|
224
|
+
const hx1 = x1 + dx;
|
|
225
|
+
const hy1 = y1 + dy;
|
|
226
|
+
if (hx0 < 0 || hy0 < 0 || hx1 > w || hy1 > h) return null;
|
|
227
|
+
if (hs.count(hx0, hy0, hx1, hy1) !== 0) return null;
|
|
228
|
+
const penalty = 1 + 1e-3 * (Math.hypot(dx, dy) / R);
|
|
229
|
+
let e = 0;
|
|
230
|
+
for (let i = 0; i < sx.length; i++) {
|
|
231
|
+
const a = (sy[i] * w + sx[i]) * ch;
|
|
232
|
+
const b = ((sy[i] + dy) * w + sx[i] + dx) * ch;
|
|
233
|
+
for (let c = 0; c < ch; c++) {
|
|
234
|
+
const d = img[a + c] - img[b + c];
|
|
235
|
+
e += d * d;
|
|
236
|
+
}
|
|
237
|
+
if ((i & 31) === 31 && e / n * penalty >= cutoff) return null;
|
|
238
|
+
}
|
|
239
|
+
return e / n * penalty;
|
|
240
|
+
};
|
|
241
|
+
const found = { best: null, e: Infinity };
|
|
242
|
+
const consider = (dx, dy) => {
|
|
243
|
+
const e = evalAt(dx, dy, found.e);
|
|
244
|
+
if (e !== null && e < found.e) {
|
|
245
|
+
found.e = e;
|
|
246
|
+
found.best = [dx, dy];
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
const step = Math.max(1, Math.floor(R / 24));
|
|
250
|
+
for (let dy = -R; dy <= R; dy += step) for (let dx = -R; dx <= R; dx += step) consider(dx, dy);
|
|
251
|
+
if (step > 1 && found.best) {
|
|
252
|
+
const [bx, by] = found.best;
|
|
253
|
+
for (let dy = by - step; dy <= by + step; dy++) for (let dx = bx - step; dx <= bx + step; dx++) consider(dx, dy);
|
|
254
|
+
}
|
|
255
|
+
return found.best;
|
|
256
|
+
}
|
|
257
|
+
var PATCH_R = 3;
|
|
258
|
+
function rng(seed) {
|
|
259
|
+
let a = seed >>> 0;
|
|
260
|
+
return () => {
|
|
261
|
+
a = a + 1831565813 >>> 0;
|
|
262
|
+
let t = a;
|
|
263
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
264
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
265
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function downsample(l, ch) {
|
|
269
|
+
const w = Math.max(1, l.w >> 1);
|
|
270
|
+
const h = Math.max(1, l.h >> 1);
|
|
271
|
+
const img = new Float32Array(w * h * ch);
|
|
272
|
+
const hole = new Uint8Array(w * h);
|
|
273
|
+
const exclude = l.exclude ? new Uint8Array(w * h) : null;
|
|
274
|
+
for (let y = 0; y < h; y++) {
|
|
275
|
+
for (let x = 0; x < w; x++) {
|
|
276
|
+
let n = 0;
|
|
277
|
+
const o = (y * w + x) * ch;
|
|
278
|
+
for (let dy = 0; dy < 2; dy++) {
|
|
279
|
+
for (let dx = 0; dx < 2; dx++) {
|
|
280
|
+
const sx = Math.min(l.w - 1, 2 * x + dx);
|
|
281
|
+
const sy = Math.min(l.h - 1, 2 * y + dy);
|
|
282
|
+
const i = sy * l.w + sx;
|
|
283
|
+
if (exclude && l.exclude[i]) exclude[y * w + x] = 1;
|
|
284
|
+
if (l.hole[i]) {
|
|
285
|
+
hole[y * w + x] = 1;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
for (let c = 0; c < ch; c++) img[o + c] += l.img[i * ch + c];
|
|
289
|
+
n++;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (n) for (let c = 0; c < ch; c++) img[o + c] /= n;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return { w, h, img, hole, exclude };
|
|
296
|
+
}
|
|
297
|
+
function contentAwareFill(w, h, ch, img, hole, options = {}) {
|
|
298
|
+
const iters = _nullishCoalesce(options.iterations, () => ( 4));
|
|
299
|
+
const base = {
|
|
300
|
+
w,
|
|
301
|
+
h,
|
|
302
|
+
img: Float32Array.from(img),
|
|
303
|
+
hole: Uint8Array.from(hole, (v) => v ? 1 : 0),
|
|
304
|
+
exclude: options.exclude ? Uint8Array.from(options.exclude, (v) => v ? 1 : 0) : null
|
|
305
|
+
};
|
|
306
|
+
const levels = [base];
|
|
307
|
+
while (levels.length < 4) {
|
|
308
|
+
const top = levels[levels.length - 1];
|
|
309
|
+
const b = maskBounds(top.w, top.h, top.hole);
|
|
310
|
+
if (!b || Math.max(b[2] - b[0], b[3] - b[1]) < 4 * PATCH_R || top.w < 8 * PATCH_R || top.h < 8 * PATCH_R) break;
|
|
311
|
+
levels.push(downsample(top, ch));
|
|
312
|
+
}
|
|
313
|
+
const random = rng(_nullishCoalesce(options.seed, () => ( w * 73856093 + h * 19349663)));
|
|
314
|
+
let prevFill = null;
|
|
315
|
+
let prevNnf = null;
|
|
316
|
+
let prevW = 0;
|
|
317
|
+
for (let li = levels.length - 1; li >= 0; li--) {
|
|
318
|
+
const { w: W, h: H, hole: holeL, exclude: excludeL } = levels[li];
|
|
319
|
+
const cur = new Float32Array(levels[li].img);
|
|
320
|
+
if (prevFill) {
|
|
321
|
+
for (let i = 0; i < W * H; i++) {
|
|
322
|
+
if (!holeL[i]) continue;
|
|
323
|
+
const x = Math.min(prevW - 1, i % W >> 1);
|
|
324
|
+
const y = Math.min(Math.floor(prevFill.length / ch / prevW) - 1, Math.floor(i / W) >> 1);
|
|
325
|
+
for (let c = 0; c < ch; c++) cur[i * ch + c] = prevFill[(y * prevW + x) * ch + c];
|
|
326
|
+
}
|
|
327
|
+
} else cur.set(membraneFill(W, H, ch, cur, holeL));
|
|
328
|
+
const holeBlocked = dilateMask(W, H, holeL, PATCH_R);
|
|
329
|
+
let blocked = holeBlocked;
|
|
330
|
+
if (excludeL) {
|
|
331
|
+
const off = dilateMask(W, H, excludeL, PATCH_R);
|
|
332
|
+
const sourceBlocked = new Uint8Array(W * H);
|
|
333
|
+
for (let i = 0; i < W * H; i++) sourceBlocked[i] = holeBlocked[i] || off[i] ? 1 : 0;
|
|
334
|
+
blocked = sourceBlocked;
|
|
335
|
+
}
|
|
336
|
+
const valid = [];
|
|
337
|
+
for (let y = PATCH_R; y < H - PATCH_R; y++) for (let x = PATCH_R; x < W - PATCH_R; x++) if (!blocked[y * W + x]) valid.push(y * W + x);
|
|
338
|
+
if (!valid.length) {
|
|
339
|
+
prevFill = cur;
|
|
340
|
+
prevNnf = null;
|
|
341
|
+
prevW = W;
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const targets = [];
|
|
345
|
+
for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) if (holeBlocked[y * W + x]) targets.push(y * W + x);
|
|
346
|
+
const nnf = new Int32Array(W * H).fill(-1);
|
|
347
|
+
const cost = new Float64Array(W * H);
|
|
348
|
+
const clampC = (v, n) => Math.min(n - 1 - PATCH_R, Math.max(PATCH_R, v));
|
|
349
|
+
const dist = (t, s, limit) => {
|
|
350
|
+
const tx = clampC(t % W, W);
|
|
351
|
+
const ty = clampC(Math.floor(t / W), H);
|
|
352
|
+
const sx = s % W;
|
|
353
|
+
const sy = Math.floor(s / W);
|
|
354
|
+
let d = 0;
|
|
355
|
+
for (let dy = -PATCH_R; dy <= PATCH_R; dy++) {
|
|
356
|
+
const tr = (ty + dy) * W;
|
|
357
|
+
const sr = (sy + dy) * W;
|
|
358
|
+
for (let dx = -PATCH_R; dx <= PATCH_R; dx++) {
|
|
359
|
+
const a = (tr + tx + dx) * ch;
|
|
360
|
+
const b = (sr + sx + dx) * ch;
|
|
361
|
+
for (let c = 0; c < ch; c++) {
|
|
362
|
+
const e = cur[a + c] - cur[b + c];
|
|
363
|
+
d += e * e;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (d >= limit) return d;
|
|
367
|
+
}
|
|
368
|
+
return d;
|
|
369
|
+
};
|
|
370
|
+
const isValid = (s) => {
|
|
371
|
+
const x = s % W;
|
|
372
|
+
const y = Math.floor(s / W);
|
|
373
|
+
return s >= 0 && x >= PATCH_R && y >= PATCH_R && x < W - PATCH_R && y < H - PATCH_R && !blocked[s];
|
|
374
|
+
};
|
|
375
|
+
for (const t of targets) {
|
|
376
|
+
let s = -1;
|
|
377
|
+
if (prevNnf) {
|
|
378
|
+
const px = Math.min(prevW - 1, t % W >> 1);
|
|
379
|
+
const py = Math.floor(t / W) >> 1;
|
|
380
|
+
const ps = prevNnf[py * prevW + px];
|
|
381
|
+
if (ps >= 0) {
|
|
382
|
+
const cand = (Math.floor(ps / prevW) * 2 + (Math.floor(t / W) & 1)) * W + ps % prevW * 2 + (t % W & 1);
|
|
383
|
+
if (isValid(cand)) s = cand;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (s < 0) s = valid[Math.floor(random() * valid.length)];
|
|
387
|
+
nnf[t] = s;
|
|
388
|
+
}
|
|
389
|
+
for (let em = 0; em < iters; em++) {
|
|
390
|
+
for (const t of targets) cost[t] = dist(t, nnf[t], Infinity);
|
|
391
|
+
for (let pass = 0; pass < 4; pass++) {
|
|
392
|
+
const fwd = pass % 2 === 0;
|
|
393
|
+
const step = fwd ? 1 : -1;
|
|
394
|
+
for (let k = 0; k < targets.length; k++) {
|
|
395
|
+
const t = targets[fwd ? k : targets.length - 1 - k];
|
|
396
|
+
const tx = t % W;
|
|
397
|
+
const ty = Math.floor(t / W);
|
|
398
|
+
for (const [nx, ny] of [[tx - step, ty], [tx, ty - step]]) {
|
|
399
|
+
if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;
|
|
400
|
+
const ns = nnf[ny * W + nx];
|
|
401
|
+
if (ns < 0) continue;
|
|
402
|
+
const cand = ns + step * (nx === tx ? W : 1);
|
|
403
|
+
if (!isValid(cand) || cand === nnf[t]) continue;
|
|
404
|
+
const d = dist(t, cand, cost[t]);
|
|
405
|
+
if (d < cost[t]) {
|
|
406
|
+
cost[t] = d;
|
|
407
|
+
nnf[t] = cand;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const bx = nnf[t] % W;
|
|
411
|
+
const by = Math.floor(nnf[t] / W);
|
|
412
|
+
for (let r = Math.max(W, H); r >= 1; r >>= 1) {
|
|
413
|
+
const cx = Math.round(bx + (random() * 2 - 1) * r);
|
|
414
|
+
const cy = Math.round(by + (random() * 2 - 1) * r);
|
|
415
|
+
const cand = cy * W + cx;
|
|
416
|
+
if (cx < 0 || cy < 0 || cx >= W || cy >= H || !isValid(cand)) continue;
|
|
417
|
+
const d = dist(t, cand, cost[t]);
|
|
418
|
+
if (d < cost[t]) {
|
|
419
|
+
cost[t] = d;
|
|
420
|
+
nnf[t] = cand;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const acc = new Float64Array(W * H * ch);
|
|
426
|
+
const wsum = new Float64Array(W * H);
|
|
427
|
+
for (const t of targets) {
|
|
428
|
+
const tx = clampC(t % W, W);
|
|
429
|
+
const ty = clampC(Math.floor(t / W), H);
|
|
430
|
+
const s = nnf[t];
|
|
431
|
+
const sx = s % W;
|
|
432
|
+
const sy = Math.floor(s / W);
|
|
433
|
+
for (let dy = -PATCH_R; dy <= PATCH_R; dy++) {
|
|
434
|
+
for (let dx = -PATCH_R; dx <= PATCH_R; dx++) {
|
|
435
|
+
const q = (ty + dy) * W + tx + dx;
|
|
436
|
+
if (!holeL[q]) continue;
|
|
437
|
+
const src = ((sy + dy) * W + sx + dx) * ch;
|
|
438
|
+
for (let c = 0; c < ch; c++) acc[q * ch + c] += cur[src + c];
|
|
439
|
+
wsum[q] += 1;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
for (let q = 0; q < W * H; q++) if (holeL[q] && wsum[q] > 0) for (let c = 0; c < ch; c++) cur[q * ch + c] = acc[q * ch + c] / wsum[q];
|
|
444
|
+
}
|
|
445
|
+
prevFill = cur;
|
|
446
|
+
prevNnf = nnf;
|
|
447
|
+
prevW = W;
|
|
448
|
+
}
|
|
449
|
+
const out = Float32Array.from(img);
|
|
450
|
+
for (let i = 0; i < w * h; i++) if (base.hole[i]) for (let c = 0; c < ch; c++) out[i * ch + c] = prevFill[i * ch + c];
|
|
451
|
+
return out;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
exports.solveMembrane = solveMembrane; exports.seamlessClone = seamlessClone; exports.membraneFill = membraneFill; exports.dilateMask = dilateMask; exports.maskBounds = maskBounds; exports.bestOffset = bestOffset; exports.contentAwareFill = contentAwareFill;
|
|
@@ -5168,9 +5168,9 @@ function serializeVectorMask(vm) {
|
|
|
5168
5168
|
if (vm.feather) out.feather = vm.feather;
|
|
5169
5169
|
return out;
|
|
5170
5170
|
}
|
|
5171
|
-
function
|
|
5172
|
-
if (!Array.isArray(
|
|
5173
|
-
|
|
5171
|
+
function readShapeComponents(stored) {
|
|
5172
|
+
if (!Array.isArray(stored)) throw new Error("its components are missing");
|
|
5173
|
+
return stored.map((raw, i) => {
|
|
5174
5174
|
const c = raw;
|
|
5175
5175
|
const op = requireOperation(c?.op, `components[${i}].op`);
|
|
5176
5176
|
if (c?.live) {
|
|
@@ -5179,7 +5179,9 @@ function readVectorMask(r) {
|
|
|
5179
5179
|
}
|
|
5180
5180
|
return { op, subpaths: Array.isArray(c?.subpaths) && !c.subpaths.length ? [] : normalizeSubpaths(c?.subpaths, `components[${i}].subpaths`) };
|
|
5181
5181
|
});
|
|
5182
|
-
|
|
5182
|
+
}
|
|
5183
|
+
function readVectorMask(r) {
|
|
5184
|
+
const vm = { components: readShapeComponents(r.components) };
|
|
5183
5185
|
if (r.fillRule === "evenodd") vm.fillRule = "evenodd";
|
|
5184
5186
|
if (r.inverted === true) vm.inverted = true;
|
|
5185
5187
|
if (r.enabled === false) vm.enabled = false;
|
|
@@ -6957,6 +6959,19 @@ function vectorBlockForComponents(components, inverted, width, height, flags = 0
|
|
|
6957
6959
|
});
|
|
6958
6960
|
return writeVectorBlock({ subpaths, initialFill: subpaths.length === 0 && inverted }, flags & ~VMSK_INVERT | (inverted && subpaths.length ? VMSK_INVERT : 0));
|
|
6959
6961
|
}
|
|
6962
|
+
var RESOURCE_WORK_PATH = 1025;
|
|
6963
|
+
var RESOURCE_PATH_FIRST = 2e3;
|
|
6964
|
+
var RESOURCE_PATH_LAST = 2997;
|
|
6965
|
+
function componentsFromPathResource(data, width, height) {
|
|
6966
|
+
const block = new Uint8Array(8 + data.length);
|
|
6967
|
+
new DataView(block.buffer).setUint32(0, 3);
|
|
6968
|
+
block.set(data, 8);
|
|
6969
|
+
const read2 = componentsFromVectorBlock(block, width, height);
|
|
6970
|
+
return read2 ? { components: read2.components, inverted: read2.inverted } : null;
|
|
6971
|
+
}
|
|
6972
|
+
function pathResourceForComponents(components, width, height) {
|
|
6973
|
+
return vectorBlockForComponents(components, false, width, height).slice(8);
|
|
6974
|
+
}
|
|
6960
6975
|
|
|
6961
6976
|
// src/image-editor/psd/channels.ts
|
|
6962
6977
|
var hexToRgb2 = (hex) => {
|
|
@@ -8986,12 +9001,14 @@ function writeBlocks(w, blocks, psb, alignment) {
|
|
|
8986
9001
|
function writeMask(w, m) {
|
|
8987
9002
|
writeRect(w, m.rect);
|
|
8988
9003
|
w.u8(m.defaultColor);
|
|
8989
|
-
const pf = (m.userDensity !== void 0 ? 1 : 0) | (m.userFeather !== void 0 ? 2 : 0);
|
|
9004
|
+
const pf = (m.userDensity !== void 0 ? 1 : 0) | (m.userFeather !== void 0 ? 2 : 0) | (m.vectorDensity !== void 0 ? 4 : 0) | (m.vectorFeather !== void 0 ? 8 : 0);
|
|
8990
9005
|
w.u8(pf ? m.flags | MASK_FLAG_PARAMETERS : m.flags & ~MASK_FLAG_PARAMETERS);
|
|
8991
9006
|
if (pf) {
|
|
8992
9007
|
w.u8(pf);
|
|
8993
9008
|
if (m.userDensity !== void 0) w.u8(m.userDensity);
|
|
8994
9009
|
if (m.userFeather !== void 0) w.f64(m.userFeather);
|
|
9010
|
+
if (m.vectorDensity !== void 0) w.u8(m.vectorDensity);
|
|
9011
|
+
if (m.vectorFeather !== void 0) w.f64(m.vectorFeather);
|
|
8995
9012
|
}
|
|
8996
9013
|
const len = 18 + (pf ? 1 + maskParameterBytes(pf) : 0);
|
|
8997
9014
|
w.zeros(Math.max(0, 20 - len) + (4 - Math.max(20, len) % 4) % 4);
|
|
@@ -10179,6 +10196,16 @@ function psdToLayers(file, options = {}) {
|
|
|
10179
10196
|
warn.extra.push(`The alpha channels could not be read (${e instanceof Error ? e.message : String(e)}).`);
|
|
10180
10197
|
}
|
|
10181
10198
|
}
|
|
10199
|
+
const paths = [];
|
|
10200
|
+
for (const r of file.resources) {
|
|
10201
|
+
const saved = r.id >= RESOURCE_PATH_FIRST && r.id <= RESOURCE_PATH_LAST;
|
|
10202
|
+
if (!saved && r.id !== RESOURCE_WORK_PATH) continue;
|
|
10203
|
+
const read2 = componentsFromPathResource(r.data, header.width, header.height);
|
|
10204
|
+
if (!read2?.components.length) continue;
|
|
10205
|
+
if (read2.inverted) warn.extra.push(`The path "${r.name || "Work Path"}" starts by subtracting, which Pixerate's paths do not keep; it came in without that.`);
|
|
10206
|
+
paths.push(saved ? { name: r.name || `Path ${paths.length + 1}`, components: read2.components } : { name: "Work Path", components: read2.components, work: true });
|
|
10207
|
+
}
|
|
10208
|
+
paths.sort((a, b) => Number(Boolean(a.work)) - Number(Boolean(b.work)));
|
|
10182
10209
|
const i32 = (b) => b && b.length >= 4 ? new DataView(b.buffer, b.byteOffset, 4).getInt32(0) : void 0;
|
|
10183
10210
|
const angle = i32(resource(file, 1037));
|
|
10184
10211
|
const altitude = i32(resource(file, 1049));
|
|
@@ -10193,6 +10220,7 @@ function psdToLayers(file, options = {}) {
|
|
|
10193
10220
|
...angle !== void 0 || altitude !== void 0 ? { globalLight: { angle: Math.max(-360, Math.min(360, angle ?? 120)), altitude: Math.max(0, Math.min(90, altitude ?? 30)) } } : {},
|
|
10194
10221
|
info: { psb: header.psb, depth: header.depth, colorMode: mode, channels: header.channels },
|
|
10195
10222
|
...alphaChannels.length ? { alphaChannels } : {},
|
|
10223
|
+
...paths.length ? { paths } : {},
|
|
10196
10224
|
...cmykProfile ? { sourceMode: { mode: "cmyk", profile: cmykProfile } } : header.colorMode === MODE_LAB ? { sourceMode: { mode: "lab" } } : {},
|
|
10197
10225
|
...resource(file, 1039) ? { iccProfile: resource(file, 1039).slice() } : {},
|
|
10198
10226
|
bitDepth: (header.depth === 16 || header.depth === 32) && header.colorMode !== MODE_INDEXED && header.colorMode !== MODE_BITMAP ? header.depth : 8
|
|
@@ -11022,6 +11050,7 @@ function exportPsd(input) {
|
|
|
11022
11050
|
};
|
|
11023
11051
|
const psb = width > PSD_MAX_DIMENSION || height > PSD_MAX_DIMENSION;
|
|
11024
11052
|
const records = [];
|
|
11053
|
+
const vectorParams = /* @__PURE__ */ new WeakMap();
|
|
11025
11054
|
let nextId = 0;
|
|
11026
11055
|
const notes = /* @__PURE__ */ new Map();
|
|
11027
11056
|
const note = (msg, name) => notes.set(msg, [...notes.get(msg) ?? [], name]);
|
|
@@ -11054,7 +11083,10 @@ function exportPsd(input) {
|
|
|
11054
11083
|
if (vm) {
|
|
11055
11084
|
const flags = (vm.linked === false ? VMSK_NOT_LINKED : 0) | (vm.enabled === false ? VMSK_DISABLED : 0);
|
|
11056
11085
|
blocks.push({ key: "vmsk", data: vectorBlockForComponents(vm.components, vm.inverted === true, width, height, flags) });
|
|
11057
|
-
|
|
11086
|
+
const params = {};
|
|
11087
|
+
if ((vm.density ?? 1) < 1) params.density = Math.round(Math.max(0, vm.density ?? 1) * 255);
|
|
11088
|
+
if ((vm.feather ?? 0) > 0) params.feather = vm.feather;
|
|
11089
|
+
if (params.density !== void 0 || params.feather !== void 0) vectorParams.set(blocks, params);
|
|
11058
11090
|
if (vm.fillRule === "evenodd") note("Vector masks are written with the nonzero fill rule (Photoshop has no even-odd paths)", layer.name);
|
|
11059
11091
|
}
|
|
11060
11092
|
return {
|
|
@@ -11263,6 +11295,15 @@ function exportPsd(input) {
|
|
|
11263
11295
|
];
|
|
11264
11296
|
if (input.iccProfile?.length) resources.push({ id: 1039, name: "", data: input.iccProfile });
|
|
11265
11297
|
resources.push(...alphaChannelResources(input.alphaChannels ?? []));
|
|
11298
|
+
let savedPathId = RESOURCE_PATH_FIRST;
|
|
11299
|
+
for (const p of input.paths ?? []) {
|
|
11300
|
+
if (!p.work && savedPathId > RESOURCE_PATH_LAST) {
|
|
11301
|
+
note("Only 998 saved paths fit in a PSD file; the rest are not written", p.name);
|
|
11302
|
+
continue;
|
|
11303
|
+
}
|
|
11304
|
+
if (p.fillRule === "evenodd") note("Paths are written with the nonzero fill rule (Photoshop has no even-odd paths)", p.name);
|
|
11305
|
+
resources.push({ id: p.work ? RESOURCE_WORK_PATH : savedPathId++, name: p.work ? "" : p.name, data: pathResourceForComponents(p.components, width, height) });
|
|
11306
|
+
}
|
|
11266
11307
|
if (input.globalLight) {
|
|
11267
11308
|
const i32 = (v) => {
|
|
11268
11309
|
const w = new PsdWriter();
|
|
@@ -11293,6 +11334,13 @@ function exportPsd(input) {
|
|
|
11293
11334
|
globalBlocks.push({ key: "lnk2", data });
|
|
11294
11335
|
}
|
|
11295
11336
|
if (filterItems.length) globalBlocks.push({ key: "FEid", data: writeFilterEffects(filterItems) });
|
|
11337
|
+
for (const rec of records) {
|
|
11338
|
+
const params = vectorParams.get(rec.blocks);
|
|
11339
|
+
if (!params) continue;
|
|
11340
|
+
rec.mask ??= { rect: EMPTY_RECT, defaultColor: 255, flags: 0 };
|
|
11341
|
+
if (params.density !== void 0) rec.mask.vectorDensity = params.density;
|
|
11342
|
+
if (params.feather !== void 0) rec.mask.vectorFeather = params.feather;
|
|
11343
|
+
}
|
|
11296
11344
|
const bytes = writePsd(
|
|
11297
11345
|
{
|
|
11298
11346
|
header: { psb, channels: planes.length, width, height, depth, colorMode: mode === "cmyk" ? 4 : mode === "lab" ? 9 : 3 },
|
|
@@ -11622,6 +11670,7 @@ export {
|
|
|
11622
11670
|
decodeDeepSurface,
|
|
11623
11671
|
serializeEffects,
|
|
11624
11672
|
deserializeEffects,
|
|
11673
|
+
readShapeComponents,
|
|
11625
11674
|
serializeLayer,
|
|
11626
11675
|
serializeContents,
|
|
11627
11676
|
serializeLayers,
|