From 171a0eaf51baedfb38ed2c9cd9b1205432034414 Mon Sep 17 00:00:00 2001 From: blessedcoolant <54517381+blessedcoolant@users.noreply.github.com> Date: Wed, 30 Aug 2023 07:04:08 +1200 Subject: [PATCH] feat: Add Lock Ratio Option --- invokeai/frontend/web/public/locales/en.json | 3 +- .../BoundingBox/ParamBoundingBoxSize.tsx | 43 +++++++++++++- .../Parameters/Core/ParamAspectRatio.tsx | 10 +++- .../components/Parameters/Core/ParamSize.tsx | 57 ++++++++++++++++--- .../parameters/store/generationSlice.ts | 12 ++++ 5 files changed, 114 insertions(+), 11 deletions(-) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index c9f6a2f2f4..8d4499c65f 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -714,7 +714,8 @@ "ui": { "showProgressImages": "Show Progress Images", "hideProgressImages": "Hide Progress Images", - "swapSizes": "Swap Sizes" + "swapSizes": "Swap Sizes", + "lockRatio": "Lock Ratio" }, "nodes": { "reloadNodeTemplates": "Reload Node Templates", diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxSize.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxSize.tsx index 438e3f4041..4c2e6d252c 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxSize.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxSize.tsx @@ -1,17 +1,50 @@ import { Flex, Spacer, Text } from '@chakra-ui/react'; -import { useAppDispatch } from 'app/store/storeHooks'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIIconButton from 'common/components/IAIIconButton'; +import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { flipBoundingBoxAxes } from 'features/canvas/store/canvasSlice'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { + setAspectRatio, + setShouldLockAspectRatio, +} from 'features/parameters/store/generationSlice'; +import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { FaLock } from 'react-icons/fa'; import { MdOutlineSwapVert } from 'react-icons/md'; import ParamAspectRatio from '../../Core/ParamAspectRatio'; import ParamBoundingBoxHeight from './ParamBoundingBoxHeight'; import ParamBoundingBoxWidth from './ParamBoundingBoxWidth'; +const sizeOptsSelector = createSelector( + [generationSelector, canvasSelector], + (generation, canvas) => { + const { shouldFitToWidthHeight, shouldLockAspectRatio } = generation; + const { boundingBoxDimensions } = canvas; + + return { + shouldFitToWidthHeight, + shouldLockAspectRatio, + boundingBoxDimensions, + }; + } +); + export default function ParamBoundingBoxSize() { const dispatch = useAppDispatch(); const { t } = useTranslation(); + const { shouldLockAspectRatio, boundingBoxDimensions } = + useAppSelector(sizeOptsSelector); + + const handleLockRatio = useCallback(() => { + dispatch( + setAspectRatio(boundingBoxDimensions.width / boundingBoxDimensions.height) + ); + dispatch(setShouldLockAspectRatio(!shouldLockAspectRatio)); + }, [shouldLockAspectRatio, boundingBoxDimensions, dispatch]); + return ( dispatch(flipBoundingBoxAxes())} /> + } + isChecked={shouldLockAspectRatio} + onClick={handleLockRatio} + /> 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 41be41ec28..bf2782b525 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 @@ -2,7 +2,10 @@ 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/parameters/store/generationSlice'; +import { + setAspectRatio, + setShouldLockAspectRatio, +} from 'features/parameters/store/generationSlice'; import { activeTabNameSelector } from '../../../../ui/store/uiSelectors'; const aspectRatios = [ @@ -34,7 +37,10 @@ export default function ParamAspectRatio() { isDisabled={ activeTabName === 'img2img' ? !shouldFitToWidthHeight : false } - onClick={() => dispatch(setAspectRatio(ratio.value))} + onClick={() => { + dispatch(setAspectRatio(ratio.value)); + dispatch(setShouldLockAspectRatio(false)); + }} > {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 c629ef4601..6ebcb0beb1 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,22 +1,54 @@ import { Flex, Spacer, Text } from '@chakra-ui/react'; -import { RootState } from 'app/store/store'; +import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIIconButton from 'common/components/IAIIconButton'; -import { toggleSize } from 'features/parameters/store/generationSlice'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; +import { + setAspectRatio, + setShouldLockAspectRatio, + toggleSize, +} from 'features/parameters/store/generationSlice'; +import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { FaLock } from 'react-icons/fa'; import { MdOutlineSwapVert } from 'react-icons/md'; +import { activeTabNameSelector } from '../../../../ui/store/uiSelectors'; import ParamAspectRatio from './ParamAspectRatio'; import ParamHeight from './ParamHeight'; import ParamWidth from './ParamWidth'; -import { activeTabNameSelector } from '../../../../ui/store/uiSelectors'; + +const sizeOptsSelector = createSelector( + [generationSelector, activeTabNameSelector], + (generation, activeTabName) => { + const { shouldFitToWidthHeight, shouldLockAspectRatio, width, height } = + generation; + + return { + activeTabName, + shouldFitToWidthHeight, + shouldLockAspectRatio, + width, + height, + }; + } +); export default function ParamSize() { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const shouldFitToWidthHeight = useAppSelector( - (state: RootState) => state.generation.shouldFitToWidthHeight - ); - const activeTabName = useAppSelector(activeTabNameSelector); + const { + activeTabName, + shouldFitToWidthHeight, + shouldLockAspectRatio, + width, + height, + } = useAppSelector(sizeOptsSelector); + + const handleLockRatio = useCallback(() => { + dispatch(setAspectRatio(width / height)); + dispatch(setShouldLockAspectRatio(!shouldLockAspectRatio)); + }, [shouldLockAspectRatio, width, height, dispatch]); + return ( dispatch(toggleSize())} /> + } + isChecked={shouldLockAspectRatio} + isDisabled={ + activeTabName === 'img2img' ? !shouldFitToWidthHeight : false + } + onClick={handleLockRatio} + /> diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 8a4b9a7963..94f7f58b31 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -62,6 +62,7 @@ export interface GenerationState { shouldUseCpuNoise: boolean; shouldShowAdvancedOptions: boolean; aspectRatio: number | null; + shouldLockAspectRatio: boolean; } export const initialGenerationState: GenerationState = { @@ -101,6 +102,7 @@ export const initialGenerationState: GenerationState = { shouldUseCpuNoise: true, shouldShowAdvancedOptions: false, aspectRatio: null, + shouldLockAspectRatio: false, }; const initialState: GenerationState = initialGenerationState; @@ -272,6 +274,15 @@ export const generationSlice = createSlice({ state.height = roundToMultiple(state.width / newAspectRatio, 8); } }, + setShouldLockAspectRatio: (state, action: PayloadAction) => { + if ( + action.payload === false && + ![null, 2 / 3, 16 / 9, 1 / 1].includes(state.aspectRatio) + ) { + state.aspectRatio = null; + } + state.shouldLockAspectRatio = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(configChanged, (state, action) => { @@ -342,6 +353,7 @@ export const { shouldUseCpuNoiseChanged, setShouldShowAdvancedOptions, setAspectRatio, + setShouldLockAspectRatio, vaePrecisionChanged, } = generationSlice.actions;