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:
Dariusz L
2025-06-29 04:41:48 +02:00
parent 7a7c8f2295
commit 9dcf38b36d
2 changed files with 290 additions and 491 deletions

View File

@@ -13,6 +13,14 @@ import { mask_editor_showing } from "./utils/mask_utils.js";
const log = createModuleLogger('Canvas'); 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 { export class Canvas {
constructor(node, widget, callbacks = {}) { constructor(node, widget, callbacks = {}) {
this.node = node; this.node = node;
@@ -41,187 +49,115 @@ export class Canvas {
this.dataInitialized = false; this.dataInitialized = false;
this.pendingDataCheck = null; 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.maskTool = new MaskTool(this, {onStateChange: this.onStateChange});
this.initCanvas();
this.canvasState = new CanvasState(this); this.canvasState = new CanvasState(this);
this.canvasInteractions = new CanvasInteractions(this); this.canvasInteractions = new CanvasInteractions(this);
this.canvasLayers = new CanvasLayers(this); this.canvasLayers = new CanvasLayers(this);
this.canvasRenderer = new CanvasRenderer(this); this.canvasRenderer = new CanvasRenderer(this);
this.canvasIO = new CanvasIO(this); this.canvasIO = new CanvasIO(this);
this.imageReferenceManager = new ImageReferenceManager(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 => ({ this.layers = this.layers.map(layer => ({
...layer, ...layer,
opacity: 1 opacity: 1
})); }));
this.imageCache = new Map();
} }
async loadStateFromDB() { // ==========================================
return this.canvasState.loadStateFromDB(); // GŁÓWNE OPERACJE FASADY
} // ==========================================
async saveStateToDB(immediate = false) {
return this.canvasState.saveStateToDB(immediate);
}
/**
* Ładuje stan canvas z bazy danych
*/
async loadInitialState() { async loadInitialState() {
log.info("Loading initial state for node:", this.node.id); log.info("Loading initial state for node:", this.node.id);
const loaded = await this.loadStateFromDB(); const loaded = await this.canvasState.loadStateFromDB();
if (!loaded) { if (!loaded) {
log.info("No saved state found, initializing from node data."); log.info("No saved state found, initializing from node data.");
await this.initNodeData(); await this.canvasIO.initNodeData();
} }
this.saveState(); this.saveState();
this.render(); this.render();
} }
_notifyStateChange() { /**
if (this.onStateChange) { * Zapisuje obecny stan
this.onStateChange(); * @param {boolean} replaceLast - Czy zastąpić ostatni stan w historii
} */
}
saveState(replaceLast = false) { saveState(replaceLast = false) {
this.canvasState.saveState(replaceLast); this.canvasState.saveState(replaceLast);
this.incrementOperationCount(); this.incrementOperationCount();
this._notifyStateChange(); this._notifyStateChange();
} }
/**
* Cofnij ostatnią operację
*/
undo() { undo() {
this.canvasState.undo(); this.canvasState.undo();
this.incrementOperationCount(); this.incrementOperationCount();
this._notifyStateChange(); this._notifyStateChange();
} }
/**
* Ponów cofniętą operację
*/
redo() { redo() {
this.canvasState.redo(); this.canvasState.redo();
this.incrementOperationCount(); this.incrementOperationCount();
this._notifyStateChange(); this._notifyStateChange();
} }
updateSelectionAfterHistory() { /**
const newSelectedLayers = []; * Renderuje canvas
if (this.selectedLayers) { */
this.selectedLayers.forEach(sl => { render() {
const found = this.layers.find(l => l.id === sl.id); this.canvasRenderer.render();
if (found) newSelectedLayers.push(found);
});
}
this.updateSelection(newSelectedLayers);
} }
updateHistoryButtons() { /**
if (this.onHistoryChange) { * Dodaje warstwę z obrazem
const historyInfo = this.canvasState.getHistoryInfo(); * @param {Image} image - Obraz do dodania
this.onHistoryChange({ * @param {Object} layerProps - Właściwości warstwy
canUndo: historyInfo.canUndo, * @param {string} addMode - Tryb dodawania
canRedo: historyInfo.canRedo */
}); async addLayer(image, layerProps = {}, addMode = 'default') {
}
}
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') {
return this.canvasLayers.addLayerWithImage(image, layerProps, addMode); return this.canvasLayers.addLayerWithImage(image, layerProps, addMode);
} }
/**
async addLayer(image, addMode = 'default') { * Usuwa wybrane warstwy
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();
}
}
removeSelectedLayers() { removeSelectedLayers() {
if (this.selectedLayers.length > 0) { if (this.selectedLayers.length > 0) {
this.saveState(); this.saveState();
@@ -232,239 +168,49 @@ export class Canvas {
} }
} }
getMouseWorldCoordinates(e) { /**
const rect = this.canvas.getBoundingClientRect(); * Aktualizuje zaznaczenie warstw
* @param {Array} newSelection - Nowa lista zaznaczonych warstw
const mouseX_DOM = e.clientX - rect.left; */
const mouseY_DOM = e.clientY - rect.top; updateSelection(newSelection) {
this.selectedLayers = newSelection || [];
const scaleX = this.offscreenCanvas.width / rect.width; this.selectedLayer = this.selectedLayers.length > 0 ? this.selectedLayers[this.selectedLayers.length - 1] : null;
const scaleY = this.offscreenCanvas.height / rect.height; if (this.onSelectionChange) {
this.onSelectionChange();
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();
} }
/**
* 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) { updateOutputAreaSize(width, height, saveHistory = true) {
return this.canvasLayers.updateOutputAreaSize(width, height, saveHistory); return this.canvasLayers.updateOutputAreaSize(width, height, saveHistory);
} }
render() { /**
this.canvasRenderer.render(); * Eksportuje spłaszczony canvas jako blob
} */
getHandles(layer) {
return this.canvasLayers.getHandles(layer);
}
getHandleAtPosition(worldX, worldY) {
return this.canvasLayers.getHandleAtPosition(worldX, worldY);
}
async getFlattenedCanvasAsBlob() { async getFlattenedCanvasAsBlob() {
return this.canvasLayers.getFlattenedCanvasAsBlob(); return this.canvasLayers.getFlattenedCanvasAsBlob();
} }
async getFlattenedSelectionAsBlob() { /**
return this.canvasLayers.getFlattenedSelectionAsBlob(); * Importuje najnowszy obraz
} */
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);
}
async importLatestImage() { async importLatestImage() {
return this.canvasIO.importLatestImage(); return this.canvasIO.importLatestImage();
} }
showBlendModeMenu(x, y) { // ==========================================
return this.canvasLayers.showBlendModeMenu(x, y); // OPERACJE NA MASCE
} // ==========================================
handleBlendModeSelection(mode) {
return this.canvasLayers.handleBlendModeSelection(mode);
}
showOpacitySlider(mode) {
return this.canvasLayers.showOpacitySlider(mode);
}
/** /**
* 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() { async startMaskEditor() {
const blob = await this.canvasLayers.getFlattenedCanvasAsBlob(); const blob = await this.canvasLayers.getFlattenedCanvasAsBlob();
if (!blob) { 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() { waitWhileMaskEditing() {
// Czekamy, aż edytor się pojawi, a potem zniknie.
if (mask_editor_showing(app)) { if (mask_editor_showing(app)) {
this.editorWasShowing = true; this.editorWasShowing = true;
} }
if (!mask_editor_showing(app) && this.editorWasShowing) { if (!mask_editor_showing(app) && this.editorWasShowing) {
// Edytor był widoczny i już go nie ma
this.editorWasShowing = false; this.editorWasShowing = false;
setTimeout(() => this.handleMaskEditorClose(), 100); // Dajemy chwilę na aktualizację setTimeout(() => this.handleMaskEditorClose(), 100);
} else { } else {
setTimeout(this.waitWhileMaskEditing.bind(this), 100); setTimeout(this.waitWhileMaskEditing.bind(this), 100);
} }
@@ -547,13 +410,11 @@ export class Canvas {
return; return;
} }
// Używamy wymiarów naszego płótna, aby zapewnić spójność
const tempCanvas = document.createElement('canvas'); const tempCanvas = document.createElement('canvas');
tempCanvas.width = this.width; tempCanvas.width = this.width;
tempCanvas.height = this.height; tempCanvas.height = this.height;
const tempCtx = tempCanvas.getContext('2d'); 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); tempCtx.drawImage(resultImage, 0, 0, this.width, this.height);
const imageData = tempCtx.getImageData(0, 0, this.width, this.height); const imageData = tempCtx.getImageData(0, 0, this.width, this.height);
@@ -561,13 +422,9 @@ export class Canvas {
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];
data[i] = 255;
// Ustawiamy biały kolor data[i + 1] = 255;
data[i] = 255; // R data[i + 2] = 255;
data[i + 1] = 255; // G
data[i + 2] = 255; // B
// Odwracamy kanał alfa
data[i + 3] = 255 - originalAlpha; data[i + 3] = 255 - originalAlpha;
} }
@@ -577,19 +434,17 @@ export class Canvas {
maskAsImage.src = tempCanvas.toDataURL(); maskAsImage.src = tempCanvas.toDataURL();
await new Promise(resolve => maskAsImage.onload = resolve); await new Promise(resolve => maskAsImage.onload = resolve);
// Łączymy nową maskę z istniejącą, zamiast ją nadpisywać
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;
maskCtx.globalCompositeOperation = 'screen'; maskCtx.globalCompositeOperation = 'screen';
maskCtx.drawImage(maskAsImage, destX, destY); maskCtx.drawImage(maskAsImage, destX, destY);
maskCtx.globalCompositeOperation = 'source-over'; // Przywracamy domyślny tryb maskCtx.globalCompositeOperation = 'source-over';
this.render(); this.render();
this.saveState(); this.saveState();
// Zaktualizuj podgląd węzła nowym, spłaszczonym obrazem
const new_preview = new Image(); const new_preview = new Image();
const blob = await this.canvasLayers.getFlattenedCanvasAsBlob(); const blob = await this.canvasLayers.getFlattenedCanvasAsBlob();
if (blob) { if (blob) {
@@ -602,4 +457,72 @@ export class Canvas {
this.render(); 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;
}
} }

View File

@@ -162,32 +162,6 @@ export class CanvasLayers {
return this.addLayerWithImage(image); 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() { moveLayerUp() {
if (this.canvasLayers.selectedLayers.length === 0) return; if (this.canvasLayers.selectedLayers.length === 0) return;
const selectedIndicesSet = new Set(this.canvasLayers.selectedLayers.map(layer => this.canvasLayers.layers.indexOf(layer))); 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) { getHandles(layer) {
if (!layer) return {}; if (!layer) return {};
@@ -442,34 +389,6 @@ export class CanvasLayers {
return null; 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) { showBlendModeMenu(x, y) {
this.closeBlendModeMenu(); 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) { showOpacitySlider(mode) {
const slider = document.createElement('input'); const slider = document.createElement('input');
slider.type = 'range'; slider.type = 'range';
@@ -734,36 +642,4 @@ export class CanvasLayers {
}, 'image/png'); }, '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');
}
} }