feat(ui): rip out document size

barely knew ye
This commit is contained in:
psychedelicious 2024-07-16 17:03:55 +10:00
parent 22ab63fe8d
commit c3c95754f7
28 changed files with 202 additions and 332 deletions

View File

@ -3,9 +3,9 @@ import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'
import type { AppDispatch, RootState } from 'app/store/store'; import type { AppDispatch, RootState } from 'app/store/store';
import type { JSONObject } from 'common/types'; import type { JSONObject } from 'common/types';
import { import {
bboxHeightChanged,
bboxWidthChanged,
caModelChanged, caModelChanged,
documentHeightChanged,
documentWidthChanged,
ipaModelChanged, ipaModelChanged,
loraDeleted, loraDeleted,
modelChanged, modelChanged,
@ -83,16 +83,16 @@ const handleMainModels: ModelHandler = (models, state, dispatch, log) => {
dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel })); dispatch(modelChanged({ model: defaultModelInList, previousModel: currentModel }));
const optimalDimension = getOptimalDimension(defaultModelInList); const optimalDimension = getOptimalDimension(defaultModelInList);
if (getIsSizeOptimal(state.canvasV2.document.rect.width, state.canvasV2.document.rect.height, optimalDimension)) { if (getIsSizeOptimal(state.canvasV2.bbox.rect.width, state.canvasV2.bbox.rect.height, optimalDimension)) {
return; return;
} }
const { width, height } = calculateNewSize( const { width, height } = calculateNewSize(
state.canvasV2.document.aspectRatio.value, state.canvasV2.bbox.aspectRatio.value,
optimalDimension * optimalDimension optimalDimension * optimalDimension
); );
dispatch(documentWidthChanged({ width })); dispatch(bboxWidthChanged({ width }));
dispatch(documentHeightChanged({ height })); dispatch(bboxHeightChanged({ height }));
return; return;
} }
} }

View File

