Add mask drawing tool and improve mask handling

Introduces a new MaskTool for interactive mask drawing with adjustable brush size, strength, and softness. Updates Canvas.js to integrate mask editing, rendering, and saving, including support for saving images with and without masks. Enhances the UI in Canvas_view.js with mask controls and a dialog for canvas resizing. Updates canvas_node.py to load images without masks for processing. These changes improve user control over mask creation and management in the canvas workflow.
This commit is contained in:
Dariusz L
2025-06-25 02:47:40 +02:00
parent 03ab254f63
commit acdd12b65e
4 changed files with 541 additions and 45 deletions

View File

@@ -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)

View File

@@ -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');

View File

@@ -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")
]);

142
js/Mask_tool.js Normal file
View File

@@ -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);
}
}