@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,508 +1,720 @@
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('../core/i18n.js');
7
-
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("../core/i18n.js");
8
7
  function luminance(r, g, b) {
9
- return 0.299 * r + 0.587 * g + 0.114 * b;
8
+ return 0.299 * r + 0.587 * g + 0.114 * b;
10
9
  }
11
- /**
12
- * Auto-colorize a grayscale image using heuristic color mapping.
13
- * Maps luminance regions to plausible colors (sky-blue for bright areas,
14
- * earth tones for dark areas, greens for mid-tones).
15
- */
16
10
  function autoColorize(source, options) {
17
- const { intensity, temperature, saturation } = options;
18
- const result = new ImageData(new Uint8ClampedArray(source.data), source.width, source.height);
19
- const data = result.data;
20
- // Temperature bias
21
- const tempBias = temperature === 'warm' ? [20, 5, -15] : temperature === 'cool' ? [-10, 0, 15] : [0, 0, 0];
22
- for (let i = 0; i < data.length; i += 4) {
23
- const lum = luminance(data[i], data[i + 1], data[i + 2]);
24
- const isGray = Math.abs(data[i] - data[i + 1]) < 15 && Math.abs(data[i + 1] - data[i + 2]) < 15;
25
- if (isGray) {
26
- // Map grayscale to heuristic colors based on luminance
27
- let cr, cg, cb;
28
- if (lum > 200) {
29
- // Very bright → sky blue / white
30
- cr = 200 + (lum - 200) * 0.8;
31
- cg = 210 + (lum - 200) * 0.6;
32
- cb = 230;
33
- }
34
- else if (lum > 140) {
35
- // Upper mid → warm tones
36
- cr = lum * 1.05;
37
- cg = lum * 0.95;
38
- cb = lum * 0.85;
39
- }
40
- else if (lum > 80) {
41
- // Mid tones → greenish
42
- cr = lum * 0.85;
43
- cg = lum * 1.1;
44
- cb = lum * 0.8;
45
- }
46
- else {
47
- // Dark → earth/brown
48
- cr = lum * 1.15;
49
- cg = lum * 0.9;
50
- cb = lum * 0.7;
51
- }
52
- // Apply temperature bias
53
- cr = Math.max(0, Math.min(255, cr + tempBias[0]));
54
- cg = Math.max(0, Math.min(255, cg + tempBias[1]));
55
- cb = Math.max(0, Math.min(255, cb + tempBias[2]));
56
- // Apply saturation
57
- const avg = (cr + cg + cb) / 3;
58
- cr = avg + (cr - avg) * saturation;
59
- cg = avg + (cg - avg) * saturation;
60
- cb = avg + (cb - avg) * saturation;
61
- // Blend with original based on intensity
62
- data[i] = Math.max(0, Math.min(255, data[i] * (1 - intensity) + cr * intensity));
63
- data[i + 1] = Math.max(0, Math.min(255, data[i + 1] * (1 - intensity) + cg * intensity));
64
- data[i + 2] = Math.max(0, Math.min(255, data[i + 2] * (1 - intensity) + cb * intensity));
65
- }
11
+ const { intensity, temperature, saturation } = options;
12
+ const result = new ImageData(new Uint8ClampedArray(source.data), source.width, source.height);
13
+ const data = result.data;
14
+ const tempBias = temperature === "warm" ? [20, 5, -15] : temperature === "cool" ? [-10, 0, 15] : [0, 0, 0];
15
+ for (let i = 0; i < data.length; i += 4) {
16
+ const lum = luminance(data[i], data[i + 1], data[i + 2]);
17
+ const isGray = Math.abs(data[i] - data[i + 1]) < 15 && Math.abs(data[i + 1] - data[i + 2]) < 15;
18
+ if (isGray) {
19
+ let cr, cg, cb;
20
+ if (lum > 200) {
21
+ cr = 200 + (lum - 200) * 0.8;
22
+ cg = 210 + (lum - 200) * 0.6;
23
+ cb = 230;
24
+ } else if (lum > 140) {
25
+ cr = lum * 1.05;
26
+ cg = lum * 0.95;
27
+ cb = lum * 0.85;
28
+ } else if (lum > 80) {
29
+ cr = lum * 0.85;
30
+ cg = lum * 1.1;
31
+ cb = lum * 0.8;
32
+ } else {
33
+ cr = lum * 1.15;
34
+ cg = lum * 0.9;
35
+ cb = lum * 0.7;
36
+ }
37
+ cr = Math.max(0, Math.min(255, cr + tempBias[0]));
38
+ cg = Math.max(0, Math.min(255, cg + tempBias[1]));
39
+ cb = Math.max(0, Math.min(255, cb + tempBias[2]));
40
+ const avg = (cr + cg + cb) / 3;
41
+ cr = avg + (cr - avg) * saturation;
42
+ cg = avg + (cg - avg) * saturation;
43
+ cb = avg + (cb - avg) * saturation;
44
+ data[i] = Math.max(0, Math.min(255, data[i] * (1 - intensity) + cr * intensity));
45
+ data[i + 1] = Math.max(0, Math.min(255, data[i + 1] * (1 - intensity) + cg * intensity));
46
+ data[i + 2] = Math.max(0, Math.min(255, data[i + 2] * (1 - intensity) + cb * intensity));
66
47
  }
67
- return result;
48
+ }
49
+ return result;
68
50
  }
69
- /**
70
- * Remove background using edge-based foreground detection.
71
- * Uses Sobel edge detection + flood fill from edges to find background.
72
- */
73
51
  function removeBackground(source, options) {
74
- const { threshold, iterations, featherRadius, replacementColor } = options;
75
- const w = source.width;
76
- const h = source.height;
77
- const result = new ImageData(new Uint8ClampedArray(source.data), w, h);
78
- const data = result.data;
79
- // 1. Compute edge magnitude (Sobel)
80
- const edges = new Float32Array(w * h);
52
+ const { threshold, iterations, featherRadius, replacementColor } = options;
53
+ const w = source.width;
54
+ const h = source.height;
55
+ const result = new ImageData(new Uint8ClampedArray(source.data), w, h);
56
+ const data = result.data;
57
+ const edges = new Float32Array(w * h);
58
+ for (let y = 1; y < h - 1; y++) {
59
+ for (let x = 1; x < w - 1; x++) {
60
+ const idx = (y * w + x) * 4;
61
+ const tl = luminance(
62
+ source.data[idx - w * 4 - 4],
63
+ source.data[idx - w * 4 - 3],
64
+ source.data[idx - w * 4 - 2]
65
+ );
66
+ const tc = luminance(
67
+ source.data[idx - w * 4],
68
+ source.data[idx - w * 4 + 1],
69
+ source.data[idx - w * 4 + 2]
70
+ );
71
+ const tr = luminance(
72
+ source.data[idx - w * 4 + 4],
73
+ source.data[idx - w * 4 + 5],
74
+ source.data[idx - w * 4 + 6]
75
+ );
76
+ const ml = luminance(source.data[idx - 4], source.data[idx - 3], source.data[idx - 2]);
77
+ const mr = luminance(source.data[idx + 4], source.data[idx + 5], source.data[idx + 6]);
78
+ const bl = luminance(
79
+ source.data[idx + w * 4 - 4],
80
+ source.data[idx + w * 4 - 3],
81
+ source.data[idx + w * 4 - 2]
82
+ );
83
+ const bc = luminance(
84
+ source.data[idx + w * 4],
85
+ source.data[idx + w * 4 + 1],
86
+ source.data[idx + w * 4 + 2]
87
+ );
88
+ const br = luminance(
89
+ source.data[idx + w * 4 + 4],
90
+ source.data[idx + w * 4 + 5],
91
+ source.data[idx + w * 4 + 6]
92
+ );
93
+ const gx = -tl - 2 * ml - bl + tr + 2 * mr + br;
94
+ const gy = -tl - 2 * tc - tr + bl + 2 * bc + br;
95
+ edges[y * w + x] = Math.sqrt(gx * gx + gy * gy) / 255;
96
+ }
97
+ }
98
+ const mask = new Float32Array(w * h);
99
+ const edgeThreshold = threshold * 0.5;
100
+ for (let i = 0; i < edges.length; i++) {
101
+ mask[i] = edges[i] > edgeThreshold ? 1 : 0;
102
+ }
103
+ for (let iter = 0; iter < iterations; iter++) {
104
+ const temp = new Float32Array(mask);
81
105
  for (let y = 1; y < h - 1; y++) {
82
- for (let x = 1; x < w - 1; x++) {
83
- const idx = (y * w + x) * 4;
84
- const tl = luminance(source.data[idx - w * 4 - 4], source.data[idx - w * 4 - 3], source.data[idx - w * 4 - 2]);
85
- const tc = luminance(source.data[idx - w * 4], source.data[idx - w * 4 + 1], source.data[idx - w * 4 + 2]);
86
- const tr = luminance(source.data[idx - w * 4 + 4], source.data[idx - w * 4 + 5], source.data[idx - w * 4 + 6]);
87
- const ml = luminance(source.data[idx - 4], source.data[idx - 3], source.data[idx - 2]);
88
- const mr = luminance(source.data[idx + 4], source.data[idx + 5], source.data[idx + 6]);
89
- const bl = luminance(source.data[idx + w * 4 - 4], source.data[idx + w * 4 - 3], source.data[idx + w * 4 - 2]);
90
- const bc = luminance(source.data[idx + w * 4], source.data[idx + w * 4 + 1], source.data[idx + w * 4 + 2]);
91
- const br = luminance(source.data[idx + w * 4 + 4], source.data[idx + w * 4 + 5], source.data[idx + w * 4 + 6]);
92
- const gx = -tl - 2 * ml - bl + tr + 2 * mr + br;
93
- const gy = -tl - 2 * tc - tr + bl + 2 * bc + br;
94
- edges[y * w + x] = Math.sqrt(gx * gx + gy * gy) / 255;
106
+ for (let x = 1; x < w - 1; x++) {
107
+ const idx = y * w + x;
108
+ if (temp[idx] > 0) {
109
+ continue;
95
110
  }
96
- }
97
- // 2. Create foreground mask from edge density
98
- const mask = new Float32Array(w * h);
99
- const edgeThreshold = threshold * 0.5;
100
- // Mark edges as definite foreground
101
- for (let i = 0; i < edges.length; i++) {
102
- mask[i] = edges[i] > edgeThreshold ? 1 : 0;
103
- }
104
- // 3. Dilate foreground mask (iterations)
105
- for (let iter = 0; iter < iterations; iter++) {
106
- const temp = new Float32Array(mask);
107
- for (let y = 1; y < h - 1; y++) {
108
- for (let x = 1; x < w - 1; x++) {
109
- const idx = y * w + x;
110
- if (temp[idx] > 0) {
111
- continue;
112
- }
113
- // Check 4-neighbors
114
- if (temp[idx - 1] > 0 || temp[idx + 1] > 0 || temp[idx - w] > 0 || temp[idx + w] > 0) {
115
- // Check color similarity with neighbor
116
- const pi = idx * 4;
117
- let isFg = false;
118
- for (const ni of [idx - 1, idx + 1, idx - w, idx + w]) {
119
- if (temp[ni] > 0) {
120
- const npi = ni * 4;
121
- const diff = (Math.abs(source.data[pi] - source.data[npi]) +
122
- Math.abs(source.data[pi + 1] - source.data[npi + 1]) +
123
- Math.abs(source.data[pi + 2] - source.data[npi + 2])) /
124
- 3;
125
- if (diff < threshold * 255) {
126
- isFg = true;
127
- break;
128
- }
129
- }
130
- }
131
- if (isFg) {
132
- mask[idx] = 1;
133
- }
134
- }
111
+ if (temp[idx - 1] > 0 || temp[idx + 1] > 0 || temp[idx - w] > 0 || temp[idx + w] > 0) {
112
+ const pi = idx * 4;
113
+ let isFg = false;
114
+ for (const ni of [idx - 1, idx + 1, idx - w, idx + w]) {
115
+ if (temp[ni] > 0) {
116
+ const npi = ni * 4;
117
+ const diff = (Math.abs(source.data[pi] - source.data[npi]) + Math.abs(source.data[pi + 1] - source.data[npi + 1]) + Math.abs(source.data[pi + 2] - source.data[npi + 2])) / 3;
118
+ if (diff < threshold * 255) {
119
+ isFg = true;
120
+ break;
121
+ }
135
122
  }
123
+ }
124
+ if (isFg) {
125
+ mask[idx] = 1;
126
+ }
136
127
  }
128
+ }
137
129
  }
138
- // 4. Flood fill from border pixels as background (anything not foreground and reachable from edge)
139
- const visited = new Uint8Array(w * h);
140
- const queue = [];
141
- // Seed from border
142
- for (let x = 0; x < w; x++) {
143
- if (mask[x] === 0) {
144
- queue.push(x);
145
- visited[x] = 1;
146
- }
147
- const bi = (h - 1) * w + x;
148
- if (mask[bi] === 0) {
149
- queue.push(bi);
150
- visited[bi] = 1;
151
- }
130
+ }
131
+ const visited = new Uint8Array(w * h);
132
+ const queue = [];
133
+ for (let x = 0; x < w; x++) {
134
+ if (mask[x] === 0) {
135
+ queue.push(x);
136
+ visited[x] = 1;
152
137
  }
153
- for (let y = 0; y < h; y++) {
154
- const li = y * w;
155
- if (mask[li] === 0) {
156
- queue.push(li);
157
- visited[li] = 1;
158
- }
159
- const ri = y * w + w - 1;
160
- if (mask[ri] === 0) {
161
- queue.push(ri);
162
- visited[ri] = 1;
163
- }
138
+ const bi = (h - 1) * w + x;
139
+ if (mask[bi] === 0) {
140
+ queue.push(bi);
141
+ visited[bi] = 1;
164
142
  }
165
- // BFS flood fill
166
- let head = 0;
167
- while (head < queue.length) {
168
- const idx = queue[head++];
169
- const x = idx % w;
170
- const y = (idx - x) / w;
171
- for (const [dx, dy] of [
172
- [0, -1],
173
- [0, 1],
174
- [-1, 0],
175
- [1, 0],
176
- ]) {
177
- const nx = x + dx;
178
- const ny = y + dy;
179
- if (nx < 0 || nx >= w || ny < 0 || ny >= h) {
180
- continue;
181
- }
182
- const ni = ny * w + nx;
183
- if (visited[ni] || mask[ni] > 0) {
184
- continue;
185
- }
186
- visited[ni] = 1;
187
- queue.push(ni);
188
- }
143
+ }
144
+ for (let y = 0; y < h; y++) {
145
+ const li = y * w;
146
+ if (mask[li] === 0) {
147
+ queue.push(li);
148
+ visited[li] = 1;
189
149
  }
190
- // 5. Apply mask with optional feathering
191
- const replacement = replacementColor ? hexToRGB(replacementColor) : null;
192
- for (let i = 0; i < w * h; i++) {
193
- let alpha = visited[i] ? 0 : 1; // background = 0, foreground = 1
194
- // Feather edges
195
- if (featherRadius > 0 && alpha > 0) {
196
- const x = i % w;
197
- const y = (i - x) / w;
198
- let minDistSq = featherRadius * featherRadius + 1;
199
- for (let fy = -featherRadius; fy <= featherRadius; fy++) {
200
- for (let fx = -featherRadius; fx <= featherRadius; fx++) {
201
- const nx = x + fx;
202
- const ny = y + fy;
203
- if (nx >= 0 && nx < w && ny >= 0 && ny < h) {
204
- if (visited[ny * w + nx]) {
205
- const d = fx * fx + fy * fy;
206
- if (d < minDistSq) {
207
- minDistSq = d;
208
- }
209
- }
210
- }
211
- }
212
- }
213
- if (minDistSq <= featherRadius * featherRadius) {
214
- alpha = Math.sqrt(minDistSq) / featherRadius;
150
+ const ri = y * w + w - 1;
151
+ if (mask[ri] === 0) {
152
+ queue.push(ri);
153
+ visited[ri] = 1;
154
+ }
155
+ }
156
+ let head = 0;
157
+ while (head < queue.length) {
158
+ const idx = queue[head++];
159
+ const x = idx % w;
160
+ const y = (idx - x) / w;
161
+ for (const [dx, dy] of [
162
+ [0, -1],
163
+ [0, 1],
164
+ [-1, 0],
165
+ [1, 0]
166
+ ]) {
167
+ const nx = x + dx;
168
+ const ny = y + dy;
169
+ if (nx < 0 || nx >= w || ny < 0 || ny >= h) {
170
+ continue;
171
+ }
172
+ const ni = ny * w + nx;
173
+ if (visited[ni] || mask[ni] > 0) {
174
+ continue;
175
+ }
176
+ visited[ni] = 1;
177
+ queue.push(ni);
178
+ }
179
+ }
180
+ const replacement = replacementColor ? hexToRGB(replacementColor) : null;
181
+ for (let i = 0; i < w * h; i++) {
182
+ let alpha = visited[i] ? 0 : 1;
183
+ if (featherRadius > 0 && alpha > 0) {
184
+ const x = i % w;
185
+ const y = (i - x) / w;
186
+ let minDistSq = featherRadius * featherRadius + 1;
187
+ for (let fy = -featherRadius; fy <= featherRadius; fy++) {
188
+ for (let fx = -featherRadius; fx <= featherRadius; fx++) {
189
+ const nx = x + fx;
190
+ const ny = y + fy;
191
+ if (nx >= 0 && nx < w && ny >= 0 && ny < h) {
192
+ if (visited[ny * w + nx]) {
193
+ const d = fx * fx + fy * fy;
194
+ if (d < minDistSq) {
195
+ minDistSq = d;
196
+ }
215
197
  }
198
+ }
216
199
  }
217
- const pi = i * 4;
218
- if (replacement) {
219
- data[pi] = Math.round(data[pi] * alpha + replacement[0] * (1 - alpha));
220
- data[pi + 1] = Math.round(data[pi + 1] * alpha + replacement[1] * (1 - alpha));
221
- data[pi + 2] = Math.round(data[pi + 2] * alpha + replacement[2] * (1 - alpha));
222
- }
223
- else {
224
- data[pi + 3] = Math.round(alpha * 255);
225
- }
200
+ }
201
+ if (minDistSq <= featherRadius * featherRadius) {
202
+ alpha = Math.sqrt(minDistSq) / featherRadius;
203
+ }
226
204
  }
227
- return result;
205
+ const pi = i * 4;
206
+ if (replacement) {
207
+ data[pi] = Math.round(data[pi] * alpha + replacement[0] * (1 - alpha));
208
+ data[pi + 1] = Math.round(data[pi + 1] * alpha + replacement[1] * (1 - alpha));
209
+ data[pi + 2] = Math.round(data[pi + 2] * alpha + replacement[2] * (1 - alpha));
210
+ } else {
211
+ data[pi + 3] = Math.round(alpha * 255);
212
+ }
213
+ }
214
+ return result;
228
215
  }
229
216
  function hexToRGB(hex) {
230
- const h = hex.replace('#', '');
231
- return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
217
+ const h = hex.replace("#", "");
218
+ return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
232
219
  }
233
- /**
234
- * Upscale image using Lanczos interpolation.
235
- */
236
220
  function upscaleImage(source, options) {
237
- const { scale, sharpen } = options;
238
- const sw = source.width;
239
- const sh = source.height;
240
- const dw = sw * scale;
241
- const dh = sh * scale;
242
- const result = new ImageData(dw, dh);
243
- const srcData = source.data;
244
- const dstData = result.data;
245
- // Lanczos kernel
246
- const a = 3; // Lanczos-3
247
- function lanczos(x) {
248
- if (x === 0) {
249
- return 1;
250
- }
251
- if (Math.abs(x) >= a) {
252
- return 0;
253
- }
254
- const pix = Math.PI * x;
255
- return (a * Math.sin(pix) * Math.sin(pix / a)) / (pix * pix);
221
+ const { scale, sharpen } = options;
222
+ const sw = source.width;
223
+ const sh = source.height;
224
+ const dw = sw * scale;
225
+ const dh = sh * scale;
226
+ const result = new ImageData(dw, dh);
227
+ const srcData = source.data;
228
+ const dstData = result.data;
229
+ const a = 3;
230
+ function lanczos(x) {
231
+ if (x === 0) {
232
+ return 1;
256
233
  }
257
- // Bicubic Lanczos resampling
258
- for (let dy = 0; dy < dh; dy++) {
259
- const sy = (dy + 0.5) / scale - 0.5;
260
- const sy0 = Math.floor(sy) - a + 1;
261
- const sy1 = Math.floor(sy) + a;
262
- for (let dx = 0; dx < dw; dx++) {
263
- const sx = (dx + 0.5) / scale - 0.5;
264
- const sx0 = Math.floor(sx) - a + 1;
265
- const sx1 = Math.floor(sx) + a;
266
- let r = 0, g = 0, b = 0, aa = 0, wSum = 0;
267
- for (let iy = sy0; iy <= sy1; iy++) {
268
- const wy = lanczos(sy - iy);
269
- const cy = Math.max(0, Math.min(sh - 1, iy));
270
- for (let ix = sx0; ix <= sx1; ix++) {
271
- const wx = lanczos(sx - ix);
272
- const w = wx * wy;
273
- const cx = Math.max(0, Math.min(sw - 1, ix));
274
- const si = (cy * sw + cx) * 4;
275
- r += srcData[si] * w;
276
- g += srcData[si + 1] * w;
277
- b += srcData[si + 2] * w;
278
- aa += srcData[si + 3] * w;
279
- wSum += w;
280
- }
281
- }
282
- const di = (dy * dw + dx) * 4;
283
- if (wSum > 0) {
284
- dstData[di] = Math.max(0, Math.min(255, r / wSum));
285
- dstData[di + 1] = Math.max(0, Math.min(255, g / wSum));
286
- dstData[di + 2] = Math.max(0, Math.min(255, b / wSum));
287
- dstData[di + 3] = Math.max(0, Math.min(255, aa / wSum));
288
- }
289
- }
234
+ if (Math.abs(x) >= a) {
235
+ return 0;
290
236
  }
291
- // Post-sharpen with unsharp mask
292
- if (sharpen > 0) {
293
- const blurred = new Float32Array(dw * dh * 4);
294
- // Quick box blur
295
- for (let y = 1; y < dh - 1; y++) {
296
- for (let x = 1; x < dw - 1; x++) {
297
- const di = (y * dw + x) * 4;
298
- for (let c = 0; c < 3; c++) {
299
- blurred[di + c] =
300
- (dstData[di + c - dw * 4] +
301
- dstData[di + c + dw * 4] +
302
- dstData[di + c - 4] +
303
- dstData[di + c + 4] +
304
- dstData[di + c] * 4) /
305
- 8;
306
- }
307
- }
237
+ const pix = Math.PI * x;
238
+ return a * Math.sin(pix) * Math.sin(pix / a) / (pix * pix);
239
+ }
240
+ for (let dy = 0; dy < dh; dy++) {
241
+ const sy = (dy + 0.5) / scale - 0.5;
242
+ const sy0 = Math.floor(sy) - a + 1;
243
+ const sy1 = Math.floor(sy) + a;
244
+ for (let dx = 0; dx < dw; dx++) {
245
+ const sx = (dx + 0.5) / scale - 0.5;
246
+ const sx0 = Math.floor(sx) - a + 1;
247
+ const sx1 = Math.floor(sx) + a;
248
+ let r = 0, g = 0, b = 0, aa = 0, wSum = 0;
249
+ for (let iy = sy0; iy <= sy1; iy++) {
250
+ const wy = lanczos(sy - iy);
251
+ const cy = Math.max(0, Math.min(sh - 1, iy));
252
+ for (let ix = sx0; ix <= sx1; ix++) {
253
+ const wx = lanczos(sx - ix);
254
+ const w = wx * wy;
255
+ const cx = Math.max(0, Math.min(sw - 1, ix));
256
+ const si = (cy * sw + cx) * 4;
257
+ r += srcData[si] * w;
258
+ g += srcData[si + 1] * w;
259
+ b += srcData[si + 2] * w;
260
+ aa += srcData[si + 3] * w;
261
+ wSum += w;
308
262
  }
309
- // Apply unsharp mask
310
- for (let i = 0; i < dstData.length; i += 4) {
311
- for (let c = 0; c < 3; c++) {
312
- const sharp = dstData[i + c] + (dstData[i + c] - blurred[i + c]) * sharpen * 2;
313
- dstData[i + c] = Math.max(0, Math.min(255, sharp));
314
- }
263
+ }
264
+ const di = (dy * dw + dx) * 4;
265
+ if (wSum > 0) {
266
+ dstData[di] = Math.max(0, Math.min(255, r / wSum));
267
+ dstData[di + 1] = Math.max(0, Math.min(255, g / wSum));
268
+ dstData[di + 2] = Math.max(0, Math.min(255, b / wSum));
269
+ dstData[di + 3] = Math.max(0, Math.min(255, aa / wSum));
270
+ }
271
+ }
272
+ }
273
+ if (sharpen > 0) {
274
+ const blurred = new Float32Array(dw * dh * 4);
275
+ for (let y = 1; y < dh - 1; y++) {
276
+ for (let x = 1; x < dw - 1; x++) {
277
+ const di = (y * dw + x) * 4;
278
+ for (let c = 0; c < 3; c++) {
279
+ blurred[di + c] = (dstData[di + c - dw * 4] + dstData[di + c + dw * 4] + dstData[di + c - 4] + dstData[di + c + 4] + dstData[di + c] * 4) / 8;
315
280
  }
281
+ }
316
282
  }
317
- return result;
283
+ for (let i = 0; i < dstData.length; i += 4) {
284
+ for (let c = 0; c < 3; c++) {
285
+ const sharp = dstData[i + c] + (dstData[i + c] - blurred[i + c]) * sharpen * 2;
286
+ dstData[i + c] = Math.max(0, Math.min(255, sharp));
287
+ }
288
+ }
289
+ }
290
+ return result;
318
291
  }
319
- /**
320
- * Denoise image using bilateral filter approximation.
321
- */
322
292
  function denoiseImage(source, options) {
323
- const { strength, detailPreservation, luminanceWeight } = options;
324
- const w = source.width;
325
- const h = source.height;
326
- const result = new ImageData(new Uint8ClampedArray(source.data), w, h);
327
- const data = result.data;
328
- const radius = Math.max(1, Math.round(strength * 5));
329
- const sigmaSpace = radius * 0.5;
330
- const sigmaColor = (1 - detailPreservation) * 150 + 10;
331
- for (let y = 0; y < h; y++) {
332
- for (let x = 0; x < w; x++) {
333
- const ci = (y * w + x) * 4;
334
- let rSum = 0, gSum = 0, bSum = 0, wSum = 0;
335
- for (let dy = -radius; dy <= radius; dy++) {
336
- for (let dx = -radius; dx <= radius; dx++) {
337
- const nx = x + dx;
338
- const ny = y + dy;
339
- if (nx < 0 || nx >= w || ny < 0 || ny >= h) {
340
- continue;
341
- }
342
- const ni = (ny * w + nx) * 4;
343
- const spatialDist = Math.sqrt(dx * dx + dy * dy);
344
- const spatialW = Math.exp(-(spatialDist * spatialDist) / (2 * sigmaSpace * sigmaSpace));
345
- // Color distance (weighted luminance vs chroma)
346
- const lumDiff = luminance(source.data[ni], source.data[ni + 1], source.data[ni + 2]) -
347
- luminance(source.data[ci], source.data[ci + 1], source.data[ci + 2]);
348
- const chromaDiff = Math.sqrt((source.data[ni] - source.data[ci]) ** 2 +
349
- (source.data[ni + 1] - source.data[ci + 1]) ** 2 +
350
- (source.data[ni + 2] - source.data[ci + 2]) ** 2);
351
- const colorDiff = lumDiff * luminanceWeight + chromaDiff * (1 - luminanceWeight);
352
- const colorW = Math.exp(-(colorDiff * colorDiff) / (2 * sigmaColor * sigmaColor));
353
- const weight = spatialW * colorW;
354
- rSum += source.data[ni] * weight;
355
- gSum += source.data[ni + 1] * weight;
356
- bSum += source.data[ni + 2] * weight;
357
- wSum += weight;
358
- }
359
- }
360
- if (wSum > 0) {
361
- data[ci] = Math.round(rSum / wSum);
362
- data[ci + 1] = Math.round(gSum / wSum);
363
- data[ci + 2] = Math.round(bSum / wSum);
364
- }
293
+ const { strength, detailPreservation, luminanceWeight } = options;
294
+ const w = source.width;
295
+ const h = source.height;
296
+ const result = new ImageData(new Uint8ClampedArray(source.data), w, h);
297
+ const data = result.data;
298
+ const radius = Math.max(1, Math.round(strength * 5));
299
+ const sigmaSpace = radius * 0.5;
300
+ const sigmaColor = (1 - detailPreservation) * 150 + 10;
301
+ for (let y = 0; y < h; y++) {
302
+ for (let x = 0; x < w; x++) {
303
+ const ci = (y * w + x) * 4;
304
+ let rSum = 0, gSum = 0, bSum = 0, wSum = 0;
305
+ for (let dy = -radius; dy <= radius; dy++) {
306
+ for (let dx = -radius; dx <= radius; dx++) {
307
+ const nx = x + dx;
308
+ const ny = y + dy;
309
+ if (nx < 0 || nx >= w || ny < 0 || ny >= h) {
310
+ continue;
311
+ }
312
+ const ni = (ny * w + nx) * 4;
313
+ const spatialDist = Math.sqrt(dx * dx + dy * dy);
314
+ const spatialW = Math.exp(-(spatialDist * spatialDist) / (2 * sigmaSpace * sigmaSpace));
315
+ const lumDiff = luminance(source.data[ni], source.data[ni + 1], source.data[ni + 2]) - luminance(source.data[ci], source.data[ci + 1], source.data[ci + 2]);
316
+ const chromaDiff = Math.sqrt(
317
+ (source.data[ni] - source.data[ci]) ** 2 + (source.data[ni + 1] - source.data[ci + 1]) ** 2 + (source.data[ni + 2] - source.data[ci + 2]) ** 2
318
+ );
319
+ const colorDiff = lumDiff * luminanceWeight + chromaDiff * (1 - luminanceWeight);
320
+ const colorW = Math.exp(-(colorDiff * colorDiff) / (2 * sigmaColor * sigmaColor));
321
+ const weight = spatialW * colorW;
322
+ rSum += source.data[ni] * weight;
323
+ gSum += source.data[ni + 1] * weight;
324
+ bSum += source.data[ni + 2] * weight;
325
+ wSum += weight;
365
326
  }
327
+ }
328
+ if (wSum > 0) {
329
+ data[ci] = Math.round(rSum / wSum);
330
+ data[ci + 1] = Math.round(gSum / wSum);
331
+ data[ci + 2] = Math.round(bSum / wSum);
332
+ }
366
333
  }
367
- return result;
334
+ }
335
+ return result;
368
336
  }
369
- /* ── Hook ── */
370
337
  function useAITools(sourceImage) {
371
- const [isProcessing, setIsProcessing] = React.useState(false);
372
- const [activeTool, setActiveTool] = React.useState('colorize');
373
- const [lastResult, setLastResult] = React.useState(null);
374
- const [colorizeOptions, setColorizeOptions] = React.useState({
375
- intensity: 0.7,
376
- temperature: 'neutral',
377
- saturation: 1.2,
378
- });
379
- const [bgRemoveOptions, setBgRemoveOptions] = React.useState({
380
- edgeSoftness: 2,
381
- threshold: 0.35,
382
- iterations: 8,
383
- featherRadius: 3,
384
- replacementColor: null,
385
- });
386
- const [upscaleOptions, setUpscaleOptions] = React.useState({
387
- scale: 2,
388
- method: 'lanczos',
389
- sharpen: 0.3,
390
- });
391
- const [denoiseOptions, setDenoiseOptions] = React.useState({
392
- strength: 0.5,
393
- detailPreservation: 0.6,
394
- luminanceWeight: 0.7,
395
- });
396
- const process = React.useCallback(async () => {
397
- if (!sourceImage) {
398
- return null;
399
- }
400
- setIsProcessing(true);
401
- const start = performance.now();
402
- let imageData;
403
- try {
404
- switch (activeTool) {
405
- case 'colorize':
406
- imageData = autoColorize(sourceImage, colorizeOptions);
407
- break;
408
- case 'background-remove':
409
- imageData = removeBackground(sourceImage, bgRemoveOptions);
410
- break;
411
- case 'upscale':
412
- imageData = upscaleImage(sourceImage, upscaleOptions);
413
- break;
414
- case 'denoise':
415
- imageData = denoiseImage(sourceImage, denoiseOptions);
416
- break;
417
- default:
418
- return null;
419
- }
420
- const result = {
421
- imageData,
422
- processingTimeMs: performance.now() - start,
423
- tool: activeTool,
424
- };
425
- setLastResult(result);
426
- return result;
427
- }
428
- finally {
429
- setIsProcessing(false);
430
- }
431
- }, [sourceImage, activeTool, colorizeOptions, bgRemoveOptions, upscaleOptions, denoiseOptions]);
432
- return {
433
- activeTool,
434
- setActiveTool,
435
- colorizeOptions,
436
- setColorizeOptions,
437
- bgRemoveOptions,
438
- setBgRemoveOptions,
439
- upscaleOptions,
440
- setUpscaleOptions,
441
- denoiseOptions,
442
- setDenoiseOptions,
443
- process,
444
- isProcessing,
445
- lastResult,
446
- };
338
+ const [isProcessing, setIsProcessing] = React.useState(false);
339
+ const [activeTool, setActiveTool] = React.useState("colorize");
340
+ const [lastResult, setLastResult] = React.useState(null);
341
+ const [colorizeOptions, setColorizeOptions] = React.useState({
342
+ intensity: 0.7,
343
+ temperature: "neutral",
344
+ saturation: 1.2
345
+ });
346
+ const [bgRemoveOptions, setBgRemoveOptions] = React.useState({
347
+ edgeSoftness: 2,
348
+ threshold: 0.35,
349
+ iterations: 8,
350
+ featherRadius: 3,
351
+ replacementColor: null
352
+ });
353
+ const [upscaleOptions, setUpscaleOptions] = React.useState({
354
+ scale: 2,
355
+ method: "lanczos",
356
+ sharpen: 0.3
357
+ });
358
+ const [denoiseOptions, setDenoiseOptions] = React.useState({
359
+ strength: 0.5,
360
+ detailPreservation: 0.6,
361
+ luminanceWeight: 0.7
362
+ });
363
+ const process = React.useCallback(async () => {
364
+ if (!sourceImage) {
365
+ return null;
366
+ }
367
+ setIsProcessing(true);
368
+ const start = performance.now();
369
+ let imageData;
370
+ try {
371
+ switch (activeTool) {
372
+ case "colorize":
373
+ imageData = autoColorize(sourceImage, colorizeOptions);
374
+ break;
375
+ case "background-remove":
376
+ imageData = removeBackground(sourceImage, bgRemoveOptions);
377
+ break;
378
+ case "upscale":
379
+ imageData = upscaleImage(sourceImage, upscaleOptions);
380
+ break;
381
+ case "denoise":
382
+ imageData = denoiseImage(sourceImage, denoiseOptions);
383
+ break;
384
+ default:
385
+ return null;
386
+ }
387
+ const result = {
388
+ imageData,
389
+ processingTimeMs: performance.now() - start,
390
+ tool: activeTool
391
+ };
392
+ setLastResult(result);
393
+ return result;
394
+ } finally {
395
+ setIsProcessing(false);
396
+ }
397
+ }, [sourceImage, activeTool, colorizeOptions, bgRemoveOptions, upscaleOptions, denoiseOptions]);
398
+ return {
399
+ activeTool,
400
+ setActiveTool,
401
+ colorizeOptions,
402
+ setColorizeOptions,
403
+ bgRemoveOptions,
404
+ setBgRemoveOptions,
405
+ upscaleOptions,
406
+ setUpscaleOptions,
407
+ denoiseOptions,
408
+ setDenoiseOptions,
409
+ process,
410
+ isProcessing,
411
+ lastResult
412
+ };
447
413
  }
448
- /* ── Component ── */
449
414
  const TOOL_LABELS = {
450
- colorize: 'Auto-Colorize',
451
- 'background-remove': 'Remove Background',
452
- upscale: 'Upscale (ESRGAN)',
453
- denoise: 'Denoise',
415
+ colorize: "Auto-Colorize",
416
+ "background-remove": "Remove Background",
417
+ upscale: "Upscale (ESRGAN)",
418
+ denoise: "Denoise"
454
419
  };
455
- /** NiceIcon builtin name for each tool. */
456
420
  const TOOL_ICONS = {
457
- colorize: 'palette',
458
- 'background-remove': 'scissors',
459
- upscale: 'search',
460
- denoise: 'wand',
421
+ colorize: "palette",
422
+ "background-remove": "scissors",
423
+ upscale: "search",
424
+ denoise: "wand"
461
425
  };
462
- const NiceAITools = ({ sourceImage, onResult, className = '', }) => {
463
- var _a;
464
- const { t } = i18n.useNiceTranslation();
465
- const ai = useAITools(sourceImage);
466
- const handleProcess = async () => {
467
- const result = await ai.process();
468
- if (result && onResult) {
469
- onResult(result);
470
- }
471
- };
472
- return (jsxRuntime.jsxs("div", { className: `nice-ai-tools ${className}`, style: { display: 'grid', gap: 8, fontSize: 13 }, children: [jsxRuntime.jsxs("strong", { style: { display: 'inline-flex', alignItems: 'center', gap: 4 }, children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "wand", size: 16 }), " ", t('aiTools.aiTools', 'AI Tools')] }), jsxRuntime.jsx("div", { style: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: Object.entries(TOOL_LABELS).map(([key, label]) => (jsxRuntime.jsxs("button", { type: "button", onClick: () => ai.setActiveTool(key), style: {
473
- flex: 1,
474
- padding: '4px 6px',
475
- fontSize: 11,
476
- cursor: 'pointer',
477
- borderRadius: 4,
478
- border: '1px solid var(--nice-border, #e2e8f0)',
479
- background: ai.activeTool === key ? 'var(--nice-color-accent, #6366f1)' : 'transparent',
480
- color: ai.activeTool === key ? 'var(--nice-color-white, #fff)' : 'inherit',
481
- minWidth: 100,
482
- display: 'inline-flex',
483
- alignItems: 'center',
484
- justifyContent: 'center',
485
- gap: 4,
486
- }, children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: TOOL_ICONS[key], size: 14 }), " ", label] }, key))) }), ai.activeTool === 'colorize' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('aiTools.intensity', 'Intensity: {intensity}').replace('{intensity}', String(ai.colorizeOptions.intensity.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.05, value: ai.colorizeOptions.intensity, onChange: (e) => ai.setColorizeOptions({ ...ai.colorizeOptions, intensity: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('aiTools.saturation', 'Saturation: {saturation}').replace('{saturation}', String(ai.colorizeOptions.saturation.toFixed(1))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 2, step: 0.1, value: ai.colorizeOptions.saturation, onChange: (e) => ai.setColorizeOptions({ ...ai.colorizeOptions, saturation: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('aiTools.temperature', 'Temperature:'), jsxRuntime.jsxs("select", { value: ai.colorizeOptions.temperature, onChange: (e) => ai.setColorizeOptions({
487
- ...ai.colorizeOptions,
488
- temperature: e.target.value,
489
- }), style: { width: '100%' }, children: [jsxRuntime.jsx("option", { value: "warm", children: t('aiTools.warm', 'Warm') }), jsxRuntime.jsx("option", { value: "neutral", children: t('aiTools.neutral', 'Neutral') }), jsxRuntime.jsx("option", { value: "cool", children: t('aiTools.cool', 'Cool') })] })] })] })), ai.activeTool === 'background-remove' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('aiTools.threshold', 'Threshold: {threshold}').replace('{threshold}', String(ai.bgRemoveOptions.threshold.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0.1, max: 0.9, step: 0.05, value: ai.bgRemoveOptions.threshold, onChange: (e) => ai.setBgRemoveOptions({ ...ai.bgRemoveOptions, threshold: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('aiTools.iterations', 'Iterations: {iterations}').replace('{iterations}', String(ai.bgRemoveOptions.iterations)), jsxRuntime.jsx("input", { type: "range", min: 1, max: 20, step: 1, value: ai.bgRemoveOptions.iterations, onChange: (e) => ai.setBgRemoveOptions({ ...ai.bgRemoveOptions, iterations: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('aiTools.featherPx', 'Feather: {featherRadius}px').replace('{featherRadius}', String(ai.bgRemoveOptions.featherRadius)), jsxRuntime.jsx("input", { type: "range", min: 0, max: 10, step: 1, value: ai.bgRemoveOptions.featherRadius, onChange: (e) => ai.setBgRemoveOptions({ ...ai.bgRemoveOptions, featherRadius: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { style: { display: 'flex', gap: 4, alignItems: 'center' }, children: [t('aiTools.replaceBg', 'Replace BG:'), jsxRuntime.jsx("input", { type: "color", value: (_a = ai.bgRemoveOptions.replacementColor) !== null && _a !== void 0 ? _a : '#ffffff', onChange: (e) => ai.setBgRemoveOptions({ ...ai.bgRemoveOptions, replacementColor: e.target.value }) }), jsxRuntime.jsx("button", { type: "button", onClick: () => ai.setBgRemoveOptions({ ...ai.bgRemoveOptions, replacementColor: null }), style: { fontSize: 10 }, children: t('aiTools.transparent', 'Transparent') })] })] })), ai.activeTool === 'upscale' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('aiTools.scale', 'Scale:'), jsxRuntime.jsxs("select", { value: ai.upscaleOptions.scale, onChange: (e) => ai.setUpscaleOptions({ ...ai.upscaleOptions, scale: +e.target.value }), style: { width: '100%' }, children: [jsxRuntime.jsx("option", { value: 2, children: "2x" }), jsxRuntime.jsx("option", { value: 3, children: "3x" }), jsxRuntime.jsx("option", { value: 4, children: "4x" })] })] }), jsxRuntime.jsxs("label", { children: [t('aiTools.sharpen', 'Sharpen: {sharpen}').replace('{sharpen}', String(ai.upscaleOptions.sharpen.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.05, value: ai.upscaleOptions.sharpen, onChange: (e) => ai.setUpscaleOptions({ ...ai.upscaleOptions, sharpen: +e.target.value }), style: { width: '100%' } })] })] })), ai.activeTool === 'denoise' && (jsxRuntime.jsxs("div", { style: { display: 'grid', gap: 4 }, children: [jsxRuntime.jsxs("label", { children: [t('aiTools.strength', 'Strength: {strength}').replace('{strength}', String(ai.denoiseOptions.strength.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.05, value: ai.denoiseOptions.strength, onChange: (e) => ai.setDenoiseOptions({ ...ai.denoiseOptions, strength: +e.target.value }), style: { width: '100%' } })] }), jsxRuntime.jsxs("label", { children: [t('aiTools.detail', 'Detail: {detailPreservation}').replace('{detailPreservation}', String(ai.denoiseOptions.detailPreservation.toFixed(2))), jsxRuntime.jsx("input", { type: "range", min: 0, max: 1, step: 0.05, value: ai.denoiseOptions.detailPreservation, onChange: (e) => ai.setDenoiseOptions({ ...ai.denoiseOptions, detailPreservation: +e.target.value }), style: { width: '100%' } })] })] })), jsxRuntime.jsx("button", { type: "button", onClick: handleProcess, disabled: ai.isProcessing || !sourceImage, style: {
490
- background: ai.isProcessing
491
- ? 'var(--nice-bg-tertiary, #94a3b8)'
492
- : 'var(--nice-color-accent, #6366f1)',
493
- color: 'var(--nice-color-white, #fff)',
494
- border: 'none',
495
- borderRadius: 4,
496
- padding: '8px 12px',
497
- cursor: ai.isProcessing ? 'wait' : 'pointer',
498
- display: 'inline-flex',
499
- alignItems: 'center',
500
- justifyContent: 'center',
501
- gap: 4,
502
- }, children: ai.isProcessing ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "refresh", size: 14 }), " ", t('aiTools.processing', 'Processing…')] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(uiCore.NiceIcon, { name: "play", size: 14 }), " ", TOOL_LABELS[ai.activeTool]] })) }), ai.lastResult && (jsxRuntime.jsxs("div", { style: { fontSize: 11, color: 'var(--nice-text-muted, #64748b)' }, children: [t('aiTools.processedInMs', 'Processed in {ms}ms').replace('{ms}', ai.lastResult.processingTimeMs.toFixed(0)), ai.activeTool === 'upscale' &&
503
- ` → ${ai.lastResult.imageData.width}×${ai.lastResult.imageData.height}`] }))] }));
426
+ const NiceAITools = ({
427
+ sourceImage,
428
+ onResult,
429
+ className = ""
430
+ }) => {
431
+ const { t } = i18n.useNiceTranslation();
432
+ const ai = useAITools(sourceImage);
433
+ const handleProcess = async () => {
434
+ const result = await ai.process();
435
+ if (result && onResult) {
436
+ onResult(result);
437
+ }
438
+ };
439
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `nice-ai-tools ${className}`, style: { display: "grid", gap: 8, fontSize: 13 }, children: [
440
+ /* @__PURE__ */ jsxRuntime.jsxs("strong", { style: { display: "inline-flex", alignItems: "center", gap: 4 }, children: [
441
+ /* @__PURE__ */ jsxRuntime.jsx(uiCore.NiceIcon, { name: "wand", size: 16 }),
442
+ " ",
443
+ t("aiTools.aiTools", "AI Tools")
444
+ ] }),
445
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 2, flexWrap: "wrap" }, children: Object.entries(TOOL_LABELS).map(([key, label]) => /* @__PURE__ */ jsxRuntime.jsxs(
446
+ "button",
447
+ {
448
+ type: "button",
449
+ onClick: () => ai.setActiveTool(key),
450
+ style: {
451
+ flex: 1,
452
+ padding: "4px 6px",
453
+ fontSize: 11,
454
+ cursor: "pointer",
455
+ borderRadius: 4,
456
+ border: "1px solid var(--nice-border, #e2e8f0)",
457
+ background: ai.activeTool === key ? "var(--nice-color-accent, #6366f1)" : "transparent",
458
+ color: ai.activeTool === key ? "var(--nice-color-white, #fff)" : "inherit",
459
+ minWidth: 100,
460
+ display: "inline-flex",
461
+ alignItems: "center",
462
+ justifyContent: "center",
463
+ gap: 4
464
+ },
465
+ children: [
466
+ /* @__PURE__ */ jsxRuntime.jsx(uiCore.NiceIcon, { name: TOOL_ICONS[key], size: 14 }),
467
+ " ",
468
+ label
469
+ ]
470
+ },
471
+ key
472
+ )) }),
473
+ ai.activeTool === "colorize" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "grid", gap: 4 }, children: [
474
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
475
+ t("aiTools.intensity", "Intensity: {intensity}").replace(
476
+ "{intensity}",
477
+ String(ai.colorizeOptions.intensity.toFixed(2))
478
+ ),
479
+ /* @__PURE__ */ jsxRuntime.jsx(
480
+ "input",
481
+ {
482
+ type: "range",
483
+ min: 0,
484
+ max: 1,
485
+ step: 0.05,
486
+ value: ai.colorizeOptions.intensity,
487
+ onChange: (e) => ai.setColorizeOptions({ ...ai.colorizeOptions, intensity: +e.target.value }),
488
+ style: { width: "100%" }
489
+ }
490
+ )
491
+ ] }),
492
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
493
+ t("aiTools.saturation", "Saturation: {saturation}").replace(
494
+ "{saturation}",
495
+ String(ai.colorizeOptions.saturation.toFixed(1))
496
+ ),
497
+ /* @__PURE__ */ jsxRuntime.jsx(
498
+ "input",
499
+ {
500
+ type: "range",
501
+ min: 0,
502
+ max: 2,
503
+ step: 0.1,
504
+ value: ai.colorizeOptions.saturation,
505
+ onChange: (e) => ai.setColorizeOptions({ ...ai.colorizeOptions, saturation: +e.target.value }),
506
+ style: { width: "100%" }
507
+ }
508
+ )
509
+ ] }),
510
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
511
+ t("aiTools.temperature", "Temperature:"),
512
+ /* @__PURE__ */ jsxRuntime.jsxs(
513
+ "select",
514
+ {
515
+ value: ai.colorizeOptions.temperature,
516
+ onChange: (e) => ai.setColorizeOptions({
517
+ ...ai.colorizeOptions,
518
+ temperature: e.target.value
519
+ }),
520
+ style: { width: "100%" },
521
+ children: [
522
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "warm", children: t("aiTools.warm", "Warm") }),
523
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "neutral", children: t("aiTools.neutral", "Neutral") }),
524
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "cool", children: t("aiTools.cool", "Cool") })
525
+ ]
526
+ }
527
+ )
528
+ ] })
529
+ ] }),
530
+ ai.activeTool === "background-remove" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "grid", gap: 4 }, children: [
531
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
532
+ t("aiTools.threshold", "Threshold: {threshold}").replace(
533
+ "{threshold}",
534
+ String(ai.bgRemoveOptions.threshold.toFixed(2))
535
+ ),
536
+ /* @__PURE__ */ jsxRuntime.jsx(
537
+ "input",
538
+ {
539
+ type: "range",
540
+ min: 0.1,
541
+ max: 0.9,
542
+ step: 0.05,
543
+ value: ai.bgRemoveOptions.threshold,
544
+ onChange: (e) => ai.setBgRemoveOptions({ ...ai.bgRemoveOptions, threshold: +e.target.value }),
545
+ style: { width: "100%" }
546
+ }
547
+ )
548
+ ] }),
549
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
550
+ t("aiTools.iterations", "Iterations: {iterations}").replace(
551
+ "{iterations}",
552
+ String(ai.bgRemoveOptions.iterations)
553
+ ),
554
+ /* @__PURE__ */ jsxRuntime.jsx(
555
+ "input",
556
+ {
557
+ type: "range",
558
+ min: 1,
559
+ max: 20,
560
+ step: 1,
561
+ value: ai.bgRemoveOptions.iterations,
562
+ onChange: (e) => ai.setBgRemoveOptions({ ...ai.bgRemoveOptions, iterations: +e.target.value }),
563
+ style: { width: "100%" }
564
+ }
565
+ )
566
+ ] }),
567
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
568
+ t("aiTools.featherPx", "Feather: {featherRadius}px").replace(
569
+ "{featherRadius}",
570
+ String(ai.bgRemoveOptions.featherRadius)
571
+ ),
572
+ /* @__PURE__ */ jsxRuntime.jsx(
573
+ "input",
574
+ {
575
+ type: "range",
576
+ min: 0,
577
+ max: 10,
578
+ step: 1,
579
+ value: ai.bgRemoveOptions.featherRadius,
580
+ onChange: (e) => ai.setBgRemoveOptions({ ...ai.bgRemoveOptions, featherRadius: +e.target.value }),
581
+ style: { width: "100%" }
582
+ }
583
+ )
584
+ ] }),
585
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [
586
+ t("aiTools.replaceBg", "Replace BG:"),
587
+ /* @__PURE__ */ jsxRuntime.jsx(
588
+ "input",
589
+ {
590
+ type: "color",
591
+ value: ai.bgRemoveOptions.replacementColor ?? "#ffffff",
592
+ onChange: (e) => ai.setBgRemoveOptions({ ...ai.bgRemoveOptions, replacementColor: e.target.value })
593
+ }
594
+ ),
595
+ /* @__PURE__ */ jsxRuntime.jsx(
596
+ "button",
597
+ {
598
+ type: "button",
599
+ onClick: () => ai.setBgRemoveOptions({ ...ai.bgRemoveOptions, replacementColor: null }),
600
+ style: { fontSize: 10 },
601
+ children: t("aiTools.transparent", "Transparent")
602
+ }
603
+ )
604
+ ] })
605
+ ] }),
606
+ ai.activeTool === "upscale" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "grid", gap: 4 }, children: [
607
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
608
+ t("aiTools.scale", "Scale:"),
609
+ /* @__PURE__ */ jsxRuntime.jsxs(
610
+ "select",
611
+ {
612
+ value: ai.upscaleOptions.scale,
613
+ onChange: (e) => ai.setUpscaleOptions({ ...ai.upscaleOptions, scale: +e.target.value }),
614
+ style: { width: "100%" },
615
+ children: [
616
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 2, children: "2x" }),
617
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 3, children: "3x" }),
618
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: 4, children: "4x" })
619
+ ]
620
+ }
621
+ )
622
+ ] }),
623
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
624
+ t("aiTools.sharpen", "Sharpen: {sharpen}").replace(
625
+ "{sharpen}",
626
+ String(ai.upscaleOptions.sharpen.toFixed(2))
627
+ ),
628
+ /* @__PURE__ */ jsxRuntime.jsx(
629
+ "input",
630
+ {
631
+ type: "range",
632
+ min: 0,
633
+ max: 1,
634
+ step: 0.05,
635
+ value: ai.upscaleOptions.sharpen,
636
+ onChange: (e) => ai.setUpscaleOptions({ ...ai.upscaleOptions, sharpen: +e.target.value }),
637
+ style: { width: "100%" }
638
+ }
639
+ )
640
+ ] })
641
+ ] }),
642
+ ai.activeTool === "denoise" && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "grid", gap: 4 }, children: [
643
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
644
+ t("aiTools.strength", "Strength: {strength}").replace(
645
+ "{strength}",
646
+ String(ai.denoiseOptions.strength.toFixed(2))
647
+ ),
648
+ /* @__PURE__ */ jsxRuntime.jsx(
649
+ "input",
650
+ {
651
+ type: "range",
652
+ min: 0,
653
+ max: 1,
654
+ step: 0.05,
655
+ value: ai.denoiseOptions.strength,
656
+ onChange: (e) => ai.setDenoiseOptions({ ...ai.denoiseOptions, strength: +e.target.value }),
657
+ style: { width: "100%" }
658
+ }
659
+ )
660
+ ] }),
661
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
662
+ t("aiTools.detail", "Detail: {detailPreservation}").replace(
663
+ "{detailPreservation}",
664
+ String(ai.denoiseOptions.detailPreservation.toFixed(2))
665
+ ),
666
+ /* @__PURE__ */ jsxRuntime.jsx(
667
+ "input",
668
+ {
669
+ type: "range",
670
+ min: 0,
671
+ max: 1,
672
+ step: 0.05,
673
+ value: ai.denoiseOptions.detailPreservation,
674
+ onChange: (e) => ai.setDenoiseOptions({ ...ai.denoiseOptions, detailPreservation: +e.target.value }),
675
+ style: { width: "100%" }
676
+ }
677
+ )
678
+ ] })
679
+ ] }),
680
+ /* @__PURE__ */ jsxRuntime.jsx(
681
+ "button",
682
+ {
683
+ type: "button",
684
+ onClick: handleProcess,
685
+ disabled: ai.isProcessing || !sourceImage,
686
+ style: {
687
+ background: ai.isProcessing ? "var(--nice-bg-tertiary, #94a3b8)" : "var(--nice-color-accent, #6366f1)",
688
+ color: "var(--nice-color-white, #fff)",
689
+ border: "none",
690
+ borderRadius: 4,
691
+ padding: "8px 12px",
692
+ cursor: ai.isProcessing ? "wait" : "pointer",
693
+ display: "inline-flex",
694
+ alignItems: "center",
695
+ justifyContent: "center",
696
+ gap: 4
697
+ },
698
+ children: ai.isProcessing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
699
+ /* @__PURE__ */ jsxRuntime.jsx(uiCore.NiceIcon, { name: "refresh", size: 14 }),
700
+ " ",
701
+ t("aiTools.processing", "Processing…")
702
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
703
+ /* @__PURE__ */ jsxRuntime.jsx(uiCore.NiceIcon, { name: "play", size: 14 }),
704
+ " ",
705
+ TOOL_LABELS[ai.activeTool]
706
+ ] })
707
+ }
708
+ ),
709
+ ai.lastResult && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: 11, color: "var(--nice-text-muted, #64748b)" }, children: [
710
+ t("aiTools.processedInMs", "Processed in {ms}ms").replace(
711
+ "{ms}",
712
+ ai.lastResult.processingTimeMs.toFixed(0)
713
+ ),
714
+ ai.activeTool === "upscale" && ` → ${ai.lastResult.imageData.width}×${ai.lastResult.imageData.height}`
715
+ ] })
716
+ ] });
504
717
  };
505
-
506
718
  exports.NiceAITools = NiceAITools;
507
719
  exports.autoColorize = autoColorize;
508
720
  exports.denoiseImage = denoiseImage;