mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-21 20:52:12 -03:00
Add automatic mask loading to mask editor
Introduces functionality for automatically applying predefined masks in the ComfyUI mask editor. Adds new API methods and utilities in Canvas.js and mask_utils.js, including support for sending a clean image, handling both new and old mask editors, and flexible mask formats. Includes documentation, usage examples, and helper functions for mask processing and error handling.
This commit is contained in:
186
Doc/AutoMaskLoading
Normal file
186
Doc/AutoMaskLoading
Normal file
@@ -0,0 +1,186 @@
|
||||
# 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
|
||||
227
MASK_AUTO_LOADING_README.md
Normal file
227
MASK_AUTO_LOADING_README.md
Normal file
@@ -0,0 +1,227 @@
|
||||
# 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
|
||||
299
js/Canvas.js
299
js/Canvas.js
@@ -217,11 +217,32 @@ export class Canvas {
|
||||
|
||||
/**
|
||||
* Uruchamia edytor masek
|
||||
* @param {Image|HTMLCanvasElement|null} predefinedMask - Opcjonalna maska do nałożenia po otwarciu editora
|
||||
* @param {boolean} sendCleanImage - Czy wysłać czysty obraz (bez maski) do editora
|
||||
*/
|
||||
async startMaskEditor() {
|
||||
// Używamy specjalnej metody która łączy pełny obraz z istniejącą maską
|
||||
// Dzięki temu edytor masek dostanie pełny obraz z maską jako punkt startowy
|
||||
const blob = await this.canvasLayers.getFlattenedCanvasForMaskEditor();
|
||||
async startMaskEditor(predefinedMask = null, sendCleanImage = true) {
|
||||
// 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();
|
||||
} catch (error) {
|
||||
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();
|
||||
}
|
||||
|
||||
if (!blob) {
|
||||
log.warn("Canvas is empty, cannot open mask editor.");
|
||||
return;
|
||||
@@ -259,6 +280,11 @@ export class Canvas {
|
||||
|
||||
this.editorWasShowing = false;
|
||||
this.waitWhileMaskEditing();
|
||||
|
||||
// Jeśli mamy predefiniowaną maskę, czekaj na otwarcie editora i nałóż ją
|
||||
if (predefinedMask) {
|
||||
this.waitForMaskEditorAndApplyMask();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
log.error("Error preparing image for mask editor:", error);
|
||||
@@ -385,6 +411,271 @@ export class Canvas {
|
||||
// METODY DLA EDYTORA MASEK
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Czeka na otwarcie mask editora i automatycznie nakłada predefiniowaną maskę
|
||||
*/
|
||||
waitForMaskEditorAndApplyMask() {
|
||||
let attempts = 0;
|
||||
const maxAttempts = 100; // Zwiększone do 10 sekund oczekiwania
|
||||
|
||||
const checkEditor = () => {
|
||||
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) {
|
||||
editorReady = true;
|
||||
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;
|
||||
log.info("New mask editor detected as ready via DOM element");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Sprawdź czy stary editor jest gotowy
|
||||
const maskCanvas = document.getElementById('maskCanvas');
|
||||
editorReady = maskCanvas && maskCanvas.getContext && maskCanvas.width > 0;
|
||||
if (editorReady) {
|
||||
log.info("Old mask editor detected as ready");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
this.pendingMask = null;
|
||||
}, 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");
|
||||
this.pendingMask = null;
|
||||
}
|
||||
};
|
||||
|
||||
checkEditor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Nakłada maskę na otwarty mask editor
|
||||
* @param {Image|HTMLCanvasElement} maskData - Dane maski do nałożenia
|
||||
*/
|
||||
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);
|
||||
log.info("Alternative method succeeded");
|
||||
} catch (fallbackError) {
|
||||
log.error("Alternative method also failed:", fallbackError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nakłada maskę na nowy mask editor (przez MessageBroker)
|
||||
* @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");
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Nakłada maskę na stary mask editor
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Przetwarza maskę do odpowiedniego formatu dla editora
|
||||
* @param {Image|HTMLCanvasElement} maskData - Oryginalne dane maski
|
||||
* @param {number} targetWidth - Docelowa szerokość
|
||||
* @param {number} targetHeight - Docelowa wysokość
|
||||
* @param {Object} maskColor - Kolor maski {r, g, b}
|
||||
* @returns {HTMLCanvasElement} Przetworzona maska
|
||||
*/
|
||||
async processMaskForEditor(maskData, targetWidth, targetHeight, maskColor) {
|
||||
const originalWidth = maskData.width || maskData.naturalWidth || this.width;
|
||||
const originalHeight = maskData.height || maskData.naturalHeight || this.height;
|
||||
|
||||
log.info("Processing mask for editor:", {
|
||||
originalSize: { width: originalWidth, height: originalHeight },
|
||||
targetSize: { width: targetWidth, height: targetHeight },
|
||||
canvasSize: { width: this.width, height: this.height }
|
||||
});
|
||||
|
||||
const tempCanvas = document.createElement('canvas');
|
||||
tempCanvas.width = targetWidth;
|
||||
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;
|
||||
|
||||
tempCtx.drawImage(maskData, offsetX, offsetY, scaledWidth, scaledHeight);
|
||||
|
||||
log.info("Mask drawn scaled to original image size:", {
|
||||
originalSize: { width: originalWidth, height: originalHeight },
|
||||
targetSize: { width: targetWidth, height: targetHeight },
|
||||
canvasSize: { width: this.width, height: this.height },
|
||||
scaleToOriginal: scaleToOriginal,
|
||||
finalSize: { width: scaledWidth, height: scaledHeight },
|
||||
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");
|
||||
return tempCanvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tworzy obiekt Image z obecnej maski canvas
|
||||
* @returns {Promise<Image>} Promise zwracający obiekt Image z maską
|
||||
*/
|
||||
async createMaskFromCurrentMask() {
|
||||
if (!this.maskTool || !this.maskTool.maskCanvas) {
|
||||
throw new Error("No mask canvas available");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const maskImage = new Image();
|
||||
maskImage.onload = () => resolve(maskImage);
|
||||
maskImage.onerror = reject;
|
||||
maskImage.src = this.maskTool.maskCanvas.toDataURL();
|
||||
});
|
||||
}
|
||||
|
||||
waitWhileMaskEditing() {
|
||||
if (mask_editor_showing(app)) {
|
||||
this.editorWasShowing = true;
|
||||
|
||||
153
js/examples/mask_editor_examples.js
Normal file
153
js/examples/mask_editor_examples.js
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Przykłady użycia automatycznego nakładania masek w mask editorze
|
||||
*
|
||||
* Te przykłady pokazują jak używać nowej funkcjonalności do automatycznego
|
||||
* nakładania predefiniowanych masek po otwarciu mask editora ComfyUI.
|
||||
*/
|
||||
|
||||
import {
|
||||
start_mask_editor_with_predefined_mask,
|
||||
create_mask_from_image_src,
|
||||
canvas_to_mask_image
|
||||
} from '../utils/mask_utils.js';
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Przykład 5: Maska z gradientem
|
||||
*/
|
||||
async function example5_gradient_mask(canvasInstance) {
|
||||
const maskCanvas = document.createElement('canvas');
|
||||
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
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(0, 0, 512, 512);
|
||||
|
||||
const maskImage = await canvas_to_mask_image(maskCanvas);
|
||||
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Przykład 6: Obsługa błędów
|
||||
*/
|
||||
async function example6_error_handling(canvasInstance) {
|
||||
try {
|
||||
const maskImage = await create_mask_from_image_src('/path/to/nonexistent.png');
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Przykład 7: Maska z istniejącego elementu canvas na stronie
|
||||
*/
|
||||
async function example7_existing_canvas_element(canvasInstance, canvasElementId) {
|
||||
const existingCanvas = document.getElementById(canvasElementId);
|
||||
if (!existingCanvas) {
|
||||
console.error('Canvas element not found:', canvasElementId);
|
||||
return;
|
||||
}
|
||||
|
||||
const maskImage = await canvas_to_mask_image(existingCanvas);
|
||||
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Przykład 8: Kombinowanie z istniejącą maską
|
||||
*/
|
||||
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,
|
||||
example3_direct_canvas_method,
|
||||
example4_binary_data_mask,
|
||||
example5_gradient_mask,
|
||||
example6_error_handling,
|
||||
example7_existing_canvas_element,
|
||||
example8_combine_with_existing_mask
|
||||
};
|
||||
@@ -41,3 +41,62 @@ export function press_maskeditor_save(app) {
|
||||
export function press_maskeditor_cancel(app) {
|
||||
get_mask_editor_cancel_button(app)?.click()
|
||||
}
|
||||
|
||||
/**
|
||||
* Uruchamia mask editor z predefiniowaną maską
|
||||
* @param {Object} canvasInstance - Instancja Canvas
|
||||
* @param {Image|HTMLCanvasElement} maskImage - Obraz maski do nałożenia
|
||||
* @param {boolean} sendCleanImage - Czy wysłać czysty obraz (bez istniejącej maski)
|
||||
*/
|
||||
export function start_mask_editor_with_predefined_mask(canvasInstance, maskImage, sendCleanImage = true) {
|
||||
if (!canvasInstance || !maskImage) {
|
||||
console.error('Canvas instance and mask image are required');
|
||||
return;
|
||||
}
|
||||
|
||||
canvasInstance.startMaskEditor(maskImage, sendCleanImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uruchamia mask editor z automatycznym zachowaniem (czysty obraz + istniejąca maska)
|
||||
* @param {Object} canvasInstance - Instancja Canvas
|
||||
*/
|
||||
export function start_mask_editor_auto(canvasInstance) {
|
||||
if (!canvasInstance) {
|
||||
console.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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tworzy maskę z obrazu dla użycia w mask editorze
|
||||
* @param {string} imageSrc - Źródło obrazu (URL lub data URL)
|
||||
* @returns {Promise<Image>} Promise zwracający obiekt Image
|
||||
*/
|
||||
export function create_mask_from_image_src(imageSrc) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = reject;
|
||||
img.src = imageSrc;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Konwertuje canvas do Image dla użycia jako maska
|
||||
* @param {HTMLCanvasElement} canvas - Canvas do konwersji
|
||||
* @returns {Promise<Image>} Promise zwracający obiekt Image
|
||||
*/
|
||||
export function canvas_to_mask_image(canvas) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = reject;
|
||||
img.src = canvas.toDataURL();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user