Spaces:
Sleeping
Sleeping
| from PIL import Image, ImageDraw, ImageFont | |
| # Add this function to get available fonts with fallbacks | |
| def get_font_path(font_name, font_size): | |
| """Get font path for the selected font with fallbacks""" | |
| font_mappings = { | |
| "Impact": [ | |
| "impact.ttf", | |
| "Impact.ttf", | |
| "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", | |
| "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" | |
| ], | |
| "Arial Bold": [ | |
| "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", | |
| "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", | |
| "arial-bold.ttf" | |
| ], | |
| "Times Bold": [ | |
| "/usr/share/fonts/truetype/liberation/LiberationSerif-Bold.ttf", | |
| "/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf", | |
| "times-bold.ttf" | |
| ], | |
| "Comic Sans": [ | |
| "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", | |
| "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", | |
| "comic.ttf" | |
| ], | |
| "Bebas Neue": [ | |
| "/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed-Bold.ttf", | |
| "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf", | |
| "bebas-neue.ttf" | |
| ] | |
| } | |
| # Get font paths for the selected font | |
| font_paths = font_mappings.get(font_name, []) | |
| # Try each path | |
| for font_path in font_paths: | |
| try: | |
| font = ImageFont.truetype(font_path, font_size) | |
| print(f"SUCCESS: Loaded {font_name} from {font_path}") | |
| return font | |
| except (OSError, IOError): | |
| continue | |
| # If no specific font found, try default system fonts | |
| default_paths = [ | |
| "DejaVuSans-Bold.ttf", | |
| "arial.ttf", | |
| "/System/Library/Fonts/Arial.ttf", | |
| "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", | |
| ] | |
| for font_path in default_paths: | |
| try: | |
| font = ImageFont.truetype(font_path, font_size) | |
| print(f"FALLBACK: Loaded default font from {font_path} for {font_name}") | |
| return font | |
| except (OSError, IOError): | |
| continue | |
| # Final fallback | |
| try: | |
| font = ImageFont.load_default() | |
| print(f"FINAL FALLBACK: Using system default for {font_name}") | |
| return font | |
| except Exception: | |
| print(f"ERROR: Could not load any font for {font_name}") | |
| return None | |
| # Function to create template images | |
| def create_pattern_template(pattern, width=200, height=120): | |
| """Create a visual template showing text placement for each pattern""" | |
| # Create black background | |
| img = Image.new('RGB', (width, height), color='black') | |
| draw = ImageDraw.Draw(img) | |
| # Define positions based on pattern | |
| positions = [] | |
| if pattern == "2-lines-top": | |
| positions = [(width // 2, int(height * 0.15)), (width // 2, int(height * 0.35))] | |
| elif pattern == "2-lines-center": | |
| positions = [(width // 2, int(height * 0.35)), (width // 2, int(height * 0.65))] | |
| elif pattern == "2-lines-bottom": | |
| positions = [(width // 2, int(height * 0.65)), (width // 2, int(height * 0.85))] | |
| elif pattern == "3-lines-top": | |
| positions = [(width // 2, int(height * 0.15)), (width // 2, int(height * 0.35)), (width // 2, int(height * 0.55))] | |
| elif pattern == "3-lines-center": | |
| positions = [(width // 2, int(height * 0.25)), (width // 2, int(height * 0.50)), (width // 2, int(height * 0.75))] | |
| elif pattern == "3-lines-bottom": | |
| positions = [(width // 2, int(height * 0.45)), (width // 2, int(height * 0.65)), (width // 2, int(height * 0.85))] | |
| # Draw white rectangles to represent text boxes | |
| for i, (x, y) in enumerate(positions): | |
| # Draw white rectangle as text placeholder | |
| box_width = 60 | |
| box_height = 12 | |
| left = x - box_width // 2 | |
| top = y - box_height // 2 | |
| right = x + box_width // 2 | |
| bottom = y + box_height // 2 | |
| draw.rectangle([left, top, right, bottom], fill='white', outline='white') | |
| return img | |
| def get_pattern_template(pattern): | |
| """Get template for pattern - create on demand to avoid initialization issues""" | |
| return create_pattern_template(pattern) | |
| # Text Overlay Functions | |
| def add_text_to_image(img, pattern, line1, line2, line3, font_size, color, add_outline, font_name): | |
| """Overlay 2- or 3-line text on the image in a preset layout.""" | |
| print("=== DEBUG: add_text_to_image called ===") | |
| print(f"Input image: {type(img)}") | |
| print(f"Pattern: {pattern}") | |
| print(f"Lines: [{line1!r}, {line2!r}, {line3!r}]") | |
| print(f"Font size: {font_size}") | |
| print(f"Font name: {font_name}") | |
| print(f"Color: {color!r}") | |
| print(f"Add outline: {add_outline}") | |
| if img is None: | |
| print("ERROR: No image provided") | |
| return None, "Please supply an image first." | |
| try: | |
| # Create a working copy | |
| print(f"Original image mode: {img.mode}, size: {img.size}") | |
| image = img.convert("RGB") | |
| print(f"Converted image mode: {image.mode}") | |
| draw = ImageDraw.Draw(image) | |
| print("ImageDraw created successfully") | |
| # UPDATED: Use selected font instead of hardcoded paths | |
| font = get_font_path(font_name, font_size) | |
| if font is None: | |
| return None, f"Could not load font: {font_name}" | |
| w, h = image.size | |
| print(f"Image dimensions: {w}x{h}") | |
| # Pattern matching (same as before) | |
| positions = [] | |
| detected_pattern = "unknown" | |
| pattern_lower = pattern.lower() | |
| if ("2-lines-top" in pattern_lower) or ("2 lines - top" in pattern_lower): | |
| positions = [(w // 2, int(h * 0.10)), (w // 2, int(h * 0.20))] | |
| detected_pattern = "2-lines-top" | |
| elif ("2-lines-bottom" in pattern_lower) or ("2 lines - bottom" in pattern_lower): | |
| positions = [(w // 2, int(h * 0.80)), (w // 2, int(h * 0.90))] | |
| detected_pattern = "2-lines-bottom" | |
| elif ("2-lines-center" in pattern_lower) or ("2 lines - center" in pattern_lower): | |
| positions = [(w // 2, int(h * 0.45)), (w // 2, int(h * 0.55))] | |
| detected_pattern = "2-lines-center" | |
| elif ("3-lines-top" in pattern_lower) or ("3 lines - top" in pattern_lower): | |
| positions = [(w // 2, int(h * 0.10)), (w // 2, int(h * 0.20)), (w // 2, int(h * 0.30))] | |
| detected_pattern = "3-lines-top" | |
| elif ("3-lines-center" in pattern_lower) or ("3 lines - center" in pattern_lower): | |
| positions = [(w // 2, int(h * 0.40)), (w // 2, int(h * 0.50)), (w // 2, int(h * 0.60))] | |
| detected_pattern = "3-lines-center" | |
| elif ("3-lines-bottom" in pattern_lower) or ("3 lines - bottom" in pattern_lower): | |
| positions = [(w // 2, int(h * 0.70)), (w // 2, int(h * 0.80)), (w // 2, int(h * 0.90))] | |
| detected_pattern = "3-lines-bottom" | |
| else: | |
| print(f"WARNING: Unknown pattern '{pattern}', using 2-lines-center as default") | |
| positions = [(w // 2, int(h * 0.45)), (w // 2, int(h * 0.55))] | |
| detected_pattern = "2-lines-center (default)" | |
| print(f"Detected pattern: {detected_pattern}") | |
| print(f"Text positions calculated: {positions}") | |
| # Color parsing (same as before) | |
| original_color = color | |
| final_color = (255, 255, 255) | |
| if isinstance(color, str): | |
| if color.startswith('rgba(') and color.endswith(')'): | |
| print(f"Parsing RGBA string: {color}") | |
| try: | |
| rgba_part = color[5:-1] | |
| values = [float(x.strip()) for x in rgba_part.split(',')] | |
| if len(values) >= 3: | |
| final_color = (int(round(values[0])), int(round(values[1])), int(round(values[2]))) | |
| print(f"Successfully parsed RGBA {color} to RGB: {final_color}") | |
| except (ValueError, IndexError) as e: | |
| print(f"Error parsing RGBA {color}: {e}, using white") | |
| final_color = (255, 255, 255) | |
| elif color.startswith('#'): | |
| print(f"Converting hex color {color}") | |
| hex_color = color.lstrip('#') | |
| if len(hex_color) == 6: | |
| try: | |
| final_color = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4)) | |
| print(f"Successfully converted hex {color} to RGB: {final_color}") | |
| except ValueError as e: | |
| print(f"Invalid hex color {color}: {e}, using white") | |
| final_color = (255, 255, 255) | |
| print(f"Final color for drawing: {final_color}") | |
| # Draw text (same logic as before) | |
| lines = [line1, line2, line3] | |
| print(f"All input lines: {lines}") | |
| active_lines = [] | |
| for i in range(len(positions)): | |
| if i < len(lines) and lines[i] and lines[i].strip(): | |
| active_lines.append((positions[i], lines[i].strip())) | |
| print(f"Active lines to draw: {len(active_lines)} out of {len(positions)} positions") | |
| text_drawn = False | |
| for i, ((x, y), txt) in enumerate(active_lines): | |
| print(f"Drawing line {i+1}: '{txt}' at position ({x}, {y}) with font {font_name}") | |
| # Optional outline/stroke | |
| if add_outline: | |
| stroke_width = max(1, font_size // 20) | |
| print(f"Adding outline with stroke width: {stroke_width}") | |
| # Draw text outline (black) | |
| for dx in [-stroke_width, 0, stroke_width]: | |
| for dy in [-stroke_width, 0, stroke_width]: | |
| if dx != 0 or dy != 0: | |
| try: | |
| draw.text((x + dx, y + dy), txt, fill=(0, 0, 0), font=font, anchor="mm") | |
| except TypeError: | |
| try: | |
| bbox = draw.textbbox((0, 0), txt, font=font) | |
| text_w = bbox[2] - bbox[0] | |
| text_h = bbox[3] - bbox[1] | |
| draw.text((x - text_w//2 + dx, y - text_h//2 + dy), txt, fill=(0, 0, 0), font=font) | |
| except Exception: | |
| draw.text((x + dx, y + dy), txt, fill=(0, 0, 0), font=font) | |
| # Draw main text | |
| try: | |
| draw.text((x, y), txt, fill=final_color, font=font, anchor="mm") | |
| print(f"SUCCESS: Main text drawn with {font_name} at ({x}, {y})") | |
| except TypeError: | |
| try: | |
| bbox = draw.textbbox((0, 0), txt, font=font) | |
| text_w = bbox[2] - bbox[0] | |
| text_h = bbox[3] - bbox[1] | |
| fallback_x = x - text_w//2 | |
| fallback_y = y - text_h//2 | |
| draw.text((fallback_x, fallback_y), txt, fill=final_color, font=font) | |
| print(f"SUCCESS: Fallback text drawn with {font_name} at ({fallback_x}, {fallback_y})") | |
| except Exception: | |
| draw.text((x, y), txt, fill=final_color, font=font) | |
| print(f"SUCCESS: Basic text drawn with {font_name} at ({x}, {y})") | |
| text_drawn = True | |
| print(f"Completed drawing line {i+1}") | |
| if not text_drawn: | |
| print("No text was drawn - all lines were empty") | |
| return img, "No text to add (all lines were empty)" | |
| print("=== Text drawing completed successfully ===") | |
| return image, f"✅ {len(active_lines)} lines added! Font: {font_name}, Pattern: {detected_pattern}" | |
| except Exception as e: | |
| print(f"CRITICAL ERROR in add_text_to_image: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| return None, f"Error adding text: {str(e)}" | |