🎨 fix: Remoção real de fundo branco via Canvas API - transparência genuína nos stickers de cena
This commit is contained in:
+76
-22
@@ -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 = `<img src="${layerData.url}"
|
||||
style="width:100%; height:100%; object-fit:cover; pointer-events:none; display:block;">`;
|
||||
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');
|
||||
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 = `<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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user