@pixerate/editor 0.275.1 → 0.277.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 (81) hide show
  1. package/dist/animation-editor/index.cjs +62 -62
  2. package/dist/animation-editor/index.js +6 -6
  3. package/dist/brushes/index.cjs +84 -71
  4. package/dist/brushes/index.js +20 -7
  5. package/dist/brushes/patch.d.cts +4 -4
  6. package/dist/brushes/patch.d.ts +4 -4
  7. package/dist/brushes/profiles.d.cts +4 -0
  8. package/dist/brushes/profiles.d.ts +4 -0
  9. package/dist/brushes/schema.d.cts +2 -0
  10. package/dist/brushes/schema.d.ts +2 -0
  11. package/dist/brushes/types.d.cts +32 -1
  12. package/dist/brushes/types.d.ts +32 -1
  13. package/dist/{brushstrokeRender-2RQL46WB.cjs → brushstrokeRender-7HOMKZNO.cjs} +21 -21
  14. package/dist/{brushstrokeRender-WGY4DZHF.js → brushstrokeRender-EIK3625T.js} +5 -5
  15. package/dist/canvas/index.cjs +13 -13
  16. package/dist/canvas/index.js +1 -1
  17. package/dist/{chunk-T6KNJVBP.cjs → chunk-4NZTK7LM.cjs} +35 -35
  18. package/dist/{chunk-LMG57L5A.js → chunk-CTHWXW5M.js} +121 -8
  19. package/dist/{chunk-43XJC3PP.js → chunk-DWPFGWW5.js} +1 -1
  20. package/dist/{chunk-MSWLJI4G.js → chunk-FNIWHSLI.js} +2 -2
  21. package/dist/{chunk-JDXWLCXF.cjs → chunk-H3EME3S5.cjs} +2 -2
  22. package/dist/{chunk-Y2ISSVPH.js → chunk-HHX7ST6Z.js} +167 -9
  23. package/dist/{chunk-6QNDGMK7.cjs → chunk-HRUK5TFW.cjs} +187 -45
  24. package/dist/{chunk-NIBFKXMD.cjs → chunk-IFWLZZCW.cjs} +619 -2
  25. package/dist/{chunk-W6JDUXP3.js → chunk-J23JOHPR.js} +1 -1
  26. package/dist/{chunk-HVO6XCI4.js → chunk-JPB65KQ6.js} +1 -1
  27. package/dist/{chunk-Y354ETAE.cjs → chunk-JVFGK3DK.cjs} +69 -58
  28. package/dist/{chunk-ACTHSQOI.js → chunk-LDZOXESA.js} +618 -1
  29. package/dist/{chunk-IEEY5RJV.js → chunk-LZE2QZCT.js} +1 -1
  30. package/dist/{chunk-DHC4E2DU.cjs → chunk-MITQGHN3.cjs} +121 -8
  31. package/dist/{chunk-75SYKUOT.js → chunk-N4MCWQZ7.js} +1 -1
  32. package/dist/{chunk-G5TCMKX5.js → chunk-NJLPW4MM.js} +17 -6
  33. package/dist/{chunk-STLTOISW.cjs → chunk-NPPMKMCU.cjs} +17 -17
  34. package/dist/{chunk-OLZXXAER.cjs → chunk-PEQWARCP.cjs} +408 -250
  35. package/dist/{chunk-ZM7IKQ4X.js → chunk-PPDUCZZV.js} +152 -10
  36. package/dist/{chunk-RAPNRK4R.cjs → chunk-QBZEC2JJ.cjs} +88 -88
  37. package/dist/{chunk-RJGW3RQS.js → chunk-ROURHJYT.js} +1 -1
  38. package/dist/{chunk-62MFUHSI.cjs → chunk-UQXULAMQ.cjs} +16 -16
  39. package/dist/{chunk-YYZC2EQO.cjs → chunk-VNF7A2JA.cjs} +23 -23
  40. package/dist/{chunk-C6VB7CPY.cjs → chunk-WFSYZO5Z.cjs} +1 -1
  41. package/dist/image-editor/index.cjs +880 -852
  42. package/dist/image-editor/index.js +36 -8
  43. package/dist/paint/index.cjs +33 -5
  44. package/dist/paint/index.d.cts +1 -0
  45. package/dist/paint/index.d.ts +1 -0
  46. package/dist/paint/index.js +32 -4
  47. package/dist/paint/live-patterns.d.cts +149 -0
  48. package/dist/paint/live-patterns.d.ts +149 -0
  49. package/dist/paint/wet/world.d.cts +13 -0
  50. package/dist/paint/wet/world.d.ts +13 -0
  51. package/dist/paint-editor/brush-model.d.cts +21 -1
  52. package/dist/paint-editor/brush-model.d.ts +21 -1
  53. package/dist/paint-editor/controller.d.cts +102 -1
  54. package/dist/paint-editor/controller.d.ts +102 -1
  55. package/dist/paint-editor/document.d.cts +8 -1
  56. package/dist/paint-editor/document.d.ts +8 -1
  57. package/dist/paint-editor/ferro-tools.d.cts +10 -0
  58. package/dist/paint-editor/ferro-tools.d.ts +10 -0
  59. package/dist/paint-editor/ferro.d.cts +134 -0
  60. package/dist/paint-editor/ferro.d.ts +134 -0
  61. package/dist/paint-editor/index.cjs +2291 -904
  62. package/dist/paint-editor/index.d.cts +2 -0
  63. package/dist/paint-editor/index.d.ts +2 -0
  64. package/dist/paint-editor/index.js +1533 -146
  65. package/dist/paint-editor/stroke.d.cts +14 -0
  66. package/dist/paint-editor/stroke.d.ts +14 -0
  67. package/dist/paint-editor/thick.d.cts +15 -2
  68. package/dist/paint-editor/thick.d.ts +15 -2
  69. package/dist/paint-editor/ui.d.cts +7 -1
  70. package/dist/paint-editor/ui.d.ts +7 -1
  71. package/dist/paint-editor/wet-tools.d.cts +42 -0
  72. package/dist/paint-editor/wet-tools.d.ts +42 -0
  73. package/dist/paint-editor/wet.d.cts +22 -1
  74. package/dist/paint-editor/wet.d.ts +22 -1
  75. package/dist/vector/index.cjs +1002 -1002
  76. package/dist/vector/index.js +7 -7
  77. package/package.json +1 -1
  78. package/skills/pixerate-brushes/SKILL.md +1 -0
  79. package/skills/pixerate-paint-editor/SKILL.md +11 -29
  80. package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
  81. package/skills/pixerate-paint-editor/references/ferrofluid.md +36 -0
