Update image saving with timestamps, fix upscaler steps placement, rename vectorize output to Image

This commit is contained in:
Sirshendu Ganguly
2026-01-23 23:21:13 -05:00
parent 91646ada84
commit 7b570f7b4b
47 changed files with 193 additions and 366 deletions
+2 -4
View File
@@ -7,8 +7,6 @@ from .modules.modelSearch import modelSearch
from .modules.bridges import mainRoute
from .modules.controlNet import controlNet
from .modules.multiInference import multiInference
from .modules.runwareBFL import runwareKontext
from .modules.runwareImagen import runwareImagen
from .modules.loraSearch import loraSearch
from .modules.loraCombine import loraCombine
from .modules.refiner import refiner
@@ -71,6 +69,7 @@ from .modules.safetyInputs import safetyInputs
from .modules.settings import RunwareSettings
from .modules.videoAdvancedFeatureInputs import videoAdvancedFeatureInputs
from .modules.advancedFeatureSettings.wanAnimateAdvancedFeatureSettings import RunwareWanAnimateAdvancedFeatureSettings
from .modules.saveImage import RunwareSaveImage
RUNWARE_COMFYUI_VERSION = "1.4.0 Beta"
@@ -90,8 +89,6 @@ NODE_CLASS_MAPPINGS = {
"Runware PhotoMaker V2": photoMaker,
"Runware Image Upscaler": upscaler,
"Runware Model Search": modelSearch,
"Runware Kontext Inference": runwareKontext,
"Runware Imagen Inference": runwareImagen,
"Runware Multi Inference": multiInference,
"Runware Lora Search": loraSearch,
"Runware Embedding Search": embeddingSearch,
@@ -156,6 +153,7 @@ NODE_CLASS_MAPPINGS = {
"Runware Settings": RunwareSettings,
"Runware Video Advanced Feature Inputs": videoAdvancedFeatureInputs,
"Runware Wan Animate Advanced Feature Settings": RunwareWanAnimateAdvancedFeatureSettings,
"Runware Save Image": RunwareSaveImage,
}
NODE_DISPLAY_NAME_MAPPINGS = {
+5
View File
@@ -121,6 +121,7 @@ const RUNWARE_NODE_TYPES = {
SETTINGS: "Runware Settings",
VIDEOADVANCEDFEATUREINPUTS: "Runware Video Advanced Feature Inputs",
WANANIMATEADVANCEDFEATURESETTINGS: "Runware Wan Animate Advanced Feature Settings",
SAVEIMAGE: "Runware Save Image",
};
const RUNWARE_NODE_PROPS = {
@@ -420,6 +421,10 @@ const RUNWARE_NODE_PROPS = {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
},
[RUNWARE_NODE_TYPES.SAVEIMAGE]: {
bgColor: DEFAULT_BGCOLOR,
colorModeOnly: true,
},
};
export {
+5 -4
View File
@@ -80,7 +80,8 @@ class bgremoval:
DESCRIPTION = "Remove backgrounds from images effortlessly using Runware's low-cost image editing Inference."
FUNCTION = "rembg"
RETURN_TYPES = ("IMAGE",)
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("Image",)
CATEGORY = "Runware"
def rembg(self, **kwargs):
@@ -112,7 +113,7 @@ class bgremoval:
"model": modelAIR,
"outputFormat": outputFormat,
"outputQuality": rwUtils.OUTPUT_QUALITY,
"outputType": "base64Data",
"outputType": "URL",
}
if includeExtraSettings:
@@ -166,5 +167,5 @@ class bgremoval:
print(f"[DEBUG] Error in Background Removal Request: {str(e)}", flush=True)
raise
images = rwUtils.convertImageB64List(genResult)
return (images, )
images = rwUtils.extractImageURLs(genResult)
return (images,)
+4 -4
View File
@@ -76,7 +76,7 @@ class controlNetPreprocessor:
DESCRIPTION = "ControlNet offers advanced capabilities for precise image processing through the use of guide images in specific formats, known as preprocessed images. This powerful tool enhances the control and customization of image generation, enabling users to achieve desired artistic styles and detailed adjustments effectively."
FUNCTION = "controlNetPreProcess"
RETURN_TYPES = ("IMAGE",)
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("GUIDE IMAGE",)
CATEGORY = "Runware"
@@ -98,7 +98,7 @@ class controlNetPreprocessor:
"preProcessorType": preProcessorType,
"outputFormat": rwUtils.OUTPUT_FORMAT,
"outputQuality": rwUtils.OUTPUT_QUALITY,
"outputType": "base64Data",
"outputType": "URL",
}
]
@@ -115,5 +115,5 @@ class controlNetPreprocessor:
genConfig[0]["highThresholdCanny"] = cannyHighThreshold
genResult = rwUtils.inferenecRequest(genConfig)
images = rwUtils.convertImageB64List(genResult)
return (images, )
images = rwUtils.extractImageURLs(genResult)
return (images,)
+21 -11
View File
@@ -1,5 +1,6 @@
from .utils import runwareUtils as rwUtils
import comfy.model_management
import os
class txt2img:
@@ -129,9 +130,9 @@ class txt2img:
"min": 32,
"max": 128,
}),
"outputFormat": (["WEBP", "PNG", "JPEG", "SVG", "TIFF"], {
"tooltip": "Choose the output image format.",
"default": "WEBP",
"outputFormat": (["WEBP", "PNG", "JPEG", "TIFF"], {
"tooltip": "Choose the output image format. This will be used for the API request. The actual saved format will match the format returned by the API (visible in imageURL).",
"default": rwUtils.getOutputFormat(),
}),
"batchSize": ("INT", {
"tooltip": "The number of images to generate in a single request.",
@@ -196,8 +197,8 @@ class txt2img:
DESCRIPTION = "Generates Images Lightning Fast With Runware Image Inference Sonic Engine."
FUNCTION = "generateImage"
RETURN_TYPES = ("IMAGE", "RUNWARETASK")
RETURN_NAMES = ("IMAGE", "RW-Task")
RETURN_TYPES = ("RUNWARETASK", "STRING")
RETURN_NAMES = ("RW-Task", "Image")
CATEGORY = "Runware"
@classmethod
@@ -251,7 +252,7 @@ class txt2img:
useSeed = kwargs.get("useSeed", True)
useClipSkip = kwargs.get("useClipSkip", True)
dimensions = kwargs.get("dimensions", "Square (512x512)")
outputFormat = kwargs.get("outputFormat", "WEBP")
outputFormat = kwargs.get("outputFormat", rwUtils.getOutputFormat())
batchSize = kwargs.get("batchSize", 1)
acceleration = kwargs.get("acceleration", "none")
useResolution = kwargs.get("useResolution", False)
@@ -454,11 +455,20 @@ class txt2img:
status = image_data["status"]
if status == "success":
# Check for image data (imageURL, imageBase64Data, mediaURL)
if "imageURL" in image_data or "imageBase64Data" in image_data or "imageURL" in image_data:
# Convert and return images
images = rwUtils.convertImageB64List(pollResult)
return (images, None)
# Check for image data (imageURL, imageBase64Data)
if "imageURL" in image_data or "imageBase64Data" in image_data:
# Extract URLs from all results
image_urls = []
for result in pollResult.get("data", []):
imageURL = result.get("imageURL")
if imageURL:
image_urls.append(imageURL)
# Return URLs as comma-separated string (or single URL)
image_url_str = ",".join(image_urls) if image_urls else ""
return (None, image_url_str)
# If status is "processing", continue polling
+4 -4
View File
@@ -48,7 +48,7 @@ class imageMasking:
DESCRIPTION = "Image Masking provides intelligent detection and mask generation for specific elements in images, particularly optimized for faces, hands, and people. Built on advanced detection models, this feature enhances the inpainting workflow by automatically creating precise masks around detected elements, enabling targeted enhancement and detailing."
FUNCTION = "imageMasking"
RETURN_TYPES = ("IMAGE", "IMAGE", "MASK")
RETURN_TYPES = ("IMAGE", "STRING", "STRING")
RETURN_NAMES = ("Image", "Mask Preview", "Mask")
CATEGORY = "Runware"
@@ -87,7 +87,7 @@ class imageMasking:
"model": detectionModel,
"confidence": confidence,
"maxDetections": maxDetections,
"outputType": "base64Data",
"outputType": "URL",
"outputFormat": rwUtils.OUTPUT_FORMAT,
"outputQuality": rwUtils.OUTPUT_QUALITY,
}
@@ -97,5 +97,5 @@ class imageMasking:
genConfig[0]["maskBlur"] = maskBlur
genResult = rwUtils.inferenecRequest(genConfig)
images = rwUtils.convertImageB64List(genResult)
return (image, images[0], images[0])
image_url = rwUtils.extractImageURLs(genResult)
return (image, image_url, image_url)
+3 -3
View File
@@ -39,7 +39,7 @@ class multiInference:
DESCRIPTION = "Allows you to Run Multiple Inference Tasks in Parallel."
FUNCTION = "multiInference"
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE")
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING")
RETURN_NAMES = ("Result 1", "Result 2", "Result 3", "Result 4")
CATEGORY = "Runware"
@@ -60,8 +60,8 @@ class multiInference:
result = await loop.run_in_executor(
None, rwUtils.inferenecRequest, taskData
)
images = rwUtils.convertImageB64List(result)
return images
image_url = rwUtils.extractImageURLs(result)
return image_url
except Exception as e:
print(f"\n---- Runware Multi Inference Task {oindex + 1} Failed ----")
print(f"Task-Type: {taskData[0]['taskType']}")
+5 -4
View File
@@ -112,7 +112,8 @@ class photoMaker:
DESCRIPTION = "Transform and style images using PhotoMaker's advanced personalization technology. Create consistent, high-quality image variations with precise subject fidelity and style control."
FUNCTION = "photoMaker"
RETURN_TYPES = ("IMAGE",)
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("Image",)
CATEGORY = "Runware"
@classmethod
@@ -171,7 +172,7 @@ class photoMaker:
"CFGScale": cfgScale,
"clipSkip": clipSkip,
"numberResults": batchSize,
"outputType": "base64Data",
"outputType": "URL",
"outputFormat": rwUtils.OUTPUT_FORMAT,
"outputQuality": rwUtils.OUTPUT_QUALITY,
}
@@ -186,5 +187,5 @@ class photoMaker:
genConfig[0]["promptWeighting"] = "compel"
genResult = rwUtils.inferenecRequest(genConfig)
images = rwUtils.convertImageB64List(genResult)
return (images, )
images = rwUtils.extractImageURLs(genResult)
return (images,)
-139
View File
@@ -1,139 +0,0 @@
from .utils import runwareUtils as rwUtils
class runwareKontext:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"Model": ([
"bfl:3@1 (FLUX.1 Kontext Pro)",
"bfl:4@1 (FLUX.1 Kontext Max)",
], {
"tooltip": "Select The Model You Want For Image Generation or Image Editing.",
"default": "bfl:3@1 (FLUX.1 Kontext Pro)",
}),
"positivePrompt": ("STRING", {
"multiline": True,
"placeholder": "Text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides positive guidance for the task. This parameter is essential to shape the desired results.\n\nYou Can Press (Ctrl + Alt + E) To Enhance The Prompt!",
"tooltip": "Text instruction to guide the model on generating the image. You Can Also Press (Ctrl + Alt + E) To Enhance The Prompt!"
}),
"Prompt Upsampling": ("BOOLEAN", {
"tooltip": "If Enabled it Will automatically modify the prompt at generation time for more creative results.",
"default": False,
"label_on": "Enabled",
"label_off": "Disabled",
}),
"Aspect Ratio": ([
"21:9 (Ultra-Wide / Landscape)",
"16:9 (Wide / Landscape)",
"4:3 (Standard / Landscape)",
"3:2 (Classic Photo / Landscape)",
"1:1 (Square)",
"2:3 (Classic Photo / Portrait)",
"3:4 (Standard / Portrait)",
"9:16 (Vertical Video / Portrait)",
"9:21 (Ultra-Tall / Portrait)"
], {
"default": "16:9 (Wide / Landscape)",
"tooltip": "Adjust the dimensions of the generated image. This setting allows you to choose from various aspect ratios, such as 16:9 for wide images or 1:1 for square images. The width and height will be automatically adjusted based on the selected aspect ratio.",
}),
"seed": ("INT", {
"tooltip": "A value used to randomize the image generation. If you want to make images reproducible (generate the same image multiple times), you can use the same seed value. Set to 0 to auto-generate a random seed.",
"default": 0,
"min": 0,
"max": 4294967295,
}),
"Multi Inference Mode": ("BOOLEAN", {
"tooltip": "If Enabled the node will skip the image generation process and will only return the Runware Task Object to be used in the Multi Inference Node.",
"default": False,
"label_on": "Enabled",
"label_off": "Disabled",
}),
"batchSize": ("INT", {
"tooltip": "The number of images to generate in a single request.",
"default": 1,
"min": 1,
"max": 10,
}),
},
"optional": {
"Reference Images": ("RUNWAREREFERENCEIMAGES", {
"tooltip": "Connect a Runware Reference Images Node to use reference images for image editing or generation.",
}),
}
}
@classmethod
def VALIDATE_INPUTS(cls, positivePrompt):
if (positivePrompt is not None and (positivePrompt == "" or len(positivePrompt) < 3 or len(positivePrompt) > 2000)):
raise Exception(
"Positive Prompt Must Be Between 3 And 2000 characters!")
return True
DESCRIPTION = "Generate And Edit Images Using Runware BFL Kontext Models."
FUNCTION = "kontextInference"
RETURN_TYPES = ("IMAGE", "RUNWARETASK")
RETURN_NAMES = ("IMAGE", "RW-Task")
CATEGORY = "Runware"
def kontextInference(self, **kwargs):
runwareModel = kwargs.get("Model")
runwareModel = runwareModel.split(" ")[0]
positivePrompt = kwargs.get("positivePrompt")
promptUpsampling = kwargs.get("Prompt Upsampling")
aspectRatio = kwargs.get("Aspect Ratio")
multiInferenceMode = kwargs.get("Multi Inference Mode", False)
referenceImages = kwargs.get("Reference Images", None)
seed = kwargs.get("seed")
batchSize = kwargs.get("batchSize", 1)
aspectRatioMap = {
"21:9": (1568, 672),
"16:9": (1392, 752),
"4:3": (1184, 880),
"3:2": (1248, 832),
"1:1": (1024, 1024),
"2:3": (832, 1248),
"3:4": (880, 1184),
"9:16": (752, 1392),
"9:21": (672, 1568),
}
secAspectRatio = aspectRatio.split(" ")[0]
width, height = aspectRatioMap.get(secAspectRatio, (1024, 1024))
genConfig = [
{
"taskType": "imageInference",
"taskUUID": rwUtils.genRandUUID(),
"positivePrompt": positivePrompt,
"height": height,
"width": width,
"model": runwareModel,
"seed": seed,
"outputType": "base64Data",
"outputFormat": rwUtils.OUTPUT_FORMAT,
"outputQuality": rwUtils.OUTPUT_QUALITY,
"numberResults": batchSize,
}
]
if (referenceImages is not None):
genConfig[0]["referenceImages"] = referenceImages
if (promptUpsampling):
genConfig[0]["providerSettings"] = {
"bfl": {
"promptUpsampling": True
}
}
# For Debugging Purposes Only
print(f"[Debugging] Task UUID: {genConfig[0]['taskUUID']}")
if (multiInferenceMode):
return (None, genConfig)
else:
genResult = rwUtils.inferenecRequest(genConfig)
images = rwUtils.convertImageB64List(genResult)
return (images, None)
-111
View File
@@ -1,111 +0,0 @@
from .utils import runwareUtils as rwUtils
class runwareImagen:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"Model": ([
"google:1@1 (Imagen 3)",
"google:1@2 (Imagen 3 Fast)",
"google:2@1 (Imagen 4 Preview)",
"google:2@2 (Imagen 4 Ultra)",
"google:2@3 (Imagen 4 Fast)",
], {
"tooltip": "Select The Google Imagen Model For Image Generation.",
"default": "google:1@1 (Imagen 3)",
}),
"positivePrompt": ("STRING", {
"multiline": True,
"placeholder": "Text instruction to guide the model on generating the image. It is usually a sentence or a paragraph that provides positive guidance for the task. This parameter is essential to shape the desired results.",
"tooltip": "Text instruction to guide the model on generating the image."
}),
"Aspect Ratio": ([
"1:1 (Square)",
"9:16 (Vertical)",
"16:9 (Wide)",
"3:4 (Portrait)",
"4:3 (Landscape)"
], {
"default": "1:1 (Square)",
"tooltip": "Adjust the dimensions of the generated image. The width and height will be automatically adjusted based on the selected aspect ratio.",
}),
"seed": ("INT", {
"tooltip": "A value used to randomize the image generation. If you want to make images reproducible (generate the same image multiple times), you can use the same seed value.",
"default": 0,
"min": 1,
"max": 9223372036854776000,
}),
"Multi Inference Mode": ("BOOLEAN", {
"tooltip": "If Enabled the node will skip the image generation process and will only return the Runware Task Object to be used in the Multi Inference Node.",
"default": False,
"label_on": "Enabled",
"label_off": "Disabled",
}),
"batchSize": ("INT", {
"tooltip": "The number of images to generate in a single request.",
"default": 1,
"min": 1,
"max": 10,
}),
}
}
@classmethod
def VALIDATE_INPUTS(cls, positivePrompt):
if (positivePrompt is not None and (positivePrompt == "" or len(positivePrompt) < 3 or len(positivePrompt) > 2000)):
raise Exception(
"Positive Prompt Must Be Between 3 And 2000 characters!")
return True
DESCRIPTION = "Generate Images Using Google Imagen Models."
FUNCTION = "imagenInference"
RETURN_TYPES = ("IMAGE", "RUNWARETASK")
RETURN_NAMES = ("IMAGE", "RW-Task")
CATEGORY = "Runware"
def imagenInference(self, **kwargs):
runwareModel = kwargs.get("Model")
runwareModel = runwareModel.split(" ")[0]
positivePrompt = kwargs.get("positivePrompt")
aspectRatio = kwargs.get("Aspect Ratio")
multiInferenceMode = kwargs.get("Multi Inference Mode", False)
seed = kwargs.get("seed")
batchSize = kwargs.get("batchSize", 1)
aspectRatioMap = {
"1:1": (1024, 1024),
"9:16": (768, 1408),
"16:9": (1408, 768),
"3:4": (896, 1280),
"4:3": (1280, 896),
}
secAspectRatio = aspectRatio.split(" ")[0]
width, height = aspectRatioMap.get(secAspectRatio, (1024, 1024))
genConfig = [
{
"taskType": "imageInference",
"taskUUID": rwUtils.genRandUUID(),
"positivePrompt": positivePrompt,
"height": height,
"width": width,
"model": runwareModel,
"seed": seed,
"outputType": "base64Data",
"outputFormat": rwUtils.OUTPUT_FORMAT,
"outputQuality": rwUtils.OUTPUT_QUALITY,
"numberResults": batchSize
}
]
# For Debugging Purposes Only
print(f"[Debugging] Task UUID: {genConfig[0]['taskUUID']}")
if (multiInferenceMode):
return (None, genConfig)
else:
genResult = rwUtils.inferenecRequest(genConfig)
images = rwUtils.convertImageB64List(genResult)
return (images, None)
+75
View File
@@ -0,0 +1,75 @@
from .utils import runwareUtils as rwUtils
import folder_paths
import os
import requests
from datetime import datetime
class RunwareSaveImage:
"""Runware Save Image node that respects format from imageURL"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"Images": ("STRING", {
"tooltip": "Image URL(s) from Runware Image Inference node. For multiple images, URLs should be comma-separated."
}),
"filename_prefix": ("STRING", {
"default": "ComfyUI",
"tooltip": "Prefix for the filename."
}),
}
}
RETURN_TYPES = ()
FUNCTION = "save_images"
OUTPUT_NODE = True
CATEGORY = "Runware"
def save_images(self, Images, filename_prefix="ComfyUI"):
"""Save images using URL directly"""
# Get output directory
output_dir = folder_paths.get_output_directory()
os.makedirs(output_dir, exist_ok=True)
# Split URLs if comma-separated (for batch processing)
image_urls = [url.strip() for url in Images.split(",") if url.strip()]
if not image_urls:
raise Exception("No image URL provided. Please connect the 'Image URL' output from Runware Image Inference node.")
saved_files = []
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Process each image
for i, image_url in enumerate(image_urls):
# Download original file directly from URL to preserve quality
response = requests.get(image_url, timeout=30)
response.raise_for_status()
img_data = response.content
# Get extension from URL (handle query parameters)
url_without_params = image_url.split('?')[0]
extension = "." + url_without_params.split('.')[-1]
# Generate filename with timestamp
if len(image_urls) > 1:
filename = f"{filename_prefix}_{timestamp}_{i+1:03}.{extension.lstrip('.')}"
else:
filename = f"{filename_prefix}_{timestamp}.{extension.lstrip('.')}"
filepath = os.path.join(output_dir, filename)
# Write the downloaded file
with open(filepath, 'wb') as f:
f.write(img_data)
print(f"[Runware] Saved original file from URL: {filepath}")
# Add preview data for ComfyUI
saved_files.append({
"filename": filename,
"subfolder": "",
"type": "output"
})
return {"ui": {"images": saved_files}}
+18 -17
View File
@@ -127,7 +127,8 @@ class upscaler:
DESCRIPTION = "Enhance the resolution and quality of your images using Runware's advanced upscaling API. Transform low-resolution images into sharp, high-definition visuals."
FUNCTION = "upscale"
RETURN_TYPES = ("IMAGE",)
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("Image",)
CATEGORY = "Runware"
def upscale(self, **kwargs):
@@ -168,28 +169,28 @@ class upscaler:
"upscaleFactor": int(upscaleFactor),
"outputFormat": rwUtils.OUTPUT_FORMAT,
"outputQuality": rwUtils.OUTPUT_QUALITY,
"outputType": "base64Data",
"outputType": "URL",
}
# Add model if specified
if model:
genConfig["model"] = model
# Add common parameters at top level - only add if their toggle is enabled
if useSteps:
genConfig["steps"] = steps
if useSeed:
genConfig["seed"] = seed
if useCFGScale:
genConfig["CFGScale"] = CFGScale
if usePrompts:
if positivePrompt:
genConfig["positivePrompt"] = positivePrompt
if negativePrompt:
genConfig["negativePrompt"] = negativePrompt
# Build settings object for advanced parameters
settings = {}
# Common parameters - only add if their toggle is enabled
if useSteps:
settings["steps"] = steps
if useSeed:
settings["seed"] = seed
if useCFGScale:
settings["CFGScale"] = CFGScale
if usePrompts:
if positivePrompt:
settings["positivePrompt"] = positivePrompt
if negativePrompt:
settings["negativePrompt"] = negativePrompt
# Clarity upscaler specific parameters
if useClarityParams:
@@ -249,5 +250,5 @@ class upscaler:
print(f"[DEBUG] Error in Image Upscale Request: {str(e)}", flush=True)
raise
images = rwUtils.convertImageB64List(genResult)
return (images, )
images = rwUtils.extractImageURLs(genResult)
return (images,)
+11 -2
View File
@@ -552,6 +552,15 @@ def convertIMG2Tensor(b64img):
tensorImage = torch.from_numpy(imageNP).squeeze()
return tensorImage
def extractImageURLs(imageDataObject):
"""Extract image URLs from API response and return as comma-separated string"""
image_urls = []
for result in imageDataObject.get("data", []):
imageURL = result.get("imageURL")
if imageURL:
image_urls.append(imageURL)
return ",".join(image_urls) if image_urls else ""
def convertImageB64List(imageDataObject):
images = ()
for result in imageDataObject["data"]:
@@ -566,8 +575,8 @@ def convertImageB64List(imageDataObject):
generatedImage = convertIMG2Tensor(generatedImage)
images += (generatedImage,)
else:
# If no base64 data, try to get image URL (check both imageURL and mediaURL)
imageURL = result.get("imageURL") or result.get("mediaURL")
# If no base64 data, try to get image URL
imageURL = result.get("imageURL")
if imageURL:
# Download image from URL and convert to tensor
try:
+6 -32
View File
@@ -1,14 +1,4 @@
from .utils import runwareUtils as rwUtils
import base64
import io
class SVGData:
"""Wrapper class to match RecraftIO.SVG format for SaveSVGNode"""
def __init__(self, svgContent):
svgBytes = io.BytesIO(svgContent.encode('utf-8'))
self.data = [svgBytes]
class vectorize:
@@ -35,12 +25,12 @@ class vectorize:
DESCRIPTION = "Convert images to vector format using Runware's vectorization service."
FUNCTION = "vectorizeImage"
RETURN_TYPES = ("SVG",)
RETURN_NAMES = ("SVG",)
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("Image",)
CATEGORY = "Runware"
def vectorizeImage(self, **kwargs):
"""Vectorize image and return SVG data"""
"""Vectorize image and return SVG URL"""
image = kwargs.get("Image")
modelName = kwargs.get("Model", "recraft:1@1")
@@ -59,9 +49,9 @@ class vectorize:
self._validateResponse(genResult)
svgData = self._extractSvgData(genResult)
svg_url = rwUtils.extractImageURLs(genResult)
return (svgData,)
return (svg_url,)
def _buildGenConfig(self, model, imageUuid):
"""Build generation configuration for API request"""
@@ -72,7 +62,7 @@ class vectorize:
"inputs": {
"image": imageUuid
},
"outputType": "base64Data",
"outputType": "URL",
"outputFormat": "svg",
}]
@@ -81,22 +71,6 @@ class vectorize:
if "errors" in genResult:
errorMessage = genResult["errors"][0]["message"]
raise Exception(f"Vectorization failed: {errorMessage}")
if "data" not in genResult or len(genResult["data"]) == 0:
raise Exception("No data returned from vectorization API")
def _extractSvgData(self, genResult):
"""Extract SVG data from API response"""
resultData = genResult["data"][0]
if "imageBase64Data" not in resultData:
raise Exception("imageBase64Data not found in response")
base64Svg = resultData["imageBase64Data"]
svgContent = base64.b64decode(base64Svg).decode('utf-8')
svgData = SVGData(svgContent)
return svgData
# Node class mappings
+2 -1
View File
@@ -1,4 +1,5 @@
python-dotenv
brotli
zstd
librosa
librosa
imageio
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1 +1 @@
{"id":"993f4b88-acb5-4f70-968f-e3a2c9c1c5b4","revision":0,"last_node_id":7,"last_link_id":8,"nodes":[{"id":6,"type":"SaveSVGNode","pos":[447.0441589355469,567.7747192382812],"size":[270,270],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"svg","name":"svg","type":"SVG","link":8},{"localized_name":"filename_prefix","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.3.46","Node name for S&R":"SaveSVGNode"},"widgets_values":["svg/ComfyUI"]},{"id":4,"type":"LoadImage","pos":[-162.27017211914062,506.9244384765625],"size":[274.080078125,314.0000305175781],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[7]},{"localized_name":"MASK","name":"MASK","type":"MASK","links":null}],"properties":{"cnr_id":"comfy-core","ver":"0.3.46","Node name for S&R":"LoadImage"},"widgets_values":["bulldog_1.webp","image"]},{"id":7,"type":"Runware Vectorize","pos":[146.1571044921875,546.4293823242188],"size":[270,58],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"Image","name":"Image","type":"IMAGE","link":7},{"localized_name":"Model","name":"Model","type":"COMBO","widget":{"name":"Model"},"link":null}],"outputs":[{"localized_name":"SVG","name":"SVG","type":"SVG","links":[8]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"8d75a6933feda430268dd0c555ecde9ccd99c7ef","Node name for S&R":"Runware Vectorize"},"widgets_values":["recraft:1@1"],"bgcolor":"#5345bf"}],"links":[[7,4,0,7,0,"IMAGE"],[8,7,0,6,0,"SVG"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.02884607845781,"offset":[476.52544538804847,-345.82928772954745]}},"version":0.4}
{"id":"993f4b88-acb5-4f70-968f-e3a2c9c1c5b4","revision":0,"last_node_id":10,"last_link_id":11,"nodes":[{"id":9,"type":"Runware Save Image","pos":[452.51519775390625,576.1388549804688],"size":[270,82],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"Images","name":"Images","type":"STRING","widget":{"name":"Images"},"link":11},{"localized_name":"filename_prefix","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null}],"outputs":[],"properties":{"cnr_id":"ComfyUI-Runware","ver":"91646ada844167e36188b535ff60063735d10ca3","Node name for S&R":"Runware Save Image"},"widgets_values":["","ComfyUI"],"bgcolor":"#5345bf"},{"id":4,"type":"LoadImage","pos":[-162.27017211914062,506.9244384765625],"size":[274.080078125,314.0000305175781],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"image","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"choose file to upload","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","links":[10]},{"localized_name":"MASK","name":"MASK","type":"MASK","links":null}],"properties":{"cnr_id":"comfy-core","ver":"0.3.46","Node name for S&R":"LoadImage"},"widgets_values":["bulldog_1.webp","image"]},{"id":10,"type":"Runware Vectorize","pos":[162.64442443847656,533.0270385742188],"size":[270,58],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"Image","name":"Image","type":"IMAGE","link":10},{"localized_name":"Model","name":"Model","type":"COMBO","widget":{"name":"Model"},"link":null}],"outputs":[{"localized_name":"Image","name":"Image","type":"STRING","links":[11]}],"properties":{"cnr_id":"ComfyUI-Runware","ver":"91646ada844167e36188b535ff60063735d10ca3","Node name for S&R":"Runware Vectorize"},"widgets_values":["recraft:1@1"],"bgcolor":"#5345bf"}],"links":[[10,4,0,10,0,"IMAGE"],[11,10,0,9,0,"STRING"]],"groups":[],"config":{},"extra":{"ds":{"scale":1.02884607845781,"offset":[476.67256864649244,-345.82928772954745]}},"version":0.4}