@pixerate/editor 0.214.0 → 0.215.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/{chunk-UOGKSVRQ.cjs → chunk-B46UQUDC.cjs} +770 -733
  2. package/dist/{chunk-W3TOZTL7.js → chunk-F7Z2MKY2.js} +115 -4
  3. package/dist/{chunk-YBJXCQTR.js → chunk-QXY4YTRI.js} +738 -701
  4. package/dist/{chunk-H65IBXFI.cjs → chunk-S55JFWDA.cjs} +350 -239
  5. package/dist/{chunk-QYLWG22N.cjs → chunk-SS53NNZO.cjs} +304 -1
  6. package/dist/{chunk-LQ5SALHK.js → chunk-U7C655AM.js} +5 -311
  7. package/dist/{chunk-L52VMQZS.cjs → chunk-UC3PWFM3.cjs} +68 -374
  8. package/dist/{chunk-L3G23N5Y.js → chunk-ZNA3IQZF.js} +304 -1
  9. package/dist/image-editor/controller.d.cts +29 -0
  10. package/dist/image-editor/controller.d.ts +29 -0
  11. package/dist/image-editor/index.cjs +1224 -1074
  12. package/dist/image-editor/index.d.cts +1 -0
  13. package/dist/image-editor/index.d.ts +1 -0
  14. package/dist/image-editor/index.js +166 -16
  15. package/dist/image-editor/layers/edge-color.d.cts +27 -0
  16. package/dist/image-editor/layers/edge-color.d.ts +27 -0
  17. package/dist/image-editor/layers/serialize.d.cts +13 -0
  18. package/dist/image-editor/layers/serialize.d.ts +13 -0
  19. package/dist/image-editor/selection-worker.cjs +4 -4
  20. package/dist/image-editor/selection-worker.js +2 -2
  21. package/dist/paint-editor/index.cjs +79 -79
  22. package/dist/paint-editor/index.js +3 -3
  23. package/dist/raster/compositor.d.cts +10 -1
  24. package/dist/raster/compositor.d.ts +10 -1
  25. package/dist/raster/deep-compositor.d.cts +1 -1
  26. package/dist/raster/deep-compositor.d.ts +1 -1
  27. package/dist/raster/index.cjs +4 -2
  28. package/dist/raster/index.js +3 -1
  29. package/dist/raster/layers.d.cts +16 -0
  30. package/dist/raster/layers.d.ts +16 -0
  31. package/package.json +1 -1
  32. package/skills/pixerate-image-editor/SKILL.md +8 -6
  33. package/skills/pixerate-image-editor/references/paths.md +12 -0
  34. package/skills/pixerate-image-selection-masking/SKILL.md +8 -8
@@ -724,6 +724,298 @@ function modifySelection(mask, op, radius) {
724
724
  }
725
725
  }
726
726
 
