mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
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:
parent
249bbfc883
commit
1575bee401
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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]);
|
||||||
|
|
||||||
|
@ -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]);
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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>;
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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) => {
|
||||||
|
@ -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}
|
||||||
|
@ -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}
|
||||||
|
@ -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(() => {
|
@ -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}>
|
@ -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>
|
@ -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,
|
||||||
|
};
|
@ -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 />}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
@ -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');
|
@ -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')}
|
@ -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,
|
|
||||||
};
|
|
@ -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;
|
|
||||||
};
|
|
@ -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}>
|
||||||
|
@ -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';
|
|
Loading…
Reference in New Issue
Block a user