@pixerate/editor 0.266.2 → 0.268.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 (149) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.js +1 -1
  3. package/dist/animation-editor/controller.d.cts +8 -0
  4. package/dist/animation-editor/controller.d.ts +8 -0
  5. package/dist/animation-editor/effect-tools.d.cts +1 -1
  6. package/dist/animation-editor/effect-tools.d.ts +1 -1
  7. package/dist/animation-editor/index.cjs +175 -108
  8. package/dist/animation-editor/index.js +94 -27
  9. package/dist/brushes/index.cjs +201 -75
  10. package/dist/brushes/index.d.cts +4 -3
  11. package/dist/brushes/index.d.ts +4 -3
  12. package/dist/brushes/index.js +138 -12
  13. package/dist/brushes/library.d.cts +27 -1
  14. package/dist/brushes/library.d.ts +27 -1
  15. package/dist/brushes/profiles.d.cts +10 -1
  16. package/dist/brushes/profiles.d.ts +10 -1
  17. package/dist/brushes/schema.d.cts +4 -0
  18. package/dist/brushes/schema.d.ts +4 -0
  19. package/dist/brushes/sources.d.cts +114 -0
  20. package/dist/brushes/sources.d.ts +114 -0
  21. package/dist/brushes/types.d.cts +37 -5
  22. package/dist/brushes/types.d.ts +37 -5
  23. package/dist/brushes/ui.d.cts +40 -4
  24. package/dist/brushes/ui.d.ts +40 -4
  25. package/dist/{brushstrokeRender-KD453OBX.js → brushstrokeRender-A727TTNQ.js} +7 -7
  26. package/dist/{brushstrokeRender-FSUWXUZY.cjs → brushstrokeRender-WR2N5HHY.cjs} +23 -23
  27. package/dist/canvas/brushTypes.d.cts +12 -0
  28. package/dist/canvas/brushTypes.d.ts +12 -0
  29. package/dist/canvas/brushstroke.d.cts +1 -1
  30. package/dist/canvas/brushstroke.d.ts +1 -1
  31. package/dist/canvas/drawController.d.cts +19 -4
  32. package/dist/canvas/drawController.d.ts +19 -4
  33. package/dist/canvas/index.cjs +144 -105
  34. package/dist/canvas/index.js +46 -7
  35. package/dist/{chunk-TOID45I2.cjs → chunk-2WOGXEDX.cjs} +1 -1
  36. package/dist/{chunk-XORJ7UZO.cjs → chunk-3VHH6V67.cjs} +13 -12
  37. package/dist/{chunk-3C33YTNN.js → chunk-3XDSNIGF.js} +73 -6
  38. package/dist/chunk-62MFUHSI.cjs +802 -0
  39. package/dist/{chunk-R44Y3EFS.js → chunk-74ODPZLX.js} +17 -345
  40. package/dist/{chunk-GCVFEPJG.js → chunk-ACTHSQOI.js} +182 -0
  41. package/dist/chunk-ADMRNWR7.js +340 -0
  42. package/dist/{chunk-MVUQSK5J.cjs → chunk-B3G7BKUV.cjs} +111 -439
  43. package/dist/{chunk-DXYHQ2VN.js → chunk-BAZMCFVL.js} +1 -1
  44. package/dist/{chunk-VRK322ZC.cjs → chunk-BPJSI4NT.cjs} +2 -2
  45. package/dist/{chunk-RFJZGTHA.cjs → chunk-EU2ZZA6L.cjs} +400 -69
  46. package/dist/{chunk-XC6DPRYI.js → chunk-GSTSZPSR.js} +154 -949
  47. package/dist/{chunk-EZOJBP4D.js → chunk-HFY5LG5J.js} +1 -1
  48. package/dist/chunk-HVO6XCI4.js +802 -0
  49. package/dist/{chunk-YHDHFJUC.cjs → chunk-J5VQYK7T.cjs} +217 -1012
  50. package/dist/{chunk-XRXFVRGY.cjs → chunk-JBB3GDBB.cjs} +2483 -208
  51. package/dist/{chunk-ACFSFUQH.js → chunk-JL6YH5WD.js} +4 -0
  52. package/dist/{chunk-CIJBSUM5.js → chunk-KZI3YWKW.js} +353 -22
  53. package/dist/{chunk-4ODRXQRW.js → chunk-LETF75CA.js} +1 -1
  54. package/dist/{chunk-FQPUF2W2.cjs → chunk-NIBFKXMD.cjs} +217 -35
  55. package/dist/{chunk-AQZQWXEU.cjs → chunk-NPWWSCWT.cjs} +35 -35
  56. package/dist/{chunk-DOZ6F2K2.js → chunk-NSETJI74.js} +3 -2
  57. package/dist/{chunk-PE6YJMQT.cjs → chunk-PFHJS4HE.cjs} +20 -19
  58. package/dist/{chunk-7KNW3WXZ.cjs → chunk-QEVLRLUN.cjs} +63 -0
  59. package/dist/{chunk-LK4SXL3V.js → chunk-QNY7BDFP.js} +1 -1
  60. package/dist/{chunk-OTN6VB5S.cjs → chunk-RDFHWAHL.cjs} +5 -1
  61. package/dist/{chunk-PXDIAYB6.cjs → chunk-STLTOISW.cjs} +34 -214
  62. package/dist/{chunk-T2LDTEXK.js → chunk-UVTO3D4E.js} +2333 -58
  63. package/dist/{chunk-6JL2D5SG.js → chunk-VE6UKYQ2.js} +3 -2
  64. package/dist/{chunk-TXFWZ5ZJ.js → chunk-W6JDUXP3.js} +4 -184
  65. package/dist/{chunk-3SBLVGOM.cjs → chunk-YOVXQHA3.cjs} +23 -23
  66. package/dist/{chunk-WLUVZKVD.cjs → chunk-YXP6X3SY.cjs} +103 -36
  67. package/dist/chunk-YZWM6ZIP.cjs +340 -0
  68. package/dist/{chunk-472INMWH.js → chunk-ZA7L5YJB.js} +63 -0
  69. package/dist/commands/builtins.d.cts +12 -1
  70. package/dist/commands/builtins.d.ts +12 -1
  71. package/dist/commands/index.cjs +2 -2
  72. package/dist/commands/index.js +1 -1
  73. package/dist/image-editor/controller.d.cts +1 -1
  74. package/dist/image-editor/controller.d.ts +1 -1
  75. package/dist/image-editor/index.cjs +868 -864
  76. package/dist/image-editor/index.js +123 -119
  77. package/dist/paint/index.cjs +8 -6
  78. package/dist/paint/index.js +56 -54
  79. package/dist/paint-editor/brush-library-tools.d.cts +12 -2
  80. package/dist/paint-editor/brush-library-tools.d.ts +12 -2
  81. package/dist/paint-editor/brush-model.d.cts +29 -1
  82. package/dist/paint-editor/brush-model.d.ts +29 -1
  83. package/dist/paint-editor/brush-source-art.d.cts +32 -0
  84. package/dist/paint-editor/brush-source-art.d.ts +32 -0
  85. package/dist/paint-editor/controller.d.cts +11 -4
  86. package/dist/paint-editor/controller.d.ts +11 -4
  87. package/dist/paint-editor/dab-stroke.d.cts +2 -0
  88. package/dist/paint-editor/dab-stroke.d.ts +2 -0
  89. package/dist/paint-editor/engine.d.cts +11 -2
  90. package/dist/paint-editor/engine.d.ts +11 -2
  91. package/dist/paint-editor/index.cjs +967 -2335
  92. package/dist/paint-editor/index.d.cts +3 -1
  93. package/dist/paint-editor/index.d.ts +3 -1
  94. package/dist/paint-editor/index.js +394 -1762
  95. package/dist/paint-editor/path-samples.d.cts +2 -0
  96. package/dist/paint-editor/path-samples.d.ts +2 -0
  97. package/dist/paint-editor/presence.d.cts +20 -1
  98. package/dist/paint-editor/presence.d.ts +20 -1
  99. package/dist/paint-editor/stamp-tip.d.cts +8 -5
  100. package/dist/paint-editor/stamp-tip.d.ts +8 -5
  101. package/dist/paint-editor/thumbnails.d.cts +6 -0
  102. package/dist/paint-editor/thumbnails.d.ts +6 -0
  103. package/dist/paint-editor/tip-grain.d.cts +34 -0
  104. package/dist/paint-editor/tip-grain.d.ts +34 -0
  105. package/dist/raster/index.cjs +2 -2
  106. package/dist/raster/index.js +9 -9
  107. package/dist/stickers/helpers.d.cts +2 -0
  108. package/dist/stickers/helpers.d.ts +2 -0
  109. package/dist/stickers/index.cjs +41 -35
  110. package/dist/stickers/index.js +22 -16
  111. package/dist/stickers/types.d.cts +4 -2
  112. package/dist/stickers/types.d.ts +4 -2
  113. package/dist/vector/backdrop.d.cts +13 -0
  114. package/dist/vector/backdrop.d.ts +13 -0
  115. package/dist/vector/brushstroke.d.cts +54 -7
  116. package/dist/vector/brushstroke.d.ts +54 -7
  117. package/dist/vector/brushstrokeMedia.d.cts +96 -0
  118. package/dist/vector/brushstrokeMedia.d.ts +96 -0
  119. package/dist/vector/controller.d.cts +17 -3
  120. package/dist/vector/controller.d.ts +17 -3
  121. package/dist/vector/index.cjs +1850 -1390
  122. package/dist/vector/index.d.cts +2 -0
  123. package/dist/vector/index.d.ts +2 -0
  124. package/dist/vector/index.js +520 -60
  125. package/dist/vector/render.d.cts +6 -0
  126. package/dist/vector/render.d.ts +6 -0
  127. package/dist/vector/types.d.cts +14 -0
  128. package/dist/vector/types.d.ts +14 -0
  129. package/package.json +1 -1
  130. package/skills/pixerate-animation-editor/SKILL.md +1 -0
  131. package/skills/pixerate-brushes/SKILL.md +37 -10
  132. package/skills/pixerate-brushes-design/SKILL.md +15 -5
  133. package/skills/pixerate-brushes-design/evals/port-to-vector.json +8 -8
  134. package/skills/pixerate-canvas/SKILL.md +11 -1
  135. package/skills/pixerate-image-editor/SKILL.md +19 -64
  136. package/skills/pixerate-image-editor/references/color-management.md +2 -1
  137. package/skills/pixerate-image-editor/references/free-transform.md +12 -0
  138. package/skills/pixerate-image-editor/references/layer-effects.md +10 -0
  139. package/skills/pixerate-image-editor/references/painting.md +17 -0
  140. package/skills/pixerate-image-editor/references/photoshop-files.md +1 -1
  141. package/skills/pixerate-image-editor/references/shapes-and-type.md +14 -0
  142. package/skills/pixerate-image-editor/references/smart-objects.md +10 -0
  143. package/skills/pixerate-paint-editor/SKILL.md +14 -4
  144. package/skills/pixerate-paint-editor/references/brush-facets.md +3 -1
  145. package/skills/pixerate-paint-editor/references/wet-media.md +9 -0
  146. package/skills/pixerate-paint-sketch-to-finish/SKILL.md +1 -0
  147. package/skills/pixerate-vector/SKILL.md +16 -6
  148. package/skills/pixerate-vector/references/best-practices.md +1 -0
  149. package/skills/pixerate-vector/references/commands.md +19 -0
