Files
2025-03-18 14:19:20 +08:00

397 lines
14 KiB
Python

import torch
import math
from torch import Tensor
import torchvision.transforms as t
from PIL import Image as ImageF
from PIL.Image import Image as ImageB
from typing import Literal, Any
def tensor_to_image(self):
return t.ToPILImage()(self.permute(2, 0, 1))
def image_to_tensor(self):
return t.ToTensor()(self).permute(1, 2, 0)
def get_sampler_by_name(method) -> Literal[0, 1, 2, 3, 4, 5]:
if method == "lanczos":
return ImageF.LANCZOS
elif method == "bicubic":
return ImageF.BICUBIC
elif method == "hamming":
return ImageF.HAMMING
elif method == "bilinear":
return ImageF.BILINEAR
elif method == "box":
return ImageF.BOX
elif method == "nearest":
return ImageF.NEAREST
else:
raise ValueError("Sampler not found.")
def create_rgba_image(width: int, height: int, color=(0, 0, 0, 0)) -> ImageB:
return ImageF.new("RGBA", (width, height), color)
Tensor.tensor_to_image = tensor_to_image
ImageB.image_to_tensor = image_to_tensor
# 调用的ComfyUI_ALLOR 的代码
# 创建图片
def ImageContainer(width, height, red, green, blue, alpha):
return create_rgba_image(width, height, (red, green, blue, int(alpha * 255))).image_to_tensor().unsqueeze(0)
#缩放大小
def ImageTransformResizeAbsolute(images, width, height, method):
def resize_tensor(tensor):
return tensor.tensor_to_image().resize((width, height), get_sampler_by_name(method)).image_to_tensor()
return torch.stack([resize_tensor(images[i]) for i in range(len(images))])
#缩放比例
def ImageTransformResizeRelative(images, scale_width, scale_height, method):
height, width = images[0, :, :, 0].shape
width = int(width * scale_width)
height = int(height * scale_height)
return ImageTransformResizeAbsolute(images, width, height, method)
#缩放偏移位置
def ImageTransformResizeClip(images, max_width, max_height, min_width, min_height, method):
height, width = images[0, :, :, 0].shape
if min_width >= max_width or min_height >= max_height:
return (images,)
scale_min = max(min_width / width, min_height / height)
scale_max = min(max_width / width, max_height / height)
scale = max(scale_min, scale_max)
return ImageTransformResizeRelative(images, scale, scale, method)
#两张图片绝对位置合成
def ImageCompositeAbsolute(
images_a,
images_b,
images_a_x,
images_a_y,
images_b_x,
images_b_y,
container_width,
container_height,
background,
method
):
def clip(value: float):
return value if value >= 0 else 0
# noinspection PyUnresolvedReferences
def composite(image_a, image_b):
img_a_height, img_a_width, img_a_dim = image_a.shape
img_b_height, img_b_width, img_b_dim = image_b.shape
if img_a_dim == 3:
image_a = torch.stack([
image_a[:, :, 0],
image_a[:, :, 1],
image_a[:, :, 2],
torch.ones((img_a_height, img_a_width))
], dim=2)
if img_b_dim == 3:
image_b = torch.stack([
image_b[:, :, 0],
image_b[:, :, 1],
image_b[:, :, 2],
torch.ones((img_b_height, img_b_width))
], dim=2)
container_x = max(img_a_width, img_b_width) if container_width == 0 else container_width
container_y = max(img_a_height, img_b_height) if container_height == 0 else container_height
container_a = torch.zeros((container_y, container_x, 4))
container_b = torch.zeros((container_y, container_x, 4))
img_a_height_c, img_a_width_c = [
clip((images_a_y + img_a_height) - container_y),
clip((images_a_x + img_a_width) - container_x)
]
img_b_height_c, img_b_width_c = [
clip((images_b_y + img_b_height) - container_y),
clip((images_b_x + img_b_width) - container_x)
]
if img_a_height_c <= img_a_height and img_a_width_c <= img_a_width:
container_a[
images_a_y:img_a_height + images_a_y - img_a_height_c,
images_a_x:img_a_width + images_a_x - img_a_width_c
] = image_a[
:img_a_height - img_a_height_c,
:img_a_width - img_a_width_c
]
if img_b_height_c <= img_b_height and img_b_width_c <= img_b_width:
container_b[
images_b_y:img_b_height + images_b_y - img_b_height_c,
images_b_x:img_b_width + images_b_x - img_b_width_c
] = image_b[
:img_b_height - img_b_height_c,
:img_b_width - img_b_width_c
]
if background == "images_a":
return ImageF.alpha_composite(
container_a.tensor_to_image(),
container_b.tensor_to_image()
).image_to_tensor()
else:
return ImageF.alpha_composite(
container_b.tensor_to_image(),
container_a.tensor_to_image()
).image_to_tensor()
if method == "pair":
if len(images_a) != len(images_b):
raise ValueError("Size of image_a and image_b not equals for pair batch type.")
return torch.stack([composite(images_a[i], images_b[i]) for i in range(len(images_a))])
elif method == "matrix":
return torch.stack([
composite(images_a[i], images_b[j]) for i in range(len(images_a)) for j in range(len(images_b))
])
return None
#两张图片相对位置合成
def ImageCompositeRelative(
images_a,
images_b,
images_a_x,
images_a_y,
images_b_x,
images_b_y,
background,
container_size_type,
method
):
def offset_by_percent(container_size: int, image_size: int, percent: float):
return int((container_size - image_size) * percent)
img_a_height, img_a_width = images_a[0, :, :, 0].shape
img_b_height, img_b_width = images_b[0, :, :, 0].shape
if container_size_type == "max":
container_width = max(img_a_width, img_b_width)
container_height = max(img_a_height, img_b_height)
elif container_size_type == "sum":
container_width = img_a_width + img_b_width
container_height = img_a_height + img_b_height
elif container_size_type == "sum_width":
container_width = img_a_width + img_b_width
container_height = max(img_a_height, img_b_height)
elif container_size_type == "sum_height":
container_width = max(img_a_width, img_b_width)
container_height = img_a_height + img_a_height
else:
raise ValueError()
return ImageCompositeAbsolute(
images_a,
images_b,
offset_by_percent(container_width, img_a_width, images_a_x),
offset_by_percent(container_height, img_a_height, images_a_y),
offset_by_percent(container_width, img_b_width, images_b_x),
offset_by_percent(container_height, img_b_height, images_b_y),
container_width,
container_height,
background,
method
)
def ImageTransformRotate(images, angle, expand, SSAA, method):
height, width = images[0, :, :, 0].shape
def rotate_tensor(tensor):
if method == "lanczos":
resize_sampler = ImageF.LANCZOS
rotate_sampler = ImageF.BICUBIC
elif method == "bicubic":
resize_sampler = ImageF.BICUBIC
rotate_sampler = ImageF.BICUBIC
elif method == "hamming":
resize_sampler = ImageF.HAMMING
rotate_sampler = ImageF.BILINEAR
elif method == "bilinear":
resize_sampler = ImageF.BILINEAR
rotate_sampler = ImageF.BILINEAR
elif method == "box":
resize_sampler = ImageF.BOX
rotate_sampler = ImageF.NEAREST
elif method == "nearest":
resize_sampler = ImageF.NEAREST
rotate_sampler = ImageF.NEAREST
else:
raise ValueError()
if SSAA > 1:
img = tensor.tensor_to_image()
img_us_scaled = img.resize((width * SSAA, height * SSAA), resize_sampler)
img_rotated = img_us_scaled.rotate(angle, rotate_sampler, expand == "true", fillcolor=(0, 0, 0, 0))
img_down_scaled = img_rotated.resize((img_rotated.width // SSAA, img_rotated.height // SSAA), resize_sampler)
result = img_down_scaled.image_to_tensor()
else:
img = tensor.tensor_to_image()
img_rotated = img.rotate(angle, rotate_sampler, expand == "true", fillcolor=(0, 0, 0, 0))
result = img_rotated.image_to_tensor()
return result
if angle == 0.0 or angle == 360.0:
return (images,)
else:
return torch.stack([
rotate_tensor(images[i]) for i in range(len(images))
])
def ImageTransformTranspose(images, method):
def transpose_tensor(tensor):
if method == "flip_horizontally":
transpose = ImageF.FLIP_LEFT_RIGHT
elif method == "flip_vertically":
transpose = ImageF.FLIP_TOP_BOTTOM
elif method == "rotate_90":
transpose = ImageF.ROTATE_90
elif method == "rotate_180":
transpose = ImageF.ROTATE_180
elif method == "rotate_270":
transpose = ImageF.ROTATE_270
elif method == "transpose":
transpose = ImageF.TRANSPOSE
elif method == "transverse":
transpose = ImageF.TRANSVERSE
else:
raise ValueError()
return tensor.tensor_to_image().transpose(transpose).image_to_tensor()
return torch.stack([transpose_tensor(images[i]) for i in range(len(images))])
def MUT8(width):
# 向上取整到最接近的8的倍数
adjusted_width = math.ceil(width / 8) * 8
return int(adjusted_width)
def resize_mask(mask, shape):
'''
输入mask和图片的shape,输出resize后的mask
shape: [H, W]
'''
return torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])),
size=(shape[0], shape[1]), mode="bilinear").squeeze(1)
def join_image_with_alpha(image: torch.Tensor, alpha: torch.Tensor):
batch_size = min(len(image), len(alpha))
out_images = []
alpha = 1.0 - resize_mask(alpha, image.shape[1:])
for i in range(batch_size):
out_images.append(torch.cat((image[i][:,:,:3], alpha[i].unsqueeze(2)), dim=2))
return torch.stack(out_images)
def add_alpha_channel(image):
# Check the shape of the input image
if image.dim() < 3:
raise ValueError("Input image must have at least 3 dimensions.")
# Get the shape of the image
shape = image.shape
# Create an alpha channel with the same shape as the last dimension except for the last dimension
alpha_channel_shape = list(shape)
alpha_channel_shape[-1] = 1
# Create the alpha channel filled with ones
alpha_channel = torch.ones(alpha_channel_shape, dtype=image.dtype, device=image.device)
# Concatenate the alpha channel to the image
image_with_alpha = torch.cat((image, alpha_channel), dim=-1)
return image_with_alpha
# 图像遮罩复合
def image_mask_composite(destination, source, x, y, mask=None, multiplier=8, resize_source=False):
destination = destination.clone().movedim(-1, 1)
source = source.clone().movedim(-1, 1)
source = source.to(destination.device)
if resize_source:
source = torch.nn.functional.interpolate(source, size=(destination.shape[2], destination.shape[3]), mode="bilinear")
x = max(-source.shape[3] * multiplier, min(x, destination.shape[3] * multiplier))
y = max(-source.shape[2] * multiplier, min(y, destination.shape[2] * multiplier))
left, top = (x // multiplier, y // multiplier)
right, bottom = (left + source.shape[3], top + source.shape[2],)
if mask is None:
mask = torch.ones_like(source)
else:
mask = mask.to(destination.device, copy=True)
mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(source.shape[2], source.shape[3]), mode="bilinear")
visible_width, visible_height = (destination.shape[3] - left + min(0, x), destination.shape[2] - top + min(0, y),)
mask = mask[:, :, :visible_height, :visible_width]
inverse_mask = torch.ones_like(mask) - mask
source_portion = mask * source[:, :, :visible_height, :visible_width]
destination_portion = inverse_mask * destination[:, :, top:bottom, left:right]
destination[:, :, top:bottom, left:right] = source_portion + destination_portion
destination = destination.movedim(1, -1)
return destination
def crop_transparent_area(image_tensor, mode='full'):
"""
裁切掉图像中的透明部分。
参数:
image_tensor (torch.Tensor): 输入图像张量,形状为 [B, H, W, C],类型为 float32。
mode (str): 裁剪模式,支持 'full'(全裁剪)、'horizontal'(水平裁剪)、'vertical'(垂直裁剪),默认为 'full'。
返回:
torch.Tensor: 裁剪后的图像张量,形状为 [B, H', W', C],类型为 float32。
"""
alpha_channel = image_tensor[0, :, :, -1]
non_zero_indices = torch.nonzero(alpha_channel > 0)
if non_zero_indices.numel() == 0:
return image_tensor # 如果没有非透明部分,返回原图
min_y, min_x = non_zero_indices.min(dim=0)[0]
max_y, max_x = non_zero_indices.max(dim=0)[0]
if mode == 'full':
# 全裁剪
cropped_image = image_tensor[:, min_y:max_y+1, min_x:max_x+1, :]
elif mode == 'horizontal':
# 按照水平裁剪
cropped_image = image_tensor[:, :, min_x:max_x+1, :]
elif mode == 'vertical':
# 按照垂直裁剪
cropped_image = image_tensor[:, min_y:max_y+1, :, :]
else:
raise ValueError("裁剪模式仅支持 'full'、'horizontal' 和 'vertical'。")
return cropped_image