67 lines
1.8 KiB
Python
67 lines
1.8 KiB
Python
import math
|
|
import torch
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
|
|
def tensor_to_pil(img_tensor):
|
|
"""
|
|
Converts a ComfyUI IMAGE tensor [H, W, C] float32 in range 0..1
|
|
into a PIL image.
|
|
"""
|
|
img = img_tensor.cpu().numpy()
|
|
img = np.clip(img * 255.0, 0, 255).astype(np.uint8)
|
|
return Image.fromarray(img)
|
|
|
|
|
|
def pil_to_tensor(img_pil):
|
|
"""
|
|
Converts a PIL image into a ComfyUI IMAGE tensor [H, W, C] float32 0..1.
|
|
"""
|
|
arr = np.array(img_pil).astype(np.float32) / 255.0
|
|
return torch.from_numpy(arr)
|
|
|
|
|
|
def fit_inside(src_w, src_h, max_w, max_h):
|
|
"""
|
|
Resizes while preserving aspect ratio so the source fits entirely inside
|
|
the target box, without cropping.
|
|
"""
|
|
if src_w <= 0 or src_h <= 0:
|
|
return 1, 1
|
|
|
|
scale = min(max_w / src_w, max_h / src_h)
|
|
new_w = max(1, int(round(src_w * scale)))
|
|
new_h = max(1, int(round(src_h * scale)))
|
|
return new_w, new_h
|
|
|
|
|
|
def aligned_offset(container_size, content_size, align):
|
|
"""
|
|
Returns the position offset for start / center / end alignment.
|
|
"""
|
|
if align == "start":
|
|
return 0
|
|
elif align == "end":
|
|
return max(0, container_size - content_size)
|
|
return max(0, (container_size - content_size) // 2)
|
|
|
|
|
|
def paste_with_alpha(dst, src_rgba, xy):
|
|
"""
|
|
Pastes an image onto another, preserving alpha if present.
|
|
"""
|
|
if src_rgba.mode == "RGBA":
|
|
dst.paste(src_rgba, xy, src_rgba.split()[-1])
|
|
else:
|
|
dst.paste(src_rgba, xy)
|
|
|
|
|
|
def add_white_padding(img_rgba, pad_px=16):
|
|
new_w = img_rgba.width + pad_px * 2
|
|
new_h = img_rgba.height + pad_px * 2
|
|
|
|
canvas = Image.new("RGBA", (new_w, new_h), (255, 255, 255, 255))
|
|
canvas.paste(img_rgba, (pad_px, pad_px), img_rgba if img_rgba.mode == "RGBA" else None)
|
|
return canvas
|