@pixerate/editor 0.189.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 (31) hide show
  1. package/dist/{chunk-ZEHTUR7Z.js → chunk-7ZWLDVFH.js} +971 -753
  2. package/dist/{chunk-ED4L4UD6.cjs → chunk-T4U4O6DS.cjs} +1005 -787
  3. package/dist/image-editor/controller.d.cts +51 -1
  4. package/dist/image-editor/controller.d.ts +51 -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 +1219 -901
  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 +321 -3
  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/panel.d.cts +11 -1
  18. package/dist/image-editor/layers/panel.d.ts +11 -1
  19. package/dist/image-editor/layers/serialize.d.cts +11 -0
  20. package/dist/image-editor/layers/serialize.d.ts +11 -0
  21. package/dist/image-editor/layers/shape.d.cts +5 -0
  22. package/dist/image-editor/layers/shape.d.ts +5 -0
  23. package/dist/image-editor/layers/vector-mask.d.cts +51 -0
  24. package/dist/image-editor/layers/vector-mask.d.ts +51 -0
  25. package/dist/image-editor/psd/import.d.cts +0 -7
  26. package/dist/image-editor/psd/import.d.ts +0 -7
  27. package/dist/paint-editor/index.cjs +6 -6
  28. package/dist/paint-editor/index.js +1 -1
  29. package/package.json +1 -1
  30. package/skills/pixerate-image-editor/SKILL.md +6 -0
  31. package/skills/pixerate-image-selection-masking/SKILL.md +6 -2
@@ -2589,192 +2589,6 @@ function removeAlpha(imageData) {
2589
2589
  }
2590
2590
  }
2591
2591
 
2592
- // src/text/types.ts
2593
- var TEXT_ALIGNS = ["left", "center", "right", "justify"];
2594
-
2595
- // src/image-editor/layers/model.ts
2596
- var MAX_EFFECT_ANIMATION_MS = 6e4;
2597
- var SHAPE_OPERATIONS = ["combine", "subtract", "intersect", "exclude"];
2598
- var LIVE_SHAPE_KINDS = ["rect", "ellipse", "polygon", "star", "line"];
2599
- var idCounter = 0;
2600
- function newLayerId(prefix = "layer") {
2601
- return `${prefix}_${Date.now().toString(36)}_${(++idCounter).toString(36)}`;
2602
- }
2603
- function pixelLayers(layers) {
2604
- return flattenLayers(layers).filter((l) => l.type === "pixel");
2605
- }
2606
- function developFilter(layer) {
2607
- return layer.filters?.find((f) => f.kind === "develop");
2608
- }
2609
- function blurFilter(layer) {
2610
- return layer.filters?.find((f) => f.kind === "blur");
2611
- }
2612
- function isActiveFilter(f) {
2613
- if (!f.enabled) return false;
2614
- if (f.kind === "blur") return Math.min(Math.round(f.radius), 40) >= 1;
2615
- return Boolean(f.brightness || f.contrast || f.saturation || f.exposure || f.temperature || f.tint);
2616
- }
2617
- function isSpatialFilter(f) {
2618
- return f.kind === "blur";
2619
- }
2620
- function pixelLayerBounds(layer, surface = layer.surface) {
2621
- const b = surface.bounds;
2622
- if (!b) return null;
2623
- return { x: b.x + layer.offset.x, y: b.y + layer.offset.y, width: b.width, height: b.height };
2624
- }
2625
- function layerTypeLabel(type) {
2626
- 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";
2627
- }
2628
- function hasSurface(layer) {
2629
- return layer.type === "pixel" || layer.type === "smart" || layer.type === "shape" || layer.type === "type";
2630
- }
2631
- function isRenderedLayer(layer) {
2632
- return layer.type === "smart" || layer.type === "shape" || layer.type === "type";
2633
- }
2634
- function shapeLayers(layers) {
2635
- return flattenLayers(layers).filter((l) => l.type === "shape");
2636
- }
2637
- function smartObjectLayers(layers) {
2638
- return flattenLayers(layers).filter((l) => l.type === "smart");
2639
- }
2640
-
2641
- // src/image-editor/layers/compositor.ts
2642
- var legacyViewCache = /* @__PURE__ */ new WeakMap();
2643
- function adjustmentNodeAsLayer(node) {
2644
- let view = legacyViewCache.get(node);
2645
- if (!view) {
2646
- view = {
2647
- id: node.id,
2648
- kind: node.kind,
2649
- enabled: node.visible,
2650
- opacity: node.opacity,
2651
- blend: node.blend,
2652
- params: node.params
2653
- };
2654
- legacyViewCache.set(node, view);
2655
- }
2656
- return view;
2657
- }
2658
- function developAdjustments(f) {
2659
- return {
2660
- brightness: f.brightness,
2661
- contrast: f.contrast,
2662
- saturation: f.saturation,
2663
- exposure: f.exposure,
2664
- temperature: f.temperature,
2665
- tint: f.tint ?? 0,
2666
- blur: 0,
2667
- opacity: 1,
2668
- process: f.process
2669
- };
2670
- }
2671
- function applyPixelFilters(data, filters) {
2672
- for (const f of filters) {
2673
- if (!isActiveFilter(f) || f.kind !== "develop") continue;
2674
- const adj = developAdjustments(f);
2675
- if (normalizeProcessVersion(adj.process) === LATEST_PROCESS_VERSION) applyProcess2Adjustments({ data }, adj);
2676
- else applyLegacyAdjustments({ data }, adj);
2677
- }
2678
- }
2679
- function depthMaskAlpha(depth, options) {
2680
- const { mode, depthRange, softness, invert } = options;
2681
- let v = invert ? 255 - depth : depth;
2682
- const soft = Math.max(1, softness || 10);
2683
- const [lo, hi] = depthRange;
2684
- if (mode === "threshold") {
2685
- if (v >= hi + soft) return 0;
2686
- if (v > hi) return Math.round((hi + soft - v) / soft * 255);
2687
- if (v >= lo) return 255;
2688
- if (v < lo - soft) return 0;
2689
- return Math.round((v - (lo - soft)) / soft * 255);
2690
- }
2691
- v += lo;
2692
- return v < 0 ? 0 : v > 255 ? 255 : v;
2693
- }
2694
- function runLayerFilters(data, width, height, filters) {
2695
- for (const f of filters) {
2696
- if (!isActiveFilter(f)) continue;
2697
- if (f.kind === "develop") applyPixelFilters(data, [f]);
2698
- else if (f.kind === "blur") applyBoxBlur({ width, height, data }, Math.min(Math.round(f.radius), 40));
2699
- }
2700
- }
2701
- function runLayerFiltersFloat(data, width, height, filters, linear) {
2702
- for (const f of filters) {
2703
- if (!isActiveFilter(f)) continue;
2704
- if (f.kind === "blur") applyBoxBlurFloat({ width, height, data }, Math.min(Math.round(f.radius), 40));
2705
- else if (f.kind === "develop") {
2706
- const adj = developAdjustments(f);
2707
- if (normalizeProcessVersion(adj.process) === LATEST_PROCESS_VERSION) applyProcess2Float(data, adj, linear);
2708
- else applyLegacyFloat(data, adj, linear);
2709
- }
2710
- }
2711
- }
2712
- function filterReach(filters) {
2713
- let r = 0;
2714
- for (const f of filters) if (isActiveFilter(f) && f.kind === "blur") r += Math.min(Math.round(f.radius), 40);
2715
- return r;
2716
- }
2717
- var filterSpecCache = /* @__PURE__ */ new WeakMap();
2718
- function pixelFilterSpec(layer) {
2719
- const list2 = layer.filters;
2720
- if (!list2?.length) return null;
2721
- if (filterSpecCache.has(list2)) return filterSpecCache.get(list2);
2722
- const filters = list2.filter(isActiveFilter);
2723
- let spec = null;
2724
- if (filters.length) {
2725
- const spatial = filters.some(isSpatialFilter);
2726
- spec = {
2727
- key: JSON.stringify(filters),
2728
- spatial,
2729
- apply: (data, width, height) => runLayerFilters(data, width, height, filters)
2730
- };
2731
- }
2732
- filterSpecCache.set(list2, spec);
2733
- return spec;
2734
- }
2735
- var PHOTO_PROFILE = {
2736
- isFilterLayer: (l) => l.type === "adjustment",
2737
- applyFilterRun: (layers, acc, _r, masks) => {
2738
- const views = layers.map(adjustmentNodeAsLayer);
2739
- applyAdjustmentLayers({ data: acc }, views, masks);
2740
- },
2741
- contentTile: (l, r, sources) => l.type === "annotation" ? sources.rasterizeAnnotation?.(l.annotation, r) ?? null : null,
2742
- /** Annotation layers draw with their annotation's blend mode unless the layer sets one. */
2743
- contentBlend: (l) => l.type === "annotation" && l.blend === "normal" && l.annotation.blend ? l.annotation.blend : l.blend,
2744
- 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,
2745
- pixelFilters: (l) => l.type === "pixel" ? pixelFilterSpec(l) : null,
2746
- /**
2747
- * A smart object draws its rendered pixels (contents → placement → smart filters) like a pixel
2748
- * layer, and a shape layer the pixels rendered from its paths.
2749
- */
2750
- isPixelLike: (l) => l.type === "smart" || l.type === "shape" || l.type === "type",
2751
- maskSourcePlane: (source, W, H, sources) => {
2752
- const opts = source;
2753
- const depth = sources.depthMap?.(opts) ?? null;
2754
- if (!depth || depth.width !== W || depth.height !== H) return null;
2755
- const v = new Float32Array(W * H);
2756
- for (let i = 0; i < v.length; i++) v[i] = depthMaskAlpha(depth.data[i * 4], opts) / 255;
2757
- return v;
2758
- }
2759
- };
2760
- var LayerCompositor = class extends RasterCompositor {
2761
- constructor(options = {}) {
2762
- super(PHOTO_PROFILE, options);
2763
- }
2764
- };
2765
- function compositeDeep(layers, rect, sources, depth) {
2766
- const deep = new DeepCompositor(new LayerCompositor(), PHOTO_PROFILE, {
2767
- applyFilterRunDeep: (run, acc, _r, masks) => applyAdjustmentLayersFloat(acc, run.map(adjustmentNodeAsLayer), masks),
2768
- pixelFiltersDeep: (l) => {
2769
- if (l.type !== "pixel") return null;
2770
- const filters = (l.filters ?? []).filter(isActiveFilter);
2771
- if (!filters.length) return null;
2772
- return { reach: filterReach(filters), apply: (data, width, height, linear) => runLayerFiltersFloat(data, width, height, filters, linear) };
2773
- }
2774
- }, depth);
2775
- return deep.composite(layers, rect, sources);
2776
- }
2777
-
2778
2592
  // src/image-editor/layers/vector-raster.ts
