None of the nodes showed description text in ComfyUI because ComfyUI reads obj_class.DESCRIPTION when it builds node_info - it does not read the class docstring, which is what these nodes had. Every registered node now carries a one-line DESCRIPTION, kept to 29-67 characters so it fits the limited space in ComfyUI Manager and the node search results. Image Transform Pro's four-sentence description is shortened to match. Descriptions are derived from each node's existing docstring, or from the README for the five classes that had no docstring (ImageBlendPro, ColorMatchBlend and the three Boolean switch nodes). README also updated for the recent changes: the New Nodes table now lists the real "Rotate & Scale" category, the XY placement direction is documented on both nodes (positive X right, positive Y up), and the v2.2.0 changelog records the placement convention, the node outputs and the MP_ImageTransform internal name.
148 lines
5.0 KiB
Python
148 lines
5.0 KiB
Python
import torch
|
|
import numpy as np
|
|
import cv2
|
|
|
|
class LevelsAdjust:
|
|
"""
|
|
Adjust image levels - black point, white point, gamma, and output levels.
|
|
"""
|
|
|
|
DESCRIPTION = "Black point, white point, gamma and output levels."
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
},
|
|
"optional": {
|
|
"black_point": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 0.5, "step": 0.01, "display": "slider"}),
|
|
"white_point": ("FLOAT", {"default": 1.0, "min": 0.5, "max": 1.0, "step": 0.01, "display": "slider"}),
|
|
"gamma": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 3.0, "step": 0.05, "display": "slider"}),
|
|
"output_black": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 0.5, "step": 0.01, "display": "slider"}),
|
|
"output_white": ("FLOAT", {"default": 1.0, "min": 0.5, "max": 1.0, "step": 0.01, "display": "slider"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "adjust_levels"
|
|
CATEGORY = "MachinePaintingNodes/Color"
|
|
|
|
def adjust_levels(self, image, black_point=0.0, white_point=1.0, gamma=1.0,
|
|
output_black=0.0, output_white=1.0):
|
|
img = image[0].cpu().numpy().astype(np.float32)
|
|
|
|
# Input levels
|
|
result = (img - black_point) / (white_point - black_point)
|
|
result = np.clip(result, 0, 1)
|
|
|
|
# Gamma
|
|
if gamma != 1.0:
|
|
result = np.power(result, 1.0 / gamma)
|
|
|
|
# Output levels
|
|
result = result * (output_white - output_black) + output_black
|
|
result = np.clip(result, 0, 1)
|
|
|
|
result_tensor = torch.from_numpy(result).unsqueeze(0)
|
|
return (result_tensor,)
|
|
|
|
|
|
class AutoLevels:
|
|
"""
|
|
Automatically adjust image levels based on histogram analysis.
|
|
"""
|
|
|
|
DESCRIPTION = "Automatic levels correction from histogram analysis."
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
},
|
|
"optional": {
|
|
"clip_percent": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 5.0, "step": 0.1, "display": "slider"}),
|
|
"strength": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05, "display": "slider"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "auto_levels"
|
|
CATEGORY = "MachinePaintingNodes/Color"
|
|
|
|
def auto_levels(self, image, clip_percent=0.1, strength=1.0):
|
|
img = image[0].cpu().numpy().astype(np.float32)
|
|
original = img.copy()
|
|
|
|
result = np.zeros_like(img)
|
|
|
|
for c in range(3):
|
|
channel = img[:, :, c]
|
|
|
|
# Find clip points
|
|
hist, bins = np.histogram(channel.flatten(), bins=256, range=(0, 1))
|
|
total_pixels = hist.sum()
|
|
clip_pixels = total_pixels * clip_percent / 100.0
|
|
|
|
# Find black point
|
|
cumsum = np.cumsum(hist)
|
|
black_idx = np.searchsorted(cumsum, clip_pixels)
|
|
black_point = black_idx / 255.0
|
|
|
|
# Find white point
|
|
cumsum_rev = np.cumsum(hist[::-1])
|
|
white_idx = 255 - np.searchsorted(cumsum_rev, clip_pixels)
|
|
white_point = white_idx / 255.0
|
|
|
|
# Apply levels
|
|
if white_point > black_point:
|
|
adjusted = (channel - black_point) / (white_point - black_point)
|
|
result[:, :, c] = np.clip(adjusted, 0, 1)
|
|
else:
|
|
result[:, :, c] = channel
|
|
|
|
# Blend with original based on strength
|
|
result = original * (1 - strength) + result * strength
|
|
result = np.clip(result, 0, 1)
|
|
|
|
result_tensor = torch.from_numpy(result).unsqueeze(0)
|
|
return (result_tensor,)
|
|
|
|
|
|
class BrightnessContrastAdjust:
|
|
"""
|
|
Simple brightness and contrast adjustment.
|
|
"""
|
|
|
|
DESCRIPTION = "Simple brightness and contrast sliders."
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"image": ("IMAGE",),
|
|
"brightness": ("FLOAT", {"default": 0.0, "min": -100.0, "max": 100.0, "step": 1.0, "display": "slider"}),
|
|
"contrast": ("FLOAT", {"default": 0.0, "min": -100.0, "max": 100.0, "step": 1.0, "display": "slider"}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("IMAGE",)
|
|
FUNCTION = "adjust"
|
|
CATEGORY = "MachinePaintingNodes/Color"
|
|
|
|
def adjust(self, image, brightness, contrast):
|
|
img = image[0].cpu().numpy().astype(np.float32)
|
|
|
|
# Brightness (-100 to 100 -> -0.5 to 0.5)
|
|
result = img + (brightness / 200.0)
|
|
|
|
# Contrast (-100 to 100 -> 0.5 to 1.5 factor)
|
|
factor = 1.0 + (contrast / 100.0)
|
|
result = (result - 0.5) * factor + 0.5
|
|
|
|
result = np.clip(result, 0, 1)
|
|
result_tensor = torch.from_numpy(result).unsqueeze(0)
|
|
|
|
return (result_tensor,)
|