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;