2779
2593
  var FLATNESS = 0.01;
2780
2594
  function flattenPath(path, tolerance = FLATNESS) {
@@ -3108,439 +2922,143 @@ function combineCoverage(a, b, op, n) {
3108
2922
  return out;
3109
2923
  }
3110
2924
 
3111
- // src/image-editor/layers/ops.ts
3112
- var LayerOperationError = class extends Error {
3113
- userMessage;
3114
- constructor(message, userMessage) {
3115
- super(message);
3116
- this.name = "LayerOperationError";
3117
- if (userMessage) this.userMessage = userMessage;
3118
- }
3119
- };
3120
- function userErrorMessage(err) {
3121
- if (err instanceof LayerOperationError && err.userMessage) return err.userMessage;
3122
- return err instanceof Error ? err.message : String(err);
3123
- }
3124
- function isPaintableLayer(layer) {
3125
- 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)}`;
3126
2935
  }
3127
- function defaultActiveLayerId(layers) {
3128
- const pixels = flattenLayers(layers).filter((l) => l.type === "pixel");
3129
- for (let i = pixels.length - 1; i >= 0; i--) if (isPaintableLayer(pixels[i])) return pixels[i].id;
3130
- return pixels[pixels.length - 1]?.id ?? null;
2936
+ function pixelLayers(layers) {
2937
+ return flattenLayers(layers).filter((l) => l.type === "pixel");
3131
2938
  }
3132
- function activeLayerAfterRemoval(prev, next, removedId) {
3133
- const before = flattenLayers(prev);
3134
- const at = before.findIndex((l) => l.id === removedId);
3135
- for (let i = at - 1; i >= 0; i--) {
3136
- const loc = findLayer(next, before[i].id);
3137
- if (loc && isPaintableLayer(loc.layer)) return loc.layer.id;
3138
- }
3139
- return defaultActiveLayerId(next);
2939
+ function developFilter(layer) {
2940
+ return layer.filters?.find((f) => f.kind === "develop");
3140
2941
  }
3141
- var fail = (message) => {
3142
- throw new LayerOperationError(message);
3143
- };
3144
- function requireLayer(layers, id) {
3145
- const loc = findLayer(layers, id);
3146
- if (!loc) {
3147
- const ids = flattenLayers(layers).map((l) => l.id);
3148
- fail(`Unknown layer "${id}". ${ids.length ? `Layers: ${ids.join(", ")}.` : "The document has no layers."} Call list_layers for ids.`);
3149
- }
3150
- return loc;
2942
+ function blurFilter(layer) {
2943
+ return layer.filters?.find((f) => f.kind === "blur");
3151
2944
  }
3152
- function requireUnlocked(layer, what, action) {
3153
- if (isLayerLocked(layer, "all")) fail(`Layer "${layer.name || layer.id}" is locked (all); unlock it before you ${action}.`);
3154
- 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);
3155
2949
  }
3156
- function placeLayer(layers, layer, placement = {}, defaultIndex) {
3157
- const given = [placement.aboveId, placement.belowId, placement.parentId].filter((v) => v !== void 0).length;
3158
- if (given > 1) fail("Give only one of aboveId, belowId and parentId.");
3159
- if (placement.aboveId || placement.belowId) {
3160
- const ref = requireLayer(layers, placement.aboveId ?? placement.belowId);
3161
- return insertLayer(layers, layer, ref.parent?.id ?? null, ref.index + (placement.aboveId ? 1 : 0));
3162
- }
3163
- if (placement.parentId) {
3164
- const ref = requireLayer(layers, placement.parentId);
3165
- if (ref.layer.type !== "group") fail(`Layer "${placement.parentId}" is not a group; parentId must be a group id.`);
3166
- return insertLayer(layers, layer, ref.layer.id, placement.index);
3167
- }
3168
- return insertLayer(layers, layer, null, placement.index ?? defaultIndex);
2950
+ function isSpatialFilter(f) {
2951
+ return f.kind === "blur";
3169
2952
  }
3170
- function removeLayer(layers, id) {
3171
- const loc = requireLayer(layers, id);
3172
- requireUnlocked(loc.layer, "all", "delete it");
3173
- return mapLayer(layers, id, () => null);
3174
- }
3175
- function moveLayer(layers, id, placement) {
3176
- const loc = requireLayer(layers, id);
3177
- requireUnlocked(loc.layer, "all", "move it");
3178
- const target = placement.aboveId ?? placement.belowId ?? placement.parentId;
3179
- if (target === id) fail("A layer cannot be placed relative to itself.");
3180
- if (target && loc.layer.type === "group" && flattenLayers(loc.layer.children).some((l) => l.id === target)) {
3181
- fail(`Group "${id}" cannot be moved into its own child "${target}".`);
3182
- }
3183
- if (target) requireLayer(layers, target);
3184
- if (!target && placement.index === void 0) fail("Give aboveId, belowId, parentId or index.");
3185
- const without = mapLayer(layers, id, () => null);
3186
- 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 };
3187
2957
  }
3188
- function withNewIds(layer) {
3189
- const next = { ...layer, id: newLayerId(layer.type === "adjustment" ? "adj" : layer.type) };
3190
- if (next.type === "group") next.children = next.children.map(withNewIds);
3191
- if (next.type === "annotation") next.annotation = { ...next.annotation, id: next.id };
3192
- if (next.type === "shape" || next.type === "type") delete next.annotation;
3193
- if (next.type === "smart" && next.psd?.placed) {
3194
- next.psd = { ...next.psd };
3195
- delete next.psd.placed;
3196
- }
3197
- 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";
3198
2960
  }
3199
- function duplicateLayer(layers, id, name) {
3200
- const loc = requireLayer(layers, id);
3201
- const copy = withNewIds(loc.layer);
3202
- copy.name = name ?? `${loc.layer.name || "Layer"} copy`;
3203
- delete copy.locks;
3204
- 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";
3205
2963
  }
3206
- function setLayerProperties(layers, id, props) {
3207
- const loc = requireLayer(layers, id);
3208
- const layer = loc.layer;
3209
- const onlyUnlocking = Object.keys(props).every((k) => k === "locks" || k === "visible" || k === "collapsed");
3210
- if (!onlyUnlocking) requireUnlocked(layer, "all", "change its properties");
3211
- const next = { ...layer };
3212
- if (props.name !== void 0) {
3213
- if (typeof props.name !== "string" || !props.name.trim()) fail("name must be a non-empty string.");
3214
- next.name = props.name.trim();
3215
- }
3216
- if (props.visible !== void 0) next.visible = Boolean(props.visible);
3217
- if (props.opacity !== void 0) {
3218
- if (!(typeof props.opacity === "number" && props.opacity >= 0 && props.opacity <= 1)) fail("opacity must be a number from 0 to 1.");
3219
- next.opacity = props.opacity;
3220
- }
3221
- if (props.fillOpacity !== void 0) {
3222
- 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.`);
3223
- if (!(typeof props.fillOpacity === "number" && props.fillOpacity >= 0 && props.fillOpacity <= 1)) fail("fillOpacity must be a number from 0 to 1.");
3224
- const p = next;
3225
- if (props.fillOpacity === 1) delete p.fillOpacity;
3226
- else p.fillOpacity = props.fillOpacity;
3227
- }
3228
- if (props.blend !== void 0) {
3229
- if (props.blend === "pass-through") {
3230
- if (layer.type !== "group") fail("pass-through is only for groups.");
3231
- } else if (!isBlendMode(props.blend)) fail(`Unknown blend mode "${props.blend}".`);
3232
- next.blend = props.blend;
3233
- }
3234
- if (props.clipped !== void 0) {
3235
- if (props.clipped && loc.index === 0) fail("The bottom layer of a group or the stack has nothing below to clip to.");
3236
- if (props.clipped) next.clipped = true;
3237
- else delete next.clipped;
3238
- }
3239
- if (props.locks !== void 0) {
3240
- const locks = {};
3241
- for (const k of ["pixels", "transparency", "position", "all"]) if (props.locks[k]) locks[k] = true;
3242
- if (Object.keys(locks).length) next.locks = locks;
3243
- else delete next.locks;
3244
- }
3245
- if (props.collapsed !== void 0) {
3246
- if (layer.type !== "group") fail("collapsed applies to groups.");
3247
- const g = next;
3248
- if (props.collapsed) g.collapsed = true;
3249
- else delete g.collapsed;
3250
- }
3251
- return mapLayer(layers, id, () => next);
2964
+ function isRenderedLayer(layer) {
2965
+ return layer.type === "smart" || layer.type === "shape" || layer.type === "type";
3252
2966
  }
