Spaces:
Runtime error
Runtime error
import math | |
import cv2 | |
from PIL import Image | |
from comfy.utils import common_upscale | |
from .utils.image_convert import mask2tensor, np2tensor, tensor2mask, tensor2np | |
from .utils.mask_utils import solid_mask | |
from .utils.utils import make_even | |
_CATEGORY = 'fnodes/image scale' | |
UPSCALE_METHODS = ['lanczos', 'nearest-exact', 'bilinear', 'area', 'bicubic'] | |
class GetImageSize: | |
def INPUT_TYPES(cls): | |
return { | |
'required': { | |
'image': ('IMAGE',), | |
} | |
} | |
RETURN_TYPES = ( | |
'INT', | |
'INT', | |
'INT', | |
) | |
RETURN_NAMES = ( | |
'width', | |
'height', | |
'count', | |
) | |
FUNCTION = 'execute' | |
CATEGORY = _CATEGORY | |
def execute(self, image): | |
return { | |
'ui': { | |
'width': (image.shape[2],), | |
'height': (image.shape[1],), | |
'count': (image.shape[0],), | |
}, | |
'result': ( | |
image.shape[2], | |
image.shape[1], | |
image.shape[0], | |
), | |
} | |
class BaseImageScaler: | |
def INPUT_TYPES(cls): | |
return { | |
'required': { | |
'image': ('IMAGE',), | |
'upscale_method': (UPSCALE_METHODS,), | |
}, | |
'optional': { | |
'mask': ('MASK',), | |
}, | |
} | |
RETURN_TYPES = ('IMAGE', 'MASK', 'INT', 'INT', 'INT') | |
RETURN_NAMES = ('image', 'mask', 'width', 'height', 'min_dimension') | |
CATEGORY = _CATEGORY | |
def scale_image(self, image, width, height, upscale_method, mask=None): | |
image_tensor = image.movedim(-1, 1) | |
scaled_image = common_upscale(image_tensor, width, height, upscale_method, 'disabled') | |
scaled_image = scaled_image.movedim(1, -1) | |
result_mask = solid_mask(width, height) | |
if mask is not None: | |
mask_image = mask2tensor(mask) | |
mask_image = mask_image.movedim(-1, 1) | |
mask_image = common_upscale(mask_image, width, height, upscale_method, 'disabled') | |
mask_image = mask_image.movedim(1, -1) | |
result_mask = tensor2mask(mask_image) | |
return scaled_image, result_mask | |
def prepare_result(self, scaled_image, result_mask, width, height): | |
return { | |
'ui': { | |
'width': (width,), | |
'height': (height,), | |
}, | |
'result': ( | |
scaled_image, | |
result_mask, | |
width, | |
height, | |
min(width, height), | |
), | |
} | |
class ImageScalerForSDModels(BaseImageScaler): | |
def INPUT_TYPES(cls): | |
base_inputs = super().INPUT_TYPES() | |
base_inputs['required']['sd_model_type'] = (['sdxl', 'sd15', 'sd15+', 'sdxl+'],) | |
return base_inputs | |
FUNCTION = 'execute' | |
DESCRIPTION = """ | |
根据SD模型类型缩放图片到指定像素数,sd15为512x512,sd15+为512x768,sdxl为1024x1024,sdxl+为1024x1280 | |
""" | |
def execute(self, image, upscale_method, sd_model_type, mask=None): | |
sd_dimensions = {'sd15': (512, 512), 'sd15+': (512, 768), 'sdxl': (1024, 1024), 'sdxl+': (1024, 1280)} | |
target_width, target_height = sd_dimensions.get(sd_model_type, (1024, 1024)) | |
total_pixels = target_width * target_height | |
scale_by = math.sqrt(total_pixels / (image.shape[2] * image.shape[1])) | |
width = round(image.shape[2] * scale_by) | |
height = round(image.shape[1] * scale_by) | |
scaled_image, result_mask = self.scale_image(image, width, height, upscale_method, mask) | |
return self.prepare_result(scaled_image, result_mask, width, height) | |
class ImageScaleBySpecifiedSide(BaseImageScaler): | |
def INPUT_TYPES(cls): | |
base_inputs = super().INPUT_TYPES() | |
base_inputs['required'].update( | |
{ | |
'size': ('INT', {'default': 512, 'min': 0, 'step': 1, 'max': 99999}), | |
'shorter': ('BOOLEAN', {'default': False}), | |
} | |
) | |
return base_inputs | |
FUNCTION = 'execute' | |
DESCRIPTION = """ | |
根据指定边长缩放图片,shorter为True时参照短边,否则参照长边 | |
""" | |
def execute(self, image, size, upscale_method, shorter, mask=None): | |
if shorter: | |
reference_side_length = min(image.shape[2], image.shape[1]) | |
else: | |
reference_side_length = max(image.shape[2], image.shape[1]) | |
scale_by = reference_side_length / size | |
width = make_even(round(image.shape[2] / scale_by)) | |
height = make_even(round(image.shape[1] / scale_by)) | |
scaled_image, result_mask = self.scale_image(image, width, height, upscale_method, mask) | |
return self.prepare_result(scaled_image, result_mask, width, height) | |
class ComputeImageScaleRatio: | |
def INPUT_TYPES(cls): | |
return { | |
'required': { | |
'image': ('IMAGE',), | |
'target_max_size': ( | |
'INT', | |
{'default': 1920, 'min': 0, 'step': 1, 'max': 99999}, | |
), | |
}, | |
} | |
RETURN_TYPES = ( | |
'FLOAT', | |
'INT', | |
'INT', | |
) | |
RETURN_NAMES = ( | |
'rescale_ratio', | |
'width', | |
'height', | |
) | |
FUNCTION = 'execute' | |
CATEGORY = _CATEGORY | |
DESCRIPTION = '根据引用图片的大小和目标最大尺寸,返回缩放比例和缩放后的宽高' | |
def execute(self, image, target_max_size): | |
samples = image.movedim(-1, 1) | |
width, height = samples.shape[3], samples.shape[2] | |
rescale_ratio = target_max_size / max(width, height) | |
new_width = make_even(round(width * rescale_ratio)) | |
new_height = make_even(round(height * rescale_ratio)) | |
return { | |
'ui': { | |
'rescale_ratio': (rescale_ratio,), | |
'width': (new_width,), | |
'height': (new_height,), | |
}, | |
'result': ( | |
rescale_ratio, | |
new_width, | |
new_height, | |
), | |
} | |
class ImageRotate: | |
def INPUT_TYPES(cls): | |
return { | |
'required': { | |
'image_from': ('IMAGE',), | |
'angle': ( | |
'FLOAT', | |
{'default': 0.1, 'min': -14096, 'max': 14096, 'step': 0.01}, | |
), | |
'expand': ('BOOLEAN', {'default': True}), | |
}, | |
} | |
RETURN_TYPES = ('IMAGE',) | |
RETURN_NAMES = ('rotated_image',) | |
FUNCTION = 'run' | |
CATEGORY = _CATEGORY | |
def run(self, image_from, angle, expand): | |
image_np = tensor2np(image_from[0]) | |
height, width = image_np.shape[:2] | |
center = (width / 2, height / 2) | |
if expand: | |
# 计算新图像的尺寸 | |
rot_mat = cv2.getRotationMatrix2D(center, angle, 1.0) | |
abs_cos = abs(rot_mat[0, 0]) | |
abs_sin = abs(rot_mat[0, 1]) | |
new_width = int(height * abs_sin + width * abs_cos) | |
new_height = int(height * abs_cos + width * abs_sin) | |
# 调整旋转矩阵 | |
rot_mat[0, 2] += (new_width / 2) - center[0] | |
rot_mat[1, 2] += (new_height / 2) - center[1] | |
# 执行旋转 | |
rotated_image = cv2.warpAffine(image_np, rot_mat, (new_width, new_height), flags=cv2.INTER_CUBIC) | |
else: | |
# 不扩展图像尺寸的旋转 | |
rot_mat = cv2.getRotationMatrix2D(center, angle, 1.0) | |
rotated_image = cv2.warpAffine(image_np, rot_mat, (width, height), flags=cv2.INTER_CUBIC) | |
# 转换回tensor格式 | |
rotated_tensor = np2tensor(rotated_image).unsqueeze(0) | |
return (rotated_tensor,) | |
class TrimImageBorders: | |
def INPUT_TYPES(cls): | |
return { | |
'required': { | |
'image': ('IMAGE',), | |
'threshold': ( | |
'INT', | |
{'default': 10, 'min': 0, 'max': 14096, 'step': 1}, | |
), | |
}, | |
} | |
RETURN_TYPES = ('IMAGE',) | |
FUNCTION = 'run' | |
CATEGORY = _CATEGORY | |
DESCRIPTION = '图片去黑边' | |
def run(self, image, threshold): | |
img = tensor2np(image[0]) | |
img = Image.fromarray(img) | |
gray_image = img.convert('L') | |
binary_image = gray_image.point(lambda x: 255 if x > threshold else 0) | |
bbox = binary_image.getbbox() | |
if bbox: | |
cropped_image = img.crop(bbox) | |
else: | |
cropped_image = img | |
cropped_image = np2tensor(cropped_image).unsqueeze(0) | |
return (cropped_image,) | |
IMAGE_SCALE_CLASS_MAPPINGS = { | |
'GetImageSize-': GetImageSize, | |
'ImageScalerForSDModels-': ImageScalerForSDModels, | |
'ImageScaleBySpecifiedSide-': ImageScaleBySpecifiedSide, | |
'ComputeImageScaleRatio-': ComputeImageScaleRatio, | |
'ImageRotate-': ImageRotate, | |
'TrimImageBorders-': TrimImageBorders, | |
} | |
IMAGE_SCALE_NAME_MAPPINGS = { | |
'GetImageSize-': 'Get Image Size', | |
'ImageScalerForSDModels-': 'Image Scaler for SD Models', | |
'ImageScaleBySpecifiedSide-': 'Image Scale By Specified Side', | |
'ComputeImageScaleRatio-': 'Compute Image Scale Ratio', | |
'ImageRotate-': 'Image Rotate', | |
'TrimImageBorders-': 'Trim Image Borders', | |
} | |