Files
Genarrative/scripts/generate-taonier-geometric-logo-concepts.py

301 lines
12 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.
from __future__ import annotations
import math
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
REPO_ROOT = Path(__file__).resolve().parents[1]
OUTPUT_DIR = REPO_ROOT / "public" / "branding" / "taonier-logo-geometric-concepts"
CONTACT_SHEET_PATH = OUTPUT_DIR / "taonier-logo-geometric-contact-sheet.png"
SIZE = 1024
SCALE = 4
INK = "#151515"
CREAM = "#fff7e6"
GOLD = "#ffd25d"
CORAL = "#ff6a5f"
MINT = "#29c9ad"
BLUE = "#2f6bff"
VARIANTS = [
("taonier-geometric-offset-core", "偏心泥孔", "offset_core"),
("taonier-geometric-mold-chip", "模芯切片", "mold_chip"),
("taonier-geometric-pinched-tile", "捏痕方标", "pinched_tile"),
("taonier-geometric-dual-plate", "双片合模", "dual_plate"),
("taonier-geometric-dot-gate", "泥点入口", "dot_gate"),
("taonier-geometric-work-knot", "作品结点", "work_knot"),
]
def hex_to_rgb(value: str) -> tuple[int, int, int]:
value = value.removeprefix("#")
return tuple(int(value[index : index + 2], 16) for index in (0, 2, 4))
def s(value: float) -> int:
return round(value * SCALE)
def rgba(value: str) -> tuple[int, int, int, int]:
red, green, blue = hex_to_rgb(value)
return red, green, blue, 255
def regular_polygon(cx: float, cy: float, radius: float, sides: int, rotation: float) -> list[tuple[int, int]]:
points = []
for index in range(sides):
angle = rotation + math.tau * index / sides
points.append((s(cx + math.cos(angle) * radius), s(cy + math.sin(angle) * radius)))
return points
def rounded_rectangle(
draw: ImageDraw.ImageDraw,
box: tuple[float, float, float, float],
radius: float,
fill: str,
) -> None:
draw.rounded_rectangle(tuple(s(value) for value in box), radius=s(radius), fill=hex_to_rgb(fill))
def circle(draw: ImageDraw.ImageDraw, cx: float, cy: float, radius: float, fill: str) -> None:
draw.ellipse((s(cx - radius), s(cy - radius), s(cx + radius), s(cy + radius)), fill=hex_to_rgb(fill))
def draw_offset_core(draw: ImageDraw.ImageDraw) -> None:
draw.rectangle((0, 0, s(SIZE), s(SIZE)), fill=hex_to_rgb("#111111"))
rounded_rectangle(draw, (236, 236, 788, 788), 148, CREAM)
circle(draw, 610, 456, 116, "#111111")
circle(draw, 610, 456, 48, GOLD)
rounded_rectangle(draw, (268, 612, 536, 718), 53, "#111111")
rounded_rectangle(draw, (294, 638, 500, 690), 26, CREAM)
circle(draw, 352, 370, 34, "#111111")
circle(draw, 352, 370, 17, GOLD)
def draw_mold_chip(draw: ImageDraw.ImageDraw) -> None:
draw.rectangle((0, 0, s(SIZE), s(SIZE)), fill=hex_to_rgb("#101418"))
draw.polygon(
[
(s(278), s(230)),
(s(734), s(230)),
(s(828), s(330)),
(s(828), s(694)),
(s(706), s(794)),
(s(278), s(794)),
(s(196), s(708)),
(s(196), s(322)),
],
fill=hex_to_rgb(CREAM),
)
circle(draw, 512, 512, 144, "#101418")
circle(draw, 512, 512, 62, GOLD)
rounded_rectangle(draw, (224, 280, 518, 370), 45, CORAL)
rounded_rectangle(draw, (574, 654, 796, 736), 41, MINT)
def draw_pinched_tile(draw: ImageDraw.ImageDraw) -> None:
draw.rectangle((0, 0, s(SIZE), s(SIZE)), fill=hex_to_rgb("#14100d"))
rounded_rectangle(draw, (232, 250, 792, 774), 170, CREAM)
circle(draw, 232, 512, 94, "#14100d")
circle(draw, 792, 512, 94, "#14100d")
draw.polygon(regular_polygon(512, 512, 104, 4, math.pi / 4), fill=hex_to_rgb("#14100d"))
circle(draw, 512, 512, 38, GOLD)
rounded_rectangle(draw, (420, 300, 604, 358), 29, CORAL)
rounded_rectangle(draw, (420, 666, 604, 724), 29, MINT)
def draw_dual_plate(draw: ImageDraw.ImageDraw) -> None:
draw.rectangle((0, 0, s(SIZE), s(SIZE)), fill=hex_to_rgb("#111111"))
rounded_rectangle(draw, (214, 316, 790, 464), 74, CORAL)
rounded_rectangle(draw, (234, 560, 810, 708), 74, MINT)
draw.polygon(regular_polygon(512, 512, 138, 4, math.pi / 4), fill=hex_to_rgb(CREAM))
draw.polygon(regular_polygon(512, 512, 76, 4, math.pi / 4), fill=hex_to_rgb("#111111"))
circle(draw, 512, 512, 32, GOLD)
circle(draw, 262, 390, 24, CREAM)
circle(draw, 762, 634, 24, CREAM)
def draw_dot_gate(draw: ImageDraw.ImageDraw) -> None:
draw.rectangle((0, 0, s(SIZE), s(SIZE)), fill=hex_to_rgb("#101010"))
rounded_rectangle(draw, (276, 330, 748, 752), 132, CREAM)
rounded_rectangle(draw, (386, 440, 638, 752), 126, "#101010")
circle(draw, 512, 260, 62, GOLD)
rounded_rectangle(draw, (450, 308, 574, 504), 62, CREAM)
circle(draw, 512, 518, 40, GOLD)
rounded_rectangle(draw, (316, 754, 708, 812), 29, CREAM)
def draw_work_knot(draw: ImageDraw.ImageDraw) -> None:
draw.rectangle((0, 0, s(SIZE), s(SIZE)), fill=hex_to_rgb("#121212"))
circle(draw, 396, 402, 132, CREAM)
circle(draw, 628, 402, 132, CREAM)
circle(draw, 396, 622, 132, CREAM)
circle(draw, 628, 622, 132, CREAM)
rounded_rectangle(draw, (386, 386, 638, 638), 72, "#121212")
draw.polygon(regular_polygon(512, 512, 96, 4, math.pi / 4), fill=hex_to_rgb(GOLD))
circle(draw, 396, 402, 48, CORAL)
circle(draw, 628, 622, 48, MINT)
circle(draw, 628, 402, 28, "#121212")
circle(draw, 396, 622, 28, "#121212")
DRAWERS = {
"offset_core": draw_offset_core,
"mold_chip": draw_mold_chip,
"pinched_tile": draw_pinched_tile,
"dual_plate": draw_dual_plate,
"dot_gate": draw_dot_gate,
"work_knot": draw_work_knot,
}
def render_variant(style: str) -> Image.Image:
image = Image.new("RGB", (SIZE * SCALE, SIZE * SCALE), hex_to_rgb("#111111"))
draw = ImageDraw.Draw(image)
DRAWERS[style](draw)
return image.resize((SIZE, SIZE), Image.Resampling.LANCZOS)
def build_svg(style: str) -> str:
# PNG 是当前评审主物料SVG 保留为后续设计师重绘的结构草图。
if style == "offset_core":
body = f'''
<rect width="1024" height="1024" rx="160" fill="#111111"/>
<rect x="236" y="236" width="552" height="552" rx="148" fill="{CREAM}"/>
<circle cx="610" cy="456" r="116" fill="#111111"/>
<circle cx="610" cy="456" r="48" fill="{GOLD}"/>
<rect x="268" y="612" width="268" height="106" rx="53" fill="#111111"/>
<rect x="294" y="638" width="206" height="52" rx="26" fill="{CREAM}"/>
<circle cx="352" cy="370" r="34" fill="#111111"/>
<circle cx="352" cy="370" r="17" fill="{GOLD}"/>'''
elif style == "mold_chip":
body = f'''
<rect width="1024" height="1024" rx="160" fill="#101418"/>
<path d="M278 230H734L828 330V694L706 794H278L196 708V322Z" fill="{CREAM}"/>
<circle cx="512" cy="512" r="144" fill="#101418"/>
<circle cx="512" cy="512" r="62" fill="{GOLD}"/>
<rect x="224" y="280" width="294" height="90" rx="45" fill="{CORAL}"/>
<rect x="574" y="654" width="222" height="82" rx="41" fill="{MINT}"/>'''
elif style == "pinched_tile":
body = f'''
<rect width="1024" height="1024" rx="160" fill="#14100d"/>
<rect x="232" y="250" width="560" height="524" rx="170" fill="{CREAM}"/>
<circle cx="232" cy="512" r="94" fill="#14100d"/>
<circle cx="792" cy="512" r="94" fill="#14100d"/>
<path d="M512 408L616 512L512 616L408 512Z" fill="#14100d"/>
<circle cx="512" cy="512" r="38" fill="{GOLD}"/>
<rect x="420" y="300" width="184" height="58" rx="29" fill="{CORAL}"/>
<rect x="420" y="666" width="184" height="58" rx="29" fill="{MINT}"/>'''
elif style == "dual_plate":
body = f'''
<rect width="1024" height="1024" rx="160" fill="#111111"/>
<rect x="214" y="316" width="576" height="148" rx="74" fill="{CORAL}"/>
<rect x="234" y="560" width="576" height="148" rx="74" fill="{MINT}"/>
<path d="M512 374L650 512L512 650L374 512Z" fill="{CREAM}"/>
<path d="M512 436L588 512L512 588L436 512Z" fill="#111111"/>
<circle cx="512" cy="512" r="32" fill="{GOLD}"/>
<circle cx="262" cy="390" r="24" fill="{CREAM}"/>
<circle cx="762" cy="634" r="24" fill="{CREAM}"/>'''
elif style == "dot_gate":
body = f'''
<rect width="1024" height="1024" rx="160" fill="#101010"/>
<rect x="276" y="330" width="472" height="422" rx="132" fill="{CREAM}"/>
<rect x="386" y="440" width="252" height="312" rx="126" fill="#101010"/>
<circle cx="512" cy="260" r="62" fill="{GOLD}"/>
<rect x="450" y="308" width="124" height="196" rx="62" fill="{CREAM}"/>
<circle cx="512" cy="518" r="40" fill="{GOLD}"/>
<rect x="316" y="754" width="392" height="58" rx="29" fill="{CREAM}"/>'''
else:
body = f'''
<rect width="1024" height="1024" rx="160" fill="#121212"/>
<circle cx="396" cy="402" r="132" fill="{CREAM}"/>
<circle cx="628" cy="402" r="132" fill="{CREAM}"/>
<circle cx="396" cy="622" r="132" fill="{CREAM}"/>
<circle cx="628" cy="622" r="132" fill="{CREAM}"/>
<rect x="386" y="386" width="252" height="252" rx="72" fill="#121212"/>
<path d="M512 416L608 512L512 608L416 512Z" fill="{GOLD}"/>
<circle cx="396" cy="402" r="48" fill="{CORAL}"/>
<circle cx="628" cy="622" r="48" fill="{MINT}"/>
<circle cx="628" cy="402" r="28" fill="#121212"/>
<circle cx="396" cy="622" r="28" fill="#121212"/>'''
return f'''<?xml version="1.0" encoding="UTF-8"?>
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
{body}
</svg>
'''
def load_font(size: int) -> ImageFont.FreeTypeFont | ImageFont.ImageFont:
candidates = [
Path("C:/Windows/Fonts/msyh.ttc"),
Path("C:/Windows/Fonts/simhei.ttf"),
Path("C:/Windows/Fonts/simsun.ttc"),
]
for candidate in candidates:
if candidate.exists():
return ImageFont.truetype(str(candidate), size)
return ImageFont.load_default()
def build_contact_sheet(previews: list[tuple[str, str, Image.Image]]) -> Image.Image:
cell_size = 320
label_height = 60
gap = 28
columns = 3
rows = 2
width = columns * cell_size + (columns + 1) * gap
height = rows * (cell_size + label_height) + (rows + 1) * gap
sheet = Image.new("RGB", (width, height), "#eee9df")
draw = ImageDraw.Draw(sheet)
font = load_font(23)
for index, (_, title, preview) in enumerate(previews):
row = index // columns
column = index % columns
x = gap + column * (cell_size + gap)
y = gap + row * (cell_size + label_height + gap)
thumbnail = preview.resize((cell_size, cell_size), Image.Resampling.LANCZOS)
sheet.paste(thumbnail, (x, y))
draw.rounded_rectangle(
(x, y + cell_size, x + cell_size, y + cell_size + label_height),
radius=10,
fill="#fffdf8",
)
label = f"{index + 1:02d} {title}"
text_box = draw.textbbox((0, 0), label, font=font)
text_x = x + (cell_size - (text_box[2] - text_box[0])) / 2
text_y = y + cell_size + (label_height - (text_box[3] - text_box[1])) / 2 - 2
draw.text((text_x, text_y), label, fill="#302a25", font=font)
return sheet
def main() -> None:
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
previews: list[tuple[str, str, Image.Image]] = []
for asset_id, title, style in VARIANTS:
preview = render_variant(style)
preview.save(OUTPUT_DIR / f"{asset_id}.png")
(OUTPUT_DIR / f"{asset_id}.svg").write_text(build_svg(style), encoding="utf-8")
previews.append((asset_id, title, preview))
contact_sheet = build_contact_sheet(previews)
contact_sheet.save(CONTACT_SHEET_PATH, quality=95)
print(
{
"ok": True,
"output_dir": str(OUTPUT_DIR),
"files": [f"{asset_id}.png" for asset_id, _, _ in VARIANTS]
+ [f"{asset_id}.svg" for asset_id, _, _ in VARIANTS]
+ [CONTACT_SHEET_PATH.name],
}
)
if __name__ == "__main__":
main()