Testcomic / backend /speech_bubble /bubble_placement.py
3v324v23's picture
Update Comic123 with local comic folder files
83e35a7
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