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