@cognima/banners 0.0.1-beta

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 (48) hide show
  1. package/assets/fonts/Manrope/Manrope-Bold.ttf +0 -0
  2. package/assets/fonts/Manrope/Manrope-Regular.ttf +0 -0
  3. package/assets/fonts/Others/AbyssinicaSIL-Regular.ttf +0 -0
  4. package/assets/fonts/Others/ChirpRegular.ttf +0 -0
  5. package/assets/fonts/Poppins/Poppins-Bold.ttf +0 -0
  6. package/assets/fonts/Poppins/Poppins-Medium.ttf +0 -0
  7. package/assets/fonts/Poppins/Poppins-Regular.ttf +0 -0
  8. package/assets/placeholders/album_art.png +0 -0
  9. package/assets/placeholders/avatar.png +0 -0
  10. package/assets/placeholders/badge.jpg +0 -0
  11. package/assets/placeholders/badge.png +0 -0
  12. package/assets/placeholders/badge_2.jpg +0 -0
  13. package/assets/placeholders/badge_3.jpg +0 -0
  14. package/assets/placeholders/badge_4.jpg +0 -0
  15. package/assets/placeholders/badge_5.jpg +0 -0
  16. package/assets/placeholders/banner.jpeg +0 -0
  17. package/assets/placeholders/images.jpeg +0 -0
  18. package/index.js +153 -0
  19. package/package.json +34 -0
  20. package/src/animation-effects.js +631 -0
  21. package/src/cache-manager.js +258 -0
  22. package/src/community-banner.js +1536 -0
  23. package/src/constants.js +208 -0
  24. package/src/discord-profile.js +584 -0
  25. package/src/e-commerce-banner.js +1214 -0
  26. package/src/effects.js +355 -0
  27. package/src/error-handler.js +305 -0
  28. package/src/event-banner.js +1319 -0
  29. package/src/facebook-post.js +679 -0
  30. package/src/gradient-welcome.js +430 -0
  31. package/src/image-filters.js +1034 -0
  32. package/src/image-processor.js +1014 -0
  33. package/src/instagram-post.js +504 -0
  34. package/src/interactive-elements.js +1208 -0
  35. package/src/linkedin-post.js +658 -0
  36. package/src/marketing-banner.js +1089 -0
  37. package/src/minimalist-banner.js +892 -0
  38. package/src/modern-profile.js +755 -0
  39. package/src/performance-optimizer.js +216 -0
  40. package/src/telegram-header.js +544 -0
  41. package/src/test-runner.js +645 -0
  42. package/src/tiktok-post.js +713 -0
  43. package/src/twitter-header.js +604 -0
  44. package/src/validator.js +442 -0
  45. package/src/welcome-leave.js +445 -0
  46. package/src/whatsapp-status.js +386 -0
  47. package/src/youtube-thumbnail.js +681 -0
  48. package/utils.js +710 -0
