/**
* Upscaling Workflow for ComfyUI
* Uses built-in UpscaleModelLoader and ImageUpscaleWithModel nodes
* Preserves alpha channel using SaveImageWithAlpha from KJNodes
*/
function getUpscalingWorkflow(imagePath, modelName = '4x-UltraSharp.safetensors', scaleFactor = 1.0) {
// Add [output] annotation for files in the output directory
let annotatedPath = imagePath;
if (!imagePath.includes('[')) {
// If it's just a filename, assume it's in the output directory
annotatedPath = `${imagePath} [output]`;
}
const workflow = {
"1": {
"inputs": {
"image": annotatedPath,
"upload": "image"
},
"class_type": "LoadImage",
"_meta": {
"title": "Load Image"
}
},
"2": {
"inputs": {
"model_name": modelName
},
"class_type": "UpscaleModelLoader",
"_meta": {
"title": "Load Upscale Model"
}
},
"3": {
"inputs": {
"upscale_model": ["2", 0],
"image": ["1", 0] // RGB image from LoadImage
},
"class_type": "ImageUpscaleWithModel",
"_meta": {
"title": "Upscale Image"
}
}
};
// Check if we need to handle alpha/mask
// LoadImage outputs: [IMAGE, MASK]
// If the image has alpha, the mask will contain it
// Use MaskToImage to convert mask for upscaling
workflow["4"] = {
"inputs": {
"mask": ["1", 1] // Get mask from LoadImage
},
"class_type": "MaskToImage",
"_meta": {
"title": "Convert Mask to Image"
}
};
// Upscale the mask using the same model
workflow["5"] = {
"inputs": {
"upscale_model": ["2", 0],
"image": ["4", 0] // Image version of mask
},
"class_type": "ImageUpscaleWithModel",
"_meta": {
"title": "Upscale Mask"
}
};
// Convert back to mask
workflow["6"] = {
"inputs": {
"image": ["5", 0],
"channel": "red" // Use red channel as mask
},
"class_type": "ImageToMask",
"_meta": {
"title": "Convert Image to Mask"
}
};
// Invert the mask (LoadImage mask is inverted for transparency)
workflow["7"] = {
"inputs": {
"mask": ["6", 0]
},
"class_type": "InvertMask",
"_meta": {
"title": "Invert Mask"
}
};
// Add optional scaling if not 1.0 (for additional scaling beyond model's native scale)
let nextNodeId = 8;
let finalImageNode = "3";
let finalMaskNode = "7";
if (scaleFactor !== 1.0) {
workflow[nextNodeId.toString()] = {
"inputs": {
"image": [finalImageNode, 0],
"upscale_method": "lanczos",
"scale_by": scaleFactor
},
"class_type": "ImageScaleBy",
"_meta": {
"title": "Additional Image Scaling"
}
};
finalImageNode = nextNodeId.toString();
nextNodeId++;
workflow[nextNodeId.toString()] = {
"inputs": {
"mask": [finalMaskNode, 0],
"width": 8192, // Will be scaled by scaleFactor
"height": 8192,
"X": 0,
"Y": 0
},
"class_type": "CropMask",
"_meta": {
"title": "Scale Mask"
}
};
finalMaskNode = nextNodeId.toString();
nextNodeId++;
}
// Save with alpha using SaveImageWithAlpha from KJNodes
workflow[nextNodeId.toString()] = {
"inputs": {
"images": [finalImageNode, 0],
"mask": [finalMaskNode, 0],
"filename_prefix": "upscaled"
},
"class_type": "SaveImageWithAlpha",
"_meta": {
"title": "Save with Alpha"
}
};
return workflow;
}
/**
* Get list of available upscaling models
* These should match the models downloaded to models/upscale_models/
*/
function getAvailableUpscaleModels() {
return {
'ultrasharp': '4x-UltraSharp.safetensors',
'animesharp': '4x-AnimeSharp.safetensors'
};
}
/**
* Helper to determine best model based on content type
*/
function selectBestModel(contentType = 'general') {
const models = getAvailableUpscaleModels();
switch(contentType) {
case 'anime':
case 'artwork':
case 'illustration':
return models.animesharp;
default:
return models.ultrasharp;
}
}
export {
getUpscalingWorkflow,
getAvailableUpscaleModels,
selectBestModel
};