修复角色图去背景读取失败

去背景前统一解析私有图片资源

裁扩本地编辑复用图片引用解析

补充角色图去背景回归测试
This commit is contained in:
2026-06-25 20:29:58 +08:00
parent 9516132e94
commit 2c53fc302e
2 changed files with 74 additions and 17 deletions
@@ -22,6 +22,8 @@ const generateEditorSoundEffectMock = vi.hoisted(() => vi.fn());
const generateEditorBackgroundMusicMock = vi.hoisted(() => vi.fn());
const editEditorImageMock = vi.hoisted(() => vi.fn());
const renderCropExpandImageMock = vi.hoisted(() => vi.fn());
const removeImageBackgroundMock = vi.hoisted(() => vi.fn());
const resolveEditorImageReferenceDataUrlMock = vi.hoisted(() => vi.fn());
vi.mock('../../services/image-editor/editorImageReference', async () => {
const actual = await vi.importActual<
@@ -29,7 +31,7 @@ vi.mock('../../services/image-editor/editorImageReference', async () => {
>('../../services/image-editor/editorImageReference');
return {
...actual,
resolveEditorImageReferenceDataUrl: vi.fn(async (src: string) => src),
resolveEditorImageReferenceDataUrl: resolveEditorImageReferenceDataUrlMock,
resolveEditorImageReferenceDataUrlForGeneration: vi.fn(
async (src: string) => src,
),
@@ -57,6 +59,7 @@ vi.mock('./ImageCanvasRasterEditModel', async () => {
>('./ImageCanvasRasterEditModel');
return {
...actual,
removeImageBackground: removeImageBackgroundMock,
renderCropExpandImage: renderCropExpandImageMock,
};
});
@@ -390,6 +393,12 @@ function GenerationWorkflowHarness({
<button type="button" onClick={() => void workflow.submitCropExpand()}>
</button>
<button
type="button"
onClick={() => void workflow.removeSelectedLayerBackground(layers[0]!)}
>
</button>
<button
type="button"
onClick={() =>
@@ -716,6 +725,9 @@ function GenerationWorkflowHarness({
describe('useImageCanvasGenerationWorkflow', () => {
beforeEach(() => {
vi.clearAllMocks();
resolveEditorImageReferenceDataUrlMock.mockImplementation(
async (src: string) => src,
);
window.localStorage.clear();
});
@@ -1326,15 +1338,15 @@ describe('useImageCanvasGenerationWorkflow', () => {
expect(screen.getByTestId('quick-edit-selection').textContent).toBe(
'layer-source:none:0',
);
fireEvent.click(screen.getByRole('button', { name: '选择快速编辑矩形框选' }));
fireEvent.click(
screen.getByRole('button', { name: '选择快速编辑矩形框选' }),
);
fireEvent.click(screen.getByRole('button', { name: '快速编辑框选' }));
expect(screen.getByTestId('quick-edit-selection').textContent).toBe(
'layer-source:rect:1',
);
expect(screen.getByTestId('quick-edit').textContent).toBe(
'-',
);
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
fireEvent.click(
screen.getByRole('button', { name: '选择快速编辑椭圆框选' }),
@@ -1344,9 +1356,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
expect(screen.getByTestId('quick-edit-selection').textContent).toBe(
'layer-source:ellipse:2',
);
expect(screen.getByTestId('quick-edit').textContent).toBe(
'-',
);
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
});
it('submits quick edits beside the source and fits source plus result', async () => {
@@ -1429,6 +1439,44 @@ describe('useImageCanvasGenerationWorkflow', () => {
expect(screen.getByTestId('fit-count').textContent).toBe('1');
});
it('resolves private character image before removing background', async () => {
resolveEditorImageReferenceDataUrlMock.mockResolvedValueOnce(
'data:image/png;base64,resolved-character',
);
removeImageBackgroundMock.mockResolvedValueOnce({
imageSrc: 'data:image/png;base64,no-background',
width: 512,
height: 768,
});
render(
<GenerationWorkflowHarness
initialLayers={[
createLayer({
assetKind: 'character',
objectKey: 'generated-character-drafts/editor/private.png',
src: '/api/assets/read-url?objectKey=generated-character-drafts/editor/private.png',
}),
]}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '去除背景' }));
await waitFor(() => {
expect(resolveEditorImageReferenceDataUrlMock).toHaveBeenCalledWith(
'generated-character-drafts/editor/private.png',
);
});
await waitFor(() => {
expect(removeImageBackgroundMock).toHaveBeenCalledWith(
'data:image/png;base64,resolved-character',
);
});
expect(screen.getByTestId('layers').textContent).toContain(
'layer-source:源图:-:character',
);
});
it('opens UI design extraction as a mark selection state before submitting', () => {
render(<GenerationWorkflowHarness />);
@@ -1456,9 +1504,7 @@ describe('useImageCanvasGenerationWorkflow', () => {
fireEvent.click(screen.getByRole('button', { name: '打开快速编辑' }));
await waitFor(() => {
expect(screen.getByTestId('quick-edit').textContent).toBe(
'-',
);
expect(screen.getByTestId('quick-edit').textContent).toBe('-');
});
fireEvent.click(screen.getByRole('button', { name: '打开修改状态' }));
fireEvent.click(screen.getByRole('button', { name: '打开UI素材框选' }));
@@ -14,6 +14,7 @@ import type {
EditorAssetSnapshot,
EditorProjectSnapshot,
} from '../../services/image-editor/editorProjectClient';
import { resolveEditorImageReferenceDataUrl } from '../../services/image-editor/editorImageReference';
import { resizeCropExpandFrame } from './ImageCanvasCropExpandModel';
import type {
CanvasGenerationDialogState,
@@ -468,8 +469,9 @@ export function useImageCanvasGenerationWorkflow({
useState<UiAssetExtractionState | null>(null);
const [quickEditSelectionState, setQuickEditSelectionState] =
useState<UiAssetExtractionState | null>(null);
const quickEditSelectionStateRef =
useRef<UiAssetExtractionState | null>(null);
const quickEditSelectionStateRef = useRef<UiAssetExtractionState | null>(
null,
);
const cropExpandPanelRef = useRef<CropExpandPanelState | null>(null);
const cropExpandResizeDragRef = useRef<CropExpandResizeDragState | null>(
null,
@@ -902,7 +904,8 @@ export function useImageCanvasGenerationWorkflow({
.find(
(dialog) =>
dialog.generatedLayerId === sourceLayer.id ||
(!dialog.generatedLayerId && dialog.sourceLayerId === sourceLayer.id),
(!dialog.generatedLayerId &&
dialog.sourceLayerId === sourceLayer.id),
);
return createQuickEditPanelDraft(sourceLayer, {
imageModel: sourceDialog?.imageModel,
@@ -1202,8 +1205,11 @@ export function useImageCanvasGenerationWorkflow({
status: 'processing',
errorMessage: undefined,
});
const sourceImageSrc = await resolveEditorImageReferenceDataUrl(
cropExpandSourceLayer.objectKey?.trim() || cropExpandSourceLayer.src,
);
const result = await renderCropExpandImage({
source: cropExpandSourceLayer.src,
source: sourceImageSrc,
insets,
});
layerCounterRef.current += 1;
@@ -1263,7 +1269,10 @@ export function useImageCanvasGenerationWorkflow({
setCropExpandPanel(null);
setQuickEditPanel(null);
try {
const result = await removeImageBackground(sourceLayer.src);
const sourceImageSrc = await resolveEditorImageReferenceDataUrl(
sourceLayer.objectKey?.trim() || sourceLayer.src,
);
const result = await removeImageBackground(sourceImageSrc);
updateSourceLayer(sourceLayer.id, (layer) => ({
...layer,
src: result.imageSrc,
@@ -1285,6 +1294,7 @@ export function useImageCanvasGenerationWorkflow({
}
},
[
resolveEditorImageReferenceDataUrl,
setActiveSidebarPanel,
setImageContextMenu,
setMetadataLayer,
@@ -1533,7 +1543,8 @@ export function useImageCanvasGenerationWorkflow({
...currentState,
tool: currentState.tool === tool ? null : tool,
draftMark: null,
status: currentState.status === 'failed' ? 'idle' : currentState.status,
status:
currentState.status === 'failed' ? 'idle' : currentState.status,
errorMessage:
currentState.status === 'failed'
? undefined