@ -1,7 +1,7 @@
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { import {
documentHeightChanged, bboxHeightChanged,
documentWidthChanged, bboxWidthChanged,
setCfgRescaleMultiplier, setCfgRescaleMultiplier,
setCfgScale, setCfgScale,
setScheduler, setScheduler,
@ -99,13 +99,13 @@ export const addSetDefaultSettingsListener = (startAppListening: AppStartListeni
const setSizeOptions = { updateAspectRatio: true, clamp: true }; const setSizeOptions = { updateAspectRatio: true, clamp: true };
if (width) { if (width) {
if (isParameterWidth(width)) { if (isParameterWidth(width)) {
dispatch(documentWidthChanged({ width, ...setSizeOptions })); dispatch(bboxWidthChanged({ width, ...setSizeOptions }));
} }
} }
if (height) { if (height) {
if (isParameterHeight(height)) { if (isParameterHeight(height)) {
dispatch(documentHeightChanged({ height, ...setSizeOptions })); dispatch(bboxHeightChanged({ height, ...setSizeOptions }));
} }
} }

View File

@ -25,7 +25,7 @@ type ResizeDirection =
| 'down-right'; | 'down-right';
export const CanvasResizer = memo(() => { export const CanvasResizer = memo(() => {
const document = useAppSelector((s) => s.canvasV2.document); const bbox = useAppSelector((s) => s.canvasV2.bbox);
const [resizeDirection, setResizeDirection] = useState<ResizeDirection>('center-out'); const [resizeDirection, setResizeDirection] = useState<ResizeDirection>('center-out');
const setDirUpLeft = useCallback(() => { const setDirUpLeft = useCallback(() => {

View File

@ -3,7 +3,7 @@ import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { documentHeightChanged, documentWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import type { ControlAdapterEntity } from 'features/controlLayers/store/types'; import type { ControlAdapterEntity } from 'features/controlLayers/store/types';
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
@ -89,15 +89,15 @@ export const CAImagePreview = memo(
if (shift) { if (shift) {
const { width, height } = controlImage; const { width, height } = controlImage;
dispatch(documentWidthChanged({ width, ...options })); dispatch(bboxWidthChanged({ width, ...options }));
dispatch(documentHeightChanged({ height, ...options })); dispatch(bboxHeightChanged({ height, ...options }));
} else { } else {
const { width, height } = calculateNewSize( const { width, height } = calculateNewSize(
controlImage.width / controlImage.height, controlImage.width / controlImage.height,
optimalDimension * optimalDimension optimalDimension * optimalDimension
); );
dispatch(documentWidthChanged({ width, ...options })); dispatch(bboxWidthChanged({ width, ...options }));
dispatch(documentHeightChanged({ height, ...options })); dispatch(bboxHeightChanged({ height, ...options }));
} }
}, [controlImage, dispatch, optimalDimension, shift]); }, [controlImage, dispatch, optimalDimension, shift]);

View File

@ -20,12 +20,10 @@ export const HeadsUpDisplay = memo(() => {
const lastMouseDownPos = useStore($lastMouseDownPos); const lastMouseDownPos = useStore($lastMouseDownPos);
const lastAddedPoint = useStore($lastAddedPoint); const lastAddedPoint = useStore($lastAddedPoint);
const bbox = useAppSelector((s) => s.canvasV2.bbox); const bbox = useAppSelector((s) => s.canvasV2.bbox);
const document = useAppSelector((s) => s.canvasV2.document);
return ( return (
<Flex flexDir="column" bg="blackAlpha.400" borderBottomEndRadius="base" p={2} minW={64} gap={2}> <Flex flexDir="column" bg="blackAlpha.400" borderBottomEndRadius="base" p={2} minW={64} gap={2}>
<HUDItem label="Zoom" value={`${round(stageAttrs.scale * 100, 2)}%`} /> <HUDItem label="Zoom" value={`${round(stageAttrs.scale * 100, 2)}%`} />
<HUDItem label="Document Size" value={`${document.rect.width}×${document.rect.height} px`} />
<HUDItem label="Stage Pos" value={`${round(stageAttrs.x, 3)}, ${round(stageAttrs.y, 3)}`} /> <HUDItem label="Stage Pos" value={`${round(stageAttrs.x, 3)}, ${round(stageAttrs.y, 3)}`} />
<HUDItem <HUDItem
label="Stage Size" label="Stage Size"

View File

@ -3,7 +3,7 @@ import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { documentHeightChanged, documentWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { bboxHeightChanged, bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import type { ImageWithDims } from 'features/controlLayers/store/types'; import type { ImageWithDims } from 'features/controlLayers/store/types';
import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types'; import type { ImageDraggableData, TypesafeDroppableData } from 'features/dnd/types';
@ -42,15 +42,15 @@ export const IPAImagePreview = memo(({ image, onChangeImage, ipAdapterId, droppa
const options = { updateAspectRatio: true, clamp: true }; const options = { updateAspectRatio: true, clamp: true };
if (shift) { if (shift) {
const { width, height } = controlImage; const { width, height } = controlImage;
dispatch(documentWidthChanged({ width, ...options })); dispatch(bboxWidthChanged({ width, ...options }));
dispatch(documentHeightChanged({ height, ...options })); dispatch(bboxHeightChanged({ height, ...options }));
} else { } else {
const { width, height } = calculateNewSize( const { width, height } = calculateNewSize(
controlImage.width / controlImage.height, controlImage.width / controlImage.height,
optimalDimension * optimalDimension optimalDimension * optimalDimension
); );
dispatch(documentWidthChanged({ width, ...options })); dispatch(bboxWidthChanged({ width, ...options }));
dispatch(documentHeightChanged({ height, ...options })); dispatch(bboxHeightChanged({ height, ...options }));
} }
}, [controlImage, dispatch, optimalDimension, shift]); }, [controlImage, dispatch, optimalDimension, shift]);

View File

@ -3,7 +3,7 @@ import { skipToken } from '@reduxjs/toolkit/query';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import IAIDndImageIcon from 'common/components/IAIDndImageIcon'; import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
import { documentHeightChanged, documentWidthChanged, iiReset } from 'features/controlLayers/store/canvasV2Slice'; import { bboxHeightChanged, bboxWidthChanged, iiReset } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import type { ImageDraggableData, InitialImageDropData } from 'features/dnd/types'; import type { ImageDraggableData, InitialImageDropData } from 'features/dnd/types';
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize'; import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
@ -36,12 +36,12 @@ export const InitialImagePreview = memo(() => {
const options = { updateAspectRatio: true, clamp: true }; const options = { updateAspectRatio: true, clamp: true };
if (shift) { if (shift) {
const { width, height } = imageDTO; const { width, height } = imageDTO;
dispatch(documentWidthChanged({ width, ...options })); dispatch(bboxWidthChanged({ width, ...options }));
dispatch(documentHeightChanged({ height, ...options })); dispatch(bboxHeightChanged({ height, ...options }));
} else { } else {
const { width, height } = calculateNewSize(imageDTO.width / imageDTO.height, optimalDimension * optimalDimension); const { width, height } = calculateNewSize(imageDTO.width / imageDTO.height, optimalDimension * optimalDimension);
dispatch(documentWidthChanged({ width, ...options })); dispatch(bboxWidthChanged({ width, ...options }));
dispatch(documentHeightChanged({ height, ...options })); dispatch(bboxHeightChanged({ height, ...options }));
} }
}, [imageDTO, dispatch, optimalDimension, shift]); }, [imageDTO, dispatch, optimalDimension, shift]);

View File

@ -1,67 +0,0 @@
import { getArbitraryBaseColor } from '@invoke-ai/ui-library';
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { DOCUMENT_FIT_PADDING_PX } from 'features/controlLayers/konva/constants';
import Konva from 'konva';
export class CanvasDocumentSizeOverlay {
group: Konva.Group;
outerRect: Konva.Rect;
innerRect: Konva.Rect;
padding: number;
manager: CanvasManager;
constructor(manager: CanvasManager, padding?: number) {
this.manager = manager;
this.padding = padding ?? DOCUMENT_FIT_PADDING_PX;
this.group = new Konva.Group({ id: 'document_overlay_group', listening: false });
this.outerRect = new Konva.Rect({
id: 'document_overlay_outer_rect',
listening: false,
fill: getArbitraryBaseColor(10),
opacity: 0.7,
});
this.innerRect = new Konva.Rect({
id: 'document_overlay_inner_rect',
listening: false,
fill: 'white',
globalCompositeOperation: 'destination-out',
});
this.group.add(this.outerRect);
this.group.add(this.innerRect);
}
render() {
const document = this.manager.stateApi.getDocument();
this.group.zIndex(0);
const x = this.manager.stage.x();
const y = this.manager.stage.y();
const width = this.manager.stage.width();
const height = this.manager.stage.height();
const scale = this.manager.stage.scaleX();
this.outerRect.setAttrs({
offsetX: x / scale,
offsetY: y / scale,
width: width / scale,
height: height / scale,
});
this.innerRect.setAttrs(document.rect);
}
fitToStage() {
const document = this.manager.stateApi.getDocument();
// Fit & center the document on the stage
const width = this.manager.stage.width();
const height = this.manager.stage.height();
const docWidthWithBuffer = document.rect.width + this.padding * 2;
const docHeightWithBuffer = document.rect.height + this.padding * 2;
const scale = Math.min(Math.min(width / docWidthWithBuffer, height / docHeightWithBuffer), 1);
const x = (width - docWidthWithBuffer * scale) / 2 + this.padding * scale;
const y = (height - docHeightWithBuffer * scale) / 2 + this.padding * scale;
this.manager.stage.setAttrs({ x, y, width, height, scaleX: scale, scaleY: scale });
this.manager.stateApi.setStageAttrs({ x, y, width, height, scale });
}
}

View File

@ -22,7 +22,6 @@ import { assert } from 'tsafe';
import { CanvasBackground } from './CanvasBackground'; import { CanvasBackground } from './CanvasBackground';
import { CanvasBbox } from './CanvasBbox'; import { CanvasBbox } from './CanvasBbox';
import { CanvasControlAdapter } from './CanvasControlAdapter'; import { CanvasControlAdapter } from './CanvasControlAdapter';
import { CanvasDocumentSizeOverlay } from './CanvasDocumentSizeOverlay';
import { CanvasInpaintMask } from './CanvasInpaintMask'; import { CanvasInpaintMask } from './CanvasInpaintMask';
import { CanvasLayer } from './CanvasLayer'; import { CanvasLayer } from './CanvasLayer';
import { CanvasPreview } from './CanvasPreview'; import { CanvasPreview } from './CanvasPreview';
@ -92,7 +91,6 @@ export class CanvasManager {
this.preview = new CanvasPreview( this.preview = new CanvasPreview(
new CanvasBbox(this), new CanvasBbox(this),
new CanvasTool(this), new CanvasTool(this),
new CanvasDocumentSizeOverlay(this),
new CanvasStagingArea(this), new CanvasStagingArea(this),
new CanvasProgressPreview(this) new CanvasProgressPreview(this)
); );
@ -221,7 +219,6 @@ export class CanvasManager {
scale: this.stage.scaleX(), scale: this.stage.scaleX(),
}); });
this.background.render(); this.background.render();
this.preview.documentSizeOverlay.render();
} }
render = async () => { render = async () => {
@ -245,7 +242,7 @@ export class CanvasManager {
if ( if (
this.isFirstRender || this.isFirstRender ||
state.initialImage !== this.prevState.initialImage || state.initialImage !== this.prevState.initialImage ||
state.document !== this.prevState.document || state.bbox.rect !== this.prevState.bbox.rect ||
state.tool.selected !== this.prevState.tool.selected || state.tool.selected !== this.prevState.tool.selected ||
state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id state.selectedEntityIdentifier?.id !== this.prevState.selectedEntityIdentifier?.id
) { ) {
@ -285,11 +282,6 @@ export class CanvasManager {
await this.renderControlAdapters(); await this.renderControlAdapters();
} }
if (this.isFirstRender || state.document !== this.prevState.document) {
log.debug('Rendering document bounds overlay');
await this.preview.documentSizeOverlay.render();
}
if ( if (
this.isFirstRender || this.isFirstRender ||
state.bbox !== this.prevState.bbox || state.bbox !== this.prevState.bbox ||
@ -367,9 +359,6 @@ export class CanvasManager {
}); });
log.debug('First render of konva stage'); log.debug('First render of konva stage');
// On first render, the document should be fit to the stage.
this.preview.documentSizeOverlay.render();
this.preview.documentSizeOverlay.fitToStage();
this.preview.tool.render(); this.preview.tool.render();
this.render(); this.render();

View File

@ -2,7 +2,6 @@ import type { CanvasProgressPreview } from 'features/controlLayers/konva/CanvasP
import Konva from 'konva'; import Konva from 'konva';
import type { CanvasBbox } from './CanvasBbox'; import type { CanvasBbox } from './CanvasBbox';
import type { CanvasDocumentSizeOverlay } from './CanvasDocumentSizeOverlay';
import type { CanvasStagingArea } from './CanvasStagingArea'; import type { CanvasStagingArea } from './CanvasStagingArea';
import type { CanvasTool } from './CanvasTool'; import type { CanvasTool } from './CanvasTool';
@ -10,22 +9,17 @@ export class CanvasPreview {
layer: Konva.Layer; layer: Konva.Layer;
tool: CanvasTool; tool: CanvasTool;
bbox: CanvasBbox; bbox: CanvasBbox;
documentSizeOverlay: CanvasDocumentSizeOverlay;
stagingArea: CanvasStagingArea; stagingArea: CanvasStagingArea;
progressPreview: CanvasProgressPreview; progressPreview: CanvasProgressPreview;
constructor( constructor(
bbox: CanvasBbox, bbox: CanvasBbox,
tool: CanvasTool, tool: CanvasTool,
documentSizeOverlay: CanvasDocumentSizeOverlay,
stagingArea: CanvasStagingArea, stagingArea: CanvasStagingArea,
progressPreview: CanvasProgressPreview progressPreview: CanvasProgressPreview
) { ) {
this.layer = new Konva.Layer({ listening: true, imageSmoothingEnabled: false }); this.layer = new Konva.Layer({ listening: true, imageSmoothingEnabled: false });
this.documentSizeOverlay = documentSizeOverlay;
this.layer.add(this.documentSizeOverlay.group);
this.stagingArea = stagingArea; this.stagingArea = stagingArea;
this.layer.add(this.stagingArea.group); this.layer.add(this.stagingArea.group);

View File

@ -207,9 +207,6 @@ export class CanvasStateApi {
getBbox = () => { getBbox = () => {
return this.getState().bbox; return this.getState().bbox;
}; };
getDocument = () => {
return this.getState().document;
};
getToolState = () => { getToolState = () => {
return this.getState().tool; return this.getState().tool;
}; };

View File

@ -137,14 +137,14 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
function getClip(entity: RegionEntity | LayerEntity | InpaintMaskEntity) { function getClip(entity: RegionEntity | LayerEntity | InpaintMaskEntity) {
const settings = getSettings(); const settings = getSettings();
const bbox = getBbox(); const bboxRect = getBbox().rect;
if (settings.clipToBbox) { if (settings.clipToBbox) {
return { return {
x: bbox.x - entity.x, x: bboxRect.x - entity.x,
y: bbox.y - entity.y, y: bboxRect.y - entity.y,
width: bbox.width, width: bboxRect.width,
height: bbox.height, height: bboxRect.height,
}; };
} else { } else {
return { return {
@ -486,7 +486,6 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
stage.position(newPos); stage.position(newPos);
setStageAttrs({ ...newPos, width: stage.width(), height: stage.height(), scale: newScale }); setStageAttrs({ ...newPos, width: stage.width(), height: stage.height(), scale: newScale });
manager.background.render(); manager.background.render();
manager.preview.documentSizeOverlay.render();
} }
} }
manager.preview.tool.render(); manager.preview.tool.render();
@ -502,7 +501,6 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
scale: stage.scaleX(), scale: stage.scaleX(),
}); });
manager.background.render(); manager.background.render();
manager.preview.documentSizeOverlay.render();
manager.preview.tool.render(); manager.preview.tool.render();
}); });
@ -540,9 +538,8 @@ export const setStageEventHandlers = (manager: CanvasManager): (() => void) => {
} else if (e.key === 'r') { } else if (e.key === 'r') {
setLastCursorPos(null); setLastCursorPos(null);
setLastMouseDownPos(null); setLastMouseDownPos(null);
manager.preview.documentSizeOverlay.fitToStage();
manager.background.render(); manager.background.render();
manager.preview.documentSizeOverlay.render(); // TODO(psyche): restore some kind of fit
} }
manager.preview.tool.render(); manager.preview.tool.render();
}; };

View File

@ -1,12 +1,17 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import { deepClone } from 'common/util/deepClone';
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
import type { BoundingBoxScaleMethod, CanvasV2State, Size } from 'features/controlLayers/store/types'; import type { BoundingBoxScaleMethod, CanvasV2State, Size } from 'features/controlLayers/store/types';
import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions'; import { getScaledBoundingBoxDimensions } from 'features/controlLayers/util/getScaledBoundingBoxDimensions';
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
import { ASPECT_RATIO_MAP, initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants';
import type { AspectRatioID } from 'features/parameters/components/DocumentSize/types';
import { getOptimalDimension } from 'features/parameters/util/optimalDimension'; import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
import type { IRect } from 'konva/lib/types'; import type { IRect } from 'konva/lib/types';
import { pick } from 'lodash-es'; import { pick } from 'lodash-es';
export const bboxReducers = { export const bboxReducers = {
scaledBboxChanged: (state, action: PayloadAction<Partial<Size>>) => { bboxScaledSizeChanged: (state, action: PayloadAction<Partial<Size>>) => {
state.layers.imageCache = null; state.layers.imageCache = null;
state.bbox.scaledSize = { ...state.bbox.scaledSize, ...action.payload }; state.bbox.scaledSize = { ...state.bbox.scaledSize, ...action.payload };
}, },
@ -30,4 +35,116 @@ export const bboxReducers = {
state.bbox.scaledSize = getScaledBoundingBoxDimensions(size, optimalDimension); state.bbox.scaledSize = getScaledBoundingBoxDimensions(size, optimalDimension);
} }
}, },
bboxWidthChanged: (state, action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }>) => {
const { width, updateAspectRatio, clamp } = action.payload;
state.bbox.rect.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width;
if (state.bbox.aspectRatio.isLocked) {
state.bbox.rect.height = roundToMultiple(state.bbox.rect.width / state.bbox.aspectRatio.value, 8);
}
if (updateAspectRatio || !state.bbox.aspectRatio.isLocked) {
state.bbox.aspectRatio.value = state.bbox.rect.width / state.bbox.rect.height;
state.bbox.aspectRatio.id = 'Free';
state.bbox.aspectRatio.isLocked = false;
}
if (!state.session.isActive) {
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.bbox.rect.width;
state.initialImage.imageObject.height = state.bbox.rect.height;
}
}
},
bboxHeightChanged: (
state,
action: PayloadAction<{ height: number; updateAspectRatio?: boolean; clamp?: boolean }>
) => {
const { height, updateAspectRatio, clamp } = action.payload;
state.bbox.rect.height = clamp ? Math.max(roundDownToMultiple(height, 8), 64) : height;
if (state.bbox.aspectRatio.isLocked) {
state.bbox.rect.width = roundToMultiple(state.bbox.rect.height * state.bbox.aspectRatio.value, 8);
}
if (updateAspectRatio || !state.bbox.aspectRatio.isLocked) {
state.bbox.aspectRatio.value = state.bbox.rect.width / state.bbox.rect.height;
state.bbox.aspectRatio.id = 'Free';
state.bbox.aspectRatio.isLocked = false;
}
if (!state.session.isActive) {
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.bbox.rect.width;
state.initialImage.imageObject.height = state.bbox.rect.height;
}
}
},
bboxAspectRatioLockToggled: (state) => {
state.bbox.aspectRatio.isLocked = !state.bbox.aspectRatio.isLocked;
},
bboxAspectRatioIdChanged: (state, action: PayloadAction<{ id: AspectRatioID }>) => {
const { id } = action.payload;
state.bbox.aspectRatio.id = id;
if (id === 'Free') {
state.bbox.aspectRatio.isLocked = false;
} else {
state.bbox.aspectRatio.isLocked = true;
state.bbox.aspectRatio.value = ASPECT_RATIO_MAP[id].ratio;
const { width, height } = calculateNewSize(
state.bbox.aspectRatio.value,
state.bbox.rect.width * state.bbox.rect.height
);
state.bbox.rect.width = width;
state.bbox.rect.height = height;
}
if (!state.session.isActive) {
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.bbox.rect.width;
state.initialImage.imageObject.height = state.bbox.rect.height;
}
}
},
bboxDimensionsSwapped: (state) => {
state.bbox.aspectRatio.value = 1 / state.bbox.aspectRatio.value;
if (state.bbox.aspectRatio.id === 'Free') {
const newWidth = state.bbox.rect.height;
const newHeight = state.bbox.rect.width;
state.bbox.rect.width = newWidth;
state.bbox.rect.height = newHeight;
} else {
const { width, height } = calculateNewSize(
state.bbox.aspectRatio.value,
state.bbox.rect.width * state.bbox.rect.height
);
state.bbox.rect.width = width;
state.bbox.rect.height = height;
state.bbox.aspectRatio.id = ASPECT_RATIO_MAP[state.bbox.aspectRatio.id].inverseID;
}
if (!state.session.isActive) {
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.bbox.rect.width;
state.initialImage.imageObject.height = state.bbox.rect.height;
}
}
},
bboxSizeOptimized: (state) => {
const optimalDimension = getOptimalDimension(state.params.model);
if (state.bbox.aspectRatio.isLocked) {
const { width, height } = calculateNewSize(state.bbox.aspectRatio.value, optimalDimension ** 2);
state.bbox.rect.width = width;
state.bbox.rect.height = height;
} else {
state.bbox.aspectRatio = deepClone(initialAspectRatioState);
state.bbox.rect.width = optimalDimension;
state.bbox.rect.height = optimalDimension;
}
if (!state.session.isActive) {
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.bbox.rect.width;
state.initialImage.imageObject.height = state.bbox.rect.height;
}
}
},
} satisfies SliceCaseReducers<CanvasV2State>; } satisfies SliceCaseReducers<CanvasV2State>;