@@ -0,0 +1,802 @@
1
+ import {
2
+ bestOffset,
3
+ contentAwareFill,
4
+ dilateMask,
5
+ maskBounds,
6
+ membraneFill,
7
+ seamlessClone
8
+ } from "./chunk-VWEWY4FY.js";
9
+ import {
10
+ DabWalker,
11
+ SmudgeTip,
12
+ TileMedium,
13
+ dabRect,
14
+ put255,
15
+ rasterizeDab
16
+ } from "./chunk-ACTHSQOI.js";
17
+ import {
18
+ blendPixel,
19
+ createPigment,
20
+ mixPigments,
21
+ srgbDecode,
22
+ srgbEncode
23
+ } from "./chunk-IRSTHLQQ.js";
24
+ import {
25
+ TILE,
26
+ tileKey
27
+ } from "./chunk-HVVR5PZW.js";
28
+
29
+ // src/paint/pigment-tools.ts
30
+ var HEX = /^#?([0-9a-f]{6})$/i;
31
+ function parseHex(value, index) {
32
+ const m = typeof value === "string" ? HEX.exec(value.trim()) : null;
33
+ if (!m) throw new Error(`colors[${index}].color is ${JSON.stringify(value)}; use #rrggbb, e.g. "#1e40af".`);
34
+ const n = parseInt(m[1], 16);
35
+ return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
36
+ }
37
+ function toHex(rgb) {
38
+ return "#" + rgb.map((v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0")).join("");
39
+ }
40
+ function createPigmentTools() {
41
+ const mixColors = {
42
+ name: "mix_colors",
43
+ title: "Mix colours like paint",
44
+ description: "Mixes colours as paint would (model pigment, the default: Kubelka-Munk mixing over reflectance spectra, so blue and yellow make green and white lightens a colour into a tint instead of greying it), or as light on screen (model rgb: the weighted RGB average). Changes nothing. Returns mixed (#rrggbb, by the chosen model), the other model's result for comparison (rgbLerp and pigment), the normalised weights and the model.",
45
+ inputSchema: {
46
+ type: "object",
47
+ properties: {
48
+ colors: {
49
+ type: "array",
50
+ minItems: 1,
51
+ maxItems: 16,
52
+ description: "The paints to mix (1 to 16).",
53
+ items: {
54
+ type: "object",
55
+ properties: {
56
+ color: { type: "string", description: "Paint colour #rrggbb." },
57
+ weight: { type: "number", minimum: 0, description: "Parts of this paint, any scale (e.g. 1 and 3 for a 1:3 mix). At least one weight must be above 0." },
58
+ strength: {
59
+ type: "number",
60
+ exclusiveMinimum: 0,
61
+ maximum: 100,
62
+ description: "Tinting strength (default 1): each part counts as this many, so a strong dark paint (e.g. 4) tints a lot of white."
63
+ }
64
+ },
65
+ required: ["color", "weight"],
66
+ additionalProperties: false
67
+ }
68
+ },
69
+ model: {
70
+ type: "string",
71
+ enum: ["pigment", "rgb"],
72
+ description: "pigment (default): mix like paint (Kubelka-Munk), as the pigment blend mode, Mixer Brush and mixing pad do; rgb: average the RGB values, as normal blending and rgb mixing brushes do (strength is ignored)."
73
+ }
74
+ },
75
+ required: ["colors"],
76
+ additionalProperties: false
77
+ },
78
+ annotations: { title: "Mix colours like paint", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
79
+ handler: (args) => {
80
+ const items = args.colors;
81
+ const rgbs = items.map((item, i) => parseHex(item.color, i));
82
+ const total = items.reduce((sum, item) => sum + item.weight, 0);
83
+ if (!(total > 0)) throw new Error("Every weight is 0; give at least one colour a weight above 0.");
84
+ const pigments = rgbs.map((rgb2, i) => createPigment(rgb2, { strength: items[i].strength }));
85
+ const mixed = mixPigments(pigments, items.map((item) => item.weight));
86
+ const lerp = [0, 1, 2].map((c) => rgbs.reduce((sum, rgb2, i) => sum + rgb2[c] * items[i].weight, 0) / total);
87
+ const rgb = args.model === "rgb";
88
+ return {
89
+ mixed: toHex(rgb ? lerp : mixed),
90
+ rgbLerp: toHex(lerp),
91
+ pigment: toHex(mixed),
92
+ weights: items.map((item) => item.weight / total),
93
+ model: rgb ? "rgb" : "kubelka-munk"
94
+ };
95
+ }
96
+ };
97
+ return [mixColors];
98
+ }
99
+
100
+ // src/paint/retouch.ts
101
+ var TONE_RANGES = ["shadows", "midtones", "highlights"];
102
+ function luma(r, g, b) {
103
+ return 0.299 * r + 0.587 * g + 0.114 * b;
104
+ }
105
+ function dodgeBurnCurve(v, e, range, burn) {
106
+ const x = Math.max(0, v);
107
+ const k = Math.min(1, Math.max(0, e));
108
+ const c = Math.min(1, x);
109
+ if (range === "midtones") {
110
+ const w2 = Math.min(1, Math.max(0, 4 * x * (1 - x)));
111
+ return x + w2 * (Math.pow(c, burn ? 1 + k : 1 / (1 + k)) - x);
112
+ }
113
+ const w = range === "shadows" ? (1 - c) * (1 - c) : c * c;
114
+ return burn ? x - k * w * x : x + k * w * (1 - c);
115
+ }
116
+ function compressGamut(c, l) {
117
+ const L = Math.min(1, Math.max(0, l));
118
+ const lo = Math.min(c[0], c[1], c[2]);
119
+ const hi = Math.max(c[0], c[1], c[2]);
120
+ let t = 1;
121
+ if (hi > 1 && hi - L > 1e-9) t = Math.min(t, (1 - L) / (hi - L));
122
+ if (lo < 0 && L - lo > 1e-9) t = Math.min(t, L / (L - lo));
123
+ for (let i = 0; i < 3; i++) c[i] = L + (c[i] - L) * t;
124
+ }
125
+ function dodgeBurn(c, e, range, burn, protectTones) {
126
+ if (!(e > 0)) return c;
127
+ if (protectTones) {
128
+ const l = luma(c[0], c[1], c[2]);
129
+ const nl = dodgeBurnCurve(l, e, range, burn);
130
+ if (l > 1e-6) for (let i = 0; i < 3; i++) c[i] *= nl / l;
131
+ else c[0] = c[1] = c[2] = nl;
132
+ compressGamut(c, nl);
133
+ } else for (let i = 0; i < 3; i++) c[i] = dodgeBurnCurve(c[i], e, range, burn);
134
+ return c;
135
+ }
136
+ function saturationOf(r, g, b) {
137
+ const lo = Math.min(r, g, b);
138
+ const hi = Math.max(r, g, b);
139
+ return hi <= 1e-6 ? 0 : Math.min(1, Math.max(0, (hi - lo) / hi));
140
+ }
141
+ function sponge(c, amount, saturate, vibrance) {
142
+ let a = Math.min(1, Math.max(0, amount));
143
+ if (!(a > 0)) return c;
144
+ if (vibrance) {
145
+ const s = saturationOf(c[0], c[1], c[2]);
146
+ a *= saturate ? 1 - s : 0.25 + 0.75 * s;
147
+ }
148
+ const l = luma(c[0], c[1], c[2]);
149
+ const k = saturate ? 1 + a : 1 - a;
150
+ for (let i = 0; i < 3; i++) c[i] = l + (c[i] - l) * k;
151
+ compressGamut(c, l);
152
+ return c;
153
+ }
154
+ function perDabStrength(e, spacing) {
155
+ return 1 - Math.pow(1 - Math.min(1, Math.max(0, e)), Math.min(1, Math.max(0.01, spacing)));
156
+ }
157
+ var RETOUCH_SAMPLES = ["current", "current-below", "all"];
158
+ var SPOT_HEAL_TYPES = ["proximity-match", "content-aware", "smooth"];
159
+ var DEFAULT_RETOUCH = {
160
+ aligned: true,
161
+ sample: "current",
162
+ spotType: "proximity-match",
163
+ range: "midtones",
164
+ exposure: 0.5,
165
+ protectTones: true,
166
+ spongeMode: "desaturate",
167
+ vibrance: true,
168
+ strength: 0.5,
169
+ historySteps: 0
170
+ };
171
+ function normalizeRetouch(raw = {}, base = DEFAULT_RETOUCH) {
172
+ const unit = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
173
+ const bool = (v, d) => typeof v === "boolean" ? v : d;
174
+ const pick = (v, all, d) => all.includes(v) ? v : d;
175
+ return {
176
+ aligned: bool(raw.aligned, base.aligned),
177
+ sample: pick(raw.sample, RETOUCH_SAMPLES, base.sample),
178
+ spotType: pick(raw.spotType, SPOT_HEAL_TYPES, base.spotType),
179
+ range: pick(raw.range, TONE_RANGES, base.range),
180
+ exposure: unit(raw.exposure, base.exposure),
181
+ protectTones: bool(raw.protectTones, base.protectTones),
182
+ spongeMode: pick(raw.spongeMode, ["desaturate", "saturate"], base.spongeMode),
183
+ vibrance: bool(raw.vibrance, base.vibrance),
184
+ strength: unit(raw.strength, base.strength),
185
+ historySteps: typeof raw.historySteps === "number" && Number.isFinite(raw.historySteps) ? Math.min(1e3, Math.max(0, Math.round(raw.historySteps))) : base.historySteps
186
+ };
187
+ }
188
+ function boxBlurPixels(data, w, h, n, r) {
189
+ if (r < 1) return;
190
+ if (n === 4) for (let i = 0; i < data.length; i += 4) for (let c = 0; c < 3; c++) data[i + c] *= data[i + 3] / 255;
191
+ const tmp = new Float32Array(data.length);
192
+ const k = 1 / (2 * r + 1);
193
+ for (let y = 0; y < h; y++) {
194
+ for (let x = 0; x < w; x++) {
195
+ for (let c = 0; c < n; c++) {
196
+ let s = 0;
197
+ for (let d = -r; d <= r; d++) s += data[(y * w + Math.min(w - 1, Math.max(0, x + d))) * n + c];
198
+ tmp[(y * w + x) * n + c] = s * k;
199
+ }
200
+ }
201
+ }
202
+ for (let y = 0; y < h; y++) {
203
+ for (let x = 0; x < w; x++) {
204
+ for (let c = 0; c < n; c++) {
205
+ let s = 0;
206
+ for (let d = -r; d <= r; d++) s += tmp[(Math.min(h - 1, Math.max(0, y + d)) * w + x) * n + c];
207
+ data[(y * w + x) * n + c] = s * k;
208
+ }
209
+ }
210
+ }
211
+ if (n === 4) {
212
+ for (let i = 0; i < data.length; i += 4) {
213
+ const a = data[i + 3] / 255;
214
+ for (let c = 0; c < 3; c++) data[i + c] = a > 0 ? data[i + c] / a : 0;
215
+ }
216
+ }
217
+ }
218
+ function retouchFilter(data, w, h, n, kind, radius = 2, amount = 1) {
219
+ if (kind === "blur") {
220
+ boxBlurPixels(data, w, h, n, radius);
221
+ boxBlurPixels(data, w, h, n, radius);
222
+ return;
223
+ }
224
+ const soft = data.slice();
225
+ boxBlurPixels(soft, w, h, n, 1);
226
+ for (let i = 0; i < data.length; i++) {
227
+ if (n === 4 && (i & 3) === 3) continue;
228
+ const v = data[i] + amount * (data[i] - soft[i]);
229
+ data[i] = v > 0 ? v : 0;
230
+ }
231
+ }
232
+ var IDENTITY_CLONE_TRANSFORM = { scaleX: 1, scaleY: 1, rotate: 0 };
233
+ function isIdentityCloneTransform(t) {
234
+ return t.scaleX === 1 && t.scaleY === 1 && (t.rotate % 360 + 360) % 360 === 0;
235
+ }
236
+ function cloneSamplePoint(t, source, anchor, x, y) {
237
+ const a = -t.rotate * Math.PI / 180;
238
+ const c = Math.cos(a);
239
+ const s = Math.sin(a);
240
+ const dx = x - anchor.x;
241
+ const dy = y - anchor.y;
242
+ return [source.x + (c * dx - s * dy) / t.scaleX, source.y + (s * dx + c * dy) / t.scaleY];
243
+ }
244
+ var TransformedPixelSource = class {
245
+ constructor(inner, map) {
246
+ this.inner = inner;
247
+ this.map = map;
248
+ }
249
+ inner;
250
+ map;
251
+ px = [0, 0, 0, 0];
252
+ sample(x, y, out) {
253
+ const [u, v] = this.map(x, y);
254
+ const x0 = Math.floor(u);
255
+ const y0 = Math.floor(v);
256
+ const fx = u - x0;
257
+ const fy = v - y0;
258
+ let r = 0;
259
+ let g = 0;
260
+ let b = 0;
261
+ let a = 0;
262
+ for (let k = 0; k < 4; k++) {
263
+ const w = (k & 1 ? fx : 1 - fx) * (k & 2 ? fy : 1 - fy);
264
+ if (w <= 0) continue;
265
+ if (!this.inner.sample(x0 + (k & 1), y0 + (k >> 1), this.px)) continue;
266
+ const pa = this.px[3] * w;
267
+ r += this.px[0] * pa;
268
+ g += this.px[1] * pa;
269
+ b += this.px[2] * pa;
270
+ a += pa;
271
+ }
272
+ if (!(a > 0)) {
273
+ out[0] = out[1] = out[2] = out[3] = 0;
274
+ return false;
275
+ }
276
+ out[0] = r / a;
277
+ out[1] = g / a;
278
+ out[2] = b / a;
279
+ out[3] = a;
280
+ return true;
281
+ }
282
+ };
283
+ var TilePixelSource = class {
284
+ constructor(readTile, channels, offset = { x: 0, y: 0 }) {
285
+ this.readTile = readTile;
286
+ this.channels = channels;
287
+ this.offset = offset;
288
+ }
289
+ readTile;
290
+ channels;
291
+ offset;
292
+ cache = /* @__PURE__ */ new Map();
293
+ lastKey = NaN;
294
+ last = null;
295
+ sample(x, y, out) {
296
+ const lx = x - this.offset.x;
297
+ const ly = y - this.offset.y;
298
+ const tx = Math.floor(lx / TILE);
299
+ const ty = Math.floor(ly / TILE);
300
+ const key = tileKey(tx, ty);
301
+ if (key !== this.lastKey) {
302
+ let t2 = this.cache.get(key);
303
+ if (t2 === void 0) {
304
+ t2 = this.readTile(tx, ty);
305
+ this.cache.set(key, t2);
306
+ }
307
+ this.last = t2;
308
+ this.lastKey = key;
309
+ }
310
+ const t = this.last;
311
+ if (!t) {
312
+ out[0] = out[1] = out[2] = out[3] = 0;
313
+ return false;
314
+ }
315
+ const p = (ly - ty * TILE) * TILE + (lx - tx * TILE);
316
+ if (this.channels === 1) {
317
+ out[0] = out[1] = out[2] = t[p];
318
+ out[3] = 255;
319
+ return true;
320
+ }
321
+ const i = p * 4;
322
+ out[0] = t[i];
323
+ out[1] = t[i + 1];
324
+ out[2] = t[i + 2];
325
+ out[3] = t[i + 3];
326
+ return out[3] > 0;
327
+ }
328
+ };
329
+ var RegionPixelSource = class {
330
+ constructor(rect, data) {
331
+ this.rect = rect;
332
+ this.data = data;
333
+ }
334
+ rect;
335
+ data;
336
+ sample(x, y, out) {
337
+ const r = this.rect;
338
+ const lx = x - r.x;
339
+ const ly = y - r.y;
340
+ if (lx < 0 || ly < 0 || lx >= r.width || ly >= r.height) {
341
+ out[0] = out[1] = out[2] = out[3] = 0;
342
+ return false;
343
+ }
344
+ const i = (ly * r.width + lx) * 4;
345
+ out[0] = this.data[i];
346
+ out[1] = this.data[i + 1];
347
+ out[2] = this.data[i + 2];
348
+ out[3] = this.data[i + 3];
349
+ return out[3] > 0;
350
+ }
351
+ };
352
+ function readSourceRegion(source, rect, dx = 0, dy = 0) {
353
+ const out = new Float32Array(rect.width * rect.height * 4);
354
+ const px = [0, 0, 0, 0];
355
+ for (let y = 0; y < rect.height; y++) {
356
+ for (let x = 0; x < rect.width; x++) {
357
+ source.sample(rect.x + x + dx, rect.y + y + dy, px);
358
+ const i = (y * rect.width + x) * 4;
359
+ out[i] = px[0];
360
+ out[i + 1] = px[1];
361
+ out[i + 2] = px[2];
362
+ out[i + 3] = px[3];
363
+ }
364
+ }
365
+ return out;
366
+ }
367
+ function applyRetouchToTile(coverage, tileData, channels, tx, ty, paint, s, options = {}) {
368
+ const out = (options.into ?? tileData).slice();
369
+ const ox = tx * TILE + s.offset.x;
370
+ const oy = ty * TILE + s.offset.y;
371
+ let x0 = 0;
372
+ let y0 = 0;
373
+ let x1 = TILE;
374
+ let y1 = TILE;
375
+ if (options.region) {
376
+ const r = options.region;
377
+ x0 = Math.max(0, Math.floor(r.x) - ox);
378
+ y0 = Math.max(0, Math.floor(r.y) - oy);
379
+ x1 = Math.min(TILE, Math.ceil(r.x + r.width) - ox);
380
+ y1 = Math.min(TILE, Math.ceil(r.y + r.height) - oy);
381
+ if (x1 <= x0 || y1 <= y0) return out;
382
+ if (options.into) {
383
+ for (let y = y0; y < y1; y++) out.set(tileData.subarray((y * TILE + x0) * channels, (y * TILE + x1) * channels), (y * TILE + x0) * channels);
384
+ }
385
+ }
386
+ const sel = s.selection;
387
+ const blend = s.blend ?? "normal";
388
+ const src = [0, 0, 0, 0];
389
+ const rgb = [0, 0, 0];
390
+ const cb = [0, 0, 0, 0];
391
+ const cs = [0, 0, 0, 0];
392
+ let covKey = NaN;
393
+ let covTile;
394
+ for (let y = y0; y < y1; y++) {
395
+ const dy = oy + y;
396
+ const cty = Math.floor(dy / TILE);
397
+ for (let x = x0; x < x1; x++) {
398
+ const dx = ox + x;
399
+ const ctx = Math.floor(dx / TILE);
400
+ const key = tileKey(ctx, cty);
401
+ if (key !== covKey) {
402
+ covKey = key;
403
+ covTile = coverage.tiles.get(key);
404
+ }
405
+ if (!covTile) continue;
406
+ const c = covTile[(dy - cty * TILE) * TILE + (dx - ctx * TILE)];
407
+ if (c <= 0) continue;
408
+ let k = c * s.opacity;
409
+ if (sel) {
410
+ if (dx < 0 || dy < 0 || dx >= sel.width || dy >= sel.height) continue;
411
+ k *= sel.data[dy * sel.width + dx] / 255;
412
+ }
413
+ if (k > 1) k = 1;
414
+ if (k <= 0) continue;
415
+ const p = y * TILE + x;
416
+ if (channels === 1) {
417
+ const g = out[p];
418
+ let t;
419
+ if (paint.kind === "tone") {
420
+ rgb[0] = rgb[1] = rgb[2] = g / 255;
421
+ paint.tone(rgb);
422
+ t = luma(rgb[0], rgb[1], rgb[2]) * 255;
423
+ } else if (paint.kind === "color") t = paint.color[0];
424
+ else {
425
+ if (!paint.source.sample(dx + paint.dx, dy + paint.dy, src)) continue;
426
+ t = src[0];
427
+ k *= src[3] / 255;
428
+ }
429
+ out[p] = Math.round(g + (t - g) * k);
430
+ continue;
431
+ }
432
+ const i = p * 4;
433
+ const a = out[i + 3];
434
+ if (paint.kind === "tone") {
435
+ if (a === 0) continue;
436
+ rgb[0] = out[i] / 255;
437
+ rgb[1] = out[i + 1] / 255;
438
+ rgb[2] = out[i + 2] / 255;
439
+ const r0 = rgb[0];
440
+ const g0 = rgb[1];
441
+ const b0 = rgb[2];
442
+ paint.tone(rgb);
443
+ put255(out, i, r0 + (rgb[0] - r0) * k);
444
+ put255(out, i + 1, g0 + (rgb[1] - g0) * k);
445
+ put255(out, i + 2, b0 + (rgb[2] - b0) * k);
446
+ continue;
447
+ }
448
+ if (s.lockTransparency && a === 0) continue;
449
+ if (paint.kind === "color") {
450
+ src[0] = paint.color[0];
451
+ src[1] = paint.color[1];
452
+ src[2] = paint.color[2];
453
+ src[3] = paint.color[3];
454
+ } else if (!paint.source.sample(dx + paint.dx, dy + paint.dy, src)) continue;
455
+ for (let ch = 0; ch < 4; ch++) {
456
+ cb[ch] = out[i + ch] / 255;
457
+ cs[ch] = src[ch] / 255;
458
+ }
459
+ const o = blendPixel(blend, cb, cs, k);
460
+ put255(out, i, o[0]);
461
+ put255(out, i + 1, o[1]);
462
+ put255(out, i + 2, o[2]);
463
+ if (!s.lockTransparency) put255(out, i + 3, o[3]);
464
+ }
465
+ }
466
+ return out;
467
+ }
468
+ function applyToneToDeepTile(coverage, samples, tx, ty, tone, s, options = {}) {
469
+ const linear = samples instanceof Float32Array;
470
+ const out = (options.into ?? samples).slice();
471
+ const ox = tx * TILE + s.offset.x;
472
+ const oy = ty * TILE + s.offset.y;
473
+ let x0 = 0;
474
+ let y0 = 0;
475
+ let x1 = TILE;
476
+ let y1 = TILE;
477
+ if (options.region) {
478
+ const r = options.region;
479
+ x0 = Math.max(0, Math.floor(r.x) - ox);
480
+ y0 = Math.max(0, Math.floor(r.y) - oy);
481
+ x1 = Math.min(TILE, Math.ceil(r.x + r.width) - ox);
482
+ y1 = Math.min(TILE, Math.ceil(r.y + r.height) - oy);
483
+ if (x1 <= x0 || y1 <= y0) return out;
484
+ if (options.into) for (let y = y0; y < y1; y++) out.set(samples.subarray((y * TILE + x0) * 4, (y * TILE + x1) * 4), (y * TILE + x0) * 4);
485
+ }
486
+ const sel = s.selection;
487
+ const rgb = [0, 0, 0];
488
+ const before = [0, 0, 0];
489
+ const excess = [0, 0, 0];
490
+ let covKey = NaN;
491
+ let covTile;
492
+ for (let y = y0; y < y1; y++) {
493
+ const dy = oy + y;
494
+ const cty = Math.floor(dy / TILE);
495
+ for (let x = x0; x < x1; x++) {
496
+ const dx = ox + x;
497
+ const ctx = Math.floor(dx / TILE);
498
+ const key = tileKey(ctx, cty);
499
+ if (key !== covKey) {
500
+ covKey = key;
501
+ covTile = coverage.tiles.get(key);
502
+ }
503
+ if (!covTile) continue;
504
+ const c = covTile[(dy - cty * TILE) * TILE + (dx - ctx * TILE)];
505
+ if (c <= 0) continue;
506
+ let k = c * s.opacity;
507
+ if (sel) {
508
+ if (dx < 0 || dy < 0 || dx >= sel.width || dy >= sel.height) continue;
509
+ k *= sel.data[dy * sel.width + dx] / 255;
510
+ }
511
+ if (k > 1) k = 1;
512
+ if (k <= 0) continue;
513
+ const i = (y * TILE + x) * 4;
514
+ if (out[i + 3] === 0) continue;
515
+ for (let ch = 0; ch < 3; ch++) {
516
+ const v = out[i + ch];
517
+ if (linear) {
518
+ excess[ch] = v > 1 ? v - 1 : 0;
519
+ rgb[ch] = srgbEncode(v > 1 ? 1 : v < 0 ? 0 : v);
520
+ } else rgb[ch] = v / 65535;
521
+ before[ch] = rgb[ch];
522
+ }
523
+ tone(rgb);
524
+ for (let ch = 0; ch < 3; ch++) {
525
+ const e = before[ch] + (rgb[ch] - before[ch]) * k;
526
+ if (linear) out[i + ch] = srgbDecode(e) + excess[ch];
527
+ else out[i + ch] = e <= 0 ? 0 : e >= 1 ? 65535 : Math.round(e * 65535);
528
+ }
529
+ }
530
+ }
531
+ return out;
532
+ }
533
+ function toneOf(tool, settings) {
534
+ const burn = tool === "burn";
535
+ return (rgb) => {
536
+ dodgeBurn(rgb, settings.exposure, settings.range, burn, settings.protectTones);
537
+ };
538
+ }
539
+ function healPixels(w, h, src, dst, mask) {
540
+ const out = new Float32Array(dst);
541
+ const bb = maskBounds(w, h, mask);
542
+ if (!bb) return out;
543
+ const x0 = Math.max(0, bb[0] - 1);
544
+ const y0 = Math.max(0, bb[1] - 1);
545
+ const x1 = Math.min(w, bb[2] + 1);
546
+ const y1 = Math.min(h, bb[3] + 1);
547
+ const rw = x1 - x0;
548
+ const rh = y1 - y0;
549
+ const s = new Float32Array(rw * rh * 4);
550
+ const d = new Float32Array(rw * rh * 4);
551
+ const m = new Uint8Array(rw * rh);
552
+ for (let y = 0; y < rh; y++) {
553
+ for (let x = 0; x < rw; x++) {
554
+ const gi = (y + y0) * w + x + x0;
555
+ const li = y * rw + x;
556
+ m[li] = mask[gi] ? 1 : 0;
557
+ for (let c = 0; c < 4; c++) {
558
+ s[li * 4 + c] = src[gi * 4 + c];
559
+ d[li * 4 + c] = dst[gi * 4 + c];
560
+ }
561
+ }
562
+ }
563
+ const healed = seamlessClone(rw, rh, 4, s, d, m);
564
+ for (let y = 0; y < rh; y++) {
565
+ for (let x = 0; x < rw; x++) {
566
+ const gi = ((y + y0) * w + x + x0) * 4;
567
+ const li = (y * rw + x) * 4;
568
+ for (let c = 0; c < 4; c++) out[gi + c] = Math.min(255, Math.max(0, healed[li + c]));
569
+ }
570
+ }
571
+ return out;
572
+ }
573
+ function spotHealPixels(w, h, img, hole, size, type, onOffset) {
574
+ const domain = dilateMask(w, h, hole, 2);
575
+ const healMask = dilateMask(w, h, hole, 1);
576
+ let filled = null;
577
+ let bestFound = null;
578
+ if (type === "proximity-match") {
579
+ const ring = Math.min(16, Math.max(3, Math.floor(size / 8)));
580
+ const off = bestOffset(w, h, 4, img, domain, ring, Math.floor(Math.max(w, h) / 2));
581
+ if (off) {
582
+ bestFound = off;
583
+ filled = new Float32Array(img);
584
+ const [ddx, ddy] = off;
585
+ for (let i = 0; i < w * h; i++) {
586
+ if (!domain[i]) continue;
587
+ const x = i % w + ddx;
588
+ const y = Math.floor(i / w) + ddy;
589
+ const j = (y * w + x) * 4;
590
+ for (let c = 0; c < 4; c++) filled[i * 4 + c] = img[j + c];
591
+ }
592
+ }
593
+ }
594
+ onOffset?.(filled ? bestFound : null);
595
+ if (!filled && type === "content-aware") filled = contentAwareFill(w, h, 4, img, domain);
596
+ if (!filled) filled = membraneFill(w, h, 4, img, domain);
597
+ return healPixels(w, h, filled, img, healMask);
598
+ }
599
+ function spotHealWindow(bounds, canvas) {
600
+ const g = Math.max(8, Math.ceil(Math.max(bounds.width, bounds.height)));
601
+ const x0 = Math.max(0, bounds.x - g);
602
+ const y0 = Math.max(0, bounds.y - g);
603
+ const x1 = Math.min(canvas.width, bounds.x + bounds.width + g);
604
+ const y1 = Math.min(canvas.height, bounds.y + bounds.height + g);
605
+ return { x: x0, y: y0, width: Math.max(0, x1 - x0), height: Math.max(0, y1 - y0) };
606
+ }
607
+ var RetouchDabStroke = class {
608
+ constructor(brush, target, effect) {
609
+ this.brush = brush;
610
+ this.effect = effect;
611
+ this.walker = new DabWalker(brush);
612
+ this.medium = new TileMedium(target);
613
+ }
614
+ brush;
615
+ effect;
616
+ dabCount = 0;
617
+ bounds = null;
618
+ walker;
619
+ medium;
620
+ smoothPos = null;
621
+ buf = new Float32Array(0);
622
+ push(points) {
623
+ const dabs = [];
624
+ for (const p of points) this.walker.push(this.smooth(p), dabs);
625
+ return this.paint(dabs);
626
+ }
627
+ finish(last) {
628
+ const dabs = [];
629
+ if (last && this.smoothPos && (this.smoothPos.x !== last.x || this.smoothPos.y !== last.y)) this.walker.push(last, dabs);
630
+ this.walker.finish(dabs);
631
+ return this.paint(dabs);
632
+ }
633
+ smooth(p) {
634
+ const a = 1 - Math.min(0.95, Math.max(0, this.brush.smoothing));
635
+ const cur = this.smoothPos;
636
+ const n = cur && a < 1 ? { ...p, x: cur.x + (p.x - cur.x) * a, y: cur.y + (p.y - cur.y) * a } : p;
637
+ this.smoothPos = n;
638
+ return n;
639
+ }
640
+ paint(dabs) {
641
+ const rects = [];
642
+ for (const d of dabs) {
643
+ const rect = dabRect(d, this.brush.hardness);
644
+ const n = rect.width * rect.height;
645
+ if (this.buf.length < n) this.buf = new Float32Array(n);
646
+ const cov = this.buf.subarray(0, n);
647
+ rasterizeDab({ ...d, alpha: 1 }, this.brush.hardness, rect, cov);
648
+ this.medium.reset();
649
+ this.effect(this.medium, d, rect, cov);
650
+ rects.push(rect);
651
+ this.bounds = unionRect(this.bounds, rect);
652
+ this.dabCount++;
653
+ }
654
+ return rects;
655
+ }
656
+ };
657
+ function smudgeEffect(brush, strength, mixing = "rgb") {
658
+ const s = Math.min(1, Math.max(0, strength));
659
+ const tip = new SmudgeTip({ strength: s, length: s, mixing }, brush.size);
660
+ return (medium, d, rect, cov) => tip.dab(medium, { x: d.x, y: d.y, radius: d.radius, opacity: d.opacity * d.alpha }, rect, cov);
661
+ }
662
+ function spongeEffect(brush, saturate, vibrance) {
663
+ const rgb = [0, 0, 0];
664
+ return (medium, d, rect, cov) => {
665
+ const w = rect.width;
666
+ for (let yy = 0; yy < rect.height; yy++) {
667
+ for (let xx = 0; xx < w; xx++) {
668
+ const cv = cov[yy * w + xx];
669
+ if (cv <= 0) continue;
670
+ const dx = rect.x + xx;
671
+ const dy = rect.y + yy;
672
+ const k = perDabStrength(cv * d.alpha * d.opacity * 0.5, brush.spacing) * medium.selectionAt(dx, dy);
673
+ if (k <= 0) continue;
674
+ const ri = medium.locate(dx, dy, false);
675
+ if (ri < 0 || medium.pixels[ri + 3] === 0) continue;
676
+ const i = medium.locate(dx, dy, true);
677
+ const px = medium.pixels;
678
+ rgb[0] = px[i] / 255;
679
+ rgb[1] = px[i + 1] / 255;
680
+ rgb[2] = px[i + 2] / 255;
681
+ sponge(rgb, k, saturate, vibrance);
682
+ put255(px, i, rgb[0]);
683
+ put255(px, i + 1, rgb[1]);
684
+ put255(px, i + 2, rgb[2]);
685
+ }
686
+ }
687
+ };
688
+ }
689
+ function rgbaViewOfGrey(target) {
690
+ const views = /* @__PURE__ */ new Map();
691
+ const dirty = /* @__PURE__ */ new Map();
692
+ const expand = (g) => {
693
+ const out = new Uint8ClampedArray(g.length * 4);
694
+ for (let i = 0; i < g.length; i++) {
695
+ out[i * 4] = out[i * 4 + 1] = out[i * 4 + 2] = g[i];
696
+ out[i * 4 + 3] = 255;
697
+ }
698
+ return out;
699
+ };
700
+ const view = {
701
+ offset: target.offset,
702
+ lockTransparency: true,
703
+ selection: target.selection,
704
+ readTile: (tx, ty) => {
705
+ const key = tileKey(tx, ty);
706
+ let v = views.get(key);
707
+ if (!v) {
708
+ v = expand(target.readTile(tx, ty) ?? new Uint8ClampedArray(TILE * TILE));
709
+ views.set(key, v);
710
+ }
711
+ return v;
712
+ },
713
+ writeTile: (tx, ty) => {
714
+ const key = tileKey(tx, ty);
715
+ let v = views.get(key);
716
+ if (!v) {
717
+ v = expand(target.readTile(tx, ty) ?? new Uint8ClampedArray(TILE * TILE));
718
+ views.set(key, v);
719
+ }
720
+ dirty.set(key, { tx, ty, data: v });
721
+ return v;
722
+ }
723
+ };
724
+ return {
725
+ target: view,
726
+ flush: () => {
727
+ for (const { tx, ty, data } of dirty.values()) {
728
+ const g = target.writeTile(tx, ty);
729
+ for (let i = 0; i < g.length; i++) g[i] = data[i * 4];
730
+ }
731
+ dirty.clear();
732
+ }
733
+ };
734
+ }
735
+ function unionRect(a, b) {
736
+ if (!a) return { ...b };
737
+ const x = Math.min(a.x, b.x);
738
+ const y = Math.min(a.y, b.y);
739
+ 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 };
740
+ }
741
+
742
+ // src/paint/pointer.ts
743
+ function pointerPressure(e) {
744
+ if (e.pointerType === "pen" && typeof e.pressure === "number" && e.pressure > 0) return Math.min(1, e.pressure);
745
+ return 1;
746
+ }
747
+ function pointerStrokeSample(e, toPoint) {
748
+ const p = toPoint(e.clientX, e.clientY);
749
+ const s = { x: p.x, y: p.y, pressure: pointerPressure(e) };
750
+ if (typeof e.timeStamp === "number" && Number.isFinite(e.timeStamp)) s.t = e.timeStamp;
751
+ if (e.pointerType === "pen") {
752
+ if (typeof e.altitudeAngle === "number" && e.altitudeAngle < Math.PI / 2 - 1e-6) {
753
+ s.altitude = e.altitudeAngle;
754
+ s.azimuth = e.azimuthAngle ?? 0;
755
+ } else if (e.tiltX || e.tiltY) {
756
+ s.tiltX = e.tiltX ?? 0;
757
+ s.tiltY = e.tiltY ?? 0;
758
+ }
759
+ }
760
+ return s;
761
+ }
762
+ function pointerStrokeSamples(e, toPoint) {
763
+ const events = e.getCoalescedEvents?.() ?? [];
764
+ return (events.length ? events : [e]).map((ev) => pointerStrokeSample(ev, toPoint));
765
+ }
766
+
767
+ export {
768
+ createPigmentTools,
769
+ TONE_RANGES,
770
+ luma,
771
+ dodgeBurnCurve,
772
+ dodgeBurn,
773
+ saturationOf,
774
+ sponge,
775
+ perDabStrength,
776
+ RETOUCH_SAMPLES,
777
+ SPOT_HEAL_TYPES,
778
+ DEFAULT_RETOUCH,
779
+ normalizeRetouch,
780
+ boxBlurPixels,
781
+ retouchFilter,
782
+ IDENTITY_CLONE_TRANSFORM,
783
+ isIdentityCloneTransform,
784
+ cloneSamplePoint,
785
+ TransformedPixelSource,
786
+ TilePixelSource,
787
+ RegionPixelSource,
788
+ readSourceRegion,
789
+ applyRetouchToTile,
790
+ applyToneToDeepTile,
791
+ toneOf,
792
+ healPixels,
793
+ spotHealPixels,
794
+ spotHealWindow,
795
+ RetouchDabStroke,
796
+ smudgeEffect,
797
+ spongeEffect,
798
+ rgbaViewOfGrey,
799
+ pointerPressure,
800
+ pointerStrokeSample,
801
+ pointerStrokeSamples
802
+ };