from typing import List, Union import numpy as np import torch, math from PIL import Image import torch import torchvision.transforms.functional as TF from typing import cast def tensor2pil(image: torch.Tensor) -> List[Image.Image]: batch_count = image.size(0) if len(image.shape) > 3 else 1 if batch_count > 1: out = [] for i in range(batch_count): out.extend(tensor2pil(image[i])) return out return [ Image.fromarray( np.clip(255.0 * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8) ) ] def pil2tensor(image: Union[Image.Image, List[Image.Image]]) -> torch.Tensor: if isinstance(image, list): return torch.cat([pil2tensor(img) for img in image], dim=0) return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0) def image_2dtransform( image, x, y, zoom, angle, shear=0, border_handling="edge", ): x = int(x) y = int(y) angle = int(angle) if image.size(0) == 0: return (torch.zeros(0),) frames_count, frame_height, frame_width, frame_channel_count = image.size() new_height, new_width = int(frame_height * zoom), int(frame_width * zoom) # - Calculate diagonal of the original image diagonal = math.sqrt(frame_width**2 + frame_height**2) max_padding = math.ceil(diagonal * zoom - min(frame_width, frame_height)) # Calculate padding for zoom pw = int(frame_width - new_width) ph = int(frame_height - new_height) pw += abs(max_padding) ph += abs(max_padding) padding = [max(0, pw + x), max(0, ph + y), max(0, pw - x), max(0, ph - y)] img = tensor2pil(image)[0] img = TF.pad( img, # transformed_frame, padding=padding, padding_mode=border_handling, ) img = cast( Image.Image, TF.affine(img, angle=angle, scale=zoom, translate=[x, y], shear=shear), ) left = abs(padding[0]) upper = abs(padding[1]) right = img.width - abs(padding[2]) bottom = img.height - abs(padding[3]) img = img.crop((left, upper, right, bottom)) return pil2tensor(img) # --------------------------- Easing functions def easeInBack(t): s = 1.70158 return t * t * ((s + 1) * t - s) def easeOutBack(t): s = 1.70158 return ((t - 1) * t * ((s + 1) * t + s)) + 1 def easeInOutBack(t): s = 1.70158 * 1.525 if t < 0.5: return (t * t * (t * (s + 1) - s)) * 2 return ((t - 2) * t * ((s + 1) * t + s) + 2) * 2 # Elastic easing functions def easeInElastic(t): if t == 0: return 0 if t == 1: return 1 p = 0.3 s = p / 4 return -(math.pow(2, 10 * (t - 1)) * math.sin((t - 1 - s) * (2 * math.pi) / p)) def easeOutElastic(t): if t == 0: return 0 if t == 1: return 1 p = 0.3 s = p / 4 return math.pow(2, -10 * t) * math.sin((t - s) * (2 * math.pi) / p) + 1 def easeInOutElastic(t): if t == 0: return 0 if t == 1: return 1 p = 0.3 * 1.5 s = p / 4 t = t * 2 if t < 1: return -0.5 * ( math.pow(2, 10 * (t - 1)) * math.sin((t - 1 - s) * (2 * math.pi) / p) ) return ( 0.5 * math.pow(2, -10 * (t - 1)) * math.sin((t - 1 - s) * (2 * math.pi) / p) + 1 ) # Bounce easing functions def easeInBounce(t): return 1 - easeOutBounce(1 - t) def easeOutBounce(t): if t < (1 / 2.75): return 7.5625 * t * t elif t < (2 / 2.75): t -= 1.5 / 2.75 return 7.5625 * t * t + 0.75 elif t < (2.5 / 2.75): t -= 2.25 / 2.75 return 7.5625 * t * t + 0.9375 else: t -= 2.625 / 2.75 return 7.5625 * t * t + 0.984375 def easeInOutBounce(t): if t < 0.5: return easeInBounce(t * 2) * 0.5 return easeOutBounce(t * 2 - 1) * 0.5 + 0.5 # Quart easing functions def easeInQuart(t): return t * t * t * t def easeOutQuart(t): t -= 1 return -(t**2 * t * t - 1) def easeInOutQuart(t): t *= 2 if t < 1: return 0.5 * t * t * t * t t -= 2 return -0.5 * (t**2 * t * t - 2) # Cubic easing functions def easeInCubic(t): return t * t * t def easeOutCubic(t): t -= 1 return t**2 * t + 1 def easeInOutCubic(t): t *= 2 if t < 1: return 0.5 * t * t * t t -= 2 return 0.5 * (t**2 * t + 2) # Circ easing functions def easeInCirc(t): return -(math.sqrt(1 - t * t) - 1) def easeOutCirc(t): t -= 1 return math.sqrt(1 - t**2) def easeInOutCirc(t): t *= 2 if t < 1: return -0.5 * (math.sqrt(1 - t**2) - 1) t -= 2 return 0.5 * (math.sqrt(1 - t**2) + 1) # Sine easing functions def easeInSine(t): return -math.cos(t * (math.pi / 2)) + 1 def easeOutSine(t): return math.sin(t * (math.pi / 2)) def easeInOutSine(t): return -0.5 * (math.cos(math.pi * t) - 1) def easeLinear(t): return t easing_functions = { "Linear": easeLinear, "Sine In": easeInSine, "Sine Out": easeOutSine, "Sine In/Out": easeInOutSine, "Quart In": easeInQuart, "Quart Out": easeOutQuart, "Quart In/Out": easeInOutQuart, "Cubic In": easeInCubic, "Cubic Out": easeOutCubic, "Cubic In/Out": easeInOutCubic, "Circ In": easeInCirc, "Circ Out": easeOutCirc, "Circ In/Out": easeInOutCirc, "Back In": easeInBack, "Back Out": easeOutBack, "Back In/Out": easeInOutBack, "Elastic In": easeInElastic, "Elastic Out": easeOutElastic, "Elastic In/Out": easeInOutElastic, "Bounce In": easeInBounce, "Bounce Out": easeOutBounce, "Bounce In/Out": easeInOutBounce, } def apply_easing(value, easing_type): function_ease = easing_functions.get(easing_type) if function_ease: return function_ease(value) raise ValueError(f"Unknown easing type: {easing_type}") # --------------------------- Easing functions