refactor(ui): move size state to regional

This commit is contained in:
psychedelicious 2024-04-25 20:08:21 +10:00 committed by Kent Keirsey
parent b6a45e53f1
commit cc4bef4859
16 changed files with 118 additions and 109 deletions

View File

@ -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;
}
}

View File

@ -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 }));
}
}

View File

@ -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]);

View File

@ -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) => {

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,

View File

@ -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;

View File

@ -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');

View File

@ -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,

View File

@ -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}`);

View File

@ -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) {

View File

@ -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]
);