@pixerate/editor 0.96.0 → 0.97.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-T422YCHR.cjs → chunk-4AB5BHYK.cjs} +237 -63
- package/dist/chunk-4AB5BHYK.cjs.map +1 -0
- package/dist/{chunk-KFTQ2PKP.js → chunk-OEVSVA6V.js} +214 -40
- package/dist/chunk-OEVSVA6V.js.map +1 -0
- package/dist/image-editor/index.cjs +413 -235
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +75 -1
- package/dist/image-editor/index.d.ts +75 -1
- package/dist/image-editor/index.js +254 -76
- package/dist/image-editor/index.js.map +1 -1
- package/dist/raster/index.cjs +4 -2
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +46 -3
- package/dist/raster/index.d.ts +46 -3
- package/dist/raster/index.js +3 -1
- package/package.json +1 -1
- package/dist/chunk-KFTQ2PKP.js.map +0 -1
- package/dist/chunk-T422YCHR.cjs.map +0 -1
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
validateEffectGradient,
|
|
59
59
|
validateLayerEffect,
|
|
60
60
|
validateLayerEffects
|
|
61
|
-
} from "../chunk-
|
|
61
|
+
} from "../chunk-OEVSVA6V.js";
|
|
62
62
|
import {
|
|
63
63
|
CoverageMap,
|
|
64
64
|
DEFAULT_BRUSH,
|
|
@@ -557,20 +557,20 @@ function hitTestCrop(canvasPt, state, vpWidth, vpHeight, handleThreshold = 14, c
|
|
|
557
557
|
}
|
|
558
558
|
function hitTestCropRect(canvasPt, rect, handleThreshold) {
|
|
559
559
|
const { x, y, width: w, height: h } = rect;
|
|
560
|
-
const
|
|
560
|
+
const px2 = canvasPt.x;
|
|
561
561
|
const py = canvasPt.y;
|
|
562
562
|
const dist = (x1, y1, x2, y2) => Math.hypot(x1 - x2, y1 - y2);
|
|
563
|
-
if (dist(
|
|
564
|
-
if (dist(
|
|
565
|
-
if (dist(
|
|
566
|
-
if (dist(
|
|
567
|
-
const nearX =
|
|
563
|
+
if (dist(px2, py, x, y) <= handleThreshold) return "nw";
|
|
564
|
+
if (dist(px2, py, x + w, y) <= handleThreshold) return "ne";
|
|
565
|
+
if (dist(px2, py, x + w, y + h) <= handleThreshold) return "se";
|
|
566
|
+
if (dist(px2, py, x, y + h) <= handleThreshold) return "sw";
|
|
567
|
+
const nearX = px2 >= x - handleThreshold && px2 <= x + w + handleThreshold;
|
|
568
568
|
const nearY = py >= y - handleThreshold && py <= y + h + handleThreshold;
|
|
569
569
|
if (nearX && Math.abs(py - y) <= handleThreshold) return "n";
|
|
570
570
|
if (nearX && Math.abs(py - (y + h)) <= handleThreshold) return "s";
|
|
571
|
-
if (nearY && Math.abs(
|
|
572
|
-
if (nearY && Math.abs(
|
|
573
|
-
if (
|
|
571
|
+
if (nearY && Math.abs(px2 - x) <= handleThreshold) return "w";
|
|
572
|
+
if (nearY && Math.abs(px2 - (x + w)) <= handleThreshold) return "e";
|
|
573
|
+
if (px2 >= x && px2 <= x + w && py >= y && py <= y + h) {
|
|
574
574
|
return "inside";
|
|
575
575
|
}
|
|
576
576
|
return null;
|
|
@@ -1598,13 +1598,13 @@ function hslToRgb(h, s, l) {
|
|
|
1598
1598
|
};
|
|
1599
1599
|
return [f(h + 1 / 3), f(h), f(h - 1 / 3)];
|
|
1600
1600
|
}
|
|
1601
|
-
function hueRangeWeight(range,
|
|
1601
|
+
function hueRangeWeight(range, deg2) {
|
|
1602
1602
|
const [a, b0, c0, d0] = range.bounds;
|
|
1603
1603
|
const rel = (x) => Number.isFinite(x) && Number.isFinite(a) ? remEuclid(x - a, 360) : 0;
|
|
1604
1604
|
const b = rel(b0);
|
|
1605
1605
|
const c = Math.max(rel(c0), b);
|
|
1606
1606
|
const d = Math.max(rel(d0), c);
|
|
1607
|
-
const h = rel(
|
|
1607
|
+
const h = rel(deg2);
|
|
1608
1608
|
if (h <= b) return b <= 0 ? 1 : h / b;
|
|
1609
1609
|
if (h <= c) return 1;
|
|
1610
1610
|
if (h <= d) return d <= c ? 1 : (d - h) / (d - c);
|
|
@@ -1616,9 +1616,9 @@ function hueRangeTables(ranges) {
|
|
|
1616
1616
|
const t = [new Float32Array(n), new Float32Array(n), new Float32Array(n)];
|
|
1617
1617
|
const active = ranges.filter((r) => !isNeutralRange(r));
|
|
1618
1618
|
for (let k = 0; k < n; k++) {
|
|
1619
|
-
const
|
|
1619
|
+
const deg2 = k / (n - 1) * 360;
|
|
1620
1620
|
for (const r of active) {
|
|
1621
|
-
const w = hueRangeWeight(r,
|
|
1621
|
+
const w = hueRangeWeight(r, deg2);
|
|
1622
1622
|
t[0][k] += w * r.hue;
|
|
1623
1623
|
t[1][k] += w * r.saturation / 100;
|
|
1624
1624
|
t[2][k] += w * r.lightness / 100;
|
|
@@ -2181,8 +2181,8 @@ function applyFastBoxBlur(imageData, radius) {
|
|
|
2181
2181
|
let r = 0, g = 0, b = 0, a = 0;
|
|
2182
2182
|
let count = 0;
|
|
2183
2183
|
for (let k = -radius; k <= radius; k++) {
|
|
2184
|
-
const
|
|
2185
|
-
const idx = (y * w +
|
|
2184
|
+
const px2 = Math.min(w - 1, Math.max(0, x + k));
|
|
2185
|
+
const idx = (y * w + px2) * 4;
|
|
2186
2186
|
r += src[idx];
|
|
2187
2187
|
g += src[idx + 1];
|
|
2188
2188
|
b += src[idx + 2];
|
|
@@ -3137,13 +3137,13 @@ var ImageEditorRenderer = class {
|
|
|
3137
3137
|
ctx.save();
|
|
3138
3138
|
try {
|
|
3139
3139
|
applyImageProjectionToContext(ctx, projection);
|
|
3140
|
-
const
|
|
3140
|
+
const px2 = 1 / (projection.scale || 1);
|
|
3141
3141
|
ctx.beginPath();
|
|
3142
|
-
ctx.arc(b.x, b.y, Math.max(
|
|
3143
|
-
ctx.lineWidth = 2 *
|
|
3142
|
+
ctx.arc(b.x, b.y, Math.max(px2, b.diameter / 2), 0, Math.PI * 2);
|
|
3143
|
+
ctx.lineWidth = 2 * px2;
|
|
3144
3144
|
ctx.strokeStyle = "rgba(0,0,0,0.6)";
|
|
3145
3145
|
ctx.stroke();
|
|
3146
|
-
ctx.lineWidth =
|
|
3146
|
+
ctx.lineWidth = px2;
|
|
3147
3147
|
ctx.strokeStyle = "#ffffff";
|
|
3148
3148
|
ctx.stroke();
|
|
3149
3149
|
} catch {
|
|
@@ -3426,32 +3426,32 @@ var ImageEditorRenderer = class {
|
|
|
3426
3426
|
this.outlineCache.set(selection, segs);
|
|
3427
3427
|
}
|
|
3428
3428
|
if (!segs.length) return;
|
|
3429
|
-
const
|
|
3429
|
+
const px2 = 1 / (scale || 1);
|
|
3430
3430
|
ctx.beginPath();
|
|
3431
3431
|
for (let i = 0; i < segs.length; i += 4) {
|
|
3432
3432
|
ctx.moveTo(segs[i], segs[i + 1]);
|
|
3433
3433
|
ctx.lineTo(segs[i + 2], segs[i + 3]);
|
|
3434
3434
|
}
|
|
3435
|
-
ctx.lineWidth =
|
|
3435
|
+
ctx.lineWidth = px2;
|
|
3436
3436
|
ctx.strokeStyle = "#ffffff";
|
|
3437
3437
|
ctx.stroke();
|
|
3438
3438
|
if (typeof ctx.setLineDash === "function") {
|
|
3439
|
-
ctx.setLineDash([4 *
|
|
3440
|
-
ctx.lineDashOffset = -offset *
|
|
3439
|
+
ctx.setLineDash([4 * px2, 4 * px2]);
|
|
3440
|
+
ctx.lineDashOffset = -offset * px2;
|
|
3441
3441
|
}
|
|
3442
3442
|
ctx.strokeStyle = "#000000";
|
|
3443
3443
|
ctx.stroke();
|
|
3444
3444
|
if (typeof ctx.setLineDash === "function") ctx.setLineDash([]);
|
|
3445
3445
|
}
|
|
3446
3446
|
drawDraftPath(ctx, points, scale) {
|
|
3447
|
-
const
|
|
3447
|
+
const px2 = 1 / (scale || 1);
|
|
3448
3448
|
ctx.beginPath();
|
|
3449
3449
|
points.forEach((p, i) => i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y));
|
|
3450
3450
|
ctx.closePath();
|
|
3451
|
-
ctx.lineWidth =
|
|
3451
|
+
ctx.lineWidth = px2;
|
|
3452
3452
|
ctx.strokeStyle = "#ffffff";
|
|
3453
3453
|
ctx.stroke();
|
|
3454
|
-
if (typeof ctx.setLineDash === "function") ctx.setLineDash([4 *
|
|
3454
|
+
if (typeof ctx.setLineDash === "function") ctx.setLineDash([4 * px2, 4 * px2]);
|
|
3455
3455
|
ctx.strokeStyle = "#000000";
|
|
3456
3456
|
ctx.stroke();
|
|
3457
3457
|
if (typeof ctx.setLineDash === "function") ctx.setLineDash([]);
|
|
@@ -4297,8 +4297,8 @@ var gfRadius = (p) => Math.min(64, Math.max(1, Math.ceil(p.radius)));
|
|
|
4297
4297
|
var smoothSigma = (p) => p.smooth / 100 * 4;
|
|
4298
4298
|
var featherSigma = (p) => p.feather / 2;
|
|
4299
4299
|
function shiftAmount(p) {
|
|
4300
|
-
const
|
|
4301
|
-
return Number.isFinite(
|
|
4300
|
+
const px2 = p.shiftEdge / 100 * Math.max(p.radius, 4);
|
|
4301
|
+
return Number.isFinite(px2) ? Math.min(64, Math.max(-64, px2)) : 0;
|
|
4302
4302
|
}
|
|
4303
4303
|
function refineEdgeHalo(params = {}) {
|
|
4304
4304
|
const p = normalizeRefineEdgeParams(params);
|
|
@@ -4702,8 +4702,8 @@ function readSurface(ref, x, y, channels, defaultValue, ctx, what) {
|
|
|
4702
4702
|
return emptySurface(channels, defaultValue);
|
|
4703
4703
|
}
|
|
4704
4704
|
try {
|
|
4705
|
-
const
|
|
4706
|
-
return surfaceFromPixels(
|
|
4705
|
+
const px2 = (ctx.decodeAsset ?? decodePngDataUrl)(url, channels);
|
|
4706
|
+
return surfaceFromPixels(px2, channels, Math.round(num(x, -1e7, 1e7, 0)), Math.round(num(y, -1e7, 1e7, 0)), defaultValue);
|
|
4707
4707
|
} catch (err) {
|
|
4708
4708
|
ctx.problems.push(`${what}: asset "${ref}" could not be decoded (${err.message})`);
|
|
4709
4709
|
return emptySurface(channels, defaultValue);
|
|
@@ -8763,17 +8763,17 @@ var ImageEditorController = class {
|
|
|
8763
8763
|
*/
|
|
8764
8764
|
sourceSurface() {
|
|
8765
8765
|
const { width, height } = this.naturalSize;
|
|
8766
|
-
const fromPixels = (
|
|
8767
|
-
let s = this.sourceSurfaces.get(
|
|
8766
|
+
const fromPixels = (px3) => {
|
|
8767
|
+
let s = this.sourceSurfaces.get(px3);
|
|
8768
8768
|
if (!s) {
|
|
8769
|
-
s = surfaceFromPixels(
|
|
8770
|
-
this.sourceSurfaces.set(
|
|
8769
|
+
s = surfaceFromPixels(px3, 4);
|
|
8770
|
+
this.sourceSurfaces.set(px3, s);
|
|
8771
8771
|
}
|
|
8772
8772
|
return s;
|
|
8773
8773
|
};
|
|
8774
|
-
const
|
|
8775
|
-
const fits = Boolean(
|
|
8776
|
-
if (this.samplePixelsOverride && fits) return fromPixels(
|
|
8774
|
+
const px2 = this.samplePixels;
|
|
8775
|
+
const fits = Boolean(px2 && px2.width === width && px2.height === height);
|
|
8776
|
+
if (this.samplePixelsOverride && fits) return fromPixels(px2);
|
|
8777
8777
|
const el = this.imageElement;
|
|
8778
8778
|
if (el) {
|
|
8779
8779
|
const cached = this.sourceSurfaces.get(el);
|
|
@@ -8787,7 +8787,7 @@ var ImageEditorController = class {
|
|
|
8787
8787
|
return s;
|
|
8788
8788
|
}
|
|
8789
8789
|
}
|
|
8790
|
-
return fits ? fromPixels(
|
|
8790
|
+
return fits ? fromPixels(px2) : null;
|
|
8791
8791
|
}
|
|
8792
8792
|
opaqueCache = /* @__PURE__ */ new WeakMap();
|
|
8793
8793
|
/** Whether every source pixel is opaque (some legacy renders depend on it). */
|
|
@@ -8846,11 +8846,11 @@ var ImageEditorController = class {
|
|
|
8846
8846
|
const { width, height } = this.naturalSize;
|
|
8847
8847
|
let surface = emptySurface(4);
|
|
8848
8848
|
if (init.pixels) {
|
|
8849
|
-
const
|
|
8850
|
-
if (!(
|
|
8849
|
+
const px2 = init.pixels;
|
|
8850
|
+
if (!(px2.width > 0 && px2.height > 0) || px2.data.length < px2.width * px2.height * 4) {
|
|
8851
8851
|
throw new LayerOperationError("pixels must be RGBA with width \xD7 height \xD7 4 bytes.");
|
|
8852
8852
|
}
|
|
8853
|
-
surface = surfaceFromPixels(
|
|
8853
|
+
surface = surfaceFromPixels(px2, 4);
|
|
8854
8854
|
} else if (init.color !== void 0) {
|
|
8855
8855
|
const c = parseColor(init.color);
|
|
8856
8856
|
surface = solidSurface(4, { x: 0, y: 0, width, height }, packRgba(c[0], c[1], c[2], c[3]));
|
|
@@ -9098,14 +9098,14 @@ var ImageEditorController = class {
|
|
|
9098
9098
|
const surface = this.surfaceOf(layer);
|
|
9099
9099
|
const sb = sel.bounds;
|
|
9100
9100
|
const src = { x: sb.x - layer.offset.x, y: sb.y - layer.offset.y, width: sb.width, height: sb.height };
|
|
9101
|
-
const
|
|
9102
|
-
const kept = options.cut ? new Uint8ClampedArray(
|
|
9101
|
+
const px2 = readRegion(surface, src);
|
|
9102
|
+
const kept = options.cut ? new Uint8ClampedArray(px2) : null;
|
|
9103
9103
|
for (let y = 0; y < sb.height; y++) {
|
|
9104
9104
|
for (let x = 0; x < sb.width; x++) {
|
|
9105
9105
|
const c = sel.data[(sb.y + y) * sel.width + sb.x + x];
|
|
9106
9106
|
const i = (y * sb.width + x) * 4 + 3;
|
|
9107
9107
|
if (kept) kept[i] = Math.round(kept[i] * (1 - c / 255));
|
|
9108
|
-
|
|
9108
|
+
px2[i] = Math.round(px2[i] * (c / 255));
|
|
9109
9109
|
}
|
|
9110
9110
|
}
|
|
9111
9111
|
const copy = {
|
|
@@ -9115,7 +9115,7 @@ var ImageEditorController = class {
|
|
|
9115
9115
|
visible: true,
|
|
9116
9116
|
opacity: 1,
|
|
9117
9117
|
blend: "normal",
|
|
9118
|
-
surface: surfaceFromPixels({ width: src.width, height: src.height, data:
|
|
9118
|
+
surface: surfaceFromPixels({ width: src.width, height: src.height, data: px2 }, 4, src.x, src.y),
|
|
9119
9119
|
offset: { ...layer.offset }
|
|
9120
9120
|
};
|
|
9121
9121
|
let next = insertLayer(this.state.layers, copy, loc.parent?.id ?? null, loc.index + 1);
|
|
@@ -10377,9 +10377,9 @@ var ImageEditorController = class {
|
|
|
10377
10377
|
return;
|
|
10378
10378
|
}
|
|
10379
10379
|
const { width, height } = this.naturalSize;
|
|
10380
|
-
let
|
|
10381
|
-
if (
|
|
10382
|
-
const data = new Uint8ClampedArray(
|
|
10380
|
+
let px2 = await this.decodeImage(newDataUrl);
|
|
10381
|
+
if (px2.width !== width || px2.height !== height) px2 = resizePixels(px2, width, height);
|
|
10382
|
+
const data = new Uint8ClampedArray(px2.data);
|
|
10383
10383
|
const sel = this.state.selection?.bounds ? this.state.selection : null;
|
|
10384
10384
|
if (sel) for (let p = 0, i = 3; p < width * height; p++, i += 4) data[i] = Math.round(data[i] * sel.data[p] / 255);
|
|
10385
10385
|
return this.addPixelLayer({ name: "Inpaint", pixels: { width, height, data } });
|
|
@@ -10400,10 +10400,10 @@ var ImageEditorController = class {
|
|
|
10400
10400
|
if (!target) throw new LayerOperationError("There is no pixel layer to mask.");
|
|
10401
10401
|
requireUnlocked(target, "all", "mask it");
|
|
10402
10402
|
const { width, height } = this.naturalSize;
|
|
10403
|
-
let
|
|
10404
|
-
if (
|
|
10403
|
+
let px2 = await this.decodeImage(newDataUrl);
|
|
10404
|
+
if (px2.width !== width || px2.height !== height) px2 = resizePixels(px2, width, height);
|
|
10405
10405
|
const alpha = new Uint8ClampedArray(width * height);
|
|
10406
|
-
for (let p = 0; p < alpha.length; p++) alpha[p] =
|
|
10406
|
+
for (let p = 0; p < alpha.length; p++) alpha[p] = px2.data[p * 4 + 3];
|
|
10407
10407
|
let surface = surfaceFromPixels({ width, height, data: alpha }, 1, 0, 0, 0);
|
|
10408
10408
|
if (target.offset.x || target.offset.y) surface = translateSurface(surface, -target.offset.x, -target.offset.y);
|
|
10409
10409
|
this.commitLayers(setLayerMask(this.state.layers, target.id, { surface, defaultValue: 0 }));
|
|
@@ -10913,24 +10913,24 @@ function pixelsOnly(layers) {
|
|
|
10913
10913
|
}
|
|
10914
10914
|
return out;
|
|
10915
10915
|
}
|
|
10916
|
-
function resizePixels(
|
|
10917
|
-
const src =
|
|
10916
|
+
function resizePixels(px2, width, height) {
|
|
10917
|
+
const src = px2.data;
|
|
10918
10918
|
const out = new Uint8ClampedArray(width * height * 4);
|
|
10919
|
-
const kx =
|
|
10920
|
-
const ky =
|
|
10919
|
+
const kx = px2.width / width;
|
|
10920
|
+
const ky = px2.height / height;
|
|
10921
10921
|
for (let y = 0; y < height; y++) {
|
|
10922
|
-
const fy = Math.min(
|
|
10922
|
+
const fy = Math.min(px2.height - 1, Math.max(0, (y + 0.5) * ky - 0.5));
|
|
10923
10923
|
const y0 = Math.floor(fy);
|
|
10924
|
-
const y1 = Math.min(
|
|
10924
|
+
const y1 = Math.min(px2.height - 1, y0 + 1);
|
|
10925
10925
|
const ty = fy - y0;
|
|
10926
10926
|
for (let x = 0; x < width; x++) {
|
|
10927
|
-
const fx = Math.min(
|
|
10927
|
+
const fx = Math.min(px2.width - 1, Math.max(0, (x + 0.5) * kx - 0.5));
|
|
10928
10928
|
const x0 = Math.floor(fx);
|
|
10929
|
-
const x1 = Math.min(
|
|
10929
|
+
const x1 = Math.min(px2.width - 1, x0 + 1);
|
|
10930
10930
|
const tx = fx - x0;
|
|
10931
10931
|
for (let c = 0; c < 4; c++) {
|
|
10932
|
-
const a = src[(y0 *
|
|
10933
|
-
const b = src[(y1 *
|
|
10932
|
+
const a = src[(y0 * px2.width + x0) * 4 + c] * (1 - tx) + src[(y0 * px2.width + x1) * 4 + c] * tx;
|
|
10933
|
+
const b = src[(y1 * px2.width + x0) * 4 + c] * (1 - tx) + src[(y1 * px2.width + x1) * 4 + c] * tx;
|
|
10934
10934
|
out[(y * width + x) * 4 + c] = a * (1 - ty) + b * ty;
|
|
10935
10935
|
}
|
|
10936
10936
|
}
|
|
@@ -11425,8 +11425,8 @@ function describeSelection(mask) {
|
|
|
11425
11425
|
if (!mask || !mask.bounds) return "No selection";
|
|
11426
11426
|
let sum = 0;
|
|
11427
11427
|
for (let i = 0; i < mask.data.length; i++) sum += mask.data[i];
|
|
11428
|
-
const
|
|
11429
|
-
const shown =
|
|
11428
|
+
const pct3 = sum / 255 / mask.data.length * 100;
|
|
11429
|
+
const shown = pct3 >= 10 ? Math.round(pct3) : Math.round(pct3 * 10) / 10;
|
|
11430
11430
|
return `${mask.bounds.width} \xD7 ${mask.bounds.height} px selected (${shown}% of the image)`;
|
|
11431
11431
|
}
|
|
11432
11432
|
|
|
@@ -11746,14 +11746,14 @@ function rebin(bins, count) {
|
|
|
11746
11746
|
return out;
|
|
11747
11747
|
}
|
|
11748
11748
|
function histogramSummary(h, bins) {
|
|
11749
|
-
const
|
|
11749
|
+
const pct3 = (n) => h.samples ? round(n / h.samples * 100) : 0;
|
|
11750
11750
|
const channels = { red: h.red, green: h.green, blue: h.blue, luminance: h.luminance };
|
|
11751
11751
|
return {
|
|
11752
11752
|
samples: h.samples,
|
|
11753
11753
|
transparent: h.transparent,
|
|
11754
11754
|
clipping: {
|
|
11755
|
-
shadowsPercent: { red:
|
|
11756
|
-
highlightsPercent: { red:
|
|
11755
|
+
shadowsPercent: { red: pct3(h.shadows[0]), green: pct3(h.shadows[1]), blue: pct3(h.shadows[2]) },
|
|
11756
|
+
highlightsPercent: { red: pct3(h.highlights[0]), green: pct3(h.highlights[1]), blue: pct3(h.highlights[2]) }
|
|
11757
11757
|
},
|
|
11758
11758
|
stats: Object.fromEntries(Object.entries(channels).map(([k, v]) => [k, binStats(v, h.samples)])),
|
|
11759
11759
|
...bins ? { bins: Object.fromEntries(Object.entries(channels).map(([k, v]) => [k, rebin(v, bins)])) } : {}
|
|
@@ -12620,7 +12620,7 @@ var DEFS = {
|
|
|
12620
12620
|
};
|
|
12621
12621
|
var nullable = (schema, description) => ({ anyOf: [schema, { type: "null" }], description });
|
|
12622
12622
|
function settingsSchema(forUpdate) {
|
|
12623
|
-
const
|
|
12623
|
+
const opt2 = (schema, description) => forUpdate ? nullable(schema, `${description} null removes it.`) : { ...schema, description };
|
|
12624
12624
|
return {
|
|
12625
12625
|
type: "object",
|
|
12626
12626
|
description: forUpdate ? "Settings to change (only these; nested objects are replaced whole). Which settings a kind takes: see add_layer_effect." : "Settings to change from Photoshop's defaults (all optional). Unknown settings for the kind are refused with the list of its settings.",
|
|
@@ -12635,7 +12635,7 @@ function settingsSchema(forUpdate) {
|
|
|
12635
12635
|
spread: { type: "number", minimum: 0, maximum: 1, description: "drop-shadow, outer-glow: 0..1 of the size made solid (default 0)." },
|
|
12636
12636
|
choke: { type: "number", minimum: 0, maximum: 1, description: "inner-shadow, inner-glow: 0..1 of the size made solid (default 0)." },
|
|
12637
12637
|
size: { type: "number", minimum: 0, maximum: 250, description: "Shadows, glows, satin, bevel-emboss: blur / size in px (default 5; satin 14); stroke: width in px (default 3)." },
|
|
12638
|
-
contour:
|
|
12638
|
+
contour: opt2({ $ref: "#/$defs/contour" }, "Shadows, glows, satin: coverage contour (default linear)."),
|
|
12639
12639
|
antiAlias: { type: "boolean", description: "Shadows, glows, satin: smooth the contour (default false; satin true)." },
|
|
12640
12640
|
noise: { type: "number", minimum: 0, maximum: 1, description: "Shadows, glows: speckle 0..1 (default 0)." },
|
|
12641
12641
|
knocksOut: { type: "boolean", description: "drop-shadow: the layer hides the shadow beneath its see-through fill (default true)." },
|
|
@@ -12649,14 +12649,14 @@ function settingsSchema(forUpdate) {
|
|
|
12649
12649
|
direction: { type: "string", enum: ["up", "down"], description: "bevel-emboss: raised (up, default) or sunk (down)." },
|
|
12650
12650
|
soften: { type: "number", minimum: 0, maximum: 16, description: "bevel-emboss: blur of the height in px (default 0)." },
|
|
12651
12651
|
altitude: { type: "number", minimum: 0, maximum: 90, description: "bevel-emboss: light altitude in degrees (default 30)." },
|
|
12652
|
-
glossContour:
|
|
12652
|
+
glossContour: opt2({ $ref: "#/$defs/contour" }, "bevel-emboss: gloss contour over the shading (default linear)."),
|
|
12653
12653
|
highlightColor: { $ref: "#/$defs/color", description: "bevel-emboss: highlight colour (default #ffffff)." },
|
|
12654
12654
|
highlightBlend: { type: "string", enum: BLEND_IDS, description: "bevel-emboss: highlight mode (default screen)." },
|
|
12655
12655
|
highlightOpacity: { type: "number", minimum: 0, maximum: 1, description: "bevel-emboss: highlight opacity (default 0.75)." },
|
|
12656
12656
|
shadowColor: { $ref: "#/$defs/color", description: "bevel-emboss: shadow colour (default #000000)." },
|
|
12657
12657
|
shadowBlend: { type: "string", enum: BLEND_IDS, description: "bevel-emboss: shadow mode (default multiply)." },
|
|
12658
12658
|
shadowOpacity: { type: "number", minimum: 0, maximum: 1, description: "bevel-emboss: shadow opacity (default 0.75)." },
|
|
12659
|
-
contourElement:
|
|
12659
|
+
contourElement: opt2(
|
|
12660
12660
|
{
|
|
12661
12661
|
type: "object",
|
|
12662
12662
|
properties: {
|
|
@@ -12669,7 +12669,7 @@ function settingsSchema(forUpdate) {
|
|
|
12669
12669
|
},
|
|
12670
12670
|
"bevel-emboss: the Contour element, shaping the bevel's height profile (default off)."
|
|
12671
12671
|
),
|
|
12672
|
-
texture:
|
|
12672
|
+
texture: opt2(
|
|
12673
12673
|
{
|
|
12674
12674
|
type: "object",
|
|
12675
12675
|
properties: {
|
|
@@ -13686,8 +13686,8 @@ function applyRegionMosaic(imageData, regions, blockSize = 12) {
|
|
|
13686
13686
|
const count = bw * bh;
|
|
13687
13687
|
for (let py = by; py < by + bh; py++) {
|
|
13688
13688
|
const rowOffset = py * width;
|
|
13689
|
-
for (let
|
|
13690
|
-
const idx = (rowOffset +
|
|
13689
|
+
for (let px2 = bx; px2 < bx + bw; px2++) {
|
|
13690
|
+
const idx = (rowOffset + px2) * 4;
|
|
13691
13691
|
rSum += data[idx];
|
|
13692
13692
|
gSum += data[idx + 1];
|
|
13693
13693
|
bSum += data[idx + 2];
|
|
@@ -13700,8 +13700,8 @@ function applyRegionMosaic(imageData, regions, blockSize = 12) {
|
|
|
13700
13700
|
const aAvg = Math.round(aSum / count);
|
|
13701
13701
|
for (let py = by; py < by + bh; py++) {
|
|
13702
13702
|
const rowOffset = py * width;
|
|
13703
|
-
for (let
|
|
13704
|
-
const idx = (rowOffset +
|
|
13703
|
+
for (let px2 = bx; px2 < bx + bw; px2++) {
|
|
13704
|
+
const idx = (rowOffset + px2) * 4;
|
|
13705
13705
|
data[idx] = rAvg;
|
|
13706
13706
|
data[idx + 1] = gAvg;
|
|
13707
13707
|
data[idx + 2] = bAvg;
|
|
@@ -13919,6 +13919,179 @@ async function runLayerMenuAction(controller, action) {
|
|
|
13919
13919
|
}
|
|
13920
13920
|
}
|
|
13921
13921
|
|
|
13922
|
+
// src/image-editor/layers/effects-ui.ts
|
|
13923
|
+
function layerEffectsPanel(layer) {
|
|
13924
|
+
const empty = { readOnly: true, enabled: true, scale: 1, rows: [] };
|
|
13925
|
+
if (!layer) return { ...empty, available: false, reason: "Select a layer to add effects." };
|
|
13926
|
+
if (layer.type === "adjustment") return { ...empty, available: false, reason: "Adjustment layers cannot have effects." };
|
|
13927
|
+
const fx = layer.effects;
|
|
13928
|
+
const readOnly = Boolean(layer.locks?.all);
|
|
13929
|
+
return {
|
|
13930
|
+
available: true,
|
|
13931
|
+
readOnly,
|
|
13932
|
+
reason: readOnly ? "The layer is locked; unlock it to change its effects." : void 0,
|
|
13933
|
+
enabled: fx?.enabled ?? true,
|
|
13934
|
+
scale: fx?.scale ?? 1,
|
|
13935
|
+
rows: (fx?.items ?? []).map((e) => ({ id: e.id, kind: e.kind, label: layerEffectLabel(e.kind), enabled: e.enabled }))
|
|
13936
|
+
};
|
|
13937
|
+
}
|
|
13938
|
+
function layerEffectsBadge(layer) {
|
|
13939
|
+
if (!layer.effects?.items.length) return null;
|
|
13940
|
+
return hasActiveEffects(layer.effects) ? "on" : "off";
|
|
13941
|
+
}
|
|
13942
|
+
var LAYER_EFFECT_MENU = LAYER_EFFECT_KINDS.map((kind) => ({ kind, label: layerEffectLabel(kind) }));
|
|
13943
|
+
var BLENDS = BLEND_MODES.filter((m) => m.id !== "pigment").map((m) => ({ value: m.id, label: m.label }));
|
|
13944
|
+
var opt = (...pairs) => pairs.map(([value, label]) => ({ value, label }));
|
|
13945
|
+
function patternOptions(patterns) {
|
|
13946
|
+
return [...patterns.map((p) => ({ value: p.id, label: p.name || p.id })), ...builtinPatternIds().map((id) => ({ value: id, label: builtinPattern(id).name }))];
|
|
13947
|
+
}
|
|
13948
|
+
function getPath(o, path) {
|
|
13949
|
+
let v = o;
|
|
13950
|
+
for (const p of path.split(".")) {
|
|
13951
|
+
if (v === null || v === void 0) return void 0;
|
|
13952
|
+
if (Array.isArray(v)) v = p === "last" ? v[v.length - 1] : v[Number(p)];
|
|
13953
|
+
else v = v[p];
|
|
13954
|
+
}
|
|
13955
|
+
return v;
|
|
13956
|
+
}
|
|
13957
|
+
function setPath(o, parts, value) {
|
|
13958
|
+
if (!parts.length) return value;
|
|
13959
|
+
const [p, ...rest] = parts;
|
|
13960
|
+
if (Array.isArray(o)) {
|
|
13961
|
+
const i = p === "last" ? o.length - 1 : Number(p);
|
|
13962
|
+
const out = [...o];
|
|
13963
|
+
out[i] = setPath(o[i], rest, value);
|
|
13964
|
+
return out;
|
|
13965
|
+
}
|
|
13966
|
+
const r = o ?? {};
|
|
13967
|
+
return { ...r, [p]: setPath(r[p], rest, value) };
|
|
13968
|
+
}
|
|
13969
|
+
function fillControls(f, allowPattern, patterns) {
|
|
13970
|
+
const types = allowPattern ? opt(["color", "Color"], ["gradient", "Gradient"], ["pattern", "Pattern"]) : opt(["color", "Color"], ["gradient", "Gradient"]);
|
|
13971
|
+
const out = [{ key: "fill.type", label: "Fill", type: "select", value: f.type, options: types }];
|
|
13972
|
+
if (f.type === "color") out.push({ key: "fill.color", label: "Color", type: "color", value: f.color });
|
|
13973
|
+
else if (f.type === "gradient") out.push(...gradientControls("fill.gradient", f.gradient));
|
|
13974
|
+
else {
|
|
13975
|
+
out.push(
|
|
13976
|
+
{ key: "fill.patternId", label: "Pattern", type: "select", value: f.patternId, options: patternOptions(patterns) },
|
|
13977
|
+
{ key: "fill.scale", label: "Scale", type: "range", min: 0.01, max: 10, step: 0.01, value: f.scale, percent: true, unit: "%" }
|
|
13978
|
+
);
|
|
13979
|
+
}
|
|
13980
|
+
return out;
|
|
13981
|
+
}
|
|
13982
|
+
function gradientControls(prefix, g) {
|
|
13983
|
+
return [
|
|
13984
|
+
{ key: `${prefix}.stops.0.color`, label: "Start colour", type: "color", value: g.stops[0]?.color ?? "#000000" },
|
|
13985
|
+
{ key: `${prefix}.stops.last.color`, label: "End colour", type: "color", value: g.stops[g.stops.length - 1]?.color ?? "#ffffff" },
|
|
13986
|
+
{ key: `${prefix}.style`, label: "Style", type: "select", value: g.style, options: opt(["linear", "Linear"], ["radial", "Radial"], ["angle", "Angle"], ["reflected", "Reflected"], ["diamond", "Diamond"]) },
|
|
13987
|
+
{ key: `${prefix}.angle`, label: "Angle", type: "range", min: -180, max: 180, step: 1, value: g.angle, unit: "\xB0" },
|
|
13988
|
+
{ key: `${prefix}.scale`, label: "Scale", type: "range", min: 0.1, max: 1.5, step: 0.01, value: g.scale, percent: true, unit: "%" },
|
|
13989
|
+
{ key: `${prefix}.reverse`, label: "Reverse", type: "checkbox", value: g.reverse },
|
|
13990
|
+
{ key: `${prefix}.align`, label: "Align with layer", type: "checkbox", value: g.align }
|
|
13991
|
+
];
|
|
13992
|
+
}
|
|
13993
|
+
var px = (key, label, value, max = 250) => ({ key, label, type: "range", min: 0, max, step: 1, value, unit: "px" });
|
|
13994
|
+
var pct2 = (key, label, value, min = 0, max = 1) => ({ key, label, type: "range", min, max, step: 0.01, value, percent: true, unit: "%" });
|
|
13995
|
+
var deg = (key, label, value, min = -180, max = 180) => ({ key, label, type: "range", min, max, step: 1, value, unit: "\xB0" });
|
|
13996
|
+
function layerEffectControls(e, patterns = []) {
|
|
13997
|
+
const blended = e.kind === "bevel-emboss" ? [] : [
|
|
13998
|
+
{ key: "blend", label: "Blend mode", type: "select", value: e.blend, options: BLENDS },
|
|
13999
|
+
pct2("opacity", "Opacity", e.opacity)
|
|
14000
|
+
];
|
|
14001
|
+
switch (e.kind) {
|
|
14002
|
+
case "drop-shadow":
|
|
14003
|
+
case "inner-shadow":
|
|
14004
|
+
return [
|
|
14005
|
+
...blended,
|
|
14006
|
+
{ key: "color", label: "Color", type: "color", value: e.color },
|
|
14007
|
+
{ key: "useGlobalLight", label: "Use global light", type: "checkbox", value: e.useGlobalLight },
|
|
14008
|
+
deg("angle", "Angle", e.angle),
|
|
14009
|
+
px("distance", "Distance", e.distance, 500),
|
|
14010
|
+
e.kind === "drop-shadow" ? pct2("spread", "Spread", e.spread) : pct2("choke", "Choke", e.choke),
|
|
14011
|
+
px("size", "Size", e.size),
|
|
14012
|
+
pct2("noise", "Noise", e.noise),
|
|
14013
|
+
...e.kind === "drop-shadow" ? [{ key: "knocksOut", label: "Layer knocks out shadow", type: "checkbox", value: e.knocksOut }] : []
|
|
14014
|
+
];
|
|
14015
|
+
case "outer-glow":
|
|
14016
|
+
case "inner-glow":
|
|
14017
|
+
return [
|
|
14018
|
+
...blended,
|
|
14019
|
+
...fillControls(e.fill, false, patterns),
|
|
14020
|
+
{ key: "technique", label: "Technique", type: "select", value: e.technique, options: opt(["softer", "Softer"], ["precise", "Precise"]) },
|
|
14021
|
+
...e.kind === "inner-glow" ? [{ key: "source", label: "Source", type: "select", value: e.source, options: opt(["edge", "Edge"], ["center", "Center"]) }] : [],
|
|
14022
|
+
e.kind === "outer-glow" ? pct2("spread", "Spread", e.spread) : pct2("choke", "Choke", e.choke),
|
|
14023
|
+
px("size", "Size", e.size),
|
|
14024
|
+
pct2("range", "Range", e.range, 0.01, 1),
|
|
14025
|
+
pct2("noise", "Noise", e.noise)
|
|
14026
|
+
];
|
|
14027
|
+
case "bevel-emboss":
|
|
14028
|
+
return [
|
|
14029
|
+
{ key: "style", label: "Style", type: "select", value: e.style, options: opt(["inner-bevel", "Inner Bevel"], ["outer-bevel", "Outer Bevel"], ["emboss", "Emboss"], ["pillow-emboss", "Pillow Emboss"], ["stroke-emboss", "Stroke Emboss"]) },
|
|
14030
|
+
{ key: "technique", label: "Technique", type: "select", value: e.technique, options: opt(["smooth", "Smooth"], ["chisel-hard", "Chisel Hard"], ["chisel-soft", "Chisel Soft"]) },
|
|
14031
|
+
pct2("depth", "Depth", e.depth, 0, 10),
|
|
14032
|
+
{ key: "direction", label: "Direction", type: "select", value: e.direction, options: opt(["up", "Up"], ["down", "Down"]) },
|
|
14033
|
+
px("size", "Size", e.size),
|
|
14034
|
+
px("soften", "Soften", e.soften, 16),
|
|
14035
|
+
{ key: "useGlobalLight", label: "Use global light", type: "checkbox", value: e.useGlobalLight },
|
|
14036
|
+
deg("angle", "Angle", e.angle),
|
|
14037
|
+
deg("altitude", "Altitude", e.altitude, 0, 90),
|
|
14038
|
+
{ key: "highlightColor", label: "Highlight", type: "color", value: e.highlightColor },
|
|
14039
|
+
{ key: "highlightBlend", label: "Highlight mode", type: "select", value: e.highlightBlend, options: BLENDS },
|
|
14040
|
+
pct2("highlightOpacity", "Highlight opacity", e.highlightOpacity),
|
|
14041
|
+
{ key: "shadowColor", label: "Shadow", type: "color", value: e.shadowColor },
|
|
14042
|
+
{ key: "shadowBlend", label: "Shadow mode", type: "select", value: e.shadowBlend, options: BLENDS },
|
|
14043
|
+
pct2("shadowOpacity", "Shadow opacity", e.shadowOpacity)
|
|
14044
|
+
];
|
|
14045
|
+
case "satin":
|
|
14046
|
+
return [
|
|
14047
|
+
...blended,
|
|
14048
|
+
{ key: "color", label: "Color", type: "color", value: e.color },
|
|
14049
|
+
deg("angle", "Angle", e.angle),
|
|
14050
|
+
px("distance", "Distance", e.distance),
|
|
14051
|
+
px("size", "Size", e.size),
|
|
14052
|
+
{ key: "invert", label: "Invert", type: "checkbox", value: e.invert }
|
|
14053
|
+
];
|
|
14054
|
+
case "color-overlay":
|
|
14055
|
+
return [...blended, { key: "color", label: "Color", type: "color", value: e.color }];
|
|
14056
|
+
case "gradient-overlay":
|
|
14057
|
+
return [...blended, ...gradientControls("gradient", e.gradient)];
|
|
14058
|
+
case "pattern-overlay":
|
|
14059
|
+
return [
|
|
14060
|
+
...blended,
|
|
14061
|
+
{ key: "patternId", label: "Pattern", type: "select", value: e.patternId, options: patternOptions(patterns) },
|
|
14062
|
+
pct2("scale", "Scale", e.scale, 0.01, 10),
|
|
14063
|
+
deg("angle", "Angle", e.angle ?? 0),
|
|
14064
|
+
{ key: "link", label: "Link with layer", type: "checkbox", value: e.link ?? true }
|
|
14065
|
+
];
|
|
14066
|
+
case "stroke":
|
|
14067
|
+
return [
|
|
14068
|
+
...blended,
|
|
14069
|
+
px("size", "Size", e.size),
|
|
14070
|
+
{ key: "position", label: "Position", type: "select", value: e.position, options: opt(["outside", "Outside"], ["inside", "Inside"], ["center", "Center"]) },
|
|
14071
|
+
...fillControls(e.fill, true, patterns)
|
|
14072
|
+
];
|
|
14073
|
+
}
|
|
14074
|
+
}
|
|
14075
|
+
function fillOfType(current, type, patterns) {
|
|
14076
|
+
if (type === "gradient") return { type: "gradient", gradient: defaultEffectGradient() };
|
|
14077
|
+
if (type === "pattern") return { type: "pattern", patternId: patterns[0]?.id ?? "checker", scale: 1 };
|
|
14078
|
+
return { type: "color", color: current.type === "color" ? current.color : "#000000" };
|
|
14079
|
+
}
|
|
14080
|
+
function effectControlChange(effect, key, value, patterns = []) {
|
|
14081
|
+
const [top, ...rest] = key.split(".");
|
|
14082
|
+
if (top === "fill" && rest[0] === "type") return { fill: fillOfType(effect.fill, String(value), patterns) };
|
|
14083
|
+
if (top === "patternId" && !rest.length) {
|
|
14084
|
+
const name = patternOptions(patterns).find((o) => o.value === value)?.label;
|
|
14085
|
+
return name ? { patternId: value, name } : { patternId: value };
|
|
14086
|
+
}
|
|
14087
|
+
if (!rest.length) return { [top]: value };
|
|
14088
|
+
if (top === "fill" && rest[0] === "patternId") {
|
|
14089
|
+
const name = patternOptions(patterns).find((o) => o.value === value)?.label;
|
|
14090
|
+
return { fill: { ...effect.fill, patternId: value, ...name ? { name } : {} } };
|
|
14091
|
+
}
|
|
14092
|
+
return { [top]: setPath(getPath(effect, top), rest, value) };
|
|
14093
|
+
}
|
|
14094
|
+
|
|
13922
14095
|
// src/image-editor/psd/ui.ts
|
|
13923
14096
|
var PSD_FILE_ACCEPT = ".psd,.psb,image/vnd.adobe.photoshop,application/x-photoshop";
|
|
13924
14097
|
function blobBytes(file) {
|
|
@@ -13991,6 +14164,7 @@ export {
|
|
|
13991
14164
|
LATEST_PROCESS_VERSION,
|
|
13992
14165
|
LAYERED_SCHEMA_VERSION,
|
|
13993
14166
|
LAYER_EFFECT_KINDS,
|
|
14167
|
+
LAYER_EFFECT_MENU,
|
|
13994
14168
|
LEGACY_PROCESS_VERSION,
|
|
13995
14169
|
LUMA_709,
|
|
13996
14170
|
LayerCompositor,
|
|
@@ -14152,6 +14326,7 @@ export {
|
|
|
14152
14326
|
duplicateLayer,
|
|
14153
14327
|
edgeWidth,
|
|
14154
14328
|
effectColorHex,
|
|
14329
|
+
effectControlChange,
|
|
14155
14330
|
effectReach,
|
|
14156
14331
|
effectsFingerprint,
|
|
14157
14332
|
effectsMargin,
|
|
@@ -14231,9 +14406,12 @@ export {
|
|
|
14231
14406
|
isSpatialFilter,
|
|
14232
14407
|
kelvinToTemperature,
|
|
14233
14408
|
layerBadge,
|
|
14409
|
+
layerEffectControls,
|
|
14234
14410
|
layerEffectFields,
|
|
14235
14411
|
layerEffectLabel,
|
|
14412
|
+
layerEffectsBadge,
|
|
14236
14413
|
layerEffectsOf,
|
|
14414
|
+
layerEffectsPanel,
|
|
14237
14415
|
layerLabel,
|
|
14238
14416
|
layerMaskCoverage,
|
|
14239
14417
|
layerMaskFromSelection,
|