@pixerate/editor 0.214.0 → 0.216.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 (40) 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 +41 -0
  10. package/dist/image-editor/controller.d.ts +41 -0
  11. package/dist/image-editor/index.cjs +1461 -1125
  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 +384 -48
  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/index.d.cts +1 -0
  20. package/dist/image-editor/selection/index.d.ts +1 -0
  21. package/dist/image-editor/selection/inspect.d.cts +9 -0
  22. package/dist/image-editor/selection/inspect.d.ts +9 -0
  23. package/dist/image-editor/selection/uncertainty.d.cts +40 -0
  24. package/dist/image-editor/selection/uncertainty.d.ts +40 -0
  25. package/dist/image-editor/selection-worker.cjs +4 -4
  26. package/dist/image-editor/selection-worker.js +2 -2
  27. package/dist/paint-editor/index.cjs +79 -79
  28. package/dist/paint-editor/index.js +3 -3
  29. package/dist/raster/compositor.d.cts +10 -1
  30. package/dist/raster/compositor.d.ts +10 -1
  31. package/dist/raster/deep-compositor.d.cts +1 -1
  32. package/dist/raster/deep-compositor.d.ts +1 -1
  33. package/dist/raster/index.cjs +4 -2
  34. package/dist/raster/index.js +3 -1
  35. package/dist/raster/layers.d.cts +16 -0
  36. package/dist/raster/layers.d.ts +16 -0
  37. package/package.json +1 -1
  38. package/skills/pixerate-image-editor/SKILL.md +9 -7
  39. package/skills/pixerate-image-editor/references/paths.md +12 -0
  40. package/skills/pixerate-image-selection-masking/SKILL.md +11 -11
@@ -2,12 +2,14 @@ import {
2
2
  parseCssColor
3
3
  } from "./chunk-3CI7FRMK.js";
