@pixerate/editor 0.275.1 → 0.276.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 +62 -62
  2. package/dist/animation-editor/index.js +6 -6
  3. package/dist/brushes/index.cjs +81 -70
  4. package/dist/brushes/index.js +17 -6
  5. package/dist/brushes/patch.d.cts +1 -1
  6. package/dist/brushes/patch.d.ts +1 -1
  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 +22 -1
  12. package/dist/brushes/types.d.ts +22 -1
  13. package/dist/{brushstrokeRender-WGY4DZHF.js → brushstrokeRender-FK3ASUUJ.js} +5 -5
  14. package/dist/{brushstrokeRender-2RQL46WB.cjs → brushstrokeRender-I6MYFCOU.cjs} +21 -21
  15. package/dist/canvas/index.cjs +13 -13
  16. package/dist/canvas/index.js +1 -1
  17. package/dist/{chunk-Y2ISSVPH.js → chunk-4EM5NGCL.js} +167 -9
  18. package/dist/{chunk-Y354ETAE.cjs → chunk-5HR5CSLC.cjs} +49 -49
  19. package/dist/{chunk-LMG57L5A.js → chunk-CTHWXW5M.js} +121 -8
  20. package/dist/{chunk-43XJC3PP.js → chunk-CVBTJVRN.js} +1 -1
  21. package/dist/{chunk-RJGW3RQS.js → chunk-GHYJAMMS.js} +1 -1
  22. package/dist/{chunk-NIBFKXMD.cjs → chunk-IFWLZZCW.cjs} +619 -2
  23. package/dist/{chunk-G5TCMKX5.js → chunk-IJ6NSWKM.js} +2 -2
  24. package/dist/{chunk-C6VB7CPY.cjs → chunk-ISCAJBPQ.cjs} +1 -1
  25. package/dist/{chunk-W6JDUXP3.js → chunk-J23JOHPR.js} +1 -1
  26. package/dist/{chunk-IEEY5RJV.js → chunk-JNMNM3SL.js} +1 -1
  27. package/dist/{chunk-HVO6XCI4.js → chunk-JPB65KQ6.js} +1 -1
  28. package/dist/{chunk-ACTHSQOI.js → chunk-LDZOXESA.js} +618 -1
  29. package/dist/{chunk-DHC4E2DU.cjs → chunk-MITQGHN3.cjs} +121 -8
  30. package/dist/{chunk-STLTOISW.cjs → chunk-NPPMKMCU.cjs} +17 -17
  31. package/dist/{chunk-75SYKUOT.js → chunk-OS6OG7DN.js} +1 -1
  32. package/dist/{chunk-OLZXXAER.cjs → chunk-OY24XRYF.cjs} +408 -250
  33. package/dist/{chunk-6QNDGMK7.cjs → chunk-Q33V5PEF.cjs} +123 -42
  34. package/dist/{chunk-JDXWLCXF.cjs → chunk-QNNZA3V5.cjs} +2 -2
  35. package/dist/{chunk-T6KNJVBP.cjs → chunk-U26LUXCJ.cjs} +35 -35
  36. package/dist/{chunk-62MFUHSI.cjs → chunk-UQXULAMQ.cjs} +16 -16
  37. package/dist/{chunk-RAPNRK4R.cjs → chunk-X7XBEX3O.cjs} +88 -88
  38. package/dist/{chunk-MSWLJI4G.js → chunk-XMSH66VU.js} +2 -2
  39. package/dist/{chunk-ZM7IKQ4X.js → chunk-XVPQNX7O.js} +89 -8
  40. package/dist/{chunk-YYZC2EQO.cjs → chunk-ZIE6EJGR.cjs} +23 -23
  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 +7 -1
  52. package/dist/paint-editor/brush-model.d.ts +7 -1
  53. package/dist/paint-editor/controller.d.cts +52 -0
  54. package/dist/paint-editor/controller.d.ts +52 -0
  55. package/dist/paint-editor/index.cjs +850 -510
  56. package/dist/paint-editor/index.js +355 -15
  57. package/dist/paint-editor/ui.d.cts +5 -1
  58. package/dist/paint-editor/ui.d.ts +5 -1
  59. package/dist/paint-editor/wet-tools.d.cts +42 -0
  60. package/dist/paint-editor/wet-tools.d.ts +42 -0
  61. package/dist/paint-editor/wet.d.cts +22 -1
  62. package/dist/paint-editor/wet.d.ts +22 -1
  63. package/dist/vector/index.cjs +1002 -1002
  64. package/dist/vector/index.js +7 -7
  65. package/package.json +1 -1
