diff --git a/canvas_node.py b/canvas_node.py index 35dd442..fafc890 100644 --- a/canvas_node.py +++ b/canvas_node.py @@ -250,9 +250,9 @@ class CanvasNode: self.__class__._canvas_cache['cache_enabled'] = cache_enabled try: - - path_image = folder_paths.get_annotated_filepath(canvas_image) - i = Image.open(path_image) + # Wczytaj obraz bez maski + path_image_without_mask = folder_paths.get_annotated_filepath(canvas_image.replace('.png', '_without_mask.png')) + i = Image.open(path_image_without_mask) i = ImageOps.exif_transpose(i) if i.mode not in ['RGB', 'RGBA']: i = i.convert('RGB') @@ -263,23 +263,22 @@ class CanvasNode: image = rgb * alpha + (1 - alpha) * 0.5 processed_image = torch.from_numpy(image)[None,] except Exception as e: - + print(f"Error loading image without mask: {str(e)}") processed_image = torch.ones((1, 512, 512, 3), dtype=torch.float32) try: - + # Wczytaj maskę + path_image = folder_paths.get_annotated_filepath(canvas_image) path_mask = path_image.replace('.png', '_mask.png') if os.path.exists(path_mask): mask = Image.open(path_mask).convert('L') mask = np.array(mask).astype(np.float32) / 255.0 processed_mask = torch.from_numpy(mask)[None,] else: - processed_mask = torch.ones((1, processed_image.shape[1], processed_image.shape[2]), dtype=torch.float32) except Exception as e: print(f"Error loading mask: {str(e)}") - processed_mask = torch.ones((1, processed_image.shape[1], processed_image.shape[2]), dtype=torch.float32) diff --git a/js/Canvas.js b/js/Canvas.js index d74642a..c2bda59 100644 --- a/js/Canvas.js +++ b/js/Canvas.js @@ -1,4 +1,5 @@ import { getCanvasState, setCanvasState, removeCanvasState } from "./db.js"; +import { MaskTool } from "./Mask_tool.js"; export class Canvas { constructor(node, widget) { @@ -50,6 +51,7 @@ export class Canvas { this.dataInitialized = false; this.pendingDataCheck = null; + this.maskTool = new MaskTool(this); this.initCanvas(); this.setupEventListeners(); this.initNodeData(); @@ -325,9 +327,20 @@ export class Canvas { handleMouseDown(e) { this.canvas.focus(); + const worldCoords = this.getMouseWorldCoordinates(e); + + if (this.maskTool.isActive) { + if (e.button === 1) { // Środkowy przycisk myszy (kółko) + this.startPanning(e); + this.render(); + return; + } + this.maskTool.handleMouseDown(worldCoords); + this.render(); + return; + } const currentTime = Date.now(); - const worldCoords = this.getMouseWorldCoordinates(e); if (e.shiftKey && e.ctrlKey) { this.startCanvasMove(worldCoords); this.render(); @@ -466,6 +479,16 @@ export class Canvas { const worldCoords = this.getMouseWorldCoordinates(e); this.lastMousePosition = worldCoords; + if (this.maskTool.isActive) { + if (this.interaction.mode === 'panning') { + this.panViewport(e); + return; + } + this.maskTool.handleMouseMove(worldCoords); + if (this.maskTool.isDrawing) this.render(); + return; + } + switch (this.interaction.mode) { case 'panning': this.panViewport(e); @@ -493,6 +516,18 @@ export class Canvas { handleMouseUp(e) { + if (this.maskTool.isActive) { + if (this.interaction.mode === 'panning') { + this.resetInteractionState(); + this.render(); + return; + } + this.maskTool.handleMouseUp(); + this.saveState(); + this.render(); + return; + } + const interactionEnded = this.interaction.mode !== 'none' && this.interaction.mode !== 'panning'; if (this.interaction.mode === 'resizingCanvas') { @@ -510,6 +545,11 @@ export class Canvas { handleMouseLeave(e) { + if (this.maskTool.isActive) { + this.maskTool.handleMouseUp(); + this.render(); + return; + } if (this.interaction.mode !== 'none') { this.resetInteractionState(); this.render(); @@ -519,7 +559,20 @@ export class Canvas { handleWheel(e) { e.preventDefault(); - if (this.selectedLayer) { + if (this.maskTool.isActive) { + // W trybie maski zezwalaj tylko na zoom i przesuwanie canvasu + const worldCoords = this.getMouseWorldCoordinates(e); + const rect = this.canvas.getBoundingClientRect(); + const mouseBufferX = (e.clientX - rect.left) * (this.offscreenCanvas.width / rect.width); + const mouseBufferY = (e.clientY - rect.top) * (this.offscreenCanvas.height / rect.height); + + const zoomFactor = e.deltaY < 0 ? 1.1 : 1 / 1.1; + const newZoom = this.viewport.zoom * zoomFactor; + + this.viewport.zoom = Math.max(0.1, Math.min(10, newZoom)); + this.viewport.x = worldCoords.x - (mouseBufferX / this.viewport.zoom); + this.viewport.y = worldCoords.y - (mouseBufferY / this.viewport.zoom); + } else if (this.selectedLayer) { const rotationStep = 5 * (e.deltaY > 0 ? -1 : 1); this.selectedLayers.forEach(layer => { @@ -593,6 +646,35 @@ export class Canvas { } handleKeyDown(e) { + if (this.maskTool.isActive) { + // W trybie maski zezwalaj tylko na podstawowe skróty (np. cofnij/powtórz) + if (e.key === 'Control') this.interaction.isCtrlPressed = true; + if (e.key === 'Alt') { + this.interaction.isAltPressed = true; + e.preventDefault(); + } + + if (e.ctrlKey) { + if (e.key.toLowerCase() === 'z') { + e.preventDefault(); + e.stopPropagation(); + if (e.shiftKey) { + this.redo(); + } else { + this.undo(); + } + return; + } + if (e.key.toLowerCase() === 'y') { + e.preventDefault(); + e.stopPropagation(); + this.redo(); + return; + } + } + return; // Blokuj inne interakcje klawiaturowe w trybie maski + } + if (e.key === 'Control') this.interaction.isCtrlPressed = true; if (e.key === 'Alt') { this.interaction.isAltPressed = true; @@ -1128,6 +1210,7 @@ export class Canvas { } this.width = width; this.height = height; + this.maskTool.resize(width, height); this.canvas.width = width; this.canvas.height = height; @@ -1210,6 +1293,23 @@ export class Canvas { }); this.drawCanvasOutline(ctx); + + // Renderowanie maski w zależności od trybu + const maskImage = this.maskTool.getMask(); + if (this.maskTool.isActive) { + // W trybie maski pokazuj maskę z przezroczystością 0.5 + ctx.globalCompositeOperation = 'source-over'; + ctx.globalAlpha = 0.5; + ctx.drawImage(maskImage, 0, 0); + ctx.globalAlpha = 1.0; + } else if (maskImage) { + // W trybie warstw pokazuj maskę jako widoczną, ale nieedytowalną + ctx.globalCompositeOperation = 'source-over'; + ctx.globalAlpha = 1.0; + ctx.drawImage(maskImage, 0, 0); + ctx.globalAlpha = 1.0; + } + if (this.interaction.mode === 'resizingCanvas' && this.interaction.canvasResizeRect) { const rect = this.interaction.canvasResizeRect; ctx.save(); @@ -1496,7 +1596,6 @@ export class Canvas { async saveToServer(fileName) { return new Promise((resolve) => { - const tempCanvas = document.createElement('canvas'); const maskCanvas = document.createElement('canvas'); tempCanvas.width = this.width; @@ -1510,36 +1609,50 @@ export class Canvas { tempCtx.fillStyle = '#ffffff'; tempCtx.fillRect(0, 0, this.width, this.height); - maskCtx.fillStyle = '#000000'; + maskCtx.fillStyle = '#ffffff'; // Białe tło dla wolnych przestrzeni maskCtx.fillRect(0, 0, this.width, this.height); + // Rysowanie warstw this.layers.sort((a, b) => a.zIndex - b.zIndex).forEach(layer => { - tempCtx.save(); - tempCtx.globalCompositeOperation = layer.blendMode || 'normal'; tempCtx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1; - tempCtx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); tempCtx.rotate(layer.rotation * Math.PI / 180); - tempCtx.drawImage( - layer.image, - -layer.width / 2, - -layer.height / 2, - layer.width, - layer.height - ); + tempCtx.drawImage(layer.image, -layer.width / 2, -layer.height / 2, layer.width, layer.height); tempCtx.restore(); maskCtx.save(); maskCtx.translate(layer.x + layer.width / 2, layer.y + layer.height / 2); maskCtx.rotate(layer.rotation * Math.PI / 180); - maskCtx.globalCompositeOperation = 'lighter'; + maskCtx.globalCompositeOperation = 'source-over'; // Używamy source-over, aby uwzględnić stopniową przezroczystość if (layer.mask) { - maskCtx.drawImage(layer.mask, -layer.width / 2, -layer.height / 2, layer.width, layer.height); - } else { + // Jeśli warstwa ma maskę, używamy jej jako alpha kanału + const layerCanvas = document.createElement('canvas'); + layerCanvas.width = layer.width; + layerCanvas.height = layer.height; + const layerCtx = layerCanvas.getContext('2d'); + layerCtx.drawImage(layer.mask, 0, 0, layer.width, layer.height); + const imageData = layerCtx.getImageData(0, 0, layer.width, layer.height); + const alphaCanvas = document.createElement('canvas'); + alphaCanvas.width = layer.width; + alphaCanvas.height = layer.height; + const alphaCtx = alphaCanvas.getContext('2d'); + const alphaData = alphaCtx.createImageData(layer.width, layer.height); + + for (let i = 0; i < imageData.data.length; i += 4) { + const alpha = imageData.data[i + 3] * (layer.opacity !== undefined ? layer.opacity : 1); + // Odwracamy alpha, aby przezroczyste obszary warstwy były nieprzezroczyste na masce + alphaData.data[i] = alphaData.data[i + 1] = alphaData.data[i + 2] = 255 - alpha; + alphaData.data[i + 3] = 255; + } + + alphaCtx.putImageData(alphaData, 0, 0); + maskCtx.drawImage(alphaCanvas, -layer.width / 2, -layer.height / 2, layer.width, layer.height); + } else { + // Jeśli warstwa nie ma maski, używamy jej alpha kanału const layerCanvas = document.createElement('canvas'); layerCanvas.width = layer.width; layerCanvas.height = layer.height; @@ -1555,7 +1668,8 @@ export class Canvas { for (let i = 0; i < imageData.data.length; i += 4) { const alpha = imageData.data[i + 3] * (layer.opacity !== undefined ? layer.opacity : 1); - alphaData.data[i] = alphaData.data[i + 1] = alphaData.data[i + 2] = alpha; + // Odwracamy alpha, aby przezroczyste obszary warstwy były nieprzezroczyste na masce + alphaData.data[i] = alphaData.data[i + 1] = alphaData.data[i + 2] = 255 - alpha; alphaData.data[i + 3] = 255; } @@ -1565,15 +1679,48 @@ export class Canvas { maskCtx.restore(); }); - const finalMaskData = maskCtx.getImageData(0, 0, this.width, this.height); - for (let i = 0; i < finalMaskData.data.length; i += 4) { - finalMaskData.data[i] = - finalMaskData.data[i + 1] = - finalMaskData.data[i + 2] = 255 - finalMaskData.data[i]; - finalMaskData.data[i + 3] = 255; - } - maskCtx.putImageData(finalMaskData, 0, 0); + // Nałóż maskę z narzędzia MaskTool, uwzględniając przezroczystość pędzla + const toolMaskCanvas = this.maskTool.getMask(); + if (toolMaskCanvas) { + // Utwórz tymczasowy canvas, aby zachować wartości alpha maski z MaskTool + const tempMaskCanvas = document.createElement('canvas'); + tempMaskCanvas.width = this.width; + tempMaskCanvas.height = this.height; + const tempMaskCtx = tempMaskCanvas.getContext('2d'); + tempMaskCtx.drawImage(toolMaskCanvas, 0, 0); + const tempMaskData = tempMaskCtx.getImageData(0, 0, this.width, this.height); + // Zachowaj wartości alpha, aby obszary narysowane pędzlem były nieprzezroczyste na masce + for (let i = 0; i < tempMaskData.data.length; i += 4) { + const alpha = tempMaskData.data[i + 3]; + tempMaskData.data[i] = tempMaskData.data[i + 1] = tempMaskData.data[i + 2] = 255; + tempMaskData.data[i + 3] = alpha; // Zachowaj oryginalną przezroczystość pędzla + } + tempMaskCtx.putImageData(tempMaskData, 0, 0); + + // Nałóż maskę z MaskTool na maskę główną + maskCtx.globalCompositeOperation = 'source-over'; // Dodaje nieprzezroczystość tam, gdzie pędzel był użyty + maskCtx.drawImage(tempMaskCanvas, 0, 0); + } + + // Zapisz obraz bez maski + const fileNameWithoutMask = fileName.replace('.png', '_without_mask.png'); + tempCanvas.toBlob(async (blobWithoutMask) => { + const formDataWithoutMask = new FormData(); + formDataWithoutMask.append("image", blobWithoutMask, fileNameWithoutMask); + formDataWithoutMask.append("overwrite", "true"); + + try { + await fetch("/upload/image", { + method: "POST", + body: formDataWithoutMask, + }); + } catch (error) { + console.error("Error uploading image without mask:", error); + } + }, "image/png"); + + // Zapisz obraz z maską tempCanvas.toBlob(async (blob) => { const formData = new FormData(); formData.append("image", blob, fileName); @@ -1586,7 +1733,6 @@ export class Canvas { }); if (resp.status === 200) { - maskCanvas.toBlob(async (maskBlob) => { const maskFormData = new FormData(); const maskFileName = fileName.replace('.png', '_mask.png'); diff --git a/js/Canvas_view.js b/js/Canvas_view.js index 44dce1c..5ffb947 100644 --- a/js/Canvas_view.js +++ b/js/Canvas_view.js @@ -65,6 +65,19 @@ async function createCanvasWidget(node, widget, app) { justify-content: flex-start; } + .painter-slider-container { + display: flex; + align-items: center; + gap: 8px; + color: #fff; + font-size: 12px; + } + + .painter-slider-container input[type="range"] { + width: 80px; + } + + .painter-button-group { display: flex; align-items: center; @@ -388,12 +401,89 @@ async function createCanvasWidget(node, widget, app) { // --- Group: Canvas & Layers --- $el("div.painter-button-group", {}, [ - $el("button.painter-button", { - textContent: "Canvas Size", - onclick: () => { - // Dialog logic remains the same - } - }), + $el("button.painter-button", { + textContent: "Canvas Size", + onclick: () => { + const dialog = $el("div.painter-dialog", { + style: { + position: 'fixed', + left: '50%', + top: '50%', + transform: 'translate(-50%, -50%)', + zIndex: '1000' + } + }, [ + $el("div", { + style: { + color: "white", + marginBottom: "10px" + } + }, [ + $el("label", { + style: { + marginRight: "5px" + } + }, [ + $el("span", {}, ["Width: "]) + ]), + $el("input", { + type: "number", + id: "canvas-width", + value: canvas.width, + min: "1", + max: "4096" + }) + ]), + $el("div", { + style: { + color: "white", + marginBottom: "10px" + } + }, [ + $el("label", { + style: { + marginRight: "5px" + } + }, [ + $el("span", {}, ["Height: "]) + ]), + $el("input", { + type: "number", + id: "canvas-height", + value: canvas.height, + min: "1", + max: "4096" + }) + ]), + $el("div", { + style: { + textAlign: "right" + } + }, [ + $el("button", { + id: "cancel-size", + textContent: "Cancel" + }), + $el("button", { + id: "confirm-size", + textContent: "OK" + }) + ]) + ]); + document.body.appendChild(dialog); + + document.getElementById('confirm-size').onclick = () => { + const width = parseInt(document.getElementById('canvas-width').value) || canvas.width; + const height = parseInt(document.getElementById('canvas-height').value) || canvas.height; + canvas.updateCanvasSize(width, height); + document.body.removeChild(dialog); + }; + + document.getElementById('cancel-size').onclick = () => { + document.body.removeChild(dialog); + }; + } + }), $el("button.painter-button.requires-selection", { textContent: "Remove Layer", onclick: () => { @@ -485,10 +575,128 @@ async function createCanvasWidget(node, widget, app) { $el("div.painter-separator"), - // --- Group: Cache --- - $el("div.painter-button-group", {}, [ - $el("button.painter-button", { - textContent: "Clear Cache", + // --- Group: Tools & History --- + $el("div.painter-button-group", {}, [ + $el("button.painter-button.requires-selection.matting-button", { + textContent: "Matting", + onclick: async (e) => { + const button = e.target.closest('.matting-button'); + if (button.classList.contains('loading')) return; + const spinner = $el("div.matting-spinner"); + button.appendChild(spinner); + button.classList.add('loading'); + + try { + if (canvas.selectedLayers.length !== 1) throw new Error("Please select exactly one image layer for matting."); + + const selectedLayer = canvas.selectedLayers[0]; + const imageData = await canvas.getLayerImageData(selectedLayer); + const response = await fetch("/matting", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({image: imageData}) + }); + if (!response.ok) throw new Error(`Server error: ${response.status} - ${response.statusText}`); + + const result = await response.json(); + const mattedImage = new Image(); + mattedImage.src = result.matted_image; + await mattedImage.decode(); + const newLayer = { ...selectedLayer, image: mattedImage, zIndex: canvas.layers.length }; + canvas.layers.push(newLayer); + canvas.updateSelection([newLayer]); + canvas.render(); + canvas.saveState(); + await canvas.saveToServer(widget.value); + app.graph.runStep(); + } catch (error) { + console.error("Matting error:", error); + alert(`Error during matting process: ${error.message}`); + } finally { + button.classList.remove('loading'); + button.removeChild(spinner); + } + } + }), + $el("button.painter-button", { id: `undo-button-${node.id}`, textContent: "Undo", disabled: true, onclick: () => canvas.undo() }), + $el("button.painter-button", { id: `redo-button-${node.id}`, textContent: "Redo", disabled: true, onclick: () => canvas.redo() }), + ]), + + $el("div.painter-separator"), + + // --- Group: Masking --- + $el("div.painter-button-group", { id: "mask-controls" }, [ + $el("button.painter-button", { + id: "mask-mode-btn", + textContent: "Draw Mask", + onclick: () => { + const maskBtn = controlPanel.querySelector('#mask-mode-btn'); + const maskControls = controlPanel.querySelector('#mask-controls'); + + if (canvas.maskTool.isActive) { + canvas.maskTool.deactivate(); + maskBtn.classList.remove('primary'); + maskControls.querySelectorAll('.mask-control').forEach(c => c.style.display = 'none'); + } else { + canvas.maskTool.activate(); + maskBtn.classList.add('primary'); + maskControls.querySelectorAll('.mask-control').forEach(c => c.style.display = 'flex'); + } + } + }), + $el("div.painter-slider-container.mask-control", { style: { display: 'none' } }, [ + $el("label", { for: "brush-size-slider", textContent: "Size:" }), + $el("input", { + id: "brush-size-slider", + type: "range", + min: "1", + max: "200", + value: "20", + oninput: (e) => canvas.maskTool.setBrushSize(parseInt(e.target.value)) + }) + ]), + $el("div.painter-slider-container.mask-control", { style: { display: 'none' } }, [ + $el("label", { for: "brush-strength-slider", textContent: "Strength:" }), + $el("input", { + id: "brush-strength-slider", + type: "range", + min: "0", + max: "1", + step: "0.05", + value: "0.5", + oninput: (e) => canvas.maskTool.setBrushStrength(parseFloat(e.target.value)) + }) + ]), + $el("div.painter-slider-container.mask-control", { style: { display: 'none' } }, [ + $el("label", { for: "brush-softness-slider", textContent: "Softness:" }), + $el("input", { + id: "brush-softness-slider", + type: "range", + min: "0", + max: "1", + step: "0.05", + value: "0.5", + oninput: (e) => canvas.maskTool.setBrushSoftness(parseFloat(e.target.value)) + }) + ]), + $el("button.painter-button.mask-control", { + textContent: "Clear Mask", + style: { display: 'none' }, + onclick: () => { + if (confirm("Are you sure you want to clear the mask?")) { + canvas.maskTool.clear(); + canvas.render(); + } + } + }) + ]), + + $el("div.painter-separator"), + + // --- Group: Cache --- + $el("div.painter-button-group", {}, [ + $el("button.painter-button", { + textContent: "Clear Cache", style: { backgroundColor: "#c54747", borderColor: "#a53737" }, onclick: async () => { if (confirm("Are you sure you want to clear all saved canvas states? This action cannot be undone.")) { @@ -503,7 +711,8 @@ async function createCanvasWidget(node, widget, app) { } }) ]) - ]) + ]), + $el("div.painter-separator") ]); diff --git a/js/Mask_tool.js b/js/Mask_tool.js new file mode 100644 index 0000000..6e934bc --- /dev/null +++ b/js/Mask_tool.js @@ -0,0 +1,142 @@ +export class MaskTool { + constructor(canvasInstance) { + this.canvasInstance = canvasInstance; + this.mainCanvas = canvasInstance.canvas; + this.maskCanvas = document.createElement('canvas'); + this.maskCtx = this.maskCanvas.getContext('2d'); + + this.isActive = false; + this.brushSize = 20; + this.brushStrength = 0.5; + this.brushSoftness = 0.5; // Domyślna miękkość pędzla (0 - twardy, 1 - bardzo miękki) + this.isDrawing = false; + this.lastPosition = null; + + this.initMaskCanvas(); + } + + setBrushSoftness(softness) { + this.brushSoftness = Math.max(0, Math.min(1, softness)); + } + + initMaskCanvas() { + this.maskCanvas.width = this.mainCanvas.width; + this.maskCanvas.height = this.mainCanvas.height; + this.clear(); + } + + activate() { + this.isActive = true; + this.canvasInstance.interaction.mode = 'drawingMask'; + console.log("Mask tool activated"); + } + + deactivate() { + this.isActive = false; + this.canvasInstance.interaction.mode = 'none'; + console.log("Mask tool deactivated"); + } + + setBrushSize(size) { + this.brushSize = Math.max(1, size); + } + + setBrushStrength(strength) { + this.brushStrength = Math.max(0, Math.min(1, strength)); + } + + handleMouseDown(worldCoords) { + if (!this.isActive) return; + this.isDrawing = true; + this.lastPosition = worldCoords; + this.draw(worldCoords); + } + + handleMouseMove(worldCoords) { + if (!this.isActive || !this.isDrawing) return; + this.draw(worldCoords); + this.lastPosition = worldCoords; + } + + handleMouseUp() { + if (!this.isActive) return; + this.isDrawing = false; + this.lastPosition = null; + } + + draw(worldCoords) { + if (!this.lastPosition) { + this.lastPosition = worldCoords; + } + + this.maskCtx.beginPath(); + this.maskCtx.moveTo(this.lastPosition.x, this.lastPosition.y); + this.maskCtx.lineTo(worldCoords.x, worldCoords.y); + + // Utwórz gradient radialny dla miękkości pędzla + const gradientRadius = this.brushSize / 2; + const softnessFactor = this.brushSoftness * gradientRadius; + const gradient = this.maskCtx.createRadialGradient( + worldCoords.x, worldCoords.y, gradientRadius - softnessFactor, + worldCoords.x, worldCoords.y, gradientRadius + ); + gradient.addColorStop(0, `rgba(255, 255, 255, ${this.brushStrength})`); + gradient.addColorStop(1, `rgba(255, 255, 255, 0)`); + + this.maskCtx.strokeStyle = gradient; + this.maskCtx.lineWidth = this.brushSize; + this.maskCtx.lineCap = 'round'; + this.maskCtx.lineJoin = 'round'; + + this.maskCtx.globalCompositeOperation = 'source-over'; + this.maskCtx.stroke(); + } + + clear() { + this.maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height); + } + + getMask() { + return this.maskCanvas; + } + + getMaskImageWithAlpha() { + const tempCanvas = document.createElement('canvas'); + tempCanvas.width = this.maskCanvas.width; + tempCanvas.height = this.maskCanvas.height; + const tempCtx = tempCanvas.getContext('2d'); + + // Kopiuj maskę na tymczasowy canvas + tempCtx.drawImage(this.maskCanvas, 0, 0); + + // Pobierz dane pikseli + const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height); + const data = imageData.data; + + // Modyfikuj kanał alfa, aby zachować zróżnicowaną przezroczystość + for (let i = 0; i < data.length; i += 4) { + const alpha = data[i]; // Wartość alfa (0-255) + data[i] = 255; // Czerwony + data[i + 1] = 255; // Zielony + data[i + 2] = 255; // Niebieski + data[i + 3] = alpha; // Alfa (zachowaj oryginalną wartość) + } + + // Zapisz zmodyfikowane dane pikseli + tempCtx.putImageData(imageData, 0, 0); + + // Utwórz obraz z tymczasowego canvasu + const maskImage = new Image(); + maskImage.src = tempCanvas.toDataURL(); + return maskImage; + } + + resize(width, height){ + const oldMask = this.maskCanvas; + this.maskCanvas = document.createElement('canvas'); + this.maskCanvas.width = width; + this.maskCanvas.height = height; + this.maskCtx = this.maskCanvas.getContext('2d'); + this.maskCtx.drawImage(oldMask, 0, 0); + } +} \ No newline at end of file