@snowcone-app/canvas 0.29.0 → 0.31.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 (92) hide show
  1. package/ERROR_CONTRACT.md +13 -0
  2. package/STYLING_CONTRACT.md +2 -2
  3. package/dist/{CanvasRenderer-_lFoIoz5.js → CanvasRenderer-hpJ05JGO.js} +4 -4
  4. package/dist/{CanvasRenderer-_lFoIoz5.js.map → CanvasRenderer-hpJ05JGO.js.map} +1 -1
  5. package/dist/{CanvasStateV1-BMxr6xb8.js → CanvasStateV1-jLOibojY.js} +876 -844
  6. package/dist/{CanvasStateV1-BMxr6xb8.js.map → CanvasStateV1-jLOibojY.js.map} +1 -1
  7. package/dist/ContextualToolbars-DRbuUE7W.js +6061 -0
  8. package/dist/ContextualToolbars-DRbuUE7W.js.map +1 -0
  9. package/dist/{EditorCanvasImpl-BNz8TPFs.js → EditorCanvasImpl-P2GHPNA3.js} +19 -20
  10. package/dist/{EditorCanvasImpl-BNz8TPFs.js.map → EditorCanvasImpl-P2GHPNA3.js.map} +1 -1
  11. package/dist/{ExportManager-B8Td7uR5.js → ExportManager-Bkzzyxgw.js} +973 -576
  12. package/dist/ExportManager-Bkzzyxgw.js.map +1 -0
  13. package/dist/{FontAnalyzer-D4Sg-P6_.js → FontAnalyzer-BYhwxDhe.js} +542 -477
  14. package/dist/FontAnalyzer-BYhwxDhe.js.map +1 -0
  15. package/dist/{ImagePanel-CN2Bu3hf.js → ImagePanel-CEr0_FYy.js} +38 -38
  16. package/dist/{ImagePanel-CN2Bu3hf.js.map → ImagePanel-CEr0_FYy.js.map} +1 -1
  17. package/dist/SnowconeCanvas-Fa0isFpO.js +1192 -0
  18. package/dist/SnowconeCanvas-Fa0isFpO.js.map +1 -0
  19. package/dist/advanced.mjs +14 -14
  20. package/dist/api/embed.d.ts +2 -0
  21. package/dist/api/stable.d.ts +2 -0
  22. package/dist/components/CanvasContextMenu.d.ts +15 -0
  23. package/dist/components/CanvasEditor/hooks/useCanvasInteraction.d.ts +27 -0
  24. package/dist/components/CanvasEditor.d.ts +8 -0
  25. package/dist/components/ImageToolbar.d.ts +4 -0
  26. package/dist/components/embed/SnowconeCanvas.d.ts +13 -0
  27. package/dist/components/embed/kernel.d.ts +8 -0
  28. package/dist/components/toolbars/EmbeddedToolbarLayout.d.ts +2 -0
  29. package/dist/components/toolbars/SecondaryPanels.d.ts +17 -1
  30. package/dist/components/toolbars/index.d.ts +2 -2
  31. package/dist/components/ui/SliderRow.d.ts +6 -0
  32. package/dist/components/ui/icons.d.ts +2 -0
  33. package/dist/components/ui/toolbar-button.d.ts +2 -2
  34. package/dist/contexts/CanvasServicesContext.d.ts +17 -0
  35. package/dist/contexts/EditorContext.d.ts +7 -0
  36. package/dist/core/image-adjustments.d.ts +32 -0
  37. package/dist/{custom-icons-B5TRC1Ed.js → custom-icons-Co4rcW0S.js} +7 -7
  38. package/dist/{custom-icons-B5TRC1Ed.js.map → custom-icons-Co4rcW0S.js.map} +1 -1
  39. package/dist/design-builder-CBG2hKc8.js +1672 -0
  40. package/dist/design-builder-CBG2hKc8.js.map +1 -0
  41. package/dist/{embed-BTRtoktw.js → embed-D4TIzO7g.js} +20 -20
  42. package/dist/{embed-BTRtoktw.js.map → embed-D4TIzO7g.js.map} +1 -1
  43. package/dist/embed.mjs +51 -46
  44. package/dist/embed.mjs.map +1 -1
  45. package/dist/errors/editorError.d.ts +8 -0
  46. package/dist/{guards-uQtEQJbs.js → guards-DgZuwVgu.js} +2 -2
  47. package/dist/{guards-uQtEQJbs.js.map → guards-DgZuwVgu.js.map} +1 -1
  48. package/dist/hooks/elementPatch.d.ts +7 -2
  49. package/dist/hooks/useKeyboardShortcuts.d.ts +15 -2
  50. package/dist/hooks/useLayers.d.ts +2 -0
  51. package/dist/index.mjs +57 -52
  52. package/dist/index.mjs.map +1 -1
  53. package/dist/internals.mjs +201 -132
  54. package/dist/internals.mjs.map +1 -1
  55. package/dist/rendering/element-painters.d.ts +0 -13
  56. package/dist/rendering/element-serializer.d.ts +3 -1
  57. package/dist/rendering/image-adjustments.d.ts +24 -0
  58. package/dist/rendering/renderer-types.d.ts +8 -1
  59. package/dist/state/CanvasStateV1.d.ts +36 -3
  60. package/dist/state/index.d.ts +2 -2
  61. package/dist/template-fields-CvFcr5zy.js +505 -0
  62. package/dist/template-fields-CvFcr5zy.js.map +1 -0
  63. package/dist/testing.mjs +46 -47
  64. package/dist/testing.mjs.map +1 -1
  65. package/dist/types/index.d.ts +21 -0
  66. package/dist/{UnifiedFontService-CgYnYTj7.js → useCommands-bPXY9rCh.js} +2429 -2145
  67. package/dist/useCommands-bPXY9rCh.js.map +1 -0
  68. package/dist/{usePieceGuideBundle-CTQszfKA.js → usePieceGuideBundle-Bj98nPk0.js} +2 -2
  69. package/dist/{usePieceGuideBundle-CTQszfKA.js.map → usePieceGuideBundle-Bj98nPk0.js.map} +1 -1
  70. package/dist/utils/custom-fonts.d.ts +99 -0
  71. package/dist/utils/featureApplied.d.ts +2 -0
  72. package/dist/validation-CsTlSvJJ.js +8686 -0
  73. package/dist/validation-CsTlSvJJ.js.map +1 -0
  74. package/dist/workers/export-protocol.d.ts +13 -0
  75. package/dist/workers/export-worker.bundle.string.d.ts +1 -1
  76. package/example/package.json +1 -1
  77. package/package.json +1 -1
  78. package/dist/ContextualToolbars-0jWvnQgp.js +0 -5833
  79. package/dist/ContextualToolbars-0jWvnQgp.js.map +0 -1
  80. package/dist/ExportManager-B8Td7uR5.js.map +0 -1
  81. package/dist/FontAnalyzer-D4Sg-P6_.js.map +0 -1
  82. package/dist/SnowconeCanvas-CR_LdRFP.js +0 -1173
  83. package/dist/SnowconeCanvas-CR_LdRFP.js.map +0 -1
  84. package/dist/UnifiedFontService-CgYnYTj7.js.map +0 -1
  85. package/dist/design-builder-COj0zkxu.js +0 -470
  86. package/dist/design-builder-COj0zkxu.js.map +0 -1
  87. package/dist/editorErrors-CCvoKj_P.js +0 -1219
  88. package/dist/editorErrors-CCvoKj_P.js.map +0 -1
  89. package/dist/template-fields-BLRp84Ny.js +0 -716
  90. package/dist/template-fields-BLRp84Ny.js.map +0 -1
  91. package/dist/validation-eCebhplp.js +0 -8402
  92. package/dist/validation-eCebhplp.js.map +0 -1