4
4
  import {
5
+ boxMean,
5
6
  createSelectionMask,
6
7
  decodeSelectionRle,
8
+ decontaminateColors,
7
9
  encodeSelectionRle,
8
10
  hashMaskContent,
9
11
  resizeSelection
10
- } from "./chunk-L3G23N5Y.js";
12
+ } from "./chunk-ZNA3IQZF.js";
11
13
  import {
12
14
  DEFAULT_GLOBAL_LIGHT,
13
15
  DeepCompositor,
@@ -43,7 +45,7 @@ import {
43
45
  validateEffectGradient,
44
46
  validateLayerEffects,
45
47
  warpSurface
46
- } from "./chunk-YBJXCQTR.js";
48
+ } from "./chunk-QXY4YTRI.js";
47
49
  import {
48
50
  affineFromHomography,
49
51
  applyHomography,
@@ -3419,6 +3421,68 @@ function unionRect(a, b) {
3419
3421
  return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
3420
3422
  }
3421
3423
 
3424
+ // src/image-editor/layers/edge-color.ts
3425
+ var EDGE_COLOR_RADIUS = 8;
3426
+ function hasEdgeUnmix(edge) {
3427
+ return Boolean(edge && ((edge.decontaminate ?? 0) > 0 || (edge.despill ?? 0) > 0));
3428
+ }
3429
+ function maskCoverageOverCanvas(mask, ox, oy, width, height) {
3430
+ const bytes = readRegion(mask.surface, { x: -ox, y: -oy, width, height });
3431
+ const feather = mask.feather ?? 0;
3432
+ if (!(feather > 0)) return new Uint8ClampedArray(bytes.buffer, bytes.byteOffset, bytes.length);
3433
+ const v = new Float32Array(bytes.length);
3434
+ for (let i = 0; i < v.length; i++) v[i] = bytes[i];
3435
+ const blurred = gaussianBlur(v, width, height, feather, "clamp");
3436
+ return Uint8ClampedArray.from(blurred);
3437
+ }
3438
+ function hasSoftBand(coverage) {
3439
+ for (let i = 0; i < coverage.length; i++) if (coverage[i] > 5 && coverage[i] < 250) return true;
3440
+ return false;
3441
+ }
3442
+ function despillEdge(rgba, coverage, w, h, amount, radius = EDGE_COLOR_RADIUS) {
3443
+ const k = Math.min(1, Math.max(0, amount));
3444
+ if (!(k > 0)) return;
3445
+ const n = w * h;
3446
+ const out = new Float32Array(n);
3447
+ for (let i = 0; i < n; i++) out[i] = coverage[i] <= 5 ? 1 : 0;
3448
+ const weight = boxMean(out, w, h, radius);
3449
+ const mean = [0, 1, 2].map((c) => {
3450
+ const v = new Float32Array(n);
3451
+ for (let i = 0; i < n; i++) v[i] = rgba[i * 4 + c] * out[i];
3452
+ return boxMean(v, w, h, radius);
3453
+ });
3454
+ const luma = (r, g, b) => 0.2126 * r + 0.7152 * g + 0.0722 * b;
3455
+ for (let i = 0; i < n; i++) {
3456
+ const c = coverage[i];
3457
+ if (c <= 5 || c >= 250 || weight[i] <= 1e-6) continue;
3458
+ const br = mean[0][i] / weight[i];
3459
+ const bg = mean[1][i] / weight[i];
3460
+ const bb = mean[2][i] / weight[i];
3461
+ const bl = luma(br, bg, bb);
3462
+ let ux = br - bl;
3463
+ let uy = bg - bl;
3464
+ let uz = bb - bl;
3465
+ const len = Math.hypot(ux, uy, uz);
3466
+ if (len < 4) continue;
3467
+ ux /= len;
3468
+ uy /= len;
3469
+ uz /= len;
3470
+ const p = i * 4;
3471
+ const l = luma(rgba[p], rgba[p + 1], rgba[p + 2]);
3472
+ const along = (rgba[p] - l) * ux + (rgba[p + 1] - l) * uy + (rgba[p + 2] - l) * uz;
3473
+ if (along <= 0) continue;
3474
+ const s = k * Math.min(1, 2 * (1 - c / 255)) * along;
3475
+ rgba[p] -= s * ux;
3476
+ rgba[p + 1] -= s * uy;
3477
+ rgba[p + 2] -= s * uz;
3478
+ }
3479
+ }
3480
+ function applyEdgeUnmix(rgba, coverage, w, h, edge) {
3481
+ const d = edge.decontaminate ?? 0;
3482
+ if (d > 0) rgba.set(decontaminateColors(rgba, coverage, w, h, d, EDGE_COLOR_RADIUS));
3483
+ despillEdge(rgba, coverage, w, h, edge.despill ?? 0);
3484
+ }
3485
+
3422
3486
  // src/image-editor/layers/compositor.ts
3423
3487
  var legacyViewCache = /* @__PURE__ */ new WeakMap();
3424
3488
  function adjustmentNodeAsLayer(node) {
@@ -3496,6 +3560,31 @@ function filterReach(filters) {
3496
3560
  return r;
3497
3561
  }
3498
3562
  var filterSpecCache = /* @__PURE__ */ new WeakMap();
3563
+ var maskIds = /* @__PURE__ */ new WeakMap();
3564
+ var nextMaskId = 1;
3565
+ var edgeSpecCache = /* @__PURE__ */ new WeakMap();
3566
+ function edgeFilterSpec(layer) {
3567
+ const edge = layer.edge;
3568
+ const mask = layer.mask;
3569
+ if (!hasEdgeUnmix(edge) || !mask || mask.enabled === false || mask.source) return pixelFilterSpec(layer);
3570
+ const hit = edgeSpecCache.get(layer);
3571
+ if (hit) return hit;
3572
+ let id = maskIds.get(mask.surface);
3573
+ if (id === void 0) maskIds.set(mask.surface, id = nextMaskId++);
3574
+ const filters = pixelFilterSpec(layer);
3575
+ const ox = layer.offset.x;
3576
+ const oy = layer.offset.y;
3577
+ const spec = {
3578
+ key: `edge:${JSON.stringify([edge.decontaminate ?? 0, edge.despill ?? 0, id, ox, oy, mask.feather ?? 0])}|${filters?.key ?? ""}`,
3579
+ spatial: true,
3580
+ apply: (data, width, height) => {
3581
+ filters?.apply(data, width, height);
3582
+ applyEdgeUnmix(data, maskCoverageOverCanvas(mask, ox, oy, width, height), width, height, edge);
3583
+ }
3584
+ };
3585
+ edgeSpecCache.set(layer, spec);
3586
+ return spec;
3587
+ }
3499
3588
  function pixelFilterSpec(layer) {
3500
3589
  const list2 = layer.filters;
3501
3590
  if (!list2?.length) return null;
@@ -3523,7 +3612,7 @@ var PHOTO_PROFILE = {
3523
3612
  /** Annotation layers draw with their annotation's blend mode unless the layer sets one. */
3524
3613
  contentBlend: (l) => l.type === "annotation" && l.blend === "normal" && l.annotation.blend ? l.annotation.blend : l.blend,
3525
3614
  layerKey: (l) => l.type === "pixel" ? [l.filters ?? null, l.source ?? null] : l.type === "adjustment" ? [l.kind, l.params] : l.type === "annotation" ? l.annotation : void 0,
3526
- pixelFilters: (l) => l.type === "pixel" ? pixelFilterSpec(l) : null,
3615
+ pixelFilters: (l) => l.type === "pixel" ? edgeFilterSpec(l) : null,
3527
3616
  /**
3528
3617
  * A smart object draws its rendered pixels (contents → placement → smart filters) like a pixel
3529
3618
  * layer, and a shape layer the pixels rendered from its paths.
@@ -3557,7 +3646,7 @@ function compositeDeep(layers, rect, sources, depth) {
3557
3646
  const deep = new DeepCompositor(new LayerCompositor(), PHOTO_PROFILE, {
3558
3647
  applyFilterRunDeep: (run, acc, _r, masks) => applyAdjustmentLayersFloat(acc, run.map(adjustmentNodeAsLayer), masks),
3559
3648
  pixelFiltersDeep: (l) => {
3560
- if (l.type !== "pixel") return null;
3649
+ if (l.type !== "pixel" || hasEdgeUnmix(l.edge)) return null;
3561
3650
  const filters = (l.filters ?? []).filter(isActiveFilter);
3562
3651
  if (!filters.length) return null;
3563
3652
  return { reach: filterReach(filters), apply: (data, width, height, linear) => runLayerFiltersFloat(data, width, height, filters, linear) };
@@ -5136,8 +5225,21 @@ function baseFields(layer, ctx) {
5136
5225
  if (layer.vectorMask) out.vectorMask = serializeVectorMask(layer.vectorMask);
5137
5226
  if (layer.locks && Object.values(layer.locks).some(Boolean)) out.locks = { ...layer.locks };
5138
5227
  if (layer.effects) out.effects = serializeEffects(layer.effects);
5228
+ if (layer.edge) out.edge = { ...layer.edge, ...layer.edge.ambientColor ? { ambientColor: [...layer.edge.ambientColor] } : {} };
5139
5229
  return out;
5140
5230
  }
5231
+ function readEdgeColor(raw) {
5232
+ if (!raw || typeof raw !== "object") return void 0;
5233
+ const r = raw;
5234
+ const out = {};
5235
+ for (const k of ["decontaminate", "despill", "ambient"]) {
5236
+ const v = r[k];
5237
+ if (typeof v === "number" && Number.isFinite(v) && v > 0) out[k] = Math.min(1, v);
5238
+ }
5239
+ const c = r.ambientColor;
5240
+ if (Array.isArray(c) && c.length === 3 && c.every((v) => typeof v === "number" && Number.isFinite(v))) out.ambientColor = c.map((v) => Math.min(255, Math.max(0, Math.round(v))));
5241
+ return out.decontaminate || out.despill || out.ambient || out.ambientColor ? out : void 0;
5242
+ }
5141
5243
  function serializeEffects(effects) {
5142
5244
  const out = { enabled: effects.enabled, items: JSON.parse(JSON.stringify(effects.items)) };
5143
5245
  if (effects.scale !== void 0 && effects.scale !== 1) out.scale = effects.scale;
@@ -5597,6 +5699,8 @@ function deserializeLayer(raw, ctx) {
5597
5699
  blend: normalizeBlendMode(r.blend)
5598
5700
  };
5599
5701
  if (r.clipped === true) base.clipped = true;
5702
+ const edge = readEdgeColor(r.edge);
5703
+ if (edge) base.edge = edge;
5600
5704
  const mask = readMask(r.mask, ctx, `Layer "${base.name || id}"`);
5601
5705
  if (mask) base.mask = mask;
5602
5706
  if (r.vectorMask && typeof r.vectorMask === "object" && r.type !== "shape") {
@@ -11551,6 +11655,12 @@ export {
11551
11655
  followLayerMove,
11552
11656
  vectorMaskBounds,
11553
11657
  vectorMaskThumbnail,
11658
+ EDGE_COLOR_RADIUS,
11659
+ hasEdgeUnmix,
11660
+ maskCoverageOverCanvas,
11661
+ hasSoftBand,
11662
+ despillEdge,
11663
+ applyEdgeUnmix,
11554
11664
  adjustmentNodeAsLayer,
11555
11665
  depthMaskAlpha,
11556
11666
  runLayerFilters,
@@ -11668,6 +11778,7 @@ export {
11668
11778
  encodeSurface,
11669
11779
  encodeDeepSurface,
11670
11780
  decodeDeepSurface,
11781
+ readEdgeColor,
11671
11782
  serializeEffects,
11672
11783
  deserializeEffects,
11673
11784
  readShapeComponents,