File size: 9,384 Bytes
a54527e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324f4d3
 
a54527e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11a8362
a54527e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont

def extract_frame(video_path: str, timestamp: str, output_image_path: str) -> None:
    """
    Extracts a frame from a video at the specified timestamp and saves it as an image.

    Parameters:
    - video_path (str): Path to the input video file.
    - timestamp (str): Timestamp in the format "HH:MM:SS.sss".
    - output_image_path (str): Path to save the extracted frame image.
    """
    # Convert timestamp to milliseconds
    h, m, s = map(float, timestamp.split(':'))
    target_msec = int((h * 3600 + m * 60 + s) * 1000)

    # Open the video
    cap = cv2.VideoCapture(video_path)
    if not cap.isOpened():
        raise IOError(f"Cannot open video file: {video_path}")

    # Set position in milliseconds
    cap.set(cv2.CAP_PROP_POS_MSEC, target_msec)

    # Read the frame
    ret, frame = cap.read()
    if not ret:
        raise RuntimeError(f"Failed to retrieve frame at {timestamp} ({target_msec} ms)")

    # Save frame as PNG
    cv2.imwrite(output_image_path, frame)
    print(f"Frame saved as {output_image_path}")

    # Clean up
    cap.release()


def center_bbox_in_circle(
    input_image_path: str,
    bbox: tuple,
    output_path: str,
    background_path = "src/assets/BACKGROUND.png",
    mask_path = "src/assets/circle_mask.png",
    circle_center=(296, 126),
    circle_radius=77,
    blend_alpha: float = 0.3
) -> None:
    """
    Centers a bounding box from an input image into a circular area on a background image.

    Parameters:
    - input_image_path (str): Path to the input image.
    - background_path (str): Path to the background image with a circle.
    - mask_path (str): Path to the circular mask image (grayscale).
    - bbox (tuple): Bounding box in normalized format (y_min, x_min, y_max, x_max), range 0–1000.
    - circle_center (tuple): (x, y) center of the target circle on the background.
    - circle_radius (int): Radius of the target circle.
    - output_path (str): Path to save the resulting image.
    - blend_alpha (float): Blending factor for the background (default: 0.3).
    """
    background = cv2.imread(background_path)
    input_image = cv2.imread(input_image_path)

    img_height, img_width = input_image.shape[:2]
    y_min = int(bbox[0] / 1000.0 * img_height)
    x_min = int(bbox[1] / 1000.0 * img_width)
    y_max = int(bbox[2] / 1000.0 * img_height)
    x_max = int(bbox[3] / 1000.0 * img_width)

    bbox_w = x_max - x_min
    bbox_h = y_max - y_min
    bbox_center_x = x_min + bbox_w // 2
    bbox_center_y = y_min + bbox_h // 2

    circle_diameter = 2 * circle_radius
    scale = min(circle_diameter / bbox_w, circle_diameter / bbox_h)

    new_width = int(img_width * scale)
    new_height = int(img_height * scale)
    resized_image = cv2.resize(input_image, (new_width, new_height))

    new_bbox_center_x = int(bbox_center_x * scale)
    new_bbox_center_y = int(bbox_center_y * scale)

    offset_x = circle_center[0] - new_bbox_center_x
    offset_y = circle_center[1] - new_bbox_center_y

    canvas = np.zeros_like(background)

    start_x = max(offset_x, 0)
    start_y = max(offset_y, 0)
    end_x = min(start_x + new_width, background.shape[1])
    end_y = min(start_y + new_height, background.shape[0])

    src_start_x = max(-offset_x, 0)
    src_start_y = max(-offset_y, 0)

    target_h = min(end_y - start_y, resized_image.shape[0] - src_start_y)
    target_w = min(end_x - start_x, resized_image.shape[1] - src_start_x)

    canvas[start_y:start_y+target_h, start_x:start_x+target_w] = resized_image[
        src_start_y:src_start_y + target_h,
        src_start_x:src_start_x + target_w
    ]

    mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)
    mask = cv2.resize(mask, (background.shape[1], background.shape[0]))

    blended = cv2.addWeighted(canvas, blend_alpha, background, 1.0 - blend_alpha, 0)
    mask_3c = cv2.merge([mask, mask, mask])
    inv_mask = cv2.bitwise_not(mask_3c)

    outside = cv2.bitwise_and(blended, mask_3c)
    inside = cv2.bitwise_and(canvas, inv_mask)
    combined = cv2.add(outside, inside)

    cv2.imwrite(output_path, combined)
    print(f"Saved result to {output_path}")


