🎨 fix: Remoção real de fundo branco via Canvas API - transparência genuína nos stickers de cena

This commit is contained in:
2026-06-08 11:02:43 +00:00
parent 4c0df55c66
commit cf5a46c86c
+76 -22
View File
@@ -7610,6 +7610,54 @@ document.addEventListener('DOMContentLoaded', () => {
const sceneLayersItems = document.getElementById('sceneLayersItems'); const sceneLayersItems = document.getElementById('sceneLayersItems');
// Cria um item de camada de imagem direto no canvas (background ou sticker) // 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) { function addSceneLayerToCanvas(layerData) {
if (!canvasModeActive || !cartazPrintableArea) return; if (!canvasModeActive || !cartazPrintableArea) return;
@@ -7622,44 +7670,50 @@ document.addEventListener('DOMContentLoaded', () => {
item.style.top = (layerData.top || 10) + '%'; item.style.top = (layerData.top || 10) + '%';
item.style.width = (layerData.width || 35) + '%'; item.style.width = (layerData.width || 35) + '%';
item.style.height = (layerData.height || 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'; item.style.background = 'transparent';
if (layerData.isBackground) { if (layerData.isBackground) {
// Fundo: cobrindo 100% do canvas
item.style.left = '0'; item.style.left = '0';
item.style.top = '0'; item.style.top = '0';
item.style.width = '100%'; item.style.width = '100%';
item.style.height = '100%'; item.style.height = '100%';
item.style.zIndex = '0'; item.style.zIndex = '0';
item.style.isolation = 'isolate'; // Isola o fundo para evitar bleeding do blend-mode dos filhos
item.style.pointerEvents = 'none'; item.style.pointerEvents = 'none';
item.innerHTML = `<img src="${layerData.url}" item.innerHTML = `<img src="${layerData.url}"
style="width:100%; height:100%; object-fit:cover; pointer-events:none; display:block;">`; style="width:100%; height:100%; object-fit:cover; pointer-events:none; display:block;">`;
item.title = '🖼️ Cenário de Fundo'; 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 = `<img src="${layerData.url}"
style="width:100%; height:100%; object-fit:contain; pointer-events:none;
mix-blend-mode: multiply; display:block;">`;
item.title = '🎬 ' + (layerData.name || 'Elemento');
}
const handle = document.createElement('div'); const handle = document.createElement('div');
handle.className = 'canvas-resize-handle'; handle.className = 'canvas-resize-handle';
item.appendChild(handle); item.appendChild(handle);
cartazPrintableArea.appendChild(item);
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
item.style.pointerEvents = 'auto'; item.style.pointerEvents = 'auto';
setupCanvasItemEvents(item); setupCanvasItemEvents(item);
} else {
// Adiciona o item ao canvas com um placeholder de loading
item.innerHTML = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;opacity:0.4;">
<span style="font-size:1.5rem;animation:spin 1s linear infinite;"></span>
</div>`;
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 = `<img src="${transparentDataUrl}"
style="width:100%; height:100%; object-fit:contain; pointer-events:none; display:block;">`;
const newHandle = document.createElement('div');
newHandle.className = 'canvas-resize-handle';
item.appendChild(newHandle);
});
} }
return item; return item;
} }