Logo oficial: a versão final do autor (guara.png), sem recorte nosso

O autor gerou a logo já sem fundo; ela passa a ser a arte oficial, usada em qualquer tema, e não é
mais alterada. scripts/processar-logo.py (que tirava o fundo) e a arte original com fundo saem;
scripts/gerar-logo.py só deriva guara-s.png (barra lateral e favicon) e o ícone do app, e os ícones
do Tauri são regenerados.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Sonnet 5.5 committed 2026-10-23 12:05:58 -03:00
1 parent 0a9622fb96
commit 05340bd8d2
56 files changed
+50 -182

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 KiB

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 KiB

After

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

After

Width:  |  Height:  |  Size: 870 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""Gera, a partir da logo oficial, os arquivos derivados usados no app e nos ícones.
A logo oficial é apps/gui/ui/guara.png (RGBA, fundo transparente, serve aos temas claro e escuro).
Ela não é alterada aqui. Saída:
apps/gui/ui/guara-s.png 128 px, recortada e quadrada (barra lateral e favicon)
apps/gui/src-tauri/icons/source.png 1024 px, a arte sobre o fundo escuro com cantos arredondados;
o CI e `npx @tauri-apps/cli icon` geram o resto dos ícones.
Uso: python3 scripts/gerar-logo.py
"""
import os
from PIL import Image, ImageDraw
RAIZ = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
UI = os.path.join(RAIZ, "apps/gui/ui")
ICONES = os.path.join(RAIZ, "apps/gui/src-tauri/icons")
FUNDO_ICONE = (10, 16, 28) # o azul-noite do fundo da arte original
def recortar_quadrado(im, margem=0.04):
"""Recorta no que é visível (opacidade > 16, ignora resquícios) e centraliza num quadrado."""
box = im.getchannel("A").point(lambda v: 255 if v > 16 else 0).getbbox()
im = im.crop(box)
w, h = im.size
lado = round(max(w, h) * (1 + 2 * margem))
quadro = Image.new("RGBA", (lado, lado), (0, 0, 0, 0))
quadro.paste(im, ((lado - w) // 2, (lado - h) // 2), im)
return quadro
def main():
logo = recortar_quadrado(Image.open(os.path.join(UI, "guara.png")).convert("RGBA"))
logo.resize((128, 128), Image.LANCZOS).save(os.path.join(UI, "guara-s.png"), optimize=True)
print("guara-s.png")
lado = 1024
fundo = Image.new("RGBA", (lado, lado), FUNDO_ICONE + (255,))
arte = logo.resize((round(lado * 0.82),) * 2, Image.LANCZOS)
fundo.paste(arte, ((lado - arte.width) // 2, (lado - arte.height) // 2), arte)
mascara = Image.new("L", (lado, lado), 0)
ImageDraw.Draw(mascara).rounded_rectangle((0, 0, lado - 1, lado - 1), radius=round(lado * 0.2237), fill=255)
icone = Image.new("RGBA", (lado, lado), (0, 0, 0, 0))
icone.paste(fundo, (0, 0), mascara)
icone.save(os.path.join(ICONES, "source.png"), optimize=True)
print("icons/source.png")
if __name__ == "__main__":
main()
-182
View File
@@ -1,182 +0,0 @@
#!/usr/bin/env python3
"""Tira o fundo da logo do Guará e gera os arquivos usados no app, no README e nos ícones.
Entrada (arte original, fundo sólido escuro): assets/logo/guara-fonte.png.
Saída: apps/gui/ui/guara.png (768 px, fundo transparente, recortada e quadrada), guara-s.png
(128 px, para a barra lateral e o favicon) e apps/gui/src-tauri/icons/source.png (ícone do app,
sobre o fundo escuro da arte; o CI gera o resto com `tauri icon`). Uma logo só serve aos temas
claro e escuro: o laranja lê bem nos dois.
O fundo é uma cor sólida: sai o que é parecido com ela e está ligado à borda da imagem (sombras
escuras dentro do desenho ficam). Na faixa de 3 px ao redor, a cor do desenho é estimada pelos
vizinhos internos e a transparência vem de onde o pixel cai entre o fundo e essa cor, o que evita
halo escuro ou claro.
Uso: python3 scripts/processar-logo.py
"""
import os
from PIL import Image
RAIZ = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
UI = os.path.join(RAIZ, "apps/gui/ui")
ICONES = os.path.join(RAIZ, "apps/gui/src-tauri/icons")
FONTE = "assets/logo/guara-fonte.png"
FUNDO = (10, 16, 28) # cor do fundo sólido da arte original
LIMIAR = 9.0 # distância que ainda conta como fundo certo
def tirar_fundo(path, bg, d0, _d1=None):
"""Remove o fundo conectado à borda e refina a faixa de 3 px ao redor do desenho.
1. Fundo certo: pixels a menos de `d0` da cor do fundo, ligados à borda da imagem ou formando
um bolsão grande (as frestas entre as pernas). Sombras escuras do desenho, que passam de
`d0`, não são tocadas.
2. Faixa de 3 px ao redor do fundo (a borda suavizada da arte): a cor verdadeira do desenho é
estimada pelos vizinhos internos mais próximos, e a opacidade sai da projeção do pixel
entre a cor do fundo e essa cor. Sem halo escuro ou claro.
3. O resto fica como está, opaco.
"""
from collections import deque
im = Image.open(path).convert("RGB")
w, h = im.size
px = im.load()
br, bgc, bb = bg
d0sq = d0 * d0
def perto(x, y):
r, g, b = px[x, y]
return (r - br) ** 2 + (g - bgc) ** 2 + (b - bb) ** 2 < d0sq
# 1) fundo certo: flood fill a partir da borda
fundo = bytearray(w * h)
visto = bytearray(w * h)
fila = deque()
for x in range(w):
for y in (0, h - 1):
if perto(x, y) and not fundo[y * w + x]:
fundo[y * w + x] = 1
fila.append((x, y))
for y in range(h):
for x in (0, w - 1):
if perto(x, y) and not fundo[y * w + x]:
fundo[y * w + x] = 1
fila.append((x, y))
while fila:
x, y = fila.popleft()
for nx, ny in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
if 0 <= nx < w and 0 <= ny < h and not fundo[ny * w + nx] and perto(nx, ny):
fundo[ny * w + nx] = 1
fila.append((nx, ny))
# 1b) bolsões de fundo dentro do desenho (frestas entre as pernas, por exemplo): regiões grandes
# com a cor do fundo que não tocam a borda. Manchas pequenas (brilho do olho) ficam.
for i0 in range(w * h):
if fundo[i0] or visto[i0]:
continue
x0, y0 = i0 % w, i0 // w
if not perto(x0, y0):
continue
comp = [(x0, y0)]
visto[i0] = 1
k = 0
while k < len(comp):
x, y = comp[k]
k += 1
for nx, ny in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
if 0 <= nx < w and 0 <= ny < h and not visto[ny * w + nx] and not fundo[ny * w + nx] and perto(nx, ny):
visto[ny * w + nx] = 1
comp.append((nx, ny))
if len(comp) >= 60:
for x, y in comp:
fundo[y * w + x] = 1
# 2) distância (em passos de 8 vizinhos) até o fundo, só até 3
dist = bytearray([255]) * (w * h)
fila = deque()
for i in range(w * h):
if fundo[i]:
dist[i] = 0
fila.append((i % w, i // w))
while fila:
x, y = fila.popleft()
d = dist[y * w + x]
if d >= 3:
continue
for dx in (-1, 0, 1):
for dy in (-1, 0, 1):
nx, ny = x + dx, y + dy
if 0 <= nx < w and 0 <= ny < h and dist[ny * w + nx] == 255:
dist[ny * w + nx] = d + 1
fila.append((nx, ny))
out = Image.new("RGBA", (w, h))
po = out.load()
R = 6
for y in range(h):
for x in range(w):
d = dist[y * w + x]
if d == 0:
po[x, y] = (0, 0, 0, 0)
elif d == 255:
r, g, b = px[x, y]
po[x, y] = (r, g, b, 255)
else: # faixa da borda
soma = [0, 0, 0]
n = 0
melhor = 99
for yy in range(max(0, y - R), min(h, y + R + 1)):
for xx in range(max(0, x - R), min(w, x + R + 1)):
if dist[yy * w + xx] == 255:
q = (xx - x) ** 2 + (yy - y) ** 2
if q < melhor:
melhor, soma, n = q, list(px[xx, yy]), 1
elif q == melhor:
c = px[xx, yy]
soma = [soma[0] + c[0], soma[1] + c[1], soma[2] + c[2]]
n += 1
r, g, b = px[x, y]
if n == 0:
po[x, y] = (r, g, b, 255)
continue
f = (soma[0] / n, soma[1] / n, soma[2] / n)
vx, vy, vz = f[0] - br, f[1] - bgc, f[2] - bb
nn = vx * vx + vy * vy + vz * vz
a = ((r - br) * vx + (g - bgc) * vy + (b - bb) * vz) / nn if nn else 1.0
a = 0.0 if a < 0.02 else 1.0 if a > 0.98 else a
po[x, y] = (round(f[0]), round(f[1]), round(f[2]), round(a * 255))
return out
def recortar_quadrado(im, margem=0.04, lado=1024):
box = im.getchannel("A").point(lambda v: 255 if v > 8 else 0).getbbox()
im = im.crop(box)
w, h = im.size
s = round(max(w, h) * (1 + 2 * margem))
quadro = Image.new("RGBA", (s, s), (0, 0, 0, 0))
quadro.paste(im, ((s - w) // 2, (s - h) // 2), im)
return quadro.resize((lado, lado), Image.LANCZOS)
def main():
logo = recortar_quadrado(tirar_fundo(os.path.join(RAIZ, FONTE), FUNDO, LIMIAR))
for sufixo, lado in (("", 768), ("-s", 128)):
logo.resize((lado, lado), Image.LANCZOS).save(os.path.join(UI, f"guara{sufixo}.png"), optimize=True)
print("guara.png e guara-s.png")
# ícone do app: a arte sobre o fundo escuro da própria arte, com cantos arredondados
from PIL import ImageDraw
lado = 1024
fundo = Image.new("RGBA", (lado, lado), FUNDO + (255,))
arte = logo.resize((round(lado * 0.82),) * 2, Image.LANCZOS)
fundo.paste(arte, ((lado - arte.width) // 2, (lado - arte.height) // 2), arte)
mascara = Image.new("L", (lado, lado), 0)
ImageDraw.Draw(mascara).rounded_rectangle((0, 0, lado - 1, lado - 1), radius=round(lado * 0.2237), fill=255)
icone = Image.new("RGBA", (lado, lado), (0, 0, 0, 0))
icone.paste(fundo, (0, 0), mascara)
icone.save(os.path.join(ICONES, "source.png"), optimize=True)
print("icons/source.png", icone.size)
if __name__ == "__main__":
main()