@@ -1,9 +1,74 @@
1
- import { A as w, a as b, C as I, I as R, R as W, S as A, T as E } from "./CanvasRenderer-_lFoIoz5.js";
2
- import { r as p } from "./ExportManager-B8Td7uR5.js";
3
- import { D as O, E as F, I as L, W as X, a as z, b as K, c as Y, d as B, e as _, f as J, h as N, i as V, j, k as q, l as Q, m as U, n as Z, o as $, p as m, q as G, s as x, t as D, u as rr, v as or, w as sr, x as ir, y as er, z as ar, A as nr, B as tr, C as hr } from "./ExportManager-B8Td7uR5.js";
4
- import { D as cr, E as fr, H as pr, J as gr, K as lr, L as ur, M as Tr, N as kr, O as Mr, Q as yr, W as Cr, V as vr, X as Sr, Y as Pr, c as wr, Z as br, _ as Ir, $ as Rr, a0 as Wr, a1 as Ar } from "./FontAnalyzer-D4Sg-P6_.js";
5
- import { A as Hr, e as Or, C as Fr, S as Lr } from "./CanvasStateV1-BMxr6xb8.js";
6
- function y(r, o) {
1
+ import { A as D, a as rr, C as or, I as sr, R as er, S as nr, T as tr } from "./CanvasRenderer-hpJ05JGO.js";
2
+ import { a as M } from "./ExportManager-Bkzzyxgw.js";
3
+ import { D as ir, E as hr, I as cr, W as dr, b as fr, d as ur, f as gr, h as lr, i as pr, j as yr, k as Mr, m as vr, n as Tr, o as kr, p as wr, q as Cr, s as br, t as Sr, u as Pr, v as Ir, w as mr, x as Ar, y as Rr, z as Wr, A as Er, B as Or, C as Hr, F as jr, G as Lr, H as Fr, J as Xr } from "./ExportManager-Bkzzyxgw.js";
4
+ import { K as H, L as j, c as L, M as F } from "./FontAnalyzer-BYhwxDhe.js";
5
+ import { N as _r, O as zr, Q as Kr, V as Br, W as Ur, X as Yr, Y as Jr, Z as Vr, _ as qr, $ as Gr, a0 as Qr, a1 as Zr, a2 as $r, a3 as xr, a4 as Dr, a5 as ro, a6 as oo, a7 as so, a8 as eo } from "./FontAnalyzer-BYhwxDhe.js";
6
+ import { A as to, e as ao, C as io, S as ho } from "./CanvasStateV1-jLOibojY.js";
7
+ const X = L("image-adjustments"), v = 1e-3;
8
+ function N(r, o) {
9
+ const { brightness: a, contrast: s, saturation: h } = o, e = a * s, n = 127.5 * (1 - s), f = Math.abs(e - 1) > v || Math.abs(n) > v, i = new Uint8ClampedArray(256);
10
+ if (f)
11
+ for (let g = 0; g < 256; g++) i[g] = g * e + n;
12
+ const c = Math.abs(h - 1) > v, t = h, d = 0.213 + 0.787 * t, u = 0.715 - 0.715 * t, b = 0.072 - 0.072 * t, S = 0.213 - 0.213 * t, P = 0.715 + 0.285 * t, I = 0.072 - 0.072 * t, m = 0.213 - 0.213 * t, A = 0.715 - 0.715 * t, R = 0.072 + 0.928 * t;
13
+ for (let g = 0; g < r.length; g += 4) {
14
+ let l = r[g], p = r[g + 1], y = r[g + 2];
15
+ if (f && (l = i[l], p = i[p], y = i[y]), c) {
16
+ const W = d * l + u * p + b * y, E = S * l + P * p + I * y, O = m * l + A * p + R * y;
17
+ l = W, p = E, y = O;
18
+ }
19
+ r[g] = l, r[g + 1] = p, r[g + 2] = y;
20
+ }
21
+ }
22
+ const k = /* @__PURE__ */ new WeakMap(), _ = 4;
23
+ function z(r) {
24
+ const o = r.naturalWidth;
25
+ return typeof o == "number" && o > 0 ? o : r.width;
26
+ }
27
+ function K(r) {
28
+ const o = r.naturalHeight;
29
+ return typeof o == "number" && o > 0 ? o : r.height;
30
+ }
31
+ function B(r, o) {
32
+ if (typeof OffscreenCanvas < "u")
33
+ return new OffscreenCanvas(r, o);
34
+ if (typeof document < "u") {
35
+ const a = document.createElement("canvas");
36
+ return a.width = r, a.height = o, a;
37
+ }
38
+ throw new Error("image-adjustments: no canvas implementation available (no OffscreenCanvas, no DOM)");
39
+ }
40
+ function U(r, o) {
41
+ const a = z(r), s = K(r);
42
+ if (a <= 0 || s <= 0)
43
+ throw new Error("image-adjustments: source has no pixels (width/height is 0)");
44
+ const h = B(a, s), e = h.getContext("2d");
45
+ if (!e)
46
+ throw new Error("image-adjustments: could not acquire a 2d context");
47
+ e.drawImage(r, 0, 0, a, s);
48
+ const n = e.getImageData(0, 0, a, s);
49
+ return N(n.data, o), e.putImageData(n, 0, 0), h;
50
+ }
51
+ function C(r, o) {
52
+ if (H(o)) return r;
53
+ const a = j(o);
54
+ let s = k.get(r);
55
+ const h = s == null ? void 0 : s.get(a);
56
+ if (h)
57
+ return s.delete(a), s.set(a, h), h;
58
+ let e;
59
+ try {
60
+ e = U(r, o);
61
+ } catch (n) {
62
+ X.error("adjustment pixel pass failed — rendering UNADJUSTED source:", n), e = r;
63
+ }
64
+ for (s || (s = /* @__PURE__ */ new Map(), k.set(r, s)), s.set(a, e); s.size > _; ) {
65
+ const n = s.keys().next().value;
66
+ s.delete(n);
67
+ }
68
+ return e;
69
+ }
70
+ F(C);
71
+ function G(r, o) {
7
72
  return !r.needsCropPixelConversion || r.cropWidth === void 0 ? r : {
8
73
  ...r,
9
74
  cropX: (r.cropX ?? 0) * o.width,
@@ -13,32 +78,36 @@ function y(r, o) {
13
78
  needsCropPixelConversion: !1
14
79
  };
15
80
  }
16
- function C(r, o, c) {
17
- var a, n;
81
+ function Q(r, o, a) {
82
+ var n, f;
83
+ const s = C(
84
+ a,
85
+ o.adjustments
86
+ );
18
87
  r.save(), r.translate(o.x, o.y), o.rotation && r.rotate(-o.rotation * Math.PI / 180);
19
- let e = 1, h = 1;
20
- if (o.flipHorizontal && (e = -1), o.flipVertical && (h = -1), (e !== 1 || h !== 1) && r.scale(e, h), o.opacity !== void 0 && o.opacity !== 1 && (r.globalAlpha = o.opacity), o.borderRadius && o.borderRadius > 0) {
88
+ let h = 1, e = 1;
89
+ if (o.flipHorizontal && (h = -1), o.flipVertical && (e = -1), (h !== 1 || e !== 1) && r.scale(h, e), o.opacity !== void 0 && o.opacity !== 1 && (r.globalAlpha = o.opacity), o.borderRadius && o.borderRadius > 0) {
21
90
  const i = Math.min(
22
91
  o.borderRadius / 100 * Math.min(o.width, o.height),
23
92
  o.width / 2,
24
93
  o.height / 2
25
94
  );
26
95
  r.beginPath();
27
- const s = -o.width / 2, t = -o.height / 2, d = o.width, f = o.height;
28
- r.moveTo(s + i, t), r.lineTo(s + d - i, t), r.arcTo(s + d, t, s + d, t + i, i), r.lineTo(s + d, t + f - i), r.arcTo(s + d, t + f, s + d - i, t + f, i), r.lineTo(s + i, t + f), r.arcTo(s, t + f, s, t + f - i, i), r.lineTo(s, t + i), r.arcTo(s, t, s + i, t, i), r.closePath(), r.clip();
96
+ const c = -o.width / 2, t = -o.height / 2, d = o.width, u = o.height;
97
+ r.moveTo(c + i, t), r.lineTo(c + d - i, t), r.arcTo(c + d, t, c + d, t + i, i), r.lineTo(c + d, t + u - i), r.arcTo(c + d, t + u, c + d - i, t + u, i), r.lineTo(c + i, t + u), r.arcTo(c, t + u, c, t + u - i, i), r.lineTo(c, t + i), r.arcTo(c, t, c + i, t, i), r.closePath(), r.clip();
29
98
  }
30
99
  if (o.cropX !== void 0 && o.cropWidth !== void 0 ? r.drawImage(
31
- c,
100
+ s,
32
101
  o.cropX,
33
102
  o.cropY || 0,
34
103
  o.cropWidth,
35
- o.cropHeight || c.height,
104
+ o.cropHeight || a.height,
36
105
  -o.width / 2,
37
106
  -o.height / 2,
38
107
  o.width,
39
108
  o.height
40
- ) : r.drawImage(c, -o.width / 2, -o.height / 2, o.width, o.height), r.restore(), (a = o.stroke) != null && a.enabled) {
41
- const i = ((n = o.knockoutParts) == null ? void 0 : n.stroke) === !0, s = {
109
+ ) : r.drawImage(s, -o.width / 2, -o.height / 2, o.width, o.height), r.restore(), (n = o.stroke) != null && n.enabled) {
110
+ const i = ((f = o.knockoutParts) == null ? void 0 : f.stroke) === !0, c = {
42
111
  x: o.x,
43
112
  y: o.y,
44
113
  rotation: o.rotation,
@@ -59,172 +128,172 @@ function C(r, o, c) {
59
128
  else if (typeof document < "u")
60
129
  d = document.createElement("canvas"), d.width = t.width, d.height = t.height;
61
130
  else {
62
- p(r, s);
131
+ M(r, c);
63
132
  return;
64
133
  }
65
- const f = d.getContext("2d");
66
- if (!f) {
67
- p(r, s);
134
+ const u = d.getContext("2d");
135
+ if (!u) {
136
+ M(r, c);
68
137
  return;
69
138
  }
70
- p(f, s, { isKnockout: !0 }), r.save(), r.globalCompositeOperation = "destination-out", r.drawImage(d, 0, 0), r.restore();
139
+ M(u, c, { isKnockout: !0 }), r.save(), r.globalCompositeOperation = "destination-out", r.drawImage(d, 0, 0), r.restore();
71
140
  } else
72
- p(r, s);
141
+ M(r, c);
73
142
  }
74
143
  }
75
- function T(r, o) {
144
+ function Y(r, o) {
76
145
  if (typeof OffscreenCanvas < "u")
77
146
  return new OffscreenCanvas(r, o);
78
- const c = document.createElement("canvas");
79
- return c.width = r, c.height = o, c;
147
+ const a = document.createElement("canvas");
148
+ return a.width = r, a.height = o, a;
80
149
  }
81
- function v(r, o) {
82
- var a;
83
- const c = o.opacity ?? 1, e = !!((a = o.stroke) != null && a.enabled);
84
- c < 1 && e ? k(r, o, c) : g(r, o, c);
150
+ function Z(r, o) {
151
+ var e;
152
+ const a = o.opacity ?? 1, s = !!((e = o.stroke) != null && e.enabled);
153
+ a < 1 && s ? J(r, o, a) : T(r, o, a);
85
154
  }
86
- function k(r, o, c) {
87
- var f;
88
- const e = o.transformData, a = (((f = o.stroke) == null ? void 0 : f.width) || 2) + 2, n = Math.ceil(e.width + a * 2), i = Math.ceil(e.height + a * 2), s = T(n, i), t = s.getContext("2d");
89
- if (!t) {
90
- g(r, o, c);
155
+ function J(r, o, a) {
156
+ var d;
157
+ const s = o.transformData, e = (((d = o.stroke) == null ? void 0 : d.width) || 2) + 2, n = Math.ceil(s.width + e * 2), f = Math.ceil(s.height + e * 2), i = Y(n, f), c = i.getContext("2d");
158
+ if (!c) {
159
+ T(r, o, a);
91
160
  return;
92
161
  }
93
- const d = {
162
+ const t = {
94
163
  ...o,
95
164
  x: n / 2,
96
- y: i / 2
165
+ y: f / 2
97
166
  };
98
- g(t, d, 1), r.save(), r.translate(o.x, o.y), o.rotation && r.rotate(-o.rotation * Math.PI / 180), r.globalAlpha = c, r.drawImage(s, -n / 2, -i / 2), r.restore();
167
+ T(c, t, 1), r.save(), r.translate(o.x, o.y), o.rotation && r.rotate(-o.rotation * Math.PI / 180), r.globalAlpha = a, r.drawImage(i, -n / 2, -f / 2), r.restore();
99
168
  }
100
- function g(r, o, c) {
101
- var a;
102
- const e = o.transformData, h = e.fillOpacity ?? 1;
103
- if (r.save(), r.translate(o.x, o.y), o.rotation && r.rotate(-o.rotation * Math.PI / 180), r.globalAlpha = c * h, r.fillStyle = e.fillColor || "#3b82f6", r.beginPath(), u(r, e), r.fill(), r.restore(), (a = o.stroke) != null && a.enabled) {
169
+ function T(r, o, a) {
170
+ var e;
171
+ const s = o.transformData, h = s.fillOpacity ?? 1;
172
+ if (r.save(), r.translate(o.x, o.y), o.rotation && r.rotate(-o.rotation * Math.PI / 180), r.globalAlpha = a * h, r.fillStyle = s.fillColor || "#3b82f6", r.beginPath(), w(r, s), r.fill(), r.restore(), (e = o.stroke) != null && e.enabled) {
104
173
  r.save(), r.translate(o.x, o.y), o.rotation && r.rotate(-o.rotation * Math.PI / 180);
105
174
  const n = o.stroke;
106
- r.globalAlpha = c * (n.opacity ?? 1), r.strokeStyle = n.color || "#000000", r.lineWidth = n.width || 2, r.lineCap = n.lineCap || "round", r.lineJoin = n.lineJoin || "round", r.beginPath(), u(r, e), r.stroke(), r.restore();
175
+ r.globalAlpha = a * (n.opacity ?? 1), r.strokeStyle = n.color || "#000000", r.lineWidth = n.width || 2, r.lineCap = n.lineCap || "round", r.lineJoin = n.lineJoin || "round", r.beginPath(), w(r, s), r.stroke(), r.restore();
107
176
  }
108
177
  }
109
- function u(r, o) {
110
- const { shapeType: c, width: e, height: h } = o;
111
- switch (c) {
178
+ function w(r, o) {
179
+ const { shapeType: a, width: s, height: h } = o;
180
+ switch (a) {
112
181
  case "rectangle": {
113
- const a = o.borderRadius || 0, n = -e / 2, i = -h / 2;
114
- if (a > 0) {
115
- const s = Math.min(a / 100 * Math.min(e, h), e / 2, h / 2);
116
- r.roundRect(n, i, e, h, s);
182
+ const e = o.borderRadius || 0, n = -s / 2, f = -h / 2;
183
+ if (e > 0) {
184
+ const i = Math.min(e / 100 * Math.min(s, h), s / 2, h / 2);
185
+ r.roundRect(n, f, s, h, i);
117
186
  } else
118
- r.rect(n, i, e, h);
187
+ r.rect(n, f, s, h);
119
188
  break;
120
189
  }
121
190
  case "circle": {
122
- const a = Math.min(e, h) / 2;
123
- r.arc(0, 0, a, 0, Math.PI * 2);
191
+ const e = Math.min(s, h) / 2;
192
+ r.arc(0, 0, e, 0, Math.PI * 2);
124
193
  break;
125
194
  }
126
195
  case "ellipse": {
127
- r.ellipse(0, 0, e / 2, h / 2, 0, 0, Math.PI * 2);
196
+ r.ellipse(0, 0, s / 2, h / 2, 0, 0, Math.PI * 2);
128
197
  break;
129
198
  }
130
199
  case "triangle": {
131
- const a = e / 2, n = h / 2;
132
- r.moveTo(0, -n), r.lineTo(a, n), r.lineTo(-a, n), r.closePath();
200
+ const e = s / 2, n = h / 2;
201
+ r.moveTo(0, -n), r.lineTo(e, n), r.lineTo(-e, n), r.closePath();
133
202
  break;
134
203
  }
135
204
  case "polygon": {
136
- const a = o.sides || 5, n = Math.min(e, h) / 2;
137
- for (let i = 0; i < a; i++) {
138
- const s = i * 2 * Math.PI / a - Math.PI / 2, t = n * Math.cos(s), d = n * Math.sin(s);
139
- i === 0 ? r.moveTo(t, d) : r.lineTo(t, d);
205
+ const e = o.sides || 5, n = Math.min(s, h) / 2;
206
+ for (let f = 0; f < e; f++) {
207
+ const i = f * 2 * Math.PI / e - Math.PI / 2, c = n * Math.cos(i), t = n * Math.sin(i);
208
+ f === 0 ? r.moveTo(c, t) : r.lineTo(c, t);
140
209
  }
141
210
  r.closePath();
142
211
  break;
143
212
  }
144
213
  case "star": {
145
- const a = o.points || 5, n = Math.min(e, h) / 2, i = n * (o.innerRadius || 0.4);
146
- for (let s = 0; s < a * 2; s++) {
147
- const t = s * Math.PI / a - Math.PI / 2, d = s % 2 === 0 ? n : i, f = d * Math.cos(t), l = d * Math.sin(t);
148
- s === 0 ? r.moveTo(f, l) : r.lineTo(f, l);
214
+ const e = o.points || 5, n = Math.min(s, h) / 2, f = n * (o.innerRadius || 0.4);
215
+ for (let i = 0; i < e * 2; i++) {
216
+ const c = i * Math.PI / e - Math.PI / 2, t = i % 2 === 0 ? n : f, d = t * Math.cos(c), u = t * Math.sin(c);
217
+ i === 0 ? r.moveTo(d, u) : r.lineTo(d, u);
149
218
  }
150
219
  r.closePath();
151
220
  break;
152
221
  }
153
222
  case "line": {
154
- const a = e / 2, n = h / 2;
155
- r.rect(-a, -n, e, h);
223
+ const e = s / 2, n = h / 2;
224
+ r.rect(-e, -n, s, h);
156
225
  break;
157
226
  }
158
227
  default:
159
- r.rect(-e / 2, -h / 2, e, h);
228
+ r.rect(-s / 2, -h / 2, s, h);
160
229
  }
161
230
  }
162
231
  export {
163
- w as AlignmentSnapSystem,
164
- Hr as AnyElementConfigSchema,
165
- Or as AnyTransformDataSchema,
166
- cr as ArchTransform,
167
- fr as ArtboardManager,
168
- b as ArtboardRenderer,
169
- pr as AscendTransform,
170
- I as CanvasRenderer,
171
- Fr as CanvasStateV1Schema,
172
- gr as CircleTransform,
173
- lr as CustomTransform,
174
- O as DEFAULT_MAX_BITMAP_DIMENSION,
175
- ur as ElementStore,
176
- F as ExportManager,
177
- Tr as FlagTransform,
178
- kr as HybridHistoryManager,
179
- L as ImportManager,
180
- R as InteractionStateMachine,
181
- Mr as LeanTransform,
182
- yr as LogLevel,
183
- W as ResizePipeline,
184
- Lr as SerializedArtboardSchema,
185
- A as SpacingSystem,
186
- E as TransformHandles,
187
- Cr as WaveTransform,
188
- X as WorkerExportManager,
189
- z as applySpaceLayoutRules,
190
- K as applyStrokeStyle,
191
- Y as buildFontString,
192
- vr as calculateFixedCornerPosition,
193
- Sr as calculateResizeHandles,
194
- Pr as calculateRotationHandlePosition,
195
- B as clearImageBitmapCache,
196
- _ as clearMaskCache,
197
- J as clearRegisteredBitmapKeys,
198
- N as createCirclePath,
199
- V as createImagePath,
200
- wr as createLogger,
201
- j as createRectPath,
202
- q as createTextPath,
203
- Q as getFontMetrics,
204
- U as getSharedWorkerExportManager,
205
- br as hitTestCircle,
206
- Ir as hitTestRect,
207
- Z as invalidateMaskCache,
208
- Rr as logger,
209
- $ as measureTextWidth,
210
- m as removeFromImageBitmapCache,
211
- G as renderCustomTransform,
212
- C as renderImageElement,
213
- p as renderImageStroke,
214
- x as renderMultilineText,
215
- D as renderPathStroke,
216
- rr as renderRichTextFillOnly,
217
- v as renderShapeElement,
218
- or as renderTextElement,
219
- sr as renderTextFillOnly,
220
- ir as renderTextStroke,
221
- er as renderWithKnockout,
222
- ar as renderWithMasks,
223
- y as resolveServerCrop,
224
- Wr as splitRichTextIntoLines,
225
- nr as terminateSharedWorkerExportManager,
226
- tr as unregisterElementBitmaps,
227
- Ar as wrapRichTextSpans,
228
- hr as wrapText
232
+ D as AlignmentSnapSystem,
233
+ to as AnyElementConfigSchema,
234
+ ao as AnyTransformDataSchema,
235
+ _r as ArchTransform,
236
+ zr as ArtboardManager,
237
+ rr as ArtboardRenderer,
238
+ Kr as AscendTransform,
239
+ or as CanvasRenderer,
240
+ io as CanvasStateV1Schema,
241
+ Br as CircleTransform,
242
+ Ur as CustomTransform,
243
+ ir as DEFAULT_MAX_BITMAP_DIMENSION,
244
+ Yr as ElementStore,
245
+ hr as ExportManager,
246
+ Jr as FlagTransform,
247
+ Vr as HybridHistoryManager,
248
+ cr as ImportManager,
249
+ sr as InteractionStateMachine,
250
+ qr as LeanTransform,
251
+ Gr as LogLevel,
252
+ er as ResizePipeline,
253
+ ho as SerializedArtboardSchema,
254
+ nr as SpacingSystem,
255
+ tr as TransformHandles,
256
+ Qr as WaveTransform,
257
+ dr as WorkerExportManager,
258
+ fr as applySpaceLayoutRules,
259
+ ur as applyStrokeStyle,
260
+ gr as buildFontString,
261
+ Zr as calculateFixedCornerPosition,
262
+ $r as calculateResizeHandles,
263
+ xr as calculateRotationHandlePosition,
264
+ lr as clearImageBitmapCache,
265
+ pr as clearMaskCache,
266
+ yr as clearRegisteredBitmapKeys,
267
+ Mr as createCirclePath,
268
+ vr as createImagePath,
269
+ L as createLogger,
270
+ Tr as createRectPath,
271
+ kr as createTextPath,
272
+ wr as getFontMetrics,
273
+ Cr as getSharedWorkerExportManager,
274
+ Dr as hitTestCircle,
275
+ ro as hitTestRect,
276
+ br as invalidateMaskCache,
277
+ oo as logger,
278
+ Sr as measureTextWidth,
279
+ Pr as removeFromImageBitmapCache,
280
+ Ir as renderCustomTransform,
281
+ Q as renderImageElement,
282
+ M as renderImageStroke,
283
+ mr as renderMultilineText,
284
+ Ar as renderPathStroke,
285
+ Rr as renderRichTextFillOnly,
286
+ Z as renderShapeElement,
287
+ Wr as renderTextElement,
288
+ Er as renderTextFillOnly,
289
+ Or as renderTextStroke,
290
+ Hr as renderWithKnockout,
291
+ jr as renderWithMasks,
292
+ G as resolveServerCrop,
293
+ so as splitRichTextIntoLines,
294
+ Lr as terminateSharedWorkerExportManager,
295
+ Fr as unregisterElementBitmaps,
296
+ eo as wrapRichTextSpans,
297
+ Xr as wrapText
229
298
  };
230
299
  //# sourceMappingURL=internals.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"internals.mjs","sources":["../src/rendering/image-renderer.ts","../src/rendering/shape-renderer.ts"],"sourcesContent":["/**\n * Image Renderer - Image element rendering with crop, flip, border radius, and stroke.\n *\n * Works in both main thread and Web Worker (via OffscreenCanvas).\n */\n\nimport type { ImageElementConfig } from '../types/index.js';\nimport { renderImageStroke } from './StrokeRenderer.js';\nimport type { RenderContext, SerializedImageElement } from './renderer-types.js';\n\n/**\n * Convert a server-render image element's crop rectangle from FRACTIONS (0–1)\n * to source pixels, using the fetched bitmap's real dimensions.\n *\n * The server-render serializers (`serializeStateForServer`,\n * `flattenImageElement`) store crop as fractions and stamp\n * `needsCropPixelConversion` because they have no bitmap at serialize time. The\n * export worker holds the bitmap and calls this right before drawing. Without\n * it, `drawImage` reads e.g. `cropWidth=1` as a 1px source rect and stretches a\n * single corner pixel across the whole element — the layer renders as a solid\n * black/transparent block. Mirrors the client-side conversion in\n * `element-serializer` (which runs at serialize time because the client DOES\n * have the bitmap). No-op unless the flag is set and a crop is present, so the\n * already-pixel client path is untouched and the conversion can't run twice.\n */\nexport function resolveServerCrop(\n el: SerializedImageElement,\n bitmap: { width: number; height: number }\n): SerializedImageElement {\n if (!el.needsCropPixelConversion || el.cropWidth === undefined) return el;\n return {\n ...el,\n cropX: (el.cropX ?? 0) * bitmap.width,\n cropY: (el.cropY ?? 0) * bitmap.height,\n cropWidth: el.cropWidth * bitmap.width,\n cropHeight: (el.cropHeight ?? 1) * bitmap.height,\n needsCropPixelConversion: false,\n };\n}\n\n/**\n * Render an image element using ImageBitmap\n * Works in both main thread and worker\n */\nexport function renderImageElement(\n ctx: RenderContext,\n elementData: SerializedImageElement,\n imageBitmap: ImageBitmap\n): void {\n ctx.save();\n\n // Translate to element position\n ctx.translate(elementData.x, elementData.y);\n\n // Apply rotation (negative for clockwise)\n if (elementData.rotation) {\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n }\n\n // Apply flip transformations\n let scaleX = 1;\n let scaleY = 1;\n if (elementData.flipHorizontal) scaleX = -1;\n if (elementData.flipVertical) scaleY = -1;\n if (scaleX !== 1 || scaleY !== 1) {\n ctx.scale(scaleX, scaleY);\n }\n\n // Apply opacity if specified\n if (elementData.opacity !== undefined && elementData.opacity !== 1) {\n ctx.globalAlpha = elementData.opacity;\n }\n\n // Apply border radius if specified (borderRadius is stored as percentage 0-100)\n if (elementData.borderRadius && elementData.borderRadius > 0) {\n const radius = Math.min(\n (elementData.borderRadius / 100) * Math.min(elementData.width, elementData.height),\n elementData.width / 2,\n elementData.height / 2\n );\n\n // Create clipping path for rounded corners\n ctx.beginPath();\n const x = -elementData.width / 2;\n const y = -elementData.height / 2;\n const w = elementData.width;\n const h = elementData.height;\n\n ctx.moveTo(x + radius, y);\n ctx.lineTo(x + w - radius, y);\n ctx.arcTo(x + w, y, x + w, y + radius, radius);\n ctx.lineTo(x + w, y + h - radius);\n ctx.arcTo(x + w, y + h, x + w - radius, y + h, radius);\n ctx.lineTo(x + radius, y + h);\n ctx.arcTo(x, y + h, x, y + h - radius, radius);\n ctx.lineTo(x, y + radius);\n ctx.arcTo(x, y, x + radius, y, radius);\n ctx.closePath();\n ctx.clip();\n }\n\n // Draw the image\n if (elementData.cropX !== undefined && elementData.cropWidth !== undefined) {\n // Draw with crop\n ctx.drawImage(\n imageBitmap,\n elementData.cropX,\n elementData.cropY || 0,\n elementData.cropWidth,\n elementData.cropHeight || imageBitmap.height,\n -elementData.width / 2,\n -elementData.height / 2,\n elementData.width,\n elementData.height\n );\n } else {\n // Draw without crop (centered)\n ctx.drawImage(imageBitmap, -elementData.width / 2, -elementData.height / 2, elementData.width, elementData.height);\n }\n\n ctx.restore();\n\n // Render stroke if enabled (after restore to apply fresh transforms)\n if (elementData.stroke?.enabled) {\n const hasKnockoutStroke = elementData.knockoutParts?.stroke === true;\n\n const imageConfig: ImageElementConfig = {\n transformType: 'image',\n x: elementData.x,\n y: elementData.y,\n rotation: elementData.rotation,\n stroke: elementData.stroke,\n knockoutParts: elementData.knockoutParts,\n transformData: {\n type: 'image',\n width: elementData.width,\n height: elementData.height,\n borderRadius: elementData.borderRadius || 0,\n },\n };\n\n if (hasKnockoutStroke) {\n // For knockout stroke, use offscreen canvas approach\n const canvas = ctx.canvas;\n let offscreen: HTMLCanvasElement | OffscreenCanvas;\n\n if (typeof OffscreenCanvas !== 'undefined') {\n offscreen = new OffscreenCanvas(canvas.width, canvas.height);\n } else if (typeof document !== 'undefined') {\n offscreen = document.createElement('canvas');\n offscreen.width = canvas.width;\n offscreen.height = canvas.height;\n } else {\n renderImageStroke(ctx, imageConfig);\n return;\n }\n\n const offCtx = offscreen.getContext('2d');\n if (!offCtx) {\n renderImageStroke(ctx, imageConfig);\n return;\n }\n\n // Render the stroke to the offscreen canvas with knockout flag\n renderImageStroke(offCtx, imageConfig, { isKnockout: true });\n\n // Composite with destination-out\n ctx.save();\n ctx.globalCompositeOperation = 'destination-out';\n ctx.drawImage(offscreen as CanvasImageSource, 0, 0);\n ctx.restore();\n } else {\n // Normal stroke\n renderImageStroke(ctx, imageConfig);\n }\n }\n}\n","/**\n * Shape Renderer - Shape element rendering (rectangle, circle, ellipse, triangle, etc.)\n *\n * Works in both main thread and Web Worker (via OffscreenCanvas).\n *\n * Opacity strategy: When a shape has both element-level opacity AND a stroke,\n * we render fill+stroke at full opacity on a temporary canvas, then composite\n * the result at the element's opacity. This prevents fill/stroke overlap from\n * compounding (e.g., 50% fill + 50% stroke overlap ≠ 75% opaque).\n */\n\nimport type { RenderContext, SerializedShapeElement } from './renderer-types.js';\n\ntype CanvasLike = HTMLCanvasElement | OffscreenCanvas;\n\nfunction createTempCanvas(width: number, height: number): CanvasLike {\n if (typeof OffscreenCanvas !== 'undefined') {\n return new OffscreenCanvas(width, height);\n }\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return canvas;\n}\n\n/**\n * Render a shape element\n * Works in both main thread and worker\n */\nexport function renderShapeElement(ctx: RenderContext, elementData: SerializedShapeElement): void {\n const elementOpacity = elementData.opacity ?? 1;\n const hasStroke = !!elementData.stroke?.enabled;\n const needsOffscreen = elementOpacity < 1 && hasStroke;\n\n if (needsOffscreen) {\n // Render fill+stroke at full opacity on a temp canvas, then composite\n renderShapeWithOffscreen(ctx, elementData, elementOpacity);\n } else {\n // No compounding risk — render directly\n renderShapeDirect(ctx, elementData, elementOpacity);\n }\n}\n\n/**\n * Render shape via temporary canvas to avoid fill/stroke opacity compounding.\n */\nfunction renderShapeWithOffscreen(\n ctx: RenderContext,\n elementData: SerializedShapeElement,\n elementOpacity: number\n): void {\n const td = elementData.transformData;\n const strokeWidth = elementData.stroke?.width || 2;\n // Pad temp canvas to fit the stroke (strokes extend beyond the shape bounds)\n const padding = strokeWidth + 2;\n const offW = Math.ceil(td.width + padding * 2);\n const offH = Math.ceil(td.height + padding * 2);\n\n const offCanvas = createTempCanvas(offW, offH);\n const offCtx = offCanvas.getContext('2d') as RenderContext;\n if (!offCtx) {\n // Fallback to direct render\n renderShapeDirect(ctx, elementData, elementOpacity);\n return;\n }\n\n // Render at center of temp canvas with full opacity (elementOpacity = 1)\n const tempData: SerializedShapeElement = {\n ...elementData,\n x: offW / 2,\n y: offH / 2,\n opacity: 1,\n };\n renderShapeDirect(offCtx, tempData, 1);\n\n // Composite temp canvas onto main canvas at element opacity\n ctx.save();\n\n // Position: elementData.(x,y) is the shape center, temp canvas center is (offW/2, offH/2)\n // We need to account for rotation around the element center\n ctx.translate(elementData.x, elementData.y);\n if (elementData.rotation) {\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n }\n\n ctx.globalAlpha = elementOpacity;\n ctx.drawImage(offCanvas, -offW / 2, -offH / 2);\n ctx.restore();\n}\n\n/**\n * Render shape directly onto ctx (no offscreen). Used when there's no\n * compounding risk, or as the inner render for the offscreen path.\n */\nfunction renderShapeDirect(\n ctx: RenderContext,\n elementData: SerializedShapeElement,\n elementOpacity: number\n): void {\n const transformData = elementData.transformData;\n const fillOpacity = transformData.fillOpacity ?? 1;\n\n ctx.save();\n ctx.translate(elementData.x, elementData.y);\n\n if (elementData.rotation) {\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n }\n\n // Fill\n ctx.globalAlpha = elementOpacity * fillOpacity;\n ctx.fillStyle = transformData.fillColor || '#3b82f6';\n ctx.beginPath();\n traceShapePath(ctx, transformData);\n ctx.fill();\n ctx.restore();\n\n // Stroke\n if (elementData.stroke?.enabled) {\n ctx.save();\n ctx.translate(elementData.x, elementData.y);\n if (elementData.rotation) {\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n }\n\n const stroke = elementData.stroke;\n ctx.globalAlpha = elementOpacity * (stroke.opacity ?? 1);\n ctx.strokeStyle = stroke.color || '#000000';\n ctx.lineWidth = stroke.width || 2;\n ctx.lineCap = stroke.lineCap || 'round';\n ctx.lineJoin = stroke.lineJoin || 'round';\n\n ctx.beginPath();\n traceShapePath(ctx, transformData);\n ctx.stroke();\n ctx.restore();\n }\n}\n\n/**\n * Trace the shape path (without fill/stroke) — reusable for both fill and stroke passes.\n */\nfunction traceShapePath(\n ctx: RenderContext,\n transformData: SerializedShapeElement['transformData']\n): void {\n const { shapeType, width, height } = transformData;\n\n switch (shapeType) {\n case 'rectangle': {\n const borderRadius = transformData.borderRadius || 0;\n const x = -width / 2;\n const y = -height / 2;\n\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(width, height), width / 2, height / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n break;\n }\n\n case 'circle': {\n const radius = Math.min(width, height) / 2;\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n break;\n }\n\n case 'ellipse': {\n ctx.ellipse(0, 0, width / 2, height / 2, 0, 0, Math.PI * 2);\n break;\n }\n\n case 'triangle': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.moveTo(0, -halfHeight);\n ctx.lineTo(halfWidth, halfHeight);\n ctx.lineTo(-halfWidth, halfHeight);\n ctx.closePath();\n break;\n }\n\n case 'polygon': {\n const sides = transformData.sides || 5;\n const radius = Math.min(width, height) / 2;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const px = radius * Math.cos(angle);\n const py = radius * Math.sin(angle);\n if (i === 0) {\n ctx.moveTo(px, py);\n } else {\n ctx.lineTo(px, py);\n }\n }\n ctx.closePath();\n break;\n }\n\n case 'star': {\n const points = transformData.points || 5;\n const outerRadius = Math.min(width, height) / 2;\n const innerRadius = outerRadius * (transformData.innerRadius || 0.4);\n for (let i = 0; i < points * 2; i++) {\n const angle = (i * Math.PI) / points - Math.PI / 2;\n const r = i % 2 === 0 ? outerRadius : innerRadius;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (i === 0) {\n ctx.moveTo(px, py);\n } else {\n ctx.lineTo(px, py);\n }\n }\n ctx.closePath();\n break;\n }\n\n case 'line': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.rect(-halfWidth, -halfHeight, width, height);\n break;\n }\n\n default:\n ctx.rect(-width / 2, -height / 2, width, height);\n }\n}\n"],"names":["resolveServerCrop","el","bitmap","renderImageElement","ctx","elementData","imageBitmap","scaleX","scaleY","radius","x","y","w","h","_a","hasKnockoutStroke","_b","imageConfig","canvas","offscreen","renderImageStroke","offCtx","createTempCanvas","width","height","renderShapeElement","elementOpacity","hasStroke","renderShapeWithOffscreen","renderShapeDirect","td","padding","offW","offH","offCanvas","tempData","transformData","fillOpacity","traceShapePath","stroke","shapeType","borderRadius","halfWidth","halfHeight","sides","angle","px","py","points","outerRadius","innerRadius","i","r"],"mappings":"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"}
1
+ {"version":3,"file":"internals.mjs","sources":["../src/rendering/image-adjustments.ts","../src/rendering/image-renderer.ts","../src/rendering/shape-renderer.ts"],"sourcesContent":["/**\n * Image adjustments (brightness / contrast / saturation) — the SINGLE pixel\n * pipeline shared by the live editor renderer and the export worker.\n *\n * This is the HEAVY side of the seam in `src/core/image-adjustments.ts`\n * (D9 element/renderer split): importing this module registers the\n * adjusted-source provider that `ImageElement.render()` looks up at paint\n * time. `rendering/element-painters.ts` imports it, so every paint driver\n * (CanvasRenderer, ExportManager, the export worker) carries it; the\n * no-canvas state graph never does.\n *\n * ## Why a pixel pass and not `ctx.filter`\n *\n * `CanvasRenderingContext2D.filter` is NOT available on Safari / iOS Safari:\n * caniuse (mdn-api_canvasrenderingcontext2d_filter, checked 2026-06) lists\n * Safari ≤ 17.6 \"Not supported\" and Safari 18.0 → 26.x \"Disabled by default\"\n * (same for iOS). iOS Safari is a hard requirement for this package, so the\n * pixel pass must exist regardless — and once it exists, using it EVERYWHERE\n * (instead of a ctx.filter fast path on Chromium/Firefox) guarantees the live\n * canvas, the client export worker, and the server renderer produce identical\n * pixels with one implementation. Browser-native filter pipelines differ in\n * intermediate precision, so a hybrid risks live-vs-export drift for zero\n * functional gain.\n *\n * ## Performance contract\n *\n * The pass runs ONCE per (source, adjustments) pair and the result is cached\n * (see {@link getAdjustedImageSource}); the 60fps render loop only ever pays\n * one extra `drawImage` of the cached canvas. Per-frame `getImageData` never\n * happens.\n *\n * ## Math\n *\n * Equivalent to the CSS Filter Effects spec functions, applied in the fixed\n * order `brightness() contrast() saturate()` on non-premultiplied sRGB:\n *\n * brightness(b): v' = v·b\n * contrast(c): v' = v·c + 127.5·(1 − c)\n * saturate(s): feColorMatrix type=\"saturate\" (luma 0.213 / 0.715 / 0.072)\n *\n * Alpha is untouched. Results clamp to [0, 255] via Uint8ClampedArray.\n *\n * Worker-safe: no DOM access at module level; the canvas factory prefers\n * `OffscreenCanvas` and falls back to `document.createElement('canvas')`.\n */\n\nimport type { ImageAdjustments } from '../types/index.js';\nimport {\n isNeutralAdjustments,\n adjustmentsCacheKey,\n registerAdjustedImageSourceProvider,\n type AdjustableImageSource,\n} from '../core/image-adjustments.js';\nimport { createLogger } from '../utils/logger.js';\n\n// Re-export the model-side helpers so heavy-side consumers (serializers,\n// worker renderer, tests) have a single import surface.\nexport {\n NEUTRAL_ADJUSTMENTS,\n isNeutralAdjustments,\n normalizeAdjustments,\n adjustmentsCacheKey,\n type AdjustableImageSource,\n} from '../core/image-adjustments.js';\n\nconst logger = createLogger('image-adjustments');\n\nconst EPSILON = 0.001;\n\n/**\n * Apply adjustments to raw RGBA pixels IN PLACE.\n *\n * Pure (no canvas, no DOM) — safe in workers and directly unit-testable.\n * Brightness and contrast are fused into a single 256-entry LUT; saturation\n * applies the feColorMatrix \"saturate\" matrix per pixel (skipped when ~1).\n */\nexport function applyAdjustmentsToPixels(data: Uint8ClampedArray, adjustments: ImageAdjustments): void {\n const { brightness, contrast, saturation } = adjustments;\n\n // Fused brightness+contrast transfer: v' = (v·b)·c + 127.5·(1 − c)\n const slope = brightness * contrast;\n const intercept = 127.5 * (1 - contrast);\n const applyLut = Math.abs(slope - 1) > EPSILON || Math.abs(intercept) > EPSILON;\n const lut = new Uint8ClampedArray(256);\n if (applyLut) {\n for (let v = 0; v < 256; v++) lut[v] = v * slope + intercept; // clamps on write\n }\n\n const applySaturation = Math.abs(saturation - 1) > EPSILON;\n // feColorMatrix type=\"saturate\" coefficients (Filter Effects spec).\n const s = saturation;\n const rr = 0.213 + 0.787 * s;\n const rg = 0.715 - 0.715 * s;\n const rb = 0.072 - 0.072 * s;\n const gr = 0.213 - 0.213 * s;\n const gg = 0.715 + 0.285 * s;\n const gb = 0.072 - 0.072 * s;\n const br = 0.213 - 0.213 * s;\n const bg = 0.715 - 0.715 * s;\n const bb = 0.072 + 0.928 * s;\n\n for (let i = 0; i < data.length; i += 4) {\n let r = data[i];\n let g = data[i + 1];\n let b = data[i + 2];\n\n if (applyLut) {\n r = lut[r];\n g = lut[g];\n b = lut[b];\n }\n\n if (applySaturation) {\n const r2 = rr * r + rg * g + rb * b;\n const g2 = gr * r + gg * g + gb * b;\n const b2 = br * r + bg * g + bb * b;\n r = r2;\n g = g2;\n b = b2;\n }\n\n data[i] = r;\n data[i + 1] = g;\n data[i + 2] = b;\n // alpha (i + 3) untouched\n }\n}\n\n// ---------------------------------------------------------------------------\n// Adjusted-source cache\n// ---------------------------------------------------------------------------\n\n/**\n * Per-source LRU of adjusted variants. WeakMap keyed by the SOURCE OBJECT so\n * entries vanish with the source (e.g. when ImageCache evicts an image or the\n * worker clears a bitmap). The inner map is bounded — slider drags produce a\n * stream of distinct adjustment values and must not accumulate full-size\n * canvases.\n */\nconst adjustedSourceCache = new WeakMap<object, Map<string, AdjustableImageSource>>();\n\n/** Max adjusted variants kept per source (oldest evicted first). */\nconst MAX_VARIANTS_PER_SOURCE = 4;\n\nfunction sourcePixelWidth(source: AdjustableImageSource): number {\n const natural = (source as HTMLImageElement).naturalWidth;\n return typeof natural === 'number' && natural > 0 ? natural : source.width;\n}\n\nfunction sourcePixelHeight(source: AdjustableImageSource): number {\n const natural = (source as HTMLImageElement).naturalHeight;\n return typeof natural === 'number' && natural > 0 ? natural : source.height;\n}\n\n/** Create a 2D canvas in whatever environment we're in (worker or DOM). */\nfunction createCanvas(width: number, height: number): HTMLCanvasElement | OffscreenCanvas {\n if (typeof OffscreenCanvas !== 'undefined') {\n return new OffscreenCanvas(width, height);\n }\n if (typeof document !== 'undefined') {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return canvas;\n }\n throw new Error('image-adjustments: no canvas implementation available (no OffscreenCanvas, no DOM)');\n}\n\n/** Run the pixel pass once, producing an adjusted copy of the source. */\nfunction renderAdjustedSource(\n source: AdjustableImageSource,\n adjustments: ImageAdjustments\n): AdjustableImageSource {\n const width = sourcePixelWidth(source);\n const height = sourcePixelHeight(source);\n if (width <= 0 || height <= 0) {\n throw new Error('image-adjustments: source has no pixels (width/height is 0)');\n }\n\n const canvas = createCanvas(width, height);\n const ctx = canvas.getContext('2d') as\n | CanvasRenderingContext2D\n | OffscreenCanvasRenderingContext2D\n | null;\n if (!ctx) {\n throw new Error('image-adjustments: could not acquire a 2d context');\n }\n\n ctx.drawImage(source, 0, 0, width, height);\n // Throws SecurityError on a tainted canvas — all canvas-bound images load\n // with crossOrigin=\"anonymous\" (CLAUDE.md gotcha #13), so taint here means\n // a host bug; the catch in getAdjustedImageSource degrades + caches.\n const imageData = ctx.getImageData(0, 0, width, height);\n applyAdjustmentsToPixels(imageData.data, adjustments);\n ctx.putImageData(imageData, 0, 0);\n return canvas as AdjustableImageSource;\n}\n\n/**\n * Get a drawable source with the adjustments baked in. Neutral adjustments\n * return the original source unchanged.\n *\n * Cached per (source object, adjustments value): the pixel pass runs once per\n * distinct pair; every subsequent call — i.e. every render frame — is a map\n * lookup. FAILED passes are cached too (as the original source) so a broken\n * environment degrades to \"no adjustment applied\" without retrying the\n * expensive pass every frame (the gotcha-#28 retry-loop lesson).\n */\nexport function getAdjustedImageSource(\n source: AdjustableImageSource,\n adjustments: ImageAdjustments | undefined | null\n): AdjustableImageSource {\n if (isNeutralAdjustments(adjustments)) return source;\n\n const key = adjustmentsCacheKey(adjustments);\n let variants = adjustedSourceCache.get(source);\n const cached = variants?.get(key);\n if (cached) {\n // Refresh LRU position.\n variants!.delete(key);\n variants!.set(key, cached);\n return cached;\n }\n\n let result: AdjustableImageSource;\n try {\n result = renderAdjustedSource(source, adjustments!);\n } catch (error) {\n logger.error('adjustment pixel pass failed — rendering UNADJUSTED source:', error);\n result = source;\n }\n\n if (!variants) {\n variants = new Map();\n adjustedSourceCache.set(source, variants);\n }\n variants.set(key, result);\n while (variants.size > MAX_VARIANTS_PER_SOURCE) {\n const oldest = variants.keys().next().value as string;\n variants.delete(oldest);\n }\n\n return result;\n}\n\n/** Test-only: drop every cached variant for a source. */\nexport function clearAdjustedSourceCacheFor(source: object): void {\n adjustedSourceCache.delete(source);\n}\n\n// Register as THE adjusted-source provider for the element model\n// (core/image-adjustments.ts seam) at module evaluation.\nregisterAdjustedImageSourceProvider(getAdjustedImageSource);\n","/**\n * Image Renderer - Image element rendering with crop, flip, border radius, and stroke.\n *\n * Works in both main thread and Web Worker (via OffscreenCanvas).\n */\n\nimport type { ImageElementConfig } from '../types/index.js';\nimport { renderImageStroke } from './StrokeRenderer.js';\nimport { getAdjustedImageSource, type AdjustableImageSource } from './image-adjustments.js';\nimport type { RenderContext, SerializedImageElement } from './renderer-types.js';\n\n/**\n * Convert a server-render image element's crop rectangle from FRACTIONS (0–1)\n * to source pixels, using the fetched bitmap's real dimensions.\n *\n * The server-render serializers (`serializeStateForServer`,\n * `flattenImageElement`) store crop as fractions and stamp\n * `needsCropPixelConversion` because they have no bitmap at serialize time. The\n * export worker holds the bitmap and calls this right before drawing. Without\n * it, `drawImage` reads e.g. `cropWidth=1` as a 1px source rect and stretches a\n * single corner pixel across the whole element — the layer renders as a solid\n * black/transparent block. Mirrors the client-side conversion in\n * `element-serializer` (which runs at serialize time because the client DOES\n * have the bitmap). No-op unless the flag is set and a crop is present, so the\n * already-pixel client path is untouched and the conversion can't run twice.\n */\nexport function resolveServerCrop(\n el: SerializedImageElement,\n bitmap: { width: number; height: number }\n): SerializedImageElement {\n if (!el.needsCropPixelConversion || el.cropWidth === undefined) return el;\n return {\n ...el,\n cropX: (el.cropX ?? 0) * bitmap.width,\n cropY: (el.cropY ?? 0) * bitmap.height,\n cropWidth: el.cropWidth * bitmap.width,\n cropHeight: (el.cropHeight ?? 1) * bitmap.height,\n needsCropPixelConversion: false,\n };\n}\n\n/**\n * Render an image element using ImageBitmap\n * Works in both main thread and worker\n */\nexport function renderImageElement(\n ctx: RenderContext,\n elementData: SerializedImageElement,\n imageBitmap: ImageBitmap\n): void {\n // Brightness/contrast/saturation: bake into a cached copy of the source\n // bitmap FIRST (no-op when neutral/absent) so crop, flip and border-radius\n // below composite adjusted pixels — same order as the live ImageElement\n // renderer. Crop source rects stay valid: the adjusted copy has identical\n // pixel dimensions.\n const source = getAdjustedImageSource(\n imageBitmap as AdjustableImageSource,\n elementData.adjustments\n );\n\n ctx.save();\n\n // Translate to element position\n ctx.translate(elementData.x, elementData.y);\n\n // Apply rotation (negative for clockwise)\n if (elementData.rotation) {\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n }\n\n // Apply flip transformations\n let scaleX = 1;\n let scaleY = 1;\n if (elementData.flipHorizontal) scaleX = -1;\n if (elementData.flipVertical) scaleY = -1;\n if (scaleX !== 1 || scaleY !== 1) {\n ctx.scale(scaleX, scaleY);\n }\n\n // Apply opacity if specified\n if (elementData.opacity !== undefined && elementData.opacity !== 1) {\n ctx.globalAlpha = elementData.opacity;\n }\n\n // Apply border radius if specified (borderRadius is stored as percentage 0-100)\n if (elementData.borderRadius && elementData.borderRadius > 0) {\n const radius = Math.min(\n (elementData.borderRadius / 100) * Math.min(elementData.width, elementData.height),\n elementData.width / 2,\n elementData.height / 2\n );\n\n // Create clipping path for rounded corners\n ctx.beginPath();\n const x = -elementData.width / 2;\n const y = -elementData.height / 2;\n const w = elementData.width;\n const h = elementData.height;\n\n ctx.moveTo(x + radius, y);\n ctx.lineTo(x + w - radius, y);\n ctx.arcTo(x + w, y, x + w, y + radius, radius);\n ctx.lineTo(x + w, y + h - radius);\n ctx.arcTo(x + w, y + h, x + w - radius, y + h, radius);\n ctx.lineTo(x + radius, y + h);\n ctx.arcTo(x, y + h, x, y + h - radius, radius);\n ctx.lineTo(x, y + radius);\n ctx.arcTo(x, y, x + radius, y, radius);\n ctx.closePath();\n ctx.clip();\n }\n\n // Draw the image\n if (elementData.cropX !== undefined && elementData.cropWidth !== undefined) {\n // Draw with crop\n ctx.drawImage(\n source,\n elementData.cropX,\n elementData.cropY || 0,\n elementData.cropWidth,\n elementData.cropHeight || imageBitmap.height,\n -elementData.width / 2,\n -elementData.height / 2,\n elementData.width,\n elementData.height\n );\n } else {\n // Draw without crop (centered)\n ctx.drawImage(source, -elementData.width / 2, -elementData.height / 2, elementData.width, elementData.height);\n }\n\n ctx.restore();\n\n // Render stroke if enabled (after restore to apply fresh transforms)\n if (elementData.stroke?.enabled) {\n const hasKnockoutStroke = elementData.knockoutParts?.stroke === true;\n\n const imageConfig: ImageElementConfig = {\n transformType: 'image',\n x: elementData.x,\n y: elementData.y,\n rotation: elementData.rotation,\n stroke: elementData.stroke,\n knockoutParts: elementData.knockoutParts,\n transformData: {\n type: 'image',\n width: elementData.width,\n height: elementData.height,\n borderRadius: elementData.borderRadius || 0,\n },\n };\n\n if (hasKnockoutStroke) {\n // For knockout stroke, use offscreen canvas approach\n const canvas = ctx.canvas;\n let offscreen: HTMLCanvasElement | OffscreenCanvas;\n\n if (typeof OffscreenCanvas !== 'undefined') {\n offscreen = new OffscreenCanvas(canvas.width, canvas.height);\n } else if (typeof document !== 'undefined') {\n offscreen = document.createElement('canvas');\n offscreen.width = canvas.width;\n offscreen.height = canvas.height;\n } else {\n renderImageStroke(ctx, imageConfig);\n return;\n }\n\n const offCtx = offscreen.getContext('2d');\n if (!offCtx) {\n renderImageStroke(ctx, imageConfig);\n return;\n }\n\n // Render the stroke to the offscreen canvas with knockout flag\n renderImageStroke(offCtx, imageConfig, { isKnockout: true });\n\n // Composite with destination-out\n ctx.save();\n ctx.globalCompositeOperation = 'destination-out';\n ctx.drawImage(offscreen as CanvasImageSource, 0, 0);\n ctx.restore();\n } else {\n // Normal stroke\n renderImageStroke(ctx, imageConfig);\n }\n }\n}\n","/**\n * Shape Renderer - Shape element rendering (rectangle, circle, ellipse, triangle, etc.)\n *\n * Works in both main thread and Web Worker (via OffscreenCanvas).\n *\n * Opacity strategy: When a shape has both element-level opacity AND a stroke,\n * we render fill+stroke at full opacity on a temporary canvas, then composite\n * the result at the element's opacity. This prevents fill/stroke overlap from\n * compounding (e.g., 50% fill + 50% stroke overlap ≠ 75% opaque).\n */\n\nimport type { RenderContext, SerializedShapeElement } from './renderer-types.js';\n\ntype CanvasLike = HTMLCanvasElement | OffscreenCanvas;\n\nfunction createTempCanvas(width: number, height: number): CanvasLike {\n if (typeof OffscreenCanvas !== 'undefined') {\n return new OffscreenCanvas(width, height);\n }\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return canvas;\n}\n\n/**\n * Render a shape element\n * Works in both main thread and worker\n */\nexport function renderShapeElement(ctx: RenderContext, elementData: SerializedShapeElement): void {\n const elementOpacity = elementData.opacity ?? 1;\n const hasStroke = !!elementData.stroke?.enabled;\n const needsOffscreen = elementOpacity < 1 && hasStroke;\n\n if (needsOffscreen) {\n // Render fill+stroke at full opacity on a temp canvas, then composite\n renderShapeWithOffscreen(ctx, elementData, elementOpacity);\n } else {\n // No compounding risk — render directly\n renderShapeDirect(ctx, elementData, elementOpacity);\n }\n}\n\n/**\n * Render shape via temporary canvas to avoid fill/stroke opacity compounding.\n */\nfunction renderShapeWithOffscreen(\n ctx: RenderContext,\n elementData: SerializedShapeElement,\n elementOpacity: number\n): void {\n const td = elementData.transformData;\n const strokeWidth = elementData.stroke?.width || 2;\n // Pad temp canvas to fit the stroke (strokes extend beyond the shape bounds)\n const padding = strokeWidth + 2;\n const offW = Math.ceil(td.width + padding * 2);\n const offH = Math.ceil(td.height + padding * 2);\n\n const offCanvas = createTempCanvas(offW, offH);\n const offCtx = offCanvas.getContext('2d') as RenderContext;\n if (!offCtx) {\n // Fallback to direct render\n renderShapeDirect(ctx, elementData, elementOpacity);\n return;\n }\n\n // Render at center of temp canvas with full opacity (elementOpacity = 1)\n const tempData: SerializedShapeElement = {\n ...elementData,\n x: offW / 2,\n y: offH / 2,\n opacity: 1,\n };\n renderShapeDirect(offCtx, tempData, 1);\n\n // Composite temp canvas onto main canvas at element opacity\n ctx.save();\n\n // Position: elementData.(x,y) is the shape center, temp canvas center is (offW/2, offH/2)\n // We need to account for rotation around the element center\n ctx.translate(elementData.x, elementData.y);\n if (elementData.rotation) {\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n }\n\n ctx.globalAlpha = elementOpacity;\n ctx.drawImage(offCanvas, -offW / 2, -offH / 2);\n ctx.restore();\n}\n\n/**\n * Render shape directly onto ctx (no offscreen). Used when there's no\n * compounding risk, or as the inner render for the offscreen path.\n */\nfunction renderShapeDirect(\n ctx: RenderContext,\n elementData: SerializedShapeElement,\n elementOpacity: number\n): void {\n const transformData = elementData.transformData;\n const fillOpacity = transformData.fillOpacity ?? 1;\n\n ctx.save();\n ctx.translate(elementData.x, elementData.y);\n\n if (elementData.rotation) {\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n }\n\n // Fill\n ctx.globalAlpha = elementOpacity * fillOpacity;\n ctx.fillStyle = transformData.fillColor || '#3b82f6';\n ctx.beginPath();\n traceShapePath(ctx, transformData);\n ctx.fill();\n ctx.restore();\n\n // Stroke\n if (elementData.stroke?.enabled) {\n ctx.save();\n ctx.translate(elementData.x, elementData.y);\n if (elementData.rotation) {\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n }\n\n const stroke = elementData.stroke;\n ctx.globalAlpha = elementOpacity * (stroke.opacity ?? 1);\n ctx.strokeStyle = stroke.color || '#000000';\n ctx.lineWidth = stroke.width || 2;\n ctx.lineCap = stroke.lineCap || 'round';\n ctx.lineJoin = stroke.lineJoin || 'round';\n\n ctx.beginPath();\n traceShapePath(ctx, transformData);\n ctx.stroke();\n ctx.restore();\n }\n}\n\n/**\n * Trace the shape path (without fill/stroke) — reusable for both fill and stroke passes.\n */\nfunction traceShapePath(\n ctx: RenderContext,\n transformData: SerializedShapeElement['transformData']\n): void {\n const { shapeType, width, height } = transformData;\n\n switch (shapeType) {\n case 'rectangle': {\n const borderRadius = transformData.borderRadius || 0;\n const x = -width / 2;\n const y = -height / 2;\n\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(width, height), width / 2, height / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n break;\n }\n\n case 'circle': {\n const radius = Math.min(width, height) / 2;\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n break;\n }\n\n case 'ellipse': {\n ctx.ellipse(0, 0, width / 2, height / 2, 0, 0, Math.PI * 2);\n break;\n }\n\n case 'triangle': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.moveTo(0, -halfHeight);\n ctx.lineTo(halfWidth, halfHeight);\n ctx.lineTo(-halfWidth, halfHeight);\n ctx.closePath();\n break;\n }\n\n case 'polygon': {\n const sides = transformData.sides || 5;\n const radius = Math.min(width, height) / 2;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const px = radius * Math.cos(angle);\n const py = radius * Math.sin(angle);\n if (i === 0) {\n ctx.moveTo(px, py);\n } else {\n ctx.lineTo(px, py);\n }\n }\n ctx.closePath();\n break;\n }\n\n case 'star': {\n const points = transformData.points || 5;\n const outerRadius = Math.min(width, height) / 2;\n const innerRadius = outerRadius * (transformData.innerRadius || 0.4);\n for (let i = 0; i < points * 2; i++) {\n const angle = (i * Math.PI) / points - Math.PI / 2;\n const r = i % 2 === 0 ? outerRadius : innerRadius;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (i === 0) {\n ctx.moveTo(px, py);\n } else {\n ctx.lineTo(px, py);\n }\n }\n ctx.closePath();\n break;\n }\n\n case 'line': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.rect(-halfWidth, -halfHeight, width, height);\n break;\n }\n\n default:\n ctx.rect(-width / 2, -height / 2, width, height);\n }\n}\n"],"names":["logger","createLogger","EPSILON","applyAdjustmentsToPixels","data","adjustments","brightness","contrast","saturation","slope","intercept","applyLut","lut","v","applySaturation","s","rr","rg","rb","gr","gg","gb","br","bg","bb","i","r","g","b","r2","g2","b2","adjustedSourceCache","MAX_VARIANTS_PER_SOURCE","sourcePixelWidth","source","natural","sourcePixelHeight","createCanvas","width","height","canvas","renderAdjustedSource","ctx","imageData","getAdjustedImageSource","isNeutralAdjustments","key","adjustmentsCacheKey","variants","cached","result","error","oldest","registerAdjustedImageSourceProvider","resolveServerCrop","el","bitmap","renderImageElement","elementData","imageBitmap","scaleX","scaleY","radius","x","y","w","h","_a","hasKnockoutStroke","_b","imageConfig","offscreen","renderImageStroke","offCtx","createTempCanvas","renderShapeElement","elementOpacity","hasStroke","renderShapeWithOffscreen","renderShapeDirect","td","padding","offW","offH","offCanvas","tempData","transformData","fillOpacity","traceShapePath","stroke","shapeType","borderRadius","halfWidth","halfHeight","sides","angle","px","py","points","outerRadius","innerRadius"],"mappings":"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"}
@@ -1,16 +1,3 @@
1
- /**
2
- * Element painters — the heavy half of the paint registry
3
- * (`src/core/paint-registry.ts`, P1 element/renderer split).
4
- *
5
- * Registers the shared paint functions the element model delegates to at
6
- * paint time. This module statically imports the paint pipeline
7
- * (transform-renderer / canvas-renderer), so it must only ever be reachable
8
- * through renderer-side code: every paint DRIVER (CanvasRenderer,
9
- * ExportManager, ElementPreviewRenderer) imports it, which keeps the
10
- * registration a static guarantee of "the renderer is loaded" without the
11
- * model graph ever touching `src/rendering/*`. CI-gated by
12
- * scripts/check-import-graph.mjs.
13
- */
14
1
  /** Idempotent; also runs at module evaluation — but do NOT rely on a plain
15
2
  * side-effect import (`import './element-painters.js'`) reaching it:
16
3
  * bundlers honoring the package `sideEffects` allowlist prune bare imports.
@@ -1,6 +1,6 @@
1
1
  import { ArtboardElement } from '../core/ArtboardElement.js';
2
2
  import { BaseElement } from '../core/BaseElement.js';
3
- import { AnyElementConfig, ArtboardDistressTexture, ArtboardImageMask } from '../types/index.js';
3
+ import { AnyElementConfig, ImageAdjustments, ArtboardDistressTexture, ArtboardImageMask } from '../types/index.js';
4
4
  /**
5
5
  * Default maximum dimension for image capping (in pixels)
6
6
  * Images larger than this on any dimension will be scaled down while preserving aspect ratio.
@@ -124,6 +124,8 @@ export interface SerializedImageElement {
124
124
  flipHorizontal?: boolean;
125
125
  flipVertical?: boolean;
126
126
  borderRadius?: number;
127
+ /** Brightness/contrast/saturation (1 = neutral); omitted when neutral. */
128
+ adjustments?: ImageAdjustments;
127
129
  opacity?: number;
128
130
  }
129
131
  /**
@@ -0,0 +1,24 @@
1
+ import { ImageAdjustments } from '../types/index.js';
2
+ import { AdjustableImageSource } from '../core/image-adjustments.js';
3
+ export { NEUTRAL_ADJUSTMENTS, isNeutralAdjustments, normalizeAdjustments, adjustmentsCacheKey, type AdjustableImageSource, } from '../core/image-adjustments.js';
4
+ /**
5
+ * Apply adjustments to raw RGBA pixels IN PLACE.
6
+ *
7
+ * Pure (no canvas, no DOM) — safe in workers and directly unit-testable.
8
+ * Brightness and contrast are fused into a single 256-entry LUT; saturation
9
+ * applies the feColorMatrix "saturate" matrix per pixel (skipped when ~1).
10
+ */
11
+ export declare function applyAdjustmentsToPixels(data: Uint8ClampedArray, adjustments: ImageAdjustments): void;
12
+ /**
13
+ * Get a drawable source with the adjustments baked in. Neutral adjustments
14
+ * return the original source unchanged.
15
+ *
16
+ * Cached per (source object, adjustments value): the pixel pass runs once per
17
+ * distinct pair; every subsequent call — i.e. every render frame — is a map
18
+ * lookup. FAILED passes are cached too (as the original source) so a broken
19
+ * environment degrades to "no adjustment applied" without retrying the
20
+ * expensive pass every frame (the gotcha-#28 retry-loop lesson).
21
+ */
22
+ export declare function getAdjustedImageSource(source: AdjustableImageSource, adjustments: ImageAdjustments | undefined | null): AdjustableImageSource;
23
+ /** Test-only: drop every cached variant for a source. */
24
+ export declare function clearAdjustedSourceCacheFor(source: object): void;
@@ -1,4 +1,4 @@
1
- import { AnyTransformData, TextAlign, StrokeConfig, OpenTypeFeatures, TextSpan, GlyphOverride } from '../types/index.js';
1
+ import { AnyTransformData, TextAlign, StrokeConfig, OpenTypeFeatures, TextSpan, GlyphOverride, ImageAdjustments } from '../types/index.js';
2
2
  export type RenderContext = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
3
3
  export interface SerializedTextElement {
4
4
  id: string;
@@ -56,6 +56,13 @@ export interface SerializedImageElement {
56
56
  flipHorizontal?: boolean;
57
57
  flipVertical?: boolean;
58
58
  borderRadius?: number;
59
+ /**
60
+ * Brightness/contrast/saturation multipliers (1 = neutral). Omitted when
61
+ * neutral. Applied by `renderImageElement` to the SOURCE bitmap (cached
62
+ * pixel pass) before crop/flip/border-radius — the same order as the live
63
+ * `ImageElement.render`, so exports match the editor.
64
+ */
65
+ adjustments?: ImageAdjustments;
59
66
  opacity?: number;
60
67
  stroke?: StrokeConfig;
61
68
  knockoutParts?: {