727
+ // src/image-editor/selection/refine.ts
728
+ var DEFAULT_REFINE_EDGE_PARAMS = {
729
+ radius: 0,
730
+ smartRadius: false,
731
+ smooth: 0,
732
+ feather: 0,
733
+ contrast: 0,
734
+ shiftEdge: 0
735
+ };
736
+ var GUIDED_FILTER_EPS = 1e-4;
737
+ var fin = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
738
+ function normalizeRefineEdgeParams(p = {}) {
739
+ return {
740
+ radius: Math.min(250, Math.max(0, fin(p.radius, 0))),
741
+ smartRadius: p.smartRadius === true,
742
+ smooth: Math.min(100, Math.max(0, fin(p.smooth, 0))),
743
+ feather: Math.min(1e3, Math.max(0, fin(p.feather, 0))),
744
+ contrast: Math.min(100, Math.max(0, fin(p.contrast, 0))),
745
+ shiftEdge: Math.min(100, Math.max(-100, fin(p.shiftEdge, 0)))
746
+ };
747
+ }
748
+ var gfRadius = (p) => Math.min(64, Math.max(1, Math.ceil(p.radius)));
749
+ var smoothSigma = (p) => p.smooth / 100 * 4;
750
+ var featherSigma = (p) => p.feather / 2;
751
+ function shiftAmount(p) {
752
+ const px = p.shiftEdge / 100 * Math.max(p.radius, 4);
753
+ return Number.isFinite(px) ? Math.min(64, Math.max(-64, px)) : 0;
754
+ }
755
+ function refineEdgeHalo(params = {}) {
756
+ const p = normalizeRefineEdgeParams(params);
757
+ const g = gfRadius(p);
758
+ const edge = p.radius > 0 ? Math.max(Math.ceil(p.radius), 2 * g + (p.smartRadius ? g + 1 : 0)) : 0;
759
+ return edge + Math.ceil(3 * smoothSigma(p)) + Math.ceil(3 * featherSigma(p)) + Math.ceil(Math.abs(shiftAmount(p))) + 2;
760
+ }
761
+ function boxMean(src, w, h, r) {
762
+ const tmp = new Float32Array(w * h);
763
+ const pre = new Float64Array(Math.max(w, h) + 1);
764
+ for (let y = 0; y < h; y++) {
765
+ const row = y * w;
766
+ for (let x = 0; x < w; x++) pre[x + 1] = pre[x] + src[row + x];
767
+ for (let x = 0; x < w; x++) {
768
+ const lo = Math.max(0, x - r);
769
+ const hi = Math.min(w, x + r + 1);
770
+ tmp[row + x] = (pre[hi] - pre[lo]) / (hi - lo);
771
+ }
772
+ }
773
+ const out = new Float32Array(w * h);
774
+ for (let x = 0; x < w; x++) {
775
+ for (let y = 0; y < h; y++) pre[y + 1] = pre[y] + tmp[y * w + x];
776
+ for (let y = 0; y < h; y++) {
777
+ const lo = Math.max(0, y - r);
778
+ const hi = Math.min(h, y + r + 1);
779
+ out[y * w + x] = (pre[hi] - pre[lo]) / (hi - lo);
780
+ }
781
+ }
782
+ return out;
783
+ }
784
+ function guidedFilterColor(guide, p, w, h, r, eps = GUIDED_FILTER_EPS) {
785
+ const n = w * h;
786
+ const [c0, c1, c2] = guide;
787
+ const prod = (a, b2) => {
788
+ const o = new Float32Array(n);
789
+ for (let i = 0; i < n; i++) o[i] = a[i] * b2[i];
790
+ return o;
791
+ };
792
+ const m0 = boxMean(c0, w, h, r);
793
+ const m1 = boxMean(c1, w, h, r);
794
+ const m2 = boxMean(c2, w, h, r);
795
+ const mp = boxMean(p, w, h, r);
796
+ const mip0 = boxMean(prod(c0, p), w, h, r);
797
+ const mip1 = boxMean(prod(c1, p), w, h, r);
798
+ const mip2 = boxMean(prod(c2, p), w, h, r);
799
+ const s00 = boxMean(prod(c0, c0), w, h, r);
800
+ const s01 = boxMean(prod(c0, c1), w, h, r);
801
+ const s02 = boxMean(prod(c0, c2), w, h, r);
802
+ const s11 = boxMean(prod(c1, c1), w, h, r);
803
+ const s12 = boxMean(prod(c1, c2), w, h, r);
804
+ const s22 = boxMean(prod(c2, c2), w, h, r);
805
+ const a0 = new Float32Array(n);
806
+ const a1 = new Float32Array(n);
807
+ const a2 = new Float32Array(n);
808
+ const b = new Float32Array(n);
809
+ for (let i = 0; i < n; i++) {
810
+ const u0 = m0[i], u1 = m1[i], u2 = m2[i], up = mp[i];
811
+ const rr = s00[i] - u0 * u0 + eps;
812
+ const rg = s01[i] - u0 * u1;
813
+ const rb = s02[i] - u0 * u2;
814
+ const gg = s11[i] - u1 * u1 + eps;
815
+ const gb = s12[i] - u1 * u2;
816
+ const bb = s22[i] - u2 * u2 + eps;
817
+ const v0 = mip0[i] - u0 * up;
818
+ const v1 = mip1[i] - u1 * up;
819
+ const v2 = mip2[i] - u2 * up;
820
+ const i00 = gg * bb - gb * gb;
821
+ const i01 = gb * rb - rg * bb;
822
+ const i02 = rg * gb - gg * rb;
823
+ const i11 = rr * bb - rb * rb;
824
+ const i12 = rb * rg - rr * gb;
825
+ const i22 = rr * gg - rg * rg;
826
+ const det = rr * i00 + rg * i01 + rb * i02;
827
+ let x0 = 0, x1 = 0, x2 = 0;
828
+ if (Math.abs(det) >= 1e-30) {
829
+ x0 = (i00 * v0 + i01 * v1 + i02 * v2) / det;
830
+ x1 = (i01 * v0 + i11 * v1 + i12 * v2) / det;
831
+ x2 = (i02 * v0 + i12 * v1 + i22 * v2) / det;
832
+ }
833
+ a0[i] = x0;
834
+ a1[i] = x1;
835
+ a2[i] = x2;
836
+ b[i] = up - x0 * u0 - x1 * u1 - x2 * u2;
837
+ }
838
+ const ma0 = boxMean(a0, w, h, r);
839
+ const ma1 = boxMean(a1, w, h, r);
840
+ const ma2 = boxMean(a2, w, h, r);
841
+ const mb = boxMean(b, w, h, r);
842
+ const out = new Float32Array(n);
843
+ for (let i = 0; i < n; i++) out[i] = ma0[i] * c0[i] + ma1[i] * c1[i] + ma2[i] * c2[i] + mb[i];
844
+ return out;
845
+ }
846
+ function edgeWidth(guide, w, h, r) {
847
+ const n = w * h;
848
+ const y = new Float32Array(n);
849
+ for (let i = 0; i < n; i++) y[i] = 0.299 * guide[0][i] + 0.587 * guide[1][i] + 0.114 * guide[2][i];
850
+ const g = new Float32Array(n);
851
+ for (let yy = 0; yy < h; yy++) {
852
+ for (let xx = 0; xx < w; xx++) {
853
+ const gx = (y[yy * w + Math.min(w - 1, xx + 1)] - y[yy * w + Math.max(0, xx - 1)]) / 2;
854
+ const gy = (y[Math.min(h - 1, yy + 1) * w + xx] - y[Math.max(0, yy - 1) * w + xx]) / 2;
855
+ g[yy * w + xx] = Math.sqrt(gx * gx + gy * gy);
856
+ }
857
+ }
858
+ const gm = greyMorphology(g, w, h, r, true);
859
+ const hi = greyMorphology(y, w, h, r, true);
860
+ const lo = greyMorphology(y, w, h, r, false);
861
+ const out = new Float32Array(n);
862
+ for (let i = 0; i < n; i++) out[i] = (hi[i] - lo[i]) / Math.max(gm[i], 1e-4);
863
+ return out;
864
+ }
865
+ function refineCoverage(guide, m, w, h, params = {}, band = null) {
866
+ const p = normalizeRefineEdgeParams(params);
867
+ let a = Float32Array.from(m);
868
+ if ((p.radius > 0 || band) && guide) {
869
+ const n = w * h;
870
+ const boundary = new Uint8Array(n);
871
+ let any = false;
872
+ for (let y = 0; y < h; y++) {
873
+ for (let x = 0; x < w; x++) {
874
+ const i = y * w + x;
875
+ const v = m[i] >= 0.5;
876
+ const diff = x > 0 && m[i - 1] >= 0.5 !== v || x + 1 < w && m[i + 1] >= 0.5 !== v || y > 0 && m[i - w] >= 0.5 !== v || y + 1 < h && m[i + w] >= 0.5 !== v;
877
+ if (diff) {
878
+ boundary[i] = 1;
879
+ any = true;
880
+ }
881
+ }
882
+ }
883
+ if (any || band) {
884
+ const dist = distanceTransform(boundary, w, h);
885
+ const r = band ? Math.max(4, gfRadius(p)) : gfRadius(p);
886
+ const q = guidedFilterColor(guide, m, w, h, r);
887
+ const widths = p.smartRadius ? edgeWidth(guide, w, h, r) : null;
888
+ for (let i = 0; i < n; i++) {
889
+ const reff = widths ? Math.min(Math.max(p.radius, 1.5), Math.max(1.5, widths[i])) : p.radius;
890
+ const edge = p.radius > 0 ? Math.min(1, Math.max(0, reff + 1 - dist[i])) : 0;
891
+ const wgt = band ? Math.max(edge, Math.min(1, Math.max(0, band[i]))) : edge;
892
+ if (wgt > 0) {
893
+ const qi = Math.min(1, Math.max(0, q[i]));
894
+ a[i] = m[i] + (qi - m[i]) * wgt;
895
+ }
896
+ }
897
+ }
898
+ }
899
+ const ss = smoothSigma(p);
900
+ if (ss >= 0.3) {
901
+ a = gaussianBlur(a, w, h, ss, "clamp");
902
+ const gain = 1 + ss;
903
+ for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
904
+ }
905
+ a = gaussianBlur(a, w, h, featherSigma(p), "clamp");
906
+ if (p.contrast > 0) {
907
+ const gain = 1 / (1 - 0.99 * p.contrast / 100);
908
+ for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
909
+ }
910
+ const amount = shiftAmount(p);
911
+ if (Math.abs(amount) >= 1e-4) a = shiftCoverage(a, w, h, amount);
912
+ return a;
913
+ }
914
+ function guidePlanes(pixels, win) {
915
+ const box = win ?? { x: 0, y: 0, width: pixels.width, height: pixels.height };
916
+ const n = box.width * box.height;
917
+ const r = new Float32Array(n);
918
+ const g = new Float32Array(n);
919
+ const b = new Float32Array(n);
920
+ const d = pixels.data;
921
+ for (let y = 0; y < box.height; y++) {
922
+ for (let x = 0; x < box.width; x++) {
923
+ const i = y * box.width + x;
924
+ const p = ((box.y + y) * pixels.width + box.x + x) * 4;
925
+ r[i] = d[p] / 255;
926
+ g[i] = d[p + 1] / 255;
927
+ b[i] = d[p + 2] / 255;
928
+ }
929
+ }
930
+ return [r, g, b];
931
+ }
932
+ function refineSelection(pixels, mask, params = {}, refineBrush = null) {
933
+ const p = normalizeRefineEdgeParams(params);
934
+ if (pixels && (pixels.width !== mask.width || pixels.height !== mask.height)) {
935
+ throw new Error("Refine Edge needs pixels the size of the selection");
936
+ }
937
+ if (!mask.bounds) return mask;
938
+ const brush = refineBrush?.bounds ? refineBrush : null;
939
+ const halo = refineEdgeHalo(p) + (brush ? 2 * Math.max(4, gfRadius(p)) : 0);
940
+ const b = brush ? {
941
+ x: Math.min(mask.bounds.x, brush.bounds.x),
942
+ y: Math.min(mask.bounds.y, brush.bounds.y),
943
+ r: Math.max(mask.bounds.x + mask.bounds.width, brush.bounds.x + brush.bounds.width),
944
+ btm: Math.max(mask.bounds.y + mask.bounds.height, brush.bounds.y + brush.bounds.height)
945
+ } : { x: mask.bounds.x, y: mask.bounds.y, r: mask.bounds.x + mask.bounds.width, btm: mask.bounds.y + mask.bounds.height };
946
+ const x0 = Math.max(0, b.x - halo);
947
+ const y0 = Math.max(0, b.y - halo);
948
+ const x1 = Math.min(mask.width, b.r + halo);
949
+ const y1 = Math.min(mask.height, b.btm + halo);
950
+ const ww = x1 - x0;
951
+ const wh = y1 - y0;
952
+ const m = new Float32Array(ww * wh);
953
+ for (let y = 0; y < wh; y++) {
954
+ const src = (y0 + y) * mask.width + x0;
955
+ for (let x = 0; x < ww; x++) m[y * ww + x] = mask.data[src + x] / 255;
956
+ }
957
+ const guide = pixels && (p.radius > 0 || brush) ? guidePlanes(pixels, { x: x0, y: y0, width: ww, height: wh }) : null;
958
+ let band = null;
959
+ if (brush) {
960
+ band = new Float32Array(ww * wh);
961
+ const bd = brush.data;
962
+ for (let y = 0; y < wh; y++) {
963
+ const src = (y0 + y) * mask.width + x0;
964
+ for (let x = 0; x < ww; x++) band[y * ww + x] = bd[src + x] / 255;
965
+ }
966
+ }
967
+ const refined = refineCoverage(guide, m, ww, wh, p, band);
968
+ const out = new Uint8Array(mask.width * mask.height);
969
+ for (let y = 0; y < wh; y++) {
970
+ const dst = (y0 + y) * mask.width + x0;
971
+ for (let x = 0; x < ww; x++) out[dst + x] = coverageToByte(refined[y * ww + x]);
972
+ }
973
+ return createSelectionMask(mask.width, mask.height, out);
974
+ }
975
+ function decontaminateColors(rgba, coverage, w, h, amount, radius = 8) {
976
+ const out = Uint8ClampedArray.from(rgba);
977
+ const n = w * h;
978
+ const k = Math.min(1, Math.max(0, amount));
979
+ if (k === 0) return out;
980
+ const r = Math.max(2, Math.round(radius));
981
+ const fgW = new Float32Array(n);
982
+ const bgW = new Float32Array(n);
983
+ for (let i = 0; i < n; i++) {
984
+ fgW[i] = coverage[i] >= 250 ? 1 : 0;
985
+ bgW[i] = coverage[i] <= 5 ? 1 : 0;
986
+ }
987
+ const meanOf = (weights, radiusPx) => {
988
+ const wm = boxMean(weights, w, h, radiusPx);
989
+ const planes = [0, 1, 2].map((c) => {
990
+ const v = new Float32Array(n);
991
+ for (let i = 0; i < n; i++) v[i] = rgba[i * 4 + c] * weights[i];
992
+ const m = boxMean(v, w, h, radiusPx);
993
+ for (let i = 0; i < n; i++) m[i] = wm[i] > 1e-6 ? m[i] / wm[i] : NaN;
994
+ return m;
995
+ });
996
+ return { weight: wm, planes };
997
+ };
998
+ const fg = meanOf(fgW, r);
999
+ const fgWide = meanOf(fgW, r * 3);
1000
+ const bg = meanOf(bgW, r);
1001
+ for (let i = 0; i < n; i++) {
1002
+ const c = coverage[i];
1003
+ if (c <= 5 || c >= 250) continue;
1004
+ const a = c / 255;
1005
+ for (let ch = 0; ch < 3; ch++) {
1006
+ const C = rgba[i * 4 + ch];
1007
+ const B = bg.planes[ch][i];
1008
+ const Fm = Number.isNaN(fg.planes[ch][i]) ? fgWide.planes[ch][i] : fg.planes[ch][i];
1009
+ let F;
1010
+ if (a >= 0.5 && !Number.isNaN(B)) F = (C - (1 - a) * B) / a;
1011
+ else if (!Number.isNaN(Fm)) F = Fm;
1012
+ else F = C;
1013
+ out[i * 4 + ch] = Math.round(C + k * (Math.min(255, Math.max(0, F)) - C));
1014
+ }
1015
+ }
1016
+ return out;
1017
+ }
1018
+
727
1019
  export {
728
1020
  SELECTION_MODES,
729
1021
  isSelectionMode,
@@ -759,5 +1051,16 @@ export {
759
1051
  greyMorphology,
760
1052
  shiftCoverage,
761
1053
  SELECTION_MODIFY_OPS,
762
- modifySelection
1054
+ modifySelection,
1055
+ DEFAULT_REFINE_EDGE_PARAMS,
1056
+ GUIDED_FILTER_EPS,
1057
+ normalizeRefineEdgeParams,
1058
+ refineEdgeHalo,
1059
+ boxMean,
1060
+ guidedFilterColor,
1061
+ edgeWidth,
1062
+ refineCoverage,
1063
+ guidePlanes,
1064
+ refineSelection,
1065
+ decontaminateColors
763
1066
  };
@@ -283,6 +283,15 @@ export interface CloneSource {
283
283
  /** A vector mask's path: several `components`, or one `shape` / `subpaths` (with `op`). */
284
284
  /** What `inspectMaskEdge` reads: the selection, a layer's painted mask or its vector mask. */
285
285
  export type MaskEdgeTarget = 'selection' | 'layer-mask' | 'vector-mask';
286
+ /** A layer's edge colour as `getEdgeColor` describes it (percentages). */
287
+ export interface EdgeColorInfo {
288
+ layerId: string;
289
+ decontaminate: number;
290
+ despill: number;
291
+ ambient: number;
292
+ /** `auto` (what is behind the layer, live) or `#rrggbb`. */
293
+ ambientSource: string;
294
+ }
286
295
  export interface VectorMaskPathInput extends ShapeGeometryInput {
287
296
  components?: ShapeGeometryInput[];
288
297
  fillRule?: ShapeFillRule;
@@ -1025,6 +1034,26 @@ export declare class ImageEditorController {
1025
1034
  parentId: string | null;
1026
1035
  index: number;
1027
1036
  };
1037
+ /** A layer's edge colour as percentages (see `setEdgeColor`). */
1038
+ getEdgeColor(id: string): EdgeColorInfo;
1039
+ private requireEdgeLayer;
1040
+ /** Whether the layer's mask (painted and vector, with feather) has a soft band. */
1041
+ private maskHasSoftBand;
1042
+ /**
1043
+ * Edge colour on a layer's soft edge, applied when compositing (nothing is baked); amounts in
1044
+ * percent, 0 turns one off. `decontaminate` unmixes the old background (the pixels the mask
1045
+ * hides) from the edge and `despill` limits its hue there: both need a pixel layer whose mask
1046
+ * (painted or vector) has a soft band. `ambient` pulls the edge toward what is behind the layer
1047
+ * (`ambientColor` null: sampled live) or a fixed `#rrggbb`; it needs a soft mask or soft
1048
+ * pixels at the edge. Validates everything before changing anything; one undo step (none with
1049
+ * `commitToHistory` false, for the later moves of a slider drag). Returns the effective settings.
1050
+ */
1051
+ setEdgeColor(id: string, change: {
1052
+ decontaminate?: number;
1053
+ despill?: number;
1054
+ ambient?: number;
1055
+ ambientColor?: string | null;
1056
+ }, commitToHistory?: boolean): EdgeColorInfo;
1028
1057
  /**
1029
1058
  * Changes name, visibility, opacity, fill, blend, clipping, locks or (groups) collapsed state, as
1030
1059
  * one undo step (`commitToHistory = false` for the moves of a slider drag). Returns the layer.
@@ -283,6 +283,15 @@ export interface CloneSource {
283
283
  /** A vector mask's path: several `components`, or one `shape` / `subpaths` (with `op`). */
284
284
  /** What `inspectMaskEdge` reads: the selection, a layer's painted mask or its vector mask. */
285
285
  export type MaskEdgeTarget = 'selection' | 'layer-mask' | 'vector-mask';
286
+ /** A layer's edge colour as `getEdgeColor` describes it (percentages). */
287
+ export interface EdgeColorInfo {
288
+ layerId: string;
289
+ decontaminate: number;
290
+ despill: number;
291
+ ambient: number;
292
+ /** `auto` (what is behind the layer, live) or `#rrggbb`. */
293
+ ambientSource: string;
294
+ }
286
295
  export interface VectorMaskPathInput extends ShapeGeometryInput {
287
296
  components?: ShapeGeometryInput[];
288
297
  fillRule?: ShapeFillRule;
@@ -1025,6 +1034,26 @@ export declare class ImageEditorController {
1025
1034
  parentId: string | null;
1026
1035
  index: number;
1027
1036
  };
1037
+ /** A layer's edge colour as percentages (see `setEdgeColor`). */
1038
+ getEdgeColor(id: string): EdgeColorInfo;
1039
+ private requireEdgeLayer;
1040
+ /** Whether the layer's mask (painted and vector, with feather) has a soft band. */
1041
+ private maskHasSoftBand;
1042
+ /**
1043
+ * Edge colour on a layer's soft edge, applied when compositing (nothing is baked); amounts in
1044
+ * percent, 0 turns one off. `decontaminate` unmixes the old background (the pixels the mask
1045
+ * hides) from the edge and `despill` limits its hue there: both need a pixel layer whose mask
1046
+ * (painted or vector) has a soft band. `ambient` pulls the edge toward what is behind the layer
1047
+ * (`ambientColor` null: sampled live) or a fixed `#rrggbb`; it needs a soft mask or soft
1048
+ * pixels at the edge. Validates everything before changing anything; one undo step (none with
1049
+ * `commitToHistory` false, for the later moves of a slider drag). Returns the effective settings.
1050
+ */
1051
+ setEdgeColor(id: string, change: {
1052
+ decontaminate?: number;
1053
+ despill?: number;
1054
+ ambient?: number;
1055
+ ambientColor?: string | null;
1056
+ }, commitToHistory?: boolean): EdgeColorInfo;
1028
1057
  /**
1029
1058
  * Changes name, visibility, opacity, fill, blend, clipping, locks or (groups) collapsed state, as
1030
1059
  * one undo step (`commitToHistory = false` for the moves of a slider drag). Returns the layer.