feat: 背面图案支持 + 大小王素材位置微调 + 10号牌pip修复
This commit is contained in:
@@ -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,21 +328,57 @@ 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)
|
||||
ctx.save()
|
||||
ctx.strokeStyle = design.border_color || '#FFFFFF'
|
||||
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.textBaseline = 'middle'
|
||||
ctx.font = `bold ${Math.round(w * 0.1)}px ${design.font_family || 'Times New Roman'}, serif`
|
||||
ctx.fillText('CARD BACK', w / 2, h / 2)
|
||||
ctx.restore()
|
||||
|
||||
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
|
||||
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.textBaseline = 'middle'
|
||||
ctx.font = `bold ${Math.round(w * 0.1)}px ${design.font_family || 'Times New Roman'}, serif`
|
||||
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