@pixerate/editor 0.113.0 → 0.115.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/THIRD_PARTY_NOTICES.md +8 -0
  2. package/dist/animation-editor/index.cjs +60 -54
  3. package/dist/animation-editor/index.cjs.map +1 -1
  4. package/dist/animation-editor/index.d.cts +3 -3
  5. package/dist/animation-editor/index.d.ts +3 -3
  6. package/dist/animation-editor/index.js +14 -8
  7. package/dist/animation-editor/index.js.map +1 -1
  8. package/dist/builtin-CY97ppKM.d.cts +354 -0
  9. package/dist/builtin-CY97ppKM.d.ts +354 -0
  10. package/dist/canvas/index.cjs +3 -3
  11. package/dist/canvas/index.cjs.map +1 -1
  12. package/dist/canvas/index.js +9 -9
  13. package/dist/chunk-26PGXHY7.cjs +753 -0
  14. package/dist/chunk-26PGXHY7.cjs.map +1 -0
  15. package/dist/chunk-2OJACTPR.cjs +929 -0
  16. package/dist/chunk-2OJACTPR.cjs.map +1 -0
  17. package/dist/chunk-3UFGNHSP.js +929 -0
  18. package/dist/chunk-3UFGNHSP.js.map +1 -0
  19. package/dist/chunk-643B2HKC.cjs +534 -0
  20. package/dist/chunk-643B2HKC.cjs.map +1 -0
  21. package/dist/chunk-6FCKOS2B.js +534 -0
  22. package/dist/chunk-6FCKOS2B.js.map +1 -0
  23. package/dist/chunk-BRLYOPLT.js +2574 -0
  24. package/dist/chunk-BRLYOPLT.js.map +1 -0
  25. package/dist/{chunk-LQEEDHOP.js → chunk-C6C5PDKU.js} +759 -761
  26. package/dist/chunk-C6C5PDKU.js.map +1 -0
  27. package/dist/{chunk-4KLT7ZYS.cjs → chunk-DFQEX3AM.cjs} +29 -29
  28. package/dist/{chunk-4KLT7ZYS.cjs.map → chunk-DFQEX3AM.cjs.map} +1 -1
  29. package/dist/chunk-G5NMLUVF.cjs +2574 -0
  30. package/dist/chunk-G5NMLUVF.cjs.map +1 -0
  31. package/dist/{chunk-YGIMK4NX.cjs → chunk-I7FQBWMQ.cjs} +813 -842
  32. package/dist/chunk-I7FQBWMQ.cjs.map +1 -0
  33. package/dist/{chunk-LPYOIVLA.js → chunk-LKCYANMW.js} +3 -3
  34. package/dist/{chunk-W3GXSUSQ.cjs → chunk-LPLCWZTM.cjs} +5087 -1283
  35. package/dist/chunk-LPLCWZTM.cjs.map +1 -0
  36. package/dist/chunk-MMVHBKMM.js +753 -0
  37. package/dist/chunk-MMVHBKMM.js.map +1 -0
  38. package/dist/{chunk-POGBUYWE.cjs → chunk-ST7E3NEE.cjs} +857 -859
  39. package/dist/chunk-ST7E3NEE.cjs.map +1 -0
  40. package/dist/{chunk-7VOHOPLW.cjs → chunk-TCKIXI46.cjs} +59 -59
  41. package/dist/{chunk-7VOHOPLW.cjs.map → chunk-TCKIXI46.cjs.map} +1 -1
  42. package/dist/{chunk-XIOMMGPD.cjs → chunk-TFOPHZ3U.cjs} +1 -1
  43. package/dist/chunk-TFOPHZ3U.cjs.map +1 -0
  44. package/dist/{chunk-Z4BC6M74.js → chunk-UBIXVXPR.js} +2 -2
  45. package/dist/{chunk-BLWCAQCW.js → chunk-VXR7XWMF.js} +4574 -770
  46. package/dist/chunk-VXR7XWMF.js.map +1 -0
  47. package/dist/{chunk-CZOXTEMN.js → chunk-XICA4PVO.js} +392 -421
  48. package/dist/chunk-XICA4PVO.js.map +1 -0
  49. package/dist/{chunk-HNS7UHCM.js → chunk-XK5LZKBD.js} +1 -1
  50. package/dist/chunk-XK5LZKBD.js.map +1 -0
  51. package/dist/cms/index.cjs +146 -0
  52. package/dist/cms/index.cjs.map +1 -0
  53. package/dist/cms/index.d.cts +51 -0
  54. package/dist/cms/index.d.ts +51 -0
  55. package/dist/cms/index.js +146 -0
  56. package/dist/cms/index.js.map +1 -0
  57. package/dist/{export-CEXEzqBf.d.cts → export-BJE8T0YA.d.cts} +279 -191
  58. package/dist/{export-DIkoK8-r.d.ts → export-Con3UKkm.d.ts} +279 -191
  59. package/dist/image-editor/index.cjs +1848 -1218
  60. package/dist/image-editor/index.cjs.map +1 -1
  61. package/dist/image-editor/index.d.cts +85 -93
  62. package/dist/image-editor/index.d.ts +85 -93
  63. package/dist/image-editor/index.js +871 -241
  64. package/dist/image-editor/index.js.map +1 -1
  65. package/dist/opentype-BZEOSOX3.d.ts +188 -0
  66. package/dist/opentype-Dpe75vL5.d.cts +188 -0
  67. package/dist/paint/index.cjs +4 -4
  68. package/dist/paint/index.d.cts +2 -2
  69. package/dist/paint/index.d.ts +2 -2
  70. package/dist/paint/index.js +3 -3
  71. package/dist/paint-editor/index.cjs +147 -145
  72. package/dist/paint-editor/index.cjs.map +1 -1
  73. package/dist/paint-editor/index.d.cts +9 -8
  74. package/dist/paint-editor/index.d.ts +9 -8
  75. package/dist/paint-editor/index.js +7 -5
  76. package/dist/paint-editor/index.js.map +1 -1
  77. package/dist/raster/index.cjs +53 -3
  78. package/dist/raster/index.cjs.map +1 -1
  79. package/dist/raster/index.d.cts +62 -91
  80. package/dist/raster/index.d.ts +62 -91
  81. package/dist/raster/index.js +59 -9
  82. package/dist/{render-BEriFuqK.d.cts → render-B1K_OoLy.d.cts} +87 -2
  83. package/dist/{render-tjzKaik-.d.ts → render-CrmHOgpo.d.ts} +87 -2
  84. package/dist/{retouch-CEIKR22W.d.cts → retouch-Bxkuc0KH.d.cts} +1 -1
  85. package/dist/{retouch-BXmeSCaB.d.ts → retouch-Do6tBs2H.d.ts} +1 -1
  86. package/dist/{surface-C8RRg_Bh.d.cts → surface-Cn30xIV6.d.cts} +6 -1
  87. package/dist/{surface-C8RRg_Bh.d.ts → surface-Cn30xIV6.d.ts} +6 -1
  88. package/dist/{svg-D_cfkhlB.d.cts → svg-BnRj0q8i.d.cts} +16 -4
  89. package/dist/{svg-wIaKLrU0.d.ts → svg-CmmBFfGT.d.ts} +16 -4
  90. package/dist/{types-DtL_qLFz.d.ts → types-1SUQKDAw.d.ts} +273 -4
  91. package/dist/{types-DyQq-WPY.d.cts → types-yINF17Ta.d.cts} +273 -4
  92. package/dist/vector/index.cjs +12170 -7343
  93. package/dist/vector/index.cjs.map +1 -1
  94. package/dist/vector/index.d.cts +1579 -16
  95. package/dist/vector/index.d.ts +1579 -16
  96. package/dist/vector/index.js +6303 -1476
  97. package/dist/vector/index.js.map +1 -1
  98. package/dist/warp-B39VsOtd.d.cts +348 -0
  99. package/dist/warp-DMN30aY6.d.ts +348 -0
  100. package/dist/zlib-dxQdgHyN.d.cts +95 -0
  101. package/dist/zlib-dxQdgHyN.d.ts +95 -0
  102. package/package.json +9 -2
  103. package/skills/pixerate-image-editor/SKILL.md +13 -36
  104. package/skills/pixerate-image-editor/references/color-management.md +42 -0
  105. package/skills/pixerate-image-editor/references/photoshop-files.md +41 -0
  106. package/skills/pixerate-paint-editor/SKILL.md +136 -4
  107. package/skills/pixerate-vector/SKILL.md +72 -2
  108. package/dist/chunk-BLWCAQCW.js.map +0 -1
  109. package/dist/chunk-CZOXTEMN.js.map +0 -1
  110. package/dist/chunk-HNS7UHCM.js.map +0 -1
  111. package/dist/chunk-JLYX4V57.cjs +0 -2001
  112. package/dist/chunk-JLYX4V57.cjs.map +0 -1
  113. package/dist/chunk-KB2YBCVS.js +0 -2001
  114. package/dist/chunk-KB2YBCVS.js.map +0 -1
  115. package/dist/chunk-LQEEDHOP.js.map +0 -1
  116. package/dist/chunk-POGBUYWE.cjs.map +0 -1
  117. package/dist/chunk-W3GXSUSQ.cjs.map +0 -1
  118. package/dist/chunk-XIOMMGPD.cjs.map +0 -1
  119. package/dist/chunk-YGIMK4NX.cjs.map +0 -1
  120. package/dist/homography-CZ-tFjay.d.cts +0 -50
  121. package/dist/homography-Ctfjoc1b.d.ts +0 -50
  122. package/dist/warp-CWHZzE3x.d.ts +0 -118
  123. package/dist/warp-D5jz_tN7.d.cts +0 -118
  124. /package/dist/{chunk-LPYOIVLA.js.map → chunk-LKCYANMW.js.map} +0 -0
  125. /package/dist/{chunk-Z4BC6M74.js.map → chunk-UBIXVXPR.js.map} +0 -0