3253
- function groupLayers(layers, ids, name = "Group") {
3254
- if (!ids.length) fail("Give at least one layer id to group.");
3255
- const locs = ids.map((id) => requireLayer(layers, id));
3256
- const parent = locs[0].parent?.id ?? null;
3257
- if (locs.some((l) => (l.parent?.id ?? null) !== parent)) fail("Layers to group must share one parent; move them next to each other first.");
3258
- for (const l of locs) requireUnlocked(l.layer, "all", "group it");
3259
- const ordered = [...new Set(locs)].sort((a, b) => a.index - b.index);
3260
- const top = ordered[ordered.length - 1].index;
3261
- const below = ordered.filter((l) => l.index < top).length;
3262
- const group = {
3263
- type: "group",
3264
- id: newLayerId("group"),
3265
- name,
3266
- visible: true,
3267
- opacity: 1,
3268
- blend: "pass-through",
3269
- children: ordered.map((l) => {
3270
- const c = { ...l.layer };
3271
- return c;
3272
- })
3273
- };
3274
- if (group.children[0]?.clipped) {
3275
- const c = { ...group.children[0] };
3276
- delete c.clipped;
3277
- group.children[0] = c;
3278
- }
3279
- let out = layers;
3280
- for (const l of ordered) out = mapLayer(out, l.layer.id, () => null);
3281
- return { layers: insertLayer(out, group, parent, top - below), id: group.id };
2967
+ function shapeLayers(layers) {
2968
+ return flattenLayers(layers).filter((l) => l.type === "shape");
3282
2969
  }
3283
- function ungroupLayer(layers, id) {
3284
- const loc = requireLayer(layers, id);
3285
- if (loc.layer.type !== "group") fail(`Layer "${id}" is not a group.`);
3286
- requireUnlocked(loc.layer, "all", "ungroup it");
3287
- const group = loc.layer;
3288
- let out = mapLayer(layers, id, () => null);
3289
- group.children.forEach((c, i) => {
3290
- out = insertLayer(out, c, loc.parent?.id ?? null, loc.index + i);
3291
- });
3292
- return { layers: out, ids: group.children.map((c) => c.id) };
2970
+ function smartObjectLayers(layers) {
2971
+ return flattenLayers(layers).filter((l) => l.type === "smart");
3293
2972
  }
3294
- function offsetLayer(layers, id, offset) {
3295
- const loc = requireLayer(layers, id);
3296
- if (loc.layer.type !== "pixel") fail(`Only pixel layers move; "${id}" is a ${loc.layer.type} layer.`);
3297
- requireUnlocked(loc.layer, "position", "move it");
3298
- if (!Number.isFinite(offset.x) || !Number.isFinite(offset.y)) fail("x and y must be numbers.");
3299
- const layer = loc.layer;
3300
- const next = { ...layer, offset: { x: Math.round(offset.x), y: Math.round(offset.y) } };
3301
- const mask = layer.mask;
3302
- if (mask && mask.linked === false && !mask.source) {
3303
- 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;
3304
2986
  }
3305
- 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 })) };
3306
2992
  }
