@cognima/banners
Version:
Biblioteca avançada para geração de banners dinâmicos para diversas plataformas e aplicações
1,014 lines (862 loc) • 28.6 kB
JavaScript
"use strict";
/**
* Módulo de Processamento de Imagem
*
* Este módulo fornece funções para processamento avançado de imagens,
* incluindo redimensionamento, recorte, composição e outros ajustes.
*
* @author Cognima Team (melhorado)
* @version 2.0.0
*/
Object.defineProperty(exports, "__esModule", { value: true });
const pureimage = require("pureimage");
const path = require("path");
const fs = require("fs");
const { loadImageWithAxios, encodeToBuffer } = require("../utils");
const filters = require("./image-filters");
/**
* Classe para processamento avançado de imagens
*/
class ImageProcessor {
/**
* Cria uma nova instância do processador de imagens
* @param {Object} options - Opções de configuração
*/
constructor(options = {}) {
this.canvas = null;
this.ctx = null;
this.width = options.width || 800;
this.height = options.height || 600;
this.backgroundColor = options.backgroundColor || "#FFFFFF";
this.quality = options.quality || 0.9;
// Inicializa o canvas
this._initCanvas();
}
/**
* Inicializa o canvas com as dimensões especificadas
* @private
*/
_initCanvas() {
this.canvas = pureimage.make(this.width, this.height);
this.ctx = this.canvas.getContext("2d");
// Preenche o fundo com a cor especificada
this.ctx.fillStyle = this.backgroundColor;
this.ctx.fillRect(0, 0, this.width, this.height);
}
/**
* Redimensiona o canvas
* @param {number} width - Nova largura
* @param {number} height - Nova altura
* @param {boolean} preserveContent - Se deve preservar o conteúdo atual
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
resize(width, height, preserveContent = true) {
if (width === this.width && height === this.height) {
return this; // Nenhuma alteração necessária
}
if (preserveContent) {
// Cria um novo canvas com as novas dimensões
const newCanvas = pureimage.make(width, height);
const newCtx = newCanvas.getContext("2d");
// Preenche o fundo com a cor especificada
newCtx.fillStyle = this.backgroundColor;
newCtx.fillRect(0, 0, width, height);
// Copia o conteúdo do canvas atual para o novo canvas
newCtx.drawImage(this.canvas, 0, 0, this.width, this.height, 0, 0, width, height);
// Atualiza as referências
this.canvas = newCanvas;
this.ctx = newCtx;
} else {
// Simplesmente cria um novo canvas vazio
this.width = width;
this.height = height;
this._initCanvas();
}
this.width = width;
this.height = height;
return this;
}
/**
* Carrega uma imagem no canvas
* @param {string|Buffer|Object} source - URL, Buffer ou caminho da imagem
* @param {Object} options - Opções de carregamento
* @returns {Promise<ImageProcessor>} - Instância atual para encadeamento
*/
async loadImage(source, options = {}) {
const {
x = 0,
y = 0,
width = null,
height = null,
fit = "contain", // contain, cover, fill, none
alignX = "center", // left, center, right
alignY = "center", // top, center, bottom
offsetX = 0,
offsetY = 0
} = options;
try {
const img = await loadImageWithAxios(source);
// Calcula as dimensões de desenho
const sourceWidth = img.width;
const sourceHeight = img.height;
const sourceAspect = sourceWidth / sourceHeight;
let drawWidth = width || this.width;
let drawHeight = height || this.height;
const targetAspect = drawWidth / drawHeight;
// Ajusta as dimensões com base no modo de ajuste
if (fit === "contain") {
if (sourceAspect > targetAspect) {
drawHeight = drawWidth / sourceAspect;
} else {
drawWidth = drawHeight * sourceAspect;
}
} else if (fit === "cover") {
if (sourceAspect > targetAspect) {
drawWidth = drawHeight * sourceAspect;
} else {
drawHeight = drawWidth / sourceAspect;
}
} else if (fit === "none") {
drawWidth = sourceWidth;
drawHeight = sourceHeight;
}
// Para "fill", mantém as dimensões especificadas
// Calcula a posição com base no alinhamento
let drawX = x;
let drawY = y;
if (width !== null && fit !== "fill") {
if (alignX === "center") {
drawX += (width - drawWidth) / 2;
} else if (alignX === "right") {
drawX += width - drawWidth;
}
}
if (height !== null && fit !== "fill") {
if (alignY === "center") {
drawY += (height - drawHeight) / 2;
} else if (alignY === "bottom") {
drawY += height - drawHeight;
}
}
// Aplica os deslocamentos
drawX += offsetX;
drawY += offsetY;
// Desenha a imagem no canvas
this.ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
return this;
} catch (err) {
console.error("Falha ao carregar imagem:", err.message);
throw new Error("Não foi possível carregar a imagem.");
}
}
/**
* Recorta o canvas para as dimensões especificadas
* @param {number} x - Posição X do recorte
* @param {number} y - Posição Y do recorte
* @param {number} width - Largura do recorte
* @param {number} height - Altura do recorte
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
crop(x, y, width, height) {
// Garante que as coordenadas estão dentro dos limites do canvas
x = Math.max(0, Math.min(x, this.width));
y = Math.max(0, Math.min(y, this.height));
width = Math.min(width, this.width - x);
height = Math.min(height, this.height - y);
// Obtém os dados da imagem na área de recorte
const imageData = this.ctx.getImageData(x, y, width, height);
// Cria um novo canvas com as dimensões do recorte
const newCanvas = pureimage.make(width, height);
const newCtx = newCanvas.getContext("2d");
// Copia os dados da imagem para o novo canvas
newCtx.putImageData(imageData, 0, 0);
// Atualiza as referências
this.canvas = newCanvas;
this.ctx = newCtx;
this.width = width;
this.height = height;
return this;
}
/**
* Rotaciona o canvas
* @param {number} angle - Ângulo de rotação em graus
* @param {boolean} resize - Se deve redimensionar o canvas para acomodar a imagem rotacionada
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
rotate(angle, resize = true) {
// Converte o ângulo para radianos
const radians = (angle * Math.PI) / 180;
// Calcula as novas dimensões se resize for true
let newWidth = this.width;
let newHeight = this.height;
if (resize) {
// Calcula as novas dimensões para acomodar a imagem rotacionada
const cos = Math.abs(Math.cos(radians));
const sin = Math.abs(Math.sin(radians));
newWidth = Math.ceil(this.width * cos + this.height * sin);
newHeight = Math.ceil(this.width * sin + this.height * cos);
}
// Cria um novo canvas com as novas dimensões
const newCanvas = pureimage.make(newWidth, newHeight);
const newCtx = newCanvas.getContext("2d");
// Preenche o fundo com a cor especificada
newCtx.fillStyle = this.backgroundColor;
newCtx.fillRect(0, 0, newWidth, newHeight);
// Translada para o centro do novo canvas
newCtx.translate(newWidth / 2, newHeight / 2);
// Rotaciona o contexto
newCtx.rotate(radians);
// Desenha a imagem original centralizada
newCtx.drawImage(
this.canvas,
-this.width / 2,
-this.height / 2,
this.width,
this.height
);
// Restaura a transformação
newCtx.setTransform(1, 0, 0, 1, 0, 0);
// Atualiza as referências
this.canvas = newCanvas;
this.ctx = newCtx;
this.width = newWidth;
this.height = newHeight;
return this;
}
/**
* Espelha o canvas horizontalmente
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
flipHorizontal() {
// Cria um novo canvas com as mesmas dimensões
const newCanvas = pureimage.make(this.width, this.height);
const newCtx = newCanvas.getContext("2d");
// Espelha horizontalmente
newCtx.scale(-1, 1);
newCtx.drawImage(this.canvas, -this.width, 0, this.width, this.height);
// Restaura a transformação
newCtx.setTransform(1, 0, 0, 1, 0, 0);
// Atualiza as referências
this.canvas = newCanvas;
this.ctx = newCtx;
return this;
}
/**
* Espelha o canvas verticalmente
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
flipVertical() {
// Cria um novo canvas com as mesmas dimensões
const newCanvas = pureimage.make(this.width, this.height);
const newCtx = newCanvas.getContext("2d");
// Espelha verticalmente
newCtx.scale(1, -1);
newCtx.drawImage(this.canvas, 0, -this.height, this.width, this.height);
// Restaura a transformação
newCtx.setTransform(1, 0, 0, 1, 0, 0);
// Atualiza as referências
this.canvas = newCanvas;
this.ctx = newCtx;
return this;
}
/**
* Adiciona uma borda ao canvas
* @param {number} thickness - Espessura da borda em pixels
* @param {string} color - Cor da borda (hexadecimal)
* @param {string} style - Estilo da borda ('solid', 'dashed', 'dotted')
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
addBorder(thickness = 5, color = "#000000", style = "solid") {
this.ctx.strokeStyle = color;
this.ctx.lineWidth = thickness;
if (style === "dashed") {
this.ctx.setLineDash([thickness * 2, thickness]);
} else if (style === "dotted") {
this.ctx.setLineDash([thickness, thickness]);
}
this.ctx.strokeRect(
thickness / 2,
thickness / 2,
this.width - thickness,
this.height - thickness
);
// Restaura o estilo de linha
this.ctx.setLineDash([]);
return this;
}
/**
* Adiciona uma marca d'água de texto ao canvas
* @param {string} text - Texto da marca d'água
* @param {Object} options - Opções da marca d'água
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
addWatermark(text, options = {}) {
const {
font = "Arial",
fontSize = 30,
color = "#000000",
opacity = 0.3,
angle = -30,
x = this.width / 2,
y = this.height / 2
} = options;
filters.applyWatermark(
this.ctx,
0,
0,
this.width,
this.height,
text,
font,
fontSize,
color,
opacity,
angle
);
return this;
}
/**
* Adiciona uma marca d'água de imagem ao canvas
* @param {string|Buffer|Object} source - URL, Buffer ou caminho da imagem
* @param {Object} options - Opções da marca d'água
* @returns {Promise<ImageProcessor>} - Instância atual para encadeamento
*/
async addImageWatermark(source, options = {}) {
const {
x = this.width / 2,
y = this.height / 2,
width = this.width * 0.3,
height = null,
opacity = 0.5,
align = "center", // center, topLeft, topRight, bottomLeft, bottomRight
offsetX = 0,
offsetY = 0
} = options;
try {
const img = await loadImageWithAxios(source);
// Calcula as dimensões proporcionais
const imgAspect = img.width / img.height;
let drawWidth = width;
let drawHeight = height || width / imgAspect;
if (!height) {
drawHeight = drawWidth / imgAspect;
} else {
drawWidth = drawHeight * imgAspect;
}
// Calcula a posição com base no alinhamento
let drawX = x;
let drawY = y;
switch (align) {
case "topLeft":
drawX = padding + offsetX;
drawY = padding + offsetY;
break;
case "topRight":
drawX = this.width - drawWidth - padding + offsetX;
drawY = padding + offsetY;
break;
case "bottomLeft":
drawX = padding + offsetX;
drawY = this.height - drawHeight - padding + offsetY;
break;
case "bottomRight":
drawX = this.width - drawWidth - padding + offsetX;
drawY = this.height - drawHeight - padding + offsetY;
break;
case "center":
default:
drawX = (this.width - drawWidth) / 2 + offsetX;
drawY = (this.height - drawHeight) / 2 + offsetY;
break;
}
// Salva o estado atual do contexto
this.ctx.save();
// Define a opacidade
this.ctx.globalAlpha = opacity;
// Desenha a imagem
this.ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
// Restaura o estado do contexto
this.ctx.restore();
return this;
} catch (err) {
console.error("Falha ao adicionar marca d'água de imagem:", err.message);
throw new Error("Não foi possível adicionar a marca d'água de imagem.");
}
}
/**
* Aplica um filtro ao canvas
* @param {string} filterName - Nome do filtro a ser aplicado
* @param {Object} options - Opções do filtro
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
applyFilter(filterName, options = {}) {
// Verifica se o filtro existe
if (!filters[filterName]) {
throw new Error(`Filtro "${filterName}" não encontrado.`);
}
// Aplica o filtro com os parâmetros fornecidos
filters[filterName](this.ctx, 0, 0, this.width, this.height, ...Object.values(options));
return this;
}
/**
* Aplica múltiplos filtros ao canvas
* @param {Array} filtersList - Lista de filtros a serem aplicados
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
applyFilters(filtersList) {
for (const filter of filtersList) {
this.applyFilter(filter.name, filter.options || {});
}
return this;
}
/**
* Adiciona texto ao canvas
* @param {string} text - Texto a ser adicionado
* @param {Object} options - Opções do texto
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
addText(text, options = {}) {
const {
x = this.width / 2,
y = this.height / 2,
font = "Arial",
fontSize = 30,
color = "#000000",
align = "center", // left, center, right
baseline = "middle", // top, middle, bottom
maxWidth = null,
lineHeight = 1.2,
shadow = false,
shadowColor = "rgba(0, 0, 0, 0.5)",
shadowBlur = 3,
shadowOffsetX = 1,
shadowOffsetY = 1,
stroke = false,
strokeColor = "#000000",
strokeWidth = 1,
background = false,
backgroundColor = "rgba(255, 255, 255, 0.5)",
padding = 5,
rotate = 0
} = options;
// Salva o estado atual do contexto
this.ctx.save();
// Configura o texto
this.ctx.font = `${fontSize}px ${font}`;
this.ctx.fillStyle = color;
this.ctx.textAlign = align;
this.ctx.textBaseline = baseline;
// Aplica rotação se necessário
if (rotate !== 0) {
this.ctx.translate(x, y);
this.ctx.rotate((rotate * Math.PI) / 180);
this.ctx.translate(-x, -y);
}
// Aplica sombra se necessário
if (shadow) {
this.ctx.shadowColor = shadowColor;
this.ctx.shadowBlur = shadowBlur;
this.ctx.shadowOffsetX = shadowOffsetX;
this.ctx.shadowOffsetY = shadowOffsetY;
}
// Se maxWidth for fornecido, quebra o texto em linhas
if (maxWidth) {
const words = text.split(" ");
const lines = [];
let currentLine = words[0];
for (let i = 1; i < words.length; i++) {
const word = words[i];
const width = this.ctx.measureText(currentLine + " " + word).width;
if (width < maxWidth) {
currentLine += " " + word;
} else {
lines.push(currentLine);
currentLine = word;
}
}
lines.push(currentLine);
// Desenha o fundo se necessário
if (background) {
let textWidth = 0;
for (const line of lines) {
const lineWidth = this.ctx.measureText(line).width;
textWidth = Math.max(textWidth, lineWidth);
}
const textHeight = lines.length * fontSize * lineHeight;
let bgX, bgY;
switch (align) {
case "left":
bgX = x;
break;
case "right":
bgX = x - textWidth;
break;
case "center":
default:
bgX = x - textWidth / 2;
break;
}
switch (baseline) {
case "top":
bgY = y;
break;
case "bottom":
bgY = y - textHeight;
break;
case "middle":
default:
bgY = y - textHeight / 2;
break;
}
this.ctx.fillStyle = backgroundColor;
this.ctx.fillRect(
bgX - padding,
bgY - padding,
textWidth + padding * 2,
textHeight + padding * 2
);
this.ctx.fillStyle = color;
}
// Desenha cada linha
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const lineY = y + (i - (lines.length - 1) / 2) * fontSize * lineHeight;
if (stroke) {
this.ctx.strokeStyle = strokeColor;
this.ctx.lineWidth = strokeWidth;
this.ctx.strokeText(line, x, lineY);
}
this.ctx.fillText(line, x, lineY);
}
} else {
// Desenha o fundo se necessário
if (background) {
const textWidth = this.ctx.measureText(text).width;
const textHeight = fontSize;
let bgX, bgY;
switch (align) {
case "left":
bgX = x;
break;
case "right":
bgX = x - textWidth;
break;
case "center":
default:
bgX = x - textWidth / 2;
break;
}
switch (baseline) {
case "top":
bgY = y;
break;
case "bottom":
bgY = y - textHeight;
break;
case "middle":
default:
bgY = y - textHeight / 2;
break;
}
this.ctx.fillStyle = backgroundColor;
this.ctx.fillRect(
bgX - padding,
bgY - padding,
textWidth + padding * 2,
textHeight + padding * 2
);
this.ctx.fillStyle = color;
}
// Desenha o texto
if (stroke) {
this.ctx.strokeStyle = strokeColor;
this.ctx.lineWidth = strokeWidth;
this.ctx.strokeText(text, x, y);
}
this.ctx.fillText(text, x, y);
}
// Restaura o estado do contexto
this.ctx.restore();
return this;
}
/**
* Adiciona uma forma ao canvas
* @param {string} shape - Tipo de forma ('rectangle', 'circle', 'ellipse', 'polygon')
* @param {Object} options - Opções da forma
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
addShape(shape, options = {}) {
const {
x = this.width / 2,
y = this.height / 2,
width = 100,
height = 100,
radius = 50,
radiusX = 50,
radiusY = 30,
sides = 6,
rotation = 0,
fill = true,
fillColor = "#000000",
stroke = false,
strokeColor = "#000000",
strokeWidth = 1,
opacity = 1
} = options;
// Salva o estado atual do contexto
this.ctx.save();
// Define a opacidade
this.ctx.globalAlpha = opacity;
// Configura o estilo de preenchimento e contorno
this.ctx.fillStyle = fillColor;
this.ctx.strokeStyle = strokeColor;
this.ctx.lineWidth = strokeWidth;
// Desenha a forma com base no tipo
switch (shape) {
case "rectangle":
if (fill) {
this.ctx.fillRect(x - width / 2, y - height / 2, width, height);
}
if (stroke) {
this.ctx.strokeRect(x - width / 2, y - height / 2, width, height);
}
break;
case "circle":
this.ctx.beginPath();
this.ctx.arc(x, y, radius, 0, Math.PI * 2);
if (fill) {
this.ctx.fill();
}
if (stroke) {
this.ctx.stroke();
}
break;
case "ellipse":
this.ctx.beginPath();
this.ctx.ellipse(x, y, radiusX, radiusY, 0, 0, Math.PI * 2);
if (fill) {
this.ctx.fill();
}
if (stroke) {
this.ctx.stroke();
}
break;
case "polygon":
this.ctx.beginPath();
const rotationInRadians = (rotation * Math.PI) / 180;
for (let i = 0; i < sides; i++) {
const angle = rotationInRadians + (i * 2 * Math.PI) / sides;
const pointX = x + radius * Math.cos(angle);
const pointY = y + radius * Math.sin(angle);
if (i === 0) {
this.ctx.moveTo(pointX, pointY);
} else {
this.ctx.lineTo(pointX, pointY);
}
}
this.ctx.closePath();
if (fill) {
this.ctx.fill();
}
if (stroke) {
this.ctx.stroke();
}
break;
default:
throw new Error(`Forma "${shape}" não suportada.`);
}
// Restaura o estado do contexto
this.ctx.restore();
return this;
}
/**
* Adiciona um gradiente ao canvas
* @param {string} type - Tipo de gradiente ('linear', 'radial')
* @param {Object} options - Opções do gradiente
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
addGradient(type, options = {}) {
const {
x1 = 0,
y1 = 0,
x2 = this.width,
y2 = this.height,
centerX = this.width / 2,
centerY = this.height / 2,
radius1 = 0,
radius2 = Math.max(this.width, this.height) / 2,
colors = ["#000000", "#FFFFFF"],
stops = null,
opacity = 1
} = options;
// Salva o estado atual do contexto
this.ctx.save();
// Define a opacidade
this.ctx.globalAlpha = opacity;
// Cria o gradiente com base no tipo
let gradient;
if (type === "linear") {
gradient = this.ctx.createLinearGradient(x1, y1, x2, y2);
} else if (type === "radial") {
gradient = this.ctx.createRadialGradient(
centerX,
centerY,
radius1,
centerX,
centerY,
radius2
);
} else {
throw new Error(`Tipo de gradiente "${type}" não suportado.`);
}
// Adiciona as cores ao gradiente
if (stops) {
// Se os stops forem fornecidos, usa-os
for (let i = 0; i < colors.length; i++) {
gradient.addColorStop(stops[i], colors[i]);
}
} else {
// Caso contrário, distribui as cores uniformemente
for (let i = 0; i < colors.length; i++) {
gradient.addColorStop(i / (colors.length - 1), colors[i]);
}
}
// Preenche o canvas com o gradiente
this.ctx.fillStyle = gradient;
this.ctx.fillRect(0, 0, this.width, this.height);
// Restaura o estado do contexto
this.ctx.restore();
return this;
}
/**
* Adiciona um padrão ao canvas
* @param {string} type - Tipo de padrão ('dots', 'lines', 'grid', 'checkerboard')
* @param {Object} options - Opções do padrão
* @returns {ImageProcessor} - Instância atual para encadeamento
*/
addPattern(type, options = {}) {
const {
color = "#000000",
backgroundColor = null,
size = 10,
spacing = 20,
lineWidth = 1,
angle = 0,
opacity = 0.2
} = options;
// Salva o estado atual do contexto
this.ctx.save();
// Define a opacidade
this.ctx.globalAlpha = opacity;
// Preenche o fundo se fornecido
if (backgroundColor) {
this.ctx.fillStyle = backgroundColor;
this.ctx.fillRect(0, 0, this.width, this.height);
}
// Configura o estilo de desenho
this.ctx.fillStyle = color;
this.ctx.strokeStyle = color;
this.ctx.lineWidth = lineWidth;
// Aplica rotação se necessário
if (angle !== 0) {
this.ctx.translate(this.width / 2, this.height / 2);
this.ctx.rotate((angle * Math.PI) / 180);
this.ctx.translate(-this.width / 2, -this.height / 2);
}
// Desenha o padrão com base no tipo
switch (type) {
case "dots":
for (let y = spacing / 2; y < this.height + spacing; y += spacing) {
for (let x = spacing / 2; x < this.width + spacing; x += spacing) {
this.ctx.beginPath();
this.ctx.arc(x, y, size / 2, 0, Math.PI * 2);
this.ctx.fill();
}
}
break;
case "lines":
for (let y = 0; y < this.height + spacing; y += spacing) {
this.ctx.beginPath();
this.ctx.moveTo(0, y);
this.ctx.lineTo(this.width, y);
this.ctx.stroke();
}
break;
case "grid":
// Linhas horizontais
for (let y = 0; y < this.height + spacing; y += spacing) {
this.ctx.beginPath();
this.ctx.moveTo(0, y);
this.ctx.lineTo(this.width, y);
this.ctx.stroke();
}
// Linhas verticais
for (let x = 0; x < this.width + spacing; x += spacing) {
this.ctx.beginPath();
this.ctx.moveTo(x, 0);
this.ctx.lineTo(x, this.height);
this.ctx.stroke();
}
break;
case "checkerboard":
const cellSize = spacing;
for (let y = 0; y < this.height + cellSize; y += cellSize) {
for (let x = 0; x < this.width + cellSize; x += cellSize) {
if ((Math.floor(x / cellSize) + Math.floor(y / cellSize)) % 2 === 0) {
this.ctx.fillRect(x, y, cellSize, cellSize);
}
}
}
break;
default:
throw new Error(`Tipo de padrão "${type}" não suportado.`);
}
// Restaura o estado do contexto
this.ctx.restore();
return this;
}
/**
* Converte o canvas para um buffer de imagem
* @param {string} format - Formato da imagem ('png', 'jpeg')
* @param {Object} options - Opções de codificação
* @returns {Promise<Buffer>} - Buffer contendo a imagem
*/
async toBuffer(format = "png", options = {}) {
const { quality = this.quality } = options;
try {
return await encodeToBuffer(this.canvas, format, quality);
} catch (err) {
console.error("Falha ao codificar a imagem:", err.message);
throw new Error("Não foi possível gerar o buffer de imagem.");
}
}
/**
* Salva o canvas em um arquivo
* @param {string} filePath - Caminho do arquivo
* @param {string} format - Formato da imagem ('png', 'jpeg')
* @param {Object} options - Opções de codificação
* @returns {Promise<string>} - Caminho do arquivo salvo
*/
async saveToFile(filePath, format = null, options = {}) {
// Determina o formato com base na extensão do arquivo se não for fornecido
if (!format) {
const ext = path.extname(filePath).toLowerCase();
format = ext === ".jpg" || ext === ".jpeg" ? "jpeg" : "png";
}
try {
const buffer = await this.toBuffer(format, options);
// Cria o diretório se não existir
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Salva o buffer no arquivo
fs.writeFileSync(filePath, buffer);
return filePath;
} catch (err) {
console.error("Falha ao salvar a imagem:", err.message);
throw new Error("Não foi possível salvar a imagem no arquivo.");
}
}
}
module.exports = ImageProcessor;