mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
refactor(ui): move size state to regional
This commit is contained in:
parent
b6a45e53f1
commit
cc4bef4859
@ -8,9 +8,10 @@ import {
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { loraRemoved } from 'features/lora/store/loraSlice';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { heightChanged, modelChanged, vaeSelected, widthChanged } from 'features/parameters/store/generationSlice';
|
||||
import { modelChanged, vaeSelected } from 'features/parameters/store/generationSlice';
|
||||
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
|
||||
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
import { refinerModelChanged } from 'features/sdxl/store/sdxlSlice';
|
||||
import { forEach } from 'lodash-es';
|
||||
import type { Logger } from 'roarr';
|
||||
@ -69,16 +70,22 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
|
||||
dispatch(modelChanged(defaultModelInList, currentModel));
|
||||
|
||||
const optimalDimension = getOptimalDimension(defaultModelInList);
|
||||
if (getIsSizeOptimal(state.generation.width, state.generation.height, optimalDimension)) {
|
||||
if (
|
||||
getIsSizeOptimal(
|
||||
state.regionalPrompts.present.size.width,
|
||||
state.regionalPrompts.present.size.height,
|
||||
optimalDimension
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const { width, height } = calculateNewSize(
|
||||
state.generation.aspectRatio.value,
|
||||
state.regionalPrompts.present.size.aspectRatio.value,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
|
||||
dispatch(widthChanged(width));
|
||||
dispatch(heightChanged(height));
|
||||
dispatch(widthChanged({ width }));
|
||||
dispatch(heightChanged({ height }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,12 @@
|
||||
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
|
||||
import { setDefaultSettings } from 'features/parameters/store/actions';
|
||||
import {
|
||||
heightRecalled,
|
||||
setCfgRescaleMultiplier,
|
||||
setCfgScale,
|
||||
setScheduler,
|
||||
setSteps,
|
||||
vaePrecisionChanged,
|
||||
vaeSelected,
|
||||
widthRecalled,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import {
|
||||
isParameterCFGRescaleMultiplier,
|
||||
@ -20,6 +18,7 @@ import {
|
||||
isParameterWidth,
|
||||
zParameterVAEModel,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import { heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { makeToast } from 'features/system/util/makeToast';
|
||||
import { t } from 'i18next';
|
||||
@ -100,13 +99,13 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
|
||||
|
||||
if (width) {
|
||||
if (isParameterWidth(width)) {
|
||||
dispatch(widthRecalled(width));
|
||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
||||
}
|
||||
}
|
||||
|
||||
if (height) {
|
||||
if (isParameterHeight(height)) {
|
||||
dispatch(heightRecalled(height));
|
||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,8 @@ import {
|
||||
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import type { TypesafeDraggableData, TypesafeDroppableData } from 'features/dnd/types';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { heightChanged, selectOptimalDimension, widthChanged } from 'features/parameters/store/generationSlice';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import { heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -99,8 +100,8 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
|
||||
controlImage.width / controlImage.height,
|
||||
optimalDimension * optimalDimension
|
||||
);
|
||||
dispatch(widthChanged(width));
|
||||
dispatch(heightChanged(height));
|
||||
dispatch(widthChanged({ width, updateAspectRatio: true }));
|
||||
dispatch(heightChanged({ height, updateAspectRatio: true }));
|
||||
}
|
||||
}, [controlImage, activeTabName, dispatch, optimalDimension]);
|
||||
|
||||
|
@ -16,7 +16,6 @@ import type {
|
||||
} from 'features/metadata/types';
|
||||
import { modelSelected } from 'features/parameters/store/actions';
|
||||
import {
|
||||
heightRecalled,
|
||||
initialImageChanged,
|
||||
setCfgRescaleMultiplier,
|
||||
setCfgScale,
|
||||
@ -25,7 +24,6 @@ import {
|
||||
setSeed,
|
||||
setSteps,
|
||||
vaeSelected,
|
||||
widthRecalled,
|
||||
} from 'features/parameters/store/generationSlice';
|
||||
import type {
|
||||
ParameterCFGRescaleMultiplier,
|
||||
@ -50,10 +48,12 @@ import type {
|
||||
ParameterWidth,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import {
|
||||
heightChanged,
|
||||
negativePrompt2Changed,
|
||||
negativePromptChanged,
|
||||
positivePrompt2Changed,
|
||||
positivePromptChanged,
|
||||
widthChanged,
|
||||
} from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
import {
|
||||
refinerModelChanged,
|
||||
@ -103,11 +103,11 @@ const recallInitialImage: MetadataRecallFunc<ImageDTO> = async (imageDTO) => {
|
||||
};
|
||||
|
||||
const recallWidth: MetadataRecallFunc<ParameterWidth> = (width) => {
|
||||
getStore().dispatch(widthRecalled(width));
|
||||
getStore().dispatch(widthChanged({ width, updateAspectRatio: true }));
|
||||
};
|
||||
|
||||
const recallHeight: MetadataRecallFunc<ParameterHeight> = (height) => {
|
||||
getStore().dispatch(heightRecalled(height));
|
||||
getStore().dispatch(heightChanged({ height, updateAspectRatio: true }));
|
||||
};
|
||||
|
||||
const recallSteps: MetadataRecallFunc<ParameterSteps> = (steps) => {
|
||||
|
@ -110,10 +110,9 @@ export const addHrfToGraph = (state: RootState, graph: NonNullableGraph): void =
|
||||
|
||||
const { vae, seamlessXAxis, seamlessYAxis } = state.generation;
|
||||
const { hrfStrength, hrfEnabled, hrfMethod } = state.hrf;
|
||||
const { width, height } = state.regionalPrompts.present.size;
|
||||
const isAutoVae = !vae;
|
||||
const isSeamlessEnabled = seamlessXAxis || seamlessYAxis;
|
||||
const width = state.generation.width;
|
||||
const height = state.generation.height;
|
||||
const optimalDimension = selectOptimalDimension(state);
|
||||
const { newWidth: hrfWidth, newHeight: hrfHeight } = calculateHrfRes(optimalDimension, width, height);
|
||||
|
||||
|
@ -47,8 +47,6 @@ export const buildLinearImageToImageGraph = async (state: RootState): Promise<No
|
||||
initialImage,
|
||||
img2imgStrength: strength,
|
||||
shouldFitToWidthHeight,
|
||||
width,
|
||||
height,
|
||||
clipSkip,
|
||||
shouldUseCpuNoise,
|
||||
vaePrecision,
|
||||
@ -56,6 +54,7 @@ export const buildLinearImageToImageGraph = async (state: RootState): Promise<No
|
||||
seamlessYAxis,
|
||||
} = state.generation;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer;
|
||||
const { width, height } = state.regionalPrompts.present.size;
|
||||
|
||||
/**
|
||||
* The easiest way to build linear graphs is to do it in the node editor, then copy and paste the
|
||||
|
@ -47,8 +47,6 @@ export const buildLinearSDXLImageToImageGraph = async (state: RootState): Promis
|
||||
steps,
|
||||
initialImage,
|
||||
shouldFitToWidthHeight,
|
||||
width,
|
||||
height,
|
||||
shouldUseCpuNoise,
|
||||
vaePrecision,
|
||||
seamlessXAxis,
|
||||
@ -56,6 +54,7 @@ export const buildLinearSDXLImageToImageGraph = async (state: RootState): Promis
|
||||
img2imgStrength: strength,
|
||||
} = state.generation;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer;
|
||||
const { width, height } = state.regionalPrompts.present.size;
|
||||
|
||||
const { refinerModel, refinerStart } = state.sdxl;
|
||||
|
||||
|
@ -36,14 +36,13 @@ export const buildLinearSDXLTextToImageGraph = async (state: RootState): Promise
|
||||
scheduler,
|
||||
seed,
|
||||
steps,
|
||||
width,
|
||||
height,
|
||||
shouldUseCpuNoise,
|
||||
vaePrecision,
|
||||
seamlessXAxis,
|
||||
seamlessYAxis,
|
||||
} = state.generation;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer;
|
||||
const { width, height } = state.regionalPrompts.present.size;
|
||||
|
||||
const { refinerModel, refinerStart } = state.sdxl;
|
||||
|
||||
|
@ -35,8 +35,6 @@ export const buildLinearTextToImageGraph = async (state: RootState): Promise<Non
|
||||
cfgRescaleMultiplier: cfg_rescale_multiplier,
|
||||
scheduler,
|
||||
steps,
|
||||
width,
|
||||
height,
|
||||
clipSkip,
|
||||
shouldUseCpuNoise,
|
||||
vaePrecision,
|
||||
@ -45,6 +43,7 @@ export const buildLinearTextToImageGraph = async (state: RootState): Promise<Non
|
||||
seed,
|
||||
} = state.generation;
|
||||
const { positivePrompt, negativePrompt } = state.regionalPrompts.present.baseLayer;
|
||||
const { width, height } = state.regionalPrompts.present.size;
|
||||
|
||||
const use_cpu = shouldUseCpuNoise;
|
||||
|
||||
|
@ -1,11 +1,7 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { isAnyControlAdapterAdded } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
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 { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
|
||||
import type {
|
||||
ParameterCanvasCoherenceMode,
|
||||
@ -16,7 +12,7 @@ import type {
|
||||
ParameterScheduler,
|
||||
ParameterVAEModel,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import { configChanged } from 'features/system/store/configSlice';
|
||||
import { clamp } from 'lodash-es';
|
||||
import type { RgbaColor } from 'react-colorful';
|
||||
@ -28,7 +24,6 @@ const initialGenerationState: GenerationState = {
|
||||
_version: 2,
|
||||
cfgScale: 7.5,
|
||||
cfgRescaleMultiplier: 0,
|
||||
height: 512,
|
||||
img2imgStrength: 0.75,
|
||||
infillMethod: 'patchmatch',
|
||||
iterations: 1,
|
||||
@ -42,7 +37,6 @@ const initialGenerationState: GenerationState = {
|
||||
shouldFitToWidthHeight: true,
|
||||
shouldRandomizeSeed: true,
|
||||
steps: 50,
|
||||
width: 512,
|
||||
model: null,
|
||||
vae: null,
|
||||
vaePrecision: 'fp32',
|
||||
@ -51,7 +45,6 @@ const initialGenerationState: GenerationState = {
|
||||
clipSkip: 0,
|
||||
shouldUseCpuNoise: true,
|
||||
shouldShowAdvancedOptions: false,
|
||||
aspectRatio: { ...initialAspectRatioState },
|
||||
infillTileSize: 32,
|
||||
infillPatchmatchDownscaleSize: 1,
|
||||
infillMosaicTileWidth: 64,
|
||||
@ -140,19 +133,6 @@ export const generationSlice = createSlice({
|
||||
const { maxClip } = CLIP_SKIP_MAP[newModel.base];
|
||||
state.clipSkip = clamp(state.clipSkip, 0, maxClip);
|
||||
}
|
||||
|
||||
if (action.meta.previousModel?.base === newModel.base) {
|
||||
// The base model hasn't changed, we don't need to optimize the size
|
||||
return;
|
||||
}
|
||||
|
||||
const optimalDimension = getOptimalDimension(newModel);
|
||||
if (getIsSizeOptimal(state.width, state.height, optimalDimension)) {
|
||||
return;
|
||||
}
|
||||
const { width, height } = calculateNewSize(state.aspectRatio.value, optimalDimension * optimalDimension);
|
||||
state.width = width;
|
||||
state.height = height;
|
||||
},
|
||||
prepare: (payload: ParameterModel | null, previousModel?: ParameterModel | null) => ({
|
||||
payload,
|
||||
@ -174,27 +154,6 @@ export const generationSlice = createSlice({
|
||||
shouldUseCpuNoiseChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldUseCpuNoise = action.payload;
|
||||
},
|
||||
widthChanged: (state, action: PayloadAction<number>) => {
|
||||
state.width = action.payload;
|
||||
},
|
||||
heightChanged: (state, action: PayloadAction<number>) => {
|
||||
state.height = action.payload;
|
||||
},
|
||||
widthRecalled: (state, action: PayloadAction<number>) => {
|
||||
state.width = action.payload;
|
||||
state.aspectRatio.value = action.payload / state.height;
|
||||
state.aspectRatio.id = 'Free';
|
||||
state.aspectRatio.isLocked = false;
|
||||
},
|
||||
heightRecalled: (state, action: PayloadAction<number>) => {
|
||||
state.height = action.payload;
|
||||
state.aspectRatio.value = state.width / action.payload;
|
||||
state.aspectRatio.id = 'Free';
|
||||
state.aspectRatio.isLocked = false;
|
||||
},
|
||||
aspectRatioChanged: (state, action: PayloadAction<AspectRatioState>) => {
|
||||
state.aspectRatio = action.payload;
|
||||
},
|
||||
setInfillMethod: (state, action: PayloadAction<string>) => {
|
||||
state.infillMethod = action.payload;
|
||||
},
|
||||
@ -229,15 +188,6 @@ export const generationSlice = createSlice({
|
||||
state.vaePrecision = action.payload.sd.vaePrecision;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: This is a temp fix to reduce issues with T2I adapter having a different downscaling
|
||||
// factor than the UNet. Hopefully we get an upstream fix in diffusers.
|
||||
builder.addMatcher(isAnyControlAdapterAdded, (state, action) => {
|
||||
if (action.payload.type === 't2i_adapter') {
|
||||
state.width = roundToMultiple(state.width, 64);
|
||||
state.height = roundToMultiple(state.height, 64);
|
||||
}
|
||||
});
|
||||
},
|
||||
selectors: {
|
||||
selectOptimalDimension: (slice) => getOptimalDimension(slice.model),
|
||||
@ -268,11 +218,6 @@ export const {
|
||||
setClipSkip,
|
||||
shouldUseCpuNoiseChanged,
|
||||
vaePrecisionChanged,
|
||||
aspectRatioChanged,
|
||||
widthChanged,
|
||||
heightChanged,
|
||||
widthRecalled,
|
||||
heightRecalled,
|
||||
setInfillTileSize,
|
||||
setInfillPatchmatchDownscaleSize,
|
||||
setInfillMosaicTileWidth,
|
||||
|
@ -1,10 +1,8 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import type {
|
||||
ParameterCanvasCoherenceMode,
|
||||
ParameterCFGRescaleMultiplier,
|
||||
ParameterCFGScale,
|
||||
ParameterHeight,
|
||||
ParameterMaskBlurMethod,
|
||||
ParameterModel,
|
||||
ParameterPrecision,
|
||||
@ -13,7 +11,6 @@ import type {
|
||||
ParameterSteps,
|
||||
ParameterStrength,
|
||||
ParameterVAEModel,
|
||||
ParameterWidth,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import type { RgbaColor } from 'react-colorful';
|
||||
|
||||
@ -21,7 +18,6 @@ export interface GenerationState {
|
||||
_version: 2;
|
||||
cfgScale: ParameterCFGScale;
|
||||
cfgRescaleMultiplier: ParameterCFGRescaleMultiplier;
|
||||
height: ParameterHeight;
|
||||
img2imgStrength: ParameterStrength;
|
||||
infillMethod: string;
|
||||
initialImage?: { imageName: string; width: number; height: number };
|
||||
@ -36,7 +32,6 @@ export interface GenerationState {
|
||||
shouldFitToWidthHeight: boolean;
|
||||
shouldRandomizeSeed: boolean;
|
||||
steps: ParameterSteps;
|
||||
width: ParameterWidth;
|
||||
model: ParameterModel | null;
|
||||
vae: ParameterVAEModel | null;
|
||||
vaePrecision: ParameterPrecision;
|
||||
@ -45,7 +40,6 @@ export interface GenerationState {
|
||||
clipSkip: number;
|
||||
shouldUseCpuNoise: boolean;
|
||||
shouldShowAdvancedOptions: boolean;
|
||||
aspectRatio: AspectRatioState;
|
||||
infillTileSize: number;
|
||||
infillPatchmatchDownscaleSize: number;
|
||||
infillMosaicTileWidth: number;
|
||||
|
@ -43,8 +43,6 @@ const useStageRenderer = (
|
||||
asPreview: boolean
|
||||
) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const width = useAppSelector((s) => s.generation.width);
|
||||
const height = useAppSelector((s) => s.generation.height);
|
||||
const state = useAppSelector((s) => s.regionalPrompts.present);
|
||||
const tool = useStore($tool);
|
||||
const { onMouseDown, onMouseUp, onMouseMove, onMouseEnter, onMouseLeave, onMouseWheel } = useMouseEvents();
|
||||
@ -121,11 +119,11 @@ const useStageRenderer = (
|
||||
const stage = stageRef.current;
|
||||
|
||||
const fitStageToContainer = () => {
|
||||
const newXScale = wrapper.offsetWidth / width;
|
||||
const newYScale = wrapper.offsetHeight / height;
|
||||
const newXScale = wrapper.offsetWidth / state.size.width;
|
||||
const newYScale = wrapper.offsetHeight / state.size.height;
|
||||
const newScale = Math.min(newXScale, newYScale, 1);
|
||||
stage.width(width * newScale);
|
||||
stage.height(height * newScale);
|
||||
stage.width(state.size.width * newScale);
|
||||
stage.height(state.size.height * newScale);
|
||||
stage.scaleX(newScale);
|
||||
stage.scaleY(newScale);
|
||||
};
|
||||
@ -137,7 +135,7 @@ const useStageRenderer = (
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [stageRef, width, height, wrapper]);
|
||||
}, [stageRef, state.size.width, state.size.height, wrapper]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
log.trace('Rendering tool preview');
|
||||
@ -188,8 +186,8 @@ const useStageRenderer = (
|
||||
// The preview should not have a background
|
||||
return;
|
||||
}
|
||||
renderers.renderBackground(stageRef.current, width, height);
|
||||
}, [stageRef, asPreview, width, height, renderers]);
|
||||
renderers.renderBackground(stageRef.current, state.size.width, state.size.height);
|
||||
}, [stageRef, asPreview, state.size.width, state.size.height, renderers]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
log.trace('Arranging layers');
|
||||
|
@ -2,15 +2,24 @@ import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit';
|
||||
import { createSlice, isAnyOf } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { moveBackward, moveForward, moveToBack, moveToFront } from 'common/util/arrayUtils';
|
||||
import { controlAdapterRemoved } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { deepClone } from 'common/util/deepClone';
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import { controlAdapterRemoved, isAnyControlAdapterAdded } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import type { ControlAdapterConfig } from 'features/controlAdapters/store/types';
|
||||
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 type {
|
||||
ParameterAutoNegative,
|
||||
ParameterHeight,
|
||||
ParameterNegativePrompt,
|
||||
ParameterNegativeStylePromptSDXL,
|
||||
ParameterPositivePrompt,
|
||||
ParameterPositiveStylePromptSDXL,
|
||||
ParameterWidth,
|
||||
} from 'features/parameters/types/parameterSchemas';
|
||||
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
|
||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { atom } from 'nanostores';
|
||||
@ -86,6 +95,11 @@ type RegionalPromptsState = {
|
||||
globalMaskLayerOpacity: number;
|
||||
isEnabled: boolean;
|
||||
baseLayer: BaseLayerState;
|
||||
size: {
|
||||
width: ParameterWidth;
|
||||
height: ParameterHeight;
|
||||
aspectRatio: AspectRatioState;
|
||||
};
|
||||
};
|
||||
|
||||
export const initialRegionalPromptsState: RegionalPromptsState = {
|
||||
@ -102,6 +116,11 @@ export const initialRegionalPromptsState: RegionalPromptsState = {
|
||||
negativePrompt2: '',
|
||||
shouldConcatPrompts: true,
|
||||
},
|
||||
size: {
|
||||
width: 512,
|
||||
height: 512,
|
||||
aspectRatio: deepClone(initialAspectRatioState),
|
||||
},
|
||||
};
|
||||
|
||||
const isLine = (obj: VectorMaskLine | VectorMaskRect): obj is VectorMaskLine => obj.type === 'vector_mask_line';
|
||||
@ -364,6 +383,27 @@ export const regionalPromptsSlice = createSlice({
|
||||
shouldConcatPromptsChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.baseLayer.shouldConcatPrompts = action.payload;
|
||||
},
|
||||
widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean }>) => {
|
||||
const { width, updateAspectRatio } = action.payload;
|
||||
state.size.width = width;
|
||||
if (updateAspectRatio) {
|
||||
state.size.aspectRatio.value = width / state.size.height;
|
||||
state.size.aspectRatio.id = 'Free';
|
||||
state.size.aspectRatio.isLocked = false;
|
||||
}
|
||||
},
|
||||
heightChanged: (state, action: PayloadAction<{ height: number; updateAspectRatio?: boolean }>) => {
|
||||
const { height, updateAspectRatio } = action.payload;
|
||||
state.size.height = height;
|
||||
if (updateAspectRatio) {
|
||||
state.size.aspectRatio.value = state.size.width / height;
|
||||
state.size.aspectRatio.id = 'Free';
|
||||
state.size.aspectRatio.isLocked = false;
|
||||
}
|
||||
},
|
||||
aspectRatioChanged: (state, action: PayloadAction<AspectRatioState>) => {
|
||||
state.size.aspectRatio = action.payload;
|
||||
},
|
||||
//#endregion
|
||||
|
||||
//#region General
|
||||
@ -396,6 +436,30 @@ export const regionalPromptsSlice = createSlice({
|
||||
layer.ipAdapterIds = layer.ipAdapterIds.filter((id) => id !== action.payload.id);
|
||||
});
|
||||
});
|
||||
|
||||
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.size.width, state.size.height, optimalDimension)) {
|
||||
return;
|
||||
}
|
||||
const { width, height } = calculateNewSize(state.size.aspectRatio.value, optimalDimension * optimalDimension);
|
||||
state.size.width = width;
|
||||
state.size.height = height;
|
||||
});
|
||||
|
||||
// TODO: This is a temp fix to reduce issues with T2I adapter having a different downscaling
|
||||
// factor than the UNet. Hopefully we get an upstream fix in diffusers.
|
||||
builder.addMatcher(isAnyControlAdapterAdded, (state, action) => {
|
||||
if (action.payload.type === 't2i_adapter') {
|
||||
state.size.width = roundToMultiple(state.size.width, 64);
|
||||
state.size.height = roundToMultiple(state.size.height, 64);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@ -461,6 +525,9 @@ export const {
|
||||
positivePrompt2Changed,
|
||||
negativePrompt2Changed,
|
||||
shouldConcatPromptsChanged,
|
||||
widthChanged,
|
||||
heightChanged,
|
||||
aspectRatioChanged,
|
||||
// General actions
|
||||
brushSizeChanged,
|
||||
globalMaskLayerOpacityChanged,
|
||||
|
@ -17,9 +17,11 @@ export const getRegionalPromptLayerBlobs = async (
|
||||
preview: boolean = false
|
||||
): Promise<Record<string, Blob>> => {
|
||||
const state = getStore().getState();
|
||||
const reduxLayers = state.regionalPrompts.present.layers.filter(isVectorMaskLayer);
|
||||
const { layers } = state.regionalPrompts.present;
|
||||
const { width, height } = state.regionalPrompts.present.size;
|
||||
const reduxLayers = layers.filter(isVectorMaskLayer);
|
||||
const container = document.createElement('div');
|
||||
const stage = new Konva.Stage({ container, width: state.generation.width, height: state.generation.height });
|
||||
const stage = new Konva.Stage({ container, width, height });
|
||||
renderers.renderLayers(stage, reduxLayers, 1, 'brush');
|
||||
|
||||
const konvaLayers = stage.find<Konva.Layer>(`.${VECTOR_MASK_LAYER_NAME}`);
|
||||
|
@ -14,6 +14,7 @@ import { ParamSeedNumberInput } from 'features/parameters/components/Seed/ParamS
|
||||
import { ParamSeedRandomize } from 'features/parameters/components/Seed/ParamSeedRandomize';
|
||||
import { ParamSeedShuffle } from 'features/parameters/components/Seed/ParamSeedShuffle';
|
||||
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
||||
import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
import { useExpanderToggle } from 'features/settingsAccordions/hooks/useExpanderToggle';
|
||||
import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/useStandaloneAccordionToggle';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
@ -24,8 +25,8 @@ import { ImageSizeCanvas } from './ImageSizeCanvas';
|
||||
import { ImageSizeLinear } from './ImageSizeLinear';
|
||||
|
||||
const selector = createMemoizedSelector(
|
||||
[selectGenerationSlice, selectCanvasSlice, selectHrfSlice, activeTabNameSelector],
|
||||
(generation, canvas, hrf, activeTabName) => {
|
||||
[selectGenerationSlice, selectCanvasSlice, selectHrfSlice, selectRegionalPromptsSlice, activeTabNameSelector],
|
||||
(generation, canvas, hrf, regionalPrompts, activeTabName) => {
|
||||
const { shouldRandomizeSeed, model } = generation;
|
||||
const { hrfEnabled } = hrf;
|
||||
const badges: string[] = [];
|
||||
@ -42,7 +43,7 @@ const selector = createMemoizedSelector(
|
||||
badges.push('locked');
|
||||
}
|
||||
} else {
|
||||
const { aspectRatio, width, height } = generation;
|
||||
const { aspectRatio, width, height } = regionalPrompts.present.size;
|
||||
badges.push(`${width}×${height}`);
|
||||
badges.push(aspectRatio.id);
|
||||
if (aspectRatio.isLocked) {
|
||||
|
@ -5,27 +5,27 @@ import { AspectRatioCanvasPreview } from 'features/parameters/components/ImageSi
|
||||
import { AspectRatioIconPreview } from 'features/parameters/components/ImageSize/AspectRatioIconPreview';
|
||||
import { ImageSize } from 'features/parameters/components/ImageSize/ImageSize';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import { aspectRatioChanged, heightChanged, widthChanged } from 'features/parameters/store/generationSlice';
|
||||
import { aspectRatioChanged, heightChanged, widthChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
export const ImageSizeLinear = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const tab = useAppSelector(activeTabNameSelector);
|
||||
const width = useAppSelector((s) => s.generation.width);
|
||||
const height = useAppSelector((s) => s.generation.height);
|
||||
const aspectRatioState = useAppSelector((s) => s.generation.aspectRatio);
|
||||
const width = useAppSelector((s) => s.regionalPrompts.present.size.width);
|
||||
const height = useAppSelector((s) => s.regionalPrompts.present.size.height);
|
||||
const aspectRatioState = useAppSelector((s) => s.regionalPrompts.present.size.aspectRatio);
|
||||
|
||||
const onChangeWidth = useCallback(
|
||||
(width: number) => {
|
||||
dispatch(widthChanged(width));
|
||||
dispatch(widthChanged({ width }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onChangeHeight = useCallback(
|
||||
(height: number) => {
|
||||
dispatch(heightChanged(height));
|
||||
dispatch(heightChanged({ height }));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user