@pixerate/editor 0.188.0 → 0.190.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 (41) hide show
  1. package/dist/{chunk-IGMNJM2H.js → chunk-7ZWLDVFH.js} +979 -753
  2. package/dist/{chunk-JYV4R62M.cjs → chunk-T4U4O6DS.cjs} +1013 -787
  3. package/dist/image-editor/controller.d.cts +68 -1
  4. package/dist/image-editor/controller.d.ts +68 -1
  5. package/dist/image-editor/free-transform.d.cts +3 -6
  6. package/dist/image-editor/free-transform.d.ts +3 -6
  7. package/dist/image-editor/index.cjs +1867 -1413
  8. package/dist/image-editor/index.d.cts +1 -0
  9. package/dist/image-editor/index.d.ts +1 -0
  10. package/dist/image-editor/index.js +869 -415
  11. package/dist/image-editor/layer-tools.d.cts +1 -1
  12. package/dist/image-editor/layer-tools.d.ts +1 -1
  13. package/dist/image-editor/layers/compositor.d.cts +3 -0
  14. package/dist/image-editor/layers/compositor.d.ts +3 -0
  15. package/dist/image-editor/layers/model.d.cts +23 -0
  16. package/dist/image-editor/layers/model.d.ts +23 -0
  17. package/dist/image-editor/layers/paint-ui.d.cts +2 -1
  18. package/dist/image-editor/layers/paint-ui.d.ts +2 -1
  19. package/dist/image-editor/layers/panel.d.cts +14 -3
  20. package/dist/image-editor/layers/panel.d.ts +14 -3
  21. package/dist/image-editor/layers/serialize.d.cts +11 -0
  22. package/dist/image-editor/layers/serialize.d.ts +11 -0
  23. package/dist/image-editor/layers/shape.d.cts +5 -0
  24. package/dist/image-editor/layers/shape.d.ts +5 -0
  25. package/dist/image-editor/layers/vector-mask.d.cts +51 -0
  26. package/dist/image-editor/layers/vector-mask.d.ts +51 -0
  27. package/dist/image-editor/psd/import.d.cts +0 -7
  28. package/dist/image-editor/psd/import.d.ts +0 -7
  29. package/dist/image-editor/renderer.d.cts +6 -0
  30. package/dist/image-editor/renderer.d.ts +6 -0
  31. package/dist/image-editor/selection/gesture.d.cts +10 -0
  32. package/dist/image-editor/selection/gesture.d.ts +10 -0
  33. package/dist/image-editor/selection/layer-mask.d.cts +6 -0
  34. package/dist/image-editor/selection/layer-mask.d.ts +6 -0
  35. package/dist/image-editor/types.d.cts +8 -0
  36. package/dist/image-editor/types.d.ts +8 -0
  37. package/dist/paint-editor/index.cjs +6 -6
  38. package/dist/paint-editor/index.js +1 -1
  39. package/package.json +1 -1
  40. package/skills/pixerate-image-editor/SKILL.md +7 -0
  41. package/skills/pixerate-image-selection-masking/SKILL.md +10 -3
@@ -1421,6 +1421,13 @@ function modifySelection(mask, op, radius) {
1421
1421
  }
1422
1422
 
1423
1423
  // src/image-editor/selection/layer-mask.ts
1424
+ function layerMaskSelection(layer, width, height) {
1425
+ if (!layer.mask || layer.mask.source) return null;
1426
+ const ox = layer.type === "pixel" ? layer.offset.x : 0;
1427
+ const oy = layer.type === "pixel" ? layer.offset.y : 0;
1428
+ const data = readRegion(layer.mask.surface, { x: -ox, y: -oy, width, height });
1429
+ return createSelectionMask(width, height, new Uint8Array(data.buffer, data.byteOffset, data.length));
1430
+ }
1424
1431
  var MAX_SIDE2 = 1 << 15;
1425
1432
  function normalizeLayerMask(raw) {
1426
1433
  if (!raw || typeof raw !== "object") return null;
@@ -2582,192 +2589,6 @@ function removeAlpha(imageData) {
2582
2589
  }
2583
2590
  }
2584
2591
 
2585
- // src/text/types.ts
2586
- var TEXT_ALIGNS = ["left", "center", "right", "justify"];
2587
-
2588
- // src/image-editor/layers/model.ts
2589
- var MAX_EFFECT_ANIMATION_MS = 6e4;
2590
- var SHAPE_OPERATIONS = ["combine", "subtract", "intersect", "exclude"];
2591
- var LIVE_SHAPE_KINDS = ["rect", "ellipse", "polygon", "star", "line"];
2592
- var idCounter = 0;
2593
- function newLayerId(prefix = "layer") {
2594
- return `${prefix}_${Date.now().toString(36)}_${(++idCounter).toString(36)}`;
2595
- }
2596
- function pixelLayers(layers) {
2597
- return flattenLayers(layers).filter((l) => l.type === "pixel");
2598
- }
2599
- function developFilter(layer) {
2600
- return layer.filters?.find((f) => f.kind === "develop");
2601
- }
2602
- function blurFilter(layer) {
2603
- return layer.filters?.find((f) => f.kind === "blur");
2604
- }
2605
- function isActiveFilter(f) {
2606
- if (!f.enabled) return false;
2607
- if (f.kind === "blur") return Math.min(Math.round(f.radius), 40) >= 1;
2608
- return Boolean(f.brightness || f.contrast || f.saturation || f.exposure || f.temperature || f.tint);
2609
- }
2610
- function isSpatialFilter(f) {
2611
- return f.kind === "blur";
2612
- }
2613
- function pixelLayerBounds(layer, surface = layer.surface) {
2614
- const b = surface.bounds;
2615
- if (!b) return null;
2616
- return { x: b.x + layer.offset.x, y: b.y + layer.offset.y, width: b.width, height: b.height };
2617
- }
2618
- function layerTypeLabel(type) {
2619
- return type === "pixel" ? "Pixel layer" : type === "adjustment" ? "Adjustment layer" : type === "group" ? "Group" : type === "smart" ? "Smart object" : type === "shape" ? "Shape layer" : type === "type" ? "Type layer" : "Annotation";
2620
- }
2621
- function hasSurface(layer) {
2622
- return layer.type === "pixel" || layer.type === "smart" || layer.type === "shape" || layer.type === "type";
2623
- }
2624
- function isRenderedLayer(layer) {
2625
- return layer.type === "smart" || layer.type === "shape" || layer.type === "type";
2626
- }
2627
- function shapeLayers(layers) {
2628
- return flattenLayers(layers).filter((l) => l.type === "shape");
2629
- }
2630
- function smartObjectLayers(layers) {
2631
- return flattenLayers(layers).filter((l) => l.type === "smart");
2632
- }
2633
-
2634
- // src/image-editor/layers/compositor.ts
2635
- var legacyViewCache = /* @__PURE__ */ new WeakMap();
2636
- function adjustmentNodeAsLayer(node) {
2637
- let view = legacyViewCache.get(node);
2638
- if (!view) {
2639
- view = {
2640
- id: node.id,
2641
- kind: node.kind,
2642
- enabled: node.visible,
2643
- opacity: node.opacity,
2644
- blend: node.blend,
2645
- params: node.params
2646
- };
2647
- legacyViewCache.set(node, view);
2648
- }
2649
- return view;
2650
- }
2651
- function developAdjustments(f) {
2652
- return {
2653
- brightness: f.brightness,
2654
- contrast: f.contrast,
2655
- saturation: f.saturation,
2656
- exposure: f.exposure,
2657
- temperature: f.temperature,
2658
- tint: f.tint ?? 0,
2659
- blur: 0,
2660
- opacity: 1,
2661
- process: f.process
2662
- };
2663
- }
2664
- function applyPixelFilters(data, filters) {
2665
- for (const f of filters) {
2666
- if (!isActiveFilter(f) || f.kind !== "develop") continue;
2667
- const adj = developAdjustments(f);
2668
- if (normalizeProcessVersion(adj.process) === LATEST_PROCESS_VERSION) applyProcess2Adjustments({ data }, adj);
2669
- else applyLegacyAdjustments({ data }, adj);
2670
- }
2671
- }
2672
- function depthMaskAlpha(depth, options) {
2673
- const { mode, depthRange, softness, invert } = options;
2674
- let v = invert ? 255 - depth : depth;
2675
- const soft = Math.max(1, softness || 10);
2676
- const [lo, hi] = depthRange;
2677
- if (mode === "threshold") {
2678
- if (v >= hi + soft) return 0;
2679
- if (v > hi) return Math.round((hi + soft - v) / soft * 255);
2680
- if (v >= lo) return 255;
2681
- if (v < lo - soft) return 0;
2682
- return Math.round((v - (lo - soft)) / soft * 255);
2683
- }
2684
- v += lo;
2685
- return v < 0 ? 0 : v > 255 ? 255 : v;
2686
- }
2687
- function runLayerFilters(data, width, height, filters) {
2688
- for (const f of filters) {
2689
- if (!isActiveFilter(f)) continue;
2690
- if (f.kind === "develop") applyPixelFilters(data, [f]);
2691
- else if (f.kind === "blur") applyBoxBlur({ width, height, data }, Math.min(Math.round(f.radius), 40));
2692
- }
2693
- }
2694
- function runLayerFiltersFloat(data, width, height, filters, linear) {
2695
- for (const f of filters) {
2696
- if (!isActiveFilter(f)) continue;
2697
- if (f.kind === "blur") applyBoxBlurFloat({ width, height, data }, Math.min(Math.round(f.radius), 40));
2698
- else if (f.kind === "develop") {
2699
- const adj = developAdjustments(f);
2700
- if (normalizeProcessVersion(adj.process) === LATEST_PROCESS_VERSION) applyProcess2Float(data, adj, linear);
2701
- else applyLegacyFloat(data, adj, linear);
2702
- }
2703
- }
2704
- }
2705
- function filterReach(filters) {
2706
- let r = 0;
2707
- for (const f of filters) if (isActiveFilter(f) && f.kind === "blur") r += Math.min(Math.round(f.radius), 40);
2708
- return r;
2709
- }
2710
- var filterSpecCache = /* @__PURE__ */ new WeakMap();
2711
- function pixelFilterSpec(layer) {
2712
- const list2 = layer.filters;
2713
- if (!list2?.length) return null;
2714
- if (filterSpecCache.has(list2)) return filterSpecCache.get(list2);
2715
- const filters = list2.filter(isActiveFilter);
2716
- let spec = null;
2717
- if (filters.length) {
2718
- const spatial = filters.some(isSpatialFilter);
2719
- spec = {
2720
- key: JSON.stringify(filters),
2721
- spatial,
2722
- apply: (data, width, height) => runLayerFilters(data, width, height, filters)
2723
- };
2724
- }
2725
- filterSpecCache.set(list2, spec);
2726
- return spec;
2727
- }
2728
- var PHOTO_PROFILE = {
2729
- isFilterLayer: (l) => l.type === "adjustment",
2730
- applyFilterRun: (layers, acc, _r, masks) => {
2731
- const views = layers.map(adjustmentNodeAsLayer);
2732
- applyAdjustmentLayers({ data: acc }, views, masks);
2733
- },
2734
- contentTile: (l, r, sources) => l.type === "annotation" ? sources.rasterizeAnnotation?.(l.annotation, r) ?? null : null,
2735
- /** Annotation layers draw with their annotation's blend mode unless the layer sets one. */
2736
- contentBlend: (l) => l.type === "annotation" && l.blend === "normal" && l.annotation.blend ? l.annotation.blend : l.blend,
2737
- 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,
2738
- pixelFilters: (l) => l.type === "pixel" ? pixelFilterSpec(l) : null,
2739
- /**
2740
- * A smart object draws its rendered pixels (contents → placement → smart filters) like a pixel
2741
- * layer, and a shape layer the pixels rendered from its paths.
2742
- */
2743
- isPixelLike: (l) => l.type === "smart" || l.type === "shape" || l.type === "type",
2744
- maskSourcePlane: (source, W, H, sources) => {
2745
- const opts = source;
2746
- const depth = sources.depthMap?.(opts) ?? null;
2747
- if (!depth || depth.width !== W || depth.height !== H) return null;
2748
- const v = new Float32Array(W * H);
2749
- for (let i = 0; i < v.length; i++) v[i] = depthMaskAlpha(depth.data[i * 4], opts) / 255;
2750
- return v;
2751
- }
2752
- };
2753
- var LayerCompositor = class extends RasterCompositor {
2754
- constructor(options = {}) {
2755
- super(PHOTO_PROFILE, options);
2756
- }
2757
- };
2758
- function compositeDeep(layers, rect, sources, depth) {
2759
- const deep = new DeepCompositor(new LayerCompositor(), PHOTO_PROFILE, {
2760
- applyFilterRunDeep: (run, acc, _r, masks) => applyAdjustmentLayersFloat(acc, run.map(adjustmentNodeAsLayer), masks),
2761
- pixelFiltersDeep: (l) => {
2762
- if (l.type !== "pixel") return null;
2763
- const filters = (l.filters ?? []).filter(isActiveFilter);
2764
- if (!filters.length) return null;
2765
- return { reach: filterReach(filters), apply: (data, width, height, linear) => runLayerFiltersFloat(data, width, height, filters, linear) };
2766
- }
2767
- }, depth);
2768
- return deep.composite(layers, rect, sources);
2769
- }
2770
-
2771
2592
  // src/image-editor/layers/vector-raster.ts
2772
2593
  var FLATNESS = 0.01;
