Compare commits
7 Commits
7417a4a893
...
1da35e4eab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1da35e4eab | ||
|
|
76934eb094 | ||
|
|
5724cb7d97 | ||
|
|
8050ec3e06 | ||
|
|
8b99784c91 | ||
|
|
172c90be7f | ||
|
|
cea66988b8 |
@@ -66,6 +66,15 @@ def get_effective_design(project, card_key):
|
|||||||
return base
|
return base
|
||||||
|
|
||||||
|
|
||||||
|
def get_effective_back_design(project):
|
||||||
|
"""获取背面设计配置,合并 back_design 与 card_overrides['back']"""
|
||||||
|
base = dict(project.back_design or {})
|
||||||
|
overrides = (project.card_overrides or {}).get('back', {})
|
||||||
|
if overrides:
|
||||||
|
base.update(overrides)
|
||||||
|
return base
|
||||||
|
|
||||||
|
|
||||||
def load_image_safe(file_path):
|
def load_image_safe(file_path):
|
||||||
"""加载图片,找不到时返回 None"""
|
"""加载图片,找不到时返回 None"""
|
||||||
if not file_path:
|
if not file_path:
|
||||||
@@ -373,9 +382,13 @@ def draw_joker(canvas, design, which, project, card_key, asset):
|
|||||||
canvas.alpha_composite(block, (w - tw - 4 - pad, h - 2 * th - 6 - pad))
|
canvas.alpha_composite(block, (w - tw - 4 - pad, h - 2 * th - 6 - pad))
|
||||||
|
|
||||||
|
|
||||||
def draw_back(canvas, design, asset):
|
def draw_back(canvas, back_design, asset):
|
||||||
"""绘制背面:素材图 + 位置微调,无素材时退化为文字"""
|
"""绘制背面:素材图 + 位置微调 + 色调叠加,无素材时退化为文字"""
|
||||||
w, h = canvas.size
|
w, h = canvas.size
|
||||||
|
# 填充背景色
|
||||||
|
bg_color = back_design.get('background_color', '#1A237E') or '#1A237E'
|
||||||
|
canvas.paste(hex_to_rgba(bg_color, 255), (0, 0, w, h))
|
||||||
|
|
||||||
body_pad_x = int(w * 0.15)
|
body_pad_x = int(w * 0.15)
|
||||||
body_pad_y_top = int(h * 0.18)
|
body_pad_y_top = int(h * 0.18)
|
||||||
body_pad_y_bot = int(h * 0.22)
|
body_pad_y_bot = int(h * 0.22)
|
||||||
@@ -384,9 +397,9 @@ def draw_back(canvas, design, asset):
|
|||||||
|
|
||||||
if asset:
|
if asset:
|
||||||
try:
|
try:
|
||||||
image_dx = float(design.get('image_dx', 0))
|
image_dx = float(back_design.get('image_dx', 0))
|
||||||
image_dy = float(design.get('image_dy', 0))
|
image_dy = float(back_design.get('image_dy', 0))
|
||||||
image_scale = float(design.get('image_scale', 1))
|
image_scale = float(back_design.get('image_scale', 1))
|
||||||
offset_x = int(body_w * image_dx)
|
offset_x = int(body_w * image_dx)
|
||||||
offset_y = int(body_h * image_dy)
|
offset_y = int(body_h * image_dy)
|
||||||
|
|
||||||
@@ -399,13 +412,26 @@ def draw_back(canvas, design, asset):
|
|||||||
x = body_pad_x + offset_x + (body_w - img.width) // 2
|
x = body_pad_x + offset_x + (body_w - img.width) // 2
|
||||||
y = body_pad_y_top + offset_y + (body_h - img.height) // 2
|
y = body_pad_y_top + offset_y + (body_h - img.height) // 2
|
||||||
canvas.alpha_composite(img, (x, y))
|
canvas.alpha_composite(img, (x, y))
|
||||||
|
|
||||||
|
# 色调叠加
|
||||||
|
pattern_color = back_design.get('pattern_color')
|
||||||
|
if pattern_color:
|
||||||
|
tint = Image.new('RGBA', (w, h), hex_to_rgba(pattern_color, 80))
|
||||||
|
canvas.alpha_composite(tint)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
# 退化:绘制边框 + 文字
|
||||||
|
border_color = back_design.get('border_color', '#C0A050') or '#C0A050'
|
||||||
draw = ImageDraw.Draw(canvas)
|
draw = ImageDraw.Draw(canvas)
|
||||||
|
border_width = int(back_design.get('border_width', 3) or 3)
|
||||||
|
if border_width > 0:
|
||||||
|
half = max(1, border_width // 2)
|
||||||
|
draw.rectangle(((half, half), (w - half, h - half)),
|
||||||
|
outline=hex_to_rgba(border_color, 255), width=border_width)
|
||||||
fnt = make_text_font('Times New Roman', max(40, int(h * 0.08)), bold=True)
|
fnt = make_text_font('Times New Roman', max(40, int(h * 0.08)), bold=True)
|
||||||
text = 'CARD BACK'
|
text = 'CARD BACK'
|
||||||
color = hex_to_rgba(design.get('border_color', '#333333'), 255)
|
color = hex_to_rgba(border_color, 255)
|
||||||
bb = draw.textbbox((0, 0), text, font=fnt)
|
bb = draw.textbbox((0, 0), text, font=fnt)
|
||||||
tw, th = bb[2] - bb[0], bb[3] - bb[1]
|
tw, th = bb[2] - bb[0], bb[3] - bb[1]
|
||||||
draw.text(((w - tw) // 2, (h - th) // 2), text, font=fnt, fill=color)
|
draw.text(((w - tw) // 2, (h - th) // 2), text, font=fnt, fill=color)
|
||||||
@@ -439,12 +465,14 @@ def generate_card_png(project, card_key, resolution='standard'):
|
|||||||
break
|
break
|
||||||
draw_joker(canvas, design, which, project, card_key, asset)
|
draw_joker(canvas, design, which, project, card_key, asset)
|
||||||
elif card_key in ('back', 'card-back'):
|
elif card_key in ('back', 'card-back'):
|
||||||
|
back_design = get_effective_back_design(project)
|
||||||
back_asset = None
|
back_asset = None
|
||||||
for a in project.assets.filter(asset_type='back'):
|
for a in project.assets.filter(asset_type='back'):
|
||||||
p = os.path.join('media', a.file_path) if a.file_path else None
|
p = os.path.join('media', a.file_path) if a.file_path else None
|
||||||
back_asset = load_image_safe(p) if p else None
|
back_asset = load_image_safe(p) if p else None
|
||||||
break
|
break
|
||||||
draw_back(canvas, design, back_asset)
|
draw_back(canvas, back_design, back_asset)
|
||||||
|
return canvas
|
||||||
else:
|
else:
|
||||||
# 'suit-rank'
|
# 'suit-rank'
|
||||||
parts = card_key.split('-')
|
parts = card_key.split('-')
|
||||||
|
|||||||
19
backend/apps/projects/migrations/0004_project_back_design.py
Normal file
19
backend/apps/projects/migrations/0004_project_back_design.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.1.4 on 2026-06-03 13:58
|
||||||
|
|
||||||
|
import apps.projects.models
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('projects', '0003_libraryasset'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='project',
|
||||||
|
name='back_design',
|
||||||
|
field=models.JSONField(default=apps.projects.models.default_back_design),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -36,6 +36,20 @@ def default_card_overrides():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def default_back_design():
|
||||||
|
"""背面专用设计配置(独立于正面)"""
|
||||||
|
return {
|
||||||
|
'background_color': '#1A237E',
|
||||||
|
'border_color': '#C0A050',
|
||||||
|
'border_width': 3,
|
||||||
|
'pattern_color': None,
|
||||||
|
'image': None,
|
||||||
|
'image_dx': 0,
|
||||||
|
'image_dy': 0,
|
||||||
|
'image_scale': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Project(models.Model):
|
class Project(models.Model):
|
||||||
"""项目配置模型"""
|
"""项目配置模型"""
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
@@ -47,6 +61,8 @@ class Project(models.Model):
|
|||||||
design = models.JSONField(default=default_design)
|
design = models.JSONField(default=default_design)
|
||||||
# 每张牌对项目级配置的覆盖
|
# 每张牌对项目级配置的覆盖
|
||||||
card_overrides = models.JSONField(default=default_card_overrides)
|
card_overrides = models.JSONField(default=default_card_overrides)
|
||||||
|
# 背面专用设计配置(独立于正面 design)
|
||||||
|
back_design = models.JSONField(default=default_back_design)
|
||||||
# 数字牌花色位置微调(相对 0~1)
|
# 数字牌花色位置微调(相对 0~1)
|
||||||
# { '1': [{'dx':0,'dy':0,'scale':1}, ...], '2': [...], ... }
|
# { '1': [{'dx':0,'dy':0,'scale':1}, ...], '2': [...], ... }
|
||||||
number_layout = models.JSONField(default=dict)
|
number_layout = models.JSONField(default=dict)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class ProjectSerializer(serializers.ModelSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'template_id',
|
'id', 'name', 'template_id',
|
||||||
'card_width', 'card_height',
|
'card_width', 'card_height',
|
||||||
'design', 'card_overrides', 'number_layout', 'face_orientations',
|
'design', 'back_design', 'card_overrides', 'number_layout', 'face_orientations',
|
||||||
'export_resolution', 'export_include_back',
|
'export_resolution', 'export_include_back',
|
||||||
'created_at', 'updated_at',
|
'created_at', 'updated_at',
|
||||||
]
|
]
|
||||||
@@ -47,7 +47,7 @@ class ProjectDetailSerializer(serializers.ModelSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'template_id',
|
'id', 'name', 'template_id',
|
||||||
'card_width', 'card_height',
|
'card_width', 'card_height',
|
||||||
'design', 'card_overrides', 'number_layout', 'face_orientations',
|
'design', 'back_design', 'card_overrides', 'number_layout', 'face_orientations',
|
||||||
'export_resolution', 'export_include_back',
|
'export_resolution', 'export_include_back',
|
||||||
'assets', 'layers',
|
'assets', 'layers',
|
||||||
'created_at', 'updated_at',
|
'created_at', 'updated_at',
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ def project_list(request):
|
|||||||
data['card_overrides'] = Project._meta.get_field('card_overrides').default()
|
data['card_overrides'] = Project._meta.get_field('card_overrides').default()
|
||||||
if 'number_layout' not in data:
|
if 'number_layout' not in data:
|
||||||
data['number_layout'] = Project._meta.get_field('number_layout').default()
|
data['number_layout'] = Project._meta.get_field('number_layout').default()
|
||||||
|
if 'back_design' not in data:
|
||||||
|
data['back_design'] = Project._meta.get_field('back_design').default()
|
||||||
# 抽出 template_id(不写进 Project 字段)
|
# 抽出 template_id(不写进 Project 字段)
|
||||||
template_id = data.pop('template_id', None)
|
template_id = data.pop('template_id', None)
|
||||||
serializer = ProjectSerializer(data=data)
|
serializer = ProjectSerializer(data=data)
|
||||||
@@ -82,13 +84,14 @@ def project_save_design(request, pk):
|
|||||||
except Project.DoesNotExist:
|
except Project.DoesNotExist:
|
||||||
return Response({'error': 'Project not found'}, status=status.HTTP_404_NOT_FOUND)
|
return Response({'error': 'Project not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
for field in ('design', 'card_overrides', 'number_layout', 'face_orientations'):
|
for field in ('design', 'back_design', 'card_overrides', 'number_layout', 'face_orientations'):
|
||||||
if field in request.data:
|
if field in request.data:
|
||||||
setattr(project, field, request.data[field])
|
setattr(project, field, request.data[field])
|
||||||
project.save()
|
project.save()
|
||||||
return Response({
|
return Response({
|
||||||
'ok': True,
|
'ok': True,
|
||||||
'design': project.design,
|
'design': project.design,
|
||||||
|
'back_design': project.back_design,
|
||||||
'card_overrides': project.card_overrides,
|
'card_overrides': project.card_overrides,
|
||||||
'number_layout': project.number_layout,
|
'number_layout': project.number_layout,
|
||||||
'face_orientations': project.face_orientations,
|
'face_orientations': project.face_orientations,
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ def apply_template_to_project(project, template):
|
|||||||
# 背景色
|
# 背景色
|
||||||
base_design['background_color'] = template.color_background
|
base_design['background_color'] = template.color_background
|
||||||
project.design = base_design
|
project.design = base_design
|
||||||
|
|
||||||
|
# 2. 初始化背面设计(如果尚未设置)
|
||||||
|
if not project.back_design:
|
||||||
|
from apps.projects.models import default_back_design
|
||||||
|
project.back_design = default_back_design()
|
||||||
|
|
||||||
project.save()
|
project.save()
|
||||||
|
|
||||||
# 2. 复制主题素材(如果绑定了 theme_id)
|
# 2. 复制主题素材(如果绑定了 theme_id)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { DEFAULT_DESIGN, listAllCards } from '@/utils/cardLayout.js'
|
import { DEFAULT_DESIGN, DEFAULT_BACK_DESIGN, listAllCards } from '@/utils/cardLayout.js'
|
||||||
|
|
||||||
const API = '/api'
|
const API = '/api'
|
||||||
|
|
||||||
@@ -30,6 +30,13 @@ export const useProjectStore = defineStore('project', () => {
|
|||||||
return { ...base, ...ovr }
|
return { ...base, ...ovr }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const effectiveBackDesign = computed(() => {
|
||||||
|
if (!project.value) return DEFAULT_BACK_DESIGN
|
||||||
|
const base = JSON.parse(JSON.stringify(project.value.back_design || DEFAULT_BACK_DESIGN))
|
||||||
|
const ovr = (project.value.card_overrides || {})['back'] || {}
|
||||||
|
return { ...base, ...ovr }
|
||||||
|
})
|
||||||
|
|
||||||
async function fetchProjects() {
|
async function fetchProjects() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
error.value = ''
|
error.value = ''
|
||||||
@@ -48,6 +55,7 @@ export const useProjectStore = defineStore('project', () => {
|
|||||||
name,
|
name,
|
||||||
template_id: templateId,
|
template_id: templateId,
|
||||||
design: DEFAULT_DESIGN,
|
design: DEFAULT_DESIGN,
|
||||||
|
back_design: DEFAULT_BACK_DESIGN,
|
||||||
card_overrides: {},
|
card_overrides: {},
|
||||||
number_layout: {},
|
number_layout: {},
|
||||||
})
|
})
|
||||||
@@ -69,6 +77,7 @@ export const useProjectStore = defineStore('project', () => {
|
|||||||
if (!r.data.design) r.data.design = JSON.parse(JSON.stringify(DEFAULT_DESIGN))
|
if (!r.data.design) r.data.design = JSON.parse(JSON.stringify(DEFAULT_DESIGN))
|
||||||
if (!r.data.card_overrides) r.data.card_overrides = {}
|
if (!r.data.card_overrides) r.data.card_overrides = {}
|
||||||
if (!r.data.number_layout) r.data.number_layout = {}
|
if (!r.data.number_layout) r.data.number_layout = {}
|
||||||
|
if (!r.data.back_design) r.data.back_design = JSON.parse(JSON.stringify(DEFAULT_BACK_DESIGN))
|
||||||
if (!r.data.assets) r.data.assets = []
|
if (!r.data.assets) r.data.assets = []
|
||||||
project.value = r.data
|
project.value = r.data
|
||||||
currentCard.value = 'spade-A'
|
currentCard.value = 'spade-A'
|
||||||
@@ -100,6 +109,7 @@ export const useProjectStore = defineStore('project', () => {
|
|||||||
try {
|
try {
|
||||||
await axios.post(`${API}/projects/${project.value.id}/design/`, {
|
await axios.post(`${API}/projects/${project.value.id}/design/`, {
|
||||||
design: project.value.design,
|
design: project.value.design,
|
||||||
|
back_design: project.value.back_design,
|
||||||
card_overrides: project.value.card_overrides,
|
card_overrides: project.value.card_overrides,
|
||||||
number_layout: project.value.number_layout,
|
number_layout: project.value.number_layout,
|
||||||
face_orientations: project.value.face_orientations || {},
|
face_orientations: project.value.face_orientations || {},
|
||||||
@@ -118,6 +128,15 @@ export const useProjectStore = defineStore('project', () => {
|
|||||||
scheduleSaveDesign()
|
scheduleSaveDesign()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function patchBackDesign(path, value) {
|
||||||
|
if (!project.value) return
|
||||||
|
if (!project.value.back_design) {
|
||||||
|
project.value.back_design = JSON.parse(JSON.stringify(DEFAULT_BACK_DESIGN))
|
||||||
|
}
|
||||||
|
setPath(project.value.back_design, path, value)
|
||||||
|
scheduleSaveDesign()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改某张牌对项目级设计的覆盖
|
* 修改某张牌对项目级设计的覆盖
|
||||||
*/
|
*/
|
||||||
@@ -170,6 +189,7 @@ export const useProjectStore = defineStore('project', () => {
|
|||||||
error,
|
error,
|
||||||
allCards,
|
allCards,
|
||||||
effectiveDesign,
|
effectiveDesign,
|
||||||
|
effectiveBackDesign,
|
||||||
fetchProjects,
|
fetchProjects,
|
||||||
createProject,
|
createProject,
|
||||||
deleteProject,
|
deleteProject,
|
||||||
@@ -178,6 +198,7 @@ export const useProjectStore = defineStore('project', () => {
|
|||||||
saveDesign,
|
saveDesign,
|
||||||
scheduleSaveDesign,
|
scheduleSaveDesign,
|
||||||
patchDesign,
|
patchDesign,
|
||||||
|
patchBackDesign,
|
||||||
patchCardOverride,
|
patchCardOverride,
|
||||||
clearCardOverride,
|
clearCardOverride,
|
||||||
patchNumberLayout,
|
patchNumberLayout,
|
||||||
|
|||||||
@@ -58,6 +58,15 @@ export function getEffectiveDesign(project, cardKey) {
|
|||||||
return { ...base, ...overrides }
|
return { ...base, ...overrides }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并项目级 back_design 与背面 card_overrides
|
||||||
|
*/
|
||||||
|
export function getEffectiveBackDesign(project) {
|
||||||
|
const base = JSON.parse(JSON.stringify(project?.back_design || DEFAULT_BACK_DESIGN))
|
||||||
|
const overrides = (project?.card_overrides || {})['back'] || {}
|
||||||
|
return { ...base, ...overrides }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算数字牌 (1-10) 实际的花色位置(绝对像素)
|
* 计算数字牌 (1-10) 实际的花色位置(绝对像素)
|
||||||
* - 默认按 LAYOUT_POSITIONS
|
* - 默认按 LAYOUT_POSITIONS
|
||||||
@@ -136,3 +145,14 @@ export const DEFAULT_DESIGN = {
|
|||||||
font_color: '#000000',
|
font_color: '#000000',
|
||||||
corner_offset: { x: 0, y: 0 },
|
corner_offset: { x: 0, y: 0 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_BACK_DESIGN = {
|
||||||
|
background_color: '#1A237E',
|
||||||
|
border_color: '#C0A050',
|
||||||
|
border_width: 3,
|
||||||
|
pattern_color: null,
|
||||||
|
image: null,
|
||||||
|
image_dx: 0,
|
||||||
|
image_dy: 0,
|
||||||
|
image_scale: 1,
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
isJoker,
|
isJoker,
|
||||||
computeNumberPipPositions,
|
computeNumberPipPositions,
|
||||||
getEffectiveDesign,
|
getEffectiveDesign,
|
||||||
|
getEffectiveBackDesign,
|
||||||
|
DEFAULT_BACK_DESIGN,
|
||||||
} from './cardLayout.js'
|
} from './cardLayout.js'
|
||||||
|
|
||||||
const CARD_W = 750
|
const CARD_W = 750
|
||||||
@@ -42,6 +44,7 @@ function loadImage(url) {
|
|||||||
async function preloadAll(project) {
|
async function preloadAll(project) {
|
||||||
const urls = new Set()
|
const urls = new Set()
|
||||||
if (project.design?.background_image) urls.add(project.design.background_image)
|
if (project.design?.background_image) urls.add(project.design.background_image)
|
||||||
|
if (project.back_design?.image) urls.add(project.back_design.image)
|
||||||
for (const s of Object.keys(project.design?.suit_symbols || {})) {
|
for (const s of Object.keys(project.design?.suit_symbols || {})) {
|
||||||
const sym = project.design.suit_symbols[s]
|
const sym = project.design.suit_symbols[s]
|
||||||
if (sym?.type === 'image' && sym.asset_id) {
|
if (sym?.type === 'image' && sym.asset_id) {
|
||||||
@@ -354,19 +357,30 @@ async function drawJokerBody(ctx, w, h, which, design, project) {
|
|||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function drawBackSide(ctx, w, h, design, project) {
|
async function drawBackSide(ctx, w, h, backDesign, project) {
|
||||||
ctx.fillStyle = design.background_color || '#1A237E'
|
ctx.fillStyle = backDesign.background_color || '#1A237E'
|
||||||
ctx.fillRect(0, 0, w, h)
|
ctx.fillRect(0, 0, w, h)
|
||||||
|
|
||||||
|
const bw = Number(backDesign.border_width) || 0
|
||||||
|
if (bw > 0) {
|
||||||
|
ctx.save()
|
||||||
|
ctx.strokeStyle = backDesign.border_color || '#C0A050'
|
||||||
|
ctx.lineWidth = bw
|
||||||
|
const half = bw / 2
|
||||||
|
drawRoundedRect(ctx, half, half, w - bw, h - bw, 16)
|
||||||
|
ctx.stroke()
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
|
||||||
const padX = Math.round(w * 0.15)
|
const padX = Math.round(w * 0.15)
|
||||||
const padTop = Math.round(h * 0.18)
|
const padTop = Math.round(h * 0.18)
|
||||||
const padBot = Math.round(h * 0.22)
|
const padBot = Math.round(h * 0.22)
|
||||||
const bodyW = w - 2 * padX
|
const bodyW = w - 2 * padX
|
||||||
const bodyH = h - padTop - padBot
|
const bodyH = h - padTop - padBot
|
||||||
|
|
||||||
const imageDx = Number(design.image_dx) || 0
|
const imageDx = Number(backDesign.image_dx) || 0
|
||||||
const imageDy = Number(design.image_dy) || 0
|
const imageDy = Number(backDesign.image_dy) || 0
|
||||||
const imageScale = Number(design.image_scale) || 1
|
const imageScale = Number(backDesign.image_scale) || 1
|
||||||
const offsetX = bodyW * imageDx
|
const offsetX = bodyW * imageDx
|
||||||
const offsetY = bodyH * imageDy
|
const offsetY = bodyH * imageDy
|
||||||
|
|
||||||
@@ -391,17 +405,21 @@ async function drawBackSide(ctx, w, h, design, project) {
|
|||||||
const drawX = padX + offsetX + (bodyW - finalW) / 2
|
const drawX = padX + offsetX + (bodyW - finalW) / 2
|
||||||
const drawY = padTop + offsetY + (bodyH - finalH) / 2
|
const drawY = padTop + offsetY + (bodyH - finalH) / 2
|
||||||
ctx.drawImage(img, drawX, drawY, finalW, finalH)
|
ctx.drawImage(img, drawX, drawY, finalW, finalH)
|
||||||
|
|
||||||
|
if (backDesign.pattern_color) {
|
||||||
|
ctx.save()
|
||||||
|
ctx.globalCompositeOperation = 'source-atop'
|
||||||
|
ctx.fillStyle = backDesign.pattern_color
|
||||||
|
ctx.globalAlpha = 0.3
|
||||||
|
ctx.fillRect(0, 0, w, h)
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.save()
|
ctx.save()
|
||||||
ctx.strokeStyle = design.border_color || '#FFFFFF'
|
ctx.fillStyle = backDesign.border_color || '#C0A050'
|
||||||
ctx.lineWidth = 6
|
|
||||||
const m = w * 0.06
|
|
||||||
drawRoundedRect(ctx, m, m, w - 2 * m, h - 2 * m, 16)
|
|
||||||
ctx.stroke()
|
|
||||||
ctx.fillStyle = design.border_color || '#FFFFFF'
|
|
||||||
ctx.textAlign = 'center'
|
ctx.textAlign = 'center'
|
||||||
ctx.textBaseline = 'middle'
|
ctx.textBaseline = 'middle'
|
||||||
ctx.font = `bold ${Math.round(w * 0.1)}px ${design.font_family || 'Times New Roman'}, serif`
|
ctx.font = `bold ${Math.round(w * 0.1)}px Times New Roman, serif`
|
||||||
ctx.fillText('CARD BACK', w / 2, h / 2)
|
ctx.fillText('CARD BACK', w / 2, h / 2)
|
||||||
ctx.restore()
|
ctx.restore()
|
||||||
}
|
}
|
||||||
@@ -442,7 +460,8 @@ export async function renderCard(canvas, project, cardKey) {
|
|||||||
await drawJokerBody(ctx, w, h, which, design, project)
|
await drawJokerBody(ctx, w, h, which, design, project)
|
||||||
drawBorder(ctx, w, h, design)
|
drawBorder(ctx, w, h, design)
|
||||||
} else if (cardKey === 'back') {
|
} else if (cardKey === 'back') {
|
||||||
await drawBackSide(ctx, w, h, design, project)
|
const backDesign = getEffectiveBackDesign(project)
|
||||||
|
await drawBackSide(ctx, w, h, backDesign, project)
|
||||||
} else {
|
} else {
|
||||||
const [suit, rank] = cardKey.split('-')
|
const [suit, rank] = cardKey.split('-')
|
||||||
drawBackground(ctx, w, h, design)
|
drawBackground(ctx, w, h, design)
|
||||||
|
|||||||
Reference in New Issue
Block a user