Versão 0.6.0: saúde do disco no estilo do CrystalDiskInfo e nova logo
Release / Cria a release (push) Canceled after 0s
Release / CLI (linux-aarch64) (push) Canceled after 0s
Release / CLI (linux-x86_64) (push) Canceled after 0s
Release / CLI (macos-aarch64) (push) Canceled after 0s
Release / CLI (macos-x86_64) (push) Canceled after 0s
Release / CLI (windows-x86_64) (push) Canceled after 0s
Release / App desktop (linux-x86_64) (push) Canceled after 0s
Release / App desktop (macos-aarch64) (push) Canceled after 0s
Release / App desktop (macos-x86_64) (push) Canceled after 0s
Release / App desktop (windows-x86_64) (push) Canceled after 0s
Release / Publica a release (push) Canceled after 0s
Release / Cria a release (push) Canceled after 0s
Release / CLI (linux-aarch64) (push) Canceled after 0s
Release / CLI (linux-x86_64) (push) Canceled after 0s
Release / CLI (macos-aarch64) (push) Canceled after 0s
Release / CLI (macos-x86_64) (push) Canceled after 0s
Release / CLI (windows-x86_64) (push) Canceled after 0s
Release / App desktop (linux-x86_64) (push) Canceled after 0s
Release / App desktop (macos-aarch64) (push) Canceled after 0s
Release / App desktop (macos-x86_64) (push) Canceled after 0s
Release / App desktop (windows-x86_64) (push) Canceled after 0s
Release / Publica a release (push) Canceled after 0s
SMART completo (estado com % de vida útil, temperatura, horas e vezes ligado, total lido/gravado, ficha do dispositivo e tabela de atributos ATA e NVMe), leitura pelo disco que contém a partição e comando `guara health`. Nova logo: a cabeça do guará recortada das trilhas de um disco, nas cores do app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f65e7ae615
commit
c3b5f79009
22 files changed
+638
-139
No files matched your search
+49
-123
@@ -1,128 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Gera a logo do Guará em estilo geométrico facetado (low-poly).
|
||||
"""Gera a logo do Guará: a cabeça do lobo-guará, de frente, recortada das
|
||||
trilhas de um disco.
|
||||
|
||||
A cabeça do lobo-guará foi contornada sobre uma foto de referência, com as
|
||||
regiões de cor do animal real: focinho escuro subindo até entre os olhos, juba
|
||||
escura na nuca, interior das orelhas e garganta claros. As facetas (Delaunay)
|
||||
seguem essas regiões, e o pescoço se solta em estilhaços para a esquerda.
|
||||
As trilhas são arcos concêntricos com centro abaixo do focinho. Na metade
|
||||
direita estão inteiras (dados recuperados) e na esquerda quebradas em
|
||||
fragmentos (dados perdidos). Focinho e olhos usam a cor do texto. As cores
|
||||
são as do app (apps/gui/ui/app.css): --accent e --accent-2 no tema claro e
|
||||
seus equivalentes no escuro, trocados por prefers-color-scheme.
|
||||
|
||||
Uso: python3 scripts/make-logo.py recorte.png [saida.svg]
|
||||
Requer numpy, scipy e pillow. O ícone (guara.svg) embute esta mesma arte.
|
||||
Uso: python3 scripts/make-logo.py [pasta] (padrão: apps/gui/ui)
|
||||
Grava guara-logo.svg e guara.svg (mesma arte; o segundo é o ícone).
|
||||
O PNG para os ícones do app (src-tauri/icons/source.png) sai desta arte
|
||||
renderizada em 1024x1024.
|
||||
"""
|
||||
import math, random, sys
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
from scipy.spatial import Delaunay
|
||||
OUT=[(98,382),(104,350),(110,318),(114,290),(116,262),(110,235),(100,212),
|
||||
(84,178),(66,140),(54,100),(50,68),(54,44),(70,52),(98,70),(128,94),(156,118),(176,146),
|
||||
(180,112),(190,78),(204,50),(216,32),(230,40),(252,64),(274,94),(292,120),(305,135),
|
||||
(330,138),(370,142),(420,150),(500,165),(500,460),(320,460),(300,420),(260,385),(225,372),(190,380),(150,398),(118,404),(102,396)]
|
||||
EARS=[[(52,78),(80,96),(115,118),(150,142),(130,170),(105,185),(85,160),(65,120)],
|
||||
[(222,48),(245,62),(270,95),(285,125),(255,165),(215,165),(195,130),(200,85)]]
|
||||
EYES=[(125,270),(200,268)]
|
||||
ZONES=[
|
||||
# interior claro das orelhas (com borda verde em volta)
|
||||
('pale',[(62,62),(92,78),(122,100),(150,124),(160,144),(138,166),(116,190),(98,166),(76,124),(62,92)]),
|
||||
('pale',[(214,50),(228,56),(248,78),(268,106),(282,128),(258,150),(226,160),(200,146),(196,112),(204,76)]),
|
||||
# testa um pouco mais clara entre as orelhas
|
||||
('brow',[(160,150),(185,148),(240,170),(270,190),(250,220),(200,230),(160,225),(130,205)]),
|
||||
# área dos olhos, um tom mais escuro
|
||||
('mask',[(108,250),(140,250),(165,246),(195,250),(222,262),(220,284),(196,292),(165,282),(140,290),(112,290)]),
|
||||
# juba escura na nuca
|
||||
('mane',[(298,136),(500,166),(500,236),(430,214),(360,198),(318,182),(300,160)]),
|
||||
# garganta clara e linha clara do lábio
|
||||
('pale',[(214,350),(234,332),(266,324),(302,320),(316,344),(306,372),(274,388),(240,380),(220,370)]),
|
||||
('pale',[(150,398),(186,382),(212,368),(222,374),(204,388),(166,404)]),
|
||||
# focinho preto subindo pelo dorso do nariz até entre os olhos
|
||||
('muzzle',[(96,384),(102,352),(108,322),(114,296),(132,284),(150,266),(164,258),(176,268),(184,294),(196,322),(206,352),(200,374),(170,392),(130,404),(104,398)]),
|
||||
]
|
||||
import os, sys
|
||||
|
||||
from PIL import ImageFilter
|
||||
REF = sys.argv[1] # recorte da cabeça da foto de referência (390x460), não versionado
|
||||
img=np.array(Image.open(REF).convert('RGB').filter(ImageFilter.MedianFilter(9)).filter(ImageFilter.GaussianBlur(4))).astype(float)
|
||||
Hc,Wc=img.shape[:2]
|
||||
rnd=random.Random(7)
|
||||
# contorno: estende o pescoço para a esquerda (depois do espelhamento)
|
||||
C=[p for p in OUT if p[0]<=500]
|
||||
def inside(poly,x,y):
|
||||
c=False; j=len(poly)-1
|
||||
for i in range(len(poly)):
|
||||
xi,yi=poly[i]; xj,yj=poly[j]
|
||||
if (yi>y)!=(yj>y) and x<(xj-xi)*(y-yi)/(yj-yi)+xi: c=not c
|
||||
j=i
|
||||
return c
|
||||
# pontos: contorno reamostrado + traços anatômicos + preenchimento com jitter
|
||||
pts=[]
|
||||
for i in range(len(C)):
|
||||
a=C[i]; b=C[(i+1)%len(C)]; n=max(1,int(math.dist(a,b)/16))
|
||||
for k in range(n): pts.append((a[0]+(b[0]-a[0])*k/n, a[1]+(b[1]-a[1])*k/n))
|
||||
FEAT=[(125,270),(200,268),(110,300),(160,286),(185,300),(100,383),(118,404),(150,396),(200,360),
|
||||
(215,330),(260,322),(300,318),(310,350),(270,386),(140,140),(110,190),(80,120),(255,100),(270,150),(225,160),
|
||||
(205,110),(175,180),(230,230),(150,230),(300,200),(340,250),(320,170),(360,330),(400,260),(450,330),(260,280)]
|
||||
pts+=FEAT
|
||||
# vértices nas bordas das regiões: as facetas seguem os contornos das cores
|
||||
for k_,Z in ZONES:
|
||||
for i in range(len(Z)):
|
||||
a=Z[i]; b=Z[(i+1)%len(Z)]; n=max(1,int(math.dist(a,b)/18))
|
||||
for j in range(n):
|
||||
q=(a[0]+(b[0]-a[0])*j/n, a[1]+(b[1]-a[1])*j/n)
|
||||
if inside(C,*q) and min(math.dist(q,p_) for p_ in pts)>6: pts.append(q)
|
||||
sp=24
|
||||
for y in np.arange(20,470,sp):
|
||||
for x in np.arange(20,510,sp):
|
||||
jx,jy=x+rnd.uniform(-8,8),y+rnd.uniform(-8,8)
|
||||
if inside(C,jx,jy) and min(math.dist((jx,jy),p) for p in pts)>15: pts.append((jx,jy))
|
||||
P=np.array(pts)
|
||||
tri=Delaunay(P)
|
||||
def sample(t):
|
||||
a,b,c=P[t]
|
||||
cols=[]
|
||||
for _ in range(40):
|
||||
u,v=rnd.random(),rnd.random()
|
||||
if u+v>1: u,v=1-u,1-v
|
||||
x,y=a+(b-a)*u+(c-a)*v
|
||||
x=min(max(x,0),Wc-1); y=min(max(y,0),Hc-1)
|
||||
if x>380: x=380-(x-380)*0.2 # o pescoço além da foto herda a cor da borda
|
||||
cols.append(img[int(y),int(x)])
|
||||
m=np.mean(cols,axis=0); return m
|
||||
RAMP=["#083a45","#0f766e","#14b8a6","#5eead4","#c9faf1"]
|
||||
def tone(rgb):
|
||||
r,g,b=rgb; L=(0.299*r+0.587*g+0.114*b)/255
|
||||
TH=[0.17,0.30,0.50,0.64]
|
||||
return RAMP[sum(L>t for t in TH)]
|
||||
def hx(c): return tuple(int(c[i:i+2],16) for i in (1,3,5))
|
||||
def mix(c1,c2,t):
|
||||
a,b=hx(c1),hx(c2); return "#"+"".join(f"{round(p+(q-p)*t):02x}" for p,q in zip(a,b))
|
||||
BASE={'muzzle':("#07323c","#0d5563"),'mane':("#0b4f5c","#0f766e"),'pale':("#9ef0e2","#e3fdf8"),'face':("#0f9e94","#3fdcc8"),'brow':("#1fb9a9","#52dccb"),'mask':("#0c7d76","#139c92")}
|
||||
def zone_of(x,y):
|
||||
for k,P_ in reversed(ZONES):
|
||||
if inside(P_,x,y): return k
|
||||
return 'face'
|
||||
F=[]
|
||||
for t in tri.simplices:
|
||||
cx,cy=P[t].mean(axis=0)
|
||||
if not inside(C,cx,cy): continue
|
||||
z=zone_of(cx,cy)
|
||||
lo,hi=BASE[z]
|
||||
# luz vinda do alto e da frente (focinho), com variação sutil por faceta
|
||||
light=0.55*(1-cy/470)+0.35*(1-min(1,cx/420))+rnd.uniform(-0.12,0.12)
|
||||
F.append((t, mix(lo,hi,max(0,min(1,light))), cx, cy))
|
||||
# espelha e encaixa no quadro 160x100 da logo
|
||||
S=92/430
|
||||
def T(x,y): return (150-(x-38)*S, 4+(y-30)*S)
|
||||
out=[]
|
||||
for t,col,cx,cy in F:
|
||||
q=[T(*P[i]) for i in t]
|
||||
# estilhaços: o pescoço (esquerda) se solta e se desfaz
|
||||
gx=T(cx,cy)[0]
|
||||
k=max(0,(78-gx)/40)
|
||||
if k>0 and rnd.random()<min(1,k*1.25):
|
||||
mx=sum(p[0] for p in q)/3; my=sum(p[1] for p in q)/3
|
||||
dx=-k*22-rnd.uniform(0,10)*k; dy=-k*6+rnd.uniform(-5,5)*k
|
||||
sc=max(0.25,1-k*0.8)
|
||||
q=[(mx+dx+(p[0]-mx)*sc, my+dy+(p[1]-my)*sc) for p in q]
|
||||
op=max(0.12,1-k*0.95)
|
||||
else: op=1
|
||||
out.append(f'<polygon points="{" ".join(f"{x:.2f},{y:.2f}" for x,y in q)}" fill="{col}" stroke="{col}" stroke-width="0.25" stroke-linejoin="round"{"" if op==1 else f" fill-opacity=\"{op:.2f}\" stroke-opacity=\"{op:.2f}\""}/>')
|
||||
eyes="".join(f'<circle cx="{T(x,y)[0]:.2f}" cy="{T(x,y)[1]:.2f}" r="1.25" fill="#04232b"/>' for x,y in EYES)
|
||||
open(sys.argv[2] if len(sys.argv) > 2 else 'guara-logo.svg','w').write(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 100" role="img" aria-label="Guará">{"".join(out)}{eyes}</svg>\n')
|
||||
HEAD = ("M50 95 C46 88 43 80 40 74 C33 70 25 64 22 56 L14 6 L36 30 "
|
||||
"C40 28 45 27 50 27 C55 27 60 28 64 30 L86 6 L78 56 "
|
||||
"C75 64 67 70 60 74 C57 80 54 88 50 95 Z")
|
||||
STYLE = (".a{stroke:#0f766e}.b{stroke:#14b8a6}.t{fill:#1d1d1f}"
|
||||
"@media (prefers-color-scheme:dark){.a{stroke:#2dd4bf}.b{stroke:#5eead4}.t{fill:#f5f5f7}}")
|
||||
|
||||
|
||||
def tracks():
|
||||
out = []
|
||||
for i, r in enumerate(range(7, 104, 7)):
|
||||
top, bot = 100 - r, 100 + r
|
||||
out.append(f'<path class="a" d="M50 {top}A{r} {r} 0 0 1 50 {bot}"/>')
|
||||
dash = f"{3 + i % 3 * 3} {3 + (i * 5) % 4}"
|
||||
out.append(f'<path class="b" d="M50 {top}A{r} {r} 0 0 0 50 {bot}" '
|
||||
f'stroke-dasharray="{dash}" stroke-dashoffset="{i * 3}"/>')
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def svg():
|
||||
return (
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img" aria-label="Guará">'
|
||||
f'<style>{STYLE}</style>'
|
||||
f'<defs><clipPath id="g-head"><path d="{HEAD}"/></clipPath></defs>'
|
||||
f'<g clip-path="url(#g-head)" fill="none" stroke-width="4.2">{tracks()}'
|
||||
# o focinho é a ponta da própria cabeça (recortada pelo mesmo contorno),
|
||||
# então nenhuma trilha escapa pelas laterais
|
||||
'<rect class="t" stroke="none" x="0" y="74" width="100" height="30"/></g>'
|
||||
'<path class="t" d="M37 50Q42 47 46 52Q41 53 37 50ZM63 50Q58 47 54 52Q59 53 63 50Z"/>'
|
||||
'</svg>\n'
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
out = sys.argv[1] if len(sys.argv) > 1 else "apps/gui/ui"
|
||||
for name in ("guara-logo.svg", "guara.svg"):
|
||||
with open(os.path.join(out, name), "w") as f:
|
||||
f.write(svg())
|
||||
Reference in new issue
Block a user