@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,1014 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Módulo de Processamento de Imagem
|
|
5
|
+
*
|
|
6
|
+
* Este módulo fornece funções para processamento avançado de imagens,
|
|
7
|
+
* incluindo redimensionamento, recorte, composição e outros ajustes.
|
|
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 path = require("path");
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
const { loadImageWithAxios, encodeToBuffer } = require("../utils");
|
|
19
|
+
const filters = require("./image-filters");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Classe para processamento avançado de imagens
|
|
23
|
+
*/
|
|
24
|
+
class ImageProcessor {
|
|
25
|
+
/**
|
|
26
|
+
* Cria uma nova instância do processador de imagens
|
|
27
|
+
* @param {Object} options - Opções de configuração
|
|
28
|
+
*/
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
this.canvas = null;
|
|
31
|
+
this.ctx = null;
|
|
32
|
+
this.width = options.width || 800;
|
|
33
|
+
this.height = options.height || 600;
|
|
34
|
+
this.backgroundColor = options.backgroundColor || "#FFFFFF";
|
|
35
|
+
this.quality = options.quality || 0.9;
|
|
36
|
+
|
|
37
|
+
// Inicializa o canvas
|
|
38
|
+
this._initCanvas();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Inicializa o canvas com as dimensões especificadas
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
_initCanvas() {
|
|
46
|
+
this.canvas = pureimage.make(this.width, this.height);
|
|
47
|
+
this.ctx = this.canvas.getContext("2d");
|
|
48
|
+
|
|
49
|
+
// Preenche o fundo com a cor especificada
|
|
50
|
+
this.ctx.fillStyle = this.backgroundColor;
|
|
51
|
+
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Redimensiona o canvas
|
|
56
|
+
* @param {number} width - Nova largura
|
|
57
|
+
* @param {number} height - Nova altura
|
|
58
|
+
* @param {boolean} preserveContent - Se deve preservar o conteúdo atual
|
|
59
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
60
|
+
*/
|
|
61
|
+
resize(width, height, preserveContent = true) {
|
|
62
|
+
if (width === this.width && height === this.height) {
|
|
63
|
+
return this; // Nenhuma alteração necessária
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (preserveContent) {
|
|
67
|
+
// Cria um novo canvas com as novas dimensões
|
|
68
|
+
const newCanvas = pureimage.make(width, height);
|
|
69
|
+
const newCtx = newCanvas.getContext("2d");
|
|
70
|
+
|
|
71
|
+
// Preenche o fundo com a cor especificada
|
|
72
|
+
newCtx.fillStyle = this.backgroundColor;
|
|
73
|
+
newCtx.fillRect(0, 0, width, height);
|
|
74
|
+
|
|
75
|
+
// Copia o conteúdo do canvas atual para o novo canvas
|
|
76
|
+
newCtx.drawImage(this.canvas, 0, 0, this.width, this.height, 0, 0, width, height);
|
|
77
|
+
|
|
78
|
+
// Atualiza as referências
|
|
79
|
+
this.canvas = newCanvas;
|
|
80
|
+
this.ctx = newCtx;
|
|
81
|
+
} else {
|
|
82
|
+
// Simplesmente cria um novo canvas vazio
|
|
83
|
+
this.width = width;
|
|
84
|
+
this.height = height;
|
|
85
|
+
this._initCanvas();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.width = width;
|
|
89
|
+
this.height = height;
|
|
90
|
+
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Carrega uma imagem no canvas
|
|
96
|
+
* @param {string|Buffer|Object} source - URL, Buffer ou caminho da imagem
|
|
97
|
+
* @param {Object} options - Opções de carregamento
|
|
98
|
+
* @returns {Promise<ImageProcessor>} - Instância atual para encadeamento
|
|
99
|
+
*/
|
|
100
|
+
async loadImage(source, options = {}) {
|
|
101
|
+
const {
|
|
102
|
+
x = 0,
|
|
103
|
+
y = 0,
|
|
104
|
+
width = null,
|
|
105
|
+
height = null,
|
|
106
|
+
fit = "contain", // contain, cover, fill, none
|
|
107
|
+
alignX = "center", // left, center, right
|
|
108
|
+
alignY = "center", // top, center, bottom
|
|
109
|
+
offsetX = 0,
|
|
110
|
+
offsetY = 0
|
|
111
|
+
} = options;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const img = await loadImageWithAxios(source);
|
|
115
|
+
|
|
116
|
+
// Calcula as dimensões de desenho
|
|
117
|
+
const sourceWidth = img.width;
|
|
118
|
+
const sourceHeight = img.height;
|
|
119
|
+
const sourceAspect = sourceWidth / sourceHeight;
|
|
120
|
+
|
|
121
|
+
let drawWidth = width || this.width;
|
|
122
|
+
let drawHeight = height || this.height;
|
|
123
|
+
const targetAspect = drawWidth / drawHeight;
|
|
124
|
+
|
|
125
|
+
// Ajusta as dimensões com base no modo de ajuste
|
|
126
|
+
if (fit === "contain") {
|
|
127
|
+
if (sourceAspect > targetAspect) {
|
|
128
|
+
drawHeight = drawWidth / sourceAspect;
|
|
129
|
+
} else {
|
|
130
|
+
drawWidth = drawHeight * sourceAspect;
|
|
131
|
+
}
|
|
132
|
+
} else if (fit === "cover") {
|
|
133
|
+
if (sourceAspect > targetAspect) {
|
|
134
|
+
drawWidth = drawHeight * sourceAspect;
|
|
135
|
+
} else {
|
|
136
|
+
drawHeight = drawWidth / sourceAspect;
|
|
137
|
+
}
|
|
138
|
+
} else if (fit === "none") {
|
|
139
|
+
drawWidth = sourceWidth;
|
|
140
|
+
drawHeight = sourceHeight;
|
|
141
|
+
}
|
|
142
|
+
// Para "fill", mantém as dimensões especificadas
|
|
143
|
+
|
|
144
|
+
// Calcula a posição com base no alinhamento
|
|
145
|
+
let drawX = x;
|
|
146
|
+
let drawY = y;
|
|
147
|
+
|
|
148
|
+
if (width !== null && fit !== "fill") {
|
|
149
|
+
if (alignX === "center") {
|
|
150
|
+
drawX += (width - drawWidth) / 2;
|
|
151
|
+
} else if (alignX === "right") {
|
|
152
|
+
drawX += width - drawWidth;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (height !== null && fit !== "fill") {
|
|
157
|
+
if (alignY === "center") {
|
|
158
|
+
drawY += (height - drawHeight) / 2;
|
|
159
|
+
} else if (alignY === "bottom") {
|
|
160
|
+
drawY += height - drawHeight;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Aplica os deslocamentos
|
|
165
|
+
drawX += offsetX;
|
|
166
|
+
drawY += offsetY;
|
|
167
|
+
|
|
168
|
+
// Desenha a imagem no canvas
|
|
169
|
+
this.ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
|
|
170
|
+
|
|
171
|
+
return this;
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error("Falha ao carregar imagem:", err.message);
|
|
174
|
+
throw new Error("Não foi possível carregar a imagem.");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Recorta o canvas para as dimensões especificadas
|
|
180
|
+
* @param {number} x - Posição X do recorte
|
|
181
|
+
* @param {number} y - Posição Y do recorte
|
|
182
|
+
* @param {number} width - Largura do recorte
|
|
183
|
+
* @param {number} height - Altura do recorte
|
|
184
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
185
|
+
*/
|
|
186
|
+
crop(x, y, width, height) {
|
|
187
|
+
// Garante que as coordenadas estão dentro dos limites do canvas
|
|
188
|
+
x = Math.max(0, Math.min(x, this.width));
|
|
189
|
+
y = Math.max(0, Math.min(y, this.height));
|
|
190
|
+
width = Math.min(width, this.width - x);
|
|
191
|
+
height = Math.min(height, this.height - y);
|
|
192
|
+
|
|
193
|
+
// Obtém os dados da imagem na área de recorte
|
|
194
|
+
const imageData = this.ctx.getImageData(x, y, width, height);
|
|
195
|
+
|
|
196
|
+
// Cria um novo canvas com as dimensões do recorte
|
|
197
|
+
const newCanvas = pureimage.make(width, height);
|
|
198
|
+
const newCtx = newCanvas.getContext("2d");
|
|
199
|
+
|
|
200
|
+
// Copia os dados da imagem para o novo canvas
|
|
201
|
+
newCtx.putImageData(imageData, 0, 0);
|
|
202
|
+
|
|
203
|
+
// Atualiza as referências
|
|
204
|
+
this.canvas = newCanvas;
|
|
205
|
+
this.ctx = newCtx;
|
|
206
|
+
this.width = width;
|
|
207
|
+
this.height = height;
|
|
208
|
+
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Rotaciona o canvas
|
|
214
|
+
* @param {number} angle - Ângulo de rotação em graus
|
|
215
|
+
* @param {boolean} resize - Se deve redimensionar o canvas para acomodar a imagem rotacionada
|
|
216
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
217
|
+
*/
|
|
218
|
+
rotate(angle, resize = true) {
|
|
219
|
+
// Converte o ângulo para radianos
|
|
220
|
+
const radians = (angle * Math.PI) / 180;
|
|
221
|
+
|
|
222
|
+
// Calcula as novas dimensões se resize for true
|
|
223
|
+
let newWidth = this.width;
|
|
224
|
+
let newHeight = this.height;
|
|
225
|
+
|
|
226
|
+
if (resize) {
|
|
227
|
+
// Calcula as novas dimensões para acomodar a imagem rotacionada
|
|
228
|
+
const cos = Math.abs(Math.cos(radians));
|
|
229
|
+
const sin = Math.abs(Math.sin(radians));
|
|
230
|
+
newWidth = Math.ceil(this.width * cos + this.height * sin);
|
|
231
|
+
newHeight = Math.ceil(this.width * sin + this.height * cos);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Cria um novo canvas com as novas dimensões
|
|
235
|
+
const newCanvas = pureimage.make(newWidth, newHeight);
|
|
236
|
+
const newCtx = newCanvas.getContext("2d");
|
|
237
|
+
|
|
238
|
+
// Preenche o fundo com a cor especificada
|
|
239
|
+
newCtx.fillStyle = this.backgroundColor;
|
|
240
|
+
newCtx.fillRect(0, 0, newWidth, newHeight);
|
|
241
|
+
|
|
242
|
+
// Translada para o centro do novo canvas
|
|
243
|
+
newCtx.translate(newWidth / 2, newHeight / 2);
|
|
244
|
+
|
|
245
|
+
// Rotaciona o contexto
|
|
246
|
+
newCtx.rotate(radians);
|
|
247
|
+
|
|
248
|
+
// Desenha a imagem original centralizada
|
|
249
|
+
newCtx.drawImage(
|
|
250
|
+
this.canvas,
|
|
251
|
+
-this.width / 2,
|
|
252
|
+
-this.height / 2,
|
|
253
|
+
this.width,
|
|
254
|
+
this.height
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Restaura a transformação
|
|
258
|
+
newCtx.setTransform(1, 0, 0, 1, 0, 0);
|
|
259
|
+
|
|
260
|
+
// Atualiza as referências
|
|
261
|
+
this.canvas = newCanvas;
|
|
262
|
+
this.ctx = newCtx;
|
|
263
|
+
this.width = newWidth;
|
|
264
|
+
this.height = newHeight;
|
|
265
|
+
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Espelha o canvas horizontalmente
|
|
271
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
272
|
+
*/
|
|
273
|
+
flipHorizontal() {
|
|
274
|
+
// Cria um novo canvas com as mesmas dimensões
|
|
275
|
+
const newCanvas = pureimage.make(this.width, this.height);
|
|
276
|
+
const newCtx = newCanvas.getContext("2d");
|
|
277
|
+
|
|
278
|
+
// Espelha horizontalmente
|
|
279
|
+
newCtx.scale(-1, 1);
|
|
280
|
+
newCtx.drawImage(this.canvas, -this.width, 0, this.width, this.height);
|
|
281
|
+
|
|
282
|
+
// Restaura a transformação
|
|
283
|
+
newCtx.setTransform(1, 0, 0, 1, 0, 0);
|
|
284
|
+
|
|
285
|
+
// Atualiza as referências
|
|
286
|
+
this.canvas = newCanvas;
|
|
287
|
+
this.ctx = newCtx;
|
|
288
|
+
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Espelha o canvas verticalmente
|
|
294
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
295
|
+
*/
|
|
296
|
+
flipVertical() {
|
|
297
|
+
// Cria um novo canvas com as mesmas dimensões
|
|
298
|
+
const newCanvas = pureimage.make(this.width, this.height);
|
|
299
|
+
const newCtx = newCanvas.getContext("2d");
|
|
300
|
+
|
|
301
|
+
// Espelha verticalmente
|
|
302
|
+
newCtx.scale(1, -1);
|
|
303
|
+
newCtx.drawImage(this.canvas, 0, -this.height, this.width, this.height);
|
|
304
|
+
|
|
305
|
+
// Restaura a transformação
|
|
306
|
+
newCtx.setTransform(1, 0, 0, 1, 0, 0);
|
|
307
|
+
|
|
308
|
+
// Atualiza as referências
|
|
309
|
+
this.canvas = newCanvas;
|
|
310
|
+
this.ctx = newCtx;
|
|
311
|
+
|
|
312
|
+
return this;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Adiciona uma borda ao canvas
|
|
317
|
+
* @param {number} thickness - Espessura da borda em pixels
|
|
318
|
+
* @param {string} color - Cor da borda (hexadecimal)
|
|
319
|
+
* @param {string} style - Estilo da borda ('solid', 'dashed', 'dotted')
|
|
320
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
321
|
+
*/
|
|
322
|
+
addBorder(thickness = 5, color = "#000000", style = "solid") {
|
|
323
|
+
this.ctx.strokeStyle = color;
|
|
324
|
+
this.ctx.lineWidth = thickness;
|
|
325
|
+
|
|
326
|
+
if (style === "dashed") {
|
|
327
|
+
this.ctx.setLineDash([thickness * 2, thickness]);
|
|
328
|
+
} else if (style === "dotted") {
|
|
329
|
+
this.ctx.setLineDash([thickness, thickness]);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
this.ctx.strokeRect(
|
|
333
|
+
thickness / 2,
|
|
334
|
+
thickness / 2,
|
|
335
|
+
this.width - thickness,
|
|
336
|
+
this.height - thickness
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
// Restaura o estilo de linha
|
|
340
|
+
this.ctx.setLineDash([]);
|
|
341
|
+
|
|
342
|
+
return this;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Adiciona uma marca d'água de texto ao canvas
|
|
347
|
+
* @param {string} text - Texto da marca d'água
|
|
348
|
+
* @param {Object} options - Opções da marca d'água
|
|
349
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
350
|
+
*/
|
|
351
|
+
addWatermark(text, options = {}) {
|
|
352
|
+
const {
|
|
353
|
+
font = "Arial",
|
|
354
|
+
fontSize = 30,
|
|
355
|
+
color = "#000000",
|
|
356
|
+
opacity = 0.3,
|
|
357
|
+
angle = -30,
|
|
358
|
+
x = this.width / 2,
|
|
359
|
+
y = this.height / 2
|
|
360
|
+
} = options;
|
|
361
|
+
|
|
362
|
+
filters.applyWatermark(
|
|
363
|
+
this.ctx,
|
|
364
|
+
0,
|
|
365
|
+
0,
|
|
366
|
+
this.width,
|
|
367
|
+
this.height,
|
|
368
|
+
text,
|
|
369
|
+
font,
|
|
370
|
+
fontSize,
|
|
371
|
+
color,
|
|
372
|
+
opacity,
|
|
373
|
+
angle
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
return this;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Adiciona uma marca d'água de imagem ao canvas
|
|
381
|
+
* @param {string|Buffer|Object} source - URL, Buffer ou caminho da imagem
|
|
382
|
+
* @param {Object} options - Opções da marca d'água
|
|
383
|
+
* @returns {Promise<ImageProcessor>} - Instância atual para encadeamento
|
|
384
|
+
*/
|
|
385
|
+
async addImageWatermark(source, options = {}) {
|
|
386
|
+
const {
|
|
387
|
+
x = this.width / 2,
|
|
388
|
+
y = this.height / 2,
|
|
389
|
+
width = this.width * 0.3,
|
|
390
|
+
height = null,
|
|
391
|
+
opacity = 0.5,
|
|
392
|
+
align = "center", // center, topLeft, topRight, bottomLeft, bottomRight
|
|
393
|
+
offsetX = 0,
|
|
394
|
+
offsetY = 0
|
|
395
|
+
} = options;
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
const img = await loadImageWithAxios(source);
|
|
399
|
+
|
|
400
|
+
// Calcula as dimensões proporcionais
|
|
401
|
+
const imgAspect = img.width / img.height;
|
|
402
|
+
let drawWidth = width;
|
|
403
|
+
let drawHeight = height || width / imgAspect;
|
|
404
|
+
|
|
405
|
+
if (!height) {
|
|
406
|
+
drawHeight = drawWidth / imgAspect;
|
|
407
|
+
} else {
|
|
408
|
+
drawWidth = drawHeight * imgAspect;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Calcula a posição com base no alinhamento
|
|
412
|
+
let drawX = x;
|
|
413
|
+
let drawY = y;
|
|
414
|
+
const padding = 10; // Padding padrão para posicionamento
|
|
415
|
+
|
|
416
|
+
switch (align) {
|
|
417
|
+
case "topLeft":
|
|
418
|
+
drawX = padding + offsetX;
|
|
419
|
+
drawY = padding + offsetY;
|
|
420
|
+
break;
|
|
421
|
+
case "topRight":
|
|
422
|
+
drawX = this.width - drawWidth - padding + offsetX;
|
|
423
|
+
drawY = padding + offsetY;
|
|
424
|
+
break;
|
|
425
|
+
case "bottomLeft":
|
|
426
|
+
drawX = padding + offsetX;
|
|
427
|
+
drawY = this.height - drawHeight - padding + offsetY;
|
|
428
|
+
break;
|
|
429
|
+
case "bottomRight":
|
|
430
|
+
drawX = this.width - drawWidth - padding + offsetX;
|
|
431
|
+
drawY = this.height - drawHeight - padding + offsetY;
|
|
432
|
+
break;
|
|
433
|
+
case "center":
|
|
434
|
+
default:
|
|
435
|
+
drawX = (this.width - drawWidth) / 2 + offsetX;
|
|
436
|
+
drawY = (this.height - drawHeight) / 2 + offsetY;
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Salva o estado atual do contexto
|
|
441
|
+
this.ctx.save();
|
|
442
|
+
|
|
443
|
+
// Define a opacidade
|
|
444
|
+
this.ctx.globalAlpha = opacity;
|
|
445
|
+
|
|
446
|
+
// Desenha a imagem
|
|
447
|
+
this.ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
|
|
448
|
+
|
|
449
|
+
// Restaura o estado do contexto
|
|
450
|
+
this.ctx.restore();
|
|
451
|
+
|
|
452
|
+
return this;
|
|
453
|
+
} catch (err) {
|
|
454
|
+
console.error("Falha ao adicionar marca d'água de imagem:", err.message);
|
|
455
|
+
throw new Error("Não foi possível adicionar a marca d'água de imagem.");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Aplica um filtro ao canvas
|
|
461
|
+
* @param {string} filterName - Nome do filtro a ser aplicado
|
|
462
|
+
* @param {Object} options - Opções do filtro
|
|
463
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
464
|
+
*/
|
|
465
|
+
applyFilter(filterName, options = {}) {
|
|
466
|
+
// Verifica se o filtro existe
|
|
467
|
+
if (!filters[filterName]) {
|
|
468
|
+
throw new Error(`Filtro "${filterName}" não encontrado.`);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Aplica o filtro com os parâmetros fornecidos
|
|
472
|
+
filters[filterName](this.ctx, 0, 0, this.width, this.height, ...Object.values(options));
|
|
473
|
+
|
|
474
|
+
return this;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Aplica múltiplos filtros ao canvas
|
|
479
|
+
* @param {Array} filtersList - Lista de filtros a serem aplicados
|
|
480
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
481
|
+
*/
|
|
482
|
+
applyFilters(filtersList) {
|
|
483
|
+
for (const filter of filtersList) {
|
|
484
|
+
this.applyFilter(filter.name, filter.options || {});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return this;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Adiciona texto ao canvas
|
|
492
|
+
* @param {string} text - Texto a ser adicionado
|
|
493
|
+
* @param {Object} options - Opções do texto
|
|
494
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
495
|
+
*/
|
|
496
|
+
addText(text, options = {}) {
|
|
497
|
+
const {
|
|
498
|
+
x = this.width / 2,
|
|
499
|
+
y = this.height / 2,
|
|
500
|
+
font = "Arial",
|
|
501
|
+
fontSize = 30,
|
|
502
|
+
color = "#000000",
|
|
503
|
+
align = "center", // left, center, right
|
|
504
|
+
baseline = "middle", // top, middle, bottom
|
|
505
|
+
maxWidth = null,
|
|
506
|
+
lineHeight = 1.2,
|
|
507
|
+
shadow = false,
|
|
508
|
+
shadowColor = "rgba(0, 0, 0, 0.5)",
|
|
509
|
+
shadowBlur = 3,
|
|
510
|
+
shadowOffsetX = 1,
|
|
511
|
+
shadowOffsetY = 1,
|
|
512
|
+
stroke = false,
|
|
513
|
+
strokeColor = "#000000",
|
|
514
|
+
strokeWidth = 1,
|
|
515
|
+
background = false,
|
|
516
|
+
backgroundColor = "rgba(255, 255, 255, 0.5)",
|
|
517
|
+
padding = 5,
|
|
518
|
+
rotate = 0
|
|
519
|
+
} = options;
|
|
520
|
+
|
|
521
|
+
// Salva o estado atual do contexto
|
|
522
|
+
this.ctx.save();
|
|
523
|
+
|
|
524
|
+
// Configura o texto
|
|
525
|
+
this.ctx.font = `${fontSize}px ${font}`;
|
|
526
|
+
this.ctx.fillStyle = color;
|
|
527
|
+
this.ctx.textAlign = align;
|
|
528
|
+
this.ctx.textBaseline = baseline;
|
|
529
|
+
|
|
530
|
+
// Aplica rotação se necessário
|
|
531
|
+
if (rotate !== 0) {
|
|
532
|
+
this.ctx.translate(x, y);
|
|
533
|
+
this.ctx.rotate((rotate * Math.PI) / 180);
|
|
534
|
+
this.ctx.translate(-x, -y);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Aplica sombra se necessário
|
|
538
|
+
if (shadow) {
|
|
539
|
+
this.ctx.shadowColor = shadowColor;
|
|
540
|
+
this.ctx.shadowBlur = shadowBlur;
|
|
541
|
+
this.ctx.shadowOffsetX = shadowOffsetX;
|
|
542
|
+
this.ctx.shadowOffsetY = shadowOffsetY;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Se maxWidth for fornecido, quebra o texto em linhas
|
|
546
|
+
if (maxWidth) {
|
|
547
|
+
const words = text.split(" ");
|
|
548
|
+
const lines = [];
|
|
549
|
+
let currentLine = words[0];
|
|
550
|
+
|
|
551
|
+
for (let i = 1; i < words.length; i++) {
|
|
552
|
+
const word = words[i];
|
|
553
|
+
const width = this.ctx.measureText(currentLine + " " + word).width;
|
|
554
|
+
|
|
555
|
+
if (width < maxWidth) {
|
|
556
|
+
currentLine += " " + word;
|
|
557
|
+
} else {
|
|
558
|
+
lines.push(currentLine);
|
|
559
|
+
currentLine = word;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
lines.push(currentLine);
|
|
564
|
+
|
|
565
|
+
// Desenha o fundo se necessário
|
|
566
|
+
if (background) {
|
|
567
|
+
let textWidth = 0;
|
|
568
|
+
for (const line of lines) {
|
|
569
|
+
const lineWidth = this.ctx.measureText(line).width;
|
|
570
|
+
textWidth = Math.max(textWidth, lineWidth);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const textHeight = lines.length * fontSize * lineHeight;
|
|
574
|
+
let bgX, bgY;
|
|
575
|
+
|
|
576
|
+
switch (align) {
|
|
577
|
+
case "left":
|
|
578
|
+
bgX = x;
|
|
579
|
+
break;
|
|
580
|
+
case "right":
|
|
581
|
+
bgX = x - textWidth;
|
|
582
|
+
break;
|
|
583
|
+
case "center":
|
|
584
|
+
default:
|
|
585
|
+
bgX = x - textWidth / 2;
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
switch (baseline) {
|
|
590
|
+
case "top":
|
|
591
|
+
bgY = y;
|
|
592
|
+
break;
|
|
593
|
+
case "bottom":
|
|
594
|
+
bgY = y - textHeight;
|
|
595
|
+
break;
|
|
596
|
+
case "middle":
|
|
597
|
+
default:
|
|
598
|
+
bgY = y - textHeight / 2;
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
this.ctx.fillStyle = backgroundColor;
|
|
603
|
+
this.ctx.fillRect(
|
|
604
|
+
bgX - padding,
|
|
605
|
+
bgY - padding,
|
|
606
|
+
textWidth + padding * 2,
|
|
607
|
+
textHeight + padding * 2
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
this.ctx.fillStyle = color;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Desenha cada linha
|
|
614
|
+
for (let i = 0; i < lines.length; i++) {
|
|
615
|
+
const line = lines[i];
|
|
616
|
+
const lineY = y + (i - (lines.length - 1) / 2) * fontSize * lineHeight;
|
|
617
|
+
|
|
618
|
+
if (stroke) {
|
|
619
|
+
this.ctx.strokeStyle = strokeColor;
|
|
620
|
+
this.ctx.lineWidth = strokeWidth;
|
|
621
|
+
this.ctx.strokeText(line, x, lineY);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
this.ctx.fillText(line, x, lineY);
|
|
625
|
+
}
|
|
626
|
+
} else {
|
|
627
|
+
// Desenha o fundo se necessário
|
|
628
|
+
if (background) {
|
|
629
|
+
const textWidth = this.ctx.measureText(text).width;
|
|
630
|
+
const textHeight = fontSize;
|
|
631
|
+
let bgX, bgY;
|
|
632
|
+
|
|
633
|
+
switch (align) {
|
|
634
|
+
case "left":
|
|
635
|
+
bgX = x;
|
|
636
|
+
break;
|
|
637
|
+
case "right":
|
|
638
|
+
bgX = x - textWidth;
|
|
639
|
+
break;
|
|
640
|
+
case "center":
|
|
641
|
+
default:
|
|
642
|
+
bgX = x - textWidth / 2;
|
|
643
|
+
break;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
switch (baseline) {
|
|
647
|
+
case "top":
|
|
648
|
+
bgY = y;
|
|
649
|
+
break;
|
|
650
|
+
case "bottom":
|
|
651
|
+
bgY = y - textHeight;
|
|
652
|
+
break;
|
|
653
|
+
case "middle":
|
|
654
|
+
default:
|
|
655
|
+
bgY = y - textHeight / 2;
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
this.ctx.fillStyle = backgroundColor;
|
|
660
|
+
this.ctx.fillRect(
|
|
661
|
+
bgX - padding,
|
|
662
|
+
bgY - padding,
|
|
663
|
+
textWidth + padding * 2,
|
|
664
|
+
textHeight + padding * 2
|
|
665
|
+
);
|
|
666
|
+
|
|
667
|
+
this.ctx.fillStyle = color;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Desenha o texto
|
|
671
|
+
if (stroke) {
|
|
672
|
+
this.ctx.strokeStyle = strokeColor;
|
|
673
|
+
this.ctx.lineWidth = strokeWidth;
|
|
674
|
+
this.ctx.strokeText(text, x, y);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
this.ctx.fillText(text, x, y);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Restaura o estado do contexto
|
|
681
|
+
this.ctx.restore();
|
|
682
|
+
|
|
683
|
+
return this;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Adiciona uma forma ao canvas
|
|
688
|
+
* @param {string} shape - Tipo de forma ('rectangle', 'circle', 'ellipse', 'polygon')
|
|
689
|
+
* @param {Object} options - Opções da forma
|
|
690
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
691
|
+
*/
|
|
692
|
+
addShape(shape, options = {}) {
|
|
693
|
+
const {
|
|
694
|
+
x = this.width / 2,
|
|
695
|
+
y = this.height / 2,
|
|
696
|
+
width = 100,
|
|
697
|
+
height = 100,
|
|
698
|
+
radius = 50,
|
|
699
|
+
radiusX = 50,
|
|
700
|
+
radiusY = 30,
|
|
701
|
+
sides = 6,
|
|
702
|
+
rotation = 0,
|
|
703
|
+
fill = true,
|
|
704
|
+
fillColor = "#000000",
|
|
705
|
+
stroke = false,
|
|
706
|
+
strokeColor = "#000000",
|
|
707
|
+
strokeWidth = 1,
|
|
708
|
+
opacity = 1
|
|
709
|
+
} = options;
|
|
710
|
+
|
|
711
|
+
// Salva o estado atual do contexto
|
|
712
|
+
this.ctx.save();
|
|
713
|
+
|
|
714
|
+
// Define a opacidade
|
|
715
|
+
this.ctx.globalAlpha = opacity;
|
|
716
|
+
|
|
717
|
+
// Configura o estilo de preenchimento e contorno
|
|
718
|
+
this.ctx.fillStyle = fillColor;
|
|
719
|
+
this.ctx.strokeStyle = strokeColor;
|
|
720
|
+
this.ctx.lineWidth = strokeWidth;
|
|
721
|
+
|
|
722
|
+
// Desenha a forma com base no tipo
|
|
723
|
+
switch (shape) {
|
|
724
|
+
case "rectangle":
|
|
725
|
+
if (fill) {
|
|
726
|
+
this.ctx.fillRect(x - width / 2, y - height / 2, width, height);
|
|
727
|
+
}
|
|
728
|
+
if (stroke) {
|
|
729
|
+
this.ctx.strokeRect(x - width / 2, y - height / 2, width, height);
|
|
730
|
+
}
|
|
731
|
+
break;
|
|
732
|
+
|
|
733
|
+
case "circle":
|
|
734
|
+
this.ctx.beginPath();
|
|
735
|
+
this.ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
736
|
+
if (fill) {
|
|
737
|
+
this.ctx.fill();
|
|
738
|
+
}
|
|
739
|
+
if (stroke) {
|
|
740
|
+
this.ctx.stroke();
|
|
741
|
+
}
|
|
742
|
+
break;
|
|
743
|
+
|
|
744
|
+
case "ellipse":
|
|
745
|
+
this.ctx.beginPath();
|
|
746
|
+
this.ctx.ellipse(x, y, radiusX, radiusY, 0, 0, Math.PI * 2);
|
|
747
|
+
if (fill) {
|
|
748
|
+
this.ctx.fill();
|
|
749
|
+
}
|
|
750
|
+
if (stroke) {
|
|
751
|
+
this.ctx.stroke();
|
|
752
|
+
}
|
|
753
|
+
break;
|
|
754
|
+
|
|
755
|
+
case "polygon":
|
|
756
|
+
this.ctx.beginPath();
|
|
757
|
+
|
|
758
|
+
const rotationInRadians = (rotation * Math.PI) / 180;
|
|
759
|
+
|
|
760
|
+
for (let i = 0; i < sides; i++) {
|
|
761
|
+
const angle = rotationInRadians + (i * 2 * Math.PI) / sides;
|
|
762
|
+
const pointX = x + radius * Math.cos(angle);
|
|
763
|
+
const pointY = y + radius * Math.sin(angle);
|
|
764
|
+
|
|
765
|
+
if (i === 0) {
|
|
766
|
+
this.ctx.moveTo(pointX, pointY);
|
|
767
|
+
} else {
|
|
768
|
+
this.ctx.lineTo(pointX, pointY);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
this.ctx.closePath();
|
|
773
|
+
|
|
774
|
+
if (fill) {
|
|
775
|
+
this.ctx.fill();
|
|
776
|
+
}
|
|
777
|
+
if (stroke) {
|
|
778
|
+
this.ctx.stroke();
|
|
779
|
+
}
|
|
780
|
+
break;
|
|
781
|
+
|
|
782
|
+
default:
|
|
783
|
+
throw new Error(`Forma "${shape}" não suportada.`);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Restaura o estado do contexto
|
|
787
|
+
this.ctx.restore();
|
|
788
|
+
|
|
789
|
+
return this;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Adiciona um gradiente ao canvas
|
|
794
|
+
* @param {string} type - Tipo de gradiente ('linear', 'radial')
|
|
795
|
+
* @param {Object} options - Opções do gradiente
|
|
796
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
797
|
+
*/
|
|
798
|
+
addGradient(type, options = {}) {
|
|
799
|
+
const {
|
|
800
|
+
x1 = 0,
|
|
801
|
+
y1 = 0,
|
|
802
|
+
x2 = this.width,
|
|
803
|
+
y2 = this.height,
|
|
804
|
+
centerX = this.width / 2,
|
|
805
|
+
centerY = this.height / 2,
|
|
806
|
+
radius1 = 0,
|
|
807
|
+
radius2 = Math.max(this.width, this.height) / 2,
|
|
808
|
+
colors = ["#000000", "#FFFFFF"],
|
|
809
|
+
stops = null,
|
|
810
|
+
opacity = 1
|
|
811
|
+
} = options;
|
|
812
|
+
|
|
813
|
+
// Salva o estado atual do contexto
|
|
814
|
+
this.ctx.save();
|
|
815
|
+
|
|
816
|
+
// Define a opacidade
|
|
817
|
+
this.ctx.globalAlpha = opacity;
|
|
818
|
+
|
|
819
|
+
// Cria o gradiente com base no tipo
|
|
820
|
+
let gradient;
|
|
821
|
+
|
|
822
|
+
if (type === "linear") {
|
|
823
|
+
gradient = this.ctx.createLinearGradient(x1, y1, x2, y2);
|
|
824
|
+
} else if (type === "radial") {
|
|
825
|
+
gradient = this.ctx.createRadialGradient(
|
|
826
|
+
centerX,
|
|
827
|
+
centerY,
|
|
828
|
+
radius1,
|
|
829
|
+
centerX,
|
|
830
|
+
centerY,
|
|
831
|
+
radius2
|
|
832
|
+
);
|
|
833
|
+
} else {
|
|
834
|
+
throw new Error(`Tipo de gradiente "${type}" não suportado.`);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Adiciona as cores ao gradiente
|
|
838
|
+
if (stops) {
|
|
839
|
+
// Se os stops forem fornecidos, usa-os
|
|
840
|
+
for (let i = 0; i < colors.length; i++) {
|
|
841
|
+
gradient.addColorStop(stops[i], colors[i]);
|
|
842
|
+
}
|
|
843
|
+
} else {
|
|
844
|
+
// Caso contrário, distribui as cores uniformemente
|
|
845
|
+
for (let i = 0; i < colors.length; i++) {
|
|
846
|
+
gradient.addColorStop(i / (colors.length - 1), colors[i]);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// Preenche o canvas com o gradiente
|
|
851
|
+
this.ctx.fillStyle = gradient;
|
|
852
|
+
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
853
|
+
|
|
854
|
+
// Restaura o estado do contexto
|
|
855
|
+
this.ctx.restore();
|
|
856
|
+
|
|
857
|
+
return this;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Adiciona um padrão ao canvas
|
|
862
|
+
* @param {string} type - Tipo de padrão ('dots', 'lines', 'grid', 'checkerboard')
|
|
863
|
+
* @param {Object} options - Opções do padrão
|
|
864
|
+
* @returns {ImageProcessor} - Instância atual para encadeamento
|
|
865
|
+
*/
|
|
866
|
+
addPattern(type, options = {}) {
|
|
867
|
+
const {
|
|
868
|
+
color = "#000000",
|
|
869
|
+
backgroundColor = null,
|
|
870
|
+
size = 10,
|
|
871
|
+
spacing = 20,
|
|
872
|
+
lineWidth = 1,
|
|
873
|
+
angle = 0,
|
|
874
|
+
opacity = 0.2
|
|
875
|
+
} = options;
|
|
876
|
+
|
|
877
|
+
// Salva o estado atual do contexto
|
|
878
|
+
this.ctx.save();
|
|
879
|
+
|
|
880
|
+
// Define a opacidade
|
|
881
|
+
this.ctx.globalAlpha = opacity;
|
|
882
|
+
|
|
883
|
+
// Preenche o fundo se fornecido
|
|
884
|
+
if (backgroundColor) {
|
|
885
|
+
this.ctx.fillStyle = backgroundColor;
|
|
886
|
+
this.ctx.fillRect(0, 0, this.width, this.height);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// Configura o estilo de desenho
|
|
890
|
+
this.ctx.fillStyle = color;
|
|
891
|
+
this.ctx.strokeStyle = color;
|
|
892
|
+
this.ctx.lineWidth = lineWidth;
|
|
893
|
+
|
|
894
|
+
// Aplica rotação se necessário
|
|
895
|
+
if (angle !== 0) {
|
|
896
|
+
this.ctx.translate(this.width / 2, this.height / 2);
|
|
897
|
+
this.ctx.rotate((angle * Math.PI) / 180);
|
|
898
|
+
this.ctx.translate(-this.width / 2, -this.height / 2);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Desenha o padrão com base no tipo
|
|
902
|
+
switch (type) {
|
|
903
|
+
case "dots":
|
|
904
|
+
for (let y = spacing / 2; y < this.height + spacing; y += spacing) {
|
|
905
|
+
for (let x = spacing / 2; x < this.width + spacing; x += spacing) {
|
|
906
|
+
this.ctx.beginPath();
|
|
907
|
+
this.ctx.arc(x, y, size / 2, 0, Math.PI * 2);
|
|
908
|
+
this.ctx.fill();
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
break;
|
|
912
|
+
|
|
913
|
+
case "lines":
|
|
914
|
+
for (let y = 0; y < this.height + spacing; y += spacing) {
|
|
915
|
+
this.ctx.beginPath();
|
|
916
|
+
this.ctx.moveTo(0, y);
|
|
917
|
+
this.ctx.lineTo(this.width, y);
|
|
918
|
+
this.ctx.stroke();
|
|
919
|
+
}
|
|
920
|
+
break;
|
|
921
|
+
|
|
922
|
+
case "grid":
|
|
923
|
+
// Linhas horizontais
|
|
924
|
+
for (let y = 0; y < this.height + spacing; y += spacing) {
|
|
925
|
+
this.ctx.beginPath();
|
|
926
|
+
this.ctx.moveTo(0, y);
|
|
927
|
+
this.ctx.lineTo(this.width, y);
|
|
928
|
+
this.ctx.stroke();
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// Linhas verticais
|
|
932
|
+
for (let x = 0; x < this.width + spacing; x += spacing) {
|
|
933
|
+
this.ctx.beginPath();
|
|
934
|
+
this.ctx.moveTo(x, 0);
|
|
935
|
+
this.ctx.lineTo(x, this.height);
|
|
936
|
+
this.ctx.stroke();
|
|
937
|
+
}
|
|
938
|
+
break;
|
|
939
|
+
|
|
940
|
+
case "checkerboard":
|
|
941
|
+
const cellSize = spacing;
|
|
942
|
+
|
|
943
|
+
for (let y = 0; y < this.height + cellSize; y += cellSize) {
|
|
944
|
+
for (let x = 0; x < this.width + cellSize; x += cellSize) {
|
|
945
|
+
if ((Math.floor(x / cellSize) + Math.floor(y / cellSize)) % 2 === 0) {
|
|
946
|
+
this.ctx.fillRect(x, y, cellSize, cellSize);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
break;
|
|
951
|
+
|
|
952
|
+
default:
|
|
953
|
+
throw new Error(`Tipo de padrão "${type}" não suportado.`);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Restaura o estado do contexto
|
|
957
|
+
this.ctx.restore();
|
|
958
|
+
|
|
959
|
+
return this;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Converte o canvas para um buffer de imagem
|
|
964
|
+
* @param {string} format - Formato da imagem ('png', 'jpeg')
|
|
965
|
+
* @param {Object} options - Opções de codificação
|
|
966
|
+
* @returns {Promise<Buffer>} - Buffer contendo a imagem
|
|
967
|
+
*/
|
|
968
|
+
async toBuffer(format = "png", options = {}) {
|
|
969
|
+
const { quality = this.quality } = options;
|
|
970
|
+
|
|
971
|
+
try {
|
|
972
|
+
return await encodeToBuffer(this.canvas, format, quality);
|
|
973
|
+
} catch (err) {
|
|
974
|
+
console.error("Falha ao codificar a imagem:", err.message);
|
|
975
|
+
throw new Error("Não foi possível gerar o buffer de imagem.");
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Salva o canvas em um arquivo
|
|
981
|
+
* @param {string} filePath - Caminho do arquivo
|
|
982
|
+
* @param {string} format - Formato da imagem ('png', 'jpeg')
|
|
983
|
+
* @param {Object} options - Opções de codificação
|
|
984
|
+
* @returns {Promise<string>} - Caminho do arquivo salvo
|
|
985
|
+
*/
|
|
986
|
+
async saveToFile(filePath, format = null, options = {}) {
|
|
987
|
+
// Determina o formato com base na extensão do arquivo se não for fornecido
|
|
988
|
+
if (!format) {
|
|
989
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
990
|
+
format = ext === ".jpg" || ext === ".jpeg" ? "jpeg" : "png";
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
try {
|
|
994
|
+
const buffer = await this.toBuffer(format, options);
|
|
995
|
+
|
|
996
|
+
// Cria o diretório se não existir
|
|
997
|
+
const dir = path.dirname(filePath);
|
|
998
|
+
if (!fs.existsSync(dir)) {
|
|
999
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Salva o buffer no arquivo
|
|
1003
|
+
fs.writeFileSync(filePath, buffer);
|
|
1004
|
+
|
|
1005
|
+
return filePath;
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
console.error("Falha ao salvar a imagem:", err.message);
|
|
1008
|
+
throw new Error("Não foi possível salvar a imagem no arquivo.");
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
module.exports = ImageProcessor;
|
|
1014
|
+
|