3307
- function setLayerMask(layers, id, mask) {
3308
- const loc = requireLayer(layers, id);
3309
- requireUnlocked(loc.layer, "all", "change its mask");
3310
- return mapLayer(layers, id, (l) => {
3311
- const next = { ...l };
3312
- if (mask) next.mask = mask;
3313
- else delete next.mask;
3314
- return next;
3315
- });
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);
3316
3004
  }
3317
- var DEVELOP_FIELDS = ["brightness", "contrast", "saturation", "exposure", "temperature", "tint"];
3318
- function normalizeFilter(raw, id = newLayerId("flt")) {
3319
- const enabled = raw.enabled !== false;
3320
- if (raw.kind === "blur") {
3321
- const r = raw.radius;
3322
- if (r !== void 0 && !(typeof r === "number" && r >= 0 && r <= 50)) fail("blur radius must be a number from 0 to 50 px.");
3323
- return { id, kind: "blur", enabled, radius: typeof r === "number" ? r : 2 };
3324
- }
3325
- if (raw.kind !== "develop") fail(`Unknown filter kind "${raw.kind}". Use develop or blur.`);
3326
- const out = { id, kind: "develop", enabled, brightness: 0, contrast: 0, saturation: 0, exposure: 0, temperature: 0, tint: 0, process: 2 };
3327
- for (const k of DEVELOP_FIELDS) {
3328
- const v = raw[k];
3329
- if (v === void 0) continue;
3330
- if (!(typeof v === "number" && v >= -100 && v <= 100)) fail(`${k} must be a number from -100 to 100.`);
3331
- out[k] = v;
3332
- }
3333
- const p = raw.process;
3334
- if (p !== void 0) {
3335
- if (p !== 1 && p !== 2) fail("process must be 1 or 2.");
3336
- out.process = p;
3337
- }
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 };
3338
3012
  return out;
3339
3013
  }
3340
- function requirePixelLayer(layers, id) {
3341
- const loc = requireLayer(layers, id);
3342
- if (loc.layer.type !== "pixel") fail(`Filters belong to pixel layers; "${id}" is a ${loc.layer.type} layer.`);
3343
- requireUnlocked(loc.layer, "all", "change its filters");
3344
- 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)) }));
3345
3016
  }
3346
- function addLayerFilter(layers, id, filter) {
3347
- const layer = requirePixelLayer(layers, id);
3348
- if (layer.filters?.some((f) => f.kind === filter.kind)) fail(`Layer "${id}" already has a ${filter.kind} filter; change it with update_layer_filter.`);
3349
- 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;
3350
3059
  }
