@pixerate/editor 0.248.0 → 0.249.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 (89) hide show
  1. package/README.md +1 -0
  2. package/dist/animation-editor/index.cjs +64 -61
  3. package/dist/animation-editor/index.js +8 -5
  4. package/dist/{brushstrokeRender-DTDPFBLC.cjs → brushstrokeRender-GCKK7V2I.cjs} +14 -11
  5. package/dist/{brushstrokeRender-QLCXROJK.js → brushstrokeRender-T3JZST44.js} +7 -4
  6. package/dist/canvas/index.cjs +8 -8
  7. package/dist/canvas/index.js +1 -1
  8. package/dist/{chunk-IBDU6EGZ.cjs → chunk-2SMMZDMG.cjs} +35 -35
  9. package/dist/{chunk-SC2RZ5EF.js → chunk-2VJAVXLI.js} +2 -2
  10. package/dist/{chunk-OLNIPTB3.cjs → chunk-2VX55NFM.cjs} +1 -320
  11. package/dist/chunk-3K6HWEXZ.cjs +92 -0
  12. package/dist/{chunk-UOOK7HTX.js → chunk-3PPJRMIG.js} +1 -1
  13. package/dist/chunk-4D6KP5BV.js +92 -0
  14. package/dist/chunk-5RH3VXAK.js +269 -0
  15. package/dist/{chunk-NZ3O3A5C.js → chunk-6BIDVPED.js} +1 -1
  16. package/dist/{chunk-2ONK3ERD.cjs → chunk-75K3W4D6.cjs} +20 -20
  17. package/dist/{chunk-7T7JWTVE.cjs → chunk-BOHDJJ2O.cjs} +8 -8
  18. package/dist/{chunk-2PBZBRP5.js → chunk-D5KGAO4Z.js} +10 -92
  19. package/dist/{chunk-5GLYUSNT.js → chunk-DOIU7VJG.js} +1 -1
  20. package/dist/{chunk-HB4ICNNA.cjs → chunk-DPYAO5U6.cjs} +42 -42
  21. package/dist/chunk-EJLRBS5Q.js +301 -0
  22. package/dist/{chunk-6G6ZDDDB.cjs → chunk-H4AOUUBE.cjs} +301 -149
  23. package/dist/{chunk-PQ3H2AAN.cjs → chunk-I7NJPHWV.cjs} +122 -204
  24. package/dist/{chunk-L6REDLNB.cjs → chunk-IOFVM3L3.cjs} +7 -7
  25. package/dist/{chunk-42TWUPNJ.js → chunk-IRSTHLQQ.js} +1 -320
  26. package/dist/{chunk-VNBUDVD4.js → chunk-J7T5QZ45.js} +226 -74
  27. package/dist/{chunk-VMROIWLL.cjs → chunk-KBYB7OWB.cjs} +3 -3
  28. package/dist/{chunk-TPWB4Y6L.cjs → chunk-KYX7NZ5V.cjs} +1 -1
  29. package/dist/chunk-LJRCU3BJ.cjs +301 -0
  30. package/dist/{chunk-WVS5KQDH.cjs → chunk-LP2MCSBE.cjs} +246 -244
  31. package/dist/{chunk-XNRBMYNB.js → chunk-O75V4ZS3.js} +1 -1
  32. package/dist/chunk-OTTT5N3K.js +322 -0
  33. package/dist/{chunk-Q2HA52V4.js → chunk-PZP2SZIZ.js} +2 -2
  34. package/dist/{chunk-ZFSLEOSQ.js → chunk-UBRJOIR5.js} +11 -9
  35. package/dist/chunk-W5M3J7PV.cjs +322 -0
  36. package/dist/{chunk-4P6H35HU.js → chunk-WA44NKPF.js} +2 -2
  37. package/dist/{chunk-XWTRATIM.cjs → chunk-WLJZZHUZ.cjs} +23 -23
  38. package/dist/{chunk-P3NIPIKS.js → chunk-WQCNC4DK.js} +1 -1
  39. package/dist/chunk-ZEYMKDYS.cjs +269 -0
  40. package/dist/image-editor/index.cjs +1055 -1050
  41. package/dist/image-editor/index.js +28 -23
  42. package/dist/paint/index.cjs +8 -5
  43. package/dist/paint/index.js +13 -10
  44. package/dist/paint-editor/controller.d.cts +37 -1
  45. package/dist/paint-editor/controller.d.ts +37 -1
  46. package/dist/paint-editor/index.cjs +745 -602
  47. package/dist/paint-editor/index.d.cts +3 -0
  48. package/dist/paint-editor/index.d.ts +3 -0
  49. package/dist/paint-editor/index.js +184 -41
  50. package/dist/paint-editor/sticker-art.d.cts +55 -0
  51. package/dist/paint-editor/sticker-art.d.ts +55 -0
  52. package/dist/paint-editor/stickers.d.cts +30 -17
  53. package/dist/paint-editor/stickers.d.ts +30 -17
  54. package/dist/paint-editor/stroke.d.cts +4 -0
  55. package/dist/paint-editor/stroke.d.ts +4 -0
  56. package/dist/paint-editor/ui.d.cts +10 -2
  57. package/dist/paint-editor/ui.d.ts +10 -2
  58. package/dist/raster/index.cjs +6 -4
  59. package/dist/raster/index.js +12 -10
  60. package/dist/stickers/browser.d.cts +48 -0
  61. package/dist/stickers/browser.d.ts +48 -0
  62. package/dist/stickers/combine.d.cts +10 -0
  63. package/dist/stickers/combine.d.ts +10 -0
  64. package/dist/stickers/conformance.d.cts +18 -0
  65. package/dist/stickers/conformance.d.ts +18 -0
  66. package/dist/stickers/editor.d.cts +14 -0
  67. package/dist/stickers/editor.d.ts +14 -0
  68. package/dist/stickers/errors.d.cts +6 -0
  69. package/dist/stickers/errors.d.ts +6 -0
  70. package/dist/stickers/fake.d.cts +16 -0
  71. package/dist/stickers/fake.d.ts +16 -0
  72. package/dist/stickers/helpers.d.cts +51 -0
  73. package/dist/stickers/helpers.d.ts +51 -0
  74. package/dist/stickers/index.cjs +331 -0
  75. package/dist/stickers/index.d.cts +16 -0
  76. package/dist/stickers/index.d.ts +16 -0
  77. package/dist/stickers/index.js +331 -0
  78. package/dist/stickers/stamps.d.cts +13 -0
  79. package/dist/stickers/stamps.d.ts +13 -0
  80. package/dist/stickers/tools.d.cts +25 -0
  81. package/dist/stickers/tools.d.ts +25 -0
  82. package/dist/stickers/types.d.cts +123 -0
  83. package/dist/stickers/types.d.ts +123 -0
  84. package/dist/vector/index.cjs +978 -975
  85. package/dist/vector/index.js +9 -6
  86. package/package.json +11 -1
  87. package/skills/README.md +1 -0
  88. package/skills/pixerate-paint-editor/SKILL.md +8 -5
  89. package/skills/pixerate-stickers/SKILL.md +45 -0
