我就是這樣製作VI-設計與程式間不對稱的harmony
從兩張 PNG 出發,用 Python 自動化生成完整品牌視覺識別系統的完整記錄。
一、什麼是 VI?為什麼要做?
VI(Visual Identity,視覺識別系統)是品牌對外溝通的「視覺語言規範」。 它回答一個核心問題:這個品牌在任何地方出現,應該長什麼樣子?
一套完整的 VI 套件至少包含:
| 類別 | 內容 |
|---|---|
| Logo 本體 | 原始透明底版、各色底版本 |
| Logo 變體 | 水平、垂直、方形、圓形 |
| 色票 | HEX / RGB / CMYK 規格 |
| 字型規範 | 主副字型、字級建議 |
| 使用規範 | 保留空間、禁止行為 |
| 應用品範例 | 名片、信頭、社群頭像 |
沒有 VI,每次設計師、工程師、行銷人員「自由發揮」,品牌就逐漸變形。 有了 VI,一個資料夾就能讓任何人做出一致的品牌表現。
二、起點:兩張 PNG
本次的原始素材只有兩張 PNG:

兩張圖都是 Ascentek 的品牌 logo,內容相同但尺寸略有差異(一張是 16:9 展示版,另一張是較緊湊的版本)。
Logo 組成分析:
- 左側:紙飛機 / A字形箭頭符號,由兩個三角形組成
- 主三角:Teal 色(
#03dcd0) - 深色三角:近黑色(
#181c20),製造立體/深度感
- 主三角:Teal 色(
- 右側:「Ascentek」品牌名稱,深色無襯線字型
- 背景:透明(RGBA)
這兩個資訊就是整套 VI 的根基:形狀 + 顏色。
三、品牌元素提取
3.1 色票提取
不靠肉眼猜色,用程式直接從像素讀取主色:
def get_dominant_colors(img, k=3):
# 過濾透明像素,只分析有內容的部分
pixels = [px[:3] for px in img.getdata() if px[3] > 0]
# 用 Pillow 的 Adaptive Palette 聚類
small = Image.new('RGB', (len(pixels), 1))
small.putdata(pixels)
pal = small.convert('P', palette=Image.ADAPTIVE, colors=k)
return pal.getpalette()[:k*3]提取結果:
| 角色 | HEX | RGB | CMYK(參考) |
|---|---|---|---|
| Primary | #03dcd0 | 3, 220, 208 | C99 M0 Y5 K14 |
| Secondary | #204b4c | 32, 75, 76 | C58 M1 Y0 K70 |
| Dark / Text | #181c20 | 24, 28, 32 | C25 M12 Y0 K88 |
3.2 Icon 邊界偵測
Logo 是「符號 + 文字」的組合圖,要做方形/圓形容器版本,必須把符號部分單獨截出來。
挑戰: 不能手動量像素,要讓程式自動找出「符號在哪裡結束、文字從哪裡開始」。
初始思路:找第一個空白欄(column gap)
# 錯誤版本 — 會在符號內部的小間隙就停止
for x in range(width):
if not has_opaque_pixels(column[x]):
return x # 太早停了!這個方法的問題:Ascentek 的 logo 符號由兩個分開的三角形組成,它們之間有小間隙。程式在這個內部間隙就停下來,結果只截到 teal 三角,深色三角被排除在外:
[teal △] [gap] [dark △] [ 大間距 ] [Ascentek 文字]
↑
這裡被誤判為 icon 結尾正確思路:找最寬的間距
icon 和文字之間的間距,一定比 icon 內部任何細縫都寬。 所以掃描所有 gap,取最寬的那個:
def find_icon_right(img):
col_has = [any_opaque_pixel_in_column(x) for x in range(width)]
# 收集所有 gap 的位置和寬度
gaps = []
for each gap in col_has:
gaps.append((gap_start_x, gap_width))
# 最寬 gap 的起點就是 icon 的右邊界
return max(gaps, key=width)[start_x]結果:邊界從 x=158(只有 teal)修正到 x=214(完整符號),深色三角成功納入。
四、工具選擇
| 工具 | 用途 | 選擇原因 |
|---|---|---|
| Python 3 | 腳本語言 | 跨平台、生態豐富 |
| Pillow(PIL) | 圖像處理核心 | 純 Python、無需 numpy、處理 RGBA 完整支援 |
| Montserrat | 品牌字型 | 原 Logo 字型風格、SIL 授權可商用、已隨 Google Fonts 取得 |
為什麼不用 Photoshop / Figma?
- 手動操作無法重現(換 logo 就要全部重做)
- 腳本化後只需替換來源圖,重跑一次就更新全部 28 個檔案
- 程式碼即文件:流程邏輯清楚記錄在腳本裡
五、核心技術:顏色適配
5.1 深色底適配(Dark Background Adaptation)
這是整套做法中最重要的一個判斷:
問題: Logo 上有深色(#181c20)的三角形和文字。把這個 Logo 直接放在深色底上,深色元素就消失不見了。
Copilot 的做法(錯誤): 把整個 Logo 染成單一顏色(全部變 teal),深色三角和文字都消失,Logo 失去立體感。
正確做法: 像素級判斷 — 只把「接近黑色」的像素改成白色,teal 部分原封不動:
def adapt_dark(img):
data = list(img.getdata())
out = []
for r, g, b, a in data:
if a < 10: # 透明:不動
out.append((0, 0, 0, 0))
elif r < 80 and g < 80 and b < 80: # 深色:改白
out.append((255, 255, 255, a))
else: # 其他(teal):保留
out.append((r, g, b, a))
...結果對比:
| 效果 | |
|---|---|
| 淺色底(原色) | teal 三角 + 深色三角 + 深色文字 ✓ |
| 深色底(適配後) | teal 三角 + 白色三角 + 白色文字 ✓ |
5.2 單色剪影(Monochrome)
印刷場景有時需要純黑或純白版本。做法是保留 alpha 通道的形狀,把所有有色像素填成指定顏色:
def silhouette(img, color_rgb):
alpha = img.split()[3] # 取出形狀遮罩
base = Image.new('RGBA', img.size, color_rgb + (255,))
base.putalpha(alpha) # 把遮罩套回去
return base5.3 Circle Primary 的陷阱
最初的圓形主色版本:把原始 icon(teal)放在 teal 底 → 完全看不見。
正確做法:先把 icon 轉成白色剪影,再放在 teal 底:
icon_white = silhouette(icon, C_WHITE) # 先變白色剪影
circ = on_canvas(icon_white, C_PRIMARY) # 再放 teal 底六、Logo 變體生成邏輯
6.1 水平版(Horizontal)
Logo 本身就是水平構圖(符號在左、文字在右),所以直接加底色就是水平版,不需要重新排版,也不需要再疊加文字(疊加文字 = Copilot 版的重影 bug 來源)。
# 正確做法:logo 已含文字,只需加背景
img = on_canvas(logo, bg_color)

6.2 垂直版(Vertical)
垂直版需要「符號在上、文字在下」的構圖。由於來源 PNG 是合體圖(符號+文字),必須:
- 用邊界偵測截出純符號(icon)
- 放大符號至合適比例
- 用 Pillow 重新 render Montserrat 字型作為文字部分
- 垂直組合
# 截出純符號
icon = logo.crop((0, 0, icon_right_x, logo.height))
# 重新排版
canvas.paste(icon, centered_top)
draw.text(centered_bottom, 'Ascentek', font=fnt('SemiBold'))6.3 方形 / 圓形容器
單純把符號(icon)放進正方形容器,加上 15% padding:
- 方形:直接輸出 400×400
- 圓形:建立同尺寸圓形遮罩,paste 時套用
七、應用品設計
7.1 名片(Business Card)
規格:3.5×2 吋 @ 300 dpi = 1050×600 px,雙面。
正面設計原則:
- 左側 10px teal 邊條(視覺定錨)
- Logo 置於左上
- teal 分隔線區隔 logo 與個人資訊區
- 文字層次:姓名(SemiBold 52pt)→ 職稱(Regular 30pt)→ 聯絡資訊(Regular 28pt)
- 網址用 Primary 色(teal)強調
- 底部 14px teal 底條收尾
背面設計原則:
- 深色底(
#181c20) - 頂底各一條 teal 細線
- 置中放深色底適配版 logo


名片是生出來了,但規格大小與 layout 這種事情就很主觀了,因人而異就不多說了。
7.2 信頭(Letterhead)
規格:A4 @ 300 dpi = 2480×3508 px。
設計結構:
┌─────────────────────────────────────┐ ← 深色 header (280px)
│ [Logo] Ascentek │
│ www... │
├─────────────────────────────────────┤ ← teal 分隔線 (18px)
│ │
│ 正文區域 │
│ │
│ │
├─────────────────────────────────────┤ ← teal 頁尾分隔線
│ 聯絡資訊 footer │
└─────────────────────────────────────┘ ← teal 底條關鍵細節:Header 內的 Logo 需使用深色適配版(adapt_dark),才能在深色底上正常顯示。
八、與 AI 協作過程中踩到的坑
坑 1:重影(Ghost Text)
現象: 名片和信頭上出現兩個「Ascentek」文字。
根本原因: 用的是「完整 logo(含文字)」作為底圖,然後又用 draw.text() 疊了一次文字。
解法: 不要在有文字的 logo 上再加文字。水平版直接用 logo 原圖,垂直版從純符號重新組合。
坑 2:深色底 Logo 看不見
現象: Logo 放深色背景時,文字和深色三角消失,只剩 teal 部分浮在背景上。
解法: 逐像素判斷,將深色像素轉白,保留 teal 不動(adapt_dark 函數)。
坑 3:Icon 截切不完整
現象: 方形/圓形容器只有 teal 三角,深色三角被排除在外。
根本原因: 用「第一個空白欄」找邊界,但符號內部兩個三角之間就有小間隙,提早停止了。
解法: 改成找「最寬的 gap」,icon 和文字之間的間距一定比 icon 內部的縫還寬。
坑 4:Circle Primary 隱形
現象: teal 圖標放在 teal 底上完全看不見。
解法: 圓形主色版必須先把 icon 轉成白色剪影(silhouette),再放在 teal 底。
坑 5:美學這種事情,是很難絕對判斷的
畢竟芝蘭之室云云與海濱的人,這些就留待自身判斷了。個人對於生成的這張名片覺得相對 LOGO 是大了一點就是了。
九、最終輸出結構
VI-ClaudeVersion/
│
├── logo/ # 所有 Logo 變體(PNG + SVG)
│ ├── logo_original.png # 透明底,最具彈性
│ ├── logo_horizontal_light.png/svg # 淺底主力版
│ ├── logo_horizontal_dark.png/svg # 深底主力版
│ ├── logo_vertical_light.png/svg # 垂直構圖(淺底)
│ ├── logo_vertical_dark.png/svg # 垂直構圖(深底)
│ ├── logo_square_light.png/svg # 方形容器
│ ├── logo_square_dark.png/svg
│ ├── logo_circle_light.png/svg # 圓形容器
│ ├── logo_circle_dark.png/svg
│ ├── logo_circle_primary.png/svg # 白剪影 on teal(App icon 用)
│ ├── logo_black.png # 單色黑(印刷)
│ └── logo_white.png # 單色白(印刷)
│
├── applications/ # 應用品範例
│ ├── business_card_front.png
│ ├── business_card_back.png
│ ├── letterhead.png
│ └── letterhead.pdf
│
├── palette.txt # 色票(HEX + RGB + CMYK)
├── VI_GUIDELINES.md # 使用規範
├── VI_Preview.png # A4 展示頁
├── VI_Preview.pdf
└── generate_vi.py # 生成腳本(可重複執行)
std-logo/ # 精簡可攜版(移植到新專案用)
├── logo_original.png
├── logo_horizontal_light/dark .png/.svg
├── logo_square_light/dark .png
├── logo_circle_light/dark/primary .png
├── logo_black/white .png
├── palette.txt
└── VI_GUIDELINES.md十、這套做法可以複用在哪裡?
前提條件
要讓這個流程套用到其他品牌,來源素材需要具備:
- PNG with transparency(透明背景) — 才能讓程式正確識別 Logo 範圍
- 向量清晰度或夠高解析度 — 建議 Logo 寬度 >= 600px,才能縮放後仍清晰
- 色彩簡潔 — 3–5 個主色最適合。漸層或照片感的 Logo 需要額外處理邏輯
移植新品牌的步驟
- 把新品牌的透明 PNG 放進資料夾
- 修改
generate_vi.py開頭的兩行:
LOGO_SRC = '你的新logo.png'
BRAND = '你的品牌名'- 如果色票不同,修改
C_PRIMARY / C_SECONDARY / C_DARK - 執行
python generate_vi.py - 全套 28 個檔案一次生成完畢
應用到各種場景的選檔指引
| 場景 | 選用檔案 |
|---|---|
| 網站 header | logo_horizontal_light.svg(淺底)/ logo_horizontal_dark.svg(深底) |
| HTML favicon | logo_circle_primary.png → 轉 .ico |
| App 啟動圖示 | logo_circle_dark.png(1024×1024 再縮) |
| GitHub / LinkedIn 頭像 | logo_square_dark.png |
| PowerPoint 封面 | logo_original.png(透明底,任意背景) |
| 單色印刷 | logo_black.png 或 logo_white.png |
| 提案 / Pitch Deck | logo_horizontal_light.png(淺底頁)或 logo_horizontal_dark.png(深底頁) |
CSS 快速接入
:root {
--color-primary: #03dcd0;
--color-secondary: #204b4c;
--color-dark: #181c20;
--font-brand: 'Montserrat', sans-serif;
}Tailwind 快速接入
// tailwind.config.js
theme: {
extend: {
colors: {
primary: '#03dcd0',
secondary: '#204b4c',
dark: '#181c20',
},
fontFamily: {
brand: ['Montserrat', 'sans-serif'],
},
}
}十一、核心思維總結
做 VI 的本質不是「畫好看的圖」,而是制定規則並讓它可以被執行。
從這次的流程中提煉出幾個關鍵原則:
- 不要手工操作可以自動化的事 — 色票提取、尺寸換算、背景加工,全部交給程式
- 從像素看真相 — 色票不靠肉眼,邊界不靠目測,用程式碼讀取原始數據
- 把設計決策寫成函數 —
adapt_dark()、silhouette()、find_icon_right(),每個決策都有明確的邏輯和名字 - 腳本即文件 — 整個流程記錄在
generate_vi.py裡,讀程式碼就知道做了什麼 - 一鍵可重現 — 換 logo 換品牌,重跑一次腳本就全部更新,沒有「只有我知道怎麼做」的黑盒
最好的 VI 系統,是一個不需要原作者在場也能被任何人正確使用的系統。
彩蛋: 其實原始的 LOGO 也是 AI 跑了幾張之後老闆挑出來的⋯⋯
製作工具:Python 3 · Pillow · Montserrat(SIL OFL)
生成腳本:VI-ClaudeVersion/generate_vi.py
標準素材包:std-logo/