refactor: share mask result application
This commit is contained in:
@@ -5,7 +5,7 @@ import { ComfyApp } from "../../scripts/app.js";
|
||||
import { createModuleLogger } from "./log_system/log_funcs.js";
|
||||
import { showErrorNotification } from "./utils/NotificationUtils.js";
|
||||
import { uploadImageBlob } from "./utils/ImageUploadUtils.js";
|
||||
import { createMaskImageFromResult, processMaskForViewport } from "./utils/MaskProcessingUtils.js";
|
||||
import { applyMaskResultToTool, processMaskForViewport } from "./utils/MaskProcessingUtils.js";
|
||||
import { updateNodePreview } from "./utils/PreviewUtils.js";
|
||||
import { mask_editor_showing, mask_editor_listen_for_cancel } from "./utils/mask_utils.js";
|
||||
import { createCanvas } from "./utils/CommonUtils.js";
|
||||
@@ -414,16 +414,15 @@ export class MaskEditorIntegration {
|
||||
// Process image to mask using MaskProcessingUtils
|
||||
log.debug("Processing image to mask using utils");
|
||||
const bounds = this.canvas.outputAreaBounds;
|
||||
const maskAsImage = await createMaskImageFromResult(resultImage, {
|
||||
targetWidth: bounds.width,
|
||||
targetHeight: bounds.height,
|
||||
invertAlpha: true
|
||||
});
|
||||
log.debug("Applying mask using chunk system", {
|
||||
boundsPos: { x: bounds.x, y: bounds.y },
|
||||
maskSize: { width: bounds.width, height: bounds.height }
|
||||
});
|
||||
this.maskTool.setMask(maskAsImage);
|
||||
await applyMaskResultToTool(resultImage, {
|
||||
targetWidth: bounds.width,
|
||||
targetHeight: bounds.height,
|
||||
invertAlpha: true
|
||||
}, () => this.maskTool);
|
||||
// Update node preview using PreviewUtils
|
||||
await updateNodePreview(this.canvas, this.node, true);
|
||||
this.savedMaskState = null;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ComfyApp } from "../../scripts/app.js";
|
||||
import { createModuleLogger } from "./log_system/log_funcs.js";
|
||||
import { showInfoNotification, showSuccessNotification, showErrorNotification } from "./utils/NotificationUtils.js";
|
||||
import { uploadCanvasAsImage, uploadImageBlob } from "./utils/ImageUploadUtils.js";
|
||||
import { createMaskImageFromResult } from "./utils/MaskProcessingUtils.js";
|
||||
import { applyMaskResultToTool } from "./utils/MaskProcessingUtils.js";
|
||||
import { updateNodePreview } from "./utils/PreviewUtils.js";
|
||||
import { validateAndFixClipspace } from "./utils/ClipspaceUtils.js";
|
||||
const log = createModuleLogger('SAMDetectorIntegration');
|
||||
@@ -305,40 +305,41 @@ async function handleSAMDetectorResult(node, resultImage) {
|
||||
}
|
||||
// Process image to mask using MaskProcessingUtils
|
||||
log.debug("Processing image to mask using utils");
|
||||
const maskAsImage = await createMaskImageFromResult(resultImage, {
|
||||
let actualCanvas;
|
||||
await applyMaskResultToTool(resultImage, {
|
||||
targetWidth: resultImage.width,
|
||||
targetHeight: resultImage.height,
|
||||
invertAlpha: true
|
||||
});
|
||||
// Apply mask to LayerForge canvas using MaskTool.setMask method
|
||||
log.debug("Checking canvas and maskTool availability", {
|
||||
hasCanvas: !!canvas,
|
||||
hasCanvasProperty: !!canvas.canvas,
|
||||
canvasCanvasKeys: canvas.canvas ? Object.keys(canvas.canvas) : [],
|
||||
hasMaskTool: !!canvas.maskTool,
|
||||
hasCanvasMaskTool: !!(canvas.canvas && canvas.canvas.maskTool),
|
||||
maskToolType: typeof canvas.maskTool,
|
||||
canvasMaskToolType: canvas.canvas ? typeof canvas.canvas.maskTool : 'undefined',
|
||||
canvasKeys: Object.keys(canvas)
|
||||
});
|
||||
// Get the actual Canvas object and its maskTool
|
||||
const actualCanvas = canvas.canvas || canvas;
|
||||
const maskTool = actualCanvas.maskTool;
|
||||
if (!maskTool) {
|
||||
log.error("MaskTool is not available. Canvas state:", {
|
||||
}, () => {
|
||||
// Apply mask to LayerForge canvas using MaskTool.setMask method
|
||||
log.debug("Checking canvas and maskTool availability", {
|
||||
hasCanvas: !!canvas,
|
||||
hasActualCanvas: !!actualCanvas,
|
||||
canvasConstructor: canvas.constructor.name,
|
||||
actualCanvasConstructor: actualCanvas ? actualCanvas.constructor.name : 'undefined',
|
||||
canvasKeys: Object.keys(canvas),
|
||||
actualCanvasKeys: actualCanvas ? Object.keys(actualCanvas) : [],
|
||||
maskToolValue: maskTool
|
||||
hasCanvasProperty: !!canvas.canvas,
|
||||
canvasCanvasKeys: canvas.canvas ? Object.keys(canvas.canvas) : [],
|
||||
hasMaskTool: !!canvas.maskTool,
|
||||
hasCanvasMaskTool: !!(canvas.canvas && canvas.canvas.maskTool),
|
||||
maskToolType: typeof canvas.maskTool,
|
||||
canvasMaskToolType: canvas.canvas ? typeof canvas.canvas.maskTool : 'undefined',
|
||||
canvasKeys: Object.keys(canvas)
|
||||
});
|
||||
throw new Error("Mask tool not available or not initialized");
|
||||
}
|
||||
log.debug("Applying SAM mask to canvas using setMask method");
|
||||
// Use the setMask method which clears existing mask and sets new one
|
||||
maskTool.setMask(maskAsImage);
|
||||
// Get the actual Canvas object and its maskTool
|
||||
actualCanvas = canvas.canvas || canvas;
|
||||
const maskTool = actualCanvas.maskTool;
|
||||
if (!maskTool) {
|
||||
log.error("MaskTool is not available. Canvas state:", {
|
||||
hasCanvas: !!canvas,
|
||||
hasActualCanvas: !!actualCanvas,
|
||||
canvasConstructor: canvas.constructor.name,
|
||||
actualCanvasConstructor: actualCanvas ? actualCanvas.constructor.name : 'undefined',
|
||||
canvasKeys: Object.keys(canvas),
|
||||
actualCanvasKeys: actualCanvas ? Object.keys(actualCanvas) : [],
|
||||
maskToolValue: maskTool
|
||||
});
|
||||
throw new Error("Mask tool not available or not initialized");
|
||||
}
|
||||
log.debug("Applying SAM mask to canvas using setMask method");
|
||||
return maskTool;
|
||||
});
|
||||
// Update canvas and save state (same as MaskEditorIntegration)
|
||||
actualCanvas.render();
|
||||
actualCanvas.saveState();
|
||||
|
||||
@@ -55,6 +55,11 @@ export async function createMaskImageFromResult(sourceImage, options) {
|
||||
});
|
||||
return convertToImage(processedMask);
|
||||
}
|
||||
export async function applyMaskResultToTool(sourceImage, options, resolveTarget) {
|
||||
const maskImage = await createMaskImageFromResult(sourceImage, options);
|
||||
resolveTarget().setMask(maskImage);
|
||||
return maskImage;
|
||||
}
|
||||
/**
|
||||
* Processes image data with custom pixel transformation
|
||||
* @param sourceImage - Source image or canvas element
|
||||
|
||||
@@ -7,7 +7,7 @@ import {api} from "../../scripts/api.js";
|
||||
import { createModuleLogger } from "./log_system/log_funcs.js";
|
||||
import { showErrorNotification } from "./utils/NotificationUtils.js";
|
||||
import { uploadCanvasAsImage, uploadCanvasWithMaskAsImage, uploadImageBlob } from "./utils/ImageUploadUtils.js";
|
||||
import { createMaskImageFromResult, processMaskForViewport } from "./utils/MaskProcessingUtils.js";
|
||||
import { applyMaskResultToTool, processMaskForViewport } from "./utils/MaskProcessingUtils.js";
|
||||
import { updateNodePreview } from "./utils/PreviewUtils.js";
|
||||
import { mask_editor_showing, mask_editor_listen_for_cancel } from "./utils/mask_utils.js";
|
||||
import { createCanvas } from "./utils/CommonUtils.js";
|
||||
@@ -499,18 +499,16 @@ export class MaskEditorIntegration {
|
||||
// Process image to mask using MaskProcessingUtils
|
||||
log.debug("Processing image to mask using utils");
|
||||
const bounds = this.canvas.outputAreaBounds;
|
||||
const maskAsImage = await createMaskImageFromResult(resultImage, {
|
||||
targetWidth: bounds.width,
|
||||
targetHeight: bounds.height,
|
||||
invertAlpha: true
|
||||
});
|
||||
|
||||
log.debug("Applying mask using chunk system", {
|
||||
boundsPos: {x: bounds.x, y: bounds.y},
|
||||
maskSize: {width: bounds.width, height: bounds.height}
|
||||
});
|
||||
|
||||
this.maskTool.setMask(maskAsImage);
|
||||
await applyMaskResultToTool(resultImage, {
|
||||
targetWidth: bounds.width,
|
||||
targetHeight: bounds.height,
|
||||
invertAlpha: true
|
||||
}, () => this.maskTool);
|
||||
|
||||
// Update node preview using PreviewUtils
|
||||
await updateNodePreview(this.canvas, this.node, true);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ComfyApp } from "../../scripts/app.js";
|
||||
import { createModuleLogger } from "./log_system/log_funcs.js";
|
||||
import { showInfoNotification, showSuccessNotification, showErrorNotification } from "./utils/NotificationUtils.js";
|
||||
import { uploadCanvasAsImage, uploadImageBlob } from "./utils/ImageUploadUtils.js";
|
||||
import { createMaskImageFromResult } from "./utils/MaskProcessingUtils.js";
|
||||
import { applyMaskResultToTool } from "./utils/MaskProcessingUtils.js";
|
||||
import { updateNodePreview } from "./utils/PreviewUtils.js";
|
||||
import { validateAndFixClipspace } from "./utils/ClipspaceUtils.js";
|
||||
import type { ComfyNode } from './types';
|
||||
@@ -346,45 +346,44 @@ async function handleSAMDetectorResult(node: ComfyNode, resultImage: HTMLImageEl
|
||||
|
||||
// Process image to mask using MaskProcessingUtils
|
||||
log.debug("Processing image to mask using utils");
|
||||
const maskAsImage = await createMaskImageFromResult(resultImage, {
|
||||
let actualCanvas: any;
|
||||
await applyMaskResultToTool(resultImage, {
|
||||
targetWidth: resultImage.width,
|
||||
targetHeight: resultImage.height,
|
||||
invertAlpha: true
|
||||
});
|
||||
|
||||
// Apply mask to LayerForge canvas using MaskTool.setMask method
|
||||
log.debug("Checking canvas and maskTool availability", {
|
||||
hasCanvas: !!canvas,
|
||||
hasCanvasProperty: !!canvas.canvas,
|
||||
canvasCanvasKeys: canvas.canvas ? Object.keys(canvas.canvas) : [],
|
||||
hasMaskTool: !!canvas.maskTool,
|
||||
hasCanvasMaskTool: !!(canvas.canvas && canvas.canvas.maskTool),
|
||||
maskToolType: typeof canvas.maskTool,
|
||||
canvasMaskToolType: canvas.canvas ? typeof canvas.canvas.maskTool : 'undefined',
|
||||
canvasKeys: Object.keys(canvas)
|
||||
});
|
||||
|
||||
// Get the actual Canvas object and its maskTool
|
||||
const actualCanvas = canvas.canvas || canvas;
|
||||
const maskTool = actualCanvas.maskTool;
|
||||
|
||||
if (!maskTool) {
|
||||
log.error("MaskTool is not available. Canvas state:", {
|
||||
}, () => {
|
||||
// Apply mask to LayerForge canvas using MaskTool.setMask method
|
||||
log.debug("Checking canvas and maskTool availability", {
|
||||
hasCanvas: !!canvas,
|
||||
hasActualCanvas: !!actualCanvas,
|
||||
canvasConstructor: canvas.constructor.name,
|
||||
actualCanvasConstructor: actualCanvas ? actualCanvas.constructor.name : 'undefined',
|
||||
canvasKeys: Object.keys(canvas),
|
||||
actualCanvasKeys: actualCanvas ? Object.keys(actualCanvas) : [],
|
||||
maskToolValue: maskTool
|
||||
hasCanvasProperty: !!canvas.canvas,
|
||||
canvasCanvasKeys: canvas.canvas ? Object.keys(canvas.canvas) : [],
|
||||
hasMaskTool: !!canvas.maskTool,
|
||||
hasCanvasMaskTool: !!(canvas.canvas && canvas.canvas.maskTool),
|
||||
maskToolType: typeof canvas.maskTool,
|
||||
canvasMaskToolType: canvas.canvas ? typeof canvas.canvas.maskTool : 'undefined',
|
||||
canvasKeys: Object.keys(canvas)
|
||||
});
|
||||
throw new Error("Mask tool not available or not initialized");
|
||||
}
|
||||
|
||||
log.debug("Applying SAM mask to canvas using setMask method");
|
||||
// Get the actual Canvas object and its maskTool
|
||||
actualCanvas = canvas.canvas || canvas;
|
||||
const maskTool = actualCanvas.maskTool;
|
||||
|
||||
// Use the setMask method which clears existing mask and sets new one
|
||||
maskTool.setMask(maskAsImage);
|
||||
if (!maskTool) {
|
||||
log.error("MaskTool is not available. Canvas state:", {
|
||||
hasCanvas: !!canvas,
|
||||
hasActualCanvas: !!actualCanvas,
|
||||
canvasConstructor: canvas.constructor.name,
|
||||
actualCanvasConstructor: actualCanvas ? actualCanvas.constructor.name : 'undefined',
|
||||
canvasKeys: Object.keys(canvas),
|
||||
actualCanvasKeys: actualCanvas ? Object.keys(actualCanvas) : [],
|
||||
maskToolValue: maskTool
|
||||
});
|
||||
throw new Error("Mask tool not available or not initialized");
|
||||
}
|
||||
|
||||
log.debug("Applying SAM mask to canvas using setMask method");
|
||||
return maskTool;
|
||||
});
|
||||
|
||||
// Update canvas and save state (same as MaskEditorIntegration)
|
||||
actualCanvas.render();
|
||||
|
||||
@@ -121,6 +121,20 @@ export async function createMaskImageFromResult(
|
||||
return convertToImage(processedMask);
|
||||
}
|
||||
|
||||
interface MaskResultTarget {
|
||||
setMask(image: HTMLImageElement): void;
|
||||
}
|
||||
|
||||
export async function applyMaskResultToTool(
|
||||
sourceImage: HTMLImageElement | HTMLCanvasElement,
|
||||
options: MaskResultOptions,
|
||||
resolveTarget: () => MaskResultTarget
|
||||
): Promise<HTMLImageElement> {
|
||||
const maskImage = await createMaskImageFromResult(sourceImage, options);
|
||||
resolveTarget().setMask(maskImage);
|
||||
return maskImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes image data with custom pixel transformation
|
||||
* @param sourceImage - Source image or canvas element
|
||||
|
||||
@@ -2,7 +2,10 @@ import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
|
||||
import { createMaskImageFromResult } from '../js/utils/MaskProcessingUtils.js';
|
||||
import {
|
||||
applyMaskResultToTool,
|
||||
createMaskImageFromResult,
|
||||
} from '../js/utils/MaskProcessingUtils.js';
|
||||
|
||||
function installMaskConversionStubs(sourcePixels) {
|
||||
const originalDocument = Object.getOwnPropertyDescriptor(globalThis, 'document');
|
||||
@@ -108,15 +111,46 @@ test('mask result conversion preserves target size, transformed pixels, and imag
|
||||
}
|
||||
});
|
||||
|
||||
test('mask result application processes before resolving and updating the target tool', async () => {
|
||||
const stubs = installMaskConversionStubs([
|
||||
10, 20, 30, 0,
|
||||
40, 50, 60, 128,
|
||||
]);
|
||||
const appliedMasks = [];
|
||||
|
||||
try {
|
||||
const result = await applyMaskResultToTool(
|
||||
{ width: 2, height: 1 },
|
||||
{ targetWidth: 2, targetHeight: 1, invertAlpha: true },
|
||||
() => ({
|
||||
setMask(mask) {
|
||||
appliedMasks.push(mask);
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
assert.deepEqual(appliedMasks, [result]);
|
||||
assert.deepEqual([...stubs.output.data], [
|
||||
255, 255, 255, 255,
|
||||
255, 255, 255, 127,
|
||||
]);
|
||||
} finally {
|
||||
stubs.restore();
|
||||
}
|
||||
});
|
||||
|
||||
test('mask integrations share conversion but retain their own side effects', async () => {
|
||||
const maskEditorSource = await readFile(new URL('../src/MaskEditorIntegration.ts', import.meta.url), 'utf8');
|
||||
const samSource = await readFile(new URL('../src/SAMDetectorIntegration.ts', import.meta.url), 'utf8');
|
||||
|
||||
assert.match(maskEditorSource, /createMaskImageFromResult\(/);
|
||||
assert.match(samSource, /createMaskImageFromResult\(/);
|
||||
assert.match(maskEditorSource, /applyMaskResultToTool\(/);
|
||||
assert.match(samSource, /applyMaskResultToTool\(/);
|
||||
assert.doesNotMatch(maskEditorSource, /processImageToMask|convertToImage/);
|
||||
assert.doesNotMatch(samSource, /processImageToMask|convertToImage/);
|
||||
assert.match(maskEditorSource, /this\.maskTool\.setMask\(maskAsImage\)/);
|
||||
assert.doesNotMatch(maskEditorSource, /setMask\(maskAsImage\)/);
|
||||
assert.doesNotMatch(samSource, /setMask\(maskAsImage\)/);
|
||||
assert.match(maskEditorSource, /targetWidth: bounds\.width/);
|
||||
assert.match(samSource, /targetWidth: resultImage\.width/);
|
||||
assert.match(samSource, /actualCanvas\.render\(\)/);
|
||||
assert.match(samSource, /actualCanvas\.saveState\(\)/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user