| """ |
| 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() |
|
|