@nice2dev/ui-graphics 1.3.14 → 1.3.16

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,1112 +1,1095 @@
1
- /**
2
- * photoFilters.ts — Canvas-based photo filter engine.
3
- *
4
- * Pure functions that operate on ImageData pixel arrays.
5
- * Each filter takes ImageData + optional params and returns a new ImageData.
6
- * Composable via `applyFilterChain`.
7
- */
8
1
  const DEFAULT_ADJUSTMENTS = {
9
- brightness: 0,
10
- contrast: 0,
11
- saturation: 0,
12
- temperature: 0,
13
- tint: 0,
14
- exposure: 0,
15
- highlights: 0,
16
- shadows: 0,
17
- sharpness: 0,
18
- vignette: 0,
19
- grain: 0,
20
- blur: 0,
2
+ brightness: 0,
3
+ contrast: 0,
4
+ saturation: 0,
5
+ temperature: 0,
6
+ tint: 0,
7
+ exposure: 0,
8
+ highlights: 0,
9
+ shadows: 0,
10
+ sharpness: 0,
11
+ vignette: 0,
12
+ grain: 0,
13
+ blur: 0
21
14
  };
22
- // ── Pixel helpers ──
23
15
  function clamp(v) {
24
- return v < 0 ? 0 : v > 255 ? 255 : v | 0;
16
+ return v < 0 ? 0 : v > 255 ? 255 : v | 0;
25
17
  }
26
18
  function lerpColor(a, b, t) {
27
- return clamp(a + (b - a) * t);
19
+ return clamp(a + (b - a) * t);
28
20
  }
29
- /** Create a working copy of ImageData */
30
21
  function cloneImageData(src) {
31
- return new ImageData(new Uint8ClampedArray(src.data), src.width, src.height);
22
+ return new ImageData(new Uint8ClampedArray(src.data), src.width, src.height);
32
23
  }
33
- /** Mix original and filtered based on intensity */
34
24
  function blendIntensity(original, filtered, intensity) {
35
- if (intensity >= 1) {
36
- return filtered;
37
- }
38
- if (intensity <= 0) {
39
- return cloneImageData(original);
40
- }
41
- const out = cloneImageData(original);
42
- const od = out.data;
43
- const fd = filtered.data;
44
- for (let i = 0; i < od.length; i += 4) {
45
- od[i] = lerpColor(od[i], fd[i], intensity);
46
- od[i + 1] = lerpColor(od[i + 1], fd[i + 1], intensity);
47
- od[i + 2] = lerpColor(od[i + 2], fd[i + 2], intensity);
48
- }
49
- return out;
25
+ if (intensity >= 1) {
26
+ return filtered;
27
+ }
28
+ if (intensity <= 0) {
29
+ return cloneImageData(original);
30
+ }
31
+ const out = cloneImageData(original);
32
+ const od = out.data;
33
+ const fd = filtered.data;
34
+ for (let i = 0; i < od.length; i += 4) {
35
+ od[i] = lerpColor(od[i], fd[i], intensity);
36
+ od[i + 1] = lerpColor(od[i + 1], fd[i + 1], intensity);
37
+ od[i + 2] = lerpColor(od[i + 2], fd[i + 2], intensity);
38
+ }
39
+ return out;
50
40
  }
51
- // ── Core filter functions ──
52
41
  function grayscale(data) {
53
- const out = cloneImageData(data);
54
- const d = out.data;
55
- for (let i = 0; i < d.length; i += 4) {
56
- const gray = 0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2];
57
- d[i] = d[i + 1] = d[i + 2] = gray;
58
- }
59
- return out;
42
+ const out = cloneImageData(data);
43
+ const d = out.data;
44
+ for (let i = 0; i < d.length; i += 4) {
45
+ const gray = 0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2];
46
+ d[i] = d[i + 1] = d[i + 2] = gray;
47
+ }
48
+ return out;
60
49
  }
61
50
  function sepia(data) {
62
- const out = cloneImageData(data);
63
- const d = out.data;
64
- for (let i = 0; i < d.length; i += 4) {
65
- const r = d[i], g = d[i + 1], b = d[i + 2];
66
- d[i] = clamp(r * 0.393 + g * 0.769 + b * 0.189);
67
- d[i + 1] = clamp(r * 0.349 + g * 0.686 + b * 0.168);
68
- d[i + 2] = clamp(r * 0.272 + g * 0.534 + b * 0.131);
69
- }
70
- return out;
51
+ const out = cloneImageData(data);
52
+ const d = out.data;
53
+ for (let i = 0; i < d.length; i += 4) {
54
+ const r = d[i], g = d[i + 1], b = d[i + 2];
55
+ d[i] = clamp(r * 0.393 + g * 0.769 + b * 0.189);
56
+ d[i + 1] = clamp(r * 0.349 + g * 0.686 + b * 0.168);
57
+ d[i + 2] = clamp(r * 0.272 + g * 0.534 + b * 0.131);
58
+ }
59
+ return out;
71
60
  }
72
61
  function invert(data) {
73
- const out = cloneImageData(data);
74
- const d = out.data;
75
- for (let i = 0; i < d.length; i += 4) {
76
- d[i] = 255 - d[i];
77
- d[i + 1] = 255 - d[i + 1];
78
- d[i + 2] = 255 - d[i + 2];
79
- }
80
- return out;
62
+ const out = cloneImageData(data);
63
+ const d = out.data;
64
+ for (let i = 0; i < d.length; i += 4) {
65
+ d[i] = 255 - d[i];
66
+ d[i + 1] = 255 - d[i + 1];
67
+ d[i + 2] = 255 - d[i + 2];
68
+ }
69
+ return out;
81
70
  }
82
71
  function adjustBrightness(data, amount) {
83
- const out = cloneImageData(data);
84
- const d = out.data;
85
- const adj = (amount / 100) * 128;
86
- for (let i = 0; i < d.length; i += 4) {
87
- d[i] = clamp(d[i] + adj);
88
- d[i + 1] = clamp(d[i + 1] + adj);
89
- d[i + 2] = clamp(d[i + 2] + adj);
90
- }
91
- return out;
72
+ const out = cloneImageData(data);
73
+ const d = out.data;
74
+ const adj = amount / 100 * 128;
75
+ for (let i = 0; i < d.length; i += 4) {
76
+ d[i] = clamp(d[i] + adj);
77
+ d[i + 1] = clamp(d[i + 1] + adj);
78
+ d[i + 2] = clamp(d[i + 2] + adj);
79
+ }
80
+ return out;
92
81
  }
93
82
  function adjustContrast(data, amount) {
94
- const out = cloneImageData(data);
95
- const d = out.data;
96
- const factor = (259 * (amount + 255)) / (255 * (259 - amount));
97
- for (let i = 0; i < d.length; i += 4) {
98
- d[i] = clamp(factor * (d[i] - 128) + 128);
99
- d[i + 1] = clamp(factor * (d[i + 1] - 128) + 128);
100
- d[i + 2] = clamp(factor * (d[i + 2] - 128) + 128);
101
- }
102
- return out;
83
+ const out = cloneImageData(data);
84
+ const d = out.data;
85
+ const factor = 259 * (amount + 255) / (255 * (259 - amount));
86
+ for (let i = 0; i < d.length; i += 4) {
87
+ d[i] = clamp(factor * (d[i] - 128) + 128);
88
+ d[i + 1] = clamp(factor * (d[i + 1] - 128) + 128);
89
+ d[i + 2] = clamp(factor * (d[i + 2] - 128) + 128);
90
+ }
91
+ return out;
103
92
  }
104
93
  function adjustSaturation(data, amount) {
105
- const out = cloneImageData(data);
106
- const d = out.data;
107
- const sat = 1 + amount / 100;
108
- for (let i = 0; i < d.length; i += 4) {
109
- const gray = 0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2];
110
- d[i] = clamp(gray + sat * (d[i] - gray));
111
- d[i + 1] = clamp(gray + sat * (d[i + 1] - gray));
112
- d[i + 2] = clamp(gray + sat * (d[i + 2] - gray));
113
- }
114
- return out;
94
+ const out = cloneImageData(data);
95
+ const d = out.data;
96
+ const sat = 1 + amount / 100;
97
+ for (let i = 0; i < d.length; i += 4) {
98
+ const gray = 0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2];
99
+ d[i] = clamp(gray + sat * (d[i] - gray));
100
+ d[i + 1] = clamp(gray + sat * (d[i + 1] - gray));
101
+ d[i + 2] = clamp(gray + sat * (d[i + 2] - gray));
102
+ }
103
+ return out;
115
104
  }
116
105
  function adjustTemperature(data, amount) {
117
- const out = cloneImageData(data);
118
- const d = out.data;
119
- const adj = amount * 0.8;
120
- for (let i = 0; i < d.length; i += 4) {
121
- d[i] = clamp(d[i] + adj); // red
122
- d[i + 2] = clamp(d[i + 2] - adj); // blue (inverse)
123
- }
124
- return out;
106
+ const out = cloneImageData(data);
107
+ const d = out.data;
108
+ const adj = amount * 0.8;
109
+ for (let i = 0; i < d.length; i += 4) {
110
+ d[i] = clamp(d[i] + adj);
111
+ d[i + 2] = clamp(d[i + 2] - adj);
112
+ }
113
+ return out;
125
114
  }
126
115
  function adjustTint(data, amount) {
127
- const out = cloneImageData(data);
128
- const d = out.data;
129
- const adj = amount * 0.5;
130
- for (let i = 0; i < d.length; i += 4) {
131
- d[i + 1] = clamp(d[i + 1] - adj); // decrease green for magenta, increase for green
132
- }
133
- return out;
116
+ const out = cloneImageData(data);
117
+ const d = out.data;
118
+ const adj = amount * 0.5;
119
+ for (let i = 0; i < d.length; i += 4) {
120
+ d[i + 1] = clamp(d[i + 1] - adj);
121
+ }
122
+ return out;
134
123
  }
135
124
  function adjustExposure(data, amount) {
136
- const out = cloneImageData(data);
137
- const d = out.data;
138
- const factor = Math.pow(2, amount / 50);
139
- for (let i = 0; i < d.length; i += 4) {
140
- d[i] = clamp(d[i] * factor);
141
- d[i + 1] = clamp(d[i + 1] * factor);
142
- d[i + 2] = clamp(d[i + 2] * factor);
143
- }
144
- return out;
125
+ const out = cloneImageData(data);
126
+ const d = out.data;
127
+ const factor = Math.pow(2, amount / 50);
128
+ for (let i = 0; i < d.length; i += 4) {
129
+ d[i] = clamp(d[i] * factor);
130
+ d[i + 1] = clamp(d[i + 1] * factor);
131
+ d[i + 2] = clamp(d[i + 2] * factor);
132
+ }
133
+ return out;
145
134
  }
