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