@nice2dev/ui-graphics 1.3.15 → 1.3.17

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 (294) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/animation/AnimatedPerson.js +470 -143
  3. package/dist/cjs/animation/AnimationEditor.js +289 -101
  4. package/dist/cjs/animation/Audience.js +83 -61
  5. package/dist/cjs/animation/BodyRenderer.js +69 -28
  6. package/dist/cjs/animation/animationHelper.js +281 -288
  7. package/dist/cjs/animation/characterTypes.js +200 -203
  8. package/dist/cjs/animation/choreoDSL.js +205 -219
  9. package/dist/cjs/animation/choreography.js +4 -32
  10. package/dist/cjs/animation/karaokeIntegration.js +68 -77
  11. package/dist/cjs/animation/lottieExport.js +108 -114
  12. package/dist/cjs/animation/motionPath.js +225 -248
  13. package/dist/cjs/animation/physics.js +227 -254
  14. package/dist/cjs/animation/rig/RigPlayer.js +53 -56
  15. package/dist/cjs/animation/rig/RiggedBody.js +71 -26
  16. package/dist/cjs/animation/rig/choreoRigBridge.js +72 -77
  17. package/dist/cjs/animation/rig/ik.js +13 -18
  18. package/dist/cjs/animation/rig/poseOps.js +34 -41
  19. package/dist/cjs/animation/rig/presets.js +64 -72
  20. package/dist/cjs/animation/rig/rigMath.js +25 -32
  21. package/dist/cjs/animation/rig/rigTypes.js +59 -63
  22. package/dist/cjs/animation/shapes/arms.js +31 -13
  23. package/dist/cjs/animation/shapes/eyes.js +129 -47
  24. package/dist/cjs/animation/shapes/hair.js +72 -45
  25. package/dist/cjs/animation/shapes/heads.js +131 -57
  26. package/dist/cjs/animation/shapes/headwear.js +47 -38
  27. package/dist/cjs/animation/shapes/legs.js +31 -13
  28. package/dist/cjs/animation/shapes/mouths.js +120 -44
  29. package/dist/cjs/animation/shapes/noses.js +80 -25
  30. package/dist/cjs/animation/shapes/outfits.js +101 -39
  31. package/dist/cjs/animation/shapes/torsos.js +15 -11
  32. package/dist/cjs/animation/spineExport.js +103 -121
  33. package/dist/cjs/core/LocalUI.js +108 -43
  34. package/dist/cjs/core/NiceBrushEngine.js +758 -552
  35. package/dist/cjs/core/collaboration.js +249 -251
  36. package/dist/cjs/core/colorBlindness.js +100 -115
  37. package/dist/cjs/core/dragDrop.js +155 -158
  38. package/dist/cjs/core/editorAsk.js +51 -41
  39. package/dist/cjs/core/fixes.js +140 -171
  40. package/dist/cjs/core/gridGuides.js +166 -201
  41. package/dist/cjs/core/historyVisual.js +156 -135
  42. package/dist/cjs/core/i18n.js +13 -14
  43. package/dist/cjs/core/integrations.js +238 -249
  44. package/dist/cjs/core/minimap.js +149 -151
  45. package/dist/cjs/core/plugins.js +98 -113
  46. package/dist/cjs/core/rtl.js +71 -83
  47. package/dist/cjs/core/shortcuts.js +196 -225
  48. package/dist/cjs/core/theme.js +142 -149
  49. package/dist/cjs/core/yjsCollaboration.js +232 -293
  50. package/dist/cjs/design/design-tools.js +12 -22
  51. package/dist/cjs/font/NiceFontEditor.js +877 -316
  52. package/dist/cjs/font/fontFormatUtils.js +388 -448
  53. package/dist/cjs/game/GameAsset2dEditor.js +1201 -392
  54. package/dist/cjs/game/GameAsset2dEditor.module.css.js +146 -5
  55. package/dist/cjs/game/gameAssetTypes.js +7 -16
  56. package/dist/cjs/game/gameAssetUtils.js +390 -413
  57. package/dist/cjs/game/useGameAssetEditor.js +820 -763
  58. package/dist/cjs/index.js +81 -84
  59. package/dist/cjs/nice2dev-ui-graphics.css +1 -1
  60. package/dist/cjs/node_modules/lib0/array.js +8 -70
  61. package/dist/cjs/node_modules/lib0/binary.js +3 -20
  62. package/dist/cjs/node_modules/lib0/broadcastchannel.js +24 -86
  63. package/dist/cjs/node_modules/lib0/buffer.js +15 -82
  64. package/dist/cjs/node_modules/lib0/conditions.js +3 -16
  65. package/dist/cjs/node_modules/lib0/decoding.js +108 -287
  66. package/dist/cjs/node_modules/lib0/dom.js +14 -44
  67. package/dist/cjs/node_modules/lib0/encoding.js +68 -418
  68. package/dist/cjs/node_modules/lib0/environment.js +26 -92
  69. package/dist/cjs/node_modules/lib0/error.js +5 -29
  70. package/dist/cjs/node_modules/lib0/function.js +27 -66
  71. package/dist/cjs/node_modules/lib0/iterator.js +8 -33
  72. package/dist/cjs/node_modules/lib0/logging.common.js +12 -22
  73. package/dist/cjs/node_modules/lib0/logging.js +32 -71
  74. package/dist/cjs/node_modules/lib0/map.js +16 -86
  75. package/dist/cjs/node_modules/lib0/math.js +3 -32
  76. package/dist/cjs/node_modules/lib0/number.js +4 -15
  77. package/dist/cjs/node_modules/lib0/object.js +14 -88
  78. package/dist/cjs/node_modules/lib0/observable.js +38 -78
  79. package/dist/cjs/node_modules/lib0/pair.js +3 -20
  80. package/dist/cjs/node_modules/lib0/prng.js +8 -77
  81. package/dist/cjs/node_modules/lib0/promise.js +6 -30
  82. package/dist/cjs/node_modules/lib0/random.js +8 -23
  83. package/dist/cjs/node_modules/lib0/schema.js +313 -687
  84. package/dist/cjs/node_modules/lib0/set.js +3 -10
  85. package/dist/cjs/node_modules/lib0/storage.js +20 -55
  86. package/dist/cjs/node_modules/lib0/string.js +17 -72
  87. package/dist/cjs/node_modules/lib0/symbol.js +2 -12
  88. package/dist/cjs/node_modules/lib0/time.js +2 -15
  89. package/dist/cjs/node_modules/lib0/trait/equality.js +3 -28
  90. package/dist/cjs/node_modules/lib0/url.js +4 -18
  91. package/dist/cjs/node_modules/lib0/webcrypto.js +2 -5
  92. package/dist/cjs/node_modules/y-protocols/auth.js +5 -19
  93. package/dist/cjs/node_modules/y-protocols/awareness.js +53 -116
  94. package/dist/cjs/node_modules/y-protocols/sync.js +16 -103
  95. package/dist/cjs/node_modules/y-websocket/src/y-websocket.js +77 -219
  96. package/dist/cjs/node_modules/yjs/dist/yjs.js +2128 -4868
  97. package/dist/cjs/photo/FilterThumb.js +37 -25
  98. package/dist/cjs/photo/NiceAITools.js +679 -467
  99. package/dist/cjs/photo/PhotoEditor.js +34 -20
  100. package/dist/cjs/photo/PhotoEditor.module.css.js +296 -5
  101. package/dist/cjs/photo/PhotoEditorCanvas.js +250 -91
  102. package/dist/cjs/photo/PhotoEditorLeftPanel.js +1086 -224
  103. package/dist/cjs/photo/PhotoEditorToolbar.js +135 -19
  104. package/dist/cjs/photo/photoEditorActions.js +89 -72
  105. package/dist/cjs/photo/photoEditorTypes.js +29 -32
  106. package/dist/cjs/photo/photoFilters.js +959 -985
  107. package/dist/cjs/photo/photoOverlays.js +899 -945
  108. package/dist/cjs/photo/photoSelection.js +427 -499
  109. package/dist/cjs/photo/usePhotoEditor.js +739 -741
  110. package/dist/cjs/pixel/HSVPicker.js +191 -175
  111. package/dist/cjs/pixel/PixelEditor.js +159 -48
  112. package/dist/cjs/pixel/PixelEditor.module.css.js +209 -5
  113. package/dist/cjs/pixel/PixelEditorCanvas.js +29 -16
  114. package/dist/cjs/pixel/PixelEditorMenuBar.js +160 -162
  115. package/dist/cjs/pixel/PixelEditorRightPanel.js +377 -45
  116. package/dist/cjs/pixel/PixelEditorStatusBar.js +47 -20
  117. package/dist/cjs/pixel/PixelEditorTimeline.js +106 -23
  118. package/dist/cjs/pixel/PixelEditorToolbar.js +99 -21
  119. package/dist/cjs/pixel/SpriteGeneratorPanel.js +483 -303
  120. package/dist/cjs/pixel/animalGeneratorPresets.js +776 -601
  121. package/dist/cjs/pixel/asepriteFormat.js +511 -560
  122. package/dist/cjs/pixel/buildingGeneratorPresets.js +693 -520
  123. package/dist/cjs/pixel/characterGeneratorPresets.js +644 -544
  124. package/dist/cjs/pixel/pixel-pro.js +52 -63
  125. package/dist/cjs/pixel/pixelEditorExports.js +388 -428
  126. package/dist/cjs/pixel/pixelEditorTypes.js +91 -108
  127. package/dist/cjs/pixel/pixelEditorUtils.js +519 -561
  128. package/dist/cjs/pixel/spriteGeneratorCore.js +206 -257
  129. package/dist/cjs/pixel/usePixelEditor.js +1321 -1343
  130. package/dist/cjs/texture/Nice3DTexturePainter.js +707 -440
  131. package/dist/cjs/ui/NiceUIDesigner.js +954 -527
  132. package/dist/cjs/vector/NiceSvgToCode.js +586 -446
  133. package/dist/cjs/vector/VectorEditor.js +290 -117
  134. package/dist/cjs/vector/VectorEditor.module.css.js +86 -5
  135. package/dist/cjs/vector/VectorEditorMenuBar.js +333 -275
  136. package/dist/cjs/vector/VectorEditorRightPanel.js +1094 -371
  137. package/dist/cjs/vector/VectorEditorShapeRenderer.js +198 -60
  138. package/dist/cjs/vector/VectorEditorStatusBar.js +39 -13
  139. package/dist/cjs/vector/useVectorEditor.js +848 -889
  140. package/dist/cjs/vector/vectorAnimation.js +110 -137
  141. package/dist/cjs/vector/vectorBooleanOps.js +398 -485
  142. package/dist/cjs/vector/vectorEditorExport.js +201 -203
  143. package/dist/cjs/vector/vectorEditorImport.js +173 -189
  144. package/dist/cjs/vector/vectorEditorTypes.js +42 -49
  145. package/dist/cjs/vector/vectorGradients.js +151 -174
  146. package/dist/cjs/vector/vectorIconExport.js +52 -59
  147. package/dist/cjs/vector/vectorIconImport.js +169 -219
  148. package/dist/esm/animation/AnimatedPerson.js +470 -141
  149. package/dist/esm/animation/AnimationEditor.js +290 -100
  150. package/dist/esm/animation/Audience.js +83 -59
  151. package/dist/esm/animation/BodyRenderer.js +71 -25
  152. package/dist/esm/animation/animationHelper.js +284 -287
  153. package/dist/esm/animation/characterTypes.js +204 -202
  154. package/dist/esm/animation/choreoDSL.js +212 -218
  155. package/dist/esm/animation/choreography.js +8 -31
  156. package/dist/esm/animation/karaokeIntegration.js +74 -76
  157. package/dist/esm/animation/lottieExport.js +112 -113
  158. package/dist/esm/animation/motionPath.js +235 -247
  159. package/dist/esm/animation/physics.js +235 -253
  160. package/dist/esm/animation/rig/RigPlayer.js +53 -54
  161. package/dist/esm/animation/rig/RiggedBody.js +71 -24
  162. package/dist/esm/animation/rig/choreoRigBridge.js +74 -76
  163. package/dist/esm/animation/rig/ik.js +14 -17
  164. package/dist/esm/animation/rig/poseOps.js +37 -40
  165. package/dist/esm/animation/rig/presets.js +70 -71
  166. package/dist/esm/animation/rig/rigMath.js +28 -31
  167. package/dist/esm/animation/rig/rigTypes.js +62 -62
  168. package/dist/esm/animation/shapes/arms.js +32 -12
  169. package/dist/esm/animation/shapes/eyes.js +135 -46
  170. package/dist/esm/animation/shapes/hair.js +80 -44
  171. package/dist/esm/animation/shapes/heads.js +140 -56
  172. package/dist/esm/animation/shapes/headwear.js +54 -37
  173. package/dist/esm/animation/shapes/legs.js +32 -12
  174. package/dist/esm/animation/shapes/mouths.js +127 -43
  175. package/dist/esm/animation/shapes/noses.js +87 -24
  176. package/dist/esm/animation/shapes/outfits.js +106 -38
  177. package/dist/esm/animation/shapes/torsos.js +16 -10
  178. package/dist/esm/animation/spineExport.js +106 -120
  179. package/dist/esm/core/LocalUI.js +111 -42
  180. package/dist/esm/core/NiceBrushEngine.js +761 -551
  181. package/dist/esm/core/collaboration.js +255 -250
  182. package/dist/esm/core/colorBlindness.js +106 -114
  183. package/dist/esm/core/dragDrop.js +157 -157
  184. package/dist/esm/core/editorAsk.js +53 -40
  185. package/dist/esm/core/fixes.js +145 -170
  186. package/dist/esm/core/gridGuides.js +178 -200
  187. package/dist/esm/core/historyVisual.js +158 -134
  188. package/dist/esm/core/i18n.js +15 -13
  189. package/dist/esm/core/integrations.js +244 -248
  190. package/dist/esm/core/minimap.js +151 -150
  191. package/dist/esm/core/plugins.js +102 -112
  192. package/dist/esm/core/rtl.js +77 -82
  193. package/dist/esm/core/shortcuts.js +200 -224
  194. package/dist/esm/core/theme.js +150 -148
  195. package/dist/esm/core/yjsCollaboration.js +240 -292
  196. package/dist/esm/design/design-tools.js +13 -21
  197. package/dist/esm/font/NiceFontEditor.js +880 -315
  198. package/dist/esm/font/fontFormatUtils.js +394 -447
  199. package/dist/esm/game/GameAsset2dEditor.js +1202 -391
  200. package/dist/esm/game/GameAsset2dEditor.module.css.js +146 -3
  201. package/dist/esm/game/gameAssetTypes.js +8 -15
  202. package/dist/esm/game/gameAssetUtils.js +408 -412
  203. package/dist/esm/game/useGameAssetEditor.js +821 -762
  204. package/dist/esm/index.js +332 -69
  205. package/dist/esm/nice2dev-ui-graphics.css +1 -1
  206. package/dist/esm/node_modules/lib0/array.js +15 -69
  207. package/dist/esm/node_modules/lib0/binary.js +14 -19
  208. package/dist/esm/node_modules/lib0/broadcastchannel.js +27 -85
  209. package/dist/esm/node_modules/lib0/buffer.js +20 -80
  210. package/dist/esm/node_modules/lib0/conditions.js +4 -15
  211. package/dist/esm/node_modules/lib0/decoding.js +128 -286
  212. package/dist/esm/node_modules/lib0/dom.js +20 -43
  213. package/dist/esm/node_modules/lib0/encoding.js +90 -415
  214. package/dist/esm/node_modules/lib0/environment.js +33 -91
  215. package/dist/esm/node_modules/lib0/error.js +8 -28
  216. package/dist/esm/node_modules/lib0/function.js +31 -65
  217. package/dist/esm/node_modules/lib0/iterator.js +11 -32
  218. package/dist/esm/node_modules/lib0/logging.common.js +22 -21
  219. package/dist/esm/node_modules/lib0/logging.js +44 -70
  220. package/dist/esm/node_modules/lib0/map.js +21 -85
  221. package/dist/esm/node_modules/lib0/math.js +9 -31
  222. package/dist/esm/node_modules/lib0/number.js +7 -14
  223. package/dist/esm/node_modules/lib0/object.js +26 -87
  224. package/dist/esm/node_modules/lib0/observable.js +40 -77
  225. package/dist/esm/node_modules/lib0/pair.js +5 -19
  226. package/dist/esm/node_modules/lib0/prng.js +15 -76
  227. package/dist/esm/node_modules/lib0/promise.js +7 -29
  228. package/dist/esm/node_modules/lib0/random.js +10 -22
  229. package/dist/esm/node_modules/lib0/schema.js +367 -686
  230. package/dist/esm/node_modules/lib0/set.js +4 -9
  231. package/dist/esm/node_modules/lib0/storage.js +23 -54
  232. package/dist/esm/node_modules/lib0/string.js +26 -71
  233. package/dist/esm/node_modules/lib0/symbol.js +3 -11
  234. package/dist/esm/node_modules/lib0/time.js +3 -14
  235. package/dist/esm/node_modules/lib0/trait/equality.js +5 -27
  236. package/dist/esm/node_modules/lib0/url.js +5 -17
  237. package/dist/esm/node_modules/lib0/webcrypto.js +3 -4
  238. package/dist/esm/node_modules/y-protocols/auth.js +7 -18
  239. package/dist/esm/node_modules/y-protocols/awareness.js +58 -115
  240. package/dist/esm/node_modules/y-protocols/sync.js +26 -102
  241. package/dist/esm/node_modules/y-websocket/src/y-websocket.js +82 -218
  242. package/dist/esm/node_modules/yjs/dist/yjs.js +2263 -4899
  243. package/dist/esm/photo/FilterThumb.js +37 -23
  244. package/dist/esm/photo/NiceAITools.js +685 -466
  245. package/dist/esm/photo/PhotoEditor.js +34 -18
  246. package/dist/esm/photo/PhotoEditor.module.css.js +296 -3
  247. package/dist/esm/photo/PhotoEditorCanvas.js +250 -89
  248. package/dist/esm/photo/PhotoEditorLeftPanel.js +1086 -222
  249. package/dist/esm/photo/PhotoEditorToolbar.js +135 -17
  250. package/dist/esm/photo/photoEditorActions.js +93 -71
  251. package/dist/esm/photo/photoEditorTypes.js +32 -31
  252. package/dist/esm/photo/photoFilters.js +967 -984
  253. package/dist/esm/photo/photoOverlays.js +913 -944
  254. package/dist/esm/photo/photoSelection.js +438 -498
  255. package/dist/esm/photo/usePhotoEditor.js +740 -740
  256. package/dist/esm/pixel/HSVPicker.js +192 -174
  257. package/dist/esm/pixel/PixelEditor.js +160 -47
  258. package/dist/esm/pixel/PixelEditor.module.css.js +209 -3
  259. package/dist/esm/pixel/PixelEditorCanvas.js +29 -14
  260. package/dist/esm/pixel/PixelEditorMenuBar.js +160 -160
  261. package/dist/esm/pixel/PixelEditorRightPanel.js +377 -43
  262. package/dist/esm/pixel/PixelEditorStatusBar.js +47 -18
  263. package/dist/esm/pixel/PixelEditorTimeline.js +106 -21
  264. package/dist/esm/pixel/PixelEditorToolbar.js +99 -19
  265. package/dist/esm/pixel/SpriteGeneratorPanel.js +483 -301
  266. package/dist/esm/pixel/animalGeneratorPresets.js +782 -600
  267. package/dist/esm/pixel/asepriteFormat.js +517 -559
  268. package/dist/esm/pixel/buildingGeneratorPresets.js +699 -519
  269. package/dist/esm/pixel/characterGeneratorPresets.js +650 -543
  270. package/dist/esm/pixel/pixel-pro.js +53 -62
  271. package/dist/esm/pixel/pixelEditorExports.js +397 -427
  272. package/dist/esm/pixel/pixelEditorTypes.js +103 -107
  273. package/dist/esm/pixel/pixelEditorUtils.js +551 -560
  274. package/dist/esm/pixel/spriteGeneratorCore.js +223 -256
  275. package/dist/esm/pixel/usePixelEditor.js +1322 -1342
  276. package/dist/esm/texture/Nice3DTexturePainter.js +709 -439
  277. package/dist/esm/ui/NiceUIDesigner.js +957 -526
  278. package/dist/esm/vector/NiceSvgToCode.js +589 -445
  279. package/dist/esm/vector/VectorEditor.js +291 -116
  280. package/dist/esm/vector/VectorEditor.module.css.js +86 -3
  281. package/dist/esm/vector/VectorEditorMenuBar.js +333 -273
  282. package/dist/esm/vector/VectorEditorRightPanel.js +1094 -369
  283. package/dist/esm/vector/VectorEditorShapeRenderer.js +199 -59
  284. package/dist/esm/vector/VectorEditorStatusBar.js +39 -11
  285. package/dist/esm/vector/useVectorEditor.js +849 -888
  286. package/dist/esm/vector/vectorAnimation.js +115 -136
  287. package/dist/esm/vector/vectorBooleanOps.js +401 -484
  288. package/dist/esm/vector/vectorEditorExport.js +207 -202
  289. package/dist/esm/vector/vectorEditorImport.js +174 -188
  290. package/dist/esm/vector/vectorEditorTypes.js +50 -48
  291. package/dist/esm/vector/vectorGradients.js +158 -173
  292. package/dist/esm/vector/vectorIconExport.js +53 -58
  293. package/dist/esm/vector/vectorIconImport.js +171 -218
  294. package/package.json +9 -15
