refactor(ui): remove modular imagesize components

This is no longer necessary with canvas v2 and added a ton of extraneous redux actions when changing the image size. Also renamed to document size
This commit is contained in:
psychedelicious 2024-07-08 18:53:45 +10:00
parent 249bbfc883
commit 1575bee401
25 changed files with 241 additions and 371 deletions

View File

@ -4,16 +4,16 @@ import type { AppDispatch, RootState } from 'app/store/store';
import type { JSONObject } from 'common/types'; import type { JSONObject } from 'common/types';
import { import {
caModelChanged, caModelChanged,
heightChanged, documentHeightChanged,
documentWidthChanged,
ipaModelChanged, ipaModelChanged,
loraDeleted, loraDeleted,
modelChanged, modelChanged,
refinerModelChanged, refinerModelChanged,
rgIPAdapterModelChanged, rgIPAdapterModelChanged,
vaeSelected, vaeSelected,
widthChanged,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice'; import { postProcessingModelChanged, upscaleModelChanged } from 'features/parameters/store/upscaleSlice';
import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas'; import { zParameterModel, zParameterVAEModel } from 'features/parameters/types/parameterSchemas';
import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension'; import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension';
@ -91,8 +91,8 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
optimalDimension * optimalDimension optimalDimension * optimalDimension
); );
dispatch(widthChanged({ width })); dispatch(documentWidthChanged({ width }));
dispatch(heightChanged({ height })); dispatch(documentHeightChanged({ height }));
return; return;
} }
} }

View File

