File size: 8,410 Bytes
c1e1051 561450d c1e1051 6a6eb84 c1e1051 6a6eb84 58c779f c1e1051 42be11d c1e1051 42be11d c1e1051 42be11d c1e1051 42be11d 58c779f 42be11d 58c779f 42be11d 58c779f 561450d 4539dc7 561450d f031ee2 4539dc7 29db2e8 f284614 f031ee2 4539dc7 29db2e8 f284614 f031ee2 29c907a d6548cc f284614 f031ee2 29c907a c1e1051 4539dc7 c1e1051 42be11d 58c779f 42be11d 58c779f 29db2e8 f040bc8 12ff0d5 f040bc8 29db2e8 4539dc7 29db2e8 4539dc7 f284614 f031ee2 f284614 29c907a f284614 f031ee2 29c907a f284614 13a99a0 f284614 f031ee2 29c907a f284614 561450d 42be11d 12ff0d5 42be11d 58c779f b7337b6 58c779f 561450d f031ee2 f284614 c1e1051 29c907a c1e1051 f040bc8 |
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 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
from fastapi import APIRouter, Query, HTTPException
from fastapi.responses import StreamingResponse
from PIL import Image, ImageDraw, ImageEnhance, ImageFont
from io import BytesIO
import requests
from typing import Optional
router = APIRouter()
def download_image_from_url(url: str) -> Image.Image:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
}
response = requests.get(url, headers=headers)
if response.status_code != 200:
raise HTTPException(status_code=400, detail=f"Imagem não pôde ser baixada. Código {response.status_code}")
try:
return Image.open(BytesIO(response.content)).convert("RGB")
except Exception as e:
raise HTTPException(status_code=400, detail=f"Erro ao abrir imagem: {str(e)}")
def resize_and_crop_to_fill(img: Image.Image, target_width: int, target_height: int) -> Image.Image:
img_ratio = img.width / img.height
target_ratio = target_width / target_height
if img_ratio > target_ratio:
scale_height = target_height
scale_width = int(scale_height * img_ratio)
else:
scale_width = target_width
scale_height = int(scale_width / img_ratio)
img_resized = img.resize((scale_width, scale_height), Image.LANCZOS)
left = (scale_width - target_width) // 2
top = (scale_height - target_height) // 2
right = left + target_width
bottom = top + target_height
return img_resized.crop((left, top, right, bottom))
def create_linear_black_gradient(width: int, height: int) -> Image.Image:
gradient = Image.new("RGBA", (width, height), color=(0, 0, 0, 0))
draw = ImageDraw.Draw(gradient)
for y in range(height):
ratio = y / height
if ratio <= 0.8558:
alpha = int(255 * (ratio / 0.8558))
else:
alpha = 255
draw.line([(0, y), (width, y)], fill=(4, 4, 4, alpha))
return gradient
def draw_single_line_text_centered(draw: ImageDraw.Draw, text: str, max_width: int, center_x: int, y: int, font_path: str):
max_font_size = 70
min_font_size = 20
for font_size in range(max_font_size, min_font_size - 1, -1):
try:
font = ImageFont.truetype(font_path, font_size)
except Exception:
continue
text_width = draw.textlength(text, font=font)
if text_width <= max_width:
spacing = -0.03 * font_size # -3% tracking
draw.text(
(center_x - text_width / 2, y),
text,
font=font,
fill=(255, 255, 255),
spacing=spacing
)
break
def create_gradient_bar(width: int, height: int, radius: int = 50) -> Image.Image:
gradient = Image.new("RGBA", (width, height))
draw = ImageDraw.Draw(gradient)
for x in range(width):
ratio = x / (width - 1)
if ratio < 0.5:
r = int(0xFF * (1 - ratio * 2) + 0xF4 * ratio * 2)
g = int(0x82 * (1 - ratio * 2) + 0x0A * ratio * 2)
b = int(0x26 * (1 - ratio * 2) + 0xFF * ratio * 2)
else:
ratio2 = (ratio - 0.5) * 2
r = int(0xF4 * (1 - ratio2) + 0x03 * ratio2)
g = int(0x0A * (1 - ratio2) + 0xD9 * ratio2)
b = int(0xFF * (1 - ratio2) + 0xE3 * ratio2)
draw.line([(x, 0), (x, height)], fill=(r, g, b, 255))
# Bordas arredondadas
mask = Image.new("L", (width, height), 0)
mask_draw = ImageDraw.Draw(mask)
mask_draw.rounded_rectangle([(0, 0), (width, height)], radius=radius, fill=255)
gradient.putalpha(mask)
return gradient
def draw_year_with_icon(draw: ImageDraw.Draw, base_image: Image.Image, text: str, icon_path: str, x: int, y: int, font_path: str, icon_width: int):
try:
font = ImageFont.truetype(font_path, 24)
except Exception:
font = ImageFont.load_default()
spacing_between_icon_and_text = 13
text_width = draw.textlength(text, font=font)
# Carregar e redimensionar o ícone mantendo proporção da altura igual a altura do texto (aprox 28px)
icon = Image.open(icon_path).convert("RGBA")
icon_ratio = icon.width / icon.height
icon_height = 28 # altura aproximada para alinhamento com o texto (altura da linha)
icon_resized = icon.resize((icon_width, int(icon_width / icon_ratio))) if icon_width < icon.height else icon.resize((int(icon_height * icon_ratio), icon_height))
# Ajuste: usar icon_height fixa para alinhamento vertical (ícones diferentes podem ter alturas diferentes)
icon_resized = icon.resize((icon_width, icon_height))
# Y para alinhar verticalmente ícone e texto
icon_y = y
text_y = y
# Colar ícone na base_image
base_image.paste(icon_resized, (x, icon_y), icon_resized)
# Desenhar texto após o ícone + espaçamento
text_x = x + icon_width + spacing_between_icon_and_text
draw.text((text_x, text_y), text, font=font, fill=(255, 255, 255), spacing=0)
def create_canvas(image_url: Optional[str], name: Optional[str], birth: Optional[str], death: Optional[str]) -> BytesIO:
width, height = 1080, 1350
canvas = Image.new("RGBA", (width, height), (0, 0, 0, 0)) # Fundo transparente
if image_url:
img = download_image_from_url(image_url)
img_bw = ImageEnhance.Color(img).enhance(0.0).convert("RGBA")
filled_img = resize_and_crop_to_fill(img_bw, width, height)
canvas.paste(filled_img, (0, 0))
gradient_overlay = create_linear_black_gradient(width, height)
canvas = Image.alpha_composite(canvas, gradient_overlay)
# Logo
try:
logo = Image.open("recurve.png").convert("RGBA")
logo_resized = logo.resize((int(148.36), int(27.9)))
canvas.paste(logo_resized, (int(66), int(74.92)), logo_resized)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao carregar a logo: {e}")
draw = ImageDraw.Draw(canvas)
# Título
if name:
name_upper = name.upper()
font_path = "fonts/Montserrat-Bold.ttf"
max_width = 947
center_x = width // 2
y = 1100
draw_single_line_text_centered(draw, name_upper, max_width, center_x, y, font_path)
# Barra gradiente
bar_x = 480
bar_y = int(1213)
bar_width = 110
bar_height = int(6.82)
bar = create_gradient_bar(bar_width, bar_height, radius=50)
canvas.paste(bar, (bar_x, bar_y), bar)
# Altura da linha do texto para alinhamento vertical dos ícones e textos
text_y = bar_y + (bar_height - 28) // 2
# Desenhar anos com ícones
font_path_semi = "fonts/Montserrat-SemiBold.ttf"
if birth:
# calcular largura do texto para posicionar o ícone e o texto lado a lado
# posicionar a estrela + texto, sendo o x do texto após o ícone + espaçamento
# A estrela deve ficar 12px à esquerda da barra
icon_width_star = 30
# Ajusta x do ícone para ficar a 12px à esquerda do início da barra menos largura do texto e espaço do ícone+texto
text_width = draw.textlength(birth, font=ImageFont.truetype(font_path_semi, 24))
icon_x = bar_x - 12 - int(text_width) - 13 - icon_width_star # 8px espaço ícone-texto
draw_year_with_icon(draw, canvas, birth, "star.png", icon_x, text_y, font_path_semi, icon_width_star)
if death:
icon_width_cross = 18
# Ícone + 8px + texto começa a 12px depois da barra
icon_x = bar_x + bar_width + 12
draw_year_with_icon(draw, canvas, death, "cross.png", icon_x, text_y, font_path_semi, icon_width_cross)
buffer = BytesIO()
canvas.save(buffer, format="PNG")
buffer.seek(0)
return buffer
@router.get("/cover/memoriam")
def get_memoriam_image(
image_url: Optional[str] = Query(None, description="URL da imagem de fundo"),
name: Optional[str] = Query(None, description="Nome (será exibido em maiúsculas)"),
birth: Optional[str] = Query(None, description="Ano de nascimento (ex: 1943)"),
death: Optional[str] = Query(None, description="Ano de falecimento (ex: 2023)")
):
try:
buffer = create_canvas(image_url, name, birth, death)
return StreamingResponse(buffer, media_type="image/png")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro ao gerar imagem: {str(e)}") |