understanding commited on
Commit
eb7fbcb
·
verified ·
1 Parent(s): 20d73c7

Create processing.py

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