diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 3cedd0db26..95de810cdd 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -15,10 +15,8 @@ import { modelManagerV2PersistConfig, modelManagerV2Slice } from 'features/model import { nodesPersistConfig, nodesSlice, nodesUndoableConfig } from 'features/nodes/store/nodesSlice'; import { workflowSettingsPersistConfig, workflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice'; import { workflowPersistConfig, workflowSlice } from 'features/nodes/store/workflowSlice'; -import { generationPersistConfig, generationSlice } from 'features/parameters/store/generationSlice'; import { upscalePersistConfig, upscaleSlice } from 'features/parameters/store/upscaleSlice'; import { queueSlice } from 'features/queue/store/queueSlice'; -import { sdxlPersistConfig, sdxlSlice } from 'features/sdxl/store/sdxlSlice'; import { stylePresetPersistConfig, stylePresetSlice } from 'features/stylePresets/store/stylePresetSlice'; import { configSlice } from 'features/system/store/configSlice'; import { systemPersistConfig, systemSlice } from 'features/system/store/systemSlice'; @@ -42,7 +40,7 @@ import { listenerMiddleware } from './middleware/listenerMiddleware'; const allReducers = { [api.reducerPath]: api.reducer, [gallerySlice.name]: gallerySlice.reducer, - [generationSlice.name]: generationSlice.reducer, + // [generationSlice.name]: generationSlice.reducer, [nodesSlice.name]: undoable(nodesSlice.reducer, nodesUndoableConfig), [systemSlice.name]: systemSlice.reducer, [configSlice.name]: configSlice.reducer, @@ -52,7 +50,7 @@ const allReducers = { [changeBoardModalSlice.name]: changeBoardModalSlice.reducer, [loraSlice.name]: loraSlice.reducer, [modelManagerV2Slice.name]: modelManagerV2Slice.reducer, - [sdxlSlice.name]: sdxlSlice.reducer, + // [sdxlSlice.name]: sdxlSlice.reducer, [queueSlice.name]: queueSlice.reducer, [workflowSlice.name]: workflowSlice.reducer, [hrfSlice.name]: hrfSlice.reducer, @@ -90,13 +88,13 @@ export type PersistConfig = { const persistConfigs: { [key in keyof typeof allReducers]?: PersistConfig } = { [galleryPersistConfig.name]: galleryPersistConfig, - [generationPersistConfig.name]: generationPersistConfig, + // [generationPersistConfig.name]: generationPersistConfig, [nodesPersistConfig.name]: nodesPersistConfig, [systemPersistConfig.name]: systemPersistConfig, [workflowPersistConfig.name]: workflowPersistConfig, [uiPersistConfig.name]: uiPersistConfig, [dynamicPromptsPersistConfig.name]: dynamicPromptsPersistConfig, - [sdxlPersistConfig.name]: sdxlPersistConfig, + // [sdxlPersistConfig.name]: sdxlPersistConfig, [loraPersistConfig.name]: loraPersistConfig, [modelManagerV2PersistConfig.name]: modelManagerV2PersistConfig, [hrfPersistConfig.name]: hrfPersistConfig, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts index 1142a8cab7..8b1721cb3f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts @@ -3,15 +3,14 @@ import { createSlice } from '@reduxjs/toolkit'; import type { PersistConfig, RootState } from 'app/store/store'; import { deepClone } from 'common/util/deepClone'; import { roundDownToMultiple } from 'common/util/roundDownToMultiple'; +import { compositingReducers } from 'features/controlLayers/store/compositingReducers'; import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers'; import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers'; import { layersReducers } from 'features/controlLayers/store/layersReducers'; +import { paramsReducers } from 'features/controlLayers/store/paramsReducers'; import { regionsReducers } from 'features/controlLayers/store/regionsReducers'; -import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; -import { modelChanged } from 'features/parameters/store/generationSlice'; -import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension'; import type { IRect, Vector2d } from 'konva/lib/types'; import { atom } from 'nanostores'; @@ -54,6 +53,46 @@ const initialState: CanvasV2State = { regions: [], layers: [], maskFillOpacity: 0.3, + compositing: { + maskBlur: 16, + maskBlurMethod: 'box', + canvasCoherenceMode: 'Gaussian Blur', + canvasCoherenceMinDenoise: 0, + canvasCoherenceEdgeSize: 16, + infillMethod: 'patchmatch', + infillTileSize: 32, + infillPatchmatchDownscaleSize: 1, + infillColorValue: { r: 0, g: 0, b: 0, a: 1 }, + }, + params: { + cfgScale: 7.5, + cfgRescaleMultiplier: 0, + img2imgStrength: 0.75, + iterations: 1, + scheduler: 'euler', + seed: 0, + shouldRandomizeSeed: true, + steps: 50, + model: null, + vae: null, + vaePrecision: 'fp32', + seamlessXAxis: false, + seamlessYAxis: false, + clipSkip: 0, + shouldUseCpuNoise: true, + positivePrompt: '', + negativePrompt: '', + positivePrompt2: '', + negativePrompt2: '', + shouldConcatPrompts: true, + refinerModel: null, + refinerSteps: 20, + refinerCFGScale: 7.5, + refinerScheduler: 'euler', + refinerPositiveAestheticScore: 6, + refinerNegativeAestheticScore: 2.5, + refinerStart: 0.8, + }, }; export const canvasV2Slice = createSlice({ @@ -64,6 +103,8 @@ export const canvasV2Slice = createSlice({ ...ipAdaptersReducers, ...controlAdaptersReducers, ...regionsReducers, + ...paramsReducers, + ...compositingReducers, widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }>) => { const { width, updateAspectRatio, clamp } = action.payload; state.document.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width; @@ -119,22 +160,6 @@ export const canvasV2Slice = createSlice({ state.controlAdapters = []; }, }, - extraReducers(builder) { - builder.addCase(modelChanged, (state, action) => { - const newModel = action.payload; - if (!newModel || action.meta.previousModel?.base === newModel.base) { - // Model was cleared or the base didn't change - return; - } - const optimalDimension = getOptimalDimension(newModel); - if (getIsSizeOptimal(state.document.width, state.document.height, optimalDimension)) { - return; - } - const { width, height } = calculateNewSize(state.document.aspectRatio.value, optimalDimension * optimalDimension); - state.document.width = width; - state.document.height = height; - }); - }, }); export const { @@ -153,6 +178,7 @@ export const { allEntitiesDeleted, // layers layerAdded, + layerRecalled, layerDeleted, layerReset, layerMovedForwardOne, @@ -230,6 +256,43 @@ export const { rgEraserLineAdded, rgLinePointAdded, rgRectAdded, + // Compositing + setInfillMethod, + setInfillTileSize, + setInfillPatchmatchDownscaleSize, + setInfillColorValue, + setMaskBlur, + setCanvasCoherenceMode, + setCanvasCoherenceEdgeSize, + setCanvasCoherenceMinDenoise, + // Parameters + setIterations, + setSteps, + setCfgScale, + setCfgRescaleMultiplier, + setScheduler, + setSeed, + setImg2imgStrength, + setSeamlessXAxis, + setSeamlessYAxis, + setShouldRandomizeSeed, + vaeSelected, + vaePrecisionChanged, + setClipSkip, + shouldUseCpuNoiseChanged, + positivePromptChanged, + negativePromptChanged, + positivePrompt2Changed, + negativePrompt2Changed, + shouldConcatPromptsChanged, + refinerModelChanged, + setRefinerSteps, + setRefinerCFGScale, + setRefinerScheduler, + setRefinerPositiveAestheticScore, + setRefinerNegativeAestheticScore, + setRefinerStart, + modelChanged, } = canvasV2Slice.actions; export const selectCanvasV2Slice = (state: RootState) => state.canvasV2; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/compositingReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/compositingReducers.ts new file mode 100644 index 0000000000..03bb81ce04 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/store/compositingReducers.ts @@ -0,0 +1,30 @@ +import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; +import type { CanvasV2State, RgbaColor } from 'features/controlLayers/store/types'; +import type { ParameterCanvasCoherenceMode } from 'features/parameters/types/parameterSchemas'; + +export const compositingReducers = { + setInfillMethod: (state, action: PayloadAction) => { + state.compositing.infillMethod = action.payload; + }, + setInfillTileSize: (state, action: PayloadAction) => { + state.compositing.infillTileSize = action.payload; + }, + setInfillPatchmatchDownscaleSize: (state, action: PayloadAction) => { + state.compositing.infillPatchmatchDownscaleSize = action.payload; + }, + setInfillColorValue: (state, action: PayloadAction) => { + state.compositing.infillColorValue = action.payload; + }, + setMaskBlur: (state, action: PayloadAction) => { + state.compositing.maskBlur = action.payload; + }, + setCanvasCoherenceMode: (state, action: PayloadAction) => { + state.compositing.canvasCoherenceMode = action.payload; + }, + setCanvasCoherenceEdgeSize: (state, action: PayloadAction) => { + state.compositing.canvasCoherenceEdgeSize = action.payload; + }, + setCanvasCoherenceMinDenoise: (state, action: PayloadAction) => { + state.compositing.canvasCoherenceMinDenoise = action.payload; + }, +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/paramsReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/paramsReducers.ts new file mode 100644 index 0000000000..023ea78f02 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/store/paramsReducers.ts @@ -0,0 +1,132 @@ +import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; +import getScaledBoundingBoxDimensions from 'features/canvas/util/getScaledBoundingBoxDimensions'; +import type { CanvasV2State } from 'features/controlLayers/store/types'; +import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; +import { CLIP_SKIP_MAP } from 'features/parameters/types/constants'; +import type { + ParameterCFGRescaleMultiplier, + ParameterCFGScale, + ParameterModel, + ParameterPrecision, + ParameterScheduler, + ParameterSDXLRefinerModel, + ParameterVAEModel, +} from 'features/parameters/types/parameterSchemas'; +import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension'; +import { clamp } from 'lodash-es'; + +export const paramsReducers = { + setIterations: (state, action: PayloadAction) => { + state.params.iterations = action.payload; + }, + setSteps: (state, action: PayloadAction) => { + state.params.steps = action.payload; + }, + setCfgScale: (state, action: PayloadAction) => { + state.params.cfgScale = action.payload; + }, + setCfgRescaleMultiplier: (state, action: PayloadAction) => { + state.params.cfgRescaleMultiplier = action.payload; + }, + setScheduler: (state, action: PayloadAction) => { + state.params.scheduler = action.payload; + }, + setSeed: (state, action: PayloadAction) => { + state.params.seed = action.payload; + state.params.shouldRandomizeSeed = false; + }, + setImg2imgStrength: (state, action: PayloadAction) => { + state.params.img2imgStrength = action.payload; + }, + setSeamlessXAxis: (state, action: PayloadAction) => { + state.params.seamlessXAxis = action.payload; + }, + setSeamlessYAxis: (state, action: PayloadAction) => { + state.params.seamlessYAxis = action.payload; + }, + setShouldRandomizeSeed: (state, action: PayloadAction) => { + state.params.shouldRandomizeSeed = action.payload; + }, + modelChanged: (state, action: PayloadAction<{ model: ParameterModel | null; previousModel?: ParameterModel }>) => { + const { model, previousModel } = action.payload; + state.params.model = model; + + // If the model base changes (e.g. SD1.5 -> SDXL), we need to change a few things + if (model === null || previousModel?.base === model.base) { + return; + } + + // Update the bbox size to match the new model's optimal size + // TODO(psyche): Should we change the document size too? + const optimalDimension = getOptimalDimension(model); + if (!getIsSizeOptimal(state.document.width, state.document.height, optimalDimension)) { + const bboxDims = calculateNewSize(state.document.aspectRatio.value, optimalDimension * optimalDimension); + state.bbox.width = bboxDims.width; + state.bbox.height = bboxDims.height; + + if (state.scaledBbox.scaleMethod === 'auto') { + const scaledBboxDims = getScaledBoundingBoxDimensions(bboxDims, optimalDimension); + state.scaledBbox.width = scaledBboxDims.width; + state.scaledBbox.height = scaledBboxDims.height; + } + } + + // Clamp CLIP skip layer count to the bounds of the new model + if (model.base === 'sdxl') { + // We don't support user-defined CLIP skip for SDXL because it doesn't do anything useful + state.params.clipSkip = 0; + } else { + const { maxClip } = CLIP_SKIP_MAP[model.base]; + state.params.clipSkip = clamp(state.params.clipSkip, 0, maxClip); + } + }, + vaeSelected: (state, action: PayloadAction) => { + // null is a valid VAE! + state.params.vae = action.payload; + }, + vaePrecisionChanged: (state, action: PayloadAction) => { + state.params.vaePrecision = action.payload; + }, + setClipSkip: (state, action: PayloadAction) => { + state.params.clipSkip = action.payload; + }, + shouldUseCpuNoiseChanged: (state, action: PayloadAction) => { + state.params.shouldUseCpuNoise = action.payload; + }, + positivePromptChanged: (state, action: PayloadAction) => { + state.params.positivePrompt = action.payload; + }, + negativePromptChanged: (state, action: PayloadAction) => { + state.params.negativePrompt = action.payload; + }, + positivePrompt2Changed: (state, action: PayloadAction) => { + state.params.positivePrompt2 = action.payload; + }, + negativePrompt2Changed: (state, action: PayloadAction) => { + state.params.negativePrompt2 = action.payload; + }, + shouldConcatPromptsChanged: (state, action: PayloadAction) => { + state.params.shouldConcatPrompts = action.payload; + }, + refinerModelChanged: (state, action: PayloadAction) => { + state.params.refinerModel = action.payload; + }, + setRefinerSteps: (state, action: PayloadAction) => { + state.params.refinerSteps = action.payload; + }, + setRefinerCFGScale: (state, action: PayloadAction) => { + state.params.refinerCFGScale = action.payload; + }, + setRefinerScheduler: (state, action: PayloadAction) => { + state.params.refinerScheduler = action.payload; + }, + setRefinerPositiveAestheticScore: (state, action: PayloadAction) => { + state.params.refinerPositiveAestheticScore = action.payload; + }, + setRefinerNegativeAestheticScore: (state, action: PayloadAction) => { + state.params.refinerNegativeAestheticScore = action.payload; + }, + setRefinerStart: (state, action: PayloadAction) => { + state.params.refinerStart = action.payload; + }, +} satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index 86ded89ed4..64e2ffea05 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -1,7 +1,26 @@ import { deepClone } from 'common/util/deepClone'; import { zModelIdentifierField } from 'features/nodes/types/common'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; -import type { ParameterHeight, ParameterWidth } from 'features/parameters/types/parameterSchemas'; +import type { + ParameterCanvasCoherenceMode, + ParameterCFGRescaleMultiplier, + ParameterCFGScale, + ParameterHeight, + ParameterMaskBlurMethod, + ParameterModel, + ParameterNegativePrompt, + ParameterNegativeStylePromptSDXL, + ParameterPositivePrompt, + ParameterPositiveStylePromptSDXL, + ParameterPrecision, + ParameterScheduler, + ParameterSDXLRefinerModel, + ParameterSeed, + ParameterSteps, + ParameterStrength, + ParameterVAEModel, + ParameterWidth, +} from 'features/parameters/types/parameterSchemas'; import { zAutoNegative, zParameterNegativePrompt, @@ -785,6 +804,46 @@ export type CanvasV2State = { ipAdapters: IPAdapterData[]; regions: RegionalGuidanceData[]; maskFillOpacity: number; + compositing: { + maskBlur: number; + maskBlurMethod: ParameterMaskBlurMethod; + canvasCoherenceMode: ParameterCanvasCoherenceMode; + canvasCoherenceMinDenoise: ParameterStrength; + canvasCoherenceEdgeSize: number; + infillMethod: string; + infillTileSize: number; + infillPatchmatchDownscaleSize: number; + infillColorValue: RgbaColor; + }; + params: { + cfgScale: ParameterCFGScale; + cfgRescaleMultiplier: ParameterCFGRescaleMultiplier; + img2imgStrength: ParameterStrength; + iterations: number; + scheduler: ParameterScheduler; + seed: ParameterSeed; + shouldRandomizeSeed: boolean; + steps: ParameterSteps; + model: ParameterModel | null; + vae: ParameterVAEModel | null; + vaePrecision: ParameterPrecision; + seamlessXAxis: boolean; + seamlessYAxis: boolean; + clipSkip: number; + shouldUseCpuNoise: boolean; + positivePrompt: ParameterPositivePrompt; + negativePrompt: ParameterNegativePrompt; + positivePrompt2: ParameterPositiveStylePromptSDXL; + negativePrompt2: ParameterNegativeStylePromptSDXL; + shouldConcatPrompts: boolean; + refinerModel: ParameterSDXLRefinerModel | null; + refinerSteps: number; + refinerCFGScale: number; + refinerScheduler: ParameterScheduler; + refinerPositiveAestheticScore: number; + refinerNegativeAestheticScore: number; + refinerStart: number; + }; }; export type StageAttrs = { x: number; y: number; width: number; height: number; scale: number };