mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-22 05:02:11 -03:00
Refactor Canvas class as facade and clean up CanvasLayers
Refactored the Canvas class to act as a facade, providing a simplified high-level interface and delegating detailed operations to internal modules. Added Polish documentation, grouped and clarified main operations, and moved legacy/delegation methods to the end for backward compatibility. Removed unused or redundant methods from CanvasLayers.js, such as removeLayer, moveLayer, addMattedLayer, isRotationHandle, getResizeHandle, handleBlendModeSelection, and getFlattenedCanvasAsDataURL, to streamline the codebase.
This commit is contained in:
657
js/Canvas.js
657
js/Canvas.js
@@ -13,6 +13,14 @@ import { mask_editor_showing } from "./utils/mask_utils.js";
|
||||
|
||||
const log = createModuleLogger('Canvas');
|
||||
|
||||
/**
|
||||
* Canvas - Fasada dla systemu rysowania
|
||||
*
|
||||
* Klasa Canvas pełni rolę fasady, oferując uproszczony interfejs wysokiego poziomu
|
||||
* dla złożonego systemu rysowania. Zamiast eksponować wszystkie metody modułów,
|
||||
* udostępnia tylko kluczowe operacje i umożliwia bezpośredni dostęp do modułów
|
||||
* gdy potrzebna jest bardziej szczegółowa kontrola.
|
||||
*/
|
||||
export class Canvas {
|
||||
constructor(node, widget, callbacks = {}) {
|
||||
this.node = node;
|
||||
@@ -41,187 +49,115 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicjalizuje moduły systemu canvas
|
||||
* @private
|
||||
*/
|
||||
_initializeModules(callbacks) {
|
||||
// Moduły są publiczne dla bezpośredniego dostępu gdy potrzebne
|
||||
this.maskTool = new MaskTool(this, {onStateChange: this.onStateChange});
|
||||
this.initCanvas();
|
||||
this.canvasState = new CanvasState(this);
|
||||
this.canvasInteractions = new CanvasInteractions(this);
|
||||
this.canvasLayers = new CanvasLayers(this);
|
||||
this.canvasRenderer = new CanvasRenderer(this);
|
||||
this.canvasIO = new CanvasIO(this);
|
||||
this.imageReferenceManager = new ImageReferenceManager(this);
|
||||
this.interaction = this.canvasInteractions.interaction;
|
||||
|
||||
this.setupEventListeners();
|
||||
this.initNodeData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Konfiguruje podstawowe właściwości canvas
|
||||
* @private
|
||||
*/
|
||||
_setupCanvas() {
|
||||
this.initCanvas();
|
||||
this.canvasInteractions.setupEventListeners();
|
||||
this.canvasIO.initNodeData();
|
||||
|
||||
// Inicjalizacja warstw z domyślną przezroczystością
|
||||
this.layers = this.layers.map(layer => ({
|
||||
...layer,
|
||||
opacity: 1
|
||||
}));
|
||||
|
||||
this.imageCache = new Map();
|
||||
}
|
||||
|
||||
async loadStateFromDB() {
|
||||
return this.canvasState.loadStateFromDB();
|
||||
}
|
||||
|
||||
async saveStateToDB(immediate = false) {
|
||||
return this.canvasState.saveStateToDB(immediate);
|
||||
}
|
||||
// ==========================================
|
||||
// GŁÓWNE OPERACJE FASADY
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Ładuje stan canvas z bazy danych
|
||||
*/
|
||||
async loadInitialState() {
|
||||
log.info("Loading initial state for node:", this.node.id);
|
||||
const loaded = await this.loadStateFromDB();
|
||||
const loaded = await this.canvasState.loadStateFromDB();
|
||||
if (!loaded) {
|
||||
log.info("No saved state found, initializing from node data.");
|
||||
await this.initNodeData();
|
||||
await this.canvasIO.initNodeData();
|
||||
}
|
||||
this.saveState();
|
||||
this.render();
|
||||
}
|
||||
|
||||
_notifyStateChange() {
|
||||
if (this.onStateChange) {
|
||||
this.onStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zapisuje obecny stan
|
||||
* @param {boolean} replaceLast - Czy zastąpić ostatni stan w historii
|
||||
*/
|
||||
saveState(replaceLast = false) {
|
||||
this.canvasState.saveState(replaceLast);
|
||||
this.incrementOperationCount();
|
||||
this._notifyStateChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cofnij ostatnią operację
|
||||
*/
|
||||
undo() {
|
||||
this.canvasState.undo();
|
||||
this.incrementOperationCount();
|
||||
this._notifyStateChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ponów cofniętą operację
|
||||
*/
|
||||
redo() {
|
||||
this.canvasState.redo();
|
||||
this.incrementOperationCount();
|
||||
this._notifyStateChange();
|
||||
}
|
||||
|
||||
updateSelectionAfterHistory() {
|
||||
const newSelectedLayers = [];
|
||||
if (this.selectedLayers) {
|
||||
this.selectedLayers.forEach(sl => {
|
||||
const found = this.layers.find(l => l.id === sl.id);
|
||||
if (found) newSelectedLayers.push(found);
|
||||
});
|
||||
}
|
||||
this.updateSelection(newSelectedLayers);
|
||||
/**
|
||||
* Renderuje canvas
|
||||
*/
|
||||
render() {
|
||||
this.canvasRenderer.render();
|
||||
}
|
||||
|
||||
updateHistoryButtons() {
|
||||
if (this.onHistoryChange) {
|
||||
const historyInfo = this.canvasState.getHistoryInfo();
|
||||
this.onHistoryChange({
|
||||
canUndo: historyInfo.canUndo,
|
||||
canRedo: historyInfo.canRedo
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
initCanvas() {
|
||||
this.canvas.width = this.width;
|
||||
this.canvas.height = this.height;
|
||||
this.canvas.style.border = '1px solid black';
|
||||
this.canvas.style.maxWidth = '100%';
|
||||
this.canvas.style.backgroundColor = '#606060';
|
||||
this.canvas.style.width = '100%';
|
||||
this.canvas.style.height = '100%';
|
||||
|
||||
|
||||
this.canvas.tabIndex = 0;
|
||||
this.canvas.style.outline = 'none';
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
this.canvasInteractions.setupEventListeners();
|
||||
}
|
||||
|
||||
updateSelection(newSelection) {
|
||||
this.selectedLayers = newSelection || [];
|
||||
this.selectedLayer = this.selectedLayers.length > 0 ? this.selectedLayers[this.selectedLayers.length - 1] : null;
|
||||
if (this.onSelectionChange) {
|
||||
this.onSelectionChange();
|
||||
}
|
||||
}
|
||||
|
||||
async copySelectedLayers() {
|
||||
return this.canvasLayers.copySelectedLayers();
|
||||
}
|
||||
|
||||
pasteLayers() {
|
||||
return this.canvasLayers.pasteLayers();
|
||||
}
|
||||
|
||||
async handlePaste(addMode) {
|
||||
return this.canvasLayers.handlePaste(addMode);
|
||||
}
|
||||
|
||||
|
||||
handleMouseMove(e) {
|
||||
this.canvasInteractions.handleMouseMove(e);
|
||||
}
|
||||
|
||||
|
||||
handleMouseUp(e) {
|
||||
this.canvasInteractions.handleMouseUp(e);
|
||||
}
|
||||
|
||||
|
||||
handleMouseLeave(e) {
|
||||
this.canvasInteractions.handleMouseLeave(e);
|
||||
}
|
||||
|
||||
|
||||
handleWheel(e) {
|
||||
this.canvasInteractions.handleWheel(e);
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
this.canvasInteractions.handleKeyDown(e);
|
||||
}
|
||||
|
||||
handleKeyUp(e) {
|
||||
this.canvasInteractions.handleKeyUp(e);
|
||||
}
|
||||
|
||||
|
||||
isRotationHandle(x, y) {
|
||||
return this.canvasLayers.isRotationHandle(x, y);
|
||||
}
|
||||
|
||||
async addLayerWithImage(image, layerProps = {}, addMode = 'default') {
|
||||
/**
|
||||
* Dodaje warstwę z obrazem
|
||||
* @param {Image} image - Obraz do dodania
|
||||
* @param {Object} layerProps - Właściwości warstwy
|
||||
* @param {string} addMode - Tryb dodawania
|
||||
*/
|
||||
async addLayer(image, layerProps = {}, addMode = 'default') {
|
||||
return this.canvasLayers.addLayerWithImage(image, layerProps, addMode);
|
||||
}
|
||||
|
||||
|
||||
async addLayer(image, addMode = 'default') {
|
||||
return this.addLayerWithImage(image, {}, addMode);
|
||||
}
|
||||
|
||||
async removeLayer(index) {
|
||||
if (index >= 0 && index < this.layers.length) {
|
||||
const layer = this.layers[index];
|
||||
if (layer.imageId) {
|
||||
const isImageUsedElsewhere = this.layers.some((l, i) => i !== index && l.imageId === layer.imageId);
|
||||
if (!isImageUsedElsewhere) {
|
||||
await removeImage(layer.imageId);
|
||||
this.imageCache.delete(layer.imageId);
|
||||
}
|
||||
}
|
||||
this.layers.splice(index, 1);
|
||||
this.selectedLayer = this.layers[this.layers.length - 1] || null;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Usuwa wybrane warstwy
|
||||
*/
|
||||
removeSelectedLayers() {
|
||||
if (this.selectedLayers.length > 0) {
|
||||
this.saveState();
|
||||
@@ -232,239 +168,49 @@ export class Canvas {
|
||||
}
|
||||
}
|
||||
|
||||
getMouseWorldCoordinates(e) {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
|
||||
const mouseX_DOM = e.clientX - rect.left;
|
||||
const mouseY_DOM = e.clientY - rect.top;
|
||||
|
||||
const scaleX = this.offscreenCanvas.width / rect.width;
|
||||
const scaleY = this.offscreenCanvas.height / rect.height;
|
||||
|
||||
const mouseX_Buffer = mouseX_DOM * scaleX;
|
||||
const mouseY_Buffer = mouseY_DOM * scaleY;
|
||||
|
||||
const worldX = (mouseX_Buffer / this.viewport.zoom) + this.viewport.x;
|
||||
const worldY = (mouseY_Buffer / this.viewport.zoom) + this.viewport.y;
|
||||
|
||||
return {x: worldX, y: worldY};
|
||||
}
|
||||
|
||||
getMouseViewCoordinates(e) {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
const mouseX_DOM = e.clientX - rect.left;
|
||||
const mouseY_DOM = e.clientY - rect.top;
|
||||
|
||||
const scaleX = this.canvas.width / rect.width;
|
||||
const scaleY = this.canvas.height / rect.height;
|
||||
|
||||
const mouseX_Canvas = mouseX_DOM * scaleX;
|
||||
const mouseY_Canvas = mouseY_DOM * scaleY;
|
||||
|
||||
return { x: mouseX_Canvas, y: mouseY_Canvas };
|
||||
}
|
||||
|
||||
|
||||
moveLayer(fromIndex, toIndex) {
|
||||
return this.canvasLayers.moveLayer(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
resizeLayer(scale) {
|
||||
this.selectedLayers.forEach(layer => {
|
||||
layer.width *= scale;
|
||||
layer.height *= scale;
|
||||
});
|
||||
this.render();
|
||||
this.saveState();
|
||||
}
|
||||
|
||||
rotateLayer(angle) {
|
||||
this.selectedLayers.forEach(layer => {
|
||||
layer.rotation += angle;
|
||||
});
|
||||
this.render();
|
||||
this.saveState();
|
||||
/**
|
||||
* Aktualizuje zaznaczenie warstw
|
||||
* @param {Array} newSelection - Nowa lista zaznaczonych warstw
|
||||
*/
|
||||
updateSelection(newSelection) {
|
||||
this.selectedLayers = newSelection || [];
|
||||
this.selectedLayer = this.selectedLayers.length > 0 ? this.selectedLayers[this.selectedLayers.length - 1] : null;
|
||||
if (this.onSelectionChange) {
|
||||
this.onSelectionChange();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zmienia rozmiar obszaru wyjściowego
|
||||
* @param {number} width - Nowa szerokość
|
||||
* @param {number} height - Nowa wysokość
|
||||
* @param {boolean} saveHistory - Czy zapisać w historii
|
||||
*/
|
||||
updateOutputAreaSize(width, height, saveHistory = true) {
|
||||
return this.canvasLayers.updateOutputAreaSize(width, height, saveHistory);
|
||||
}
|
||||
|
||||
render() {
|
||||
this.canvasRenderer.render();
|
||||
}
|
||||
|
||||
|
||||
getHandles(layer) {
|
||||
return this.canvasLayers.getHandles(layer);
|
||||
}
|
||||
|
||||
getHandleAtPosition(worldX, worldY) {
|
||||
return this.canvasLayers.getHandleAtPosition(worldX, worldY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Eksportuje spłaszczony canvas jako blob
|
||||
*/
|
||||
async getFlattenedCanvasAsBlob() {
|
||||
return this.canvasLayers.getFlattenedCanvasAsBlob();
|
||||
}
|
||||
|
||||
async getFlattenedSelectionAsBlob() {
|
||||
return this.canvasLayers.getFlattenedSelectionAsBlob();
|
||||
}
|
||||
|
||||
moveLayerUp() {
|
||||
return this.canvasLayers.moveLayerUp();
|
||||
}
|
||||
|
||||
moveLayerDown() {
|
||||
return this.canvasLayers.moveLayerDown();
|
||||
}
|
||||
|
||||
|
||||
getLayerAtPosition(worldX, worldY) {
|
||||
return this.canvasLayers.getLayerAtPosition(worldX, worldY);
|
||||
}
|
||||
|
||||
getResizeHandle(x, y) {
|
||||
return this.canvasLayers.getResizeHandle(x, y);
|
||||
}
|
||||
|
||||
async mirrorHorizontal() {
|
||||
return this.canvasLayers.mirrorHorizontal();
|
||||
}
|
||||
|
||||
async mirrorVertical() {
|
||||
return this.canvasLayers.mirrorVertical();
|
||||
}
|
||||
|
||||
async getLayerImageData(layer) {
|
||||
return this.canvasLayers.getLayerImageData(layer);
|
||||
}
|
||||
|
||||
addMattedLayer(image, mask) {
|
||||
return this.canvasLayers.addMattedLayer(image, mask);
|
||||
}
|
||||
|
||||
async addInputToCanvas(inputImage, inputMask) {
|
||||
return this.canvasIO.addInputToCanvas(inputImage, inputMask);
|
||||
}
|
||||
|
||||
async convertTensorToImage(tensor) {
|
||||
return this.canvasIO.convertTensorToImage(tensor);
|
||||
}
|
||||
|
||||
async convertTensorToMask(tensor) {
|
||||
return this.canvasIO.convertTensorToMask(tensor);
|
||||
}
|
||||
|
||||
async initNodeData() {
|
||||
return this.canvasIO.initNodeData();
|
||||
}
|
||||
|
||||
scheduleDataCheck() {
|
||||
return this.canvasIO.scheduleDataCheck();
|
||||
}
|
||||
|
||||
async processImageData(imageData) {
|
||||
return this.canvasIO.processImageData(imageData);
|
||||
}
|
||||
|
||||
addScaledLayer(image, scale) {
|
||||
return this.canvasIO.addScaledLayer(image, scale);
|
||||
}
|
||||
|
||||
convertTensorToImageData(tensor) {
|
||||
return this.canvasIO.convertTensorToImageData(tensor);
|
||||
}
|
||||
|
||||
async createImageFromData(imageData) {
|
||||
return this.canvasIO.createImageFromData(imageData);
|
||||
}
|
||||
|
||||
async retryDataLoad(maxRetries = 3, delay = 1000) {
|
||||
return this.canvasIO.retryDataLoad(maxRetries, delay);
|
||||
}
|
||||
|
||||
async processMaskData(maskData) {
|
||||
return this.canvasIO.processMaskData(maskData);
|
||||
}
|
||||
|
||||
async loadImageFromCache(base64Data) {
|
||||
return this.canvasIO.loadImageFromCache(base64Data);
|
||||
}
|
||||
|
||||
async importImage(cacheData) {
|
||||
return this.canvasIO.importImage(cacheData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Importuje najnowszy obraz
|
||||
*/
|
||||
async importLatestImage() {
|
||||
return this.canvasIO.importLatestImage();
|
||||
}
|
||||
|
||||
showBlendModeMenu(x, y) {
|
||||
return this.canvasLayers.showBlendModeMenu(x, y);
|
||||
}
|
||||
|
||||
handleBlendModeSelection(mode) {
|
||||
return this.canvasLayers.handleBlendModeSelection(mode);
|
||||
}
|
||||
|
||||
showOpacitySlider(mode) {
|
||||
return this.canvasLayers.showOpacitySlider(mode);
|
||||
}
|
||||
// ==========================================
|
||||
// OPERACJE NA MASCE
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Zwiększa licznik operacji (wywoływane przy każdej operacji na canvas)
|
||||
* Uruchamia edytor masek
|
||||
*/
|
||||
incrementOperationCount() {
|
||||
if (this.imageReferenceManager) {
|
||||
this.imageReferenceManager.incrementOperationCount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ręczne uruchomienie garbage collection
|
||||
*/
|
||||
async runGarbageCollection() {
|
||||
if (this.imageReferenceManager) {
|
||||
await this.imageReferenceManager.manualGarbageCollection();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca statystyki garbage collection
|
||||
*/
|
||||
getGarbageCollectionStats() {
|
||||
if (this.imageReferenceManager) {
|
||||
const stats = this.imageReferenceManager.getStats();
|
||||
return {
|
||||
...stats,
|
||||
operationCount: this.imageReferenceManager.operationCount,
|
||||
operationThreshold: this.imageReferenceManager.operationThreshold
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ustawia próg operacji dla automatycznego GC
|
||||
*/
|
||||
setGarbageCollectionThreshold(threshold) {
|
||||
if (this.imageReferenceManager) {
|
||||
this.imageReferenceManager.setOperationThreshold(threshold);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Czyści zasoby canvas (wywoływane przy usuwaniu)
|
||||
*/
|
||||
destroy() {
|
||||
if (this.imageReferenceManager) {
|
||||
this.imageReferenceManager.destroy();
|
||||
}
|
||||
log.info("Canvas destroyed");
|
||||
}
|
||||
|
||||
async startMaskEditor() {
|
||||
const blob = await this.canvasLayers.getFlattenedCanvasAsBlob();
|
||||
if (!blob) {
|
||||
@@ -511,16 +257,133 @@ export class Canvas {
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// METODY POMOCNICZE
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Inicjalizuje podstawowe właściwości canvas
|
||||
*/
|
||||
initCanvas() {
|
||||
this.canvas.width = this.width;
|
||||
this.canvas.height = this.height;
|
||||
this.canvas.style.border = '1px solid black';
|
||||
this.canvas.style.maxWidth = '100%';
|
||||
this.canvas.style.backgroundColor = '#606060';
|
||||
this.canvas.style.width = '100%';
|
||||
this.canvas.style.height = '100%';
|
||||
this.canvas.tabIndex = 0;
|
||||
this.canvas.style.outline = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera współrzędne myszy w układzie świata
|
||||
* @param {MouseEvent} e - Zdarzenie myszy
|
||||
*/
|
||||
getMouseWorldCoordinates(e) {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
|
||||
const mouseX_DOM = e.clientX - rect.left;
|
||||
const mouseY_DOM = e.clientY - rect.top;
|
||||
|
||||
const scaleX = this.offscreenCanvas.width / rect.width;
|
||||
const scaleY = this.offscreenCanvas.height / rect.height;
|
||||
|
||||
const mouseX_Buffer = mouseX_DOM * scaleX;
|
||||
const mouseY_Buffer = mouseY_DOM * scaleY;
|
||||
|
||||
const worldX = (mouseX_Buffer / this.viewport.zoom) + this.viewport.x;
|
||||
const worldY = (mouseY_Buffer / this.viewport.zoom) + this.viewport.y;
|
||||
|
||||
return {x: worldX, y: worldY};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera współrzędne myszy w układzie widoku
|
||||
* @param {MouseEvent} e - Zdarzenie myszy
|
||||
*/
|
||||
getMouseViewCoordinates(e) {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
const mouseX_DOM = e.clientX - rect.left;
|
||||
const mouseY_DOM = e.clientY - rect.top;
|
||||
|
||||
const scaleX = this.canvas.width / rect.width;
|
||||
const scaleY = this.canvas.height / rect.height;
|
||||
|
||||
const mouseX_Canvas = mouseX_DOM * scaleX;
|
||||
const mouseY_Canvas = mouseY_DOM * scaleY;
|
||||
|
||||
return { x: mouseX_Canvas, y: mouseY_Canvas };
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualizuje zaznaczenie po operacji historii
|
||||
*/
|
||||
updateSelectionAfterHistory() {
|
||||
const newSelectedLayers = [];
|
||||
if (this.selectedLayers) {
|
||||
this.selectedLayers.forEach(sl => {
|
||||
const found = this.layers.find(l => l.id === sl.id);
|
||||
if (found) newSelectedLayers.push(found);
|
||||
});
|
||||
}
|
||||
this.updateSelection(newSelectedLayers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualizuje przyciski historii
|
||||
*/
|
||||
updateHistoryButtons() {
|
||||
if (this.onHistoryChange) {
|
||||
const historyInfo = this.canvasState.getHistoryInfo();
|
||||
this.onHistoryChange({
|
||||
canUndo: historyInfo.canUndo,
|
||||
canRedo: historyInfo.canRedo
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwiększa licznik operacji (dla garbage collection)
|
||||
*/
|
||||
incrementOperationCount() {
|
||||
if (this.imageReferenceManager) {
|
||||
this.imageReferenceManager.incrementOperationCount();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Czyści zasoby canvas
|
||||
*/
|
||||
destroy() {
|
||||
if (this.imageReferenceManager) {
|
||||
this.imageReferenceManager.destroy();
|
||||
}
|
||||
log.info("Canvas destroyed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Powiadamia o zmianie stanu
|
||||
* @private
|
||||
*/
|
||||
_notifyStateChange() {
|
||||
if (this.onStateChange) {
|
||||
this.onStateChange();
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// METODY DLA EDYTORA MASEK
|
||||
// ==========================================
|
||||
|
||||
waitWhileMaskEditing() {
|
||||
// Czekamy, aż edytor się pojawi, a potem zniknie.
|
||||
if (mask_editor_showing(app)) {
|
||||
this.editorWasShowing = true;
|
||||
}
|
||||
|
||||
if (!mask_editor_showing(app) && this.editorWasShowing) {
|
||||
// Edytor był widoczny i już go nie ma
|
||||
this.editorWasShowing = false;
|
||||
setTimeout(() => this.handleMaskEditorClose(), 100); // Dajemy chwilę na aktualizację
|
||||
setTimeout(() => this.handleMaskEditorClose(), 100);
|
||||
} else {
|
||||
setTimeout(this.waitWhileMaskEditing.bind(this), 100);
|
||||
}
|
||||
@@ -547,28 +410,22 @@ export class Canvas {
|
||||
return;
|
||||
}
|
||||
|
||||
// Używamy wymiarów naszego płótna, aby zapewnić spójność
|
||||
const tempCanvas = document.createElement('canvas');
|
||||
tempCanvas.width = this.width;
|
||||
tempCanvas.height = this.height;
|
||||
const tempCtx = tempCanvas.getContext('2d');
|
||||
|
||||
// Rysujemy obrazek z edytora, który zawiera maskę w kanale alfa
|
||||
tempCtx.drawImage(resultImage, 0, 0, this.width, this.height);
|
||||
|
||||
const imageData = tempCtx.getImageData(0, 0, this.width, this.height);
|
||||
const data = imageData.data;
|
||||
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
const originalAlpha = data[i + 3];
|
||||
|
||||
// Ustawiamy biały kolor
|
||||
data[i] = 255; // R
|
||||
data[i + 1] = 255; // G
|
||||
data[i + 2] = 255; // B
|
||||
|
||||
// Odwracamy kanał alfa
|
||||
data[i + 3] = 255 - originalAlpha;
|
||||
const originalAlpha = data[i + 3];
|
||||
data[i] = 255;
|
||||
data[i + 1] = 255;
|
||||
data[i + 2] = 255;
|
||||
data[i + 3] = 255 - originalAlpha;
|
||||
}
|
||||
|
||||
tempCtx.putImageData(imageData, 0, 0);
|
||||
@@ -577,19 +434,17 @@ export class Canvas {
|
||||
maskAsImage.src = tempCanvas.toDataURL();
|
||||
await new Promise(resolve => maskAsImage.onload = resolve);
|
||||
|
||||
// Łączymy nową maskę z istniejącą, zamiast ją nadpisywać
|
||||
const maskCtx = this.maskTool.maskCtx;
|
||||
const destX = -this.maskTool.x;
|
||||
const destY = -this.maskTool.y;
|
||||
|
||||
maskCtx.globalCompositeOperation = 'screen';
|
||||
maskCtx.drawImage(maskAsImage, destX, destY);
|
||||
maskCtx.globalCompositeOperation = 'source-over'; // Przywracamy domyślny tryb
|
||||
maskCtx.globalCompositeOperation = 'source-over';
|
||||
|
||||
this.render();
|
||||
this.saveState();
|
||||
|
||||
// Zaktualizuj podgląd węzła nowym, spłaszczonym obrazem
|
||||
const new_preview = new Image();
|
||||
const blob = await this.canvasLayers.getFlattenedCanvasAsBlob();
|
||||
if (blob) {
|
||||
@@ -602,4 +457,72 @@ export class Canvas {
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// METODY DELEGUJĄCE DLA KOMPATYBILNOŚCI
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Te metody są zachowane tymczasowo dla kompatybilności wstecznej.
|
||||
* W nowych implementacjach należy używać bezpośrednio odpowiednich modułów:
|
||||
* - this.canvasLayers dla operacji na warstwach
|
||||
* - this.canvasInteractions dla obsługi interakcji
|
||||
* - this.canvasIO dla operacji I/O
|
||||
* - this.canvasState dla zarządzania stanem
|
||||
*/
|
||||
|
||||
// Delegacje do CanvasState
|
||||
async saveStateToDB(immediate = false) { return this.canvasState.saveStateToDB(immediate); }
|
||||
|
||||
// Delegacje do CanvasLayers
|
||||
async copySelectedLayers() { return this.canvasLayers.copySelectedLayers(); }
|
||||
async handlePaste(addMode) { return this.canvasLayers.handlePaste(addMode); }
|
||||
async addLayerWithImage(image, layerProps = {}, addMode = 'default') {
|
||||
return this.canvasLayers.addLayerWithImage(image, layerProps, addMode);
|
||||
}
|
||||
moveLayerUp() { return this.canvasLayers.moveLayerUp(); }
|
||||
moveLayerDown() { return this.canvasLayers.moveLayerDown(); }
|
||||
resizeLayer(scale) {
|
||||
this.selectedLayers.forEach(layer => {
|
||||
layer.width *= scale;
|
||||
layer.height *= scale;
|
||||
});
|
||||
this.render();
|
||||
this.saveState();
|
||||
}
|
||||
rotateLayer(angle) {
|
||||
this.selectedLayers.forEach(layer => {
|
||||
layer.rotation += angle;
|
||||
});
|
||||
this.render();
|
||||
this.saveState();
|
||||
}
|
||||
getLayerAtPosition(worldX, worldY) { return this.canvasLayers.getLayerAtPosition(worldX, worldY); }
|
||||
getHandles(layer) { return this.canvasLayers.getHandles(layer); }
|
||||
getHandleAtPosition(worldX, worldY) { return this.canvasLayers.getHandleAtPosition(worldX, worldY); }
|
||||
async mirrorHorizontal() { return this.canvasLayers.mirrorHorizontal(); }
|
||||
async mirrorVertical() { return this.canvasLayers.mirrorVertical(); }
|
||||
async getLayerImageData(layer) { return this.canvasLayers.getLayerImageData(layer); }
|
||||
showBlendModeMenu(x, y) { return this.canvasLayers.showBlendModeMenu(x, y); }
|
||||
// Delegacje do CanvasInteractions
|
||||
handleMouseMove(e) { this.canvasInteractions.handleMouseMove(e); }
|
||||
|
||||
|
||||
// Delegacje do ImageReferenceManager
|
||||
async runGarbageCollection() {
|
||||
if (this.imageReferenceManager) {
|
||||
await this.imageReferenceManager.manualGarbageCollection();
|
||||
}
|
||||
}
|
||||
getGarbageCollectionStats() {
|
||||
if (this.imageReferenceManager) {
|
||||
const stats = this.imageReferenceManager.getStats();
|
||||
return {
|
||||
...stats,
|
||||
operationCount: this.imageReferenceManager.operationCount,
|
||||
operationThreshold: this.imageReferenceManager.operationThreshold
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,32 +162,6 @@ export class CanvasLayers {
|
||||
return this.addLayerWithImage(image);
|
||||
}
|
||||
|
||||
async removeLayer(index) {
|
||||
if (index >= 0 && index < this.canvasLayers.layers.length) {
|
||||
const layer = this.canvasLayers.layers[index];
|
||||
if (layer.imageId) {
|
||||
const isImageUsedElsewhere = this.canvasLayers.layers.some((l, i) => i !== index && l.imageId === layer.imageId);
|
||||
if (!isImageUsedElsewhere) {
|
||||
await removeImage(layer.imageId);
|
||||
this.canvasLayers.imageCache.delete(layer.imageId);
|
||||
}
|
||||
}
|
||||
this.canvasLayers.layers.splice(index, 1);
|
||||
this.canvasLayers.selectedLayer = this.canvasLayers.layers[this.canvasLayers.layers.length - 1] || null;
|
||||
this.canvasLayers.render();
|
||||
this.canvasLayers.saveState();
|
||||
}
|
||||
}
|
||||
|
||||
moveLayer(fromIndex, toIndex) {
|
||||
if (fromIndex >= 0 && fromIndex < this.canvasLayers.layers.length &&
|
||||
toIndex >= 0 && toIndex < this.canvasLayers.layers.length) {
|
||||
const layer = this.canvasLayers.layers.splice(fromIndex, 1)[0];
|
||||
this.canvasLayers.layers.splice(toIndex, 0, layer);
|
||||
this.canvasLayers.render();
|
||||
}
|
||||
}
|
||||
|
||||
moveLayerUp() {
|
||||
if (this.canvasLayers.selectedLayers.length === 0) return;
|
||||
const selectedIndicesSet = new Set(this.canvasLayers.selectedLayers.map(layer => this.canvasLayers.layers.indexOf(layer)));
|
||||
@@ -361,33 +335,6 @@ export class CanvasLayers {
|
||||
}
|
||||
}
|
||||
|
||||
addMattedLayer(image, mask) {
|
||||
const layer = {
|
||||
image: image,
|
||||
mask: mask,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
rotation: 0,
|
||||
zIndex: this.canvasLayers.layers.length
|
||||
};
|
||||
|
||||
this.canvasLayers.layers.push(layer);
|
||||
this.canvasLayers.selectedLayer = layer;
|
||||
this.canvasLayers.render();
|
||||
}
|
||||
|
||||
isRotationHandle(x, y) {
|
||||
if (!this.canvasLayers.selectedLayer) return false;
|
||||
|
||||
const handleX = this.canvasLayers.selectedLayer.x + this.canvasLayers.selectedLayer.width / 2;
|
||||
const handleY = this.canvasLayers.selectedLayer.y - 20;
|
||||
const handleRadius = 5;
|
||||
|
||||
return Math.sqrt(Math.pow(x - handleX, 2) + Math.pow(y - handleY, 2)) <= handleRadius;
|
||||
}
|
||||
|
||||
getHandles(layer) {
|
||||
if (!layer) return {};
|
||||
|
||||
@@ -442,34 +389,6 @@ export class CanvasLayers {
|
||||
return null;
|
||||
}
|
||||
|
||||
getResizeHandle(x, y) {
|
||||
if (!this.canvasLayers.selectedLayer) return null;
|
||||
|
||||
const handleRadius = 5;
|
||||
const handles = {
|
||||
'nw': {x: this.canvasLayers.selectedLayer.x, y: this.canvasLayers.selectedLayer.y},
|
||||
'ne': {
|
||||
x: this.canvasLayers.selectedLayer.x + this.canvasLayers.selectedLayer.width,
|
||||
y: this.canvasLayers.selectedLayer.y
|
||||
},
|
||||
'se': {
|
||||
x: this.canvasLayers.selectedLayer.x + this.canvasLayers.selectedLayer.width,
|
||||
y: this.canvasLayers.selectedLayer.y + this.canvasLayers.selectedLayer.height
|
||||
},
|
||||
'sw': {
|
||||
x: this.canvasLayers.selectedLayer.x,
|
||||
y: this.canvasLayers.selectedLayer.y + this.canvasLayers.selectedLayer.height
|
||||
}
|
||||
};
|
||||
|
||||
for (const [position, point] of Object.entries(handles)) {
|
||||
if (Math.sqrt(Math.pow(x - point.x, 2) + Math.pow(y - point.y, 2)) <= handleRadius) {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
showBlendModeMenu(x, y) {
|
||||
this.closeBlendModeMenu();
|
||||
|
||||
@@ -591,17 +510,6 @@ export class CanvasLayers {
|
||||
}
|
||||
}
|
||||
|
||||
handleBlendModeSelection(mode) {
|
||||
if (this.selectedBlendMode === mode && !this.isAdjustingOpacity) {
|
||||
this.applyBlendMode(mode, this.blendOpacity);
|
||||
this.closeBlendModeMenu();
|
||||
} else {
|
||||
this.selectedBlendMode = mode;
|
||||
this.isAdjustingOpacity = true;
|
||||
this.showOpacitySlider(mode);
|
||||
}
|
||||
}
|
||||
|
||||
showOpacitySlider(mode) {
|
||||
const slider = document.createElement('input');
|
||||
slider.type = 'range';
|
||||
@@ -734,36 +642,4 @@ export class CanvasLayers {
|
||||
}, 'image/png');
|
||||
});
|
||||
}
|
||||
|
||||
async getFlattenedCanvasAsDataURL() {
|
||||
if (this.canvasLayers.layers.length === 0) return null;
|
||||
|
||||
const tempCanvas = document.createElement('canvas');
|
||||
tempCanvas.width = this.canvasLayers.width;
|
||||
tempCanvas.height = this.canvasLayers.height;
|
||||
const tempCtx = tempCanvas.getContext('2d');
|
||||
|
||||
const sortedLayers = [...this.canvasLayers.layers].sort((a, b) => a.zIndex - b.zIndex);
|
||||
sortedLayers.forEach(layer => {
|
||||
if (!layer.image) return;
|
||||
|
||||
tempCtx.save();
|
||||
tempCtx.globalCompositeOperation = layer.blendMode || 'normal';
|
||||
tempCtx.globalAlpha = layer.opacity !== undefined ? layer.opacity : 1;
|
||||
const centerX = layer.x + layer.width / 2;
|
||||
const centerY = layer.y + layer.height / 2;
|
||||
tempCtx.translate(centerX, centerY);
|
||||
tempCtx.rotate(layer.rotation * Math.PI / 180);
|
||||
tempCtx.drawImage(
|
||||
layer.image,
|
||||
-layer.width / 2,
|
||||
-layer.height / 2,
|
||||
layer.width,
|
||||
layer.height
|
||||
);
|
||||
tempCtx.restore();
|
||||
});
|
||||
|
||||
return tempCanvas.toDataURL('image/png');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user