新增画布完美像素功能
在图片工具栏接入完美像素交互、占位流程与历史保护 新增严格像素网格检测和登录态同步处理接口 补齐素材类型校验、持久化边界、并发限制及技术文档 修复 Windows rlib 门禁长文件名解析
This commit is contained in:
@@ -93,6 +93,7 @@ import {
|
||||
buildCropExpandInsetsFromFrame,
|
||||
removeImageBackground,
|
||||
renderCropExpandImage,
|
||||
snapImageToPerfectPixels,
|
||||
} from './ImageCanvasRasterEditModel';
|
||||
import {
|
||||
createUiAssetExtractionDraftMark,
|
||||
@@ -628,6 +629,7 @@ type GenerationWorkflowOptions = {
|
||||
project: EditorProjectSnapshot,
|
||||
action?: CanvasHistoryAction,
|
||||
) => void;
|
||||
flushProjectPersistence?: () => Promise<void>;
|
||||
onWalletBalanceMayHaveChanged?: () => void;
|
||||
};
|
||||
|
||||
@@ -662,6 +664,7 @@ export function useImageCanvasGenerationWorkflow({
|
||||
assetFolderId,
|
||||
upsertGeneratedAsset,
|
||||
applyProjectSnapshot,
|
||||
flushProjectPersistence,
|
||||
onWalletBalanceMayHaveChanged,
|
||||
}: GenerationWorkflowOptions) {
|
||||
const [isTaskSidebarOpen, setIsTaskSidebarOpen] = useState(false);
|
||||
@@ -677,6 +680,10 @@ export function useImageCanvasGenerationWorkflow({
|
||||
const splittingIconSpritesheetLayerIdsRef = useRef(new Set<string>());
|
||||
const [splittingIconSpritesheetLayerIds, setSplittingIconSpritesheetLayerIds] =
|
||||
useState<Set<string>>(() => new Set());
|
||||
const perfectPixelLayerIdsRef = useRef(new Set<string>());
|
||||
const [perfectPixelLayerIds, setPerfectPixelLayerIds] = useState<Set<string>>(
|
||||
() => new Set(),
|
||||
);
|
||||
const [isSpecMenuOpen, setIsSpecMenuOpen] = useState(false);
|
||||
const [isGenerationReferenceMenuOpen, setIsGenerationReferenceMenuOpen] =
|
||||
useState(false);
|
||||
@@ -1734,6 +1741,160 @@ export function useImageCanvasGenerationWorkflow({
|
||||
],
|
||||
);
|
||||
|
||||
const snapSelectedLayerToPerfectPixels = useCallback(
|
||||
async (sourceLayer: CanvasLayer) => {
|
||||
const normalizedProjectId = projectId?.trim();
|
||||
const isStaticRasterLayer =
|
||||
(sourceLayer.mediaType === undefined ||
|
||||
sourceLayer.mediaType === 'image') &&
|
||||
sourceLayer.assetKind !== 'character-animation';
|
||||
if (
|
||||
!isStaticRasterLayer ||
|
||||
!normalizedProjectId ||
|
||||
!applyProjectSnapshot ||
|
||||
!flushProjectPersistence ||
|
||||
perfectPixelLayerIdsRef.current.has(sourceLayer.id)
|
||||
) {
|
||||
if (
|
||||
isStaticRasterLayer &&
|
||||
(!normalizedProjectId ||
|
||||
!applyProjectSnapshot ||
|
||||
!flushProjectPersistence)
|
||||
) {
|
||||
showGenerationWarning('项目尚未准备好,暂时无法执行完美像素。');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
perfectPixelLayerIdsRef.current.add(sourceLayer.id);
|
||||
setPerfectPixelLayerIds((currentLayerIds) => {
|
||||
const nextLayerIds = new Set(currentLayerIds);
|
||||
nextLayerIds.add(sourceLayer.id);
|
||||
return nextLayerIds;
|
||||
});
|
||||
closeGenerationTransientState();
|
||||
setImageContextMenu(null);
|
||||
setMetadataLayer(null);
|
||||
setCropExpandPanel(null);
|
||||
setQuickEditPanel(null);
|
||||
setCharacterAnimationPanel(null);
|
||||
|
||||
let perfectPixelDialogId: string | undefined;
|
||||
try {
|
||||
const assetLabel = `${sourceLayer.title} · 完美像素`;
|
||||
const placement = openPlacedCanvasGenerationDialog({
|
||||
...createQuickEditGenerationDialogDraft({
|
||||
sourceLayer,
|
||||
prompt: '完美像素',
|
||||
assetLabel,
|
||||
status: 'generating',
|
||||
frame: {
|
||||
width: sourceLayer.width,
|
||||
height: sourceLayer.height,
|
||||
},
|
||||
}),
|
||||
composerOpen: false,
|
||||
});
|
||||
perfectPixelDialogId = placement.dialogId;
|
||||
if (!placement.placeholder) {
|
||||
throw new Error('无法创建完美像素处理占位');
|
||||
}
|
||||
|
||||
const sourceImageSrc = await resolveEditorGenerationMediaReference(
|
||||
sourceLayer,
|
||||
'image',
|
||||
normalizedProjectId,
|
||||
);
|
||||
await flushProjectPersistence();
|
||||
const sourceResourceId = sourceLayer.resourceId.trim();
|
||||
const result = await snapImageToPerfectPixels({
|
||||
sourceImageSrc,
|
||||
projectId: normalizedProjectId,
|
||||
sourceResourceId:
|
||||
sourceResourceId &&
|
||||
!sourceResourceId.startsWith('local-resource-') &&
|
||||
!sourceResourceId.startsWith('generation-dialog:')
|
||||
? sourceResourceId
|
||||
: undefined,
|
||||
assetKind: sourceLayer.assetKind,
|
||||
generationInputs: sourceLayer.generationInputs,
|
||||
assetFolderId,
|
||||
assetLabel,
|
||||
canvasCompletion: {
|
||||
dialogId: perfectPixelDialogId,
|
||||
title: assetLabel,
|
||||
placeholder: placement.placeholder,
|
||||
},
|
||||
});
|
||||
upsertGeneratedAsset?.(result.asset);
|
||||
if (!result.project) {
|
||||
if (hasCanvasGenerationDialogById(perfectPixelDialogId)) {
|
||||
updateCanvasGenerationDialogById(perfectPixelDialogId, () => null);
|
||||
showGenerationWarning(
|
||||
'完美像素结果已保存到素材库,画布占位已不存在。',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!hasCanvasGenerationDialogById(perfectPixelDialogId)) {
|
||||
return;
|
||||
}
|
||||
applyProjectSnapshot(result.project, {
|
||||
type: 'perfect-pixel',
|
||||
count: 1,
|
||||
});
|
||||
setActiveTool('select');
|
||||
setActiveSidebarPanel('layers');
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error && error.message.trim()
|
||||
? error.message
|
||||
: '完美像素处理失败';
|
||||
if (
|
||||
perfectPixelDialogId &&
|
||||
hasCanvasGenerationDialogById(perfectPixelDialogId)
|
||||
) {
|
||||
updateCanvasGenerationDialogById(perfectPixelDialogId, (dialog) => ({
|
||||
...dialog,
|
||||
status: 'failed',
|
||||
errorMessage,
|
||||
}));
|
||||
} else if (!perfectPixelDialogId) {
|
||||
showGenerationWarning(errorMessage);
|
||||
}
|
||||
} finally {
|
||||
perfectPixelLayerIdsRef.current.delete(sourceLayer.id);
|
||||
setPerfectPixelLayerIds((currentLayerIds) => {
|
||||
if (!currentLayerIds.has(sourceLayer.id)) {
|
||||
return currentLayerIds;
|
||||
}
|
||||
const nextLayerIds = new Set(currentLayerIds);
|
||||
nextLayerIds.delete(sourceLayer.id);
|
||||
return nextLayerIds;
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
applyProjectSnapshot,
|
||||
assetFolderId,
|
||||
closeGenerationTransientState,
|
||||
flushProjectPersistence,
|
||||
hasCanvasGenerationDialogById,
|
||||
openPlacedCanvasGenerationDialog,
|
||||
projectId,
|
||||
setActiveSidebarPanel,
|
||||
setActiveTool,
|
||||
setCharacterAnimationPanel,
|
||||
setCropExpandPanel,
|
||||
setImageContextMenu,
|
||||
setMetadataLayer,
|
||||
setQuickEditPanel,
|
||||
showGenerationWarning,
|
||||
updateCanvasGenerationDialogById,
|
||||
upsertGeneratedAsset,
|
||||
],
|
||||
);
|
||||
|
||||
const splitSelectedIconSpritesheet = useCallback(
|
||||
async (sourceLayer: CanvasLayer) => {
|
||||
if (
|
||||
@@ -2576,6 +2737,8 @@ export function useImageCanvasGenerationWorkflow({
|
||||
openCropExpandPanel,
|
||||
startCropExpandFrameResize,
|
||||
removeSelectedLayerBackground,
|
||||
snapSelectedLayerToPerfectPixels,
|
||||
perfectPixelLayerIds,
|
||||
splitSelectedIconSpritesheet,
|
||||
splittingIconSpritesheetLayerIds,
|
||||
taskListRefreshKey,
|
||||
@@ -2679,6 +2842,8 @@ export function useImageCanvasGenerationWorkflow({
|
||||
quickEditPanel,
|
||||
quickEditSourceLayer,
|
||||
removeSelectedLayerBackground,
|
||||
snapSelectedLayerToPerfectPixels,
|
||||
perfectPixelLayerIds,
|
||||
splitSelectedIconSpritesheet,
|
||||
splittingIconSpritesheetLayerIds,
|
||||
submitCharacterAnimation,
|
||||
|
||||
Reference in New Issue
Block a user