3351
- function updateLayerFilter(layers, id, filterId, changes) {
3352
- const layer = requirePixelLayer(layers, id);
3353
- const i = (layer.filters ?? []).findIndex((f) => f.id === filterId);
3354
- if (i === -1) fail(`Layer "${id}" has no filter "${filterId}". Filters: ${(layer.filters ?? []).map((f) => f.id).join(", ") || "none"}.`);
3355
- const current = layer.filters[i];
3356
- if ("kind" in changes && changes.kind !== current.kind) fail("A filter kind cannot change; remove it and add another.");
3357
- const merged2 = normalizeFilter({ ...current, ...changes, kind: current.kind }, current.id);
3358
- const filters = [...layer.filters];
3359
- filters[i] = merged2;
3360
- return mapLayer(layers, id, () => ({ ...layer, filters }));
3361
- }
3362
- function removeLayerFilter(layers, id, filterId) {
3363
- const layer = requirePixelLayer(layers, id);
3364
- if (!(layer.filters ?? []).some((f) => f.id === filterId)) fail(`Layer "${id}" has no filter "${filterId}".`);
3365
- const filters = layer.filters.filter((f) => f.id !== filterId);
3366
- const next = { ...layer };
3367
- if (filters.length) next.filters = filters;
3368
- else delete next.filters;
3369
- return mapLayer(layers, id, () => next);
3370
- }
3371
- function requireEffectLayer(layers, id, action) {
3372
- const layer = requireLayer(layers, id).layer;
3373
- if (layer.type === "adjustment") fail(`Layer "${layer.name || id}" is an adjustment layer; effects go on pixel, annotation and group layers.`);
3374
- requireUnlocked(layer, "all", action);
3375
- return layer;
3376
- }
3377
- function layerEffectsOf(layer) {
3378
- return layer.effects ?? { enabled: true, items: [] };
3379
- }
3380
- function guard(fn) {
3381
- try {
3382
- return fn();
3383
- } catch (e) {
3384
- if (e instanceof LayerEffectError) fail(e.message);
3385
- throw e;
3386
- }
3387
- }
3388
- function setLayerEffects(layers, id, effects) {
3389
- const layer = requireEffectLayer(layers, id, "change its effects");
3390
- const next = { ...layer };
3391
- if (effects && (effects.items.length || effects.psd || effects.scale !== void 0 || !effects.enabled)) {
3392
- guard(() => validateLayerEffects(effects));
3393
- next.effects = effects;
3394
- } else delete next.effects;
3395
- return mapLayer(layers, id, () => next);
3396
- }
3397
- function addLayerEffect(layers, id, kind, params = {}, index) {
3398
- if (!isLayerEffectKind(kind)) fail(`Unknown effect kind "${String(kind)}". Kinds: ${LAYER_EFFECT_KINDS.join(", ")}.`);
3399
- const layer = requireEffectLayer(layers, id, "add effects");
3400
- const current = layerEffectsOf(layer);
3401
- const ids = new Set(current.items.map((e) => e.id));
3402
- let effectId = newEffectId();
3403
- while (ids.has(effectId)) effectId = newEffectId();
3404
- const { id: _ignored, kind: _kind, ...rest } = params;
3405
- const effect = guard(() => mergeLayerEffect(defaultLayerEffect(kind, effectId), rest));
3406
- const items = [...current.items];
3407
- const at = index === void 0 ? items.length : Math.max(0, Math.min(items.length, Math.floor(index)));
3408
- items.splice(at, 0, effect);
3409
- return { layers: setLayerEffects(layers, id, { ...current, items }), effect };
3410
- }
3411
- function requireEffect(layer, effectId) {
3412
- const items = layer.effects?.items ?? [];
3413
- const i = items.findIndex((e) => e.id === effectId);
3414
- 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.`);
3415
- return i;
3416
- }
3417
- function updateLayerEffect(layers, id, effectId, changes) {
3418
- const layer = requireEffectLayer(layers, id, "change its effects");
3419
- const i = requireEffect(layer, effectId);
3420
- const current = layer.effects;
3421
- const items = [...current.items];
3422
- items[i] = guard(() => mergeLayerEffect(items[i], changes));
3423
- return setLayerEffects(layers, id, { ...current, items });
3424
- }
3425
- function removeLayerEffect(layers, id, effectId) {
3426
- const layer = requireEffectLayer(layers, id, "change its effects");
3427
- requireEffect(layer, effectId);
3428
- const current = layer.effects;
3429
- return setLayerEffects(layers, id, { ...current, items: current.items.filter((e) => e.id !== effectId) });
3430
- }
3431
- function setLayerEffectsEnabled(layers, id, enabled, scale) {
3432
- const layer = requireEffectLayer(layers, id, "change its effects");
3433
- 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}.`);
3434
- const current = layerEffectsOf(layer);
3435
- const next = { ...current, enabled };
3436
- if (scale !== void 0) {
3437
- if (scale === 1) delete next.scale;
3438
- else next.scale = scale;
3439
- }
3440
- return setLayerEffects(layers, id, next);
3441
- }
3442
- function parseBlend(value, allowPassThrough = false) {
3443
- if (value === void 0) return void 0;
3444
- if (value === "pass-through" && allowPassThrough) return value;
3445
- if (!isBlendMode(value)) fail(`Unknown blend mode "${String(value)}".`);
3446
- return value;
3447
- }
3448
- function unionRect(a, b) {
3449
- if (!a) return b;
3450
- if (!b) return a;
3451
- const x = Math.min(a.x, b.x);
3452
- const y = Math.min(a.y, b.y);
3453
- 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 };
3454
- }
3455
-
3456
- // src/image-editor/layers/shape.ts
3457
- var SHAPE_RENDER_MARGIN = MAX_EFFECT_REACH;
3458
- var DEFAULT_MITER_LIMIT = 100;
3459
- var fail2 = (message) => {
3460
- throw new LayerOperationError(message);
3461
- };
3462
- var orientation = (anchors) => {
3463
- let a = 0;
3464
- for (let i = 0; i < anchors.length; i++) {
3465
- const p = anchors[i];
3466
- const q2 = anchors[(i + 1) % anchors.length];
3467
- a += p.x * q2.y - q2.x * p.y;
3468
- }
3469
- return a;
3470
- };
3471
- function polygon(points) {
3472
- const anchors = orientation(points) < 0 ? [...points].reverse() : points;
3473
- return { closed: true, anchors: anchors.map((p) => ({ x: p.x, y: p.y })) };
3474
- }
3475
- function arrowheadPolygon(head, tip, dir) {
3476
- const len = Math.max(0, head.length);
3477
- const hw = Math.max(0, head.width) / 2;
3478
- if (!(len > 0) || !(hw > 0)) return null;
3479
- const n = { x: -dir.y, y: dir.x };
3480
- const base = { x: tip.x - dir.x * len, y: tip.y - dir.y * len };
3481
- const c = Math.max(-0.5, Math.min(0.5, head.concavity ?? 0));
3482
- const pts = [tip, { x: base.x + n.x * hw, y: base.y + n.y * hw }];
3483
- if (c) pts.push({ x: base.x + dir.x * len * c, y: base.y + dir.y * len * c });
3484
- pts.push({ x: base.x - n.x * hw, y: base.y - n.y * hw });
3485
- return polygon(pts);
3486
- }
3487
- function mapAnchor(a, f) {
3488
- const p = f(a);
3489
- const out = { x: p.x, y: p.y };
3490
- if (a.in) out.in = f(a.in);
3491
- if (a.out) out.out = f(a.out);
3492
- if (a.type) out.type = a.type;
3493
- if (a.corner) out.corner = { ...a.corner };
3494
- return out;
3495
- }
3496
- function mapSubpaths(subpaths, f) {
3497
- return expandVectorCorners(subpaths).map((sp) => ({ closed: sp.closed, anchors: sp.anchors.map((a) => mapAnchor(a, f)) }));
3498
- }
3499
- function liveShapeSubpaths(live) {
3500
- let out;
3501
- switch (live.kind) {
3502
- case "rect": {
3503
- const x = Math.min(live.x, live.x + live.width);
3504
- const y = Math.min(live.y, live.y + live.height);
3505
- const sp = rectSubpath({ x, y, width: Math.abs(live.width), height: Math.abs(live.height) });
3506
- const r = live.radii ?? [0, 0, 0, 0];
3507
- out = [{ closed: true, anchors: sp.anchors.map((a, i) => r[i] > 0 ? setAnchorCorner(a, r[i]) : a) }];
3508
- break;
3509
- }
3510
- case "ellipse":
3511
- out = [ellipseSubpath(normBox(live))];
3512
- break;
3513
- case "polygon":
3514
- out = [polygonSubpath(normBox(live), live.sides)];
3515
- break;
3516
- case "star":
3517
- out = [starSubpath(normBox(live), live.points, Math.max(0, Math.min(1, live.innerRatio)))];
3518
- break;
3519
- case "line": {
3520
- const a = { x: live.x1, y: live.y1 };
3521
- const b = { x: live.x2, y: live.y2 };
3522
- const len = Math.hypot(b.x - a.x, b.y - a.y);
3523
- out = [];
3524
- if (len > 0) {
3525
- const d = { x: (b.x - a.x) / len, y: (b.y - a.y) / len };
3526
- const hw = Math.max(0, live.weight) / 2;
3527
- if (hw > 0) {
3528
- const n = { x: -d.y * hw, y: d.x * hw };
3529
- 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 }]));
3530
- }
3531
- const end = live.endArrow ? arrowheadPolygon(live.endArrow, b, d) : null;
3532
- const start = live.startArrow ? arrowheadPolygon(live.startArrow, a, { x: -d.x, y: -d.y }) : null;
3533
- if (start) out.push(start);
3534
- if (end) out.push(end);
3535
- }
3536
- break;
3537
- }
3538
- }
3539
- const m = live.transform;
3540
- return m && !isIdentityMatrix(m) ? mapSubpaths(out, (p) => applyMatrix(m, p)) : out;
3541
- }
3542
- function normBox(b) {
3543
- 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) };
3544
3062
  }
3545
3063
  function liveComponent(live, op = "combine") {
3546
3064
  return { op, subpaths: liveShapeSubpaths(live), live };
@@ -3667,6 +3185,10 @@ function fillCoverage(geo, rect, rule, inverted = false) {
3667
3185
  });
3668
3186
  return acc;
3669
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
+ }
3670
3192
  function paintContext(env) {
3671
3193
  const tiles = /* @__PURE__ */ new Map();
3672
3194
  return {
@@ -3766,29 +3288,29 @@ function rerenderShape(layer, env) {
3766
3288
  }
3767
3289
  var isNum = (v) => typeof v === "number" && Number.isFinite(v);
3768
3290
  function requireNum(v, path, min = -Infinity, max = Infinity) {
3769
- if (!isNum(v)) fail2(`${path} must be a finite number.`);
3770
- 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}.`);
3771
3293
  return v;
3772
3294
  }
3773
3295
  function normalizeShapePaint(v, path = "paint") {
3774
3296
  if (typeof v === "string") {
3775
3297
  const c = parseCssColor(v);
3776
- 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).`);
3777
3299
  return c.alpha < 1 ? { type: "color", color: c.hex, opacity: c.alpha } : { type: "color", color: c.hex };
3778
3300
  }