146
135
  function adjustHighlights(data, amount) {
147
- const out = cloneImageData(data);
148
- const d = out.data;
149
- const adj = amount / 100;
150
- for (let i = 0; i < d.length; i += 4) {
151
- const lum = (d[i] + d[i + 1] + d[i + 2]) / 3;
152
- if (lum > 128) {
153
- const factor = ((lum - 128) / 127) * adj * 40;
154
- d[i] = clamp(d[i] + factor);
155
- d[i + 1] = clamp(d[i + 1] + factor);
156
- d[i + 2] = clamp(d[i + 2] + factor);
157
- }
158
- }
159
- return out;
136
+ const out = cloneImageData(data);
137
+ const d = out.data;
138
+ const adj = amount / 100;
139
+ for (let i = 0; i < d.length; i += 4) {
140
+ const lum = (d[i] + d[i + 1] + d[i + 2]) / 3;
141
+ if (lum > 128) {
142
+ const factor = (lum - 128) / 127 * adj * 40;
143
+ d[i] = clamp(d[i] + factor);
144
+ d[i + 1] = clamp(d[i + 1] + factor);
145
+ d[i + 2] = clamp(d[i + 2] + factor);
146
+ }
147
+ }
148
+ return out;
160
149
  }
161
150
  function adjustShadows(data, amount) {
162
- const out = cloneImageData(data);
163
- const d = out.data;
164
- const adj = amount / 100;
165
- for (let i = 0; i < d.length; i += 4) {
166
- const lum = (d[i] + d[i + 1] + d[i + 2]) / 3;
167
- if (lum < 128) {
168
- const factor = ((128 - lum) / 128) * adj * 40;
169
- d[i] = clamp(d[i] + factor);
170
- d[i + 1] = clamp(d[i + 1] + factor);
171
- d[i + 2] = clamp(d[i + 2] + factor);
172
- }
173
- }
174
- return out;
151
+ const out = cloneImageData(data);
152
+ const d = out.data;
153
+ const adj = amount / 100;
154
+ for (let i = 0; i < d.length; i += 4) {
155
+ const lum = (d[i] + d[i + 1] + d[i + 2]) / 3;
156
+ if (lum < 128) {
157
+ const factor = (128 - lum) / 128 * adj * 40;
158
+ d[i] = clamp(d[i] + factor);
159
+ d[i + 1] = clamp(d[i + 1] + factor);
160
+ d[i + 2] = clamp(d[i + 2] + factor);
161
+ }
162
+ }
163
+ return out;
175
164
  }
176
165
  function posterize(data, levels = 4) {
177
- const out = cloneImageData(data);
178
- const d = out.data;
179
- const step = 255 / (levels - 1);
180
- for (let i = 0; i < d.length; i += 4) {
181
- d[i] = clamp(Math.round(d[i] / step) * step);
182
- d[i + 1] = clamp(Math.round(d[i + 1] / step) * step);
183
- d[i + 2] = clamp(Math.round(d[i + 2] / step) * step);
184
- }
185
- return out;
166
+ const out = cloneImageData(data);
167
+ const d = out.data;
168
+ const step = 255 / (levels - 1);
169
+ for (let i = 0; i < d.length; i += 4) {
170
+ d[i] = clamp(Math.round(d[i] / step) * step);
171
+ d[i + 1] = clamp(Math.round(d[i + 1] / step) * step);
172
+ d[i + 2] = clamp(Math.round(d[i + 2] / step) * step);
173
+ }
174
+ return out;
186
175
  }
187
176
  function solarize(data) {
188
- const out = cloneImageData(data);
189
- const d = out.data;
190
- for (let i = 0; i < d.length; i += 4) {
191
- d[i] = d[i] > 128 ? 255 - d[i] : d[i];
192
- d[i + 1] = d[i + 1] > 128 ? 255 - d[i + 1] : d[i + 1];
193
- d[i + 2] = d[i + 2] > 128 ? 255 - d[i + 2] : d[i + 2];
194
- }
195
- return out;
177
+ const out = cloneImageData(data);
178
+ const d = out.data;
179
+ for (let i = 0; i < d.length; i += 4) {
180
+ d[i] = d[i] > 128 ? 255 - d[i] : d[i];
181
+ d[i + 1] = d[i + 1] > 128 ? 255 - d[i + 1] : d[i + 1];
182
+ d[i + 2] = d[i + 2] > 128 ? 255 - d[i + 2] : d[i + 2];
183
+ }
184
+ return out;
196
185
  }
197
186
  function channelShift(data) {
198
- const out = cloneImageData(data);
199
- const d = out.data;
200
- for (let i = 0; i < d.length; i += 4) {
201
- const r = d[i], g = d[i + 1], b = d[i + 2];
202
- d[i] = g; // R ← G
203
- d[i + 1] = b; // G ← B
204
- d[i + 2] = r; // B ← R
205
- }
206
- return out;
187
+ const out = cloneImageData(data);
188
+ const d = out.data;
189
+ for (let i = 0; i < d.length; i += 4) {
190
+ const r = d[i], g = d[i + 1], b = d[i + 2];
191
+ d[i] = g;
192
+ d[i + 1] = b;
193
+ d[i + 2] = r;
194
+ }
195
+ return out;
207
196
  }
208
197
  function cyberpunk(data) {
209
- const out = cloneImageData(data);
210
- const d = out.data;
211
- for (let i = 0; i < d.length; i += 4) {
212
- const lum = 0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2];
213
- d[i] = clamp(lum * 0.2 + d[i] * 0.8 + 30); // boost red
214
- d[i + 1] = clamp(lum * 0.5 + d[i + 1] * 0.15 - 10); // crush green
215
- d[i + 2] = clamp(lum * 0.3 + d[i + 2] * 0.9 + 50); // heavy blue
216
- }
217
- return out;
198
+ const out = cloneImageData(data);
199
+ const d = out.data;
200
+ for (let i = 0; i < d.length; i += 4) {
201
+ const lum = 0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2];
202
+ d[i] = clamp(lum * 0.2 + d[i] * 0.8 + 30);
203
+ d[i + 1] = clamp(lum * 0.5 + d[i + 1] * 0.15 - 10);
204
+ d[i + 2] = clamp(lum * 0.3 + d[i + 2] * 0.9 + 50);
205
+ }
206
+ return out;
218
207
  }
219
208
  function retroWave(data) {
220
- const out = cloneImageData(data);
221
- const d = out.data;
222
- for (let i = 0; i < d.length; i += 4) {
223
- d[i] = clamp(d[i] * 1.1 + 20);
224
- d[i + 1] = clamp(d[i + 1] * 0.6 - 10);
225
- d[i + 2] = clamp(d[i + 2] * 1.3 + 30);
226
- }
227
- return adjustContrast(out, 30);
209
+ const out = cloneImageData(data);
210
+ const d = out.data;
211
+ for (let i = 0; i < d.length; i += 4) {
212
+ d[i] = clamp(d[i] * 1.1 + 20);
213
+ d[i + 1] = clamp(d[i + 1] * 0.6 - 10);
214
+ d[i + 2] = clamp(d[i + 2] * 1.3 + 30);
215
+ }
216
+ return adjustContrast(out, 30);
228
217
  }
229
218
  function vintageFilm(data) {
230
- let out = cloneImageData(data);
231
- const d = out.data;
232
- // warm tone overlay
233
- for (let i = 0; i < d.length; i += 4) {
234
- d[i] = clamp(d[i] * 1.08 + 8);
235
- d[i + 1] = clamp(d[i + 1] * 0.95 + 5);
236
- d[i + 2] = clamp(d[i + 2] * 0.85 - 5);
237
- }
238
- out = adjustContrast(out, -15);
239
- return adjustSaturation(out, -20);
219
+ let out = cloneImageData(data);
220
+ const d = out.data;
221
+ for (let i = 0; i < d.length; i += 4) {
222
+ d[i] = clamp(d[i] * 1.08 + 8);
223
+ d[i + 1] = clamp(d[i + 1] * 0.95 + 5);
224
+ d[i + 2] = clamp(d[i + 2] * 0.85 - 5);
225
+ }
226
+ out = adjustContrast(out, -15);
227
+ return adjustSaturation(out, -20);
240
228
  }
241
229
  function kodachrome(data) {
242
- const out = cloneImageData(data);
243
- const d = out.data;
244
- for (let i = 0; i < d.length; i += 4) {
245
- const r = d[i], g = d[i + 1], b = d[i + 2];
246
- d[i] = clamp(r * 1.129 + g * 0.097 + b * 0.035 - 12);
247
- d[i + 1] = clamp(r * 0.019 + g * 0.955 + b * 0.041 + 8);
248
- d[i + 2] = clamp(r * 0.016 + g * 0.033 + b * 0.837 + 28);
249
- }
250
- return out;
230
+ const out = cloneImageData(data);
231
+ const d = out.data;
232
+ for (let i = 0; i < d.length; i += 4) {
233
+ const r = d[i], g = d[i + 1], b = d[i + 2];
234
+ d[i] = clamp(r * 1.129 + g * 0.097 + b * 0.035 - 12);
235
+ d[i + 1] = clamp(r * 0.019 + g * 0.955 + b * 0.041 + 8);
236
+ d[i + 2] = clamp(r * 0.016 + g * 0.033 + b * 0.837 + 28);
237
+ }
238
+ return out;
251
239
  }
252
240
  function polaroid(data) {
253
- const out = cloneImageData(data);
254
- const d = out.data;
255
- for (let i = 0; i < d.length; i += 4) {
256
- const r = d[i], g = d[i + 1], b = d[i + 2];
257
- d[i] = clamp(r * 1.438 - g * 0.062 - b * 0.062 + 10);
258
- d[i + 1] = clamp(-r * 0.122 + g * 1.378 - b * 0.122 + 6);
259
- d[i + 2] = clamp(-r * 0.016 - g * 0.016 + b * 1.483 - 12);
260
- }
261
- return out;
241
+ const out = cloneImageData(data);
242
+ const d = out.data;
243
+ for (let i = 0; i < d.length; i += 4) {
244
+ const r = d[i], g = d[i + 1], b = d[i + 2];
245
+ d[i] = clamp(r * 1.438 - g * 0.062 - b * 0.062 + 10);
246
+ d[i + 1] = clamp(-r * 0.122 + g * 1.378 - b * 0.122 + 6);
247
+ d[i + 2] = clamp(-r * 0.016 - g * 0.016 + b * 1.483 - 12);
248
+ }
249
+ return out;
262
250
  }
263
251
  function technicolor(data) {
264
- const out = cloneImageData(data);
265
- const d = out.data;
266
- for (let i = 0; i < d.length; i += 4) {
267
- const r = d[i], g = d[i + 1], b = d[i + 2];
268
- d[i] = clamp(r * 1.913 - g * 0.282 - b * 0.023);
269
- d[i + 1] = clamp(-r * 0.204 + g * 1.397 - b * 0.018);
270
- d[i + 2] = clamp(-r * 0.199 - g * 0.291 + b * 1.765);
271
- }
272
- return out;
252
+ const out = cloneImageData(data);
253
+ const d = out.data;
254
+ for (let i = 0; i < d.length; i += 4) {
255
+ const r = d[i], g = d[i + 1], b = d[i + 2];
256
+ d[i] = clamp(r * 1.913 - g * 0.282 - b * 0.023);
257
+ d[i + 1] = clamp(-r * 0.204 + g * 1.397 - b * 0.018);
258
+ d[i + 2] = clamp(-r * 0.199 - g * 0.291 + b * 1.765);
259
+ }
260
+ return out;
273
261
  }