@@ -1,589 +1,795 @@
1
- 'use strict';
2
-
3
- var jsxRuntime = require('react/jsx-runtime');
4
- var uiCore = require('@nice2dev/ui-core');
5
- var React = require('react');
6
- var i18n = require('./i18n.js');
7
-
8
- /* ── Brush Presets ── */
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const uiCore = require("@nice2dev/ui-core");
5
+ const React = require("react");
6
+ const i18n = require("./i18n.js");
9
7
  const DEFAULT_DYNAMICS = {
10
- source: 'none',
11
- min: 1,
12
- max: 1,
13
- curve: 'linear',
8
+ source: "none",
9
+ min: 1,
10
+ max: 1,
11
+ curve: "linear"
14
12
  };
15
13
  const PRESSURE_SIZE = {
16
- source: 'pressure',
17
- min: 0.1,
18
- max: 1.0,
19
- curve: 'linear',
14
+ source: "pressure",
15
+ min: 0.1,
16
+ max: 1,
17
+ curve: "linear"
20
18
  };
21
19
  const PRESSURE_OPACITY = {
22
- source: 'pressure',
23
- min: 0.2,
24
- max: 1.0,
25
- curve: 'ease-in',
20
+ source: "pressure",
21
+ min: 0.2,
22
+ max: 1,
23
+ curve: "ease-in"
26
24
  };
