@pixerate/editor 0.99.0 → 0.100.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.
|
@@ -3198,22 +3198,44 @@ var ImageEditorRenderer = class {
|
|
|
3198
3198
|
ctx.save();
|
|
3199
3199
|
try {
|
|
3200
3200
|
const scr = (p) => projectImagePoint(p, projection);
|
|
3201
|
-
const outline = t.outline.map(scr);
|
|
3202
3201
|
ctx.beginPath();
|
|
3203
|
-
|
|
3204
|
-
|
|
3202
|
+
if (t.warp) {
|
|
3203
|
+
for (const c of t.warp.curves) {
|
|
3204
|
+
const [a, b, d, e] = c.map(scr);
|
|
3205
|
+
ctx.moveTo(a.x, a.y);
|
|
3206
|
+
ctx.bezierCurveTo(b.x, b.y, d.x, d.y, e.x, e.y);
|
|
3207
|
+
}
|
|
3208
|
+
for (const h of t.warp.handles) {
|
|
3209
|
+
const a = scr(h.anchor);
|
|
3210
|
+
const q2 = scr(h.point);
|
|
3211
|
+
ctx.moveTo(a.x, a.y);
|
|
3212
|
+
ctx.lineTo(q2.x, q2.y);
|
|
3213
|
+
}
|
|
3214
|
+
} else {
|
|
3215
|
+
const outline = t.outline.map(scr);
|
|
3216
|
+
outline.forEach((p, i) => i ? ctx.lineTo(p.x, p.y) : ctx.moveTo(p.x, p.y));
|
|
3217
|
+
ctx.closePath();
|
|
3218
|
+
}
|
|
3205
3219
|
ctx.lineWidth = 3;
|
|
3206
3220
|
ctx.strokeStyle = "rgba(0,0,0,0.5)";
|
|
3207
3221
|
ctx.stroke();
|
|
3208
3222
|
ctx.lineWidth = 1;
|
|
3209
3223
|
ctx.strokeStyle = "#ffffff";
|
|
3210
3224
|
ctx.stroke();
|
|
3211
|
-
for (const h of t.handles.map(scr)) {
|
|
3225
|
+
for (const h of [...t.handles, ...t.warp?.anchors ?? []].map(scr)) {
|
|
3212
3226
|
ctx.fillStyle = "#ffffff";
|
|
3213
3227
|
ctx.strokeStyle = "#1e293b";
|
|
3214
3228
|
ctx.fillRect(h.x - 4, h.y - 4, 8, 8);
|
|
3215
3229
|
ctx.strokeRect(h.x - 4, h.y - 4, 8, 8);
|
|
3216
3230
|
}
|
|
3231
|
+
for (const h of (t.warp?.handles ?? []).map((w) => scr(w.point))) {
|
|
3232
|
+
ctx.beginPath();
|
|
3233
|
+
ctx.arc(h.x, h.y, 3, 0, Math.PI * 2);
|
|
3234
|
+
ctx.fillStyle = "#ffffff";
|
|
3235
|
+
ctx.fill();
|
|
3236
|
+
ctx.strokeStyle = "#1e293b";
|
|
3237
|
+
ctx.stroke();
|
|
3238
|
+
}
|
|
3217
3239
|
const r = scr(t.reference);
|
|
3218
3240
|
ctx.beginPath();
|
|
3219
3241
|
ctx.arc(r.x, r.y, 5, 0, Math.PI * 2);
|
|
@@ -15117,7 +15139,17 @@ var FREE_TRANSFORM_MODE_OPTIONS = [
|
|
|
15117
15139
|
{ id: "rotate", label: "Rotate", title: "Every handle rotates about the reference point" },
|
|
15118
15140
|
{ id: "skew", label: "Skew", title: "Sides slide along themselves" },
|
|
15119
15141
|
{ id: "distort", label: "Distort", title: "Each corner moves freely" },
|
|
15120
|
-
{ id: "perspective", label: "Perspective", title: "A corner and its partner move symmetrically" }
|
|
15142
|
+
{ id: "perspective", label: "Perspective", title: "A corner and its partner move symmetrically" },
|
|
15143
|
+
{ id: "warp", label: "Warp", title: "Bend through a mesh: drag its points or pull inside; presets in the bar" }
|
|
15144
|
+
];
|
|
15145
|
+
var WARP_STYLE_OPTIONS = [
|
|
15146
|
+
{ id: "custom", label: "Custom" },
|
|
15147
|
+
...WARP_STYLES.map((id) => ({ id, label: WARP_STYLE_LABELS[id] }))
|
|
15148
|
+
];
|
|
15149
|
+
var WARP_GRID_OPTIONS = [
|
|
15150
|
+
{ id: "1x1", label: "Default", columns: 1, rows: 1 },
|
|
15151
|
+
{ id: "3x3", label: "3 \xD7 3", columns: 3, rows: 3 },
|
|
15152
|
+
{ id: "5x5", label: "5 \xD7 5", columns: 5, rows: 5 }
|
|
15121
15153
|
];
|
|
15122
15154
|
var FREE_TRANSFORM_FIELDS = [
|
|
15123
15155
|
{ id: "x", label: "X", unit: "px", title: "Reference point X (document pixels)" },
|
|
@@ -15141,6 +15173,7 @@ var CURSOR = {
|
|
|
15141
15173
|
inside: "move",
|
|
15142
15174
|
outside: "alias"
|
|
15143
15175
|
};
|
|
15176
|
+
var meshIndex = (h) => h && h.startsWith("mesh:") ? Number(h.slice(5)) : -1;
|
|
15144
15177
|
var mod = (m) => Boolean(m.ctrlKey || m.metaKey);
|
|
15145
15178
|
var add = (a, b) => ({ x: a.x + b.x, y: a.y + b.y });
|
|
15146
15179
|
var sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y });
|
|
@@ -15163,6 +15196,15 @@ function insideQuad(q2, p) {
|
|
|
15163
15196
|
}
|
|
15164
15197
|
return true;
|
|
15165
15198
|
}
|
|
15199
|
+
function insidePolygon(poly, p) {
|
|
15200
|
+
let inside = false;
|
|
15201
|
+
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
15202
|
+
const a = poly[i];
|
|
15203
|
+
const b = poly[j];
|
|
15204
|
+
if (a.y > p.y !== b.y > p.y && p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x) inside = !inside;
|
|
15205
|
+
}
|
|
15206
|
+
return inside;
|
|
15207
|
+
}
|
|
15166
15208
|
var FreeTransformTool = class {
|
|
15167
15209
|
constructor(controller, options = {}) {
|
|
15168
15210
|
this.controller = controller;
|
|
@@ -15176,6 +15218,9 @@ var FreeTransformTool = class {
|
|
|
15176
15218
|
refBox = { x: 0, y: 0 };
|
|
15177
15219
|
corners = null;
|
|
15178
15220
|
drag = null;
|
|
15221
|
+
/** The warp mesh in the frame's own space (null: no warp). */
|
|
15222
|
+
mesh = null;
|
|
15223
|
+
warpSettings = { style: "custom", bend: 50, horizontal: 0, vertical: 0, orientation: "horizontal", columns: 1, rows: 1 };
|
|
15179
15224
|
pending = false;
|
|
15180
15225
|
viewScale = 1;
|
|
15181
15226
|
viewClip;
|
|
@@ -15222,6 +15267,8 @@ var FreeTransformTool = class {
|
|
|
15222
15267
|
this.corners = s.geometry.corners;
|
|
15223
15268
|
this.refBox = { x: s.box.x + s.box.width / 2, y: s.box.y + s.box.height / 2 };
|
|
15224
15269
|
this.modeValue = "scale";
|
|
15270
|
+
this.mesh = null;
|
|
15271
|
+
this.warpSettings = { style: "custom", bend: 50, horizontal: 0, vertical: 0, orientation: "horizontal", columns: 1, rows: 1 };
|
|
15225
15272
|
this.typed = {};
|
|
15226
15273
|
this.error = null;
|
|
15227
15274
|
} catch (e) {
|
|
@@ -15232,7 +15279,39 @@ var FreeTransformTool = class {
|
|
|
15232
15279
|
}
|
|
15233
15280
|
setMode(mode) {
|
|
15234
15281
|
this.modeValue = mode;
|
|
15282
|
+
const s = this.session;
|
|
15283
|
+
if (mode === "warp" && s && !this.mesh) this.mesh = identityWarpMesh(s.box, this.warpSettings.columns, this.warpSettings.rows);
|
|
15284
|
+
this.changed();
|
|
15285
|
+
}
|
|
15286
|
+
/** The Warp options bar's settings. */
|
|
15287
|
+
warpBar() {
|
|
15288
|
+
return { ...this.warpSettings };
|
|
15289
|
+
}
|
|
15290
|
+
/** The warp mesh (frame space), or null. */
|
|
15291
|
+
get warp() {
|
|
15292
|
+
return this.mesh;
|
|
15293
|
+
}
|
|
15294
|
+
/**
|
|
15295
|
+
* Applies Warp options: a preset `style` with `bend` / `horizontal` / `vertical` (percent) and
|
|
15296
|
+
* `orientation` replaces the mesh; `custom` keeps it; the grid size re-splits it (exactly when it
|
|
15297
|
+
* can). Switches to Warp mode.
|
|
15298
|
+
*/
|
|
15299
|
+
setWarp(changes) {
|
|
15300
|
+
const s = this.session;
|
|
15301
|
+
if (!s) return;
|
|
15302
|
+
this.modeValue = "warp";
|
|
15303
|
+
const next = { ...this.warpSettings, ...changes };
|
|
15304
|
+
this.warpSettings = next;
|
|
15305
|
+
let mesh = this.mesh ?? identityWarpMesh(s.box, next.columns, next.rows);
|
|
15306
|
+
if (next.style !== "custom" && ("style" in changes || "bend" in changes || "horizontal" in changes || "vertical" in changes || "orientation" in changes)) {
|
|
15307
|
+
const preset = { style: next.style, bend: next.bend, horizontal: next.horizontal, vertical: next.vertical, orientation: next.orientation };
|
|
15308
|
+
mesh = warpPresetMesh(preset, s.box) ?? identityWarpMesh(s.box);
|
|
15309
|
+
mesh = resizeWarpMesh(mesh, next.columns, next.rows);
|
|
15310
|
+
}
|
|
15311
|
+
if ("columns" in changes || "rows" in changes) mesh = resizeWarpMesh(mesh, next.columns, next.rows);
|
|
15312
|
+
this.mesh = mesh;
|
|
15235
15313
|
this.changed();
|
|
15314
|
+
this.preview();
|
|
15236
15315
|
}
|
|
15237
15316
|
/** Sets the reference point to an anchor of the frame (the options bar's locator). */
|
|
15238
15317
|
setReference(anchor) {
|
|
@@ -15271,12 +15350,54 @@ var FreeTransformTool = class {
|
|
|
15271
15350
|
const p = applyHomography(h, { x: s.box.x + fx * s.box.width, y: s.box.y + fy * s.box.height }) ?? { x: 0, y: 0 };
|
|
15272
15351
|
return { id, x: p.x, y: p.y };
|
|
15273
15352
|
});
|
|
15274
|
-
|
|
15353
|
+
const out = { outline: this.corners.map((p) => ({ x: p.x, y: p.y })), handles, reference: this.referencePoint() ?? { x: 0, y: 0 } };
|
|
15354
|
+
if (this.modeValue === "warp" && this.mesh) {
|
|
15355
|
+
const m = this.mesh;
|
|
15356
|
+
const at = (p) => applyHomography(h, p) ?? p;
|
|
15357
|
+
const pts = m.points.map(at);
|
|
15358
|
+
const edit = warpMeshEditPoints(m);
|
|
15359
|
+
out.handles = [];
|
|
15360
|
+
out.outline = warpMeshGridCurves({ ...m, points: pts }).length ? this.meshOutline(pts, m) : out.outline;
|
|
15361
|
+
out.warp = {
|
|
15362
|
+
curves: warpMeshGridCurves({ ...m, points: pts }),
|
|
15363
|
+
anchors: edit.anchors.map((i) => pts[i]),
|
|
15364
|
+
handles: edit.handles.map((e) => ({ point: pts[e.index], anchor: pts[e.anchor] }))
|
|
15365
|
+
};
|
|
15366
|
+
}
|
|
15367
|
+
return out;
|
|
15368
|
+
}
|
|
15369
|
+
/** The mesh's border anchors, clockwise (for the inside test). */
|
|
15370
|
+
meshOutline(pts, m) {
|
|
15371
|
+
const nx = 3 * (m.us.length - 1) + 1;
|
|
15372
|
+
const ny = 3 * (m.vs.length - 1) + 1;
|
|
15373
|
+
const ring = [];
|
|
15374
|
+
for (let i = 0; i < nx; i++) ring.push(pts[i]);
|
|
15375
|
+
for (let j = 1; j < ny; j++) ring.push(pts[j * nx + nx - 1]);
|
|
15376
|
+
for (let i = nx - 2; i >= 0; i--) ring.push(pts[(ny - 1) * nx + i]);
|
|
15377
|
+
for (let j = ny - 2; j > 0; j--) ring.push(pts[j * nx]);
|
|
15378
|
+
return ring;
|
|
15275
15379
|
}
|
|
15276
15380
|
/** What a document point is over (`tolerance` in document pixels: a few screen pixels / scale). */
|
|
15277
15381
|
hit(p, tolerance) {
|
|
15278
15382
|
const o = this.overlay();
|
|
15279
15383
|
if (!o) return null;
|
|
15384
|
+
if (this.modeValue === "warp" && this.mesh) {
|
|
15385
|
+
const h = this.homography();
|
|
15386
|
+
const edit = warpMeshEditPoints(this.mesh);
|
|
15387
|
+
let best2 = -1;
|
|
15388
|
+
let bestD2 = tolerance;
|
|
15389
|
+
for (const i of [...edit.anchors, ...edit.handles.map((e) => e.index)]) {
|
|
15390
|
+
const q2 = applyHomography(h, this.mesh.points[i]);
|
|
15391
|
+
if (!q2) continue;
|
|
15392
|
+
const d = Math.hypot(q2.x - p.x, q2.y - p.y);
|
|
15393
|
+
if (d <= bestD2) {
|
|
15394
|
+
best2 = i;
|
|
15395
|
+
bestD2 = d;
|
|
15396
|
+
}
|
|
15397
|
+
}
|
|
15398
|
+
if (best2 >= 0) return `mesh:${best2}`;
|
|
15399
|
+
return insidePolygon(o.outline, p) ? "inside" : "outside";
|
|
15400
|
+
}
|
|
15280
15401
|
let best = null;
|
|
15281
15402
|
let bestD = tolerance;
|
|
15282
15403
|
for (const h of o.handles) {
|
|
@@ -15293,6 +15414,8 @@ var FreeTransformTool = class {
|
|
|
15293
15414
|
/** The CSS cursor for a handle. */
|
|
15294
15415
|
cursor(handle) {
|
|
15295
15416
|
if (!handle) return "default";
|
|
15417
|
+
if (meshIndex(handle) >= 0) return "pointer";
|
|
15418
|
+
if (this.modeValue === "warp") return handle === "inside" ? "grab" : "default";
|
|
15296
15419
|
if (this.modeValue === "rotate" && handle !== "inside" && handle !== "reference") return CURSOR.outside;
|
|
15297
15420
|
return CURSOR[handle];
|
|
15298
15421
|
}
|
|
@@ -15301,9 +15424,24 @@ var FreeTransformTool = class {
|
|
|
15301
15424
|
if (!this.session || !this.corners) return null;
|
|
15302
15425
|
const handle = this.hit(p, tolerance);
|
|
15303
15426
|
if (!handle) return null;
|
|
15304
|
-
|
|
15427
|
+
let st = null;
|
|
15428
|
+
if (this.modeValue === "warp" && this.mesh && handle === "inside") {
|
|
15429
|
+
const q2 = this.toFrame(p, this.corners);
|
|
15430
|
+
if (q2) {
|
|
15431
|
+
const r = warpMeshParamAt(this.mesh, q2);
|
|
15432
|
+
st = { s: r.s, t: r.t };
|
|
15433
|
+
}
|
|
15434
|
+
}
|
|
15435
|
+
this.drag = { handle, start: { x: p.x, y: p.y }, corners: this.corners, refBox: { ...this.refBox }, ref: this.referencePoint() ?? p, mesh: this.mesh, st };
|
|
15305
15436
|
return handle;
|
|
15306
15437
|
}
|
|
15438
|
+
/** A document point in the frame's own space under `corners`. */
|
|
15439
|
+
toFrame(p, corners) {
|
|
15440
|
+
const s = this.session;
|
|
15441
|
+
const h = s ? rectToQuad(s.box, corners) : null;
|
|
15442
|
+
const inv = h && invertHomography(h);
|
|
15443
|
+
return inv ? applyHomography(inv, p) : null;
|
|
15444
|
+
}
|
|
15307
15445
|
/** Continues the drag to `p` with the modifiers held now. */
|
|
15308
15446
|
move(p, m = {}) {
|
|
15309
15447
|
const d = this.drag;
|
|
@@ -15317,6 +15455,20 @@ var FreeTransformTool = class {
|
|
|
15317
15455
|
this.changed();
|
|
15318
15456
|
return;
|
|
15319
15457
|
}
|
|
15458
|
+
if (this.modeValue === "warp" && d.mesh) {
|
|
15459
|
+
const a = this.toFrame(d.start, d.corners);
|
|
15460
|
+
const b = this.toFrame(p, d.corners);
|
|
15461
|
+
if (!a || !b) return;
|
|
15462
|
+
const delta = { x: b.x - a.x, y: b.y - a.y };
|
|
15463
|
+
const i = meshIndex(d.handle);
|
|
15464
|
+
if (i >= 0) this.mesh = dragWarpMeshPoint(d.mesh, i, delta);
|
|
15465
|
+
else if (d.handle === "inside" && d.st) this.mesh = pullWarpMesh(d.mesh, d.st.s, d.st.t, delta);
|
|
15466
|
+
else return;
|
|
15467
|
+
this.warpSettings = { ...this.warpSettings, style: "custom" };
|
|
15468
|
+
this.changed();
|
|
15469
|
+
this.preview();
|
|
15470
|
+
return;
|
|
15471
|
+
}
|
|
15320
15472
|
const corners = this.dragCorners(d, p, m);
|
|
15321
15473
|
if (corners) this.setCorners(corners);
|
|
15322
15474
|
}
|
|
@@ -15415,7 +15567,7 @@ var FreeTransformTool = class {
|
|
|
15415
15567
|
this.pending = false;
|
|
15416
15568
|
const s = this.session;
|
|
15417
15569
|
if (!s || !this.corners) return;
|
|
15418
|
-
this.run(() => s.update({ corners: this.corners }, { scale: this.viewScale, clip: this.viewClip }));
|
|
15570
|
+
this.run(() => s.update({ corners: this.corners, warp: this.mesh }, { scale: this.viewScale, clip: this.viewClip }));
|
|
15419
15571
|
};
|
|
15420
15572
|
if (this.options.schedule) this.options.schedule(run);
|
|
15421
15573
|
else run();
|
|
@@ -15480,12 +15632,13 @@ var FreeTransformTool = class {
|
|
|
15480
15632
|
let res = null;
|
|
15481
15633
|
this.pending = false;
|
|
15482
15634
|
this.run(() => {
|
|
15483
|
-
if (this.corners) s.update({ corners: this.corners }, false);
|
|
15635
|
+
if (this.corners) s.update({ corners: this.corners, warp: this.mesh }, false);
|
|
15484
15636
|
res = s.commit();
|
|
15485
15637
|
});
|
|
15486
15638
|
this.lastResult = res;
|
|
15487
15639
|
this.sessionRef = null;
|
|
15488
15640
|
this.corners = null;
|
|
15641
|
+
this.mesh = null;
|
|
15489
15642
|
this.drag = null;
|
|
15490
15643
|
this.changed();
|
|
15491
15644
|
return res;
|
|
@@ -15495,6 +15648,7 @@ var FreeTransformTool = class {
|
|
|
15495
15648
|
this.sessionRef?.cancel();
|
|
15496
15649
|
this.sessionRef = null;
|
|
15497
15650
|
this.corners = null;
|
|
15651
|
+
this.mesh = null;
|
|
15498
15652
|
this.drag = null;
|
|
15499
15653
|
this.pending = false;
|
|
15500
15654
|
this.error = null;
|
|
@@ -15629,8 +15783,10 @@ export {
|
|
|
15629
15783
|
TiledSurface,
|
|
15630
15784
|
TransformTooLargeError,
|
|
15631
15785
|
VIBRANCE_GREY,
|
|
15786
|
+
WARP_GRID_OPTIONS,
|
|
15632
15787
|
WARP_STYLES,
|
|
15633
15788
|
WARP_STYLE_LABELS,
|
|
15789
|
+
WARP_STYLE_OPTIONS,
|
|
15634
15790
|
XYZ_D50_TO_SRGB,
|
|
15635
15791
|
ZlibError,
|
|
15636
15792
|
activeAdjustmentLayers,
|