app
Chip Fetcher (cf)
Public Made by Adomby adom
Crawls the web on your behalf to gather everything about the chips you care about: datasheets, symbols, footprints, 3D models, stock and pricing, straight from the manufacturer first, into your EDA tool. Say "cf" to your AI.
master
John Lauer
hero v6: bridge-family layout, 242px title, crawl value prop (Hero Studio stamped); 1.3.3
59be9e9
1mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
#!/usr/bin/env python3
"""Compose the chip-fetcher hero in the Adom bridge-family style (Pup / KiCad / Fusion).
Same geometry as kicad-bridge's compose_hero.py (itself measured off Pup's
published asset): 2000x1250, Familjen Grotesk Bold title at 242px (cap height
155px, cap top y=263, ink left x=83), Satoshi subtitle at 52px, the ADOM
wordmark and the BRIDGE pill cropped verbatim from Pup's hero, the real app in
a 1030x1025 rounded vitrine at (920,150). The only family deviation: the title
is two lines ("Chip" / "Fetcher") because one word at 240px does not fit.
Usage:
python3 compose_hero.py <dashboard-capture.png> <out.png>
The capture should be the REAL dashboard at 1030 wide. A 1030x910 in-container
capture gets Pup's Chrome titlebar strip stacked on top to make 1030x1025; a
1030x1025 real-window capture is used as is.
"""
import sys
from pathlib import Path
import numpy as np
from PIL import Image, ImageDraw, ImageFilter, ImageFont
KB = Path("/home/adom/project/adom-desktop-kicad-bridge/skills")
PUP = KB / "kicad-bridge-hero" / "reference-pup-hero.png"
FONTS = KB / "kicad-tour" / "hero" / "fonts"
TITLEBAR = Path("/home/adom/project/pup-bridge/dev-skills/pup-hero-image/assets/win_slot3.png")
W, H = 2000, 1250
TITLE_LINES = ["Chip", "Fetcher"]
SUB_1A, SUB_1B = "Crawls the web for your chips, ", ""
SUB_2A, SUB_2B = "brings back ", "all the chip data."
TEAL = (0, 230, 220)
LOGO_CROP, LOGO_AT = (60, 48, 335, 130), (60, 48)
PILL_CROP, PILL_AT = (1796, 36, 1964, 108), (1796, 36)
INK_LEFT = 91 # the studio stamps the standard wordmark with its ink at x=91; title + subtitle align to it
CAP_TOP = 263
LINE_H = 245
SUB_Y1, SUB_Y2 = CAP_TOP + 2 * LINE_H + 0, CAP_TOP + 2 * LINE_H + 64 # below the second title line
VITRINE_AT, VITRINE_W, VITRINE_H = (920, 150), 1030, 1025
RADIUS, SHADOW_BLUR, SHADOW_DY = 24, 28, 12
def left_ink(im, y0, y1):
a = np.array(im).astype(int)
diff = np.abs(a - a[700, 400]).sum(axis=2) > 45
for x in range(40, 900):
if diff[y0:y1, x].sum() > 2:
return x
return None
def draw_pill(d, pup):
"""Same pill as the bridge family (position, size, fill sampled from Pup's
asset), reading APP: Chip Fetcher is an app, not a bridge."""
fill = (0, 184, 177) # the Adom teal, #00b8b1 (Pup's pill measures (1,182,172))
text = (5, 34, 31) # --on-accent, #05221f
x0, y0, x1, y1 = PILL_CROP
w = 128
x0 = x1 - w
d.rounded_rectangle((x0, y0, x1 - 1, y1 - 1), radius=(y1 - y0) // 2, fill=fill)
pf = ImageFont.truetype(str(FONTS / "FamiljenGrotesk-Bold.ttf"), 34)
label = "APP"
tw = d.textlength(label, font=pf)
bb = pf.getbbox(label)
d.text((x0 + (w - tw) / 2, y0 + ((y1 - y0) - (bb[3] - bb[1])) / 2 - bb[1]), label, font=pf, fill=text)
def draw_title(d, tf, dx):
for i, line in enumerate(TITLE_LINES):
y = CAP_TOP + i * LINE_H - tf.getbbox(line[0])[1]
x = dx
for ch in line:
d.text((x, y), ch, font=tf, fill=(230, 237, 243))
x += d.textlength(ch, font=tf) - 5
def compose(title_dx, sub_dx, pup, base, tf, sf):
hero = base.copy()
# wordmark: NOT drawn here; Hero Studio stamps the standard mark (overlay.logo=true, hero-studio#37)
d = ImageDraw.Draw(hero)
draw_pill(d, pup)
draw_title(d, tf, title_dx)
scol = (232, 238, 244)
w = d.textlength(SUB_1A, font=sf)
d.text((sub_dx, SUB_Y1), SUB_1A, font=sf, fill=scol)
d.text((sub_dx + w, SUB_Y1), SUB_1B, font=sf, fill=scol)
w = d.textlength(SUB_2A, font=sf)
d.text((sub_dx, SUB_Y2), SUB_2A, font=sf, fill=scol)
d.text((sub_dx + w, SUB_Y2), SUB_2B, font=sf, fill=TEAL)
return hero
def main(capture_path, out_path):
pup = Image.open(PUP).convert("RGB")
pa = np.array(pup)
tl, tr = pa[6, 6].astype(float), pa[6, W - 7].astype(float)
bl, br = pa[H - 7, 6].astype(float), pa[H - 7, W - 7].astype(float)
xs = np.linspace(0, 1, W)[None, :, None]
ys = np.linspace(0, 1, H)[:, None, None]
base = Image.fromarray((tl * (1 - xs) * (1 - ys) + tr * xs * (1 - ys)
+ bl * (1 - xs) * ys + br * xs * ys).astype(np.uint8))
tf = ImageFont.truetype(str(FONTS / "FamiljenGrotesk-Bold.ttf"), 242)
sf = ImageFont.truetype(str(FONTS / "Satoshi-Regular.ttf"), 52)
t_dx, s_dx = 98, 84
im = compose(t_dx, s_dx, pup, base, tf, sf)
t_dx += INK_LEFT - left_ink(im, CAP_TOP, CAP_TOP + 2 * LINE_H - 40)
s_dx += INK_LEFT - left_ink(im, SUB_Y1 - 6, SUB_Y2 + 70)
hero = compose(t_dx, s_dx, pup, base, tf, sf)
ti = left_ink(hero, CAP_TOP, CAP_TOP + 2 * LINE_H - 40)
su = left_ink(hero, SUB_Y1 - 6, SUB_Y2 + 70)
assert (ti, su) == (INK_LEFT, INK_LEFT), f"alignment drifted: {(ti, su)}"
win = Image.open(capture_path).convert("RGB")
if win.size[0] != VITRINE_W:
win = win.resize((VITRINE_W, round(win.size[1] * VITRINE_W / win.size[0])), Image.LANCZOS)
if win.size[1] < VITRINE_H:
strip_h = VITRINE_H - win.size[1]
strip = Image.open(TITLEBAR).convert("RGB")
strip = strip.crop((12, 0, strip.size[0] - 12, 125)).resize((VITRINE_W, strip_h), Image.LANCZOS)
stacked = Image.new("RGB", (VITRINE_W, VITRINE_H))
stacked.paste(strip, (0, 0))
stacked.paste(win, (0, strip_h))
win = stacked
elif win.size[1] > VITRINE_H:
win = win.crop((0, 0, VITRINE_W, VITRINE_H))
mask = Image.new("L", (VITRINE_W, VITRINE_H), 0)
ImageDraw.Draw(mask).rounded_rectangle((0, 0, VITRINE_W - 1, VITRINE_H - 1), radius=RADIUS, fill=255)
sh = Image.new("L", (W, H), 0)
vx, vy = VITRINE_AT
ImageDraw.Draw(sh).rounded_rectangle((vx, vy + SHADOW_DY, vx + VITRINE_W, vy + SHADOW_DY + VITRINE_H),
radius=RADIUS, fill=110)
sh = sh.filter(ImageFilter.GaussianBlur(SHADOW_BLUR))
hero = Image.composite(Image.new("RGB", (W, H), (0, 0, 0)), hero, sh)
hero.paste(win, VITRINE_AT, mask)
hero.save(out_path)
print(f"saved {out_path} (title/sub ink left = {INK_LEFT}, title 242px Familjen Bold, two lines)")
if __name__ == "__main__":
if len(sys.argv) != 3:
sys.exit(__doc__)
main(sys.argv[1], sys.argv[2])