View File

@ -5,7 +5,6 @@ import { deepClone } from 'common/util/deepClone';
import { bboxReducers } from 'features/controlLayers/store/bboxReducers'; import { bboxReducers } from 'features/controlLayers/store/bboxReducers';
import { compositingReducers } from 'features/controlLayers/store/compositingReducers'; import { compositingReducers } from 'features/controlLayers/store/compositingReducers';
import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers'; import { controlAdaptersReducers } from 'features/controlLayers/store/controlAdaptersReducers';
import { documentReducers } from 'features/controlLayers/store/documentReducers';
import { initialImageReducers } from 'features/controlLayers/store/initialImageReducers'; import { initialImageReducers } from 'features/controlLayers/store/initialImageReducers';
import { inpaintMaskReducers } from 'features/controlLayers/store/inpaintMaskReducers'; import { inpaintMaskReducers } from 'features/controlLayers/store/inpaintMaskReducers';
import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers'; import { ipAdaptersReducers } from 'features/controlLayers/store/ipAdaptersReducers';
@ -63,12 +62,9 @@ const initialState: CanvasV2State = {
width: 50, width: 50,
}, },
}, },
document: {
rect: { x: 0, y: 0, width: 512, height: 512 },
aspectRatio: deepClone(initialAspectRatioState),
},
bbox: { bbox: {
rect: { x: 0, y: 0, width: 512, height: 512 }, rect: { x: 0, y: 0, width: 512, height: 512 },
aspectRatio: deepClone(initialAspectRatioState),
scaleMethod: 'auto', scaleMethod: 'auto',
scaledSize: { scaledSize: {
width: 512, width: 512,
@ -149,7 +145,6 @@ export const canvasV2Slice = createSlice({
...bboxReducers, ...bboxReducers,
...inpaintMaskReducers, ...inpaintMaskReducers,
...sessionReducers, ...sessionReducers,
...documentReducers,
...initialImageReducers, ...initialImageReducers,
entitySelected: (state, action: PayloadAction<CanvasEntityIdentifier>) => { entitySelected: (state, action: PayloadAction<CanvasEntityIdentifier>) => {
state.selectedEntityIdentifier = action.payload; state.selectedEntityIdentifier = action.payload;
@ -164,7 +159,6 @@ export const canvasV2Slice = createSlice({
canvasReset: (state) => { canvasReset: (state) => {
state.bbox = deepClone(initialState.bbox); state.bbox = deepClone(initialState.bbox);
state.controlAdapters = deepClone(initialState.controlAdapters); state.controlAdapters = deepClone(initialState.controlAdapters);
state.document = deepClone(initialState.document);
state.ipAdapters = deepClone(initialState.ipAdapters); state.ipAdapters = deepClone(initialState.ipAdapters);
state.layers = deepClone(initialState.layers); state.layers = deepClone(initialState.layers);
state.regions = deepClone(initialState.regions); state.regions = deepClone(initialState.regions);
@ -178,7 +172,6 @@ export const canvasV2Slice = createSlice({
}); });
export const { export const {
bboxChanged,
brushWidthChanged, brushWidthChanged,
eraserWidthChanged, eraserWidthChanged,
fillChanged, fillChanged,
@ -188,17 +181,18 @@ export const {
maskOpacityChanged, maskOpacityChanged,
entitySelected, entitySelected,
allEntitiesDeleted, allEntitiesDeleted,
scaledBboxChanged,
bboxScaleMethodChanged,
clipToBboxChanged, clipToBboxChanged,
canvasReset, canvasReset,
// document // bbox
documentWidthChanged, bboxChanged,
documentHeightChanged, bboxScaledSizeChanged,
documentAspectRatioLockToggled, bboxScaleMethodChanged,
documentAspectRatioIdChanged, bboxWidthChanged,
documentDimensionsSwapped, bboxHeightChanged,
documentSizeOptimized, bboxAspectRatioLockToggled,
bboxAspectRatioIdChanged,
bboxDimensionsSwapped,
bboxSizeOptimized,
// layers // layers
layerAdded, layerAdded,
layerRecalled, layerRecalled,

View File

@ -1,141 +0,0 @@
import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit';
import { deepClone } from 'common/util/deepClone';
import { roundDownToMultiple, roundToMultiple } from 'common/util/roundDownToMultiple';
import type { CanvasV2State } from 'features/controlLayers/store/types';
import { calculateNewSize } from 'features/parameters/components/DocumentSize/calculateNewSize';
import { ASPECT_RATIO_MAP, initialAspectRatioState } from 'features/parameters/components/DocumentSize/constants';
import type { AspectRatioID } from 'features/parameters/components/DocumentSize/types';
import { getOptimalDimension } from 'features/parameters/util/optimalDimension';
export const documentReducers = {
documentWidthChanged: (
state,
action: PayloadAction<{ width: number; updateAspectRatio?: boolean; clamp?: boolean }>
) => {
const { width, updateAspectRatio, clamp } = action.payload;
state.document.rect.width = clamp ? Math.max(roundDownToMultiple(width, 8), 64) : width;
if (state.document.aspectRatio.isLocked) {
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.rect.width / state.document.rect.height;
state.document.aspectRatio.id = 'Free';
state.document.aspectRatio.isLocked = false;
}
if (!state.session.isActive) {
state.bbox.rect.width = state.document.rect.width;
state.bbox.rect.height = state.document.rect.height;
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.document.rect.width;
state.initialImage.imageObject.height = state.document.rect.height;
}
}
},
documentHeightChanged: (
state,
action: PayloadAction<{ height: number; updateAspectRatio?: boolean; clamp?: boolean }>
) => {
const { height, updateAspectRatio, clamp } = action.payload;
state.document.rect.height = clamp ? Math.max(roundDownToMultiple(height, 8), 64) : height;
if (state.document.aspectRatio.isLocked) {
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.rect.width / state.document.rect.height;
state.document.aspectRatio.id = 'Free';
state.document.aspectRatio.isLocked = false;
}
if (!state.session.isActive) {
state.bbox.rect.width = state.document.rect.width;
state.bbox.rect.height = state.document.rect.height;
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.document.rect.width;
state.initialImage.imageObject.height = state.document.rect.height;
}
}
},
documentAspectRatioLockToggled: (state) => {
state.document.aspectRatio.isLocked = !state.document.aspectRatio.isLocked;
},
documentAspectRatioIdChanged: (state, action: PayloadAction<{ id: AspectRatioID }>) => {
const { id } = action.payload;
state.document.aspectRatio.id = id;
if (id === 'Free') {
state.document.aspectRatio.isLocked = false;
} else {
state.document.aspectRatio.isLocked = true;
state.document.aspectRatio.value = ASPECT_RATIO_MAP[id].ratio;
const { width, height } = calculateNewSize(
state.document.aspectRatio.value,
state.document.rect.width * state.document.rect.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;
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.document.rect.width;
state.initialImage.imageObject.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.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.rect.width * state.document.rect.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;
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.document.rect.width;
state.initialImage.imageObject.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.rect.width = width;
state.document.rect.height = height;
} else {
state.document.aspectRatio = deepClone(initialAspectRatioState);
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;
if (state.initialImage.imageObject) {
state.initialImage.imageObject.width = state.document.rect.width;
state.initialImage.imageObject.height = state.document.rect.height;
}
}
},
} satisfies SliceCaseReducers<CanvasV2State>;

View File

@ -62,8 +62,8 @@ export const paramsReducers = {
// Update the bbox size to match the new model's optimal size // Update the bbox size to match the new model's optimal size
// TODO(psyche): Should we change the document size too? // TODO(psyche): Should we change the document size too?
const optimalDimension = getOptimalDimension(model); const optimalDimension = getOptimalDimension(model);
if (!getIsSizeOptimal(state.document.rect.width, state.document.rect.height, optimalDimension)) { if (!getIsSizeOptimal(state.bbox.rect.width, state.bbox.rect.height, optimalDimension)) {
const bboxDims = calculateNewSize(state.document.aspectRatio.value, optimalDimension * optimalDimension); const bboxDims = calculateNewSize(state.bbox.aspectRatio.value, optimalDimension * optimalDimension);
state.bbox.rect.width = bboxDims.width; state.bbox.rect.width = bboxDims.width;
state.bbox.rect.height = bboxDims.height; state.bbox.rect.height = bboxDims.height;

View File

@ -847,15 +847,6 @@ export type CanvasV2State = {
eraser: { width: number }; eraser: { width: number };
fill: RgbaColor; fill: RgbaColor;
}; };
document: {
rect: {
x: number;
y: number;
width: ParameterWidth;
height: ParameterHeight;
};
aspectRatio: AspectRatioState;
};
settings: { settings: {
imageSmoothing: boolean; imageSmoothing: boolean;
maskOpacity: number; maskOpacity: number;
@ -872,6 +863,7 @@ export type CanvasV2State = {
width: ParameterWidth; width: ParameterWidth;
height: ParameterHeight; height: ParameterHeight;
}; };
aspectRatio: AspectRatioState;
scaledSize: { scaledSize: {
width: ParameterWidth; width: ParameterWidth;
height: ParameterHeight; height: ParameterHeight;

View File

@ -11,9 +11,9 @@ import {
getRGId, getRGId,
} from 'features/controlLayers/konva/naming'; } from 'features/controlLayers/konva/naming';
import { import {
bboxHeightChanged,
bboxWidthChanged,
caRecalled, caRecalled,
documentHeightChanged,
documentWidthChanged,
ipaRecalled, ipaRecalled,
layerAllDeleted, layerAllDeleted,
layerRecalled, layerRecalled,
@ -115,11 +115,11 @@ const recallScheduler: MetadataRecallFunc<ParameterScheduler> = (scheduler) => {
const setSizeOptions = { updateAspectRatio: true, clamp: true }; const setSizeOptions = { updateAspectRatio: true, clamp: true };
const recallWidth: MetadataRecallFunc<ParameterWidth> = (width) => { const recallWidth: MetadataRecallFunc<ParameterWidth> = (width) => {
getStore().dispatch(documentWidthChanged({ width, ...setSizeOptions })); getStore().dispatch(bboxWidthChanged({ width, ...setSizeOptions }));
}; };
const recallHeight: MetadataRecallFunc<ParameterHeight> = (height) => { const recallHeight: MetadataRecallFunc<ParameterHeight> = (height) => {
getStore().dispatch(documentHeightChanged({ height, ...setSizeOptions })); getStore().dispatch(bboxHeightChanged({ height, ...setSizeOptions }));
}; };
const recallSteps: MetadataRecallFunc<ParameterSteps> = (steps) => { const recallSteps: MetadataRecallFunc<ParameterSteps> = (steps) => {

View File

@ -1,6 +1,6 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { scaledBboxChanged } from 'features/controlLayers/store/canvasV2Slice'; import { bboxScaledSizeChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -20,7 +20,7 @@ const ParamScaledHeight = () => {
const onChange = useCallback( const onChange = useCallback(
(height: number) => { (height: number) => {
dispatch(scaledBboxChanged({ height })); dispatch(bboxScaledSizeChanged({ height }));
}, },
[dispatch] [dispatch]
); );

View File

@ -1,6 +1,6 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { scaledBboxChanged } from 'features/controlLayers/store/canvasV2Slice'; import { bboxScaledSizeChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -19,7 +19,7 @@ const ParamScaledWidth = () => {
const fineStep = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.fineStep); const fineStep = useAppSelector((s) => s.config.sd.scaledBoundingBoxWidth.fineStep);
const onChange = useCallback( const onChange = useCallback(
(width: number) => { (width: number) => {
dispatch(scaledBboxChanged({ width })); dispatch(bboxScaledSizeChanged({ width }));
}, },
[dispatch] [dispatch]
); );

View File

@ -1,7 +1,7 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { documentHeightChanged } from 'features/controlLayers/store/canvasV2Slice'; import { bboxHeightChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -10,7 +10,7 @@ export const ParamHeight = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const height = useAppSelector((s) => s.canvasV2.document.rect.height); const height = useAppSelector((s) => s.canvasV2.bbox.rect.height);
const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin); const sliderMin = useAppSelector((s) => s.config.sd.height.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax); const sliderMax = useAppSelector((s) => s.config.sd.height.sliderMax);
const numberInputMin = useAppSelector((s) => s.config.sd.height.numberInputMin); const numberInputMin = useAppSelector((s) => s.config.sd.height.numberInputMin);
@ -20,7 +20,7 @@ export const ParamHeight = memo(() => {
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
dispatch(documentHeightChanged({ height: v })); dispatch(bboxHeightChanged({ height: v }));
}, },
[dispatch] [dispatch]
); );

View File

@ -1,7 +1,7 @@
import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library'; import { CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { documentWidthChanged } from 'features/controlLayers/store/canvasV2Slice'; import { bboxWidthChanged } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
export const ParamWidth = memo(() => { export const ParamWidth = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const width = useAppSelector((s) => s.canvasV2.document.rect.width); const width = useAppSelector((s) => s.canvasV2.bbox.rect.width);
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin); const sliderMin = useAppSelector((s) => s.config.sd.width.sliderMin);
const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax); const sliderMax = useAppSelector((s) => s.config.sd.width.sliderMax);
@ -20,7 +20,7 @@ export const ParamWidth = memo(() => {
const onChange = useCallback( const onChange = useCallback(
(v: number) => { (v: number) => {
dispatch(documentWidthChanged({ width: v })); dispatch(bboxWidthChanged({ width: v }));
}, },
[dispatch] [dispatch]
); );

View File

@ -16,13 +16,13 @@ import {
} from './constants'; } from './constants';
export const AspectRatioIconPreview = memo(() => { export const AspectRatioIconPreview = memo(() => {
const document = useAppSelector((s) => s.canvasV2.document); const bbox = useAppSelector((s) => s.canvasV2.bbox);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const containerSize = useSize(containerRef); const containerSize = useSize(containerRef);
const shouldShowIcon = useMemo( const shouldShowIcon = useMemo(
() => document.aspectRatio.value < ICON_HIGH_CUTOFF && document.aspectRatio.value > ICON_LOW_CUTOFF, () => bbox.aspectRatio.value < ICON_HIGH_CUTOFF && bbox.aspectRatio.value > ICON_LOW_CUTOFF,
[document.aspectRatio.value] [bbox.aspectRatio.value]
); );
const { width, height } = useMemo(() => { const { width, height } = useMemo(() => {
@ -30,19 +30,19 @@ export const AspectRatioIconPreview = memo(() => {
return { width: 0, height: 0 }; return { width: 0, height: 0 };
} }
let width = document.rect.width; let width = bbox.rect.width;
let height = document.rect.height; let height = bbox.rect.height;
if (document.rect.width > document.rect.height) { if (bbox.rect.width > bbox.rect.height) {
width = containerSize.width; width = containerSize.width;
height = width / document.aspectRatio.value; height = width / bbox.aspectRatio.value;
} else { } else {
height = containerSize.height; height = containerSize.height;
width = height * document.aspectRatio.value; width = height * bbox.aspectRatio.value;
} }
return { width, height }; return { width, height };
}, [containerSize, document.rect.width, document.rect.height, document.aspectRatio.value]); }, [containerSize, bbox.rect.width, bbox.rect.height, bbox.aspectRatio.value]);
return ( return (
<Flex w="full" h="full" alignItems="center" justifyContent="center" ref={containerRef}> <Flex w="full" h="full" alignItems="center" justifyContent="center" ref={containerRef}>

View File

@ -3,7 +3,7 @@ import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import type { SingleValue } from 'chakra-react-select'; import type { SingleValue } from 'chakra-react-select';
import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover'; import { InformationalPopover } from 'common/components/InformationalPopover/InformationalPopover';
import { documentAspectRatioIdChanged } from 'features/controlLayers/store/canvasV2Slice'; import { bboxAspectRatioIdChanged } from 'features/controlLayers/store/canvasV2Slice';
import { ASPECT_RATIO_OPTIONS } from 'features/parameters/components/DocumentSize/constants'; import { ASPECT_RATIO_OPTIONS } from 'features/parameters/components/DocumentSize/constants';
import { isAspectRatioID } from 'features/parameters/components/DocumentSize/types'; import { isAspectRatioID } from 'features/parameters/components/DocumentSize/types';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
@ -12,14 +12,14 @@ import { useTranslation } from 'react-i18next';
export const AspectRatioSelect = memo(() => { export const AspectRatioSelect = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const id = useAppSelector((s) => s.canvasV2.document.aspectRatio.id); const id = useAppSelector((s) => s.canvasV2.bbox.aspectRatio.id);
const onChange = useCallback( const onChange = useCallback(
(v: SingleValue<ComboboxOption>) => { (v: SingleValue<ComboboxOption>) => {
if (!v || !isAspectRatioID(v.value)) { if (!v || !isAspectRatioID(v.value)) {
return; return;
} }
dispatch(documentAspectRatioIdChanged({ id: v.value })); dispatch(bboxAspectRatioIdChanged({ id: v.value }));
}, },
[dispatch] [dispatch]
); );

View File

@ -1,6 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { documentAspectRatioLockToggled } from 'features/controlLayers/store/canvasV2Slice'; import { bboxAspectRatioLockToggled } from 'features/controlLayers/store/canvasV2Slice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi'; import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi';
@ -8,9 +8,9 @@ import { PiLockSimpleFill, PiLockSimpleOpenBold } from 'react-icons/pi';
export const LockAspectRatioButton = memo(() => { export const LockAspectRatioButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const isLocked = useAppSelector((s) => s.canvasV2.document.aspectRatio.isLocked); const isLocked = useAppSelector((s) => s.canvasV2.bbox.aspectRatio.isLocked);
const onClick = useCallback(() => { const onClick = useCallback(() => {
dispatch(documentAspectRatioLockToggled()); dispatch(bboxAspectRatioLockToggled());
}, [dispatch]); }, [dispatch]);
return ( return (

View File

@ -1,6 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { documentSizeOptimized } from 'features/controlLayers/store/canvasV2Slice'; import { bboxSizeOptimized } from 'features/controlLayers/store/canvasV2Slice';
import { selectOptimalDimension } from 'features/controlLayers/store/selectors'; import { selectOptimalDimension } from 'features/controlLayers/store/selectors';
import { getIsSizeTooLarge, getIsSizeTooSmall } from 'features/parameters/util/optimalDimension'; import { getIsSizeTooLarge, getIsSizeTooSmall } from 'features/parameters/util/optimalDimension';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
@ -10,8 +10,8 @@ import { RiSparklingFill } from 'react-icons/ri';
export const SetOptimalSizeButton = memo(() => { export const SetOptimalSizeButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const width = useAppSelector((s) => s.canvasV2.document.rect.width); const width = useAppSelector((s) => s.canvasV2.bbox.rect.width);
const height = useAppSelector((s) => s.canvasV2.document.rect.height); const height = useAppSelector((s) => s.canvasV2.bbox.rect.height);
const optimalDimension = useAppSelector(selectOptimalDimension); const optimalDimension = useAppSelector(selectOptimalDimension);
const isSizeTooSmall = useMemo( const isSizeTooSmall = useMemo(
() => getIsSizeTooSmall(width, height, optimalDimension), () => getIsSizeTooSmall(width, height, optimalDimension),
@ -22,7 +22,7 @@ export const SetOptimalSizeButton = memo(() => {
[height, width, optimalDimension] [height, width, optimalDimension]
); );
const onClick = useCallback(() => { const onClick = useCallback(() => {
dispatch(documentSizeOptimized()); dispatch(bboxSizeOptimized());
}, [dispatch]); }, [dispatch]);
const tooltip = useMemo(() => { const tooltip = useMemo(() => {
if (isSizeTooSmall) { if (isSizeTooSmall) {

View File

@ -1,6 +1,6 @@
import { IconButton } from '@invoke-ai/ui-library'; import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { documentDimensionsSwapped } from 'features/controlLayers/store/canvasV2Slice'; import { bboxDimensionsSwapped } from 'features/controlLayers/store/canvasV2Slice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PiArrowsDownUpBold } from 'react-icons/pi'; import { PiArrowsDownUpBold } from 'react-icons/pi';
@ -9,7 +9,7 @@ export const SwapDimensionsButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const onClick = useCallback(() => { const onClick = useCallback(() => {
dispatch(documentDimensionsSwapped()); dispatch(bboxDimensionsSwapped());
}, [dispatch]); }, [dispatch]);
return ( return (
<IconButton <IconButton

View File

@ -24,8 +24,8 @@ const selector = createMemoizedSelector([selectHrfSlice, selectCanvasV2Slice], (
const badges: string[] = []; const badges: string[] = [];
const isSDXL = model?.base === 'sdxl'; const isSDXL = model?.base === 'sdxl';
const { aspectRatio } = canvasV2.document; const { aspectRatio } = canvasV2.bbox;
const { width, height } = canvasV2.document.rect; const { width, height } = canvasV2.bbox.rect;
badges.push(`${width}×${height}`); badges.push(`${width}×${height}`);
badges.push(aspectRatio.id); badges.push(aspectRatio.id);