27
25
  const TILT_ROTATION = {
28
- source: 'tilt',
29
- min: 0,
30
- max: 1,
31
- curve: 'linear',
26
+ source: "tilt",
27
+ min: 0,
28
+ max: 1,
29
+ curve: "linear"
32
30
  };
33
31
  const BRUSH_PRESETS = {
34
- 'hard-round': {
35
- name: 'Hard Round',
36
- size: 10,
37
- opacity: 1,
38
- flow: 1,
39
- hardness: 1,
40
- spacing: 0.1,
41
- tipShape: 'circle',
42
- tipRotation: 0,
43
- roundness: 1,
44
- scatter: 0,
45
- scatterBothAxes: false,
46
- blendMode: 'normal',
47
- smoothing: 0.5,
48
- sizeDynamics: PRESSURE_SIZE,
49
- opacityDynamics: DEFAULT_DYNAMICS,
50
- flowDynamics: DEFAULT_DYNAMICS,
51
- rotationDynamics: DEFAULT_DYNAMICS,
52
- scatterDynamics: DEFAULT_DYNAMICS,
53
- wetEdges: false,
54
- colorJitterHue: 0,
55
- colorJitterSat: 0,
56
- colorJitterBright: 0,
57
- },
58
- 'soft-round': {
59
- name: 'Soft Round',
60
- size: 20,
61
- opacity: 0.8,
62
- flow: 0.5,
63
- hardness: 0,
64
- spacing: 0.08,
65
- tipShape: 'circle',
66
- tipRotation: 0,
67
- roundness: 1,
68
- scatter: 0,
69
- scatterBothAxes: false,
70
- blendMode: 'normal',
71
- smoothing: 0.6,
72
- sizeDynamics: PRESSURE_SIZE,
73
- opacityDynamics: PRESSURE_OPACITY,
74
- flowDynamics: DEFAULT_DYNAMICS,
75
- rotationDynamics: DEFAULT_DYNAMICS,
76
- scatterDynamics: DEFAULT_DYNAMICS,
77
- wetEdges: false,
78
- colorJitterHue: 0,
79
- colorJitterSat: 0,
80
- colorJitterBright: 0,
81
- },
82
- calligraphy: {
83
- name: 'Calligraphy',
84
- size: 15,
85
- opacity: 1,
86
- flow: 1,
87
- hardness: 0.8,
88
- spacing: 0.05,
89
- tipShape: 'circle',
90
- tipRotation: Math.PI / 4,
91
- roundness: 0.2,
92
- scatter: 0,
93
- scatterBothAxes: false,
94
- blendMode: 'normal',
95
- smoothing: 0.7,
96
- sizeDynamics: PRESSURE_SIZE,
97
- opacityDynamics: DEFAULT_DYNAMICS,
98
- flowDynamics: DEFAULT_DYNAMICS,
99
- rotationDynamics: TILT_ROTATION,
100
- scatterDynamics: DEFAULT_DYNAMICS,
101
- wetEdges: false,
102
- colorJitterHue: 0,
103
- colorJitterSat: 0,
104
- colorJitterBright: 0,
105
- },
106
- airbrush: {
107
- name: 'Airbrush',
108
- size: 40,
109
- opacity: 0.3,
110
- flow: 0.15,
111
- hardness: 0,
112
- spacing: 0.05,
113
- tipShape: 'circle',
114
- tipRotation: 0,
115
- roundness: 1,
116
- scatter: 0.3,
117
- scatterBothAxes: true,
118
- blendMode: 'normal',
119
- smoothing: 0.3,
120
- sizeDynamics: PRESSURE_SIZE,
121
- opacityDynamics: PRESSURE_OPACITY,
122
- flowDynamics: { source: 'pressure', min: 0.05, max: 0.4, curve: 'ease-in' },
123
- rotationDynamics: DEFAULT_DYNAMICS,
124
- scatterDynamics: { source: 'pressure', min: 0.5, max: 0.05, curve: 'linear' },
125
- wetEdges: false,
126
- colorJitterHue: 0,
127
- colorJitterSat: 0,
128
- colorJitterBright: 0,
129
- },
130
- watercolor: {
131
- name: 'Watercolor',
132
- size: 30,
133
- opacity: 0.4,
134
- flow: 0.3,
135
- hardness: 0,
136
- spacing: 0.06,
137
- tipShape: 'circle',
138
- tipRotation: 0,
139
- roundness: 0.85,
140
- scatter: 0.15,
141
- scatterBothAxes: true,
142
- blendMode: 'multiply',
143
- smoothing: 0.8,
144
- sizeDynamics: PRESSURE_SIZE,
145
- opacityDynamics: PRESSURE_OPACITY,
146
- flowDynamics: DEFAULT_DYNAMICS,
147
- rotationDynamics: { source: 'random', min: 0, max: 1, curve: 'linear' },
148
- scatterDynamics: DEFAULT_DYNAMICS,
149
- wetEdges: true,
150
- colorJitterHue: 0.05,
151
- colorJitterSat: 0.1,
152
- colorJitterBright: 0.08,
153
- },
154
- pencil: {
155
- name: 'Pencil',
156
- size: 3,
157
- opacity: 0.9,
158
- flow: 0.8,
159
- hardness: 0.7,
160
- spacing: 0.04,
161
- tipShape: 'circle',
162
- tipRotation: 0,
163
- roundness: 0.9,
164
- scatter: 0.02,
165
- scatterBothAxes: true,
166
- blendMode: 'multiply',
167
- smoothing: 0.4,
168
- sizeDynamics: { source: 'pressure', min: 0.5, max: 1.0, curve: 'ease-out' },
169
- opacityDynamics: { source: 'pressure', min: 0.3, max: 1.0, curve: 'ease-in' },
170
- flowDynamics: DEFAULT_DYNAMICS,
171
- rotationDynamics: DEFAULT_DYNAMICS,
172
- scatterDynamics: DEFAULT_DYNAMICS,
173
- wetEdges: false,
174
- colorJitterHue: 0,
175
- colorJitterSat: 0,
176
- colorJitterBright: 0.05,
177
- },
178
- ink: {
179
- name: 'Ink',
180
- size: 5,
181
- opacity: 1,
182
- flow: 1,
183
- hardness: 1,
184
- spacing: 0.03,
185
- tipShape: 'circle',
186
- tipRotation: 0,
187
- roundness: 1,
188
- scatter: 0,
189
- scatterBothAxes: false,
190
- blendMode: 'normal',
191
- smoothing: 0.9,
192
- sizeDynamics: { source: 'speed', min: 0.3, max: 1.5, curve: 'ease-in-out' },
193
- opacityDynamics: DEFAULT_DYNAMICS,
194
- flowDynamics: DEFAULT_DYNAMICS,
195
- rotationDynamics: DEFAULT_DYNAMICS,
196
- scatterDynamics: DEFAULT_DYNAMICS,
197
- wetEdges: false,
198
- colorJitterHue: 0,
199
- colorJitterSat: 0,
200
- colorJitterBright: 0,
201
- },
202
- scatter: {
203
- name: 'Scatter',
204
- size: 12,
205
- opacity: 0.7,
206
- flow: 1,
207
- hardness: 0.5,
208
- spacing: 0.4,
209
- tipShape: 'circle',
210
- tipRotation: 0,
211
- roundness: 1,
212
- scatter: 1,
213
- scatterBothAxes: true,
214
- blendMode: 'normal',
215
- smoothing: 0.2,
216
- sizeDynamics: { source: 'random', min: 0.3, max: 1.5, curve: 'linear' },
217
- opacityDynamics: { source: 'random', min: 0.4, max: 1.0, curve: 'linear' },
218
- flowDynamics: DEFAULT_DYNAMICS,
219
- rotationDynamics: { source: 'random', min: 0, max: 1, curve: 'linear' },
220
- scatterDynamics: DEFAULT_DYNAMICS,
221
- wetEdges: false,
222
- colorJitterHue: 0.1,
223
- colorJitterSat: 0.15,
224
- colorJitterBright: 0.1,
225
- },
32
+ "hard-round": {
33
+ name: "Hard Round",
34
+ size: 10,
35
+ opacity: 1,
36
+ flow: 1,
37
+ hardness: 1,
38
+ spacing: 0.1,
39
+ tipShape: "circle",
40
+ tipRotation: 0,
41
+ roundness: 1,
42
+ scatter: 0,
43
+ scatterBothAxes: false,
44
+ blendMode: "normal",
45
+ smoothing: 0.5,
46
+ sizeDynamics: PRESSURE_SIZE,
47
+ opacityDynamics: DEFAULT_DYNAMICS,
48
+ flowDynamics: DEFAULT_DYNAMICS,
49
+ rotationDynamics: DEFAULT_DYNAMICS,
50
+ scatterDynamics: DEFAULT_DYNAMICS,
51
+ wetEdges: false,
52
+ colorJitterHue: 0,
53
+ colorJitterSat: 0,
54
+ colorJitterBright: 0
55
+ },
56
+ "soft-round": {
57
+ name: "Soft Round",
58
+ size: 20,
59
+ opacity: 0.8,
60
+ flow: 0.5,
61
+ hardness: 0,
62
+ spacing: 0.08,
63
+ tipShape: "circle",
64
+ tipRotation: 0,
65
+ roundness: 1,
66
+ scatter: 0,
67
+ scatterBothAxes: false,
68
+ blendMode: "normal",
69
+ smoothing: 0.6,
70
+ sizeDynamics: PRESSURE_SIZE,
71
+ opacityDynamics: PRESSURE_OPACITY,
72
+ flowDynamics: DEFAULT_DYNAMICS,
73
+ rotationDynamics: DEFAULT_DYNAMICS,
74
+ scatterDynamics: DEFAULT_DYNAMICS,
75
+ wetEdges: false,
76
+ colorJitterHue: 0,
77
+ colorJitterSat: 0,
78
+ colorJitterBright: 0
79
+ },
80
+ calligraphy: {
81
+ name: "Calligraphy",
82
+ size: 15,
83
+ opacity: 1,
84
+ flow: 1,
85
+ hardness: 0.8,
86
+ spacing: 0.05,
87
+ tipShape: "circle",
88
+ tipRotation: Math.PI / 4,
89
+ roundness: 0.2,
90
+ scatter: 0,
91
+ scatterBothAxes: false,
92
+ blendMode: "normal",
93
+ smoothing: 0.7,
94
+ sizeDynamics: PRESSURE_SIZE,
95
+ opacityDynamics: DEFAULT_DYNAMICS,
96
+ flowDynamics: DEFAULT_DYNAMICS,
97
+ rotationDynamics: TILT_ROTATION,
98
+ scatterDynamics: DEFAULT_DYNAMICS,
99
+ wetEdges: false,
100
+ colorJitterHue: 0,
101
+ colorJitterSat: 0,
102
+ colorJitterBright: 0
103
+ },
104
+ airbrush: {
105
+ name: "Airbrush",
106
+ size: 40,
107
+ opacity: 0.3,
108
+ flow: 0.15,
109
+ hardness: 0,
110
+ spacing: 0.05,
111
+ tipShape: "circle",
112
+ tipRotation: 0,
113
+ roundness: 1,
114
+ scatter: 0.3,
115
+ scatterBothAxes: true,
116
+ blendMode: "normal",
117
+ smoothing: 0.3,
118
+ sizeDynamics: PRESSURE_SIZE,
119
+ opacityDynamics: PRESSURE_OPACITY,
120
+ flowDynamics: { source: "pressure", min: 0.05, max: 0.4, curve: "ease-in" },
121
+ rotationDynamics: DEFAULT_DYNAMICS,
122
+ scatterDynamics: { source: "pressure", min: 0.5, max: 0.05, curve: "linear" },
123
+ wetEdges: false,
124
+ colorJitterHue: 0,
125
+ colorJitterSat: 0,
126
+ colorJitterBright: 0
127
+ },
128
+ watercolor: {
129
+ name: "Watercolor",
130
+ size: 30,
131
+ opacity: 0.4,
132
+ flow: 0.3,
133
+ hardness: 0,
134
+ spacing: 0.06,
135
+ tipShape: "circle",
136
+ tipRotation: 0,
137
+ roundness: 0.85,
138
+ scatter: 0.15,
139
+ scatterBothAxes: true,
140
+ blendMode: "multiply",
141
+ smoothing: 0.8,
142
+ sizeDynamics: PRESSURE_SIZE,
143
+ opacityDynamics: PRESSURE_OPACITY,
144
+ flowDynamics: DEFAULT_DYNAMICS,
145
+ rotationDynamics: { source: "random", min: 0, max: 1, curve: "linear" },
146
+ scatterDynamics: DEFAULT_DYNAMICS,
147
+ wetEdges: true,
148
+ colorJitterHue: 0.05,
149
+ colorJitterSat: 0.1,
150
+ colorJitterBright: 0.08
151
+ },
152
+ pencil: {
153
+ name: "Pencil",
154
+ size: 3,
155
+ opacity: 0.9,
156
+ flow: 0.8,
157
+ hardness: 0.7,
158
+ spacing: 0.04,
159
+ tipShape: "circle",
160
+ tipRotation: 0,
161
+ roundness: 0.9,
162
+ scatter: 0.02,
163
+ scatterBothAxes: true,
164
+ blendMode: "multiply",
165
+ smoothing: 0.4,
166
+ sizeDynamics: { source: "pressure", min: 0.5, max: 1, curve: "ease-out" },
167
+ opacityDynamics: { source: "pressure", min: 0.3, max: 1, curve: "ease-in" },
168
+ flowDynamics: DEFAULT_DYNAMICS,
169
+ rotationDynamics: DEFAULT_DYNAMICS,
170
+ scatterDynamics: DEFAULT_DYNAMICS,
171
+ wetEdges: false,
172
+ colorJitterHue: 0,
173
+ colorJitterSat: 0,
174
+ colorJitterBright: 0.05
175
+ },
176
+ ink: {
177
+ name: "Ink",
178
+ size: 5,
179
+ opacity: 1,
180
+ flow: 1,
181
+ hardness: 1,
182
+ spacing: 0.03,
183
+ tipShape: "circle",
184
+ tipRotation: 0,
185
+ roundness: 1,
186
+ scatter: 0,
187
+ scatterBothAxes: false,
188
+ blendMode: "normal",
189
+ smoothing: 0.9,
190
+ sizeDynamics: { source: "speed", min: 0.3, max: 1.5, curve: "ease-in-out" },
191
+ opacityDynamics: DEFAULT_DYNAMICS,
192
+ flowDynamics: DEFAULT_DYNAMICS,
193
+ rotationDynamics: DEFAULT_DYNAMICS,
194
+ scatterDynamics: DEFAULT_DYNAMICS,
195
+ wetEdges: false,
196
+ colorJitterHue: 0,
197
+ colorJitterSat: 0,
198
+ colorJitterBright: 0
199
+ },
200
+ scatter: {
201
+ name: "Scatter",
202
+ size: 12,
203
+ opacity: 0.7,
204
+ flow: 1,
205
+ hardness: 0.5,
206
+ spacing: 0.4,
207
+ tipShape: "circle",
208
+ tipRotation: 0,
209
+ roundness: 1,
210
+ scatter: 1,
211
+ scatterBothAxes: true,
212
+ blendMode: "normal",
213
+ smoothing: 0.2,
214
+ sizeDynamics: { source: "random", min: 0.3, max: 1.5, curve: "linear" },
215
+ opacityDynamics: { source: "random", min: 0.4, max: 1, curve: "linear" },
216
+ flowDynamics: DEFAULT_DYNAMICS,
217
+ rotationDynamics: { source: "random", min: 0, max: 1, curve: "linear" },
218
+ scatterDynamics: DEFAULT_DYNAMICS,
219
+ wetEdges: false,
220
+ colorJitterHue: 0.1,
221
+ colorJitterSat: 0.15,
222
+ colorJitterBright: 0.1
223
+ }
226
224
  };