@@ -0,0 +1,1034 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Módulo de Filtros de Imagem
5
+ *
6
+ * Este módulo fornece funções para aplicar diversos filtros e efeitos
7
+ * em imagens para uso nos banners.
8
+ *
9
+ * @author Cognima Team (melhorado)
10
+ * @version 2.0.0
11
+ */
12
+
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+
15
+ const pureimage = require("pureimage");
16
+ const { hexToRgba } = require("../utils");
17
+
18
+ /**
19
+ * Aplica um filtro de escala de cinza a uma imagem
20
+ * @param {Object} ctx - Contexto do canvas
21
+ * @param {number} x - Posição X
22
+ * @param {number} y - Posição Y
23
+ * @param {number} width - Largura
24
+ * @param {number} height - Altura
25
+ * @param {number} intensity - Intensidade do filtro (0-1)
26
+ */
27
+ function applyGrayscale(ctx, x, y, width, height, intensity = 1) {
28
+ const imageData = ctx.getImageData(x, y, width, height);
29
+ const data = imageData.data;
30
+
31
+ for (let i = 0; i < data.length; i += 4) {
32
+ const r = data[i];
33
+ const g = data[i + 1];
34
+ const b = data[i + 2];
35
+
36
+ // Fórmula de luminância para escala de cinza
37
+ const gray = 0.299 * r + 0.587 * g + 0.114 * b;
38
+
39
+ // Aplica a intensidade do filtro
40
+ data[i] = r * (1 - intensity) + gray * intensity;
41
+ data[i + 1] = g * (1 - intensity) + gray * intensity;
42
+ data[i + 2] = b * (1 - intensity) + gray * intensity;
43
+ }
44
+
45
+ ctx.putImageData(imageData, x, y);
46
+ }
47
+
48
+ /**
49
+ * Aplica um filtro sépia a uma imagem
50
+ * @param {Object} ctx - Contexto do canvas
51
+ * @param {number} x - Posição X
52
+ * @param {number} y - Posição Y
53
+ * @param {number} width - Largura
54
+ * @param {number} height - Altura
55
+ * @param {number} intensity - Intensidade do filtro (0-1)
56
+ */
57
+ function applySepia(ctx, x, y, width, height, intensity = 1) {
58
+ const imageData = ctx.getImageData(x, y, width, height);
59
+ const data = imageData.data;
60
+
61
+ for (let i = 0; i < data.length; i += 4) {
62
+ const r = data[i];
63
+ const g = data[i + 1];
64
+ const b = data[i + 2];
65
+
66
+ // Fórmula para filtro sépia
67
+ const newR = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
68
+ const newG = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
69
+ const newB = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
70
+
71
+ // Aplica a intensidade do filtro
72
+ data[i] = r * (1 - intensity) + newR * intensity;
73
+ data[i + 1] = g * (1 - intensity) + newG * intensity;
74
+ data[i + 2] = b * (1 - intensity) + newB * intensity;
75
+ }
76
+
77
+ ctx.putImageData(imageData, x, y);
78
+ }
79
+
80
+ /**
81
+ * Ajusta o brilho de uma imagem
82
+ * @param {Object} ctx - Contexto do canvas
83
+ * @param {number} x - Posição X
84
+ * @param {number} y - Posição Y
85
+ * @param {number} width - Largura
86
+ * @param {number} height - Altura
87
+ * @param {number} value - Valor do ajuste (-100 a 100)
88
+ */
89
+ function adjustBrightness(ctx, x, y, width, height, value) {
90
+ const imageData = ctx.getImageData(x, y, width, height);
91
+ const data = imageData.data;
92
+
93
+ // Normaliza o valor para um fator multiplicativo
94
+ const factor = 1 + (value / 100);
95
+
96
+ for (let i = 0; i < data.length; i += 4) {
97
+ data[i] = Math.min(255, Math.max(0, data[i] * factor));
98
+ data[i + 1] = Math.min(255, Math.max(0, data[i + 1] * factor));
99
+ data[i + 2] = Math.min(255, Math.max(0, data[i + 2] * factor));
100
+ }
101
+
102
+ ctx.putImageData(imageData, x, y);
103
+ }
104
+
105
+ /**
106
+ * Ajusta o contraste de uma imagem
107
+ * @param {Object} ctx - Contexto do canvas
108
+ * @param {number} x - Posição X
109
+ * @param {number} y - Posição Y
110
+ * @param {number} width - Largura
111
+ * @param {number} height - Altura
112
+ * @param {number} value - Valor do ajuste (-100 a 100)
113
+ */
114
+ function adjustContrast(ctx, x, y, width, height, value) {
115
+ const imageData = ctx.getImageData(x, y, width, height);
116
+ const data = imageData.data;
117
+
118
+ // Normaliza o valor para um fator de contraste
119
+ const factor = (259 * (value + 255)) / (255 * (259 - value));
120
+
121
+ for (let i = 0; i < data.length; i += 4) {
122
+ data[i] = Math.min(255, Math.max(0, factor * (data[i] - 128) + 128));
123
+ data[i + 1] = Math.min(255, Math.max(0, factor * (data[i + 1] - 128) + 128));
124
+ data[i + 2] = Math.min(255, Math.max(0, factor * (data[i + 2] - 128) + 128));
125
+ }
126
+
127
+ ctx.putImageData(imageData, x, y);
128
+ }
129
+
130
+ /**
131
+ * Ajusta a saturação de uma imagem
132
+ * @param {Object} ctx - Contexto do canvas
133
+ * @param {number} x - Posição X
134
+ * @param {number} y - Posição Y
135
+ * @param {number} width - Largura
136
+ * @param {number} height - Altura
137
+ * @param {number} value - Valor do ajuste (-100 a 100)
138
+ */
139
+ function adjustSaturation(ctx, x, y, width, height, value) {
140
+ const imageData = ctx.getImageData(x, y, width, height);
141
+ const data = imageData.data;
142
+
143
+ // Normaliza o valor para um fator de saturação
144
+ const factor = 1 + (value / 100);
145
+
146
+ for (let i = 0; i < data.length; i += 4) {
147
+ const r = data[i];
148
+ const g = data[i + 1];
149
+ const b = data[i + 2];
150
+
151
+ // Fórmula de luminância para escala de cinza
152
+ const gray = 0.299 * r + 0.587 * g + 0.114 * b;
153
+
154
+ // Ajusta a saturação
155
+ data[i] = Math.min(255, Math.max(0, gray + factor * (r - gray)));
156
+ data[i + 1] = Math.min(255, Math.max(0, gray + factor * (g - gray)));
157
+ data[i + 2] = Math.min(255, Math.max(0, gray + factor * (b - gray)));
158
+ }
159
+
160
+ ctx.putImageData(imageData, x, y);
161
+ }
162
+
163
+ /**
164
+ * Aplica um filtro de desfoque a uma imagem
165
+ * @param {Object} ctx - Contexto do canvas
166
+ * @param {number} x - Posição X
167
+ * @param {number} y - Posição Y
168
+ * @param {number} width - Largura
169
+ * @param {number} height - Altura
170
+ * @param {number} radius - Raio do desfoque (1-10)
171
+ */
172
+ function applyBlur(ctx, x, y, width, height, radius = 3) {
173
+ // Limita o raio para evitar operações muito intensivas
174
+ radius = Math.min(10, Math.max(1, radius));
175
+
176
+ const imageData = ctx.getImageData(x, y, width, height);
177
+ const data = imageData.data;
178
+ const result = new Uint8ClampedArray(data.length);
179
+
180
+ // Copia os dados originais
181
+ for (let i = 0; i < data.length; i++) {
182
+ result[i] = data[i];
183
+ }
184
+
185
+ // Aplica o desfoque horizontal
186
+ for (let y = 0; y < height; y++) {
187
+ for (let x = 0; x < width; x++) {
188
+ let r = 0, g = 0, b = 0, a = 0, count = 0;
189
+
190
+ for (let i = -radius; i <= radius; i++) {
191
+ const posX = Math.min(width - 1, Math.max(0, x + i));
192
+ const index = (y * width + posX) * 4;
193
+
194
+ r += data[index];
195
+ g += data[index + 1];
196
+ b += data[index + 2];
197
+ a += data[index + 3];
198
+ count++;
199
+ }
200
+
201
+ const resultIndex = (y * width + x) * 4;
202
+ result[resultIndex] = r / count;
203
+ result[resultIndex + 1] = g / count;
204
+ result[resultIndex + 2] = b / count;
205
+ result[resultIndex + 3] = a / count;
206
+ }
207
+ }
208
+
209
+ // Copia os resultados de volta para data
210
+ for (let i = 0; i < data.length; i++) {
211
+ data[i] = result[i];
212
+ }
213
+
214
+ // Aplica o desfoque vertical
215
+ for (let x = 0; x < width; x++) {
216
+ for (let y = 0; y < height; y++) {
217
+ let r = 0, g = 0, b = 0, a = 0, count = 0;
218
+
219
+ for (let i = -radius; i <= radius; i++) {
220
+ const posY = Math.min(height - 1, Math.max(0, y + i));
221
+ const index = (posY * width + x) * 4;
222
+
223
+ r += data[index];
224
+ g += data[index + 1];
225
+ b += data[index + 2];
226
+ a += data[index + 3];
227
+ count++;
228
+ }
229
+
230
+ const resultIndex = (y * width + x) * 4;
231
+ result[resultIndex] = r / count;
232
+ result[resultIndex + 1] = g / count;
233
+ result[resultIndex + 2] = b / count;
234
+ result[resultIndex + 3] = a / count;
235
+ }
236
+ }
237
+
238
+ // Cria um novo ImageData com os resultados
239
+ const resultImageData = new ImageData(result, width, height);
240
+ ctx.putImageData(resultImageData, x, y);
241
+ }
242
+
243
+ /**
244
+ * Aplica um filtro de nitidez a uma imagem
245
+ * @param {Object} ctx - Contexto do canvas
246
+ * @param {number} x - Posição X
247
+ * @param {number} y - Posição Y
248
+ * @param {number} width - Largura
249
+ * @param {number} height - Altura
250
+ * @param {number} amount - Quantidade de nitidez (0-1)
251
+ */
252
+ function applySharpen(ctx, x, y, width, height, amount = 0.5) {
253
+ // Limita a quantidade para evitar artefatos
254
+ amount = Math.min(1, Math.max(0, amount));
255
+
256
+ const imageData = ctx.getImageData(x, y, width, height);
257
+ const data = imageData.data;
258
+ const result = new Uint8ClampedArray(data.length);
259
+
260
+ // Copia os dados originais
261
+ for (let i = 0; i < data.length; i++) {
262
+ result[i] = data[i];
263
+ }
264
+
265
+ // Kernel de nitidez
266
+ const kernel = [
267
+ 0, -amount, 0,
268
+ -amount, 1 + 4 * amount, -amount,
269
+ 0, -amount, 0
270
+ ];
271
+
272
+ // Aplica o kernel
273
+ for (let y = 1; y < height - 1; y++) {
274
+ for (let x = 1; x < width - 1; x++) {
275
+ const centerIndex = (y * width + x) * 4;
276
+
277
+ let r = 0, g = 0, b = 0;
278
+
279
+ // Aplica o kernel 3x3
280
+ for (let ky = -1; ky <= 1; ky++) {
281
+ for (let kx = -1; kx <= 1; kx++) {
282
+ const kernelIndex = (ky + 1) * 3 + (kx + 1);
283
+ const pixelIndex = ((y + ky) * width + (x + kx)) * 4;
284
+
285
+ r += data[pixelIndex] * kernel[kernelIndex];
286
+ g += data[pixelIndex + 1] * kernel[kernelIndex];
287
+ b += data[pixelIndex + 2] * kernel[kernelIndex];
288
+ }
289
+ }
290
+
291
+ result[centerIndex] = Math.min(255, Math.max(0, r));
292
+ result[centerIndex + 1] = Math.min(255, Math.max(0, g));
293
+ result[centerIndex + 2] = Math.min(255, Math.max(0, b));
294
+ }
295
+ }
296
+
297
+ // Cria um novo ImageData com os resultados
298
+ const resultImageData = new ImageData(result, width, height);
299
+ ctx.putImageData(resultImageData, x, y);
300
+ }
301
+
302
+ /**
303
+ * Aplica um filtro de vinheta a uma imagem
304
+ * @param {Object} ctx - Contexto do canvas
305
+ * @param {number} x - Posição X
306
+ * @param {number} y - Posição Y
307
+ * @param {number} width - Largura
308
+ * @param {number} height - Altura
309
+ * @param {number} amount - Quantidade de vinheta (0-1)
310
+ * @param {string} color - Cor da vinheta (hexadecimal)
311
+ */
312
+ function applyVignette(ctx, x, y, width, height, amount = 0.5, color = "#000000") {
313
+ // Limita a quantidade para evitar artefatos
314
+ amount = Math.min(1, Math.max(0, amount));
315
+
316
+ ctx.save();
317
+
318
+ // Cria um gradiente radial para a vinheta
319
+ const gradient = ctx.createRadialGradient(
320
+ x + width / 2,
321
+ y + height / 2,
322
+ 0,
323
+ x + width / 2,
324
+ y + height / 2,
325
+ Math.max(width, height) / 2
326
+ );
327
+
328
+ gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
329
+ gradient.addColorStop(0.5, hexToRgba(color, 0));
330
+ gradient.addColorStop(1, hexToRgba(color, amount));
331
+
332
+ ctx.fillStyle = gradient;
333
+ ctx.fillRect(x, y, width, height);
334
+
335
+ ctx.restore();
336
+ }
337
+
338
+ /**
339
+ * Aplica um filtro de duotone a uma imagem
340
+ * @param {Object} ctx - Contexto do canvas
341
+ * @param {number} x - Posição X
342
+ * @param {number} y - Posição Y
343
+ * @param {number} width - Largura
344
+ * @param {number} height - Altura
345
+ * @param {string} lightColor - Cor clara (hexadecimal)
346
+ * @param {string} darkColor - Cor escura (hexadecimal)
347
+ * @param {number} intensity - Intensidade do filtro (0-1)
348
+ */
349
+ function applyDuotone(ctx, x, y, width, height, lightColor = "#FFFFFF", darkColor = "#000000", intensity = 1) {
350
+ // Converte as cores hexadecimais para componentes RGB
351
+ const lightRGB = {
352
+ r: parseInt(lightColor.slice(1, 3), 16),
353
+ g: parseInt(lightColor.slice(3, 5), 16),
354
+ b: parseInt(lightColor.slice(5, 7), 16)
355
+ };
356
+
357
+ const darkRGB = {
358
+ r: parseInt(darkColor.slice(1, 3), 16),
359
+ g: parseInt(darkColor.slice(3, 5), 16),
360
+ b: parseInt(darkColor.slice(5, 7), 16)
361
+ };
362
+
363
+ const imageData = ctx.getImageData(x, y, width, height);
364
+ const data = imageData.data;
365
+
366
+ for (let i = 0; i < data.length; i += 4) {
367
+ // Converte para escala de cinza
368
+ const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
369
+ const normalized = gray / 255;
370
+
371
+ // Interpola entre as cores escura e clara com base no valor de cinza
372
+ const r = darkRGB.r + normalized * (lightRGB.r - darkRGB.r);
373
+ const g = darkRGB.g + normalized * (lightRGB.g - darkRGB.g);
374
+ const b = darkRGB.b + normalized * (lightRGB.b - darkRGB.b);
375
+
376
+ // Aplica a intensidade do filtro
377
+ data[i] = data[i] * (1 - intensity) + r * intensity;
378
+ data[i + 1] = data[i + 1] * (1 - intensity) + g * intensity;
379
+ data[i + 2] = data[i + 2] * (1 - intensity) + b * intensity;
380
+ }
381
+
382
+ ctx.putImageData(imageData, x, y);
383
+ }
384
+
385
+ /**
386
+ * Aplica um filtro de cor sólida a uma imagem
387
+ * @param {Object} ctx - Contexto do canvas
388
+ * @param {number} x - Posição X
389
+ * @param {number} y - Posição Y
390
+ * @param {number} width - Largura
391
+ * @param {number} height - Altura
392
+ * @param {string} color - Cor (hexadecimal)
393
+ * @param {string} blendMode - Modo de mesclagem ('multiply', 'screen', 'overlay')
394
+ * @param {number} opacity - Opacidade (0-1)
395
+ */
396
+ function applyColorOverlay(ctx, x, y, width, height, color = "#000000", blendMode = "multiply", opacity = 0.5) {
397
+ // Converte a cor hexadecimal para componentes RGB
398
+ const rgb = {
399
+ r: parseInt(color.slice(1, 3), 16),
400
+ g: parseInt(color.slice(3, 5), 16),
401
+ b: parseInt(color.slice(5, 7), 16)
402
+ };
403
+
404
+ const imageData = ctx.getImageData(x, y, width, height);
405
+ const data = imageData.data;
406
+
407
+ for (let i = 0; i < data.length; i += 4) {
408
+ const r = data[i];
409
+ const g = data[i + 1];
410
+ const b = data[i + 2];
411
+
412
+ let newR, newG, newB;
413
+
414
+ // Aplica o modo de mesclagem
415
+ switch (blendMode) {
416
+ case "multiply":
417
+ newR = (r * rgb.r) / 255;
418
+ newG = (g * rgb.g) / 255;
419
+ newB = (b * rgb.b) / 255;
420
+ break;
421
+ case "screen":
422
+ newR = 255 - ((255 - r) * (255 - rgb.r)) / 255;
423
+ newG = 255 - ((255 - g) * (255 - rgb.g)) / 255;
424
+ newB = 255 - ((255 - b) * (255 - rgb.b)) / 255;
425
+ break;
426
+ case "overlay":
427
+ newR = r < 128 ? (2 * r * rgb.r) / 255 : 255 - (2 * (255 - r) * (255 - rgb.r)) / 255;
428
+ newG = g < 128 ? (2 * g * rgb.g) / 255 : 255 - (2 * (255 - g) * (255 - rgb.g)) / 255;
429
+ newB = b < 128 ? (2 * b * rgb.b) / 255 : 255 - (2 * (255 - b) * (255 - rgb.b)) / 255;
430
+ break;
431
+ default:
432
+ newR = rgb.r;
433
+ newG = rgb.g;
434
+ newB = rgb.b;
435
+ }
436
+
437
+ // Aplica a opacidade
438
+ data[i] = r * (1 - opacity) + newR * opacity;
439
+ data[i + 1] = g * (1 - opacity) + newG * opacity;
440
+ data[i + 2] = b * (1 - opacity) + newB * opacity;
441
+ }
442
+
443
+ ctx.putImageData(imageData, x, y);
444
+ }
445
+
446
+ /**
447
+ * Aplica um filtro de ruído a uma imagem
448
+ * @param {Object} ctx - Contexto do canvas
449
+ * @param {number} x - Posição X
450
+ * @param {number} y - Posição Y
451
+ * @param {number} width - Largura
452
+ * @param {number} height - Altura
453
+ * @param {number} amount - Quantidade de ruído (0-1)
454
+ * @param {string} type - Tipo de ruído ('mono', 'color')
455
+ */
456
+ function applyNoise(ctx, x, y, width, height, amount = 0.2, type = "mono") {
457
+ // Limita a quantidade para evitar artefatos
458
+ amount = Math.min(1, Math.max(0, amount)) * 50;
459
+
460
+ const imageData = ctx.getImageData(x, y, width, height);
461
+ const data = imageData.data;
462
+
463
+ for (let i = 0; i < data.length; i += 4) {
464
+ if (type === "mono") {
465
+ // Ruído monocromático (mesmo valor para R, G e B)
466
+ const noise = Math.round((Math.random() - 0.5) * amount * 2);
467
+
468
+ data[i] = Math.min(255, Math.max(0, data[i] + noise));
469
+ data[i + 1] = Math.min(255, Math.max(0, data[i + 1] + noise));
470
+ data[i + 2] = Math.min(255, Math.max(0, data[i + 2] + noise));
471
+ } else {
472
+ // Ruído colorido (valores diferentes para R, G e B)
473
+ data[i] = Math.min(255, Math.max(0, data[i] + Math.round((Math.random() - 0.5) * amount * 2)));
474
+ data[i + 1] = Math.min(255, Math.max(0, data[i + 1] + Math.round((Math.random() - 0.5) * amount * 2)));
475
+ data[i + 2] = Math.min(255, Math.max(0, data[i + 2] + Math.round((Math.random() - 0.5) * amount * 2)));
476
+ }
477
+ }
478
+
479
+ ctx.putImageData(imageData, x, y);
480
+ }
481
+
482
+ /**
483
+ * Aplica um filtro de posterização a uma imagem
484
+ * @param {Object} ctx - Contexto do canvas
485
+ * @param {number} x - Posição X
486
+ * @param {number} y - Posição Y
487
+ * @param {number} width - Largura
488
+ * @param {number} height - Altura
489
+ * @param {number} levels - Número de níveis (2-20)
490
+ */
491
+ function applyPosterize(ctx, x, y, width, height, levels = 5) {
492
+ // Limita os níveis para evitar artefatos
493
+ levels = Math.min(20, Math.max(2, levels));
494
+
495
+ const imageData = ctx.getImageData(x, y, width, height);
496
+ const data = imageData.data;
497
+
498
+ const step = 255 / (levels - 1);
499
+
500
+ for (let i = 0; i < data.length; i += 4) {
501
+ data[i] = Math.round(Math.round(data[i] / step) * step);
502
+ data[i + 1] = Math.round(Math.round(data[i + 1] / step) * step);
503
+ data[i + 2] = Math.round(Math.round(data[i + 2] / step) * step);
504
+ }
505
+
506
+ ctx.putImageData(imageData, x, y);
507
+ }
508
+
509
+ /**
510
+ * Aplica um filtro de pixelização a uma imagem
511
+ * @param {Object} ctx - Contexto do canvas
512
+ * @param {number} x - Posição X
513
+ * @param {number} y - Posição Y
514
+ * @param {number} width - Largura
515
+ * @param {number} height - Altura
516
+ * @param {number} pixelSize - Tamanho dos pixels (1-50)
517
+ */
518
+ function applyPixelate(ctx, x, y, width, height, pixelSize = 10) {
519
+ // Limita o tamanho dos pixels para evitar operações muito intensivas
520
+ pixelSize = Math.min(50, Math.max(1, pixelSize));
521
+
522
+ const imageData = ctx.getImageData(x, y, width, height);
523
+ const data = imageData.data;
524
+ const tempCanvas = pureimage.make(width, height);
525
+ const tempCtx = tempCanvas.getContext("2d");
526
+
527
+ // Copia os dados originais para o canvas temporário
528
+ const tempImageData = tempCtx.createImageData(width, height);
529
+ for (let i = 0; i < data.length; i++) {
530
+ tempImageData.data[i] = data[i];
531
+ }
532
+ tempCtx.putImageData(tempImageData, 0, 0);
533
+
534
+ // Limpa o canvas original
535
+ ctx.clearRect(x, y, width, height);
536
+
537
+ // Desenha a imagem pixelizada
538
+ for (let blockY = 0; blockY < height; blockY += pixelSize) {
539
+ for (let blockX = 0; blockX < width; blockX += pixelSize) {
540
+ // Calcula o tamanho do bloco atual (pode ser menor nas bordas)
541
+ const blockWidth = Math.min(pixelSize, width - blockX);
542
+ const blockHeight = Math.min(pixelSize, height - blockY);
543
+
544
+ // Obtém a cor média do bloco
545
+ let r = 0, g = 0, b = 0, a = 0, count = 0;
546
+
547
+ for (let py = 0; py < blockHeight; py++) {
548
+ for (let px = 0; px < blockWidth; px++) {
549
+ const index = ((blockY + py) * width + (blockX + px)) * 4;
550
+
551
+ r += data[index];
552
+ g += data[index + 1];
553
+ b += data[index + 2];
554
+ a += data[index + 3];
555
+ count++;
556
+ }
557
+ }
558
+
559
+ r = Math.round(r / count);
560
+ g = Math.round(g / count);
561
+ b = Math.round(b / count);
562
+ a = Math.round(a / count);
563
+
564
+ // Preenche o bloco com a cor média
565
+ ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a / 255})`;
566
+ ctx.fillRect(x + blockX, y + blockY, blockWidth, blockHeight);
567
+ }
568
+ }
569
+ }
570
+
571
+ /**
572
+ * Aplica um filtro de borda a uma imagem
573
+ * @param {Object} ctx - Contexto do canvas
574
+ * @param {number} x - Posição X
575
+ * @param {number} y - Posição Y
576
+ * @param {number} width - Largura
577
+ * @param {number} height - Altura
578
+ * @param {number} thickness - Espessura da borda (1-10)
579
+ * @param {string} color - Cor da borda (hexadecimal)
580
+ */
581
+ function applyBorder(ctx, x, y, width, height, thickness = 5, color = "#000000") {
582
+ // Limita a espessura para evitar operações muito intensivas
583
+ thickness = Math.min(10, Math.max(1, thickness));
584
+
585
+ ctx.save();
586
+
587
+ ctx.strokeStyle = color;
588
+ ctx.lineWidth = thickness;
589
+ ctx.strokeRect(x + thickness / 2, y + thickness / 2, width - thickness, height - thickness);
590
+
591
+ ctx.restore();
592
+ }
593
+
594
+ /**
595
+ * Aplica um filtro de moldura a uma imagem
596
+ * @param {Object} ctx - Contexto do canvas
597
+ * @param {number} x - Posição X
598
+ * @param {number} y - Posição Y
599
+ * @param {number} width - Largura
600
+ * @param {number} height - Altura
601
+ * @param {number} thickness - Espessura da moldura (10-50)
602
+ * @param {string} color - Cor da moldura (hexadecimal)
603
+ * @param {string} style - Estilo da moldura ('solid', 'double', 'inset')
604
+ */
605
+ function applyFrame(ctx, x, y, width, height, thickness = 20, color = "#FFFFFF", style = "solid") {
606
+ // Limita a espessura para evitar operações muito intensivas
607
+ thickness = Math.min(50, Math.max(10, thickness));
608
+
609
+ ctx.save();
610
+
611
+ switch (style) {
612
+ case "double":
613
+ // Moldura dupla
614
+ const outerThickness = thickness;
615
+ const innerThickness = thickness / 2;
616
+ const gap = thickness / 4;
617
+
618
+ // Moldura externa
619
+ ctx.fillStyle = color;
620
+ ctx.fillRect(x, y, width, outerThickness); // Superior
621
+ ctx.fillRect(x, y + height - outerThickness, width, outerThickness); // Inferior
622
+ ctx.fillRect(x, y + outerThickness, outerThickness, height - outerThickness * 2); // Esquerda
623
+ ctx.fillRect(x + width - outerThickness, y + outerThickness, outerThickness, height - outerThickness * 2); // Direita
624
+
625
+ // Moldura interna
626
+ ctx.fillRect(x + outerThickness + gap, y + outerThickness + gap, width - 2 * (outerThickness + gap), innerThickness); // Superior
627
+ ctx.fillRect(x + outerThickness + gap, y + height - outerThickness - gap - innerThickness, width - 2 * (outerThickness + gap), innerThickness); // Inferior
628
+ ctx.fillRect(x + outerThickness + gap, y + outerThickness + gap + innerThickness, innerThickness, height - 2 * (outerThickness + gap + innerThickness)); // Esquerda
629
+ ctx.fillRect(x + width - outerThickness - gap - innerThickness, y + outerThickness + gap + innerThickness, innerThickness, height - 2 * (outerThickness + gap + innerThickness)); // Direita
630
+ break;
631
+
632
+ case "inset":
633
+ // Moldura com efeito de profundidade
634
+ ctx.fillStyle = color;
635
+ ctx.fillRect(x, y, width, thickness); // Superior
636
+ ctx.fillRect(x, y + height - thickness, width, thickness); // Inferior
637
+ ctx.fillRect(x, y + thickness, thickness, height - thickness * 2); // Esquerda
638
+ ctx.fillRect(x + width - thickness, y + thickness, thickness, height - thickness * 2); // Direita
639
+
640
+ // Sombras para efeito de profundidade
641
+ const lightColor = "#FFFFFF";
642
+ const darkColor = "#000000";
643
+ const shadowSize = thickness / 4;
644
+
645
+ // Sombras claras (superior e esquerda)
646
+ ctx.fillStyle = hexToRgba(lightColor, 0.3);
647
+ ctx.fillRect(x, y, width, shadowSize); // Superior
648
+ ctx.fillRect(x, y + shadowSize, shadowSize, height - shadowSize * 2); // Esquerda
649
+
650
+ // Sombras escuras (inferior e direita)
651
+ ctx.fillStyle = hexToRgba(darkColor, 0.3);
652
+ ctx.fillRect(x, y + height - shadowSize, width, shadowSize); // Inferior
653
+ ctx.fillRect(x + width - shadowSize, y + shadowSize, shadowSize, height - shadowSize * 2); // Direita
654
+ break;
655
+
656
+ case "solid":
657
+ default:
658
+ // Moldura sólida
659
+ ctx.fillStyle = color;
660
+ ctx.fillRect(x, y, width, thickness); // Superior
661
+ ctx.fillRect(x, y + height - thickness, width, thickness); // Inferior
662
+ ctx.fillRect(x, y + thickness, thickness, height - thickness * 2); // Esquerda
663
+ ctx.fillRect(x + width - thickness, y + thickness, thickness, height - thickness * 2); // Direita
664
+ break;
665
+ }
666
+
667
+ ctx.restore();
668
+ }
669
+
670
+ /**
671
+ * Aplica um filtro de reflexo a uma imagem
672
+ * @param {Object} ctx - Contexto do canvas
673
+ * @param {number} x - Posição X
674
+ * @param {number} y - Posição Y
675
+ * @param {number} width - Largura
676
+ * @param {number} height - Altura
677
+ * @param {number} reflectionHeight - Altura da reflexão (10-100)
678
+ * @param {number} opacity - Opacidade da reflexão (0-1)
679
+ * @param {number} gap - Espaço entre a imagem e a reflexão (0-10)
680
+ */
681
+ function applyReflection(ctx, x, y, width, height, reflectionHeight = 50, opacity = 0.5, gap = 2) {
682
+ // Limita os parâmetros para evitar operações muito intensivas
683
+ reflectionHeight = Math.min(100, Math.max(10, reflectionHeight));
684
+ opacity = Math.min(1, Math.max(0, opacity));
685
+ gap = Math.min(10, Math.max(0, gap));
686
+
687
+ // Obtém os dados da imagem original
688
+ const imageData = ctx.getImageData(x, y, width, height);
689
+
690
+ // Cria um canvas temporário para a reflexão
691
+ const tempCanvas = pureimage.make(width, reflectionHeight);
692
+ const tempCtx = tempCanvas.getContext("2d");
693
+
694
+ // Desenha a parte inferior da imagem original invertida
695
+ tempCtx.save();
696
+ tempCtx.scale(1, -1);
697
+ tempCtx.drawImage(
698
+ imageData,
699
+ 0, height - reflectionHeight, width, reflectionHeight,
700
+ 0, -reflectionHeight, width, reflectionHeight
701
+ );
702
+ tempCtx.restore();
703
+
704
+ // Aplica um gradiente de opacidade à reflexão
705
+ const gradient = tempCtx.createLinearGradient(0, 0, 0, reflectionHeight);
706
+ gradient.addColorStop(0, `rgba(255, 255, 255, ${opacity})`);
707
+ gradient.addColorStop(1, "rgba(255, 255, 255, 0)");
708
+
709
+ tempCtx.globalCompositeOperation = "destination-in";
710
+ tempCtx.fillStyle = gradient;
711
+ tempCtx.fillRect(0, 0, width, reflectionHeight);
712
+
713
+ // Desenha a reflexão abaixo da imagem original
714
+ ctx.drawImage(tempCanvas, x, y + height + gap);
715
+ }
716
+
717
+ /**
718
+ * Aplica um filtro de sombra a uma imagem
719
+ * @param {Object} ctx - Contexto do canvas
720
+ * @param {number} x - Posição X
721
+ * @param {number} y - Posição Y
722
+ * @param {number} width - Largura
723
+ * @param {number} height - Altura
724
+ * @param {number} blur - Desfoque da sombra (0-20)
725
+ * @param {number} offsetX - Deslocamento horizontal da sombra (-20 a 20)
726
+ * @param {number} offsetY - Deslocamento vertical da sombra (-20 a 20)
727
+ * @param {string} color - Cor da sombra (hexadecimal)
728
+ * @param {number} opacity - Opacidade da sombra (0-1)
729
+ */
730
+ function applyShadow(ctx, x, y, width, height, blur = 10, offsetX = 5, offsetY = 5, color = "#000000", opacity = 0.5) {
731
+ // Limita os parâmetros para evitar operações muito intensivas
732
+ blur = Math.min(20, Math.max(0, blur));
733
+ offsetX = Math.min(20, Math.max(-20, offsetX));
734
+ offsetY = Math.min(20, Math.max(-20, offsetY));
735
+ opacity = Math.min(1, Math.max(0, opacity));
736
+
737
+ ctx.save();
738
+
739
+ // Desenha a sombra
740
+ ctx.shadowBlur = blur;
741
+ ctx.shadowOffsetX = offsetX;
742
+ ctx.shadowOffsetY = offsetY;
743
+ ctx.shadowColor = hexToRgba(color, opacity);
744
+
745
+ // Desenha um retângulo transparente para projetar a sombra
746
+ ctx.fillStyle = "rgba(0, 0, 0, 0)";
747
+ ctx.fillRect(x, y, width, height);
748
+
749
+ ctx.restore();
750
+ }
751
+
752
+ /**
753
+ * Aplica um filtro de marca d'água a uma imagem
754
+ * @param {Object} ctx - Contexto do canvas
755
+ * @param {number} x - Posição X
756
+ * @param {number} y - Posição Y
757
+ * @param {number} width - Largura
758
+ * @param {number} height - Altura
759
+ * @param {string} text - Texto da marca d'água
760
+ * @param {string} font - Nome da fonte
761
+ * @param {number} fontSize - Tamanho da fonte
762
+ * @param {string} color - Cor do texto (hexadecimal)
763
+ * @param {number} opacity - Opacidade do texto (0-1)
764
+ * @param {number} angle - Ângulo de rotação em graus (-45 a 45)
765
+ */
766
+ function applyWatermark(ctx, x, y, width, height, text, font, fontSize = 30, color = "#000000", opacity = 0.3, angle = -30) {
767
+ // Limita os parâmetros para evitar operações muito intensivas
768
+ fontSize = Math.min(100, Math.max(10, fontSize));
769
+ opacity = Math.min(1, Math.max(0, opacity));
770
+ angle = Math.min(45, Math.max(-45, angle));
771
+
772
+ ctx.save();
773
+
774
+ // Configura o estilo do texto
775
+ ctx.font = `${fontSize}px ${font}`;
776
+ ctx.fillStyle = hexToRgba(color, opacity);
777
+ ctx.textAlign = "center";
778
+ ctx.textBaseline = "middle";
779
+
780
+ // Aplica a rotação
781
+ const centerX = x + width / 2;
782
+ const centerY = y + height / 2;
783
+ ctx.translate(centerX, centerY);
784
+ ctx.rotate(angle * Math.PI / 180);
785
+
786
+ // Desenha o texto
787
+ ctx.fillText(text, 0, 0);
788
+
789
+ ctx.restore();
790
+ }
791
+
792
+ /**
793
+ * Aplica um filtro de recorte circular a uma imagem
794
+ * @param {Object} ctx - Contexto do canvas
795
+ * @param {number} x - Posição X
796
+ * @param {number} y - Posição Y
797
+ * @param {number} width - Largura
798
+ * @param {number} height - Altura
799
+ * @param {number} radius - Raio do círculo (se não fornecido, usa o menor entre largura/2 e altura/2)
800
+ */
801
+ function applyCircularCrop(ctx, x, y, width, height, radius = null) {
802
+ // Se o raio não for fornecido, usa o menor entre largura/2 e altura/2
803
+ if (radius === null) {
804
+ radius = Math.min(width, height) / 2;
805
+ }
806
+
807
+ const centerX = x + width / 2;
808
+ const centerY = y + height / 2;
809
+
810
+ // Obtém os dados da imagem original
811
+ const imageData = ctx.getImageData(x, y, width, height);
812
+
813
+ // Limpa a área original
814
+ ctx.clearRect(x, y, width, height);
815
+
816
+ // Cria um caminho circular
817
+ ctx.save();
818
+ ctx.beginPath();
819
+ ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
820
+ ctx.closePath();
821
+ ctx.clip();
822
+
823
+ // Desenha a imagem original dentro do círculo
824
+ ctx.putImageData(imageData, x, y);
825
+
826
+ ctx.restore();
827
+ }
828
+
829
+ /**
830
+ * Aplica um filtro de recorte em forma de coração a uma imagem
831
+ * @param {Object} ctx - Contexto do canvas
832
+ * @param {number} x - Posição X
833
+ * @param {number} y - Posição Y
834
+ * @param {number} width - Largura
835
+ * @param {number} height - Altura
836
+ * @param {number} size - Tamanho do coração (se não fornecido, usa o menor entre largura e altura)
837
+ */
838
+ function applyHeartCrop(ctx, x, y, width, height, size = null) {
839
+ // Se o tamanho não for fornecido, usa o menor entre largura e altura
840
+ if (size === null) {
841
+ size = Math.min(width, height);
842
+ }
843
+
844
+ const centerX = x + width / 2;
845
+ const centerY = y + height / 2;
846
+
847
+ // Obtém os dados da imagem original
848
+ const imageData = ctx.getImageData(x, y, width, height);
849
+
850
+ // Limpa a área original
851
+ ctx.clearRect(x, y, width, height);
852
+
853
+ // Cria um caminho em forma de coração
854
+ ctx.save();
855
+ ctx.beginPath();
856
+
857
+ // Desenha o coração
858
+ const topCurveHeight = size * 0.3;
859
+ ctx.moveTo(centerX, centerY + size / 4);
860
+
861
+ // Lado esquerdo do coração
862
+ ctx.bezierCurveTo(
863
+ centerX, centerY,
864
+ centerX - size / 2, centerY,
865
+ centerX - size / 2, centerY - topCurveHeight
866
+ );
867
+ ctx.bezierCurveTo(
868
+ centerX - size / 2, centerY - size / 2,
869
+ centerX, centerY - size / 2,
870
+ centerX, centerY - topCurveHeight
871
+ );
872
+
873
+ // Lado direito do coração
874
+ ctx.bezierCurveTo(
875
+ centerX, centerY - size / 2,
876
+ centerX + size / 2, centerY - size / 2,
877
+ centerX + size / 2, centerY - topCurveHeight
878
+ );
879
+ ctx.bezierCurveTo(
880
+ centerX + size / 2, centerY,
881
+ centerX, centerY,
882
+ centerX, centerY + size / 4
883
+ );
884
+
885
+ ctx.closePath();
886
+ ctx.clip();
887
+
888
+ // Desenha a imagem original dentro do coração
889
+ ctx.putImageData(imageData, x, y);
890
+
891
+ ctx.restore();
892
+ }
893
+
894
+ /**
895
+ * Aplica um filtro de recorte em forma de estrela a uma imagem
896
+ * @param {Object} ctx - Contexto do canvas
897
+ * @param {number} x - Posição X
898
+ * @param {number} y - Posição Y
899
+ * @param {number} width - Largura
900
+ * @param {number} height - Altura
901
+ * @param {number} size - Tamanho da estrela (se não fornecido, usa o menor entre largura e altura)
902
+ * @param {number} points - Número de pontas da estrela (5-10)
903
+ */
904
+ function applyStarCrop(ctx, x, y, width, height, size = null, points = 5) {
905
+ // Se o tamanho não for fornecido, usa o menor entre largura e altura
906
+ if (size === null) {
907
+ size = Math.min(width, height);
908
+ }
909
+
910
+ // Limita o número de pontas
911
+ points = Math.min(10, Math.max(5, points));
912
+
913
+ const centerX = x + width / 2;
914
+ const centerY = y + height / 2;
915
+ const outerRadius = size / 2;
916
+ const innerRadius = outerRadius * 0.4;
917
+
918
+ // Obtém os dados da imagem original
919
+ const imageData = ctx.getImageData(x, y, width, height);
920
+
921
+ // Limpa a área original
922
+ ctx.clearRect(x, y, width, height);
923
+
924
+ // Cria um caminho em forma de estrela
925
+ ctx.save();
926
+ ctx.beginPath();
927
+
928
+ // Desenha a estrela
929
+ for (let i = 0; i < points * 2; i++) {
930
+ const radius = i % 2 === 0 ? outerRadius : innerRadius;
931
+ const angle = (Math.PI * i) / points;
932
+ const pointX = centerX + radius * Math.sin(angle);
933
+ const pointY = centerY - radius * Math.cos(angle);
934
+
935
+ if (i === 0) {
936
+ ctx.moveTo(pointX, pointY);
937
+ } else {
938
+ ctx.lineTo(pointX, pointY);
939
+ }
940
+ }
941
+
942
+ ctx.closePath();
943
+ ctx.clip();
944
+
945
+ // Desenha a imagem original dentro da estrela
946
+ ctx.putImageData(imageData, x, y);
947
+
948
+ ctx.restore();
949
+ }
950
+
951
+ /**
952
+ * Aplica um filtro de recorte em forma de polígono a uma imagem
953
+ * @param {Object} ctx - Contexto do canvas
954
+ * @param {number} x - Posição X
955
+ * @param {number} y - Posição Y
956
+ * @param {number} width - Largura
957
+ * @param {number} height - Altura
958
+ * @param {number} size - Tamanho do polígono (se não fornecido, usa o menor entre largura e altura)
959
+ * @param {number} sides - Número de lados do polígono (3-12)
960
+ * @param {number} rotation - Rotação em graus (0-360)
961
+ */
962
+ function applyPolygonCrop(ctx, x, y, width, height, size = null, sides = 6, rotation = 0) {
963
+ // Se o tamanho não for fornecido, usa o menor entre largura e altura
964
+ if (size === null) {
965
+ size = Math.min(width, height);
966
+ }
967
+
968
+ // Limita o número de lados
969
+ sides = Math.min(12, Math.max(3, sides));
970
+
971
+ const centerX = x + width / 2;
972
+ const centerY = y + height / 2;
973
+ const radius = size / 2;
974
+
975
+ // Obtém os dados da imagem original
976
+ const imageData = ctx.getImageData(x, y, width, height);
977
+
978
+ // Limpa a área original
979
+ ctx.clearRect(x, y, width, height);
980
+
981
+ // Cria um caminho em forma de polígono
982
+ ctx.save();
983
+ ctx.beginPath();
984
+
985
+ // Desenha o polígono
986
+ const rotationInRadians = (rotation * Math.PI) / 180;
987
+
988
+ for (let i = 0; i < sides; i++) {
989
+ const angle = rotationInRadians + (i * 2 * Math.PI) / sides;
990
+ const pointX = centerX + radius * Math.cos(angle);
991
+ const pointY = centerY + radius * Math.sin(angle);
992
+
993
+ if (i === 0) {
994
+ ctx.moveTo(pointX, pointY);
995
+ } else {
996
+ ctx.lineTo(pointX, pointY);
997
+ }
998
+ }
999
+
1000
+ ctx.closePath();
1001
+ ctx.clip();
1002
+
1003
+ // Desenha a imagem original dentro do polígono
1004
+ ctx.putImageData(imageData, x, y);
1005
+
1006
+ ctx.restore();
1007
+ }
1008
+
1009
+ // Exporta todas as funções
1010
+ module.exports = {
1011
+ applyGrayscale,
1012
+ applySepia,
1013
+ adjustBrightness,
1014
+ adjustContrast,
1015
+ adjustSaturation,
1016
+ applyBlur,
1017
+ applySharpen,
1018
+ applyVignette,
1019
+ applyDuotone,
1020
+ applyColorOverlay,
1021
+ applyNoise,
1022
+ applyPosterize,
1023
+ applyPixelate,
1024
+ applyBorder,
1025
+ applyFrame,
1026
+ applyReflection,
1027
+ applyShadow,
1028
+ applyWatermark,
1029
+ applyCircularCrop,
1030
+ applyHeartCrop,
1031
+ applyStarCrop,
1032
+ applyPolygonCrop
1033
+ };
1034
+