@@ -0,0 +1,348 @@
1
+ import { B as BezierPath } from './bezier-DYBvLgUH.js';
2
+ import { W as WarpStyle, a as WarpMesh, V as Vec2, B as Box } from './types-CwMPoRMU.js';
3
+
4
+ /**
5
+ * Shared text model: character and paragraph styles over runs of plain text, and the `TextShaper`
6
+ * interface through which layout reaches fonts. Used by the image editor's type layers and the
7
+ * vector editor's text nodes, so both lay text out the same way. No DOM and no third-party code.
8
+ */
9
+
10
+ /** An axis-aligned rectangle. */
11
+ interface TextRect {
12
+ x: number;
13
+ y: number;
14
+ width: number;
15
+ height: number;
16
+ }
17
+ /**
18
+ * The character style of a run of text (Photoshop's Character panel). Sizes are document pixels;
19
+ * tracking is in 1/1000 em.
20
+ */
21
+ interface TextStyle {
22
+ /** Family name ("Inter"); fonts the system lacks fall back to a generic family, with a warning. */
23
+ fontFamily: string;
24
+ /** CSS weight, 100..900 (default 400). */
25
+ fontWeight: number;
26
+ /** Default false. */
27
+ italic?: boolean;
28
+ /** Font size in pixels. */
29
+ fontSize: number;
30
+ /** `#rrggbb`. */
31
+ color: string;
32
+ /** 0..1 (missing = 1). */
33
+ opacity?: number;
34
+ /** Letter spacing in 1/1000 em (missing = 0). */
35
+ tracking?: number;
36
+ /** Baseline-to-baseline distance in pixels; missing = auto (the paragraph's `autoLeading` × size). */
37
+ leading?: number;
38
+ /** Pixels; positive raises the text (missing = 0). */
39
+ baselineShift?: number;
40
+ underline?: boolean;
41
+ strikethrough?: boolean;
42
+ /** PostScript name from a PSD (`ArialMT`), kept so the exact face can be found and written back. */
43
+ postScriptName?: string;
44
+ }
45
+ /** A character style over `length` UTF-16 code units of the text, in order. */
46
+ interface TextRun {
47
+ length: number;
48
+ style: TextStyle;
49
+ }
50
+ type TextAlign = 'left' | 'center' | 'right' | 'justify';
51
+ declare const TEXT_ALIGNS: readonly TextAlign[];
52
+ /** Paragraph style (Photoshop's Paragraph panel); lengths in pixels. */
53
+ interface ParagraphStyle {
54
+ /** Default `left`. `justify` stretches every line of a paragraph box but the last. */
55
+ align?: TextAlign;
56
+ /** Auto leading as a multiple of the font size (default 1.2). */
57
+ autoLeading?: number;
58
+ firstLineIndent?: number;
59
+ startIndent?: number;
60
+ endIndent?: number;
61
+ spaceBefore?: number;
62
+ spaceAfter?: number;
63
+ }
64
+ /** A paragraph style over `length` UTF-16 code units (whole paragraphs, line break included). */
65
+ interface ParagraphRun {
66
+ length: number;
67
+ style: ParagraphStyle;
68
+ }
69
+ /** A font as a shaper is asked for it (sizes in pixels). */
70
+ interface TextFont {
71
+ family: string;
72
+ weight: number;
73
+ italic: boolean;
74
+ size: number;
75
+ postScriptName?: string;
76
+ }
77
+ /**
78
+ * What layout and rendering need from fonts. Implement `outline` (glyph outlines, pure) or `draw`
79
+ * (onto a 2D canvas), or both.
80
+ */
81
+ interface TextShaper {
82
+ /** Identifies the shaper (part of the render cache key). */
83
+ readonly id: string;
84
+ /** The family that draws `font`: the family itself when available, else a fallback (`available: false`). */
85
+ resolve(font: TextFont): {
86
+ family: string;
87
+ available: boolean;
88
+ };
89
+ /** Ascent and descent (both positive, pixels) of the resolved font at `font.size`. */
90
+ metrics(font: TextFont): {
91
+ ascent: number;
92
+ descent: number;
93
+ };
94
+ /**
95
+ * Caret positions of `text` set in `font`: `text.length + 1` x offsets in pixels from the start
96
+ * (kerning included, tracking not).
97
+ */
98
+ measure(text: string, font: TextFont): ArrayLike<number>;
99
+ /** Glyph outlines of `text` with its start on the baseline at (`x`, `y`), pixels, y down. */
100
+ outline?(text: string, font: TextFont, x: number, y: number): BezierPath;
101
+ /** Draws `text` with its start on the baseline at (`x`, `y`) in the context's current transform, fill style and alpha. */
102
+ draw?(ctx: CanvasRenderingContext2D, text: string, font: TextFont, x: number, y: number): void;
103
+ /** Families this shaper can draw (for font pickers and agents), when it can tell. */
104
+ families?(): string[];
105
+ }
106
+
107
+ /**
108
+ * Text layout shared by the image editor's type layers and the vector editor's text nodes: styled
109
+ * runs, paragraphs, line breaking (at spaces and after break characters, and between wide CJK
110
+ * characters), alignment and justification, tracking, leading, indents, spacing, baseline shift,
111
+ * caret positions and hit testing. Everything that needs a font goes through a `TextShaper`, so
112
+ * the same measurements give the same lines in every host.
113
+ */
114
+
115
+ /** What layout needs: the text, its character and paragraph runs and its frame. */
116
+ interface TextBlock {
117
+ text: string;
118
+ runs: ReadonlyArray<{
119
+ length: number;
120
+ style: TextStyle;
121
+ }>;
122
+ paragraphs: readonly ParagraphRun[];
123
+ /**
124
+ * `point`: lines grow from the origin, one per paragraph; the first baseline is at y = 0, or one
125
+ * ascent down with `origin: 'top'`. `box`: lines wrap inside `width` and the first baseline is one
126
+ * ascent below the top (`height` bounds the layout's box; lines past it still lay out).
127
+ */
128
+ frame: {
129
+ kind: 'point';
130
+ origin?: 'baseline' | 'top';
131
+ } | {
132
+ kind: 'box';
133
+ width: number;
134
+ height: number;
135
+ };
136
+ }
137
+ declare const DEFAULT_TEXT_STYLE: TextStyle;
138
+ declare const DEFAULT_AUTO_LEADING = 1.2;
139
+ /** Runs fitted to a text of `length` (the last run stretches or is cut), never empty while there is text. */
140
+ declare function fitRuns<R extends {
141
+ length: number;
142
+ style: object;
143
+ }>(runs: readonly R[], length: number, fallback: R['style']): R[];
144
+ /** The style of the run holding code unit `index` (the last run past the end). */
145
+ declare function styleAt<S extends object>(runs: ReadonlyArray<{
146
+ length: number;
147
+ style: S;
148
+ }>, index: number, fallback: S): S;
149
+ /** Runs with `patch(style)` applied over [start, end). */
150
+ declare function restyleRange<S extends object>(runs: ReadonlyArray<{
151
+ length: number;
152
+ style: S;
153
+ }>, start: number, end: number, patch: (s: S) => S): Array<{
154
+ length: number;
155
+ style: S;
156
+ }>;
157
+ /** Runs after replacing [start, end) of the text with `inserted` code units styled `style`. */
158
+ declare function spliceRuns<S extends object>(runs: ReadonlyArray<{
159
+ length: number;
160
+ style: S;
161
+ }>, start: number, end: number, inserted: number, style: S): Array<{
162
+ length: number;
163
+ style: S;
164
+ }>;
165
+ /** Paragraph ranges [start, end) of the text, the line break included in the paragraph it ends. */
166
+ declare function paragraphRanges(text: string): Array<{
167
+ start: number;
168
+ end: number;
169
+ content: number;
170
+ }>;
171
+ /** Paragraph runs snapped to whole paragraphs: each paragraph takes the style at its start. */
172
+ declare function fitParagraphRuns(runs: readonly ParagraphRun[], text: string): ParagraphRun[];
173
+ /** One laid-out character. */
174
+ interface LaidChar {
175
+ /** Index in the text (UTF-16 code unit). */
176
+ index: number;
177
+ /** Text-space x of its start and its advance (tracking and justification included). */
178
+ x: number;
179
+ advance: number;
180
+ }
181
+ /** A run of characters on one line in one style. */
182
+ interface LaidFragment {
183
+ start: number;
184
+ end: number;
185
+ style: TextStyle;
186
+ font: TextFont;
187
+ /** The family the shaper draws it with. */
188
+ family: string;
189
+ x: number;
190
+ /** Baseline in text space (baseline shift applied). */
191
+ y: number;
192
+ /** Per-character x positions relative to `x` (`end − start + 1` entries). */
193
+ carets: number[];
194
+ /** Whether characters are placed one by one (tracking or justification). */
195
+ spaced: boolean;
196
+ }
197
+ interface LaidLine {
198
+ start: number;
199
+ /** End of the line's characters (line break and trailing spaces excluded from `width`). */
200
+ end: number;
201
+ baseline: number;
202
+ ascent: number;
203
+ descent: number;
204
+ x: number;
205
+ width: number;
206
+ chars: LaidChar[];
207
+ fragments: LaidFragment[];
208
+ }
209
+ /** A laid-out type layer in text space. */
210
+ interface TextLayout {
211
+ lines: LaidLine[];
212
+ /** Bounds of the lines (ascent to descent), text space; the box for paragraph text. */
213
+ bounds: TextRect;
214
+ /** Families replaced by a fallback. */
215
+ missingFonts: string[];
216
+ }
217
+ declare function fontOf(style: TextStyle): TextFont;
218
+ /** Lays out a type layer with `shaper` (memoised per layer object and shaper). */
219
+ declare function layoutText(layer: TextBlock, shaper: TextShaper): TextLayout;
220
+ /** The text index nearest a text-space point (for clicks): the line under it, then the closest caret. */
221
+ declare function textIndexAt(layout: TextLayout, p: {
222
+ x: number;
223
+ y: number;
224
+ }): number;
225
+ /** The caret at text index `index`: its x and the line's top and bottom, text space. */
226
+ declare function caretAt(layout: TextLayout, index: number): {
227
+ x: number;
228
+ top: number;
229
+ bottom: number;
230
+ line: number;
231
+ };
232
+
233
+ /**
234
+ * Warp geometry shared by the vector editor's envelopes and the image editor's Free Transform
235
+ * warp: preset warp styles and editable Bézier meshes, both forward maps from a source box onto
236
+ * the plane.
237
+ *
238
+ * - **Presets** (`WarpStyle`, Photoshop's fifteen with bend and horizontal / vertical distortion
239
+ * in percent): each is one bicubic Bézier patch whose 4 × 4 control grid follows the bend
240
+ * (`warpPresetGrid`). Every preset is the identity at bend 0 and continuous in it.
241
+ * - **Meshes** (`WarpMesh`): a grid of bicubic patches. Splitting a mesh (`splitWarpMeshU` / `V`)
242
+ * is an exact de Casteljau subdivision, so it never changes the shape, and a preset turns into
243
+ * an editable mesh of any size exactly.
244
+ *
245
+ * Ported from ArtCraft (MIT OR Apache-2.0, © ArtCraft Team and contributors; see
246
+ * THIRD_PARTY_NOTICES.md): photocraft `crates/geom/src/warp.rs` (`WarpStyle`, `StyleWarp`,
247
+ * `rest_grid`, `edge_arc`, `arc_grid`, `blend_rows`, `preset_grid`, `bernstein`, `split_cubic`,
248
+ * `BERN_INV`, and `BezierMesh`: `identity`, `fit`, `eval`, `split_u`, `split_v`, `remove_split_u`,
249
+ * `transpose`, `map_points`, `param_at`, `pull`, `smooth_lines`, `drag_point`, `control_bounds`;
250
+ * `Warp::map` and `Warp::to_mesh`).
251
+ */
252
+
253
+ /** The fifteen presets in Photoshop's menu order. */
254
+ declare const WARP_STYLES: readonly WarpStyle[];
255
+ declare const WARP_STYLE_LABELS: Readonly<Record<WarpStyle, string>>;
256
+ /**
257
+ * A preset as a Bézier control grid (rows of `[x, y]` in box units, y down). `b`, `hd`, `vd` are
258
+ * the bend and the horizontal / vertical distortion as fractions (−1…1); `aspect` is the box's
259
+ * height over its width. The arc family stays circular in pixels, the other offsets scale with the
260
+ * box, and a vertical warp is the horizontal one with the axes (and distortions) swapped.
261
+ */
262
+ declare function warpPresetGrid(style: WarpStyle, b: number, hd: number, vd: number, vertical: boolean, aspect: number): [number, number][][];
263
+ /** A preset warp's settings (bend and distortions in percent, −100…100). */
264
+ interface WarpPreset {
265
+ style: WarpStyle;
266
+ bend: number;
267
+ horizontal?: number;
268
+ vertical?: number;
269
+ /** Bend along the vertical axis. */
270
+ orientation?: 'horizontal' | 'vertical';
271
+ }
272
+ /**
273
+ * A preset as one Bézier patch over `bounds` (points in the bounds' space), or null when it
274
+ * changes nothing (an unknown style, all-zero amounts, or an empty box).
275
+ */
276
+ declare function warpPresetMesh(preset: WarpPreset, bounds: Box): WarpMesh | null;
277
+ /** Control points per row. */
278
+ declare const warpMeshColumns: (m: WarpMesh) => number;
279
+ /** Control point rows. */
280
+ declare const warpMeshRows: (m: WarpMesh) => number;
281
+ /** Whether a mesh is well formed: knots from 0 to 1 increasing, the right number of finite points. */
282
+ declare function isValidWarpMesh(m: unknown): m is WarpMesh;
283
+ /** Fits a mesh with patch boundaries `us` / `vs` to the map `f` (box fractions → output) by interpolating it at each patch's thirds. Exact for bicubic maps (affine maps included). */
284
+ declare function fitWarpMesh(f: (s: number, t: number) => Vec2, us: readonly number[], vs: readonly number[]): WarpMesh;
285
+ /** The identity mesh over `bounds` with `columns × rows` patches. */
286
+ declare function identityWarpMesh(bounds: Box, columns?: number, rows?: number): WarpMesh;
287
+ /** The surface at box fractions `(s, t)`. */
288
+ declare function evalWarpMesh(m: WarpMesh, s: number, t: number): Vec2;
289
+ /** The mesh with the patch column holding `s` split at `s` (exact). Null when `s` is on or too close to a boundary. */
290
+ declare function splitWarpMeshU(m: WarpMesh, s: number): WarpMesh | null;
291
+ /** The mesh with the patch row holding `t` split at `t` (exact). */
292
+ declare function splitWarpMeshV(m: WarpMesh, t: number): WarpMesh | null;
293
+ /**
294
+ * The mesh without interior vertical boundary `k` (1 … us.length − 2), its two patch columns
295
+ * merged: exact when they came from an unedited split; otherwise the outer control points stay and
296
+ * the inner handles are extrapolated.
297
+ */
298
+ declare function removeWarpMeshSplitU(m: WarpMesh, k: number): WarpMesh | null;
299
+ /** Removes interior horizontal boundary `k`. */
300
+ declare function removeWarpMeshSplitV(m: WarpMesh, k: number): WarpMesh | null;
301
+ /** Every control point through `f` (exact for affine maps). */
302
+ declare function mapWarpMeshPoints(m: WarpMesh, f: (p: Vec2) => Vec2): WarpMesh;
303
+ /**
304
+ * The mesh with `columns × rows` patches over the same surface: split exactly when its knots are
305
+ * a subset of the new even ones (a one-patch mesh always is), otherwise refitted.
306
+ */
307
+ declare function resizeWarpMesh(m: WarpMesh, columns: number, rows: number): WarpMesh;
308
+ /** Bounding box of the control points (the surface lies inside it). */
309
+ declare function warpMeshControlBounds(m: WarpMesh): Box;
310
+ /** Box fractions `(s, t)` whose surface point is nearest `p` (a coarse search, then Newton steps). */
311
+ declare function warpMeshParamAt(m: WarpMesh, p: Vec2): {
312
+ s: number;
313
+ t: number;
314
+ distance: number;
315
+ };
316
+ /**
317
+ * `base` with control point `index` dragged by `d`, keeping the surface smooth across the grid
318
+ * lines: an anchor moves with the eight points around it as one block; a handle moves with the two
319
+ * points beside it across its line, and the handle opposite it on the same anchor turns the other
320
+ * way (with its two neighbours), so the line keeps passing straight through the anchor. An inner
321
+ * point of a patch moves alone.
322
+ */
323
+ declare function dragWarpMeshPoint(base: WarpMesh, index: number, d: Vec2): WarpMesh;
324
+ /**
325
+ * `base` with its surface point at box fractions `(s, t)` pulled by `d`, the way dragging inside a
326
+ * patch works: interior anchors within about two cells follow by a share of `d` that fades with
327
+ * distance (each with its block of handles, so grid lines stay smooth); the outer border stays
328
+ * pinned unless the grab is near it, and then it bows; the four corners never move. The grabbed
329
+ * patch's inner points (and, near the border, the border's handles) make up the rest, so the
330
+ * grabbed point lands exactly at its start + `d`.
331
+ */
332
+ declare function pullWarpMesh(base: WarpMesh, s: number, t: number, d: Vec2): WarpMesh;
333
+ /**
334
+ * The control points an editor shows: the anchors (where grid lines cross) and the handles along
335
+ * the grid lines, each with the anchor it belongs to. Points inside a patch are left out (drag
336
+ * inside a patch to pull the surface instead).
337
+ */
338
+ declare function warpMeshEditPoints(m: WarpMesh): {
339
+ anchors: number[];
340
+ handles: {
341
+ index: number;
342
+ anchor: number;
343
+ }[];
344
+ };
345
+ /** The mesh's grid lines (patch borders) as cubics `[p0, p1, p2, p3]`. */
346
+ declare function warpMeshGridCurves(m: WarpMesh): Vec2[][];
347
+
348
+ export { warpPresetGrid as A, warpPresetMesh as B, type TextAlign as C, DEFAULT_AUTO_LEADING as D, DEFAULT_TEXT_STYLE as E, type LaidChar as F, TEXT_ALIGNS as G, caretAt as H, fitParagraphRuns as I, fitRuns as J, fontOf as K, type LaidFragment as L, layoutText as M, paragraphRanges as N, restyleRange as O, type ParagraphRun as P, spliceRuns as Q, styleAt as R, textIndexAt as S, type TextShaper as T, type WarpPreset as W, type TextFont as a, type TextRun as b, type ParagraphStyle as c, type TextStyle as d, type TextLayout as e, type LaidLine as f, WARP_STYLES as g, WARP_STYLE_LABELS as h, dragWarpMeshPoint as i, evalWarpMesh as j, fitWarpMesh as k, isValidWarpMesh as l, mapWarpMeshPoints as m, removeWarpMeshSplitV as n, resizeWarpMesh as o, pullWarpMesh as p, splitWarpMeshV as q, removeWarpMeshSplitU as r, splitWarpMeshU as s, identityWarpMesh as t, warpMeshControlBounds as u, warpMeshEditPoints as v, warpMeshColumns as w, warpMeshGridCurves as x, warpMeshParamAt as y, warpMeshRows as z };
@@ -0,0 +1,95 @@
1
+ /** Above this σ a Gaussian is approximated by three box passes (cost independent of σ). */
2
+ declare const DIRECT_GAUSSIAN_MAX_SIGMA = 8;
3
+ /** How a blur treats pixels beyond the image: `zero`, or `clamp` (replicate the border). */
4
+ type BlurEdge = 'zero' | 'clamp';
5
+ type Edge = BlurEdge;
6
+ /**
7
+ * Separable Gaussian blur of a single-channel float image. `edge` `zero` treats pixels beyond
8
+ * the image as 0 (selection feather), `clamp` replicates the border (mask refinement). σ up to
9
+ * {@link DIRECT_GAUSSIAN_MAX_SIGMA} is convolved directly; wider blurs use three box passes, so
10
+ * the cost per pixel never grows with σ.
11
+ */
12
+ declare function gaussianBlur(src: ArrayLike<number>, w: number, h: number, sigma: number, edge?: Edge): Float32Array;
13
+
14
+ /**
15
+ * Curve lookup tables shared by Curves adjustments and layer-effect contours.
16
+ */
17
+ /** Entries of a curve LUT (input 0..1 in `size - 1` steps). */
18
+ declare const CURVE_LUT_SIZE = 4096;
19
+ /**
20
+ * Photoshop curve: a natural cubic spline through the points (0..1 in and out; it may overshoot
21
+ * between points, as Photoshop's does), constant outside the first / last point, clamped to
22
+ * 0..1, sampled into a LUT. Fewer than two distinct points: the identity.
23
+ */
24
+ declare function curveLut(points: ReadonlyArray<{
25
+ input: number;
26
+ output: number;
27
+ }>, size?: number): Float32Array;
28
+
29
+ /**
30
+ * Dependency-free, synchronous PNG encode / decode for layer pixels and masks, plus base64 and
31
+ * data-URL helpers. Canvas `toDataURL` is lossy for partly transparent pixels (it goes through
32
+ * premultiplied alpha), so layer assets use this codec and round-trip exactly.
33
+ *
34
+ * Encodes 8-bit greyscale (masks) or RGBA (layers). Decodes non-interlaced 1/2/4/8/16-bit
35
+ * greyscale, RGB, palette, grey + alpha and RGBA, always to 8-bit RGBA or grey.
36
+ */
37
+ interface DecodedPng {
38
+ width: number;
39
+ height: number;
40
+ /** 4 for RGBA output, 1 for greyscale without alpha when `keepGray` was asked for. */
41
+ channels: 1 | 4;
42
+ data: Uint8ClampedArray;
43
+ }
44
+ /** PNG bytes for `width × height` pixels of 1 (grey) or 4 (RGBA) bytes each. */
45
+ declare function encodePng(width: number, height: number, data: ArrayLike<number>, channels?: 1 | 4): Uint8Array;
46
+ declare function isPng(bytes: Uint8Array): boolean;
47
+ /**
48
+ * Decodes a PNG. Output is RGBA (4 channels) unless `keepGray` is set and the image is
49
+ * greyscale without alpha, in which case it is one byte per pixel.
50
+ */
51
+ declare function decodePng(bytes: Uint8Array, options?: {
52
+ keepGray?: boolean;
53
+ maxPixels?: number;
54
+ }): DecodedPng;
55
+ declare function bytesToBase64(bytes: Uint8Array): string;
56
+ declare function base64ToBytes(text: string): Uint8Array;
57
+ /** The bytes and MIME type of a `data:` URL (base64 or percent-encoded). */
58
+ declare function parseDataUrl(url: string): {
59
+ mime: string;
60
+ bytes: Uint8Array;
61
+ } | null;
62
+ declare function pngDataUrl(width: number, height: number, data: ArrayLike<number>, channels?: 1 | 4): string;
63
+
64
+ /**
65
+ * Synchronous zlib (RFC 1950) and raw deflate (RFC 1951), dependency-free, for the image editor's
66
+ * layer PNGs and PSD ZIP channels. `CompressionStream` is asynchronous and not everywhere, and
67
+ * layer pixels must serialize inside the synchronous `toJSON()`.
68
+ *
69
+ * - `inflate` handles stored, fixed and dynamic blocks and refuses to produce more than
70
+ * `maxOutput` bytes (a decompression bomb guard).
71
+ * - `deflate` is a greedy LZ77 (hash chains, 32 KiB window) with one dynamic Huffman block per
72
+ * 64 Ki symbols. It favours simplicity over the last few percent of ratio.
73
+ */
74
+ declare class ZlibError extends Error {
75
+ constructor(message: string);
76
+ }
77
+ /** Inflates raw deflate data (no zlib header). */
78
+ declare function inflateRaw(data: Uint8Array, options?: {
79
+ maxOutput?: number;
80
+ sizeHint?: number;
81
+ }): Uint8Array;
82
+ /** Adler-32 checksum (zlib trailer). */
83
+ declare function adler32(data: Uint8Array): number;
84
+ /** Inflates zlib data (header, deflate stream, Adler-32 trailer). */
85
+ declare function inflate(data: Uint8Array, options?: {
86
+ maxOutput?: number;
87
+ sizeHint?: number;
88
+ verify?: boolean;
89
+ }): Uint8Array;
90
+ /** Raw deflate (no zlib header). `level` 0 stores the data uncompressed. */
91
+ declare function deflateRaw(data: Uint8Array, level?: 0 | 1): Uint8Array;
92
+ /** zlib stream: header, raw deflate, Adler-32. */
93
+ declare function deflate(data: Uint8Array, level?: 0 | 1): Uint8Array;
94
+
95
+ export { type BlurEdge as B, CURVE_LUT_SIZE as C, DIRECT_GAUSSIAN_MAX_SIGMA as D, ZlibError as Z, type DecodedPng as a, adler32 as b, base64ToBytes as c, bytesToBase64 as d, curveLut as e, decodePng as f, deflate as g, deflateRaw as h, encodePng as i, gaussianBlur as j, inflate as k, inflateRaw as l, isPng as m, pngDataUrl as n, parseDataUrl as p };
@@ -0,0 +1,95 @@
1
+ /** Above this σ a Gaussian is approximated by three box passes (cost independent of σ). */
2
+ declare const DIRECT_GAUSSIAN_MAX_SIGMA = 8;
3
+ /** How a blur treats pixels beyond the image: `zero`, or `clamp` (replicate the border). */
4
+ type BlurEdge = 'zero' | 'clamp';
5
+ type Edge = BlurEdge;
6
+ /**
7
+ * Separable Gaussian blur of a single-channel float image. `edge` `zero` treats pixels beyond
8
+ * the image as 0 (selection feather), `clamp` replicates the border (mask refinement). σ up to
9
+ * {@link DIRECT_GAUSSIAN_MAX_SIGMA} is convolved directly; wider blurs use three box passes, so
10
+ * the cost per pixel never grows with σ.
11
+ */
12
+ declare function gaussianBlur(src: ArrayLike<number>, w: number, h: number, sigma: number, edge?: Edge): Float32Array;
13
+
14
+ /**
15
+ * Curve lookup tables shared by Curves adjustments and layer-effect contours.
16
+ */
17
+ /** Entries of a curve LUT (input 0..1 in `size - 1` steps). */
18
+ declare const CURVE_LUT_SIZE = 4096;
19
+ /**
20
+ * Photoshop curve: a natural cubic spline through the points (0..1 in and out; it may overshoot
21
+ * between points, as Photoshop's does), constant outside the first / last point, clamped to
22
+ * 0..1, sampled into a LUT. Fewer than two distinct points: the identity.
23
+ */
24
+ declare function curveLut(points: ReadonlyArray<{
25
+ input: number;
26
+ output: number;
27
+ }>, size?: number): Float32Array;
28
+
29
+ /**
30
+ * Dependency-free, synchronous PNG encode / decode for layer pixels and masks, plus base64 and
31
+ * data-URL helpers. Canvas `toDataURL` is lossy for partly transparent pixels (it goes through
32
+ * premultiplied alpha), so layer assets use this codec and round-trip exactly.
33
+ *
34
+ * Encodes 8-bit greyscale (masks) or RGBA (layers). Decodes non-interlaced 1/2/4/8/16-bit
35
+ * greyscale, RGB, palette, grey + alpha and RGBA, always to 8-bit RGBA or grey.
36
+ */
37
+ interface DecodedPng {
38
+ width: number;
39
+ height: number;
40
+ /** 4 for RGBA output, 1 for greyscale without alpha when `keepGray` was asked for. */
41
+ channels: 1 | 4;
42
+ data: Uint8ClampedArray;
43
+ }
44
+ /** PNG bytes for `width × height` pixels of 1 (grey) or 4 (RGBA) bytes each. */
45
+ declare function encodePng(width: number, height: number, data: ArrayLike<number>, channels?: 1 | 4): Uint8Array;
46
+ declare function isPng(bytes: Uint8Array): boolean;
47
+ /**
48
+ * Decodes a PNG. Output is RGBA (4 channels) unless `keepGray` is set and the image is
49
+ * greyscale without alpha, in which case it is one byte per pixel.
50
+ */
51
+ declare function decodePng(bytes: Uint8Array, options?: {
52
+ keepGray?: boolean;
53
+ maxPixels?: number;
54
+ }): DecodedPng;
55
+ declare function bytesToBase64(bytes: Uint8Array): string;
56
+ declare function base64ToBytes(text: string): Uint8Array;
57
+ /** The bytes and MIME type of a `data:` URL (base64 or percent-encoded). */
58
+ declare function parseDataUrl(url: string): {
59
+ mime: string;
60
+ bytes: Uint8Array;
61
+ } | null;
62
+ declare function pngDataUrl(width: number, height: number, data: ArrayLike<number>, channels?: 1 | 4): string;
63
+
64
+ /**
65
+ * Synchronous zlib (RFC 1950) and raw deflate (RFC 1951), dependency-free, for the image editor's
66
+ * layer PNGs and PSD ZIP channels. `CompressionStream` is asynchronous and not everywhere, and
67
+ * layer pixels must serialize inside the synchronous `toJSON()`.
68
+ *
69
+ * - `inflate` handles stored, fixed and dynamic blocks and refuses to produce more than
70
+ * `maxOutput` bytes (a decompression bomb guard).
71
+ * - `deflate` is a greedy LZ77 (hash chains, 32 KiB window) with one dynamic Huffman block per
72
+ * 64 Ki symbols. It favours simplicity over the last few percent of ratio.
73
+ */
74
+ declare class ZlibError extends Error {
75
+ constructor(message: string);
76
+ }
77
+ /** Inflates raw deflate data (no zlib header). */
78
+ declare function inflateRaw(data: Uint8Array, options?: {
79
+ maxOutput?: number;
80
+ sizeHint?: number;
81
+ }): Uint8Array;
82
+ /** Adler-32 checksum (zlib trailer). */
83
+ declare function adler32(data: Uint8Array): number;
84
+ /** Inflates zlib data (header, deflate stream, Adler-32 trailer). */
85
+ declare function inflate(data: Uint8Array, options?: {
86
+ maxOutput?: number;
87
+ sizeHint?: number;
88
+ verify?: boolean;
89
+ }): Uint8Array;
90
+ /** Raw deflate (no zlib header). `level` 0 stores the data uncompressed. */
91
+ declare function deflateRaw(data: Uint8Array, level?: 0 | 1): Uint8Array;
92
+ /** zlib stream: header, raw deflate, Adler-32. */
93
+ declare function deflate(data: Uint8Array, level?: 0 | 1): Uint8Array;
94
+
95
+ export { type BlurEdge as B, CURVE_LUT_SIZE as C, DIRECT_GAUSSIAN_MAX_SIGMA as D, ZlibError as Z, type DecodedPng as a, adler32 as b, base64ToBytes as c, bytesToBase64 as d, curveLut as e, decodePng as f, deflate as g, deflateRaw as h, encodePng as i, gaussianBlur as j, inflate as k, inflateRaw as l, isPng as m, pngDataUrl as n, parseDataUrl as p };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.113.0",
3
+ "version": "0.115.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -79,6 +79,11 @@
79
79
  "import": "./dist/raster/index.js",
