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>
No files matched your search
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 403 KiB After Width: | Height: | Size: 419 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 387 KiB After Width: | Height: | Size: 406 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 339 KiB After Width: | Height: | Size: 870 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
@@ -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()
|
||||||
@@ -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()
|
|
||||||