File size: 4,939 Bytes
cf5e7f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b17c519
cf5e7f4
 
 
 
b17c519
cf5e7f4
 
 
 
 
 
b17c519
cf5e7f4
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# Modifying the code to ensure the mouth is open when the character starts talking

import random
import time
import cv2
import av
import numpy as np

def resize_and_crop(image, dim=(640, 480)):
    h, w = image.shape[:2]
    aspect_ratio = w / h

    target_width, target_height = dim
    target_aspect = target_width / target_height

    if aspect_ratio > target_aspect:
        # Original aspect is wider than target, fit by height
        new_height = target_height
        new_width = int(target_height * aspect_ratio)
    else:
        # Original aspect is taller than target, fit by width
        new_width = target_width
        new_height = int(target_width / aspect_ratio)

    # Resize the image with new dimensions
    resized_image = cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_AREA)

    # Crop to target dimensions
    x_offset = (new_width - target_width) // 2
    y_offset = (new_height - target_height) // 2

    cropped_image = resized_image[y_offset:y_offset + target_height, x_offset:x_offset + target_width]

    return cropped_image

def overlay_images(background, overlay, x, y):
    """
    Overlay an image with transparency over another image.
    """
    # Check if overlay dimensions fit within the background at the given (x, y) position
    if y + overlay.shape[0] > background.shape[0] or x + overlay.shape[1] > background.shape[1]:
        raise ValueError("Overlay dimensions exceed background dimensions at the specified position.")

    # Extract the alpha channel from the overlay and create an inverse alpha channel
    alpha = overlay[:, :, 3] / 255.0
    inverse_alpha = 1.0 - alpha

    # Convert overlay to BGR if it's in RGB
    if overlay.shape[2] == 4:  # If it has an alpha channel
        overlay = cv2.cvtColor(overlay[:, :, :3], cv2.COLOR_RGB2BGR)
        overlay = np.concatenate([overlay, overlay[:, :, 3:]], axis=2)  # Add alpha channel back
    else:
        overlay = cv2.cvtColor(overlay, cv2.COLOR_RGB2BGR)
        
    # Overlay the images
    for c in range(0, 3):
        background[y:overlay.shape[0]+y, x:overlay.shape[1]+x, c] = (
            alpha * overlay[:, :, c] + inverse_alpha * background[y:overlay.shape[0]+y, x:overlay.shape[1]+x, c]
        )
        
    return background

def create_charles_frames(background, charles_frames):
    output_frames = []
    # Load background image
    background = cv2.imread(background, cv2.COLOR_BGR2RGB)
    background = cv2.cvtColor(background, cv2.COLOR_BGR2RGB)
    # resize background to match user image
    background = resize_and_crop(background, (640, 480))

    for bot_image_path in charles_frames:
        bot_image = cv2.imread(bot_image_path, cv2.IMREAD_UNCHANGED)

        # assert bot image is square
        assert bot_image.shape[0] == bot_image.shape[1]

        # resize bot image if it is larger than backgroun impage in any direction
        if bot_image.shape[0] > background.shape[0]:
            bot_image = cv2.resize(bot_image, (background.shape[0], background.shape[0]), interpolation=cv2.INTER_AREA)

        # Overlay bot image on the right-hand side
        x_bot = background.shape[1] - bot_image.shape[1]
        y_bot = background.shape[0] - bot_image.shape[0]
        background_with_bot = overlay_images(background.copy(), bot_image, x_bot, y_bot)

        output_frames.append(background_with_bot)
    
    return output_frames

class CharlesAnimator:
    def __init__(self):
        self.mouth_open = False
        self.last_change_time = 0
        self.next_change_in = 0
        self.was_talking = False
        # use static frames for pefromance
        self.static_frames = create_charles_frames("./images/zoom-background.png", [
            "./images/charles.png", 
            "./images/charles-open.png"
            ])

    def update(self, is_talking):
        start_talking = True if is_talking and not self.was_talking else False
        self.was_talking = is_talking
        current_time = time.time()

        # Open the mouth when the character starts talking
        if start_talking:
            self.mouth_open = True
            self.next_change_in = current_time + random.uniform(0.05, 0.25)
            return self.mouth_open

        # Initialize the next change time if it's zero.
        if self.next_change_in == 0:
            self.next_change_in = current_time + random.uniform(0.05, 0.25)

        # Update the mouth state only if the character is talking.
        if is_talking:
            # Check if it's time to change the mouth state.
            if current_time >= self.next_change_in:
                self.mouth_open = not self.mouth_open
                self.next_change_in = current_time + random.uniform(0.05, 0.25)
        else:
            # Close the mouth if the character is not talking.
            self.mouth_open = False

        frame = self.static_frames[1] if self.mouth_open else self.static_frames[0]
        return frame