274
262
  function brownie(data) {
275
- const out = cloneImageData(data);
276
- const d = out.data;
277
- for (let i = 0; i < d.length; i += 4) {
278
- const r = d[i], g = d[i + 1], b = d[i + 2];
279
- d[i] = clamp(r * 0.599 + g * 0.345 + b * 0.272);
280
- d[i + 1] = clamp(r * 0.168 + g * 0.706 + b * 0.131);
281
- d[i + 2] = clamp(r * 0.131 + g * 0.168 + b * 0.706);
282
- }
283
- return out;
263
+ const out = cloneImageData(data);
264
+ const d = out.data;
265
+ for (let i = 0; i < d.length; i += 4) {
266
+ const r = d[i], g = d[i + 1], b = d[i + 2];
267
+ d[i] = clamp(r * 0.599 + g * 0.345 + b * 0.272);
268
+ d[i + 1] = clamp(r * 0.168 + g * 0.706 + b * 0.131);
269
+ d[i + 2] = clamp(r * 0.131 + g * 0.168 + b * 0.706);
270
+ }
271
+ return out;
284
272
  }
285
273
  function duotone(data, params) {
286
- const out = cloneImageData(data);
287
- const d = out.data;
288
- // audit-allow: literal-color +2 — the duotone filter's default inks, written into the photo
289
- const dark = hexToRgb((params === null || params === void 0 ? void 0 : params.darkColor) || '#001f3f');
290
- const light = hexToRgb((params === null || params === void 0 ? void 0 : params.lightColor) || '#ff851b');
291
- for (let i = 0; i < d.length; i += 4) {
292
- const lum = (0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2]) / 255;
293
- d[i] = clamp(dark[0] + (light[0] - dark[0]) * lum);
294
- d[i + 1] = clamp(dark[1] + (light[1] - dark[1]) * lum);
295
- d[i + 2] = clamp(dark[2] + (light[2] - dark[2]) * lum);
296
- }
297
- return out;
274
+ const out = cloneImageData(data);
275
+ const d = out.data;
276
+ const dark = hexToRgb(params?.darkColor || "#001f3f");
277
+ const light = hexToRgb(params?.lightColor || "#ff851b");
278
+ for (let i = 0; i < d.length; i += 4) {
279
+ const lum = (0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2]) / 255;
280
+ d[i] = clamp(dark[0] + (light[0] - dark[0]) * lum);
281
+ d[i + 1] = clamp(dark[1] + (light[1] - dark[1]) * lum);
282
+ d[i + 2] = clamp(dark[2] + (light[2] - dark[2]) * lum);
283
+ }
284
+ return out;
298
285
  }
299
286
  function threshold(data, level = 128) {
300
- const out = cloneImageData(data);
301
- const d = out.data;
302
- for (let i = 0; i < d.length; i += 4) {
303
- const lum = 0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2];
304
- const v = lum >= level ? 255 : 0;
305
- d[i] = d[i + 1] = d[i + 2] = v;
306
- }
307
- return out;
287
+ const out = cloneImageData(data);
288
+ const d = out.data;
289
+ for (let i = 0; i < d.length; i += 4) {
290
+ const lum = 0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2];
291
+ const v = lum >= level ? 255 : 0;
292
+ d[i] = d[i + 1] = d[i + 2] = v;
293
+ }
294
+ return out;
308
295
  }
309
296
  function emboss(data) {
310
- return convolve(data, [-2, -1, 0, -1, 1, 1, 0, 1, 2]);
297
+ return convolve(data, [-2, -1, 0, -1, 1, 1, 0, 1, 2]);
311
298
  }
312
299
  function edgeDetect(data) {
313
- return convolve(data, [-1, -1, -1, -1, 8, -1, -1, -1, -1]);
300
+ return convolve(data, [-1, -1, -1, -1, 8, -1, -1, -1, -1]);
314
301
  }
315
302
  function sharpen(data, amount = 1) {
316
- const k = amount;
317
- return convolve(data, [0, -k, 0, -k, 1 + 4 * k, -k, 0, -k, 0]);
303
+ const k = amount;
304
+ return convolve(data, [0, -k, 0, -k, 1 + 4 * k, -k, 0, -k, 0]);
318
305
  }
319
306
  function comicBook(data) {
320
- const edges = edgeDetect(data);
321
- const poster = posterize(data, 5);
322
- const out = cloneImageData(poster);
323
- const d = out.data;
324
- const ed = edges.data;
325
- for (let i = 0; i < d.length; i += 4) {
326
- const edgeLum = (ed[i] + ed[i + 1] + ed[i + 2]) / 3;
327
- if (edgeLum > 30) {
328
- d[i] = d[i + 1] = d[i + 2] = 0; // black edge lines
329
- }
330
- }
331
- return adjustSaturation(out, 40);
307
+ const edges = edgeDetect(data);
308
+ const poster = posterize(data, 5);
309
+ const out = cloneImageData(poster);
310
+ const d = out.data;
311
+ const ed = edges.data;
312
+ for (let i = 0; i < d.length; i += 4) {
313
+ const edgeLum = (ed[i] + ed[i + 1] + ed[i + 2]) / 3;
314
+ if (edgeLum > 30) {
315
+ d[i] = d[i + 1] = d[i + 2] = 0;
316
+ }
317
+ }
318
+ return adjustSaturation(out, 40);
332
319
  }
333
320
  function heatMap(data) {
334
- const out = cloneImageData(data);
335
- const d = out.data;
336
- for (let i = 0; i < d.length; i += 4) {
337
- const lum = (0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2]) / 255;
338
- // heatmap: blue → cyan → green → yellow → red
339
- if (lum < 0.25) {
340
- d[i] = 0;
341
- d[i + 1] = clamp(lum * 4 * 255);
342
- d[i + 2] = 255;
343
- }
344
- else if (lum < 0.5) {
345
- d[i] = 0;
346
- d[i + 1] = 255;
347
- d[i + 2] = clamp((1 - (lum - 0.25) * 4) * 255);
348
- }
349
- else if (lum < 0.75) {
350
- d[i] = clamp((lum - 0.5) * 4 * 255);
351
- d[i + 1] = 255;
352
- d[i + 2] = 0;
353
- }
354
- else {
355
- d[i] = 255;
356
- d[i + 1] = clamp((1 - (lum - 0.75) * 4) * 255);
357
- d[i + 2] = 0;
358
- }
359
- }
360
- return out;
321
+ const out = cloneImageData(data);
322
+ const d = out.data;
323
+ for (let i = 0; i < d.length; i += 4) {
324
+ const lum = (0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2]) / 255;
325
+ if (lum < 0.25) {
326
+ d[i] = 0;
327
+ d[i + 1] = clamp(lum * 4 * 255);
328
+ d[i + 2] = 255;
329
+ } else if (lum < 0.5) {
330
+ d[i] = 0;
331
+ d[i + 1] = 255;
332
+ d[i + 2] = clamp((1 - (lum - 0.25) * 4) * 255);
333
+ } else if (lum < 0.75) {
334
+ d[i] = clamp((lum - 0.5) * 4 * 255);
335
+ d[i + 1] = 255;
336
+ d[i + 2] = 0;
337
+ } else {
338
+ d[i] = 255;
339
+ d[i + 1] = clamp((1 - (lum - 0.75) * 4) * 255);
340
+ d[i + 2] = 0;
341
+ }
342
+ }
343
+ return out;
361
344
  }
362
345
  function nightVision(data) {
363
- const out = cloneImageData(data);
364
- const d = out.data;
365
- for (let i = 0; i < d.length; i += 4) {
366
- const lum = 0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2];
367
- d[i] = 0;
368
- d[i + 1] = clamp(lum * 1.4 + 10);
369
- d[i + 2] = 0;
370
- }
371
- return out;
346
+ const out = cloneImageData(data);
347
+ const d = out.data;
348
+ for (let i = 0; i < d.length; i += 4) {
349
+ const lum = 0.299 * d[i] + 0.587 * d[i + 1] + 0.114 * d[i + 2];
350
+ d[i] = 0;
351
+ d[i + 1] = clamp(lum * 1.4 + 10);
352
+ d[i + 2] = 0;
353
+ }
354
+ return out;
372
355
  }
373
356
  function xRay(data) {
374
- let out = invert(data);
375
- out = grayscale(out);
376
- return adjustContrast(out, 40);
357
+ let out = invert(data);
358
+ out = grayscale(out);
359
+ return adjustContrast(out, 40);
377
360
  }
378
361
  function crossProcess(data) {
379
- const out = cloneImageData(data);
380
- const d = out.data;
381
- for (let i = 0; i < d.length; i += 4) {
382
- // S-curve on green, inverse on blue
383
- d[i] = clamp(d[i] * 1.1 + 10);
384
- d[i + 1] = clamp(Math.pow(d[i + 1] / 255, 0.85) * 280);
385
- d[i + 2] = clamp(255 - (255 - d[i + 2]) * 0.7);
386
- }
387
- return out;
362
+ const out = cloneImageData(data);
363
+ const d = out.data;
364
+ for (let i = 0; i < d.length; i += 4) {
365
+ d[i] = clamp(d[i] * 1.1 + 10);
366
+ d[i + 1] = clamp(Math.pow(d[i + 1] / 255, 0.85) * 280);
367
+ d[i + 2] = clamp(255 - (255 - d[i + 2]) * 0.7);
368
+ }
369
+ return out;
388
370
  }
389
371
  function lomoLeaks(data) {
390
- const out = cloneImageData(data);
391
- const d = out.data;
392
- const w = data.width, h = data.height;
393
- for (let y = 0; y < h; y++) {
394
- for (let x = 0; x < w; x++) {
395
- const i = (y * w + x) * 4;
396
- // Warm light leak from top-left
397
- const dist = Math.sqrt(x * x + y * y) / Math.sqrt(w * w + h * h);
398
- const leak = Math.max(0, 1 - dist * 1.8) * 60;
399
- d[i] = clamp(d[i] + leak * 1.5);
400
- d[i + 1] = clamp(d[i + 1] + leak * 0.8);
401
- d[i + 2] = clamp(d[i + 2] - leak * 0.3);
402
- }
403
- }
404
- return adjustSaturation(out, 15);
372
+ const out = cloneImageData(data);
373
+ const d = out.data;
374
+ const w = data.width, h = data.height;
375
+ for (let y = 0; y < h; y++) {
376
+ for (let x = 0; x < w; x++) {
377
+ const i = (y * w + x) * 4;
378
+ const dist = Math.sqrt(x * x + y * y) / Math.sqrt(w * w + h * h);
379
+ const leak = Math.max(0, 1 - dist * 1.8) * 60;
380
+ d[i] = clamp(d[i] + leak * 1.5);
381
+ d[i + 1] = clamp(d[i + 1] + leak * 0.8);
382
+ d[i + 2] = clamp(d[i + 2] - leak * 0.3);
383
+ }
384
+ }
385
+ return adjustSaturation(out, 15);
405
386
  }
