|
|
""" |
|
|
AI-Powered Speech Bubble Placement System |
|
|
Simplified and robust bubble positioning. |
|
|
""" |
|
|
|
|
|
import cv2 |
|
|
from typing import Tuple, Optional |
|
|
|
|
|
class AIBubblePlacer: |
|
|
""" |
|
|
AIBubblePlacer finds the best position for a speech bubble. |
|
|
This version uses a simpler, more reliable heuristic-based approach. |
|
|
""" |
|
|
|
|
|
def __init__(self): |
|
|
|
|
|
self.bubble_width = 160 |
|
|
self.bubble_height = 80 |
|
|
self.panel_width = 300 |
|
|
self.panel_height = 200 |
|
|
self.padding = 10 |
|
|
|
|
|
def place_bubble_ai(self, image_path: str, lip_coords: Optional[Tuple[int, int]] = None) -> Tuple[int, int]: |
|
|
""" |
|
|
Determines the optimal placement for a speech bubble. |
|
|
|
|
|
The strategy is: |
|
|
1. If a face is detected, try to place the bubble above the face. |
|
|
2. If that's not possible, try other corners (top-left, top-right). |
|
|
3. If no face is found, analyze the image for the quietest corner. |
|
|
4. Always ensure the bubble stays within the panel boundaries. |
|
|
""" |
|
|
try: |
|
|
image = cv2.imread(image_path) |
|
|
if image is None: |
|
|
return (50, 20) |
|
|
|
|
|
|
|
|
if lip_coords and lip_coords != (-1, -1): |
|
|
lip_x, lip_y = lip_coords |
|
|
|
|
|
|
|
|
|
|
|
ideal_x = lip_x - (self.bubble_width // 2) |
|
|
|
|
|
ideal_y = lip_y - self.bubble_height - 40 |
|
|
|
|
|
|
|
|
if ideal_y > self.padding: |
|
|
final_x = self._clamp(ideal_x, self.padding, self.panel_width - self.bubble_width - self.padding) |
|
|
final_y = self._clamp(ideal_y, self.padding, self.panel_height - self.bubble_height - self.padding) |
|
|
return (int(final_x), int(final_y)) |
|
|
|
|
|
|
|
|
return self._find_best_corner(image) |
|
|
|
|
|
except Exception as e: |
|
|
print(f"ERROR in AI bubble placer: {e}") |
|
|
return (50, 20) |
|
|
|
|
|
def _clamp(self, value, min_value, max_value): |
|
|
"""Helper function to keep a value within a specific range.""" |
|
|
return max(min_value, min(value, max_value)) |
|
|
|
|
|
def _get_region_clarity(self, image, rect): |
|
|
"""Calculates the 'clarity' of a region (low edge count is clearer).""" |
|
|
x, y, w, h = rect |
|
|
roi = image[y:y+h, x:x+w] |
|
|
if roi.size == 0: |
|
|
return float('inf') |
|
|
|
|
|
gray_roi = cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) |
|
|
edges = cv2.Canny(gray_roi, 100, 200) |
|
|
return np.sum(edges == 0) |
|
|
|
|
|
def _find_best_corner(self, image): |
|
|
"""Analyzes the four corners of the image to find the least busy one.""" |
|
|
h, w, _ = image.shape |
|
|
|
|
|
|
|
|
corner_regions = { |
|
|
"top_left": (self.padding, self.padding, self.bubble_width, self.bubble_height), |
|
|
"top_right": (w - self.bubble_width - self.padding, self.padding, self.bubble_width, self.bubble_height), |
|
|
"bottom_left": (self.padding, h - self.bubble_height - self.padding, self.bubble_width, self.bubble_height), |
|
|
"bottom_right": (w - self.bubble_width - self.padding, h - self.bubble_height - self.padding, self.bubble_width, self.bubble_height) |
|
|
} |
|
|
|
|
|
best_corner_name = None |
|
|
max_clarity = -1 |
|
|
|
|
|
for name, rect in corner_regions.items(): |
|
|
clarity = self._get_region_clarity(image, rect) |
|
|
if clarity > max_clarity: |
|
|
max_clarity = clarity |
|
|
best_corner_name = name |
|
|
|
|
|
|
|
|
best_rect = corner_regions.get(best_corner_name, ("top_left", (self.padding, self.padding))) |
|
|
return (best_rect[0], best_rect[1]) |
|
|
|
|
|
|
|
|
ai_bubble_placer = AIBubblePlacer() |
|
|
|