@pixerate/editor 0.276.0 → 0.278.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 (65) hide show
  1. package/dist/animation-editor/index.cjs +59 -59
  2. package/dist/animation-editor/index.js +3 -3
  3. package/dist/brushes/index.cjs +69 -67
  4. package/dist/brushes/index.js +6 -4
  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 +2 -0
  8. package/dist/brushes/profiles.d.ts +2 -0
  9. package/dist/brushes/schema.d.cts +1 -0
  10. package/dist/brushes/schema.d.ts +1 -0
  11. package/dist/brushes/types.d.cts +10 -0
  12. package/dist/brushes/types.d.ts +10 -0
  13. package/dist/{brushstrokeRender-I6MYFCOU.cjs → brushstrokeRender-7HOMKZNO.cjs} +15 -15
  14. package/dist/{brushstrokeRender-FK3ASUUJ.js → brushstrokeRender-EIK3625T.js} +3 -3
  15. package/dist/canvas/index.cjs +13 -13
  16. package/dist/canvas/index.js +1 -1
  17. package/dist/{chunk-U26LUXCJ.cjs → chunk-4NZTK7LM.cjs} +35 -35
  18. package/dist/{chunk-CVBTJVRN.js → chunk-DWPFGWW5.js} +1 -1
  19. package/dist/{chunk-XMSH66VU.js → chunk-FNIWHSLI.js} +1 -1
  20. package/dist/{chunk-QNNZA3V5.cjs → chunk-H3EME3S5.cjs} +2 -2
  21. package/dist/{chunk-4EM5NGCL.js → chunk-HHX7ST6Z.js} +1 -1
  22. package/dist/{chunk-Q33V5PEF.cjs → chunk-HRUK5TFW.cjs} +73 -12
  23. package/dist/{chunk-5HR5CSLC.cjs → chunk-JVFGK3DK.cjs} +69 -58
  24. package/dist/{chunk-JNMNM3SL.js → chunk-LZE2QZCT.js} +1 -1
  25. package/dist/{chunk-OS6OG7DN.js → chunk-N4MCWQZ7.js} +1 -1
  26. package/dist/{chunk-IJ6NSWKM.js → chunk-NJLPW4MM.js} +17 -6
  27. package/dist/{chunk-OY24XRYF.cjs → chunk-PEQWARCP.cjs} +12 -12
  28. package/dist/{chunk-XVPQNX7O.js → chunk-PPDUCZZV.js} +66 -5
  29. package/dist/{chunk-X7XBEX3O.cjs → chunk-QBZEC2JJ.cjs} +86 -86
  30. package/dist/{chunk-GHYJAMMS.js → chunk-ROURHJYT.js} +1 -1
  31. package/dist/{chunk-ZIE6EJGR.cjs → chunk-VNF7A2JA.cjs} +23 -23
  32. package/dist/{chunk-ISCAJBPQ.cjs → chunk-WFSYZO5Z.cjs} +1 -1
  33. package/dist/image-editor/index.cjs +788 -788
  34. package/dist/image-editor/index.js +4 -4
  35. package/dist/paint-editor/brush-model.d.cts +15 -1
  36. package/dist/paint-editor/brush-model.d.ts +15 -1
  37. package/dist/paint-editor/controller.d.cts +90 -9
  38. package/dist/paint-editor/controller.d.ts +90 -9
  39. package/dist/paint-editor/document.d.cts +8 -1
  40. package/dist/paint-editor/document.d.ts +8 -1
  41. package/dist/paint-editor/ferro-tools.d.cts +10 -0
  42. package/dist/paint-editor/ferro-tools.d.ts +10 -0
  43. package/dist/paint-editor/ferro.d.cts +134 -0
  44. package/dist/paint-editor/ferro.d.ts +134 -0
  45. package/dist/paint-editor/index.cjs +2138 -872
  46. package/dist/paint-editor/index.d.cts +2 -0
  47. package/dist/paint-editor/index.d.ts +2 -0
  48. package/dist/paint-editor/index.js +1446 -180
  49. package/dist/paint-editor/serialize.d.cts +3 -1
  50. package/dist/paint-editor/serialize.d.ts +3 -1
  51. package/dist/paint-editor/stroke.d.cts +17 -0
  52. package/dist/paint-editor/stroke.d.ts +17 -0
  53. package/dist/paint-editor/strokes.d.cts +11 -1
  54. package/dist/paint-editor/strokes.d.ts +11 -1
  55. package/dist/paint-editor/thick.d.cts +15 -2
  56. package/dist/paint-editor/thick.d.ts +15 -2
  57. package/dist/paint-editor/ui.d.cts +2 -0
  58. package/dist/paint-editor/ui.d.ts +2 -0
  59. package/dist/vector/index.cjs +998 -998
  60. package/dist/vector/index.js +4 -4
  61. package/package.json +1 -1
  62. package/skills/pixerate-brushes/SKILL.md +1 -0
  63. package/skills/pixerate-paint-editor/SKILL.md +12 -30
  64. package/skills/pixerate-paint-editor/references/brush-facets.md +1 -0
  65. package/skills/pixerate-paint-editor/references/ferrofluid.md +36 -0
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Ferrofluid paint: a per-layer liquid ferrofluid channel (`doc.ferro`, one byte 0..255 per pixel
3
+ * in layer space, like `doc.light`) and document-space magnets (`doc.magnets`).
4
+ *
5
+ * Where the analytical dipole magnetic field `|B|` exceeds the critical threshold `Bc` and
6
+ * `ferro > 0`, the liquid rises into a hexagonal Rosensweig spike relief field whose spacing
7
+ * follows the capillary wavelength, whose amplitude scales as `sqrt(1 - Bc / |B|) * ferro`, and
8
+ * whose peaks tilt along `B`. Adding or moving a magnet runs deterministic advection steps on
9
+ * `doc.ferro` so the liquid gathers toward the magnets.
10
+ *
11
+ * At render time, the spike height combines with `doc.thick` height and defaults to a dark,
12
+ * glossy metallic material where `ferro > 0`, so `lightTile` shades the spikes with the canvas
13
+ * lighting. `cure_ferrofluid` bakes the current spikes into `doc.thick` and clears `doc.ferro`.
14
+ */
15
+ import { TiledSurface, type RasterRect } from '../raster/surface.cjs';
16
+ import { DEFAULT_FERRO, normalizeFerro as normalizeFerroSettings, type FerroSettings, type PaintBrush } from './brushes.cjs';
17
+ import { type ThickChannels } from './thick.cjs';
18
+ export { DEFAULT_FERRO, normalizeFerroSettings, type FerroSettings };
19
+ /** A dipole magnet on the canvas (document pixel coordinates). */
20
+ export interface PaintMagnet {
21
+ id: string;
22
+ x: number;
23
+ y: number;
24
+ /** Dipole strength and polarity (-1 .. 1). */
25
+ strength: number;
26
+ /** Characteristic dipole falloff radius in canvas pixels (8 .. 2000). */
27
+ radius: number;
28
+ }
29
+ export declare const DEFAULT_MAGNET_STRENGTH = 0.75;
30
+ export declare const DEFAULT_MAGNET_RADIUS = 72;
31
+ /** Dark carrier fluid sRGB colour (`#111827`) for advected ferrofluid outside existing pigment. */
32
+ export declare const FERRO_CARRIER_RGB: readonly [number, number, number];
33
+ /** Default glossy metallic material for ferrofluid (`gloss ≈ 0.88`, `metallic ≈ 0.65`, `rim ≈ 0.25`). */
34
+ export declare const FERRO_DEFAULT_GLOSS = 224;
35
+ export declare const FERRO_DEFAULT_METALLIC = 166;
36
+ export declare const FERRO_DEFAULT_RIM = 64;
37
+ /** Validates and normalizes a magnet; throws a descriptive Error when a field is out of range. */
38
+ export declare function normalizeMagnet(raw: Partial<PaintMagnet> & {
39
+ id: string;
40
+ x: number;
41
+ y: number;
42
+ }, base?: PaintMagnet): PaintMagnet;
43
+ export declare function emptyFerro(): TiledSurface;
44
+ /** The ferrofluid channel as grey paint for a stroke's twin: white, with alpha = ferro. */
45
+ export declare function ferroToShadow(ferro: TiledSurface): TiledSurface;
46
+ /** A twin's grey paint back as ferrofluid: grey × alpha. */
47
+ export declare function shadowToFerro(shadow: TiledSurface): TiledSurface;
48
+ /** The brush a stroke's ferrofluid twin paints with: same geometry, normal blend, RGB mixing. */
49
+ export declare function ferroTwinBrush(brush: PaintBrush): PaintBrush;
50
+ /** Ferrofluid scaled by `k` (0..1), e.g. under a fill that covers it. */
51
+ export declare function scaleFerro(ferro: TiledSurface, k: number): TiledSurface;
52
+ /** Ferrofluid of an upper layer merged down onto the one below (both in lower layer's space). */
53
+ export declare function mergeFerro(lower: TiledSurface | undefined, upper: TiledSurface | undefined, upperColor: TiledSurface): TiledSurface | undefined;
54
+ export interface FerroFieldSample {
55
+ bx: number;
56
+ by: number;
57
+ bz: number;
58
+ magnitude: number;
59
+ }
60
+ /**
61
+ * Analytical 3D magnetic dipole field `B = (bx, by, bz)` at canvas point `(x, y)` from a set of
62
+ * dipole magnets placed on the canvas plane (`z = -radius`).
63
+ */
64
+ export declare function ferroFieldAt(x: number, y: number, magnets: readonly PaintMagnet[]): FerroFieldSample;
65
+ /**
66
+ * Hexagonal Rosensweig instability spike height (0..255) at canvas coordinate `(x, y)` for a pixel
67
+ * with liquid ferrofluid byte `ferroByte` (0..255).
68
+ *
69
+ * Where `|B| > threshold` and `ferroByte > 0`, spikes rise with amplitude `∝ sqrt(1 - Bc / |B|) * ferro`
70
+ * at the capillary `wavelength` and tilt along the tangential field `(bx, by)`.
71
+ */
72
+ export declare function ferroSpikeHeightAt(x: number, y: number, ferroByte: number, magnets: readonly PaintMagnet[], settings?: FerroSettings): number;
73
+ /**
74
+ * Runs deterministic mass-gathering advection steps on a layer's `ferro` surface toward `magnets`.
75
+ * Each step transports liquid ferrofluid along the dipole attraction field via bilinear splatting.
76
+ */
77
+ export declare function advectFerro(ferro: TiledSurface, magnets: readonly PaintMagnet[], offset?: {
78
+ x: number;
79
+ y: number;
80
+ }, steps?: number): TiledSurface;
81
+ export interface FerroStats {
82
+ bounds: RasterRect | null;
83
+ peak: number;
84
+ liquidPixels: number;
85
+ maxSpikeHeight: number;
86
+ spikePixels: number;
87
+ magnetCount: number;
88
+ }
89
+ /**
90
+ * Inspects a layer's liquid ferrofluid channel and active Rosensweig spike relief under `magnets`.
91
+ */
92
+ export declare function ferroInfo(ferro: TiledSurface | undefined, magnets?: readonly PaintMagnet[], settings?: FerroSettings, offset?: {
93
+ x: number;
94
+ y: number;
95
+ }): FerroStats;
96
+ export interface FerroLayerInput {
97
+ surface: TiledSurface;
98
+ magnets: readonly PaintMagnet[];
99
+ settings: FerroSettings;
100
+ offset: {
101
+ x: number;
102
+ y: number;
103
+ };
104
+ }
105
+ export declare function ferroCacheKey(ferro: FerroLayerInput | null | undefined): string;
106
+ /**
107
+ * Bakes a layer's liquid ferrofluid and active Rosensweig spikes into permanent `ThickChannels`
108
+ * (and ensures carrier colour covers advected ferrofluid pixels) for `cure_ferrofluid`.
109
+ */
110
+ export declare function bakeFerroIntoThick(color: TiledSurface, ferro: TiledSurface, thick: ThickChannels | undefined, magnets: readonly PaintMagnet[], settings?: FerroSettings, offset?: {
111
+ x: number;
112
+ y: number;
113
+ }): {
114
+ color: TiledSurface;
115
+ thick: ThickChannels;
116
+ maxSpikeHeight: number;
117
+ spikePixels: number;
118
+ curedPixels: number;
119
+ };
120
+ /** A layer's liquid ferrofluid as saved: its rect (layer space), grey PNG, and spike settings. */
121
+ export interface SavedFerro {
122
+ rect: RasterRect;
123
+ ferro: string;
124
+ wavelength?: number;
125
+ threshold?: number;
126
+ }
127
+ export declare function ferroToJSON(ferro: TiledSurface, settings?: FerroSettings): SavedFerro | null;
128
+ /** Saved form → ferrofluid surface and settings; throws a sentence when it cannot be read. */
129
+ export declare function ferroFromJSON(raw: unknown): {
130
+ surface: TiledSurface;
131
+ settings: FerroSettings;
132
+ };
133
+ /** Saved magnets array → validated `PaintMagnet[]`. */
134
+ export declare function magnetsFromJSON(raw: unknown): PaintMagnet[];
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Ferrofluid paint: a per-layer liquid ferrofluid channel (`doc.ferro`, one byte 0..255 per pixel
3
+ * in layer space, like `doc.light`) and document-space magnets (`doc.magnets`).
4
+ *
5
+ * Where the analytical dipole magnetic field `|B|` exceeds the critical threshold `Bc` and
6
+ * `ferro > 0`, the liquid rises into a hexagonal Rosensweig spike relief field whose spacing
7
+ * follows the capillary wavelength, whose amplitude scales as `sqrt(1 - Bc / |B|) * ferro`, and
8
+ * whose peaks tilt along `B`. Adding or moving a magnet runs deterministic advection steps on
9
+ * `doc.ferro` so the liquid gathers toward the magnets.
10
+ *
11
+ * At render time, the spike height combines with `doc.thick` height and defaults to a dark,
12
+ * glossy metallic material where `ferro > 0`, so `lightTile` shades the spikes with the canvas
13
+ * lighting. `cure_ferrofluid` bakes the current spikes into `doc.thick` and clears `doc.ferro`.
14
+ */
15
+ import { TiledSurface, type RasterRect } from '../raster/surface.js';
16
+ import { DEFAULT_FERRO, normalizeFerro as normalizeFerroSettings, type FerroSettings, type PaintBrush } from './brushes.js';
17
+ import { type ThickChannels } from './thick.js';
18
+ export { DEFAULT_FERRO, normalizeFerroSettings, type FerroSettings };
19
+ /** A dipole magnet on the canvas (document pixel coordinates). */
20
+ export interface PaintMagnet {
21
+ id: string;
22
+ x: number;
23
+ y: number;
24
+ /** Dipole strength and polarity (-1 .. 1). */
25
+ strength: number;
26
+ /** Characteristic dipole falloff radius in canvas pixels (8 .. 2000). */
27
+ radius: number;
28
+ }
29
+ export declare const DEFAULT_MAGNET_STRENGTH = 0.75;
30
+ export declare const DEFAULT_MAGNET_RADIUS = 72;
31
+ /** Dark carrier fluid sRGB colour (`#111827`) for advected ferrofluid outside existing pigment. */
32
+ export declare const FERRO_CARRIER_RGB: readonly [number, number, number];
33
+ /** Default glossy metallic material for ferrofluid (`gloss ≈ 0.88`, `metallic ≈ 0.65`, `rim ≈ 0.25`). */
34
+ export declare const FERRO_DEFAULT_GLOSS = 224;
35
+ export declare const FERRO_DEFAULT_METALLIC = 166;
36
+ export declare const FERRO_DEFAULT_RIM = 64;
37
+ /** Validates and normalizes a magnet; throws a descriptive Error when a field is out of range. */
38
+ export declare function normalizeMagnet(raw: Partial<PaintMagnet> & {
39
+ id: string;
40
+ x: number;
41
+ y: number;
42
+ }, base?: PaintMagnet): PaintMagnet;
43
+ export declare function emptyFerro(): TiledSurface;
44
+ /** The ferrofluid channel as grey paint for a stroke's twin: white, with alpha = ferro. */
45
+ export declare function ferroToShadow(ferro: TiledSurface): TiledSurface;
46
+ /** A twin's grey paint back as ferrofluid: grey × alpha. */
47
+ export declare function shadowToFerro(shadow: TiledSurface): TiledSurface;
48
+ /** The brush a stroke's ferrofluid twin paints with: same geometry, normal blend, RGB mixing. */
49
+ export declare function ferroTwinBrush(brush: PaintBrush): PaintBrush;
50
+ /** Ferrofluid scaled by `k` (0..1), e.g. under a fill that covers it. */
51
+ export declare function scaleFerro(ferro: TiledSurface, k: number): TiledSurface;
52
+ /** Ferrofluid of an upper layer merged down onto the one below (both in lower layer's space). */
53
+ export declare function mergeFerro(lower: TiledSurface | undefined, upper: TiledSurface | undefined, upperColor: TiledSurface): TiledSurface | undefined;
54
+ export interface FerroFieldSample {
55
+ bx: number;
56
+ by: number;
57
+ bz: number;
58
+ magnitude: number;
59
+ }
60
+ /**
61
+ * Analytical 3D magnetic dipole field `B = (bx, by, bz)` at canvas point `(x, y)` from a set of
62
+ * dipole magnets placed on the canvas plane (`z = -radius`).
63
+ */
64
+ export declare function ferroFieldAt(x: number, y: number, magnets: readonly PaintMagnet[]): FerroFieldSample;
65
+ /**
66
+ * Hexagonal Rosensweig instability spike height (0..255) at canvas coordinate `(x, y)` for a pixel
67
+ * with liquid ferrofluid byte `ferroByte` (0..255).
68
+ *
69
+ * Where `|B| > threshold` and `ferroByte > 0`, spikes rise with amplitude `∝ sqrt(1 - Bc / |B|) * ferro`
70
+ * at the capillary `wavelength` and tilt along the tangential field `(bx, by)`.
71
+ */
72
+ export declare function ferroSpikeHeightAt(x: number, y: number, ferroByte: number, magnets: readonly PaintMagnet[], settings?: FerroSettings): number;
73
+ /**
74
+ * Runs deterministic mass-gathering advection steps on a layer's `ferro` surface toward `magnets`.
75
+ * Each step transports liquid ferrofluid along the dipole attraction field via bilinear splatting.
76
+ */
77
+ export declare function advectFerro(ferro: TiledSurface, magnets: readonly PaintMagnet[], offset?: {
78
+ x: number;
79
+ y: number;
80
+ }, steps?: number): TiledSurface;
81
+ export interface FerroStats {
82
+ bounds: RasterRect | null;
83
+ peak: number;
84
+ liquidPixels: number;
85
+ maxSpikeHeight: number;
86
+ spikePixels: number;
87
+ magnetCount: number;
88
+ }
89
+ /**
90
+ * Inspects a layer's liquid ferrofluid channel and active Rosensweig spike relief under `magnets`.
91
+ */
92
+ export declare function ferroInfo(ferro: TiledSurface | undefined, magnets?: readonly PaintMagnet[], settings?: FerroSettings, offset?: {
93
+ x: number;
94
+ y: number;
95
+ }): FerroStats;
96
+ export interface FerroLayerInput {
97
+ surface: TiledSurface;
98
+ magnets: readonly PaintMagnet[];
99
+ settings: FerroSettings;
100
+ offset: {
101
+ x: number;
102
+ y: number;
103
+ };
104
+ }
105
+ export declare function ferroCacheKey(ferro: FerroLayerInput | null | undefined): string;
106
+ /**
107
+ * Bakes a layer's liquid ferrofluid and active Rosensweig spikes into permanent `ThickChannels`
108
+ * (and ensures carrier colour covers advected ferrofluid pixels) for `cure_ferrofluid`.
109
+ */
110
+ export declare function bakeFerroIntoThick(color: TiledSurface, ferro: TiledSurface, thick: ThickChannels | undefined, magnets: readonly PaintMagnet[], settings?: FerroSettings, offset?: {
111
+ x: number;
112
+ y: number;
113
+ }): {
114
+ color: TiledSurface;
115
+ thick: ThickChannels;
116
+ maxSpikeHeight: number;
117
+ spikePixels: number;
118
+ curedPixels: number;
119
+ };
120
+ /** A layer's liquid ferrofluid as saved: its rect (layer space), grey PNG, and spike settings. */
121
+ export interface SavedFerro {
122
+ rect: RasterRect;
123
+ ferro: string;
124
+ wavelength?: number;
125
+ threshold?: number;
126
+ }
127
+ export declare function ferroToJSON(ferro: TiledSurface, settings?: FerroSettings): SavedFerro | null;
128
+ /** Saved form → ferrofluid surface and settings; throws a sentence when it cannot be read. */
129
+ export declare function ferroFromJSON(raw: unknown): {
130
+ surface: TiledSurface;
131
+ settings: FerroSettings;
132
+ };
133
+ /** Saved magnets array → validated `PaintMagnet[]`. */
134
+ export declare function magnetsFromJSON(raw: unknown): PaintMagnet[];