main
noah Publish tmc2262 component page (1/5) a7e2d34 7d ago
"""Component hero card in the existing wiki family (4000 x 2500): dark card, gradient top rule, COMPONENT eyebrow,
part number, category, maker and package, source footer, and the part render on the right over a soft glow."""
import math, os
import numpy as np
from PIL import Image, ImageDraw, ImageFilter, ImageFont

H = os.path.dirname(os.path.abspath(__file__))
FONTS = os.path.join(H, '..', 'fonts')
W, HH = 4000, 2500
BG = (13, 17, 23)
INK, INK2, INK3 = (236, 241, 245), (196, 204, 212), (122, 132, 143)
TEAL = (0, 214, 205)


def font(name, size):
    return ImageFont.truetype(os.path.join(FONTS, name), size)


def fit(d, text, name, size, maxw):
    while size > 60 and d.textlength(text, font=font(name, size)) > maxw:
        size -= 6
    return font(name, size)


def key_background(im, tol=4):
    """Make the renderer's flat backdrop transparent: pixels close to the corner colour AND connected to the border
    (so dark parts of the component itself survive), with a soft 1 px edge. The backdrop is one exact colour
    (64, 75, 85), so the match is tight: at 26 the grey end faces of 0603/1210 parts (68, 68, 66) were cut away too
    and showed the page through them."""
    from scipy import ndimage
    a = np.asarray(im).astype(int)
    bg = np.median(np.concatenate([a[0, :, :3], a[-1, :, :3], a[:, 0, :3], a[:, -1, :3]]), axis=0)
    close = np.abs(a[..., :3] - bg).max(axis=2) <= tol
    lab, n = ndimage.label(close)
    edge = set(np.unique(np.concatenate([lab[0], lab[-1], lab[:, 0], lab[:, -1]]))) - {0}
    mask = np.isin(lab, list(edge))
    alpha = np.where(mask, 0, 255).astype('uint8')
    alpha = np.asarray(Image.fromarray(alpha).filter(ImageFilter.GaussianBlur(0.8)))
    out = im.copy(); out.putalpha(Image.fromarray(alpha))
    return out


def make(render_png, out_png, title, subtitle, line, footer='Manufacturer datasheet  ·  KiCad / LCSC CAD  ·  Adom Wiki', eyebrow='COMPONENT',
         box=(1550, 1500), center=(3080, 1300), text_w=2000):
    img = Image.new('RGB', (W, HH), BG)
    # soft teal glow behind the part
    y, x = np.mgrid[0:HH // 4, 0:W // 4].astype(float)
    g = np.exp(-(((x - 2900 / 4) / (900 / 4)) ** 2 + ((y - 1300 / 4) / (800 / 4)) ** 2))
    glow = np.clip(np.array(BG, float) + g[..., None] * np.array([6, 38, 44], float), 0, 255).astype('uint8')
    img.paste(Image.fromarray(glow).resize((W, HH), Image.BICUBIC))
    d = ImageDraw.Draw(img)
    # gradient top rule
    for i in range(W):
        t = i / W
        c = tuple(int(a + (b - a) * t) for a, b in zip((0, 214, 205), (140, 107, 247)))
        d.line([(i, 0), (i, 18)], fill=c)
    # the part
    part = key_background(Image.open(render_png).convert('RGBA'))
    bb = part.getbbox()
    if bb:
        part = part.crop(bb)
    s = min(box[0] / part.width, box[1] / part.height)
    part = part.resize((int(part.width * s), int(part.height * s)), Image.LANCZOS)
    img.paste(part, (center[0] - part.width // 2, center[1] - part.height // 2), part)
    d = ImageDraw.Draw(img)
    # text column
    x0 = 230
    d.text((x0, 420), ' '.join(eyebrow), font=font('JetBrainsMono-Medium.ttf', 64), fill=TEAL)
    ft = fit(d, title, 'FamiljenGrotesk-Bold.ttf', 400, text_w)
    d.text((x0, 560), title, font=ft, fill=INK)
    tb = d.textbbox((x0, 560), title, font=ft)
    y = tb[3] + 90
    fs = fit(d, subtitle, 'Satoshi-Medium.otf', 120, text_w)
    d.text((x0, y), subtitle, font=fs, fill=INK2)
    y = d.textbbox((x0, y), subtitle, font=fs)[3] + 40
    fl = fit(d, line, 'Satoshi-Medium.otf', 92, text_w)
    d.text((x0, y), line, font=fl, fill=INK3)
    d.text((x0, 2230), footer, font=font('JetBrainsMono-Regular.ttf', 56), fill=INK3)
    img.save(out_png, optimize=True)
    return out_png