修复图片改造后二维清晰度降级问题 (#90)

前端按图层真实尺寸恢复图片改造比例与清晰度

后端在角色去背景后恢复生成原图像素尺寸

补充前后端二千像素尺寸回归测试

同步更新图片画布技术文档与项目决策记录

---------

Co-authored-by: MengHao <menghao@MengHaodeMacBook-Air.local>
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/90
Reviewed-by: 段舒康 <kdletters@qq.com>
Co-authored-by: 孟豪 <mh18530625731@163.com>
Co-committed-by: 孟豪 <mh18530625731@163.com>
This commit was merged in pull request #90.
This commit is contained in:
2026-07-18 16:50:17 +08:00
committed by 段舒康
parent a5ae283561
commit 47f3297e84
16 changed files with 583 additions and 190 deletions
@@ -144,7 +144,7 @@ describe('ImageCanvasGenerationDialogModel', () => {
});
});
it('creates character generation drafts with model dimensions and icon drafts with icon frame dimensions', () => {
it('creates character and icon generation drafts with the selected model dimensions', () => {
const canvasSize = { width: 960, height: 720 };
const viewport = { x: 0, y: 0, scale: 1 };
@@ -183,12 +183,12 @@ describe('ImageCanvasGenerationDialogModel', () => {
iconSpecReference: null,
iconDescriptions: [],
placeholder: {
x: 300,
y: 180,
width: 360,
height: 360,
originalWidth: 512,
originalHeight: 512,
x: -32,
y: -152,
width: 1024,
height: 1024,
originalWidth: 1024,
originalHeight: 1024,
},
});
});
@@ -233,10 +233,10 @@ describe('ImageCanvasGenerationDialogModel', () => {
imageSize: '1K',
uiDesignSpecReference: null,
placeholder: {
x: -32,
y: 72,
width: 1024,
height: 576,
x: -64,
y: 56,
width: 1088,
height: 608,
},
});
});
@@ -538,6 +538,10 @@ describe('ImageCanvasGenerationDialogModel', () => {
placeholder: {
x: 472,
y: 140,
width: 1024,
height: 768,
originalWidth: 1024,
originalHeight: 768,
},
});
@@ -614,9 +618,58 @@ describe('ImageCanvasGenerationDialogModel', () => {
imageModel: 'gpt-image-2',
aspectRatio: '2:3',
imageSize: '2K',
placeholder: {
x: 472,
y: 140,
width: 1376,
height: 2048,
originalWidth: 1376,
originalHeight: 2048,
},
});
});
it.each([
['character', 'character'],
['icon', 'icon-spritesheet'],
['ui-design', 'ui-design'],
] as const)(
'restores %s remodel placeholders to the selected 2K dimensions',
(mode, assetKind) => {
expect(
createSameSourceGenerationDialogDraft({
sourceLayer: createLayer({ assetKind }),
canvasSize: { width: 960, height: 720 },
viewport: { x: 0, y: 0, scale: 1 },
sourceDialog: {
id: `generation-dialog-${mode}`,
mode,
prompt: '来源生成器',
status: 'idle',
generatedLayerId: 'layer-source',
imageModel: IMAGE_MODEL_GPT_IMAGE_2,
aspectRatio: '2:3',
imageSize: '2K',
},
mode: 'redraw',
}),
).toMatchObject({
mode,
imageModel: IMAGE_MODEL_GPT_IMAGE_2,
aspectRatio: '2:3',
imageSize: '2K',
placeholder: {
x: 472,
y: 140,
width: 1376,
height: 2048,
originalWidth: 1376,
originalHeight: 2048,
},
});
},
);
it('restores a plain generated layer from its own metadata', () => {
const canvasSize = { width: 960, height: 720 };
const viewport = { x: 0, y: 0, scale: 1 };
@@ -672,6 +725,33 @@ describe('ImageCanvasGenerationDialogModel', () => {
});
});
it('preserves 2K dimensions when reopening a persisted character layer', () => {
expect(
createLayerGenerationDialogDraft({
sourceLayer: createLayer({
sourceType: 'generated',
assetKind: 'character',
model: IMAGE_MODEL_NANOBANANA2,
originalWidth: 2048,
originalHeight: 2048,
generationInputs: {
fields: [{ title: '角色设定', value: '香水师角色' }],
references: [],
},
}),
canvasSize: { width: 960, height: 720 },
viewport: { x: 0, y: 0, scale: 1 },
}),
).toMatchObject({
mode: 'character',
prompt: '香水师角色',
imageModel: IMAGE_MODEL_NANOBANANA2,
aspectRatio: '1:1',
imageSize: '2K',
generatedLayerId: 'layer-source',
});
});
it('restores video and audio generators from media layer metadata', () => {
const context = {
canvasSize: { width: 960, height: 720 },