@pixerate/editor 0.189.0 → 0.191.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.
- package/dist/{chunk-ED4L4UD6.cjs → chunk-2UR4OX3B.cjs} +1002 -787
- package/dist/{chunk-M7XIC2FC.js → chunk-RQ2EM5TY.js} +333 -0
- package/dist/{chunk-ZEHTUR7Z.js → chunk-TXYXF32N.js} +968 -753
- package/dist/{chunk-5UEFNX24.cjs → chunk-ZQGZWFPU.cjs} +344 -11
- package/dist/image-editor/controller.d.cts +73 -1
- package/dist/image-editor/controller.d.ts +73 -1
- package/dist/image-editor/free-transform.d.cts +3 -6
- package/dist/image-editor/free-transform.d.ts +3 -6
- package/dist/image-editor/index.cjs +1382 -904
- package/dist/image-editor/index.d.cts +1 -0
- package/dist/image-editor/index.d.ts +1 -0
- package/dist/image-editor/index.js +485 -7
- package/dist/image-editor/layer-tools.d.cts +1 -1
- package/dist/image-editor/layer-tools.d.ts +1 -1
- package/dist/image-editor/layers/compositor.d.cts +3 -0
- package/dist/image-editor/layers/compositor.d.ts +3 -0
- package/dist/image-editor/layers/model.d.cts +23 -0
- package/dist/image-editor/layers/model.d.ts +23 -0
- package/dist/image-editor/layers/panel.d.cts +11 -1
- package/dist/image-editor/layers/panel.d.ts +11 -1
- package/dist/image-editor/layers/serialize.d.cts +11 -0
- package/dist/image-editor/layers/serialize.d.ts +11 -0
- package/dist/image-editor/layers/shape.d.cts +5 -0
- package/dist/image-editor/layers/shape.d.ts +5 -0
- package/dist/image-editor/layers/vector-mask.d.cts +51 -0
- package/dist/image-editor/layers/vector-mask.d.ts +51 -0
- package/dist/image-editor/psd/import.d.cts +0 -7
- package/dist/image-editor/psd/import.d.ts +0 -7
- package/dist/image-editor/selection/index.d.cts +1 -0
- package/dist/image-editor/selection/index.d.ts +1 -0
- package/dist/image-editor/selection/path.d.cts +37 -0
- package/dist/image-editor/selection/path.d.ts +37 -0
- package/dist/paint-editor/index.cjs +6 -6
- package/dist/paint-editor/index.js +1 -1
- package/dist/vector/index.cjs +21 -303
- package/dist/vector/index.js +15 -297
- package/dist/vector/trace.d.cts +8 -1
- package/dist/vector/trace.d.ts +8 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +8 -0
- package/skills/pixerate-image-selection-masking/SKILL.md +10 -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/
|
|
3112
|
-
var
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
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
|
|
3128
|
-
|
|
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
|
|
3133
|
-
|
|
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
|
-
|
|
3142
|
-
|
|
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
|
|
3153
|
-
if (
|
|
3154
|
-
if (
|
|
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
|
|
3157
|
-
|
|
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
|
|
3171
|
-
const
|
|
3172
|
-
|
|
3173
|
-
return
|
|
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
|
|
3189
|
-
|
|
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
|
|
3200
|
-
|
|
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
|
|
3207
|
-
|
|
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
|
|
3254
|
-
|
|
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
|
|
3284
|
-
|
|
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
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
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
|
|
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
|
|
3308
|
-
const
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
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
|
-
|
|
3318
|
-
|
|
3319
|
-
const
|
|
3320
|
-
if (
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
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
|
|
3341
|
-
|
|
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
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
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
|
|
3352
|
-
|
|
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))
|
|
3770
|
-
if (v < min || v > max)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
3327
|
+
if (e instanceof LayerEffectError) fail(e.message);
|
|
3806
3328
|
throw e;
|
|
3807
3329
|
}
|
|
3808
|
-
if (gradient.stops.length < 1)
|
|
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)
|
|
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
|
|
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
|
|
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))
|
|
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))
|
|
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)
|
|
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
|
|
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))
|
|
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)
|
|
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
|
|
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))
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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))))
|
|
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
|
|
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)
|
|
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))
|
|
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")
|
|
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)
|
|
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)
|
|
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)
|
|
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,642 @@ 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 ring of flattenPath(componentPath(c))) {
|
|
3746
|
+
for (let k = 0; k + 1 < ring.length; k += 2) {
|
|
3747
|
+
x0 = Math.min(x0, ring[k]);
|
|
3748
|
+
x1 = Math.max(x1, ring[k]);
|
|
3749
|
+
y0 = Math.min(y0, ring[k + 1]);
|
|
3750
|
+
y1 = Math.max(y1, ring[k + 1]);
|
|
3751
|
+
}
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
return x0 === Infinity ? null : { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
|
|
3755
|
+
}
|
|
3756
|
+
var thumbCache = /* @__PURE__ */ new WeakMap();
|
|
3757
|
+
function vectorMaskThumbnail(vm, width, height, maxSide = 64) {
|
|
3758
|
+
const key = `${width}x${height}:${maxSide}`;
|
|
3759
|
+
const hit = thumbCache.get(vm);
|
|
3760
|
+
if (hit?.key === key) return hit.pixels;
|
|
3761
|
+
const k = Math.min(1, maxSide / Math.max(1, width, height));
|
|
3762
|
+
const tw = Math.max(1, Math.round(width * k));
|
|
3763
|
+
const th = Math.max(1, Math.round(height * k));
|
|
3764
|
+
const scaled = transformVectorMask(vm, (p) => ({ x: p.x * k, y: p.y * k }), [k, 0, 0, k, 0, 0]);
|
|
3765
|
+
const cov = componentsCoverage(scaled.components, { x: 0, y: 0, width: tw, height: th }, vm.fillRule ?? "nonzero", vm.inverted === true);
|
|
3766
|
+
const data = new Uint8ClampedArray(tw * th * 4);
|
|
3767
|
+
for (let i = 0; i < cov.length; i++) {
|
|
3768
|
+
const v = Math.round(Math.min(1, Math.max(0, cov[i])) * 255);
|
|
3769
|
+
data[i * 4] = data[i * 4 + 1] = data[i * 4 + 2] = v;
|
|
3770
|
+
data[i * 4 + 3] = 255;
|
|
3771
|
+
}
|
|
3772
|
+
const pixels = { width: tw, height: th, data };
|
|
3773
|
+
thumbCache.set(vm, { key, pixels });
|
|
3774
|
+
return pixels;
|
|
3775
|
+
}
|
|
3776
|
+
|
|
3777
|
+
// src/image-editor/layers/ops.ts
|
|
3778
|
+
var LayerOperationError = class extends Error {
|
|
3779
|
+
userMessage;
|
|
3780
|
+
constructor(message, userMessage) {
|
|
3781
|
+
super(message);
|
|
3782
|
+
this.name = "LayerOperationError";
|
|
3783
|
+
if (userMessage) this.userMessage = userMessage;
|
|
3784
|
+
}
|
|
3785
|
+
};
|
|
3786
|
+
function userErrorMessage(err) {
|
|
3787
|
+
if (err instanceof LayerOperationError && err.userMessage) return err.userMessage;
|
|
3788
|
+
return err instanceof Error ? err.message : String(err);
|
|
3789
|
+
}
|
|
3790
|
+
function isPaintableLayer(layer) {
|
|
3791
|
+
return layer.type === "pixel" && layer.visible && !isLayerLocked(layer, "pixels");
|
|
3792
|
+
}
|
|
3793
|
+
function defaultActiveLayerId(layers) {
|
|
3794
|
+
const pixels = flattenLayers(layers).filter((l) => l.type === "pixel");
|
|
3795
|
+
for (let i = pixels.length - 1; i >= 0; i--) if (isPaintableLayer(pixels[i])) return pixels[i].id;
|
|
3796
|
+
return pixels[pixels.length - 1]?.id ?? null;
|
|
3797
|
+
}
|
|
3798
|
+
function activeLayerAfterRemoval(prev, next, removedId) {
|
|
3799
|
+
const before = flattenLayers(prev);
|
|
3800
|
+
const at = before.findIndex((l) => l.id === removedId);
|
|
3801
|
+
for (let i = at - 1; i >= 0; i--) {
|
|
3802
|
+
const loc = findLayer(next, before[i].id);
|
|
3803
|
+
if (loc && isPaintableLayer(loc.layer)) return loc.layer.id;
|
|
3804
|
+
}
|
|
3805
|
+
return defaultActiveLayerId(next);
|
|
3806
|
+
}
|
|
3807
|
+
var fail2 = (message) => {
|
|
3808
|
+
throw new LayerOperationError(message);
|
|
3809
|
+
};
|
|
3810
|
+
function requireLayer(layers, id) {
|
|
3811
|
+
const loc = findLayer(layers, id);
|
|
3812
|
+
if (!loc) {
|
|
3813
|
+
const ids = flattenLayers(layers).map((l) => l.id);
|
|
3814
|
+
fail2(`Unknown layer "${id}". ${ids.length ? `Layers: ${ids.join(", ")}.` : "The document has no layers."} Call list_layers for ids.`);
|
|
3815
|
+
}
|
|
3816
|
+
return loc;
|
|
3817
|
+
}
|
|
3818
|
+
function requireUnlocked(layer, what, action) {
|
|
3819
|
+
if (isLayerLocked(layer, "all")) fail2(`Layer "${layer.name || layer.id}" is locked (all); unlock it before you ${action}.`);
|
|
3820
|
+
if (what !== "all" && isLayerLocked(layer, what)) fail2(`Layer "${layer.name || layer.id}" has its ${what} locked; unlock it before you ${action}.`);
|
|
3821
|
+
}
|
|
3822
|
+
function placeLayer(layers, layer, placement = {}, defaultIndex) {
|
|
3823
|
+
const given = [placement.aboveId, placement.belowId, placement.parentId].filter((v) => v !== void 0).length;
|
|
3824
|
+
if (given > 1) fail2("Give only one of aboveId, belowId and parentId.");
|
|
3825
|
+
if (placement.aboveId || placement.belowId) {
|
|
3826
|
+
const ref = requireLayer(layers, placement.aboveId ?? placement.belowId);
|
|
3827
|
+
return insertLayer(layers, layer, ref.parent?.id ?? null, ref.index + (placement.aboveId ? 1 : 0));
|
|
3828
|
+
}
|
|
3829
|
+
if (placement.parentId) {
|
|
3830
|
+
const ref = requireLayer(layers, placement.parentId);
|
|
3831
|
+
if (ref.layer.type !== "group") fail2(`Layer "${placement.parentId}" is not a group; parentId must be a group id.`);
|
|
3832
|
+
return insertLayer(layers, layer, ref.layer.id, placement.index);
|
|
3833
|
+
}
|
|
3834
|
+
return insertLayer(layers, layer, null, placement.index ?? defaultIndex);
|
|
3835
|
+
}
|
|
3836
|
+
function removeLayer(layers, id) {
|
|
3837
|
+
const loc = requireLayer(layers, id);
|
|
3838
|
+
requireUnlocked(loc.layer, "all", "delete it");
|
|
3839
|
+
return mapLayer(layers, id, () => null);
|
|
3840
|
+
}
|
|
3841
|
+
function moveLayer(layers, id, placement) {
|
|
3842
|
+
const loc = requireLayer(layers, id);
|
|
3843
|
+
requireUnlocked(loc.layer, "all", "move it");
|
|
3844
|
+
const target = placement.aboveId ?? placement.belowId ?? placement.parentId;
|
|
3845
|
+
if (target === id) fail2("A layer cannot be placed relative to itself.");
|
|
3846
|
+
if (target && loc.layer.type === "group" && flattenLayers(loc.layer.children).some((l) => l.id === target)) {
|
|
3847
|
+
fail2(`Group "${id}" cannot be moved into its own child "${target}".`);
|
|
3848
|
+
}
|
|
3849
|
+
if (target) requireLayer(layers, target);
|
|
3850
|
+
if (!target && placement.index === void 0) fail2("Give aboveId, belowId, parentId or index.");
|
|
3851
|
+
const without = mapLayer(layers, id, () => null);
|
|
3852
|
+
return placeLayer(without, loc.layer, placement);
|
|
3853
|
+
}
|
|
3854
|
+
function withNewIds(layer) {
|
|
3855
|
+
const next = { ...layer, id: newLayerId(layer.type === "adjustment" ? "adj" : layer.type) };
|
|
3856
|
+
if (next.type === "group") next.children = next.children.map(withNewIds);
|
|
3857
|
+
if (next.type === "annotation") next.annotation = { ...next.annotation, id: next.id };
|
|
3858
|
+
if (next.type === "shape" || next.type === "type") delete next.annotation;
|
|
3859
|
+
if (next.type === "smart" && next.psd?.placed) {
|
|
3860
|
+
next.psd = { ...next.psd };
|
|
3861
|
+
delete next.psd.placed;
|
|
3862
|
+
}
|
|
3863
|
+
return next;
|
|
3864
|
+
}
|
|
3865
|
+
function duplicateLayer(layers, id, name) {
|
|
3866
|
+
const loc = requireLayer(layers, id);
|
|
3867
|
+
const copy = withNewIds(loc.layer);
|
|
3868
|
+
copy.name = name ?? `${loc.layer.name || "Layer"} copy`;
|
|
3869
|
+
delete copy.locks;
|
|
3870
|
+
return { layers: insertLayer(layers, copy, loc.parent?.id ?? null, loc.index + 1), id: copy.id };
|
|
3871
|
+
}
|
|
3872
|
+
function setLayerProperties(layers, id, props) {
|
|
3873
|
+
const loc = requireLayer(layers, id);
|
|
3874
|
+
const layer = loc.layer;
|
|
3875
|
+
const onlyUnlocking = Object.keys(props).every((k) => k === "locks" || k === "visible" || k === "collapsed");
|
|
3876
|
+
if (!onlyUnlocking) requireUnlocked(layer, "all", "change its properties");
|
|
3877
|
+
const next = { ...layer };
|
|
3878
|
+
if (props.name !== void 0) {
|
|
3879
|
+
if (typeof props.name !== "string" || !props.name.trim()) fail2("name must be a non-empty string.");
|
|
3880
|
+
next.name = props.name.trim();
|
|
3881
|
+
}
|
|
3882
|
+
if (props.visible !== void 0) next.visible = Boolean(props.visible);
|
|
3883
|
+
if (props.opacity !== void 0) {
|
|
3884
|
+
if (!(typeof props.opacity === "number" && props.opacity >= 0 && props.opacity <= 1)) fail2("opacity must be a number from 0 to 1.");
|
|
3885
|
+
next.opacity = props.opacity;
|
|
3886
|
+
}
|
|
3887
|
+
if (props.fillOpacity !== void 0) {
|
|
3888
|
+
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.`);
|
|
3889
|
+
if (!(typeof props.fillOpacity === "number" && props.fillOpacity >= 0 && props.fillOpacity <= 1)) fail2("fillOpacity must be a number from 0 to 1.");
|
|
3890
|
+
const p = next;
|
|
3891
|
+
if (props.fillOpacity === 1) delete p.fillOpacity;
|
|
3892
|
+
else p.fillOpacity = props.fillOpacity;
|
|
3893
|
+
}
|
|
3894
|
+
if (props.blend !== void 0) {
|
|
3895
|
+
if (props.blend === "pass-through") {
|
|
3896
|
+
if (layer.type !== "group") fail2("pass-through is only for groups.");
|
|
3897
|
+
} else if (!isBlendMode(props.blend)) fail2(`Unknown blend mode "${props.blend}".`);
|
|
3898
|
+
next.blend = props.blend;
|
|
3899
|
+
}
|
|
3900
|
+
if (props.clipped !== void 0) {
|
|
3901
|
+
if (props.clipped && loc.index === 0) fail2("The bottom layer of a group or the stack has nothing below to clip to.");
|
|
3902
|
+
if (props.clipped) next.clipped = true;
|
|
3903
|
+
else delete next.clipped;
|
|
3904
|
+
}
|
|
3905
|
+
if (props.locks !== void 0) {
|
|
3906
|
+
const locks = {};
|
|
3907
|
+
for (const k of ["pixels", "transparency", "position", "all"]) if (props.locks[k]) locks[k] = true;
|
|
3908
|
+
if (Object.keys(locks).length) next.locks = locks;
|
|
3909
|
+
else delete next.locks;
|
|
3910
|
+
}
|
|
3911
|
+
if (props.collapsed !== void 0) {
|
|
3912
|
+
if (layer.type !== "group") fail2("collapsed applies to groups.");
|
|
3913
|
+
const g = next;
|
|
3914
|
+
if (props.collapsed) g.collapsed = true;
|
|
3915
|
+
else delete g.collapsed;
|
|
3916
|
+
}
|
|
3917
|
+
return mapLayer(layers, id, () => next);
|
|
3918
|
+
}
|
|
3919
|
+
function groupLayers(layers, ids, name = "Group") {
|
|
3920
|
+
if (!ids.length) fail2("Give at least one layer id to group.");
|
|
3921
|
+
const locs = ids.map((id) => requireLayer(layers, id));
|
|
3922
|
+
const parent = locs[0].parent?.id ?? null;
|
|
3923
|
+
if (locs.some((l) => (l.parent?.id ?? null) !== parent)) fail2("Layers to group must share one parent; move them next to each other first.");
|
|
3924
|
+
for (const l of locs) requireUnlocked(l.layer, "all", "group it");
|
|
3925
|
+
const ordered = [...new Set(locs)].sort((a, b) => a.index - b.index);
|
|
3926
|
+
const top = ordered[ordered.length - 1].index;
|
|
3927
|
+
const below = ordered.filter((l) => l.index < top).length;
|
|
3928
|
+
const group = {
|
|
3929
|
+
type: "group",
|
|
3930
|
+
id: newLayerId("group"),
|
|
3931
|
+
name,
|
|
3932
|
+
visible: true,
|
|
3933
|
+
opacity: 1,
|
|
3934
|
+
blend: "pass-through",
|
|
3935
|
+
children: ordered.map((l) => {
|
|
3936
|
+
const c = { ...l.layer };
|
|
3937
|
+
return c;
|
|
3938
|
+
})
|
|
3939
|
+
};
|
|
3940
|
+
if (group.children[0]?.clipped) {
|
|
3941
|
+
const c = { ...group.children[0] };
|
|
3942
|
+
delete c.clipped;
|
|
3943
|
+
group.children[0] = c;
|
|
3944
|
+
}
|
|
3945
|
+
let out = layers;
|
|
3946
|
+
for (const l of ordered) out = mapLayer(out, l.layer.id, () => null);
|
|
3947
|
+
return { layers: insertLayer(out, group, parent, top - below), id: group.id };
|
|
3948
|
+
}
|
|
3949
|
+
function ungroupLayer(layers, id) {
|
|
3950
|
+
const loc = requireLayer(layers, id);
|
|
3951
|
+
if (loc.layer.type !== "group") fail2(`Layer "${id}" is not a group.`);
|
|
3952
|
+
requireUnlocked(loc.layer, "all", "ungroup it");
|
|
3953
|
+
const group = loc.layer;
|
|
3954
|
+
let out = mapLayer(layers, id, () => null);
|
|
3955
|
+
group.children.forEach((c, i) => {
|
|
3956
|
+
out = insertLayer(out, c, loc.parent?.id ?? null, loc.index + i);
|
|
3957
|
+
});
|
|
3958
|
+
return { layers: out, ids: group.children.map((c) => c.id) };
|
|
3959
|
+
}
|
|
3960
|
+
function offsetLayer(layers, id, offset) {
|
|
3961
|
+
const loc = requireLayer(layers, id);
|
|
3962
|
+
if (loc.layer.type !== "pixel") fail2(`Only pixel layers move; "${id}" is a ${loc.layer.type} layer.`);
|
|
3963
|
+
requireUnlocked(loc.layer, "position", "move it");
|
|
3964
|
+
if (!Number.isFinite(offset.x) || !Number.isFinite(offset.y)) fail2("x and y must be numbers.");
|
|
3965
|
+
const layer = loc.layer;
|
|
3966
|
+
const next = { ...layer, offset: { x: Math.round(offset.x), y: Math.round(offset.y) } };
|
|
3967
|
+
const mask = layer.mask;
|
|
3968
|
+
if (mask && mask.linked === false && !mask.source) {
|
|
3969
|
+
next.mask = { ...mask, surface: translateSurface(mask.surface, layer.offset.x - next.offset.x, layer.offset.y - next.offset.y) };
|
|
3970
|
+
}
|
|
3971
|
+
return mapLayer(layers, id, () => followLayerMove(next, next.offset.x - layer.offset.x, next.offset.y - layer.offset.y));
|
|
3972
|
+
}
|
|
3973
|
+
function setLayerMask(layers, id, mask) {
|
|
3974
|
+
const loc = requireLayer(layers, id);
|
|
3975
|
+
requireUnlocked(loc.layer, "all", "change its mask");
|
|
3976
|
+
return mapLayer(layers, id, (l) => {
|
|
3977
|
+
const next = { ...l };
|
|
3978
|
+
if (mask) next.mask = mask;
|
|
3979
|
+
else delete next.mask;
|
|
3980
|
+
return next;
|
|
3981
|
+
});
|
|
3982
|
+
}
|
|
3983
|
+
var DEVELOP_FIELDS = ["brightness", "contrast", "saturation", "exposure", "temperature", "tint"];
|
|
3984
|
+
function normalizeFilter(raw, id = newLayerId("flt")) {
|
|
3985
|
+
const enabled = raw.enabled !== false;
|
|
3986
|
+
if (raw.kind === "blur") {
|
|
3987
|
+
const r = raw.radius;
|
|
3988
|
+
if (r !== void 0 && !(typeof r === "number" && r >= 0 && r <= 50)) fail2("blur radius must be a number from 0 to 50 px.");
|
|
3989
|
+
return { id, kind: "blur", enabled, radius: typeof r === "number" ? r : 2 };
|
|
3990
|
+
}
|
|
3991
|
+
if (raw.kind !== "develop") fail2(`Unknown filter kind "${raw.kind}". Use develop or blur.`);
|
|
3992
|
+
const out = { id, kind: "develop", enabled, brightness: 0, contrast: 0, saturation: 0, exposure: 0, temperature: 0, tint: 0, process: 2 };
|
|
3993
|
+
for (const k of DEVELOP_FIELDS) {
|
|
3994
|
+
const v = raw[k];
|
|
3995
|
+
if (v === void 0) continue;
|
|
3996
|
+
if (!(typeof v === "number" && v >= -100 && v <= 100)) fail2(`${k} must be a number from -100 to 100.`);
|
|
3997
|
+
out[k] = v;
|
|
3998
|
+
}
|
|
3999
|
+
const p = raw.process;
|
|
4000
|
+
if (p !== void 0) {
|
|
4001
|
+
if (p !== 1 && p !== 2) fail2("process must be 1 or 2.");
|
|
4002
|
+
out.process = p;
|
|
4003
|
+
}
|
|
4004
|
+
return out;
|
|
4005
|
+
}
|
|
4006
|
+
function requirePixelLayer(layers, id) {
|
|
4007
|
+
const loc = requireLayer(layers, id);
|
|
4008
|
+
if (loc.layer.type !== "pixel") fail2(`Filters belong to pixel layers; "${id}" is a ${loc.layer.type} layer.`);
|
|
4009
|
+
requireUnlocked(loc.layer, "all", "change its filters");
|
|
4010
|
+
return loc.layer;
|
|
4011
|
+
}
|
|
4012
|
+
function addLayerFilter(layers, id, filter) {
|
|
4013
|
+
const layer = requirePixelLayer(layers, id);
|
|
4014
|
+
if (layer.filters?.some((f) => f.kind === filter.kind)) fail2(`Layer "${id}" already has a ${filter.kind} filter; change it with update_layer_filter.`);
|
|
4015
|
+
return mapLayer(layers, id, () => ({ ...layer, filters: [...layer.filters ?? [], filter] }));
|
|
4016
|
+
}
|
|
4017
|
+
function updateLayerFilter(layers, id, filterId, changes) {
|
|
4018
|
+
const layer = requirePixelLayer(layers, id);
|
|
4019
|
+
const i = (layer.filters ?? []).findIndex((f) => f.id === filterId);
|
|
4020
|
+
if (i === -1) fail2(`Layer "${id}" has no filter "${filterId}". Filters: ${(layer.filters ?? []).map((f) => f.id).join(", ") || "none"}.`);
|
|
4021
|
+
const current = layer.filters[i];
|
|
4022
|
+
if ("kind" in changes && changes.kind !== current.kind) fail2("A filter kind cannot change; remove it and add another.");
|
|
4023
|
+
const merged2 = normalizeFilter({ ...current, ...changes, kind: current.kind }, current.id);
|
|
4024
|
+
const filters = [...layer.filters];
|
|
4025
|
+
filters[i] = merged2;
|
|
4026
|
+
return mapLayer(layers, id, () => ({ ...layer, filters }));
|
|
4027
|
+
}
|
|
4028
|
+
function removeLayerFilter(layers, id, filterId) {
|
|
4029
|
+
const layer = requirePixelLayer(layers, id);
|
|
4030
|
+
if (!(layer.filters ?? []).some((f) => f.id === filterId)) fail2(`Layer "${id}" has no filter "${filterId}".`);
|
|
4031
|
+
const filters = layer.filters.filter((f) => f.id !== filterId);
|
|
4032
|
+
const next = { ...layer };
|
|
4033
|
+
if (filters.length) next.filters = filters;
|
|
4034
|
+
else delete next.filters;
|
|
4035
|
+
return mapLayer(layers, id, () => next);
|
|
4036
|
+
}
|
|
4037
|
+
function requireEffectLayer(layers, id, action) {
|
|
4038
|
+
const layer = requireLayer(layers, id).layer;
|
|
4039
|
+
if (layer.type === "adjustment") fail2(`Layer "${layer.name || id}" is an adjustment layer; effects go on pixel, annotation and group layers.`);
|
|
4040
|
+
requireUnlocked(layer, "all", action);
|
|
4041
|
+
return layer;
|
|
4042
|
+
}
|
|
4043
|
+
function layerEffectsOf(layer) {
|
|
4044
|
+
return layer.effects ?? { enabled: true, items: [] };
|
|
4045
|
+
}
|
|
4046
|
+
function guard(fn) {
|
|
4047
|
+
try {
|
|
4048
|
+
return fn();
|
|
4049
|
+
} catch (e) {
|
|
4050
|
+
if (e instanceof LayerEffectError) fail2(e.message);
|
|
4051
|
+
throw e;
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
function setLayerEffects(layers, id, effects) {
|
|
4055
|
+
const layer = requireEffectLayer(layers, id, "change its effects");
|
|
4056
|
+
const next = { ...layer };
|
|
4057
|
+
if (effects && (effects.items.length || effects.psd || effects.scale !== void 0 || !effects.enabled)) {
|
|
4058
|
+
guard(() => validateLayerEffects(effects));
|
|
4059
|
+
next.effects = effects;
|
|
4060
|
+
} else delete next.effects;
|
|
4061
|
+
return mapLayer(layers, id, () => next);
|
|
4062
|
+
}
|
|
4063
|
+
function addLayerEffect(layers, id, kind, params = {}, index) {
|
|
4064
|
+
if (!isLayerEffectKind(kind)) fail2(`Unknown effect kind "${String(kind)}". Kinds: ${LAYER_EFFECT_KINDS.join(", ")}.`);
|
|
4065
|
+
const layer = requireEffectLayer(layers, id, "add effects");
|
|
4066
|
+
const current = layerEffectsOf(layer);
|
|
4067
|
+
const ids = new Set(current.items.map((e) => e.id));
|
|
4068
|
+
let effectId = newEffectId();
|
|
4069
|
+
while (ids.has(effectId)) effectId = newEffectId();
|
|
4070
|
+
const { id: _ignored, kind: _kind, ...rest } = params;
|
|
4071
|
+
const effect = guard(() => mergeLayerEffect(defaultLayerEffect(kind, effectId), rest));
|
|
4072
|
+
const items = [...current.items];
|
|
4073
|
+
const at = index === void 0 ? items.length : Math.max(0, Math.min(items.length, Math.floor(index)));
|
|
4074
|
+
items.splice(at, 0, effect);
|
|
4075
|
+
return { layers: setLayerEffects(layers, id, { ...current, items }), effect };
|
|
4076
|
+
}
|
|
4077
|
+
function requireEffect(layer, effectId) {
|
|
4078
|
+
const items = layer.effects?.items ?? [];
|
|
4079
|
+
const i = items.findIndex((e) => e.id === effectId);
|
|
4080
|
+
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.`);
|
|
4081
|
+
return i;
|
|
4082
|
+
}
|
|
4083
|
+
function updateLayerEffect(layers, id, effectId, changes) {
|
|
4084
|
+
const layer = requireEffectLayer(layers, id, "change its effects");
|
|
4085
|
+
const i = requireEffect(layer, effectId);
|
|
4086
|
+
const current = layer.effects;
|
|
4087
|
+
const items = [...current.items];
|
|
4088
|
+
items[i] = guard(() => mergeLayerEffect(items[i], changes));
|
|
4089
|
+
return setLayerEffects(layers, id, { ...current, items });
|
|
4090
|
+
}
|
|
4091
|
+
function removeLayerEffect(layers, id, effectId) {
|
|
4092
|
+
const layer = requireEffectLayer(layers, id, "change its effects");
|
|
4093
|
+
requireEffect(layer, effectId);
|
|
4094
|
+
const current = layer.effects;
|
|
4095
|
+
return setLayerEffects(layers, id, { ...current, items: current.items.filter((e) => e.id !== effectId) });
|
|
4096
|
+
}
|
|
4097
|
+
function setLayerEffectsEnabled(layers, id, enabled, scale) {
|
|
4098
|
+
const layer = requireEffectLayer(layers, id, "change its effects");
|
|
4099
|
+
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}.`);
|
|
4100
|
+
const current = layerEffectsOf(layer);
|
|
4101
|
+
const next = { ...current, enabled };
|
|
4102
|
+
if (scale !== void 0) {
|
|
4103
|
+
if (scale === 1) delete next.scale;
|
|
4104
|
+
else next.scale = scale;
|
|
4105
|
+
}
|
|
4106
|
+
return setLayerEffects(layers, id, next);
|
|
4107
|
+
}
|
|
4108
|
+
function parseBlend(value, allowPassThrough = false) {
|
|
4109
|
+
if (value === void 0) return void 0;
|
|
4110
|
+
if (value === "pass-through" && allowPassThrough) return value;
|
|
4111
|
+
if (!isBlendMode(value)) fail2(`Unknown blend mode "${String(value)}".`);
|
|
4112
|
+
return value;
|
|
4113
|
+
}
|
|
4114
|
+
function unionRect(a, b) {
|
|
4115
|
+
if (!a) return b;
|
|
4116
|
+
if (!b) return a;
|
|
4117
|
+
const x = Math.min(a.x, b.x);
|
|
4118
|
+
const y = Math.min(a.y, b.y);
|
|
4119
|
+
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 };
|
|
4120
|
+
}
|
|
4121
|
+
|
|
4122
|
+
// src/image-editor/layers/compositor.ts
|
|
4123
|
+
var legacyViewCache = /* @__PURE__ */ new WeakMap();
|
|
4124
|
+
function adjustmentNodeAsLayer(node) {
|
|
4125
|
+
let view = legacyViewCache.get(node);
|
|
4126
|
+
if (!view) {
|
|
4127
|
+
view = {
|
|
4128
|
+
id: node.id,
|
|
4129
|
+
kind: node.kind,
|
|
4130
|
+
enabled: node.visible,
|
|
4131
|
+
opacity: node.opacity,
|
|
4132
|
+
blend: node.blend,
|
|
4133
|
+
params: node.params
|
|
4134
|
+
};
|
|
4135
|
+
legacyViewCache.set(node, view);
|
|
4136
|
+
}
|
|
4137
|
+
return view;
|
|
4138
|
+
}
|
|
4139
|
+
function developAdjustments(f) {
|
|
4140
|
+
return {
|
|
4141
|
+
brightness: f.brightness,
|
|
4142
|
+
contrast: f.contrast,
|
|
4143
|
+
saturation: f.saturation,
|
|
4144
|
+
exposure: f.exposure,
|
|
4145
|
+
temperature: f.temperature,
|
|
4146
|
+
tint: f.tint ?? 0,
|
|
4147
|
+
blur: 0,
|
|
4148
|
+
opacity: 1,
|
|
4149
|
+
process: f.process
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
4152
|
+
function applyPixelFilters(data, filters) {
|
|
4153
|
+
for (const f of filters) {
|
|
4154
|
+
if (!isActiveFilter(f) || f.kind !== "develop") continue;
|
|
4155
|
+
const adj = developAdjustments(f);
|
|
4156
|
+
if (normalizeProcessVersion(adj.process) === LATEST_PROCESS_VERSION) applyProcess2Adjustments({ data }, adj);
|
|
4157
|
+
else applyLegacyAdjustments({ data }, adj);
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
function depthMaskAlpha(depth, options) {
|
|
4161
|
+
const { mode, depthRange, softness, invert } = options;
|
|
4162
|
+
let v = invert ? 255 - depth : depth;
|
|
4163
|
+
const soft = Math.max(1, softness || 10);
|
|
4164
|
+
const [lo, hi] = depthRange;
|
|
4165
|
+
if (mode === "threshold") {
|
|
4166
|
+
if (v >= hi + soft) return 0;
|
|
4167
|
+
if (v > hi) return Math.round((hi + soft - v) / soft * 255);
|
|
4168
|
+
if (v >= lo) return 255;
|
|
4169
|
+
if (v < lo - soft) return 0;
|
|
4170
|
+
return Math.round((v - (lo - soft)) / soft * 255);
|
|
4171
|
+
}
|
|
4172
|
+
v += lo;
|
|
4173
|
+
return v < 0 ? 0 : v > 255 ? 255 : v;
|
|
4174
|
+
}
|
|
4175
|
+
function runLayerFilters(data, width, height, filters) {
|
|
4176
|
+
for (const f of filters) {
|
|
4177
|
+
if (!isActiveFilter(f)) continue;
|
|
4178
|
+
if (f.kind === "develop") applyPixelFilters(data, [f]);
|
|
4179
|
+
else if (f.kind === "blur") applyBoxBlur({ width, height, data }, Math.min(Math.round(f.radius), 40));
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
function runLayerFiltersFloat(data, width, height, filters, linear) {
|
|
4183
|
+
for (const f of filters) {
|
|
4184
|
+
if (!isActiveFilter(f)) continue;
|
|
4185
|
+
if (f.kind === "blur") applyBoxBlurFloat({ width, height, data }, Math.min(Math.round(f.radius), 40));
|
|
4186
|
+
else if (f.kind === "develop") {
|
|
4187
|
+
const adj = developAdjustments(f);
|
|
4188
|
+
if (normalizeProcessVersion(adj.process) === LATEST_PROCESS_VERSION) applyProcess2Float(data, adj, linear);
|
|
4189
|
+
else applyLegacyFloat(data, adj, linear);
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
function filterReach(filters) {
|
|
4194
|
+
let r = 0;
|
|
4195
|
+
for (const f of filters) if (isActiveFilter(f) && f.kind === "blur") r += Math.min(Math.round(f.radius), 40);
|
|
4196
|
+
return r;
|
|
4197
|
+
}
|
|
4198
|
+
var filterSpecCache = /* @__PURE__ */ new WeakMap();
|
|
4199
|
+
function pixelFilterSpec(layer) {
|
|
4200
|
+
const list2 = layer.filters;
|
|
4201
|
+
if (!list2?.length) return null;
|
|
4202
|
+
if (filterSpecCache.has(list2)) return filterSpecCache.get(list2);
|
|
4203
|
+
const filters = list2.filter(isActiveFilter);
|
|
4204
|
+
let spec = null;
|
|
4205
|
+
if (filters.length) {
|
|
4206
|
+
const spatial = filters.some(isSpatialFilter);
|
|
4207
|
+
spec = {
|
|
4208
|
+
key: JSON.stringify(filters),
|
|
4209
|
+
spatial,
|
|
4210
|
+
apply: (data, width, height) => runLayerFilters(data, width, height, filters)
|
|
4211
|
+
};
|
|
4212
|
+
}
|
|
4213
|
+
filterSpecCache.set(list2, spec);
|
|
4214
|
+
return spec;
|
|
4215
|
+
}
|
|
4216
|
+
var PHOTO_PROFILE = {
|
|
4217
|
+
isFilterLayer: (l) => l.type === "adjustment",
|
|
4218
|
+
applyFilterRun: (layers, acc, _r, masks) => {
|
|
4219
|
+
const views = layers.map(adjustmentNodeAsLayer);
|
|
4220
|
+
applyAdjustmentLayers({ data: acc }, views, masks);
|
|
4221
|
+
},
|
|
4222
|
+
contentTile: (l, r, sources) => l.type === "annotation" ? sources.rasterizeAnnotation?.(l.annotation, r) ?? null : null,
|
|
4223
|
+
/** Annotation layers draw with their annotation's blend mode unless the layer sets one. */
|
|
4224
|
+
contentBlend: (l) => l.type === "annotation" && l.blend === "normal" && l.annotation.blend ? l.annotation.blend : l.blend,
|
|
4225
|
+
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,
|
|
4226
|
+
pixelFilters: (l) => l.type === "pixel" ? pixelFilterSpec(l) : null,
|
|
4227
|
+
/**
|
|
4228
|
+
* A smart object draws its rendered pixels (contents → placement → smart filters) like a pixel
|
|
4229
|
+
* layer, and a shape layer the pixels rendered from its paths.
|
|
4230
|
+
*/
|
|
4231
|
+
isPixelLike: (l) => l.type === "smart" || l.type === "shape" || l.type === "type",
|
|
4232
|
+
maskSourcePlane: (source, W, H, sources) => {
|
|
4233
|
+
const opts = source;
|
|
4234
|
+
const depth = sources.depthMap?.(opts) ?? null;
|
|
4235
|
+
if (!depth || depth.width !== W || depth.height !== H) return null;
|
|
4236
|
+
const v = new Float32Array(W * H);
|
|
4237
|
+
for (let i = 0; i < v.length; i++) v[i] = depthMaskAlpha(depth.data[i * 4], opts) / 255;
|
|
4238
|
+
return v;
|
|
4239
|
+
}
|
|
4240
|
+
};
|
|
4241
|
+
var LayerCompositor = class extends RasterCompositor {
|
|
4242
|
+
constructor(options = {}) {
|
|
4243
|
+
super(PHOTO_PROFILE, options);
|
|
4244
|
+
}
|
|
4245
|
+
// Vector masks reach the engine as part of each layer's raster mask (see `resolveVectorMasks`).
|
|
4246
|
+
composite(layers, rect, sources, options) {
|
|
4247
|
+
return super.composite(resolveVectorMasks(layers, sources.width, sources.height), rect, sources, options);
|
|
4248
|
+
}
|
|
4249
|
+
compositeTiles(layers, rect, sources, onTile, options) {
|
|
4250
|
+
super.compositeTiles(resolveVectorMasks(layers, sources.width, sources.height), rect, sources, onTile, options);
|
|
4251
|
+
}
|
|
4252
|
+
tile(layers, tx, ty, sources) {
|
|
4253
|
+
return super.tile(resolveVectorMasks(layers, sources.width, sources.height), tx, ty, sources);
|
|
4254
|
+
}
|
|
4255
|
+
};
|
|
4256
|
+
function compositeDeep(layers, rect, sources, depth) {
|
|
4257
|
+
const deep = new DeepCompositor(new LayerCompositor(), PHOTO_PROFILE, {
|
|
4258
|
+
applyFilterRunDeep: (run, acc, _r, masks) => applyAdjustmentLayersFloat(acc, run.map(adjustmentNodeAsLayer), masks),
|
|
4259
|
+
pixelFiltersDeep: (l) => {
|
|
4260
|
+
if (l.type !== "pixel") return null;
|
|
4261
|
+
const filters = (l.filters ?? []).filter(isActiveFilter);
|
|
4262
|
+
if (!filters.length) return null;
|
|
4263
|
+
return { reach: filterReach(filters), apply: (data, width, height, linear) => runLayerFiltersFloat(data, width, height, filters, linear) };
|
|
4264
|
+
}
|
|
4265
|
+
}, depth);
|
|
4266
|
+
return deep.composite(resolveVectorMasks(layers, sources.width, sources.height), rect, sources);
|
|
4123
4267
|
}
|
|
4124
4268
|
|
|
4125
4269
|
// src/image-editor/layers/text.ts
|
|
@@ -5247,6 +5391,7 @@ function offsetSmartObject(layers, id, dx, dy, canvas) {
|
|
|
5247
5391
|
const moveMask = (m2) => m2 && m2.linked !== false && !m2.source ? { ...m2, surface: translateSurface(m2.surface, mx, my) } : m2;
|
|
5248
5392
|
if (layer.mask) next.mask = moveMask(layer.mask);
|
|
5249
5393
|
if (layer.smartFilters?.mask) next.smartFilters = { ...layer.smartFilters, mask: moveMask(layer.smartFilters.mask) };
|
|
5394
|
+
next = followLayerMove(next, mx, my);
|
|
5250
5395
|
const spatial = activeSmartFilters(layer.smartFilters).some((f) => f.kind === "blur") || activeEffectSmartFilters(layer.smartFilters).length > 0;
|
|
5251
5396
|
const b = contentBounds(layer.surface);
|
|
5252
5397
|
const m = SMART_RENDER_MARGIN;
|
|
@@ -5389,9 +5534,9 @@ function convertToSmartObject(layers, ids, canvas, options) {
|
|
|
5389
5534
|
const nested = ordered.map((l) => {
|
|
5390
5535
|
let layer = l.layer;
|
|
5391
5536
|
if (layer.type === "pixel" && layer.source === "document") {
|
|
5392
|
-
const
|
|
5393
|
-
delete
|
|
5394
|
-
layer =
|
|
5537
|
+
const resolved2 = { ...layer, surface: options.surfaceOf(layer) };
|
|
5538
|
+
delete resolved2.source;
|
|
5539
|
+
layer = resolved2;
|
|
5395
5540
|
}
|
|
5396
5541
|
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
5542
|
return translateLayer(layer, -b.x, -b.y);
|
|
@@ -5683,6 +5828,7 @@ function baseFields(layer, ctx) {
|
|
|
5683
5828
|
};
|
|
5684
5829
|
if (layer.clipped) out.clipped = true;
|
|
5685
5830
|
if (layer.mask) out.mask = serializeMask(layer.mask, `${layer.id}-mask`, ctx);
|
|
5831
|
+
if (layer.vectorMask) out.vectorMask = serializeVectorMask(layer.vectorMask);
|
|
5686
5832
|
if (layer.locks && Object.values(layer.locks).some(Boolean)) out.locks = { ...layer.locks };
|
|
5687
5833
|
if (layer.effects) out.effects = serializeEffects(layer.effects);
|
|
5688
5834
|
return out;
|
|
@@ -5707,6 +5853,36 @@ function deserializeEffects(raw) {
|
|
|
5707
5853
|
}
|
|
5708
5854
|
return normalizeLayerEffects({ ...r, psd });
|
|
5709
5855
|
}
|
|
5856
|
+
function serializeVectorMask(vm) {
|
|
5857
|
+
const out = { components: JSON.parse(JSON.stringify(vm.components)) };
|
|
5858
|
+
if (vm.fillRule === "evenodd") out.fillRule = "evenodd";
|
|
5859
|
+
if (vm.inverted) out.inverted = true;
|
|
5860
|
+
if (vm.enabled === false) out.enabled = false;
|
|
5861
|
+
if (vm.linked === false) out.linked = false;
|
|
5862
|
+
if (vm.density !== void 0 && vm.density !== 1) out.density = vm.density;
|
|
5863
|
+
if (vm.feather) out.feather = vm.feather;
|
|
5864
|
+
return out;
|
|
5865
|
+
}
|
|
5866
|
+
function readVectorMask(r) {
|
|
5867
|
+
if (!Array.isArray(r.components)) throw new Error("its components are missing");
|
|
5868
|
+
const components = r.components.map((raw, i) => {
|
|
5869
|
+
const c = raw;
|
|
5870
|
+
const op = requireOperation(c?.op, `components[${i}].op`);
|
|
5871
|
+
if (c?.live) {
|
|
5872
|
+
const live = normalizeLiveShape(c.live, `components[${i}].live`);
|
|
5873
|
+
return { op, subpaths: liveShapeSubpaths(live), live };
|
|
5874
|
+
}
|
|
5875
|
+
return { op, subpaths: Array.isArray(c?.subpaths) && !c.subpaths.length ? [] : normalizeSubpaths(c?.subpaths, `components[${i}].subpaths`) };
|
|
5876
|
+
});
|
|
5877
|
+
const vm = { components };
|
|
5878
|
+
if (r.fillRule === "evenodd") vm.fillRule = "evenodd";
|
|
5879
|
+
if (r.inverted === true) vm.inverted = true;
|
|
5880
|
+
if (r.enabled === false) vm.enabled = false;
|
|
5881
|
+
if (r.linked === false) vm.linked = false;
|
|
5882
|
+
if (typeof r.density === "number" && r.density !== 1) vm.density = num2(r.density, 0, 1, 1);
|
|
5883
|
+
if (typeof r.feather === "number" && r.feather > 0) vm.feather = num2(r.feather, 0, 1e3, 0);
|
|
5884
|
+
return vm;
|
|
5885
|
+
}
|
|
5710
5886
|
function serializeMask(mask, hint, ctx) {
|
|
5711
5887
|
const out = { defaultValue: mask.defaultValue };
|
|
5712
5888
|
if (!mask.source) {
|
|
@@ -6114,6 +6290,13 @@ function deserializeLayer(raw, ctx) {
|
|
|
6114
6290
|
if (r.clipped === true) base.clipped = true;
|
|
6115
6291
|
const mask = readMask(r.mask, ctx, `Layer "${base.name || id}"`);
|
|
6116
6292
|
if (mask) base.mask = mask;
|
|
6293
|
+
if (r.vectorMask && typeof r.vectorMask === "object" && r.type !== "shape") {
|
|
6294
|
+
try {
|
|
6295
|
+
base.vectorMask = readVectorMask(r.vectorMask);
|
|
6296
|
+
} catch (err) {
|
|
6297
|
+
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.`);
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6117
6300
|
if (r.locks && typeof r.locks === "object") {
|
|
6118
6301
|
const locks = {};
|
|
6119
6302
|
for (const k of ["pixels", "transparency", "position", "all"]) if (r.locks[k] === true) locks[k] = true;
|
|
@@ -7337,97 +7520,6 @@ function writeAdjustmentBlock(kind, params) {
|
|
|
7337
7520
|
throw new Error(`Unknown adjustment kind "${kind}".`);
|
|
7338
7521
|
}
|
|
7339
7522
|
|
|
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
7523
|
// src/image-editor/psd/paths.ts
|
|
7432
7524
|
var RECORD = 26;
|
|
7433
7525
|
var fixed = (v) => v / 16777216;
|
|
@@ -7487,6 +7579,8 @@ function writePathRecords(path) {
|
|
|
7487
7579
|
return w.result();
|
|
7488
7580
|
}
|
|
7489
7581
|
var VMSK_INVERT = 1;
|
|
7582
|
+
var VMSK_NOT_LINKED = 2;
|
|
7583
|
+
var VMSK_DISABLED = 4;
|
|
7490
7584
|
function readVectorBlock(data) {
|
|
7491
7585
|
if (data.length < 8) return null;
|
|
7492
7586
|
const r = new PsdReader(data);
|
|
@@ -7557,6 +7651,97 @@ function vectorBlockForComponents(components, inverted, width, height, flags = 0
|
|
|
7557
7651
|
return writeVectorBlock({ subpaths, initialFill: subpaths.length === 0 && inverted }, flags & ~VMSK_INVERT | (inverted && subpaths.length ? VMSK_INVERT : 0));
|
|
7558
7652
|
}
|
|
7559
7653
|
|
|
7654
|
+
// src/image-editor/psd/channels.ts
|
|
7655
|
+
var hexToRgb2 = (hex) => {
|
|
7656
|
+
const v = parseInt(/^#?([0-9a-f]{6})$/i.exec(hex)?.[1] ?? "ff0000", 16);
|
|
7657
|
+
return [v >> 16 & 255, v >> 8 & 255, v & 255];
|
|
7658
|
+
};
|
|
7659
|
+
var rgbToHex = (r, g, b) => `#${[r, g, b].map((c) => c.toString(16).padStart(2, "0")).join("")}`;
|
|
7660
|
+
function alphaChannelResources(channels) {
|
|
7661
|
+
if (!channels.length) return [];
|
|
7662
|
+
const names = new PsdWriter();
|
|
7663
|
+
for (const c of channels) names.pascal(encodeLegacyName(c.name), 1);
|
|
7664
|
+
const unicode = new PsdWriter();
|
|
7665
|
+
for (const c of channels) unicode.unicode(c.name);
|
|
7666
|
+
const display = new PsdWriter();
|
|
7667
|
+
display.u32(1);
|
|
7668
|
+
for (const c of channels) {
|
|
7669
|
+
const [r, g, b] = hexToRgb2(c.color);
|
|
7670
|
+
display.u16(0);
|
|
7671
|
+
for (const v of [r, g, b, 0]) display.u16(v * 257);
|
|
7672
|
+
display.u16(Math.round(Math.min(1, Math.max(0, c.opacity)) * 100));
|
|
7673
|
+
display.u8(c.indicates === "selected" ? 0 : 1);
|
|
7674
|
+
display.u8(0);
|
|
7675
|
+
}
|
|
7676
|
+
return [
|
|
7677
|
+
{ id: 1006, name: "", data: names.result() },
|
|
7678
|
+
{ id: 1045, name: "", data: unicode.result() },
|
|
7679
|
+
{ id: 1077, name: "", data: display.result() }
|
|
7680
|
+
];
|
|
7681
|
+
}
|
|
7682
|
+
function planeTo8(plane, n, depth) {
|
|
7683
|
+
if (depth === 8) return plane.slice(0, n);
|
|
7684
|
+
const out = new Uint8Array(n);
|
|
7685
|
+
const view = new DataView(plane.buffer, plane.byteOffset, plane.byteLength);
|
|
7686
|
+
for (let i = 0; i < n; i++) {
|
|
7687
|
+
if (depth === 16) out[i] = plane[2 * i];
|
|
7688
|
+
else out[i] = Math.round(Math.min(1, Math.max(0, view.getFloat32(4 * i, false))) * 255);
|
|
7689
|
+
}
|
|
7690
|
+
return out;
|
|
7691
|
+
}
|
|
7692
|
+
function readAlphaChannels(planes, header, first, resource2, warn) {
|
|
7693
|
+
const count = header.channels - first;
|
|
7694
|
+
if (count <= 0 || header.depth === 1) return [];
|
|
7695
|
+
const size = rowBytes(header.width, header.depth) * header.height;
|
|
7696
|
+
const n = header.width * header.height;
|
|
7697
|
+
const names = [];
|
|
7698
|
+
const unicode = resource2(1045);
|
|
7699
|
+
if (unicode) {
|
|
7700
|
+
const r = new PsdReader(unicode);
|
|
7701
|
+
while (r.remaining >= 4 && names.length < count) names.push(r.unicode().replace(/\0+$/, ""));
|
|
7702
|
+
} else {
|
|
7703
|
+
const legacy = resource2(1006);
|
|
7704
|
+
if (legacy) {
|
|
7705
|
+
const r = new PsdReader(legacy);
|
|
7706
|
+
while (r.remaining >= 1 && names.length < count) names.push(new TextDecoder("latin1").decode(r.pascal(1)));
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7709
|
+
const display = [];
|
|
7710
|
+
const info = resource2(1077);
|
|
7711
|
+
if (info && info.length >= 4) {
|
|
7712
|
+
const r = new PsdReader(info);
|
|
7713
|
+
r.u32();
|
|
7714
|
+
while (r.remaining >= 14 && display.length < count) {
|
|
7715
|
+
r.u16();
|
|
7716
|
+
const [cr, cg, cb] = [r.u16(), r.u16(), r.u16()];
|
|
7717
|
+
r.u16();
|
|
7718
|
+
const opacity = r.u16();
|
|
7719
|
+
const kind = r.u8();
|
|
7720
|
+
r.u8();
|
|
7721
|
+
display.push({ color: rgbToHex(cr >> 8, cg >> 8, cb >> 8), opacity: Math.min(100, opacity) / 100, kind });
|
|
7722
|
+
}
|
|
7723
|
+
}
|
|
7724
|
+
const out = [];
|
|
7725
|
+
for (let i = 0; i < count; i++) {
|
|
7726
|
+
const start = (first + i) * size;
|
|
7727
|
+
if (start + size > planes.length) break;
|
|
7728
|
+
const d = display[i];
|
|
7729
|
+
const name = names[i] || `Alpha ${i + 1}`;
|
|
7730
|
+
if (d?.kind === 2) {
|
|
7731
|
+
warn(`Spot colour channel "${name}" is not supported and was left out.`);
|
|
7732
|
+
continue;
|
|
7733
|
+
}
|
|
7734
|
+
out.push({
|
|
7735
|
+
name,
|
|
7736
|
+
data: planeTo8(planes.subarray(start, start + size), n, header.depth),
|
|
7737
|
+
color: d?.color ?? "#ff0000",
|
|
7738
|
+
opacity: d?.opacity ?? 0.5,
|
|
7739
|
+
indicates: d?.kind === 0 ? "selected" : "masked"
|
|
7740
|
+
});
|
|
7741
|
+
}
|
|
7742
|
+
return out;
|
|
7743
|
+
}
|
|
7744
|
+
|
|
7560
7745
|
// src/image-editor/psd/shapes.ts
|
|
7561
7746
|
var SHAPE_PSD_KEYS = ["vsms", "vmsk", "vscg", "vstk", "vogk", "SoCo", "GdFl", "PtFl"];
|
|
7562
7747
|
var unit = (u, value) => ({ type: "UntF", unit: u, value });
|
|
@@ -10188,9 +10373,22 @@ function baseProps(ctx, rec, name, notes) {
|
|
|
10188
10373
|
ctx.warn.add("Artboards are not supported; they came in as plain groups, without their background colour and clipping", name);
|
|
10189
10374
|
notes.push("artboard imported as a group");
|
|
10190
10375
|
}
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
|
|
10376
|
+
const vectorBlock = psdKind(rec) === "shape" ? void 0 : rec.blocks.find((b) => b.key === "vsms") ?? rec.blocks.find((b) => b.key === "vmsk");
|
|
10377
|
+
if (vectorBlock) {
|
|
10378
|
+
const read2 = componentsFromVectorBlock(vectorBlock.data, ctx.file.header.width, ctx.file.header.height);
|
|
10379
|
+
if (read2) {
|
|
10380
|
+
const vm = { components: read2.components };
|
|
10381
|
+
if (read2.inverted) vm.inverted = true;
|
|
10382
|
+
if (read2.flags & VMSK_NOT_LINKED) vm.linked = false;
|
|
10383
|
+
if (read2.flags & VMSK_DISABLED) vm.enabled = false;
|
|
10384
|
+
const m = rec.mask;
|
|
10385
|
+
if (m?.vectorDensity !== void 0 && m.vectorDensity < 255) vm.density = percent(m.vectorDensity);
|
|
10386
|
+
if (m?.vectorFeather !== void 0 && m.vectorFeather > 0) vm.feather = Math.min(1e3, m.vectorFeather);
|
|
10387
|
+
props.vectorMask = vm;
|
|
10388
|
+
} else {
|
|
10389
|
+
ctx.warn.add("A vector mask could not be read and was ignored", name);
|
|
10390
|
+
notes.push("vector mask ignored");
|
|
10391
|
+
}
|
|
10194
10392
|
}
|
|
10195
10393
|
return props;
|
|
10196
10394
|
}
|
|
@@ -11539,6 +11737,13 @@ function exportPsd(input) {
|
|
|
11539
11737
|
if (fx) blocks.push(fx);
|
|
11540
11738
|
const locks = lockBits(layer);
|
|
11541
11739
|
if (locks) blocks.push({ key: "lspf", data: u32Bytes(locks >>> 0) });
|
|
11740
|
+
const vm = layer.type !== "shape" ? layer.vectorMask : void 0;
|
|
11741
|
+
if (vm) {
|
|
11742
|
+
const flags = (vm.linked === false ? VMSK_NOT_LINKED : 0) | (vm.enabled === false ? VMSK_DISABLED : 0);
|
|
11743
|
+
blocks.push({ key: "vmsk", data: vectorBlockForComponents(vm.components, vm.inverted === true, width, height, flags) });
|
|
11744
|
+
if ((vm.density ?? 1) !== 1 || (vm.feather ?? 0) > 0) note("Vector mask density and feather are not written; the paths are", layer.name);
|
|
11745
|
+
if (vm.fillRule === "evenodd") note("Vector masks are written with the nonzero fill rule (Photoshop has no even-odd paths)", layer.name);
|
|
11746
|
+
}
|
|
11542
11747
|
return {
|
|
11543
11748
|
opacity: Math.round(Math.min(1, Math.max(0, layer.opacity)) * 255),
|
|
11544
11749
|
clipping: layer.clipped ? 1 : 0,
|
|
@@ -11934,6 +12139,8 @@ export {
|
|
|
11934
12139
|
applyBoxBlur,
|
|
11935
12140
|
applyDepthMask,
|
|
11936
12141
|
removeAlpha,
|
|
12142
|
+
flattenPath,
|
|
12143
|
+
rasterizeCoverage,
|
|
11937
12144
|
TEXT_ALIGNS,
|
|
11938
12145
|
MAX_EFFECT_ANIMATION_MS,
|
|
11939
12146
|
SHAPE_OPERATIONS,
|
|
@@ -11950,17 +12157,6 @@ export {
|
|
|
11950
12157
|
isRenderedLayer,
|
|
11951
12158
|
shapeLayers,
|
|
11952
12159
|
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
12160
|
LayerOperationError,
|
|
11965
12161
|
userErrorMessage,
|
|
11966
12162
|
isPaintableLayer,
|
|
@@ -11998,6 +12194,7 @@ export {
|
|
|
11998
12194
|
componentPath,
|
|
11999
12195
|
shapeOutline,
|
|
12000
12196
|
shapeGeometryBounds,
|
|
12197
|
+
componentsCoverage,
|
|
12001
12198
|
shapeRenderClip,
|
|
12002
12199
|
renderShapeRect,
|
|
12003
12200
|
renderShape,
|
|
@@ -12018,6 +12215,24 @@ export {
|
|
|
12018
12215
|
shapeFromAnnotation,
|
|
12019
12216
|
annotationBlend,
|
|
12020
12217
|
annotationOfShape,
|
|
12218
|
+
isActiveVectorMask,
|
|
12219
|
+
vectorMaskCoverage,
|
|
12220
|
+
effectiveLayerMask,
|
|
12221
|
+
resolveVectorMasks,
|
|
12222
|
+
offsetVectorMask,
|
|
12223
|
+
transformVectorMask,
|
|
12224
|
+
followLayerMove,
|
|
12225
|
+
vectorMaskBounds,
|
|
12226
|
+
vectorMaskThumbnail,
|
|
12227
|
+
adjustmentNodeAsLayer,
|
|
12228
|
+
depthMaskAlpha,
|
|
12229
|
+
runLayerFilters,
|
|
12230
|
+
runLayerFiltersFloat,
|
|
12231
|
+
LayerCompositor,
|
|
12232
|
+
compositeDeep,
|
|
12233
|
+
DEFAULT_ADJUSTMENTS_VALUES,
|
|
12234
|
+
DEFAULT_TRANSFORM_VALUES,
|
|
12235
|
+
DEFAULT_DEPTH_MASK_VALUES,
|
|
12021
12236
|
TEXT_RENDER_MARGIN,
|
|
12022
12237
|
textMatrix,
|
|
12023
12238
|
textOutlines,
|