69 lines
2.1 KiB
Vue
69 lines
2.1 KiB
Vue
// 像素头像渲染组件:把 'px:NN' 编码画成像素图(8×8 网格放大,锐利无插值)
|
||
// 兼容旧 emoji 数据:非像素编码时原样以文字显示,历史用户零迁移
|
||
<script setup>
|
||
import { computed } from 'vue'
|
||
import { AVATAR_SHAPES, AVATAR_PALETTES, decodePixelAvatar } from '../games/pixelAvatars'
|
||
|
||
const props = defineProps({
|
||
// 头像值:'px:NN' 像素编码 或 旧 emoji 字符
|
||
code: { type: String, default: '' },
|
||
// 渲染尺寸(px)
|
||
size: { type: Number, default: 32 },
|
||
})
|
||
|
||
const px = computed(() => decodePixelAvatar(props.code))
|
||
|
||
// dataURL 缓存:同 code+size 只画一次(组件实例间不共享,同一实例多次渲染直接命中)
|
||
const cache = new Map()
|
||
function dataUrl(size) {
|
||
const key = props.code + '@' + size
|
||
if (cache.has(key)) return cache.get(key)
|
||
const p = px.value
|
||
if (!p) return ''
|
||
const shape = AVATAR_SHAPES[p.shape]
|
||
const pal = AVATAR_PALETTES[p.palette]
|
||
const cell = Math.max(1, Math.round(size / 8))
|
||
const real = cell * 8
|
||
const cv = document.createElement('canvas')
|
||
cv.width = real
|
||
cv.height = real
|
||
const ctx = cv.getContext('2d')
|
||
const colors = { 1: pal.c1, 2: pal.c2, 3: pal.c3, 4: '#ffffff', 5: '#1a1a24' }
|
||
shape.forEach((row, y) => {
|
||
for (let x = 0; x < 8; x++) {
|
||
const ch = row[x]
|
||
if (ch === '.') continue
|
||
ctx.fillStyle = colors[ch] || colors[1]
|
||
ctx.fillRect(x * cell, y * cell, cell, cell)
|
||
}
|
||
})
|
||
const url = cv.toDataURL()
|
||
cache.set(key, url)
|
||
return url
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<span class="px-avatar" :style="{ width: size + 'px', height: size + 'px', fontSize: Math.round(size * 0.6) + 'px' }">
|
||
<img v-if="px" :src="dataUrl(size)" :width="size" :height="size" alt="" class="px-img" />
|
||
<template v-else>{{ code || '🎮' }}</template>
|
||
</span>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.px-avatar {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
line-height: 1;
|
||
flex-shrink: 0;
|
||
}
|
||
.px-img {
|
||
display: block;
|
||
width: 100%;
|
||
height: 100%;
|
||
image-rendering: pixelated; /* 像素图放大不模糊 */
|
||
}
|
||
</style>
|