|
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 |
|
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)) |
|
|
|
|
|
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) |
|
|
|
|
|
icon = Image.open(icon_path).convert("RGBA") |
|
icon_ratio = icon.width / icon.height |
|
icon_height = 28 |
|
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)) |
|
|
|
|
|
icon_resized = icon.resize((icon_width, icon_height)) |
|
|
|
|
|
icon_y = y |
|
text_y = y |
|
|
|
|
|
base_image.paste(icon_resized, (x, icon_y), icon_resized) |
|
|
|
|
|
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)) |
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
text_y = bar_y + (bar_height - 28) // 2 |
|
|
|
|
|
font_path_semi = "fonts/Montserrat-SemiBold.ttf" |
|
|
|
if birth: |
|
|
|
|
|
|
|
icon_width_star = 30 |
|
|
|
text_width = draw.textlength(birth, font=ImageFont.truetype(font_path_semi, 24)) |
|
icon_x = bar_x - 12 - int(text_width) - 13 - icon_width_star |
|
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 |
|
|
|
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)}") |