diff --git a/__init__.py b/__init__.py index b680338..1e45bc8 100644 --- a/__init__.py +++ b/__init__.py @@ -38,7 +38,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {} APP_CONFIGS: List[AppConfig] = [] APP_NAME: str = "Flow" -APP_VERSION: str = "0.4.3" +APP_VERSION: str = "0.4.4" PURPLE = "\033[38;5;129m" RESET = "\033[0m" FLOWMSG = f"{PURPLE}Flow{RESET}" diff --git a/pyproject.toml b/pyproject.toml index 8cf9013..bba3c6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-disty-flow" description = "Flow is a custom node designed to provide a more user-friendly interface for ComfyUI by acting as an alternative user interface for running workflows. It is not a replacement for workflow creation.\nFlow is currently in the early stages of development, so expect bugs and ongoing feature enhancements. With your support and feedback, Flow will settle into a steady stream." -version = "0.4.3" +version = "0.4.4" license = {file = "LICENSE"} [project.urls] diff --git a/web/core/css/main.css b/web/core/css/main.css index 2612a87..0b65ae5 100644 --- a/web/core/css/main.css +++ b/web/core/css/main.css @@ -26,6 +26,8 @@ --color-button-primary-text: #f4b6ff; --color-button-primary-text-hover: #131312; --color-button-primary-active: #8621b7; + --color-button-primary-active-strong: #c96af8; + --color-button-primary-text-active: #8621b7; --color-button-secondary: #131312; --color-button-secondary-hover: #8621b7; @@ -702,7 +704,16 @@ input[type="number"] { border-bottom: 1px solid var(--color-border); } - +.dropdown-stepper-container { + display: flex; + flex-direction: column; + background-color: var(--color-background-secondary); + color: var(--color-primary-text); + width: 100%; + margin-bottom: 4px; + padding: 5px; + font-weight: bold; +} select { width: 100%; padding: 8px; @@ -806,7 +817,7 @@ button { border: none; background-color: var(--color-background-secondary); font-size: 1.2em; - color: var(--color-secondary); + color: var(--color-button-secondary-text); font-weight: bold; cursor: pointer; transition: background-color 0.3s ease, color 0.3s ease; @@ -1857,6 +1868,7 @@ html:not(.css-loading) body { display: flex; flex-direction: column; align-items: stretch; + padding: 6px; border: 1px dashed var(--color-border); } @@ -1886,7 +1898,7 @@ html:not(.css-loading) body { display: none; } .cbp-brush-ui-content { - padding: 10px; + padding:10px 0 ; display: flex; flex-direction: column; gap: 10px; @@ -2014,46 +2026,106 @@ html:not(.css-loading) body { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } - + .mbp-container { - /* --mbp-primary: var(--color-button-primary); - --mbp-primary-hover: var(--color-button-primary-hover); - --mbp-bg: var(--color-background-secondary); - --mbp-border: var(--color-border); - --mbp-text: var(--color-primary-text); - --mbp-icon: var(--color-primary); */ - padding: 0.5rem; + /* --mbp-primary: var(--color-button-primary); + --mbp-primary-hover: var(--color-button-primary-hover); + --mbp-bg: var(--color-background-secondary); + --mbp-border: var(--color-border); + --mbp-text: var(--color-primary-text); + --mbp-icon: var(--color-primary); */ + padding: 1rem; + } + .mbp-config-section { + /* padding: 0.5rem; */ + /* border: solid 1px var(--color-button-primary-hover); */ + /* background-color: var(--color-background-secondary); */ + } + .mbp-config-item-buttons-group{ + /* background: red; */ + display: flex; + align-items: center; + flex-direction: row; + color: var(--color-button-secondary-text); + + } + .mbp-config-item { + display: flex; + align-items: center; + /* flex-direction: column; */ + justify-content: space-between; + margin-bottom: 10px; + /* border: 1px dashed var(--color-border); */ + padding: 2px; + background-color: var(--color-background-secondary); + width: 100%; + border: none; + + } + .mbp-config-item #DecrementButton,.mbp-config-item #IncrementButton{ + /* background-color: #570d7b; */ + flex: 1; + color: var(--color-button-secondary-text); + } + .mbp-config-item-saving { + display: flex; + align-items: center; + flex-direction: column; + border: none; + + } + + .mbp-config-item-saving label { + margin-bottom: 0.3rem; + + } + .mbp-config-item .maskConfigInput{ + /* background-color: #570d7b; */ + width: 100%; + flex: 2; + text-align: center; + border: none; + + } + .mbp-config-item-label{ + margin: 0 6px; + } .mbp-header { - display: flex; - gap: 0.5rem; - margin-bottom: 0.75rem; + display: flex; + gap: 0.5rem; + margin-bottom: 0.75rem; } .mbp-header .mbp-select { - flex: 1; + flex: 1; } .mbp-button { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.5rem; - border: 1px solid var(--mbp-border); - background: var(--color-button-primary) ; - /* border-radius: 0.375rem; */ - cursor: pointer; - transition: all 0.2s; - color: var(--mbp-icon); - min-width: 36px; - height: 36px; - width: 100%; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5rem; + /* border: 1px solid var(--mbp-border); */ + background: var(--color-button-primary) ; + cursor: pointer; + transition: all 0.2s; + color: var(--color-button-secondary-text); + min-width: 36px; + height: 36px; + width: 100%; + } + .mbp-button.active { + border: dashed 1px var(--color-button-primary-text); } .mbp-button:hover { - background: var(--color-button-primary-hover); - color: var(--mbp-primary); + background: var(--color-button-primary-hover); + color: var(--mbp-primary); + } + #saveMaskBtn{ + } .mbp-button svg { @@ -2080,23 +2152,24 @@ html:not(.css-loading) body { } .mbp-select { - padding: 0.5rem; - border: none; - background: var(--color-background); - color: var(--mbp-text); - cursor: pointer; - appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%236b7280'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 0.5rem center; - background-size: 1.25rem; - padding-right: 2.5rem; - height: 36px; - outline: none; + padding: 0.5rem; + background: var(--color-background); + color: var(--mbp-text); + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%236b7280'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.5rem center; + background-size: 1.25rem; + padding-right: 2.5rem; + height: 36px; + outline: none; + border: none; + } .mbp-select:hover { - /* background: var(--color-button-primary-hover); */ - border-top: solid 1px var(--color-button-primary-hover); + /* background: var(--color-button-primary-hover); */ + /* border-top: solid 1px var(--color-button-primary-hover); */ } .mbp-button-group { @@ -2110,24 +2183,29 @@ html:not(.css-loading) body { align-items: center; justify-content: space-between; margin-bottom: 10px; - border: 1px dashed var(--color-border); + /* border: 1px dashed var(--color-border); */ padding: 2px; + background: var(--color-background-secondary); + } + .mbp-toggle { position: relative; display: inline-block; width: 40px; height: 100%; - border-left: 1px dashed var(--color-border); + /* border: 1px solid var(--color-border); */ + background: var(--color-background-secondary); align-items: center; - - + padding: 5px; } + .mbp-toggle input { opacity: 0; width: 0; height: 0; } + .mbp-toggle-slider { position: absolute; cursor: pointer; @@ -2139,30 +2217,36 @@ html:not(.css-loading) body { /* background-color: var(--color-background-secondary); */ transition: .4s; } + .mbp-toggle-slider:before { position: absolute; content: ""; height: 20px; width: 20px; - left: 2px; - bottom: 0px; + left: -2px; + bottom: 5px; background-color: var(--color-button-secondary); transition: .4s; } + .mbp-toggle-slider:after { position: absolute; content: ""; height: 20px; width: 20px; - left: 2px; - bottom: 0px; + left: -2px; + bottom: 5px; background-color: var(--color-button-primary); transition: .4s; + border-radius: 45px; + } + .mbp-toggle input:checked + .mbp-toggle-slider { /* background-color: var(--color-button-secondary-active); */ } + .mbp-toggle input:checked + .mbp-toggle-slider:before { transform: translateX(20px); } @@ -2170,25 +2254,29 @@ html:not(.css-loading) body { .mbp-toggle input:checked + .mbp-toggle-slider:after { transform: translateX(20px); - background-color: var(--color-button-secondary-active); - + background-color: var(--color-button-primary-text); } .mbp-label { - font-size: 0.75rem; - color: var(--mbp-text); - user-select: none; - padding: 0 6px; + /* font-size: 0.75rem; */ + color: var(--mbp-text); + user-select: none; + padding: 0 6px; } .mbp-button-group { - display: flex; - gap: 0.5rem; - margin: 0.75rem 0; - + display: flex; + gap: 0.5rem; + margin: 0.75rem 0; } - .upper-canvas, .lower-canvas { pointer-events: auto !important; } - \ No newline at end of file + /* .mbp-config-section{ + display: flex; + flex-direction: column; + gap: 10px; + padding: 10px; + border: 1px dashed var(--color-border); + background-color: var(--color-background-secondary); + } */ \ No newline at end of file diff --git a/web/core/js/common/components/DataComponent.js b/web/core/js/common/components/DataComponent.js index 464517c..8f63038 100644 --- a/web/core/js/common/components/DataComponent.js +++ b/web/core/js/common/components/DataComponent.js @@ -2,6 +2,7 @@ import { store } from '../scripts/stateManagerMain.js'; import { updateWorkflow } from './workflowManager.js'; function getNestedValue(obj, path) { + // console.log('getNestedValue', obj, path); return path.split('.').reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, obj); } @@ -25,7 +26,7 @@ class DataComponent { const data = getNestedValue(state, this.dataPath); if (data === undefined) { - console.warn(`DataComponent [${this.id}]: No data found at path "${this.dataPath}" in the store.`); + // console.warn(`DataComponent [${this.id}]: No data found at path "${this.dataPath}" in the store.`); return; } diff --git a/web/core/js/common/components/LoraWorkflowManager.js b/web/core/js/common/components/LoraWorkflowManager.js index db595b9..34fc891 100644 --- a/web/core/js/common/components/LoraWorkflowManager.js +++ b/web/core/js/common/components/LoraWorkflowManager.js @@ -3,6 +3,7 @@ import WorkflowNodeAdder from './WorkflowNodeAdder.js'; import DropdownStepper from './DropdownStepper.js'; class LoraWorkflowManager { + constructor(workflow, flowConfig) { this.workflowManager = new WorkflowNodeAdder(workflow); this.flowConfig = flowConfig; @@ -14,26 +15,33 @@ class LoraWorkflowManager { initializeUI() { this.addButton = document.createElement('button'); this.addButton.textContent = '+LoRA'; - this.addButton.classList.add('add-lora-button'); + this.addButton.classList.add('add-lora-button'); this.addButton.style.marginBottom = '5px'; - this.container.appendChild(this.addButton); + this.container.appendChild(this.addButton); this.addButton.addEventListener('click', () => this.handleAddLora()); } handleAddLora() { try { - const newNodeId = this.workflowManager.addLora(); + const modelLoaders = this.workflowManager._findModelLoaders(); + if (modelLoaders.length === 0) { + throw new Error('No model loader found in the workflow to attach LoRA.'); + } + const targetModelLoader = modelLoaders[0]; + const newNodeId = this.workflowManager.addLora(targetModelLoader.id); + const updatedWorkflow = this.workflowManager.getWorkflow(); const dynamicConfig = this.createDynamicConfig(newNodeId); - const loraContainer = document.createElement('div'); loraContainer.id = dynamicConfig.id; loraContainer.classList.add('dropdown-stepper-container'); this.container.appendChild(loraContainer); - new DropdownStepper(dynamicConfig, this.workflowManager.getWorkflow()); + new DropdownStepper(dynamicConfig, updatedWorkflow); + // console.log(`LoRA node ${newNodeId} added successfully to model loader ${targetModelLoader.id}.`); } catch (error) { console.error('Error adding LoRA:', error); + alert(`Failed to add LoRA: ${error.message}`); } } @@ -62,9 +70,9 @@ class LoraWorkflowManager { ] }; } - getWorkflow() { return this.workflowManager.getWorkflow(); } } + export default LoraWorkflowManager; diff --git a/web/core/js/common/components/WorkflowNodeAdder.js b/web/core/js/common/components/WorkflowNodeAdder.js index ba2ecb8..1ad1950 100644 --- a/web/core/js/common/components/WorkflowNodeAdder.js +++ b/web/core/js/common/components/WorkflowNodeAdder.js @@ -3,40 +3,53 @@ class WorkflowNodeAdder { if (typeof workflow !== 'object' || workflow === null || Array.isArray(workflow)) { throw new TypeError('Workflow must be a non-null object'); } - this.workflow = { ...workflow }; + this.workflow = JSON.parse(JSON.stringify(workflow)); this.existingIds = new Set(Object.keys(this.workflow).map(id => parseInt(id, 10))); this.highestId = this._getHighestNodeId(); - this.loraCount = this._countExistingLoras(); } - addLora() { + addLora(modelLoaderId) { + if (!this.workflow[modelLoaderId]) { + throw new Error(`Model loader node with ID ${modelLoaderId} does not exist.`); + } + + const modelLoader = this.workflow[modelLoaderId]; + if (!this._isModelLoader(modelLoader.class_type)) { + throw new Error(`Node ID ${modelLoaderId} is not a recognized model loader.`); + } + const newLoraId = this._getNextNodeId(); const loraNode = this._createLoraNode(newLoraId); - const existingLoras = this._findLoraNodes(); - + const existingLoras = this._findLoraNodes(modelLoaderId); if (existingLoras.length === 0) { - const modelLoaders = this._findModelLoaders(); - if (modelLoaders.length === 0) { - throw new Error('No model loader found in the workflow to attach LoRA'); + const firstConnectedNodes = this._findConnectedNodes(modelLoaderId); + if (firstConnectedNodes.length === 0) { + throw new Error(`No nodes are directly connected to model loader ID ${modelLoaderId}.`); } - modelLoaders.forEach(loader => { - const originalModelInput = loader.inputs.model; - loader.inputs.model = [newLoraId.toString(), 0]; - loraNode.inputs.model = originalModelInput; + firstConnectedNodes.forEach(node => { + node.inputs.model = [newLoraId.toString(), 0]; }); + + loraNode.inputs.model = [modelLoaderId.toString(), 0]; } else { - const lastLora = existingLoras[existingLoras.length - 1]; - const originalModelInput = lastLora.inputs.model; - lastLora.inputs.model = [newLoraId.toString(), 0]; - loraNode.inputs.model = originalModelInput; + const lastLora = this._getLastLoraNode(existingLoras); + const firstConnectedNodes = this._findConnectedNodes(lastLora.id); + if (firstConnectedNodes.length === 0) { + throw new Error(`No nodes are directly connected to the last LoRA node ID ${lastLora.id}.`); + } + + firstConnectedNodes.forEach(node => { + node.inputs.model = [newLoraId.toString(), 0]; + }); + + loraNode.inputs.model = [lastLora.id.toString(), 0]; } this.workflow[newLoraId.toString()] = loraNode; this.existingIds.add(newLoraId); this.highestId = newLoraId; - this.loraCount += 1; return newLoraId; } @@ -59,22 +72,44 @@ class WorkflowNodeAdder { }; } - _findLoraNodes() { + _findLoraNodes(modelLoaderId) { return Object.entries(this.workflow) .filter(([_, node]) => node.class_type === "LoraLoaderModelOnly") - .map(([id, node]) => ({ id: parseInt(id, 10), ...node })); + .map(([id, node]) => ({ id: parseInt(id, 10), ...node })) + .filter(lora => { + const modelInput = lora.inputs.model; + return Array.isArray(modelInput) && parseInt(modelInput[0], 10) === modelLoaderId; + }); } _findModelLoaders() { - const modelLoaders = []; + return Object.entries(this.workflow) + .filter(([_, node]) => { + const hasModelInput = node.inputs && node.inputs.model !== undefined; + return !hasModelInput && this._isModelLoader(node.class_type); + }) + .map(([id, node]) => ({ id: parseInt(id, 10), ...node })); + } - Object.entries(this.workflow).forEach(([id, node]) => { - if (node.inputs && Array.isArray(node.inputs.model) && node.inputs.model.length === 2) { - modelLoaders.push({ id: parseInt(id, 10), ...node }); - } - }); + _isModelLoader(classType) { + const modelLoaderTypes = ["UNETLoader","CheckpointLoaderSimple","DownloadAndLoadMochiModel,UnetLoaderGGUF"]; + return modelLoaderTypes.includes(classType); + } - return modelLoaders; + _findConnectedNodes(nodeId) { + return Object.entries(this.workflow) + .filter(([_, node]) => { + if (!node.inputs || !node.inputs.model) return false; + const modelInput = node.inputs.model; + return Array.isArray(modelInput) && parseInt(modelInput[0], 10) === nodeId; + }) + .map(([id, node]) => ({ id: parseInt(id, 10), ...node })); + } + + _getLastLoraNode(loraNodes) { + return loraNodes.reduce((prev, current) => { + return (prev.id > current.id) ? prev : current; + }, loraNodes[0]); } _getNextNodeId() { @@ -82,11 +117,7 @@ class WorkflowNodeAdder { } _getHighestNodeId() { - return Math.max(...this.existingIds, 0); - } - - _countExistingLoras() { - return this._findLoraNodes().length; + return this.existingIds.size > 0 ? Math.max(...this.existingIds) : 0; } } diff --git a/web/core/js/common/components/canvas/CanvasControlsPlugin.js b/web/core/js/common/components/canvas/CanvasControlsPlugin.js index 1121b02..a30dc18 100644 --- a/web/core/js/common/components/canvas/CanvasControlsPlugin.js +++ b/web/core/js/common/components/canvas/CanvasControlsPlugin.js @@ -1,5 +1,4 @@ import { CanvasPlugin } from './CanvasPlugin.js'; - export class CanvasControlsPlugin extends CanvasPlugin { constructor(options = {}) { super('CanvasControlsPlugin'); @@ -239,7 +238,9 @@ export class CanvasControlsPlugin extends CanvasPlugin { const evt = opt.e; this.lastPosX = evt.clientX; this.lastPosY = evt.clientY; - this.canvas.setCursor('grabbing'); + // this.canvas.setCursor(`url(${this.getPanIcon()}), auto`); + this.canvas.setCursor('move'); + evt.preventDefault(); evt.stopPropagation(); } @@ -272,28 +273,27 @@ export class CanvasControlsPlugin extends CanvasPlugin { this.canvas.requestRenderAll(); this.lastPosX = e.clientX; this.lastPosY = e.clientY; + this.canvas.setCursor('move'); + // this.canvas.setCursor(`url(${this.getPanIcon()}), auto`); } + } onMouseUp(opt) { if (this.isPanning) { this.isPanning = false; - this.canvas.setCursor(this.isPanMode || this.isAltPan ? 'grab' : 'default'); + this.canvas.setCursor(this.isPanMode || this.isAltPan ? 'default' : 'default'); } } togglePanMode() { this.isPanMode = !this.isPanMode; this.updatePanButtonState(); - this.canvasManager.emit('pan:toggled'); - - // if (this.isPanMode) { - // this.canvas.setCursor('grab'); - // this.canvasManager.emit('pan:activated'); - - // } else { - // this.canvas.setCursor('default'); - // this.canvasManager.emit('pan:deactivated'); - // } + // this.canvasManager.emit('pan:toggled'); + if (this.isPanMode) { + this.canvasManager.emit('pan:activated'); + } else { + // this.canvasManager.emit('pan:deactivated'); + } } zoomIn() { @@ -301,6 +301,7 @@ export class CanvasControlsPlugin extends CanvasPlugin { zoom *= 1.1; if (zoom > 20) zoom = 20; this.canvas.zoomToPoint({ x: this.canvas.width / 2, y: this.canvas.height / 2 }, zoom); + } zoomOut() { @@ -314,19 +315,22 @@ export class CanvasControlsPlugin extends CanvasPlugin { this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]); } - zoom(e,delta) { let zoom = this.canvas.getZoom(); zoom *= 0.999 ** delta; if (zoom > 20) zoom = 20; if (zoom < 0.1) zoom = 0.1; this.canvas.zoomToPoint({ x: e.offsetX, y: e.offsetY }, zoom); - // this.canvasManager.emit('zoom:changed', { zoom }); + if (delta > 0) { + this.canvas.setCursor('zoom-out'); + } else { + this.canvas.setCursor('zoom-in'); + } } horizontalPan(delta) { const vpt = this.canvas.viewportTransform; - const panDelta = delta / 10; // Adjust pan speed as necessary + const panDelta = delta / 10; vpt[4] += panDelta; this.canvas.requestRenderAll(); // this.canvasManager.emit('pan:changed', { x: vpt[4], y: vpt[5] }); @@ -334,7 +338,7 @@ export class CanvasControlsPlugin extends CanvasPlugin { verticalPan(delta) { const vpt = this.canvas.viewportTransform; - const panDelta = delta / 10; // Adjust pan speed as necessary + const panDelta = delta / 10; vpt[5] += panDelta; this.canvas.requestRenderAll(); // this.canvasManager.emit('pan:changed', { x: vpt[4], y: vpt[5] }); @@ -497,6 +501,16 @@ export class CanvasControlsPlugin extends CanvasPlugin { `; } + getPanIcon() { + const svg = ` + + + + `; + // Convert the SVG to a Data URI + return `data:image/svg+xml;base64,${btoa(svg)}`; + } + } diff --git a/web/core/js/common/components/canvas/CanvasLoader.js b/web/core/js/common/components/canvas/CanvasLoader.js index 2b91f2a..54aefa2 100644 --- a/web/core/js/common/components/canvas/CanvasLoader.js +++ b/web/core/js/common/components/canvas/CanvasLoader.js @@ -273,7 +273,7 @@ export class CanvasLoader { this.canvasManager.registerPlugin(imageAdderPlugin); } - console.log('All plugins registered successfully'); + console.log('All canvas plugins registered successfully'); this.canvasManager.on('switchView', (newView) => { // console.log(`Switching viewType to: ${newView}`); diff --git a/web/core/js/common/components/canvas/CanvasManager.js b/web/core/js/common/components/canvas/CanvasManager.js index ca00eaa..44d501a 100644 --- a/web/core/js/common/components/canvas/CanvasManager.js +++ b/web/core/js/common/components/canvas/CanvasManager.js @@ -63,14 +63,14 @@ export class CanvasManager extends EventEmitter { } plugin.init(this); this.plugins.add(plugin); - console.log(`CanvasManager: Registered plugin ${plugin.name}`); + // console.log(`CanvasManager: Registered plugin ${plugin.name}`); } unregisterPlugin(plugin) { if (this.plugins.has(plugin)) { plugin.destroy(); this.plugins.delete(plugin); - console.log(`CanvasManager: Unregistered plugin ${plugin.name}`); + // console.log(`CanvasManager: Unregistered plugin ${plugin.name}`); } } diff --git a/web/core/js/common/components/canvas/CustomBrushPlugin.js b/web/core/js/common/components/canvas/CustomBrushPlugin.js index 4f93ebe..c151b53 100644 --- a/web/core/js/common/components/canvas/CustomBrushPlugin.js +++ b/web/core/js/common/components/canvas/CustomBrushPlugin.js @@ -10,7 +10,7 @@ export class CustomBrushPlugin extends CanvasPlugin { this.maxBrushSize = options.maxBrushSize || 500; this.brushColor = options.brushColor || '#000000'; this.brushOpacity = options.brushOpacity !== undefined ? options.brushOpacity : 1; - this.brushTipResizeSpeed = options.brushTipResizeSpeed || 1; + this.brushTipResizeSpeed = options.brushTipResizeSpeed || 10; this.cursorOutlineType = options.cursorOutlineType || 'dashed'; this.cursorPrimaryColor = options.cursorPrimaryColor || '#000000'; this.cursorSecondaryColor = options.cursorSecondaryColor || '#FFFFFF'; @@ -28,7 +28,7 @@ export class CustomBrushPlugin extends CanvasPlugin { this.isMouseDown = false; this.lastPointer = null; this.isMouseOverCanvas = false; - + this.cursorText = null; this.currentPath = null; this.brushIcon = '/core/media/ui/paintree.png'; @@ -82,6 +82,12 @@ export class CustomBrushPlugin extends CanvasPlugin { this.enableDrawingMode = this.enableDrawingMode.bind(this); this.onCanvasPointerEnter = this.onCanvasPointerEnter.bind(this); this.onCanvasPointerLeave = this.onCanvasPointerLeave.bind(this); + + store.subscribe((state) => { + if (state.isQueueRunning) { + this.disableDrawingMode(); + } + }); } init(canvasManager) { @@ -96,13 +102,6 @@ export class CustomBrushPlugin extends CanvasPlugin { this.createUI(); - // if (this.canvasElement) { - // if (this.canvasElement.tagName.toLowerCase() === 'canvas' && !this.canvasElement.hasAttribute('tabindex')) { - // this.canvasElement.setAttribute('tabindex', '0'); - // // console.log('Set tabindex="0" on the canvas element to make it focusable.'); - // } - // } - this.attachEventListeners(); this.brushSizeInput.value = this.brushSize; @@ -114,7 +113,7 @@ export class CustomBrushPlugin extends CanvasPlugin { this.canvas.getObjects().forEach(this.enforceObjectProperties); this.canvasManager.on('viewport:changed', this.onViewportChanged); - this.canvasManager.on('pan:toggled', this.disableDrawingMode); + this.canvasManager.on('pan:activated', this.disableDrawingMode); } injectHiddenCursorCSS() { @@ -129,9 +128,7 @@ export class CustomBrushPlugin extends CanvasPlugin { document.head.appendChild(style); } - // Helper function to convert HEX to RGBA getFillColorWithOpacity(hex, alpha) { - // Remove '#' if present hex = hex.replace('#', ''); let r, g, b; if (hex.length === 3) { @@ -143,7 +140,6 @@ export class CustomBrushPlugin extends CanvasPlugin { g = parseInt(hex.substring(2, 4), 16); b = parseInt(hex.substring(4, 6), 16); } else { - // Invalid format return 'rgba(0,0,0,1)'; } return `rgba(${r},${g},${b},${alpha})`; @@ -249,7 +245,7 @@ export class CustomBrushPlugin extends CanvasPlugin { } onCanvasPointerLeave(e) { - if (!this.isMouseOverCanvas) return; // Prevent redundant processing + if (!this.isMouseOverCanvas) return; this.isMouseOverCanvas = false; if (this.canvasElement && typeof this.canvasElement.blur === 'function') { @@ -282,15 +278,9 @@ export class CustomBrushPlugin extends CanvasPlugin { // console.log(`Brush disabled via ${key} key press.`); } } - - switch (key) { - case 'd': - this.onToggleDrawingMode(); - break; - default: - break; + if (key === 'd' || key === 'D' || key === 'm' || key === 'M') { + this.onToggleDrawingMode(); } - if (this.isMouseOverCanvas) { e.preventDefault(); } @@ -311,7 +301,6 @@ export class CustomBrushPlugin extends CanvasPlugin { } } - disableSelectionBox() { this.canvas.selection = false; this.canvas.selectionColor = 'transparent'; @@ -347,6 +336,8 @@ export class CustomBrushPlugin extends CanvasPlugin { this.attachDrawingEvents(); this.updateCursorCircle(); this.canvas.requestRenderAll(); + this.canvasManager.emit('brush:activated'); + } disableDrawingMode() { @@ -360,6 +351,7 @@ export class CustomBrushPlugin extends CanvasPlugin { this.configureCanvasObjects(false); this.detachDrawingEvents(); this.removeCursorCircles(); + this.canvasManager.emit('brush:deactivated'); } updateToggleButton(isActive) { @@ -405,10 +397,8 @@ export class CustomBrushPlugin extends CanvasPlugin { onToggleDrawingMode() { if (!this.drawingMode) { this.enableDrawingMode(); - this.canvasManager.emit('brush:activated'); } else { this.disableDrawingMode(); - this.canvasManager.emit('brush:deactivated'); } } @@ -491,121 +481,11 @@ export class CustomBrushPlugin extends CanvasPlugin { this.canvas.off('mouse:out', this.handleMouseOut); } - // updateCursorCircle() { - // const dashArray = this.getStrokeDashArray(); - // const isOutlined = this.cursorOutlineType === 'dashed' || this.cursorOutlineType === 'dotted'; - - // if (!this.cursorCircle) { - // this.cursorCircle = new fabric.Circle({ - // radius: this.brushSize / 2, - // fill: this.cursorFill - // ? (this.useBrushColorPrimaryColor - // ? this.getFillColorWithOpacity(this.brushColor, this.brushOpacity) - // : this.getFillColorWithOpacity(this.cursorPrimaryColor, this.cursorRingOpacityAffected ? this.brushOpacity : 1)) - // : 'transparent', - // stroke: this.getCursorStrokeColor(), - // strokeWidth: this.cursorOutlineType === 'none' ? 0 : this.cursorLineWidth, - // selectable: false, - // evented: false, - // hasControls: false, - // hasBorders: false, - // originX: 'center', - // originY: 'center', - // strokeDashArray: dashArray, - // hoverCursor: 'none' - // }); - - // if (isOutlined && !this.useBrushColorForCursorRing) { - // this.secondaryCircle = new fabric.Circle({ - // radius: this.brushSize / 2, - // fill: 'transparent', - // stroke: this.cursorSecondaryColor, - // strokeWidth: this.cursorLineWidth, - // selectable: false, - // evented: false, - // hasControls: false, - // hasBorders: false, - // originX: 'center', - // originY: 'center', - // strokeDashArray: dashArray, - // strokeDashOffset: this.cursorOutlineType === 'dashed' ? dashArray[0] : dashArray[0], - // hoverCursor: 'none' - // }); - // this.canvas.add(this.secondaryCircle); - // } - - // this.canvas.add(this.cursorCircle); - // if (this.secondaryCircle) { - // this.secondaryCircle.bringToFront(); - // } - // this.cursorCircle.bringToFront(); - // } else { - // this.cursorCircle.set({ - // radius: this.brushSize / 2, - // fill: this.cursorFill - // ? (this.useBrushColorPrimaryColor - // ? this.getFillColorWithOpacity(this.brushColor, this.brushOpacity) - // : this.getFillColorWithOpacity(this.cursorPrimaryColor, this.cursorRingOpacityAffected ? this.brushOpacity : 1)) - // : 'transparent', - // stroke: this.getCursorStrokeColor(), - // strokeWidth: this.cursorOutlineType === 'none' ? 0 : this.cursorLineWidth, - // strokeDashArray: dashArray - // }); - - // if (isOutlined && !this.useBrushColorForCursorRing) { - // if (!this.secondaryCircle) { - // this.secondaryCircle = new fabric.Circle({ - // radius: this.brushSize / 2, - // fill: 'transparent', - // stroke: this.cursorSecondaryColor, - // strokeWidth: this.cursorLineWidth, - // selectable: false, - // evented: false, - // hasControls: false, - // hasBorders: false, - // originX: 'center', - // originY: 'center', - // strokeDashArray: dashArray, - // strokeDashOffset: this.cursorOutlineType === 'dashed' ? dashArray[0] : dashArray[0], - // hoverCursor: 'none' - // }); - // this.canvas.add(this.secondaryCircle); - // } else { - // this.secondaryCircle.set({ - // radius: this.brushSize / 2, - // stroke: this.cursorSecondaryColor, - // strokeWidth: this.cursorLineWidth, - // strokeDashArray: dashArray, - // strokeDashOffset: this.cursorOutlineType === 'dashed' ? dashArray[0] : dashArray[0] - // }); - // } - // } else if (this.secondaryCircle) { - // this.canvas.remove(this.secondaryCircle); - // this.secondaryCircle = null; - // } - // } - - // if (this.currentZoom !== 0) { - // const scale = 1 / this.currentZoom; - // this.cursorCircle.set({ - // scaleX: scale, - // scaleY: scale - // }); - // if (this.secondaryCircle) { - // this.secondaryCircle.set({ - // scaleX: scale, - // scaleY: scale - // }); - // } - // } - // } - updateCursorCircle() { const dashArray = this.getStrokeDashArray(); const isOutlined = this.cursorOutlineType === 'dashed' || this.cursorOutlineType === 'dotted'; - // Define positions off the canvas - const offCanvasPosition = -100000; // Position to the top-left outside the canvas + const offCanvasPosition = -100000; if (!this.cursorCircle) { this.cursorCircle = new fabric.Circle({ @@ -623,8 +503,8 @@ export class CustomBrushPlugin extends CanvasPlugin { hasBorders: false, originX: 'center', originY: 'center', - left: offCanvasPosition, // Set initial horizontal position off-canvas - top: offCanvasPosition, // Set initial vertical position off-canvas + left: offCanvasPosition, + top: offCanvasPosition, strokeDashArray: dashArray, hoverCursor: 'none' }); @@ -641,8 +521,8 @@ export class CustomBrushPlugin extends CanvasPlugin { hasBorders: false, originX: 'center', originY: 'center', - left: offCanvasPosition, // Set initial horizontal position off-canvas - top: offCanvasPosition, // Set initial vertical position off-canvas + left: offCanvasPosition, + top: offCanvasPosition, strokeDashArray: dashArray, strokeDashOffset: this.cursorOutlineType === 'dashed' ? dashArray[0] : dashArray[0], hoverCursor: 'none' @@ -681,8 +561,8 @@ export class CustomBrushPlugin extends CanvasPlugin { hasBorders: false, originX: 'center', originY: 'center', - left: offCanvasPosition, // Set initial horizontal position off-canvas - top: offCanvasPosition, // Set initial vertical position off-canvas + left: offCanvasPosition, + top: offCanvasPosition, strokeDashArray: dashArray, strokeDashOffset: this.cursorOutlineType === 'dashed' ? dashArray[0] : dashArray[0], hoverCursor: 'none' @@ -716,8 +596,7 @@ export class CustomBrushPlugin extends CanvasPlugin { }); } } - - // Ensure the canvas is re-rendered to reflect changes + this.canvas.requestRenderAll(); } @@ -758,13 +637,13 @@ export class CustomBrushPlugin extends CanvasPlugin { opt.e.stopPropagation(); let deltaSize = delta < 0 ? 1 : -1; - deltaSize *= this.brushTipResizeSpeed; + deltaSize *= this.brushTipResizeSpeed; //currenly set to 1 this.brushSize = Math.min(this.maxBrushSize, Math.max(this.minBrushSize, this.brushSize + deltaSize)); this.brushSizeInput.value = this.brushSize; this.updateCursorCircle(); this.canvas.requestRenderAll(); } - + handleMouseOver() { if (this.drawingMode) { if (this.cursorCircle) { diff --git a/web/core/js/common/components/canvas/ImageLoaderPlugin.js b/web/core/js/common/components/canvas/ImageLoaderPlugin.js index 3b185a8..845fc93 100644 --- a/web/core/js/common/components/canvas/ImageLoaderPlugin.js +++ b/web/core/js/common/components/canvas/ImageLoaderPlugin.js @@ -18,6 +18,7 @@ export class ImageLoaderPlugin extends CanvasPlugin { this.loadedImages = []; this.originalImages = {}; + this.loadedImageType = 'uploaded'; // 'uploaded' 'preview' 'final' this.onImageDrop = this.onImageDrop.bind(this); this.onDoubleClick = this.onDoubleClick.bind(this); @@ -41,6 +42,7 @@ export class ImageLoaderPlugin extends CanvasPlugin { this.canvasManager.on('image:remove', this.onImageRemove); window.addEventListener('finalImageData', this.handleFinalImageData); window.addEventListener('previewImageData', this.handlePreviewImageData); + } createUI() { @@ -331,6 +333,7 @@ export class ImageLoaderPlugin extends CanvasPlugin { try { const dataURL = await this.readFileAsDataURL(file); this.loadImageFromDataURL(dataURL); + this.loadedImageType = 'uploaded'; } catch (error) { console.error('ImageLoaderPlugin: Failed to read the image file.', error); } @@ -389,6 +392,8 @@ export class ImageLoaderPlugin extends CanvasPlugin { lockMovementY: true, originX: 'center', originY: 'center', + objectCaching: false + }); const scaleFactor = this.updateImageScaleAndPosition(img, borderRect, canvasWidth, canvasHeight, originalWidth, originalHeight); @@ -419,6 +424,7 @@ export class ImageLoaderPlugin extends CanvasPlugin { originalWidth: originalWidth, originalHeight: originalHeight, scaleFactor: scaleFactor, + loadedImageType: this.loadedImageType, }); this.canvasManager.emit('image:list:updated', { @@ -451,6 +457,7 @@ export class ImageLoaderPlugin extends CanvasPlugin { } } + this.loadedImageType = 'preview'; this.loadImageFromDataURL(dataURL); } @@ -471,6 +478,7 @@ export class ImageLoaderPlugin extends CanvasPlugin { } } + this.loadedImageType = 'final'; this.loadImageFromDataURL(dataURL); } diff --git a/web/core/js/common/components/canvas/MaskBrushPlugin.js b/web/core/js/common/components/canvas/MaskBrushPlugin.js index 5c43409..2672348 100644 --- a/web/core/js/common/components/canvas/MaskBrushPlugin.js +++ b/web/core/js/common/components/canvas/MaskBrushPlugin.js @@ -1,7 +1,6 @@ import { CustomBrushPlugin } from './CustomBrushPlugin.js'; import { MaskExportUtilities } from './MaskExportUtilities.js'; import { store } from '../../scripts/stateManagerMain.js'; - export class MaskBrushPlugin extends CustomBrushPlugin { constructor(options = {}) { super({ ...options, name: 'MaskBrushPlugin' }); @@ -12,8 +11,10 @@ export class MaskBrushPlugin extends CustomBrushPlugin { this.brushIcon = '/core/media/ui/double-face-mask.png'; this.isSubscribed = false; this.resizeObserver = null; - this.clearMasksOnImageLoaded = options.clearMasksOnImageLoaded !== undefined ? options.clearMasksOnImageLoaded : true; + this.hidePreviewMask = false; + this.onAddMask = this.onAddMask.bind(this); + this.onToggleEraser = this.onToggleEraser.bind(this); this.onChangeMask = this.onChangeMask.bind(this); this.onChangeMaskColor = this.onChangeMaskColor.bind(this); this.onMoveMaskUp = this.onMoveMaskUp.bind(this); @@ -22,7 +23,6 @@ export class MaskBrushPlugin extends CustomBrushPlugin { this.onHandleSave = this.onHandleSave.bind(this); this.onRemoveMask = this.onRemoveMask.bind(this); this.onImageLoaded = this.onImageLoaded.bind(this); - this.onCanvasStateChanged = this.onCanvasStateChanged.bind(this); this.onHandleSaveFromCanvas = this.onHandleSaveFromCanvas.bind(this); this.onWindowResize = this.onWindowResize.bind(this); this.onUndoMaskStroke = this.onUndoMaskStroke.bind(this); @@ -32,303 +32,26 @@ export class MaskBrushPlugin extends CustomBrushPlugin { this.onClearAllMasks = this.onClearAllMasks.bind(this); this.onToggleHideMask = this.onToggleHideMask.bind(this); this.onToggleHideAllMasks = this.onToggleHideAllMasks.bind(this); - this.handleStateChange = this.handleStateChange.bind(this); - - this.maskExportUtilities = new MaskExportUtilities(this); + this.onToggleHidePreviewMask = this.onToggleHidePreviewMask.bind(this); + this.onResumeMask = this.onResumeMask.bind(this); + // store.subscribe((state) => { + // if (state.isQueueRunning) { + // this.hideMask(); + // } + // }); } init(canvasManager) { super.init(canvasManager); - + //removes brush opacity from the UI if (this.brushOpacityInput && this.brushOpacityInput.parentElement) { this.brushOpacityInput.parentElement.style.display = 'none'; } + this.uiContainer.querySelector('.cbp-brush-ui-title').textContent = 'Mask Settings'; this.extendUI(); - this.attachAdditionalEventListeners(); - - window.addEventListener('resize', this.onWindowResize); - - this.canvasManager.on('image:loaded', this.onImageLoaded); - this.canvasManager.on('canvas:state:changed', this.onCanvasStateChanged); - this.canvasManager.on('undo:mask:stroke', this.onUndoMaskStroke); - this.canvasManager.on('redo:mask:stroke', this.onRedoMaskStroke); - this.canvasManager.on('save:trigger', this.onHandleSaveFromCanvas); - - this.setupResizeObserver(); - } - - setupResizeObserver() { - const canvasContainer = this.canvas.getElement().parentElement; - if (!canvasContainer) { - console.error('Canvas container element not found.'); - return; - } - - this.resizeObserver = new ResizeObserver(entries => { - for (let entry of entries) { - if (entry.target === canvasContainer) { - this.onImageModified(); - } - } - }); - - this.resizeObserver.observe(canvasContainer); - } - - extendUI() { - const temp = document.createElement('div'); - temp.innerHTML = ` -
- - - - - - - -
- - -
-
- - -
- - - -
- `; - - - while (temp.firstChild) { - this.uiContainer.appendChild(temp.firstChild); - } - - this.addMaskBtn = this.uiContainer.querySelector('#addMaskBtn'); - this.maskList = this.uiContainer.querySelector('#maskList'); - this.applyColorToMaskCheckbox = this.uiContainer.querySelector('#applyColorToMaskCheckbox'); - this.moveMaskUpBtn = this.uiContainer.querySelector('#moveMaskUpBtn'); - this.moveMaskDownBtn = this.uiContainer.querySelector('#moveMaskDownBtn'); - this.removeMaskBtn = this.uiContainer.querySelector('#removeMaskBtn'); - this.clearMaskBtn = this.uiContainer.querySelector('#clearMaskBtn'); - this.clearAllMasksBtn = this.uiContainer.querySelector('#clearAllMasksBtn'); - this.hideMaskCheckbox = this.uiContainer.querySelector('#hideMaskCheckbox'); - this.hideAllMasksCheckbox = this.uiContainer.querySelector('#hideAllMasksCheckbox'); - this.saveOptionsSelect = this.uiContainer.querySelector('#saveOptionsSelect'); - this.saveMaskBtn = this.uiContainer.querySelector('#saveMaskBtn'); - this.disableDrawingModeBtn = this.uiContainer.querySelector('#disableDrawingModeBtn'); - - this.saveOptions = [ - { - value: 'saveCroppedMask', - text: 'Cropped Mask', - handler: () => this.maskExportUtilities.saveCroppedMask(), - exportFunction: () => this.maskExportUtilities.exportCroppedMask(), - show: true, - }, - { - value: 'saveCroppedAlphaOnImage', - text: 'Cropped Alpha', - handler: () => this.maskExportUtilities.saveCroppedAlphaOnImage(), - exportFunction: () => this.maskExportUtilities.exportCroppedAlphaOnImage(), - show: true, - }, - { - value: 'saveCroppedImage', - text: 'Cropped Image', - handler: () => this.maskExportUtilities.saveCroppedImage(), - exportFunction: () => this.maskExportUtilities.exportCroppedImage(), - show: true, - }, - - { - value: 'saveMaskAlphaOnImage', - text: 'Mask As Alpha on Image', - handler: () => this.maskExportUtilities.saveMaskAlphaOnImage(), - exportFunction: () => this.maskExportUtilities.exportMaskAlphaOnImage(), - show: true, - }, - { - value: 'saveAllMasksAlphaOnImage', - text: 'All Masks As Alpha on Image', - handler: () => this.maskExportUtilities.saveAllMasksAlphaOnImage(), - exportFunction: () => this.maskExportUtilities.exportAllMasksAlphaOnImage(), - show: true, - }, - { - value: 'saveAllMasksCombinedAlphaOnImage', - text: 'All Masks As Alpha Combined on Image', - handler: () => this.maskExportUtilities.saveAllMasksCombinedAlphaOnImage(), - exportFunction: () => this.maskExportUtilities.exportAllMasksCombinedAlphaOnImage(), - show: true, - }, - { - value: 'saveMask', - text: 'Mask', - handler: () => this.maskExportUtilities.saveMask(), - exportFunction: () => this.maskExportUtilities.exportMask(), - show: true, - }, - { - value: 'saveAllMasks', - text: 'All Masks', - handler: () => this.maskExportUtilities.saveAllMasks(), - exportFunction: () => this.maskExportUtilities.exportAllMasks(), - show: true, - }, - { - value: 'saveAllMasksCombined', - text: 'All Masks Combined', - handler: () => this.maskExportUtilities.saveAllMasksCombined(), - exportFunction: () => this.maskExportUtilities.exportAllMasksCombined(), - show: true, - }, - { - value: 'saveAllMasksCombinedBW', - text: 'All Masks Combined (B&W)', - handler: () => this.maskExportUtilities.saveAllMasksCombinedBlackWhite(), - exportFunction: () => this.maskExportUtilities.exportMasksCombinedBlackWhite(), - show: true, - }, - { - value: 'saveMaskOnImage', - text: 'Mask on Image', - handler: () => this.maskExportUtilities.saveMaskOnImage(), - exportFunction: () => this.maskExportUtilities.exportMaskOnImage(), - show: true, - }, - { - value: 'saveAllMasksOnImage', - text: 'All Masks on Image', - handler: () => this.maskExportUtilities.saveAllMasksOnImage(), - exportFunction: () => this.maskExportUtilities.exportAllMasksOnImage(), - show: true, - }, - { - value: 'saveAllMasksCombinedOnImage', - text: 'All Masks Combined on Image', - handler: () => this.maskExportUtilities.saveAllMasksCombinedOnImage(), - exportFunction: () => this.maskExportUtilities.exportAllMasksCombinedOnImage(), - show: true, - }, - { - value: 'saveAllMasksCombinedBWOnImage', - text: 'All Masks Combined (B&W) on Image', - handler: () => this.maskExportUtilities.saveAllMasksCombinedBlackWhiteOnImage(), - exportFunction: () => this.maskExportUtilities.exportAllMasksCombinedBlackWhiteOnImage(), - show: true, - }, - ]; - - this.saveOptions.forEach(optionData => { - if (optionData.show) { - const option = document.createElement('option'); - option.value = optionData.value; - option.text = optionData.text; - this.saveOptionsSelect.add(option); - } - }); } attachAdditionalEventListeners() { @@ -341,9 +64,21 @@ export class MaskBrushPlugin extends CustomBrushPlugin { this.removeMaskBtn.addEventListener('click', this.onRemoveMask); this.clearMaskBtn.addEventListener('click', this.onClearMask); this.clearAllMasksBtn.addEventListener('click', this.onClearAllMasks); + this.resumeMasksBtn.addEventListener('click', this.onResumeMask); this.hideMaskCheckbox.addEventListener('change', this.onToggleHideMask); this.hideAllMasksCheckbox.addEventListener('change', this.onToggleHideAllMasks); + this.hideMaskPreviewCheckbox.addEventListener('change', this.onToggleHidePreviewMask); this.saveMaskBtn.addEventListener('click', this.onHandleSave); + this.eraserBtn.addEventListener('click', this.onToggleEraser); + + + this.canvasManager.on('image:loaded', this.onImageLoaded); + this.canvasManager.on('undo:mask:stroke', this.onUndoMaskStroke); + this.canvasManager.on('redo:mask:stroke', this.onRedoMaskStroke); + this.canvasManager.on('save:trigger', this.onHandleSaveFromCanvas); + window.addEventListener('resize', this.onWindowResize); + this.addResizeObserver(); + } detachAdditionalEventListeners() { @@ -356,54 +91,50 @@ export class MaskBrushPlugin extends CustomBrushPlugin { this.removeMaskBtn.removeEventListener('click', this.onRemoveMask); this.clearMaskBtn.removeEventListener('click', this.onClearMask); this.clearAllMasksBtn.removeEventListener('click', this.onClearAllMasks); + this.resumeMasksBtn.addEventListener('click', this.resumeMask); this.hideMaskCheckbox.removeEventListener('change', this.onToggleHideMask); this.hideAllMasksCheckbox.removeEventListener('change', this.onToggleHideAllMasks); + this.hideMaskPreviewCheckbox.removeEventListener('change', this.onToggleHidePreviewMask); this.saveMaskBtn.removeEventListener('click', this.onHandleSave); this.canvasManager.off('image:loaded', this.onImageLoaded); - this.canvasManager.off('canvas:state:changed', this.onCanvasStateChanged); this.canvasManager.off('undo:mask:stroke', this.onUndoMaskStroke); this.canvasManager.off('redo:mask:stroke', this.onRedoMaskStroke); - - window.removeEventListener('resize', this.onWindowResize); - this.canvasManager.off('save:trigger', this.onHandleSaveFromCanvas); + window.removeEventListener('resize', this.onWindowResize); + this.removeResizeObserver(); - // Disconnect the ResizeObserver - if (this.resizeObserver) { - this.resizeObserver.disconnect(); - this.resizeObserver = null; - } - } - - onWindowResize() { - this.onImageModified(); } - - handleStateChange(state) { - // console.log('---Current state---', state.hideMask); - // console.log('---this.clearMasksOnImageLoaded ---', this.clearMasksOnImageLoaded ); - - - if (this.clearMasksOnImageLoaded == false) { - if (state.hideMask) { - this.hideMaskCheckbox.checked = true; - // this.currentMask.visible = false - // this.currentMask.fabricImage.visible = this.currentMask.visible - // this.canvas.renderAll(); - // console.log('---Current state--- hideMask true', state.hideMask); + + onToggleEraser() { + if(this.drawingMode){ + this.isEraserActive = !this.isEraserActive; + console.log('Eraser Active:', this.isEraserActive); + if (this.isEraserActive) { + this.eraserBtn.classList.add('active'); + this.cursorFill = '#FF0000'; + this.updateBrushColorAndCursor('eraser'); + this.updateCursorCircle() ; } else { - // console.log('---Current state--- hideMask false', state.hideMask); - // this.hideMaskCheckbox.checked = false; - // this.canvas.renderAll(); - // this.onToggleDrawingMode() + this.eraserBtn.classList.remove('active'); + this.updateBrushColorAndCursor('brush'); } } } - onImageLoaded(event) { - const { image, originalWidth, originalHeight, scaleFactor } = event; + enableDrawingMode(){ + super.enableDrawingMode(); + this.showMask(); + } + disableDrawingMode(){ + super.disableDrawingMode(); + this.isEraserActive = false; + this.eraserBtn.classList.remove('active'); + this.updateBrushColorAndCursor('brush'); + } + onImageLoaded(event) { + const { image, originalWidth, originalHeight, scaleFactor , loadedImageType} = event; this.imageObject = image; this.imageOriginalWidth = originalWidth; this.imageOriginalHeight = originalHeight; @@ -411,81 +142,33 @@ export class MaskBrushPlugin extends CustomBrushPlugin { this.imageObject.on('modified', this.onImageModified); - if (!this.isSubscribed) { - this.unsubscribe = store.subscribe(this.handleStateChange); - this.isSubscribed = true; - } - if (this.clearMasksOnImageLoaded) { - this.masks.forEach(mask => { - this.canvas.remove(mask.fabricImage); - }); - this.masks = []; - this.currentMask = null; - this.maskList.options.length = 0; - - this.maskStrokeHistory = {}; - - this.onAddMask(); - // if (!this.drawingMode) { - // this.disableDrawingMode(); - // } - } else { - this.masks.forEach(mask => { - this.canvas.remove(mask.fabricImage); - mask.fabricImage = new fabric.Image(mask.canvasEl, { - left: this.imageObject.left, - top: this.imageObject.top, - originX: this.imageObject.originX, - originY: this.imageObject.originY, - selectable: false, - evented: false, - lockMovementX: true, - lockMovementY: true, - lockRotation: true, - lockScalingX: true, - lockScalingY: true, - hasControls: false, - hasBorders: false, - hoverCursor: 'default', - opacity: this.brushOpacity, - scaleX: this.imageObject.scaleX, - scaleY: this.imageObject.scaleY, - willReadFrequently: true, - }); - this.canvas.add(mask.fabricImage); - mask.fabricImage.bringToFront(); - - if (this.hideMaskCheckbox.checked) { - this.currentMask.visible = false - this.currentMask.fabricImage.visible = this.currentMask.visible - this.canvas.renderAll(); - // console.log('---Current state--- hideMask true', state.hideMask); - // this.onToggleDrawingMode() - - } - else if (this.hideMaskCheckbox.checked === false) { - // console.log('---Current state--- false', state.hideMask); - // this.hideMaskCheckbox.checked = false; - // this.canvas.renderAll(); - } - }); - - if (this.masks.length > 0) { - if (!this.currentMask || !this.masks.includes(this.currentMask)) { - this.currentMask = this.masks[0]; - } - this.maskList.value = this.currentMask.name; - this.updateBrushColorAndCursor(); - } else { - this.onAddMask(); - } - this.canvas.renderAll(); + switch (loadedImageType) { + case 'uploaded': + this.onUploadedImage(); + break; + case 'preview': + if (this.hideMaskPreviewCheckbox.checked) { + this.onResumeMask(); + } + break; + case 'final': + this.onResumeMask(); + if (this.hideMaskCheckbox.checked) { + this.hideMask(); + } + break; + default: + break; } + + } + onUploadedImage() { + this.addNewMask(); } onImageModified() { if (!this.imageObject) { - console.error('Image object is not defined.'); + console.warn('Image object is not defined.'); return; } @@ -503,33 +186,8 @@ export class MaskBrushPlugin extends CustomBrushPlugin { this.canvas.renderAll(); } - onCanvasStateChanged() { - this.masks.forEach(mask => { - if (mask.fabricImage) { - mask.fabricImage.selectable = !this.drawingMode; - mask.fabricImage.evented = !this.drawingMode; - } - }); - this.canvas.renderAll(); - } - - onAddMask() { - if (!this.imageObject) { - alert('Please load an image before adding masks.'); - return; - } - - const maskName = `Mask ${this.maskList.options.length + 1}`; - const color = this.brushColor; - - const maskCanvas = document.createElement('canvas'); - maskCanvas.width = this.imageOriginalWidth; - maskCanvas.height = this.imageOriginalHeight; - const maskCtx = maskCanvas.getContext('2d'); - - maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); - - const maskFabricImage = new fabric.Image(maskCanvas, { + getMaskOptions() { + return { left: this.imageObject.left, top: this.imageObject.top, originX: this.imageObject.originX, @@ -548,11 +206,37 @@ export class MaskBrushPlugin extends CustomBrushPlugin { scaleX: this.imageObject.scaleX, scaleY: this.imageObject.scaleY, willReadFrequently: true, + }; + } + + addNewMask() { + // Clear the current masks from the canvas + this.masks.forEach(mask => { + this.canvas.remove(mask.fabricImage); }); - + this.masks = []; + this.currentMask = null; + this.maskList.options.length = 0; + this.maskStrokeHistory = {}; + this.onAddMask(); + } + + onAddMask() { + const maskName = `Mask ${this.maskList.options.length + 1}`; + const color = this.brushColor; + + const maskCanvas = document.createElement('canvas'); + maskCanvas.width = this.imageOriginalWidth; + maskCanvas.height = this.imageOriginalHeight; + const maskCtx = maskCanvas.getContext('2d', { willReadFrequently: true }); + + maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); + + const maskFabricImage = new fabric.Image(maskCanvas, this.getMaskOptions()); + this.canvas.add(maskFabricImage); maskFabricImage.bringToFront(); - + const mask = { name: maskName, color: color, @@ -561,22 +245,181 @@ export class MaskBrushPlugin extends CustomBrushPlugin { ctx: maskCtx, visible: true, }; - + this.masks.push(mask); this.currentMask = mask; - + this.maskStrokeHistory[maskName] = { undoStack: [], redoStack: [] }; - + const option = document.createElement('option'); option.value = maskName; option.text = maskName; option.dataset.color = color; this.maskList.add(option); this.maskList.value = maskName; + + this.updateBrushColorAndCursor(); + } + + onResumeMask() { + this.resumeMask(); + } + + resumeMask() { + this.masks.forEach(mask => { + this.canvas.remove(mask.fabricImage); + mask.fabricImage = new fabric.Image(mask.canvasEl, this.getMaskOptions()); + this.canvas.add(mask.fabricImage); + mask.fabricImage.bringToFront(); + }); + + if (this.masks.length > 0) { + if (!this.currentMask || !this.masks.includes(this.currentMask)) { + this.currentMask = this.masks[0]; + } + this.maskList.value = this.currentMask.name; + this.updateBrushColorAndCursor(); + } else { + this.onAddMask(); + } + this.canvas.renderAll(); + } + + onRemoveMask() { + if (this.masks.length === 0) { + alert('No masks available to remove.'); + return; + } + + const selectedIndex = this.maskList.selectedIndex; + if (selectedIndex === -1) { + alert('Please select a mask to remove.'); + return; + } + + const maskName = this.maskList.options[selectedIndex].value; + const mask = this.masks.find(m => m.name === maskName); + + if (!mask) { + alert('Selected mask not found.'); + return; + } + + const confirmRemoval = confirm(`Are you sure you want to remove "${maskName}"?`); + if (!confirmRemoval) { + return; + } + + this.canvas.remove(mask.fabricImage); + + this.masks = this.masks.filter(m => m.name !== maskName); + + delete this.maskStrokeHistory[maskName]; + + this.maskList.remove(selectedIndex); + + if (this.masks.length > 0) { + const newIndex = selectedIndex > 0 ? selectedIndex - 1 : 0; + this.maskList.selectedIndex = newIndex; + this.currentMask = this.masks[newIndex]; + + this.hideMaskCheckbox.checked = !this.currentMask.visible; + + } else { + this.currentMask = null; + this.hideMaskCheckbox.checked = false; + } + this.updateBrushColorAndCursor(); + + this.canvas.renderAll(); + } + + onClearMask() { + this.currentMask.ctx.clearRect(0, 0, this.currentMask.canvasEl.width, this.currentMask.canvasEl.height); + this.currentMask.fabricImage.dirty = true; + this.canvas.renderAll(); + + this.maskStrokeHistory[this.currentMask.name] = { + undoStack: [], + redoStack: [] + }; + } + + onClearAllMasks() { + this.masks.forEach(mask => { + mask.ctx.clearRect(0, 0, mask.canvasEl.width, mask.canvasEl.height); + mask.fabricImage.dirty = true; + + this.maskStrokeHistory[mask.name] = { + undoStack: [], + redoStack: [] + }; + }); + this.canvas.renderAll(); + } + // onToggleDrawingMode() { + // if (!this.drawingMode) { + // this.enableDrawingMode(); + // } else { + // this.disableDrawingMode(); + // } + // } + hideMask(){ + this.currentMask.visible = false; + this.currentMask.fabricImage.visible = this.currentMask.visible; + this.hideMaskCheckbox.checked = true; + this.hidePreviewMask = false; + this.hideMaskPreviewCheckbox.checked = false; + this.canvas.renderAll(); + } + showMask(){ + this.currentMask.visible = true; + this.currentMask.fabricImage.visible = this.currentMask.visible; + this.hideMaskCheckbox.checked = false; + this.hidePreviewMask = true; + + this.hideMaskPreviewCheckbox.checked = true; + this.canvas.renderAll(); + } + onToggleHideMask() { + if (!this.hideMaskCheckbox.checked) { + this.showMask() + } + else { + this.hideMask() + } + } + // Fix it + onToggleHideMaskKey() { + if (this.hideMaskCheckbox.checked) { + this.showMask() + } + else { + this.hideMask() + } + } + onToggleHideAllMasks() { + const hideAll = this.hideAllMasksCheckbox.checked; + this.masks.forEach(mask => { + mask.visible = !hideAll; + mask.fabricImage.visible = mask.visible; + }); + if (this.currentMask) { + this.hideMaskCheckbox.checked = !this.currentMask.visible; + } + this.canvas.renderAll(); + } + + onToggleHidePreviewMask() { + if(this.hidePreviewMask){ + this.hidePreviewMask = false; + } else { + this.hidePreviewMask = true; + } } onChangeMask() { @@ -694,182 +537,70 @@ export class MaskBrushPlugin extends CustomBrushPlugin { } } - onRemoveMask() { - if (this.masks.length === 0) { - alert('No masks available to remove.'); - return; + updateBrushColorAndCursor() { + if (this.currentMask) { + this.brushColor = this.currentMask.color; + + this.updateCursorCircle(); + this.canvas.requestRenderAll(); + } else { + this.brushColor = '#FF0000'; + this.updateCursorCircle(); + this.canvas.requestRenderAll(); } + } - const selectedIndex = this.maskList.selectedIndex; - if (selectedIndex === -1) { - alert('Please select a mask to remove.'); + onMouseDown(o) { + if (!this.currentMask || !this.currentMask.ctx) { + console.warn('Cannot draw: currentMask or its context is null.'); return; } - const maskName = this.maskList.options[selectedIndex].value; - const mask = this.masks.find(m => m.name === maskName); + this.isMouseDown = true; + this.isStrokeInProgress = true; - if (!mask) { - alert('Selected mask not found.'); - return; - } + const pointer = this.canvas.getPointer(o.e, true); + const transformedPointer = this.mapPointerToImageSpace(pointer); - const confirmRemoval = confirm(`Are you sure you want to remove "${maskName}"?`); - if (!confirmRemoval) { + if (!this.isPointerInsideImage(transformedPointer)) { + this.isMouseDown = false; + this.isStrokeInProgress = false; return; } - this.canvas.remove(mask.fabricImage); + this.lastPointer = transformedPointer; - this.masks = this.masks.filter(m => m.name !== maskName); + const imageDataBefore = this.currentMask.ctx.getImageData(0, 0, this.currentMask.canvasEl.width, this.currentMask.canvasEl.height); + this.maskStrokeHistory[this.currentMask.name].undoStack.push(imageDataBefore); + if (this.maskStrokeHistory[this.currentMask.name].undoStack.length > this.maxHistory) { + this.maskStrokeHistory[this.currentMask.name].undoStack.shift(); + } - delete this.maskStrokeHistory[maskName]; + this.maskStrokeHistory[this.currentMask.name].redoStack = []; - this.maskList.remove(selectedIndex); + this.drawOnMask(transformedPointer); + } + onMouseMove(o) { + if (!this.currentMask || !this.currentMask.ctx) { + console.warn('Cannot draw: currentMask or its context is null.'); + // return; + } - if (this.masks.length > 0) { - const newIndex = selectedIndex > 0 ? selectedIndex - 1 : 0; - this.maskList.selectedIndex = newIndex; - this.currentMask = this.masks[newIndex]; + const pointer = this.canvas.getPointer(o.e, true); + const transformedPointer = this.mapPointerToImageSpace(pointer); - this.hideMaskCheckbox.checked = !this.currentMask.visible; + this.updateCursorPosition(o); - } else { - this.currentMask = null; - this.hideMaskCheckbox.checked = false; - } + if (this.isMouseDown && this.isStrokeInProgress) { + if (!this.isPointerInsideImage(transformedPointer)) { + return; + } - this.updateBrushColorAndCursor(); + this.drawLineOnMask(this.lastPointer, transformedPointer); + this.lastPointer = transformedPointer; + } - this.canvas.renderAll(); - } - - onClearMask() { - if (!this.currentMask) { - alert('No mask selected to clear.'); - return; - } - - const confirmClear = confirm(`Are you sure you want to clear "${this.currentMask.name}"?`); - if (!confirmClear) { - return; - } - - this.currentMask.ctx.clearRect(0, 0, this.currentMask.canvasEl.width, this.currentMask.canvasEl.height); - this.currentMask.fabricImage.dirty = true; - this.canvas.renderAll(); - - this.maskStrokeHistory[this.currentMask.name] = { - undoStack: [], - redoStack: [] - }; - } - - onClearAllMasks() { - // const confirmClearAll = confirm('Are you sure you want to clear all masks?'); - // if (!confirmClearAll) { - // return; - // } - - this.masks.forEach(mask => { - mask.ctx.clearRect(0, 0, mask.canvasEl.width, mask.canvasEl.height); - mask.fabricImage.dirty = true; - - this.maskStrokeHistory[mask.name] = { - undoStack: [], - redoStack: [] - }; - }); - this.canvas.renderAll(); - } - - onToggleHideMask() { - if (!this.currentMask) { - this.hideMaskCheckbox.checked = false; - return; - } - - this.currentMask.visible = !this.hideMaskCheckbox.checked; - this.currentMask.fabricImage.visible = this.currentMask.visible; - this.canvas.renderAll(); - } - - onToggleHideAllMasks() { - const hideAll = this.hideAllMasksCheckbox.checked; - this.masks.forEach(mask => { - mask.visible = !hideAll; - mask.fabricImage.visible = mask.visible; - }); - if (this.currentMask) { - this.hideMaskCheckbox.checked = !this.currentMask.visible; - } - this.canvas.renderAll(); - } - - updateBrushColorAndCursor() { - if (this.currentMask) { - this.brushColor = this.currentMask.color; - this.updateCursorCircle(); - this.canvas.requestRenderAll(); - } else { - this.brushColor = '#FF0000'; - this.updateCursorCircle(); - this.canvas.requestRenderAll(); - } - } - - onMouseDown(o) { - if (!this.currentMask || !this.currentMask.ctx) { - console.error('Cannot draw: currentMask or its context is null.'); - return; - } - - this.isMouseDown = true; - this.isStrokeInProgress = true; - - const pointer = this.canvas.getPointer(o.e, true); - const transformedPointer = this.mapPointerToImageSpace(pointer); - - if (!this.isPointerInsideImage(transformedPointer)) { - this.isMouseDown = false; - this.isStrokeInProgress = false; - return; - } - - this.lastPointer = transformedPointer; - - const imageDataBefore = this.currentMask.ctx.getImageData(0, 0, this.currentMask.canvasEl.width, this.currentMask.canvasEl.height); - this.maskStrokeHistory[this.currentMask.name].undoStack.push(imageDataBefore); - if (this.maskStrokeHistory[this.currentMask.name].undoStack.length > this.maxHistory) { - this.maskStrokeHistory[this.currentMask.name].undoStack.shift(); - } - - this.maskStrokeHistory[this.currentMask.name].redoStack = []; - - this.drawOnMask(transformedPointer); - } - - onMouseMove(o) { - if (!this.currentMask || !this.currentMask.ctx) { - console.warn('Cannot draw: currentMask or its context is null.'); - // return; - } - - const pointer = this.canvas.getPointer(o.e, true); - const transformedPointer = this.mapPointerToImageSpace(pointer); - - this.updateCursorPosition(o); - - if (this.isMouseDown && this.isStrokeInProgress) { - if (!this.isPointerInsideImage(transformedPointer)) { - return; - } - - this.drawLineOnMask(this.lastPointer, transformedPointer); - this.lastPointer = transformedPointer; - } - - this.canvas.requestRenderAll(); + this.canvas.requestRenderAll(); } onMouseUp() { @@ -954,21 +685,33 @@ export class MaskBrushPlugin extends CustomBrushPlugin { drawOnMask(point) { if (!this.imageObject) { - console.error('Image object is not defined.'); - return; + console.warn('Image object is not defined.'); + // return; + } + + const previousCompositeOperation = this.currentMask.ctx.globalCompositeOperation; + + if (this.isEraserActive) { + this.currentMask.ctx.globalCompositeOperation = 'destination-out'; + this.currentMask.ctx.fillStyle = 'rgba(0,0,0,1)'; + } else { + this.currentMask.ctx.globalCompositeOperation = 'source-over'; + this.currentMask.ctx.fillStyle = this.brushColor; } - this.currentMask.ctx.globalAlpha = 1; const adjustedBrushSize = this.brushSize / (this.currentZoom * this.imageObject.scaleX); - this.currentMask.ctx.fillStyle = this.brushColor; this.currentMask.ctx.beginPath(); this.currentMask.ctx.arc(point.x, point.y, adjustedBrushSize / 2, 0, Math.PI * 2); this.currentMask.ctx.fill(); + + // Restore the previous composite operation + this.currentMask.ctx.globalCompositeOperation = previousCompositeOperation; + this.currentMask.fabricImage.dirty = true; const strokeData = { - type: 'add', + type: this.isEraserActive ? 'erase' : 'add', maskName: this.currentMask.name, color: this.brushColor, position: { x: point.x, y: point.y }, @@ -983,21 +726,31 @@ export class MaskBrushPlugin extends CustomBrushPlugin { return; } - this.currentMask.ctx.globalAlpha = 1; + const previousCompositeOperation = this.currentMask.ctx.globalCompositeOperation; + + if (this.isEraserActive) { + this.currentMask.ctx.globalCompositeOperation = 'destination-out'; + this.currentMask.ctx.strokeStyle = 'rgba(0,0,0,1)'; + } else { + this.currentMask.ctx.globalCompositeOperation = 'source-over'; + this.currentMask.ctx.strokeStyle = this.brushColor; + } const adjustedBrushSize = this.brushSize / (this.currentZoom * this.imageObject.scaleX); - this.currentMask.ctx.strokeStyle = this.brushColor; this.currentMask.ctx.lineWidth = adjustedBrushSize; this.currentMask.ctx.lineCap = 'round'; this.currentMask.ctx.beginPath(); this.currentMask.ctx.moveTo(fromPoint.x, fromPoint.y); this.currentMask.ctx.lineTo(toPoint.x, toPoint.y); this.currentMask.ctx.stroke(); + + this.currentMask.ctx.globalCompositeOperation = previousCompositeOperation; + this.currentMask.fabricImage.dirty = true; const strokeData = { - type: 'add', + type: this.isEraserActive ? 'erase' : 'add', maskName: this.currentMask.name, color: this.brushColor, from: { x: fromPoint.x, y: fromPoint.y }, @@ -1007,6 +760,36 @@ export class MaskBrushPlugin extends CustomBrushPlugin { this.canvasManager.emit('mask:stroke:added', strokeData); } + onUndoMaskStroke(strokeData) { + if (strokeData.maskName) { + this.undoLastStroke(strokeData.maskName); + } + } + + onRedoMaskStroke(strokeData) { + if (strokeData.maskName) { + this.redoLastStroke(strokeData.maskName, strokeData); + } + } + + destroy() { + this.detachAdditionalEventListeners(); + + this.masks.forEach(mask => { + this.canvas.remove(mask.fabricImage); + }); + this.masks = []; + this.currentMask = null; + + if (this.imageObject) { + this.imageObject.off('modified', this.onImageModified); + } + + this.canvasManager.off('undo:mask:stroke', this.onUndoMaskStroke); + this.canvasManager.off('redo:mask:stroke', this.onRedoMaskStroke); + super.destroy(); + } + onHandleSave() { const selectedOption = this.saveOptionsSelect.value; @@ -1031,28 +814,477 @@ export class MaskBrushPlugin extends CustomBrushPlugin { return this.saveOptions.find(opt => opt.value === optionValue)?.exportFunction || null; } - destroy() { - this.detachAdditionalEventListeners(); - this.masks.forEach(mask => { - this.canvas.remove(mask.fabricImage); + addResizeObserver() { + const canvasContainer = this.canvas.getElement().parentElement; + if (!canvasContainer) { + console.error('Canvas container element not found.'); + return; + } + + this.resizeObserver = new ResizeObserver(entries => { + for (let entry of entries) { + if (entry.target === canvasContainer) { + this.onImageModified(); + } + } }); - this.masks = []; - this.currentMask = null; - if (this.imageObject) { - this.imageObject.off('modified', this.onImageModified); + this.resizeObserver.observe(canvasContainer); + } + + removeResizeObserver(){ + if (this.resizeObserver) { + this.resizeObserver.disconnect(); + this.resizeObserver = null; } + } - this.canvasManager.off('undo:mask:stroke', this.onUndoMaskStroke); - this.canvasManager.off('redo:mask:stroke', this.onRedoMaskStroke); - this.canvasManager.off('pan:activated', this.onPanActivated); - this.canvasManager.off('pan:deactivated', this.onPanDeactivated); + onWindowResize() { + this.onImageModified(); + } + onKeyDown(e) { + super.onKeyDown(e); + if (e.key === 'e' || e.key === 'E') { + this.onToggleEraser(); + } + if (e.key === 'q' || e.key === 'Q') { + console.log('q pressed', e.key); + this.onToggleHideMaskKey(); + } + } + extendUI() { + const temp = document.createElement('div'); + temp.innerHTML = this.getHTML(); - if (this.unsubscribe) { - this.unsubscribe(); - this.isSubscribed = false; + while (temp.firstChild) { + this.uiContainer.appendChild(temp.firstChild); } - super.destroy(); + + this.addMaskBtn = this.uiContainer.querySelector('#addMaskBtn'); + this.maskList = this.uiContainer.querySelector('#maskList'); + this.applyColorToMaskCheckbox = this.uiContainer.querySelector('#applyColorToMaskCheckbox'); + this.moveMaskUpBtn = this.uiContainer.querySelector('#moveMaskUpBtn'); + this.moveMaskDownBtn = this.uiContainer.querySelector('#moveMaskDownBtn'); + this.removeMaskBtn = this.uiContainer.querySelector('#removeMaskBtn'); + this.clearMaskBtn = this.uiContainer.querySelector('#clearMaskBtn'); + this.clearAllMasksBtn = this.uiContainer.querySelector('#clearAllMasksBtn'); + this.resumeMasksBtn = this.uiContainer.querySelector('#resumeMasksBtn'); + this.hideMaskCheckbox = this.uiContainer.querySelector('#hideMaskCheckbox'); + this.hideAllMasksCheckbox = this.uiContainer.querySelector('#hideAllMasksCheckbox'); + this.hideMaskPreviewCheckbox = this.uiContainer.querySelector('#hideMaskPreviewCheckbox'); + this.saveOptionsSelect = this.uiContainer.querySelector('#saveOptionsSelect'); + this.saveMaskBtn = this.uiContainer.querySelector('#saveMaskBtn'); + this.eraserBtn = this.uiContainer.querySelector('#eraserBtn'); + this.disableDrawingModeBtn = this.uiContainer.querySelector('#disableDrawingModeBtn'); + + this.saveOptions = [ + { + value: 'saveCroppedMask', + text: 'Cropped Mask', + handler: () => this.maskExportUtilities.saveCroppedMask(), + exportFunction: () => this.maskExportUtilities.exportCroppedMask(), + show: true, + }, + { + value: 'saveCroppedAlphaOnImage', + text: 'Cropped Alpha Mask on Image', + handler: () => this.maskExportUtilities.saveCroppedAlphaOnImage(), + exportFunction: () => this.maskExportUtilities.exportCroppedAlphaOnImage(), + show: true, + }, + { + value: 'saveCroppedImage', + text: 'Cropped Image', + handler: () => this.maskExportUtilities.saveCroppedImage(), + exportFunction: () => this.maskExportUtilities.exportCroppedImage(), + show: true, + }, + { + value: 'saveCroppedAlphaMask', + text: 'Cropped Alpha Mask', + handler: () => this.maskExportUtilities.saveCroppedAlphaMask(), + exportFunction: () => this.maskExportUtilities.exportCroppedAlphaMask(), + show: true, + + }, + { + value: 'saveMask', + text: 'Mask', + handler: () => this.maskExportUtilities.saveMask(), + exportFunction: () => this.maskExportUtilities.exportMask(), + show: true, + }, + { + value: 'saveMaskAlphaOnImage', + text: 'Alpha Mask on Image', + handler: () => this.maskExportUtilities.saveMaskAlphaOnImage(), + exportFunction: () => this.maskExportUtilities.exportMaskAlphaOnImage(), + show: true, + }, + { + value: 'saveAllMasksAlphaOnImage', + text: 'All Masks As Alpha on Image', + handler: () => this.maskExportUtilities.saveAllMasksAlphaOnImage(), + exportFunction: () => this.maskExportUtilities.exportAllMasksAlphaOnImage(), + show: false, + }, + { + value: 'saveAllMasksCombinedAlphaOnImage', + text: 'All Masks As Alpha Combined on Image', + handler: () => this.maskExportUtilities.saveAllMasksCombinedAlphaOnImage(), + exportFunction: () => this.maskExportUtilities.exportAllMasksCombinedAlphaOnImage(), + show: false, + }, + { + value: 'saveAllMasks', + text: 'All Masks', + handler: () => this.maskExportUtilities.saveAllMasks(), + exportFunction: () => this.maskExportUtilities.exportAllMasks(), + show: false, + }, + { + value: 'saveAllMasksCombined', + text: 'All Masks Combined', + handler: () => this.maskExportUtilities.saveAllMasksCombined(), + exportFunction: () => this.maskExportUtilities.exportAllMasksCombined(), + show: false, + }, + { + value: 'saveAllMasksCombinedBW', + text: 'All Masks Combined (B&W)', + handler: () => this.maskExportUtilities.saveAllMasksCombinedBlackWhite(), + exportFunction: () => this.maskExportUtilities.exportMasksCombinedBlackWhite(), + show: false, + }, + { + value: 'saveMaskOnImage', + text: 'Mask on Image', + handler: () => this.maskExportUtilities.saveMaskOnImage(), + exportFunction: () => this.maskExportUtilities.exportMaskOnImage(), + show: true, + }, + { + value: 'saveAllMasksOnImage', + text: 'All Masks on Image', + handler: () => this.maskExportUtilities.saveAllMasksOnImage(), + exportFunction: () => this.maskExportUtilities.exportAllMasksOnImage(), + show: false, + }, + { + value: 'saveAllMasksCombinedOnImage', + text: 'All Masks Combined on Image', + handler: () => this.maskExportUtilities.saveAllMasksCombinedOnImage(), + exportFunction: () => this.maskExportUtilities.exportAllMasksCombinedOnImage(), + show: false, + }, + { + value: 'saveAllMasksCombinedBWOnImage', + text: 'All Masks Combined (B&W) on Image', + handler: () => this.maskExportUtilities.saveAllMasksCombinedBlackWhiteOnImage(), + exportFunction: () => this.maskExportUtilities.exportAllMasksCombinedBlackWhiteOnImage(), + show: false, + }, + ]; + + this.saveOptions.forEach(optionData => { + if (optionData.show) { + const option = document.createElement('option'); + option.value = optionData.value; + option.text = optionData.text; + this.saveOptionsSelect.add(option); + } + }); + + const updateConfigValue = (key, value) => { + const updated = {}; + updated[key] = value; + this.maskExportUtilities.setConfig(updated); + }; + + this.uiContainer.querySelector('#maskConfigPadding').addEventListener('input', (e) => { + updateConfigValue('padding', parseInt(e.target.value, 10)); + }); + + this.uiContainer.querySelector('#maskConfigResizeMask').addEventListener('change', (e) => { + updateConfigValue('resizeMask', e.target.checked); + }); + // resizeDimensions + this.uiContainer.querySelector('#maskConfigResizeDimensions').addEventListener('input', (e) => { + updateConfigValue('resizeDimensions', parseInt(e.target.value, 10)); + }); + // this.uiContainer.querySelector('#maskConfigResizeWidth').addEventListener('input', (e) => { + // updateConfigValue('resizeWidth', parseInt(e.target.value, 10)); + // }); + // this.uiContainer.querySelector('#maskConfigResizeHeight').addEventListener('input', (e) => { + // updateConfigValue('resizeHeight', parseInt(e.target.value, 10)); + // }); + this.uiContainer.querySelector('#maskConfigResizeKeepProportion').addEventListener('change', (e) => { + updateConfigValue('resizeKeepProportion', e.target.checked); + }); + this.uiContainer.querySelector('#maskConfigBlurMask').addEventListener('input', (e) => { + updateConfigValue('blurMask', parseInt(e.target.value, 10)); + }); + this.uiContainer.querySelector('#maskConfigBW').addEventListener('change', (e) => { + updateConfigValue('bw', e.target.checked); + }); + + this.uiContainer.addEventListener('click', (event) => { + const target = event.target; + + if (target.classList.contains('increment-button') || target.classList.contains('decrement-button') || + target.closest('.increment-button') || target.closest('.decrement-button')) { + + const button = target.classList.contains('increment-button') || target.classList.contains('decrement-button') + ? target + : target.closest('.increment-button') || target.closest('.decrement-button'); + + if (!button) return; + + const configKey = button.getAttribute('data-config-key'); + const step = parseInt(button.getAttribute('data-step'), 10) || 1; + const isIncrement = button.classList.contains('increment-button'); + const input = this.uiContainer.querySelector(`#maskConfig${this.capitalizeFirstLetter(configKey)}`); + if (input) { + let currentValue = parseInt(input.value, 10) || 0; + currentValue += isIncrement ? step : -step; + input.value = currentValue; + updateConfigValue(configKey, currentValue); + } + } + }); + } + + capitalizeFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + } + + clearAllMasksIcon() { + return ` + + + + + + + + + + `; + } + + getHTML() { + return ` + + + + + + + + +
+ + + + + +
+ +
+ + +
+ + +
+
+ + +
+ + +
+ + + + +
+ + +
+ + + + +
+ + + + + + +
+
+ + + +
+ +
+ +
+ + `; } + } diff --git a/web/core/js/common/components/canvas/MaskExportUtilities.js b/web/core/js/common/components/canvas/MaskExportUtilities.js index 5e005ea..aaea89b 100644 --- a/web/core/js/common/components/canvas/MaskExportUtilities.js +++ b/web/core/js/common/components/canvas/MaskExportUtilities.js @@ -1,24 +1,31 @@ import { store } from '../../scripts/stateManagerMain.js'; - export class MaskExportUtilities { constructor(plugin) { this.plugin = plugin; this.isSubscribed = false; this.config = { - padding: 50, - margin: 0, + padding: 0, resizeMask: true, - resizeWidth: 1024, - resizeHeight: 1024, + resizeWidth: 0, + resizeHeight: 0, + resizeDimensions: 1024, resizeKeepProportion: true, - blurMask: 25, - bw: true + blurMask: 50, + bw: true, }; + this.config.resizeWidth = this.config.resizeDimensions; + this.config.resizeHeight = this.config.resizeDimensions; + } + + setConfig(updatedConfig) { + this.config = { ...this.config, ...updatedConfig }; + this.config.resizeWidth = this.config.resizeDimensions; + this.config.resizeHeight = this.config.resizeDimensions; } _getBoundingBox(canvas) { - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d', { willReadFrequently: true }); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; let minX = canvas.width, minY = canvas.height, maxX = 0, maxY = 0; @@ -38,14 +45,11 @@ export class MaskExportUtilities { return { minX, minY, maxX, maxY }; } - _addPadding(canvas, padding, touchesEdges) { - const { touchesLeft, touchesRight, touchesTop, touchesBottom } = touchesEdges; - - const padLeft = touchesLeft ? 0 : padding / 2; - const padRight = touchesRight ? 0 : padding / 2; - const padTop = touchesTop ? 0 : padding / 2; - const padBottom = touchesBottom ? 0 : padding / 2; + const padLeft = touchesEdges.touchesLeft ? 0 : padding; + const padRight = touchesEdges.touchesRight ? 0 : padding; + const padTop = touchesEdges.touchesTop ? 0 : padding; + const padBottom = touchesEdges.touchesBottom ? 0 : padding; const paddedWidth = canvas.width + padLeft + padRight; const paddedHeight = canvas.height + padTop + padBottom; @@ -53,7 +57,7 @@ export class MaskExportUtilities { const paddedCanvas = document.createElement('canvas'); paddedCanvas.width = paddedWidth; paddedCanvas.height = paddedHeight; - const paddedCtx = paddedCanvas.getContext('2d'); + const paddedCtx = paddedCanvas.getContext('2d', { willReadFrequently: true }); paddedCtx.clearRect(0, 0, paddedWidth, paddedHeight); paddedCtx.drawImage(canvas, padLeft, padTop, canvas.width, canvas.height); @@ -61,50 +65,81 @@ export class MaskExportUtilities { return paddedCanvas; } - - _addMargin(canvas, margin, blurMask) { - if (margin <= 0) return canvas; - - const effectiveMargin = Math.max(margin, blurMask * 2); - const marginWidth = canvas.width + effectiveMargin; - const marginHeight = canvas.height + effectiveMargin; - - const marginCanvas = document.createElement('canvas'); - marginCanvas.width = marginWidth; - marginCanvas.height = marginHeight; - const marginCtx = marginCanvas.getContext('2d'); - - marginCtx.clearRect(0, 0, marginWidth, marginHeight); - const marginOffsetX = effectiveMargin / 2; - const marginOffsetY = effectiveMargin / 2; - marginCtx.drawImage(canvas, marginOffsetX, marginOffsetY, canvas.width, canvas.height); - - return marginCanvas; - } - _resizeCanvas(canvas, targetWidth, targetHeight, keepProportion) { if (!this.config.resizeMask) return canvas; + const originalWidth = canvas.width; + const originalHeight = canvas.height; + + let scaleX = targetWidth / originalWidth; + let scaleY = targetHeight / originalHeight; + if (keepProportion) { - const aspectRatio = canvas.width / canvas.height; - if (targetWidth / targetHeight > aspectRatio) { - targetWidth = Math.round(targetHeight * aspectRatio); - } else { - targetHeight = Math.round(targetWidth / aspectRatio); - } + const scale = Math.min(scaleX, scaleY); + scaleX = scale; + scaleY = scale; + targetWidth = Math.round(originalWidth * scale); + targetHeight = Math.round(originalHeight * scale); } const resizedCanvas = document.createElement('canvas'); resizedCanvas.width = targetWidth; resizedCanvas.height = targetHeight; - const resizedCtx = resizedCanvas.getContext('2d'); - resizedCtx.drawImage(canvas, 0, 0, targetWidth, targetHeight); + const resizedCtx = resizedCanvas.getContext('2d', { willReadFrequently: true }); + + resizedCtx.clearRect(0, 0, targetWidth, targetHeight); + const tempCanvas = document.createElement('canvas'); + tempCanvas.width = originalWidth; + tempCanvas.height = originalHeight; + const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true }); + tempCtx.drawImage(canvas, 0, 0); + + resizedCtx.save(); + resizedCtx.scale(scaleX, scaleY); + resizedCtx.drawImage(tempCanvas, 0, 0); + resizedCtx.restore(); return resizedCanvas; } + _convertToAlphaMask(canvas) { + const ctx = canvas.getContext('2d', { willReadFrequently: true }); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data = imageData.data; + + for (let i = 0; i < data.length; i += 4) { + const intensity = data[i]; + const alpha = 255 - intensity; + data[i] = 0; + data[i + 1] = 0; + data[i + 2] = 0; + data[i + 3] = alpha; + } + + ctx.putImageData(imageData, 0, 0); + return canvas; + } + + _addAlphaOnImage(imageCanvas, alphaMaskCanvas) { + const imageCtx = imageCanvas.getContext('2d', { willReadFrequently: true }); + const alphaCtx = alphaMaskCanvas.getContext('2d', { willReadFrequently: true }); + + const imageData = imageCtx.getImageData(0, 0, imageCanvas.width, imageCanvas.height); + const maskData = alphaCtx.getImageData(0, 0, alphaMaskCanvas.width, alphaMaskCanvas.height); + const imgDataArr = imageData.data; + const maskDataArr = maskData.data; + + for (let i = 0; i < imgDataArr.length; i += 4) { + const maskAlpha = maskDataArr[i + 3]; + imgDataArr[i + 3] = Math.min(imgDataArr[i + 3], maskAlpha); + } + + imageCtx.putImageData(imageData, 0, 0); + return imageCanvas; + } + _convertToBlackAndWhite(canvas) { - const ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d', { willReadFrequently: true }); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imageData.data; @@ -133,7 +168,7 @@ export class MaskExportUtilities { const blurredCanvas = document.createElement('canvas'); blurredCanvas.width = canvas.width; blurredCanvas.height = canvas.height; - const blurredCtx = blurredCanvas.getContext('2d'); + const blurredCtx = blurredCanvas.getContext('2d', { willReadFrequently: true }); blurredCtx.fillStyle = 'black'; blurredCtx.fillRect(0, 0, blurredCanvas.width, blurredCanvas.height); @@ -145,7 +180,6 @@ export class MaskExportUtilities { return blurredCanvas; } - downloadImage(dataUrl, filename) { const link = document.createElement('a'); link.href = dataUrl; @@ -155,13 +189,10 @@ export class MaskExportUtilities { document.body.removeChild(link); } - _processBoundingBox(canvas) { const { minX, minY, maxX, maxY } = this._getBoundingBox(canvas); if (maxX < minX || maxY < minY) { - // alert('Selected mask is completely transparent.'); - return null; } @@ -175,114 +206,176 @@ export class MaskExportUtilities { return { minX, minY, maxX, maxY, touchesEdges }; } - - exportCroppedMask(overrides = {}) { + exportCroppedAssets(overrides = {}) { const config = { ...this.config, ...overrides }; - if (!this.plugin.currentMask) { - // alert('No mask selected to export.'); + alert('No mask selected to export.'); + return null; + } + + if (!this.plugin.imageObject) { + alert('No image loaded to export.'); return null; } try { const maskCanvas = this.plugin.currentMask.canvasEl; - const boundingBox = this._processBoundingBox(maskCanvas); - if (!boundingBox) return null; + const maskBoundingBox = this._processBoundingBox(maskCanvas); + if (!maskBoundingBox) return null; - const { minX, minY, maxX, maxY, touchesEdges } = boundingBox; - let croppedWidth = maxX - minX + 1; - let croppedHeight = maxY - minY + 1; - const originalCroppedWidth = croppedWidth; - const originalCroppedHeight = croppedHeight; + const { minX, minY, maxX, maxY, touchesEdges } = maskBoundingBox; + const originalCroppedWidth = maxX - minX + 1; + const originalCroppedHeight = maxY - minY + 1; - const croppedCanvas = document.createElement('canvas'); - croppedCanvas.width = croppedWidth; - croppedCanvas.height = croppedHeight; - const croppedCtx = croppedCanvas.getContext('2d'); - croppedCtx.drawImage(maskCanvas, minX, minY, croppedWidth, croppedHeight, 0, 0, croppedWidth, croppedHeight); + const croppedMaskCanvas = document.createElement('canvas'); + croppedMaskCanvas.width = originalCroppedWidth; + croppedMaskCanvas.height = originalCroppedHeight; + const croppedMaskCtx = croppedMaskCanvas.getContext('2d', { willReadFrequently: true }); + croppedMaskCtx.drawImage( + maskCanvas, + minX, minY, originalCroppedWidth, originalCroppedHeight, + 0, 0, originalCroppedWidth, originalCroppedHeight + ); + + config.padding = (croppedMaskCanvas.width * 0.2); + const growMaskForFinalComposite = (croppedMaskCanvas.width * 0.1); + // console.log('config.padding', config.padding); + // console.log('(growMaskForFinalComposite',growMaskForFinalComposite); + let processedMaskCanvas = croppedMaskCanvas; if (config.padding > 0) { - const paddedCanvas = this._addPadding(croppedCanvas, config.padding, touchesEdges); - croppedWidth = paddedCanvas.width; - croppedHeight = paddedCanvas.height; - croppedCanvas.width = paddedCanvas.width; - croppedCanvas.height = paddedCanvas.height; - croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height); - croppedCtx.drawImage(paddedCanvas, 0, 0); + processedMaskCanvas = this._addPadding(croppedMaskCanvas, config.padding, touchesEdges); } - if (config.margin > 0) { - const marginCanvas = this._addMargin(croppedCanvas, config.margin, config.blurMask); - croppedWidth = marginCanvas.width; - croppedHeight = marginCanvas.height; - croppedCanvas.width = marginCanvas.width; - croppedCanvas.height = marginCanvas.height; - croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height); - croppedCtx.drawImage(marginCanvas, 0, 0); - } + const imageCanvas = document.createElement('canvas'); + const imageCtx = imageCanvas.getContext('2d', { willReadFrequently: true }); + imageCanvas.width = this.plugin.imageOriginalWidth; + imageCanvas.height = this.plugin.imageOriginalHeight; + imageCtx.drawImage( + this.plugin.imageObject.getElement(), + 0, + 0, + imageCanvas.width, + imageCanvas.height + ); + + const padLeft = touchesEdges.touchesLeft ? 0 : config.padding; + const padTop = touchesEdges.touchesTop ? 0 : config.padding; + const padRight = touchesEdges.touchesRight ? 0 : config.padding; + const padBottom = touchesEdges.touchesBottom ? 0 : config.padding; + + const paddedMinX = Math.max(minX - padLeft, 0); + const paddedMinY = Math.max(minY - padTop, 0); + const paddedMaxX = Math.min(maxX + padRight, imageCanvas.width - 1); + const paddedMaxY = Math.min(maxY + padBottom, imageCanvas.height - 1); + const croppedImageWidth = paddedMaxX - paddedMinX + 1; + const croppedImageHeight = paddedMaxY - paddedMinY + 1; + + const croppedImageCanvas = document.createElement('canvas'); + croppedImageCanvas.width = croppedImageWidth; + croppedImageCanvas.height = croppedImageHeight; + const croppedImageCtx = croppedImageCanvas.getContext('2d', { willReadFrequently: true }); + croppedImageCtx.drawImage( + imageCanvas, + paddedMinX, paddedMinY, croppedImageWidth, croppedImageHeight, + 0, 0, croppedImageWidth, croppedImageHeight + ); + + let finalMaskCanvas = processedMaskCanvas; + let finalImageCanvas = croppedImageCanvas; if (config.resizeMask) { - const resizedCanvas = this._resizeCanvas(croppedCanvas, config.resizeWidth, config.resizeHeight, config.resizeKeepProportion); - croppedWidth = resizedCanvas.width; - croppedHeight = resizedCanvas.height; - croppedCanvas.width = resizedCanvas.width; - croppedCanvas.height = resizedCanvas.height; - croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height); - croppedCtx.drawImage(resizedCanvas, 0, 0); + finalMaskCanvas = this._resizeCanvas( + processedMaskCanvas, + (config.resizeWidth+config.padding), + (config.resizeHeight+config.padding), + config.resizeKeepProportion + ); + + finalImageCanvas = this._resizeCanvas( + croppedImageCanvas, + (config.resizeWidth+config.padding), + (config.resizeHeight+config.padding), + config.resizeKeepProportion + ); } if (config.bw) { - this._convertToBlackAndWhite(croppedCanvas); + this._convertToBlackAndWhite(finalMaskCanvas); } if (config.blurMask > 0) { - const blurredCanvas = this._applyBlur(croppedCanvas, config.blurMask); - croppedCanvas.width = blurredCanvas.width; - croppedCanvas.height = blurredCanvas.height; - croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height); - croppedCtx.drawImage(blurredCanvas, 0, 0); + finalMaskCanvas = this._applyBlur(finalMaskCanvas, config.blurMask); } + const alphaMaskCanvas = document.createElement('canvas'); + alphaMaskCanvas.width = finalMaskCanvas.width; + alphaMaskCanvas.height = finalMaskCanvas.height; + const alphaMaskCtx = alphaMaskCanvas.getContext('2d', { willReadFrequently: true }); + alphaMaskCtx.drawImage(finalMaskCanvas, 0, 0); + + this._convertToAlphaMask(alphaMaskCanvas); + + const alphaOnImageCanvas = document.createElement('canvas'); + alphaOnImageCanvas.width = finalImageCanvas.width; + alphaOnImageCanvas.height = finalImageCanvas.height; + const alphaOnImageCtx = alphaOnImageCanvas.getContext('2d', { willReadFrequently: true }); + alphaOnImageCtx.drawImage(finalImageCanvas, 0, 0); + this._addAlphaOnImage(alphaOnImageCanvas, alphaMaskCanvas); + + const maskDataURL = finalMaskCanvas.toDataURL('image/png'); + const imageDataURL = finalImageCanvas.toDataURL('image/png'); + const alphaMaskDataURL = alphaMaskCanvas.toDataURL('image/png'); + const alphaOnImageDataURL = alphaOnImageCanvas.toDataURL('image/png'); + const croppedImage = { loadedImage: { width: this.plugin.imageOriginalWidth, height: this.plugin.imageOriginalHeight }, mask: { - x: minX, - y: minY, - width: originalCroppedWidth, - height: originalCroppedHeight, - resizePaddingWidth: croppedWidth, - resizePaddingHeight: croppedHeight + x: paddedMinX, + y: paddedMinY, + width: croppedImageWidth, + height: croppedImageHeight, + resizePaddingWidth: finalImageCanvas.width, + resizePaddingHeight: finalImageCanvas.height, + growMaskForFinalComposite: growMaskForFinalComposite }, }; + store.dispatch({ type: 'SET_CROPPED_IMAGE', payload: croppedImage }); - return croppedCanvas.toDataURL('image/png'); + return { + mask: maskDataURL, + image: imageDataURL, + alphaMask: alphaMaskDataURL, + alphaOnimage: alphaOnImageDataURL + }; } catch (error) { - console.error('Error exporting cropped mask:', error); - alert('An error occurred while exporting the cropped mask.'); + console.error('Error exporting cropped assets:', error); + alert('An error occurred while exporting the cropped assets.'); return null; } } + exportCroppedMask(overrides = {}) { + const assets = this.exportCroppedAssets(overrides); + return assets ? assets.mask : null; + } saveCroppedMask(overrides = {}) { - const config = { ...this.config, ...overrides }; - const dataURL = this.exportCroppedMask(overrides); - if (dataURL) { + const assets = this.exportCroppedAssets(overrides); + if (assets && assets.mask) { + const config = { ...this.config, ...overrides }; const filenameParts = [`${this.plugin.currentMask.name}_cropped`]; if (config.padding > 0) { filenameParts.push(`padded_${config.padding}`); } - if (config.margin > 0) { - filenameParts.push(`margin_${config.margin}`); - } if (config.resizeMask) { const proportion = config.resizeKeepProportion ? '_proportional' : ''; filenameParts.push(`resized_${config.resizeWidth}x${config.resizeHeight}${proportion}`); @@ -294,123 +387,19 @@ export class MaskExportUtilities { filenameParts.push(`bw_${config.bw}`); } const filename = `${filenameParts.join('_')}.png`; - this.downloadImage(dataURL, filename); + this.downloadImage(assets.mask, filename); } } - exportCroppedImage(overrides = {}) { - const config = { - padding: 0, - resizeMask: true, - resizeWidth: 1024, - resizeHeight: 1024, - resizeKeepProportion: true, - ...overrides - }; - - if (!this.plugin.imageObject) { - alert('No image loaded to export.'); - return null; - } - - if (!this.plugin.currentMask) { - alert('No mask selected to guide the cropping.'); - return null; - } - - try { - const imageCanvas = document.createElement('canvas'); - const imageCtx = imageCanvas.getContext('2d'); - imageCanvas.width = this.plugin.imageOriginalWidth; - imageCanvas.height = this.plugin.imageOriginalHeight; - imageCtx.drawImage( - this.plugin.imageObject.getElement(), - 0, - 0, - imageCanvas.width, - imageCanvas.height - ); - - const maskCanvas = this.plugin.currentMask.canvasEl; - const boundingBox = this._processBoundingBox(maskCanvas); - if (!boundingBox) return null; - - const { minX, minY, maxX, maxY } = boundingBox; - let croppedWidth = maxX - minX + 1; - let croppedHeight = maxY - minY + 1; - - const croppedCanvas = document.createElement('canvas'); - croppedCanvas.width = croppedWidth; - croppedCanvas.height = croppedHeight; - const croppedCtx = croppedCanvas.getContext('2d'); - croppedCtx.drawImage( - imageCanvas, - minX, - minY, - croppedWidth, - croppedHeight, - 0, - 0, - croppedWidth, - croppedHeight - ); - - if (config.padding > 0) { - const paddedCanvas = document.createElement('canvas'); - paddedCanvas.width = croppedWidth + config.padding; - paddedCanvas.height = croppedHeight + config.padding; - const paddedCtx = paddedCanvas.getContext('2d'); - - paddedCtx.clearRect(0, 0, paddedCanvas.width, paddedCanvas.height); - const offsetX = config.padding / 2; - const offsetY = config.padding / 2; - paddedCtx.drawImage( - croppedCanvas, - offsetX, - offsetY, - croppedWidth, - croppedHeight - ); - - croppedCanvas.width = paddedCanvas.width; - croppedCanvas.height = paddedCanvas.height; - croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height); - croppedCtx.drawImage(paddedCanvas, 0, 0); - croppedWidth = paddedCanvas.width; - croppedHeight = paddedCanvas.height; - } - - if (config.resizeMask) { - const resizedCanvas = this._resizeCanvas(croppedCanvas, config.resizeWidth, config.resizeHeight, config.resizeKeepProportion); - croppedCanvas.width = resizedCanvas.width; - croppedCanvas.height = resizedCanvas.height; - croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height); - croppedCtx.drawImage(resizedCanvas, 0, 0); - croppedWidth = resizedCanvas.width; - croppedHeight = resizedCanvas.height; - } - - return croppedCanvas.toDataURL('image/png'); - - } catch (error) { - console.error('Error exporting cropped image:', error); - alert('An error occurred while exporting the cropped image.'); - return null; - } + const assets = this.exportCroppedAssets(overrides); + return assets ? assets.image : null; } saveCroppedImage(overrides = {}) { - const config = { - padding: 0, - resizeMask: true, - resizeWidth: 1024, - resizeHeight: 1024, - resizeKeepProportion: true, - ...overrides - }; - const dataURL = this.exportCroppedImage(overrides); - if (dataURL) { + const assets = this.exportCroppedAssets(overrides); + if (assets && assets.image) { + const config = { ...this.config, ...overrides }; const filenameParts = [`${this.plugin.imageObject.name}_cropped`]; if (config.padding > 0) { filenameParts.push(`padded_${config.padding}`); @@ -419,184 +408,55 @@ export class MaskExportUtilities { const proportion = config.resizeKeepProportion ? '_proportional' : ''; filenameParts.push(`resized_${config.resizeWidth}x${config.resizeHeight}${proportion}`); } + if (config.blurMask > 0) { + filenameParts.push(`blurred_${config.blurMask}`); + } + if (config.bw) { + filenameParts.push(`bw_${config.bw}`); + } const filename = `${filenameParts.join('_')}.png`; - this.downloadImage(dataURL, filename); + this.downloadImage(assets.image, filename); } } exportCroppedAlphaOnImage(overrides = {}) { - const config = { - padding: 50, - resizeMask: true, - resizeWidth: 1024, - resizeHeight: 1024, - resizeKeepProportion: true, - blurMask: 25, - ...overrides - }; - - if (!this.plugin.imageObject) { - alert('No image loaded to export.'); - return null; - } - - if (!this.plugin.currentMask) { - alert('No mask selected to guide the cropping.'); - return null; - } - - try { - const imageCanvas = document.createElement('canvas'); - const imageCtx = imageCanvas.getContext('2d'); - imageCanvas.width = this.plugin.imageOriginalWidth; - imageCanvas.height = this.plugin.imageOriginalHeight; - imageCtx.drawImage( - this.plugin.imageObject.getElement(), - 0, - 0, - imageCanvas.width, - imageCanvas.height - ); - - const maskCanvas = this.plugin.currentMask.canvasEl; - const boundingBox = this._processBoundingBox(maskCanvas); - if (!boundingBox) return null; - - const { minX, minY, maxX, maxY } = boundingBox; - const originalCroppedWidth = maxX - minX + 1; - const originalCroppedHeight = maxY - minY + 1; - - const croppedCanvas = document.createElement('canvas'); - croppedCanvas.width = originalCroppedWidth; - croppedCanvas.height = originalCroppedHeight; - const croppedCtx = croppedCanvas.getContext('2d'); - croppedCtx.drawImage( - imageCanvas, - minX, - minY, - originalCroppedWidth, - originalCroppedHeight, - 0, - 0, - originalCroppedWidth, - originalCroppedHeight - ); + const assets = this.exportCroppedAssets(overrides); + return assets ? assets.alphaOnimage : null; + } + saveCroppedAlphaOnImage(overrides = {}) { + const assets = this.exportCroppedAssets(overrides); + if (assets && assets.alphaOnimage) { + const config = { ...this.config, ...overrides }; + const filenameParts = [`${this.plugin.imageObject.name}_cropped_alphaOnImage`]; if (config.padding > 0) { - const paddedCanvas = document.createElement('canvas'); - paddedCanvas.width = originalCroppedWidth + config.padding; - paddedCanvas.height = originalCroppedHeight + config.padding; - const paddedCtx = paddedCanvas.getContext('2d'); - - paddedCtx.clearRect(0, 0, paddedCanvas.width, paddedCanvas.height); - const offsetX = config.padding / 2; - const offsetY = config.padding / 2; - paddedCtx.drawImage( - croppedCanvas, - offsetX, - offsetY, - originalCroppedWidth, - originalCroppedHeight - ); - - croppedCanvas.width = paddedCanvas.width; - croppedCanvas.height = paddedCanvas.height; - croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height); - croppedCtx.drawImage(paddedCanvas, 0, 0); + filenameParts.push(`padded_${config.padding}`); } - - const scaleXOriginal = croppedCanvas.width; - const scaleYOriginal = croppedCanvas.height; - if (config.resizeMask) { - const resizedCanvas = this._resizeCanvas(croppedCanvas, config.resizeWidth, config.resizeHeight, config.resizeKeepProportion); - croppedCanvas.width = resizedCanvas.width; - croppedCanvas.height = resizedCanvas.height; - croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height); - croppedCtx.drawImage(resizedCanvas, 0, 0); - } - - const tempMaskCanvas = document.createElement('canvas'); - tempMaskCanvas.width = croppedCanvas.width; - tempMaskCanvas.height = croppedCanvas.height; - const tempMaskCtx = tempMaskCanvas.getContext('2d'); - tempMaskCtx.drawImage( - maskCanvas, - minX, - minY, - originalCroppedWidth, - originalCroppedHeight, - 0, - 0, - croppedCanvas.width, - croppedCanvas.height - ); - - const tempMaskImageData = tempMaskCtx.getImageData(0, 0, croppedCanvas.width, croppedCanvas.height); - const tempMaskData = tempMaskImageData.data; - - const croppedImageData = croppedCtx.getImageData(0, 0, croppedCanvas.width, croppedCanvas.height); - const croppedData = croppedImageData.data; - - for (let i = 0; i < croppedData.length; i += 4) { - const maskAlpha = tempMaskData[i + 3]; - // Invert the alpha: 255 - maskAlpha - croppedData[i + 3] = 255 - maskAlpha; + const proportion = config.resizeKeepProportion ? '_proportional' : ''; + filenameParts.push(`resized_${config.resizeWidth}x${config.resizeHeight}${proportion}`); } - - croppedCtx.putImageData(croppedImageData, 0, 0); - if (config.blurMask > 0) { - const blurredCanvas = this._applyBlur(croppedCanvas, config.blurMask); - croppedCanvas.width = blurredCanvas.width; - croppedCanvas.height = blurredCanvas.height; - croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height); - croppedCtx.drawImage(blurredCanvas, 0, 0); + filenameParts.push(`blurred_${config.blurMask}`); } - - const finalCroppedWidth = croppedCanvas.width; - const finalCroppedHeight = croppedCanvas.height; - const croppedImage = { - loadedImage: { - width: imageCanvas.width, - height: imageCanvas.height - }, - mask: { - x: minX, - y: minY, - width: originalCroppedWidth, - height: originalCroppedHeight, - resizePaddingWidth: finalCroppedWidth, - resizePaddingHeight: finalCroppedHeight - }, - }; - store.dispatch({ - type: 'SET_CROPPED_IMAGE', - payload: croppedImage - }); - - return croppedCanvas.toDataURL('image/png'); - - } catch (error) { - console.error('Error exporting cropped alpha on image:', error); - alert('An error occurred while exporting the cropped alpha on image.'); - return null; + if (config.bw) { + filenameParts.push(`bw_${config.bw}`); + } + const filename = `${filenameParts.join('_')}.png`; + this.downloadImage(assets.alphaOnimage, filename); } } - saveCroppedAlphaOnImage(overrides = {}) { - const config = { - padding: 50, - resizeMask: true, - resizeWidth: 1024, - resizeHeight: 1024, - resizeKeepProportion: true, - blurMask: 25, - ...overrides - }; - const dataURL = this.exportCroppedAlphaOnImage(overrides); - if (dataURL) { - const filenameParts = [`${this.plugin.imageObject.name}_cropped_alpha_on_image`]; + exportCroppedAlphaMask(overrides = {}) { + const assets = this.exportCroppedAssets(overrides); + return assets ? assets.alphaMask : null; + } + + saveCroppedAlphaMask(overrides = {}) { + const assets = this.exportCroppedAssets(overrides); + if (assets && assets.alphaMask) { + const config = { ...this.config, ...overrides }; + const filenameParts = [`${this.plugin.currentMask.name}_cropped_alphaMask`]; if (config.padding > 0) { filenameParts.push(`padded_${config.padding}`); } @@ -607,16 +467,20 @@ export class MaskExportUtilities { if (config.blurMask > 0) { filenameParts.push(`blurred_${config.blurMask}`); } + if (config.bw) { + filenameParts.push(`bw_${config.bw}`); + } const filename = `${filenameParts.join('_')}.png`; - this.downloadImage(dataURL, filename); + this.downloadImage(assets.alphaMask, filename); } } + // old createCombinedAlphaMask() { const alphaCanvas = document.createElement('canvas'); alphaCanvas.width = this.plugin.imageOriginalWidth; alphaCanvas.height = this.plugin.imageOriginalHeight; - const alphaCtx = alphaCanvas.getContext('2d'); + const alphaCtx = alphaCanvas.getContext('2d', { willReadFrequently: true }); alphaCtx.clearRect(0, 0, alphaCanvas.width, alphaCanvas.height); this.plugin.masks.forEach(mask => { @@ -641,14 +505,14 @@ export class MaskExportUtilities { const combinedCanvas = document.createElement('canvas'); combinedCanvas.width = this.plugin.imageOriginalWidth; combinedCanvas.height = this.plugin.imageOriginalHeight; - const combinedCtx = combinedCanvas.getContext('2d'); + const combinedCtx = combinedCanvas.getContext('2d', { willReadFrequently: true }); combinedCtx.drawImage(this.plugin.imageObject.getElement(), 0, 0, combinedCanvas.width, combinedCanvas.height); const alphaMask = document.createElement('canvas'); alphaMask.width = this.plugin.imageOriginalWidth; alphaMask.height = this.plugin.imageOriginalHeight; - const alphaCtx = alphaMask.getContext('2d'); + const alphaCtx = alphaMask.getContext('2d', { willReadFrequently: true }); alphaCtx.drawImage(this.plugin.currentMask.canvasEl, 0, 0, alphaMask.width, alphaMask.height); combinedCtx.globalCompositeOperation = 'destination-out'; @@ -665,7 +529,6 @@ export class MaskExportUtilities { } } - exportAllMasksAlphaOnImage() { if (!this.plugin.imageObject) { alert('No image loaded to save the alphas on.'); @@ -682,14 +545,14 @@ export class MaskExportUtilities { const combinedCanvas = document.createElement('canvas'); combinedCanvas.width = this.plugin.imageOriginalWidth; combinedCanvas.height = this.plugin.imageOriginalHeight; - const combinedCtx = combinedCanvas.getContext('2d'); + const combinedCtx = combinedCanvas.getContext('2d', { willReadFrequently: true }); combinedCtx.drawImage(this.plugin.imageObject.getElement(), 0, 0, combinedCanvas.width, combinedCanvas.height); const alphaMask = document.createElement('canvas'); alphaMask.width = this.plugin.imageOriginalWidth; alphaMask.height = this.plugin.imageOriginalHeight; - const alphaCtx = alphaMask.getContext('2d'); + const alphaCtx = alphaMask.getContext('2d', { willReadFrequently: true }); alphaCtx.drawImage(mask.canvasEl, 0, 0, alphaMask.width, alphaMask.height); combinedCtx.globalCompositeOperation = 'destination-out'; @@ -726,7 +589,7 @@ export class MaskExportUtilities { const combinedCanvas = document.createElement('canvas'); combinedCanvas.width = this.plugin.imageOriginalWidth; combinedCanvas.height = this.plugin.imageOriginalHeight; - const combinedCtx = combinedCanvas.getContext('2d'); + const combinedCtx = combinedCanvas.getContext('2d', { willReadFrequently: true }); combinedCtx.drawImage(this.plugin.imageObject.getElement(), 0, 0, combinedCanvas.width, combinedCanvas.height); @@ -777,7 +640,7 @@ export class MaskExportUtilities { const combinedCanvas = document.createElement('canvas'); combinedCanvas.width = this.plugin.imageOriginalWidth; combinedCanvas.height = this.plugin.imageOriginalHeight; - const combinedCtx = combinedCanvas.getContext('2d'); + const combinedCtx = combinedCanvas.getContext('2d', { willReadFrequently: true }); combinedCtx.drawImage(this.plugin.imageObject.getElement(), 0, 0, combinedCanvas.width, combinedCanvas.height); @@ -804,7 +667,7 @@ export class MaskExportUtilities { const combinedCanvas = document.createElement('canvas'); combinedCanvas.width = this.plugin.imageOriginalWidth; combinedCanvas.height = this.plugin.imageOriginalHeight; - const combinedCtx = combinedCanvas.getContext('2d'); + const combinedCtx = combinedCanvas.getContext('2d', { willReadFrequently: true }); this.plugin.masks.forEach(mask => { combinedCtx.drawImage(mask.canvasEl, 0, 0, combinedCanvas.width, combinedCanvas.height); @@ -829,7 +692,7 @@ export class MaskExportUtilities { const combinedCanvas = document.createElement('canvas'); combinedCanvas.width = this.plugin.imageOriginalWidth; combinedCanvas.height = this.plugin.imageOriginalHeight; - const combinedCtx = combinedCanvas.getContext('2d'); + const combinedCtx = combinedCanvas.getContext('2d', { willReadFrequently: true }); combinedCtx.fillStyle = 'black'; combinedCtx.fillRect(0, 0, combinedCanvas.width, combinedCanvas.height); @@ -838,7 +701,7 @@ export class MaskExportUtilities { const maskCanvas = document.createElement('canvas'); maskCanvas.width = this.plugin.imageOriginalWidth; maskCanvas.height = this.plugin.imageOriginalHeight; - const maskCtx = maskCanvas.getContext('2d'); + const maskCtx = maskCanvas.getContext('2d', { willReadFrequently: true }); maskCtx.drawImage(mask.canvasEl, 0, 0, maskCanvas.width, maskCanvas.height); @@ -848,9 +711,9 @@ export class MaskExportUtilities { for (let i = 0; i < data.length; i += 4) { const alpha = data[i + 3]; if (alpha > 0) { - data[i] = 255; // Red - data[i + 1] = 255; // Green - data[i + 2] = 255; // Blue + data[i] = 255; + data[i + 1] = 255; + data[i + 2] = 255; } } @@ -883,7 +746,7 @@ export class MaskExportUtilities { const combinedCanvas = document.createElement('canvas'); combinedCanvas.width = this.plugin.imageOriginalWidth; combinedCanvas.height = this.plugin.imageOriginalHeight; - const combinedCtx = combinedCanvas.getContext('2d'); + const combinedCtx = combinedCanvas.getContext('2d', { willReadFrequently: true }); combinedCtx.drawImage(this.plugin.imageObject.getElement(), 0, 0, combinedCanvas.width, combinedCanvas.height); combinedCtx.drawImage(this.plugin.currentMask.canvasEl, 0, 0, combinedCanvas.width, combinedCanvas.height); diff --git a/web/core/js/common/scripts/ThemeManager.js b/web/core/js/common/scripts/ThemeManager.js index c0a5623..d2d3fdb 100644 --- a/web/core/js/common/scripts/ThemeManager.js +++ b/web/core/js/common/scripts/ThemeManager.js @@ -127,7 +127,7 @@ class ThemeManager { if (this.themes.length === 0) { console.warn('No themes found in the CSS file.'); } else { - console.info(`Loaded ${this.themes.length} themes from CSS.`); + // console.info(`Loaded ${this.themes.length} themes from CSS.`); } if (!this.themes.some(t => t.value === 'create-custom')) { @@ -190,9 +190,9 @@ class ThemeManager { } if (this.externalCustomThemes.length === 0) { - console.info('No external custom themes loaded.'); + // console.info('No external custom themes loaded.'); } else { - console.info(`Loaded ${this.externalCustomThemes.length} external custom theme styles.`); + // console.info(`Loaded ${this.externalCustomThemes.length} external custom theme styles.`); } } catch (error) { console.error('Error loading external custom themes:', error); @@ -267,9 +267,9 @@ class ThemeManager { selector.setAttribute('aria-label', 'Select Theme'); targetElement.appendChild(selector); - console.info('Theme selector dropdown created and injected.'); + // console.info('Theme selector dropdown created and injected.'); } else { - console.info('Theme selector dropdown already exists. Populating with themes.'); + // console.info('Theme selector dropdown already exists. Populating with themes.'); selector.innerHTML = ''; } @@ -330,7 +330,7 @@ class ThemeManager { selector.removeEventListener('change', this.handleThemeChange); selector.addEventListener('change', this.handleThemeChange.bind(this)); - console.info('Theme selector dropdown populated with themes.'); + // console.info('Theme selector dropdown populated with themes.'); } groupThemesBySet(themes) { diff --git a/web/core/js/common/scripts/injectStylesheet.js b/web/core/js/common/scripts/injectStylesheet.js index 475ec6d..634acab 100644 --- a/web/core/js/common/scripts/injectStylesheet.js +++ b/web/core/js/common/scripts/injectStylesheet.js @@ -16,7 +16,7 @@ function injectStylesheet(href, id = null) { link.onload = function() { this.onload = null; this.rel = 'stylesheet'; - console.log(`Stylesheet "${href}" has been loaded and applied.`); + // console.log(`Stylesheet "${href}" has been loaded and applied.`); }; link.onerror = function() { diff --git a/web/core/js/common/scripts/stateManagerMain.js b/web/core/js/common/scripts/stateManagerMain.js index 41afbca..cba80fd 100644 --- a/web/core/js/common/scripts/stateManagerMain.js +++ b/web/core/js/common/scripts/stateManagerMain.js @@ -78,6 +78,8 @@ class StateManager { return { ...state, croppedImage: action.payload }; case 'SET_MASKING_TYPE': return { ...state, maskingType: action.payload }; + case 'SET_QUEUE_RUNNING': + return { ...state, isQueueRunning: action.payload }; case 'RESET_STATE': return this.constructor.initialState; default: @@ -116,4 +118,5 @@ export const store = new StateManager({ hideMask:false, croppedImage: {}, maskingType: 'full', // Possible values: 'full', 'cropped' + isQueueRunning: false, }); diff --git a/web/core/js/common/scripts/templates.js b/web/core/js/common/scripts/templates.js index 76fa6c1..845e97a 100644 --- a/web/core/js/common/scripts/templates.js +++ b/web/core/js/common/scripts/templates.js @@ -710,7 +710,34 @@ const componentTemplates = { } } }, + UNETLoader: { + type: 'components', + nodeClass: 'UNETLoader', + components: [ + { + type: 'dropdown', + params: { + label: 'Model', + nodePath: '{nodeId}.inputs.unet_name', + key: 'unet_name', + url: 'UNETLoader' + + }, + }, + { + type: 'dropdown', + params: { + label: 'Type', + nodePath: '{nodeId}.inputs.weight_dtype', + key: 'weight_dtype', + url: 'UNETLoader', + }, + }, + ], + }, + + UnetLoaderGGUF: { type: 'component', nodeClass: null, component: { @@ -719,7 +746,7 @@ const componentTemplates = { label: 'Model', nodePath: '{nodeId}.inputs.unet_name', key: 'unet_name', - url: 'UNETLoader' + url: 'UnetLoaderGGUF' } } }, @@ -837,5 +864,65 @@ const componentTemplates = { }, ], }, + GrowMask_Expand: { + type: 'component', + nodeClass: null, + component: { + type: 'dataComponent', + params: { + name: 'growMaskForFinalComposite', + nodePath: '228.inputs.expand', + dataPath: 'croppedImage.mask.growMaskForFinalComposite', + } + } + }, + ImageCompositeMasked_X: { + type: 'component', + nodeClass: null, + component: { + type: 'dataComponent', + params: { + name: 'croppedImageMmaskX', + nodePath: '201.inputs.x', + dataPath: 'croppedImage.mask.x', + } + } + }, + ImageCompositeMasked_Y: { + type: 'component', + nodeClass: null, + component: { + type: 'dataComponent', + params: { + name: 'croppedImageMmaskY', + nodePath: '201.inputs.y', + dataPath: 'croppedImage.mask.y', + } + } + }, + ImageCompositeMasked_Width: { + type: 'component', + nodeClass: null, + component: { + type: 'dataComponent', + params: { + name: 'croppedImageMmaskWidth', + nodePath: '225.inputs.width', + dataPath: 'croppedImage.mask.width', + } + } + }, + ImageCompositeMasked_Height: { + type: 'component', + nodeClass: null, + component: { + type: 'dataComponent', + params: { + name: 'croppedImageMmaskHeight', + nodePath: '225.inputs.height', + dataPath: 'croppedImage.mask.height', + } + } + }, }; export { componentTemplates }; diff --git a/web/core/loadScripts.js b/web/core/loadScripts.js index 2142e09..a21b49f 100644 --- a/web/core/loadScripts.js +++ b/web/core/loadScripts.js @@ -25,7 +25,7 @@ const loadScript = (src) => { script.onload = resolve; script.onerror = reject; document.head.appendChild(script); - console.log(`${src} loaded`); + // console.log(`${src} loaded`); }); }; diff --git a/web/core/main.js b/web/core/main.js index 3a6d17e..7e8308c 100644 --- a/web/core/main.js +++ b/web/core/main.js @@ -262,6 +262,7 @@ import { store } from './js/common/scripts/stateManagerMain.js'; async function queue() { + console.log("Queueing new job"); if (canvasLoader && canvasLoader.isInitialized) { await CanvasComponent(flowConfig, workflow, canvasLoader); @@ -290,7 +291,10 @@ import { store } from './js/common/scripts/stateManagerMain.js'; console.log(`Added job to queue. Job ID: ${jobId}`); console.log("Current queue:", StateManager.getJobQueue()); console.log("queued workflow:", workflow); - + store.dispatch({ + type: 'SET_QUEUE_RUNNING', + payload: true + }); updateQueueDisplay(StateManager.getJobQueue()); if (!StateManager.isProcessing()) { @@ -303,6 +307,10 @@ import { store } from './js/common/scripts/stateManagerMain.js'; type: 'TOGGLE_MASK', payload: true }); + + + + if (StateManager.isProcessing()) return; if (StateManager.getJobQueue().length === 0) { @@ -341,7 +349,7 @@ import { store } from './js/common/scripts/stateManagerMain.js'; if (!response.ok) { throw new Error('Failed to process prompt.'); } - const result = await response.json(); + // const result = await response.json(); } catch (error) { console.error('Error processing prompt:', error); throw error; @@ -384,9 +392,10 @@ import { store } from './js/common/scripts/stateManagerMain.js'; } async function queue_interrupt() { - showSpinner('Interrupting...'); + console.log("Interrupting last job"); const data = { 'client_id': client_id }; try { + showSpinner(); const response = await fetch('/interrupt', { method: 'POST', cache: 'no-cache', @@ -398,18 +407,17 @@ import { store } from './js/common/scripts/stateManagerMain.js'; if (!response.ok) { throw new Error('Failed to interrupt the process.'); } - const result = await response.json(); console.log('Interrupted:', result); } catch (error) { console.error('Error during interrupt:', error); + hideSpinner(); } finally { - // hideSpinner(); + hideSpinner(); } } document.getElementById('generateButton').addEventListener('click', function () { - console.log("Queueing new job"); queue(); }); document.addEventListener('keydown', function(event) { @@ -419,7 +427,6 @@ import { store } from './js/common/scripts/stateManagerMain.js'; } }); document.getElementById('interruptButton').addEventListener('click', function () { - console.log("Interrupting last job"); interrupt(); }); diff --git a/web/flow/js/main.js b/web/flow/js/main.js index c5b524a..ab7074c 100644 --- a/web/flow/js/main.js +++ b/web/flow/js/main.js @@ -20,7 +20,7 @@ const priorityFlowIds = [ 'n0y8e', 'yigqn', '12slf', - '89hf7', + '28s1h', 'y2gic', 'f9k2j', 'k5ttn',