mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat: Add Lock Ratio Option
This commit is contained in:
parent
2469859c01
commit
171a0eaf51
@ -714,7 +714,8 @@
|
|||||||
"ui": {
|
"ui": {
|
||||||
"showProgressImages": "Show Progress Images",
|
"showProgressImages": "Show Progress Images",
|
||||||
"hideProgressImages": "Hide Progress Images",
|
"hideProgressImages": "Hide Progress Images",
|
||||||
"swapSizes": "Swap Sizes"
|
"swapSizes": "Swap Sizes",
|
||||||
|
"lockRatio": "Lock Ratio"
|
||||||
},
|
},
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"reloadNodeTemplates": "Reload Node Templates",
|
"reloadNodeTemplates": "Reload Node Templates",
|
||||||
|
@ -1,17 +1,50 @@
|
|||||||
import { Flex, Spacer, Text } from '@chakra-ui/react';
|
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 IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { flipBoundingBoxAxes } from 'features/canvas/store/canvasSlice';
|
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 { useTranslation } from 'react-i18next';
|
||||||
|
import { FaLock } from 'react-icons/fa';
|
||||||
import { MdOutlineSwapVert } from 'react-icons/md';
|
import { MdOutlineSwapVert } from 'react-icons/md';
|
||||||
import ParamAspectRatio from '../../Core/ParamAspectRatio';
|
import ParamAspectRatio from '../../Core/ParamAspectRatio';
|
||||||
import ParamBoundingBoxHeight from './ParamBoundingBoxHeight';
|
import ParamBoundingBoxHeight from './ParamBoundingBoxHeight';
|
||||||
import ParamBoundingBoxWidth from './ParamBoundingBoxWidth';
|
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() {
|
export default function ParamBoundingBoxSize() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { shouldLockAspectRatio, boundingBoxDimensions } =
|
||||||
|
useAppSelector(sizeOptsSelector);
|
||||||
|
|
||||||
|
const handleLockRatio = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
setAspectRatio(boundingBoxDimensions.width / boundingBoxDimensions.height)
|
||||||
|
);
|
||||||
|
dispatch(setShouldLockAspectRatio(!shouldLockAspectRatio));
|
||||||
|
}, [shouldLockAspectRatio, boundingBoxDimensions, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
@ -49,6 +82,14 @@ export default function ParamBoundingBoxSize() {
|
|||||||
fontSize={20}
|
fontSize={20}
|
||||||
onClick={() => dispatch(flipBoundingBoxAxes())}
|
onClick={() => dispatch(flipBoundingBoxAxes())}
|
||||||
/>
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip={t('ui.lockRatio')}
|
||||||
|
aria-label={t('ui.lockRatio')}
|
||||||
|
size="sm"
|
||||||
|
icon={<FaLock />}
|
||||||
|
isChecked={shouldLockAspectRatio}
|
||||||
|
onClick={handleLockRatio}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<ParamBoundingBoxWidth />
|
<ParamBoundingBoxWidth />
|
||||||
<ParamBoundingBoxHeight />
|
<ParamBoundingBoxHeight />
|
||||||
|
@ -2,7 +2,10 @@ import { ButtonGroup, Flex } from '@chakra-ui/react';
|
|||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
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';
|
import { activeTabNameSelector } from '../../../../ui/store/uiSelectors';
|
||||||
|
|
||||||
const aspectRatios = [
|
const aspectRatios = [
|
||||||
@ -34,7 +37,10 @@ export default function ParamAspectRatio() {
|
|||||||
isDisabled={
|
isDisabled={
|
||||||
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
||||||
}
|
}
|
||||||
onClick={() => dispatch(setAspectRatio(ratio.value))}
|
onClick={() => {
|
||||||
|
dispatch(setAspectRatio(ratio.value));
|
||||||
|
dispatch(setShouldLockAspectRatio(false));
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{ratio.name}
|
{ratio.name}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
|
@ -1,22 +1,54 @@
|
|||||||
import { Flex, Spacer, Text } from '@chakra-ui/react';
|
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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
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 { useTranslation } from 'react-i18next';
|
||||||
|
import { FaLock } from 'react-icons/fa';
|
||||||
import { MdOutlineSwapVert } from 'react-icons/md';
|
import { MdOutlineSwapVert } from 'react-icons/md';
|
||||||
|
import { activeTabNameSelector } from '../../../../ui/store/uiSelectors';
|
||||||
import ParamAspectRatio from './ParamAspectRatio';
|
import ParamAspectRatio from './ParamAspectRatio';
|
||||||
import ParamHeight from './ParamHeight';
|
import ParamHeight from './ParamHeight';
|
||||||
import ParamWidth from './ParamWidth';
|
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() {
|
export default function ParamSize() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const shouldFitToWidthHeight = useAppSelector(
|
const {
|
||||||
(state: RootState) => state.generation.shouldFitToWidthHeight
|
activeTabName,
|
||||||
);
|
shouldFitToWidthHeight,
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
shouldLockAspectRatio,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
} = useAppSelector(sizeOptsSelector);
|
||||||
|
|
||||||
|
const handleLockRatio = useCallback(() => {
|
||||||
|
dispatch(setAspectRatio(width / height));
|
||||||
|
dispatch(setShouldLockAspectRatio(!shouldLockAspectRatio));
|
||||||
|
}, [shouldLockAspectRatio, width, height, dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
@ -57,6 +89,17 @@ export default function ParamSize() {
|
|||||||
}
|
}
|
||||||
onClick={() => dispatch(toggleSize())}
|
onClick={() => dispatch(toggleSize())}
|
||||||
/>
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
tooltip={t('ui.lockRatio')}
|
||||||
|
aria-label={t('ui.lockRatio')}
|
||||||
|
size="sm"
|
||||||
|
icon={<FaLock />}
|
||||||
|
isChecked={shouldLockAspectRatio}
|
||||||
|
isDisabled={
|
||||||
|
activeTabName === 'img2img' ? !shouldFitToWidthHeight : false
|
||||||
|
}
|
||||||
|
onClick={handleLockRatio}
|
||||||
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex gap={2} alignItems="center">
|
<Flex gap={2} alignItems="center">
|
||||||
<Flex gap={2} flexDirection="column" width="full">
|
<Flex gap={2} flexDirection="column" width="full">
|
||||||
|
@ -62,6 +62,7 @@ export interface GenerationState {
|
|||||||
shouldUseCpuNoise: boolean;
|
shouldUseCpuNoise: boolean;
|
||||||
shouldShowAdvancedOptions: boolean;
|
shouldShowAdvancedOptions: boolean;
|
||||||
aspectRatio: number | null;
|
aspectRatio: number | null;
|
||||||
|
shouldLockAspectRatio: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialGenerationState: GenerationState = {
|
export const initialGenerationState: GenerationState = {
|
||||||
@ -101,6 +102,7 @@ export const initialGenerationState: GenerationState = {
|
|||||||
shouldUseCpuNoise: true,
|
shouldUseCpuNoise: true,
|
||||||
shouldShowAdvancedOptions: false,
|
shouldShowAdvancedOptions: false,
|
||||||
aspectRatio: null,
|
aspectRatio: null,
|
||||||
|
shouldLockAspectRatio: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState: GenerationState = initialGenerationState;
|
const initialState: GenerationState = initialGenerationState;
|
||||||
@ -272,6 +274,15 @@ export const generationSlice = createSlice({
|
|||||||
state.height = roundToMultiple(state.width / newAspectRatio, 8);
|
state.height = roundToMultiple(state.width / newAspectRatio, 8);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setShouldLockAspectRatio: (state, action: PayloadAction<boolean>) => {
|
||||||
|
if (
|
||||||
|
action.payload === false &&
|
||||||
|
![null, 2 / 3, 16 / 9, 1 / 1].includes(state.aspectRatio)
|
||||||
|
) {
|
||||||
|
state.aspectRatio = null;
|
||||||
|
}
|
||||||
|
state.shouldLockAspectRatio = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(configChanged, (state, action) => {
|
builder.addCase(configChanged, (state, action) => {
|
||||||
@ -342,6 +353,7 @@ export const {
|
|||||||
shouldUseCpuNoiseChanged,
|
shouldUseCpuNoiseChanged,
|
||||||
setShouldShowAdvancedOptions,
|
setShouldShowAdvancedOptions,
|
||||||
setAspectRatio,
|
setAspectRatio,
|
||||||
|
setShouldLockAspectRatio,
|
||||||
vaePrecisionChanged,
|
vaePrecisionChanged,
|
||||||
} = generationSlice.actions;
|
} = generationSlice.actions;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user