Adds crop_x/crop_y/crop_width/crop_height inputs, applied before the label bar is composited, plus an Edit Crop popup for dragging the region visually on the original image.
124 lines
5.2 KiB
Python
124 lines
5.2 KiB
Python
import numpy as np
|
|
|
|
try:
|
|
from PIL import Image, ImageOps
|
|
except ImportError: # pragma: no cover - PIL ships with ComfyUI
|
|
Image = None
|
|
ImageOps = None
|
|
|
|
try:
|
|
from ._label_ops import apply_crop, compose_labeled_image
|
|
except ImportError: # importable for tests without package context
|
|
from _label_ops import apply_crop, compose_labeled_image
|
|
|
|
CATEGORY = "MoonPack/image"
|
|
|
|
|
|
class LabeledLoadImage:
|
|
DESCRIPTION = (
|
|
"Loads an image and bakes an editable caption bar into it (e.g. 'Front View', "
|
|
"'Character A'), so a reference stays identifiable once it's combined into a "
|
|
"bigger Reference Concat/Grid layout. The bar is added as extra space at the "
|
|
"top or bottom - only the crop region (if set) affects the photo's extent, "
|
|
"the label bar itself never crops or resizes it further. A bold arrow in "
|
|
"the bar points back at the photo; text and arrow can each be turned off "
|
|
"independently. An optional border (same color as the bar) frames the whole "
|
|
"result. Use the node's Edit Crop button to select a region visually, and "
|
|
"the Preview button for an instant, exact look at the result before queuing."
|
|
)
|
|
SEARCH_ALIASES = ["load image", "label", "caption", "annotate", "reference", "text overlay"]
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
import os
|
|
|
|
import folder_paths
|
|
|
|
input_dir = folder_paths.get_input_directory()
|
|
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
|
|
files = folder_paths.filter_files_content_types(files, ["image"])
|
|
return {
|
|
"required": {
|
|
"image": (sorted(files), {"image_upload": True}),
|
|
"text": ("STRING", {
|
|
"default": "", "multiline": False,
|
|
"tooltip": "Caption drawn in the label bar. Auto-shrinks to fit, then truncates with an ellipsis.",
|
|
}),
|
|
"text_position": (["top", "bottom"], {
|
|
"default": "top",
|
|
"tooltip": "Which edge of the image the label bar is added to.",
|
|
}),
|
|
"show_label": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Draw the caption text in the bar.",
|
|
}),
|
|
"show_arrow": ("BOOLEAN", {
|
|
"default": True,
|
|
"tooltip": "Draw a bold arrow in the bar, pointing back at the photo.",
|
|
}),
|
|
"background_color": ("STRING", {
|
|
"default": "#000000",
|
|
"tooltip": "Label bar fill color and border color, as a hex code.",
|
|
}),
|
|
"text_color": ("STRING", {
|
|
"default": "#ffffff",
|
|
"tooltip": "Caption text and arrow color, as a hex code.",
|
|
}),
|
|
"border_width": ("INT", {
|
|
"default": 0, "min": 0, "max": 512, "step": 1,
|
|
"tooltip": "Inset border thickness in pixels, drawn in background_color around the whole result. 0 = no border.",
|
|
}),
|
|
"crop_x": ("INT", {
|
|
"default": 0, "min": 0, "max": 999999, "step": 1,
|
|
"tooltip": "Left edge of the crop region, in source-image pixels. Use the Edit Crop button to set this visually.",
|
|
}),
|
|
"crop_y": ("INT", {
|
|
"default": 0, "min": 0, "max": 999999, "step": 1,
|
|
"tooltip": "Top edge of the crop region, in source-image pixels. Use the Edit Crop button to set this visually.",
|
|
}),
|
|
"crop_width": ("INT", {
|
|
"default": 0, "min": 0, "max": 999999, "step": 1,
|
|
"tooltip": "Crop region width in pixels. 0 = everything to the right of crop_x (no crop, if crop_x/crop_y/crop_height are also 0).",
|
|
}),
|
|
"crop_height": ("INT", {
|
|
"default": 0, "min": 0, "max": 999999, "step": 1,
|
|
"tooltip": "Crop region height in pixels. 0 = everything below crop_y (no crop, if crop_x/crop_y/crop_width are also 0).",
|
|
}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
RETURN_NAMES = ("image",)
|
|
FUNCTION = "load_image"
|
|
CATEGORY = CATEGORY
|
|
|
|
def load_image(self, image, text, text_position, show_label, show_arrow,
|
|
background_color, text_color, border_width,
|
|
crop_x, crop_y, crop_width, crop_height):
|
|
import folder_paths
|
|
|
|
image_path = folder_paths.get_annotated_filepath(image)
|
|
img = Image.open(image_path)
|
|
img = ImageOps.exif_transpose(img).convert("RGB")
|
|
img = apply_crop(img, crop_x, crop_y, crop_width, crop_height)
|
|
|
|
composed = compose_labeled_image(
|
|
img, text, text_position, show_label, show_arrow,
|
|
background_color, text_color, border_width,
|
|
)
|
|
|
|
arr = np.array(composed).astype(np.float32) / 255.0
|
|
tensor = arr[None, ...]
|
|
|
|
import torch
|
|
return (torch.from_numpy(tensor),)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"MoonPack_LabeledLoadImage": LabeledLoadImage,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"MoonPack_LabeledLoadImage": "Load Image (Labeled)",
|
|
}
|