mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): modularize imagesize components
Canvas and non-canvas have separate width and height and need their own separate aspect ratios. In order to not duplicate a lot of aspect ratio logic, the components relating to image size have been modularized.
This commit is contained in:
committed by
Kent Keirsey
parent
011757c497
commit
4f43eda09b
@@ -1,4 +1,4 @@
|
||||
import type { GenerationState } from './generationSlice';
|
||||
import type { GenerationState } from './types';
|
||||
|
||||
/**
|
||||
* Generation slice persist denylist
|
||||
|
||||
@@ -2,77 +2,25 @@ import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { isAnyControlAdapterAdded } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { ASPECT_RATIO_MAP } from 'features/parameters/components/ImageSize/constants';
|
||||
import type { AspectRatioID } from 'features/parameters/components/ImageSize/types';
|
||||
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
|
||||
import type {
|
||||
ParameterCanvasCoherenceMode,
|
||||
ParameterCFGRescaleMultiplier,
|
||||
ParameterCFGScale,
|
||||
ParameterHeight,
|
||||
ParameterMaskBlurMethod,
|
||||
ParameterModel,
|
||||
ParameterNegativePrompt,
|
||||
ParameterPositivePrompt,
|
||||
ParameterPrecision,
|
||||
ParameterScheduler,
|
||||
ParameterSeed,
|
||||
ParameterSteps,
|
||||
ParameterStrength,
|
||||
ParameterVAEModel,
|
||||
ParameterWidth,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import { zParameterModel } from 'features/parameters/types/parameterSchemas';
|
||||
import { configChanged } from 'features/system/store/configSlice';
|
||||
import { clamp, cloneDeep } from 'lodash-es';
|
||||
import { clamp } from 'lodash-es';
|
||||
import type { ImageDTO } from 'services/api/types';
|
||||
|
||||
export interface GenerationState {
|
||||
cfgScale: ParameterCFGScale;
|
||||
cfgRescaleMultiplier: ParameterCFGRescaleMultiplier;
|
||||
height: ParameterHeight;
|
||||
img2imgStrength: ParameterStrength;
|
||||
infillMethod: string;
|
||||
initialImage?: { imageName: string; width: number; height: number };
|
||||
iterations: number;
|
||||
perlin: number;
|
||||
positivePrompt: ParameterPositivePrompt;
|
||||
negativePrompt: ParameterNegativePrompt;
|
||||
scheduler: ParameterScheduler;
|
||||
maskBlur: number;
|
||||
maskBlurMethod: ParameterMaskBlurMethod;
|
||||
canvasCoherenceMode: ParameterCanvasCoherenceMode;
|
||||
canvasCoherenceSteps: number;
|
||||
canvasCoherenceStrength: ParameterStrength;
|
||||
seed: ParameterSeed;
|
||||
seedWeights: string;
|
||||
shouldFitToWidthHeight: boolean;
|
||||
shouldGenerateVariations: boolean;
|
||||
shouldRandomizeSeed: boolean;
|
||||
steps: ParameterSteps;
|
||||
threshold: number;
|
||||
infillTileSize: number;
|
||||
infillPatchmatchDownscaleSize: number;
|
||||
variationAmount: number;
|
||||
width: ParameterWidth;
|
||||
shouldUseSymmetry: boolean;
|
||||
horizontalSymmetrySteps: number;
|
||||
verticalSymmetrySteps: number;
|
||||
model: ParameterModel | null;
|
||||
vae: ParameterVAEModel | null;
|
||||
vaePrecision: ParameterPrecision;
|
||||
seamlessXAxis: boolean;
|
||||
seamlessYAxis: boolean;
|
||||
clipSkip: number;
|
||||
shouldUseCpuNoise: boolean;
|
||||
shouldShowAdvancedOptions: boolean;
|
||||
aspectRatio: {
|
||||
id: AspectRatioID;
|
||||
value: number;
|
||||
isLocked: boolean;
|
||||
};
|
||||
}
|
||||
import type { GenerationState } from './types';
|
||||
|
||||
export const initialGenerationState: GenerationState = {
|
||||
cfgScale: 7.5,
|
||||
@@ -112,18 +60,12 @@ export const initialGenerationState: GenerationState = {
|
||||
clipSkip: 0,
|
||||
shouldUseCpuNoise: true,
|
||||
shouldShowAdvancedOptions: false,
|
||||
aspectRatio: {
|
||||
id: '1:1',
|
||||
value: 1,
|
||||
isLocked: false,
|
||||
},
|
||||
aspectRatio: { ...initialAspectRatioState },
|
||||
};
|
||||
|
||||
const initialState: GenerationState = initialGenerationState;
|
||||
|
||||
export const generationSlice = createSlice({
|
||||
name: 'generation',
|
||||
initialState,
|
||||
initialState: initialGenerationState,
|
||||
reducers: {
|
||||
setPositivePrompt: (state, action: PayloadAction<string>) => {
|
||||
state.positivePrompt = action.payload;
|
||||
@@ -279,93 +221,15 @@ export const generationSlice = createSlice({
|
||||
shouldUseCpuNoiseChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldUseCpuNoise = action.payload;
|
||||
},
|
||||
aspectRatioSelected: (
|
||||
state,
|
||||
action: PayloadAction<GenerationState['aspectRatio']['id']>
|
||||
) => {
|
||||
const aspectRatioID = action.payload;
|
||||
state.aspectRatio.id = aspectRatioID;
|
||||
if (aspectRatioID === 'Free') {
|
||||
// If the new aspect ratio is free, we only unlock
|
||||
state.aspectRatio.isLocked = false;
|
||||
} else {
|
||||
// The new aspect ratio not free, so we need to coerce the size & lock
|
||||
state.aspectRatio.isLocked = true;
|
||||
const aspectRatio = ASPECT_RATIO_MAP[aspectRatioID].ratio;
|
||||
state.aspectRatio.value = aspectRatio;
|
||||
const { width, height } = calculateNewSize(
|
||||
aspectRatio,
|
||||
state.width,
|
||||
state.height
|
||||
);
|
||||
state.width = width;
|
||||
state.height = height;
|
||||
}
|
||||
},
|
||||
dimensionsSwapped: (state) => {
|
||||
// We always invert the aspect ratio
|
||||
const aspectRatio = 1 / state.aspectRatio.value;
|
||||
state.aspectRatio.value = aspectRatio;
|
||||
if (state.aspectRatio.id === 'Free') {
|
||||
// If the aspect ratio is free, we just swap the dimensions
|
||||
const oldWidth = state.width;
|
||||
const oldHeight = state.height;
|
||||
state.width = oldHeight;
|
||||
state.height = oldWidth;
|
||||
} else {
|
||||
// Else we need to calculate the new size
|
||||
const { width, height } = calculateNewSize(
|
||||
aspectRatio,
|
||||
state.width,
|
||||
state.height
|
||||
);
|
||||
state.width = width;
|
||||
state.height = height;
|
||||
// Update the aspect ratio ID to match the new aspect ratio
|
||||
state.aspectRatio.id = ASPECT_RATIO_MAP[state.aspectRatio.id].inverseID;
|
||||
}
|
||||
},
|
||||
widthChanged: (state, action: PayloadAction<GenerationState['width']>) => {
|
||||
const width = action.payload;
|
||||
let height = state.height;
|
||||
if (state.aspectRatio.isLocked) {
|
||||
// When locked, we calculate the new height based on the aspect ratio
|
||||
height = roundToMultiple(width / state.aspectRatio.value, 8);
|
||||
} else {
|
||||
// Else we unlock, set the aspect ratio to free, and update the aspect ratio itself
|
||||
state.aspectRatio.isLocked = false;
|
||||
state.aspectRatio.id = 'Free';
|
||||
state.aspectRatio.value = width / height;
|
||||
}
|
||||
state.width = width;
|
||||
state.height = height;
|
||||
},
|
||||
heightChanged: (
|
||||
state,
|
||||
action: PayloadAction<GenerationState['height']>
|
||||
) => {
|
||||
const height = action.payload;
|
||||
let width = state.width;
|
||||
if (state.aspectRatio.isLocked) {
|
||||
// When locked, we calculate the new width based on the aspect ratio
|
||||
width = roundToMultiple(height * state.aspectRatio.value, 8);
|
||||
} else {
|
||||
// Else we unlock, set the aspect ratio to free, and update the aspect ratio itself
|
||||
state.aspectRatio.isLocked = false;
|
||||
state.aspectRatio.id = 'Free';
|
||||
state.aspectRatio.value = width / height;
|
||||
}
|
||||
state.height = height;
|
||||
state.width = width;
|
||||
},
|
||||
isLockedToggled: (state) => {
|
||||
state.aspectRatio.isLocked = !state.aspectRatio.isLocked;
|
||||
},
|
||||
sizeReset: (state, action: PayloadAction<number>) => {
|
||||
state.aspectRatio = cloneDeep(initialGenerationState.aspectRatio);
|
||||
widthChanged: (state, action: PayloadAction<number>) => {
|
||||
state.width = action.payload;
|
||||
},
|
||||
heightChanged: (state, action: PayloadAction<number>) => {
|
||||
state.height = action.payload;
|
||||
},
|
||||
aspectRatioChanged: (state, action: PayloadAction<AspectRatioState>) => {
|
||||
state.aspectRatio = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(configChanged, (state, action) => {
|
||||
@@ -436,13 +300,10 @@ export const {
|
||||
setSeamlessYAxis,
|
||||
setClipSkip,
|
||||
shouldUseCpuNoiseChanged,
|
||||
aspectRatioSelected,
|
||||
dimensionsSwapped,
|
||||
vaePrecisionChanged,
|
||||
aspectRatioChanged,
|
||||
widthChanged,
|
||||
heightChanged,
|
||||
isLockedToggled,
|
||||
sizeReset,
|
||||
vaePrecisionChanged,
|
||||
} = generationSlice.actions;
|
||||
|
||||
export default generationSlice.reducer;
|
||||
|
||||
60
invokeai/frontend/web/src/features/parameters/store/types.ts
Normal file
60
invokeai/frontend/web/src/features/parameters/store/types.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import type {
|
||||
ParameterCanvasCoherenceMode,
|
||||
ParameterCFGRescaleMultiplier,
|
||||
ParameterCFGScale,
|
||||
ParameterHeight,
|
||||
ParameterMaskBlurMethod,
|
||||
ParameterModel,
|
||||
ParameterNegativePrompt,
|
||||
ParameterPositivePrompt,
|
||||
ParameterPrecision,
|
||||
ParameterScheduler,
|
||||
ParameterSeed,
|
||||
ParameterSteps,
|
||||
ParameterStrength,
|
||||
ParameterVAEModel,
|
||||
ParameterWidth,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
|
||||
export interface GenerationState {
|
||||
cfgScale: ParameterCFGScale;
|
||||
cfgRescaleMultiplier: ParameterCFGRescaleMultiplier;
|
||||
height: ParameterHeight;
|
||||
img2imgStrength: ParameterStrength;
|
||||
infillMethod: string;
|
||||
initialImage?: { imageName: string; width: number; height: number };
|
||||
iterations: number;
|
||||
perlin: number;
|
||||
positivePrompt: ParameterPositivePrompt;
|
||||
negativePrompt: ParameterNegativePrompt;
|
||||
scheduler: ParameterScheduler;
|
||||
maskBlur: number;
|
||||
maskBlurMethod: ParameterMaskBlurMethod;
|
||||
canvasCoherenceMode: ParameterCanvasCoherenceMode;
|
||||
canvasCoherenceSteps: number;
|
||||
canvasCoherenceStrength: ParameterStrength;
|
||||
seed: ParameterSeed;
|
||||
seedWeights: string;
|
||||
shouldFitToWidthHeight: boolean;
|
||||
shouldGenerateVariations: boolean;
|
||||
shouldRandomizeSeed: boolean;
|
||||
steps: ParameterSteps;
|
||||
threshold: number;
|
||||
infillTileSize: number;
|
||||
infillPatchmatchDownscaleSize: number;
|
||||
variationAmount: number;
|
||||
width: ParameterWidth;
|
||||
shouldUseSymmetry: boolean;
|
||||
horizontalSymmetrySteps: number;
|
||||
verticalSymmetrySteps: number;
|
||||
model: ParameterModel | null;
|
||||
vae: ParameterVAEModel | null;
|
||||
vaePrecision: ParameterPrecision;
|
||||
seamlessXAxis: boolean;
|
||||
seamlessYAxis: boolean;
|
||||
clipSkip: number;
|
||||
shouldUseCpuNoise: boolean;
|
||||
shouldShowAdvancedOptions: boolean;
|
||||
aspectRatio: AspectRatioState;
|
||||
}
|
||||
Reference in New Issue
Block a user