406
387
  function fadeOut(data) {
407
- let out = cloneImageData(data);
408
- out = adjustContrast(out, -30);
409
- const d = out.data;
410
- for (let i = 0; i < d.length; i += 4) {
411
- d[i] = clamp(d[i] + 25);
412
- d[i + 1] = clamp(d[i + 1] + 20);
413
- d[i + 2] = clamp(d[i + 2] + 30);
414
- }
415
- return adjustSaturation(out, -30);
388
+ let out = cloneImageData(data);
389
+ out = adjustContrast(out, -30);
390
+ const d = out.data;
391
+ for (let i = 0; i < d.length; i += 4) {
392
+ d[i] = clamp(d[i] + 25);
393
+ d[i + 1] = clamp(d[i + 1] + 20);
394
+ d[i + 2] = clamp(d[i + 2] + 30);
395
+ }
396
+ return adjustSaturation(out, -30);
416
397
  }
417
398
  function glitch(data) {
418
- const out = cloneImageData(data);
419
- const d = out.data;
420
- const w = data.width;
421
- // Shift some rows horizontally by random amounts
422
- for (let y = 0; y < data.height; y++) {
423
- if (Math.random() > 0.92) {
424
- const shift = Math.floor(Math.random() * 30 - 15);
425
- for (let x = 0; x < w; x++) {
426
- const srcX = (((x + shift) % w) + w) % w;
427
- const di = (y * w + x) * 4;
428
- const si = (y * w + srcX) * 4;
429
- // Red channel from shifted position
430
- d[di] = data.data[si];
431
- }
432
- }
433
- }
434
- return out;
399
+ const out = cloneImageData(data);
400
+ const d = out.data;
401
+ const w = data.width;
402
+ for (let y = 0; y < data.height; y++) {
403
+ if (Math.random() > 0.92) {
404
+ const shift = Math.floor(Math.random() * 30 - 15);
405
+ for (let x = 0; x < w; x++) {
406
+ const srcX = ((x + shift) % w + w) % w;
407
+ const di = (y * w + x) * 4;
408
+ const si = (y * w + srcX) * 4;
409
+ d[di] = data.data[si];
410
+ }
411
+ }
412
+ }
413
+ return out;
435
414
  }
