Update app.py
Browse files
app.py
CHANGED
|
@@ -10,17 +10,15 @@ app = Flask(__name__)
|
|
| 10 |
|
| 11 |
class TelegramChatGenerator:
|
| 12 |
def __init__(self):
|
| 13 |
-
#
|
| 14 |
-
self.
|
| 15 |
-
self.
|
| 16 |
-
self.
|
| 17 |
-
self.
|
| 18 |
-
self.reply_bubble_color = (45, 48, 55)
|
| 19 |
-
self.reply_line_color = (55, 125, 255) # Blue accent line
|
| 20 |
self.text_color = (255, 255, 255)
|
| 21 |
-
self.reply_text_color = (
|
| 22 |
self.username_color = (255, 255, 255)
|
| 23 |
-
self.time_color = (
|
| 24 |
|
| 25 |
# Load fonts with fallbacks
|
| 26 |
self.load_fonts()
|
|
@@ -31,11 +29,11 @@ class TelegramChatGenerator:
|
|
| 31 |
def load_fonts(self):
|
| 32 |
"""Load fonts with multiple fallbacks"""
|
| 33 |
font_paths = [
|
| 34 |
-
"
|
| 35 |
-
"
|
| 36 |
-
"
|
| 37 |
-
"
|
| 38 |
-
"
|
| 39 |
]
|
| 40 |
|
| 41 |
try:
|
|
@@ -59,22 +57,6 @@ class TelegramChatGenerator:
|
|
| 59 |
self.reply_font = ImageFont.load_default()
|
| 60 |
self.time_font = ImageFont.load_default()
|
| 61 |
|
| 62 |
-
def create_gradient_background(self, size):
|
| 63 |
-
"""Create a gradient background"""
|
| 64 |
-
img = Image.new('RGB', size, self.bg_gradient_start)
|
| 65 |
-
draw = ImageDraw.Draw(img)
|
| 66 |
-
|
| 67 |
-
# Create vertical gradient
|
| 68 |
-
for y in range(size[1]):
|
| 69 |
-
ratio = y / size[1]
|
| 70 |
-
r = int(self.bg_gradient_start[0] * (1 - ratio) + self.bg_gradient_end[0] * ratio)
|
| 71 |
-
g = int(self.bg_gradient_start[1] * (1 - ratio) + self.bg_gradient_end[1] * ratio)
|
| 72 |
-
b = int(self.bg_gradient_start[2] * (1 - ratio) + self.bg_gradient_end[2] * ratio)
|
| 73 |
-
|
| 74 |
-
draw.line([(0, y), (size[0], y)], fill=(r, g, b))
|
| 75 |
-
|
| 76 |
-
return img
|
| 77 |
-
|
| 78 |
def wrap_text(self, text, font, max_width):
|
| 79 |
"""Smart text wrapping based on pixel width"""
|
| 80 |
words = text.split()
|
|
@@ -101,8 +83,8 @@ class TelegramChatGenerator:
|
|
| 101 |
|
| 102 |
return '\n'.join(lines)
|
| 103 |
|
| 104 |
-
def
|
| 105 |
-
"""Draw bubble with
|
| 106 |
x1, y1, x2, y2 = coords
|
| 107 |
|
| 108 |
# Ensure coordinates are valid
|
|
@@ -114,20 +96,29 @@ class TelegramChatGenerator:
|
|
| 114 |
if max_radius < 1:
|
| 115 |
max_radius = 1
|
| 116 |
|
| 117 |
-
# Draw shadow
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
# Draw main bubble
|
| 124 |
self.draw_rounded_rectangle(draw, coords, max_radius, fill)
|
| 125 |
|
| 126 |
-
# Add subtle
|
| 127 |
-
if y2 - y1 >
|
| 128 |
-
highlight_coords = (x1, y1, x2, y1 +
|
| 129 |
-
lighter_fill = tuple(min(255, c +
|
| 130 |
-
self.draw_rounded_rectangle(draw, highlight_coords, max_radius, lighter_fill)
|
| 131 |
|
| 132 |
def draw_rounded_rectangle(self, draw, coords, radius, fill):
|
| 133 |
"""Draw a perfect rounded rectangle with bounds checking"""
|
|
@@ -197,73 +188,52 @@ class TelegramChatGenerator:
|
|
| 197 |
draw.ellipse([x, y, x + 2, y + 2], fill=color)
|
| 198 |
|
| 199 |
def generate_chat_bubble(self, message, username, reply_to=None, replied_username=None):
|
| 200 |
-
#
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
# Add subtle pattern
|
| 204 |
-
self.add_subtle_pattern(img)
|
| 205 |
-
|
| 206 |
-
draw = ImageDraw.Draw(img)
|
| 207 |
-
|
| 208 |
-
# Calculate bubble dimensions with proper spacing
|
| 209 |
-
padding = 50
|
| 210 |
-
max_bubble_width = self.image_size - (padding * 2) - 60
|
| 211 |
-
|
| 212 |
-
# Ensure minimum dimensions
|
| 213 |
-
if max_bubble_width < 200:
|
| 214 |
-
max_bubble_width = 200
|
| 215 |
|
| 216 |
-
#
|
| 217 |
-
wrapped_message = self.wrap_text(message, self.font,
|
| 218 |
msg_width, msg_height = self.get_text_dimensions(wrapped_message, self.font)
|
| 219 |
-
|
| 220 |
username_width, username_height = self.get_text_dimensions(username, self.username_font)
|
| 221 |
|
| 222 |
-
# Handle reply
|
| 223 |
reply_height = 0
|
| 224 |
wrapped_reply = ""
|
| 225 |
if reply_to and replied_username:
|
| 226 |
-
# Truncate and wrap reply
|
| 227 |
truncated_reply = reply_to[:100] + "..." if len(reply_to) > 100 else reply_to
|
| 228 |
-
wrapped_reply = self.wrap_text(truncated_reply, self.reply_font,
|
| 229 |
reply_username_w, reply_username_h = self.get_text_dimensions(replied_username, self.reply_font)
|
| 230 |
reply_msg_w, reply_msg_h = self.get_text_dimensions(wrapped_reply, self.reply_font)
|
| 231 |
reply_height = reply_username_h + reply_msg_h + 25
|
| 232 |
|
| 233 |
-
# Calculate
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
bubble_width = max(min_bubble_width, min(max_bubble_width, msg_width + bubble_padding * 2))
|
| 237 |
-
bubble_height = max(80, username_height + msg_height + bubble_padding * 2 + 50)
|
| 238 |
|
| 239 |
if reply_to:
|
| 240 |
bubble_height += reply_height + 15
|
| 241 |
|
| 242 |
-
#
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
[bubble_x, bubble_y, bubble_x + bubble_width, bubble_y + bubble_height],
|
| 263 |
-
radius=20,
|
| 264 |
-
fill=self.bubble_color,
|
| 265 |
-
shadow_offset=4
|
| 266 |
-
)
|
| 267 |
|
| 268 |
# Current position for content
|
| 269 |
content_y = bubble_y + bubble_padding
|
|
@@ -273,7 +243,7 @@ class TelegramChatGenerator:
|
|
| 273 |
reply_y = content_y
|
| 274 |
reply_bubble_height = max(30, reply_height + 10)
|
| 275 |
|
| 276 |
-
# Reply background
|
| 277 |
reply_bubble_x1 = bubble_x + 15
|
| 278 |
reply_bubble_x2 = bubble_x + bubble_width - 15
|
| 279 |
reply_bubble_y1 = reply_y
|
|
@@ -287,27 +257,27 @@ class TelegramChatGenerator:
|
|
| 287 |
fill=self.reply_bubble_color
|
| 288 |
)
|
| 289 |
|
| 290 |
-
# Blue accent line
|
| 291 |
if reply_bubble_height > 10:
|
| 292 |
-
draw.rectangle([bubble_x +
|
| 293 |
fill=self.reply_line_color)
|
| 294 |
|
| 295 |
# Reply content
|
| 296 |
-
reply_content_x = bubble_x +
|
| 297 |
-
reply_content_y = reply_y +
|
| 298 |
|
| 299 |
# Reply username
|
| 300 |
if reply_content_x < bubble_x + bubble_width - 20:
|
| 301 |
draw.text((reply_content_x, reply_content_y), replied_username,
|
| 302 |
fill=self.reply_line_color, font=self.reply_font)
|
| 303 |
-
reply_content_y +=
|
| 304 |
|
| 305 |
# Reply message
|
| 306 |
for line in wrapped_reply.split('\n'):
|
| 307 |
if reply_content_y < bubble_y + bubble_height - 30:
|
| 308 |
draw.text((reply_content_x, reply_content_y), line,
|
| 309 |
fill=self.reply_text_color, font=self.reply_font)
|
| 310 |
-
reply_content_y +=
|
| 311 |
|
| 312 |
content_y += reply_bubble_height + 15
|
| 313 |
|
|
@@ -315,30 +285,30 @@ class TelegramChatGenerator:
|
|
| 315 |
if content_y < bubble_y + bubble_height - 50:
|
| 316 |
draw.text((bubble_x + bubble_padding, content_y), username,
|
| 317 |
fill=self.username_color, font=self.username_font)
|
| 318 |
-
content_y += username_height +
|
| 319 |
|
| 320 |
# Main message text with better spacing
|
| 321 |
for line in wrapped_message.split('\n'):
|
| 322 |
if content_y < bubble_y + bubble_height - 40:
|
| 323 |
draw.text((bubble_x + bubble_padding, content_y), line,
|
| 324 |
fill=self.text_color, font=self.font)
|
| 325 |
-
content_y +=
|
| 326 |
|
| 327 |
-
# Add timestamp
|
| 328 |
current_time = datetime.now().strftime("%H:%M")
|
| 329 |
time_bbox = self.time_font.getbbox(current_time)
|
| 330 |
time_width = time_bbox[2] - time_bbox[0]
|
| 331 |
|
| 332 |
-
timestamp_x = bubble_x + bubble_width - time_width - bubble_padding
|
| 333 |
-
timestamp_y = bubble_y + bubble_height -
|
| 334 |
|
| 335 |
if timestamp_x > bubble_x and timestamp_y > bubble_y:
|
| 336 |
draw.text((timestamp_x, timestamp_y),
|
| 337 |
current_time, fill=self.time_color, font=self.time_font)
|
| 338 |
|
| 339 |
-
# Add
|
| 340 |
check_x = bubble_x + bubble_width - 25
|
| 341 |
-
check_y = bubble_y + bubble_height -
|
| 342 |
|
| 343 |
if check_x > bubble_x and check_y > bubble_y:
|
| 344 |
self.draw_checkmarks(draw, check_x, check_y)
|
|
|
|
| 10 |
|
| 11 |
class TelegramChatGenerator:
|
| 12 |
def __init__(self):
|
| 13 |
+
# Custom bubble colors
|
| 14 |
+
self.bubble_color = (31, 39, 69) # #1f2745
|
| 15 |
+
self.bubble_shadow = (20, 25, 45) # Darker shadow
|
| 16 |
+
self.reply_bubble_color = (25, 31, 55) # Darker for contrast
|
| 17 |
+
self.reply_line_color = (70, 130, 255) # Blue accent line
|
|
|
|
|
|
|
| 18 |
self.text_color = (255, 255, 255)
|
| 19 |
+
self.reply_text_color = (160, 170, 185)
|
| 20 |
self.username_color = (255, 255, 255)
|
| 21 |
+
self.time_color = (160, 170, 185)
|
| 22 |
|
| 23 |
# Load fonts with fallbacks
|
| 24 |
self.load_fonts()
|
|
|
|
| 29 |
def load_fonts(self):
|
| 30 |
"""Load fonts with multiple fallbacks"""
|
| 31 |
font_paths = [
|
| 32 |
+
"Roboto-Regular.ttf",
|
| 33 |
+
"Arial.ttf",
|
| 34 |
+
"Arial.ttf",
|
| 35 |
+
"arial.ttf",
|
| 36 |
+
"DejaVuSans.ttf"
|
| 37 |
]
|
| 38 |
|
| 39 |
try:
|
|
|
|
| 57 |
self.reply_font = ImageFont.load_default()
|
| 58 |
self.time_font = ImageFont.load_default()
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
def wrap_text(self, text, font, max_width):
|
| 61 |
"""Smart text wrapping based on pixel width"""
|
| 62 |
words = text.split()
|
|
|
|
| 83 |
|
| 84 |
return '\n'.join(lines)
|
| 85 |
|
| 86 |
+
def draw_telegram_bubble(self, draw, coords, radius, fill, shadow_offset=6):
|
| 87 |
+
"""Draw Telegram-style bubble with enhanced shadow"""
|
| 88 |
x1, y1, x2, y2 = coords
|
| 89 |
|
| 90 |
# Ensure coordinates are valid
|
|
|
|
| 96 |
if max_radius < 1:
|
| 97 |
max_radius = 1
|
| 98 |
|
| 99 |
+
# Draw multiple shadow layers for depth
|
| 100 |
+
for i in range(shadow_offset, 0, -1):
|
| 101 |
+
shadow_alpha = max(10, 40 - i * 5) # Varying opacity
|
| 102 |
+
shadow_coords = (x1 + i, y1 + i, x2 + i, y2 + i)
|
| 103 |
+
shadow_color = (0, 0, 0, shadow_alpha)
|
| 104 |
+
|
| 105 |
+
# Create shadow layer
|
| 106 |
+
shadow_layer = Image.new('RGBA', draw.im.size, (0, 0, 0, 0))
|
| 107 |
+
shadow_draw = ImageDraw.Draw(shadow_layer)
|
| 108 |
+
self.draw_rounded_rectangle(shadow_draw, shadow_coords, max_radius, shadow_color)
|
| 109 |
+
|
| 110 |
+
# Blur the shadow
|
| 111 |
+
shadow_layer = shadow_layer.filter(ImageFilter.GaussianBlur(radius=1))
|
| 112 |
+
draw.im.paste(shadow_layer, (0, 0), shadow_layer)
|
| 113 |
|
| 114 |
# Draw main bubble
|
| 115 |
self.draw_rounded_rectangle(draw, coords, max_radius, fill)
|
| 116 |
|
| 117 |
+
# Add subtle inner highlight
|
| 118 |
+
if y2 - y1 > 6:
|
| 119 |
+
highlight_coords = (x1 + 1, y1 + 1, x2 - 1, y1 + 3)
|
| 120 |
+
lighter_fill = tuple(min(255, c + 15) for c in fill)
|
| 121 |
+
self.draw_rounded_rectangle(draw, highlight_coords, max_radius - 1, lighter_fill)
|
| 122 |
|
| 123 |
def draw_rounded_rectangle(self, draw, coords, radius, fill):
|
| 124 |
"""Draw a perfect rounded rectangle with bounds checking"""
|
|
|
|
| 188 |
draw.ellipse([x, y, x + 2, y + 2], fill=color)
|
| 189 |
|
| 190 |
def generate_chat_bubble(self, message, username, reply_to=None, replied_username=None):
|
| 191 |
+
# Calculate bubble dimensions first to determine canvas size
|
| 192 |
+
padding = 40
|
| 193 |
+
bubble_padding = 25
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
|
| 195 |
+
# Estimate text dimensions
|
| 196 |
+
wrapped_message = self.wrap_text(message, self.font, 500) # Max width estimate
|
| 197 |
msg_width, msg_height = self.get_text_dimensions(wrapped_message, self.font)
|
|
|
|
| 198 |
username_width, username_height = self.get_text_dimensions(username, self.username_font)
|
| 199 |
|
| 200 |
+
# Handle reply dimensions
|
| 201 |
reply_height = 0
|
| 202 |
wrapped_reply = ""
|
| 203 |
if reply_to and replied_username:
|
|
|
|
| 204 |
truncated_reply = reply_to[:100] + "..." if len(reply_to) > 100 else reply_to
|
| 205 |
+
wrapped_reply = self.wrap_text(truncated_reply, self.reply_font, 450)
|
| 206 |
reply_username_w, reply_username_h = self.get_text_dimensions(replied_username, self.reply_font)
|
| 207 |
reply_msg_w, reply_msg_h = self.get_text_dimensions(wrapped_reply, self.reply_font)
|
| 208 |
reply_height = reply_username_h + reply_msg_h + 25
|
| 209 |
|
| 210 |
+
# Calculate bubble dimensions
|
| 211 |
+
bubble_width = max(300, min(600, msg_width + bubble_padding * 2 + 50))
|
| 212 |
+
bubble_height = max(100, username_height + msg_height + bubble_padding * 2 + 50)
|
|
|
|
|
|
|
| 213 |
|
| 214 |
if reply_to:
|
| 215 |
bubble_height += reply_height + 15
|
| 216 |
|
| 217 |
+
# Create canvas with transparent background, sized to fit bubble + padding
|
| 218 |
+
canvas_width = bubble_width + padding * 2
|
| 219 |
+
canvas_height = bubble_height + padding * 2
|
| 220 |
+
|
| 221 |
+
# Create image with transparent background
|
| 222 |
+
img = Image.new('RGBA', (canvas_width, canvas_height), (0, 0, 0, 0))
|
| 223 |
+
draw = ImageDraw.Draw(img)
|
| 224 |
+
|
| 225 |
+
# Position bubble in center of canvas
|
| 226 |
+
bubble_x = padding
|
| 227 |
+
bubble_y = padding
|
| 228 |
+
|
| 229 |
+
# Draw main bubble with enhanced shadow
|
| 230 |
+
self.draw_telegram_bubble(
|
| 231 |
+
draw,
|
| 232 |
+
[bubble_x, bubble_y, bubble_x + bubble_width, bubble_y + bubble_height],
|
| 233 |
+
radius=18,
|
| 234 |
+
fill=self.bubble_color,
|
| 235 |
+
shadow_offset=6
|
| 236 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
|
| 238 |
# Current position for content
|
| 239 |
content_y = bubble_y + bubble_padding
|
|
|
|
| 243 |
reply_y = content_y
|
| 244 |
reply_bubble_height = max(30, reply_height + 10)
|
| 245 |
|
| 246 |
+
# Reply background
|
| 247 |
reply_bubble_x1 = bubble_x + 15
|
| 248 |
reply_bubble_x2 = bubble_x + bubble_width - 15
|
| 249 |
reply_bubble_y1 = reply_y
|
|
|
|
| 257 |
fill=self.reply_bubble_color
|
| 258 |
)
|
| 259 |
|
| 260 |
+
# Blue accent line (thicker and more prominent)
|
| 261 |
if reply_bubble_height > 10:
|
| 262 |
+
draw.rectangle([bubble_x + 18, reply_y + 8, bubble_x + 22, reply_y + reply_bubble_height - 8],
|
| 263 |
fill=self.reply_line_color)
|
| 264 |
|
| 265 |
# Reply content
|
| 266 |
+
reply_content_x = bubble_x + 30
|
| 267 |
+
reply_content_y = reply_y + 12
|
| 268 |
|
| 269 |
# Reply username
|
| 270 |
if reply_content_x < bubble_x + bubble_width - 20:
|
| 271 |
draw.text((reply_content_x, reply_content_y), replied_username,
|
| 272 |
fill=self.reply_line_color, font=self.reply_font)
|
| 273 |
+
reply_content_y += 22
|
| 274 |
|
| 275 |
# Reply message
|
| 276 |
for line in wrapped_reply.split('\n'):
|
| 277 |
if reply_content_y < bubble_y + bubble_height - 30:
|
| 278 |
draw.text((reply_content_x, reply_content_y), line,
|
| 279 |
fill=self.reply_text_color, font=self.reply_font)
|
| 280 |
+
reply_content_y += 18
|
| 281 |
|
| 282 |
content_y += reply_bubble_height + 15
|
| 283 |
|
|
|
|
| 285 |
if content_y < bubble_y + bubble_height - 50:
|
| 286 |
draw.text((bubble_x + bubble_padding, content_y), username,
|
| 287 |
fill=self.username_color, font=self.username_font)
|
| 288 |
+
content_y += username_height + 12
|
| 289 |
|
| 290 |
# Main message text with better spacing
|
| 291 |
for line in wrapped_message.split('\n'):
|
| 292 |
if content_y < bubble_y + bubble_height - 40:
|
| 293 |
draw.text((bubble_x + bubble_padding, content_y), line,
|
| 294 |
fill=self.text_color, font=self.font)
|
| 295 |
+
content_y += 30
|
| 296 |
|
| 297 |
+
# Add timestamp in bottom right
|
| 298 |
current_time = datetime.now().strftime("%H:%M")
|
| 299 |
time_bbox = self.time_font.getbbox(current_time)
|
| 300 |
time_width = time_bbox[2] - time_bbox[0]
|
| 301 |
|
| 302 |
+
timestamp_x = bubble_x + bubble_width - time_width - bubble_padding - 30
|
| 303 |
+
timestamp_y = bubble_y + bubble_height - 28
|
| 304 |
|
| 305 |
if timestamp_x > bubble_x and timestamp_y > bubble_y:
|
| 306 |
draw.text((timestamp_x, timestamp_y),
|
| 307 |
current_time, fill=self.time_color, font=self.time_font)
|
| 308 |
|
| 309 |
+
# Add double checkmarks
|
| 310 |
check_x = bubble_x + bubble_width - 25
|
| 311 |
+
check_y = bubble_y + bubble_height - 25
|
| 312 |
|
| 313 |
if check_x > bubble_x and check_y > bubble_y:
|
| 314 |
self.draw_checkmarks(draw, check_x, check_y)
|