apexify.js 4.9.30 → 5.0.1

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 (129) hide show
  1. package/CHANGELOG.md +137 -0
  2. package/README.md +119 -4
  3. package/apex-banner.png +0 -0
  4. package/dist/cjs/Canvas/ApexPainter.d.ts +158 -145
  5. package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
  6. package/dist/cjs/Canvas/ApexPainter.js +1443 -418
  7. package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
  8. package/dist/cjs/Canvas/utils/Charts/charts.d.ts +7 -2
  9. package/dist/cjs/Canvas/utils/Charts/charts.d.ts.map +1 -1
  10. package/dist/cjs/Canvas/utils/Charts/charts.js +3 -1
  11. package/dist/cjs/Canvas/utils/Charts/charts.js.map +1 -1
  12. package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts +75 -0
  13. package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
  14. package/dist/cjs/Canvas/utils/Custom/advancedLines.js +263 -0
  15. package/dist/cjs/Canvas/utils/Custom/advancedLines.js.map +1 -0
  16. package/dist/cjs/Canvas/utils/Custom/customLines.d.ts +2 -1
  17. package/dist/cjs/Canvas/utils/Custom/customLines.d.ts.map +1 -1
  18. package/dist/cjs/Canvas/utils/Custom/customLines.js +73 -6
  19. package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -1
  20. package/dist/cjs/Canvas/utils/General/batchOperations.d.ts +17 -0
  21. package/dist/cjs/Canvas/utils/General/batchOperations.d.ts.map +1 -0
  22. package/dist/cjs/Canvas/utils/General/batchOperations.js +88 -0
  23. package/dist/cjs/Canvas/utils/General/batchOperations.js.map +1 -0
  24. package/dist/cjs/Canvas/utils/General/general functions.d.ts +25 -3
  25. package/dist/cjs/Canvas/utils/General/general functions.d.ts.map +1 -1
  26. package/dist/cjs/Canvas/utils/General/general functions.js +37 -9
  27. package/dist/cjs/Canvas/utils/General/general functions.js.map +1 -1
  28. package/dist/cjs/Canvas/utils/General/imageCompression.d.ts +19 -0
  29. package/dist/cjs/Canvas/utils/General/imageCompression.d.ts.map +1 -0
  30. package/dist/cjs/Canvas/utils/General/imageCompression.js +262 -0
  31. package/dist/cjs/Canvas/utils/General/imageCompression.js.map +1 -0
  32. package/dist/cjs/Canvas/utils/General/imageStitching.d.ts +20 -0
  33. package/dist/cjs/Canvas/utils/General/imageStitching.d.ts.map +1 -0
  34. package/dist/cjs/Canvas/utils/General/imageStitching.js +227 -0
  35. package/dist/cjs/Canvas/utils/General/imageStitching.js.map +1 -0
  36. package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts +37 -0
  37. package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
  38. package/dist/cjs/Canvas/utils/Image/imageEffects.js +128 -0
  39. package/dist/cjs/Canvas/utils/Image/imageEffects.js.map +1 -0
  40. package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts +67 -0
  41. package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
  42. package/dist/cjs/Canvas/utils/Image/imageMasking.js +276 -0
  43. package/dist/cjs/Canvas/utils/Image/imageMasking.js.map +1 -0
  44. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
  45. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
  46. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
  47. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
  48. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
  49. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js +233 -0
  50. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
  51. package/dist/cjs/Canvas/utils/types.d.ts +153 -0
  52. package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
  53. package/dist/cjs/Canvas/utils/types.js.map +1 -1
  54. package/dist/cjs/Canvas/utils/utils.d.ts +9 -2
  55. package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
  56. package/dist/cjs/Canvas/utils/utils.js +32 -1
  57. package/dist/cjs/Canvas/utils/utils.js.map +1 -1
  58. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  59. package/dist/esm/Canvas/ApexPainter.d.ts +158 -145
  60. package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
  61. package/dist/esm/Canvas/ApexPainter.js +1443 -418
  62. package/dist/esm/Canvas/ApexPainter.js.map +1 -1
  63. package/dist/esm/Canvas/utils/Charts/charts.d.ts +7 -2
  64. package/dist/esm/Canvas/utils/Charts/charts.d.ts.map +1 -1
  65. package/dist/esm/Canvas/utils/Charts/charts.js +3 -1
  66. package/dist/esm/Canvas/utils/Charts/charts.js.map +1 -1
  67. package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts +75 -0
  68. package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
  69. package/dist/esm/Canvas/utils/Custom/advancedLines.js +263 -0
  70. package/dist/esm/Canvas/utils/Custom/advancedLines.js.map +1 -0
  71. package/dist/esm/Canvas/utils/Custom/customLines.d.ts +2 -1
  72. package/dist/esm/Canvas/utils/Custom/customLines.d.ts.map +1 -1
  73. package/dist/esm/Canvas/utils/Custom/customLines.js +73 -6
  74. package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -1
  75. package/dist/esm/Canvas/utils/General/batchOperations.d.ts +17 -0
  76. package/dist/esm/Canvas/utils/General/batchOperations.d.ts.map +1 -0
  77. package/dist/esm/Canvas/utils/General/batchOperations.js +88 -0
  78. package/dist/esm/Canvas/utils/General/batchOperations.js.map +1 -0
  79. package/dist/esm/Canvas/utils/General/general functions.d.ts +25 -3
  80. package/dist/esm/Canvas/utils/General/general functions.d.ts.map +1 -1
  81. package/dist/esm/Canvas/utils/General/general functions.js +37 -9
  82. package/dist/esm/Canvas/utils/General/general functions.js.map +1 -1
  83. package/dist/esm/Canvas/utils/General/imageCompression.d.ts +19 -0
  84. package/dist/esm/Canvas/utils/General/imageCompression.d.ts.map +1 -0
  85. package/dist/esm/Canvas/utils/General/imageCompression.js +262 -0
  86. package/dist/esm/Canvas/utils/General/imageCompression.js.map +1 -0
  87. package/dist/esm/Canvas/utils/General/imageStitching.d.ts +20 -0
  88. package/dist/esm/Canvas/utils/General/imageStitching.d.ts.map +1 -0
  89. package/dist/esm/Canvas/utils/General/imageStitching.js +227 -0
  90. package/dist/esm/Canvas/utils/General/imageStitching.js.map +1 -0
  91. package/dist/esm/Canvas/utils/Image/imageEffects.d.ts +37 -0
  92. package/dist/esm/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
  93. package/dist/esm/Canvas/utils/Image/imageEffects.js +128 -0
  94. package/dist/esm/Canvas/utils/Image/imageEffects.js.map +1 -0
  95. package/dist/esm/Canvas/utils/Image/imageMasking.d.ts +67 -0
  96. package/dist/esm/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
  97. package/dist/esm/Canvas/utils/Image/imageMasking.js +276 -0
  98. package/dist/esm/Canvas/utils/Image/imageMasking.js.map +1 -0
  99. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
  100. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
  101. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
  102. package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
  103. package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
  104. package/dist/esm/Canvas/utils/Texts/textPathRenderer.js +233 -0
  105. package/dist/esm/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
  106. package/dist/esm/Canvas/utils/types.d.ts +153 -0
  107. package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
  108. package/dist/esm/Canvas/utils/types.js.map +1 -1
  109. package/dist/esm/Canvas/utils/utils.d.ts +9 -2
  110. package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
  111. package/dist/esm/Canvas/utils/utils.js +32 -1
  112. package/dist/esm/Canvas/utils/utils.js.map +1 -1
  113. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  114. package/lib/Canvas/ApexPainter.ts +1327 -266
  115. package/lib/Canvas/utils/Charts/charts.ts +16 -7
  116. package/lib/Canvas/utils/Custom/advancedLines.ts +335 -0
  117. package/lib/Canvas/utils/Custom/customLines.ts +84 -9
  118. package/lib/Canvas/utils/General/batchOperations.ts +103 -0
  119. package/lib/Canvas/utils/General/general functions.ts +85 -41
  120. package/lib/Canvas/utils/General/imageCompression.ts +316 -0
  121. package/lib/Canvas/utils/General/imageStitching.ts +252 -0
  122. package/lib/Canvas/utils/Image/imageEffects.ts +175 -0
  123. package/lib/Canvas/utils/Image/imageMasking.ts +335 -0
  124. package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +455 -444
  125. package/lib/Canvas/utils/Texts/textPathRenderer.ts +320 -0
  126. package/lib/Canvas/utils/types.ts +156 -0
  127. package/lib/Canvas/utils/utils.ts +52 -2
  128. package/package.json +75 -35
  129. package/types/imgur.d.ts +65 -0
