newapi / routers /memoriam.py
habulaj's picture
Update routers/memoriam.py
b7337b6 verified
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:
response = requests.get(url)
if response.status_code != 200:
raise HTTPException(status_code=400, detail="Imagem não pôde ser baixada.")
return Image.open(BytesIO(response.content)).convert("RGB")
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)}")