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