def overlay_image_bottom_right(
    background_path: str,
    overlay_path: str,
    output_path: str
) -> None:
    """
    Overlays an image (optionally with transparency) onto the bottom-right corner of another image.

    Parameters:
    - background_path (str): Path to the background image.
    - overlay_path (str): Path to the overlay image (can have alpha channel).
    - output_path (str): Path to save the combined image.
    """
    # Load images (support alpha if available)
    background = cv2.imread(background_path, cv2.IMREAD_UNCHANGED)
    overlay = cv2.imread(overlay_path, cv2.IMREAD_UNCHANGED)

    h_bg, w_bg = background.shape[:2]
    h_ov, w_ov = overlay.shape[:2]

    x_offset = w_bg - w_ov
    y_offset = h_bg - h_ov

    if overlay.shape[2] == 4:
        # Split channels
        overlay_rgb = overlay[:, :, :3]
        alpha = overlay[:, :, 3] / 255.0

        # Handle 3-channel background (add alpha if missing)
        if background.shape[2] == 3:
            background = cv2.cvtColor(background, cv2.COLOR_BGR2BGRA)

        for c in range(3):  # Only blend RGB channels
            background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov, c] = (
                alpha * overlay_rgb[:, :, c] +
                (1 - alpha) * background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov, c]
            )
    else:
        # No alpha channel: paste directly
        background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov] = overlay

    # Ensure saving as 3-channel PNG
    if background.shape[2] == 4:
        background = cv2.cvtColor(background, cv2.COLOR_BGRA2BGR)

    cv2.imwrite(output_path, background)
    print(f"Combined image saved to {output_path}")
    
def overlay_image_top_left(
    background_path: str,
    overlay_path: str,
    output_path: str,
    padding_x: int = 20,
    padding_y: int = 20
) -> None:
    """
    Overlays an image (optionally with transparency) onto the top-left corner of another image with padding.

    Parameters:
    - background_path (str): Path to the background image.
    - overlay_path (str): Path to the overlay image (can have alpha channel).
    - output_path (str): Path to save the resulting image.
    - padding_x (int): Horizontal padding from the left.
    - padding_y (int): Vertical padding from the top.
    """
    # Load images (support alpha if available)
    background = cv2.imread(background_path, cv2.IMREAD_UNCHANGED)
    overlay = cv2.imread(overlay_path, cv2.IMREAD_UNCHANGED)

    h_bg, w_bg = background.shape[:2]
    h_ov, w_ov = overlay.shape[:2]

    x_offset = padding_x
    y_offset = padding_y

    # Ensure overlay fits in background with padding
    if x_offset + w_ov > w_bg or y_offset + h_ov > h_bg:
        raise ValueError("Overlay image does not fit within background with given padding.")

    if overlay.shape[2] == 4:
        overlay_rgb = overlay[:, :, :3]
        alpha = overlay[:, :, 3] / 255.0

        # Add alpha channel to background if needed
        if background.shape[2] == 3:
            background = cv2.cvtColor(background, cv2.COLOR_BGR2BGRA)

        for c in range(3):  # Blend RGB channels
            background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov, c] = (
                alpha * overlay_rgb[:, :, c] +
                (1 - alpha) * background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov, c]
            )
    else:
        # No alpha: paste directly
        background[y_offset:y_offset+h_ov, x_offset:x_offset+w_ov] = overlay

    # Convert back to BGR before saving (if needed)
    if background.shape[2] == 4:
        background = cv2.cvtColor(background, cv2.COLOR_BGRA2BGR)

    cv2.imwrite(output_path, background)
    print(f"Image with overlay saved to {output_path}")
    
    
    
def annotate_image_with_phrase_and_label(image_path, output_path, phrase, label):
    
    font_path = "src/assets/Herokid-BoldUltraCondensed.otf"  # Path to your custom font file
    
    img_cv2 = cv2.imread(image_path)
    if img_cv2 is None:
        raise ValueError("Could not load the image.")

    # Convert BGR (OpenCV) to RGB (Pillow)
    img_rgb = cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB)
    img_pil = Image.fromarray(img_rgb)

    draw = ImageDraw.Draw(img_pil)

    # Load custom font
    font_size = 50
    font = ImageFont.truetype(font_path, font_size)

    # Image dimensions
    width, height = img_pil.size
    margin = 10

    # Phrase (bottom-left in white)
    phrase_bbox = draw.textbbox((0, 0), phrase, font=font)
    phrase_width = phrase_bbox[2] - phrase_bbox[0]
    phrase_height = phrase_bbox[3] - phrase_bbox[1]
    phrase_pos = (margin, height - phrase_height - margin)
    draw.text(phrase_pos, phrase, font=font, fill="white")

    # Label (bottom-right in black)
    label_bbox = draw.textbbox((0, 0), label, font=font)
    label_width = label_bbox[2] - label_bbox[0]
    label_height = label_bbox[3] - label_bbox[1]
    label_pos = (width - label_width - margin, height - label_height - margin)
    draw.text(label_pos, label, font=font, fill="black")

    # Convert back to OpenCV and save
    img_output = cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
    cv2.imwrite(output_path, img_output)