80
80
  "require": "./dist/raster/index.cjs"
81
81
  },
82
+ "./cms": {
83
+ "types": "./dist/cms/index.d.ts",
84
+ "import": "./dist/cms/index.js",
85
+ "require": "./dist/cms/index.cjs"
86
+ },
82
87
  "./paint": {
83
88
  "types": "./dist/paint/index.d.ts",
84
89
  "import": "./dist/paint/index.js",
@@ -192,6 +197,7 @@
192
197
  "@types/dagre": "^0.7.54",
193
198
  "@types/markdown-it": "^14.2.0",
194
199
  "@types/node": "^26.6.5",
200
+ "assemblyscript": "0.28.20",
195
201
  "fast-check": "^3.23.2",
196
202
  "mermaid": "12.0.0",
197
203
  "tiptap-markdown": "^0.8.10",
@@ -213,6 +219,7 @@
213
219
  "build": "tsup",
214
220
  "typecheck": "tsc --noEmit",
215
221
  "test": "vitest run",
216
- "test:corpus": "node scripts/fetch-psd-corpus.mjs && PSD_CORPUS=1 vitest run tests/psd-corpus.test.ts"
222
+ "test:corpus": "node scripts/fetch-psd-corpus.mjs && PSD_CORPUS=1 vitest run tests/psd-corpus.test.ts",
223
+ "build:wasm": "node scripts/build-wet-wasm.mjs"
217
224
  }
