feat(ui): add optimal size handling

This commit is contained in:
psychedelicious 2024-01-03 23:56:28 +11:00 committed by Kent Keirsey
parent 1a4be78013
commit 4fdc4c15f9
30 changed files with 461 additions and 416 deletions

View File

@ -1079,6 +1079,11 @@
"aspect": "Aspect", "aspect": "Aspect",
"aspectRatio": "Aspect Ratio", "aspectRatio": "Aspect Ratio",
"aspectRatioFree": "Free", "aspectRatioFree": "Free",
"lockAspectRatio": "Lock Aspect Ratio",
"swapDimensions": "Swap Dimensions",
"setToOptimalSize": "Optimize size for model",
"setToOptimalSizeTooSmall": "$t(parameters.setToOptimalSize) (may be too small)",
"setToOptimalSizeTooLarge": "$t(parameters.setToOptimalSize) (may be too large)",
"boundingBoxHeader": "Bounding Box", "boundingBoxHeader": "Bounding Box",
"boundingBoxHeight": "Bounding Box Height", "boundingBoxHeight": "Bounding Box Height",
"boundingBoxWidth": "Bounding Box Width", "boundingBoxWidth": "Bounding Box Width",

View File

@ -13,7 +13,10 @@ import type {
import { imageSelected } from 'features/gallery/store/gallerySlice'; import { imageSelected } from 'features/gallery/store/gallerySlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { workflowExposedFieldAdded } from 'features/nodes/store/workflowSlice'; import { workflowExposedFieldAdded } from 'features/nodes/store/workflowSlice';
import { initialImageChanged } from 'features/parameters/store/generationSlice'; import {
initialImageChanged,
selectOptimalDimension,
} from 'features/parameters/store/generationSlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import { startAppListening } from '../'; import { startAppListening } from '../';
@ -26,7 +29,7 @@ export const dndDropped = createAction<{
export const addImageDroppedListener = () => { export const addImageDroppedListener = () => {
startAppListening({ startAppListening({
actionCreator: dndDropped, actionCreator: dndDropped,
effect: async (action, { dispatch }) => { effect: async (action, { dispatch, getState }) => {
const log = logger('dnd'); const log = logger('dnd');
const { activeData, overData } = action.payload; const { activeData, overData } = action.payload;
@ -115,7 +118,12 @@ export const addImageDroppedListener = () => {
activeData.payloadType === 'IMAGE_DTO' && activeData.payloadType === 'IMAGE_DTO' &&
activeData.payload.imageDTO activeData.payload.imageDTO
) { ) {
dispatch(setInitialCanvasImage(activeData.payload.imageDTO)); dispatch(
setInitialCanvasImage(
activeData.payload.imageDTO,
selectOptimalDimension(getState())
)
);
return; return;
} }

View File

@ -6,7 +6,10 @@ import {
controlAdapterIsEnabledChanged, controlAdapterIsEnabledChanged,
} from 'features/controlAdapters/store/controlAdaptersSlice'; } from 'features/controlAdapters/store/controlAdaptersSlice';
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
import { initialImageChanged } from 'features/parameters/store/generationSlice'; import {
initialImageChanged,
selectOptimalDimension,
} from 'features/parameters/store/generationSlice';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { omit } from 'lodash-es'; import { omit } from 'lodash-es';
@ -76,7 +79,9 @@ export const addImageUploadedFulfilledListener = () => {
} }
if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') { if (postUploadAction?.type === 'SET_CANVAS_INITIAL_IMAGE') {
dispatch(setInitialCanvasImage(imageDTO)); dispatch(
setInitialCanvasImage(imageDTO, selectOptimalDimension(state))
);
dispatch( dispatch(
addToast({ addToast({
...DEFAULT_UPLOADED_TOAST, ...DEFAULT_UPLOADED_TOAST,

View File

@ -1,5 +1,4 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
import { import {
controlAdapterIsEnabledChanged, controlAdapterIsEnabledChanged,
selectControlAdapterAll, selectControlAdapterAll,
@ -7,10 +6,8 @@ import {
import { loraRemoved } from 'features/lora/store/loraSlice'; import { loraRemoved } from 'features/lora/store/loraSlice';
import { modelSelected } from 'features/parameters/store/actions'; import { modelSelected } from 'features/parameters/store/actions';
import { import {
heightChanged,
modelChanged, modelChanged,
vaeSelected, vaeSelected,
widthChanged,
} from 'features/parameters/store/generationSlice'; } from 'features/parameters/store/generationSlice';
import { zParameterModel } from 'features/parameters/types/parameterSchemas'; import { zParameterModel } from 'features/parameters/types/parameterSchemas';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
@ -84,22 +81,6 @@ export const addModelSelectedListener = () => {
} }
} }
// Update Width / Height / Bounding Box Dimensions on Model Change
if (
state.generation.model?.base_model !== newModel.base_model &&
state.ui.shouldAutoChangeDimensions
) {
if (['sdxl', 'sdxl-refiner'].includes(newModel.base_model)) {
dispatch(widthChanged(1024));
dispatch(heightChanged(1024));
dispatch(setBoundingBoxDimensions({ width: 1024, height: 1024 }));
} else {
dispatch(widthChanged(512));
dispatch(heightChanged(512));
dispatch(setBoundingBoxDimensions({ width: 512, height: 512 }));
}
}
dispatch(modelChanged(newModel)); dispatch(modelChanged(newModel));
}, },
}); });

View File

@ -26,6 +26,8 @@ import {
CANVAS_GRID_SIZE_COARSE, CANVAS_GRID_SIZE_COARSE,
CANVAS_GRID_SIZE_FINE, CANVAS_GRID_SIZE_FINE,
} from 'features/canvas/store/constants'; } 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 Konva from 'konva';
import type { GroupConfig } from 'konva/lib/Group'; import type { GroupConfig } from 'konva/lib/Group';
import type { KonvaEventObject } from 'konva/lib/Node'; import type { KonvaEventObject } from 'konva/lib/Node';
@ -37,8 +39,8 @@ import { Group, Rect, Transformer } from 'react-konva';
const borderDash = [4, 4]; const borderDash = [4, 4];
const boundingBoxPreviewSelector = createMemoizedSelector( const boundingBoxPreviewSelector = createMemoizedSelector(
[stateSelector], [stateSelector, selectOptimalDimension],
({ canvas }) => { ({ canvas }, optimalDimension) => {
const { const {
boundingBoxCoordinates, boundingBoxCoordinates,
boundingBoxDimensions, boundingBoxDimensions,
@ -56,6 +58,7 @@ const boundingBoxPreviewSelector = createMemoizedSelector(
tool, tool,
hitStrokeWidth: 20 / stageScale, hitStrokeWidth: 20 / stageScale,
aspectRatio, aspectRatio,
optimalDimension,
}; };
} }
); );
@ -73,6 +76,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
tool, tool,
hitStrokeWidth, hitStrokeWidth,
aspectRatio, aspectRatio,
optimalDimension,
} = useAppSelector(boundingBoxPreviewSelector); } = useAppSelector(boundingBoxPreviewSelector);
const transformerRef = useRef<Konva.Transformer>(null); const transformerRef = useRef<Konva.Transformer>(null);
@ -163,22 +167,25 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
const y = Math.round(rect.y()); const y = Math.round(rect.y());
if (aspectRatio.isLocked) { if (aspectRatio.isLocked) {
const newHeight = roundDownToMultipleMin( const newDimensions = calculateNewSize(aspectRatio.value, width * height);
width / aspectRatio.value,
gridSize
);
dispatch( dispatch(
setBoundingBoxDimensions({ setBoundingBoxDimensions(
width: roundDownToMultipleMin(width, gridSize), {
height: newHeight, width: roundDownToMultipleMin(newDimensions.width, gridSize),
}) height: roundDownToMultipleMin(newDimensions.height, gridSize),
},
optimalDimension
)
); );
} else { } else {
dispatch( dispatch(
setBoundingBoxDimensions({ setBoundingBoxDimensions(
width: roundDownToMultipleMin(width, gridSize), {
height: roundDownToMultipleMin(height, gridSize), width: roundDownToMultipleMin(width, gridSize),
}) height: roundDownToMultipleMin(height, gridSize),
},
optimalDimension
)
); );
dispatch( dispatch(
aspectRatioChanged({ aspectRatioChanged({
@ -205,6 +212,7 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
dispatch, dispatch,
shouldSnapToGrid, shouldSnapToGrid,
gridSize, gridSize,
optimalDimension,
]); ]);
const anchorDragBoundFunc = useCallback( const anchorDragBoundFunc = useCallback(
@ -233,7 +241,6 @@ const IAICanvasBoundingBox = (props: IAICanvasBoundingBoxPreviewProps) => {
x: roundDownToMultiple(newPos.x, scaledStep) + offsetX, x: roundDownToMultiple(newPos.x, scaledStep) + offsetX,
y: roundDownToMultiple(newPos.y, scaledStep) + offsetY, y: roundDownToMultiple(newPos.y, scaledStep) + offsetY,
}; };
console.log({ oldPos, newPos, newCoordinates });
return newCoordinates; return newCoordinates;
}, },

View File

@ -11,6 +11,12 @@ import floorCoordinates from 'features/canvas/util/floorCoordinates';
import getScaledBoundingBoxDimensions from 'features/canvas/util/getScaledBoundingBoxDimensions'; import getScaledBoundingBoxDimensions from 'features/canvas/util/getScaledBoundingBoxDimensions';
import roundDimensionsToMultiple from 'features/canvas/util/roundDimensionsToMultiple'; import roundDimensionsToMultiple from 'features/canvas/util/roundDimensionsToMultiple';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; 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 type { IRect, Vector2d } from 'konva/lib/types';
import { clamp, cloneDeep } from 'lodash-es'; import { clamp, cloneDeep } from 'lodash-es';
import type { RgbaColor } from 'react-colorful'; 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({ export const canvasSlice = createSlice({
name: 'canvas', name: 'canvas',
initialState: initialCanvasState, initialState: initialCanvasState,
@ -132,117 +161,90 @@ export const canvasSlice = createSlice({
state.isMaskEnabled = action.payload; state.isMaskEnabled = action.payload;
state.layer = action.payload ? 'mask' : 'base'; state.layer = action.payload ? 'mask' : 'base';
}, },
setInitialCanvasImage: (state, action: PayloadAction<ImageDTO>) => { setInitialCanvasImage: {
const image = action.payload; reducer: (state, action: PayloadActionWithOptimalDimension<ImageDTO>) => {
const { width, height } = image; const { width, height, image_name } = action.payload;
const { stageDimensions } = state; const { optimalDimension } = action.meta;
const { stageDimensions } = state;
const newBoundingBoxDimensions = { const newBoundingBoxDimensions = {
width: roundDownToMultiple( width: roundDownToMultiple(
clamp(width, CANVAS_GRID_SIZE_FINE, 512), clamp(width, CANVAS_GRID_SIZE_FINE, optimalDimension),
CANVAS_GRID_SIZE_FINE CANVAS_GRID_SIZE_FINE
), ),
height: roundDownToMultiple( height: roundDownToMultiple(
clamp(height, CANVAS_GRID_SIZE_FINE, 512), clamp(height, CANVAS_GRID_SIZE_FINE, optimalDimension),
CANVAS_GRID_SIZE_FINE CANVAS_GRID_SIZE_FINE
), ),
}; };
const newBoundingBoxCoordinates = { const newBoundingBoxCoordinates = {
x: roundToMultiple( x: roundToMultiple(
width / 2 - newBoundingBoxDimensions.width / 2, width / 2 - newBoundingBoxDimensions.width / 2,
CANVAS_GRID_SIZE_FINE CANVAS_GRID_SIZE_FINE
), ),
y: roundToMultiple( y: roundToMultiple(
height / 2 - newBoundingBoxDimensions.height / 2, height / 2 - newBoundingBoxDimensions.height / 2,
CANVAS_GRID_SIZE_FINE CANVAS_GRID_SIZE_FINE
), ),
}; };
if (state.boundingBoxScaleMethod === 'auto') { if (state.boundingBoxScaleMethod === 'auto') {
const scaledDimensions = getScaledBoundingBoxDimensions( const scaledDimensions = getScaledBoundingBoxDimensions(
newBoundingBoxDimensions 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; const newCoordinates = calculateCoordinates(
state.boundingBoxCoordinates = newBoundingBoxCoordinates; stageDimensions.width,
stageDimensions.height,
state.pastLayerStates.push(cloneDeep(state.layerState)); 0,
0,
state.layerState = { width,
...cloneDeep(initialLayerState), height,
objects: [ newScale
{ );
kind: 'image', state.stageScale = newScale;
layer: 'base', state.stageCoordinates = newCoordinates;
x: 0, },
y: 0, prepare: (payload: ImageDTO, optimalDimension: number) => ({
width: width, payload,
height: height, meta: {
imageName: image.image_name, optimalDimension,
},
],
};
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,
}, },
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>) => { setBoundingBoxCoordinates: (state, action: PayloadAction<Vector2d>) => {
state.boundingBoxCoordinates = floorCoordinates(action.payload); state.boundingBoxCoordinates = floorCoordinates(action.payload);
@ -518,38 +520,6 @@ export const canvasSlice = createSlice({
state.stageScale = newScale; state.stageScale = newScale;
state.stageCoordinates = newCoordinates; 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) => { nextStagingAreaImage: (state) => {
@ -601,69 +571,29 @@ export const canvasSlice = createSlice({
state.shouldShowStagingImage = true; state.shouldShowStagingImage = true;
state.batchIds = []; state.batchIds = [];
}, },
fitBoundingBoxToStage: (state) => { setBoundingBoxScaleMethod: {
const { reducer: (
boundingBoxDimensions, state,
boundingBoxCoordinates, action: PayloadActionWithOptimalDimension<BoundingBoxScaleMethod>
stageDimensions, ) => {
stageScale, const boundingBoxScaleMethod = action.payload;
} = state; const { optimalDimension } = action.meta;
const scaledStageWidth = stageDimensions.width / stageScale; state.boundingBoxScaleMethod = boundingBoxScaleMethod;
const scaledStageHeight = stageDimensions.height / stageScale;
if ( if (boundingBoxScaleMethod === 'auto') {
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') {
const scaledDimensions = getScaledBoundingBoxDimensions( const scaledDimensions = getScaledBoundingBoxDimensions(
newBoundingBoxDimensions state.boundingBoxDimensions,
optimalDimension
); );
state.scaledBoundingBoxDimensions = scaledDimensions; state.scaledBoundingBoxDimensions = scaledDimensions;
} }
} },
}, prepare: (payload: BoundingBoxScaleMethod, optimalDimension: number) => ({
setBoundingBoxScaleMethod: ( payload,
state, meta: {
action: PayloadAction<BoundingBoxScaleMethod> optimalDimension,
) => { },
state.boundingBoxScaleMethod = action.payload; }),
if (action.payload === 'auto') {
const scaledDimensions = getScaledBoundingBoxDimensions(
state.boundingBoxDimensions
);
state.scaledBoundingBoxDimensions = scaledDimensions;
}
}, },
setScaledBoundingBoxDimensions: ( setScaledBoundingBoxDimensions: (
state, state,
@ -671,6 +601,37 @@ export const canvasSlice = createSlice({
) => { ) => {
state.scaledBoundingBoxDimensions = action.payload; 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>) => { setShouldShowStagingImage: (state, action: PayloadAction<boolean>) => {
state.shouldShowStagingImage = action.payload; state.shouldShowStagingImage = action.payload;
}, },
@ -714,6 +675,22 @@ export const canvasSlice = createSlice({
}, },
}, },
extraReducers: (builder) => { 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) => { builder.addCase(appSocketQueueItemStatusChanged, (state, action) => {
const batch_status = action.payload.data.batch_status; const batch_status = action.payload.data.batch_status;
if (!state.batchIds.includes(batch_status.batch_id)) { if (!state.batchIds.includes(batch_status.batch_id)) {
@ -754,7 +731,6 @@ export const {
commitColorPickerColor, commitColorPickerColor,
commitStagingAreaImage, commitStagingAreaImage,
discardStagedImages, discardStagedImages,
fitBoundingBoxToStage,
nextStagingAreaImage, nextStagingAreaImage,
prevStagingAreaImage, prevStagingAreaImage,
redo, redo,
@ -764,7 +740,6 @@ export const {
setBoundingBoxDimensions, setBoundingBoxDimensions,
setBoundingBoxPreviewFill, setBoundingBoxPreviewFill,
setBoundingBoxScaleMethod, setBoundingBoxScaleMethod,
flipBoundingBoxAxes,
setBrushColor, setBrushColor,
setBrushSize, setBrushSize,
setColorPickerColor, setColorPickerColor,
@ -799,6 +774,7 @@ export const {
canvasBatchIdAdded, canvasBatchIdAdded,
canvasBatchIdsReset, canvasBatchIdsReset,
aspectRatioChanged, aspectRatioChanged,
scaledBoundingBoxDimensionsReset,
} = canvasSlice.actions; } = canvasSlice.actions;
export default canvasSlice.reducer; export default canvasSlice.reducer;

View File

@ -2,19 +2,22 @@ import { roundToMultiple } from 'common/util/roundDownToMultiple';
import type { Dimensions } from 'features/canvas/store/canvasTypes'; import type { Dimensions } from 'features/canvas/store/canvasTypes';
import { CANVAS_GRID_SIZE_FINE } from 'features/canvas/store/constants'; 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 { width, height } = dimensions;
const scaledDimensions = { width, height }; const scaledDimensions = { width, height };
const targetArea = 512 * 512; const targetArea = optimalDimension * optimalDimension;
const aspectRatio = width / height; const aspectRatio = width / height;
let currentArea = width * height; let currentArea = width * height;
let maxDimension = 448; let maxDimension = optimalDimension - CANVAS_GRID_SIZE_FINE;
while (currentArea < targetArea) { while (currentArea < targetArea) {
maxDimension += CANVAS_GRID_SIZE_FINE; maxDimension += CANVAS_GRID_SIZE_FINE;
if (width === height) { if (width === height) {
scaledDimensions.width = 512; scaledDimensions.width = optimalDimension;
scaledDimensions.height = 512; scaledDimensions.height = optimalDimension;
break; break;
} else { } else {
if (aspectRatio > 1) { if (aspectRatio > 1) {

View File

@ -17,6 +17,7 @@ import type {
} from 'features/dnd/types'; } from 'features/dnd/types';
import { import {
heightChanged, heightChanged,
selectOptimalDimension,
widthChanged, widthChanged,
} from 'features/parameters/store/generationSlice'; } from 'features/parameters/store/generationSlice';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
@ -37,8 +38,8 @@ type Props = {
}; };
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
stateSelector, [stateSelector, activeTabNameSelector, selectOptimalDimension],
({ controlAdapters, gallery, system }) => { ({ controlAdapters, gallery, system }, activeTabName, optimalDimension) => {
const { pendingControlImages } = controlAdapters; const { pendingControlImages } = controlAdapters;
const { autoAddBoardId } = gallery; const { autoAddBoardId } = gallery;
const { isConnected } = system; const { isConnected } = system;
@ -47,6 +48,8 @@ const selector = createMemoizedSelector(
pendingControlImages, pendingControlImages,
autoAddBoardId, autoAddBoardId,
isConnected, isConnected,
activeTabName,
optimalDimension,
}; };
} }
); );
@ -55,13 +58,15 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
const controlImageName = useControlAdapterControlImage(id); const controlImageName = useControlAdapterControlImage(id);
const processedControlImageName = useControlAdapterProcessedControlImage(id); const processedControlImageName = useControlAdapterProcessedControlImage(id);
const processorType = useControlAdapterProcessorType(id); const processorType = useControlAdapterProcessorType(id);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const {
const { pendingControlImages, autoAddBoardId, isConnected } = pendingControlImages,
useAppSelector(selector); autoAddBoardId,
const activeTabName = useAppSelector(activeTabNameSelector); isConnected,
activeTabName,
optimalDimension,
} = useAppSelector(selector);
const [isMouseOverImage, setIsMouseOverImage] = useState(false); const [isMouseOverImage, setIsMouseOverImage] = useState(false);
@ -113,16 +118,19 @@ const ControlAdapterImagePreview = ({ isSmall, id }: Props) => {
if (activeTabName === 'unifiedCanvas') { if (activeTabName === 'unifiedCanvas') {
dispatch( dispatch(
setBoundingBoxDimensions({ setBoundingBoxDimensions(
width: controlImage.width, {
height: controlImage.height, width: controlImage.width,
}) height: controlImage.height,
},
optimalDimension
)
); );
} else { } else {
dispatch(widthChanged(controlImage.width)); dispatch(widthChanged(controlImage.width));
dispatch(heightChanged(controlImage.height)); dispatch(heightChanged(controlImage.height));
} }
}, [controlImage, activeTabName, dispatch]); }, [controlImage, activeTabName, dispatch, optimalDimension]);
const handleMouseEnter = useCallback(() => { const handleMouseEnter = useCallback(() => {
setIsMouseOverImage(true); setIsMouseOverImage(true);

View File

@ -2,7 +2,7 @@ import { Flex, Spinner } from '@chakra-ui/react';
import { useStore } from '@nanostores/react'; import { useStore } from '@nanostores/react';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { $customStarUI } from 'app/store/nanostores/customStarUI'; import { $customStarUI } from 'app/store/nanostores/customStarUI';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { InvMenuItem } from 'common/components/InvMenu/InvMenuItem'; import { InvMenuItem } from 'common/components/InvMenu/InvMenuItem';
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard'; import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
@ -17,6 +17,7 @@ import {
} from 'features/gallery/store/actions'; } from 'features/gallery/store/actions';
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters'; import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
import { initialImageSelected } from 'features/parameters/store/actions'; import { initialImageSelected } from 'features/parameters/store/actions';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus'; import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow'; import { useGetAndLoadEmbeddedWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadEmbeddedWorkflow';
@ -49,12 +50,10 @@ type SingleSelectionMenuItemsProps = {
const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => { const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
const { imageDTO } = props; const { imageDTO } = props;
const optimalDimension = useAppSelector(selectOptimalDimension);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const toaster = useAppToaster(); const toaster = useAppToaster();
const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled; const isCanvasEnabled = useFeatureStatus('unifiedCanvas').isFeatureEnabled;
const customStarUi = useStore($customStarUI); const customStarUi = useStore($customStarUI);
@ -115,7 +114,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
flushSync(() => { flushSync(() => {
dispatch(setActiveTab('unifiedCanvas')); dispatch(setActiveTab('unifiedCanvas'));
}); });
dispatch(setInitialCanvasImage(imageDTO)); dispatch(setInitialCanvasImage(imageDTO, optimalDimension));
toaster({ toaster({
title: t('toast.sentToUnifiedCanvas'), title: t('toast.sentToUnifiedCanvas'),
@ -123,7 +122,7 @@ const SingleSelectionMenuItems = (props: SingleSelectionMenuItemsProps) => {
duration: 2500, duration: 2500,
isClosable: true, isClosable: true,
}); });
}, [dispatch, imageDTO, t, toaster]); }, [dispatch, imageDTO, t, toaster, optimalDimension]);
const handleUseAllParameters = useCallback(() => { const handleUseAllParameters = useCallback(() => {
recallAllParameters(metadata); recallAllParameters(metadata);

View File

@ -1,6 +1,7 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import type { RootState } from 'app/store/store'; import type { RootState } from 'app/store/store';
import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { roundToMultiple } from 'common/util/roundDownToMultiple';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import type { import type {
DenoiseLatentsInvocation, DenoiseLatentsInvocation,
Edge, Edge,
@ -66,26 +67,20 @@ function copyConnectionsToDenoiseLatentsHrf(graph: NonNullableGraph): void {
* Adjusts the width and height to maintain the aspect ratio and constrains them by the model's dimension limits, * Adjusts the width and height to maintain the aspect ratio and constrains them by the model's dimension limits,
* rounding down to the nearest multiple of 8. * rounding down to the nearest multiple of 8.
* *
* @param {string} baseModel The base model type, which determines the base dimension used in calculations. * @param {number} optimalDimension The optimal dimension for the base model.
* @param {number} width The current width to be adjusted for HRF. * @param {number} width The current width to be adjusted for HRF.
* @param {number} height The current height to be adjusted for HRF. * @param {number} height The current height to be adjusted for HRF.
* @return {{newWidth: number, newHeight: number}} The new width and height, adjusted and rounded as needed. * @return {{newWidth: number, newHeight: number}} The new width and height, adjusted and rounded as needed.
*/ */
function calculateHrfRes( function calculateHrfRes(
baseModel: string, optimalDimension: number,
width: number, width: number,
height: number height: number
): { newWidth: number; newHeight: number } { ): { newWidth: number; newHeight: number } {
const aspect = width / height; const aspect = width / height;
let dimension;
if (baseModel == 'sdxl') {
dimension = 1024;
} else {
dimension = 512;
}
const minDimension = Math.floor(dimension * 0.5); const minDimension = Math.floor(optimalDimension * 0.5);
const modelArea = dimension * dimension; // Assuming square images for model_area const modelArea = optimalDimension * optimalDimension; // Assuming square images for model_area
let initWidth; let initWidth;
let initHeight; let initHeight;
@ -126,11 +121,9 @@ export const addHrfToGraph = (
const isAutoVae = !vae; const isAutoVae = !vae;
const width = state.generation.width; const width = state.generation.width;
const height = state.generation.height; const height = state.generation.height;
const baseModel = state.generation.model const optimalDimension = selectOptimalDimension(state);
? state.generation.model.base_model
: 'sd1';
const { newWidth: hrfWidth, newHeight: hrfHeight } = calculateHrfRes( const { newWidth: hrfWidth, newHeight: hrfHeight } = calculateHrfRes(
baseModel, optimalDimension,
width, width,
height height
); );

View File

@ -1,5 +1,4 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { InvControl } from 'common/components/InvControl/InvControl'; import { InvControl } from 'common/components/InvControl/InvControl';
import { InvSlider } from 'common/components/InvSlider/InvSlider'; import { InvSlider } from 'common/components/InvSlider/InvSlider';
@ -9,21 +8,15 @@ import {
CANVAS_GRID_SIZE_FINE, CANVAS_GRID_SIZE_FINE,
} from 'features/canvas/store/constants'; } from 'features/canvas/store/constants';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext'; import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[stateSelector, isStagingSelector], [selectOptimalDimension, isStagingSelector],
({ generation }, isStaging) => { (optimalDimension, isStaging) => {
const { model } = generation;
const initial = ['sdxl', 'sdxl-refiner'].includes(
model?.base_model as string
)
? 1024
: 512;
return { return {
initial, initial: optimalDimension,
model,
isStaging, isStaging,
}; };
} }

View File

@ -1,5 +1,4 @@
import { createMemoizedSelector } from 'app/store/createMemoizedSelector'; import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { InvControl } from 'common/components/InvControl/InvControl'; import { InvControl } from 'common/components/InvControl/InvControl';
import { InvSlider } from 'common/components/InvSlider/InvSlider'; import { InvSlider } from 'common/components/InvSlider/InvSlider';
@ -9,21 +8,15 @@ import {
CANVAS_GRID_SIZE_FINE, CANVAS_GRID_SIZE_FINE,
} from 'features/canvas/store/constants'; } from 'features/canvas/store/constants';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext'; import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[stateSelector, isStagingSelector], [selectOptimalDimension, isStagingSelector],
({ generation }, isStaging) => { (optimalDimension, isStaging) => {
const { model } = generation;
const initial = ['sdxl', 'sdxl-refiner'].includes(
model?.base_model as string
)
? 1024
: 512;
return { return {
initial, initial: optimalDimension,
model,
isStaging, isStaging,
}; };
} }

View File

@ -7,6 +7,7 @@ import type {
} from 'common/components/InvSelect/types'; } from 'common/components/InvSelect/types';
import { setBoundingBoxScaleMethod } from 'features/canvas/store/canvasSlice'; import { setBoundingBoxScaleMethod } from 'features/canvas/store/canvasSlice';
import { isBoundingBoxScaleMethod } from 'features/canvas/store/canvasTypes'; import { isBoundingBoxScaleMethod } from 'features/canvas/store/canvasTypes';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -18,20 +19,20 @@ export const OPTIONS: InvSelectOption[] = [
const ParamScaleBeforeProcessing = () => { const ParamScaleBeforeProcessing = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { t } = useTranslation();
const boundingBoxScaleMethod = useAppSelector( const boundingBoxScaleMethod = useAppSelector(
(state) => state.canvas.boundingBoxScaleMethod (state) => state.canvas.boundingBoxScaleMethod
); );
const optimalDimension = useAppSelector(selectOptimalDimension);
const { t } = useTranslation();
const onChange = useCallback<InvSelectOnChange>( const onChange = useCallback<InvSelectOnChange>(
(v) => { (v) => {
if (!isBoundingBoxScaleMethod(v?.value)) { if (!isBoundingBoxScaleMethod(v?.value)) {
return; return;
} }
dispatch(setBoundingBoxScaleMethod(v.value)); dispatch(setBoundingBoxScaleMethod(v.value, optimalDimension));
}, },
[dispatch] [dispatch, optimalDimension]
); );
const value = useMemo( const value = useMemo(

View File

@ -5,18 +5,18 @@ import { InvControl } from 'common/components/InvControl/InvControl';
import { InvSlider } from 'common/components/InvSlider/InvSlider'; import { InvSlider } from 'common/components/InvSlider/InvSlider';
import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { roundToMultiple } from 'common/util/roundDownToMultiple';
import { setScaledBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; import { setScaledBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[stateSelector], [stateSelector, selectOptimalDimension],
({ generation, canvas }) => { ({ canvas }, optimalDimension) => {
const { scaledBoundingBoxDimensions, boundingBoxScaleMethod, aspectRatio } = const { scaledBoundingBoxDimensions, boundingBoxScaleMethod, aspectRatio } =
canvas; canvas;
const { model } = generation;
return { return {
model, optimalDimension,
scaledBoundingBoxDimensions, scaledBoundingBoxDimensions,
isManual: boundingBoxScaleMethod === 'manual', isManual: boundingBoxScaleMethod === 'manual',
aspectRatio, aspectRatio,
@ -26,12 +26,12 @@ const selector = createMemoizedSelector(
const ParamScaledHeight = () => { const ParamScaledHeight = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { model, isManual, scaledBoundingBoxDimensions, aspectRatio } = const {
useAppSelector(selector); isManual,
scaledBoundingBoxDimensions,
const initial = ['sdxl', 'sdxl-refiner'].includes(model?.base_model as string) aspectRatio,
? 1024 optimalDimension,
: 512; } = useAppSelector(selector);
const { t } = useTranslation(); const { t } = useTranslation();
@ -56,7 +56,7 @@ const ParamScaledHeight = () => {
const handleResetScaledHeight = useCallback(() => { const handleResetScaledHeight = useCallback(() => {
let resetWidth = scaledBoundingBoxDimensions.width; let resetWidth = scaledBoundingBoxDimensions.width;
const resetHeight = Math.floor(initial); const resetHeight = Math.floor(optimalDimension);
if (aspectRatio) { if (aspectRatio) {
resetWidth = roundToMultiple(resetHeight * aspectRatio.value, 64); resetWidth = roundToMultiple(resetHeight * aspectRatio.value, 64);
@ -68,7 +68,12 @@ const ParamScaledHeight = () => {
height: resetHeight, height: resetHeight,
}) })
); );
}, [aspectRatio, dispatch, initial, scaledBoundingBoxDimensions.width]); }, [
aspectRatio,
dispatch,
optimalDimension,
scaledBoundingBoxDimensions.width,
]);
return ( return (
<InvControl isDisabled={!isManual} label={t('parameters.scaledHeight')}> <InvControl isDisabled={!isManual} label={t('parameters.scaledHeight')}>

View File

@ -5,18 +5,18 @@ import { InvControl } from 'common/components/InvControl/InvControl';
import { InvSlider } from 'common/components/InvSlider/InvSlider'; import { InvSlider } from 'common/components/InvSlider/InvSlider';
import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { roundToMultiple } from 'common/util/roundDownToMultiple';
import { setScaledBoundingBoxDimensions } from 'features/canvas/store/canvasSlice'; import { setScaledBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[stateSelector], [stateSelector, selectOptimalDimension],
({ canvas, generation }) => { ({ canvas }, optimalDimension) => {
const { boundingBoxScaleMethod, scaledBoundingBoxDimensions, aspectRatio } = const { boundingBoxScaleMethod, scaledBoundingBoxDimensions, aspectRatio } =
canvas; canvas;
const { model } = generation;
return { return {
model, initial: optimalDimension,
scaledBoundingBoxDimensions, scaledBoundingBoxDimensions,
aspectRatio, aspectRatio,
isManual: boundingBoxScaleMethod === 'manual', isManual: boundingBoxScaleMethod === 'manual',
@ -26,13 +26,9 @@ const selector = createMemoizedSelector(
const ParamScaledWidth = () => { const ParamScaledWidth = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { model, isManual, scaledBoundingBoxDimensions, aspectRatio } = const { initial, isManual, scaledBoundingBoxDimensions, aspectRatio } =
useAppSelector(selector); useAppSelector(selector);
const initial = ['sdxl', 'sdxl-refiner'].includes(model?.base_model as string)
? 1024
: 512;
const { t } = useTranslation(); const { t } = useTranslation();
const handleChangeScaledWidth = useCallback( const handleChangeScaledWidth = useCallback(

View File

@ -5,23 +5,17 @@ import { InvControl } from 'common/components/InvControl/InvControl';
import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput'; import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput';
import { InvSlider } from 'common/components/InvSlider/InvSlider'; import { InvSlider } from 'common/components/InvSlider/InvSlider';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext'; import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[stateSelector], [stateSelector, selectOptimalDimension],
({ generation, config }) => { ({ config }, optimalDimension) => {
const { min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.height; const { min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.height;
const { model } = generation;
const initial = ['sdxl', 'sdxl-refiner'].includes(
model?.base_model as string
)
? 1024
: 512;
return { return {
initial, initial: optimalDimension,
min, min,
max: sliderMax, max: sliderMax,
inputMax, inputMax,

View File

@ -5,23 +5,17 @@ import { InvControl } from 'common/components/InvControl/InvControl';
import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput'; import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput';
import { InvSlider } from 'common/components/InvSlider/InvSlider'; import { InvSlider } from 'common/components/InvSlider/InvSlider';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext'; import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const selector = createMemoizedSelector( const selector = createMemoizedSelector(
[stateSelector], [stateSelector, selectOptimalDimension],
({ generation, config }) => { ({ config }, optimalDimension) => {
const { min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.width; const { min, sliderMax, inputMax, fineStep, coarseStep } = config.sd.width;
const { model } = generation;
const initial = ['sdxl', 'sdxl-refiner'].includes(
model?.base_model as string
)
? 1024
: 512;
return { return {
initial, initial: optimalDimension,
min, min,
max: sliderMax, max: sliderMax,
step: coarseStep, step: coarseStep,

View File

@ -1,3 +1,4 @@
import { useAppSelector } from 'app/store/storeHooks';
import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { roundToMultiple } from 'common/util/roundDownToMultiple';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize'; import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
import { import {
@ -8,6 +9,7 @@ import type {
AspectRatioID, AspectRatioID,
AspectRatioState, AspectRatioState,
} from 'features/parameters/components/ImageSize/types'; } from 'features/parameters/components/ImageSize/types';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { createContext, useCallback, useContext, useMemo } from 'react'; import { createContext, useCallback, useContext, useMemo } from 'react';
export type ImageSizeContextInnerValue = { export type ImageSizeContextInnerValue = {
@ -28,7 +30,7 @@ export type ImageSizeContext = {
widthChanged: (width: number) => void; widthChanged: (width: number) => void;
heightChanged: (height: number) => void; heightChanged: (height: number) => void;
isLockedToggled: () => void; isLockedToggled: () => void;
sizeReset: (width: number, height: number) => void; setOptimalSize: () => void;
}; };
export const ImageSizeContext = export const ImageSizeContext =
@ -36,6 +38,7 @@ export const ImageSizeContext =
export const useImageSizeContext = (): ImageSizeContext => { export const useImageSizeContext = (): ImageSizeContext => {
const _ctx = useContext(ImageSizeContext); const _ctx = useContext(ImageSizeContext);
const optimalDimension = useAppSelector(selectOptimalDimension);
if (!_ctx) { if (!_ctx) {
throw new Error( throw new Error(
@ -58,8 +61,7 @@ export const useImageSizeContext = (): ImageSizeContext => {
state.value = ASPECT_RATIO_MAP[state.id].ratio; state.value = ASPECT_RATIO_MAP[state.id].ratio;
const { width, height } = calculateNewSize( const { width, height } = calculateNewSize(
state.value, state.value,
_ctx.width, _ctx.width * _ctx.height
_ctx.height
); );
_ctx.onChangeWidth(width); _ctx.onChangeWidth(width);
_ctx.onChangeHeight(height); _ctx.onChangeHeight(height);
@ -84,8 +86,7 @@ export const useImageSizeContext = (): ImageSizeContext => {
// Else we need to calculate the new size // Else we need to calculate the new size
const { width, height } = calculateNewSize( const { width, height } = calculateNewSize(
state.value, state.value,
_ctx.width, _ctx.width * _ctx.height
_ctx.height
); );
_ctx.onChangeWidth(width); _ctx.onChangeWidth(width);
_ctx.onChangeHeight(height); _ctx.onChangeHeight(height);
@ -141,15 +142,20 @@ export const useImageSizeContext = (): ImageSizeContext => {
_ctx.onChangeAspectRatioState(state); _ctx.onChangeAspectRatioState(state);
}, [_ctx]); }, [_ctx]);
const sizeReset = useCallback( const setOptimalSize = useCallback(() => {
(width: number, height: number) => { if (_ctx.aspectRatioState.isLocked) {
const state = { ...initialAspectRatioState }; const { width, height } = calculateNewSize(
_ctx.onChangeAspectRatioState(state); _ctx.aspectRatioState.value,
optimalDimension * optimalDimension
);
_ctx.onChangeWidth(width); _ctx.onChangeWidth(width);
_ctx.onChangeHeight(height); _ctx.onChangeHeight(height);
}, } else {
[_ctx] _ctx.onChangeAspectRatioState({ ...initialAspectRatioState });
); _ctx.onChangeWidth(optimalDimension);
_ctx.onChangeHeight(optimalDimension);
}
}, [_ctx, optimalDimension]);
const ctx = useMemo( const ctx = useMemo(
() => ({ () => ({
@ -159,7 +165,7 @@ export const useImageSizeContext = (): ImageSizeContext => {
widthChanged, widthChanged,
heightChanged, heightChanged,
isLockedToggled, isLockedToggled,
sizeReset, setOptimalSize,
}), }),
[ [
_ctx, _ctx,
@ -167,7 +173,7 @@ export const useImageSizeContext = (): ImageSizeContext => {
dimensionsSwapped, dimensionsSwapped,
heightChanged, heightChanged,
isLockedToggled, isLockedToggled,
sizeReset, setOptimalSize,
widthChanged, widthChanged,
] ]
); );

View File

@ -13,6 +13,7 @@ export const LockAspectRatioButton = memo(() => {
return ( return (
<InvIconButton <InvIconButton
tooltip={t('parameters.lockAspectRatio')}
aria-label={t('parameters.lockAspectRatio')} aria-label={t('parameters.lockAspectRatio')}
onClick={onClick} onClick={onClick}
variant={ctx.aspectRatioState.isLocked ? 'outline' : 'ghost'} variant={ctx.aspectRatioState.isLocked ? 'outline' : 'ghost'}

View File

@ -1,27 +1,49 @@
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton'; import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext'; import { useImageSizeContext } from 'features/parameters/components/ImageSize/ImageSizeContext';
import { memo, useCallback } from 'react'; import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import {
getIsSizeTooLarge,
getIsSizeTooSmall,
} from 'features/parameters/util/optimalDimension';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { IoSparkles } from 'react-icons/io5'; import { IoSparkles } from 'react-icons/io5';
export const SetOptimalSizeButton = memo(() => { export const SetOptimalSizeButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const ctx = useImageSizeContext(); const ctx = useImageSizeContext();
const optimalDimension = useAppSelector((state) => const optimalDimension = useAppSelector(selectOptimalDimension);
state.generation.model?.base_model === 'sdxl' ? 1024 : 512 const isSizeTooSmall = useMemo(
() => getIsSizeTooSmall(ctx.width, ctx.height, optimalDimension),
[ctx.height, ctx.width, optimalDimension]
);
const isSizeTooLarge = useMemo(
() => getIsSizeTooLarge(ctx.width, ctx.height, optimalDimension),
[ctx.height, ctx.width, optimalDimension]
); );
const onClick = useCallback(() => { const onClick = useCallback(() => {
ctx.sizeReset(optimalDimension, optimalDimension); ctx.setOptimalSize();
}, [ctx, optimalDimension]); }, [ctx]);
const tooltip = useMemo(() => {
if (isSizeTooSmall) {
return t('parameters.setToOptimalSizeTooSmall');
}
if (isSizeTooLarge) {
return t('parameters.setToOptimalSizeTooLarge');
}
return t('parameters.setToOptimalSize');
}, [isSizeTooLarge, isSizeTooSmall, t]);
return ( return (
<InvIconButton <InvIconButton
aria-label={t('parameters.lockAspectRatio')} tooltip={tooltip}
aria-label={t('parameters.setToOptimalSize')}
onClick={onClick} onClick={onClick}
variant="ghost" variant="ghost"
size="sm" size="sm"
icon={<IoSparkles />} icon={<IoSparkles />}
colorScheme={isSizeTooSmall || isSizeTooLarge ? 'warning' : 'base'}
/> />
); );
}); });

View File

@ -12,6 +12,7 @@ export const SwapDimensionsButton = memo(() => {
}, [ctx]); }, [ctx]);
return ( return (
<InvIconButton <InvIconButton
tooltip={t('parameters.swapDimensions')}
aria-label={t('parameters.swapDimensions')} aria-label={t('parameters.swapDimensions')}
onClick={onClick} onClick={onClick}
variant="ghost" variant="ghost"

View File

@ -3,17 +3,18 @@ import { roundToMultiple } from 'common/util/roundDownToMultiple';
/** /**
* Calculate the new width and height that will fit the given aspect ratio, retaining the input area * Calculate the new width and height that will fit the given aspect ratio, retaining the input area
* @param ratio The aspect ratio to calculate the new size for * @param ratio The aspect ratio to calculate the new size for
* @param width The input width * @param area The input area
* @param height The input height
* @returns The width and height that will fit the given aspect ratio, retaining the input area * @returns The width and height that will fit the given aspect ratio, retaining the input area
*/ */
export const calculateNewSize = ( export const calculateNewSize = (
ratio: number, ratio: number,
width: number, area: number
height: number
): { width: number; height: number } => { ): { width: number; height: number } => {
const area = width * height; const exactWidth = Math.sqrt(area * ratio);
const newWidth = roundToMultiple(Math.sqrt(area * ratio), 8); const exactHeight = exactWidth / ratio;
const newHeight = roundToMultiple(area / newWidth, 8);
return { width: newWidth, height: newHeight }; return {
width: roundToMultiple(exactWidth, 8),
height: roundToMultiple(exactHeight, 8),
};
}; };

View File

@ -1,8 +1,9 @@
import { skipToken } from '@reduxjs/toolkit/query'; import { skipToken } from '@reduxjs/toolkit/query';
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice';
import { initialImageSelected } from 'features/parameters/store/actions'; import { initialImageSelected } from 'features/parameters/store/actions';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { setActiveTab } from 'features/ui/store/uiSlice'; import { setActiveTab } from 'features/ui/store/uiSlice';
import { t } from 'i18next'; import { t } from 'i18next';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
@ -18,8 +19,8 @@ export const usePreselectedImage = (selectedImage?: {
action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters'; action: 'sendToImg2Img' | 'sendToCanvas' | 'useAllParameters';
}) => { }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { recallAllParameters } = useRecallParameters(); const { recallAllParameters } = useRecallParameters();
const optimalDimension = useAppSelector(selectOptimalDimension);
const toaster = useAppToaster(); const toaster = useAppToaster();
const { currentData: selectedImageDto } = useGetImageDTOQuery( const { currentData: selectedImageDto } = useGetImageDTOQuery(
@ -32,7 +33,7 @@ export const usePreselectedImage = (selectedImage?: {
const handleSendToCanvas = useCallback(() => { const handleSendToCanvas = useCallback(() => {
if (selectedImageDto) { if (selectedImageDto) {
dispatch(setInitialCanvasImage(selectedImageDto)); dispatch(setInitialCanvasImage(selectedImageDto, optimalDimension));
dispatch(setActiveTab('unifiedCanvas')); dispatch(setActiveTab('unifiedCanvas'));
toaster({ toaster({
title: t('toast.sentToUnifiedCanvas'), title: t('toast.sentToUnifiedCanvas'),
@ -41,7 +42,7 @@ export const usePreselectedImage = (selectedImage?: {
isClosable: true, isClosable: true,
}); });
} }
}, [dispatch, toaster, selectedImageDto]); }, [selectedImageDto, dispatch, optimalDimension, toaster]);
const handleSendToImg2Img = useCallback(() => { const handleSendToImg2Img = useCallback(() => {
if (selectedImageDto) { if (selectedImageDto) {

View File

@ -2,6 +2,7 @@ import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
import { roundToMultiple } from 'common/util/roundDownToMultiple'; import { roundToMultiple } from 'common/util/roundDownToMultiple';
import { isAnyControlAdapterAdded } from 'features/controlAdapters/store/controlAdaptersSlice'; import { isAnyControlAdapterAdded } from 'features/controlAdapters/store/controlAdaptersSlice';
import { calculateNewSize } from 'features/parameters/components/ImageSize/calculateNewSize';
import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants'; import { initialAspectRatioState } from 'features/parameters/components/ImageSize/constants';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
import { CLIP_SKIP_MAP } from 'features/parameters/types/constants'; import { CLIP_SKIP_MAP } from 'features/parameters/types/constants';
@ -16,6 +17,10 @@ import type {
ParameterVAEModel, ParameterVAEModel,
} from 'features/parameters/types/parameterSchemas'; } from 'features/parameters/types/parameterSchemas';
import { zParameterModel } from 'features/parameters/types/parameterSchemas'; import { zParameterModel } from 'features/parameters/types/parameterSchemas';
import {
getIsSizeOptimal,
getOptimalDimension,
} from 'features/parameters/util/optimalDimension';
import { configChanged } from 'features/system/store/configSlice'; import { configChanged } from 'features/system/store/configSlice';
import { clamp } from 'lodash-es'; import { clamp } from 'lodash-es';
import type { ImageDTO } from 'services/api/types'; import type { ImageDTO } from 'services/api/types';
@ -198,15 +203,26 @@ export const generationSlice = createSlice({
state.initialImage = { imageName: image_name, width, height }; state.initialImage = { imageName: image_name, width, height };
}, },
modelChanged: (state, action: PayloadAction<ParameterModel | null>) => { modelChanged: (state, action: PayloadAction<ParameterModel | null>) => {
state.model = action.payload; const newModel = action.payload;
state.model = newModel;
if (state.model === null) { if (newModel === null) {
return; return;
} }
// Clamp ClipSkip Based On Selected Model // Clamp ClipSkip Based On Selected Model
const { maxClip } = CLIP_SKIP_MAP[state.model.base_model]; const { maxClip } = CLIP_SKIP_MAP[newModel.base_model];
state.clipSkip = clamp(state.clipSkip, 0, maxClip); state.clipSkip = clamp(state.clipSkip, 0, maxClip);
const optimalDimension = getOptimalDimension(newModel);
if (getIsSizeOptimal(state.width, state.height, optimalDimension)) {
return;
}
const { width, height } = calculateNewSize(
state.aspectRatio.value,
optimalDimension * optimalDimension
);
state.width = width;
state.height = height;
}, },
vaeSelected: (state, action: PayloadAction<ParameterVAEModel | null>) => { vaeSelected: (state, action: PayloadAction<ParameterVAEModel | null>) => {
// null is a valid VAE! // null is a valid VAE!
@ -259,6 +275,9 @@ export const generationSlice = createSlice({
} }
}); });
}, },
selectors: {
selectOptimalDimension: (slice) => getOptimalDimension(slice.model),
},
}); });
export const { export const {
@ -306,4 +325,6 @@ export const {
heightChanged, heightChanged,
} = generationSlice.actions; } = generationSlice.actions;
export const { selectOptimalDimension } = generationSlice.selectors;
export default generationSlice.reducer; export default generationSlice.reducer;

View File

@ -1,3 +1,4 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
import type { import type {
ParameterCanvasCoherenceMode, ParameterCanvasCoherenceMode,
@ -58,3 +59,9 @@ export interface GenerationState {
shouldShowAdvancedOptions: boolean; shouldShowAdvancedOptions: boolean;
aspectRatio: AspectRatioState; aspectRatio: AspectRatioState;
} }
export type PayloadActionWithOptimalDimension<T = void> = PayloadAction<
T,
string,
{ optimalDimension: number }
>;

View File

@ -0,0 +1,57 @@
import type { ModelIdentifier } from 'features/nodes/types/common';
/**
* Gets the optimal dimension for a givel model, based on the model's base_model
* @param model The model identifier
* @returns The optimal dimension for the model
*/
export const getOptimalDimension = (model?: ModelIdentifier | null): number =>
model?.base_model === 'sdxl' ? 1024 : 512;
const MIN_AREA_FACTOR = 0.8;
const MAX_AREA_FACTOR = 1.2;
export const getIsSizeTooSmall = (
width: number,
height: number,
optimalDimension: number
): boolean => {
const currentArea = width * height;
const optimalArea = optimalDimension * optimalDimension;
if (currentArea < optimalArea * MIN_AREA_FACTOR) {
return true;
}
return false;
};
export const getIsSizeTooLarge = (
width: number,
height: number,
optimalDimension: number
): boolean => {
const currentArea = width * height;
const optimalArea = optimalDimension * optimalDimension;
if (currentArea > optimalArea * MAX_AREA_FACTOR) {
return true;
}
return false;
};
/**
* Gets whether the current width and height needs to be resized to the optimal dimension.
* The current width and height needs to be resized if the current area is not within 20% of the optimal area.
* @param width The width to compare with the optimal dimension
* @param height The height to compare with the optimal dimension
* @param optimalDimension The optimal dimension
* @returns Whether the current width and height needs to be resized to the optimal dimension
*/
export const getIsSizeOptimal = (
width: number,
height: number,
optimalDimension: number
): boolean => {
return (
!getIsSizeTooSmall(width, height, optimalDimension) &&
!getIsSizeTooLarge(width, height, optimalDimension)
);
};

View File

@ -7,6 +7,7 @@ import ParamBoundingBoxHeight from 'features/parameters/components/Canvas/Boundi
import ParamBoundingBoxWidth from 'features/parameters/components/Canvas/BoundingBox/ParamBoundingBoxWidth'; import ParamBoundingBoxWidth from 'features/parameters/components/Canvas/BoundingBox/ParamBoundingBoxWidth';
import { ImageSize } from 'features/parameters/components/ImageSize/ImageSize'; import { ImageSize } from 'features/parameters/components/ImageSize/ImageSize';
import type { AspectRatioState } from 'features/parameters/components/ImageSize/types'; import type { AspectRatioState } from 'features/parameters/components/ImageSize/types';
import { selectOptimalDimension } from 'features/parameters/store/generationSlice';
import { memo, useCallback } from 'react'; import { memo, useCallback } from 'react';
export const ImageSizeCanvas = memo(() => { export const ImageSizeCanvas = memo(() => {
@ -15,19 +16,20 @@ export const ImageSizeCanvas = memo(() => {
(state) => state.canvas.boundingBoxDimensions (state) => state.canvas.boundingBoxDimensions
); );
const aspectRatioState = useAppSelector((state) => state.canvas.aspectRatio); const aspectRatioState = useAppSelector((state) => state.canvas.aspectRatio);
const optimalDimension = useAppSelector(selectOptimalDimension);
const onChangeWidth = useCallback( const onChangeWidth = useCallback(
(width: number) => { (width: number) => {
dispatch(setBoundingBoxDimensions({ width })); dispatch(setBoundingBoxDimensions({ width }, optimalDimension));
}, },
[dispatch] [dispatch, optimalDimension]
); );
const onChangeHeight = useCallback( const onChangeHeight = useCallback(
(height: number) => { (height: number) => {
dispatch(setBoundingBoxDimensions({ height })); dispatch(setBoundingBoxDimensions({ height }, optimalDimension));
}, },
[dispatch] [dispatch, optimalDimension]
); );
const onChangeAspectRatioState = useCallback( const onChangeAspectRatioState = useCallback(

View File

@ -27,10 +27,7 @@ import {
shouldUseNSFWCheckerChanged, shouldUseNSFWCheckerChanged,
shouldUseWatermarkerChanged, shouldUseWatermarkerChanged,
} from 'features/system/store/systemSlice'; } from 'features/system/store/systemSlice';
import { import { setShouldShowProgressInViewer } from 'features/ui/store/uiSlice';
setShouldAutoChangeDimensions,
setShouldShowProgressInViewer,
} from 'features/ui/store/uiSlice';
import type { ChangeEvent, ReactElement } from 'react'; import type { ChangeEvent, ReactElement } from 'react';
import { cloneElement, memo, useCallback, useEffect, useState } from 'react'; import { cloneElement, memo, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -54,7 +51,7 @@ const selector = createMemoizedSelector(
shouldEnableInformationalPopovers, shouldEnableInformationalPopovers,
} = system; } = system;
const { shouldUseCpuNoise } = generation; const { shouldUseCpuNoise } = generation;
const { shouldShowProgressInViewer, shouldAutoChangeDimensions } = ui; const { shouldShowProgressInViewer } = ui;
return { return {
shouldUseCpuNoise, shouldUseCpuNoise,
@ -65,7 +62,6 @@ const selector = createMemoizedSelector(
shouldAntialiasProgressImage, shouldAntialiasProgressImage,
shouldUseNSFWChecker, shouldUseNSFWChecker,
shouldUseWatermarker, shouldUseWatermarker,
shouldAutoChangeDimensions,
shouldEnableInformationalPopovers, shouldEnableInformationalPopovers,
}; };
} }
@ -135,7 +131,6 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
shouldAntialiasProgressImage, shouldAntialiasProgressImage,
shouldUseNSFWChecker, shouldUseNSFWChecker,
shouldUseWatermarker, shouldUseWatermarker,
shouldAutoChangeDimensions,
shouldEnableInformationalPopovers, shouldEnableInformationalPopovers,
} = useAppSelector(selector); } = useAppSelector(selector);
@ -191,12 +186,6 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
}, },
[dispatch] [dispatch]
); );
const handleChangeShouldAutoChangeDimensions = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(setShouldAutoChangeDimensions(e.target.checked));
},
[dispatch]
);
const handleChangeShouldEnableInformationalPopovers = useCallback( const handleChangeShouldEnableInformationalPopovers = useCallback(
(e: ChangeEvent<HTMLInputElement>) => { (e: ChangeEvent<HTMLInputElement>) => {
dispatch(setShouldEnableInformationalPopovers(e.target.checked)); dispatch(setShouldEnableInformationalPopovers(e.target.checked));
@ -280,12 +269,6 @@ const SettingsModal = ({ children, config }: SettingsModalProps) => {
onChange={handleChangeShouldAntialiasProgressImage} onChange={handleChangeShouldAntialiasProgressImage}
/> />
</InvControl> </InvControl>
<InvControl label={t('settings.autoChangeDimensions')}>
<InvSwitch
isChecked={shouldAutoChangeDimensions}
onChange={handleChangeShouldAutoChangeDimensions}
/>
</InvControl>
<InvControl <InvControl
label={t('parameters.useCpuNoise')} label={t('parameters.useCpuNoise')}
feature="noiseUseCPU" feature="noiseUseCPU"

View File

@ -11,7 +11,6 @@ export const initialUIState: UIState = {
shouldShowExistingModelsInSearch: false, shouldShowExistingModelsInSearch: false,
shouldHidePreview: false, shouldHidePreview: false,
shouldShowProgressInViewer: true, shouldShowProgressInViewer: true,
shouldAutoChangeDimensions: false,
panels: {}, panels: {},
}; };
@ -37,9 +36,6 @@ export const uiSlice = createSlice({
setShouldShowProgressInViewer: (state, action: PayloadAction<boolean>) => { setShouldShowProgressInViewer: (state, action: PayloadAction<boolean>) => {
state.shouldShowProgressInViewer = action.payload; state.shouldShowProgressInViewer = action.payload;
}, },
setShouldAutoChangeDimensions: (state, action: PayloadAction<boolean>) => {
state.shouldAutoChangeDimensions = action.payload;
},
panelsChanged: ( panelsChanged: (
state, state,
action: PayloadAction<{ name: string; value: string }> action: PayloadAction<{ name: string; value: string }>
@ -60,7 +56,6 @@ export const {
setShouldShowExistingModelsInSearch, setShouldShowExistingModelsInSearch,
setShouldHidePreview, setShouldHidePreview,
setShouldShowProgressInViewer, setShouldShowProgressInViewer,
setShouldAutoChangeDimensions,
panelsChanged, panelsChanged,
} = uiSlice.actions; } = uiSlice.actions;

View File

@ -1,23 +1,10 @@
import type { InvokeTabName } from './tabMap'; import type { InvokeTabName } from './tabMap';
export type Coordinates = {
x: number;
y: number;
};
export type Dimensions = {
width: number | string;
height: number | string;
};
export type Rect = Coordinates & Dimensions;
export interface UIState { export interface UIState {
activeTab: InvokeTabName; activeTab: InvokeTabName;
shouldShowImageDetails: boolean; shouldShowImageDetails: boolean;
shouldShowExistingModelsInSearch: boolean; shouldShowExistingModelsInSearch: boolean;
shouldHidePreview: boolean; shouldHidePreview: boolean;
shouldShowProgressInViewer: boolean; shouldShowProgressInViewer: boolean;
shouldAutoChangeDimensions: boolean;
panels: Record<string, string>; panels: Record<string, string>;
} }