436
415
  function pixelate(data, blockSize = 8) {
437
- const out = cloneImageData(data);
438
- const d = out.data;
439
- const w = data.width, h = data.height;
440
- for (let by = 0; by < h; by += blockSize) {
441
- for (let bx = 0; bx < w; bx += blockSize) {
442
- let rSum = 0, gSum = 0, bSum = 0, count = 0;
443
- for (let dy = 0; dy < blockSize && by + dy < h; dy++) {
444
- for (let dx = 0; dx < blockSize && bx + dx < w; dx++) {
445
- const i = ((by + dy) * w + (bx + dx)) * 4;
446
- rSum += d[i];
447
- gSum += d[i + 1];
448
- bSum += d[i + 2];
449
- count++;
450
- }
451
- }
452
- const rAvg = rSum / count, gAvg = gSum / count, bAvg = bSum / count;
453
- for (let dy = 0; dy < blockSize && by + dy < h; dy++) {
454
- for (let dx = 0; dx < blockSize && bx + dx < w; dx++) {
455
- const i = ((by + dy) * w + (bx + dx)) * 4;
456
- d[i] = rAvg;
457
- d[i + 1] = gAvg;
458
- d[i + 2] = bAvg;
459
- }
460
- }
416
+ const out = cloneImageData(data);
417
+ const d = out.data;
418
+ const w = data.width, h = data.height;
419
+ for (let by = 0; by < h; by += blockSize) {
420
+ for (let bx = 0; bx < w; bx += blockSize) {
421
+ let rSum = 0, gSum = 0, bSum = 0, count = 0;
422
+ for (let dy = 0; dy < blockSize && by + dy < h; dy++) {
423
+ for (let dx = 0; dx < blockSize && bx + dx < w; dx++) {
424
+ const i = ((by + dy) * w + (bx + dx)) * 4;
425
+ rSum += d[i];
426
+ gSum += d[i + 1];
427
+ bSum += d[i + 2];
428
+ count++;
429
+ }
430
+ }
431
+ const rAvg = rSum / count, gAvg = gSum / count, bAvg = bSum / count;
432
+ for (let dy = 0; dy < blockSize && by + dy < h; dy++) {
433
+ for (let dx = 0; dx < blockSize && bx + dx < w; dx++) {
434
+ const i = ((by + dy) * w + (bx + dx)) * 4;
435
+ d[i] = rAvg;
436
+ d[i + 1] = gAvg;
437
+ d[i + 2] = bAvg;
461
438
  }
439
+ }
462
440
  }
463
- return out;
441
+ }
442
+ return out;
464
443
  }
465
444
  function oilPainting(data, radius = 3) {
466
- const out = cloneImageData(data);
467
- const d = out.data;
468
- const src = data.data;
469
- const w = data.width, h = data.height;
470
- const levels = 20;
471
- for (let y = 0; y < h; y++) {
472
- for (let x = 0; x < w; x++) {
473
- const counts = new Int32Array(levels);
474
- const rSum = new Float64Array(levels);
475
- const gSum = new Float64Array(levels);
476
- const bSum = new Float64Array(levels);
477
- for (let ky = -radius; ky <= radius; ky++) {
478
- for (let kx = -radius; kx <= radius; kx++) {
479
- const ny = Math.min(h - 1, Math.max(0, y + ky));
480
- const nx = Math.min(w - 1, Math.max(0, x + kx));
481
- const ni = (ny * w + nx) * 4;
482
- const lum = Math.floor((((src[ni] + src[ni + 1] + src[ni + 2]) / 3) * levels) / 256);
483
- const li = Math.min(lum, levels - 1);
484
- counts[li]++;
485
- rSum[li] += src[ni];
486
- gSum[li] += src[ni + 1];
487
- bSum[li] += src[ni + 2];
488
- }
489
- }
490
- let maxCount = 0, maxIdx = 0;
491
- for (let l = 0; l < levels; l++) {
492
- if (counts[l] > maxCount) {
493
- maxCount = counts[l];
494
- maxIdx = l;
495
- }
496
- }
497
- const i = (y * w + x) * 4;
498
- d[i] = rSum[maxIdx] / maxCount;
499
- d[i + 1] = gSum[maxIdx] / maxCount;
500
- d[i + 2] = bSum[maxIdx] / maxCount;
445
+ const out = cloneImageData(data);
446
+ const d = out.data;
447
+ const src = data.data;
448
+ const w = data.width, h = data.height;
449
+ const levels = 20;
450
+ for (let y = 0; y < h; y++) {
451
+ for (let x = 0; x < w; x++) {
452
+ const counts = new Int32Array(levels);
453
+ const rSum = new Float64Array(levels);
454
+ const gSum = new Float64Array(levels);
455
+ const bSum = new Float64Array(levels);
456
+ for (let ky = -radius; ky <= radius; ky++) {
457
+ for (let kx = -radius; kx <= radius; kx++) {
458
+ const ny = Math.min(h - 1, Math.max(0, y + ky));
459
+ const nx = Math.min(w - 1, Math.max(0, x + kx));
460
+ const ni = (ny * w + nx) * 4;
461
+ const lum = Math.floor((src[ni] + src[ni + 1] + src[ni + 2]) / 3 * levels / 256);
462
+ const li = Math.min(lum, levels - 1);
463
+ counts[li]++;
464
+ rSum[li] += src[ni];
465
+ gSum[li] += src[ni + 1];
466
+ bSum[li] += src[ni + 2];
501
467
  }
502
- }
503
- return out;
468
+ }
469
+ let maxCount = 0, maxIdx = 0;
470
+ for (let l = 0; l < levels; l++) {
471
+ if (counts[l] > maxCount) {
472
+ maxCount = counts[l];
473
+ maxIdx = l;
474
+ }
475
+ }
476
+ const i = (y * w + x) * 4;
477
+ d[i] = rSum[maxIdx] / maxCount;
478
+ d[i + 1] = gSum[maxIdx] / maxCount;
479
+ d[i + 2] = bSum[maxIdx] / maxCount;
480
+ }
481
+ }
482
+ return out;
504
483
  }
505
484
  function tiltShift(data) {
506
- // Simple blur for top and bottom thirds
507
- const out = cloneImageData(data);
508
- const d = out.data;
509
- const src = data.data;
510
- const w = data.width, h = data.height;
511
- const centerY = h / 2;
512
- const focusHalf = h * 0.15;
513
- for (let y = 0; y < h; y++) {
514
- const dist = Math.abs(y - centerY);
515
- const blurRadius = dist < focusHalf ? 0 : Math.min(6, Math.floor((dist - focusHalf) / (h * 0.1)));
516
- if (blurRadius === 0) {
517
- continue;
485
+ const out = cloneImageData(data);
486
+ const d = out.data;
487
+ const src = data.data;
488
+ const w = data.width, h = data.height;
489
+ const centerY = h / 2;
490
+ const focusHalf = h * 0.15;
491
+ for (let y = 0; y < h; y++) {
492
+ const dist = Math.abs(y - centerY);
493
+ const blurRadius = dist < focusHalf ? 0 : Math.min(6, Math.floor((dist - focusHalf) / (h * 0.1)));
494
+ if (blurRadius === 0) {
495
+ continue;
496
+ }
497
+ for (let x = 0; x < w; x++) {
498
+ let rSum = 0, gSum = 0, bSum = 0, count = 0;
499
+ for (let ky = -blurRadius; ky <= blurRadius; ky++) {
500
+ for (let kx = -blurRadius; kx <= blurRadius; kx++) {
501
+ const ny = Math.min(h - 1, Math.max(0, y + ky));
502
+ const nx = Math.min(w - 1, Math.max(0, x + kx));
503
+ const si = (ny * w + nx) * 4;
504
+ rSum += src[si];
505
+ gSum += src[si + 1];
506
+ bSum += src[si + 2];
507
+ count++;
518
508
  }
519
- for (let x = 0; x < w; x++) {
520
- let rSum = 0, gSum = 0, bSum = 0, count = 0;
521
- for (let ky = -blurRadius; ky <= blurRadius; ky++) {
522
- for (let kx = -blurRadius; kx <= blurRadius; kx++) {
523
- const ny = Math.min(h - 1, Math.max(0, y + ky));
524
- const nx = Math.min(w - 1, Math.max(0, x + kx));
525
- const si = (ny * w + nx) * 4;
526
- rSum += src[si];
527
- gSum += src[si + 1];
528
- bSum += src[si + 2];
529
- count++;
530
- }
531
- }
532
- const i = (y * w + x) * 4;
533
- d[i] = rSum / count;
534
- d[i + 1] = gSum / count;
535
- d[i + 2] = bSum / count;
536
- }
537
- }
538
- return adjustSaturation(out, 20);
509
+ }
510
+ const i = (y * w + x) * 4;
511
+ d[i] = rSum / count;
512
+ d[i + 1] = gSum / count;
513
+ d[i + 2] = bSum / count;
514
+ }
515
+ }
516
+ return adjustSaturation(out, 20);
539
517
  }
540
518
  function pencilSketch(data) {
541
- const gray = grayscale(data);
542
- const inv = invert(gray);
543
- // Simple Gaussian-like blur on inverted
544
- const blurred = boxBlur(inv, 8);
545
- const out = cloneImageData(gray);
546
- const d = out.data;
547
- const bd = blurred.data;
548
- // Color dodge blend
549
- for (let i = 0; i < d.length; i += 4) {
550
- const base = d[i];
551
- const blend = bd[i];
552
- const r = blend === 255 ? 255 : clamp((base * 256) / (255 - blend));
553
- d[i] = d[i + 1] = d[i + 2] = r;
554
- }
555
- return out;
519
+ const gray = grayscale(data);
520
+ const inv = invert(gray);
521
+ const blurred = boxBlur(inv, 8);
522
+ const out = cloneImageData(gray);
523
+ const d = out.data;
524
+ const bd = blurred.data;
525
+ for (let i = 0; i < d.length; i += 4) {
526
+ const base = d[i];
527
+ const blend = bd[i];
528
+ const r = blend === 255 ? 255 : clamp(base * 256 / (255 - blend));
529
+ d[i] = d[i + 1] = d[i + 2] = r;
530
+ }
531
+ return out;
556
532
  }
557
533
  function watercolor(data) {
558
- let out = boxBlur(data, 3);
559
- out = adjustSaturation(out, 30);
560
- out = adjustContrast(out, -10);
561
- // Slight edge darkening
562
- const edges = edgeDetect(data);
563
- const d = out.data;
564
- const ed = edges.data;
565
- for (let i = 0; i < d.length; i += 4) {
566
- const edgeLum = (ed[i] + ed[i + 1] + ed[i + 2]) / 3;
567
- const darken = Math.min(edgeLum * 0.15, 25);
568
- d[i] = clamp(d[i] - darken);
569
- d[i + 1] = clamp(d[i + 1] - darken);
570
- d[i + 2] = clamp(d[i + 2] - darken);
571
- }
572
- return out;
534
+ let out = boxBlur(data, 3);
535
+ out = adjustSaturation(out, 30);
536
+ out = adjustContrast(out, -10);
537
+ const edges = edgeDetect(data);
538
+ const d = out.data;
539
+ const ed = edges.data;
540
+ for (let i = 0; i < d.length; i += 4) {
541
+ const edgeLum = (ed[i] + ed[i + 1] + ed[i + 2]) / 3;
542
+ const darken = Math.min(edgeLum * 0.15, 25);
543
+ d[i] = clamp(d[i] - darken);
544
+ d[i + 1] = clamp(d[i + 1] - darken);
545
+ d[i + 2] = clamp(d[i + 2] - darken);
546
+ }
547
+ return out;
573
548
  }
574
549
  function dreamyGlow(data) {
575
- const blurred = boxBlur(data, 6);
576
- const out = cloneImageData(data);
577
- const d = out.data;
578
- const bd = blurred.data;
579
- // Screen blend
580
- for (let i = 0; i < d.length; i += 4) {
581
- d[i] = clamp(255 - ((255 - d[i]) * (255 - bd[i])) / 255);
582
- d[i + 1] = clamp(255 - ((255 - d[i + 1]) * (255 - bd[i + 1])) / 255);
583
- d[i + 2] = clamp(255 - ((255 - d[i + 2]) * (255 - bd[i + 2])) / 255);
584
- }
585
- return adjustSaturation(out, 15);
550
+ const blurred = boxBlur(data, 6);
551
+ const out = cloneImageData(data);
552
+ const d = out.data;
553
+ const bd = blurred.data;
554
+ for (let i = 0; i < d.length; i += 4) {
555
+ d[i] = clamp(255 - (255 - d[i]) * (255 - bd[i]) / 255);
556
+ d[i + 1] = clamp(255 - (255 - d[i + 1]) * (255 - bd[i + 1]) / 255);
557
+ d[i + 2] = clamp(255 - (255 - d[i + 2]) * (255 - bd[i + 2]) / 255);
558
+ }
559
+ return adjustSaturation(out, 15);
586
560
  }
587
561
  function frostedGlass(data) {
588
- const out = cloneImageData(data);
589
- const d = out.data;
590
- const src = data.data;
591
- const w = data.width, h = data.height;
592
- const radius = 4;
593
- for (let y = 0; y < h; y++) {
594
- for (let x = 0; x < w; x++) {
595
- const ox = x + Math.floor(Math.random() * radius * 2 - radius);
596
- const oy = y + Math.floor(Math.random() * radius * 2 - radius);
597
- const nx = Math.min(w - 1, Math.max(0, ox));
598
- const ny = Math.min(h - 1, Math.max(0, oy));
599
- const di = (y * w + x) * 4;
600
- const si = (ny * w + nx) * 4;
601
- d[di] = src[si];
602
- d[di + 1] = src[si + 1];
603
- d[di + 2] = src[si + 2];
604
- }
605
- }
606
- return out;
562
+ const out = cloneImageData(data);
563
+ const d = out.data;
564
+ const src = data.data;
565
+ const w = data.width, h = data.height;
566
+ const radius = 4;
567
+ for (let y = 0; y < h; y++) {
568
+ for (let x = 0; x < w; x++) {
569
+ const ox = x + Math.floor(Math.random() * radius * 2 - radius);
570
+ const oy = y + Math.floor(Math.random() * radius * 2 - radius);
571
+ const nx = Math.min(w - 1, Math.max(0, ox));
572
+ const ny = Math.min(h - 1, Math.max(0, oy));
573
+ const di = (y * w + x) * 4;
574
+ const si = (ny * w + nx) * 4;
575
+ d[di] = src[si];
576
+ d[di + 1] = src[si + 1];
577
+ d[di + 2] = src[si + 2];
578
+ }
579
+ }
580
+ return out;
607
581
  }
608
582
  function pop(data) {
609
- let out = adjustSaturation(data, 50);
610
- out = adjustContrast(out, 20);
611
- return adjustBrightness(out, 5);
583
+ let out = adjustSaturation(data, 50);
584
+ out = adjustContrast(out, 20);
585
+ return adjustBrightness(out, 5);
612
586
  }
613
587
  function moody(data) {
614
- let out = adjustSaturation(data, -25);
615
- out = adjustContrast(out, 15);
616
- out = adjustTemperature(out, -15);
617
- return adjustBrightness(out, -10);
588
+ let out = adjustSaturation(data, -25);
589
+ out = adjustContrast(out, 15);
590
+ out = adjustTemperature(out, -15);
591
+ return adjustBrightness(out, -10);
618
592
  }
619
593
  function golden(data) {
620
- const out = cloneImageData(data);
621
- const d = out.data;
622
- for (let i = 0; i < d.length; i += 4) {
623
- const lum = (d[i] + d[i + 1] + d[i + 2]) / 3;
624
- d[i] = clamp(lum * 0.4 + d[i] * 0.7 + 20);
625
- d[i + 1] = clamp(lum * 0.3 + d[i + 1] * 0.6 + 10);
626
- d[i + 2] = clamp(lum * 0.1 + d[i + 2] * 0.3 - 10);
627
- }
628
- return adjustContrast(out, 10);
594
+ const out = cloneImageData(data);
595
+ const d = out.data;
596
+ for (let i = 0; i < d.length; i += 4) {
597
+ const lum = (d[i] + d[i + 1] + d[i + 2]) / 3;
598
+ d[i] = clamp(lum * 0.4 + d[i] * 0.7 + 20);
599
+ d[i + 1] = clamp(lum * 0.3 + d[i + 1] * 0.6 + 10);
600
+ d[i + 2] = clamp(lum * 0.1 + d[i + 2] * 0.3 - 10);
601
+ }
602
+ return adjustContrast(out, 10);
629
603
  }
630
604
  function radioactive(data) {
631
- const out = cloneImageData(data);
632
- const d = out.data;
633
- for (let i = 0; i < d.length; i += 4) {
634
- d[i] = clamp(d[i] * 0.3);
635
- d[i + 1] = clamp(d[i + 1] * 1.6 + 30);
636
- d[i + 2] = clamp(d[i + 2] * 0.2);
637
- }
638
- return out;
605
+ const out = cloneImageData(data);
606
+ const d = out.data;
607
+ for (let i = 0; i < d.length; i += 4) {
608
+ d[i] = clamp(d[i] * 0.3);
609
+ d[i + 1] = clamp(d[i + 1] * 1.6 + 30);
610
+ d[i + 2] = clamp(d[i + 2] * 0.2);
611
+ }
612
+ return out;
639
613
  }
640
614
  function arctic(data) {
641
- let out = adjustTemperature(data, -50);
642
- out = adjustSaturation(out, -15);
643
- out = adjustBrightness(out, 10);
644
- return adjustContrast(out, 10);
615
+ let out = adjustTemperature(data, -50);
616
+ out = adjustSaturation(out, -15);
617
+ out = adjustBrightness(out, 10);
618
+ return adjustContrast(out, 10);
645
619
  }
646
620
  function sunset(data) {
647
- const out = cloneImageData(data);
648
- const d = out.data;
649
- for (let i = 0; i < d.length; i += 4) {
650
- d[i] = clamp(d[i] * 1.15 + 15);
651
- d[i + 1] = clamp(d[i + 1] * 0.85 + 5);
652
- d[i + 2] = clamp(d[i + 2] * 0.65 - 10);
653
- }
654
- return adjustContrast(out, 10);
621
+ const out = cloneImageData(data);
622
+ const d = out.data;
623
+ for (let i = 0; i < d.length; i += 4) {
624
+ d[i] = clamp(d[i] * 1.15 + 15);
625
+ d[i + 1] = clamp(d[i + 1] * 0.85 + 5);
626
+ d[i + 2] = clamp(d[i + 2] * 0.65 - 10);
627
+ }
628
+ return adjustContrast(out, 10);
655
629
  }
656
- // ── Convolution helper ──
657
630
  function convolve(data, kernel) {
658
- const out = cloneImageData(data);
659
- const d = out.data;
660
- const src = data.data;
661
- const w = data.width, h = data.height;
662
- const size = Math.sqrt(kernel.length) | 0;
663
- const half = size >> 1;
664
- for (let y = 0; y < h; y++) {
665
- for (let x = 0; x < w; x++) {
666
- let r = 0, g = 0, b = 0;
667
- for (let ky = 0; ky < size; ky++) {
668
- for (let kx = 0; kx < size; kx++) {
669
- const ny = Math.min(h - 1, Math.max(0, y + ky - half));
670
- const nx = Math.min(w - 1, Math.max(0, x + kx - half));
671
- const weight = kernel[ky * size + kx];
672
- const si = (ny * w + nx) * 4;
673
- r += src[si] * weight;
674
- g += src[si + 1] * weight;
675
- b += src[si + 2] * weight;
676
- }
677
- }
678
- const i = (y * w + x) * 4;
679
- d[i] = clamp(r);
680
- d[i + 1] = clamp(g);
681
- d[i + 2] = clamp(b);
631
+ const out = cloneImageData(data);
632
+ const d = out.data;
633
+ const src = data.data;
634
+ const w = data.width, h = data.height;
635
+ const size = Math.sqrt(kernel.length) | 0;
636
+ const half = size >> 1;
637
+ for (let y = 0; y < h; y++) {
638
+ for (let x = 0; x < w; x++) {
639
+ let r = 0, g = 0, b = 0;
640
+ for (let ky = 0; ky < size; ky++) {
641
+ for (let kx = 0; kx < size; kx++) {
642
+ const ny = Math.min(h - 1, Math.max(0, y + ky - half));
643
+ const nx = Math.min(w - 1, Math.max(0, x + kx - half));
644
+ const weight = kernel[ky * size + kx];
645
+ const si = (ny * w + nx) * 4;
646
+ r += src[si] * weight;
647
+ g += src[si + 1] * weight;
648
+ b += src[si + 2] * weight;
682
649
  }
683
- }
684
- return out;
650
+ }
651
+ const i = (y * w + x) * 4;
652
+ d[i] = clamp(r);
653
+ d[i + 1] = clamp(g);
654
+ d[i + 2] = clamp(b);
655
+ }
656
+ }
657
+ return out;
685
658
  }
686
659
  function boxBlur(data, radius) {
687
- const size = radius * 2 + 1;
688
- const weight = 1 / (size * size);
689
- const kernel = new Array(size * size).fill(weight);
690
- return convolve(data, kernel);
660
+ const size = radius * 2 + 1;
661
+ const weight = 1 / (size * size);
662
+ const kernel = new Array(size * size).fill(weight);
663
+ return convolve(data, kernel);
691
664
  }
692
665
  function hexToRgb(hex) {
693
- const h = hex.replace('#', '');
694
- return [
695
- parseInt(h.substring(0, 2), 16),
696
- parseInt(h.substring(2, 4), 16),
697
- parseInt(h.substring(4, 6), 16),
698
- ];
666
+ const h = hex.replace("#", "");
667
+ return [
668
+ parseInt(h.substring(0, 2), 16),
669
+ parseInt(h.substring(2, 4), 16),
670
+ parseInt(h.substring(4, 6), 16)
671
+ ];
699
672
  }
700
- // ── Canvas helpers for vignette & grain (applied via context) ──
701
673
  function applyVignette(ctx, w, h, amount) {
702
- if (amount <= 0) {
703
- return;
704
- }
705
- const cx = w / 2, cy = h / 2;
706
- const radius = Math.max(w, h) * 0.7;
707
- const grad = ctx.createRadialGradient(cx, cy, radius * 0.4, cx, cy, radius);
708
- grad.addColorStop(0, 'transparent');
709
- grad.addColorStop(1, `rgba(0,0,0,${(amount / 100) * 0.7})`);
710
- ctx.fillStyle = grad;
711
- ctx.fillRect(0, 0, w, h);
674
+ if (amount <= 0) {
675
+ return;
676
+ }
677
+ const cx = w / 2, cy = h / 2;
678
+ const radius = Math.max(w, h) * 0.7;
679
+ const grad = ctx.createRadialGradient(cx, cy, radius * 0.4, cx, cy, radius);
680
+ grad.addColorStop(0, "transparent");
681
+ grad.addColorStop(1, `rgba(0,0,0,${amount / 100 * 0.7})`);
682
+ ctx.fillStyle = grad;
683
+ ctx.fillRect(0, 0, w, h);
712
684
  }
713
685
  function applyGrain(ctx, w, h, amount) {
714
- if (amount <= 0) {
715
- return;
716
- }
717
- const imageData = ctx.getImageData(0, 0, w, h);
718
- const d = imageData.data;
719
- const intensity = (amount / 100) * 50;
720
- for (let i = 0; i < d.length; i += 4) {
721
- const noise = (Math.random() - 0.5) * intensity;
722
- d[i] = clamp(d[i] + noise);
723
- d[i + 1] = clamp(d[i + 1] + noise);
724
- d[i + 2] = clamp(d[i + 2] + noise);
725
- }
726
- ctx.putImageData(imageData, 0, 0);
686
+ if (amount <= 0) {
687
+ return;
688
+ }
689
+ const imageData = ctx.getImageData(0, 0, w, h);
690
+ const d = imageData.data;
691
+ const intensity = amount / 100 * 50;
692
+ for (let i = 0; i < d.length; i += 4) {
693
+ const noise = (Math.random() - 0.5) * intensity;
694
+ d[i] = clamp(d[i] + noise);
695
+ d[i + 1] = clamp(d[i + 1] + noise);
696
+ d[i + 2] = clamp(d[i + 2] + noise);
697
+ }
698
+ ctx.putImageData(imageData, 0, 0);
727
699
  }
728
- // ── Filter catalog ──
729
700
  const PHOTO_FILTERS = [
730
- // Color
731
- {
732
- id: 'none',
733
- name: 'Original',
734
- category: 'color',
735
- icon: 'square',
736
- apply: (d) => cloneImageData(d),
737
- },
738
- {
739
- id: 'grayscale',
740
- name: 'B&W',
741
- category: 'color',
742
- icon: 'circle',
743
- apply: (d, p) => { var _a; return blendIntensity(d, grayscale(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
744
- },
745
- {
746
- id: 'sepia',
747
- name: 'Sepia',
748
- category: 'color',
749
- icon: 'droplet',
750
- apply: (d, p) => { var _a; return blendIntensity(d, sepia(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
751
- },
752
- {
753
- id: 'invert',
754
- name: 'Negative',
755
- category: 'color',
756
- icon: 'refresh',
757
- apply: (d, p) => { var _a; return blendIntensity(d, invert(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
758
- },
759
- {
760
- id: 'duotone',
761
- name: 'Duotone',
762
- category: 'color',
763
- icon: 'palette',
764
- apply: (d, p) => { var _a; return blendIntensity(d, duotone(d, p), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
765
- },
766
- {
767
- id: 'channelShift',
768
- name: 'Channel Shift',
769
- category: 'color',
770
- icon: 'layers',
771
- apply: (d, p) => { var _a; return blendIntensity(d, channelShift(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
772
- },
773
- {
774
- id: 'threshold',
775
- name: 'Threshold',
776
- category: 'color',
777
- icon: 'hexagon',
778
- apply: (d, p) => { var _a; return blendIntensity(d, threshold(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
779
- },
780
- {
781
- id: 'pop',
782
- name: 'Pop',
783
- category: 'color',
784
- icon: 'zap',
785
- apply: (d, p) => { var _a; return blendIntensity(d, pop(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
786
- },
787
- // Light
788
- {
789
- id: 'dreamyGlow',
790
- name: 'Dreamy Glow',
791
- category: 'light',
792
- icon: 'wand',
793
- apply: (d, p) => { var _a; return blendIntensity(d, dreamyGlow(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
794
- },
795
- {
796
- id: 'lomoLeaks',
797
- name: 'Light Leaks',
798
- category: 'light',
799
- icon: 'sun',
800
- apply: (d, p) => { var _a; return blendIntensity(d, lomoLeaks(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
801
- },
802
- {
803
- id: 'fadeOut',
804
- name: 'Fade',
805
- category: 'light',
806
- icon: 'cloud',
807
- apply: (d, p) => { var _a; return blendIntensity(d, fadeOut(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
808
- },
809
- {
810
- id: 'golden',
811
- name: 'Golden',
812
- category: 'light',
813
- icon: 'star',
814
- apply: (d, p) => { var _a; return blendIntensity(d, golden(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
815
- },
816
- {
817
- id: 'sunset',
818
- name: 'Sunset',
819
- category: 'light',
820
- icon: 'mountain',
821
- apply: (d, p) => { var _a; return blendIntensity(d, sunset(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
822
- },
823
- {
824
- id: 'moody',
825
- name: 'Moody',
826
- category: 'light',
827
- icon: 'moon',
828
- apply: (d, p) => { var _a; return blendIntensity(d, moody(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
829
- },
830
- {
831
- id: 'arctic',
832
- name: 'Arctic',
833
- category: 'light',
834
- icon: 'thermometer',
835
- apply: (d, p) => { var _a; return blendIntensity(d, arctic(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
836
- },
837
- // Vintage
838
- {
839
- id: 'vintageFilm',
840
- name: 'Vintage Film',
841
- category: 'vintage',
842
- icon: 'video',
843
- apply: (d, p) => { var _a; return blendIntensity(d, vintageFilm(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
844
- },
845
- {
846
- id: 'kodachrome',
847
- name: 'Kodachrome',
848
- category: 'vintage',
849
- icon: 'camera',
850
- apply: (d, p) => { var _a; return blendIntensity(d, kodachrome(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
851
- },
852
- {
853
- id: 'polaroid',
854
- name: 'Polaroid',
855
- category: 'vintage',
856
- icon: 'image',
857
- apply: (d, p) => { var _a; return blendIntensity(d, polaroid(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
858
- },
859
- {
860
- id: 'technicolor',
861
- name: 'Technicolor',
862
- category: 'vintage',
863
- icon: 'play',
864
- apply: (d, p) => { var _a; return blendIntensity(d, technicolor(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
865
- },
866
- {
867
- id: 'brownie',
868
- name: 'Brownie',
869
- category: 'vintage',
870
- icon: 'box',
871
- apply: (d, p) => { var _a; return blendIntensity(d, brownie(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
872
- },
873
- {
874
- id: 'crossProcess',
875
- name: 'Cross Process',
876
- category: 'vintage',
877
- icon: 'flask',
878
- apply: (d, p) => { var _a; return blendIntensity(d, crossProcess(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
879
- },
880
- // Artistic
881
- {
882
- id: 'pencilSketch',
883
- name: 'Pencil sketch',
884
- category: 'artistic',
885
- icon: 'pen',
886
- apply: (d, p) => { var _a; return blendIntensity(d, pencilSketch(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
887
- },
888
- {
889
- id: 'oilPainting',
890
- name: 'Oil paint',
891
- category: 'artistic',
892
- icon: 'palette',
893
- apply: (d, p) => { var _a; return blendIntensity(d, oilPainting(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
894
- },
895
- {
896
- id: 'watercolor',
897
- name: 'Watercolor',
898
- category: 'artistic',
899
- icon: 'brush',
900
- apply: (d, p) => { var _a; return blendIntensity(d, watercolor(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
901
- },
902
- {
903
- id: 'comicBook',
904
- name: 'Comic book',
905
- category: 'artistic',
906
- icon: 'message',
907
- apply: (d, p) => { var _a; return blendIntensity(d, comicBook(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
908
- },
909
- {
910
- id: 'posterize',
911
- name: 'Poster',
912
- category: 'artistic',
913
- icon: 'layers',
914
- apply: (d, p) => { var _a; return blendIntensity(d, posterize(d, 5), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
915
- },
916
- {
917
- id: 'emboss',
918
- name: 'Emboss',
919
- category: 'artistic',
920
- icon: 'triangle',
921
- apply: (d, p) => { var _a; return blendIntensity(d, emboss(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
922
- },
923
- {
924
- id: 'edgeDetect',
925
- name: 'Edges',
926
- category: 'artistic',
927
- icon: 'search',
928
- apply: (d, p) => { var _a; return blendIntensity(d, edgeDetect(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
929
- },
930
- {
931
- id: 'tiltShift',
932
- name: 'Tilt Shift',
933
- category: 'artistic',
934
- icon: 'target',
935
- apply: (d, p) => { var _a; return blendIntensity(d, tiltShift(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
936
- },
937
- // Fun
938
- {
939
- id: 'cyberpunk',
940
- name: 'Cyberpunk',
941
- category: 'fun',
942
- icon: 'code',
943
- apply: (d, p) => { var _a; return blendIntensity(d, cyberpunk(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
944
- },
945
- {
946
- id: 'retroWave',
947
- name: 'Retrowave',
948
- category: 'fun',
949
- icon: 'gamepad',
950
- apply: (d, p) => { var _a; return blendIntensity(d, retroWave(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
951
- },
952
- {
953
- id: 'nightVision',
954
- name: 'Night Vision',
955
- category: 'fun',
956
- icon: 'eye',
957
- apply: (d, p) => { var _a; return blendIntensity(d, nightVision(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
958
- },
959
- {
960
- id: 'xRay',
961
- name: 'X-Ray',
962
- category: 'fun',
963
- icon: 'slash',
964
- apply: (d, p) => { var _a; return blendIntensity(d, xRay(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
965
- },
966
- {
967
- id: 'heatMap',
968
- name: 'Heat Map',
969
- category: 'fun',
970
- icon: 'thermometer',
971
- apply: (d, p) => { var _a; return blendIntensity(d, heatMap(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
972
- },
973
- {
974
- id: 'radioactive',
975
- name: 'Radioactive',
976
- category: 'fun',
977
- icon: 'warning',
978
- apply: (d, p) => { var _a; return blendIntensity(d, radioactive(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
979
- },
980
- {
981
- id: 'glitch',
982
- name: 'Glitch',
983
- category: 'fun',
984
- icon: 'monitor',
985
- apply: (d, p) => { var _a; return blendIntensity(d, glitch(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
986
- },
987
- {
988
- id: 'pixelate',
989
- name: 'Pixelate',
990
- category: 'fun',
991
- icon: 'grid',
992
- apply: (d, p) => { var _a; return blendIntensity(d, pixelate(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
993
- },
994
- {
995
- id: 'frostedGlass',
996
- name: 'Frosted glass',
997
- category: 'fun',
998
- icon: 'box',
999
- apply: (d, p) => { var _a; return blendIntensity(d, frostedGlass(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
1000
- },
1001
- {
1002
- id: 'solarize',
1003
- name: 'Solarize',
1004
- category: 'fun',
1005
- icon: 'sun',
1006
- apply: (d, p) => { var _a; return blendIntensity(d, solarize(d), (_a = p === null || p === void 0 ? void 0 : p.intensity) !== null && _a !== void 0 ? _a : 1); },
1007
- },
701
+ // Color
702
+ {
703
+ id: "none",
704
+ name: "Original",
705
+ category: "color",
706
+ icon: "square",
707
+ apply: (d) => cloneImageData(d)
708
+ },
709
+ {
710
+ id: "grayscale",
711
+ name: "B&W",
712
+ category: "color",
713
+ icon: "circle",
714
+ apply: (d, p) => blendIntensity(d, grayscale(d), p?.intensity ?? 1)
715
+ },
716
+ {
717
+ id: "sepia",
718
+ name: "Sepia",
719
+ category: "color",
720
+ icon: "droplet",
721
+ apply: (d, p) => blendIntensity(d, sepia(d), p?.intensity ?? 1)
722
+ },
723
+ {
724
+ id: "invert",
725
+ name: "Negative",
726
+ category: "color",
727
+ icon: "refresh",
728
+ apply: (d, p) => blendIntensity(d, invert(d), p?.intensity ?? 1)
729
+ },
730
+ {
731
+ id: "duotone",
732
+ name: "Duotone",
733
+ category: "color",
734
+ icon: "palette",
735
+ apply: (d, p) => blendIntensity(d, duotone(d, p), p?.intensity ?? 1)
736
+ },
737
+ {
738
+ id: "channelShift",
739
+ name: "Channel Shift",
740
+ category: "color",
741
+ icon: "layers",
742
+ apply: (d, p) => blendIntensity(d, channelShift(d), p?.intensity ?? 1)
743
+ },
744
+ {
745
+ id: "threshold",
746
+ name: "Threshold",
747
+ category: "color",
748
+ icon: "hexagon",
749
+ apply: (d, p) => blendIntensity(d, threshold(d), p?.intensity ?? 1)
750
+ },
751
+ {
752
+ id: "pop",
753
+ name: "Pop",
754
+ category: "color",
755
+ icon: "zap",
756
+ apply: (d, p) => blendIntensity(d, pop(d), p?.intensity ?? 1)
757
+ },
758
+ // Light
759
+ {
760
+ id: "dreamyGlow",
761
+ name: "Dreamy Glow",
762
+ category: "light",
763
+ icon: "wand",
764
+ apply: (d, p) => blendIntensity(d, dreamyGlow(d), p?.intensity ?? 1)
765
+ },
766
+ {
767
+ id: "lomoLeaks",
768
+ name: "Light Leaks",
769
+ category: "light",
770
+ icon: "sun",
771
+ apply: (d, p) => blendIntensity(d, lomoLeaks(d), p?.intensity ?? 1)
772
+ },
773
+ {
774
+ id: "fadeOut",
775
+ name: "Fade",
776
+ category: "light",
777
+ icon: "cloud",
778
+ apply: (d, p) => blendIntensity(d, fadeOut(d), p?.intensity ?? 1)
779
+ },
780
+ {
781
+ id: "golden",
782
+ name: "Golden",
783
+ category: "light",
784
+ icon: "star",
785
+ apply: (d, p) => blendIntensity(d, golden(d), p?.intensity ?? 1)
786
+ },
787
+ {
788
+ id: "sunset",
789
+ name: "Sunset",
790
+ category: "light",
791
+ icon: "mountain",
792
+ apply: (d, p) => blendIntensity(d, sunset(d), p?.intensity ?? 1)
793
+ },
794
+ {
795
+ id: "moody",
796
+ name: "Moody",
797
+ category: "light",
798
+ icon: "moon",
799
+ apply: (d, p) => blendIntensity(d, moody(d), p?.intensity ?? 1)
800
+ },
801
+ {
802
+ id: "arctic",
803
+ name: "Arctic",
804
+ category: "light",
805
+ icon: "thermometer",
806
+ apply: (d, p) => blendIntensity(d, arctic(d), p?.intensity ?? 1)
807
+ },
808
+ // Vintage
809
+ {
810
+ id: "vintageFilm",
811
+ name: "Vintage Film",
812
+ category: "vintage",
813
+ icon: "video",
814
+ apply: (d, p) => blendIntensity(d, vintageFilm(d), p?.intensity ?? 1)
815
+ },
816
+ {
817
+ id: "kodachrome",
818
+ name: "Kodachrome",
819
+ category: "vintage",
820
+ icon: "camera",
821
+ apply: (d, p) => blendIntensity(d, kodachrome(d), p?.intensity ?? 1)
822
+ },
823
+ {
824
+ id: "polaroid",
825
+ name: "Polaroid",
826
+ category: "vintage",
827
+ icon: "image",
828
+ apply: (d, p) => blendIntensity(d, polaroid(d), p?.intensity ?? 1)
829
+ },
830
+ {
831
+ id: "technicolor",
832
+ name: "Technicolor",
833
+ category: "vintage",
834
+ icon: "play",
835
+ apply: (d, p) => blendIntensity(d, technicolor(d), p?.intensity ?? 1)
836
+ },
837
+ {
838
+ id: "brownie",
839
+ name: "Brownie",
840
+ category: "vintage",
841
+ icon: "box",
842
+ apply: (d, p) => blendIntensity(d, brownie(d), p?.intensity ?? 1)
843
+ },
844
+ {
845
+ id: "crossProcess",
846
+ name: "Cross Process",
847
+ category: "vintage",
848
+ icon: "flask",
849
+ apply: (d, p) => blendIntensity(d, crossProcess(d), p?.intensity ?? 1)
850
+ },
851
+ // Artistic
852
+ {
853
+ id: "pencilSketch",
854
+ name: "Pencil sketch",
855
+ category: "artistic",
856
+ icon: "pen",
857
+ apply: (d, p) => blendIntensity(d, pencilSketch(d), p?.intensity ?? 1)
858
+ },
859
+ {
860
+ id: "oilPainting",
861
+ name: "Oil paint",
862
+ category: "artistic",
863
+ icon: "palette",
864
+ apply: (d, p) => blendIntensity(d, oilPainting(d), p?.intensity ?? 1)
865
+ },
866
+ {
867
+ id: "watercolor",
868
+ name: "Watercolor",
869
+ category: "artistic",
870
+ icon: "brush",
871
+ apply: (d, p) => blendIntensity(d, watercolor(d), p?.intensity ?? 1)
872
+ },
873
+ {
874
+ id: "comicBook",
875
+ name: "Comic book",
876
+ category: "artistic",
877
+ icon: "message",
878
+ apply: (d, p) => blendIntensity(d, comicBook(d), p?.intensity ?? 1)
879
+ },
880
+ {
881
+ id: "posterize",
882
+ name: "Poster",
883
+ category: "artistic",
884
+ icon: "layers",
885
+ apply: (d, p) => blendIntensity(d, posterize(d, 5), p?.intensity ?? 1)
886
+ },
887
+ {
888
+ id: "emboss",
889
+ name: "Emboss",
890
+ category: "artistic",
891
+ icon: "triangle",
892
+ apply: (d, p) => blendIntensity(d, emboss(d), p?.intensity ?? 1)
893
+ },
894
+ {
895
+ id: "edgeDetect",
896
+ name: "Edges",
897
+ category: "artistic",
898
+ icon: "search",
899
+ apply: (d, p) => blendIntensity(d, edgeDetect(d), p?.intensity ?? 1)
900
+ },
901
+ {
902
+ id: "tiltShift",
903
+ name: "Tilt Shift",
904
+ category: "artistic",
905
+ icon: "target",
906
+ apply: (d, p) => blendIntensity(d, tiltShift(d), p?.intensity ?? 1)
907
+ },
908
+ // Fun
909
+ {
910
+ id: "cyberpunk",
911
+ name: "Cyberpunk",
912
+ category: "fun",
913
+ icon: "code",
914
+ apply: (d, p) => blendIntensity(d, cyberpunk(d), p?.intensity ?? 1)
915
+ },
916
+ {
917
+ id: "retroWave",
918
+ name: "Retrowave",
919
+ category: "fun",
920
+ icon: "gamepad",
921
+ apply: (d, p) => blendIntensity(d, retroWave(d), p?.intensity ?? 1)
922
+ },
923
+ {
924
+ id: "nightVision",
925
+ name: "Night Vision",
926
+ category: "fun",
927
+ icon: "eye",
928
+ apply: (d, p) => blendIntensity(d, nightVision(d), p?.intensity ?? 1)
929
+ },
930
+ {
931
+ id: "xRay",
932
+ name: "X-Ray",
933
+ category: "fun",
934
+ icon: "slash",
935
+ apply: (d, p) => blendIntensity(d, xRay(d), p?.intensity ?? 1)
936
+ },
937
+ {
938
+ id: "heatMap",
939
+ name: "Heat Map",
940
+ category: "fun",
941
+ icon: "thermometer",
942
+ apply: (d, p) => blendIntensity(d, heatMap(d), p?.intensity ?? 1)
943
+ },
944
+ {
945
+ id: "radioactive",
946
+ name: "Radioactive",
947
+ category: "fun",
948
+ icon: "warning",
949
+ apply: (d, p) => blendIntensity(d, radioactive(d), p?.intensity ?? 1)
950
+ },
951
+ {
952
+ id: "glitch",
953
+ name: "Glitch",
954
+ category: "fun",
955
+ icon: "monitor",
956
+ apply: (d, p) => blendIntensity(d, glitch(d), p?.intensity ?? 1)
957
+ },
958
+ {
959
+ id: "pixelate",
960
+ name: "Pixelate",
961
+ category: "fun",
962
+ icon: "grid",
963
+ apply: (d, p) => blendIntensity(d, pixelate(d), p?.intensity ?? 1)
964
+ },
965
+ {
966
+ id: "frostedGlass",
967
+ name: "Frosted glass",
968
+ category: "fun",
969
+ icon: "box",
970
+ apply: (d, p) => blendIntensity(d, frostedGlass(d), p?.intensity ?? 1)
971
+ },
972
+ {
973
+ id: "solarize",
974
+ name: "Solarize",
975
+ category: "fun",
976
+ icon: "sun",
977
+ apply: (d, p) => blendIntensity(d, solarize(d), p?.intensity ?? 1)
978
+ }
1008
979
  ];
1009
- /** Get filter by ID */
1010
980
  function getFilter(id) {
1011
- return PHOTO_FILTERS.find((f) => f.id === id);
981
+ return PHOTO_FILTERS.find((f) => f.id === id);
1012
982
  }
1013
- /** Apply adjustments to ImageData */
1014
983
  function applyAdjustments(data, adj) {
1015
- let out = data;
1016
- if (adj.exposure && adj.exposure !== 0) {
1017
- out = adjustExposure(out, adj.exposure);
1018
- }
1019
- if (adj.brightness && adj.brightness !== 0) {
1020
- out = adjustBrightness(out, adj.brightness);
1021
- }
1022
- if (adj.contrast && adj.contrast !== 0) {
1023
- out = adjustContrast(out, adj.contrast);
1024
- }
1025
- if (adj.saturation && adj.saturation !== 0) {
1026
- out = adjustSaturation(out, adj.saturation);
1027
- }
1028
- if (adj.temperature && adj.temperature !== 0) {
1029
- out = adjustTemperature(out, adj.temperature);
1030
- }
1031
- if (adj.tint && adj.tint !== 0) {
1032
- out = adjustTint(out, adj.tint);
1033
- }
1034
- if (adj.highlights && adj.highlights !== 0) {
1035
- out = adjustHighlights(out, adj.highlights);
1036
- }
1037
- if (adj.shadows && adj.shadows !== 0) {
1038
- out = adjustShadows(out, adj.shadows);
1039
- }
1040
- if (adj.sharpness && adj.sharpness > 0) {
1041
- out = sharpen(out, adj.sharpness / 100);
1042
- }
1043
- return out;
984
+ let out = data;
985
+ if (adj.exposure && adj.exposure !== 0) {
986
+ out = adjustExposure(out, adj.exposure);
987
+ }
988
+ if (adj.brightness && adj.brightness !== 0) {
989
+ out = adjustBrightness(out, adj.brightness);
990
+ }
991
+ if (adj.contrast && adj.contrast !== 0) {
992
+ out = adjustContrast(out, adj.contrast);
993
+ }
994
+ if (adj.saturation && adj.saturation !== 0) {
995
+ out = adjustSaturation(out, adj.saturation);
996
+ }
997
+ if (adj.temperature && adj.temperature !== 0) {
998
+ out = adjustTemperature(out, adj.temperature);
999
+ }
1000
+ if (adj.tint && adj.tint !== 0) {
1001
+ out = adjustTint(out, adj.tint);
1002
+ }
1003
+ if (adj.highlights && adj.highlights !== 0) {
1004
+ out = adjustHighlights(out, adj.highlights);
1005
+ }
1006
+ if (adj.shadows && adj.shadows !== 0) {
1007
+ out = adjustShadows(out, adj.shadows);
1008
+ }
1009
+ if (adj.sharpness && adj.sharpness > 0) {
1010
+ out = sharpen(out, adj.sharpness / 100);
1011
+ }
1012
+ return out;
1044
1013
  }
1045
- /** Full pipeline: filter + adjustments → canvas, then vignette & grain via context */
1046
1014
  function renderToCanvas(canvas, sourceImage, filterId, filterParams, adjustments, cropRect, rotation, flipH, flipV) {
1047
- const ctx = canvas.getContext('2d', { willReadFrequently: true });
1048
- if (!ctx) {
1049
- return;
1050
- }
1051
- // Source dimensions
1052
- const sw = sourceImage.naturalWidth;
1053
- const sh = sourceImage.naturalHeight;
1054
- // Crop region (in source pixels)
1055
- const crop = cropRect !== null && cropRect !== void 0 ? cropRect : { x: 0, y: 0, w: sw, h: sh };
1056
- // Rotation affects output dimensions
1057
- const rot = (((rotation !== null && rotation !== void 0 ? rotation : 0) % 360) + 360) % 360;
1058
- const swapped = rot === 90 || rot === 270;
1059
- const outW = swapped ? crop.h : crop.w;
1060
- const outH = swapped ? crop.w : crop.h;
1061
- canvas.width = outW;
1062
- canvas.height = outH;
1063
- ctx.save();
1064
- ctx.translate(outW / 2, outH / 2);
1065
- if (rot) {
1066
- ctx.rotate((rot * Math.PI) / 180);
1067
- }
1068
- if (flipH) {
1069
- ctx.scale(-1, 1);
1070
- }
1071
- if (flipV) {
1072
- ctx.scale(1, -1);
1073
- }
1074
- ctx.drawImage(sourceImage, crop.x, crop.y, crop.w, crop.h, -crop.w / 2, -crop.h / 2, crop.w, crop.h);
1075
- ctx.restore();
1076
- // Apply filter
1077
- if (filterId && filterId !== 'none') {
1078
- const filter = getFilter(filterId);
1079
- if (filter) {
1080
- const imgData = ctx.getImageData(0, 0, outW, outH);
1081
- const filtered = filter.apply(imgData, filterParams);
1082
- ctx.putImageData(filtered, 0, 0);
1083
- }
1084
- }
1085
- // Apply adjustments
1086
- const hasAdj = Object.values(adjustments).some((v) => v !== 0 && v !== undefined);
1087
- if (hasAdj) {
1088
- const imgData = ctx.getImageData(0, 0, outW, outH);
1089
- const adjusted = applyAdjustments(imgData, adjustments);
1090
- ctx.putImageData(adjusted, 0, 0);
1091
- }
1092
- // Vignette & grain (applied via context drawing)
1093
- if (adjustments.vignette && adjustments.vignette > 0) {
1094
- applyVignette(ctx, outW, outH, adjustments.vignette);
1095
- }
1096
- if (adjustments.grain && adjustments.grain > 0) {
1097
- applyGrain(ctx, outW, outH, adjustments.grain);
1098
- }
1015
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
1016
+ if (!ctx) {
1017
+ return;
1018
+ }
1019
+ const sw = sourceImage.naturalWidth;
1020
+ const sh = sourceImage.naturalHeight;
1021
+ const crop = cropRect ?? { x: 0, y: 0, w: sw, h: sh };
1022
+ const rot = ((rotation ?? 0) % 360 + 360) % 360;
1023
+ const swapped = rot === 90 || rot === 270;
1024
+ const outW = swapped ? crop.h : crop.w;
1025
+ const outH = swapped ? crop.w : crop.h;
1026
+ canvas.width = outW;
1027
+ canvas.height = outH;
1028
+ ctx.save();
1029
+ ctx.translate(outW / 2, outH / 2);
1030
+ if (rot) {
1031
+ ctx.rotate(rot * Math.PI / 180);
1032
+ }
1033
+ if (flipH) {
1034
+ ctx.scale(-1, 1);
1035
+ }
1036
+ if (flipV) {
1037
+ ctx.scale(1, -1);
1038
+ }
1039
+ ctx.drawImage(
1040
+ sourceImage,
1041
+ crop.x,
1042
+ crop.y,
1043
+ crop.w,
1044
+ crop.h,
1045
+ -crop.w / 2,
1046
+ -crop.h / 2,
1047
+ crop.w,
1048
+ crop.h
1049
+ );
1050
+ ctx.restore();
1051
+ if (filterId && filterId !== "none") {
1052
+ const filter = getFilter(filterId);
1053
+ if (filter) {
1054
+ const imgData = ctx.getImageData(0, 0, outW, outH);
1055
+ const filtered = filter.apply(imgData, filterParams);
1056
+ ctx.putImageData(filtered, 0, 0);
1057
+ }
1058
+ }
1059
+ const hasAdj = Object.values(adjustments).some((v) => v !== 0 && v !== void 0);
1060
+ if (hasAdj) {
1061
+ const imgData = ctx.getImageData(0, 0, outW, outH);
1062
+ const adjusted = applyAdjustments(imgData, adjustments);
1063
+ ctx.putImageData(adjusted, 0, 0);
1064
+ }
1065
+ if (adjustments.vignette && adjustments.vignette > 0) {
1066
+ applyVignette(ctx, outW, outH, adjustments.vignette);
1067
+ }
1068
+ if (adjustments.grain && adjustments.grain > 0) {
1069
+ applyGrain(ctx, outW, outH, adjustments.grain);
1070
+ }
1099
1071
  }
1100
- /** Export canvas as File (for upload) */
1101
- function canvasToFile(canvas, filename = 'photo.jpg', quality = 0.92) {
1102
- return new Promise((resolve, reject) => {
1103
- canvas.toBlob((blob) => {
1104
- if (!blob) {
1105
- return reject(new Error('Canvas toBlob failed'));
1106
- }
1107
- resolve(new File([blob], filename, { type: 'image/jpeg' }));
1108
- }, 'image/jpeg', quality);
1109
- });
1072
+ function canvasToFile(canvas, filename = "photo.jpg", quality = 0.92) {
1073
+ return new Promise((resolve, reject) => {
1074
+ canvas.toBlob(
1075
+ (blob) => {
1076
+ if (!blob) {
1077
+ return reject(new Error("Canvas toBlob failed"));
1078
+ }
1079
+ resolve(new File([blob], filename, { type: "image/jpeg" }));
1080
+ },
1081
+ "image/jpeg",
1082
+ quality
1083
+ );
1084
+ });
1110
1085
  }
1111
-
1112
- export { DEFAULT_ADJUSTMENTS, PHOTO_FILTERS, applyAdjustments, applyGrain, applyVignette, canvasToFile, getFilter, renderToCanvas };
1086
+ export {
1087
+ DEFAULT_ADJUSTMENTS,
1088
+ PHOTO_FILTERS,
1089
+ applyAdjustments,
1090
+ applyGrain,
1091
+ applyVignette,
1092
+ canvasToFile,
1093
+ getFilter,
1094
+ renderToCanvas
1095
+ };