kiwi/scripts/logo/generate_bird_silhouette.py
pyr0ball c18bfec8f5
Some checks are pending
CI / Backend (Python) (push) Waiting to run
CI / Frontend (Vue) (push) Waiting to run
Mirror / mirror (push) Waiting to run
fix(lint): add ruff config, fix all lint errors for GitHub CI
- pyproject.toml: add [tool.ruff] config suppressing E402/W293 globally,
  F841/E741/E702 in tests, E741 in scripts
- inventory.py: split semicolon import (E702)
- recipes.py: fix logger -> log (F821 undefined name)
- shopping.py: rename l -> lnk in list comprehension (E741)
- format_conversion.py: noqa F841 on CUDA flag (used as future hook)
- backfill_keywords.py: rename done -> _done (F841)
- ingest_purplecarrot.py: drop == True comparison (E712)
- Auto-fix: I001 import sorting, F401 unused imports across all files
2026-07-06 02:49:02 -07:00

154 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Draw a kiwi bird (Apteryx) silhouette as SVG for use as ControlNet guidance.
Key anatomy:
- Very round oval body (nearly spherical)
- Small oval head, short neck
- EXTREMELY long thin downward-curved beak (≈ body height)
- No visible wings (vestigial, hidden in feathers)
- Short stocky legs with 3 forward toes + 1 rear toe
Rendered as colored (rufous brown) on white, 768×768 — gives img2img a palette to work with.
"""
import math
W, H = 768, 768
# Kiwi bird colour palette
COL_BODY = "#7A3B20" # dark rufous brown
COL_HEAD = "#8C4828" # slightly lighter brown for head
COL_NECK = "#7A3B20" # same as body
COL_BEAK = "#C8A96E" # horn/tan beak
COL_LEG = "#4A2410" # very dark brown legs/feet
COL_EYE_RG = "white" # eye ring
COL_PUPIL = "#1A0A00" # near-black pupil
def pt(x, y): return f"{x:.1f},{y:.1f}"
def bezier_arc(cx, cy, rx, ry, start_deg, end_deg, steps=32):
"""Approximate an ellipse arc as a polyline."""
points = []
for i in range(steps + 1):
t = math.radians(start_deg + (end_deg - start_deg) * i / steps)
x = cx + rx * math.cos(t)
y = cy + ry * math.sin(t)
points.append((x, y))
return points
lines = [
'<?xml version="1.0" encoding="UTF-8"?>',
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" width="{W}" height="{H}">',
' <rect width="100%" height="100%" fill="white"/>',
]
# ── Body ────────────────────────────────────────────────────────────────────
BCX, BCY = 430, 440
BRX, BRY = 155, 175
lines.append(f' <ellipse cx="{BCX}" cy="{BCY}" rx="{BRX}" ry="{BRY}" fill="{COL_BODY}"/>')
# ── Head ────────────────────────────────────────────────────────────────────
HCX, HCY = 315, 295
HRX, HRY = 60, 52
lines.append(f' <ellipse cx="{HCX}" cy="{HCY}" rx="{HRX}" ry="{HRY}" fill="{COL_HEAD}"/>')
# ── Neck ────────────────────────────────────────────────────────────────────
neck_path = (
f'M {pt(HCX - 25, HCY + 42)} '
f'Q {pt(BCX - 140, BCY - 120)} {pt(BCX - 125, BCY - 148)} '
f'L {pt(BCX - 60, BCY - 165)} '
f'Q {pt(HCX + 30, HCY - 30)} {pt(HCX + 30, HCY + 38)} '
f'Z'
)
lines.append(f' <path d="{neck_path}" fill="{COL_NECK}"/>')
# ── Beak ────────────────────────────────────────────────────────────────────
# Starts at left side of head, curves to a point
# Kiwi beak: starts narrow at head, stays thin all the way, slight downward arc
beak_base_x = HCX - HRX + 5 # where beak attaches to head (left side)
beak_base_y = HCY + 5
beak_tip_x = 118 # very long — extends well past the head
beak_tip_y = HCY + 28 # droops slightly at the tip
# Upper beak edge: from base, curving gently down to tip
# Lower beak edge: parallel, slightly below
beak_w_base = 14 # width at head
beak_w_mid = 8 # width at midpoint
mid_x = (beak_base_x + beak_tip_x) / 2
mid_y_upper = (beak_base_y - beak_w_base/2 + beak_tip_y - 2) / 2 - 2
mid_y_lower = (beak_base_y + beak_w_base/2 + beak_tip_y + 2) / 2 + 2
beak_path = (
f'M {pt(beak_base_x, beak_base_y - beak_w_base/2)} '
f'Q {pt(mid_x, mid_y_upper)} {pt(beak_tip_x, beak_tip_y)} '
f'Q {pt(mid_x, mid_y_lower)} {pt(beak_base_x, beak_base_y + beak_w_base/2)} '
f'Z'
)
lines.append(f' <path d="{beak_path}" fill="{COL_BEAK}"/>')
# ── Legs ────────────────────────────────────────────────────────────────────
def draw_leg(lines, hip_x, foot_x, foot_y, toe_spread=28):
"""Draw a leg with a 3-toed foot."""
hip_y = BCY + BRY - 20
ankle_y = foot_y - 22
leg_w = 20
leg_path = (
f'M {pt(hip_x - leg_w/2, hip_y)} '
f'L {pt(foot_x - leg_w/2, ankle_y)} '
f'L {pt(foot_x + leg_w/2, ankle_y)} '
f'L {pt(hip_x + leg_w/2, hip_y)} Z'
)
lines.append(f' <path d="{leg_path}" fill="{COL_LEG}"/>')
# Three forward toes + one small rear toe
toe_len = 38
toe_w = 7
angles = [-25, 0, 25] # degrees from horizontal
for a in angles:
rad = math.radians(a)
tx = foot_x + toe_len * math.cos(rad)
ty = ankle_y + toe_len * math.sin(rad)
# Toe as thin tapered path
perp = math.radians(a + 90)
toe_path = (
f'M {pt(foot_x + toe_w * math.cos(perp), ankle_y + toe_w * math.sin(perp))} '
f'L {pt(tx + 2, ty)} '
f'L {pt(foot_x - toe_w * math.cos(perp), ankle_y - toe_w * math.sin(perp))} Z'
)
lines.append(f' <path d="{toe_path}" fill="{COL_LEG}"/>')
# Rear hallux
rear_x = foot_x - 20
rear_y = ankle_y + 8
toe_path = (
f'M {pt(foot_x - 4, ankle_y)} '
f'L {pt(rear_x, rear_y)} '
f'L {pt(foot_x + 4, ankle_y)} Z'
)
lines.append(f' <path d="{toe_path}" fill="{COL_LEG}"/>')
# Left leg (slightly forward)
draw_leg(lines, hip_x=BCX - 38, foot_x=BCX - 45, foot_y=BCY + BRY + 52)
# Right leg (slightly behind)
draw_leg(lines, hip_x=BCX + 28, foot_x=BCX + 38, foot_y=BCY + BRY + 45)
# ── Eye ─────────────────────────────────────────────────────────────────────
# Small white circle (eye highlight) on the head
# Kiwi eye is tiny, close to the beak base
eye_x = HCX - 18
eye_y = HCY - 4
lines.append(f' <circle cx="{eye_x}" cy="{eye_y}" r="10" fill="{COL_EYE_RG}"/>')
lines.append(f' <circle cx="{eye_x}" cy="{eye_y}" r="5" fill="{COL_PUPIL}"/>')
lines.append('</svg>')
svg = '\n'.join(lines)
out = '/tmp/kiwi_silhouette.svg'
with open(out, 'w') as f:
f.write(svg)
print(f"Written: {out}")