Remove auto mask loading docs and clean up code comments

Deleted documentation files related to automatic mask loading. Cleaned up and streamlined comments across Canvas.js, CanvasInteractions.js, CanvasLayers.js, CanvasView.js, MaskTool.js, mask_utils.js, and example scripts for improved clarity and maintainability. No functional changes to core logic.
This commit is contained in:
Dariusz L
2025-07-01 06:42:20 +02:00
parent 30fb89451f
commit a0ceb3b97c
9 changed files with 126 additions and 642 deletions

View File

@@ -1,186 +0,0 @@
# Automatyczne Nakładanie Masek w Mask Editorze
## Przegląd
Ta funkcjonalność pozwala na automatyczne nakładanie predefiniowanych masek po otwarciu mask editora ComfyUI. Główną zaletą jest możliwość wysłania czystego obrazu (bez maski) do editora, a następnie automatyczne nałożenie maski w edytorze, co pozwala na prawidłowe działanie narzędzi takich jak gumka.
## Kluczowe Funkcje
### 1. Wysyłanie Czystego Obrazu
- Możliwość wysłania obrazu bez istniejącej maski do mask editora
- Editor "pamięta" oryginalny obraz pod maską
- Prawidłowe działanie gumki i innych narzędzi edycji
### 2. Automatyczne Nakładanie Maski
- Maska jest nakładana automatycznie po otwarciu editora
- Obsługa zarówno nowego jak i starego mask editora ComfyUI
- Natychmiastowe nakładanie bez opóźnień
### 3. Elastyczne Formaty Masek
- Obsługa obiektów Image
- Obsługa elementów HTMLCanvasElement
- Automatyczna konwersja formatów
## API
### Canvas.startMaskEditor(predefinedMask, sendCleanImage)
Główna metoda do uruchamiania mask editora z predefiniowaną maską.
**Parametry:**
- `predefinedMask` (Image|HTMLCanvasElement|null) - Opcjonalna maska do nałożenia
- `sendCleanImage` (boolean) - Czy wysłać czysty obraz (domyślnie false)
**Przykład:**
```javascript
// Wyślij czysty obraz i nałóż maskę w edytorze
await canvas.startMaskEditor(maskImage, true);
// Wyślij obraz z istniejącą maską i dodaj nową maskę
await canvas.startMaskEditor(maskImage, false);
```
### Funkcje Pomocnicze (mask_utils.js)
#### start_mask_editor_with_predefined_mask(canvasInstance, maskImage, sendCleanImage)
Pomocnicza funkcja dla łatwiejszego użycia.
**Parametry:**
- `canvasInstance` - Instancja Canvas
- `maskImage` - Obraz maski
- `sendCleanImage` - Czy wysłać czysty obraz (domyślnie true)
#### create_mask_from_image_src(imageSrc)
Tworzy obiekt Image z URL lub data URL.
**Parametry:**
- `imageSrc` (string) - URL obrazu
**Zwraca:** Promise<Image>
#### canvas_to_mask_image(canvas)
Konwertuje HTMLCanvasElement do obiektu Image.
**Parametry:**
- `canvas` (HTMLCanvasElement) - Canvas do konwersji
**Zwraca:** Promise<Image>
## Jak to Działa
### 1. Przygotowanie Obrazu
```javascript
// Wybór metody w zależności od parametru sendCleanImage
if (sendCleanImage) {
blob = await this.canvasLayers.getFlattenedCanvasAsBlob();
} else {
blob = await this.canvasLayers.getFlattenedCanvasForMaskEditor();
}
```
### 2. Otwieranie Editora
```javascript
// Standardowy proces otwierania mask editora
ComfyApp.copyToClipspace(this.node);
ComfyApp.clipspace_return_node = this.node;
ComfyApp.open_maskeditor();
```
### 3. Oczekiwanie na Otwarcie
```javascript
waitForMaskEditorAndApplyMask() {
const checkEditor = () => {
if (mask_editor_showing(app)) {
// Editor się otworzył - nałóż maskę
setTimeout(() => {
this.applyMaskToEditor(this.pendingMask);
}, 200);
} else {
setTimeout(checkEditor, 100);
}
};
checkEditor();
}
```
### 4. Nakładanie Maski
```javascript
// Automatyczne wykrywanie typu editora
const useNewEditor = app.ui.settings.getSettingValue('Comfy.MaskEditor.UseNewEditor');
if (useNewEditor) {
await this.applyMaskToNewEditor(maskData);
} else {
await this.applyMaskToOldEditor(maskData);
}
```
## Obsługa Różnych Editorów
### Nowy Editor (MessageBroker)
```javascript
const editor = MaskEditorDialog.instance;
const messageBroker = editor.getMessageBroker();
const maskCanvas = await messageBroker.pull('maskCanvas');
const maskCtx = await messageBroker.pull('maskCtx');
```
### Stary Editor (Bezpośredni Dostęp)
```javascript
const maskCanvas = document.getElementById('maskCanvas');
const maskCtx = maskCanvas.getContext('2d');
```
## Przetwarzanie Masek
Maski są automatycznie przetwarzane do odpowiedniego formatu:
```javascript
// Konwersja do formatu editora
for (let i = 0; i < data.length; i += 4) {
const alpha = data[i + 3];
data[i] = maskColor.r; // R
data[i + 1] = maskColor.g; // G
data[i + 2] = maskColor.b; // B
data[i + 3] = alpha; // Zachowaj alpha
}
```
## Obsługa Błędów
System zawiera kompleksową obsługę błędów:
```javascript
try {
await this.applyMaskToEditor(maskData);
log.info("Predefined mask applied successfully");
} catch (error) {
log.error("Failed to apply predefined mask:", error);
}
```
## Kompatybilność
- ✅ Nowy mask editor ComfyUI (MessageBroker)
- ✅ Stary mask editor ComfyUI (bezpośredni dostęp)
- ✅ Wszystkie formaty masek (Image, Canvas)
- ✅ Automatyczne wykrywanie typu editora
## Zalety
1. **Zachowanie Oryginalnego Obrazu**: Editor "pamięta" co jest pod maską
2. **Prawidłowe Działanie Gumki**: Możliwość usuwania części maski
3. **Elastyczność**: Obsługa różnych formatów i editorów
4. **Automatyzacja**: Brak potrzeby ręcznego nakładania masek
5. **Kompatybilność**: Działa z istniejącym kodem
## Przypadki Użycia
- Automatyczne nakładanie masek z AI/ML modeli
- Predefiniowane szablony masek
- Integracja z zewnętrznymi narzędziami
- Workflow automatyzacja
- Batch processing masek