@@ -0,0 +1,252 @@
1
+ import { createCanvas, loadImage, SKRSContext2D, Image } from '@napi-rs/canvas';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import { StitchOptions, CollageLayout } from '../types';
5
+
6
+ /**
7
+ * Stitches multiple images together
8
+ * @param images - Array of image sources
9
+ * @param options - Stitching options
10
+ * @returns Stitched image buffer
11
+ */
12
+ export async function stitchImages(
13
+ images: Array<string | Buffer>,
14
+ options: StitchOptions = {}
15
+ ): Promise<Buffer> {
16
+ if (!images || images.length === 0) {
17
+ throw new Error('stitchImages: images array is required');
18
+ }
19
+
20
+ const {
21
+ direction = 'horizontal',
22
+ overlap = 0,
23
+ blend = false,
24
+ spacing = 0
25
+ } = options;
26
+
27
+ // Load all images
28
+ const loadedImages: Image[] = [];
29
+ for (const imgSource of images) {
30
+ let img: Image;
31
+ if (Buffer.isBuffer(imgSource)) {
32
+ img = await loadImage(imgSource);
33
+ } else if (imgSource.startsWith('http')) {
34
+ img = await loadImage(imgSource);
35
+ } else {
36
+ const imgPath = path.join(process.cwd(), imgSource);
37
+ img = await loadImage(fs.readFileSync(imgPath));
38
+ }
39
+ loadedImages.push(img);
40
+ }
41
+
42
+ if (loadedImages.length === 0) {
43
+ throw new Error('stitchImages: No valid images loaded');
44
+ }
45
+
46
+ // Calculate canvas dimensions
47
+ let canvasWidth = 0;
48
+ let canvasHeight = 0;
49
+ let maxWidth = 0;
50
+ let maxHeight = 0;
51
+
52
+ for (const img of loadedImages) {
53
+ maxWidth = Math.max(maxWidth, img.width);
54
+ maxHeight = Math.max(maxHeight, img.height);
55
+ }
56
+
57
+ if (direction === 'horizontal') {
58
+ canvasWidth = loadedImages.reduce((sum, img) => sum + img.width, 0);
59
+ canvasWidth -= overlap * (loadedImages.length - 1);
60
+ canvasWidth += spacing * (loadedImages.length - 1);
61
+ canvasHeight = maxHeight;
62
+ } else if (direction === 'vertical') {
63
+ canvasWidth = maxWidth;
64
+ canvasHeight = loadedImages.reduce((sum, img) => sum + img.height, 0);
65
+ canvasHeight -= overlap * (loadedImages.length - 1);
66
+ canvasHeight += spacing * (loadedImages.length - 1);
67
+ } else if (direction === 'grid') {
68
+ const cols = Math.ceil(Math.sqrt(loadedImages.length));
69
+ const rows = Math.ceil(loadedImages.length / cols);
70
+ canvasWidth = maxWidth * cols + spacing * (cols - 1);
71
+ canvasHeight = maxHeight * rows + spacing * (rows - 1);
72
+ }
73
+
74
+ // Create canvas
75
+ const canvas = createCanvas(canvasWidth, canvasHeight);
76
+ const ctx = canvas.getContext('2d') as SKRSContext2D;
77
+ if (!ctx) throw new Error("Unable to get 2D context");
78
+
79
+ // Draw images
80
+ let currentX = 0;
81
+ let currentY = 0;
82
+
83
+ for (let i = 0; i < loadedImages.length; i++) {
84
+ const img = loadedImages[i];
85
+
86
+ if (direction === 'horizontal') {
87
+ if (i > 0) {
88
+ currentX -= overlap;
89
+ currentX += spacing;
90
+ }
91
+ ctx.drawImage(img, currentX, 0, img.width, img.height);
92
+ currentX += img.width;
93
+ } else if (direction === 'vertical') {
94
+ if (i > 0) {
95
+ currentY -= overlap;
96
+ currentY += spacing;
97
+ }
98
+ ctx.drawImage(img, 0, currentY, img.width, img.height);
99
+ currentY += img.height;
100
+ } else if (direction === 'grid') {
101
+ const cols = Math.ceil(Math.sqrt(loadedImages.length));
102
+ const col = i % cols;
103
+ const row = Math.floor(i / cols);
104
+ const x = col * (maxWidth + spacing);
105
+ const y = row * (maxHeight + spacing);
106
+ ctx.drawImage(img, x, y, img.width, img.height);
107
+ }
108
+
109
+ // Apply blending if enabled and not first image
110
+ if (blend && i > 0 && overlap > 0) {
111
+ ctx.globalCompositeOperation = 'multiply';
112
+ ctx.globalAlpha = 0.5;
113
+ if (direction === 'horizontal') {
114
+ ctx.drawImage(img, currentX - img.width - spacing + overlap, 0, img.width, img.height);
115
+ } else if (direction === 'vertical') {
116
+ ctx.drawImage(img, 0, currentY - img.height - spacing + overlap, img.width, img.height);
117
+ }
118
+ ctx.globalAlpha = 1;
119
+ ctx.globalCompositeOperation = 'source-over';
120
+ }
121
+ }
122
+
123
+ return canvas.toBuffer('image/png');
124
+ }
125
+
126
+ /**
127
+ * Creates an image collage
128
+ * @param images - Array of image sources with optional dimensions
129
+ * @param layout - Collage layout configuration
130
+ * @returns Collage image buffer
131
+ */
132
+ export async function createCollage(
133
+ images: Array<{ source: string | Buffer; width?: number; height?: number }>,
134
+ layout: CollageLayout
135
+ ): Promise<Buffer> {
136
+ if (!images || images.length === 0) {
137
+ throw new Error('createCollage: images array is required');
138
+ }
139
+
140
+ const {
141
+ type = 'grid',
142
+ columns = 3,
143
+ rows = 3,
144
+ spacing = 10,
145
+ background = '#ffffff',
146
+ borderRadius = 0
147
+ } = layout;
148
+
149
+ // Load all images
150
+ const loadedImages: Array<{ image: Image; width: number; height: number }> = [];
151
+ for (const imgConfig of images) {
152
+ let img: Image;
153
+ if (Buffer.isBuffer(imgConfig.source)) {
154
+ img = await loadImage(imgConfig.source);
155
+ } else if (typeof imgConfig.source === 'string' && imgConfig.source.startsWith('http')) {
156
+ img = await loadImage(imgConfig.source);
157
+ } else {
158
+ const imgPath = path.join(process.cwd(), imgConfig.source as string);
159
+ img = await loadImage(fs.readFileSync(imgPath));
160
+ }
161
+
162
+ loadedImages.push({
163
+ image: img,
164
+ width: imgConfig.width || img.width,
165
+ height: imgConfig.height || img.height
166
+ });
167
+ }
168
+
169
+ // Calculate canvas dimensions
170
+ let canvasWidth = 0;
171
+ let canvasHeight = 0;
172
+
173
+ if (type === 'grid') {
174
+ const cellWidth = Math.max(...loadedImages.map(img => img.width));
175
+ const cellHeight = Math.max(...loadedImages.map(img => img.height));
176
+ canvasWidth = cellWidth * columns + spacing * (columns - 1);
177
+ canvasHeight = cellHeight * rows + spacing * (rows - 1);
178
+ } else if (type === 'masonry') {
179
+ // Masonry layout - columns with varying heights
180
+ const colWidths: number[] = new Array(columns).fill(0);
181
+ const colHeights: number[] = new Array(columns).fill(0);
182
+
183
+ for (let i = 0; i < loadedImages.length; i++) {
184
+ const col = i % columns;
185
+ colWidths[col] = Math.max(colWidths[col], loadedImages[i].width);
186
+ colHeights[col] += loadedImages[i].height + (i >= columns ? spacing : 0);
187
+ }
188
+
189
+ canvasWidth = Math.max(...colWidths) * columns + spacing * (columns - 1);
190
+ canvasHeight = Math.max(...colHeights);
191
+ } else if (type === 'carousel') {
192
+ // Horizontal carousel
193
+ canvasWidth = loadedImages.reduce((sum, img) => sum + img.width, 0) + spacing * (loadedImages.length - 1);
194
+ canvasHeight = Math.max(...loadedImages.map(img => img.height));
195
+ } else {
196
+ // Custom - use provided dimensions or calculate
197
+ canvasWidth = 800;
198
+ canvasHeight = 600;
199
+ }
200
+
201
+ // Create canvas
202
+ const canvas = createCanvas(canvasWidth, canvasHeight);
203
+ const ctx = canvas.getContext('2d') as SKRSContext2D;
204
+ if (!ctx) throw new Error("Unable to get 2D context");
205
+
206
+ // Draw background
207
+ ctx.fillStyle = background;
208
+ ctx.fillRect(0, 0, canvasWidth, canvasHeight);
209
+
210
+ // Draw images
211
+ let currentX = 0;
212
+ let currentY = 0;
213
+ const colHeights: number[] = new Array(columns).fill(0);
214
+
215
+ for (let i = 0; i < loadedImages.length; i++) {
216
+ const imgData = loadedImages[i];
217
+
218
+ if (type === 'grid') {
219
+ const col = i % columns;
220
+ const row = Math.floor(i / columns);
221
+ const cellWidth = Math.max(...loadedImages.map(img => img.width));
222
+ const cellHeight = Math.max(...loadedImages.map(img => img.height));
223
+ currentX = col * (cellWidth + spacing);
224
+ currentY = row * (cellHeight + spacing);
225
+ } else if (type === 'masonry') {
226
+ const col = i % columns;
227
+ currentX = col * (Math.max(...loadedImages.map(img => img.width)) + spacing);
228
+ currentY = colHeights[col];
229
+ colHeights[col] += imgData.height + spacing;
230
+ } else if (type === 'carousel') {
231
+ if (i > 0) currentX += spacing;
232
+ currentY = (canvasHeight - imgData.height) / 2;
233
+ }
234
+
235
+ // Apply border radius if specified
236
+ if (borderRadius > 0) {
237
+ ctx.save();
238
+ ctx.beginPath();
239
+ ctx.roundRect(currentX, currentY, imgData.width, imgData.height, borderRadius);
240
+ ctx.clip();
241
+ }
242
+
243
+ ctx.drawImage(imgData.image, currentX, currentY, imgData.width, imgData.height);
244
+
245
+ if (borderRadius > 0) {
246
+ ctx.restore();
247
+ }
248
+ }
249
+
250
+ return canvas.toBuffer('image/png');
251
+ }
252
+
@@ -0,0 +1,175 @@
1
+ import { SKRSContext2D, Image } from '@napi-rs/canvas';
2
+
3
+ /**
4
+ * Applies vignette effect to the canvas
5
+ * @param ctx - Canvas 2D context
6
+ * @param intensity - Vignette intensity (0-1)
7
+ * @param size - Vignette size (0-1, where 1 = full canvas)
8
+ * @param width - Canvas width
9
+ * @param height - Canvas height
10
+ */
11
+ export function applyVignette(
12
+ ctx: SKRSContext2D,
13
+ intensity: number,
14
+ size: number,
15
+ width: number,
16
+ height: number
17
+ ): void {
18
+ const imageData = ctx.getImageData(0, 0, width, height);
19
+ const pixels = imageData.data;
20
+ const centerX = width / 2;
21
+ const centerY = height / 2;
22
+ const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
23
+ const vignetteRadius = maxDistance * size;
24
+
25
+ for (let i = 0; i < pixels.length; i += 4) {
26
+ const x = (i / 4) % width;
27
+ const y = Math.floor((i / 4) / width);
28
+ const dx = x - centerX;
29
+ const dy = y - centerY;
30
+ const distance = Math.sqrt(dx * dx + dy * dy);
31
+
32
+ if (distance > vignetteRadius) {
33
+ const vignetteAmount = Math.min(1, (distance - vignetteRadius) / (maxDistance - vignetteRadius));
34
+ const darken = 1 - (vignetteAmount * intensity);
35
+
36
+ pixels[i] = Math.round(pixels[i] * darken); // R
37
+ pixels[i + 1] = Math.round(pixels[i + 1] * darken); // G
38
+ pixels[i + 2] = Math.round(pixels[i + 2] * darken); // B
39
+ }
40
+ }
41
+
42
+ ctx.putImageData(imageData, 0, 0);
43
+ }
44
+
45
+ /**
46
+ * Applies lens flare effect to the canvas
47
+ * @param ctx - Canvas 2D context
48
+ * @param flareX - Flare center X position
49
+ * @param flareY - Flare center Y position
50
+ * @param intensity - Flare intensity (0-1)
51
+ * @param width - Canvas width
52
+ * @param height - Canvas height
53
+ */
54
+ export function applyLensFlare(
55
+ ctx: SKRSContext2D,
56
+ flareX: number,
57
+ flareY: number,
58
+ intensity: number,
59
+ width: number,
60
+ height: number
61
+ ): void {
62
+ ctx.save();
63
+
64
+ // Create gradient for lens flare
65
+ const gradient = ctx.createRadialGradient(
66
+ flareX, flareY, 0,
67
+ flareX, flareY, Math.max(width, height) * 0.5
68
+ );
69
+
70
+ const flareColor = `rgba(255, 255, 255, ${intensity * 0.6})`;
71
+ gradient.addColorStop(0, flareColor);
72
+ gradient.addColorStop(0.3, `rgba(255, 255, 255, ${intensity * 0.3})`);
73
+ gradient.addColorStop(0.6, `rgba(255, 255, 200, ${intensity * 0.1})`);
74
+ gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
75
+
76
+ ctx.globalCompositeOperation = 'screen';
77
+ ctx.fillStyle = gradient;
78
+ ctx.fillRect(0, 0, width, height);
79
+
80
+ // Add additional flare elements
81
+ const flareElements = [
82
+ { x: flareX * 0.7, y: flareY * 0.7, size: 30, opacity: intensity * 0.4 },
83
+ { x: flareX * 1.3, y: flareY * 1.1, size: 20, opacity: intensity * 0.3 },
84
+ { x: flareX * 0.9, y: flareY * 1.2, size: 15, opacity: intensity * 0.2 }
85
+ ];
86
+
87
+ for (const element of flareElements) {
88
+ if (element.x >= 0 && element.x < width && element.y >= 0 && element.y < height) {
89
+ const elementGradient = ctx.createRadialGradient(
90
+ element.x, element.y, 0,
91
+ element.x, element.y, element.size
92
+ );
93
+ elementGradient.addColorStop(0, `rgba(255, 255, 255, ${element.opacity})`);
94
+ elementGradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
95
+ ctx.fillStyle = elementGradient;
96
+ ctx.fillRect(element.x - element.size, element.y - element.size, element.size * 2, element.size * 2);
97
+ }
98
+ }
99
+
100
+ ctx.restore();
101
+ }
102
+
103
+ /**
104
+ * Applies chromatic aberration effect
105
+ * @param ctx - Canvas 2D context
106
+ * @param intensity - Aberration intensity (0-1)
107
+ * @param width - Canvas width
108
+ * @param height - Canvas height
109
+ */
110
+ export function applyChromaticAberration(
111
+ ctx: SKRSContext2D,
112
+ intensity: number,
113
+ width: number,
114
+ height: number
115
+ ): void {
116
+ const imageData = ctx.getImageData(0, 0, width, height);
117
+ const pixels = imageData.data;
118
+ const newPixels = new Uint8ClampedArray(pixels.length);
119
+ const offset = Math.round(intensity * 5); // Max 5 pixel offset
120
+
121
+ for (let y = 0; y < height; y++) {
122
+ for (let x = 0; x < width; x++) {
123
+ const idx = (y * width + x) * 4;
124
+
125
+ // Red channel - shift left
126
+ const redX = Math.max(0, Math.min(width - 1, x - offset));
127
+ const redIdx = (y * width + redX) * 4;
128
+ newPixels[idx] = pixels[redIdx];
129
+
130
+ // Green channel - no shift
131
+ newPixels[idx + 1] = pixels[idx + 1];
132
+
133
+ // Blue channel - shift right
134
+ const blueX = Math.max(0, Math.min(width - 1, x + offset));
135
+ const blueIdx = (y * width + blueX) * 4;
136
+ newPixels[idx + 2] = pixels[blueIdx];
137
+
138
+ // Alpha channel - no change
139
+ newPixels[idx + 3] = pixels[idx + 3];
140
+ }
141
+ }
142
+
143
+ ctx.putImageData(new ImageData(newPixels, width, height), 0, 0);
144
+ }
145
+
146
+ /**
147
+ * Applies film grain effect
148
+ * @param ctx - Canvas 2D context
149
+ * @param intensity - Grain intensity (0-1)
150
+ * @param width - Canvas width
151
+ * @param height - Canvas height
152
+ */
153
+ export function applyFilmGrain(
154
+ ctx: SKRSContext2D,
155
+ intensity: number,
156
+ width: number,
157
+ height: number
158
+ ): void {
159
+ const imageData = ctx.getImageData(0, 0, width, height);
160
+ const pixels = imageData.data;
161
+ const grainAmount = intensity * 30; // Max 30 pixel variation
162
+
163
+ for (let i = 0; i < pixels.length; i += 4) {
164
+ // Generate random grain
165
+ const grain = (Math.random() - 0.5) * grainAmount;
166
+
167
+ pixels[i] = Math.max(0, Math.min(255, pixels[i] + grain)); // R
168
+ pixels[i + 1] = Math.max(0, Math.min(255, pixels[i + 1] + grain)); // G
169
+ pixels[i + 2] = Math.max(0, Math.min(255, pixels[i + 2] + grain)); // B
170
+ // Alpha unchanged
171
+ }
172
+
173
+ ctx.putImageData(imageData, 0, 0);
174
+ }
175
+