diff --git a/js/Canvas.js b/js/Canvas.js index 6a71db4..065b6da 100644 --- a/js/Canvas.js +++ b/js/Canvas.js @@ -197,6 +197,13 @@ export class Canvas { return this.canvasLayers.getFlattenedCanvasAsBlob(); } + /** + * Eksportuje spłaszczony canvas z maską jako kanałem alpha + */ + async getFlattenedCanvasWithMaskAsBlob() { + return this.canvasLayers.getFlattenedCanvasWithMaskAsBlob(); + } + /** * Importuje najnowszy obraz */ @@ -212,6 +219,7 @@ export class Canvas { * Uruchamia edytor masek */ async startMaskEditor() { + // Dla edytora masek używamy zwykłego spłaszczonego obrazu bez alpha const blob = await this.canvasLayers.getFlattenedCanvasAsBlob(); if (!blob) { log.warn("Canvas is empty, cannot open mask editor."); @@ -446,7 +454,8 @@ export class Canvas { this.saveState(); const new_preview = new Image(); - const blob = await this.canvasLayers.getFlattenedCanvasAsBlob(); + // Użyj nowej metody z maską jako kanałem alpha + const blob = await this.canvasLayers.getFlattenedCanvasWithMaskAsBlob(); if (blob) { new_preview.src = URL.createObjectURL(blob); await new Promise(r => new_preview.onload = r); diff --git a/js/CanvasLayers.js b/js/CanvasLayers.js index 785bcc0..4466456 100644 --- a/js/CanvasLayers.js +++ b/js/CanvasLayers.js @@ -596,6 +596,84 @@ export class CanvasLayers { }); } + async getFlattenedCanvasWithMaskAsBlob() { + return new Promise((resolve, reject) => { + const tempCanvas = document.createElement('canvas'); + tempCanvas.width = this.canvas.width; + tempCanvas.height = this.canvas.height; + const tempCtx = tempCanvas.getContext('2d'); + + // Najpierw renderuj wszystkie warstwy + const sortedLayers = [...this.canvas.layers].sort((a, b) => a.zIndex - b.zIndex); + + sortedLayers.forEach(layer => { + if (!layer.image) return; + + tempCtx.save(); + tempCtx.globalCompositeOperation = layer.blendMode || 'normal'; + tempCtx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1; + const centerX = layer.x + layer.width / 2; + const centerY = layer.y + layer.height / 2; + tempCtx.translate(centerX, centerY); + tempCtx.rotate(layer.rotation * Math.PI / 180); + tempCtx.drawImage( + layer.image, + -layer.width / 2, + -layer.height / 2, + layer.width, + layer.height + ); + + tempCtx.restore(); + }); + + // Pobierz dane obrazu + const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height); + const data = imageData.data; + + // Pobierz maskę + const maskCanvas = this.canvas.maskTool.getMask(); + + if (maskCanvas && maskCanvas.width > 0 && maskCanvas.height > 0) { + // Stwórz tymczasowy canvas dla maski w rozmiarze output area + const maskTempCanvas = document.createElement('canvas'); + maskTempCanvas.width = this.canvas.width; + maskTempCanvas.height = this.canvas.height; + const maskTempCtx = maskTempCanvas.getContext('2d'); + + // Narysuj odpowiedni fragment maski (uwzględniając pozycję maskTool) + const maskX = -this.canvas.maskTool.x; + const maskY = -this.canvas.maskTool.y; + maskTempCtx.drawImage(maskCanvas, maskX, maskY); + + // Pobierz dane maski + const maskImageData = maskTempCtx.getImageData(0, 0, this.canvas.width, this.canvas.height); + const maskData = maskImageData.data; + + // Zastosuj maskę jako kanał alpha + for (let i = 0; i < data.length; i += 4) { + // Pobierz wartość maski (używamy czerwonego kanału, bo maska jest biała) + const maskValue = maskData[i]; // R kanał maski + + // Maska biała (255) = pełna przezroczystość (alpha = 255) + // Maska czarna (0) = brak przezroczystości (alpha = 0) + data[i + 3] = maskValue; // Ustaw alpha na wartość maski + } + + // Zapisz zmodyfikowane dane obrazu + tempCtx.putImageData(imageData, 0, 0); + } + + tempCanvas.toBlob((blob) => { + if (blob) { + resolve(blob); + } else { + reject(new Error('Canvas toBlob failed.')); + } + }, 'image/png'); + }); + } + async getFlattenedSelectionAsBlob() { if (this.canvas.selectedLayers.length === 0) { return null; diff --git a/js/CanvasView.js b/js/CanvasView.js index 8d73959..8373880 100644 --- a/js/CanvasView.js +++ b/js/CanvasView.js @@ -917,8 +917,24 @@ async function createCanvasWidget(node, widget, app) { const triggerWidget = node.widgets.find(w => w.name === "trigger"); - const updateOutput = () => { + const updateOutput = async () => { triggerWidget.value = (triggerWidget.value + 1) % 99999999; + + // Aktualizuj podgląd node z maską jako alpha + try { + const new_preview = new Image(); + const blob = await canvas.getFlattenedCanvasWithMaskAsBlob(); + if (blob) { + new_preview.src = URL.createObjectURL(blob); + await new Promise(r => new_preview.onload = r); + node.imgs = [new_preview]; + } else { + node.imgs = []; + } + } catch (error) { + console.error("Error updating node preview:", error); + } + // app.graph.runStep(); // Potentially not needed if we just want to mark dirty }; @@ -1210,6 +1226,19 @@ app.registerExtension({ } }, }, + { + content: "Open Image with Mask Alpha", + callback: async () => { + try { + const blob = await self.canvasWidget.getFlattenedCanvasWithMaskAsBlob(); + const url = URL.createObjectURL(blob); + window.open(url, '_blank'); + setTimeout(() => URL.revokeObjectURL(url), 1000); + } catch (e) { + log.error("Error opening image with mask:", e); + } + }, + }, { content: "Copy Image", callback: async () => { @@ -1224,6 +1253,20 @@ app.registerExtension({ } }, }, + { + content: "Copy Image with Mask Alpha", + callback: async () => { + try { + const blob = await self.canvasWidget.getFlattenedCanvasWithMaskAsBlob(); + const item = new ClipboardItem({'image/png': blob}); + await navigator.clipboard.write([item]); + log.info("Image with mask alpha copied to clipboard."); + } catch (e) { + log.error("Error copying image with mask:", e); + alert("Failed to copy image with mask to clipboard."); + } + }, + }, { content: "Save Image", callback: async () => { @@ -1242,6 +1285,24 @@ app.registerExtension({ } }, }, + { + content: "Save Image with Mask Alpha", + callback: async () => { + try { + const blob = await self.canvasWidget.getFlattenedCanvasWithMaskAsBlob(); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'canvas_output_with_mask.png'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + setTimeout(() => URL.revokeObjectURL(url), 1000); + } catch (e) { + log.error("Error saving image with mask:", e); + } + }, + }, ]; if (options.length > 0) { options.unshift({content: "___", disabled: true});