@ -1,13 +1,13 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { import {
heightChanged, documentHeightChanged,
documentWidthChanged,
setCfgRescaleMultiplier, setCfgRescaleMultiplier,
setCfgScale, setCfgScale,
setScheduler, setScheduler,
setSteps, setSteps,
vaePrecisionChanged, vaePrecisionChanged,
vaeSelected, vaeSelected,
widthChanged,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import { setDefaultSettings } from 'features/parameters/store/actions'; import { setDefaultSettings } from 'features/parameters/store/actions';
import { import {
@ -99,13 +99,13 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
const setSizeOptions = { updateAspectRatio: true, clamp: true }; const setSizeOptions = { updateAspectRatio: true, clamp: true };
if (width) { if (width) {
if (isParameterWidth(width)) { if (isParameterWidth(width)) {
dispatch(widthChanged({ width, ...setSizeOptions })); dispatch(documentWidthChanged({ width, ...setSizeOptions }));
} }
} }
if (height) { if (height) {
if (isParameterHeight(height)) { if (isParameterHeight(height)) {
dispatch(heightChanged({ height, ...setSizeOptions })); dispatch(documentHeightChanged({ height, ...setSizeOptions }));
} }
} }

View File

@ -3,11 +3,11 @@ import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { documentHeightChanged, documentWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import type { ControlAdapterEntity } from 'features/controlLayers/store/types'; import type { ControlAdapterEntity } from 'features/controlLayers/store/types';
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi'; import { PiArrowCounterClockwiseBold, PiFloppyDiskBold, PiRulerBold } from 'react-icons/pi';
@ -89,15 +89,15 @@ export const CAImagePreview = memo(
if (shift) { if (shift) {
const { width, height } = controlImage; const { width, height } = controlImage;
dispatch(widthChanged({ width, ...options })); dispatch(documentWidthChanged({ width, ...options }));
dispatch(heightChanged({ height, ...options })); dispatch(documentHeightChanged({ height, ...options }));
} else { } else {
const { width, height } = calculateNewSize( const { width, height } = calculateNewSize(
controlImage.width / controlImage.height, controlImage.width / controlImage.height,
optimalDimension * optimalDimension optimalDimension * optimalDimension
); );
dispatch(widthChanged({ width, ...options })); dispatch(documentWidthChanged({ width, ...options }));
dispatch(heightChanged({ height, ...options })); dispatch(documentHeightChanged({ height, ...options }));
} }
}, [controlImage, dispatch, optimalDimension, shift]); }, [controlImage, dispatch, optimalDimension, shift]);

View File

@ -3,11 +3,11 @@ import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { documentHeightChanged, documentWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import type { ImageWithDims } from 'features/controlLayers/store/types'; import type { ImageWithDims } from 'features/controlLayers/store/types';
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
import { memo, useCallback, useEffect, useMemo } from 'react'; import { memo, useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiArrowCounterClockwiseBold, PiRulerBold } from 'react-icons/pi'; import { PiArrowCounterClockwiseBold, PiRulerBold } from 'react-icons/pi';
@ -42,15 +42,15 @@ export const IPAImagePreview = memo(({ image, onChangeImage, ipAdapterId, droppa
const options = { updateAspectRatio: true, clamp: true }; const options = { updateAspectRatio: true, clamp: true };
if (shift) { if (shift) {
const { width, height } = controlImage; const { width, height } = controlImage;
dispatch(widthChanged({ width, ...options })); dispatch(documentWidthChanged({ width, ...options }));
dispatch(heightChanged({ height, ...options })); dispatch(documentHeightChanged({ height, ...options }));
} else { } else {
const { width, height } = calculateNewSize( const { width, height } = calculateNewSize(
controlImage.width / controlImage.height, controlImage.width / controlImage.height,
optimalDimension * optimalDimension optimalDimension * optimalDimension
); );
dispatch(widthChanged({ width, ...options })); dispatch(documentWidthChanged({ width, ...options }));
dispatch(heightChanged({ height, ...options })); dispatch(documentHeightChanged({ height, ...options }));
} }
}, [controlImage, dispatch, optimalDimension, shift]); }, [controlImage, dispatch, optimalDimension, shift]);

View File

@ -2,10 +2,10 @@ import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone'; import { deepClone } from 'common/util/deepClone';
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
import { bboxReducers } from 'features/controlLayers/store/bboxReducers'; import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
import { compositingReducers } from 'features/controlLayers/store/compositingReducers'; import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers'; import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers';
import { documentReducers } from 'features/controlLayers/store/documentReducers';
import { inpaintMaskReducers } from 'features/controlLayers/store/inpaintMaskReducers'; import { inpaintMaskReducers } from 'features/controlLayers/store/inpaintMaskReducers';
import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers'; import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers';
import { layersReducers } from 'features/controlLayers/store/layersReducers'; import { layersReducers } from 'features/controlLayers/store/layersReducers';
@ -15,8 +15,7 @@ import { regionsReducers } from 'features/controlLayers/store/regionsReducers';
import { sessionReducers } from 'features/controlLayers/store/sessionReducers'; import { sessionReducers } from 'features/controlLayers/store/sessionReducers';
import { settingsReducers } from 'features/controlLayers/store/settingsReducers'; import { settingsReducers } from 'features/controlLayers/store/settingsReducers';
import { toolReducers } from 'features/controlLayers/store/toolReducers'; import { toolReducers } from 'features/controlLayers/store/toolReducers';
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants'; import { initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
import { atom } from 'nanostores'; import { atom } from 'nanostores';
import type { InvocationDenoiseProgressEvent } from 'services/events/types'; import type { InvocationDenoiseProgressEvent } from 'services/events/types';
@ -145,27 +144,7 @@ export const canvasV2Slice = createSlice({
...bboxReducers, ...bboxReducers,
...inpaintMaskReducers, ...inpaintMaskReducers,
...sessionReducers, ...sessionReducers,
widthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }>) => { ...documentReducers,
const { width, updateAspectRatio, clamp } = action.payload;
state.document.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width;
if (updateAspectRatio) {
state.document.aspectRatio.value = state.document.width / state.document.height;
state.document.aspectRatio.id = 'Free';
state.document.aspectRatio.isLocked = false;
}
},
heightChanged: (state, action: PayloadAction<{ height: number; updateAspectRatio?: boolean; clamp?: boolean }>) => {
const { height, updateAspectRatio, clamp } = action.payload;
state.document.height = clamp ? Math.max(roundDownToMultiple(height, 8), 64) : height;
if (updateAspectRatio) {
state.document.aspectRatio.value = state.document.width / state.document.height;
state.document.aspectRatio.id = 'Free';
state.document.aspectRatio.isLocked = false;
}
},
aspectRatioChanged: (state, action: PayloadAction<AspectRatioState>) => {
state.document.aspectRatio = action.payload;
},
entitySelected: (state, action: PayloadAction<CanvasEntityIdentifier>) => { entitySelected: (state, action: PayloadAction<CanvasEntityIdentifier>) => {
state.selectedEntityIdentifier = action.payload; state.selectedEntityIdentifier = action.payload;
}, },
@ -192,9 +171,6 @@ export const canvasV2Slice = createSlice({
}); });
export const { export const {
widthChanged,
heightChanged,
aspectRatioChanged,
bboxChanged, bboxChanged,
brushWidthChanged, brushWidthChanged,
eraserWidthChanged, eraserWidthChanged,
@ -209,6 +185,13 @@ export const {
bboxScaleMethodChanged, bboxScaleMethodChanged,
clipToBboxChanged, clipToBboxChanged,
canvasReset, canvasReset,
// document
documentWidthChanged,
documentHeightChanged,
documentAspectRatioLockToggled,
documentAspectRatioIdChanged,
documentDimensionsSwapped,
documentSizeOptimized,
// layers // layers
layerAdded, layerAdded,
layerRecalled, layerRecalled,

View File

@ -0,0 +1,104 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import { deepClone } from 'common/util/deepClone';
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
import type { CanvasV2State } from 'features/controlLayers/store/types';
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
import { ASPECT_RATIO_MAP, initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants';
import type { AspectRatioID } from 'features/parameters/components/DocumentSize/types';
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
export const documentReducers = {
documentWidthChanged: (
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;
if (state.document.aspectRatio.isLocked) {
state.document.height = roundToMultiple(state.document.width / state.document.aspectRatio.value, 8);
}
if (updateAspectRatio || !state.document.aspectRatio.isLocked) {
state.document.aspectRatio.value = state.document.width / state.document.height;
state.document.aspectRatio.id = 'Free';
state.document.aspectRatio.isLocked = false;
}
if (!state.session.isActive) {
state.bbox.width = state.document.width;
state.bbox.height = state.document.height;
}
},
documentHeightChanged: (
state,
action: PayloadAction<{ height: number; updateAspectRatio?: boolean; clamp?: boolean }>
) => {
const { height, updateAspectRatio, clamp } = action.payload;
state.document.height = clamp ? Math.max(roundDownToMultiple(height, 8), 64) : height;
if (state.document.aspectRatio.isLocked) {
state.document.width = roundToMultiple(state.document.height * state.document.aspectRatio.value, 8);
}
if (updateAspectRatio || !state.document.aspectRatio.isLocked) {
state.document.aspectRatio.value = state.document.width / state.document.height;
state.document.aspectRatio.id = 'Free';
state.document.aspectRatio.isLocked = false;
}
if (!state.session.isActive) {
state.bbox.width = state.document.width;
state.bbox.height = state.document.height;
}
},
documentAspectRatioLockToggled: (state) => {
state.document.aspectRatio.isLocked = !state.document.aspectRatio.isLocked;
},
documentAspectRatioIdChanged: (state, action: PayloadAction<{ id: AspectRatioID }>) => {
const { id } = action.payload;
state.document.aspectRatio.id = id;
if (id === 'Free') {
state.document.aspectRatio.isLocked = false;
} else {
state.document.aspectRatio.isLocked = true;
state.document.aspectRatio.value = ASPECT_RATIO_MAP[id].ratio;
const { width, height } = calculateNewSize(
state.document.aspectRatio.value,
state.document.width * state.document.height
);
state.document.width = width;
state.document.height = height;
}
},
documentDimensionsSwapped: (state) => {
state.document.aspectRatio.value = 1 / state.document.aspectRatio.value;
if (state.document.aspectRatio.id === 'Free') {
const newWidth = state.document.height;
const newHeight = state.document.width;
state.document.width = newWidth;
state.document.height = newHeight;
} else {
const { width, height } = calculateNewSize(
state.document.aspectRatio.value,
state.document.width * state.document.height
);
state.document.width = width;
state.document.height = height;
state.document.aspectRatio.id = ASPECT_RATIO_MAP[state.document.aspectRatio.id].inverseID;
}
},
documentSizeOptimized: (state) => {
const optimalDimension = getOptimalDimension(state.params.model);
if (state.document.aspectRatio.isLocked) {
const { width, height } = calculateNewSize(state.document.aspectRatio.value, optimalDimension ** 2);
state.document.width = width;
state.document.height = height;
} else {
state.document.aspectRatio = deepClone(initialAspectRatioState);
state.document.width = optimalDimension;
state.document.height = optimalDimension;
}
},
} satisfies SliceCaseReducers<CanvasV2State>;

View File

@ -1,7 +1,7 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import type { CanvasV2State } from 'features/controlLayers/store/types'; import type { CanvasV2State } from 'features/controlLayers/store/types';
import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions'; import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants'; import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
import type { import type {
ParameterCFGRescaleMultiplier, ParameterCFGRescaleMultiplier,

View File

@ -4,7 +4,7 @@ import { CanvasLayer } from 'features/controlLayers/konva/CanvasLayer';
import { CanvasRegion } from 'features/controlLayers/konva/CanvasRegion'; import { CanvasRegion } from 'features/controlLayers/konva/CanvasRegion';
import { getImageObjectId } from 'features/controlLayers/konva/naming'; import { getImageObjectId } from 'features/controlLayers/konva/naming';
import { zModelIdentifierField } from 'features/nodes/types/common'; import { zModelIdentifierField } from 'features/nodes/types/common';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; import type { AspectRatioState } from 'features/parameters/components/DocumentSize/types';
import type { import type {
ParameterCanvasCoherenceMode, ParameterCanvasCoherenceMode,
ParameterCFGRescaleMultiplier, ParameterCFGRescaleMultiplier,

View File

@ -12,7 +12,8 @@ import {
} from 'features/controlLayers/konva/naming'; } from 'features/controlLayers/konva/naming';
import { import {
caRecalled, caRecalled,
heightChanged, documentHeightChanged,
documentWidthChanged,
ipaRecalled, ipaRecalled,
layerAllDeleted, layerAllDeleted,
layerRecalled, layerRecalled,
@ -37,7 +38,6 @@ import {
setSeed, setSeed,
setSteps, setSteps,
vaeSelected, vaeSelected,
widthChanged,
} from 'features/controlLayers/store/canvasV2Slice'; } from 'features/controlLayers/store/canvasV2Slice';
import type { import type {
ControlAdapterEntity, ControlAdapterEntity,
@ -115,11 +115,11 @@ const recallScheduler: MetadataRecallFunc<ParameterScheduler> = (scheduler) => {
const setSizeOptions = { updateAspectRatio: true, clamp: true }; const setSizeOptions = { updateAspectRatio: true, clamp: true };
const recallWidth: MetadataRecallFunc<ParameterWidth> = (width) => { const recallWidth: MetadataRecallFunc<ParameterWidth> = (width) => {
getStore().dispatch(widthChanged({ width, ...setSizeOptions })); getStore().dispatch(documentWidthChanged({ width, ...setSizeOptions }));
}; };
const recallHeight: MetadataRecallFunc<ParameterHeight> = (height) => { const recallHeight: MetadataRecallFunc<ParameterHeight> = (height) => {
getStore().dispatch(heightChanged({ height, ...setSizeOptions })); getStore().dispatch(documentHeightChanged({ height, ...setSizeOptions }));
}; };
const recallSteps: MetadataRecallFunc<ParameterSteps> = (steps) => { const recallSteps: MetadataRecallFunc<ParameterSteps> = (steps) => {

View File

@ -1,15 +1,16 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { documentHeightChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const ParamHeight = memo(() => { export const ParamHeight = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const ctx = useImageSizeContext(); const dispatch = useAppDispatch();
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const height = useAppSelector((s) => s.canvasV2.document.height);
const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin); const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax); const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.height.numberInputMin); const numberInputMin = useAppSelector((s) => s.config.sd.height.numberInputMin);
@ -19,9 +20,9 @@ export const ParamHeight = memo(() => {
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
ctx.heightChanged(v); dispatch(documentHeightChanged({ height: v }));
}, },
[ctx] [dispatch]
); );
const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]); const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]);
@ -32,7 +33,7 @@ export const ParamHeight = memo(() => {
<FormLabel>{t('parameters.height')}</FormLabel> <FormLabel>{t('parameters.height')}</FormLabel>
</InformationalPopover> </InformationalPopover>
<CompositeSlider <CompositeSlider
value={ctx.height} value={height}
defaultValue={optimalDimension} defaultValue={optimalDimension}
onChange={onChange} onChange={onChange}
min={sliderMin} min={sliderMin}
@ -42,7 +43,7 @@ export const ParamHeight = memo(() => {
marks={marks} marks={marks}
/> />
<CompositeNumberInput <CompositeNumberInput
value={ctx.height} value={height}
defaultValue={optimalDimension} defaultValue={optimalDimension}
onChange={onChange} onChange={onChange}
min={numberInputMin} min={numberInputMin}

View File

@ -1,14 +1,15 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { documentWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const ParamWidth = memo(() => { export const ParamWidth = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const ctx = useImageSizeContext(); const dispatch = useAppDispatch();
const width = useAppSelector((s) => s.canvasV2.document.width);
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin); const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax); const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax);
@ -19,9 +20,9 @@ export const ParamWidth = memo(() => {
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
ctx.widthChanged(v); dispatch(documentWidthChanged({ width: v }));
}, },
[ctx] [dispatch]
); );
const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]); const marks = useMemo(() => [sliderMin, optimalDimension, sliderMax], [sliderMin, optimalDimension, sliderMax]);
@ -32,7 +33,7 @@ export const ParamWidth = memo(() => {
<FormLabel>{t('parameters.width')}</FormLabel> <FormLabel>{t('parameters.width')}</FormLabel>
</InformationalPopover> </InformationalPopover>
<CompositeSlider <CompositeSlider
value={ctx.width} value={width}
onChange={onChange} onChange={onChange}
defaultValue={optimalDimension} defaultValue={optimalDimension}
min={sliderMin} min={sliderMin}
@ -42,7 +43,7 @@ export const ParamWidth = memo(() => {
marks={marks} marks={marks}
/> />
<CompositeNumberInput <CompositeNumberInput
value={ctx.width} value={width}
onChange={onChange} onChange={onChange}
defaultValue={optimalDimension} defaultValue={optimalDimension}
min={numberInputMin} min={numberInputMin}

View File

@ -1,6 +1,6 @@
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice'; import { $isPreviewVisible } from 'features/controlLayers/store/canvasV2Slice';
import { AspectRatioIconPreview } from 'features/parameters/components/ImageSize/AspectRatioIconPreview'; import { AspectRatioIconPreview } from 'features/parameters/components/DocumentSize/AspectRatioIconPreview';
import { memo } from 'react'; import { memo } from 'react';
export const AspectRatioCanvasPreview = memo(() => { export const AspectRatioCanvasPreview = memo(() => {

View File

@ -1,6 +1,6 @@
import { useSize } from '@chakra-ui/react-use-size'; import { useSize } from '@chakra-ui/react-use-size';
import { Flex, Icon } from '@invoke-ai/ui-library'; import { Flex, Icon } from '@invoke-ai/ui-library';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext'; import { useAppSelector } from 'app/store/storeHooks';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { memo, useMemo, useRef } from 'react'; import { memo, useMemo, useRef } from 'react';
import { PiFrameCorners } from 'react-icons/pi'; import { PiFrameCorners } from 'react-icons/pi';
@ -16,13 +16,13 @@ import {
} from './constants'; } from './constants';
export const AspectRatioIconPreview = memo(() => { export const AspectRatioIconPreview = memo(() => {
const ctx = useImageSizeContext(); const document = useAppSelector((s) => s.canvasV2.document);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const containerSize = useSize(containerRef); const containerSize = useSize(containerRef);
const shouldShowIcon = useMemo( const shouldShowIcon = useMemo(
() => ctx.aspectRatioState.value < ICON_HIGH_CUTOFF && ctx.aspectRatioState.value > ICON_LOW_CUTOFF, () => document.aspectRatio.value < ICON_HIGH_CUTOFF && document.aspectRatio.value > ICON_LOW_CUTOFF,
[ctx.aspectRatioState.value] [document.aspectRatio.value]
); );
const { width, height } = useMemo(() => { const { width, height } = useMemo(() => {
@ -30,19 +30,19 @@ export const AspectRatioIconPreview = memo(() => {
return { width: 0, height: 0 }; return { width: 0, height: 0 };
} }
let width = ctx.width; let width = document.width;
let height = ctx.height; let height = document.height;
if (ctx.width > ctx.height) { if (document.width > document.height) {
width = containerSize.width; width = containerSize.width;
height = width / ctx.aspectRatioState.value; height = width / document.aspectRatio.value;
} else { } else {
height = containerSize.height; height = containerSize.height;
width = height * ctx.aspectRatioState.value; width = height * document.aspectRatio.value;
} }
return { width, height }; return { width, height };
}, [containerSize, ctx.width, ctx.height, ctx.aspectRatioState.value]); }, [containerSize, document.width, document.height, document.aspectRatio.value]);
return ( return (
<Flex w="full" h="full" alignItems="center" justifyContent="center" ref={containerRef}> <Flex w="full" h="full" alignItems="center" justifyContent="center" ref={containerRef}>

View File

@ -1,31 +1,30 @@
import type { ComboboxOption, SystemStyleObject } from '@invoke-ai/ui-library'; import type { ComboboxOption, SystemStyleObject } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import type { SingleValue } from 'chakra-react-select'; import type { SingleValue } from 'chakra-react-select';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { ASPECT_RATIO_OPTIONS } from 'features/parameters/components/ImageSize/constants'; import { documentAspectRatioIdChanged } from 'features/controlLayers/store/canvasV2Slice';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext'; import { ASPECT_RATIO_OPTIONS } from 'features/parameters/components/DocumentSize/constants';
import { isAspectRatioID } from 'features/parameters/components/ImageSize/types'; import { isAspectRatioID } from 'features/parameters/components/DocumentSize/types';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
export const AspectRatioSelect = memo(() => { export const AspectRatioSelect = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const ctx = useImageSizeContext(); const dispatch = useAppDispatch();
const id = useAppSelector((s) => s.canvasV2.document.aspectRatio.id);
const onChange = useCallback( const onChange = useCallback(
(v: SingleValue<ComboboxOption>) => { (v: SingleValue<ComboboxOption>) => {
if (!v || !isAspectRatioID(v.value)) { if (!v || !isAspectRatioID(v.value)) {
return; return;
} }
ctx.aspectRatioSelected(v.value); dispatch(documentAspectRatioIdChanged({ id: v.value }));
}, },
[ctx] [dispatch]
); );
const value = useMemo( const value = useMemo(() => ASPECT_RATIO_OPTIONS.filter((o) => o.value === id)[0], [id]);
() => ASPECT_RATIO_OPTIONS.filter((o) => o.value === ctx.aspectRatioState.id)[0],
[ctx.aspectRatioState.id]
);
return ( return (
<FormControl> <FormControl>

View File

@ -0,0 +1,38 @@
import type { FormLabelProps } from '@invoke-ai/ui-library';
import { Flex, FormControlGroup } from '@invoke-ai/ui-library';
import { ParamHeight } from 'features/parameters/components/Core/ParamHeight';
import { ParamWidth } from 'features/parameters/components/Core/ParamWidth';
import { AspectRatioIconPreview } from 'features/parameters/components/DocumentSize/AspectRatioIconPreview';
import { AspectRatioSelect } from 'features/parameters/components/DocumentSize/AspectRatioSelect';
import { LockAspectRatioButton } from 'features/parameters/components/DocumentSize/LockAspectRatioButton';
import { SetOptimalSizeButton } from 'features/parameters/components/DocumentSize/SetOptimalSizeButton';
import { SwapDimensionsButton } from 'features/parameters/components/DocumentSize/SwapDimensionsButton';
import { memo } from 'react';
export const DocumentSize = memo(() => {
return (
<Flex gap={4} alignItems="center">
<Flex gap={4} flexDirection="column" width="full">
<FormControlGroup formLabelProps={formLabelProps}>
<Flex gap={4}>
<AspectRatioSelect />
<SwapDimensionsButton />
<LockAspectRatioButton />
<SetOptimalSizeButton />
</Flex>
<ParamWidth />
<ParamHeight />
</FormControlGroup>
</Flex>
<Flex w="108px" h="108px" flexShrink={0} flexGrow={0}>
<AspectRatioIconPreview />
</Flex>
</Flex>
);
});
DocumentSize.displayName = 'DocumentSize';
const formLabelProps: FormLabelProps = {
minW: 14,
};

View File

@ -1,24 +1,26 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { documentAspectRatioLockToggled } from 'features/controlLayers/store/canvasV2Slice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi'; import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi';
export const LockAspectRatioButton = memo(() => { export const LockAspectRatioButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const ctx = useImageSizeContext(); const dispatch = useAppDispatch();
const isLocked = useAppSelector((s) => s.canvasV2.document.aspectRatio.isLocked);
const onClick = useCallback(() => { const onClick = useCallback(() => {
ctx.isLockedToggled(); dispatch(documentAspectRatioLockToggled());
}, [ctx]); }, [dispatch]);
return ( return (
<IconButton <IconButton
tooltip={t('parameters.lockAspectRatio')} tooltip={t('parameters.lockAspectRatio')}
aria-label={t('parameters.lockAspectRatio')} aria-label={t('parameters.lockAspectRatio')}
onClick={onClick} onClick={onClick}
variant={ctx.aspectRatioState.isLocked ? 'outline' : 'ghost'} variant={isLocked ? 'outline' : 'ghost'}
size="sm" size="sm"
icon={ctx.aspectRatioState.isLocked ? <PiLockSimpleFill /> : <PiLockSimpleOpenBold />} icon={isLocked ? <PiLockSimpleFill /> : <PiLockSimpleOpenBold />}
/> />
); );
}); });

View File

@ -1,7 +1,7 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { documentSizeOptimized } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { getIsSizeTooLarge, getIsSizeTooSmall } from 'features/parameters/util/optimalDimension'; import { getIsSizeTooLarge, getIsSizeTooSmall } from 'features/parameters/util/optimalDimension';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -9,19 +9,21 @@ import { RiSparklingFill } from 'react-icons/ri';
export const SetOptimalSizeButton = memo(() => { export const SetOptimalSizeButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const ctx = useImageSizeContext(); const dispatch = useAppDispatch();
const width = useAppSelector((s) => s.canvasV2.document.width);
const height = useAppSelector((s) => s.canvasV2.document.height);
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const isSizeTooSmall = useMemo( const isSizeTooSmall = useMemo(
() => getIsSizeTooSmall(ctx.width, ctx.height, optimalDimension), () => getIsSizeTooSmall(width, height, optimalDimension),
[ctx.height, ctx.width, optimalDimension] [height, width, optimalDimension]
); );
const isSizeTooLarge = useMemo( const isSizeTooLarge = useMemo(
() => getIsSizeTooLarge(ctx.width, ctx.height, optimalDimension), () => getIsSizeTooLarge(width, height, optimalDimension),
[ctx.height, ctx.width, optimalDimension] [height, width, optimalDimension]
); );
const onClick = useCallback(() => { const onClick = useCallback(() => {
ctx.setOptimalSize(); dispatch(documentSizeOptimized());
}, [ctx]); }, [dispatch]);
const tooltip = useMemo(() => { const tooltip = useMemo(() => {
if (isSizeTooSmall) { if (isSizeTooSmall) {
return t('parameters.setToOptimalSizeTooSmall'); return t('parameters.setToOptimalSizeTooSmall');

View File

@ -1,15 +1,16 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext'; import { useAppDispatch } from 'app/store/storeHooks';
import { documentDimensionsSwapped } from 'features/controlLayers/store/canvasV2Slice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiArrowsDownUpBold } from 'react-icons/pi'; import { PiArrowsDownUpBold } from 'react-icons/pi';
export const SwapDimensionsButton = memo(() => { export const SwapDimensionsButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const ctx = useImageSizeContext(); const dispatch = useAppDispatch();
const onClick = useCallback(() => { const onClick = useCallback(() => {
ctx.dimensionsSwapped(); dispatch(documentDimensionsSwapped());
}, [ctx]); }, [dispatch]);
return ( return (
<IconButton <IconButton
tooltip={t('parameters.swapDimensions')} tooltip={t('parameters.swapDimensions')}

View File

@ -1,47 +0,0 @@
import type { FormLabelProps } from '@invoke-ai/ui-library';
import { Flex, FormControlGroup } from '@invoke-ai/ui-library';
import { AspectRatioSelect } from 'features/parameters/components/ImageSize/AspectRatioSelect';
import type { ImageSizeContextInnerValue } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { ImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { LockAspectRatioButton } from 'features/parameters/components/ImageSize/LockAspectRatioButton';
import { SetOptimalSizeButton } from 'features/parameters/components/ImageSize/SetOptimalSizeButton';
import { SwapDimensionsButton } from 'features/parameters/components/ImageSize/SwapDimensionsButton';
import type { ReactNode } from 'react';
import { memo } from 'react';
type ImageSizeProps = ImageSizeContextInnerValue & {
widthComponent: ReactNode;
heightComponent: ReactNode;
previewComponent: ReactNode;
};
export const ImageSize = memo((props: ImageSizeProps) => {
const { widthComponent, heightComponent, previewComponent, ...ctx } = props;
return (
<ImageSizeContext.Provider value={ctx}>
<Flex gap={4} alignItems="center">
<Flex gap={4} flexDirection="column" width="full">
<FormControlGroup formLabelProps={formLabelProps}>
<Flex gap={4}>
<AspectRatioSelect />
<SwapDimensionsButton />
<LockAspectRatioButton />
<SetOptimalSizeButton />
</Flex>
{widthComponent}
{heightComponent}
</FormControlGroup>
</Flex>
<Flex w="108px" h="108px" flexShrink={0} flexGrow={0}>
{previewComponent}
</Flex>
</Flex>
</ImageSizeContext.Provider>
);
});
ImageSize.displayName = 'ImageSize';
const formLabelProps: FormLabelProps = {
minW: 14,
};

View File

@ -1,156 +0,0 @@
import { useAppSelector } from 'app/store/storeHooks';
import { roundToMultiple } from 'common/util/roundDownToMultiple';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
import { ASPECT_RATIO_MAP, initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
import type { AspectRatioID, AspectRatioState } from 'features/parameters/components/ImageSize/types';
import { createContext, useCallback, useContext, useMemo } from 'react';
export type ImageSizeContextInnerValue = {
width: number;
height: number;
aspectRatioState: AspectRatioState;
onChangeWidth: (width: number) => void;
onChangeHeight: (height: number) => void;
onChangeAspectRatioState: (aspectRatioState: AspectRatioState) => void;
};
export type ImageSizeContext = {
width: number;
height: number;
aspectRatioState: AspectRatioState;
aspectRatioSelected: (aspectRatioID: AspectRatioID) => void;
dimensionsSwapped: () => void;
widthChanged: (width: number) => void;
heightChanged: (height: number) => void;
isLockedToggled: () => void;
setOptimalSize: () => void;
};
export const ImageSizeContext = createContext<ImageSizeContextInnerValue | null>(null);
export const useImageSizeContext = (): ImageSizeContext => {
const _ctx = useContext(ImageSizeContext);
const optimalDimension = useAppSelector(selectOptimalDimension);
if (!_ctx) {
throw new Error('useImageSizeContext must be used within a ImageSizeContext.Provider');
}
const aspectRatioSelected = useCallback(
(aspectRatioID: AspectRatioID) => {
const state: AspectRatioState = {
..._ctx.aspectRatioState,
id: aspectRatioID,
};
if (state.id === 'Free') {
// If the new aspect ratio is free, we only unlock
state.isLocked = false;
} else {
// The new aspect ratio not free, so we need to coerce the size & lock
state.isLocked = true;
state.value = ASPECT_RATIO_MAP[state.id].ratio;
const { width, height } = calculateNewSize(state.value, _ctx.width * _ctx.height);
_ctx.onChangeWidth(width);
_ctx.onChangeHeight(height);
}
_ctx.onChangeAspectRatioState(state);
},
[_ctx]
);
const dimensionsSwapped = useCallback(() => {
const state = {
..._ctx.aspectRatioState,
};
// We always invert the aspect ratio
state.value = 1 / state.value;
if (state.id === 'Free') {
// If the aspect ratio is free, we just swap the dimensions
const newWidth = _ctx.height;
const newHeight = _ctx.width;
_ctx.onChangeWidth(newWidth);
_ctx.onChangeHeight(newHeight);
} else {
// Else we need to calculate the new size
const { width, height } = calculateNewSize(state.value, _ctx.width * _ctx.height);
_ctx.onChangeWidth(width);
_ctx.onChangeHeight(height);
// Update the aspect ratio ID to match the new aspect ratio
state.id = ASPECT_RATIO_MAP[state.id].inverseID;
}
_ctx.onChangeAspectRatioState(state);
}, [_ctx]);
const widthChanged = useCallback(
(width: number) => {
let height = _ctx.height;
const state = { ..._ctx.aspectRatioState };
if (state.isLocked) {
// When locked, we calculate the new height based on the aspect ratio
height = roundToMultiple(width / state.value, 8);
} else {
// Else we unlock, set the aspect ratio to free, and update the aspect ratio itself
state.isLocked = false;
state.id = 'Free';
state.value = width / height;
}
_ctx.onChangeWidth(width);
_ctx.onChangeHeight(height);
_ctx.onChangeAspectRatioState(state);
},
[_ctx]
);
const heightChanged = useCallback(
(height: number) => {
let width = _ctx.width;
const state = { ..._ctx.aspectRatioState };
if (state.isLocked) {
// When locked, we calculate the new width based on the aspect ratio
width = roundToMultiple(height * state.value, 8);
} else {
// Else we unlock, set the aspect ratio to free, and update the aspect ratio itself
state.isLocked = false;
state.id = 'Free';
state.value = width / height;
}
_ctx.onChangeWidth(width);
_ctx.onChangeHeight(height);
_ctx.onChangeAspectRatioState(state);
},
[_ctx]
);
const isLockedToggled = useCallback(() => {
const state = { ..._ctx.aspectRatioState };
state.isLocked = !state.isLocked;
_ctx.onChangeAspectRatioState(state);
}, [_ctx]);
const setOptimalSize = useCallback(() => {
if (_ctx.aspectRatioState.isLocked) {
const { width, height } = calculateNewSize(_ctx.aspectRatioState.value, optimalDimension * optimalDimension);
_ctx.onChangeWidth(width);
_ctx.onChangeHeight(height);
} else {
_ctx.onChangeAspectRatioState({ ...initialAspectRatioState });
_ctx.onChangeWidth(optimalDimension);
_ctx.onChangeHeight(optimalDimension);
}
}, [_ctx, optimalDimension]);
const ctx = useMemo(
() => ({
..._ctx,
aspectRatioSelected,
dimensionsSwapped,
widthChanged,
heightChanged,
isLockedToggled,
setOptimalSize,
}),
[_ctx, aspectRatioSelected, dimensionsSwapped, heightChanged, isLockedToggled, setOptimalSize, widthChanged]
);
return ctx;
};

View File

@ -9,6 +9,7 @@ import ParamScaleBeforeProcessing from 'features/parameters/components/Canvas/In
import ParamScaledHeight from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaledHeight'; import ParamScaledHeight from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaledHeight';
import ParamScaledWidth from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaledWidth'; import ParamScaledWidth from 'features/parameters/components/Canvas/InfillAndScaling/ParamScaledWidth';
import ParamImageToImageStrength from 'features/parameters/components/Canvas/ParamImageToImageStrength'; import ParamImageToImageStrength from 'features/parameters/components/Canvas/ParamImageToImageStrength';
import { DocumentSize } from 'features/parameters/components/DocumentSize/DocumentSize';
import { ParamSeedNumberInput } from 'features/parameters/components/Seed/ParamSeedNumberInput'; import { ParamSeedNumberInput } from 'features/parameters/components/Seed/ParamSeedNumberInput';
import { ParamSeedRandomize } from 'features/parameters/components/Seed/ParamSeedRandomize'; import { ParamSeedRandomize } from 'features/parameters/components/Seed/ParamSeedRandomize';
import { ParamSeedShuffle } from 'features/parameters/components/Seed/ParamSeedShuffle'; import { ParamSeedShuffle } from 'features/parameters/components/Seed/ParamSeedShuffle';
@ -17,7 +18,6 @@ import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ImageSizeLinear } from './ImageSizeLinear';
const selector = createMemoizedSelector([selectHrfSlice, selectCanvasV2Slice], (hrf, canvasV2) => { const selector = createMemoizedSelector([selectHrfSlice, selectCanvasV2Slice], (hrf, canvasV2) => {
const { shouldRandomizeSeed, model } = canvasV2.params; const { shouldRandomizeSeed, model } = canvasV2.params;
@ -68,7 +68,7 @@ export const ImageSettingsAccordion = memo(() => {
> >
<Flex px={4} pt={4} w="full" h="full" flexDir="column" data-testid="image-settings-accordion"> <Flex px={4} pt={4} w="full" h="full" flexDir="column" data-testid="image-settings-accordion">
<Flex flexDir="column" gap={4}> <Flex flexDir="column" gap={4}>
<ImageSizeLinear /> <DocumentSize />
<ParamImageToImageStrength /> <ParamImageToImageStrength />
</Flex> </Flex>
<Expander label={t('accordions.advanced.options')} isOpen={isOpenExpander} onToggle={onToggleExpander}> <Expander label={t('accordions.advanced.options')} isOpen={isOpenExpander} onToggle={onToggleExpander}>

View File

@ -1,58 +0,0 @@
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { aspectRatioChanged, heightChanged, widthChanged } from 'features/controlLayers/store/canvasV2Slice';
import { ParamHeight } from 'features/parameters/components/Core/ParamHeight';
import { ParamWidth } from 'features/parameters/components/Core/ParamWidth';
import { AspectRatioCanvasPreview } from 'features/parameters/components/ImageSize/AspectRatioCanvasPreview';
import { ImageSize } from 'features/parameters/components/ImageSize/ImageSize';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
import { memo, useCallback } from 'react';
export const ImageSizeLinear = memo(() => {
const dispatch = useAppDispatch();
const width = useAppSelector((s) => s.canvasV2.document.width);
const height = useAppSelector((s) => s.canvasV2.document.height);
const aspectRatioState = useAppSelector((s) => s.canvasV2.document.aspectRatio);
const onChangeWidth = useCallback(
(width: number) => {
if (width === 0) {
return;
}
dispatch(widthChanged({ width }));
},
[dispatch]
);
const onChangeHeight = useCallback(
(height: number) => {
if (height === 0) {
return;
}
dispatch(heightChanged({ height }));
},
[dispatch]
);
const onChangeAspectRatioState = useCallback(
(aspectRatioState: AspectRatioState) => {
dispatch(aspectRatioChanged(aspectRatioState));
},
[dispatch]
);
return (
<ImageSize
width={width}
height={height}
aspectRatioState={aspectRatioState}
heightComponent={<ParamHeight />}
widthComponent={<ParamWidth />}
previewComponent={<AspectRatioCanvasPreview />}
onChangeAspectRatioState={onChangeAspectRatioState}
onChangeWidth={onChangeWidth}
onChangeHeight={onChangeHeight}
/>
);
});
ImageSizeLinear.displayName = 'ImageSizeLinear';