227
- /* ── Utility functions ── */
228
225
  function applyCurve(value, curve) {
229
- const t = Math.max(0, Math.min(1, value));
230
- switch (curve) {
231
- case 'ease-in':
232
- return t * t;
233
- case 'ease-out':
234
- return 1 - (1 - t) * (1 - t);
235
- case 'ease-in-out':
236
- return t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t);
237
- default:
238
- return t;
239
- }
226
+ const t = Math.max(0, Math.min(1, value));
227
+ switch (curve) {
228
+ case "ease-in":
229
+ return t * t;
230
+ case "ease-out":
231
+ return 1 - (1 - t) * (1 - t);
232
+ case "ease-in-out":
233
+ return t < 0.5 ? 2 * t * t : 1 - 2 * (1 - t) * (1 - t);
234
+ default:
235
+ return t;
236
+ }
240
237
  }
241
238
  function evaluateDynamic(dynamic, point, speed) {
242
- if (dynamic.source === 'none') {
243
- return dynamic.max;
244
- }
245
- let input;
246
- switch (dynamic.source) {
247
- case 'pressure':
248
- input = point.pressure;
249
- break;
250
- case 'tilt':
251
- input = Math.sqrt(point.tiltX * point.tiltX + point.tiltY * point.tiltY) / 90;
252
- break;
253
- case 'speed':
254
- input = Math.min(1, speed / 2000);
255
- break;
256
- case 'random':
257
- input = Math.random();
258
- break;
259
- default:
260
- input = 1;
261
- }
262
- const curved = applyCurve(input, dynamic.curve);
263
- return dynamic.min + (dynamic.max - dynamic.min) * curved;
239
+ if (dynamic.source === "none") {
240
+ return dynamic.max;
241
+ }
242
+ let input;
243
+ switch (dynamic.source) {
244
+ case "pressure":
245
+ input = point.pressure;
246
+ break;
247
+ case "tilt":
248
+ input = Math.sqrt(point.tiltX * point.tiltX + point.tiltY * point.tiltY) / 90;
249
+ break;
250
+ case "speed":
251
+ input = Math.min(1, speed / 2e3);
252
+ break;
253
+ case "random":
254
+ input = Math.random();
255
+ break;
256
+ default:
257
+ input = 1;
258
+ }
259
+ const curved = applyCurve(input, dynamic.curve);
260
+ return dynamic.min + (dynamic.max - dynamic.min) * curved;
264
261
  }