@@ -51,3 +51,6 @@ export type { WetBackendInfo } from '../paint/wet/world.cjs';
51
51
  export * from '../paint/gradient-paint.cjs';
52
52
  export * from './playback.cjs';
53
53
  export * from './editorDocument.cjs';
54
+ export { StickerBrowser, type StickerBrowserItem, type StickerBrowserState } from '../stickers/browser.cjs';
55
+ export type { StickerProvider, StickerRef, StickerSummary } from '../stickers/types.cjs';
56
+ export * from './sticker-art.cjs';
@@ -51,3 +51,6 @@ export type { WetBackendInfo } from '../paint/wet/world.js';
51
51
  export * from '../paint/gradient-paint.js';
52
52
  export * from './playback.js';
53
53
  export * from './editorDocument.js';
54
+ export { StickerBrowser, type StickerBrowserItem, type StickerBrowserState } from '../stickers/browser.js';
55
+ export type { StickerProvider, StickerRef, StickerSummary } from '../stickers/types.js';
56
+ export * from './sticker-art.js';
@@ -1,3 +1,7 @@
1
+ import {
2
+ StickerBrowser,
3
+ editorStickers
4
+ } from "../chunk-EJLRBS5Q.js";
1
5
  import {
2
6
  toVideoFfmpeg
3
7
  } from "../chunk-L3JFOTBQ.js";
@@ -8,7 +12,7 @@ import {
8
12
  resolveExternalAssets,
9
13
  serializeLayers,
10
14
  serializeLayersExternal
11
- } from "../chunk-ZFSLEOSQ.js";
15
+ } from "../chunk-UBRJOIR5.js";
12
16
  import "../chunk-3CI7FRMK.js";
13
17
  import "../chunk-ZNA3IQZF.js";
14
18
  import {
@@ -26,7 +30,7 @@ import {
26
30
  mergeLayerEffect,
27
31
  newEffectId,
28
32
  validateLayerEffects
29
- } from "../chunk-UOOK7HTX.js";
33
+ } from "../chunk-3PPJRMIG.js";
30
34
  import "../chunk-OHKOFKBY.js";
31
35
  import "../chunk-ZIF4UJJ6.js";
32
36
  import {
@@ -53,7 +57,7 @@ import {
53
57
  pigmentOf,
54
58
  pointerPressure,
55
59
  wetSupport
56
- } from "../chunk-P3NIPIKS.js";
60
+ } from "../chunk-WQCNC4DK.js";
57
61
  import "../chunk-HVQVHUTV.js";
