from backend.utils import convert_to_css_pixel, get_panel_type, types import math BUBBLE_WIDTH = 200 BUBBLE_HEIGHT = 94 def add_bubble_padding(least_roi_x, least_roi_y, crop_coord): left,right,top,bottom = crop_coord panel = get_panel_type(left, right, top, bottom) image_width = types[panel]['width'] image_height = types[panel]['height'] if least_roi_x == 0: if panel == '1' or panel == '2': least_roi_x += 10 elif panel == '3': least_roi_x += 30 else: least_roi_x += 20 elif least_roi_x == image_width: least_roi_x -= BUBBLE_WIDTH + 15 elif least_roi_x >= image_width - BUBBLE_WIDTH: least_roi_x -= BUBBLE_WIDTH - (image_width - least_roi_x) + 15 if least_roi_y == 0: if panel == '2': least_roi_y += 30 else: least_roi_y += 15 elif least_roi_y == image_height: least_roi_y -= BUUBLE_HEIGHT + 15 elif least_roi_y >= image_height - BUUBLE_HEIGHT: least_roi_y -= BUUBLE_HEIGHT - (image_height - least_roi_y) + 15 return least_roi_x, least_roi_y def get_bubble_position(image_coords, CAM_data=None, lip_coords=None): """ Redesigned bubble placement for smart resize - positions relative to actual image content """ left, right, top, bottom = image_coords # Calculate image dimensions within panel image_width = right - left image_height = bottom - top print(f"Image area: {image_width:.0f}x{image_height:.0f} at ({left:.0f}, {top:.0f})") # Define safe bubble positions relative to the actual image content safe_positions = _get_safe_image_positions(left, right, top, bottom, image_width, image_height) # If we have lip coordinates, create face exclusion zones if lip_coords and lip_coords[0] != -1 and lip_coords[1] != -1: lip_x, lip_y = lip_coords # Lip coordinates are already in panel coordinate system print(f"Lip detected at coords: ({lip_x}, {lip_y})") # Filter out positions too close to the face face_exclusion_radius = 60 # Standard exclusion radius filtered_positions = [] for pos in safe_positions: distance = math.sqrt((pos[0] - lip_x)**2 + (pos[1] - lip_y)**2) if distance > face_exclusion_radius: filtered_positions.append(pos) if filtered_positions: safe_positions = filtered_positions print(f"Filtered to {len(safe_positions)} face-safe positions") else: print("Warning: No face-safe positions found, using all safe positions") # Select the best position (prefer corners and edges of image) best_position = _select_best_image_position(safe_positions, left, right, top, bottom) print(f"Selected bubble position: {best_position}") return best_position def _get_safe_image_positions(left, right, top, bottom, image_width, image_height): """ Generate safe bubble positions relative to the actual image content """ positions = [] # Calculate margins to keep bubbles within image bounds margin_x = BUBBLE_WIDTH / 2 + 20 margin_y = BUBBLE_HEIGHT / 2 + 20 # Define grid within the image area - focus on upper areas grid_cols = 4 grid_rows = 4 # More rows for better upper area coverage # Calculate grid cell size within image cell_width = image_width / grid_cols cell_height = image_height / grid_rows # Generate grid positions within image - prioritize upper areas for row in range(grid_rows): for col in range(grid_cols): x = left + col * cell_width + cell_width / 2 y = top + row * cell_height + cell_height / 2 # Ensure bubble fits within image bounds if (left + margin_x <= x <= right - margin_x and top + margin_y <= y <= bottom - margin_y): positions.append((x, y)) # Add extra positions in upper areas for better coverage upper_positions = [] upper_margin = 40 # Top edge positions for i in range(1, grid_cols): x = left + i * cell_width y = top + upper_margin if (left + margin_x <= x <= right - margin_x and top + margin_y <= y <= bottom - margin_y): upper_positions.append((x, y)) # Upper quarter positions upper_quarter_y = top + (bottom - top) * 0.25 for i in range(1, grid_cols): x = left + i * cell_width y = upper_quarter_y if (left + margin_x <= x <= right - margin_x and top + margin_y <= y <= bottom - margin_y): upper_positions.append((x, y)) positions.extend(upper_positions) # Add corner positions relative to image - prioritize upper corners corner_margin = 30 corners = [ (left + corner_margin, top + corner_margin), # Top-left of image (highest priority) (right - corner_margin, top + corner_margin), # Top-right of image (highest priority) (left + corner_margin, top + (bottom - top) * 0.2), # Upper-left area (right - corner_margin, top + (bottom - top) * 0.2), # Upper-right area (left + corner_margin, bottom - corner_margin), # Bottom-left of image (lower priority) (right - corner_margin, bottom - corner_margin) # Bottom-right of image (lower priority) ] for corner in corners: if (left + margin_x <= corner[0] <= right - margin_x and top + margin_y <= corner[1] <= bottom - margin_y): positions.append(corner) # Add edge positions along image boundaries edge_positions = [] edge_margin = 50 # Top edge of image for i in range(1, grid_cols): x = left + i * cell_width y = top + edge_margin if (left + margin_x <= x <= right - margin_x and top + margin_y <= y <= bottom - margin_y): edge_positions.append((x, y)) # Right edge of image for i in range(1, grid_rows): x = right - edge_margin y = top + i * cell_height if (left + margin_x <= x <= right - margin_x and top + margin_y <= y <= bottom - margin_y): edge_positions.append((x, y)) positions.extend(edge_positions) # If still no positions, use image center if len(positions) == 0: center_x = left + image_width / 2 center_y = top + image_height / 2 positions.append((center_x, center_y)) print(f"Warning: Image too small, using center position only") print(f"Generated {len(positions)} safe positions for image area {image_width:.0f}x{image_height:.0f}") return positions def _select_best_image_position(positions, left, right, top, bottom): """ Select the best position relative to image content Priority: TOP areas > corners > edges > center """ if not positions: # Fallback to upper area if no positions available return (left + (right - left) / 2, top + (bottom - top) * 0.2) # 20% from top # Score positions based on preference scored_positions = [] for pos in positions: x, y = pos score = 0 # STRONGLY prefer upper areas (highest priority) upper_threshold = (bottom - top) * 0.4 # Top 40% of image if y < top + upper_threshold: score += 200 # Much higher score for upper areas # Prefer top quarter (highest score) top_quarter = (bottom - top) * 0.25 if y < top + top_quarter: score += 150 # Prefer corners of image (high score) corner_threshold = 50 if (x < left + corner_threshold or x > right - corner_threshold) and \ (y < top + corner_threshold or y > bottom - corner_threshold): score += 100 # Prefer edges of image (medium score) edge_threshold = 80 if (x < left + edge_threshold or x > right - edge_threshold) or \ (y < top + edge_threshold or y > bottom - edge_threshold): score += 50 # Prefer right side (common comic bubble placement) if x > left + (right - left) * 0.6: # Right 40% of image score += 30 # Penalize lower areas lower_threshold = (bottom - top) * 0.7 # Bottom 30% of image if y > top + lower_threshold: score -= 50 # Negative score for lower areas scored_positions.append((pos, score)) # Sort by score (highest first) and return the best scored_positions.sort(key=lambda x: x[1], reverse=True) best_position = scored_positions[0][0] print(f"Selected position with score {scored_positions[0][1]} at y={best_position[1]:.0f} (top={top:.0f}, bottom={bottom:.0f})") return best_position