| """Render a courtroom-sketch witness placard as the portrait placeholder. |
| |
| python3 scripts/make_portrait_placeholder.py -> assets/marcus_reid.png |
| |
| app.py shows assets/marcus_reid.png if it exists, else an empty box. A real |
| AI portrait (HF ZeroGPU) can overwrite this file later; until then this gives the |
| demo an intentional, on-theme visual instead of a blank frame. Pure PIL — no GPU, |
| no network — and it matches the app's parchment palette. |
| """ |
| from __future__ import annotations |
|
|
| import os |
|
|
| from PIL import Image, ImageDraw, ImageFont |
|
|
| W, H = 768, 960 |
| PARCH = (239, 231, 211) |
| CARD = (247, 241, 225) |
| BORDER = (201, 183, 141) |
| INK = (58, 44, 24) |
| SUB = (107, 88, 54) |
| MAROON = (122, 47, 47) |
| SKETCH = (90, 74, 53) |
| SKETCH_HI = (120, 102, 78) |
|
|
| FONT_DIRS = [ |
| "/System/Library/Fonts/Supplemental/", |
| "/System/Library/Fonts/", |
| "/Library/Fonts/", |
| ] |
| SERIF = ["Georgia.ttf", "Palatino.ttc", "Times New Roman.ttf", "Baskerville.ttc"] |
| SERIF_B = ["Georgia Bold.ttf", "Times New Roman Bold.ttf", "Georgia.ttf"] |
|
|
|
|
| def _font(names, size): |
| for d in FONT_DIRS: |
| for n in names: |
| p = os.path.join(d, n) |
| if os.path.exists(p): |
| try: |
| return ImageFont.truetype(p, size) |
| except Exception: |
| pass |
| return ImageFont.load_default() |
|
|
|
|
| def _spaced(draw, xy, text, font, fill, spacing=6, anchor_center=None): |
| """Draw letter-spaced text; if anchor_center given, center on that x.""" |
| widths = [draw.textlength(c, font=font) for c in text] |
| total = sum(widths) + spacing * (len(text) - 1) |
| x = (anchor_center - total / 2) if anchor_center is not None else xy[0] |
| y = xy[1] |
| for c, w in zip(text, widths): |
| draw.text((x, y), c, font=font, fill=fill) |
| x += w + spacing |
| return total |
|
|
|
|
| def _scales(draw, cx, top): |
| """A small balance-scale glyph, drawn from primitives.""" |
| col = INK |
| draw.line([(cx, top), (cx, top + 54)], fill=col, width=4) |
| draw.ellipse([cx - 5, top - 5, cx + 5, top + 5], fill=col) |
| beam_y, span = top + 14, 70 |
| draw.line([(cx - span, beam_y), (cx + span, beam_y)], fill=col, width=4) |
| for sx in (cx - span, cx + span): |
| draw.line([(sx, beam_y), (sx - 18, beam_y + 34)], fill=col, width=2) |
| draw.line([(sx, beam_y), (sx + 18, beam_y + 34)], fill=col, width=2) |
| draw.arc([sx - 20, beam_y + 24, sx + 20, beam_y + 50], 0, 180, fill=col, width=3) |
| draw.line([(cx - 26, top + 54), (cx + 26, top + 54)], fill=col, width=4) |
|
|
|
|
| def _silhouette(draw, cx, cy): |
| """A courtroom-sketch bust: shoulders, neck, head, with a suit + tie hint.""" |
| |
| draw.ellipse([cx - 165, cy + 70, cx + 165, cy + 360], fill=SKETCH) |
| draw.rectangle([cx - 165, cy + 215, cx + 165, cy + 360], fill=SKETCH) |
| |
| draw.polygon([(cx - 40, cy + 95), (cx, cy + 185), (cx + 40, cy + 95)], fill=CARD) |
| draw.polygon([(cx - 12, cy + 120), (cx + 12, cy + 120), (cx + 18, cy + 210), |
| (cx, cy + 235), (cx - 18, cy + 210)], fill=(64, 40, 40)) |
| draw.polygon([(cx - 40, cy + 95), (cx - 14, cy + 112), (cx, cy + 150), |
| (cx - 16, cy + 150)], fill=SKETCH_HI) |
| draw.polygon([(cx + 40, cy + 95), (cx + 14, cy + 112), (cx, cy + 150), |
| (cx + 16, cy + 150)], fill=SKETCH_HI) |
| |
| draw.rectangle([cx - 26, cy + 40, cx + 26, cy + 110], fill=SKETCH) |
| draw.ellipse([cx - 70, cy - 110, cx + 70, cy + 60], fill=SKETCH) |
| |
| draw.chord([cx - 72, cy - 120, cx + 72, cy + 10], 180, 360, fill=SKETCH_HI) |
|
|
|
|
| def main(): |
| root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| out_dir = os.path.join(root, "assets") |
| os.makedirs(out_dir, exist_ok=True) |
| out = os.path.join(out_dir, "marcus_reid.png") |
|
|
| img = Image.new("RGB", (W, H), PARCH) |
| d = ImageDraw.Draw(img) |
|
|
| |
| m = 28 |
| d.rectangle([m, m, W - m, H - m], fill=CARD, outline=BORDER, width=3) |
| d.rectangle([m + 12, m + 12, W - m - 12, H - m - 12], outline=BORDER, width=1) |
|
|
| f_top = _font(SERIF_B, 30) |
| f_name = _font(SERIF_B, 58) |
| f_sub = _font(SERIF, 27) |
| f_foot = _font(SERIF, 20) |
|
|
| _scales(d, W // 2, 62) |
| _spaced(d, (0, 150), "SWORN WITNESS", f_top, MAROON, spacing=10, anchor_center=W // 2) |
|
|
| _silhouette(d, W // 2, 330) |
|
|
| |
| bar_y = 720 |
| d.rectangle([m + 40, bar_y, W - m - 40, bar_y + 86], fill=INK) |
| _spaced(d, (0, bar_y + 16), "MARCUS REID", f_name, CARD, spacing=4, anchor_center=W // 2) |
|
|
| sub = "Chief Financial Officer · Halcyon Dynamics" |
| tw = d.textlength(sub, font=f_sub) |
| d.text(((W - tw) / 2, bar_y + 104), sub, font=f_sub, fill=SUB) |
|
|
| foot = "WitnessBox — State's Exhibit" |
| fw = d.textlength(foot, font=f_foot) |
| d.text(((W - fw) / 2, H - m - 52), foot, font=f_foot, fill=BORDER) |
|
|
| img.save(out) |
| print(f"wrote {out} ({W}x{H})") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|