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',