2773
2594
  function flattenPath(path, tolerance = FLATNESS) {
@@ -3101,439 +2922,143 @@ function combineCoverage(a, b, op, n) {
3101
2922
  return out;
3102
2923
  }
3103
2924
 
3104
- // src/image-editor/layers/ops.ts
3105
- var LayerOperationError = class extends Error {
3106
- userMessage;
3107
- constructor(message, userMessage) {
3108
- super(message);
3109
- this.name = "LayerOperationError";
3110
- if (userMessage) this.userMessage = userMessage;
3111
- }
3112
- };
3113
- function userErrorMessage(err) {
3114
- if (err instanceof LayerOperationError && err.userMessage) return err.userMessage;
3115
- return err instanceof Error ? err.message : String(err);
3116
- }
3117
- function isPaintableLayer(layer) {
3118
- return layer.type === "pixel" && layer.visible && !isLayerLocked(layer, "pixels");
2925
+ // src/text/types.ts
2926
+ var TEXT_ALIGNS = ["left", "center", "right", "justify"];
2927
+
2928
+ // src/image-editor/layers/model.ts
2929
+ var MAX_EFFECT_ANIMATION_MS = 6e4;
2930
+ var SHAPE_OPERATIONS = ["combine", "subtract", "intersect", "exclude"];
2931
+ var LIVE_SHAPE_KINDS = ["rect", "ellipse", "polygon", "star", "line"];
2932
+ var idCounter = 0;
2933
+ function newLayerId(prefix = "layer") {
2934
+ return `${prefix}_${Date.now().toString(36)}_${(++idCounter).toString(36)}`;
3119
2935
  }
3120
- function defaultActiveLayerId(layers) {
3121
- const pixels = flattenLayers(layers).filter((l) => l.type === "pixel");
3122
- for (let i = pixels.length - 1; i >= 0; i--) if (isPaintableLayer(pixels[i])) return pixels[i].id;
3123
- return pixels[pixels.length - 1]?.id ?? null;
2936
+ function pixelLayers(layers) {
2937
+ return flattenLayers(layers).filter((l) => l.type === "pixel");
3124
2938
  }
3125
- function activeLayerAfterRemoval(prev, next, removedId) {
3126
- const before = flattenLayers(prev);
3127
- const at = before.findIndex((l) => l.id === removedId);
3128
- for (let i = at - 1; i >= 0; i--) {
3129
- const loc = findLayer(next, before[i].id);
3130
- if (loc && isPaintableLayer(loc.layer)) return loc.layer.id;
3131
- }
3132
- return defaultActiveLayerId(next);
2939
+ function developFilter(layer) {
2940
+ return layer.filters?.find((f) => f.kind === "develop");
3133
2941
  }
3134
- var fail = (message) => {
3135
- throw new LayerOperationError(message);
3136
- };
3137
- function requireLayer(layers, id) {
3138
- const loc = findLayer(layers, id);
3139
- if (!loc) {
3140
- const ids = flattenLayers(layers).map((l) => l.id);
3141
- fail(`Unknown layer "${id}". ${ids.length ? `Layers: ${ids.join(", ")}.` : "The document has no layers."} Call list_layers for ids.`);
3142
- }
3143
- return loc;
2942
+ function blurFilter(layer) {
2943
+ return layer.filters?.find((f) => f.kind === "blur");
3144
2944
  }
3145
- function requireUnlocked(layer, what, action) {
3146
- if (isLayerLocked(layer, "all")) fail(`Layer "${layer.name || layer.id}" is locked (all); unlock it before you ${action}.`);
3147
- if (what !== "all" && isLayerLocked(layer, what)) fail(`Layer "${layer.name || layer.id}" has its ${what} locked; unlock it before you ${action}.`);
2945
+ function isActiveFilter(f) {
2946
+ if (!f.enabled) return false;
2947
+ if (f.kind === "blur") return Math.min(Math.round(f.radius), 40) >= 1;
2948
+ return Boolean(f.brightness || f.contrast || f.saturation || f.exposure || f.temperature || f.tint);
3148
2949
  }
3149
- function placeLayer(layers, layer, placement = {}, defaultIndex) {
3150
- const given = [placement.aboveId, placement.belowId, placement.parentId].filter((v) => v !== void 0).length;
3151
- if (given > 1) fail("Give only one of aboveId, belowId and parentId.");
3152
- if (placement.aboveId || placement.belowId) {
3153
- const ref = requireLayer(layers, placement.aboveId ?? placement.belowId);
3154
- return insertLayer(layers, layer, ref.parent?.id ?? null, ref.index + (placement.aboveId ? 1 : 0));
3155
- }
3156
- if (placement.parentId) {
3157
- const ref = requireLayer(layers, placement.parentId);
3158
- if (ref.layer.type !== "group") fail(`Layer "${placement.parentId}" is not a group; parentId must be a group id.`);
3159
- return insertLayer(layers, layer, ref.layer.id, placement.index);
3160
- }
3161
- return insertLayer(layers, layer, null, placement.index ?? defaultIndex);
2950
+ function isSpatialFilter(f) {
2951
+ return f.kind === "blur";
3162
2952
  }
3163
- function removeLayer(layers, id) {
3164
- const loc = requireLayer(layers, id);
3165
- requireUnlocked(loc.layer, "all", "delete it");
3166
- return mapLayer(layers, id, () => null);
3167
- }
3168
- function moveLayer(layers, id, placement) {
3169
- const loc = requireLayer(layers, id);
3170
- requireUnlocked(loc.layer, "all", "move it");
3171
- const target = placement.aboveId ?? placement.belowId ?? placement.parentId;
3172
- if (target === id) fail("A layer cannot be placed relative to itself.");
3173
- if (target && loc.layer.type === "group" && flattenLayers(loc.layer.children).some((l) => l.id === target)) {
3174
- fail(`Group "${id}" cannot be moved into its own child "${target}".`);
3175
- }
3176
- if (target) requireLayer(layers, target);
3177
- if (!target && placement.index === void 0) fail("Give aboveId, belowId, parentId or index.");
3178
- const without = mapLayer(layers, id, () => null);
3179
- return placeLayer(without, loc.layer, placement);
2953
+ function pixelLayerBounds(layer, surface = layer.surface) {
2954
+ const b = surface.bounds;
2955
+ if (!b) return null;
2956
+ return { x: b.x + layer.offset.x, y: b.y + layer.offset.y, width: b.width, height: b.height };
3180
2957
  }
3181
- function withNewIds(layer) {
3182
- const next = { ...layer, id: newLayerId(layer.type === "adjustment" ? "adj" : layer.type) };
3183
- if (next.type === "group") next.children = next.children.map(withNewIds);
3184
- if (next.type === "annotation") next.annotation = { ...next.annotation, id: next.id };
3185
- if (next.type === "shape" || next.type === "type") delete next.annotation;
3186
- if (next.type === "smart" && next.psd?.placed) {
3187
- next.psd = { ...next.psd };
3188
- delete next.psd.placed;
3189
- }
3190
- return next;
2958
+ function layerTypeLabel(type) {
2959
+ return type === "pixel" ? "Pixel layer" : type === "adjustment" ? "Adjustment layer" : type === "group" ? "Group" : type === "smart" ? "Smart object" : type === "shape" ? "Shape layer" : type === "type" ? "Type layer" : "Annotation";
3191
2960
  }
3192
- function duplicateLayer(layers, id, name) {
3193
- const loc = requireLayer(layers, id);
3194
- const copy = withNewIds(loc.layer);
3195
- copy.name = name ?? `${loc.layer.name || "Layer"} copy`;
3196
- delete copy.locks;
3197
- return { layers: insertLayer(layers, copy, loc.parent?.id ?? null, loc.index + 1), id: copy.id };
2961
+ function hasSurface(layer) {
2962
+ return layer.type === "pixel" || layer.type === "smart" || layer.type === "shape" || layer.type === "type";
3198
2963
  }
3199
- function setLayerProperties(layers, id, props) {
3200
- const loc = requireLayer(layers, id);
3201
- const layer = loc.layer;
3202
- const onlyUnlocking = Object.keys(props).every((k) => k === "locks" || k === "visible" || k === "collapsed");
3203
- if (!onlyUnlocking) requireUnlocked(layer, "all", "change its properties");
3204
- const next = { ...layer };
3205
- if (props.name !== void 0) {
3206
- if (typeof props.name !== "string" || !props.name.trim()) fail("name must be a non-empty string.");
3207
- next.name = props.name.trim();
3208
- }
3209
- if (props.visible !== void 0) next.visible = Boolean(props.visible);
3210
- if (props.opacity !== void 0) {
3211
- if (!(typeof props.opacity === "number" && props.opacity >= 0 && props.opacity <= 1)) fail("opacity must be a number from 0 to 1.");
3212
- next.opacity = props.opacity;
3213
- }
3214
- if (props.fillOpacity !== void 0) {
3215
- if (layer.type !== "pixel" && layer.type !== "smart" && layer.type !== "shape" && layer.type !== "type") fail(`fillOpacity applies to pixel, shape, type and smart object layers; "${id}" is a ${layer.type} layer.`);
3216
- if (!(typeof props.fillOpacity === "number" && props.fillOpacity >= 0 && props.fillOpacity <= 1)) fail("fillOpacity must be a number from 0 to 1.");
3217
- const p = next;
3218
- if (props.fillOpacity === 1) delete p.fillOpacity;
3219
- else p.fillOpacity = props.fillOpacity;
3220
- }
3221
- if (props.blend !== void 0) {
3222
- if (props.blend === "pass-through") {
3223
- if (layer.type !== "group") fail("pass-through is only for groups.");
3224
- } else if (!isBlendMode(props.blend)) fail(`Unknown blend mode "${props.blend}".`);
3225
- next.blend = props.blend;
3226
- }
3227
- if (props.clipped !== void 0) {
3228
- if (props.clipped && loc.index === 0) fail("The bottom layer of a group or the stack has nothing below to clip to.");
3229
- if (props.clipped) next.clipped = true;
3230
- else delete next.clipped;
3231
- }
3232
- if (props.locks !== void 0) {
3233
- const locks = {};
3234
- for (const k of ["pixels", "transparency", "position", "all"]) if (props.locks[k]) locks[k] = true;
3235
- if (Object.keys(locks).length) next.locks = locks;
3236
- else delete next.locks;
3237
- }
3238
- if (props.collapsed !== void 0) {
3239
- if (layer.type !== "group") fail("collapsed applies to groups.");
3240
- const g = next;
3241
- if (props.collapsed) g.collapsed = true;
3242
- else delete g.collapsed;
3243
- }
3244
- return mapLayer(layers, id, () => next);
2964
+ function isRenderedLayer(layer) {
2965
+ return layer.type === "smart" || layer.type === "shape" || layer.type === "type";
3245
2966
  }
3246
- function groupLayers(layers, ids, name = "Group") {
3247
- if (!ids.length) fail("Give at least one layer id to group.");
3248
- const locs = ids.map((id) => requireLayer(layers, id));
3249
- const parent = locs[0].parent?.id ?? null;
3250
- if (locs.some((l) => (l.parent?.id ?? null) !== parent)) fail("Layers to group must share one parent; move them next to each other first.");
3251
- for (const l of locs) requireUnlocked(l.layer, "all", "group it");
3252
- const ordered = [...new Set(locs)].sort((a, b) => a.index - b.index);
3253
- const top = ordered[ordered.length - 1].index;
3254
- const below = ordered.filter((l) => l.index < top).length;
3255
- const group = {
3256
- type: "group",
3257
- id: newLayerId("group"),
3258
- name,
3259
- visible: true,
3260
- opacity: 1,
3261
- blend: "pass-through",
3262
- children: ordered.map((l) => {
3263
- const c = { ...l.layer };
3264
- return c;
3265
- })
3266
- };
3267
- if (group.children[0]?.clipped) {
3268
- const c = { ...group.children[0] };
3269
- delete c.clipped;
3270
- group.children[0] = c;
3271
- }
3272
- let out = layers;
3273
- for (const l of ordered) out = mapLayer(out, l.layer.id, () => null);
3274
- return { layers: insertLayer(out, group, parent, top - below), id: group.id };
2967
+ function shapeLayers(layers) {
2968
+ return flattenLayers(layers).filter((l) => l.type === "shape");
3275
2969
  }
3276
- function ungroupLayer(layers, id) {
3277
- const loc = requireLayer(layers, id);
3278
- if (loc.layer.type !== "group") fail(`Layer "${id}" is not a group.`);
3279
- requireUnlocked(loc.layer, "all", "ungroup it");
3280
- const group = loc.layer;
3281
- let out = mapLayer(layers, id, () => null);
3282
- group.children.forEach((c, i) => {
3283
- out = insertLayer(out, c, loc.parent?.id ?? null, loc.index + i);
3284
- });
3285
- return { layers: out, ids: group.children.map((c) => c.id) };
2970
+ function smartObjectLayers(layers) {
2971
+ return flattenLayers(layers).filter((l) => l.type === "smart");
3286
2972
  }
3287
- function offsetLayer(layers, id, offset) {
3288
- const loc = requireLayer(layers, id);
3289
- if (loc.layer.type !== "pixel") fail(`Only pixel layers move; "${id}" is a ${loc.layer.type} layer.`);
3290
- requireUnlocked(loc.layer, "position", "move it");
3291
- if (!Number.isFinite(offset.x) || !Number.isFinite(offset.y)) fail("x and y must be numbers.");
3292
- const layer = loc.layer;
3293
- const next = { ...layer, offset: { x: Math.round(offset.x), y: Math.round(offset.y) } };
3294
- const mask = layer.mask;
3295
- if (mask && mask.linked === false && !mask.source) {
3296
- next.mask = { ...mask, surface: translateSurface(mask.surface, layer.offset.x - next.offset.x, layer.offset.y - next.offset.y) };
2973
+
2974
+ // src/image-editor/layers/shape.ts
2975
+ var SHAPE_RENDER_MARGIN = MAX_EFFECT_REACH;
2976
+ var DEFAULT_MITER_LIMIT = 100;
2977
+ var fail = (message) => {
2978
+ throw new LayerOperationError(message);
2979
+ };
2980
+ var orientation = (anchors) => {
2981
+ let a = 0;
2982
+ for (let i = 0; i < anchors.length; i++) {
2983
+ const p = anchors[i];
2984
+ const q2 = anchors[(i + 1) % anchors.length];
2985
+ a += p.x * q2.y - q2.x * p.y;
3297
2986
  }
3298
- return mapLayer(layers, id, () => next);
2987
+ return a;
2988
+ };
2989
+ function polygon(points) {
2990
+ const anchors = orientation(points) < 0 ? [...points].reverse() : points;
2991
+ return { closed: true, anchors: anchors.map((p) => ({ x: p.x, y: p.y })) };
3299
2992
  }
3300
- function setLayerMask(layers, id, mask) {
3301
- const loc = requireLayer(layers, id);
3302
- requireUnlocked(loc.layer, "all", "change its mask");
3303
- return mapLayer(layers, id, (l) => {
3304
- const next = { ...l };
3305
- if (mask) next.mask = mask;
3306
- else delete next.mask;
3307
- return next;
3308
- });
2993
+ function arrowheadPolygon(head, tip, dir) {
2994
+ const len = Math.max(0, head.length);
2995
+ const hw = Math.max(0, head.width) / 2;
2996
+ if (!(len > 0) || !(hw > 0)) return null;
2997
+ const n = { x: -dir.y, y: dir.x };
2998
+ const base = { x: tip.x - dir.x * len, y: tip.y - dir.y * len };
2999
+ const c = Math.max(-0.5, Math.min(0.5, head.concavity ?? 0));
3000
+ const pts = [tip, { x: base.x + n.x * hw, y: base.y + n.y * hw }];
3001
+ if (c) pts.push({ x: base.x + dir.x * len * c, y: base.y + dir.y * len * c });
3002
+ pts.push({ x: base.x - n.x * hw, y: base.y - n.y * hw });
3003
+ return polygon(pts);
3309
3004
  }
3310
- var DEVELOP_FIELDS = ["brightness", "contrast", "saturation", "exposure", "temperature", "tint"];
3311
- function normalizeFilter(raw, id = newLayerId("flt")) {
3312
- const enabled = raw.enabled !== false;
3313
- if (raw.kind === "blur") {
3314
- const r = raw.radius;
3315
- if (r !== void 0 && !(typeof r === "number" && r >= 0 && r <= 50)) fail("blur radius must be a number from 0 to 50 px.");
3316
- return { id, kind: "blur", enabled, radius: typeof r === "number" ? r : 2 };
3317
- }
3318
- if (raw.kind !== "develop") fail(`Unknown filter kind "${raw.kind}". Use develop or blur.`);
3319
- const out = { id, kind: "develop", enabled, brightness: 0, contrast: 0, saturation: 0, exposure: 0, temperature: 0, tint: 0, process: 2 };
3320
- for (const k of DEVELOP_FIELDS) {
3321
- const v = raw[k];
3322
- if (v === void 0) continue;
3323
- if (!(typeof v === "number" && v >= -100 && v <= 100)) fail(`${k} must be a number from -100 to 100.`);
3324
- out[k] = v;
3325
- }
3326
- const p = raw.process;
3327
- if (p !== void 0) {
3328
- if (p !== 1 && p !== 2) fail("process must be 1 or 2.");
3329
- out.process = p;
3330
- }
3005
+ function mapAnchor(a, f) {
3006
+ const p = f(a);
3007
+ const out = { x: p.x, y: p.y };
3008
+ if (a.in) out.in = f(a.in);
3009
+ if (a.out) out.out = f(a.out);
3010
+ if (a.type) out.type = a.type;
3011
+ if (a.corner) out.corner = { ...a.corner };
3331
3012
  return out;
3332
3013
  }
3333
- function requirePixelLayer(layers, id) {
3334
- const loc = requireLayer(layers, id);
3335
- if (loc.layer.type !== "pixel") fail(`Filters belong to pixel layers; "${id}" is a ${loc.layer.type} layer.`);
3336
- requireUnlocked(loc.layer, "all", "change its filters");
3337
- return loc.layer;
3014
+ function mapSubpaths(subpaths, f) {
3015
+ return expandVectorCorners(subpaths).map((sp) => ({ closed: sp.closed, anchors: sp.anchors.map((a) => mapAnchor(a, f)) }));
3338
3016
  }
3339
- function addLayerFilter(layers, id, filter) {
3340
- const layer = requirePixelLayer(layers, id);
3341
- if (layer.filters?.some((f) => f.kind === filter.kind)) fail(`Layer "${id}" already has a ${filter.kind} filter; change it with update_layer_filter.`);
3342
- return mapLayer(layers, id, () => ({ ...layer, filters: [...layer.filters ?? [], filter] }));
3017
+ function liveShapeSubpaths(live) {
3018
+ let out;
3019
+ switch (live.kind) {
3020
+ case "rect": {
3021
+ const x = Math.min(live.x, live.x + live.width);
3022
+ const y = Math.min(live.y, live.y + live.height);
3023
+ const sp = rectSubpath({ x, y, width: Math.abs(live.width), height: Math.abs(live.height) });
3024
+ const r = live.radii ?? [0, 0, 0, 0];
3025
+ out = [{ closed: true, anchors: sp.anchors.map((a, i) => r[i] > 0 ? setAnchorCorner(a, r[i]) : a) }];
3026
+ break;
3027
+ }
3028
+ case "ellipse":
3029
+ out = [ellipseSubpath(normBox(live))];
3030
+ break;
3031
+ case "polygon":
3032
+ out = [polygonSubpath(normBox(live), live.sides)];
3033
+ break;
3034
+ case "star":
3035
+ out = [starSubpath(normBox(live), live.points, Math.max(0, Math.min(1, live.innerRatio)))];
3036
+ break;
3037
+ case "line": {
3038
+ const a = { x: live.x1, y: live.y1 };
3039
+ const b = { x: live.x2, y: live.y2 };
3040
+ const len = Math.hypot(b.x - a.x, b.y - a.y);
3041
+ out = [];
3042
+ if (len > 0) {
3043
+ const d = { x: (b.x - a.x) / len, y: (b.y - a.y) / len };
3044
+ const hw = Math.max(0, live.weight) / 2;
3045
+ if (hw > 0) {
3046
+ const n = { x: -d.y * hw, y: d.x * hw };
3047
+ out.push(polygon([{ x: a.x + n.x, y: a.y + n.y }, { x: b.x + n.x, y: b.y + n.y }, { x: b.x - n.x, y: b.y - n.y }, { x: a.x - n.x, y: a.y - n.y }]));
3048
+ }
3049
+ const end = live.endArrow ? arrowheadPolygon(live.endArrow, b, d) : null;
3050
+ const start = live.startArrow ? arrowheadPolygon(live.startArrow, a, { x: -d.x, y: -d.y }) : null;
3051
+ if (start) out.push(start);
3052
+ if (end) out.push(end);
3053
+ }
3054
+ break;
3055
+ }
3056
+ }
3057
+ const m = live.transform;
3058
+ return m && !isIdentityMatrix(m) ? mapSubpaths(out, (p) => applyMatrix(m, p)) : out;
3343
3059
  }
3344
- function updateLayerFilter(layers, id, filterId, changes) {
3345
- const layer = requirePixelLayer(layers, id);
3346
- const i = (layer.filters ?? []).findIndex((f) => f.id === filterId);
3347
- if (i === -1) fail(`Layer "${id}" has no filter "${filterId}". Filters: ${(layer.filters ?? []).map((f) => f.id).join(", ") || "none"}.`);
3348
- const current = layer.filters[i];
3349
- if ("kind" in changes && changes.kind !== current.kind) fail("A filter kind cannot change; remove it and add another.");
3350
- const merged2 = normalizeFilter({ ...current, ...changes, kind: current.kind }, current.id);
3351
- const filters = [...layer.filters];
3352
- filters[i] = merged2;
3353
- return mapLayer(layers, id, () => ({ ...layer, filters }));
3354
- }
3355
- function removeLayerFilter(layers, id, filterId) {
3356
- const layer = requirePixelLayer(layers, id);
3357
- if (!(layer.filters ?? []).some((f) => f.id === filterId)) fail(`Layer "${id}" has no filter "${filterId}".`);
3358
- const filters = layer.filters.filter((f) => f.id !== filterId);
3359
- const next = { ...layer };
3360
- if (filters.length) next.filters = filters;
3361
- else delete next.filters;
3362
- return mapLayer(layers, id, () => next);
3363
- }
3364
- function requireEffectLayer(layers, id, action) {
3365
- const layer = requireLayer(layers, id).layer;
3366
- if (layer.type === "adjustment") fail(`Layer "${layer.name || id}" is an adjustment layer; effects go on pixel, annotation and group layers.`);
3367
- requireUnlocked(layer, "all", action);
3368
- return layer;
3369
- }
3370
- function layerEffectsOf(layer) {
3371
- return layer.effects ?? { enabled: true, items: [] };
3372
- }
3373
- function guard(fn) {
3374
- try {
3375
- return fn();
3376
- } catch (e) {
3377
- if (e instanceof LayerEffectError) fail(e.message);
3378
- throw e;
3379
- }
3380
- }
3381
- function setLayerEffects(layers, id, effects) {
3382
- const layer = requireEffectLayer(layers, id, "change its effects");
3383
- const next = { ...layer };
3384
- if (effects && (effects.items.length || effects.psd || effects.scale !== void 0 || !effects.enabled)) {
3385
- guard(() => validateLayerEffects(effects));
3386
- next.effects = effects;
3387
- } else delete next.effects;
3388
- return mapLayer(layers, id, () => next);
3389
- }
3390
- function addLayerEffect(layers, id, kind, params = {}, index) {
3391
- if (!isLayerEffectKind(kind)) fail(`Unknown effect kind "${String(kind)}". Kinds: ${LAYER_EFFECT_KINDS.join(", ")}.`);
3392
- const layer = requireEffectLayer(layers, id, "add effects");
3393
- const current = layerEffectsOf(layer);
3394
- const ids = new Set(current.items.map((e) => e.id));
3395
- let effectId = newEffectId();
3396
- while (ids.has(effectId)) effectId = newEffectId();
3397
- const { id: _ignored, kind: _kind, ...rest } = params;
3398
- const effect = guard(() => mergeLayerEffect(defaultLayerEffect(kind, effectId), rest));
3399
- const items = [...current.items];
3400
- const at = index === void 0 ? items.length : Math.max(0, Math.min(items.length, Math.floor(index)));
3401
- items.splice(at, 0, effect);
3402
- return { layers: setLayerEffects(layers, id, { ...current, items }), effect };
3403
- }
3404
- function requireEffect(layer, effectId) {
3405
- const items = layer.effects?.items ?? [];
3406
- const i = items.findIndex((e) => e.id === effectId);
3407
- if (i === -1) fail(`Layer "${layer.name || layer.id}" has no effect "${effectId}". Effects: ${items.map((e) => `${e.id} (${e.kind})`).join(", ") || "none"}; call list_layer_effects.`);
3408
- return i;
3409
- }
3410
- function updateLayerEffect(layers, id, effectId, changes) {
3411
- const layer = requireEffectLayer(layers, id, "change its effects");
3412
- const i = requireEffect(layer, effectId);
3413
- const current = layer.effects;
3414
- const items = [...current.items];
3415
- items[i] = guard(() => mergeLayerEffect(items[i], changes));
3416
- return setLayerEffects(layers, id, { ...current, items });
3417
- }
3418
- function removeLayerEffect(layers, id, effectId) {
3419
- const layer = requireEffectLayer(layers, id, "change its effects");
3420
- requireEffect(layer, effectId);
3421
- const current = layer.effects;
3422
- return setLayerEffects(layers, id, { ...current, items: current.items.filter((e) => e.id !== effectId) });
3423
- }
3424
- function setLayerEffectsEnabled(layers, id, enabled, scale) {
3425
- const layer = requireEffectLayer(layers, id, "change its effects");
3426
- if (scale !== void 0 && !(Number.isFinite(scale) && scale >= 0.01 && scale <= 10)) fail(`scale must be from 0.01 to 10 (1 = 100 %), got ${scale}.`);
3427
- const current = layerEffectsOf(layer);
3428
- const next = { ...current, enabled };
3429
- if (scale !== void 0) {
3430
- if (scale === 1) delete next.scale;
3431
- else next.scale = scale;
3432
- }
3433
- return setLayerEffects(layers, id, next);
3434
- }
3435
- function parseBlend(value, allowPassThrough = false) {
3436
- if (value === void 0) return void 0;
3437
- if (value === "pass-through" && allowPassThrough) return value;
3438
- if (!isBlendMode(value)) fail(`Unknown blend mode "${String(value)}".`);
3439
- return value;
3440
- }
3441
- function unionRect(a, b) {
3442
- if (!a) return b;
3443
- if (!b) return a;
3444
- const x = Math.min(a.x, b.x);
3445
- const y = Math.min(a.y, b.y);
3446
- 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 };
3447
- }
3448
-
3449
- // src/image-editor/layers/shape.ts
3450
- var SHAPE_RENDER_MARGIN = MAX_EFFECT_REACH;
3451
- var DEFAULT_MITER_LIMIT = 100;
3452
- var fail2 = (message) => {
3453
- throw new LayerOperationError(message);
3454
- };
3455
- var orientation = (anchors) => {
3456
- let a = 0;
3457
- for (let i = 0; i < anchors.length; i++) {
3458
- const p = anchors[i];
3459
- const q2 = anchors[(i + 1) % anchors.length];
3460
- a += p.x * q2.y - q2.x * p.y;
3461
- }
3462
- return a;
3463
- };
3464
- function polygon(points) {
3465
- const anchors = orientation(points) < 0 ? [...points].reverse() : points;
3466
- return { closed: true, anchors: anchors.map((p) => ({ x: p.x, y: p.y })) };
3467
- }
3468
- function arrowheadPolygon(head, tip, dir) {
3469
- const len = Math.max(0, head.length);
3470
- const hw = Math.max(0, head.width) / 2;
3471
- if (!(len > 0) || !(hw > 0)) return null;
3472
- const n = { x: -dir.y, y: dir.x };
3473
- const base = { x: tip.x - dir.x * len, y: tip.y - dir.y * len };
3474
- const c = Math.max(-0.5, Math.min(0.5, head.concavity ?? 0));
3475
- const pts = [tip, { x: base.x + n.x * hw, y: base.y + n.y * hw }];
3476
- if (c) pts.push({ x: base.x + dir.x * len * c, y: base.y + dir.y * len * c });
3477
- pts.push({ x: base.x - n.x * hw, y: base.y - n.y * hw });
3478
- return polygon(pts);
3479
- }
3480
- function mapAnchor(a, f) {
3481
- const p = f(a);
3482
- const out = { x: p.x, y: p.y };
3483
- if (a.in) out.in = f(a.in);
3484
- if (a.out) out.out = f(a.out);
3485
- if (a.type) out.type = a.type;
3486
- if (a.corner) out.corner = { ...a.corner };
3487
- return out;
3488
- }
3489
- function mapSubpaths(subpaths, f) {
3490
- return expandVectorCorners(subpaths).map((sp) => ({ closed: sp.closed, anchors: sp.anchors.map((a) => mapAnchor(a, f)) }));
3491
- }
3492
- function liveShapeSubpaths(live) {
3493
- let out;
3494
- switch (live.kind) {
3495
- case "rect": {
3496
- const x = Math.min(live.x, live.x + live.width);
3497
- const y = Math.min(live.y, live.y + live.height);
3498
- const sp = rectSubpath({ x, y, width: Math.abs(live.width), height: Math.abs(live.height) });
3499
- const r = live.radii ?? [0, 0, 0, 0];
3500
- out = [{ closed: true, anchors: sp.anchors.map((a, i) => r[i] > 0 ? setAnchorCorner(a, r[i]) : a) }];
3501
- break;
3502
- }
3503
- case "ellipse":
3504
- out = [ellipseSubpath(normBox(live))];
3505
- break;
3506
- case "polygon":
3507
- out = [polygonSubpath(normBox(live), live.sides)];
3508
- break;
3509
- case "star":
3510
- out = [starSubpath(normBox(live), live.points, Math.max(0, Math.min(1, live.innerRatio)))];
3511
- break;
3512
- case "line": {
3513
- const a = { x: live.x1, y: live.y1 };
3514
- const b = { x: live.x2, y: live.y2 };
3515
- const len = Math.hypot(b.x - a.x, b.y - a.y);
3516
- out = [];
3517
- if (len > 0) {
3518
- const d = { x: (b.x - a.x) / len, y: (b.y - a.y) / len };
3519
- const hw = Math.max(0, live.weight) / 2;
3520
- if (hw > 0) {
3521
- const n = { x: -d.y * hw, y: d.x * hw };
3522
- out.push(polygon([{ x: a.x + n.x, y: a.y + n.y }, { x: b.x + n.x, y: b.y + n.y }, { x: b.x - n.x, y: b.y - n.y }, { x: a.x - n.x, y: a.y - n.y }]));
3523
- }
3524
- const end = live.endArrow ? arrowheadPolygon(live.endArrow, b, d) : null;
3525
- const start = live.startArrow ? arrowheadPolygon(live.startArrow, a, { x: -d.x, y: -d.y }) : null;
3526
- if (start) out.push(start);
3527
- if (end) out.push(end);
3528
- }
3529
- break;
3530
- }
3531
- }
3532
- const m = live.transform;
3533
- return m && !isIdentityMatrix(m) ? mapSubpaths(out, (p) => applyMatrix(m, p)) : out;
3534
- }
3535
- function normBox(b) {
3536
- return { x: Math.min(b.x, b.x + b.width), y: Math.min(b.y, b.y + b.height), width: Math.abs(b.width), height: Math.abs(b.height) };
3060
+ function normBox(b) {
3061
+ return { x: Math.min(b.x, b.x + b.width), y: Math.min(b.y, b.y + b.height), width: Math.abs(b.width), height: Math.abs(b.height) };
3537
3062
  }
3538
3063
  function liveComponent(live, op = "combine") {
3539
3064
  return { op, subpaths: liveShapeSubpaths(live), live };
@@ -3660,6 +3185,10 @@ function fillCoverage(geo, rect, rule, inverted = false) {
3660
3185
  });
3661
3186
  return acc;
3662
3187
  }
3188
+ function componentsCoverage(components, rect, rule = "nonzero", inverted = false) {
3189
+ const geo = { fills: components.map((c) => ({ op: c.op, flat: flattenPath(componentPath(c)) })) };
3190
+ return fillCoverage(geo, rect, rule, inverted) ?? new Float32Array(rect.width * rect.height);
3191
+ }
3663
3192
  function paintContext(env) {
3664
3193
  const tiles = /* @__PURE__ */ new Map();
3665
3194
  return {
@@ -3759,29 +3288,29 @@ function rerenderShape(layer, env) {
3759
3288
  }
3760
3289
  var isNum = (v) => typeof v === "number" && Number.isFinite(v);
3761
3290
  function requireNum(v, path, min = -Infinity, max = Infinity) {
3762
- if (!isNum(v)) fail2(`${path} must be a finite number.`);
3763
- if (v < min || v > max) fail2(`${path} must be between ${min} and ${max}, got ${v}.`);
3291
+ if (!isNum(v)) fail(`${path} must be a finite number.`);
3292
+ if (v < min || v > max) fail(`${path} must be between ${min} and ${max}, got ${v}.`);
3764
3293
  return v;
3765
3294
  }
3766
3295
  function normalizeShapePaint(v, path = "paint") {
3767
3296
  if (typeof v === "string") {
3768
3297
  const c = parseCssColor(v);
3769
- if (!c) return fail2(`${path}: "${v}" is not a colour; use #rrggbb (or #rrggbbaa, rgb(), a CSS colour name).`);
3298
+ if (!c) return fail(`${path}: "${v}" is not a colour; use #rrggbb (or #rrggbbaa, rgb(), a CSS colour name).`);
3770
3299
  return c.alpha < 1 ? { type: "color", color: c.hex, opacity: c.alpha } : { type: "color", color: c.hex };
3771
3300
  }
3772
- if (!v || typeof v !== "object") return fail2(`${path} must be a colour string or an object with type color, gradient or pattern.`);
3301
+ if (!v || typeof v !== "object") return fail(`${path} must be a colour string or an object with type color, gradient or pattern.`);
3773
3302
  const o = v;
3774
3303
  const opacity = o.opacity === void 0 ? void 0 : requireNum(o.opacity, `${path}.opacity`, 0, 1);
3775
3304
  const withOpacity = (p) => opacity !== void 0 && opacity !== 1 ? { ...p, opacity } : p;
3776
3305
  if (o.type === "color") {
3777
3306
  const c = parseCssColor(o.color);
3778
- if (!c) return fail2(`${path}.color must be a colour such as #rrggbb.`);
3307
+ if (!c) return fail(`${path}.color must be a colour such as #rrggbb.`);
3779
3308
  const op = (opacity ?? 1) * c.alpha;
3780
3309
  return op !== 1 ? { type: "color", color: c.hex, opacity: op } : { type: "color", color: c.hex };
3781
3310
  }
3782
3311
  if (o.type === "gradient") {
3783
3312
  const g = o.gradient;
3784
- if (!g || typeof g !== "object") return fail2(`${path}.gradient is required: { stops: [{ position, color }], style, angle, \u2026 }.`);
3313
+ if (!g || typeof g !== "object") return fail(`${path}.gradient is required: { stops: [{ position, color }], style, angle, \u2026 }.`);
3785
3314
  const gradient = {
3786
3315
  stops: Array.isArray(g.stops) ? g.stops.map((s) => ({ position: s.position, color: parseCssColor(s.color)?.hex ?? s.color })) : [],
3787
3316
  ...g.opacityStops ? { opacityStops: g.opacityStops } : {},
@@ -3795,14 +3324,14 @@ function normalizeShapePaint(v, path = "paint") {
3795
3324
  try {
3796
3325
  validateEffectGradient(gradient, `${path}.gradient`);
3797
3326
  } catch (e) {
3798
- if (e instanceof LayerEffectError) fail2(e.message);
3327
+ if (e instanceof LayerEffectError) fail(e.message);
3799
3328
  throw e;
3800
3329
  }
3801
- if (gradient.stops.length < 1) fail2(`${path}.gradient.stops needs at least one { position, color }.`);
3330
+ if (gradient.stops.length < 1) fail(`${path}.gradient.stops needs at least one { position, color }.`);
3802
3331
  return withOpacity({ type: "gradient", gradient });
3803
3332
  }
3804
3333
  if (o.type === "pattern") {
3805
- if (typeof o.patternId !== "string" || !o.patternId) fail2(`${path}.patternId is required (a document or built-in pattern id).`);
3334
+ if (typeof o.patternId !== "string" || !o.patternId) fail(`${path}.patternId is required (a document or built-in pattern id).`);
3806
3335
  const p = { type: "pattern", patternId: o.patternId, scale: o.scale === void 0 ? 1 : requireNum(o.scale, `${path}.scale`, 0.01, 10) };
3807
3336
  if (typeof o.name === "string") p.name = o.name;
3808
3337
  if (o.angle !== void 0) p.angle = requireNum(o.angle, `${path}.angle`, -360, 360);
@@ -3813,10 +3342,10 @@ function normalizeShapePaint(v, path = "paint") {
3813
3342
  }
3814
3343
  return withOpacity(p);
3815
3344
  }
3816
- return fail2(`${path}.type must be color, gradient or pattern, got ${JSON.stringify(o.type)}.`);
3345
+ return fail(`${path}.type must be color, gradient or pattern, got ${JSON.stringify(o.type)}.`);
3817
3346
  }
3818
3347
  function normalizeShapeStroke(v, path = "stroke", base) {
3819
- if (!v || typeof v !== "object") return fail2(`${path} must be an object { width, paint, align?, cap?, join?, miterLimit?, dashes?, dashOffset? } or null.`);
3348
+ if (!v || typeof v !== "object") return fail(`${path} must be an object { width, paint, align?, cap?, join?, miterLimit?, dashes?, dashOffset? } or null.`);
3820
3349
  const o = v;
3821
3350
  const width = o.width === void 0 && base ? base.width : requireNum(o.width, `${path}.width`, 0, 1e4);
3822
3351
  const paint = o.paint === void 0 && base ? base.paint : o.paint === void 0 && o.color !== void 0 ? normalizeShapePaint(o.color, `${path}.color`) : normalizeShapePaint(o.paint, `${path}.paint`);
@@ -3824,7 +3353,7 @@ function normalizeShapeStroke(v, path = "stroke", base) {
3824
3353
  const pick = (k, allowed) => {
3825
3354
  const value = o[k] === void 0 ? base?.[k] : o[k];
3826
3355
  if (value === void 0) return;
3827
- if (!allowed.includes(value)) fail2(`${path}.${k} must be one of ${allowed.join(", ")}, got ${JSON.stringify(value)}.`);
3356
+ if (!allowed.includes(value)) fail(`${path}.${k} must be one of ${allowed.join(", ")}, got ${JSON.stringify(value)}.`);
3828
3357
  if (value !== (k === "align" ? "center" : k === "cap" ? "butt" : "miter")) out[k] = value;
3829
3358
  };
3830
3359
  pick("align", ["inside", "center", "outside"]);
@@ -3834,9 +3363,9 @@ function normalizeShapeStroke(v, path = "stroke", base) {
3834
3363
  if (miter !== void 0 && miter !== DEFAULT_MITER_LIMIT) out.miterLimit = miter;
3835
3364
  const dashes = o.dashes === void 0 ? base?.dashes : o.dashes;
3836
3365
  if (dashes !== void 0 && dashes !== null) {
3837
- if (!Array.isArray(dashes) || dashes.length > 32 || !dashes.every((d) => isNum(d) && d >= 0 && d <= 1e4)) fail2(`${path}.dashes must be at most 32 lengths 0..10000 (multiples of the width).`);
3366
+ if (!Array.isArray(dashes) || dashes.length > 32 || !dashes.every((d) => isNum(d) && d >= 0 && d <= 1e4)) fail(`${path}.dashes must be at most 32 lengths 0..10000 (multiples of the width).`);
3838
3367
  if (dashes.length) {
3839
- if (dashes.reduce((s, d) => s + d, 0) < 0.01) fail2(`${path}.dashes must add up to at least 0.01 widths.`);
3368
+ if (dashes.reduce((s, d) => s + d, 0) < 0.01) fail(`${path}.dashes must add up to at least 0.01 widths.`);
3840
3369
  out.dashes = [...dashes];
3841
3370
  }
3842
3371
  }
@@ -3854,7 +3383,7 @@ function box(o, path) {
3854
3383
  }
3855
3384
  function arrowhead(v, path) {
3856
3385
  if (v === null || v === void 0 || v === false) return null;
3857
- if (typeof v !== "object") return fail2(`${path} must be { width, length, concavity? } in pixels, or null.`);
3386
+ if (typeof v !== "object") return fail(`${path} must be { width, length, concavity? } in pixels, or null.`);
3858
3387
  const o = v;
3859
3388
  const head = { width: requireNum(o.width, `${path}.width`, 0, 1e5), length: requireNum(o.length, `${path}.length`, 0, 1e5) };
3860
3389
  if (o.concavity !== void 0 && o.concavity !== 0) head.concavity = requireNum(o.concavity, `${path}.concavity`, -0.5, 0.5);
@@ -3862,13 +3391,13 @@ function arrowhead(v, path) {
3862
3391
  }
3863
3392
  function matrixArg(v, path) {
3864
3393
  if (v === void 0 || v === null) return void 0;
3865
- if (!Array.isArray(v) || v.length !== 6 || !v.every(isNum)) fail2(`${path} must be six numbers [a, b, c, d, e, f].`);
3394
+ if (!Array.isArray(v) || v.length !== 6 || !v.every(isNum)) fail(`${path} must be six numbers [a, b, c, d, e, f].`);
3866
3395
  const m = v;
3867
- if (Math.abs(m[0] * m[3] - m[1] * m[2]) < 1e-12) fail2(`${path} is singular.`);
3396
+ if (Math.abs(m[0] * m[3] - m[1] * m[2]) < 1e-12) fail(`${path} is singular.`);
3868
3397
  return isIdentityMatrix(m) ? void 0 : [...m];
3869
3398
  }
3870
3399
  function normalizeLiveShape(v, path = "shape") {
3871
- if (!v || typeof v !== "object") return fail2(`${path} must be an object with kind ${LIVE_SHAPE_KINDS.join(", ")}.`);
3400
+ if (!v || typeof v !== "object") return fail(`${path} must be an object with kind ${LIVE_SHAPE_KINDS.join(", ")}.`);
3872
3401
  const o = v;
3873
3402
  const transform = matrixArg(o.transform, `${path}.transform`);
3874
3403
  const t = transform ? { transform } : {};
@@ -3879,7 +3408,7 @@ function normalizeLiveShape(v, path = "shape") {
3879
3408
  if (o.radii !== void 0 || o.radius !== void 0) {
3880
3409
  const r = o.radii ?? o.radius;
3881
3410
  const list2 = typeof r === "number" ? [r, r, r, r] : r;
3882
- if (!Array.isArray(list2) || list2.length !== 4 || !list2.every((x) => isNum(x) && x >= 0)) fail2(`${path}.radii must be four radii \u2265 0 [top-left, top-right, bottom-right, bottom-left] (or radius, one number).`);
3411
+ if (!Array.isArray(list2) || list2.length !== 4 || !list2.every((x) => isNum(x) && x >= 0)) fail(`${path}.radii must be four radii \u2265 0 [top-left, top-right, bottom-right, bottom-left] (or radius, one number).`);
3883
3412
  if (list2.some((x) => x > 0)) radii = [...list2];
3884
3413
  }
3885
3414
  return { kind: "rect", ...b, ...radii ? { radii } : {}, ...t };
@@ -3910,16 +3439,16 @@ function normalizeLiveShape(v, path = "shape") {
3910
3439
  const e = arrowhead(o.endArrow, `${path}.endArrow`);
3911
3440
  if (s) line.startArrow = s;
3912
3441
  if (e) line.endArrow = e;
3913
- if (line.x1 === line.x2 && line.y1 === line.y2) fail2(`${path}: the line's two ends are the same point.`);
3442
+ if (line.x1 === line.x2 && line.y1 === line.y2) fail(`${path}: the line's two ends are the same point.`);
3914
3443
  return line;
3915
3444
  }
3916
3445
  default:
3917
- return fail2(`${path}.kind must be one of ${LIVE_SHAPE_KINDS.join(", ")}, got ${JSON.stringify(o.kind)}.`);
3446
+ return fail(`${path}.kind must be one of ${LIVE_SHAPE_KINDS.join(", ")}, got ${JSON.stringify(o.kind)}.`);
3918
3447
  }
3919
3448
  }
3920
3449
  function point(v, path) {
3921
3450
  const p = v;
3922
- if (!p || typeof p !== "object") return fail2(`${path} must be { x, y } in document pixels.`);
3451
+ if (!p || typeof p !== "object") return fail(`${path} must be { x, y } in document pixels.`);
3923
3452
  return { x: requireNum(p.x, `${path}.x`), y: requireNum(p.y, `${path}.y`) };
3924
3453
  }
3925
3454
  function normalizeSubpaths(v, path = "subpaths") {
@@ -3928,15 +3457,15 @@ function normalizeSubpaths(v, path = "subpaths") {
3928
3457
  try {
3929
3458
  subs = bezierToSubpaths(v);
3930
3459
  } catch {
3931
- return fail2(`${path}: could not read the SVG path data.`);
3460
+ return fail(`${path}: could not read the SVG path data.`);
3932
3461
  }
3933
- if (!subs.length || !subs.every((s) => s.anchors.every((a) => isNum(a.x) && isNum(a.y)))) fail2(`${path}: the SVG path data holds no drawable subpath.`);
3462
+ if (!subs.length || !subs.every((s) => s.anchors.every((a) => isNum(a.x) && isNum(a.y)))) fail(`${path}: the SVG path data holds no drawable subpath.`);
3934
3463
  return subs;
3935
3464
  }
3936
- if (!Array.isArray(v) || !v.length) return fail2(`${path} must be a non-empty array of { anchors: [{ x, y, in?, out? }], closed } (or SVG path data).`);
3465
+ if (!Array.isArray(v) || !v.length) return fail(`${path} must be a non-empty array of { anchors: [{ x, y, in?, out? }], closed } (or SVG path data).`);
3937
3466
  return v.map((s, i) => {
3938
3467
  const sp = s;
3939
- if (!sp || !Array.isArray(sp.anchors) || sp.anchors.length < 1) fail2(`${path}[${i}].anchors must be a non-empty array.`);
3468
+ if (!sp || !Array.isArray(sp.anchors) || sp.anchors.length < 1) fail(`${path}[${i}].anchors must be a non-empty array.`);
3940
3469
  const anchors = sp.anchors.map((a, j) => {
3941
3470
  const at = `${path}[${i}].anchors[${j}]`;
3942
3471
  const p = point(a, at);
@@ -3956,16 +3485,16 @@ function normalizeSubpaths(v, path = "subpaths") {
3956
3485
  }
3957
3486
  function requireOperation(v, path = "op") {
3958
3487
  if (v === void 0) return "combine";
3959
- if (!SHAPE_OPERATIONS.includes(v)) fail2(`${path} must be one of ${SHAPE_OPERATIONS.join(", ")}, got ${JSON.stringify(v)}.`);
3488
+ if (!SHAPE_OPERATIONS.includes(v)) fail(`${path} must be one of ${SHAPE_OPERATIONS.join(", ")}, got ${JSON.stringify(v)}.`);
3960
3489
  return v;
3961
3490
  }
3962
3491
  function requireShapeLayer(layers, id) {
3963
3492
  const layer = requireLayer(layers, id).layer;
3964
- if (layer.type !== "shape") fail2(`Layer "${layer.name || id}" is a ${layer.type} layer, not a shape layer; list_layers shows each layer's type.`);
3493
+ if (layer.type !== "shape") fail(`Layer "${layer.name || id}" is a ${layer.type} layer, not a shape layer; list_layers shows each layer's type.`);
3965
3494
  return layer;
3966
3495
  }
3967
3496
  function createShapeLayer(init, env, id = newLayerId()) {
3968
- if (!init.components.length) fail2("A shape needs at least one component.");
3497
+ if (!init.components.length) fail("A shape needs at least one component.");
3969
3498
  const layer = {
3970
3499
  type: "shape",
3971
3500
  id,
@@ -4005,7 +3534,7 @@ function updateShapeLayer(layers, id, change, env, action = "edit it") {
4005
3534
  const patch = change(current);
4006
3535
  const next = { ...current, ...patch };
4007
3536
  if (!("annotation" in patch)) delete next.annotation;
4008
- if (!next.components.length) fail2("A shape needs at least one component; delete the layer instead.");
3537
+ if (!next.components.length) fail("A shape needs at least one component; delete the layer instead.");
4009
3538
  const rendered = rerenderShape(next, env);
4010
3539
  return { layers: mapLayer(layers, id, () => rendered), layer: rendered };
4011
3540
  }
@@ -4029,7 +3558,7 @@ function mergeShapeComponents(components, fillRule = "nonzero") {
4029
3558
  if (components.length < 2) return [...components];
4030
3559
  const path = shapeOutline(components, fillRule);
4031
3560
  const subpaths = bezierToSubpaths(path);
4032
- if (!subpaths.length) fail2("The components combine to nothing (the operations remove every area), so there is nothing to merge into.");
3561
+ if (!subpaths.length) fail("The components combine to nothing (the operations remove every area), so there is nothing to merge into.");
4033
3562
  return [{ op: "combine", subpaths }];
4034
3563
  }
4035
3564
  function shapeAsPixelLayer(layer) {
@@ -4099,20 +3628,645 @@ function shapeFromAnnotation(a) {
4099
3628
  default:
4100
3629
  return null;
4101
3630
  }
4102
- }
4103
- function annotationBlend(a, layerBlend) {
4104
- if (layerBlend !== "normal") return layerBlend;
4105
- return a.blend && isBlendMode(a.blend) ? a.blend : "normal";
4106
- }
4107
- function annotationOfShape(layer) {
4108
- const a = layer.annotation;
4109
- if (!a) return null;
4110
- const blend = layer.blend;
4111
- if ((a.blend ?? "normal") === blend) return a;
4112
- const out = { ...a };
4113
- if (blend === "normal") delete out.blend;
4114
- else out.blend = blend;
4115
- return out;
3631
+ }
3632
+ function annotationBlend(a, layerBlend) {
3633
+ if (layerBlend !== "normal") return layerBlend;
3634
+ return a.blend && isBlendMode(a.blend) ? a.blend : "normal";
3635
+ }
3636
+ function annotationOfShape(layer) {
3637
+ const a = layer.annotation;
3638
+ if (!a) return null;
3639
+ const blend = layer.blend;
3640
+ if ((a.blend ?? "normal") === blend) return a;
3641
+ const out = { ...a };
3642
+ if (blend === "normal") delete out.blend;
3643
+ else out.blend = blend;
3644
+ return out;
3645
+ }
3646
+
3647
+ // src/image-editor/layers/vector-mask.ts
3648
+ function isActiveVectorMask(vm) {
3649
+ return Boolean(vm && vm.enabled !== false);
3650
+ }
3651
+ function vectorMaskCoverage(vm, width, height) {
3652
+ let v = componentsCoverage(vm.components, { x: 0, y: 0, width, height }, vm.fillRule ?? "nonzero", vm.inverted === true);
3653
+ if ((vm.feather ?? 0) > 0) v = gaussianBlur(v, width, height, vm.feather, "clamp");
3654
+ const density = vm.density ?? 1;
3655
+ if (density !== 1) for (let i = 0; i < v.length; i++) v[i] = 1 - density * (1 - v[i]);
3656
+ return v;
3657
+ }
3658
+ function outsideValue(vm) {
3659
+ return 1 - (vm.density ?? 1) * (1 - (vm.inverted ? 1 : 0));
3660
+ }
3661
+ function rasterCoverage(mask, ox, oy, width, height) {
3662
+ const bytes = readRegion(mask.surface, { x: -ox, y: -oy, width, height });
3663
+ let v = new Float32Array(width * height);
3664
+ for (let i = 0; i < v.length; i++) v[i] = bytes[i] / 255;
3665
+ if ((mask.feather ?? 0) > 0) v = gaussianBlur(v, width, height, mask.feather, "clamp");
3666
+ const density = mask.density ?? 1;
3667
+ if (density !== 1) for (let i = 0; i < v.length; i++) v[i] = 1 - density * (1 - v[i]);
3668
+ return v;
3669
+ }
3670
+ var layerOffset = (layer) => layer.type === "pixel" ? layer.offset : { x: 0, y: 0 };
3671
+ function effectiveLayerMask(layer, width, height) {
3672
+ const vm = layer.vectorMask;
3673
+ const raster = layer.mask;
3674
+ if (!isActiveVectorMask(vm) || raster?.source || width < 1 || height < 1) return raster;
3675
+ const { x: ox, y: oy } = layerOffset(layer);
3676
+ const v = vectorMaskCoverage(vm, width, height);
3677
+ let outside = outsideValue(vm);
3678
+ if (raster && raster.enabled !== false) {
3679
+ const r = rasterCoverage(raster, ox, oy, width, height);
3680
+ for (let i = 0; i < v.length; i++) v[i] *= r[i];
3681
+ outside *= 1 - (raster.density ?? 1) * (1 - raster.defaultValue / 255);
3682
+ }
3683
+ const bytes = new Uint8ClampedArray(v.length);
3684
+ for (let i = 0; i < v.length; i++) bytes[i] = Math.round(v[i] * 255);
3685
+ const defaultValue = outside >= 0.5 ? 255 : 0;
3686
+ return { surface: writeRegion(emptySurface(1, defaultValue), { x: -ox, y: -oy, width, height }, bytes), defaultValue };
3687
+ }
3688
+ var resolved = /* @__PURE__ */ new WeakMap();
3689
+ function hasVectorMask(layers) {
3690
+ for (const l of layers) {
3691
+ if (isActiveVectorMask(l.vectorMask)) return true;
3692
+ if (l.type === "group" && hasVectorMask(l.children)) return true;
3693
+ }
3694
+ return false;
3695
+ }
3696
+ function resolveVectorMasks(layers, width, height) {
3697
+ if (!hasVectorMask(layers)) return layers;
3698
+ return layers.map((l) => resolveLayer(l, width, height));
3699
+ }
3700
+ function resolveLayer(layer, width, height) {
3701
+ let next = layer;
3702
+ if (layer.type === "group" && hasVectorMask(layer.children)) {
3703
+ const key2 = `${width}x${height}`;
3704
+ const hit2 = resolved.get(layer);
3705
+ if (hit2?.key === key2) return hit2.layer;
3706
+ next = { ...layer, children: layer.children.map((c) => resolveLayer(c, width, height)) };
3707
+ if (!isActiveVectorMask(layer.vectorMask)) {
3708
+ resolved.set(layer, { key: key2, layer: next });
3709
+ return next;
3710
+ }
3711
+ }
3712
+ if (!isActiveVectorMask(layer.vectorMask)) return layer;
3713
+ const key = `${width}x${height}:v`;
3714
+ const hit = resolved.get(layer);
3715
+ if (hit?.key === key) return hit.layer;
3716
+ const mask = effectiveLayerMask(layer, width, height);
3717
+ const out = { ...next, mask };
3718
+ delete out.vectorMask;
3719
+ if (!mask) delete out.mask;
3720
+ resolved.set(layer, { key, layer: out });
3721
+ return out;
3722
+ }
3723
+ function offsetVectorMask(vm, dx, dy) {
3724
+ if (!dx && !dy) return vm;
3725
+ return { ...vm, components: vm.components.map((c) => ({ ...c, subpaths: mapSubpaths(c.subpaths, (p) => ({ x: p.x + dx, y: p.y + dy })), ...c.live ? { live: offsetLive(c.live, dx, dy) } : {} })) };
3726
+ }
3727
+ function offsetLive(live, dx, dy) {
3728
+ if (live.kind === "line") return { ...live, x1: live.x1 + dx, y1: live.y1 + dy, x2: live.x2 + dx, y2: live.y2 + dy };
3729
+ return { ...live, x: live.x + dx, y: live.y + dy };
3730
+ }
3731
+ function transformVectorMask(vm, f, affine) {
3732
+ return { ...vm, components: transformShapeComponents(vm.components, f, affine) };
3733
+ }
3734
+ function followLayerMove(layer, dx, dy) {
3735
+ const vm = layer.vectorMask;
3736
+ if (!vm || vm.linked === false || !dx && !dy) return layer;
3737
+ return { ...layer, vectorMask: offsetVectorMask(vm, dx, dy) };
3738
+ }
3739
+ function vectorMaskBounds(vm) {
3740
+ let x0 = Infinity;
3741
+ let y0 = Infinity;
3742
+ let x1 = -Infinity;
3743
+ let y1 = -Infinity;
3744
+ for (const c of vm.components) {
3745
+ for (const sp of c.subpaths) {
3746
+ for (const a of sp.anchors) {
3747
+ for (const p of [a, a.in, a.out]) {
3748
+ if (!p) continue;
3749
+ x0 = Math.min(x0, p.x);
3750
+ y0 = Math.min(y0, p.y);
3751
+ x1 = Math.max(x1, p.x);
3752
+ y1 = Math.max(y1, p.y);
3753
+ }
3754
+ }
3755
+ }
3756
+ }
3757
+ return x0 === Infinity ? null : { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
3758
+ }
3759
+ var thumbCache = /* @__PURE__ */ new WeakMap();
3760
+ function vectorMaskThumbnail(vm, width, height, maxSide = 64) {
3761
+ const key = `${width}x${height}:${maxSide}`;
3762
+ const hit = thumbCache.get(vm);
3763
+ if (hit?.key === key) return hit.pixels;
3764
+ const k = Math.min(1, maxSide / Math.max(1, width, height));
3765
+ const tw = Math.max(1, Math.round(width * k));
3766
+ const th = Math.max(1, Math.round(height * k));
3767
+ const scaled = transformVectorMask(vm, (p) => ({ x: p.x * k, y: p.y * k }), [k, 0, 0, k, 0, 0]);
3768
+ const cov = componentsCoverage(scaled.components, { x: 0, y: 0, width: tw, height: th }, vm.fillRule ?? "nonzero", vm.inverted === true);
3769
+ const data = new Uint8ClampedArray(tw * th * 4);
3770
+ for (let i = 0; i < cov.length; i++) {
3771
+ const v = Math.round(Math.min(1, Math.max(0, cov[i])) * 255);
3772
+ data[i * 4] = data[i * 4 + 1] = data[i * 4 + 2] = v;
3773
+ data[i * 4 + 3] = 255;
3774
+ }
3775
+ const pixels = { width: tw, height: th, data };
3776
+ thumbCache.set(vm, { key, pixels });
3777
+ return pixels;
3778
+ }
3779
+
3780
+ // src/image-editor/layers/ops.ts
3781
+ var LayerOperationError = class extends Error {
3782
+ userMessage;
3783
+ constructor(message, userMessage) {
3784
+ super(message);
3785
+ this.name = "LayerOperationError";
3786
+ if (userMessage) this.userMessage = userMessage;
3787
+ }
3788
+ };
3789
+ function userErrorMessage(err) {
3790
+ if (err instanceof LayerOperationError && err.userMessage) return err.userMessage;
3791
+ return err instanceof Error ? err.message : String(err);
3792
+ }
3793
+ function isPaintableLayer(layer) {
3794
+ return layer.type === "pixel" && layer.visible && !isLayerLocked(layer, "pixels");
3795
+ }
3796
+ function defaultActiveLayerId(layers) {
3797
+ const pixels = flattenLayers(layers).filter((l) => l.type === "pixel");
3798
+ for (let i = pixels.length - 1; i >= 0; i--) if (isPaintableLayer(pixels[i])) return pixels[i].id;
3799
+ return pixels[pixels.length - 1]?.id ?? null;
3800
+ }
3801
+ function activeLayerAfterRemoval(prev, next, removedId) {
3802
+ const before = flattenLayers(prev);
3803
+ const at = before.findIndex((l) => l.id === removedId);
3804
+ for (let i = at - 1; i >= 0; i--) {
3805
+ const loc = findLayer(next, before[i].id);
3806
+ if (loc && isPaintableLayer(loc.layer)) return loc.layer.id;
3807
+ }
3808
+ return defaultActiveLayerId(next);
3809
+ }
3810
+ var fail2 = (message) => {
3811
+ throw new LayerOperationError(message);
3812
+ };
3813
+ function requireLayer(layers, id) {
3814
+ const loc = findLayer(layers, id);
3815
+ if (!loc) {
3816
+ const ids = flattenLayers(layers).map((l) => l.id);
3817
+ fail2(`Unknown layer "${id}". ${ids.length ? `Layers: ${ids.join(", ")}.` : "The document has no layers."} Call list_layers for ids.`);
3818
+ }
3819
+ return loc;
3820
+ }
3821
+ function requireUnlocked(layer, what, action) {
3822
+ if (isLayerLocked(layer, "all")) fail2(`Layer "${layer.name || layer.id}" is locked (all); unlock it before you ${action}.`);
3823
+ if (what !== "all" && isLayerLocked(layer, what)) fail2(`Layer "${layer.name || layer.id}" has its ${what} locked; unlock it before you ${action}.`);
3824
+ }
3825
+ function placeLayer(layers, layer, placement = {}, defaultIndex) {
3826
+ const given = [placement.aboveId, placement.belowId, placement.parentId].filter((v) => v !== void 0).length;
3827
+ if (given > 1) fail2("Give only one of aboveId, belowId and parentId.");
3828
+ if (placement.aboveId || placement.belowId) {
3829
+ const ref = requireLayer(layers, placement.aboveId ?? placement.belowId);
3830
+ return insertLayer(layers, layer, ref.parent?.id ?? null, ref.index + (placement.aboveId ? 1 : 0));
3831
+ }
3832
+ if (placement.parentId) {
3833
+ const ref = requireLayer(layers, placement.parentId);
3834
+ if (ref.layer.type !== "group") fail2(`Layer "${placement.parentId}" is not a group; parentId must be a group id.`);
3835
+ return insertLayer(layers, layer, ref.layer.id, placement.index);
3836
+ }
3837
+ return insertLayer(layers, layer, null, placement.index ?? defaultIndex);
3838
+ }
3839
+ function removeLayer(layers, id) {
3840
+ const loc = requireLayer(layers, id);
3841
+ requireUnlocked(loc.layer, "all", "delete it");
3842
+ return mapLayer(layers, id, () => null);
3843
+ }
3844
+ function moveLayer(layers, id, placement) {
3845
+ const loc = requireLayer(layers, id);
3846
+ requireUnlocked(loc.layer, "all", "move it");
3847
+ const target = placement.aboveId ?? placement.belowId ?? placement.parentId;
3848
+ if (target === id) fail2("A layer cannot be placed relative to itself.");
3849
+ if (target && loc.layer.type === "group" && flattenLayers(loc.layer.children).some((l) => l.id === target)) {
3850
+ fail2(`Group "${id}" cannot be moved into its own child "${target}".`);
3851
+ }
3852
+ if (target) requireLayer(layers, target);
3853
+ if (!target && placement.index === void 0) fail2("Give aboveId, belowId, parentId or index.");
3854
+ const without = mapLayer(layers, id, () => null);
3855
+ return placeLayer(without, loc.layer, placement);
3856
+ }
3857
+ function withNewIds(layer) {
3858
+ const next = { ...layer, id: newLayerId(layer.type === "adjustment" ? "adj" : layer.type) };
3859
+ if (next.type === "group") next.children = next.children.map(withNewIds);
3860
+ if (next.type === "annotation") next.annotation = { ...next.annotation, id: next.id };
3861
+ if (next.type === "shape" || next.type === "type") delete next.annotation;
3862
+ if (next.type === "smart" && next.psd?.placed) {
3863
+ next.psd = { ...next.psd };
3864
+ delete next.psd.placed;
3865
+ }
3866
+ return next;
3867
+ }
3868
+ function duplicateLayer(layers, id, name) {
3869
+ const loc = requireLayer(layers, id);
3870
+ const copy = withNewIds(loc.layer);
3871
+ copy.name = name ?? `${loc.layer.name || "Layer"} copy`;
3872
+ delete copy.locks;
3873
+ return { layers: insertLayer(layers, copy, loc.parent?.id ?? null, loc.index + 1), id: copy.id };
3874
+ }
3875
+ function setLayerProperties(layers, id, props) {
3876
+ const loc = requireLayer(layers, id);
3877
+ const layer = loc.layer;
3878
+ const onlyUnlocking = Object.keys(props).every((k) => k === "locks" || k === "visible" || k === "collapsed");
3879
+ if (!onlyUnlocking) requireUnlocked(layer, "all", "change its properties");
3880
+ const next = { ...layer };
3881
+ if (props.name !== void 0) {
3882
+ if (typeof props.name !== "string" || !props.name.trim()) fail2("name must be a non-empty string.");
3883
+ next.name = props.name.trim();
3884
+ }
3885
+ if (props.visible !== void 0) next.visible = Boolean(props.visible);
3886
+ if (props.opacity !== void 0) {
3887
+ if (!(typeof props.opacity === "number" && props.opacity >= 0 && props.opacity <= 1)) fail2("opacity must be a number from 0 to 1.");
3888
+ next.opacity = props.opacity;
3889
+ }
3890
+ if (props.fillOpacity !== void 0) {
3891
+ if (layer.type !== "pixel" && layer.type !== "smart" && layer.type !== "shape" && layer.type !== "type") fail2(`fillOpacity applies to pixel, shape, type and smart object layers; "${id}" is a ${layer.type} layer.`);
3892
+ if (!(typeof props.fillOpacity === "number" && props.fillOpacity >= 0 && props.fillOpacity <= 1)) fail2("fillOpacity must be a number from 0 to 1.");
3893
+ const p = next;
3894
+ if (props.fillOpacity === 1) delete p.fillOpacity;
3895
+ else p.fillOpacity = props.fillOpacity;
3896
+ }
3897
+ if (props.blend !== void 0) {
3898
+ if (props.blend === "pass-through") {
3899
+ if (layer.type !== "group") fail2("pass-through is only for groups.");
3900
+ } else if (!isBlendMode(props.blend)) fail2(`Unknown blend mode "${props.blend}".`);
3901
+ next.blend = props.blend;
3902
+ }
3903
+ if (props.clipped !== void 0) {
3904
+ if (props.clipped && loc.index === 0) fail2("The bottom layer of a group or the stack has nothing below to clip to.");
3905
+ if (props.clipped) next.clipped = true;
3906
+ else delete next.clipped;
3907
+ }
3908
+ if (props.locks !== void 0) {
3909
+ const locks = {};
3910
+ for (const k of ["pixels", "transparency", "position", "all"]) if (props.locks[k]) locks[k] = true;
3911
+ if (Object.keys(locks).length) next.locks = locks;
3912
+ else delete next.locks;
3913
+ }
3914
+ if (props.collapsed !== void 0) {
3915
+ if (layer.type !== "group") fail2("collapsed applies to groups.");
3916
+ const g = next;
3917
+ if (props.collapsed) g.collapsed = true;
3918
+ else delete g.collapsed;
3919
+ }
3920
+ return mapLayer(layers, id, () => next);
3921
+ }
3922
+ function groupLayers(layers, ids, name = "Group") {
3923
+ if (!ids.length) fail2("Give at least one layer id to group.");
3924
+ const locs = ids.map((id) => requireLayer(layers, id));
3925
+ const parent = locs[0].parent?.id ?? null;
3926
+ if (locs.some((l) => (l.parent?.id ?? null) !== parent)) fail2("Layers to group must share one parent; move them next to each other first.");
3927
+ for (const l of locs) requireUnlocked(l.layer, "all", "group it");
3928
+ const ordered = [...new Set(locs)].sort((a, b) => a.index - b.index);
3929
+ const top = ordered[ordered.length - 1].index;
3930
+ const below = ordered.filter((l) => l.index < top).length;
3931
+ const group = {
3932
+ type: "group",
3933
+ id: newLayerId("group"),
3934
+ name,
3935
+ visible: true,
3936
+ opacity: 1,
3937
+ blend: "pass-through",
3938
+ children: ordered.map((l) => {
3939
+ const c = { ...l.layer };
3940
+ return c;
3941
+ })
3942
+ };
3943
+ if (group.children[0]?.clipped) {
3944
+ const c = { ...group.children[0] };
3945
+ delete c.clipped;
3946
+ group.children[0] = c;
3947
+ }
3948
+ let out = layers;
3949
+ for (const l of ordered) out = mapLayer(out, l.layer.id, () => null);
3950
+ return { layers: insertLayer(out, group, parent, top - below), id: group.id };
3951
+ }
3952
+ function ungroupLayer(layers, id) {
3953
+ const loc = requireLayer(layers, id);
3954
+ if (loc.layer.type !== "group") fail2(`Layer "${id}" is not a group.`);
3955
+ requireUnlocked(loc.layer, "all", "ungroup it");
3956
+ const group = loc.layer;
3957
+ let out = mapLayer(layers, id, () => null);
3958
+ group.children.forEach((c, i) => {
3959
+ out = insertLayer(out, c, loc.parent?.id ?? null, loc.index + i);
3960
+ });
3961
+ return { layers: out, ids: group.children.map((c) => c.id) };
3962
+ }
3963
+ function offsetLayer(layers, id, offset) {
3964
+ const loc = requireLayer(layers, id);
3965
+ if (loc.layer.type !== "pixel") fail2(`Only pixel layers move; "${id}" is a ${loc.layer.type} layer.`);
3966
+ requireUnlocked(loc.layer, "position", "move it");
3967
+ if (!Number.isFinite(offset.x) || !Number.isFinite(offset.y)) fail2("x and y must be numbers.");
3968
+ const layer = loc.layer;
3969
+ const next = { ...layer, offset: { x: Math.round(offset.x), y: Math.round(offset.y) } };
3970
+ const mask = layer.mask;
3971
+ if (mask && mask.linked === false && !mask.source) {
3972
+ next.mask = { ...mask, surface: translateSurface(mask.surface, layer.offset.x - next.offset.x, layer.offset.y - next.offset.y) };
3973
+ }
3974
+ return mapLayer(layers, id, () => followLayerMove(next, next.offset.x - layer.offset.x, next.offset.y - layer.offset.y));
3975
+ }
3976
+ function setLayerMask(layers, id, mask) {
3977
+ const loc = requireLayer(layers, id);
3978
+ requireUnlocked(loc.layer, "all", "change its mask");
3979
+ return mapLayer(layers, id, (l) => {
3980
+ const next = { ...l };
3981
+ if (mask) next.mask = mask;
3982
+ else delete next.mask;
3983
+ return next;
3984
+ });
3985
+ }
3986
+ var DEVELOP_FIELDS = ["brightness", "contrast", "saturation", "exposure", "temperature", "tint"];
3987
+ function normalizeFilter(raw, id = newLayerId("flt")) {
3988
+ const enabled = raw.enabled !== false;
3989
+ if (raw.kind === "blur") {
3990
+ const r = raw.radius;
3991
+ if (r !== void 0 && !(typeof r === "number" && r >= 0 && r <= 50)) fail2("blur radius must be a number from 0 to 50 px.");
3992
+ return { id, kind: "blur", enabled, radius: typeof r === "number" ? r : 2 };
3993
+ }
3994
+ if (raw.kind !== "develop") fail2(`Unknown filter kind "${raw.kind}". Use develop or blur.`);
3995
+ const out = { id, kind: "develop", enabled, brightness: 0, contrast: 0, saturation: 0, exposure: 0, temperature: 0, tint: 0, process: 2 };
3996
+ for (const k of DEVELOP_FIELDS) {
3997
+ const v = raw[k];
3998
+ if (v === void 0) continue;
3999
+ if (!(typeof v === "number" && v >= -100 && v <= 100)) fail2(`${k} must be a number from -100 to 100.`);
4000
+ out[k] = v;
4001
+ }
4002
+ const p = raw.process;
4003
+ if (p !== void 0) {
4004
+ if (p !== 1 && p !== 2) fail2("process must be 1 or 2.");
4005
+ out.process = p;
4006
+ }
4007
+ return out;
4008
+ }
4009
+ function requirePixelLayer(layers, id) {
4010
+ const loc = requireLayer(layers, id);
4011
+ if (loc.layer.type !== "pixel") fail2(`Filters belong to pixel layers; "${id}" is a ${loc.layer.type} layer.`);
4012
+ requireUnlocked(loc.layer, "all", "change its filters");
4013
+ return loc.layer;
4014
+ }
4015
+ function addLayerFilter(layers, id, filter) {
4016
+ const layer = requirePixelLayer(layers, id);
4017
+ if (layer.filters?.some((f) => f.kind === filter.kind)) fail2(`Layer "${id}" already has a ${filter.kind} filter; change it with update_layer_filter.`);
4018
+ return mapLayer(layers, id, () => ({ ...layer, filters: [...layer.filters ?? [], filter] }));
4019
+ }
4020
+ function updateLayerFilter(layers, id, filterId, changes) {
4021
+ const layer = requirePixelLayer(layers, id);
4022
+ const i = (layer.filters ?? []).findIndex((f) => f.id === filterId);
4023
+ if (i === -1) fail2(`Layer "${id}" has no filter "${filterId}". Filters: ${(layer.filters ?? []).map((f) => f.id).join(", ") || "none"}.`);
4024
+ const current = layer.filters[i];
4025
+ if ("kind" in changes && changes.kind !== current.kind) fail2("A filter kind cannot change; remove it and add another.");
4026
+ const merged2 = normalizeFilter({ ...current, ...changes, kind: current.kind }, current.id);
4027
+ const filters = [...layer.filters];
4028
+ filters[i] = merged2;
4029
+ return mapLayer(layers, id, () => ({ ...layer, filters }));
4030
+ }
4031
+ function removeLayerFilter(layers, id, filterId) {
4032
+ const layer = requirePixelLayer(layers, id);
4033
+ if (!(layer.filters ?? []).some((f) => f.id === filterId)) fail2(`Layer "${id}" has no filter "${filterId}".`);
4034
+ const filters = layer.filters.filter((f) => f.id !== filterId);
4035
+ const next = { ...layer };
4036
+ if (filters.length) next.filters = filters;
4037
+ else delete next.filters;
4038
+ return mapLayer(layers, id, () => next);
4039
+ }
4040
+ function requireEffectLayer(layers, id, action) {
4041
+ const layer = requireLayer(layers, id).layer;
4042
+ if (layer.type === "adjustment") fail2(`Layer "${layer.name || id}" is an adjustment layer; effects go on pixel, annotation and group layers.`);
4043
+ requireUnlocked(layer, "all", action);
4044
+ return layer;
4045
+ }
4046
+ function layerEffectsOf(layer) {
4047
+ return layer.effects ?? { enabled: true, items: [] };
4048
+ }
4049
+ function guard(fn) {
4050
+ try {
4051
+ return fn();
4052
+ } catch (e) {
4053
+ if (e instanceof LayerEffectError) fail2(e.message);
4054
+ throw e;
4055
+ }
4056
+ }
4057
+ function setLayerEffects(layers, id, effects) {
4058
+ const layer = requireEffectLayer(layers, id, "change its effects");
4059
+ const next = { ...layer };
4060
+ if (effects && (effects.items.length || effects.psd || effects.scale !== void 0 || !effects.enabled)) {
4061
+ guard(() => validateLayerEffects(effects));
4062
+ next.effects = effects;
4063
+ } else delete next.effects;
4064
+ return mapLayer(layers, id, () => next);
4065
+ }
4066
+ function addLayerEffect(layers, id, kind, params = {}, index) {
4067
+ if (!isLayerEffectKind(kind)) fail2(`Unknown effect kind "${String(kind)}". Kinds: ${LAYER_EFFECT_KINDS.join(", ")}.`);
4068
+ const layer = requireEffectLayer(layers, id, "add effects");
4069
+ const current = layerEffectsOf(layer);
4070
+ const ids = new Set(current.items.map((e) => e.id));
4071
+ let effectId = newEffectId();
4072
+ while (ids.has(effectId)) effectId = newEffectId();
4073
+ const { id: _ignored, kind: _kind, ...rest } = params;
4074
+ const effect = guard(() => mergeLayerEffect(defaultLayerEffect(kind, effectId), rest));
4075
+ const items = [...current.items];
4076
+ const at = index === void 0 ? items.length : Math.max(0, Math.min(items.length, Math.floor(index)));
4077
+ items.splice(at, 0, effect);
4078
+ return { layers: setLayerEffects(layers, id, { ...current, items }), effect };
4079
+ }
4080
+ function requireEffect(layer, effectId) {
4081
+ const items = layer.effects?.items ?? [];
4082
+ const i = items.findIndex((e) => e.id === effectId);
4083
+ if (i === -1) fail2(`Layer "${layer.name || layer.id}" has no effect "${effectId}". Effects: ${items.map((e) => `${e.id} (${e.kind})`).join(", ") || "none"}; call list_layer_effects.`);
4084
+ return i;
4085
+ }
4086
+ function updateLayerEffect(layers, id, effectId, changes) {
4087
+ const layer = requireEffectLayer(layers, id, "change its effects");
4088
+ const i = requireEffect(layer, effectId);
4089
+ const current = layer.effects;
4090
+ const items = [...current.items];
4091
+ items[i] = guard(() => mergeLayerEffect(items[i], changes));
4092
+ return setLayerEffects(layers, id, { ...current, items });
4093
+ }
4094
+ function removeLayerEffect(layers, id, effectId) {
4095
+ const layer = requireEffectLayer(layers, id, "change its effects");
4096
+ requireEffect(layer, effectId);
4097
+ const current = layer.effects;
4098
+ return setLayerEffects(layers, id, { ...current, items: current.items.filter((e) => e.id !== effectId) });
4099
+ }
4100
+ function setLayerEffectsEnabled(layers, id, enabled, scale) {
4101
+ const layer = requireEffectLayer(layers, id, "change its effects");
4102
+ if (scale !== void 0 && !(Number.isFinite(scale) && scale >= 0.01 && scale <= 10)) fail2(`scale must be from 0.01 to 10 (1 = 100 %), got ${scale}.`);
4103
+ const current = layerEffectsOf(layer);
4104
+ const next = { ...current, enabled };
4105
+ if (scale !== void 0) {
4106
+ if (scale === 1) delete next.scale;
4107
+ else next.scale = scale;
4108
+ }
4109
+ return setLayerEffects(layers, id, next);
4110
+ }
4111
+ function parseBlend(value, allowPassThrough = false) {
4112
+ if (value === void 0) return void 0;
4113
+ if (value === "pass-through" && allowPassThrough) return value;
4114
+ if (!isBlendMode(value)) fail2(`Unknown blend mode "${String(value)}".`);
4115
+ return value;
4116
+ }
4117
+ function unionRect(a, b) {
4118
+ if (!a) return b;
4119
+ if (!b) return a;
4120
+ const x = Math.min(a.x, b.x);
4121
+ const y = Math.min(a.y, b.y);
4122
+ 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 };
4123
+ }
4124
+
4125
+ // src/image-editor/layers/compositor.ts
4126
+ var legacyViewCache = /* @__PURE__ */ new WeakMap();
4127
+ function adjustmentNodeAsLayer(node) {
4128
+ let view = legacyViewCache.get(node);
4129
+ if (!view) {
4130
+ view = {
4131
+ id: node.id,
4132
+ kind: node.kind,
4133
+ enabled: node.visible,
4134
+ opacity: node.opacity,
4135
+ blend: node.blend,
4136
+ params: node.params
4137
+ };
4138
+ legacyViewCache.set(node, view);
4139
+ }
4140
+ return view;
4141
+ }
4142
+ function developAdjustments(f) {
4143
+ return {
4144
+ brightness: f.brightness,
4145
+ contrast: f.contrast,
4146
+ saturation: f.saturation,
4147
+ exposure: f.exposure,
4148
+ temperature: f.temperature,
4149
+ tint: f.tint ?? 0,
4150
+ blur: 0,
4151
+ opacity: 1,
4152
+ process: f.process
4153
+ };
4154
+ }
4155
+ function applyPixelFilters(data, filters) {
4156
+ for (const f of filters) {
4157
+ if (!isActiveFilter(f) || f.kind !== "develop") continue;
4158
+ const adj = developAdjustments(f);
4159
+ if (normalizeProcessVersion(adj.process) === LATEST_PROCESS_VERSION) applyProcess2Adjustments({ data }, adj);
4160
+ else applyLegacyAdjustments({ data }, adj);
4161
+ }
4162
+ }
4163
+ function depthMaskAlpha(depth, options) {
4164
+ const { mode, depthRange, softness, invert } = options;
4165
+ let v = invert ? 255 - depth : depth;
4166
+ const soft = Math.max(1, softness || 10);
4167
+ const [lo, hi] = depthRange;
4168
+ if (mode === "threshold") {
4169
+ if (v >= hi + soft) return 0;
4170
+ if (v > hi) return Math.round((hi + soft - v) / soft * 255);
4171
+ if (v >= lo) return 255;
4172
+ if (v < lo - soft) return 0;
4173
+ return Math.round((v - (lo - soft)) / soft * 255);
4174
+ }
4175
+ v += lo;
4176
+ return v < 0 ? 0 : v > 255 ? 255 : v;
4177
+ }
4178
+ function runLayerFilters(data, width, height, filters) {
4179
+ for (const f of filters) {
4180
+ if (!isActiveFilter(f)) continue;
4181
+ if (f.kind === "develop") applyPixelFilters(data, [f]);
4182
+ else if (f.kind === "blur") applyBoxBlur({ width, height, data }, Math.min(Math.round(f.radius), 40));
4183
+ }
4184
+ }
4185
+ function runLayerFiltersFloat(data, width, height, filters, linear) {
4186
+ for (const f of filters) {
4187
+ if (!isActiveFilter(f)) continue;
4188
+ if (f.kind === "blur") applyBoxBlurFloat({ width, height, data }, Math.min(Math.round(f.radius), 40));
4189
+ else if (f.kind === "develop") {
4190
+ const adj = developAdjustments(f);
4191
+ if (normalizeProcessVersion(adj.process) === LATEST_PROCESS_VERSION) applyProcess2Float(data, adj, linear);
4192
+ else applyLegacyFloat(data, adj, linear);
4193
+ }
4194
+ }
4195
+ }
4196
+ function filterReach(filters) {
4197
+ let r = 0;
4198
+ for (const f of filters) if (isActiveFilter(f) && f.kind === "blur") r += Math.min(Math.round(f.radius), 40);
4199
+ return r;
4200
+ }
4201
+ var filterSpecCache = /* @__PURE__ */ new WeakMap();
4202
+ function pixelFilterSpec(layer) {
4203
+ const list2 = layer.filters;
4204
+ if (!list2?.length) return null;
4205
+ if (filterSpecCache.has(list2)) return filterSpecCache.get(list2);
4206
+ const filters = list2.filter(isActiveFilter);
4207
+ let spec = null;
4208
+ if (filters.length) {
4209
+ const spatial = filters.some(isSpatialFilter);
4210
+ spec = {
4211
+ key: JSON.stringify(filters),
4212
+ spatial,
4213
+ apply: (data, width, height) => runLayerFilters(data, width, height, filters)
4214
+ };
4215
+ }
4216
+ filterSpecCache.set(list2, spec);
4217
+ return spec;
4218
+ }
4219
+ var PHOTO_PROFILE = {
4220
+ isFilterLayer: (l) => l.type === "adjustment",
4221
+ applyFilterRun: (layers, acc, _r, masks) => {
4222
+ const views = layers.map(adjustmentNodeAsLayer);
4223
+ applyAdjustmentLayers({ data: acc }, views, masks);
4224
+ },
4225
+ contentTile: (l, r, sources) => l.type === "annotation" ? sources.rasterizeAnnotation?.(l.annotation, r) ?? null : null,
4226
+ /** Annotation layers draw with their annotation's blend mode unless the layer sets one. */
4227
+ contentBlend: (l) => l.type === "annotation" && l.blend === "normal" && l.annotation.blend ? l.annotation.blend : l.blend,
4228
+ 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,
4229
+ pixelFilters: (l) => l.type === "pixel" ? pixelFilterSpec(l) : null,
4230
+ /**
4231
+ * A smart object draws its rendered pixels (contents → placement → smart filters) like a pixel
4232
+ * layer, and a shape layer the pixels rendered from its paths.
4233
+ */
4234
+ isPixelLike: (l) => l.type === "smart" || l.type === "shape" || l.type === "type",
4235
+ maskSourcePlane: (source, W, H, sources) => {
4236
+ const opts = source;
4237
+ const depth = sources.depthMap?.(opts) ?? null;
4238
+ if (!depth || depth.width !== W || depth.height !== H) return null;
4239
+ const v = new Float32Array(W * H);
4240
+ for (let i = 0; i < v.length; i++) v[i] = depthMaskAlpha(depth.data[i * 4], opts) / 255;
4241
+ return v;
4242
+ }
4243
+ };
4244
+ var LayerCompositor = class extends RasterCompositor {
4245
+ constructor(options = {}) {
4246
+ super(PHOTO_PROFILE, options);
4247
+ }
4248
+ // Vector masks reach the engine as part of each layer's raster mask (see `resolveVectorMasks`).
4249
+ composite(layers, rect, sources, options) {
4250
+ return super.composite(resolveVectorMasks(layers, sources.width, sources.height), rect, sources, options);
4251
+ }
4252
+ compositeTiles(layers, rect, sources, onTile, options) {
4253
+ super.compositeTiles(resolveVectorMasks(layers, sources.width, sources.height), rect, sources, onTile, options);
4254
+ }
4255
+ tile(layers, tx, ty, sources) {
4256
+ return super.tile(resolveVectorMasks(layers, sources.width, sources.height), tx, ty, sources);
4257
+ }
4258
+ };
4259
+ function compositeDeep(layers, rect, sources, depth) {
4260
+ const deep = new DeepCompositor(new LayerCompositor(), PHOTO_PROFILE, {
4261
+ applyFilterRunDeep: (run, acc, _r, masks) => applyAdjustmentLayersFloat(acc, run.map(adjustmentNodeAsLayer), masks),
4262
+ pixelFiltersDeep: (l) => {
4263
+ if (l.type !== "pixel") return null;
4264
+ const filters = (l.filters ?? []).filter(isActiveFilter);
4265
+ if (!filters.length) return null;
4266
+ return { reach: filterReach(filters), apply: (data, width, height, linear) => runLayerFiltersFloat(data, width, height, filters, linear) };
4267
+ }
4268
+ }, depth);
4269
+ return deep.composite(resolveVectorMasks(layers, sources.width, sources.height), rect, sources);
4116
4270
  }
4117
4271
 
4118
4272
  // src/image-editor/layers/text.ts
@@ -5240,6 +5394,7 @@ function offsetSmartObject(layers, id, dx, dy, canvas) {
5240
5394
  const moveMask = (m2) => m2 && m2.linked !== false && !m2.source ? { ...m2, surface: translateSurface(m2.surface, mx, my) } : m2;
5241
5395
  if (layer.mask) next.mask = moveMask(layer.mask);
5242
5396
  if (layer.smartFilters?.mask) next.smartFilters = { ...layer.smartFilters, mask: moveMask(layer.smartFilters.mask) };
5397
+ next = followLayerMove(next, mx, my);
5243
5398
  const spatial = activeSmartFilters(layer.smartFilters).some((f) => f.kind === "blur") || activeEffectSmartFilters(layer.smartFilters).length > 0;
5244
5399
  const b = contentBounds(layer.surface);
5245
5400
  const m = SMART_RENDER_MARGIN;
@@ -5382,9 +5537,9 @@ function convertToSmartObject(layers, ids, canvas, options) {
5382
5537
  const nested = ordered.map((l) => {
5383
5538
  let layer = l.layer;
5384
5539
  if (layer.type === "pixel" && layer.source === "document") {
5385
- const resolved = { ...layer, surface: options.surfaceOf(layer) };
5386
- delete resolved.source;
5387
- layer = resolved;
5540
+ const resolved2 = { ...layer, surface: options.surfaceOf(layer) };
5541
+ delete resolved2.source;
5542
+ layer = resolved2;
5388
5543
  }
5389
5544
  if (flattenLayers([layer]).some((x) => x.type === "pixel" && x.source === "document")) fail4(`Layer "${layer.name || layer.id}" holds the document image inside a group; convert the Background on its own.`);
5390
5545
  return translateLayer(layer, -b.x, -b.y);
@@ -5676,6 +5831,7 @@ function baseFields(layer, ctx) {
5676
5831
  };
5677
5832
  if (layer.clipped) out.clipped = true;
5678
5833
  if (layer.mask) out.mask = serializeMask(layer.mask, `${layer.id}-mask`, ctx);
5834
+ if (layer.vectorMask) out.vectorMask = serializeVectorMask(layer.vectorMask);
5679
5835
  if (layer.locks && Object.values(layer.locks).some(Boolean)) out.locks = { ...layer.locks };
5680
5836
  if (layer.effects) out.effects = serializeEffects(layer.effects);
5681
5837
  return out;
@@ -5700,6 +5856,36 @@ function deserializeEffects(raw) {
5700
5856
  }
5701
5857
  return normalizeLayerEffects({ ...r, psd });
5702
5858
  }
5859
+ function serializeVectorMask(vm) {
5860
+ const out = { components: JSON.parse(JSON.stringify(vm.components)) };
5861
+ if (vm.fillRule === "evenodd") out.fillRule = "evenodd";
5862
+ if (vm.inverted) out.inverted = true;
5863
+ if (vm.enabled === false) out.enabled = false;
5864
+ if (vm.linked === false) out.linked = false;
5865
+ if (vm.density !== void 0 && vm.density !== 1) out.density = vm.density;
5866
+ if (vm.feather) out.feather = vm.feather;
5867
+ return out;
5868
+ }
5869
+ function readVectorMask(r) {
5870
+ if (!Array.isArray(r.components)) throw new Error("its components are missing");
5871
+ const components = r.components.map((raw, i) => {
5872
+ const c = raw;
5873
+ const op = requireOperation(c?.op, `components[${i}].op`);
5874
+ if (c?.live) {
5875
+ const live = normalizeLiveShape(c.live, `components[${i}].live`);
5876
+ return { op, subpaths: liveShapeSubpaths(live), live };
5877
+ }
5878
+ return { op, subpaths: Array.isArray(c?.subpaths) && !c.subpaths.length ? [] : normalizeSubpaths(c?.subpaths, `components[${i}].subpaths`) };
5879
+ });
5880
+ const vm = { components };
5881
+ if (r.fillRule === "evenodd") vm.fillRule = "evenodd";
5882
+ if (r.inverted === true) vm.inverted = true;
5883
+ if (r.enabled === false) vm.enabled = false;
5884
+ if (r.linked === false) vm.linked = false;
5885
+ if (typeof r.density === "number" && r.density !== 1) vm.density = num2(r.density, 0, 1, 1);
5886
+ if (typeof r.feather === "number" && r.feather > 0) vm.feather = num2(r.feather, 0, 1e3, 0);
5887
+ return vm;
5888
+ }
5703
5889
  function serializeMask(mask, hint, ctx) {
5704
5890
  const out = { defaultValue: mask.defaultValue };
5705
5891
  if (!mask.source) {
@@ -6107,6 +6293,13 @@ function deserializeLayer(raw, ctx) {
6107
6293
  if (r.clipped === true) base.clipped = true;
6108
6294
  const mask = readMask(r.mask, ctx, `Layer "${base.name || id}"`);
6109
6295
  if (mask) base.mask = mask;
6296
+ if (r.vectorMask && typeof r.vectorMask === "object" && r.type !== "shape") {
6297
+ try {
6298
+ base.vectorMask = readVectorMask(r.vectorMask);
6299
+ } catch (err) {
6300
+ ctx.problems.push(`The vector mask of layer "${base.name || id}" could not be read (${err instanceof Error ? err.message : String(err)}); it was left out.`);
6301
+ }
6302
+ }
6110
6303
  if (r.locks && typeof r.locks === "object") {
6111
6304
  const locks = {};
6112
6305
  for (const k of ["pixels", "transparency", "position", "all"]) if (r.locks[k] === true) locks[k] = true;
@@ -7330,97 +7523,6 @@ function writeAdjustmentBlock(kind, params) {
7330
7523
  throw new Error(`Unknown adjustment kind "${kind}".`);
7331
7524
  }
7332
7525
 
7333
- // src/image-editor/psd/channels.ts
7334
- var hexToRgb2 = (hex) => {
7335
- const v = parseInt(/^#?([0-9a-f]{6})$/i.exec(hex)?.[1] ?? "ff0000", 16);
7336
- return [v >> 16 & 255, v >> 8 & 255, v & 255];
7337
- };
7338
- var rgbToHex = (r, g, b) => `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
7339
- function alphaChannelResources(channels) {
7340
- if (!channels.length) return [];
7341
- const names = new PsdWriter();
7342
- for (const c of channels) names.pascal(encodeLegacyName(c.name), 1);
7343
- const unicode = new PsdWriter();
7344
- for (const c of channels) unicode.unicode(c.name);
7345
- const display = new PsdWriter();
7346
- display.u32(1);
7347
- for (const c of channels) {
7348
- const [r, g, b] = hexToRgb2(c.color);
7349
- display.u16(0);
7350
- for (const v of [r, g, b, 0]) display.u16(v * 257);
7351
- display.u16(Math.round(Math.min(1, Math.max(0, c.opacity)) * 100));
7352
- display.u8(c.indicates === "selected" ? 0 : 1);
7353
- display.u8(0);
7354
- }
7355
- return [
7356
- { id: 1006, name: "", data: names.result() },
7357
- { id: 1045, name: "", data: unicode.result() },
7358
- { id: 1077, name: "", data: display.result() }
7359
- ];
7360
- }
7361
- function planeTo8(plane, n, depth) {
7362
- if (depth === 8) return plane.slice(0, n);
7363
- const out = new Uint8Array(n);
7364
- const view = new DataView(plane.buffer, plane.byteOffset, plane.byteLength);
7365
- for (let i = 0; i < n; i++) {
7366
- if (depth === 16) out[i] = plane[2 * i];
7367
- else out[i] = Math.round(Math.min(1, Math.max(0, view.getFloat32(4 * i, false))) * 255);
7368
- }
7369
- return out;
7370
- }
7371
- function readAlphaChannels(planes, header, first, resource2, warn) {
7372
- const count = header.channels - first;
7373
- if (count <= 0 || header.depth === 1) return [];
7374
- const size = rowBytes(header.width, header.depth) * header.height;
7375
- const n = header.width * header.height;
7376
- const names = [];
7377
- const unicode = resource2(1045);
7378
- if (unicode) {
7379
- const r = new PsdReader(unicode);
7380
- while (r.remaining >= 4 && names.length < count) names.push(r.unicode().replace(/\0+$/, ""));
7381
- } else {
7382
- const legacy = resource2(1006);
7383
- if (legacy) {
7384
- const r = new PsdReader(legacy);
7385
- while (r.remaining >= 1 && names.length < count) names.push(new TextDecoder("latin1").decode(r.pascal(1)));
7386
- }
7387
- }
7388
- const display = [];
7389
- const info = resource2(1077);
7390
- if (info && info.length >= 4) {
7391
- const r = new PsdReader(info);
7392
- r.u32();
7393
- while (r.remaining >= 14 && display.length < count) {
7394
- r.u16();
7395
- const [cr, cg, cb] = [r.u16(), r.u16(), r.u16()];
7396
- r.u16();
7397
- const opacity = r.u16();
7398
- const kind = r.u8();
7399
- r.u8();
7400
- display.push({ color: rgbToHex(cr >> 8, cg >> 8, cb >> 8), opacity: Math.min(100, opacity) / 100, kind });
7401
- }
7402
- }
7403
- const out = [];
7404
- for (let i = 0; i < count; i++) {
7405
- const start = (first + i) * size;
7406
- if (start + size > planes.length) break;
7407
- const d = display[i];
7408
- const name = names[i] || `Alpha ${i + 1}`;
7409
- if (d?.kind === 2) {
7410
- warn(`Spot colour channel "${name}" is not supported and was left out.`);
7411
- continue;
7412
- }
7413
- out.push({
7414
- name,
7415
- data: planeTo8(planes.subarray(start, start + size), n, header.depth),
7416
- color: d?.color ?? "#ff0000",
7417
- opacity: d?.opacity ?? 0.5,
7418
- indicates: d?.kind === 0 ? "selected" : "masked"
7419
- });
7420
- }
7421
- return out;
7422
- }
7423
-
7424
7526
  // src/image-editor/psd/paths.ts
7425
7527
  var RECORD = 26;
7426
7528
  var fixed = (v) => v / 16777216;
@@ -7480,6 +7582,8 @@ function writePathRecords(path) {
7480
7582
  return w.result();
7481
7583
  }
7482
7584
  var VMSK_INVERT = 1;
7585
+ var VMSK_NOT_LINKED = 2;
7586
+ var VMSK_DISABLED = 4;
7483
7587
  function readVectorBlock(data) {
7484
7588
  if (data.length < 8) return null;
7485
7589
  const r = new PsdReader(data);
@@ -7550,6 +7654,97 @@ function vectorBlockForComponents(components, inverted, width, height, flags = 0
7550
7654
  return writeVectorBlock({ subpaths, initialFill: subpaths.length === 0 && inverted }, flags & ~VMSK_INVERT | (inverted && subpaths.length ? VMSK_INVERT : 0));
7551
7655
  }
7552
7656
 
7657
+ // src/image-editor/psd/channels.ts
7658
+ var hexToRgb2 = (hex) => {
7659
+ const v = parseInt(/^#?([0-9a-f]{6})$/i.exec(hex)?.[1] ?? "ff0000", 16);
7660
+ return [v >> 16 & 255, v >> 8 & 255, v & 255];
7661
+ };
7662
+ var rgbToHex = (r, g, b) => `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
7663
+ function alphaChannelResources(channels) {
7664
+ if (!channels.length) return [];
7665
+ const names = new PsdWriter();
7666
+ for (const c of channels) names.pascal(encodeLegacyName(c.name), 1);
7667
+ const unicode = new PsdWriter();
7668
+ for (const c of channels) unicode.unicode(c.name);
7669
+ const display = new PsdWriter();
7670
+ display.u32(1);
7671
+ for (const c of channels) {
7672
+ const [r, g, b] = hexToRgb2(c.color);
7673
+ display.u16(0);
7674
+ for (const v of [r, g, b, 0]) display.u16(v * 257);
7675
+ display.u16(Math.round(Math.min(1, Math.max(0, c.opacity)) * 100));
7676
+ display.u8(c.indicates === "selected" ? 0 : 1);
7677
+ display.u8(0);
7678
+ }
7679
+ return [
7680
+ { id: 1006, name: "", data: names.result() },
7681
+ { id: 1045, name: "", data: unicode.result() },
7682
+ { id: 1077, name: "", data: display.result() }
7683
+ ];
7684
+ }
7685
+ function planeTo8(plane, n, depth) {
7686
+ if (depth === 8) return plane.slice(0, n);
7687
+ const out = new Uint8Array(n);
7688
+ const view = new DataView(plane.buffer, plane.byteOffset, plane.byteLength);
7689
+ for (let i = 0; i < n; i++) {
7690
+ if (depth === 16) out[i] = plane[2 * i];
7691
+ else out[i] = Math.round(Math.min(1, Math.max(0, view.getFloat32(4 * i, false))) * 255);
7692
+ }
7693
+ return out;
7694
+ }
7695
+ function readAlphaChannels(planes, header, first, resource2, warn) {
7696
+ const count = header.channels - first;
7697
+ if (count <= 0 || header.depth === 1) return [];
7698
+ const size = rowBytes(header.width, header.depth) * header.height;
7699
+ const n = header.width * header.height;
7700
+ const names = [];
7701
+ const unicode = resource2(1045);
7702
+ if (unicode) {
7703
+ const r = new PsdReader(unicode);
7704
+ while (r.remaining >= 4 && names.length < count) names.push(r.unicode().replace(/\0+$/, ""));
7705
+ } else {
7706
+ const legacy = resource2(1006);
7707
+ if (legacy) {
7708
+ const r = new PsdReader(legacy);
7709
+ while (r.remaining >= 1 && names.length < count) names.push(new TextDecoder("latin1").decode(r.pascal(1)));
7710
+ }
7711
+ }
7712
+ const display = [];
7713
+ const info = resource2(1077);
7714
+ if (info && info.length >= 4) {
7715
+ const r = new PsdReader(info);
7716
+ r.u32();
7717
+ while (r.remaining >= 14 && display.length < count) {
7718
+ r.u16();
7719
+ const [cr, cg, cb] = [r.u16(), r.u16(), r.u16()];
7720
+ r.u16();
7721
+ const opacity = r.u16();
7722
+ const kind = r.u8();
7723
+ r.u8();
7724
+ display.push({ color: rgbToHex(cr >> 8, cg >> 8, cb >> 8), opacity: Math.min(100, opacity) / 100, kind });
7725
+ }
7726
+ }
7727
+ const out = [];
7728
+ for (let i = 0; i < count; i++) {
7729
+ const start = (first + i) * size;
7730
+ if (start + size > planes.length) break;
7731
+ const d = display[i];
7732
+ const name = names[i] || `Alpha ${i + 1}`;
7733
+ if (d?.kind === 2) {
7734
+ warn(`Spot colour channel "${name}" is not supported and was left out.`);
7735
+ continue;
7736
+ }
7737
+ out.push({
7738
+ name,
7739
+ data: planeTo8(planes.subarray(start, start + size), n, header.depth),
7740
+ color: d?.color ?? "#ff0000",
7741
+ opacity: d?.opacity ?? 0.5,
7742
+ indicates: d?.kind === 0 ? "selected" : "masked"
7743
+ });
7744
+ }
7745
+ return out;
7746
+ }
7747
+
7553
7748
  // src/image-editor/psd/shapes.ts
7554
7749
  var SHAPE_PSD_KEYS = ["vsms", "vmsk", "vscg", "vstk", "vogk", "SoCo", "GdFl", "PtFl"];
7555
7750
  var unit = (u, value) => ({ type: "UntF", unit: u, value });
@@ -10181,9 +10376,22 @@ function baseProps(ctx, rec, name, notes) {
10181
10376
  ctx.warn.add("Artboards are not supported; they came in as plain groups, without their background colour and clipping", name);
10182
10377
  notes.push("artboard imported as a group");
10183
10378
  }
10184
- if (rec.blocks.some((b) => VECTOR_KEYS.includes(b.key)) && psdKind(rec) === "pixel") {
10185
- ctx.warn.add("Vector masks are not supported and were ignored", name);
10186
- notes.push("vector mask ignored");
10379
+ const vectorBlock = psdKind(rec) === "shape" ? void 0 : rec.blocks.find((b) => b.key === "vsms") ?? rec.blocks.find((b) => b.key === "vmsk");
10380
+ if (vectorBlock) {
10381
+ const read2 = componentsFromVectorBlock(vectorBlock.data, ctx.file.header.width, ctx.file.header.height);
10382
+ if (read2) {
10383
+ const vm = { components: read2.components };
10384
+ if (read2.inverted) vm.inverted = true;
10385
+ if (read2.flags & VMSK_NOT_LINKED) vm.linked = false;
10386
+ if (read2.flags & VMSK_DISABLED) vm.enabled = false;
10387
+ const m = rec.mask;
10388
+ if (m?.vectorDensity !== void 0 && m.vectorDensity < 255) vm.density = percent(m.vectorDensity);
10389
+ if (m?.vectorFeather !== void 0 && m.vectorFeather > 0) vm.feather = Math.min(1e3, m.vectorFeather);
10390
+ props.vectorMask = vm;
10391
+ } else {
10392
+ ctx.warn.add("A vector mask could not be read and was ignored", name);
10393
+ notes.push("vector mask ignored");
10394
+ }
10187
10395
  }
10188
10396
  return props;
10189
10397
  }
@@ -11532,6 +11740,13 @@ function exportPsd(input) {
11532
11740
  if (fx) blocks.push(fx);
11533
11741
  const locks = lockBits(layer);
11534
11742
  if (locks) blocks.push({ key: "lspf", data: u32Bytes(locks >>> 0) });
11743
+ const vm = layer.type !== "shape" ? layer.vectorMask : void 0;
11744
+ if (vm) {
11745
+ const flags = (vm.linked === false ? VMSK_NOT_LINKED : 0) | (vm.enabled === false ? VMSK_DISABLED : 0);
11746
+ blocks.push({ key: "vmsk", data: vectorBlockForComponents(vm.components, vm.inverted === true, width, height, flags) });
11747
+ if ((vm.density ?? 1) !== 1 || (vm.feather ?? 0) > 0) note("Vector mask density and feather are not written; the paths are", layer.name);
11748
+ if (vm.fillRule === "evenodd") note("Vector masks are written with the nonzero fill rule (Photoshop has no even-odd paths)", layer.name);
11749
+ }
11535
11750
  return {
11536
11751
  opacity: Math.round(Math.min(1, Math.max(0, layer.opacity)) * 255),
11537
11752
  clipping: layer.clipped ? 1 : 0,
@@ -11870,6 +12085,7 @@ export {
11870
12085
  shiftCoverage,
11871
12086
  SELECTION_MODIFY_OPS,
11872
12087
  modifySelection,
12088
+ layerMaskSelection,
11873
12089
  normalizeLayerMask,
11874
12090
  layerMaskFromSelection,
11875
12091
  updateLayerMaskProps,
@@ -11926,6 +12142,8 @@ export {
11926
12142
  applyBoxBlur,
11927
12143
  applyDepthMask,
11928
12144
  removeAlpha,
12145
+ flattenPath,
12146
+ rasterizeCoverage,
11929
12147
  TEXT_ALIGNS,
11930
12148
  MAX_EFFECT_ANIMATION_MS,
11931
12149
  SHAPE_OPERATIONS,
@@ -11942,17 +12160,6 @@ export {
11942
12160
  isRenderedLayer,
11943
12161
  shapeLayers,
11944
12162
  smartObjectLayers,
11945
- adjustmentNodeAsLayer,
11946
- depthMaskAlpha,
11947
- runLayerFilters,
11948
- runLayerFiltersFloat,
11949
- LayerCompositor,
11950
- compositeDeep,
11951
- DEFAULT_ADJUSTMENTS_VALUES,
11952
- DEFAULT_TRANSFORM_VALUES,
11953
- DEFAULT_DEPTH_MASK_VALUES,
11954
- flattenPath,
11955
- rasterizeCoverage,
11956
12163
  LayerOperationError,
11957
12164
  userErrorMessage,
11958
12165
  isPaintableLayer,
@@ -11990,6 +12197,7 @@ export {
11990
12197
  componentPath,
11991
12198
  shapeOutline,
11992
12199
  shapeGeometryBounds,
12200
+ componentsCoverage,
11993
12201
  shapeRenderClip,
11994
12202
  renderShapeRect,
11995
12203
  renderShape,
@@ -12010,6 +12218,24 @@ export {
12010
12218
  shapeFromAnnotation,
12011
12219
  annotationBlend,
12012
12220
  annotationOfShape,
12221
+ isActiveVectorMask,
12222
+ vectorMaskCoverage,
12223
+ effectiveLayerMask,
12224
+ resolveVectorMasks,
12225
+ offsetVectorMask,
12226
+ transformVectorMask,
12227
+ followLayerMove,
12228
+ vectorMaskBounds,
12229
+ vectorMaskThumbnail,
12230
+ adjustmentNodeAsLayer,
12231
+ depthMaskAlpha,
12232
+ runLayerFilters,
12233
+ runLayerFiltersFloat,
12234
+ LayerCompositor,
12235
+ compositeDeep,
12236
+ DEFAULT_ADJUSTMENTS_VALUES,
12237
+ DEFAULT_TRANSFORM_VALUES,
12238
+ DEFAULT_DEPTH_MASK_VALUES,
12013
12239
  TEXT_RENDER_MARGIN,
12014
12240
  textMatrix,
12015
12241
  textOutlines,