Spaces:
Running
Running
Update routers/image.py
Browse files- routers/image.py +57 -56
routers/image.py
CHANGED
|
@@ -664,7 +664,7 @@ def get_logo_path(slide_color: int) -> str:
|
|
| 664 |
else:
|
| 665 |
return "recurveblack.png" # Logo preta
|
| 666 |
|
| 667 |
-
def create_slide_canvas(image_url: str, slide: int, text: Optional[str] = None, color: int = 1) -> BytesIO:
|
| 668 |
"""
|
| 669 |
Cria um canvas para slides com imagem cortada baseada no parâmetro slide.
|
| 670 |
|
|
@@ -755,14 +755,17 @@ def create_slide_canvas(image_url: str, slide: int, text: Optional[str] = None,
|
|
| 755 |
# Colar imagem no canvas
|
| 756 |
canvas.paste(img_with_mask, (x_pos, y_pos), img_with_mask)
|
| 757 |
|
| 758 |
-
# Obter cor do texto baseada
|
| 759 |
-
text_color
|
|
|
|
|
|
|
|
|
|
| 760 |
|
| 761 |
# Adicionar texto se slide for 1 e text for fornecido
|
| 762 |
if slide == 1 and text and text.strip():
|
| 763 |
draw = ImageDraw.Draw(canvas)
|
| 764 |
# Renderizar texto com as especificações: X: 200, Y: 155, L: 815, MAX 5 linhas
|
| 765 |
-
render_slide_text(draw, text, x=200, y=155, max_width=815, max_lines=5, text_color=
|
| 766 |
|
| 767 |
# Adicionar texto se slide for 2 e text for fornecido
|
| 768 |
if slide == 2 and text and text.strip():
|
|
@@ -770,7 +773,7 @@ def create_slide_canvas(image_url: str, slide: int, text: Optional[str] = None,
|
|
| 770 |
# Renderizar texto com as especificações: X: 445, Y: 370, L: 585, MAX 19 linhas
|
| 771 |
# A imagem do slide 2 agora vai de Y: 370 a Y: 1150 (altura 780px)
|
| 772 |
# Para centralizar o texto na área da imagem, o centro é Y: 370 + (780/2) = 760
|
| 773 |
-
render_slide_text(draw, text, x=445, y=760, max_width=585, max_lines=19, center_vertical=True, text_color=
|
| 774 |
|
| 775 |
# Adicionar logo no canto inferior direito
|
| 776 |
try:
|
|
@@ -798,7 +801,7 @@ def create_slide_canvas(image_url: str, slide: int, text: Optional[str] = None,
|
|
| 798 |
buffer.seek(0)
|
| 799 |
return buffer
|
| 800 |
|
| 801 |
-
def create_quote_canvas(quote_text: str, role: Optional[str] = None, name: Optional[str] = None, color: int = 1, quote_background: bool = False, image_url: Optional[str] = None) -> BytesIO:
|
| 802 |
"""
|
| 803 |
Cria um canvas para quotes com fundo colorido, imagem quote.png e texto centralizado.
|
| 804 |
|
|
@@ -896,28 +899,26 @@ def create_quote_canvas(quote_text: str, role: Optional[str] = None, name: Optio
|
|
| 896 |
text_x = 180
|
| 897 |
text_y = canvas_height // 2 # Centralizar verticalmente
|
| 898 |
|
| 899 |
-
# Obter cor do texto baseada no
|
| 900 |
-
if
|
| 901 |
-
|
| 902 |
-
|
| 903 |
-
|
| 904 |
-
# Quando quote_background=False, usar cor baseada na cor do fundo
|
| 905 |
-
text_color = get_text_color(color)
|
| 906 |
|
| 907 |
# Adicionar imagem quote.png ou quoteblank.png acima do texto
|
| 908 |
try:
|
| 909 |
-
# Escolher imagem baseada no quote_background e
|
| 910 |
if quote_background:
|
| 911 |
# Quando quote_background=True, sempre usar quotered.png
|
| 912 |
quote_image_name = "quotered.png"
|
| 913 |
else:
|
| 914 |
-
# Quando quote_background=False, escolher baseado
|
| 915 |
-
if
|
| 916 |
-
#
|
| 917 |
-
quote_image_name = "
|
| 918 |
else:
|
| 919 |
-
#
|
| 920 |
-
quote_image_name = "quotered.png"
|
| 921 |
quote_img = Image.open(quote_image_name).convert("RGBA")
|
| 922 |
quote_width, quote_height = 87, 67.46
|
| 923 |
|
|
@@ -1043,11 +1044,11 @@ def create_quote_canvas(quote_text: str, role: Optional[str] = None, name: Optio
|
|
| 1043 |
quote_text_with_symbol = quote_text + '”'
|
| 1044 |
|
| 1045 |
# Renderizar texto com as especificações solicitadas
|
| 1046 |
-
render_quote_text(draw, quote_text_with_symbol, text_x, text_y, text_width,
|
| 1047 |
|
| 1048 |
# Renderizar cargo e nome se fornecidos
|
| 1049 |
if role or name:
|
| 1050 |
-
render_author_info(draw, canvas, role, name, text_x, text_y, text_width, quote_text_with_symbol,
|
| 1051 |
|
| 1052 |
buffer = BytesIO()
|
| 1053 |
canvas.convert("RGB").save(buffer, format="PNG")
|
|
@@ -1284,23 +1285,17 @@ def render_author_info(draw: ImageDraw.Draw, canvas: Image.Image, role: Optional
|
|
| 1284 |
except (OSError, IOError):
|
| 1285 |
font = ImageFont.load_default()
|
| 1286 |
|
| 1287 |
-
# Determinar cores baseadas no
|
| 1288 |
-
if
|
| 1289 |
-
#
|
| 1290 |
cargo_color = (255, 255, 255, 255)
|
|
|
|
| 1291 |
nome_color = (255, 255, 255, int(255 * 0.65))
|
| 1292 |
-
else:
|
| 1293 |
-
#
|
| 1294 |
-
|
| 1295 |
-
|
| 1296 |
-
|
| 1297 |
-
# Cor do nome: branco com 65% de opacidade
|
| 1298 |
-
nome_color = (255, 255, 255, int(255 * 0.65))
|
| 1299 |
-
else: # Se texto principal for preto
|
| 1300 |
-
# Cor do cargo: 140F09 (RGB: 20, 15, 9)
|
| 1301 |
-
cargo_color = (20, 15, 9, 255)
|
| 1302 |
-
# Cor do nome: 140F09 com 65% de opacidade
|
| 1303 |
-
nome_color = (20, 15, 9, int(255 * 0.65))
|
| 1304 |
|
| 1305 |
# Calcular altura do texto principal para posicionar o autor
|
| 1306 |
# Usar a mesma lógica do render_quote_text para calcular a altura
|
|
@@ -1513,7 +1508,7 @@ def render_author_info(draw: ImageDraw.Draw, canvas: Image.Image, role: Optional
|
|
| 1513 |
current_y += name_height + 5
|
| 1514 |
|
| 1515 |
def create_canvas(image_url: str, text: Optional[str] = None,
|
| 1516 |
-
text_position: str = "bottom", background: bool = False, color: int = 1, background_position: str = "bottom") -> BytesIO:
|
| 1517 |
# Dimensões fixas do Instagram
|
| 1518 |
width, height = 1080, 1350
|
| 1519 |
|
|
@@ -1598,19 +1593,21 @@ def create_canvas(image_url: str, text: Optional[str] = None,
|
|
| 1598 |
background_rect = Image.new("RGBA", (width, background_height), color=bg_color)
|
| 1599 |
canvas.paste(background_rect, (0, background_start_y))
|
| 1600 |
else:
|
| 1601 |
-
# Adicionar gradiente apenas se background=False
|
| 1602 |
# Lógica inteligente para posicionamento do gradiente
|
| 1603 |
|
| 1604 |
-
# Se
|
| 1605 |
-
if
|
| 1606 |
-
|
| 1607 |
-
|
| 1608 |
-
|
| 1609 |
-
|
| 1610 |
-
|
| 1611 |
-
#
|
| 1612 |
-
|
| 1613 |
-
|
|
|
|
|
|
|
| 1614 |
|
| 1615 |
# Adicionar logo na posição (X: 880, Y: 1260)
|
| 1616 |
try:
|
|
@@ -1662,14 +1659,17 @@ def create_canvas(image_url: str, text: Optional[str] = None,
|
|
| 1662 |
else:
|
| 1663 |
text_y = None
|
| 1664 |
|
| 1665 |
-
# Obter cor do texto baseada
|
| 1666 |
-
text_color
|
|
|
|
|
|
|
|
|
|
| 1667 |
|
| 1668 |
# Renderizar texto se fornecido
|
| 1669 |
if text and text.strip() and text_y is not None:
|
| 1670 |
# Alinhar pela base quando no bottom; pelo topo quando no top
|
| 1671 |
align_text_top = (text_position == "top")
|
| 1672 |
-
render_text(draw, text, x=78, y=text_y, max_width=924, max_lines=5, align_top=align_text_top, text_color=
|
| 1673 |
|
| 1674 |
buffer = BytesIO()
|
| 1675 |
canvas.convert("RGB").save(buffer, format="PNG")
|
|
@@ -1688,18 +1688,19 @@ def get_image(
|
|
| 1688 |
quote: Optional[str] = Query(None, description="Texto da quote para gerar poster com fundo colorido"),
|
| 1689 |
role: Optional[str] = Query(None, description="Position/role of the person who said the quote"),
|
| 1690 |
name: Optional[str] = Query(None, description="Name of the person who said the quote"),
|
| 1691 |
-
quote_background: bool = Query(False, description="Se True, centraliza texto, role/name e símbolo da quote")
|
|
|
|
| 1692 |
):
|
| 1693 |
try:
|
| 1694 |
# Se o parâmetro quote for fornecido, usar a funcionalidade de quote
|
| 1695 |
if quote is not None and quote.strip():
|
| 1696 |
-
buffer = create_quote_canvas(quote, role, name, color, quote_background, image_url)
|
| 1697 |
# Se o parâmetro slide for fornecido, usar a funcionalidade de slides
|
| 1698 |
elif slide is not None:
|
| 1699 |
-
buffer = create_slide_canvas(image_url, slide, text, color)
|
| 1700 |
else:
|
| 1701 |
# Usar funcionalidade normal de criação de imagem
|
| 1702 |
-
buffer = create_canvas(image_url, text, text_position, background, color, background_position)
|
| 1703 |
return StreamingResponse(buffer, media_type="image/png")
|
| 1704 |
except Exception as e:
|
| 1705 |
raise HTTPException(status_code=500, detail=f"Erro ao gerar imagem: {str(e)}")
|
|
|
|
| 664 |
else:
|
| 665 |
return "recurveblack.png" # Logo preta
|
| 666 |
|
| 667 |
+
def create_slide_canvas(image_url: str, slide: int, text: Optional[str] = None, color: int = 1, text_color: str = "white") -> BytesIO:
|
| 668 |
"""
|
| 669 |
Cria um canvas para slides com imagem cortada baseada no parâmetro slide.
|
| 670 |
|
|
|
|
| 755 |
# Colar imagem no canvas
|
| 756 |
canvas.paste(img_with_mask, (x_pos, y_pos), img_with_mask)
|
| 757 |
|
| 758 |
+
# Obter cor do texto baseada no parâmetro text_color
|
| 759 |
+
if text_color.lower() == "black":
|
| 760 |
+
text_color_tuple = (0, 0, 0, 255) # #000000
|
| 761 |
+
else: # white (padrão)
|
| 762 |
+
text_color_tuple = (255, 255, 255, 255) # branco
|
| 763 |
|
| 764 |
# Adicionar texto se slide for 1 e text for fornecido
|
| 765 |
if slide == 1 and text and text.strip():
|
| 766 |
draw = ImageDraw.Draw(canvas)
|
| 767 |
# Renderizar texto com as especificações: X: 200, Y: 155, L: 815, MAX 5 linhas
|
| 768 |
+
render_slide_text(draw, text, x=200, y=155, max_width=815, max_lines=5, text_color=text_color_tuple)
|
| 769 |
|
| 770 |
# Adicionar texto se slide for 2 e text for fornecido
|
| 771 |
if slide == 2 and text and text.strip():
|
|
|
|
| 773 |
# Renderizar texto com as especificações: X: 445, Y: 370, L: 585, MAX 19 linhas
|
| 774 |
# A imagem do slide 2 agora vai de Y: 370 a Y: 1150 (altura 780px)
|
| 775 |
# Para centralizar o texto na área da imagem, o centro é Y: 370 + (780/2) = 760
|
| 776 |
+
render_slide_text(draw, text, x=445, y=760, max_width=585, max_lines=19, center_vertical=True, text_color=text_color_tuple)
|
| 777 |
|
| 778 |
# Adicionar logo no canto inferior direito
|
| 779 |
try:
|
|
|
|
| 801 |
buffer.seek(0)
|
| 802 |
return buffer
|
| 803 |
|
| 804 |
+
def create_quote_canvas(quote_text: str, role: Optional[str] = None, name: Optional[str] = None, color: int = 1, quote_background: bool = False, image_url: Optional[str] = None, text_color: str = "white") -> BytesIO:
|
| 805 |
"""
|
| 806 |
Cria um canvas para quotes com fundo colorido, imagem quote.png e texto centralizado.
|
| 807 |
|
|
|
|
| 899 |
text_x = 180
|
| 900 |
text_y = canvas_height // 2 # Centralizar verticalmente
|
| 901 |
|
| 902 |
+
# Obter cor do texto baseada no parâmetro text_color
|
| 903 |
+
if text_color.lower() == "black":
|
| 904 |
+
text_color_tuple = (0, 0, 0, 255) # #000000
|
| 905 |
+
else: # white (padrão)
|
| 906 |
+
text_color_tuple = (255, 255, 255, 255) # branco
|
|
|
|
|
|
|
| 907 |
|
| 908 |
# Adicionar imagem quote.png ou quoteblank.png acima do texto
|
| 909 |
try:
|
| 910 |
+
# Escolher imagem baseada no quote_background e text_color
|
| 911 |
if quote_background:
|
| 912 |
# Quando quote_background=True, sempre usar quotered.png
|
| 913 |
quote_image_name = "quotered.png"
|
| 914 |
else:
|
| 915 |
+
# Quando quote_background=False, escolher baseado no text_color
|
| 916 |
+
if text_color.lower() == "black":
|
| 917 |
+
# Se text_color=black, usar quote.png (preta)
|
| 918 |
+
quote_image_name = "quote.png"
|
| 919 |
else:
|
| 920 |
+
# Se text_color=white, usar quotered.png (vermelha)
|
| 921 |
+
quote_image_name = "quotered.png"
|
| 922 |
quote_img = Image.open(quote_image_name).convert("RGBA")
|
| 923 |
quote_width, quote_height = 87, 67.46
|
| 924 |
|
|
|
|
| 1044 |
quote_text_with_symbol = quote_text + '”'
|
| 1045 |
|
| 1046 |
# Renderizar texto com as especificações solicitadas
|
| 1047 |
+
render_quote_text(draw, quote_text_with_symbol, text_x, text_y, text_width, text_color_tuple, quote_background)
|
| 1048 |
|
| 1049 |
# Renderizar cargo e nome se fornecidos
|
| 1050 |
if role or name:
|
| 1051 |
+
render_author_info(draw, canvas, role, name, text_x, text_y, text_width, quote_text_with_symbol, text_color_tuple, quote_background)
|
| 1052 |
|
| 1053 |
buffer = BytesIO()
|
| 1054 |
canvas.convert("RGB").save(buffer, format="PNG")
|
|
|
|
| 1285 |
except (OSError, IOError):
|
| 1286 |
font = ImageFont.load_default()
|
| 1287 |
|
| 1288 |
+
# Determinar cores baseadas no text_color
|
| 1289 |
+
if text_color == (255, 255, 255, 255): # Se texto principal for branco
|
| 1290 |
+
# Cor do cargo: branco
|
| 1291 |
cargo_color = (255, 255, 255, 255)
|
| 1292 |
+
# Cor do nome: branco com 65% de opacidade
|
| 1293 |
nome_color = (255, 255, 255, int(255 * 0.65))
|
| 1294 |
+
else: # Se texto principal for preto
|
| 1295 |
+
# Cor do cargo: 140F09 (RGB: 20, 15, 9)
|
| 1296 |
+
cargo_color = (20, 15, 9, 255)
|
| 1297 |
+
# Cor do nome: 140F09 com 65% de opacidade
|
| 1298 |
+
nome_color = (20, 15, 9, int(255 * 0.65))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1299 |
|
| 1300 |
# Calcular altura do texto principal para posicionar o autor
|
| 1301 |
# Usar a mesma lógica do render_quote_text para calcular a altura
|
|
|
|
| 1508 |
current_y += name_height + 5
|
| 1509 |
|
| 1510 |
def create_canvas(image_url: str, text: Optional[str] = None,
|
| 1511 |
+
text_position: str = "bottom", background: bool = False, color: int = 1, background_position: str = "bottom", text_color: str = "white") -> BytesIO:
|
| 1512 |
# Dimensões fixas do Instagram
|
| 1513 |
width, height = 1080, 1350
|
| 1514 |
|
|
|
|
| 1593 |
background_rect = Image.new("RGBA", (width, background_height), color=bg_color)
|
| 1594 |
canvas.paste(background_rect, (0, background_start_y))
|
| 1595 |
else:
|
| 1596 |
+
# Adicionar gradiente apenas se background=False e text_color não for black
|
| 1597 |
# Lógica inteligente para posicionamento do gradiente
|
| 1598 |
|
| 1599 |
+
# Se text_color=black, não adicionar gradientes pretos
|
| 1600 |
+
if text_color.lower() != "black":
|
| 1601 |
+
# Se text_position=bottom, sempre gradiente embaixo (obrigatório)
|
| 1602 |
+
if text_position == "bottom":
|
| 1603 |
+
gradient_overlay = create_gradient_overlay(width, height, "bottom")
|
| 1604 |
+
canvas = Image.alpha_composite(canvas, gradient_overlay)
|
| 1605 |
+
|
| 1606 |
+
# Se text_position=top
|
| 1607 |
+
elif text_position == "top":
|
| 1608 |
+
# Sempre gradiente em cima quando texto está no topo
|
| 1609 |
+
gradient_overlay = create_gradient_overlay(width, height, "top")
|
| 1610 |
+
canvas = Image.alpha_composite(canvas, gradient_overlay)
|
| 1611 |
|
| 1612 |
# Adicionar logo na posição (X: 880, Y: 1260)
|
| 1613 |
try:
|
|
|
|
| 1659 |
else:
|
| 1660 |
text_y = None
|
| 1661 |
|
| 1662 |
+
# Obter cor do texto baseada no parâmetro text_color
|
| 1663 |
+
if text_color.lower() == "black":
|
| 1664 |
+
text_color_tuple = (0, 0, 0, 255) # #000000
|
| 1665 |
+
else: # white (padrão)
|
| 1666 |
+
text_color_tuple = (255, 255, 255, 255) # branco
|
| 1667 |
|
| 1668 |
# Renderizar texto se fornecido
|
| 1669 |
if text and text.strip() and text_y is not None:
|
| 1670 |
# Alinhar pela base quando no bottom; pelo topo quando no top
|
| 1671 |
align_text_top = (text_position == "top")
|
| 1672 |
+
render_text(draw, text, x=78, y=text_y, max_width=924, max_lines=5, align_top=align_text_top, text_color=text_color_tuple)
|
| 1673 |
|
| 1674 |
buffer = BytesIO()
|
| 1675 |
canvas.convert("RGB").save(buffer, format="PNG")
|
|
|
|
| 1688 |
quote: Optional[str] = Query(None, description="Texto da quote para gerar poster com fundo colorido"),
|
| 1689 |
role: Optional[str] = Query(None, description="Position/role of the person who said the quote"),
|
| 1690 |
name: Optional[str] = Query(None, description="Name of the person who said the quote"),
|
| 1691 |
+
quote_background: bool = Query(False, description="Se True, centraliza texto, role/name e símbolo da quote"),
|
| 1692 |
+
text_color: str = Query("white", description="Cor do texto: 'white' ou 'black'")
|
| 1693 |
):
|
| 1694 |
try:
|
| 1695 |
# Se o parâmetro quote for fornecido, usar a funcionalidade de quote
|
| 1696 |
if quote is not None and quote.strip():
|
| 1697 |
+
buffer = create_quote_canvas(quote, role, name, color, quote_background, image_url, text_color)
|
| 1698 |
# Se o parâmetro slide for fornecido, usar a funcionalidade de slides
|
| 1699 |
elif slide is not None:
|
| 1700 |
+
buffer = create_slide_canvas(image_url, slide, text, color, text_color)
|
| 1701 |
else:
|
| 1702 |
# Usar funcionalidade normal de criação de imagem
|
| 1703 |
+
buffer = create_canvas(image_url, text, text_position, background, color, background_position, text_color)
|
| 1704 |
return StreamingResponse(buffer, media_type="image/png")
|
| 1705 |
except Exception as e:
|
| 1706 |
raise HTTPException(status_code=500, detail=f"Erro ao gerar imagem: {str(e)}")
|