From d01d5b6fa983bf193771548c2d8ee00f1483cec6 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 9 Jul 2023 23:18:06 +1200 Subject: [PATCH 01/12] feat: Add Aspect Ratio --- invokeai/frontend/web/public/locales/en.json | 3 +- .../Parameters/Core/ParamAspectRatio.tsx | 49 +++++++++++++++++++ .../Parameters/Core/ParamHeight.tsx | 18 ++++--- .../components/Parameters/Core/ParamSize.tsx | 35 +++++++++++++ .../components/Parameters/Core/ParamWidth.tsx | 18 ++++--- .../parameters/store/generationSlice.ts | 10 +++- .../ImageToImageTabCoreParameters.tsx | 15 +++--- .../TextToImageTabCoreParameters.tsx | 15 +++--- .../UnifiedCanvasCoreParameters.tsx | 6 +-- .../web/src/features/ui/store/uiSlice.ts | 5 ++ .../web/src/features/ui/store/uiTypes.ts | 1 + 11 files changed, 140 insertions(+), 35 deletions(-) create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx create mode 100644 invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSize.tsx diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index b403fde2c6..29a3c0907c 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -528,7 +528,8 @@ "hidePreview": "Hide Preview", "showPreview": "Show Preview", "controlNetControlMode": "Control Mode", - "clipSkip": "Clip Skip" + "clipSkip": "Clip Skip", + "aspectRatio": "Aspect Ratio" }, "settings": { "models": "Models", diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx new file mode 100644 index 0000000000..f14a88af75 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx @@ -0,0 +1,49 @@ +import { Flex } from '@chakra-ui/react'; +import { RootState } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIButton from 'common/components/IAIButton'; +import { setAspectRatio } from 'features/ui/store/uiSlice'; +import { ReactNode } from 'react'; + +const aspectRatios = [ + { name: 'Free', value: null }, + { name: '4:3', value: 4 / 3 }, + { name: '16:9', value: 16 / 9 }, + { name: '3:2', value: 3 / 2 }, +]; + +export const roundToEight = (number: number) => { + return Math.round(number / 8) * 8; +}; + +export default function ParamAspectRatio() { + const aspectRatio = useAppSelector( + (state: RootState) => state.ui.aspectRatio + ); + + const dispatch = useAppDispatch(); + + const renderAspectRatios = () => { + const aspectRatiosToRender: ReactNode[] = []; + aspectRatios.forEach((ratio) => { + aspectRatiosToRender.push( + dispatch(setAspectRatio(ratio.value))} + > + {ratio.name} + + ); + }); + return aspectRatiosToRender; + }; + + return ( + + {renderAspectRatios()} + + ); +} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx index 6939ede424..617fe07fc5 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx @@ -3,18 +3,21 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider, { IAIFullSliderProps } from 'common/components/IAISlider'; import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { setHeight } from 'features/parameters/store/generationSlice'; +import { setHeight, setWidth } from 'features/parameters/store/generationSlice'; import { configSelector } from 'features/system/store/configSelectors'; import { hotkeysSelector } from 'features/ui/store/hotkeysSlice'; +import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { roundToEight } from './ParamAspectRatio'; const selector = createSelector( - [generationSelector, hotkeysSelector, configSelector], - (generation, hotkeys, config) => { + [generationSelector, hotkeysSelector, configSelector, uiSelector], + (generation, hotkeys, config, ui) => { const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.height; const { height } = generation; + const { aspectRatio } = ui; const step = hotkeys.shift ? fineStep : coarseStep; @@ -25,6 +28,7 @@ const selector = createSelector( sliderMax, inputMax, step, + aspectRatio, }; }, defaultSelectorOptions @@ -36,7 +40,7 @@ type ParamHeightProps = Omit< >; const ParamHeight = (props: ParamHeightProps) => { - const { height, initial, min, sliderMax, inputMax, step } = + const { height, initial, min, sliderMax, inputMax, step, aspectRatio } = useAppSelector(selector); const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -44,13 +48,15 @@ const ParamHeight = (props: ParamHeightProps) => { const handleChange = useCallback( (v: number) => { dispatch(setHeight(v)); + if (aspectRatio) dispatch(setWidth(roundToEight(height * aspectRatio))); }, - [dispatch] + [dispatch, height, aspectRatio] ); const handleReset = useCallback(() => { dispatch(setHeight(initial)); - }, [dispatch, initial]); + if (aspectRatio) dispatch(setWidth(roundToEight(height * aspectRatio))); + }, [dispatch, initial, height, aspectRatio]); return ( state.generation.shouldFitToWidthHeight + ); + return ( + + + + {t('parameters.aspectRatio')} + + + + + + + + + ); +} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx index b4121184b5..daf08e2ada 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx @@ -3,18 +3,21 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider, { IAIFullSliderProps } from 'common/components/IAISlider'; import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { setWidth } from 'features/parameters/store/generationSlice'; +import { setHeight, setWidth } from 'features/parameters/store/generationSlice'; import { configSelector } from 'features/system/store/configSelectors'; import { hotkeysSelector } from 'features/ui/store/hotkeysSlice'; +import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { roundToEight } from './ParamAspectRatio'; const selector = createSelector( - [generationSelector, hotkeysSelector, configSelector], - (generation, hotkeys, config) => { + [generationSelector, hotkeysSelector, configSelector, uiSelector], + (generation, hotkeys, config, ui) => { const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.width; const { width } = generation; + const { aspectRatio } = ui; const step = hotkeys.shift ? fineStep : coarseStep; @@ -25,6 +28,7 @@ const selector = createSelector( sliderMax, inputMax, step, + aspectRatio, }; }, defaultSelectorOptions @@ -33,7 +37,7 @@ const selector = createSelector( type ParamWidthProps = Omit; const ParamWidth = (props: ParamWidthProps) => { - const { width, initial, min, sliderMax, inputMax, step } = + const { width, initial, min, sliderMax, inputMax, step, aspectRatio } = useAppSelector(selector); const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -41,13 +45,15 @@ const ParamWidth = (props: ParamWidthProps) => { const handleChange = useCallback( (v: number) => { dispatch(setWidth(v)); + if (aspectRatio) dispatch(setHeight(roundToEight(width / aspectRatio))); }, - [dispatch] + [dispatch, aspectRatio, width] ); const handleReset = useCallback(() => { dispatch(setWidth(initial)); - }, [dispatch, initial]); + if (aspectRatio) dispatch(setHeight(roundToEight(width / aspectRatio))); + }, [dispatch, initial, width, aspectRatio]); return ( { + const ratio = action.payload; + if (ratio) state.height = roundToEight(state.width / ratio); + }); }, }); diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx index b333a0caf2..cda908da50 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/ImageToImage/ImageToImageTabCoreParameters.tsx @@ -4,11 +4,10 @@ import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAICollapse from 'common/components/IAICollapse'; import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale'; -import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight'; import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations'; import ParamModelandVAEandScheduler from 'features/parameters/components/Parameters/Core/ParamModelandVAEandScheduler'; +import ParamSize from 'features/parameters/components/Parameters/Core/ParamSize'; import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps'; -import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth'; import ImageToImageFit from 'features/parameters/components/Parameters/ImageToImage/ImageToImageFit'; import ImageToImageStrength from 'features/parameters/components/Parameters/ImageToImage/ImageToImageStrength'; import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull'; @@ -47,15 +46,14 @@ const ImageToImageTabCoreParameters = () => { > {shouldUseSliders ? ( <> + + + - - - - - + ) : ( <> @@ -68,8 +66,7 @@ const ImageToImageTabCoreParameters = () => { - - + )} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx index b007497db2..de8f64e661 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/TextToImage/TextToImageTabCoreParameters.tsx @@ -5,11 +5,10 @@ import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAICollapse from 'common/components/IAICollapse'; import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale'; -import ParamHeight from 'features/parameters/components/Parameters/Core/ParamHeight'; import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations'; import ParamModelandVAEandScheduler from 'features/parameters/components/Parameters/Core/ParamModelandVAEandScheduler'; +import ParamSize from 'features/parameters/components/Parameters/Core/ParamSize'; import ParamSteps from 'features/parameters/components/Parameters/Core/ParamSteps'; -import ParamWidth from 'features/parameters/components/Parameters/Core/ParamWidth'; import ParamSeedFull from 'features/parameters/components/Parameters/Seed/ParamSeedFull'; import { memo } from 'react'; @@ -43,15 +42,14 @@ const TextToImageTabCoreParameters = () => { > {shouldUseSliders ? ( <> + + + - - - - - + ) : ( <> @@ -64,8 +62,7 @@ const TextToImageTabCoreParameters = () => { - - + )} diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx index ecce61c218..6ea9d4bc8d 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasCoreParameters.tsx @@ -44,13 +44,13 @@ const UnifiedCanvasCoreParameters = () => { > {shouldUseSliders ? ( <> + + + - - - diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 04fee42126..4f38f84fe2 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -21,6 +21,7 @@ export const initialUIState: UIState = { shouldShowProgressInViewer: true, shouldShowEmbeddingPicker: false, shouldShowAdvancedOptions: false, + aspectRatio: null, favoriteSchedulers: [], }; @@ -104,6 +105,9 @@ export const uiSlice = createSlice({ setShouldShowAdvancedOptions: (state, action: PayloadAction) => { state.shouldShowAdvancedOptions = action.payload; }, + setAspectRatio: (state, action: PayloadAction) => { + state.aspectRatio = action.payload; + }, }, extraReducers(builder) { builder.addCase(initialImageChanged, (state) => { @@ -132,6 +136,7 @@ export const { favoriteSchedulersChanged, toggleEmbeddingPicker, setShouldShowAdvancedOptions, + setAspectRatio, } = uiSlice.actions; export default uiSlice.reducer; diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index 2356446030..e574f0ab79 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -29,5 +29,6 @@ export interface UIState { shouldShowProgressInViewer: boolean; shouldShowEmbeddingPicker: boolean; shouldShowAdvancedOptions: boolean; + aspectRatio: number | null; favoriteSchedulers: SchedulerParam[]; } From cafd97e5bceb9812c65eb4f785f59d49bbc304d6 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Sun, 9 Jul 2023 23:24:15 +1200 Subject: [PATCH 02/12] fix: Reset handler not adjusting correctly --- .../parameters/components/Parameters/Core/ParamHeight.tsx | 4 ++-- .../parameters/components/Parameters/Core/ParamWidth.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx index 617fe07fc5..8d0c6fbb6e 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx @@ -55,8 +55,8 @@ const ParamHeight = (props: ParamHeightProps) => { const handleReset = useCallback(() => { dispatch(setHeight(initial)); - if (aspectRatio) dispatch(setWidth(roundToEight(height * aspectRatio))); - }, [dispatch, initial, height, aspectRatio]); + if (aspectRatio) dispatch(setWidth(roundToEight(initial * aspectRatio))); + }, [dispatch, initial, aspectRatio]); return ( { const handleReset = useCallback(() => { dispatch(setWidth(initial)); - if (aspectRatio) dispatch(setHeight(roundToEight(width / aspectRatio))); - }, [dispatch, initial, width, aspectRatio]); + if (aspectRatio) dispatch(setHeight(roundToEight(initial / aspectRatio))); + }, [dispatch, initial, aspectRatio]); return ( Date: Sun, 9 Jul 2023 22:13:26 +1000 Subject: [PATCH 03/12] fix(ui): fix number input on aspect ratio --- .../components/Parameters/Core/ParamHeight.tsx | 14 ++++++++++---- .../components/Parameters/Core/ParamWidth.tsx | 14 ++++++++++---- .../features/parameters/store/generationSlice.ts | 4 +++- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx index 8d0c6fbb6e..63abe0ddf9 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx @@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider, { IAIFullSliderProps } from 'common/components/IAISlider'; +import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setHeight, setWidth } from 'features/parameters/store/generationSlice'; import { configSelector } from 'features/system/store/configSelectors'; @@ -9,7 +10,6 @@ import { hotkeysSelector } from 'features/ui/store/hotkeysSlice'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { roundToEight } from './ParamAspectRatio'; const selector = createSelector( [generationSelector, hotkeysSelector, configSelector, uiSelector], @@ -48,14 +48,20 @@ const ParamHeight = (props: ParamHeightProps) => { const handleChange = useCallback( (v: number) => { dispatch(setHeight(v)); - if (aspectRatio) dispatch(setWidth(roundToEight(height * aspectRatio))); + if (aspectRatio) { + const newWidth = roundToMultiple(v * aspectRatio, 8); + dispatch(setWidth(newWidth)); + } }, - [dispatch, height, aspectRatio] + [dispatch, aspectRatio] ); const handleReset = useCallback(() => { dispatch(setHeight(initial)); - if (aspectRatio) dispatch(setWidth(roundToEight(initial * aspectRatio))); + if (aspectRatio) { + const newWidth = roundToMultiple(initial * aspectRatio, 8); + dispatch(setWidth(newWidth)); + } }, [dispatch, initial, aspectRatio]); return ( diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx index fd6decea11..991db19097 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamWidth.tsx @@ -2,6 +2,7 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider, { IAIFullSliderProps } from 'common/components/IAISlider'; +import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { generationSelector } from 'features/parameters/store/generationSelectors'; import { setHeight, setWidth } from 'features/parameters/store/generationSlice'; import { configSelector } from 'features/system/store/configSelectors'; @@ -9,7 +10,6 @@ import { hotkeysSelector } from 'features/ui/store/hotkeysSlice'; import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { roundToEight } from './ParamAspectRatio'; const selector = createSelector( [generationSelector, hotkeysSelector, configSelector, uiSelector], @@ -45,14 +45,20 @@ const ParamWidth = (props: ParamWidthProps) => { const handleChange = useCallback( (v: number) => { dispatch(setWidth(v)); - if (aspectRatio) dispatch(setHeight(roundToEight(width / aspectRatio))); + if (aspectRatio) { + const newHeight = roundToMultiple(v / aspectRatio, 8); + dispatch(setHeight(newHeight)); + } }, - [dispatch, aspectRatio, width] + [dispatch, aspectRatio] ); const handleReset = useCallback(() => { dispatch(setWidth(initial)); - if (aspectRatio) dispatch(setHeight(roundToEight(initial / aspectRatio))); + if (aspectRatio) { + const newHeight = roundToMultiple(initial / aspectRatio, 8); + dispatch(setHeight(newHeight)); + } }, [dispatch, initial, aspectRatio]); return ( diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 692e0113d6..89e2f29972 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -268,7 +268,9 @@ export const generationSlice = createSlice({ }); builder.addCase(setAspectRatio, (state, action) => { const ratio = action.payload; - if (ratio) state.height = roundToEight(state.width / ratio); + if (ratio) { + state.height = roundToMultiple(state.width / ratio, 8); + } }); }, }); From 0e178c3bb7bca9ed4e827d9f8e7a803a9f350922 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sun, 9 Jul 2023 22:13:38 +1000 Subject: [PATCH 04/12] feat(ui): aspect ratio styling --- .../Parameters/Core/ParamAspectRatio.tsx | 40 +++++++------------ .../components/Parameters/Core/ParamSize.tsx | 31 ++++++++++---- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx index f14a88af75..1b575b5793 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx @@ -1,9 +1,8 @@ -import { Flex } from '@chakra-ui/react'; +import { ButtonGroup, Flex } from '@chakra-ui/react'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIButton from 'common/components/IAIButton'; import { setAspectRatio } from 'features/ui/store/uiSlice'; -import { ReactNode } from 'react'; const aspectRatios = [ { name: 'Free', value: null }, @@ -12,10 +11,6 @@ const aspectRatios = [ { name: '3:2', value: 3 / 2 }, ]; -export const roundToEight = (number: number) => { - return Math.round(number / 8) * 8; -}; - export default function ParamAspectRatio() { const aspectRatio = useAppSelector( (state: RootState) => state.ui.aspectRatio @@ -23,27 +18,20 @@ export default function ParamAspectRatio() { const dispatch = useAppDispatch(); - const renderAspectRatios = () => { - const aspectRatiosToRender: ReactNode[] = []; - aspectRatios.forEach((ratio) => { - aspectRatiosToRender.push( - dispatch(setAspectRatio(ratio.value))} - > - {ratio.name} - - ); - }); - return aspectRatiosToRender; - }; - return ( - - {renderAspectRatios()} + + + {aspectRatios.map((ratio) => ( + dispatch(setAspectRatio(ratio.value))} + > + {ratio.name} + + ))} + ); } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSize.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSize.tsx index 55340ce799..15637bb091 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSize.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSize.tsx @@ -1,4 +1,4 @@ -import { Flex, Text } from '@chakra-ui/react'; +import { Flex, Spacer, Text } from '@chakra-ui/react'; import { RootState } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; import { useTranslation } from 'react-i18next'; @@ -13,17 +13,32 @@ export default function ParamSize() { ); return ( - + {t('parameters.aspectRatio')} + From dfe338fc5099154854ebf1c728abbe355925d924 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sun, 9 Jul 2023 22:47:54 +1000 Subject: [PATCH 05/12] fix(ui): fix missing import --- .../web/src/features/parameters/store/generationSlice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 89e2f29972..5e476e1b3b 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -1,6 +1,7 @@ import type { PayloadAction } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit'; import { DEFAULT_SCHEDULER_NAME } from 'app/constants'; +import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { configChanged } from 'features/system/store/configSlice'; import { setAspectRatio, @@ -9,7 +10,6 @@ import { import { clamp } from 'lodash-es'; import { ImageDTO } from 'services/api/types'; import { clipSkipMap } from '../components/Parameters/Advanced/ParamClipSkip'; -import { roundToEight } from '../components/Parameters/Core/ParamAspectRatio'; import { CfgScaleParam, HeightParam, From 265996d230f4727d1f58c3e75cdc847ddac6497b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 10 Jul 2023 11:21:56 +1000 Subject: [PATCH 06/12] feat(ui): memoize ImageContextMenu selector Without the selector itself being memoized, the gallery was rerendering on every progress image. --- .../gallery/components/ImageContextMenu.tsx | 75 +++++++++---------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu.tsx index 1e5f95ab0d..64b1d349d8 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu.tsx @@ -1,49 +1,32 @@ -import { MenuItem, MenuList } from '@chakra-ui/react'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { memo, useCallback, useContext } from 'react'; -import { - FaExpand, - FaFolder, - FaFolderPlus, - FaShare, - FaTrash, -} from 'react-icons/fa'; -import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu'; -import { - resizeAndScaleCanvas, - setInitialCanvasImage, -} from 'features/canvas/store/canvasSlice'; -import { setActiveTab } from 'features/ui/store/uiSlice'; -import { useTranslation } from 'react-i18next'; import { ExternalLinkIcon } from '@chakra-ui/icons'; -import { IoArrowUndoCircleOutline } from 'react-icons/io5'; +import { MenuItem, MenuList } from '@chakra-ui/react'; import { createSelector } from '@reduxjs/toolkit'; -import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; -import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters'; -import { initialImageSelected } from 'features/parameters/store/actions'; -import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions'; import { useAppToaster } from 'app/components/Toaster'; -import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext'; -import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages'; -import { ImageDTO } from 'services/api/types'; -import { RootState, stateSelector } from 'app/store/store'; +import { stateSelector } from 'app/store/store'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { ContextMenu, ContextMenuProps } from 'chakra-ui-contextmenu'; import { imagesAddedToBatch, selectionAddedToBatch, } from 'features/batch/store/batchSlice'; -import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; +import { + resizeAndScaleCanvas, + setInitialCanvasImage, +} from 'features/canvas/store/canvasSlice'; import { imageToDeleteSelected } from 'features/imageDeletion/store/imageDeletionSlice'; - -const selector = createSelector( - [stateSelector, (state: RootState, imageDTO: ImageDTO) => imageDTO], - ({ gallery, batch }, imageDTO) => { - const selectionCount = gallery.selection.length; - const isInBatch = batch.imageNames.includes(imageDTO.image_name); - - return { selectionCount, isInBatch }; - }, - defaultSelectorOptions -); +import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters'; +import { initialImageSelected } from 'features/parameters/store/actions'; +import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; +import { setActiveTab } from 'features/ui/store/uiSlice'; +import { memo, useCallback, useContext, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { FaExpand, FaFolder, FaShare, FaTrash } from 'react-icons/fa'; +import { IoArrowUndoCircleOutline } from 'react-icons/io5'; +import { useRemoveImageFromBoardMutation } from 'services/api/endpoints/boardImages'; +import { ImageDTO } from 'services/api/types'; +import { AddImageToBoardContext } from '../../../app/contexts/AddImageToBoardContext'; +import { sentImageToCanvas, sentImageToImg2Img } from '../store/actions'; type Props = { image: ImageDTO; @@ -51,9 +34,21 @@ type Props = { }; const ImageContextMenu = ({ image, children }: Props) => { - const { selectionCount, isInBatch } = useAppSelector((state) => - selector(state, image) + const selector = useMemo( + () => + createSelector( + [stateSelector], + ({ gallery, batch }) => { + const selectionCount = gallery.selection.length; + const isInBatch = batch.imageNames.includes(image.image_name); + + return { selectionCount, isInBatch }; + }, + defaultSelectorOptions + ), + [image.image_name] ); + const { selectionCount, isInBatch } = useAppSelector(selector); const dispatch = useAppDispatch(); const { t } = useTranslation(); From a7b8109ac28d13bfc62d0246b2c07f139dc95358 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 10 Jul 2023 11:22:34 +1000 Subject: [PATCH 07/12] feat(ui): memoize NextPrevImageButtons component This was rerendering on every progress image, now it doesn't --- .../src/features/gallery/components/NextPrevImageButtons.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx b/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx index 4177db9a1b..3fcdd54cc9 100644 --- a/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/NextPrevImageButtons.tsx @@ -8,7 +8,7 @@ import { selectImagesById, } from 'features/gallery/store/gallerySlice'; import { clamp, isEqual } from 'lodash-es'; -import { useCallback, useState } from 'react'; +import { memo, useCallback, useState } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { useTranslation } from 'react-i18next'; import { FaAngleDoubleRight, FaAngleLeft, FaAngleRight } from 'react-icons/fa'; @@ -227,4 +227,4 @@ const NextPrevImageButtons = () => { ); }; -export default NextPrevImageButtons; +export default memo(NextPrevImageButtons); From 1c45d18e6d1065dc875e98a6e510ddc51d098a4c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 10 Jul 2023 11:23:13 +1000 Subject: [PATCH 08/12] fix(ui): correctly set disabled on invoke button during generation It wasn't disabled when it should have been, looked clickable during generation. --- .../parameters/components/ProcessButtons/InvokeButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx index e2338e2575..2e399647d8 100644 --- a/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/ProcessButtons/InvokeButton.tsx @@ -78,7 +78,7 @@ export default function InvokeButton(props: InvokeButton) { aria-label={t('parameters.invoke')} type="submit" icon={} - isDisabled={!isReady} + isDisabled={!isReady || isProcessing} onClick={handleInvoke} tooltip={t('parameters.invoke')} tooltipProps={{ placement: 'top' }} @@ -95,7 +95,7 @@ export default function InvokeButton(props: InvokeButton) { Date: Mon, 10 Jul 2023 11:30:35 +1000 Subject: [PATCH 09/12] fix(nodes): remove `board_id` column from `images` table This is extraneous; the `board_images` table holds image-board relationships. --- invokeai/app/services/image_record_storage.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/invokeai/app/services/image_record_storage.py b/invokeai/app/services/image_record_storage.py index 5e5996ae76..014006eb7a 100644 --- a/invokeai/app/services/image_record_storage.py +++ b/invokeai/app/services/image_record_storage.py @@ -1,22 +1,16 @@ +import sqlite3 +import threading from abc import ABC, abstractmethod from datetime import datetime from typing import Generic, Optional, TypeVar, cast -import sqlite3 -import threading from pydantic import BaseModel, Field from pydantic.generics import GenericModel +from invokeai.app.models.image import ImageCategory, ResourceOrigin from invokeai.app.models.metadata import ImageMetadata -from invokeai.app.models.image import ( - ImageCategory, - ResourceOrigin, -) from invokeai.app.services.models.image_record import ( - ImageRecord, - ImageRecordChanges, - deserialize_image_record, -) + ImageRecord, ImageRecordChanges, deserialize_image_record) T = TypeVar("T", bound=BaseModel) @@ -162,7 +156,6 @@ class SqliteImageRecordStorage(ImageRecordStorageBase): node_id TEXT, metadata TEXT, is_intermediate BOOLEAN DEFAULT FALSE, - board_id TEXT, created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), -- Updated via trigger updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), From 964c71dcb02d5ddbd1c376e963ddd0bf31a1bbe7 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Mon, 10 Jul 2023 18:10:57 +1200 Subject: [PATCH 10/12] feat: Add Swap Sizes --- invokeai/frontend/web/public/locales/en.json | 5 +++-- .../Parameters/Core/ParamAspectRatio.tsx | 6 ++--- .../components/Parameters/Core/ParamSize.tsx | 22 +++++++++++++++---- .../parameters/store/generationSlice.ts | 8 ++++++- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 29a3c0907c..6734f1dcd1 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -529,7 +529,7 @@ "showPreview": "Show Preview", "controlNetControlMode": "Control Mode", "clipSkip": "Clip Skip", - "aspectRatio": "Aspect Ratio" + "aspectRatio": "Ratio" }, "settings": { "models": "Models", @@ -672,6 +672,7 @@ }, "ui": { "showProgressImages": "Show Progress Images", - "hideProgressImages": "Hide Progress Images" + "hideProgressImages": "Hide Progress Images", + "swapSizes": "Swap Sizes" } } diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx index 1b575b5793..0a568fb84d 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamAspectRatio.tsx @@ -6,9 +6,9 @@ import { setAspectRatio } from 'features/ui/store/uiSlice'; const aspectRatios = [ { name: 'Free', value: null }, - { name: '4:3', value: 4 / 3 }, - { name: '16:9', value: 16 / 9 }, - { name: '3:2', value: 3 / 2 }, + { name: 'Portrait', value: 0.67 / 1 }, + { name: 'Wide', value: 16 / 9 }, + { name: 'Square', value: 1 / 1 }, ]; export default function ParamAspectRatio() { diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSize.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSize.tsx index 15637bb091..496431ff3d 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSize.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamSize.tsx @@ -1,13 +1,17 @@ import { Flex, Spacer, Text } from '@chakra-ui/react'; import { RootState } from 'app/store/store'; -import { useAppSelector } from 'app/store/storeHooks'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIIconButton from 'common/components/IAIIconButton'; +import { toggleSize } from 'features/parameters/store/generationSlice'; import { useTranslation } from 'react-i18next'; +import { MdOutlineSwapVert } from 'react-icons/md'; import ParamAspectRatio from './ParamAspectRatio'; import ParamHeight from './ParamHeight'; import ParamWidth from './ParamWidth'; export default function ParamSize() { const { t } = useTranslation(); + const dispatch = useAppDispatch(); const shouldFitToWidthHeight = useAppSelector( (state: RootState) => state.generation.shouldFitToWidthHeight ); @@ -40,10 +44,20 @@ export default function ParamSize() { + } + fontSize={20} + onClick={() => dispatch(toggleSize())} + /> - - - + + + + + ); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 5e476e1b3b..56728f216f 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -143,6 +143,11 @@ export const generationSlice = createSlice({ setWidth: (state, action: PayloadAction) => { state.width = action.payload; }, + toggleSize: (state) => { + const [width, height] = [state.width, state.height]; + state.width = height; + state.height = width; + }, setScheduler: (state, action: PayloadAction) => { state.scheduler = action.payload; }, @@ -281,7 +286,9 @@ export const { resetParametersState, resetSeed, setCfgScale, + setWidth, setHeight, + toggleSize, setImg2imgStrength, setInfillMethod, setIterations, @@ -302,7 +309,6 @@ export const { setThreshold, setTileSize, setVariationAmount, - setWidth, setShouldUseSymmetry, setHorizontalSymmetrySteps, setVerticalSymmetrySteps, From 18e2b130fce4ebcb88a785fe4ab1c34800816c41 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Mon, 10 Jul 2023 09:39:06 -0400 Subject: [PATCH 11/12] disable multiselect --- .../features/gallery/components/GalleryImage.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx index a8d4c84adc..42217230ce 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx @@ -53,13 +53,15 @@ const GalleryImage = (props: HoverableImageProps) => { const handleClick = useCallback( (e: MouseEvent) => { - if (e.shiftKey) { - dispatch(imageRangeEndSelected(props.imageDTO.image_name)); - } else if (e.ctrlKey || e.metaKey) { - dispatch(imageSelectionToggled(props.imageDTO.image_name)); - } else { - dispatch(imageSelected(props.imageDTO.image_name)); - } + // multiselect disabled for now + // if (e.shiftKey) { + // dispatch(imageRangeEndSelected(props.imageDTO.image_name)); + // } else if (e.ctrlKey || e.metaKey) { + // dispatch(imageSelectionToggled(props.imageDTO.image_name)); + // } else { + // dispatch(imageSelected(props.imageDTO.image_name)); + // } + dispatch(imageSelected(props.imageDTO.image_name)); }, [dispatch, props.imageDTO.image_name] ); From f46f8058befcbc9779643d31cc9777ca36b52858 Mon Sep 17 00:00:00 2001 From: Mary Hipp Date: Mon, 10 Jul 2023 09:45:14 -0400 Subject: [PATCH 12/12] load thumbnail --- .../web/src/features/gallery/components/GalleryImage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx index 42217230ce..468db558b3 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryImage.tsx @@ -123,6 +123,7 @@ const GalleryImage = (props: HoverableImageProps) => { // withResetIcon // removed bc it's too easy to accidentally delete images isDropDisabled={true} isUploadDisabled={true} + thumbnail={true} /> )}