From cf5a46c86c2894d290ce309b77cabd80128268c4 Mon Sep 17 00:00:00 2001 From: admtracksteel Date: Mon, 8 Jun 2026 11:02:43 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20fix:=20Remo=C3=A7=C3=A3o=20real?= =?UTF-8?q?=20de=20fundo=20branco=20via=20Canvas=20API=20-=20transpar?= =?UTF-8?q?=C3=AAncia=20genu=C3=ADna=20nos=20stickers=20de=20cena?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/app.js | 98 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 22 deletions(-) diff --git a/public/app.js b/public/app.js index 2434ffe..5293338 100644 --- a/public/app.js +++ b/public/app.js @@ -7610,6 +7610,54 @@ document.addEventListener('DOMContentLoaded', () => { const sceneLayersItems = document.getElementById('sceneLayersItems'); // Cria um item de camada de imagem direto no canvas (background ou sticker) + // Remoção de fundo branco via Canvas API — gera PNG com transparência real + async function removeWhiteBackground(imgUrl, threshold = 235) { + return new Promise((resolve) => { + const img = new Image(); + img.crossOrigin = 'anonymous'; // necessário para getImageData funcionar + img.onload = () => { + const offscreen = document.createElement('canvas'); + offscreen.width = img.naturalWidth; + offscreen.height = img.naturalHeight; + const ctx = offscreen.getContext('2d', { willReadFrequently: true }); + ctx.drawImage(img, 0, 0); + + const imageData = ctx.getImageData(0, 0, offscreen.width, offscreen.height); + const data = imageData.data; + // threshold: pixels com R,G,B >= threshold são considerados "fundos a remover" + // softRange: faixa de suavização para bordas anti-aliased + const softRange = 20; + + for (let i = 0; i < data.length; i += 4) { + const r = data[i]; + const g = data[i + 1]; + const b = data[i + 2]; + + // Pega o mínimo dos canais (garante que só pixels "esbranquiçados" sejam afetados) + const minChannel = Math.min(r, g, b); + + if (minChannel >= threshold) { + // Área central do branco: totalmente transparente + data[i + 3] = 0; + } else if (minChannel >= threshold - softRange) { + // Zona de borda: transição linear suave para preservar contornos + const ratio = (minChannel - (threshold - softRange)) / softRange; + data[i + 3] = Math.round((1 - ratio) * data[i + 3]); + } + // Pixels abaixo do threshold - softRange: mantém opacidade original + } + + ctx.putImageData(imageData, 0, 0); + resolve(offscreen.toDataURL('image/png')); + }; + img.onerror = () => { + console.warn('[removeWhiteBackground] Falha ao carregar imagem, usando original:', imgUrl); + resolve(imgUrl); + }; + img.src = imgUrl; + }); + } + function addSceneLayerToCanvas(layerData) { if (!canvasModeActive || !cartazPrintableArea) return; @@ -7622,44 +7670,50 @@ document.addEventListener('DOMContentLoaded', () => { item.style.top = (layerData.top || 10) + '%'; item.style.width = (layerData.width || 35) + '%'; item.style.height = (layerData.height || 35) + '%'; - item.style.zIndex = layerData.isBackground ? '1' : String(10 + (layerData.zOffset || 0)); + item.style.zIndex = layerData.isBackground ? '0' : String(10 + (layerData.zOffset || 0)); item.style.background = 'transparent'; if (layerData.isBackground) { - // Fundo: cobrindo 100% do canvas item.style.left = '0'; item.style.top = '0'; item.style.width = '100%'; item.style.height = '100%'; item.style.zIndex = '0'; - item.style.isolation = 'isolate'; // Isola o fundo para evitar bleeding do blend-mode dos filhos item.style.pointerEvents = 'none'; item.innerHTML = ``; item.title = '🖼️ Cenário de Fundo'; - } else { - // Elementos: mix-blend-mode: multiply faz o fundo branco desaparecer (efeito sticker) - item.style.isolation = 'isolate'; - item.innerHTML = ``; - item.title = '🎬 ' + (layerData.name || 'Elemento'); - } - const handle = document.createElement('div'); - handle.className = 'canvas-resize-handle'; - item.appendChild(handle); - - cartazPrintableArea.appendChild(item); - - if (!layerData.isBackground) { - setupCanvasItemEvents(item); - // Permitir que o fundo seja selecionável após clicar nele diretamente - } else { - // para o fundo, adicionar capacidade de clicar para habilitar arrastar + const handle = document.createElement('div'); + handle.className = 'canvas-resize-handle'; + item.appendChild(handle); + cartazPrintableArea.appendChild(item); item.style.pointerEvents = 'auto'; setupCanvasItemEvents(item); + + } else { + // Adiciona o item ao canvas com um placeholder de loading + item.innerHTML = `
+ +
`; + item.title = '🎬 ' + (layerData.name || 'Elemento'); + + const handle = document.createElement('div'); + handle.className = 'canvas-resize-handle'; + item.appendChild(handle); + cartazPrintableArea.appendChild(item); + setupCanvasItemEvents(item); + + // Remove o fundo branco de forma assíncrona via Canvas API + removeWhiteBackground(layerData.url).then(transparentDataUrl => { + item.innerHTML = ``; + const newHandle = document.createElement('div'); + newHandle.className = 'canvas-resize-handle'; + item.appendChild(newHandle); + }); } + return item; }