58
62
  import {
59
63
  writeZip
@@ -64,7 +68,7 @@ import {
64
68
  realPresenceClock,
65
69
  tweenPresenceCursor
66
70
  } from "../chunk-IVSWRVB4.js";
67
- import "../chunk-Q2HA52V4.js";
71
+ import "../chunk-PZP2SZIZ.js";
68
72
  import {
69
73
  BRUSH_ID,
70
74
  BRUSH_ROTATIONS,
@@ -89,6 +93,7 @@ import {
89
93
  IDENTITY_COPY,
90
94
  IMPASTO_TOOLS,
91
95
  MAX_DOT_TEXTURES,
96
+ MAX_STICKER_STAMPS,
92
97
  MULTI_COLOR_MODES,
93
98
  PAINT_BRUSH_CATEGORIES,
94
99
  PAINT_BRUSH_ENGINES,
@@ -100,7 +105,8 @@ import {
100
105
  PenDabWalker,
101
106
  PenSmoother,
102
107
  RIBBON_PAIRS,
103
- STAMP_KINDS,
108
+ STICKER_ART_SIZE,
109
+ StickerArtCache,
104
110
  StickerSpray,
105
111
  WET_MEDIA,
106
112
  WET_TOOLS,
@@ -108,6 +114,7 @@ import {
108
114
  brushTakesPaint,
109
115
  brushToJSON,
110
116
  clonePaper,
117
+ defaultStickerRasterizer,
111
118
  drawSticker,
112
119
  effectDabField,
113
120
  effectPaintKey,
@@ -138,16 +145,25 @@ import {
138
145
  readPaintEffectPaint,
139
146
  renderPaper,
140
147
  renderStickers,
148
+ sampleArt,
141
149
  sampleEffectAt,
142
150
  sampleEffectField,
143
- stampCoverage,
144
151
  stickerAt,
145
152
  stickerRect,
153
+ stickerStamp,
146
154
  stickerSurface,
147
155
  tiltToOrientation,
148
156
  toStrokePoint,
149
157
  usesPaintExchange
150
- } from "../chunk-VNBUDVD4.js";
158
+ } from "../chunk-J7T5QZ45.js";
159
+ import {
160
+ STAMP_KINDS,
161
+ StickersSlot,
162
+ createStickerTools,
163
+ editorStampOf,
164
+ normalizeStickerRef,
165
+ stampCoverage
166
+ } from "../chunk-5RH3VXAK.js";
151
167
  import "../chunk-M6DNZNVY.js";
152
168
  import "../chunk-WEIK3UYB.js";
153
169
  import "../chunk-PWBZH3FK.js";
@@ -188,7 +204,6 @@ import {
188
204
  PaperGrain,
189
205
  SmudgeTip,
190
206
  TileMedium,
191
- TileableNoise,
192
207
  addPaintGradientStop,
193
208
  applyStrokeToTile,
194
209
  blenderDab,
@@ -198,46 +213,51 @@ import {
198
213
  exchangeRates,
199
214
  grainCatch,
200
215
  grainHeights,
201
- hash2,
202
216
  hexToPaintRgb,
203
217
  isGradientPaint,
204
218
  loadMixer,
205
- mix32,
206
219
  mixerRates,
207
220
  normalizeHexColor,
208
221
  paintGradientMappingFor,
209
222
  paintSourceColor,
210
223
  paintSourceCss,
211
224
  parsePaintSource,
212
- rand01,
213
- randSigned,
214
225
  rasterizeDab,
215
226
  readPaintSource,
216
227
  removePaintGradientStop,
217
228
  resolvePaintGradient,
218
229
  reversePaintGradient,
219
230
  scalePaintGradient,
220
- seedFromString,
221
231
  solidPaint,
222
232
  updatePaintGradientStop
223
- } from "../chunk-2PBZBRP5.js";
233
+ } from "../chunk-D5KGAO4Z.js";
234
+ import {
235
+ TileableNoise,
236
+ hash2,
237
+ mix32,
238
+ rand01,
239
+ randSigned,
240
+ seedFromString
241
+ } from "../chunk-4D6KP5BV.js";
224
242
  import "../chunk-ODBT55Q7.js";
225
243
  import {
226
244
  BLEND_MODES,
227
245
  PAINTERLY_BLEND_MODES,
246
+ isBlendMode,
247
+ isPainterlyBlendMode,
248
+ mixPigment8,
249
+ mixPigments,
250
+ standardBlendMode
251
+ } from "../chunk-IRSTHLQQ.js";
252
+ import {
228
253
  base64ToBytes,
229
254
  bytesToBase64,
230
255
  decodePng,
231
256
  encodePng,
232
- isBlendMode,
233
- isPainterlyBlendMode,
234
257
  isPng,
235
- mixPigment8,
236
- mixPigments,
237
258
  parseDataUrl,
238
- pngDataUrl,
239
- standardBlendMode
240
- } from "../chunk-42TWUPNJ.js";
259
+ pngDataUrl
260
+ } from "../chunk-OTTT5N3K.js";
241
261
  import {
242
262
  TILE,
243
263
  TiledSurface,
@@ -1305,7 +1325,7 @@ var PaintStrokeSession = class _PaintStrokeSession {
1305
1325
  };
1306
1326
  if (this.wetDriver) {
1307
1327
  } else if (init.sticker) {
1308
- this.stickerSpray = new StickerSpray({ brush, target: this.tileTarget(), color: init.colorHex, seed: init.seed, copies: this.copies, nextId: init.sticker.nextId, paint: init.paint ?? null, effect });
1328
+ this.stickerSpray = new StickerSpray({ brush, target: this.tileTarget(), color: init.colorHex, seed: init.seed, copies: this.copies, nextId: init.sticker.nextId, paint: init.paint ?? null, effect, art: init.sticker.art });
1309
1329
  } else if (init.thick) {
1310
1330
  const rgb = (c) => [c[0] / 255, c[1] / 255, c[2] / 255];
1311
1331
  this.thickDriver = new ThickStrokeDriver({ brush, target: this.tileTarget(), channels: init.thick.channels, color: rgb(init.color), load: init.mixerLoad, seed: init.seed, copies: this.copies, paint: init.paint ?? null, effect });
@@ -1515,6 +1535,7 @@ var PaintStrokeSession = class _PaintStrokeSession {
1515
1535
  const surface = changed ? new TiledSurface(ch, tiles, this.base.defaultValue) : null;
1516
1536
  const dabs = this.dry ? this.dry.dabCount : this.thickDriver ? this.thickDriver.dabCount : this.stickerSpray ? this.stickerSpray.dabCount : this.wet.dabCount;
1517
1537
  this.result = { layerId: this.layerId, bounds: changed ? this.bounds : null, dabs, strokeId: null };
1538
+ if (this.stickerSpray?.skipped.size) this.result.skippedStickers = [...this.stickerSpray.skipped];
1518
1539
  const carried = this.wet?.load ?? this.thickDriver?.load;
1519
1540
  if (carried) this.result.mixerLoad = this.init.brush.behavior.dirty ? carried : cleanMixer(carried);
1520
1541
  this.host.commit(this, surface, false);
@@ -4161,10 +4182,16 @@ var PaintEditorController = class _PaintEditorController {
4161
4182
  wetOp = null;
4162
4183
  /** The effects provider slot (see `setEffectsProvider`). */
4163
4184
  effects = new EffectsSlot();
4185
+ /** The sticker library slot (see `setStickersProvider`). */
4186
+ stickers = new StickersSlot(editorStickers());
4187
+ stickerArt = new StickerArtCache();
4188
+ rasterizeSticker;
4164
4189
  effectsRenderer = new PaintEffectsRenderer();
4165
4190
  paperEffectNodeCache = null;
4166
4191
  constructor(options = {}) {
4167
4192
  this.historyLimit = Math.max(1, options.historyLimit ?? 200);
4193
+ this.rasterizeSticker = options.rasterizeSticker ?? defaultStickerRasterizer;
4194
+ if (options.stickers) this.stickers.set(options.stickers);
4168
4195
  this.doc = createPaintDocument({ width: options.width, height: options.height, dpi: options.dpi, paper: this.resolvePaper(options.paper) });
4169
4196
  this.userBrushes = (options.brushes ?? []).map((b) => normalizePaintBrush({ ...b, builtin: false }));
4170
4197
  const start = this.findBrush(options.brushId ?? DEFAULT_BRUSH_ID) ?? this.findBrush(DEFAULT_BRUSH_ID);
@@ -5323,6 +5350,7 @@ var PaintEditorController = class _PaintEditorController {
5323
5350
  else delete next.builtin;
5324
5351
  this.brush = next;
5325
5352
  this.emit([]);
5353
+ if (next.engine === "sticker" && next.sticker.stamps.some((r) => !editorStampOf(r))) void this.prepareStickers(next.sticker.stamps).catch(() => this.emit([]));
5326
5354
  return this.brush;
5327
5355
  }
5328
5356
  /** Saves the active brush (or `brush`) into the user library under `id` / `name`. */
@@ -5648,7 +5676,7 @@ var PaintEditorController = class _PaintEditorController {
5648
5676
  thick: brush.engine === "impasto" ? { channels: doc.thick?.[layer.id] ?? emptyThickChannels() } : void 0,
5649
5677
  // Light paint: a twin of the stroke paints the light channel (laying, dimming, moving light).
5650
5678
  light: !wet && brush.engine !== "sticker" && (brush.light.amount > 0 || doc.light?.[layer.id]) ? { surface: doc.light?.[layer.id] ?? emptyLight() } : null,
5651
- sticker: brush.engine === "sticker" ? { nextId: this.nextStickerNumber(doc) } : void 0,
5679
+ sticker: brush.engine === "sticker" ? { nextId: this.nextStickerNumber(doc), art: this.stickerArt.get } : void 0,
5652
5680
  paint: effect ? null : this.strokeGradient(init, brush, color),
5653
5681
  effect
5654
5682
  });
@@ -6445,9 +6473,19 @@ var PaintEditorController = class _PaintEditorController {
6445
6473
  const stickers = {};
6446
6474
  for (const [id, list] of Object.entries(ext.stickers)) {
6447
6475
  if (!findLayer(state.layers, id) || !Array.isArray(list)) continue;
6448
- const ok = list.filter((x) => x && typeof x.id === "string" && STAMP_KINDS.includes(x.stamp) && Number.isFinite(x.x) && Number.isFinite(x.y) && x.size > 0);
6476
+ const ok = list.flatMap((x) => {
6477
+ if (!x || typeof x.id !== "string" || !Number.isFinite(x.x) || !Number.isFinite(x.y) || !(x.size > 0)) return [];
6478
+ try {
6479
+ const ref2 = normalizeStickerRef(x.ref ?? x.stamp);
6480
+ const stamp = editorStampOf(ref2);
6481
+ const { stamp: _old, ...rest } = x;
6482
+ return [{ ...rest, ref: ref2, ...stamp ? { stamp } : {} }];
6483
+ } catch {
6484
+ return [];
6485
+ }
6486
+ });
6449
6487
  if (ok.length !== list.length) problems.push(`Some stickers of layer "${id}" could not be read; they were dropped.`);
6450
- stickers[id] = ok.map((x) => ({ ...x }));
6488
+ stickers[id] = ok;
6451
6489
  }
6452
6490
  if (Object.keys(stickers).length) state = { ...state, stickers };
6453
6491
  }
@@ -6551,6 +6589,8 @@ var PaintEditorController = class _PaintEditorController {
6551
6589
  delete this.extensions.paperEffect;
6552
6590
  this.viewCompositor.clear();
6553
6591
  this.emit();
6592
+ const refs = Object.values(this.doc.stickers ?? {}).flatMap((list) => list.map((x) => x.ref));
6593
+ if (refs.some((r) => !editorStampOf(r))) void this.prepareStickers(refs).catch(() => this.emit([]));
6554
6594
  return loaded.problems;
6555
6595
  }
6556
6596
  /** Opens a saved document (clears the history). Returns what could not be read. */
@@ -6878,6 +6918,56 @@ var PaintEditorController = class _PaintEditorController {
6878
6918
  if (!stickers) throw new PaintEditorError(`Layer "${l.name}" has no stickers; spray some with a sticker brush (list_brushes category sticker).`);
6879
6919
  return { layer: l, stickers };
6880
6920
  }
6921
+ // --- The sticker library ---------------------------------------------------------
6922
+ /** The sticker library: the editor's own stamps unless a host set its own. */
6923
+ get stickersProvider() {
6924
+ return this.stickers.provider;
6925
+ }
6926
+ /**
6927
+ * Sets the sticker library (`null` goes back to the editor's own stamps, `editorStickers()`);
6928
+ * to offer both, pass `combineStickerProviders([editorStickers(), yours])`. Loaded art is
6929
+ * forgotten. Not an edit: no undo step.
6930
+ */
6931
+ setStickersProvider(provider) {
6932
+ if ((provider ?? this.stickers.fallback) === this.stickers.provider) return;
6933
+ this.stickers.set(provider);
6934
+ this.stickerArt.clear();
6935
+ this.emit([]);
6936
+ }
6937
+ /**
6938
+ * Loads the art of `refs` (provider stickers; editor stamps need none) so sprays and placements
6939
+ * can draw them. Rejects with a `PaintEditorError` naming an unknown ref and the closest known
6940
+ * ones, or a sticker whose art failed. Emits when art arrives, so views redraw.
6941
+ */
6942
+ async prepareStickers(refs) {
6943
+ const list = [...new Set(refs.map((r) => this.readStickerRef(r)))].filter((r) => !editorStampOf(r));
6944
+ try {
6945
+ await Promise.all(list.map((r) => this.stickerArt.ensure(this.stickers.provider, r, this.rasterizeSticker)));
6946
+ } catch (e) {
6947
+ throw new PaintEditorError(e instanceof Error ? e.message : String(e));
6948
+ } finally {
6949
+ if (list.length) this.emit([]);
6950
+ }
6951
+ }
6952
+ /** Whether a sticker's art is ready to draw: `ready` (always for editor stamps), `loading`, `failed` (with why) or `none`. */
6953
+ stickerArtStatus(ref2) {
6954
+ return this.stickerArt.status(this.readStickerRef(ref2));
6955
+ }
6956
+ readStickerRef(ref2) {
6957
+ try {
6958
+ return normalizeStickerRef(ref2);
6959
+ } catch (e) {
6960
+ throw new PaintEditorError(e.message);
6961
+ }
6962
+ }
6963
+ /** Refuses to redraw stickers whose art has not loaded (they would vanish), naming them and what to call. */
6964
+ requireStickerArt(stickers) {
6965
+ const missing = [...new Set(stickers.filter((s) => !stickerStamp(s) && !this.stickerArt.get(s.ref)).map((s) => s.ref))];
6966
+ if (missing.length) {
6967
+ for (const r of missing) void this.stickerArt.ensure(this.stickers.provider, r, this.rasterizeSticker).then(() => this.emit([]), () => this.emit([]));
6968
+ throw new PaintEditorError(`The art for ${missing.map((r) => `"${r}"`).join(", ")} has not loaded yet; it is loading now (prepareStickers), so try again when it has.`);
6969
+ }
6970
+ }
6881
6971
  /** A layer's stickers, bottom first. */
6882
6972
  listStickers(layerId) {
6883
6973
  const id = layerId ?? this.doc.activeLayerId;
@@ -6891,6 +6981,7 @@ var PaintEditorController = class _PaintEditorController {
6891
6981
  */
6892
6982
  commitStickers(label, layer, stickers, dirty) {
6893
6983
  if (isLayerLocked(layer, "pixels")) throw new PaintEditorError(`Layer "${layer.name}" has its pixels locked; unlock it first.`);
6984
+ this.requireStickerArt(stickers);
6894
6985
  const before = this.previewBase() ?? this.doc;
6895
6986
  this.stickerPreview = null;
6896
6987
  const surface = this.redrawStickers(layer, stickers, dirty);
@@ -6899,7 +6990,7 @@ var PaintEditorController = class _PaintEditorController {
6899
6990
  }
6900
6991
  redrawStickers(layer, stickers, dirty) {
6901
6992
  const partial = dirty && !layer.offset.x && !layer.offset.y;
6902
- return partial ? stickerSurface(stickers, layer.surface, dirty) : stickerSurface(stickers);
6993
+ return partial ? stickerSurface(stickers, layer.surface, dirty, this.stickerArt.get) : stickerSurface(stickers, void 0, void 0, this.stickerArt.get);
6903
6994
  }
6904
6995
  /** The document from before the sticker preview on screen, or `null` when none is (anything else changed the document since). */
6905
6996
  previewBase() {
@@ -6918,7 +7009,7 @@ var PaintEditorController = class _PaintEditorController {
6918
7009
  const id = layerId ?? this.doc.activeLayerId;
6919
7010
  if (!id) return null;
6920
7011
  this.requireLayer(id);
6921
- const hit = stickerAt(this.doc.stickers?.[id] ?? [], x, y);
7012
+ const hit = stickerAt(this.doc.stickers?.[id] ?? [], x, y, this.stickerArt.get);
6922
7013
  return hit ? { ...hit } : null;
6923
7014
  }
6924
7015
  /** Picks a sticker for the panel and canvas (`null` for none). Editor state, not an undo step. */
@@ -6972,7 +7063,7 @@ var PaintEditorController = class _PaintEditorController {
6972
7063
  */
6973
7064
  addStickers(layerId, input) {
6974
7065
  const layer = this.wetLayer(layerId, "add stickers to");
6975
- if (!input.length) throw new PaintEditorError("Pass at least one sticker (stamp, x, y).");
7066
+ if (!input.length) throw new PaintEditorError("Pass at least one sticker (ref, x, y).");
6976
7067
  if (input.length > MAX_ADD_STICKERS) throw new PaintEditorError(`At most ${MAX_ADD_STICKERS} stickers per call; split them over several calls.`);
6977
7068
  const existing = this.doc.stickers?.[layer.id];
6978
7069
  if (!existing?.length) {
@@ -6980,7 +7071,8 @@ var PaintEditorController = class _PaintEditorController {
6980
7071
  }
6981
7072
  let n = this.nextStickerNumber();
6982
7073
  const added = input.map((raw, i) => {
6983
- for (const k of ["stamp", "x", "y"]) if (raw[k] === void 0) throw new PaintEditorError(`Sticker ${i} needs ${k}.`);
7074
+ if (raw.ref === void 0 && raw.stamp === void 0) throw new PaintEditorError(`Sticker ${i} needs ref (a sticker ref from find_stickers, e.g. "editor:star").`);
7075
+ for (const k of ["x", "y"]) if (raw[k] === void 0) throw new PaintEditorError(`Sticker ${i} needs ${k}.`);
6984
7076
  let u;
6985
7077
  try {
6986
7078
  u = this.readStickerUpdate(raw);
@@ -8045,7 +8137,7 @@ function createPaintStickerTools(c) {
8045
8137
  {
8046
8138
  name: "list_stickers",
8047
8139
  title: "List stickers",
8048
- description: "Lists a layer\u2019s stickers bottom first: id, stamp, x, y (centre, document pixels), size (diameter px), rotation (degrees clockwise), color, opacity. Empty for a layer without stickers. Changes nothing.",
8140
+ description: "Lists a layer\u2019s stickers bottom first: id, ref (provider:id; stamp too for the editor\u2019s own), x, y (centre, document pixels), size (diameter px), rotation (degrees clockwise), color, opacity. Empty for a layer without stickers. Changes nothing.",
8049
8141
  inputSchema: { type: "object", properties: { layerId: LAYER_ID }, additionalProperties: false },
8050
8142
  annotations: { title: "List stickers", ...READ },
8051
8143
  coordinates: { out: ["stickers[]"] },
@@ -8072,7 +8164,7 @@ function createPaintStickerTools(c) {
8072
8164
  {
8073
8165
  name: "add_stickers",
8074
8166
  title: "Place stickers",
8075
- description: "Places stickers exactly where you say, on top of a sticker layer or onto an empty layer (which becomes a sticker layer). Use it for deliberate placement; spray with paint_stroke and a sticker brush for a random scatter. One undo step. Returns the new stickers with their ids, for update_sticker, arrange_stickers and remove_stickers.",
8167
+ description: "Places stickers exactly where you say, on top of a sticker layer or onto an empty layer (which becomes a sticker layer): any sticker from the sticker library by ref (find_stickers), its art loaded first; tintable ones take color. Use it for deliberate placement; spray with paint_stroke and a sticker brush for a random scatter. An unknown ref is refused with the closest ones. One undo step. Returns the new stickers with their ids, for update_sticker, arrange_stickers and remove_stickers.",
8076
8168
  inputSchema: {
8077
8169
  type: "object",
8078
8170
  properties: {
@@ -8085,7 +8177,8 @@ function createPaintStickerTools(c) {
8085
8177
  items: {
8086
8178
  type: "object",
8087
8179
  properties: {
8088
- stamp: { type: "string", enum: [...STAMP_KINDS], description: "The stamp." },
8180
+ ref: { type: "string", minLength: 1, description: "The sticker: a ref provider:id from find_stickers (e.g. editor:star). Give ref or stamp." },
8181
+ stamp: { type: "string", enum: [...STAMP_KINDS], description: "Older form: an editor stamp (same as ref editor:<stamp>)." },
8089
8182
  x: { type: "number", description: "Centre x in document pixels." },
8090
8183
  y: { type: "number", description: "Centre y in document pixels." },
8091
8184
  size: { type: "number", minimum: 2, maximum: 2048, description: "Diameter in pixels (default 48)." },
@@ -8094,7 +8187,7 @@ function createPaintStickerTools(c) {
8094
8187
  opacity: { type: "number", minimum: 0, maximum: 1, description: "Opacity 0..1 (default 1)." },
8095
8188
  seed: { type: "integer", minimum: 0, description: "Texture seed (default from the new id); splat and confetti shapes vary with it." }
8096
8189
  },
8097
- required: ["stamp", "x", "y"],
8190
+ required: ["x", "y"],
8098
8191
  additionalProperties: false
8099
8192
  }
8100
8193
  }
@@ -8104,7 +8197,14 @@ function createPaintStickerTools(c) {
8104
8197
  },
8105
8198
  annotations: { title: "Place stickers", ...WRITE },
8106
8199
  coordinates: { in: ["stickers[]"], out: ["stickers[]"] },
8107
- handler: ({ layerId, stickers }) => ({ layerId: layerId ?? c.document.activeLayerId, stickers: c.addStickers(layerId, stickers) })
8200
+ handler: async ({ layerId, stickers }) => {
8201
+ const list = stickers;
8202
+ list.forEach((x, i) => {
8203
+ if (x.ref === void 0 && x.stamp === void 0) throw new Error(`Sticker ${i} needs ref (a sticker ref from find_stickers, e.g. "editor:star").`);
8204
+ });
8205
+ await c.prepareStickers(list.map((x) => x.ref ?? `editor:${x.stamp}`));
8206
+ return { layerId: layerId ?? c.document.activeLayerId, stickers: c.addStickers(layerId, list) };
8207
+ }
8108
8208
  },
8109
8209
  {
8110
8210
  name: "update_sticker",
@@ -8115,7 +8215,8 @@ function createPaintStickerTools(c) {
8115
8215
  properties: {
8116
8216
  layerId: LAYER_ID,
8117
8217
  id: { type: "string", description: "Sticker id from list_stickers." },
8118
- stamp: { type: "string", enum: [...STAMP_KINDS], description: "Another stamp." },
8218
+ ref: { type: "string", minLength: 1, description: "Another sticker: a ref provider:id from find_stickers." },
8219
+ stamp: { type: "string", enum: [...STAMP_KINDS], description: "Older form: another editor stamp (same as ref editor:<stamp>)." },
8119
8220
  x: { type: "number", description: "Centre x in document pixels." },
8120
8221
  y: { type: "number", description: "Centre y in document pixels." },
8121
8222
  size: { type: "number", minimum: 2, maximum: 2048, description: "Diameter in pixels." },
@@ -8128,7 +8229,11 @@ function createPaintStickerTools(c) {
8128
8229
  },
8129
8230
  annotations: { title: "Change a sticker", ...WRITE },
8130
8231
  coordinates: { in: [""], out: [""] },
8131
- handler: ({ layerId, id, ...update }) => c.updateSticker(layerId, id, update)
8232
+ handler: async ({ layerId, id, ...update }) => {
8233
+ const refs = c.listStickers(layerId).map((s) => s.ref);
8234
+ await c.prepareStickers(update.ref ? [...refs, update.ref] : refs);
8235
+ return c.updateSticker(layerId, id, update);
8236
+ }
8132
8237
  },
8133
8238
  {
8134
8239
  name: "arrange_stickers",
@@ -8145,7 +8250,10 @@ function createPaintStickerTools(c) {
8145
8250
  additionalProperties: false
8146
8251
  },
8147
8252
  annotations: { title: "Bring stickers forward or back", ...WRITE },
8148
- handler: ({ layerId, ids, to }) => ({ order: c.arrangeStickers(layerId, ids, to) })
8253
+ handler: async ({ layerId, ids, to }) => {
8254
+ await c.prepareStickers(c.listStickers(layerId).map((s) => s.ref));
8255
+ return { order: c.arrangeStickers(layerId, ids, to) };
8256
+ }
8149
8257
  },
8150
8258
  {
8151
8259
  name: "remove_stickers",
@@ -8153,7 +8261,10 @@ function createPaintStickerTools(c) {
8153
8261
  description: "Deletes stickers by id, or every sticker on the layer when ids is missing. One undo step. Returns the ids removed.",
8154
8262
  inputSchema: { type: "object", properties: { layerId: LAYER_ID, ids: { type: "array", items: { type: "string", description: "Sticker id." }, description: "Sticker ids (default all)." } }, additionalProperties: false },
8155
8263
  annotations: { title: "Delete stickers", ...DESTRUCTIVE },
8156
- handler: ({ layerId, ids }) => ({ removed: c.removeStickers(layerId, ids) })
8264
+ handler: async ({ layerId, ids }) => {
8265
+ await c.prepareStickers(c.listStickers(layerId).map((s) => s.ref));
8266
+ return { removed: c.removeStickers(layerId, ids) };
8267
+ }
8157
8268
  }
8158
8269
  ];
8159
8270
  }
@@ -9167,7 +9278,13 @@ var STICKER_BRUSH = {
9167
9278
  type: "object",
9168
9279
  description: "Sticker engine settings: stamps sprayed along the stroke as editable stickers. Missing fields keep their current values.",
9169
9280
  properties: {
9170
- stamps: { type: "array", minItems: 1, maxItems: 9, items: { type: "string", enum: [...STAMP_KINDS], description: "A stamp." }, description: "The stamps to spray (one picked at random per sticker)." },
9281
+ stamps: {
9282
+ type: "array",
9283
+ minItems: 1,
9284
+ maxItems: MAX_STICKER_STAMPS,
9285
+ items: { type: "string", minLength: 1, description: "A sticker ref provider:id from find_stickers (e.g. editor:star); a bare editor stamp (star) means editor:star." },
9286
+ description: `The stickers to spray, one picked at random per sticker: refs from find_stickers (the editor's own: ${STAMP_KINDS.map((k) => `editor:${k}`).join(", ")}). Each is checked against the sticker library and an unknown ref is refused with the closest ones. A provider's sticker is drawn from its art (tinted to the paint colour when tintable), loaded before the stroke.`
9287
+ },
9171
9288
  density: { type: "number", minimum: 0, maximum: 1, description: "How closely stickers follow each other: about density \xD7 4 stickers per tip diameter of travel (0.25 \u2248 one per diameter, 1 \u2248 four). Default 0.4." },
9172
9289
  sizeJitter: { type: "number", minimum: 0, maximum: 1, description: "Random size change." },
9173
9290
  rotationJitter: { type: "number", minimum: 0, maximum: 1, description: "Random turn as a fraction of \xB1180\xB0." },
@@ -9594,7 +9711,11 @@ function createPaintEditorToolProvider(controller, options = {}) {
9594
9711
  description: "Chooses the active brush (id) and / or changes its settings; strokes without a brush use it. Editor state, not an undo step. Changes stay on the active brush until save_brush stores them. Returns the active brush.",
9595
9712
  inputSchema: { type: "object", properties: { id: { type: "string", description: "Brush id from list_brushes to start from." }, ...BRUSH_FIELDS }, additionalProperties: false },
9596
9713
  annotations: { title: "Set the brush", ...SET4 },
9597
- handler: (a) => brushToJSON(c.setBrush(a))
9714
+ handler: async (a) => {
9715
+ const stamps = a.sticker?.stamps;
9716
+ if (stamps) await c.prepareStickers(stamps);
9717
+ return brushToJSON(c.setBrush(a));
9718
+ }
9598
9719
  });
9599
9720
  add({
9600
9721
  name: "save_brush",
@@ -9770,6 +9891,9 @@ function createPaintEditorToolProvider(controller, options = {}) {
9770
9891
  coordinates: { in: ["points[]"], out: ["bounds"] },
9771
9892
  handler: async (a) => {
9772
9893
  await c.wetIdle();
9894
+ const base = a.brushId ? c.getBrush(a.brushId) : c.getState().brush;
9895
+ const over = a.brush;
9896
+ if ((over?.engine ?? base?.engine) === "sticker") await c.prepareStickers(over?.sticker?.stamps ?? base?.sticker.stamps ?? []);
9773
9897
  const stroke = { points: a.points, layerId: a.layerId, brushId: a.brushId, brush: a.brush, color: a.color, paint: a.paint, seed: a.seed, snapToPerspective: a.snapToPerspective };
9774
9898
  const r = await act({ kind: "stroke", stroke }, () => c.paintStroke(stroke));
9775
9899
  return r.mixerLoad ? { ...r, mixerLoad: loadResult(r.mixerLoad) } : r;
@@ -10078,6 +10202,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
10078
10202
  tools.push(...createPaintReplayTools(c));
10079
10203
  tools.push(...createPaintStickerTools(c));
10080
10204
  tools.push(...createPigmentTools());
10205
+ tools.push(...createStickerTools({ provider: () => c.stickersProvider }));
10081
10206
  tools.push(
10082
10207
  ...createLayerEffectTools(
10083
10208
  {
@@ -10251,7 +10376,7 @@ var PaintGesture = class {
10251
10376
  }
10252
10377
  const brush = this.controller.getState().brush;
10253
10378
  const size = Math.min(2048, Math.max(2, Math.round(brush.tip.size)));
10254
- const [added] = this.controller.addStickers(void 0, [{ stamp: brush.sticker.stamps[0] ?? "star", x: round2(p.x), y: round2(p.y), size }]);
10379
+ const [added] = this.controller.addStickers(void 0, [{ ref: brush.sticker.stamps[0] ?? "editor:star", x: round2(p.x), y: round2(p.y), size }]);
10255
10380
  this.controller.selectSticker(added.id);
10256
10381
  } catch (err) {
10257
10382
  this.error = err instanceof Error ? err.message : String(err);
@@ -10787,6 +10912,15 @@ var RIBBON_PAIR_LABELS = { adjacent: "Neighbours", "first-last": "Outermost" };
10787
10912
  var DOT_TEXTURE_ORDER_LABELS = { sequence: "In turn", random: "Random" };
10788
10913
  var DOT_ROTATION_LABELS = { follow: "Follow motion", random: "Random", fixed: "Tip angle" };
10789
10914
  var STAMP_LABELS = { dot: "Dot", star: "Star", heart: "Heart", leaf: "Leaf", flower: "Flower", confetti: "Confetti", splat: "Splat", sparkle: "Sparkle", ring: "Ring" };
10915
+ function toggleStickerRef(refs, ref2) {
10916
+ if (refs.includes(ref2)) return refs.length > 1 ? refs.filter((r) => r !== ref2) : [...refs];
10917
+ return refs.length < MAX_STICKER_STAMPS ? [...refs, ref2] : [...refs];
10918
+ }
10919
+ function stickerRefLabel(ref2) {
10920
+ const stamp = ref2.startsWith("editor:") ? ref2.slice(7) : null;
10921
+ if (stamp && STAMP_LABELS[stamp]) return STAMP_LABELS[stamp];
10922
+ return ref2.slice(ref2.indexOf(":") + 1);
10923
+ }
10790
10924
  function toggleStamp(stamps, stamp) {
10791
10925
  const next = stamps.includes(stamp) ? stamps.filter((s) => s !== stamp) : [...stamps, stamp];
10792
10926
  return next.length ? next : [stamp];
@@ -11141,6 +11275,7 @@ export {
11141
11275
  MAX_DOT_TEXTURES,
11142
11276
  MAX_IMAGE_SIDE,
11143
11277
  MAX_LIVE_EFFECT_SOURCES,
11278
+ MAX_STICKER_STAMPS,
11144
11279
  MAX_ZOOM,
11145
11280
  MIN_CANVAS_SIDE,
11146
11281
  MIN_ZOOM,
@@ -11205,10 +11340,13 @@ export {
11205
11340
  STAMP_LABELS,
11206
11341
  STENCIL_KINDS,
11207
11342
  STENCIL_SHAPES,
11343
+ STICKER_ART_SIZE,
11208
11344
  STICKER_SLIDERS,
11209
11345
  STROKE_SLIDERS,
11210
11346
  SYMMETRY_MODES,
11211
11347
  StencilSnapper,
11348
+ StickerArtCache,
11349
+ StickerBrowser,
11212
11350
  StickerSpray,
11213
11351
  TRACING_NODE_ID,
11214
11352
  ThickStrokeDriver,
@@ -11256,6 +11394,7 @@ export {
11256
11394
  decodeImageBlob,
11257
11395
  defaultGuides,
11258
11396
  defaultImageEncoder,
11397
+ defaultStickerRasterizer,
11259
11398
  deserializePaintDocument,
11260
11399
  deserializePaintDocumentAsync,
11261
11400
  documentExtensions,
@@ -11374,6 +11513,7 @@ export {
11374
11513
  rgbToHexColor,
11375
11514
  rgbToHsl,
11376
11515
  rgbToHsv,
11516
+ sampleArt,
11377
11517
  sampleEffectAt,
11378
11518
  sampleEffectField,
11379
11519
  samplePressure,
@@ -11391,6 +11531,8 @@ export {
11391
11531
  stencilOutline,
11392
11532
  stickerAt,
11393
11533
  stickerRect,
11534
+ stickerRefLabel,
11535
+ stickerStamp,
11394
11536
  stickerSurface,
11395
11537
  strokeBounds,
11396
11538
  strokeDuration,
@@ -11408,6 +11550,7 @@ export {
11408
11550
  toStrokePoint,
11409
11551
  toggleDotTexture,
11410
11552
  toggleStamp,
11553
+ toggleStickerRef,
11411
11554
  translateStencil,
11412
11555
  updatePaintGradientStop,
11413
11556
  usesPaintExchange,
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Art for stickers from a sticker provider (anything but the editor's own procedural stamps): the
3
+ * spray brush draws while a stroke is in progress, so it can't wait for a provider. Each sticker
4
+ * is loaded once, rasterised to RGBA and kept here; drawing reads it synchronously.
5
+ *
6
+ * PNG art (`data:` URLs) is decoded here, in any environment. SVG and `https:` images need a
7
+ * rasteriser: the browser's (`createImageBitmap` and `OffscreenCanvas`) by default, or the
8
+ * host's (`PaintEditorOptions.rasterizeSticker`).
9
+ */
10
+ import type { StickerAsset, StickerProvider, StickerRef } from '../stickers/types.cjs';
11
+ /** Rasterised sticker art (straight RGBA) and whether to recolour it. */
12
+ export interface StickerArt {
13
+ width: number;
14
+ height: number;
15
+ data: Uint8ClampedArray;
16
+ /** Flat art: drawn in the sticker's colour, keeping its alpha. */
17
+ tintable: boolean;
18
+ }
19
+ /** The art for a ref, when it has loaded. */
20
+ export type StickerArtLookup = (ref: StickerRef) => StickerArt | undefined;
21
+ /** Turns a sticker asset into RGBA at about `size` px on its longest side. */
22
+ export type StickerRasterizer = (asset: StickerAsset, size: number) => Promise<{
23
+ width: number;
24
+ height: number;
25
+ data: Uint8ClampedArray;
26
+ }>;
27
+ /** Longest side sticker art is kept at (px); larger stickers are drawn from it, smoothly scaled. */
28
+ export declare const STICKER_ART_SIZE = 256;
29
+ /**
30
+ * Decodes PNG data URLs anywhere; draws SVG and other images with the browser's
31
+ * `createImageBitmap` and `OffscreenCanvas` when present, and otherwise refuses, naming the option
32
+ * that adds a rasteriser.
33
+ */
34
+ export declare const defaultStickerRasterizer: StickerRasterizer;
35
+ /** Loaded sticker art by ref, with the state of each load. */
36
+ export declare class StickerArtCache {
37
+ private readonly entries;
38
+ /** The art for `ref` when it has loaded (editor stamps never need any). */
39
+ get: StickerArtLookup;
40
+ /** `ready`, `loading`, `failed` (with the reason) or `none` (never asked for). Editor stamps are always `ready`. */
41
+ status(ref: StickerRef): {
42
+ status: 'ready' | 'loading' | 'failed' | 'none';
43
+ error?: string;
44
+ };
45
+ /**
46
+ * Loads and rasterises `ref` from `provider` once (later calls share the load). Unknown refs
47
+ * reject with the closest known ones; failures are kept so they are reported, and retried on
48
+ * the next call.
49
+ */
50
+ ensure(provider: StickerProvider, ref: StickerRef, rasterize: StickerRasterizer): Promise<StickerArt | null>;
51
+ /** Forgets every load (a new provider). */
52
+ clear(): void;
53
+ }
54
+ /** Bilinear RGBA sample of `art` at (x, y) in its pixels (straight alpha, colour weighted by alpha). */
55
+ export declare function sampleArt(art: StickerArt, x: number, y: number, out: number[]): number[];