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