feat: 背面图案支持 + 大小王素材位置微调 + 10号牌pip修复
This commit is contained in:
@@ -32,7 +32,7 @@ LAYOUT_POSITIONS = {
|
||||
7: [(0.30, 0.15), (0.70, 0.15), (0.50, 0.35), (0.30, 0.55), (0.70, 0.55), (0.30, 0.85), (0.70, 0.85)],
|
||||
8: [(0.30, 0.15), (0.70, 0.15), (0.50, 0.32), (0.30, 0.50), (0.70, 0.50), (0.50, 0.68), (0.30, 0.85), (0.70, 0.85)],
|
||||
9: [(0.30, 0.15), (0.70, 0.15), (0.50, 0.30), (0.22, 0.50), (0.50, 0.50), (0.78, 0.50), (0.50, 0.70), (0.30, 0.85), (0.70, 0.85)],
|
||||
10: [(0.30, 0.15), (0.70, 0.15), (0.30, 0.35), (0.70, 0.35), (0.50, 0.50), (0.30, 0.65), (0.70, 0.65), (0.30, 0.85), (0.70, 0.85)],
|
||||
10: [(0.30, 0.15), (0.70, 0.15), (0.30, 0.35), (0.70, 0.35), (0.50, 0.45), (0.50, 0.55), (0.30, 0.65), (0.70, 0.65), (0.30, 0.85), (0.70, 0.85)],
|
||||
}
|
||||
|
||||
|
||||
@@ -300,10 +300,25 @@ def draw_joker(canvas, design, which, project, card_key, asset):
|
||||
body_h = h - body_pad_y_top - body_pad_y_bot
|
||||
if asset:
|
||||
try:
|
||||
img = asset.copy()
|
||||
img.thumbnail((body_w, body_h), Image.LANCZOS)
|
||||
canvas.alpha_composite(img, (body_pad_x + (body_w - img.width) // 2,
|
||||
body_pad_y_top + (body_h - img.height) // 2))
|
||||
half_h = body_h // 2
|
||||
image_dx = float(design.get('image_dx', 0))
|
||||
image_dy = float(design.get('image_dy', 0))
|
||||
image_scale = float(design.get('image_scale', 1))
|
||||
offset_x = int(body_w * image_dx)
|
||||
offset_y = int(body_h * image_dy)
|
||||
|
||||
img_copy = asset.copy()
|
||||
if image_scale != 1:
|
||||
sw = max(1, int(img_copy.width * image_scale))
|
||||
sh = max(1, int(img_copy.height * image_scale))
|
||||
img_copy = img_copy.resize((sw, sh), Image.LANCZOS)
|
||||
img_copy.thumbnail((body_w, half_h), Image.LANCZOS)
|
||||
x = body_pad_x + offset_x + (body_w - img_copy.width) // 2
|
||||
y_top = body_pad_y_top + offset_y + (half_h - img_copy.height) // 2
|
||||
canvas.alpha_composite(img_copy, (x, y_top))
|
||||
img_flipped = img_copy.transpose(Image.FLIP_TOP_BOTTOM)
|
||||
y_bot = body_pad_y_top + half_h + offset_y + (half_h - img_flipped.height) // 2
|
||||
canvas.alpha_composite(img_flipped, (x, y_bot))
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
@@ -339,6 +354,44 @@ def draw_joker(canvas, design, which, project, card_key, asset):
|
||||
canvas.alpha_composite(block, (w - tw - 4 - pad, h - 2 * th - 6 - pad))
|
||||
|
||||
|
||||
def draw_back(canvas, design, asset):
|
||||
"""绘制背面:素材图 + 位置微调,无素材时退化为文字"""
|
||||
w, h = canvas.size
|
||||
body_pad_x = int(w * 0.15)
|
||||
body_pad_y_top = int(h * 0.18)
|
||||
body_pad_y_bot = int(h * 0.22)
|
||||
body_w = w - 2 * body_pad_x
|
||||
body_h = h - body_pad_y_top - body_pad_y_bot
|
||||
|
||||
image_dx = float(design.get('image_dx', 0))
|
||||
image_dy = float(design.get('image_dy', 0))
|
||||
image_scale = float(design.get('image_scale', 1))
|
||||
offset_x = int(body_w * image_dx)
|
||||
offset_y = int(body_h * image_dy)
|
||||
|
||||
if asset:
|
||||
try:
|
||||
img = asset.copy()
|
||||
if image_scale != 1:
|
||||
sw = max(1, int(img.width * image_scale))
|
||||
sh = max(1, int(img.height * image_scale))
|
||||
img = img.resize((sw, sh), Image.LANCZOS)
|
||||
img.thumbnail((body_w, body_h), Image.LANCZOS)
|
||||
x = body_pad_x + offset_x + (body_w - img.width) // 2
|
||||
y = body_pad_y_top + offset_y + (body_h - img.height) // 2
|
||||
canvas.alpha_composite(img, (x, y))
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
fnt = make_text_font('Times New Roman', max(40, int(h * 0.08)), bold=True)
|
||||
text = 'CARD BACK'
|
||||
color = hex_to_rgba(design.get('border_color', '#333333'), 255)
|
||||
bb = draw.textbbox((0, 0), text, font=fnt)
|
||||
tw, th = bb[2] - bb[0], bb[3] - bb[1]
|
||||
draw.text(((w - tw) // 2, (h - th) // 2), text, font=fnt, fill=color)
|
||||
|
||||
|
||||
def generate_card_png(project, card_key, resolution='standard'):
|
||||
"""根据项目配置生成单张牌 PNG"""
|
||||
scale_map = {'standard': 1, 'hd': 2, 'ultra-hd': 4}
|
||||
@@ -366,14 +419,12 @@ def generate_card_png(project, card_key, resolution='standard'):
|
||||
break
|
||||
draw_joker(canvas, design, which, project, card_key, asset)
|
||||
elif card_key in ('back', 'card-back'):
|
||||
# 简化:背面同整体背景 + 一行文字
|
||||
draw = ImageDraw.Draw(canvas)
|
||||
fnt = make_text_font('Times New Roman', max(40, int(h * 0.08)), bold=True)
|
||||
text = 'CARD BACK'
|
||||
color = hex_to_rgba(design.get('border_color', '#333333'), 255)
|
||||
bb = draw.textbbox((0, 0), text, font=fnt)
|
||||
tw, th = bb[2] - bb[0], bb[3] - bb[1]
|
||||
draw.text(((w - tw) // 2, (h - th) // 2), text, font=fnt, fill=color)
|
||||
back_asset = None
|
||||
for a in project.assets.filter(asset_type='back'):
|
||||
p = os.path.join('media', a.file_path) if a.file_path else None
|
||||
back_asset = load_image_safe(p) if p else None
|
||||
break
|
||||
draw_back(canvas, design, back_asset)
|
||||
else:
|
||||
# 'suit-rank'
|
||||
parts = card_key.split('-')
|
||||
|
||||
75
docs/superpowers/specs/2026-06-01-back-image-joker-offset.md
Normal file
75
docs/superpowers/specs/2026-06-01-back-image-joker-offset.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# 背面图案渲染 + 大小王素材位置微调
|
||||
|
||||
## 背景
|
||||
|
||||
当前背面牌(back)虽有 `'back'` 素材类型的上传 UI,但渲染时完全不使用素材,仅显示纯色 + "CARD BACK" 文字。大小王素材已渲染但无位置微调功能。
|
||||
|
||||
## 需求
|
||||
|
||||
### 1. 背面图案渲染
|
||||
- 优先从 `project.assets` 中读取 `asset_type='back'` 的素材图片
|
||||
- 有图时:在躯干区域居中绘制(contain 模式等比适配)
|
||||
- 无图时:保持现有回退(纯色背景 + "CARD BACK" 文字 + 边框)
|
||||
- 支持位置微调
|
||||
|
||||
### 2. 大小王素材位置微调
|
||||
- 在 DesignPanel 选中大小王时,显示 dx/dy/缩放滑块
|
||||
- 数据存储在 `card_overrides[joker-key].image_dx, image_dy, image_scale`
|
||||
- `drawJokerBody()` 绘制时应用偏移
|
||||
|
||||
## 数据模型
|
||||
|
||||
扩展 `card_overrides` JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"joker-big": { "image_dx": 0, "image_dy": 0, "image_scale": 1 },
|
||||
"joker-small": { "image_dx": 0, "image_dy": 0, "image_scale": 1 },
|
||||
"back": { "image_dx": 0, "image_dy": 0, "image_scale": 1 }
|
||||
}
|
||||
```
|
||||
|
||||
- `image_dx` / `image_dy`:相对躯体区域的比例偏移,范围 -0.05 ~ +0.05
|
||||
- `image_scale`:缩放系数,范围 0.6 ~ 1.4
|
||||
|
||||
## 前端实现
|
||||
|
||||
### cardRenderer.js
|
||||
|
||||
#### `drawBackSide()` 改动
|
||||
- 新增 `project` 参数以读取素材和 card_overrides
|
||||
- 调用 `assetByType(project, 'back', 'back')` 获取素材
|
||||
- 有素材时绘制图片(contain 适配 + 偏移应用)
|
||||
- `renderCard()` 调用处传入 `project`
|
||||
|
||||
#### `drawJokerBody()` 改动
|
||||
- 读取 `override = design.image_dx/dy/scale`(由 `getEffectiveDesign()` 合并后传入的 design 包含 card_overrides 字段)
|
||||
- 在图片绘制坐标上叠加 `bodyW * image_dx`、`bodyH * image_dy`
|
||||
- 缩放叠加 `drawW * image_scale`、`drawH * image_scale`
|
||||
|
||||
### projectStore.js
|
||||
- 开放 `canHaveOverride` 覆盖 joker 和 back
|
||||
- 添加图片偏移的 patch 方法(复用已有 `patchCardOverride` 或新增)
|
||||
|
||||
### DesignPanel.vue
|
||||
- 移除 `canHaveOverride` 对 joker/back 的限制
|
||||
- 当当前牌为 joker 或 back 时,显示图片位置微调滑块(dx/dy/缩放)
|
||||
|
||||
## 后端实现
|
||||
|
||||
### exports/utils.py
|
||||
|
||||
#### `generate_card_png()` back 分支
|
||||
- 查找 `project.assets.filter(asset_type='back')` 获取素材
|
||||
- `load_image_safe()` 加载图片
|
||||
- `draw_back()` 函数中用 `thumbnail` contain 适配 + 偏移
|
||||
|
||||
#### `draw_joker()` 改动
|
||||
- 读取 `card_overrides` 的 image_dx/dy/scale
|
||||
- 在坐标计算中叠加偏移和缩放
|
||||
|
||||
## UI 滑块参数
|
||||
- dx: range -0.05 ~ +0.05, step 0.005
|
||||
- dy: range -0.05 ~ +0.05, step 0.005
|
||||
- scale: range 0.6 ~ 1.4, step 0.05
|
||||
(与现有 number_layout 一致)
|
||||
@@ -0,0 +1,75 @@
|
||||
# 大小王对称渲染
|
||||
|
||||
## 背景
|
||||
|
||||
当前大小王在有素材图片时,将整张图居中适配绘制在躯体区域,无对称处理。需要改为:上半区显示原图,下半区显示原图的垂直镜像,实现类似 JQK 人像的对称效果。
|
||||
|
||||
## 需求
|
||||
|
||||
- 上传的素材图片作为**上半部分**的源图
|
||||
- 躯体区域纵向平分:上半区绘制原图(居中适配),下半区绘制垂直翻转后的同一张图
|
||||
- 角标 JOKER + BIG/SMALL 不变
|
||||
- 无素材时回退到白字 "JOKER" 居中(不变)
|
||||
|
||||
## 修改范围
|
||||
|
||||
| 文件 | 函数 |
|
||||
|------|------|
|
||||
| `frontend/src/utils/cardRenderer.js` | `drawJokerBody()` |
|
||||
| `backend/apps/exports/utils.py` | `draw_joker()` |
|
||||
|
||||
## 前端实现 (Canvas2D)
|
||||
|
||||
在 `drawJokerBody` 中,当有素材图片时:
|
||||
|
||||
```js
|
||||
// 躯体区域上下平分
|
||||
const halfH = bodyH / 2
|
||||
|
||||
// 上半区:原图居中适配
|
||||
const fit = fitSize(imgW, imgH, bodyW, halfH)
|
||||
ctx.drawImage(img, bodyX + (bodyW - fit.w) / 2, bodyY + (halfH - fit.h) / 2, fit.w, fit.h)
|
||||
|
||||
// 下半区:垂直翻转后居中适配
|
||||
ctx.save()
|
||||
ctx.translate(0, bodyY + halfH) // 移动到下半区中线
|
||||
ctx.scale(1, -1) // 垂直翻转
|
||||
ctx.drawImage(img, bodyX + (bodyW - fit.w) / 2, -(halfH - fit.h) / 2, fit.w, fit.h)
|
||||
ctx.restore()
|
||||
```
|
||||
|
||||
## 后端实现 (PIL)
|
||||
|
||||
在 `draw_joker` 中,当有素材图片时:
|
||||
|
||||
```python
|
||||
half_h = body_h // 2
|
||||
|
||||
# 上半区:原图居中适配
|
||||
img.thumbnail((body_w, half_h), Image.LANCZOS)
|
||||
x1 = body_x + (body_w - img.width) // 2
|
||||
y1 = body_y + (half_h - img.height) // 2
|
||||
canvas.paste(img, (x1, y1), img)
|
||||
|
||||
# 下半区:垂直翻转后居中适配
|
||||
img_flipped = img.transpose(Image.FLIP_TOP_BOTTOM)
|
||||
y2 = body_y + half_h + (half_h - img_flipped.height) // 2
|
||||
canvas.paste(img_flipped, (x1, y2), img_flipped)
|
||||
```
|
||||
|
||||
Note: `thumbnail` 是原地修改,所以上半区粘贴完成后需要重新加载原图再做翻转,或者先复制一份。
|
||||
|
||||
修正:
|
||||
```python
|
||||
img_copy = img.copy()
|
||||
img_copy.thumbnail((body_w, half_h), Image.LANCZOS)
|
||||
# 上半区粘贴 img_copy
|
||||
img_flipped = img_copy.transpose(Image.FLIP_TOP_BOTTOM)
|
||||
# 下半区粘贴 img_flipped
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 前端当前使用 `Math.max` (fill) 适配策略,改为对称后每个半区内图片尺寸一致
|
||||
- 后端当前使用 `thumbnail` (contain) 适配,保持一致即可
|
||||
- 躯体 padding 前端 18%/22%,后端 15%/20%,各自保持不变
|
||||
@@ -79,6 +79,24 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 图片位置微调(大小王 / 背面) -->
|
||||
<section v-if="showImageOffset">
|
||||
<h4>图片位置微调</h4>
|
||||
<p class="hint">拖动滑块微调素材图片的位置与缩放(0 = 默认)</p>
|
||||
<div class="pip-row">
|
||||
<label class="mini-label">dx</label>
|
||||
<input type="range" min="-0.05" max="0.05" step="0.005"
|
||||
:value="imageOffsetVal('image_dx')" @input="setImageOffset('image_dx', $event.target.value)" />
|
||||
<label class="mini-label">dy</label>
|
||||
<input type="range" min="-0.05" max="0.05" step="0.005"
|
||||
:value="imageOffsetVal('image_dy')" @input="setImageOffset('image_dy', $event.target.value)" />
|
||||
<label class="mini-label">缩放</label>
|
||||
<input type="range" min="0.6" max="1.4" step="0.05"
|
||||
:value="imageOffsetVal('image_scale')" @input="setImageOffset('image_scale', $event.target.value)" />
|
||||
</div>
|
||||
<button @click="resetImageOffset" class="mini ghost">重置图片位置</button>
|
||||
</section>
|
||||
|
||||
<!-- 数字牌花色位置微调 -->
|
||||
<section v-if="isNumberCard">
|
||||
<h4>数字牌花色位置微调</h4>
|
||||
@@ -141,9 +159,7 @@ function setBorder(path, v) { store.patchDesign(path, v) }
|
||||
function setSuit(suit, path, v) { store.patchDesign(`suit_symbols.${suit}.${path}`, v) }
|
||||
function setDesign(path, v) { store.patchDesign(path, v) }
|
||||
|
||||
const canHaveOverride = computed(() => {
|
||||
return !isJoker(store.currentCard) && store.currentCard !== 'back'
|
||||
})
|
||||
const canHaveOverride = computed(() => true)
|
||||
function setOverrideBg() {
|
||||
if (!canHaveOverride.value) return
|
||||
store.patchCardOverride(store.currentCard, 'background_color', design.value.background_color)
|
||||
@@ -161,6 +177,25 @@ const isNumberCard = computed(() => {
|
||||
return /^[0-9]+$/.test(r) || r === 'A'
|
||||
})
|
||||
|
||||
const showImageOffset = computed(() => {
|
||||
return isJoker(store.currentCard) || store.currentCard === 'back'
|
||||
})
|
||||
|
||||
function imageOffsetVal(key) {
|
||||
if (!override.value) return key === 'image_scale' ? 1 : 0
|
||||
const v = Number(override.value[key])
|
||||
if (Number.isNaN(v)) return key === 'image_scale' ? 1 : 0
|
||||
return v
|
||||
}
|
||||
function setImageOffset(key, val) {
|
||||
store.patchCardOverride(store.currentCard, key, parseFloat(val))
|
||||
}
|
||||
function resetImageOffset() {
|
||||
store.patchCardOverride(store.currentCard, 'image_dx', 0)
|
||||
store.patchCardOverride(store.currentCard, 'image_dy', 0)
|
||||
store.patchCardOverride(store.currentCard, 'image_scale', 1)
|
||||
}
|
||||
|
||||
const selectedRank = ref(1)
|
||||
watch(() => store.currentCard, () => {
|
||||
if (isNumberCard.value) {
|
||||
|
||||
@@ -16,7 +16,7 @@ export const LAYOUT_POSITIONS = {
|
||||
7: [{ x: 0.30, y: 0.15 }, { x: 0.70, y: 0.15 }, { x: 0.50, y: 0.35 }, { x: 0.30, y: 0.55 }, { x: 0.70, y: 0.55 }, { x: 0.30, y: 0.85 }, { x: 0.70, y: 0.85 }],
|
||||
8: [{ x: 0.30, y: 0.15 }, { x: 0.70, y: 0.15 }, { x: 0.50, y: 0.32 }, { x: 0.30, y: 0.50 }, { x: 0.70, y: 0.50 }, { x: 0.50, y: 0.68 }, { x: 0.30, y: 0.85 }, { x: 0.70, y: 0.85 }],
|
||||
9: [{ x: 0.30, y: 0.15 }, { x: 0.70, y: 0.15 }, { x: 0.50, y: 0.30 }, { x: 0.22, y: 0.50 }, { x: 0.50, y: 0.50 }, { x: 0.78, y: 0.50 }, { x: 0.50, y: 0.70 }, { x: 0.30, y: 0.85 }, { x: 0.70, y: 0.85 }],
|
||||
10: [{ x: 0.30, y: 0.15 }, { x: 0.70, y: 0.15 }, { x: 0.30, y: 0.35 }, { x: 0.70, y: 0.35 }, { x: 0.50, y: 0.50 }, { x: 0.30, y: 0.65 }, { x: 0.70, y: 0.65 }, { x: 0.30, y: 0.85 }, { x: 0.70, y: 0.85 }],
|
||||
10: [{ x: 0.30, y: 0.15 }, { x: 0.70, y: 0.15 }, { x: 0.30, y: 0.35 }, { x: 0.70, y: 0.35 }, { x: 0.50, y: 0.45 }, { x: 0.50, y: 0.55 }, { x: 0.30, y: 0.65 }, { x: 0.70, y: 0.65 }, { x: 0.30, y: 0.85 }, { x: 0.70, y: 0.85 }],
|
||||
}
|
||||
|
||||
export const SUIT_TEXT = {
|
||||
|
||||
@@ -271,15 +271,30 @@ async function drawJokerBody(ctx, w, h, which, design, project) {
|
||||
}
|
||||
|
||||
if (img && img.complete && img.naturalWidth) {
|
||||
const ratio = img.naturalWidth / img.naturalHeight
|
||||
const target = bodyW / bodyH
|
||||
const halfH = bodyH / 2
|
||||
const imgRatio = img.naturalWidth / img.naturalHeight
|
||||
const target = bodyW / halfH
|
||||
let drawW, drawH
|
||||
if (ratio > target) {
|
||||
drawW = bodyW; drawH = bodyW / ratio
|
||||
if (imgRatio > target) {
|
||||
drawW = bodyW; drawH = bodyW / imgRatio
|
||||
} else {
|
||||
drawH = bodyH; drawW = bodyH * ratio
|
||||
drawH = halfH; drawW = halfH * imgRatio
|
||||
}
|
||||
ctx.drawImage(img, padX + (bodyW - drawW) / 2, padTop + (bodyH - drawH) / 2, drawW, drawH)
|
||||
const imageDx = Number(design.image_dx) || 0
|
||||
const imageDy = Number(design.image_dy) || 0
|
||||
const imageScale = Number(design.image_scale) || 1
|
||||
const finalW = Math.max(1, drawW * imageScale)
|
||||
const finalH = Math.max(1, drawH * imageScale)
|
||||
const offsetX = bodyW * imageDx
|
||||
const offsetY = bodyH * imageDy
|
||||
const topX = padX + offsetX + (bodyW - finalW) / 2
|
||||
const topY = padTop + offsetY + (halfH - finalH) / 2
|
||||
ctx.drawImage(img, topX, topY, finalW, finalH)
|
||||
ctx.save()
|
||||
ctx.translate(0, padTop + bodyH)
|
||||
ctx.scale(1, -1)
|
||||
ctx.drawImage(img, topX, (halfH - finalH) / 2 - offsetY, finalW, finalH)
|
||||
ctx.restore()
|
||||
} else {
|
||||
// 退化
|
||||
const big = Math.round(h * 0.25)
|
||||
@@ -313,9 +328,44 @@ async function drawJokerBody(ctx, w, h, which, design, project) {
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
function drawBackSide(ctx, w, h, design) {
|
||||
async function drawBackSide(ctx, w, h, design, project) {
|
||||
ctx.fillStyle = design.background_color || '#1A237E'
|
||||
ctx.fillRect(0, 0, w, h)
|
||||
|
||||
const padX = Math.round(w * 0.15)
|
||||
const padTop = Math.round(h * 0.18)
|
||||
const padBot = Math.round(h * 0.22)
|
||||
const bodyW = w - 2 * padX
|
||||
const bodyH = h - padTop - padBot
|
||||
|
||||
const imageDx = Number(design.image_dx) || 0
|
||||
const imageDy = Number(design.image_dy) || 0
|
||||
const imageScale = Number(design.image_scale) || 1
|
||||
const offsetX = bodyW * imageDx
|
||||
const offsetY = bodyH * imageDy
|
||||
|
||||
const asset = assetByType(project, 'back', 'back')
|
||||
let img = null
|
||||
if (asset?.file_url) {
|
||||
img = imageCache.get(asset.file_url) || null
|
||||
if (img) await loadImage(asset.file_url)
|
||||
}
|
||||
|
||||
if (img && img.complete && img.naturalWidth) {
|
||||
const ratio = img.naturalWidth / img.naturalHeight
|
||||
const target = bodyW / bodyH
|
||||
let drawW, drawH
|
||||
if (ratio > target) {
|
||||
drawW = bodyW; drawH = bodyW / ratio
|
||||
} else {
|
||||
drawH = bodyH; drawW = bodyH * ratio
|
||||
}
|
||||
const finalW = Math.max(1, drawW * imageScale)
|
||||
const finalH = Math.max(1, drawH * imageScale)
|
||||
const drawX = padX + offsetX + (bodyW - finalW) / 2
|
||||
const drawY = padTop + offsetY + (bodyH - finalH) / 2
|
||||
ctx.drawImage(img, drawX, drawY, finalW, finalH)
|
||||
} else {
|
||||
ctx.save()
|
||||
ctx.strokeStyle = design.border_color || '#FFFFFF'
|
||||
ctx.lineWidth = 6
|
||||
@@ -329,6 +379,7 @@ function drawBackSide(ctx, w, h, design) {
|
||||
ctx.fillText('CARD BACK', w / 2, h / 2)
|
||||
ctx.restore()
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- 公开 API ---------- */
|
||||
|
||||
@@ -365,7 +416,7 @@ export async function renderCard(canvas, project, cardKey) {
|
||||
await drawJokerBody(ctx, w, h, which, design, project)
|
||||
drawBorder(ctx, w, h, design)
|
||||
} else if (cardKey === 'back') {
|
||||
drawBackSide(ctx, w, h, design)
|
||||
await drawBackSide(ctx, w, h, design, project)
|
||||
} else {
|
||||
const [suit, rank] = cardKey.split('-')
|
||||
drawBackground(ctx, w, h, design)
|
||||
|
||||
Reference in New Issue
Block a user