@@ -8,7 +8,7 @@ import {
8
8
  listPresets,
9
9
  normalizeBrushSpec,
10
10
  resolveBrush
11
- } from "./chunk-ZM7IKQ4X.js";
11
+ } from "./chunk-PPDUCZZV.js";
12
12
  import {
13
13
  editorStampOf,
14
14
  stampCoverage
@@ -74,17 +74,18 @@ import {
74
74
  normalizeTilt,
75
75
  normalizeWetParams,
76
76
  pigmentOf
77
- } from "./chunk-LMG57L5A.js";
77
+ } from "./chunk-CTHWXW5M.js";
78
78
  import {
79
79
  BrushstrokeCache,
80
80
  brushstrokeDataUrl,
81
81
  brushstrokeSamples,
82
82
  isBrushstrokeBrush
83
- } from "./chunk-W6JDUXP3.js";
83
+ } from "./chunk-J23JOHPR.js";
84
84
  import {
85
85
  CoverageMap,
86
86
  EMPTY_MIXER_LOAD,
87
87
  GRAIN_PATTERNS,
88
+ LivePatternField,
88
89
  PaintExchange,
89
90
  PaintGradientSampler,
90
91
  PaperGrain,
@@ -97,12 +98,14 @@ import {
97
98
  grainCatch,
98
99
  loadMixer,
99
100
  mixerRates,
101
+ moveLivePatternSnapshot,
102
+ normalizeLivePatterns,
100
103
  parsePaintSource,
101
104
  rasterizeDab,
102
105
  readPaintSource,
103
106
  resolvePaintGradient,
104
107
  scalePaintGradient
105
- } from "./chunk-ACTHSQOI.js";
108
+ } from "./chunk-LDZOXESA.js";
106
109
  import {
107
110
  rand01,
108
111
  randSigned
@@ -3914,6 +3917,17 @@ var WetStrokeDriver = class {
3914
3917
  } else world.paint(laneId, rect, cov, this.medium.options.water * k, null);
3915
3918
  ex.drain();
3916
3919
  if (wet.rewet > 0) world.rewet(laneId, rect, cov, wet.rewet * k * 0.15, this.init.paperColor);
3920
+ if (brush.livePatterns) {
3921
+ world.seedLivePatterns(
3922
+ laneId,
3923
+ d.x,
3924
+ d.y,
3925
+ Math.max(2, d.radius),
3926
+ this.init.seed + this.dabCount * 131 + copy * 997 | 0,
3927
+ Math.max(0.65, d.pressure),
3928
+ brush.livePatterns
3929
+ );
3930
+ }
3917
3931
  break;
3918
3932
  }