@@ -74,7 +74,7 @@ function profileAlongPolyline(points, values, count = 32) {
74
74
  var round3 = (v) => Math.round(v * 1e3) / 1e3;
75
75
  var loading = null;
76
76
  function loadCanvasBrushstrokeRenderer() {
77
- loading ??= import("./brushstrokeRender-WGY4DZHF.js").catch((error) => {
77
+ loading ??= import("./brushstrokeRender-FK3ASUUJ.js").catch((error) => {
78
78
  loading = null;
79
79
  throw error;
80
80
  });
@@ -13,7 +13,7 @@ import {
13
13
  dabRect,
14
14
  put255,
15
15
  rasterizeDab
16
- } from "./chunk-ACTHSQOI.js";
16
+ } from "./chunk-LDZOXESA.js";
17
17
  import {
18
18
  blendPixel,
19
19
  createPigment,
@@ -17,6 +17,8 @@ import {
17
17
  } from "./chunk-IRSTHLQQ.js";
18
18
  import {
19
19
  TILE,
20
+ TiledSurface,
21
+ finishTile,
20
22
  tileCoord,
21
23
  tileKey
22
24
  } from "./chunk-HVVR5PZW.js";
@@ -1866,6 +1868,607 @@ function union(a, b) {
1866
1868
  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 };
1867
1869
  }
1868
1870
 
1871
+ // src/paint/live-patterns.ts
1872
+ var PATTERN_TILE = 128;
1873
+ var PATTERN_CELLS = PATTERN_TILE * PATTERN_TILE;
1874
+ var PATTERN_SUBSTEPS = 4;
1875
+ var PATTERN_V_EPS = 0.02;
1876
+ var LIVE_PATTERN_KINDS = ["spots", "stripes", "coral", "maze", "worms"];
1877
+ var LIVE_PATTERN_PRESETS = {
1878
+ spots: {
1879
+ kind: "spots",
1880
+ label: "Spots",
1881
+ description: "Dividing Turing spots (mitosis).",
1882
+ feed: 0.035,
1883
+ kill: 0.065
1884
+ },
1885
+ stripes: {
1886
+ kind: "stripes",
1887
+ label: "Stripes",
1888
+ description: "Labyrinthine Turing stripes.",
1889
+ feed: 0.042,
1890
+ kill: 0.06
1891
+ },
1892
+ coral: {
1893
+ kind: "coral",
1894
+ label: "Coral",
1895
+ description: "Branching coral dendrites.",
1896
+ feed: 0.0545,
1897
+ kill: 0.062
1898
+ },
1899
+ maze: {
1900
+ kind: "maze",
1901
+ label: "Maze",
1902
+ description: "Winding interconnected maze channels.",
1903
+ feed: 0.029,
1904
+ kill: 0.057
1905
+ },
1906
+ worms: {
1907
+ kind: "worms",
1908
+ label: "Worms",
1909
+ description: "Wiggling vermicular tracks.",
1910
+ feed: 0.058,
1911
+ kill: 0.065
1912
+ }
1913
+ };
1914
+ var DEFAULT_LIVE_PATTERNS = {
1915
+ pattern: "spots",
1916
+ feed: LIVE_PATTERN_PRESETS.spots.feed,
1917
+ kill: LIVE_PATTERN_PRESETS.spots.kill,
1918
+ scale: 1,
1919
+ contrast: 1,
1920
+ secondColor: null,
1921
+ relief: 0
1922
+ };
1923
+ function isLivePatternKind(v) {
1924
+ return typeof v === "string" && LIVE_PATTERN_KINDS.includes(v);
1925
+ }
1926
+ function normalizePatternHexColor(v, fallback = null) {
1927
+ if (v === null) return null;
1928
+ if (typeof v !== "string") return fallback;
1929
+ const s = v.trim().toLowerCase();
1930
+ if (/^#[0-9a-f]{6}$/.test(s)) return s;
1931
+ if (/^#[0-9a-f]{3}$/.test(s)) {
1932
+ return `#${s[1]}${s[1]}${s[2]}${s[2]}${s[3]}${s[3]}`;
1933
+ }
1934
+ return fallback;
1935
+ }
1936
+ function parsePatternHexColor(hex) {
1937
+ if (!hex || !/^#[0-9a-f]{6}$/i.test(hex)) return null;
1938
+ return [
1939
+ Number.parseInt(hex.slice(1, 3), 16),
1940
+ Number.parseInt(hex.slice(3, 5), 16),
1941
+ Number.parseInt(hex.slice(5, 7), 16)
1942
+ ];
1943
+ }
1944
+ function round4(n) {
1945
+ return Math.round(n * 1e4) / 1e4;
1946
+ }
1947
+ function normalizeLivePatterns(raw = {}, base = DEFAULT_LIVE_PATTERNS) {
1948
+ const src = raw ?? {};
1949
+ const hasExplicitPattern = isLivePatternKind(src.pattern);
1950
+ const pattern = hasExplicitPattern ? src.pattern : base.pattern;
1951
+ const preset = LIVE_PATTERN_PRESETS[pattern];
1952
+ const num = (v, d, min, max) => typeof v === "number" && Number.isFinite(v) ? Math.min(max, Math.max(min, v)) : d;
1953
+ const defaultFeed = hasExplicitPattern ? preset.feed : base.feed;
1954
+ const defaultKill = hasExplicitPattern ? preset.kill : base.kill;
1955
+ return {
1956
+ pattern,
1957
+ feed: round4(num(src.feed, defaultFeed, 0.01, 0.1)),
1958
+ kill: round4(num(src.kill, defaultKill, 0.03, 0.08)),
1959
+ scale: round4(num(src.scale, base.scale, 0.5, 4)),
1960
+ contrast: round4(num(src.contrast, base.contrast, 0, 2)),
1961
+ secondColor: src.secondColor === void 0 ? base.secondColor : normalizePatternHexColor(src.secondColor, base.secondColor),
1962
+ relief: round4(num(src.relief, base.relief, 0, 1))
1963
+ };
1964
+ }
1965
+ function patternNoise(gx, gy, seed) {
1966
+ let h = Math.imul(gx | 0, 374761393) + Math.imul(gy | 0, 668265263) + Math.imul(seed | 0, 1442695041) | 0;
1967
+ h = Math.imul(h ^ h >>> 13, 1274126177);
1968
+ return ((h ^ h >>> 16) >>> 0) / 4294967296;
1969
+ }
1970
+ var LivePatternField = class {
1971
+ width;
1972
+ height;
1973
+ tilesX;
1974
+ tilesY;
1975
+ settings = { ...DEFAULT_LIVE_PATTERNS };
1976
+ enabled = false;
1977
+ tiles = /* @__PURE__ */ new Map();
1978
+ constructor(width, height, settings) {
1979
+ this.width = Math.max(1, Math.round(width));
1980
+ this.height = Math.max(1, Math.round(height));
1981
+ this.tilesX = Math.ceil(this.width / TILE);
1982
+ this.tilesY = Math.ceil(this.height / TILE);
1983
+ if (settings) {
1984
+ this.settings = normalizeLivePatterns(settings, this.settings);
1985
+ this.enabled = true;
1986
+ }
1987
+ }
1988
+ resize(width, height) {
1989
+ this.width = Math.max(1, Math.round(width));
1990
+ this.height = Math.max(1, Math.round(height));
1991
+ this.tilesX = Math.ceil(this.width / TILE);
1992
+ this.tilesY = Math.ceil(this.height / TILE);
1993
+ }
1994
+ configure(settings) {
1995
+ this.settings = normalizeLivePatterns(settings, this.settings);
1996
+ this.enabled = true;
1997
+ return { ...this.settings };
1998
+ }
1999
+ clear() {
2000
+ this.enabled = false;
2001
+ this.tiles.clear();
2002
+ }
2003
+ ensureTile(tx, ty) {
2004
+ if (tx < 0 || ty < 0 || tx >= this.tilesX || ty >= this.tilesY) return null;
2005
+ const key = tileKey(tx, ty);
2006
+ let tile = this.tiles.get(key);
2007
+ if (!tile) {
2008
+ const u = new Float64Array(PATTERN_CELLS);
2009
+ u.fill(1);
2010
+ const v = new Float64Array(PATTERN_CELLS);
2011
+ const bakedV = new Float64Array(PATTERN_CELLS);
2012
+ tile = { u, v, bakedV };
2013
+ this.tiles.set(key, tile);
2014
+ }
2015
+ return tile;
2016
+ }
2017
+ /**
2018
+ * Seeds Gray-Scott reagent `V` inside a circle at document coordinates `(cx, cy)` with document
2019
+ * `radius` (pixels). Returns the number of half-resolution cells seeded.
2020
+ */
2021
+ seedDisc(cx, cy, radius, seed = 1, strength = 1) {
2022
+ this.enabled = true;
2023
+ const s = Math.min(1, Math.max(0.05, strength));
2024
+ const rDoc = Math.max(2, radius);
2025
+ const px = cx * 0.5;
2026
+ const py = cy * 0.5;
2027
+ const pr = Math.max(1, rDoc * 0.5);
2028
+ const maxGx = Math.ceil(this.width * 0.5);
2029
+ const maxGy = Math.ceil(this.height * 0.5);
2030
+ const gx0 = Math.max(0, Math.floor(px - pr));
2031
+ const gy0 = Math.max(0, Math.floor(py - pr));
2032
+ const gx1 = Math.min(maxGx - 1, Math.ceil(px + pr));
2033
+ const gy1 = Math.min(maxGy - 1, Math.ceil(py + pr));
2034
+ const r2 = pr * pr;
2035
+ let seeded = 0;
2036
+ for (let gy = gy0; gy <= gy1; gy++) {
2037
+ const dy = gy + 0.5 - py;
2038
+ for (let gx = gx0; gx <= gx1; gx++) {
2039
+ const dx = gx + 0.5 - px;
2040
+ const d2 = dx * dx + dy * dy;
2041
+ if (d2 > r2) continue;
2042
+ const tx = Math.floor(gx / PATTERN_TILE);
2043
+ const ty = Math.floor(gy / PATTERN_TILE);
2044
+ const tile = this.ensureTile(tx, ty);
2045
+ if (!tile) continue;
2046
+ const lx = gx - tx * PATTERN_TILE;
2047
+ const ly = gy - ty * PATTERN_TILE;
2048
+ const idx = ly * PATTERN_TILE + lx;
2049
+ const coarse = patternNoise(gx >> 1, gy >> 1, seed);
2050
+ const fine = patternNoise(gx, gy, seed + 101);
2051
+ const nearCenter = d2 <= Math.max(2.5, r2 * 0.08);
2052
+ if (nearCenter || d2 <= r2 * 0.65 && coarse > 0.72) {
2053
+ const falloff = 1 - d2 / r2 * 0.2;
2054
+ const targetV = (0.45 + 0.12 * fine) * s * falloff;
2055
+ if (targetV > tile.v[idx]) {
2056
+ tile.v[idx] = targetV;
2057
+ tile.u[idx] = Math.min(tile.u[idx], 0.5);
2058
+ }
2059
+ tile.bakedV[idx] = 0;
2060
+ seeded++;
2061
+ } else if (tile.v[idx] === 0 && tile.u[idx] === 1) {
2062
+ tile.u[idx] = 0.99 - 0.02 * fine * s;
2063
+ tile.bakedV[idx] = 0;
2064
+ }
2065
+ }
2066
+ }
2067
+ return seeded;
2068
+ }
2069
+ /**
2070
+ * Advances the Gray-Scott reaction-diffusion field for `wetSteps` wet simulation steps.
2071
+ * Only cells where `isWetCell(tx, ty, lx, ly)` is true update; dry cells have zero rate and freeze.
2072
+ * Returns whether any cell updated.
2073
+ */
2074
+ step(wetSteps, isWetCell, canWakeTile) {
2075
+ if (!this.enabled || this.tiles.size === 0 || wetSteps <= 0) return false;
2076
+ const totalSubsteps = Math.max(1, Math.floor(wetSteps)) * PATTERN_SUBSTEPS;
2077
+ const F = this.settings.feed;
2078
+ const k = this.settings.kill;
2079
+ const fk = F + k;
2080
+ const scaleClamped = Math.min(2.2, Math.max(0.4, this.settings.scale));
2081
+ const Du = 0.16 * scaleClamped;
2082
+ const Dv = 0.08 * scaleClamped;
2083
+ for (const [key, tile] of [...this.tiles.entries()]) {
2084
+ const { tx, ty } = tileCoord(key);
2085
+ let top = false;
2086
+ let bottom = false;
2087
+ let left = false;
2088
+ let right = false;
2089
+ for (let i = 0; i < PATTERN_TILE; i++) {
2090
+ if (tile.v[i] > PATTERN_V_EPS || tile.v[PATTERN_TILE + i] > PATTERN_V_EPS) top = true;
2091
+ if (tile.v[(PATTERN_TILE - 1) * PATTERN_TILE + i] > PATTERN_V_EPS || tile.v[(PATTERN_TILE - 2) * PATTERN_TILE + i] > PATTERN_V_EPS) bottom = true;
2092
+ if (tile.v[i * PATTERN_TILE] > PATTERN_V_EPS || tile.v[i * PATTERN_TILE + 1] > PATTERN_V_EPS) left = true;
2093
+ if (tile.v[i * PATTERN_TILE + PATTERN_TILE - 1] > PATTERN_V_EPS || tile.v[i * PATTERN_TILE + PATTERN_TILE - 2] > PATTERN_V_EPS) right = true;
2094
+ }
2095
+ const wake = (nx, ny) => {
2096
+ if (nx < 0 || ny < 0 || nx >= this.tilesX || ny >= this.tilesY) return;
2097
+ if (canWakeTile && !canWakeTile(nx, ny)) return;
2098
+ this.ensureTile(nx, ny);
2099
+ };
2100
+ if (top) wake(tx, ty - 1);
2101
+ if (bottom) wake(tx, ty + 1);
2102
+ if (left) wake(tx - 1, ty);
2103
+ if (right) wake(tx + 1, ty);
2104
+ }
2105
+ const entries = [];
2106
+ let anyWet = false;
2107
+ for (const [key, tile] of this.tiles.entries()) {
2108
+ const { tx, ty } = tileCoord(key);
2109
+ const wet = new Uint8Array(PATTERN_CELLS);
2110
+ let hasWet = false;
2111
+ const tileW = Math.min(PATTERN_TILE, Math.ceil(this.width * 0.5) - tx * PATTERN_TILE);
2112
+ const tileH = Math.min(PATTERN_TILE, Math.ceil(this.height * 0.5) - ty * PATTERN_TILE);
2113
+ for (let ly = 0; ly < tileH; ly++) {
2114
+ for (let lx = 0; lx < tileW; lx++) {
2115
+ if (isWetCell(tx, ty, lx, ly)) {
2116
+ wet[ly * PATTERN_TILE + lx] = 1;
2117
+ hasWet = true;
2118
+ anyWet = true;
2119
+ }
2120
+ }
2121
+ }
2122
+ entries.push({
2123
+ key,
2124
+ tx,
2125
+ ty,
2126
+ u: tile.u,
2127
+ v: tile.v,
2128
+ nextU: new Float64Array(tile.u),
2129
+ nextV: new Float64Array(tile.v),
2130
+ wet,
2131
+ hasWet
2132
+ });
2133
+ }
2134
+ if (!anyWet) return false;
2135
+ const byKey = /* @__PURE__ */ new Map();
2136
+ for (const e of entries) byKey.set(e.key, e);
2137
+ const sampleUV = (entry, lx, ly, out) => {
2138
+ if (lx >= 0 && lx < PATTERN_TILE && ly >= 0 && ly < PATTERN_TILE) {
2139
+ const idx2 = ly * PATTERN_TILE + lx;
2140
+ out[0] = entry.u[idx2];
2141
+ out[1] = entry.v[idx2];
2142
+ return;
2143
+ }
2144
+ let ntx = entry.tx;
2145
+ let nty = entry.ty;
2146
+ let nlx = lx;
2147
+ let nly = ly;
2148
+ if (nlx < 0) {
2149
+ ntx -= 1;
2150
+ nlx += PATTERN_TILE;
2151
+ } else if (nlx >= PATTERN_TILE) {
2152
+ ntx += 1;
2153
+ nlx -= PATTERN_TILE;
2154
+ }
2155
+ if (nly < 0) {
2156
+ nty -= 1;
2157
+ nly += PATTERN_TILE;
2158
+ } else if (nly >= PATTERN_TILE) {
2159
+ nty += 1;
2160
+ nly -= PATTERN_TILE;
2161
+ }
2162
+ const nb = byKey.get(tileKey(ntx, nty));
2163
+ if (!nb) {
2164
+ const cx = Math.min(PATTERN_TILE - 1, Math.max(0, lx));
2165
+ const cy = Math.min(PATTERN_TILE - 1, Math.max(0, ly));
2166
+ const idx2 = cy * PATTERN_TILE + cx;
2167
+ out[0] = entry.u[idx2];
2168
+ out[1] = entry.v[idx2];
2169
+ return;
2170
+ }
2171
+ const idx = nly * PATTERN_TILE + nlx;
2172
+ out[0] = nb.u[idx];
2173
+ out[1] = nb.v[idx];
2174
+ };
2175
+ const nbScratch = new Float64Array(2);
2176
+ for (let s = 0; s < totalSubsteps; s++) {
2177
+ for (const e of entries) {
2178
+ if (!e.hasWet) continue;
2179
+ const { u, v, nextU, nextV, wet } = e;
2180
+ for (let ly = 0; ly < PATTERN_TILE; ly++) {
2181
+ const row = ly * PATTERN_TILE;
2182
+ const interiorY = ly > 0 && ly < PATTERN_TILE - 1;
2183
+ for (let lx = 0; lx < PATTERN_TILE; lx++) {
2184
+ const idx = row + lx;
2185
+ if (!wet[idx]) {
2186
+ nextU[idx] = u[idx];
2187
+ nextV[idx] = v[idx];
2188
+ continue;
2189
+ }
2190
+ const uc = u[idx];
2191
+ const vc = v[idx];
2192
+ let lapU = -uc;
2193
+ let lapV = -vc;
2194
+ if (interiorY && lx > 0 && lx < PATTERN_TILE - 1) {
2195
+ const n = idx - PATTERN_TILE;
2196
+ const so = idx + PATTERN_TILE;
2197
+ lapU += 0.2 * (u[idx - 1] + u[idx + 1] + u[n] + u[so]) + 0.05 * (u[n - 1] + u[n + 1] + u[so - 1] + u[so + 1]);
2198
+ lapV += 0.2 * (v[idx - 1] + v[idx + 1] + v[n] + v[so]) + 0.05 * (v[n - 1] + v[n + 1] + v[so - 1] + v[so + 1]);
2199
+ } else {
2200
+ sampleUV(e, lx - 1, ly, nbScratch);
2201
+ lapU += 0.2 * nbScratch[0];
2202
+ lapV += 0.2 * nbScratch[1];
2203
+ sampleUV(e, lx + 1, ly, nbScratch);
2204
+ lapU += 0.2 * nbScratch[0];
2205
+ lapV += 0.2 * nbScratch[1];
2206
+ sampleUV(e, lx, ly - 1, nbScratch);
2207
+ lapU += 0.2 * nbScratch[0];
2208
+ lapV += 0.2 * nbScratch[1];
2209
+ sampleUV(e, lx, ly + 1, nbScratch);
2210
+ lapU += 0.2 * nbScratch[0];
2211
+ lapV += 0.2 * nbScratch[1];
2212
+ sampleUV(e, lx - 1, ly - 1, nbScratch);
2213
+ lapU += 0.05 * nbScratch[0];
2214
+ lapV += 0.05 * nbScratch[1];
2215
+ sampleUV(e, lx + 1, ly - 1, nbScratch);
2216
+ lapU += 0.05 * nbScratch[0];
2217
+ lapV += 0.05 * nbScratch[1];
2218
+ sampleUV(e, lx - 1, ly + 1, nbScratch);
2219
+ lapU += 0.05 * nbScratch[0];
2220
+ lapV += 0.05 * nbScratch[1];
2221
+ sampleUV(e, lx + 1, ly + 1, nbScratch);
2222
+ lapU += 0.05 * nbScratch[0];
2223
+ lapV += 0.05 * nbScratch[1];
2224
+ }
2225
+ const uvv = uc * vc * vc;
2226
+ const nu = uc + Du * lapU - uvv + F * (1 - uc);
2227
+ const nv = vc + Dv * lapV + uvv - fk * vc;
2228
+ nextU[idx] = nu < 0 ? 0 : nu > 1 ? 1 : nu;
2229
+ nextV[idx] = nv < 0 ? 0 : nv > 1 ? 1 : nv;
2230
+ }
2231
+ }
2232
+ }
2233
+ for (const e of entries) {
2234
+ if (!e.hasWet) continue;
2235
+ e.u.set(e.nextU);
2236
+ e.v.set(e.nextV);
2237
+ }
2238
+ }
2239
+ return true;
2240
+ }
2241
+ /**
2242
+ * Bilinearly samples unbaked `V` (`max(0, v - bakedV)`) at document-tile pixel `(x, y)` in `0..255`.
2243
+ */
2244
+ sampleUnbakedV(tile, x, y) {
2245
+ const fx = Math.min(PATTERN_TILE - 1, Math.max(0, (x + 0.5) * 0.5 - 0.5));
2246
+ const fy = Math.min(PATTERN_TILE - 1, Math.max(0, (y + 0.5) * 0.5 - 0.5));
2247
+ const x0 = Math.floor(fx);
2248
+ const y0 = Math.floor(fy);
2249
+ const x1 = Math.min(PATTERN_TILE - 1, x0 + 1);
2250
+ const y1 = Math.min(PATTERN_TILE - 1, y0 + 1);
2251
+ const tx = fx - x0;
2252
+ const ty = fy - y0;
2253
+ const at = (ix, iy) => {
2254
+ const idx = iy * PATTERN_TILE + ix;
2255
+ const val = tile.v[idx] - tile.bakedV[idx];
2256
+ return val > 0 ? val : 0;
2257
+ };
2258
+ const v00 = at(x0, y0);
2259
+ const v10 = at(x1, y0);
2260
+ const v01 = at(x0, y1);
2261
+ const v11 = at(x1, y1);
2262
+ const top = v00 + (v10 - v00) * tx;
2263
+ const bot = v01 + (v11 - v01) * tx;
2264
+ return top + (bot - top) * ty;
2265
+ }
2266
+ /**
2267
+ * Blends the pattern's `V` concentration into a shaded `256 × 256` RGBA wet tile `out`.
2268
+ * Returns whether any pixel in `out` was modified.
2269
+ */
2270
+ shadeTile(tx, ty, out, w, h) {
2271
+ if (!this.enabled || this.settings.contrast <= 0) return false;
2272
+ const tile = this.tiles.get(tileKey(tx, ty));
2273
+ if (!tile) return false;
2274
+ const second = parsePatternHexColor(this.settings.secondColor);
2275
+ const contrast = this.settings.contrast;
2276
+ let changed = false;
2277
+ for (let y = 0; y < h; y++) {
2278
+ for (let x = 0; x < w; x++) {
2279
+ const i = (y * TILE + x) * 4;
2280
+ const a = out[i + 3];
2281
+ if (a === 0) continue;
2282
+ const vVal = this.sampleUnbakedV(tile, x, y);
2283
+ if (vVal <= 1e-4) continue;
2284
+ const t = Math.min(1, vVal * 2.2 * contrast);
2285
+ if (t <= 1e-3) continue;
2286
+ if (second) {
2287
+ out[i] = Math.round(out[i] + (second[0] - out[i]) * t);
2288
+ out[i + 1] = Math.round(out[i + 1] + (second[1] - out[i + 1]) * t);
2289
+ out[i + 2] = Math.round(out[i + 2] + (second[2] - out[i + 2]) * t);
2290
+ out[i + 3] = Math.min(255, Math.round(a + (255 - a) * t * 0.35));
2291
+ } else {
2292
+ const lum = out[i] * 77 + out[i + 1] * 150 + out[i + 2] * 29 >> 8;
2293
+ if (lum < 48) {
2294
+ out[i] = Math.round(out[i] + (220 - out[i]) * 0.4 * t);
2295
+ out[i + 1] = Math.round(out[i + 1] + (220 - out[i + 1]) * 0.4 * t);
2296
+ out[i + 2] = Math.round(out[i + 2] + (220 - out[i + 2]) * 0.4 * t);
2297
+ } else {
2298
+ const scale = 1 - 0.45 * t;
2299
+ out[i] = Math.round(out[i] * scale);
2300
+ out[i + 1] = Math.round(out[i + 1] * scale);
2301
+ out[i + 2] = Math.round(out[i + 2] * scale);
2302
+ }
2303
+ out[i + 3] = Math.min(255, Math.round(a + (255 - a) * t * 0.28));
2304
+ }
2305
+ changed = true;
2306
+ }
2307
+ }
2308
+ return changed;
2309
+ }
2310
+ /** Marks a tile's current `V` values as baked into the layer's base surface so they are not baked twice. */
2311
+ markBaked(tx, ty) {
2312
+ const tile = this.tiles.get(tileKey(tx, ty));
2313
+ if (tile) tile.bakedV.set(tile.v);
2314
+ }
2315
+ /**
2316
+ * When `settings.relief > 0`, writes pattern `V` ridges into a 1-channel thick height surface.
2317
+ * Returns the updated height `TiledSurface`, or `currentHeight` when `relief <= 0` or nothing changed.
2318
+ */
2319
+ applyReliefHeight(currentHeight) {
2320
+ const relief = this.settings.relief;
2321
+ if (!this.enabled || relief <= 0 || this.tiles.size === 0) return currentHeight;
2322
+ let nextTiles = null;
2323
+ for (const [key, pTile] of this.tiles.entries()) {
2324
+ const { tx, ty } = tileCoord(key);
2325
+ const w = Math.min(TILE, this.width - tx * TILE);
2326
+ const h = Math.min(TILE, this.height - ty * TILE);
2327
+ if (w <= 0 || h <= 0) continue;
2328
+ let maxTileV = 0;
2329
+ for (let i = 0; i < PATTERN_CELLS; i++) {
2330
+ if (pTile.v[i] > maxTileV) maxTileV = pTile.v[i];
2331
+ }
2332
+ if (maxTileV < PATTERN_V_EPS) continue;
2333
+ const existing = currentHeight.tiles.get(key);
2334
+ const buf = existing ? existing.data.length ? new Uint8ClampedArray(existing.data) : new Uint8ClampedArray(TILE * TILE).fill(existing.uniform ?? 0) : new Uint8ClampedArray(TILE * TILE);
2335
+ let tileChanged = false;
2336
+ for (let y = 0; y < h; y++) {
2337
+ const ly = y >> 1;
2338
+ const row = ly * PATTERN_TILE;
2339
+ for (let x = 0; x < w; x++) {
2340
+ const vVal = pTile.v[row + (x >> 1)];
2341
+ if (vVal < PATTERN_V_EPS) continue;
2342
+ const ridge = Math.min(255, Math.round(Math.min(1, vVal * 2.2) * relief * 255));
2343
+ const p = y * TILE + x;
2344
+ if (ridge > buf[p]) {
2345
+ buf[p] = ridge;
2346
+ tileChanged = true;
2347
+ }
2348
+ }
2349
+ }
2350
+ if (tileChanged) {
2351
+ if (!nextTiles) nextTiles = new Map(currentHeight.tiles);
2352
+ const finished = finishTile(buf, 1, 0);
2353
+ if (finished) nextTiles.set(key, finished);
2354
+ else nextTiles.delete(key);
2355
+ }
2356
+ }
2357
+ return nextTiles ? new TiledSurface(1, nextTiles, 0) : currentHeight;
2358
+ }
2359
+ /** Computes summary statistics of the layer's live pattern field. */
2360
+ stats(isWetCell) {
2361
+ if (!this.enabled || this.tiles.size === 0) {
2362
+ return {
2363
+ enabled: this.enabled,
2364
+ settings: { ...this.settings },
2365
+ activeTiles: 0,
2366
+ patternCells: 0,
2367
+ wetPatternCells: 0,
2368
+ meanV: 0,
2369
+ maxV: 0,
2370
+ bounds: null
2371
+ };
2372
+ }
2373
+ let activeTiles = 0;
2374
+ let patternCells = 0;
2375
+ let wetPatternCells = 0;
2376
+ let sumV = 0;
2377
+ let maxV = 0;
2378
+ let bx0 = Infinity;
2379
+ let by0 = Infinity;
2380
+ let bx1 = -Infinity;
2381
+ let by1 = -Infinity;
2382
+ for (const [key, tile] of this.tiles.entries()) {
2383
+ const { tx, ty } = tileCoord(key);
2384
+ const tileW = Math.min(PATTERN_TILE, Math.ceil(this.width * 0.5) - tx * PATTERN_TILE);
2385
+ const tileH = Math.min(PATTERN_TILE, Math.ceil(this.height * 0.5) - ty * PATTERN_TILE);
2386
+ let tileHasCells = false;
2387
+ for (let ly = 0; ly < tileH; ly++) {
2388
+ const row = ly * PATTERN_TILE;
2389
+ for (let lx = 0; lx < tileW; lx++) {
2390
+ const val = tile.v[row + lx];
2391
+ if (val > maxV) maxV = val;
2392
+ if (val >= PATTERN_V_EPS) {
2393
+ tileHasCells = true;
2394
+ patternCells++;
2395
+ sumV += val;
2396
+ if (isWetCell && isWetCell(tx, ty, lx, ly)) wetPatternCells++;
2397
+ const gx0 = (tx * PATTERN_TILE + lx) * 2;
2398
+ const gy0 = (ty * PATTERN_TILE + ly) * 2;
2399
+ const gx1 = Math.min(this.width, gx0 + 2);
2400
+ const gy1 = Math.min(this.height, gy0 + 2);
2401
+ if (gx0 < bx0) bx0 = gx0;
2402
+ if (gy0 < by0) by0 = gy0;
2403
+ if (gx1 > bx1) bx1 = gx1;
2404
+ if (gy1 > by1) by1 = gy1;
2405
+ }
2406
+ }
2407
+ }
2408
+ if (tileHasCells) activeTiles++;
2409
+ }
2410
+ return {
2411
+ enabled: this.enabled,
2412
+ settings: { ...this.settings },
2413
+ activeTiles,
2414
+ patternCells,
2415
+ wetPatternCells,
2416
+ meanV: patternCells > 0 ? round4(sumV / patternCells) : 0,
2417
+ maxV: round4(maxV),
2418
+ bounds: patternCells > 0 ? { x: bx0, y: by0, width: bx1 - bx0, height: by1 - by0 } : null
2419
+ };
2420
+ }
2421
+ snapshot() {
2422
+ const tiles = /* @__PURE__ */ new Map();
2423
+ let bytes = 0;
2424
+ for (const [key, t] of this.tiles.entries()) {
2425
+ const u = new Float64Array(t.u);
2426
+ const v = new Float64Array(t.v);
2427
+ const bakedV = new Float64Array(t.bakedV);
2428
+ tiles.set(key, { u, v, bakedV });
2429
+ bytes += u.byteLength + v.byteLength + bakedV.byteLength;
2430
+ }
2431
+ return {
2432
+ settings: { ...this.settings },
2433
+ enabled: this.enabled,
2434
+ tiles,
2435
+ bytes
2436
+ };
2437
+ }
2438
+ restore(snap) {
2439
+ this.settings = { ...snap.settings };
2440
+ this.enabled = snap.enabled;
2441
+ this.tiles.clear();
2442
+ for (const [key, t] of snap.tiles.entries()) {
2443
+ this.tiles.set(key, {
2444
+ u: new Float64Array(t.u),
2445
+ v: new Float64Array(t.v),
2446
+ bakedV: new Float64Array(t.bakedV)
2447
+ });
2448
+ }
2449
+ }
2450
+ };
2451
+ function moveLivePatternSnapshot(snap, to, tx0, ty0) {
2452
+ const maxTilesX = Math.ceil(to.width / TILE);
2453
+ const maxTilesY = Math.ceil(to.height / TILE);
2454
+ const tiles = /* @__PURE__ */ new Map();
2455
+ let bytes = 0;
2456
+ for (const [key, t] of snap.tiles.entries()) {
2457
+ const { tx, ty } = tileCoord(key);
2458
+ const ntx = tx + tx0;
2459
+ const nty = ty + ty0;
2460
+ if (ntx < 0 || nty < 0 || ntx >= maxTilesX || nty >= maxTilesY) continue;
2461
+ tiles.set(tileKey(ntx, nty), t);
2462
+ bytes += t.u.byteLength + t.v.byteLength + t.bakedV.byteLength;
2463
+ }
2464
+ return {
2465
+ settings: { ...snap.settings },
2466
+ enabled: snap.enabled,
2467
+ tiles,
2468
+ bytes
2469
+ };
2470
+ }
2471
+
1869
2472
  export {
1870
2473
  PAINT_GRADIENT_SPACES,
1871
2474
  PAINT_GRADIENT_REPEATS,
@@ -1944,5 +2547,19 @@ export {
1944
2547
  mixerRates,
1945
2548
  loadMixer,
1946
2549
  cleanMixer,
1947
- MixerStroke
2550
+ MixerStroke,
2551
+ PATTERN_TILE,
2552
+ PATTERN_CELLS,
2553
+ PATTERN_SUBSTEPS,
2554
+ PATTERN_V_EPS,
2555
+ LIVE_PATTERN_KINDS,
2556
+ LIVE_PATTERN_PRESETS,
2557
+ DEFAULT_LIVE_PATTERNS,
2558
+ isLivePatternKind,
2559
+ normalizePatternHexColor,
2560
+ parsePatternHexColor,
2561
+ normalizeLivePatterns,
2562
+ patternNoise,
2563
+ LivePatternField,
2564
+ moveLivePatternSnapshot
1948
2565
  };