mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-25 14:25:44 -03:00
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:
@@ -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
|
|
||||||
@@ -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
|
|
||||||
180
js/Canvas.js
180
js/Canvas.js
@@ -50,19 +50,15 @@ export class Canvas {
|
|||||||
this.dataInitialized = false;
|
this.dataInitialized = false;
|
||||||
this.pendingDataCheck = null;
|
this.pendingDataCheck = null;
|
||||||
this.imageCache = new Map();
|
this.imageCache = new Map();
|
||||||
|
|
||||||
// Inicjalizacja modułów
|
|
||||||
this._initializeModules(callbacks);
|
this._initializeModules(callbacks);
|
||||||
|
|
||||||
// Podstawowa konfiguracja
|
|
||||||
this._setupCanvas();
|
this._setupCanvas();
|
||||||
|
|
||||||
// Delegacja interaction dla kompatybilności wstecznej
|
|
||||||
this.interaction = this.canvasInteractions.interaction;
|
this.interaction = this.canvasInteractions.interaction;
|
||||||
|
|
||||||
console.log('Canvas widget element:', this.node);
|
console.log('Canvas widget element:', this.node);
|
||||||
|
|
||||||
// Dodaj metodę do kontroli widoczności podglądu
|
|
||||||
this.previewVisible = true; // Domyślnie widoczny
|
this.previewVisible = true; // Domyślnie widoczny
|
||||||
this.setPreviewVisibility(false);
|
this.setPreviewVisibility(false);
|
||||||
}
|
}
|
||||||
@@ -95,16 +91,14 @@ export class Canvas {
|
|||||||
async setPreviewVisibility(visible) {
|
async setPreviewVisibility(visible) {
|
||||||
this.previewVisible = visible;
|
this.previewVisible = visible;
|
||||||
console.log("Canvas preview visibility set to:", visible);
|
console.log("Canvas preview visibility set to:", visible);
|
||||||
|
|
||||||
// Znajdź i kontroluj ImagePreviewWidget
|
|
||||||
const imagePreviewWidget = await this.waitForWidget("$$canvas-image-preview", this.node);
|
const imagePreviewWidget = await this.waitForWidget("$$canvas-image-preview", this.node);
|
||||||
if (imagePreviewWidget) {
|
if (imagePreviewWidget) {
|
||||||
console.log("Found $$canvas-image-preview widget, controlling visibility");
|
console.log("Found $$canvas-image-preview widget, controlling visibility");
|
||||||
|
|
||||||
if (visible) {
|
if (visible) {
|
||||||
console.log("=== SHOWING WIDGET ===");
|
console.log("=== SHOWING WIDGET ===");
|
||||||
|
|
||||||
// Pokaż widget
|
|
||||||
if (imagePreviewWidget.options) {
|
if (imagePreviewWidget.options) {
|
||||||
imagePreviewWidget.options.hidden = false;
|
imagePreviewWidget.options.hidden = false;
|
||||||
}
|
}
|
||||||
@@ -123,8 +117,7 @@ export class Canvas {
|
|||||||
console.log("ImagePreviewWidget shown");
|
console.log("ImagePreviewWidget shown");
|
||||||
} else {
|
} else {
|
||||||
console.log("=== HIDING WIDGET ===");
|
console.log("=== HIDING WIDGET ===");
|
||||||
|
|
||||||
// Ukryj widget
|
|
||||||
if (imagePreviewWidget.options) {
|
if (imagePreviewWidget.options) {
|
||||||
imagePreviewWidget.options.hidden = true;
|
imagePreviewWidget.options.hidden = true;
|
||||||
}
|
}
|
||||||
@@ -154,7 +147,7 @@ export class Canvas {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_initializeModules(callbacks) {
|
_initializeModules(callbacks) {
|
||||||
// Moduły są publiczne dla bezpośredniego dostępu gdy potrzebne
|
|
||||||
this.maskTool = new MaskTool(this, {onStateChange: this.onStateChange});
|
this.maskTool = new MaskTool(this, {onStateChange: this.onStateChange});
|
||||||
this.canvasState = new CanvasState(this);
|
this.canvasState = new CanvasState(this);
|
||||||
this.canvasInteractions = new CanvasInteractions(this);
|
this.canvasInteractions = new CanvasInteractions(this);
|
||||||
@@ -172,17 +165,15 @@ export class Canvas {
|
|||||||
this.initCanvas();
|
this.initCanvas();
|
||||||
this.canvasInteractions.setupEventListeners();
|
this.canvasInteractions.setupEventListeners();
|
||||||
this.canvasIO.initNodeData();
|
this.canvasIO.initNodeData();
|
||||||
|
|
||||||
// Inicjalizacja warstw z domyślną przezroczystością
|
|
||||||
this.layers = this.layers.map(layer => ({
|
this.layers = this.layers.map(layer => ({
|
||||||
...layer,
|
...layer,
|
||||||
opacity: 1
|
opacity: 1
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// GŁÓWNE OPERACJE FASADY
|
|
||||||
// ==========================================
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ładuje stan canvas z bazy danych
|
* Ładuje stan canvas z bazy danych
|
||||||
@@ -300,9 +291,8 @@ export class Canvas {
|
|||||||
return this.canvasIO.importLatestImage();
|
return this.canvasIO.importLatestImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// OPERACJE NA MASCE
|
|
||||||
// ==========================================
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uruchamia edytor masek
|
* Uruchamia edytor masek
|
||||||
@@ -310,11 +300,10 @@ export class Canvas {
|
|||||||
* @param {boolean} sendCleanImage - Czy wysłać czysty obraz (bez maski) do editora
|
* @param {boolean} sendCleanImage - Czy wysłać czysty obraz (bez maski) do editora
|
||||||
*/
|
*/
|
||||||
async startMaskEditor(predefinedMask = null, sendCleanImage = true) {
|
async startMaskEditor(predefinedMask = null, sendCleanImage = true) {
|
||||||
// Zapisz obecny stan maski przed otwarciem editora (dla obsługi Cancel)
|
|
||||||
this.savedMaskState = await this.saveMaskState();
|
this.savedMaskState = await this.saveMaskState();
|
||||||
this.maskEditorCancelled = false;
|
this.maskEditorCancelled = false;
|
||||||
|
|
||||||
// Jeśli nie ma predefiniowanej maski, stwórz ją z istniejącej maski canvas
|
|
||||||
if (!predefinedMask && this.maskTool && this.maskTool.maskCanvas) {
|
if (!predefinedMask && this.maskTool && this.maskTool.maskCanvas) {
|
||||||
try {
|
try {
|
||||||
predefinedMask = await this.createMaskFromCurrentMask();
|
predefinedMask = await this.createMaskFromCurrentMask();
|
||||||
@@ -322,17 +311,15 @@ export class Canvas {
|
|||||||
log.warn("Could not create mask from current mask:", error);
|
log.warn("Could not create mask from current mask:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Przechowaj maskę do późniejszego użycia
|
|
||||||
this.pendingMask = predefinedMask;
|
this.pendingMask = predefinedMask;
|
||||||
|
|
||||||
// Wybierz odpowiednią metodę w zależności od parametru sendCleanImage
|
|
||||||
let blob;
|
let blob;
|
||||||
if (sendCleanImage) {
|
if (sendCleanImage) {
|
||||||
// Wyślij czysty obraz bez maski (domyślne zachowanie)
|
|
||||||
blob = await this.canvasLayers.getFlattenedCanvasAsBlob();
|
blob = await this.canvasLayers.getFlattenedCanvasAsBlob();
|
||||||
} else {
|
} else {
|
||||||
// Używamy specjalnej metody która łączy pełny obraz z istniejącą maską
|
|
||||||
blob = await this.canvasLayers.getFlattenedCanvasForMaskEditor();
|
blob = await this.canvasLayers.getFlattenedCanvasForMaskEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,11 +360,9 @@ export class Canvas {
|
|||||||
|
|
||||||
this.editorWasShowing = false;
|
this.editorWasShowing = false;
|
||||||
this.waitWhileMaskEditing();
|
this.waitWhileMaskEditing();
|
||||||
|
|
||||||
// Nasłuchuj na przycisk Cancel
|
|
||||||
this.setupCancelListener();
|
this.setupCancelListener();
|
||||||
|
|
||||||
// Jeśli mamy predefiniowaną maskę, czekaj na otwarcie editora i nałóż ją
|
|
||||||
if (predefinedMask) {
|
if (predefinedMask) {
|
||||||
this.waitForMaskEditorAndApplyMask();
|
this.waitForMaskEditorAndApplyMask();
|
||||||
}
|
}
|
||||||
@@ -388,9 +373,8 @@ export class Canvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// METODY POMOCNICZE
|
|
||||||
// ==========================================
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inicjalizuje podstawowe właściwości canvas
|
* 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ę
|
* Czeka na otwarcie mask editora i automatycznie nakłada predefiniowaną maskę
|
||||||
@@ -518,15 +501,15 @@ export class Canvas {
|
|||||||
attempts++;
|
attempts++;
|
||||||
|
|
||||||
if (mask_editor_showing(app)) {
|
if (mask_editor_showing(app)) {
|
||||||
// Editor się otworzył - sprawdź czy jest w pełni zainicjalizowany
|
|
||||||
const useNewEditor = app.ui.settings.getSettingValue('Comfy.MaskEditor.UseNewEditor');
|
const useNewEditor = app.ui.settings.getSettingValue('Comfy.MaskEditor.UseNewEditor');
|
||||||
let editorReady = false;
|
let editorReady = false;
|
||||||
|
|
||||||
if (useNewEditor) {
|
if (useNewEditor) {
|
||||||
// Sprawdź czy nowy editor jest gotowy - różne metody wykrywania
|
|
||||||
const MaskEditorDialog = window.MaskEditorDialog;
|
const MaskEditorDialog = window.MaskEditorDialog;
|
||||||
if (MaskEditorDialog && MaskEditorDialog.instance) {
|
if (MaskEditorDialog && MaskEditorDialog.instance) {
|
||||||
// Sprawdź czy ma MessageBroker i czy canvas jest dostępny
|
|
||||||
try {
|
try {
|
||||||
const messageBroker = MaskEditorDialog.instance.getMessageBroker();
|
const messageBroker = MaskEditorDialog.instance.getMessageBroker();
|
||||||
if (messageBroker) {
|
if (messageBroker) {
|
||||||
@@ -534,16 +517,15 @@ export class Canvas {
|
|||||||
log.info("New mask editor detected as ready via MessageBroker");
|
log.info("New mask editor detected as ready via MessageBroker");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// MessageBroker jeszcze nie gotowy
|
|
||||||
editorReady = false;
|
editorReady = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alternatywne wykrywanie - sprawdź czy istnieje element maskEditor
|
|
||||||
if (!editorReady) {
|
if (!editorReady) {
|
||||||
const maskEditorElement = document.getElementById('maskEditor');
|
const maskEditorElement = document.getElementById('maskEditor');
|
||||||
if (maskEditorElement && maskEditorElement.style.display !== 'none') {
|
if (maskEditorElement && maskEditorElement.style.display !== 'none') {
|
||||||
// Sprawdź czy ma canvas wewnątrz
|
|
||||||
const canvas = maskEditorElement.querySelector('canvas');
|
const canvas = maskEditorElement.querySelector('canvas');
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
editorReady = true;
|
editorReady = true;
|
||||||
@@ -552,7 +534,7 @@ export class Canvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Sprawdź czy stary editor jest gotowy
|
|
||||||
const maskCanvas = document.getElementById('maskCanvas');
|
const maskCanvas = document.getElementById('maskCanvas');
|
||||||
editorReady = maskCanvas && maskCanvas.getContext && maskCanvas.width > 0;
|
editorReady = maskCanvas && maskCanvas.getContext && maskCanvas.width > 0;
|
||||||
if (editorReady) {
|
if (editorReady) {
|
||||||
@@ -561,21 +543,21 @@ export class Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (editorReady) {
|
if (editorReady) {
|
||||||
// Editor jest gotowy - nałóż maskę po krótkim opóźnieniu
|
|
||||||
log.info("Applying mask to editor after", attempts * 100, "ms wait");
|
log.info("Applying mask to editor after", attempts * 100, "ms wait");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.applyMaskToEditor(this.pendingMask);
|
this.applyMaskToEditor(this.pendingMask);
|
||||||
this.pendingMask = null; // Wyczyść po użyciu
|
this.pendingMask = null; // Wyczyść po użyciu
|
||||||
}, 300); // Krótsze opóźnienie gdy już wiemy że jest gotowy
|
}, 300); // Krótsze opóźnienie gdy już wiemy że jest gotowy
|
||||||
} else if (attempts < maxAttempts) {
|
} else if (attempts < maxAttempts) {
|
||||||
// Editor widoczny ale nie gotowy - sprawdź ponownie
|
|
||||||
if (attempts % 10 === 0) {
|
if (attempts % 10 === 0) {
|
||||||
log.info("Waiting for mask editor to be ready... attempt", attempts, "/", maxAttempts);
|
log.info("Waiting for mask editor to be ready... attempt", attempts, "/", maxAttempts);
|
||||||
}
|
}
|
||||||
setTimeout(checkEditor, 100);
|
setTimeout(checkEditor, 100);
|
||||||
} else {
|
} else {
|
||||||
log.warn("Mask editor timeout - editor not ready after", maxAttempts * 100, "ms");
|
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...");
|
log.info("Attempting to apply mask anyway...");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.applyMaskToEditor(this.pendingMask);
|
this.applyMaskToEditor(this.pendingMask);
|
||||||
@@ -583,7 +565,7 @@ export class Canvas {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
} else if (attempts < maxAttempts) {
|
} else if (attempts < maxAttempts) {
|
||||||
// Editor jeszcze nie widoczny - sprawdź ponownie
|
|
||||||
setTimeout(checkEditor, 100);
|
setTimeout(checkEditor, 100);
|
||||||
} else {
|
} else {
|
||||||
log.warn("Mask editor timeout - editor not showing after", maxAttempts * 100, "ms");
|
log.warn("Mask editor timeout - editor not showing after", maxAttempts * 100, "ms");
|
||||||
@@ -600,28 +582,28 @@ export class Canvas {
|
|||||||
*/
|
*/
|
||||||
async applyMaskToEditor(maskData) {
|
async applyMaskToEditor(maskData) {
|
||||||
try {
|
try {
|
||||||
// Sprawdź czy używamy nowego czy starego editora
|
|
||||||
const useNewEditor = app.ui.settings.getSettingValue('Comfy.MaskEditor.UseNewEditor');
|
const useNewEditor = app.ui.settings.getSettingValue('Comfy.MaskEditor.UseNewEditor');
|
||||||
|
|
||||||
if (useNewEditor) {
|
if (useNewEditor) {
|
||||||
// Sprawdź czy nowy editor jest rzeczywiście dostępny
|
|
||||||
const MaskEditorDialog = window.MaskEditorDialog;
|
const MaskEditorDialog = window.MaskEditorDialog;
|
||||||
if (MaskEditorDialog && MaskEditorDialog.instance) {
|
if (MaskEditorDialog && MaskEditorDialog.instance) {
|
||||||
// Nowy editor - użyj MessageBroker
|
|
||||||
await this.applyMaskToNewEditor(maskData);
|
await this.applyMaskToNewEditor(maskData);
|
||||||
} else {
|
} else {
|
||||||
log.warn("New editor setting enabled but instance not found, trying old editor");
|
log.warn("New editor setting enabled but instance not found, trying old editor");
|
||||||
await this.applyMaskToOldEditor(maskData);
|
await this.applyMaskToOldEditor(maskData);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Stary editor - bezpośredni dostęp do canvas
|
|
||||||
await this.applyMaskToOldEditor(maskData);
|
await this.applyMaskToOldEditor(maskData);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Predefined mask applied to mask editor successfully");
|
log.info("Predefined mask applied to mask editor successfully");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error("Failed to apply predefined mask to editor:", error);
|
log.error("Failed to apply predefined mask to editor:", error);
|
||||||
// Spróbuj alternatywną metodę
|
|
||||||
try {
|
try {
|
||||||
log.info("Trying alternative mask application method...");
|
log.info("Trying alternative mask application method...");
|
||||||
await this.applyMaskToOldEditor(maskData);
|
await this.applyMaskToOldEditor(maskData);
|
||||||
@@ -637,7 +619,7 @@ export class Canvas {
|
|||||||
* @param {Image|HTMLCanvasElement} maskData - Dane maski
|
* @param {Image|HTMLCanvasElement} maskData - Dane maski
|
||||||
*/
|
*/
|
||||||
async applyMaskToNewEditor(maskData) {
|
async applyMaskToNewEditor(maskData) {
|
||||||
// Pobierz instancję nowego editora
|
|
||||||
const MaskEditorDialog = window.MaskEditorDialog;
|
const MaskEditorDialog = window.MaskEditorDialog;
|
||||||
if (!MaskEditorDialog || !MaskEditorDialog.instance) {
|
if (!MaskEditorDialog || !MaskEditorDialog.instance) {
|
||||||
throw new Error("New mask editor instance not found");
|
throw new Error("New mask editor instance not found");
|
||||||
@@ -645,20 +627,16 @@ export class Canvas {
|
|||||||
|
|
||||||
const editor = MaskEditorDialog.instance;
|
const editor = MaskEditorDialog.instance;
|
||||||
const messageBroker = editor.getMessageBroker();
|
const messageBroker = editor.getMessageBroker();
|
||||||
|
|
||||||
// Pobierz canvas maski z editora
|
|
||||||
const maskCanvas = await messageBroker.pull('maskCanvas');
|
const maskCanvas = await messageBroker.pull('maskCanvas');
|
||||||
const maskCtx = await messageBroker.pull('maskCtx');
|
const maskCtx = await messageBroker.pull('maskCtx');
|
||||||
const maskColor = await messageBroker.pull('getMaskColor');
|
const maskColor = await messageBroker.pull('getMaskColor');
|
||||||
|
|
||||||
// Konwertuj maskę do odpowiedniego formatu
|
|
||||||
const processedMask = await this.processMaskForEditor(maskData, maskCanvas.width, maskCanvas.height, maskColor);
|
const processedMask = await this.processMaskForEditor(maskData, maskCanvas.width, maskCanvas.height, maskColor);
|
||||||
|
|
||||||
// Nałóż maskę na canvas
|
|
||||||
maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
|
maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
|
||||||
maskCtx.drawImage(processedMask, 0, 0);
|
maskCtx.drawImage(processedMask, 0, 0);
|
||||||
|
|
||||||
// Zapisz stan dla undo/redo
|
|
||||||
messageBroker.publish('saveState');
|
messageBroker.publish('saveState');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -667,19 +645,17 @@ export class Canvas {
|
|||||||
* @param {Image|HTMLCanvasElement} maskData - Dane maski
|
* @param {Image|HTMLCanvasElement} maskData - Dane maski
|
||||||
*/
|
*/
|
||||||
async applyMaskToOldEditor(maskData) {
|
async applyMaskToOldEditor(maskData) {
|
||||||
// Znajdź canvas maski w starym edytorze
|
|
||||||
const maskCanvas = document.getElementById('maskCanvas');
|
const maskCanvas = document.getElementById('maskCanvas');
|
||||||
if (!maskCanvas) {
|
if (!maskCanvas) {
|
||||||
throw new Error("Old mask editor canvas not found");
|
throw new Error("Old mask editor canvas not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const maskCtx = maskCanvas.getContext('2d');
|
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 maskColor = { r: 255, g: 255, b: 255 };
|
||||||
const processedMask = await this.processMaskForEditor(maskData, maskCanvas.width, maskCanvas.height, maskColor);
|
const processedMask = await this.processMaskForEditor(maskData, maskCanvas.width, maskCanvas.height, maskColor);
|
||||||
|
|
||||||
// Nałóż maskę na canvas
|
|
||||||
maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
|
maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
|
||||||
maskCtx.drawImage(processedMask, 0, 0);
|
maskCtx.drawImage(processedMask, 0, 0);
|
||||||
}
|
}
|
||||||
@@ -707,18 +683,14 @@ export class Canvas {
|
|||||||
tempCanvas.height = targetHeight;
|
tempCanvas.height = targetHeight;
|
||||||
const tempCtx = tempCanvas.getContext('2d');
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
|
|
||||||
// Wyczyść canvas
|
|
||||||
tempCtx.clearRect(0, 0, targetWidth, targetHeight);
|
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);
|
const scaleToOriginal = Math.min(originalWidth / this.width, originalHeight / this.height);
|
||||||
|
|
||||||
// Maska powinna pokryć cały obszar originalSize
|
|
||||||
const scaledWidth = this.width * scaleToOriginal;
|
const scaledWidth = this.width * scaleToOriginal;
|
||||||
const scaledHeight = this.height * scaleToOriginal;
|
const scaledHeight = this.height * scaleToOriginal;
|
||||||
|
|
||||||
// Wyśrodkuj na target canvas (który reprezentuje viewport mask editora)
|
|
||||||
const offsetX = (targetWidth - scaledWidth) / 2;
|
const offsetX = (targetWidth - scaledWidth) / 2;
|
||||||
const offsetY = (targetHeight - scaledHeight) / 2;
|
const offsetY = (targetHeight - scaledHeight) / 2;
|
||||||
|
|
||||||
@@ -733,22 +705,18 @@ export class Canvas {
|
|||||||
offset: { x: offsetX, y: offsetY }
|
offset: { x: offsetX, y: offsetY }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pobierz dane obrazu i przetwórz je
|
|
||||||
const imageData = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
|
const imageData = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
|
||||||
const data = imageData.data;
|
const data = imageData.data;
|
||||||
|
|
||||||
// Konwertuj maskę do formatu editora (alpha channel jako maska)
|
|
||||||
for (let i = 0; i < data.length; i += 4) {
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
const alpha = data[i + 3]; // Oryginalny kanał alpha
|
const alpha = data[i + 3]; // Oryginalny kanał alpha
|
||||||
|
|
||||||
// Ustaw kolor maski
|
|
||||||
data[i] = maskColor.r; // R
|
data[i] = maskColor.r; // R
|
||||||
data[i + 1] = maskColor.g; // G
|
data[i + 1] = maskColor.g; // G
|
||||||
data[i + 2] = maskColor.b; // B
|
data[i + 2] = maskColor.b; // B
|
||||||
data[i + 3] = alpha; // Zachowaj oryginalny alpha
|
data[i + 3] = alpha; // Zachowaj oryginalny alpha
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zapisz przetworzone dane z powrotem
|
|
||||||
tempCtx.putImageData(imageData, 0, 0);
|
tempCtx.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
log.info("Mask processing completed - full size scaling applied");
|
log.info("Mask processing completed - full size scaling applied");
|
||||||
@@ -833,20 +801,18 @@ export class Canvas {
|
|||||||
const maskCtx = this.maskTool.maskCtx;
|
const maskCtx = this.maskTool.maskCtx;
|
||||||
const destX = -this.maskTool.x;
|
const destX = -this.maskTool.x;
|
||||||
const destY = -this.maskTool.y;
|
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.globalCompositeOperation = 'source-over';
|
||||||
maskCtx.clearRect(destX, destY, this.width, this.height);
|
maskCtx.clearRect(destX, destY, this.width, this.height);
|
||||||
|
|
||||||
// Teraz narysuj nową maskę
|
|
||||||
maskCtx.drawImage(maskAsImage, destX, destY);
|
maskCtx.drawImage(maskAsImage, destX, destY);
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
this.saveState();
|
this.saveState();
|
||||||
|
|
||||||
const new_preview = new Image();
|
const new_preview = new Image();
|
||||||
// Użyj nowej metody z maską jako kanałem alpha
|
|
||||||
const blob = await this.canvasLayers.getFlattenedCanvasWithMaskAsBlob();
|
const blob = await this.canvasLayers.getFlattenedCanvasWithMaskAsBlob();
|
||||||
if (blob) {
|
if (blob) {
|
||||||
new_preview.src = URL.createObjectURL(blob);
|
new_preview.src = URL.createObjectURL(blob);
|
||||||
@@ -859,9 +825,8 @@ export class Canvas {
|
|||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// OBSŁUGA ANULOWANIA MASK EDITORA
|
|
||||||
// ==========================================
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zapisuje obecny stan maski przed otwarciem editora
|
* Zapisuje obecny stan maski przed otwarciem editora
|
||||||
@@ -872,7 +837,6 @@ export class Canvas {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skopiuj dane z mask canvas
|
|
||||||
const maskCanvas = this.maskTool.maskCanvas;
|
const maskCanvas = this.maskTool.maskCanvas;
|
||||||
const savedCanvas = document.createElement('canvas');
|
const savedCanvas = document.createElement('canvas');
|
||||||
savedCanvas.width = maskCanvas.width;
|
savedCanvas.width = maskCanvas.width;
|
||||||
@@ -898,14 +862,12 @@ export class Canvas {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Przywróć dane maski
|
|
||||||
if (savedState.maskData) {
|
if (savedState.maskData) {
|
||||||
const maskCtx = this.maskTool.maskCtx;
|
const maskCtx = this.maskTool.maskCtx;
|
||||||
maskCtx.clearRect(0, 0, this.maskTool.maskCanvas.width, this.maskTool.maskCanvas.height);
|
maskCtx.clearRect(0, 0, this.maskTool.maskCanvas.width, this.maskTool.maskCanvas.height);
|
||||||
maskCtx.drawImage(savedState.maskData, 0, 0);
|
maskCtx.drawImage(savedState.maskData, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Przywróć pozycję maski
|
|
||||||
if (savedState.maskPosition) {
|
if (savedState.maskPosition) {
|
||||||
this.maskTool.x = savedState.maskPosition.x;
|
this.maskTool.x = savedState.maskPosition.x;
|
||||||
this.maskTool.y = savedState.maskPosition.y;
|
this.maskTool.y = savedState.maskPosition.y;
|
||||||
@@ -930,25 +892,20 @@ export class Canvas {
|
|||||||
*/
|
*/
|
||||||
async handleMaskEditorClose() {
|
async handleMaskEditorClose() {
|
||||||
console.log("Node object after mask editor close:", this.node);
|
console.log("Node object after mask editor close:", this.node);
|
||||||
|
|
||||||
// Sprawdź czy editor został anulowany
|
|
||||||
if (this.maskEditorCancelled) {
|
if (this.maskEditorCancelled) {
|
||||||
log.info("Mask editor was cancelled - restoring original mask state");
|
log.info("Mask editor was cancelled - restoring original mask state");
|
||||||
|
|
||||||
// Przywróć oryginalny stan maski
|
|
||||||
if (this.savedMaskState) {
|
if (this.savedMaskState) {
|
||||||
await this.restoreMaskState(this.savedMaskState);
|
await this.restoreMaskState(this.savedMaskState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wyczyść flagi
|
|
||||||
this.maskEditorCancelled = false;
|
this.maskEditorCancelled = false;
|
||||||
this.savedMaskState = null;
|
this.savedMaskState = null;
|
||||||
|
|
||||||
// Nie przetwarzaj wyniku z editora
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kontynuuj normalną obsługę save
|
|
||||||
if (!this.node.imgs || !this.node.imgs.length === 0 || !this.node.imgs[0].src) {
|
if (!this.node.imgs || !this.node.imgs.length === 0 || !this.node.imgs[0].src) {
|
||||||
log.warn("Mask editor was closed without a result.");
|
log.warn("Mask editor was closed without a result.");
|
||||||
return;
|
return;
|
||||||
@@ -995,20 +952,18 @@ export class Canvas {
|
|||||||
const maskCtx = this.maskTool.maskCtx;
|
const maskCtx = this.maskTool.maskCtx;
|
||||||
const destX = -this.maskTool.x;
|
const destX = -this.maskTool.x;
|
||||||
const destY = -this.maskTool.y;
|
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.globalCompositeOperation = 'source-over';
|
||||||
maskCtx.clearRect(destX, destY, this.width, this.height);
|
maskCtx.clearRect(destX, destY, this.width, this.height);
|
||||||
|
|
||||||
// Teraz narysuj nową maskę
|
|
||||||
maskCtx.drawImage(maskAsImage, destX, destY);
|
maskCtx.drawImage(maskAsImage, destX, destY);
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
this.saveState();
|
this.saveState();
|
||||||
|
|
||||||
const new_preview = new Image();
|
const new_preview = new Image();
|
||||||
// Użyj nowej metody z maską jako kanałem alpha
|
|
||||||
const blob = await this.canvasLayers.getFlattenedCanvasWithMaskAsBlob();
|
const blob = await this.canvasLayers.getFlattenedCanvasWithMaskAsBlob();
|
||||||
if (blob) {
|
if (blob) {
|
||||||
new_preview.src = URL.createObjectURL(blob);
|
new_preview.src = URL.createObjectURL(blob);
|
||||||
@@ -1019,8 +974,7 @@ export class Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.render();
|
this.render();
|
||||||
|
|
||||||
// Wyczyść zapisany stan po pomyślnym save
|
|
||||||
this.savedMaskState = null;
|
this.savedMaskState = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,8 +194,7 @@ export class CanvasInteractions {
|
|||||||
this.resetInteractionState();
|
this.resetInteractionState();
|
||||||
this.canvas.render();
|
this.canvas.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear internal clipboard when mouse leaves canvas
|
|
||||||
if (this.canvas.canvasLayers.internalClipboard.length > 0) {
|
if (this.canvas.canvasLayers.internalClipboard.length > 0) {
|
||||||
this.canvas.canvasLayers.internalClipboard = [];
|
this.canvas.canvasLayers.internalClipboard = [];
|
||||||
log.info("Internal clipboard cleared - mouse left canvas");
|
log.info("Internal clipboard cleared - mouse left canvas");
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ export class CanvasLayers {
|
|||||||
this.canvas.saveState();
|
this.canvas.saveState();
|
||||||
const newLayers = [];
|
const newLayers = [];
|
||||||
|
|
||||||
// Calculate the center of the copied layers
|
|
||||||
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||||
this.internalClipboard.forEach(layer => {
|
this.internalClipboard.forEach(layer => {
|
||||||
minX = Math.min(minX, layer.x);
|
minX = Math.min(minX, layer.x);
|
||||||
@@ -63,7 +62,6 @@ export class CanvasLayers {
|
|||||||
const centerX = (minX + maxX) / 2;
|
const centerX = (minX + maxX) / 2;
|
||||||
const centerY = (minY + maxY) / 2;
|
const centerY = (minY + maxY) / 2;
|
||||||
|
|
||||||
// Calculate offset to position at mouse cursor
|
|
||||||
const mouseX = this.canvas.lastMousePosition.x;
|
const mouseX = this.canvas.lastMousePosition.x;
|
||||||
const mouseY = this.canvas.lastMousePosition.y;
|
const mouseY = this.canvas.lastMousePosition.y;
|
||||||
const offsetX = mouseX - centerX;
|
const offsetX = mouseX - centerX;
|
||||||
@@ -89,14 +87,12 @@ export class CanvasLayers {
|
|||||||
try {
|
try {
|
||||||
log.info(`Paste operation started with preference: ${this.clipboardPreference}`);
|
log.info(`Paste operation started with preference: ${this.clipboardPreference}`);
|
||||||
|
|
||||||
// ALWAYS FIRST: Check Internal Clipboard
|
|
||||||
if (this.internalClipboard.length > 0) {
|
if (this.internalClipboard.length > 0) {
|
||||||
log.info("Pasting from internal clipboard");
|
log.info("Pasting from internal clipboard");
|
||||||
this.pasteLayers();
|
this.pasteLayers();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SECOND: Use preference setting
|
|
||||||
if (this.clipboardPreference === 'clipspace') {
|
if (this.clipboardPreference === 'clipspace') {
|
||||||
log.info("Attempting paste from ComfyUI Clipspace");
|
log.info("Attempting paste from ComfyUI Clipspace");
|
||||||
if (!await this.tryClipspacePaste(addMode)) {
|
if (!await this.tryClipspacePaste(addMode)) {
|
||||||
@@ -116,8 +112,7 @@ export class CanvasLayers {
|
|||||||
try {
|
try {
|
||||||
log.info("Attempting to paste from ComfyUI Clipspace");
|
log.info("Attempting to paste from ComfyUI Clipspace");
|
||||||
const clipspaceResult = ComfyApp.pasteFromClipspace(this.canvas.node);
|
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) {
|
if (this.canvas.node.imgs && this.canvas.node.imgs.length > 0) {
|
||||||
const clipspaceImage = this.canvas.node.imgs[0];
|
const clipspaceImage = this.canvas.node.imgs[0];
|
||||||
if (clipspaceImage && clipspaceImage.src) {
|
if (clipspaceImage && clipspaceImage.src) {
|
||||||
@@ -672,7 +667,6 @@ export class CanvasLayers {
|
|||||||
tempCanvas.height = this.canvas.height;
|
tempCanvas.height = this.canvas.height;
|
||||||
const tempCtx = tempCanvas.getContext('2d');
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
|
|
||||||
// Najpierw renderuj wszystkie warstwy
|
|
||||||
const sortedLayers = [...this.canvas.layers].sort((a, b) => a.zIndex - b.zIndex);
|
const sortedLayers = [...this.canvas.layers].sort((a, b) => a.zIndex - b.zIndex);
|
||||||
|
|
||||||
sortedLayers.forEach(layer => {
|
sortedLayers.forEach(layer => {
|
||||||
@@ -696,14 +690,12 @@ export class CanvasLayers {
|
|||||||
tempCtx.restore();
|
tempCtx.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pobierz dane obrazu
|
|
||||||
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||||
const data = imageData.data;
|
const data = imageData.data;
|
||||||
|
|
||||||
// Pobierz maskę z maskTool (używając tej samej logiki co w CanvasIO)
|
|
||||||
const toolMaskCanvas = this.canvas.maskTool.getMask();
|
const toolMaskCanvas = this.canvas.maskTool.getMask();
|
||||||
if (toolMaskCanvas) {
|
if (toolMaskCanvas) {
|
||||||
// Stwórz tymczasowy canvas dla maski w rozmiarze output area
|
|
||||||
const tempMaskCanvas = document.createElement('canvas');
|
const tempMaskCanvas = document.createElement('canvas');
|
||||||
tempMaskCanvas.width = this.canvas.width;
|
tempMaskCanvas.width = this.canvas.width;
|
||||||
tempMaskCanvas.height = this.canvas.height;
|
tempMaskCanvas.height = this.canvas.height;
|
||||||
@@ -711,7 +703,6 @@ export class CanvasLayers {
|
|||||||
|
|
||||||
tempMaskCtx.clearRect(0, 0, tempMaskCanvas.width, tempMaskCanvas.height);
|
tempMaskCtx.clearRect(0, 0, tempMaskCanvas.width, tempMaskCanvas.height);
|
||||||
|
|
||||||
// Użyj tej samej logiki co w CanvasIO
|
|
||||||
const maskX = this.canvas.maskTool.x;
|
const maskX = this.canvas.maskTool.x;
|
||||||
const maskY = this.canvas.maskTool.y;
|
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);
|
const tempMaskData = tempMaskCtx.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
||||||
for (let i = 0; i < tempMaskData.data.length; i += 4) {
|
for (let i = 0; i < tempMaskData.data.length; i += 4) {
|
||||||
const alpha = tempMaskData.data[i + 3];
|
const alpha = tempMaskData.data[i + 3];
|
||||||
@@ -746,21 +736,18 @@ export class CanvasLayers {
|
|||||||
}
|
}
|
||||||
tempMaskCtx.putImageData(tempMaskData, 0, 0);
|
tempMaskCtx.putImageData(tempMaskData, 0, 0);
|
||||||
|
|
||||||
// Zastosuj maskę do obrazu
|
|
||||||
const maskImageData = tempMaskCtx.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
const maskImageData = tempMaskCtx.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
||||||
const maskData = maskImageData.data;
|
const maskData = maskImageData.data;
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i += 4) {
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
const originalAlpha = data[i + 3];
|
const originalAlpha = data[i + 3];
|
||||||
const maskAlpha = maskData[i + 3] / 255; // Użyj kanału alpha maski
|
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;
|
const invertedMaskAlpha = 1 - maskAlpha;
|
||||||
data[i + 3] = originalAlpha * invertedMaskAlpha;
|
data[i + 3] = originalAlpha * invertedMaskAlpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zapisz zmodyfikowane dane obrazu
|
|
||||||
tempCtx.putImageData(imageData, 0, 0);
|
tempCtx.putImageData(imageData, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -781,7 +768,6 @@ export class CanvasLayers {
|
|||||||
tempCanvas.height = this.canvas.height;
|
tempCanvas.height = this.canvas.height;
|
||||||
const tempCtx = tempCanvas.getContext('2d');
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
|
|
||||||
// Renderuj wszystkie warstwy (pełny obraz)
|
|
||||||
const sortedLayers = [...this.canvas.layers].sort((a, b) => a.zIndex - b.zIndex);
|
const sortedLayers = [...this.canvas.layers].sort((a, b) => a.zIndex - b.zIndex);
|
||||||
|
|
||||||
sortedLayers.forEach(layer => {
|
sortedLayers.forEach(layer => {
|
||||||
@@ -805,14 +791,12 @@ export class CanvasLayers {
|
|||||||
tempCtx.restore();
|
tempCtx.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pobierz dane obrazu
|
|
||||||
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||||
const data = imageData.data;
|
const data = imageData.data;
|
||||||
|
|
||||||
// Pobierz maskę z maskTool i zastosuj ją jako kanał alpha
|
|
||||||
const toolMaskCanvas = this.canvas.maskTool.getMask();
|
const toolMaskCanvas = this.canvas.maskTool.getMask();
|
||||||
if (toolMaskCanvas) {
|
if (toolMaskCanvas) {
|
||||||
// Stwórz tymczasowy canvas dla maski w rozmiarze output area
|
|
||||||
const tempMaskCanvas = document.createElement('canvas');
|
const tempMaskCanvas = document.createElement('canvas');
|
||||||
tempMaskCanvas.width = this.canvas.width;
|
tempMaskCanvas.width = this.canvas.width;
|
||||||
tempMaskCanvas.height = this.canvas.height;
|
tempMaskCanvas.height = this.canvas.height;
|
||||||
@@ -820,7 +804,6 @@ export class CanvasLayers {
|
|||||||
|
|
||||||
tempMaskCtx.clearRect(0, 0, tempMaskCanvas.width, tempMaskCanvas.height);
|
tempMaskCtx.clearRect(0, 0, tempMaskCanvas.width, tempMaskCanvas.height);
|
||||||
|
|
||||||
// Użyj tej samej logiki co w CanvasIO
|
|
||||||
const maskX = this.canvas.maskTool.x;
|
const maskX = this.canvas.maskTool.x;
|
||||||
const maskY = this.canvas.maskTool.y;
|
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);
|
const tempMaskData = tempMaskCtx.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
||||||
for (let i = 0; i < tempMaskData.data.length; i += 4) {
|
for (let i = 0; i < tempMaskData.data.length; i += 4) {
|
||||||
const alpha = tempMaskData.data[i + 3];
|
const alpha = tempMaskData.data[i + 3];
|
||||||
@@ -855,21 +837,18 @@ export class CanvasLayers {
|
|||||||
}
|
}
|
||||||
tempMaskCtx.putImageData(tempMaskData, 0, 0);
|
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 maskImageData = tempMaskCtx.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
||||||
const maskData = maskImageData.data;
|
const maskData = maskImageData.data;
|
||||||
|
|
||||||
for (let i = 0; i < data.length; i += 4) {
|
for (let i = 0; i < data.length; i += 4) {
|
||||||
const originalAlpha = data[i + 3];
|
const originalAlpha = data[i + 3];
|
||||||
const maskAlpha = maskData[i + 3] / 255;
|
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;
|
const invertedMaskAlpha = 1 - maskAlpha;
|
||||||
data[i + 3] = originalAlpha * invertedMaskAlpha;
|
data[i + 3] = originalAlpha * invertedMaskAlpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zapisz zmodyfikowane dane obrazu
|
|
||||||
tempCtx.putImageData(imageData, 0, 0);
|
tempCtx.putImageData(imageData, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -485,8 +485,7 @@ async function createCanvasWidget(node, widget, app) {
|
|||||||
} else {
|
} else {
|
||||||
helpTooltip.innerHTML = standardShortcuts;
|
helpTooltip.innerHTML = standardShortcuts;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Najpierw wyświetlamy tooltip z visibility: hidden aby obliczyć jego wymiary
|
|
||||||
helpTooltip.style.visibility = 'hidden';
|
helpTooltip.style.visibility = 'hidden';
|
||||||
helpTooltip.style.display = 'block';
|
helpTooltip.style.display = 'block';
|
||||||
|
|
||||||
@@ -494,29 +493,23 @@ async function createCanvasWidget(node, widget, app) {
|
|||||||
const tooltipRect = helpTooltip.getBoundingClientRect();
|
const tooltipRect = helpTooltip.getBoundingClientRect();
|
||||||
const viewportWidth = window.innerWidth;
|
const viewportWidth = window.innerWidth;
|
||||||
const viewportHeight = window.innerHeight;
|
const viewportHeight = window.innerHeight;
|
||||||
|
|
||||||
// Obliczamy pozycję
|
|
||||||
let left = buttonRect.left;
|
let left = buttonRect.left;
|
||||||
let top = buttonRect.bottom + 5;
|
let top = buttonRect.bottom + 5;
|
||||||
|
|
||||||
// Sprawdzamy czy tooltip wychodzi poza prawy brzeg ekranu
|
|
||||||
if (left + tooltipRect.width > viewportWidth) {
|
if (left + tooltipRect.width > viewportWidth) {
|
||||||
left = viewportWidth - tooltipRect.width - 10;
|
left = viewportWidth - tooltipRect.width - 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sprawdzamy czy tooltip wychodzi poza dolny brzeg ekranu
|
|
||||||
if (top + tooltipRect.height > viewportHeight) {
|
if (top + tooltipRect.height > viewportHeight) {
|
||||||
// Wyświetlamy nad przyciskiem zamiast pod
|
|
||||||
top = buttonRect.top - tooltipRect.height - 5;
|
top = buttonRect.top - tooltipRect.height - 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upewniamy się, że tooltip nie wychodzi poza lewy brzeg
|
|
||||||
if (left < 10) left = 10;
|
if (left < 10) left = 10;
|
||||||
|
|
||||||
// Upewniamy się, że tooltip nie wychodzi poza górny brzeg
|
|
||||||
if (top < 10) top = 10;
|
if (top < 10) top = 10;
|
||||||
|
|
||||||
// Ustawiamy finalną pozycję i pokazujemy tooltip
|
|
||||||
helpTooltip.style.left = `${left}px`;
|
helpTooltip.style.left = `${left}px`;
|
||||||
helpTooltip.style.top = `${top}px`;
|
helpTooltip.style.top = `${top}px`;
|
||||||
helpTooltip.style.visibility = 'visible';
|
helpTooltip.style.visibility = 'visible';
|
||||||
@@ -671,7 +664,7 @@ async function createCanvasWidget(node, widget, app) {
|
|||||||
const height = parseInt(document.getElementById('canvas-height').value) || canvas.height;
|
const height = parseInt(document.getElementById('canvas-height').value) || canvas.height;
|
||||||
canvas.updateOutputAreaSize(width, height);
|
canvas.updateOutputAreaSize(width, height);
|
||||||
document.body.removeChild(dialog);
|
document.body.removeChild(dialog);
|
||||||
// updateOutput is triggered by saveState in updateOutputAreaSize
|
|
||||||
};
|
};
|
||||||
|
|
||||||
document.getElementById('cancel-size').onclick = () => {
|
document.getElementById('cancel-size').onclick = () => {
|
||||||
@@ -946,8 +939,7 @@ async function createCanvasWidget(node, widget, app) {
|
|||||||
|
|
||||||
const updateOutput = async () => {
|
const updateOutput = async () => {
|
||||||
triggerWidget.value = (triggerWidget.value + 1) % 99999999;
|
triggerWidget.value = (triggerWidget.value + 1) % 99999999;
|
||||||
|
|
||||||
// ZAWSZE generuj obraz (potrzebny dla funkcji masek)
|
|
||||||
try {
|
try {
|
||||||
const new_preview = new Image();
|
const new_preview = new Image();
|
||||||
const blob = await canvas.getFlattenedCanvasWithMaskAsBlob();
|
const blob = await canvas.getFlattenedCanvasWithMaskAsBlob();
|
||||||
@@ -961,8 +953,7 @@ async function createCanvasWidget(node, widget, app) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating node preview:", 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", {
|
const canvasContainer = $el("div.painterCanvasContainer.painter-container", {
|
||||||
@@ -1111,8 +1102,6 @@ async function createCanvasWidget(node, widget, app) {
|
|||||||
canvas.loadInitialState();
|
canvas.loadInitialState();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
|
||||||
// Konfiguracja opcji ukrywania podglądu
|
|
||||||
const showPreviewWidget = node.widgets.find(w => w.name === "show_preview");
|
const showPreviewWidget = node.widgets.find(w => w.name === "show_preview");
|
||||||
if (showPreviewWidget) {
|
if (showPreviewWidget) {
|
||||||
const originalCallback = showPreviewWidget.callback;
|
const originalCallback = showPreviewWidget.callback;
|
||||||
@@ -1121,20 +1110,17 @@ async function createCanvasWidget(node, widget, app) {
|
|||||||
if (originalCallback) {
|
if (originalCallback) {
|
||||||
originalCallback.call(this, value);
|
originalCallback.call(this, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kontroluj widoczność podglądu w Canvas
|
|
||||||
if (canvas && canvas.setPreviewVisibility) {
|
if (canvas && canvas.setPreviewVisibility) {
|
||||||
canvas.setPreviewVisibility(value);
|
canvas.setPreviewVisibility(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wymuś przerysowanie node'a
|
|
||||||
if (node.graph && node.graph.canvas) {
|
if (node.graph && node.graph.canvas) {
|
||||||
node.setDirtyCanvas(true, true);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate through every widget attached to this node
|
|
||||||
this.widgets.forEach(w => {
|
this.widgets.forEach(w => {
|
||||||
log.debug(`Widget name: ${w.name}, type: ${w.type}, value: ${w.value}`);
|
log.debug(`Widget name: ${w.name}, type: ${w.type}, value: ${w.value}`);
|
||||||
});
|
});
|
||||||
@@ -1265,8 +1250,7 @@ app.registerExtension({
|
|||||||
originalGetExtraMenuOptions?.apply(this, arguments);
|
originalGetExtraMenuOptions?.apply(this, arguments);
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
// Usuń standardową opcję "Open in MaskEditor" jeśli istnieje
|
|
||||||
const maskEditorIndex = options.findIndex(option =>
|
const maskEditorIndex = options.findIndex(option =>
|
||||||
option && option.content === "Open in MaskEditor"
|
option && option.content === "Open in MaskEditor"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export class MaskTool {
|
|||||||
if (this.brushHardness === 1) {
|
if (this.brushHardness === 1) {
|
||||||
this.maskCtx.strokeStyle = `rgba(255, 255, 255, ${this.brushStrength})`;
|
this.maskCtx.strokeStyle = `rgba(255, 255, 255, ${this.brushStrength})`;
|
||||||
} else {
|
} else {
|
||||||
// hardness: 1 = hard edge, 0 = soft edge
|
|
||||||
const innerRadius = gradientRadius * this.brushHardness;
|
const innerRadius = gradientRadius * this.brushHardness;
|
||||||
const gradient = this.maskCtx.createRadialGradient(
|
const gradient = this.maskCtx.createRadialGradient(
|
||||||
canvasX, canvasY, innerRadius,
|
canvasX, canvasY, innerRadius,
|
||||||
@@ -281,18 +281,16 @@ export class MaskTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setMask(image) {
|
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 destX = -this.x;
|
||||||
const destY = -this.y;
|
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);
|
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);
|
this.maskCtx.drawImage(image, destX, destY);
|
||||||
|
|
||||||
if (this.onStateChange) {
|
if (this.onStateChange) {
|
||||||
|
|||||||
@@ -15,11 +15,10 @@ import {
|
|||||||
* Przykład 1: Podstawowe użycie z obrazem maski
|
* Przykład 1: Podstawowe użycie z obrazem maski
|
||||||
*/
|
*/
|
||||||
async function example1_basic_usage(canvasInstance) {
|
async function example1_basic_usage(canvasInstance) {
|
||||||
// Załaduj obraz maski z URL
|
|
||||||
const maskImage = await create_mask_from_image_src('/path/to/mask.png');
|
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);
|
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
|
* Przykład 2: Użycie z canvas jako maska
|
||||||
*/
|
*/
|
||||||
async function example2_canvas_mask(canvasInstance) {
|
async function example2_canvas_mask(canvasInstance) {
|
||||||
// Stwórz canvas z maską programowo
|
|
||||||
const maskCanvas = document.createElement('canvas');
|
const maskCanvas = document.createElement('canvas');
|
||||||
maskCanvas.width = 512;
|
maskCanvas.width = 512;
|
||||||
maskCanvas.height = 512;
|
maskCanvas.height = 512;
|
||||||
const ctx = maskCanvas.getContext('2d');
|
const ctx = maskCanvas.getContext('2d');
|
||||||
|
|
||||||
// Narysuj prostą maskę - białe koło na czarnym tle
|
|
||||||
ctx.fillStyle = 'black';
|
ctx.fillStyle = 'black';
|
||||||
ctx.fillRect(0, 0, 512, 512);
|
ctx.fillRect(0, 0, 512, 512);
|
||||||
ctx.fillStyle = 'white';
|
ctx.fillStyle = 'white';
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.arc(256, 256, 100, 0, 2 * Math.PI);
|
ctx.arc(256, 256, 100, 0, 2 * Math.PI);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
// Konwertuj canvas do Image
|
|
||||||
const maskImage = await canvas_to_mask_image(maskCanvas);
|
const maskImage = await canvas_to_mask_image(maskCanvas);
|
||||||
|
|
||||||
// Uruchom mask editor
|
|
||||||
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, true);
|
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
|
* Przykład 3: Bezpośrednie użycie metody Canvas
|
||||||
*/
|
*/
|
||||||
async function example3_direct_canvas_method(canvasInstance) {
|
async function example3_direct_canvas_method(canvasInstance) {
|
||||||
// Załaduj maskę
|
|
||||||
const maskImage = await create_mask_from_image_src('/path/to/mask.png');
|
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);
|
await canvasInstance.startMaskEditor(maskImage, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,17 +60,14 @@ async function example3_direct_canvas_method(canvasInstance) {
|
|||||||
* Przykład 4: Tworzenie maski z danych binarnych
|
* Przykład 4: Tworzenie maski z danych binarnych
|
||||||
*/
|
*/
|
||||||
async function example4_binary_data_mask(canvasInstance, binaryData) {
|
async function example4_binary_data_mask(canvasInstance, binaryData) {
|
||||||
// Konwertuj dane binarne do data URL
|
|
||||||
const blob = new Blob([binaryData], { type: 'image/png' });
|
const blob = new Blob([binaryData], { type: 'image/png' });
|
||||||
const dataUrl = URL.createObjectURL(blob);
|
const dataUrl = URL.createObjectURL(blob);
|
||||||
|
|
||||||
// Stwórz obraz z data URL
|
|
||||||
const maskImage = await create_mask_from_image_src(dataUrl);
|
const maskImage = await create_mask_from_image_src(dataUrl);
|
||||||
|
|
||||||
// Uruchom mask editor
|
|
||||||
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, true);
|
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, true);
|
||||||
|
|
||||||
// Wyczyść URL po użyciu
|
|
||||||
URL.revokeObjectURL(dataUrl);
|
URL.revokeObjectURL(dataUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,8 +79,7 @@ async function example5_gradient_mask(canvasInstance) {
|
|||||||
maskCanvas.width = 512;
|
maskCanvas.width = 512;
|
||||||
maskCanvas.height = 512;
|
maskCanvas.height = 512;
|
||||||
const ctx = maskCanvas.getContext('2d');
|
const ctx = maskCanvas.getContext('2d');
|
||||||
|
|
||||||
// Stwórz gradient od przezroczystego do białego
|
|
||||||
const gradient = ctx.createLinearGradient(0, 0, 512, 0);
|
const gradient = ctx.createLinearGradient(0, 0, 512, 0);
|
||||||
gradient.addColorStop(0, 'rgba(0, 0, 0, 0)'); // Przezroczysty
|
gradient.addColorStop(0, 'rgba(0, 0, 0, 0)'); // Przezroczysty
|
||||||
gradient.addColorStop(1, 'rgba(255, 255, 255, 1)'); // Biały
|
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);
|
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Błąd podczas ładowania maski:', error);
|
console.error('Błąd podczas ładowania maski:', error);
|
||||||
|
|
||||||
// Fallback - uruchom editor bez predefiniowanej maski
|
|
||||||
await canvasInstance.startMaskEditor();
|
await canvasInstance.startMaskEditor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,13 +124,11 @@ async function example7_existing_canvas_element(canvasInstance, canvasElementId)
|
|||||||
*/
|
*/
|
||||||
async function example8_combine_with_existing_mask(canvasInstance) {
|
async function example8_combine_with_existing_mask(canvasInstance) {
|
||||||
const maskImage = await create_mask_from_image_src('/path/to/mask.png');
|
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);
|
start_mask_editor_with_predefined_mask(canvasInstance, maskImage, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eksportuj przykłady dla użycia w innych plikach
|
|
||||||
export {
|
export {
|
||||||
example1_basic_usage,
|
example1_basic_usage,
|
||||||
example2_canvas_mask,
|
example2_canvas_mask,
|
||||||
|
|||||||
@@ -21,14 +21,13 @@ export function hide_mask_editor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function get_mask_editor_cancel_button(app) {
|
function get_mask_editor_cancel_button(app) {
|
||||||
// Główny przycisk Cancel z nowego editora
|
|
||||||
const cancelButton = document.getElementById("maskEditor_topBarCancelButton");
|
const cancelButton = document.getElementById("maskEditor_topBarCancelButton");
|
||||||
if (cancelButton) {
|
if (cancelButton) {
|
||||||
log.debug("Found cancel button by ID: maskEditor_topBarCancelButton");
|
log.debug("Found cancel button by ID: maskEditor_topBarCancelButton");
|
||||||
return cancelButton;
|
return cancelButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sprawdź inne możliwe selektory (bez :contains które nie działają)
|
|
||||||
const cancelSelectors = [
|
const cancelSelectors = [
|
||||||
'button[onclick*="cancel"]',
|
'button[onclick*="cancel"]',
|
||||||
'button[onclick*="Cancel"]',
|
'button[onclick*="Cancel"]',
|
||||||
@@ -46,8 +45,7 @@ function get_mask_editor_cancel_button(app) {
|
|||||||
log.warn("Invalid selector:", selector, e);
|
log.warn("Invalid selector:", selector, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback - sprawdź wszystkie przyciski w dokumencie po tekście
|
|
||||||
const allButtons = document.querySelectorAll('button, input[type="button"]');
|
const allButtons = document.querySelectorAll('button, input[type="button"]');
|
||||||
for (const button of allButtons) {
|
for (const button of allButtons) {
|
||||||
const text = button.textContent || button.value || '';
|
const text = button.textContent || button.value || '';
|
||||||
@@ -56,8 +54,7 @@ function get_mask_editor_cancel_button(app) {
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ostatni fallback - stary editor
|
|
||||||
const editorElement = get_mask_editor_element(app);
|
const editorElement = get_mask_editor_element(app);
|
||||||
if (editorElement) {
|
if (editorElement) {
|
||||||
return editorElement?.parentElement?.lastChild?.childNodes[2];
|
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) {
|
export function mask_editor_listen_for_cancel(app, callback) {
|
||||||
// Spróbuj znaleźć przycisk Cancel wielokrotnie z opóźnieniem
|
|
||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
const maxAttempts = 50; // 5 sekund
|
const maxAttempts = 50; // 5 sekund
|
||||||
|
|
||||||
@@ -86,12 +83,11 @@ export function mask_editor_listen_for_cancel(app, callback) {
|
|||||||
cancel_button.filter_listener_added = true;
|
cancel_button.filter_listener_added = true;
|
||||||
return true; // Znaleziono i podłączono
|
return true; // Znaleziono i podłączono
|
||||||
} else if (attempts < maxAttempts) {
|
} else if (attempts < maxAttempts) {
|
||||||
// Spróbuj ponownie za 100ms
|
|
||||||
setTimeout(findAndAttachListener, 100);
|
setTimeout(findAndAttachListener, 100);
|
||||||
} else {
|
} else {
|
||||||
log.warn("Could not find cancel button after", maxAttempts, "attempts");
|
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 globalClickHandler = (event) => {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
const text = target.textContent || target.value || '';
|
const text = target.textContent || target.value || '';
|
||||||
@@ -144,10 +140,9 @@ export function start_mask_editor_auto(canvasInstance) {
|
|||||||
log.error('Canvas instance is required');
|
log.error('Canvas instance is required');
|
||||||
return;
|
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();
|
canvasInstance.startMaskEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user