@guinetik/gcanvas 1.0.0

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 (349) hide show
  1. package/.github/workflows/release.yaml +70 -0
  2. package/.jshintrc +4 -0
  3. package/.vscode/settings.json +22 -0
  4. package/CLAUDE.md +310 -0
  5. package/blackhole.jpg +0 -0
  6. package/demo.png +0 -0
  7. package/demos/CNAME +1 -0
  8. package/demos/animations.html +31 -0
  9. package/demos/basic.html +38 -0
  10. package/demos/baskara.html +31 -0
  11. package/demos/bezier.html +35 -0
  12. package/demos/beziersignature.html +29 -0
  13. package/demos/blackhole.html +28 -0
  14. package/demos/blob.html +35 -0
  15. package/demos/demos.css +289 -0
  16. package/demos/easing.html +28 -0
  17. package/demos/events.html +195 -0
  18. package/demos/fluent.html +647 -0
  19. package/demos/fractals.html +36 -0
  20. package/demos/genart.html +26 -0
  21. package/demos/gendream.html +26 -0
  22. package/demos/group.html +36 -0
  23. package/demos/home.html +587 -0
  24. package/demos/index.html +364 -0
  25. package/demos/isometric.html +34 -0
  26. package/demos/js/animations.js +452 -0
  27. package/demos/js/basic.js +204 -0
  28. package/demos/js/baskara.js +751 -0
  29. package/demos/js/bezier.js +692 -0
  30. package/demos/js/beziersignature.js +241 -0
  31. package/demos/js/blackhole/accretiondisk.obj.js +379 -0
  32. package/demos/js/blackhole/blackhole.obj.js +318 -0
  33. package/demos/js/blackhole/index.js +409 -0
  34. package/demos/js/blackhole/particle.js +56 -0
  35. package/demos/js/blackhole/starfield.obj.js +218 -0
  36. package/demos/js/blob.js +2263 -0
  37. package/demos/js/easing.js +477 -0
  38. package/demos/js/fluent.js +183 -0
  39. package/demos/js/fractals.js +931 -0
  40. package/demos/js/fractalworker.js +93 -0
  41. package/demos/js/genart.js +268 -0
  42. package/demos/js/gendream.js +209 -0
  43. package/demos/js/group.js +140 -0
  44. package/demos/js/info-toggle.js +25 -0
  45. package/demos/js/isometric.js +863 -0
  46. package/demos/js/kerr.js +1556 -0
  47. package/demos/js/lavalamp.js +590 -0
  48. package/demos/js/layout.js +354 -0
  49. package/demos/js/mondrian.js +285 -0
  50. package/demos/js/opacity.js +275 -0
  51. package/demos/js/painter.js +484 -0
  52. package/demos/js/particles-showcase.js +514 -0
  53. package/demos/js/particles.js +299 -0
  54. package/demos/js/patterns.js +397 -0
  55. package/demos/js/penrose/artifact.js +69 -0
  56. package/demos/js/penrose/blackhole.js +121 -0
  57. package/demos/js/penrose/constants.js +73 -0
  58. package/demos/js/penrose/game.js +943 -0
  59. package/demos/js/penrose/lore.js +278 -0
  60. package/demos/js/penrose/penrosescene.js +892 -0
  61. package/demos/js/penrose/ship.js +216 -0
  62. package/demos/js/penrose/sounds.js +211 -0
  63. package/demos/js/penrose/voidparticle.js +55 -0
  64. package/demos/js/penrose/voidscene.js +258 -0
  65. package/demos/js/penrose/voidship.js +144 -0
  66. package/demos/js/penrose/wormhole.js +46 -0
  67. package/demos/js/pipeline.js +555 -0
  68. package/demos/js/scene.js +304 -0
  69. package/demos/js/scenes.js +320 -0
  70. package/demos/js/schrodinger.js +410 -0
  71. package/demos/js/schwarzschild.js +1023 -0
  72. package/demos/js/shapes.js +628 -0
  73. package/demos/js/space/alien.js +171 -0
  74. package/demos/js/space/boom.js +98 -0
  75. package/demos/js/space/boss.js +353 -0
  76. package/demos/js/space/buff.js +73 -0
  77. package/demos/js/space/bullet.js +102 -0
  78. package/demos/js/space/constants.js +85 -0
  79. package/demos/js/space/game.js +1884 -0
  80. package/demos/js/space/hud.js +112 -0
  81. package/demos/js/space/laserbeam.js +179 -0
  82. package/demos/js/space/lightning.js +277 -0
  83. package/demos/js/space/minion.js +192 -0
  84. package/demos/js/space/missile.js +212 -0
  85. package/demos/js/space/player.js +430 -0
  86. package/demos/js/space/powerup.js +90 -0
  87. package/demos/js/space/starfield.js +58 -0
  88. package/demos/js/space/starpower.js +90 -0
  89. package/demos/js/spacetime.js +559 -0
  90. package/demos/js/svgtween.js +204 -0
  91. package/demos/js/tde/accretiondisk.js +418 -0
  92. package/demos/js/tde/blackhole.js +219 -0
  93. package/demos/js/tde/blackholescene.js +209 -0
  94. package/demos/js/tde/config.js +59 -0
  95. package/demos/js/tde/index.js +695 -0
  96. package/demos/js/tde/jets.js +290 -0
  97. package/demos/js/tde/lensedstarfield.js +147 -0
  98. package/demos/js/tde/tdestar.js +317 -0
  99. package/demos/js/tde/tidalstream.js +356 -0
  100. package/demos/js/tde_old/blackhole.obj.js +354 -0
  101. package/demos/js/tde_old/debris.obj.js +791 -0
  102. package/demos/js/tde_old/flare.obj.js +239 -0
  103. package/demos/js/tde_old/index.js +448 -0
  104. package/demos/js/tde_old/star.obj.js +812 -0
  105. package/demos/js/tiles.js +312 -0
  106. package/demos/js/tweendemo.js +79 -0
  107. package/demos/js/visibility.js +102 -0
  108. package/demos/kerr.html +28 -0
  109. package/demos/lavalamp.html +27 -0
  110. package/demos/layouts.html +37 -0
  111. package/demos/logo.svg +4 -0
  112. package/demos/loop.html +84 -0
  113. package/demos/mondrian.html +32 -0
  114. package/demos/og_image.png +0 -0
  115. package/demos/opacity.html +36 -0
  116. package/demos/painter.html +39 -0
  117. package/demos/particles-showcase.html +28 -0
  118. package/demos/particles.html +24 -0
  119. package/demos/patterns.html +33 -0
  120. package/demos/penrose-game.html +31 -0
  121. package/demos/pipeline.html +737 -0
  122. package/demos/scene.html +33 -0
  123. package/demos/scenes.html +96 -0
  124. package/demos/schrodinger.html +27 -0
  125. package/demos/schwarzschild.html +27 -0
  126. package/demos/shapes.html +16 -0
  127. package/demos/space.html +85 -0
  128. package/demos/spacetime.html +27 -0
  129. package/demos/svgtween.html +29 -0
  130. package/demos/tde.html +28 -0
  131. package/demos/tiles.html +28 -0
  132. package/demos/transforms.html +400 -0
  133. package/demos/tween.html +45 -0
  134. package/demos/visibility.html +33 -0
  135. package/disk_example.png +0 -0
  136. package/docs/README.md +222 -0
  137. package/docs/concepts/architecture-overview.md +204 -0
  138. package/docs/concepts/lifecycle.md +255 -0
  139. package/docs/concepts/rendering-pipeline.md +279 -0
  140. package/docs/concepts/tde-zorder.md +106 -0
  141. package/docs/concepts/two-layer-architecture.md +229 -0
  142. package/docs/getting-started/first-game.md +354 -0
  143. package/docs/getting-started/hello-world.md +269 -0
  144. package/docs/getting-started/installation.md +157 -0
  145. package/docs/modules/collision/README.md +453 -0
  146. package/docs/modules/fluent/README.md +1075 -0
  147. package/docs/modules/game/README.md +303 -0
  148. package/docs/modules/isometric-camera.md +210 -0
  149. package/docs/modules/isometric.md +275 -0
  150. package/docs/modules/painter/README.md +328 -0
  151. package/docs/modules/particle/README.md +559 -0
  152. package/docs/modules/shapes/README.md +221 -0
  153. package/docs/modules/shapes/base/euclidian.md +123 -0
  154. package/docs/modules/shapes/base/geometry2d.md +204 -0
  155. package/docs/modules/shapes/base/renderable.md +215 -0
  156. package/docs/modules/shapes/base/shape.md +262 -0
  157. package/docs/modules/shapes/base/transformable.md +243 -0
  158. package/docs/modules/shapes/hierarchy.md +218 -0
  159. package/docs/modules/state/README.md +577 -0
  160. package/docs/modules/util/README.md +99 -0
  161. package/docs/modules/util/camera3d.md +412 -0
  162. package/docs/modules/util/scene3d.md +395 -0
  163. package/index.html +17 -0
  164. package/jsdoc.json +50 -0
  165. package/package.json +55 -0
  166. package/readme.md +599 -0
  167. package/scripts/build-demo.js +69 -0
  168. package/scripts/bundle4llm.js +276 -0
  169. package/scripts/clearconsole.js +48 -0
  170. package/src/collision/collision-system.js +332 -0
  171. package/src/collision/collision.js +303 -0
  172. package/src/collision/index.js +10 -0
  173. package/src/fluent/fluent-game.js +430 -0
  174. package/src/fluent/fluent-go.js +1060 -0
  175. package/src/fluent/fluent-layer.js +152 -0
  176. package/src/fluent/fluent-scene.js +291 -0
  177. package/src/fluent/index.js +98 -0
  178. package/src/fluent/sketch.js +380 -0
  179. package/src/game/game.js +467 -0
  180. package/src/game/index.js +49 -0
  181. package/src/game/objects/go.js +220 -0
  182. package/src/game/objects/imagego.js +30 -0
  183. package/src/game/objects/index.js +54 -0
  184. package/src/game/objects/isometric-scene.js +260 -0
  185. package/src/game/objects/layoutscene.js +549 -0
  186. package/src/game/objects/scene.js +175 -0
  187. package/src/game/objects/scene3d.js +118 -0
  188. package/src/game/objects/text.js +221 -0
  189. package/src/game/objects/wrapper.js +232 -0
  190. package/src/game/pipeline.js +243 -0
  191. package/src/game/ui/button.js +396 -0
  192. package/src/game/ui/cursor.js +93 -0
  193. package/src/game/ui/fps.js +91 -0
  194. package/src/game/ui/index.js +5 -0
  195. package/src/game/ui/togglebutton.js +93 -0
  196. package/src/game/ui/tooltip.js +249 -0
  197. package/src/index.js +25 -0
  198. package/src/io/events.js +20 -0
  199. package/src/io/index.js +86 -0
  200. package/src/io/input.js +70 -0
  201. package/src/io/keys.js +152 -0
  202. package/src/io/mouse.js +61 -0
  203. package/src/io/touch.js +39 -0
  204. package/src/logger/debugtab.js +138 -0
  205. package/src/logger/index.js +3 -0
  206. package/src/logger/loggable.js +47 -0
  207. package/src/logger/logger.js +113 -0
  208. package/src/math/complex.js +37 -0
  209. package/src/math/constants.js +1 -0
  210. package/src/math/fractal.js +1271 -0
  211. package/src/math/gr.js +201 -0
  212. package/src/math/heat.js +202 -0
  213. package/src/math/index.js +12 -0
  214. package/src/math/noise.js +433 -0
  215. package/src/math/orbital.js +191 -0
  216. package/src/math/patterns.js +1339 -0
  217. package/src/math/penrose.js +259 -0
  218. package/src/math/quantum.js +115 -0
  219. package/src/math/random.js +195 -0
  220. package/src/math/tensor.js +1009 -0
  221. package/src/mixins/anchor.js +131 -0
  222. package/src/mixins/draggable.js +72 -0
  223. package/src/mixins/index.js +2 -0
  224. package/src/motion/bezier.js +132 -0
  225. package/src/motion/bounce.js +58 -0
  226. package/src/motion/easing.js +349 -0
  227. package/src/motion/float.js +130 -0
  228. package/src/motion/follow.js +125 -0
  229. package/src/motion/hop.js +52 -0
  230. package/src/motion/index.js +82 -0
  231. package/src/motion/motion.js +1124 -0
  232. package/src/motion/orbit.js +49 -0
  233. package/src/motion/oscillate.js +39 -0
  234. package/src/motion/parabolic.js +141 -0
  235. package/src/motion/patrol.js +147 -0
  236. package/src/motion/pendulum.js +48 -0
  237. package/src/motion/pulse.js +88 -0
  238. package/src/motion/shake.js +83 -0
  239. package/src/motion/spiral.js +144 -0
  240. package/src/motion/spring.js +150 -0
  241. package/src/motion/swing.js +47 -0
  242. package/src/motion/tween.js +92 -0
  243. package/src/motion/tweenetik.js +139 -0
  244. package/src/motion/waypoint.js +210 -0
  245. package/src/painter/index.js +8 -0
  246. package/src/painter/painter.colors.js +331 -0
  247. package/src/painter/painter.effects.js +230 -0
  248. package/src/painter/painter.img.js +229 -0
  249. package/src/painter/painter.js +295 -0
  250. package/src/painter/painter.lines.js +189 -0
  251. package/src/painter/painter.opacity.js +41 -0
  252. package/src/painter/painter.shapes.js +277 -0
  253. package/src/painter/painter.text.js +273 -0
  254. package/src/particle/emitter.js +124 -0
  255. package/src/particle/index.js +11 -0
  256. package/src/particle/particle-system.js +322 -0
  257. package/src/particle/particle.js +71 -0
  258. package/src/particle/updaters.js +170 -0
  259. package/src/shapes/arc.js +43 -0
  260. package/src/shapes/arrow.js +33 -0
  261. package/src/shapes/bezier.js +42 -0
  262. package/src/shapes/circle.js +62 -0
  263. package/src/shapes/clouds.js +56 -0
  264. package/src/shapes/cone.js +219 -0
  265. package/src/shapes/cross.js +70 -0
  266. package/src/shapes/cube.js +244 -0
  267. package/src/shapes/cylinder.js +254 -0
  268. package/src/shapes/diamond.js +48 -0
  269. package/src/shapes/euclidian.js +111 -0
  270. package/src/shapes/figure.js +115 -0
  271. package/src/shapes/geometry.js +220 -0
  272. package/src/shapes/group.js +375 -0
  273. package/src/shapes/heart.js +42 -0
  274. package/src/shapes/hexagon.js +26 -0
  275. package/src/shapes/image.js +192 -0
  276. package/src/shapes/index.js +111 -0
  277. package/src/shapes/line.js +29 -0
  278. package/src/shapes/pattern.js +90 -0
  279. package/src/shapes/pin.js +44 -0
  280. package/src/shapes/poly.js +31 -0
  281. package/src/shapes/prism.js +226 -0
  282. package/src/shapes/rect.js +35 -0
  283. package/src/shapes/renderable.js +333 -0
  284. package/src/shapes/ring.js +26 -0
  285. package/src/shapes/roundrect.js +95 -0
  286. package/src/shapes/shape.js +117 -0
  287. package/src/shapes/slice.js +26 -0
  288. package/src/shapes/sphere.js +314 -0
  289. package/src/shapes/sphere3d.js +537 -0
  290. package/src/shapes/square.js +15 -0
  291. package/src/shapes/star.js +99 -0
  292. package/src/shapes/svg.js +408 -0
  293. package/src/shapes/text.js +553 -0
  294. package/src/shapes/traceable.js +83 -0
  295. package/src/shapes/transform.js +357 -0
  296. package/src/shapes/transformable.js +172 -0
  297. package/src/shapes/triangle.js +26 -0
  298. package/src/sound/index.js +17 -0
  299. package/src/sound/sound.js +473 -0
  300. package/src/sound/synth.analyzer.js +149 -0
  301. package/src/sound/synth.effects.js +207 -0
  302. package/src/sound/synth.envelope.js +59 -0
  303. package/src/sound/synth.js +229 -0
  304. package/src/sound/synth.musical.js +160 -0
  305. package/src/sound/synth.noise.js +85 -0
  306. package/src/sound/synth.oscillators.js +293 -0
  307. package/src/state/index.js +10 -0
  308. package/src/state/state-machine.js +371 -0
  309. package/src/util/camera3d.js +438 -0
  310. package/src/util/index.js +6 -0
  311. package/src/util/isometric-camera.js +235 -0
  312. package/src/util/layout.js +317 -0
  313. package/src/util/position.js +147 -0
  314. package/src/util/tasks.js +47 -0
  315. package/src/util/zindex.js +287 -0
  316. package/src/webgl/index.js +9 -0
  317. package/src/webgl/shaders/sphere-shaders.js +994 -0
  318. package/src/webgl/webgl-renderer.js +388 -0
  319. package/tde.png +0 -0
  320. package/test/math/orbital.test.js +61 -0
  321. package/test/math/tensor.test.js +114 -0
  322. package/test/particle/emitter.test.js +204 -0
  323. package/test/particle/particle-system.test.js +310 -0
  324. package/test/particle/particle.test.js +116 -0
  325. package/test/particle/updaters.test.js +386 -0
  326. package/test/setup.js +120 -0
  327. package/test/shapes/euclidian.test.js +44 -0
  328. package/test/shapes/geometry.test.js +86 -0
  329. package/test/shapes/group.test.js +86 -0
  330. package/test/shapes/rectangle.test.js +64 -0
  331. package/test/shapes/transform.test.js +379 -0
  332. package/test/util/camera3d.test.js +428 -0
  333. package/test/util/scene3d.test.js +352 -0
  334. package/types/collision.d.ts +249 -0
  335. package/types/common.d.ts +155 -0
  336. package/types/game.d.ts +497 -0
  337. package/types/index.d.ts +309 -0
  338. package/types/io.d.ts +188 -0
  339. package/types/logger.d.ts +127 -0
  340. package/types/math.d.ts +268 -0
  341. package/types/mixins.d.ts +92 -0
  342. package/types/motion.d.ts +678 -0
  343. package/types/painter.d.ts +378 -0
  344. package/types/shapes.d.ts +864 -0
  345. package/types/sound.d.ts +672 -0
  346. package/types/state.d.ts +251 -0
  347. package/types/util.d.ts +253 -0
  348. package/vite.config.js +50 -0
  349. package/vitest.config.js +13 -0
