🎨 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');
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user