Files
game-cards-poker-design/backend/apps/exports/utils.py
Poker Design Developer 48629736f4 Implement Django backend and Vue frontend structure
- Django backend with projects, templates, exports apps
- SQLite database models for Project, Asset, CardLayer
- REST API endpoints for project management
- Vue frontend with Vite, Element Plus, Fabric.js
- Home page for project selection
- Editor page with Fabric.js canvas integration
2026-05-31 14:55:01 +08:00

196 lines
6.0 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 PIL import Image, ImageDraw
import io
import os
def load_image(file_path, scale=1):
"""加载图片并应用缩放"""
img = Image.open(file_path).convert('RGBA')
if scale > 1:
new_size = (int(img.width * scale), int(img.height * scale))
img = img.resize(new_size, Image.LANCZOS)
return img
def generate_symmetrical_face_card(original_image_path, scale=1):
"""
生成JQK中心对称图案
输入:原始图片路径
输出:中心对称的图像数组(上半部分、下半部分)
"""
original = load_image(original_image_path, scale)
width, height = original.size
half_height = height // 2
# 创建上半部分
top_half = original.crop((0, 0, width, half_height))
# 创建下半部分并翻转
bottom_half = original.crop((0, half_height, width, height))
bottom_half = bottom_half.transpose(Image.FLIP_TOP_BOTTOM)
return top_half, bottom_half
def render_background(canvas, layer, scale):
"""渲染背景层"""
if layer.properties:
properties = layer.properties
width = canvas.size[0]
height = canvas.size[1]
# 解析color如 '#FF0000' 或 'rgb(255,0,0)'
bg_color = properties.get('color', '#FFFFFF')
# 创建背景矩形
draw = ImageDraw.Draw(canvas, 'RGBA')
draw.rectangle(((0, 0), (width, height)), fill=bg_color + 'FF')
# 如果有纹理或图案路径
texture_path = properties.get('texture_path')
if texture_path and os.path.exists(texture_path):
texture = load_image(texture_path, scale)
bg_height = height // 4
for y in range(0, height, bg_height):
canvas.paste(texture, (0, y), texture)
def render_image_layer(canvas, project, layer, scale):
"""渲染图片层(人像、花色等)"""
if not layer.file_ref or not layer.file_ref.file_path:
return
asset_path = os.path.join(project.media_root, layer.file_ref.file_path)
if not os.path.exists(asset_path):
return
image = load_image(asset_path, scale)
# 获取位置信息
properties = layer.properties or {}
x = properties.get('x', 0)
y = properties.get('y', 0)
width = properties.get('width', image.size[0])
height = properties.get('height', image.size[1])
# 计算实际坐标
canvas_width, canvas_height = canvas.size
actual_x = (x / project.card_width) * canvas_width
actual_y = (y / project.card_height) * canvas_height
# 计算实际尺寸
actual_w = (width / project.card_width) * canvas_width
actual_h = (height / project.card_height) * canvas_height
# 裁剪图片
cropped = image.copy()
cropped.thumbnail((actual_w, actual_h), Image.LANCZOS)
# 计算居中位置
paste_x = actual_x + (actual_w - cropped.size[0]) / 2
paste_y = actual_y + (actual_h - cropped.size[1]) / 2
canvas.paste(cropped, (int(paste_x), int(paste_y)), cropped)
def render_text_layer(canvas, layer, scale):
"""渲染文字层"""
properties = layer.properties or {}
draw = ImageDraw.Draw(canvas, 'RGBA')
text = properties.get('text', '')
x = properties.get('x', 0)
y = properties.get('y', 0)
# 解析字体和颜色
font = properties.get('font', None)
if font and isinstance(font, dict):
font_size = int(font.get('size', 24) * scale)
font_path = font.get('path')
from PIL import ImageFont
if font_path and os.path.exists(font_path):
try:
custom_font = ImageFont.truetype(font_path, font_size)
except:
custom_font = None
else:
custom_font = None
else:
from PIL import ImageFont
custom_font = ImageFont.load_default()
# 转换颜色
color = properties.get('color', '#000000')
if color.startswith('#'):
r = int(color[1:3], 16)
g = int(color[3:5], 16)
b = int(color[5:7], 16)
fill = (r, g, b, 255)
else:
fill = (0, 0, 0, 255)
# 计算实际坐标和尺寸
canvas_width, canvas_height = canvas.size
actual_x = (x / project.card_width) * canvas_width
actual_y = (y / project.card_height) * canvas_height
bbox = draw.textbbox((0, 0), text, font=custom_font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
# 居中计算
paste_x = actual_x + (canvas_width * project.card_width * 0.3 - text_width) / 2
paste_y = actual_y + (canvas_height * project.card_height * 0.5 - text_height) / 2
draw.text((int(paste_x), int(paste_y)), text, font=custom_font, fill=fill)
def generate_card_png(project, card_key, resolution='standard', scale_map={ 'standard': 1, 'hd': 2, 'ultra-hd': 4 }):
"""
生成单张牌的PNG图片
Args:
project: Project对象
card_key: 牌面key'hearts-A', 'spades-K', 'joker-big'
resolution: 分辨率standard/hd/ultra-hd
scale_map: 分辨率对应的缩放比例
Returns:
Image对象
"""
scale = scale_map.get(resolution, 1)
# 创建基础画布
# 牌面坐标系
x_offset = int(50 * scale)
y_offset = int(50 * scale)
draw_width = int((project.card_width - 100) * scale)
draw_height = int((project.card_height - 100) * scale)
canvas = Image.new('RGBA', (draw_width, draw_height))
draw = ImageDraw.Draw(canvas, 'RGBA')
draw.rectangle(((0, 0), (draw_width, draw_height)), fill=(255, 255, 255, 255))
# 获取卡片类型的所有图层
layers = CardLayer.objects.filter(
project=project,
card_key=card_key,
visible=True
).order_by('z_index')
# 渲染各图层
for layer in layers:
layer_type = layer.layer_type
if layer_type == 'background':
render_background(canvas, layer, scale)
elif layer_type == 'image':
render_image_layer(canvas, project, layer, scale)
elif layer_type == 'text':
render_text_layer(canvas, layer, scale)
return canvas