Files
2025-12-28 21:23:18 +08:00

170 lines
4.9 KiB
JavaScript

import { app } from "../../scripts/app.js";
import { openMaskEditor } from "./RGBYPMaskEditor_ui.js";
export const GP = {
nodes: new Map(),
baseNode: null,
};
export function releaseImage(img) {
if (!img) return;
try {
img.onload = null;
img.onerror = null;
img.src = "";
img = null;
} catch (_) {
}
}
export function cleanupEditorState(nodeId) {
const state = getNodeState(nodeId);
if (!state) return;
if (state.baseImg) {
releaseImage(state.baseImg);
state.baseImg = null;
}
if (state.maskImg) {
releaseImage(state.maskImg);
state.maskImg = null;
}
state.originalCanvas = null;
state.maskCanvas = null;
state.canvasContainer = null;
state.centralPanel = null;
state.drawCursor = null;
state.colorButtons = [];
state.brushSlider = null;
state.opacitySlider = null;
state.brushBtn = null;
state.eraseBtn = null;
state.scrollBtn = null;
state.clearBtn = null;
state.saveBtn = null;
state.helpIcon = null;
state.helpPanel = null;
state.helpCloseBtn = null;
state.overlayDialog = null;
state.dialogElement = null;
}
export function setNodeState(nodeId, partial) {
const state = getNodeState(nodeId);
Object.assign(state, partial); // updates only the specified fields
GP.nodes.set(nodeId, state);
};
export function getNodeState(nodeId) {
if (!GP.nodes.has(nodeId)) {
GP.nodes.set(nodeId, {
overlayDialog: null,
dialogElement: null,
helpPanel: null,
helpIcon: null,
helpCloseBtn: null,
baseImg: null,
maskImg: null,
originalCanvas: null,
maskCanvas: null,
centralPanel: null,
canvasContainer: null,
zoom: 1,
zoomPrevWidth: null,
zoomPrevHeight: null,
drawCursor: null,
isDrawing: false,
drawLastX: 0,
drawLastY: 0,
drawBrushSize: 50,
drawColor: 0,
colorButtons: [],
lastCursorClientX: 0,
lastCursorClientY: 0,
maskOpacity: 0.75,
currentTool: 'Brush',
prevTool: 'Brush',
brushBtn: null,
eraserBtn: null,
scrollBtn: null,
clearBtn: null,
drawMode: 'draw',
tempOriginal: null,
tempMask: null,
tempComposite: null,
});
}
return GP.nodes.get(nodeId);
};
export const HELP = `
<h2>Hotkeys</h2>
<table style="border-spacing: 5px; border-collapse:separate;">
<tr><td><b>1, 2, 3, 4, 5</b></td><td>Change brush color</td></tr>
<tr><td><b>SHIFT + 1</b></td><td>Also changes brush color</td></tr>
<tr><td><b>SHIFT + A / SHIFT + D</b></td><td>Brush size</td></tr>
<tr><td><b>SHIFT + W / SHIFT + S</b></td><td>Mask opacity</td></tr>
<tr><td><b>SHIFT + Z / SHIFT + X</b></td><td>Zoom (or use the mouse wheel)</td></tr>
<tr><td><b>SHIFT + C</b></td><td>Fit image to window</td></tr>
<tr><td><b>SHIFT + V</b></td><td>Generate automatic masks. Each press creates the next one</td></tr>
<tr><td><b>SPACE / SHIFT + SPACE</b></td><td>Image scroll mode</td></tr>
<tr><td><b>ESC</b></td><td>Close the window without saving</td></tr>
<tr><td><b>ENTER</b></td><td>Save and close the window</td></tr>
</table>
`;
/**
* Minimal RGBYP Mask Editor extension.
* - Registers only a context-menu entry.
* - Does NOT load or process any images.
*/
app.registerExtension({
name: "RGBYPMaskEditor",
async setup(appInstance) {
const LGraphCanvas = window.LGraphCanvas;
if (!LGraphCanvas) return;
const proto = LGraphCanvas.prototype;
const origGetNodeMenuOptions = proto.getNodeMenuOptions;
proto.getNodeMenuOptions = function (node) {
GP.baseNode = node
// setNodeState(node.id, { baseNode: node });
let options = [];
try {
options = origGetNodeMenuOptions
? origGetNodeMenuOptions.call(this, node) || []
: [];
} catch (e) {
options = options || [];
}
// Visual separator
options.unshift(null);
// Simple menu item that only opens the stub dialog
options.unshift({
content: `
<span style="display:flex;align-items:center;gap:6px;">
<img src="/extensions/ComfyUI-RGBYP-Mask-Editor/i_menu.png"
style="width:16px;height:16px;image-rendering:pixelated;">
<span>RGBYP Mask Editor</span>
</span>
`,
callback: () => {
openMaskEditor(node);
},
});
return options;
};
},
});