265
262
  function hexToRGBA(hex) {
266
- const h = hex.replace('#', '');
267
- return [
268
- parseInt(h.slice(0, 2), 16),
269
- parseInt(h.slice(2, 4), 16),
270
- parseInt(h.slice(4, 6), 16),
271
- 255,
272
- ];
263
+ const h = hex.replace("#", "");
264
+ return [
265
+ parseInt(h.slice(0, 2), 16),
266
+ parseInt(h.slice(2, 4), 16),
267
+ parseInt(h.slice(4, 6), 16),
268
+ 255
269
+ ];
273
270
  }
274
271
  function hslToRgb(h, s, l) {
275
- h = ((h % 1) + 1) % 1;
276
- s = Math.max(0, Math.min(1, s));
277
- l = Math.max(0, Math.min(1, l));
278
- const a = s * Math.min(l, 1 - l);
279
- const f = (n) => {
280
- const k = (n + h * 12) % 12;
281
- return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
282
- };
283
- return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
272
+ h = (h % 1 + 1) % 1;
273
+ s = Math.max(0, Math.min(1, s));
274
+ l = Math.max(0, Math.min(1, l));
275
+ const a = s * Math.min(l, 1 - l);
276
+ const f = (n) => {
277
+ const k = (n + h * 12) % 12;
278
+ return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
279
+ };
280
+ return [Math.round(f(0) * 255), Math.round(f(8) * 255), Math.round(f(4) * 255)];
284
281
  }
285
282
  function rgbToHsl(r, g, b) {
286
- r /= 255;
287
- g /= 255;
288
- b /= 255;
289
- const max = Math.max(r, g, b), min = Math.min(r, g, b);
290
- const l = (max + min) / 2;
291
- if (max === min) {
292
- return [0, 0, l];
293
- }
294
- const d = max - min;
295
- const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
296
- let h = 0;
297
- if (max === r) {
298
- h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
299
- }
300
- else if (max === g) {
301
- h = ((b - r) / d + 2) / 6;
302
- }
303
- else {
304
- h = ((r - g) / d + 4) / 6;
305
- }
306
- return [h, s, l];
283
+ r /= 255;
284
+ g /= 255;
285
+ b /= 255;
286
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
287
+ const l = (max + min) / 2;
288
+ if (max === min) {
289
+ return [0, 0, l];
290
+ }
291
+ const d = max - min;
292
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
293
+ let h = 0;
294
+ if (max === r) {
295
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
296
+ } else if (max === g) {
297
+ h = ((b - r) / d + 2) / 6;
298
+ } else {
299
+ h = ((r - g) / d + 4) / 6;
300
+ }
301
+ return [h, s, l];
307
302
  }