View File

@@ -1,227 +0,0 @@
# Automatyczne Nakładanie Masek w Mask Editorze
## 🎯 Cel
Ta funkcjonalność rozwiązuje problem automatycznego nakładania predefiniowanych masek w mask editorze ComfyUI. Główną zaletą jest możliwość wysłania **czystego obrazu** (bez maski) do editora, a następnie automatyczne nałożenie maski w edytorze, co pozwala na prawidłowe działanie narzędzi takich jak gumka.
## ✨ Kluczowe Zalety
- 🖼️ **Czysty Obraz**: Wysyłanie obrazu bez istniejącej maski
- 🎨 **Prawidłowa Gumka**: Editor "pamięta" oryginalny obraz pod maską
-**Automatyzacja**: Maska nakładana automatycznie po otwarciu
- 🔄 **Kompatybilność**: Obsługa nowego i starego mask editora
- 📐 **Elastyczność**: Różne formaty masek (Image, Canvas)
## 🚀 Szybki Start
### Automatyczne Zachowanie (Zalecane)
```javascript
import { start_mask_editor_auto } from './js/utils/mask_utils.js';
// Uruchom mask editor z automatycznym zachowaniem:
// - Wyślij czysty obraz (bez maski)
// - Automatycznie nałóż istniejącą maskę z canvas
start_mask_editor_auto(canvasInstance);
```
### Użycie z Predefiniowaną Maską
```javascript
import { start_mask_editor_with_predefined_mask, create_mask_from_image_src } from './js/utils/mask_utils.js';
// Załaduj maskę z URL
const maskImage = await create_mask_from_image_src('/path/to/mask.png');
// Uruchom mask editor z czystym obrazem i predefiniowaną maską
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, true);
```
### Bezpośrednie Użycie Canvas API
```javascript
// Automatyczne zachowanie (domyślne)
await canvasInstance.startMaskEditor();
// Z predefiniowaną maską
await canvasInstance.startMaskEditor(maskImage, true);
```
## 📚 API Reference
### Canvas.startMaskEditor(predefinedMask, sendCleanImage)
**Parametry:**
- `predefinedMask` (Image|HTMLCanvasElement|null) - Maska do nałożenia
- `sendCleanImage` (boolean) - Czy wysłać czysty obraz (domyślnie false)
### Funkcje Pomocnicze
#### `start_mask_editor_with_predefined_mask(canvasInstance, maskImage, sendCleanImage)`
Główna funkcja pomocnicza do uruchamiania editora z maską.
#### `create_mask_from_image_src(imageSrc)`
Tworzy obiekt Image z URL.
#### `canvas_to_mask_image(canvas)`
Konwertuje Canvas do Image.
## 💡 Przykłady Użycia
### 1. Maska z URL
```javascript
const maskImage = await create_mask_from_image_src('/masks/face_mask.png');
start_mask_editor_with_predefined_mask(canvas, maskImage, true);
```
### 2. Maska z Canvas
```javascript
// Stwórz maskę programowo
const maskCanvas = document.createElement('canvas');
const ctx = maskCanvas.getContext('2d');
// ... rysowanie maski ...
const maskImage = await canvas_to_mask_image(maskCanvas);
start_mask_editor_with_predefined_mask(canvas, maskImage, true);
```
### 3. Maska z Danych Binarnych
```javascript
const blob = new Blob([binaryData], { type: 'image/png' });
const dataUrl = URL.createObjectURL(blob);
const maskImage = await create_mask_from_image_src(dataUrl);
start_mask_editor_with_predefined_mask(canvas, maskImage, true);
```
## 🔧 Jak to Działa
### 1. Przygotowanie Obrazu
System wybiera odpowiednią metodę w zależności od parametru `sendCleanImage`:
- `true`: Wysyła czysty obraz bez maski
- `false`: Wysyła obraz z istniejącą maską
### 2. Otwieranie Editora
Standardowy proces otwierania mask editora ComfyUI.
### 3. Automatyczne Nakładanie
Po otwarciu editora system:
- Wykrywa typ editora (nowy/stary)
- Przetwarza maskę do odpowiedniego formatu
- Nakłada maskę na canvas editora
- Zapisuje stan dla undo/redo
## 🎛️ Tryby Działania
### Czysty Obraz (Zalecany)
```javascript
// Wyślij czysty obraz, nałóż maskę w edytorze
await canvas.startMaskEditor(maskImage, true);
```
**Zalety:**
- Gumka działa prawidłowo
- Editor "pamięta" oryginalny obraz
- Pełna funkcjonalność narzędzi
### Kombinowany
```javascript
// Wyślij obraz z istniejącą maską, dodaj nową maskę
await canvas.startMaskEditor(maskImage, false);
```
**Zastosowanie:**
- Łączenie wielu masek
- Dodawanie do istniejącej maski
## 🔍 Obsługa Błędów
```javascript
try {
const maskImage = await create_mask_from_image_src('/path/to/mask.png');
start_mask_editor_with_predefined_mask(canvas, maskImage, true);
} catch (error) {
console.error('Błąd ładowania maski:', error);
// Fallback - uruchom bez maski
await canvas.startMaskEditor();
}
```
## 🏗️ Architektura
### Komponenty
- **Canvas.js**: Główna logika i API
- **mask_utils.js**: Funkcje pomocnicze
- **Detektory Editora**: Automatyczne wykrywanie typu editora
- **Procesory Masek**: Konwersja formatów
### Przepływ Danych
```
Maska → Przetwarzanie → Editor → Automatyczne Nakładanie
```
## 🔧 Konfiguracja
### Wymagania
- ComfyUI z mask editorem
- Obsługa ES6 modules
- Canvas API
### Integracja
```javascript
import { Canvas } from './js/Canvas.js';
import { start_mask_editor_with_predefined_mask } from './js/utils/mask_utils.js';
```
## 📋 Przypadki Użycia
- 🤖 **AI/ML Modele**: Automatyczne maski z modeli
- 📝 **Szablony**: Predefiniowane wzorce masek
- 🔗 **Integracje**: Zewnętrzne narzędzia i API
- ⚙️ **Workflow**: Automatyzacja procesów
- 📦 **Batch Processing**: Masowe przetwarzanie
## 🐛 Rozwiązywanie Problemów
### Maska się nie nakłada
- Sprawdź czy obraz maski jest załadowany
- Upewnij się że editor jest w pełni otwarty
- Sprawdź logi w konsoli
### Gumka nie działa
- Użyj `sendCleanImage = true`
- Sprawdź czy maska ma prawidłowy kanał alpha
### Błędy kompatybilności
- Sprawdź ustawienia mask editora w ComfyUI
- Upewnij się że używasz odpowiedniej wersji
## 📁 Struktura Plików
```
js/
├── Canvas.js # Główne API
├── utils/
│ └── mask_utils.js # Funkcje pomocnicze
├── examples/
│ └── mask_editor_examples.js # Przykłady użycia
└── Doc/
└── AutoMaskLoading # Szczegółowa dokumentacja
```
## 🔄 Kompatybilność
- ✅ Nowy mask editor ComfyUI (MessageBroker)
- ✅ Stary mask editor ComfyUI (bezpośredni dostęp)
- ✅ Wszystkie formaty masek (Image, Canvas, URL)
- ✅ Automatyczne wykrywanie konfiguracji
## 📈 Wydajność
- Minimalne opóźnienie nakładania (200ms)
- Automatyczna optymalizacja formatów
- Efektywne zarządzanie pamięcią
- Asynchroniczne operacje
---
**Autor:** Cline AI Assistant
**Wersja:** 1.0.0
**Data:** 2025-06-30

