diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts index fa1e453ff9..a7b9f82d57 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/modelsLoaded.ts @@ -83,7 +83,7 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => { dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel })); const optimalDimension = getOptimalDimension(defaultModelInList); - if (getIsSizeOptimal(state.canvasV2.document.width, state.canvasV2.document.height, optimalDimension)) { + if (getIsSizeOptimal(state.canvasV2.document.rect.width, state.canvasV2.document.rect.height, optimalDimension)) { return; } const { width, height } = calculateNewSize( diff --git a/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx b/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx index 6e49df0ca7..9de1876f60 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/HeadsUpDisplay.tsx @@ -25,18 +25,18 @@ export const HeadsUpDisplay = memo(() => { return ( - + - - - - - - + + + + + + { const gridSize = this.manager.stateApi.getCtrlKey() || this.manager.stateApi.getMetaKey() ? 8 : 64; - const oldBbox = this.manager.stateApi.getBbox(); - const newBbox: IRect = { - ...oldBbox, + const bbox = this.manager.stateApi.getBbox(); + const bboxRect: Rect = { + ...bbox.rect, x: roundToMultiple(this.rect.x(), gridSize), y: roundToMultiple(this.rect.y(), gridSize), }; - this.rect.setAttrs(newBbox); - if (oldBbox.x !== newBbox.x || oldBbox.y !== newBbox.y) { - this.manager.stateApi.onBboxTransformed(newBbox); + this.rect.setAttrs(bboxRect); + if (bbox.rect.x !== bboxRect.x || bbox.rect.y !== bboxRect.y) { + this.manager.stateApi.onBboxTransformed(bboxRect); } }); @@ -170,7 +170,7 @@ export class CanvasBbox { height = fittedHeight; } - const bbox = { + const bboxRect = { x: Math.round(x), y: Math.round(y), width, @@ -180,15 +180,15 @@ export class CanvasBbox { // Update the bboxRect's attrs directly with the new transform, and reset its scale to 1. // TODO(psyche): In `renderBboxPreview()` we also call setAttrs, need to do it twice to ensure it renders correctly. // Gotta be a way to avoid setting it twice... - this.rect.setAttrs({ ...bbox, scaleX: 1, scaleY: 1 }); + this.rect.setAttrs({ ...bboxRect, scaleX: 1, scaleY: 1 }); // Update the bbox in internal state. - this.manager.stateApi.onBboxTransformed(bbox); + this.manager.stateApi.onBboxTransformed(bboxRect); // Update the aspect ratio buffer whenever the shift key is not held - this allows for a nice UX where you can start // a transform, get the right aspect ratio, then hold shift to lock it in. if (!shift) { - $aspectRatioBuffer.set(bbox.width / bbox.height); + $aspectRatioBuffer.set(bboxRect.width / bboxRect.height); } }); @@ -210,10 +210,10 @@ export class CanvasBbox { this.group.listening(toolState.selected === 'bbox'); this.rect.setAttrs({ - x: bbox.x, - y: bbox.y, - width: bbox.width, - height: bbox.height, + x: bbox.rect.x, + y: bbox.rect.y, + width: bbox.rect.width, + height: bbox.rect.height, scaleX: 1, scaleY: 1, listening: toolState.selected === 'bbox', diff --git a/invokeai/frontend/web/src/features/controlLayers/store/bboxReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/bboxReducers.ts index ded7d30a49..c01bdf5b1a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/bboxReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/bboxReducers.ts @@ -16,17 +16,17 @@ export const bboxReducers = { if (action.payload === 'auto') { const optimalDimension = getOptimalDimension(state.params.model); - const size = pick(state.bbox, 'width', 'height'); + const size = pick(state.bbox.rect, 'width', 'height'); state.bbox.scaledSize = getScaledBoundingBoxDimensions(size, optimalDimension); } }, bboxChanged: (state, action: PayloadAction) => { - state.bbox = { ...state.bbox, ...action.payload }; + state.bbox.rect = action.payload; state.layers.imageCache = null; if (state.bbox.scaleMethod === 'auto') { const optimalDimension = getOptimalDimension(state.params.model); - const size = pick(state.bbox, 'width', 'height'); + const size = pick(state.bbox.rect, 'width', 'height'); state.bbox.scaledSize = getScaledBoundingBoxDimensions(size, optimalDimension); } }, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts index 5f07dde123..a19556ed3a 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts @@ -55,15 +55,11 @@ const initialState: CanvasV2State = { }, }, document: { - width: 512, - height: 512, + rect: { x: 0, y: 0, width: 512, height: 512 }, aspectRatio: deepClone(initialAspectRatioState), }, bbox: { - x: 0, - y: 0, - width: 512, - height: 512, + rect: { x: 0, y: 0, width: 512, height: 512 }, scaleMethod: 'auto', scaledSize: { width: 512, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/documentReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/documentReducers.ts index 2d14f05cd1..16fb34f7c0 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/documentReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/documentReducers.ts @@ -13,21 +13,21 @@ export const documentReducers = { action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }> ) => { const { width, updateAspectRatio, clamp } = action.payload; - state.document.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width; + state.document.rect.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width; if (state.document.aspectRatio.isLocked) { - state.document.height = roundToMultiple(state.document.width / state.document.aspectRatio.value, 8); + state.document.rect.height = roundToMultiple(state.document.rect.width / state.document.aspectRatio.value, 8); } if (updateAspectRatio || !state.document.aspectRatio.isLocked) { - state.document.aspectRatio.value = state.document.width / state.document.height; + state.document.aspectRatio.value = state.document.rect.width / state.document.rect.height; state.document.aspectRatio.id = 'Free'; state.document.aspectRatio.isLocked = false; } if (!state.session.isActive) { - state.bbox.width = state.document.width; - state.bbox.height = state.document.height; + state.bbox.rect.width = state.document.rect.width; + state.bbox.rect.height = state.document.rect.height; } }, documentHeightChanged: ( @@ -36,21 +36,21 @@ export const documentReducers = { ) => { const { height, updateAspectRatio, clamp } = action.payload; - state.document.height = clamp ? Math.max(roundDownToMultiple(height, 8), 64) : height; + state.document.rect.height = clamp ? Math.max(roundDownToMultiple(height, 8), 64) : height; if (state.document.aspectRatio.isLocked) { - state.document.width = roundToMultiple(state.document.height * state.document.aspectRatio.value, 8); + state.document.rect.width = roundToMultiple(state.document.rect.height * state.document.aspectRatio.value, 8); } if (updateAspectRatio || !state.document.aspectRatio.isLocked) { - state.document.aspectRatio.value = state.document.width / state.document.height; + state.document.aspectRatio.value = state.document.rect.width / state.document.rect.height; state.document.aspectRatio.id = 'Free'; state.document.aspectRatio.isLocked = false; } if (!state.session.isActive) { - state.bbox.width = state.document.width; - state.bbox.height = state.document.height; + state.bbox.rect.width = state.document.rect.width; + state.bbox.rect.height = state.document.rect.height; } }, documentAspectRatioLockToggled: (state) => { @@ -66,39 +66,51 @@ export const documentReducers = { state.document.aspectRatio.value = ASPECT_RATIO_MAP[id].ratio; const { width, height } = calculateNewSize( state.document.aspectRatio.value, - state.document.width * state.document.height + state.document.rect.width * state.document.rect.height ); - state.document.width = width; - state.document.height = height; + state.document.rect.width = width; + state.document.rect.height = height; + } + if (!state.session.isActive) { + state.bbox.rect.width = state.document.rect.width; + state.bbox.rect.height = state.document.rect.height; } }, documentDimensionsSwapped: (state) => { state.document.aspectRatio.value = 1 / state.document.aspectRatio.value; if (state.document.aspectRatio.id === 'Free') { - const newWidth = state.document.height; - const newHeight = state.document.width; - state.document.width = newWidth; - state.document.height = newHeight; + const newWidth = state.document.rect.height; + const newHeight = state.document.rect.width; + state.document.rect.width = newWidth; + state.document.rect.height = newHeight; } else { const { width, height } = calculateNewSize( state.document.aspectRatio.value, - state.document.width * state.document.height + state.document.rect.width * state.document.rect.height ); - state.document.width = width; - state.document.height = height; + state.document.rect.width = width; + state.document.rect.height = height; state.document.aspectRatio.id = ASPECT_RATIO_MAP[state.document.aspectRatio.id].inverseID; } + if (!state.session.isActive) { + state.bbox.rect.width = state.document.rect.width; + state.bbox.rect.height = state.document.rect.height; + } }, documentSizeOptimized: (state) => { const optimalDimension = getOptimalDimension(state.params.model); if (state.document.aspectRatio.isLocked) { const { width, height } = calculateNewSize(state.document.aspectRatio.value, optimalDimension ** 2); - state.document.width = width; - state.document.height = height; + state.document.rect.width = width; + state.document.rect.height = height; } else { state.document.aspectRatio = deepClone(initialAspectRatioState); - state.document.width = optimalDimension; - state.document.height = optimalDimension; + state.document.rect.width = optimalDimension; + state.document.rect.height = optimalDimension; + } + if (!state.session.isActive) { + state.bbox.rect.width = state.document.rect.width; + state.bbox.rect.height = state.document.rect.height; } }, } satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/paramsReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/paramsReducers.ts index ac7abd6615..6cf8fc68c3 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/paramsReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/paramsReducers.ts @@ -62,10 +62,10 @@ export const paramsReducers = { // Update the bbox size to match the new model's optimal size // TODO(psyche): Should we change the document size too? const optimalDimension = getOptimalDimension(model); - if (!getIsSizeOptimal(state.document.width, state.document.height, optimalDimension)) { + if (!getIsSizeOptimal(state.document.rect.width, state.document.rect.height, optimalDimension)) { const bboxDims = calculateNewSize(state.document.aspectRatio.value, optimalDimension * optimalDimension); - state.bbox.width = bboxDims.width; - state.bbox.height = bboxDims.height; + state.bbox.rect.width = bboxDims.width; + state.bbox.rect.height = bboxDims.height; if (state.bbox.scaleMethod === 'auto') { state.bbox.scaledSize = getScaledBoundingBoxDimensions(bboxDims, optimalDimension); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index 23688e7091..f215e1b194 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -831,8 +831,12 @@ export type CanvasV2State = { fill: RgbaColor; }; document: { - width: ParameterWidth; - height: ParameterHeight; + rect: { + x: number; + y: number; + width: ParameterWidth; + height: ParameterHeight; + }; aspectRatio: AspectRatioState; }; settings: { @@ -845,10 +849,12 @@ export type CanvasV2State = { clipToBbox: boolean; }; bbox: { - x: number; - y: number; - width: ParameterWidth; - height: ParameterHeight; + rect: { + x: number; + y: number; + width: ParameterWidth; + height: ParameterHeight; + }; scaledSize: { width: ParameterWidth; height: ParameterHeight; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts index 30cbd48f9e..ab3fcef493 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSD1Graph.ts @@ -214,7 +214,7 @@ export const buildSD1Graph = async (state: RootState, manager: CanvasManager): P manager, state.canvasV2.controlAdapters.entities, g, - state.canvasV2.bbox, + state.canvasV2.bbox.rect, denoise, modelConfig.base ); @@ -223,7 +223,7 @@ export const buildSD1Graph = async (state: RootState, manager: CanvasManager): P manager, state.canvasV2.regions.entities, g, - state.canvasV2.bbox, + state.canvasV2.bbox.rect, modelConfig.base, denoise, posCond, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts index 2233445a25..c6bb11b9ba 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/buildSDXLGraph.ts @@ -218,7 +218,7 @@ export const buildSDXLGraph = async (state: RootState, manager: CanvasManager): manager, state.canvasV2.controlAdapters.entities, g, - state.canvasV2.bbox, + state.canvasV2.bbox.rect, denoise, modelConfig.base ); @@ -227,7 +227,7 @@ export const buildSDXLGraph = async (state: RootState, manager: CanvasManager): manager, state.canvasV2.regions.entities, g, - state.canvasV2.bbox, + state.canvasV2.bbox.rect, modelConfig.base, denoise, posCond, diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts b/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts index 7097029ca7..9818357fc0 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts @@ -75,7 +75,7 @@ export const getIsIntermediate = (state: RootState) => { }; export const getSizes = (bboxState: CanvasV2State['bbox']) => { - const originalSize = pick(bboxState, 'width', 'height'); + const originalSize = pick(bboxState.rect, 'width', 'height'); const scaledSize = ['auto', 'manual'].includes(bboxState.scaleMethod) ? bboxState.scaledSize : originalSize; return { originalSize, scaledSize }; }; diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamHeight.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamHeight.tsx index 68a2c05c16..dbf8d9346a 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamHeight.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamHeight.tsx @@ -10,7 +10,7 @@ export const ParamHeight = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const optimalDimension = useAppSelector(selectOptimalDimension); - const height = useAppSelector((s) => s.canvasV2.document.height); + const height = useAppSelector((s) => s.canvasV2.document.rect.height); const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin); const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax); const numberInputMin = useAppSelector((s) => s.config.sd.height.numberInputMin); diff --git a/invokeai/frontend/web/src/features/parameters/components/Core/ParamWidth.tsx b/invokeai/frontend/web/src/features/parameters/components/Core/ParamWidth.tsx index 2d1b6935b4..ddf4975fc9 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Core/ParamWidth.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Core/ParamWidth.tsx @@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next'; export const ParamWidth = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const width = useAppSelector((s) => s.canvasV2.document.width); + const width = useAppSelector((s) => s.canvasV2.document.rect.width); const optimalDimension = useAppSelector(selectOptimalDimension); const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin); const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax); diff --git a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/AspectRatioIconPreview.tsx b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/AspectRatioIconPreview.tsx index b69cd0666d..9b8af7e16e 100644 --- a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/AspectRatioIconPreview.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/AspectRatioIconPreview.tsx @@ -30,10 +30,10 @@ export const AspectRatioIconPreview = memo(() => { return { width: 0, height: 0 }; } - let width = document.width; - let height = document.height; + let width = document.rect.width; + let height = document.rect.height; - if (document.width > document.height) { + if (document.rect.width > document.rect.height) { width = containerSize.width; height = width / document.aspectRatio.value; } else { @@ -42,7 +42,7 @@ export const AspectRatioIconPreview = memo(() => { } return { width, height }; - }, [containerSize, document.width, document.height, document.aspectRatio.value]); + }, [containerSize, document.rect.width, document.rect.height, document.aspectRatio.value]); return ( diff --git a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SetOptimalSizeButton.tsx b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SetOptimalSizeButton.tsx index 7becf2e36f..a5e88513f8 100644 --- a/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SetOptimalSizeButton.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/DocumentSize/SetOptimalSizeButton.tsx @@ -10,8 +10,8 @@ import { RiSparklingFill } from 'react-icons/ri'; export const SetOptimalSizeButton = memo(() => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const width = useAppSelector((s) => s.canvasV2.document.width); - const height = useAppSelector((s) => s.canvasV2.document.height); + const width = useAppSelector((s) => s.canvasV2.document.rect.width); + const height = useAppSelector((s) => s.canvasV2.document.rect.height); const optimalDimension = useAppSelector(selectOptimalDimension); const isSizeTooSmall = useMemo( () => getIsSizeTooSmall(width, height, optimalDimension), diff --git a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx index 866a20fb2d..145a0213af 100644 --- a/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx +++ b/invokeai/frontend/web/src/features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion.tsx @@ -18,14 +18,15 @@ import { useStandaloneAccordionToggle } from 'features/settingsAccordions/hooks/ import { memo } from 'react'; import { useTranslation } from 'react-i18next'; - const selector = createMemoizedSelector([selectHrfSlice, selectCanvasV2Slice], (hrf, canvasV2) => { const { shouldRandomizeSeed, model } = canvasV2.params; const { hrfEnabled } = hrf; const badges: string[] = []; const isSDXL = model?.base === 'sdxl'; - const { aspectRatio, width, height } = canvasV2.document; + const { aspectRatio } = canvasV2.document; + const { width, height } = canvasV2.document.rect; + badges.push(`${width}×${height}`); badges.push(aspectRatio.id);