refactor: 收口 BangDream 卡牌 SD 缩略图规格

This commit is contained in:
sunlei 2026-06-07 05:21:01 +08:00
parent c26aeeb921
commit e48ea5100a
4 changed files with 123 additions and 9 deletions

View File

@ -392,6 +392,7 @@ export interface TsuguHook {
- 已新增 `render-blocks/list-difficulty-detail-spec.ts`,收口玩家难度详情列表的徽章宽度/字号/圆角、正文宽度/行高、项画布高度和通用列表行高/间距;`list-difficulty-detail.ts` 改为消费 spec玩家详情里的已通关/Full Combo/All Perfect 难度详情绘制顺序和输出结构保持不变;`list-difficulty-detail-spec.spec.ts` 覆盖徽章参数、正文绘制参数、项布局和列表框架参数,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
- 已新增 `render-blocks/list-band-detail-spec.ts`,收口玩家详情乐队等级、舞台挑战达成情况和乐队编成等级列表的 Logo 缩放、正文宽度/行高、项画布、通用列表框架和 Rank 等级图片布局;`list-band-detail.ts` 改为消费 spec玩家详情里的乐队详情绘制顺序、资源 repository 和输出结构保持不变;`list-band-detail-spec.spec.ts` 覆盖 Logo/正文参数、项布局、列表框架、Rank 画布/图片位置和等级图片 Rank ID 封顶,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
- 已新增 `render-blocks/list-character-detail-spec.ts`,收口玩家详情角色等级列表的头像缩放、正文宽度/行高、项画布和通用列表框架;`list-character-detail.ts` 改为消费 spec玩家详情角色等级绘制顺序、角色数据读取和输出结构保持不变`list-character-detail-spec.spec.ts` 覆盖头像/正文参数、项布局和列表框架参数,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
- 已新增 `render-blocks/list-card-sd-character-spec.ts`,收口卡牌详情演出缩略图的 SD 角色 sprite 裁切网格、列表缩放行高、字号和间距;`list-card-sd-character.ts` 改为消费 spec素材加载、四帧裁切和列表绘制顺序保持不变`list-card-sd-character-spec.spec.ts` 覆盖四帧裁切坐标、单帧裁切计算和列表规格,本地 `/查卡 472` 图片 smoke 输出非空。
- smoke/Jenkins/远程调试卡点继续按已固化规则处理同一卡点第二次尝试前必须改变可验证变量Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值smoke 没有真实图片落盘时必须失败。
### Phase 6策略 policy 和时间/档线规则

View File

@ -0,0 +1,67 @@
export interface CardSdCharacterCropRect {
sourceX: number;
sourceY: number;
width: number;
height: number;
}
export const BANGDREAM_CARD_SD_CHARACTER_SPEC = {
sprite: {
columns: 2,
rows: 2,
cropOffsetY: 84,
cropWidth: 400,
cropHeight: 470,
},
list: {
targetWidth: 190,
spacing: 0,
},
} as const;
/**
* SD sprite
*
* @param index - SD
*/
export function getCardSdCharacterCropRect(
index: number,
): CardSdCharacterCropRect {
const { columns, cropHeight, cropOffsetY, cropWidth } =
BANGDREAM_CARD_SD_CHARACTER_SPEC.sprite;
return {
sourceX: (index % columns) * cropWidth,
sourceY: cropOffsetY + Math.floor(index / columns) * cropHeight,
width: cropWidth,
height: cropHeight,
};
}
/**
* SD
*/
export function getCardSdCharacterCropRects(): CardSdCharacterCropRect[] {
const { columns, rows } = BANGDREAM_CARD_SD_CHARACTER_SPEC.sprite;
return Array.from({ length: columns * rows }, (_, index) =>
getCardSdCharacterCropRect(index),
);
}
/**
* SD
*/
export function getCardSdCharacterListLineHeight() {
const { cropHeight, cropWidth } = BANGDREAM_CARD_SD_CHARACTER_SPEC.sprite;
const { targetWidth } = BANGDREAM_CARD_SD_CHARACTER_SPEC.list;
return (cropHeight / cropWidth) * targetWidth;
}
/**
* SD
*/
export function getCardSdCharacterListTextSize() {
return getCardSdCharacterListLineHeight();
}

View File

@ -2,6 +2,12 @@ import { Costume } from '@/qqbot/plugins/bangDream/tsugu/models/costume';
import { Card } from '@/qqbot/plugins/bangDream/tsugu/models/card';
import { Canvas } from 'skia-canvas';
import { drawList } from './list-frame';
import {
BANGDREAM_CARD_SD_CHARACTER_SPEC,
getCardSdCharacterCropRects,
getCardSdCharacterListLineHeight,
getCardSdCharacterListTextSize,
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-sd-character-spec';
/**
* SD角色In列表
@ -14,22 +20,29 @@ export async function drawSdCharacterInList(card: Card): Promise<Canvas> {
const costume = new Costume(costumeId);
await costume.initFull();
const sdCharacterImage = await costume.getSdCharacter();
//从高度84开始把sdCharaImage切成田字形的四分大小都为400*470
const sdCharacterImageList: Array<Canvas> = [];
for (let i = 0; i < 4; i++) {
const canvas = new Canvas(400, 470);
for (const cropRect of getCardSdCharacterCropRects()) {
const canvas = new Canvas(cropRect.width, cropRect.height);
const context = canvas.getContext('2d');
const x = i % 2 === 0 ? 0 : 400;
const y = i < 2 ? 84 : 554;
context.drawImage(sdCharacterImage, x, y, 400, 470, 0, 0, 400, 470);
context.drawImage(
sdCharacterImage,
cropRect.sourceX,
cropRect.sourceY,
cropRect.width,
cropRect.height,
0,
0,
cropRect.width,
cropRect.height,
);
sdCharacterImageList.push(canvas);
}
return drawList({
key: '演出缩略图',
content: sdCharacterImageList,
lineHeight: (470 / 400) * 190,
textSize: (470 / 400) * 190,
spacing: 0,
lineHeight: getCardSdCharacterListLineHeight(),
textSize: getCardSdCharacterListTextSize(),
spacing: BANGDREAM_CARD_SD_CHARACTER_SPEC.list.spacing,
});
}

View File

@ -0,0 +1,33 @@
import {
BANGDREAM_CARD_SD_CHARACTER_SPEC,
getCardSdCharacterCropRect,
getCardSdCharacterCropRects,
getCardSdCharacterListLineHeight,
getCardSdCharacterListTextSize,
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-card-sd-character-spec';
describe('BangDream card SD character list spec', () => {
it('keeps the historical four-frame SD character crop grid', () => {
expect(getCardSdCharacterCropRects()).toEqual([
{ sourceX: 0, sourceY: 84, width: 400, height: 470 },
{ sourceX: 400, sourceY: 84, width: 400, height: 470 },
{ sourceX: 0, sourceY: 554, width: 400, height: 470 },
{ sourceX: 400, sourceY: 554, width: 400, height: 470 },
]);
});
it('computes individual crop rects from the shared sprite spec', () => {
expect(getCardSdCharacterCropRect(3)).toEqual({
sourceX: 400,
sourceY: 554,
width: 400,
height: 470,
});
});
it('keeps list line height, text size and spacing stable', () => {
expect(BANGDREAM_CARD_SD_CHARACTER_SPEC.list.spacing).toBe(0);
expect(getCardSdCharacterListLineHeight()).toBe(223.25);
expect(getCardSdCharacterListTextSize()).toBe(223.25);
});
});