View File

@@ -50,19 +50,15 @@ export class Canvas {
this.dataInitialized = false;
this.pendingDataCheck = null;
this.imageCache = new Map();
// Inicjalizacja modułów
this._initializeModules(callbacks);
// Podstawowa konfiguracja
this._setupCanvas();
// Delegacja interaction dla kompatybilności wstecznej
this.interaction = this.canvasInteractions.interaction;
console.log('Canvas widget element:', this.node);
// Dodaj metodę do kontroli widoczności podglądu
this.previewVisible = true; // Domyślnie widoczny
this.setPreviewVisibility(false);
}
@@ -95,16 +91,14 @@ export class Canvas {
async setPreviewVisibility(visible) {
this.previewVisible = visible;
console.log("Canvas preview visibility set to:", visible);
// Znajdź i kontroluj ImagePreviewWidget
const imagePreviewWidget = await this.waitForWidget("$$canvas-image-preview", this.node);
if (imagePreviewWidget) {
console.log("Found $$canvas-image-preview widget, controlling visibility");
if (visible) {
console.log("=== SHOWING WIDGET ===");
// Pokaż widget
if (imagePreviewWidget.options) {
imagePreviewWidget.options.hidden = false;
}
@@ -123,8 +117,7 @@ export class Canvas {
console.log("ImagePreviewWidget shown");
} else {
console.log("=== HIDING WIDGET ===");
// Ukryj widget
if (imagePreviewWidget.options) {
imagePreviewWidget.options.hidden = true;
}
@@ -154,7 +147,7 @@ export class Canvas {
* @private
*/
_initializeModules(callbacks) {
// Moduły są publiczne dla bezpośredniego dostępu gdy potrzebne
this.maskTool = new MaskTool(this, {onStateChange: this.onStateChange});
this.canvasState = new CanvasState(this);
this.canvasInteractions = new CanvasInteractions(this);
@@ -172,17 +165,15 @@ export class Canvas {
this.initCanvas();
this.canvasInteractions.setupEventListeners();
this.canvasIO.initNodeData();
// Inicjalizacja warstw z domyślną przezroczystością
this.layers = this.layers.map(layer => ({
...layer,
opacity: 1
}));
}
// ==========================================
// GŁÓWNE OPERACJE FASADY
// ==========================================
/**
* Ładuje stan canvas z bazy danych
@@ -300,9 +291,8 @@ export class Canvas {
return this.canvasIO.importLatestImage();
}
// ==========================================
// OPERACJE NA MASCE
// ==========================================
/**
* Uruchamia edytor masek
@@ -310,11 +300,10 @@ export class Canvas {
* @param {boolean} sendCleanImage - Czy wysłać czysty obraz (bez maski) do editora
*/
async startMaskEditor(predefinedMask = null, sendCleanImage = true) {
// Zapisz obecny stan maski przed otwarciem editora (dla obsługi Cancel)
this.savedMaskState = await this.saveMaskState();
this.maskEditorCancelled = false;
// Jeśli nie ma predefiniowanej maski, stwórz ją z istniejącej maski canvas
if (!predefinedMask && this.maskTool && this.maskTool.maskCanvas) {
try {
predefinedMask = await this.createMaskFromCurrentMask();
@@ -322,17 +311,15 @@ export class Canvas {
log.warn("Could not create mask from current mask:", error);
}
}
// Przechowaj maskę do późniejszego użycia
this.pendingMask = predefinedMask;
// Wybierz odpowiednią metodę w zależności od parametru sendCleanImage
let blob;
if (sendCleanImage) {
// Wyślij czysty obraz bez maski (domyślne zachowanie)
blob = await this.canvasLayers.getFlattenedCanvasAsBlob();
} else {
// Używamy specjalnej metody która łączy pełny obraz z istniejącą maską
blob = await this.canvasLayers.getFlattenedCanvasForMaskEditor();
}
@@ -373,11 +360,9 @@ export class Canvas {
this.editorWasShowing = false;
this.waitWhileMaskEditing();
// Nasłuchuj na przycisk Cancel
this.setupCancelListener();
// Jeśli mamy predefiniowaną maskę, czekaj na otwarcie editora i nałóż ją
if (predefinedMask) {
this.waitForMaskEditorAndApplyMask();
}
@@ -388,9 +373,8 @@ export class Canvas {
}
}
// ==========================================
// METODY POMOCNICZE
// ==========================================
/**
* Inicjalizuje podstawowe właściwości canvas
@@ -503,9 +487,8 @@ export class Canvas {
}
}
// ==========================================
// METODY DLA EDYTORA MASEK
// ==========================================
/**
* Czeka na otwarcie mask editora i automatycznie nakłada predefiniowaną maskę
@@ -518,15 +501,15 @@ export class Canvas {
attempts++;
if (mask_editor_showing(app)) {
// Editor się otworzył - sprawdź czy jest w pełni zainicjalizowany
const useNewEditor = app.ui.settings.getSettingValue('Comfy.MaskEditor.UseNewEditor');
let editorReady = false;
if (useNewEditor) {
// Sprawdź czy nowy editor jest gotowy - różne metody wykrywania
const MaskEditorDialog = window.MaskEditorDialog;
if (MaskEditorDialog && MaskEditorDialog.instance) {
// Sprawdź czy ma MessageBroker i czy canvas jest dostępny
try {
const messageBroker = MaskEditorDialog.instance.getMessageBroker();
if (messageBroker) {
@@ -534,16 +517,15 @@ export class Canvas {
log.info("New mask editor detected as ready via MessageBroker");
}
} catch (e) {
// MessageBroker jeszcze nie gotowy
editorReady = false;
}
}
// Alternatywne wykrywanie - sprawdź czy istnieje element maskEditor
if (!editorReady) {
const maskEditorElement = document.getElementById('maskEditor');
if (maskEditorElement && maskEditorElement.style.display !== 'none') {
// Sprawdź czy ma canvas wewnątrz
const canvas = maskEditorElement.querySelector('canvas');
if (canvas) {
editorReady = true;
@@ -552,7 +534,7 @@ export class Canvas {
}
}
} else {
// Sprawdź czy stary editor jest gotowy
const maskCanvas = document.getElementById('maskCanvas');
editorReady = maskCanvas && maskCanvas.getContext && maskCanvas.width > 0;
if (editorReady) {
@@ -561,21 +543,21 @@ export class Canvas {
}
if (editorReady) {
// Editor jest gotowy - nałóż maskę po krótkim opóźnieniu
log.info("Applying mask to editor after", attempts * 100, "ms wait");
setTimeout(() => {
this.applyMaskToEditor(this.pendingMask);
this.pendingMask = null; // Wyczyść po użyciu
}, 300); // Krótsze opóźnienie gdy już wiemy że jest gotowy
} else if (attempts < maxAttempts) {
// Editor widoczny ale nie gotowy - sprawdź ponownie
if (attempts % 10 === 0) {
log.info("Waiting for mask editor to be ready... attempt", attempts, "/", maxAttempts);
}
setTimeout(checkEditor, 100);
} else {
log.warn("Mask editor timeout - editor not ready after", maxAttempts * 100, "ms");
// Spróbuj nałożyć maskę mimo wszystko
log.info("Attempting to apply mask anyway...");
setTimeout(() => {
this.applyMaskToEditor(this.pendingMask);
@@ -583,7 +565,7 @@ export class Canvas {
}, 100);
}
} else if (attempts < maxAttempts) {
// Editor jeszcze nie widoczny - sprawdź ponownie
setTimeout(checkEditor, 100);
} else {
log.warn("Mask editor timeout - editor not showing after", maxAttempts * 100, "ms");
@@ -600,28 +582,28 @@ export class Canvas {
*/
async applyMaskToEditor(maskData) {
try {
// Sprawdź czy używamy nowego czy starego editora
const useNewEditor = app.ui.settings.getSettingValue('Comfy.MaskEditor.UseNewEditor');
if (useNewEditor) {
// Sprawdź czy nowy editor jest rzeczywiście dostępny
const MaskEditorDialog = window.MaskEditorDialog;
if (MaskEditorDialog && MaskEditorDialog.instance) {
// Nowy editor - użyj MessageBroker
await this.applyMaskToNewEditor(maskData);
} else {
log.warn("New editor setting enabled but instance not found, trying old editor");
await this.applyMaskToOldEditor(maskData);
}
} else {
// Stary editor - bezpośredni dostęp do canvas
await this.applyMaskToOldEditor(maskData);
}
log.info("Predefined mask applied to mask editor successfully");
} catch (error) {
log.error("Failed to apply predefined mask to editor:", error);
// Spróbuj alternatywną metodę
try {
log.info("Trying alternative mask application method...");
await this.applyMaskToOldEditor(maskData);
@@ -637,7 +619,7 @@ export class Canvas {
* @param {Image|HTMLCanvasElement} maskData - Dane maski
*/
async applyMaskToNewEditor(maskData) {
// Pobierz instancję nowego editora
const MaskEditorDialog = window.MaskEditorDialog;
if (!MaskEditorDialog || !MaskEditorDialog.instance) {
throw new Error("New mask editor instance not found");
@@ -645,20 +627,16 @@ export class Canvas {
const editor = MaskEditorDialog.instance;
const messageBroker = editor.getMessageBroker();
// Pobierz canvas maski z editora
const maskCanvas = await messageBroker.pull('maskCanvas');
const maskCtx = await messageBroker.pull('maskCtx');
const maskColor = await messageBroker.pull('getMaskColor');
// Konwertuj maskę do odpowiedniego formatu
const processedMask = await this.processMaskForEditor(maskData, maskCanvas.width, maskCanvas.height, maskColor);
// Nałóż maskę na canvas
maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
maskCtx.drawImage(processedMask, 0, 0);
// Zapisz stan dla undo/redo
messageBroker.publish('saveState');
}
@@ -667,19 +645,17 @@ export class Canvas {
* @param {Image|HTMLCanvasElement} maskData - Dane maski
*/
async applyMaskToOldEditor(maskData) {
// Znajdź canvas maski w starym edytorze
const maskCanvas = document.getElementById('maskCanvas');
if (!maskCanvas) {
throw new Error("Old mask editor canvas not found");
}
const maskCtx = maskCanvas.getContext('2d');
// Konwertuj maskę do odpowiedniego formatu (dla starego editora używamy białego koloru)
const maskColor = { r: 255, g: 255, b: 255 };
const processedMask = await this.processMaskForEditor(maskData, maskCanvas.width, maskCanvas.height, maskColor);
// Nałóż maskę na canvas
maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
maskCtx.drawImage(processedMask, 0, 0);
}
@@ -707,18 +683,14 @@ export class Canvas {
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext('2d');
// Wyczyść canvas
tempCtx.clearRect(0, 0, targetWidth, targetHeight);
// Skaluj maskę do originalSize zamiast targetSize
// originalSize to prawdziwy rozmiar obrazu w mask editorze
const scaleToOriginal = Math.min(originalWidth / this.width, originalHeight / this.height);
// Maska powinna pokryć cały obszar originalSize
const scaledWidth = this.width * scaleToOriginal;
const scaledHeight = this.height * scaleToOriginal;
// Wyśrodkuj na target canvas (który reprezentuje viewport mask editora)
const offsetX = (targetWidth - scaledWidth) / 2;
const offsetY = (targetHeight - scaledHeight) / 2;
@@ -733,22 +705,18 @@ export class Canvas {
offset: { x: offsetX, y: offsetY }
});
// Pobierz dane obrazu i przetwórz je
const imageData = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
const data = imageData.data;
// Konwertuj maskę do formatu editora (alpha channel jako maska)
for (let i = 0; i < data.length; i += 4) {
const alpha = data[i + 3]; // Oryginalny kanał alpha
// Ustaw kolor maski
data[i] = maskColor.r; // R
data[i + 1] = maskColor.g; // G
data[i + 2] = maskColor.b; // B
data[i + 3] = alpha; // Zachowaj oryginalny alpha
}
// Zapisz przetworzone dane z powrotem
tempCtx.putImageData(imageData, 0, 0);
log.info("Mask processing completed - full size scaling applied");
@@ -833,20 +801,18 @@ export class Canvas {
const maskCtx = this.maskTool.maskCtx;
const destX = -this.maskTool.x;
const destY = -this.maskTool.y;
// Zamiast dodawać maskę (screen), zastąp całą maskę (source-over)
// Najpierw wyczyść obszar który będzie zastąpiony
maskCtx.globalCompositeOperation = 'source-over';
maskCtx.clearRect(destX, destY, this.width, this.height);
// Teraz narysuj nową maskę
maskCtx.drawImage(maskAsImage, destX, destY);
this.render();
this.saveState();
const new_preview = new Image();
// Użyj nowej metody z maską jako kanałem alpha
const blob = await this.canvasLayers.getFlattenedCanvasWithMaskAsBlob();
if (blob) {
new_preview.src = URL.createObjectURL(blob);
@@ -859,9 +825,8 @@ export class Canvas {
this.render();
}
// ==========================================
// OBSŁUGA ANULOWANIA MASK EDITORA
// ==========================================
/**
* Zapisuje obecny stan maski przed otwarciem editora
@@ -872,7 +837,6 @@ export class Canvas {
return null;
}
// Skopiuj dane z mask canvas
const maskCanvas = this.maskTool.maskCanvas;
const savedCanvas = document.createElement('canvas');
savedCanvas.width = maskCanvas.width;
@@ -898,14 +862,12 @@ export class Canvas {
return;
}
// Przywróć dane maski
if (savedState.maskData) {
const maskCtx = this.maskTool.maskCtx;
maskCtx.clearRect(0, 0, this.maskTool.maskCanvas.width, this.maskTool.maskCanvas.height);
maskCtx.drawImage(savedState.maskData, 0, 0);
}
// Przywróć pozycję maski
if (savedState.maskPosition) {
this.maskTool.x = savedState.maskPosition.x;
this.maskTool.y = savedState.maskPosition.y;
@@ -930,25 +892,20 @@ export class Canvas {
*/
async handleMaskEditorClose() {
console.log("Node object after mask editor close:", this.node);
// Sprawdź czy editor został anulowany
if (this.maskEditorCancelled) {
log.info("Mask editor was cancelled - restoring original mask state");
// Przywróć oryginalny stan maski
if (this.savedMaskState) {
await this.restoreMaskState(this.savedMaskState);
}
// Wyczyść flagi
this.maskEditorCancelled = false;
this.savedMaskState = null;
// Nie przetwarzaj wyniku z editora
return;
}
// Kontynuuj normalną obsługę save
if (!this.node.imgs || !this.node.imgs.length === 0 || !this.node.imgs[0].src) {
log.warn("Mask editor was closed without a result.");
return;
@@ -995,20 +952,18 @@ export class Canvas {
const maskCtx = this.maskTool.maskCtx;
const destX = -this.maskTool.x;
const destY = -this.maskTool.y;
// Zamiast dodawać maskę (screen), zastąp całą maskę (source-over)
// Najpierw wyczyść obszar który będzie zastąpiony
maskCtx.globalCompositeOperation = 'source-over';
maskCtx.clearRect(destX, destY, this.width, this.height);
// Teraz narysuj nową maskę
maskCtx.drawImage(maskAsImage, destX, destY);
this.render();
this.saveState();
const new_preview = new Image();
// Użyj nowej metody z maską jako kanałem alpha
const blob = await this.canvasLayers.getFlattenedCanvasWithMaskAsBlob();
if (blob) {
new_preview.src = URL.createObjectURL(blob);
@@ -1019,8 +974,7 @@ export class Canvas {
}
this.render();
// Wyczyść zapisany stan po pomyślnym save
this.savedMaskState = null;
}
}

View File

@@ -194,8 +194,7 @@ export class CanvasInteractions {
this.resetInteractionState();
this.canvas.render();
}
// Clear internal clipboard when mouse leaves canvas
if (this.canvas.canvasLayers.internalClipboard.length > 0) {
this.canvas.canvasLayers.internalClipboard = [];
log.info("Internal clipboard cleared - mouse left canvas");

View File

@@ -51,7 +51,6 @@ export class CanvasLayers {
this.canvas.saveState();
const newLayers = [];
// Calculate the center of the copied layers
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
this.internalClipboard.forEach(layer => {
minX = Math.min(minX, layer.x);
@@ -63,7 +62,6 @@ export class CanvasLayers {
const centerX = (minX + maxX) / 2;
const centerY = (minY + maxY) / 2;
// Calculate offset to position at mouse cursor
const mouseX = this.canvas.lastMousePosition.x;
const mouseY = this.canvas.lastMousePosition.y;
const offsetX = mouseX - centerX;
@@ -89,14 +87,12 @@ export class CanvasLayers {
try {
log.info(`Paste operation started with preference: ${this.clipboardPreference}`);
// ALWAYS FIRST: Check Internal Clipboard
if (this.internalClipboard.length > 0) {
log.info("Pasting from internal clipboard");
this.pasteLayers();
return;
}
// SECOND: Use preference setting
if (this.clipboardPreference === 'clipspace') {
log.info("Attempting paste from ComfyUI Clipspace");
if (!await this.tryClipspacePaste(addMode)) {
@@ -116,8 +112,7 @@ export class CanvasLayers {
try {
log.info("Attempting to paste from ComfyUI Clipspace");
const clipspaceResult = ComfyApp.pasteFromClipspace(this.canvas.node);
// Check if clipspace operation was successful and node has images
if (this.canvas.node.imgs && this.canvas.node.imgs.length > 0) {
const clipspaceImage = this.canvas.node.imgs[0];
if (clipspaceImage && clipspaceImage.src) {
@@ -672,7 +667,6 @@ export class CanvasLayers {
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 => {
@@ -696,14 +690,12 @@ export class CanvasLayers {
tempCtx.restore();
});
// Pobierz dane obrazu
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
const data = imageData.data;
// Pobierz maskę z maskTool (używając tej samej logiki co w CanvasIO)
const toolMaskCanvas = this.canvas.maskTool.getMask();
if (toolMaskCanvas) {
// Stwórz tymczasowy canvas dla maski w rozmiarze output area
const tempMaskCanvas = document.createElement('canvas');
tempMaskCanvas.width = this.canvas.width;
tempMaskCanvas.height = this.canvas.height;
@@ -711,7 +703,6 @@ export class CanvasLayers {
tempMaskCtx.clearRect(0, 0, tempMaskCanvas.width, tempMaskCanvas.height);
// Użyj tej samej logiki co w CanvasIO
const maskX = this.canvas.maskTool.x;
const maskY = this.canvas.maskTool.y;
@@ -737,7 +728,6 @@ export class CanvasLayers {
);
}
// Konwertuj maskę do formatu alpha (tak jak w CanvasIO)
const tempMaskData = tempMaskCtx.getImageData(0, 0, this.canvas.width, this.canvas.height);
for (let i = 0; i < tempMaskData.data.length; i += 4) {
const alpha = tempMaskData.data[i + 3];
@@ -746,21 +736,18 @@ export class CanvasLayers {
}
tempMaskCtx.putImageData(tempMaskData, 0, 0);
// Zastosuj maskę do obrazu
const maskImageData = tempMaskCtx.getImageData(0, 0, this.canvas.width, this.canvas.height);
const maskData = maskImageData.data;
for (let i = 0; i < data.length; i += 4) {
const originalAlpha = data[i + 3];
const maskAlpha = maskData[i + 3] / 255; // Użyj kanału alpha maski
// ODWRÓCONA LOGIKA: Tam gdzie jest maska (alpha = 1) = przezroczysty
// Tam gdzie nie ma maski (alpha = 0) = widoczny
const invertedMaskAlpha = 1 - maskAlpha;
data[i + 3] = originalAlpha * invertedMaskAlpha;
}
// Zapisz zmodyfikowane dane obrazu
tempCtx.putImageData(imageData, 0, 0);
}
@@ -781,7 +768,6 @@ export class CanvasLayers {
tempCanvas.height = this.canvas.height;
const tempCtx = tempCanvas.getContext('2d');
// Renderuj wszystkie warstwy (pełny obraz)
const sortedLayers = [...this.canvas.layers].sort((a, b) => a.zIndex - b.zIndex);
sortedLayers.forEach(layer => {
@@ -805,14 +791,12 @@ export class CanvasLayers {
tempCtx.restore();
});
// Pobierz dane obrazu
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
const data = imageData.data;
// Pobierz maskę z maskTool i zastosuj ją jako kanał alpha
const toolMaskCanvas = this.canvas.maskTool.getMask();
if (toolMaskCanvas) {
// Stwórz tymczasowy canvas dla maski w rozmiarze output area
const tempMaskCanvas = document.createElement('canvas');
tempMaskCanvas.width = this.canvas.width;
tempMaskCanvas.height = this.canvas.height;
@@ -820,7 +804,6 @@ export class CanvasLayers {
tempMaskCtx.clearRect(0, 0, tempMaskCanvas.width, tempMaskCanvas.height);
// Użyj tej samej logiki co w CanvasIO
const maskX = this.canvas.maskTool.x;
const maskY = this.canvas.maskTool.y;
@@ -846,7 +829,6 @@ export class CanvasLayers {
);
}
// Konwertuj maskę do formatu alpha
const tempMaskData = tempMaskCtx.getImageData(0, 0, this.canvas.width, this.canvas.height);
for (let i = 0; i < tempMaskData.data.length; i += 4) {
const alpha = tempMaskData.data[i + 3];
@@ -855,21 +837,18 @@ export class CanvasLayers {
}
tempMaskCtx.putImageData(tempMaskData, 0, 0);
// Zastosuj maskę do obrazu - NORMALNA LOGIKA dla edytora masek
const maskImageData = tempMaskCtx.getImageData(0, 0, this.canvas.width, this.canvas.height);
const maskData = maskImageData.data;
for (let i = 0; i < data.length; i += 4) {
const originalAlpha = data[i + 3];
const maskAlpha = maskData[i + 3] / 255;
// ODWRÓCONA LOGIKA dla edytora: Tam gdzie jest maska (alpha = 1) = przezroczysty
// Tam gdzie nie ma maski (alpha = 0) = widoczny
const invertedMaskAlpha = 1 - maskAlpha;
data[i + 3] = originalAlpha * invertedMaskAlpha;
}
// Zapisz zmodyfikowane dane obrazu
tempCtx.putImageData(imageData, 0, 0);
}

View File

@@ -485,8 +485,7 @@ async function createCanvasWidget(node, widget, app) {
} else {
helpTooltip.innerHTML = standardShortcuts;
}
// Najpierw wyświetlamy tooltip z visibility: hidden aby obliczyć jego wymiary
helpTooltip.style.visibility = 'hidden';
helpTooltip.style.display = 'block';
@@ -494,29 +493,23 @@ async function createCanvasWidget(node, widget, app) {
const tooltipRect = helpTooltip.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Obliczamy pozycję
let left = buttonRect.left;
let top = buttonRect.bottom + 5;
// Sprawdzamy czy tooltip wychodzi poza prawy brzeg ekranu
if (left + tooltipRect.width > viewportWidth) {
left = viewportWidth - tooltipRect.width - 10;
}
// Sprawdzamy czy tooltip wychodzi poza dolny brzeg ekranu
if (top + tooltipRect.height > viewportHeight) {
// Wyświetlamy nad przyciskiem zamiast pod
top = buttonRect.top - tooltipRect.height - 5;
}
// Upewniamy się, że tooltip nie wychodzi poza lewy brzeg
if (left < 10) left = 10;
// Upewniamy się, że tooltip nie wychodzi poza górny brzeg
if (top < 10) top = 10;
// Ustawiamy finalną pozycję i pokazujemy tooltip
helpTooltip.style.left = `${left}px`;
helpTooltip.style.top = `${top}px`;
helpTooltip.style.visibility = 'visible';
@@ -671,7 +664,7 @@ async function createCanvasWidget(node, widget, app) {
const height = parseInt(document.getElementById('canvas-height').value) || canvas.height;
canvas.updateOutputAreaSize(width, height);
document.body.removeChild(dialog);
// updateOutput is triggered by saveState in updateOutputAreaSize
};
document.getElementById('cancel-size').onclick = () => {
@@ -946,8 +939,7 @@ async function createCanvasWidget(node, widget, app) {
const updateOutput = async () => {
triggerWidget.value = (triggerWidget.value + 1) % 99999999;
// ZAWSZE generuj obraz (potrzebny dla funkcji masek)
try {
const new_preview = new Image();
const blob = await canvas.getFlattenedCanvasWithMaskAsBlob();
@@ -961,8 +953,7 @@ async function createCanvasWidget(node, widget, app) {
} catch (error) {
console.error("Error updating node preview:", error);
}
// app.graph.runStep(); // Potentially not needed if we just want to mark dirty
};
const canvasContainer = $el("div.painterCanvasContainer.painter-container", {
@@ -1111,8 +1102,6 @@ async function createCanvasWidget(node, widget, app) {
canvas.loadInitialState();
}, 100);
// Konfiguracja opcji ukrywania podglądu
const showPreviewWidget = node.widgets.find(w => w.name === "show_preview");
if (showPreviewWidget) {
const originalCallback = showPreviewWidget.callback;
@@ -1121,20 +1110,17 @@ async function createCanvasWidget(node, widget, app) {
if (originalCallback) {
originalCallback.call(this, value);
}
// Kontroluj widoczność podglądu w Canvas
if (canvas && canvas.setPreviewVisibility) {
canvas.setPreviewVisibility(value);
}
// Wymuś przerysowanie node'a
if (node.graph && node.graph.canvas) {
node.setDirtyCanvas(true, true);
}
};
// Podgląd jest automatycznie ukrywany w konstruktorze Canvas.js
// Nie potrzebujemy już wywoływać setInitialPreviewVisibility
}
@@ -1213,7 +1199,6 @@ app.registerExtension({
return;
}
// Iterate through every widget attached to this node
this.widgets.forEach(w => {
log.debug(`Widget name: ${w.name}, type: ${w.type}, value: ${w.value}`);
});
@@ -1265,8 +1250,7 @@ app.registerExtension({
originalGetExtraMenuOptions?.apply(this, arguments);
const self = this;
// Usuń standardową opcję "Open in MaskEditor" jeśli istnieje
const maskEditorIndex = options.findIndex(option =>
option && option.content === "Open in MaskEditor"
);

View File

@@ -162,7 +162,7 @@ export class MaskTool {
if (this.brushHardness === 1) {
this.maskCtx.strokeStyle = `rgba(255, 255, 255, ${this.brushStrength})`;
} else {
// hardness: 1 = hard edge, 0 = soft edge
const innerRadius = gradientRadius * this.brushHardness;
const gradient = this.maskCtx.createRadialGradient(
canvasX, canvasY, innerRadius,
@@ -281,18 +281,16 @@ export class MaskTool {
}
setMask(image) {
// `this.x` i `this.y` przechowują pozycję lewego górnego rogu płótna maski
// względem lewego górnego rogu widoku. Zatem (-this.x, -this.y) to pozycja
// lewego górnego rogu widoku na płótnie maski.
const destX = -this.x;
const destY = -this.y;
// Wyczyść tylko ten obszar na dużym płótnie maski, który odpowiada
// widocznemu obszarowi wyjściowemu.
this.maskCtx.clearRect(destX, destY, this.canvasInstance.width, this.canvasInstance.height);
// Narysuj nowy obraz maski (który ma rozmiar obszaru wyjściowego)
// dokładnie w tym wyczyszczonym miejscu.
this.maskCtx.drawImage(image, destX, destY);
if (this.onStateChange) {

View File

@@ -15,11 +15,10 @@ import {
* Przykład 1: Podstawowe użycie z obrazem maski
*/
async function example1_basic_usage(canvasInstance) {
// Załaduj obraz maski z URL
const maskImage = await create_mask_from_image_src('/path/to/mask.png');
// Uruchom mask editor z predefiniowaną maską
// sendCleanImage = true oznacza że wyślemy czysty obraz bez istniejącej maski
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, true);
}
@@ -27,24 +26,21 @@ async function example1_basic_usage(canvasInstance) {
* Przykład 2: Użycie z canvas jako maska
*/
async function example2_canvas_mask(canvasInstance) {
// Stwórz canvas z maską programowo
const maskCanvas = document.createElement('canvas');
maskCanvas.width = 512;
maskCanvas.height = 512;
const ctx = maskCanvas.getContext('2d');
// Narysuj prostą maskę - białe koło na czarnym tle
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, 512, 512);
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(256, 256, 100, 0, 2 * Math.PI);
ctx.fill();
// Konwertuj canvas do Image
const maskImage = await canvas_to_mask_image(maskCanvas);
// Uruchom mask editor
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, true);
}
@@ -52,12 +48,11 @@ async function example2_canvas_mask(canvasInstance) {
* Przykład 3: Bezpośrednie użycie metody Canvas
*/
async function example3_direct_canvas_method(canvasInstance) {
// Załaduj maskę
const maskImage = await create_mask_from_image_src('/path/to/mask.png');
// Bezpośrednie wywołanie metody Canvas
// Parametr 1: predefiniowana maska
// Parametr 2: czy wysłać czysty obraz (true = tak, false = z istniejącą maską)
await canvasInstance.startMaskEditor(maskImage, true);
}
@@ -65,17 +60,14 @@ async function example3_direct_canvas_method(canvasInstance) {
* Przykład 4: Tworzenie maski z danych binarnych
*/
async function example4_binary_data_mask(canvasInstance, binaryData) {
// Konwertuj dane binarne do data URL
const blob = new Blob([binaryData], { type: 'image/png' });
const dataUrl = URL.createObjectURL(blob);
// Stwórz obraz z data URL
const maskImage = await create_mask_from_image_src(dataUrl);
// Uruchom mask editor
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, true);
// Wyczyść URL po użyciu
URL.revokeObjectURL(dataUrl);
}
@@ -87,8 +79,7 @@ async function example5_gradient_mask(canvasInstance) {
maskCanvas.width = 512;
maskCanvas.height = 512;
const ctx = maskCanvas.getContext('2d');
// Stwórz gradient od przezroczystego do białego
const gradient = ctx.createLinearGradient(0, 0, 512, 0);
gradient.addColorStop(0, 'rgba(0, 0, 0, 0)'); // Przezroczysty
gradient.addColorStop(1, 'rgba(255, 255, 255, 1)'); // Biały
@@ -109,8 +100,7 @@ async function example6_error_handling(canvasInstance) {
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, true);
} catch (error) {
console.error('Błąd podczas ładowania maski:', error);
// Fallback - uruchom editor bez predefiniowanej maski
await canvasInstance.startMaskEditor();
}
}
@@ -134,13 +124,11 @@ async function example7_existing_canvas_element(canvasInstance, canvasElementId)
*/
async function example8_combine_with_existing_mask(canvasInstance) {
const maskImage = await create_mask_from_image_src('/path/to/mask.png');
// sendCleanImage = false oznacza że wyślemy obraz z istniejącą maską
// Nowa maska zostanie nałożona dodatkowo w edytorze
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, false);
}
// Eksportuj przykłady dla użycia w innych plikach
export {
example1_basic_usage,
example2_canvas_mask,

View File

@@ -21,14 +21,13 @@ export function hide_mask_editor() {
}
function get_mask_editor_cancel_button(app) {
// Główny przycisk Cancel z nowego editora
const cancelButton = document.getElementById("maskEditor_topBarCancelButton");
if (cancelButton) {
log.debug("Found cancel button by ID: maskEditor_topBarCancelButton");
return cancelButton;
}
// Sprawdź inne możliwe selektory (bez :contains które nie działają)
const cancelSelectors = [
'button[onclick*="cancel"]',
'button[onclick*="Cancel"]',
@@ -46,8 +45,7 @@ function get_mask_editor_cancel_button(app) {
log.warn("Invalid selector:", selector, e);
}
}
// Fallback - sprawdź wszystkie przyciski w dokumencie po tekście
const allButtons = document.querySelectorAll('button, input[type="button"]');
for (const button of allButtons) {
const text = button.textContent || button.value || '';
@@ -56,8 +54,7 @@ function get_mask_editor_cancel_button(app) {
return button;
}
}
// Ostatni fallback - stary editor
const editorElement = get_mask_editor_element(app);
if (editorElement) {
return editorElement?.parentElement?.lastChild?.childNodes[2];
@@ -72,7 +69,7 @@ function get_mask_editor_save_button(app) {
}
export function mask_editor_listen_for_cancel(app, callback) {
// Spróbuj znaleźć przycisk Cancel wielokrotnie z opóźnieniem
let attempts = 0;
const maxAttempts = 50; // 5 sekund
@@ -86,12 +83,11 @@ export function mask_editor_listen_for_cancel(app, callback) {
cancel_button.filter_listener_added = true;
return true; // Znaleziono i podłączono
} else if (attempts < maxAttempts) {
// Spróbuj ponownie za 100ms
setTimeout(findAndAttachListener, 100);
} else {
log.warn("Could not find cancel button after", maxAttempts, "attempts");
// Ostatnia próba - nasłuchuj na wszystkie kliknięcia w dokumencie
const globalClickHandler = (event) => {
const target = event.target;
const text = target.textContent || target.value || '';
@@ -144,10 +140,9 @@ export function start_mask_editor_auto(canvasInstance) {
log.error('Canvas instance is required');
return;
}
// Wywołaj bez parametrów - użyje domyślnych wartości (null, true)
// Co oznacza: brak predefiniowanej maski, ale wyślij czysty obraz
// i automatycznie nałóż istniejącą maskę z canvas
canvasInstance.startMaskEditor();
}