Files
game-cards/utility/生成扑克牌.py

254 lines
9.0 KiB
Python
Raw Permalink Normal View History

import os
import subprocess
import svgwrite
# ========== 卡片基础尺寸 ==========
CARD_W = 63.5 # 毫米(标准桥牌尺寸宽)
CARD_H = 88.9 # 毫米
MM_TO_PX = 3.779527559 # 1mm ≈ 3.78px (96dpi)
W_PX = int(CARD_W * MM_TO_PX)
H_PX = int(CARD_H * MM_TO_PX)
CORNER_RADIUS = 8 # 圆角半径(毫米)
# 花色配置
SUIT_NAME = {'S': 'Spade', 'H': 'Heart', 'C': 'Club', 'D': 'Diamond'}
SUIT_COLOR = {'S': '#000000', 'H': '#C00000', 'C': '#000000', 'D': '#C00000'}
# 点数映射
RANK_NUM = {
'2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, '10':10,
'J':11, 'Q':12, 'K':13, 'A':1
}
# ========== 辅助函数 ==========
def mm_to_px(mm_val):
"""毫米转像素96 DPI"""
return mm_val * MM_TO_PX
def add_corner_indicators(dwg, rank, suit, x_off, y_off, scale=1.0, rotation=0):
"""角标:点数 + 花色字母,无特殊符号"""
group = dwg.g(transform=f"translate({x_off},{y_off}) rotate({rotation})")
rank_font_size = 12 * scale
# 点数
group.add(dwg.text(
rank,
insert=(0, rank_font_size),
font_size=f"{rank_font_size}pt",
font_family="Arial, sans-serif",
font_weight="bold",
fill=SUIT_COLOR[suit]
))
# 花色字母 S/H/C/D
suit_char = suit
suit_font_size = 10 * scale
group.add(dwg.text(
suit_char,
insert=(0, rank_font_size + suit_font_size),
font_size=f"{suit_font_size}pt",
fill=SUIT_COLOR[suit]
))
return group
def draw_suit_symbol(dwg, suit, x, y, size_mm):
"""纯SVG图形绘制四种花色彻底解决方框/乱码"""
g = dwg.g(transform=f"translate({x}, {y})")
s = size_mm / 2.0
color = SUIT_COLOR[suit]
if suit == "H":
# 红桃
path = f"M 0 {0.3*s} C {-s} {-0.3*s}, {-s} {-s}, 0 {-0.5*s} C {s} {-s}, {s} {-0.3*s}, 0 {0.3*s}"
g.add(dwg.path(d=path, fill=color))
elif suit == "D":
# 方块
pts = [(0, -s), (s, 0), (0, s), (-s, 0)]
g.add(dwg.polygon(points=pts, fill=color))
elif suit == "S":
# 黑桃
path = f"M 0 {-s} C {-s} 0, {-s} {0.6*s}, 0 {0.3*s} C {s} {0.6*s}, {s} 0, 0 {-s}"
g.add(dwg.path(d=path, fill=color))
g.add(dwg.rect(insert=(-0.1*s, 0.3*s), size=(0.2*s, 0.7*s), fill=color))
elif suit == "C":
# 梅花
r = s * 0.4
g.add(dwg.circle(center=(-r, -0.5*r), r=r, fill=color))
g.add(dwg.circle(center=(r, -0.5*r), r=r, fill=color))
g.add(dwg.circle(center=(0, 0.2*r), r=r, fill=color))
g.add(dwg.rect(insert=(-0.1*s, 0.6*r), size=(0.2*s, 0.8*s), fill=color))
return g
# ========== 数字牌点阵布局 ==========
LAYOUTS = {
2: [(0.5, 0.25), (0.5, 0.75)],
3: [(0.5, 0.20), (0.5, 0.50), (0.5, 0.80)],
4: [(0.25, 0.25), (0.75, 0.25), (0.25, 0.75), (0.75, 0.75)],
5: [(0.25, 0.25), (0.75, 0.25), (0.5, 0.5), (0.25, 0.75), (0.75, 0.75)],
6: [(0.25, 0.20), (0.75, 0.20), (0.25, 0.50), (0.75, 0.50),
(0.25, 0.80), (0.75, 0.80)],
7: [(0.25, 0.15), (0.75, 0.15), (0.5, 0.35), (0.25, 0.55), (0.75, 0.55),
(0.25, 0.85), (0.75, 0.85)],
8: [(0.25, 0.15), (0.75, 0.15), (0.25, 0.35), (0.75, 0.35),
(0.25, 0.65), (0.75, 0.65), (0.25, 0.85), (0.75, 0.85)],
9: [(0.25, 0.12), (0.5, 0.12), (0.75, 0.12),
(0.25, 0.35), (0.5, 0.35), (0.75, 0.35),
(0.25, 0.65), (0.5, 0.65), (0.75, 0.65)],
10:[(0.25, 0.10), (0.5, 0.10), (0.75, 0.10),
(0.25, 0.30), (0.5, 0.30), (0.75, 0.30),
(0.25, 0.70), (0.5, 0.70), (0.75, 0.70),
(0.5, 0.90)]
}
def add_pips(dwg, suit, rank):
"""绘制中间花色点阵"""
num = RANK_NUM[rank]
if num not in LAYOUTS:
return
points = LAYOUTS[num]
symbol_size_mm = 7.0
for (x_frac, y_frac) in points:
x = x_frac * CARD_W
y = y_frac * CARD_H
dwg.add(draw_suit_symbol(dwg, suit, x, y, symbol_size_mm))
def add_ace_center(dwg, suit):
"""A 牌中心大花色"""
big_size = 25
x = CARD_W / 2
y = CARD_H / 2
dwg.add(draw_suit_symbol(dwg, suit, x, y, big_size))
# ========== 人头牌 J/Q/K 简易图形 ==========
def draw_king(dwg, suit, x_center, y_center):
g = dwg.g()
s = 4
g.add(dwg.polygon([(x_center-s, y_center-s), (x_center, y_center-s-3), (x_center+s, y_center-s)], fill=SUIT_COLOR[suit]))
g.add(dwg.rect(insert=(x_center-s/2, y_center-s), size=(s, s), fill=SUIT_COLOR[suit]))
g.add(dwg.circle(center=(x_center, y_center), r=3, fill=SUIT_COLOR[suit]))
g.add(dwg.rect(insert=(x_center-s/2, y_center+2), size=(s, 5), fill=SUIT_COLOR[suit]))
return g
def draw_queen(dwg, suit, x_center, y_center):
g = dwg.g()
g.add(dwg.circle(center=(x_center, y_center), r=2.5, fill=SUIT_COLOR[suit]))
g.add(dwg.rect(insert=(x_center-3, y_center+2), size=(6, 4), fill=SUIT_COLOR[suit]))
return g
def draw_jack(dwg, suit, x_center, y_center):
g = dwg.g()
g.add(dwg.circle(center=(x_center, y_center), r=2.5, fill=SUIT_COLOR[suit]))
g.add(dwg.line(start=(x_center+2, y_center-2), end=(x_center+5, y_center+3), stroke=SUIT_COLOR[suit], stroke_width=1))
return g
def add_court_card_center(dwg, rank, suit):
x_center = CARD_W / 2
y_center = CARD_H / 2
drawer = {
'K': draw_king,
'Q': draw_queen,
'J': draw_jack
}[rank]
dwg.add(drawer(dwg, suit, x_center, y_center - 8))
bot = drawer(dwg, suit, x_center, y_center + 8)
bot["transform"] = f"translate({x_center},{y_center+8}) scale(1,-1) translate(-{x_center},-{y_center+8})"
dwg.add(bot)
# ========== 生成单张SVG卡牌 ==========
def create_card_svg(rank, suit, output_path):
dwg = svgwrite.Drawing(output_path, size=(f"{CARD_W}mm", f"{CARD_H}mm"), profile='tiny')
dwg.viewbox(width=CARD_W, height=CARD_H)
# 卡片边框
dwg.add(dwg.rect(
insert=(0,0),
size=(CARD_W, CARD_H),
rx=CORNER_RADIUS,
ry=CORNER_RADIUS,
fill='none',
stroke='#333333',
stroke_width=0.5
))
# 左上角标
dwg.add(add_corner_indicators(dwg, rank, suit, 3, 3, scale=1.0, rotation=0))
# 右下角标(倒置)
dwg.add(add_corner_indicators(dwg, rank, suit, CARD_W-3, CARD_H-3, scale=1.0, rotation=180))
# 中心图案
if rank == 'A':
add_ace_center(dwg, suit)
elif rank in ['J','Q','K']:
add_court_card_center(dwg, rank, suit)
else:
add_pips(dwg, suit, rank)
dwg.save()
print(f"SVG 已生成: {output_path}")
def create_joker_svg(joker_type, output_path):
dwg = svgwrite.Drawing(output_path, size=(f"{CARD_W}mm", f"{CARD_H}mm"), profile='tiny')
dwg.viewbox(width=CARD_W, height=CARD_H)
dwg.add(dwg.rect(
insert=(0,0),
size=(CARD_W, CARD_H),
rx=CORNER_RADIUS,
ry=CORNER_RADIUS,
fill='none',
stroke='#333333',
stroke_width=0.5
))
text_color = '#0000FF' if joker_type == 'SJ' else '#FF0000'
cx, cy = CARD_W/2, CARD_H/2
dwg.add(dwg.text("JOKER", insert=(cx, cy-8),
font_size="20pt", font_family="Arial", font_weight="bold",
fill=text_color, text_anchor="middle"))
sub = "Small" if joker_type == 'SJ' else "Big"
dwg.add(dwg.text(sub, insert=(cx, cy+10),
font_size="12pt", fill=text_color, text_anchor="middle"))
dwg.save()
print(f"SVG 已生成: {output_path}")
# ========== 批量生成全部SVG ==========
def generate_all_svgs(output_dir='assets/cards_svg'):
os.makedirs(output_dir, exist_ok=True)
ranks = ['2','3','4','5','6','7','8','9','10','J','Q','K','A']
suits = ['S','H','C','D']
for rank in ranks:
for suit in suits:
fname = f"{rank}{suit}.svg"
full_path = os.path.join(output_dir, fname)
create_card_svg(rank, suit, full_path)
# 大小王
create_joker_svg('SJ', os.path.join(output_dir, 'SJ.svg'))
create_joker_svg('BJ', os.path.join(output_dir, 'BJ.svg'))
# ========== 使用 Inkscape 转 PNG彻底抛弃 cairosvg / cairo ==========
def export_svg_to_png(svg_path, png_path, scale=2):
dpi = 96 * scale
cmd = [
"inkscape",
svg_path,
"--export-type=png",
f"--export-dpi={dpi}",
"-o", png_path
]
subprocess.run(cmd, check=True)
print(f"PNG 已生成: {png_path}")
def batch_export_png(svg_dir='assets/cards_svg', png_dir='assets/cards_png', scales=[2,4]):
os.makedirs(png_dir, exist_ok=True)
for fname in os.listdir(svg_dir):
if fname.lower().endswith('.svg'):
svg_path = os.path.join(svg_dir, fname)
base = fname.replace(".svg", "")
for scale in scales:
png_name = f"{base}@{scale}x.png"
png_path = os.path.join(png_dir, png_name)
export_svg_to_png(svg_path, png_path, scale)
# ========== 主入口 ==========
if __name__ == '__main__':
# 第一步:生成 SVG必跑现在SVG图形完全正常
generate_all_svgs()
# 第二步如需PNG先安装 Inkscape 并加入环境变量,再取消下面注释
# batch_export_png(scales=[2,4])