@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.
- package/assets/fonts/Manrope/Manrope-Bold.ttf +0 -0
- package/assets/fonts/Manrope/Manrope-Regular.ttf +0 -0
- package/assets/fonts/Others/AbyssinicaSIL-Regular.ttf +0 -0
- package/assets/fonts/Others/ChirpRegular.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Bold.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Medium.ttf +0 -0
- package/assets/fonts/Poppins/Poppins-Regular.ttf +0 -0
- package/assets/placeholders/album_art.png +0 -0
- package/assets/placeholders/avatar.png +0 -0
- package/assets/placeholders/badge.jpg +0 -0
- package/assets/placeholders/badge.png +0 -0
- package/assets/placeholders/badge_2.jpg +0 -0
- package/assets/placeholders/badge_3.jpg +0 -0
- package/assets/placeholders/badge_4.jpg +0 -0
- package/assets/placeholders/badge_5.jpg +0 -0
- package/assets/placeholders/banner.jpeg +0 -0
- package/assets/placeholders/images.jpeg +0 -0
- package/index.js +153 -0
- package/package.json +34 -0
- package/src/animation-effects.js +631 -0
- package/src/cache-manager.js +258 -0
- package/src/community-banner.js +1536 -0
- package/src/constants.js +208 -0
- package/src/discord-profile.js +584 -0
- package/src/e-commerce-banner.js +1214 -0
- package/src/effects.js +355 -0
- package/src/error-handler.js +305 -0
- package/src/event-banner.js +1319 -0
- package/src/facebook-post.js +679 -0
- package/src/gradient-welcome.js +430 -0
- package/src/image-filters.js +1034 -0
- package/src/image-processor.js +1014 -0
- package/src/instagram-post.js +504 -0
- package/src/interactive-elements.js +1208 -0
- package/src/linkedin-post.js +658 -0
- package/src/marketing-banner.js +1089 -0
- package/src/minimalist-banner.js +892 -0
- package/src/modern-profile.js +755 -0
- package/src/performance-optimizer.js +216 -0
- package/src/telegram-header.js +544 -0
- package/src/test-runner.js +645 -0
- package/src/tiktok-post.js +713 -0
- package/src/twitter-header.js +604 -0
- package/src/validator.js +442 -0
- package/src/welcome-leave.js +445 -0
- package/src/whatsapp-status.js +386 -0
- package/src/youtube-thumbnail.js +681 -0
- 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
|
+
|