@@ -0,0 +1,230 @@
1
+ import { Painter } from "./painter";
2
+
3
+ export class PainterEffects {
4
+ /**
5
+ * Set shadow properties
6
+ * @param {string} color - Shadow color
7
+ * @param {number} blur - Shadow blur
8
+ * @param {number} [offsetX=0] - Shadow X offset
9
+ * @param {number} [offsetY=0] - Shadow Y offset
10
+ * @returns {void}
11
+ */
12
+ static dropShadow(color, blur, offsetX = 0, offsetY = 0) {
13
+ Painter.ctx.shadowColor = color;
14
+ Painter.ctx.shadowBlur = blur;
15
+ Painter.ctx.shadowOffsetX = offsetX;
16
+ Painter.ctx.shadowOffsetY = offsetY;
17
+ }
18
+
19
+ /**
20
+ * Clear shadow
21
+ * @returns {void}
22
+ */
23
+ static clearShadow() {
24
+ Painter.ctx.shadowColor = "rgba(0, 0, 0, 0)";
25
+ Painter.ctx.shadowBlur = 0;
26
+ Painter.ctx.shadowOffsetX = 0;
27
+ Painter.ctx.shadowOffsetY = 0;
28
+ }
29
+
30
+ /**
31
+ * Set global alpha
32
+ * @param {number} alpha - Alpha value (0-1)
33
+ * @returns {void}
34
+ */
35
+ static setAlpha(alpha) {
36
+ Painter.ctx.globalAlpha = alpha;
37
+ }
38
+
39
+ /**
40
+ * Set global composite operation
41
+ * @param {GlobalCompositeOperation} operation - Composite operation
42
+ * @returns {void}
43
+ */
44
+ static setBlendMode(operation) {
45
+ Painter.ctx.globalCompositeOperation = operation;
46
+ }
47
+
48
+ /**
49
+ * Clip to a rectangular region
50
+ * @param {number} x - X coordinate
51
+ * @param {number} y - Y coordinate
52
+ * @param {number} width - Width
53
+ * @param {number} height - Height
54
+ * @returns {void}
55
+ */
56
+ static clipRect(x, y, width, height) {
57
+ Painter.ctx.beginPath();
58
+ Painter.ctx.rect(x, y, width, height);
59
+ Painter.ctx.clip();
60
+ }
61
+
62
+ /**
63
+ * Clip to a circular region
64
+ * @param {number} x - Center X
65
+ * @param {number} y - Center Y
66
+ * @param {number} radius - Circle radius
67
+ * @returns {void}
68
+ */
69
+ static clipCircle(x, y, radius) {
70
+ Painter.ctx.beginPath();
71
+ Painter.shapes.arc(x, y, radius, 0, Math.PI * 2);
72
+ Painter.ctx.clip();
73
+ }
74
+
75
+ /**
76
+ * Apply a blur filter to a region
77
+ * @param {number} x - X coordinate
78
+ * @param {number} y - Y coordinate
79
+ * @param {number} width - Width
80
+ * @param {number} height - Height
81
+ * @param {number} blur - Blur amount (pixels)
82
+ * @returns {void}
83
+ */
84
+ static blurRegion(x, y, width, height, blur) {
85
+ // Save current filter
86
+ const currentFilter = Painter.ctx.filter;
87
+
88
+ // Apply blur filter
89
+ Painter.ctx.filter = `blur(${blur}px)`;
90
+
91
+ // Draw the region from the canvas back onto itself with the filter
92
+ const imageData = Painter.ctx.getImageData(x, y, width, height);
93
+ Painter.ctx.putImageData(imageData, x, y);
94
+
95
+ // Restore previous filter
96
+ Painter.ctx.filter = currentFilter;
97
+ }
98
+
99
+
100
+ // Static properties to track current effect state
101
+ static _activeEffects = new Map();
102
+ static _animationId = null;
103
+
104
+ /**
105
+ * Create a glow effect that can be animated
106
+ * @param {string} color - Base color for the glow
107
+ * @param {number} blur - Initial blur amount
108
+ * @param {Object} [options] - Animation options
109
+ * @param {number} [options.pulseSpeed=0] - Speed of pulsing (0 for static)
110
+ * @param {number} [options.pulseMin=blur*0.5] - Minimum blur during pulsing
111
+ * @param {number} [options.pulseMax=blur*1.5] - Maximum blur during pulsing
112
+ * @param {number} [options.colorShift=0] - Hue shift speed (0 for static)
113
+ * @returns {Object} - Control object for the effect
114
+ */
115
+ static createGlow(color, blur, options = {}) {
116
+ const id = 'glow-' + Math.random().toString(36).substr(2, 9);
117
+ const defaultOptions = {
118
+ pulseSpeed: 0,
119
+ pulseMin: blur * 0.5,
120
+ pulseMax: blur * 1.5,
121
+ colorShift: 0
122
+ };
123
+
124
+ const effectOptions = {...defaultOptions, ...options};
125
+
126
+ // Create effect state
127
+ const effectState = {
128
+ id,
129
+ type: 'glow',
130
+ active: true,
131
+ time: 0,
132
+ color,
133
+ blur,
134
+ options: effectOptions,
135
+
136
+ // Method to update effect parameters
137
+ update(params) {
138
+ Object.assign(this, params);
139
+ return this;
140
+ },
141
+
142
+ // Method to stop the effect
143
+ stop() {
144
+ this.active = false;
145
+ PainterEffects._activeEffects.delete(this.id);
146
+ return this;
147
+ },
148
+
149
+ // Method to apply the effect for the current frame
150
+ apply() {
151
+ if (!this.active) return;
152
+
153
+ let currentBlur = this.blur;
154
+ let currentColor = this.color;
155
+
156
+ // Apply animation if enabled
157
+ if (this.options.pulseSpeed > 0) {
158
+ const pulseFactor = Math.sin(this.time * this.options.pulseSpeed) * 0.5 + 0.5;
159
+ currentBlur = this.options.pulseMin +
160
+ pulseFactor * (this.options.pulseMax - this.options.pulseMin);
161
+ }
162
+
163
+ if (this.options.colorShift > 0) {
164
+ // Apply color shift - would need color parsing and HSL conversion
165
+ // This is a simplified placeholder
166
+ currentColor = currentColor.replace('hue',
167
+ (this.time * this.options.colorShift) % 360);
168
+ }
169
+
170
+ // Apply the actual glow effect
171
+ Painter.ctx.shadowColor = currentColor;
172
+ Painter.ctx.shadowBlur = currentBlur;
173
+ Painter.ctx.shadowOffsetX = 0;
174
+ Painter.ctx.shadowOffsetY = 0;
175
+
176
+ this.time += 1/60; // Assuming 60fps
177
+ return this;
178
+ }
179
+ };
180
+
181
+ // Store and start tracking the effect
182
+ PainterEffects._activeEffects.set(id, effectState);
183
+ PainterEffects._startAnimationLoop();
184
+
185
+ return effectState;
186
+ }
187
+
188
+ /**
189
+ * Start the animation loop if not already running
190
+ * @private
191
+ */
192
+ static _startAnimationLoop() {
193
+ if (PainterEffects._animationId !== null) return;
194
+
195
+ const animate = () => {
196
+ // Apply all active effects
197
+ PainterEffects._activeEffects.forEach(effect => {
198
+ if (effect.active) effect.apply();
199
+ });
200
+
201
+ // Stop the loop if no active effects
202
+ if (PainterEffects._activeEffects.size === 0) {
203
+ cancelAnimationFrame(PainterEffects._animationId);
204
+ PainterEffects._animationId = null;
205
+ return;
206
+ }
207
+
208
+ PainterEffects._animationId = requestAnimationFrame(animate);
209
+ };
210
+
211
+ PainterEffects._animationId = requestAnimationFrame(animate);
212
+ }
213
+
214
+ /**
215
+ * Clear all active effects
216
+ */
217
+ static clearAllEffects() {
218
+ PainterEffects._activeEffects.forEach(effect => effect.stop());
219
+ PainterEffects._activeEffects.clear();
220
+
221
+ // Reset canvas context properties
222
+ Painter.ctx.shadowColor = "rgba(0, 0, 0, 0)";
223
+ Painter.ctx.shadowBlur = 0;
224
+ Painter.ctx.shadowOffsetX = 0;
225
+ Painter.ctx.shadowOffsetY = 0;
226
+ Painter.ctx.filter = "none";
227
+ Painter.ctx.globalAlpha = 1;
228
+ Painter.ctx.globalCompositeOperation = "source-over";
229
+ }
230
+ }
@@ -0,0 +1,229 @@
1
+ // ---------------------------------------------------------------------------
2
+ // src/painter-images.js
3
+ // Centralised helpers for Image, ImageData and bitmap work.
4
+ // ---------------------------------------------------------------------------
5
+ import { Painter } from "./painter";
6
+
7
+ /**
8
+ * Painter.img – all Canvas 2D image utilities in one names‑space.
9
+ * Relies on a live Painter.ctx that points at the active canvas context.
10
+ */
11
+ export class PainterImages {
12
+ /* ─────────────────────────────── DRAWING ────────────────────────────── */
13
+
14
+ /**
15
+ * Draw an image or canvas onto the current context.
16
+ * @param {CanvasImageSource} source <img>, <canvas>, <video>, etc.
17
+ * @param {number} x, y Destination position (after transforms)
18
+ * @param {object} [opts] Declarative options
19
+ * @prop {number} width,height Destination size (defaults → intrinsic)
20
+ * @prop {object} crop { sx, sy, sw, sh } source rectangle
21
+ * @prop {string} anchor top‑left | center | bottom‑right …
22
+ * @prop {number} rotation Radians, about anchor point
23
+ * @prop {number} scaleX,scaleY Independent or uniform scaling
24
+ * @prop {boolean} flipX,flipY Mirror horizontally / vertically
25
+ * @prop {number} alpha Multiplicative opacity (1 = opaque)
26
+ * @prop {boolean} smoothing ImageSmoothingEnabled toggle
27
+ */
28
+ static draw(
29
+ source,
30
+ x = 0,
31
+ y = 0,
32
+ {
33
+ width,
34
+ height,
35
+ crop = null,
36
+ anchor = "top‑left",
37
+ rotation = 0,
38
+ scaleX = 1,
39
+ scaleY = 1,
40
+ flipX = false,
41
+ flipY = false,
42
+ alpha = 1,
43
+ smoothing = true,
44
+ } = {}
45
+ ) {
46
+ const ctx = Painter.ctx;
47
+ if (!ctx || !source) return;
48
+
49
+ const iw = width ?? (crop ? crop.sw : source.width ?? source.videoWidth);
50
+ const ih = height ?? (crop ? crop.sh : source.height ?? source.videoHeight);
51
+
52
+ const ax = { left: 0, center: 0.5, right: 1 }[anchor.split("-").pop()] ?? 0;
53
+ const ay = { top: 0, center: 0.5, bottom: 1 }[anchor.split("-")[0]] ?? 0;
54
+ const ox = -iw * ax;
55
+ const oy = -ih * ay;
56
+
57
+ ctx.save();
58
+ ctx.imageSmoothingEnabled = smoothing;
59
+ ctx.globalAlpha *= alpha;
60
+ ctx.translate(x, y);
61
+ if (rotation) ctx.rotate(rotation);
62
+ if (flipX || flipY) ctx.scale(flipX ? -1 : 1, flipY ? -1 : 1);
63
+ ctx.scale(scaleX, scaleY);
64
+
65
+ if (crop) {
66
+ const { sx, sy, sw, sh } = crop;
67
+ ctx.drawImage(source, sx, sy, sw, sh, ox, oy, iw, ih);
68
+ } else {
69
+ ctx.drawImage(source, ox, oy, iw, ih);
70
+ }
71
+ ctx.restore();
72
+ }
73
+
74
+ /** Quick alias for draw that only needs `{width,height}` overrides. */
75
+ static blit(source, x, y, w, h) {
76
+ this.draw(source, x, y, { width: w, height: h });
77
+ }
78
+
79
+ /* ───────────────────────────── PATTERNS ─────────────────────────────── */
80
+
81
+ /** `ctx.createPattern` shortcut. */
82
+ static createPattern(image, repetition = "repeat") {
83
+ return Painter.ctx.createPattern(image, repetition);
84
+ }
85
+
86
+ /**
87
+ * Fill a rectangle with a pattern in one line.
88
+ */
89
+ static fillPattern(image, x, y, width, height) {
90
+ const ctx = Painter.ctx;
91
+ ctx.save();
92
+ ctx.fillStyle = image;
93
+ ctx.fillRect(x, y, width, height);
94
+ ctx.restore();
95
+ }
96
+
97
+ /* ─────────────────────────── IMAGE DATA API ─────────────────────────── */
98
+
99
+ static createImageData(width, height) {
100
+ return Painter.ctx.createImageData(width, height);
101
+ }
102
+
103
+ static cloneImageData(imageData) {
104
+ return new ImageData(
105
+ new Uint8ClampedArray(imageData.data),
106
+ imageData.width,
107
+ imageData.height
108
+ );
109
+ }
110
+
111
+ static getImageData(x, y, width, height) {
112
+ return Painter.ctx.getImageData(x, y, width, height);
113
+ }
114
+
115
+ static putImageData(
116
+ imageData,
117
+ x,
118
+ y,
119
+ dirtyX = 0,
120
+ dirtyY = 0,
121
+ dirtyWidth = imageData.width,
122
+ dirtyHeight = imageData.height
123
+ ) {
124
+ Painter.ctx.putImageData(
125
+ imageData,
126
+ x,
127
+ y,
128
+ dirtyX,
129
+ dirtyY,
130
+ dirtyWidth,
131
+ dirtyHeight
132
+ );
133
+ }
134
+
135
+ /**
136
+ * Map‑style pixel transform: pass a callback that returns [r,g,b,a].
137
+ * Very handy for simple filters without manual loop boilerplate.
138
+ */
139
+ static mapPixels(imageData, fn) {
140
+ const d = imageData.data;
141
+ for (let i = 0; i < d.length; i += 4) {
142
+ const idx = i >> 2;
143
+ const res = fn(d[i], d[i + 1], d[i + 2], d[i + 3], idx);
144
+ if (res) [d[i], d[i + 1], d[i + 2], d[i + 3]] = res;
145
+ }
146
+ return imageData;
147
+ }
148
+
149
+ /** Set one pixel in an ImageData object. */
150
+ static setPixel(imageData, x, y, r, g, b, a = 255) {
151
+ const i = (y * imageData.width + x) * 4;
152
+ const d = imageData.data;
153
+ d[i] = r;
154
+ d[i + 1] = g;
155
+ d[i + 2] = b;
156
+ d[i + 3] = a;
157
+ }
158
+
159
+ /* ───────────────────────────── BITMAP HELPERS ───────────────────────── */
160
+
161
+ /** Convert the **current canvas** to an ImageBitmap (cheap GPU upload). */
162
+ static async toBitmap({ type = "image/png", quality = 0.92 } = {}) {
163
+ const canvas = Painter.ctx.canvas;
164
+ const blob = await canvas.convertToBlob({ type, quality });
165
+ return createImageBitmap(blob);
166
+ }
167
+
168
+ /** Create an ImageBitmap from any CanvasImageSource. */
169
+ static async createBitmap(image) {
170
+ return createImageBitmap(image);
171
+ }
172
+
173
+ /* ───────────── ImageData ← raw pixels convenience ───────────── */
174
+
175
+ /**
176
+ * Converts a flat RGBA array into an ImageData object.
177
+ * @param {Uint8ClampedArray} rgbaArray
178
+ * @param {number} width
179
+ * @param {number} height
180
+ * @returns {ImageData}
181
+ */
182
+ static toImageData(rgbaArray, width, height) {
183
+ if (rgbaArray.length !== width * height * 4) {
184
+ throw new Error("Invalid RGBA array size for given dimensions");
185
+ }
186
+ return new ImageData(rgbaArray, width, height);
187
+ }
188
+
189
+ /**
190
+ * Asynchronously creates an ImageBitmap from raw RGBA data.
191
+ * Can be used directly with createPattern or drawImage.
192
+ * @param {Uint8ClampedArray} rgbaArray
193
+ * @param {number} width
194
+ * @param {number} height
195
+ * @returns {Promise<ImageBitmap>}
196
+ */
197
+ static async createImageBitmapFromPixels(rgbaArray, width, height) {
198
+ const imgData = this.toImageData(rgbaArray, width, height);
199
+ return await createImageBitmap(imgData);
200
+ }
201
+
202
+ /**
203
+ * Creates a pattern from ImageData via a temporary canvas.
204
+ * @param {ImageData} imageData
205
+ * @param {"repeat"|"repeat-x"|"repeat-y"|"no-repeat"} repeat
206
+ * @returns {CanvasPattern}
207
+ */
208
+ static createPatternFromImageData(imageData, repeat = "repeat") {
209
+ const canvas = document.createElement("canvas");
210
+ canvas.width = imageData.width;
211
+ canvas.height = imageData.height;
212
+ const ctx = canvas.getContext("2d");
213
+ ctx.putImageData(imageData, 0, 0);
214
+ return ctx.createPattern(canvas, repeat);
215
+ }
216
+
217
+ /**
218
+ * Shortcut to go directly from pixel array to CanvasPattern
219
+ * @param {Uint8ClampedArray} rgbaArray
220
+ * @param {number} width
221
+ * @param {number} height
222
+ * @param {string} repeat
223
+ * @returns {CanvasPattern}
224
+ */
225
+ static createPatternFromPixels(rgbaArray, width, height, repeat = "repeat") {
226
+ const imgData = this.toImageData(rgbaArray, width, height);
227
+ return this.createPatternFromImageData(imgData, repeat);
228
+ }
229
+ }