feat(ui): make documnet size a rect

This commit is contained in:
psychedelicious 2024-07-08 19:15:51 +10:00
parent 36e94af598
commit 3f9496c237
16 changed files with 96 additions and 81 deletions

View File

@ -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(

View File

@ -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)}` : '?, ?'}

View File

@ -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',

View File

@ -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);
}
},

View File

@ -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,

View File

@ -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>;

View File

@ -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);

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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 };
};

View File

@ -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);

View File

@ -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);

View File

@ -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}>

View File

@ -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),

View File

@ -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);