@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.
@@ -58,7 +58,7 @@ import {
58
58
  validateEffectGradient,
59
59
  validateLayerEffect,
60
60
  validateLayerEffects
61
- } from "../chunk-KFTQ2PKP.js";
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 px = canvasPt.x;
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(px, py, x, y) <= handleThreshold) return "nw";
564
- if (dist(px, py, x + w, y) <= handleThreshold) return "ne";
565
- if (dist(px, py, x + w, y + h) <= handleThreshold) return "se";
566
- if (dist(px, py, x, y + h) <= handleThreshold) return "sw";
567
- const nearX = px >= x - handleThreshold && px <= x + w + handleThreshold;
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(px - x) <= handleThreshold) return "w";
572
- if (nearY && Math.abs(px - (x + w)) <= handleThreshold) return "e";
573
- if (px >= x && px <= x + w && py >= y && py <= y + h) {
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, deg) {
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(deg);
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 deg = k / (n - 1) * 360;
1619
+ const deg2 = k / (n - 1) * 360;
1620
1620
  for (const r of active) {
1621
- const w = hueRangeWeight(r, deg);
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 px = Math.min(w - 1, Math.max(0, x + k));
2185
- const idx = (y * w + px) * 4;
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 px = 1 / (projection.scale || 1);
3140
+ const px2 = 1 / (projection.scale || 1);
3141
3141
  ctx.beginPath();
3142
- ctx.arc(b.x, b.y, Math.max(px, b.diameter / 2), 0, Math.PI * 2);
3143
- ctx.lineWidth = 2 * px;
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 = px;
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 px = 1 / (scale || 1);
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 = px;
3435
+ ctx.lineWidth = px2;
3436
3436
  ctx.strokeStyle = "#ffffff";
3437
3437
  ctx.stroke();
3438
3438
  if (typeof ctx.setLineDash === "function") {
3439
- ctx.setLineDash([4 * px, 4 * px]);
3440
- ctx.lineDashOffset = -offset * px;
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 px = 1 / (scale || 1);
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 = px;
3451
+ ctx.lineWidth = px2;
3452
3452
  ctx.strokeStyle = "#ffffff";
3453
3453
  ctx.stroke();
3454
- if (typeof ctx.setLineDash === "function") ctx.setLineDash([4 * px, 4 * px]);
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 px = p.shiftEdge / 100 * Math.max(p.radius, 4);
4301
- return Number.isFinite(px) ? Math.min(64, Math.max(-64, px)) : 0;
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 px = (ctx.decodeAsset ?? decodePngDataUrl)(url, channels);
4706
- return surfaceFromPixels(px, channels, Math.round(num(x, -1e7, 1e7, 0)), Math.round(num(y, -1e7, 1e7, 0)), defaultValue);
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 = (px2) => {
8767
- let s = this.sourceSurfaces.get(px2);
8766
+ const fromPixels = (px3) => {
8767
+ let s = this.sourceSurfaces.get(px3);
8768
8768
  if (!s) {
8769
- s = surfaceFromPixels(px2, 4);
8770
- this.sourceSurfaces.set(px2, s);
8769
+ s = surfaceFromPixels(px3, 4);
8770
+ this.sourceSurfaces.set(px3, s);
8771
8771
  }
8772
8772
  return s;
8773
8773
  };
8774
- const px = this.samplePixels;
8775
- const fits = Boolean(px && px.width === width && px.height === height);
8776
- if (this.samplePixelsOverride && fits) return fromPixels(px);
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(px) : null;
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 px = init.pixels;
8850
- if (!(px.width > 0 && px.height > 0) || px.data.length < px.width * px.height * 4) {
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(px, 4);
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 px = readRegion(surface, src);
9102
- const kept = options.cut ? new Uint8ClampedArray(px) : null;
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
- px[i] = Math.round(px[i] * (c / 255));
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: px }, 4, src.x, src.y),
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 px = await this.decodeImage(newDataUrl);
10381
- if (px.width !== width || px.height !== height) px = resizePixels(px, width, height);
10382
- const data = new Uint8ClampedArray(px.data);
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 px = await this.decodeImage(newDataUrl);
10404
- if (px.width !== width || px.height !== height) px = resizePixels(px, width, height);
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] = px.data[p * 4 + 3];
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(px, width, height) {
10917
- const src = px.data;
10916
+ function resizePixels(px2, width, height) {
10917
+ const src = px2.data;
10918
10918
  const out = new Uint8ClampedArray(width * height * 4);
10919
- const kx = px.width / width;
10920
- const ky = px.height / height;
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(px.height - 1, Math.max(0, (y + 0.5) * ky - 0.5));
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(px.height - 1, y0 + 1);
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(px.width - 1, Math.max(0, (x + 0.5) * kx - 0.5));
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(px.width - 1, x0 + 1);
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 * px.width + x0) * 4 + c] * (1 - tx) + src[(y0 * px.width + x1) * 4 + c] * tx;
10933
- const b = src[(y1 * px.width + x0) * 4 + c] * (1 - tx) + src[(y1 * px.width + x1) * 4 + c] * tx;
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 pct2 = sum / 255 / mask.data.length * 100;
11429
- const shown = pct2 >= 10 ? Math.round(pct2) : Math.round(pct2 * 10) / 10;
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 pct2 = (n) => h.samples ? round(n / h.samples * 100) : 0;
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: pct2(h.shadows[0]), green: pct2(h.shadows[1]), blue: pct2(h.shadows[2]) },
11756
- highlightsPercent: { red: pct2(h.highlights[0]), green: pct2(h.highlights[1]), blue: pct2(h.highlights[2]) }
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 opt = (schema, description) => forUpdate ? nullable(schema, `${description} null removes it.`) : { ...schema, description };
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: opt({ $ref: "#/$defs/contour" }, "Shadows, glows, satin: coverage contour (default linear)."),
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: opt({ $ref: "#/$defs/contour" }, "bevel-emboss: gloss contour over the shading (default linear)."),
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: opt(
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: opt(
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 px = bx; px < bx + bw; px++) {
13690
- const idx = (rowOffset + px) * 4;
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 px = bx; px < bx + bw; px++) {
13704
- const idx = (rowOffset + px) * 4;
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,