diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 1391f35930..b198f27160 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -528,7 +528,7 @@ "hidePreview": "Hide Preview", "showPreview": "Show Preview", "controlNetControlMode": "Control Mode", - "clipSkip": "Clip Skip", + "clipSkip": "CLIP Skip", "aspectRatio": "Ratio" }, "settings": { diff --git a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx index 0241f3eb55..3421749658 100644 --- a/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx +++ b/invokeai/frontend/web/src/features/canvas/components/IAICanvasToolbar/IAICanvasBoundingBox.tsx @@ -12,6 +12,7 @@ import { setIsMovingBoundingBox, setIsTransformingBoundingBox, } from 'features/canvas/store/canvasSlice'; +import { uiSelector } from 'features/ui/store/uiSelectors'; import Konva from 'konva'; import { GroupConfig } from 'konva/lib/Group'; import { KonvaEventObject } from 'konva/lib/Node'; @@ -22,8 +23,8 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Group, Rect, Transformer } from 'react-konva'; const boundingBoxPreviewSelector = createSelector( - canvasSelector, - (canvas) => { + [canvasSelector, uiSelector], + (canvas, ui) => { const { boundingBoxCoordinates, boundingBoxDimensions, @@ -35,6 +36,8 @@ const boundingBoxPreviewSelector = createSelector( shouldSnapToGrid, } = canvas; + const { aspectRatio } = ui; + return { boundingBoxCoordinates, boundingBoxDimensions, @@ -45,6 +48,7 @@ const boundingBoxPreviewSelector = createSelector( shouldSnapToGrid, tool, hitStrokeWidth: 20 / stageScale, + aspectRatio, }; }, { @@ -70,6 +74,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => { shouldSnapToGrid, tool, hitStrokeWidth, + aspectRatio, } = useAppSelector(boundingBoxPreviewSelector); const transformerRef = useRef(null); @@ -137,12 +142,22 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => { const x = Math.round(rect.x()); const y = Math.round(rect.y()); - dispatch( - setBoundingBoxDimensions({ - width, - height, - }) - ); + if (aspectRatio) { + const newHeight = roundToMultiple(width / aspectRatio, 64); + dispatch( + setBoundingBoxDimensions({ + width: width, + height: newHeight, + }) + ); + } else { + dispatch( + setBoundingBoxDimensions({ + width, + height, + }) + ); + } dispatch( setBoundingBoxCoordinates({ @@ -154,7 +169,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => { // Reset the scale now that the coords/dimensions have been un-scaled rect.scaleX(1); rect.scaleY(1); - }, [dispatch, shouldSnapToGrid]); + }, [dispatch, shouldSnapToGrid, aspectRatio]); const anchorDragBoundFunc = useCallback( ( diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 371719e087..550cfff4bc 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -7,7 +7,14 @@ import { import { IRect, Vector2d } from 'konva/lib/types'; import { clamp, cloneDeep } from 'lodash-es'; // +import { + setActiveTab, + setAspectRatio, + setShouldUseCanvasBetaLayout, +} from 'features/ui/store/uiSlice'; import { RgbaColor } from 'react-colorful'; +import { sessionCanceled } from 'services/api/thunks/session'; +import { ImageDTO } from 'services/api/types'; import calculateCoordinates from '../util/calculateCoordinates'; import calculateScale from '../util/calculateScale'; import { STAGE_PADDING_PERCENTAGE } from '../util/constants'; @@ -28,13 +35,6 @@ import { isCanvasBaseImage, isCanvasMaskLine, } from './canvasTypes'; -import { ImageDTO } from 'services/api/types'; -import { sessionCanceled } from 'services/api/thunks/session'; -import { - setActiveTab, - setShouldUseCanvasBetaLayout, -} from 'features/ui/store/uiSlice'; -import { imageUrlsReceived } from 'services/api/thunks/image'; export const initialLayerState: CanvasLayerState = { objects: [], @@ -240,6 +240,16 @@ export const canvasSlice = createSlice({ state.scaledBoundingBoxDimensions = scaledDimensions; } }, + flipBoundingBoxAxes: (state) => { + const [currWidth, currHeight] = [ + state.boundingBoxDimensions.width, + state.boundingBoxDimensions.height, + ]; + state.boundingBoxDimensions = { + width: currHeight, + height: currWidth, + }; + }, setBoundingBoxCoordinates: (state, action: PayloadAction) => { state.boundingBoxCoordinates = floorCoordinates(action.payload); }, @@ -864,6 +874,15 @@ export const canvasSlice = createSlice({ builder.addCase(setActiveTab, (state, action) => { state.doesCanvasNeedScaling = true; }); + builder.addCase(setAspectRatio, (state, action) => { + const ratio = action.payload; + if (ratio) { + state.boundingBoxDimensions.height = roundToMultiple( + state.boundingBoxDimensions.width / ratio, + 64 + ); + } + }); // builder.addCase(imageUrlsReceived.fulfilled, (state, action) => { // const { image_name, image_url, thumbnail_url } = action.payload; @@ -912,6 +931,7 @@ export const { setBoundingBoxDimensions, setBoundingBoxPreviewFill, setBoundingBoxScaleMethod, + flipBoundingBoxAxes, setBrushColor, setBrushSize, setCanvasContainerDimensions, 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 b529c87225..12fc4abcf7 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 @@ -2,22 +2,26 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider from 'common/components/IAISlider'; +import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { canvasSelector, isStagingSelector, } from 'features/canvas/store/canvasSelectors'; import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; +import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - [canvasSelector, isStagingSelector], - (canvas, isStaging) => { + [canvasSelector, isStagingSelector, uiSelector], + (canvas, isStaging, ui) => { const { boundingBoxDimensions } = canvas; + const { aspectRatio } = ui; return { boundingBoxDimensions, isStaging, + aspectRatio, }; }, defaultSelectorOptions @@ -25,7 +29,8 @@ const selector = createSelector( const ParamBoundingBoxWidth = () => { const dispatch = useAppDispatch(); - const { boundingBoxDimensions, isStaging } = useAppSelector(selector); + const { boundingBoxDimensions, isStaging, aspectRatio } = + useAppSelector(selector); const { t } = useTranslation(); @@ -36,6 +41,15 @@ const ParamBoundingBoxWidth = () => { height: Math.floor(v), }) ); + if (aspectRatio) { + const newWidth = roundToMultiple(v * aspectRatio, 64); + dispatch( + setBoundingBoxDimensions({ + width: newWidth, + height: Math.floor(v), + }) + ); + } }; const handleResetHeight = () => { @@ -45,6 +59,15 @@ const ParamBoundingBoxWidth = () => { height: Math.floor(512), }) ); + if (aspectRatio) { + const newWidth = roundToMultiple(512 * aspectRatio, 64); + dispatch( + setBoundingBoxDimensions({ + width: newWidth, + height: Math.floor(512), + }) + ); + } }; return ( 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 new file mode 100644 index 0000000000..3c07768461 --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxSize.tsx @@ -0,0 +1,57 @@ +import { Flex, Spacer, Text } from '@chakra-ui/react'; +import { useAppDispatch } from 'app/store/storeHooks'; +import IAIIconButton from 'common/components/IAIIconButton'; +import { flipBoundingBoxAxes } from 'features/canvas/store/canvasSlice'; +import { useTranslation } from 'react-i18next'; +import { MdOutlineSwapVert } from 'react-icons/md'; +import ParamAspectRatio from '../../Core/ParamAspectRatio'; +import ParamBoundingBoxHeight from './ParamBoundingBoxHeight'; +import ParamBoundingBoxWidth from './ParamBoundingBoxWidth'; + +export default function ParamBoundingBoxSize() { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + return ( + + + + {t('parameters.aspectRatio')} + + + + } + fontSize={20} + onClick={() => dispatch(flipBoundingBoxAxes())} + /> + + + + + ); +} 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 07d74049ed..5c9a82110a 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 @@ -2,22 +2,26 @@ import { createSelector } from '@reduxjs/toolkit'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISlider from 'common/components/IAISlider'; +import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { canvasSelector, isStagingSelector, } from 'features/canvas/store/canvasSelectors'; import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; +import { uiSelector } from 'features/ui/store/uiSelectors'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( - [canvasSelector, isStagingSelector], - (canvas, isStaging) => { + [canvasSelector, isStagingSelector, uiSelector], + (canvas, isStaging, ui) => { const { boundingBoxDimensions } = canvas; + const { aspectRatio } = ui; return { boundingBoxDimensions, isStaging, + aspectRatio, }; }, defaultSelectorOptions @@ -25,7 +29,8 @@ const selector = createSelector( const ParamBoundingBoxWidth = () => { const dispatch = useAppDispatch(); - const { boundingBoxDimensions, isStaging } = useAppSelector(selector); + const { boundingBoxDimensions, isStaging, aspectRatio } = + useAppSelector(selector); const { t } = useTranslation(); @@ -36,6 +41,15 @@ const ParamBoundingBoxWidth = () => { width: Math.floor(v), }) ); + if (aspectRatio) { + const newHeight = roundToMultiple(v / aspectRatio, 64); + dispatch( + setBoundingBoxDimensions({ + width: Math.floor(v), + height: newHeight, + }) + ); + } }; const handleResetWidth = () => { @@ -45,6 +59,15 @@ const ParamBoundingBoxWidth = () => { width: Math.floor(512), }) ); + if (aspectRatio) { + const newHeight = roundToMultiple(512 / aspectRatio, 64); + dispatch( + setBoundingBoxDimensions({ + width: Math.floor(512), + height: newHeight, + }) + ); + } }; return ( 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 6ea9d4bc8d..6e4ce7d5d0 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 @@ -4,8 +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 ParamBoundingBoxHeight from 'features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxHeight'; -import ParamBoundingBoxWidth from 'features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxWidth'; +import ParamBoundingBoxSize from 'features/parameters/components/Parameters/Canvas/BoundingBox/ParamBoundingBoxSize'; import ParamCFGScale from 'features/parameters/components/Parameters/Core/ParamCFGScale'; import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations'; import ParamModelandVAEandScheduler from 'features/parameters/components/Parameters/Core/ParamModelandVAEandScheduler'; @@ -51,8 +50,7 @@ const UnifiedCanvasCoreParameters = () => { - - + ) : ( <> @@ -65,8 +63,7 @@ const UnifiedCanvasCoreParameters = () => { - - + )}