diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index e39f438146..8d4499c65f 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -506,12 +506,13 @@ "hiresStrength": "High Res Strength", "imageFit": "Fit Initial Image To Output Size", "codeformerFidelity": "Fidelity", + "compositingSettingsHeader": "Compositing Settings", "maskAdjustmentsHeader": "Mask Adjustments", - "maskBlur": "Mask Blur", - "maskBlurMethod": "Mask Blur Method", + "maskBlur": "Blur", + "maskBlurMethod": "Blur Method", "coherencePassHeader": "Coherence Pass", - "coherenceSteps": "Coherence Pass Steps", - "coherenceStrength": "Coherence Pass Strength", + "coherenceSteps": "Steps", + "coherenceStrength": "Strength", "seamLowThreshold": "Low", "seamHighThreshold": "High", "scaleBeforeProcessing": "Scale Before Processing", @@ -569,6 +570,7 @@ "useSlidersForAll": "Use Sliders For All Options", "showProgressInViewer": "Show Progress Images in Viewer", "antialiasProgressImages": "Antialias Progress Images", + "autoChangeDimensions": "Update W/H To Model Defaults On Change", "resetWebUI": "Reset Web UI", "resetWebUIDesc1": "Resetting the web UI only resets the browser's local cache of your images and remembered settings. It does not delete any images from disk.", "resetWebUIDesc2": "If images aren't showing up in the gallery or something else isn't working, please try resetting before submitting an issue on GitHub.", @@ -712,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/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts index e44cbe504a..240890f043 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelSelected.ts @@ -1,9 +1,12 @@ import { logger } from 'app/logging/logger'; +import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; import { controlNetRemoved } from 'features/controlNet/store/controlNetSlice'; import { loraRemoved } from 'features/lora/store/loraSlice'; import { modelSelected } from 'features/parameters/store/actions'; import { modelChanged, + setHeight, + setWidth, vaeSelected, } from 'features/parameters/store/generationSlice'; import { zMainOrOnnxModel } from 'features/parameters/types/parameterSchemas'; @@ -74,6 +77,22 @@ export const addModelSelectedListener = () => { } } + // Update Width / Height / Bounding Box Dimensions on Model Change + if ( + state.generation.model?.base_model !== newModel.base_model && + state.ui.shouldAutoChangeDimensions + ) { + if (['sdxl', 'sdxl-refiner'].includes(newModel.base_model)) { + dispatch(setWidth(1024)); + dispatch(setHeight(1024)); + dispatch(setBoundingBoxDimensions({ width: 1024, height: 1024 })); + } else { + dispatch(setWidth(512)); + dispatch(setHeight(512)); + dispatch(setBoundingBoxDimensions({ width: 512, height: 512 })); + } + } + dispatch(modelChanged(newModel)); }, }); diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts index 6b544252db..ce2a21c6e7 100644 --- a/invokeai/frontend/web/src/app/store/store.ts +++ b/invokeai/frontend/web/src/app/store/store.ts @@ -6,11 +6,11 @@ import { configureStore, } from '@reduxjs/toolkit'; import canvasReducer from 'features/canvas/store/canvasSlice'; +import changeBoardModalReducer from 'features/changeBoardModal/store/slice'; import controlNetReducer from 'features/controlNet/store/controlNetSlice'; +import deleteImageModalReducer from 'features/deleteImageModal/store/slice'; import dynamicPromptsReducer from 'features/dynamicPrompts/store/dynamicPromptsSlice'; import galleryReducer from 'features/gallery/store/gallerySlice'; -import deleteImageModalReducer from 'features/deleteImageModal/store/slice'; -import changeBoardModalReducer from 'features/changeBoardModal/store/slice'; import loraReducer from 'features/lora/store/loraSlice'; import nodesReducer from 'features/nodes/store/nodesSlice'; import generationReducer from 'features/parameters/store/generationSlice'; diff --git a/invokeai/frontend/web/src/common/components/IAICollapse.tsx b/invokeai/frontend/web/src/common/components/IAICollapse.tsx index a5e08e6ddc..c2e6cebe99 100644 --- a/invokeai/frontend/web/src/common/components/IAICollapse.tsx +++ b/invokeai/frontend/web/src/common/components/IAICollapse.tsx @@ -86,8 +86,8 @@ const IAICollapse = (props: IAIToggleCollapseProps) => { { const dispatch = useAppDispatch(); const { pendingControlImages, autoAddBoardId } = useAppSelector(selector); + const activeTabName = useAppSelector(activeTabNameSelector); const [isMouseOverImage, setIsMouseOverImage] = useState(false); @@ -67,23 +72,54 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => { const [changeIsIntermediate] = useChangeImageIsIntermediateMutation(); const [addToBoard] = useAddImageToBoardMutation(); - + const [removeFromBoard] = useRemoveImageFromBoardMutation(); const handleResetControlImage = useCallback(() => { dispatch(controlNetImageChanged({ controlNetId, controlImage: null })); }, [controlNetId, dispatch]); - const handleSaveControlImage = useCallback(() => { + const handleSaveControlImage = useCallback(async () => { if (!processedControlImage) { return; } - changeIsIntermediate({ + await changeIsIntermediate({ imageDTO: processedControlImage, is_intermediate: false, - }); + }).unwrap(); - addToBoard({ imageDTO: processedControlImage, board_id: autoAddBoardId }); - }, [processedControlImage, autoAddBoardId, changeIsIntermediate, addToBoard]); + if (autoAddBoardId !== 'none') { + addToBoard({ + imageDTO: processedControlImage, + board_id: autoAddBoardId, + }); + } else { + removeFromBoard({ imageDTO: processedControlImage }); + } + }, [ + processedControlImage, + changeIsIntermediate, + autoAddBoardId, + addToBoard, + removeFromBoard, + ]); + + const handleSetControlImageToDimensions = useCallback(() => { + if (!processedControlImage) { + return; + } + + if (activeTabName === 'unifiedCanvas') { + dispatch( + setBoundingBoxDimensions({ + width: processedControlImage.width, + height: processedControlImage.height, + }) + ); + } else { + dispatch(setWidth(processedControlImage.width)); + dispatch(setHeight(processedControlImage.height)); + } + }, [processedControlImage, activeTabName, dispatch]); const handleMouseEnter = useCallback(() => { setIsMouseOverImage(true); @@ -144,21 +180,7 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => { imageDTO={controlImage} isDropDisabled={shouldShowProcessedImage || !isEnabled} postUploadAction={postUploadAction} - > - <> - : undefined} - tooltip="Reset Control Image" - /> - : undefined} - tooltip="Save Control Image" - styleOverrides={{ marginTop: 6 }} - /> - - + /> { imageDTO={processedControlImage} isUploadDisabled={true} isDropDisabled={!isEnabled} - > - <> - : undefined} - tooltip="Reset Control Image" - /> - : undefined} - tooltip="Save Control Image" - styleOverrides={{ marginTop: 6 }} - /> - - + /> + + <> + : undefined} + tooltip="Reset Control Image" + /> + : undefined} + tooltip="Save Control Image" + styleOverrides={{ marginTop: 6 }} + /> + : undefined} + tooltip="Set Control Image Dimensions To W/H" + styleOverrides={{ marginTop: 12 }} + /> + + {pendingControlImages.includes(controlNetId) && ( { const { galleryView } = useAppSelector(selector); const dispatch = useAppDispatch(); const { isOpen: isBoardListOpen, onToggle: onToggleBoardList } = - useDisclosure(); + useDisclosure({ defaultIsOpen: true }); const handleClickImages = useCallback(() => { dispatch(galleryViewChanged('images')); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx index 64e44e63d0..1711c9344a 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight.tsx @@ -14,8 +14,9 @@ const selector = createSelector( [stateSelector, isStagingSelector], ({ canvas, generation }, isStaging) => { const { boundingBoxDimensions } = canvas; - const { aspectRatio } = generation; + const { model, aspectRatio } = generation; return { + model, boundingBoxDimensions, isStaging, aspectRatio, @@ -26,11 +27,15 @@ const selector = createSelector( const ParamBoundingBoxWidth = () => { const dispatch = useAppDispatch(); - const { boundingBoxDimensions, isStaging, aspectRatio } = + const { model, boundingBoxDimensions, isStaging, aspectRatio } = useAppSelector(selector); const { t } = useTranslation(); + const initial = ['sdxl', 'sdxl-refiner'].includes(model?.base_model as string) + ? 1024 + : 512; + const handleChangeHeight = (v: number) => { dispatch( setBoundingBoxDimensions({ @@ -53,15 +58,15 @@ const ParamBoundingBoxWidth = () => { dispatch( setBoundingBoxDimensions({ ...boundingBoxDimensions, - height: Math.floor(512), + height: Math.floor(initial), }) ); if (aspectRatio) { - const newWidth = roundToMultiple(512 * aspectRatio, 64); + const newWidth = roundToMultiple(initial * aspectRatio, 64); dispatch( setBoundingBoxDimensions({ width: newWidth, - height: Math.floor(512), + height: Math.floor(initial), }) ); } @@ -71,7 +76,7 @@ const ParamBoundingBoxWidth = () => { { + 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(() => { + if (shouldLockAspectRatio) { + dispatch(setShouldLockAspectRatio(false)); + if ( + !mappedAspectRatios.includes( + boundingBoxDimensions.width / boundingBoxDimensions.height + ) + ) { + dispatch(setAspectRatio(null)); + } else { + dispatch( + setAspectRatio( + boundingBoxDimensions.width / boundingBoxDimensions.height + ) + ); + } + } else { + dispatch(setShouldLockAspectRatio(true)); + dispatch( + setAspectRatio( + boundingBoxDimensions.width / boundingBoxDimensions.height + ) + ); + } + }, [shouldLockAspectRatio, boundingBoxDimensions, dispatch]); + + const handleToggleSize = useCallback(() => { + dispatch(flipBoundingBoxAxes()); + dispatch(setAspectRatio(null)); + if (shouldLockAspectRatio) { + dispatch( + setAspectRatio( + boundingBoxDimensions.height / boundingBoxDimensions.width + ) + ); + } + }, [dispatch, shouldLockAspectRatio, boundingBoxDimensions]); + return ( } fontSize={20} - onClick={() => dispatch(flipBoundingBoxAxes())} + onClick={handleToggleSize} + /> + } + isChecked={shouldLockAspectRatio} + onClick={handleLockRatio} /> diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx index d5671fb0c0..a70d5b33ca 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth.tsx @@ -14,8 +14,9 @@ const selector = createSelector( [stateSelector, isStagingSelector], ({ canvas, generation }, isStaging) => { const { boundingBoxDimensions } = canvas; - const { aspectRatio } = generation; + const { model, aspectRatio } = generation; return { + model, boundingBoxDimensions, isStaging, aspectRatio, @@ -26,9 +27,13 @@ const selector = createSelector( const ParamBoundingBoxWidth = () => { const dispatch = useAppDispatch(); - const { boundingBoxDimensions, isStaging, aspectRatio } = + const { model, boundingBoxDimensions, isStaging, aspectRatio } = useAppSelector(selector); + const initial = ['sdxl', 'sdxl-refiner'].includes(model?.base_model as string) + ? 1024 + : 512; + const { t } = useTranslation(); const handleChangeWidth = (v: number) => { @@ -53,14 +58,14 @@ const ParamBoundingBoxWidth = () => { dispatch( setBoundingBoxDimensions({ ...boundingBoxDimensions, - width: Math.floor(512), + width: Math.floor(initial), }) ); if (aspectRatio) { - const newHeight = roundToMultiple(512 / aspectRatio, 64); + const newHeight = roundToMultiple(initial / aspectRatio, 64); dispatch( setBoundingBoxDimensions({ - width: Math.floor(512), + width: Math.floor(initial), height: newHeight, }) ); @@ -71,7 +76,7 @@ const ParamBoundingBoxWidth = () => { { + const { t } = useTranslation(); + + return ( + + + + + + + + + + + + + + ); +}; + +export default memo(ParamCompositingSettingsCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx index a531eba57f..8907e46e5f 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse.tsx @@ -1,8 +1,9 @@ -import { Flex } from '@chakra-ui/react'; +import { Divider, Flex } from '@chakra-ui/react'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import IAICollapse from 'common/components/IAICollapse'; +import SubParametersWrapper from '../../SubParametersWrapper'; import ParamInfillMethod from './ParamInfillMethod'; import ParamInfillTilesize from './ParamInfillTilesize'; import ParamScaleBeforeProcessing from './ParamScaleBeforeProcessing'; @@ -15,11 +16,16 @@ const ParamInfillCollapse = () => { return ( - - - - - + + + + + + + + + + ); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx index 2cd70c4445..7dae01bb91 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledHeight.tsx @@ -5,16 +5,17 @@ import IAISlider from 'common/components/IAISlider'; import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { setScaledBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; import { generationSelector } from 'features/parameters/store/generationSelectors'; -import { systemSelector } from 'features/system/store/systemSelectors'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - [generationSelector, systemSelector, canvasSelector], - (parameters, system, canvas) => { + [generationSelector, canvasSelector], + (generation, canvas) => { const { scaledBoundingBoxDimensions, boundingBoxScaleMethod } = canvas; + const { model } = generation; return { + model, scaledBoundingBoxDimensions, isManual: boundingBoxScaleMethod === 'manual', }; @@ -24,7 +25,12 @@ const selector = createSelector( const ParamScaledHeight = () => { const dispatch = useAppDispatch(); - const { isManual, scaledBoundingBoxDimensions } = useAppSelector(selector); + const { model, isManual, scaledBoundingBoxDimensions } = + useAppSelector(selector); + + const initial = ['sdxl', 'sdxl-refiner'].includes(model?.base_model as string) + ? 1024 + : 512; const { t } = useTranslation(); @@ -41,7 +47,7 @@ const ParamScaledHeight = () => { dispatch( setScaledBoundingBoxDimensions({ ...scaledBoundingBoxDimensions, - height: Math.floor(512), + height: Math.floor(initial), }) ); }; @@ -51,7 +57,7 @@ const ParamScaledHeight = () => { isDisabled={!isManual} label={t('parameters.scaledHeight')} min={64} - max={1024} + max={1536} step={64} value={scaledBoundingBoxDimensions.height} onChange={handleChangeScaledHeight} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledWidth.tsx index acceaeb9a2..0eda5a122c 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledWidth.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamScaledWidth.tsx @@ -4,15 +4,18 @@ import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider from 'common/components/IAISlider'; import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { setScaledBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; +import { generationSelector } from 'features/parameters/store/generationSelectors'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - [canvasSelector], - (canvas) => { + [canvasSelector, generationSelector], + (canvas, generation) => { const { boundingBoxScaleMethod, scaledBoundingBoxDimensions } = canvas; + const { model } = generation; return { + model, scaledBoundingBoxDimensions, isManual: boundingBoxScaleMethod === 'manual', }; @@ -22,7 +25,12 @@ const selector = createSelector( const ParamScaledWidth = () => { const dispatch = useAppDispatch(); - const { isManual, scaledBoundingBoxDimensions } = useAppSelector(selector); + const { model, isManual, scaledBoundingBoxDimensions } = + useAppSelector(selector); + + const initial = ['sdxl', 'sdxl-refiner'].includes(model?.base_model as string) + ? 1024 + : 512; const { t } = useTranslation(); @@ -39,7 +47,7 @@ const ParamScaledWidth = () => { dispatch( setScaledBoundingBoxDimensions({ ...scaledBoundingBoxDimensions, - width: Math.floor(512), + width: Math.floor(initial), }) ); }; @@ -49,7 +57,7 @@ const ParamScaledWidth = () => { isDisabled={!isManual} label={t('parameters.scaledWidth')} min={64} - max={1024} + max={1536} step={64} value={scaledBoundingBoxDimensions.width} onChange={handleChangeScaledWidth} diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/MaskAdjustment/ParamMaskAdjustmentCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/MaskAdjustment/ParamMaskAdjustmentCollapse.tsx deleted file mode 100644 index 9ca6503d3d..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/MaskAdjustment/ParamMaskAdjustmentCollapse.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import IAICollapse from 'common/components/IAICollapse'; -import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; -import ParamMaskBlur from './ParamMaskBlur'; -import ParamMaskBlurMethod from './ParamMaskBlurMethod'; - -const ParamMaskAdjustmentCollapse = () => { - const { t } = useTranslation(); - - return ( - - - - - - - ); -}; - -export default memo(ParamMaskAdjustmentCollapse); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamPainting/ParamCanvasCoherencePassCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamPainting/ParamCanvasCoherencePassCollapse.tsx deleted file mode 100644 index b454c4ccec..0000000000 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/SeamPainting/ParamCanvasCoherencePassCollapse.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import IAICollapse from 'common/components/IAICollapse'; -import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; -import ParamCanvasCoherenceSteps from './ParamCanvasCoherenceSteps'; -import ParamCanvasCoherenceStrength from './ParamCanvasCoherenceStrength'; - -const ParamCanvasCoherencePassCollapse = () => { - const { t } = useTranslation(); - - return ( - - - - - - - ); -}; - -export default memo(ParamCanvasCoherencePassCollapse); 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..05e0b09cee 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 = [ @@ -12,6 +15,8 @@ const aspectRatios = [ { name: '1:1', value: 1 / 1 }, ]; +export const mappedAspectRatios = aspectRatios.map((ar) => ar.value); + export default function ParamAspectRatio() { const aspectRatio = useAppSelector( (state: RootState) => state.generation.aspectRatio @@ -34,7 +39,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/ParamHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/Core/ParamHeight.tsx index 37ed43ddc0..097dec4270 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 @@ -11,16 +11,15 @@ import { useTranslation } from 'react-i18next'; const selector = createSelector( [stateSelector], ({ generation, hotkeys, config }) => { - const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = - config.sd.height; - const { height } = generation; + const { min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.height; + const { model, height } = generation; const { aspectRatio } = generation; const step = hotkeys.shift ? fineStep : coarseStep; return { + model, height, - initial, min, sliderMax, inputMax, @@ -37,11 +36,15 @@ type ParamHeightProps = Omit< >; const ParamHeight = (props: ParamHeightProps) => { - const { height, initial, min, sliderMax, inputMax, step, aspectRatio } = + const { model, height, min, sliderMax, inputMax, step, aspectRatio } = useAppSelector(selector); const dispatch = useAppDispatch(); const { t } = useTranslation(); + const initial = ['sdxl', 'sdxl-refiner'].includes(model?.base_model as string) + ? 1024 + : 512; + const handleChange = useCallback( (v: number) => { dispatch(setHeight(v)); 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..63700c4922 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,71 @@ 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 ParamAspectRatio from './ParamAspectRatio'; +import { activeTabNameSelector } from '../../../../ui/store/uiSelectors'; +import ParamAspectRatio, { mappedAspectRatios } 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(() => { + if (shouldLockAspectRatio) { + dispatch(setShouldLockAspectRatio(false)); + if (!mappedAspectRatios.includes(width / height)) { + dispatch(setAspectRatio(null)); + } else { + dispatch(setAspectRatio(width / height)); + } + } else { + dispatch(setShouldLockAspectRatio(true)); + dispatch(setAspectRatio(width / height)); + } + }, [shouldLockAspectRatio, width, height, dispatch]); + + const handleToggleSize = useCallback(() => { + dispatch(toggleSize()); + dispatch(setAspectRatio(null)); + if (shouldLockAspectRatio) { + dispatch(setAspectRatio(height / width)); + } + }, [dispatch, shouldLockAspectRatio, width, height]); + return ( dispatch(toggleSize())} + onClick={handleToggleSize} + /> + } + isChecked={shouldLockAspectRatio} + isDisabled={ + activeTabName === 'img2img' ? !shouldFitToWidthHeight : false + } + onClick={handleLockRatio} /> 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 895b6cedbf..d1c763fe45 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 @@ -11,15 +11,14 @@ import { useTranslation } from 'react-i18next'; const selector = createSelector( [stateSelector], ({ generation, hotkeys, config }) => { - const { initial, min, sliderMax, inputMax, fineStep, coarseStep } = - config.sd.width; - const { width, aspectRatio } = generation; + const { min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.width; + const { model, width, aspectRatio } = generation; const step = hotkeys.shift ? fineStep : coarseStep; return { + model, width, - initial, min, sliderMax, inputMax, @@ -33,11 +32,15 @@ const selector = createSelector( type ParamWidthProps = Omit; const ParamWidth = (props: ParamWidthProps) => { - const { width, initial, min, sliderMax, inputMax, step, aspectRatio } = + const { model, width, min, sliderMax, inputMax, step, aspectRatio } = useAppSelector(selector); const dispatch = useAppDispatch(); const { t } = useTranslation(); + const initial = ['sdxl', 'sdxl-refiner'].includes(model?.base_model as string) + ? 1024 + : 512; + const handleChange = useCallback( (v: number) => { dispatch(setWidth(v)); diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx index b45fc9f386..2a14ee634c 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ImageToImage/ImageToImageStrength.tsx @@ -6,6 +6,7 @@ import IAISlider from 'common/components/IAISlider'; import { setImg2imgStrength } from 'features/parameters/store/generationSlice'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import SubParametersWrapper from '../SubParametersWrapper'; const selector = createSelector( [stateSelector], @@ -44,20 +45,22 @@ const ImageToImageStrength = () => { }, [dispatch, initial]); return ( - + + + ); }; diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/SubParametersWrapper.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/SubParametersWrapper.tsx new file mode 100644 index 0000000000..96c78b1336 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/SubParametersWrapper.tsx @@ -0,0 +1,39 @@ +import { Flex, Text } from '@chakra-ui/react'; +import { ReactNode, memo } from 'react'; + +type SubParameterWrapperProps = { + children: ReactNode | ReactNode[]; + label?: string; +}; + +const SubParametersWrapper = (props: SubParameterWrapperProps) => ( + + {props.label && ( + + {props.label} + + )} + {props.children} + +); + +SubParametersWrapper.displayName = 'SubSettingsWrapper'; + +export default memo(SubParametersWrapper); diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index 8a4b9a7963..5a830b6e95 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,9 @@ export const generationSlice = createSlice({ state.height = roundToMultiple(state.width / newAspectRatio, 8); } }, + setShouldLockAspectRatio: (state, action: PayloadAction) => { + state.shouldLockAspectRatio = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(configChanged, (state, action) => { @@ -342,6 +347,7 @@ export const { shouldUseCpuNoiseChanged, setShouldShowAdvancedOptions, setAspectRatio, + setShouldLockAspectRatio, vaePrecisionChanged, } = generationSlice.actions; diff --git a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLImg2ImgDenoisingStrength.tsx b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLImg2ImgDenoisingStrength.tsx index 52d7567339..66ad518655 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLImg2ImgDenoisingStrength.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLImg2ImgDenoisingStrength.tsx @@ -3,6 +3,7 @@ import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider from 'common/components/IAISlider'; +import SubParametersWrapper from 'features/parameters/components/Parameters/SubParametersWrapper'; import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { setSDXLImg2ImgDenoisingStrength } from '../store/sdxlSlice'; @@ -34,19 +35,21 @@ const ParamSDXLImg2ImgDenoisingStrength = () => { }, [dispatch]); return ( - + + + ); }; diff --git a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx index 5a3a8dc379..fcf4d0bcd3 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/ParamSDXLRefinerCollapse.tsx @@ -4,6 +4,7 @@ import { stateSelector } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAICollapse from 'common/components/IAICollapse'; +import { memo } from 'react'; import ParamSDXLRefinerCFGScale from './SDXLRefiner/ParamSDXLRefinerCFGScale'; import ParamSDXLRefinerModelSelect from './SDXLRefiner/ParamSDXLRefinerModelSelect'; import ParamSDXLRefinerNegativeAestheticScore from './SDXLRefiner/ParamSDXLRefinerNegativeAestheticScore'; @@ -12,7 +13,6 @@ import ParamSDXLRefinerScheduler from './SDXLRefiner/ParamSDXLRefinerScheduler'; import ParamSDXLRefinerStart from './SDXLRefiner/ParamSDXLRefinerStart'; import ParamSDXLRefinerSteps from './SDXLRefiner/ParamSDXLRefinerSteps'; import ParamUseSDXLRefiner from './SDXLRefiner/ParamUseSDXLRefiner'; -import { memo } from 'react'; const selector = createSelector( stateSelector, diff --git a/invokeai/frontend/web/src/features/sdxl/components/SDXLUnifiedCanvasTabParameters.tsx b/invokeai/frontend/web/src/features/sdxl/components/SDXLUnifiedCanvasTabParameters.tsx index 8fc4a3181c..01236d8ec3 100644 --- a/invokeai/frontend/web/src/features/sdxl/components/SDXLUnifiedCanvasTabParameters.tsx +++ b/invokeai/frontend/web/src/features/sdxl/components/SDXLUnifiedCanvasTabParameters.tsx @@ -1,8 +1,7 @@ import ParamDynamicPromptsCollapse from 'features/dynamicPrompts/components/ParamDynamicPromptsCollapse'; import ParamLoraCollapse from 'features/lora/components/ParamLoraCollapse'; +import ParamCompositingSettingsCollapse from 'features/parameters/components/Parameters/Canvas/Compositing/ParamCompositingSettingsCollapse'; import ParamInfillAndScalingCollapse from 'features/parameters/components/Parameters/Canvas/InfillAndScaling/ParamInfillAndScalingCollapse'; -import ParamMaskAdjustmentCollapse from 'features/parameters/components/Parameters/Canvas/MaskAdjustment/ParamMaskAdjustmentCollapse'; -import ParamCanvasCoherencePassCollapse from 'features/parameters/components/Parameters/Canvas/SeamPainting/ParamCanvasCoherencePassCollapse'; import ParamControlNetCollapse from 'features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse'; import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/ParamNoiseCollapse'; import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse'; @@ -20,9 +19,8 @@ export default function SDXLUnifiedCanvasTabParameters() { - - + ); diff --git a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx index 9f9f9fc58a..c1d02aed50 100644 --- a/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx +++ b/invokeai/frontend/web/src/features/system/components/SettingsModal/SettingsModal.tsx @@ -30,6 +30,7 @@ import { shouldUseWatermarkerChanged, } from 'features/system/store/systemSlice'; import { + setShouldAutoChangeDimensions, setShouldShowProgressInViewer, setShouldUseSliders, } from 'features/ui/store/uiSlice'; @@ -68,7 +69,11 @@ const selector = createSelector( shouldUseWatermarker, } = system; - const { shouldUseSliders, shouldShowProgressInViewer } = ui; + const { + shouldUseSliders, + shouldShowProgressInViewer, + shouldAutoChangeDimensions, + } = ui; const { shouldShowAdvancedOptions } = generation; @@ -83,6 +88,7 @@ const selector = createSelector( shouldShowAdvancedOptions, shouldUseNSFWChecker, shouldUseWatermarker, + shouldAutoChangeDimensions, }; }, { @@ -158,6 +164,7 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { shouldShowAdvancedOptions, shouldUseNSFWChecker, shouldUseWatermarker, + shouldAutoChangeDimensions, } = useAppSelector(selector); const handleClickResetWebUI = useCallback(() => { @@ -297,6 +304,13 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => { ) } /> + ) => + dispatch(setShouldAutoChangeDimensions(e.target.checked)) + } + /> {shouldShowLocalizationToggle && ( { - - + diff --git a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts index 2e4227e8e6..82c9ef4e77 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiSlice.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiSlice.ts @@ -15,6 +15,7 @@ export const initialUIState: UIState = { shouldHidePreview: false, shouldShowProgressInViewer: true, shouldShowEmbeddingPicker: false, + shouldAutoChangeDimensions: false, favoriteSchedulers: [], globalContextMenuCloseTrigger: 0, panels: {}, @@ -57,6 +58,9 @@ export const uiSlice = createSlice({ toggleEmbeddingPicker: (state) => { state.shouldShowEmbeddingPicker = !state.shouldShowEmbeddingPicker; }, + setShouldAutoChangeDimensions: (state, action: PayloadAction) => { + state.shouldAutoChangeDimensions = action.payload; + }, contextMenusClosed: (state) => { state.globalContextMenuCloseTrigger += 1; }, @@ -84,6 +88,7 @@ export const { setShouldShowProgressInViewer, favoriteSchedulersChanged, toggleEmbeddingPicker, + setShouldAutoChangeDimensions, contextMenusClosed, panelsChanged, } = uiSlice.actions; diff --git a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts index 0f0ed64f97..41a359a651 100644 --- a/invokeai/frontend/web/src/features/ui/store/uiTypes.ts +++ b/invokeai/frontend/web/src/features/ui/store/uiTypes.ts @@ -21,6 +21,7 @@ export interface UIState { shouldHidePreview: boolean; shouldShowProgressInViewer: boolean; shouldShowEmbeddingPicker: boolean; + shouldAutoChangeDimensions: boolean; favoriteSchedulers: SchedulerParam[]; globalContextMenuCloseTrigger: number; panels: Record;