308
303
  function smoothPoints(points, smoothing) {
309
- if (smoothing <= 0 || points.length < 3) {
310
- return points;
304
+ if (smoothing <= 0 || points.length < 3) {
305
+ return points;
306
+ }
307
+ const windowSize = Math.max(2, Math.round(smoothing * 10));
308
+ const result = [points[0]];
309
+ for (let i = 1; i < points.length - 1; i++) {
310
+ const start = Math.max(0, i - windowSize);
311
+ const end = Math.min(points.length - 1, i + windowSize);
312
+ let sumX = 0, sumY = 0, count = 0;
313
+ for (let j = start; j <= end; j++) {
314
+ const w = 1 - Math.abs(j - i) / (windowSize + 1);
315
+ sumX += points[j].x * w;
316
+ sumY += points[j].y * w;
317
+ count += w;
311
318
  }
312
- const windowSize = Math.max(2, Math.round(smoothing * 10));
313
- const result = [points[0]];
314
- for (let i = 1; i < points.length - 1; i++) {
315
- const start = Math.max(0, i - windowSize);
316
- const end = Math.min(points.length - 1, i + windowSize);
317
- let sumX = 0, sumY = 0, count = 0;
318
- for (let j = start; j <= end; j++) {
319
- const w = 1 - Math.abs(j - i) / (windowSize + 1);
320
- sumX += points[j].x * w;
321
- sumY += points[j].y * w;
322
- count += w;
323
- }
324
- result.push({
325
- ...points[i],
326
- x: sumX / count,
327
- y: sumY / count,
328
- });
329
- }
330
- result.push(points[points.length - 1]);
331
- return result;
319
+ result.push({
320
+ ...points[i],
321
+ x: sumX / count,
322
+ y: sumY / count
323
+ });
324
+ }
325
+ result.push(points[points.length - 1]);
326
+ return result;
332
327
  }
333
- /* ── Stamp rendering ── */
334
328
  function renderStamp(ctx, x, y, size, settings, color, dynamicRotation, dynamicOpacity) {
335
- ctx.save();
336
- ctx.translate(x, y);
337
- ctx.rotate(settings.tipRotation + dynamicRotation * Math.PI * 2);
338
- ctx.scale(1, settings.roundness);
339
- const half = size / 2;
340
- if (settings.wetEdges) {
341
- // Wet edges: stronger at periphery
342
- const gradient = ctx.createRadialGradient(0, 0, half * 0.6, 0, 0, half);
343
- const alpha = dynamicOpacity * settings.flow;
344
- gradient.addColorStop(0, `rgba(${color[0]},${color[1]},${color[2]},${alpha * 0.3})`);
345
- gradient.addColorStop(0.7, `rgba(${color[0]},${color[1]},${color[2]},${alpha * 0.6})`);
346
- gradient.addColorStop(1, `rgba(${color[0]},${color[1]},${color[2]},${alpha})`);
347
- ctx.fillStyle = gradient;
348
- }
349
- else if (settings.hardness < 1) {
350
- // Soft brush with radial gradient
351
- const gradient = ctx.createRadialGradient(0, 0, half * settings.hardness, 0, 0, half);
352
- const alpha = dynamicOpacity * settings.flow;
353
- gradient.addColorStop(0, `rgba(${color[0]},${color[1]},${color[2]},${alpha})`);
354
- gradient.addColorStop(1, `rgba(${color[0]},${color[1]},${color[2]},0)`);
355
- ctx.fillStyle = gradient;
329
+ ctx.save();
330
+ ctx.translate(x, y);
331
+ ctx.rotate(settings.tipRotation + dynamicRotation * Math.PI * 2);
332
+ ctx.scale(1, settings.roundness);
333
+ const half = size / 2;
334
+ if (settings.wetEdges) {
335
+ const gradient = ctx.createRadialGradient(0, 0, half * 0.6, 0, 0, half);
336
+ const alpha = dynamicOpacity * settings.flow;
337
+ gradient.addColorStop(0, `rgba(${color[0]},${color[1]},${color[2]},${alpha * 0.3})`);
338
+ gradient.addColorStop(0.7, `rgba(${color[0]},${color[1]},${color[2]},${alpha * 0.6})`);
339
+ gradient.addColorStop(1, `rgba(${color[0]},${color[1]},${color[2]},${alpha})`);
340
+ ctx.fillStyle = gradient;
341
+ } else if (settings.hardness < 1) {
342
+ const gradient = ctx.createRadialGradient(0, 0, half * settings.hardness, 0, 0, half);
343
+ const alpha = dynamicOpacity * settings.flow;
344
+ gradient.addColorStop(0, `rgba(${color[0]},${color[1]},${color[2]},${alpha})`);
345
+ gradient.addColorStop(1, `rgba(${color[0]},${color[1]},${color[2]},0)`);
346
+ ctx.fillStyle = gradient;
347
+ } else {
348
+ ctx.fillStyle = `rgba(${color[0]},${color[1]},${color[2]},${dynamicOpacity * settings.flow})`;
349
+ }
350
+ ctx.globalCompositeOperation = settings.blendMode;
351
+ switch (settings.tipShape) {
352
+ case "square":
353
+ ctx.fillRect(-half, -half, size, size);
354
+ break;
355
+ case "diamond":
356
+ ctx.beginPath();
357
+ ctx.moveTo(0, -half);
358
+ ctx.lineTo(half, 0);
359
+ ctx.lineTo(0, half);
360
+ ctx.lineTo(-half, 0);
361
+ ctx.closePath();
362
+ ctx.fill();
363
+ break;
364
+ default:
365
+ ctx.beginPath();
366
+ ctx.arc(0, 0, half, 0, Math.PI * 2);
367
+ ctx.fill();
368
+ break;
369
+ }
370
+ ctx.restore();
371
+ }
372
+ function useBrushEngine(canvas, color = "#000000") {
373
+ const [settings, setSettings] = React.useState({ ...BRUSH_PRESETS["hard-round"] });
374
+ const [isDrawing, setIsDrawing] = React.useState(false);
375
+ const strokeRef = React.useRef([]);
376
+ const lastStampRef = React.useRef(null);
377
+ const lastTimestampRef = React.useRef(0);
378
+ const applyPreset = React.useCallback((presetName) => {
379
+ const preset = BRUSH_PRESETS[presetName];
380
+ if (preset) {
381
+ setSettings({ ...preset });
356
382
  }
357
- else {
358
- ctx.fillStyle = `rgba(${color[0]},${color[1]},${color[2]},${dynamicOpacity * settings.flow})`;
383
+ }, []);
384
+ const startStroke = React.useCallback(
385
+ (e) => {
386
+ const point = {
387
+ x: e.offsetX,
388
+ y: e.offsetY,
389
+ pressure: e.pressure || 0.5,
390
+ tiltX: e.tiltX || 0,
391
+ tiltY: e.tiltY || 0,
392
+ twist: e.twist || 0,
393
+ timestamp: performance.now()
394
+ };
395
+ strokeRef.current = [point];
396
+ lastStampRef.current = { x: point.x, y: point.y };
397
+ lastTimestampRef.current = point.timestamp;
398
+ setIsDrawing(true);
399
+ if (canvas) {
400
+ const ctx = canvas.getContext("2d");
401
+ if (ctx) {
402
+ const rgba = hexToRGBA(color);
403
+ const dynamicSize = settings.size * evaluateDynamic(settings.sizeDynamics, point, 0);
404
+ const dynamicOpacity = settings.opacity * evaluateDynamic(settings.opacityDynamics, point, 0);
405
+ const dynamicRotation = evaluateDynamic(settings.rotationDynamics, point, 0);
406
+ renderStamp(
407
+ ctx,
408
+ point.x,
409
+ point.y,
410
+ dynamicSize,
411
+ settings,
412
+ rgba,
413
+ dynamicRotation,
414
+ dynamicOpacity
415
+ );
416
+ }
417
+ }
418
+ },
419
+ [canvas, color, settings]
420
+ );
421
+ const continueStroke = React.useCallback(
422
+ (e) => {
423
+ if (!strokeRef.current.length || !canvas) {
424
+ return;
425
+ }
426
+ const now = performance.now();
427
+ const point = {
428
+ x: e.offsetX,
429
+ y: e.offsetY,
430
+ pressure: e.pressure || 0.5,
431
+ tiltX: e.tiltX || 0,
432
+ tiltY: e.tiltY || 0,
433
+ twist: e.twist || 0,
434
+ timestamp: now
435
+ };
436
+ strokeRef.current.push(point);
437
+ const ctx = canvas.getContext("2d");
438
+ if (!ctx || !lastStampRef.current) {
439
+ return;
440
+ }
441
+ const dt = now - lastTimestampRef.current;
442
+ const dx = point.x - lastStampRef.current.x;
443
+ const dy = point.y - lastStampRef.current.y;
444
+ const dist = Math.sqrt(dx * dx + dy * dy);
445
+ const speed = dt > 0 ? dist / dt * 1e3 : 0;
446
+ const dynamicSize = settings.size * evaluateDynamic(settings.sizeDynamics, point, speed);
447
+ const stampDist = Math.max(1, dynamicSize * settings.spacing);
448
+ if (dist < stampDist) {
449
+ return;
450
+ }
451
+ const steps = Math.floor(dist / stampDist);
452
+ const baseRGBA = hexToRGBA(color);
453
+ for (let i = 1; i <= steps; i++) {
454
+ const t = i / steps;
455
+ let sx = lastStampRef.current.x + dx * t;
456
+ let sy = lastStampRef.current.y + dy * t;
457
+ const scatterAmount = settings.scatter * evaluateDynamic(settings.scatterDynamics, point, speed);
458
+ if (scatterAmount > 0) {
459
+ const scatterDist = scatterAmount * dynamicSize;
460
+ sx += (Math.random() - 0.5) * 2 * scatterDist;
461
+ if (settings.scatterBothAxes) {
462
+ sy += (Math.random() - 0.5) * 2 * scatterDist;
463
+ }
464
+ }
465
+ let stampColor = baseRGBA;
466
+ if (settings.colorJitterHue > 0 || settings.colorJitterSat > 0 || settings.colorJitterBright > 0) {
467
+ const [h, s, l] = rgbToHsl(baseRGBA[0], baseRGBA[1], baseRGBA[2]);
468
+ const jH = h + (Math.random() - 0.5) * settings.colorJitterHue;
469
+ const jS = s + (Math.random() - 0.5) * settings.colorJitterSat;
470
+ const jL = l + (Math.random() - 0.5) * settings.colorJitterBright;
471
+ const [r, g, b] = hslToRgb(jH, jS, jL);
472
+ stampColor = [r, g, b, 255];
473
+ }
474
+ const dynamicOpacity = settings.opacity * evaluateDynamic(settings.opacityDynamics, point, speed);
475
+ const dynamicRotation = evaluateDynamic(settings.rotationDynamics, point, speed);
476
+ renderStamp(
477
+ ctx,
478
+ sx,
479
+ sy,
480
+ dynamicSize,
481
+ settings,
482
+ stampColor,
483
+ dynamicRotation,
484
+ dynamicOpacity
485
+ );
486
+ }
487
+ lastStampRef.current = { x: point.x, y: point.y };
488
+ lastTimestampRef.current = now;
489
+ },
490
+ [canvas, color, settings]
491
+ );
492
+ const endStroke = React.useCallback(() => {
493
+ if (!strokeRef.current.length) {
494
+ return null;
359
495
  }
360
- ctx.globalCompositeOperation = settings.blendMode;
361
- switch (settings.tipShape) {
362
- case 'square':
363
- ctx.fillRect(-half, -half, size, size);
364
- break;
365
- case 'diamond':
366
- ctx.beginPath();
367
- ctx.moveTo(0, -half);
368
- ctx.lineTo(half, 0);
369
- ctx.lineTo(0, half);
370
- ctx.lineTo(-half, 0);
371
- ctx.closePath();
372
- ctx.fill();
373
- break;
374
- default: // circle
375
- ctx.beginPath();
376
- ctx.arc(0, 0, half, 0, Math.PI * 2);
377
- ctx.fill();
378
- break;
496
+ const smoothed = smoothPoints(strokeRef.current, settings.smoothing);
497
+ const stroke = {
498
+ id: `stroke-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
499
+ points: smoothed,
500
+ color,
501
+ settings: { ...settings }
502
+ };
503
+ strokeRef.current = [];
504
+ lastStampRef.current = null;
505
+ setIsDrawing(false);
506
+ return stroke;
507
+ }, [color, settings]);
508
+ React.useEffect(() => {
509
+ if (!canvas) {
510
+ return;
379
511
  }
380
- ctx.restore();
512
+ const onDown = (e) => {
513
+ canvas.setPointerCapture(e.pointerId);
514
+ startStroke(e);
515
+ };
516
+ const onMove = (e) => {
517
+ if (e.buttons > 0) {
518
+ continueStroke(e);
519
+ }
520
+ };
521
+ const onUp = () => {
522
+ endStroke();
523
+ };
524
+ canvas.addEventListener("pointerdown", onDown);
525
+ canvas.addEventListener("pointermove", onMove);
526
+ canvas.addEventListener("pointerup", onUp);
527
+ canvas.addEventListener("pointercancel", onUp);
528
+ canvas.style.touchAction = "none";
529
+ return () => {
530
+ canvas.removeEventListener("pointerdown", onDown);
531
+ canvas.removeEventListener("pointermove", onMove);
532
+ canvas.removeEventListener("pointerup", onUp);
533
+ canvas.removeEventListener("pointercancel", onUp);
534
+ };
535
+ }, [canvas, startStroke, continueStroke, endStroke]);
536
+ return {
537
+ settings,
538
+ setSettings,
539
+ applyPreset,
540
+ isDrawing,
541
+ startStroke,
542
+ continueStroke,
543
+ endStroke,
544
+ presetNames: Object.keys(BRUSH_PRESETS)
545
+ };
381
546
  }
382
- /* ── Hook ── */
383
- function useBrushEngine(canvas,
384
- // Paint, not a theme role — and canvas ignores var().
385
- color = '#000000') {
386
- const [settings, setSettings] = React.useState({ ...BRUSH_PRESETS['hard-round'] });
387
- const [isDrawing, setIsDrawing] = React.useState(false);
388
- const strokeRef = React.useRef([]);
389
- const lastStampRef = React.useRef(null);
390
- const lastTimestampRef = React.useRef(0);
391
- const applyPreset = React.useCallback((presetName) => {
392
- const preset = BRUSH_PRESETS[presetName];
393
- if (preset) {
394
- setSettings({ ...preset });
395
- }
396
- }, []);
397
- const startStroke = React.useCallback((e) => {
398
- const point = {
399
- x: e.offsetX,
400
- y: e.offsetY,
401
- pressure: e.pressure || 0.5,
402
- tiltX: e.tiltX || 0,
403
- tiltY: e.tiltY || 0,
404
- twist: e.twist || 0,
405
- timestamp: performance.now(),
406
- };
407
- strokeRef.current = [point];
408
- lastStampRef.current = { x: point.x, y: point.y };
409
- lastTimestampRef.current = point.timestamp;
410
- setIsDrawing(true);
411
- // First stamp
412
- if (canvas) {
413
- const ctx = canvas.getContext('2d');
414
- if (ctx) {
415
- const rgba = hexToRGBA(color);
416
- const dynamicSize = settings.size * evaluateDynamic(settings.sizeDynamics, point, 0);
417
- const dynamicOpacity = settings.opacity * evaluateDynamic(settings.opacityDynamics, point, 0);
418
- const dynamicRotation = evaluateDynamic(settings.rotationDynamics, point, 0);
419
- renderStamp(ctx, point.x, point.y, dynamicSize, settings, rgba, dynamicRotation, dynamicOpacity);
547
+ const NiceBrushEngine = ({
548
+ canvas,
549
+ color = "#000000",
550
+ onStrokeEnd,
551
+ className = ""
552
+ }) => {
553
+ const { t } = i18n.useNiceTranslation();
554
+ const engine = useBrushEngine(canvas, color);
555
+ const { endStroke } = engine;
556
+ React.useEffect(() => {
557
+ if (!canvas) {
558
+ return;
559
+ }
560
+ const onUp = () => {
561
+ const stroke = endStroke();
562
+ if (stroke && onStrokeEnd) {
563
+ onStrokeEnd(stroke);
564
+ }
565
+ };
566
+ canvas.addEventListener("pointerup", onUp);
567
+ return () => canvas.removeEventListener("pointerup", onUp);
568
+ }, [canvas, endStroke, onStrokeEnd]);
569
+ const s = engine.settings;
570
+ return /* @__PURE__ */ jsxRuntime.jsxs(
571
+ "div",
572
+ {
573
+ className: `nice-brush-engine ${className}`,
574
+ style: { display: "grid", gap: 6, fontSize: 13 },
575
+ children: [
576
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
577
+ /* @__PURE__ */ jsxRuntime.jsx(uiCore.NiceIcon, { name: "brush", size: 14 }),
578
+ " ",
579
+ t("brushEngine.brushEngineV2", "Brush Engine v2")
580
+ ] }),
581
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 2, flexWrap: "wrap" }, children: engine.presetNames.map((name) => /* @__PURE__ */ jsxRuntime.jsx(
582
+ "button",
583
+ {
584
+ type: "button",
585
+ onClick: () => engine.applyPreset(name),
586
+ style: {
587
+ padding: "2px 6px",
588
+ fontSize: 10,
589
+ cursor: "pointer",
590
+ borderRadius: 4,
591
+ border: "1px solid var(--nice-border, #e2e8f0)",
592
+ background: s.name === BRUSH_PRESETS[name]?.name ? "var(--nice-color-accent, #6366f1)" : "transparent",
593
+ color: s.name === BRUSH_PRESETS[name]?.name ? "var(--nice-color-white, #fff)" : "inherit"
594
+ },
595
+ children: BRUSH_PRESETS[name].name
596
+ },
597
+ name
598
+ )) }),
599
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
600
+ t("brushEngine.sizePx", "Size: {size}px").replace("{size}", String(s.size)),
601
+ /* @__PURE__ */ jsxRuntime.jsx(
602
+ "input",
603
+ {
604
+ type: "range",
605
+ min: 1,
606
+ max: 200,
607
+ value: s.size,
608
+ onChange: (e) => engine.setSettings({ ...s, size: +e.target.value }),
609
+ style: { width: "100%" }
420
610
  }
421
- }
422
- }, [canvas, color, settings]);
423
- const continueStroke = React.useCallback((e) => {
424
- if (!strokeRef.current.length || !canvas) {
425
- return;
426
- }
427
- const now = performance.now();
428
- const point = {
429
- x: e.offsetX,
430
- y: e.offsetY,
431
- pressure: e.pressure || 0.5,
432
- tiltX: e.tiltX || 0,
433
- tiltY: e.tiltY || 0,
434
- twist: e.twist || 0,
435
- timestamp: now,
436
- };
437
- strokeRef.current.push(point);
438
- const ctx = canvas.getContext('2d');
439
- if (!ctx || !lastStampRef.current) {
440
- return;
441
- }
442
- // Calculate speed
443
- const dt = now - lastTimestampRef.current;
444
- const dx = point.x - lastStampRef.current.x;
445
- const dy = point.y - lastStampRef.current.y;
446
- const dist = Math.sqrt(dx * dx + dy * dy);
447
- const speed = dt > 0 ? (dist / dt) * 1000 : 0;
448
- // Stamp spacing
449
- const dynamicSize = settings.size * evaluateDynamic(settings.sizeDynamics, point, speed);
450
- const stampDist = Math.max(1, dynamicSize * settings.spacing);
451
- if (dist < stampDist) {
452
- return;
453
- }
454
- const steps = Math.floor(dist / stampDist);
455
- const baseRGBA = hexToRGBA(color);
456
- for (let i = 1; i <= steps; i++) {
457
- const t = i / steps;
458
- let sx = lastStampRef.current.x + dx * t;
459
- let sy = lastStampRef.current.y + dy * t;
460
- // Apply scatter
461
- const scatterAmount = settings.scatter * evaluateDynamic(settings.scatterDynamics, point, speed);
462
- if (scatterAmount > 0) {
463
- const scatterDist = scatterAmount * dynamicSize;
464
- sx += (Math.random() - 0.5) * 2 * scatterDist;
465
- if (settings.scatterBothAxes) {
466
- sy += (Math.random() - 0.5) * 2 * scatterDist;
467
- }
611
+ )
612
+ ] }),
613
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
614
+ t("brushEngine.opacity", "Opacity: {opacity}%").replace(
615
+ "{opacity}",
616
+ String(Math.round(s.opacity * 100))
617
+ ),
618
+ /* @__PURE__ */ jsxRuntime.jsx(
619
+ "input",
620
+ {
621
+ type: "range",
622
+ min: 0,
623
+ max: 1,
624
+ step: 0.01,
625
+ value: s.opacity,
626
+ onChange: (e) => engine.setSettings({ ...s, opacity: +e.target.value }),
627
+ style: { width: "100%" }
468
628
  }
469
- // Color jitter
470
- let stampColor = baseRGBA;
471
- if (settings.colorJitterHue > 0 ||
472
- settings.colorJitterSat > 0 ||
473
- settings.colorJitterBright > 0) {
474
- const [h, s, l] = rgbToHsl(baseRGBA[0], baseRGBA[1], baseRGBA[2]);
475
- const jH = h + (Math.random() - 0.5) * settings.colorJitterHue;
476
- const jS = s + (Math.random() - 0.5) * settings.colorJitterSat;
477
- const jL = l + (Math.random() - 0.5) * settings.colorJitterBright;
478
- const [r, g, b] = hslToRgb(jH, jS, jL);
479
- stampColor = [r, g, b, 255];
629
+ )
630
+ ] }),
631
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
632
+ t("brushEngine.flow", "Flow: {flow}%").replace("{flow}", String(Math.round(s.flow * 100))),
633
+ /* @__PURE__ */ jsxRuntime.jsx(
634
+ "input",
635
+ {
636
+ type: "range",
637
+ min: 0,
638
+ max: 1,
639
+ step: 0.01,
640
+ value: s.flow,
641
+ onChange: (e) => engine.setSettings({ ...s, flow: +e.target.value }),
642
+ style: { width: "100%" }
480
643
  }
481
- const dynamicOpacity = settings.opacity * evaluateDynamic(settings.opacityDynamics, point, speed);
482
- const dynamicRotation = evaluateDynamic(settings.rotationDynamics, point, speed);
483
- renderStamp(ctx, sx, sy, dynamicSize, settings, stampColor, dynamicRotation, dynamicOpacity);
484
- }
485
- lastStampRef.current = { x: point.x, y: point.y };
486
- lastTimestampRef.current = now;
487
- }, [canvas, color, settings]);
488
- const endStroke = React.useCallback(() => {
489
- if (!strokeRef.current.length) {
490
- return null;
491
- }
492
- const smoothed = smoothPoints(strokeRef.current, settings.smoothing);
493
- const stroke = {
494
- id: `stroke-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
495
- points: smoothed,
496
- color,
497
- settings: { ...settings },
498
- };
499
- strokeRef.current = [];
500
- lastStampRef.current = null;
501
- setIsDrawing(false);
502
- return stroke;
503
- }, [color, settings]);
504
- // Auto-attach pointer events
505
- React.useEffect(() => {
506
- if (!canvas) {
507
- return;
508
- }
509
- const onDown = (e) => {
510
- canvas.setPointerCapture(e.pointerId);
511
- startStroke(e);
512
- };
513
- const onMove = (e) => {
514
- if (e.buttons > 0) {
515
- continueStroke(e);
644
+ )
645
+ ] }),
646
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
647
+ t("brushEngine.hardness", "Hardness: {hardness}%").replace(
648
+ "{hardness}",
649
+ String(Math.round(s.hardness * 100))
650
+ ),
651
+ /* @__PURE__ */ jsxRuntime.jsx(
652
+ "input",
653
+ {
654
+ type: "range",
655
+ min: 0,
656
+ max: 1,
657
+ step: 0.01,
658
+ value: s.hardness,
659
+ onChange: (e) => engine.setSettings({ ...s, hardness: +e.target.value }),
660
+ style: { width: "100%" }
516
661
  }
517
- };
518
- const onUp = () => {
519
- endStroke();
520
- };
521
- canvas.addEventListener('pointerdown', onDown);
522
- canvas.addEventListener('pointermove', onMove);
523
- canvas.addEventListener('pointerup', onUp);
524
- canvas.addEventListener('pointercancel', onUp);
525
- // Enable pressure events
526
- canvas.style.touchAction = 'none';
527
- return () => {
528
- canvas.removeEventListener('pointerdown', onDown);
529
- canvas.removeEventListener('pointermove', onMove);
530
- canvas.removeEventListener('pointerup', onUp);
531
- canvas.removeEventListener('pointercancel', onUp);
532
- };
533
- }, [canvas, startStroke, continueStroke, endStroke]);
534
- return {
535
- settings,
536
- setSettings,
537
- applyPreset,
538
- isDrawing,
539
- startStroke,
540
- continueStroke,
541
- endStroke,
542
- presetNames: Object.keys(BRUSH_PRESETS),
543
- };
544
- }
545
- /* ── Component ── */
546
- const NiceBrushEngine = ({ canvas, color = '#000000', onStrokeEnd, className = '', }) => {
547
- const { t } = i18n.useNiceTranslation();
548
- const engine = useBrushEngine(canvas, color);
549
- const { endStroke } = engine;
550
- // Override endStroke to fire callback
551
- React.useEffect(() => {
552
- if (!canvas) {
553
- return;
554
- }
555
- const onUp = () => {
556
- const stroke = endStroke();
557
- if (stroke && onStrokeEnd) {
558
- onStrokeEnd(stroke);
662
+ )
663
+ ] }),
664
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
665
+ t("brushEngine.smoothing", "Smoothing: {smoothing}%").replace(
666
+ "{smoothing}",
667
+ String(Math.round(s.smoothing * 100))
668
+ ),
669
+ /* @__PURE__ */ jsxRuntime.jsx(
670
+ "input",
671
+ {
672
+ type: "range",
673
+ min: 0,
674
+ max: 1,
675
+ step: 0.01,
676
+ value: s.smoothing,
677
+ onChange: (e) => engine.setSettings({ ...s, smoothing: +e.target.value }),
678
+ style: { width: "100%" }
559
679
  }
560
- };
561
- canvas.addEventListener('pointerup', onUp);
562
- return () => canvas.removeEventListener('pointerup', onUp);
563
- }, [canvas, endStroke, onStrokeEnd]);
564
- const s = engine.settings;
565
- return (jsxRuntime.jsxs("div", { className: `nice-brush-engine ${className}`, style: { display: 'grid', gap: 6, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "brush", size: 14 }), " ", t('brushEngine.brushEngineV2', 'Brush Engine v2')] }), jsxRuntime.jsx("div", { style: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: engine.presetNames.map((name) => {
566
- var _a, _b;
567
- return (jsxRuntime.jsx("button", { type: "button", onClick: () => engine.applyPreset(name), style: {
568
- padding: '2px 6px',
569
- fontSize: 10,
570
- cursor: 'pointer',
571
- borderRadius: 4,
572
- border: '1px solid var(--nice-border, #e2e8f0)',
573
- background: s.name === ((_a = BRUSH_PRESETS[name]) === null || _a === void 0 ? void 0 : _a.name)
574
- ? 'var(--nice-color-accent, #6366f1)'
575
- : 'transparent',
576
- color: s.name === ((_b = BRUSH_PRESETS[name]) === null || _b === void 0 ? void 0 : _b.name) ? 'var(--nice-color-white, #fff)' : 'inherit',
577
- }, children: BRUSH_PRESETS[name].name }, name));
578
- }) }), jsxRuntime.jsxs("label", { children: [t('brushEngine.sizePx', 'Size: {size}px').replace('{size}', String(s.size)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 200, value: s.size, onChange: (e) => engine.setSettings({ ...s, size: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('brushEngine.opacity', 'Opacity: {opacity}%').replace('{opacity}', String(Math.round(s.opacity * 100))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.opacity, onChange: (e) => engine.setSettings({ ...s, opacity: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('brushEngine.flow', 'Flow: {flow}%').replace('{flow}', String(Math.round(s.flow * 100))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.flow, onChange: (e) => engine.setSettings({ ...s, flow: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('brushEngine.hardness', 'Hardness: {hardness}%').replace('{hardness}', String(Math.round(s.hardness * 100))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.hardness, onChange: (e) => engine.setSettings({ ...s, hardness: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('brushEngine.smoothing', 'Smoothing: {smoothing}%').replace('{smoothing}', String(Math.round(s.smoothing * 100))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.smoothing, onChange: (e) => engine.setSettings({ ...s, smoothing: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('brushEngine.tip', 'Tip:'), jsxRuntime.jsxs("select", { value: s.tipShape, onChange: (e) => engine.setSettings({ ...s, tipShape: e.target.value }), style: { width: '100%' }, children: [jsxRuntime.jsx("option", { value: "circle", children: t('brushEngine.circle', 'Circle') }), jsxRuntime.jsx("option", { value: "square", children: t('brushEngine.square', 'Square') }), jsxRuntime.jsx("option", { value: "diamond", children: t('brushEngine.diamond', 'Diamond') }), jsxRuntime.jsx("option", { value: "texture", children: t('brushEngine.texture', 'Texture') })] })] }), jsxRuntime.jsxs("label", { children: [t('brushEngine.roundness', 'Roundness: {roundness}%').replace('{roundness}', String(Math.round(s.roundness * 100))), jsxRuntime.jsx("input", { type: "range", min: 0.05, max: 1, step: 0.05, value: s.roundness, onChange: (e) => engine.setSettings({ ...s, roundness: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("details", { children: [jsxRuntime.jsxs("summary", { style: { cursor: 'pointer', fontSize: 12 }, children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "bar-chart", size: 12 }), " ", t('brushEngine.sizeDynamics', 'Size Dynamics')] }), jsxRuntime.jsxs("label", { children: [t('brushEngine.source', 'Source:'), jsxRuntime.jsxs("select", { value: s.sizeDynamics.source, onChange: (e) => engine.setSettings({
579
- ...s,
580
- sizeDynamics: {
581
- ...s.sizeDynamics,
582
- source: e.target.value,
583
- },
584
- }), style: { width: '100%' }, children: [jsxRuntime.jsx("option", { value: "none", children: t('brushEngine.off', 'Off') }), jsxRuntime.jsx("option", { value: "pressure", children: t('brushEngine.pressure', 'Pressure') }), jsxRuntime.jsx("option", { value: "tilt", children: t('brushEngine.tilt', 'Tilt') }), jsxRuntime.jsx("option", { value: "speed", children: t('brushEngine.speed', 'Speed') }), jsxRuntime.jsx("option", { value: "random", children: t('brushEngine.random', 'Random') })] })] })] }), jsxRuntime.jsxs("label", { children: [t('brushEngine.scatter', 'Scatter: {scatter}%').replace('{scatter}', String(Math.round(s.scatter * 100))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.01, value: s.scatter, onChange: (e) => engine.setSettings({ ...s, scatter: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('brushEngine.blend', 'Blend:'), jsxRuntime.jsx("select", { value: s.blendMode, onChange: (e) => engine.setSettings({ ...s, blendMode: e.target.value }), style: { width: '100%' }, children: ['normal', 'multiply', 'screen', 'overlay', 'darken', 'lighten'].map((m) => (jsxRuntime.jsx("option", { value: m, children: m }, m))) })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [jsxRuntime.jsx("input", { type: "checkbox", checked: s.wetEdges, onChange: (e) => engine.setSettings({ ...s, wetEdges: e.target.checked }) }), t('brushEngine.wetEdges', 'Wet Edges')] })] }));
680
+ )
681
+ ] }),
682
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
683
+ t("brushEngine.tip", "Tip:"),
684
+ /* @__PURE__ */ jsxRuntime.jsxs(
685
+ "select",
686
+ {
687
+ value: s.tipShape,
688
+ onChange: (e) => engine.setSettings({ ...s, tipShape: e.target.value }),
689
+ style: { width: "100%" },
690
+ children: [
691
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "circle", children: t("brushEngine.circle", "Circle") }),
692
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "square", children: t("brushEngine.square", "Square") }),
693
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "diamond", children: t("brushEngine.diamond", "Diamond") }),
694
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "texture", children: t("brushEngine.texture", "Texture") })
695
+ ]
696
+ }
697
+ )
698
+ ] }),
699
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
700
+ t("brushEngine.roundness", "Roundness: {roundness}%").replace(
701
+ "{roundness}",
702
+ String(Math.round(s.roundness * 100))
703
+ ),
704
+ /* @__PURE__ */ jsxRuntime.jsx(
705
+ "input",
706
+ {
707
+ type: "range",
708
+ min: 0.05,
709
+ max: 1,
710
+ step: 0.05,
711
+ value: s.roundness,
712
+ onChange: (e) => engine.setSettings({ ...s, roundness: +e.target.value }),
713
+ style: { width: "100%" }
714
+ }
715
+ )
716
+ ] }),
717
+ /* @__PURE__ */ jsxRuntime.jsxs("details", { children: [
718
+ /* @__PURE__ */ jsxRuntime.jsxs("summary", { style: { cursor: "pointer", fontSize: 12 }, children: [
719
+ /* @__PURE__ */ jsxRuntime.jsx(uiCore.NiceIcon, { name: "bar-chart", size: 12 }),
720
+ " ",
721
+ t("brushEngine.sizeDynamics", "Size Dynamics")
722
+ ] }),
723
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
724
+ t("brushEngine.source", "Source:"),
725
+ /* @__PURE__ */ jsxRuntime.jsxs(
726
+ "select",
727
+ {
728
+ value: s.sizeDynamics.source,
729
+ onChange: (e) => engine.setSettings({
730
+ ...s,
731
+ sizeDynamics: {
732
+ ...s.sizeDynamics,
733
+ source: e.target.value
734
+ }
735
+ }),
736
+ style: { width: "100%" },
737
+ children: [
738
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "none", children: t("brushEngine.off", "Off") }),
739
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "pressure", children: t("brushEngine.pressure", "Pressure") }),
740
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "tilt", children: t("brushEngine.tilt", "Tilt") }),
741
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "speed", children: t("brushEngine.speed", "Speed") }),
742
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "random", children: t("brushEngine.random", "Random") })
743
+ ]
744
+ }
745
+ )
746
+ ] })
747
+ ] }),
748
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
749
+ t("brushEngine.scatter", "Scatter: {scatter}%").replace(
750
+ "{scatter}",
751
+ String(Math.round(s.scatter * 100))
752
+ ),
753
+ /* @__PURE__ */ jsxRuntime.jsx(
754
+ "input",
755
+ {
756
+ type: "range",
757
+ min: 0,
758
+ max: 1,
759
+ step: 0.01,
760
+ value: s.scatter,
761
+ onChange: (e) => engine.setSettings({ ...s, scatter: +e.target.value }),
762
+ style: { width: "100%" }
763
+ }
764
+ )
765
+ ] }),
766
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
767
+ t("brushEngine.blend", "Blend:"),
768
+ /* @__PURE__ */ jsxRuntime.jsx(
769
+ "select",
770
+ {
771
+ value: s.blendMode,
772
+ onChange: (e) => engine.setSettings({ ...s, blendMode: e.target.value }),
773
+ style: { width: "100%" },
774
+ children: ["normal", "multiply", "screen", "overlay", "darken", "lighten"].map((m) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: m, children: m }, m))
775
+ }
776
+ )
777
+ ] }),
778
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [
779
+ /* @__PURE__ */ jsxRuntime.jsx(
780
+ "input",
781
+ {
782
+ type: "checkbox",
783
+ checked: s.wetEdges,
784
+ onChange: (e) => engine.setSettings({ ...s, wetEdges: e.target.checked })
785
+ }
786
+ ),
787
+ t("brushEngine.wetEdges", "Wet Edges")
788
+ ] })
789
+ ]
790
+ }
791
+ );
585
792
  };
586
-
587
793
  exports.BRUSH_PRESETS = BRUSH_PRESETS;
588
794
  exports.NiceBrushEngine = NiceBrushEngine;
589
795
  exports.useBrushEngine = useBrushEngine;