habulaj commited on
Commit
c1e1051
·
verified ·
1 Parent(s): 830c1c3

Create memoriam.py

Browse files
Files changed (1) hide show
  1. routers/memoriam.py +216 -0
routers/memoriam.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Query, HTTPException
2
+ from fastapi.responses import StreamingResponse
3
+ from PIL import Image, ImageDraw, ImageFont
4
+ from io import BytesIO
5
+ import requests
6
+ from typing import Optional
7
+ import os
8
+
9
+ router = APIRouter()
10
+
11
+ def download_image_from_url(url: str) -> Image.Image:
12
+ response = requests.get(url)
13
+ if response.status_code != 200:
14
+ raise HTTPException(status_code=400, detail="Imagem não pôde ser baixada.")
15
+ return Image.open(BytesIO(response.content)).convert("RGBA")
16
+
17
+ def resize_and_crop_to_fill(img: Image.Image, target_width: int, target_height: int) -> Image.Image:
18
+ img_ratio = img.width / img.height
19
+ target_ratio = target_width / target_height
20
+
21
+ if img_ratio > target_ratio:
22
+ scale_height = target_height
23
+ scale_width = int(scale_height * img_ratio)
24
+ else:
25
+ scale_width = target_width
26
+ scale_height = int(scale_width / img_ratio)
27
+
28
+ img_resized = img.resize((scale_width, scale_height), Image.LANCZOS)
29
+
30
+ left = (scale_width - target_width) // 2
31
+ top = (scale_height - target_height) // 2
32
+ right = left + target_width
33
+ bottom = top + target_height
34
+
35
+ return img_resized.crop((left, top, right, bottom))
36
+
37
+ def create_black_gradient_overlay(width: int, height: int) -> Image.Image:
38
+ gradient = Image.new("RGBA", (width, height))
39
+ draw = ImageDraw.Draw(gradient)
40
+ for y in range(height):
41
+ opacity = int(255 * (y / height))
42
+ draw.line([(0, y), (width, y)], fill=(4, 4, 4, opacity))
43
+ return gradient
44
+
45
+ def wrap_text(text: str, font: ImageFont.FreeTypeFont, max_width: int, draw: ImageDraw.Draw) -> list[str]:
46
+ words = text.split()
47
+ lines = []
48
+ current_line = ""
49
+
50
+ for word in words:
51
+ test_line = f"{current_line} {word}".strip()
52
+ if draw.textlength(test_line, font=font) <= max_width:
53
+ current_line = test_line
54
+ else:
55
+ if current_line:
56
+ lines.append(current_line)
57
+ current_line = word
58
+ if current_line:
59
+ lines.append(current_line)
60
+ return lines
61
+
62
+ def get_responsive_font_and_lines(text: str, font_path: str, max_width: int, max_lines: int = 3,
63
+ max_font_size: int = 50, min_font_size: int = 20) -> tuple[ImageFont.FreeTypeFont, list[str], int]:
64
+ """
65
+ Retorna a fonte e linhas ajustadas para caber no número máximo de linhas.
66
+
67
+ Args:
68
+ text: Texto a ser renderizado
69
+ font_path: Caminho para o arquivo de fonte
70
+ max_width: Largura máxima disponível
71
+ max_lines: Número máximo de linhas permitidas
72
+ max_font_size: Tamanho máximo da fonte
73
+ min_font_size: Tamanho mínimo da fonte
74
+
75
+ Returns:
76
+ tuple: (fonte, linhas, tamanho_da_fonte)
77
+ """
78
+ # Criar um draw temporário para calcular tamanhos
79
+ temp_img = Image.new("RGB", (1, 1))
80
+ temp_draw = ImageDraw.Draw(temp_img)
81
+
82
+ current_font_size = max_font_size
83
+
84
+ while current_font_size >= min_font_size:
85
+ try:
86
+ font = ImageFont.truetype(font_path, current_font_size)
87
+ except Exception:
88
+ # Se não conseguir carregar a fonte, usar fonte padrão
89
+ font = ImageFont.load_default()
90
+
91
+ lines = wrap_text(text, font, max_width, temp_draw)
92
+
93
+ # Se o texto cabe no número máximo de linhas, usar este tamanho
94
+ if len(lines) <= max_lines:
95
+ return font, lines, current_font_size
96
+
97
+ # Diminuir o tamanho da fonte
98
+ current_font_size -= 1
99
+
100
+ # Se chegou ao tamanho mínimo, usar mesmo assim
101
+ try:
102
+ font = ImageFont.truetype(font_path, min_font_size)
103
+ except Exception:
104
+ font = ImageFont.load_default()
105
+
106
+ lines = wrap_text(text, font, max_width, temp_draw)
107
+ return font, lines, min_font_size
108
+
109
+ def create_gradient_bar(width: int, height: int) -> Image.Image:
110
+ gradient = Image.new("RGBA", (width, height))
111
+ draw = ImageDraw.Draw(gradient)
112
+
113
+ for x in range(width):
114
+ ratio = x / (width - 1)
115
+ if ratio < 0.5:
116
+ r = int(0xFF * (1 - ratio * 2) + 0xF4 * ratio * 2)
117
+ g = int(0x82 * (1 - ratio * 2) + 0x0A * ratio * 2)
118
+ b = int(0x26 * (1 - ratio * 2) + 0xFF * ratio * 2)
119
+ else:
120
+ ratio2 = (ratio - 0.5) * 2
121
+ r = int(0xF4 * (1 - ratio2) + 0x03 * ratio2)
122
+ g = int(0x0A * (1 - ratio2) + 0xD9 * ratio2)
123
+ b = int(0xFF * (1 - ratio2) + 0xE3 * ratio2)
124
+ draw.line([(x, 0), (x, height)], fill=(r, g, b, 255))
125
+
126
+ radius = 44
127
+ mask = Image.new("L", (width, height), 0)
128
+ mask_draw = ImageDraw.Draw(mask)
129
+ mask_draw.rounded_rectangle([(0, 0), (width, height)], radius=radius, fill=255)
130
+
131
+ gradient.putalpha(mask)
132
+ return gradient
133
+
134
+ def create_canvas(image_url: Optional[str], headline: Optional[str]) -> BytesIO:
135
+ width, height = 1080, 1350
136
+ padding_x = 60
137
+ bottom_padding = 80
138
+ max_width = width - 2 * padding_x
139
+
140
+ canvas = Image.new("RGBA", (width, height), color=(255, 255, 255, 255))
141
+
142
+ if image_url:
143
+ img = download_image_from_url(image_url)
144
+ filled_img = resize_and_crop_to_fill(img, width, height)
145
+ canvas.paste(filled_img, (0, 0))
146
+
147
+ gradient_overlay = create_black_gradient_overlay(width, height)
148
+ canvas = Image.alpha_composite(canvas, gradient_overlay)
149
+
150
+ if headline:
151
+ draw = ImageDraw.Draw(canvas)
152
+ font_path = "fonts/Montserrat-Bold.ttf"
153
+
154
+ try:
155
+ # Obter fonte e linhas responsivas
156
+ font, lines, font_size = get_responsive_font_and_lines(
157
+ headline, font_path, max_width, max_lines=3,
158
+ max_font_size=50, min_font_size=20
159
+ )
160
+
161
+ # Calcular line_height baseado no tamanho da fonte atual
162
+ line_height = int(font_size * 1.22) # Proporção similar à original (61/50 = 1.22)
163
+
164
+ except Exception as e:
165
+ raise HTTPException(status_code=500, detail=f"Erro ao processar a fonte: {e}")
166
+
167
+ total_text_height = len(lines) * line_height
168
+ start_y = height - bottom_padding - total_text_height
169
+
170
+ # Posições dos elementos acima do texto
171
+ bar_width = 375
172
+ bar_height = 6
173
+ space_bar_to_text = 16
174
+ space_logo_to_bar = 20
175
+ logo_width, logo_height = 162, 30
176
+
177
+ # Y do degradê
178
+ bar_y = start_y - space_bar_to_text - bar_height
179
+ # Y da logo
180
+ logo_y = bar_y - space_logo_to_bar - logo_height
181
+
182
+ # Garantir que a logo não ultrapasse o topo
183
+ if logo_y > 0:
184
+ # Adiciona logo
185
+ try:
186
+ logo_path = "recurve.png"
187
+ logo = Image.open(logo_path).convert("RGBA")
188
+ logo_resized = logo.resize((logo_width, logo_height))
189
+ canvas.paste(logo_resized, (padding_x, logo_y), logo_resized)
190
+ except Exception as e:
191
+ raise HTTPException(status_code=500, detail=f"Erro ao carregar a logo: {e}")
192
+
193
+ # Adiciona barra colorida
194
+ bar = create_gradient_bar(bar_width, bar_height)
195
+ canvas.paste(bar, (padding_x, bar_y), bar)
196
+
197
+ # Adiciona texto
198
+ for i, line in enumerate(lines):
199
+ y = start_y + i * line_height
200
+ draw.text((padding_x, y), line, font=font, fill=(255, 255, 255))
201
+
202
+ buffer = BytesIO()
203
+ canvas.convert("RGB").save(buffer, format="PNG")
204
+ buffer.seek(0)
205
+ return buffer
206
+
207
+ @router.get("/news")
208
+ def get_news_image(
209
+ image_url: Optional[str] = Query(None, description="URL da imagem para preencher o fundo"),
210
+ headline: Optional[str] = Query(None, description="Texto do título (opcional)")
211
+ ):
212
+ try:
213
+ buffer = create_canvas(image_url, headline)
214
+ return StreamingResponse(buffer, media_type="image/png")
215
+ except Exception as e:
216
+ raise HTTPException(status_code=500, detail=f"Erro ao gerar imagem: {str(e)}")