@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
JavaScript
"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
};