800 lines
25 KiB
Python
800 lines
25 KiB
Python
"""Typed, stage-scoped configuration buses for PortraitUtils workflows.
|
|
|
|
V2 deliberately keeps configuration objects separate from images and masks.
|
|
PortraitUtils processing wrappers consume the typed objects directly; small
|
|
readers expose primitive values only where a third-party node requires them.
|
|
Only the current configured interfaces are registered. Shared processing
|
|
implementations are internal and do not expose their retired node IDs.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from .auto_adjust import AutoAdjustNode
|
|
from .intelligent_auto_crop import IntelligentAutoCrop
|
|
from .load_image_combined import LoadImageCombined
|
|
from .outpaint_config import OutpaintPaddingComputeNode
|
|
from .core.outpaint import compute_padding
|
|
from .core.borders import BorderCropEngine
|
|
from .core.composite import CompositeEngine
|
|
from .core.tensors import image_tensor
|
|
from .smart_photo_prepare import ASPECT_OPTIONS
|
|
from .stitch_image_by_mask import StitchByMask
|
|
from .seed_utils import (
|
|
SEED_MAX,
|
|
SEED_MIN,
|
|
_clamp_seed,
|
|
_new_random_seed,
|
|
_resolve_seed,
|
|
)
|
|
|
|
from .core.validation import choice, integer
|
|
from .core.contracts import (
|
|
validate_config,
|
|
PADDING_FILLS,
|
|
CROP_CONFIG_TYPE,
|
|
EDIT_CONFIG_TYPE,
|
|
OUTPAINT_CONFIG_TYPE,
|
|
FINISH_CONFIG_TYPE,
|
|
SOURCE_INFO_TYPE,
|
|
CropConfigV2,
|
|
EditConfigV2,
|
|
OutpaintConfigV2,
|
|
FinishConfigV2,
|
|
SourceInfoV2,
|
|
_expect,
|
|
)
|
|
|
|
|
|
class CropConfigNodeV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"autocrop_section": (
|
|
["── AUTOCROP · SOLID BORDER REMOVAL ──"],
|
|
{"default": "── AUTOCROP · SOLID BORDER REMOVAL ──"},
|
|
),
|
|
"autocrop_strip_bottom_banner": (
|
|
"BOOLEAN",
|
|
{
|
|
"default": True,
|
|
"tooltip": "Remove a dark text/banner strip touching the bottom edge before border detection.",
|
|
},
|
|
),
|
|
"autocrop_detect_borders": (
|
|
"BOOLEAN",
|
|
{
|
|
"default": True,
|
|
"tooltip": "Remove uniform solid borders before subject framing.",
|
|
},
|
|
),
|
|
"autocrop_fuzz_tolerance": (
|
|
"FLOAT",
|
|
{
|
|
"default": 0.07,
|
|
"min": 0.0,
|
|
"max": 0.5,
|
|
"step": 0.01,
|
|
"tooltip": "Color variation tolerated while identifying a solid border.",
|
|
},
|
|
),
|
|
"autocrop_edge_uniformity": (
|
|
"FLOAT",
|
|
{
|
|
"default": 0.85,
|
|
"min": 0.0,
|
|
"max": 1.0,
|
|
"step": 0.01,
|
|
"tooltip": "Fraction of an edge row/column that must match the border color.",
|
|
},
|
|
),
|
|
"autocrop_pad_px": (
|
|
"INT",
|
|
{
|
|
"default": 0,
|
|
"min": 0,
|
|
"max": 256,
|
|
"step": 1,
|
|
"tooltip": "Pixels of detected border to retain on each cropped edge.",
|
|
},
|
|
),
|
|
"smart_crop_section": (
|
|
["── SMART CROP · SUBJECT / NATIVE SIZE ──"],
|
|
{"default": "── SMART CROP · SUBJECT / NATIVE SIZE ──"},
|
|
),
|
|
"smart_crop": (
|
|
"BOOLEAN",
|
|
{
|
|
"default": False,
|
|
"tooltip": "OFF keeps the widest safe frame. ON frames more tightly around the subject mask.",
|
|
},
|
|
),
|
|
"resolution_policy": (
|
|
[
|
|
"Auto (preserve detail)",
|
|
"Auto (closest scale)",
|
|
"Force 1 MP",
|
|
"Force 1.5 MP",
|
|
"Force 2K (~4 MP)",
|
|
],
|
|
{
|
|
"default": "Auto (preserve detail)",
|
|
"tooltip": "Auto: FireRed uses ~1 MP workflow presets; Klein can use 1/1.5 MP; Qwen 2.1 can also use 2K (~4 MP). Force 1.5 MP is an experimental larger output for FireRed. Force 2K is Qwen 2.1 only.",
|
|
},
|
|
),
|
|
"forced_aspect_ratio": (
|
|
list(ASPECT_OPTIONS),
|
|
{
|
|
"default": "Auto",
|
|
"tooltip": "Force an exact output aspect family, or use Auto for minimal cropping.",
|
|
},
|
|
),
|
|
"subject_mask": (
|
|
"STRING",
|
|
{
|
|
"default": "",
|
|
"multiline": True,
|
|
"placeholder": "Subject mask prompt",
|
|
},
|
|
),
|
|
},
|
|
"optional": {
|
|
"padding_fill": (
|
|
list(PADDING_FILLS),
|
|
{
|
|
"default": "Edge extension",
|
|
"tooltip": "Smart Photo Prepare only: fill added canvas with Black, White, or repeated edge pixels. Does not change crop/protection decisions or AutoCrop border removal.",
|
|
},
|
|
),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (CROP_CONFIG_TYPE,)
|
|
RETURN_NAMES = ("crop_config",)
|
|
FUNCTION = "build"
|
|
CATEGORY = "PortraitUtils/Config V2"
|
|
|
|
def build(
|
|
self,
|
|
autocrop_section,
|
|
autocrop_strip_bottom_banner,
|
|
autocrop_detect_borders,
|
|
autocrop_fuzz_tolerance,
|
|
autocrop_edge_uniformity,
|
|
autocrop_pad_px,
|
|
smart_crop_section,
|
|
smart_crop,
|
|
resolution_policy,
|
|
forced_aspect_ratio,
|
|
subject_mask,
|
|
padding_fill="Edge extension",
|
|
):
|
|
return (
|
|
validate_config(CropConfigV2(
|
|
autocrop_strip_bottom_banner,
|
|
autocrop_detect_borders,
|
|
autocrop_fuzz_tolerance,
|
|
autocrop_edge_uniformity,
|
|
autocrop_pad_px,
|
|
smart_crop,
|
|
resolution_policy,
|
|
forced_aspect_ratio,
|
|
subject_mask,
|
|
padding_fill,
|
|
), "crop_config"),
|
|
)
|
|
|
|
|
|
class EditConfigNodeV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"blend_opacity": (
|
|
"FLOAT",
|
|
{"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01},
|
|
),
|
|
"stitch_opacity": (
|
|
"FLOAT",
|
|
{"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01},
|
|
),
|
|
"inpaint_prompt": (
|
|
"STRING",
|
|
{
|
|
"default": "",
|
|
"multiline": True,
|
|
"placeholder": "Image-edit / inpaint prompt",
|
|
},
|
|
),
|
|
"stitch_prompt_1": (
|
|
"STRING",
|
|
{
|
|
"default": "",
|
|
"multiline": False,
|
|
"placeholder": "Stitch 1 (e.g. hair)",
|
|
},
|
|
),
|
|
"stitch_prompt_2": (
|
|
"STRING",
|
|
{
|
|
"default": "",
|
|
"multiline": False,
|
|
"placeholder": "Stitch 2 (e.g. background)",
|
|
},
|
|
),
|
|
"stitch_prompt_3": (
|
|
"STRING",
|
|
{
|
|
"default": "",
|
|
"multiline": False,
|
|
"placeholder": "Stitch 3 (e.g. rocks)",
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (EDIT_CONFIG_TYPE,)
|
|
RETURN_NAMES = ("edit_config",)
|
|
FUNCTION = "build"
|
|
CATEGORY = "PortraitUtils/Config V2"
|
|
|
|
def build(self, blend_opacity, stitch_opacity, inpaint_prompt,
|
|
stitch_prompt_1, stitch_prompt_2, stitch_prompt_3):
|
|
return (
|
|
validate_config(EditConfigV2(
|
|
inpaint_prompt=inpaint_prompt,
|
|
blend_opacity=blend_opacity,
|
|
stitch_opacity=stitch_opacity,
|
|
stitch_prompt_1=stitch_prompt_1,
|
|
stitch_prompt_2=stitch_prompt_2,
|
|
stitch_prompt_3=stitch_prompt_3,
|
|
), "edit_config"),
|
|
)
|
|
|
|
|
|
class OutpaintConfigNodeV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"mode": (["Percent", "Pixels"], {"default": "Percent"}),
|
|
"gravity": (
|
|
[
|
|
"center",
|
|
"left",
|
|
"right",
|
|
"top",
|
|
"bottom",
|
|
"top left",
|
|
"top right",
|
|
"bottom right",
|
|
"bottom left",
|
|
],
|
|
{"default": "center"},
|
|
),
|
|
"horizontal_percent": (
|
|
"FLOAT",
|
|
{"default": 20.0, "min": 0.0, "max": 10000.0, "step": 0.1},
|
|
),
|
|
"vertical_percent": (
|
|
"FLOAT",
|
|
{"default": 10.0, "min": 0.0, "max": 10000.0, "step": 0.1},
|
|
),
|
|
"left_px": ("INT", {"default": 0, "min": 0, "max": 1000000}),
|
|
"right_px": ("INT", {"default": 0, "min": 0, "max": 1000000}),
|
|
"top_px": ("INT", {"default": 0, "min": 0, "max": 1000000}),
|
|
"bottom_px": ("INT", {"default": 0, "min": 0, "max": 1000000}),
|
|
"outpaint_prompt": (
|
|
"STRING",
|
|
{
|
|
"default": "",
|
|
"multiline": True,
|
|
"placeholder": "Outpaint prompt",
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = (OUTPAINT_CONFIG_TYPE,)
|
|
RETURN_NAMES = ("outpaint_config",)
|
|
FUNCTION = "build"
|
|
CATEGORY = "PortraitUtils/Config V2"
|
|
|
|
def build(
|
|
self,
|
|
mode,
|
|
gravity,
|
|
horizontal_percent,
|
|
vertical_percent,
|
|
left_px,
|
|
right_px,
|
|
top_px,
|
|
bottom_px,
|
|
outpaint_prompt,
|
|
):
|
|
return (
|
|
validate_config(OutpaintConfigV2(
|
|
mode,
|
|
gravity,
|
|
horizontal_percent,
|
|
vertical_percent,
|
|
left_px,
|
|
right_px,
|
|
top_px,
|
|
bottom_px,
|
|
outpaint_prompt
|
|
), "outpaint_config"),
|
|
)
|
|
|
|
|
|
class FinishConfigNodeV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"initial_upscale": (
|
|
"FLOAT",
|
|
{"default": 2.0, "min": 0.1, "max": 100.0, "step": 0.1},
|
|
),
|
|
"final_upscale": (
|
|
"FLOAT",
|
|
{"default": 4.0, "min": 0.1, "max": 100.0, "step": 0.1},
|
|
),
|
|
"auto_levels": ("BOOLEAN", {"default": False}),
|
|
"auto_tone": ("BOOLEAN", {"default": False}),
|
|
"auto_color": ("BOOLEAN", {"default": False}),
|
|
"flip_horizontal": ("BOOLEAN", {"default": False}),
|
|
"seed": (
|
|
"INT",
|
|
{"default": 0, "min": SEED_MIN, "max": SEED_MAX},
|
|
),
|
|
},
|
|
"hidden": {
|
|
"prompt": "PROMPT",
|
|
"extra_pnginfo": "EXTRA_PNGINFO",
|
|
"unique_id": "UNIQUE_ID",
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (FINISH_CONFIG_TYPE,)
|
|
RETURN_NAMES = ("finish_config",)
|
|
FUNCTION = "build"
|
|
CATEGORY = "PortraitUtils/Config V2"
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, **kwargs):
|
|
seed = kwargs.get("seed", 0)
|
|
if seed in (-1, -2, -3):
|
|
return _new_random_seed()
|
|
visible = tuple(
|
|
(key, value)
|
|
for key, value in kwargs.items()
|
|
if key not in ("prompt", "extra_pnginfo", "unique_id")
|
|
)
|
|
return repr(visible)
|
|
|
|
def build(
|
|
self,
|
|
initial_upscale,
|
|
final_upscale,
|
|
auto_levels,
|
|
auto_tone,
|
|
auto_color,
|
|
flip_horizontal,
|
|
seed,
|
|
prompt=None,
|
|
extra_pnginfo=None,
|
|
unique_id=None,
|
|
):
|
|
integer(seed, "Finish Config.seed", -3, SEED_MAX)
|
|
seed_value = _clamp_seed(
|
|
_resolve_seed(seed, prompt, extra_pnginfo, unique_id)
|
|
)
|
|
return (
|
|
validate_config(FinishConfigV2(
|
|
initial_upscale,
|
|
final_upscale,
|
|
auto_levels,
|
|
auto_tone,
|
|
auto_color,
|
|
flip_horizontal,
|
|
seed_value
|
|
), "finish_config"),
|
|
)
|
|
|
|
|
|
class LoadImageCombinedV2(LoadImageCombined):
|
|
RETURN_TYPES = ("IMAGE", SOURCE_INFO_TYPE)
|
|
RETURN_NAMES = ("image", "source_info")
|
|
FUNCTION = "load_image_v2"
|
|
|
|
def load_image_v2(
|
|
self,
|
|
mode,
|
|
input_dir,
|
|
output_dir,
|
|
pattern,
|
|
strip_trailing_numbers,
|
|
repeat_last,
|
|
image,
|
|
):
|
|
loaded, filename, resolved_output_dir, width, height = self.load_image(
|
|
mode,
|
|
input_dir,
|
|
output_dir,
|
|
pattern,
|
|
strip_trailing_numbers,
|
|
repeat_last,
|
|
image,
|
|
)
|
|
return (
|
|
loaded,
|
|
SourceInfoV2(filename, resolved_output_dir, int(width), int(height)),
|
|
)
|
|
|
|
|
|
class IntelligentAutoCropV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"crop_config": (CROP_CONFIG_TYPE, {"forceInput": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = IntelligentAutoCrop.RETURN_TYPES
|
|
RETURN_NAMES = IntelligentAutoCrop.RETURN_NAMES
|
|
FUNCTION = "run"
|
|
CATEGORY = "PortraitUtils/Transform V2"
|
|
|
|
def run(self, image, crop_config):
|
|
config = _expect(crop_config, CropConfigV2, "crop_config")
|
|
return BorderCropEngine().run(image, config, "Configured tolerances")
|
|
|
|
|
|
|
|
|
|
class OutpaintPaddingComputeV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"outpaint_config": (OUTPAINT_CONFIG_TYPE, {"forceInput": True}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = OutpaintPaddingComputeNode.RETURN_TYPES
|
|
RETURN_NAMES = OutpaintPaddingComputeNode.RETURN_NAMES
|
|
FUNCTION = "compute"
|
|
CATEGORY = "PortraitUtils/Config V2"
|
|
|
|
def compute(self, image, outpaint_config):
|
|
config = _expect(
|
|
outpaint_config, OutpaintConfigV2, "outpaint_config"
|
|
)
|
|
tensor = image_tensor(image, "Outpaint Padding.image")
|
|
return compute_padding(int(tensor.shape[2]), int(tensor.shape[1]), config)
|
|
|
|
|
|
class AutoAdjustV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"finish_config": (FINISH_CONFIG_TYPE, {"forceInput": True}),
|
|
"precision": (
|
|
["Histogram (fast)", "Exact"],
|
|
{
|
|
"default": "Histogram (fast)",
|
|
"tooltip": "256-bin statistics are efficient and close to Photoshop's histogram-based behavior.",
|
|
},
|
|
),
|
|
"levels_shadow_clip_pct": (
|
|
"FLOAT",
|
|
{"default": 0.1, "min": 0.0, "max": 5.0, "step": 0.01},
|
|
),
|
|
"levels_highlight_clip_pct": (
|
|
"FLOAT",
|
|
{"default": 0.1, "min": 0.0, "max": 5.0, "step": 0.01},
|
|
),
|
|
"levels_gamma_normalize": ("BOOLEAN", {"default": False}),
|
|
"tone_mode": (
|
|
["Per-channel", "Monochromatic"],
|
|
{"default": "Per-channel"},
|
|
),
|
|
"tone_shadow_clip_pct": (
|
|
"FLOAT",
|
|
{"default": 0.1, "min": 0.0, "max": 5.0, "step": 0.01},
|
|
),
|
|
"tone_highlight_clip_pct": (
|
|
"FLOAT",
|
|
{"default": 0.1, "min": 0.0, "max": 5.0, "step": 0.01},
|
|
),
|
|
"snap_neutral_midtones": (
|
|
"BOOLEAN",
|
|
{
|
|
"default": True,
|
|
"tooltip": "Used by Auto Color to neutralize genuinely low-chroma midtones.",
|
|
},
|
|
),
|
|
"adjustment_mode": (
|
|
[
|
|
"Use Finish Config switches",
|
|
"Auto Levels / Contrast",
|
|
"Auto Tone",
|
|
"Auto Color",
|
|
],
|
|
{
|
|
"default": "Use Finish Config switches",
|
|
"tooltip": "Select one Photoshop-style command or use the existing Finish Config booleans.",
|
|
},
|
|
),
|
|
"strength": (
|
|
"FLOAT",
|
|
{
|
|
"default": 1.0,
|
|
"min": 0.0,
|
|
"max": 1.0,
|
|
"step": 0.01,
|
|
"tooltip": "Blend the correction with the original image.",
|
|
},
|
|
),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "apply"
|
|
CATEGORY = "PortraitUtils/Adjustment V2"
|
|
|
|
def apply(
|
|
self,
|
|
image,
|
|
finish_config,
|
|
precision,
|
|
levels_shadow_clip_pct,
|
|
levels_highlight_clip_pct,
|
|
levels_gamma_normalize,
|
|
tone_mode,
|
|
tone_shadow_clip_pct,
|
|
tone_highlight_clip_pct,
|
|
snap_neutral_midtones,
|
|
adjustment_mode="Use Finish Config switches",
|
|
strength=1.0,
|
|
):
|
|
config = _expect(finish_config, FinishConfigV2, "finish_config")
|
|
node_mode = (
|
|
"Use switches (legacy)"
|
|
if adjustment_mode == "Use Finish Config switches"
|
|
else adjustment_mode
|
|
)
|
|
return AutoAdjustNode().apply(
|
|
image,
|
|
precision,
|
|
config.auto_levels,
|
|
levels_shadow_clip_pct,
|
|
levels_highlight_clip_pct,
|
|
levels_gamma_normalize,
|
|
config.auto_tone,
|
|
tone_mode,
|
|
tone_shadow_clip_pct,
|
|
tone_highlight_clip_pct,
|
|
config.auto_color,
|
|
snap_neutral_midtones,
|
|
config.flip_horizontal,
|
|
node_mode,
|
|
strength,
|
|
)
|
|
|
|
|
|
class StitchByMaskV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image_a": ("IMAGE",),
|
|
"image_b": ("IMAGE",),
|
|
"edit_config": (EDIT_CONFIG_TYPE, {"forceInput": True}),
|
|
"opacity_source": (
|
|
["blend_opacity", "stitch_opacity"],
|
|
{"default": "stitch_opacity"},
|
|
),
|
|
"invert_mask": ("BOOLEAN", {"default": False}),
|
|
"bypass_mask": ("BOOLEAN", {"default": False}),
|
|
"feather_radius": (
|
|
"INT",
|
|
{"default": 5, "min": 0, "max": 100, "step": 1},
|
|
),
|
|
"force_size": ("BOOLEAN", {"default": False}),
|
|
"target_width": (
|
|
"INT",
|
|
{"default": 1344, "min": 16, "max": 8192, "step": 1},
|
|
),
|
|
"target_height": (
|
|
"INT",
|
|
{"default": 768, "min": 16, "max": 8192, "step": 1},
|
|
),
|
|
},
|
|
"optional": {"mask": ("MASK",)},
|
|
}
|
|
|
|
RETURN_TYPES = StitchByMask.RETURN_TYPES
|
|
RETURN_NAMES = StitchByMask.RETURN_NAMES
|
|
FUNCTION = "blend"
|
|
CATEGORY = "PortraitUtils/Composite V2"
|
|
|
|
def blend(
|
|
self,
|
|
image_a,
|
|
image_b,
|
|
edit_config,
|
|
opacity_source,
|
|
invert_mask=False,
|
|
bypass_mask=False,
|
|
feather_radius=5,
|
|
force_size=False,
|
|
target_width=1344,
|
|
target_height=768,
|
|
mask=None,
|
|
):
|
|
config = _expect(edit_config, EditConfigV2, "edit_config")
|
|
return CompositeEngine().blend(
|
|
image_a, image_b, config, opacity_source, invert_mask, bypass_mask,
|
|
feather_radius, force_size, target_width, target_height, mask,
|
|
)
|
|
|
|
|
|
class CropPromptV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {"required": {"crop_config": (CROP_CONFIG_TYPE, {"forceInput": True})}}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("subject_mask",)
|
|
FUNCTION = "read"
|
|
CATEGORY = "PortraitUtils/Config V2/Readers"
|
|
|
|
def read(self, crop_config):
|
|
return (_expect(crop_config, CropConfigV2, "crop_config").subject_mask,)
|
|
|
|
|
|
class EditPromptV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"edit_config": (EDIT_CONFIG_TYPE, {"forceInput": True}),
|
|
"prompt": (
|
|
["inpaint_prompt", "stitch_prompt_1", "stitch_prompt_2", "stitch_prompt_3"],
|
|
{"default": "inpaint_prompt"},
|
|
),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("prompt",)
|
|
FUNCTION = "read"
|
|
CATEGORY = "PortraitUtils/Config V2/Readers"
|
|
|
|
def read(self, edit_config, prompt):
|
|
config = _expect(edit_config, EditConfigV2, "edit_config")
|
|
choice(prompt, "prompt", ("inpaint_prompt", "stitch_prompt_1", "stitch_prompt_2", "stitch_prompt_3"))
|
|
return (getattr(config, prompt),)
|
|
|
|
|
|
class OutpaintPromptV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"outpaint_config": (OUTPAINT_CONFIG_TYPE, {"forceInput": True})
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("outpaint_prompt",)
|
|
FUNCTION = "read"
|
|
CATEGORY = "PortraitUtils/Config V2/Readers"
|
|
|
|
def read(self, outpaint_config):
|
|
return (
|
|
_expect(
|
|
outpaint_config, OutpaintConfigV2, "outpaint_config"
|
|
).outpaint_prompt,
|
|
)
|
|
|
|
|
|
class FinishSeedV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"finish_config": (FINISH_CONFIG_TYPE, {"forceInput": True})
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("INT",)
|
|
RETURN_NAMES = ("seed",)
|
|
FUNCTION = "read"
|
|
CATEGORY = "PortraitUtils/Config V2/Readers"
|
|
|
|
def read(self, finish_config):
|
|
return (_expect(finish_config, FinishConfigV2, "finish_config").seed,)
|
|
|
|
|
|
class FinishScaleV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"finish_config": (FINISH_CONFIG_TYPE, {"forceInput": True}),
|
|
"scale": (
|
|
["initial_upscale", "final_upscale"],
|
|
{"default": "final_upscale"},
|
|
),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("FLOAT",)
|
|
RETURN_NAMES = ("scale",)
|
|
FUNCTION = "read"
|
|
CATEGORY = "PortraitUtils/Config V2/Readers"
|
|
|
|
def read(self, finish_config, scale):
|
|
config = _expect(finish_config, FinishConfigV2, "finish_config")
|
|
choice(scale, "scale", ("initial_upscale", "final_upscale"))
|
|
return (float(getattr(config, scale)),)
|
|
|
|
|
|
class SourceSaveInfoV2:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"source_info": (SOURCE_INFO_TYPE, {"forceInput": True})
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING")
|
|
RETURN_NAMES = ("filename", "output_dir")
|
|
FUNCTION = "read"
|
|
CATEGORY = "PortraitUtils/Config V2/Readers"
|
|
|
|
def read(self, source_info):
|
|
info = _expect(source_info, SourceInfoV2, "source_info")
|
|
return info.filename_no_ext, info.output_dir
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"PortraitCropConfigV2": CropConfigNodeV2,
|
|
"PortraitEditConfigV2": EditConfigNodeV2,
|
|
"PortraitOutpaintConfigV2": OutpaintConfigNodeV2,
|
|
"PortraitFinishConfigV2": FinishConfigNodeV2,
|
|
"OutpaintPaddingComputeV2": OutpaintPaddingComputeV2,
|
|
"AutoAdjustV2": AutoAdjustV2,
|
|
"PortraitCropPromptV2": CropPromptV2,
|
|
"PortraitEditPromptV2": EditPromptV2,
|
|
"PortraitOutpaintPromptV2": OutpaintPromptV2,
|
|
"PortraitFinishSeedV2": FinishSeedV2,
|
|
"PortraitFinishScaleV2": FinishScaleV2,
|
|
"PortraitSourceSaveInfoV2": SourceSaveInfoV2,
|
|
}
|
|
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"PortraitCropConfigV2": "Input & Crop Config",
|
|
"PortraitEditConfigV2": "Edit & Composite Config",
|
|
"PortraitOutpaintConfigV2": "Outpaint Config",
|
|
"PortraitFinishConfigV2": "Finish & Run Config",
|
|
"OutpaintPaddingComputeV2": "Outpaint Padding Compute",
|
|
"AutoAdjustV2": "Auto Adjust (Photoshop-style)",
|
|
"PortraitCropPromptV2": "Crop Prompt from Config",
|
|
"PortraitEditPromptV2": "Edit Prompt from Config",
|
|
"PortraitOutpaintPromptV2": "Outpaint Prompt from Config",
|
|
"PortraitFinishSeedV2": "Seed from Config",
|
|
"PortraitFinishScaleV2": "Scale from Config",
|
|
"PortraitSourceSaveInfoV2": "Save Info from Source",
|
|
}
|