Merge pull request #104 from Runware/feature-advancedFeaturesImageInference

Added hiresfix, layerDiffuse, Regional Prompting and Watermark
This commit is contained in:
Sirshendu Ganguly
2026-03-12 16:22:33 -04:00
committed by GitHub
13 changed files with 1274 additions and 540 deletions
+8
View File
@@ -25,6 +25,10 @@ from .modules.ipAdapterCombine import ipAdapterCombine
from .modules.vaeSearch import vaeSearch
from .modules.referenceImages import referenceImages
from .modules.imageInferenceInputs import imageInferenceInputs
from .modules.imageInferenceAdvancedFeatures import RunwareImageInferenceAdvancedFeatures
from .modules.imageWatermarkAdvancedFeature import RunwareWatermarkAdvancedFeature
from .modules.imageRegionalPromptingAdvancedFeature import RunwareRegionalPromptingAdvancedFeature
from .modules.imageRegionalPromptingAdvancedFeatureRegions import RunwareRegionalPromptingAdvancedFeatureRegions
from .modules.videoInference import txt2vid
from .modules.videoModelSearch import videoModelSearch
from .modules.videoSettings import RunwareVideoSettings
@@ -132,6 +136,10 @@ NODE_CLASS_MAPPINGS = {
"Runware IPAdapters Combine": ipAdapterCombine,
"Runware Reference Images": referenceImages,
"Runware Image Inference Inputs": imageInferenceInputs,
"Runware Image Advanced Feature Input": RunwareImageInferenceAdvancedFeatures,
"Runware Watermark Advanced Feature": RunwareWatermarkAdvancedFeature,
"Runware Regional Prompting Advanced Feature": RunwareRegionalPromptingAdvancedFeature,
"Runware Regional Prompting Advanced Feature Regions": RunwareRegionalPromptingAdvancedFeatureRegions,
"Runware Video Inference": txt2vid,
"Runware Video Model Search": videoModelSearch,
"Runware Frame Images": RunwareFrameImages,
+7 -1
View File
@@ -1,6 +1,6 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, vectorizeModelSearchFilterHandler, vectorizeToggleHandler, useParameterToggleHandler, imageInferenceToggleHandler, upscalerToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, audioInferenceSpeechToggleHandler, audioSettingsToggleHandler, videoSettingsToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, xaiProviderSettingsToggleHandler, viduProviderSettingsToggleHandler, sourcefulProviderSettingsToggleHandler, sourcefulProviderSettingsFontsToggleHandler, threeDInferenceToggleHandler, threeDInferenceSettingsToggleHandler, threeDInferenceSettingsLatToggleHandler, ultralyticsProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, mireloProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler, audioInferenceInputsToggleHandler } from "./utils.js";
import { promptEnhanceHandler, syncDimensionsNodeHandler, searchNodeHandler, APIKeyHandler, captionNodeHandler, mediaUUIDHandler, save3DFilepathHandler, videoTranscriptionHandler, videoOutputsHandler, handleCustomErrors, videoInferenceDimensionsHandler, videoModelSearchFilterHandler, audioModelSearchFilterHandler, vectorizeModelSearchFilterHandler, vectorizeToggleHandler, useParameterToggleHandler, imageInferenceToggleHandler, imageInferenceAdvancedFeaturesToggleHandler, watermarkAdvancedFeatureToggleHandler, regionalPromptingRegionsToggleHandler, upscalerToggleHandler, videoUpscalerToggleHandler, audioInferenceToggleHandler, audioInferenceSpeechToggleHandler, audioSettingsToggleHandler, videoSettingsToggleHandler, acceleratorOptionsToggleHandler, bytedanceProviderSettingsToggleHandler, xaiProviderSettingsToggleHandler, viduProviderSettingsToggleHandler, sourcefulProviderSettingsToggleHandler, sourcefulProviderSettingsFontsToggleHandler, threeDInferenceToggleHandler, threeDInferenceSettingsToggleHandler, threeDInferenceSettingsLatToggleHandler, ultralyticsProviderSettingsToggleHandler, openaiProviderSettingsToggleHandler, lightricksProviderSettingsToggleHandler, klingProviderSettingsToggleHandler, lumaProviderSettingsToggleHandler, briaProviderSettingsToggleHandler, pixverseProviderSettingsToggleHandler, alibabaProviderSettingsToggleHandler, mireloProviderSettingsToggleHandler, googleProviderSettingsToggleHandler, syncProviderSettingsToggleHandler, syncSegmentToggleHandler, settingsToggleHandler, audioInputToggleHandler, speechInputToggleHandler, briaProviderMaskToggleHandler, wanAnimateAdvancedFeatureSettingsToggleHandler, videoAdvancedFeatureInputsToggleHandler, audioInferenceInputsToggleHandler } from "./utils.js";
import { RUNWARE_NODE_TYPES, RUNWARE_NODE_PROPS, SEARCH_TERMS } from "./types.js";
const nodeInitList = [];
@@ -78,6 +78,12 @@ app.registerExtension({
videoUpscalerToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOINFERENCE) {
audioInferenceToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.IMAGEINFERENCEADVANCEDFEATURES) {
imageInferenceAdvancedFeaturesToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.WATERMARKADVANCEDFEATURE) {
watermarkAdvancedFeatureToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.REGIONALPROMPTINGADVFEATUREREGIONS) {
regionalPromptingRegionsToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOINFERENCESPEECH) {
audioInferenceSpeechToggleHandler(node);
} else if(nodeClass === RUNWARE_NODE_TYPES.AUDIOSETTINGS) {
+559 -539
View File
File diff suppressed because it is too large Load Diff
+170
View File
@@ -3835,6 +3835,173 @@ function videoAdvancedFeatureInputsToggleHandler(advancedFeatureNode) {
}
}
function imageInferenceAdvancedFeaturesToggleHandler(advancedFeaturesNode) {
function toggleWidgetState(useWidget, paramWidget, paramName) {
if (!useWidget || !paramWidget) return;
function toggleEnabled() {
const enabled = useWidget.value === true;
if (paramWidget.inputEl) {
paramWidget.inputEl.disabled = !enabled;
paramWidget.inputEl.style.opacity = enabled ? "1" : "0.5";
paramWidget.inputEl.style.cursor = enabled ? "text" : "not-allowed";
paramWidget.inputEl.readOnly = !enabled;
}
paramWidget.disabled = !enabled;
if (!paramWidget.inputEl && paramName) {
const nodeElement = advancedFeaturesNode.htmlElements?.widgetsContainer || advancedFeaturesNode.htmlElements;
if (nodeElement) {
const input = nodeElement.querySelector(`input[name="${paramName}"], select[name="${paramName}"]`);
if (input) {
input.disabled = !enabled;
input.style.opacity = enabled ? "1" : "0.5";
input.style.cursor = enabled ? "text" : "not-allowed";
}
}
}
advancedFeaturesNode.setDirtyCanvas(true);
}
appendWidgetCB(useWidget, () => setTimeout(toggleEnabled, 50));
setTimeout(toggleEnabled, 100);
}
const useLayerDiffuseWidget = advancedFeaturesNode.widgets.find(w => w.name === "useLayerDiffuse");
const layerDiffuseWidget = advancedFeaturesNode.widgets.find(w => w.name === "layerDiffuse");
if (useLayerDiffuseWidget && layerDiffuseWidget) toggleWidgetState(useLayerDiffuseWidget, layerDiffuseWidget, "layerDiffuse");
const useHiresFixWidget = advancedFeaturesNode.widgets.find(w => w.name === "useHiresFix");
const hiresFixWidget = advancedFeaturesNode.widgets.find(w => w.name === "hiresFix");
if (useHiresFixWidget && hiresFixWidget) toggleWidgetState(useHiresFixWidget, hiresFixWidget, "hiresFix");
}
function watermarkAdvancedFeatureToggleHandler(watermarkNode) {
if (!watermarkNode?.widgets) return;
function toggleWidgetState(useWidget, paramWidget, paramName) {
if (!useWidget || !paramWidget) return;
function toggleEnabled() {
const enabled = useWidget.value === true;
if (paramWidget.inputEl) {
paramWidget.inputEl.disabled = !enabled;
paramWidget.inputEl.style.opacity = enabled ? "1" : "0.5";
paramWidget.inputEl.style.cursor = enabled ? "text" : "not-allowed";
paramWidget.inputEl.readOnly = !enabled;
}
paramWidget.disabled = !enabled;
if (!paramWidget.inputEl && paramName) {
const nodeElement = watermarkNode.htmlElements?.widgetsContainer || watermarkNode.htmlElements;
if (nodeElement) {
const input = nodeElement.querySelector(`input[name="${paramName}"], textarea[name="${paramName}"], select[name="${paramName}"]`);
if (input) {
input.disabled = !enabled;
input.style.opacity = enabled ? "1" : "0.5";
input.style.cursor = enabled ? "text" : "not-allowed";
input.readOnly = !enabled;
}
}
}
watermarkNode.setDirtyCanvas(true);
}
appendWidgetCB(useWidget, () => {
setTimeout(toggleEnabled, 50);
});
setTimeout(toggleEnabled, 100);
}
const pairs = [
["useText", "text"],
["useImage", "image"],
["useDisplayPosition", "displayPosition"],
["useTiled", "tiled"],
["useOpacity", "opacity"],
["useFontColor", "fontColor"],
["useBgColor", "bgColor"],
];
for (const [useName, paramName] of pairs) {
const useWidget = watermarkNode.widgets.find(w => w.name === useName);
const paramWidget = watermarkNode.widgets.find(w => w.name === paramName);
if (useWidget && paramWidget) {
toggleWidgetState(useWidget, paramWidget, paramName);
}
}
}
function regionalPromptingRegionsToggleHandler(regionsNode) {
if (!regionsNode?.widgets) return;
function toggleWidgetState(useWidget, paramWidgets, paramNames) {
if (!useWidget || !paramWidgets || paramWidgets.length === 0) return;
function toggleEnabled() {
const enabled = useWidget.value === true;
for (let i = 0; i < paramWidgets.length; i++) {
const paramWidget = paramWidgets[i];
const paramName = paramNames[i];
if (!paramWidget) continue;
if (paramWidget.inputEl) {
paramWidget.inputEl.disabled = !enabled;
paramWidget.inputEl.style.opacity = enabled ? "1" : "0.5";
paramWidget.inputEl.style.cursor = enabled ? "text" : "not-allowed";
paramWidget.inputEl.readOnly = !enabled;
}
paramWidget.disabled = !enabled;
if (!paramWidget.inputEl && paramName) {
const nodeElement = regionsNode.htmlElements?.widgetsContainer || regionsNode.htmlElements;
if (nodeElement) {
const input = nodeElement.querySelector(`input[name="${paramName}"], textarea[name="${paramName}"], select[name="${paramName}"]`);
if (input) {
input.disabled = !enabled;
input.style.opacity = enabled ? "1" : "0.5";
input.style.cursor = enabled ? "text" : "not-allowed";
input.readOnly = !enabled;
}
}
}
}
regionsNode.setDirtyCanvas(true);
}
appendWidgetCB(useWidget, () => {
setTimeout(toggleEnabled, 50);
});
setTimeout(toggleEnabled, 100);
}
for (let idx = 1; idx <= 4; idx++) {
const useWidget = regionsNode.widgets.find(w => w.name === `useRegion${idx}`);
const promptWidget = regionsNode.widgets.find(w => w.name === `region${idx}Prompt`);
const x0Widget = regionsNode.widgets.find(w => w.name === `region${idx}_x0`);
const y0Widget = regionsNode.widgets.find(w => w.name === `region${idx}_y0`);
const x1Widget = regionsNode.widgets.find(w => w.name === `region${idx}_x1`);
const y1Widget = regionsNode.widgets.find(w => w.name === `region${idx}_y1`);
const paramWidgets = [promptWidget, x0Widget, y0Widget, x1Widget, y1Widget];
const paramNames = [
`region${idx}Prompt`,
`region${idx}_x0`,
`region${idx}_y0`,
`region${idx}_x1`,
`region${idx}_y1`,
];
if (useWidget) {
toggleWidgetState(useWidget, paramWidgets, paramNames);
}
}
}
function audioInferenceInputsToggleHandler(audioInputsNode) {
// Find widgets
const useVideoWidget = audioInputsNode.widgets.find(w => w.name === "useVideo");
@@ -3953,6 +4120,9 @@ export {
vectorizeToggleHandler,
useParameterToggleHandler,
imageInferenceToggleHandler,
imageInferenceAdvancedFeaturesToggleHandler,
watermarkAdvancedFeatureToggleHandler,
regionalPromptingRegionsToggleHandler,
upscalerToggleHandler,
videoUpscalerToggleHandler,
audioInferenceToggleHandler,
+8
View File
@@ -194,6 +194,9 @@ class txt2img:
"ultralytics": ("RUNWAREULTRALYTICSINPUTS", {
"tooltip": "Connect Runware Image Inference Ultralytics node to configure Ultralytics parameters (maskBlur, maskPadding, confidence, prompts, steps, CFGScale, strength).",
}),
"advancedFeatures": ("RUNWAREIMAGEINFERENCEADVANCEDFEATURES", {
"tooltip": "Connect Runware Image Advanced Feature Input to configure advancedFeatures: layerDiffuse (transparency), hiresFix (two-stage high-res), watermark, and regionalPrompting (per-region prompts and masks).",
}),
}
}
@@ -237,6 +240,7 @@ class txt2img:
safetyInputs = kwargs.get("safetyInputs", None)
settings = kwargs.get("settings", None)
ultralytics = kwargs.get("ultralytics", None)
advanced_features = kwargs.get("advancedFeatures", None)
seedImage = kwargs.get("seedImage", None)
seedImageStrength = kwargs.get("strength", 0.8)
maskImage = kwargs.get("maskImage", None)
@@ -402,6 +406,10 @@ class txt2img:
if ultralytics is not None and isinstance(ultralytics, dict) and len(ultralytics) > 0:
genConfig[0]["ultralytics"] = ultralytics
# Add advancedFeatures if provided (layerDiffuse, hiresFix, etc.)
if advanced_features is not None and isinstance(advanced_features, dict) and len(advanced_features) > 0:
genConfig[0]["advancedFeatures"] = advanced_features
if (multiInferenceMode):
return (None, genConfig)
else:
+80
View File
@@ -0,0 +1,80 @@
from typing import Dict, Any
class RunwareImageInferenceAdvancedFeatures:
"""Runware Image Advanced Feature Input Node"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {},
"optional": {
"useLayerDiffuse": ("BOOLEAN", {
"tooltip": "Enable to include layerDiffuse in advancedFeatures. When enabled, LayerDiffuse generates images with transparency (alpha) without post-processing background removal.",
"default": False,
}),
"layerDiffuse": ("BOOLEAN", {
"tooltip": "Enable LayerDiffuse technology for direct generation of images with transparency (alpha channels). Only used when 'Use Layer Diffuse' is enabled.",
"default": False,
"label_on": "true",
"label_off": "false",
}),
"useHiresFix": ("BOOLEAN", {
"tooltip": "Enable to include hiresFix in advancedFeatures. When enabled, uses a two-stage process for higher-resolution, higher-detail images.",
"default": False,
}),
"hiresFix": ("BOOLEAN", {
"tooltip": "Enable a two-stage generation: first at native resolution, then upscale and refine in a second pass. Only used when 'Use Hires Fix' is enabled.",
"default": False,
"label_on": "true",
"label_off": "false",
}),
"watermark": ("RUNWAREIMAGEWATERMARKADVFEATURE", {
"tooltip": "Connect Runware Watermark Advanced Feature to include watermark configuration in advancedFeatures.watermark.",
}),
"regionalPrompting": ("RUNWAREREGIONALPROMPTINGADVFEATURE", {
"tooltip": "Connect Runware Regional Prompting Advanced Feature to configure regionalPrompting in advancedFeatures.",
}),
},
}
RETURN_TYPES = ("RUNWAREIMAGEINFERENCEADVANCEDFEATURES",)
RETURN_NAMES = ("advancedFeatures",)
FUNCTION = "createAdvancedFeatures"
CATEGORY = "Runware"
DESCRIPTION = (
"Configure advanced features for Runware Image Inference: layerDiffuse (transparency), "
"hiresFix (two-stage high-res), watermark, and regional prompting. "
"Connect to Runware Image Inference advancedFeatures input."
)
def createAdvancedFeatures(self, watermark=None, regionalPrompting=None, **kwargs) -> tuple:
"""Build advancedFeatures dict for the API."""
use_layer_diffuse = kwargs.get("useLayerDiffuse", False)
layer_diffuse = kwargs.get("layerDiffuse", False)
use_hires_fix = kwargs.get("useHiresFix", False)
hires_fix = kwargs.get("hiresFix", False)
advanced_features: Dict[str, Any] = {}
if use_layer_diffuse:
advanced_features["layerDiffuse"] = bool(layer_diffuse)
if use_hires_fix:
advanced_features["hiresFix"] = bool(hires_fix)
if isinstance(watermark, dict) and watermark:
advanced_features["watermark"] = watermark
if isinstance(regionalPrompting, dict) and regionalPrompting:
advanced_features["regionalPrompting"] = regionalPrompting
return (advanced_features,)
NODE_CLASS_MAPPINGS = {
"RunwareImageInferenceAdvancedFeatures": RunwareImageInferenceAdvancedFeatures,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RunwareImageInferenceAdvancedFeatures": "Runware Image Advanced Feature Input",
}
@@ -0,0 +1,80 @@
from typing import Dict, Any, Tuple
class RunwareRegionalPromptingAdvancedFeature:
"""
Runware Regional Prompting Advanced Feature Node
Builds the advancedFeatures.regionalPrompting configuration object:
- injectSteps
- backgroundPrompt
- baseRatio
- regions (connected from the Regions node)
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {},
"optional": {
"injectSteps": ("INT", {
"tooltip": "Steps to inject regional masks (1-100).",
"default": 15,
"min": 1,
"max": 100,
}),
"backgroundPrompt": ("STRING", {
"tooltip": "Background prompt applied to uncovered areas.",
"default": "",
}),
"baseRatio": ("FLOAT", {
"tooltip": "Base prompt attention weight (0.0 - 1.0).",
"default": 0.2,
"min": 0.0,
"max": 1.0,
"step": 0.01,
}),
"regions": ("RUNWAREREGIONALPROMPTINGREGIONS", {
"tooltip": "Connect Runware Regional Prompting Advanced Feature Regions node to define regional prompts and masks.",
}),
},
}
RETURN_TYPES: Tuple[str] = ("RUNWAREREGIONALPROMPTINGADVFEATURE",)
RETURN_NAMES = ("regionalPrompting",)
FUNCTION = "createRegionalPrompting"
CATEGORY = "Runware"
DESCRIPTION = (
"Configure regional prompting advanced feature (injectSteps, backgroundPrompt, "
"baseRatio, and regions). Connect output to Runware Image Advanced Feature Input "
"regionalPrompting input."
)
def createRegionalPrompting(self, regions=None, **kwargs) -> tuple:
"""Build regionalPrompting dict for advancedFeatures.regionalPrompting."""
inject_steps = int(kwargs.get("injectSteps", 15))
base_ratio = float(kwargs.get("baseRatio", 0.2))
background_prompt = (kwargs.get("backgroundPrompt", "") or "").strip()
config: Dict[str, Any] = {
"injectSteps": inject_steps,
"baseRatio": base_ratio,
}
if background_prompt:
config["backgroundPrompt"] = background_prompt
if isinstance(regions, list) and regions:
config["regions"] = regions
return (config,)
NODE_CLASS_MAPPINGS = {
"RunwareRegionalPromptingAdvancedFeature": RunwareRegionalPromptingAdvancedFeature,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RunwareRegionalPromptingAdvancedFeature": "Runware Regional Prompting Advanced Feature",
}
@@ -0,0 +1,196 @@
from typing import Dict, Any, List, Tuple
class RunwareRegionalPromptingAdvancedFeatureRegions:
"""
Runware Regional Prompting Advanced Feature Regions Node
Builds the advancedFeatures.regionalPrompting.regions array, with up to 4 regions.
Each region has a prompt and a rectangular mask [x0, y0, x1, y1].
"""
@classmethod
def INPUT_TYPES(cls):
coords_tooltip = "Mask rectangle as [x0, y0, x1, y1] in image pixel coordinates."
return {
"required": {},
"optional": {
# Region 1
"useRegion1": ("BOOLEAN", {
"tooltip": "Enable first regional prompt mask.",
"default": False,
}),
"region1Prompt": ("STRING", {
"tooltip": "Prompt for region 1.",
"default": "",
}),
"region1_x0": ("INT", {
"tooltip": coords_tooltip,
"default": 0,
"min": 0,
"max": 8192,
}),
"region1_y0": ("INT", {
"tooltip": coords_tooltip,
"default": 0,
"min": 0,
"max": 8192,
}),
"region1_x1": ("INT", {
"tooltip": coords_tooltip,
"default": 512,
"min": 0,
"max": 8192,
}),
"region1_y1": ("INT", {
"tooltip": coords_tooltip,
"default": 512,
"min": 0,
"max": 8192,
}),
# Region 2
"useRegion2": ("BOOLEAN", {
"tooltip": "Enable second regional prompt mask.",
"default": False,
}),
"region2Prompt": ("STRING", {
"tooltip": "Prompt for region 2.",
"default": "",
}),
"region2_x0": ("INT", {
"tooltip": coords_tooltip,
"default": 512,
"min": 0,
"max": 8192,
}),
"region2_y0": ("INT", {
"tooltip": coords_tooltip,
"default": 0,
"min": 0,
"max": 8192,
}),
"region2_x1": ("INT", {
"tooltip": coords_tooltip,
"default": 1024,
"min": 0,
"max": 8192,
}),
"region2_y1": ("INT", {
"tooltip": coords_tooltip,
"default": 512,
"min": 0,
"max": 8192,
}),
# Region 3
"useRegion3": ("BOOLEAN", {
"tooltip": "Enable third regional prompt mask.",
"default": False,
}),
"region3Prompt": ("STRING", {
"tooltip": "Prompt for region 3.",
"default": "",
}),
"region3_x0": ("INT", {
"tooltip": coords_tooltip,
"default": 0,
"min": 0,
"max": 8192,
}),
"region3_y0": ("INT", {
"tooltip": coords_tooltip,
"default": 512,
"min": 0,
"max": 8192,
}),
"region3_x1": ("INT", {
"tooltip": coords_tooltip,
"default": 512,
"min": 0,
"max": 8192,
}),
"region3_y1": ("INT", {
"tooltip": coords_tooltip,
"default": 1024,
"min": 0,
"max": 8192,
}),
# Region 4
"useRegion4": ("BOOLEAN", {
"tooltip": "Enable fourth regional prompt mask.",
"default": False,
}),
"region4Prompt": ("STRING", {
"tooltip": "Prompt for region 4.",
"default": "",
}),
"region4_x0": ("INT", {
"tooltip": coords_tooltip,
"default": 512,
"min": 0,
"max": 8192,
}),
"region4_y0": ("INT", {
"tooltip": coords_tooltip,
"default": 512,
"min": 0,
"max": 8192,
}),
"region4_x1": ("INT", {
"tooltip": coords_tooltip,
"default": 1024,
"min": 0,
"max": 8192,
}),
"region4_y1": ("INT", {
"tooltip": coords_tooltip,
"default": 1024,
"min": 0,
"max": 8192,
}),
},
}
RETURN_TYPES: Tuple[str] = ("RUNWAREREGIONALPROMPTINGREGIONS",)
RETURN_NAMES = ("regions",)
FUNCTION = "createRegions"
CATEGORY = "Runware"
DESCRIPTION = (
"Configure regions array for regional prompting. Each enabled region provides "
"a prompt and a rectangular mask [x0, y0, x1, y1]. Connect to Runware Regional "
"Prompting Advanced Feature regions input."
)
def createRegions(self, **kwargs) -> tuple:
"""Build list of regional prompting regions."""
regions: List[Dict[str, Any]] = []
for idx in range(1, 5):
use_region = kwargs.get(f"useRegion{idx}", False)
prompt = (kwargs.get(f"region{idx}Prompt", "") or "").strip()
if not use_region or not prompt:
continue
x0 = int(kwargs.get(f"region{idx}_x0", 0))
y0 = int(kwargs.get(f"region{idx}_y0", 0))
x1 = int(kwargs.get(f"region{idx}_x1", 0))
y1 = int(kwargs.get(f"region{idx}_y1", 0))
mask = [x0, y0, x1, y1]
regions.append(
{
"prompt": prompt,
"mask": mask,
}
)
return (regions,)
NODE_CLASS_MAPPINGS = {
"RunwareRegionalPromptingAdvancedFeatureRegions": RunwareRegionalPromptingAdvancedFeatureRegions,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RunwareRegionalPromptingAdvancedFeatureRegions": "Runware Regional Prompting Advanced Feature Regions",
}
+162
View File
@@ -0,0 +1,162 @@
from typing import Dict, Any, Tuple
from .utils.runwareUtils import convertTensor2IMG
class RunwareWatermarkAdvancedFeature:
"""Runware Watermark Advanced Feature Node"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {},
"optional": {
"useText": ("BOOLEAN", {
"tooltip": "Enable to set text content for the watermark (2-32 characters).",
"default": False,
}),
"text": ("STRING", {
"tooltip": "Text content for the watermark (2-32 characters). Only used when 'Use Text' is enabled.",
"default": "",
}),
"useImage": ("BOOLEAN", {
"tooltip": "Enable to set image content for the watermark (connect an IMAGE, UUID, URL, data URI, or base64).",
"default": False,
}),
"image": ("IMAGE", {
"tooltip": "Image to use as watermark. Can be connected from a Load Image node; internally converted to a data URI/UUID when used.",
}),
"useDisplayPosition": ("BOOLEAN", {
"tooltip": "Enable to set watermark display position.",
"default": False,
}),
"displayPosition": ([
"top-left", "top-center", "top-right",
"center-left", "center-center", "center-right",
"bottom-left", "bottom-center", "bottom-right",
], {
"tooltip": "Watermark position on the image.",
"default": "bottom-right",
}),
"useTiled": ("BOOLEAN", {
"tooltip": "Enable to tile the watermark across the image at -45 degrees.",
"default": False,
}),
"tiled": ("BOOLEAN", {
"tooltip": "When enabled, distribute watermark across the image at -45 degrees. Only used when 'Use Tiled' is enabled.",
"default": False,
"label_on": "true",
"label_off": "false",
}),
"useOpacity": ("BOOLEAN", {
"tooltip": "Enable to set watermark opacity (0.1 - 1.0).",
"default": False,
}),
"opacity": ("FLOAT", {
"tooltip": "Watermark opacity (0.1 - 1.0). Only used when 'Use Opacity' is enabled.",
"default": 0.6,
"min": 0.1,
"max": 1.0,
"step": 0.05,
}),
"useFontColor": ("BOOLEAN", {
"tooltip": "Enable to set watermark font color (hex).",
"default": False,
}),
"fontColor": ("STRING", {
"tooltip": "Text color in hex format (e.g. #ffffff). Only used when 'Use Font Color' is enabled.",
"default": "#ffffff",
}),
"useBgColor": ("BOOLEAN", {
"tooltip": "Enable to set watermark background color (hex).",
"default": False,
}),
"bgColor": ("STRING", {
"tooltip": "Background color in hex format (e.g. #000000 or transparent). Only used when 'Use Bg Color' is enabled.",
"default": "transparent",
}),
},
}
RETURN_TYPES: Tuple[str] = ("RUNWAREIMAGEWATERMARKADVFEATURE",)
RETURN_NAMES = ("watermark",)
FUNCTION = "createWatermark"
CATEGORY = "Runware"
DESCRIPTION = (
"Configure watermark advanced feature for Runware Image Inference. "
"You must provide exactly one of text or image. Connect output to "
"Runware Image Advanced Feature Input watermark connection."
)
def createWatermark(self, **kwargs) -> tuple:
"""Build watermark dict for advancedFeatures.watermark."""
use_text = kwargs.get("useText", False)
text = (kwargs.get("text", "") or "").strip()
use_image = kwargs.get("useImage", False)
raw_image = kwargs.get("image", None)
has_text = use_text and bool(text)
image_str: str = ""
if use_image:
if raw_image is None:
raise Exception(
"Watermark 'useImage' is enabled but no IMAGE input was provided. "
"Connect an IMAGE (e.g., from a Load Image node) to the watermark node."
)
# If an IMAGE tensor is connected, convert it to a data URI / UUID.
try:
# convertTensor2IMG handles caching and returns either UUID or data URI.
image_str = convertTensor2IMG(raw_image)
except Exception as e:
raise Exception(
f"Failed to convert watermark IMAGE input. "
f"Ensure the connected image is valid. Details: {e}"
) from e
has_image = use_image and bool(image_str)
if has_text == has_image:
raise Exception(
"Watermark configuration requires exactly one of text or image (but not both or neither)."
)
use_pos = kwargs.get("useDisplayPosition", False)
pos = kwargs.get("displayPosition", "bottom-right")
use_tiled = kwargs.get("useTiled", False)
tiled = bool(kwargs.get("tiled", False))
use_opacity = kwargs.get("useOpacity", False)
opacity = float(kwargs.get("opacity", 0.6))
use_font = kwargs.get("useFontColor", False)
font_color = (kwargs.get("fontColor", "#ffffff") or "").strip()
use_bg = kwargs.get("useBgColor", False)
bg_color = (kwargs.get("bgColor", "transparent") or "").strip()
watermark: Dict[str, Any] = {}
if has_text:
watermark["text"] = text
if has_image:
watermark["image"] = image_str
if use_pos:
watermark["displayPosition"] = pos
if use_tiled:
watermark["tiled"] = tiled
if use_opacity:
watermark["opacity"] = opacity
if use_font and font_color:
watermark["fontColor"] = font_color
if use_bg and bg_color:
watermark["bgColor"] = bg_color
return (watermark,)
NODE_CLASS_MAPPINGS = {
"RunwareWatermarkAdvancedFeature": RunwareWatermarkAdvancedFeature,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RunwareWatermarkAdvancedFeature": "Runware Watermark Advanced Feature",
}
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