3779
- 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.`);
3780
3302
  const o = v;
3781
3303
  const opacity = o.opacity === void 0 ? void 0 : requireNum(o.opacity, `${path}.opacity`, 0, 1);
3782
3304
  const withOpacity = (p) => opacity !== void 0 && opacity !== 1 ? { ...p, opacity } : p;
3783
3305
  if (o.type === "color") {
3784
3306
  const c = parseCssColor(o.color);
3785
- 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.`);
3786
3308
  const op = (opacity ?? 1) * c.alpha;
3787
3309
  return op !== 1 ? { type: "color", color: c.hex, opacity: op } : { type: "color", color: c.hex };
3788
3310
  }
3789
3311
  if (o.type === "gradient") {
3790
3312
  const g = o.gradient;
3791
- 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 }.`);
3792
3314
  const gradient = {
3793
3315
  stops: Array.isArray(g.stops) ? g.stops.map((s) => ({ position: s.position, color: parseCssColor(s.color)?.hex ?? s.color })) : [],
3794
3316
  ...g.opacityStops ? { opacityStops: g.opacityStops } : {},
@@ -3802,14 +3324,14 @@ function normalizeShapePaint(v, path = "paint") {
3802
3324
  try {
3803
3325
  validateEffectGradient(gradient, `${path}.gradient`);
3804
3326
  } catch (e) {
3805
- if (e instanceof LayerEffectError) fail2(e.message);
3327
+ if (e instanceof LayerEffectError) fail(e.message);
3806
3328
  throw e;
3807
3329
  }
3808
- 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 }.`);
3809
3331
  return withOpacity({ type: "gradient", gradient });
3810
3332
  }
3811
3333
  if (o.type === "pattern") {
3812
- 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).`);
3813
3335
  const p = { type: "pattern", patternId: o.patternId, scale: o.scale === void 0 ? 1 : requireNum(o.scale, `${path}.scale`, 0.01, 10) };
3814
3336
  if (typeof o.name === "string") p.name = o.name;
3815
3337
  if (o.angle !== void 0) p.angle = requireNum(o.angle, `${path}.angle`, -360, 360);
@@ -3820,10 +3342,10 @@ function normalizeShapePaint(v, path = "paint") {
3820
3342
  }
3821
3343
  return withOpacity(p);
3822
3344
  }
3823
- 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)}.`);
3824
3346
  }
3825
3347
  function normalizeShapeStroke(v, path = "stroke", base) {
3826
- 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.`);
3827
3349
  const o = v;
3828
3350
  const width = o.width === void 0 && base ? base.width : requireNum(o.width, `${path}.width`, 0, 1e4);
3829
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`);
@@ -3831,7 +3353,7 @@ function normalizeShapeStroke(v, path = "stroke", base) {
3831
3353
  const pick = (k, allowed) => {
3832
3354
  const value = o[k] === void 0 ? base?.[k] : o[k];
3833
3355
  if (value === void 0) return;
3834
- 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)}.`);
3835
3357
  if (value !== (k === "align" ? "center" : k === "cap" ? "butt" : "miter")) out[k] = value;
3836
3358
  };
3837
3359
  pick("align", ["inside", "center", "outside"]);
@@ -3841,9 +3363,9 @@ function normalizeShapeStroke(v, path = "stroke", base) {
3841
3363
  if (miter !== void 0 && miter !== DEFAULT_MITER_LIMIT) out.miterLimit = miter;
3842
3364
  const dashes = o.dashes === void 0 ? base?.dashes : o.dashes;
3843
3365
  if (dashes !== void 0 && dashes !== null) {
3844
- 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).`);
3845
3367
  if (dashes.length) {
3846
- 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.`);
3847
3369
  out.dashes = [...dashes];
3848
3370
  }
3849
3371
  }
@@ -3861,7 +3383,7 @@ function box(o, path) {
3861
3383
  }
3862
3384
  function arrowhead(v, path) {
3863
3385
  if (v === null || v === void 0 || v === false) return null;
3864
- 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.`);
3865
3387
  const o = v;
3866
3388
  const head = { width: requireNum(o.width, `${path}.width`, 0, 1e5), length: requireNum(o.length, `${path}.length`, 0, 1e5) };
3867
3389
  if (o.concavity !== void 0 && o.concavity !== 0) head.concavity = requireNum(o.concavity, `${path}.concavity`, -0.5, 0.5);
@@ -3869,13 +3391,13 @@ function arrowhead(v, path) {
3869
3391
  }
3870
3392
  function matrixArg(v, path) {
3871
3393
  if (v === void 0 || v === null) return void 0;
3872
- 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].`);
3873
3395
  const m = v;
3874
- 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.`);
3875
3397
  return isIdentityMatrix(m) ? void 0 : [...m];
3876
3398
  }
3877
3399
  function normalizeLiveShape(v, path = "shape") {
3878
- 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(", ")}.`);
3879
3401
  const o = v;
3880
3402
  const transform = matrixArg(o.transform, `${path}.transform`);
3881
3403
  const t = transform ? { transform } : {};
@@ -3886,7 +3408,7 @@ function normalizeLiveShape(v, path = "shape") {
3886
3408
  if (o.radii !== void 0 || o.radius !== void 0) {
3887
3409
  const r = o.radii ?? o.radius;
3888
3410
  const list2 = typeof r === "number" ? [r, r, r, r] : r;
3889
- 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).`);
3890
3412
  if (list2.some((x) => x > 0)) radii = [...list2];
3891
3413
  }
3892
3414
  return { kind: "rect", ...b, ...radii ? { radii } : {}, ...t };
@@ -3917,16 +3439,16 @@ function normalizeLiveShape(v, path = "shape") {
3917
3439
  const e = arrowhead(o.endArrow, `${path}.endArrow`);
3918
3440
  if (s) line.startArrow = s;
3919
3441
  if (e) line.endArrow = e;
3920
- 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.`);
3921
3443
  return line;
3922
3444
  }
3923
3445
  default:
3924
- 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)}.`);
3925
3447
  }
3926
3448
  }
3927
3449
  function point(v, path) {
3928
3450
  const p = v;
3929
- 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.`);
3930
3452
  return { x: requireNum(p.x, `${path}.x`), y: requireNum(p.y, `${path}.y`) };
3931
3453
  }
3932
3454
  function normalizeSubpaths(v, path = "subpaths") {
@@ -3935,15 +3457,15 @@ function normalizeSubpaths(v, path = "subpaths") {
3935
3457
  try {
3936
3458
  subs = bezierToSubpaths(v);
3937
3459
  } catch {
3938
- return fail2(`${path}: could not read the SVG path data.`);
3460
+ return fail(`${path}: could not read the SVG path data.`);
3939
3461
  }
3940
- 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.`);
3941
3463
  return subs;
3942
3464
  }
