From 826f448af9c40b58838771121609189238a12090 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Wed, 2 Jul 2025 00:21:53 +0200 Subject: [PATCH] Add documentation for core modules and update Canvas.js Added documentation files for ComfyApi, ComfyApp, LitegraphService, and MaskEditor, summarizing their main functions and usage. Refactored js/Canvas.js to improve mask processing logic, using viewport pan for cropping and applying mask color only to non-transparent pixels. Also made minor formatting and logging consistency improvements throughout Canvas.js. --- Doc/ComfyApi | 96 +++++++++++++++++++++++++++++++++++++++ Doc/ComfyApp | 72 +++++++++++++++++++++++++++++ Doc/LitegraphService | 75 ++++++++++++++++++++++++++++++ Doc/MaskEditor | 76 +++++++++++++++++++++++++++++++ js/Canvas.js | 106 +++++++++++++++++++++---------------------- 5 files changed, 372 insertions(+), 53 deletions(-) create mode 100644 Doc/ComfyApi create mode 100644 Doc/ComfyApp create mode 100644 Doc/LitegraphService create mode 100644 Doc/MaskEditor diff --git a/Doc/ComfyApi b/Doc/ComfyApi new file mode 100644 index 0000000..d02ebe1 --- /dev/null +++ b/Doc/ComfyApi @@ -0,0 +1,96 @@ +# ComfyApi - Function Documentation Summary import { api } from "../../scripts/api.js"; + +## Basic Information + +ComfyApi is a class for communication with ComfyUI backend via WebSocket and REST API. + +## Main Functions: + +### Connection and Initialization + +- constructor() - Initializes API, sets host and base path +- init() - Starts WebSocket connection for real-time updates +- #createSocket() - Creates and manages WebSocket connection + +### URL Management + +- internalURL(route) - Generates URL for internal endpoints +- apiURL(route) - Generates URL for public API endpoints +- fileURL(route) - Generates URL for static files +- fetchApi(route, options) - Performs HTTP requests with automatic user headers + +### Event Handling + +- addEventListener(type, callback) - Listens for API events (status, executing, progress, etc.) +- removeEventListener(type, callback) - Removes event listeners +- dispatchCustomEvent(type, detail) - Emits custom events + +### Queue and Prompt Management + +- queuePrompt(number, data) - Adds prompt to execution queue +- getQueue() - Gets current queue state (Running/Pending) +- interrupt() - Interrupts currently executing prompt +- clearItems(type) - Clears queue or history +- deleteItem(type, id) - Removes item from queue or history + +### History and Statistics + +- getHistory(max_items) - Gets history of executed prompts +- getSystemStats() - Gets system statistics (Python, OS, GPU, etc.) +- getLogs() - Gets system logs +- getRawLogs() - Gets raw logs +- subscribeLogs(enabled) - Enables/disables log subscription + +### Model and Resource Management + +- getNodeDefs(options) - Gets definitions of available nodes +- getExtensions() - List of installed extensions +- getEmbeddings() - List of available embeddings +- getModelFolders() - List of model folders +- getModels(folder) - List of models in given folder +- viewMetadata(folder, model) - Metadata of specific model + +### Workflow Templates + +- getWorkflowTemplates() - Gets workflow templates from custom nodes +- getCoreWorkflowTemplates() - Gets core workflow templates + +### User Management + +- getUserConfig() - Gets user configuration +- createUser(username) - Creates new user +- getSettings() - Gets all user settings +- getSetting(id) - Gets specific setting +- storeSettings(settings) - Saves settings dictionary +- storeSetting(id, value) - Saves single setting + +### User Data + +- getUserData(file) - Gets user data file +- storeUserData(file, data, options) - Saves user data +- deleteUserData(file) - Deletes user data file +- moveUserData(source, dest) - Moves data file +- listUserDataFullInfo(dir) - Lists files with full information + +### Other + +- getFolderPaths() - Gets system folder paths +- getCustomNodesI18n() - Gets internationalization data for custom nodes + +## Important Properties + +- clientId - Client ID from WebSocket +- authToken - Authorization token for ComfyOrg account +- apiKey - API key for ComfyOrg account +- socket - Active WebSocket connection + +## WebSocket Event Types + +- status - System status +- executing - Currently executing node +- progress - Execution progress +- executed - Node executed +- execution_start/success/error/interrupted/cached - Execution events +- logs - System logs +- b_preview - Image preview (binary) +- reconnecting/reconnected - Connection events diff --git a/Doc/ComfyApp b/Doc/ComfyApp new file mode 100644 index 0000000..934b68a --- /dev/null +++ b/Doc/ComfyApp @@ -0,0 +1,72 @@ +## __Main ComfyApp Functions__ import { app, ComfyApp } from "../../scripts/app.js"; + +### __Application Management__ + +- `setup(canvasEl)` - Initializes the application on the page, loads extensions, registers nodes +- `resizeCanvas()` - Adjusts canvas size to window +- `clean()` - Clears application state (node outputs, image previews, errors) + +### __Workflow Management__ + +- `loadGraphData(graphData, clean, restore_view, workflow, options)` - Loads workflow data from JSON +- `loadApiJson(apiData, fileName)` - Loads workflow from API format +- `graphToPrompt(graph, options)` - Converts graph to prompt for execution +- `handleFile(file)` - Handles file loading (PNG, WebP, JSON, MP3, MP4, SVG, etc.) + +### __Execution__ + +- `queuePrompt(number, batchCount, queueNodeIds)` - Queues prompt for execution +- `registerNodes()` - Registers node definitions from backend +- `registerNodeDef(nodeId, nodeDef)` - Registers single node definition +- `refreshComboInNodes()` - Refreshes combo lists in nodes + +### __Node Management__ + +- `registerExtension(extension)` - Registers ComfyUI extension +- `updateVueAppNodeDefs(defs)` - Updates node definitions in Vue app +- `revokePreviews(nodeId)` - Frees memory for node previews + +### __Clipboard__ + +- `copyToClipspace(node)` - Copies node to clipboard +- `pasteFromClipspace(node)` - Pastes data from clipboard to node + +### __Position Conversion__ + +- `clientPosToCanvasPos(pos)` - Converts client position to canvas position +- `canvasPosToClientPos(pos)` - Converts canvas position to client position + +### __Error Handling__ + +- `showErrorOnFileLoad(file)` - Displays file loading error +- `#showMissingNodesError(missingNodeTypes)` - Shows missing nodes error +- `#showMissingModelsError(missingModels, paths)` - Shows missing models error + +### __Internal Handlers__ + +- `#addDropHandler()` - Handles drag and drop of files +- `#addProcessKeyHandler()` - Handles keyboard input +- `#addDrawNodeHandler()` - Modifies node drawing behavior +- `#addApiUpdateHandlers()` - Handles API updates +- `#addConfigureHandler()` - Graph configuration flag +- `#addAfterConfigureHandler()` - Post-configuration handling + +### __Deprecated Properties__ + +Many properties are marked as deprecated and redirect to appropriate stores: + +- `lastNodeErrors` → `useExecutionStore().lastNodeErrors` +- `lastExecutionError` → `useExecutionStore().lastExecutionError` +- `runningNodeId` → `useExecutionStore().executingNodeId` +- `shiftDown` → `useWorkspaceStore().shiftDown` +- `widgets` → `useWidgetStore().widgets` +- `extensions` → `useExtensionStore().extensions` + +### __Utility Functions__ + +- `sanitizeNodeName(string)` - Cleans node name from dangerous characters +- `getPreviewFormatParam()` - Returns preview format parameter +- `getRandParam()` - Returns random parameter for refresh +- `isApiJson(data)` - Checks if data is in API JSON format + +This application uses Vue and TypeScript composition pattern, where many functionalities are separated into different services and stores (e.g., `useExecutionStore`, `useWorkflowService`, `useExtensionService`, etc.). diff --git a/Doc/LitegraphService b/Doc/LitegraphService new file mode 100644 index 0000000..0c2fb5e --- /dev/null +++ b/Doc/LitegraphService @@ -0,0 +1,75 @@ +LitegraphService Documentation + +Main functions of useLitegraphService() + +Node Registration and Creation Functions: + +registerNodeDef(nodeId: string, nodeDefV1: ComfyNodeDefV1) + +- Registers node definition in LiteGraph system +- Creates ComfyNode class with inputs, outputs and widgets +- Adds context menu, background drawing and keyboard handling +- Invokes extensions before registration + +addNodeOnGraph(nodeDef, options) + +- Adds new node to graph at specified position +- By default places node at canvas center + +Navigation and View Functions: + +getCanvasCenter(): Vector2 + +- Returns canvas center coordinates accounting for DPI + +goToNode(nodeId: NodeId) + +- Animates transition to specified node on canvas + +resetView() + +- Resets canvas view to default settings (scale 1, offset [0,0]) + +fitView() + +- Fits canvas view to show all nodes + +Node Handling Functions (internal): + +addNodeContextMenuHandler(node) + +- Adds context menu with options: + + - Open/Copy/Save image (for image nodes) + - Bypass node + - Copy/Paste to Clipspace + - Open in MaskEditor (for image nodes) + +addDrawBackgroundHandler(node) + +- Adds node background drawing logic +- Handles image, animation and video previews +- Manages thumbnail display + +addNodeKeyHandler(node) + +- Adds keyboard handling: + + - Left/Right arrows: navigate between images + - Escape: close image preview + +ComfyNode Class (created by registerNodeDef): + +Main methods: + +- #addInputs() - adds inputs and widgets to node +- #addOutputs() - adds outputs to node +- configure() - configures node from serialized data +- #setupStrokeStyles() - sets border styles (errors, execution, etc.) + +Properties: + +- comfyClass - ComfyUI class name +- nodeData - node definition +- Automatic yellow coloring for API nodes + diff --git a/Doc/MaskEditor b/Doc/MaskEditor new file mode 100644 index 0000000..9bce49b --- /dev/null +++ b/Doc/MaskEditor @@ -0,0 +1,76 @@ +MASKEDITOR.TS FUNCTION DOCUMENTATION + +MaskEditorDialog - Main mask editor class + +- getInstance() - Singleton pattern, returns editor instance +- show() - Opens the mask editor +- save() - Saves mask to server +- destroy() - Closes and cleans up editor +- isOpened() - Checks if editor is open + +CanvasHistory - Change history management + +- saveState() - Saves current canvas state +- undo() - Undo last operation +- redo() - Redo undone operation +- clearStates() - Clears history + +BrushTool - Brush tool + +- setBrushSize(size) - Sets brush size +- setBrushOpacity(opacity) - Sets brush opacity +- setBrushHardness(hardness) - Sets brush hardness +- setBrushType(type) - Sets brush shape (circle/square) +- startDrawing() - Starts drawing +- handleDrawing() - Handles drawing during movement +- drawEnd() - Ends drawing + +PaintBucketTool - Fill tool + +- floodFill(point) - Fills area with color from point +- setTolerance(tolerance) - Sets color tolerance +- setFillOpacity(opacity) - Sets fill opacity +- invertMask() - Inverts mask + +ColorSelectTool - Color selection tool + +- fillColorSelection(point) - Selects similar colors +- setTolerance(tolerance) - Sets selection tolerance +- setLivePreview(enabled) - Enables/disables live preview +- setComparisonMethod(method) - Sets color comparison method +- setApplyWholeImage(enabled) - Applies to whole image +- setSelectOpacity(opacity) - Sets selection opacity + +UIManager - Interface management + +- updateBrushPreview() - Updates brush preview +- setBrushVisibility(visible) - Shows/hides brush +- screenToCanvas(coords) - Converts screen coordinates to canvas +- getMaskColor() - Returns mask color +- setSaveButtonEnabled(enabled) - Enables/disables save button + +ToolManager - Tool management + +- setTool(tool) - Sets active tool +- getCurrentTool() - Returns active tool +- handlePointerDown/Move/Up() - Handles mouse/touch events + +PanAndZoomManager - View management + +- zoom(event) - Zooms in/out canvas +- handlePanStart/Move() - Handles canvas panning +- initializeCanvasPanZoom() - Initializes canvas view +- smoothResetView() - Smoothly resets view + +MessageBroker - Communication system + +- publish(topic, data) - Publishes message +- subscribe(topic, callback) - Subscribes to topic +- pull(topic, data) - Pulls data from topic +- createPullTopic/PushTopic() - Creates communication topics + +KeyboardManager - Keyboard handling + +- addListeners() - Adds keyboard listeners +- removeListeners() - Removes listeners +- isKeyDown(key) - Checks if key is pressed diff --git a/js/Canvas.js b/js/Canvas.js index 6f5129a..b282b90 100644 --- a/js/Canvas.js +++ b/js/Canvas.js @@ -26,7 +26,7 @@ export class Canvas { this.node = node; this.widget = widget; this.canvas = document.createElement('canvas'); - this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); + this.ctx = this.canvas.getContext('2d', {willReadFrequently: true}); this.width = 512; this.height = 512; this.layers = []; @@ -60,7 +60,7 @@ export class Canvas { log.debug('Canvas widget element:', this.node); log.info('Canvas initialized', { nodeId: this.node.id, - dimensions: { width: this.width, height: this.height }, + dimensions: {width: this.width, height: this.height}, viewport: this.viewport }); @@ -187,7 +187,7 @@ export class Canvas { * @param {boolean} replaceLast - Czy zastąpić ostatni stan w historii */ saveState(replaceLast = false) { - log.debug('Saving canvas state', { replaceLast, layersCount: this.layers.length }); + log.debug('Saving canvas state', {replaceLast, layersCount: this.layers.length}); this.canvasState.saveState(replaceLast); this.incrementOperationCount(); this._notifyStateChange(); @@ -200,11 +200,11 @@ export class Canvas { log.info('Performing undo operation'); const historyInfo = this.canvasState.getHistoryInfo(); log.debug('History state before undo:', historyInfo); - + this.canvasState.undo(); this.incrementOperationCount(); this._notifyStateChange(); - + log.debug('Undo completed, layers count:', this.layers.length); } @@ -216,11 +216,11 @@ export class Canvas { log.info('Performing redo operation'); const historyInfo = this.canvasState.getHistoryInfo(); log.debug('History state before redo:', historyInfo); - + this.canvasState.redo(); this.incrementOperationCount(); this._notifyStateChange(); - + log.debug('Redo completed, layers count:', this.layers.length); } @@ -246,17 +246,17 @@ export class Canvas { */ removeSelectedLayers() { if (this.selectedLayers.length > 0) { - log.info('Removing selected layers', { + log.info('Removing selected layers', { layersToRemove: this.selectedLayers.length, - totalLayers: this.layers.length + totalLayers: this.layers.length }); - + this.saveState(); this.layers = this.layers.filter(l => !this.selectedLayers.includes(l)); this.updateSelection([]); this.render(); this.saveState(); - + log.debug('Layers removed successfully, remaining layers:', this.layers.length); } else { log.debug('No layers selected for removal'); @@ -271,13 +271,13 @@ export class Canvas { const previousSelection = this.selectedLayers.length; this.selectedLayers = newSelection || []; this.selectedLayer = this.selectedLayers.length > 0 ? this.selectedLayers[this.selectedLayers.length - 1] : null; - + log.debug('Selection updated', { previousCount: previousSelection, newCount: this.selectedLayers.length, selectedLayerIds: this.selectedLayers.map(l => l.id || 'unknown') }); - + if (this.onSelectionChange) { this.onSelectionChange(); } @@ -321,10 +321,10 @@ export class Canvas { * @param {boolean} sendCleanImage - Czy wysłać czysty obraz (bez maski) do editora */ async startMaskEditor(predefinedMask = null, sendCleanImage = true) { - log.info('Starting mask editor', { - hasPredefinedMask: !!predefinedMask, + log.info('Starting mask editor', { + hasPredefinedMask: !!predefinedMask, sendCleanImage, - layersCount: this.layers.length + layersCount: this.layers.length }); this.savedMaskState = await this.saveMaskState(); @@ -683,7 +683,7 @@ export class Canvas { throw new Error("Old mask editor canvas not found"); } - const maskCtx = maskCanvas.getContext('2d', { willReadFrequently: true }); + const maskCtx = maskCanvas.getContext('2d', {willReadFrequently: true}); const maskColor = {r: 255, g: 255, b: 255}; const processedMask = await this.processMaskForEditor(maskData, maskCanvas.width, maskCanvas.height, maskColor); @@ -699,59 +699,58 @@ export class Canvas { * @param {number} targetHeight - Docelowa wysokość * @param {Object} maskColor - Kolor maski {r, g, b} * @returns {HTMLCanvasElement} Przetworzona maska - */ - async processMaskForEditor(maskData, targetWidth, targetHeight, maskColor) { - const originalWidth = maskData.width || maskData.naturalWidth || this.width; - const originalHeight = maskData.height || maskData.naturalHeight || this.height; + */async processMaskForEditor(maskData, targetWidth, targetHeight, maskColor) { + // Współrzędne przesunięcia (pan) widoku edytora + const panX = this.maskTool.x; + const panY = this.maskTool.y; log.info("Processing mask for editor:", { - originalSize: {width: originalWidth, height: originalHeight}, + sourceSize: {width: maskData.width, height: maskData.height}, targetSize: {width: targetWidth, height: targetHeight}, - canvasSize: {width: this.width, height: this.height} + viewportPan: {x: panX, y: panY} }); const tempCanvas = document.createElement('canvas'); tempCanvas.width = targetWidth; tempCanvas.height = targetHeight; - const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true }); + const tempCtx = tempCanvas.getContext('2d', {willReadFrequently: true}); - tempCtx.clearRect(0, 0, targetWidth, targetHeight); + const sourceX = -panX; + const sourceY = -panY; + tempCtx.drawImage( + maskData, // Źródło: pełna maska z "output area" + sourceX, // sx: Prawdziwa współrzędna X na dużej masce (np. 1000) + sourceY, // sy: Prawdziwa współrzędna Y na dużej masce (np. 1000) + targetWidth, // sWidth: Szerokość wycinanego fragmentu + targetHeight, // sHeight: Wysokość wycinanego fragmentu + 0, // dx: Gdzie wkleić w płótnie docelowym (zawsze 0) + 0, // dy: Gdzie wkleić w płótnie docelowym (zawsze 0) + targetWidth, // dWidth: Szerokość wklejanego obrazu + targetHeight // dHeight: Wysokość wklejanego obrazu + ); - const scaleToOriginal = Math.min(originalWidth / this.width, originalHeight / this.height); - - const scaledWidth = this.width * scaleToOriginal; - const scaledHeight = this.height * scaleToOriginal; - - const offsetX = (targetWidth - scaledWidth) / 2; - const offsetY = (targetHeight - scaledHeight) / 2; - - tempCtx.drawImage(maskData, offsetX, offsetY, scaledWidth, scaledHeight); - - log.info("Mask drawn scaled to original image size:", { - originalSize: {width: originalWidth, height: originalHeight}, - targetSize: {width: targetWidth, height: targetHeight}, - canvasSize: {width: this.width, height: this.height}, - scaleToOriginal: scaleToOriginal, - finalSize: {width: scaledWidth, height: scaledHeight}, - offset: {x: offsetX, y: offsetY} + log.info("Mask viewport cropped correctly.", { + source: "maskData", + cropArea: {x: sourceX, y: sourceY, width: targetWidth, height: targetHeight} }); + // Reszta kodu (zmiana koloru) pozostaje bez zmian const imageData = tempCtx.getImageData(0, 0, targetWidth, targetHeight); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { - const alpha = data[i + 3]; // Oryginalny kanał alpha - - data[i] = maskColor.r; // R - data[i + 1] = maskColor.g; // G - data[i + 2] = maskColor.b; // B - data[i + 3] = alpha; // Zachowaj oryginalny alpha + const alpha = data[i + 3]; + if (alpha > 0) { + data[i] = maskColor.r; + data[i + 1] = maskColor.g; + data[i + 2] = maskColor.b; + } } tempCtx.putImageData(imageData, 0, 0); - log.info("Mask processing completed - full size scaling applied"); + log.info("Mask processing completed - color applied."); return tempCanvas; } @@ -784,6 +783,7 @@ export class Canvas { setTimeout(this.waitWhileMaskEditing.bind(this), 100); } } + /** * Zapisuje obecny stan maski przed otwarciem editora * @returns {Object} Zapisany stan maski @@ -797,7 +797,7 @@ export class Canvas { const savedCanvas = document.createElement('canvas'); savedCanvas.width = maskCanvas.width; savedCanvas.height = maskCanvas.height; - const savedCtx = savedCanvas.getContext('2d', { willReadFrequently: true }); + const savedCtx = savedCanvas.getContext('2d', {willReadFrequently: true}); savedCtx.drawImage(maskCanvas, 0, 0); return { @@ -878,7 +878,7 @@ export class Canvas { resultImage.onload = resolve; resultImage.onerror = reject; }); - + log.debug("Result image loaded successfully", { width: resultImage.width, height: resultImage.height @@ -893,7 +893,7 @@ export class Canvas { const tempCanvas = document.createElement('canvas'); tempCanvas.width = this.width; tempCanvas.height = this.height; - const tempCtx = tempCanvas.getContext('2d', { willReadFrequently: true }); + const tempCtx = tempCanvas.getContext('2d', {willReadFrequently: true}); tempCtx.drawImage(resultImage, 0, 0, this.width, this.height); @@ -920,7 +920,7 @@ export class Canvas { const destX = -this.maskTool.x; const destY = -this.maskTool.y; - log.debug("Applying mask to canvas", { destX, destY }); + log.debug("Applying mask to canvas", {destX, destY}); maskCtx.globalCompositeOperation = 'source-over'; maskCtx.clearRect(destX, destY, this.width, this.height);