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)}")