feat: add inpaint region functionality and related tools
- Enhanced cursor behavior for new tools: semantic select, mask lasso, and mask rectangle. - Updated mask edit state to include mask asset ID and kind. - Implemented inpaint region commands for adding, applying, and removing inpaint regions. - Introduced new operations for lasso and semantic selection tools. - Created UI components for candidate review and inpaint region management. - Added tests for inpaint region commands to ensure functionality. - Updated various components to support new inpaint features and improve user experience.
This commit is contained in:
@@ -23,6 +23,7 @@ export type GenerationSetCompareModePayload = {
|
||||
export type GenerationRemoveCandidatePayload = {
|
||||
candidateId: GenerationCandidateId;
|
||||
};
|
||||
export type GenerationToggleCandidateFavoritePayload = { candidateId: GenerationCandidateId };
|
||||
|
||||
export type GenerationReuseCandidateSettingsPayload = {
|
||||
candidateId: GenerationCandidateId;
|
||||
@@ -41,6 +42,7 @@ export type GenerationReplaceCandidatePixelsPayload = {
|
||||
};
|
||||
|
||||
export type GenerationStartJobPayload = { jobId: GenerationJobId; kind: GenerationJobKind; label: string; startedAt: number };
|
||||
export type GenerationUpdateJobPayload = { jobId: GenerationJobId; progress: number; detail: string };
|
||||
export type GenerationSucceedJobPayload = { jobId: GenerationJobId; finishedAt: number };
|
||||
export type GenerationFailJobPayload = { jobId: GenerationJobId; finishedAt: number; error: string };
|
||||
export type GenerationCancelJobPayload = { jobId: GenerationJobId; finishedAt: number };
|
||||
@@ -57,7 +59,8 @@ export const generationAddCandidateCommand: Command<GenerationAddCandidatePayloa
|
||||
name: "Add generation candidate",
|
||||
history: { mode: "ignore" },
|
||||
execute({ state }, payload) {
|
||||
const candidates = retainCandidateBudget([payload.candidate, ...state.editor.generation.candidates.filter((candidate) => candidate.id !== payload.candidate.id)]);
|
||||
const existing = state.editor.generation.candidates.filter((candidate) => candidate.id !== payload.candidate.id);
|
||||
const candidates = retainCandidateBudget([payload.candidate, ...existing.filter((candidate) => candidate.favorite), ...existing.filter((candidate) => !candidate.favorite)]);
|
||||
return {
|
||||
...state,
|
||||
editor: {
|
||||
@@ -130,6 +133,25 @@ export const generationRemoveCandidateCommand: Command<GenerationRemoveCandidate
|
||||
},
|
||||
};
|
||||
|
||||
export const generationToggleCandidateFavoriteCommand: Command<GenerationToggleCandidateFavoritePayload> = {
|
||||
id: commandIds.generationToggleCandidateFavorite,
|
||||
name: "Favorite generation candidate",
|
||||
history: { mode: "ignore" },
|
||||
execute({ state }, payload) {
|
||||
if (!state.editor.generation.candidates.some((candidate) => candidate.id === payload.candidateId)) return state;
|
||||
return {
|
||||
...state,
|
||||
editor: {
|
||||
...state.editor,
|
||||
generation: {
|
||||
...state.editor.generation,
|
||||
candidates: state.editor.generation.candidates.map((candidate) => candidate.id === payload.candidateId ? { ...candidate, favorite: !candidate.favorite } : candidate),
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const generationClearCandidatesCommand: Command = {
|
||||
id: commandIds.generationClearCandidates,
|
||||
name: "Clear generation candidates",
|
||||
@@ -260,6 +282,17 @@ export const generationStartJobCommand: Command<GenerationStartJobPayload> = {
|
||||
},
|
||||
};
|
||||
|
||||
export const generationUpdateJobCommand: Command<GenerationUpdateJobPayload> = {
|
||||
id: commandIds.generationUpdateJob,
|
||||
name: "Update generation progress",
|
||||
history: { mode: "ignore" },
|
||||
execute({ state }, payload) {
|
||||
const job = state.editor.generation.jobs.find((candidate) => candidate.id === payload.jobId);
|
||||
if (!job || job.status !== "running" || !Number.isFinite(payload.progress) || !payload.detail.trim()) return state;
|
||||
return updateJobs(state, state.editor.generation.jobs.map((candidate) => candidate.id === payload.jobId ? { ...candidate, progress: Math.max(0, Math.min(1, payload.progress)), detail: payload.detail.trim() } : candidate));
|
||||
},
|
||||
};
|
||||
|
||||
export const generationSucceedJobCommand: Command<GenerationSucceedJobPayload> = {
|
||||
id: commandIds.generationSucceedJob,
|
||||
name: "Complete generation job",
|
||||
@@ -322,11 +355,13 @@ export const generationCommands = [
|
||||
generationSelectCandidateCommand,
|
||||
generationSetCompareModeCommand,
|
||||
generationRemoveCandidateCommand,
|
||||
generationToggleCandidateFavoriteCommand,
|
||||
generationClearCandidatesCommand,
|
||||
generationReuseCandidateSettingsCommand,
|
||||
generationApplyCandidateAsLayerCommand,
|
||||
generationReplaceCandidatePixelsCommand,
|
||||
generationStartJobCommand,
|
||||
generationUpdateJobCommand,
|
||||
generationSucceedJobCommand,
|
||||
generationFailJobCommand,
|
||||
generationCancelJobCommand,
|
||||
@@ -364,7 +399,7 @@ function retainCandidateBudget(candidates: GenerationCandidate[]): GenerationCan
|
||||
}
|
||||
|
||||
function candidateRetainedBytes(candidate: GenerationCandidate): number {
|
||||
return [candidate.source, candidate.inputImage, candidate.maskImage, candidate.inpaint?.inputImage, candidate.inpaint?.maskImage]
|
||||
return [candidate.source, candidate.inputImage, candidate.maskImage, candidate.blendMaskImage, candidate.inpaint?.inputImage, candidate.inpaint?.maskImage, candidate.inpaint?.editMaskImage, candidate.inpaint?.blendMaskImage]
|
||||
.reduce((total, source) => total + (source?.length ?? 0) * 2, 0);
|
||||
}
|
||||
|
||||
@@ -441,11 +476,14 @@ function generationProvenance(candidate: GenerationCandidate, acceptance: Genera
|
||||
scheduler: candidate.settings.scheduler,
|
||||
width: candidate.settings.width,
|
||||
height: candidate.settings.height,
|
||||
batchSize: candidate.settings.batchSize,
|
||||
refinePass: candidate.settings.refinePass,
|
||||
refineStrength: candidate.settings.refineStrength,
|
||||
},
|
||||
inpaint: candidate.inpaint
|
||||
? {
|
||||
targetLayerId: candidate.inpaint.targetLayerId,
|
||||
maskLayerId: candidate.inpaint.maskLayerId,
|
||||
regionId: candidate.inpaint.regionId,
|
||||
sourceAssetId: candidate.inpaint.sourceAssetId,
|
||||
maskAssetId: candidate.inpaint.maskAssetId,
|
||||
crop: {
|
||||
@@ -465,6 +503,11 @@ function generationProvenance(candidate: GenerationCandidate, acceptance: Genera
|
||||
maskFeather: candidate.inpaint.backend.maskFeather,
|
||||
maskExpand: candidate.inpaint.backend.maskExpand,
|
||||
cropPadding: candidate.inpaint.backend.cropPadding,
|
||||
profile: candidate.settings.inpaint.profile,
|
||||
structureControl: candidate.settings.inpaint.structureControl,
|
||||
controlStrength: candidate.settings.inpaint.controlStrength,
|
||||
controlModel: candidate.settings.inpaint.controlModel,
|
||||
colorMatch: candidate.settings.inpaint.colorMatch,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
|
||||
Reference in New Issue
Block a user