218
225
  }
@@ -183,6 +183,10 @@ drawing or editing either.
183
183
  false` and `cleanEachStroke: false`, start with `load_mixer_brush`, read the brush with
184
184
  `get_mixer_brush` when unsure, and reset it with `clean_mixer_brush`.
185
185
 
186
+ ### Colour management
187
+
188
+ The working space, Assign / Convert to Profile with intents, soft proofing with a gamut warning
189
+ and colour conversions for print briefs: read [references/color-management.md](references/color-management.md).
186
190
  ### Retouching
187
191
 
188
192
  Clone Stamp, Healing Brush, Spot Healing, Smudge, Dodge, Burn and Sponge: retouch on an empty
@@ -192,42 +196,9 @@ flaws and `clone_stamp` to copy exactly, and keep strokes short and soft. Read
192
196
 
193
197
  ### Photoshop files
194
198
 
195
- 1. **Inspect before opening.** `inspect_psd` lists every layer with the type it would become and
196
- a status: `mapped` (held exactly), `approximated` (type, fill and shape layers come in as their
197
- stored pixels; linked smart objects as their stored pixels; Photoshop smart filters other than
198
- Box Blur are kept but not drawn; Dissolve becomes Normal; effects with gradient midpoints or
199
- smoothness, noise gradients, glow jitter, legacy bevels or patterns missing from the file are
200
- drawn approximately) or
201
- `dropped` (Brightness/Contrast, Exposure, Channel Mixer, Gradient Map, Selective Color and
202
- Color Lookup adjustments). Tell the user what would be lost before `import_psd` when anything
203
- is not `mapped`.
204
- 2. **Layer effects come in live**: every Photoshop layer style maps to an effect you can read
205
- with `list_layer_effects` and change as usual, with the file's patterns (listed in
206
- `list_layer_effects.patterns`) and its global light. An effects block the editor cannot read
207
- is kept and written back by `export_psd`, but not drawn; the warnings say so.
208
- 3. **`import_psd` replaces the document** (one undo step). 16- and 32-bit files keep their
209
- depth (`get_image_info.bitDepth`); CMYK, Lab, grayscale and indexed files are converted to RGB,
210
- and ICC profiles are ignored; the warnings say so. `export_psd` writes at the document's depth.
211
- Change it with `set_bit_depth`: 16-bit for smooth gradients and strong grades, 32-bit for
212
- linear-light HDR (layers blend in linear light there). Adjustment layers, blend modes, merges
213
- and the exported merged image keep the depth; layer effects, layer filters, painting and
214
- transforms still work in 8 bits on what they change, so grade a deep file with adjustment
215
- layers rather than repainting it when the depth matters. Afterwards work with `list_layers` as usual: there is no Background source image, every
216
- pixel comes from the layers.
217
- 4. **Smart objects come in live**: embedded contents (a PSB opens as a nested document you can
218
- edit with `edit_smart_object_contents`, a PNG as raster contents), the placement and warp, the
219
- smart filters and the filter mask; the layer shows Photoshop's own pixels until you change it.
220
- JPEG, TIFF and SVG contents are decoded only in a browser, so elsewhere such a smart object
221
- cannot be transformed (the error says so). Photoshop filters we do not model stay in the list
222
- (kind `psd`): hide, reorder, re-blend or remove them, and `export_psd` writes them back.
223
- 5. **`export_psd` writes the stack as it is**, in the source frame: crop and rotation are not
224
- applied, layer filters and depth masks are baked, annotation layers are rasterized (only in
225
- a browser). Effects are written as Photoshop layer styles: an effects block opened from a PSD
226
- goes back byte for byte while its effects are unchanged, and the patterns effects use (the
227
- built-in ones too) are embedded. Only the first outer glow, inner glow, bevel, satin and
228
- pattern overlay of a layer fit in a PSD. Smart objects are written as smart objects (an
229
- unchanged one byte for byte); a Develop smart filter has no Photoshop filter and is left out of
230
- the list (the pixels still show it). Read its `warnings` and pass them on. The file comes back as base64 (`data`).
199
+ `inspect_psd` before `import_psd`, then work with `list_layers`; `export_psd` writes the stack
200
+ in the source frame at the document's bit depth, colour mode and profile. Read
201
+ [references/photoshop-files.md](references/photoshop-files.md) before opening or saving a PSD.
231
202
 
232
203
  ## Best practices
233
204
 
@@ -403,6 +374,12 @@ flaws and `clone_stamp` to copy exactly, and keep strokes short and soft. Read
403
374
  | `get_mixer_brush` | Read-only. The Mixer Brush settings and what it carries (`reservoir` #rrggbb or null, `amount` left 0..1, `pickup` #rrggbb or null). Call it before a mixer stroke with `loadEachStroke: false`. |
404
375
  | `load_mixer_brush` | Fill the Mixer Brush reservoir with `color` (default the foreground), amount 1; keeps the pickup. View state, not an undo step. Use before strokes with `loadEachStroke: false`. Returns the load. |
405
376
  | `clean_mixer_brush` | Empty the Mixer Brush pickup well (`emptyReservoir: true` also drops the loaded paint, so it only smears). View state, not an undo step. Returns the load. |
377
+ | `get_color_profile` | Read-only. The document's profile (`name`, `builtin` id or null, `colorSpace`, `srgb`), the Proof Setup or null, and the built-in profiles. |
378
+ | `assign_profile` | Make the document's numbers mean colours in `profile` (built-in id or `icc:<base64>`; RGB only). The look changes, pixels do not. One undo step. Returns the profile. |
379
+ | `convert_to_profile` | Convert pixel layers into `profile` keeping their look (`intent`, `bpc`), then assign it. One undo step. Returns the profile, `convertedLayerIds` and `unconvertedLayerIds` (shape, type, effects, smart objects). |
380
+ | `set_proof_setup` | Soft-proof the viewport through `profile` (`intent`, `bpc`, `simulatePaper`, `gamutWarning`), or `off: true`. View state. Returns the setup. |
381
+ | `set_color_mode` | Image › Mode: `rgb`, `cmyk` (with the press `profile`, default coated-cmyk, and the separation `intent`) or `lab`. Pixels stay RGB; CMYK documents proof through their profile and `export_psd` writes CMYK / Lab channels. One undo step. Returns the profile summary with `mode`. |
382
+ | `convert_colors` | Read-only. `colors` #rrggbb from the document's profile (or `from`) to `to`: channel `values` 0..1 (CMYK, RGB, grey), `hex` for RGB, and `lab`. |
406
383
  | `clone_stamp` | Copy pixels from the clone source along `points`: each point paints what is at point + offset (source − first point, whole px). `source` `{x, y}` sets the source for this stroke (else the current one, see `set_clone_source`); `aligned` keeps the offset for later strokes; `sample` current \| current-below \| all; `brush` `{size, hardness, spacing, opacity, flow, smoothing, pressureSize, pressureOpacity}`; `layerId`, `channel` (a mask clones from itself). One undo step. Returns the layer, channel, `bounds`, dabs and `clone` `{offset, source, aligned}`. Call `set_clone_source` or pass `source` first. |
407
384
  | `heal_brush` | As `clone_stamp`, then blends the cloned texture into the colour and light around the stroke (Poisson seamless cloning, when the stroke ends). Use for blemishes, wrinkles, dust and seams where the surroundings have the right tone. Returns as `clone_stamp`. |
408
385
  | `spot_heal` | Remove what `points` cover with no source: `type` proximity-match (default: copies the best matching nearby patch, then heals it in) or smooth (fills from the edges). `sample`, `brush`, `layerId`, `channel`. One undo step. Returns the layer, channel, `bounds`, dabs and `spotHeal` `{offset}` (null for a smooth fill). |