Spaces:
Runtime error
Runtime error
File size: 10,433 Bytes
eb7fbcb |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# processing.py
import os
import random
import textwrap
import logging
from typing import List, Optional
from PIL import Image, ImageDraw, ImageFont, ImageOps, ImageFilter, ImageEnhance, UnidentifiedImageError
from config import Config
from utils import add_noise_to_image
logger = logging.getLogger(__name__)
def apply_template(user_image_path: str, caption: str, template_path: str) -> Optional[str]:
"""
Applies user image and caption to a predefined template.
Assumes template_path points to a PNG with a transparent "window" for the user image,
and other graphics are the foreground.
"""
output_filename = f"result_{os.path.basename(template_path).split('.')[0]}_{random.randint(10000, 99999)}.jpg"
output_path = os.path.join(Config.OUTPUT_DIR, output_filename)
logger.debug(f"Applying foreground template '{os.path.basename(template_path)}' over user image '{os.path.basename(user_image_path)}'. Output: {output_path}")
try:
with Image.open(user_image_path).convert("RGBA") as user_image_orig, \
Image.open(template_path).convert("RGBA") as foreground_graphic_template:
logger.debug(f"User image size: {user_image_orig.size}, Foreground template size: {foreground_graphic_template.size}")
# 1. Resize user's image to fit the placeholder size
logger.debug(f"Resizing user image to placeholder size {Config.PLACEHOLDER_SIZE}")
user_image_resized = ImageOps.fit(user_image_orig, Config.PLACEHOLDER_SIZE, Image.Resampling.LANCZOS)
logger.debug(f"User image resized to: {user_image_resized.size}")
# 2. Create a new transparent base canvas of the final template size
final_base_image = Image.new("RGBA", Config.TEMPLATE_SIZE, (0, 0, 0, 0))
logger.debug(f"Created base canvas of size {Config.TEMPLATE_SIZE}")
# 3. Paste the user's resized image onto this base canvas at the placeholder position
logger.debug(f"Pasting resized user image onto base canvas at {Config.PLACEHOLDER_POSITION}")
final_base_image.paste(user_image_resized, Config.PLACEHOLDER_POSITION, user_image_resized if user_image_resized.mode == 'RGBA' else None)
# 4. Ensure your Canva template (foreground_graphic_template) is the correct final size
if foreground_graphic_template.size != Config.TEMPLATE_SIZE:
logger.warning(f"Foreground template size {foreground_graphic_template.size} "
f"differs from expected {Config.TEMPLATE_SIZE}. Resizing it.")
foreground_graphic_template = foreground_graphic_template.resize(Config.TEMPLATE_SIZE, Image.Resampling.LANCZOS)
# 5. Composite the Canva template ON TOP of the base image
logger.debug("Alpha compositing user image base with foreground Canva template.")
combined_image = Image.alpha_composite(final_base_image, foreground_graphic_template)
logger.debug(f"Combined image size: {combined_image.size}, mode: {combined_image.mode}")
# --- Add Caption Text (on top of everything) ---
logger.debug("Adding caption text to the combined image")
draw = ImageDraw.Draw(combined_image)
try:
font_size = Config.MAX_FONT_SIZE // 2
font = ImageFont.truetype(Config.FONT_PATH, font_size)
logger.debug(f"Using font '{Config.FONT_PATH}' size {font_size}")
except IOError:
logger.warning(f"Failed loading font '{Config.FONT_PATH}'. Using Pillow's default.")
font = ImageFont.load_default()
wrapped_text = textwrap.fill(caption, width=Config.MAX_CAPTION_WIDTH)
# --- Adjust Text Position based on Config ---
text_bbox = draw.textbbox((0,0), wrapped_text, font=font, align="center")
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
text_x = (Config.TEMPLATE_SIZE[0] - text_width) // 2 # Centered horizontally on whole template
text_y = Config.TEXT_AREA_Y_START + (Config.TEXT_AREA_HEIGHT - text_height) // 2 # Centered vertically in defined text area
logger.debug(f"Calculated text position: ({text_x}, {text_y}) for text area starting at Y={Config.TEXT_AREA_Y_START} with height {Config.TEXT_AREA_HEIGHT}")
shadow_offset = 1
draw.text((text_x + shadow_offset, text_y + shadow_offset), wrapped_text, font=font, fill="black", align="center")
draw.text((text_x, text_y), wrapped_text, font=font, fill="white", align="center")
final_output_image = combined_image.convert("RGB")
logger.debug(f"Saving final image to {output_path} with quality {Config.JPEG_QUALITY}")
final_output_image.save(output_path, "JPEG", quality=Config.JPEG_QUALITY)
logger.info(f"Generated final image: {output_path}")
return output_path
except FileNotFoundError:
logger.error(f"Template or user image not found. Template: '{template_path}', User Image: '{user_image_path}'")
except UnidentifiedImageError:
logger.error(f"Could not identify image file. Template: '{template_path}', User Image: '{user_image_path}'")
except Exception as e:
logger.error(f"Error applying template '{os.path.basename(template_path)}': {e}", exc_info=True)
return None
def create_auto_template(user_image_path: str, caption: str, variant: int) -> Optional[str]:
output_filename = f"auto_template_{variant}_{random.randint(10000, 99999)}.jpg"
output_path = os.path.join(Config.OUTPUT_DIR, output_filename)
logger.debug(f"Creating auto template variant {variant} -> {output_path}")
try:
bg_color = (random.randint(0, 150), random.randint(0, 150), random.randint(0, 150))
bg = Image.new('RGB', Config.AUTO_TEMPLATE_SIZE, color=bg_color)
with Image.open(user_image_path) as user_img_orig:
user_img = user_img_orig.copy()
user_img = ImageOps.fit(user_img, Config.AUTO_USER_IMAGE_SIZE, Image.Resampling.LANCZOS)
effect_choice = random.choice(['blur', 'noise', 'color', 'contrast', 'sharpness', 'none'])
if effect_choice == 'blur': user_img = user_img.filter(ImageFilter.GaussianBlur(random.uniform(0.5, 1.8)))
elif effect_choice == 'noise': user_img = add_noise_to_image(user_img, Config.NOISE_INTENSITY)
elif effect_choice == 'color': user_img = ImageEnhance.Color(user_img).enhance(random.uniform(0.3, 1.7))
elif effect_choice == 'contrast': user_img = ImageEnhance.Contrast(user_img).enhance(random.uniform(0.7, 1.4))
elif effect_choice == 'sharpness': user_img = ImageEnhance.Sharpness(user_img).enhance(random.uniform(1.1, 2.0))
border_color = (random.randint(180, 255), random.randint(180, 255), random.randint(180, 255))
border_width = random.randint(8, 20)
user_img = ImageOps.expand(user_img, border=border_width, fill=border_color)
paste_x = (bg.width - user_img.width) // 2
paste_y = (bg.height - user_img.height) // 3
bg.paste(user_img, (paste_x, paste_y))
draw = ImageDraw.Draw(bg)
try:
font_size = random.randint(Config.MIN_FONT_SIZE, Config.MAX_FONT_SIZE)
font = ImageFont.truetype(Config.FONT_PATH, font_size)
except IOError:
logger.warning(f"Failed loading font '{Config.FONT_PATH}'. Using default.")
font = ImageFont.load_default()
wrapped_text = textwrap.fill(caption, width=Config.MAX_CAPTION_WIDTH)
text_bbox = draw.textbbox((0, 0), wrapped_text, font=font, align="center")
text_x = (bg.width - (text_bbox[2] - text_bbox[0])) // 2
text_y = paste_y + user_img.height + 30 # Position text below the image
text_color = (random.randint(200, 255), random.randint(200, 255), random.randint(200, 255))
stroke_color = (random.randint(0, 50), random.randint(0, 50), random.randint(0, 50))
draw.text((text_x, text_y), wrapped_text, font=font, fill=text_color, stroke_width=Config.TEXT_STROKE_WIDTH, stroke_fill=stroke_color, align="center")
bg.save(output_path, "JPEG", quality=Config.JPEG_QUALITY)
logger.info(f"Generated auto-template image: {output_path}")
return output_path
except FileNotFoundError: logger.error(f"User image not found: '{user_image_path}'")
except UnidentifiedImageError: logger.error(f"Unidentified user image: '{user_image_path}'")
except Exception as e: logger.error(f"Error creating auto-template {variant}: {e}", exc_info=True)
return None
def load_predefined_templates() -> List[str]:
templates = []
template_dir = Config.PREDEFINED_TEMPLATES_DIR
logger.debug(f"Searching templates in: {os.path.abspath(template_dir)}")
try:
if not os.path.isdir(template_dir):
logger.warning(f"Templates directory not found: '{template_dir}'")
return []
files = os.listdir(template_dir)
for file in files:
if file.lower().endswith(('.png', '.jpg', '.jpeg')):
full_path = os.path.join(template_dir, file)
if os.path.isfile(full_path): templates.append(full_path)
if not templates: logger.warning(f"No valid template files found in '{template_dir}'.")
else: logger.info(f"Loaded {len(templates)} templates.")
except Exception as e: logger.error(f"Error loading templates: {e}", exc_info=True)
return templates
def process_images_sync(user_image_path: str, caption: str) -> List[str]:
logger.info("Starting sync image processing task...")
generated_image_paths: List[str] = []
predefined_templates = load_predefined_templates()
if predefined_templates:
for template_path in predefined_templates:
result_path = apply_template(user_image_path, caption, template_path)
if result_path: generated_image_paths.append(result_path)
for i in range(Config.AUTO_TEMPLATES_COUNT):
result_path = create_auto_template(user_image_path, caption, i)
if result_path: generated_image_paths.append(result_path)
logger.info(f"Sync processing finished. Generated {len(generated_image_paths)} images.")
return generated_image_paths
|