3919
3933
  case "water": {
@@ -3966,6 +3980,7 @@ var PaintWetLayers = class {
3966
3980
  world = null;
3967
3981
  tilt = { ...FLAT };
3968
3982
  settings = /* @__PURE__ */ new Map();
3983
+ patterns = /* @__PURE__ */ new Map();
3969
3984
  paused = false;
3970
3985
  /** Wet strokes dry as soon as they end (one undo step with the stroke). */
3971
3986
  instantDry = false;
@@ -4072,6 +4087,14 @@ var PaintWetLayers = class {
4072
4087
  extend(width, height, left2, top, paper) {
4073
4088
  if (!this.canExtend(left2, top)) throw new Error("Wet paint only moves by whole 256 px tiles, between strokes.");
4074
4089
  for (const [id, s] of this.settings) if (s.flow.center) this.settings.set(id, { ...s, flow: { ...s.flow, center: { x: s.flow.center.x + left2, y: s.flow.center.y + top } } });
4090
+ const tx0 = left2 / WET_TILE;
4091
+ const ty0 = top / WET_TILE;
4092
+ for (const [id, f] of this.patterns) {
4093
+ const moved2 = moveLivePatternSnapshot(f.snapshot(), { width, height }, tx0, ty0);
4094
+ const next = new LivePatternField(width, height);
4095
+ next.restore(moved2);
4096
+ this.patterns.set(id, next);
4097
+ }
4075
4098
  const old = this.world;
4076
4099
  const snap = old?.laneIds().length ? old.snapshot() : null;
4077
4100
  this.dropWorld();
@@ -4088,6 +4111,7 @@ var PaintWetLayers = class {
4088
4111
  });
4089
4112
  this.world = this.makeWorld(width, height, paper);
4090
4113
  this.world.restore(moved);
4114
+ for (const id of this.world.laneIds()) this.patterns.set(id, this.world.livePatternField(id));
4091
4115
  }
4092
4116
  /** Whether any layer (or `layerId`) has wet paint. */
4093
4117
  isWet(layerId) {
@@ -4111,18 +4135,32 @@ var PaintWetLayers = class {
4111
4135
  settingsOf(layerId, medium = "watercolor") {
4112
4136
  let s = this.settings.get(layerId);
4113
4137
  if (!s) {
4114
- s = { params: normalizeWetParams(WET_MEDIUM_PARAMS[medium], DEFAULT_WET_PARAMS), flow: { ...DEFAULT_FLOW } };
4138
+ s = { params: normalizeWetParams(WET_MEDIUM_PARAMS[medium], DEFAULT_WET_PARAMS), flow: { ...DEFAULT_FLOW }, livePatterns: null };
4115
4139
  this.settings.set(layerId, s);
4116
4140
  }
4117
4141
  return s;
4118
4142
  }
4143
+ patternFieldOf(layerId) {
4144
+ const { width, height } = this.host.canvas();
4145
+ let f = this.patterns.get(layerId);
4146
+ if (!f) {
4147
+ f = new LivePatternField(width, height);
4148
+ const s = this.settings.get(layerId)?.livePatterns;
4149
+ if (s) f.configure(s);
4150
+ this.patterns.set(layerId, f);
4151
+ } else if (f.width !== width || f.height !== height) {
4152
+ f.resize(width, height);
4153
+ }
4154
+ return f;
4155
+ }
4119
4156
  /** Puts a layer in the world (its current pixels become the base under the wet paint). */
4120
4157
  ensureLane(layerId, medium) {
4121
4158
  const w = this.ensureWorld();
4159
+ const lp = this.patternFieldOf(layerId);
4122
4160
  if (!w.hasLane(layerId)) {
4123
4161
  const base = this.host.layerSurface(layerId) ?? new TiledSurface(4, /* @__PURE__ */ new Map(), 0);
4124
4162
  const s = this.settingsOf(layerId, medium);
4125
- w.ensureLane(layerId, { params: s.params, base, light: this.host.layerLight(layerId), ghost: s.ghost });
4163
+ w.ensureLane(layerId, { params: s.params, base, light: this.host.layerLight(layerId), ghost: s.ghost, livePatterns: lp });
4126
4164
  const { width, height } = this.host.canvas();
4127
4165
  if (s.flow.kind !== "none") w.setFlow(layerId, flowField(s.flow, width, height), s.flow.strength);
4128
4166
  }
@@ -4143,6 +4181,65 @@ var PaintWetLayers = class {
4143
4181
  }
4144
4182
  return { ...s.flow };
4145
4183
  }
4184
+ setLivePatterns(layerId, update) {
4185
+ const s = this.settingsOf(layerId);
4186
+ const f = this.patternFieldOf(layerId);
4187
+ if (update === null) {
4188
+ f.clear();
4189
+ s.livePatterns = null;
4190
+ if (this.world?.hasLane(layerId)) this.syncDisplays();
4191
+ return null;
4192
+ }
4193
+ const next = this.world?.hasLane(layerId) ? this.world.setLivePatterns(layerId, update) : f.configure(update);
4194
+ s.livePatterns = { ...next };
4195
+ this.syncDisplays();
4196
+ return { ...next };
4197
+ }
4198
+ seedLivePatterns(layerId, points, radius, seed = 1, settings) {
4199
+ const s = this.settingsOf(layerId);
4200
+ const f = this.patternFieldOf(layerId);
4201
+ if (settings) {
4202
+ const next = this.world?.hasLane(layerId) ? this.world.setLivePatterns(layerId, settings) : f.configure(settings);
4203
+ s.livePatterns = { ...next };
4204
+ } else if (!f.enabled) {
4205
+ const next = this.world?.hasLane(layerId) ? this.world.setLivePatterns(layerId, s.livePatterns ?? {}) : f.configure(s.livePatterns ?? {});
4206
+ s.livePatterns = { ...next };
4207
+ }
4208
+ let seededCells = 0;
4209
+ const hasLane = Boolean(this.world?.hasLane(layerId));
4210
+ for (let i = 0; i < points.length; i++) {
4211
+ const p = points[i];
4212
+ const str = p.strength ?? 1;
4213
+ const ptSeed = seed + i * 7919 | 0;
4214
+ if (hasLane) {
4215
+ seededCells += this.world.seedLivePatterns(layerId, p.x, p.y, radius, ptSeed, str);
4216
+ } else {
4217
+ seededCells += f.seedDisc(p.x, p.y, radius, ptSeed, str);
4218
+ }
4219
+ }
4220
+ this.syncDisplays();
4221
+ return { seededCells, stats: this.livePatternStats(layerId) };
4222
+ }
4223
+ livePatternStats(layerId) {
4224
+ const f = this.patterns.get(layerId);
4225
+ if (f?.enabled) {
4226
+ const s = this.settingsOf(layerId);
4227
+ if (!s.livePatterns) s.livePatterns = { ...f.settings };
4228
+ }
4229
+ if (this.world?.hasLane(layerId)) return this.world.livePatternStats(layerId);
4230
+ if (f) return f.stats(() => false);
4231
+ const cfg = this.settings.get(layerId)?.livePatterns ?? null;
4232
+ return {
4233
+ enabled: Boolean(cfg),
4234
+ settings: normalizeLivePatterns(cfg ?? {}),
4235
+ activeTiles: 0,
4236
+ patternCells: 0,
4237
+ wetPatternCells: 0,
4238
+ meanV: 0,
4239
+ maxV: 0,
4240
+ bounds: null
4241
+ };
4242
+ }
4146
4243
  setTilt(update) {
4147
4244
  this.tilt = normalizeTilt(update, this.tilt);
4148
4245
  this.world?.setTilt(this.tilt);
@@ -4155,6 +4252,8 @@ var PaintWetLayers = class {
4155
4252
  /** A layer as tools describe it. */
4156
4253
  info(layerId) {
4157
4254
  const s = this.settingsOf(layerId);
4255
+ const f = this.patterns.get(layerId);
4256
+ if (f?.enabled && !s.livePatterns) s.livePatterns = { ...f.settings };
4158
4257
  const st = this.world?.hasLane(layerId) ? this.world.stats(layerId) : null;
4159
4258
  const g = this.world?.hasLane(layerId) ? this.world.getGhost(layerId) : s.ghost ?? null;
4160
4259
  return {
@@ -4165,6 +4264,7 @@ var PaintWetLayers = class {
4165
4264
  activeTiles: st?.activeTiles ?? 0,
4166
4265
  params: { ...s.params },
4167
4266
  flow: { ...s.flow },
4267
+ livePatterns: s.livePatterns ? { ...s.livePatterns } : null,
4168
4268
  ...g ? { ghost: { remaining: Math.round(g.remaining * FREE_INTERVAL / this.speed * 100) / 100 } } : {}
4169
4269
  };
4170
4270
  }
@@ -4174,10 +4274,13 @@ var PaintWetLayers = class {
4174
4274
  /** Shows the current wet pixels (at most `maxTiles` reshaded per layer). */
4175
4275
  syncDisplays(maxTiles = Infinity) {
4176
4276
  const w = this.world;
4277
+ this.syncHeights();
4177
4278
  if (!w) return [];
4178
4279
  const surfaces = /* @__PURE__ */ new Map();
4179
4280
  const rects = [];
4180
4281
  for (const id of w.laneIds()) {
4282
+ const f = this.patterns.get(id);
4283
+ if (f?.enabled) this.settingsOf(id).livePatterns = { ...f.settings };
4181
4284
  const d = w.display(id, maxTiles);
4182
4285
  if (this.host.layerSurface(id) !== d.surface) surfaces.set(id, d.surface);
4183
4286
  rects.push(...d.changed);
@@ -4189,10 +4292,13 @@ var PaintWetLayers = class {
4189
4292
  /** Like `syncDisplays`, shading on the GPU when Engine B is active (all stale tiles at once). */
4190
4293
  async syncDisplaysGpu(maxCpuTiles, lead = false) {
4191
4294
  const w = this.world;
4295
+ this.syncHeights();
4192
4296
  if (!w) return [];
4193
4297
  const surfaces = /* @__PURE__ */ new Map();
4194
4298
  const rects = [];
4195
4299
  for (const id of w.laneIds()) {
4300
+ const f = this.patterns.get(id);
4301
+ if (f?.enabled) this.settingsOf(id).livePatterns = { ...f.settings };
4196
4302
  const d = await w.displayGpu(id, maxCpuTiles, lead);
4197
4303
  if (!w.hasLane(id)) continue;
4198
4304
  if (this.host.layerSurface(id) !== d.surface) surfaces.set(id, d.surface);
@@ -4202,6 +4308,18 @@ var PaintWetLayers = class {
4202
4308
  if (surfaces.size) this.host.updateSurfaces(surfaces, rects);
4203
4309
  return rects;
4204
4310
  }
4311
+ /** Updates thick height maps for any layers with live pattern relief (`relief > 0`). */
4312
+ syncHeights() {
4313
+ if (!this.host.layerHeight || !this.host.updateHeights) return;
4314
+ const heights = /* @__PURE__ */ new Map();
4315
+ for (const [id, f] of this.patterns) {
4316
+ if (!f.enabled || !(f.settings.relief > 0) || f.tiles.size === 0) continue;
4317
+ const cur = this.host.layerHeight(id) ?? new TiledSurface(1, /* @__PURE__ */ new Map(), 0);
4318
+ const next = f.applyReliefHeight(cur);
4319
+ if (next !== cur) heights.set(id, next);
4320
+ }
4321
+ if (heights.size) this.host.updateHeights(heights);
4322
+ }
4205
4323
  /** Shows each wet layer's live light paint (the light under it with the wet light shaded in). */
4206
4324
  syncLights() {
4207
4325
  const w = this.world;
@@ -4376,18 +4494,57 @@ var PaintWetLayers = class {
4376
4494
  /** A snapshot of the world for undo (`null` when there is none). */
4377
4495
  snapshot() {
4378
4496
  const w = this.world;
4379
- if (!w || !w.laneIds().length) return null;
4380
- return w.snapshot();
4497
+ const patterns = /* @__PURE__ */ new Map();
4498
+ let patternBytes = 0;
4499
+ for (const [id, f] of this.patterns) {
4500
+ if (!f.enabled && f.tiles.size === 0) continue;
4501
+ const ps = f.snapshot();
4502
+ patterns.set(id, ps);
4503
+ patternBytes += ps.bytes;
4504
+ }
4505
+ if (!w || !w.laneIds().length) {
4506
+ if (!patterns.size) return null;
4507
+ return {
4508
+ clock: w?.clock ?? 0,
4509
+ tilt: { ...this.tilt },
4510
+ lanes: [],
4511
+ slots: /* @__PURE__ */ new Map(),
4512
+ patterns,
4513
+ bytes: patternBytes
4514
+ };
4515
+ }
4516
+ const snap = w.snapshot();
4517
+ return patterns.size ? { ...snap, patterns } : snap;
4381
4518
  }
4382
4519
  /** Puts a snapshot back (`null`: no wet layers). Returns the layer surfaces it implies. */
4383
4520
  restore(snap) {
4521
+ this.patterns.clear();
4522
+ for (const [, s] of this.settings) s.livePatterns = null;
4384
4523
  if (!snap) {
4385
4524
  if (this.world) for (const id of this.world.laneIds()) this.world.removeLane(id);
4386
4525
  return;
4387
4526
  }
4388
4527
  const { width, height } = this.host.canvas();
4389
4528
  if (this.world && (this.world.width !== width || this.world.height !== height)) this.dropWorld();
4390
- this.ensureWorld().restore(snap);
4529
+ if (snap.patterns) {
4530
+ for (const [id, ps] of snap.patterns) {
4531
+ const f = new LivePatternField(width, height);
4532
+ f.restore(ps);
4533
+ this.patterns.set(id, f);
4534
+ this.settingsOf(id).livePatterns = f.enabled ? { ...f.settings } : null;
4535
+ }
4536
+ }
4537
+ if (snap.lanes.length) {
4538
+ const w = this.ensureWorld();
4539
+ w.restore(snap);
4540
+ for (const id of w.laneIds()) {
4541
+ const f = w.livePatternField(id);
4542
+ this.patterns.set(id, f);
4543
+ this.settingsOf(id).livePatterns = f.enabled ? { ...f.settings } : null;
4544
+ }
4545
+ } else if (this.world) {
4546
+ for (const id of this.world.laneIds()) this.world.removeLane(id);
4547
+ }
4391
4548
  this.syncDisplays();
4392
4549
  }
4393
4550
  dropWorld() {
@@ -4401,6 +4558,7 @@ var PaintWetLayers = class {
4401
4558
  this.world = null;
4402
4559
  this.driver = null;
4403
4560
  this.settings.clear();
4561
+ this.patterns.clear();
4404
4562
  this.lastTick = null;
4405
4563
  this.stepsOwed = 0;
4406
4564
  }