mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add optimal size handling
This commit is contained in:
committed by
Kent Keirsey
parent
1a4be78013
commit
4fdc4c15f9
@ -26,6 +26,8 @@ import {
|
||||
CANVAS_GRID_SIZE_COARSE,
|
||||
CANVAS_GRID_SIZE_FINE,
|
||||
} from 'features/canvas/store/constants';
|
||||
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
|
||||
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
|
||||
import type Konva from 'konva';
|
||||
import type { GroupConfig } from 'konva/lib/Group';
|
||||
import type { KonvaEventObject } from 'konva/lib/Node';
|
||||
@ -37,8 +39,8 @@ import { Group, Rect, Transformer } from 'react-konva';
|
||||
const borderDash = [4, 4];
|
||||
|
||||
const boundingBoxPreviewSelector = createMemoizedSelector(
|
||||
[stateSelector],
|
||||
({ canvas }) => {
|
||||
[stateSelector, selectOptimalDimension],
|
||||
({ canvas }, optimalDimension) => {
|
||||
const {
|
||||
boundingBoxCoordinates,
|
||||
boundingBoxDimensions,
|
||||
@ -56,6 +58,7 @@ const boundingBoxPreviewSelector = createMemoizedSelector(
|
||||
tool,
|
||||
hitStrokeWidth: 20 / stageScale,
|
||||
aspectRatio,
|
||||
optimalDimension,
|
||||
};
|
||||
}
|
||||
);
|
||||
@ -73,6 +76,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
tool,
|
||||
hitStrokeWidth,
|
||||
aspectRatio,
|
||||
optimalDimension,
|
||||
} = useAppSelector(boundingBoxPreviewSelector);
|
||||
|
||||
const transformerRef = useRef<Konva.Transformer>(null);
|
||||
@ -163,22 +167,25 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
const y = Math.round(rect.y());
|
||||
|
||||
if (aspectRatio.isLocked) {
|
||||
const newHeight = roundDownToMultipleMin(
|
||||
width / aspectRatio.value,
|
||||
gridSize
|
||||
);
|
||||
const newDimensions = calculateNewSize(aspectRatio.value, width * height);
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
width: roundDownToMultipleMin(width, gridSize),
|
||||
height: newHeight,
|
||||
})
|
||||
setBoundingBoxDimensions(
|
||||
{
|
||||
width: roundDownToMultipleMin(newDimensions.width, gridSize),
|
||||
height: roundDownToMultipleMin(newDimensions.height, gridSize),
|
||||
},
|
||||
optimalDimension
|
||||
)
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
setBoundingBoxDimensions({
|
||||
width: roundDownToMultipleMin(width, gridSize),
|
||||
height: roundDownToMultipleMin(height, gridSize),
|
||||
})
|
||||
setBoundingBoxDimensions(
|
||||
{
|
||||
width: roundDownToMultipleMin(width, gridSize),
|
||||
height: roundDownToMultipleMin(height, gridSize),
|
||||
},
|
||||
optimalDimension
|
||||
)
|
||||
);
|
||||
dispatch(
|
||||
aspectRatioChanged({
|
||||
@ -205,6 +212,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
dispatch,
|
||||
shouldSnapToGrid,
|
||||
gridSize,
|
||||
optimalDimension,
|
||||
]);
|
||||
|
||||
const anchorDragBoundFunc = useCallback(
|
||||
@ -233,7 +241,6 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
|
||||
x: roundDownToMultiple(newPos.x, scaledStep) + offsetX,
|
||||
y: roundDownToMultiple(newPos.y, scaledStep) + offsetY,
|
||||
};
|
||||
console.log({ oldPos, newPos, newCoordinates });
|
||||
|
||||
return newCoordinates;
|
||||
},
|
||||
|
@ -11,6 +11,12 @@ import floorCoordinates from 'features/canvas/util/floorCoordinates';
|
||||
import getScaledBoundingBoxDimensions from 'features/canvas/util/getScaledBoundingBoxDimensions';
|
||||
import roundDimensionsToMultiple from 'features/canvas/util/roundDimensionsToMultiple';
|
||||
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
|
||||
import { modelChanged } from 'features/parameters/store/generationSlice';
|
||||
import type { PayloadActionWithOptimalDimension } from 'features/parameters/store/types';
|
||||
import {
|
||||
getIsSizeOptimal,
|
||||
getOptimalDimension,
|
||||
} from 'features/parameters/util/optimalDimension';
|
||||
import type { IRect, Vector2d } from 'konva/lib/types';
|
||||
import { clamp, cloneDeep } from 'lodash-es';
|
||||
import type { RgbaColor } from 'react-colorful';
|
||||
@ -86,6 +92,29 @@ export const initialCanvasState: CanvasState = {
|
||||
},
|
||||
};
|
||||
|
||||
const setBoundingBoxDimensionsReducer = (
|
||||
state: CanvasState,
|
||||
payload: Partial<Dimensions>,
|
||||
optimalDimension: number
|
||||
) => {
|
||||
const boundingBoxDimensions = payload;
|
||||
const newDimensions = roundDimensionsToMultiple(
|
||||
{
|
||||
...state.boundingBoxDimensions,
|
||||
...boundingBoxDimensions,
|
||||
},
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
);
|
||||
state.boundingBoxDimensions = newDimensions;
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newDimensions,
|
||||
optimalDimension
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
};
|
||||
|
||||
export const canvasSlice = createSlice({
|
||||
name: 'canvas',
|
||||
initialState: initialCanvasState,
|
||||
@ -132,117 +161,90 @@ export const canvasSlice = createSlice({
|
||||
state.isMaskEnabled = action.payload;
|
||||
state.layer = action.payload ? 'mask' : 'base';
|
||||
},
|
||||
setInitialCanvasImage: (state, action: PayloadAction<ImageDTO>) => {
|
||||
const image = action.payload;
|
||||
const { width, height } = image;
|
||||
const { stageDimensions } = state;
|
||||
setInitialCanvasImage: {
|
||||
reducer: (state, action: PayloadActionWithOptimalDimension<ImageDTO>) => {
|
||||
const { width, height, image_name } = action.payload;
|
||||
const { optimalDimension } = action.meta;
|
||||
const { stageDimensions } = state;
|
||||
|
||||
const newBoundingBoxDimensions = {
|
||||
width: roundDownToMultiple(
|
||||
clamp(width, CANVAS_GRID_SIZE_FINE, 512),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
height: roundDownToMultiple(
|
||||
clamp(height, CANVAS_GRID_SIZE_FINE, 512),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
const newBoundingBoxDimensions = {
|
||||
width: roundDownToMultiple(
|
||||
clamp(width, CANVAS_GRID_SIZE_FINE, optimalDimension),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
height: roundDownToMultiple(
|
||||
clamp(height, CANVAS_GRID_SIZE_FINE, optimalDimension),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
|
||||
const newBoundingBoxCoordinates = {
|
||||
x: roundToMultiple(
|
||||
width / 2 - newBoundingBoxDimensions.width / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
y: roundToMultiple(
|
||||
height / 2 - newBoundingBoxDimensions.height / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
const newBoundingBoxCoordinates = {
|
||||
x: roundToMultiple(
|
||||
width / 2 - newBoundingBoxDimensions.width / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
y: roundToMultiple(
|
||||
height / 2 - newBoundingBoxDimensions.height / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions,
|
||||
optimalDimension
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
state.boundingBoxCoordinates = newBoundingBoxCoordinates;
|
||||
|
||||
state.pastLayerStates.push(cloneDeep(state.layerState));
|
||||
|
||||
state.layerState = {
|
||||
...cloneDeep(initialLayerState),
|
||||
objects: [
|
||||
{
|
||||
kind: 'image',
|
||||
layer: 'base',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width,
|
||||
height,
|
||||
imageName: image_name,
|
||||
},
|
||||
],
|
||||
};
|
||||
state.futureLayerStates = [];
|
||||
state.batchIds = [];
|
||||
|
||||
const newScale = calculateScale(
|
||||
stageDimensions.width,
|
||||
stageDimensions.height,
|
||||
width,
|
||||
height,
|
||||
STAGE_PADDING_PERCENTAGE
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
state.boundingBoxCoordinates = newBoundingBoxCoordinates;
|
||||
|
||||
state.pastLayerStates.push(cloneDeep(state.layerState));
|
||||
|
||||
state.layerState = {
|
||||
...cloneDeep(initialLayerState),
|
||||
objects: [
|
||||
{
|
||||
kind: 'image',
|
||||
layer: 'base',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: width,
|
||||
height: height,
|
||||
imageName: image.image_name,
|
||||
},
|
||||
],
|
||||
};
|
||||
state.futureLayerStates = [];
|
||||
state.batchIds = [];
|
||||
|
||||
const newScale = calculateScale(
|
||||
stageDimensions.width,
|
||||
stageDimensions.height,
|
||||
width,
|
||||
height,
|
||||
STAGE_PADDING_PERCENTAGE
|
||||
);
|
||||
|
||||
const newCoordinates = calculateCoordinates(
|
||||
stageDimensions.width,
|
||||
stageDimensions.height,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
newScale
|
||||
);
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
},
|
||||
setBoundingBoxDimensions: (
|
||||
state,
|
||||
action: PayloadAction<Partial<Dimensions>>
|
||||
) => {
|
||||
const newDimensions = roundDimensionsToMultiple(
|
||||
{
|
||||
...state.boundingBoxDimensions,
|
||||
...action.payload,
|
||||
const newCoordinates = calculateCoordinates(
|
||||
stageDimensions.width,
|
||||
stageDimensions.height,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
newScale
|
||||
);
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
},
|
||||
prepare: (payload: ImageDTO, optimalDimension: number) => ({
|
||||
payload,
|
||||
meta: {
|
||||
optimalDimension,
|
||||
},
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
);
|
||||
state.boundingBoxDimensions = newDimensions;
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(newDimensions);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
},
|
||||
flipBoundingBoxAxes: (state) => {
|
||||
const [currWidth, currHeight] = [
|
||||
state.boundingBoxDimensions.width,
|
||||
state.boundingBoxDimensions.height,
|
||||
];
|
||||
const [currScaledWidth, currScaledHeight] = [
|
||||
state.scaledBoundingBoxDimensions.width,
|
||||
state.scaledBoundingBoxDimensions.height,
|
||||
];
|
||||
state.boundingBoxDimensions = {
|
||||
width: currHeight,
|
||||
height: currWidth,
|
||||
};
|
||||
state.scaledBoundingBoxDimensions = {
|
||||
width: currScaledHeight,
|
||||
height: currScaledWidth,
|
||||
};
|
||||
}),
|
||||
},
|
||||
setBoundingBoxCoordinates: (state, action: PayloadAction<Vector2d>) => {
|
||||
state.boundingBoxCoordinates = floorCoordinates(action.payload);
|
||||
@ -518,38 +520,6 @@ export const canvasSlice = createSlice({
|
||||
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
} else {
|
||||
const newScale = calculateScale(
|
||||
stageWidth,
|
||||
stageHeight,
|
||||
512,
|
||||
512,
|
||||
STAGE_PADDING_PERCENTAGE
|
||||
);
|
||||
|
||||
const newCoordinates = calculateCoordinates(
|
||||
stageWidth,
|
||||
stageHeight,
|
||||
0,
|
||||
0,
|
||||
512,
|
||||
512,
|
||||
newScale
|
||||
);
|
||||
|
||||
const newBoundingBoxDimensions = { width: 512, height: 512 };
|
||||
|
||||
state.stageScale = newScale;
|
||||
state.stageCoordinates = newCoordinates;
|
||||
state.boundingBoxCoordinates = { x: 0, y: 0 };
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
}
|
||||
},
|
||||
nextStagingAreaImage: (state) => {
|
||||
@ -601,69 +571,29 @@ export const canvasSlice = createSlice({
|
||||
state.shouldShowStagingImage = true;
|
||||
state.batchIds = [];
|
||||
},
|
||||
fitBoundingBoxToStage: (state) => {
|
||||
const {
|
||||
boundingBoxDimensions,
|
||||
boundingBoxCoordinates,
|
||||
stageDimensions,
|
||||
stageScale,
|
||||
} = state;
|
||||
const scaledStageWidth = stageDimensions.width / stageScale;
|
||||
const scaledStageHeight = stageDimensions.height / stageScale;
|
||||
setBoundingBoxScaleMethod: {
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadActionWithOptimalDimension<BoundingBoxScaleMethod>
|
||||
) => {
|
||||
const boundingBoxScaleMethod = action.payload;
|
||||
const { optimalDimension } = action.meta;
|
||||
state.boundingBoxScaleMethod = boundingBoxScaleMethod;
|
||||
|
||||
if (
|
||||
boundingBoxCoordinates.x < 0 ||
|
||||
boundingBoxCoordinates.x + boundingBoxDimensions.width >
|
||||
scaledStageWidth ||
|
||||
boundingBoxCoordinates.y < 0 ||
|
||||
boundingBoxCoordinates.y + boundingBoxDimensions.height >
|
||||
scaledStageHeight
|
||||
) {
|
||||
const newBoundingBoxDimensions = {
|
||||
width: roundDownToMultiple(
|
||||
clamp(scaledStageWidth, CANVAS_GRID_SIZE_FINE, 512),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
height: roundDownToMultiple(
|
||||
clamp(scaledStageHeight, CANVAS_GRID_SIZE_FINE, 512),
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
|
||||
const newBoundingBoxCoordinates = {
|
||||
x: roundToMultiple(
|
||||
scaledStageWidth / 2 - newBoundingBoxDimensions.width / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
y: roundToMultiple(
|
||||
scaledStageHeight / 2 - newBoundingBoxDimensions.height / 2,
|
||||
CANVAS_GRID_SIZE_FINE
|
||||
),
|
||||
};
|
||||
|
||||
state.boundingBoxDimensions = newBoundingBoxDimensions;
|
||||
state.boundingBoxCoordinates = newBoundingBoxCoordinates;
|
||||
|
||||
if (state.boundingBoxScaleMethod === 'auto') {
|
||||
if (boundingBoxScaleMethod === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
newBoundingBoxDimensions
|
||||
state.boundingBoxDimensions,
|
||||
optimalDimension
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
}
|
||||
},
|
||||
setBoundingBoxScaleMethod: (
|
||||
state,
|
||||
action: PayloadAction<BoundingBoxScaleMethod>
|
||||
) => {
|
||||
state.boundingBoxScaleMethod = action.payload;
|
||||
|
||||
if (action.payload === 'auto') {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
state.boundingBoxDimensions
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
}
|
||||
},
|
||||
prepare: (payload: BoundingBoxScaleMethod, optimalDimension: number) => ({
|
||||
payload,
|
||||
meta: {
|
||||
optimalDimension,
|
||||
},
|
||||
}),
|
||||
},
|
||||
setScaledBoundingBoxDimensions: (
|
||||
state,
|
||||
@ -671,6 +601,37 @@ export const canvasSlice = createSlice({
|
||||
) => {
|
||||
state.scaledBoundingBoxDimensions = action.payload;
|
||||
},
|
||||
setBoundingBoxDimensions: {
|
||||
reducer: (
|
||||
state,
|
||||
action: PayloadActionWithOptimalDimension<Partial<Dimensions>>
|
||||
) => {
|
||||
setBoundingBoxDimensionsReducer(
|
||||
state,
|
||||
action.payload,
|
||||
action.meta.optimalDimension
|
||||
);
|
||||
},
|
||||
prepare: (payload: Partial<Dimensions>, optimalDimension: number) => ({
|
||||
payload,
|
||||
meta: {
|
||||
optimalDimension,
|
||||
},
|
||||
}),
|
||||
},
|
||||
scaledBoundingBoxDimensionsReset: {
|
||||
reducer: (state, action: PayloadActionWithOptimalDimension) => {
|
||||
const scaledDimensions = getScaledBoundingBoxDimensions(
|
||||
state.boundingBoxDimensions,
|
||||
action.meta.optimalDimension
|
||||
);
|
||||
state.scaledBoundingBoxDimensions = scaledDimensions;
|
||||
},
|
||||
prepare: (payload: void, optimalDimension: number) => ({
|
||||
payload: undefined,
|
||||
meta: { optimalDimension },
|
||||
}),
|
||||
},
|
||||
setShouldShowStagingImage: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldShowStagingImage = action.payload;
|
||||
},
|
||||
@ -714,6 +675,22 @@ export const canvasSlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(modelChanged, (state, action) => {
|
||||
const optimalDimension = getOptimalDimension(action.payload);
|
||||
const { width, height } = state.boundingBoxDimensions;
|
||||
if (getIsSizeOptimal(width, height, optimalDimension)) {
|
||||
return;
|
||||
}
|
||||
setBoundingBoxDimensionsReducer(
|
||||
state,
|
||||
{
|
||||
width,
|
||||
height,
|
||||
},
|
||||
optimalDimension
|
||||
);
|
||||
});
|
||||
|
||||
builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
|
||||
const batch_status = action.payload.data.batch_status;
|
||||
if (!state.batchIds.includes(batch_status.batch_id)) {
|
||||
@ -754,7 +731,6 @@ export const {
|
||||
commitColorPickerColor,
|
||||
commitStagingAreaImage,
|
||||
discardStagedImages,
|
||||
fitBoundingBoxToStage,
|
||||
nextStagingAreaImage,
|
||||
prevStagingAreaImage,
|
||||
redo,
|
||||
@ -764,7 +740,6 @@ export const {
|
||||
setBoundingBoxDimensions,
|
||||
setBoundingBoxPreviewFill,
|
||||
setBoundingBoxScaleMethod,
|
||||
flipBoundingBoxAxes,
|
||||
setBrushColor,
|
||||
setBrushSize,
|
||||
setColorPickerColor,
|
||||
@ -799,6 +774,7 @@ export const {
|
||||
canvasBatchIdAdded,
|
||||
canvasBatchIdsReset,
|
||||
aspectRatioChanged,
|
||||
scaledBoundingBoxDimensionsReset,
|
||||
} = canvasSlice.actions;
|
||||
|
||||
export default canvasSlice.reducer;
|
||||
|
@ -2,19 +2,22 @@ import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import type { Dimensions } from 'features/canvas/store/canvasTypes';
|
||||
import { CANVAS_GRID_SIZE_FINE } from 'features/canvas/store/constants';
|
||||
|
||||
const getScaledBoundingBoxDimensions = (dimensions: Dimensions) => {
|
||||
const getScaledBoundingBoxDimensions = (
|
||||
dimensions: Dimensions,
|
||||
optimalDimension: number
|
||||
) => {
|
||||
const { width, height } = dimensions;
|
||||
|
||||
const scaledDimensions = { width, height };
|
||||
const targetArea = 512 * 512;
|
||||
const targetArea = optimalDimension * optimalDimension;
|
||||
const aspectRatio = width / height;
|
||||
let currentArea = width * height;
|
||||
let maxDimension = 448;
|
||||
let maxDimension = optimalDimension - CANVAS_GRID_SIZE_FINE;
|
||||
while (currentArea < targetArea) {
|
||||
maxDimension += CANVAS_GRID_SIZE_FINE;
|
||||
if (width === height) {
|
||||
scaledDimensions.width = 512;
|
||||
scaledDimensions.height = 512;
|
||||
scaledDimensions.width = optimalDimension;
|
||||
scaledDimensions.height = optimalDimension;
|
||||
break;
|
||||
} else {
|
||||
if (aspectRatio > 1) {
|
||||
|
Reference in New Issue
Block a user