3943
- 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).`);
3944
3466
  return v.map((s, i) => {
3945
3467
  const sp = s;
3946
- 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.`);
3947
3469
  const anchors = sp.anchors.map((a, j) => {
3948
3470
  const at = `${path}[${i}].anchors[${j}]`;
3949
3471
  const p = point(a, at);
@@ -3963,16 +3485,16 @@ function normalizeSubpaths(v, path = "subpaths") {
3963
3485
  }
3964
3486
  function requireOperation(v, path = "op") {
3965
3487
  if (v === void 0) return "combine";
3966
- 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)}.`);
3967
3489
  return v;
3968
3490
  }
3969
3491
  function requireShapeLayer(layers, id) {
3970
3492
  const layer = requireLayer(layers, id).layer;
3971
- 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.`);
3972
3494
  return layer;
3973
3495
  }
3974
3496
  function createShapeLayer(init, env, id = newLayerId()) {
3975
- 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.");
3976
3498
  const layer = {
3977
3499
  type: "shape",
3978
3500
  id,
@@ -4012,7 +3534,7 @@ function updateShapeLayer(layers, id, change, env, action = "edit it") {
4012
3534
  const patch = change(current);
4013
3535
  const next = { ...current, ...patch };
4014
3536
  if (!("annotation" in patch)) delete next.annotation;
4015
- 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.");
4016
3538
  const rendered = rerenderShape(next, env);
4017
3539
  return { layers: mapLayer(layers, id, () => rendered), layer: rendered };
4018
3540
  }
@@ -4036,7 +3558,7 @@ function mergeShapeComponents(components, fillRule = "nonzero") {
4036
3558
  if (components.length < 2) return [...components];
4037
3559
  const path = shapeOutline(components, fillRule);
4038
3560
  const subpaths = bezierToSubpaths(path);
4039
- 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.");
4040
3562
  return [{ op: "combine", subpaths }];
4041
3563
  }
4042
3564
  function shapeAsPixelLayer(layer) {
@@ -4106,20 +3628,645 @@ function shapeFromAnnotation(a) {
4106
3628
  default:
4107
3629
  return null;
4108
3630
  }
4109
- }
4110
- function annotationBlend(a, layerBlend) {
4111
- if (layerBlend !== "normal") return layerBlend;
4112
- return a.blend && isBlendMode(a.blend) ? a.blend : "normal";
4113
- }
4114
- function annotationOfShape(layer) {
4115
- const a = layer.annotation;
4116
- if (!a) return null;
4117
- const blend = layer.blend;
4118
- if ((a.blend ?? "normal") === blend) return a;
4119
- const out = { ...a };
4120
- if (blend === "normal") delete out.blend;
4121
- else out.blend = blend;
4122
- 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);
4123
4270
  }
4124
4271
 
4125
4272
  // src/image-editor/layers/text.ts
@@ -5247,6 +5394,7 @@ function offsetSmartObject(layers, id, dx, dy, canvas) {
5247
5394
  const moveMask = (m2) => m2 && m2.linked !== false && !m2.source ? { ...m2, surface: translateSurface(m2.surface, mx, my) } : m2;
5248
5395
  if (layer.mask) next.mask = moveMask(layer.mask);
5249
5396
  if (layer.smartFilters?.mask) next.smartFilters = { ...layer.smartFilters, mask: moveMask(layer.smartFilters.mask) };
5397
+ next = followLayerMove(next, mx, my);
5250
5398
  const spatial = activeSmartFilters(layer.smartFilters).some((f) => f.kind === "blur") || activeEffectSmartFilters(layer.smartFilters).length > 0;
5251
5399
  const b = contentBounds(layer.surface);
5252
5400
  const m = SMART_RENDER_MARGIN;
@@ -5389,9 +5537,9 @@ function convertToSmartObject(layers, ids, canvas, options) {
5389
5537
  const nested = ordered.map((l) => {
5390
5538
  let layer = l.layer;
5391
5539
  if (layer.type === "pixel" && layer.source === "document") {
5392
- const resolved = { ...layer, surface: options.surfaceOf(layer) };
5393
- delete resolved.source;
5394
- layer = resolved;
5540
+ const resolved2 = { ...layer, surface: options.surfaceOf(layer) };
5541
+ delete resolved2.source;
5542
+ layer = resolved2;
5395
5543
  }
5396
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.`);
5397
5545
  return translateLayer(layer, -b.x, -b.y);
@@ -5683,6 +5831,7 @@ function baseFields(layer, ctx) {
5683
5831
  };
5684
5832
  if (layer.clipped) out.clipped = true;
5685
5833
  if (layer.mask) out.mask = serializeMask(layer.mask, `${layer.id}-mask`, ctx);
5834
+ if (layer.vectorMask) out.vectorMask = serializeVectorMask(layer.vectorMask);
5686
5835
  if (layer.locks && Object.values(layer.locks).some(Boolean)) out.locks = { ...layer.locks };
5687
5836
  if (layer.effects) out.effects = serializeEffects(layer.effects);
5688
5837
  return out;
@@ -5707,6 +5856,36 @@ function deserializeEffects(raw) {
5707
5856
  }
5708
5857
  return normalizeLayerEffects({ ...r, psd });
5709
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
+ }
5710
5889
  function serializeMask(mask, hint, ctx) {
5711
5890
  const out = { defaultValue: mask.defaultValue };
5712
5891
  if (!mask.source) {
@@ -6114,6 +6293,13 @@ function deserializeLayer(raw, ctx) {
6114
6293
  if (r.clipped === true) base.clipped = true;
6115
6294
  const mask = readMask(r.mask, ctx, `Layer "${base.name || id}"`);
6116
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
+ }
6117
6303
  if (r.locks && typeof r.locks === "object") {
6118
6304
  const locks = {};
6119
6305
  for (const k of ["pixels", "transparency", "position", "all"]) if (r.locks[k] === true) locks[k] = true;
@@ -7337,97 +7523,6 @@ function writeAdjustmentBlock(kind, params) {
7337
7523
  throw new Error(`Unknown adjustment kind "${kind}".`);
7338
7524
  }
7339
7525
 
7340
- // src/image-editor/psd/channels.ts
7341
- var hexToRgb2 = (hex) => {
7342
- const v = parseInt(/^#?([0-9a-f]{6})$/i.exec(hex)?.[1] ?? "ff0000", 16);
7343
- return [v >> 16 & 255, v >> 8 & 255, v & 255];
7344
- };
7345
- var rgbToHex = (r, g, b) => `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
7346
- function alphaChannelResources(channels) {
7347
- if (!channels.length) return [];
7348
- const names = new PsdWriter();
7349
- for (const c of channels) names.pascal(encodeLegacyName(c.name), 1);
7350
- const unicode = new PsdWriter();
7351
- for (const c of channels) unicode.unicode(c.name);
7352
- const display = new PsdWriter();
7353
- display.u32(1);
7354
- for (const c of channels) {
7355
- const [r, g, b] = hexToRgb2(c.color);
7356
- display.u16(0);
7357
- for (const v of [r, g, b, 0]) display.u16(v * 257);
7358
- display.u16(Math.round(Math.min(1, Math.max(0, c.opacity)) * 100));
7359
- display.u8(c.indicates === "selected" ? 0 : 1);
7360
- display.u8(0);
7361
- }
7362
- return [
7363
- { id: 1006, name: "", data: names.result() },
7364
- { id: 1045, name: "", data: unicode.result() },
7365
- { id: 1077, name: "", data: display.result() }
7366
- ];
7367
- }
7368
- function planeTo8(plane, n, depth) {
7369
- if (depth === 8) return plane.slice(0, n);
7370
- const out = new Uint8Array(n);
7371
- const view = new DataView(plane.buffer, plane.byteOffset, plane.byteLength);
7372
- for (let i = 0; i < n; i++) {
7373
- if (depth === 16) out[i] = plane[2 * i];
7374
- else out[i] = Math.round(Math.min(1, Math.max(0, view.getFloat32(4 * i, false))) * 255);
7375
- }
7376
- return out;
7377
- }
7378
- function readAlphaChannels(planes, header, first, resource2, warn) {
7379
- const count = header.channels - first;
7380
- if (count <= 0 || header.depth === 1) return [];
7381
- const size = rowBytes(header.width, header.depth) * header.height;
7382
- const n = header.width * header.height;
7383
- const names = [];
7384
- const unicode = resource2(1045);
7385
- if (unicode) {
7386
- const r = new PsdReader(unicode);
7387
- while (r.remaining >= 4 && names.length < count) names.push(r.unicode().replace(/\0+$/, ""));
7388
- } else {
7389
- const legacy = resource2(1006);
7390
- if (legacy) {
7391
- const r = new PsdReader(legacy);
7392
- while (r.remaining >= 1 && names.length < count) names.push(new TextDecoder("latin1").decode(r.pascal(1)));
7393
- }
7394
- }
7395
- const display = [];
7396
- const info = resource2(1077);
7397
- if (info && info.length >= 4) {
7398
- const r = new PsdReader(info);
7399
- r.u32();
7400
- while (r.remaining >= 14 && display.length < count) {
7401
- r.u16();
7402
- const [cr, cg, cb] = [r.u16(), r.u16(), r.u16()];
7403
- r.u16();
7404
- const opacity = r.u16();
7405
- const kind = r.u8();
7406
- r.u8();
7407
- display.push({ color: rgbToHex(cr >> 8, cg >> 8, cb >> 8), opacity: Math.min(100, opacity) / 100, kind });
7408
- }
7409
- }
7410
- const out = [];
7411
- for (let i = 0; i < count; i++) {
7412
- const start = (first + i) * size;
7413
- if (start + size > planes.length) break;
7414
- const d = display[i];
7415
- const name = names[i] || `Alpha ${i + 1}`;
7416
- if (d?.kind === 2) {
7417
- warn(`Spot colour channel "${name}" is not supported and was left out.`);
7418
- continue;
7419
- }
7420
- out.push({
7421
- name,
7422
- data: planeTo8(planes.subarray(start, start + size), n, header.depth),
7423
- color: d?.color ?? "#ff0000",
7424
- opacity: d?.opacity ?? 0.5,
7425
- indicates: d?.kind === 0 ? "selected" : "masked"
7426
- });
7427
- }
7428
- return out;
7429
- }
7430
-
7431
7526
  // src/image-editor/psd/paths.ts
7432
7527
  var RECORD = 26;
7433
7528
  var fixed = (v) => v / 16777216;
@@ -7487,6 +7582,8 @@ function writePathRecords(path) {
7487
7582
  return w.result();
7488
7583
  }
7489
7584
  var VMSK_INVERT = 1;
7585
+ var VMSK_NOT_LINKED = 2;
7586
+ var VMSK_DISABLED = 4;
7490
7587
  function readVectorBlock(data) {
7491
7588
  if (data.length < 8) return null;
7492
7589
  const r = new PsdReader(data);
@@ -7557,6 +7654,97 @@ function vectorBlockForComponents(components, inverted, width, height, flags = 0
7557
7654
  return writeVectorBlock({ subpaths, initialFill: subpaths.length === 0 && inverted }, flags & ~VMSK_INVERT | (inverted && subpaths.length ? VMSK_INVERT : 0));
7558
7655
  }
7559
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
+
7560
7748
  // src/image-editor/psd/shapes.ts
7561
7749
  var SHAPE_PSD_KEYS = ["vsms", "vmsk", "vscg", "vstk", "vogk", "SoCo", "GdFl", "PtFl"];
7562
7750
  var unit = (u, value) => ({ type: "UntF", unit: u, value });
@@ -10188,9 +10376,22 @@ function baseProps(ctx, rec, name, notes) {
10188
10376
  ctx.warn.add("Artboards are not supported; they came in as plain groups, without their background colour and clipping", name);
10189
10377
  notes.push("artboard imported as a group");
10190
10378
  }
10191
- if (rec.blocks.some((b) => VECTOR_KEYS.includes(b.key)) && psdKind(rec) === "pixel") {
10192
- ctx.warn.add("Vector masks are not supported and were ignored", name);
10193
- 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
+ }
10194
10395
  }
10195
10396
  return props;
10196
10397
  }
@@ -11539,6 +11740,13 @@ function exportPsd(input) {
11539
11740
  if (fx) blocks.push(fx);
11540
11741
  const locks = lockBits(layer);
11541
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
+ }
11542
11750
  return {
11543
11751
  opacity: Math.round(Math.min(1, Math.max(0, layer.opacity)) * 255),
11544
11752
  clipping: layer.clipped ? 1 : 0,
@@ -11934,6 +12142,8 @@ export {
11934
12142
  applyBoxBlur,
11935
12143
  applyDepthMask,
11936
12144
  removeAlpha,
12145
+ flattenPath,
12146
+ rasterizeCoverage,
11937
12147
  TEXT_ALIGNS,
11938
12148
  MAX_EFFECT_ANIMATION_MS,
11939
12149
  SHAPE_OPERATIONS,
@@ -11950,17 +12160,6 @@ export {
11950
12160
  isRenderedLayer,
11951
12161
  shapeLayers,
11952
12162
  smartObjectLayers,
11953
- adjustmentNodeAsLayer,
11954
- depthMaskAlpha,
11955
- runLayerFilters,
11956
- runLayerFiltersFloat,
11957
- LayerCompositor,
11958
- compositeDeep,
11959
- DEFAULT_ADJUSTMENTS_VALUES,
11960
- DEFAULT_TRANSFORM_VALUES,
11961
- DEFAULT_DEPTH_MASK_VALUES,
11962
- flattenPath,
11963
- rasterizeCoverage,
11964
12163
  LayerOperationError,
11965
12164
  userErrorMessage,
11966
12165
  isPaintableLayer,
@@ -11998,6 +12197,7 @@ export {
11998
12197
  componentPath,
11999
12198
  shapeOutline,
12000
12199
  shapeGeometryBounds,
12200
+ componentsCoverage,
12001
12201
  shapeRenderClip,
12002
12202
  renderShapeRect,
12003
12203
  renderShape,
@@ -12018,6 +12218,24 @@ export {
12018
12218
  shapeFromAnnotation,
12019
12219
  annotationBlend,
12020
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,
12021
12239
  TEXT_RENDER_MARGIN,
12022
12240
  textMatrix,
12023
12241
  textOutlines,