mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): make documnet size a rect
This commit is contained in:
parent
36e94af598
commit
3f9496c237
@ -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(
|
||||
|
@ -25,18 +25,18 @@ export const HeadsUpDisplay = memo(() => {
|
||||
return (
|
||||
<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="Document Size" value={`${document.width}×${document.height} px`} />
|
||||
<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 Size"
|
||||
value={`${round(stageAttrs.width / stageAttrs.scale, 2)}×${round(stageAttrs.height / stageAttrs.scale, 2)} px`}
|
||||
/>
|
||||
<HUDItem label="BBox Size" value={`${bbox.width}×${bbox.height} px`} />
|
||||
<HUDItem label="BBox Position" value={`${bbox.x}, ${bbox.y}`} />
|
||||
<HUDItem label="BBox Width % 8" value={round(bbox.width % 8, 2)} />
|
||||
<HUDItem label="BBox Height % 8" value={round(bbox.height % 8, 2)} />
|
||||
<HUDItem label="BBox X % 8" value={round(bbox.x % 8, 2)} />
|
||||
<HUDItem label="BBox Y % 8" value={round(bbox.y % 8, 2)} />
|
||||
<HUDItem label="BBox Size" value={`${bbox.rect.width}×${bbox.rect.height} px`} />
|
||||
<HUDItem label="BBox Position" value={`${bbox.rect.x}, ${bbox.rect.y}`} />
|
||||
<HUDItem label="BBox Width % 8" value={round(bbox.rect.width % 8, 2)} />
|
||||
<HUDItem label="BBox Height % 8" value={round(bbox.rect.height % 8, 2)} />
|
||||
<HUDItem label="BBox X % 8" value={round(bbox.rect.x % 8, 2)} />
|
||||
<HUDItem label="BBox Y % 8" value={round(bbox.rect.y % 8, 2)} />
|
||||
<HUDItem
|
||||
label="Cursor Position"
|
||||
value={cursorPos ? `${round(cursorPos.x, 2)}, ${round(cursorPos.y, 2)}` : '?, ?'}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { roundToMultiple, roundToMultipleMin } from 'common/util/roundDownToMultiple';
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import type { Rect } from 'features/controlLayers/store/types';
|
||||
import Konva from 'konva';
|
||||
import type { IRect } from 'konva/lib/types';
|
||||
import { atom } from 'nanostores';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
@ -29,7 +29,7 @@ export class CanvasBbox {
|
||||
// Create a stash to hold onto the last aspect ratio of the bbox - this allows for locking the aspect ratio when
|
||||
// transforming the bbox.
|
||||
const bbox = this.manager.stateApi.getBbox();
|
||||
const $aspectRatioBuffer = atom(bbox.width / bbox.height);
|
||||
const $aspectRatioBuffer = atom(bbox.rect.width / bbox.rect.height);
|
||||
|
||||
// Use a transformer for the generation bbox. Transformers need some shape to transform, we will use a fully
|
||||
// transparent rect for this purpose.
|
||||
@ -42,15 +42,15 @@ export class CanvasBbox {
|
||||
});
|
||||
this.rect.on('dragmove', () => {
|
||||
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',
|
||||
|
@ -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<IRect>) => {
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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<CanvasV2State>;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 };
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 (
|
||||
<Flex w="full" h="full" alignItems="center" justifyContent="center" ref={containerRef}>
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user