UNPKG

@cognima/banners

Version:

Biblioteca avançada para geração de banners dinâmicos para diversas plataformas e aplicações

1,035 lines (868 loc) 33.9 kB
"use strict"; /** * Módulo de Filtros de Imagem * * Este módulo fornece funções para aplicar diversos filtros e efeitos * em imagens para uso nos banners. * * @author Cognima Team (melhorado) * @version 2.0.0 */ Object.defineProperty(exports, "__esModule", { value: true }); const pureimage = require("pureimage"); const { hexToRgba } = require("../utils"); /** * Aplica um filtro de escala de cinza a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} intensity - Intensidade do filtro (0-1) */ function applyGrayscale(ctx, x, y, width, height, intensity = 1) { const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Fórmula de luminância para escala de cinza const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Aplica a intensidade do filtro data[i] = r * (1 - intensity) + gray * intensity; data[i + 1] = g * (1 - intensity) + gray * intensity; data[i + 2] = b * (1 - intensity) + gray * intensity; } ctx.putImageData(imageData, x, y); } /** * Aplica um filtro sépia a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} intensity - Intensidade do filtro (0-1) */ function applySepia(ctx, x, y, width, height, intensity = 1) { const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Fórmula para filtro sépia const newR = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189)); const newG = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168)); const newB = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131)); // Aplica a intensidade do filtro data[i] = r * (1 - intensity) + newR * intensity; data[i + 1] = g * (1 - intensity) + newG * intensity; data[i + 2] = b * (1 - intensity) + newB * intensity; } ctx.putImageData(imageData, x, y); } /** * Ajusta o brilho de uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} value - Valor do ajuste (-100 a 100) */ function adjustBrightness(ctx, x, y, width, height, value) { const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; // Normaliza o valor para um fator multiplicativo const factor = 1 + (value / 100); for (let i = 0; i < data.length; i += 4) { data[i] = Math.min(255, Math.max(0, data[i] * factor)); data[i + 1] = Math.min(255, Math.max(0, data[i + 1] * factor)); data[i + 2] = Math.min(255, Math.max(0, data[i + 2] * factor)); } ctx.putImageData(imageData, x, y); } /** * Ajusta o contraste de uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} value - Valor do ajuste (-100 a 100) */ function adjustContrast(ctx, x, y, width, height, value) { const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; // Normaliza o valor para um fator de contraste const factor = (259 * (value + 255)) / (255 * (259 - value)); for (let i = 0; i < data.length; i += 4) { data[i] = Math.min(255, Math.max(0, factor * (data[i] - 128) + 128)); data[i + 1] = Math.min(255, Math.max(0, factor * (data[i + 1] - 128) + 128)); data[i + 2] = Math.min(255, Math.max(0, factor * (data[i + 2] - 128) + 128)); } ctx.putImageData(imageData, x, y); } /** * Ajusta a saturação de uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} value - Valor do ajuste (-100 a 100) */ function adjustSaturation(ctx, x, y, width, height, value) { const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; // Normaliza o valor para um fator de saturação const factor = 1 + (value / 100); for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; // Fórmula de luminância para escala de cinza const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Ajusta a saturação data[i] = Math.min(255, Math.max(0, gray + factor * (r - gray))); data[i + 1] = Math.min(255, Math.max(0, gray + factor * (g - gray))); data[i + 2] = Math.min(255, Math.max(0, gray + factor * (b - gray))); } ctx.putImageData(imageData, x, y); } /** * Aplica um filtro de desfoque a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} radius - Raio do desfoque (1-10) */ function applyBlur(ctx, x, y, width, height, radius = 3) { // Limita o raio para evitar operações muito intensivas radius = Math.min(10, Math.max(1, radius)); const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; const result = new Uint8ClampedArray(data.length); // Copia os dados originais for (let i = 0; i < data.length; i++) { result[i] = data[i]; } // Aplica o desfoque horizontal for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let r = 0, g = 0, b = 0, a = 0, count = 0; for (let i = -radius; i <= radius; i++) { const posX = Math.min(width - 1, Math.max(0, x + i)); const index = (y * width + posX) * 4; r += data[index]; g += data[index + 1]; b += data[index + 2]; a += data[index + 3]; count++; } const resultIndex = (y * width + x) * 4; result[resultIndex] = r / count; result[resultIndex + 1] = g / count; result[resultIndex + 2] = b / count; result[resultIndex + 3] = a / count; } } // Copia os resultados de volta para data for (let i = 0; i < data.length; i++) { data[i] = result[i]; } // Aplica o desfoque vertical for (let x = 0; x < width; x++) { for (let y = 0; y < height; y++) { let r = 0, g = 0, b = 0, a = 0, count = 0; for (let i = -radius; i <= radius; i++) { const posY = Math.min(height - 1, Math.max(0, y + i)); const index = (posY * width + x) * 4; r += data[index]; g += data[index + 1]; b += data[index + 2]; a += data[index + 3]; count++; } const resultIndex = (y * width + x) * 4; result[resultIndex] = r / count; result[resultIndex + 1] = g / count; result[resultIndex + 2] = b / count; result[resultIndex + 3] = a / count; } } // Cria um novo ImageData com os resultados const resultImageData = new ImageData(result, width, height); ctx.putImageData(resultImageData, x, y); } /** * Aplica um filtro de nitidez a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} amount - Quantidade de nitidez (0-1) */ function applySharpen(ctx, x, y, width, height, amount = 0.5) { // Limita a quantidade para evitar artefatos amount = Math.min(1, Math.max(0, amount)); const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; const result = new Uint8ClampedArray(data.length); // Copia os dados originais for (let i = 0; i < data.length; i++) { result[i] = data[i]; } // Kernel de nitidez const kernel = [ 0, -amount, 0, -amount, 1 + 4 * amount, -amount, 0, -amount, 0 ]; // Aplica o kernel for (let y = 1; y < height - 1; y++) { for (let x = 1; x < width - 1; x++) { const centerIndex = (y * width + x) * 4; let r = 0, g = 0, b = 0; // Aplica o kernel 3x3 for (let ky = -1; ky <= 1; ky++) { for (let kx = -1; kx <= 1; kx++) { const kernelIndex = (ky + 1) * 3 + (kx + 1); const pixelIndex = ((y + ky) * width + (x + kx)) * 4; r += data[pixelIndex] * kernel[kernelIndex]; g += data[pixelIndex + 1] * kernel[kernelIndex]; b += data[pixelIndex + 2] * kernel[kernelIndex]; } } result[centerIndex] = Math.min(255, Math.max(0, r)); result[centerIndex + 1] = Math.min(255, Math.max(0, g)); result[centerIndex + 2] = Math.min(255, Math.max(0, b)); } } // Cria um novo ImageData com os resultados const resultImageData = new ImageData(result, width, height); ctx.putImageData(resultImageData, x, y); } /** * Aplica um filtro de vinheta a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} amount - Quantidade de vinheta (0-1) * @param {string} color - Cor da vinheta (hexadecimal) */ function applyVignette(ctx, x, y, width, height, amount = 0.5, color = "#000000") { // Limita a quantidade para evitar artefatos amount = Math.min(1, Math.max(0, amount)); ctx.save(); // Cria um gradiente radial para a vinheta const gradient = ctx.createRadialGradient( x + width / 2, y + height / 2, 0, x + width / 2, y + height / 2, Math.max(width, height) / 2 ); gradient.addColorStop(0, "rgba(0, 0, 0, 0)"); gradient.addColorStop(0.5, hexToRgba(color, 0)); gradient.addColorStop(1, hexToRgba(color, amount)); ctx.fillStyle = gradient; ctx.fillRect(x, y, width, height); ctx.restore(); } /** * Aplica um filtro de duotone a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {string} lightColor - Cor clara (hexadecimal) * @param {string} darkColor - Cor escura (hexadecimal) * @param {number} intensity - Intensidade do filtro (0-1) */ function applyDuotone(ctx, x, y, width, height, lightColor = "#FFFFFF", darkColor = "#000000", intensity = 1) { // Converte as cores hexadecimais para componentes RGB const lightRGB = { r: parseInt(lightColor.slice(1, 3), 16), g: parseInt(lightColor.slice(3, 5), 16), b: parseInt(lightColor.slice(5, 7), 16) }; const darkRGB = { r: parseInt(darkColor.slice(1, 3), 16), g: parseInt(darkColor.slice(3, 5), 16), b: parseInt(darkColor.slice(5, 7), 16) }; const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { // Converte para escala de cinza const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]; const normalized = gray / 255; // Interpola entre as cores escura e clara com base no valor de cinza const r = darkRGB.r + normalized * (lightRGB.r - darkRGB.r); const g = darkRGB.g + normalized * (lightRGB.g - darkRGB.g); const b = darkRGB.b + normalized * (lightRGB.b - darkRGB.b); // Aplica a intensidade do filtro data[i] = data[i] * (1 - intensity) + r * intensity; data[i + 1] = data[i + 1] * (1 - intensity) + g * intensity; data[i + 2] = data[i + 2] * (1 - intensity) + b * intensity; } ctx.putImageData(imageData, x, y); } /** * Aplica um filtro de cor sólida a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {string} color - Cor (hexadecimal) * @param {string} blendMode - Modo de mesclagem ('multiply', 'screen', 'overlay') * @param {number} opacity - Opacidade (0-1) */ function applyColorOverlay(ctx, x, y, width, height, color = "#000000", blendMode = "multiply", opacity = 0.5) { // Converte a cor hexadecimal para componentes RGB const rgb = { r: parseInt(color.slice(1, 3), 16), g: parseInt(color.slice(3, 5), 16), b: parseInt(color.slice(5, 7), 16) }; const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { const r = data[i]; const g = data[i + 1]; const b = data[i + 2]; let newR, newG, newB; // Aplica o modo de mesclagem switch (blendMode) { case "multiply": newR = (r * rgb.r) / 255; newG = (g * rgb.g) / 255; newB = (b * rgb.b) / 255; break; case "screen": newR = 255 - ((255 - r) * (255 - rgb.r)) / 255; newG = 255 - ((255 - g) * (255 - rgb.g)) / 255; newB = 255 - ((255 - b) * (255 - rgb.b)) / 255; break; case "overlay": newR = r < 128 ? (2 * r * rgb.r) / 255 : 255 - (2 * (255 - r) * (255 - rgb.r)) / 255; newG = g < 128 ? (2 * g * rgb.g) / 255 : 255 - (2 * (255 - g) * (255 - rgb.g)) / 255; newB = b < 128 ? (2 * b * rgb.b) / 255 : 255 - (2 * (255 - b) * (255 - rgb.b)) / 255; break; default: newR = rgb.r; newG = rgb.g; newB = rgb.b; } // Aplica a opacidade data[i] = r * (1 - opacity) + newR * opacity; data[i + 1] = g * (1 - opacity) + newG * opacity; data[i + 2] = b * (1 - opacity) + newB * opacity; } ctx.putImageData(imageData, x, y); } /** * Aplica um filtro de ruído a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} amount - Quantidade de ruído (0-1) * @param {string} type - Tipo de ruído ('mono', 'color') */ function applyNoise(ctx, x, y, width, height, amount = 0.2, type = "mono") { // Limita a quantidade para evitar artefatos amount = Math.min(1, Math.max(0, amount)) * 50; const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { if (type === "mono") { // Ruído monocromático (mesmo valor para R, G e B) const noise = Math.round((Math.random() - 0.5) * amount * 2); data[i] = Math.min(255, Math.max(0, data[i] + noise)); data[i + 1] = Math.min(255, Math.max(0, data[i + 1] + noise)); data[i + 2] = Math.min(255, Math.max(0, data[i + 2] + noise)); } else { // Ruído colorido (valores diferentes para R, G e B) data[i] = Math.min(255, Math.max(0, data[i] + Math.round((Math.random() - 0.5) * amount * 2))); data[i + 1] = Math.min(255, Math.max(0, data[i + 1] + Math.round((Math.random() - 0.5) * amount * 2))); data[i + 2] = Math.min(255, Math.max(0, data[i + 2] + Math.round((Math.random() - 0.5) * amount * 2))); } } ctx.putImageData(imageData, x, y); } /** * Aplica um filtro de posterização a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} levels - Número de níveis (2-20) */ function applyPosterize(ctx, x, y, width, height, levels = 5) { // Limita os níveis para evitar artefatos levels = Math.min(20, Math.max(2, levels)); const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; const step = 255 / (levels - 1); for (let i = 0; i < data.length; i += 4) { data[i] = Math.round(Math.round(data[i] / step) * step); data[i + 1] = Math.round(Math.round(data[i + 1] / step) * step); data[i + 2] = Math.round(Math.round(data[i + 2] / step) * step); } ctx.putImageData(imageData, x, y); } /** * Aplica um filtro de pixelização a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} pixelSize - Tamanho dos pixels (1-50) */ function applyPixelate(ctx, x, y, width, height, pixelSize = 10) { // Limita o tamanho dos pixels para evitar operações muito intensivas pixelSize = Math.min(50, Math.max(1, pixelSize)); const imageData = ctx.getImageData(x, y, width, height); const data = imageData.data; const tempCanvas = pureimage.make(width, height); const tempCtx = tempCanvas.getContext("2d"); // Copia os dados originais para o canvas temporário const tempImageData = tempCtx.createImageData(width, height); for (let i = 0; i < data.length; i++) { tempImageData.data[i] = data[i]; } tempCtx.putImageData(tempImageData, 0, 0); // Limpa o canvas original ctx.clearRect(x, y, width, height); // Desenha a imagem pixelizada for (let blockY = 0; blockY < height; blockY += pixelSize) { for (let blockX = 0; blockX < width; blockX += pixelSize) { // Calcula o tamanho do bloco atual (pode ser menor nas bordas) const blockWidth = Math.min(pixelSize, width - blockX); const blockHeight = Math.min(pixelSize, height - blockY); // Obtém a cor média do bloco let r = 0, g = 0, b = 0, a = 0, count = 0; for (let py = 0; py < blockHeight; py++) { for (let px = 0; px < blockWidth; px++) { const index = ((blockY + py) * width + (blockX + px)) * 4; r += data[index]; g += data[index + 1]; b += data[index + 2]; a += data[index + 3]; count++; } } r = Math.round(r / count); g = Math.round(g / count); b = Math.round(b / count); a = Math.round(a / count); // Preenche o bloco com a cor média ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a / 255})`; ctx.fillRect(x + blockX, y + blockY, blockWidth, blockHeight); } } } /** * Aplica um filtro de borda a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} thickness - Espessura da borda (1-10) * @param {string} color - Cor da borda (hexadecimal) */ function applyBorder(ctx, x, y, width, height, thickness = 5, color = "#000000") { // Limita a espessura para evitar operações muito intensivas thickness = Math.min(10, Math.max(1, thickness)); ctx.save(); ctx.strokeStyle = color; ctx.lineWidth = thickness; ctx.strokeRect(x + thickness / 2, y + thickness / 2, width - thickness, height - thickness); ctx.restore(); } /** * Aplica um filtro de moldura a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} thickness - Espessura da moldura (10-50) * @param {string} color - Cor da moldura (hexadecimal) * @param {string} style - Estilo da moldura ('solid', 'double', 'inset') */ function applyFrame(ctx, x, y, width, height, thickness = 20, color = "#FFFFFF", style = "solid") { // Limita a espessura para evitar operações muito intensivas thickness = Math.min(50, Math.max(10, thickness)); ctx.save(); switch (style) { case "double": // Moldura dupla const outerThickness = thickness; const innerThickness = thickness / 2; const gap = thickness / 4; // Moldura externa ctx.fillStyle = color; ctx.fillRect(x, y, width, outerThickness); // Superior ctx.fillRect(x, y + height - outerThickness, width, outerThickness); // Inferior ctx.fillRect(x, y + outerThickness, outerThickness, height - outerThickness * 2); // Esquerda ctx.fillRect(x + width - outerThickness, y + outerThickness, outerThickness, height - outerThickness * 2); // Direita // Moldura interna ctx.fillRect(x + outerThickness + gap, y + outerThickness + gap, width - 2 * (outerThickness + gap), innerThickness); // Superior ctx.fillRect(x + outerThickness + gap, y + height - outerThickness - gap - innerThickness, width - 2 * (outerThickness + gap), innerThickness); // Inferior ctx.fillRect(x + outerThickness + gap, y + outerThickness + gap + innerThickness, innerThickness, height - 2 * (outerThickness + gap + innerThickness)); // Esquerda ctx.fillRect(x + width - outerThickness - gap - innerThickness, y + outerThickness + gap + innerThickness, innerThickness, height - 2 * (outerThickness + gap + innerThickness)); // Direita break; case "inset": // Moldura com efeito de profundidade ctx.fillStyle = color; ctx.fillRect(x, y, width, thickness); // Superior ctx.fillRect(x, y + height - thickness, width, thickness); // Inferior ctx.fillRect(x, y + thickness, thickness, height - thickness * 2); // Esquerda ctx.fillRect(x + width - thickness, y + thickness, thickness, height - thickness * 2); // Direita // Sombras para efeito de profundidade const lightColor = "#FFFFFF"; const darkColor = "#000000"; const shadowSize = thickness / 4; // Sombras claras (superior e esquerda) ctx.fillStyle = hexToRgba(lightColor, 0.3); ctx.fillRect(x, y, width, shadowSize); // Superior ctx.fillRect(x, y + shadowSize, shadowSize, height - shadowSize * 2); // Esquerda // Sombras escuras (inferior e direita) ctx.fillStyle = hexToRgba(darkColor, 0.3); ctx.fillRect(x, y + height - shadowSize, width, shadowSize); // Inferior ctx.fillRect(x + width - shadowSize, y + shadowSize, shadowSize, height - shadowSize * 2); // Direita break; case "solid": default: // Moldura sólida ctx.fillStyle = color; ctx.fillRect(x, y, width, thickness); // Superior ctx.fillRect(x, y + height - thickness, width, thickness); // Inferior ctx.fillRect(x, y + thickness, thickness, height - thickness * 2); // Esquerda ctx.fillRect(x + width - thickness, y + thickness, thickness, height - thickness * 2); // Direita break; } ctx.restore(); } /** * Aplica um filtro de reflexo a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} reflectionHeight - Altura da reflexão (10-100) * @param {number} opacity - Opacidade da reflexão (0-1) * @param {number} gap - Espaço entre a imagem e a reflexão (0-10) */ function applyReflection(ctx, x, y, width, height, reflectionHeight = 50, opacity = 0.5, gap = 2) { // Limita os parâmetros para evitar operações muito intensivas reflectionHeight = Math.min(100, Math.max(10, reflectionHeight)); opacity = Math.min(1, Math.max(0, opacity)); gap = Math.min(10, Math.max(0, gap)); // Obtém os dados da imagem original const imageData = ctx.getImageData(x, y, width, height); // Cria um canvas temporário para a reflexão const tempCanvas = pureimage.make(width, reflectionHeight); const tempCtx = tempCanvas.getContext("2d"); // Desenha a parte inferior da imagem original invertida tempCtx.save(); tempCtx.scale(1, -1); tempCtx.drawImage( imageData, 0, height - reflectionHeight, width, reflectionHeight, 0, -reflectionHeight, width, reflectionHeight ); tempCtx.restore(); // Aplica um gradiente de opacidade à reflexão const gradient = tempCtx.createLinearGradient(0, 0, 0, reflectionHeight); gradient.addColorStop(0, `rgba(255, 255, 255, ${opacity})`); gradient.addColorStop(1, "rgba(255, 255, 255, 0)"); tempCtx.globalCompositeOperation = "destination-in"; tempCtx.fillStyle = gradient; tempCtx.fillRect(0, 0, width, reflectionHeight); // Desenha a reflexão abaixo da imagem original ctx.drawImage(tempCanvas, x, y + height + gap); } /** * Aplica um filtro de sombra a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} blur - Desfoque da sombra (0-20) * @param {number} offsetX - Deslocamento horizontal da sombra (-20 a 20) * @param {number} offsetY - Deslocamento vertical da sombra (-20 a 20) * @param {string} color - Cor da sombra (hexadecimal) * @param {number} opacity - Opacidade da sombra (0-1) */ function applyShadow(ctx, x, y, width, height, blur = 10, offsetX = 5, offsetY = 5, color = "#000000", opacity = 0.5) { // Limita os parâmetros para evitar operações muito intensivas blur = Math.min(20, Math.max(0, blur)); offsetX = Math.min(20, Math.max(-20, offsetX)); offsetY = Math.min(20, Math.max(-20, offsetY)); opacity = Math.min(1, Math.max(0, opacity)); ctx.save(); // Desenha a sombra ctx.shadowBlur = blur; ctx.shadowOffsetX = offsetX; ctx.shadowOffsetY = offsetY; ctx.shadowColor = hexToRgba(color, opacity); // Desenha um retângulo transparente para projetar a sombra ctx.fillStyle = "rgba(0, 0, 0, 0)"; ctx.fillRect(x, y, width, height); ctx.restore(); } /** * Aplica um filtro de marca d'água a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {string} text - Texto da marca d'água * @param {string} font - Nome da fonte * @param {number} fontSize - Tamanho da fonte * @param {string} color - Cor do texto (hexadecimal) * @param {number} opacity - Opacidade do texto (0-1) * @param {number} angle - Ângulo de rotação em graus (-45 a 45) */ function applyWatermark(ctx, x, y, width, height, text, font, fontSize = 30, color = "#000000", opacity = 0.3, angle = -30) { // Limita os parâmetros para evitar operações muito intensivas fontSize = Math.min(100, Math.max(10, fontSize)); opacity = Math.min(1, Math.max(0, opacity)); angle = Math.min(45, Math.max(-45, angle)); ctx.save(); // Configura o estilo do texto ctx.font = `${fontSize}px ${font}`; ctx.fillStyle = hexToRgba(color, opacity); ctx.textAlign = "center"; ctx.textBaseline = "middle"; // Aplica a rotação const centerX = x + width / 2; const centerY = y + height / 2; ctx.translate(centerX, centerY); ctx.rotate(angle * Math.PI / 180); // Desenha o texto ctx.fillText(text, 0, 0); ctx.restore(); } /** * Aplica um filtro de recorte circular a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} radius - Raio do círculo (se não fornecido, usa o menor entre largura/2 e altura/2) */ function applyCircularCrop(ctx, x, y, width, height, radius = null) { // Se o raio não for fornecido, usa o menor entre largura/2 e altura/2 if (radius === null) { radius = Math.min(width, height) / 2; } const centerX = x + width / 2; const centerY = y + height / 2; // Obtém os dados da imagem original const imageData = ctx.getImageData(x, y, width, height); // Limpa a área original ctx.clearRect(x, y, width, height); // Cria um caminho circular ctx.save(); ctx.beginPath(); ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); ctx.closePath(); ctx.clip(); // Desenha a imagem original dentro do círculo ctx.putImageData(imageData, x, y); ctx.restore(); } /** * Aplica um filtro de recorte em forma de coração a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} size - Tamanho do coração (se não fornecido, usa o menor entre largura e altura) */ function applyHeartCrop(ctx, x, y, width, height, size = null) { // Se o tamanho não for fornecido, usa o menor entre largura e altura if (size === null) { size = Math.min(width, height); } const centerX = x + width / 2; const centerY = y + height / 2; // Obtém os dados da imagem original const imageData = ctx.getImageData(x, y, width, height); // Limpa a área original ctx.clearRect(x, y, width, height); // Cria um caminho em forma de coração ctx.save(); ctx.beginPath(); // Desenha o coração const topCurveHeight = size * 0.3; ctx.moveTo(centerX, centerY + size / 4); // Lado esquerdo do coração ctx.bezierCurveTo( centerX, centerY, centerX - size / 2, centerY, centerX - size / 2, centerY - topCurveHeight ); ctx.bezierCurveTo( centerX - size / 2, centerY - size / 2, centerX, centerY - size / 2, centerX, centerY - topCurveHeight ); // Lado direito do coração ctx.bezierCurveTo( centerX, centerY - size / 2, centerX + size / 2, centerY - size / 2, centerX + size / 2, centerY - topCurveHeight ); ctx.bezierCurveTo( centerX + size / 2, centerY, centerX, centerY, centerX, centerY + size / 4 ); ctx.closePath(); ctx.clip(); // Desenha a imagem original dentro do coração ctx.putImageData(imageData, x, y); ctx.restore(); } /** * Aplica um filtro de recorte em forma de estrela a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} size - Tamanho da estrela (se não fornecido, usa o menor entre largura e altura) * @param {number} points - Número de pontas da estrela (5-10) */ function applyStarCrop(ctx, x, y, width, height, size = null, points = 5) { // Se o tamanho não for fornecido, usa o menor entre largura e altura if (size === null) { size = Math.min(width, height); } // Limita o número de pontas points = Math.min(10, Math.max(5, points)); const centerX = x + width / 2; const centerY = y + height / 2; const outerRadius = size / 2; const innerRadius = outerRadius * 0.4; // Obtém os dados da imagem original const imageData = ctx.getImageData(x, y, width, height); // Limpa a área original ctx.clearRect(x, y, width, height); // Cria um caminho em forma de estrela ctx.save(); ctx.beginPath(); // Desenha a estrela for (let i = 0; i < points * 2; i++) { const radius = i % 2 === 0 ? outerRadius : innerRadius; const angle = (Math.PI * i) / points; const pointX = centerX + radius * Math.sin(angle); const pointY = centerY - radius * Math.cos(angle); if (i === 0) { ctx.moveTo(pointX, pointY); } else { ctx.lineTo(pointX, pointY); } } ctx.closePath(); ctx.clip(); // Desenha a imagem original dentro da estrela ctx.putImageData(imageData, x, y); ctx.restore(); } /** * Aplica um filtro de recorte em forma de polígono a uma imagem * @param {Object} ctx - Contexto do canvas * @param {number} x - Posição X * @param {number} y - Posição Y * @param {number} width - Largura * @param {number} height - Altura * @param {number} size - Tamanho do polígono (se não fornecido, usa o menor entre largura e altura) * @param {number} sides - Número de lados do polígono (3-12) * @param {number} rotation - Rotação em graus (0-360) */ function applyPolygonCrop(ctx, x, y, width, height, size = null, sides = 6, rotation = 0) { // Se o tamanho não for fornecido, usa o menor entre largura e altura if (size === null) { size = Math.min(width, height); } // Limita o número de lados sides = Math.min(12, Math.max(3, sides)); const centerX = x + width / 2; const centerY = y + height / 2; const radius = size / 2; // Obtém os dados da imagem original const imageData = ctx.getImageData(x, y, width, height); // Limpa a área original ctx.clearRect(x, y, width, height); // Cria um caminho em forma de polígono ctx.save(); ctx.beginPath(); // Desenha o polígono const rotationInRadians = (rotation * Math.PI) / 180; for (let i = 0; i < sides; i++) { const angle = rotationInRadians + (i * 2 * Math.PI) / sides; const pointX = centerX + radius * Math.cos(angle); const pointY = centerY + radius * Math.sin(angle); if (i === 0) { ctx.moveTo(pointX, pointY); } else { ctx.lineTo(pointX, pointY); } } ctx.closePath(); ctx.clip(); // Desenha a imagem original dentro do polígono ctx.putImageData(imageData, x, y); ctx.restore(); } // Exporta todas as funções module.exports = { applyGrayscale, applySepia, adjustBrightness, adjustContrast, adjustSaturation, applyBlur, applySharpen, applyVignette, applyDuotone, applyColorOverlay, applyNoise, applyPosterize, applyPixelate, applyBorder, applyFrame, applyReflection, applyShadow, applyWatermark, applyCircularCrop, applyHeartCrop, applyStarCrop, applyPolygonCrop };