250 lines
5.9 KiB
Python
250 lines
5.9 KiB
Python
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 |