Spaces:
Sleeping
Sleeping
| """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) # #efe7d3 page | |
| CARD = (247, 241, 225) # #f7f1e1 | |
| BORDER = (201, 183, 141) # #c9b78d | |
| INK = (58, 44, 24) # #3a2c18 | |
| SUB = (107, 88, 54) # #6b5836 | |
| MAROON = (122, 47, 47) # #7a2f2f | |
| SKETCH = (90, 74, 53) # sepia for the silhouette | |
| 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) # post | |
| draw.ellipse([cx - 5, top - 5, cx + 5, top + 5], fill=col) # finial | |
| 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) # base | |
| def _silhouette(draw, cx, cy): | |
| """A courtroom-sketch bust: shoulders, neck, head, with a suit + tie hint.""" | |
| # shoulders / suit | |
| draw.ellipse([cx - 165, cy + 70, cx + 165, cy + 360], fill=SKETCH) | |
| draw.rectangle([cx - 165, cy + 215, cx + 165, cy + 360], fill=SKETCH) | |
| # collar V + tie | |
| 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)) # tie | |
| draw.polygon([(cx - 40, cy + 95), (cx - 14, cy + 112), (cx, cy + 150), | |
| (cx - 16, cy + 150)], fill=SKETCH_HI) # lapel L | |
| draw.polygon([(cx + 40, cy + 95), (cx + 14, cy + 112), (cx, cy + 150), | |
| (cx + 16, cy + 150)], fill=SKETCH_HI) # lapel R | |
| # neck + head | |
| draw.rectangle([cx - 26, cy + 40, cx + 26, cy + 110], fill=SKETCH) | |
| draw.ellipse([cx - 70, cy - 110, cx + 70, cy + 60], fill=SKETCH) | |
| # hair sweep | |
| 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) | |
| # card with double frame | |
| 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) | |
| # nameplate bar | |
| 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() | |