#!/usr/bin/env python3 # Generate the on-screen bitmap assets the CircuitPython firmware (app.py) blits: # logo.bin - the VARASYS wordmark (no tagline), tinted brand cyan at the top # midi.bin - a 5-pin DIN icon (lights green when a MIDI host is listening) # usb.bin - the USB "trident" icon (lights when the device is USB-connected) # # Each file is a single 4-bit-alpha image with a 2-byte header (matches the font packing # in gen_font.py, just without the glyph metrics table): # byte 0 = width, byte 1 = height, then ((w*h+1)//2) bytes of 4-bit alpha, # row-major, two pixels per byte (first pixel = high nibble). # app.py's load_alpha()/make_glyph() decode it and blend bg->fg per pixel (smooth). # # Re-run after changing the logo/icons: python3 pico-cp/gen_assets.py # Writes pico-cp/{logo,midi,usb}.bin and /tmp/assets_verify.png (eyeball it). import math, pathlib from PIL import Image, ImageDraw HERE = pathlib.Path(__file__).parent LOGO_PNG = pathlib.Path.home() / "src/varasys_logo/For using with dark colored background/VARASYS Limited.png" SS = 6 # supersample factor for the drawn icons (downscaled -> anti-aliased) def pack(coverage_img): """coverage_img: 'L' image (0..255 alpha). -> bytes(w, h, packed 4-bit alpha).""" w, h = coverage_img.size assert w <= 255 and h <= 255, "asset too large for the 1-byte dims (%dx%d)" % (w, h) px = coverage_img.load() nib = [] for y in range(h): for x in range(w): nib.append(px[x, y] >> 4) # 8-bit -> 4-bit alpha if len(nib) % 2: nib.append(0) out = bytearray([w, h]) for i in range(0, len(nib), 2): out.append((nib[i] << 4) | nib[i + 1]) return bytes(out) def make_logo(target_w=156): img = Image.open(LOGO_PNG).convert("RGBA") r, g, b, a = img.split() # Coverage = alpha if the PNG is genuinely transparent, else brightness (cyan on a flat bg). if a.getextrema()[0] < 250: cov = a else: cov = img.convert("L") bbox = cov.getbbox() if bbox: cov = cov.crop(bbox) w, h = cov.size th = max(1, round(target_w * h / w)) cov = cov.resize((target_w, th), Image.LANCZOS) return pack(cov) def make_midi(size=22): cw = size * SS img = Image.new("L", (cw, cw), 0) d = ImageDraw.Draw(img) cx = cy = cw / 2 R = cw * 0.44 lw = max(2, int(cw * 0.07)) d.ellipse([cx - R, cy - R, cx + R, cy + R], outline=255, width=lw) # connector shell # 5 pins in the DIN fan (one top-centre, a flanking pair above, a wider pair below) ring = R * 0.60 pr = cw * 0.075 pins = [(0.0, -1.0), (-0.72, -0.55), (0.72, -0.55), (-0.95, 0.18), (0.95, 0.18)] for fx, fy in pins: x = cx + fx * ring y = cy + fy * ring d.ellipse([x - pr, y - pr, x + pr, y + pr], fill=255) return pack(img.resize((size, size), Image.LANCZOS)) def make_usb(size=22): cw = size * SS img = Image.new("L", (cw, cw), 0) d = ImageDraw.Draw(img) cx = cw / 2 lw = max(2, int(cw * 0.075)) top, bot = cw * 0.10, cw * 0.90 d.line([(cx, top + cw * 0.10), (cx, bot)], fill=255, width=lw) # shaft # arrowhead at the top ah = cw * 0.13 d.polygon([(cx, top), (cx - ah, top + ah * 1.4), (cx + ah, top + ah * 1.4)], fill=255) # base plug (filled circle at the bottom) br = cw * 0.10 d.ellipse([cx - br, bot - br, cx + br, bot + br], fill=255) # left branch -> small filled circle ly = cw * 0.46 lx = cx - cw * 0.26 d.line([(cx, ly + cw * 0.10), (lx, ly)], fill=255, width=lw) cr = cw * 0.085 d.ellipse([lx - cr, ly - cr, lx + cr, ly + cr], fill=255) # right branch -> small filled square ry = cw * 0.34 rx = cx + cw * 0.26 d.line([(cx, ry + cw * 0.10), (rx, ry)], fill=255, width=lw) sq = cw * 0.08 d.rectangle([rx - sq, ry - sq, rx + sq, ry + sq], fill=255) return pack(img.resize((size, size), Image.LANCZOS)) def unpack_to_img(blob, fg=(10, 179, 247), bg=(6, 9, 14)): """Decode like app.py would, for the verify sheet.""" w, h = blob[0], blob[1] im = Image.new("RGB", (w, h), bg) for k in range(w * h): byte = blob[2 + (k >> 1)] nib = (byte >> 4) if (k & 1) == 0 else (byte & 0xF) t = nib * 17 col = tuple((bg[i] * (255 - t) + fg[i] * t) // 255 for i in range(3)) im.putpixel((k % w, k // w), col) return im def main(): assets = {"logo": make_logo(), "midi": make_midi(), "usb": make_usb()} for name, blob in assets.items(): (HERE / (name + ".bin")).write_bytes(blob) print("wrote %s.bin %dx%d %d bytes" % (name, blob[0], blob[1], len(blob))) # verify sheet on a dark panel-like background pad = 12 imgs = [unpack_to_img(b) for b in assets.values()] W = max(i.width for i in imgs) + pad * 2 H = sum(i.height for i in imgs) + pad * (len(imgs) + 1) sheet = Image.new("RGB", (W, H), (6, 9, 14)) y = pad for i in imgs: sheet.paste(i, (pad, y)) y += i.height + pad sheet.save("/tmp/assets_verify.png") print("verify -> /tmp/assets_verify.png") if __name__ == "__main__": main()